##// END OF EJS Templates
rename path_auditor to pathauditor...
Adrian Buehlmann -
r14220:21b8ce4d default
parent child Browse files
Show More
@@ -1,1397 +1,1397 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, glob, tempfile
10 import os, sys, errno, re, glob, tempfile
11 import util, scmutil, templater, patch, error, templatekw
11 import util, scmutil, templater, patch, error, templatekw
12 import match as matchmod
12 import match as matchmod
13 import similar, revset, subrepo
13 import similar, revset, subrepo
14
14
15 revrangesep = ':'
15 revrangesep = ':'
16
16
17 def parsealiases(cmd):
17 def parsealiases(cmd):
18 return cmd.lstrip("^").split("|")
18 return cmd.lstrip("^").split("|")
19
19
20 def findpossible(cmd, table, strict=False):
20 def findpossible(cmd, table, strict=False):
21 """
21 """
22 Return cmd -> (aliases, command table entry)
22 Return cmd -> (aliases, command table entry)
23 for each matching command.
23 for each matching command.
24 Return debug commands (or their aliases) only if no normal command matches.
24 Return debug commands (or their aliases) only if no normal command matches.
25 """
25 """
26 choice = {}
26 choice = {}
27 debugchoice = {}
27 debugchoice = {}
28 for e in table.keys():
28 for e in table.keys():
29 aliases = parsealiases(e)
29 aliases = parsealiases(e)
30 found = None
30 found = None
31 if cmd in aliases:
31 if cmd in aliases:
32 found = cmd
32 found = cmd
33 elif not strict:
33 elif not strict:
34 for a in aliases:
34 for a in aliases:
35 if a.startswith(cmd):
35 if a.startswith(cmd):
36 found = a
36 found = a
37 break
37 break
38 if found is not None:
38 if found is not None:
39 if aliases[0].startswith("debug") or found.startswith("debug"):
39 if aliases[0].startswith("debug") or found.startswith("debug"):
40 debugchoice[found] = (aliases, table[e])
40 debugchoice[found] = (aliases, table[e])
41 else:
41 else:
42 choice[found] = (aliases, table[e])
42 choice[found] = (aliases, table[e])
43
43
44 if not choice and debugchoice:
44 if not choice and debugchoice:
45 choice = debugchoice
45 choice = debugchoice
46
46
47 return choice
47 return choice
48
48
49 def findcmd(cmd, table, strict=True):
49 def findcmd(cmd, table, strict=True):
50 """Return (aliases, command table entry) for command string."""
50 """Return (aliases, command table entry) for command string."""
51 choice = findpossible(cmd, table, strict)
51 choice = findpossible(cmd, table, strict)
52
52
53 if cmd in choice:
53 if cmd in choice:
54 return choice[cmd]
54 return choice[cmd]
55
55
56 if len(choice) > 1:
56 if len(choice) > 1:
57 clist = choice.keys()
57 clist = choice.keys()
58 clist.sort()
58 clist.sort()
59 raise error.AmbiguousCommand(cmd, clist)
59 raise error.AmbiguousCommand(cmd, clist)
60
60
61 if choice:
61 if choice:
62 return choice.values()[0]
62 return choice.values()[0]
63
63
64 raise error.UnknownCommand(cmd)
64 raise error.UnknownCommand(cmd)
65
65
66 def findrepo(p):
66 def findrepo(p):
67 while not os.path.isdir(os.path.join(p, ".hg")):
67 while not os.path.isdir(os.path.join(p, ".hg")):
68 oldp, p = p, os.path.dirname(p)
68 oldp, p = p, os.path.dirname(p)
69 if p == oldp:
69 if p == oldp:
70 return None
70 return None
71
71
72 return p
72 return p
73
73
74 def bail_if_changed(repo):
74 def bail_if_changed(repo):
75 if repo.dirstate.p2() != nullid:
75 if repo.dirstate.p2() != nullid:
76 raise util.Abort(_('outstanding uncommitted merge'))
76 raise util.Abort(_('outstanding uncommitted merge'))
77 modified, added, removed, deleted = repo.status()[:4]
77 modified, added, removed, deleted = repo.status()[:4]
78 if modified or added or removed or deleted:
78 if modified or added or removed or deleted:
79 raise util.Abort(_("outstanding uncommitted changes"))
79 raise util.Abort(_("outstanding uncommitted changes"))
80
80
81 def logmessage(opts):
81 def logmessage(opts):
82 """ get the log message according to -m and -l option """
82 """ get the log message according to -m and -l option """
83 message = opts.get('message')
83 message = opts.get('message')
84 logfile = opts.get('logfile')
84 logfile = opts.get('logfile')
85
85
86 if message and logfile:
86 if message and logfile:
87 raise util.Abort(_('options --message and --logfile are mutually '
87 raise util.Abort(_('options --message and --logfile are mutually '
88 'exclusive'))
88 'exclusive'))
89 if not message and logfile:
89 if not message and logfile:
90 try:
90 try:
91 if logfile == '-':
91 if logfile == '-':
92 message = sys.stdin.read()
92 message = sys.stdin.read()
93 else:
93 else:
94 message = util.readfile(logfile)
94 message = util.readfile(logfile)
95 except IOError, inst:
95 except IOError, inst:
96 raise util.Abort(_("can't read commit message '%s': %s") %
96 raise util.Abort(_("can't read commit message '%s': %s") %
97 (logfile, inst.strerror))
97 (logfile, inst.strerror))
98 return message
98 return message
99
99
100 def loglimit(opts):
100 def loglimit(opts):
101 """get the log limit according to option -l/--limit"""
101 """get the log limit according to option -l/--limit"""
102 limit = opts.get('limit')
102 limit = opts.get('limit')
103 if limit:
103 if limit:
104 try:
104 try:
105 limit = int(limit)
105 limit = int(limit)
106 except ValueError:
106 except ValueError:
107 raise util.Abort(_('limit must be a positive integer'))
107 raise util.Abort(_('limit must be a positive integer'))
108 if limit <= 0:
108 if limit <= 0:
109 raise util.Abort(_('limit must be positive'))
109 raise util.Abort(_('limit must be positive'))
110 else:
110 else:
111 limit = None
111 limit = None
112 return limit
112 return limit
113
113
114 def revsingle(repo, revspec, default='.'):
114 def revsingle(repo, revspec, default='.'):
115 if not revspec:
115 if not revspec:
116 return repo[default]
116 return repo[default]
117
117
118 l = revrange(repo, [revspec])
118 l = revrange(repo, [revspec])
119 if len(l) < 1:
119 if len(l) < 1:
120 raise util.Abort(_('empty revision set'))
120 raise util.Abort(_('empty revision set'))
121 return repo[l[-1]]
121 return repo[l[-1]]
122
122
123 def revpair(repo, revs):
123 def revpair(repo, revs):
124 if not revs:
124 if not revs:
125 return repo.dirstate.p1(), None
125 return repo.dirstate.p1(), None
126
126
127 l = revrange(repo, revs)
127 l = revrange(repo, revs)
128
128
129 if len(l) == 0:
129 if len(l) == 0:
130 return repo.dirstate.p1(), None
130 return repo.dirstate.p1(), None
131
131
132 if len(l) == 1:
132 if len(l) == 1:
133 return repo.lookup(l[0]), None
133 return repo.lookup(l[0]), None
134
134
135 return repo.lookup(l[0]), repo.lookup(l[-1])
135 return repo.lookup(l[0]), repo.lookup(l[-1])
136
136
137 def revrange(repo, revs):
137 def revrange(repo, revs):
138 """Yield revision as strings from a list of revision specifications."""
138 """Yield revision as strings from a list of revision specifications."""
139
139
140 def revfix(repo, val, defval):
140 def revfix(repo, val, defval):
141 if not val and val != 0 and defval is not None:
141 if not val and val != 0 and defval is not None:
142 return defval
142 return defval
143 return repo.changelog.rev(repo.lookup(val))
143 return repo.changelog.rev(repo.lookup(val))
144
144
145 seen, l = set(), []
145 seen, l = set(), []
146 for spec in revs:
146 for spec in revs:
147 # attempt to parse old-style ranges first to deal with
147 # attempt to parse old-style ranges first to deal with
148 # things like old-tag which contain query metacharacters
148 # things like old-tag which contain query metacharacters
149 try:
149 try:
150 if isinstance(spec, int):
150 if isinstance(spec, int):
151 seen.add(spec)
151 seen.add(spec)
152 l.append(spec)
152 l.append(spec)
153 continue
153 continue
154
154
155 if revrangesep in spec:
155 if revrangesep in spec:
156 start, end = spec.split(revrangesep, 1)
156 start, end = spec.split(revrangesep, 1)
157 start = revfix(repo, start, 0)
157 start = revfix(repo, start, 0)
158 end = revfix(repo, end, len(repo) - 1)
158 end = revfix(repo, end, len(repo) - 1)
159 step = start > end and -1 or 1
159 step = start > end and -1 or 1
160 for rev in xrange(start, end + step, step):
160 for rev in xrange(start, end + step, step):
161 if rev in seen:
161 if rev in seen:
162 continue
162 continue
163 seen.add(rev)
163 seen.add(rev)
164 l.append(rev)
164 l.append(rev)
165 continue
165 continue
166 elif spec and spec in repo: # single unquoted rev
166 elif spec and spec in repo: # single unquoted rev
167 rev = revfix(repo, spec, None)
167 rev = revfix(repo, spec, None)
168 if rev in seen:
168 if rev in seen:
169 continue
169 continue
170 seen.add(rev)
170 seen.add(rev)
171 l.append(rev)
171 l.append(rev)
172 continue
172 continue
173 except error.RepoLookupError:
173 except error.RepoLookupError:
174 pass
174 pass
175
175
176 # fall through to new-style queries if old-style fails
176 # fall through to new-style queries if old-style fails
177 m = revset.match(repo.ui, spec)
177 m = revset.match(repo.ui, spec)
178 for r in m(repo, range(len(repo))):
178 for r in m(repo, range(len(repo))):
179 if r not in seen:
179 if r not in seen:
180 l.append(r)
180 l.append(r)
181 seen.update(l)
181 seen.update(l)
182
182
183 return l
183 return l
184
184
185 def make_filename(repo, pat, node,
185 def make_filename(repo, pat, node,
186 total=None, seqno=None, revwidth=None, pathname=None):
186 total=None, seqno=None, revwidth=None, pathname=None):
187 node_expander = {
187 node_expander = {
188 'H': lambda: hex(node),
188 'H': lambda: hex(node),
189 'R': lambda: str(repo.changelog.rev(node)),
189 'R': lambda: str(repo.changelog.rev(node)),
190 'h': lambda: short(node),
190 'h': lambda: short(node),
191 }
191 }
192 expander = {
192 expander = {
193 '%': lambda: '%',
193 '%': lambda: '%',
194 'b': lambda: os.path.basename(repo.root),
194 'b': lambda: os.path.basename(repo.root),
195 }
195 }
196
196
197 try:
197 try:
198 if node:
198 if node:
199 expander.update(node_expander)
199 expander.update(node_expander)
200 if node:
200 if node:
201 expander['r'] = (lambda:
201 expander['r'] = (lambda:
202 str(repo.changelog.rev(node)).zfill(revwidth or 0))
202 str(repo.changelog.rev(node)).zfill(revwidth or 0))
203 if total is not None:
203 if total is not None:
204 expander['N'] = lambda: str(total)
204 expander['N'] = lambda: str(total)
205 if seqno is not None:
205 if seqno is not None:
206 expander['n'] = lambda: str(seqno)
206 expander['n'] = lambda: str(seqno)
207 if total is not None and seqno is not None:
207 if total is not None and seqno is not None:
208 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
208 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
209 if pathname is not None:
209 if pathname is not None:
210 expander['s'] = lambda: os.path.basename(pathname)
210 expander['s'] = lambda: os.path.basename(pathname)
211 expander['d'] = lambda: os.path.dirname(pathname) or '.'
211 expander['d'] = lambda: os.path.dirname(pathname) or '.'
212 expander['p'] = lambda: pathname
212 expander['p'] = lambda: pathname
213
213
214 newname = []
214 newname = []
215 patlen = len(pat)
215 patlen = len(pat)
216 i = 0
216 i = 0
217 while i < patlen:
217 while i < patlen:
218 c = pat[i]
218 c = pat[i]
219 if c == '%':
219 if c == '%':
220 i += 1
220 i += 1
221 c = pat[i]
221 c = pat[i]
222 c = expander[c]()
222 c = expander[c]()
223 newname.append(c)
223 newname.append(c)
224 i += 1
224 i += 1
225 return ''.join(newname)
225 return ''.join(newname)
226 except KeyError, inst:
226 except KeyError, inst:
227 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
227 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
228 inst.args[0])
228 inst.args[0])
229
229
230 def make_file(repo, pat, node=None,
230 def make_file(repo, pat, node=None,
231 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
231 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
232
232
233 writable = mode not in ('r', 'rb')
233 writable = mode not in ('r', 'rb')
234
234
235 if not pat or pat == '-':
235 if not pat or pat == '-':
236 fp = writable and sys.stdout or sys.stdin
236 fp = writable and sys.stdout or sys.stdin
237 return os.fdopen(os.dup(fp.fileno()), mode)
237 return os.fdopen(os.dup(fp.fileno()), mode)
238 if hasattr(pat, 'write') and writable:
238 if hasattr(pat, 'write') and writable:
239 return pat
239 return pat
240 if hasattr(pat, 'read') and 'r' in mode:
240 if hasattr(pat, 'read') and 'r' in mode:
241 return pat
241 return pat
242 return open(make_filename(repo, pat, node, total, seqno, revwidth,
242 return open(make_filename(repo, pat, node, total, seqno, revwidth,
243 pathname),
243 pathname),
244 mode)
244 mode)
245
245
246 def expandpats(pats):
246 def expandpats(pats):
247 if not util.expandglobs:
247 if not util.expandglobs:
248 return list(pats)
248 return list(pats)
249 ret = []
249 ret = []
250 for p in pats:
250 for p in pats:
251 kind, name = matchmod._patsplit(p, None)
251 kind, name = matchmod._patsplit(p, None)
252 if kind is None:
252 if kind is None:
253 try:
253 try:
254 globbed = glob.glob(name)
254 globbed = glob.glob(name)
255 except re.error:
255 except re.error:
256 globbed = [name]
256 globbed = [name]
257 if globbed:
257 if globbed:
258 ret.extend(globbed)
258 ret.extend(globbed)
259 continue
259 continue
260 ret.append(p)
260 ret.append(p)
261 return ret
261 return ret
262
262
263 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
263 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
264 if pats == ("",):
264 if pats == ("",):
265 pats = []
265 pats = []
266 if not globbed and default == 'relpath':
266 if not globbed and default == 'relpath':
267 pats = expandpats(pats or [])
267 pats = expandpats(pats or [])
268 m = matchmod.match(repo.root, repo.getcwd(), pats,
268 m = matchmod.match(repo.root, repo.getcwd(), pats,
269 opts.get('include'), opts.get('exclude'), default,
269 opts.get('include'), opts.get('exclude'), default,
270 auditor=repo.auditor)
270 auditor=repo.auditor)
271 def badfn(f, msg):
271 def badfn(f, msg):
272 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
272 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
273 m.bad = badfn
273 m.bad = badfn
274 return m
274 return m
275
275
276 def matchall(repo):
276 def matchall(repo):
277 return matchmod.always(repo.root, repo.getcwd())
277 return matchmod.always(repo.root, repo.getcwd())
278
278
279 def matchfiles(repo, files):
279 def matchfiles(repo, files):
280 return matchmod.exact(repo.root, repo.getcwd(), files)
280 return matchmod.exact(repo.root, repo.getcwd(), files)
281
281
282 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
282 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
283 if dry_run is None:
283 if dry_run is None:
284 dry_run = opts.get('dry_run')
284 dry_run = opts.get('dry_run')
285 if similarity is None:
285 if similarity is None:
286 similarity = float(opts.get('similarity') or 0)
286 similarity = float(opts.get('similarity') or 0)
287 # we'd use status here, except handling of symlinks and ignore is tricky
287 # we'd use status here, except handling of symlinks and ignore is tricky
288 added, unknown, deleted, removed = [], [], [], []
288 added, unknown, deleted, removed = [], [], [], []
289 audit_path = scmutil.path_auditor(repo.root)
289 audit_path = scmutil.pathauditor(repo.root)
290 m = match(repo, pats, opts)
290 m = match(repo, pats, opts)
291 for abs in repo.walk(m):
291 for abs in repo.walk(m):
292 target = repo.wjoin(abs)
292 target = repo.wjoin(abs)
293 good = True
293 good = True
294 try:
294 try:
295 audit_path(abs)
295 audit_path(abs)
296 except (OSError, util.Abort):
296 except (OSError, util.Abort):
297 good = False
297 good = False
298 rel = m.rel(abs)
298 rel = m.rel(abs)
299 exact = m.exact(abs)
299 exact = m.exact(abs)
300 if good and abs not in repo.dirstate:
300 if good and abs not in repo.dirstate:
301 unknown.append(abs)
301 unknown.append(abs)
302 if repo.ui.verbose or not exact:
302 if repo.ui.verbose or not exact:
303 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
303 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
304 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
304 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
305 or (os.path.isdir(target) and not os.path.islink(target))):
305 or (os.path.isdir(target) and not os.path.islink(target))):
306 deleted.append(abs)
306 deleted.append(abs)
307 if repo.ui.verbose or not exact:
307 if repo.ui.verbose or not exact:
308 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
308 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
309 # for finding renames
309 # for finding renames
310 elif repo.dirstate[abs] == 'r':
310 elif repo.dirstate[abs] == 'r':
311 removed.append(abs)
311 removed.append(abs)
312 elif repo.dirstate[abs] == 'a':
312 elif repo.dirstate[abs] == 'a':
313 added.append(abs)
313 added.append(abs)
314 copies = {}
314 copies = {}
315 if similarity > 0:
315 if similarity > 0:
316 for old, new, score in similar.findrenames(repo,
316 for old, new, score in similar.findrenames(repo,
317 added + unknown, removed + deleted, similarity):
317 added + unknown, removed + deleted, similarity):
318 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
318 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
319 repo.ui.status(_('recording removal of %s as rename to %s '
319 repo.ui.status(_('recording removal of %s as rename to %s '
320 '(%d%% similar)\n') %
320 '(%d%% similar)\n') %
321 (m.rel(old), m.rel(new), score * 100))
321 (m.rel(old), m.rel(new), score * 100))
322 copies[new] = old
322 copies[new] = old
323
323
324 if not dry_run:
324 if not dry_run:
325 wctx = repo[None]
325 wctx = repo[None]
326 wlock = repo.wlock()
326 wlock = repo.wlock()
327 try:
327 try:
328 wctx.remove(deleted)
328 wctx.remove(deleted)
329 wctx.add(unknown)
329 wctx.add(unknown)
330 for new, old in copies.iteritems():
330 for new, old in copies.iteritems():
331 wctx.copy(old, new)
331 wctx.copy(old, new)
332 finally:
332 finally:
333 wlock.release()
333 wlock.release()
334
334
335 def updatedir(ui, repo, patches, similarity=0):
335 def updatedir(ui, repo, patches, similarity=0):
336 '''Update dirstate after patch application according to metadata'''
336 '''Update dirstate after patch application according to metadata'''
337 if not patches:
337 if not patches:
338 return
338 return
339 copies = []
339 copies = []
340 removes = set()
340 removes = set()
341 cfiles = patches.keys()
341 cfiles = patches.keys()
342 cwd = repo.getcwd()
342 cwd = repo.getcwd()
343 if cwd:
343 if cwd:
344 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
344 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
345 for f in patches:
345 for f in patches:
346 gp = patches[f]
346 gp = patches[f]
347 if not gp:
347 if not gp:
348 continue
348 continue
349 if gp.op == 'RENAME':
349 if gp.op == 'RENAME':
350 copies.append((gp.oldpath, gp.path))
350 copies.append((gp.oldpath, gp.path))
351 removes.add(gp.oldpath)
351 removes.add(gp.oldpath)
352 elif gp.op == 'COPY':
352 elif gp.op == 'COPY':
353 copies.append((gp.oldpath, gp.path))
353 copies.append((gp.oldpath, gp.path))
354 elif gp.op == 'DELETE':
354 elif gp.op == 'DELETE':
355 removes.add(gp.path)
355 removes.add(gp.path)
356
356
357 wctx = repo[None]
357 wctx = repo[None]
358 for src, dst in copies:
358 for src, dst in copies:
359 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
359 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
360 if (not similarity) and removes:
360 if (not similarity) and removes:
361 wctx.remove(sorted(removes), True)
361 wctx.remove(sorted(removes), True)
362
362
363 for f in patches:
363 for f in patches:
364 gp = patches[f]
364 gp = patches[f]
365 if gp and gp.mode:
365 if gp and gp.mode:
366 islink, isexec = gp.mode
366 islink, isexec = gp.mode
367 dst = repo.wjoin(gp.path)
367 dst = repo.wjoin(gp.path)
368 # patch won't create empty files
368 # patch won't create empty files
369 if gp.op == 'ADD' and not os.path.lexists(dst):
369 if gp.op == 'ADD' and not os.path.lexists(dst):
370 flags = (isexec and 'x' or '') + (islink and 'l' or '')
370 flags = (isexec and 'x' or '') + (islink and 'l' or '')
371 repo.wwrite(gp.path, '', flags)
371 repo.wwrite(gp.path, '', flags)
372 util.set_flags(dst, islink, isexec)
372 util.set_flags(dst, islink, isexec)
373 addremove(repo, cfiles, similarity=similarity)
373 addremove(repo, cfiles, similarity=similarity)
374 files = patches.keys()
374 files = patches.keys()
375 files.extend([r for r in removes if r not in files])
375 files.extend([r for r in removes if r not in files])
376 return sorted(files)
376 return sorted(files)
377
377
378 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
378 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
379 """Update the dirstate to reflect the intent of copying src to dst. For
379 """Update the dirstate to reflect the intent of copying src to dst. For
380 different reasons it might not end with dst being marked as copied from src.
380 different reasons it might not end with dst being marked as copied from src.
381 """
381 """
382 origsrc = repo.dirstate.copied(src) or src
382 origsrc = repo.dirstate.copied(src) or src
383 if dst == origsrc: # copying back a copy?
383 if dst == origsrc: # copying back a copy?
384 if repo.dirstate[dst] not in 'mn' and not dryrun:
384 if repo.dirstate[dst] not in 'mn' and not dryrun:
385 repo.dirstate.normallookup(dst)
385 repo.dirstate.normallookup(dst)
386 else:
386 else:
387 if repo.dirstate[origsrc] == 'a' and origsrc == src:
387 if repo.dirstate[origsrc] == 'a' and origsrc == src:
388 if not ui.quiet:
388 if not ui.quiet:
389 ui.warn(_("%s has not been committed yet, so no copy "
389 ui.warn(_("%s has not been committed yet, so no copy "
390 "data will be stored for %s.\n")
390 "data will be stored for %s.\n")
391 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
391 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
392 if repo.dirstate[dst] in '?r' and not dryrun:
392 if repo.dirstate[dst] in '?r' and not dryrun:
393 wctx.add([dst])
393 wctx.add([dst])
394 elif not dryrun:
394 elif not dryrun:
395 wctx.copy(origsrc, dst)
395 wctx.copy(origsrc, dst)
396
396
397 def copy(ui, repo, pats, opts, rename=False):
397 def copy(ui, repo, pats, opts, rename=False):
398 # called with the repo lock held
398 # called with the repo lock held
399 #
399 #
400 # hgsep => pathname that uses "/" to separate directories
400 # hgsep => pathname that uses "/" to separate directories
401 # ossep => pathname that uses os.sep to separate directories
401 # ossep => pathname that uses os.sep to separate directories
402 cwd = repo.getcwd()
402 cwd = repo.getcwd()
403 targets = {}
403 targets = {}
404 after = opts.get("after")
404 after = opts.get("after")
405 dryrun = opts.get("dry_run")
405 dryrun = opts.get("dry_run")
406 wctx = repo[None]
406 wctx = repo[None]
407
407
408 def walkpat(pat):
408 def walkpat(pat):
409 srcs = []
409 srcs = []
410 badstates = after and '?' or '?r'
410 badstates = after and '?' or '?r'
411 m = match(repo, [pat], opts, globbed=True)
411 m = match(repo, [pat], opts, globbed=True)
412 for abs in repo.walk(m):
412 for abs in repo.walk(m):
413 state = repo.dirstate[abs]
413 state = repo.dirstate[abs]
414 rel = m.rel(abs)
414 rel = m.rel(abs)
415 exact = m.exact(abs)
415 exact = m.exact(abs)
416 if state in badstates:
416 if state in badstates:
417 if exact and state == '?':
417 if exact and state == '?':
418 ui.warn(_('%s: not copying - file is not managed\n') % rel)
418 ui.warn(_('%s: not copying - file is not managed\n') % rel)
419 if exact and state == 'r':
419 if exact and state == 'r':
420 ui.warn(_('%s: not copying - file has been marked for'
420 ui.warn(_('%s: not copying - file has been marked for'
421 ' remove\n') % rel)
421 ' remove\n') % rel)
422 continue
422 continue
423 # abs: hgsep
423 # abs: hgsep
424 # rel: ossep
424 # rel: ossep
425 srcs.append((abs, rel, exact))
425 srcs.append((abs, rel, exact))
426 return srcs
426 return srcs
427
427
428 # abssrc: hgsep
428 # abssrc: hgsep
429 # relsrc: ossep
429 # relsrc: ossep
430 # otarget: ossep
430 # otarget: ossep
431 def copyfile(abssrc, relsrc, otarget, exact):
431 def copyfile(abssrc, relsrc, otarget, exact):
432 abstarget = scmutil.canonpath(repo.root, cwd, otarget)
432 abstarget = scmutil.canonpath(repo.root, cwd, otarget)
433 reltarget = repo.pathto(abstarget, cwd)
433 reltarget = repo.pathto(abstarget, cwd)
434 target = repo.wjoin(abstarget)
434 target = repo.wjoin(abstarget)
435 src = repo.wjoin(abssrc)
435 src = repo.wjoin(abssrc)
436 state = repo.dirstate[abstarget]
436 state = repo.dirstate[abstarget]
437
437
438 scmutil.checkportable(ui, abstarget)
438 scmutil.checkportable(ui, abstarget)
439
439
440 # check for collisions
440 # check for collisions
441 prevsrc = targets.get(abstarget)
441 prevsrc = targets.get(abstarget)
442 if prevsrc is not None:
442 if prevsrc is not None:
443 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
443 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
444 (reltarget, repo.pathto(abssrc, cwd),
444 (reltarget, repo.pathto(abssrc, cwd),
445 repo.pathto(prevsrc, cwd)))
445 repo.pathto(prevsrc, cwd)))
446 return
446 return
447
447
448 # check for overwrites
448 # check for overwrites
449 exists = os.path.lexists(target)
449 exists = os.path.lexists(target)
450 if not after and exists or after and state in 'mn':
450 if not after and exists or after and state in 'mn':
451 if not opts['force']:
451 if not opts['force']:
452 ui.warn(_('%s: not overwriting - file exists\n') %
452 ui.warn(_('%s: not overwriting - file exists\n') %
453 reltarget)
453 reltarget)
454 return
454 return
455
455
456 if after:
456 if after:
457 if not exists:
457 if not exists:
458 if rename:
458 if rename:
459 ui.warn(_('%s: not recording move - %s does not exist\n') %
459 ui.warn(_('%s: not recording move - %s does not exist\n') %
460 (relsrc, reltarget))
460 (relsrc, reltarget))
461 else:
461 else:
462 ui.warn(_('%s: not recording copy - %s does not exist\n') %
462 ui.warn(_('%s: not recording copy - %s does not exist\n') %
463 (relsrc, reltarget))
463 (relsrc, reltarget))
464 return
464 return
465 elif not dryrun:
465 elif not dryrun:
466 try:
466 try:
467 if exists:
467 if exists:
468 os.unlink(target)
468 os.unlink(target)
469 targetdir = os.path.dirname(target) or '.'
469 targetdir = os.path.dirname(target) or '.'
470 if not os.path.isdir(targetdir):
470 if not os.path.isdir(targetdir):
471 os.makedirs(targetdir)
471 os.makedirs(targetdir)
472 util.copyfile(src, target)
472 util.copyfile(src, target)
473 except IOError, inst:
473 except IOError, inst:
474 if inst.errno == errno.ENOENT:
474 if inst.errno == errno.ENOENT:
475 ui.warn(_('%s: deleted in working copy\n') % relsrc)
475 ui.warn(_('%s: deleted in working copy\n') % relsrc)
476 else:
476 else:
477 ui.warn(_('%s: cannot copy - %s\n') %
477 ui.warn(_('%s: cannot copy - %s\n') %
478 (relsrc, inst.strerror))
478 (relsrc, inst.strerror))
479 return True # report a failure
479 return True # report a failure
480
480
481 if ui.verbose or not exact:
481 if ui.verbose or not exact:
482 if rename:
482 if rename:
483 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
483 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
484 else:
484 else:
485 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
485 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
486
486
487 targets[abstarget] = abssrc
487 targets[abstarget] = abssrc
488
488
489 # fix up dirstate
489 # fix up dirstate
490 dirstatecopy(ui, repo, wctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd)
490 dirstatecopy(ui, repo, wctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd)
491 if rename and not dryrun:
491 if rename and not dryrun:
492 wctx.remove([abssrc], not after)
492 wctx.remove([abssrc], not after)
493
493
494 # pat: ossep
494 # pat: ossep
495 # dest ossep
495 # dest ossep
496 # srcs: list of (hgsep, hgsep, ossep, bool)
496 # srcs: list of (hgsep, hgsep, ossep, bool)
497 # return: function that takes hgsep and returns ossep
497 # return: function that takes hgsep and returns ossep
498 def targetpathfn(pat, dest, srcs):
498 def targetpathfn(pat, dest, srcs):
499 if os.path.isdir(pat):
499 if os.path.isdir(pat):
500 abspfx = scmutil.canonpath(repo.root, cwd, pat)
500 abspfx = scmutil.canonpath(repo.root, cwd, pat)
501 abspfx = util.localpath(abspfx)
501 abspfx = util.localpath(abspfx)
502 if destdirexists:
502 if destdirexists:
503 striplen = len(os.path.split(abspfx)[0])
503 striplen = len(os.path.split(abspfx)[0])
504 else:
504 else:
505 striplen = len(abspfx)
505 striplen = len(abspfx)
506 if striplen:
506 if striplen:
507 striplen += len(os.sep)
507 striplen += len(os.sep)
508 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
508 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
509 elif destdirexists:
509 elif destdirexists:
510 res = lambda p: os.path.join(dest,
510 res = lambda p: os.path.join(dest,
511 os.path.basename(util.localpath(p)))
511 os.path.basename(util.localpath(p)))
512 else:
512 else:
513 res = lambda p: dest
513 res = lambda p: dest
514 return res
514 return res
515
515
516 # pat: ossep
516 # pat: ossep
517 # dest ossep
517 # dest ossep
518 # srcs: list of (hgsep, hgsep, ossep, bool)
518 # srcs: list of (hgsep, hgsep, ossep, bool)
519 # return: function that takes hgsep and returns ossep
519 # return: function that takes hgsep and returns ossep
520 def targetpathafterfn(pat, dest, srcs):
520 def targetpathafterfn(pat, dest, srcs):
521 if matchmod.patkind(pat):
521 if matchmod.patkind(pat):
522 # a mercurial pattern
522 # a mercurial pattern
523 res = lambda p: os.path.join(dest,
523 res = lambda p: os.path.join(dest,
524 os.path.basename(util.localpath(p)))
524 os.path.basename(util.localpath(p)))
525 else:
525 else:
526 abspfx = scmutil.canonpath(repo.root, cwd, pat)
526 abspfx = scmutil.canonpath(repo.root, cwd, pat)
527 if len(abspfx) < len(srcs[0][0]):
527 if len(abspfx) < len(srcs[0][0]):
528 # A directory. Either the target path contains the last
528 # A directory. Either the target path contains the last
529 # component of the source path or it does not.
529 # component of the source path or it does not.
530 def evalpath(striplen):
530 def evalpath(striplen):
531 score = 0
531 score = 0
532 for s in srcs:
532 for s in srcs:
533 t = os.path.join(dest, util.localpath(s[0])[striplen:])
533 t = os.path.join(dest, util.localpath(s[0])[striplen:])
534 if os.path.lexists(t):
534 if os.path.lexists(t):
535 score += 1
535 score += 1
536 return score
536 return score
537
537
538 abspfx = util.localpath(abspfx)
538 abspfx = util.localpath(abspfx)
539 striplen = len(abspfx)
539 striplen = len(abspfx)
540 if striplen:
540 if striplen:
541 striplen += len(os.sep)
541 striplen += len(os.sep)
542 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
542 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
543 score = evalpath(striplen)
543 score = evalpath(striplen)
544 striplen1 = len(os.path.split(abspfx)[0])
544 striplen1 = len(os.path.split(abspfx)[0])
545 if striplen1:
545 if striplen1:
546 striplen1 += len(os.sep)
546 striplen1 += len(os.sep)
547 if evalpath(striplen1) > score:
547 if evalpath(striplen1) > score:
548 striplen = striplen1
548 striplen = striplen1
549 res = lambda p: os.path.join(dest,
549 res = lambda p: os.path.join(dest,
550 util.localpath(p)[striplen:])
550 util.localpath(p)[striplen:])
551 else:
551 else:
552 # a file
552 # a file
553 if destdirexists:
553 if destdirexists:
554 res = lambda p: os.path.join(dest,
554 res = lambda p: os.path.join(dest,
555 os.path.basename(util.localpath(p)))
555 os.path.basename(util.localpath(p)))
556 else:
556 else:
557 res = lambda p: dest
557 res = lambda p: dest
558 return res
558 return res
559
559
560
560
561 pats = expandpats(pats)
561 pats = expandpats(pats)
562 if not pats:
562 if not pats:
563 raise util.Abort(_('no source or destination specified'))
563 raise util.Abort(_('no source or destination specified'))
564 if len(pats) == 1:
564 if len(pats) == 1:
565 raise util.Abort(_('no destination specified'))
565 raise util.Abort(_('no destination specified'))
566 dest = pats.pop()
566 dest = pats.pop()
567 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
567 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
568 if not destdirexists:
568 if not destdirexists:
569 if len(pats) > 1 or matchmod.patkind(pats[0]):
569 if len(pats) > 1 or matchmod.patkind(pats[0]):
570 raise util.Abort(_('with multiple sources, destination must be an '
570 raise util.Abort(_('with multiple sources, destination must be an '
571 'existing directory'))
571 'existing directory'))
572 if util.endswithsep(dest):
572 if util.endswithsep(dest):
573 raise util.Abort(_('destination %s is not a directory') % dest)
573 raise util.Abort(_('destination %s is not a directory') % dest)
574
574
575 tfn = targetpathfn
575 tfn = targetpathfn
576 if after:
576 if after:
577 tfn = targetpathafterfn
577 tfn = targetpathafterfn
578 copylist = []
578 copylist = []
579 for pat in pats:
579 for pat in pats:
580 srcs = walkpat(pat)
580 srcs = walkpat(pat)
581 if not srcs:
581 if not srcs:
582 continue
582 continue
583 copylist.append((tfn(pat, dest, srcs), srcs))
583 copylist.append((tfn(pat, dest, srcs), srcs))
584 if not copylist:
584 if not copylist:
585 raise util.Abort(_('no files to copy'))
585 raise util.Abort(_('no files to copy'))
586
586
587 errors = 0
587 errors = 0
588 for targetpath, srcs in copylist:
588 for targetpath, srcs in copylist:
589 for abssrc, relsrc, exact in srcs:
589 for abssrc, relsrc, exact in srcs:
590 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
590 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
591 errors += 1
591 errors += 1
592
592
593 if errors:
593 if errors:
594 ui.warn(_('(consider using --after)\n'))
594 ui.warn(_('(consider using --after)\n'))
595
595
596 return errors != 0
596 return errors != 0
597
597
598 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
598 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
599 runargs=None, appendpid=False):
599 runargs=None, appendpid=False):
600 '''Run a command as a service.'''
600 '''Run a command as a service.'''
601
601
602 if opts['daemon'] and not opts['daemon_pipefds']:
602 if opts['daemon'] and not opts['daemon_pipefds']:
603 # Signal child process startup with file removal
603 # Signal child process startup with file removal
604 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
604 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
605 os.close(lockfd)
605 os.close(lockfd)
606 try:
606 try:
607 if not runargs:
607 if not runargs:
608 runargs = util.hgcmd() + sys.argv[1:]
608 runargs = util.hgcmd() + sys.argv[1:]
609 runargs.append('--daemon-pipefds=%s' % lockpath)
609 runargs.append('--daemon-pipefds=%s' % lockpath)
610 # Don't pass --cwd to the child process, because we've already
610 # Don't pass --cwd to the child process, because we've already
611 # changed directory.
611 # changed directory.
612 for i in xrange(1, len(runargs)):
612 for i in xrange(1, len(runargs)):
613 if runargs[i].startswith('--cwd='):
613 if runargs[i].startswith('--cwd='):
614 del runargs[i]
614 del runargs[i]
615 break
615 break
616 elif runargs[i].startswith('--cwd'):
616 elif runargs[i].startswith('--cwd'):
617 del runargs[i:i + 2]
617 del runargs[i:i + 2]
618 break
618 break
619 def condfn():
619 def condfn():
620 return not os.path.exists(lockpath)
620 return not os.path.exists(lockpath)
621 pid = util.rundetached(runargs, condfn)
621 pid = util.rundetached(runargs, condfn)
622 if pid < 0:
622 if pid < 0:
623 raise util.Abort(_('child process failed to start'))
623 raise util.Abort(_('child process failed to start'))
624 finally:
624 finally:
625 try:
625 try:
626 os.unlink(lockpath)
626 os.unlink(lockpath)
627 except OSError, e:
627 except OSError, e:
628 if e.errno != errno.ENOENT:
628 if e.errno != errno.ENOENT:
629 raise
629 raise
630 if parentfn:
630 if parentfn:
631 return parentfn(pid)
631 return parentfn(pid)
632 else:
632 else:
633 return
633 return
634
634
635 if initfn:
635 if initfn:
636 initfn()
636 initfn()
637
637
638 if opts['pid_file']:
638 if opts['pid_file']:
639 mode = appendpid and 'a' or 'w'
639 mode = appendpid and 'a' or 'w'
640 fp = open(opts['pid_file'], mode)
640 fp = open(opts['pid_file'], mode)
641 fp.write(str(os.getpid()) + '\n')
641 fp.write(str(os.getpid()) + '\n')
642 fp.close()
642 fp.close()
643
643
644 if opts['daemon_pipefds']:
644 if opts['daemon_pipefds']:
645 lockpath = opts['daemon_pipefds']
645 lockpath = opts['daemon_pipefds']
646 try:
646 try:
647 os.setsid()
647 os.setsid()
648 except AttributeError:
648 except AttributeError:
649 pass
649 pass
650 os.unlink(lockpath)
650 os.unlink(lockpath)
651 util.hidewindow()
651 util.hidewindow()
652 sys.stdout.flush()
652 sys.stdout.flush()
653 sys.stderr.flush()
653 sys.stderr.flush()
654
654
655 nullfd = os.open(util.nulldev, os.O_RDWR)
655 nullfd = os.open(util.nulldev, os.O_RDWR)
656 logfilefd = nullfd
656 logfilefd = nullfd
657 if logfile:
657 if logfile:
658 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
658 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
659 os.dup2(nullfd, 0)
659 os.dup2(nullfd, 0)
660 os.dup2(logfilefd, 1)
660 os.dup2(logfilefd, 1)
661 os.dup2(logfilefd, 2)
661 os.dup2(logfilefd, 2)
662 if nullfd not in (0, 1, 2):
662 if nullfd not in (0, 1, 2):
663 os.close(nullfd)
663 os.close(nullfd)
664 if logfile and logfilefd not in (0, 1, 2):
664 if logfile and logfilefd not in (0, 1, 2):
665 os.close(logfilefd)
665 os.close(logfilefd)
666
666
667 if runfn:
667 if runfn:
668 return runfn()
668 return runfn()
669
669
670 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
670 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
671 opts=None):
671 opts=None):
672 '''export changesets as hg patches.'''
672 '''export changesets as hg patches.'''
673
673
674 total = len(revs)
674 total = len(revs)
675 revwidth = max([len(str(rev)) for rev in revs])
675 revwidth = max([len(str(rev)) for rev in revs])
676
676
677 def single(rev, seqno, fp):
677 def single(rev, seqno, fp):
678 ctx = repo[rev]
678 ctx = repo[rev]
679 node = ctx.node()
679 node = ctx.node()
680 parents = [p.node() for p in ctx.parents() if p]
680 parents = [p.node() for p in ctx.parents() if p]
681 branch = ctx.branch()
681 branch = ctx.branch()
682 if switch_parent:
682 if switch_parent:
683 parents.reverse()
683 parents.reverse()
684 prev = (parents and parents[0]) or nullid
684 prev = (parents and parents[0]) or nullid
685
685
686 shouldclose = False
686 shouldclose = False
687 if not fp:
687 if not fp:
688 fp = make_file(repo, template, node, total=total, seqno=seqno,
688 fp = make_file(repo, template, node, total=total, seqno=seqno,
689 revwidth=revwidth, mode='ab')
689 revwidth=revwidth, mode='ab')
690 if fp != template:
690 if fp != template:
691 shouldclose = True
691 shouldclose = True
692 if fp != sys.stdout and hasattr(fp, 'name'):
692 if fp != sys.stdout and hasattr(fp, 'name'):
693 repo.ui.note("%s\n" % fp.name)
693 repo.ui.note("%s\n" % fp.name)
694
694
695 fp.write("# HG changeset patch\n")
695 fp.write("# HG changeset patch\n")
696 fp.write("# User %s\n" % ctx.user())
696 fp.write("# User %s\n" % ctx.user())
697 fp.write("# Date %d %d\n" % ctx.date())
697 fp.write("# Date %d %d\n" % ctx.date())
698 if branch and branch != 'default':
698 if branch and branch != 'default':
699 fp.write("# Branch %s\n" % branch)
699 fp.write("# Branch %s\n" % branch)
700 fp.write("# Node ID %s\n" % hex(node))
700 fp.write("# Node ID %s\n" % hex(node))
701 fp.write("# Parent %s\n" % hex(prev))
701 fp.write("# Parent %s\n" % hex(prev))
702 if len(parents) > 1:
702 if len(parents) > 1:
703 fp.write("# Parent %s\n" % hex(parents[1]))
703 fp.write("# Parent %s\n" % hex(parents[1]))
704 fp.write(ctx.description().rstrip())
704 fp.write(ctx.description().rstrip())
705 fp.write("\n\n")
705 fp.write("\n\n")
706
706
707 for chunk in patch.diff(repo, prev, node, opts=opts):
707 for chunk in patch.diff(repo, prev, node, opts=opts):
708 fp.write(chunk)
708 fp.write(chunk)
709
709
710 if shouldclose:
710 if shouldclose:
711 fp.close()
711 fp.close()
712
712
713 for seqno, rev in enumerate(revs):
713 for seqno, rev in enumerate(revs):
714 single(rev, seqno + 1, fp)
714 single(rev, seqno + 1, fp)
715
715
716 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
716 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
717 changes=None, stat=False, fp=None, prefix='',
717 changes=None, stat=False, fp=None, prefix='',
718 listsubrepos=False):
718 listsubrepos=False):
719 '''show diff or diffstat.'''
719 '''show diff or diffstat.'''
720 if fp is None:
720 if fp is None:
721 write = ui.write
721 write = ui.write
722 else:
722 else:
723 def write(s, **kw):
723 def write(s, **kw):
724 fp.write(s)
724 fp.write(s)
725
725
726 if stat:
726 if stat:
727 diffopts = diffopts.copy(context=0)
727 diffopts = diffopts.copy(context=0)
728 width = 80
728 width = 80
729 if not ui.plain():
729 if not ui.plain():
730 width = ui.termwidth()
730 width = ui.termwidth()
731 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
731 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
732 prefix=prefix)
732 prefix=prefix)
733 for chunk, label in patch.diffstatui(util.iterlines(chunks),
733 for chunk, label in patch.diffstatui(util.iterlines(chunks),
734 width=width,
734 width=width,
735 git=diffopts.git):
735 git=diffopts.git):
736 write(chunk, label=label)
736 write(chunk, label=label)
737 else:
737 else:
738 for chunk, label in patch.diffui(repo, node1, node2, match,
738 for chunk, label in patch.diffui(repo, node1, node2, match,
739 changes, diffopts, prefix=prefix):
739 changes, diffopts, prefix=prefix):
740 write(chunk, label=label)
740 write(chunk, label=label)
741
741
742 if listsubrepos:
742 if listsubrepos:
743 ctx1 = repo[node1]
743 ctx1 = repo[node1]
744 ctx2 = repo[node2]
744 ctx2 = repo[node2]
745 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
745 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
746 if node2 is not None:
746 if node2 is not None:
747 node2 = ctx2.substate[subpath][1]
747 node2 = ctx2.substate[subpath][1]
748 submatch = matchmod.narrowmatcher(subpath, match)
748 submatch = matchmod.narrowmatcher(subpath, match)
749 sub.diff(diffopts, node2, submatch, changes=changes,
749 sub.diff(diffopts, node2, submatch, changes=changes,
750 stat=stat, fp=fp, prefix=prefix)
750 stat=stat, fp=fp, prefix=prefix)
751
751
752 class changeset_printer(object):
752 class changeset_printer(object):
753 '''show changeset information when templating not requested.'''
753 '''show changeset information when templating not requested.'''
754
754
755 def __init__(self, ui, repo, patch, diffopts, buffered):
755 def __init__(self, ui, repo, patch, diffopts, buffered):
756 self.ui = ui
756 self.ui = ui
757 self.repo = repo
757 self.repo = repo
758 self.buffered = buffered
758 self.buffered = buffered
759 self.patch = patch
759 self.patch = patch
760 self.diffopts = diffopts
760 self.diffopts = diffopts
761 self.header = {}
761 self.header = {}
762 self.hunk = {}
762 self.hunk = {}
763 self.lastheader = None
763 self.lastheader = None
764 self.footer = None
764 self.footer = None
765
765
766 def flush(self, rev):
766 def flush(self, rev):
767 if rev in self.header:
767 if rev in self.header:
768 h = self.header[rev]
768 h = self.header[rev]
769 if h != self.lastheader:
769 if h != self.lastheader:
770 self.lastheader = h
770 self.lastheader = h
771 self.ui.write(h)
771 self.ui.write(h)
772 del self.header[rev]
772 del self.header[rev]
773 if rev in self.hunk:
773 if rev in self.hunk:
774 self.ui.write(self.hunk[rev])
774 self.ui.write(self.hunk[rev])
775 del self.hunk[rev]
775 del self.hunk[rev]
776 return 1
776 return 1
777 return 0
777 return 0
778
778
779 def close(self):
779 def close(self):
780 if self.footer:
780 if self.footer:
781 self.ui.write(self.footer)
781 self.ui.write(self.footer)
782
782
783 def show(self, ctx, copies=None, matchfn=None, **props):
783 def show(self, ctx, copies=None, matchfn=None, **props):
784 if self.buffered:
784 if self.buffered:
785 self.ui.pushbuffer()
785 self.ui.pushbuffer()
786 self._show(ctx, copies, matchfn, props)
786 self._show(ctx, copies, matchfn, props)
787 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
787 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
788 else:
788 else:
789 self._show(ctx, copies, matchfn, props)
789 self._show(ctx, copies, matchfn, props)
790
790
791 def _show(self, ctx, copies, matchfn, props):
791 def _show(self, ctx, copies, matchfn, props):
792 '''show a single changeset or file revision'''
792 '''show a single changeset or file revision'''
793 changenode = ctx.node()
793 changenode = ctx.node()
794 rev = ctx.rev()
794 rev = ctx.rev()
795
795
796 if self.ui.quiet:
796 if self.ui.quiet:
797 self.ui.write("%d:%s\n" % (rev, short(changenode)),
797 self.ui.write("%d:%s\n" % (rev, short(changenode)),
798 label='log.node')
798 label='log.node')
799 return
799 return
800
800
801 log = self.repo.changelog
801 log = self.repo.changelog
802 date = util.datestr(ctx.date())
802 date = util.datestr(ctx.date())
803
803
804 hexfunc = self.ui.debugflag and hex or short
804 hexfunc = self.ui.debugflag and hex or short
805
805
806 parents = [(p, hexfunc(log.node(p)))
806 parents = [(p, hexfunc(log.node(p)))
807 for p in self._meaningful_parentrevs(log, rev)]
807 for p in self._meaningful_parentrevs(log, rev)]
808
808
809 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
809 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
810 label='log.changeset')
810 label='log.changeset')
811
811
812 branch = ctx.branch()
812 branch = ctx.branch()
813 # don't show the default branch name
813 # don't show the default branch name
814 if branch != 'default':
814 if branch != 'default':
815 self.ui.write(_("branch: %s\n") % branch,
815 self.ui.write(_("branch: %s\n") % branch,
816 label='log.branch')
816 label='log.branch')
817 for bookmark in self.repo.nodebookmarks(changenode):
817 for bookmark in self.repo.nodebookmarks(changenode):
818 self.ui.write(_("bookmark: %s\n") % bookmark,
818 self.ui.write(_("bookmark: %s\n") % bookmark,
819 label='log.bookmark')
819 label='log.bookmark')
820 for tag in self.repo.nodetags(changenode):
820 for tag in self.repo.nodetags(changenode):
821 self.ui.write(_("tag: %s\n") % tag,
821 self.ui.write(_("tag: %s\n") % tag,
822 label='log.tag')
822 label='log.tag')
823 for parent in parents:
823 for parent in parents:
824 self.ui.write(_("parent: %d:%s\n") % parent,
824 self.ui.write(_("parent: %d:%s\n") % parent,
825 label='log.parent')
825 label='log.parent')
826
826
827 if self.ui.debugflag:
827 if self.ui.debugflag:
828 mnode = ctx.manifestnode()
828 mnode = ctx.manifestnode()
829 self.ui.write(_("manifest: %d:%s\n") %
829 self.ui.write(_("manifest: %d:%s\n") %
830 (self.repo.manifest.rev(mnode), hex(mnode)),
830 (self.repo.manifest.rev(mnode), hex(mnode)),
831 label='ui.debug log.manifest')
831 label='ui.debug log.manifest')
832 self.ui.write(_("user: %s\n") % ctx.user(),
832 self.ui.write(_("user: %s\n") % ctx.user(),
833 label='log.user')
833 label='log.user')
834 self.ui.write(_("date: %s\n") % date,
834 self.ui.write(_("date: %s\n") % date,
835 label='log.date')
835 label='log.date')
836
836
837 if self.ui.debugflag:
837 if self.ui.debugflag:
838 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
838 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
839 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
839 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
840 files):
840 files):
841 if value:
841 if value:
842 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
842 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
843 label='ui.debug log.files')
843 label='ui.debug log.files')
844 elif ctx.files() and self.ui.verbose:
844 elif ctx.files() and self.ui.verbose:
845 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
845 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
846 label='ui.note log.files')
846 label='ui.note log.files')
847 if copies and self.ui.verbose:
847 if copies and self.ui.verbose:
848 copies = ['%s (%s)' % c for c in copies]
848 copies = ['%s (%s)' % c for c in copies]
849 self.ui.write(_("copies: %s\n") % ' '.join(copies),
849 self.ui.write(_("copies: %s\n") % ' '.join(copies),
850 label='ui.note log.copies')
850 label='ui.note log.copies')
851
851
852 extra = ctx.extra()
852 extra = ctx.extra()
853 if extra and self.ui.debugflag:
853 if extra and self.ui.debugflag:
854 for key, value in sorted(extra.items()):
854 for key, value in sorted(extra.items()):
855 self.ui.write(_("extra: %s=%s\n")
855 self.ui.write(_("extra: %s=%s\n")
856 % (key, value.encode('string_escape')),
856 % (key, value.encode('string_escape')),
857 label='ui.debug log.extra')
857 label='ui.debug log.extra')
858
858
859 description = ctx.description().strip()
859 description = ctx.description().strip()
860 if description:
860 if description:
861 if self.ui.verbose:
861 if self.ui.verbose:
862 self.ui.write(_("description:\n"),
862 self.ui.write(_("description:\n"),
863 label='ui.note log.description')
863 label='ui.note log.description')
864 self.ui.write(description,
864 self.ui.write(description,
865 label='ui.note log.description')
865 label='ui.note log.description')
866 self.ui.write("\n\n")
866 self.ui.write("\n\n")
867 else:
867 else:
868 self.ui.write(_("summary: %s\n") %
868 self.ui.write(_("summary: %s\n") %
869 description.splitlines()[0],
869 description.splitlines()[0],
870 label='log.summary')
870 label='log.summary')
871 self.ui.write("\n")
871 self.ui.write("\n")
872
872
873 self.showpatch(changenode, matchfn)
873 self.showpatch(changenode, matchfn)
874
874
875 def showpatch(self, node, matchfn):
875 def showpatch(self, node, matchfn):
876 if not matchfn:
876 if not matchfn:
877 matchfn = self.patch
877 matchfn = self.patch
878 if matchfn:
878 if matchfn:
879 stat = self.diffopts.get('stat')
879 stat = self.diffopts.get('stat')
880 diff = self.diffopts.get('patch')
880 diff = self.diffopts.get('patch')
881 diffopts = patch.diffopts(self.ui, self.diffopts)
881 diffopts = patch.diffopts(self.ui, self.diffopts)
882 prev = self.repo.changelog.parents(node)[0]
882 prev = self.repo.changelog.parents(node)[0]
883 if stat:
883 if stat:
884 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
884 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
885 match=matchfn, stat=True)
885 match=matchfn, stat=True)
886 if diff:
886 if diff:
887 if stat:
887 if stat:
888 self.ui.write("\n")
888 self.ui.write("\n")
889 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
889 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
890 match=matchfn, stat=False)
890 match=matchfn, stat=False)
891 self.ui.write("\n")
891 self.ui.write("\n")
892
892
893 def _meaningful_parentrevs(self, log, rev):
893 def _meaningful_parentrevs(self, log, rev):
894 """Return list of meaningful (or all if debug) parentrevs for rev.
894 """Return list of meaningful (or all if debug) parentrevs for rev.
895
895
896 For merges (two non-nullrev revisions) both parents are meaningful.
896 For merges (two non-nullrev revisions) both parents are meaningful.
897 Otherwise the first parent revision is considered meaningful if it
897 Otherwise the first parent revision is considered meaningful if it
898 is not the preceding revision.
898 is not the preceding revision.
899 """
899 """
900 parents = log.parentrevs(rev)
900 parents = log.parentrevs(rev)
901 if not self.ui.debugflag and parents[1] == nullrev:
901 if not self.ui.debugflag and parents[1] == nullrev:
902 if parents[0] >= rev - 1:
902 if parents[0] >= rev - 1:
903 parents = []
903 parents = []
904 else:
904 else:
905 parents = [parents[0]]
905 parents = [parents[0]]
906 return parents
906 return parents
907
907
908
908
909 class changeset_templater(changeset_printer):
909 class changeset_templater(changeset_printer):
910 '''format changeset information.'''
910 '''format changeset information.'''
911
911
912 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
912 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
913 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
913 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
914 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
914 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
915 defaulttempl = {
915 defaulttempl = {
916 'parent': '{rev}:{node|formatnode} ',
916 'parent': '{rev}:{node|formatnode} ',
917 'manifest': '{rev}:{node|formatnode}',
917 'manifest': '{rev}:{node|formatnode}',
918 'file_copy': '{name} ({source})',
918 'file_copy': '{name} ({source})',
919 'extra': '{key}={value|stringescape}'
919 'extra': '{key}={value|stringescape}'
920 }
920 }
921 # filecopy is preserved for compatibility reasons
921 # filecopy is preserved for compatibility reasons
922 defaulttempl['filecopy'] = defaulttempl['file_copy']
922 defaulttempl['filecopy'] = defaulttempl['file_copy']
923 self.t = templater.templater(mapfile, {'formatnode': formatnode},
923 self.t = templater.templater(mapfile, {'formatnode': formatnode},
924 cache=defaulttempl)
924 cache=defaulttempl)
925 self.cache = {}
925 self.cache = {}
926
926
927 def use_template(self, t):
927 def use_template(self, t):
928 '''set template string to use'''
928 '''set template string to use'''
929 self.t.cache['changeset'] = t
929 self.t.cache['changeset'] = t
930
930
931 def _meaningful_parentrevs(self, ctx):
931 def _meaningful_parentrevs(self, ctx):
932 """Return list of meaningful (or all if debug) parentrevs for rev.
932 """Return list of meaningful (or all if debug) parentrevs for rev.
933 """
933 """
934 parents = ctx.parents()
934 parents = ctx.parents()
935 if len(parents) > 1:
935 if len(parents) > 1:
936 return parents
936 return parents
937 if self.ui.debugflag:
937 if self.ui.debugflag:
938 return [parents[0], self.repo['null']]
938 return [parents[0], self.repo['null']]
939 if parents[0].rev() >= ctx.rev() - 1:
939 if parents[0].rev() >= ctx.rev() - 1:
940 return []
940 return []
941 return parents
941 return parents
942
942
943 def _show(self, ctx, copies, matchfn, props):
943 def _show(self, ctx, copies, matchfn, props):
944 '''show a single changeset or file revision'''
944 '''show a single changeset or file revision'''
945
945
946 showlist = templatekw.showlist
946 showlist = templatekw.showlist
947
947
948 # showparents() behaviour depends on ui trace level which
948 # showparents() behaviour depends on ui trace level which
949 # causes unexpected behaviours at templating level and makes
949 # causes unexpected behaviours at templating level and makes
950 # it harder to extract it in a standalone function. Its
950 # it harder to extract it in a standalone function. Its
951 # behaviour cannot be changed so leave it here for now.
951 # behaviour cannot be changed so leave it here for now.
952 def showparents(**args):
952 def showparents(**args):
953 ctx = args['ctx']
953 ctx = args['ctx']
954 parents = [[('rev', p.rev()), ('node', p.hex())]
954 parents = [[('rev', p.rev()), ('node', p.hex())]
955 for p in self._meaningful_parentrevs(ctx)]
955 for p in self._meaningful_parentrevs(ctx)]
956 return showlist('parent', parents, **args)
956 return showlist('parent', parents, **args)
957
957
958 props = props.copy()
958 props = props.copy()
959 props.update(templatekw.keywords)
959 props.update(templatekw.keywords)
960 props['parents'] = showparents
960 props['parents'] = showparents
961 props['templ'] = self.t
961 props['templ'] = self.t
962 props['ctx'] = ctx
962 props['ctx'] = ctx
963 props['repo'] = self.repo
963 props['repo'] = self.repo
964 props['revcache'] = {'copies': copies}
964 props['revcache'] = {'copies': copies}
965 props['cache'] = self.cache
965 props['cache'] = self.cache
966
966
967 # find correct templates for current mode
967 # find correct templates for current mode
968
968
969 tmplmodes = [
969 tmplmodes = [
970 (True, None),
970 (True, None),
971 (self.ui.verbose, 'verbose'),
971 (self.ui.verbose, 'verbose'),
972 (self.ui.quiet, 'quiet'),
972 (self.ui.quiet, 'quiet'),
973 (self.ui.debugflag, 'debug'),
973 (self.ui.debugflag, 'debug'),
974 ]
974 ]
975
975
976 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
976 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
977 for mode, postfix in tmplmodes:
977 for mode, postfix in tmplmodes:
978 for type in types:
978 for type in types:
979 cur = postfix and ('%s_%s' % (type, postfix)) or type
979 cur = postfix and ('%s_%s' % (type, postfix)) or type
980 if mode and cur in self.t:
980 if mode and cur in self.t:
981 types[type] = cur
981 types[type] = cur
982
982
983 try:
983 try:
984
984
985 # write header
985 # write header
986 if types['header']:
986 if types['header']:
987 h = templater.stringify(self.t(types['header'], **props))
987 h = templater.stringify(self.t(types['header'], **props))
988 if self.buffered:
988 if self.buffered:
989 self.header[ctx.rev()] = h
989 self.header[ctx.rev()] = h
990 else:
990 else:
991 if self.lastheader != h:
991 if self.lastheader != h:
992 self.lastheader = h
992 self.lastheader = h
993 self.ui.write(h)
993 self.ui.write(h)
994
994
995 # write changeset metadata, then patch if requested
995 # write changeset metadata, then patch if requested
996 key = types['changeset']
996 key = types['changeset']
997 self.ui.write(templater.stringify(self.t(key, **props)))
997 self.ui.write(templater.stringify(self.t(key, **props)))
998 self.showpatch(ctx.node(), matchfn)
998 self.showpatch(ctx.node(), matchfn)
999
999
1000 if types['footer']:
1000 if types['footer']:
1001 if not self.footer:
1001 if not self.footer:
1002 self.footer = templater.stringify(self.t(types['footer'],
1002 self.footer = templater.stringify(self.t(types['footer'],
1003 **props))
1003 **props))
1004
1004
1005 except KeyError, inst:
1005 except KeyError, inst:
1006 msg = _("%s: no key named '%s'")
1006 msg = _("%s: no key named '%s'")
1007 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1007 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1008 except SyntaxError, inst:
1008 except SyntaxError, inst:
1009 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1009 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1010
1010
1011 def show_changeset(ui, repo, opts, buffered=False):
1011 def show_changeset(ui, repo, opts, buffered=False):
1012 """show one changeset using template or regular display.
1012 """show one changeset using template or regular display.
1013
1013
1014 Display format will be the first non-empty hit of:
1014 Display format will be the first non-empty hit of:
1015 1. option 'template'
1015 1. option 'template'
1016 2. option 'style'
1016 2. option 'style'
1017 3. [ui] setting 'logtemplate'
1017 3. [ui] setting 'logtemplate'
1018 4. [ui] setting 'style'
1018 4. [ui] setting 'style'
1019 If all of these values are either the unset or the empty string,
1019 If all of these values are either the unset or the empty string,
1020 regular display via changeset_printer() is done.
1020 regular display via changeset_printer() is done.
1021 """
1021 """
1022 # options
1022 # options
1023 patch = False
1023 patch = False
1024 if opts.get('patch') or opts.get('stat'):
1024 if opts.get('patch') or opts.get('stat'):
1025 patch = matchall(repo)
1025 patch = matchall(repo)
1026
1026
1027 tmpl = opts.get('template')
1027 tmpl = opts.get('template')
1028 style = None
1028 style = None
1029 if tmpl:
1029 if tmpl:
1030 tmpl = templater.parsestring(tmpl, quoted=False)
1030 tmpl = templater.parsestring(tmpl, quoted=False)
1031 else:
1031 else:
1032 style = opts.get('style')
1032 style = opts.get('style')
1033
1033
1034 # ui settings
1034 # ui settings
1035 if not (tmpl or style):
1035 if not (tmpl or style):
1036 tmpl = ui.config('ui', 'logtemplate')
1036 tmpl = ui.config('ui', 'logtemplate')
1037 if tmpl:
1037 if tmpl:
1038 tmpl = templater.parsestring(tmpl)
1038 tmpl = templater.parsestring(tmpl)
1039 else:
1039 else:
1040 style = util.expandpath(ui.config('ui', 'style', ''))
1040 style = util.expandpath(ui.config('ui', 'style', ''))
1041
1041
1042 if not (tmpl or style):
1042 if not (tmpl or style):
1043 return changeset_printer(ui, repo, patch, opts, buffered)
1043 return changeset_printer(ui, repo, patch, opts, buffered)
1044
1044
1045 mapfile = None
1045 mapfile = None
1046 if style and not tmpl:
1046 if style and not tmpl:
1047 mapfile = style
1047 mapfile = style
1048 if not os.path.split(mapfile)[0]:
1048 if not os.path.split(mapfile)[0]:
1049 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1049 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1050 or templater.templatepath(mapfile))
1050 or templater.templatepath(mapfile))
1051 if mapname:
1051 if mapname:
1052 mapfile = mapname
1052 mapfile = mapname
1053
1053
1054 try:
1054 try:
1055 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
1055 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
1056 except SyntaxError, inst:
1056 except SyntaxError, inst:
1057 raise util.Abort(inst.args[0])
1057 raise util.Abort(inst.args[0])
1058 if tmpl:
1058 if tmpl:
1059 t.use_template(tmpl)
1059 t.use_template(tmpl)
1060 return t
1060 return t
1061
1061
1062 def finddate(ui, repo, date):
1062 def finddate(ui, repo, date):
1063 """Find the tipmost changeset that matches the given date spec"""
1063 """Find the tipmost changeset that matches the given date spec"""
1064
1064
1065 df = util.matchdate(date)
1065 df = util.matchdate(date)
1066 m = matchall(repo)
1066 m = matchall(repo)
1067 results = {}
1067 results = {}
1068
1068
1069 def prep(ctx, fns):
1069 def prep(ctx, fns):
1070 d = ctx.date()
1070 d = ctx.date()
1071 if df(d[0]):
1071 if df(d[0]):
1072 results[ctx.rev()] = d
1072 results[ctx.rev()] = d
1073
1073
1074 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1074 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1075 rev = ctx.rev()
1075 rev = ctx.rev()
1076 if rev in results:
1076 if rev in results:
1077 ui.status(_("Found revision %s from %s\n") %
1077 ui.status(_("Found revision %s from %s\n") %
1078 (rev, util.datestr(results[rev])))
1078 (rev, util.datestr(results[rev])))
1079 return str(rev)
1079 return str(rev)
1080
1080
1081 raise util.Abort(_("revision matching date not found"))
1081 raise util.Abort(_("revision matching date not found"))
1082
1082
1083 def walkchangerevs(repo, match, opts, prepare):
1083 def walkchangerevs(repo, match, opts, prepare):
1084 '''Iterate over files and the revs in which they changed.
1084 '''Iterate over files and the revs in which they changed.
1085
1085
1086 Callers most commonly need to iterate backwards over the history
1086 Callers most commonly need to iterate backwards over the history
1087 in which they are interested. Doing so has awful (quadratic-looking)
1087 in which they are interested. Doing so has awful (quadratic-looking)
1088 performance, so we use iterators in a "windowed" way.
1088 performance, so we use iterators in a "windowed" way.
1089
1089
1090 We walk a window of revisions in the desired order. Within the
1090 We walk a window of revisions in the desired order. Within the
1091 window, we first walk forwards to gather data, then in the desired
1091 window, we first walk forwards to gather data, then in the desired
1092 order (usually backwards) to display it.
1092 order (usually backwards) to display it.
1093
1093
1094 This function returns an iterator yielding contexts. Before
1094 This function returns an iterator yielding contexts. Before
1095 yielding each context, the iterator will first call the prepare
1095 yielding each context, the iterator will first call the prepare
1096 function on each context in the window in forward order.'''
1096 function on each context in the window in forward order.'''
1097
1097
1098 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1098 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1099 if start < end:
1099 if start < end:
1100 while start < end:
1100 while start < end:
1101 yield start, min(windowsize, end - start)
1101 yield start, min(windowsize, end - start)
1102 start += windowsize
1102 start += windowsize
1103 if windowsize < sizelimit:
1103 if windowsize < sizelimit:
1104 windowsize *= 2
1104 windowsize *= 2
1105 else:
1105 else:
1106 while start > end:
1106 while start > end:
1107 yield start, min(windowsize, start - end - 1)
1107 yield start, min(windowsize, start - end - 1)
1108 start -= windowsize
1108 start -= windowsize
1109 if windowsize < sizelimit:
1109 if windowsize < sizelimit:
1110 windowsize *= 2
1110 windowsize *= 2
1111
1111
1112 follow = opts.get('follow') or opts.get('follow_first')
1112 follow = opts.get('follow') or opts.get('follow_first')
1113
1113
1114 if not len(repo):
1114 if not len(repo):
1115 return []
1115 return []
1116
1116
1117 if follow:
1117 if follow:
1118 defrange = '%s:0' % repo['.'].rev()
1118 defrange = '%s:0' % repo['.'].rev()
1119 else:
1119 else:
1120 defrange = '-1:0'
1120 defrange = '-1:0'
1121 revs = revrange(repo, opts['rev'] or [defrange])
1121 revs = revrange(repo, opts['rev'] or [defrange])
1122 if not revs:
1122 if not revs:
1123 return []
1123 return []
1124 wanted = set()
1124 wanted = set()
1125 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1125 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1126 fncache = {}
1126 fncache = {}
1127 change = util.cachefunc(repo.changectx)
1127 change = util.cachefunc(repo.changectx)
1128
1128
1129 # First step is to fill wanted, the set of revisions that we want to yield.
1129 # First step is to fill wanted, the set of revisions that we want to yield.
1130 # When it does not induce extra cost, we also fill fncache for revisions in
1130 # When it does not induce extra cost, we also fill fncache for revisions in
1131 # wanted: a cache of filenames that were changed (ctx.files()) and that
1131 # wanted: a cache of filenames that were changed (ctx.files()) and that
1132 # match the file filtering conditions.
1132 # match the file filtering conditions.
1133
1133
1134 if not slowpath and not match.files():
1134 if not slowpath and not match.files():
1135 # No files, no patterns. Display all revs.
1135 # No files, no patterns. Display all revs.
1136 wanted = set(revs)
1136 wanted = set(revs)
1137 copies = []
1137 copies = []
1138
1138
1139 if not slowpath:
1139 if not slowpath:
1140 # We only have to read through the filelog to find wanted revisions
1140 # We only have to read through the filelog to find wanted revisions
1141
1141
1142 minrev, maxrev = min(revs), max(revs)
1142 minrev, maxrev = min(revs), max(revs)
1143 def filerevgen(filelog, last):
1143 def filerevgen(filelog, last):
1144 """
1144 """
1145 Only files, no patterns. Check the history of each file.
1145 Only files, no patterns. Check the history of each file.
1146
1146
1147 Examines filelog entries within minrev, maxrev linkrev range
1147 Examines filelog entries within minrev, maxrev linkrev range
1148 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1148 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1149 tuples in backwards order
1149 tuples in backwards order
1150 """
1150 """
1151 cl_count = len(repo)
1151 cl_count = len(repo)
1152 revs = []
1152 revs = []
1153 for j in xrange(0, last + 1):
1153 for j in xrange(0, last + 1):
1154 linkrev = filelog.linkrev(j)
1154 linkrev = filelog.linkrev(j)
1155 if linkrev < minrev:
1155 if linkrev < minrev:
1156 continue
1156 continue
1157 # only yield rev for which we have the changelog, it can
1157 # only yield rev for which we have the changelog, it can
1158 # happen while doing "hg log" during a pull or commit
1158 # happen while doing "hg log" during a pull or commit
1159 if linkrev >= cl_count:
1159 if linkrev >= cl_count:
1160 break
1160 break
1161
1161
1162 parentlinkrevs = []
1162 parentlinkrevs = []
1163 for p in filelog.parentrevs(j):
1163 for p in filelog.parentrevs(j):
1164 if p != nullrev:
1164 if p != nullrev:
1165 parentlinkrevs.append(filelog.linkrev(p))
1165 parentlinkrevs.append(filelog.linkrev(p))
1166 n = filelog.node(j)
1166 n = filelog.node(j)
1167 revs.append((linkrev, parentlinkrevs,
1167 revs.append((linkrev, parentlinkrevs,
1168 follow and filelog.renamed(n)))
1168 follow and filelog.renamed(n)))
1169
1169
1170 return reversed(revs)
1170 return reversed(revs)
1171 def iterfiles():
1171 def iterfiles():
1172 for filename in match.files():
1172 for filename in match.files():
1173 yield filename, None
1173 yield filename, None
1174 for filename_node in copies:
1174 for filename_node in copies:
1175 yield filename_node
1175 yield filename_node
1176 for file_, node in iterfiles():
1176 for file_, node in iterfiles():
1177 filelog = repo.file(file_)
1177 filelog = repo.file(file_)
1178 if not len(filelog):
1178 if not len(filelog):
1179 if node is None:
1179 if node is None:
1180 # A zero count may be a directory or deleted file, so
1180 # A zero count may be a directory or deleted file, so
1181 # try to find matching entries on the slow path.
1181 # try to find matching entries on the slow path.
1182 if follow:
1182 if follow:
1183 raise util.Abort(
1183 raise util.Abort(
1184 _('cannot follow nonexistent file: "%s"') % file_)
1184 _('cannot follow nonexistent file: "%s"') % file_)
1185 slowpath = True
1185 slowpath = True
1186 break
1186 break
1187 else:
1187 else:
1188 continue
1188 continue
1189
1189
1190 if node is None:
1190 if node is None:
1191 last = len(filelog) - 1
1191 last = len(filelog) - 1
1192 else:
1192 else:
1193 last = filelog.rev(node)
1193 last = filelog.rev(node)
1194
1194
1195
1195
1196 # keep track of all ancestors of the file
1196 # keep track of all ancestors of the file
1197 ancestors = set([filelog.linkrev(last)])
1197 ancestors = set([filelog.linkrev(last)])
1198
1198
1199 # iterate from latest to oldest revision
1199 # iterate from latest to oldest revision
1200 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1200 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1201 if not follow:
1201 if not follow:
1202 if rev > maxrev:
1202 if rev > maxrev:
1203 continue
1203 continue
1204 else:
1204 else:
1205 # Note that last might not be the first interesting
1205 # Note that last might not be the first interesting
1206 # rev to us:
1206 # rev to us:
1207 # if the file has been changed after maxrev, we'll
1207 # if the file has been changed after maxrev, we'll
1208 # have linkrev(last) > maxrev, and we still need
1208 # have linkrev(last) > maxrev, and we still need
1209 # to explore the file graph
1209 # to explore the file graph
1210 if rev not in ancestors:
1210 if rev not in ancestors:
1211 continue
1211 continue
1212 # XXX insert 1327 fix here
1212 # XXX insert 1327 fix here
1213 if flparentlinkrevs:
1213 if flparentlinkrevs:
1214 ancestors.update(flparentlinkrevs)
1214 ancestors.update(flparentlinkrevs)
1215
1215
1216 fncache.setdefault(rev, []).append(file_)
1216 fncache.setdefault(rev, []).append(file_)
1217 wanted.add(rev)
1217 wanted.add(rev)
1218 if copied:
1218 if copied:
1219 copies.append(copied)
1219 copies.append(copied)
1220 if slowpath:
1220 if slowpath:
1221 # We have to read the changelog to match filenames against
1221 # We have to read the changelog to match filenames against
1222 # changed files
1222 # changed files
1223
1223
1224 if follow:
1224 if follow:
1225 raise util.Abort(_('can only follow copies/renames for explicit '
1225 raise util.Abort(_('can only follow copies/renames for explicit '
1226 'filenames'))
1226 'filenames'))
1227
1227
1228 # The slow path checks files modified in every changeset.
1228 # The slow path checks files modified in every changeset.
1229 for i in sorted(revs):
1229 for i in sorted(revs):
1230 ctx = change(i)
1230 ctx = change(i)
1231 matches = filter(match, ctx.files())
1231 matches = filter(match, ctx.files())
1232 if matches:
1232 if matches:
1233 fncache[i] = matches
1233 fncache[i] = matches
1234 wanted.add(i)
1234 wanted.add(i)
1235
1235
1236 class followfilter(object):
1236 class followfilter(object):
1237 def __init__(self, onlyfirst=False):
1237 def __init__(self, onlyfirst=False):
1238 self.startrev = nullrev
1238 self.startrev = nullrev
1239 self.roots = set()
1239 self.roots = set()
1240 self.onlyfirst = onlyfirst
1240 self.onlyfirst = onlyfirst
1241
1241
1242 def match(self, rev):
1242 def match(self, rev):
1243 def realparents(rev):
1243 def realparents(rev):
1244 if self.onlyfirst:
1244 if self.onlyfirst:
1245 return repo.changelog.parentrevs(rev)[0:1]
1245 return repo.changelog.parentrevs(rev)[0:1]
1246 else:
1246 else:
1247 return filter(lambda x: x != nullrev,
1247 return filter(lambda x: x != nullrev,
1248 repo.changelog.parentrevs(rev))
1248 repo.changelog.parentrevs(rev))
1249
1249
1250 if self.startrev == nullrev:
1250 if self.startrev == nullrev:
1251 self.startrev = rev
1251 self.startrev = rev
1252 return True
1252 return True
1253
1253
1254 if rev > self.startrev:
1254 if rev > self.startrev:
1255 # forward: all descendants
1255 # forward: all descendants
1256 if not self.roots:
1256 if not self.roots:
1257 self.roots.add(self.startrev)
1257 self.roots.add(self.startrev)
1258 for parent in realparents(rev):
1258 for parent in realparents(rev):
1259 if parent in self.roots:
1259 if parent in self.roots:
1260 self.roots.add(rev)
1260 self.roots.add(rev)
1261 return True
1261 return True
1262 else:
1262 else:
1263 # backwards: all parents
1263 # backwards: all parents
1264 if not self.roots:
1264 if not self.roots:
1265 self.roots.update(realparents(self.startrev))
1265 self.roots.update(realparents(self.startrev))
1266 if rev in self.roots:
1266 if rev in self.roots:
1267 self.roots.remove(rev)
1267 self.roots.remove(rev)
1268 self.roots.update(realparents(rev))
1268 self.roots.update(realparents(rev))
1269 return True
1269 return True
1270
1270
1271 return False
1271 return False
1272
1272
1273 # it might be worthwhile to do this in the iterator if the rev range
1273 # it might be worthwhile to do this in the iterator if the rev range
1274 # is descending and the prune args are all within that range
1274 # is descending and the prune args are all within that range
1275 for rev in opts.get('prune', ()):
1275 for rev in opts.get('prune', ()):
1276 rev = repo.changelog.rev(repo.lookup(rev))
1276 rev = repo.changelog.rev(repo.lookup(rev))
1277 ff = followfilter()
1277 ff = followfilter()
1278 stop = min(revs[0], revs[-1])
1278 stop = min(revs[0], revs[-1])
1279 for x in xrange(rev, stop - 1, -1):
1279 for x in xrange(rev, stop - 1, -1):
1280 if ff.match(x):
1280 if ff.match(x):
1281 wanted.discard(x)
1281 wanted.discard(x)
1282
1282
1283 # Now that wanted is correctly initialized, we can iterate over the
1283 # Now that wanted is correctly initialized, we can iterate over the
1284 # revision range, yielding only revisions in wanted.
1284 # revision range, yielding only revisions in wanted.
1285 def iterate():
1285 def iterate():
1286 if follow and not match.files():
1286 if follow and not match.files():
1287 ff = followfilter(onlyfirst=opts.get('follow_first'))
1287 ff = followfilter(onlyfirst=opts.get('follow_first'))
1288 def want(rev):
1288 def want(rev):
1289 return ff.match(rev) and rev in wanted
1289 return ff.match(rev) and rev in wanted
1290 else:
1290 else:
1291 def want(rev):
1291 def want(rev):
1292 return rev in wanted
1292 return rev in wanted
1293
1293
1294 for i, window in increasing_windows(0, len(revs)):
1294 for i, window in increasing_windows(0, len(revs)):
1295 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1295 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1296 for rev in sorted(nrevs):
1296 for rev in sorted(nrevs):
1297 fns = fncache.get(rev)
1297 fns = fncache.get(rev)
1298 ctx = change(rev)
1298 ctx = change(rev)
1299 if not fns:
1299 if not fns:
1300 def fns_generator():
1300 def fns_generator():
1301 for f in ctx.files():
1301 for f in ctx.files():
1302 if match(f):
1302 if match(f):
1303 yield f
1303 yield f
1304 fns = fns_generator()
1304 fns = fns_generator()
1305 prepare(ctx, fns)
1305 prepare(ctx, fns)
1306 for rev in nrevs:
1306 for rev in nrevs:
1307 yield change(rev)
1307 yield change(rev)
1308 return iterate()
1308 return iterate()
1309
1309
1310 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1310 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1311 join = lambda f: os.path.join(prefix, f)
1311 join = lambda f: os.path.join(prefix, f)
1312 bad = []
1312 bad = []
1313 oldbad = match.bad
1313 oldbad = match.bad
1314 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1314 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1315 names = []
1315 names = []
1316 wctx = repo[None]
1316 wctx = repo[None]
1317 cca = None
1317 cca = None
1318 abort, warn = scmutil.checkportabilityalert(ui)
1318 abort, warn = scmutil.checkportabilityalert(ui)
1319 if abort or warn:
1319 if abort or warn:
1320 cca = scmutil.casecollisionauditor(ui, abort, wctx)
1320 cca = scmutil.casecollisionauditor(ui, abort, wctx)
1321 for f in repo.walk(match):
1321 for f in repo.walk(match):
1322 exact = match.exact(f)
1322 exact = match.exact(f)
1323 if exact or f not in repo.dirstate:
1323 if exact or f not in repo.dirstate:
1324 if cca:
1324 if cca:
1325 cca(f)
1325 cca(f)
1326 names.append(f)
1326 names.append(f)
1327 if ui.verbose or not exact:
1327 if ui.verbose or not exact:
1328 ui.status(_('adding %s\n') % match.rel(join(f)))
1328 ui.status(_('adding %s\n') % match.rel(join(f)))
1329
1329
1330 if listsubrepos:
1330 if listsubrepos:
1331 for subpath in wctx.substate:
1331 for subpath in wctx.substate:
1332 sub = wctx.sub(subpath)
1332 sub = wctx.sub(subpath)
1333 try:
1333 try:
1334 submatch = matchmod.narrowmatcher(subpath, match)
1334 submatch = matchmod.narrowmatcher(subpath, match)
1335 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1335 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1336 except error.LookupError:
1336 except error.LookupError:
1337 ui.status(_("skipping missing subrepository: %s\n")
1337 ui.status(_("skipping missing subrepository: %s\n")
1338 % join(subpath))
1338 % join(subpath))
1339
1339
1340 if not dryrun:
1340 if not dryrun:
1341 rejected = wctx.add(names, prefix)
1341 rejected = wctx.add(names, prefix)
1342 bad.extend(f for f in rejected if f in match.files())
1342 bad.extend(f for f in rejected if f in match.files())
1343 return bad
1343 return bad
1344
1344
1345 def commit(ui, repo, commitfunc, pats, opts):
1345 def commit(ui, repo, commitfunc, pats, opts):
1346 '''commit the specified files or all outstanding changes'''
1346 '''commit the specified files or all outstanding changes'''
1347 date = opts.get('date')
1347 date = opts.get('date')
1348 if date:
1348 if date:
1349 opts['date'] = util.parsedate(date)
1349 opts['date'] = util.parsedate(date)
1350 message = logmessage(opts)
1350 message = logmessage(opts)
1351
1351
1352 # extract addremove carefully -- this function can be called from a command
1352 # extract addremove carefully -- this function can be called from a command
1353 # that doesn't support addremove
1353 # that doesn't support addremove
1354 if opts.get('addremove'):
1354 if opts.get('addremove'):
1355 addremove(repo, pats, opts)
1355 addremove(repo, pats, opts)
1356
1356
1357 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1357 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1358
1358
1359 def commiteditor(repo, ctx, subs):
1359 def commiteditor(repo, ctx, subs):
1360 if ctx.description():
1360 if ctx.description():
1361 return ctx.description()
1361 return ctx.description()
1362 return commitforceeditor(repo, ctx, subs)
1362 return commitforceeditor(repo, ctx, subs)
1363
1363
1364 def commitforceeditor(repo, ctx, subs):
1364 def commitforceeditor(repo, ctx, subs):
1365 edittext = []
1365 edittext = []
1366 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1366 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1367 if ctx.description():
1367 if ctx.description():
1368 edittext.append(ctx.description())
1368 edittext.append(ctx.description())
1369 edittext.append("")
1369 edittext.append("")
1370 edittext.append("") # Empty line between message and comments.
1370 edittext.append("") # Empty line between message and comments.
1371 edittext.append(_("HG: Enter commit message."
1371 edittext.append(_("HG: Enter commit message."
1372 " Lines beginning with 'HG:' are removed."))
1372 " Lines beginning with 'HG:' are removed."))
1373 edittext.append(_("HG: Leave message empty to abort commit."))
1373 edittext.append(_("HG: Leave message empty to abort commit."))
1374 edittext.append("HG: --")
1374 edittext.append("HG: --")
1375 edittext.append(_("HG: user: %s") % ctx.user())
1375 edittext.append(_("HG: user: %s") % ctx.user())
1376 if ctx.p2():
1376 if ctx.p2():
1377 edittext.append(_("HG: branch merge"))
1377 edittext.append(_("HG: branch merge"))
1378 if ctx.branch():
1378 if ctx.branch():
1379 edittext.append(_("HG: branch '%s'") % ctx.branch())
1379 edittext.append(_("HG: branch '%s'") % ctx.branch())
1380 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1380 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1381 edittext.extend([_("HG: added %s") % f for f in added])
1381 edittext.extend([_("HG: added %s") % f for f in added])
1382 edittext.extend([_("HG: changed %s") % f for f in modified])
1382 edittext.extend([_("HG: changed %s") % f for f in modified])
1383 edittext.extend([_("HG: removed %s") % f for f in removed])
1383 edittext.extend([_("HG: removed %s") % f for f in removed])
1384 if not added and not modified and not removed:
1384 if not added and not modified and not removed:
1385 edittext.append(_("HG: no files changed"))
1385 edittext.append(_("HG: no files changed"))
1386 edittext.append("")
1386 edittext.append("")
1387 # run editor in the repository root
1387 # run editor in the repository root
1388 olddir = os.getcwd()
1388 olddir = os.getcwd()
1389 os.chdir(repo.root)
1389 os.chdir(repo.root)
1390 text = repo.ui.edit("\n".join(edittext), ctx.user())
1390 text = repo.ui.edit("\n".join(edittext), ctx.user())
1391 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1391 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1392 os.chdir(olddir)
1392 os.chdir(olddir)
1393
1393
1394 if not text.strip():
1394 if not text.strip():
1395 raise util.Abort(_("empty commit message"))
1395 raise util.Abort(_("empty commit message"))
1396
1396
1397 return text
1397 return text
@@ -1,4973 +1,4973 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, sys, difflib, time, tempfile
11 import os, re, sys, difflib, time, tempfile
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, sshserver, hbisect, hgweb, hgweb.server
14 import archival, changegroup, cmdutil, sshserver, hbisect, hgweb, hgweb.server
15 import merge as mergemod
15 import merge as mergemod
16 import minirst, revset, templatefilters
16 import minirst, revset, templatefilters
17 import dagparser, context, simplemerge
17 import dagparser, context, simplemerge
18 import random, setdiscovery, treediscovery, dagutil
18 import random, setdiscovery, treediscovery, dagutil
19
19
20 # Commands start here, listed alphabetically
20 # Commands start here, listed alphabetically
21
21
22 def add(ui, repo, *pats, **opts):
22 def add(ui, repo, *pats, **opts):
23 """add the specified files on the next commit
23 """add the specified files on the next commit
24
24
25 Schedule files to be version controlled and added to the
25 Schedule files to be version controlled and added to the
26 repository.
26 repository.
27
27
28 The files will be added to the repository at the next commit. To
28 The files will be added to the repository at the next commit. To
29 undo an add before that, see :hg:`forget`.
29 undo an add before that, see :hg:`forget`.
30
30
31 If no names are given, add all files to the repository.
31 If no names are given, add all files to the repository.
32
32
33 .. container:: verbose
33 .. container:: verbose
34
34
35 An example showing how new (unknown) files are added
35 An example showing how new (unknown) files are added
36 automatically by :hg:`add`::
36 automatically by :hg:`add`::
37
37
38 $ ls
38 $ ls
39 foo.c
39 foo.c
40 $ hg status
40 $ hg status
41 ? foo.c
41 ? foo.c
42 $ hg add
42 $ hg add
43 adding foo.c
43 adding foo.c
44 $ hg status
44 $ hg status
45 A foo.c
45 A foo.c
46
46
47 Returns 0 if all files are successfully added.
47 Returns 0 if all files are successfully added.
48 """
48 """
49
49
50 m = cmdutil.match(repo, pats, opts)
50 m = cmdutil.match(repo, pats, opts)
51 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
51 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
52 opts.get('subrepos'), prefix="")
52 opts.get('subrepos'), prefix="")
53 return rejected and 1 or 0
53 return rejected and 1 or 0
54
54
55 def addremove(ui, repo, *pats, **opts):
55 def addremove(ui, repo, *pats, **opts):
56 """add all new files, delete all missing files
56 """add all new files, delete all missing files
57
57
58 Add all new files and remove all missing files from the
58 Add all new files and remove all missing files from the
59 repository.
59 repository.
60
60
61 New files are ignored if they match any of the patterns in
61 New files are ignored if they match any of the patterns in
62 ``.hgignore``. As with add, these changes take effect at the next
62 ``.hgignore``. As with add, these changes take effect at the next
63 commit.
63 commit.
64
64
65 Use the -s/--similarity option to detect renamed files. With a
65 Use the -s/--similarity option to detect renamed files. With a
66 parameter greater than 0, this compares every removed file with
66 parameter greater than 0, this compares every removed file with
67 every added file and records those similar enough as renames. This
67 every added file and records those similar enough as renames. This
68 option takes a percentage between 0 (disabled) and 100 (files must
68 option takes a percentage between 0 (disabled) and 100 (files must
69 be identical) as its parameter. Detecting renamed files this way
69 be identical) as its parameter. Detecting renamed files this way
70 can be expensive. After using this option, :hg:`status -C` can be
70 can be expensive. After using this option, :hg:`status -C` can be
71 used to check which files were identified as moved or renamed.
71 used to check which files were identified as moved or renamed.
72
72
73 Returns 0 if all files are successfully added.
73 Returns 0 if all files are successfully added.
74 """
74 """
75 try:
75 try:
76 sim = float(opts.get('similarity') or 100)
76 sim = float(opts.get('similarity') or 100)
77 except ValueError:
77 except ValueError:
78 raise util.Abort(_('similarity must be a number'))
78 raise util.Abort(_('similarity must be a number'))
79 if sim < 0 or sim > 100:
79 if sim < 0 or sim > 100:
80 raise util.Abort(_('similarity must be between 0 and 100'))
80 raise util.Abort(_('similarity must be between 0 and 100'))
81 return cmdutil.addremove(repo, pats, opts, similarity=sim / 100.0)
81 return cmdutil.addremove(repo, pats, opts, similarity=sim / 100.0)
82
82
83 def annotate(ui, repo, *pats, **opts):
83 def annotate(ui, repo, *pats, **opts):
84 """show changeset information by line for each file
84 """show changeset information by line for each file
85
85
86 List changes in files, showing the revision id responsible for
86 List changes in files, showing the revision id responsible for
87 each line
87 each line
88
88
89 This command is useful for discovering when a change was made and
89 This command is useful for discovering when a change was made and
90 by whom.
90 by whom.
91
91
92 Without the -a/--text option, annotate will avoid processing files
92 Without the -a/--text option, annotate will avoid processing files
93 it detects as binary. With -a, annotate will annotate the file
93 it detects as binary. With -a, annotate will annotate the file
94 anyway, although the results will probably be neither useful
94 anyway, although the results will probably be neither useful
95 nor desirable.
95 nor desirable.
96
96
97 Returns 0 on success.
97 Returns 0 on success.
98 """
98 """
99 if opts.get('follow'):
99 if opts.get('follow'):
100 # --follow is deprecated and now just an alias for -f/--file
100 # --follow is deprecated and now just an alias for -f/--file
101 # to mimic the behavior of Mercurial before version 1.5
101 # to mimic the behavior of Mercurial before version 1.5
102 opts['file'] = True
102 opts['file'] = True
103
103
104 datefunc = ui.quiet and util.shortdate or util.datestr
104 datefunc = ui.quiet and util.shortdate or util.datestr
105 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
105 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
106
106
107 if not pats:
107 if not pats:
108 raise util.Abort(_('at least one filename or pattern is required'))
108 raise util.Abort(_('at least one filename or pattern is required'))
109
109
110 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
110 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
111 ('number', lambda x: str(x[0].rev())),
111 ('number', lambda x: str(x[0].rev())),
112 ('changeset', lambda x: short(x[0].node())),
112 ('changeset', lambda x: short(x[0].node())),
113 ('date', getdate),
113 ('date', getdate),
114 ('file', lambda x: x[0].path()),
114 ('file', lambda x: x[0].path()),
115 ]
115 ]
116
116
117 if (not opts.get('user') and not opts.get('changeset')
117 if (not opts.get('user') and not opts.get('changeset')
118 and not opts.get('date') and not opts.get('file')):
118 and not opts.get('date') and not opts.get('file')):
119 opts['number'] = True
119 opts['number'] = True
120
120
121 linenumber = opts.get('line_number') is not None
121 linenumber = opts.get('line_number') is not None
122 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
122 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
123 raise util.Abort(_('at least one of -n/-c is required for -l'))
123 raise util.Abort(_('at least one of -n/-c is required for -l'))
124
124
125 funcmap = [func for op, func in opmap if opts.get(op)]
125 funcmap = [func for op, func in opmap if opts.get(op)]
126 if linenumber:
126 if linenumber:
127 lastfunc = funcmap[-1]
127 lastfunc = funcmap[-1]
128 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
128 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
129
129
130 def bad(x, y):
130 def bad(x, y):
131 raise util.Abort("%s: %s" % (x, y))
131 raise util.Abort("%s: %s" % (x, y))
132
132
133 ctx = cmdutil.revsingle(repo, opts.get('rev'))
133 ctx = cmdutil.revsingle(repo, opts.get('rev'))
134 m = cmdutil.match(repo, pats, opts)
134 m = cmdutil.match(repo, pats, opts)
135 m.bad = bad
135 m.bad = bad
136 follow = not opts.get('no_follow')
136 follow = not opts.get('no_follow')
137 for abs in ctx.walk(m):
137 for abs in ctx.walk(m):
138 fctx = ctx[abs]
138 fctx = ctx[abs]
139 if not opts.get('text') and util.binary(fctx.data()):
139 if not opts.get('text') and util.binary(fctx.data()):
140 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
140 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
141 continue
141 continue
142
142
143 lines = fctx.annotate(follow=follow, linenumber=linenumber)
143 lines = fctx.annotate(follow=follow, linenumber=linenumber)
144 pieces = []
144 pieces = []
145
145
146 for f in funcmap:
146 for f in funcmap:
147 l = [f(n) for n, dummy in lines]
147 l = [f(n) for n, dummy in lines]
148 if l:
148 if l:
149 sized = [(x, encoding.colwidth(x)) for x in l]
149 sized = [(x, encoding.colwidth(x)) for x in l]
150 ml = max([w for x, w in sized])
150 ml = max([w for x, w in sized])
151 pieces.append(["%s%s" % (' ' * (ml - w), x) for x, w in sized])
151 pieces.append(["%s%s" % (' ' * (ml - w), x) for x, w in sized])
152
152
153 if pieces:
153 if pieces:
154 for p, l in zip(zip(*pieces), lines):
154 for p, l in zip(zip(*pieces), lines):
155 ui.write("%s: %s" % (" ".join(p), l[1]))
155 ui.write("%s: %s" % (" ".join(p), l[1]))
156
156
157 def archive(ui, repo, dest, **opts):
157 def archive(ui, repo, dest, **opts):
158 '''create an unversioned archive of a repository revision
158 '''create an unversioned archive of a repository revision
159
159
160 By default, the revision used is the parent of the working
160 By default, the revision used is the parent of the working
161 directory; use -r/--rev to specify a different revision.
161 directory; use -r/--rev to specify a different revision.
162
162
163 The archive type is automatically detected based on file
163 The archive type is automatically detected based on file
164 extension (or override using -t/--type).
164 extension (or override using -t/--type).
165
165
166 Valid types are:
166 Valid types are:
167
167
168 :``files``: a directory full of files (default)
168 :``files``: a directory full of files (default)
169 :``tar``: tar archive, uncompressed
169 :``tar``: tar archive, uncompressed
170 :``tbz2``: tar archive, compressed using bzip2
170 :``tbz2``: tar archive, compressed using bzip2
171 :``tgz``: tar archive, compressed using gzip
171 :``tgz``: tar archive, compressed using gzip
172 :``uzip``: zip archive, uncompressed
172 :``uzip``: zip archive, uncompressed
173 :``zip``: zip archive, compressed using deflate
173 :``zip``: zip archive, compressed using deflate
174
174
175 The exact name of the destination archive or directory is given
175 The exact name of the destination archive or directory is given
176 using a format string; see :hg:`help export` for details.
176 using a format string; see :hg:`help export` for details.
177
177
178 Each member added to an archive file has a directory prefix
178 Each member added to an archive file has a directory prefix
179 prepended. Use -p/--prefix to specify a format string for the
179 prepended. Use -p/--prefix to specify a format string for the
180 prefix. The default is the basename of the archive, with suffixes
180 prefix. The default is the basename of the archive, with suffixes
181 removed.
181 removed.
182
182
183 Returns 0 on success.
183 Returns 0 on success.
184 '''
184 '''
185
185
186 ctx = cmdutil.revsingle(repo, opts.get('rev'))
186 ctx = cmdutil.revsingle(repo, opts.get('rev'))
187 if not ctx:
187 if not ctx:
188 raise util.Abort(_('no working directory: please specify a revision'))
188 raise util.Abort(_('no working directory: please specify a revision'))
189 node = ctx.node()
189 node = ctx.node()
190 dest = cmdutil.make_filename(repo, dest, node)
190 dest = cmdutil.make_filename(repo, dest, node)
191 if os.path.realpath(dest) == repo.root:
191 if os.path.realpath(dest) == repo.root:
192 raise util.Abort(_('repository root cannot be destination'))
192 raise util.Abort(_('repository root cannot be destination'))
193
193
194 kind = opts.get('type') or archival.guesskind(dest) or 'files'
194 kind = opts.get('type') or archival.guesskind(dest) or 'files'
195 prefix = opts.get('prefix')
195 prefix = opts.get('prefix')
196
196
197 if dest == '-':
197 if dest == '-':
198 if kind == 'files':
198 if kind == 'files':
199 raise util.Abort(_('cannot archive plain files to stdout'))
199 raise util.Abort(_('cannot archive plain files to stdout'))
200 dest = sys.stdout
200 dest = sys.stdout
201 if not prefix:
201 if not prefix:
202 prefix = os.path.basename(repo.root) + '-%h'
202 prefix = os.path.basename(repo.root) + '-%h'
203
203
204 prefix = cmdutil.make_filename(repo, prefix, node)
204 prefix = cmdutil.make_filename(repo, prefix, node)
205 matchfn = cmdutil.match(repo, [], opts)
205 matchfn = cmdutil.match(repo, [], opts)
206 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
206 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
207 matchfn, prefix, subrepos=opts.get('subrepos'))
207 matchfn, prefix, subrepos=opts.get('subrepos'))
208
208
209 def backout(ui, repo, node=None, rev=None, **opts):
209 def backout(ui, repo, node=None, rev=None, **opts):
210 '''reverse effect of earlier changeset
210 '''reverse effect of earlier changeset
211
211
212 Prepare a new changeset with the effect of REV undone in the
212 Prepare a new changeset with the effect of REV undone in the
213 current working directory.
213 current working directory.
214
214
215 If REV is the parent of the working directory, then this new changeset
215 If REV is the parent of the working directory, then this new changeset
216 is committed automatically. Otherwise, hg needs to merge the
216 is committed automatically. Otherwise, hg needs to merge the
217 changes and the merged result is left uncommitted.
217 changes and the merged result is left uncommitted.
218
218
219 By default, the pending changeset will have one parent,
219 By default, the pending changeset will have one parent,
220 maintaining a linear history. With --merge, the pending changeset
220 maintaining a linear history. With --merge, the pending changeset
221 will instead have two parents: the old parent of the working
221 will instead have two parents: the old parent of the working
222 directory and a new child of REV that simply undoes REV.
222 directory and a new child of REV that simply undoes REV.
223
223
224 Before version 1.7, the behavior without --merge was equivalent to
224 Before version 1.7, the behavior without --merge was equivalent to
225 specifying --merge followed by :hg:`update --clean .` to cancel
225 specifying --merge followed by :hg:`update --clean .` to cancel
226 the merge and leave the child of REV as a head to be merged
226 the merge and leave the child of REV as a head to be merged
227 separately.
227 separately.
228
228
229 See :hg:`help dates` for a list of formats valid for -d/--date.
229 See :hg:`help dates` for a list of formats valid for -d/--date.
230
230
231 Returns 0 on success.
231 Returns 0 on success.
232 '''
232 '''
233 if rev and node:
233 if rev and node:
234 raise util.Abort(_("please specify just one revision"))
234 raise util.Abort(_("please specify just one revision"))
235
235
236 if not rev:
236 if not rev:
237 rev = node
237 rev = node
238
238
239 if not rev:
239 if not rev:
240 raise util.Abort(_("please specify a revision to backout"))
240 raise util.Abort(_("please specify a revision to backout"))
241
241
242 date = opts.get('date')
242 date = opts.get('date')
243 if date:
243 if date:
244 opts['date'] = util.parsedate(date)
244 opts['date'] = util.parsedate(date)
245
245
246 cmdutil.bail_if_changed(repo)
246 cmdutil.bail_if_changed(repo)
247 node = cmdutil.revsingle(repo, rev).node()
247 node = cmdutil.revsingle(repo, rev).node()
248
248
249 op1, op2 = repo.dirstate.parents()
249 op1, op2 = repo.dirstate.parents()
250 a = repo.changelog.ancestor(op1, node)
250 a = repo.changelog.ancestor(op1, node)
251 if a != node:
251 if a != node:
252 raise util.Abort(_('cannot backout change on a different branch'))
252 raise util.Abort(_('cannot backout change on a different branch'))
253
253
254 p1, p2 = repo.changelog.parents(node)
254 p1, p2 = repo.changelog.parents(node)
255 if p1 == nullid:
255 if p1 == nullid:
256 raise util.Abort(_('cannot backout a change with no parents'))
256 raise util.Abort(_('cannot backout a change with no parents'))
257 if p2 != nullid:
257 if p2 != nullid:
258 if not opts.get('parent'):
258 if not opts.get('parent'):
259 raise util.Abort(_('cannot backout a merge changeset without '
259 raise util.Abort(_('cannot backout a merge changeset without '
260 '--parent'))
260 '--parent'))
261 p = repo.lookup(opts['parent'])
261 p = repo.lookup(opts['parent'])
262 if p not in (p1, p2):
262 if p not in (p1, p2):
263 raise util.Abort(_('%s is not a parent of %s') %
263 raise util.Abort(_('%s is not a parent of %s') %
264 (short(p), short(node)))
264 (short(p), short(node)))
265 parent = p
265 parent = p
266 else:
266 else:
267 if opts.get('parent'):
267 if opts.get('parent'):
268 raise util.Abort(_('cannot use --parent on non-merge changeset'))
268 raise util.Abort(_('cannot use --parent on non-merge changeset'))
269 parent = p1
269 parent = p1
270
270
271 # the backout should appear on the same branch
271 # the backout should appear on the same branch
272 branch = repo.dirstate.branch()
272 branch = repo.dirstate.branch()
273 hg.clean(repo, node, show_stats=False)
273 hg.clean(repo, node, show_stats=False)
274 repo.dirstate.setbranch(branch)
274 repo.dirstate.setbranch(branch)
275 revert_opts = opts.copy()
275 revert_opts = opts.copy()
276 revert_opts['date'] = None
276 revert_opts['date'] = None
277 revert_opts['all'] = True
277 revert_opts['all'] = True
278 revert_opts['rev'] = hex(parent)
278 revert_opts['rev'] = hex(parent)
279 revert_opts['no_backup'] = None
279 revert_opts['no_backup'] = None
280 revert(ui, repo, **revert_opts)
280 revert(ui, repo, **revert_opts)
281 if not opts.get('merge') and op1 != node:
281 if not opts.get('merge') and op1 != node:
282 try:
282 try:
283 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
283 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
284 return hg.update(repo, op1)
284 return hg.update(repo, op1)
285 finally:
285 finally:
286 ui.setconfig('ui', 'forcemerge', '')
286 ui.setconfig('ui', 'forcemerge', '')
287
287
288 commit_opts = opts.copy()
288 commit_opts = opts.copy()
289 commit_opts['addremove'] = False
289 commit_opts['addremove'] = False
290 if not commit_opts['message'] and not commit_opts['logfile']:
290 if not commit_opts['message'] and not commit_opts['logfile']:
291 # we don't translate commit messages
291 # we don't translate commit messages
292 commit_opts['message'] = "Backed out changeset %s" % short(node)
292 commit_opts['message'] = "Backed out changeset %s" % short(node)
293 commit_opts['force_editor'] = True
293 commit_opts['force_editor'] = True
294 commit(ui, repo, **commit_opts)
294 commit(ui, repo, **commit_opts)
295 def nice(node):
295 def nice(node):
296 return '%d:%s' % (repo.changelog.rev(node), short(node))
296 return '%d:%s' % (repo.changelog.rev(node), short(node))
297 ui.status(_('changeset %s backs out changeset %s\n') %
297 ui.status(_('changeset %s backs out changeset %s\n') %
298 (nice(repo.changelog.tip()), nice(node)))
298 (nice(repo.changelog.tip()), nice(node)))
299 if opts.get('merge') and op1 != node:
299 if opts.get('merge') and op1 != node:
300 hg.clean(repo, op1, show_stats=False)
300 hg.clean(repo, op1, show_stats=False)
301 ui.status(_('merging with changeset %s\n')
301 ui.status(_('merging with changeset %s\n')
302 % nice(repo.changelog.tip()))
302 % nice(repo.changelog.tip()))
303 try:
303 try:
304 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
304 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
305 return hg.merge(repo, hex(repo.changelog.tip()))
305 return hg.merge(repo, hex(repo.changelog.tip()))
306 finally:
306 finally:
307 ui.setconfig('ui', 'forcemerge', '')
307 ui.setconfig('ui', 'forcemerge', '')
308 return 0
308 return 0
309
309
310 def bisect(ui, repo, rev=None, extra=None, command=None,
310 def bisect(ui, repo, rev=None, extra=None, command=None,
311 reset=None, good=None, bad=None, skip=None, extend=None,
311 reset=None, good=None, bad=None, skip=None, extend=None,
312 noupdate=None):
312 noupdate=None):
313 """subdivision search of changesets
313 """subdivision search of changesets
314
314
315 This command helps to find changesets which introduce problems. To
315 This command helps to find changesets which introduce problems. To
316 use, mark the earliest changeset you know exhibits the problem as
316 use, mark the earliest changeset you know exhibits the problem as
317 bad, then mark the latest changeset which is free from the problem
317 bad, then mark the latest changeset which is free from the problem
318 as good. Bisect will update your working directory to a revision
318 as good. Bisect will update your working directory to a revision
319 for testing (unless the -U/--noupdate option is specified). Once
319 for testing (unless the -U/--noupdate option is specified). Once
320 you have performed tests, mark the working directory as good or
320 you have performed tests, mark the working directory as good or
321 bad, and bisect will either update to another candidate changeset
321 bad, and bisect will either update to another candidate changeset
322 or announce that it has found the bad revision.
322 or announce that it has found the bad revision.
323
323
324 As a shortcut, you can also use the revision argument to mark a
324 As a shortcut, you can also use the revision argument to mark a
325 revision as good or bad without checking it out first.
325 revision as good or bad without checking it out first.
326
326
327 If you supply a command, it will be used for automatic bisection.
327 If you supply a command, it will be used for automatic bisection.
328 Its exit status will be used to mark revisions as good or bad:
328 Its exit status will be used to mark revisions as good or bad:
329 status 0 means good, 125 means to skip the revision, 127
329 status 0 means good, 125 means to skip the revision, 127
330 (command not found) will abort the bisection, and any other
330 (command not found) will abort the bisection, and any other
331 non-zero exit status means the revision is bad.
331 non-zero exit status means the revision is bad.
332
332
333 Returns 0 on success.
333 Returns 0 on success.
334 """
334 """
335 def extendbisectrange(nodes, good):
335 def extendbisectrange(nodes, good):
336 # bisect is incomplete when it ends on a merge node and
336 # bisect is incomplete when it ends on a merge node and
337 # one of the parent was not checked.
337 # one of the parent was not checked.
338 parents = repo[nodes[0]].parents()
338 parents = repo[nodes[0]].parents()
339 if len(parents) > 1:
339 if len(parents) > 1:
340 side = good and state['bad'] or state['good']
340 side = good and state['bad'] or state['good']
341 num = len(set(i.node() for i in parents) & set(side))
341 num = len(set(i.node() for i in parents) & set(side))
342 if num == 1:
342 if num == 1:
343 return parents[0].ancestor(parents[1])
343 return parents[0].ancestor(parents[1])
344 return None
344 return None
345
345
346 def print_result(nodes, good):
346 def print_result(nodes, good):
347 displayer = cmdutil.show_changeset(ui, repo, {})
347 displayer = cmdutil.show_changeset(ui, repo, {})
348 if len(nodes) == 1:
348 if len(nodes) == 1:
349 # narrowed it down to a single revision
349 # narrowed it down to a single revision
350 if good:
350 if good:
351 ui.write(_("The first good revision is:\n"))
351 ui.write(_("The first good revision is:\n"))
352 else:
352 else:
353 ui.write(_("The first bad revision is:\n"))
353 ui.write(_("The first bad revision is:\n"))
354 displayer.show(repo[nodes[0]])
354 displayer.show(repo[nodes[0]])
355 extendnode = extendbisectrange(nodes, good)
355 extendnode = extendbisectrange(nodes, good)
356 if extendnode is not None:
356 if extendnode is not None:
357 ui.write(_('Not all ancestors of this changeset have been'
357 ui.write(_('Not all ancestors of this changeset have been'
358 ' checked.\nUse bisect --extend to continue the '
358 ' checked.\nUse bisect --extend to continue the '
359 'bisection from\nthe common ancestor, %s.\n')
359 'bisection from\nthe common ancestor, %s.\n')
360 % extendnode)
360 % extendnode)
361 else:
361 else:
362 # multiple possible revisions
362 # multiple possible revisions
363 if good:
363 if good:
364 ui.write(_("Due to skipped revisions, the first "
364 ui.write(_("Due to skipped revisions, the first "
365 "good revision could be any of:\n"))
365 "good revision could be any of:\n"))
366 else:
366 else:
367 ui.write(_("Due to skipped revisions, the first "
367 ui.write(_("Due to skipped revisions, the first "
368 "bad revision could be any of:\n"))
368 "bad revision could be any of:\n"))
369 for n in nodes:
369 for n in nodes:
370 displayer.show(repo[n])
370 displayer.show(repo[n])
371 displayer.close()
371 displayer.close()
372
372
373 def check_state(state, interactive=True):
373 def check_state(state, interactive=True):
374 if not state['good'] or not state['bad']:
374 if not state['good'] or not state['bad']:
375 if (good or bad or skip or reset) and interactive:
375 if (good or bad or skip or reset) and interactive:
376 return
376 return
377 if not state['good']:
377 if not state['good']:
378 raise util.Abort(_('cannot bisect (no known good revisions)'))
378 raise util.Abort(_('cannot bisect (no known good revisions)'))
379 else:
379 else:
380 raise util.Abort(_('cannot bisect (no known bad revisions)'))
380 raise util.Abort(_('cannot bisect (no known bad revisions)'))
381 return True
381 return True
382
382
383 # backward compatibility
383 # backward compatibility
384 if rev in "good bad reset init".split():
384 if rev in "good bad reset init".split():
385 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
385 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
386 cmd, rev, extra = rev, extra, None
386 cmd, rev, extra = rev, extra, None
387 if cmd == "good":
387 if cmd == "good":
388 good = True
388 good = True
389 elif cmd == "bad":
389 elif cmd == "bad":
390 bad = True
390 bad = True
391 else:
391 else:
392 reset = True
392 reset = True
393 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
393 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
394 raise util.Abort(_('incompatible arguments'))
394 raise util.Abort(_('incompatible arguments'))
395
395
396 if reset:
396 if reset:
397 p = repo.join("bisect.state")
397 p = repo.join("bisect.state")
398 if os.path.exists(p):
398 if os.path.exists(p):
399 os.unlink(p)
399 os.unlink(p)
400 return
400 return
401
401
402 state = hbisect.load_state(repo)
402 state = hbisect.load_state(repo)
403
403
404 if command:
404 if command:
405 changesets = 1
405 changesets = 1
406 try:
406 try:
407 while changesets:
407 while changesets:
408 # update state
408 # update state
409 status = util.system(command)
409 status = util.system(command)
410 if status == 125:
410 if status == 125:
411 transition = "skip"
411 transition = "skip"
412 elif status == 0:
412 elif status == 0:
413 transition = "good"
413 transition = "good"
414 # status < 0 means process was killed
414 # status < 0 means process was killed
415 elif status == 127:
415 elif status == 127:
416 raise util.Abort(_("failed to execute %s") % command)
416 raise util.Abort(_("failed to execute %s") % command)
417 elif status < 0:
417 elif status < 0:
418 raise util.Abort(_("%s killed") % command)
418 raise util.Abort(_("%s killed") % command)
419 else:
419 else:
420 transition = "bad"
420 transition = "bad"
421 ctx = cmdutil.revsingle(repo, rev)
421 ctx = cmdutil.revsingle(repo, rev)
422 rev = None # clear for future iterations
422 rev = None # clear for future iterations
423 state[transition].append(ctx.node())
423 state[transition].append(ctx.node())
424 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
424 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
425 check_state(state, interactive=False)
425 check_state(state, interactive=False)
426 # bisect
426 # bisect
427 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
427 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
428 # update to next check
428 # update to next check
429 cmdutil.bail_if_changed(repo)
429 cmdutil.bail_if_changed(repo)
430 hg.clean(repo, nodes[0], show_stats=False)
430 hg.clean(repo, nodes[0], show_stats=False)
431 finally:
431 finally:
432 hbisect.save_state(repo, state)
432 hbisect.save_state(repo, state)
433 print_result(nodes, good)
433 print_result(nodes, good)
434 return
434 return
435
435
436 # update state
436 # update state
437
437
438 if rev:
438 if rev:
439 nodes = [repo.lookup(i) for i in cmdutil.revrange(repo, [rev])]
439 nodes = [repo.lookup(i) for i in cmdutil.revrange(repo, [rev])]
440 else:
440 else:
441 nodes = [repo.lookup('.')]
441 nodes = [repo.lookup('.')]
442
442
443 if good or bad or skip:
443 if good or bad or skip:
444 if good:
444 if good:
445 state['good'] += nodes
445 state['good'] += nodes
446 elif bad:
446 elif bad:
447 state['bad'] += nodes
447 state['bad'] += nodes
448 elif skip:
448 elif skip:
449 state['skip'] += nodes
449 state['skip'] += nodes
450 hbisect.save_state(repo, state)
450 hbisect.save_state(repo, state)
451
451
452 if not check_state(state):
452 if not check_state(state):
453 return
453 return
454
454
455 # actually bisect
455 # actually bisect
456 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
456 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
457 if extend:
457 if extend:
458 if not changesets:
458 if not changesets:
459 extendnode = extendbisectrange(nodes, good)
459 extendnode = extendbisectrange(nodes, good)
460 if extendnode is not None:
460 if extendnode is not None:
461 ui.write(_("Extending search to changeset %d:%s\n"
461 ui.write(_("Extending search to changeset %d:%s\n"
462 % (extendnode.rev(), extendnode)))
462 % (extendnode.rev(), extendnode)))
463 if noupdate:
463 if noupdate:
464 return
464 return
465 cmdutil.bail_if_changed(repo)
465 cmdutil.bail_if_changed(repo)
466 return hg.clean(repo, extendnode.node())
466 return hg.clean(repo, extendnode.node())
467 raise util.Abort(_("nothing to extend"))
467 raise util.Abort(_("nothing to extend"))
468
468
469 if changesets == 0:
469 if changesets == 0:
470 print_result(nodes, good)
470 print_result(nodes, good)
471 else:
471 else:
472 assert len(nodes) == 1 # only a single node can be tested next
472 assert len(nodes) == 1 # only a single node can be tested next
473 node = nodes[0]
473 node = nodes[0]
474 # compute the approximate number of remaining tests
474 # compute the approximate number of remaining tests
475 tests, size = 0, 2
475 tests, size = 0, 2
476 while size <= changesets:
476 while size <= changesets:
477 tests, size = tests + 1, size * 2
477 tests, size = tests + 1, size * 2
478 rev = repo.changelog.rev(node)
478 rev = repo.changelog.rev(node)
479 ui.write(_("Testing changeset %d:%s "
479 ui.write(_("Testing changeset %d:%s "
480 "(%d changesets remaining, ~%d tests)\n")
480 "(%d changesets remaining, ~%d tests)\n")
481 % (rev, short(node), changesets, tests))
481 % (rev, short(node), changesets, tests))
482 if not noupdate:
482 if not noupdate:
483 cmdutil.bail_if_changed(repo)
483 cmdutil.bail_if_changed(repo)
484 return hg.clean(repo, node)
484 return hg.clean(repo, node)
485
485
486 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
486 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
487 rename=None, inactive=False):
487 rename=None, inactive=False):
488 '''track a line of development with movable markers
488 '''track a line of development with movable markers
489
489
490 Bookmarks are pointers to certain commits that move when
490 Bookmarks are pointers to certain commits that move when
491 committing. Bookmarks are local. They can be renamed, copied and
491 committing. Bookmarks are local. They can be renamed, copied and
492 deleted. It is possible to use bookmark names in :hg:`merge` and
492 deleted. It is possible to use bookmark names in :hg:`merge` and
493 :hg:`update` to merge and update respectively to a given bookmark.
493 :hg:`update` to merge and update respectively to a given bookmark.
494
494
495 You can use :hg:`bookmark NAME` to set a bookmark on the working
495 You can use :hg:`bookmark NAME` to set a bookmark on the working
496 directory's parent revision with the given name. If you specify
496 directory's parent revision with the given name. If you specify
497 a revision using -r REV (where REV may be an existing bookmark),
497 a revision using -r REV (where REV may be an existing bookmark),
498 the bookmark is assigned to that revision.
498 the bookmark is assigned to that revision.
499
499
500 Bookmarks can be pushed and pulled between repositories (see :hg:`help
500 Bookmarks can be pushed and pulled between repositories (see :hg:`help
501 push` and :hg:`help pull`). This requires both the local and remote
501 push` and :hg:`help pull`). This requires both the local and remote
502 repositories to support bookmarks. For versions prior to 1.8, this means
502 repositories to support bookmarks. For versions prior to 1.8, this means
503 the bookmarks extension must be enabled.
503 the bookmarks extension must be enabled.
504 '''
504 '''
505 hexfn = ui.debugflag and hex or short
505 hexfn = ui.debugflag and hex or short
506 marks = repo._bookmarks
506 marks = repo._bookmarks
507 cur = repo.changectx('.').node()
507 cur = repo.changectx('.').node()
508
508
509 if rename:
509 if rename:
510 if rename not in marks:
510 if rename not in marks:
511 raise util.Abort(_("bookmark '%s' does not exist") % rename)
511 raise util.Abort(_("bookmark '%s' does not exist") % rename)
512 if mark in marks and not force:
512 if mark in marks and not force:
513 raise util.Abort(_("bookmark '%s' already exists "
513 raise util.Abort(_("bookmark '%s' already exists "
514 "(use -f to force)") % mark)
514 "(use -f to force)") % mark)
515 if mark is None:
515 if mark is None:
516 raise util.Abort(_("new bookmark name required"))
516 raise util.Abort(_("new bookmark name required"))
517 marks[mark] = marks[rename]
517 marks[mark] = marks[rename]
518 if repo._bookmarkcurrent == rename and not inactive:
518 if repo._bookmarkcurrent == rename and not inactive:
519 bookmarks.setcurrent(repo, mark)
519 bookmarks.setcurrent(repo, mark)
520 del marks[rename]
520 del marks[rename]
521 bookmarks.write(repo)
521 bookmarks.write(repo)
522 return
522 return
523
523
524 if delete:
524 if delete:
525 if mark is None:
525 if mark is None:
526 raise util.Abort(_("bookmark name required"))
526 raise util.Abort(_("bookmark name required"))
527 if mark not in marks:
527 if mark not in marks:
528 raise util.Abort(_("bookmark '%s' does not exist") % mark)
528 raise util.Abort(_("bookmark '%s' does not exist") % mark)
529 if mark == repo._bookmarkcurrent:
529 if mark == repo._bookmarkcurrent:
530 bookmarks.setcurrent(repo, None)
530 bookmarks.setcurrent(repo, None)
531 del marks[mark]
531 del marks[mark]
532 bookmarks.write(repo)
532 bookmarks.write(repo)
533 return
533 return
534
534
535 if mark is not None:
535 if mark is not None:
536 if "\n" in mark:
536 if "\n" in mark:
537 raise util.Abort(_("bookmark name cannot contain newlines"))
537 raise util.Abort(_("bookmark name cannot contain newlines"))
538 mark = mark.strip()
538 mark = mark.strip()
539 if not mark:
539 if not mark:
540 raise util.Abort(_("bookmark names cannot consist entirely of "
540 raise util.Abort(_("bookmark names cannot consist entirely of "
541 "whitespace"))
541 "whitespace"))
542 if inactive and mark == repo._bookmarkcurrent:
542 if inactive and mark == repo._bookmarkcurrent:
543 bookmarks.setcurrent(repo, None)
543 bookmarks.setcurrent(repo, None)
544 return
544 return
545 if mark in marks and not force:
545 if mark in marks and not force:
546 raise util.Abort(_("bookmark '%s' already exists "
546 raise util.Abort(_("bookmark '%s' already exists "
547 "(use -f to force)") % mark)
547 "(use -f to force)") % mark)
548 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
548 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
549 and not force):
549 and not force):
550 raise util.Abort(
550 raise util.Abort(
551 _("a bookmark cannot have the name of an existing branch"))
551 _("a bookmark cannot have the name of an existing branch"))
552 if rev:
552 if rev:
553 marks[mark] = repo.lookup(rev)
553 marks[mark] = repo.lookup(rev)
554 else:
554 else:
555 marks[mark] = repo.changectx('.').node()
555 marks[mark] = repo.changectx('.').node()
556 if not inactive and repo.changectx('.').node() == marks[mark]:
556 if not inactive and repo.changectx('.').node() == marks[mark]:
557 bookmarks.setcurrent(repo, mark)
557 bookmarks.setcurrent(repo, mark)
558 bookmarks.write(repo)
558 bookmarks.write(repo)
559 return
559 return
560
560
561 if mark is None:
561 if mark is None:
562 if rev:
562 if rev:
563 raise util.Abort(_("bookmark name required"))
563 raise util.Abort(_("bookmark name required"))
564 if len(marks) == 0:
564 if len(marks) == 0:
565 ui.status(_("no bookmarks set\n"))
565 ui.status(_("no bookmarks set\n"))
566 else:
566 else:
567 for bmark, n in sorted(marks.iteritems()):
567 for bmark, n in sorted(marks.iteritems()):
568 current = repo._bookmarkcurrent
568 current = repo._bookmarkcurrent
569 if bmark == current and n == cur:
569 if bmark == current and n == cur:
570 prefix, label = '*', 'bookmarks.current'
570 prefix, label = '*', 'bookmarks.current'
571 else:
571 else:
572 prefix, label = ' ', ''
572 prefix, label = ' ', ''
573
573
574 if ui.quiet:
574 if ui.quiet:
575 ui.write("%s\n" % bmark, label=label)
575 ui.write("%s\n" % bmark, label=label)
576 else:
576 else:
577 ui.write(" %s %-25s %d:%s\n" % (
577 ui.write(" %s %-25s %d:%s\n" % (
578 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
578 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
579 label=label)
579 label=label)
580 return
580 return
581
581
582 def branch(ui, repo, label=None, **opts):
582 def branch(ui, repo, label=None, **opts):
583 """set or show the current branch name
583 """set or show the current branch name
584
584
585 With no argument, show the current branch name. With one argument,
585 With no argument, show the current branch name. With one argument,
586 set the working directory branch name (the branch will not exist
586 set the working directory branch name (the branch will not exist
587 in the repository until the next commit). Standard practice
587 in the repository until the next commit). Standard practice
588 recommends that primary development take place on the 'default'
588 recommends that primary development take place on the 'default'
589 branch.
589 branch.
590
590
591 Unless -f/--force is specified, branch will not let you set a
591 Unless -f/--force is specified, branch will not let you set a
592 branch name that already exists, even if it's inactive.
592 branch name that already exists, even if it's inactive.
593
593
594 Use -C/--clean to reset the working directory branch to that of
594 Use -C/--clean to reset the working directory branch to that of
595 the parent of the working directory, negating a previous branch
595 the parent of the working directory, negating a previous branch
596 change.
596 change.
597
597
598 Use the command :hg:`update` to switch to an existing branch. Use
598 Use the command :hg:`update` to switch to an existing branch. Use
599 :hg:`commit --close-branch` to mark this branch as closed.
599 :hg:`commit --close-branch` to mark this branch as closed.
600
600
601 Returns 0 on success.
601 Returns 0 on success.
602 """
602 """
603
603
604 if opts.get('clean'):
604 if opts.get('clean'):
605 label = repo[None].p1().branch()
605 label = repo[None].p1().branch()
606 repo.dirstate.setbranch(label)
606 repo.dirstate.setbranch(label)
607 ui.status(_('reset working directory to branch %s\n') % label)
607 ui.status(_('reset working directory to branch %s\n') % label)
608 elif label:
608 elif label:
609 if not opts.get('force') and label in repo.branchtags():
609 if not opts.get('force') and label in repo.branchtags():
610 if label not in [p.branch() for p in repo.parents()]:
610 if label not in [p.branch() for p in repo.parents()]:
611 raise util.Abort(_('a branch of the same name already exists'),
611 raise util.Abort(_('a branch of the same name already exists'),
612 hint=_("use 'hg update' to switch to it"))
612 hint=_("use 'hg update' to switch to it"))
613 repo.dirstate.setbranch(label)
613 repo.dirstate.setbranch(label)
614 ui.status(_('marked working directory as branch %s\n') % label)
614 ui.status(_('marked working directory as branch %s\n') % label)
615 else:
615 else:
616 ui.write("%s\n" % repo.dirstate.branch())
616 ui.write("%s\n" % repo.dirstate.branch())
617
617
618 def branches(ui, repo, active=False, closed=False):
618 def branches(ui, repo, active=False, closed=False):
619 """list repository named branches
619 """list repository named branches
620
620
621 List the repository's named branches, indicating which ones are
621 List the repository's named branches, indicating which ones are
622 inactive. If -c/--closed is specified, also list branches which have
622 inactive. If -c/--closed is specified, also list branches which have
623 been marked closed (see :hg:`commit --close-branch`).
623 been marked closed (see :hg:`commit --close-branch`).
624
624
625 If -a/--active is specified, only show active branches. A branch
625 If -a/--active is specified, only show active branches. A branch
626 is considered active if it contains repository heads.
626 is considered active if it contains repository heads.
627
627
628 Use the command :hg:`update` to switch to an existing branch.
628 Use the command :hg:`update` to switch to an existing branch.
629
629
630 Returns 0.
630 Returns 0.
631 """
631 """
632
632
633 hexfunc = ui.debugflag and hex or short
633 hexfunc = ui.debugflag and hex or short
634 activebranches = [repo[n].branch() for n in repo.heads()]
634 activebranches = [repo[n].branch() for n in repo.heads()]
635 def testactive(tag, node):
635 def testactive(tag, node):
636 realhead = tag in activebranches
636 realhead = tag in activebranches
637 open = node in repo.branchheads(tag, closed=False)
637 open = node in repo.branchheads(tag, closed=False)
638 return realhead and open
638 return realhead and open
639 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
639 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
640 for tag, node in repo.branchtags().items()],
640 for tag, node in repo.branchtags().items()],
641 reverse=True)
641 reverse=True)
642
642
643 for isactive, node, tag in branches:
643 for isactive, node, tag in branches:
644 if (not active) or isactive:
644 if (not active) or isactive:
645 if ui.quiet:
645 if ui.quiet:
646 ui.write("%s\n" % tag)
646 ui.write("%s\n" % tag)
647 else:
647 else:
648 hn = repo.lookup(node)
648 hn = repo.lookup(node)
649 if isactive:
649 if isactive:
650 label = 'branches.active'
650 label = 'branches.active'
651 notice = ''
651 notice = ''
652 elif hn not in repo.branchheads(tag, closed=False):
652 elif hn not in repo.branchheads(tag, closed=False):
653 if not closed:
653 if not closed:
654 continue
654 continue
655 label = 'branches.closed'
655 label = 'branches.closed'
656 notice = _(' (closed)')
656 notice = _(' (closed)')
657 else:
657 else:
658 label = 'branches.inactive'
658 label = 'branches.inactive'
659 notice = _(' (inactive)')
659 notice = _(' (inactive)')
660 if tag == repo.dirstate.branch():
660 if tag == repo.dirstate.branch():
661 label = 'branches.current'
661 label = 'branches.current'
662 rev = str(node).rjust(31 - encoding.colwidth(tag))
662 rev = str(node).rjust(31 - encoding.colwidth(tag))
663 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
663 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
664 tag = ui.label(tag, label)
664 tag = ui.label(tag, label)
665 ui.write("%s %s%s\n" % (tag, rev, notice))
665 ui.write("%s %s%s\n" % (tag, rev, notice))
666
666
667 def bundle(ui, repo, fname, dest=None, **opts):
667 def bundle(ui, repo, fname, dest=None, **opts):
668 """create a changegroup file
668 """create a changegroup file
669
669
670 Generate a compressed changegroup file collecting changesets not
670 Generate a compressed changegroup file collecting changesets not
671 known to be in another repository.
671 known to be in another repository.
672
672
673 If you omit the destination repository, then hg assumes the
673 If you omit the destination repository, then hg assumes the
674 destination will have all the nodes you specify with --base
674 destination will have all the nodes you specify with --base
675 parameters. To create a bundle containing all changesets, use
675 parameters. To create a bundle containing all changesets, use
676 -a/--all (or --base null).
676 -a/--all (or --base null).
677
677
678 You can change compression method with the -t/--type option.
678 You can change compression method with the -t/--type option.
679 The available compression methods are: none, bzip2, and
679 The available compression methods are: none, bzip2, and
680 gzip (by default, bundles are compressed using bzip2).
680 gzip (by default, bundles are compressed using bzip2).
681
681
682 The bundle file can then be transferred using conventional means
682 The bundle file can then be transferred using conventional means
683 and applied to another repository with the unbundle or pull
683 and applied to another repository with the unbundle or pull
684 command. This is useful when direct push and pull are not
684 command. This is useful when direct push and pull are not
685 available or when exporting an entire repository is undesirable.
685 available or when exporting an entire repository is undesirable.
686
686
687 Applying bundles preserves all changeset contents including
687 Applying bundles preserves all changeset contents including
688 permissions, copy/rename information, and revision history.
688 permissions, copy/rename information, and revision history.
689
689
690 Returns 0 on success, 1 if no changes found.
690 Returns 0 on success, 1 if no changes found.
691 """
691 """
692 revs = None
692 revs = None
693 if 'rev' in opts:
693 if 'rev' in opts:
694 revs = cmdutil.revrange(repo, opts['rev'])
694 revs = cmdutil.revrange(repo, opts['rev'])
695
695
696 if opts.get('all'):
696 if opts.get('all'):
697 base = ['null']
697 base = ['null']
698 else:
698 else:
699 base = cmdutil.revrange(repo, opts.get('base'))
699 base = cmdutil.revrange(repo, opts.get('base'))
700 if base:
700 if base:
701 if dest:
701 if dest:
702 raise util.Abort(_("--base is incompatible with specifying "
702 raise util.Abort(_("--base is incompatible with specifying "
703 "a destination"))
703 "a destination"))
704 common = [repo.lookup(rev) for rev in base]
704 common = [repo.lookup(rev) for rev in base]
705 else:
705 else:
706 dest = ui.expandpath(dest or 'default-push', dest or 'default')
706 dest = ui.expandpath(dest or 'default-push', dest or 'default')
707 dest, branches = hg.parseurl(dest, opts.get('branch'))
707 dest, branches = hg.parseurl(dest, opts.get('branch'))
708 other = hg.repository(hg.remoteui(repo, opts), dest)
708 other = hg.repository(hg.remoteui(repo, opts), dest)
709 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
709 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
710 inc = discovery.findcommonincoming(repo, other, force=opts.get('force'))
710 inc = discovery.findcommonincoming(repo, other, force=opts.get('force'))
711 common, _anyinc, _heads = inc
711 common, _anyinc, _heads = inc
712
712
713 nodes = revs and map(repo.lookup, revs) or revs
713 nodes = revs and map(repo.lookup, revs) or revs
714 cg = repo.getbundle('bundle', common=common, heads=nodes)
714 cg = repo.getbundle('bundle', common=common, heads=nodes)
715 if not cg:
715 if not cg:
716 ui.status(_("no changes found\n"))
716 ui.status(_("no changes found\n"))
717 return 1
717 return 1
718
718
719 bundletype = opts.get('type', 'bzip2').lower()
719 bundletype = opts.get('type', 'bzip2').lower()
720 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
720 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
721 bundletype = btypes.get(bundletype)
721 bundletype = btypes.get(bundletype)
722 if bundletype not in changegroup.bundletypes:
722 if bundletype not in changegroup.bundletypes:
723 raise util.Abort(_('unknown bundle type specified with --type'))
723 raise util.Abort(_('unknown bundle type specified with --type'))
724
724
725 changegroup.writebundle(cg, fname, bundletype)
725 changegroup.writebundle(cg, fname, bundletype)
726
726
727 def cat(ui, repo, file1, *pats, **opts):
727 def cat(ui, repo, file1, *pats, **opts):
728 """output the current or given revision of files
728 """output the current or given revision of files
729
729
730 Print the specified files as they were at the given revision. If
730 Print the specified files as they were at the given revision. If
731 no revision is given, the parent of the working directory is used,
731 no revision is given, the parent of the working directory is used,
732 or tip if no revision is checked out.
732 or tip if no revision is checked out.
733
733
734 Output may be to a file, in which case the name of the file is
734 Output may be to a file, in which case the name of the file is
735 given using a format string. The formatting rules are the same as
735 given using a format string. The formatting rules are the same as
736 for the export command, with the following additions:
736 for the export command, with the following additions:
737
737
738 :``%s``: basename of file being printed
738 :``%s``: basename of file being printed
739 :``%d``: dirname of file being printed, or '.' if in repository root
739 :``%d``: dirname of file being printed, or '.' if in repository root
740 :``%p``: root-relative path name of file being printed
740 :``%p``: root-relative path name of file being printed
741
741
742 Returns 0 on success.
742 Returns 0 on success.
743 """
743 """
744 ctx = cmdutil.revsingle(repo, opts.get('rev'))
744 ctx = cmdutil.revsingle(repo, opts.get('rev'))
745 err = 1
745 err = 1
746 m = cmdutil.match(repo, (file1,) + pats, opts)
746 m = cmdutil.match(repo, (file1,) + pats, opts)
747 for abs in ctx.walk(m):
747 for abs in ctx.walk(m):
748 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
748 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
749 data = ctx[abs].data()
749 data = ctx[abs].data()
750 if opts.get('decode'):
750 if opts.get('decode'):
751 data = repo.wwritedata(abs, data)
751 data = repo.wwritedata(abs, data)
752 fp.write(data)
752 fp.write(data)
753 fp.close()
753 fp.close()
754 err = 0
754 err = 0
755 return err
755 return err
756
756
757 def clone(ui, source, dest=None, **opts):
757 def clone(ui, source, dest=None, **opts):
758 """make a copy of an existing repository
758 """make a copy of an existing repository
759
759
760 Create a copy of an existing repository in a new directory.
760 Create a copy of an existing repository in a new directory.
761
761
762 If no destination directory name is specified, it defaults to the
762 If no destination directory name is specified, it defaults to the
763 basename of the source.
763 basename of the source.
764
764
765 The location of the source is added to the new repository's
765 The location of the source is added to the new repository's
766 ``.hg/hgrc`` file, as the default to be used for future pulls.
766 ``.hg/hgrc`` file, as the default to be used for future pulls.
767
767
768 See :hg:`help urls` for valid source format details.
768 See :hg:`help urls` for valid source format details.
769
769
770 It is possible to specify an ``ssh://`` URL as the destination, but no
770 It is possible to specify an ``ssh://`` URL as the destination, but no
771 ``.hg/hgrc`` and working directory will be created on the remote side.
771 ``.hg/hgrc`` and working directory will be created on the remote side.
772 Please see :hg:`help urls` for important details about ``ssh://`` URLs.
772 Please see :hg:`help urls` for important details about ``ssh://`` URLs.
773
773
774 A set of changesets (tags, or branch names) to pull may be specified
774 A set of changesets (tags, or branch names) to pull may be specified
775 by listing each changeset (tag, or branch name) with -r/--rev.
775 by listing each changeset (tag, or branch name) with -r/--rev.
776 If -r/--rev is used, the cloned repository will contain only a subset
776 If -r/--rev is used, the cloned repository will contain only a subset
777 of the changesets of the source repository. Only the set of changesets
777 of the changesets of the source repository. Only the set of changesets
778 defined by all -r/--rev options (including all their ancestors)
778 defined by all -r/--rev options (including all their ancestors)
779 will be pulled into the destination repository.
779 will be pulled into the destination repository.
780 No subsequent changesets (including subsequent tags) will be present
780 No subsequent changesets (including subsequent tags) will be present
781 in the destination.
781 in the destination.
782
782
783 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
783 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
784 local source repositories.
784 local source repositories.
785
785
786 For efficiency, hardlinks are used for cloning whenever the source
786 For efficiency, hardlinks are used for cloning whenever the source
787 and destination are on the same filesystem (note this applies only
787 and destination are on the same filesystem (note this applies only
788 to the repository data, not to the working directory). Some
788 to the repository data, not to the working directory). Some
789 filesystems, such as AFS, implement hardlinking incorrectly, but
789 filesystems, such as AFS, implement hardlinking incorrectly, but
790 do not report errors. In these cases, use the --pull option to
790 do not report errors. In these cases, use the --pull option to
791 avoid hardlinking.
791 avoid hardlinking.
792
792
793 In some cases, you can clone repositories and the working directory
793 In some cases, you can clone repositories and the working directory
794 using full hardlinks with ::
794 using full hardlinks with ::
795
795
796 $ cp -al REPO REPOCLONE
796 $ cp -al REPO REPOCLONE
797
797
798 This is the fastest way to clone, but it is not always safe. The
798 This is the fastest way to clone, but it is not always safe. The
799 operation is not atomic (making sure REPO is not modified during
799 operation is not atomic (making sure REPO is not modified during
800 the operation is up to you) and you have to make sure your editor
800 the operation is up to you) and you have to make sure your editor
801 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
801 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
802 this is not compatible with certain extensions that place their
802 this is not compatible with certain extensions that place their
803 metadata under the .hg directory, such as mq.
803 metadata under the .hg directory, such as mq.
804
804
805 Mercurial will update the working directory to the first applicable
805 Mercurial will update the working directory to the first applicable
806 revision from this list:
806 revision from this list:
807
807
808 a) null if -U or the source repository has no changesets
808 a) null if -U or the source repository has no changesets
809 b) if -u . and the source repository is local, the first parent of
809 b) if -u . and the source repository is local, the first parent of
810 the source repository's working directory
810 the source repository's working directory
811 c) the changeset specified with -u (if a branch name, this means the
811 c) the changeset specified with -u (if a branch name, this means the
812 latest head of that branch)
812 latest head of that branch)
813 d) the changeset specified with -r
813 d) the changeset specified with -r
814 e) the tipmost head specified with -b
814 e) the tipmost head specified with -b
815 f) the tipmost head specified with the url#branch source syntax
815 f) the tipmost head specified with the url#branch source syntax
816 g) the tipmost head of the default branch
816 g) the tipmost head of the default branch
817 h) tip
817 h) tip
818
818
819 Returns 0 on success.
819 Returns 0 on success.
820 """
820 """
821 if opts.get('noupdate') and opts.get('updaterev'):
821 if opts.get('noupdate') and opts.get('updaterev'):
822 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
822 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
823
823
824 r = hg.clone(hg.remoteui(ui, opts), source, dest,
824 r = hg.clone(hg.remoteui(ui, opts), source, dest,
825 pull=opts.get('pull'),
825 pull=opts.get('pull'),
826 stream=opts.get('uncompressed'),
826 stream=opts.get('uncompressed'),
827 rev=opts.get('rev'),
827 rev=opts.get('rev'),
828 update=opts.get('updaterev') or not opts.get('noupdate'),
828 update=opts.get('updaterev') or not opts.get('noupdate'),
829 branch=opts.get('branch'))
829 branch=opts.get('branch'))
830
830
831 return r is None
831 return r is None
832
832
833 def commit(ui, repo, *pats, **opts):
833 def commit(ui, repo, *pats, **opts):
834 """commit the specified files or all outstanding changes
834 """commit the specified files or all outstanding changes
835
835
836 Commit changes to the given files into the repository. Unlike a
836 Commit changes to the given files into the repository. Unlike a
837 centralized SCM, this operation is a local operation. See
837 centralized SCM, this operation is a local operation. See
838 :hg:`push` for a way to actively distribute your changes.
838 :hg:`push` for a way to actively distribute your changes.
839
839
840 If a list of files is omitted, all changes reported by :hg:`status`
840 If a list of files is omitted, all changes reported by :hg:`status`
841 will be committed.
841 will be committed.
842
842
843 If you are committing the result of a merge, do not provide any
843 If you are committing the result of a merge, do not provide any
844 filenames or -I/-X filters.
844 filenames or -I/-X filters.
845
845
846 If no commit message is specified, Mercurial starts your
846 If no commit message is specified, Mercurial starts your
847 configured editor where you can enter a message. In case your
847 configured editor where you can enter a message. In case your
848 commit fails, you will find a backup of your message in
848 commit fails, you will find a backup of your message in
849 ``.hg/last-message.txt``.
849 ``.hg/last-message.txt``.
850
850
851 See :hg:`help dates` for a list of formats valid for -d/--date.
851 See :hg:`help dates` for a list of formats valid for -d/--date.
852
852
853 Returns 0 on success, 1 if nothing changed.
853 Returns 0 on success, 1 if nothing changed.
854 """
854 """
855 extra = {}
855 extra = {}
856 if opts.get('close_branch'):
856 if opts.get('close_branch'):
857 if repo['.'].node() not in repo.branchheads():
857 if repo['.'].node() not in repo.branchheads():
858 # The topo heads set is included in the branch heads set of the
858 # The topo heads set is included in the branch heads set of the
859 # current branch, so it's sufficient to test branchheads
859 # current branch, so it's sufficient to test branchheads
860 raise util.Abort(_('can only close branch heads'))
860 raise util.Abort(_('can only close branch heads'))
861 extra['close'] = 1
861 extra['close'] = 1
862 e = cmdutil.commiteditor
862 e = cmdutil.commiteditor
863 if opts.get('force_editor'):
863 if opts.get('force_editor'):
864 e = cmdutil.commitforceeditor
864 e = cmdutil.commitforceeditor
865
865
866 def commitfunc(ui, repo, message, match, opts):
866 def commitfunc(ui, repo, message, match, opts):
867 return repo.commit(message, opts.get('user'), opts.get('date'), match,
867 return repo.commit(message, opts.get('user'), opts.get('date'), match,
868 editor=e, extra=extra)
868 editor=e, extra=extra)
869
869
870 branch = repo[None].branch()
870 branch = repo[None].branch()
871 bheads = repo.branchheads(branch)
871 bheads = repo.branchheads(branch)
872
872
873 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
873 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
874 if not node:
874 if not node:
875 stat = repo.status(match=cmdutil.match(repo, pats, opts))
875 stat = repo.status(match=cmdutil.match(repo, pats, opts))
876 if stat[3]:
876 if stat[3]:
877 ui.status(_("nothing changed (%d missing files, see 'hg status')\n")
877 ui.status(_("nothing changed (%d missing files, see 'hg status')\n")
878 % len(stat[3]))
878 % len(stat[3]))
879 else:
879 else:
880 ui.status(_("nothing changed\n"))
880 ui.status(_("nothing changed\n"))
881 return 1
881 return 1
882
882
883 ctx = repo[node]
883 ctx = repo[node]
884 parents = ctx.parents()
884 parents = ctx.parents()
885
885
886 if bheads and not [x for x in parents
886 if bheads and not [x for x in parents
887 if x.node() in bheads and x.branch() == branch]:
887 if x.node() in bheads and x.branch() == branch]:
888 ui.status(_('created new head\n'))
888 ui.status(_('created new head\n'))
889 # The message is not printed for initial roots. For the other
889 # The message is not printed for initial roots. For the other
890 # changesets, it is printed in the following situations:
890 # changesets, it is printed in the following situations:
891 #
891 #
892 # Par column: for the 2 parents with ...
892 # Par column: for the 2 parents with ...
893 # N: null or no parent
893 # N: null or no parent
894 # B: parent is on another named branch
894 # B: parent is on another named branch
895 # C: parent is a regular non head changeset
895 # C: parent is a regular non head changeset
896 # H: parent was a branch head of the current branch
896 # H: parent was a branch head of the current branch
897 # Msg column: whether we print "created new head" message
897 # Msg column: whether we print "created new head" message
898 # In the following, it is assumed that there already exists some
898 # In the following, it is assumed that there already exists some
899 # initial branch heads of the current branch, otherwise nothing is
899 # initial branch heads of the current branch, otherwise nothing is
900 # printed anyway.
900 # printed anyway.
901 #
901 #
902 # Par Msg Comment
902 # Par Msg Comment
903 # NN y additional topo root
903 # NN y additional topo root
904 #
904 #
905 # BN y additional branch root
905 # BN y additional branch root
906 # CN y additional topo head
906 # CN y additional topo head
907 # HN n usual case
907 # HN n usual case
908 #
908 #
909 # BB y weird additional branch root
909 # BB y weird additional branch root
910 # CB y branch merge
910 # CB y branch merge
911 # HB n merge with named branch
911 # HB n merge with named branch
912 #
912 #
913 # CC y additional head from merge
913 # CC y additional head from merge
914 # CH n merge with a head
914 # CH n merge with a head
915 #
915 #
916 # HH n head merge: head count decreases
916 # HH n head merge: head count decreases
917
917
918 if not opts.get('close_branch'):
918 if not opts.get('close_branch'):
919 for r in parents:
919 for r in parents:
920 if r.extra().get('close') and r.branch() == branch:
920 if r.extra().get('close') and r.branch() == branch:
921 ui.status(_('reopening closed branch head %d\n') % r)
921 ui.status(_('reopening closed branch head %d\n') % r)
922
922
923 if ui.debugflag:
923 if ui.debugflag:
924 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
924 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
925 elif ui.verbose:
925 elif ui.verbose:
926 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
926 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
927
927
928 def copy(ui, repo, *pats, **opts):
928 def copy(ui, repo, *pats, **opts):
929 """mark files as copied for the next commit
929 """mark files as copied for the next commit
930
930
931 Mark dest as having copies of source files. If dest is a
931 Mark dest as having copies of source files. If dest is a
932 directory, copies are put in that directory. If dest is a file,
932 directory, copies are put in that directory. If dest is a file,
933 the source must be a single file.
933 the source must be a single file.
934
934
935 By default, this command copies the contents of files as they
935 By default, this command copies the contents of files as they
936 exist in the working directory. If invoked with -A/--after, the
936 exist in the working directory. If invoked with -A/--after, the
937 operation is recorded, but no copying is performed.
937 operation is recorded, but no copying is performed.
938
938
939 This command takes effect with the next commit. To undo a copy
939 This command takes effect with the next commit. To undo a copy
940 before that, see :hg:`revert`.
940 before that, see :hg:`revert`.
941
941
942 Returns 0 on success, 1 if errors are encountered.
942 Returns 0 on success, 1 if errors are encountered.
943 """
943 """
944 wlock = repo.wlock(False)
944 wlock = repo.wlock(False)
945 try:
945 try:
946 return cmdutil.copy(ui, repo, pats, opts)
946 return cmdutil.copy(ui, repo, pats, opts)
947 finally:
947 finally:
948 wlock.release()
948 wlock.release()
949
949
950 def debugancestor(ui, repo, *args):
950 def debugancestor(ui, repo, *args):
951 """find the ancestor revision of two revisions in a given index"""
951 """find the ancestor revision of two revisions in a given index"""
952 if len(args) == 3:
952 if len(args) == 3:
953 index, rev1, rev2 = args
953 index, rev1, rev2 = args
954 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
954 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
955 lookup = r.lookup
955 lookup = r.lookup
956 elif len(args) == 2:
956 elif len(args) == 2:
957 if not repo:
957 if not repo:
958 raise util.Abort(_("there is no Mercurial repository here "
958 raise util.Abort(_("there is no Mercurial repository here "
959 "(.hg not found)"))
959 "(.hg not found)"))
960 rev1, rev2 = args
960 rev1, rev2 = args
961 r = repo.changelog
961 r = repo.changelog
962 lookup = repo.lookup
962 lookup = repo.lookup
963 else:
963 else:
964 raise util.Abort(_('either two or three arguments required'))
964 raise util.Abort(_('either two or three arguments required'))
965 a = r.ancestor(lookup(rev1), lookup(rev2))
965 a = r.ancestor(lookup(rev1), lookup(rev2))
966 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
966 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
967
967
968 def debugbuilddag(ui, repo, text,
968 def debugbuilddag(ui, repo, text,
969 mergeable_file=False,
969 mergeable_file=False,
970 overwritten_file=False,
970 overwritten_file=False,
971 new_file=False):
971 new_file=False):
972 """builds a repo with a given dag from scratch in the current empty repo
972 """builds a repo with a given dag from scratch in the current empty repo
973
973
974 Elements:
974 Elements:
975
975
976 - "+n" is a linear run of n nodes based on the current default parent
976 - "+n" is a linear run of n nodes based on the current default parent
977 - "." is a single node based on the current default parent
977 - "." is a single node based on the current default parent
978 - "$" resets the default parent to null (implied at the start);
978 - "$" resets the default parent to null (implied at the start);
979 otherwise the default parent is always the last node created
979 otherwise the default parent is always the last node created
980 - "<p" sets the default parent to the backref p
980 - "<p" sets the default parent to the backref p
981 - "*p" is a fork at parent p, which is a backref
981 - "*p" is a fork at parent p, which is a backref
982 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
982 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
983 - "/p2" is a merge of the preceding node and p2
983 - "/p2" is a merge of the preceding node and p2
984 - ":tag" defines a local tag for the preceding node
984 - ":tag" defines a local tag for the preceding node
985 - "@branch" sets the named branch for subsequent nodes
985 - "@branch" sets the named branch for subsequent nodes
986 - "#...\\n" is a comment up to the end of the line
986 - "#...\\n" is a comment up to the end of the line
987
987
988 Whitespace between the above elements is ignored.
988 Whitespace between the above elements is ignored.
989
989
990 A backref is either
990 A backref is either
991
991
992 - a number n, which references the node curr-n, where curr is the current
992 - a number n, which references the node curr-n, where curr is the current
993 node, or
993 node, or
994 - the name of a local tag you placed earlier using ":tag", or
994 - the name of a local tag you placed earlier using ":tag", or
995 - empty to denote the default parent.
995 - empty to denote the default parent.
996
996
997 All string valued-elements are either strictly alphanumeric, or must
997 All string valued-elements are either strictly alphanumeric, or must
998 be enclosed in double quotes ("..."), with "\\" as escape character.
998 be enclosed in double quotes ("..."), with "\\" as escape character.
999 """
999 """
1000
1000
1001 cl = repo.changelog
1001 cl = repo.changelog
1002 if len(cl) > 0:
1002 if len(cl) > 0:
1003 raise util.Abort(_('repository is not empty'))
1003 raise util.Abort(_('repository is not empty'))
1004
1004
1005 if mergeable_file:
1005 if mergeable_file:
1006 linesperrev = 2
1006 linesperrev = 2
1007 # determine number of revs in DAG
1007 # determine number of revs in DAG
1008 n = 0
1008 n = 0
1009 for type, data in dagparser.parsedag(text):
1009 for type, data in dagparser.parsedag(text):
1010 if type == 'n':
1010 if type == 'n':
1011 n += 1
1011 n += 1
1012 # make a file with k lines per rev
1012 # make a file with k lines per rev
1013 initialmergedlines = [str(i) for i in xrange(0, n * linesperrev)]
1013 initialmergedlines = [str(i) for i in xrange(0, n * linesperrev)]
1014 initialmergedlines.append("")
1014 initialmergedlines.append("")
1015
1015
1016 tags = []
1016 tags = []
1017
1017
1018 tr = repo.transaction("builddag")
1018 tr = repo.transaction("builddag")
1019 try:
1019 try:
1020
1020
1021 at = -1
1021 at = -1
1022 atbranch = 'default'
1022 atbranch = 'default'
1023 nodeids = []
1023 nodeids = []
1024 for type, data in dagparser.parsedag(text):
1024 for type, data in dagparser.parsedag(text):
1025 if type == 'n':
1025 if type == 'n':
1026 ui.note('node %s\n' % str(data))
1026 ui.note('node %s\n' % str(data))
1027 id, ps = data
1027 id, ps = data
1028
1028
1029 files = []
1029 files = []
1030 fctxs = {}
1030 fctxs = {}
1031
1031
1032 p2 = None
1032 p2 = None
1033 if mergeable_file:
1033 if mergeable_file:
1034 fn = "mf"
1034 fn = "mf"
1035 p1 = repo[ps[0]]
1035 p1 = repo[ps[0]]
1036 if len(ps) > 1:
1036 if len(ps) > 1:
1037 p2 = repo[ps[1]]
1037 p2 = repo[ps[1]]
1038 pa = p1.ancestor(p2)
1038 pa = p1.ancestor(p2)
1039 base, local, other = [x[fn].data() for x in pa, p1, p2]
1039 base, local, other = [x[fn].data() for x in pa, p1, p2]
1040 m3 = simplemerge.Merge3Text(base, local, other)
1040 m3 = simplemerge.Merge3Text(base, local, other)
1041 ml = [l.strip() for l in m3.merge_lines()]
1041 ml = [l.strip() for l in m3.merge_lines()]
1042 ml.append("")
1042 ml.append("")
1043 elif at > 0:
1043 elif at > 0:
1044 ml = p1[fn].data().split("\n")
1044 ml = p1[fn].data().split("\n")
1045 else:
1045 else:
1046 ml = initialmergedlines
1046 ml = initialmergedlines
1047 ml[id * linesperrev] += " r%i" % id
1047 ml[id * linesperrev] += " r%i" % id
1048 mergedtext = "\n".join(ml)
1048 mergedtext = "\n".join(ml)
1049 files.append(fn)
1049 files.append(fn)
1050 fctxs[fn] = context.memfilectx(fn, mergedtext)
1050 fctxs[fn] = context.memfilectx(fn, mergedtext)
1051
1051
1052 if overwritten_file:
1052 if overwritten_file:
1053 fn = "of"
1053 fn = "of"
1054 files.append(fn)
1054 files.append(fn)
1055 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1055 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1056
1056
1057 if new_file:
1057 if new_file:
1058 fn = "nf%i" % id
1058 fn = "nf%i" % id
1059 files.append(fn)
1059 files.append(fn)
1060 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1060 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1061 if len(ps) > 1:
1061 if len(ps) > 1:
1062 if not p2:
1062 if not p2:
1063 p2 = repo[ps[1]]
1063 p2 = repo[ps[1]]
1064 for fn in p2:
1064 for fn in p2:
1065 if fn.startswith("nf"):
1065 if fn.startswith("nf"):
1066 files.append(fn)
1066 files.append(fn)
1067 fctxs[fn] = p2[fn]
1067 fctxs[fn] = p2[fn]
1068
1068
1069 def fctxfn(repo, cx, path):
1069 def fctxfn(repo, cx, path):
1070 return fctxs.get(path)
1070 return fctxs.get(path)
1071
1071
1072 if len(ps) == 0 or ps[0] < 0:
1072 if len(ps) == 0 or ps[0] < 0:
1073 pars = [None, None]
1073 pars = [None, None]
1074 elif len(ps) == 1:
1074 elif len(ps) == 1:
1075 pars = [nodeids[ps[0]], None]
1075 pars = [nodeids[ps[0]], None]
1076 else:
1076 else:
1077 pars = [nodeids[p] for p in ps]
1077 pars = [nodeids[p] for p in ps]
1078 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1078 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1079 date=(id, 0),
1079 date=(id, 0),
1080 user="debugbuilddag",
1080 user="debugbuilddag",
1081 extra={'branch': atbranch})
1081 extra={'branch': atbranch})
1082 nodeid = repo.commitctx(cx)
1082 nodeid = repo.commitctx(cx)
1083 nodeids.append(nodeid)
1083 nodeids.append(nodeid)
1084 at = id
1084 at = id
1085 elif type == 'l':
1085 elif type == 'l':
1086 id, name = data
1086 id, name = data
1087 ui.note('tag %s\n' % name)
1087 ui.note('tag %s\n' % name)
1088 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1088 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1089 elif type == 'a':
1089 elif type == 'a':
1090 ui.note('branch %s\n' % data)
1090 ui.note('branch %s\n' % data)
1091 atbranch = data
1091 atbranch = data
1092 tr.close()
1092 tr.close()
1093 finally:
1093 finally:
1094 tr.release()
1094 tr.release()
1095
1095
1096 if tags:
1096 if tags:
1097 tagsf = repo.opener("localtags", "w")
1097 tagsf = repo.opener("localtags", "w")
1098 try:
1098 try:
1099 tagsf.write("".join(tags))
1099 tagsf.write("".join(tags))
1100 finally:
1100 finally:
1101 tagsf.close()
1101 tagsf.close()
1102
1102
1103 def debugcommands(ui, cmd='', *args):
1103 def debugcommands(ui, cmd='', *args):
1104 """list all available commands and options"""
1104 """list all available commands and options"""
1105 for cmd, vals in sorted(table.iteritems()):
1105 for cmd, vals in sorted(table.iteritems()):
1106 cmd = cmd.split('|')[0].strip('^')
1106 cmd = cmd.split('|')[0].strip('^')
1107 opts = ', '.join([i[1] for i in vals[1]])
1107 opts = ', '.join([i[1] for i in vals[1]])
1108 ui.write('%s: %s\n' % (cmd, opts))
1108 ui.write('%s: %s\n' % (cmd, opts))
1109
1109
1110 def debugcomplete(ui, cmd='', **opts):
1110 def debugcomplete(ui, cmd='', **opts):
1111 """returns the completion list associated with the given command"""
1111 """returns the completion list associated with the given command"""
1112
1112
1113 if opts.get('options'):
1113 if opts.get('options'):
1114 options = []
1114 options = []
1115 otables = [globalopts]
1115 otables = [globalopts]
1116 if cmd:
1116 if cmd:
1117 aliases, entry = cmdutil.findcmd(cmd, table, False)
1117 aliases, entry = cmdutil.findcmd(cmd, table, False)
1118 otables.append(entry[1])
1118 otables.append(entry[1])
1119 for t in otables:
1119 for t in otables:
1120 for o in t:
1120 for o in t:
1121 if "(DEPRECATED)" in o[3]:
1121 if "(DEPRECATED)" in o[3]:
1122 continue
1122 continue
1123 if o[0]:
1123 if o[0]:
1124 options.append('-%s' % o[0])
1124 options.append('-%s' % o[0])
1125 options.append('--%s' % o[1])
1125 options.append('--%s' % o[1])
1126 ui.write("%s\n" % "\n".join(options))
1126 ui.write("%s\n" % "\n".join(options))
1127 return
1127 return
1128
1128
1129 cmdlist = cmdutil.findpossible(cmd, table)
1129 cmdlist = cmdutil.findpossible(cmd, table)
1130 if ui.verbose:
1130 if ui.verbose:
1131 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1131 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1132 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1132 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1133
1133
1134 def debugfsinfo(ui, path = "."):
1134 def debugfsinfo(ui, path = "."):
1135 """show information detected about current filesystem"""
1135 """show information detected about current filesystem"""
1136 util.writefile('.debugfsinfo', '')
1136 util.writefile('.debugfsinfo', '')
1137 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1137 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1138 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1138 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1139 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1139 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1140 and 'yes' or 'no'))
1140 and 'yes' or 'no'))
1141 os.unlink('.debugfsinfo')
1141 os.unlink('.debugfsinfo')
1142
1142
1143 def debugrebuildstate(ui, repo, rev="tip"):
1143 def debugrebuildstate(ui, repo, rev="tip"):
1144 """rebuild the dirstate as it would look like for the given revision"""
1144 """rebuild the dirstate as it would look like for the given revision"""
1145 ctx = cmdutil.revsingle(repo, rev)
1145 ctx = cmdutil.revsingle(repo, rev)
1146 wlock = repo.wlock()
1146 wlock = repo.wlock()
1147 try:
1147 try:
1148 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1148 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1149 finally:
1149 finally:
1150 wlock.release()
1150 wlock.release()
1151
1151
1152 def debugcheckstate(ui, repo):
1152 def debugcheckstate(ui, repo):
1153 """validate the correctness of the current dirstate"""
1153 """validate the correctness of the current dirstate"""
1154 parent1, parent2 = repo.dirstate.parents()
1154 parent1, parent2 = repo.dirstate.parents()
1155 m1 = repo[parent1].manifest()
1155 m1 = repo[parent1].manifest()
1156 m2 = repo[parent2].manifest()
1156 m2 = repo[parent2].manifest()
1157 errors = 0
1157 errors = 0
1158 for f in repo.dirstate:
1158 for f in repo.dirstate:
1159 state = repo.dirstate[f]
1159 state = repo.dirstate[f]
1160 if state in "nr" and f not in m1:
1160 if state in "nr" and f not in m1:
1161 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1161 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1162 errors += 1
1162 errors += 1
1163 if state in "a" and f in m1:
1163 if state in "a" and f in m1:
1164 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1164 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1165 errors += 1
1165 errors += 1
1166 if state in "m" and f not in m1 and f not in m2:
1166 if state in "m" and f not in m1 and f not in m2:
1167 ui.warn(_("%s in state %s, but not in either manifest\n") %
1167 ui.warn(_("%s in state %s, but not in either manifest\n") %
1168 (f, state))
1168 (f, state))
1169 errors += 1
1169 errors += 1
1170 for f in m1:
1170 for f in m1:
1171 state = repo.dirstate[f]
1171 state = repo.dirstate[f]
1172 if state not in "nrm":
1172 if state not in "nrm":
1173 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1173 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1174 errors += 1
1174 errors += 1
1175 if errors:
1175 if errors:
1176 error = _(".hg/dirstate inconsistent with current parent's manifest")
1176 error = _(".hg/dirstate inconsistent with current parent's manifest")
1177 raise util.Abort(error)
1177 raise util.Abort(error)
1178
1178
1179 def showconfig(ui, repo, *values, **opts):
1179 def showconfig(ui, repo, *values, **opts):
1180 """show combined config settings from all hgrc files
1180 """show combined config settings from all hgrc files
1181
1181
1182 With no arguments, print names and values of all config items.
1182 With no arguments, print names and values of all config items.
1183
1183
1184 With one argument of the form section.name, print just the value
1184 With one argument of the form section.name, print just the value
1185 of that config item.
1185 of that config item.
1186
1186
1187 With multiple arguments, print names and values of all config
1187 With multiple arguments, print names and values of all config
1188 items with matching section names.
1188 items with matching section names.
1189
1189
1190 With --debug, the source (filename and line number) is printed
1190 With --debug, the source (filename and line number) is printed
1191 for each config item.
1191 for each config item.
1192
1192
1193 Returns 0 on success.
1193 Returns 0 on success.
1194 """
1194 """
1195
1195
1196 for f in scmutil.rcpath():
1196 for f in scmutil.rcpath():
1197 ui.debug(_('read config from: %s\n') % f)
1197 ui.debug(_('read config from: %s\n') % f)
1198 untrusted = bool(opts.get('untrusted'))
1198 untrusted = bool(opts.get('untrusted'))
1199 if values:
1199 if values:
1200 sections = [v for v in values if '.' not in v]
1200 sections = [v for v in values if '.' not in v]
1201 items = [v for v in values if '.' in v]
1201 items = [v for v in values if '.' in v]
1202 if len(items) > 1 or items and sections:
1202 if len(items) > 1 or items and sections:
1203 raise util.Abort(_('only one config item permitted'))
1203 raise util.Abort(_('only one config item permitted'))
1204 for section, name, value in ui.walkconfig(untrusted=untrusted):
1204 for section, name, value in ui.walkconfig(untrusted=untrusted):
1205 value = str(value).replace('\n', '\\n')
1205 value = str(value).replace('\n', '\\n')
1206 sectname = section + '.' + name
1206 sectname = section + '.' + name
1207 if values:
1207 if values:
1208 for v in values:
1208 for v in values:
1209 if v == section:
1209 if v == section:
1210 ui.debug('%s: ' %
1210 ui.debug('%s: ' %
1211 ui.configsource(section, name, untrusted))
1211 ui.configsource(section, name, untrusted))
1212 ui.write('%s=%s\n' % (sectname, value))
1212 ui.write('%s=%s\n' % (sectname, value))
1213 elif v == sectname:
1213 elif v == sectname:
1214 ui.debug('%s: ' %
1214 ui.debug('%s: ' %
1215 ui.configsource(section, name, untrusted))
1215 ui.configsource(section, name, untrusted))
1216 ui.write(value, '\n')
1216 ui.write(value, '\n')
1217 else:
1217 else:
1218 ui.debug('%s: ' %
1218 ui.debug('%s: ' %
1219 ui.configsource(section, name, untrusted))
1219 ui.configsource(section, name, untrusted))
1220 ui.write('%s=%s\n' % (sectname, value))
1220 ui.write('%s=%s\n' % (sectname, value))
1221
1221
1222 def debugknown(ui, repopath, *ids, **opts):
1222 def debugknown(ui, repopath, *ids, **opts):
1223 """test whether node ids are known to a repo
1223 """test whether node ids are known to a repo
1224
1224
1225 Every ID must be a full-length hex node id string. Returns a list of 0s and 1s
1225 Every ID must be a full-length hex node id string. Returns a list of 0s and 1s
1226 indicating unknown/known.
1226 indicating unknown/known.
1227 """
1227 """
1228 repo = hg.repository(ui, repopath)
1228 repo = hg.repository(ui, repopath)
1229 if not repo.capable('known'):
1229 if not repo.capable('known'):
1230 raise util.Abort("known() not supported by target repository")
1230 raise util.Abort("known() not supported by target repository")
1231 flags = repo.known([bin(s) for s in ids])
1231 flags = repo.known([bin(s) for s in ids])
1232 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1232 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1233
1233
1234 def debugbundle(ui, bundlepath, all=None, **opts):
1234 def debugbundle(ui, bundlepath, all=None, **opts):
1235 """lists the contents of a bundle"""
1235 """lists the contents of a bundle"""
1236 f = url.open(ui, bundlepath)
1236 f = url.open(ui, bundlepath)
1237 try:
1237 try:
1238 gen = changegroup.readbundle(f, bundlepath)
1238 gen = changegroup.readbundle(f, bundlepath)
1239 if all:
1239 if all:
1240 ui.write("format: id, p1, p2, cset, delta base, len(delta)\n")
1240 ui.write("format: id, p1, p2, cset, delta base, len(delta)\n")
1241
1241
1242 def showchunks(named):
1242 def showchunks(named):
1243 ui.write("\n%s\n" % named)
1243 ui.write("\n%s\n" % named)
1244 chain = None
1244 chain = None
1245 while 1:
1245 while 1:
1246 chunkdata = gen.deltachunk(chain)
1246 chunkdata = gen.deltachunk(chain)
1247 if not chunkdata:
1247 if not chunkdata:
1248 break
1248 break
1249 node = chunkdata['node']
1249 node = chunkdata['node']
1250 p1 = chunkdata['p1']
1250 p1 = chunkdata['p1']
1251 p2 = chunkdata['p2']
1251 p2 = chunkdata['p2']
1252 cs = chunkdata['cs']
1252 cs = chunkdata['cs']
1253 deltabase = chunkdata['deltabase']
1253 deltabase = chunkdata['deltabase']
1254 delta = chunkdata['delta']
1254 delta = chunkdata['delta']
1255 ui.write("%s %s %s %s %s %s\n" %
1255 ui.write("%s %s %s %s %s %s\n" %
1256 (hex(node), hex(p1), hex(p2),
1256 (hex(node), hex(p1), hex(p2),
1257 hex(cs), hex(deltabase), len(delta)))
1257 hex(cs), hex(deltabase), len(delta)))
1258 chain = node
1258 chain = node
1259
1259
1260 chunkdata = gen.changelogheader()
1260 chunkdata = gen.changelogheader()
1261 showchunks("changelog")
1261 showchunks("changelog")
1262 chunkdata = gen.manifestheader()
1262 chunkdata = gen.manifestheader()
1263 showchunks("manifest")
1263 showchunks("manifest")
1264 while 1:
1264 while 1:
1265 chunkdata = gen.filelogheader()
1265 chunkdata = gen.filelogheader()
1266 if not chunkdata:
1266 if not chunkdata:
1267 break
1267 break
1268 fname = chunkdata['filename']
1268 fname = chunkdata['filename']
1269 showchunks(fname)
1269 showchunks(fname)
1270 else:
1270 else:
1271 chunkdata = gen.changelogheader()
1271 chunkdata = gen.changelogheader()
1272 chain = None
1272 chain = None
1273 while 1:
1273 while 1:
1274 chunkdata = gen.deltachunk(chain)
1274 chunkdata = gen.deltachunk(chain)
1275 if not chunkdata:
1275 if not chunkdata:
1276 break
1276 break
1277 node = chunkdata['node']
1277 node = chunkdata['node']
1278 ui.write("%s\n" % hex(node))
1278 ui.write("%s\n" % hex(node))
1279 chain = node
1279 chain = node
1280 finally:
1280 finally:
1281 f.close()
1281 f.close()
1282
1282
1283 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1283 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1284 """retrieves a bundle from a repo
1284 """retrieves a bundle from a repo
1285
1285
1286 Every ID must be a full-length hex node id string. Saves the bundle to the
1286 Every ID must be a full-length hex node id string. Saves the bundle to the
1287 given file.
1287 given file.
1288 """
1288 """
1289 repo = hg.repository(ui, repopath)
1289 repo = hg.repository(ui, repopath)
1290 if not repo.capable('getbundle'):
1290 if not repo.capable('getbundle'):
1291 raise util.Abort("getbundle() not supported by target repository")
1291 raise util.Abort("getbundle() not supported by target repository")
1292 args = {}
1292 args = {}
1293 if common:
1293 if common:
1294 args['common'] = [bin(s) for s in common]
1294 args['common'] = [bin(s) for s in common]
1295 if head:
1295 if head:
1296 args['heads'] = [bin(s) for s in head]
1296 args['heads'] = [bin(s) for s in head]
1297 bundle = repo.getbundle('debug', **args)
1297 bundle = repo.getbundle('debug', **args)
1298
1298
1299 bundletype = opts.get('type', 'bzip2').lower()
1299 bundletype = opts.get('type', 'bzip2').lower()
1300 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1300 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1301 bundletype = btypes.get(bundletype)
1301 bundletype = btypes.get(bundletype)
1302 if bundletype not in changegroup.bundletypes:
1302 if bundletype not in changegroup.bundletypes:
1303 raise util.Abort(_('unknown bundle type specified with --type'))
1303 raise util.Abort(_('unknown bundle type specified with --type'))
1304 changegroup.writebundle(bundle, bundlepath, bundletype)
1304 changegroup.writebundle(bundle, bundlepath, bundletype)
1305
1305
1306 def debugpushkey(ui, repopath, namespace, *keyinfo):
1306 def debugpushkey(ui, repopath, namespace, *keyinfo):
1307 '''access the pushkey key/value protocol
1307 '''access the pushkey key/value protocol
1308
1308
1309 With two args, list the keys in the given namespace.
1309 With two args, list the keys in the given namespace.
1310
1310
1311 With five args, set a key to new if it currently is set to old.
1311 With five args, set a key to new if it currently is set to old.
1312 Reports success or failure.
1312 Reports success or failure.
1313 '''
1313 '''
1314
1314
1315 target = hg.repository(ui, repopath)
1315 target = hg.repository(ui, repopath)
1316 if keyinfo:
1316 if keyinfo:
1317 key, old, new = keyinfo
1317 key, old, new = keyinfo
1318 r = target.pushkey(namespace, key, old, new)
1318 r = target.pushkey(namespace, key, old, new)
1319 ui.status(str(r) + '\n')
1319 ui.status(str(r) + '\n')
1320 return not r
1320 return not r
1321 else:
1321 else:
1322 for k, v in target.listkeys(namespace).iteritems():
1322 for k, v in target.listkeys(namespace).iteritems():
1323 ui.write("%s\t%s\n" % (k.encode('string-escape'),
1323 ui.write("%s\t%s\n" % (k.encode('string-escape'),
1324 v.encode('string-escape')))
1324 v.encode('string-escape')))
1325
1325
1326 def debugrevspec(ui, repo, expr):
1326 def debugrevspec(ui, repo, expr):
1327 '''parse and apply a revision specification'''
1327 '''parse and apply a revision specification'''
1328 if ui.verbose:
1328 if ui.verbose:
1329 tree = revset.parse(expr)[0]
1329 tree = revset.parse(expr)[0]
1330 ui.note(tree, "\n")
1330 ui.note(tree, "\n")
1331 newtree = revset.findaliases(ui, tree)
1331 newtree = revset.findaliases(ui, tree)
1332 if newtree != tree:
1332 if newtree != tree:
1333 ui.note(newtree, "\n")
1333 ui.note(newtree, "\n")
1334 func = revset.match(ui, expr)
1334 func = revset.match(ui, expr)
1335 for c in func(repo, range(len(repo))):
1335 for c in func(repo, range(len(repo))):
1336 ui.write("%s\n" % c)
1336 ui.write("%s\n" % c)
1337
1337
1338 def debugsetparents(ui, repo, rev1, rev2=None):
1338 def debugsetparents(ui, repo, rev1, rev2=None):
1339 """manually set the parents of the current working directory
1339 """manually set the parents of the current working directory
1340
1340
1341 This is useful for writing repository conversion tools, but should
1341 This is useful for writing repository conversion tools, but should
1342 be used with care.
1342 be used with care.
1343
1343
1344 Returns 0 on success.
1344 Returns 0 on success.
1345 """
1345 """
1346
1346
1347 r1 = cmdutil.revsingle(repo, rev1).node()
1347 r1 = cmdutil.revsingle(repo, rev1).node()
1348 r2 = cmdutil.revsingle(repo, rev2, 'null').node()
1348 r2 = cmdutil.revsingle(repo, rev2, 'null').node()
1349
1349
1350 wlock = repo.wlock()
1350 wlock = repo.wlock()
1351 try:
1351 try:
1352 repo.dirstate.setparents(r1, r2)
1352 repo.dirstate.setparents(r1, r2)
1353 finally:
1353 finally:
1354 wlock.release()
1354 wlock.release()
1355
1355
1356 def debugstate(ui, repo, nodates=None, datesort=None):
1356 def debugstate(ui, repo, nodates=None, datesort=None):
1357 """show the contents of the current dirstate"""
1357 """show the contents of the current dirstate"""
1358 timestr = ""
1358 timestr = ""
1359 showdate = not nodates
1359 showdate = not nodates
1360 if datesort:
1360 if datesort:
1361 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
1361 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
1362 else:
1362 else:
1363 keyfunc = None # sort by filename
1363 keyfunc = None # sort by filename
1364 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
1364 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
1365 if showdate:
1365 if showdate:
1366 if ent[3] == -1:
1366 if ent[3] == -1:
1367 # Pad or slice to locale representation
1367 # Pad or slice to locale representation
1368 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
1368 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
1369 time.localtime(0)))
1369 time.localtime(0)))
1370 timestr = 'unset'
1370 timestr = 'unset'
1371 timestr = (timestr[:locale_len] +
1371 timestr = (timestr[:locale_len] +
1372 ' ' * (locale_len - len(timestr)))
1372 ' ' * (locale_len - len(timestr)))
1373 else:
1373 else:
1374 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
1374 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
1375 time.localtime(ent[3]))
1375 time.localtime(ent[3]))
1376 if ent[1] & 020000:
1376 if ent[1] & 020000:
1377 mode = 'lnk'
1377 mode = 'lnk'
1378 else:
1378 else:
1379 mode = '%3o' % (ent[1] & 0777)
1379 mode = '%3o' % (ent[1] & 0777)
1380 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
1380 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
1381 for f in repo.dirstate.copies():
1381 for f in repo.dirstate.copies():
1382 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
1382 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
1383
1383
1384 def debugsub(ui, repo, rev=None):
1384 def debugsub(ui, repo, rev=None):
1385 ctx = cmdutil.revsingle(repo, rev, None)
1385 ctx = cmdutil.revsingle(repo, rev, None)
1386 for k, v in sorted(ctx.substate.items()):
1386 for k, v in sorted(ctx.substate.items()):
1387 ui.write('path %s\n' % k)
1387 ui.write('path %s\n' % k)
1388 ui.write(' source %s\n' % v[0])
1388 ui.write(' source %s\n' % v[0])
1389 ui.write(' revision %s\n' % v[1])
1389 ui.write(' revision %s\n' % v[1])
1390
1390
1391 def debugdag(ui, repo, file_=None, *revs, **opts):
1391 def debugdag(ui, repo, file_=None, *revs, **opts):
1392 """format the changelog or an index DAG as a concise textual description
1392 """format the changelog or an index DAG as a concise textual description
1393
1393
1394 If you pass a revlog index, the revlog's DAG is emitted. If you list
1394 If you pass a revlog index, the revlog's DAG is emitted. If you list
1395 revision numbers, they get labelled in the output as rN.
1395 revision numbers, they get labelled in the output as rN.
1396
1396
1397 Otherwise, the changelog DAG of the current repo is emitted.
1397 Otherwise, the changelog DAG of the current repo is emitted.
1398 """
1398 """
1399 spaces = opts.get('spaces')
1399 spaces = opts.get('spaces')
1400 dots = opts.get('dots')
1400 dots = opts.get('dots')
1401 if file_:
1401 if file_:
1402 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1402 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1403 revs = set((int(r) for r in revs))
1403 revs = set((int(r) for r in revs))
1404 def events():
1404 def events():
1405 for r in rlog:
1405 for r in rlog:
1406 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1406 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1407 if r in revs:
1407 if r in revs:
1408 yield 'l', (r, "r%i" % r)
1408 yield 'l', (r, "r%i" % r)
1409 elif repo:
1409 elif repo:
1410 cl = repo.changelog
1410 cl = repo.changelog
1411 tags = opts.get('tags')
1411 tags = opts.get('tags')
1412 branches = opts.get('branches')
1412 branches = opts.get('branches')
1413 if tags:
1413 if tags:
1414 labels = {}
1414 labels = {}
1415 for l, n in repo.tags().items():
1415 for l, n in repo.tags().items():
1416 labels.setdefault(cl.rev(n), []).append(l)
1416 labels.setdefault(cl.rev(n), []).append(l)
1417 def events():
1417 def events():
1418 b = "default"
1418 b = "default"
1419 for r in cl:
1419 for r in cl:
1420 if branches:
1420 if branches:
1421 newb = cl.read(cl.node(r))[5]['branch']
1421 newb = cl.read(cl.node(r))[5]['branch']
1422 if newb != b:
1422 if newb != b:
1423 yield 'a', newb
1423 yield 'a', newb
1424 b = newb
1424 b = newb
1425 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1425 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1426 if tags:
1426 if tags:
1427 ls = labels.get(r)
1427 ls = labels.get(r)
1428 if ls:
1428 if ls:
1429 for l in ls:
1429 for l in ls:
1430 yield 'l', (r, l)
1430 yield 'l', (r, l)
1431 else:
1431 else:
1432 raise util.Abort(_('need repo for changelog dag'))
1432 raise util.Abort(_('need repo for changelog dag'))
1433
1433
1434 for line in dagparser.dagtextlines(events(),
1434 for line in dagparser.dagtextlines(events(),
1435 addspaces=spaces,
1435 addspaces=spaces,
1436 wraplabels=True,
1436 wraplabels=True,
1437 wrapannotations=True,
1437 wrapannotations=True,
1438 wrapnonlinear=dots,
1438 wrapnonlinear=dots,
1439 usedots=dots,
1439 usedots=dots,
1440 maxlinewidth=70):
1440 maxlinewidth=70):
1441 ui.write(line)
1441 ui.write(line)
1442 ui.write("\n")
1442 ui.write("\n")
1443
1443
1444 def debugdata(ui, repo, file_, rev):
1444 def debugdata(ui, repo, file_, rev):
1445 """dump the contents of a data file revision"""
1445 """dump the contents of a data file revision"""
1446 r = None
1446 r = None
1447 if repo:
1447 if repo:
1448 filelog = repo.file(file_)
1448 filelog = repo.file(file_)
1449 if len(filelog):
1449 if len(filelog):
1450 r = filelog
1450 r = filelog
1451 if not r:
1451 if not r:
1452 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
1452 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
1453 file_[:-2] + ".i")
1453 file_[:-2] + ".i")
1454 try:
1454 try:
1455 ui.write(r.revision(r.lookup(rev)))
1455 ui.write(r.revision(r.lookup(rev)))
1456 except KeyError:
1456 except KeyError:
1457 raise util.Abort(_('invalid revision identifier %s') % rev)
1457 raise util.Abort(_('invalid revision identifier %s') % rev)
1458
1458
1459 def debugdate(ui, date, range=None, **opts):
1459 def debugdate(ui, date, range=None, **opts):
1460 """parse and display a date"""
1460 """parse and display a date"""
1461 if opts["extended"]:
1461 if opts["extended"]:
1462 d = util.parsedate(date, util.extendeddateformats)
1462 d = util.parsedate(date, util.extendeddateformats)
1463 else:
1463 else:
1464 d = util.parsedate(date)
1464 d = util.parsedate(date)
1465 ui.write("internal: %s %s\n" % d)
1465 ui.write("internal: %s %s\n" % d)
1466 ui.write("standard: %s\n" % util.datestr(d))
1466 ui.write("standard: %s\n" % util.datestr(d))
1467 if range:
1467 if range:
1468 m = util.matchdate(range)
1468 m = util.matchdate(range)
1469 ui.write("match: %s\n" % m(d[0]))
1469 ui.write("match: %s\n" % m(d[0]))
1470
1470
1471 def debugignore(ui, repo, *values, **opts):
1471 def debugignore(ui, repo, *values, **opts):
1472 """display the combined ignore pattern"""
1472 """display the combined ignore pattern"""
1473 ignore = repo.dirstate._ignore
1473 ignore = repo.dirstate._ignore
1474 if hasattr(ignore, 'includepat'):
1474 if hasattr(ignore, 'includepat'):
1475 ui.write("%s\n" % ignore.includepat)
1475 ui.write("%s\n" % ignore.includepat)
1476 else:
1476 else:
1477 raise util.Abort(_("no ignore patterns found"))
1477 raise util.Abort(_("no ignore patterns found"))
1478
1478
1479 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1479 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1480 """runs the changeset discovery protocol in isolation"""
1480 """runs the changeset discovery protocol in isolation"""
1481 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl), opts.get('branch'))
1481 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl), opts.get('branch'))
1482 remote = hg.repository(hg.remoteui(repo, opts), remoteurl)
1482 remote = hg.repository(hg.remoteui(repo, opts), remoteurl)
1483 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1483 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1484
1484
1485 # make sure tests are repeatable
1485 # make sure tests are repeatable
1486 random.seed(12323)
1486 random.seed(12323)
1487
1487
1488 def doit(localheads, remoteheads):
1488 def doit(localheads, remoteheads):
1489 if opts.get('old'):
1489 if opts.get('old'):
1490 if localheads:
1490 if localheads:
1491 raise util.Abort('cannot use localheads with old style discovery')
1491 raise util.Abort('cannot use localheads with old style discovery')
1492 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1492 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1493 force=True)
1493 force=True)
1494 common = set(common)
1494 common = set(common)
1495 if not opts.get('nonheads'):
1495 if not opts.get('nonheads'):
1496 ui.write("unpruned common: %s\n" % " ".join([short(n)
1496 ui.write("unpruned common: %s\n" % " ".join([short(n)
1497 for n in common]))
1497 for n in common]))
1498 dag = dagutil.revlogdag(repo.changelog)
1498 dag = dagutil.revlogdag(repo.changelog)
1499 all = dag.ancestorset(dag.internalizeall(common))
1499 all = dag.ancestorset(dag.internalizeall(common))
1500 common = dag.externalizeall(dag.headsetofconnecteds(all))
1500 common = dag.externalizeall(dag.headsetofconnecteds(all))
1501 else:
1501 else:
1502 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1502 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1503 common = set(common)
1503 common = set(common)
1504 rheads = set(hds)
1504 rheads = set(hds)
1505 lheads = set(repo.heads())
1505 lheads = set(repo.heads())
1506 ui.write("common heads: %s\n" % " ".join([short(n) for n in common]))
1506 ui.write("common heads: %s\n" % " ".join([short(n) for n in common]))
1507 if lheads <= common:
1507 if lheads <= common:
1508 ui.write("local is subset\n")
1508 ui.write("local is subset\n")
1509 elif rheads <= common:
1509 elif rheads <= common:
1510 ui.write("remote is subset\n")
1510 ui.write("remote is subset\n")
1511
1511
1512 serverlogs = opts.get('serverlog')
1512 serverlogs = opts.get('serverlog')
1513 if serverlogs:
1513 if serverlogs:
1514 for filename in serverlogs:
1514 for filename in serverlogs:
1515 logfile = open(filename, 'r')
1515 logfile = open(filename, 'r')
1516 try:
1516 try:
1517 line = logfile.readline()
1517 line = logfile.readline()
1518 while line:
1518 while line:
1519 parts = line.strip().split(';')
1519 parts = line.strip().split(';')
1520 op = parts[1]
1520 op = parts[1]
1521 if op == 'cg':
1521 if op == 'cg':
1522 pass
1522 pass
1523 elif op == 'cgss':
1523 elif op == 'cgss':
1524 doit(parts[2].split(' '), parts[3].split(' '))
1524 doit(parts[2].split(' '), parts[3].split(' '))
1525 elif op == 'unb':
1525 elif op == 'unb':
1526 doit(parts[3].split(' '), parts[2].split(' '))
1526 doit(parts[3].split(' '), parts[2].split(' '))
1527 line = logfile.readline()
1527 line = logfile.readline()
1528 finally:
1528 finally:
1529 logfile.close()
1529 logfile.close()
1530
1530
1531 else:
1531 else:
1532 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
1532 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
1533 opts.get('remote_head'))
1533 opts.get('remote_head'))
1534 localrevs = opts.get('local_head')
1534 localrevs = opts.get('local_head')
1535 doit(localrevs, remoterevs)
1535 doit(localrevs, remoterevs)
1536
1536
1537
1537
1538 def debugindex(ui, repo, file_, **opts):
1538 def debugindex(ui, repo, file_, **opts):
1539 """dump the contents of an index file"""
1539 """dump the contents of an index file"""
1540 r = None
1540 r = None
1541 if repo:
1541 if repo:
1542 filelog = repo.file(file_)
1542 filelog = repo.file(file_)
1543 if len(filelog):
1543 if len(filelog):
1544 r = filelog
1544 r = filelog
1545
1545
1546 format = opts.get('format', 0)
1546 format = opts.get('format', 0)
1547 if format not in (0, 1):
1547 if format not in (0, 1):
1548 raise util.Abort(_("unknown format %d") % format)
1548 raise util.Abort(_("unknown format %d") % format)
1549
1549
1550 if not r:
1550 if not r:
1551 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1551 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1552
1552
1553 if format == 0:
1553 if format == 0:
1554 ui.write(" rev offset length base linkrev"
1554 ui.write(" rev offset length base linkrev"
1555 " nodeid p1 p2\n")
1555 " nodeid p1 p2\n")
1556 elif format == 1:
1556 elif format == 1:
1557 ui.write(" rev flag offset length"
1557 ui.write(" rev flag offset length"
1558 " size base link p1 p2 nodeid\n")
1558 " size base link p1 p2 nodeid\n")
1559
1559
1560 for i in r:
1560 for i in r:
1561 node = r.node(i)
1561 node = r.node(i)
1562 if format == 0:
1562 if format == 0:
1563 try:
1563 try:
1564 pp = r.parents(node)
1564 pp = r.parents(node)
1565 except:
1565 except:
1566 pp = [nullid, nullid]
1566 pp = [nullid, nullid]
1567 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1567 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1568 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
1568 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
1569 short(node), short(pp[0]), short(pp[1])))
1569 short(node), short(pp[0]), short(pp[1])))
1570 elif format == 1:
1570 elif format == 1:
1571 pr = r.parentrevs(i)
1571 pr = r.parentrevs(i)
1572 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1572 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1573 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1573 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1574 r.base(i), r.linkrev(i), pr[0], pr[1], short(node)))
1574 r.base(i), r.linkrev(i), pr[0], pr[1], short(node)))
1575
1575
1576 def debugindexdot(ui, repo, file_):
1576 def debugindexdot(ui, repo, file_):
1577 """dump an index DAG as a graphviz dot file"""
1577 """dump an index DAG as a graphviz dot file"""
1578 r = None
1578 r = None
1579 if repo:
1579 if repo:
1580 filelog = repo.file(file_)
1580 filelog = repo.file(file_)
1581 if len(filelog):
1581 if len(filelog):
1582 r = filelog
1582 r = filelog
1583 if not r:
1583 if not r:
1584 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1584 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1585 ui.write("digraph G {\n")
1585 ui.write("digraph G {\n")
1586 for i in r:
1586 for i in r:
1587 node = r.node(i)
1587 node = r.node(i)
1588 pp = r.parents(node)
1588 pp = r.parents(node)
1589 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1589 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1590 if pp[1] != nullid:
1590 if pp[1] != nullid:
1591 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1591 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1592 ui.write("}\n")
1592 ui.write("}\n")
1593
1593
1594 def debuginstall(ui):
1594 def debuginstall(ui):
1595 '''test Mercurial installation
1595 '''test Mercurial installation
1596
1596
1597 Returns 0 on success.
1597 Returns 0 on success.
1598 '''
1598 '''
1599
1599
1600 def writetemp(contents):
1600 def writetemp(contents):
1601 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1601 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1602 f = os.fdopen(fd, "wb")
1602 f = os.fdopen(fd, "wb")
1603 f.write(contents)
1603 f.write(contents)
1604 f.close()
1604 f.close()
1605 return name
1605 return name
1606
1606
1607 problems = 0
1607 problems = 0
1608
1608
1609 # encoding
1609 # encoding
1610 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1610 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1611 try:
1611 try:
1612 encoding.fromlocal("test")
1612 encoding.fromlocal("test")
1613 except util.Abort, inst:
1613 except util.Abort, inst:
1614 ui.write(" %s\n" % inst)
1614 ui.write(" %s\n" % inst)
1615 ui.write(_(" (check that your locale is properly set)\n"))
1615 ui.write(_(" (check that your locale is properly set)\n"))
1616 problems += 1
1616 problems += 1
1617
1617
1618 # compiled modules
1618 # compiled modules
1619 ui.status(_("Checking installed modules (%s)...\n")
1619 ui.status(_("Checking installed modules (%s)...\n")
1620 % os.path.dirname(__file__))
1620 % os.path.dirname(__file__))
1621 try:
1621 try:
1622 import bdiff, mpatch, base85, osutil
1622 import bdiff, mpatch, base85, osutil
1623 except Exception, inst:
1623 except Exception, inst:
1624 ui.write(" %s\n" % inst)
1624 ui.write(" %s\n" % inst)
1625 ui.write(_(" One or more extensions could not be found"))
1625 ui.write(_(" One or more extensions could not be found"))
1626 ui.write(_(" (check that you compiled the extensions)\n"))
1626 ui.write(_(" (check that you compiled the extensions)\n"))
1627 problems += 1
1627 problems += 1
1628
1628
1629 # templates
1629 # templates
1630 ui.status(_("Checking templates...\n"))
1630 ui.status(_("Checking templates...\n"))
1631 try:
1631 try:
1632 import templater
1632 import templater
1633 templater.templater(templater.templatepath("map-cmdline.default"))
1633 templater.templater(templater.templatepath("map-cmdline.default"))
1634 except Exception, inst:
1634 except Exception, inst:
1635 ui.write(" %s\n" % inst)
1635 ui.write(" %s\n" % inst)
1636 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1636 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1637 problems += 1
1637 problems += 1
1638
1638
1639 # editor
1639 # editor
1640 ui.status(_("Checking commit editor...\n"))
1640 ui.status(_("Checking commit editor...\n"))
1641 editor = ui.geteditor()
1641 editor = ui.geteditor()
1642 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
1642 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
1643 if not cmdpath:
1643 if not cmdpath:
1644 if editor == 'vi':
1644 if editor == 'vi':
1645 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1645 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1646 ui.write(_(" (specify a commit editor in your configuration"
1646 ui.write(_(" (specify a commit editor in your configuration"
1647 " file)\n"))
1647 " file)\n"))
1648 else:
1648 else:
1649 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1649 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1650 ui.write(_(" (specify a commit editor in your configuration"
1650 ui.write(_(" (specify a commit editor in your configuration"
1651 " file)\n"))
1651 " file)\n"))
1652 problems += 1
1652 problems += 1
1653
1653
1654 # check username
1654 # check username
1655 ui.status(_("Checking username...\n"))
1655 ui.status(_("Checking username...\n"))
1656 try:
1656 try:
1657 ui.username()
1657 ui.username()
1658 except util.Abort, e:
1658 except util.Abort, e:
1659 ui.write(" %s\n" % e)
1659 ui.write(" %s\n" % e)
1660 ui.write(_(" (specify a username in your configuration file)\n"))
1660 ui.write(_(" (specify a username in your configuration file)\n"))
1661 problems += 1
1661 problems += 1
1662
1662
1663 if not problems:
1663 if not problems:
1664 ui.status(_("No problems detected\n"))
1664 ui.status(_("No problems detected\n"))
1665 else:
1665 else:
1666 ui.write(_("%s problems detected,"
1666 ui.write(_("%s problems detected,"
1667 " please check your install!\n") % problems)
1667 " please check your install!\n") % problems)
1668
1668
1669 return problems
1669 return problems
1670
1670
1671 def debugrename(ui, repo, file1, *pats, **opts):
1671 def debugrename(ui, repo, file1, *pats, **opts):
1672 """dump rename information"""
1672 """dump rename information"""
1673
1673
1674 ctx = cmdutil.revsingle(repo, opts.get('rev'))
1674 ctx = cmdutil.revsingle(repo, opts.get('rev'))
1675 m = cmdutil.match(repo, (file1,) + pats, opts)
1675 m = cmdutil.match(repo, (file1,) + pats, opts)
1676 for abs in ctx.walk(m):
1676 for abs in ctx.walk(m):
1677 fctx = ctx[abs]
1677 fctx = ctx[abs]
1678 o = fctx.filelog().renamed(fctx.filenode())
1678 o = fctx.filelog().renamed(fctx.filenode())
1679 rel = m.rel(abs)
1679 rel = m.rel(abs)
1680 if o:
1680 if o:
1681 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1681 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1682 else:
1682 else:
1683 ui.write(_("%s not renamed\n") % rel)
1683 ui.write(_("%s not renamed\n") % rel)
1684
1684
1685 def debugwalk(ui, repo, *pats, **opts):
1685 def debugwalk(ui, repo, *pats, **opts):
1686 """show how files match on given patterns"""
1686 """show how files match on given patterns"""
1687 m = cmdutil.match(repo, pats, opts)
1687 m = cmdutil.match(repo, pats, opts)
1688 items = list(repo.walk(m))
1688 items = list(repo.walk(m))
1689 if not items:
1689 if not items:
1690 return
1690 return
1691 fmt = 'f %%-%ds %%-%ds %%s' % (
1691 fmt = 'f %%-%ds %%-%ds %%s' % (
1692 max([len(abs) for abs in items]),
1692 max([len(abs) for abs in items]),
1693 max([len(m.rel(abs)) for abs in items]))
1693 max([len(m.rel(abs)) for abs in items]))
1694 for abs in items:
1694 for abs in items:
1695 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1695 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1696 ui.write("%s\n" % line.rstrip())
1696 ui.write("%s\n" % line.rstrip())
1697
1697
1698 def debugwireargs(ui, repopath, *vals, **opts):
1698 def debugwireargs(ui, repopath, *vals, **opts):
1699 repo = hg.repository(hg.remoteui(ui, opts), repopath)
1699 repo = hg.repository(hg.remoteui(ui, opts), repopath)
1700 for opt in remoteopts:
1700 for opt in remoteopts:
1701 del opts[opt[1]]
1701 del opts[opt[1]]
1702 args = {}
1702 args = {}
1703 for k, v in opts.iteritems():
1703 for k, v in opts.iteritems():
1704 if v:
1704 if v:
1705 args[k] = v
1705 args[k] = v
1706 # run twice to check that we don't mess up the stream for the next command
1706 # run twice to check that we don't mess up the stream for the next command
1707 res1 = repo.debugwireargs(*vals, **args)
1707 res1 = repo.debugwireargs(*vals, **args)
1708 res2 = repo.debugwireargs(*vals, **args)
1708 res2 = repo.debugwireargs(*vals, **args)
1709 ui.write("%s\n" % res1)
1709 ui.write("%s\n" % res1)
1710 if res1 != res2:
1710 if res1 != res2:
1711 ui.warn("%s\n" % res2)
1711 ui.warn("%s\n" % res2)
1712
1712
1713 def diff(ui, repo, *pats, **opts):
1713 def diff(ui, repo, *pats, **opts):
1714 """diff repository (or selected files)
1714 """diff repository (or selected files)
1715
1715
1716 Show differences between revisions for the specified files.
1716 Show differences between revisions for the specified files.
1717
1717
1718 Differences between files are shown using the unified diff format.
1718 Differences between files are shown using the unified diff format.
1719
1719
1720 .. note::
1720 .. note::
1721 diff may generate unexpected results for merges, as it will
1721 diff may generate unexpected results for merges, as it will
1722 default to comparing against the working directory's first
1722 default to comparing against the working directory's first
1723 parent changeset if no revisions are specified.
1723 parent changeset if no revisions are specified.
1724
1724
1725 When two revision arguments are given, then changes are shown
1725 When two revision arguments are given, then changes are shown
1726 between those revisions. If only one revision is specified then
1726 between those revisions. If only one revision is specified then
1727 that revision is compared to the working directory, and, when no
1727 that revision is compared to the working directory, and, when no
1728 revisions are specified, the working directory files are compared
1728 revisions are specified, the working directory files are compared
1729 to its parent.
1729 to its parent.
1730
1730
1731 Alternatively you can specify -c/--change with a revision to see
1731 Alternatively you can specify -c/--change with a revision to see
1732 the changes in that changeset relative to its first parent.
1732 the changes in that changeset relative to its first parent.
1733
1733
1734 Without the -a/--text option, diff will avoid generating diffs of
1734 Without the -a/--text option, diff will avoid generating diffs of
1735 files it detects as binary. With -a, diff will generate a diff
1735 files it detects as binary. With -a, diff will generate a diff
1736 anyway, probably with undesirable results.
1736 anyway, probably with undesirable results.
1737
1737
1738 Use the -g/--git option to generate diffs in the git extended diff
1738 Use the -g/--git option to generate diffs in the git extended diff
1739 format. For more information, read :hg:`help diffs`.
1739 format. For more information, read :hg:`help diffs`.
1740
1740
1741 Returns 0 on success.
1741 Returns 0 on success.
1742 """
1742 """
1743
1743
1744 revs = opts.get('rev')
1744 revs = opts.get('rev')
1745 change = opts.get('change')
1745 change = opts.get('change')
1746 stat = opts.get('stat')
1746 stat = opts.get('stat')
1747 reverse = opts.get('reverse')
1747 reverse = opts.get('reverse')
1748
1748
1749 if revs and change:
1749 if revs and change:
1750 msg = _('cannot specify --rev and --change at the same time')
1750 msg = _('cannot specify --rev and --change at the same time')
1751 raise util.Abort(msg)
1751 raise util.Abort(msg)
1752 elif change:
1752 elif change:
1753 node2 = cmdutil.revsingle(repo, change, None).node()
1753 node2 = cmdutil.revsingle(repo, change, None).node()
1754 node1 = repo[node2].p1().node()
1754 node1 = repo[node2].p1().node()
1755 else:
1755 else:
1756 node1, node2 = cmdutil.revpair(repo, revs)
1756 node1, node2 = cmdutil.revpair(repo, revs)
1757
1757
1758 if reverse:
1758 if reverse:
1759 node1, node2 = node2, node1
1759 node1, node2 = node2, node1
1760
1760
1761 diffopts = patch.diffopts(ui, opts)
1761 diffopts = patch.diffopts(ui, opts)
1762 m = cmdutil.match(repo, pats, opts)
1762 m = cmdutil.match(repo, pats, opts)
1763 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1763 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1764 listsubrepos=opts.get('subrepos'))
1764 listsubrepos=opts.get('subrepos'))
1765
1765
1766 def export(ui, repo, *changesets, **opts):
1766 def export(ui, repo, *changesets, **opts):
1767 """dump the header and diffs for one or more changesets
1767 """dump the header and diffs for one or more changesets
1768
1768
1769 Print the changeset header and diffs for one or more revisions.
1769 Print the changeset header and diffs for one or more revisions.
1770
1770
1771 The information shown in the changeset header is: author, date,
1771 The information shown in the changeset header is: author, date,
1772 branch name (if non-default), changeset hash, parent(s) and commit
1772 branch name (if non-default), changeset hash, parent(s) and commit
1773 comment.
1773 comment.
1774
1774
1775 .. note::
1775 .. note::
1776 export may generate unexpected diff output for merge
1776 export may generate unexpected diff output for merge
1777 changesets, as it will compare the merge changeset against its
1777 changesets, as it will compare the merge changeset against its
1778 first parent only.
1778 first parent only.
1779
1779
1780 Output may be to a file, in which case the name of the file is
1780 Output may be to a file, in which case the name of the file is
1781 given using a format string. The formatting rules are as follows:
1781 given using a format string. The formatting rules are as follows:
1782
1782
1783 :``%%``: literal "%" character
1783 :``%%``: literal "%" character
1784 :``%H``: changeset hash (40 hexadecimal digits)
1784 :``%H``: changeset hash (40 hexadecimal digits)
1785 :``%N``: number of patches being generated
1785 :``%N``: number of patches being generated
1786 :``%R``: changeset revision number
1786 :``%R``: changeset revision number
1787 :``%b``: basename of the exporting repository
1787 :``%b``: basename of the exporting repository
1788 :``%h``: short-form changeset hash (12 hexadecimal digits)
1788 :``%h``: short-form changeset hash (12 hexadecimal digits)
1789 :``%n``: zero-padded sequence number, starting at 1
1789 :``%n``: zero-padded sequence number, starting at 1
1790 :``%r``: zero-padded changeset revision number
1790 :``%r``: zero-padded changeset revision number
1791
1791
1792 Without the -a/--text option, export will avoid generating diffs
1792 Without the -a/--text option, export will avoid generating diffs
1793 of files it detects as binary. With -a, export will generate a
1793 of files it detects as binary. With -a, export will generate a
1794 diff anyway, probably with undesirable results.
1794 diff anyway, probably with undesirable results.
1795
1795
1796 Use the -g/--git option to generate diffs in the git extended diff
1796 Use the -g/--git option to generate diffs in the git extended diff
1797 format. See :hg:`help diffs` for more information.
1797 format. See :hg:`help diffs` for more information.
1798
1798
1799 With the --switch-parent option, the diff will be against the
1799 With the --switch-parent option, the diff will be against the
1800 second parent. It can be useful to review a merge.
1800 second parent. It can be useful to review a merge.
1801
1801
1802 Returns 0 on success.
1802 Returns 0 on success.
1803 """
1803 """
1804 changesets += tuple(opts.get('rev', []))
1804 changesets += tuple(opts.get('rev', []))
1805 if not changesets:
1805 if not changesets:
1806 raise util.Abort(_("export requires at least one changeset"))
1806 raise util.Abort(_("export requires at least one changeset"))
1807 revs = cmdutil.revrange(repo, changesets)
1807 revs = cmdutil.revrange(repo, changesets)
1808 if len(revs) > 1:
1808 if len(revs) > 1:
1809 ui.note(_('exporting patches:\n'))
1809 ui.note(_('exporting patches:\n'))
1810 else:
1810 else:
1811 ui.note(_('exporting patch:\n'))
1811 ui.note(_('exporting patch:\n'))
1812 cmdutil.export(repo, revs, template=opts.get('output'),
1812 cmdutil.export(repo, revs, template=opts.get('output'),
1813 switch_parent=opts.get('switch_parent'),
1813 switch_parent=opts.get('switch_parent'),
1814 opts=patch.diffopts(ui, opts))
1814 opts=patch.diffopts(ui, opts))
1815
1815
1816 def forget(ui, repo, *pats, **opts):
1816 def forget(ui, repo, *pats, **opts):
1817 """forget the specified files on the next commit
1817 """forget the specified files on the next commit
1818
1818
1819 Mark the specified files so they will no longer be tracked
1819 Mark the specified files so they will no longer be tracked
1820 after the next commit.
1820 after the next commit.
1821
1821
1822 This only removes files from the current branch, not from the
1822 This only removes files from the current branch, not from the
1823 entire project history, and it does not delete them from the
1823 entire project history, and it does not delete them from the
1824 working directory.
1824 working directory.
1825
1825
1826 To undo a forget before the next commit, see :hg:`add`.
1826 To undo a forget before the next commit, see :hg:`add`.
1827
1827
1828 Returns 0 on success.
1828 Returns 0 on success.
1829 """
1829 """
1830
1830
1831 if not pats:
1831 if not pats:
1832 raise util.Abort(_('no files specified'))
1832 raise util.Abort(_('no files specified'))
1833
1833
1834 m = cmdutil.match(repo, pats, opts)
1834 m = cmdutil.match(repo, pats, opts)
1835 s = repo.status(match=m, clean=True)
1835 s = repo.status(match=m, clean=True)
1836 forget = sorted(s[0] + s[1] + s[3] + s[6])
1836 forget = sorted(s[0] + s[1] + s[3] + s[6])
1837 errs = 0
1837 errs = 0
1838
1838
1839 for f in m.files():
1839 for f in m.files():
1840 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
1840 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
1841 ui.warn(_('not removing %s: file is already untracked\n')
1841 ui.warn(_('not removing %s: file is already untracked\n')
1842 % m.rel(f))
1842 % m.rel(f))
1843 errs = 1
1843 errs = 1
1844
1844
1845 for f in forget:
1845 for f in forget:
1846 if ui.verbose or not m.exact(f):
1846 if ui.verbose or not m.exact(f):
1847 ui.status(_('removing %s\n') % m.rel(f))
1847 ui.status(_('removing %s\n') % m.rel(f))
1848
1848
1849 repo[None].remove(forget, unlink=False)
1849 repo[None].remove(forget, unlink=False)
1850 return errs
1850 return errs
1851
1851
1852 def grep(ui, repo, pattern, *pats, **opts):
1852 def grep(ui, repo, pattern, *pats, **opts):
1853 """search for a pattern in specified files and revisions
1853 """search for a pattern in specified files and revisions
1854
1854
1855 Search revisions of files for a regular expression.
1855 Search revisions of files for a regular expression.
1856
1856
1857 This command behaves differently than Unix grep. It only accepts
1857 This command behaves differently than Unix grep. It only accepts
1858 Python/Perl regexps. It searches repository history, not the
1858 Python/Perl regexps. It searches repository history, not the
1859 working directory. It always prints the revision number in which a
1859 working directory. It always prints the revision number in which a
1860 match appears.
1860 match appears.
1861
1861
1862 By default, grep only prints output for the first revision of a
1862 By default, grep only prints output for the first revision of a
1863 file in which it finds a match. To get it to print every revision
1863 file in which it finds a match. To get it to print every revision
1864 that contains a change in match status ("-" for a match that
1864 that contains a change in match status ("-" for a match that
1865 becomes a non-match, or "+" for a non-match that becomes a match),
1865 becomes a non-match, or "+" for a non-match that becomes a match),
1866 use the --all flag.
1866 use the --all flag.
1867
1867
1868 Returns 0 if a match is found, 1 otherwise.
1868 Returns 0 if a match is found, 1 otherwise.
1869 """
1869 """
1870 reflags = 0
1870 reflags = 0
1871 if opts.get('ignore_case'):
1871 if opts.get('ignore_case'):
1872 reflags |= re.I
1872 reflags |= re.I
1873 try:
1873 try:
1874 regexp = re.compile(pattern, reflags)
1874 regexp = re.compile(pattern, reflags)
1875 except re.error, inst:
1875 except re.error, inst:
1876 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1876 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1877 return 1
1877 return 1
1878 sep, eol = ':', '\n'
1878 sep, eol = ':', '\n'
1879 if opts.get('print0'):
1879 if opts.get('print0'):
1880 sep = eol = '\0'
1880 sep = eol = '\0'
1881
1881
1882 getfile = util.lrucachefunc(repo.file)
1882 getfile = util.lrucachefunc(repo.file)
1883
1883
1884 def matchlines(body):
1884 def matchlines(body):
1885 begin = 0
1885 begin = 0
1886 linenum = 0
1886 linenum = 0
1887 while True:
1887 while True:
1888 match = regexp.search(body, begin)
1888 match = regexp.search(body, begin)
1889 if not match:
1889 if not match:
1890 break
1890 break
1891 mstart, mend = match.span()
1891 mstart, mend = match.span()
1892 linenum += body.count('\n', begin, mstart) + 1
1892 linenum += body.count('\n', begin, mstart) + 1
1893 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1893 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1894 begin = body.find('\n', mend) + 1 or len(body)
1894 begin = body.find('\n', mend) + 1 or len(body)
1895 lend = begin - 1
1895 lend = begin - 1
1896 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1896 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1897
1897
1898 class linestate(object):
1898 class linestate(object):
1899 def __init__(self, line, linenum, colstart, colend):
1899 def __init__(self, line, linenum, colstart, colend):
1900 self.line = line
1900 self.line = line
1901 self.linenum = linenum
1901 self.linenum = linenum
1902 self.colstart = colstart
1902 self.colstart = colstart
1903 self.colend = colend
1903 self.colend = colend
1904
1904
1905 def __hash__(self):
1905 def __hash__(self):
1906 return hash((self.linenum, self.line))
1906 return hash((self.linenum, self.line))
1907
1907
1908 def __eq__(self, other):
1908 def __eq__(self, other):
1909 return self.line == other.line
1909 return self.line == other.line
1910
1910
1911 matches = {}
1911 matches = {}
1912 copies = {}
1912 copies = {}
1913 def grepbody(fn, rev, body):
1913 def grepbody(fn, rev, body):
1914 matches[rev].setdefault(fn, [])
1914 matches[rev].setdefault(fn, [])
1915 m = matches[rev][fn]
1915 m = matches[rev][fn]
1916 for lnum, cstart, cend, line in matchlines(body):
1916 for lnum, cstart, cend, line in matchlines(body):
1917 s = linestate(line, lnum, cstart, cend)
1917 s = linestate(line, lnum, cstart, cend)
1918 m.append(s)
1918 m.append(s)
1919
1919
1920 def difflinestates(a, b):
1920 def difflinestates(a, b):
1921 sm = difflib.SequenceMatcher(None, a, b)
1921 sm = difflib.SequenceMatcher(None, a, b)
1922 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1922 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1923 if tag == 'insert':
1923 if tag == 'insert':
1924 for i in xrange(blo, bhi):
1924 for i in xrange(blo, bhi):
1925 yield ('+', b[i])
1925 yield ('+', b[i])
1926 elif tag == 'delete':
1926 elif tag == 'delete':
1927 for i in xrange(alo, ahi):
1927 for i in xrange(alo, ahi):
1928 yield ('-', a[i])
1928 yield ('-', a[i])
1929 elif tag == 'replace':
1929 elif tag == 'replace':
1930 for i in xrange(alo, ahi):
1930 for i in xrange(alo, ahi):
1931 yield ('-', a[i])
1931 yield ('-', a[i])
1932 for i in xrange(blo, bhi):
1932 for i in xrange(blo, bhi):
1933 yield ('+', b[i])
1933 yield ('+', b[i])
1934
1934
1935 def display(fn, ctx, pstates, states):
1935 def display(fn, ctx, pstates, states):
1936 rev = ctx.rev()
1936 rev = ctx.rev()
1937 datefunc = ui.quiet and util.shortdate or util.datestr
1937 datefunc = ui.quiet and util.shortdate or util.datestr
1938 found = False
1938 found = False
1939 filerevmatches = {}
1939 filerevmatches = {}
1940 def binary():
1940 def binary():
1941 flog = getfile(fn)
1941 flog = getfile(fn)
1942 return util.binary(flog.read(ctx.filenode(fn)))
1942 return util.binary(flog.read(ctx.filenode(fn)))
1943
1943
1944 if opts.get('all'):
1944 if opts.get('all'):
1945 iter = difflinestates(pstates, states)
1945 iter = difflinestates(pstates, states)
1946 else:
1946 else:
1947 iter = [('', l) for l in states]
1947 iter = [('', l) for l in states]
1948 for change, l in iter:
1948 for change, l in iter:
1949 cols = [fn, str(rev)]
1949 cols = [fn, str(rev)]
1950 before, match, after = None, None, None
1950 before, match, after = None, None, None
1951 if opts.get('line_number'):
1951 if opts.get('line_number'):
1952 cols.append(str(l.linenum))
1952 cols.append(str(l.linenum))
1953 if opts.get('all'):
1953 if opts.get('all'):
1954 cols.append(change)
1954 cols.append(change)
1955 if opts.get('user'):
1955 if opts.get('user'):
1956 cols.append(ui.shortuser(ctx.user()))
1956 cols.append(ui.shortuser(ctx.user()))
1957 if opts.get('date'):
1957 if opts.get('date'):
1958 cols.append(datefunc(ctx.date()))
1958 cols.append(datefunc(ctx.date()))
1959 if opts.get('files_with_matches'):
1959 if opts.get('files_with_matches'):
1960 c = (fn, rev)
1960 c = (fn, rev)
1961 if c in filerevmatches:
1961 if c in filerevmatches:
1962 continue
1962 continue
1963 filerevmatches[c] = 1
1963 filerevmatches[c] = 1
1964 else:
1964 else:
1965 before = l.line[:l.colstart]
1965 before = l.line[:l.colstart]
1966 match = l.line[l.colstart:l.colend]
1966 match = l.line[l.colstart:l.colend]
1967 after = l.line[l.colend:]
1967 after = l.line[l.colend:]
1968 ui.write(sep.join(cols))
1968 ui.write(sep.join(cols))
1969 if before is not None:
1969 if before is not None:
1970 if not opts.get('text') and binary():
1970 if not opts.get('text') and binary():
1971 ui.write(sep + " Binary file matches")
1971 ui.write(sep + " Binary file matches")
1972 else:
1972 else:
1973 ui.write(sep + before)
1973 ui.write(sep + before)
1974 ui.write(match, label='grep.match')
1974 ui.write(match, label='grep.match')
1975 ui.write(after)
1975 ui.write(after)
1976 ui.write(eol)
1976 ui.write(eol)
1977 found = True
1977 found = True
1978 return found
1978 return found
1979
1979
1980 skip = {}
1980 skip = {}
1981 revfiles = {}
1981 revfiles = {}
1982 matchfn = cmdutil.match(repo, pats, opts)
1982 matchfn = cmdutil.match(repo, pats, opts)
1983 found = False
1983 found = False
1984 follow = opts.get('follow')
1984 follow = opts.get('follow')
1985
1985
1986 def prep(ctx, fns):
1986 def prep(ctx, fns):
1987 rev = ctx.rev()
1987 rev = ctx.rev()
1988 pctx = ctx.p1()
1988 pctx = ctx.p1()
1989 parent = pctx.rev()
1989 parent = pctx.rev()
1990 matches.setdefault(rev, {})
1990 matches.setdefault(rev, {})
1991 matches.setdefault(parent, {})
1991 matches.setdefault(parent, {})
1992 files = revfiles.setdefault(rev, [])
1992 files = revfiles.setdefault(rev, [])
1993 for fn in fns:
1993 for fn in fns:
1994 flog = getfile(fn)
1994 flog = getfile(fn)
1995 try:
1995 try:
1996 fnode = ctx.filenode(fn)
1996 fnode = ctx.filenode(fn)
1997 except error.LookupError:
1997 except error.LookupError:
1998 continue
1998 continue
1999
1999
2000 copied = flog.renamed(fnode)
2000 copied = flog.renamed(fnode)
2001 copy = follow and copied and copied[0]
2001 copy = follow and copied and copied[0]
2002 if copy:
2002 if copy:
2003 copies.setdefault(rev, {})[fn] = copy
2003 copies.setdefault(rev, {})[fn] = copy
2004 if fn in skip:
2004 if fn in skip:
2005 if copy:
2005 if copy:
2006 skip[copy] = True
2006 skip[copy] = True
2007 continue
2007 continue
2008 files.append(fn)
2008 files.append(fn)
2009
2009
2010 if fn not in matches[rev]:
2010 if fn not in matches[rev]:
2011 grepbody(fn, rev, flog.read(fnode))
2011 grepbody(fn, rev, flog.read(fnode))
2012
2012
2013 pfn = copy or fn
2013 pfn = copy or fn
2014 if pfn not in matches[parent]:
2014 if pfn not in matches[parent]:
2015 try:
2015 try:
2016 fnode = pctx.filenode(pfn)
2016 fnode = pctx.filenode(pfn)
2017 grepbody(pfn, parent, flog.read(fnode))
2017 grepbody(pfn, parent, flog.read(fnode))
2018 except error.LookupError:
2018 except error.LookupError:
2019 pass
2019 pass
2020
2020
2021 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2021 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2022 rev = ctx.rev()
2022 rev = ctx.rev()
2023 parent = ctx.p1().rev()
2023 parent = ctx.p1().rev()
2024 for fn in sorted(revfiles.get(rev, [])):
2024 for fn in sorted(revfiles.get(rev, [])):
2025 states = matches[rev][fn]
2025 states = matches[rev][fn]
2026 copy = copies.get(rev, {}).get(fn)
2026 copy = copies.get(rev, {}).get(fn)
2027 if fn in skip:
2027 if fn in skip:
2028 if copy:
2028 if copy:
2029 skip[copy] = True
2029 skip[copy] = True
2030 continue
2030 continue
2031 pstates = matches.get(parent, {}).get(copy or fn, [])
2031 pstates = matches.get(parent, {}).get(copy or fn, [])
2032 if pstates or states:
2032 if pstates or states:
2033 r = display(fn, ctx, pstates, states)
2033 r = display(fn, ctx, pstates, states)
2034 found = found or r
2034 found = found or r
2035 if r and not opts.get('all'):
2035 if r and not opts.get('all'):
2036 skip[fn] = True
2036 skip[fn] = True
2037 if copy:
2037 if copy:
2038 skip[copy] = True
2038 skip[copy] = True
2039 del matches[rev]
2039 del matches[rev]
2040 del revfiles[rev]
2040 del revfiles[rev]
2041
2041
2042 return not found
2042 return not found
2043
2043
2044 def heads(ui, repo, *branchrevs, **opts):
2044 def heads(ui, repo, *branchrevs, **opts):
2045 """show current repository heads or show branch heads
2045 """show current repository heads or show branch heads
2046
2046
2047 With no arguments, show all repository branch heads.
2047 With no arguments, show all repository branch heads.
2048
2048
2049 Repository "heads" are changesets with no child changesets. They are
2049 Repository "heads" are changesets with no child changesets. They are
2050 where development generally takes place and are the usual targets
2050 where development generally takes place and are the usual targets
2051 for update and merge operations. Branch heads are changesets that have
2051 for update and merge operations. Branch heads are changesets that have
2052 no child changeset on the same branch.
2052 no child changeset on the same branch.
2053
2053
2054 If one or more REVs are given, only branch heads on the branches
2054 If one or more REVs are given, only branch heads on the branches
2055 associated with the specified changesets are shown.
2055 associated with the specified changesets are shown.
2056
2056
2057 If -c/--closed is specified, also show branch heads marked closed
2057 If -c/--closed is specified, also show branch heads marked closed
2058 (see :hg:`commit --close-branch`).
2058 (see :hg:`commit --close-branch`).
2059
2059
2060 If STARTREV is specified, only those heads that are descendants of
2060 If STARTREV is specified, only those heads that are descendants of
2061 STARTREV will be displayed.
2061 STARTREV will be displayed.
2062
2062
2063 If -t/--topo is specified, named branch mechanics will be ignored and only
2063 If -t/--topo is specified, named branch mechanics will be ignored and only
2064 changesets without children will be shown.
2064 changesets without children will be shown.
2065
2065
2066 Returns 0 if matching heads are found, 1 if not.
2066 Returns 0 if matching heads are found, 1 if not.
2067 """
2067 """
2068
2068
2069 start = None
2069 start = None
2070 if 'rev' in opts:
2070 if 'rev' in opts:
2071 start = cmdutil.revsingle(repo, opts['rev'], None).node()
2071 start = cmdutil.revsingle(repo, opts['rev'], None).node()
2072
2072
2073 if opts.get('topo'):
2073 if opts.get('topo'):
2074 heads = [repo[h] for h in repo.heads(start)]
2074 heads = [repo[h] for h in repo.heads(start)]
2075 else:
2075 else:
2076 heads = []
2076 heads = []
2077 for b, ls in repo.branchmap().iteritems():
2077 for b, ls in repo.branchmap().iteritems():
2078 if start is None:
2078 if start is None:
2079 heads += [repo[h] for h in ls]
2079 heads += [repo[h] for h in ls]
2080 continue
2080 continue
2081 startrev = repo.changelog.rev(start)
2081 startrev = repo.changelog.rev(start)
2082 descendants = set(repo.changelog.descendants(startrev))
2082 descendants = set(repo.changelog.descendants(startrev))
2083 descendants.add(startrev)
2083 descendants.add(startrev)
2084 rev = repo.changelog.rev
2084 rev = repo.changelog.rev
2085 heads += [repo[h] for h in ls if rev(h) in descendants]
2085 heads += [repo[h] for h in ls if rev(h) in descendants]
2086
2086
2087 if branchrevs:
2087 if branchrevs:
2088 branches = set(repo[br].branch() for br in branchrevs)
2088 branches = set(repo[br].branch() for br in branchrevs)
2089 heads = [h for h in heads if h.branch() in branches]
2089 heads = [h for h in heads if h.branch() in branches]
2090
2090
2091 if not opts.get('closed'):
2091 if not opts.get('closed'):
2092 heads = [h for h in heads if not h.extra().get('close')]
2092 heads = [h for h in heads if not h.extra().get('close')]
2093
2093
2094 if opts.get('active') and branchrevs:
2094 if opts.get('active') and branchrevs:
2095 dagheads = repo.heads(start)
2095 dagheads = repo.heads(start)
2096 heads = [h for h in heads if h.node() in dagheads]
2096 heads = [h for h in heads if h.node() in dagheads]
2097
2097
2098 if branchrevs:
2098 if branchrevs:
2099 haveheads = set(h.branch() for h in heads)
2099 haveheads = set(h.branch() for h in heads)
2100 if branches - haveheads:
2100 if branches - haveheads:
2101 headless = ', '.join(b for b in branches - haveheads)
2101 headless = ', '.join(b for b in branches - haveheads)
2102 msg = _('no open branch heads found on branches %s')
2102 msg = _('no open branch heads found on branches %s')
2103 if opts.get('rev'):
2103 if opts.get('rev'):
2104 msg += _(' (started at %s)' % opts['rev'])
2104 msg += _(' (started at %s)' % opts['rev'])
2105 ui.warn((msg + '\n') % headless)
2105 ui.warn((msg + '\n') % headless)
2106
2106
2107 if not heads:
2107 if not heads:
2108 return 1
2108 return 1
2109
2109
2110 heads = sorted(heads, key=lambda x: -x.rev())
2110 heads = sorted(heads, key=lambda x: -x.rev())
2111 displayer = cmdutil.show_changeset(ui, repo, opts)
2111 displayer = cmdutil.show_changeset(ui, repo, opts)
2112 for ctx in heads:
2112 for ctx in heads:
2113 displayer.show(ctx)
2113 displayer.show(ctx)
2114 displayer.close()
2114 displayer.close()
2115
2115
2116 def help_(ui, name=None, with_version=False, unknowncmd=False, full=True):
2116 def help_(ui, name=None, with_version=False, unknowncmd=False, full=True):
2117 """show help for a given topic or a help overview
2117 """show help for a given topic or a help overview
2118
2118
2119 With no arguments, print a list of commands with short help messages.
2119 With no arguments, print a list of commands with short help messages.
2120
2120
2121 Given a topic, extension, or command name, print help for that
2121 Given a topic, extension, or command name, print help for that
2122 topic.
2122 topic.
2123
2123
2124 Returns 0 if successful.
2124 Returns 0 if successful.
2125 """
2125 """
2126 option_lists = []
2126 option_lists = []
2127 textwidth = min(ui.termwidth(), 80) - 2
2127 textwidth = min(ui.termwidth(), 80) - 2
2128
2128
2129 def addglobalopts(aliases):
2129 def addglobalopts(aliases):
2130 if ui.verbose:
2130 if ui.verbose:
2131 option_lists.append((_("global options:"), globalopts))
2131 option_lists.append((_("global options:"), globalopts))
2132 if name == 'shortlist':
2132 if name == 'shortlist':
2133 option_lists.append((_('use "hg help" for the full list '
2133 option_lists.append((_('use "hg help" for the full list '
2134 'of commands'), ()))
2134 'of commands'), ()))
2135 else:
2135 else:
2136 if name == 'shortlist':
2136 if name == 'shortlist':
2137 msg = _('use "hg help" for the full list of commands '
2137 msg = _('use "hg help" for the full list of commands '
2138 'or "hg -v" for details')
2138 'or "hg -v" for details')
2139 elif name and not full:
2139 elif name and not full:
2140 msg = _('use "hg help %s" to show the full help text' % name)
2140 msg = _('use "hg help %s" to show the full help text' % name)
2141 elif aliases:
2141 elif aliases:
2142 msg = _('use "hg -v help%s" to show builtin aliases and '
2142 msg = _('use "hg -v help%s" to show builtin aliases and '
2143 'global options') % (name and " " + name or "")
2143 'global options') % (name and " " + name or "")
2144 else:
2144 else:
2145 msg = _('use "hg -v help %s" to show global options') % name
2145 msg = _('use "hg -v help %s" to show global options') % name
2146 option_lists.append((msg, ()))
2146 option_lists.append((msg, ()))
2147
2147
2148 def helpcmd(name):
2148 def helpcmd(name):
2149 if with_version:
2149 if with_version:
2150 version_(ui)
2150 version_(ui)
2151 ui.write('\n')
2151 ui.write('\n')
2152
2152
2153 try:
2153 try:
2154 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
2154 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
2155 except error.AmbiguousCommand, inst:
2155 except error.AmbiguousCommand, inst:
2156 # py3k fix: except vars can't be used outside the scope of the
2156 # py3k fix: except vars can't be used outside the scope of the
2157 # except block, nor can be used inside a lambda. python issue4617
2157 # except block, nor can be used inside a lambda. python issue4617
2158 prefix = inst.args[0]
2158 prefix = inst.args[0]
2159 select = lambda c: c.lstrip('^').startswith(prefix)
2159 select = lambda c: c.lstrip('^').startswith(prefix)
2160 helplist(_('list of commands:\n\n'), select)
2160 helplist(_('list of commands:\n\n'), select)
2161 return
2161 return
2162
2162
2163 # check if it's an invalid alias and display its error if it is
2163 # check if it's an invalid alias and display its error if it is
2164 if getattr(entry[0], 'badalias', False):
2164 if getattr(entry[0], 'badalias', False):
2165 if not unknowncmd:
2165 if not unknowncmd:
2166 entry[0](ui)
2166 entry[0](ui)
2167 return
2167 return
2168
2168
2169 # synopsis
2169 # synopsis
2170 if len(entry) > 2:
2170 if len(entry) > 2:
2171 if entry[2].startswith('hg'):
2171 if entry[2].startswith('hg'):
2172 ui.write("%s\n" % entry[2])
2172 ui.write("%s\n" % entry[2])
2173 else:
2173 else:
2174 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
2174 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
2175 else:
2175 else:
2176 ui.write('hg %s\n' % aliases[0])
2176 ui.write('hg %s\n' % aliases[0])
2177
2177
2178 # aliases
2178 # aliases
2179 if full and not ui.quiet and len(aliases) > 1:
2179 if full and not ui.quiet and len(aliases) > 1:
2180 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
2180 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
2181
2181
2182 # description
2182 # description
2183 doc = gettext(entry[0].__doc__)
2183 doc = gettext(entry[0].__doc__)
2184 if not doc:
2184 if not doc:
2185 doc = _("(no help text available)")
2185 doc = _("(no help text available)")
2186 if hasattr(entry[0], 'definition'): # aliased command
2186 if hasattr(entry[0], 'definition'): # aliased command
2187 if entry[0].definition.startswith('!'): # shell alias
2187 if entry[0].definition.startswith('!'): # shell alias
2188 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
2188 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
2189 else:
2189 else:
2190 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
2190 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
2191 if ui.quiet or not full:
2191 if ui.quiet or not full:
2192 doc = doc.splitlines()[0]
2192 doc = doc.splitlines()[0]
2193 keep = ui.verbose and ['verbose'] or []
2193 keep = ui.verbose and ['verbose'] or []
2194 formatted, pruned = minirst.format(doc, textwidth, keep=keep)
2194 formatted, pruned = minirst.format(doc, textwidth, keep=keep)
2195 ui.write("\n%s\n" % formatted)
2195 ui.write("\n%s\n" % formatted)
2196 if pruned:
2196 if pruned:
2197 ui.write(_('\nuse "hg -v help %s" to show verbose help\n') % name)
2197 ui.write(_('\nuse "hg -v help %s" to show verbose help\n') % name)
2198
2198
2199 if not ui.quiet:
2199 if not ui.quiet:
2200 # options
2200 # options
2201 if entry[1]:
2201 if entry[1]:
2202 option_lists.append((_("options:\n"), entry[1]))
2202 option_lists.append((_("options:\n"), entry[1]))
2203
2203
2204 addglobalopts(False)
2204 addglobalopts(False)
2205
2205
2206 def helplist(header, select=None):
2206 def helplist(header, select=None):
2207 h = {}
2207 h = {}
2208 cmds = {}
2208 cmds = {}
2209 for c, e in table.iteritems():
2209 for c, e in table.iteritems():
2210 f = c.split("|", 1)[0]
2210 f = c.split("|", 1)[0]
2211 if select and not select(f):
2211 if select and not select(f):
2212 continue
2212 continue
2213 if (not select and name != 'shortlist' and
2213 if (not select and name != 'shortlist' and
2214 e[0].__module__ != __name__):
2214 e[0].__module__ != __name__):
2215 continue
2215 continue
2216 if name == "shortlist" and not f.startswith("^"):
2216 if name == "shortlist" and not f.startswith("^"):
2217 continue
2217 continue
2218 f = f.lstrip("^")
2218 f = f.lstrip("^")
2219 if not ui.debugflag and f.startswith("debug"):
2219 if not ui.debugflag and f.startswith("debug"):
2220 continue
2220 continue
2221 doc = e[0].__doc__
2221 doc = e[0].__doc__
2222 if doc and 'DEPRECATED' in doc and not ui.verbose:
2222 if doc and 'DEPRECATED' in doc and not ui.verbose:
2223 continue
2223 continue
2224 doc = gettext(doc)
2224 doc = gettext(doc)
2225 if not doc:
2225 if not doc:
2226 doc = _("(no help text available)")
2226 doc = _("(no help text available)")
2227 h[f] = doc.splitlines()[0].rstrip()
2227 h[f] = doc.splitlines()[0].rstrip()
2228 cmds[f] = c.lstrip("^")
2228 cmds[f] = c.lstrip("^")
2229
2229
2230 if not h:
2230 if not h:
2231 ui.status(_('no commands defined\n'))
2231 ui.status(_('no commands defined\n'))
2232 return
2232 return
2233
2233
2234 ui.status(header)
2234 ui.status(header)
2235 fns = sorted(h)
2235 fns = sorted(h)
2236 m = max(map(len, fns))
2236 m = max(map(len, fns))
2237 for f in fns:
2237 for f in fns:
2238 if ui.verbose:
2238 if ui.verbose:
2239 commands = cmds[f].replace("|",", ")
2239 commands = cmds[f].replace("|",", ")
2240 ui.write(" %s:\n %s\n"%(commands, h[f]))
2240 ui.write(" %s:\n %s\n"%(commands, h[f]))
2241 else:
2241 else:
2242 ui.write('%s\n' % (util.wrap(h[f], textwidth,
2242 ui.write('%s\n' % (util.wrap(h[f], textwidth,
2243 initindent=' %-*s ' % (m, f),
2243 initindent=' %-*s ' % (m, f),
2244 hangindent=' ' * (m + 4))))
2244 hangindent=' ' * (m + 4))))
2245
2245
2246 if not ui.quiet:
2246 if not ui.quiet:
2247 addglobalopts(True)
2247 addglobalopts(True)
2248
2248
2249 def helptopic(name):
2249 def helptopic(name):
2250 for names, header, doc in help.helptable:
2250 for names, header, doc in help.helptable:
2251 if name in names:
2251 if name in names:
2252 break
2252 break
2253 else:
2253 else:
2254 raise error.UnknownCommand(name)
2254 raise error.UnknownCommand(name)
2255
2255
2256 # description
2256 # description
2257 if not doc:
2257 if not doc:
2258 doc = _("(no help text available)")
2258 doc = _("(no help text available)")
2259 if hasattr(doc, '__call__'):
2259 if hasattr(doc, '__call__'):
2260 doc = doc()
2260 doc = doc()
2261
2261
2262 ui.write("%s\n\n" % header)
2262 ui.write("%s\n\n" % header)
2263 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
2263 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
2264
2264
2265 def helpext(name):
2265 def helpext(name):
2266 try:
2266 try:
2267 mod = extensions.find(name)
2267 mod = extensions.find(name)
2268 doc = gettext(mod.__doc__) or _('no help text available')
2268 doc = gettext(mod.__doc__) or _('no help text available')
2269 except KeyError:
2269 except KeyError:
2270 mod = None
2270 mod = None
2271 doc = extensions.disabledext(name)
2271 doc = extensions.disabledext(name)
2272 if not doc:
2272 if not doc:
2273 raise error.UnknownCommand(name)
2273 raise error.UnknownCommand(name)
2274
2274
2275 if '\n' not in doc:
2275 if '\n' not in doc:
2276 head, tail = doc, ""
2276 head, tail = doc, ""
2277 else:
2277 else:
2278 head, tail = doc.split('\n', 1)
2278 head, tail = doc.split('\n', 1)
2279 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
2279 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
2280 if tail:
2280 if tail:
2281 ui.write(minirst.format(tail, textwidth))
2281 ui.write(minirst.format(tail, textwidth))
2282 ui.status('\n\n')
2282 ui.status('\n\n')
2283
2283
2284 if mod:
2284 if mod:
2285 try:
2285 try:
2286 ct = mod.cmdtable
2286 ct = mod.cmdtable
2287 except AttributeError:
2287 except AttributeError:
2288 ct = {}
2288 ct = {}
2289 modcmds = set([c.split('|', 1)[0] for c in ct])
2289 modcmds = set([c.split('|', 1)[0] for c in ct])
2290 helplist(_('list of commands:\n\n'), modcmds.__contains__)
2290 helplist(_('list of commands:\n\n'), modcmds.__contains__)
2291 else:
2291 else:
2292 ui.write(_('use "hg help extensions" for information on enabling '
2292 ui.write(_('use "hg help extensions" for information on enabling '
2293 'extensions\n'))
2293 'extensions\n'))
2294
2294
2295 def helpextcmd(name):
2295 def helpextcmd(name):
2296 cmd, ext, mod = extensions.disabledcmd(ui, name, ui.config('ui', 'strict'))
2296 cmd, ext, mod = extensions.disabledcmd(ui, name, ui.config('ui', 'strict'))
2297 doc = gettext(mod.__doc__).splitlines()[0]
2297 doc = gettext(mod.__doc__).splitlines()[0]
2298
2298
2299 msg = help.listexts(_("'%s' is provided by the following "
2299 msg = help.listexts(_("'%s' is provided by the following "
2300 "extension:") % cmd, {ext: doc}, len(ext),
2300 "extension:") % cmd, {ext: doc}, len(ext),
2301 indent=4)
2301 indent=4)
2302 ui.write(minirst.format(msg, textwidth))
2302 ui.write(minirst.format(msg, textwidth))
2303 ui.write('\n\n')
2303 ui.write('\n\n')
2304 ui.write(_('use "hg help extensions" for information on enabling '
2304 ui.write(_('use "hg help extensions" for information on enabling '
2305 'extensions\n'))
2305 'extensions\n'))
2306
2306
2307 help.addtopichook('revsets', revset.makedoc)
2307 help.addtopichook('revsets', revset.makedoc)
2308 help.addtopichook('templates', templatekw.makedoc)
2308 help.addtopichook('templates', templatekw.makedoc)
2309 help.addtopichook('templates', templatefilters.makedoc)
2309 help.addtopichook('templates', templatefilters.makedoc)
2310
2310
2311 if name and name != 'shortlist':
2311 if name and name != 'shortlist':
2312 i = None
2312 i = None
2313 if unknowncmd:
2313 if unknowncmd:
2314 queries = (helpextcmd,)
2314 queries = (helpextcmd,)
2315 else:
2315 else:
2316 queries = (helptopic, helpcmd, helpext, helpextcmd)
2316 queries = (helptopic, helpcmd, helpext, helpextcmd)
2317 for f in queries:
2317 for f in queries:
2318 try:
2318 try:
2319 f(name)
2319 f(name)
2320 i = None
2320 i = None
2321 break
2321 break
2322 except error.UnknownCommand, inst:
2322 except error.UnknownCommand, inst:
2323 i = inst
2323 i = inst
2324 if i:
2324 if i:
2325 raise i
2325 raise i
2326
2326
2327 else:
2327 else:
2328 # program name
2328 # program name
2329 if ui.verbose or with_version:
2329 if ui.verbose or with_version:
2330 version_(ui)
2330 version_(ui)
2331 else:
2331 else:
2332 ui.status(_("Mercurial Distributed SCM\n"))
2332 ui.status(_("Mercurial Distributed SCM\n"))
2333 ui.status('\n')
2333 ui.status('\n')
2334
2334
2335 # list of commands
2335 # list of commands
2336 if name == "shortlist":
2336 if name == "shortlist":
2337 header = _('basic commands:\n\n')
2337 header = _('basic commands:\n\n')
2338 else:
2338 else:
2339 header = _('list of commands:\n\n')
2339 header = _('list of commands:\n\n')
2340
2340
2341 helplist(header)
2341 helplist(header)
2342 if name != 'shortlist':
2342 if name != 'shortlist':
2343 exts, maxlength = extensions.enabled()
2343 exts, maxlength = extensions.enabled()
2344 text = help.listexts(_('enabled extensions:'), exts, maxlength)
2344 text = help.listexts(_('enabled extensions:'), exts, maxlength)
2345 if text:
2345 if text:
2346 ui.write("\n%s\n" % minirst.format(text, textwidth))
2346 ui.write("\n%s\n" % minirst.format(text, textwidth))
2347
2347
2348 # list all option lists
2348 # list all option lists
2349 opt_output = []
2349 opt_output = []
2350 multioccur = False
2350 multioccur = False
2351 for title, options in option_lists:
2351 for title, options in option_lists:
2352 opt_output.append(("\n%s" % title, None))
2352 opt_output.append(("\n%s" % title, None))
2353 for option in options:
2353 for option in options:
2354 if len(option) == 5:
2354 if len(option) == 5:
2355 shortopt, longopt, default, desc, optlabel = option
2355 shortopt, longopt, default, desc, optlabel = option
2356 else:
2356 else:
2357 shortopt, longopt, default, desc = option
2357 shortopt, longopt, default, desc = option
2358 optlabel = _("VALUE") # default label
2358 optlabel = _("VALUE") # default label
2359
2359
2360 if _("DEPRECATED") in desc and not ui.verbose:
2360 if _("DEPRECATED") in desc and not ui.verbose:
2361 continue
2361 continue
2362 if isinstance(default, list):
2362 if isinstance(default, list):
2363 numqualifier = " %s [+]" % optlabel
2363 numqualifier = " %s [+]" % optlabel
2364 multioccur = True
2364 multioccur = True
2365 elif (default is not None) and not isinstance(default, bool):
2365 elif (default is not None) and not isinstance(default, bool):
2366 numqualifier = " %s" % optlabel
2366 numqualifier = " %s" % optlabel
2367 else:
2367 else:
2368 numqualifier = ""
2368 numqualifier = ""
2369 opt_output.append(("%2s%s" %
2369 opt_output.append(("%2s%s" %
2370 (shortopt and "-%s" % shortopt,
2370 (shortopt and "-%s" % shortopt,
2371 longopt and " --%s%s" %
2371 longopt and " --%s%s" %
2372 (longopt, numqualifier)),
2372 (longopt, numqualifier)),
2373 "%s%s" % (desc,
2373 "%s%s" % (desc,
2374 default
2374 default
2375 and _(" (default: %s)") % default
2375 and _(" (default: %s)") % default
2376 or "")))
2376 or "")))
2377 if multioccur:
2377 if multioccur:
2378 msg = _("\n[+] marked option can be specified multiple times")
2378 msg = _("\n[+] marked option can be specified multiple times")
2379 if ui.verbose and name != 'shortlist':
2379 if ui.verbose and name != 'shortlist':
2380 opt_output.append((msg, None))
2380 opt_output.append((msg, None))
2381 else:
2381 else:
2382 opt_output.insert(-1, (msg, None))
2382 opt_output.insert(-1, (msg, None))
2383
2383
2384 if not name:
2384 if not name:
2385 ui.write(_("\nadditional help topics:\n\n"))
2385 ui.write(_("\nadditional help topics:\n\n"))
2386 topics = []
2386 topics = []
2387 for names, header, doc in help.helptable:
2387 for names, header, doc in help.helptable:
2388 topics.append((sorted(names, key=len, reverse=True)[0], header))
2388 topics.append((sorted(names, key=len, reverse=True)[0], header))
2389 topics_len = max([len(s[0]) for s in topics])
2389 topics_len = max([len(s[0]) for s in topics])
2390 for t, desc in topics:
2390 for t, desc in topics:
2391 ui.write(" %-*s %s\n" % (topics_len, t, desc))
2391 ui.write(" %-*s %s\n" % (topics_len, t, desc))
2392
2392
2393 if opt_output:
2393 if opt_output:
2394 colwidth = encoding.colwidth
2394 colwidth = encoding.colwidth
2395 # normalize: (opt or message, desc or None, width of opt)
2395 # normalize: (opt or message, desc or None, width of opt)
2396 entries = [desc and (opt, desc, colwidth(opt)) or (opt, None, 0)
2396 entries = [desc and (opt, desc, colwidth(opt)) or (opt, None, 0)
2397 for opt, desc in opt_output]
2397 for opt, desc in opt_output]
2398 hanging = max([e[2] for e in entries])
2398 hanging = max([e[2] for e in entries])
2399 for opt, desc, width in entries:
2399 for opt, desc, width in entries:
2400 if desc:
2400 if desc:
2401 initindent = ' %s%s ' % (opt, ' ' * (hanging - width))
2401 initindent = ' %s%s ' % (opt, ' ' * (hanging - width))
2402 hangindent = ' ' * (hanging + 3)
2402 hangindent = ' ' * (hanging + 3)
2403 ui.write('%s\n' % (util.wrap(desc, textwidth,
2403 ui.write('%s\n' % (util.wrap(desc, textwidth,
2404 initindent=initindent,
2404 initindent=initindent,
2405 hangindent=hangindent)))
2405 hangindent=hangindent)))
2406 else:
2406 else:
2407 ui.write("%s\n" % opt)
2407 ui.write("%s\n" % opt)
2408
2408
2409 def identify(ui, repo, source=None, rev=None,
2409 def identify(ui, repo, source=None, rev=None,
2410 num=None, id=None, branch=None, tags=None, bookmarks=None):
2410 num=None, id=None, branch=None, tags=None, bookmarks=None):
2411 """identify the working copy or specified revision
2411 """identify the working copy or specified revision
2412
2412
2413 Print a summary identifying the repository state at REV using one or
2413 Print a summary identifying the repository state at REV using one or
2414 two parent hash identifiers, followed by a "+" if the working
2414 two parent hash identifiers, followed by a "+" if the working
2415 directory has uncommitted changes, the branch name (if not default),
2415 directory has uncommitted changes, the branch name (if not default),
2416 a list of tags, and a list of bookmarks.
2416 a list of tags, and a list of bookmarks.
2417
2417
2418 When REV is not given, print a summary of the current state of the
2418 When REV is not given, print a summary of the current state of the
2419 repository.
2419 repository.
2420
2420
2421 Specifying a path to a repository root or Mercurial bundle will
2421 Specifying a path to a repository root or Mercurial bundle will
2422 cause lookup to operate on that repository/bundle.
2422 cause lookup to operate on that repository/bundle.
2423
2423
2424 Returns 0 if successful.
2424 Returns 0 if successful.
2425 """
2425 """
2426
2426
2427 if not repo and not source:
2427 if not repo and not source:
2428 raise util.Abort(_("there is no Mercurial repository here "
2428 raise util.Abort(_("there is no Mercurial repository here "
2429 "(.hg not found)"))
2429 "(.hg not found)"))
2430
2430
2431 hexfunc = ui.debugflag and hex or short
2431 hexfunc = ui.debugflag and hex or short
2432 default = not (num or id or branch or tags or bookmarks)
2432 default = not (num or id or branch or tags or bookmarks)
2433 output = []
2433 output = []
2434 revs = []
2434 revs = []
2435
2435
2436 if source:
2436 if source:
2437 source, branches = hg.parseurl(ui.expandpath(source))
2437 source, branches = hg.parseurl(ui.expandpath(source))
2438 repo = hg.repository(ui, source)
2438 repo = hg.repository(ui, source)
2439 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
2439 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
2440
2440
2441 if not repo.local():
2441 if not repo.local():
2442 if num or branch or tags:
2442 if num or branch or tags:
2443 raise util.Abort(
2443 raise util.Abort(
2444 _("can't query remote revision number, branch, or tags"))
2444 _("can't query remote revision number, branch, or tags"))
2445 if not rev and revs:
2445 if not rev and revs:
2446 rev = revs[0]
2446 rev = revs[0]
2447 if not rev:
2447 if not rev:
2448 rev = "tip"
2448 rev = "tip"
2449
2449
2450 remoterev = repo.lookup(rev)
2450 remoterev = repo.lookup(rev)
2451 if default or id:
2451 if default or id:
2452 output = [hexfunc(remoterev)]
2452 output = [hexfunc(remoterev)]
2453
2453
2454 def getbms():
2454 def getbms():
2455 bms = []
2455 bms = []
2456
2456
2457 if 'bookmarks' in repo.listkeys('namespaces'):
2457 if 'bookmarks' in repo.listkeys('namespaces'):
2458 hexremoterev = hex(remoterev)
2458 hexremoterev = hex(remoterev)
2459 bms = [bm for bm, bmr in repo.listkeys('bookmarks').iteritems()
2459 bms = [bm for bm, bmr in repo.listkeys('bookmarks').iteritems()
2460 if bmr == hexremoterev]
2460 if bmr == hexremoterev]
2461
2461
2462 return bms
2462 return bms
2463
2463
2464 if bookmarks:
2464 if bookmarks:
2465 output.extend(getbms())
2465 output.extend(getbms())
2466 elif default and not ui.quiet:
2466 elif default and not ui.quiet:
2467 # multiple bookmarks for a single parent separated by '/'
2467 # multiple bookmarks for a single parent separated by '/'
2468 bm = '/'.join(getbms())
2468 bm = '/'.join(getbms())
2469 if bm:
2469 if bm:
2470 output.append(bm)
2470 output.append(bm)
2471 else:
2471 else:
2472 if not rev:
2472 if not rev:
2473 ctx = repo[None]
2473 ctx = repo[None]
2474 parents = ctx.parents()
2474 parents = ctx.parents()
2475 changed = ""
2475 changed = ""
2476 if default or id or num:
2476 if default or id or num:
2477 changed = util.any(repo.status()) and "+" or ""
2477 changed = util.any(repo.status()) and "+" or ""
2478 if default or id:
2478 if default or id:
2479 output = ["%s%s" %
2479 output = ["%s%s" %
2480 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
2480 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
2481 if num:
2481 if num:
2482 output.append("%s%s" %
2482 output.append("%s%s" %
2483 ('+'.join([str(p.rev()) for p in parents]), changed))
2483 ('+'.join([str(p.rev()) for p in parents]), changed))
2484 else:
2484 else:
2485 ctx = cmdutil.revsingle(repo, rev)
2485 ctx = cmdutil.revsingle(repo, rev)
2486 if default or id:
2486 if default or id:
2487 output = [hexfunc(ctx.node())]
2487 output = [hexfunc(ctx.node())]
2488 if num:
2488 if num:
2489 output.append(str(ctx.rev()))
2489 output.append(str(ctx.rev()))
2490
2490
2491 if default and not ui.quiet:
2491 if default and not ui.quiet:
2492 b = ctx.branch()
2492 b = ctx.branch()
2493 if b != 'default':
2493 if b != 'default':
2494 output.append("(%s)" % b)
2494 output.append("(%s)" % b)
2495
2495
2496 # multiple tags for a single parent separated by '/'
2496 # multiple tags for a single parent separated by '/'
2497 t = '/'.join(ctx.tags())
2497 t = '/'.join(ctx.tags())
2498 if t:
2498 if t:
2499 output.append(t)
2499 output.append(t)
2500
2500
2501 # multiple bookmarks for a single parent separated by '/'
2501 # multiple bookmarks for a single parent separated by '/'
2502 bm = '/'.join(ctx.bookmarks())
2502 bm = '/'.join(ctx.bookmarks())
2503 if bm:
2503 if bm:
2504 output.append(bm)
2504 output.append(bm)
2505 else:
2505 else:
2506 if branch:
2506 if branch:
2507 output.append(ctx.branch())
2507 output.append(ctx.branch())
2508
2508
2509 if tags:
2509 if tags:
2510 output.extend(ctx.tags())
2510 output.extend(ctx.tags())
2511
2511
2512 if bookmarks:
2512 if bookmarks:
2513 output.extend(ctx.bookmarks())
2513 output.extend(ctx.bookmarks())
2514
2514
2515 ui.write("%s\n" % ' '.join(output))
2515 ui.write("%s\n" % ' '.join(output))
2516
2516
2517 def import_(ui, repo, patch1, *patches, **opts):
2517 def import_(ui, repo, patch1, *patches, **opts):
2518 """import an ordered set of patches
2518 """import an ordered set of patches
2519
2519
2520 Import a list of patches and commit them individually (unless
2520 Import a list of patches and commit them individually (unless
2521 --no-commit is specified).
2521 --no-commit is specified).
2522
2522
2523 If there are outstanding changes in the working directory, import
2523 If there are outstanding changes in the working directory, import
2524 will abort unless given the -f/--force flag.
2524 will abort unless given the -f/--force flag.
2525
2525
2526 You can import a patch straight from a mail message. Even patches
2526 You can import a patch straight from a mail message. Even patches
2527 as attachments work (to use the body part, it must have type
2527 as attachments work (to use the body part, it must have type
2528 text/plain or text/x-patch). From and Subject headers of email
2528 text/plain or text/x-patch). From and Subject headers of email
2529 message are used as default committer and commit message. All
2529 message are used as default committer and commit message. All
2530 text/plain body parts before first diff are added to commit
2530 text/plain body parts before first diff are added to commit
2531 message.
2531 message.
2532
2532
2533 If the imported patch was generated by :hg:`export`, user and
2533 If the imported patch was generated by :hg:`export`, user and
2534 description from patch override values from message headers and
2534 description from patch override values from message headers and
2535 body. Values given on command line with -m/--message and -u/--user
2535 body. Values given on command line with -m/--message and -u/--user
2536 override these.
2536 override these.
2537
2537
2538 If --exact is specified, import will set the working directory to
2538 If --exact is specified, import will set the working directory to
2539 the parent of each patch before applying it, and will abort if the
2539 the parent of each patch before applying it, and will abort if the
2540 resulting changeset has a different ID than the one recorded in
2540 resulting changeset has a different ID than the one recorded in
2541 the patch. This may happen due to character set problems or other
2541 the patch. This may happen due to character set problems or other
2542 deficiencies in the text patch format.
2542 deficiencies in the text patch format.
2543
2543
2544 With -s/--similarity, hg will attempt to discover renames and
2544 With -s/--similarity, hg will attempt to discover renames and
2545 copies in the patch in the same way as 'addremove'.
2545 copies in the patch in the same way as 'addremove'.
2546
2546
2547 To read a patch from standard input, use "-" as the patch name. If
2547 To read a patch from standard input, use "-" as the patch name. If
2548 a URL is specified, the patch will be downloaded from it.
2548 a URL is specified, the patch will be downloaded from it.
2549 See :hg:`help dates` for a list of formats valid for -d/--date.
2549 See :hg:`help dates` for a list of formats valid for -d/--date.
2550
2550
2551 Returns 0 on success.
2551 Returns 0 on success.
2552 """
2552 """
2553 patches = (patch1,) + patches
2553 patches = (patch1,) + patches
2554
2554
2555 date = opts.get('date')
2555 date = opts.get('date')
2556 if date:
2556 if date:
2557 opts['date'] = util.parsedate(date)
2557 opts['date'] = util.parsedate(date)
2558
2558
2559 try:
2559 try:
2560 sim = float(opts.get('similarity') or 0)
2560 sim = float(opts.get('similarity') or 0)
2561 except ValueError:
2561 except ValueError:
2562 raise util.Abort(_('similarity must be a number'))
2562 raise util.Abort(_('similarity must be a number'))
2563 if sim < 0 or sim > 100:
2563 if sim < 0 or sim > 100:
2564 raise util.Abort(_('similarity must be between 0 and 100'))
2564 raise util.Abort(_('similarity must be between 0 and 100'))
2565
2565
2566 if opts.get('exact') or not opts.get('force'):
2566 if opts.get('exact') or not opts.get('force'):
2567 cmdutil.bail_if_changed(repo)
2567 cmdutil.bail_if_changed(repo)
2568
2568
2569 d = opts["base"]
2569 d = opts["base"]
2570 strip = opts["strip"]
2570 strip = opts["strip"]
2571 wlock = lock = None
2571 wlock = lock = None
2572 msgs = []
2572 msgs = []
2573
2573
2574 def tryone(ui, hunk):
2574 def tryone(ui, hunk):
2575 tmpname, message, user, date, branch, nodeid, p1, p2 = \
2575 tmpname, message, user, date, branch, nodeid, p1, p2 = \
2576 patch.extract(ui, hunk)
2576 patch.extract(ui, hunk)
2577
2577
2578 if not tmpname:
2578 if not tmpname:
2579 return None
2579 return None
2580 commitid = _('to working directory')
2580 commitid = _('to working directory')
2581
2581
2582 try:
2582 try:
2583 cmdline_message = cmdutil.logmessage(opts)
2583 cmdline_message = cmdutil.logmessage(opts)
2584 if cmdline_message:
2584 if cmdline_message:
2585 # pickup the cmdline msg
2585 # pickup the cmdline msg
2586 message = cmdline_message
2586 message = cmdline_message
2587 elif message:
2587 elif message:
2588 # pickup the patch msg
2588 # pickup the patch msg
2589 message = message.strip()
2589 message = message.strip()
2590 else:
2590 else:
2591 # launch the editor
2591 # launch the editor
2592 message = None
2592 message = None
2593 ui.debug('message:\n%s\n' % message)
2593 ui.debug('message:\n%s\n' % message)
2594
2594
2595 wp = repo.parents()
2595 wp = repo.parents()
2596 if opts.get('exact'):
2596 if opts.get('exact'):
2597 if not nodeid or not p1:
2597 if not nodeid or not p1:
2598 raise util.Abort(_('not a Mercurial patch'))
2598 raise util.Abort(_('not a Mercurial patch'))
2599 p1 = repo.lookup(p1)
2599 p1 = repo.lookup(p1)
2600 p2 = repo.lookup(p2 or hex(nullid))
2600 p2 = repo.lookup(p2 or hex(nullid))
2601
2601
2602 if p1 != wp[0].node():
2602 if p1 != wp[0].node():
2603 hg.clean(repo, p1)
2603 hg.clean(repo, p1)
2604 repo.dirstate.setparents(p1, p2)
2604 repo.dirstate.setparents(p1, p2)
2605 elif p2:
2605 elif p2:
2606 try:
2606 try:
2607 p1 = repo.lookup(p1)
2607 p1 = repo.lookup(p1)
2608 p2 = repo.lookup(p2)
2608 p2 = repo.lookup(p2)
2609 if p1 == wp[0].node():
2609 if p1 == wp[0].node():
2610 repo.dirstate.setparents(p1, p2)
2610 repo.dirstate.setparents(p1, p2)
2611 except error.RepoError:
2611 except error.RepoError:
2612 pass
2612 pass
2613 if opts.get('exact') or opts.get('import_branch'):
2613 if opts.get('exact') or opts.get('import_branch'):
2614 repo.dirstate.setbranch(branch or 'default')
2614 repo.dirstate.setbranch(branch or 'default')
2615
2615
2616 files = {}
2616 files = {}
2617 try:
2617 try:
2618 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
2618 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
2619 files=files, eolmode=None)
2619 files=files, eolmode=None)
2620 finally:
2620 finally:
2621 files = cmdutil.updatedir(ui, repo, files,
2621 files = cmdutil.updatedir(ui, repo, files,
2622 similarity=sim / 100.0)
2622 similarity=sim / 100.0)
2623 if opts.get('no_commit'):
2623 if opts.get('no_commit'):
2624 if message:
2624 if message:
2625 msgs.append(message)
2625 msgs.append(message)
2626 else:
2626 else:
2627 if opts.get('exact'):
2627 if opts.get('exact'):
2628 m = None
2628 m = None
2629 else:
2629 else:
2630 m = cmdutil.matchfiles(repo, files or [])
2630 m = cmdutil.matchfiles(repo, files or [])
2631 n = repo.commit(message, opts.get('user') or user,
2631 n = repo.commit(message, opts.get('user') or user,
2632 opts.get('date') or date, match=m,
2632 opts.get('date') or date, match=m,
2633 editor=cmdutil.commiteditor)
2633 editor=cmdutil.commiteditor)
2634 if opts.get('exact'):
2634 if opts.get('exact'):
2635 if hex(n) != nodeid:
2635 if hex(n) != nodeid:
2636 repo.rollback()
2636 repo.rollback()
2637 raise util.Abort(_('patch is damaged'
2637 raise util.Abort(_('patch is damaged'
2638 ' or loses information'))
2638 ' or loses information'))
2639 # Force a dirstate write so that the next transaction
2639 # Force a dirstate write so that the next transaction
2640 # backups an up-do-date file.
2640 # backups an up-do-date file.
2641 repo.dirstate.write()
2641 repo.dirstate.write()
2642 if n:
2642 if n:
2643 commitid = short(n)
2643 commitid = short(n)
2644
2644
2645 return commitid
2645 return commitid
2646 finally:
2646 finally:
2647 os.unlink(tmpname)
2647 os.unlink(tmpname)
2648
2648
2649 try:
2649 try:
2650 wlock = repo.wlock()
2650 wlock = repo.wlock()
2651 lock = repo.lock()
2651 lock = repo.lock()
2652 lastcommit = None
2652 lastcommit = None
2653 for p in patches:
2653 for p in patches:
2654 pf = os.path.join(d, p)
2654 pf = os.path.join(d, p)
2655
2655
2656 if pf == '-':
2656 if pf == '-':
2657 ui.status(_("applying patch from stdin\n"))
2657 ui.status(_("applying patch from stdin\n"))
2658 pf = sys.stdin
2658 pf = sys.stdin
2659 else:
2659 else:
2660 ui.status(_("applying %s\n") % p)
2660 ui.status(_("applying %s\n") % p)
2661 pf = url.open(ui, pf)
2661 pf = url.open(ui, pf)
2662
2662
2663 haspatch = False
2663 haspatch = False
2664 for hunk in patch.split(pf):
2664 for hunk in patch.split(pf):
2665 commitid = tryone(ui, hunk)
2665 commitid = tryone(ui, hunk)
2666 if commitid:
2666 if commitid:
2667 haspatch = True
2667 haspatch = True
2668 if lastcommit:
2668 if lastcommit:
2669 ui.status(_('applied %s\n') % lastcommit)
2669 ui.status(_('applied %s\n') % lastcommit)
2670 lastcommit = commitid
2670 lastcommit = commitid
2671
2671
2672 if not haspatch:
2672 if not haspatch:
2673 raise util.Abort(_('no diffs found'))
2673 raise util.Abort(_('no diffs found'))
2674
2674
2675 if msgs:
2675 if msgs:
2676 repo.opener.write('last-message.txt', '\n* * *\n'.join(msgs))
2676 repo.opener.write('last-message.txt', '\n* * *\n'.join(msgs))
2677 finally:
2677 finally:
2678 release(lock, wlock)
2678 release(lock, wlock)
2679
2679
2680 def incoming(ui, repo, source="default", **opts):
2680 def incoming(ui, repo, source="default", **opts):
2681 """show new changesets found in source
2681 """show new changesets found in source
2682
2682
2683 Show new changesets found in the specified path/URL or the default
2683 Show new changesets found in the specified path/URL or the default
2684 pull location. These are the changesets that would have been pulled
2684 pull location. These are the changesets that would have been pulled
2685 if a pull at the time you issued this command.
2685 if a pull at the time you issued this command.
2686
2686
2687 For remote repository, using --bundle avoids downloading the
2687 For remote repository, using --bundle avoids downloading the
2688 changesets twice if the incoming is followed by a pull.
2688 changesets twice if the incoming is followed by a pull.
2689
2689
2690 See pull for valid source format details.
2690 See pull for valid source format details.
2691
2691
2692 Returns 0 if there are incoming changes, 1 otherwise.
2692 Returns 0 if there are incoming changes, 1 otherwise.
2693 """
2693 """
2694 if opts.get('bundle') and opts.get('subrepos'):
2694 if opts.get('bundle') and opts.get('subrepos'):
2695 raise util.Abort(_('cannot combine --bundle and --subrepos'))
2695 raise util.Abort(_('cannot combine --bundle and --subrepos'))
2696
2696
2697 if opts.get('bookmarks'):
2697 if opts.get('bookmarks'):
2698 source, branches = hg.parseurl(ui.expandpath(source),
2698 source, branches = hg.parseurl(ui.expandpath(source),
2699 opts.get('branch'))
2699 opts.get('branch'))
2700 other = hg.repository(hg.remoteui(repo, opts), source)
2700 other = hg.repository(hg.remoteui(repo, opts), source)
2701 if 'bookmarks' not in other.listkeys('namespaces'):
2701 if 'bookmarks' not in other.listkeys('namespaces'):
2702 ui.warn(_("remote doesn't support bookmarks\n"))
2702 ui.warn(_("remote doesn't support bookmarks\n"))
2703 return 0
2703 return 0
2704 ui.status(_('comparing with %s\n') % util.hidepassword(source))
2704 ui.status(_('comparing with %s\n') % util.hidepassword(source))
2705 return bookmarks.diff(ui, repo, other)
2705 return bookmarks.diff(ui, repo, other)
2706
2706
2707 ret = hg.incoming(ui, repo, source, opts)
2707 ret = hg.incoming(ui, repo, source, opts)
2708 return ret
2708 return ret
2709
2709
2710 def init(ui, dest=".", **opts):
2710 def init(ui, dest=".", **opts):
2711 """create a new repository in the given directory
2711 """create a new repository in the given directory
2712
2712
2713 Initialize a new repository in the given directory. If the given
2713 Initialize a new repository in the given directory. If the given
2714 directory does not exist, it will be created.
2714 directory does not exist, it will be created.
2715
2715
2716 If no directory is given, the current directory is used.
2716 If no directory is given, the current directory is used.
2717
2717
2718 It is possible to specify an ``ssh://`` URL as the destination.
2718 It is possible to specify an ``ssh://`` URL as the destination.
2719 See :hg:`help urls` for more information.
2719 See :hg:`help urls` for more information.
2720
2720
2721 Returns 0 on success.
2721 Returns 0 on success.
2722 """
2722 """
2723 hg.repository(hg.remoteui(ui, opts), ui.expandpath(dest), create=1)
2723 hg.repository(hg.remoteui(ui, opts), ui.expandpath(dest), create=1)
2724
2724
2725 def locate(ui, repo, *pats, **opts):
2725 def locate(ui, repo, *pats, **opts):
2726 """locate files matching specific patterns
2726 """locate files matching specific patterns
2727
2727
2728 Print files under Mercurial control in the working directory whose
2728 Print files under Mercurial control in the working directory whose
2729 names match the given patterns.
2729 names match the given patterns.
2730
2730
2731 By default, this command searches all directories in the working
2731 By default, this command searches all directories in the working
2732 directory. To search just the current directory and its
2732 directory. To search just the current directory and its
2733 subdirectories, use "--include .".
2733 subdirectories, use "--include .".
2734
2734
2735 If no patterns are given to match, this command prints the names
2735 If no patterns are given to match, this command prints the names
2736 of all files under Mercurial control in the working directory.
2736 of all files under Mercurial control in the working directory.
2737
2737
2738 If you want to feed the output of this command into the "xargs"
2738 If you want to feed the output of this command into the "xargs"
2739 command, use the -0 option to both this command and "xargs". This
2739 command, use the -0 option to both this command and "xargs". This
2740 will avoid the problem of "xargs" treating single filenames that
2740 will avoid the problem of "xargs" treating single filenames that
2741 contain whitespace as multiple filenames.
2741 contain whitespace as multiple filenames.
2742
2742
2743 Returns 0 if a match is found, 1 otherwise.
2743 Returns 0 if a match is found, 1 otherwise.
2744 """
2744 """
2745 end = opts.get('print0') and '\0' or '\n'
2745 end = opts.get('print0') and '\0' or '\n'
2746 rev = cmdutil.revsingle(repo, opts.get('rev'), None).node()
2746 rev = cmdutil.revsingle(repo, opts.get('rev'), None).node()
2747
2747
2748 ret = 1
2748 ret = 1
2749 m = cmdutil.match(repo, pats, opts, default='relglob')
2749 m = cmdutil.match(repo, pats, opts, default='relglob')
2750 m.bad = lambda x, y: False
2750 m.bad = lambda x, y: False
2751 for abs in repo[rev].walk(m):
2751 for abs in repo[rev].walk(m):
2752 if not rev and abs not in repo.dirstate:
2752 if not rev and abs not in repo.dirstate:
2753 continue
2753 continue
2754 if opts.get('fullpath'):
2754 if opts.get('fullpath'):
2755 ui.write(repo.wjoin(abs), end)
2755 ui.write(repo.wjoin(abs), end)
2756 else:
2756 else:
2757 ui.write(((pats and m.rel(abs)) or abs), end)
2757 ui.write(((pats and m.rel(abs)) or abs), end)
2758 ret = 0
2758 ret = 0
2759
2759
2760 return ret
2760 return ret
2761
2761
2762 def log(ui, repo, *pats, **opts):
2762 def log(ui, repo, *pats, **opts):
2763 """show revision history of entire repository or files
2763 """show revision history of entire repository or files
2764
2764
2765 Print the revision history of the specified files or the entire
2765 Print the revision history of the specified files or the entire
2766 project.
2766 project.
2767
2767
2768 File history is shown without following rename or copy history of
2768 File history is shown without following rename or copy history of
2769 files. Use -f/--follow with a filename to follow history across
2769 files. Use -f/--follow with a filename to follow history across
2770 renames and copies. --follow without a filename will only show
2770 renames and copies. --follow without a filename will only show
2771 ancestors or descendants of the starting revision. --follow-first
2771 ancestors or descendants of the starting revision. --follow-first
2772 only follows the first parent of merge revisions.
2772 only follows the first parent of merge revisions.
2773
2773
2774 If no revision range is specified, the default is ``tip:0`` unless
2774 If no revision range is specified, the default is ``tip:0`` unless
2775 --follow is set, in which case the working directory parent is
2775 --follow is set, in which case the working directory parent is
2776 used as the starting revision. You can specify a revision set for
2776 used as the starting revision. You can specify a revision set for
2777 log, see :hg:`help revsets` for more information.
2777 log, see :hg:`help revsets` for more information.
2778
2778
2779 See :hg:`help dates` for a list of formats valid for -d/--date.
2779 See :hg:`help dates` for a list of formats valid for -d/--date.
2780
2780
2781 By default this command prints revision number and changeset id,
2781 By default this command prints revision number and changeset id,
2782 tags, non-trivial parents, user, date and time, and a summary for
2782 tags, non-trivial parents, user, date and time, and a summary for
2783 each commit. When the -v/--verbose switch is used, the list of
2783 each commit. When the -v/--verbose switch is used, the list of
2784 changed files and full commit message are shown.
2784 changed files and full commit message are shown.
2785
2785
2786 .. note::
2786 .. note::
2787 log -p/--patch may generate unexpected diff output for merge
2787 log -p/--patch may generate unexpected diff output for merge
2788 changesets, as it will only compare the merge changeset against
2788 changesets, as it will only compare the merge changeset against
2789 its first parent. Also, only files different from BOTH parents
2789 its first parent. Also, only files different from BOTH parents
2790 will appear in files:.
2790 will appear in files:.
2791
2791
2792 Returns 0 on success.
2792 Returns 0 on success.
2793 """
2793 """
2794
2794
2795 matchfn = cmdutil.match(repo, pats, opts)
2795 matchfn = cmdutil.match(repo, pats, opts)
2796 limit = cmdutil.loglimit(opts)
2796 limit = cmdutil.loglimit(opts)
2797 count = 0
2797 count = 0
2798
2798
2799 endrev = None
2799 endrev = None
2800 if opts.get('copies') and opts.get('rev'):
2800 if opts.get('copies') and opts.get('rev'):
2801 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
2801 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
2802
2802
2803 df = False
2803 df = False
2804 if opts["date"]:
2804 if opts["date"]:
2805 df = util.matchdate(opts["date"])
2805 df = util.matchdate(opts["date"])
2806
2806
2807 branches = opts.get('branch', []) + opts.get('only_branch', [])
2807 branches = opts.get('branch', []) + opts.get('only_branch', [])
2808 opts['branch'] = [repo.lookupbranch(b) for b in branches]
2808 opts['branch'] = [repo.lookupbranch(b) for b in branches]
2809
2809
2810 displayer = cmdutil.show_changeset(ui, repo, opts, True)
2810 displayer = cmdutil.show_changeset(ui, repo, opts, True)
2811 def prep(ctx, fns):
2811 def prep(ctx, fns):
2812 rev = ctx.rev()
2812 rev = ctx.rev()
2813 parents = [p for p in repo.changelog.parentrevs(rev)
2813 parents = [p for p in repo.changelog.parentrevs(rev)
2814 if p != nullrev]
2814 if p != nullrev]
2815 if opts.get('no_merges') and len(parents) == 2:
2815 if opts.get('no_merges') and len(parents) == 2:
2816 return
2816 return
2817 if opts.get('only_merges') and len(parents) != 2:
2817 if opts.get('only_merges') and len(parents) != 2:
2818 return
2818 return
2819 if opts.get('branch') and ctx.branch() not in opts['branch']:
2819 if opts.get('branch') and ctx.branch() not in opts['branch']:
2820 return
2820 return
2821 if df and not df(ctx.date()[0]):
2821 if df and not df(ctx.date()[0]):
2822 return
2822 return
2823 if opts['user'] and not [k for k in opts['user']
2823 if opts['user'] and not [k for k in opts['user']
2824 if k.lower() in ctx.user().lower()]:
2824 if k.lower() in ctx.user().lower()]:
2825 return
2825 return
2826 if opts.get('keyword'):
2826 if opts.get('keyword'):
2827 for k in [kw.lower() for kw in opts['keyword']]:
2827 for k in [kw.lower() for kw in opts['keyword']]:
2828 if (k in ctx.user().lower() or
2828 if (k in ctx.user().lower() or
2829 k in ctx.description().lower() or
2829 k in ctx.description().lower() or
2830 k in " ".join(ctx.files()).lower()):
2830 k in " ".join(ctx.files()).lower()):
2831 break
2831 break
2832 else:
2832 else:
2833 return
2833 return
2834
2834
2835 copies = None
2835 copies = None
2836 if opts.get('copies') and rev:
2836 if opts.get('copies') and rev:
2837 copies = []
2837 copies = []
2838 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2838 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2839 for fn in ctx.files():
2839 for fn in ctx.files():
2840 rename = getrenamed(fn, rev)
2840 rename = getrenamed(fn, rev)
2841 if rename:
2841 if rename:
2842 copies.append((fn, rename[0]))
2842 copies.append((fn, rename[0]))
2843
2843
2844 revmatchfn = None
2844 revmatchfn = None
2845 if opts.get('patch') or opts.get('stat'):
2845 if opts.get('patch') or opts.get('stat'):
2846 if opts.get('follow') or opts.get('follow_first'):
2846 if opts.get('follow') or opts.get('follow_first'):
2847 # note: this might be wrong when following through merges
2847 # note: this might be wrong when following through merges
2848 revmatchfn = cmdutil.match(repo, fns, default='path')
2848 revmatchfn = cmdutil.match(repo, fns, default='path')
2849 else:
2849 else:
2850 revmatchfn = matchfn
2850 revmatchfn = matchfn
2851
2851
2852 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2852 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2853
2853
2854 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2854 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2855 if count == limit:
2855 if count == limit:
2856 break
2856 break
2857 if displayer.flush(ctx.rev()):
2857 if displayer.flush(ctx.rev()):
2858 count += 1
2858 count += 1
2859 displayer.close()
2859 displayer.close()
2860
2860
2861 def manifest(ui, repo, node=None, rev=None):
2861 def manifest(ui, repo, node=None, rev=None):
2862 """output the current or given revision of the project manifest
2862 """output the current or given revision of the project manifest
2863
2863
2864 Print a list of version controlled files for the given revision.
2864 Print a list of version controlled files for the given revision.
2865 If no revision is given, the first parent of the working directory
2865 If no revision is given, the first parent of the working directory
2866 is used, or the null revision if no revision is checked out.
2866 is used, or the null revision if no revision is checked out.
2867
2867
2868 With -v, print file permissions, symlink and executable bits.
2868 With -v, print file permissions, symlink and executable bits.
2869 With --debug, print file revision hashes.
2869 With --debug, print file revision hashes.
2870
2870
2871 Returns 0 on success.
2871 Returns 0 on success.
2872 """
2872 """
2873
2873
2874 if rev and node:
2874 if rev and node:
2875 raise util.Abort(_("please specify just one revision"))
2875 raise util.Abort(_("please specify just one revision"))
2876
2876
2877 if not node:
2877 if not node:
2878 node = rev
2878 node = rev
2879
2879
2880 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2880 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2881 ctx = cmdutil.revsingle(repo, node)
2881 ctx = cmdutil.revsingle(repo, node)
2882 for f in ctx:
2882 for f in ctx:
2883 if ui.debugflag:
2883 if ui.debugflag:
2884 ui.write("%40s " % hex(ctx.manifest()[f]))
2884 ui.write("%40s " % hex(ctx.manifest()[f]))
2885 if ui.verbose:
2885 if ui.verbose:
2886 ui.write(decor[ctx.flags(f)])
2886 ui.write(decor[ctx.flags(f)])
2887 ui.write("%s\n" % f)
2887 ui.write("%s\n" % f)
2888
2888
2889 def merge(ui, repo, node=None, **opts):
2889 def merge(ui, repo, node=None, **opts):
2890 """merge working directory with another revision
2890 """merge working directory with another revision
2891
2891
2892 The current working directory is updated with all changes made in
2892 The current working directory is updated with all changes made in
2893 the requested revision since the last common predecessor revision.
2893 the requested revision since the last common predecessor revision.
2894
2894
2895 Files that changed between either parent are marked as changed for
2895 Files that changed between either parent are marked as changed for
2896 the next commit and a commit must be performed before any further
2896 the next commit and a commit must be performed before any further
2897 updates to the repository are allowed. The next commit will have
2897 updates to the repository are allowed. The next commit will have
2898 two parents.
2898 two parents.
2899
2899
2900 ``--tool`` can be used to specify the merge tool used for file
2900 ``--tool`` can be used to specify the merge tool used for file
2901 merges. It overrides the HGMERGE environment variable and your
2901 merges. It overrides the HGMERGE environment variable and your
2902 configuration files. See :hg:`help merge-tools` for options.
2902 configuration files. See :hg:`help merge-tools` for options.
2903
2903
2904 If no revision is specified, the working directory's parent is a
2904 If no revision is specified, the working directory's parent is a
2905 head revision, and the current branch contains exactly one other
2905 head revision, and the current branch contains exactly one other
2906 head, the other head is merged with by default. Otherwise, an
2906 head, the other head is merged with by default. Otherwise, an
2907 explicit revision with which to merge with must be provided.
2907 explicit revision with which to merge with must be provided.
2908
2908
2909 :hg:`resolve` must be used to resolve unresolved files.
2909 :hg:`resolve` must be used to resolve unresolved files.
2910
2910
2911 To undo an uncommitted merge, use :hg:`update --clean .` which
2911 To undo an uncommitted merge, use :hg:`update --clean .` which
2912 will check out a clean copy of the original merge parent, losing
2912 will check out a clean copy of the original merge parent, losing
2913 all changes.
2913 all changes.
2914
2914
2915 Returns 0 on success, 1 if there are unresolved files.
2915 Returns 0 on success, 1 if there are unresolved files.
2916 """
2916 """
2917
2917
2918 if opts.get('rev') and node:
2918 if opts.get('rev') and node:
2919 raise util.Abort(_("please specify just one revision"))
2919 raise util.Abort(_("please specify just one revision"))
2920 if not node:
2920 if not node:
2921 node = opts.get('rev')
2921 node = opts.get('rev')
2922
2922
2923 if not node:
2923 if not node:
2924 branch = repo[None].branch()
2924 branch = repo[None].branch()
2925 bheads = repo.branchheads(branch)
2925 bheads = repo.branchheads(branch)
2926 if len(bheads) > 2:
2926 if len(bheads) > 2:
2927 raise util.Abort(_("branch '%s' has %d heads - "
2927 raise util.Abort(_("branch '%s' has %d heads - "
2928 "please merge with an explicit rev")
2928 "please merge with an explicit rev")
2929 % (branch, len(bheads)),
2929 % (branch, len(bheads)),
2930 hint=_("run 'hg heads .' to see heads"))
2930 hint=_("run 'hg heads .' to see heads"))
2931
2931
2932 parent = repo.dirstate.p1()
2932 parent = repo.dirstate.p1()
2933 if len(bheads) == 1:
2933 if len(bheads) == 1:
2934 if len(repo.heads()) > 1:
2934 if len(repo.heads()) > 1:
2935 raise util.Abort(_("branch '%s' has one head - "
2935 raise util.Abort(_("branch '%s' has one head - "
2936 "please merge with an explicit rev")
2936 "please merge with an explicit rev")
2937 % branch,
2937 % branch,
2938 hint=_("run 'hg heads' to see all heads"))
2938 hint=_("run 'hg heads' to see all heads"))
2939 msg = _('there is nothing to merge')
2939 msg = _('there is nothing to merge')
2940 if parent != repo.lookup(repo[None].branch()):
2940 if parent != repo.lookup(repo[None].branch()):
2941 msg = _('%s - use "hg update" instead') % msg
2941 msg = _('%s - use "hg update" instead') % msg
2942 raise util.Abort(msg)
2942 raise util.Abort(msg)
2943
2943
2944 if parent not in bheads:
2944 if parent not in bheads:
2945 raise util.Abort(_('working directory not at a head revision'),
2945 raise util.Abort(_('working directory not at a head revision'),
2946 hint=_("use 'hg update' or merge with an "
2946 hint=_("use 'hg update' or merge with an "
2947 "explicit revision"))
2947 "explicit revision"))
2948 node = parent == bheads[0] and bheads[-1] or bheads[0]
2948 node = parent == bheads[0] and bheads[-1] or bheads[0]
2949 else:
2949 else:
2950 node = cmdutil.revsingle(repo, node).node()
2950 node = cmdutil.revsingle(repo, node).node()
2951
2951
2952 if opts.get('preview'):
2952 if opts.get('preview'):
2953 # find nodes that are ancestors of p2 but not of p1
2953 # find nodes that are ancestors of p2 but not of p1
2954 p1 = repo.lookup('.')
2954 p1 = repo.lookup('.')
2955 p2 = repo.lookup(node)
2955 p2 = repo.lookup(node)
2956 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
2956 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
2957
2957
2958 displayer = cmdutil.show_changeset(ui, repo, opts)
2958 displayer = cmdutil.show_changeset(ui, repo, opts)
2959 for node in nodes:
2959 for node in nodes:
2960 displayer.show(repo[node])
2960 displayer.show(repo[node])
2961 displayer.close()
2961 displayer.close()
2962 return 0
2962 return 0
2963
2963
2964 try:
2964 try:
2965 # ui.forcemerge is an internal variable, do not document
2965 # ui.forcemerge is an internal variable, do not document
2966 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2966 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2967 return hg.merge(repo, node, force=opts.get('force'))
2967 return hg.merge(repo, node, force=opts.get('force'))
2968 finally:
2968 finally:
2969 ui.setconfig('ui', 'forcemerge', '')
2969 ui.setconfig('ui', 'forcemerge', '')
2970
2970
2971 def outgoing(ui, repo, dest=None, **opts):
2971 def outgoing(ui, repo, dest=None, **opts):
2972 """show changesets not found in the destination
2972 """show changesets not found in the destination
2973
2973
2974 Show changesets not found in the specified destination repository
2974 Show changesets not found in the specified destination repository
2975 or the default push location. These are the changesets that would
2975 or the default push location. These are the changesets that would
2976 be pushed if a push was requested.
2976 be pushed if a push was requested.
2977
2977
2978 See pull for details of valid destination formats.
2978 See pull for details of valid destination formats.
2979
2979
2980 Returns 0 if there are outgoing changes, 1 otherwise.
2980 Returns 0 if there are outgoing changes, 1 otherwise.
2981 """
2981 """
2982
2982
2983 if opts.get('bookmarks'):
2983 if opts.get('bookmarks'):
2984 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2984 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2985 dest, branches = hg.parseurl(dest, opts.get('branch'))
2985 dest, branches = hg.parseurl(dest, opts.get('branch'))
2986 other = hg.repository(hg.remoteui(repo, opts), dest)
2986 other = hg.repository(hg.remoteui(repo, opts), dest)
2987 if 'bookmarks' not in other.listkeys('namespaces'):
2987 if 'bookmarks' not in other.listkeys('namespaces'):
2988 ui.warn(_("remote doesn't support bookmarks\n"))
2988 ui.warn(_("remote doesn't support bookmarks\n"))
2989 return 0
2989 return 0
2990 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
2990 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
2991 return bookmarks.diff(ui, other, repo)
2991 return bookmarks.diff(ui, other, repo)
2992
2992
2993 ret = hg.outgoing(ui, repo, dest, opts)
2993 ret = hg.outgoing(ui, repo, dest, opts)
2994 return ret
2994 return ret
2995
2995
2996 def parents(ui, repo, file_=None, **opts):
2996 def parents(ui, repo, file_=None, **opts):
2997 """show the parents of the working directory or revision
2997 """show the parents of the working directory or revision
2998
2998
2999 Print the working directory's parent revisions. If a revision is
2999 Print the working directory's parent revisions. If a revision is
3000 given via -r/--rev, the parent of that revision will be printed.
3000 given via -r/--rev, the parent of that revision will be printed.
3001 If a file argument is given, the revision in which the file was
3001 If a file argument is given, the revision in which the file was
3002 last changed (before the working directory revision or the
3002 last changed (before the working directory revision or the
3003 argument to --rev if given) is printed.
3003 argument to --rev if given) is printed.
3004
3004
3005 Returns 0 on success.
3005 Returns 0 on success.
3006 """
3006 """
3007
3007
3008 ctx = cmdutil.revsingle(repo, opts.get('rev'), None)
3008 ctx = cmdutil.revsingle(repo, opts.get('rev'), None)
3009
3009
3010 if file_:
3010 if file_:
3011 m = cmdutil.match(repo, (file_,), opts)
3011 m = cmdutil.match(repo, (file_,), opts)
3012 if m.anypats() or len(m.files()) != 1:
3012 if m.anypats() or len(m.files()) != 1:
3013 raise util.Abort(_('can only specify an explicit filename'))
3013 raise util.Abort(_('can only specify an explicit filename'))
3014 file_ = m.files()[0]
3014 file_ = m.files()[0]
3015 filenodes = []
3015 filenodes = []
3016 for cp in ctx.parents():
3016 for cp in ctx.parents():
3017 if not cp:
3017 if not cp:
3018 continue
3018 continue
3019 try:
3019 try:
3020 filenodes.append(cp.filenode(file_))
3020 filenodes.append(cp.filenode(file_))
3021 except error.LookupError:
3021 except error.LookupError:
3022 pass
3022 pass
3023 if not filenodes:
3023 if not filenodes:
3024 raise util.Abort(_("'%s' not found in manifest!") % file_)
3024 raise util.Abort(_("'%s' not found in manifest!") % file_)
3025 fl = repo.file(file_)
3025 fl = repo.file(file_)
3026 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
3026 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
3027 else:
3027 else:
3028 p = [cp.node() for cp in ctx.parents()]
3028 p = [cp.node() for cp in ctx.parents()]
3029
3029
3030 displayer = cmdutil.show_changeset(ui, repo, opts)
3030 displayer = cmdutil.show_changeset(ui, repo, opts)
3031 for n in p:
3031 for n in p:
3032 if n != nullid:
3032 if n != nullid:
3033 displayer.show(repo[n])
3033 displayer.show(repo[n])
3034 displayer.close()
3034 displayer.close()
3035
3035
3036 def paths(ui, repo, search=None):
3036 def paths(ui, repo, search=None):
3037 """show aliases for remote repositories
3037 """show aliases for remote repositories
3038
3038
3039 Show definition of symbolic path name NAME. If no name is given,
3039 Show definition of symbolic path name NAME. If no name is given,
3040 show definition of all available names.
3040 show definition of all available names.
3041
3041
3042 Path names are defined in the [paths] section of your
3042 Path names are defined in the [paths] section of your
3043 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3043 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3044 repository, ``.hg/hgrc`` is used, too.
3044 repository, ``.hg/hgrc`` is used, too.
3045
3045
3046 The path names ``default`` and ``default-push`` have a special
3046 The path names ``default`` and ``default-push`` have a special
3047 meaning. When performing a push or pull operation, they are used
3047 meaning. When performing a push or pull operation, they are used
3048 as fallbacks if no location is specified on the command-line.
3048 as fallbacks if no location is specified on the command-line.
3049 When ``default-push`` is set, it will be used for push and
3049 When ``default-push`` is set, it will be used for push and
3050 ``default`` will be used for pull; otherwise ``default`` is used
3050 ``default`` will be used for pull; otherwise ``default`` is used
3051 as the fallback for both. When cloning a repository, the clone
3051 as the fallback for both. When cloning a repository, the clone
3052 source is written as ``default`` in ``.hg/hgrc``. Note that
3052 source is written as ``default`` in ``.hg/hgrc``. Note that
3053 ``default`` and ``default-push`` apply to all inbound (e.g.
3053 ``default`` and ``default-push`` apply to all inbound (e.g.
3054 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
3054 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
3055 :hg:`bundle`) operations.
3055 :hg:`bundle`) operations.
3056
3056
3057 See :hg:`help urls` for more information.
3057 See :hg:`help urls` for more information.
3058
3058
3059 Returns 0 on success.
3059 Returns 0 on success.
3060 """
3060 """
3061 if search:
3061 if search:
3062 for name, path in ui.configitems("paths"):
3062 for name, path in ui.configitems("paths"):
3063 if name == search:
3063 if name == search:
3064 ui.write("%s\n" % util.hidepassword(path))
3064 ui.write("%s\n" % util.hidepassword(path))
3065 return
3065 return
3066 ui.warn(_("not found!\n"))
3066 ui.warn(_("not found!\n"))
3067 return 1
3067 return 1
3068 else:
3068 else:
3069 for name, path in ui.configitems("paths"):
3069 for name, path in ui.configitems("paths"):
3070 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
3070 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
3071
3071
3072 def postincoming(ui, repo, modheads, optupdate, checkout):
3072 def postincoming(ui, repo, modheads, optupdate, checkout):
3073 if modheads == 0:
3073 if modheads == 0:
3074 return
3074 return
3075 if optupdate:
3075 if optupdate:
3076 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
3076 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
3077 return hg.update(repo, checkout)
3077 return hg.update(repo, checkout)
3078 else:
3078 else:
3079 ui.status(_("not updating, since new heads added\n"))
3079 ui.status(_("not updating, since new heads added\n"))
3080 if modheads > 1:
3080 if modheads > 1:
3081 currentbranchheads = len(repo.branchheads())
3081 currentbranchheads = len(repo.branchheads())
3082 if currentbranchheads == modheads:
3082 if currentbranchheads == modheads:
3083 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3083 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3084 elif currentbranchheads > 1:
3084 elif currentbranchheads > 1:
3085 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to merge)\n"))
3085 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to merge)\n"))
3086 else:
3086 else:
3087 ui.status(_("(run 'hg heads' to see heads)\n"))
3087 ui.status(_("(run 'hg heads' to see heads)\n"))
3088 else:
3088 else:
3089 ui.status(_("(run 'hg update' to get a working copy)\n"))
3089 ui.status(_("(run 'hg update' to get a working copy)\n"))
3090
3090
3091 def pull(ui, repo, source="default", **opts):
3091 def pull(ui, repo, source="default", **opts):
3092 """pull changes from the specified source
3092 """pull changes from the specified source
3093
3093
3094 Pull changes from a remote repository to a local one.
3094 Pull changes from a remote repository to a local one.
3095
3095
3096 This finds all changes from the repository at the specified path
3096 This finds all changes from the repository at the specified path
3097 or URL and adds them to a local repository (the current one unless
3097 or URL and adds them to a local repository (the current one unless
3098 -R is specified). By default, this does not update the copy of the
3098 -R is specified). By default, this does not update the copy of the
3099 project in the working directory.
3099 project in the working directory.
3100
3100
3101 Use :hg:`incoming` if you want to see what would have been added
3101 Use :hg:`incoming` if you want to see what would have been added
3102 by a pull at the time you issued this command. If you then decide
3102 by a pull at the time you issued this command. If you then decide
3103 to add those changes to the repository, you should use :hg:`pull
3103 to add those changes to the repository, you should use :hg:`pull
3104 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3104 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3105
3105
3106 If SOURCE is omitted, the 'default' path will be used.
3106 If SOURCE is omitted, the 'default' path will be used.
3107 See :hg:`help urls` for more information.
3107 See :hg:`help urls` for more information.
3108
3108
3109 Returns 0 on success, 1 if an update had unresolved files.
3109 Returns 0 on success, 1 if an update had unresolved files.
3110 """
3110 """
3111 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3111 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3112 other = hg.repository(hg.remoteui(repo, opts), source)
3112 other = hg.repository(hg.remoteui(repo, opts), source)
3113 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3113 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3114 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3114 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3115
3115
3116 if opts.get('bookmark'):
3116 if opts.get('bookmark'):
3117 if not revs:
3117 if not revs:
3118 revs = []
3118 revs = []
3119 rb = other.listkeys('bookmarks')
3119 rb = other.listkeys('bookmarks')
3120 for b in opts['bookmark']:
3120 for b in opts['bookmark']:
3121 if b not in rb:
3121 if b not in rb:
3122 raise util.Abort(_('remote bookmark %s not found!') % b)
3122 raise util.Abort(_('remote bookmark %s not found!') % b)
3123 revs.append(rb[b])
3123 revs.append(rb[b])
3124
3124
3125 if revs:
3125 if revs:
3126 try:
3126 try:
3127 revs = [other.lookup(rev) for rev in revs]
3127 revs = [other.lookup(rev) for rev in revs]
3128 except error.CapabilityError:
3128 except error.CapabilityError:
3129 err = _("other repository doesn't support revision lookup, "
3129 err = _("other repository doesn't support revision lookup, "
3130 "so a rev cannot be specified.")
3130 "so a rev cannot be specified.")
3131 raise util.Abort(err)
3131 raise util.Abort(err)
3132
3132
3133 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
3133 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
3134 bookmarks.updatefromremote(ui, repo, other)
3134 bookmarks.updatefromremote(ui, repo, other)
3135 if checkout:
3135 if checkout:
3136 checkout = str(repo.changelog.rev(other.lookup(checkout)))
3136 checkout = str(repo.changelog.rev(other.lookup(checkout)))
3137 repo._subtoppath = source
3137 repo._subtoppath = source
3138 try:
3138 try:
3139 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
3139 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
3140
3140
3141 finally:
3141 finally:
3142 del repo._subtoppath
3142 del repo._subtoppath
3143
3143
3144 # update specified bookmarks
3144 # update specified bookmarks
3145 if opts.get('bookmark'):
3145 if opts.get('bookmark'):
3146 for b in opts['bookmark']:
3146 for b in opts['bookmark']:
3147 # explicit pull overrides local bookmark if any
3147 # explicit pull overrides local bookmark if any
3148 ui.status(_("importing bookmark %s\n") % b)
3148 ui.status(_("importing bookmark %s\n") % b)
3149 repo._bookmarks[b] = repo[rb[b]].node()
3149 repo._bookmarks[b] = repo[rb[b]].node()
3150 bookmarks.write(repo)
3150 bookmarks.write(repo)
3151
3151
3152 return ret
3152 return ret
3153
3153
3154 def push(ui, repo, dest=None, **opts):
3154 def push(ui, repo, dest=None, **opts):
3155 """push changes to the specified destination
3155 """push changes to the specified destination
3156
3156
3157 Push changesets from the local repository to the specified
3157 Push changesets from the local repository to the specified
3158 destination.
3158 destination.
3159
3159
3160 This operation is symmetrical to pull: it is identical to a pull
3160 This operation is symmetrical to pull: it is identical to a pull
3161 in the destination repository from the current one.
3161 in the destination repository from the current one.
3162
3162
3163 By default, push will not allow creation of new heads at the
3163 By default, push will not allow creation of new heads at the
3164 destination, since multiple heads would make it unclear which head
3164 destination, since multiple heads would make it unclear which head
3165 to use. In this situation, it is recommended to pull and merge
3165 to use. In this situation, it is recommended to pull and merge
3166 before pushing.
3166 before pushing.
3167
3167
3168 Use --new-branch if you want to allow push to create a new named
3168 Use --new-branch if you want to allow push to create a new named
3169 branch that is not present at the destination. This allows you to
3169 branch that is not present at the destination. This allows you to
3170 only create a new branch without forcing other changes.
3170 only create a new branch without forcing other changes.
3171
3171
3172 Use -f/--force to override the default behavior and push all
3172 Use -f/--force to override the default behavior and push all
3173 changesets on all branches.
3173 changesets on all branches.
3174
3174
3175 If -r/--rev is used, the specified revision and all its ancestors
3175 If -r/--rev is used, the specified revision and all its ancestors
3176 will be pushed to the remote repository.
3176 will be pushed to the remote repository.
3177
3177
3178 Please see :hg:`help urls` for important details about ``ssh://``
3178 Please see :hg:`help urls` for important details about ``ssh://``
3179 URLs. If DESTINATION is omitted, a default path will be used.
3179 URLs. If DESTINATION is omitted, a default path will be used.
3180
3180
3181 Returns 0 if push was successful, 1 if nothing to push.
3181 Returns 0 if push was successful, 1 if nothing to push.
3182 """
3182 """
3183
3183
3184 if opts.get('bookmark'):
3184 if opts.get('bookmark'):
3185 for b in opts['bookmark']:
3185 for b in opts['bookmark']:
3186 # translate -B options to -r so changesets get pushed
3186 # translate -B options to -r so changesets get pushed
3187 if b in repo._bookmarks:
3187 if b in repo._bookmarks:
3188 opts.setdefault('rev', []).append(b)
3188 opts.setdefault('rev', []).append(b)
3189 else:
3189 else:
3190 # if we try to push a deleted bookmark, translate it to null
3190 # if we try to push a deleted bookmark, translate it to null
3191 # this lets simultaneous -r, -b options continue working
3191 # this lets simultaneous -r, -b options continue working
3192 opts.setdefault('rev', []).append("null")
3192 opts.setdefault('rev', []).append("null")
3193
3193
3194 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3194 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3195 dest, branches = hg.parseurl(dest, opts.get('branch'))
3195 dest, branches = hg.parseurl(dest, opts.get('branch'))
3196 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
3196 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
3197 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
3197 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
3198 other = hg.repository(hg.remoteui(repo, opts), dest)
3198 other = hg.repository(hg.remoteui(repo, opts), dest)
3199 if revs:
3199 if revs:
3200 revs = [repo.lookup(rev) for rev in revs]
3200 revs = [repo.lookup(rev) for rev in revs]
3201
3201
3202 repo._subtoppath = dest
3202 repo._subtoppath = dest
3203 try:
3203 try:
3204 # push subrepos depth-first for coherent ordering
3204 # push subrepos depth-first for coherent ordering
3205 c = repo['']
3205 c = repo['']
3206 subs = c.substate # only repos that are committed
3206 subs = c.substate # only repos that are committed
3207 for s in sorted(subs):
3207 for s in sorted(subs):
3208 if not c.sub(s).push(opts.get('force')):
3208 if not c.sub(s).push(opts.get('force')):
3209 return False
3209 return False
3210 finally:
3210 finally:
3211 del repo._subtoppath
3211 del repo._subtoppath
3212 result = repo.push(other, opts.get('force'), revs=revs,
3212 result = repo.push(other, opts.get('force'), revs=revs,
3213 newbranch=opts.get('new_branch'))
3213 newbranch=opts.get('new_branch'))
3214
3214
3215 result = (result == 0)
3215 result = (result == 0)
3216
3216
3217 if opts.get('bookmark'):
3217 if opts.get('bookmark'):
3218 rb = other.listkeys('bookmarks')
3218 rb = other.listkeys('bookmarks')
3219 for b in opts['bookmark']:
3219 for b in opts['bookmark']:
3220 # explicit push overrides remote bookmark if any
3220 # explicit push overrides remote bookmark if any
3221 if b in repo._bookmarks:
3221 if b in repo._bookmarks:
3222 ui.status(_("exporting bookmark %s\n") % b)
3222 ui.status(_("exporting bookmark %s\n") % b)
3223 new = repo[b].hex()
3223 new = repo[b].hex()
3224 elif b in rb:
3224 elif b in rb:
3225 ui.status(_("deleting remote bookmark %s\n") % b)
3225 ui.status(_("deleting remote bookmark %s\n") % b)
3226 new = '' # delete
3226 new = '' # delete
3227 else:
3227 else:
3228 ui.warn(_('bookmark %s does not exist on the local '
3228 ui.warn(_('bookmark %s does not exist on the local '
3229 'or remote repository!\n') % b)
3229 'or remote repository!\n') % b)
3230 return 2
3230 return 2
3231 old = rb.get(b, '')
3231 old = rb.get(b, '')
3232 r = other.pushkey('bookmarks', b, old, new)
3232 r = other.pushkey('bookmarks', b, old, new)
3233 if not r:
3233 if not r:
3234 ui.warn(_('updating bookmark %s failed!\n') % b)
3234 ui.warn(_('updating bookmark %s failed!\n') % b)
3235 if not result:
3235 if not result:
3236 result = 2
3236 result = 2
3237
3237
3238 return result
3238 return result
3239
3239
3240 def recover(ui, repo):
3240 def recover(ui, repo):
3241 """roll back an interrupted transaction
3241 """roll back an interrupted transaction
3242
3242
3243 Recover from an interrupted commit or pull.
3243 Recover from an interrupted commit or pull.
3244
3244
3245 This command tries to fix the repository status after an
3245 This command tries to fix the repository status after an
3246 interrupted operation. It should only be necessary when Mercurial
3246 interrupted operation. It should only be necessary when Mercurial
3247 suggests it.
3247 suggests it.
3248
3248
3249 Returns 0 if successful, 1 if nothing to recover or verify fails.
3249 Returns 0 if successful, 1 if nothing to recover or verify fails.
3250 """
3250 """
3251 if repo.recover():
3251 if repo.recover():
3252 return hg.verify(repo)
3252 return hg.verify(repo)
3253 return 1
3253 return 1
3254
3254
3255 def remove(ui, repo, *pats, **opts):
3255 def remove(ui, repo, *pats, **opts):
3256 """remove the specified files on the next commit
3256 """remove the specified files on the next commit
3257
3257
3258 Schedule the indicated files for removal from the repository.
3258 Schedule the indicated files for removal from the repository.
3259
3259
3260 This only removes files from the current branch, not from the
3260 This only removes files from the current branch, not from the
3261 entire project history. -A/--after can be used to remove only
3261 entire project history. -A/--after can be used to remove only
3262 files that have already been deleted, -f/--force can be used to
3262 files that have already been deleted, -f/--force can be used to
3263 force deletion, and -Af can be used to remove files from the next
3263 force deletion, and -Af can be used to remove files from the next
3264 revision without deleting them from the working directory.
3264 revision without deleting them from the working directory.
3265
3265
3266 The following table details the behavior of remove for different
3266 The following table details the behavior of remove for different
3267 file states (columns) and option combinations (rows). The file
3267 file states (columns) and option combinations (rows). The file
3268 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
3268 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
3269 reported by :hg:`status`). The actions are Warn, Remove (from
3269 reported by :hg:`status`). The actions are Warn, Remove (from
3270 branch) and Delete (from disk)::
3270 branch) and Delete (from disk)::
3271
3271
3272 A C M !
3272 A C M !
3273 none W RD W R
3273 none W RD W R
3274 -f R RD RD R
3274 -f R RD RD R
3275 -A W W W R
3275 -A W W W R
3276 -Af R R R R
3276 -Af R R R R
3277
3277
3278 This command schedules the files to be removed at the next commit.
3278 This command schedules the files to be removed at the next commit.
3279 To undo a remove before that, see :hg:`revert`.
3279 To undo a remove before that, see :hg:`revert`.
3280
3280
3281 Returns 0 on success, 1 if any warnings encountered.
3281 Returns 0 on success, 1 if any warnings encountered.
3282 """
3282 """
3283
3283
3284 ret = 0
3284 ret = 0
3285 after, force = opts.get('after'), opts.get('force')
3285 after, force = opts.get('after'), opts.get('force')
3286 if not pats and not after:
3286 if not pats and not after:
3287 raise util.Abort(_('no files specified'))
3287 raise util.Abort(_('no files specified'))
3288
3288
3289 m = cmdutil.match(repo, pats, opts)
3289 m = cmdutil.match(repo, pats, opts)
3290 s = repo.status(match=m, clean=True)
3290 s = repo.status(match=m, clean=True)
3291 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
3291 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
3292
3292
3293 for f in m.files():
3293 for f in m.files():
3294 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
3294 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
3295 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
3295 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
3296 ret = 1
3296 ret = 1
3297
3297
3298 if force:
3298 if force:
3299 remove, forget = modified + deleted + clean, added
3299 remove, forget = modified + deleted + clean, added
3300 elif after:
3300 elif after:
3301 remove, forget = deleted, []
3301 remove, forget = deleted, []
3302 for f in modified + added + clean:
3302 for f in modified + added + clean:
3303 ui.warn(_('not removing %s: file still exists (use -f'
3303 ui.warn(_('not removing %s: file still exists (use -f'
3304 ' to force removal)\n') % m.rel(f))
3304 ' to force removal)\n') % m.rel(f))
3305 ret = 1
3305 ret = 1
3306 else:
3306 else:
3307 remove, forget = deleted + clean, []
3307 remove, forget = deleted + clean, []
3308 for f in modified:
3308 for f in modified:
3309 ui.warn(_('not removing %s: file is modified (use -f'
3309 ui.warn(_('not removing %s: file is modified (use -f'
3310 ' to force removal)\n') % m.rel(f))
3310 ' to force removal)\n') % m.rel(f))
3311 ret = 1
3311 ret = 1
3312 for f in added:
3312 for f in added:
3313 ui.warn(_('not removing %s: file has been marked for add (use -f'
3313 ui.warn(_('not removing %s: file has been marked for add (use -f'
3314 ' to force removal)\n') % m.rel(f))
3314 ' to force removal)\n') % m.rel(f))
3315 ret = 1
3315 ret = 1
3316
3316
3317 for f in sorted(remove + forget):
3317 for f in sorted(remove + forget):
3318 if ui.verbose or not m.exact(f):
3318 if ui.verbose or not m.exact(f):
3319 ui.status(_('removing %s\n') % m.rel(f))
3319 ui.status(_('removing %s\n') % m.rel(f))
3320
3320
3321 repo[None].forget(forget)
3321 repo[None].forget(forget)
3322 repo[None].remove(remove, unlink=not after)
3322 repo[None].remove(remove, unlink=not after)
3323 return ret
3323 return ret
3324
3324
3325 def rename(ui, repo, *pats, **opts):
3325 def rename(ui, repo, *pats, **opts):
3326 """rename files; equivalent of copy + remove
3326 """rename files; equivalent of copy + remove
3327
3327
3328 Mark dest as copies of sources; mark sources for deletion. If dest
3328 Mark dest as copies of sources; mark sources for deletion. If dest
3329 is a directory, copies are put in that directory. If dest is a
3329 is a directory, copies are put in that directory. If dest is a
3330 file, there can only be one source.
3330 file, there can only be one source.
3331
3331
3332 By default, this command copies the contents of files as they
3332 By default, this command copies the contents of files as they
3333 exist in the working directory. If invoked with -A/--after, the
3333 exist in the working directory. If invoked with -A/--after, the
3334 operation is recorded, but no copying is performed.
3334 operation is recorded, but no copying is performed.
3335
3335
3336 This command takes effect at the next commit. To undo a rename
3336 This command takes effect at the next commit. To undo a rename
3337 before that, see :hg:`revert`.
3337 before that, see :hg:`revert`.
3338
3338
3339 Returns 0 on success, 1 if errors are encountered.
3339 Returns 0 on success, 1 if errors are encountered.
3340 """
3340 """
3341 wlock = repo.wlock(False)
3341 wlock = repo.wlock(False)
3342 try:
3342 try:
3343 return cmdutil.copy(ui, repo, pats, opts, rename=True)
3343 return cmdutil.copy(ui, repo, pats, opts, rename=True)
3344 finally:
3344 finally:
3345 wlock.release()
3345 wlock.release()
3346
3346
3347 def resolve(ui, repo, *pats, **opts):
3347 def resolve(ui, repo, *pats, **opts):
3348 """redo merges or set/view the merge status of files
3348 """redo merges or set/view the merge status of files
3349
3349
3350 Merges with unresolved conflicts are often the result of
3350 Merges with unresolved conflicts are often the result of
3351 non-interactive merging using the ``internal:merge`` configuration
3351 non-interactive merging using the ``internal:merge`` configuration
3352 setting, or a command-line merge tool like ``diff3``. The resolve
3352 setting, or a command-line merge tool like ``diff3``. The resolve
3353 command is used to manage the files involved in a merge, after
3353 command is used to manage the files involved in a merge, after
3354 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
3354 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
3355 working directory must have two parents).
3355 working directory must have two parents).
3356
3356
3357 The resolve command can be used in the following ways:
3357 The resolve command can be used in the following ways:
3358
3358
3359 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
3359 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
3360 files, discarding any previous merge attempts. Re-merging is not
3360 files, discarding any previous merge attempts. Re-merging is not
3361 performed for files already marked as resolved. Use ``--all/-a``
3361 performed for files already marked as resolved. Use ``--all/-a``
3362 to selects all unresolved files. ``--tool`` can be used to specify
3362 to selects all unresolved files. ``--tool`` can be used to specify
3363 the merge tool used for the given files. It overrides the HGMERGE
3363 the merge tool used for the given files. It overrides the HGMERGE
3364 environment variable and your configuration files.
3364 environment variable and your configuration files.
3365
3365
3366 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
3366 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
3367 (e.g. after having manually fixed-up the files). The default is
3367 (e.g. after having manually fixed-up the files). The default is
3368 to mark all unresolved files.
3368 to mark all unresolved files.
3369
3369
3370 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
3370 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
3371 default is to mark all resolved files.
3371 default is to mark all resolved files.
3372
3372
3373 - :hg:`resolve -l`: list files which had or still have conflicts.
3373 - :hg:`resolve -l`: list files which had or still have conflicts.
3374 In the printed list, ``U`` = unresolved and ``R`` = resolved.
3374 In the printed list, ``U`` = unresolved and ``R`` = resolved.
3375
3375
3376 Note that Mercurial will not let you commit files with unresolved
3376 Note that Mercurial will not let you commit files with unresolved
3377 merge conflicts. You must use :hg:`resolve -m ...` before you can
3377 merge conflicts. You must use :hg:`resolve -m ...` before you can
3378 commit after a conflicting merge.
3378 commit after a conflicting merge.
3379
3379
3380 Returns 0 on success, 1 if any files fail a resolve attempt.
3380 Returns 0 on success, 1 if any files fail a resolve attempt.
3381 """
3381 """
3382
3382
3383 all, mark, unmark, show, nostatus = \
3383 all, mark, unmark, show, nostatus = \
3384 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
3384 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
3385
3385
3386 if (show and (mark or unmark)) or (mark and unmark):
3386 if (show and (mark or unmark)) or (mark and unmark):
3387 raise util.Abort(_("too many options specified"))
3387 raise util.Abort(_("too many options specified"))
3388 if pats and all:
3388 if pats and all:
3389 raise util.Abort(_("can't specify --all and patterns"))
3389 raise util.Abort(_("can't specify --all and patterns"))
3390 if not (all or pats or show or mark or unmark):
3390 if not (all or pats or show or mark or unmark):
3391 raise util.Abort(_('no files or directories specified; '
3391 raise util.Abort(_('no files or directories specified; '
3392 'use --all to remerge all files'))
3392 'use --all to remerge all files'))
3393
3393
3394 ms = mergemod.mergestate(repo)
3394 ms = mergemod.mergestate(repo)
3395 m = cmdutil.match(repo, pats, opts)
3395 m = cmdutil.match(repo, pats, opts)
3396 ret = 0
3396 ret = 0
3397
3397
3398 for f in ms:
3398 for f in ms:
3399 if m(f):
3399 if m(f):
3400 if show:
3400 if show:
3401 if nostatus:
3401 if nostatus:
3402 ui.write("%s\n" % f)
3402 ui.write("%s\n" % f)
3403 else:
3403 else:
3404 ui.write("%s %s\n" % (ms[f].upper(), f),
3404 ui.write("%s %s\n" % (ms[f].upper(), f),
3405 label='resolve.' +
3405 label='resolve.' +
3406 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
3406 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
3407 elif mark:
3407 elif mark:
3408 ms.mark(f, "r")
3408 ms.mark(f, "r")
3409 elif unmark:
3409 elif unmark:
3410 ms.mark(f, "u")
3410 ms.mark(f, "u")
3411 else:
3411 else:
3412 wctx = repo[None]
3412 wctx = repo[None]
3413 mctx = wctx.parents()[-1]
3413 mctx = wctx.parents()[-1]
3414
3414
3415 # backup pre-resolve (merge uses .orig for its own purposes)
3415 # backup pre-resolve (merge uses .orig for its own purposes)
3416 a = repo.wjoin(f)
3416 a = repo.wjoin(f)
3417 util.copyfile(a, a + ".resolve")
3417 util.copyfile(a, a + ".resolve")
3418
3418
3419 try:
3419 try:
3420 # resolve file
3420 # resolve file
3421 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
3421 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
3422 if ms.resolve(f, wctx, mctx):
3422 if ms.resolve(f, wctx, mctx):
3423 ret = 1
3423 ret = 1
3424 finally:
3424 finally:
3425 ui.setconfig('ui', 'forcemerge', '')
3425 ui.setconfig('ui', 'forcemerge', '')
3426
3426
3427 # replace filemerge's .orig file with our resolve file
3427 # replace filemerge's .orig file with our resolve file
3428 util.rename(a + ".resolve", a + ".orig")
3428 util.rename(a + ".resolve", a + ".orig")
3429
3429
3430 ms.commit()
3430 ms.commit()
3431 return ret
3431 return ret
3432
3432
3433 def revert(ui, repo, *pats, **opts):
3433 def revert(ui, repo, *pats, **opts):
3434 """restore individual files or directories to an earlier state
3434 """restore individual files or directories to an earlier state
3435
3435
3436 .. note::
3436 .. note::
3437 This command is most likely not what you are looking for.
3437 This command is most likely not what you are looking for.
3438 Revert will partially overwrite content in the working
3438 Revert will partially overwrite content in the working
3439 directory without changing the working directory parents. Use
3439 directory without changing the working directory parents. Use
3440 :hg:`update -r rev` to check out earlier revisions, or
3440 :hg:`update -r rev` to check out earlier revisions, or
3441 :hg:`update --clean .` to undo a merge which has added another
3441 :hg:`update --clean .` to undo a merge which has added another
3442 parent.
3442 parent.
3443
3443
3444 With no revision specified, revert the named files or directories
3444 With no revision specified, revert the named files or directories
3445 to the contents they had in the parent of the working directory.
3445 to the contents they had in the parent of the working directory.
3446 This restores the contents of the affected files to an unmodified
3446 This restores the contents of the affected files to an unmodified
3447 state and unschedules adds, removes, copies, and renames. If the
3447 state and unschedules adds, removes, copies, and renames. If the
3448 working directory has two parents, you must explicitly specify a
3448 working directory has two parents, you must explicitly specify a
3449 revision.
3449 revision.
3450
3450
3451 Using the -r/--rev option, revert the given files or directories
3451 Using the -r/--rev option, revert the given files or directories
3452 to their contents as of a specific revision. This can be helpful
3452 to their contents as of a specific revision. This can be helpful
3453 to "roll back" some or all of an earlier change. See :hg:`help
3453 to "roll back" some or all of an earlier change. See :hg:`help
3454 dates` for a list of formats valid for -d/--date.
3454 dates` for a list of formats valid for -d/--date.
3455
3455
3456 Revert modifies the working directory. It does not commit any
3456 Revert modifies the working directory. It does not commit any
3457 changes, or change the parent of the working directory. If you
3457 changes, or change the parent of the working directory. If you
3458 revert to a revision other than the parent of the working
3458 revert to a revision other than the parent of the working
3459 directory, the reverted files will thus appear modified
3459 directory, the reverted files will thus appear modified
3460 afterwards.
3460 afterwards.
3461
3461
3462 If a file has been deleted, it is restored. Files scheduled for
3462 If a file has been deleted, it is restored. Files scheduled for
3463 addition are just unscheduled and left as they are. If the
3463 addition are just unscheduled and left as they are. If the
3464 executable mode of a file was changed, it is reset.
3464 executable mode of a file was changed, it is reset.
3465
3465
3466 If names are given, all files matching the names are reverted.
3466 If names are given, all files matching the names are reverted.
3467 If no arguments are given, no files are reverted.
3467 If no arguments are given, no files are reverted.
3468
3468
3469 Modified files are saved with a .orig suffix before reverting.
3469 Modified files are saved with a .orig suffix before reverting.
3470 To disable these backups, use --no-backup.
3470 To disable these backups, use --no-backup.
3471
3471
3472 Returns 0 on success.
3472 Returns 0 on success.
3473 """
3473 """
3474
3474
3475 if opts.get("date"):
3475 if opts.get("date"):
3476 if opts.get("rev"):
3476 if opts.get("rev"):
3477 raise util.Abort(_("you can't specify a revision and a date"))
3477 raise util.Abort(_("you can't specify a revision and a date"))
3478 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
3478 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
3479
3479
3480 parent, p2 = repo.dirstate.parents()
3480 parent, p2 = repo.dirstate.parents()
3481 if not opts.get('rev') and p2 != nullid:
3481 if not opts.get('rev') and p2 != nullid:
3482 raise util.Abort(_('uncommitted merge - '
3482 raise util.Abort(_('uncommitted merge - '
3483 'use "hg update", see "hg help revert"'))
3483 'use "hg update", see "hg help revert"'))
3484
3484
3485 if not pats and not opts.get('all'):
3485 if not pats and not opts.get('all'):
3486 raise util.Abort(_('no files or directories specified; '
3486 raise util.Abort(_('no files or directories specified; '
3487 'use --all to revert the whole repo'))
3487 'use --all to revert the whole repo'))
3488
3488
3489 ctx = cmdutil.revsingle(repo, opts.get('rev'))
3489 ctx = cmdutil.revsingle(repo, opts.get('rev'))
3490 node = ctx.node()
3490 node = ctx.node()
3491 mf = ctx.manifest()
3491 mf = ctx.manifest()
3492 if node == parent:
3492 if node == parent:
3493 pmf = mf
3493 pmf = mf
3494 else:
3494 else:
3495 pmf = None
3495 pmf = None
3496
3496
3497 # need all matching names in dirstate and manifest of target rev,
3497 # need all matching names in dirstate and manifest of target rev,
3498 # so have to walk both. do not print errors if files exist in one
3498 # so have to walk both. do not print errors if files exist in one
3499 # but not other.
3499 # but not other.
3500
3500
3501 names = {}
3501 names = {}
3502
3502
3503 wlock = repo.wlock()
3503 wlock = repo.wlock()
3504 try:
3504 try:
3505 # walk dirstate.
3505 # walk dirstate.
3506
3506
3507 m = cmdutil.match(repo, pats, opts)
3507 m = cmdutil.match(repo, pats, opts)
3508 m.bad = lambda x, y: False
3508 m.bad = lambda x, y: False
3509 for abs in repo.walk(m):
3509 for abs in repo.walk(m):
3510 names[abs] = m.rel(abs), m.exact(abs)
3510 names[abs] = m.rel(abs), m.exact(abs)
3511
3511
3512 # walk target manifest.
3512 # walk target manifest.
3513
3513
3514 def badfn(path, msg):
3514 def badfn(path, msg):
3515 if path in names:
3515 if path in names:
3516 return
3516 return
3517 path_ = path + '/'
3517 path_ = path + '/'
3518 for f in names:
3518 for f in names:
3519 if f.startswith(path_):
3519 if f.startswith(path_):
3520 return
3520 return
3521 ui.warn("%s: %s\n" % (m.rel(path), msg))
3521 ui.warn("%s: %s\n" % (m.rel(path), msg))
3522
3522
3523 m = cmdutil.match(repo, pats, opts)
3523 m = cmdutil.match(repo, pats, opts)
3524 m.bad = badfn
3524 m.bad = badfn
3525 for abs in repo[node].walk(m):
3525 for abs in repo[node].walk(m):
3526 if abs not in names:
3526 if abs not in names:
3527 names[abs] = m.rel(abs), m.exact(abs)
3527 names[abs] = m.rel(abs), m.exact(abs)
3528
3528
3529 m = cmdutil.matchfiles(repo, names)
3529 m = cmdutil.matchfiles(repo, names)
3530 changes = repo.status(match=m)[:4]
3530 changes = repo.status(match=m)[:4]
3531 modified, added, removed, deleted = map(set, changes)
3531 modified, added, removed, deleted = map(set, changes)
3532
3532
3533 # if f is a rename, also revert the source
3533 # if f is a rename, also revert the source
3534 cwd = repo.getcwd()
3534 cwd = repo.getcwd()
3535 for f in added:
3535 for f in added:
3536 src = repo.dirstate.copied(f)
3536 src = repo.dirstate.copied(f)
3537 if src and src not in names and repo.dirstate[src] == 'r':
3537 if src and src not in names and repo.dirstate[src] == 'r':
3538 removed.add(src)
3538 removed.add(src)
3539 names[src] = (repo.pathto(src, cwd), True)
3539 names[src] = (repo.pathto(src, cwd), True)
3540
3540
3541 def removeforget(abs):
3541 def removeforget(abs):
3542 if repo.dirstate[abs] == 'a':
3542 if repo.dirstate[abs] == 'a':
3543 return _('forgetting %s\n')
3543 return _('forgetting %s\n')
3544 return _('removing %s\n')
3544 return _('removing %s\n')
3545
3545
3546 revert = ([], _('reverting %s\n'))
3546 revert = ([], _('reverting %s\n'))
3547 add = ([], _('adding %s\n'))
3547 add = ([], _('adding %s\n'))
3548 remove = ([], removeforget)
3548 remove = ([], removeforget)
3549 undelete = ([], _('undeleting %s\n'))
3549 undelete = ([], _('undeleting %s\n'))
3550
3550
3551 disptable = (
3551 disptable = (
3552 # dispatch table:
3552 # dispatch table:
3553 # file state
3553 # file state
3554 # action if in target manifest
3554 # action if in target manifest
3555 # action if not in target manifest
3555 # action if not in target manifest
3556 # make backup if in target manifest
3556 # make backup if in target manifest
3557 # make backup if not in target manifest
3557 # make backup if not in target manifest
3558 (modified, revert, remove, True, True),
3558 (modified, revert, remove, True, True),
3559 (added, revert, remove, True, False),
3559 (added, revert, remove, True, False),
3560 (removed, undelete, None, False, False),
3560 (removed, undelete, None, False, False),
3561 (deleted, revert, remove, False, False),
3561 (deleted, revert, remove, False, False),
3562 )
3562 )
3563
3563
3564 for abs, (rel, exact) in sorted(names.items()):
3564 for abs, (rel, exact) in sorted(names.items()):
3565 mfentry = mf.get(abs)
3565 mfentry = mf.get(abs)
3566 target = repo.wjoin(abs)
3566 target = repo.wjoin(abs)
3567 def handle(xlist, dobackup):
3567 def handle(xlist, dobackup):
3568 xlist[0].append(abs)
3568 xlist[0].append(abs)
3569 if (dobackup and not opts.get('no_backup') and
3569 if (dobackup and not opts.get('no_backup') and
3570 os.path.lexists(target)):
3570 os.path.lexists(target)):
3571 bakname = "%s.orig" % rel
3571 bakname = "%s.orig" % rel
3572 ui.note(_('saving current version of %s as %s\n') %
3572 ui.note(_('saving current version of %s as %s\n') %
3573 (rel, bakname))
3573 (rel, bakname))
3574 if not opts.get('dry_run'):
3574 if not opts.get('dry_run'):
3575 util.rename(target, bakname)
3575 util.rename(target, bakname)
3576 if ui.verbose or not exact:
3576 if ui.verbose or not exact:
3577 msg = xlist[1]
3577 msg = xlist[1]
3578 if not isinstance(msg, basestring):
3578 if not isinstance(msg, basestring):
3579 msg = msg(abs)
3579 msg = msg(abs)
3580 ui.status(msg % rel)
3580 ui.status(msg % rel)
3581 for table, hitlist, misslist, backuphit, backupmiss in disptable:
3581 for table, hitlist, misslist, backuphit, backupmiss in disptable:
3582 if abs not in table:
3582 if abs not in table:
3583 continue
3583 continue
3584 # file has changed in dirstate
3584 # file has changed in dirstate
3585 if mfentry:
3585 if mfentry:
3586 handle(hitlist, backuphit)
3586 handle(hitlist, backuphit)
3587 elif misslist is not None:
3587 elif misslist is not None:
3588 handle(misslist, backupmiss)
3588 handle(misslist, backupmiss)
3589 break
3589 break
3590 else:
3590 else:
3591 if abs not in repo.dirstate:
3591 if abs not in repo.dirstate:
3592 if mfentry:
3592 if mfentry:
3593 handle(add, True)
3593 handle(add, True)
3594 elif exact:
3594 elif exact:
3595 ui.warn(_('file not managed: %s\n') % rel)
3595 ui.warn(_('file not managed: %s\n') % rel)
3596 continue
3596 continue
3597 # file has not changed in dirstate
3597 # file has not changed in dirstate
3598 if node == parent:
3598 if node == parent:
3599 if exact:
3599 if exact:
3600 ui.warn(_('no changes needed to %s\n') % rel)
3600 ui.warn(_('no changes needed to %s\n') % rel)
3601 continue
3601 continue
3602 if pmf is None:
3602 if pmf is None:
3603 # only need parent manifest in this unlikely case,
3603 # only need parent manifest in this unlikely case,
3604 # so do not read by default
3604 # so do not read by default
3605 pmf = repo[parent].manifest()
3605 pmf = repo[parent].manifest()
3606 if abs in pmf:
3606 if abs in pmf:
3607 if mfentry:
3607 if mfentry:
3608 # if version of file is same in parent and target
3608 # if version of file is same in parent and target
3609 # manifests, do nothing
3609 # manifests, do nothing
3610 if (pmf[abs] != mfentry or
3610 if (pmf[abs] != mfentry or
3611 pmf.flags(abs) != mf.flags(abs)):
3611 pmf.flags(abs) != mf.flags(abs)):
3612 handle(revert, False)
3612 handle(revert, False)
3613 else:
3613 else:
3614 handle(remove, False)
3614 handle(remove, False)
3615
3615
3616 if not opts.get('dry_run'):
3616 if not opts.get('dry_run'):
3617 def checkout(f):
3617 def checkout(f):
3618 fc = ctx[f]
3618 fc = ctx[f]
3619 repo.wwrite(f, fc.data(), fc.flags())
3619 repo.wwrite(f, fc.data(), fc.flags())
3620
3620
3621 audit_path = scmutil.path_auditor(repo.root)
3621 audit_path = scmutil.pathauditor(repo.root)
3622 for f in remove[0]:
3622 for f in remove[0]:
3623 if repo.dirstate[f] == 'a':
3623 if repo.dirstate[f] == 'a':
3624 repo.dirstate.forget(f)
3624 repo.dirstate.forget(f)
3625 continue
3625 continue
3626 audit_path(f)
3626 audit_path(f)
3627 try:
3627 try:
3628 util.unlinkpath(repo.wjoin(f))
3628 util.unlinkpath(repo.wjoin(f))
3629 except OSError:
3629 except OSError:
3630 pass
3630 pass
3631 repo.dirstate.remove(f)
3631 repo.dirstate.remove(f)
3632
3632
3633 normal = None
3633 normal = None
3634 if node == parent:
3634 if node == parent:
3635 # We're reverting to our parent. If possible, we'd like status
3635 # We're reverting to our parent. If possible, we'd like status
3636 # to report the file as clean. We have to use normallookup for
3636 # to report the file as clean. We have to use normallookup for
3637 # merges to avoid losing information about merged/dirty files.
3637 # merges to avoid losing information about merged/dirty files.
3638 if p2 != nullid:
3638 if p2 != nullid:
3639 normal = repo.dirstate.normallookup
3639 normal = repo.dirstate.normallookup
3640 else:
3640 else:
3641 normal = repo.dirstate.normal
3641 normal = repo.dirstate.normal
3642 for f in revert[0]:
3642 for f in revert[0]:
3643 checkout(f)
3643 checkout(f)
3644 if normal:
3644 if normal:
3645 normal(f)
3645 normal(f)
3646
3646
3647 for f in add[0]:
3647 for f in add[0]:
3648 checkout(f)
3648 checkout(f)
3649 repo.dirstate.add(f)
3649 repo.dirstate.add(f)
3650
3650
3651 normal = repo.dirstate.normallookup
3651 normal = repo.dirstate.normallookup
3652 if node == parent and p2 == nullid:
3652 if node == parent and p2 == nullid:
3653 normal = repo.dirstate.normal
3653 normal = repo.dirstate.normal
3654 for f in undelete[0]:
3654 for f in undelete[0]:
3655 checkout(f)
3655 checkout(f)
3656 normal(f)
3656 normal(f)
3657
3657
3658 finally:
3658 finally:
3659 wlock.release()
3659 wlock.release()
3660
3660
3661 def rollback(ui, repo, **opts):
3661 def rollback(ui, repo, **opts):
3662 """roll back the last transaction (dangerous)
3662 """roll back the last transaction (dangerous)
3663
3663
3664 This command should be used with care. There is only one level of
3664 This command should be used with care. There is only one level of
3665 rollback, and there is no way to undo a rollback. It will also
3665 rollback, and there is no way to undo a rollback. It will also
3666 restore the dirstate at the time of the last transaction, losing
3666 restore the dirstate at the time of the last transaction, losing
3667 any dirstate changes since that time. This command does not alter
3667 any dirstate changes since that time. This command does not alter
3668 the working directory.
3668 the working directory.
3669
3669
3670 Transactions are used to encapsulate the effects of all commands
3670 Transactions are used to encapsulate the effects of all commands
3671 that create new changesets or propagate existing changesets into a
3671 that create new changesets or propagate existing changesets into a
3672 repository. For example, the following commands are transactional,
3672 repository. For example, the following commands are transactional,
3673 and their effects can be rolled back:
3673 and their effects can be rolled back:
3674
3674
3675 - commit
3675 - commit
3676 - import
3676 - import
3677 - pull
3677 - pull
3678 - push (with this repository as the destination)
3678 - push (with this repository as the destination)
3679 - unbundle
3679 - unbundle
3680
3680
3681 This command is not intended for use on public repositories. Once
3681 This command is not intended for use on public repositories. Once
3682 changes are visible for pull by other users, rolling a transaction
3682 changes are visible for pull by other users, rolling a transaction
3683 back locally is ineffective (someone else may already have pulled
3683 back locally is ineffective (someone else may already have pulled
3684 the changes). Furthermore, a race is possible with readers of the
3684 the changes). Furthermore, a race is possible with readers of the
3685 repository; for example an in-progress pull from the repository
3685 repository; for example an in-progress pull from the repository
3686 may fail if a rollback is performed.
3686 may fail if a rollback is performed.
3687
3687
3688 Returns 0 on success, 1 if no rollback data is available.
3688 Returns 0 on success, 1 if no rollback data is available.
3689 """
3689 """
3690 return repo.rollback(opts.get('dry_run'))
3690 return repo.rollback(opts.get('dry_run'))
3691
3691
3692 def root(ui, repo):
3692 def root(ui, repo):
3693 """print the root (top) of the current working directory
3693 """print the root (top) of the current working directory
3694
3694
3695 Print the root directory of the current repository.
3695 Print the root directory of the current repository.
3696
3696
3697 Returns 0 on success.
3697 Returns 0 on success.
3698 """
3698 """
3699 ui.write(repo.root + "\n")
3699 ui.write(repo.root + "\n")
3700
3700
3701 def serve(ui, repo, **opts):
3701 def serve(ui, repo, **opts):
3702 """start stand-alone webserver
3702 """start stand-alone webserver
3703
3703
3704 Start a local HTTP repository browser and pull server. You can use
3704 Start a local HTTP repository browser and pull server. You can use
3705 this for ad-hoc sharing and browsing of repositories. It is
3705 this for ad-hoc sharing and browsing of repositories. It is
3706 recommended to use a real web server to serve a repository for
3706 recommended to use a real web server to serve a repository for
3707 longer periods of time.
3707 longer periods of time.
3708
3708
3709 Please note that the server does not implement access control.
3709 Please note that the server does not implement access control.
3710 This means that, by default, anybody can read from the server and
3710 This means that, by default, anybody can read from the server and
3711 nobody can write to it by default. Set the ``web.allow_push``
3711 nobody can write to it by default. Set the ``web.allow_push``
3712 option to ``*`` to allow everybody to push to the server. You
3712 option to ``*`` to allow everybody to push to the server. You
3713 should use a real web server if you need to authenticate users.
3713 should use a real web server if you need to authenticate users.
3714
3714
3715 By default, the server logs accesses to stdout and errors to
3715 By default, the server logs accesses to stdout and errors to
3716 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
3716 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
3717 files.
3717 files.
3718
3718
3719 To have the server choose a free port number to listen on, specify
3719 To have the server choose a free port number to listen on, specify
3720 a port number of 0; in this case, the server will print the port
3720 a port number of 0; in this case, the server will print the port
3721 number it uses.
3721 number it uses.
3722
3722
3723 Returns 0 on success.
3723 Returns 0 on success.
3724 """
3724 """
3725
3725
3726 if opts["stdio"]:
3726 if opts["stdio"]:
3727 if repo is None:
3727 if repo is None:
3728 raise error.RepoError(_("There is no Mercurial repository here"
3728 raise error.RepoError(_("There is no Mercurial repository here"
3729 " (.hg not found)"))
3729 " (.hg not found)"))
3730 s = sshserver.sshserver(ui, repo)
3730 s = sshserver.sshserver(ui, repo)
3731 s.serve_forever()
3731 s.serve_forever()
3732
3732
3733 # this way we can check if something was given in the command-line
3733 # this way we can check if something was given in the command-line
3734 if opts.get('port'):
3734 if opts.get('port'):
3735 opts['port'] = util.getport(opts.get('port'))
3735 opts['port'] = util.getport(opts.get('port'))
3736
3736
3737 baseui = repo and repo.baseui or ui
3737 baseui = repo and repo.baseui or ui
3738 optlist = ("name templates style address port prefix ipv6"
3738 optlist = ("name templates style address port prefix ipv6"
3739 " accesslog errorlog certificate encoding")
3739 " accesslog errorlog certificate encoding")
3740 for o in optlist.split():
3740 for o in optlist.split():
3741 val = opts.get(o, '')
3741 val = opts.get(o, '')
3742 if val in (None, ''): # should check against default options instead
3742 if val in (None, ''): # should check against default options instead
3743 continue
3743 continue
3744 baseui.setconfig("web", o, val)
3744 baseui.setconfig("web", o, val)
3745 if repo and repo.ui != baseui:
3745 if repo and repo.ui != baseui:
3746 repo.ui.setconfig("web", o, val)
3746 repo.ui.setconfig("web", o, val)
3747
3747
3748 o = opts.get('web_conf') or opts.get('webdir_conf')
3748 o = opts.get('web_conf') or opts.get('webdir_conf')
3749 if not o:
3749 if not o:
3750 if not repo:
3750 if not repo:
3751 raise error.RepoError(_("There is no Mercurial repository"
3751 raise error.RepoError(_("There is no Mercurial repository"
3752 " here (.hg not found)"))
3752 " here (.hg not found)"))
3753 o = repo.root
3753 o = repo.root
3754
3754
3755 app = hgweb.hgweb(o, baseui=ui)
3755 app = hgweb.hgweb(o, baseui=ui)
3756
3756
3757 class service(object):
3757 class service(object):
3758 def init(self):
3758 def init(self):
3759 util.set_signal_handler()
3759 util.set_signal_handler()
3760 self.httpd = hgweb.server.create_server(ui, app)
3760 self.httpd = hgweb.server.create_server(ui, app)
3761
3761
3762 if opts['port'] and not ui.verbose:
3762 if opts['port'] and not ui.verbose:
3763 return
3763 return
3764
3764
3765 if self.httpd.prefix:
3765 if self.httpd.prefix:
3766 prefix = self.httpd.prefix.strip('/') + '/'
3766 prefix = self.httpd.prefix.strip('/') + '/'
3767 else:
3767 else:
3768 prefix = ''
3768 prefix = ''
3769
3769
3770 port = ':%d' % self.httpd.port
3770 port = ':%d' % self.httpd.port
3771 if port == ':80':
3771 if port == ':80':
3772 port = ''
3772 port = ''
3773
3773
3774 bindaddr = self.httpd.addr
3774 bindaddr = self.httpd.addr
3775 if bindaddr == '0.0.0.0':
3775 if bindaddr == '0.0.0.0':
3776 bindaddr = '*'
3776 bindaddr = '*'
3777 elif ':' in bindaddr: # IPv6
3777 elif ':' in bindaddr: # IPv6
3778 bindaddr = '[%s]' % bindaddr
3778 bindaddr = '[%s]' % bindaddr
3779
3779
3780 fqaddr = self.httpd.fqaddr
3780 fqaddr = self.httpd.fqaddr
3781 if ':' in fqaddr:
3781 if ':' in fqaddr:
3782 fqaddr = '[%s]' % fqaddr
3782 fqaddr = '[%s]' % fqaddr
3783 if opts['port']:
3783 if opts['port']:
3784 write = ui.status
3784 write = ui.status
3785 else:
3785 else:
3786 write = ui.write
3786 write = ui.write
3787 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
3787 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
3788 (fqaddr, port, prefix, bindaddr, self.httpd.port))
3788 (fqaddr, port, prefix, bindaddr, self.httpd.port))
3789
3789
3790 def run(self):
3790 def run(self):
3791 self.httpd.serve_forever()
3791 self.httpd.serve_forever()
3792
3792
3793 service = service()
3793 service = service()
3794
3794
3795 cmdutil.service(opts, initfn=service.init, runfn=service.run)
3795 cmdutil.service(opts, initfn=service.init, runfn=service.run)
3796
3796
3797 def status(ui, repo, *pats, **opts):
3797 def status(ui, repo, *pats, **opts):
3798 """show changed files in the working directory
3798 """show changed files in the working directory
3799
3799
3800 Show status of files in the repository. If names are given, only
3800 Show status of files in the repository. If names are given, only
3801 files that match are shown. Files that are clean or ignored or
3801 files that match are shown. Files that are clean or ignored or
3802 the source of a copy/move operation, are not listed unless
3802 the source of a copy/move operation, are not listed unless
3803 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
3803 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
3804 Unless options described with "show only ..." are given, the
3804 Unless options described with "show only ..." are given, the
3805 options -mardu are used.
3805 options -mardu are used.
3806
3806
3807 Option -q/--quiet hides untracked (unknown and ignored) files
3807 Option -q/--quiet hides untracked (unknown and ignored) files
3808 unless explicitly requested with -u/--unknown or -i/--ignored.
3808 unless explicitly requested with -u/--unknown or -i/--ignored.
3809
3809
3810 .. note::
3810 .. note::
3811 status may appear to disagree with diff if permissions have
3811 status may appear to disagree with diff if permissions have
3812 changed or a merge has occurred. The standard diff format does
3812 changed or a merge has occurred. The standard diff format does
3813 not report permission changes and diff only reports changes
3813 not report permission changes and diff only reports changes
3814 relative to one merge parent.
3814 relative to one merge parent.
3815
3815
3816 If one revision is given, it is used as the base revision.
3816 If one revision is given, it is used as the base revision.
3817 If two revisions are given, the differences between them are
3817 If two revisions are given, the differences between them are
3818 shown. The --change option can also be used as a shortcut to list
3818 shown. The --change option can also be used as a shortcut to list
3819 the changed files of a revision from its first parent.
3819 the changed files of a revision from its first parent.
3820
3820
3821 The codes used to show the status of files are::
3821 The codes used to show the status of files are::
3822
3822
3823 M = modified
3823 M = modified
3824 A = added
3824 A = added
3825 R = removed
3825 R = removed
3826 C = clean
3826 C = clean
3827 ! = missing (deleted by non-hg command, but still tracked)
3827 ! = missing (deleted by non-hg command, but still tracked)
3828 ? = not tracked
3828 ? = not tracked
3829 I = ignored
3829 I = ignored
3830 = origin of the previous file listed as A (added)
3830 = origin of the previous file listed as A (added)
3831
3831
3832 Returns 0 on success.
3832 Returns 0 on success.
3833 """
3833 """
3834
3834
3835 revs = opts.get('rev')
3835 revs = opts.get('rev')
3836 change = opts.get('change')
3836 change = opts.get('change')
3837
3837
3838 if revs and change:
3838 if revs and change:
3839 msg = _('cannot specify --rev and --change at the same time')
3839 msg = _('cannot specify --rev and --change at the same time')
3840 raise util.Abort(msg)
3840 raise util.Abort(msg)
3841 elif change:
3841 elif change:
3842 node2 = repo.lookup(change)
3842 node2 = repo.lookup(change)
3843 node1 = repo[node2].p1().node()
3843 node1 = repo[node2].p1().node()
3844 else:
3844 else:
3845 node1, node2 = cmdutil.revpair(repo, revs)
3845 node1, node2 = cmdutil.revpair(repo, revs)
3846
3846
3847 cwd = (pats and repo.getcwd()) or ''
3847 cwd = (pats and repo.getcwd()) or ''
3848 end = opts.get('print0') and '\0' or '\n'
3848 end = opts.get('print0') and '\0' or '\n'
3849 copy = {}
3849 copy = {}
3850 states = 'modified added removed deleted unknown ignored clean'.split()
3850 states = 'modified added removed deleted unknown ignored clean'.split()
3851 show = [k for k in states if opts.get(k)]
3851 show = [k for k in states if opts.get(k)]
3852 if opts.get('all'):
3852 if opts.get('all'):
3853 show += ui.quiet and (states[:4] + ['clean']) or states
3853 show += ui.quiet and (states[:4] + ['clean']) or states
3854 if not show:
3854 if not show:
3855 show = ui.quiet and states[:4] or states[:5]
3855 show = ui.quiet and states[:4] or states[:5]
3856
3856
3857 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
3857 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
3858 'ignored' in show, 'clean' in show, 'unknown' in show,
3858 'ignored' in show, 'clean' in show, 'unknown' in show,
3859 opts.get('subrepos'))
3859 opts.get('subrepos'))
3860 changestates = zip(states, 'MAR!?IC', stat)
3860 changestates = zip(states, 'MAR!?IC', stat)
3861
3861
3862 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
3862 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
3863 ctxn = repo[nullid]
3863 ctxn = repo[nullid]
3864 ctx1 = repo[node1]
3864 ctx1 = repo[node1]
3865 ctx2 = repo[node2]
3865 ctx2 = repo[node2]
3866 added = stat[1]
3866 added = stat[1]
3867 if node2 is None:
3867 if node2 is None:
3868 added = stat[0] + stat[1] # merged?
3868 added = stat[0] + stat[1] # merged?
3869
3869
3870 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
3870 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
3871 if k in added:
3871 if k in added:
3872 copy[k] = v
3872 copy[k] = v
3873 elif v in added:
3873 elif v in added:
3874 copy[v] = k
3874 copy[v] = k
3875
3875
3876 for state, char, files in changestates:
3876 for state, char, files in changestates:
3877 if state in show:
3877 if state in show:
3878 format = "%s %%s%s" % (char, end)
3878 format = "%s %%s%s" % (char, end)
3879 if opts.get('no_status'):
3879 if opts.get('no_status'):
3880 format = "%%s%s" % end
3880 format = "%%s%s" % end
3881
3881
3882 for f in files:
3882 for f in files:
3883 ui.write(format % repo.pathto(f, cwd),
3883 ui.write(format % repo.pathto(f, cwd),
3884 label='status.' + state)
3884 label='status.' + state)
3885 if f in copy:
3885 if f in copy:
3886 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
3886 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
3887 label='status.copied')
3887 label='status.copied')
3888
3888
3889 def summary(ui, repo, **opts):
3889 def summary(ui, repo, **opts):
3890 """summarize working directory state
3890 """summarize working directory state
3891
3891
3892 This generates a brief summary of the working directory state,
3892 This generates a brief summary of the working directory state,
3893 including parents, branch, commit status, and available updates.
3893 including parents, branch, commit status, and available updates.
3894
3894
3895 With the --remote option, this will check the default paths for
3895 With the --remote option, this will check the default paths for
3896 incoming and outgoing changes. This can be time-consuming.
3896 incoming and outgoing changes. This can be time-consuming.
3897
3897
3898 Returns 0 on success.
3898 Returns 0 on success.
3899 """
3899 """
3900
3900
3901 ctx = repo[None]
3901 ctx = repo[None]
3902 parents = ctx.parents()
3902 parents = ctx.parents()
3903 pnode = parents[0].node()
3903 pnode = parents[0].node()
3904
3904
3905 for p in parents:
3905 for p in parents:
3906 # label with log.changeset (instead of log.parent) since this
3906 # label with log.changeset (instead of log.parent) since this
3907 # shows a working directory parent *changeset*:
3907 # shows a working directory parent *changeset*:
3908 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
3908 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
3909 label='log.changeset')
3909 label='log.changeset')
3910 ui.write(' '.join(p.tags()), label='log.tag')
3910 ui.write(' '.join(p.tags()), label='log.tag')
3911 if p.bookmarks():
3911 if p.bookmarks():
3912 ui.write(' ' + ' '.join(p.bookmarks()), label='log.bookmark')
3912 ui.write(' ' + ' '.join(p.bookmarks()), label='log.bookmark')
3913 if p.rev() == -1:
3913 if p.rev() == -1:
3914 if not len(repo):
3914 if not len(repo):
3915 ui.write(_(' (empty repository)'))
3915 ui.write(_(' (empty repository)'))
3916 else:
3916 else:
3917 ui.write(_(' (no revision checked out)'))
3917 ui.write(_(' (no revision checked out)'))
3918 ui.write('\n')
3918 ui.write('\n')
3919 if p.description():
3919 if p.description():
3920 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
3920 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
3921 label='log.summary')
3921 label='log.summary')
3922
3922
3923 branch = ctx.branch()
3923 branch = ctx.branch()
3924 bheads = repo.branchheads(branch)
3924 bheads = repo.branchheads(branch)
3925 m = _('branch: %s\n') % branch
3925 m = _('branch: %s\n') % branch
3926 if branch != 'default':
3926 if branch != 'default':
3927 ui.write(m, label='log.branch')
3927 ui.write(m, label='log.branch')
3928 else:
3928 else:
3929 ui.status(m, label='log.branch')
3929 ui.status(m, label='log.branch')
3930
3930
3931 st = list(repo.status(unknown=True))[:6]
3931 st = list(repo.status(unknown=True))[:6]
3932
3932
3933 c = repo.dirstate.copies()
3933 c = repo.dirstate.copies()
3934 copied, renamed = [], []
3934 copied, renamed = [], []
3935 for d, s in c.iteritems():
3935 for d, s in c.iteritems():
3936 if s in st[2]:
3936 if s in st[2]:
3937 st[2].remove(s)
3937 st[2].remove(s)
3938 renamed.append(d)
3938 renamed.append(d)
3939 else:
3939 else:
3940 copied.append(d)
3940 copied.append(d)
3941 if d in st[1]:
3941 if d in st[1]:
3942 st[1].remove(d)
3942 st[1].remove(d)
3943 st.insert(3, renamed)
3943 st.insert(3, renamed)
3944 st.insert(4, copied)
3944 st.insert(4, copied)
3945
3945
3946 ms = mergemod.mergestate(repo)
3946 ms = mergemod.mergestate(repo)
3947 st.append([f for f in ms if ms[f] == 'u'])
3947 st.append([f for f in ms if ms[f] == 'u'])
3948
3948
3949 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
3949 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
3950 st.append(subs)
3950 st.append(subs)
3951
3951
3952 labels = [ui.label(_('%d modified'), 'status.modified'),
3952 labels = [ui.label(_('%d modified'), 'status.modified'),
3953 ui.label(_('%d added'), 'status.added'),
3953 ui.label(_('%d added'), 'status.added'),
3954 ui.label(_('%d removed'), 'status.removed'),
3954 ui.label(_('%d removed'), 'status.removed'),
3955 ui.label(_('%d renamed'), 'status.copied'),
3955 ui.label(_('%d renamed'), 'status.copied'),
3956 ui.label(_('%d copied'), 'status.copied'),
3956 ui.label(_('%d copied'), 'status.copied'),
3957 ui.label(_('%d deleted'), 'status.deleted'),
3957 ui.label(_('%d deleted'), 'status.deleted'),
3958 ui.label(_('%d unknown'), 'status.unknown'),
3958 ui.label(_('%d unknown'), 'status.unknown'),
3959 ui.label(_('%d ignored'), 'status.ignored'),
3959 ui.label(_('%d ignored'), 'status.ignored'),
3960 ui.label(_('%d unresolved'), 'resolve.unresolved'),
3960 ui.label(_('%d unresolved'), 'resolve.unresolved'),
3961 ui.label(_('%d subrepos'), 'status.modified')]
3961 ui.label(_('%d subrepos'), 'status.modified')]
3962 t = []
3962 t = []
3963 for s, l in zip(st, labels):
3963 for s, l in zip(st, labels):
3964 if s:
3964 if s:
3965 t.append(l % len(s))
3965 t.append(l % len(s))
3966
3966
3967 t = ', '.join(t)
3967 t = ', '.join(t)
3968 cleanworkdir = False
3968 cleanworkdir = False
3969
3969
3970 if len(parents) > 1:
3970 if len(parents) > 1:
3971 t += _(' (merge)')
3971 t += _(' (merge)')
3972 elif branch != parents[0].branch():
3972 elif branch != parents[0].branch():
3973 t += _(' (new branch)')
3973 t += _(' (new branch)')
3974 elif (parents[0].extra().get('close') and
3974 elif (parents[0].extra().get('close') and
3975 pnode in repo.branchheads(branch, closed=True)):
3975 pnode in repo.branchheads(branch, closed=True)):
3976 t += _(' (head closed)')
3976 t += _(' (head closed)')
3977 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
3977 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
3978 t += _(' (clean)')
3978 t += _(' (clean)')
3979 cleanworkdir = True
3979 cleanworkdir = True
3980 elif pnode not in bheads:
3980 elif pnode not in bheads:
3981 t += _(' (new branch head)')
3981 t += _(' (new branch head)')
3982
3982
3983 if cleanworkdir:
3983 if cleanworkdir:
3984 ui.status(_('commit: %s\n') % t.strip())
3984 ui.status(_('commit: %s\n') % t.strip())
3985 else:
3985 else:
3986 ui.write(_('commit: %s\n') % t.strip())
3986 ui.write(_('commit: %s\n') % t.strip())
3987
3987
3988 # all ancestors of branch heads - all ancestors of parent = new csets
3988 # all ancestors of branch heads - all ancestors of parent = new csets
3989 new = [0] * len(repo)
3989 new = [0] * len(repo)
3990 cl = repo.changelog
3990 cl = repo.changelog
3991 for a in [cl.rev(n) for n in bheads]:
3991 for a in [cl.rev(n) for n in bheads]:
3992 new[a] = 1
3992 new[a] = 1
3993 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
3993 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
3994 new[a] = 1
3994 new[a] = 1
3995 for a in [p.rev() for p in parents]:
3995 for a in [p.rev() for p in parents]:
3996 if a >= 0:
3996 if a >= 0:
3997 new[a] = 0
3997 new[a] = 0
3998 for a in cl.ancestors(*[p.rev() for p in parents]):
3998 for a in cl.ancestors(*[p.rev() for p in parents]):
3999 new[a] = 0
3999 new[a] = 0
4000 new = sum(new)
4000 new = sum(new)
4001
4001
4002 if new == 0:
4002 if new == 0:
4003 ui.status(_('update: (current)\n'))
4003 ui.status(_('update: (current)\n'))
4004 elif pnode not in bheads:
4004 elif pnode not in bheads:
4005 ui.write(_('update: %d new changesets (update)\n') % new)
4005 ui.write(_('update: %d new changesets (update)\n') % new)
4006 else:
4006 else:
4007 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
4007 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
4008 (new, len(bheads)))
4008 (new, len(bheads)))
4009
4009
4010 if opts.get('remote'):
4010 if opts.get('remote'):
4011 t = []
4011 t = []
4012 source, branches = hg.parseurl(ui.expandpath('default'))
4012 source, branches = hg.parseurl(ui.expandpath('default'))
4013 other = hg.repository(hg.remoteui(repo, {}), source)
4013 other = hg.repository(hg.remoteui(repo, {}), source)
4014 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
4014 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
4015 ui.debug('comparing with %s\n' % util.hidepassword(source))
4015 ui.debug('comparing with %s\n' % util.hidepassword(source))
4016 repo.ui.pushbuffer()
4016 repo.ui.pushbuffer()
4017 common, incoming, rheads = discovery.findcommonincoming(repo, other)
4017 common, incoming, rheads = discovery.findcommonincoming(repo, other)
4018 repo.ui.popbuffer()
4018 repo.ui.popbuffer()
4019 if incoming:
4019 if incoming:
4020 t.append(_('1 or more incoming'))
4020 t.append(_('1 or more incoming'))
4021
4021
4022 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
4022 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
4023 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
4023 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
4024 other = hg.repository(hg.remoteui(repo, {}), dest)
4024 other = hg.repository(hg.remoteui(repo, {}), dest)
4025 ui.debug('comparing with %s\n' % util.hidepassword(dest))
4025 ui.debug('comparing with %s\n' % util.hidepassword(dest))
4026 repo.ui.pushbuffer()
4026 repo.ui.pushbuffer()
4027 common, _anyinc, _heads = discovery.findcommonincoming(repo, other)
4027 common, _anyinc, _heads = discovery.findcommonincoming(repo, other)
4028 repo.ui.popbuffer()
4028 repo.ui.popbuffer()
4029 o = repo.changelog.findmissing(common=common)
4029 o = repo.changelog.findmissing(common=common)
4030 if o:
4030 if o:
4031 t.append(_('%d outgoing') % len(o))
4031 t.append(_('%d outgoing') % len(o))
4032 if 'bookmarks' in other.listkeys('namespaces'):
4032 if 'bookmarks' in other.listkeys('namespaces'):
4033 lmarks = repo.listkeys('bookmarks')
4033 lmarks = repo.listkeys('bookmarks')
4034 rmarks = other.listkeys('bookmarks')
4034 rmarks = other.listkeys('bookmarks')
4035 diff = set(rmarks) - set(lmarks)
4035 diff = set(rmarks) - set(lmarks)
4036 if len(diff) > 0:
4036 if len(diff) > 0:
4037 t.append(_('%d incoming bookmarks') % len(diff))
4037 t.append(_('%d incoming bookmarks') % len(diff))
4038 diff = set(lmarks) - set(rmarks)
4038 diff = set(lmarks) - set(rmarks)
4039 if len(diff) > 0:
4039 if len(diff) > 0:
4040 t.append(_('%d outgoing bookmarks') % len(diff))
4040 t.append(_('%d outgoing bookmarks') % len(diff))
4041
4041
4042 if t:
4042 if t:
4043 ui.write(_('remote: %s\n') % (', '.join(t)))
4043 ui.write(_('remote: %s\n') % (', '.join(t)))
4044 else:
4044 else:
4045 ui.status(_('remote: (synced)\n'))
4045 ui.status(_('remote: (synced)\n'))
4046
4046
4047 def tag(ui, repo, name1, *names, **opts):
4047 def tag(ui, repo, name1, *names, **opts):
4048 """add one or more tags for the current or given revision
4048 """add one or more tags for the current or given revision
4049
4049
4050 Name a particular revision using <name>.
4050 Name a particular revision using <name>.
4051
4051
4052 Tags are used to name particular revisions of the repository and are
4052 Tags are used to name particular revisions of the repository and are
4053 very useful to compare different revisions, to go back to significant
4053 very useful to compare different revisions, to go back to significant
4054 earlier versions or to mark branch points as releases, etc. Changing
4054 earlier versions or to mark branch points as releases, etc. Changing
4055 an existing tag is normally disallowed; use -f/--force to override.
4055 an existing tag is normally disallowed; use -f/--force to override.
4056
4056
4057 If no revision is given, the parent of the working directory is
4057 If no revision is given, the parent of the working directory is
4058 used, or tip if no revision is checked out.
4058 used, or tip if no revision is checked out.
4059
4059
4060 To facilitate version control, distribution, and merging of tags,
4060 To facilitate version control, distribution, and merging of tags,
4061 they are stored as a file named ".hgtags" which is managed similarly
4061 they are stored as a file named ".hgtags" which is managed similarly
4062 to other project files and can be hand-edited if necessary. This
4062 to other project files and can be hand-edited if necessary. This
4063 also means that tagging creates a new commit. The file
4063 also means that tagging creates a new commit. The file
4064 ".hg/localtags" is used for local tags (not shared among
4064 ".hg/localtags" is used for local tags (not shared among
4065 repositories).
4065 repositories).
4066
4066
4067 Tag commits are usually made at the head of a branch. If the parent
4067 Tag commits are usually made at the head of a branch. If the parent
4068 of the working directory is not a branch head, :hg:`tag` aborts; use
4068 of the working directory is not a branch head, :hg:`tag` aborts; use
4069 -f/--force to force the tag commit to be based on a non-head
4069 -f/--force to force the tag commit to be based on a non-head
4070 changeset.
4070 changeset.
4071
4071
4072 See :hg:`help dates` for a list of formats valid for -d/--date.
4072 See :hg:`help dates` for a list of formats valid for -d/--date.
4073
4073
4074 Since tag names have priority over branch names during revision
4074 Since tag names have priority over branch names during revision
4075 lookup, using an existing branch name as a tag name is discouraged.
4075 lookup, using an existing branch name as a tag name is discouraged.
4076
4076
4077 Returns 0 on success.
4077 Returns 0 on success.
4078 """
4078 """
4079
4079
4080 rev_ = "."
4080 rev_ = "."
4081 names = [t.strip() for t in (name1,) + names]
4081 names = [t.strip() for t in (name1,) + names]
4082 if len(names) != len(set(names)):
4082 if len(names) != len(set(names)):
4083 raise util.Abort(_('tag names must be unique'))
4083 raise util.Abort(_('tag names must be unique'))
4084 for n in names:
4084 for n in names:
4085 if n in ['tip', '.', 'null']:
4085 if n in ['tip', '.', 'null']:
4086 raise util.Abort(_("the name '%s' is reserved") % n)
4086 raise util.Abort(_("the name '%s' is reserved") % n)
4087 if not n:
4087 if not n:
4088 raise util.Abort(_('tag names cannot consist entirely of whitespace'))
4088 raise util.Abort(_('tag names cannot consist entirely of whitespace'))
4089 if opts.get('rev') and opts.get('remove'):
4089 if opts.get('rev') and opts.get('remove'):
4090 raise util.Abort(_("--rev and --remove are incompatible"))
4090 raise util.Abort(_("--rev and --remove are incompatible"))
4091 if opts.get('rev'):
4091 if opts.get('rev'):
4092 rev_ = opts['rev']
4092 rev_ = opts['rev']
4093 message = opts.get('message')
4093 message = opts.get('message')
4094 if opts.get('remove'):
4094 if opts.get('remove'):
4095 expectedtype = opts.get('local') and 'local' or 'global'
4095 expectedtype = opts.get('local') and 'local' or 'global'
4096 for n in names:
4096 for n in names:
4097 if not repo.tagtype(n):
4097 if not repo.tagtype(n):
4098 raise util.Abort(_("tag '%s' does not exist") % n)
4098 raise util.Abort(_("tag '%s' does not exist") % n)
4099 if repo.tagtype(n) != expectedtype:
4099 if repo.tagtype(n) != expectedtype:
4100 if expectedtype == 'global':
4100 if expectedtype == 'global':
4101 raise util.Abort(_("tag '%s' is not a global tag") % n)
4101 raise util.Abort(_("tag '%s' is not a global tag") % n)
4102 else:
4102 else:
4103 raise util.Abort(_("tag '%s' is not a local tag") % n)
4103 raise util.Abort(_("tag '%s' is not a local tag") % n)
4104 rev_ = nullid
4104 rev_ = nullid
4105 if not message:
4105 if not message:
4106 # we don't translate commit messages
4106 # we don't translate commit messages
4107 message = 'Removed tag %s' % ', '.join(names)
4107 message = 'Removed tag %s' % ', '.join(names)
4108 elif not opts.get('force'):
4108 elif not opts.get('force'):
4109 for n in names:
4109 for n in names:
4110 if n in repo.tags():
4110 if n in repo.tags():
4111 raise util.Abort(_("tag '%s' already exists "
4111 raise util.Abort(_("tag '%s' already exists "
4112 "(use -f to force)") % n)
4112 "(use -f to force)") % n)
4113 if not opts.get('local'):
4113 if not opts.get('local'):
4114 p1, p2 = repo.dirstate.parents()
4114 p1, p2 = repo.dirstate.parents()
4115 if p2 != nullid:
4115 if p2 != nullid:
4116 raise util.Abort(_('uncommitted merge'))
4116 raise util.Abort(_('uncommitted merge'))
4117 bheads = repo.branchheads()
4117 bheads = repo.branchheads()
4118 if not opts.get('force') and bheads and p1 not in bheads:
4118 if not opts.get('force') and bheads and p1 not in bheads:
4119 raise util.Abort(_('not at a branch head (use -f to force)'))
4119 raise util.Abort(_('not at a branch head (use -f to force)'))
4120 r = cmdutil.revsingle(repo, rev_).node()
4120 r = cmdutil.revsingle(repo, rev_).node()
4121
4121
4122 if not message:
4122 if not message:
4123 # we don't translate commit messages
4123 # we don't translate commit messages
4124 message = ('Added tag %s for changeset %s' %
4124 message = ('Added tag %s for changeset %s' %
4125 (', '.join(names), short(r)))
4125 (', '.join(names), short(r)))
4126
4126
4127 date = opts.get('date')
4127 date = opts.get('date')
4128 if date:
4128 if date:
4129 date = util.parsedate(date)
4129 date = util.parsedate(date)
4130
4130
4131 if opts.get('edit'):
4131 if opts.get('edit'):
4132 message = ui.edit(message, ui.username())
4132 message = ui.edit(message, ui.username())
4133
4133
4134 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
4134 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
4135
4135
4136 def tags(ui, repo):
4136 def tags(ui, repo):
4137 """list repository tags
4137 """list repository tags
4138
4138
4139 This lists both regular and local tags. When the -v/--verbose
4139 This lists both regular and local tags. When the -v/--verbose
4140 switch is used, a third column "local" is printed for local tags.
4140 switch is used, a third column "local" is printed for local tags.
4141
4141
4142 Returns 0 on success.
4142 Returns 0 on success.
4143 """
4143 """
4144
4144
4145 hexfunc = ui.debugflag and hex or short
4145 hexfunc = ui.debugflag and hex or short
4146 tagtype = ""
4146 tagtype = ""
4147
4147
4148 for t, n in reversed(repo.tagslist()):
4148 for t, n in reversed(repo.tagslist()):
4149 if ui.quiet:
4149 if ui.quiet:
4150 ui.write("%s\n" % t)
4150 ui.write("%s\n" % t)
4151 continue
4151 continue
4152
4152
4153 hn = hexfunc(n)
4153 hn = hexfunc(n)
4154 r = "%5d:%s" % (repo.changelog.rev(n), hn)
4154 r = "%5d:%s" % (repo.changelog.rev(n), hn)
4155 spaces = " " * (30 - encoding.colwidth(t))
4155 spaces = " " * (30 - encoding.colwidth(t))
4156
4156
4157 if ui.verbose:
4157 if ui.verbose:
4158 if repo.tagtype(t) == 'local':
4158 if repo.tagtype(t) == 'local':
4159 tagtype = " local"
4159 tagtype = " local"
4160 else:
4160 else:
4161 tagtype = ""
4161 tagtype = ""
4162 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
4162 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
4163
4163
4164 def tip(ui, repo, **opts):
4164 def tip(ui, repo, **opts):
4165 """show the tip revision
4165 """show the tip revision
4166
4166
4167 The tip revision (usually just called the tip) is the changeset
4167 The tip revision (usually just called the tip) is the changeset
4168 most recently added to the repository (and therefore the most
4168 most recently added to the repository (and therefore the most
4169 recently changed head).
4169 recently changed head).
4170
4170
4171 If you have just made a commit, that commit will be the tip. If
4171 If you have just made a commit, that commit will be the tip. If
4172 you have just pulled changes from another repository, the tip of
4172 you have just pulled changes from another repository, the tip of
4173 that repository becomes the current tip. The "tip" tag is special
4173 that repository becomes the current tip. The "tip" tag is special
4174 and cannot be renamed or assigned to a different changeset.
4174 and cannot be renamed or assigned to a different changeset.
4175
4175
4176 Returns 0 on success.
4176 Returns 0 on success.
4177 """
4177 """
4178 displayer = cmdutil.show_changeset(ui, repo, opts)
4178 displayer = cmdutil.show_changeset(ui, repo, opts)
4179 displayer.show(repo[len(repo) - 1])
4179 displayer.show(repo[len(repo) - 1])
4180 displayer.close()
4180 displayer.close()
4181
4181
4182 def unbundle(ui, repo, fname1, *fnames, **opts):
4182 def unbundle(ui, repo, fname1, *fnames, **opts):
4183 """apply one or more changegroup files
4183 """apply one or more changegroup files
4184
4184
4185 Apply one or more compressed changegroup files generated by the
4185 Apply one or more compressed changegroup files generated by the
4186 bundle command.
4186 bundle command.
4187
4187
4188 Returns 0 on success, 1 if an update has unresolved files.
4188 Returns 0 on success, 1 if an update has unresolved files.
4189 """
4189 """
4190 fnames = (fname1,) + fnames
4190 fnames = (fname1,) + fnames
4191
4191
4192 lock = repo.lock()
4192 lock = repo.lock()
4193 wc = repo['.']
4193 wc = repo['.']
4194 try:
4194 try:
4195 for fname in fnames:
4195 for fname in fnames:
4196 f = url.open(ui, fname)
4196 f = url.open(ui, fname)
4197 gen = changegroup.readbundle(f, fname)
4197 gen = changegroup.readbundle(f, fname)
4198 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname,
4198 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname,
4199 lock=lock)
4199 lock=lock)
4200 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
4200 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
4201 finally:
4201 finally:
4202 lock.release()
4202 lock.release()
4203 return postincoming(ui, repo, modheads, opts.get('update'), None)
4203 return postincoming(ui, repo, modheads, opts.get('update'), None)
4204
4204
4205 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
4205 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
4206 """update working directory (or switch revisions)
4206 """update working directory (or switch revisions)
4207
4207
4208 Update the repository's working directory to the specified
4208 Update the repository's working directory to the specified
4209 changeset. If no changeset is specified, update to the tip of the
4209 changeset. If no changeset is specified, update to the tip of the
4210 current named branch.
4210 current named branch.
4211
4211
4212 If the changeset is not a descendant of the working directory's
4212 If the changeset is not a descendant of the working directory's
4213 parent, the update is aborted. With the -c/--check option, the
4213 parent, the update is aborted. With the -c/--check option, the
4214 working directory is checked for uncommitted changes; if none are
4214 working directory is checked for uncommitted changes; if none are
4215 found, the working directory is updated to the specified
4215 found, the working directory is updated to the specified
4216 changeset.
4216 changeset.
4217
4217
4218 The following rules apply when the working directory contains
4218 The following rules apply when the working directory contains
4219 uncommitted changes:
4219 uncommitted changes:
4220
4220
4221 1. If neither -c/--check nor -C/--clean is specified, and if
4221 1. If neither -c/--check nor -C/--clean is specified, and if
4222 the requested changeset is an ancestor or descendant of
4222 the requested changeset is an ancestor or descendant of
4223 the working directory's parent, the uncommitted changes
4223 the working directory's parent, the uncommitted changes
4224 are merged into the requested changeset and the merged
4224 are merged into the requested changeset and the merged
4225 result is left uncommitted. If the requested changeset is
4225 result is left uncommitted. If the requested changeset is
4226 not an ancestor or descendant (that is, it is on another
4226 not an ancestor or descendant (that is, it is on another
4227 branch), the update is aborted and the uncommitted changes
4227 branch), the update is aborted and the uncommitted changes
4228 are preserved.
4228 are preserved.
4229
4229
4230 2. With the -c/--check option, the update is aborted and the
4230 2. With the -c/--check option, the update is aborted and the
4231 uncommitted changes are preserved.
4231 uncommitted changes are preserved.
4232
4232
4233 3. With the -C/--clean option, uncommitted changes are discarded and
4233 3. With the -C/--clean option, uncommitted changes are discarded and
4234 the working directory is updated to the requested changeset.
4234 the working directory is updated to the requested changeset.
4235
4235
4236 Use null as the changeset to remove the working directory (like
4236 Use null as the changeset to remove the working directory (like
4237 :hg:`clone -U`).
4237 :hg:`clone -U`).
4238
4238
4239 If you want to update just one file to an older changeset, use
4239 If you want to update just one file to an older changeset, use
4240 :hg:`revert`.
4240 :hg:`revert`.
4241
4241
4242 See :hg:`help dates` for a list of formats valid for -d/--date.
4242 See :hg:`help dates` for a list of formats valid for -d/--date.
4243
4243
4244 Returns 0 on success, 1 if there are unresolved files.
4244 Returns 0 on success, 1 if there are unresolved files.
4245 """
4245 """
4246 if rev and node:
4246 if rev and node:
4247 raise util.Abort(_("please specify just one revision"))
4247 raise util.Abort(_("please specify just one revision"))
4248
4248
4249 if rev is None or rev == '':
4249 if rev is None or rev == '':
4250 rev = node
4250 rev = node
4251
4251
4252 # if we defined a bookmark, we have to remember the original bookmark name
4252 # if we defined a bookmark, we have to remember the original bookmark name
4253 brev = rev
4253 brev = rev
4254 rev = cmdutil.revsingle(repo, rev, rev).rev()
4254 rev = cmdutil.revsingle(repo, rev, rev).rev()
4255
4255
4256 if check and clean:
4256 if check and clean:
4257 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
4257 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
4258
4258
4259 if check:
4259 if check:
4260 # we could use dirty() but we can ignore merge and branch trivia
4260 # we could use dirty() but we can ignore merge and branch trivia
4261 c = repo[None]
4261 c = repo[None]
4262 if c.modified() or c.added() or c.removed():
4262 if c.modified() or c.added() or c.removed():
4263 raise util.Abort(_("uncommitted local changes"))
4263 raise util.Abort(_("uncommitted local changes"))
4264
4264
4265 if date:
4265 if date:
4266 if rev is not None:
4266 if rev is not None:
4267 raise util.Abort(_("you can't specify a revision and a date"))
4267 raise util.Abort(_("you can't specify a revision and a date"))
4268 rev = cmdutil.finddate(ui, repo, date)
4268 rev = cmdutil.finddate(ui, repo, date)
4269
4269
4270 if clean or check:
4270 if clean or check:
4271 ret = hg.clean(repo, rev)
4271 ret = hg.clean(repo, rev)
4272 else:
4272 else:
4273 ret = hg.update(repo, rev)
4273 ret = hg.update(repo, rev)
4274
4274
4275 if brev in repo._bookmarks:
4275 if brev in repo._bookmarks:
4276 bookmarks.setcurrent(repo, brev)
4276 bookmarks.setcurrent(repo, brev)
4277
4277
4278 return ret
4278 return ret
4279
4279
4280 def verify(ui, repo):
4280 def verify(ui, repo):
4281 """verify the integrity of the repository
4281 """verify the integrity of the repository
4282
4282
4283 Verify the integrity of the current repository.
4283 Verify the integrity of the current repository.
4284
4284
4285 This will perform an extensive check of the repository's
4285 This will perform an extensive check of the repository's
4286 integrity, validating the hashes and checksums of each entry in
4286 integrity, validating the hashes and checksums of each entry in
4287 the changelog, manifest, and tracked files, as well as the
4287 the changelog, manifest, and tracked files, as well as the
4288 integrity of their crosslinks and indices.
4288 integrity of their crosslinks and indices.
4289
4289
4290 Returns 0 on success, 1 if errors are encountered.
4290 Returns 0 on success, 1 if errors are encountered.
4291 """
4291 """
4292 return hg.verify(repo)
4292 return hg.verify(repo)
4293
4293
4294 def version_(ui):
4294 def version_(ui):
4295 """output version and copyright information"""
4295 """output version and copyright information"""
4296 ui.write(_("Mercurial Distributed SCM (version %s)\n")
4296 ui.write(_("Mercurial Distributed SCM (version %s)\n")
4297 % util.version())
4297 % util.version())
4298 ui.status(_(
4298 ui.status(_(
4299 "(see http://mercurial.selenic.com for more information)\n"
4299 "(see http://mercurial.selenic.com for more information)\n"
4300 "\nCopyright (C) 2005-2011 Matt Mackall and others\n"
4300 "\nCopyright (C) 2005-2011 Matt Mackall and others\n"
4301 "This is free software; see the source for copying conditions. "
4301 "This is free software; see the source for copying conditions. "
4302 "There is NO\nwarranty; "
4302 "There is NO\nwarranty; "
4303 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
4303 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
4304 ))
4304 ))
4305
4305
4306 # Command options and aliases are listed here, alphabetically
4306 # Command options and aliases are listed here, alphabetically
4307
4307
4308 globalopts = [
4308 globalopts = [
4309 ('R', 'repository', '',
4309 ('R', 'repository', '',
4310 _('repository root directory or name of overlay bundle file'),
4310 _('repository root directory or name of overlay bundle file'),
4311 _('REPO')),
4311 _('REPO')),
4312 ('', 'cwd', '',
4312 ('', 'cwd', '',
4313 _('change working directory'), _('DIR')),
4313 _('change working directory'), _('DIR')),
4314 ('y', 'noninteractive', None,
4314 ('y', 'noninteractive', None,
4315 _('do not prompt, assume \'yes\' for any required answers')),
4315 _('do not prompt, assume \'yes\' for any required answers')),
4316 ('q', 'quiet', None, _('suppress output')),
4316 ('q', 'quiet', None, _('suppress output')),
4317 ('v', 'verbose', None, _('enable additional output')),
4317 ('v', 'verbose', None, _('enable additional output')),
4318 ('', 'config', [],
4318 ('', 'config', [],
4319 _('set/override config option (use \'section.name=value\')'),
4319 _('set/override config option (use \'section.name=value\')'),
4320 _('CONFIG')),
4320 _('CONFIG')),
4321 ('', 'debug', None, _('enable debugging output')),
4321 ('', 'debug', None, _('enable debugging output')),
4322 ('', 'debugger', None, _('start debugger')),
4322 ('', 'debugger', None, _('start debugger')),
4323 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
4323 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
4324 _('ENCODE')),
4324 _('ENCODE')),
4325 ('', 'encodingmode', encoding.encodingmode,
4325 ('', 'encodingmode', encoding.encodingmode,
4326 _('set the charset encoding mode'), _('MODE')),
4326 _('set the charset encoding mode'), _('MODE')),
4327 ('', 'traceback', None, _('always print a traceback on exception')),
4327 ('', 'traceback', None, _('always print a traceback on exception')),
4328 ('', 'time', None, _('time how long the command takes')),
4328 ('', 'time', None, _('time how long the command takes')),
4329 ('', 'profile', None, _('print command execution profile')),
4329 ('', 'profile', None, _('print command execution profile')),
4330 ('', 'version', None, _('output version information and exit')),
4330 ('', 'version', None, _('output version information and exit')),
4331 ('h', 'help', None, _('display help and exit')),
4331 ('h', 'help', None, _('display help and exit')),
4332 ]
4332 ]
4333
4333
4334 dryrunopts = [('n', 'dry-run', None,
4334 dryrunopts = [('n', 'dry-run', None,
4335 _('do not perform actions, just print output'))]
4335 _('do not perform actions, just print output'))]
4336
4336
4337 remoteopts = [
4337 remoteopts = [
4338 ('e', 'ssh', '',
4338 ('e', 'ssh', '',
4339 _('specify ssh command to use'), _('CMD')),
4339 _('specify ssh command to use'), _('CMD')),
4340 ('', 'remotecmd', '',
4340 ('', 'remotecmd', '',
4341 _('specify hg command to run on the remote side'), _('CMD')),
4341 _('specify hg command to run on the remote side'), _('CMD')),
4342 ('', 'insecure', None,
4342 ('', 'insecure', None,
4343 _('do not verify server certificate (ignoring web.cacerts config)')),
4343 _('do not verify server certificate (ignoring web.cacerts config)')),
4344 ]
4344 ]
4345
4345
4346 walkopts = [
4346 walkopts = [
4347 ('I', 'include', [],
4347 ('I', 'include', [],
4348 _('include names matching the given patterns'), _('PATTERN')),
4348 _('include names matching the given patterns'), _('PATTERN')),
4349 ('X', 'exclude', [],
4349 ('X', 'exclude', [],
4350 _('exclude names matching the given patterns'), _('PATTERN')),
4350 _('exclude names matching the given patterns'), _('PATTERN')),
4351 ]
4351 ]
4352
4352
4353 commitopts = [
4353 commitopts = [
4354 ('m', 'message', '',
4354 ('m', 'message', '',
4355 _('use text as commit message'), _('TEXT')),
4355 _('use text as commit message'), _('TEXT')),
4356 ('l', 'logfile', '',
4356 ('l', 'logfile', '',
4357 _('read commit message from file'), _('FILE')),
4357 _('read commit message from file'), _('FILE')),
4358 ]
4358 ]
4359
4359
4360 commitopts2 = [
4360 commitopts2 = [
4361 ('d', 'date', '',
4361 ('d', 'date', '',
4362 _('record the specified date as commit date'), _('DATE')),
4362 _('record the specified date as commit date'), _('DATE')),
4363 ('u', 'user', '',
4363 ('u', 'user', '',
4364 _('record the specified user as committer'), _('USER')),
4364 _('record the specified user as committer'), _('USER')),
4365 ]
4365 ]
4366
4366
4367 templateopts = [
4367 templateopts = [
4368 ('', 'style', '',
4368 ('', 'style', '',
4369 _('display using template map file'), _('STYLE')),
4369 _('display using template map file'), _('STYLE')),
4370 ('', 'template', '',
4370 ('', 'template', '',
4371 _('display with template'), _('TEMPLATE')),
4371 _('display with template'), _('TEMPLATE')),
4372 ]
4372 ]
4373
4373
4374 logopts = [
4374 logopts = [
4375 ('p', 'patch', None, _('show patch')),
4375 ('p', 'patch', None, _('show patch')),
4376 ('g', 'git', None, _('use git extended diff format')),
4376 ('g', 'git', None, _('use git extended diff format')),
4377 ('l', 'limit', '',
4377 ('l', 'limit', '',
4378 _('limit number of changes displayed'), _('NUM')),
4378 _('limit number of changes displayed'), _('NUM')),
4379 ('M', 'no-merges', None, _('do not show merges')),
4379 ('M', 'no-merges', None, _('do not show merges')),
4380 ('', 'stat', None, _('output diffstat-style summary of changes')),
4380 ('', 'stat', None, _('output diffstat-style summary of changes')),
4381 ] + templateopts
4381 ] + templateopts
4382
4382
4383 diffopts = [
4383 diffopts = [
4384 ('a', 'text', None, _('treat all files as text')),
4384 ('a', 'text', None, _('treat all files as text')),
4385 ('g', 'git', None, _('use git extended diff format')),
4385 ('g', 'git', None, _('use git extended diff format')),
4386 ('', 'nodates', None, _('omit dates from diff headers'))
4386 ('', 'nodates', None, _('omit dates from diff headers'))
4387 ]
4387 ]
4388
4388
4389 diffopts2 = [
4389 diffopts2 = [
4390 ('p', 'show-function', None, _('show which function each change is in')),
4390 ('p', 'show-function', None, _('show which function each change is in')),
4391 ('', 'reverse', None, _('produce a diff that undoes the changes')),
4391 ('', 'reverse', None, _('produce a diff that undoes the changes')),
4392 ('w', 'ignore-all-space', None,
4392 ('w', 'ignore-all-space', None,
4393 _('ignore white space when comparing lines')),
4393 _('ignore white space when comparing lines')),
4394 ('b', 'ignore-space-change', None,
4394 ('b', 'ignore-space-change', None,
4395 _('ignore changes in the amount of white space')),
4395 _('ignore changes in the amount of white space')),
4396 ('B', 'ignore-blank-lines', None,
4396 ('B', 'ignore-blank-lines', None,
4397 _('ignore changes whose lines are all blank')),
4397 _('ignore changes whose lines are all blank')),
4398 ('U', 'unified', '',
4398 ('U', 'unified', '',
4399 _('number of lines of context to show'), _('NUM')),
4399 _('number of lines of context to show'), _('NUM')),
4400 ('', 'stat', None, _('output diffstat-style summary of changes')),
4400 ('', 'stat', None, _('output diffstat-style summary of changes')),
4401 ]
4401 ]
4402
4402
4403 similarityopts = [
4403 similarityopts = [
4404 ('s', 'similarity', '',
4404 ('s', 'similarity', '',
4405 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
4405 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
4406 ]
4406 ]
4407
4407
4408 subrepoopts = [
4408 subrepoopts = [
4409 ('S', 'subrepos', None,
4409 ('S', 'subrepos', None,
4410 _('recurse into subrepositories'))
4410 _('recurse into subrepositories'))
4411 ]
4411 ]
4412
4412
4413 table = {
4413 table = {
4414 "^add": (add, walkopts + subrepoopts + dryrunopts,
4414 "^add": (add, walkopts + subrepoopts + dryrunopts,
4415 _('[OPTION]... [FILE]...')),
4415 _('[OPTION]... [FILE]...')),
4416 "addremove":
4416 "addremove":
4417 (addremove, similarityopts + walkopts + dryrunopts,
4417 (addremove, similarityopts + walkopts + dryrunopts,
4418 _('[OPTION]... [FILE]...')),
4418 _('[OPTION]... [FILE]...')),
4419 "^annotate|blame":
4419 "^annotate|blame":
4420 (annotate,
4420 (annotate,
4421 [('r', 'rev', '',
4421 [('r', 'rev', '',
4422 _('annotate the specified revision'), _('REV')),
4422 _('annotate the specified revision'), _('REV')),
4423 ('', 'follow', None,
4423 ('', 'follow', None,
4424 _('follow copies/renames and list the filename (DEPRECATED)')),
4424 _('follow copies/renames and list the filename (DEPRECATED)')),
4425 ('', 'no-follow', None, _("don't follow copies and renames")),
4425 ('', 'no-follow', None, _("don't follow copies and renames")),
4426 ('a', 'text', None, _('treat all files as text')),
4426 ('a', 'text', None, _('treat all files as text')),
4427 ('u', 'user', None, _('list the author (long with -v)')),
4427 ('u', 'user', None, _('list the author (long with -v)')),
4428 ('f', 'file', None, _('list the filename')),
4428 ('f', 'file', None, _('list the filename')),
4429 ('d', 'date', None, _('list the date (short with -q)')),
4429 ('d', 'date', None, _('list the date (short with -q)')),
4430 ('n', 'number', None, _('list the revision number (default)')),
4430 ('n', 'number', None, _('list the revision number (default)')),
4431 ('c', 'changeset', None, _('list the changeset')),
4431 ('c', 'changeset', None, _('list the changeset')),
4432 ('l', 'line-number', None,
4432 ('l', 'line-number', None,
4433 _('show line number at the first appearance'))
4433 _('show line number at the first appearance'))
4434 ] + walkopts,
4434 ] + walkopts,
4435 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
4435 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
4436 "archive":
4436 "archive":
4437 (archive,
4437 (archive,
4438 [('', 'no-decode', None, _('do not pass files through decoders')),
4438 [('', 'no-decode', None, _('do not pass files through decoders')),
4439 ('p', 'prefix', '',
4439 ('p', 'prefix', '',
4440 _('directory prefix for files in archive'), _('PREFIX')),
4440 _('directory prefix for files in archive'), _('PREFIX')),
4441 ('r', 'rev', '',
4441 ('r', 'rev', '',
4442 _('revision to distribute'), _('REV')),
4442 _('revision to distribute'), _('REV')),
4443 ('t', 'type', '',
4443 ('t', 'type', '',
4444 _('type of distribution to create'), _('TYPE')),
4444 _('type of distribution to create'), _('TYPE')),
4445 ] + subrepoopts + walkopts,
4445 ] + subrepoopts + walkopts,
4446 _('[OPTION]... DEST')),
4446 _('[OPTION]... DEST')),
4447 "backout":
4447 "backout":
4448 (backout,
4448 (backout,
4449 [('', 'merge', None,
4449 [('', 'merge', None,
4450 _('merge with old dirstate parent after backout')),
4450 _('merge with old dirstate parent after backout')),
4451 ('', 'parent', '',
4451 ('', 'parent', '',
4452 _('parent to choose when backing out merge'), _('REV')),
4452 _('parent to choose when backing out merge'), _('REV')),
4453 ('t', 'tool', '',
4453 ('t', 'tool', '',
4454 _('specify merge tool')),
4454 _('specify merge tool')),
4455 ('r', 'rev', '',
4455 ('r', 'rev', '',
4456 _('revision to backout'), _('REV')),
4456 _('revision to backout'), _('REV')),
4457 ] + walkopts + commitopts + commitopts2,
4457 ] + walkopts + commitopts + commitopts2,
4458 _('[OPTION]... [-r] REV')),
4458 _('[OPTION]... [-r] REV')),
4459 "bisect":
4459 "bisect":
4460 (bisect,
4460 (bisect,
4461 [('r', 'reset', False, _('reset bisect state')),
4461 [('r', 'reset', False, _('reset bisect state')),
4462 ('g', 'good', False, _('mark changeset good')),
4462 ('g', 'good', False, _('mark changeset good')),
4463 ('b', 'bad', False, _('mark changeset bad')),
4463 ('b', 'bad', False, _('mark changeset bad')),
4464 ('s', 'skip', False, _('skip testing changeset')),
4464 ('s', 'skip', False, _('skip testing changeset')),
4465 ('e', 'extend', False, _('extend the bisect range')),
4465 ('e', 'extend', False, _('extend the bisect range')),
4466 ('c', 'command', '',
4466 ('c', 'command', '',
4467 _('use command to check changeset state'), _('CMD')),
4467 _('use command to check changeset state'), _('CMD')),
4468 ('U', 'noupdate', False, _('do not update to target'))],
4468 ('U', 'noupdate', False, _('do not update to target'))],
4469 _("[-gbsr] [-U] [-c CMD] [REV]")),
4469 _("[-gbsr] [-U] [-c CMD] [REV]")),
4470 "bookmarks":
4470 "bookmarks":
4471 (bookmark,
4471 (bookmark,
4472 [('f', 'force', False, _('force')),
4472 [('f', 'force', False, _('force')),
4473 ('r', 'rev', '', _('revision'), _('REV')),
4473 ('r', 'rev', '', _('revision'), _('REV')),
4474 ('d', 'delete', False, _('delete a given bookmark')),
4474 ('d', 'delete', False, _('delete a given bookmark')),
4475 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
4475 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
4476 ('i', 'inactive', False, _('do not mark a new bookmark active'))],
4476 ('i', 'inactive', False, _('do not mark a new bookmark active'))],
4477 _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]')),
4477 _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]')),
4478 "branch":
4478 "branch":
4479 (branch,
4479 (branch,
4480 [('f', 'force', None,
4480 [('f', 'force', None,
4481 _('set branch name even if it shadows an existing branch')),
4481 _('set branch name even if it shadows an existing branch')),
4482 ('C', 'clean', None, _('reset branch name to parent branch name'))],
4482 ('C', 'clean', None, _('reset branch name to parent branch name'))],
4483 _('[-fC] [NAME]')),
4483 _('[-fC] [NAME]')),
4484 "branches":
4484 "branches":
4485 (branches,
4485 (branches,
4486 [('a', 'active', False,
4486 [('a', 'active', False,
4487 _('show only branches that have unmerged heads')),
4487 _('show only branches that have unmerged heads')),
4488 ('c', 'closed', False,
4488 ('c', 'closed', False,
4489 _('show normal and closed branches'))],
4489 _('show normal and closed branches'))],
4490 _('[-ac]')),
4490 _('[-ac]')),
4491 "bundle":
4491 "bundle":
4492 (bundle,
4492 (bundle,
4493 [('f', 'force', None,
4493 [('f', 'force', None,
4494 _('run even when the destination is unrelated')),
4494 _('run even when the destination is unrelated')),
4495 ('r', 'rev', [],
4495 ('r', 'rev', [],
4496 _('a changeset intended to be added to the destination'),
4496 _('a changeset intended to be added to the destination'),
4497 _('REV')),
4497 _('REV')),
4498 ('b', 'branch', [],
4498 ('b', 'branch', [],
4499 _('a specific branch you would like to bundle'),
4499 _('a specific branch you would like to bundle'),
4500 _('BRANCH')),
4500 _('BRANCH')),
4501 ('', 'base', [],
4501 ('', 'base', [],
4502 _('a base changeset assumed to be available at the destination'),
4502 _('a base changeset assumed to be available at the destination'),
4503 _('REV')),
4503 _('REV')),
4504 ('a', 'all', None, _('bundle all changesets in the repository')),
4504 ('a', 'all', None, _('bundle all changesets in the repository')),
4505 ('t', 'type', 'bzip2',
4505 ('t', 'type', 'bzip2',
4506 _('bundle compression type to use'), _('TYPE')),
4506 _('bundle compression type to use'), _('TYPE')),
4507 ] + remoteopts,
4507 ] + remoteopts,
4508 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
4508 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
4509 "cat":
4509 "cat":
4510 (cat,
4510 (cat,
4511 [('o', 'output', '',
4511 [('o', 'output', '',
4512 _('print output to file with formatted name'), _('FORMAT')),
4512 _('print output to file with formatted name'), _('FORMAT')),
4513 ('r', 'rev', '',
4513 ('r', 'rev', '',
4514 _('print the given revision'), _('REV')),
4514 _('print the given revision'), _('REV')),
4515 ('', 'decode', None, _('apply any matching decode filter')),
4515 ('', 'decode', None, _('apply any matching decode filter')),
4516 ] + walkopts,
4516 ] + walkopts,
4517 _('[OPTION]... FILE...')),
4517 _('[OPTION]... FILE...')),
4518 "^clone":
4518 "^clone":
4519 (clone,
4519 (clone,
4520 [('U', 'noupdate', None,
4520 [('U', 'noupdate', None,
4521 _('the clone will include an empty working copy (only a repository)')),
4521 _('the clone will include an empty working copy (only a repository)')),
4522 ('u', 'updaterev', '',
4522 ('u', 'updaterev', '',
4523 _('revision, tag or branch to check out'), _('REV')),
4523 _('revision, tag or branch to check out'), _('REV')),
4524 ('r', 'rev', [],
4524 ('r', 'rev', [],
4525 _('include the specified changeset'), _('REV')),
4525 _('include the specified changeset'), _('REV')),
4526 ('b', 'branch', [],
4526 ('b', 'branch', [],
4527 _('clone only the specified branch'), _('BRANCH')),
4527 _('clone only the specified branch'), _('BRANCH')),
4528 ('', 'pull', None, _('use pull protocol to copy metadata')),
4528 ('', 'pull', None, _('use pull protocol to copy metadata')),
4529 ('', 'uncompressed', None,
4529 ('', 'uncompressed', None,
4530 _('use uncompressed transfer (fast over LAN)')),
4530 _('use uncompressed transfer (fast over LAN)')),
4531 ] + remoteopts,
4531 ] + remoteopts,
4532 _('[OPTION]... SOURCE [DEST]')),
4532 _('[OPTION]... SOURCE [DEST]')),
4533 "^commit|ci":
4533 "^commit|ci":
4534 (commit,
4534 (commit,
4535 [('A', 'addremove', None,
4535 [('A', 'addremove', None,
4536 _('mark new/missing files as added/removed before committing')),
4536 _('mark new/missing files as added/removed before committing')),
4537 ('', 'close-branch', None,
4537 ('', 'close-branch', None,
4538 _('mark a branch as closed, hiding it from the branch list')),
4538 _('mark a branch as closed, hiding it from the branch list')),
4539 ] + walkopts + commitopts + commitopts2,
4539 ] + walkopts + commitopts + commitopts2,
4540 _('[OPTION]... [FILE]...')),
4540 _('[OPTION]... [FILE]...')),
4541 "copy|cp":
4541 "copy|cp":
4542 (copy,
4542 (copy,
4543 [('A', 'after', None, _('record a copy that has already occurred')),
4543 [('A', 'after', None, _('record a copy that has already occurred')),
4544 ('f', 'force', None,
4544 ('f', 'force', None,
4545 _('forcibly copy over an existing managed file')),
4545 _('forcibly copy over an existing managed file')),
4546 ] + walkopts + dryrunopts,
4546 ] + walkopts + dryrunopts,
4547 _('[OPTION]... [SOURCE]... DEST')),
4547 _('[OPTION]... [SOURCE]... DEST')),
4548 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
4548 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
4549 "debugbuilddag":
4549 "debugbuilddag":
4550 (debugbuilddag,
4550 (debugbuilddag,
4551 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
4551 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
4552 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
4552 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
4553 ('n', 'new-file', None, _('add new file at each rev')),
4553 ('n', 'new-file', None, _('add new file at each rev')),
4554 ],
4554 ],
4555 _('[OPTION]... TEXT')),
4555 _('[OPTION]... TEXT')),
4556 "debugbundle":
4556 "debugbundle":
4557 (debugbundle,
4557 (debugbundle,
4558 [('a', 'all', None, _('show all details')),
4558 [('a', 'all', None, _('show all details')),
4559 ],
4559 ],
4560 _('FILE')),
4560 _('FILE')),
4561 "debugcheckstate": (debugcheckstate, [], ''),
4561 "debugcheckstate": (debugcheckstate, [], ''),
4562 "debugcommands": (debugcommands, [], _('[COMMAND]')),
4562 "debugcommands": (debugcommands, [], _('[COMMAND]')),
4563 "debugcomplete":
4563 "debugcomplete":
4564 (debugcomplete,
4564 (debugcomplete,
4565 [('o', 'options', None, _('show the command options'))],
4565 [('o', 'options', None, _('show the command options'))],
4566 _('[-o] CMD')),
4566 _('[-o] CMD')),
4567 "debugdag":
4567 "debugdag":
4568 (debugdag,
4568 (debugdag,
4569 [('t', 'tags', None, _('use tags as labels')),
4569 [('t', 'tags', None, _('use tags as labels')),
4570 ('b', 'branches', None, _('annotate with branch names')),
4570 ('b', 'branches', None, _('annotate with branch names')),
4571 ('', 'dots', None, _('use dots for runs')),
4571 ('', 'dots', None, _('use dots for runs')),
4572 ('s', 'spaces', None, _('separate elements by spaces')),
4572 ('s', 'spaces', None, _('separate elements by spaces')),
4573 ],
4573 ],
4574 _('[OPTION]... [FILE [REV]...]')),
4574 _('[OPTION]... [FILE [REV]...]')),
4575 "debugdate":
4575 "debugdate":
4576 (debugdate,
4576 (debugdate,
4577 [('e', 'extended', None, _('try extended date formats'))],
4577 [('e', 'extended', None, _('try extended date formats'))],
4578 _('[-e] DATE [RANGE]')),
4578 _('[-e] DATE [RANGE]')),
4579 "debugdata": (debugdata, [], _('FILE REV')),
4579 "debugdata": (debugdata, [], _('FILE REV')),
4580 "debugdiscovery": (debugdiscovery,
4580 "debugdiscovery": (debugdiscovery,
4581 [('', 'old', None,
4581 [('', 'old', None,
4582 _('use old-style discovery')),
4582 _('use old-style discovery')),
4583 ('', 'nonheads', None,
4583 ('', 'nonheads', None,
4584 _('use old-style discovery with non-heads included')),
4584 _('use old-style discovery with non-heads included')),
4585 ] + remoteopts,
4585 ] + remoteopts,
4586 _('[-l REV] [-r REV] [-b BRANCH]...'
4586 _('[-l REV] [-r REV] [-b BRANCH]...'
4587 ' [OTHER]')),
4587 ' [OTHER]')),
4588 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
4588 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
4589 "debuggetbundle":
4589 "debuggetbundle":
4590 (debuggetbundle,
4590 (debuggetbundle,
4591 [('H', 'head', [], _('id of head node'), _('ID')),
4591 [('H', 'head', [], _('id of head node'), _('ID')),
4592 ('C', 'common', [], _('id of common node'), _('ID')),
4592 ('C', 'common', [], _('id of common node'), _('ID')),
4593 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
4593 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
4594 ],
4594 ],
4595 _('REPO FILE [-H|-C ID]...')),
4595 _('REPO FILE [-H|-C ID]...')),
4596 "debugignore": (debugignore, [], ''),
4596 "debugignore": (debugignore, [], ''),
4597 "debugindex": (debugindex,
4597 "debugindex": (debugindex,
4598 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
4598 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
4599 _('FILE')),
4599 _('FILE')),
4600 "debugindexdot": (debugindexdot, [], _('FILE')),
4600 "debugindexdot": (debugindexdot, [], _('FILE')),
4601 "debuginstall": (debuginstall, [], ''),
4601 "debuginstall": (debuginstall, [], ''),
4602 "debugknown": (debugknown, [], _('REPO ID...')),
4602 "debugknown": (debugknown, [], _('REPO ID...')),
4603 "debugpushkey": (debugpushkey, [], _('REPO NAMESPACE [KEY OLD NEW]')),
4603 "debugpushkey": (debugpushkey, [], _('REPO NAMESPACE [KEY OLD NEW]')),
4604 "debugrebuildstate":
4604 "debugrebuildstate":
4605 (debugrebuildstate,
4605 (debugrebuildstate,
4606 [('r', 'rev', '',
4606 [('r', 'rev', '',
4607 _('revision to rebuild to'), _('REV'))],
4607 _('revision to rebuild to'), _('REV'))],
4608 _('[-r REV] [REV]')),
4608 _('[-r REV] [REV]')),
4609 "debugrename":
4609 "debugrename":
4610 (debugrename,
4610 (debugrename,
4611 [('r', 'rev', '',
4611 [('r', 'rev', '',
4612 _('revision to debug'), _('REV'))],
4612 _('revision to debug'), _('REV'))],
4613 _('[-r REV] FILE')),
4613 _('[-r REV] FILE')),
4614 "debugrevspec":
4614 "debugrevspec":
4615 (debugrevspec, [], ('REVSPEC')),
4615 (debugrevspec, [], ('REVSPEC')),
4616 "debugsetparents":
4616 "debugsetparents":
4617 (debugsetparents, [], _('REV1 [REV2]')),
4617 (debugsetparents, [], _('REV1 [REV2]')),
4618 "debugstate":
4618 "debugstate":
4619 (debugstate,
4619 (debugstate,
4620 [('', 'nodates', None, _('do not display the saved mtime')),
4620 [('', 'nodates', None, _('do not display the saved mtime')),
4621 ('', 'datesort', None, _('sort by saved mtime'))],
4621 ('', 'datesort', None, _('sort by saved mtime'))],
4622 _('[OPTION]...')),
4622 _('[OPTION]...')),
4623 "debugsub":
4623 "debugsub":
4624 (debugsub,
4624 (debugsub,
4625 [('r', 'rev', '',
4625 [('r', 'rev', '',
4626 _('revision to check'), _('REV'))],
4626 _('revision to check'), _('REV'))],
4627 _('[-r REV] [REV]')),
4627 _('[-r REV] [REV]')),
4628 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
4628 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
4629 "debugwireargs":
4629 "debugwireargs":
4630 (debugwireargs,
4630 (debugwireargs,
4631 [('', 'three', '', 'three'),
4631 [('', 'three', '', 'three'),
4632 ('', 'four', '', 'four'),
4632 ('', 'four', '', 'four'),
4633 ('', 'five', '', 'five'),
4633 ('', 'five', '', 'five'),
4634 ] + remoteopts,
4634 ] + remoteopts,
4635 _('REPO [OPTIONS]... [ONE [TWO]]')),
4635 _('REPO [OPTIONS]... [ONE [TWO]]')),
4636 "^diff":
4636 "^diff":
4637 (diff,
4637 (diff,
4638 [('r', 'rev', [],
4638 [('r', 'rev', [],
4639 _('revision'), _('REV')),
4639 _('revision'), _('REV')),
4640 ('c', 'change', '',
4640 ('c', 'change', '',
4641 _('change made by revision'), _('REV'))
4641 _('change made by revision'), _('REV'))
4642 ] + diffopts + diffopts2 + walkopts + subrepoopts,
4642 ] + diffopts + diffopts2 + walkopts + subrepoopts,
4643 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...')),
4643 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...')),
4644 "^export":
4644 "^export":
4645 (export,
4645 (export,
4646 [('o', 'output', '',
4646 [('o', 'output', '',
4647 _('print output to file with formatted name'), _('FORMAT')),
4647 _('print output to file with formatted name'), _('FORMAT')),
4648 ('', 'switch-parent', None, _('diff against the second parent')),
4648 ('', 'switch-parent', None, _('diff against the second parent')),
4649 ('r', 'rev', [],
4649 ('r', 'rev', [],
4650 _('revisions to export'), _('REV')),
4650 _('revisions to export'), _('REV')),
4651 ] + diffopts,
4651 ] + diffopts,
4652 _('[OPTION]... [-o OUTFILESPEC] REV...')),
4652 _('[OPTION]... [-o OUTFILESPEC] REV...')),
4653 "^forget":
4653 "^forget":
4654 (forget,
4654 (forget,
4655 [] + walkopts,
4655 [] + walkopts,
4656 _('[OPTION]... FILE...')),
4656 _('[OPTION]... FILE...')),
4657 "grep":
4657 "grep":
4658 (grep,
4658 (grep,
4659 [('0', 'print0', None, _('end fields with NUL')),
4659 [('0', 'print0', None, _('end fields with NUL')),
4660 ('', 'all', None, _('print all revisions that match')),
4660 ('', 'all', None, _('print all revisions that match')),
4661 ('a', 'text', None, _('treat all files as text')),
4661 ('a', 'text', None, _('treat all files as text')),
4662 ('f', 'follow', None,
4662 ('f', 'follow', None,
4663 _('follow changeset history,'
4663 _('follow changeset history,'
4664 ' or file history across copies and renames')),
4664 ' or file history across copies and renames')),
4665 ('i', 'ignore-case', None, _('ignore case when matching')),
4665 ('i', 'ignore-case', None, _('ignore case when matching')),
4666 ('l', 'files-with-matches', None,
4666 ('l', 'files-with-matches', None,
4667 _('print only filenames and revisions that match')),
4667 _('print only filenames and revisions that match')),
4668 ('n', 'line-number', None, _('print matching line numbers')),
4668 ('n', 'line-number', None, _('print matching line numbers')),
4669 ('r', 'rev', [],
4669 ('r', 'rev', [],
4670 _('only search files changed within revision range'), _('REV')),
4670 _('only search files changed within revision range'), _('REV')),
4671 ('u', 'user', None, _('list the author (long with -v)')),
4671 ('u', 'user', None, _('list the author (long with -v)')),
4672 ('d', 'date', None, _('list the date (short with -q)')),
4672 ('d', 'date', None, _('list the date (short with -q)')),
4673 ] + walkopts,
4673 ] + walkopts,
4674 _('[OPTION]... PATTERN [FILE]...')),
4674 _('[OPTION]... PATTERN [FILE]...')),
4675 "heads":
4675 "heads":
4676 (heads,
4676 (heads,
4677 [('r', 'rev', '',
4677 [('r', 'rev', '',
4678 _('show only heads which are descendants of STARTREV'),
4678 _('show only heads which are descendants of STARTREV'),
4679 _('STARTREV')),
4679 _('STARTREV')),
4680 ('t', 'topo', False, _('show topological heads only')),
4680 ('t', 'topo', False, _('show topological heads only')),
4681 ('a', 'active', False,
4681 ('a', 'active', False,
4682 _('show active branchheads only (DEPRECATED)')),
4682 _('show active branchheads only (DEPRECATED)')),
4683 ('c', 'closed', False,
4683 ('c', 'closed', False,
4684 _('show normal and closed branch heads')),
4684 _('show normal and closed branch heads')),
4685 ] + templateopts,
4685 ] + templateopts,
4686 _('[-ac] [-r STARTREV] [REV]...')),
4686 _('[-ac] [-r STARTREV] [REV]...')),
4687 "help": (help_, [], _('[TOPIC]')),
4687 "help": (help_, [], _('[TOPIC]')),
4688 "identify|id":
4688 "identify|id":
4689 (identify,
4689 (identify,
4690 [('r', 'rev', '',
4690 [('r', 'rev', '',
4691 _('identify the specified revision'), _('REV')),
4691 _('identify the specified revision'), _('REV')),
4692 ('n', 'num', None, _('show local revision number')),
4692 ('n', 'num', None, _('show local revision number')),
4693 ('i', 'id', None, _('show global revision id')),
4693 ('i', 'id', None, _('show global revision id')),
4694 ('b', 'branch', None, _('show branch')),
4694 ('b', 'branch', None, _('show branch')),
4695 ('t', 'tags', None, _('show tags')),
4695 ('t', 'tags', None, _('show tags')),
4696 ('B', 'bookmarks', None, _('show bookmarks'))],
4696 ('B', 'bookmarks', None, _('show bookmarks'))],
4697 _('[-nibtB] [-r REV] [SOURCE]')),
4697 _('[-nibtB] [-r REV] [SOURCE]')),
4698 "import|patch":
4698 "import|patch":
4699 (import_,
4699 (import_,
4700 [('p', 'strip', 1,
4700 [('p', 'strip', 1,
4701 _('directory strip option for patch. This has the same '
4701 _('directory strip option for patch. This has the same '
4702 'meaning as the corresponding patch option'),
4702 'meaning as the corresponding patch option'),
4703 _('NUM')),
4703 _('NUM')),
4704 ('b', 'base', '',
4704 ('b', 'base', '',
4705 _('base path'), _('PATH')),
4705 _('base path'), _('PATH')),
4706 ('f', 'force', None,
4706 ('f', 'force', None,
4707 _('skip check for outstanding uncommitted changes')),
4707 _('skip check for outstanding uncommitted changes')),
4708 ('', 'no-commit', None,
4708 ('', 'no-commit', None,
4709 _("don't commit, just update the working directory")),
4709 _("don't commit, just update the working directory")),
4710 ('', 'exact', None,
4710 ('', 'exact', None,
4711 _('apply patch to the nodes from which it was generated')),
4711 _('apply patch to the nodes from which it was generated')),
4712 ('', 'import-branch', None,
4712 ('', 'import-branch', None,
4713 _('use any branch information in patch (implied by --exact)'))] +
4713 _('use any branch information in patch (implied by --exact)'))] +
4714 commitopts + commitopts2 + similarityopts,
4714 commitopts + commitopts2 + similarityopts,
4715 _('[OPTION]... PATCH...')),
4715 _('[OPTION]... PATCH...')),
4716 "incoming|in":
4716 "incoming|in":
4717 (incoming,
4717 (incoming,
4718 [('f', 'force', None,
4718 [('f', 'force', None,
4719 _('run even if remote repository is unrelated')),
4719 _('run even if remote repository is unrelated')),
4720 ('n', 'newest-first', None, _('show newest record first')),
4720 ('n', 'newest-first', None, _('show newest record first')),
4721 ('', 'bundle', '',
4721 ('', 'bundle', '',
4722 _('file to store the bundles into'), _('FILE')),
4722 _('file to store the bundles into'), _('FILE')),
4723 ('r', 'rev', [],
4723 ('r', 'rev', [],
4724 _('a remote changeset intended to be added'), _('REV')),
4724 _('a remote changeset intended to be added'), _('REV')),
4725 ('B', 'bookmarks', False, _("compare bookmarks")),
4725 ('B', 'bookmarks', False, _("compare bookmarks")),
4726 ('b', 'branch', [],
4726 ('b', 'branch', [],
4727 _('a specific branch you would like to pull'), _('BRANCH')),
4727 _('a specific branch you would like to pull'), _('BRANCH')),
4728 ] + logopts + remoteopts + subrepoopts,
4728 ] + logopts + remoteopts + subrepoopts,
4729 _('[-p] [-n] [-M] [-f] [-r REV]...'
4729 _('[-p] [-n] [-M] [-f] [-r REV]...'
4730 ' [--bundle FILENAME] [SOURCE]')),
4730 ' [--bundle FILENAME] [SOURCE]')),
4731 "^init":
4731 "^init":
4732 (init,
4732 (init,
4733 remoteopts,
4733 remoteopts,
4734 _('[-e CMD] [--remotecmd CMD] [DEST]')),
4734 _('[-e CMD] [--remotecmd CMD] [DEST]')),
4735 "locate":
4735 "locate":
4736 (locate,
4736 (locate,
4737 [('r', 'rev', '',
4737 [('r', 'rev', '',
4738 _('search the repository as it is in REV'), _('REV')),
4738 _('search the repository as it is in REV'), _('REV')),
4739 ('0', 'print0', None,
4739 ('0', 'print0', None,
4740 _('end filenames with NUL, for use with xargs')),
4740 _('end filenames with NUL, for use with xargs')),
4741 ('f', 'fullpath', None,
4741 ('f', 'fullpath', None,
4742 _('print complete paths from the filesystem root')),
4742 _('print complete paths from the filesystem root')),
4743 ] + walkopts,
4743 ] + walkopts,
4744 _('[OPTION]... [PATTERN]...')),
4744 _('[OPTION]... [PATTERN]...')),
4745 "^log|history":
4745 "^log|history":
4746 (log,
4746 (log,
4747 [('f', 'follow', None,
4747 [('f', 'follow', None,
4748 _('follow changeset history,'
4748 _('follow changeset history,'
4749 ' or file history across copies and renames')),
4749 ' or file history across copies and renames')),
4750 ('', 'follow-first', None,
4750 ('', 'follow-first', None,
4751 _('only follow the first parent of merge changesets')),
4751 _('only follow the first parent of merge changesets')),
4752 ('d', 'date', '',
4752 ('d', 'date', '',
4753 _('show revisions matching date spec'), _('DATE')),
4753 _('show revisions matching date spec'), _('DATE')),
4754 ('C', 'copies', None, _('show copied files')),
4754 ('C', 'copies', None, _('show copied files')),
4755 ('k', 'keyword', [],
4755 ('k', 'keyword', [],
4756 _('do case-insensitive search for a given text'), _('TEXT')),
4756 _('do case-insensitive search for a given text'), _('TEXT')),
4757 ('r', 'rev', [],
4757 ('r', 'rev', [],
4758 _('show the specified revision or range'), _('REV')),
4758 _('show the specified revision or range'), _('REV')),
4759 ('', 'removed', None, _('include revisions where files were removed')),
4759 ('', 'removed', None, _('include revisions where files were removed')),
4760 ('m', 'only-merges', None, _('show only merges')),
4760 ('m', 'only-merges', None, _('show only merges')),
4761 ('u', 'user', [],
4761 ('u', 'user', [],
4762 _('revisions committed by user'), _('USER')),
4762 _('revisions committed by user'), _('USER')),
4763 ('', 'only-branch', [],
4763 ('', 'only-branch', [],
4764 _('show only changesets within the given named branch (DEPRECATED)'),
4764 _('show only changesets within the given named branch (DEPRECATED)'),
4765 _('BRANCH')),
4765 _('BRANCH')),
4766 ('b', 'branch', [],
4766 ('b', 'branch', [],
4767 _('show changesets within the given named branch'), _('BRANCH')),
4767 _('show changesets within the given named branch'), _('BRANCH')),
4768 ('P', 'prune', [],
4768 ('P', 'prune', [],
4769 _('do not display revision or any of its ancestors'), _('REV')),
4769 _('do not display revision or any of its ancestors'), _('REV')),
4770 ] + logopts + walkopts,
4770 ] + logopts + walkopts,
4771 _('[OPTION]... [FILE]')),
4771 _('[OPTION]... [FILE]')),
4772 "manifest":
4772 "manifest":
4773 (manifest,
4773 (manifest,
4774 [('r', 'rev', '',
4774 [('r', 'rev', '',
4775 _('revision to display'), _('REV'))],
4775 _('revision to display'), _('REV'))],
4776 _('[-r REV]')),
4776 _('[-r REV]')),
4777 "^merge":
4777 "^merge":
4778 (merge,
4778 (merge,
4779 [('f', 'force', None, _('force a merge with outstanding changes')),
4779 [('f', 'force', None, _('force a merge with outstanding changes')),
4780 ('t', 'tool', '', _('specify merge tool')),
4780 ('t', 'tool', '', _('specify merge tool')),
4781 ('r', 'rev', '',
4781 ('r', 'rev', '',
4782 _('revision to merge'), _('REV')),
4782 _('revision to merge'), _('REV')),
4783 ('P', 'preview', None,
4783 ('P', 'preview', None,
4784 _('review revisions to merge (no merge is performed)'))],
4784 _('review revisions to merge (no merge is performed)'))],
4785 _('[-P] [-f] [[-r] REV]')),
4785 _('[-P] [-f] [[-r] REV]')),
4786 "outgoing|out":
4786 "outgoing|out":
4787 (outgoing,
4787 (outgoing,
4788 [('f', 'force', None,
4788 [('f', 'force', None,
4789 _('run even when the destination is unrelated')),
4789 _('run even when the destination is unrelated')),
4790 ('r', 'rev', [],
4790 ('r', 'rev', [],
4791 _('a changeset intended to be included in the destination'),
4791 _('a changeset intended to be included in the destination'),
4792 _('REV')),
4792 _('REV')),
4793 ('n', 'newest-first', None, _('show newest record first')),
4793 ('n', 'newest-first', None, _('show newest record first')),
4794 ('B', 'bookmarks', False, _("compare bookmarks")),
4794 ('B', 'bookmarks', False, _("compare bookmarks")),
4795 ('b', 'branch', [],
4795 ('b', 'branch', [],
4796 _('a specific branch you would like to push'), _('BRANCH')),
4796 _('a specific branch you would like to push'), _('BRANCH')),
4797 ] + logopts + remoteopts + subrepoopts,
4797 ] + logopts + remoteopts + subrepoopts,
4798 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
4798 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
4799 "parents":
4799 "parents":
4800 (parents,
4800 (parents,
4801 [('r', 'rev', '',
4801 [('r', 'rev', '',
4802 _('show parents of the specified revision'), _('REV')),
4802 _('show parents of the specified revision'), _('REV')),
4803 ] + templateopts,
4803 ] + templateopts,
4804 _('[-r REV] [FILE]')),
4804 _('[-r REV] [FILE]')),
4805 "paths": (paths, [], _('[NAME]')),
4805 "paths": (paths, [], _('[NAME]')),
4806 "^pull":
4806 "^pull":
4807 (pull,
4807 (pull,
4808 [('u', 'update', None,
4808 [('u', 'update', None,
4809 _('update to new branch head if changesets were pulled')),
4809 _('update to new branch head if changesets were pulled')),
4810 ('f', 'force', None,
4810 ('f', 'force', None,
4811 _('run even when remote repository is unrelated')),
4811 _('run even when remote repository is unrelated')),
4812 ('r', 'rev', [],
4812 ('r', 'rev', [],
4813 _('a remote changeset intended to be added'), _('REV')),
4813 _('a remote changeset intended to be added'), _('REV')),
4814 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4814 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4815 ('b', 'branch', [],
4815 ('b', 'branch', [],
4816 _('a specific branch you would like to pull'), _('BRANCH')),
4816 _('a specific branch you would like to pull'), _('BRANCH')),
4817 ] + remoteopts,
4817 ] + remoteopts,
4818 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
4818 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
4819 "^push":
4819 "^push":
4820 (push,
4820 (push,
4821 [('f', 'force', None, _('force push')),
4821 [('f', 'force', None, _('force push')),
4822 ('r', 'rev', [],
4822 ('r', 'rev', [],
4823 _('a changeset intended to be included in the destination'),
4823 _('a changeset intended to be included in the destination'),
4824 _('REV')),
4824 _('REV')),
4825 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4825 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4826 ('b', 'branch', [],
4826 ('b', 'branch', [],
4827 _('a specific branch you would like to push'), _('BRANCH')),
4827 _('a specific branch you would like to push'), _('BRANCH')),
4828 ('', 'new-branch', False, _('allow pushing a new branch')),
4828 ('', 'new-branch', False, _('allow pushing a new branch')),
4829 ] + remoteopts,
4829 ] + remoteopts,
4830 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
4830 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
4831 "recover": (recover, []),
4831 "recover": (recover, []),
4832 "^remove|rm":
4832 "^remove|rm":
4833 (remove,
4833 (remove,
4834 [('A', 'after', None, _('record delete for missing files')),
4834 [('A', 'after', None, _('record delete for missing files')),
4835 ('f', 'force', None,
4835 ('f', 'force', None,
4836 _('remove (and delete) file even if added or modified')),
4836 _('remove (and delete) file even if added or modified')),
4837 ] + walkopts,
4837 ] + walkopts,
4838 _('[OPTION]... FILE...')),
4838 _('[OPTION]... FILE...')),
4839 "rename|move|mv":
4839 "rename|move|mv":
4840 (rename,
4840 (rename,
4841 [('A', 'after', None, _('record a rename that has already occurred')),
4841 [('A', 'after', None, _('record a rename that has already occurred')),
4842 ('f', 'force', None,
4842 ('f', 'force', None,
4843 _('forcibly copy over an existing managed file')),
4843 _('forcibly copy over an existing managed file')),
4844 ] + walkopts + dryrunopts,
4844 ] + walkopts + dryrunopts,
4845 _('[OPTION]... SOURCE... DEST')),
4845 _('[OPTION]... SOURCE... DEST')),
4846 "resolve":
4846 "resolve":
4847 (resolve,
4847 (resolve,
4848 [('a', 'all', None, _('select all unresolved files')),
4848 [('a', 'all', None, _('select all unresolved files')),
4849 ('l', 'list', None, _('list state of files needing merge')),
4849 ('l', 'list', None, _('list state of files needing merge')),
4850 ('m', 'mark', None, _('mark files as resolved')),
4850 ('m', 'mark', None, _('mark files as resolved')),
4851 ('u', 'unmark', None, _('mark files as unresolved')),
4851 ('u', 'unmark', None, _('mark files as unresolved')),
4852 ('t', 'tool', '', _('specify merge tool')),
4852 ('t', 'tool', '', _('specify merge tool')),
4853 ('n', 'no-status', None, _('hide status prefix'))]
4853 ('n', 'no-status', None, _('hide status prefix'))]
4854 + walkopts,
4854 + walkopts,
4855 _('[OPTION]... [FILE]...')),
4855 _('[OPTION]... [FILE]...')),
4856 "revert":
4856 "revert":
4857 (revert,
4857 (revert,
4858 [('a', 'all', None, _('revert all changes when no arguments given')),
4858 [('a', 'all', None, _('revert all changes when no arguments given')),
4859 ('d', 'date', '',
4859 ('d', 'date', '',
4860 _('tipmost revision matching date'), _('DATE')),
4860 _('tipmost revision matching date'), _('DATE')),
4861 ('r', 'rev', '',
4861 ('r', 'rev', '',
4862 _('revert to the specified revision'), _('REV')),
4862 _('revert to the specified revision'), _('REV')),
4863 ('', 'no-backup', None, _('do not save backup copies of files')),
4863 ('', 'no-backup', None, _('do not save backup copies of files')),
4864 ] + walkopts + dryrunopts,
4864 ] + walkopts + dryrunopts,
4865 _('[OPTION]... [-r REV] [NAME]...')),
4865 _('[OPTION]... [-r REV] [NAME]...')),
4866 "rollback": (rollback, dryrunopts),
4866 "rollback": (rollback, dryrunopts),
4867 "root": (root, []),
4867 "root": (root, []),
4868 "^serve":
4868 "^serve":
4869 (serve,
4869 (serve,
4870 [('A', 'accesslog', '',
4870 [('A', 'accesslog', '',
4871 _('name of access log file to write to'), _('FILE')),
4871 _('name of access log file to write to'), _('FILE')),
4872 ('d', 'daemon', None, _('run server in background')),
4872 ('d', 'daemon', None, _('run server in background')),
4873 ('', 'daemon-pipefds', '',
4873 ('', 'daemon-pipefds', '',
4874 _('used internally by daemon mode'), _('NUM')),
4874 _('used internally by daemon mode'), _('NUM')),
4875 ('E', 'errorlog', '',
4875 ('E', 'errorlog', '',
4876 _('name of error log file to write to'), _('FILE')),
4876 _('name of error log file to write to'), _('FILE')),
4877 # use string type, then we can check if something was passed
4877 # use string type, then we can check if something was passed
4878 ('p', 'port', '',
4878 ('p', 'port', '',
4879 _('port to listen on (default: 8000)'), _('PORT')),
4879 _('port to listen on (default: 8000)'), _('PORT')),
4880 ('a', 'address', '',
4880 ('a', 'address', '',
4881 _('address to listen on (default: all interfaces)'), _('ADDR')),
4881 _('address to listen on (default: all interfaces)'), _('ADDR')),
4882 ('', 'prefix', '',
4882 ('', 'prefix', '',
4883 _('prefix path to serve from (default: server root)'), _('PREFIX')),
4883 _('prefix path to serve from (default: server root)'), _('PREFIX')),
4884 ('n', 'name', '',
4884 ('n', 'name', '',
4885 _('name to show in web pages (default: working directory)'),
4885 _('name to show in web pages (default: working directory)'),
4886 _('NAME')),
4886 _('NAME')),
4887 ('', 'web-conf', '',
4887 ('', 'web-conf', '',
4888 _('name of the hgweb config file (see "hg help hgweb")'),
4888 _('name of the hgweb config file (see "hg help hgweb")'),
4889 _('FILE')),
4889 _('FILE')),
4890 ('', 'webdir-conf', '',
4890 ('', 'webdir-conf', '',
4891 _('name of the hgweb config file (DEPRECATED)'), _('FILE')),
4891 _('name of the hgweb config file (DEPRECATED)'), _('FILE')),
4892 ('', 'pid-file', '',
4892 ('', 'pid-file', '',
4893 _('name of file to write process ID to'), _('FILE')),
4893 _('name of file to write process ID to'), _('FILE')),
4894 ('', 'stdio', None, _('for remote clients')),
4894 ('', 'stdio', None, _('for remote clients')),
4895 ('t', 'templates', '',
4895 ('t', 'templates', '',
4896 _('web templates to use'), _('TEMPLATE')),
4896 _('web templates to use'), _('TEMPLATE')),
4897 ('', 'style', '',
4897 ('', 'style', '',
4898 _('template style to use'), _('STYLE')),
4898 _('template style to use'), _('STYLE')),
4899 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4899 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4900 ('', 'certificate', '',
4900 ('', 'certificate', '',
4901 _('SSL certificate file'), _('FILE'))],
4901 _('SSL certificate file'), _('FILE'))],
4902 _('[OPTION]...')),
4902 _('[OPTION]...')),
4903 "showconfig|debugconfig":
4903 "showconfig|debugconfig":
4904 (showconfig,
4904 (showconfig,
4905 [('u', 'untrusted', None, _('show untrusted configuration options'))],
4905 [('u', 'untrusted', None, _('show untrusted configuration options'))],
4906 _('[-u] [NAME]...')),
4906 _('[-u] [NAME]...')),
4907 "^summary|sum":
4907 "^summary|sum":
4908 (summary,
4908 (summary,
4909 [('', 'remote', None, _('check for push and pull'))], '[--remote]'),
4909 [('', 'remote', None, _('check for push and pull'))], '[--remote]'),
4910 "^status|st":
4910 "^status|st":
4911 (status,
4911 (status,
4912 [('A', 'all', None, _('show status of all files')),
4912 [('A', 'all', None, _('show status of all files')),
4913 ('m', 'modified', None, _('show only modified files')),
4913 ('m', 'modified', None, _('show only modified files')),
4914 ('a', 'added', None, _('show only added files')),
4914 ('a', 'added', None, _('show only added files')),
4915 ('r', 'removed', None, _('show only removed files')),
4915 ('r', 'removed', None, _('show only removed files')),
4916 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4916 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4917 ('c', 'clean', None, _('show only files without changes')),
4917 ('c', 'clean', None, _('show only files without changes')),
4918 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4918 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4919 ('i', 'ignored', None, _('show only ignored files')),
4919 ('i', 'ignored', None, _('show only ignored files')),
4920 ('n', 'no-status', None, _('hide status prefix')),
4920 ('n', 'no-status', None, _('hide status prefix')),
4921 ('C', 'copies', None, _('show source of copied files')),
4921 ('C', 'copies', None, _('show source of copied files')),
4922 ('0', 'print0', None,
4922 ('0', 'print0', None,
4923 _('end filenames with NUL, for use with xargs')),
4923 _('end filenames with NUL, for use with xargs')),
4924 ('', 'rev', [],
4924 ('', 'rev', [],
4925 _('show difference from revision'), _('REV')),
4925 _('show difference from revision'), _('REV')),
4926 ('', 'change', '',
4926 ('', 'change', '',
4927 _('list the changed files of a revision'), _('REV')),
4927 _('list the changed files of a revision'), _('REV')),
4928 ] + walkopts + subrepoopts,
4928 ] + walkopts + subrepoopts,
4929 _('[OPTION]... [FILE]...')),
4929 _('[OPTION]... [FILE]...')),
4930 "tag":
4930 "tag":
4931 (tag,
4931 (tag,
4932 [('f', 'force', None, _('force tag')),
4932 [('f', 'force', None, _('force tag')),
4933 ('l', 'local', None, _('make the tag local')),
4933 ('l', 'local', None, _('make the tag local')),
4934 ('r', 'rev', '',
4934 ('r', 'rev', '',
4935 _('revision to tag'), _('REV')),
4935 _('revision to tag'), _('REV')),
4936 ('', 'remove', None, _('remove a tag')),
4936 ('', 'remove', None, _('remove a tag')),
4937 # -l/--local is already there, commitopts cannot be used
4937 # -l/--local is already there, commitopts cannot be used
4938 ('e', 'edit', None, _('edit commit message')),
4938 ('e', 'edit', None, _('edit commit message')),
4939 ('m', 'message', '',
4939 ('m', 'message', '',
4940 _('use <text> as commit message'), _('TEXT')),
4940 _('use <text> as commit message'), _('TEXT')),
4941 ] + commitopts2,
4941 ] + commitopts2,
4942 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
4942 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
4943 "tags": (tags, [], ''),
4943 "tags": (tags, [], ''),
4944 "tip":
4944 "tip":
4945 (tip,
4945 (tip,
4946 [('p', 'patch', None, _('show patch')),
4946 [('p', 'patch', None, _('show patch')),
4947 ('g', 'git', None, _('use git extended diff format')),
4947 ('g', 'git', None, _('use git extended diff format')),
4948 ] + templateopts,
4948 ] + templateopts,
4949 _('[-p] [-g]')),
4949 _('[-p] [-g]')),
4950 "unbundle":
4950 "unbundle":
4951 (unbundle,
4951 (unbundle,
4952 [('u', 'update', None,
4952 [('u', 'update', None,
4953 _('update to new branch head if changesets were unbundled'))],
4953 _('update to new branch head if changesets were unbundled'))],
4954 _('[-u] FILE...')),
4954 _('[-u] FILE...')),
4955 "^update|up|checkout|co":
4955 "^update|up|checkout|co":
4956 (update,
4956 (update,
4957 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
4957 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
4958 ('c', 'check', None,
4958 ('c', 'check', None,
4959 _('update across branches if no uncommitted changes')),
4959 _('update across branches if no uncommitted changes')),
4960 ('d', 'date', '',
4960 ('d', 'date', '',
4961 _('tipmost revision matching date'), _('DATE')),
4961 _('tipmost revision matching date'), _('DATE')),
4962 ('r', 'rev', '',
4962 ('r', 'rev', '',
4963 _('revision'), _('REV'))],
4963 _('revision'), _('REV'))],
4964 _('[-c] [-C] [-d DATE] [[-r] REV]')),
4964 _('[-c] [-C] [-d DATE] [[-r] REV]')),
4965 "verify": (verify, []),
4965 "verify": (verify, []),
4966 "version": (version_, []),
4966 "version": (version_, []),
4967 }
4967 }
4968
4968
4969 norepo = ("clone init version help debugcommands debugcomplete"
4969 norepo = ("clone init version help debugcommands debugcomplete"
4970 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
4970 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
4971 " debugknown debuggetbundle debugbundle")
4971 " debugknown debuggetbundle debugbundle")
4972 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
4972 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
4973 " debugdata debugindex debugindexdot")
4973 " debugdata debugindex debugindexdot")
@@ -1,1957 +1,1957 b''
1 # localrepo.py - read/write repository class for mercurial
1 # localrepo.py - read/write repository class 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 bin, hex, nullid, nullrev, short
8 from node import bin, hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import repo, changegroup, subrepo, discovery, pushkey
10 import repo, changegroup, subrepo, discovery, pushkey
11 import changelog, dirstate, filelog, manifest, context, bookmarks
11 import changelog, dirstate, filelog, manifest, context, bookmarks
12 import lock, transaction, store, encoding
12 import lock, transaction, store, encoding
13 import scmutil, util, extensions, hook, error
13 import scmutil, util, extensions, hook, error
14 import match as matchmod
14 import match as matchmod
15 import merge as mergemod
15 import merge as mergemod
16 import tags as tagsmod
16 import tags as tagsmod
17 from lock import release
17 from lock import release
18 import weakref, errno, os, time, inspect
18 import weakref, errno, os, time, inspect
19 propertycache = util.propertycache
19 propertycache = util.propertycache
20
20
21 class localrepository(repo.repository):
21 class localrepository(repo.repository):
22 capabilities = set(('lookup', 'changegroupsubset', 'branchmap', 'pushkey',
22 capabilities = set(('lookup', 'changegroupsubset', 'branchmap', 'pushkey',
23 'known', 'getbundle'))
23 'known', 'getbundle'))
24 supportedformats = set(('revlogv1',))
24 supportedformats = set(('revlogv1',))
25 supported = supportedformats | set(('store', 'fncache', 'shared',
25 supported = supportedformats | set(('store', 'fncache', 'shared',
26 'dotencode'))
26 'dotencode'))
27
27
28 def __init__(self, baseui, path=None, create=0):
28 def __init__(self, baseui, path=None, create=0):
29 repo.repository.__init__(self)
29 repo.repository.__init__(self)
30 self.root = os.path.realpath(util.expandpath(path))
30 self.root = os.path.realpath(util.expandpath(path))
31 self.path = os.path.join(self.root, ".hg")
31 self.path = os.path.join(self.root, ".hg")
32 self.origroot = path
32 self.origroot = path
33 self.auditor = scmutil.path_auditor(self.root, self._checknested)
33 self.auditor = scmutil.pathauditor(self.root, self._checknested)
34 self.opener = scmutil.opener(self.path)
34 self.opener = scmutil.opener(self.path)
35 self.wopener = scmutil.opener(self.root)
35 self.wopener = scmutil.opener(self.root)
36 self.baseui = baseui
36 self.baseui = baseui
37 self.ui = baseui.copy()
37 self.ui = baseui.copy()
38
38
39 try:
39 try:
40 self.ui.readconfig(self.join("hgrc"), self.root)
40 self.ui.readconfig(self.join("hgrc"), self.root)
41 extensions.loadall(self.ui)
41 extensions.loadall(self.ui)
42 except IOError:
42 except IOError:
43 pass
43 pass
44
44
45 if not os.path.isdir(self.path):
45 if not os.path.isdir(self.path):
46 if create:
46 if create:
47 if not os.path.exists(path):
47 if not os.path.exists(path):
48 util.makedirs(path)
48 util.makedirs(path)
49 util.makedir(self.path, notindexed=True)
49 util.makedir(self.path, notindexed=True)
50 requirements = ["revlogv1"]
50 requirements = ["revlogv1"]
51 if self.ui.configbool('format', 'usestore', True):
51 if self.ui.configbool('format', 'usestore', True):
52 os.mkdir(os.path.join(self.path, "store"))
52 os.mkdir(os.path.join(self.path, "store"))
53 requirements.append("store")
53 requirements.append("store")
54 if self.ui.configbool('format', 'usefncache', True):
54 if self.ui.configbool('format', 'usefncache', True):
55 requirements.append("fncache")
55 requirements.append("fncache")
56 if self.ui.configbool('format', 'dotencode', True):
56 if self.ui.configbool('format', 'dotencode', True):
57 requirements.append('dotencode')
57 requirements.append('dotencode')
58 # create an invalid changelog
58 # create an invalid changelog
59 self.opener.append(
59 self.opener.append(
60 "00changelog.i",
60 "00changelog.i",
61 '\0\0\0\2' # represents revlogv2
61 '\0\0\0\2' # represents revlogv2
62 ' dummy changelog to prevent using the old repo layout'
62 ' dummy changelog to prevent using the old repo layout'
63 )
63 )
64 else:
64 else:
65 raise error.RepoError(_("repository %s not found") % path)
65 raise error.RepoError(_("repository %s not found") % path)
66 elif create:
66 elif create:
67 raise error.RepoError(_("repository %s already exists") % path)
67 raise error.RepoError(_("repository %s already exists") % path)
68 else:
68 else:
69 # find requirements
69 # find requirements
70 requirements = set()
70 requirements = set()
71 try:
71 try:
72 requirements = set(self.opener.read("requires").splitlines())
72 requirements = set(self.opener.read("requires").splitlines())
73 except IOError, inst:
73 except IOError, inst:
74 if inst.errno != errno.ENOENT:
74 if inst.errno != errno.ENOENT:
75 raise
75 raise
76 for r in requirements - self.supported:
76 for r in requirements - self.supported:
77 raise error.RequirementError(
77 raise error.RequirementError(
78 _("requirement '%s' not supported") % r)
78 _("requirement '%s' not supported") % r)
79
79
80 self.sharedpath = self.path
80 self.sharedpath = self.path
81 try:
81 try:
82 s = os.path.realpath(self.opener.read("sharedpath"))
82 s = os.path.realpath(self.opener.read("sharedpath"))
83 if not os.path.exists(s):
83 if not os.path.exists(s):
84 raise error.RepoError(
84 raise error.RepoError(
85 _('.hg/sharedpath points to nonexistent directory %s') % s)
85 _('.hg/sharedpath points to nonexistent directory %s') % s)
86 self.sharedpath = s
86 self.sharedpath = s
87 except IOError, inst:
87 except IOError, inst:
88 if inst.errno != errno.ENOENT:
88 if inst.errno != errno.ENOENT:
89 raise
89 raise
90
90
91 self.store = store.store(requirements, self.sharedpath, scmutil.opener)
91 self.store = store.store(requirements, self.sharedpath, scmutil.opener)
92 self.spath = self.store.path
92 self.spath = self.store.path
93 self.sopener = self.store.opener
93 self.sopener = self.store.opener
94 self.sjoin = self.store.join
94 self.sjoin = self.store.join
95 self.opener.createmode = self.store.createmode
95 self.opener.createmode = self.store.createmode
96 self._applyrequirements(requirements)
96 self._applyrequirements(requirements)
97 if create:
97 if create:
98 self._writerequirements()
98 self._writerequirements()
99
99
100 # These two define the set of tags for this repository. _tags
100 # These two define the set of tags for this repository. _tags
101 # maps tag name to node; _tagtypes maps tag name to 'global' or
101 # maps tag name to node; _tagtypes maps tag name to 'global' or
102 # 'local'. (Global tags are defined by .hgtags across all
102 # 'local'. (Global tags are defined by .hgtags across all
103 # heads, and local tags are defined in .hg/localtags.) They
103 # heads, and local tags are defined in .hg/localtags.) They
104 # constitute the in-memory cache of tags.
104 # constitute the in-memory cache of tags.
105 self._tags = None
105 self._tags = None
106 self._tagtypes = None
106 self._tagtypes = None
107
107
108 self._branchcache = None
108 self._branchcache = None
109 self._branchcachetip = None
109 self._branchcachetip = None
110 self.nodetagscache = None
110 self.nodetagscache = None
111 self.filterpats = {}
111 self.filterpats = {}
112 self._datafilters = {}
112 self._datafilters = {}
113 self._transref = self._lockref = self._wlockref = None
113 self._transref = self._lockref = self._wlockref = None
114
114
115 def _applyrequirements(self, requirements):
115 def _applyrequirements(self, requirements):
116 self.requirements = requirements
116 self.requirements = requirements
117 self.sopener.options = {}
117 self.sopener.options = {}
118
118
119 def _writerequirements(self):
119 def _writerequirements(self):
120 reqfile = self.opener("requires", "w")
120 reqfile = self.opener("requires", "w")
121 for r in self.requirements:
121 for r in self.requirements:
122 reqfile.write("%s\n" % r)
122 reqfile.write("%s\n" % r)
123 reqfile.close()
123 reqfile.close()
124
124
125 def _checknested(self, path):
125 def _checknested(self, path):
126 """Determine if path is a legal nested repository."""
126 """Determine if path is a legal nested repository."""
127 if not path.startswith(self.root):
127 if not path.startswith(self.root):
128 return False
128 return False
129 subpath = path[len(self.root) + 1:]
129 subpath = path[len(self.root) + 1:]
130
130
131 # XXX: Checking against the current working copy is wrong in
131 # XXX: Checking against the current working copy is wrong in
132 # the sense that it can reject things like
132 # the sense that it can reject things like
133 #
133 #
134 # $ hg cat -r 10 sub/x.txt
134 # $ hg cat -r 10 sub/x.txt
135 #
135 #
136 # if sub/ is no longer a subrepository in the working copy
136 # if sub/ is no longer a subrepository in the working copy
137 # parent revision.
137 # parent revision.
138 #
138 #
139 # However, it can of course also allow things that would have
139 # However, it can of course also allow things that would have
140 # been rejected before, such as the above cat command if sub/
140 # been rejected before, such as the above cat command if sub/
141 # is a subrepository now, but was a normal directory before.
141 # is a subrepository now, but was a normal directory before.
142 # The old path auditor would have rejected by mistake since it
142 # The old path auditor would have rejected by mistake since it
143 # panics when it sees sub/.hg/.
143 # panics when it sees sub/.hg/.
144 #
144 #
145 # All in all, checking against the working copy seems sensible
145 # All in all, checking against the working copy seems sensible
146 # since we want to prevent access to nested repositories on
146 # since we want to prevent access to nested repositories on
147 # the filesystem *now*.
147 # the filesystem *now*.
148 ctx = self[None]
148 ctx = self[None]
149 parts = util.splitpath(subpath)
149 parts = util.splitpath(subpath)
150 while parts:
150 while parts:
151 prefix = os.sep.join(parts)
151 prefix = os.sep.join(parts)
152 if prefix in ctx.substate:
152 if prefix in ctx.substate:
153 if prefix == subpath:
153 if prefix == subpath:
154 return True
154 return True
155 else:
155 else:
156 sub = ctx.sub(prefix)
156 sub = ctx.sub(prefix)
157 return sub.checknested(subpath[len(prefix) + 1:])
157 return sub.checknested(subpath[len(prefix) + 1:])
158 else:
158 else:
159 parts.pop()
159 parts.pop()
160 return False
160 return False
161
161
162 @util.propertycache
162 @util.propertycache
163 def _bookmarks(self):
163 def _bookmarks(self):
164 return bookmarks.read(self)
164 return bookmarks.read(self)
165
165
166 @util.propertycache
166 @util.propertycache
167 def _bookmarkcurrent(self):
167 def _bookmarkcurrent(self):
168 return bookmarks.readcurrent(self)
168 return bookmarks.readcurrent(self)
169
169
170 @propertycache
170 @propertycache
171 def changelog(self):
171 def changelog(self):
172 c = changelog.changelog(self.sopener)
172 c = changelog.changelog(self.sopener)
173 if 'HG_PENDING' in os.environ:
173 if 'HG_PENDING' in os.environ:
174 p = os.environ['HG_PENDING']
174 p = os.environ['HG_PENDING']
175 if p.startswith(self.root):
175 if p.startswith(self.root):
176 c.readpending('00changelog.i.a')
176 c.readpending('00changelog.i.a')
177 self.sopener.options['defversion'] = c.version
177 self.sopener.options['defversion'] = c.version
178 return c
178 return c
179
179
180 @propertycache
180 @propertycache
181 def manifest(self):
181 def manifest(self):
182 return manifest.manifest(self.sopener)
182 return manifest.manifest(self.sopener)
183
183
184 @propertycache
184 @propertycache
185 def dirstate(self):
185 def dirstate(self):
186 warned = [0]
186 warned = [0]
187 def validate(node):
187 def validate(node):
188 try:
188 try:
189 self.changelog.rev(node)
189 self.changelog.rev(node)
190 return node
190 return node
191 except error.LookupError:
191 except error.LookupError:
192 if not warned[0]:
192 if not warned[0]:
193 warned[0] = True
193 warned[0] = True
194 self.ui.warn(_("warning: ignoring unknown"
194 self.ui.warn(_("warning: ignoring unknown"
195 " working parent %s!\n") % short(node))
195 " working parent %s!\n") % short(node))
196 return nullid
196 return nullid
197
197
198 return dirstate.dirstate(self.opener, self.ui, self.root, validate)
198 return dirstate.dirstate(self.opener, self.ui, self.root, validate)
199
199
200 def __getitem__(self, changeid):
200 def __getitem__(self, changeid):
201 if changeid is None:
201 if changeid is None:
202 return context.workingctx(self)
202 return context.workingctx(self)
203 return context.changectx(self, changeid)
203 return context.changectx(self, changeid)
204
204
205 def __contains__(self, changeid):
205 def __contains__(self, changeid):
206 try:
206 try:
207 return bool(self.lookup(changeid))
207 return bool(self.lookup(changeid))
208 except error.RepoLookupError:
208 except error.RepoLookupError:
209 return False
209 return False
210
210
211 def __nonzero__(self):
211 def __nonzero__(self):
212 return True
212 return True
213
213
214 def __len__(self):
214 def __len__(self):
215 return len(self.changelog)
215 return len(self.changelog)
216
216
217 def __iter__(self):
217 def __iter__(self):
218 for i in xrange(len(self)):
218 for i in xrange(len(self)):
219 yield i
219 yield i
220
220
221 def url(self):
221 def url(self):
222 return 'file:' + self.root
222 return 'file:' + self.root
223
223
224 def hook(self, name, throw=False, **args):
224 def hook(self, name, throw=False, **args):
225 return hook.hook(self.ui, self, name, throw, **args)
225 return hook.hook(self.ui, self, name, throw, **args)
226
226
227 tag_disallowed = ':\r\n'
227 tag_disallowed = ':\r\n'
228
228
229 def _tag(self, names, node, message, local, user, date, extra={}):
229 def _tag(self, names, node, message, local, user, date, extra={}):
230 if isinstance(names, str):
230 if isinstance(names, str):
231 allchars = names
231 allchars = names
232 names = (names,)
232 names = (names,)
233 else:
233 else:
234 allchars = ''.join(names)
234 allchars = ''.join(names)
235 for c in self.tag_disallowed:
235 for c in self.tag_disallowed:
236 if c in allchars:
236 if c in allchars:
237 raise util.Abort(_('%r cannot be used in a tag name') % c)
237 raise util.Abort(_('%r cannot be used in a tag name') % c)
238
238
239 branches = self.branchmap()
239 branches = self.branchmap()
240 for name in names:
240 for name in names:
241 self.hook('pretag', throw=True, node=hex(node), tag=name,
241 self.hook('pretag', throw=True, node=hex(node), tag=name,
242 local=local)
242 local=local)
243 if name in branches:
243 if name in branches:
244 self.ui.warn(_("warning: tag %s conflicts with existing"
244 self.ui.warn(_("warning: tag %s conflicts with existing"
245 " branch name\n") % name)
245 " branch name\n") % name)
246
246
247 def writetags(fp, names, munge, prevtags):
247 def writetags(fp, names, munge, prevtags):
248 fp.seek(0, 2)
248 fp.seek(0, 2)
249 if prevtags and prevtags[-1] != '\n':
249 if prevtags and prevtags[-1] != '\n':
250 fp.write('\n')
250 fp.write('\n')
251 for name in names:
251 for name in names:
252 m = munge and munge(name) or name
252 m = munge and munge(name) or name
253 if self._tagtypes and name in self._tagtypes:
253 if self._tagtypes and name in self._tagtypes:
254 old = self._tags.get(name, nullid)
254 old = self._tags.get(name, nullid)
255 fp.write('%s %s\n' % (hex(old), m))
255 fp.write('%s %s\n' % (hex(old), m))
256 fp.write('%s %s\n' % (hex(node), m))
256 fp.write('%s %s\n' % (hex(node), m))
257 fp.close()
257 fp.close()
258
258
259 prevtags = ''
259 prevtags = ''
260 if local:
260 if local:
261 try:
261 try:
262 fp = self.opener('localtags', 'r+')
262 fp = self.opener('localtags', 'r+')
263 except IOError:
263 except IOError:
264 fp = self.opener('localtags', 'a')
264 fp = self.opener('localtags', 'a')
265 else:
265 else:
266 prevtags = fp.read()
266 prevtags = fp.read()
267
267
268 # local tags are stored in the current charset
268 # local tags are stored in the current charset
269 writetags(fp, names, None, prevtags)
269 writetags(fp, names, None, prevtags)
270 for name in names:
270 for name in names:
271 self.hook('tag', node=hex(node), tag=name, local=local)
271 self.hook('tag', node=hex(node), tag=name, local=local)
272 return
272 return
273
273
274 try:
274 try:
275 fp = self.wfile('.hgtags', 'rb+')
275 fp = self.wfile('.hgtags', 'rb+')
276 except IOError:
276 except IOError:
277 fp = self.wfile('.hgtags', 'ab')
277 fp = self.wfile('.hgtags', 'ab')
278 else:
278 else:
279 prevtags = fp.read()
279 prevtags = fp.read()
280
280
281 # committed tags are stored in UTF-8
281 # committed tags are stored in UTF-8
282 writetags(fp, names, encoding.fromlocal, prevtags)
282 writetags(fp, names, encoding.fromlocal, prevtags)
283
283
284 fp.close()
284 fp.close()
285
285
286 if '.hgtags' not in self.dirstate:
286 if '.hgtags' not in self.dirstate:
287 self[None].add(['.hgtags'])
287 self[None].add(['.hgtags'])
288
288
289 m = matchmod.exact(self.root, '', ['.hgtags'])
289 m = matchmod.exact(self.root, '', ['.hgtags'])
290 tagnode = self.commit(message, user, date, extra=extra, match=m)
290 tagnode = self.commit(message, user, date, extra=extra, match=m)
291
291
292 for name in names:
292 for name in names:
293 self.hook('tag', node=hex(node), tag=name, local=local)
293 self.hook('tag', node=hex(node), tag=name, local=local)
294
294
295 return tagnode
295 return tagnode
296
296
297 def tag(self, names, node, message, local, user, date):
297 def tag(self, names, node, message, local, user, date):
298 '''tag a revision with one or more symbolic names.
298 '''tag a revision with one or more symbolic names.
299
299
300 names is a list of strings or, when adding a single tag, names may be a
300 names is a list of strings or, when adding a single tag, names may be a
301 string.
301 string.
302
302
303 if local is True, the tags are stored in a per-repository file.
303 if local is True, the tags are stored in a per-repository file.
304 otherwise, they are stored in the .hgtags file, and a new
304 otherwise, they are stored in the .hgtags file, and a new
305 changeset is committed with the change.
305 changeset is committed with the change.
306
306
307 keyword arguments:
307 keyword arguments:
308
308
309 local: whether to store tags in non-version-controlled file
309 local: whether to store tags in non-version-controlled file
310 (default False)
310 (default False)
311
311
312 message: commit message to use if committing
312 message: commit message to use if committing
313
313
314 user: name of user to use if committing
314 user: name of user to use if committing
315
315
316 date: date tuple to use if committing'''
316 date: date tuple to use if committing'''
317
317
318 if not local:
318 if not local:
319 for x in self.status()[:5]:
319 for x in self.status()[:5]:
320 if '.hgtags' in x:
320 if '.hgtags' in x:
321 raise util.Abort(_('working copy of .hgtags is changed '
321 raise util.Abort(_('working copy of .hgtags is changed '
322 '(please commit .hgtags manually)'))
322 '(please commit .hgtags manually)'))
323
323
324 self.tags() # instantiate the cache
324 self.tags() # instantiate the cache
325 self._tag(names, node, message, local, user, date)
325 self._tag(names, node, message, local, user, date)
326
326
327 def tags(self):
327 def tags(self):
328 '''return a mapping of tag to node'''
328 '''return a mapping of tag to node'''
329 if self._tags is None:
329 if self._tags is None:
330 (self._tags, self._tagtypes) = self._findtags()
330 (self._tags, self._tagtypes) = self._findtags()
331
331
332 return self._tags
332 return self._tags
333
333
334 def _findtags(self):
334 def _findtags(self):
335 '''Do the hard work of finding tags. Return a pair of dicts
335 '''Do the hard work of finding tags. Return a pair of dicts
336 (tags, tagtypes) where tags maps tag name to node, and tagtypes
336 (tags, tagtypes) where tags maps tag name to node, and tagtypes
337 maps tag name to a string like \'global\' or \'local\'.
337 maps tag name to a string like \'global\' or \'local\'.
338 Subclasses or extensions are free to add their own tags, but
338 Subclasses or extensions are free to add their own tags, but
339 should be aware that the returned dicts will be retained for the
339 should be aware that the returned dicts will be retained for the
340 duration of the localrepo object.'''
340 duration of the localrepo object.'''
341
341
342 # XXX what tagtype should subclasses/extensions use? Currently
342 # XXX what tagtype should subclasses/extensions use? Currently
343 # mq and bookmarks add tags, but do not set the tagtype at all.
343 # mq and bookmarks add tags, but do not set the tagtype at all.
344 # Should each extension invent its own tag type? Should there
344 # Should each extension invent its own tag type? Should there
345 # be one tagtype for all such "virtual" tags? Or is the status
345 # be one tagtype for all such "virtual" tags? Or is the status
346 # quo fine?
346 # quo fine?
347
347
348 alltags = {} # map tag name to (node, hist)
348 alltags = {} # map tag name to (node, hist)
349 tagtypes = {}
349 tagtypes = {}
350
350
351 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
351 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
352 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
352 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
353
353
354 # Build the return dicts. Have to re-encode tag names because
354 # Build the return dicts. Have to re-encode tag names because
355 # the tags module always uses UTF-8 (in order not to lose info
355 # the tags module always uses UTF-8 (in order not to lose info
356 # writing to the cache), but the rest of Mercurial wants them in
356 # writing to the cache), but the rest of Mercurial wants them in
357 # local encoding.
357 # local encoding.
358 tags = {}
358 tags = {}
359 for (name, (node, hist)) in alltags.iteritems():
359 for (name, (node, hist)) in alltags.iteritems():
360 if node != nullid:
360 if node != nullid:
361 try:
361 try:
362 # ignore tags to unknown nodes
362 # ignore tags to unknown nodes
363 self.changelog.lookup(node)
363 self.changelog.lookup(node)
364 tags[encoding.tolocal(name)] = node
364 tags[encoding.tolocal(name)] = node
365 except error.LookupError:
365 except error.LookupError:
366 pass
366 pass
367 tags['tip'] = self.changelog.tip()
367 tags['tip'] = self.changelog.tip()
368 tagtypes = dict([(encoding.tolocal(name), value)
368 tagtypes = dict([(encoding.tolocal(name), value)
369 for (name, value) in tagtypes.iteritems()])
369 for (name, value) in tagtypes.iteritems()])
370 return (tags, tagtypes)
370 return (tags, tagtypes)
371
371
372 def tagtype(self, tagname):
372 def tagtype(self, tagname):
373 '''
373 '''
374 return the type of the given tag. result can be:
374 return the type of the given tag. result can be:
375
375
376 'local' : a local tag
376 'local' : a local tag
377 'global' : a global tag
377 'global' : a global tag
378 None : tag does not exist
378 None : tag does not exist
379 '''
379 '''
380
380
381 self.tags()
381 self.tags()
382
382
383 return self._tagtypes.get(tagname)
383 return self._tagtypes.get(tagname)
384
384
385 def tagslist(self):
385 def tagslist(self):
386 '''return a list of tags ordered by revision'''
386 '''return a list of tags ordered by revision'''
387 l = []
387 l = []
388 for t, n in self.tags().iteritems():
388 for t, n in self.tags().iteritems():
389 r = self.changelog.rev(n)
389 r = self.changelog.rev(n)
390 l.append((r, t, n))
390 l.append((r, t, n))
391 return [(t, n) for r, t, n in sorted(l)]
391 return [(t, n) for r, t, n in sorted(l)]
392
392
393 def nodetags(self, node):
393 def nodetags(self, node):
394 '''return the tags associated with a node'''
394 '''return the tags associated with a node'''
395 if not self.nodetagscache:
395 if not self.nodetagscache:
396 self.nodetagscache = {}
396 self.nodetagscache = {}
397 for t, n in self.tags().iteritems():
397 for t, n in self.tags().iteritems():
398 self.nodetagscache.setdefault(n, []).append(t)
398 self.nodetagscache.setdefault(n, []).append(t)
399 for tags in self.nodetagscache.itervalues():
399 for tags in self.nodetagscache.itervalues():
400 tags.sort()
400 tags.sort()
401 return self.nodetagscache.get(node, [])
401 return self.nodetagscache.get(node, [])
402
402
403 def nodebookmarks(self, node):
403 def nodebookmarks(self, node):
404 marks = []
404 marks = []
405 for bookmark, n in self._bookmarks.iteritems():
405 for bookmark, n in self._bookmarks.iteritems():
406 if n == node:
406 if n == node:
407 marks.append(bookmark)
407 marks.append(bookmark)
408 return sorted(marks)
408 return sorted(marks)
409
409
410 def _branchtags(self, partial, lrev):
410 def _branchtags(self, partial, lrev):
411 # TODO: rename this function?
411 # TODO: rename this function?
412 tiprev = len(self) - 1
412 tiprev = len(self) - 1
413 if lrev != tiprev:
413 if lrev != tiprev:
414 ctxgen = (self[r] for r in xrange(lrev + 1, tiprev + 1))
414 ctxgen = (self[r] for r in xrange(lrev + 1, tiprev + 1))
415 self._updatebranchcache(partial, ctxgen)
415 self._updatebranchcache(partial, ctxgen)
416 self._writebranchcache(partial, self.changelog.tip(), tiprev)
416 self._writebranchcache(partial, self.changelog.tip(), tiprev)
417
417
418 return partial
418 return partial
419
419
420 def updatebranchcache(self):
420 def updatebranchcache(self):
421 tip = self.changelog.tip()
421 tip = self.changelog.tip()
422 if self._branchcache is not None and self._branchcachetip == tip:
422 if self._branchcache is not None and self._branchcachetip == tip:
423 return self._branchcache
423 return self._branchcache
424
424
425 oldtip = self._branchcachetip
425 oldtip = self._branchcachetip
426 self._branchcachetip = tip
426 self._branchcachetip = tip
427 if oldtip is None or oldtip not in self.changelog.nodemap:
427 if oldtip is None or oldtip not in self.changelog.nodemap:
428 partial, last, lrev = self._readbranchcache()
428 partial, last, lrev = self._readbranchcache()
429 else:
429 else:
430 lrev = self.changelog.rev(oldtip)
430 lrev = self.changelog.rev(oldtip)
431 partial = self._branchcache
431 partial = self._branchcache
432
432
433 self._branchtags(partial, lrev)
433 self._branchtags(partial, lrev)
434 # this private cache holds all heads (not just tips)
434 # this private cache holds all heads (not just tips)
435 self._branchcache = partial
435 self._branchcache = partial
436
436
437 def branchmap(self):
437 def branchmap(self):
438 '''returns a dictionary {branch: [branchheads]}'''
438 '''returns a dictionary {branch: [branchheads]}'''
439 self.updatebranchcache()
439 self.updatebranchcache()
440 return self._branchcache
440 return self._branchcache
441
441
442 def branchtags(self):
442 def branchtags(self):
443 '''return a dict where branch names map to the tipmost head of
443 '''return a dict where branch names map to the tipmost head of
444 the branch, open heads come before closed'''
444 the branch, open heads come before closed'''
445 bt = {}
445 bt = {}
446 for bn, heads in self.branchmap().iteritems():
446 for bn, heads in self.branchmap().iteritems():
447 tip = heads[-1]
447 tip = heads[-1]
448 for h in reversed(heads):
448 for h in reversed(heads):
449 if 'close' not in self.changelog.read(h)[5]:
449 if 'close' not in self.changelog.read(h)[5]:
450 tip = h
450 tip = h
451 break
451 break
452 bt[bn] = tip
452 bt[bn] = tip
453 return bt
453 return bt
454
454
455 def _readbranchcache(self):
455 def _readbranchcache(self):
456 partial = {}
456 partial = {}
457 try:
457 try:
458 f = self.opener("cache/branchheads")
458 f = self.opener("cache/branchheads")
459 lines = f.read().split('\n')
459 lines = f.read().split('\n')
460 f.close()
460 f.close()
461 except (IOError, OSError):
461 except (IOError, OSError):
462 return {}, nullid, nullrev
462 return {}, nullid, nullrev
463
463
464 try:
464 try:
465 last, lrev = lines.pop(0).split(" ", 1)
465 last, lrev = lines.pop(0).split(" ", 1)
466 last, lrev = bin(last), int(lrev)
466 last, lrev = bin(last), int(lrev)
467 if lrev >= len(self) or self[lrev].node() != last:
467 if lrev >= len(self) or self[lrev].node() != last:
468 # invalidate the cache
468 # invalidate the cache
469 raise ValueError('invalidating branch cache (tip differs)')
469 raise ValueError('invalidating branch cache (tip differs)')
470 for l in lines:
470 for l in lines:
471 if not l:
471 if not l:
472 continue
472 continue
473 node, label = l.split(" ", 1)
473 node, label = l.split(" ", 1)
474 label = encoding.tolocal(label.strip())
474 label = encoding.tolocal(label.strip())
475 partial.setdefault(label, []).append(bin(node))
475 partial.setdefault(label, []).append(bin(node))
476 except KeyboardInterrupt:
476 except KeyboardInterrupt:
477 raise
477 raise
478 except Exception, inst:
478 except Exception, inst:
479 if self.ui.debugflag:
479 if self.ui.debugflag:
480 self.ui.warn(str(inst), '\n')
480 self.ui.warn(str(inst), '\n')
481 partial, last, lrev = {}, nullid, nullrev
481 partial, last, lrev = {}, nullid, nullrev
482 return partial, last, lrev
482 return partial, last, lrev
483
483
484 def _writebranchcache(self, branches, tip, tiprev):
484 def _writebranchcache(self, branches, tip, tiprev):
485 try:
485 try:
486 f = self.opener("cache/branchheads", "w", atomictemp=True)
486 f = self.opener("cache/branchheads", "w", atomictemp=True)
487 f.write("%s %s\n" % (hex(tip), tiprev))
487 f.write("%s %s\n" % (hex(tip), tiprev))
488 for label, nodes in branches.iteritems():
488 for label, nodes in branches.iteritems():
489 for node in nodes:
489 for node in nodes:
490 f.write("%s %s\n" % (hex(node), encoding.fromlocal(label)))
490 f.write("%s %s\n" % (hex(node), encoding.fromlocal(label)))
491 f.rename()
491 f.rename()
492 except (IOError, OSError):
492 except (IOError, OSError):
493 pass
493 pass
494
494
495 def _updatebranchcache(self, partial, ctxgen):
495 def _updatebranchcache(self, partial, ctxgen):
496 # collect new branch entries
496 # collect new branch entries
497 newbranches = {}
497 newbranches = {}
498 for c in ctxgen:
498 for c in ctxgen:
499 newbranches.setdefault(c.branch(), []).append(c.node())
499 newbranches.setdefault(c.branch(), []).append(c.node())
500 # if older branchheads are reachable from new ones, they aren't
500 # if older branchheads are reachable from new ones, they aren't
501 # really branchheads. Note checking parents is insufficient:
501 # really branchheads. Note checking parents is insufficient:
502 # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
502 # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
503 for branch, newnodes in newbranches.iteritems():
503 for branch, newnodes in newbranches.iteritems():
504 bheads = partial.setdefault(branch, [])
504 bheads = partial.setdefault(branch, [])
505 bheads.extend(newnodes)
505 bheads.extend(newnodes)
506 if len(bheads) <= 1:
506 if len(bheads) <= 1:
507 continue
507 continue
508 bheads = sorted(bheads, key=lambda x: self[x].rev())
508 bheads = sorted(bheads, key=lambda x: self[x].rev())
509 # starting from tip means fewer passes over reachable
509 # starting from tip means fewer passes over reachable
510 while newnodes:
510 while newnodes:
511 latest = newnodes.pop()
511 latest = newnodes.pop()
512 if latest not in bheads:
512 if latest not in bheads:
513 continue
513 continue
514 minbhrev = self[bheads[0]].node()
514 minbhrev = self[bheads[0]].node()
515 reachable = self.changelog.reachable(latest, minbhrev)
515 reachable = self.changelog.reachable(latest, minbhrev)
516 reachable.remove(latest)
516 reachable.remove(latest)
517 if reachable:
517 if reachable:
518 bheads = [b for b in bheads if b not in reachable]
518 bheads = [b for b in bheads if b not in reachable]
519 partial[branch] = bheads
519 partial[branch] = bheads
520
520
521 def lookup(self, key):
521 def lookup(self, key):
522 if isinstance(key, int):
522 if isinstance(key, int):
523 return self.changelog.node(key)
523 return self.changelog.node(key)
524 elif key == '.':
524 elif key == '.':
525 return self.dirstate.p1()
525 return self.dirstate.p1()
526 elif key == 'null':
526 elif key == 'null':
527 return nullid
527 return nullid
528 elif key == 'tip':
528 elif key == 'tip':
529 return self.changelog.tip()
529 return self.changelog.tip()
530 n = self.changelog._match(key)
530 n = self.changelog._match(key)
531 if n:
531 if n:
532 return n
532 return n
533 if key in self._bookmarks:
533 if key in self._bookmarks:
534 return self._bookmarks[key]
534 return self._bookmarks[key]
535 if key in self.tags():
535 if key in self.tags():
536 return self.tags()[key]
536 return self.tags()[key]
537 if key in self.branchtags():
537 if key in self.branchtags():
538 return self.branchtags()[key]
538 return self.branchtags()[key]
539 n = self.changelog._partialmatch(key)
539 n = self.changelog._partialmatch(key)
540 if n:
540 if n:
541 return n
541 return n
542
542
543 # can't find key, check if it might have come from damaged dirstate
543 # can't find key, check if it might have come from damaged dirstate
544 if key in self.dirstate.parents():
544 if key in self.dirstate.parents():
545 raise error.Abort(_("working directory has unknown parent '%s'!")
545 raise error.Abort(_("working directory has unknown parent '%s'!")
546 % short(key))
546 % short(key))
547 try:
547 try:
548 if len(key) == 20:
548 if len(key) == 20:
549 key = hex(key)
549 key = hex(key)
550 except TypeError:
550 except TypeError:
551 pass
551 pass
552 raise error.RepoLookupError(_("unknown revision '%s'") % key)
552 raise error.RepoLookupError(_("unknown revision '%s'") % key)
553
553
554 def lookupbranch(self, key, remote=None):
554 def lookupbranch(self, key, remote=None):
555 repo = remote or self
555 repo = remote or self
556 if key in repo.branchmap():
556 if key in repo.branchmap():
557 return key
557 return key
558
558
559 repo = (remote and remote.local()) and remote or self
559 repo = (remote and remote.local()) and remote or self
560 return repo[key].branch()
560 return repo[key].branch()
561
561
562 def known(self, nodes):
562 def known(self, nodes):
563 nm = self.changelog.nodemap
563 nm = self.changelog.nodemap
564 return [(n in nm) for n in nodes]
564 return [(n in nm) for n in nodes]
565
565
566 def local(self):
566 def local(self):
567 return True
567 return True
568
568
569 def join(self, f):
569 def join(self, f):
570 return os.path.join(self.path, f)
570 return os.path.join(self.path, f)
571
571
572 def wjoin(self, f):
572 def wjoin(self, f):
573 return os.path.join(self.root, f)
573 return os.path.join(self.root, f)
574
574
575 def file(self, f):
575 def file(self, f):
576 if f[0] == '/':
576 if f[0] == '/':
577 f = f[1:]
577 f = f[1:]
578 return filelog.filelog(self.sopener, f)
578 return filelog.filelog(self.sopener, f)
579
579
580 def changectx(self, changeid):
580 def changectx(self, changeid):
581 return self[changeid]
581 return self[changeid]
582
582
583 def parents(self, changeid=None):
583 def parents(self, changeid=None):
584 '''get list of changectxs for parents of changeid'''
584 '''get list of changectxs for parents of changeid'''
585 return self[changeid].parents()
585 return self[changeid].parents()
586
586
587 def filectx(self, path, changeid=None, fileid=None):
587 def filectx(self, path, changeid=None, fileid=None):
588 """changeid can be a changeset revision, node, or tag.
588 """changeid can be a changeset revision, node, or tag.
589 fileid can be a file revision or node."""
589 fileid can be a file revision or node."""
590 return context.filectx(self, path, changeid, fileid)
590 return context.filectx(self, path, changeid, fileid)
591
591
592 def getcwd(self):
592 def getcwd(self):
593 return self.dirstate.getcwd()
593 return self.dirstate.getcwd()
594
594
595 def pathto(self, f, cwd=None):
595 def pathto(self, f, cwd=None):
596 return self.dirstate.pathto(f, cwd)
596 return self.dirstate.pathto(f, cwd)
597
597
598 def wfile(self, f, mode='r'):
598 def wfile(self, f, mode='r'):
599 return self.wopener(f, mode)
599 return self.wopener(f, mode)
600
600
601 def _link(self, f):
601 def _link(self, f):
602 return os.path.islink(self.wjoin(f))
602 return os.path.islink(self.wjoin(f))
603
603
604 def _loadfilter(self, filter):
604 def _loadfilter(self, filter):
605 if filter not in self.filterpats:
605 if filter not in self.filterpats:
606 l = []
606 l = []
607 for pat, cmd in self.ui.configitems(filter):
607 for pat, cmd in self.ui.configitems(filter):
608 if cmd == '!':
608 if cmd == '!':
609 continue
609 continue
610 mf = matchmod.match(self.root, '', [pat])
610 mf = matchmod.match(self.root, '', [pat])
611 fn = None
611 fn = None
612 params = cmd
612 params = cmd
613 for name, filterfn in self._datafilters.iteritems():
613 for name, filterfn in self._datafilters.iteritems():
614 if cmd.startswith(name):
614 if cmd.startswith(name):
615 fn = filterfn
615 fn = filterfn
616 params = cmd[len(name):].lstrip()
616 params = cmd[len(name):].lstrip()
617 break
617 break
618 if not fn:
618 if not fn:
619 fn = lambda s, c, **kwargs: util.filter(s, c)
619 fn = lambda s, c, **kwargs: util.filter(s, c)
620 # Wrap old filters not supporting keyword arguments
620 # Wrap old filters not supporting keyword arguments
621 if not inspect.getargspec(fn)[2]:
621 if not inspect.getargspec(fn)[2]:
622 oldfn = fn
622 oldfn = fn
623 fn = lambda s, c, **kwargs: oldfn(s, c)
623 fn = lambda s, c, **kwargs: oldfn(s, c)
624 l.append((mf, fn, params))
624 l.append((mf, fn, params))
625 self.filterpats[filter] = l
625 self.filterpats[filter] = l
626 return self.filterpats[filter]
626 return self.filterpats[filter]
627
627
628 def _filter(self, filterpats, filename, data):
628 def _filter(self, filterpats, filename, data):
629 for mf, fn, cmd in filterpats:
629 for mf, fn, cmd in filterpats:
630 if mf(filename):
630 if mf(filename):
631 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
631 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
632 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
632 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
633 break
633 break
634
634
635 return data
635 return data
636
636
637 @propertycache
637 @propertycache
638 def _encodefilterpats(self):
638 def _encodefilterpats(self):
639 return self._loadfilter('encode')
639 return self._loadfilter('encode')
640
640
641 @propertycache
641 @propertycache
642 def _decodefilterpats(self):
642 def _decodefilterpats(self):
643 return self._loadfilter('decode')
643 return self._loadfilter('decode')
644
644
645 def adddatafilter(self, name, filter):
645 def adddatafilter(self, name, filter):
646 self._datafilters[name] = filter
646 self._datafilters[name] = filter
647
647
648 def wread(self, filename):
648 def wread(self, filename):
649 if self._link(filename):
649 if self._link(filename):
650 data = os.readlink(self.wjoin(filename))
650 data = os.readlink(self.wjoin(filename))
651 else:
651 else:
652 data = self.wopener.read(filename)
652 data = self.wopener.read(filename)
653 return self._filter(self._encodefilterpats, filename, data)
653 return self._filter(self._encodefilterpats, filename, data)
654
654
655 def wwrite(self, filename, data, flags):
655 def wwrite(self, filename, data, flags):
656 data = self._filter(self._decodefilterpats, filename, data)
656 data = self._filter(self._decodefilterpats, filename, data)
657 if 'l' in flags:
657 if 'l' in flags:
658 self.wopener.symlink(data, filename)
658 self.wopener.symlink(data, filename)
659 else:
659 else:
660 self.wopener.write(filename, data)
660 self.wopener.write(filename, data)
661 if 'x' in flags:
661 if 'x' in flags:
662 util.set_flags(self.wjoin(filename), False, True)
662 util.set_flags(self.wjoin(filename), False, True)
663
663
664 def wwritedata(self, filename, data):
664 def wwritedata(self, filename, data):
665 return self._filter(self._decodefilterpats, filename, data)
665 return self._filter(self._decodefilterpats, filename, data)
666
666
667 def transaction(self, desc):
667 def transaction(self, desc):
668 tr = self._transref and self._transref() or None
668 tr = self._transref and self._transref() or None
669 if tr and tr.running():
669 if tr and tr.running():
670 return tr.nest()
670 return tr.nest()
671
671
672 # abort here if the journal already exists
672 # abort here if the journal already exists
673 if os.path.exists(self.sjoin("journal")):
673 if os.path.exists(self.sjoin("journal")):
674 raise error.RepoError(
674 raise error.RepoError(
675 _("abandoned transaction found - run hg recover"))
675 _("abandoned transaction found - run hg recover"))
676
676
677 # save dirstate for rollback
677 # save dirstate for rollback
678 try:
678 try:
679 ds = self.opener.read("dirstate")
679 ds = self.opener.read("dirstate")
680 except IOError:
680 except IOError:
681 ds = ""
681 ds = ""
682 self.opener.write("journal.dirstate", ds)
682 self.opener.write("journal.dirstate", ds)
683 self.opener.write("journal.branch",
683 self.opener.write("journal.branch",
684 encoding.fromlocal(self.dirstate.branch()))
684 encoding.fromlocal(self.dirstate.branch()))
685 self.opener.write("journal.desc",
685 self.opener.write("journal.desc",
686 "%d\n%s\n" % (len(self), desc))
686 "%d\n%s\n" % (len(self), desc))
687
687
688 renames = [(self.sjoin("journal"), self.sjoin("undo")),
688 renames = [(self.sjoin("journal"), self.sjoin("undo")),
689 (self.join("journal.dirstate"), self.join("undo.dirstate")),
689 (self.join("journal.dirstate"), self.join("undo.dirstate")),
690 (self.join("journal.branch"), self.join("undo.branch")),
690 (self.join("journal.branch"), self.join("undo.branch")),
691 (self.join("journal.desc"), self.join("undo.desc"))]
691 (self.join("journal.desc"), self.join("undo.desc"))]
692 tr = transaction.transaction(self.ui.warn, self.sopener,
692 tr = transaction.transaction(self.ui.warn, self.sopener,
693 self.sjoin("journal"),
693 self.sjoin("journal"),
694 aftertrans(renames),
694 aftertrans(renames),
695 self.store.createmode)
695 self.store.createmode)
696 self._transref = weakref.ref(tr)
696 self._transref = weakref.ref(tr)
697 return tr
697 return tr
698
698
699 def recover(self):
699 def recover(self):
700 lock = self.lock()
700 lock = self.lock()
701 try:
701 try:
702 if os.path.exists(self.sjoin("journal")):
702 if os.path.exists(self.sjoin("journal")):
703 self.ui.status(_("rolling back interrupted transaction\n"))
703 self.ui.status(_("rolling back interrupted transaction\n"))
704 transaction.rollback(self.sopener, self.sjoin("journal"),
704 transaction.rollback(self.sopener, self.sjoin("journal"),
705 self.ui.warn)
705 self.ui.warn)
706 self.invalidate()
706 self.invalidate()
707 return True
707 return True
708 else:
708 else:
709 self.ui.warn(_("no interrupted transaction available\n"))
709 self.ui.warn(_("no interrupted transaction available\n"))
710 return False
710 return False
711 finally:
711 finally:
712 lock.release()
712 lock.release()
713
713
714 def rollback(self, dryrun=False):
714 def rollback(self, dryrun=False):
715 wlock = lock = None
715 wlock = lock = None
716 try:
716 try:
717 wlock = self.wlock()
717 wlock = self.wlock()
718 lock = self.lock()
718 lock = self.lock()
719 if os.path.exists(self.sjoin("undo")):
719 if os.path.exists(self.sjoin("undo")):
720 try:
720 try:
721 args = self.opener.read("undo.desc").splitlines()
721 args = self.opener.read("undo.desc").splitlines()
722 if len(args) >= 3 and self.ui.verbose:
722 if len(args) >= 3 and self.ui.verbose:
723 desc = _("repository tip rolled back to revision %s"
723 desc = _("repository tip rolled back to revision %s"
724 " (undo %s: %s)\n") % (
724 " (undo %s: %s)\n") % (
725 int(args[0]) - 1, args[1], args[2])
725 int(args[0]) - 1, args[1], args[2])
726 elif len(args) >= 2:
726 elif len(args) >= 2:
727 desc = _("repository tip rolled back to revision %s"
727 desc = _("repository tip rolled back to revision %s"
728 " (undo %s)\n") % (
728 " (undo %s)\n") % (
729 int(args[0]) - 1, args[1])
729 int(args[0]) - 1, args[1])
730 except IOError:
730 except IOError:
731 desc = _("rolling back unknown transaction\n")
731 desc = _("rolling back unknown transaction\n")
732 self.ui.status(desc)
732 self.ui.status(desc)
733 if dryrun:
733 if dryrun:
734 return
734 return
735 transaction.rollback(self.sopener, self.sjoin("undo"),
735 transaction.rollback(self.sopener, self.sjoin("undo"),
736 self.ui.warn)
736 self.ui.warn)
737 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
737 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
738 if os.path.exists(self.join('undo.bookmarks')):
738 if os.path.exists(self.join('undo.bookmarks')):
739 util.rename(self.join('undo.bookmarks'),
739 util.rename(self.join('undo.bookmarks'),
740 self.join('bookmarks'))
740 self.join('bookmarks'))
741 try:
741 try:
742 branch = self.opener.read("undo.branch")
742 branch = self.opener.read("undo.branch")
743 self.dirstate.setbranch(branch)
743 self.dirstate.setbranch(branch)
744 except IOError:
744 except IOError:
745 self.ui.warn(_("named branch could not be reset, "
745 self.ui.warn(_("named branch could not be reset, "
746 "current branch is still: %s\n")
746 "current branch is still: %s\n")
747 % self.dirstate.branch())
747 % self.dirstate.branch())
748 self.invalidate()
748 self.invalidate()
749 self.dirstate.invalidate()
749 self.dirstate.invalidate()
750 self.destroyed()
750 self.destroyed()
751 parents = tuple([p.rev() for p in self.parents()])
751 parents = tuple([p.rev() for p in self.parents()])
752 if len(parents) > 1:
752 if len(parents) > 1:
753 self.ui.status(_("working directory now based on "
753 self.ui.status(_("working directory now based on "
754 "revisions %d and %d\n") % parents)
754 "revisions %d and %d\n") % parents)
755 else:
755 else:
756 self.ui.status(_("working directory now based on "
756 self.ui.status(_("working directory now based on "
757 "revision %d\n") % parents)
757 "revision %d\n") % parents)
758 else:
758 else:
759 self.ui.warn(_("no rollback information available\n"))
759 self.ui.warn(_("no rollback information available\n"))
760 return 1
760 return 1
761 finally:
761 finally:
762 release(lock, wlock)
762 release(lock, wlock)
763
763
764 def invalidatecaches(self):
764 def invalidatecaches(self):
765 self._tags = None
765 self._tags = None
766 self._tagtypes = None
766 self._tagtypes = None
767 self.nodetagscache = None
767 self.nodetagscache = None
768 self._branchcache = None # in UTF-8
768 self._branchcache = None # in UTF-8
769 self._branchcachetip = None
769 self._branchcachetip = None
770
770
771 def invalidate(self):
771 def invalidate(self):
772 for a in ("changelog", "manifest", "_bookmarks", "_bookmarkcurrent"):
772 for a in ("changelog", "manifest", "_bookmarks", "_bookmarkcurrent"):
773 if a in self.__dict__:
773 if a in self.__dict__:
774 delattr(self, a)
774 delattr(self, a)
775 self.invalidatecaches()
775 self.invalidatecaches()
776
776
777 def _lock(self, lockname, wait, releasefn, acquirefn, desc):
777 def _lock(self, lockname, wait, releasefn, acquirefn, desc):
778 try:
778 try:
779 l = lock.lock(lockname, 0, releasefn, desc=desc)
779 l = lock.lock(lockname, 0, releasefn, desc=desc)
780 except error.LockHeld, inst:
780 except error.LockHeld, inst:
781 if not wait:
781 if not wait:
782 raise
782 raise
783 self.ui.warn(_("waiting for lock on %s held by %r\n") %
783 self.ui.warn(_("waiting for lock on %s held by %r\n") %
784 (desc, inst.locker))
784 (desc, inst.locker))
785 # default to 600 seconds timeout
785 # default to 600 seconds timeout
786 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
786 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
787 releasefn, desc=desc)
787 releasefn, desc=desc)
788 if acquirefn:
788 if acquirefn:
789 acquirefn()
789 acquirefn()
790 return l
790 return l
791
791
792 def lock(self, wait=True):
792 def lock(self, wait=True):
793 '''Lock the repository store (.hg/store) and return a weak reference
793 '''Lock the repository store (.hg/store) and return a weak reference
794 to the lock. Use this before modifying the store (e.g. committing or
794 to the lock. Use this before modifying the store (e.g. committing or
795 stripping). If you are opening a transaction, get a lock as well.)'''
795 stripping). If you are opening a transaction, get a lock as well.)'''
796 l = self._lockref and self._lockref()
796 l = self._lockref and self._lockref()
797 if l is not None and l.held:
797 if l is not None and l.held:
798 l.lock()
798 l.lock()
799 return l
799 return l
800
800
801 l = self._lock(self.sjoin("lock"), wait, self.store.write,
801 l = self._lock(self.sjoin("lock"), wait, self.store.write,
802 self.invalidate, _('repository %s') % self.origroot)
802 self.invalidate, _('repository %s') % self.origroot)
803 self._lockref = weakref.ref(l)
803 self._lockref = weakref.ref(l)
804 return l
804 return l
805
805
806 def wlock(self, wait=True):
806 def wlock(self, wait=True):
807 '''Lock the non-store parts of the repository (everything under
807 '''Lock the non-store parts of the repository (everything under
808 .hg except .hg/store) and return a weak reference to the lock.
808 .hg except .hg/store) and return a weak reference to the lock.
809 Use this before modifying files in .hg.'''
809 Use this before modifying files in .hg.'''
810 l = self._wlockref and self._wlockref()
810 l = self._wlockref and self._wlockref()
811 if l is not None and l.held:
811 if l is not None and l.held:
812 l.lock()
812 l.lock()
813 return l
813 return l
814
814
815 l = self._lock(self.join("wlock"), wait, self.dirstate.write,
815 l = self._lock(self.join("wlock"), wait, self.dirstate.write,
816 self.dirstate.invalidate, _('working directory of %s') %
816 self.dirstate.invalidate, _('working directory of %s') %
817 self.origroot)
817 self.origroot)
818 self._wlockref = weakref.ref(l)
818 self._wlockref = weakref.ref(l)
819 return l
819 return l
820
820
821 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
821 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
822 """
822 """
823 commit an individual file as part of a larger transaction
823 commit an individual file as part of a larger transaction
824 """
824 """
825
825
826 fname = fctx.path()
826 fname = fctx.path()
827 text = fctx.data()
827 text = fctx.data()
828 flog = self.file(fname)
828 flog = self.file(fname)
829 fparent1 = manifest1.get(fname, nullid)
829 fparent1 = manifest1.get(fname, nullid)
830 fparent2 = fparent2o = manifest2.get(fname, nullid)
830 fparent2 = fparent2o = manifest2.get(fname, nullid)
831
831
832 meta = {}
832 meta = {}
833 copy = fctx.renamed()
833 copy = fctx.renamed()
834 if copy and copy[0] != fname:
834 if copy and copy[0] != fname:
835 # Mark the new revision of this file as a copy of another
835 # Mark the new revision of this file as a copy of another
836 # file. This copy data will effectively act as a parent
836 # file. This copy data will effectively act as a parent
837 # of this new revision. If this is a merge, the first
837 # of this new revision. If this is a merge, the first
838 # parent will be the nullid (meaning "look up the copy data")
838 # parent will be the nullid (meaning "look up the copy data")
839 # and the second one will be the other parent. For example:
839 # and the second one will be the other parent. For example:
840 #
840 #
841 # 0 --- 1 --- 3 rev1 changes file foo
841 # 0 --- 1 --- 3 rev1 changes file foo
842 # \ / rev2 renames foo to bar and changes it
842 # \ / rev2 renames foo to bar and changes it
843 # \- 2 -/ rev3 should have bar with all changes and
843 # \- 2 -/ rev3 should have bar with all changes and
844 # should record that bar descends from
844 # should record that bar descends from
845 # bar in rev2 and foo in rev1
845 # bar in rev2 and foo in rev1
846 #
846 #
847 # this allows this merge to succeed:
847 # this allows this merge to succeed:
848 #
848 #
849 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
849 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
850 # \ / merging rev3 and rev4 should use bar@rev2
850 # \ / merging rev3 and rev4 should use bar@rev2
851 # \- 2 --- 4 as the merge base
851 # \- 2 --- 4 as the merge base
852 #
852 #
853
853
854 cfname = copy[0]
854 cfname = copy[0]
855 crev = manifest1.get(cfname)
855 crev = manifest1.get(cfname)
856 newfparent = fparent2
856 newfparent = fparent2
857
857
858 if manifest2: # branch merge
858 if manifest2: # branch merge
859 if fparent2 == nullid or crev is None: # copied on remote side
859 if fparent2 == nullid or crev is None: # copied on remote side
860 if cfname in manifest2:
860 if cfname in manifest2:
861 crev = manifest2[cfname]
861 crev = manifest2[cfname]
862 newfparent = fparent1
862 newfparent = fparent1
863
863
864 # find source in nearest ancestor if we've lost track
864 # find source in nearest ancestor if we've lost track
865 if not crev:
865 if not crev:
866 self.ui.debug(" %s: searching for copy revision for %s\n" %
866 self.ui.debug(" %s: searching for copy revision for %s\n" %
867 (fname, cfname))
867 (fname, cfname))
868 for ancestor in self[None].ancestors():
868 for ancestor in self[None].ancestors():
869 if cfname in ancestor:
869 if cfname in ancestor:
870 crev = ancestor[cfname].filenode()
870 crev = ancestor[cfname].filenode()
871 break
871 break
872
872
873 if crev:
873 if crev:
874 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
874 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
875 meta["copy"] = cfname
875 meta["copy"] = cfname
876 meta["copyrev"] = hex(crev)
876 meta["copyrev"] = hex(crev)
877 fparent1, fparent2 = nullid, newfparent
877 fparent1, fparent2 = nullid, newfparent
878 else:
878 else:
879 self.ui.warn(_("warning: can't find ancestor for '%s' "
879 self.ui.warn(_("warning: can't find ancestor for '%s' "
880 "copied from '%s'!\n") % (fname, cfname))
880 "copied from '%s'!\n") % (fname, cfname))
881
881
882 elif fparent2 != nullid:
882 elif fparent2 != nullid:
883 # is one parent an ancestor of the other?
883 # is one parent an ancestor of the other?
884 fparentancestor = flog.ancestor(fparent1, fparent2)
884 fparentancestor = flog.ancestor(fparent1, fparent2)
885 if fparentancestor == fparent1:
885 if fparentancestor == fparent1:
886 fparent1, fparent2 = fparent2, nullid
886 fparent1, fparent2 = fparent2, nullid
887 elif fparentancestor == fparent2:
887 elif fparentancestor == fparent2:
888 fparent2 = nullid
888 fparent2 = nullid
889
889
890 # is the file changed?
890 # is the file changed?
891 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
891 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
892 changelist.append(fname)
892 changelist.append(fname)
893 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
893 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
894
894
895 # are just the flags changed during merge?
895 # are just the flags changed during merge?
896 if fparent1 != fparent2o and manifest1.flags(fname) != fctx.flags():
896 if fparent1 != fparent2o and manifest1.flags(fname) != fctx.flags():
897 changelist.append(fname)
897 changelist.append(fname)
898
898
899 return fparent1
899 return fparent1
900
900
901 def commit(self, text="", user=None, date=None, match=None, force=False,
901 def commit(self, text="", user=None, date=None, match=None, force=False,
902 editor=False, extra={}):
902 editor=False, extra={}):
903 """Add a new revision to current repository.
903 """Add a new revision to current repository.
904
904
905 Revision information is gathered from the working directory,
905 Revision information is gathered from the working directory,
906 match can be used to filter the committed files. If editor is
906 match can be used to filter the committed files. If editor is
907 supplied, it is called to get a commit message.
907 supplied, it is called to get a commit message.
908 """
908 """
909
909
910 def fail(f, msg):
910 def fail(f, msg):
911 raise util.Abort('%s: %s' % (f, msg))
911 raise util.Abort('%s: %s' % (f, msg))
912
912
913 if not match:
913 if not match:
914 match = matchmod.always(self.root, '')
914 match = matchmod.always(self.root, '')
915
915
916 if not force:
916 if not force:
917 vdirs = []
917 vdirs = []
918 match.dir = vdirs.append
918 match.dir = vdirs.append
919 match.bad = fail
919 match.bad = fail
920
920
921 wlock = self.wlock()
921 wlock = self.wlock()
922 try:
922 try:
923 wctx = self[None]
923 wctx = self[None]
924 merge = len(wctx.parents()) > 1
924 merge = len(wctx.parents()) > 1
925
925
926 if (not force and merge and match and
926 if (not force and merge and match and
927 (match.files() or match.anypats())):
927 (match.files() or match.anypats())):
928 raise util.Abort(_('cannot partially commit a merge '
928 raise util.Abort(_('cannot partially commit a merge '
929 '(do not specify files or patterns)'))
929 '(do not specify files or patterns)'))
930
930
931 changes = self.status(match=match, clean=force)
931 changes = self.status(match=match, clean=force)
932 if force:
932 if force:
933 changes[0].extend(changes[6]) # mq may commit unchanged files
933 changes[0].extend(changes[6]) # mq may commit unchanged files
934
934
935 # check subrepos
935 # check subrepos
936 subs = []
936 subs = []
937 removedsubs = set()
937 removedsubs = set()
938 for p in wctx.parents():
938 for p in wctx.parents():
939 removedsubs.update(s for s in p.substate if match(s))
939 removedsubs.update(s for s in p.substate if match(s))
940 for s in wctx.substate:
940 for s in wctx.substate:
941 removedsubs.discard(s)
941 removedsubs.discard(s)
942 if match(s) and wctx.sub(s).dirty():
942 if match(s) and wctx.sub(s).dirty():
943 subs.append(s)
943 subs.append(s)
944 if (subs or removedsubs):
944 if (subs or removedsubs):
945 if (not match('.hgsub') and
945 if (not match('.hgsub') and
946 '.hgsub' in (wctx.modified() + wctx.added())):
946 '.hgsub' in (wctx.modified() + wctx.added())):
947 raise util.Abort(_("can't commit subrepos without .hgsub"))
947 raise util.Abort(_("can't commit subrepos without .hgsub"))
948 if '.hgsubstate' not in changes[0]:
948 if '.hgsubstate' not in changes[0]:
949 changes[0].insert(0, '.hgsubstate')
949 changes[0].insert(0, '.hgsubstate')
950
950
951 if subs and not self.ui.configbool('ui', 'commitsubrepos', True):
951 if subs and not self.ui.configbool('ui', 'commitsubrepos', True):
952 changedsubs = [s for s in subs if wctx.sub(s).dirty(True)]
952 changedsubs = [s for s in subs if wctx.sub(s).dirty(True)]
953 if changedsubs:
953 if changedsubs:
954 raise util.Abort(_("uncommitted changes in subrepo %s")
954 raise util.Abort(_("uncommitted changes in subrepo %s")
955 % changedsubs[0])
955 % changedsubs[0])
956
956
957 # make sure all explicit patterns are matched
957 # make sure all explicit patterns are matched
958 if not force and match.files():
958 if not force and match.files():
959 matched = set(changes[0] + changes[1] + changes[2])
959 matched = set(changes[0] + changes[1] + changes[2])
960
960
961 for f in match.files():
961 for f in match.files():
962 if f == '.' or f in matched or f in wctx.substate:
962 if f == '.' or f in matched or f in wctx.substate:
963 continue
963 continue
964 if f in changes[3]: # missing
964 if f in changes[3]: # missing
965 fail(f, _('file not found!'))
965 fail(f, _('file not found!'))
966 if f in vdirs: # visited directory
966 if f in vdirs: # visited directory
967 d = f + '/'
967 d = f + '/'
968 for mf in matched:
968 for mf in matched:
969 if mf.startswith(d):
969 if mf.startswith(d):
970 break
970 break
971 else:
971 else:
972 fail(f, _("no match under directory!"))
972 fail(f, _("no match under directory!"))
973 elif f not in self.dirstate:
973 elif f not in self.dirstate:
974 fail(f, _("file not tracked!"))
974 fail(f, _("file not tracked!"))
975
975
976 if (not force and not extra.get("close") and not merge
976 if (not force and not extra.get("close") and not merge
977 and not (changes[0] or changes[1] or changes[2])
977 and not (changes[0] or changes[1] or changes[2])
978 and wctx.branch() == wctx.p1().branch()):
978 and wctx.branch() == wctx.p1().branch()):
979 return None
979 return None
980
980
981 ms = mergemod.mergestate(self)
981 ms = mergemod.mergestate(self)
982 for f in changes[0]:
982 for f in changes[0]:
983 if f in ms and ms[f] == 'u':
983 if f in ms and ms[f] == 'u':
984 raise util.Abort(_("unresolved merge conflicts "
984 raise util.Abort(_("unresolved merge conflicts "
985 "(see hg help resolve)"))
985 "(see hg help resolve)"))
986
986
987 cctx = context.workingctx(self, text, user, date, extra, changes)
987 cctx = context.workingctx(self, text, user, date, extra, changes)
988 if editor:
988 if editor:
989 cctx._text = editor(self, cctx, subs)
989 cctx._text = editor(self, cctx, subs)
990 edited = (text != cctx._text)
990 edited = (text != cctx._text)
991
991
992 # commit subs
992 # commit subs
993 if subs or removedsubs:
993 if subs or removedsubs:
994 state = wctx.substate.copy()
994 state = wctx.substate.copy()
995 for s in sorted(subs):
995 for s in sorted(subs):
996 sub = wctx.sub(s)
996 sub = wctx.sub(s)
997 self.ui.status(_('committing subrepository %s\n') %
997 self.ui.status(_('committing subrepository %s\n') %
998 subrepo.subrelpath(sub))
998 subrepo.subrelpath(sub))
999 sr = sub.commit(cctx._text, user, date)
999 sr = sub.commit(cctx._text, user, date)
1000 state[s] = (state[s][0], sr)
1000 state[s] = (state[s][0], sr)
1001 subrepo.writestate(self, state)
1001 subrepo.writestate(self, state)
1002
1002
1003 # Save commit message in case this transaction gets rolled back
1003 # Save commit message in case this transaction gets rolled back
1004 # (e.g. by a pretxncommit hook). Leave the content alone on
1004 # (e.g. by a pretxncommit hook). Leave the content alone on
1005 # the assumption that the user will use the same editor again.
1005 # the assumption that the user will use the same editor again.
1006 msgfile = self.opener('last-message.txt', 'wb')
1006 msgfile = self.opener('last-message.txt', 'wb')
1007 msgfile.write(cctx._text)
1007 msgfile.write(cctx._text)
1008 msgfile.close()
1008 msgfile.close()
1009
1009
1010 p1, p2 = self.dirstate.parents()
1010 p1, p2 = self.dirstate.parents()
1011 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1011 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1012 try:
1012 try:
1013 self.hook("precommit", throw=True, parent1=hookp1, parent2=hookp2)
1013 self.hook("precommit", throw=True, parent1=hookp1, parent2=hookp2)
1014 ret = self.commitctx(cctx, True)
1014 ret = self.commitctx(cctx, True)
1015 except:
1015 except:
1016 if edited:
1016 if edited:
1017 msgfn = self.pathto(msgfile.name[len(self.root)+1:])
1017 msgfn = self.pathto(msgfile.name[len(self.root)+1:])
1018 self.ui.write(
1018 self.ui.write(
1019 _('note: commit message saved in %s\n') % msgfn)
1019 _('note: commit message saved in %s\n') % msgfn)
1020 raise
1020 raise
1021
1021
1022 # update bookmarks, dirstate and mergestate
1022 # update bookmarks, dirstate and mergestate
1023 bookmarks.update(self, p1, ret)
1023 bookmarks.update(self, p1, ret)
1024 for f in changes[0] + changes[1]:
1024 for f in changes[0] + changes[1]:
1025 self.dirstate.normal(f)
1025 self.dirstate.normal(f)
1026 for f in changes[2]:
1026 for f in changes[2]:
1027 self.dirstate.forget(f)
1027 self.dirstate.forget(f)
1028 self.dirstate.setparents(ret)
1028 self.dirstate.setparents(ret)
1029 ms.reset()
1029 ms.reset()
1030 finally:
1030 finally:
1031 wlock.release()
1031 wlock.release()
1032
1032
1033 self.hook("commit", node=hex(ret), parent1=hookp1, parent2=hookp2)
1033 self.hook("commit", node=hex(ret), parent1=hookp1, parent2=hookp2)
1034 return ret
1034 return ret
1035
1035
1036 def commitctx(self, ctx, error=False):
1036 def commitctx(self, ctx, error=False):
1037 """Add a new revision to current repository.
1037 """Add a new revision to current repository.
1038 Revision information is passed via the context argument.
1038 Revision information is passed via the context argument.
1039 """
1039 """
1040
1040
1041 tr = lock = None
1041 tr = lock = None
1042 removed = list(ctx.removed())
1042 removed = list(ctx.removed())
1043 p1, p2 = ctx.p1(), ctx.p2()
1043 p1, p2 = ctx.p1(), ctx.p2()
1044 user = ctx.user()
1044 user = ctx.user()
1045
1045
1046 lock = self.lock()
1046 lock = self.lock()
1047 try:
1047 try:
1048 tr = self.transaction("commit")
1048 tr = self.transaction("commit")
1049 trp = weakref.proxy(tr)
1049 trp = weakref.proxy(tr)
1050
1050
1051 if ctx.files():
1051 if ctx.files():
1052 m1 = p1.manifest().copy()
1052 m1 = p1.manifest().copy()
1053 m2 = p2.manifest()
1053 m2 = p2.manifest()
1054
1054
1055 # check in files
1055 # check in files
1056 new = {}
1056 new = {}
1057 changed = []
1057 changed = []
1058 linkrev = len(self)
1058 linkrev = len(self)
1059 for f in sorted(ctx.modified() + ctx.added()):
1059 for f in sorted(ctx.modified() + ctx.added()):
1060 self.ui.note(f + "\n")
1060 self.ui.note(f + "\n")
1061 try:
1061 try:
1062 fctx = ctx[f]
1062 fctx = ctx[f]
1063 new[f] = self._filecommit(fctx, m1, m2, linkrev, trp,
1063 new[f] = self._filecommit(fctx, m1, m2, linkrev, trp,
1064 changed)
1064 changed)
1065 m1.set(f, fctx.flags())
1065 m1.set(f, fctx.flags())
1066 except OSError, inst:
1066 except OSError, inst:
1067 self.ui.warn(_("trouble committing %s!\n") % f)
1067 self.ui.warn(_("trouble committing %s!\n") % f)
1068 raise
1068 raise
1069 except IOError, inst:
1069 except IOError, inst:
1070 errcode = getattr(inst, 'errno', errno.ENOENT)
1070 errcode = getattr(inst, 'errno', errno.ENOENT)
1071 if error or errcode and errcode != errno.ENOENT:
1071 if error or errcode and errcode != errno.ENOENT:
1072 self.ui.warn(_("trouble committing %s!\n") % f)
1072 self.ui.warn(_("trouble committing %s!\n") % f)
1073 raise
1073 raise
1074 else:
1074 else:
1075 removed.append(f)
1075 removed.append(f)
1076
1076
1077 # update manifest
1077 # update manifest
1078 m1.update(new)
1078 m1.update(new)
1079 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1079 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1080 drop = [f for f in removed if f in m1]
1080 drop = [f for f in removed if f in m1]
1081 for f in drop:
1081 for f in drop:
1082 del m1[f]
1082 del m1[f]
1083 mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(),
1083 mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(),
1084 p2.manifestnode(), (new, drop))
1084 p2.manifestnode(), (new, drop))
1085 files = changed + removed
1085 files = changed + removed
1086 else:
1086 else:
1087 mn = p1.manifestnode()
1087 mn = p1.manifestnode()
1088 files = []
1088 files = []
1089
1089
1090 # update changelog
1090 # update changelog
1091 self.changelog.delayupdate()
1091 self.changelog.delayupdate()
1092 n = self.changelog.add(mn, files, ctx.description(),
1092 n = self.changelog.add(mn, files, ctx.description(),
1093 trp, p1.node(), p2.node(),
1093 trp, p1.node(), p2.node(),
1094 user, ctx.date(), ctx.extra().copy())
1094 user, ctx.date(), ctx.extra().copy())
1095 p = lambda: self.changelog.writepending() and self.root or ""
1095 p = lambda: self.changelog.writepending() and self.root or ""
1096 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1096 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1097 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1097 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1098 parent2=xp2, pending=p)
1098 parent2=xp2, pending=p)
1099 self.changelog.finalize(trp)
1099 self.changelog.finalize(trp)
1100 tr.close()
1100 tr.close()
1101
1101
1102 if self._branchcache:
1102 if self._branchcache:
1103 self.updatebranchcache()
1103 self.updatebranchcache()
1104 return n
1104 return n
1105 finally:
1105 finally:
1106 if tr:
1106 if tr:
1107 tr.release()
1107 tr.release()
1108 lock.release()
1108 lock.release()
1109
1109
1110 def destroyed(self):
1110 def destroyed(self):
1111 '''Inform the repository that nodes have been destroyed.
1111 '''Inform the repository that nodes have been destroyed.
1112 Intended for use by strip and rollback, so there's a common
1112 Intended for use by strip and rollback, so there's a common
1113 place for anything that has to be done after destroying history.'''
1113 place for anything that has to be done after destroying history.'''
1114 # XXX it might be nice if we could take the list of destroyed
1114 # XXX it might be nice if we could take the list of destroyed
1115 # nodes, but I don't see an easy way for rollback() to do that
1115 # nodes, but I don't see an easy way for rollback() to do that
1116
1116
1117 # Ensure the persistent tag cache is updated. Doing it now
1117 # Ensure the persistent tag cache is updated. Doing it now
1118 # means that the tag cache only has to worry about destroyed
1118 # means that the tag cache only has to worry about destroyed
1119 # heads immediately after a strip/rollback. That in turn
1119 # heads immediately after a strip/rollback. That in turn
1120 # guarantees that "cachetip == currenttip" (comparing both rev
1120 # guarantees that "cachetip == currenttip" (comparing both rev
1121 # and node) always means no nodes have been added or destroyed.
1121 # and node) always means no nodes have been added or destroyed.
1122
1122
1123 # XXX this is suboptimal when qrefresh'ing: we strip the current
1123 # XXX this is suboptimal when qrefresh'ing: we strip the current
1124 # head, refresh the tag cache, then immediately add a new head.
1124 # head, refresh the tag cache, then immediately add a new head.
1125 # But I think doing it this way is necessary for the "instant
1125 # But I think doing it this way is necessary for the "instant
1126 # tag cache retrieval" case to work.
1126 # tag cache retrieval" case to work.
1127 self.invalidatecaches()
1127 self.invalidatecaches()
1128
1128
1129 def walk(self, match, node=None):
1129 def walk(self, match, node=None):
1130 '''
1130 '''
1131 walk recursively through the directory tree or a given
1131 walk recursively through the directory tree or a given
1132 changeset, finding all files matched by the match
1132 changeset, finding all files matched by the match
1133 function
1133 function
1134 '''
1134 '''
1135 return self[node].walk(match)
1135 return self[node].walk(match)
1136
1136
1137 def status(self, node1='.', node2=None, match=None,
1137 def status(self, node1='.', node2=None, match=None,
1138 ignored=False, clean=False, unknown=False,
1138 ignored=False, clean=False, unknown=False,
1139 listsubrepos=False):
1139 listsubrepos=False):
1140 """return status of files between two nodes or node and working directory
1140 """return status of files between two nodes or node and working directory
1141
1141
1142 If node1 is None, use the first dirstate parent instead.
1142 If node1 is None, use the first dirstate parent instead.
1143 If node2 is None, compare node1 with working directory.
1143 If node2 is None, compare node1 with working directory.
1144 """
1144 """
1145
1145
1146 def mfmatches(ctx):
1146 def mfmatches(ctx):
1147 mf = ctx.manifest().copy()
1147 mf = ctx.manifest().copy()
1148 for fn in mf.keys():
1148 for fn in mf.keys():
1149 if not match(fn):
1149 if not match(fn):
1150 del mf[fn]
1150 del mf[fn]
1151 return mf
1151 return mf
1152
1152
1153 if isinstance(node1, context.changectx):
1153 if isinstance(node1, context.changectx):
1154 ctx1 = node1
1154 ctx1 = node1
1155 else:
1155 else:
1156 ctx1 = self[node1]
1156 ctx1 = self[node1]
1157 if isinstance(node2, context.changectx):
1157 if isinstance(node2, context.changectx):
1158 ctx2 = node2
1158 ctx2 = node2
1159 else:
1159 else:
1160 ctx2 = self[node2]
1160 ctx2 = self[node2]
1161
1161
1162 working = ctx2.rev() is None
1162 working = ctx2.rev() is None
1163 parentworking = working and ctx1 == self['.']
1163 parentworking = working and ctx1 == self['.']
1164 match = match or matchmod.always(self.root, self.getcwd())
1164 match = match or matchmod.always(self.root, self.getcwd())
1165 listignored, listclean, listunknown = ignored, clean, unknown
1165 listignored, listclean, listunknown = ignored, clean, unknown
1166
1166
1167 # load earliest manifest first for caching reasons
1167 # load earliest manifest first for caching reasons
1168 if not working and ctx2.rev() < ctx1.rev():
1168 if not working and ctx2.rev() < ctx1.rev():
1169 ctx2.manifest()
1169 ctx2.manifest()
1170
1170
1171 if not parentworking:
1171 if not parentworking:
1172 def bad(f, msg):
1172 def bad(f, msg):
1173 if f not in ctx1:
1173 if f not in ctx1:
1174 self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
1174 self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
1175 match.bad = bad
1175 match.bad = bad
1176
1176
1177 if working: # we need to scan the working dir
1177 if working: # we need to scan the working dir
1178 subrepos = []
1178 subrepos = []
1179 if '.hgsub' in self.dirstate:
1179 if '.hgsub' in self.dirstate:
1180 subrepos = ctx1.substate.keys()
1180 subrepos = ctx1.substate.keys()
1181 s = self.dirstate.status(match, subrepos, listignored,
1181 s = self.dirstate.status(match, subrepos, listignored,
1182 listclean, listunknown)
1182 listclean, listunknown)
1183 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
1183 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
1184
1184
1185 # check for any possibly clean files
1185 # check for any possibly clean files
1186 if parentworking and cmp:
1186 if parentworking and cmp:
1187 fixup = []
1187 fixup = []
1188 # do a full compare of any files that might have changed
1188 # do a full compare of any files that might have changed
1189 for f in sorted(cmp):
1189 for f in sorted(cmp):
1190 if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f)
1190 if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f)
1191 or ctx1[f].cmp(ctx2[f])):
1191 or ctx1[f].cmp(ctx2[f])):
1192 modified.append(f)
1192 modified.append(f)
1193 else:
1193 else:
1194 fixup.append(f)
1194 fixup.append(f)
1195
1195
1196 # update dirstate for files that are actually clean
1196 # update dirstate for files that are actually clean
1197 if fixup:
1197 if fixup:
1198 if listclean:
1198 if listclean:
1199 clean += fixup
1199 clean += fixup
1200
1200
1201 try:
1201 try:
1202 # updating the dirstate is optional
1202 # updating the dirstate is optional
1203 # so we don't wait on the lock
1203 # so we don't wait on the lock
1204 wlock = self.wlock(False)
1204 wlock = self.wlock(False)
1205 try:
1205 try:
1206 for f in fixup:
1206 for f in fixup:
1207 self.dirstate.normal(f)
1207 self.dirstate.normal(f)
1208 finally:
1208 finally:
1209 wlock.release()
1209 wlock.release()
1210 except error.LockError:
1210 except error.LockError:
1211 pass
1211 pass
1212
1212
1213 if not parentworking:
1213 if not parentworking:
1214 mf1 = mfmatches(ctx1)
1214 mf1 = mfmatches(ctx1)
1215 if working:
1215 if working:
1216 # we are comparing working dir against non-parent
1216 # we are comparing working dir against non-parent
1217 # generate a pseudo-manifest for the working dir
1217 # generate a pseudo-manifest for the working dir
1218 mf2 = mfmatches(self['.'])
1218 mf2 = mfmatches(self['.'])
1219 for f in cmp + modified + added:
1219 for f in cmp + modified + added:
1220 mf2[f] = None
1220 mf2[f] = None
1221 mf2.set(f, ctx2.flags(f))
1221 mf2.set(f, ctx2.flags(f))
1222 for f in removed:
1222 for f in removed:
1223 if f in mf2:
1223 if f in mf2:
1224 del mf2[f]
1224 del mf2[f]
1225 else:
1225 else:
1226 # we are comparing two revisions
1226 # we are comparing two revisions
1227 deleted, unknown, ignored = [], [], []
1227 deleted, unknown, ignored = [], [], []
1228 mf2 = mfmatches(ctx2)
1228 mf2 = mfmatches(ctx2)
1229
1229
1230 modified, added, clean = [], [], []
1230 modified, added, clean = [], [], []
1231 for fn in mf2:
1231 for fn in mf2:
1232 if fn in mf1:
1232 if fn in mf1:
1233 if (fn not in deleted and
1233 if (fn not in deleted and
1234 (mf1.flags(fn) != mf2.flags(fn) or
1234 (mf1.flags(fn) != mf2.flags(fn) or
1235 (mf1[fn] != mf2[fn] and
1235 (mf1[fn] != mf2[fn] and
1236 (mf2[fn] or ctx1[fn].cmp(ctx2[fn]))))):
1236 (mf2[fn] or ctx1[fn].cmp(ctx2[fn]))))):
1237 modified.append(fn)
1237 modified.append(fn)
1238 elif listclean:
1238 elif listclean:
1239 clean.append(fn)
1239 clean.append(fn)
1240 del mf1[fn]
1240 del mf1[fn]
1241 elif fn not in deleted:
1241 elif fn not in deleted:
1242 added.append(fn)
1242 added.append(fn)
1243 removed = mf1.keys()
1243 removed = mf1.keys()
1244
1244
1245 r = modified, added, removed, deleted, unknown, ignored, clean
1245 r = modified, added, removed, deleted, unknown, ignored, clean
1246
1246
1247 if listsubrepos:
1247 if listsubrepos:
1248 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
1248 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
1249 if working:
1249 if working:
1250 rev2 = None
1250 rev2 = None
1251 else:
1251 else:
1252 rev2 = ctx2.substate[subpath][1]
1252 rev2 = ctx2.substate[subpath][1]
1253 try:
1253 try:
1254 submatch = matchmod.narrowmatcher(subpath, match)
1254 submatch = matchmod.narrowmatcher(subpath, match)
1255 s = sub.status(rev2, match=submatch, ignored=listignored,
1255 s = sub.status(rev2, match=submatch, ignored=listignored,
1256 clean=listclean, unknown=listunknown,
1256 clean=listclean, unknown=listunknown,
1257 listsubrepos=True)
1257 listsubrepos=True)
1258 for rfiles, sfiles in zip(r, s):
1258 for rfiles, sfiles in zip(r, s):
1259 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
1259 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
1260 except error.LookupError:
1260 except error.LookupError:
1261 self.ui.status(_("skipping missing subrepository: %s\n")
1261 self.ui.status(_("skipping missing subrepository: %s\n")
1262 % subpath)
1262 % subpath)
1263
1263
1264 for l in r:
1264 for l in r:
1265 l.sort()
1265 l.sort()
1266 return r
1266 return r
1267
1267
1268 def heads(self, start=None):
1268 def heads(self, start=None):
1269 heads = self.changelog.heads(start)
1269 heads = self.changelog.heads(start)
1270 # sort the output in rev descending order
1270 # sort the output in rev descending order
1271 return sorted(heads, key=self.changelog.rev, reverse=True)
1271 return sorted(heads, key=self.changelog.rev, reverse=True)
1272
1272
1273 def branchheads(self, branch=None, start=None, closed=False):
1273 def branchheads(self, branch=None, start=None, closed=False):
1274 '''return a (possibly filtered) list of heads for the given branch
1274 '''return a (possibly filtered) list of heads for the given branch
1275
1275
1276 Heads are returned in topological order, from newest to oldest.
1276 Heads are returned in topological order, from newest to oldest.
1277 If branch is None, use the dirstate branch.
1277 If branch is None, use the dirstate branch.
1278 If start is not None, return only heads reachable from start.
1278 If start is not None, return only heads reachable from start.
1279 If closed is True, return heads that are marked as closed as well.
1279 If closed is True, return heads that are marked as closed as well.
1280 '''
1280 '''
1281 if branch is None:
1281 if branch is None:
1282 branch = self[None].branch()
1282 branch = self[None].branch()
1283 branches = self.branchmap()
1283 branches = self.branchmap()
1284 if branch not in branches:
1284 if branch not in branches:
1285 return []
1285 return []
1286 # the cache returns heads ordered lowest to highest
1286 # the cache returns heads ordered lowest to highest
1287 bheads = list(reversed(branches[branch]))
1287 bheads = list(reversed(branches[branch]))
1288 if start is not None:
1288 if start is not None:
1289 # filter out the heads that cannot be reached from startrev
1289 # filter out the heads that cannot be reached from startrev
1290 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1290 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1291 bheads = [h for h in bheads if h in fbheads]
1291 bheads = [h for h in bheads if h in fbheads]
1292 if not closed:
1292 if not closed:
1293 bheads = [h for h in bheads if
1293 bheads = [h for h in bheads if
1294 ('close' not in self.changelog.read(h)[5])]
1294 ('close' not in self.changelog.read(h)[5])]
1295 return bheads
1295 return bheads
1296
1296
1297 def branches(self, nodes):
1297 def branches(self, nodes):
1298 if not nodes:
1298 if not nodes:
1299 nodes = [self.changelog.tip()]
1299 nodes = [self.changelog.tip()]
1300 b = []
1300 b = []
1301 for n in nodes:
1301 for n in nodes:
1302 t = n
1302 t = n
1303 while 1:
1303 while 1:
1304 p = self.changelog.parents(n)
1304 p = self.changelog.parents(n)
1305 if p[1] != nullid or p[0] == nullid:
1305 if p[1] != nullid or p[0] == nullid:
1306 b.append((t, n, p[0], p[1]))
1306 b.append((t, n, p[0], p[1]))
1307 break
1307 break
1308 n = p[0]
1308 n = p[0]
1309 return b
1309 return b
1310
1310
1311 def between(self, pairs):
1311 def between(self, pairs):
1312 r = []
1312 r = []
1313
1313
1314 for top, bottom in pairs:
1314 for top, bottom in pairs:
1315 n, l, i = top, [], 0
1315 n, l, i = top, [], 0
1316 f = 1
1316 f = 1
1317
1317
1318 while n != bottom and n != nullid:
1318 while n != bottom and n != nullid:
1319 p = self.changelog.parents(n)[0]
1319 p = self.changelog.parents(n)[0]
1320 if i == f:
1320 if i == f:
1321 l.append(n)
1321 l.append(n)
1322 f = f * 2
1322 f = f * 2
1323 n = p
1323 n = p
1324 i += 1
1324 i += 1
1325
1325
1326 r.append(l)
1326 r.append(l)
1327
1327
1328 return r
1328 return r
1329
1329
1330 def pull(self, remote, heads=None, force=False):
1330 def pull(self, remote, heads=None, force=False):
1331 lock = self.lock()
1331 lock = self.lock()
1332 try:
1332 try:
1333 tmp = discovery.findcommonincoming(self, remote, heads=heads,
1333 tmp = discovery.findcommonincoming(self, remote, heads=heads,
1334 force=force)
1334 force=force)
1335 common, fetch, rheads = tmp
1335 common, fetch, rheads = tmp
1336 if not fetch:
1336 if not fetch:
1337 self.ui.status(_("no changes found\n"))
1337 self.ui.status(_("no changes found\n"))
1338 result = 0
1338 result = 0
1339 else:
1339 else:
1340 if heads is None and list(common) == [nullid]:
1340 if heads is None and list(common) == [nullid]:
1341 self.ui.status(_("requesting all changes\n"))
1341 self.ui.status(_("requesting all changes\n"))
1342 elif heads is None and remote.capable('changegroupsubset'):
1342 elif heads is None and remote.capable('changegroupsubset'):
1343 # issue1320, avoid a race if remote changed after discovery
1343 # issue1320, avoid a race if remote changed after discovery
1344 heads = rheads
1344 heads = rheads
1345
1345
1346 if remote.capable('getbundle'):
1346 if remote.capable('getbundle'):
1347 cg = remote.getbundle('pull', common=common,
1347 cg = remote.getbundle('pull', common=common,
1348 heads=heads or rheads)
1348 heads=heads or rheads)
1349 elif heads is None:
1349 elif heads is None:
1350 cg = remote.changegroup(fetch, 'pull')
1350 cg = remote.changegroup(fetch, 'pull')
1351 elif not remote.capable('changegroupsubset'):
1351 elif not remote.capable('changegroupsubset'):
1352 raise util.Abort(_("partial pull cannot be done because "
1352 raise util.Abort(_("partial pull cannot be done because "
1353 "other repository doesn't support "
1353 "other repository doesn't support "
1354 "changegroupsubset."))
1354 "changegroupsubset."))
1355 else:
1355 else:
1356 cg = remote.changegroupsubset(fetch, heads, 'pull')
1356 cg = remote.changegroupsubset(fetch, heads, 'pull')
1357 result = self.addchangegroup(cg, 'pull', remote.url(),
1357 result = self.addchangegroup(cg, 'pull', remote.url(),
1358 lock=lock)
1358 lock=lock)
1359 finally:
1359 finally:
1360 lock.release()
1360 lock.release()
1361
1361
1362 return result
1362 return result
1363
1363
1364 def checkpush(self, force, revs):
1364 def checkpush(self, force, revs):
1365 """Extensions can override this function if additional checks have
1365 """Extensions can override this function if additional checks have
1366 to be performed before pushing, or call it if they override push
1366 to be performed before pushing, or call it if they override push
1367 command.
1367 command.
1368 """
1368 """
1369 pass
1369 pass
1370
1370
1371 def push(self, remote, force=False, revs=None, newbranch=False):
1371 def push(self, remote, force=False, revs=None, newbranch=False):
1372 '''Push outgoing changesets (limited by revs) from the current
1372 '''Push outgoing changesets (limited by revs) from the current
1373 repository to remote. Return an integer:
1373 repository to remote. Return an integer:
1374 - 0 means HTTP error *or* nothing to push
1374 - 0 means HTTP error *or* nothing to push
1375 - 1 means we pushed and remote head count is unchanged *or*
1375 - 1 means we pushed and remote head count is unchanged *or*
1376 we have outgoing changesets but refused to push
1376 we have outgoing changesets but refused to push
1377 - other values as described by addchangegroup()
1377 - other values as described by addchangegroup()
1378 '''
1378 '''
1379 # there are two ways to push to remote repo:
1379 # there are two ways to push to remote repo:
1380 #
1380 #
1381 # addchangegroup assumes local user can lock remote
1381 # addchangegroup assumes local user can lock remote
1382 # repo (local filesystem, old ssh servers).
1382 # repo (local filesystem, old ssh servers).
1383 #
1383 #
1384 # unbundle assumes local user cannot lock remote repo (new ssh
1384 # unbundle assumes local user cannot lock remote repo (new ssh
1385 # servers, http servers).
1385 # servers, http servers).
1386
1386
1387 self.checkpush(force, revs)
1387 self.checkpush(force, revs)
1388 lock = None
1388 lock = None
1389 unbundle = remote.capable('unbundle')
1389 unbundle = remote.capable('unbundle')
1390 if not unbundle:
1390 if not unbundle:
1391 lock = remote.lock()
1391 lock = remote.lock()
1392 try:
1392 try:
1393 cg, remote_heads = discovery.prepush(self, remote, force, revs,
1393 cg, remote_heads = discovery.prepush(self, remote, force, revs,
1394 newbranch)
1394 newbranch)
1395 ret = remote_heads
1395 ret = remote_heads
1396 if cg is not None:
1396 if cg is not None:
1397 if unbundle:
1397 if unbundle:
1398 # local repo finds heads on server, finds out what
1398 # local repo finds heads on server, finds out what
1399 # revs it must push. once revs transferred, if server
1399 # revs it must push. once revs transferred, if server
1400 # finds it has different heads (someone else won
1400 # finds it has different heads (someone else won
1401 # commit/push race), server aborts.
1401 # commit/push race), server aborts.
1402 if force:
1402 if force:
1403 remote_heads = ['force']
1403 remote_heads = ['force']
1404 # ssh: return remote's addchangegroup()
1404 # ssh: return remote's addchangegroup()
1405 # http: return remote's addchangegroup() or 0 for error
1405 # http: return remote's addchangegroup() or 0 for error
1406 ret = remote.unbundle(cg, remote_heads, 'push')
1406 ret = remote.unbundle(cg, remote_heads, 'push')
1407 else:
1407 else:
1408 # we return an integer indicating remote head count change
1408 # we return an integer indicating remote head count change
1409 ret = remote.addchangegroup(cg, 'push', self.url(),
1409 ret = remote.addchangegroup(cg, 'push', self.url(),
1410 lock=lock)
1410 lock=lock)
1411 finally:
1411 finally:
1412 if lock is not None:
1412 if lock is not None:
1413 lock.release()
1413 lock.release()
1414
1414
1415 self.ui.debug("checking for updated bookmarks\n")
1415 self.ui.debug("checking for updated bookmarks\n")
1416 rb = remote.listkeys('bookmarks')
1416 rb = remote.listkeys('bookmarks')
1417 for k in rb.keys():
1417 for k in rb.keys():
1418 if k in self._bookmarks:
1418 if k in self._bookmarks:
1419 nr, nl = rb[k], hex(self._bookmarks[k])
1419 nr, nl = rb[k], hex(self._bookmarks[k])
1420 if nr in self:
1420 if nr in self:
1421 cr = self[nr]
1421 cr = self[nr]
1422 cl = self[nl]
1422 cl = self[nl]
1423 if cl in cr.descendants():
1423 if cl in cr.descendants():
1424 r = remote.pushkey('bookmarks', k, nr, nl)
1424 r = remote.pushkey('bookmarks', k, nr, nl)
1425 if r:
1425 if r:
1426 self.ui.status(_("updating bookmark %s\n") % k)
1426 self.ui.status(_("updating bookmark %s\n") % k)
1427 else:
1427 else:
1428 self.ui.warn(_('updating bookmark %s'
1428 self.ui.warn(_('updating bookmark %s'
1429 ' failed!\n') % k)
1429 ' failed!\n') % k)
1430
1430
1431 return ret
1431 return ret
1432
1432
1433 def changegroupinfo(self, nodes, source):
1433 def changegroupinfo(self, nodes, source):
1434 if self.ui.verbose or source == 'bundle':
1434 if self.ui.verbose or source == 'bundle':
1435 self.ui.status(_("%d changesets found\n") % len(nodes))
1435 self.ui.status(_("%d changesets found\n") % len(nodes))
1436 if self.ui.debugflag:
1436 if self.ui.debugflag:
1437 self.ui.debug("list of changesets:\n")
1437 self.ui.debug("list of changesets:\n")
1438 for node in nodes:
1438 for node in nodes:
1439 self.ui.debug("%s\n" % hex(node))
1439 self.ui.debug("%s\n" % hex(node))
1440
1440
1441 def changegroupsubset(self, bases, heads, source):
1441 def changegroupsubset(self, bases, heads, source):
1442 """Compute a changegroup consisting of all the nodes that are
1442 """Compute a changegroup consisting of all the nodes that are
1443 descendents of any of the bases and ancestors of any of the heads.
1443 descendents of any of the bases and ancestors of any of the heads.
1444 Return a chunkbuffer object whose read() method will return
1444 Return a chunkbuffer object whose read() method will return
1445 successive changegroup chunks.
1445 successive changegroup chunks.
1446
1446
1447 It is fairly complex as determining which filenodes and which
1447 It is fairly complex as determining which filenodes and which
1448 manifest nodes need to be included for the changeset to be complete
1448 manifest nodes need to be included for the changeset to be complete
1449 is non-trivial.
1449 is non-trivial.
1450
1450
1451 Another wrinkle is doing the reverse, figuring out which changeset in
1451 Another wrinkle is doing the reverse, figuring out which changeset in
1452 the changegroup a particular filenode or manifestnode belongs to.
1452 the changegroup a particular filenode or manifestnode belongs to.
1453 """
1453 """
1454 cl = self.changelog
1454 cl = self.changelog
1455 if not bases:
1455 if not bases:
1456 bases = [nullid]
1456 bases = [nullid]
1457 csets, bases, heads = cl.nodesbetween(bases, heads)
1457 csets, bases, heads = cl.nodesbetween(bases, heads)
1458 # We assume that all ancestors of bases are known
1458 # We assume that all ancestors of bases are known
1459 common = set(cl.ancestors(*[cl.rev(n) for n in bases]))
1459 common = set(cl.ancestors(*[cl.rev(n) for n in bases]))
1460 return self._changegroupsubset(common, csets, heads, source)
1460 return self._changegroupsubset(common, csets, heads, source)
1461
1461
1462 def getbundle(self, source, heads=None, common=None):
1462 def getbundle(self, source, heads=None, common=None):
1463 """Like changegroupsubset, but returns the set difference between the
1463 """Like changegroupsubset, but returns the set difference between the
1464 ancestors of heads and the ancestors common.
1464 ancestors of heads and the ancestors common.
1465
1465
1466 If heads is None, use the local heads. If common is None, use [nullid].
1466 If heads is None, use the local heads. If common is None, use [nullid].
1467
1467
1468 The nodes in common might not all be known locally due to the way the
1468 The nodes in common might not all be known locally due to the way the
1469 current discovery protocol works.
1469 current discovery protocol works.
1470 """
1470 """
1471 cl = self.changelog
1471 cl = self.changelog
1472 if common:
1472 if common:
1473 nm = cl.nodemap
1473 nm = cl.nodemap
1474 common = [n for n in common if n in nm]
1474 common = [n for n in common if n in nm]
1475 else:
1475 else:
1476 common = [nullid]
1476 common = [nullid]
1477 if not heads:
1477 if not heads:
1478 heads = cl.heads()
1478 heads = cl.heads()
1479 common, missing = cl.findcommonmissing(common, heads)
1479 common, missing = cl.findcommonmissing(common, heads)
1480 if not missing:
1480 if not missing:
1481 return None
1481 return None
1482 return self._changegroupsubset(common, missing, heads, source)
1482 return self._changegroupsubset(common, missing, heads, source)
1483
1483
1484 def _changegroupsubset(self, commonrevs, csets, heads, source):
1484 def _changegroupsubset(self, commonrevs, csets, heads, source):
1485
1485
1486 cl = self.changelog
1486 cl = self.changelog
1487 mf = self.manifest
1487 mf = self.manifest
1488 mfs = {} # needed manifests
1488 mfs = {} # needed manifests
1489 fnodes = {} # needed file nodes
1489 fnodes = {} # needed file nodes
1490 changedfiles = set()
1490 changedfiles = set()
1491 fstate = ['', {}]
1491 fstate = ['', {}]
1492 count = [0]
1492 count = [0]
1493
1493
1494 # can we go through the fast path ?
1494 # can we go through the fast path ?
1495 heads.sort()
1495 heads.sort()
1496 if heads == sorted(self.heads()):
1496 if heads == sorted(self.heads()):
1497 return self._changegroup(csets, source)
1497 return self._changegroup(csets, source)
1498
1498
1499 # slow path
1499 # slow path
1500 self.hook('preoutgoing', throw=True, source=source)
1500 self.hook('preoutgoing', throw=True, source=source)
1501 self.changegroupinfo(csets, source)
1501 self.changegroupinfo(csets, source)
1502
1502
1503 # filter any nodes that claim to be part of the known set
1503 # filter any nodes that claim to be part of the known set
1504 def prune(revlog, missing):
1504 def prune(revlog, missing):
1505 for n in missing:
1505 for n in missing:
1506 if revlog.linkrev(revlog.rev(n)) not in commonrevs:
1506 if revlog.linkrev(revlog.rev(n)) not in commonrevs:
1507 yield n
1507 yield n
1508
1508
1509 def lookup(revlog, x):
1509 def lookup(revlog, x):
1510 if revlog == cl:
1510 if revlog == cl:
1511 c = cl.read(x)
1511 c = cl.read(x)
1512 changedfiles.update(c[3])
1512 changedfiles.update(c[3])
1513 mfs.setdefault(c[0], x)
1513 mfs.setdefault(c[0], x)
1514 count[0] += 1
1514 count[0] += 1
1515 self.ui.progress(_('bundling'), count[0], unit=_('changesets'))
1515 self.ui.progress(_('bundling'), count[0], unit=_('changesets'))
1516 return x
1516 return x
1517 elif revlog == mf:
1517 elif revlog == mf:
1518 clnode = mfs[x]
1518 clnode = mfs[x]
1519 mdata = mf.readfast(x)
1519 mdata = mf.readfast(x)
1520 for f in changedfiles:
1520 for f in changedfiles:
1521 if f in mdata:
1521 if f in mdata:
1522 fnodes.setdefault(f, {}).setdefault(mdata[f], clnode)
1522 fnodes.setdefault(f, {}).setdefault(mdata[f], clnode)
1523 count[0] += 1
1523 count[0] += 1
1524 self.ui.progress(_('bundling'), count[0],
1524 self.ui.progress(_('bundling'), count[0],
1525 unit=_('manifests'), total=len(mfs))
1525 unit=_('manifests'), total=len(mfs))
1526 return mfs[x]
1526 return mfs[x]
1527 else:
1527 else:
1528 self.ui.progress(
1528 self.ui.progress(
1529 _('bundling'), count[0], item=fstate[0],
1529 _('bundling'), count[0], item=fstate[0],
1530 unit=_('files'), total=len(changedfiles))
1530 unit=_('files'), total=len(changedfiles))
1531 return fstate[1][x]
1531 return fstate[1][x]
1532
1532
1533 bundler = changegroup.bundle10(lookup)
1533 bundler = changegroup.bundle10(lookup)
1534
1534
1535 def gengroup():
1535 def gengroup():
1536 # Create a changenode group generator that will call our functions
1536 # Create a changenode group generator that will call our functions
1537 # back to lookup the owning changenode and collect information.
1537 # back to lookup the owning changenode and collect information.
1538 for chunk in cl.group(csets, bundler):
1538 for chunk in cl.group(csets, bundler):
1539 yield chunk
1539 yield chunk
1540 self.ui.progress(_('bundling'), None)
1540 self.ui.progress(_('bundling'), None)
1541
1541
1542 # Create a generator for the manifestnodes that calls our lookup
1542 # Create a generator for the manifestnodes that calls our lookup
1543 # and data collection functions back.
1543 # and data collection functions back.
1544 count[0] = 0
1544 count[0] = 0
1545 for chunk in mf.group(prune(mf, mfs), bundler):
1545 for chunk in mf.group(prune(mf, mfs), bundler):
1546 yield chunk
1546 yield chunk
1547 self.ui.progress(_('bundling'), None)
1547 self.ui.progress(_('bundling'), None)
1548
1548
1549 mfs.clear()
1549 mfs.clear()
1550
1550
1551 # Go through all our files in order sorted by name.
1551 # Go through all our files in order sorted by name.
1552 count[0] = 0
1552 count[0] = 0
1553 for fname in sorted(changedfiles):
1553 for fname in sorted(changedfiles):
1554 filerevlog = self.file(fname)
1554 filerevlog = self.file(fname)
1555 if not len(filerevlog):
1555 if not len(filerevlog):
1556 raise util.Abort(_("empty or missing revlog for %s") % fname)
1556 raise util.Abort(_("empty or missing revlog for %s") % fname)
1557 fstate[0] = fname
1557 fstate[0] = fname
1558 fstate[1] = fnodes.pop(fname, {})
1558 fstate[1] = fnodes.pop(fname, {})
1559 first = True
1559 first = True
1560
1560
1561 for chunk in filerevlog.group(prune(filerevlog, fstate[1]),
1561 for chunk in filerevlog.group(prune(filerevlog, fstate[1]),
1562 bundler):
1562 bundler):
1563 if first:
1563 if first:
1564 if chunk == bundler.close():
1564 if chunk == bundler.close():
1565 break
1565 break
1566 count[0] += 1
1566 count[0] += 1
1567 yield bundler.fileheader(fname)
1567 yield bundler.fileheader(fname)
1568 first = False
1568 first = False
1569 yield chunk
1569 yield chunk
1570 # Signal that no more groups are left.
1570 # Signal that no more groups are left.
1571 yield bundler.close()
1571 yield bundler.close()
1572 self.ui.progress(_('bundling'), None)
1572 self.ui.progress(_('bundling'), None)
1573
1573
1574 if csets:
1574 if csets:
1575 self.hook('outgoing', node=hex(csets[0]), source=source)
1575 self.hook('outgoing', node=hex(csets[0]), source=source)
1576
1576
1577 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
1577 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
1578
1578
1579 def changegroup(self, basenodes, source):
1579 def changegroup(self, basenodes, source):
1580 # to avoid a race we use changegroupsubset() (issue1320)
1580 # to avoid a race we use changegroupsubset() (issue1320)
1581 return self.changegroupsubset(basenodes, self.heads(), source)
1581 return self.changegroupsubset(basenodes, self.heads(), source)
1582
1582
1583 def _changegroup(self, nodes, source):
1583 def _changegroup(self, nodes, source):
1584 """Compute the changegroup of all nodes that we have that a recipient
1584 """Compute the changegroup of all nodes that we have that a recipient
1585 doesn't. Return a chunkbuffer object whose read() method will return
1585 doesn't. Return a chunkbuffer object whose read() method will return
1586 successive changegroup chunks.
1586 successive changegroup chunks.
1587
1587
1588 This is much easier than the previous function as we can assume that
1588 This is much easier than the previous function as we can assume that
1589 the recipient has any changenode we aren't sending them.
1589 the recipient has any changenode we aren't sending them.
1590
1590
1591 nodes is the set of nodes to send"""
1591 nodes is the set of nodes to send"""
1592
1592
1593 cl = self.changelog
1593 cl = self.changelog
1594 mf = self.manifest
1594 mf = self.manifest
1595 mfs = {}
1595 mfs = {}
1596 changedfiles = set()
1596 changedfiles = set()
1597 fstate = ['']
1597 fstate = ['']
1598 count = [0]
1598 count = [0]
1599
1599
1600 self.hook('preoutgoing', throw=True, source=source)
1600 self.hook('preoutgoing', throw=True, source=source)
1601 self.changegroupinfo(nodes, source)
1601 self.changegroupinfo(nodes, source)
1602
1602
1603 revset = set([cl.rev(n) for n in nodes])
1603 revset = set([cl.rev(n) for n in nodes])
1604
1604
1605 def gennodelst(log):
1605 def gennodelst(log):
1606 for r in log:
1606 for r in log:
1607 if log.linkrev(r) in revset:
1607 if log.linkrev(r) in revset:
1608 yield log.node(r)
1608 yield log.node(r)
1609
1609
1610 def lookup(revlog, x):
1610 def lookup(revlog, x):
1611 if revlog == cl:
1611 if revlog == cl:
1612 c = cl.read(x)
1612 c = cl.read(x)
1613 changedfiles.update(c[3])
1613 changedfiles.update(c[3])
1614 mfs.setdefault(c[0], x)
1614 mfs.setdefault(c[0], x)
1615 count[0] += 1
1615 count[0] += 1
1616 self.ui.progress(_('bundling'), count[0], unit=_('changesets'))
1616 self.ui.progress(_('bundling'), count[0], unit=_('changesets'))
1617 return x
1617 return x
1618 elif revlog == mf:
1618 elif revlog == mf:
1619 count[0] += 1
1619 count[0] += 1
1620 self.ui.progress(_('bundling'), count[0],
1620 self.ui.progress(_('bundling'), count[0],
1621 unit=_('manifests'), total=len(mfs))
1621 unit=_('manifests'), total=len(mfs))
1622 return cl.node(revlog.linkrev(revlog.rev(x)))
1622 return cl.node(revlog.linkrev(revlog.rev(x)))
1623 else:
1623 else:
1624 self.ui.progress(
1624 self.ui.progress(
1625 _('bundling'), count[0], item=fstate[0],
1625 _('bundling'), count[0], item=fstate[0],
1626 total=len(changedfiles), unit=_('files'))
1626 total=len(changedfiles), unit=_('files'))
1627 return cl.node(revlog.linkrev(revlog.rev(x)))
1627 return cl.node(revlog.linkrev(revlog.rev(x)))
1628
1628
1629 bundler = changegroup.bundle10(lookup)
1629 bundler = changegroup.bundle10(lookup)
1630
1630
1631 def gengroup():
1631 def gengroup():
1632 '''yield a sequence of changegroup chunks (strings)'''
1632 '''yield a sequence of changegroup chunks (strings)'''
1633 # construct a list of all changed files
1633 # construct a list of all changed files
1634
1634
1635 for chunk in cl.group(nodes, bundler):
1635 for chunk in cl.group(nodes, bundler):
1636 yield chunk
1636 yield chunk
1637 self.ui.progress(_('bundling'), None)
1637 self.ui.progress(_('bundling'), None)
1638
1638
1639 count[0] = 0
1639 count[0] = 0
1640 for chunk in mf.group(gennodelst(mf), bundler):
1640 for chunk in mf.group(gennodelst(mf), bundler):
1641 yield chunk
1641 yield chunk
1642 self.ui.progress(_('bundling'), None)
1642 self.ui.progress(_('bundling'), None)
1643
1643
1644 count[0] = 0
1644 count[0] = 0
1645 for fname in sorted(changedfiles):
1645 for fname in sorted(changedfiles):
1646 filerevlog = self.file(fname)
1646 filerevlog = self.file(fname)
1647 if not len(filerevlog):
1647 if not len(filerevlog):
1648 raise util.Abort(_("empty or missing revlog for %s") % fname)
1648 raise util.Abort(_("empty or missing revlog for %s") % fname)
1649 fstate[0] = fname
1649 fstate[0] = fname
1650 first = True
1650 first = True
1651 for chunk in filerevlog.group(gennodelst(filerevlog), bundler):
1651 for chunk in filerevlog.group(gennodelst(filerevlog), bundler):
1652 if first:
1652 if first:
1653 if chunk == bundler.close():
1653 if chunk == bundler.close():
1654 break
1654 break
1655 count[0] += 1
1655 count[0] += 1
1656 yield bundler.fileheader(fname)
1656 yield bundler.fileheader(fname)
1657 first = False
1657 first = False
1658 yield chunk
1658 yield chunk
1659 yield bundler.close()
1659 yield bundler.close()
1660 self.ui.progress(_('bundling'), None)
1660 self.ui.progress(_('bundling'), None)
1661
1661
1662 if nodes:
1662 if nodes:
1663 self.hook('outgoing', node=hex(nodes[0]), source=source)
1663 self.hook('outgoing', node=hex(nodes[0]), source=source)
1664
1664
1665 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
1665 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
1666
1666
1667 def addchangegroup(self, source, srctype, url, emptyok=False, lock=None):
1667 def addchangegroup(self, source, srctype, url, emptyok=False, lock=None):
1668 """Add the changegroup returned by source.read() to this repo.
1668 """Add the changegroup returned by source.read() to this repo.
1669 srctype is a string like 'push', 'pull', or 'unbundle'. url is
1669 srctype is a string like 'push', 'pull', or 'unbundle'. url is
1670 the URL of the repo where this changegroup is coming from.
1670 the URL of the repo where this changegroup is coming from.
1671 If lock is not None, the function takes ownership of the lock
1671 If lock is not None, the function takes ownership of the lock
1672 and releases it after the changegroup is added.
1672 and releases it after the changegroup is added.
1673
1673
1674 Return an integer summarizing the change to this repo:
1674 Return an integer summarizing the change to this repo:
1675 - nothing changed or no source: 0
1675 - nothing changed or no source: 0
1676 - more heads than before: 1+added heads (2..n)
1676 - more heads than before: 1+added heads (2..n)
1677 - fewer heads than before: -1-removed heads (-2..-n)
1677 - fewer heads than before: -1-removed heads (-2..-n)
1678 - number of heads stays the same: 1
1678 - number of heads stays the same: 1
1679 """
1679 """
1680 def csmap(x):
1680 def csmap(x):
1681 self.ui.debug("add changeset %s\n" % short(x))
1681 self.ui.debug("add changeset %s\n" % short(x))
1682 return len(cl)
1682 return len(cl)
1683
1683
1684 def revmap(x):
1684 def revmap(x):
1685 return cl.rev(x)
1685 return cl.rev(x)
1686
1686
1687 if not source:
1687 if not source:
1688 return 0
1688 return 0
1689
1689
1690 self.hook('prechangegroup', throw=True, source=srctype, url=url)
1690 self.hook('prechangegroup', throw=True, source=srctype, url=url)
1691
1691
1692 changesets = files = revisions = 0
1692 changesets = files = revisions = 0
1693 efiles = set()
1693 efiles = set()
1694
1694
1695 # write changelog data to temp files so concurrent readers will not see
1695 # write changelog data to temp files so concurrent readers will not see
1696 # inconsistent view
1696 # inconsistent view
1697 cl = self.changelog
1697 cl = self.changelog
1698 cl.delayupdate()
1698 cl.delayupdate()
1699 oldheads = cl.heads()
1699 oldheads = cl.heads()
1700
1700
1701 tr = self.transaction("\n".join([srctype, util.hidepassword(url)]))
1701 tr = self.transaction("\n".join([srctype, util.hidepassword(url)]))
1702 try:
1702 try:
1703 trp = weakref.proxy(tr)
1703 trp = weakref.proxy(tr)
1704 # pull off the changeset group
1704 # pull off the changeset group
1705 self.ui.status(_("adding changesets\n"))
1705 self.ui.status(_("adding changesets\n"))
1706 clstart = len(cl)
1706 clstart = len(cl)
1707 class prog(object):
1707 class prog(object):
1708 step = _('changesets')
1708 step = _('changesets')
1709 count = 1
1709 count = 1
1710 ui = self.ui
1710 ui = self.ui
1711 total = None
1711 total = None
1712 def __call__(self):
1712 def __call__(self):
1713 self.ui.progress(self.step, self.count, unit=_('chunks'),
1713 self.ui.progress(self.step, self.count, unit=_('chunks'),
1714 total=self.total)
1714 total=self.total)
1715 self.count += 1
1715 self.count += 1
1716 pr = prog()
1716 pr = prog()
1717 source.callback = pr
1717 source.callback = pr
1718
1718
1719 source.changelogheader()
1719 source.changelogheader()
1720 if (cl.addgroup(source, csmap, trp) is None
1720 if (cl.addgroup(source, csmap, trp) is None
1721 and not emptyok):
1721 and not emptyok):
1722 raise util.Abort(_("received changelog group is empty"))
1722 raise util.Abort(_("received changelog group is empty"))
1723 clend = len(cl)
1723 clend = len(cl)
1724 changesets = clend - clstart
1724 changesets = clend - clstart
1725 for c in xrange(clstart, clend):
1725 for c in xrange(clstart, clend):
1726 efiles.update(self[c].files())
1726 efiles.update(self[c].files())
1727 efiles = len(efiles)
1727 efiles = len(efiles)
1728 self.ui.progress(_('changesets'), None)
1728 self.ui.progress(_('changesets'), None)
1729
1729
1730 # pull off the manifest group
1730 # pull off the manifest group
1731 self.ui.status(_("adding manifests\n"))
1731 self.ui.status(_("adding manifests\n"))
1732 pr.step = _('manifests')
1732 pr.step = _('manifests')
1733 pr.count = 1
1733 pr.count = 1
1734 pr.total = changesets # manifests <= changesets
1734 pr.total = changesets # manifests <= changesets
1735 # no need to check for empty manifest group here:
1735 # no need to check for empty manifest group here:
1736 # if the result of the merge of 1 and 2 is the same in 3 and 4,
1736 # if the result of the merge of 1 and 2 is the same in 3 and 4,
1737 # no new manifest will be created and the manifest group will
1737 # no new manifest will be created and the manifest group will
1738 # be empty during the pull
1738 # be empty during the pull
1739 source.manifestheader()
1739 source.manifestheader()
1740 self.manifest.addgroup(source, revmap, trp)
1740 self.manifest.addgroup(source, revmap, trp)
1741 self.ui.progress(_('manifests'), None)
1741 self.ui.progress(_('manifests'), None)
1742
1742
1743 needfiles = {}
1743 needfiles = {}
1744 if self.ui.configbool('server', 'validate', default=False):
1744 if self.ui.configbool('server', 'validate', default=False):
1745 # validate incoming csets have their manifests
1745 # validate incoming csets have their manifests
1746 for cset in xrange(clstart, clend):
1746 for cset in xrange(clstart, clend):
1747 mfest = self.changelog.read(self.changelog.node(cset))[0]
1747 mfest = self.changelog.read(self.changelog.node(cset))[0]
1748 mfest = self.manifest.readdelta(mfest)
1748 mfest = self.manifest.readdelta(mfest)
1749 # store file nodes we must see
1749 # store file nodes we must see
1750 for f, n in mfest.iteritems():
1750 for f, n in mfest.iteritems():
1751 needfiles.setdefault(f, set()).add(n)
1751 needfiles.setdefault(f, set()).add(n)
1752
1752
1753 # process the files
1753 # process the files
1754 self.ui.status(_("adding file changes\n"))
1754 self.ui.status(_("adding file changes\n"))
1755 pr.step = 'files'
1755 pr.step = 'files'
1756 pr.count = 1
1756 pr.count = 1
1757 pr.total = efiles
1757 pr.total = efiles
1758 source.callback = None
1758 source.callback = None
1759
1759
1760 while 1:
1760 while 1:
1761 chunkdata = source.filelogheader()
1761 chunkdata = source.filelogheader()
1762 if not chunkdata:
1762 if not chunkdata:
1763 break
1763 break
1764 f = chunkdata["filename"]
1764 f = chunkdata["filename"]
1765 self.ui.debug("adding %s revisions\n" % f)
1765 self.ui.debug("adding %s revisions\n" % f)
1766 pr()
1766 pr()
1767 fl = self.file(f)
1767 fl = self.file(f)
1768 o = len(fl)
1768 o = len(fl)
1769 if fl.addgroup(source, revmap, trp) is None:
1769 if fl.addgroup(source, revmap, trp) is None:
1770 raise util.Abort(_("received file revlog group is empty"))
1770 raise util.Abort(_("received file revlog group is empty"))
1771 revisions += len(fl) - o
1771 revisions += len(fl) - o
1772 files += 1
1772 files += 1
1773 if f in needfiles:
1773 if f in needfiles:
1774 needs = needfiles[f]
1774 needs = needfiles[f]
1775 for new in xrange(o, len(fl)):
1775 for new in xrange(o, len(fl)):
1776 n = fl.node(new)
1776 n = fl.node(new)
1777 if n in needs:
1777 if n in needs:
1778 needs.remove(n)
1778 needs.remove(n)
1779 if not needs:
1779 if not needs:
1780 del needfiles[f]
1780 del needfiles[f]
1781 self.ui.progress(_('files'), None)
1781 self.ui.progress(_('files'), None)
1782
1782
1783 for f, needs in needfiles.iteritems():
1783 for f, needs in needfiles.iteritems():
1784 fl = self.file(f)
1784 fl = self.file(f)
1785 for n in needs:
1785 for n in needs:
1786 try:
1786 try:
1787 fl.rev(n)
1787 fl.rev(n)
1788 except error.LookupError:
1788 except error.LookupError:
1789 raise util.Abort(
1789 raise util.Abort(
1790 _('missing file data for %s:%s - run hg verify') %
1790 _('missing file data for %s:%s - run hg verify') %
1791 (f, hex(n)))
1791 (f, hex(n)))
1792
1792
1793 dh = 0
1793 dh = 0
1794 if oldheads:
1794 if oldheads:
1795 heads = cl.heads()
1795 heads = cl.heads()
1796 dh = len(heads) - len(oldheads)
1796 dh = len(heads) - len(oldheads)
1797 for h in heads:
1797 for h in heads:
1798 if h not in oldheads and 'close' in self[h].extra():
1798 if h not in oldheads and 'close' in self[h].extra():
1799 dh -= 1
1799 dh -= 1
1800 htext = ""
1800 htext = ""
1801 if dh:
1801 if dh:
1802 htext = _(" (%+d heads)") % dh
1802 htext = _(" (%+d heads)") % dh
1803
1803
1804 self.ui.status(_("added %d changesets"
1804 self.ui.status(_("added %d changesets"
1805 " with %d changes to %d files%s\n")
1805 " with %d changes to %d files%s\n")
1806 % (changesets, revisions, files, htext))
1806 % (changesets, revisions, files, htext))
1807
1807
1808 if changesets > 0:
1808 if changesets > 0:
1809 p = lambda: cl.writepending() and self.root or ""
1809 p = lambda: cl.writepending() and self.root or ""
1810 self.hook('pretxnchangegroup', throw=True,
1810 self.hook('pretxnchangegroup', throw=True,
1811 node=hex(cl.node(clstart)), source=srctype,
1811 node=hex(cl.node(clstart)), source=srctype,
1812 url=url, pending=p)
1812 url=url, pending=p)
1813
1813
1814 # make changelog see real files again
1814 # make changelog see real files again
1815 cl.finalize(trp)
1815 cl.finalize(trp)
1816
1816
1817 tr.close()
1817 tr.close()
1818 finally:
1818 finally:
1819 tr.release()
1819 tr.release()
1820 if lock:
1820 if lock:
1821 lock.release()
1821 lock.release()
1822
1822
1823 if changesets > 0:
1823 if changesets > 0:
1824 # forcefully update the on-disk branch cache
1824 # forcefully update the on-disk branch cache
1825 self.ui.debug("updating the branch cache\n")
1825 self.ui.debug("updating the branch cache\n")
1826 self.updatebranchcache()
1826 self.updatebranchcache()
1827 self.hook("changegroup", node=hex(cl.node(clstart)),
1827 self.hook("changegroup", node=hex(cl.node(clstart)),
1828 source=srctype, url=url)
1828 source=srctype, url=url)
1829
1829
1830 for i in xrange(clstart, clend):
1830 for i in xrange(clstart, clend):
1831 self.hook("incoming", node=hex(cl.node(i)),
1831 self.hook("incoming", node=hex(cl.node(i)),
1832 source=srctype, url=url)
1832 source=srctype, url=url)
1833
1833
1834 # never return 0 here:
1834 # never return 0 here:
1835 if dh < 0:
1835 if dh < 0:
1836 return dh - 1
1836 return dh - 1
1837 else:
1837 else:
1838 return dh + 1
1838 return dh + 1
1839
1839
1840 def stream_in(self, remote, requirements):
1840 def stream_in(self, remote, requirements):
1841 lock = self.lock()
1841 lock = self.lock()
1842 try:
1842 try:
1843 fp = remote.stream_out()
1843 fp = remote.stream_out()
1844 l = fp.readline()
1844 l = fp.readline()
1845 try:
1845 try:
1846 resp = int(l)
1846 resp = int(l)
1847 except ValueError:
1847 except ValueError:
1848 raise error.ResponseError(
1848 raise error.ResponseError(
1849 _('Unexpected response from remote server:'), l)
1849 _('Unexpected response from remote server:'), l)
1850 if resp == 1:
1850 if resp == 1:
1851 raise util.Abort(_('operation forbidden by server'))
1851 raise util.Abort(_('operation forbidden by server'))
1852 elif resp == 2:
1852 elif resp == 2:
1853 raise util.Abort(_('locking the remote repository failed'))
1853 raise util.Abort(_('locking the remote repository failed'))
1854 elif resp != 0:
1854 elif resp != 0:
1855 raise util.Abort(_('the server sent an unknown error code'))
1855 raise util.Abort(_('the server sent an unknown error code'))
1856 self.ui.status(_('streaming all changes\n'))
1856 self.ui.status(_('streaming all changes\n'))
1857 l = fp.readline()
1857 l = fp.readline()
1858 try:
1858 try:
1859 total_files, total_bytes = map(int, l.split(' ', 1))
1859 total_files, total_bytes = map(int, l.split(' ', 1))
1860 except (ValueError, TypeError):
1860 except (ValueError, TypeError):
1861 raise error.ResponseError(
1861 raise error.ResponseError(
1862 _('Unexpected response from remote server:'), l)
1862 _('Unexpected response from remote server:'), l)
1863 self.ui.status(_('%d files to transfer, %s of data\n') %
1863 self.ui.status(_('%d files to transfer, %s of data\n') %
1864 (total_files, util.bytecount(total_bytes)))
1864 (total_files, util.bytecount(total_bytes)))
1865 start = time.time()
1865 start = time.time()
1866 for i in xrange(total_files):
1866 for i in xrange(total_files):
1867 # XXX doesn't support '\n' or '\r' in filenames
1867 # XXX doesn't support '\n' or '\r' in filenames
1868 l = fp.readline()
1868 l = fp.readline()
1869 try:
1869 try:
1870 name, size = l.split('\0', 1)
1870 name, size = l.split('\0', 1)
1871 size = int(size)
1871 size = int(size)
1872 except (ValueError, TypeError):
1872 except (ValueError, TypeError):
1873 raise error.ResponseError(
1873 raise error.ResponseError(
1874 _('Unexpected response from remote server:'), l)
1874 _('Unexpected response from remote server:'), l)
1875 self.ui.debug('adding %s (%s)\n' % (name, util.bytecount(size)))
1875 self.ui.debug('adding %s (%s)\n' % (name, util.bytecount(size)))
1876 # for backwards compat, name was partially encoded
1876 # for backwards compat, name was partially encoded
1877 ofp = self.sopener(store.decodedir(name), 'w')
1877 ofp = self.sopener(store.decodedir(name), 'w')
1878 for chunk in util.filechunkiter(fp, limit=size):
1878 for chunk in util.filechunkiter(fp, limit=size):
1879 ofp.write(chunk)
1879 ofp.write(chunk)
1880 ofp.close()
1880 ofp.close()
1881 elapsed = time.time() - start
1881 elapsed = time.time() - start
1882 if elapsed <= 0:
1882 if elapsed <= 0:
1883 elapsed = 0.001
1883 elapsed = 0.001
1884 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
1884 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
1885 (util.bytecount(total_bytes), elapsed,
1885 (util.bytecount(total_bytes), elapsed,
1886 util.bytecount(total_bytes / elapsed)))
1886 util.bytecount(total_bytes / elapsed)))
1887
1887
1888 # new requirements = old non-format requirements + new format-related
1888 # new requirements = old non-format requirements + new format-related
1889 # requirements from the streamed-in repository
1889 # requirements from the streamed-in repository
1890 requirements.update(set(self.requirements) - self.supportedformats)
1890 requirements.update(set(self.requirements) - self.supportedformats)
1891 self._applyrequirements(requirements)
1891 self._applyrequirements(requirements)
1892 self._writerequirements()
1892 self._writerequirements()
1893
1893
1894 self.invalidate()
1894 self.invalidate()
1895 return len(self.heads()) + 1
1895 return len(self.heads()) + 1
1896 finally:
1896 finally:
1897 lock.release()
1897 lock.release()
1898
1898
1899 def clone(self, remote, heads=[], stream=False):
1899 def clone(self, remote, heads=[], stream=False):
1900 '''clone remote repository.
1900 '''clone remote repository.
1901
1901
1902 keyword arguments:
1902 keyword arguments:
1903 heads: list of revs to clone (forces use of pull)
1903 heads: list of revs to clone (forces use of pull)
1904 stream: use streaming clone if possible'''
1904 stream: use streaming clone if possible'''
1905
1905
1906 # now, all clients that can request uncompressed clones can
1906 # now, all clients that can request uncompressed clones can
1907 # read repo formats supported by all servers that can serve
1907 # read repo formats supported by all servers that can serve
1908 # them.
1908 # them.
1909
1909
1910 # if revlog format changes, client will have to check version
1910 # if revlog format changes, client will have to check version
1911 # and format flags on "stream" capability, and use
1911 # and format flags on "stream" capability, and use
1912 # uncompressed only if compatible.
1912 # uncompressed only if compatible.
1913
1913
1914 if stream and not heads:
1914 if stream and not heads:
1915 # 'stream' means remote revlog format is revlogv1 only
1915 # 'stream' means remote revlog format is revlogv1 only
1916 if remote.capable('stream'):
1916 if remote.capable('stream'):
1917 return self.stream_in(remote, set(('revlogv1',)))
1917 return self.stream_in(remote, set(('revlogv1',)))
1918 # otherwise, 'streamreqs' contains the remote revlog format
1918 # otherwise, 'streamreqs' contains the remote revlog format
1919 streamreqs = remote.capable('streamreqs')
1919 streamreqs = remote.capable('streamreqs')
1920 if streamreqs:
1920 if streamreqs:
1921 streamreqs = set(streamreqs.split(','))
1921 streamreqs = set(streamreqs.split(','))
1922 # if we support it, stream in and adjust our requirements
1922 # if we support it, stream in and adjust our requirements
1923 if not streamreqs - self.supportedformats:
1923 if not streamreqs - self.supportedformats:
1924 return self.stream_in(remote, streamreqs)
1924 return self.stream_in(remote, streamreqs)
1925 return self.pull(remote, heads)
1925 return self.pull(remote, heads)
1926
1926
1927 def pushkey(self, namespace, key, old, new):
1927 def pushkey(self, namespace, key, old, new):
1928 self.hook('prepushkey', throw=True, namespace=namespace, key=key,
1928 self.hook('prepushkey', throw=True, namespace=namespace, key=key,
1929 old=old, new=new)
1929 old=old, new=new)
1930 ret = pushkey.push(self, namespace, key, old, new)
1930 ret = pushkey.push(self, namespace, key, old, new)
1931 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
1931 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
1932 ret=ret)
1932 ret=ret)
1933 return ret
1933 return ret
1934
1934
1935 def listkeys(self, namespace):
1935 def listkeys(self, namespace):
1936 self.hook('prelistkeys', throw=True, namespace=namespace)
1936 self.hook('prelistkeys', throw=True, namespace=namespace)
1937 values = pushkey.list(self, namespace)
1937 values = pushkey.list(self, namespace)
1938 self.hook('listkeys', namespace=namespace, values=values)
1938 self.hook('listkeys', namespace=namespace, values=values)
1939 return values
1939 return values
1940
1940
1941 def debugwireargs(self, one, two, three=None, four=None, five=None):
1941 def debugwireargs(self, one, two, three=None, four=None, five=None):
1942 '''used to test argument passing over the wire'''
1942 '''used to test argument passing over the wire'''
1943 return "%s %s %s %s %s" % (one, two, three, four, five)
1943 return "%s %s %s %s %s" % (one, two, three, four, five)
1944
1944
1945 # used to avoid circular references so destructors work
1945 # used to avoid circular references so destructors work
1946 def aftertrans(files):
1946 def aftertrans(files):
1947 renamefiles = [tuple(t) for t in files]
1947 renamefiles = [tuple(t) for t in files]
1948 def a():
1948 def a():
1949 for src, dest in renamefiles:
1949 for src, dest in renamefiles:
1950 util.rename(src, dest)
1950 util.rename(src, dest)
1951 return a
1951 return a
1952
1952
1953 def instance(ui, path, create):
1953 def instance(ui, path, create):
1954 return localrepository(ui, util.localpath(path), create)
1954 return localrepository(ui, util.localpath(path), create)
1955
1955
1956 def islocal(path):
1956 def islocal(path):
1957 return True
1957 return True
@@ -1,561 +1,561 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):
84 def _checkunknown(wctx, mctx):
85 "check for collisions between unknown files and files in mctx"
85 "check for collisions between unknown files and files in mctx"
86 for f in wctx.unknown():
86 for f in wctx.unknown():
87 if f in mctx and mctx[f].cmp(wctx[f]):
87 if f in mctx and mctx[f].cmp(wctx[f]):
88 raise util.Abort(_("untracked file in working directory differs"
88 raise util.Abort(_("untracked file in working directory differs"
89 " from file in requested revision: '%s'") % f)
89 " from file in requested revision: '%s'") % f)
90
90
91 def _checkcollision(mctx):
91 def _checkcollision(mctx):
92 "check for case folding collisions in the destination context"
92 "check for case folding collisions in the destination context"
93 folded = {}
93 folded = {}
94 for fn in mctx:
94 for fn in mctx:
95 fold = fn.lower()
95 fold = fn.lower()
96 if fold in folded:
96 if fold in folded:
97 raise util.Abort(_("case-folding collision between %s and %s")
97 raise util.Abort(_("case-folding collision between %s and %s")
98 % (fn, folded[fold]))
98 % (fn, folded[fold]))
99 folded[fold] = fn
99 folded[fold] = fn
100
100
101 def _forgetremoved(wctx, mctx, branchmerge):
101 def _forgetremoved(wctx, mctx, branchmerge):
102 """
102 """
103 Forget removed files
103 Forget removed files
104
104
105 If we're jumping between revisions (as opposed to merging), and if
105 If we're jumping between revisions (as opposed to merging), and if
106 neither the working directory nor the target rev has the file,
106 neither the working directory nor the target rev has the file,
107 then we need to remove it from the dirstate, to prevent the
107 then we need to remove it from the dirstate, to prevent the
108 dirstate from listing the file when it is no longer in the
108 dirstate from listing the file when it is no longer in the
109 manifest.
109 manifest.
110
110
111 If we're merging, and the other revision has removed a file
111 If we're merging, and the other revision has removed a file
112 that is not present in the working directory, we need to mark it
112 that is not present in the working directory, we need to mark it
113 as removed.
113 as removed.
114 """
114 """
115
115
116 action = []
116 action = []
117 state = branchmerge and 'r' or 'f'
117 state = branchmerge and 'r' or 'f'
118 for f in wctx.deleted():
118 for f in wctx.deleted():
119 if f not in mctx:
119 if f not in mctx:
120 action.append((f, state))
120 action.append((f, state))
121
121
122 if not branchmerge:
122 if not branchmerge:
123 for f in wctx.removed():
123 for f in wctx.removed():
124 if f not in mctx:
124 if f not in mctx:
125 action.append((f, "f"))
125 action.append((f, "f"))
126
126
127 return action
127 return action
128
128
129 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
129 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
130 """
130 """
131 Merge p1 and p2 with ancestor pa and generate merge action list
131 Merge p1 and p2 with ancestor pa and generate merge action list
132
132
133 overwrite = whether we clobber working files
133 overwrite = whether we clobber working files
134 partial = function to filter file lists
134 partial = function to filter file lists
135 """
135 """
136
136
137 def fmerge(f, f2, fa):
137 def fmerge(f, f2, fa):
138 """merge flags"""
138 """merge flags"""
139 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
139 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
140 if m == n: # flags agree
140 if m == n: # flags agree
141 return m # unchanged
141 return m # unchanged
142 if m and n and not a: # flags set, don't agree, differ from parent
142 if m and n and not a: # flags set, don't agree, differ from parent
143 r = repo.ui.promptchoice(
143 r = repo.ui.promptchoice(
144 _(" conflicting flags for %s\n"
144 _(" conflicting flags for %s\n"
145 "(n)one, e(x)ec or sym(l)ink?") % f,
145 "(n)one, e(x)ec or sym(l)ink?") % f,
146 (_("&None"), _("E&xec"), _("Sym&link")), 0)
146 (_("&None"), _("E&xec"), _("Sym&link")), 0)
147 if r == 1:
147 if r == 1:
148 return "x" # Exec
148 return "x" # Exec
149 if r == 2:
149 if r == 2:
150 return "l" # Symlink
150 return "l" # Symlink
151 return ""
151 return ""
152 if m and m != a: # changed from a to m
152 if m and m != a: # changed from a to m
153 return m
153 return m
154 if n and n != a: # changed from a to n
154 if n and n != a: # changed from a to n
155 return n
155 return n
156 return '' # flag was cleared
156 return '' # flag was cleared
157
157
158 def act(msg, m, f, *args):
158 def act(msg, m, f, *args):
159 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
159 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
160 action.append((f, m) + args)
160 action.append((f, m) + args)
161
161
162 action, copy = [], {}
162 action, copy = [], {}
163
163
164 if overwrite:
164 if overwrite:
165 pa = p1
165 pa = p1
166 elif pa == p2: # backwards
166 elif pa == p2: # backwards
167 pa = p1.p1()
167 pa = p1.p1()
168 elif pa and repo.ui.configbool("merge", "followcopies", True):
168 elif pa and repo.ui.configbool("merge", "followcopies", True):
169 dirs = repo.ui.configbool("merge", "followdirs", True)
169 dirs = repo.ui.configbool("merge", "followdirs", True)
170 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
170 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
171 for of, fl in diverge.iteritems():
171 for of, fl in diverge.iteritems():
172 act("divergent renames", "dr", of, fl)
172 act("divergent renames", "dr", of, fl)
173
173
174 repo.ui.note(_("resolving manifests\n"))
174 repo.ui.note(_("resolving manifests\n"))
175 repo.ui.debug(" overwrite %s partial %s\n" % (overwrite, bool(partial)))
175 repo.ui.debug(" overwrite %s partial %s\n" % (overwrite, bool(partial)))
176 repo.ui.debug(" ancestor %s local %s remote %s\n" % (pa, p1, p2))
176 repo.ui.debug(" ancestor %s local %s remote %s\n" % (pa, p1, p2))
177
177
178 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
178 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
179 copied = set(copy.values())
179 copied = set(copy.values())
180
180
181 if '.hgsubstate' in m1:
181 if '.hgsubstate' in m1:
182 # check whether sub state is modified
182 # check whether sub state is modified
183 for s in p1.substate:
183 for s in p1.substate:
184 if p1.sub(s).dirty():
184 if p1.sub(s).dirty():
185 m1['.hgsubstate'] += "+"
185 m1['.hgsubstate'] += "+"
186 break
186 break
187
187
188 # Compare manifests
188 # Compare manifests
189 for f, n in m1.iteritems():
189 for f, n in m1.iteritems():
190 if partial and not partial(f):
190 if partial and not partial(f):
191 continue
191 continue
192 if f in m2:
192 if f in m2:
193 rflags = fmerge(f, f, f)
193 rflags = fmerge(f, f, f)
194 a = ma.get(f, nullid)
194 a = ma.get(f, nullid)
195 if n == m2[f] or m2[f] == a: # same or local newer
195 if n == m2[f] or m2[f] == a: # same or local newer
196 # is file locally modified or flags need changing?
196 # is file locally modified or flags need changing?
197 # dirstate flags may need to be made current
197 # dirstate flags may need to be made current
198 if m1.flags(f) != rflags or n[20:]:
198 if m1.flags(f) != rflags or n[20:]:
199 act("update permissions", "e", f, rflags)
199 act("update permissions", "e", f, rflags)
200 elif n == a: # remote newer
200 elif n == a: # remote newer
201 act("remote is newer", "g", f, rflags)
201 act("remote is newer", "g", f, rflags)
202 else: # both changed
202 else: # both changed
203 act("versions differ", "m", f, f, f, rflags, False)
203 act("versions differ", "m", f, f, f, rflags, False)
204 elif f in copied: # files we'll deal with on m2 side
204 elif f in copied: # files we'll deal with on m2 side
205 pass
205 pass
206 elif f in copy:
206 elif f in copy:
207 f2 = copy[f]
207 f2 = copy[f]
208 if f2 not in m2: # directory rename
208 if f2 not in m2: # directory rename
209 act("remote renamed directory to " + f2, "d",
209 act("remote renamed directory to " + f2, "d",
210 f, None, f2, m1.flags(f))
210 f, None, f2, m1.flags(f))
211 else: # case 2 A,B/B/B or case 4,21 A/B/B
211 else: # case 2 A,B/B/B or case 4,21 A/B/B
212 act("local copied/moved to " + f2, "m",
212 act("local copied/moved to " + f2, "m",
213 f, f2, f, fmerge(f, f2, f2), False)
213 f, f2, f, fmerge(f, f2, f2), False)
214 elif f in ma: # clean, a different, no remote
214 elif f in ma: # clean, a different, no remote
215 if n != ma[f]:
215 if n != ma[f]:
216 if repo.ui.promptchoice(
216 if repo.ui.promptchoice(
217 _(" local changed %s which remote deleted\n"
217 _(" local changed %s which remote deleted\n"
218 "use (c)hanged version or (d)elete?") % f,
218 "use (c)hanged version or (d)elete?") % f,
219 (_("&Changed"), _("&Delete")), 0):
219 (_("&Changed"), _("&Delete")), 0):
220 act("prompt delete", "r", f)
220 act("prompt delete", "r", f)
221 else:
221 else:
222 act("prompt keep", "a", f)
222 act("prompt keep", "a", f)
223 elif n[20:] == "a": # added, no remote
223 elif n[20:] == "a": # added, no remote
224 act("remote deleted", "f", f)
224 act("remote deleted", "f", f)
225 elif n[20:] != "u":
225 elif n[20:] != "u":
226 act("other deleted", "r", f)
226 act("other deleted", "r", f)
227
227
228 for f, n in m2.iteritems():
228 for f, n in m2.iteritems():
229 if partial and not partial(f):
229 if partial and not partial(f):
230 continue
230 continue
231 if f in m1 or f in copied: # files already visited
231 if f in m1 or f in copied: # files already visited
232 continue
232 continue
233 if f in copy:
233 if f in copy:
234 f2 = copy[f]
234 f2 = copy[f]
235 if f2 not in m1: # directory rename
235 if f2 not in m1: # directory rename
236 act("local renamed directory to " + f2, "d",
236 act("local renamed directory to " + f2, "d",
237 None, f, f2, m2.flags(f))
237 None, f, f2, m2.flags(f))
238 elif f2 in m2: # rename case 1, A/A,B/A
238 elif f2 in m2: # rename case 1, A/A,B/A
239 act("remote copied to " + f, "m",
239 act("remote copied to " + f, "m",
240 f2, f, f, fmerge(f2, f, f2), False)
240 f2, f, f, fmerge(f2, f, f2), False)
241 else: # case 3,20 A/B/A
241 else: # case 3,20 A/B/A
242 act("remote moved to " + f, "m",
242 act("remote moved to " + f, "m",
243 f2, f, f, fmerge(f2, f, f2), True)
243 f2, f, f, fmerge(f2, f, f2), True)
244 elif f not in ma:
244 elif f not in ma:
245 act("remote created", "g", f, m2.flags(f))
245 act("remote created", "g", f, m2.flags(f))
246 elif n != ma[f]:
246 elif n != ma[f]:
247 if repo.ui.promptchoice(
247 if repo.ui.promptchoice(
248 _("remote changed %s which local deleted\n"
248 _("remote changed %s which local deleted\n"
249 "use (c)hanged version or leave (d)eleted?") % f,
249 "use (c)hanged version or leave (d)eleted?") % f,
250 (_("&Changed"), _("&Deleted")), 0) == 0:
250 (_("&Changed"), _("&Deleted")), 0) == 0:
251 act("prompt recreating", "g", f, m2.flags(f))
251 act("prompt recreating", "g", f, m2.flags(f))
252
252
253 return action
253 return action
254
254
255 def actionkey(a):
255 def actionkey(a):
256 return a[1] == 'r' and -1 or 0, a
256 return a[1] == 'r' and -1 or 0, a
257
257
258 def applyupdates(repo, action, wctx, mctx, actx, overwrite):
258 def applyupdates(repo, action, wctx, mctx, actx, overwrite):
259 """apply the merge action list to the working directory
259 """apply the merge action list to the working directory
260
260
261 wctx is the working copy context
261 wctx is the working copy context
262 mctx is the context to be merged into the working copy
262 mctx is the context to be merged into the working copy
263 actx is the context of the common ancestor
263 actx is the context of the common ancestor
264
264
265 Return a tuple of counts (updated, merged, removed, unresolved) that
265 Return a tuple of counts (updated, merged, removed, unresolved) that
266 describes how many files were affected by the update.
266 describes how many files were affected by the update.
267 """
267 """
268
268
269 updated, merged, removed, unresolved = 0, 0, 0, 0
269 updated, merged, removed, unresolved = 0, 0, 0, 0
270 ms = mergestate(repo)
270 ms = mergestate(repo)
271 ms.reset(wctx.p1().node())
271 ms.reset(wctx.p1().node())
272 moves = []
272 moves = []
273 action.sort(key=actionkey)
273 action.sort(key=actionkey)
274
274
275 # prescan for merges
275 # prescan for merges
276 u = repo.ui
276 u = repo.ui
277 for a in action:
277 for a in action:
278 f, m = a[:2]
278 f, m = a[:2]
279 if m == 'm': # merge
279 if m == 'm': # merge
280 f2, fd, flags, move = a[2:]
280 f2, fd, flags, move = a[2:]
281 if f == '.hgsubstate': # merged internally
281 if f == '.hgsubstate': # merged internally
282 continue
282 continue
283 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
283 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
284 fcl = wctx[f]
284 fcl = wctx[f]
285 fco = mctx[f2]
285 fco = mctx[f2]
286 if mctx == actx: # backwards, use working dir parent as ancestor
286 if mctx == actx: # backwards, use working dir parent as ancestor
287 if fcl.parents():
287 if fcl.parents():
288 fca = fcl.p1()
288 fca = fcl.p1()
289 else:
289 else:
290 fca = repo.filectx(f, fileid=nullrev)
290 fca = repo.filectx(f, fileid=nullrev)
291 else:
291 else:
292 fca = fcl.ancestor(fco, actx)
292 fca = fcl.ancestor(fco, actx)
293 if not fca:
293 if not fca:
294 fca = repo.filectx(f, fileid=nullrev)
294 fca = repo.filectx(f, fileid=nullrev)
295 ms.add(fcl, fco, fca, fd, flags)
295 ms.add(fcl, fco, fca, fd, flags)
296 if f != fd and move:
296 if f != fd and move:
297 moves.append(f)
297 moves.append(f)
298
298
299 # remove renamed files after safely stored
299 # remove renamed files after safely stored
300 for f in moves:
300 for f in moves:
301 if os.path.lexists(repo.wjoin(f)):
301 if os.path.lexists(repo.wjoin(f)):
302 repo.ui.debug("removing %s\n" % f)
302 repo.ui.debug("removing %s\n" % f)
303 os.unlink(repo.wjoin(f))
303 os.unlink(repo.wjoin(f))
304
304
305 audit_path = scmutil.path_auditor(repo.root)
305 audit_path = scmutil.pathauditor(repo.root)
306
306
307 numupdates = len(action)
307 numupdates = len(action)
308 for i, a in enumerate(action):
308 for i, a in enumerate(action):
309 f, m = a[:2]
309 f, m = a[:2]
310 u.progress(_('updating'), i + 1, item=f, total=numupdates,
310 u.progress(_('updating'), i + 1, item=f, total=numupdates,
311 unit=_('files'))
311 unit=_('files'))
312 if f and f[0] == "/":
312 if f and f[0] == "/":
313 continue
313 continue
314 if m == "r": # remove
314 if m == "r": # remove
315 repo.ui.note(_("removing %s\n") % f)
315 repo.ui.note(_("removing %s\n") % f)
316 audit_path(f)
316 audit_path(f)
317 if f == '.hgsubstate': # subrepo states need updating
317 if f == '.hgsubstate': # subrepo states need updating
318 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
318 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
319 try:
319 try:
320 util.unlinkpath(repo.wjoin(f))
320 util.unlinkpath(repo.wjoin(f))
321 except OSError, inst:
321 except OSError, inst:
322 if inst.errno != errno.ENOENT:
322 if inst.errno != errno.ENOENT:
323 repo.ui.warn(_("update failed to remove %s: %s!\n") %
323 repo.ui.warn(_("update failed to remove %s: %s!\n") %
324 (f, inst.strerror))
324 (f, inst.strerror))
325 removed += 1
325 removed += 1
326 elif m == "m": # merge
326 elif m == "m": # merge
327 if f == '.hgsubstate': # subrepo states need updating
327 if f == '.hgsubstate': # subrepo states need updating
328 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx), overwrite)
328 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx), overwrite)
329 continue
329 continue
330 f2, fd, flags, move = a[2:]
330 f2, fd, flags, move = a[2:]
331 r = ms.resolve(fd, wctx, mctx)
331 r = ms.resolve(fd, wctx, mctx)
332 if r is not None and r > 0:
332 if r is not None and r > 0:
333 unresolved += 1
333 unresolved += 1
334 else:
334 else:
335 if r is None:
335 if r is None:
336 updated += 1
336 updated += 1
337 else:
337 else:
338 merged += 1
338 merged += 1
339 util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
339 util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
340 if (move and repo.dirstate.normalize(fd) != f
340 if (move and repo.dirstate.normalize(fd) != f
341 and os.path.lexists(repo.wjoin(f))):
341 and os.path.lexists(repo.wjoin(f))):
342 repo.ui.debug("removing %s\n" % f)
342 repo.ui.debug("removing %s\n" % f)
343 os.unlink(repo.wjoin(f))
343 os.unlink(repo.wjoin(f))
344 elif m == "g": # get
344 elif m == "g": # get
345 flags = a[2]
345 flags = a[2]
346 repo.ui.note(_("getting %s\n") % f)
346 repo.ui.note(_("getting %s\n") % f)
347 t = mctx.filectx(f).data()
347 t = mctx.filectx(f).data()
348 repo.wwrite(f, t, flags)
348 repo.wwrite(f, t, flags)
349 t = None
349 t = None
350 updated += 1
350 updated += 1
351 if f == '.hgsubstate': # subrepo states need updating
351 if f == '.hgsubstate': # subrepo states need updating
352 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
352 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
353 elif m == "d": # directory rename
353 elif m == "d": # directory rename
354 f2, fd, flags = a[2:]
354 f2, fd, flags = a[2:]
355 if f:
355 if f:
356 repo.ui.note(_("moving %s to %s\n") % (f, fd))
356 repo.ui.note(_("moving %s to %s\n") % (f, fd))
357 t = wctx.filectx(f).data()
357 t = wctx.filectx(f).data()
358 repo.wwrite(fd, t, flags)
358 repo.wwrite(fd, t, flags)
359 util.unlinkpath(repo.wjoin(f))
359 util.unlinkpath(repo.wjoin(f))
360 if f2:
360 if f2:
361 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
361 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
362 t = mctx.filectx(f2).data()
362 t = mctx.filectx(f2).data()
363 repo.wwrite(fd, t, flags)
363 repo.wwrite(fd, t, flags)
364 updated += 1
364 updated += 1
365 elif m == "dr": # divergent renames
365 elif m == "dr": # divergent renames
366 fl = a[2]
366 fl = a[2]
367 repo.ui.warn(_("note: possible conflict - %s was renamed "
367 repo.ui.warn(_("note: possible conflict - %s was renamed "
368 "multiple times to:\n") % f)
368 "multiple times to:\n") % f)
369 for nf in fl:
369 for nf in fl:
370 repo.ui.warn(" %s\n" % nf)
370 repo.ui.warn(" %s\n" % nf)
371 elif m == "e": # exec
371 elif m == "e": # exec
372 flags = a[2]
372 flags = a[2]
373 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
373 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
374 ms.commit()
374 ms.commit()
375 u.progress(_('updating'), None, total=numupdates, unit=_('files'))
375 u.progress(_('updating'), None, total=numupdates, unit=_('files'))
376
376
377 return updated, merged, removed, unresolved
377 return updated, merged, removed, unresolved
378
378
379 def recordupdates(repo, action, branchmerge):
379 def recordupdates(repo, action, branchmerge):
380 "record merge actions to the dirstate"
380 "record merge actions to the dirstate"
381
381
382 for a in action:
382 for a in action:
383 f, m = a[:2]
383 f, m = a[:2]
384 if m == "r": # remove
384 if m == "r": # remove
385 if branchmerge:
385 if branchmerge:
386 repo.dirstate.remove(f)
386 repo.dirstate.remove(f)
387 else:
387 else:
388 repo.dirstate.forget(f)
388 repo.dirstate.forget(f)
389 elif m == "a": # re-add
389 elif m == "a": # re-add
390 if not branchmerge:
390 if not branchmerge:
391 repo.dirstate.add(f)
391 repo.dirstate.add(f)
392 elif m == "f": # forget
392 elif m == "f": # forget
393 repo.dirstate.forget(f)
393 repo.dirstate.forget(f)
394 elif m == "e": # exec change
394 elif m == "e": # exec change
395 repo.dirstate.normallookup(f)
395 repo.dirstate.normallookup(f)
396 elif m == "g": # get
396 elif m == "g": # get
397 if branchmerge:
397 if branchmerge:
398 repo.dirstate.otherparent(f)
398 repo.dirstate.otherparent(f)
399 else:
399 else:
400 repo.dirstate.normal(f)
400 repo.dirstate.normal(f)
401 elif m == "m": # merge
401 elif m == "m": # merge
402 f2, fd, flag, move = a[2:]
402 f2, fd, flag, move = a[2:]
403 if branchmerge:
403 if branchmerge:
404 # We've done a branch merge, mark this file as merged
404 # We've done a branch merge, mark this file as merged
405 # so that we properly record the merger later
405 # so that we properly record the merger later
406 repo.dirstate.merge(fd)
406 repo.dirstate.merge(fd)
407 if f != f2: # copy/rename
407 if f != f2: # copy/rename
408 if move:
408 if move:
409 repo.dirstate.remove(f)
409 repo.dirstate.remove(f)
410 if f != fd:
410 if f != fd:
411 repo.dirstate.copy(f, fd)
411 repo.dirstate.copy(f, fd)
412 else:
412 else:
413 repo.dirstate.copy(f2, fd)
413 repo.dirstate.copy(f2, fd)
414 else:
414 else:
415 # We've update-merged a locally modified file, so
415 # We've update-merged a locally modified file, so
416 # we set the dirstate to emulate a normal checkout
416 # we set the dirstate to emulate a normal checkout
417 # of that file some time in the past. Thus our
417 # of that file some time in the past. Thus our
418 # merge will appear as a normal local file
418 # merge will appear as a normal local file
419 # modification.
419 # modification.
420 if f2 == fd: # file not locally copied/moved
420 if f2 == fd: # file not locally copied/moved
421 repo.dirstate.normallookup(fd)
421 repo.dirstate.normallookup(fd)
422 if move:
422 if move:
423 repo.dirstate.forget(f)
423 repo.dirstate.forget(f)
424 elif m == "d": # directory rename
424 elif m == "d": # directory rename
425 f2, fd, flag = a[2:]
425 f2, fd, flag = a[2:]
426 if not f2 and f not in repo.dirstate:
426 if not f2 and f not in repo.dirstate:
427 # untracked file moved
427 # untracked file moved
428 continue
428 continue
429 if branchmerge:
429 if branchmerge:
430 repo.dirstate.add(fd)
430 repo.dirstate.add(fd)
431 if f:
431 if f:
432 repo.dirstate.remove(f)
432 repo.dirstate.remove(f)
433 repo.dirstate.copy(f, fd)
433 repo.dirstate.copy(f, fd)
434 if f2:
434 if f2:
435 repo.dirstate.copy(f2, fd)
435 repo.dirstate.copy(f2, fd)
436 else:
436 else:
437 repo.dirstate.normal(fd)
437 repo.dirstate.normal(fd)
438 if f:
438 if f:
439 repo.dirstate.forget(f)
439 repo.dirstate.forget(f)
440
440
441 def update(repo, node, branchmerge, force, partial, ancestor=None):
441 def update(repo, node, branchmerge, force, partial, ancestor=None):
442 """
442 """
443 Perform a merge between the working directory and the given node
443 Perform a merge between the working directory and the given node
444
444
445 node = the node to update to, or None if unspecified
445 node = the node to update to, or None if unspecified
446 branchmerge = whether to merge between branches
446 branchmerge = whether to merge between branches
447 force = whether to force branch merging or file overwriting
447 force = whether to force branch merging or file overwriting
448 partial = a function to filter file lists (dirstate not updated)
448 partial = a function to filter file lists (dirstate not updated)
449
449
450 The table below shows all the behaviors of the update command
450 The table below shows all the behaviors of the update command
451 given the -c and -C or no options, whether the working directory
451 given the -c and -C or no options, whether the working directory
452 is dirty, whether a revision is specified, and the relationship of
452 is dirty, whether a revision is specified, and the relationship of
453 the parent rev to the target rev (linear, on the same named
453 the parent rev to the target rev (linear, on the same named
454 branch, or on another named branch).
454 branch, or on another named branch).
455
455
456 This logic is tested by test-update-branches.t.
456 This logic is tested by test-update-branches.t.
457
457
458 -c -C dirty rev | linear same cross
458 -c -C dirty rev | linear same cross
459 n n n n | ok (1) x
459 n n n n | ok (1) x
460 n n n y | ok ok ok
460 n n n y | ok ok ok
461 n n y * | merge (2) (2)
461 n n y * | merge (2) (2)
462 n y * * | --- discard ---
462 n y * * | --- discard ---
463 y n y * | --- (3) ---
463 y n y * | --- (3) ---
464 y n n * | --- ok ---
464 y n n * | --- ok ---
465 y y * * | --- (4) ---
465 y y * * | --- (4) ---
466
466
467 x = can't happen
467 x = can't happen
468 * = don't-care
468 * = don't-care
469 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
469 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
470 2 = abort: crosses branches (use 'hg merge' to merge or
470 2 = abort: crosses branches (use 'hg merge' to merge or
471 use 'hg update -C' to discard changes)
471 use 'hg update -C' to discard changes)
472 3 = abort: uncommitted local changes
472 3 = abort: uncommitted local changes
473 4 = incompatible options (checked in commands.py)
473 4 = incompatible options (checked in commands.py)
474
474
475 Return the same tuple as applyupdates().
475 Return the same tuple as applyupdates().
476 """
476 """
477
477
478 onode = node
478 onode = node
479 wlock = repo.wlock()
479 wlock = repo.wlock()
480 try:
480 try:
481 wc = repo[None]
481 wc = repo[None]
482 if node is None:
482 if node is None:
483 # tip of current branch
483 # tip of current branch
484 try:
484 try:
485 node = repo.branchtags()[wc.branch()]
485 node = repo.branchtags()[wc.branch()]
486 except KeyError:
486 except KeyError:
487 if wc.branch() == "default": # no default branch!
487 if wc.branch() == "default": # no default branch!
488 node = repo.lookup("tip") # update to tip
488 node = repo.lookup("tip") # update to tip
489 else:
489 else:
490 raise util.Abort(_("branch %s not found") % wc.branch())
490 raise util.Abort(_("branch %s not found") % wc.branch())
491 overwrite = force and not branchmerge
491 overwrite = force and not branchmerge
492 pl = wc.parents()
492 pl = wc.parents()
493 p1, p2 = pl[0], repo[node]
493 p1, p2 = pl[0], repo[node]
494 if ancestor:
494 if ancestor:
495 pa = repo[ancestor]
495 pa = repo[ancestor]
496 else:
496 else:
497 pa = p1.ancestor(p2)
497 pa = p1.ancestor(p2)
498
498
499 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
499 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
500
500
501 ### check phase
501 ### check phase
502 if not overwrite and len(pl) > 1:
502 if not overwrite and len(pl) > 1:
503 raise util.Abort(_("outstanding uncommitted merges"))
503 raise util.Abort(_("outstanding uncommitted merges"))
504 if branchmerge:
504 if branchmerge:
505 if pa == p2:
505 if pa == p2:
506 raise util.Abort(_("merging with a working directory ancestor"
506 raise util.Abort(_("merging with a working directory ancestor"
507 " has no effect"))
507 " has no effect"))
508 elif pa == p1:
508 elif pa == p1:
509 if p1.branch() == p2.branch():
509 if p1.branch() == p2.branch():
510 raise util.Abort(_("nothing to merge (use 'hg update'"
510 raise util.Abort(_("nothing to merge (use 'hg update'"
511 " or check 'hg heads')"))
511 " or check 'hg heads')"))
512 if not force and (wc.files() or wc.deleted()):
512 if not force and (wc.files() or wc.deleted()):
513 raise util.Abort(_("outstanding uncommitted changes "
513 raise util.Abort(_("outstanding uncommitted changes "
514 "(use 'hg status' to list changes)"))
514 "(use 'hg status' to list changes)"))
515 for s in wc.substate:
515 for s in wc.substate:
516 if wc.sub(s).dirty():
516 if wc.sub(s).dirty():
517 raise util.Abort(_("outstanding uncommitted changes in "
517 raise util.Abort(_("outstanding uncommitted changes in "
518 "subrepository '%s'") % s)
518 "subrepository '%s'") % s)
519
519
520 elif not overwrite:
520 elif not overwrite:
521 if pa == p1 or pa == p2: # linear
521 if pa == p1 or pa == p2: # linear
522 pass # all good
522 pass # all good
523 elif wc.files() or wc.deleted():
523 elif wc.files() or wc.deleted():
524 raise util.Abort(_("crosses branches (merge branches or use"
524 raise util.Abort(_("crosses branches (merge branches or use"
525 " --clean to discard changes)"))
525 " --clean to discard changes)"))
526 elif onode is None:
526 elif onode is None:
527 raise util.Abort(_("crosses branches (merge branches or use"
527 raise util.Abort(_("crosses branches (merge branches or use"
528 " --check to force update)"))
528 " --check to force update)"))
529 else:
529 else:
530 # Allow jumping branches if clean and specific rev given
530 # Allow jumping branches if clean and specific rev given
531 overwrite = True
531 overwrite = True
532
532
533 ### calculate phase
533 ### calculate phase
534 action = []
534 action = []
535 wc.status(unknown=True) # prime cache
535 wc.status(unknown=True) # prime cache
536 if not force:
536 if not force:
537 _checkunknown(wc, p2)
537 _checkunknown(wc, p2)
538 if not util.checkcase(repo.path):
538 if not util.checkcase(repo.path):
539 _checkcollision(p2)
539 _checkcollision(p2)
540 action += _forgetremoved(wc, p2, branchmerge)
540 action += _forgetremoved(wc, p2, branchmerge)
541 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
541 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
542
542
543 ### apply phase
543 ### apply phase
544 if not branchmerge: # just jump to the new rev
544 if not branchmerge: # just jump to the new rev
545 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
545 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
546 if not partial:
546 if not partial:
547 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
547 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
548
548
549 stats = applyupdates(repo, action, wc, p2, pa, overwrite)
549 stats = applyupdates(repo, action, wc, p2, pa, overwrite)
550
550
551 if not partial:
551 if not partial:
552 repo.dirstate.setparents(fp1, fp2)
552 repo.dirstate.setparents(fp1, fp2)
553 recordupdates(repo, action, branchmerge)
553 recordupdates(repo, action, branchmerge)
554 if not branchmerge:
554 if not branchmerge:
555 repo.dirstate.setbranch(p2.branch())
555 repo.dirstate.setbranch(p2.branch())
556 finally:
556 finally:
557 wlock.release()
557 wlock.release()
558
558
559 if not partial:
559 if not partial:
560 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
560 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
561 return stats
561 return stats
@@ -1,465 +1,465 b''
1 # scmutil.py - Mercurial core utility functions
1 # scmutil.py - Mercurial core utility functions
2 #
2 #
3 # Copyright Matt Mackall <mpm@selenic.com>
3 # Copyright 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 i18n import _
8 from i18n import _
9 import util, error, osutil
9 import util, error, osutil
10 import os, errno, stat, sys
10 import os, errno, stat, sys
11
11
12 def checkfilename(f):
12 def checkfilename(f):
13 '''Check that the filename f is an acceptable filename for a tracked file'''
13 '''Check that the filename f is an acceptable filename for a tracked file'''
14 if '\r' in f or '\n' in f:
14 if '\r' in f or '\n' in f:
15 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
15 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
16
16
17 def checkportable(ui, f):
17 def checkportable(ui, f):
18 '''Check if filename f is portable and warn or abort depending on config'''
18 '''Check if filename f is portable and warn or abort depending on config'''
19 checkfilename(f)
19 checkfilename(f)
20 abort, warn = checkportabilityalert(ui)
20 abort, warn = checkportabilityalert(ui)
21 if abort or warn:
21 if abort or warn:
22 msg = util.checkwinfilename(f)
22 msg = util.checkwinfilename(f)
23 if msg:
23 if msg:
24 msg = "%s: %r" % (msg, f)
24 msg = "%s: %r" % (msg, f)
25 if abort:
25 if abort:
26 raise util.Abort(msg)
26 raise util.Abort(msg)
27 ui.warn(_("warning: %s\n") % msg)
27 ui.warn(_("warning: %s\n") % msg)
28
28
29 def checkportabilityalert(ui):
29 def checkportabilityalert(ui):
30 '''check if the user's config requests nothing, a warning, or abort for
30 '''check if the user's config requests nothing, a warning, or abort for
31 non-portable filenames'''
31 non-portable filenames'''
32 val = ui.config('ui', 'portablefilenames', 'warn')
32 val = ui.config('ui', 'portablefilenames', 'warn')
33 lval = val.lower()
33 lval = val.lower()
34 bval = util.parsebool(val)
34 bval = util.parsebool(val)
35 abort = os.name == 'nt' or lval == 'abort'
35 abort = os.name == 'nt' or lval == 'abort'
36 warn = bval or lval == 'warn'
36 warn = bval or lval == 'warn'
37 if bval is None and not (warn or abort or lval == 'ignore'):
37 if bval is None and not (warn or abort or lval == 'ignore'):
38 raise error.ConfigError(
38 raise error.ConfigError(
39 _("ui.portablefilenames value is invalid ('%s')") % val)
39 _("ui.portablefilenames value is invalid ('%s')") % val)
40 return abort, warn
40 return abort, warn
41
41
42 class casecollisionauditor(object):
42 class casecollisionauditor(object):
43 def __init__(self, ui, abort, existingiter):
43 def __init__(self, ui, abort, existingiter):
44 self._ui = ui
44 self._ui = ui
45 self._abort = abort
45 self._abort = abort
46 self._map = {}
46 self._map = {}
47 for f in existingiter:
47 for f in existingiter:
48 self._map[f.lower()] = f
48 self._map[f.lower()] = f
49
49
50 def __call__(self, f):
50 def __call__(self, f):
51 fl = f.lower()
51 fl = f.lower()
52 map = self._map
52 map = self._map
53 if fl in map and map[fl] != f:
53 if fl in map and map[fl] != f:
54 msg = _('possible case-folding collision for %s') % f
54 msg = _('possible case-folding collision for %s') % f
55 if self._abort:
55 if self._abort:
56 raise util.Abort(msg)
56 raise util.Abort(msg)
57 self._ui.warn(_("warning: %s\n") % msg)
57 self._ui.warn(_("warning: %s\n") % msg)
58 map[fl] = f
58 map[fl] = f
59
59
60 class path_auditor(object):
60 class pathauditor(object):
61 '''ensure that a filesystem path contains no banned components.
61 '''ensure that a filesystem path contains no banned components.
62 the following properties of a path are checked:
62 the following properties of a path are checked:
63
63
64 - ends with a directory separator
64 - ends with a directory separator
65 - under top-level .hg
65 - under top-level .hg
66 - starts at the root of a windows drive
66 - starts at the root of a windows drive
67 - contains ".."
67 - contains ".."
68 - traverses a symlink (e.g. a/symlink_here/b)
68 - traverses a symlink (e.g. a/symlink_here/b)
69 - inside a nested repository (a callback can be used to approve
69 - inside a nested repository (a callback can be used to approve
70 some nested repositories, e.g., subrepositories)
70 some nested repositories, e.g., subrepositories)
71 '''
71 '''
72
72
73 def __init__(self, root, callback=None):
73 def __init__(self, root, callback=None):
74 self.audited = set()
74 self.audited = set()
75 self.auditeddir = set()
75 self.auditeddir = set()
76 self.root = root
76 self.root = root
77 self.callback = callback
77 self.callback = callback
78
78
79 def __call__(self, path):
79 def __call__(self, path):
80 '''Check the relative path.
80 '''Check the relative path.
81 path may contain a pattern (e.g. foodir/**.txt)'''
81 path may contain a pattern (e.g. foodir/**.txt)'''
82
82
83 if path in self.audited:
83 if path in self.audited:
84 return
84 return
85 # AIX ignores "/" at end of path, others raise EISDIR.
85 # AIX ignores "/" at end of path, others raise EISDIR.
86 if util.endswithsep(path):
86 if util.endswithsep(path):
87 raise util.Abort(_("path ends in directory separator: %s") % path)
87 raise util.Abort(_("path ends in directory separator: %s") % path)
88 normpath = os.path.normcase(path)
88 normpath = os.path.normcase(path)
89 parts = util.splitpath(normpath)
89 parts = util.splitpath(normpath)
90 if (os.path.splitdrive(path)[0]
90 if (os.path.splitdrive(path)[0]
91 or parts[0].lower() in ('.hg', '.hg.', '')
91 or parts[0].lower() in ('.hg', '.hg.', '')
92 or os.pardir in parts):
92 or os.pardir in parts):
93 raise util.Abort(_("path contains illegal component: %s") % path)
93 raise util.Abort(_("path contains illegal component: %s") % path)
94 if '.hg' in path.lower():
94 if '.hg' in path.lower():
95 lparts = [p.lower() for p in parts]
95 lparts = [p.lower() for p in parts]
96 for p in '.hg', '.hg.':
96 for p in '.hg', '.hg.':
97 if p in lparts[1:]:
97 if p in lparts[1:]:
98 pos = lparts.index(p)
98 pos = lparts.index(p)
99 base = os.path.join(*parts[:pos])
99 base = os.path.join(*parts[:pos])
100 raise util.Abort(_('path %r is inside nested repo %r')
100 raise util.Abort(_('path %r is inside nested repo %r')
101 % (path, base))
101 % (path, base))
102
102
103 parts.pop()
103 parts.pop()
104 prefixes = []
104 prefixes = []
105 while parts:
105 while parts:
106 prefix = os.sep.join(parts)
106 prefix = os.sep.join(parts)
107 if prefix in self.auditeddir:
107 if prefix in self.auditeddir:
108 break
108 break
109 curpath = os.path.join(self.root, prefix)
109 curpath = os.path.join(self.root, prefix)
110 try:
110 try:
111 st = os.lstat(curpath)
111 st = os.lstat(curpath)
112 except OSError, err:
112 except OSError, err:
113 # EINVAL can be raised as invalid path syntax under win32.
113 # EINVAL can be raised as invalid path syntax under win32.
114 # They must be ignored for patterns can be checked too.
114 # They must be ignored for patterns can be checked too.
115 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
115 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
116 raise
116 raise
117 else:
117 else:
118 if stat.S_ISLNK(st.st_mode):
118 if stat.S_ISLNK(st.st_mode):
119 raise util.Abort(
119 raise util.Abort(
120 _('path %r traverses symbolic link %r')
120 _('path %r traverses symbolic link %r')
121 % (path, prefix))
121 % (path, prefix))
122 elif (stat.S_ISDIR(st.st_mode) and
122 elif (stat.S_ISDIR(st.st_mode) and
123 os.path.isdir(os.path.join(curpath, '.hg'))):
123 os.path.isdir(os.path.join(curpath, '.hg'))):
124 if not self.callback or not self.callback(curpath):
124 if not self.callback or not self.callback(curpath):
125 raise util.Abort(_('path %r is inside nested repo %r') %
125 raise util.Abort(_('path %r is inside nested repo %r') %
126 (path, prefix))
126 (path, prefix))
127 prefixes.append(prefix)
127 prefixes.append(prefix)
128 parts.pop()
128 parts.pop()
129
129
130 self.audited.add(path)
130 self.audited.add(path)
131 # only add prefixes to the cache after checking everything: we don't
131 # only add prefixes to the cache after checking everything: we don't
132 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
132 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
133 self.auditeddir.update(prefixes)
133 self.auditeddir.update(prefixes)
134
134
135 class abstractopener(object):
135 class abstractopener(object):
136 """Abstract base class; cannot be instantiated"""
136 """Abstract base class; cannot be instantiated"""
137
137
138 def __init__(self, *args, **kwargs):
138 def __init__(self, *args, **kwargs):
139 '''Prevent instantiation; don't call this from subclasses.'''
139 '''Prevent instantiation; don't call this from subclasses.'''
140 raise NotImplementedError('attempted instantiating ' + str(type(self)))
140 raise NotImplementedError('attempted instantiating ' + str(type(self)))
141
141
142 def read(self, path):
142 def read(self, path):
143 fp = self(path, 'rb')
143 fp = self(path, 'rb')
144 try:
144 try:
145 return fp.read()
145 return fp.read()
146 finally:
146 finally:
147 fp.close()
147 fp.close()
148
148
149 def write(self, path, data):
149 def write(self, path, data):
150 fp = self(path, 'wb')
150 fp = self(path, 'wb')
151 try:
151 try:
152 return fp.write(data)
152 return fp.write(data)
153 finally:
153 finally:
154 fp.close()
154 fp.close()
155
155
156 def append(self, path, data):
156 def append(self, path, data):
157 fp = self(path, 'ab')
157 fp = self(path, 'ab')
158 try:
158 try:
159 return fp.write(data)
159 return fp.write(data)
160 finally:
160 finally:
161 fp.close()
161 fp.close()
162
162
163 class opener(abstractopener):
163 class opener(abstractopener):
164 '''Open files relative to a base directory
164 '''Open files relative to a base directory
165
165
166 This class is used to hide the details of COW semantics and
166 This class is used to hide the details of COW semantics and
167 remote file access from higher level code.
167 remote file access from higher level code.
168 '''
168 '''
169 def __init__(self, base, audit=True):
169 def __init__(self, base, audit=True):
170 self.base = base
170 self.base = base
171 if audit:
171 if audit:
172 self.auditor = path_auditor(base)
172 self.auditor = pathauditor(base)
173 else:
173 else:
174 self.auditor = util.always
174 self.auditor = util.always
175 self.createmode = None
175 self.createmode = None
176 self._trustnlink = None
176 self._trustnlink = None
177
177
178 @util.propertycache
178 @util.propertycache
179 def _can_symlink(self):
179 def _can_symlink(self):
180 return util.checklink(self.base)
180 return util.checklink(self.base)
181
181
182 def _fixfilemode(self, name):
182 def _fixfilemode(self, name):
183 if self.createmode is None:
183 if self.createmode is None:
184 return
184 return
185 os.chmod(name, self.createmode & 0666)
185 os.chmod(name, self.createmode & 0666)
186
186
187 def __call__(self, path, mode="r", text=False, atomictemp=False):
187 def __call__(self, path, mode="r", text=False, atomictemp=False):
188 r = util.checkosfilename(path)
188 r = util.checkosfilename(path)
189 if r:
189 if r:
190 raise util.Abort("%s: %r" % (r, path))
190 raise util.Abort("%s: %r" % (r, path))
191 self.auditor(path)
191 self.auditor(path)
192 f = os.path.join(self.base, path)
192 f = os.path.join(self.base, path)
193
193
194 if not text and "b" not in mode:
194 if not text and "b" not in mode:
195 mode += "b" # for that other OS
195 mode += "b" # for that other OS
196
196
197 nlink = -1
197 nlink = -1
198 dirname, basename = os.path.split(f)
198 dirname, basename = os.path.split(f)
199 # If basename is empty, then the path is malformed because it points
199 # If basename is empty, then the path is malformed because it points
200 # to a directory. Let the posixfile() call below raise IOError.
200 # to a directory. Let the posixfile() call below raise IOError.
201 if basename and mode not in ('r', 'rb'):
201 if basename and mode not in ('r', 'rb'):
202 if atomictemp:
202 if atomictemp:
203 if not os.path.isdir(dirname):
203 if not os.path.isdir(dirname):
204 util.makedirs(dirname, self.createmode)
204 util.makedirs(dirname, self.createmode)
205 return util.atomictempfile(f, mode, self.createmode)
205 return util.atomictempfile(f, mode, self.createmode)
206 try:
206 try:
207 if 'w' in mode:
207 if 'w' in mode:
208 util.unlink(f)
208 util.unlink(f)
209 nlink = 0
209 nlink = 0
210 else:
210 else:
211 # nlinks() may behave differently for files on Windows
211 # nlinks() may behave differently for files on Windows
212 # shares if the file is open.
212 # shares if the file is open.
213 fd = util.posixfile(f)
213 fd = util.posixfile(f)
214 nlink = util.nlinks(f)
214 nlink = util.nlinks(f)
215 if nlink < 1:
215 if nlink < 1:
216 nlink = 2 # force mktempcopy (issue1922)
216 nlink = 2 # force mktempcopy (issue1922)
217 fd.close()
217 fd.close()
218 except (OSError, IOError), e:
218 except (OSError, IOError), e:
219 if e.errno != errno.ENOENT:
219 if e.errno != errno.ENOENT:
220 raise
220 raise
221 nlink = 0
221 nlink = 0
222 if not os.path.isdir(dirname):
222 if not os.path.isdir(dirname):
223 util.makedirs(dirname, self.createmode)
223 util.makedirs(dirname, self.createmode)
224 if nlink > 0:
224 if nlink > 0:
225 if self._trustnlink is None:
225 if self._trustnlink is None:
226 self._trustnlink = nlink > 1 or util.checknlink(f)
226 self._trustnlink = nlink > 1 or util.checknlink(f)
227 if nlink > 1 or not self._trustnlink:
227 if nlink > 1 or not self._trustnlink:
228 util.rename(util.mktempcopy(f), f)
228 util.rename(util.mktempcopy(f), f)
229 fp = util.posixfile(f, mode)
229 fp = util.posixfile(f, mode)
230 if nlink == 0:
230 if nlink == 0:
231 self._fixfilemode(f)
231 self._fixfilemode(f)
232 return fp
232 return fp
233
233
234 def symlink(self, src, dst):
234 def symlink(self, src, dst):
235 self.auditor(dst)
235 self.auditor(dst)
236 linkname = os.path.join(self.base, dst)
236 linkname = os.path.join(self.base, dst)
237 try:
237 try:
238 os.unlink(linkname)
238 os.unlink(linkname)
239 except OSError:
239 except OSError:
240 pass
240 pass
241
241
242 dirname = os.path.dirname(linkname)
242 dirname = os.path.dirname(linkname)
243 if not os.path.exists(dirname):
243 if not os.path.exists(dirname):
244 util.makedirs(dirname, self.createmode)
244 util.makedirs(dirname, self.createmode)
245
245
246 if self._can_symlink:
246 if self._can_symlink:
247 try:
247 try:
248 os.symlink(src, linkname)
248 os.symlink(src, linkname)
249 except OSError, err:
249 except OSError, err:
250 raise OSError(err.errno, _('could not symlink to %r: %s') %
250 raise OSError(err.errno, _('could not symlink to %r: %s') %
251 (src, err.strerror), linkname)
251 (src, err.strerror), linkname)
252 else:
252 else:
253 f = self(dst, "w")
253 f = self(dst, "w")
254 f.write(src)
254 f.write(src)
255 f.close()
255 f.close()
256 self._fixfilemode(dst)
256 self._fixfilemode(dst)
257
257
258 class filteropener(abstractopener):
258 class filteropener(abstractopener):
259 '''Wrapper opener for filtering filenames with a function.'''
259 '''Wrapper opener for filtering filenames with a function.'''
260
260
261 def __init__(self, opener, filter):
261 def __init__(self, opener, filter):
262 self._filter = filter
262 self._filter = filter
263 self._orig = opener
263 self._orig = opener
264
264
265 def __call__(self, path, *args, **kwargs):
265 def __call__(self, path, *args, **kwargs):
266 return self._orig(self._filter(path), *args, **kwargs)
266 return self._orig(self._filter(path), *args, **kwargs)
267
267
268 def canonpath(root, cwd, myname, auditor=None):
268 def canonpath(root, cwd, myname, auditor=None):
269 '''return the canonical path of myname, given cwd and root'''
269 '''return the canonical path of myname, given cwd and root'''
270 if util.endswithsep(root):
270 if util.endswithsep(root):
271 rootsep = root
271 rootsep = root
272 else:
272 else:
273 rootsep = root + os.sep
273 rootsep = root + os.sep
274 name = myname
274 name = myname
275 if not os.path.isabs(name):
275 if not os.path.isabs(name):
276 name = os.path.join(root, cwd, name)
276 name = os.path.join(root, cwd, name)
277 name = os.path.normpath(name)
277 name = os.path.normpath(name)
278 if auditor is None:
278 if auditor is None:
279 auditor = path_auditor(root)
279 auditor = pathauditor(root)
280 if name != rootsep and name.startswith(rootsep):
280 if name != rootsep and name.startswith(rootsep):
281 name = name[len(rootsep):]
281 name = name[len(rootsep):]
282 auditor(name)
282 auditor(name)
283 return util.pconvert(name)
283 return util.pconvert(name)
284 elif name == root:
284 elif name == root:
285 return ''
285 return ''
286 else:
286 else:
287 # Determine whether `name' is in the hierarchy at or beneath `root',
287 # Determine whether `name' is in the hierarchy at or beneath `root',
288 # by iterating name=dirname(name) until that causes no change (can't
288 # by iterating name=dirname(name) until that causes no change (can't
289 # check name == '/', because that doesn't work on windows). For each
289 # check name == '/', because that doesn't work on windows). For each
290 # `name', compare dev/inode numbers. If they match, the list `rel'
290 # `name', compare dev/inode numbers. If they match, the list `rel'
291 # holds the reversed list of components making up the relative file
291 # holds the reversed list of components making up the relative file
292 # name we want.
292 # name we want.
293 root_st = os.stat(root)
293 root_st = os.stat(root)
294 rel = []
294 rel = []
295 while True:
295 while True:
296 try:
296 try:
297 name_st = os.stat(name)
297 name_st = os.stat(name)
298 except OSError:
298 except OSError:
299 break
299 break
300 if util.samestat(name_st, root_st):
300 if util.samestat(name_st, root_st):
301 if not rel:
301 if not rel:
302 # name was actually the same as root (maybe a symlink)
302 # name was actually the same as root (maybe a symlink)
303 return ''
303 return ''
304 rel.reverse()
304 rel.reverse()
305 name = os.path.join(*rel)
305 name = os.path.join(*rel)
306 auditor(name)
306 auditor(name)
307 return util.pconvert(name)
307 return util.pconvert(name)
308 dirname, basename = os.path.split(name)
308 dirname, basename = os.path.split(name)
309 rel.append(basename)
309 rel.append(basename)
310 if dirname == name:
310 if dirname == name:
311 break
311 break
312 name = dirname
312 name = dirname
313
313
314 raise util.Abort('%s not under root' % myname)
314 raise util.Abort('%s not under root' % myname)
315
315
316 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
316 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
317 '''yield every hg repository under path, recursively.'''
317 '''yield every hg repository under path, recursively.'''
318 def errhandler(err):
318 def errhandler(err):
319 if err.filename == path:
319 if err.filename == path:
320 raise err
320 raise err
321 if followsym and hasattr(os.path, 'samestat'):
321 if followsym and hasattr(os.path, 'samestat'):
322 def _add_dir_if_not_there(dirlst, dirname):
322 def _add_dir_if_not_there(dirlst, dirname):
323 match = False
323 match = False
324 samestat = os.path.samestat
324 samestat = os.path.samestat
325 dirstat = os.stat(dirname)
325 dirstat = os.stat(dirname)
326 for lstdirstat in dirlst:
326 for lstdirstat in dirlst:
327 if samestat(dirstat, lstdirstat):
327 if samestat(dirstat, lstdirstat):
328 match = True
328 match = True
329 break
329 break
330 if not match:
330 if not match:
331 dirlst.append(dirstat)
331 dirlst.append(dirstat)
332 return not match
332 return not match
333 else:
333 else:
334 followsym = False
334 followsym = False
335
335
336 if (seen_dirs is None) and followsym:
336 if (seen_dirs is None) and followsym:
337 seen_dirs = []
337 seen_dirs = []
338 _add_dir_if_not_there(seen_dirs, path)
338 _add_dir_if_not_there(seen_dirs, path)
339 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
339 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
340 dirs.sort()
340 dirs.sort()
341 if '.hg' in dirs:
341 if '.hg' in dirs:
342 yield root # found a repository
342 yield root # found a repository
343 qroot = os.path.join(root, '.hg', 'patches')
343 qroot = os.path.join(root, '.hg', 'patches')
344 if os.path.isdir(os.path.join(qroot, '.hg')):
344 if os.path.isdir(os.path.join(qroot, '.hg')):
345 yield qroot # we have a patch queue repo here
345 yield qroot # we have a patch queue repo here
346 if recurse:
346 if recurse:
347 # avoid recursing inside the .hg directory
347 # avoid recursing inside the .hg directory
348 dirs.remove('.hg')
348 dirs.remove('.hg')
349 else:
349 else:
350 dirs[:] = [] # don't descend further
350 dirs[:] = [] # don't descend further
351 elif followsym:
351 elif followsym:
352 newdirs = []
352 newdirs = []
353 for d in dirs:
353 for d in dirs:
354 fname = os.path.join(root, d)
354 fname = os.path.join(root, d)
355 if _add_dir_if_not_there(seen_dirs, fname):
355 if _add_dir_if_not_there(seen_dirs, fname):
356 if os.path.islink(fname):
356 if os.path.islink(fname):
357 for hgname in walkrepos(fname, True, seen_dirs):
357 for hgname in walkrepos(fname, True, seen_dirs):
358 yield hgname
358 yield hgname
359 else:
359 else:
360 newdirs.append(d)
360 newdirs.append(d)
361 dirs[:] = newdirs
361 dirs[:] = newdirs
362
362
363 def os_rcpath():
363 def os_rcpath():
364 '''return default os-specific hgrc search path'''
364 '''return default os-specific hgrc search path'''
365 path = system_rcpath()
365 path = system_rcpath()
366 path.extend(user_rcpath())
366 path.extend(user_rcpath())
367 path = [os.path.normpath(f) for f in path]
367 path = [os.path.normpath(f) for f in path]
368 return path
368 return path
369
369
370 _rcpath = None
370 _rcpath = None
371
371
372 def rcpath():
372 def rcpath():
373 '''return hgrc search path. if env var HGRCPATH is set, use it.
373 '''return hgrc search path. if env var HGRCPATH is set, use it.
374 for each item in path, if directory, use files ending in .rc,
374 for each item in path, if directory, use files ending in .rc,
375 else use item.
375 else use item.
376 make HGRCPATH empty to only look in .hg/hgrc of current repo.
376 make HGRCPATH empty to only look in .hg/hgrc of current repo.
377 if no HGRCPATH, use default os-specific path.'''
377 if no HGRCPATH, use default os-specific path.'''
378 global _rcpath
378 global _rcpath
379 if _rcpath is None:
379 if _rcpath is None:
380 if 'HGRCPATH' in os.environ:
380 if 'HGRCPATH' in os.environ:
381 _rcpath = []
381 _rcpath = []
382 for p in os.environ['HGRCPATH'].split(os.pathsep):
382 for p in os.environ['HGRCPATH'].split(os.pathsep):
383 if not p:
383 if not p:
384 continue
384 continue
385 p = util.expandpath(p)
385 p = util.expandpath(p)
386 if os.path.isdir(p):
386 if os.path.isdir(p):
387 for f, kind in osutil.listdir(p):
387 for f, kind in osutil.listdir(p):
388 if f.endswith('.rc'):
388 if f.endswith('.rc'):
389 _rcpath.append(os.path.join(p, f))
389 _rcpath.append(os.path.join(p, f))
390 else:
390 else:
391 _rcpath.append(p)
391 _rcpath.append(p)
392 else:
392 else:
393 _rcpath = os_rcpath()
393 _rcpath = os_rcpath()
394 return _rcpath
394 return _rcpath
395
395
396 if os.name != 'nt':
396 if os.name != 'nt':
397
397
398 def rcfiles(path):
398 def rcfiles(path):
399 rcs = [os.path.join(path, 'hgrc')]
399 rcs = [os.path.join(path, 'hgrc')]
400 rcdir = os.path.join(path, 'hgrc.d')
400 rcdir = os.path.join(path, 'hgrc.d')
401 try:
401 try:
402 rcs.extend([os.path.join(rcdir, f)
402 rcs.extend([os.path.join(rcdir, f)
403 for f, kind in osutil.listdir(rcdir)
403 for f, kind in osutil.listdir(rcdir)
404 if f.endswith(".rc")])
404 if f.endswith(".rc")])
405 except OSError:
405 except OSError:
406 pass
406 pass
407 return rcs
407 return rcs
408
408
409 def system_rcpath():
409 def system_rcpath():
410 path = []
410 path = []
411 # old mod_python does not set sys.argv
411 # old mod_python does not set sys.argv
412 if len(getattr(sys, 'argv', [])) > 0:
412 if len(getattr(sys, 'argv', [])) > 0:
413 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
413 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
414 '/../etc/mercurial'))
414 '/../etc/mercurial'))
415 path.extend(rcfiles('/etc/mercurial'))
415 path.extend(rcfiles('/etc/mercurial'))
416 return path
416 return path
417
417
418 def user_rcpath():
418 def user_rcpath():
419 return [os.path.expanduser('~/.hgrc')]
419 return [os.path.expanduser('~/.hgrc')]
420
420
421 else:
421 else:
422
422
423 _HKEY_LOCAL_MACHINE = 0x80000002L
423 _HKEY_LOCAL_MACHINE = 0x80000002L
424
424
425 def system_rcpath():
425 def system_rcpath():
426 '''return default os-specific hgrc search path'''
426 '''return default os-specific hgrc search path'''
427 rcpath = []
427 rcpath = []
428 filename = util.executable_path()
428 filename = util.executable_path()
429 # Use mercurial.ini found in directory with hg.exe
429 # Use mercurial.ini found in directory with hg.exe
430 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
430 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
431 if os.path.isfile(progrc):
431 if os.path.isfile(progrc):
432 rcpath.append(progrc)
432 rcpath.append(progrc)
433 return rcpath
433 return rcpath
434 # Use hgrc.d found in directory with hg.exe
434 # Use hgrc.d found in directory with hg.exe
435 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
435 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
436 if os.path.isdir(progrcd):
436 if os.path.isdir(progrcd):
437 for f, kind in osutil.listdir(progrcd):
437 for f, kind in osutil.listdir(progrcd):
438 if f.endswith('.rc'):
438 if f.endswith('.rc'):
439 rcpath.append(os.path.join(progrcd, f))
439 rcpath.append(os.path.join(progrcd, f))
440 return rcpath
440 return rcpath
441 # else look for a system rcpath in the registry
441 # else look for a system rcpath in the registry
442 value = util.lookup_reg('SOFTWARE\\Mercurial', None,
442 value = util.lookup_reg('SOFTWARE\\Mercurial', None,
443 _HKEY_LOCAL_MACHINE)
443 _HKEY_LOCAL_MACHINE)
444 if not isinstance(value, str) or not value:
444 if not isinstance(value, str) or not value:
445 return rcpath
445 return rcpath
446 value = value.replace('/', os.sep)
446 value = value.replace('/', os.sep)
447 for p in value.split(os.pathsep):
447 for p in value.split(os.pathsep):
448 if p.lower().endswith('mercurial.ini'):
448 if p.lower().endswith('mercurial.ini'):
449 rcpath.append(p)
449 rcpath.append(p)
450 elif os.path.isdir(p):
450 elif os.path.isdir(p):
451 for f, kind in osutil.listdir(p):
451 for f, kind in osutil.listdir(p):
452 if f.endswith('.rc'):
452 if f.endswith('.rc'):
453 rcpath.append(os.path.join(p, f))
453 rcpath.append(os.path.join(p, f))
454 return rcpath
454 return rcpath
455
455
456 def user_rcpath():
456 def user_rcpath():
457 '''return os-specific hgrc search path to the user dir'''
457 '''return os-specific hgrc search path to the user dir'''
458 home = os.path.expanduser('~')
458 home = os.path.expanduser('~')
459 path = [os.path.join(home, 'mercurial.ini'),
459 path = [os.path.join(home, 'mercurial.ini'),
460 os.path.join(home, '.hgrc')]
460 os.path.join(home, '.hgrc')]
461 userprofile = os.environ.get('USERPROFILE')
461 userprofile = os.environ.get('USERPROFILE')
462 if userprofile:
462 if userprofile:
463 path.append(os.path.join(userprofile, 'mercurial.ini'))
463 path.append(os.path.join(userprofile, 'mercurial.ini'))
464 path.append(os.path.join(userprofile, '.hgrc'))
464 path.append(os.path.join(userprofile, '.hgrc'))
465 return path
465 return path
@@ -1,1045 +1,1045 b''
1 # subrepo.py - sub-repository handling for Mercurial
1 # subrepo.py - sub-repository handling for Mercurial
2 #
2 #
3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import errno, os, re, xml.dom.minidom, shutil, posixpath
8 import errno, os, re, xml.dom.minidom, shutil, posixpath
9 import stat, subprocess, tarfile
9 import stat, subprocess, tarfile
10 from i18n import _
10 from i18n import _
11 import config, scmutil, util, node, error, cmdutil, bookmarks
11 import config, scmutil, util, node, error, cmdutil, bookmarks
12 hg = None
12 hg = None
13 propertycache = util.propertycache
13 propertycache = util.propertycache
14
14
15 nullstate = ('', '', 'empty')
15 nullstate = ('', '', 'empty')
16
16
17 def state(ctx, ui):
17 def state(ctx, ui):
18 """return a state dict, mapping subrepo paths configured in .hgsub
18 """return a state dict, mapping subrepo paths configured in .hgsub
19 to tuple: (source from .hgsub, revision from .hgsubstate, kind
19 to tuple: (source from .hgsub, revision from .hgsubstate, kind
20 (key in types dict))
20 (key in types dict))
21 """
21 """
22 p = config.config()
22 p = config.config()
23 def read(f, sections=None, remap=None):
23 def read(f, sections=None, remap=None):
24 if f in ctx:
24 if f in ctx:
25 try:
25 try:
26 data = ctx[f].data()
26 data = ctx[f].data()
27 except IOError, err:
27 except IOError, err:
28 if err.errno != errno.ENOENT:
28 if err.errno != errno.ENOENT:
29 raise
29 raise
30 # handle missing subrepo spec files as removed
30 # handle missing subrepo spec files as removed
31 ui.warn(_("warning: subrepo spec file %s not found\n") % f)
31 ui.warn(_("warning: subrepo spec file %s not found\n") % f)
32 return
32 return
33 p.parse(f, data, sections, remap, read)
33 p.parse(f, data, sections, remap, read)
34 else:
34 else:
35 raise util.Abort(_("subrepo spec file %s not found") % f)
35 raise util.Abort(_("subrepo spec file %s not found") % f)
36
36
37 if '.hgsub' in ctx:
37 if '.hgsub' in ctx:
38 read('.hgsub')
38 read('.hgsub')
39
39
40 for path, src in ui.configitems('subpaths'):
40 for path, src in ui.configitems('subpaths'):
41 p.set('subpaths', path, src, ui.configsource('subpaths', path))
41 p.set('subpaths', path, src, ui.configsource('subpaths', path))
42
42
43 rev = {}
43 rev = {}
44 if '.hgsubstate' in ctx:
44 if '.hgsubstate' in ctx:
45 try:
45 try:
46 for l in ctx['.hgsubstate'].data().splitlines():
46 for l in ctx['.hgsubstate'].data().splitlines():
47 revision, path = l.split(" ", 1)
47 revision, path = l.split(" ", 1)
48 rev[path] = revision
48 rev[path] = revision
49 except IOError, err:
49 except IOError, err:
50 if err.errno != errno.ENOENT:
50 if err.errno != errno.ENOENT:
51 raise
51 raise
52
52
53 state = {}
53 state = {}
54 for path, src in p[''].items():
54 for path, src in p[''].items():
55 kind = 'hg'
55 kind = 'hg'
56 if src.startswith('['):
56 if src.startswith('['):
57 if ']' not in src:
57 if ']' not in src:
58 raise util.Abort(_('missing ] in subrepo source'))
58 raise util.Abort(_('missing ] in subrepo source'))
59 kind, src = src.split(']', 1)
59 kind, src = src.split(']', 1)
60 kind = kind[1:]
60 kind = kind[1:]
61
61
62 for pattern, repl in p.items('subpaths'):
62 for pattern, repl in p.items('subpaths'):
63 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
63 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
64 # does a string decode.
64 # does a string decode.
65 repl = repl.encode('string-escape')
65 repl = repl.encode('string-escape')
66 # However, we still want to allow back references to go
66 # However, we still want to allow back references to go
67 # through unharmed, so we turn r'\\1' into r'\1'. Again,
67 # through unharmed, so we turn r'\\1' into r'\1'. Again,
68 # extra escapes are needed because re.sub string decodes.
68 # extra escapes are needed because re.sub string decodes.
69 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
69 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
70 try:
70 try:
71 src = re.sub(pattern, repl, src, 1)
71 src = re.sub(pattern, repl, src, 1)
72 except re.error, e:
72 except re.error, e:
73 raise util.Abort(_("bad subrepository pattern in %s: %s")
73 raise util.Abort(_("bad subrepository pattern in %s: %s")
74 % (p.source('subpaths', pattern), e))
74 % (p.source('subpaths', pattern), e))
75
75
76 state[path] = (src.strip(), rev.get(path, ''), kind)
76 state[path] = (src.strip(), rev.get(path, ''), kind)
77
77
78 return state
78 return state
79
79
80 def writestate(repo, state):
80 def writestate(repo, state):
81 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
81 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
82 repo.wwrite('.hgsubstate',
82 repo.wwrite('.hgsubstate',
83 ''.join(['%s %s\n' % (state[s][1], s)
83 ''.join(['%s %s\n' % (state[s][1], s)
84 for s in sorted(state)]), '')
84 for s in sorted(state)]), '')
85
85
86 def submerge(repo, wctx, mctx, actx, overwrite):
86 def submerge(repo, wctx, mctx, actx, overwrite):
87 """delegated from merge.applyupdates: merging of .hgsubstate file
87 """delegated from merge.applyupdates: merging of .hgsubstate file
88 in working context, merging context and ancestor context"""
88 in working context, merging context and ancestor context"""
89 if mctx == actx: # backwards?
89 if mctx == actx: # backwards?
90 actx = wctx.p1()
90 actx = wctx.p1()
91 s1 = wctx.substate
91 s1 = wctx.substate
92 s2 = mctx.substate
92 s2 = mctx.substate
93 sa = actx.substate
93 sa = actx.substate
94 sm = {}
94 sm = {}
95
95
96 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
96 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
97
97
98 def debug(s, msg, r=""):
98 def debug(s, msg, r=""):
99 if r:
99 if r:
100 r = "%s:%s:%s" % r
100 r = "%s:%s:%s" % r
101 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
101 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
102
102
103 for s, l in s1.items():
103 for s, l in s1.items():
104 a = sa.get(s, nullstate)
104 a = sa.get(s, nullstate)
105 ld = l # local state with possible dirty flag for compares
105 ld = l # local state with possible dirty flag for compares
106 if wctx.sub(s).dirty():
106 if wctx.sub(s).dirty():
107 ld = (l[0], l[1] + "+")
107 ld = (l[0], l[1] + "+")
108 if wctx == actx: # overwrite
108 if wctx == actx: # overwrite
109 a = ld
109 a = ld
110
110
111 if s in s2:
111 if s in s2:
112 r = s2[s]
112 r = s2[s]
113 if ld == r or r == a: # no change or local is newer
113 if ld == r or r == a: # no change or local is newer
114 sm[s] = l
114 sm[s] = l
115 continue
115 continue
116 elif ld == a: # other side changed
116 elif ld == a: # other side changed
117 debug(s, "other changed, get", r)
117 debug(s, "other changed, get", r)
118 wctx.sub(s).get(r, overwrite)
118 wctx.sub(s).get(r, overwrite)
119 sm[s] = r
119 sm[s] = r
120 elif ld[0] != r[0]: # sources differ
120 elif ld[0] != r[0]: # sources differ
121 if repo.ui.promptchoice(
121 if repo.ui.promptchoice(
122 _(' subrepository sources for %s differ\n'
122 _(' subrepository sources for %s differ\n'
123 'use (l)ocal source (%s) or (r)emote source (%s)?')
123 'use (l)ocal source (%s) or (r)emote source (%s)?')
124 % (s, l[0], r[0]),
124 % (s, l[0], r[0]),
125 (_('&Local'), _('&Remote')), 0):
125 (_('&Local'), _('&Remote')), 0):
126 debug(s, "prompt changed, get", r)
126 debug(s, "prompt changed, get", r)
127 wctx.sub(s).get(r, overwrite)
127 wctx.sub(s).get(r, overwrite)
128 sm[s] = r
128 sm[s] = r
129 elif ld[1] == a[1]: # local side is unchanged
129 elif ld[1] == a[1]: # local side is unchanged
130 debug(s, "other side changed, get", r)
130 debug(s, "other side changed, get", r)
131 wctx.sub(s).get(r, overwrite)
131 wctx.sub(s).get(r, overwrite)
132 sm[s] = r
132 sm[s] = r
133 else:
133 else:
134 debug(s, "both sides changed, merge with", r)
134 debug(s, "both sides changed, merge with", r)
135 wctx.sub(s).merge(r)
135 wctx.sub(s).merge(r)
136 sm[s] = l
136 sm[s] = l
137 elif ld == a: # remote removed, local unchanged
137 elif ld == a: # remote removed, local unchanged
138 debug(s, "remote removed, remove")
138 debug(s, "remote removed, remove")
139 wctx.sub(s).remove()
139 wctx.sub(s).remove()
140 else:
140 else:
141 if repo.ui.promptchoice(
141 if repo.ui.promptchoice(
142 _(' local changed subrepository %s which remote removed\n'
142 _(' local changed subrepository %s which remote removed\n'
143 'use (c)hanged version or (d)elete?') % s,
143 'use (c)hanged version or (d)elete?') % s,
144 (_('&Changed'), _('&Delete')), 0):
144 (_('&Changed'), _('&Delete')), 0):
145 debug(s, "prompt remove")
145 debug(s, "prompt remove")
146 wctx.sub(s).remove()
146 wctx.sub(s).remove()
147
147
148 for s, r in sorted(s2.items()):
148 for s, r in sorted(s2.items()):
149 if s in s1:
149 if s in s1:
150 continue
150 continue
151 elif s not in sa:
151 elif s not in sa:
152 debug(s, "remote added, get", r)
152 debug(s, "remote added, get", r)
153 mctx.sub(s).get(r)
153 mctx.sub(s).get(r)
154 sm[s] = r
154 sm[s] = r
155 elif r != sa[s]:
155 elif r != sa[s]:
156 if repo.ui.promptchoice(
156 if repo.ui.promptchoice(
157 _(' remote changed subrepository %s which local removed\n'
157 _(' remote changed subrepository %s which local removed\n'
158 'use (c)hanged version or (d)elete?') % s,
158 'use (c)hanged version or (d)elete?') % s,
159 (_('&Changed'), _('&Delete')), 0) == 0:
159 (_('&Changed'), _('&Delete')), 0) == 0:
160 debug(s, "prompt recreate", r)
160 debug(s, "prompt recreate", r)
161 wctx.sub(s).get(r)
161 wctx.sub(s).get(r)
162 sm[s] = r
162 sm[s] = r
163
163
164 # record merged .hgsubstate
164 # record merged .hgsubstate
165 writestate(repo, sm)
165 writestate(repo, sm)
166
166
167 def _updateprompt(ui, sub, dirty, local, remote):
167 def _updateprompt(ui, sub, dirty, local, remote):
168 if dirty:
168 if dirty:
169 msg = (_(' subrepository sources for %s differ\n'
169 msg = (_(' subrepository sources for %s differ\n'
170 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
170 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
171 % (subrelpath(sub), local, remote))
171 % (subrelpath(sub), local, remote))
172 else:
172 else:
173 msg = (_(' subrepository sources for %s differ (in checked out version)\n'
173 msg = (_(' subrepository sources for %s differ (in checked out version)\n'
174 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
174 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
175 % (subrelpath(sub), local, remote))
175 % (subrelpath(sub), local, remote))
176 return ui.promptchoice(msg, (_('&Local'), _('&Remote')), 0)
176 return ui.promptchoice(msg, (_('&Local'), _('&Remote')), 0)
177
177
178 def reporelpath(repo):
178 def reporelpath(repo):
179 """return path to this (sub)repo as seen from outermost repo"""
179 """return path to this (sub)repo as seen from outermost repo"""
180 parent = repo
180 parent = repo
181 while hasattr(parent, '_subparent'):
181 while hasattr(parent, '_subparent'):
182 parent = parent._subparent
182 parent = parent._subparent
183 return repo.root[len(parent.root)+1:]
183 return repo.root[len(parent.root)+1:]
184
184
185 def subrelpath(sub):
185 def subrelpath(sub):
186 """return path to this subrepo as seen from outermost repo"""
186 """return path to this subrepo as seen from outermost repo"""
187 if hasattr(sub, '_relpath'):
187 if hasattr(sub, '_relpath'):
188 return sub._relpath
188 return sub._relpath
189 if not hasattr(sub, '_repo'):
189 if not hasattr(sub, '_repo'):
190 return sub._path
190 return sub._path
191 return reporelpath(sub._repo)
191 return reporelpath(sub._repo)
192
192
193 def _abssource(repo, push=False, abort=True):
193 def _abssource(repo, push=False, abort=True):
194 """return pull/push path of repo - either based on parent repo .hgsub info
194 """return pull/push path of repo - either based on parent repo .hgsub info
195 or on the top repo config. Abort or return None if no source found."""
195 or on the top repo config. Abort or return None if no source found."""
196 if hasattr(repo, '_subparent'):
196 if hasattr(repo, '_subparent'):
197 source = util.url(repo._subsource)
197 source = util.url(repo._subsource)
198 source.path = posixpath.normpath(source.path)
198 source.path = posixpath.normpath(source.path)
199 if posixpath.isabs(source.path) or source.scheme:
199 if posixpath.isabs(source.path) or source.scheme:
200 return str(source)
200 return str(source)
201 parent = _abssource(repo._subparent, push, abort=False)
201 parent = _abssource(repo._subparent, push, abort=False)
202 if parent:
202 if parent:
203 parent = util.url(parent)
203 parent = util.url(parent)
204 parent.path = posixpath.join(parent.path, source.path)
204 parent.path = posixpath.join(parent.path, source.path)
205 parent.path = posixpath.normpath(parent.path)
205 parent.path = posixpath.normpath(parent.path)
206 return str(parent)
206 return str(parent)
207 else: # recursion reached top repo
207 else: # recursion reached top repo
208 if hasattr(repo, '_subtoppath'):
208 if hasattr(repo, '_subtoppath'):
209 return repo._subtoppath
209 return repo._subtoppath
210 if push and repo.ui.config('paths', 'default-push'):
210 if push and repo.ui.config('paths', 'default-push'):
211 return repo.ui.config('paths', 'default-push')
211 return repo.ui.config('paths', 'default-push')
212 if repo.ui.config('paths', 'default'):
212 if repo.ui.config('paths', 'default'):
213 return repo.ui.config('paths', 'default')
213 return repo.ui.config('paths', 'default')
214 if abort:
214 if abort:
215 raise util.Abort(_("default path for subrepository %s not found") %
215 raise util.Abort(_("default path for subrepository %s not found") %
216 reporelpath(repo))
216 reporelpath(repo))
217
217
218 def itersubrepos(ctx1, ctx2):
218 def itersubrepos(ctx1, ctx2):
219 """find subrepos in ctx1 or ctx2"""
219 """find subrepos in ctx1 or ctx2"""
220 # Create a (subpath, ctx) mapping where we prefer subpaths from
220 # Create a (subpath, ctx) mapping where we prefer subpaths from
221 # ctx1. The subpaths from ctx2 are important when the .hgsub file
221 # ctx1. The subpaths from ctx2 are important when the .hgsub file
222 # has been modified (in ctx2) but not yet committed (in ctx1).
222 # has been modified (in ctx2) but not yet committed (in ctx1).
223 subpaths = dict.fromkeys(ctx2.substate, ctx2)
223 subpaths = dict.fromkeys(ctx2.substate, ctx2)
224 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
224 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
225 for subpath, ctx in sorted(subpaths.iteritems()):
225 for subpath, ctx in sorted(subpaths.iteritems()):
226 yield subpath, ctx.sub(subpath)
226 yield subpath, ctx.sub(subpath)
227
227
228 def subrepo(ctx, path):
228 def subrepo(ctx, path):
229 """return instance of the right subrepo class for subrepo in path"""
229 """return instance of the right subrepo class for subrepo in path"""
230 # subrepo inherently violates our import layering rules
230 # subrepo inherently violates our import layering rules
231 # because it wants to make repo objects from deep inside the stack
231 # because it wants to make repo objects from deep inside the stack
232 # so we manually delay the circular imports to not break
232 # so we manually delay the circular imports to not break
233 # scripts that don't use our demand-loading
233 # scripts that don't use our demand-loading
234 global hg
234 global hg
235 import hg as h
235 import hg as h
236 hg = h
236 hg = h
237
237
238 scmutil.path_auditor(ctx._repo.root)(path)
238 scmutil.pathauditor(ctx._repo.root)(path)
239 state = ctx.substate.get(path, nullstate)
239 state = ctx.substate.get(path, nullstate)
240 if state[2] not in types:
240 if state[2] not in types:
241 raise util.Abort(_('unknown subrepo type %s') % state[2])
241 raise util.Abort(_('unknown subrepo type %s') % state[2])
242 return types[state[2]](ctx, path, state[:2])
242 return types[state[2]](ctx, path, state[:2])
243
243
244 # subrepo classes need to implement the following abstract class:
244 # subrepo classes need to implement the following abstract class:
245
245
246 class abstractsubrepo(object):
246 class abstractsubrepo(object):
247
247
248 def dirty(self, ignoreupdate=False):
248 def dirty(self, ignoreupdate=False):
249 """returns true if the dirstate of the subrepo is dirty or does not
249 """returns true if the dirstate of the subrepo is dirty or does not
250 match current stored state. If ignoreupdate is true, only check
250 match current stored state. If ignoreupdate is true, only check
251 whether the subrepo has uncommitted changes in its dirstate.
251 whether the subrepo has uncommitted changes in its dirstate.
252 """
252 """
253 raise NotImplementedError
253 raise NotImplementedError
254
254
255 def checknested(self, path):
255 def checknested(self, path):
256 """check if path is a subrepository within this repository"""
256 """check if path is a subrepository within this repository"""
257 return False
257 return False
258
258
259 def commit(self, text, user, date):
259 def commit(self, text, user, date):
260 """commit the current changes to the subrepo with the given
260 """commit the current changes to the subrepo with the given
261 log message. Use given user and date if possible. Return the
261 log message. Use given user and date if possible. Return the
262 new state of the subrepo.
262 new state of the subrepo.
263 """
263 """
264 raise NotImplementedError
264 raise NotImplementedError
265
265
266 def remove(self):
266 def remove(self):
267 """remove the subrepo
267 """remove the subrepo
268
268
269 (should verify the dirstate is not dirty first)
269 (should verify the dirstate is not dirty first)
270 """
270 """
271 raise NotImplementedError
271 raise NotImplementedError
272
272
273 def get(self, state, overwrite=False):
273 def get(self, state, overwrite=False):
274 """run whatever commands are needed to put the subrepo into
274 """run whatever commands are needed to put the subrepo into
275 this state
275 this state
276 """
276 """
277 raise NotImplementedError
277 raise NotImplementedError
278
278
279 def merge(self, state):
279 def merge(self, state):
280 """merge currently-saved state with the new state."""
280 """merge currently-saved state with the new state."""
281 raise NotImplementedError
281 raise NotImplementedError
282
282
283 def push(self, force):
283 def push(self, force):
284 """perform whatever action is analogous to 'hg push'
284 """perform whatever action is analogous to 'hg push'
285
285
286 This may be a no-op on some systems.
286 This may be a no-op on some systems.
287 """
287 """
288 raise NotImplementedError
288 raise NotImplementedError
289
289
290 def add(self, ui, match, dryrun, prefix):
290 def add(self, ui, match, dryrun, prefix):
291 return []
291 return []
292
292
293 def status(self, rev2, **opts):
293 def status(self, rev2, **opts):
294 return [], [], [], [], [], [], []
294 return [], [], [], [], [], [], []
295
295
296 def diff(self, diffopts, node2, match, prefix, **opts):
296 def diff(self, diffopts, node2, match, prefix, **opts):
297 pass
297 pass
298
298
299 def outgoing(self, ui, dest, opts):
299 def outgoing(self, ui, dest, opts):
300 return 1
300 return 1
301
301
302 def incoming(self, ui, source, opts):
302 def incoming(self, ui, source, opts):
303 return 1
303 return 1
304
304
305 def files(self):
305 def files(self):
306 """return filename iterator"""
306 """return filename iterator"""
307 raise NotImplementedError
307 raise NotImplementedError
308
308
309 def filedata(self, name):
309 def filedata(self, name):
310 """return file data"""
310 """return file data"""
311 raise NotImplementedError
311 raise NotImplementedError
312
312
313 def fileflags(self, name):
313 def fileflags(self, name):
314 """return file flags"""
314 """return file flags"""
315 return ''
315 return ''
316
316
317 def archive(self, ui, archiver, prefix):
317 def archive(self, ui, archiver, prefix):
318 files = self.files()
318 files = self.files()
319 total = len(files)
319 total = len(files)
320 relpath = subrelpath(self)
320 relpath = subrelpath(self)
321 ui.progress(_('archiving (%s)') % relpath, 0,
321 ui.progress(_('archiving (%s)') % relpath, 0,
322 unit=_('files'), total=total)
322 unit=_('files'), total=total)
323 for i, name in enumerate(files):
323 for i, name in enumerate(files):
324 flags = self.fileflags(name)
324 flags = self.fileflags(name)
325 mode = 'x' in flags and 0755 or 0644
325 mode = 'x' in flags and 0755 or 0644
326 symlink = 'l' in flags
326 symlink = 'l' in flags
327 archiver.addfile(os.path.join(prefix, self._path, name),
327 archiver.addfile(os.path.join(prefix, self._path, name),
328 mode, symlink, self.filedata(name))
328 mode, symlink, self.filedata(name))
329 ui.progress(_('archiving (%s)') % relpath, i + 1,
329 ui.progress(_('archiving (%s)') % relpath, i + 1,
330 unit=_('files'), total=total)
330 unit=_('files'), total=total)
331 ui.progress(_('archiving (%s)') % relpath, None)
331 ui.progress(_('archiving (%s)') % relpath, None)
332
332
333
333
334 class hgsubrepo(abstractsubrepo):
334 class hgsubrepo(abstractsubrepo):
335 def __init__(self, ctx, path, state):
335 def __init__(self, ctx, path, state):
336 self._path = path
336 self._path = path
337 self._state = state
337 self._state = state
338 r = ctx._repo
338 r = ctx._repo
339 root = r.wjoin(path)
339 root = r.wjoin(path)
340 create = False
340 create = False
341 if not os.path.exists(os.path.join(root, '.hg')):
341 if not os.path.exists(os.path.join(root, '.hg')):
342 create = True
342 create = True
343 util.makedirs(root)
343 util.makedirs(root)
344 self._repo = hg.repository(r.ui, root, create=create)
344 self._repo = hg.repository(r.ui, root, create=create)
345 self._repo._subparent = r
345 self._repo._subparent = r
346 self._repo._subsource = state[0]
346 self._repo._subsource = state[0]
347
347
348 if create:
348 if create:
349 fp = self._repo.opener("hgrc", "w", text=True)
349 fp = self._repo.opener("hgrc", "w", text=True)
350 fp.write('[paths]\n')
350 fp.write('[paths]\n')
351
351
352 def addpathconfig(key, value):
352 def addpathconfig(key, value):
353 if value:
353 if value:
354 fp.write('%s = %s\n' % (key, value))
354 fp.write('%s = %s\n' % (key, value))
355 self._repo.ui.setconfig('paths', key, value)
355 self._repo.ui.setconfig('paths', key, value)
356
356
357 defpath = _abssource(self._repo, abort=False)
357 defpath = _abssource(self._repo, abort=False)
358 defpushpath = _abssource(self._repo, True, abort=False)
358 defpushpath = _abssource(self._repo, True, abort=False)
359 addpathconfig('default', defpath)
359 addpathconfig('default', defpath)
360 if defpath != defpushpath:
360 if defpath != defpushpath:
361 addpathconfig('default-push', defpushpath)
361 addpathconfig('default-push', defpushpath)
362 fp.close()
362 fp.close()
363
363
364 def add(self, ui, match, dryrun, prefix):
364 def add(self, ui, match, dryrun, prefix):
365 return cmdutil.add(ui, self._repo, match, dryrun, True,
365 return cmdutil.add(ui, self._repo, match, dryrun, True,
366 os.path.join(prefix, self._path))
366 os.path.join(prefix, self._path))
367
367
368 def status(self, rev2, **opts):
368 def status(self, rev2, **opts):
369 try:
369 try:
370 rev1 = self._state[1]
370 rev1 = self._state[1]
371 ctx1 = self._repo[rev1]
371 ctx1 = self._repo[rev1]
372 ctx2 = self._repo[rev2]
372 ctx2 = self._repo[rev2]
373 return self._repo.status(ctx1, ctx2, **opts)
373 return self._repo.status(ctx1, ctx2, **opts)
374 except error.RepoLookupError, inst:
374 except error.RepoLookupError, inst:
375 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
375 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
376 % (inst, subrelpath(self)))
376 % (inst, subrelpath(self)))
377 return [], [], [], [], [], [], []
377 return [], [], [], [], [], [], []
378
378
379 def diff(self, diffopts, node2, match, prefix, **opts):
379 def diff(self, diffopts, node2, match, prefix, **opts):
380 try:
380 try:
381 node1 = node.bin(self._state[1])
381 node1 = node.bin(self._state[1])
382 # We currently expect node2 to come from substate and be
382 # We currently expect node2 to come from substate and be
383 # in hex format
383 # in hex format
384 if node2 is not None:
384 if node2 is not None:
385 node2 = node.bin(node2)
385 node2 = node.bin(node2)
386 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
386 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
387 node1, node2, match,
387 node1, node2, match,
388 prefix=os.path.join(prefix, self._path),
388 prefix=os.path.join(prefix, self._path),
389 listsubrepos=True, **opts)
389 listsubrepos=True, **opts)
390 except error.RepoLookupError, inst:
390 except error.RepoLookupError, inst:
391 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
391 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
392 % (inst, subrelpath(self)))
392 % (inst, subrelpath(self)))
393
393
394 def archive(self, ui, archiver, prefix):
394 def archive(self, ui, archiver, prefix):
395 abstractsubrepo.archive(self, ui, archiver, prefix)
395 abstractsubrepo.archive(self, ui, archiver, prefix)
396
396
397 rev = self._state[1]
397 rev = self._state[1]
398 ctx = self._repo[rev]
398 ctx = self._repo[rev]
399 for subpath in ctx.substate:
399 for subpath in ctx.substate:
400 s = subrepo(ctx, subpath)
400 s = subrepo(ctx, subpath)
401 s.archive(ui, archiver, os.path.join(prefix, self._path))
401 s.archive(ui, archiver, os.path.join(prefix, self._path))
402
402
403 def dirty(self, ignoreupdate=False):
403 def dirty(self, ignoreupdate=False):
404 r = self._state[1]
404 r = self._state[1]
405 if r == '' and not ignoreupdate: # no state recorded
405 if r == '' and not ignoreupdate: # no state recorded
406 return True
406 return True
407 w = self._repo[None]
407 w = self._repo[None]
408 if w.p1() != self._repo[r] and not ignoreupdate:
408 if w.p1() != self._repo[r] and not ignoreupdate:
409 # different version checked out
409 # different version checked out
410 return True
410 return True
411 return w.dirty() # working directory changed
411 return w.dirty() # working directory changed
412
412
413 def checknested(self, path):
413 def checknested(self, path):
414 return self._repo._checknested(self._repo.wjoin(path))
414 return self._repo._checknested(self._repo.wjoin(path))
415
415
416 def commit(self, text, user, date):
416 def commit(self, text, user, date):
417 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
417 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
418 n = self._repo.commit(text, user, date)
418 n = self._repo.commit(text, user, date)
419 if not n:
419 if not n:
420 return self._repo['.'].hex() # different version checked out
420 return self._repo['.'].hex() # different version checked out
421 return node.hex(n)
421 return node.hex(n)
422
422
423 def remove(self):
423 def remove(self):
424 # we can't fully delete the repository as it may contain
424 # we can't fully delete the repository as it may contain
425 # local-only history
425 # local-only history
426 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
426 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
427 hg.clean(self._repo, node.nullid, False)
427 hg.clean(self._repo, node.nullid, False)
428
428
429 def _get(self, state):
429 def _get(self, state):
430 source, revision, kind = state
430 source, revision, kind = state
431 if revision not in self._repo:
431 if revision not in self._repo:
432 self._repo._subsource = source
432 self._repo._subsource = source
433 srcurl = _abssource(self._repo)
433 srcurl = _abssource(self._repo)
434 self._repo.ui.status(_('pulling subrepo %s from %s\n')
434 self._repo.ui.status(_('pulling subrepo %s from %s\n')
435 % (subrelpath(self), srcurl))
435 % (subrelpath(self), srcurl))
436 other = hg.repository(self._repo.ui, srcurl)
436 other = hg.repository(self._repo.ui, srcurl)
437 self._repo.pull(other)
437 self._repo.pull(other)
438 bookmarks.updatefromremote(self._repo.ui, self._repo, other)
438 bookmarks.updatefromremote(self._repo.ui, self._repo, other)
439
439
440 def get(self, state, overwrite=False):
440 def get(self, state, overwrite=False):
441 self._get(state)
441 self._get(state)
442 source, revision, kind = state
442 source, revision, kind = state
443 self._repo.ui.debug("getting subrepo %s\n" % self._path)
443 self._repo.ui.debug("getting subrepo %s\n" % self._path)
444 hg.clean(self._repo, revision, False)
444 hg.clean(self._repo, revision, False)
445
445
446 def merge(self, state):
446 def merge(self, state):
447 self._get(state)
447 self._get(state)
448 cur = self._repo['.']
448 cur = self._repo['.']
449 dst = self._repo[state[1]]
449 dst = self._repo[state[1]]
450 anc = dst.ancestor(cur)
450 anc = dst.ancestor(cur)
451
451
452 def mergefunc():
452 def mergefunc():
453 if anc == cur:
453 if anc == cur:
454 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
454 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
455 hg.update(self._repo, state[1])
455 hg.update(self._repo, state[1])
456 elif anc == dst:
456 elif anc == dst:
457 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
457 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
458 else:
458 else:
459 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
459 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
460 hg.merge(self._repo, state[1], remind=False)
460 hg.merge(self._repo, state[1], remind=False)
461
461
462 wctx = self._repo[None]
462 wctx = self._repo[None]
463 if self.dirty():
463 if self.dirty():
464 if anc != dst:
464 if anc != dst:
465 if _updateprompt(self._repo.ui, self, wctx.dirty(), cur, dst):
465 if _updateprompt(self._repo.ui, self, wctx.dirty(), cur, dst):
466 mergefunc()
466 mergefunc()
467 else:
467 else:
468 mergefunc()
468 mergefunc()
469 else:
469 else:
470 mergefunc()
470 mergefunc()
471
471
472 def push(self, force):
472 def push(self, force):
473 # push subrepos depth-first for coherent ordering
473 # push subrepos depth-first for coherent ordering
474 c = self._repo['']
474 c = self._repo['']
475 subs = c.substate # only repos that are committed
475 subs = c.substate # only repos that are committed
476 for s in sorted(subs):
476 for s in sorted(subs):
477 if not c.sub(s).push(force):
477 if not c.sub(s).push(force):
478 return False
478 return False
479
479
480 dsturl = _abssource(self._repo, True)
480 dsturl = _abssource(self._repo, True)
481 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
481 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
482 (subrelpath(self), dsturl))
482 (subrelpath(self), dsturl))
483 other = hg.repository(self._repo.ui, dsturl)
483 other = hg.repository(self._repo.ui, dsturl)
484 return self._repo.push(other, force)
484 return self._repo.push(other, force)
485
485
486 def outgoing(self, ui, dest, opts):
486 def outgoing(self, ui, dest, opts):
487 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
487 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
488
488
489 def incoming(self, ui, source, opts):
489 def incoming(self, ui, source, opts):
490 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
490 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
491
491
492 def files(self):
492 def files(self):
493 rev = self._state[1]
493 rev = self._state[1]
494 ctx = self._repo[rev]
494 ctx = self._repo[rev]
495 return ctx.manifest()
495 return ctx.manifest()
496
496
497 def filedata(self, name):
497 def filedata(self, name):
498 rev = self._state[1]
498 rev = self._state[1]
499 return self._repo[rev][name].data()
499 return self._repo[rev][name].data()
500
500
501 def fileflags(self, name):
501 def fileflags(self, name):
502 rev = self._state[1]
502 rev = self._state[1]
503 ctx = self._repo[rev]
503 ctx = self._repo[rev]
504 return ctx.flags(name)
504 return ctx.flags(name)
505
505
506
506
507 class svnsubrepo(abstractsubrepo):
507 class svnsubrepo(abstractsubrepo):
508 def __init__(self, ctx, path, state):
508 def __init__(self, ctx, path, state):
509 self._path = path
509 self._path = path
510 self._state = state
510 self._state = state
511 self._ctx = ctx
511 self._ctx = ctx
512 self._ui = ctx._repo.ui
512 self._ui = ctx._repo.ui
513
513
514 def _svncommand(self, commands, filename=''):
514 def _svncommand(self, commands, filename=''):
515 cmd = ['svn']
515 cmd = ['svn']
516 # Starting in svn 1.5 --non-interactive is a global flag
516 # Starting in svn 1.5 --non-interactive is a global flag
517 # instead of being per-command, but we need to support 1.4 so
517 # instead of being per-command, but we need to support 1.4 so
518 # we have to be intelligent about what commands take
518 # we have to be intelligent about what commands take
519 # --non-interactive.
519 # --non-interactive.
520 if (not self._ui.interactive() and
520 if (not self._ui.interactive() and
521 commands[0] in ('update', 'checkout', 'commit')):
521 commands[0] in ('update', 'checkout', 'commit')):
522 cmd.append('--non-interactive')
522 cmd.append('--non-interactive')
523 cmd.extend(commands)
523 cmd.extend(commands)
524 if filename is not None:
524 if filename is not None:
525 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
525 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
526 cmd.append(path)
526 cmd.append(path)
527 env = dict(os.environ)
527 env = dict(os.environ)
528 # Avoid localized output, preserve current locale for everything else.
528 # Avoid localized output, preserve current locale for everything else.
529 env['LC_MESSAGES'] = 'C'
529 env['LC_MESSAGES'] = 'C'
530 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
530 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
531 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
531 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
532 universal_newlines=True, env=env)
532 universal_newlines=True, env=env)
533 stdout, stderr = p.communicate()
533 stdout, stderr = p.communicate()
534 stderr = stderr.strip()
534 stderr = stderr.strip()
535 if stderr:
535 if stderr:
536 raise util.Abort(stderr)
536 raise util.Abort(stderr)
537 return stdout
537 return stdout
538
538
539 @propertycache
539 @propertycache
540 def _svnversion(self):
540 def _svnversion(self):
541 output = self._svncommand(['--version'], filename=None)
541 output = self._svncommand(['--version'], filename=None)
542 m = re.search(r'^svn,\s+version\s+(\d+)\.(\d+)', output)
542 m = re.search(r'^svn,\s+version\s+(\d+)\.(\d+)', output)
543 if not m:
543 if not m:
544 raise util.Abort(_('cannot retrieve svn tool version'))
544 raise util.Abort(_('cannot retrieve svn tool version'))
545 return (int(m.group(1)), int(m.group(2)))
545 return (int(m.group(1)), int(m.group(2)))
546
546
547 def _wcrevs(self):
547 def _wcrevs(self):
548 # Get the working directory revision as well as the last
548 # Get the working directory revision as well as the last
549 # commit revision so we can compare the subrepo state with
549 # commit revision so we can compare the subrepo state with
550 # both. We used to store the working directory one.
550 # both. We used to store the working directory one.
551 output = self._svncommand(['info', '--xml'])
551 output = self._svncommand(['info', '--xml'])
552 doc = xml.dom.minidom.parseString(output)
552 doc = xml.dom.minidom.parseString(output)
553 entries = doc.getElementsByTagName('entry')
553 entries = doc.getElementsByTagName('entry')
554 lastrev, rev = '0', '0'
554 lastrev, rev = '0', '0'
555 if entries:
555 if entries:
556 rev = str(entries[0].getAttribute('revision')) or '0'
556 rev = str(entries[0].getAttribute('revision')) or '0'
557 commits = entries[0].getElementsByTagName('commit')
557 commits = entries[0].getElementsByTagName('commit')
558 if commits:
558 if commits:
559 lastrev = str(commits[0].getAttribute('revision')) or '0'
559 lastrev = str(commits[0].getAttribute('revision')) or '0'
560 return (lastrev, rev)
560 return (lastrev, rev)
561
561
562 def _wcrev(self):
562 def _wcrev(self):
563 return self._wcrevs()[0]
563 return self._wcrevs()[0]
564
564
565 def _wcchanged(self):
565 def _wcchanged(self):
566 """Return (changes, extchanges) where changes is True
566 """Return (changes, extchanges) where changes is True
567 if the working directory was changed, and extchanges is
567 if the working directory was changed, and extchanges is
568 True if any of these changes concern an external entry.
568 True if any of these changes concern an external entry.
569 """
569 """
570 output = self._svncommand(['status', '--xml'])
570 output = self._svncommand(['status', '--xml'])
571 externals, changes = [], []
571 externals, changes = [], []
572 doc = xml.dom.minidom.parseString(output)
572 doc = xml.dom.minidom.parseString(output)
573 for e in doc.getElementsByTagName('entry'):
573 for e in doc.getElementsByTagName('entry'):
574 s = e.getElementsByTagName('wc-status')
574 s = e.getElementsByTagName('wc-status')
575 if not s:
575 if not s:
576 continue
576 continue
577 item = s[0].getAttribute('item')
577 item = s[0].getAttribute('item')
578 props = s[0].getAttribute('props')
578 props = s[0].getAttribute('props')
579 path = e.getAttribute('path')
579 path = e.getAttribute('path')
580 if item == 'external':
580 if item == 'external':
581 externals.append(path)
581 externals.append(path)
582 if (item not in ('', 'normal', 'unversioned', 'external')
582 if (item not in ('', 'normal', 'unversioned', 'external')
583 or props not in ('', 'none')):
583 or props not in ('', 'none')):
584 changes.append(path)
584 changes.append(path)
585 for path in changes:
585 for path in changes:
586 for ext in externals:
586 for ext in externals:
587 if path == ext or path.startswith(ext + os.sep):
587 if path == ext or path.startswith(ext + os.sep):
588 return True, True
588 return True, True
589 return bool(changes), False
589 return bool(changes), False
590
590
591 def dirty(self, ignoreupdate=False):
591 def dirty(self, ignoreupdate=False):
592 if not self._wcchanged()[0]:
592 if not self._wcchanged()[0]:
593 if self._state[1] in self._wcrevs() or ignoreupdate:
593 if self._state[1] in self._wcrevs() or ignoreupdate:
594 return False
594 return False
595 return True
595 return True
596
596
597 def commit(self, text, user, date):
597 def commit(self, text, user, date):
598 # user and date are out of our hands since svn is centralized
598 # user and date are out of our hands since svn is centralized
599 changed, extchanged = self._wcchanged()
599 changed, extchanged = self._wcchanged()
600 if not changed:
600 if not changed:
601 return self._wcrev()
601 return self._wcrev()
602 if extchanged:
602 if extchanged:
603 # Do not try to commit externals
603 # Do not try to commit externals
604 raise util.Abort(_('cannot commit svn externals'))
604 raise util.Abort(_('cannot commit svn externals'))
605 commitinfo = self._svncommand(['commit', '-m', text])
605 commitinfo = self._svncommand(['commit', '-m', text])
606 self._ui.status(commitinfo)
606 self._ui.status(commitinfo)
607 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
607 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
608 if not newrev:
608 if not newrev:
609 raise util.Abort(commitinfo.splitlines()[-1])
609 raise util.Abort(commitinfo.splitlines()[-1])
610 newrev = newrev.groups()[0]
610 newrev = newrev.groups()[0]
611 self._ui.status(self._svncommand(['update', '-r', newrev]))
611 self._ui.status(self._svncommand(['update', '-r', newrev]))
612 return newrev
612 return newrev
613
613
614 def remove(self):
614 def remove(self):
615 if self.dirty():
615 if self.dirty():
616 self._ui.warn(_('not removing repo %s because '
616 self._ui.warn(_('not removing repo %s because '
617 'it has changes.\n' % self._path))
617 'it has changes.\n' % self._path))
618 return
618 return
619 self._ui.note(_('removing subrepo %s\n') % self._path)
619 self._ui.note(_('removing subrepo %s\n') % self._path)
620
620
621 def onerror(function, path, excinfo):
621 def onerror(function, path, excinfo):
622 if function is not os.remove:
622 if function is not os.remove:
623 raise
623 raise
624 # read-only files cannot be unlinked under Windows
624 # read-only files cannot be unlinked under Windows
625 s = os.stat(path)
625 s = os.stat(path)
626 if (s.st_mode & stat.S_IWRITE) != 0:
626 if (s.st_mode & stat.S_IWRITE) != 0:
627 raise
627 raise
628 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
628 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
629 os.remove(path)
629 os.remove(path)
630
630
631 path = self._ctx._repo.wjoin(self._path)
631 path = self._ctx._repo.wjoin(self._path)
632 shutil.rmtree(path, onerror=onerror)
632 shutil.rmtree(path, onerror=onerror)
633 try:
633 try:
634 os.removedirs(os.path.dirname(path))
634 os.removedirs(os.path.dirname(path))
635 except OSError:
635 except OSError:
636 pass
636 pass
637
637
638 def get(self, state, overwrite=False):
638 def get(self, state, overwrite=False):
639 if overwrite:
639 if overwrite:
640 self._svncommand(['revert', '--recursive'])
640 self._svncommand(['revert', '--recursive'])
641 args = ['checkout']
641 args = ['checkout']
642 if self._svnversion >= (1, 5):
642 if self._svnversion >= (1, 5):
643 args.append('--force')
643 args.append('--force')
644 args.extend([state[0], '--revision', state[1]])
644 args.extend([state[0], '--revision', state[1]])
645 status = self._svncommand(args)
645 status = self._svncommand(args)
646 if not re.search('Checked out revision [0-9]+.', status):
646 if not re.search('Checked out revision [0-9]+.', status):
647 raise util.Abort(status.splitlines()[-1])
647 raise util.Abort(status.splitlines()[-1])
648 self._ui.status(status)
648 self._ui.status(status)
649
649
650 def merge(self, state):
650 def merge(self, state):
651 old = self._state[1]
651 old = self._state[1]
652 new = state[1]
652 new = state[1]
653 if new != self._wcrev():
653 if new != self._wcrev():
654 dirty = old == self._wcrev() or self._wcchanged()[0]
654 dirty = old == self._wcrev() or self._wcchanged()[0]
655 if _updateprompt(self._ui, self, dirty, self._wcrev(), new):
655 if _updateprompt(self._ui, self, dirty, self._wcrev(), new):
656 self.get(state, False)
656 self.get(state, False)
657
657
658 def push(self, force):
658 def push(self, force):
659 # push is a no-op for SVN
659 # push is a no-op for SVN
660 return True
660 return True
661
661
662 def files(self):
662 def files(self):
663 output = self._svncommand(['list'])
663 output = self._svncommand(['list'])
664 # This works because svn forbids \n in filenames.
664 # This works because svn forbids \n in filenames.
665 return output.splitlines()
665 return output.splitlines()
666
666
667 def filedata(self, name):
667 def filedata(self, name):
668 return self._svncommand(['cat'], name)
668 return self._svncommand(['cat'], name)
669
669
670
670
671 class gitsubrepo(abstractsubrepo):
671 class gitsubrepo(abstractsubrepo):
672 def __init__(self, ctx, path, state):
672 def __init__(self, ctx, path, state):
673 # TODO add git version check.
673 # TODO add git version check.
674 self._state = state
674 self._state = state
675 self._ctx = ctx
675 self._ctx = ctx
676 self._path = path
676 self._path = path
677 self._relpath = os.path.join(reporelpath(ctx._repo), path)
677 self._relpath = os.path.join(reporelpath(ctx._repo), path)
678 self._abspath = ctx._repo.wjoin(path)
678 self._abspath = ctx._repo.wjoin(path)
679 self._subparent = ctx._repo
679 self._subparent = ctx._repo
680 self._ui = ctx._repo.ui
680 self._ui = ctx._repo.ui
681
681
682 def _gitcommand(self, commands, env=None, stream=False):
682 def _gitcommand(self, commands, env=None, stream=False):
683 return self._gitdir(commands, env=env, stream=stream)[0]
683 return self._gitdir(commands, env=env, stream=stream)[0]
684
684
685 def _gitdir(self, commands, env=None, stream=False):
685 def _gitdir(self, commands, env=None, stream=False):
686 return self._gitnodir(commands, env=env, stream=stream,
686 return self._gitnodir(commands, env=env, stream=stream,
687 cwd=self._abspath)
687 cwd=self._abspath)
688
688
689 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
689 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
690 """Calls the git command
690 """Calls the git command
691
691
692 The methods tries to call the git command. versions previor to 1.6.0
692 The methods tries to call the git command. versions previor to 1.6.0
693 are not supported and very probably fail.
693 are not supported and very probably fail.
694 """
694 """
695 self._ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
695 self._ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
696 # unless ui.quiet is set, print git's stderr,
696 # unless ui.quiet is set, print git's stderr,
697 # which is mostly progress and useful info
697 # which is mostly progress and useful info
698 errpipe = None
698 errpipe = None
699 if self._ui.quiet:
699 if self._ui.quiet:
700 errpipe = open(os.devnull, 'w')
700 errpipe = open(os.devnull, 'w')
701 p = subprocess.Popen(['git'] + commands, bufsize=-1, cwd=cwd, env=env,
701 p = subprocess.Popen(['git'] + commands, bufsize=-1, cwd=cwd, env=env,
702 close_fds=util.closefds,
702 close_fds=util.closefds,
703 stdout=subprocess.PIPE, stderr=errpipe)
703 stdout=subprocess.PIPE, stderr=errpipe)
704 if stream:
704 if stream:
705 return p.stdout, None
705 return p.stdout, None
706
706
707 retdata = p.stdout.read().strip()
707 retdata = p.stdout.read().strip()
708 # wait for the child to exit to avoid race condition.
708 # wait for the child to exit to avoid race condition.
709 p.wait()
709 p.wait()
710
710
711 if p.returncode != 0 and p.returncode != 1:
711 if p.returncode != 0 and p.returncode != 1:
712 # there are certain error codes that are ok
712 # there are certain error codes that are ok
713 command = commands[0]
713 command = commands[0]
714 if command in ('cat-file', 'symbolic-ref'):
714 if command in ('cat-file', 'symbolic-ref'):
715 return retdata, p.returncode
715 return retdata, p.returncode
716 # for all others, abort
716 # for all others, abort
717 raise util.Abort('git %s error %d in %s' %
717 raise util.Abort('git %s error %d in %s' %
718 (command, p.returncode, self._relpath))
718 (command, p.returncode, self._relpath))
719
719
720 return retdata, p.returncode
720 return retdata, p.returncode
721
721
722 def _gitmissing(self):
722 def _gitmissing(self):
723 return not os.path.exists(os.path.join(self._abspath, '.git'))
723 return not os.path.exists(os.path.join(self._abspath, '.git'))
724
724
725 def _gitstate(self):
725 def _gitstate(self):
726 return self._gitcommand(['rev-parse', 'HEAD'])
726 return self._gitcommand(['rev-parse', 'HEAD'])
727
727
728 def _gitcurrentbranch(self):
728 def _gitcurrentbranch(self):
729 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
729 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
730 if err:
730 if err:
731 current = None
731 current = None
732 return current
732 return current
733
733
734 def _gitremote(self, remote):
734 def _gitremote(self, remote):
735 out = self._gitcommand(['remote', 'show', '-n', remote])
735 out = self._gitcommand(['remote', 'show', '-n', remote])
736 line = out.split('\n')[1]
736 line = out.split('\n')[1]
737 i = line.index('URL: ') + len('URL: ')
737 i = line.index('URL: ') + len('URL: ')
738 return line[i:]
738 return line[i:]
739
739
740 def _githavelocally(self, revision):
740 def _githavelocally(self, revision):
741 out, code = self._gitdir(['cat-file', '-e', revision])
741 out, code = self._gitdir(['cat-file', '-e', revision])
742 return code == 0
742 return code == 0
743
743
744 def _gitisancestor(self, r1, r2):
744 def _gitisancestor(self, r1, r2):
745 base = self._gitcommand(['merge-base', r1, r2])
745 base = self._gitcommand(['merge-base', r1, r2])
746 return base == r1
746 return base == r1
747
747
748 def _gitbranchmap(self):
748 def _gitbranchmap(self):
749 '''returns 2 things:
749 '''returns 2 things:
750 a map from git branch to revision
750 a map from git branch to revision
751 a map from revision to branches'''
751 a map from revision to branches'''
752 branch2rev = {}
752 branch2rev = {}
753 rev2branch = {}
753 rev2branch = {}
754
754
755 out = self._gitcommand(['for-each-ref', '--format',
755 out = self._gitcommand(['for-each-ref', '--format',
756 '%(objectname) %(refname)'])
756 '%(objectname) %(refname)'])
757 for line in out.split('\n'):
757 for line in out.split('\n'):
758 revision, ref = line.split(' ')
758 revision, ref = line.split(' ')
759 if (not ref.startswith('refs/heads/') and
759 if (not ref.startswith('refs/heads/') and
760 not ref.startswith('refs/remotes/')):
760 not ref.startswith('refs/remotes/')):
761 continue
761 continue
762 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
762 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
763 continue # ignore remote/HEAD redirects
763 continue # ignore remote/HEAD redirects
764 branch2rev[ref] = revision
764 branch2rev[ref] = revision
765 rev2branch.setdefault(revision, []).append(ref)
765 rev2branch.setdefault(revision, []).append(ref)
766 return branch2rev, rev2branch
766 return branch2rev, rev2branch
767
767
768 def _gittracking(self, branches):
768 def _gittracking(self, branches):
769 'return map of remote branch to local tracking branch'
769 'return map of remote branch to local tracking branch'
770 # assumes no more than one local tracking branch for each remote
770 # assumes no more than one local tracking branch for each remote
771 tracking = {}
771 tracking = {}
772 for b in branches:
772 for b in branches:
773 if b.startswith('refs/remotes/'):
773 if b.startswith('refs/remotes/'):
774 continue
774 continue
775 remote = self._gitcommand(['config', 'branch.%s.remote' % b])
775 remote = self._gitcommand(['config', 'branch.%s.remote' % b])
776 if remote:
776 if remote:
777 ref = self._gitcommand(['config', 'branch.%s.merge' % b])
777 ref = self._gitcommand(['config', 'branch.%s.merge' % b])
778 tracking['refs/remotes/%s/%s' %
778 tracking['refs/remotes/%s/%s' %
779 (remote, ref.split('/', 2)[2])] = b
779 (remote, ref.split('/', 2)[2])] = b
780 return tracking
780 return tracking
781
781
782 def _abssource(self, source):
782 def _abssource(self, source):
783 if '://' not in source:
783 if '://' not in source:
784 # recognize the scp syntax as an absolute source
784 # recognize the scp syntax as an absolute source
785 colon = source.find(':')
785 colon = source.find(':')
786 if colon != -1 and '/' not in source[:colon]:
786 if colon != -1 and '/' not in source[:colon]:
787 return source
787 return source
788 self._subsource = source
788 self._subsource = source
789 return _abssource(self)
789 return _abssource(self)
790
790
791 def _fetch(self, source, revision):
791 def _fetch(self, source, revision):
792 if self._gitmissing():
792 if self._gitmissing():
793 source = self._abssource(source)
793 source = self._abssource(source)
794 self._ui.status(_('cloning subrepo %s from %s\n') %
794 self._ui.status(_('cloning subrepo %s from %s\n') %
795 (self._relpath, source))
795 (self._relpath, source))
796 self._gitnodir(['clone', source, self._abspath])
796 self._gitnodir(['clone', source, self._abspath])
797 if self._githavelocally(revision):
797 if self._githavelocally(revision):
798 return
798 return
799 self._ui.status(_('pulling subrepo %s from %s\n') %
799 self._ui.status(_('pulling subrepo %s from %s\n') %
800 (self._relpath, self._gitremote('origin')))
800 (self._relpath, self._gitremote('origin')))
801 # try only origin: the originally cloned repo
801 # try only origin: the originally cloned repo
802 self._gitcommand(['fetch'])
802 self._gitcommand(['fetch'])
803 if not self._githavelocally(revision):
803 if not self._githavelocally(revision):
804 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
804 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
805 (revision, self._relpath))
805 (revision, self._relpath))
806
806
807 def dirty(self, ignoreupdate=False):
807 def dirty(self, ignoreupdate=False):
808 if self._gitmissing():
808 if self._gitmissing():
809 return True
809 return True
810 if not ignoreupdate and self._state[1] != self._gitstate():
810 if not ignoreupdate and self._state[1] != self._gitstate():
811 # different version checked out
811 # different version checked out
812 return True
812 return True
813 # check for staged changes or modified files; ignore untracked files
813 # check for staged changes or modified files; ignore untracked files
814 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
814 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
815 return code == 1
815 return code == 1
816
816
817 def get(self, state, overwrite=False):
817 def get(self, state, overwrite=False):
818 source, revision, kind = state
818 source, revision, kind = state
819 self._fetch(source, revision)
819 self._fetch(source, revision)
820 # if the repo was set to be bare, unbare it
820 # if the repo was set to be bare, unbare it
821 if self._gitcommand(['config', '--bool', 'core.bare']) == 'true':
821 if self._gitcommand(['config', '--bool', 'core.bare']) == 'true':
822 self._gitcommand(['config', 'core.bare', 'false'])
822 self._gitcommand(['config', 'core.bare', 'false'])
823 if self._gitstate() == revision:
823 if self._gitstate() == revision:
824 self._gitcommand(['reset', '--hard', 'HEAD'])
824 self._gitcommand(['reset', '--hard', 'HEAD'])
825 return
825 return
826 elif self._gitstate() == revision:
826 elif self._gitstate() == revision:
827 if overwrite:
827 if overwrite:
828 # first reset the index to unmark new files for commit, because
828 # first reset the index to unmark new files for commit, because
829 # reset --hard will otherwise throw away files added for commit,
829 # reset --hard will otherwise throw away files added for commit,
830 # not just unmark them.
830 # not just unmark them.
831 self._gitcommand(['reset', 'HEAD'])
831 self._gitcommand(['reset', 'HEAD'])
832 self._gitcommand(['reset', '--hard', 'HEAD'])
832 self._gitcommand(['reset', '--hard', 'HEAD'])
833 return
833 return
834 branch2rev, rev2branch = self._gitbranchmap()
834 branch2rev, rev2branch = self._gitbranchmap()
835
835
836 def checkout(args):
836 def checkout(args):
837 cmd = ['checkout']
837 cmd = ['checkout']
838 if overwrite:
838 if overwrite:
839 # first reset the index to unmark new files for commit, because
839 # first reset the index to unmark new files for commit, because
840 # the -f option will otherwise throw away files added for
840 # the -f option will otherwise throw away files added for
841 # commit, not just unmark them.
841 # commit, not just unmark them.
842 self._gitcommand(['reset', 'HEAD'])
842 self._gitcommand(['reset', 'HEAD'])
843 cmd.append('-f')
843 cmd.append('-f')
844 self._gitcommand(cmd + args)
844 self._gitcommand(cmd + args)
845
845
846 def rawcheckout():
846 def rawcheckout():
847 # no branch to checkout, check it out with no branch
847 # no branch to checkout, check it out with no branch
848 self._ui.warn(_('checking out detached HEAD in subrepo %s\n') %
848 self._ui.warn(_('checking out detached HEAD in subrepo %s\n') %
849 self._relpath)
849 self._relpath)
850 self._ui.warn(_('check out a git branch if you intend '
850 self._ui.warn(_('check out a git branch if you intend '
851 'to make changes\n'))
851 'to make changes\n'))
852 checkout(['-q', revision])
852 checkout(['-q', revision])
853
853
854 if revision not in rev2branch:
854 if revision not in rev2branch:
855 rawcheckout()
855 rawcheckout()
856 return
856 return
857 branches = rev2branch[revision]
857 branches = rev2branch[revision]
858 firstlocalbranch = None
858 firstlocalbranch = None
859 for b in branches:
859 for b in branches:
860 if b == 'refs/heads/master':
860 if b == 'refs/heads/master':
861 # master trumps all other branches
861 # master trumps all other branches
862 checkout(['refs/heads/master'])
862 checkout(['refs/heads/master'])
863 return
863 return
864 if not firstlocalbranch and not b.startswith('refs/remotes/'):
864 if not firstlocalbranch and not b.startswith('refs/remotes/'):
865 firstlocalbranch = b
865 firstlocalbranch = b
866 if firstlocalbranch:
866 if firstlocalbranch:
867 checkout([firstlocalbranch])
867 checkout([firstlocalbranch])
868 return
868 return
869
869
870 tracking = self._gittracking(branch2rev.keys())
870 tracking = self._gittracking(branch2rev.keys())
871 # choose a remote branch already tracked if possible
871 # choose a remote branch already tracked if possible
872 remote = branches[0]
872 remote = branches[0]
873 if remote not in tracking:
873 if remote not in tracking:
874 for b in branches:
874 for b in branches:
875 if b in tracking:
875 if b in tracking:
876 remote = b
876 remote = b
877 break
877 break
878
878
879 if remote not in tracking:
879 if remote not in tracking:
880 # create a new local tracking branch
880 # create a new local tracking branch
881 local = remote.split('/', 2)[2]
881 local = remote.split('/', 2)[2]
882 checkout(['-b', local, remote])
882 checkout(['-b', local, remote])
883 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
883 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
884 # When updating to a tracked remote branch,
884 # When updating to a tracked remote branch,
885 # if the local tracking branch is downstream of it,
885 # if the local tracking branch is downstream of it,
886 # a normal `git pull` would have performed a "fast-forward merge"
886 # a normal `git pull` would have performed a "fast-forward merge"
887 # which is equivalent to updating the local branch to the remote.
887 # which is equivalent to updating the local branch to the remote.
888 # Since we are only looking at branching at update, we need to
888 # Since we are only looking at branching at update, we need to
889 # detect this situation and perform this action lazily.
889 # detect this situation and perform this action lazily.
890 if tracking[remote] != self._gitcurrentbranch():
890 if tracking[remote] != self._gitcurrentbranch():
891 checkout([tracking[remote]])
891 checkout([tracking[remote]])
892 self._gitcommand(['merge', '--ff', remote])
892 self._gitcommand(['merge', '--ff', remote])
893 else:
893 else:
894 # a real merge would be required, just checkout the revision
894 # a real merge would be required, just checkout the revision
895 rawcheckout()
895 rawcheckout()
896
896
897 def commit(self, text, user, date):
897 def commit(self, text, user, date):
898 if self._gitmissing():
898 if self._gitmissing():
899 raise util.Abort(_("subrepo %s is missing") % self._relpath)
899 raise util.Abort(_("subrepo %s is missing") % self._relpath)
900 cmd = ['commit', '-a', '-m', text]
900 cmd = ['commit', '-a', '-m', text]
901 env = os.environ.copy()
901 env = os.environ.copy()
902 if user:
902 if user:
903 cmd += ['--author', user]
903 cmd += ['--author', user]
904 if date:
904 if date:
905 # git's date parser silently ignores when seconds < 1e9
905 # git's date parser silently ignores when seconds < 1e9
906 # convert to ISO8601
906 # convert to ISO8601
907 env['GIT_AUTHOR_DATE'] = util.datestr(date,
907 env['GIT_AUTHOR_DATE'] = util.datestr(date,
908 '%Y-%m-%dT%H:%M:%S %1%2')
908 '%Y-%m-%dT%H:%M:%S %1%2')
909 self._gitcommand(cmd, env=env)
909 self._gitcommand(cmd, env=env)
910 # make sure commit works otherwise HEAD might not exist under certain
910 # make sure commit works otherwise HEAD might not exist under certain
911 # circumstances
911 # circumstances
912 return self._gitstate()
912 return self._gitstate()
913
913
914 def merge(self, state):
914 def merge(self, state):
915 source, revision, kind = state
915 source, revision, kind = state
916 self._fetch(source, revision)
916 self._fetch(source, revision)
917 base = self._gitcommand(['merge-base', revision, self._state[1]])
917 base = self._gitcommand(['merge-base', revision, self._state[1]])
918 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
918 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
919
919
920 def mergefunc():
920 def mergefunc():
921 if base == revision:
921 if base == revision:
922 self.get(state) # fast forward merge
922 self.get(state) # fast forward merge
923 elif base != self._state[1]:
923 elif base != self._state[1]:
924 self._gitcommand(['merge', '--no-commit', revision])
924 self._gitcommand(['merge', '--no-commit', revision])
925
925
926 if self.dirty():
926 if self.dirty():
927 if self._gitstate() != revision:
927 if self._gitstate() != revision:
928 dirty = self._gitstate() == self._state[1] or code != 0
928 dirty = self._gitstate() == self._state[1] or code != 0
929 if _updateprompt(self._ui, self, dirty,
929 if _updateprompt(self._ui, self, dirty,
930 self._state[1][:7], revision[:7]):
930 self._state[1][:7], revision[:7]):
931 mergefunc()
931 mergefunc()
932 else:
932 else:
933 mergefunc()
933 mergefunc()
934
934
935 def push(self, force):
935 def push(self, force):
936 if self._gitmissing():
936 if self._gitmissing():
937 raise util.Abort(_("subrepo %s is missing") % self._relpath)
937 raise util.Abort(_("subrepo %s is missing") % self._relpath)
938 # if a branch in origin contains the revision, nothing to do
938 # if a branch in origin contains the revision, nothing to do
939 branch2rev, rev2branch = self._gitbranchmap()
939 branch2rev, rev2branch = self._gitbranchmap()
940 if self._state[1] in rev2branch:
940 if self._state[1] in rev2branch:
941 for b in rev2branch[self._state[1]]:
941 for b in rev2branch[self._state[1]]:
942 if b.startswith('refs/remotes/origin/'):
942 if b.startswith('refs/remotes/origin/'):
943 return True
943 return True
944 for b, revision in branch2rev.iteritems():
944 for b, revision in branch2rev.iteritems():
945 if b.startswith('refs/remotes/origin/'):
945 if b.startswith('refs/remotes/origin/'):
946 if self._gitisancestor(self._state[1], revision):
946 if self._gitisancestor(self._state[1], revision):
947 return True
947 return True
948 # otherwise, try to push the currently checked out branch
948 # otherwise, try to push the currently checked out branch
949 cmd = ['push']
949 cmd = ['push']
950 if force:
950 if force:
951 cmd.append('--force')
951 cmd.append('--force')
952
952
953 current = self._gitcurrentbranch()
953 current = self._gitcurrentbranch()
954 if current:
954 if current:
955 # determine if the current branch is even useful
955 # determine if the current branch is even useful
956 if not self._gitisancestor(self._state[1], current):
956 if not self._gitisancestor(self._state[1], current):
957 self._ui.warn(_('unrelated git branch checked out '
957 self._ui.warn(_('unrelated git branch checked out '
958 'in subrepo %s\n') % self._relpath)
958 'in subrepo %s\n') % self._relpath)
959 return False
959 return False
960 self._ui.status(_('pushing branch %s of subrepo %s\n') %
960 self._ui.status(_('pushing branch %s of subrepo %s\n') %
961 (current.split('/', 2)[2], self._relpath))
961 (current.split('/', 2)[2], self._relpath))
962 self._gitcommand(cmd + ['origin', current])
962 self._gitcommand(cmd + ['origin', current])
963 return True
963 return True
964 else:
964 else:
965 self._ui.warn(_('no branch checked out in subrepo %s\n'
965 self._ui.warn(_('no branch checked out in subrepo %s\n'
966 'cannot push revision %s') %
966 'cannot push revision %s') %
967 (self._relpath, self._state[1]))
967 (self._relpath, self._state[1]))
968 return False
968 return False
969
969
970 def remove(self):
970 def remove(self):
971 if self._gitmissing():
971 if self._gitmissing():
972 return
972 return
973 if self.dirty():
973 if self.dirty():
974 self._ui.warn(_('not removing repo %s because '
974 self._ui.warn(_('not removing repo %s because '
975 'it has changes.\n') % self._relpath)
975 'it has changes.\n') % self._relpath)
976 return
976 return
977 # we can't fully delete the repository as it may contain
977 # we can't fully delete the repository as it may contain
978 # local-only history
978 # local-only history
979 self._ui.note(_('removing subrepo %s\n') % self._relpath)
979 self._ui.note(_('removing subrepo %s\n') % self._relpath)
980 self._gitcommand(['config', 'core.bare', 'true'])
980 self._gitcommand(['config', 'core.bare', 'true'])
981 for f in os.listdir(self._abspath):
981 for f in os.listdir(self._abspath):
982 if f == '.git':
982 if f == '.git':
983 continue
983 continue
984 path = os.path.join(self._abspath, f)
984 path = os.path.join(self._abspath, f)
985 if os.path.isdir(path) and not os.path.islink(path):
985 if os.path.isdir(path) and not os.path.islink(path):
986 shutil.rmtree(path)
986 shutil.rmtree(path)
987 else:
987 else:
988 os.remove(path)
988 os.remove(path)
989
989
990 def archive(self, ui, archiver, prefix):
990 def archive(self, ui, archiver, prefix):
991 source, revision = self._state
991 source, revision = self._state
992 self._fetch(source, revision)
992 self._fetch(source, revision)
993
993
994 # Parse git's native archive command.
994 # Parse git's native archive command.
995 # This should be much faster than manually traversing the trees
995 # This should be much faster than manually traversing the trees
996 # and objects with many subprocess calls.
996 # and objects with many subprocess calls.
997 tarstream = self._gitcommand(['archive', revision], stream=True)
997 tarstream = self._gitcommand(['archive', revision], stream=True)
998 tar = tarfile.open(fileobj=tarstream, mode='r|')
998 tar = tarfile.open(fileobj=tarstream, mode='r|')
999 relpath = subrelpath(self)
999 relpath = subrelpath(self)
1000 ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1000 ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1001 for i, info in enumerate(tar):
1001 for i, info in enumerate(tar):
1002 if info.isdir():
1002 if info.isdir():
1003 continue
1003 continue
1004 if info.issym():
1004 if info.issym():
1005 data = info.linkname
1005 data = info.linkname
1006 else:
1006 else:
1007 data = tar.extractfile(info).read()
1007 data = tar.extractfile(info).read()
1008 archiver.addfile(os.path.join(prefix, self._path, info.name),
1008 archiver.addfile(os.path.join(prefix, self._path, info.name),
1009 info.mode, info.issym(), data)
1009 info.mode, info.issym(), data)
1010 ui.progress(_('archiving (%s)') % relpath, i + 1,
1010 ui.progress(_('archiving (%s)') % relpath, i + 1,
1011 unit=_('files'))
1011 unit=_('files'))
1012 ui.progress(_('archiving (%s)') % relpath, None)
1012 ui.progress(_('archiving (%s)') % relpath, None)
1013
1013
1014
1014
1015 def status(self, rev2, **opts):
1015 def status(self, rev2, **opts):
1016 if self._gitmissing():
1016 if self._gitmissing():
1017 # if the repo is missing, return no results
1017 # if the repo is missing, return no results
1018 return [], [], [], [], [], [], []
1018 return [], [], [], [], [], [], []
1019 rev1 = self._state[1]
1019 rev1 = self._state[1]
1020 modified, added, removed = [], [], []
1020 modified, added, removed = [], [], []
1021 if rev2:
1021 if rev2:
1022 command = ['diff-tree', rev1, rev2]
1022 command = ['diff-tree', rev1, rev2]
1023 else:
1023 else:
1024 command = ['diff-index', rev1]
1024 command = ['diff-index', rev1]
1025 out = self._gitcommand(command)
1025 out = self._gitcommand(command)
1026 for line in out.split('\n'):
1026 for line in out.split('\n'):
1027 tab = line.find('\t')
1027 tab = line.find('\t')
1028 if tab == -1:
1028 if tab == -1:
1029 continue
1029 continue
1030 status, f = line[tab - 1], line[tab + 1:]
1030 status, f = line[tab - 1], line[tab + 1:]
1031 if status == 'M':
1031 if status == 'M':
1032 modified.append(f)
1032 modified.append(f)
1033 elif status == 'A':
1033 elif status == 'A':
1034 added.append(f)
1034 added.append(f)
1035 elif status == 'D':
1035 elif status == 'D':
1036 removed.append(f)
1036 removed.append(f)
1037
1037
1038 deleted = unknown = ignored = clean = []
1038 deleted = unknown = ignored = clean = []
1039 return modified, added, removed, deleted, unknown, ignored, clean
1039 return modified, added, removed, deleted, unknown, ignored, clean
1040
1040
1041 types = {
1041 types = {
1042 'hg': hgsubrepo,
1042 'hg': hgsubrepo,
1043 'svn': svnsubrepo,
1043 'svn': svnsubrepo,
1044 'git': gitsubrepo,
1044 'git': gitsubrepo,
1045 }
1045 }
General Comments 0
You need to be logged in to leave comments. Login now