##// END OF EJS Templates
use __contains__, index or split instead of str.find...
Benoit Boissinot -
r2579:0875cda0 default
parent child Browse files
Show More
@@ -1,3612 +1,3612 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from demandload import demandload
8 from demandload import demandload
9 from node import *
9 from node import *
10 from i18n import gettext as _
10 from i18n import gettext as _
11 demandload(globals(), "os re sys signal shutil imp urllib pdb")
11 demandload(globals(), "os re sys signal shutil imp urllib pdb")
12 demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo")
12 demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo")
13 demandload(globals(), "fnmatch mdiff random signal tempfile time")
13 demandload(globals(), "fnmatch mdiff random signal tempfile time")
14 demandload(globals(), "traceback errno socket version struct atexit sets bz2")
14 demandload(globals(), "traceback errno socket version struct atexit sets bz2")
15 demandload(globals(), "archival cStringIO changegroup email.Parser")
15 demandload(globals(), "archival cStringIO changegroup email.Parser")
16 demandload(globals(), "hgweb.server sshserver")
16 demandload(globals(), "hgweb.server sshserver")
17
17
18 class UnknownCommand(Exception):
18 class UnknownCommand(Exception):
19 """Exception raised if command is not in the command table."""
19 """Exception raised if command is not in the command table."""
20 class AmbiguousCommand(Exception):
20 class AmbiguousCommand(Exception):
21 """Exception raised if command shortcut matches more than one command."""
21 """Exception raised if command shortcut matches more than one command."""
22
22
23 def bail_if_changed(repo):
23 def bail_if_changed(repo):
24 modified, added, removed, deleted, unknown = repo.changes()
24 modified, added, removed, deleted, unknown = repo.changes()
25 if modified or added or removed or deleted:
25 if modified or added or removed or deleted:
26 raise util.Abort(_("outstanding uncommitted changes"))
26 raise util.Abort(_("outstanding uncommitted changes"))
27
27
28 def filterfiles(filters, files):
28 def filterfiles(filters, files):
29 l = [x for x in files if x in filters]
29 l = [x for x in files if x in filters]
30
30
31 for t in filters:
31 for t in filters:
32 if t and t[-1] != "/":
32 if t and t[-1] != "/":
33 t += "/"
33 t += "/"
34 l += [x for x in files if x.startswith(t)]
34 l += [x for x in files if x.startswith(t)]
35 return l
35 return l
36
36
37 def relpath(repo, args):
37 def relpath(repo, args):
38 cwd = repo.getcwd()
38 cwd = repo.getcwd()
39 if cwd:
39 if cwd:
40 return [util.normpath(os.path.join(cwd, x)) for x in args]
40 return [util.normpath(os.path.join(cwd, x)) for x in args]
41 return args
41 return args
42
42
43 def matchpats(repo, pats=[], opts={}, head=''):
43 def matchpats(repo, pats=[], opts={}, head=''):
44 cwd = repo.getcwd()
44 cwd = repo.getcwd()
45 if not pats and cwd:
45 if not pats and cwd:
46 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
46 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
47 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
47 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
48 cwd = ''
48 cwd = ''
49 return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
49 return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
50 opts.get('exclude'), head)
50 opts.get('exclude'), head)
51
51
52 def makewalk(repo, pats, opts, node=None, head='', badmatch=None):
52 def makewalk(repo, pats, opts, node=None, head='', badmatch=None):
53 files, matchfn, anypats = matchpats(repo, pats, opts, head)
53 files, matchfn, anypats = matchpats(repo, pats, opts, head)
54 exact = dict(zip(files, files))
54 exact = dict(zip(files, files))
55 def walk():
55 def walk():
56 for src, fn in repo.walk(node=node, files=files, match=matchfn,
56 for src, fn in repo.walk(node=node, files=files, match=matchfn,
57 badmatch=badmatch):
57 badmatch=badmatch):
58 yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
58 yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
59 return files, matchfn, walk()
59 return files, matchfn, walk()
60
60
61 def walk(repo, pats, opts, node=None, head='', badmatch=None):
61 def walk(repo, pats, opts, node=None, head='', badmatch=None):
62 files, matchfn, results = makewalk(repo, pats, opts, node, head, badmatch)
62 files, matchfn, results = makewalk(repo, pats, opts, node, head, badmatch)
63 for r in results:
63 for r in results:
64 yield r
64 yield r
65
65
66 def walkchangerevs(ui, repo, pats, opts):
66 def walkchangerevs(ui, repo, pats, opts):
67 '''Iterate over files and the revs they changed in.
67 '''Iterate over files and the revs they changed in.
68
68
69 Callers most commonly need to iterate backwards over the history
69 Callers most commonly need to iterate backwards over the history
70 it is interested in. Doing so has awful (quadratic-looking)
70 it is interested in. Doing so has awful (quadratic-looking)
71 performance, so we use iterators in a "windowed" way.
71 performance, so we use iterators in a "windowed" way.
72
72
73 We walk a window of revisions in the desired order. Within the
73 We walk a window of revisions in the desired order. Within the
74 window, we first walk forwards to gather data, then in the desired
74 window, we first walk forwards to gather data, then in the desired
75 order (usually backwards) to display it.
75 order (usually backwards) to display it.
76
76
77 This function returns an (iterator, getchange, matchfn) tuple. The
77 This function returns an (iterator, getchange, matchfn) tuple. The
78 getchange function returns the changelog entry for a numeric
78 getchange function returns the changelog entry for a numeric
79 revision. The iterator yields 3-tuples. They will be of one of
79 revision. The iterator yields 3-tuples. They will be of one of
80 the following forms:
80 the following forms:
81
81
82 "window", incrementing, lastrev: stepping through a window,
82 "window", incrementing, lastrev: stepping through a window,
83 positive if walking forwards through revs, last rev in the
83 positive if walking forwards through revs, last rev in the
84 sequence iterated over - use to reset state for the current window
84 sequence iterated over - use to reset state for the current window
85
85
86 "add", rev, fns: out-of-order traversal of the given file names
86 "add", rev, fns: out-of-order traversal of the given file names
87 fns, which changed during revision rev - use to gather data for
87 fns, which changed during revision rev - use to gather data for
88 possible display
88 possible display
89
89
90 "iter", rev, None: in-order traversal of the revs earlier iterated
90 "iter", rev, None: in-order traversal of the revs earlier iterated
91 over with "add" - use to display data'''
91 over with "add" - use to display data'''
92
92
93 def increasing_windows(start, end, windowsize=8, sizelimit=512):
93 def increasing_windows(start, end, windowsize=8, sizelimit=512):
94 if start < end:
94 if start < end:
95 while start < end:
95 while start < end:
96 yield start, min(windowsize, end-start)
96 yield start, min(windowsize, end-start)
97 start += windowsize
97 start += windowsize
98 if windowsize < sizelimit:
98 if windowsize < sizelimit:
99 windowsize *= 2
99 windowsize *= 2
100 else:
100 else:
101 while start > end:
101 while start > end:
102 yield start, min(windowsize, start-end-1)
102 yield start, min(windowsize, start-end-1)
103 start -= windowsize
103 start -= windowsize
104 if windowsize < sizelimit:
104 if windowsize < sizelimit:
105 windowsize *= 2
105 windowsize *= 2
106
106
107
107
108 files, matchfn, anypats = matchpats(repo, pats, opts)
108 files, matchfn, anypats = matchpats(repo, pats, opts)
109
109
110 if repo.changelog.count() == 0:
110 if repo.changelog.count() == 0:
111 return [], False, matchfn
111 return [], False, matchfn
112
112
113 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
113 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
114 wanted = {}
114 wanted = {}
115 slowpath = anypats
115 slowpath = anypats
116 fncache = {}
116 fncache = {}
117
117
118 chcache = {}
118 chcache = {}
119 def getchange(rev):
119 def getchange(rev):
120 ch = chcache.get(rev)
120 ch = chcache.get(rev)
121 if ch is None:
121 if ch is None:
122 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
122 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
123 return ch
123 return ch
124
124
125 if not slowpath and not files:
125 if not slowpath and not files:
126 # No files, no patterns. Display all revs.
126 # No files, no patterns. Display all revs.
127 wanted = dict(zip(revs, revs))
127 wanted = dict(zip(revs, revs))
128 if not slowpath:
128 if not slowpath:
129 # Only files, no patterns. Check the history of each file.
129 # Only files, no patterns. Check the history of each file.
130 def filerevgen(filelog):
130 def filerevgen(filelog):
131 for i, window in increasing_windows(filelog.count()-1, -1):
131 for i, window in increasing_windows(filelog.count()-1, -1):
132 revs = []
132 revs = []
133 for j in xrange(i - window, i + 1):
133 for j in xrange(i - window, i + 1):
134 revs.append(filelog.linkrev(filelog.node(j)))
134 revs.append(filelog.linkrev(filelog.node(j)))
135 revs.reverse()
135 revs.reverse()
136 for rev in revs:
136 for rev in revs:
137 yield rev
137 yield rev
138
138
139 minrev, maxrev = min(revs), max(revs)
139 minrev, maxrev = min(revs), max(revs)
140 for file_ in files:
140 for file_ in files:
141 filelog = repo.file(file_)
141 filelog = repo.file(file_)
142 # A zero count may be a directory or deleted file, so
142 # A zero count may be a directory or deleted file, so
143 # try to find matching entries on the slow path.
143 # try to find matching entries on the slow path.
144 if filelog.count() == 0:
144 if filelog.count() == 0:
145 slowpath = True
145 slowpath = True
146 break
146 break
147 for rev in filerevgen(filelog):
147 for rev in filerevgen(filelog):
148 if rev <= maxrev:
148 if rev <= maxrev:
149 if rev < minrev:
149 if rev < minrev:
150 break
150 break
151 fncache.setdefault(rev, [])
151 fncache.setdefault(rev, [])
152 fncache[rev].append(file_)
152 fncache[rev].append(file_)
153 wanted[rev] = 1
153 wanted[rev] = 1
154 if slowpath:
154 if slowpath:
155 # The slow path checks files modified in every changeset.
155 # The slow path checks files modified in every changeset.
156 def changerevgen():
156 def changerevgen():
157 for i, window in increasing_windows(repo.changelog.count()-1, -1):
157 for i, window in increasing_windows(repo.changelog.count()-1, -1):
158 for j in xrange(i - window, i + 1):
158 for j in xrange(i - window, i + 1):
159 yield j, getchange(j)[3]
159 yield j, getchange(j)[3]
160
160
161 for rev, changefiles in changerevgen():
161 for rev, changefiles in changerevgen():
162 matches = filter(matchfn, changefiles)
162 matches = filter(matchfn, changefiles)
163 if matches:
163 if matches:
164 fncache[rev] = matches
164 fncache[rev] = matches
165 wanted[rev] = 1
165 wanted[rev] = 1
166
166
167 def iterate():
167 def iterate():
168 for i, window in increasing_windows(0, len(revs)):
168 for i, window in increasing_windows(0, len(revs)):
169 yield 'window', revs[0] < revs[-1], revs[-1]
169 yield 'window', revs[0] < revs[-1], revs[-1]
170 nrevs = [rev for rev in revs[i:i+window]
170 nrevs = [rev for rev in revs[i:i+window]
171 if rev in wanted]
171 if rev in wanted]
172 srevs = list(nrevs)
172 srevs = list(nrevs)
173 srevs.sort()
173 srevs.sort()
174 for rev in srevs:
174 for rev in srevs:
175 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
175 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
176 yield 'add', rev, fns
176 yield 'add', rev, fns
177 for rev in nrevs:
177 for rev in nrevs:
178 yield 'iter', rev, None
178 yield 'iter', rev, None
179 return iterate(), getchange, matchfn
179 return iterate(), getchange, matchfn
180
180
181 revrangesep = ':'
181 revrangesep = ':'
182
182
183 def revfix(repo, val, defval):
183 def revfix(repo, val, defval):
184 '''turn user-level id of changeset into rev number.
184 '''turn user-level id of changeset into rev number.
185 user-level id can be tag, changeset, rev number, or negative rev
185 user-level id can be tag, changeset, rev number, or negative rev
186 number relative to number of revs (-1 is tip, etc).'''
186 number relative to number of revs (-1 is tip, etc).'''
187 if not val:
187 if not val:
188 return defval
188 return defval
189 try:
189 try:
190 num = int(val)
190 num = int(val)
191 if str(num) != val:
191 if str(num) != val:
192 raise ValueError
192 raise ValueError
193 if num < 0:
193 if num < 0:
194 num += repo.changelog.count()
194 num += repo.changelog.count()
195 if num < 0:
195 if num < 0:
196 num = 0
196 num = 0
197 elif num >= repo.changelog.count():
197 elif num >= repo.changelog.count():
198 raise ValueError
198 raise ValueError
199 except ValueError:
199 except ValueError:
200 try:
200 try:
201 num = repo.changelog.rev(repo.lookup(val))
201 num = repo.changelog.rev(repo.lookup(val))
202 except KeyError:
202 except KeyError:
203 raise util.Abort(_('invalid revision identifier %s'), val)
203 raise util.Abort(_('invalid revision identifier %s'), val)
204 return num
204 return num
205
205
206 def revpair(ui, repo, revs):
206 def revpair(ui, repo, revs):
207 '''return pair of nodes, given list of revisions. second item can
207 '''return pair of nodes, given list of revisions. second item can
208 be None, meaning use working dir.'''
208 be None, meaning use working dir.'''
209 if not revs:
209 if not revs:
210 return repo.dirstate.parents()[0], None
210 return repo.dirstate.parents()[0], None
211 end = None
211 end = None
212 if len(revs) == 1:
212 if len(revs) == 1:
213 start = revs[0]
213 start = revs[0]
214 if revrangesep in start:
214 if revrangesep in start:
215 start, end = start.split(revrangesep, 1)
215 start, end = start.split(revrangesep, 1)
216 start = revfix(repo, start, 0)
216 start = revfix(repo, start, 0)
217 end = revfix(repo, end, repo.changelog.count() - 1)
217 end = revfix(repo, end, repo.changelog.count() - 1)
218 else:
218 else:
219 start = revfix(repo, start, None)
219 start = revfix(repo, start, None)
220 elif len(revs) == 2:
220 elif len(revs) == 2:
221 if revrangesep in revs[0] or revrangesep in revs[1]:
221 if revrangesep in revs[0] or revrangesep in revs[1]:
222 raise util.Abort(_('too many revisions specified'))
222 raise util.Abort(_('too many revisions specified'))
223 start = revfix(repo, revs[0], None)
223 start = revfix(repo, revs[0], None)
224 end = revfix(repo, revs[1], None)
224 end = revfix(repo, revs[1], None)
225 else:
225 else:
226 raise util.Abort(_('too many revisions specified'))
226 raise util.Abort(_('too many revisions specified'))
227 if end is not None: end = repo.lookup(str(end))
227 if end is not None: end = repo.lookup(str(end))
228 return repo.lookup(str(start)), end
228 return repo.lookup(str(start)), end
229
229
230 def revrange(ui, repo, revs):
230 def revrange(ui, repo, revs):
231 """Yield revision as strings from a list of revision specifications."""
231 """Yield revision as strings from a list of revision specifications."""
232 seen = {}
232 seen = {}
233 for spec in revs:
233 for spec in revs:
234 if spec.find(revrangesep) >= 0:
234 if revrangesep in spec:
235 start, end = spec.split(revrangesep, 1)
235 start, end = spec.split(revrangesep, 1)
236 start = revfix(repo, start, 0)
236 start = revfix(repo, start, 0)
237 end = revfix(repo, end, repo.changelog.count() - 1)
237 end = revfix(repo, end, repo.changelog.count() - 1)
238 step = start > end and -1 or 1
238 step = start > end and -1 or 1
239 for rev in xrange(start, end+step, step):
239 for rev in xrange(start, end+step, step):
240 if rev in seen:
240 if rev in seen:
241 continue
241 continue
242 seen[rev] = 1
242 seen[rev] = 1
243 yield str(rev)
243 yield str(rev)
244 else:
244 else:
245 rev = revfix(repo, spec, None)
245 rev = revfix(repo, spec, None)
246 if rev in seen:
246 if rev in seen:
247 continue
247 continue
248 seen[rev] = 1
248 seen[rev] = 1
249 yield str(rev)
249 yield str(rev)
250
250
251 def make_filename(repo, pat, node,
251 def make_filename(repo, pat, node,
252 total=None, seqno=None, revwidth=None, pathname=None):
252 total=None, seqno=None, revwidth=None, pathname=None):
253 node_expander = {
253 node_expander = {
254 'H': lambda: hex(node),
254 'H': lambda: hex(node),
255 'R': lambda: str(repo.changelog.rev(node)),
255 'R': lambda: str(repo.changelog.rev(node)),
256 'h': lambda: short(node),
256 'h': lambda: short(node),
257 }
257 }
258 expander = {
258 expander = {
259 '%': lambda: '%',
259 '%': lambda: '%',
260 'b': lambda: os.path.basename(repo.root),
260 'b': lambda: os.path.basename(repo.root),
261 }
261 }
262
262
263 try:
263 try:
264 if node:
264 if node:
265 expander.update(node_expander)
265 expander.update(node_expander)
266 if node and revwidth is not None:
266 if node and revwidth is not None:
267 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
267 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
268 if total is not None:
268 if total is not None:
269 expander['N'] = lambda: str(total)
269 expander['N'] = lambda: str(total)
270 if seqno is not None:
270 if seqno is not None:
271 expander['n'] = lambda: str(seqno)
271 expander['n'] = lambda: str(seqno)
272 if total is not None and seqno is not None:
272 if total is not None and seqno is not None:
273 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
273 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
274 if pathname is not None:
274 if pathname is not None:
275 expander['s'] = lambda: os.path.basename(pathname)
275 expander['s'] = lambda: os.path.basename(pathname)
276 expander['d'] = lambda: os.path.dirname(pathname) or '.'
276 expander['d'] = lambda: os.path.dirname(pathname) or '.'
277 expander['p'] = lambda: pathname
277 expander['p'] = lambda: pathname
278
278
279 newname = []
279 newname = []
280 patlen = len(pat)
280 patlen = len(pat)
281 i = 0
281 i = 0
282 while i < patlen:
282 while i < patlen:
283 c = pat[i]
283 c = pat[i]
284 if c == '%':
284 if c == '%':
285 i += 1
285 i += 1
286 c = pat[i]
286 c = pat[i]
287 c = expander[c]()
287 c = expander[c]()
288 newname.append(c)
288 newname.append(c)
289 i += 1
289 i += 1
290 return ''.join(newname)
290 return ''.join(newname)
291 except KeyError, inst:
291 except KeyError, inst:
292 raise util.Abort(_("invalid format spec '%%%s' in output file name"),
292 raise util.Abort(_("invalid format spec '%%%s' in output file name"),
293 inst.args[0])
293 inst.args[0])
294
294
295 def make_file(repo, pat, node=None,
295 def make_file(repo, pat, node=None,
296 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
296 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
297 if not pat or pat == '-':
297 if not pat or pat == '-':
298 return 'w' in mode and sys.stdout or sys.stdin
298 return 'w' in mode and sys.stdout or sys.stdin
299 if hasattr(pat, 'write') and 'w' in mode:
299 if hasattr(pat, 'write') and 'w' in mode:
300 return pat
300 return pat
301 if hasattr(pat, 'read') and 'r' in mode:
301 if hasattr(pat, 'read') and 'r' in mode:
302 return pat
302 return pat
303 return open(make_filename(repo, pat, node, total, seqno, revwidth,
303 return open(make_filename(repo, pat, node, total, seqno, revwidth,
304 pathname),
304 pathname),
305 mode)
305 mode)
306
306
307 def write_bundle(cg, filename=None, compress=True):
307 def write_bundle(cg, filename=None, compress=True):
308 """Write a bundle file and return its filename.
308 """Write a bundle file and return its filename.
309
309
310 Existing files will not be overwritten.
310 Existing files will not be overwritten.
311 If no filename is specified, a temporary file is created.
311 If no filename is specified, a temporary file is created.
312 bz2 compression can be turned off.
312 bz2 compression can be turned off.
313 The bundle file will be deleted in case of errors.
313 The bundle file will be deleted in case of errors.
314 """
314 """
315 class nocompress(object):
315 class nocompress(object):
316 def compress(self, x):
316 def compress(self, x):
317 return x
317 return x
318 def flush(self):
318 def flush(self):
319 return ""
319 return ""
320
320
321 fh = None
321 fh = None
322 cleanup = None
322 cleanup = None
323 try:
323 try:
324 if filename:
324 if filename:
325 if os.path.exists(filename):
325 if os.path.exists(filename):
326 raise util.Abort(_("file '%s' already exists"), filename)
326 raise util.Abort(_("file '%s' already exists"), filename)
327 fh = open(filename, "wb")
327 fh = open(filename, "wb")
328 else:
328 else:
329 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
329 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
330 fh = os.fdopen(fd, "wb")
330 fh = os.fdopen(fd, "wb")
331 cleanup = filename
331 cleanup = filename
332
332
333 if compress:
333 if compress:
334 fh.write("HG10")
334 fh.write("HG10")
335 z = bz2.BZ2Compressor(9)
335 z = bz2.BZ2Compressor(9)
336 else:
336 else:
337 fh.write("HG10UN")
337 fh.write("HG10UN")
338 z = nocompress()
338 z = nocompress()
339 # parse the changegroup data, otherwise we will block
339 # parse the changegroup data, otherwise we will block
340 # in case of sshrepo because we don't know the end of the stream
340 # in case of sshrepo because we don't know the end of the stream
341
341
342 # an empty chunkiter is the end of the changegroup
342 # an empty chunkiter is the end of the changegroup
343 empty = False
343 empty = False
344 while not empty:
344 while not empty:
345 empty = True
345 empty = True
346 for chunk in changegroup.chunkiter(cg):
346 for chunk in changegroup.chunkiter(cg):
347 empty = False
347 empty = False
348 fh.write(z.compress(changegroup.genchunk(chunk)))
348 fh.write(z.compress(changegroup.genchunk(chunk)))
349 fh.write(z.compress(changegroup.closechunk()))
349 fh.write(z.compress(changegroup.closechunk()))
350 fh.write(z.flush())
350 fh.write(z.flush())
351 cleanup = None
351 cleanup = None
352 return filename
352 return filename
353 finally:
353 finally:
354 if fh is not None:
354 if fh is not None:
355 fh.close()
355 fh.close()
356 if cleanup is not None:
356 if cleanup is not None:
357 os.unlink(cleanup)
357 os.unlink(cleanup)
358
358
359 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
359 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
360 changes=None, text=False, opts={}):
360 changes=None, text=False, opts={}):
361 if not node1:
361 if not node1:
362 node1 = repo.dirstate.parents()[0]
362 node1 = repo.dirstate.parents()[0]
363 # reading the data for node1 early allows it to play nicely
363 # reading the data for node1 early allows it to play nicely
364 # with repo.changes and the revlog cache.
364 # with repo.changes and the revlog cache.
365 change = repo.changelog.read(node1)
365 change = repo.changelog.read(node1)
366 mmap = repo.manifest.read(change[0])
366 mmap = repo.manifest.read(change[0])
367 date1 = util.datestr(change[2])
367 date1 = util.datestr(change[2])
368
368
369 if not changes:
369 if not changes:
370 changes = repo.changes(node1, node2, files, match=match)
370 changes = repo.changes(node1, node2, files, match=match)
371 modified, added, removed, deleted, unknown = changes
371 modified, added, removed, deleted, unknown = changes
372 if files:
372 if files:
373 modified, added, removed = map(lambda x: filterfiles(files, x),
373 modified, added, removed = map(lambda x: filterfiles(files, x),
374 (modified, added, removed))
374 (modified, added, removed))
375
375
376 if not modified and not added and not removed:
376 if not modified and not added and not removed:
377 return
377 return
378
378
379 if node2:
379 if node2:
380 change = repo.changelog.read(node2)
380 change = repo.changelog.read(node2)
381 mmap2 = repo.manifest.read(change[0])
381 mmap2 = repo.manifest.read(change[0])
382 _date2 = util.datestr(change[2])
382 _date2 = util.datestr(change[2])
383 def date2(f):
383 def date2(f):
384 return _date2
384 return _date2
385 def read(f):
385 def read(f):
386 return repo.file(f).read(mmap2[f])
386 return repo.file(f).read(mmap2[f])
387 else:
387 else:
388 tz = util.makedate()[1]
388 tz = util.makedate()[1]
389 _date2 = util.datestr()
389 _date2 = util.datestr()
390 def date2(f):
390 def date2(f):
391 try:
391 try:
392 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
392 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
393 except OSError, err:
393 except OSError, err:
394 if err.errno != errno.ENOENT: raise
394 if err.errno != errno.ENOENT: raise
395 return _date2
395 return _date2
396 def read(f):
396 def read(f):
397 return repo.wread(f)
397 return repo.wread(f)
398
398
399 if ui.quiet:
399 if ui.quiet:
400 r = None
400 r = None
401 else:
401 else:
402 hexfunc = ui.verbose and hex or short
402 hexfunc = ui.verbose and hex or short
403 r = [hexfunc(node) for node in [node1, node2] if node]
403 r = [hexfunc(node) for node in [node1, node2] if node]
404
404
405 diffopts = ui.diffopts()
405 diffopts = ui.diffopts()
406 showfunc = opts.get('show_function') or diffopts['showfunc']
406 showfunc = opts.get('show_function') or diffopts['showfunc']
407 ignorews = opts.get('ignore_all_space') or diffopts['ignorews']
407 ignorews = opts.get('ignore_all_space') or diffopts['ignorews']
408 for f in modified:
408 for f in modified:
409 to = None
409 to = None
410 if f in mmap:
410 if f in mmap:
411 to = repo.file(f).read(mmap[f])
411 to = repo.file(f).read(mmap[f])
412 tn = read(f)
412 tn = read(f)
413 fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, text=text,
413 fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, text=text,
414 showfunc=showfunc, ignorews=ignorews))
414 showfunc=showfunc, ignorews=ignorews))
415 for f in added:
415 for f in added:
416 to = None
416 to = None
417 tn = read(f)
417 tn = read(f)
418 fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, text=text,
418 fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, text=text,
419 showfunc=showfunc, ignorews=ignorews))
419 showfunc=showfunc, ignorews=ignorews))
420 for f in removed:
420 for f in removed:
421 to = repo.file(f).read(mmap[f])
421 to = repo.file(f).read(mmap[f])
422 tn = None
422 tn = None
423 fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, text=text,
423 fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, text=text,
424 showfunc=showfunc, ignorews=ignorews))
424 showfunc=showfunc, ignorews=ignorews))
425
425
426 def trimuser(ui, name, rev, revcache):
426 def trimuser(ui, name, rev, revcache):
427 """trim the name of the user who committed a change"""
427 """trim the name of the user who committed a change"""
428 user = revcache.get(rev)
428 user = revcache.get(rev)
429 if user is None:
429 if user is None:
430 user = revcache[rev] = ui.shortuser(name)
430 user = revcache[rev] = ui.shortuser(name)
431 return user
431 return user
432
432
433 class changeset_printer(object):
433 class changeset_printer(object):
434 '''show changeset information when templating not requested.'''
434 '''show changeset information when templating not requested.'''
435
435
436 def __init__(self, ui, repo):
436 def __init__(self, ui, repo):
437 self.ui = ui
437 self.ui = ui
438 self.repo = repo
438 self.repo = repo
439
439
440 def show(self, rev=0, changenode=None, brinfo=None):
440 def show(self, rev=0, changenode=None, brinfo=None):
441 '''show a single changeset or file revision'''
441 '''show a single changeset or file revision'''
442 log = self.repo.changelog
442 log = self.repo.changelog
443 if changenode is None:
443 if changenode is None:
444 changenode = log.node(rev)
444 changenode = log.node(rev)
445 elif not rev:
445 elif not rev:
446 rev = log.rev(changenode)
446 rev = log.rev(changenode)
447
447
448 if self.ui.quiet:
448 if self.ui.quiet:
449 self.ui.write("%d:%s\n" % (rev, short(changenode)))
449 self.ui.write("%d:%s\n" % (rev, short(changenode)))
450 return
450 return
451
451
452 changes = log.read(changenode)
452 changes = log.read(changenode)
453 date = util.datestr(changes[2])
453 date = util.datestr(changes[2])
454
454
455 parents = [(log.rev(p), self.ui.verbose and hex(p) or short(p))
455 parents = [(log.rev(p), self.ui.verbose and hex(p) or short(p))
456 for p in log.parents(changenode)
456 for p in log.parents(changenode)
457 if self.ui.debugflag or p != nullid]
457 if self.ui.debugflag or p != nullid]
458 if (not self.ui.debugflag and len(parents) == 1 and
458 if (not self.ui.debugflag and len(parents) == 1 and
459 parents[0][0] == rev-1):
459 parents[0][0] == rev-1):
460 parents = []
460 parents = []
461
461
462 if self.ui.verbose:
462 if self.ui.verbose:
463 self.ui.write(_("changeset: %d:%s\n") % (rev, hex(changenode)))
463 self.ui.write(_("changeset: %d:%s\n") % (rev, hex(changenode)))
464 else:
464 else:
465 self.ui.write(_("changeset: %d:%s\n") % (rev, short(changenode)))
465 self.ui.write(_("changeset: %d:%s\n") % (rev, short(changenode)))
466
466
467 for tag in self.repo.nodetags(changenode):
467 for tag in self.repo.nodetags(changenode):
468 self.ui.status(_("tag: %s\n") % tag)
468 self.ui.status(_("tag: %s\n") % tag)
469 for parent in parents:
469 for parent in parents:
470 self.ui.write(_("parent: %d:%s\n") % parent)
470 self.ui.write(_("parent: %d:%s\n") % parent)
471
471
472 if brinfo and changenode in brinfo:
472 if brinfo and changenode in brinfo:
473 br = brinfo[changenode]
473 br = brinfo[changenode]
474 self.ui.write(_("branch: %s\n") % " ".join(br))
474 self.ui.write(_("branch: %s\n") % " ".join(br))
475
475
476 self.ui.debug(_("manifest: %d:%s\n") %
476 self.ui.debug(_("manifest: %d:%s\n") %
477 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
477 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
478 self.ui.status(_("user: %s\n") % changes[1])
478 self.ui.status(_("user: %s\n") % changes[1])
479 self.ui.status(_("date: %s\n") % date)
479 self.ui.status(_("date: %s\n") % date)
480
480
481 if self.ui.debugflag:
481 if self.ui.debugflag:
482 files = self.repo.changes(log.parents(changenode)[0], changenode)
482 files = self.repo.changes(log.parents(changenode)[0], changenode)
483 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
483 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
484 files):
484 files):
485 if value:
485 if value:
486 self.ui.note("%-12s %s\n" % (key, " ".join(value)))
486 self.ui.note("%-12s %s\n" % (key, " ".join(value)))
487 else:
487 else:
488 self.ui.note(_("files: %s\n") % " ".join(changes[3]))
488 self.ui.note(_("files: %s\n") % " ".join(changes[3]))
489
489
490 description = changes[4].strip()
490 description = changes[4].strip()
491 if description:
491 if description:
492 if self.ui.verbose:
492 if self.ui.verbose:
493 self.ui.status(_("description:\n"))
493 self.ui.status(_("description:\n"))
494 self.ui.status(description)
494 self.ui.status(description)
495 self.ui.status("\n\n")
495 self.ui.status("\n\n")
496 else:
496 else:
497 self.ui.status(_("summary: %s\n") %
497 self.ui.status(_("summary: %s\n") %
498 description.splitlines()[0])
498 description.splitlines()[0])
499 self.ui.status("\n")
499 self.ui.status("\n")
500
500
501 def show_changeset(ui, repo, opts):
501 def show_changeset(ui, repo, opts):
502 '''show one changeset. uses template or regular display. caller
502 '''show one changeset. uses template or regular display. caller
503 can pass in 'style' and 'template' options in opts.'''
503 can pass in 'style' and 'template' options in opts.'''
504
504
505 tmpl = opts.get('template')
505 tmpl = opts.get('template')
506 if tmpl:
506 if tmpl:
507 tmpl = templater.parsestring(tmpl, quoted=False)
507 tmpl = templater.parsestring(tmpl, quoted=False)
508 else:
508 else:
509 tmpl = ui.config('ui', 'logtemplate')
509 tmpl = ui.config('ui', 'logtemplate')
510 if tmpl: tmpl = templater.parsestring(tmpl)
510 if tmpl: tmpl = templater.parsestring(tmpl)
511 mapfile = opts.get('style') or ui.config('ui', 'style')
511 mapfile = opts.get('style') or ui.config('ui', 'style')
512 if tmpl or mapfile:
512 if tmpl or mapfile:
513 if mapfile:
513 if mapfile:
514 if not os.path.isfile(mapfile):
514 if not os.path.isfile(mapfile):
515 mapname = templater.templatepath('map-cmdline.' + mapfile)
515 mapname = templater.templatepath('map-cmdline.' + mapfile)
516 if not mapname: mapname = templater.templatepath(mapfile)
516 if not mapname: mapname = templater.templatepath(mapfile)
517 if mapname: mapfile = mapname
517 if mapname: mapfile = mapname
518 try:
518 try:
519 t = templater.changeset_templater(ui, repo, mapfile)
519 t = templater.changeset_templater(ui, repo, mapfile)
520 except SyntaxError, inst:
520 except SyntaxError, inst:
521 raise util.Abort(inst.args[0])
521 raise util.Abort(inst.args[0])
522 if tmpl: t.use_template(tmpl)
522 if tmpl: t.use_template(tmpl)
523 return t
523 return t
524 return changeset_printer(ui, repo)
524 return changeset_printer(ui, repo)
525
525
526 def show_version(ui):
526 def show_version(ui):
527 """output version and copyright information"""
527 """output version and copyright information"""
528 ui.write(_("Mercurial Distributed SCM (version %s)\n")
528 ui.write(_("Mercurial Distributed SCM (version %s)\n")
529 % version.get_version())
529 % version.get_version())
530 ui.status(_(
530 ui.status(_(
531 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
531 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
532 "This is free software; see the source for copying conditions. "
532 "This is free software; see the source for copying conditions. "
533 "There is NO\nwarranty; "
533 "There is NO\nwarranty; "
534 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
534 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
535 ))
535 ))
536
536
537 def help_(ui, name=None, with_version=False):
537 def help_(ui, name=None, with_version=False):
538 """show help for a command, extension, or list of commands
538 """show help for a command, extension, or list of commands
539
539
540 With no arguments, print a list of commands and short help.
540 With no arguments, print a list of commands and short help.
541
541
542 Given a command name, print help for that command.
542 Given a command name, print help for that command.
543
543
544 Given an extension name, print help for that extension, and the
544 Given an extension name, print help for that extension, and the
545 commands it provides."""
545 commands it provides."""
546 option_lists = []
546 option_lists = []
547
547
548 def helpcmd(name):
548 def helpcmd(name):
549 if with_version:
549 if with_version:
550 show_version(ui)
550 show_version(ui)
551 ui.write('\n')
551 ui.write('\n')
552 aliases, i = findcmd(name)
552 aliases, i = findcmd(name)
553 # synopsis
553 # synopsis
554 ui.write("%s\n\n" % i[2])
554 ui.write("%s\n\n" % i[2])
555
555
556 # description
556 # description
557 doc = i[0].__doc__
557 doc = i[0].__doc__
558 if not doc:
558 if not doc:
559 doc = _("(No help text available)")
559 doc = _("(No help text available)")
560 if ui.quiet:
560 if ui.quiet:
561 doc = doc.splitlines(0)[0]
561 doc = doc.splitlines(0)[0]
562 ui.write("%s\n" % doc.rstrip())
562 ui.write("%s\n" % doc.rstrip())
563
563
564 if not ui.quiet:
564 if not ui.quiet:
565 # aliases
565 # aliases
566 if len(aliases) > 1:
566 if len(aliases) > 1:
567 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
567 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
568
568
569 # options
569 # options
570 if i[1]:
570 if i[1]:
571 option_lists.append(("options", i[1]))
571 option_lists.append(("options", i[1]))
572
572
573 def helplist(select=None):
573 def helplist(select=None):
574 h = {}
574 h = {}
575 cmds = {}
575 cmds = {}
576 for c, e in table.items():
576 for c, e in table.items():
577 f = c.split("|", 1)[0]
577 f = c.split("|", 1)[0]
578 if select and not select(f):
578 if select and not select(f):
579 continue
579 continue
580 if name == "shortlist" and not f.startswith("^"):
580 if name == "shortlist" and not f.startswith("^"):
581 continue
581 continue
582 f = f.lstrip("^")
582 f = f.lstrip("^")
583 if not ui.debugflag and f.startswith("debug"):
583 if not ui.debugflag and f.startswith("debug"):
584 continue
584 continue
585 doc = e[0].__doc__
585 doc = e[0].__doc__
586 if not doc:
586 if not doc:
587 doc = _("(No help text available)")
587 doc = _("(No help text available)")
588 h[f] = doc.splitlines(0)[0].rstrip()
588 h[f] = doc.splitlines(0)[0].rstrip()
589 cmds[f] = c.lstrip("^")
589 cmds[f] = c.lstrip("^")
590
590
591 fns = h.keys()
591 fns = h.keys()
592 fns.sort()
592 fns.sort()
593 m = max(map(len, fns))
593 m = max(map(len, fns))
594 for f in fns:
594 for f in fns:
595 if ui.verbose:
595 if ui.verbose:
596 commands = cmds[f].replace("|",", ")
596 commands = cmds[f].replace("|",", ")
597 ui.write(" %s:\n %s\n"%(commands, h[f]))
597 ui.write(" %s:\n %s\n"%(commands, h[f]))
598 else:
598 else:
599 ui.write(' %-*s %s\n' % (m, f, h[f]))
599 ui.write(' %-*s %s\n' % (m, f, h[f]))
600
600
601 def helpext(name):
601 def helpext(name):
602 try:
602 try:
603 mod = findext(name)
603 mod = findext(name)
604 except KeyError:
604 except KeyError:
605 raise UnknownCommand(name)
605 raise UnknownCommand(name)
606
606
607 doc = (mod.__doc__ or _('No help text available')).splitlines(0)
607 doc = (mod.__doc__ or _('No help text available')).splitlines(0)
608 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
608 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
609 for d in doc[1:]:
609 for d in doc[1:]:
610 ui.write(d, '\n')
610 ui.write(d, '\n')
611
611
612 ui.status('\n')
612 ui.status('\n')
613 if ui.verbose:
613 if ui.verbose:
614 ui.status(_('list of commands:\n\n'))
614 ui.status(_('list of commands:\n\n'))
615 else:
615 else:
616 ui.status(_('list of commands (use "hg help -v %s" '
616 ui.status(_('list of commands (use "hg help -v %s" '
617 'to show aliases and global options):\n\n') % name)
617 'to show aliases and global options):\n\n') % name)
618
618
619 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in mod.cmdtable])
619 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in mod.cmdtable])
620 helplist(modcmds.has_key)
620 helplist(modcmds.has_key)
621
621
622 if name and name != 'shortlist':
622 if name and name != 'shortlist':
623 try:
623 try:
624 helpcmd(name)
624 helpcmd(name)
625 except UnknownCommand:
625 except UnknownCommand:
626 helpext(name)
626 helpext(name)
627
627
628 else:
628 else:
629 # program name
629 # program name
630 if ui.verbose or with_version:
630 if ui.verbose or with_version:
631 show_version(ui)
631 show_version(ui)
632 else:
632 else:
633 ui.status(_("Mercurial Distributed SCM\n"))
633 ui.status(_("Mercurial Distributed SCM\n"))
634 ui.status('\n')
634 ui.status('\n')
635
635
636 # list of commands
636 # list of commands
637 if name == "shortlist":
637 if name == "shortlist":
638 ui.status(_('basic commands (use "hg help" '
638 ui.status(_('basic commands (use "hg help" '
639 'for the full list or option "-v" for details):\n\n'))
639 'for the full list or option "-v" for details):\n\n'))
640 elif ui.verbose:
640 elif ui.verbose:
641 ui.status(_('list of commands:\n\n'))
641 ui.status(_('list of commands:\n\n'))
642 else:
642 else:
643 ui.status(_('list of commands (use "hg help -v" '
643 ui.status(_('list of commands (use "hg help -v" '
644 'to show aliases and global options):\n\n'))
644 'to show aliases and global options):\n\n'))
645
645
646 helplist()
646 helplist()
647
647
648 # global options
648 # global options
649 if ui.verbose:
649 if ui.verbose:
650 option_lists.append(("global options", globalopts))
650 option_lists.append(("global options", globalopts))
651
651
652 # list all option lists
652 # list all option lists
653 opt_output = []
653 opt_output = []
654 for title, options in option_lists:
654 for title, options in option_lists:
655 opt_output.append(("\n%s:\n" % title, None))
655 opt_output.append(("\n%s:\n" % title, None))
656 for shortopt, longopt, default, desc in options:
656 for shortopt, longopt, default, desc in options:
657 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
657 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
658 longopt and " --%s" % longopt),
658 longopt and " --%s" % longopt),
659 "%s%s" % (desc,
659 "%s%s" % (desc,
660 default
660 default
661 and _(" (default: %s)") % default
661 and _(" (default: %s)") % default
662 or "")))
662 or "")))
663
663
664 if opt_output:
664 if opt_output:
665 opts_len = max([len(line[0]) for line in opt_output if line[1]])
665 opts_len = max([len(line[0]) for line in opt_output if line[1]])
666 for first, second in opt_output:
666 for first, second in opt_output:
667 if second:
667 if second:
668 ui.write(" %-*s %s\n" % (opts_len, first, second))
668 ui.write(" %-*s %s\n" % (opts_len, first, second))
669 else:
669 else:
670 ui.write("%s\n" % first)
670 ui.write("%s\n" % first)
671
671
672 # Commands start here, listed alphabetically
672 # Commands start here, listed alphabetically
673
673
674 def add(ui, repo, *pats, **opts):
674 def add(ui, repo, *pats, **opts):
675 """add the specified files on the next commit
675 """add the specified files on the next commit
676
676
677 Schedule files to be version controlled and added to the repository.
677 Schedule files to be version controlled and added to the repository.
678
678
679 The files will be added to the repository at the next commit.
679 The files will be added to the repository at the next commit.
680
680
681 If no names are given, add all files in the repository.
681 If no names are given, add all files in the repository.
682 """
682 """
683
683
684 names = []
684 names = []
685 for src, abs, rel, exact in walk(repo, pats, opts):
685 for src, abs, rel, exact in walk(repo, pats, opts):
686 if exact:
686 if exact:
687 if ui.verbose:
687 if ui.verbose:
688 ui.status(_('adding %s\n') % rel)
688 ui.status(_('adding %s\n') % rel)
689 names.append(abs)
689 names.append(abs)
690 elif repo.dirstate.state(abs) == '?':
690 elif repo.dirstate.state(abs) == '?':
691 ui.status(_('adding %s\n') % rel)
691 ui.status(_('adding %s\n') % rel)
692 names.append(abs)
692 names.append(abs)
693 if not opts.get('dry_run'):
693 if not opts.get('dry_run'):
694 repo.add(names)
694 repo.add(names)
695
695
696 def addremove(ui, repo, *pats, **opts):
696 def addremove(ui, repo, *pats, **opts):
697 """add all new files, delete all missing files (DEPRECATED)
697 """add all new files, delete all missing files (DEPRECATED)
698
698
699 (DEPRECATED)
699 (DEPRECATED)
700 Add all new files and remove all missing files from the repository.
700 Add all new files and remove all missing files from the repository.
701
701
702 New files are ignored if they match any of the patterns in .hgignore. As
702 New files are ignored if they match any of the patterns in .hgignore. As
703 with add, these changes take effect at the next commit.
703 with add, these changes take effect at the next commit.
704
704
705 This command is now deprecated and will be removed in a future
705 This command is now deprecated and will be removed in a future
706 release. Please use add and remove --after instead.
706 release. Please use add and remove --after instead.
707 """
707 """
708 ui.warn(_('(the addremove command is deprecated; use add and remove '
708 ui.warn(_('(the addremove command is deprecated; use add and remove '
709 '--after instead)\n'))
709 '--after instead)\n'))
710 return addremove_lock(ui, repo, pats, opts)
710 return addremove_lock(ui, repo, pats, opts)
711
711
712 def addremove_lock(ui, repo, pats, opts, wlock=None):
712 def addremove_lock(ui, repo, pats, opts, wlock=None):
713 add, remove = [], []
713 add, remove = [], []
714 for src, abs, rel, exact in walk(repo, pats, opts):
714 for src, abs, rel, exact in walk(repo, pats, opts):
715 if src == 'f' and repo.dirstate.state(abs) == '?':
715 if src == 'f' and repo.dirstate.state(abs) == '?':
716 add.append(abs)
716 add.append(abs)
717 if ui.verbose or not exact:
717 if ui.verbose or not exact:
718 ui.status(_('adding %s\n') % ((pats and rel) or abs))
718 ui.status(_('adding %s\n') % ((pats and rel) or abs))
719 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
719 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
720 remove.append(abs)
720 remove.append(abs)
721 if ui.verbose or not exact:
721 if ui.verbose or not exact:
722 ui.status(_('removing %s\n') % ((pats and rel) or abs))
722 ui.status(_('removing %s\n') % ((pats and rel) or abs))
723 if not opts.get('dry_run'):
723 if not opts.get('dry_run'):
724 repo.add(add, wlock=wlock)
724 repo.add(add, wlock=wlock)
725 repo.remove(remove, wlock=wlock)
725 repo.remove(remove, wlock=wlock)
726
726
727 def annotate(ui, repo, *pats, **opts):
727 def annotate(ui, repo, *pats, **opts):
728 """show changeset information per file line
728 """show changeset information per file line
729
729
730 List changes in files, showing the revision id responsible for each line
730 List changes in files, showing the revision id responsible for each line
731
731
732 This command is useful to discover who did a change or when a change took
732 This command is useful to discover who did a change or when a change took
733 place.
733 place.
734
734
735 Without the -a option, annotate will avoid processing files it
735 Without the -a option, annotate will avoid processing files it
736 detects as binary. With -a, annotate will generate an annotation
736 detects as binary. With -a, annotate will generate an annotation
737 anyway, probably with undesirable results.
737 anyway, probably with undesirable results.
738 """
738 """
739 def getnode(rev):
739 def getnode(rev):
740 return short(repo.changelog.node(rev))
740 return short(repo.changelog.node(rev))
741
741
742 ucache = {}
742 ucache = {}
743 def getname(rev):
743 def getname(rev):
744 try:
744 try:
745 return ucache[rev]
745 return ucache[rev]
746 except:
746 except:
747 u = trimuser(ui, repo.changectx(rev).user(), rev, ucache)
747 u = trimuser(ui, repo.changectx(rev).user(), rev, ucache)
748 ucache[rev] = u
748 ucache[rev] = u
749 return u
749 return u
750
750
751 dcache = {}
751 dcache = {}
752 def getdate(rev):
752 def getdate(rev):
753 datestr = dcache.get(rev)
753 datestr = dcache.get(rev)
754 if datestr is None:
754 if datestr is None:
755 datestr = dcache[rev] = util.datestr(repo.changectx(rev).date())
755 datestr = dcache[rev] = util.datestr(repo.changectx(rev).date())
756 return datestr
756 return datestr
757
757
758 if not pats:
758 if not pats:
759 raise util.Abort(_('at least one file name or pattern required'))
759 raise util.Abort(_('at least one file name or pattern required'))
760
760
761 opmap = [['user', getname], ['number', str], ['changeset', getnode],
761 opmap = [['user', getname], ['number', str], ['changeset', getnode],
762 ['date', getdate]]
762 ['date', getdate]]
763 if not opts['user'] and not opts['changeset'] and not opts['date']:
763 if not opts['user'] and not opts['changeset'] and not opts['date']:
764 opts['number'] = 1
764 opts['number'] = 1
765
765
766 ctx = repo.changectx(opts['rev'] or repo.dirstate.parents()[0])
766 ctx = repo.changectx(opts['rev'] or repo.dirstate.parents()[0])
767
767
768 for src, abs, rel, exact in walk(repo, pats, opts, node=ctx.node()):
768 for src, abs, rel, exact in walk(repo, pats, opts, node=ctx.node()):
769 fctx = ctx.filectx(abs)
769 fctx = ctx.filectx(abs)
770 if not opts['text'] and util.binary(fctx.data()):
770 if not opts['text'] and util.binary(fctx.data()):
771 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
771 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
772 continue
772 continue
773
773
774 lines = fctx.annotate()
774 lines = fctx.annotate()
775 pieces = []
775 pieces = []
776
776
777 for o, f in opmap:
777 for o, f in opmap:
778 if opts[o]:
778 if opts[o]:
779 l = [f(n) for n, dummy in lines]
779 l = [f(n) for n, dummy in lines]
780 if l:
780 if l:
781 m = max(map(len, l))
781 m = max(map(len, l))
782 pieces.append(["%*s" % (m, x) for x in l])
782 pieces.append(["%*s" % (m, x) for x in l])
783
783
784 if pieces:
784 if pieces:
785 for p, l in zip(zip(*pieces), lines):
785 for p, l in zip(zip(*pieces), lines):
786 ui.write("%s: %s" % (" ".join(p), l[1]))
786 ui.write("%s: %s" % (" ".join(p), l[1]))
787
787
788 def archive(ui, repo, dest, **opts):
788 def archive(ui, repo, dest, **opts):
789 '''create unversioned archive of a repository revision
789 '''create unversioned archive of a repository revision
790
790
791 By default, the revision used is the parent of the working
791 By default, the revision used is the parent of the working
792 directory; use "-r" to specify a different revision.
792 directory; use "-r" to specify a different revision.
793
793
794 To specify the type of archive to create, use "-t". Valid
794 To specify the type of archive to create, use "-t". Valid
795 types are:
795 types are:
796
796
797 "files" (default): a directory full of files
797 "files" (default): a directory full of files
798 "tar": tar archive, uncompressed
798 "tar": tar archive, uncompressed
799 "tbz2": tar archive, compressed using bzip2
799 "tbz2": tar archive, compressed using bzip2
800 "tgz": tar archive, compressed using gzip
800 "tgz": tar archive, compressed using gzip
801 "uzip": zip archive, uncompressed
801 "uzip": zip archive, uncompressed
802 "zip": zip archive, compressed using deflate
802 "zip": zip archive, compressed using deflate
803
803
804 The exact name of the destination archive or directory is given
804 The exact name of the destination archive or directory is given
805 using a format string; see "hg help export" for details.
805 using a format string; see "hg help export" for details.
806
806
807 Each member added to an archive file has a directory prefix
807 Each member added to an archive file has a directory prefix
808 prepended. Use "-p" to specify a format string for the prefix.
808 prepended. Use "-p" to specify a format string for the prefix.
809 The default is the basename of the archive, with suffixes removed.
809 The default is the basename of the archive, with suffixes removed.
810 '''
810 '''
811
811
812 if opts['rev']:
812 if opts['rev']:
813 node = repo.lookup(opts['rev'])
813 node = repo.lookup(opts['rev'])
814 else:
814 else:
815 node, p2 = repo.dirstate.parents()
815 node, p2 = repo.dirstate.parents()
816 if p2 != nullid:
816 if p2 != nullid:
817 raise util.Abort(_('uncommitted merge - please provide a '
817 raise util.Abort(_('uncommitted merge - please provide a '
818 'specific revision'))
818 'specific revision'))
819
819
820 dest = make_filename(repo, dest, node)
820 dest = make_filename(repo, dest, node)
821 if os.path.realpath(dest) == repo.root:
821 if os.path.realpath(dest) == repo.root:
822 raise util.Abort(_('repository root cannot be destination'))
822 raise util.Abort(_('repository root cannot be destination'))
823 dummy, matchfn, dummy = matchpats(repo, [], opts)
823 dummy, matchfn, dummy = matchpats(repo, [], opts)
824 kind = opts.get('type') or 'files'
824 kind = opts.get('type') or 'files'
825 prefix = opts['prefix']
825 prefix = opts['prefix']
826 if dest == '-':
826 if dest == '-':
827 if kind == 'files':
827 if kind == 'files':
828 raise util.Abort(_('cannot archive plain files to stdout'))
828 raise util.Abort(_('cannot archive plain files to stdout'))
829 dest = sys.stdout
829 dest = sys.stdout
830 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
830 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
831 prefix = make_filename(repo, prefix, node)
831 prefix = make_filename(repo, prefix, node)
832 archival.archive(repo, dest, node, kind, not opts['no_decode'],
832 archival.archive(repo, dest, node, kind, not opts['no_decode'],
833 matchfn, prefix)
833 matchfn, prefix)
834
834
835 def backout(ui, repo, rev, **opts):
835 def backout(ui, repo, rev, **opts):
836 '''reverse effect of earlier changeset
836 '''reverse effect of earlier changeset
837
837
838 Commit the backed out changes as a new changeset. The new
838 Commit the backed out changes as a new changeset. The new
839 changeset is a child of the backed out changeset.
839 changeset is a child of the backed out changeset.
840
840
841 If you back out a changeset other than the tip, a new head is
841 If you back out a changeset other than the tip, a new head is
842 created. This head is the parent of the working directory. If
842 created. This head is the parent of the working directory. If
843 you back out an old changeset, your working directory will appear
843 you back out an old changeset, your working directory will appear
844 old after the backout. You should merge the backout changeset
844 old after the backout. You should merge the backout changeset
845 with another head.
845 with another head.
846
846
847 The --merge option remembers the parent of the working directory
847 The --merge option remembers the parent of the working directory
848 before starting the backout, then merges the new head with that
848 before starting the backout, then merges the new head with that
849 changeset afterwards. This saves you from doing the merge by
849 changeset afterwards. This saves you from doing the merge by
850 hand. The result of this merge is not committed, as for a normal
850 hand. The result of this merge is not committed, as for a normal
851 merge.'''
851 merge.'''
852
852
853 bail_if_changed(repo)
853 bail_if_changed(repo)
854 op1, op2 = repo.dirstate.parents()
854 op1, op2 = repo.dirstate.parents()
855 if op2 != nullid:
855 if op2 != nullid:
856 raise util.Abort(_('outstanding uncommitted merge'))
856 raise util.Abort(_('outstanding uncommitted merge'))
857 node = repo.lookup(rev)
857 node = repo.lookup(rev)
858 parent, p2 = repo.changelog.parents(node)
858 parent, p2 = repo.changelog.parents(node)
859 if parent == nullid:
859 if parent == nullid:
860 raise util.Abort(_('cannot back out a change with no parents'))
860 raise util.Abort(_('cannot back out a change with no parents'))
861 if p2 != nullid:
861 if p2 != nullid:
862 raise util.Abort(_('cannot back out a merge'))
862 raise util.Abort(_('cannot back out a merge'))
863 repo.update(node, force=True, show_stats=False)
863 repo.update(node, force=True, show_stats=False)
864 revert_opts = opts.copy()
864 revert_opts = opts.copy()
865 revert_opts['rev'] = hex(parent)
865 revert_opts['rev'] = hex(parent)
866 revert(ui, repo, **revert_opts)
866 revert(ui, repo, **revert_opts)
867 commit_opts = opts.copy()
867 commit_opts = opts.copy()
868 commit_opts['addremove'] = False
868 commit_opts['addremove'] = False
869 if not commit_opts['message'] and not commit_opts['logfile']:
869 if not commit_opts['message'] and not commit_opts['logfile']:
870 commit_opts['message'] = _("Backed out changeset %s") % (hex(node))
870 commit_opts['message'] = _("Backed out changeset %s") % (hex(node))
871 commit_opts['force_editor'] = True
871 commit_opts['force_editor'] = True
872 commit(ui, repo, **commit_opts)
872 commit(ui, repo, **commit_opts)
873 def nice(node):
873 def nice(node):
874 return '%d:%s' % (repo.changelog.rev(node), short(node))
874 return '%d:%s' % (repo.changelog.rev(node), short(node))
875 ui.status(_('changeset %s backs out changeset %s\n') %
875 ui.status(_('changeset %s backs out changeset %s\n') %
876 (nice(repo.changelog.tip()), nice(node)))
876 (nice(repo.changelog.tip()), nice(node)))
877 if op1 != node:
877 if op1 != node:
878 if opts['merge']:
878 if opts['merge']:
879 ui.status(_('merging with changeset %s\n') % nice(op1))
879 ui.status(_('merging with changeset %s\n') % nice(op1))
880 doupdate(ui, repo, hex(op1), **opts)
880 doupdate(ui, repo, hex(op1), **opts)
881 else:
881 else:
882 ui.status(_('the backout changeset is a new head - '
882 ui.status(_('the backout changeset is a new head - '
883 'do not forget to merge\n'))
883 'do not forget to merge\n'))
884 ui.status(_('(use "backout -m" if you want to auto-merge)\n'))
884 ui.status(_('(use "backout -m" if you want to auto-merge)\n'))
885
885
886 def bundle(ui, repo, fname, dest=None, **opts):
886 def bundle(ui, repo, fname, dest=None, **opts):
887 """create a changegroup file
887 """create a changegroup file
888
888
889 Generate a compressed changegroup file collecting all changesets
889 Generate a compressed changegroup file collecting all changesets
890 not found in the other repository.
890 not found in the other repository.
891
891
892 This file can then be transferred using conventional means and
892 This file can then be transferred using conventional means and
893 applied to another repository with the unbundle command. This is
893 applied to another repository with the unbundle command. This is
894 useful when native push and pull are not available or when
894 useful when native push and pull are not available or when
895 exporting an entire repository is undesirable. The standard file
895 exporting an entire repository is undesirable. The standard file
896 extension is ".hg".
896 extension is ".hg".
897
897
898 Unlike import/export, this exactly preserves all changeset
898 Unlike import/export, this exactly preserves all changeset
899 contents including permissions, rename data, and revision history.
899 contents including permissions, rename data, and revision history.
900 """
900 """
901 dest = ui.expandpath(dest or 'default-push', dest or 'default')
901 dest = ui.expandpath(dest or 'default-push', dest or 'default')
902 other = hg.repository(ui, dest)
902 other = hg.repository(ui, dest)
903 o = repo.findoutgoing(other, force=opts['force'])
903 o = repo.findoutgoing(other, force=opts['force'])
904 cg = repo.changegroup(o, 'bundle')
904 cg = repo.changegroup(o, 'bundle')
905 write_bundle(cg, fname)
905 write_bundle(cg, fname)
906
906
907 def cat(ui, repo, file1, *pats, **opts):
907 def cat(ui, repo, file1, *pats, **opts):
908 """output the latest or given revisions of files
908 """output the latest or given revisions of files
909
909
910 Print the specified files as they were at the given revision.
910 Print the specified files as they were at the given revision.
911 If no revision is given then the tip is used.
911 If no revision is given then the tip is used.
912
912
913 Output may be to a file, in which case the name of the file is
913 Output may be to a file, in which case the name of the file is
914 given using a format string. The formatting rules are the same as
914 given using a format string. The formatting rules are the same as
915 for the export command, with the following additions:
915 for the export command, with the following additions:
916
916
917 %s basename of file being printed
917 %s basename of file being printed
918 %d dirname of file being printed, or '.' if in repo root
918 %d dirname of file being printed, or '.' if in repo root
919 %p root-relative path name of file being printed
919 %p root-relative path name of file being printed
920 """
920 """
921 ctx = repo.changectx(opts['rev'] or -1)
921 ctx = repo.changectx(opts['rev'] or -1)
922 for src, abs, rel, exact in walk(repo, (file1,) + pats, opts, ctx.node()):
922 for src, abs, rel, exact in walk(repo, (file1,) + pats, opts, ctx.node()):
923 fp = make_file(repo, opts['output'], ctx.node(), pathname=abs)
923 fp = make_file(repo, opts['output'], ctx.node(), pathname=abs)
924 fp.write(ctx.filectx(abs).data())
924 fp.write(ctx.filectx(abs).data())
925
925
926 def clone(ui, source, dest=None, **opts):
926 def clone(ui, source, dest=None, **opts):
927 """make a copy of an existing repository
927 """make a copy of an existing repository
928
928
929 Create a copy of an existing repository in a new directory.
929 Create a copy of an existing repository in a new directory.
930
930
931 If no destination directory name is specified, it defaults to the
931 If no destination directory name is specified, it defaults to the
932 basename of the source.
932 basename of the source.
933
933
934 The location of the source is added to the new repository's
934 The location of the source is added to the new repository's
935 .hg/hgrc file, as the default to be used for future pulls.
935 .hg/hgrc file, as the default to be used for future pulls.
936
936
937 For efficiency, hardlinks are used for cloning whenever the source
937 For efficiency, hardlinks are used for cloning whenever the source
938 and destination are on the same filesystem. Some filesystems,
938 and destination are on the same filesystem. Some filesystems,
939 such as AFS, implement hardlinking incorrectly, but do not report
939 such as AFS, implement hardlinking incorrectly, but do not report
940 errors. In these cases, use the --pull option to avoid
940 errors. In these cases, use the --pull option to avoid
941 hardlinking.
941 hardlinking.
942
942
943 See pull for valid source format details.
943 See pull for valid source format details.
944 """
944 """
945 if dest is None:
945 if dest is None:
946 dest = os.path.basename(os.path.normpath(source))
946 dest = os.path.basename(os.path.normpath(source))
947
947
948 if os.path.exists(dest):
948 if os.path.exists(dest):
949 raise util.Abort(_("destination '%s' already exists"), dest)
949 raise util.Abort(_("destination '%s' already exists"), dest)
950
950
951 class Dircleanup(object):
951 class Dircleanup(object):
952 def __init__(self, dir_):
952 def __init__(self, dir_):
953 self.rmtree = shutil.rmtree
953 self.rmtree = shutil.rmtree
954 self.dir_ = dir_
954 self.dir_ = dir_
955 def close(self):
955 def close(self):
956 self.dir_ = None
956 self.dir_ = None
957 def __del__(self):
957 def __del__(self):
958 if self.dir_:
958 if self.dir_:
959 self.rmtree(self.dir_, True)
959 self.rmtree(self.dir_, True)
960
960
961 if opts['ssh']:
961 if opts['ssh']:
962 ui.setconfig("ui", "ssh", opts['ssh'])
962 ui.setconfig("ui", "ssh", opts['ssh'])
963 if opts['remotecmd']:
963 if opts['remotecmd']:
964 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
964 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
965
965
966 source = ui.expandpath(source)
966 source = ui.expandpath(source)
967 src_repo = hg.repository(ui, source)
967 src_repo = hg.repository(ui, source)
968
968
969 dest_repo = None
969 dest_repo = None
970 try:
970 try:
971 dest_repo = hg.repository(ui, dest)
971 dest_repo = hg.repository(ui, dest)
972 raise util.Abort(_("destination '%s' already exists." % dest))
972 raise util.Abort(_("destination '%s' already exists." % dest))
973 except hg.RepoError:
973 except hg.RepoError:
974 dest_repo = hg.repository(ui, dest, create=1)
974 dest_repo = hg.repository(ui, dest, create=1)
975
975
976 dest_path = None
976 dest_path = None
977 d = None
977 d = None
978 if dest_repo.local():
978 if dest_repo.local():
979 dest_path = os.path.realpath(dest)
979 dest_path = os.path.realpath(dest)
980 d = Dircleanup(dest_path)
980 d = Dircleanup(dest_path)
981
981
982 abspath = source
982 abspath = source
983 copy = False
983 copy = False
984 if src_repo.local() and dest_repo.local():
984 if src_repo.local() and dest_repo.local():
985 abspath = os.path.abspath(source)
985 abspath = os.path.abspath(source)
986 if not opts['pull'] and not opts['rev']:
986 if not opts['pull'] and not opts['rev']:
987 copy = True
987 copy = True
988
988
989 if copy:
989 if copy:
990 try:
990 try:
991 # we use a lock here because if we race with commit, we
991 # we use a lock here because if we race with commit, we
992 # can end up with extra data in the cloned revlogs that's
992 # can end up with extra data in the cloned revlogs that's
993 # not pointed to by changesets, thus causing verify to
993 # not pointed to by changesets, thus causing verify to
994 # fail
994 # fail
995 l1 = src_repo.lock()
995 l1 = src_repo.lock()
996 except lock.LockException:
996 except lock.LockException:
997 copy = False
997 copy = False
998
998
999 if copy:
999 if copy:
1000 # we lock here to avoid premature writing to the target
1000 # we lock here to avoid premature writing to the target
1001 l2 = lock.lock(os.path.join(dest_path, ".hg", "lock"))
1001 l2 = lock.lock(os.path.join(dest_path, ".hg", "lock"))
1002
1002
1003 # we need to remove the (empty) data dir in dest so copyfiles can do it's work
1003 # we need to remove the (empty) data dir in dest so copyfiles can do it's work
1004 os.rmdir( os.path.join(dest_path, ".hg", "data") )
1004 os.rmdir( os.path.join(dest_path, ".hg", "data") )
1005 files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
1005 files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
1006 for f in files.split():
1006 for f in files.split():
1007 src = os.path.join(source, ".hg", f)
1007 src = os.path.join(source, ".hg", f)
1008 dst = os.path.join(dest_path, ".hg", f)
1008 dst = os.path.join(dest_path, ".hg", f)
1009 try:
1009 try:
1010 util.copyfiles(src, dst)
1010 util.copyfiles(src, dst)
1011 except OSError, inst:
1011 except OSError, inst:
1012 if inst.errno != errno.ENOENT:
1012 if inst.errno != errno.ENOENT:
1013 raise
1013 raise
1014
1014
1015 # we need to re-init the repo after manually copying the data into it
1015 # we need to re-init the repo after manually copying the data into it
1016 dest_repo = hg.repository(ui, dest)
1016 dest_repo = hg.repository(ui, dest)
1017
1017
1018 else:
1018 else:
1019 revs = None
1019 revs = None
1020 if opts['rev']:
1020 if opts['rev']:
1021 if not src_repo.local():
1021 if not src_repo.local():
1022 error = _("clone -r not supported yet for remote repositories.")
1022 error = _("clone -r not supported yet for remote repositories.")
1023 raise util.Abort(error)
1023 raise util.Abort(error)
1024 else:
1024 else:
1025 revs = [src_repo.lookup(rev) for rev in opts['rev']]
1025 revs = [src_repo.lookup(rev) for rev in opts['rev']]
1026
1026
1027 if dest_repo.local():
1027 if dest_repo.local():
1028 dest_repo.pull(src_repo, heads = revs)
1028 dest_repo.pull(src_repo, heads = revs)
1029 elif src_repo.local():
1029 elif src_repo.local():
1030 src_repo.push(dest_repo, revs = revs)
1030 src_repo.push(dest_repo, revs = revs)
1031 else:
1031 else:
1032 error = _("clone from remote to remote not supported.")
1032 error = _("clone from remote to remote not supported.")
1033 raise util.Abort(error)
1033 raise util.Abort(error)
1034
1034
1035 if dest_repo.local():
1035 if dest_repo.local():
1036 f = dest_repo.opener("hgrc", "w", text=True)
1036 f = dest_repo.opener("hgrc", "w", text=True)
1037 f.write("[paths]\n")
1037 f.write("[paths]\n")
1038 f.write("default = %s\n" % abspath)
1038 f.write("default = %s\n" % abspath)
1039 f.close()
1039 f.close()
1040
1040
1041 if not opts['noupdate']:
1041 if not opts['noupdate']:
1042 doupdate(dest_repo.ui, dest_repo)
1042 doupdate(dest_repo.ui, dest_repo)
1043
1043
1044 if d:
1044 if d:
1045 d.close()
1045 d.close()
1046
1046
1047 def commit(ui, repo, *pats, **opts):
1047 def commit(ui, repo, *pats, **opts):
1048 """commit the specified files or all outstanding changes
1048 """commit the specified files or all outstanding changes
1049
1049
1050 Commit changes to the given files into the repository.
1050 Commit changes to the given files into the repository.
1051
1051
1052 If a list of files is omitted, all changes reported by "hg status"
1052 If a list of files is omitted, all changes reported by "hg status"
1053 will be committed.
1053 will be committed.
1054
1054
1055 If no commit message is specified, the editor configured in your hgrc
1055 If no commit message is specified, the editor configured in your hgrc
1056 or in the EDITOR environment variable is started to enter a message.
1056 or in the EDITOR environment variable is started to enter a message.
1057 """
1057 """
1058 message = opts['message']
1058 message = opts['message']
1059 logfile = opts['logfile']
1059 logfile = opts['logfile']
1060
1060
1061 if message and logfile:
1061 if message and logfile:
1062 raise util.Abort(_('options --message and --logfile are mutually '
1062 raise util.Abort(_('options --message and --logfile are mutually '
1063 'exclusive'))
1063 'exclusive'))
1064 if not message and logfile:
1064 if not message and logfile:
1065 try:
1065 try:
1066 if logfile == '-':
1066 if logfile == '-':
1067 message = sys.stdin.read()
1067 message = sys.stdin.read()
1068 else:
1068 else:
1069 message = open(logfile).read()
1069 message = open(logfile).read()
1070 except IOError, inst:
1070 except IOError, inst:
1071 raise util.Abort(_("can't read commit message '%s': %s") %
1071 raise util.Abort(_("can't read commit message '%s': %s") %
1072 (logfile, inst.strerror))
1072 (logfile, inst.strerror))
1073
1073
1074 if opts['addremove']:
1074 if opts['addremove']:
1075 addremove_lock(ui, repo, pats, opts)
1075 addremove_lock(ui, repo, pats, opts)
1076 fns, match, anypats = matchpats(repo, pats, opts)
1076 fns, match, anypats = matchpats(repo, pats, opts)
1077 if pats:
1077 if pats:
1078 modified, added, removed, deleted, unknown = (
1078 modified, added, removed, deleted, unknown = (
1079 repo.changes(files=fns, match=match))
1079 repo.changes(files=fns, match=match))
1080 files = modified + added + removed
1080 files = modified + added + removed
1081 else:
1081 else:
1082 files = []
1082 files = []
1083 try:
1083 try:
1084 repo.commit(files, message, opts['user'], opts['date'], match,
1084 repo.commit(files, message, opts['user'], opts['date'], match,
1085 force_editor=opts.get('force_editor'))
1085 force_editor=opts.get('force_editor'))
1086 except ValueError, inst:
1086 except ValueError, inst:
1087 raise util.Abort(str(inst))
1087 raise util.Abort(str(inst))
1088
1088
1089 def docopy(ui, repo, pats, opts, wlock):
1089 def docopy(ui, repo, pats, opts, wlock):
1090 # called with the repo lock held
1090 # called with the repo lock held
1091 cwd = repo.getcwd()
1091 cwd = repo.getcwd()
1092 errors = 0
1092 errors = 0
1093 copied = []
1093 copied = []
1094 targets = {}
1094 targets = {}
1095
1095
1096 def okaytocopy(abs, rel, exact):
1096 def okaytocopy(abs, rel, exact):
1097 reasons = {'?': _('is not managed'),
1097 reasons = {'?': _('is not managed'),
1098 'a': _('has been marked for add'),
1098 'a': _('has been marked for add'),
1099 'r': _('has been marked for remove')}
1099 'r': _('has been marked for remove')}
1100 state = repo.dirstate.state(abs)
1100 state = repo.dirstate.state(abs)
1101 reason = reasons.get(state)
1101 reason = reasons.get(state)
1102 if reason:
1102 if reason:
1103 if state == 'a':
1103 if state == 'a':
1104 origsrc = repo.dirstate.copied(abs)
1104 origsrc = repo.dirstate.copied(abs)
1105 if origsrc is not None:
1105 if origsrc is not None:
1106 return origsrc
1106 return origsrc
1107 if exact:
1107 if exact:
1108 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
1108 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
1109 else:
1109 else:
1110 return abs
1110 return abs
1111
1111
1112 def copy(origsrc, abssrc, relsrc, target, exact):
1112 def copy(origsrc, abssrc, relsrc, target, exact):
1113 abstarget = util.canonpath(repo.root, cwd, target)
1113 abstarget = util.canonpath(repo.root, cwd, target)
1114 reltarget = util.pathto(cwd, abstarget)
1114 reltarget = util.pathto(cwd, abstarget)
1115 prevsrc = targets.get(abstarget)
1115 prevsrc = targets.get(abstarget)
1116 if prevsrc is not None:
1116 if prevsrc is not None:
1117 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1117 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1118 (reltarget, abssrc, prevsrc))
1118 (reltarget, abssrc, prevsrc))
1119 return
1119 return
1120 if (not opts['after'] and os.path.exists(reltarget) or
1120 if (not opts['after'] and os.path.exists(reltarget) or
1121 opts['after'] and repo.dirstate.state(abstarget) not in '?r'):
1121 opts['after'] and repo.dirstate.state(abstarget) not in '?r'):
1122 if not opts['force']:
1122 if not opts['force']:
1123 ui.warn(_('%s: not overwriting - file exists\n') %
1123 ui.warn(_('%s: not overwriting - file exists\n') %
1124 reltarget)
1124 reltarget)
1125 return
1125 return
1126 if not opts['after'] and not opts.get('dry_run'):
1126 if not opts['after'] and not opts.get('dry_run'):
1127 os.unlink(reltarget)
1127 os.unlink(reltarget)
1128 if opts['after']:
1128 if opts['after']:
1129 if not os.path.exists(reltarget):
1129 if not os.path.exists(reltarget):
1130 return
1130 return
1131 else:
1131 else:
1132 targetdir = os.path.dirname(reltarget) or '.'
1132 targetdir = os.path.dirname(reltarget) or '.'
1133 if not os.path.isdir(targetdir) and not opts.get('dry_run'):
1133 if not os.path.isdir(targetdir) and not opts.get('dry_run'):
1134 os.makedirs(targetdir)
1134 os.makedirs(targetdir)
1135 try:
1135 try:
1136 restore = repo.dirstate.state(abstarget) == 'r'
1136 restore = repo.dirstate.state(abstarget) == 'r'
1137 if restore and not opts.get('dry_run'):
1137 if restore and not opts.get('dry_run'):
1138 repo.undelete([abstarget], wlock)
1138 repo.undelete([abstarget], wlock)
1139 try:
1139 try:
1140 if not opts.get('dry_run'):
1140 if not opts.get('dry_run'):
1141 shutil.copyfile(relsrc, reltarget)
1141 shutil.copyfile(relsrc, reltarget)
1142 shutil.copymode(relsrc, reltarget)
1142 shutil.copymode(relsrc, reltarget)
1143 restore = False
1143 restore = False
1144 finally:
1144 finally:
1145 if restore:
1145 if restore:
1146 repo.remove([abstarget], wlock)
1146 repo.remove([abstarget], wlock)
1147 except shutil.Error, inst:
1147 except shutil.Error, inst:
1148 raise util.Abort(str(inst))
1148 raise util.Abort(str(inst))
1149 except IOError, inst:
1149 except IOError, inst:
1150 if inst.errno == errno.ENOENT:
1150 if inst.errno == errno.ENOENT:
1151 ui.warn(_('%s: deleted in working copy\n') % relsrc)
1151 ui.warn(_('%s: deleted in working copy\n') % relsrc)
1152 else:
1152 else:
1153 ui.warn(_('%s: cannot copy - %s\n') %
1153 ui.warn(_('%s: cannot copy - %s\n') %
1154 (relsrc, inst.strerror))
1154 (relsrc, inst.strerror))
1155 errors += 1
1155 errors += 1
1156 return
1156 return
1157 if ui.verbose or not exact:
1157 if ui.verbose or not exact:
1158 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1158 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1159 targets[abstarget] = abssrc
1159 targets[abstarget] = abssrc
1160 if abstarget != origsrc and not opts.get('dry_run'):
1160 if abstarget != origsrc and not opts.get('dry_run'):
1161 repo.copy(origsrc, abstarget, wlock)
1161 repo.copy(origsrc, abstarget, wlock)
1162 copied.append((abssrc, relsrc, exact))
1162 copied.append((abssrc, relsrc, exact))
1163
1163
1164 def targetpathfn(pat, dest, srcs):
1164 def targetpathfn(pat, dest, srcs):
1165 if os.path.isdir(pat):
1165 if os.path.isdir(pat):
1166 abspfx = util.canonpath(repo.root, cwd, pat)
1166 abspfx = util.canonpath(repo.root, cwd, pat)
1167 if destdirexists:
1167 if destdirexists:
1168 striplen = len(os.path.split(abspfx)[0])
1168 striplen = len(os.path.split(abspfx)[0])
1169 else:
1169 else:
1170 striplen = len(abspfx)
1170 striplen = len(abspfx)
1171 if striplen:
1171 if striplen:
1172 striplen += len(os.sep)
1172 striplen += len(os.sep)
1173 res = lambda p: os.path.join(dest, p[striplen:])
1173 res = lambda p: os.path.join(dest, p[striplen:])
1174 elif destdirexists:
1174 elif destdirexists:
1175 res = lambda p: os.path.join(dest, os.path.basename(p))
1175 res = lambda p: os.path.join(dest, os.path.basename(p))
1176 else:
1176 else:
1177 res = lambda p: dest
1177 res = lambda p: dest
1178 return res
1178 return res
1179
1179
1180 def targetpathafterfn(pat, dest, srcs):
1180 def targetpathafterfn(pat, dest, srcs):
1181 if util.patkind(pat, None)[0]:
1181 if util.patkind(pat, None)[0]:
1182 # a mercurial pattern
1182 # a mercurial pattern
1183 res = lambda p: os.path.join(dest, os.path.basename(p))
1183 res = lambda p: os.path.join(dest, os.path.basename(p))
1184 else:
1184 else:
1185 abspfx = util.canonpath(repo.root, cwd, pat)
1185 abspfx = util.canonpath(repo.root, cwd, pat)
1186 if len(abspfx) < len(srcs[0][0]):
1186 if len(abspfx) < len(srcs[0][0]):
1187 # A directory. Either the target path contains the last
1187 # A directory. Either the target path contains the last
1188 # component of the source path or it does not.
1188 # component of the source path or it does not.
1189 def evalpath(striplen):
1189 def evalpath(striplen):
1190 score = 0
1190 score = 0
1191 for s in srcs:
1191 for s in srcs:
1192 t = os.path.join(dest, s[0][striplen:])
1192 t = os.path.join(dest, s[0][striplen:])
1193 if os.path.exists(t):
1193 if os.path.exists(t):
1194 score += 1
1194 score += 1
1195 return score
1195 return score
1196
1196
1197 striplen = len(abspfx)
1197 striplen = len(abspfx)
1198 if striplen:
1198 if striplen:
1199 striplen += len(os.sep)
1199 striplen += len(os.sep)
1200 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1200 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1201 score = evalpath(striplen)
1201 score = evalpath(striplen)
1202 striplen1 = len(os.path.split(abspfx)[0])
1202 striplen1 = len(os.path.split(abspfx)[0])
1203 if striplen1:
1203 if striplen1:
1204 striplen1 += len(os.sep)
1204 striplen1 += len(os.sep)
1205 if evalpath(striplen1) > score:
1205 if evalpath(striplen1) > score:
1206 striplen = striplen1
1206 striplen = striplen1
1207 res = lambda p: os.path.join(dest, p[striplen:])
1207 res = lambda p: os.path.join(dest, p[striplen:])
1208 else:
1208 else:
1209 # a file
1209 # a file
1210 if destdirexists:
1210 if destdirexists:
1211 res = lambda p: os.path.join(dest, os.path.basename(p))
1211 res = lambda p: os.path.join(dest, os.path.basename(p))
1212 else:
1212 else:
1213 res = lambda p: dest
1213 res = lambda p: dest
1214 return res
1214 return res
1215
1215
1216
1216
1217 pats = list(pats)
1217 pats = list(pats)
1218 if not pats:
1218 if not pats:
1219 raise util.Abort(_('no source or destination specified'))
1219 raise util.Abort(_('no source or destination specified'))
1220 if len(pats) == 1:
1220 if len(pats) == 1:
1221 raise util.Abort(_('no destination specified'))
1221 raise util.Abort(_('no destination specified'))
1222 dest = pats.pop()
1222 dest = pats.pop()
1223 destdirexists = os.path.isdir(dest)
1223 destdirexists = os.path.isdir(dest)
1224 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
1224 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
1225 raise util.Abort(_('with multiple sources, destination must be an '
1225 raise util.Abort(_('with multiple sources, destination must be an '
1226 'existing directory'))
1226 'existing directory'))
1227 if opts['after']:
1227 if opts['after']:
1228 tfn = targetpathafterfn
1228 tfn = targetpathafterfn
1229 else:
1229 else:
1230 tfn = targetpathfn
1230 tfn = targetpathfn
1231 copylist = []
1231 copylist = []
1232 for pat in pats:
1232 for pat in pats:
1233 srcs = []
1233 srcs = []
1234 for tag, abssrc, relsrc, exact in walk(repo, [pat], opts):
1234 for tag, abssrc, relsrc, exact in walk(repo, [pat], opts):
1235 origsrc = okaytocopy(abssrc, relsrc, exact)
1235 origsrc = okaytocopy(abssrc, relsrc, exact)
1236 if origsrc:
1236 if origsrc:
1237 srcs.append((origsrc, abssrc, relsrc, exact))
1237 srcs.append((origsrc, abssrc, relsrc, exact))
1238 if not srcs:
1238 if not srcs:
1239 continue
1239 continue
1240 copylist.append((tfn(pat, dest, srcs), srcs))
1240 copylist.append((tfn(pat, dest, srcs), srcs))
1241 if not copylist:
1241 if not copylist:
1242 raise util.Abort(_('no files to copy'))
1242 raise util.Abort(_('no files to copy'))
1243
1243
1244 for targetpath, srcs in copylist:
1244 for targetpath, srcs in copylist:
1245 for origsrc, abssrc, relsrc, exact in srcs:
1245 for origsrc, abssrc, relsrc, exact in srcs:
1246 copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
1246 copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
1247
1247
1248 if errors:
1248 if errors:
1249 ui.warn(_('(consider using --after)\n'))
1249 ui.warn(_('(consider using --after)\n'))
1250 return errors, copied
1250 return errors, copied
1251
1251
1252 def copy(ui, repo, *pats, **opts):
1252 def copy(ui, repo, *pats, **opts):
1253 """mark files as copied for the next commit
1253 """mark files as copied for the next commit
1254
1254
1255 Mark dest as having copies of source files. If dest is a
1255 Mark dest as having copies of source files. If dest is a
1256 directory, copies are put in that directory. If dest is a file,
1256 directory, copies are put in that directory. If dest is a file,
1257 there can only be one source.
1257 there can only be one source.
1258
1258
1259 By default, this command copies the contents of files as they
1259 By default, this command copies the contents of files as they
1260 stand in the working directory. If invoked with --after, the
1260 stand in the working directory. If invoked with --after, the
1261 operation is recorded, but no copying is performed.
1261 operation is recorded, but no copying is performed.
1262
1262
1263 This command takes effect in the next commit.
1263 This command takes effect in the next commit.
1264
1264
1265 NOTE: This command should be treated as experimental. While it
1265 NOTE: This command should be treated as experimental. While it
1266 should properly record copied files, this information is not yet
1266 should properly record copied files, this information is not yet
1267 fully used by merge, nor fully reported by log.
1267 fully used by merge, nor fully reported by log.
1268 """
1268 """
1269 wlock = repo.wlock(0)
1269 wlock = repo.wlock(0)
1270 errs, copied = docopy(ui, repo, pats, opts, wlock)
1270 errs, copied = docopy(ui, repo, pats, opts, wlock)
1271 return errs
1271 return errs
1272
1272
1273 def debugancestor(ui, index, rev1, rev2):
1273 def debugancestor(ui, index, rev1, rev2):
1274 """find the ancestor revision of two revisions in a given index"""
1274 """find the ancestor revision of two revisions in a given index"""
1275 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index, "", 0)
1275 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index, "", 0)
1276 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
1276 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
1277 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1277 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1278
1278
1279 def debugcomplete(ui, cmd='', **opts):
1279 def debugcomplete(ui, cmd='', **opts):
1280 """returns the completion list associated with the given command"""
1280 """returns the completion list associated with the given command"""
1281
1281
1282 if opts['options']:
1282 if opts['options']:
1283 options = []
1283 options = []
1284 otables = [globalopts]
1284 otables = [globalopts]
1285 if cmd:
1285 if cmd:
1286 aliases, entry = findcmd(cmd)
1286 aliases, entry = findcmd(cmd)
1287 otables.append(entry[1])
1287 otables.append(entry[1])
1288 for t in otables:
1288 for t in otables:
1289 for o in t:
1289 for o in t:
1290 if o[0]:
1290 if o[0]:
1291 options.append('-%s' % o[0])
1291 options.append('-%s' % o[0])
1292 options.append('--%s' % o[1])
1292 options.append('--%s' % o[1])
1293 ui.write("%s\n" % "\n".join(options))
1293 ui.write("%s\n" % "\n".join(options))
1294 return
1294 return
1295
1295
1296 clist = findpossible(cmd).keys()
1296 clist = findpossible(cmd).keys()
1297 clist.sort()
1297 clist.sort()
1298 ui.write("%s\n" % "\n".join(clist))
1298 ui.write("%s\n" % "\n".join(clist))
1299
1299
1300 def debugrebuildstate(ui, repo, rev=None):
1300 def debugrebuildstate(ui, repo, rev=None):
1301 """rebuild the dirstate as it would look like for the given revision"""
1301 """rebuild the dirstate as it would look like for the given revision"""
1302 if not rev:
1302 if not rev:
1303 rev = repo.changelog.tip()
1303 rev = repo.changelog.tip()
1304 else:
1304 else:
1305 rev = repo.lookup(rev)
1305 rev = repo.lookup(rev)
1306 change = repo.changelog.read(rev)
1306 change = repo.changelog.read(rev)
1307 n = change[0]
1307 n = change[0]
1308 files = repo.manifest.readflags(n)
1308 files = repo.manifest.readflags(n)
1309 wlock = repo.wlock()
1309 wlock = repo.wlock()
1310 repo.dirstate.rebuild(rev, files.iteritems())
1310 repo.dirstate.rebuild(rev, files.iteritems())
1311
1311
1312 def debugcheckstate(ui, repo):
1312 def debugcheckstate(ui, repo):
1313 """validate the correctness of the current dirstate"""
1313 """validate the correctness of the current dirstate"""
1314 parent1, parent2 = repo.dirstate.parents()
1314 parent1, parent2 = repo.dirstate.parents()
1315 repo.dirstate.read()
1315 repo.dirstate.read()
1316 dc = repo.dirstate.map
1316 dc = repo.dirstate.map
1317 keys = dc.keys()
1317 keys = dc.keys()
1318 keys.sort()
1318 keys.sort()
1319 m1n = repo.changelog.read(parent1)[0]
1319 m1n = repo.changelog.read(parent1)[0]
1320 m2n = repo.changelog.read(parent2)[0]
1320 m2n = repo.changelog.read(parent2)[0]
1321 m1 = repo.manifest.read(m1n)
1321 m1 = repo.manifest.read(m1n)
1322 m2 = repo.manifest.read(m2n)
1322 m2 = repo.manifest.read(m2n)
1323 errors = 0
1323 errors = 0
1324 for f in dc:
1324 for f in dc:
1325 state = repo.dirstate.state(f)
1325 state = repo.dirstate.state(f)
1326 if state in "nr" and f not in m1:
1326 if state in "nr" and f not in m1:
1327 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1327 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1328 errors += 1
1328 errors += 1
1329 if state in "a" and f in m1:
1329 if state in "a" and f in m1:
1330 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1330 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1331 errors += 1
1331 errors += 1
1332 if state in "m" and f not in m1 and f not in m2:
1332 if state in "m" and f not in m1 and f not in m2:
1333 ui.warn(_("%s in state %s, but not in either manifest\n") %
1333 ui.warn(_("%s in state %s, but not in either manifest\n") %
1334 (f, state))
1334 (f, state))
1335 errors += 1
1335 errors += 1
1336 for f in m1:
1336 for f in m1:
1337 state = repo.dirstate.state(f)
1337 state = repo.dirstate.state(f)
1338 if state not in "nrm":
1338 if state not in "nrm":
1339 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1339 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1340 errors += 1
1340 errors += 1
1341 if errors:
1341 if errors:
1342 error = _(".hg/dirstate inconsistent with current parent's manifest")
1342 error = _(".hg/dirstate inconsistent with current parent's manifest")
1343 raise util.Abort(error)
1343 raise util.Abort(error)
1344
1344
1345 def debugconfig(ui, repo, *values):
1345 def debugconfig(ui, repo, *values):
1346 """show combined config settings from all hgrc files
1346 """show combined config settings from all hgrc files
1347
1347
1348 With no args, print names and values of all config items.
1348 With no args, print names and values of all config items.
1349
1349
1350 With one arg of the form section.name, print just the value of
1350 With one arg of the form section.name, print just the value of
1351 that config item.
1351 that config item.
1352
1352
1353 With multiple args, print names and values of all config items
1353 With multiple args, print names and values of all config items
1354 with matching section names."""
1354 with matching section names."""
1355
1355
1356 if values:
1356 if values:
1357 if len([v for v in values if '.' in v]) > 1:
1357 if len([v for v in values if '.' in v]) > 1:
1358 raise util.Abort(_('only one config item permitted'))
1358 raise util.Abort(_('only one config item permitted'))
1359 for section, name, value in ui.walkconfig():
1359 for section, name, value in ui.walkconfig():
1360 sectname = section + '.' + name
1360 sectname = section + '.' + name
1361 if values:
1361 if values:
1362 for v in values:
1362 for v in values:
1363 if v == section:
1363 if v == section:
1364 ui.write('%s=%s\n' % (sectname, value))
1364 ui.write('%s=%s\n' % (sectname, value))
1365 elif v == sectname:
1365 elif v == sectname:
1366 ui.write(value, '\n')
1366 ui.write(value, '\n')
1367 else:
1367 else:
1368 ui.write('%s=%s\n' % (sectname, value))
1368 ui.write('%s=%s\n' % (sectname, value))
1369
1369
1370 def debugsetparents(ui, repo, rev1, rev2=None):
1370 def debugsetparents(ui, repo, rev1, rev2=None):
1371 """manually set the parents of the current working directory
1371 """manually set the parents of the current working directory
1372
1372
1373 This is useful for writing repository conversion tools, but should
1373 This is useful for writing repository conversion tools, but should
1374 be used with care.
1374 be used with care.
1375 """
1375 """
1376
1376
1377 if not rev2:
1377 if not rev2:
1378 rev2 = hex(nullid)
1378 rev2 = hex(nullid)
1379
1379
1380 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1380 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1381
1381
1382 def debugstate(ui, repo):
1382 def debugstate(ui, repo):
1383 """show the contents of the current dirstate"""
1383 """show the contents of the current dirstate"""
1384 repo.dirstate.read()
1384 repo.dirstate.read()
1385 dc = repo.dirstate.map
1385 dc = repo.dirstate.map
1386 keys = dc.keys()
1386 keys = dc.keys()
1387 keys.sort()
1387 keys.sort()
1388 for file_ in keys:
1388 for file_ in keys:
1389 ui.write("%c %3o %10d %s %s\n"
1389 ui.write("%c %3o %10d %s %s\n"
1390 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
1390 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
1391 time.strftime("%x %X",
1391 time.strftime("%x %X",
1392 time.localtime(dc[file_][3])), file_))
1392 time.localtime(dc[file_][3])), file_))
1393 for f in repo.dirstate.copies:
1393 for f in repo.dirstate.copies:
1394 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copies[f], f))
1394 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copies[f], f))
1395
1395
1396 def debugdata(ui, file_, rev):
1396 def debugdata(ui, file_, rev):
1397 """dump the contents of an data file revision"""
1397 """dump the contents of an data file revision"""
1398 r = revlog.revlog(util.opener(os.getcwd(), audit=False),
1398 r = revlog.revlog(util.opener(os.getcwd(), audit=False),
1399 file_[:-2] + ".i", file_, 0)
1399 file_[:-2] + ".i", file_, 0)
1400 try:
1400 try:
1401 ui.write(r.revision(r.lookup(rev)))
1401 ui.write(r.revision(r.lookup(rev)))
1402 except KeyError:
1402 except KeyError:
1403 raise util.Abort(_('invalid revision identifier %s'), rev)
1403 raise util.Abort(_('invalid revision identifier %s'), rev)
1404
1404
1405 def debugindex(ui, file_):
1405 def debugindex(ui, file_):
1406 """dump the contents of an index file"""
1406 """dump the contents of an index file"""
1407 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1407 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1408 ui.write(" rev offset length base linkrev" +
1408 ui.write(" rev offset length base linkrev" +
1409 " nodeid p1 p2\n")
1409 " nodeid p1 p2\n")
1410 for i in range(r.count()):
1410 for i in range(r.count()):
1411 node = r.node(i)
1411 node = r.node(i)
1412 pp = r.parents(node)
1412 pp = r.parents(node)
1413 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1413 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1414 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
1414 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
1415 short(node), short(pp[0]), short(pp[1])))
1415 short(node), short(pp[0]), short(pp[1])))
1416
1416
1417 def debugindexdot(ui, file_):
1417 def debugindexdot(ui, file_):
1418 """dump an index DAG as a .dot file"""
1418 """dump an index DAG as a .dot file"""
1419 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1419 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1420 ui.write("digraph G {\n")
1420 ui.write("digraph G {\n")
1421 for i in range(r.count()):
1421 for i in range(r.count()):
1422 node = r.node(i)
1422 node = r.node(i)
1423 pp = r.parents(node)
1423 pp = r.parents(node)
1424 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1424 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1425 if pp[1] != nullid:
1425 if pp[1] != nullid:
1426 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1426 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1427 ui.write("}\n")
1427 ui.write("}\n")
1428
1428
1429 def debugrename(ui, repo, file, rev=None):
1429 def debugrename(ui, repo, file, rev=None):
1430 """dump rename information"""
1430 """dump rename information"""
1431 r = repo.file(relpath(repo, [file])[0])
1431 r = repo.file(relpath(repo, [file])[0])
1432 if rev:
1432 if rev:
1433 try:
1433 try:
1434 # assume all revision numbers are for changesets
1434 # assume all revision numbers are for changesets
1435 n = repo.lookup(rev)
1435 n = repo.lookup(rev)
1436 change = repo.changelog.read(n)
1436 change = repo.changelog.read(n)
1437 m = repo.manifest.read(change[0])
1437 m = repo.manifest.read(change[0])
1438 n = m[relpath(repo, [file])[0]]
1438 n = m[relpath(repo, [file])[0]]
1439 except (hg.RepoError, KeyError):
1439 except (hg.RepoError, KeyError):
1440 n = r.lookup(rev)
1440 n = r.lookup(rev)
1441 else:
1441 else:
1442 n = r.tip()
1442 n = r.tip()
1443 m = r.renamed(n)
1443 m = r.renamed(n)
1444 if m:
1444 if m:
1445 ui.write(_("renamed from %s:%s\n") % (m[0], hex(m[1])))
1445 ui.write(_("renamed from %s:%s\n") % (m[0], hex(m[1])))
1446 else:
1446 else:
1447 ui.write(_("not renamed\n"))
1447 ui.write(_("not renamed\n"))
1448
1448
1449 def debugwalk(ui, repo, *pats, **opts):
1449 def debugwalk(ui, repo, *pats, **opts):
1450 """show how files match on given patterns"""
1450 """show how files match on given patterns"""
1451 items = list(walk(repo, pats, opts))
1451 items = list(walk(repo, pats, opts))
1452 if not items:
1452 if not items:
1453 return
1453 return
1454 fmt = '%%s %%-%ds %%-%ds %%s' % (
1454 fmt = '%%s %%-%ds %%-%ds %%s' % (
1455 max([len(abs) for (src, abs, rel, exact) in items]),
1455 max([len(abs) for (src, abs, rel, exact) in items]),
1456 max([len(rel) for (src, abs, rel, exact) in items]))
1456 max([len(rel) for (src, abs, rel, exact) in items]))
1457 for src, abs, rel, exact in items:
1457 for src, abs, rel, exact in items:
1458 line = fmt % (src, abs, rel, exact and 'exact' or '')
1458 line = fmt % (src, abs, rel, exact and 'exact' or '')
1459 ui.write("%s\n" % line.rstrip())
1459 ui.write("%s\n" % line.rstrip())
1460
1460
1461 def diff(ui, repo, *pats, **opts):
1461 def diff(ui, repo, *pats, **opts):
1462 """diff repository (or selected files)
1462 """diff repository (or selected files)
1463
1463
1464 Show differences between revisions for the specified files.
1464 Show differences between revisions for the specified files.
1465
1465
1466 Differences between files are shown using the unified diff format.
1466 Differences between files are shown using the unified diff format.
1467
1467
1468 When two revision arguments are given, then changes are shown
1468 When two revision arguments are given, then changes are shown
1469 between those revisions. If only one revision is specified then
1469 between those revisions. If only one revision is specified then
1470 that revision is compared to the working directory, and, when no
1470 that revision is compared to the working directory, and, when no
1471 revisions are specified, the working directory files are compared
1471 revisions are specified, the working directory files are compared
1472 to its parent.
1472 to its parent.
1473
1473
1474 Without the -a option, diff will avoid generating diffs of files
1474 Without the -a option, diff will avoid generating diffs of files
1475 it detects as binary. With -a, diff will generate a diff anyway,
1475 it detects as binary. With -a, diff will generate a diff anyway,
1476 probably with undesirable results.
1476 probably with undesirable results.
1477 """
1477 """
1478 node1, node2 = revpair(ui, repo, opts['rev'])
1478 node1, node2 = revpair(ui, repo, opts['rev'])
1479
1479
1480 fns, matchfn, anypats = matchpats(repo, pats, opts)
1480 fns, matchfn, anypats = matchpats(repo, pats, opts)
1481
1481
1482 dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn,
1482 dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn,
1483 text=opts['text'], opts=opts)
1483 text=opts['text'], opts=opts)
1484
1484
1485 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
1485 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
1486 node = repo.lookup(changeset)
1486 node = repo.lookup(changeset)
1487 parents = [p for p in repo.changelog.parents(node) if p != nullid]
1487 parents = [p for p in repo.changelog.parents(node) if p != nullid]
1488 if opts['switch_parent']:
1488 if opts['switch_parent']:
1489 parents.reverse()
1489 parents.reverse()
1490 prev = (parents and parents[0]) or nullid
1490 prev = (parents and parents[0]) or nullid
1491 change = repo.changelog.read(node)
1491 change = repo.changelog.read(node)
1492
1492
1493 fp = make_file(repo, opts['output'], node, total=total, seqno=seqno,
1493 fp = make_file(repo, opts['output'], node, total=total, seqno=seqno,
1494 revwidth=revwidth)
1494 revwidth=revwidth)
1495 if fp != sys.stdout:
1495 if fp != sys.stdout:
1496 ui.note("%s\n" % fp.name)
1496 ui.note("%s\n" % fp.name)
1497
1497
1498 fp.write("# HG changeset patch\n")
1498 fp.write("# HG changeset patch\n")
1499 fp.write("# User %s\n" % change[1])
1499 fp.write("# User %s\n" % change[1])
1500 fp.write("# Date %d %d\n" % change[2])
1500 fp.write("# Date %d %d\n" % change[2])
1501 fp.write("# Node ID %s\n" % hex(node))
1501 fp.write("# Node ID %s\n" % hex(node))
1502 fp.write("# Parent %s\n" % hex(prev))
1502 fp.write("# Parent %s\n" % hex(prev))
1503 if len(parents) > 1:
1503 if len(parents) > 1:
1504 fp.write("# Parent %s\n" % hex(parents[1]))
1504 fp.write("# Parent %s\n" % hex(parents[1]))
1505 fp.write(change[4].rstrip())
1505 fp.write(change[4].rstrip())
1506 fp.write("\n\n")
1506 fp.write("\n\n")
1507
1507
1508 dodiff(fp, ui, repo, prev, node, text=opts['text'])
1508 dodiff(fp, ui, repo, prev, node, text=opts['text'])
1509 if fp != sys.stdout:
1509 if fp != sys.stdout:
1510 fp.close()
1510 fp.close()
1511
1511
1512 def export(ui, repo, *changesets, **opts):
1512 def export(ui, repo, *changesets, **opts):
1513 """dump the header and diffs for one or more changesets
1513 """dump the header and diffs for one or more changesets
1514
1514
1515 Print the changeset header and diffs for one or more revisions.
1515 Print the changeset header and diffs for one or more revisions.
1516
1516
1517 The information shown in the changeset header is: author,
1517 The information shown in the changeset header is: author,
1518 changeset hash, parent and commit comment.
1518 changeset hash, parent and commit comment.
1519
1519
1520 Output may be to a file, in which case the name of the file is
1520 Output may be to a file, in which case the name of the file is
1521 given using a format string. The formatting rules are as follows:
1521 given using a format string. The formatting rules are as follows:
1522
1522
1523 %% literal "%" character
1523 %% literal "%" character
1524 %H changeset hash (40 bytes of hexadecimal)
1524 %H changeset hash (40 bytes of hexadecimal)
1525 %N number of patches being generated
1525 %N number of patches being generated
1526 %R changeset revision number
1526 %R changeset revision number
1527 %b basename of the exporting repository
1527 %b basename of the exporting repository
1528 %h short-form changeset hash (12 bytes of hexadecimal)
1528 %h short-form changeset hash (12 bytes of hexadecimal)
1529 %n zero-padded sequence number, starting at 1
1529 %n zero-padded sequence number, starting at 1
1530 %r zero-padded changeset revision number
1530 %r zero-padded changeset revision number
1531
1531
1532 Without the -a option, export will avoid generating diffs of files
1532 Without the -a option, export will avoid generating diffs of files
1533 it detects as binary. With -a, export will generate a diff anyway,
1533 it detects as binary. With -a, export will generate a diff anyway,
1534 probably with undesirable results.
1534 probably with undesirable results.
1535
1535
1536 With the --switch-parent option, the diff will be against the second
1536 With the --switch-parent option, the diff will be against the second
1537 parent. It can be useful to review a merge.
1537 parent. It can be useful to review a merge.
1538 """
1538 """
1539 if not changesets:
1539 if not changesets:
1540 raise util.Abort(_("export requires at least one changeset"))
1540 raise util.Abort(_("export requires at least one changeset"))
1541 seqno = 0
1541 seqno = 0
1542 revs = list(revrange(ui, repo, changesets))
1542 revs = list(revrange(ui, repo, changesets))
1543 total = len(revs)
1543 total = len(revs)
1544 revwidth = max(map(len, revs))
1544 revwidth = max(map(len, revs))
1545 msg = len(revs) > 1 and _("Exporting patches:\n") or _("Exporting patch:\n")
1545 msg = len(revs) > 1 and _("Exporting patches:\n") or _("Exporting patch:\n")
1546 ui.note(msg)
1546 ui.note(msg)
1547 for cset in revs:
1547 for cset in revs:
1548 seqno += 1
1548 seqno += 1
1549 doexport(ui, repo, cset, seqno, total, revwidth, opts)
1549 doexport(ui, repo, cset, seqno, total, revwidth, opts)
1550
1550
1551 def forget(ui, repo, *pats, **opts):
1551 def forget(ui, repo, *pats, **opts):
1552 """don't add the specified files on the next commit (DEPRECATED)
1552 """don't add the specified files on the next commit (DEPRECATED)
1553
1553
1554 (DEPRECATED)
1554 (DEPRECATED)
1555 Undo an 'hg add' scheduled for the next commit.
1555 Undo an 'hg add' scheduled for the next commit.
1556
1556
1557 This command is now deprecated and will be removed in a future
1557 This command is now deprecated and will be removed in a future
1558 release. Please use revert instead.
1558 release. Please use revert instead.
1559 """
1559 """
1560 ui.warn(_("(the forget command is deprecated; use revert instead)\n"))
1560 ui.warn(_("(the forget command is deprecated; use revert instead)\n"))
1561 forget = []
1561 forget = []
1562 for src, abs, rel, exact in walk(repo, pats, opts):
1562 for src, abs, rel, exact in walk(repo, pats, opts):
1563 if repo.dirstate.state(abs) == 'a':
1563 if repo.dirstate.state(abs) == 'a':
1564 forget.append(abs)
1564 forget.append(abs)
1565 if ui.verbose or not exact:
1565 if ui.verbose or not exact:
1566 ui.status(_('forgetting %s\n') % ((pats and rel) or abs))
1566 ui.status(_('forgetting %s\n') % ((pats and rel) or abs))
1567 repo.forget(forget)
1567 repo.forget(forget)
1568
1568
1569 def grep(ui, repo, pattern, *pats, **opts):
1569 def grep(ui, repo, pattern, *pats, **opts):
1570 """search for a pattern in specified files and revisions
1570 """search for a pattern in specified files and revisions
1571
1571
1572 Search revisions of files for a regular expression.
1572 Search revisions of files for a regular expression.
1573
1573
1574 This command behaves differently than Unix grep. It only accepts
1574 This command behaves differently than Unix grep. It only accepts
1575 Python/Perl regexps. It searches repository history, not the
1575 Python/Perl regexps. It searches repository history, not the
1576 working directory. It always prints the revision number in which
1576 working directory. It always prints the revision number in which
1577 a match appears.
1577 a match appears.
1578
1578
1579 By default, grep only prints output for the first revision of a
1579 By default, grep only prints output for the first revision of a
1580 file in which it finds a match. To get it to print every revision
1580 file in which it finds a match. To get it to print every revision
1581 that contains a change in match status ("-" for a match that
1581 that contains a change in match status ("-" for a match that
1582 becomes a non-match, or "+" for a non-match that becomes a match),
1582 becomes a non-match, or "+" for a non-match that becomes a match),
1583 use the --all flag.
1583 use the --all flag.
1584 """
1584 """
1585 reflags = 0
1585 reflags = 0
1586 if opts['ignore_case']:
1586 if opts['ignore_case']:
1587 reflags |= re.I
1587 reflags |= re.I
1588 regexp = re.compile(pattern, reflags)
1588 regexp = re.compile(pattern, reflags)
1589 sep, eol = ':', '\n'
1589 sep, eol = ':', '\n'
1590 if opts['print0']:
1590 if opts['print0']:
1591 sep = eol = '\0'
1591 sep = eol = '\0'
1592
1592
1593 fcache = {}
1593 fcache = {}
1594 def getfile(fn):
1594 def getfile(fn):
1595 if fn not in fcache:
1595 if fn not in fcache:
1596 fcache[fn] = repo.file(fn)
1596 fcache[fn] = repo.file(fn)
1597 return fcache[fn]
1597 return fcache[fn]
1598
1598
1599 def matchlines(body):
1599 def matchlines(body):
1600 begin = 0
1600 begin = 0
1601 linenum = 0
1601 linenum = 0
1602 while True:
1602 while True:
1603 match = regexp.search(body, begin)
1603 match = regexp.search(body, begin)
1604 if not match:
1604 if not match:
1605 break
1605 break
1606 mstart, mend = match.span()
1606 mstart, mend = match.span()
1607 linenum += body.count('\n', begin, mstart) + 1
1607 linenum += body.count('\n', begin, mstart) + 1
1608 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1608 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1609 lend = body.find('\n', mend)
1609 lend = body.find('\n', mend)
1610 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1610 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1611 begin = lend + 1
1611 begin = lend + 1
1612
1612
1613 class linestate(object):
1613 class linestate(object):
1614 def __init__(self, line, linenum, colstart, colend):
1614 def __init__(self, line, linenum, colstart, colend):
1615 self.line = line
1615 self.line = line
1616 self.linenum = linenum
1616 self.linenum = linenum
1617 self.colstart = colstart
1617 self.colstart = colstart
1618 self.colend = colend
1618 self.colend = colend
1619 def __eq__(self, other):
1619 def __eq__(self, other):
1620 return self.line == other.line
1620 return self.line == other.line
1621 def __hash__(self):
1621 def __hash__(self):
1622 return hash(self.line)
1622 return hash(self.line)
1623
1623
1624 matches = {}
1624 matches = {}
1625 def grepbody(fn, rev, body):
1625 def grepbody(fn, rev, body):
1626 matches[rev].setdefault(fn, {})
1626 matches[rev].setdefault(fn, {})
1627 m = matches[rev][fn]
1627 m = matches[rev][fn]
1628 for lnum, cstart, cend, line in matchlines(body):
1628 for lnum, cstart, cend, line in matchlines(body):
1629 s = linestate(line, lnum, cstart, cend)
1629 s = linestate(line, lnum, cstart, cend)
1630 m[s] = s
1630 m[s] = s
1631
1631
1632 # FIXME: prev isn't used, why ?
1632 # FIXME: prev isn't used, why ?
1633 prev = {}
1633 prev = {}
1634 ucache = {}
1634 ucache = {}
1635 def display(fn, rev, states, prevstates):
1635 def display(fn, rev, states, prevstates):
1636 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
1636 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
1637 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
1637 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
1638 counts = {'-': 0, '+': 0}
1638 counts = {'-': 0, '+': 0}
1639 filerevmatches = {}
1639 filerevmatches = {}
1640 for l in diff:
1640 for l in diff:
1641 if incrementing or not opts['all']:
1641 if incrementing or not opts['all']:
1642 change = ((l in prevstates) and '-') or '+'
1642 change = ((l in prevstates) and '-') or '+'
1643 r = rev
1643 r = rev
1644 else:
1644 else:
1645 change = ((l in states) and '-') or '+'
1645 change = ((l in states) and '-') or '+'
1646 r = prev[fn]
1646 r = prev[fn]
1647 cols = [fn, str(rev)]
1647 cols = [fn, str(rev)]
1648 if opts['line_number']:
1648 if opts['line_number']:
1649 cols.append(str(l.linenum))
1649 cols.append(str(l.linenum))
1650 if opts['all']:
1650 if opts['all']:
1651 cols.append(change)
1651 cols.append(change)
1652 if opts['user']:
1652 if opts['user']:
1653 cols.append(trimuser(ui, getchange(rev)[1], rev,
1653 cols.append(trimuser(ui, getchange(rev)[1], rev,
1654 ucache))
1654 ucache))
1655 if opts['files_with_matches']:
1655 if opts['files_with_matches']:
1656 c = (fn, rev)
1656 c = (fn, rev)
1657 if c in filerevmatches:
1657 if c in filerevmatches:
1658 continue
1658 continue
1659 filerevmatches[c] = 1
1659 filerevmatches[c] = 1
1660 else:
1660 else:
1661 cols.append(l.line)
1661 cols.append(l.line)
1662 ui.write(sep.join(cols), eol)
1662 ui.write(sep.join(cols), eol)
1663 counts[change] += 1
1663 counts[change] += 1
1664 return counts['+'], counts['-']
1664 return counts['+'], counts['-']
1665
1665
1666 fstate = {}
1666 fstate = {}
1667 skip = {}
1667 skip = {}
1668 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1668 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1669 count = 0
1669 count = 0
1670 incrementing = False
1670 incrementing = False
1671 for st, rev, fns in changeiter:
1671 for st, rev, fns in changeiter:
1672 if st == 'window':
1672 if st == 'window':
1673 incrementing = rev
1673 incrementing = rev
1674 matches.clear()
1674 matches.clear()
1675 elif st == 'add':
1675 elif st == 'add':
1676 change = repo.changelog.read(repo.lookup(str(rev)))
1676 change = repo.changelog.read(repo.lookup(str(rev)))
1677 mf = repo.manifest.read(change[0])
1677 mf = repo.manifest.read(change[0])
1678 matches[rev] = {}
1678 matches[rev] = {}
1679 for fn in fns:
1679 for fn in fns:
1680 if fn in skip:
1680 if fn in skip:
1681 continue
1681 continue
1682 fstate.setdefault(fn, {})
1682 fstate.setdefault(fn, {})
1683 try:
1683 try:
1684 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1684 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1685 except KeyError:
1685 except KeyError:
1686 pass
1686 pass
1687 elif st == 'iter':
1687 elif st == 'iter':
1688 states = matches[rev].items()
1688 states = matches[rev].items()
1689 states.sort()
1689 states.sort()
1690 for fn, m in states:
1690 for fn, m in states:
1691 if fn in skip:
1691 if fn in skip:
1692 continue
1692 continue
1693 if incrementing or not opts['all'] or fstate[fn]:
1693 if incrementing or not opts['all'] or fstate[fn]:
1694 pos, neg = display(fn, rev, m, fstate[fn])
1694 pos, neg = display(fn, rev, m, fstate[fn])
1695 count += pos + neg
1695 count += pos + neg
1696 if pos and not opts['all']:
1696 if pos and not opts['all']:
1697 skip[fn] = True
1697 skip[fn] = True
1698 fstate[fn] = m
1698 fstate[fn] = m
1699 prev[fn] = rev
1699 prev[fn] = rev
1700
1700
1701 if not incrementing:
1701 if not incrementing:
1702 fstate = fstate.items()
1702 fstate = fstate.items()
1703 fstate.sort()
1703 fstate.sort()
1704 for fn, state in fstate:
1704 for fn, state in fstate:
1705 if fn in skip:
1705 if fn in skip:
1706 continue
1706 continue
1707 display(fn, rev, {}, state)
1707 display(fn, rev, {}, state)
1708 return (count == 0 and 1) or 0
1708 return (count == 0 and 1) or 0
1709
1709
1710 def heads(ui, repo, **opts):
1710 def heads(ui, repo, **opts):
1711 """show current repository heads
1711 """show current repository heads
1712
1712
1713 Show all repository head changesets.
1713 Show all repository head changesets.
1714
1714
1715 Repository "heads" are changesets that don't have children
1715 Repository "heads" are changesets that don't have children
1716 changesets. They are where development generally takes place and
1716 changesets. They are where development generally takes place and
1717 are the usual targets for update and merge operations.
1717 are the usual targets for update and merge operations.
1718 """
1718 """
1719 if opts['rev']:
1719 if opts['rev']:
1720 heads = repo.heads(repo.lookup(opts['rev']))
1720 heads = repo.heads(repo.lookup(opts['rev']))
1721 else:
1721 else:
1722 heads = repo.heads()
1722 heads = repo.heads()
1723 br = None
1723 br = None
1724 if opts['branches']:
1724 if opts['branches']:
1725 br = repo.branchlookup(heads)
1725 br = repo.branchlookup(heads)
1726 displayer = show_changeset(ui, repo, opts)
1726 displayer = show_changeset(ui, repo, opts)
1727 for n in heads:
1727 for n in heads:
1728 displayer.show(changenode=n, brinfo=br)
1728 displayer.show(changenode=n, brinfo=br)
1729
1729
1730 def identify(ui, repo):
1730 def identify(ui, repo):
1731 """print information about the working copy
1731 """print information about the working copy
1732
1732
1733 Print a short summary of the current state of the repo.
1733 Print a short summary of the current state of the repo.
1734
1734
1735 This summary identifies the repository state using one or two parent
1735 This summary identifies the repository state using one or two parent
1736 hash identifiers, followed by a "+" if there are uncommitted changes
1736 hash identifiers, followed by a "+" if there are uncommitted changes
1737 in the working directory, followed by a list of tags for this revision.
1737 in the working directory, followed by a list of tags for this revision.
1738 """
1738 """
1739 parents = [p for p in repo.dirstate.parents() if p != nullid]
1739 parents = [p for p in repo.dirstate.parents() if p != nullid]
1740 if not parents:
1740 if not parents:
1741 ui.write(_("unknown\n"))
1741 ui.write(_("unknown\n"))
1742 return
1742 return
1743
1743
1744 hexfunc = ui.verbose and hex or short
1744 hexfunc = ui.verbose and hex or short
1745 modified, added, removed, deleted, unknown = repo.changes()
1745 modified, added, removed, deleted, unknown = repo.changes()
1746 output = ["%s%s" %
1746 output = ["%s%s" %
1747 ('+'.join([hexfunc(parent) for parent in parents]),
1747 ('+'.join([hexfunc(parent) for parent in parents]),
1748 (modified or added or removed or deleted) and "+" or "")]
1748 (modified or added or removed or deleted) and "+" or "")]
1749
1749
1750 if not ui.quiet:
1750 if not ui.quiet:
1751 # multiple tags for a single parent separated by '/'
1751 # multiple tags for a single parent separated by '/'
1752 parenttags = ['/'.join(tags)
1752 parenttags = ['/'.join(tags)
1753 for tags in map(repo.nodetags, parents) if tags]
1753 for tags in map(repo.nodetags, parents) if tags]
1754 # tags for multiple parents separated by ' + '
1754 # tags for multiple parents separated by ' + '
1755 if parenttags:
1755 if parenttags:
1756 output.append(' + '.join(parenttags))
1756 output.append(' + '.join(parenttags))
1757
1757
1758 ui.write("%s\n" % ' '.join(output))
1758 ui.write("%s\n" % ' '.join(output))
1759
1759
1760 def import_(ui, repo, patch1, *patches, **opts):
1760 def import_(ui, repo, patch1, *patches, **opts):
1761 """import an ordered set of patches
1761 """import an ordered set of patches
1762
1762
1763 Import a list of patches and commit them individually.
1763 Import a list of patches and commit them individually.
1764
1764
1765 If there are outstanding changes in the working directory, import
1765 If there are outstanding changes in the working directory, import
1766 will abort unless given the -f flag.
1766 will abort unless given the -f flag.
1767
1767
1768 You can import a patch straight from a mail message. Even patches
1768 You can import a patch straight from a mail message. Even patches
1769 as attachments work (body part must be type text/plain or
1769 as attachments work (body part must be type text/plain or
1770 text/x-patch to be used). From and Subject headers of email
1770 text/x-patch to be used). From and Subject headers of email
1771 message are used as default committer and commit message. All
1771 message are used as default committer and commit message. All
1772 text/plain body parts before first diff are added to commit
1772 text/plain body parts before first diff are added to commit
1773 message.
1773 message.
1774
1774
1775 If imported patch was generated by hg export, user and description
1775 If imported patch was generated by hg export, user and description
1776 from patch override values from message headers and body. Values
1776 from patch override values from message headers and body. Values
1777 given on command line with -m and -u override these.
1777 given on command line with -m and -u override these.
1778
1778
1779 To read a patch from standard input, use patch name "-".
1779 To read a patch from standard input, use patch name "-".
1780 """
1780 """
1781 patches = (patch1,) + patches
1781 patches = (patch1,) + patches
1782
1782
1783 if not opts['force']:
1783 if not opts['force']:
1784 bail_if_changed(repo)
1784 bail_if_changed(repo)
1785
1785
1786 d = opts["base"]
1786 d = opts["base"]
1787 strip = opts["strip"]
1787 strip = opts["strip"]
1788
1788
1789 mailre = re.compile(r'(?:From |[\w-]+:)')
1789 mailre = re.compile(r'(?:From |[\w-]+:)')
1790
1790
1791 # attempt to detect the start of a patch
1791 # attempt to detect the start of a patch
1792 # (this heuristic is borrowed from quilt)
1792 # (this heuristic is borrowed from quilt)
1793 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
1793 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
1794 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
1794 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
1795 '(---|\*\*\*)[ \t])', re.MULTILINE)
1795 '(---|\*\*\*)[ \t])', re.MULTILINE)
1796
1796
1797 for patch in patches:
1797 for patch in patches:
1798 pf = os.path.join(d, patch)
1798 pf = os.path.join(d, patch)
1799
1799
1800 message = None
1800 message = None
1801 user = None
1801 user = None
1802 date = None
1802 date = None
1803 hgpatch = False
1803 hgpatch = False
1804
1804
1805 p = email.Parser.Parser()
1805 p = email.Parser.Parser()
1806 if pf == '-':
1806 if pf == '-':
1807 msg = p.parse(sys.stdin)
1807 msg = p.parse(sys.stdin)
1808 ui.status(_("applying patch from stdin\n"))
1808 ui.status(_("applying patch from stdin\n"))
1809 else:
1809 else:
1810 msg = p.parse(file(pf))
1810 msg = p.parse(file(pf))
1811 ui.status(_("applying %s\n") % patch)
1811 ui.status(_("applying %s\n") % patch)
1812
1812
1813 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
1813 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
1814 tmpfp = os.fdopen(fd, 'w')
1814 tmpfp = os.fdopen(fd, 'w')
1815 try:
1815 try:
1816 message = msg['Subject']
1816 message = msg['Subject']
1817 if message:
1817 if message:
1818 message = message.replace('\n\t', ' ')
1818 message = message.replace('\n\t', ' ')
1819 ui.debug('Subject: %s\n' % message)
1819 ui.debug('Subject: %s\n' % message)
1820 user = msg['From']
1820 user = msg['From']
1821 if user:
1821 if user:
1822 ui.debug('From: %s\n' % user)
1822 ui.debug('From: %s\n' % user)
1823 diffs_seen = 0
1823 diffs_seen = 0
1824 ok_types = ('text/plain', 'text/x-patch')
1824 ok_types = ('text/plain', 'text/x-patch')
1825 for part in msg.walk():
1825 for part in msg.walk():
1826 content_type = part.get_content_type()
1826 content_type = part.get_content_type()
1827 ui.debug('Content-Type: %s\n' % content_type)
1827 ui.debug('Content-Type: %s\n' % content_type)
1828 if content_type not in ok_types:
1828 if content_type not in ok_types:
1829 continue
1829 continue
1830 payload = part.get_payload(decode=True)
1830 payload = part.get_payload(decode=True)
1831 m = diffre.search(payload)
1831 m = diffre.search(payload)
1832 if m:
1832 if m:
1833 ui.debug(_('found patch at byte %d\n') % m.start(0))
1833 ui.debug(_('found patch at byte %d\n') % m.start(0))
1834 diffs_seen += 1
1834 diffs_seen += 1
1835 hgpatch = False
1835 hgpatch = False
1836 fp = cStringIO.StringIO()
1836 fp = cStringIO.StringIO()
1837 if message:
1837 if message:
1838 fp.write(message)
1838 fp.write(message)
1839 fp.write('\n')
1839 fp.write('\n')
1840 for line in payload[:m.start(0)].splitlines():
1840 for line in payload[:m.start(0)].splitlines():
1841 if line.startswith('# HG changeset patch'):
1841 if line.startswith('# HG changeset patch'):
1842 ui.debug(_('patch generated by hg export\n'))
1842 ui.debug(_('patch generated by hg export\n'))
1843 hgpatch = True
1843 hgpatch = True
1844 # drop earlier commit message content
1844 # drop earlier commit message content
1845 fp.seek(0)
1845 fp.seek(0)
1846 fp.truncate()
1846 fp.truncate()
1847 elif hgpatch:
1847 elif hgpatch:
1848 if line.startswith('# User '):
1848 if line.startswith('# User '):
1849 user = line[7:]
1849 user = line[7:]
1850 ui.debug('From: %s\n' % user)
1850 ui.debug('From: %s\n' % user)
1851 elif line.startswith("# Date "):
1851 elif line.startswith("# Date "):
1852 date = line[7:]
1852 date = line[7:]
1853 if not line.startswith('# '):
1853 if not line.startswith('# '):
1854 fp.write(line)
1854 fp.write(line)
1855 fp.write('\n')
1855 fp.write('\n')
1856 message = fp.getvalue()
1856 message = fp.getvalue()
1857 if tmpfp:
1857 if tmpfp:
1858 tmpfp.write(payload)
1858 tmpfp.write(payload)
1859 if not payload.endswith('\n'):
1859 if not payload.endswith('\n'):
1860 tmpfp.write('\n')
1860 tmpfp.write('\n')
1861 elif not diffs_seen and message and content_type == 'text/plain':
1861 elif not diffs_seen and message and content_type == 'text/plain':
1862 message += '\n' + payload
1862 message += '\n' + payload
1863
1863
1864 if opts['message']:
1864 if opts['message']:
1865 # pickup the cmdline msg
1865 # pickup the cmdline msg
1866 message = opts['message']
1866 message = opts['message']
1867 elif message:
1867 elif message:
1868 # pickup the patch msg
1868 # pickup the patch msg
1869 message = message.strip()
1869 message = message.strip()
1870 else:
1870 else:
1871 # launch the editor
1871 # launch the editor
1872 message = None
1872 message = None
1873 ui.debug(_('message:\n%s\n') % message)
1873 ui.debug(_('message:\n%s\n') % message)
1874
1874
1875 tmpfp.close()
1875 tmpfp.close()
1876 if not diffs_seen:
1876 if not diffs_seen:
1877 raise util.Abort(_('no diffs found'))
1877 raise util.Abort(_('no diffs found'))
1878
1878
1879 files = util.patch(strip, tmpname, ui)
1879 files = util.patch(strip, tmpname, ui)
1880 if len(files) > 0:
1880 if len(files) > 0:
1881 addremove_lock(ui, repo, files, {})
1881 addremove_lock(ui, repo, files, {})
1882 repo.commit(files, message, user, date)
1882 repo.commit(files, message, user, date)
1883 finally:
1883 finally:
1884 os.unlink(tmpname)
1884 os.unlink(tmpname)
1885
1885
1886 def incoming(ui, repo, source="default", **opts):
1886 def incoming(ui, repo, source="default", **opts):
1887 """show new changesets found in source
1887 """show new changesets found in source
1888
1888
1889 Show new changesets found in the specified path/URL or the default
1889 Show new changesets found in the specified path/URL or the default
1890 pull location. These are the changesets that would be pulled if a pull
1890 pull location. These are the changesets that would be pulled if a pull
1891 was requested.
1891 was requested.
1892
1892
1893 For remote repository, using --bundle avoids downloading the changesets
1893 For remote repository, using --bundle avoids downloading the changesets
1894 twice if the incoming is followed by a pull.
1894 twice if the incoming is followed by a pull.
1895
1895
1896 See pull for valid source format details.
1896 See pull for valid source format details.
1897 """
1897 """
1898 source = ui.expandpath(source)
1898 source = ui.expandpath(source)
1899 if opts['ssh']:
1899 if opts['ssh']:
1900 ui.setconfig("ui", "ssh", opts['ssh'])
1900 ui.setconfig("ui", "ssh", opts['ssh'])
1901 if opts['remotecmd']:
1901 if opts['remotecmd']:
1902 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1902 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1903
1903
1904 other = hg.repository(ui, source)
1904 other = hg.repository(ui, source)
1905 incoming = repo.findincoming(other, force=opts["force"])
1905 incoming = repo.findincoming(other, force=opts["force"])
1906 if not incoming:
1906 if not incoming:
1907 ui.status(_("no changes found\n"))
1907 ui.status(_("no changes found\n"))
1908 return
1908 return
1909
1909
1910 cleanup = None
1910 cleanup = None
1911 try:
1911 try:
1912 fname = opts["bundle"]
1912 fname = opts["bundle"]
1913 if fname or not other.local():
1913 if fname or not other.local():
1914 # create a bundle (uncompressed if other repo is not local)
1914 # create a bundle (uncompressed if other repo is not local)
1915 cg = other.changegroup(incoming, "incoming")
1915 cg = other.changegroup(incoming, "incoming")
1916 fname = cleanup = write_bundle(cg, fname, compress=other.local())
1916 fname = cleanup = write_bundle(cg, fname, compress=other.local())
1917 # keep written bundle?
1917 # keep written bundle?
1918 if opts["bundle"]:
1918 if opts["bundle"]:
1919 cleanup = None
1919 cleanup = None
1920 if not other.local():
1920 if not other.local():
1921 # use the created uncompressed bundlerepo
1921 # use the created uncompressed bundlerepo
1922 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1922 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1923
1923
1924 revs = None
1924 revs = None
1925 if opts['rev']:
1925 if opts['rev']:
1926 revs = [other.lookup(rev) for rev in opts['rev']]
1926 revs = [other.lookup(rev) for rev in opts['rev']]
1927 o = other.changelog.nodesbetween(incoming, revs)[0]
1927 o = other.changelog.nodesbetween(incoming, revs)[0]
1928 if opts['newest_first']:
1928 if opts['newest_first']:
1929 o.reverse()
1929 o.reverse()
1930 displayer = show_changeset(ui, other, opts)
1930 displayer = show_changeset(ui, other, opts)
1931 for n in o:
1931 for n in o:
1932 parents = [p for p in other.changelog.parents(n) if p != nullid]
1932 parents = [p for p in other.changelog.parents(n) if p != nullid]
1933 if opts['no_merges'] and len(parents) == 2:
1933 if opts['no_merges'] and len(parents) == 2:
1934 continue
1934 continue
1935 displayer.show(changenode=n)
1935 displayer.show(changenode=n)
1936 if opts['patch']:
1936 if opts['patch']:
1937 prev = (parents and parents[0]) or nullid
1937 prev = (parents and parents[0]) or nullid
1938 dodiff(ui, ui, other, prev, n)
1938 dodiff(ui, ui, other, prev, n)
1939 ui.write("\n")
1939 ui.write("\n")
1940 finally:
1940 finally:
1941 if hasattr(other, 'close'):
1941 if hasattr(other, 'close'):
1942 other.close()
1942 other.close()
1943 if cleanup:
1943 if cleanup:
1944 os.unlink(cleanup)
1944 os.unlink(cleanup)
1945
1945
1946 def init(ui, dest="."):
1946 def init(ui, dest="."):
1947 """create a new repository in the given directory
1947 """create a new repository in the given directory
1948
1948
1949 Initialize a new repository in the given directory. If the given
1949 Initialize a new repository in the given directory. If the given
1950 directory does not exist, it is created.
1950 directory does not exist, it is created.
1951
1951
1952 If no directory is given, the current directory is used.
1952 If no directory is given, the current directory is used.
1953 """
1953 """
1954 hg.repository(ui, dest, create=1)
1954 hg.repository(ui, dest, create=1)
1955
1955
1956 def locate(ui, repo, *pats, **opts):
1956 def locate(ui, repo, *pats, **opts):
1957 """locate files matching specific patterns
1957 """locate files matching specific patterns
1958
1958
1959 Print all files under Mercurial control whose names match the
1959 Print all files under Mercurial control whose names match the
1960 given patterns.
1960 given patterns.
1961
1961
1962 This command searches the current directory and its
1962 This command searches the current directory and its
1963 subdirectories. To search an entire repository, move to the root
1963 subdirectories. To search an entire repository, move to the root
1964 of the repository.
1964 of the repository.
1965
1965
1966 If no patterns are given to match, this command prints all file
1966 If no patterns are given to match, this command prints all file
1967 names.
1967 names.
1968
1968
1969 If you want to feed the output of this command into the "xargs"
1969 If you want to feed the output of this command into the "xargs"
1970 command, use the "-0" option to both this command and "xargs".
1970 command, use the "-0" option to both this command and "xargs".
1971 This will avoid the problem of "xargs" treating single filenames
1971 This will avoid the problem of "xargs" treating single filenames
1972 that contain white space as multiple filenames.
1972 that contain white space as multiple filenames.
1973 """
1973 """
1974 end = opts['print0'] and '\0' or '\n'
1974 end = opts['print0'] and '\0' or '\n'
1975 rev = opts['rev']
1975 rev = opts['rev']
1976 if rev:
1976 if rev:
1977 node = repo.lookup(rev)
1977 node = repo.lookup(rev)
1978 else:
1978 else:
1979 node = None
1979 node = None
1980
1980
1981 for src, abs, rel, exact in walk(repo, pats, opts, node=node,
1981 for src, abs, rel, exact in walk(repo, pats, opts, node=node,
1982 head='(?:.*/|)'):
1982 head='(?:.*/|)'):
1983 if not node and repo.dirstate.state(abs) == '?':
1983 if not node and repo.dirstate.state(abs) == '?':
1984 continue
1984 continue
1985 if opts['fullpath']:
1985 if opts['fullpath']:
1986 ui.write(os.path.join(repo.root, abs), end)
1986 ui.write(os.path.join(repo.root, abs), end)
1987 else:
1987 else:
1988 ui.write(((pats and rel) or abs), end)
1988 ui.write(((pats and rel) or abs), end)
1989
1989
1990 def log(ui, repo, *pats, **opts):
1990 def log(ui, repo, *pats, **opts):
1991 """show revision history of entire repository or files
1991 """show revision history of entire repository or files
1992
1992
1993 Print the revision history of the specified files or the entire project.
1993 Print the revision history of the specified files or the entire project.
1994
1994
1995 By default this command outputs: changeset id and hash, tags,
1995 By default this command outputs: changeset id and hash, tags,
1996 non-trivial parents, user, date and time, and a summary for each
1996 non-trivial parents, user, date and time, and a summary for each
1997 commit. When the -v/--verbose switch is used, the list of changed
1997 commit. When the -v/--verbose switch is used, the list of changed
1998 files and full commit message is shown.
1998 files and full commit message is shown.
1999 """
1999 """
2000 class dui(object):
2000 class dui(object):
2001 # Implement and delegate some ui protocol. Save hunks of
2001 # Implement and delegate some ui protocol. Save hunks of
2002 # output for later display in the desired order.
2002 # output for later display in the desired order.
2003 def __init__(self, ui):
2003 def __init__(self, ui):
2004 self.ui = ui
2004 self.ui = ui
2005 self.hunk = {}
2005 self.hunk = {}
2006 self.header = {}
2006 self.header = {}
2007 def bump(self, rev):
2007 def bump(self, rev):
2008 self.rev = rev
2008 self.rev = rev
2009 self.hunk[rev] = []
2009 self.hunk[rev] = []
2010 self.header[rev] = []
2010 self.header[rev] = []
2011 def note(self, *args):
2011 def note(self, *args):
2012 if self.verbose:
2012 if self.verbose:
2013 self.write(*args)
2013 self.write(*args)
2014 def status(self, *args):
2014 def status(self, *args):
2015 if not self.quiet:
2015 if not self.quiet:
2016 self.write(*args)
2016 self.write(*args)
2017 def write(self, *args):
2017 def write(self, *args):
2018 self.hunk[self.rev].append(args)
2018 self.hunk[self.rev].append(args)
2019 def write_header(self, *args):
2019 def write_header(self, *args):
2020 self.header[self.rev].append(args)
2020 self.header[self.rev].append(args)
2021 def debug(self, *args):
2021 def debug(self, *args):
2022 if self.debugflag:
2022 if self.debugflag:
2023 self.write(*args)
2023 self.write(*args)
2024 def __getattr__(self, key):
2024 def __getattr__(self, key):
2025 return getattr(self.ui, key)
2025 return getattr(self.ui, key)
2026
2026
2027 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
2027 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
2028
2028
2029 if opts['limit']:
2029 if opts['limit']:
2030 try:
2030 try:
2031 limit = int(opts['limit'])
2031 limit = int(opts['limit'])
2032 except ValueError:
2032 except ValueError:
2033 raise util.Abort(_('limit must be a positive integer'))
2033 raise util.Abort(_('limit must be a positive integer'))
2034 if limit <= 0: raise util.Abort(_('limit must be positive'))
2034 if limit <= 0: raise util.Abort(_('limit must be positive'))
2035 else:
2035 else:
2036 limit = sys.maxint
2036 limit = sys.maxint
2037 count = 0
2037 count = 0
2038
2038
2039 displayer = show_changeset(ui, repo, opts)
2039 displayer = show_changeset(ui, repo, opts)
2040 for st, rev, fns in changeiter:
2040 for st, rev, fns in changeiter:
2041 if st == 'window':
2041 if st == 'window':
2042 du = dui(ui)
2042 du = dui(ui)
2043 displayer.ui = du
2043 displayer.ui = du
2044 elif st == 'add':
2044 elif st == 'add':
2045 du.bump(rev)
2045 du.bump(rev)
2046 changenode = repo.changelog.node(rev)
2046 changenode = repo.changelog.node(rev)
2047 parents = [p for p in repo.changelog.parents(changenode)
2047 parents = [p for p in repo.changelog.parents(changenode)
2048 if p != nullid]
2048 if p != nullid]
2049 if opts['no_merges'] and len(parents) == 2:
2049 if opts['no_merges'] and len(parents) == 2:
2050 continue
2050 continue
2051 if opts['only_merges'] and len(parents) != 2:
2051 if opts['only_merges'] and len(parents) != 2:
2052 continue
2052 continue
2053
2053
2054 if opts['keyword']:
2054 if opts['keyword']:
2055 changes = getchange(rev)
2055 changes = getchange(rev)
2056 miss = 0
2056 miss = 0
2057 for k in [kw.lower() for kw in opts['keyword']]:
2057 for k in [kw.lower() for kw in opts['keyword']]:
2058 if not (k in changes[1].lower() or
2058 if not (k in changes[1].lower() or
2059 k in changes[4].lower() or
2059 k in changes[4].lower() or
2060 k in " ".join(changes[3][:20]).lower()):
2060 k in " ".join(changes[3][:20]).lower()):
2061 miss = 1
2061 miss = 1
2062 break
2062 break
2063 if miss:
2063 if miss:
2064 continue
2064 continue
2065
2065
2066 br = None
2066 br = None
2067 if opts['branches']:
2067 if opts['branches']:
2068 br = repo.branchlookup([repo.changelog.node(rev)])
2068 br = repo.branchlookup([repo.changelog.node(rev)])
2069
2069
2070 displayer.show(rev, brinfo=br)
2070 displayer.show(rev, brinfo=br)
2071 if opts['patch']:
2071 if opts['patch']:
2072 prev = (parents and parents[0]) or nullid
2072 prev = (parents and parents[0]) or nullid
2073 dodiff(du, du, repo, prev, changenode, match=matchfn)
2073 dodiff(du, du, repo, prev, changenode, match=matchfn)
2074 du.write("\n\n")
2074 du.write("\n\n")
2075 elif st == 'iter':
2075 elif st == 'iter':
2076 if count == limit: break
2076 if count == limit: break
2077 if du.header[rev]:
2077 if du.header[rev]:
2078 for args in du.header[rev]:
2078 for args in du.header[rev]:
2079 ui.write_header(*args)
2079 ui.write_header(*args)
2080 if du.hunk[rev]:
2080 if du.hunk[rev]:
2081 count += 1
2081 count += 1
2082 for args in du.hunk[rev]:
2082 for args in du.hunk[rev]:
2083 ui.write(*args)
2083 ui.write(*args)
2084
2084
2085 def manifest(ui, repo, rev=None):
2085 def manifest(ui, repo, rev=None):
2086 """output the latest or given revision of the project manifest
2086 """output the latest or given revision of the project manifest
2087
2087
2088 Print a list of version controlled files for the given revision.
2088 Print a list of version controlled files for the given revision.
2089
2089
2090 The manifest is the list of files being version controlled. If no revision
2090 The manifest is the list of files being version controlled. If no revision
2091 is given then the tip is used.
2091 is given then the tip is used.
2092 """
2092 """
2093 if rev:
2093 if rev:
2094 try:
2094 try:
2095 # assume all revision numbers are for changesets
2095 # assume all revision numbers are for changesets
2096 n = repo.lookup(rev)
2096 n = repo.lookup(rev)
2097 change = repo.changelog.read(n)
2097 change = repo.changelog.read(n)
2098 n = change[0]
2098 n = change[0]
2099 except hg.RepoError:
2099 except hg.RepoError:
2100 n = repo.manifest.lookup(rev)
2100 n = repo.manifest.lookup(rev)
2101 else:
2101 else:
2102 n = repo.manifest.tip()
2102 n = repo.manifest.tip()
2103 m = repo.manifest.read(n)
2103 m = repo.manifest.read(n)
2104 mf = repo.manifest.readflags(n)
2104 mf = repo.manifest.readflags(n)
2105 files = m.keys()
2105 files = m.keys()
2106 files.sort()
2106 files.sort()
2107
2107
2108 for f in files:
2108 for f in files:
2109 ui.write("%40s %3s %s\n" % (hex(m[f]), mf[f] and "755" or "644", f))
2109 ui.write("%40s %3s %s\n" % (hex(m[f]), mf[f] and "755" or "644", f))
2110
2110
2111 def merge(ui, repo, node=None, **opts):
2111 def merge(ui, repo, node=None, **opts):
2112 """Merge working directory with another revision
2112 """Merge working directory with another revision
2113
2113
2114 Merge the contents of the current working directory and the
2114 Merge the contents of the current working directory and the
2115 requested revision. Files that changed between either parent are
2115 requested revision. Files that changed between either parent are
2116 marked as changed for the next commit and a commit must be
2116 marked as changed for the next commit and a commit must be
2117 performed before any further updates are allowed.
2117 performed before any further updates are allowed.
2118 """
2118 """
2119 return doupdate(ui, repo, node=node, merge=True, **opts)
2119 return doupdate(ui, repo, node=node, merge=True, **opts)
2120
2120
2121 def outgoing(ui, repo, dest=None, **opts):
2121 def outgoing(ui, repo, dest=None, **opts):
2122 """show changesets not found in destination
2122 """show changesets not found in destination
2123
2123
2124 Show changesets not found in the specified destination repository or
2124 Show changesets not found in the specified destination repository or
2125 the default push location. These are the changesets that would be pushed
2125 the default push location. These are the changesets that would be pushed
2126 if a push was requested.
2126 if a push was requested.
2127
2127
2128 See pull for valid destination format details.
2128 See pull for valid destination format details.
2129 """
2129 """
2130 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2130 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2131 if opts['ssh']:
2131 if opts['ssh']:
2132 ui.setconfig("ui", "ssh", opts['ssh'])
2132 ui.setconfig("ui", "ssh", opts['ssh'])
2133 if opts['remotecmd']:
2133 if opts['remotecmd']:
2134 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
2134 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
2135 revs = None
2135 revs = None
2136 if opts['rev']:
2136 if opts['rev']:
2137 revs = [repo.lookup(rev) for rev in opts['rev']]
2137 revs = [repo.lookup(rev) for rev in opts['rev']]
2138
2138
2139 other = hg.repository(ui, dest)
2139 other = hg.repository(ui, dest)
2140 o = repo.findoutgoing(other, force=opts['force'])
2140 o = repo.findoutgoing(other, force=opts['force'])
2141 if not o:
2141 if not o:
2142 ui.status(_("no changes found\n"))
2142 ui.status(_("no changes found\n"))
2143 return
2143 return
2144 o = repo.changelog.nodesbetween(o, revs)[0]
2144 o = repo.changelog.nodesbetween(o, revs)[0]
2145 if opts['newest_first']:
2145 if opts['newest_first']:
2146 o.reverse()
2146 o.reverse()
2147 displayer = show_changeset(ui, repo, opts)
2147 displayer = show_changeset(ui, repo, opts)
2148 for n in o:
2148 for n in o:
2149 parents = [p for p in repo.changelog.parents(n) if p != nullid]
2149 parents = [p for p in repo.changelog.parents(n) if p != nullid]
2150 if opts['no_merges'] and len(parents) == 2:
2150 if opts['no_merges'] and len(parents) == 2:
2151 continue
2151 continue
2152 displayer.show(changenode=n)
2152 displayer.show(changenode=n)
2153 if opts['patch']:
2153 if opts['patch']:
2154 prev = (parents and parents[0]) or nullid
2154 prev = (parents and parents[0]) or nullid
2155 dodiff(ui, ui, repo, prev, n)
2155 dodiff(ui, ui, repo, prev, n)
2156 ui.write("\n")
2156 ui.write("\n")
2157
2157
2158 def parents(ui, repo, rev=None, branches=None, **opts):
2158 def parents(ui, repo, rev=None, branches=None, **opts):
2159 """show the parents of the working dir or revision
2159 """show the parents of the working dir or revision
2160
2160
2161 Print the working directory's parent revisions.
2161 Print the working directory's parent revisions.
2162 """
2162 """
2163 if rev:
2163 if rev:
2164 p = repo.changelog.parents(repo.lookup(rev))
2164 p = repo.changelog.parents(repo.lookup(rev))
2165 else:
2165 else:
2166 p = repo.dirstate.parents()
2166 p = repo.dirstate.parents()
2167
2167
2168 br = None
2168 br = None
2169 if branches is not None:
2169 if branches is not None:
2170 br = repo.branchlookup(p)
2170 br = repo.branchlookup(p)
2171 displayer = show_changeset(ui, repo, opts)
2171 displayer = show_changeset(ui, repo, opts)
2172 for n in p:
2172 for n in p:
2173 if n != nullid:
2173 if n != nullid:
2174 displayer.show(changenode=n, brinfo=br)
2174 displayer.show(changenode=n, brinfo=br)
2175
2175
2176 def paths(ui, repo, search=None):
2176 def paths(ui, repo, search=None):
2177 """show definition of symbolic path names
2177 """show definition of symbolic path names
2178
2178
2179 Show definition of symbolic path name NAME. If no name is given, show
2179 Show definition of symbolic path name NAME. If no name is given, show
2180 definition of available names.
2180 definition of available names.
2181
2181
2182 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2182 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2183 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2183 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2184 """
2184 """
2185 if search:
2185 if search:
2186 for name, path in ui.configitems("paths"):
2186 for name, path in ui.configitems("paths"):
2187 if name == search:
2187 if name == search:
2188 ui.write("%s\n" % path)
2188 ui.write("%s\n" % path)
2189 return
2189 return
2190 ui.warn(_("not found!\n"))
2190 ui.warn(_("not found!\n"))
2191 return 1
2191 return 1
2192 else:
2192 else:
2193 for name, path in ui.configitems("paths"):
2193 for name, path in ui.configitems("paths"):
2194 ui.write("%s = %s\n" % (name, path))
2194 ui.write("%s = %s\n" % (name, path))
2195
2195
2196 def postincoming(ui, repo, modheads, optupdate):
2196 def postincoming(ui, repo, modheads, optupdate):
2197 if modheads == 0:
2197 if modheads == 0:
2198 return
2198 return
2199 if optupdate:
2199 if optupdate:
2200 if modheads == 1:
2200 if modheads == 1:
2201 return doupdate(ui, repo)
2201 return doupdate(ui, repo)
2202 else:
2202 else:
2203 ui.status(_("not updating, since new heads added\n"))
2203 ui.status(_("not updating, since new heads added\n"))
2204 if modheads > 1:
2204 if modheads > 1:
2205 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2205 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2206 else:
2206 else:
2207 ui.status(_("(run 'hg update' to get a working copy)\n"))
2207 ui.status(_("(run 'hg update' to get a working copy)\n"))
2208
2208
2209 def pull(ui, repo, source="default", **opts):
2209 def pull(ui, repo, source="default", **opts):
2210 """pull changes from the specified source
2210 """pull changes from the specified source
2211
2211
2212 Pull changes from a remote repository to a local one.
2212 Pull changes from a remote repository to a local one.
2213
2213
2214 This finds all changes from the repository at the specified path
2214 This finds all changes from the repository at the specified path
2215 or URL and adds them to the local repository. By default, this
2215 or URL and adds them to the local repository. By default, this
2216 does not update the copy of the project in the working directory.
2216 does not update the copy of the project in the working directory.
2217
2217
2218 Valid URLs are of the form:
2218 Valid URLs are of the form:
2219
2219
2220 local/filesystem/path
2220 local/filesystem/path
2221 http://[user@]host[:port][/path]
2221 http://[user@]host[:port][/path]
2222 https://[user@]host[:port][/path]
2222 https://[user@]host[:port][/path]
2223 ssh://[user@]host[:port][/path]
2223 ssh://[user@]host[:port][/path]
2224
2224
2225 Some notes about using SSH with Mercurial:
2225 Some notes about using SSH with Mercurial:
2226 - SSH requires an accessible shell account on the destination machine
2226 - SSH requires an accessible shell account on the destination machine
2227 and a copy of hg in the remote path or specified with as remotecmd.
2227 and a copy of hg in the remote path or specified with as remotecmd.
2228 - /path is relative to the remote user's home directory by default.
2228 - /path is relative to the remote user's home directory by default.
2229 Use two slashes at the start of a path to specify an absolute path.
2229 Use two slashes at the start of a path to specify an absolute path.
2230 - Mercurial doesn't use its own compression via SSH; the right thing
2230 - Mercurial doesn't use its own compression via SSH; the right thing
2231 to do is to configure it in your ~/.ssh/ssh_config, e.g.:
2231 to do is to configure it in your ~/.ssh/ssh_config, e.g.:
2232 Host *.mylocalnetwork.example.com
2232 Host *.mylocalnetwork.example.com
2233 Compression off
2233 Compression off
2234 Host *
2234 Host *
2235 Compression on
2235 Compression on
2236 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2236 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2237 with the --ssh command line option.
2237 with the --ssh command line option.
2238 """
2238 """
2239 source = ui.expandpath(source)
2239 source = ui.expandpath(source)
2240
2240
2241 if opts['ssh']:
2241 if opts['ssh']:
2242 ui.setconfig("ui", "ssh", opts['ssh'])
2242 ui.setconfig("ui", "ssh", opts['ssh'])
2243 if opts['remotecmd']:
2243 if opts['remotecmd']:
2244 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
2244 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
2245
2245
2246 other = hg.repository(ui, source)
2246 other = hg.repository(ui, source)
2247 ui.status(_('pulling from %s\n') % (source))
2247 ui.status(_('pulling from %s\n') % (source))
2248 revs = None
2248 revs = None
2249 if opts['rev'] and not other.local():
2249 if opts['rev'] and not other.local():
2250 raise util.Abort(_("pull -r doesn't work for remote repositories yet"))
2250 raise util.Abort(_("pull -r doesn't work for remote repositories yet"))
2251 elif opts['rev']:
2251 elif opts['rev']:
2252 revs = [other.lookup(rev) for rev in opts['rev']]
2252 revs = [other.lookup(rev) for rev in opts['rev']]
2253 modheads = repo.pull(other, heads=revs, force=opts['force'])
2253 modheads = repo.pull(other, heads=revs, force=opts['force'])
2254 return postincoming(ui, repo, modheads, opts['update'])
2254 return postincoming(ui, repo, modheads, opts['update'])
2255
2255
2256 def push(ui, repo, dest=None, **opts):
2256 def push(ui, repo, dest=None, **opts):
2257 """push changes to the specified destination
2257 """push changes to the specified destination
2258
2258
2259 Push changes from the local repository to the given destination.
2259 Push changes from the local repository to the given destination.
2260
2260
2261 This is the symmetrical operation for pull. It helps to move
2261 This is the symmetrical operation for pull. It helps to move
2262 changes from the current repository to a different one. If the
2262 changes from the current repository to a different one. If the
2263 destination is local this is identical to a pull in that directory
2263 destination is local this is identical to a pull in that directory
2264 from the current one.
2264 from the current one.
2265
2265
2266 By default, push will refuse to run if it detects the result would
2266 By default, push will refuse to run if it detects the result would
2267 increase the number of remote heads. This generally indicates the
2267 increase the number of remote heads. This generally indicates the
2268 the client has forgotten to sync and merge before pushing.
2268 the client has forgotten to sync and merge before pushing.
2269
2269
2270 Valid URLs are of the form:
2270 Valid URLs are of the form:
2271
2271
2272 local/filesystem/path
2272 local/filesystem/path
2273 ssh://[user@]host[:port][/path]
2273 ssh://[user@]host[:port][/path]
2274
2274
2275 Look at the help text for the pull command for important details
2275 Look at the help text for the pull command for important details
2276 about ssh:// URLs.
2276 about ssh:// URLs.
2277 """
2277 """
2278 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2278 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2279
2279
2280 if opts['ssh']:
2280 if opts['ssh']:
2281 ui.setconfig("ui", "ssh", opts['ssh'])
2281 ui.setconfig("ui", "ssh", opts['ssh'])
2282 if opts['remotecmd']:
2282 if opts['remotecmd']:
2283 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
2283 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
2284
2284
2285 other = hg.repository(ui, dest)
2285 other = hg.repository(ui, dest)
2286 ui.status('pushing to %s\n' % (dest))
2286 ui.status('pushing to %s\n' % (dest))
2287 revs = None
2287 revs = None
2288 if opts['rev']:
2288 if opts['rev']:
2289 revs = [repo.lookup(rev) for rev in opts['rev']]
2289 revs = [repo.lookup(rev) for rev in opts['rev']]
2290 r = repo.push(other, opts['force'], revs=revs)
2290 r = repo.push(other, opts['force'], revs=revs)
2291 return r == 0
2291 return r == 0
2292
2292
2293 def rawcommit(ui, repo, *flist, **rc):
2293 def rawcommit(ui, repo, *flist, **rc):
2294 """raw commit interface (DEPRECATED)
2294 """raw commit interface (DEPRECATED)
2295
2295
2296 (DEPRECATED)
2296 (DEPRECATED)
2297 Lowlevel commit, for use in helper scripts.
2297 Lowlevel commit, for use in helper scripts.
2298
2298
2299 This command is not intended to be used by normal users, as it is
2299 This command is not intended to be used by normal users, as it is
2300 primarily useful for importing from other SCMs.
2300 primarily useful for importing from other SCMs.
2301
2301
2302 This command is now deprecated and will be removed in a future
2302 This command is now deprecated and will be removed in a future
2303 release, please use debugsetparents and commit instead.
2303 release, please use debugsetparents and commit instead.
2304 """
2304 """
2305
2305
2306 ui.warn(_("(the rawcommit command is deprecated)\n"))
2306 ui.warn(_("(the rawcommit command is deprecated)\n"))
2307
2307
2308 message = rc['message']
2308 message = rc['message']
2309 if not message and rc['logfile']:
2309 if not message and rc['logfile']:
2310 try:
2310 try:
2311 message = open(rc['logfile']).read()
2311 message = open(rc['logfile']).read()
2312 except IOError:
2312 except IOError:
2313 pass
2313 pass
2314 if not message and not rc['logfile']:
2314 if not message and not rc['logfile']:
2315 raise util.Abort(_("missing commit message"))
2315 raise util.Abort(_("missing commit message"))
2316
2316
2317 files = relpath(repo, list(flist))
2317 files = relpath(repo, list(flist))
2318 if rc['files']:
2318 if rc['files']:
2319 files += open(rc['files']).read().splitlines()
2319 files += open(rc['files']).read().splitlines()
2320
2320
2321 rc['parent'] = map(repo.lookup, rc['parent'])
2321 rc['parent'] = map(repo.lookup, rc['parent'])
2322
2322
2323 try:
2323 try:
2324 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
2324 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
2325 except ValueError, inst:
2325 except ValueError, inst:
2326 raise util.Abort(str(inst))
2326 raise util.Abort(str(inst))
2327
2327
2328 def recover(ui, repo):
2328 def recover(ui, repo):
2329 """roll back an interrupted transaction
2329 """roll back an interrupted transaction
2330
2330
2331 Recover from an interrupted commit or pull.
2331 Recover from an interrupted commit or pull.
2332
2332
2333 This command tries to fix the repository status after an interrupted
2333 This command tries to fix the repository status after an interrupted
2334 operation. It should only be necessary when Mercurial suggests it.
2334 operation. It should only be necessary when Mercurial suggests it.
2335 """
2335 """
2336 if repo.recover():
2336 if repo.recover():
2337 return repo.verify()
2337 return repo.verify()
2338 return 1
2338 return 1
2339
2339
2340 def remove(ui, repo, *pats, **opts):
2340 def remove(ui, repo, *pats, **opts):
2341 """remove the specified files on the next commit
2341 """remove the specified files on the next commit
2342
2342
2343 Schedule the indicated files for removal from the repository.
2343 Schedule the indicated files for removal from the repository.
2344
2344
2345 This command schedules the files to be removed at the next commit.
2345 This command schedules the files to be removed at the next commit.
2346 This only removes files from the current branch, not from the
2346 This only removes files from the current branch, not from the
2347 entire project history. If the files still exist in the working
2347 entire project history. If the files still exist in the working
2348 directory, they will be deleted from it. If invoked with --after,
2348 directory, they will be deleted from it. If invoked with --after,
2349 files that have been manually deleted are marked as removed.
2349 files that have been manually deleted are marked as removed.
2350
2350
2351 Modified files and added files are not removed by default. To
2351 Modified files and added files are not removed by default. To
2352 remove them, use the -f/--force option.
2352 remove them, use the -f/--force option.
2353 """
2353 """
2354 names = []
2354 names = []
2355 if not opts['after'] and not pats:
2355 if not opts['after'] and not pats:
2356 raise util.Abort(_('no files specified'))
2356 raise util.Abort(_('no files specified'))
2357 files, matchfn, anypats = matchpats(repo, pats, opts)
2357 files, matchfn, anypats = matchpats(repo, pats, opts)
2358 exact = dict.fromkeys(files)
2358 exact = dict.fromkeys(files)
2359 mardu = map(dict.fromkeys, repo.changes(files=files, match=matchfn))
2359 mardu = map(dict.fromkeys, repo.changes(files=files, match=matchfn))
2360 modified, added, removed, deleted, unknown = mardu
2360 modified, added, removed, deleted, unknown = mardu
2361 remove, forget = [], []
2361 remove, forget = [], []
2362 for src, abs, rel, exact in walk(repo, pats, opts):
2362 for src, abs, rel, exact in walk(repo, pats, opts):
2363 reason = None
2363 reason = None
2364 if abs not in deleted and opts['after']:
2364 if abs not in deleted and opts['after']:
2365 reason = _('is still present')
2365 reason = _('is still present')
2366 elif abs in modified and not opts['force']:
2366 elif abs in modified and not opts['force']:
2367 reason = _('is modified (use -f to force removal)')
2367 reason = _('is modified (use -f to force removal)')
2368 elif abs in added:
2368 elif abs in added:
2369 if opts['force']:
2369 if opts['force']:
2370 forget.append(abs)
2370 forget.append(abs)
2371 continue
2371 continue
2372 reason = _('has been marked for add (use -f to force removal)')
2372 reason = _('has been marked for add (use -f to force removal)')
2373 elif abs in unknown:
2373 elif abs in unknown:
2374 reason = _('is not managed')
2374 reason = _('is not managed')
2375 elif abs in removed:
2375 elif abs in removed:
2376 continue
2376 continue
2377 if reason:
2377 if reason:
2378 if exact:
2378 if exact:
2379 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2379 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2380 else:
2380 else:
2381 if ui.verbose or not exact:
2381 if ui.verbose or not exact:
2382 ui.status(_('removing %s\n') % rel)
2382 ui.status(_('removing %s\n') % rel)
2383 remove.append(abs)
2383 remove.append(abs)
2384 repo.forget(forget)
2384 repo.forget(forget)
2385 repo.remove(remove, unlink=not opts['after'])
2385 repo.remove(remove, unlink=not opts['after'])
2386
2386
2387 def rename(ui, repo, *pats, **opts):
2387 def rename(ui, repo, *pats, **opts):
2388 """rename files; equivalent of copy + remove
2388 """rename files; equivalent of copy + remove
2389
2389
2390 Mark dest as copies of sources; mark sources for deletion. If
2390 Mark dest as copies of sources; mark sources for deletion. If
2391 dest is a directory, copies are put in that directory. If dest is
2391 dest is a directory, copies are put in that directory. If dest is
2392 a file, there can only be one source.
2392 a file, there can only be one source.
2393
2393
2394 By default, this command copies the contents of files as they
2394 By default, this command copies the contents of files as they
2395 stand in the working directory. If invoked with --after, the
2395 stand in the working directory. If invoked with --after, the
2396 operation is recorded, but no copying is performed.
2396 operation is recorded, but no copying is performed.
2397
2397
2398 This command takes effect in the next commit.
2398 This command takes effect in the next commit.
2399
2399
2400 NOTE: This command should be treated as experimental. While it
2400 NOTE: This command should be treated as experimental. While it
2401 should properly record rename files, this information is not yet
2401 should properly record rename files, this information is not yet
2402 fully used by merge, nor fully reported by log.
2402 fully used by merge, nor fully reported by log.
2403 """
2403 """
2404 wlock = repo.wlock(0)
2404 wlock = repo.wlock(0)
2405 errs, copied = docopy(ui, repo, pats, opts, wlock)
2405 errs, copied = docopy(ui, repo, pats, opts, wlock)
2406 names = []
2406 names = []
2407 for abs, rel, exact in copied:
2407 for abs, rel, exact in copied:
2408 if ui.verbose or not exact:
2408 if ui.verbose or not exact:
2409 ui.status(_('removing %s\n') % rel)
2409 ui.status(_('removing %s\n') % rel)
2410 names.append(abs)
2410 names.append(abs)
2411 if not opts.get('dry_run'):
2411 if not opts.get('dry_run'):
2412 repo.remove(names, True, wlock)
2412 repo.remove(names, True, wlock)
2413 return errs
2413 return errs
2414
2414
2415 def revert(ui, repo, *pats, **opts):
2415 def revert(ui, repo, *pats, **opts):
2416 """revert files or dirs to their states as of some revision
2416 """revert files or dirs to their states as of some revision
2417
2417
2418 With no revision specified, revert the named files or directories
2418 With no revision specified, revert the named files or directories
2419 to the contents they had in the parent of the working directory.
2419 to the contents they had in the parent of the working directory.
2420 This restores the contents of the affected files to an unmodified
2420 This restores the contents of the affected files to an unmodified
2421 state. If the working directory has two parents, you must
2421 state. If the working directory has two parents, you must
2422 explicitly specify the revision to revert to.
2422 explicitly specify the revision to revert to.
2423
2423
2424 Modified files are saved with a .orig suffix before reverting.
2424 Modified files are saved with a .orig suffix before reverting.
2425 To disable these backups, use --no-backup.
2425 To disable these backups, use --no-backup.
2426
2426
2427 Using the -r option, revert the given files or directories to
2427 Using the -r option, revert the given files or directories to
2428 their contents as of a specific revision. This can be helpful to"roll
2428 their contents as of a specific revision. This can be helpful to"roll
2429 back" some or all of a change that should not have been committed.
2429 back" some or all of a change that should not have been committed.
2430
2430
2431 Revert modifies the working directory. It does not commit any
2431 Revert modifies the working directory. It does not commit any
2432 changes, or change the parent of the working directory. If you
2432 changes, or change the parent of the working directory. If you
2433 revert to a revision other than the parent of the working
2433 revert to a revision other than the parent of the working
2434 directory, the reverted files will thus appear modified
2434 directory, the reverted files will thus appear modified
2435 afterwards.
2435 afterwards.
2436
2436
2437 If a file has been deleted, it is recreated. If the executable
2437 If a file has been deleted, it is recreated. If the executable
2438 mode of a file was changed, it is reset.
2438 mode of a file was changed, it is reset.
2439
2439
2440 If names are given, all files matching the names are reverted.
2440 If names are given, all files matching the names are reverted.
2441
2441
2442 If no arguments are given, all files in the repository are reverted.
2442 If no arguments are given, all files in the repository are reverted.
2443 """
2443 """
2444 parent, p2 = repo.dirstate.parents()
2444 parent, p2 = repo.dirstate.parents()
2445 if opts['rev']:
2445 if opts['rev']:
2446 node = repo.lookup(opts['rev'])
2446 node = repo.lookup(opts['rev'])
2447 elif p2 != nullid:
2447 elif p2 != nullid:
2448 raise util.Abort(_('working dir has two parents; '
2448 raise util.Abort(_('working dir has two parents; '
2449 'you must specify the revision to revert to'))
2449 'you must specify the revision to revert to'))
2450 else:
2450 else:
2451 node = parent
2451 node = parent
2452 mf = repo.manifest.read(repo.changelog.read(node)[0])
2452 mf = repo.manifest.read(repo.changelog.read(node)[0])
2453 if node == parent:
2453 if node == parent:
2454 pmf = mf
2454 pmf = mf
2455 else:
2455 else:
2456 pmf = None
2456 pmf = None
2457
2457
2458 wlock = repo.wlock()
2458 wlock = repo.wlock()
2459
2459
2460 # need all matching names in dirstate and manifest of target rev,
2460 # need all matching names in dirstate and manifest of target rev,
2461 # so have to walk both. do not print errors if files exist in one
2461 # so have to walk both. do not print errors if files exist in one
2462 # but not other.
2462 # but not other.
2463
2463
2464 names = {}
2464 names = {}
2465 target_only = {}
2465 target_only = {}
2466
2466
2467 # walk dirstate.
2467 # walk dirstate.
2468
2468
2469 for src, abs, rel, exact in walk(repo, pats, opts, badmatch=mf.has_key):
2469 for src, abs, rel, exact in walk(repo, pats, opts, badmatch=mf.has_key):
2470 names[abs] = (rel, exact)
2470 names[abs] = (rel, exact)
2471 if src == 'b':
2471 if src == 'b':
2472 target_only[abs] = True
2472 target_only[abs] = True
2473
2473
2474 # walk target manifest.
2474 # walk target manifest.
2475
2475
2476 for src, abs, rel, exact in walk(repo, pats, opts, node=node,
2476 for src, abs, rel, exact in walk(repo, pats, opts, node=node,
2477 badmatch=names.has_key):
2477 badmatch=names.has_key):
2478 if abs in names: continue
2478 if abs in names: continue
2479 names[abs] = (rel, exact)
2479 names[abs] = (rel, exact)
2480 target_only[abs] = True
2480 target_only[abs] = True
2481
2481
2482 changes = repo.changes(match=names.has_key, wlock=wlock)
2482 changes = repo.changes(match=names.has_key, wlock=wlock)
2483 modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
2483 modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
2484
2484
2485 revert = ([], _('reverting %s\n'))
2485 revert = ([], _('reverting %s\n'))
2486 add = ([], _('adding %s\n'))
2486 add = ([], _('adding %s\n'))
2487 remove = ([], _('removing %s\n'))
2487 remove = ([], _('removing %s\n'))
2488 forget = ([], _('forgetting %s\n'))
2488 forget = ([], _('forgetting %s\n'))
2489 undelete = ([], _('undeleting %s\n'))
2489 undelete = ([], _('undeleting %s\n'))
2490 update = {}
2490 update = {}
2491
2491
2492 disptable = (
2492 disptable = (
2493 # dispatch table:
2493 # dispatch table:
2494 # file state
2494 # file state
2495 # action if in target manifest
2495 # action if in target manifest
2496 # action if not in target manifest
2496 # action if not in target manifest
2497 # make backup if in target manifest
2497 # make backup if in target manifest
2498 # make backup if not in target manifest
2498 # make backup if not in target manifest
2499 (modified, revert, remove, True, True),
2499 (modified, revert, remove, True, True),
2500 (added, revert, forget, True, False),
2500 (added, revert, forget, True, False),
2501 (removed, undelete, None, False, False),
2501 (removed, undelete, None, False, False),
2502 (deleted, revert, remove, False, False),
2502 (deleted, revert, remove, False, False),
2503 (unknown, add, None, True, False),
2503 (unknown, add, None, True, False),
2504 (target_only, add, None, False, False),
2504 (target_only, add, None, False, False),
2505 )
2505 )
2506
2506
2507 entries = names.items()
2507 entries = names.items()
2508 entries.sort()
2508 entries.sort()
2509
2509
2510 for abs, (rel, exact) in entries:
2510 for abs, (rel, exact) in entries:
2511 mfentry = mf.get(abs)
2511 mfentry = mf.get(abs)
2512 def handle(xlist, dobackup):
2512 def handle(xlist, dobackup):
2513 xlist[0].append(abs)
2513 xlist[0].append(abs)
2514 update[abs] = 1
2514 update[abs] = 1
2515 if dobackup and not opts['no_backup'] and os.path.exists(rel):
2515 if dobackup and not opts['no_backup'] and os.path.exists(rel):
2516 bakname = "%s.orig" % rel
2516 bakname = "%s.orig" % rel
2517 ui.note(_('saving current version of %s as %s\n') %
2517 ui.note(_('saving current version of %s as %s\n') %
2518 (rel, bakname))
2518 (rel, bakname))
2519 if not opts.get('dry_run'):
2519 if not opts.get('dry_run'):
2520 shutil.copyfile(rel, bakname)
2520 shutil.copyfile(rel, bakname)
2521 shutil.copymode(rel, bakname)
2521 shutil.copymode(rel, bakname)
2522 if ui.verbose or not exact:
2522 if ui.verbose or not exact:
2523 ui.status(xlist[1] % rel)
2523 ui.status(xlist[1] % rel)
2524 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2524 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2525 if abs not in table: continue
2525 if abs not in table: continue
2526 # file has changed in dirstate
2526 # file has changed in dirstate
2527 if mfentry:
2527 if mfentry:
2528 handle(hitlist, backuphit)
2528 handle(hitlist, backuphit)
2529 elif misslist is not None:
2529 elif misslist is not None:
2530 handle(misslist, backupmiss)
2530 handle(misslist, backupmiss)
2531 else:
2531 else:
2532 if exact: ui.warn(_('file not managed: %s\n' % rel))
2532 if exact: ui.warn(_('file not managed: %s\n' % rel))
2533 break
2533 break
2534 else:
2534 else:
2535 # file has not changed in dirstate
2535 # file has not changed in dirstate
2536 if node == parent:
2536 if node == parent:
2537 if exact: ui.warn(_('no changes needed to %s\n' % rel))
2537 if exact: ui.warn(_('no changes needed to %s\n' % rel))
2538 continue
2538 continue
2539 if pmf is None:
2539 if pmf is None:
2540 # only need parent manifest in this unlikely case,
2540 # only need parent manifest in this unlikely case,
2541 # so do not read by default
2541 # so do not read by default
2542 pmf = repo.manifest.read(repo.changelog.read(parent)[0])
2542 pmf = repo.manifest.read(repo.changelog.read(parent)[0])
2543 if abs in pmf:
2543 if abs in pmf:
2544 if mfentry:
2544 if mfentry:
2545 # if version of file is same in parent and target
2545 # if version of file is same in parent and target
2546 # manifests, do nothing
2546 # manifests, do nothing
2547 if pmf[abs] != mfentry:
2547 if pmf[abs] != mfentry:
2548 handle(revert, False)
2548 handle(revert, False)
2549 else:
2549 else:
2550 handle(remove, False)
2550 handle(remove, False)
2551
2551
2552 if not opts.get('dry_run'):
2552 if not opts.get('dry_run'):
2553 repo.dirstate.forget(forget[0])
2553 repo.dirstate.forget(forget[0])
2554 r = repo.update(node, False, True, update.has_key, False, wlock=wlock,
2554 r = repo.update(node, False, True, update.has_key, False, wlock=wlock,
2555 show_stats=False)
2555 show_stats=False)
2556 repo.dirstate.update(add[0], 'a')
2556 repo.dirstate.update(add[0], 'a')
2557 repo.dirstate.update(undelete[0], 'n')
2557 repo.dirstate.update(undelete[0], 'n')
2558 repo.dirstate.update(remove[0], 'r')
2558 repo.dirstate.update(remove[0], 'r')
2559 return r
2559 return r
2560
2560
2561 def rollback(ui, repo):
2561 def rollback(ui, repo):
2562 """roll back the last transaction in this repository
2562 """roll back the last transaction in this repository
2563
2563
2564 Roll back the last transaction in this repository, restoring the
2564 Roll back the last transaction in this repository, restoring the
2565 project to its state prior to the transaction.
2565 project to its state prior to the transaction.
2566
2566
2567 Transactions are used to encapsulate the effects of all commands
2567 Transactions are used to encapsulate the effects of all commands
2568 that create new changesets or propagate existing changesets into a
2568 that create new changesets or propagate existing changesets into a
2569 repository. For example, the following commands are transactional,
2569 repository. For example, the following commands are transactional,
2570 and their effects can be rolled back:
2570 and their effects can be rolled back:
2571
2571
2572 commit
2572 commit
2573 import
2573 import
2574 pull
2574 pull
2575 push (with this repository as destination)
2575 push (with this repository as destination)
2576 unbundle
2576 unbundle
2577
2577
2578 This command should be used with care. There is only one level of
2578 This command should be used with care. There is only one level of
2579 rollback, and there is no way to undo a rollback.
2579 rollback, and there is no way to undo a rollback.
2580
2580
2581 This command is not intended for use on public repositories. Once
2581 This command is not intended for use on public repositories. Once
2582 changes are visible for pull by other users, rolling a transaction
2582 changes are visible for pull by other users, rolling a transaction
2583 back locally is ineffective (someone else may already have pulled
2583 back locally is ineffective (someone else may already have pulled
2584 the changes). Furthermore, a race is possible with readers of the
2584 the changes). Furthermore, a race is possible with readers of the
2585 repository; for example an in-progress pull from the repository
2585 repository; for example an in-progress pull from the repository
2586 may fail if a rollback is performed.
2586 may fail if a rollback is performed.
2587 """
2587 """
2588 repo.rollback()
2588 repo.rollback()
2589
2589
2590 def root(ui, repo):
2590 def root(ui, repo):
2591 """print the root (top) of the current working dir
2591 """print the root (top) of the current working dir
2592
2592
2593 Print the root directory of the current repository.
2593 Print the root directory of the current repository.
2594 """
2594 """
2595 ui.write(repo.root + "\n")
2595 ui.write(repo.root + "\n")
2596
2596
2597 def serve(ui, repo, **opts):
2597 def serve(ui, repo, **opts):
2598 """export the repository via HTTP
2598 """export the repository via HTTP
2599
2599
2600 Start a local HTTP repository browser and pull server.
2600 Start a local HTTP repository browser and pull server.
2601
2601
2602 By default, the server logs accesses to stdout and errors to
2602 By default, the server logs accesses to stdout and errors to
2603 stderr. Use the "-A" and "-E" options to log to files.
2603 stderr. Use the "-A" and "-E" options to log to files.
2604 """
2604 """
2605
2605
2606 if opts["stdio"]:
2606 if opts["stdio"]:
2607 if repo is None:
2607 if repo is None:
2608 raise hg.RepoError(_('no repo found'))
2608 raise hg.RepoError(_('no repo found'))
2609 s = sshserver.sshserver(ui, repo)
2609 s = sshserver.sshserver(ui, repo)
2610 s.serve_forever()
2610 s.serve_forever()
2611
2611
2612 optlist = ("name templates style address port ipv6"
2612 optlist = ("name templates style address port ipv6"
2613 " accesslog errorlog webdir_conf")
2613 " accesslog errorlog webdir_conf")
2614 for o in optlist.split():
2614 for o in optlist.split():
2615 if opts[o]:
2615 if opts[o]:
2616 ui.setconfig("web", o, opts[o])
2616 ui.setconfig("web", o, opts[o])
2617
2617
2618 if repo is None and not ui.config("web", "webdir_conf"):
2618 if repo is None and not ui.config("web", "webdir_conf"):
2619 raise hg.RepoError(_('no repo found'))
2619 raise hg.RepoError(_('no repo found'))
2620
2620
2621 if opts['daemon'] and not opts['daemon_pipefds']:
2621 if opts['daemon'] and not opts['daemon_pipefds']:
2622 rfd, wfd = os.pipe()
2622 rfd, wfd = os.pipe()
2623 args = sys.argv[:]
2623 args = sys.argv[:]
2624 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
2624 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
2625 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
2625 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
2626 args[0], args)
2626 args[0], args)
2627 os.close(wfd)
2627 os.close(wfd)
2628 os.read(rfd, 1)
2628 os.read(rfd, 1)
2629 os._exit(0)
2629 os._exit(0)
2630
2630
2631 try:
2631 try:
2632 httpd = hgweb.server.create_server(ui, repo)
2632 httpd = hgweb.server.create_server(ui, repo)
2633 except socket.error, inst:
2633 except socket.error, inst:
2634 raise util.Abort(_('cannot start server: ') + inst.args[1])
2634 raise util.Abort(_('cannot start server: ') + inst.args[1])
2635
2635
2636 if ui.verbose:
2636 if ui.verbose:
2637 addr, port = httpd.socket.getsockname()
2637 addr, port = httpd.socket.getsockname()
2638 if addr == '0.0.0.0':
2638 if addr == '0.0.0.0':
2639 addr = socket.gethostname()
2639 addr = socket.gethostname()
2640 else:
2640 else:
2641 try:
2641 try:
2642 addr = socket.gethostbyaddr(addr)[0]
2642 addr = socket.gethostbyaddr(addr)[0]
2643 except socket.error:
2643 except socket.error:
2644 pass
2644 pass
2645 if port != 80:
2645 if port != 80:
2646 ui.status(_('listening at http://%s:%d/\n') % (addr, port))
2646 ui.status(_('listening at http://%s:%d/\n') % (addr, port))
2647 else:
2647 else:
2648 ui.status(_('listening at http://%s/\n') % addr)
2648 ui.status(_('listening at http://%s/\n') % addr)
2649
2649
2650 if opts['pid_file']:
2650 if opts['pid_file']:
2651 fp = open(opts['pid_file'], 'w')
2651 fp = open(opts['pid_file'], 'w')
2652 fp.write(str(os.getpid()) + '\n')
2652 fp.write(str(os.getpid()) + '\n')
2653 fp.close()
2653 fp.close()
2654
2654
2655 if opts['daemon_pipefds']:
2655 if opts['daemon_pipefds']:
2656 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
2656 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
2657 os.close(rfd)
2657 os.close(rfd)
2658 os.write(wfd, 'y')
2658 os.write(wfd, 'y')
2659 os.close(wfd)
2659 os.close(wfd)
2660 sys.stdout.flush()
2660 sys.stdout.flush()
2661 sys.stderr.flush()
2661 sys.stderr.flush()
2662 fd = os.open(util.nulldev, os.O_RDWR)
2662 fd = os.open(util.nulldev, os.O_RDWR)
2663 if fd != 0: os.dup2(fd, 0)
2663 if fd != 0: os.dup2(fd, 0)
2664 if fd != 1: os.dup2(fd, 1)
2664 if fd != 1: os.dup2(fd, 1)
2665 if fd != 2: os.dup2(fd, 2)
2665 if fd != 2: os.dup2(fd, 2)
2666 if fd not in (0, 1, 2): os.close(fd)
2666 if fd not in (0, 1, 2): os.close(fd)
2667
2667
2668 httpd.serve_forever()
2668 httpd.serve_forever()
2669
2669
2670 def status(ui, repo, *pats, **opts):
2670 def status(ui, repo, *pats, **opts):
2671 """show changed files in the working directory
2671 """show changed files in the working directory
2672
2672
2673 Show changed files in the repository. If names are
2673 Show changed files in the repository. If names are
2674 given, only files that match are shown.
2674 given, only files that match are shown.
2675
2675
2676 The codes used to show the status of files are:
2676 The codes used to show the status of files are:
2677 M = modified
2677 M = modified
2678 A = added
2678 A = added
2679 R = removed
2679 R = removed
2680 ! = deleted, but still tracked
2680 ! = deleted, but still tracked
2681 ? = not tracked
2681 ? = not tracked
2682 I = ignored (not shown by default)
2682 I = ignored (not shown by default)
2683 """
2683 """
2684
2684
2685 show_ignored = opts['ignored'] and True or False
2685 show_ignored = opts['ignored'] and True or False
2686 files, matchfn, anypats = matchpats(repo, pats, opts)
2686 files, matchfn, anypats = matchpats(repo, pats, opts)
2687 cwd = (pats and repo.getcwd()) or ''
2687 cwd = (pats and repo.getcwd()) or ''
2688 modified, added, removed, deleted, unknown, ignored = [
2688 modified, added, removed, deleted, unknown, ignored = [
2689 [util.pathto(cwd, x) for x in n]
2689 [util.pathto(cwd, x) for x in n]
2690 for n in repo.changes(files=files, match=matchfn,
2690 for n in repo.changes(files=files, match=matchfn,
2691 show_ignored=show_ignored)]
2691 show_ignored=show_ignored)]
2692
2692
2693 changetypes = [('modified', 'M', modified),
2693 changetypes = [('modified', 'M', modified),
2694 ('added', 'A', added),
2694 ('added', 'A', added),
2695 ('removed', 'R', removed),
2695 ('removed', 'R', removed),
2696 ('deleted', '!', deleted),
2696 ('deleted', '!', deleted),
2697 ('unknown', '?', unknown),
2697 ('unknown', '?', unknown),
2698 ('ignored', 'I', ignored)]
2698 ('ignored', 'I', ignored)]
2699
2699
2700 end = opts['print0'] and '\0' or '\n'
2700 end = opts['print0'] and '\0' or '\n'
2701
2701
2702 for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]]
2702 for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]]
2703 or changetypes):
2703 or changetypes):
2704 if opts['no_status']:
2704 if opts['no_status']:
2705 format = "%%s%s" % end
2705 format = "%%s%s" % end
2706 else:
2706 else:
2707 format = "%s %%s%s" % (char, end)
2707 format = "%s %%s%s" % (char, end)
2708
2708
2709 for f in changes:
2709 for f in changes:
2710 ui.write(format % f)
2710 ui.write(format % f)
2711
2711
2712 def tag(ui, repo, name, rev_=None, **opts):
2712 def tag(ui, repo, name, rev_=None, **opts):
2713 """add a tag for the current tip or a given revision
2713 """add a tag for the current tip or a given revision
2714
2714
2715 Name a particular revision using <name>.
2715 Name a particular revision using <name>.
2716
2716
2717 Tags are used to name particular revisions of the repository and are
2717 Tags are used to name particular revisions of the repository and are
2718 very useful to compare different revision, to go back to significant
2718 very useful to compare different revision, to go back to significant
2719 earlier versions or to mark branch points as releases, etc.
2719 earlier versions or to mark branch points as releases, etc.
2720
2720
2721 If no revision is given, the tip is used.
2721 If no revision is given, the tip is used.
2722
2722
2723 To facilitate version control, distribution, and merging of tags,
2723 To facilitate version control, distribution, and merging of tags,
2724 they are stored as a file named ".hgtags" which is managed
2724 they are stored as a file named ".hgtags" which is managed
2725 similarly to other project files and can be hand-edited if
2725 similarly to other project files and can be hand-edited if
2726 necessary. The file '.hg/localtags' is used for local tags (not
2726 necessary. The file '.hg/localtags' is used for local tags (not
2727 shared among repositories).
2727 shared among repositories).
2728 """
2728 """
2729 if name == "tip":
2729 if name == "tip":
2730 raise util.Abort(_("the name 'tip' is reserved"))
2730 raise util.Abort(_("the name 'tip' is reserved"))
2731 if rev_ is not None:
2731 if rev_ is not None:
2732 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2732 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2733 "please use 'hg tag [-r REV] NAME' instead\n"))
2733 "please use 'hg tag [-r REV] NAME' instead\n"))
2734 if opts['rev']:
2734 if opts['rev']:
2735 raise util.Abort(_("use only one form to specify the revision"))
2735 raise util.Abort(_("use only one form to specify the revision"))
2736 if opts['rev']:
2736 if opts['rev']:
2737 rev_ = opts['rev']
2737 rev_ = opts['rev']
2738 if rev_:
2738 if rev_:
2739 r = hex(repo.lookup(rev_))
2739 r = hex(repo.lookup(rev_))
2740 else:
2740 else:
2741 r = hex(repo.changelog.tip())
2741 r = hex(repo.changelog.tip())
2742
2742
2743 disallowed = (revrangesep, '\r', '\n')
2743 disallowed = (revrangesep, '\r', '\n')
2744 for c in disallowed:
2744 for c in disallowed:
2745 if name.find(c) >= 0:
2745 if c in name:
2746 raise util.Abort(_("%s cannot be used in a tag name") % repr(c))
2746 raise util.Abort(_("%s cannot be used in a tag name") % repr(c))
2747
2747
2748 repo.hook('pretag', throw=True, node=r, tag=name,
2748 repo.hook('pretag', throw=True, node=r, tag=name,
2749 local=int(not not opts['local']))
2749 local=int(not not opts['local']))
2750
2750
2751 if opts['local']:
2751 if opts['local']:
2752 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
2752 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
2753 repo.hook('tag', node=r, tag=name, local=1)
2753 repo.hook('tag', node=r, tag=name, local=1)
2754 return
2754 return
2755
2755
2756 for x in repo.changes():
2756 for x in repo.changes():
2757 if ".hgtags" in x:
2757 if ".hgtags" in x:
2758 raise util.Abort(_("working copy of .hgtags is changed "
2758 raise util.Abort(_("working copy of .hgtags is changed "
2759 "(please commit .hgtags manually)"))
2759 "(please commit .hgtags manually)"))
2760
2760
2761 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
2761 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
2762 if repo.dirstate.state(".hgtags") == '?':
2762 if repo.dirstate.state(".hgtags") == '?':
2763 repo.add([".hgtags"])
2763 repo.add([".hgtags"])
2764
2764
2765 message = (opts['message'] or
2765 message = (opts['message'] or
2766 _("Added tag %s for changeset %s") % (name, r))
2766 _("Added tag %s for changeset %s") % (name, r))
2767 try:
2767 try:
2768 repo.commit([".hgtags"], message, opts['user'], opts['date'])
2768 repo.commit([".hgtags"], message, opts['user'], opts['date'])
2769 repo.hook('tag', node=r, tag=name, local=0)
2769 repo.hook('tag', node=r, tag=name, local=0)
2770 except ValueError, inst:
2770 except ValueError, inst:
2771 raise util.Abort(str(inst))
2771 raise util.Abort(str(inst))
2772
2772
2773 def tags(ui, repo):
2773 def tags(ui, repo):
2774 """list repository tags
2774 """list repository tags
2775
2775
2776 List the repository tags.
2776 List the repository tags.
2777
2777
2778 This lists both regular and local tags.
2778 This lists both regular and local tags.
2779 """
2779 """
2780
2780
2781 l = repo.tagslist()
2781 l = repo.tagslist()
2782 l.reverse()
2782 l.reverse()
2783 for t, n in l:
2783 for t, n in l:
2784 try:
2784 try:
2785 r = "%5d:%s" % (repo.changelog.rev(n), hex(n))
2785 r = "%5d:%s" % (repo.changelog.rev(n), hex(n))
2786 except KeyError:
2786 except KeyError:
2787 r = " ?:?"
2787 r = " ?:?"
2788 if ui.quiet:
2788 if ui.quiet:
2789 ui.write("%s\n" % t)
2789 ui.write("%s\n" % t)
2790 else:
2790 else:
2791 ui.write("%-30s %s\n" % (t, r))
2791 ui.write("%-30s %s\n" % (t, r))
2792
2792
2793 def tip(ui, repo, **opts):
2793 def tip(ui, repo, **opts):
2794 """show the tip revision
2794 """show the tip revision
2795
2795
2796 Show the tip revision.
2796 Show the tip revision.
2797 """
2797 """
2798 n = repo.changelog.tip()
2798 n = repo.changelog.tip()
2799 br = None
2799 br = None
2800 if opts['branches']:
2800 if opts['branches']:
2801 br = repo.branchlookup([n])
2801 br = repo.branchlookup([n])
2802 show_changeset(ui, repo, opts).show(changenode=n, brinfo=br)
2802 show_changeset(ui, repo, opts).show(changenode=n, brinfo=br)
2803 if opts['patch']:
2803 if opts['patch']:
2804 dodiff(ui, ui, repo, repo.changelog.parents(n)[0], n)
2804 dodiff(ui, ui, repo, repo.changelog.parents(n)[0], n)
2805
2805
2806 def unbundle(ui, repo, fname, **opts):
2806 def unbundle(ui, repo, fname, **opts):
2807 """apply a changegroup file
2807 """apply a changegroup file
2808
2808
2809 Apply a compressed changegroup file generated by the bundle
2809 Apply a compressed changegroup file generated by the bundle
2810 command.
2810 command.
2811 """
2811 """
2812 f = urllib.urlopen(fname)
2812 f = urllib.urlopen(fname)
2813
2813
2814 header = f.read(6)
2814 header = f.read(6)
2815 if not header.startswith("HG"):
2815 if not header.startswith("HG"):
2816 raise util.Abort(_("%s: not a Mercurial bundle file") % fname)
2816 raise util.Abort(_("%s: not a Mercurial bundle file") % fname)
2817 elif not header.startswith("HG10"):
2817 elif not header.startswith("HG10"):
2818 raise util.Abort(_("%s: unknown bundle version") % fname)
2818 raise util.Abort(_("%s: unknown bundle version") % fname)
2819 elif header == "HG10BZ":
2819 elif header == "HG10BZ":
2820 def generator(f):
2820 def generator(f):
2821 zd = bz2.BZ2Decompressor()
2821 zd = bz2.BZ2Decompressor()
2822 zd.decompress("BZ")
2822 zd.decompress("BZ")
2823 for chunk in f:
2823 for chunk in f:
2824 yield zd.decompress(chunk)
2824 yield zd.decompress(chunk)
2825 elif header == "HG10UN":
2825 elif header == "HG10UN":
2826 def generator(f):
2826 def generator(f):
2827 for chunk in f:
2827 for chunk in f:
2828 yield chunk
2828 yield chunk
2829 else:
2829 else:
2830 raise util.Abort(_("%s: unknown bundle compression type")
2830 raise util.Abort(_("%s: unknown bundle compression type")
2831 % fname)
2831 % fname)
2832 gen = generator(util.filechunkiter(f, 4096))
2832 gen = generator(util.filechunkiter(f, 4096))
2833 modheads = repo.addchangegroup(util.chunkbuffer(gen), 'unbundle')
2833 modheads = repo.addchangegroup(util.chunkbuffer(gen), 'unbundle')
2834 return postincoming(ui, repo, modheads, opts['update'])
2834 return postincoming(ui, repo, modheads, opts['update'])
2835
2835
2836 def undo(ui, repo):
2836 def undo(ui, repo):
2837 """undo the last commit or pull (DEPRECATED)
2837 """undo the last commit or pull (DEPRECATED)
2838
2838
2839 (DEPRECATED)
2839 (DEPRECATED)
2840 This command is now deprecated and will be removed in a future
2840 This command is now deprecated and will be removed in a future
2841 release. Please use the rollback command instead. For usage
2841 release. Please use the rollback command instead. For usage
2842 instructions, see the rollback command.
2842 instructions, see the rollback command.
2843 """
2843 """
2844 ui.warn(_('(the undo command is deprecated; use rollback instead)\n'))
2844 ui.warn(_('(the undo command is deprecated; use rollback instead)\n'))
2845 repo.rollback()
2845 repo.rollback()
2846
2846
2847 def update(ui, repo, node=None, merge=False, clean=False, force=None,
2847 def update(ui, repo, node=None, merge=False, clean=False, force=None,
2848 branch=None, **opts):
2848 branch=None, **opts):
2849 """update or merge working directory
2849 """update or merge working directory
2850
2850
2851 Update the working directory to the specified revision.
2851 Update the working directory to the specified revision.
2852
2852
2853 If there are no outstanding changes in the working directory and
2853 If there are no outstanding changes in the working directory and
2854 there is a linear relationship between the current version and the
2854 there is a linear relationship between the current version and the
2855 requested version, the result is the requested version.
2855 requested version, the result is the requested version.
2856
2856
2857 To merge the working directory with another revision, use the
2857 To merge the working directory with another revision, use the
2858 merge command.
2858 merge command.
2859
2859
2860 By default, update will refuse to run if doing so would require
2860 By default, update will refuse to run if doing so would require
2861 merging or discarding local changes.
2861 merging or discarding local changes.
2862 """
2862 """
2863 if merge:
2863 if merge:
2864 ui.warn(_('(the -m/--merge option is deprecated; '
2864 ui.warn(_('(the -m/--merge option is deprecated; '
2865 'use the merge command instead)\n'))
2865 'use the merge command instead)\n'))
2866 return doupdate(ui, repo, node, merge, clean, force, branch, **opts)
2866 return doupdate(ui, repo, node, merge, clean, force, branch, **opts)
2867
2867
2868 def doupdate(ui, repo, node=None, merge=False, clean=False, force=None,
2868 def doupdate(ui, repo, node=None, merge=False, clean=False, force=None,
2869 branch=None, **opts):
2869 branch=None, **opts):
2870 if branch:
2870 if branch:
2871 br = repo.branchlookup(branch=branch)
2871 br = repo.branchlookup(branch=branch)
2872 found = []
2872 found = []
2873 for x in br:
2873 for x in br:
2874 if branch in br[x]:
2874 if branch in br[x]:
2875 found.append(x)
2875 found.append(x)
2876 if len(found) > 1:
2876 if len(found) > 1:
2877 ui.warn(_("Found multiple heads for %s\n") % branch)
2877 ui.warn(_("Found multiple heads for %s\n") % branch)
2878 for x in found:
2878 for x in found:
2879 show_changeset(ui, repo, opts).show(changenode=x, brinfo=br)
2879 show_changeset(ui, repo, opts).show(changenode=x, brinfo=br)
2880 return 1
2880 return 1
2881 if len(found) == 1:
2881 if len(found) == 1:
2882 node = found[0]
2882 node = found[0]
2883 ui.warn(_("Using head %s for branch %s\n") % (short(node), branch))
2883 ui.warn(_("Using head %s for branch %s\n") % (short(node), branch))
2884 else:
2884 else:
2885 ui.warn(_("branch %s not found\n") % (branch))
2885 ui.warn(_("branch %s not found\n") % (branch))
2886 return 1
2886 return 1
2887 else:
2887 else:
2888 node = node and repo.lookup(node) or repo.changelog.tip()
2888 node = node and repo.lookup(node) or repo.changelog.tip()
2889 return repo.update(node, allow=merge, force=clean, forcemerge=force)
2889 return repo.update(node, allow=merge, force=clean, forcemerge=force)
2890
2890
2891 def verify(ui, repo):
2891 def verify(ui, repo):
2892 """verify the integrity of the repository
2892 """verify the integrity of the repository
2893
2893
2894 Verify the integrity of the current repository.
2894 Verify the integrity of the current repository.
2895
2895
2896 This will perform an extensive check of the repository's
2896 This will perform an extensive check of the repository's
2897 integrity, validating the hashes and checksums of each entry in
2897 integrity, validating the hashes and checksums of each entry in
2898 the changelog, manifest, and tracked files, as well as the
2898 the changelog, manifest, and tracked files, as well as the
2899 integrity of their crosslinks and indices.
2899 integrity of their crosslinks and indices.
2900 """
2900 """
2901 return repo.verify()
2901 return repo.verify()
2902
2902
2903 # Command options and aliases are listed here, alphabetically
2903 # Command options and aliases are listed here, alphabetically
2904
2904
2905 table = {
2905 table = {
2906 "^add":
2906 "^add":
2907 (add,
2907 (add,
2908 [('I', 'include', [], _('include names matching the given patterns')),
2908 [('I', 'include', [], _('include names matching the given patterns')),
2909 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2909 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2910 ('n', 'dry-run', None, _('do not perform actions, just print output'))],
2910 ('n', 'dry-run', None, _('do not perform actions, just print output'))],
2911 _('hg add [OPTION]... [FILE]...')),
2911 _('hg add [OPTION]... [FILE]...')),
2912 "debugaddremove|addremove":
2912 "debugaddremove|addremove":
2913 (addremove,
2913 (addremove,
2914 [('I', 'include', [], _('include names matching the given patterns')),
2914 [('I', 'include', [], _('include names matching the given patterns')),
2915 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2915 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2916 ('n', 'dry-run', None, _('do not perform actions, just print output'))],
2916 ('n', 'dry-run', None, _('do not perform actions, just print output'))],
2917 _('hg addremove [OPTION]... [FILE]...')),
2917 _('hg addremove [OPTION]... [FILE]...')),
2918 "^annotate":
2918 "^annotate":
2919 (annotate,
2919 (annotate,
2920 [('r', 'rev', '', _('annotate the specified revision')),
2920 [('r', 'rev', '', _('annotate the specified revision')),
2921 ('a', 'text', None, _('treat all files as text')),
2921 ('a', 'text', None, _('treat all files as text')),
2922 ('u', 'user', None, _('list the author')),
2922 ('u', 'user', None, _('list the author')),
2923 ('d', 'date', None, _('list the date')),
2923 ('d', 'date', None, _('list the date')),
2924 ('n', 'number', None, _('list the revision number (default)')),
2924 ('n', 'number', None, _('list the revision number (default)')),
2925 ('c', 'changeset', None, _('list the changeset')),
2925 ('c', 'changeset', None, _('list the changeset')),
2926 ('I', 'include', [], _('include names matching the given patterns')),
2926 ('I', 'include', [], _('include names matching the given patterns')),
2927 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2927 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2928 _('hg annotate [-r REV] [-a] [-u] [-d] [-n] [-c] FILE...')),
2928 _('hg annotate [-r REV] [-a] [-u] [-d] [-n] [-c] FILE...')),
2929 "archive":
2929 "archive":
2930 (archive,
2930 (archive,
2931 [('', 'no-decode', None, _('do not pass files through decoders')),
2931 [('', 'no-decode', None, _('do not pass files through decoders')),
2932 ('p', 'prefix', '', _('directory prefix for files in archive')),
2932 ('p', 'prefix', '', _('directory prefix for files in archive')),
2933 ('r', 'rev', '', _('revision to distribute')),
2933 ('r', 'rev', '', _('revision to distribute')),
2934 ('t', 'type', '', _('type of distribution to create')),
2934 ('t', 'type', '', _('type of distribution to create')),
2935 ('I', 'include', [], _('include names matching the given patterns')),
2935 ('I', 'include', [], _('include names matching the given patterns')),
2936 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2936 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2937 _('hg archive [OPTION]... DEST')),
2937 _('hg archive [OPTION]... DEST')),
2938 "backout":
2938 "backout":
2939 (backout,
2939 (backout,
2940 [('', 'merge', None,
2940 [('', 'merge', None,
2941 _('merge with old dirstate parent after backout')),
2941 _('merge with old dirstate parent after backout')),
2942 ('m', 'message', '', _('use <text> as commit message')),
2942 ('m', 'message', '', _('use <text> as commit message')),
2943 ('l', 'logfile', '', _('read commit message from <file>')),
2943 ('l', 'logfile', '', _('read commit message from <file>')),
2944 ('d', 'date', '', _('record datecode as commit date')),
2944 ('d', 'date', '', _('record datecode as commit date')),
2945 ('u', 'user', '', _('record user as committer')),
2945 ('u', 'user', '', _('record user as committer')),
2946 ('I', 'include', [], _('include names matching the given patterns')),
2946 ('I', 'include', [], _('include names matching the given patterns')),
2947 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2947 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2948 _('hg backout [OPTION]... REV')),
2948 _('hg backout [OPTION]... REV')),
2949 "bundle":
2949 "bundle":
2950 (bundle,
2950 (bundle,
2951 [('f', 'force', None,
2951 [('f', 'force', None,
2952 _('run even when remote repository is unrelated'))],
2952 _('run even when remote repository is unrelated'))],
2953 _('hg bundle FILE DEST')),
2953 _('hg bundle FILE DEST')),
2954 "cat":
2954 "cat":
2955 (cat,
2955 (cat,
2956 [('o', 'output', '', _('print output to file with formatted name')),
2956 [('o', 'output', '', _('print output to file with formatted name')),
2957 ('r', 'rev', '', _('print the given revision')),
2957 ('r', 'rev', '', _('print the given revision')),
2958 ('I', 'include', [], _('include names matching the given patterns')),
2958 ('I', 'include', [], _('include names matching the given patterns')),
2959 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2959 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2960 _('hg cat [OPTION]... FILE...')),
2960 _('hg cat [OPTION]... FILE...')),
2961 "^clone":
2961 "^clone":
2962 (clone,
2962 (clone,
2963 [('U', 'noupdate', None, _('do not update the new working directory')),
2963 [('U', 'noupdate', None, _('do not update the new working directory')),
2964 ('r', 'rev', [],
2964 ('r', 'rev', [],
2965 _('a changeset you would like to have after cloning')),
2965 _('a changeset you would like to have after cloning')),
2966 ('', 'pull', None, _('use pull protocol to copy metadata')),
2966 ('', 'pull', None, _('use pull protocol to copy metadata')),
2967 ('e', 'ssh', '', _('specify ssh command to use')),
2967 ('e', 'ssh', '', _('specify ssh command to use')),
2968 ('', 'remotecmd', '',
2968 ('', 'remotecmd', '',
2969 _('specify hg command to run on the remote side'))],
2969 _('specify hg command to run on the remote side'))],
2970 _('hg clone [OPTION]... SOURCE [DEST]')),
2970 _('hg clone [OPTION]... SOURCE [DEST]')),
2971 "^commit|ci":
2971 "^commit|ci":
2972 (commit,
2972 (commit,
2973 [('A', 'addremove', None,
2973 [('A', 'addremove', None,
2974 _('mark new/missing files as added/removed before committing')),
2974 _('mark new/missing files as added/removed before committing')),
2975 ('m', 'message', '', _('use <text> as commit message')),
2975 ('m', 'message', '', _('use <text> as commit message')),
2976 ('l', 'logfile', '', _('read the commit message from <file>')),
2976 ('l', 'logfile', '', _('read the commit message from <file>')),
2977 ('d', 'date', '', _('record datecode as commit date')),
2977 ('d', 'date', '', _('record datecode as commit date')),
2978 ('u', 'user', '', _('record user as commiter')),
2978 ('u', 'user', '', _('record user as commiter')),
2979 ('I', 'include', [], _('include names matching the given patterns')),
2979 ('I', 'include', [], _('include names matching the given patterns')),
2980 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2980 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2981 _('hg commit [OPTION]... [FILE]...')),
2981 _('hg commit [OPTION]... [FILE]...')),
2982 "copy|cp":
2982 "copy|cp":
2983 (copy,
2983 (copy,
2984 [('A', 'after', None, _('record a copy that has already occurred')),
2984 [('A', 'after', None, _('record a copy that has already occurred')),
2985 ('f', 'force', None,
2985 ('f', 'force', None,
2986 _('forcibly copy over an existing managed file')),
2986 _('forcibly copy over an existing managed file')),
2987 ('I', 'include', [], _('include names matching the given patterns')),
2987 ('I', 'include', [], _('include names matching the given patterns')),
2988 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2988 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2989 ('n', 'dry-run', None, _('do not perform actions, just print output'))],
2989 ('n', 'dry-run', None, _('do not perform actions, just print output'))],
2990 _('hg copy [OPTION]... [SOURCE]... DEST')),
2990 _('hg copy [OPTION]... [SOURCE]... DEST')),
2991 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2991 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2992 "debugcomplete":
2992 "debugcomplete":
2993 (debugcomplete,
2993 (debugcomplete,
2994 [('o', 'options', None, _('show the command options'))],
2994 [('o', 'options', None, _('show the command options'))],
2995 _('debugcomplete [-o] CMD')),
2995 _('debugcomplete [-o] CMD')),
2996 "debugrebuildstate":
2996 "debugrebuildstate":
2997 (debugrebuildstate,
2997 (debugrebuildstate,
2998 [('r', 'rev', '', _('revision to rebuild to'))],
2998 [('r', 'rev', '', _('revision to rebuild to'))],
2999 _('debugrebuildstate [-r REV] [REV]')),
2999 _('debugrebuildstate [-r REV] [REV]')),
3000 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
3000 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
3001 "debugconfig": (debugconfig, [], _('debugconfig [NAME]...')),
3001 "debugconfig": (debugconfig, [], _('debugconfig [NAME]...')),
3002 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
3002 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
3003 "debugstate": (debugstate, [], _('debugstate')),
3003 "debugstate": (debugstate, [], _('debugstate')),
3004 "debugdata": (debugdata, [], _('debugdata FILE REV')),
3004 "debugdata": (debugdata, [], _('debugdata FILE REV')),
3005 "debugindex": (debugindex, [], _('debugindex FILE')),
3005 "debugindex": (debugindex, [], _('debugindex FILE')),
3006 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
3006 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
3007 "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
3007 "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
3008 "debugwalk":
3008 "debugwalk":
3009 (debugwalk,
3009 (debugwalk,
3010 [('I', 'include', [], _('include names matching the given patterns')),
3010 [('I', 'include', [], _('include names matching the given patterns')),
3011 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3011 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3012 _('debugwalk [OPTION]... [FILE]...')),
3012 _('debugwalk [OPTION]... [FILE]...')),
3013 "^diff":
3013 "^diff":
3014 (diff,
3014 (diff,
3015 [('r', 'rev', [], _('revision')),
3015 [('r', 'rev', [], _('revision')),
3016 ('a', 'text', None, _('treat all files as text')),
3016 ('a', 'text', None, _('treat all files as text')),
3017 ('p', 'show-function', None,
3017 ('p', 'show-function', None,
3018 _('show which function each change is in')),
3018 _('show which function each change is in')),
3019 ('w', 'ignore-all-space', None,
3019 ('w', 'ignore-all-space', None,
3020 _('ignore white space when comparing lines')),
3020 _('ignore white space when comparing lines')),
3021 ('I', 'include', [], _('include names matching the given patterns')),
3021 ('I', 'include', [], _('include names matching the given patterns')),
3022 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3022 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3023 _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')),
3023 _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')),
3024 "^export":
3024 "^export":
3025 (export,
3025 (export,
3026 [('o', 'output', '', _('print output to file with formatted name')),
3026 [('o', 'output', '', _('print output to file with formatted name')),
3027 ('a', 'text', None, _('treat all files as text')),
3027 ('a', 'text', None, _('treat all files as text')),
3028 ('', 'switch-parent', None, _('diff against the second parent'))],
3028 ('', 'switch-parent', None, _('diff against the second parent'))],
3029 _('hg export [-a] [-o OUTFILESPEC] REV...')),
3029 _('hg export [-a] [-o OUTFILESPEC] REV...')),
3030 "debugforget|forget":
3030 "debugforget|forget":
3031 (forget,
3031 (forget,
3032 [('I', 'include', [], _('include names matching the given patterns')),
3032 [('I', 'include', [], _('include names matching the given patterns')),
3033 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3033 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3034 _('hg forget [OPTION]... FILE...')),
3034 _('hg forget [OPTION]... FILE...')),
3035 "grep":
3035 "grep":
3036 (grep,
3036 (grep,
3037 [('0', 'print0', None, _('end fields with NUL')),
3037 [('0', 'print0', None, _('end fields with NUL')),
3038 ('', 'all', None, _('print all revisions that match')),
3038 ('', 'all', None, _('print all revisions that match')),
3039 ('i', 'ignore-case', None, _('ignore case when matching')),
3039 ('i', 'ignore-case', None, _('ignore case when matching')),
3040 ('l', 'files-with-matches', None,
3040 ('l', 'files-with-matches', None,
3041 _('print only filenames and revs that match')),
3041 _('print only filenames and revs that match')),
3042 ('n', 'line-number', None, _('print matching line numbers')),
3042 ('n', 'line-number', None, _('print matching line numbers')),
3043 ('r', 'rev', [], _('search in given revision range')),
3043 ('r', 'rev', [], _('search in given revision range')),
3044 ('u', 'user', None, _('print user who committed change')),
3044 ('u', 'user', None, _('print user who committed change')),
3045 ('I', 'include', [], _('include names matching the given patterns')),
3045 ('I', 'include', [], _('include names matching the given patterns')),
3046 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3046 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3047 _('hg grep [OPTION]... PATTERN [FILE]...')),
3047 _('hg grep [OPTION]... PATTERN [FILE]...')),
3048 "heads":
3048 "heads":
3049 (heads,
3049 (heads,
3050 [('b', 'branches', None, _('show branches')),
3050 [('b', 'branches', None, _('show branches')),
3051 ('', 'style', '', _('display using template map file')),
3051 ('', 'style', '', _('display using template map file')),
3052 ('r', 'rev', '', _('show only heads which are descendants of rev')),
3052 ('r', 'rev', '', _('show only heads which are descendants of rev')),
3053 ('', 'template', '', _('display with template'))],
3053 ('', 'template', '', _('display with template'))],
3054 _('hg heads [-b] [-r <rev>]')),
3054 _('hg heads [-b] [-r <rev>]')),
3055 "help": (help_, [], _('hg help [COMMAND]')),
3055 "help": (help_, [], _('hg help [COMMAND]')),
3056 "identify|id": (identify, [], _('hg identify')),
3056 "identify|id": (identify, [], _('hg identify')),
3057 "import|patch":
3057 "import|patch":
3058 (import_,
3058 (import_,
3059 [('p', 'strip', 1,
3059 [('p', 'strip', 1,
3060 _('directory strip option for patch. This has the same\n'
3060 _('directory strip option for patch. This has the same\n'
3061 'meaning as the corresponding patch option')),
3061 'meaning as the corresponding patch option')),
3062 ('m', 'message', '', _('use <text> as commit message')),
3062 ('m', 'message', '', _('use <text> as commit message')),
3063 ('b', 'base', '', _('base path')),
3063 ('b', 'base', '', _('base path')),
3064 ('f', 'force', None,
3064 ('f', 'force', None,
3065 _('skip check for outstanding uncommitted changes'))],
3065 _('skip check for outstanding uncommitted changes'))],
3066 _('hg import [-p NUM] [-b BASE] [-m MESSAGE] [-f] PATCH...')),
3066 _('hg import [-p NUM] [-b BASE] [-m MESSAGE] [-f] PATCH...')),
3067 "incoming|in": (incoming,
3067 "incoming|in": (incoming,
3068 [('M', 'no-merges', None, _('do not show merges')),
3068 [('M', 'no-merges', None, _('do not show merges')),
3069 ('f', 'force', None,
3069 ('f', 'force', None,
3070 _('run even when remote repository is unrelated')),
3070 _('run even when remote repository is unrelated')),
3071 ('', 'style', '', _('display using template map file')),
3071 ('', 'style', '', _('display using template map file')),
3072 ('n', 'newest-first', None, _('show newest record first')),
3072 ('n', 'newest-first', None, _('show newest record first')),
3073 ('', 'bundle', '', _('file to store the bundles into')),
3073 ('', 'bundle', '', _('file to store the bundles into')),
3074 ('p', 'patch', None, _('show patch')),
3074 ('p', 'patch', None, _('show patch')),
3075 ('r', 'rev', [], _('a specific revision you would like to pull')),
3075 ('r', 'rev', [], _('a specific revision you would like to pull')),
3076 ('', 'template', '', _('display with template')),
3076 ('', 'template', '', _('display with template')),
3077 ('e', 'ssh', '', _('specify ssh command to use')),
3077 ('e', 'ssh', '', _('specify ssh command to use')),
3078 ('', 'remotecmd', '',
3078 ('', 'remotecmd', '',
3079 _('specify hg command to run on the remote side'))],
3079 _('specify hg command to run on the remote side'))],
3080 _('hg incoming [-p] [-n] [-M] [-r REV]...'
3080 _('hg incoming [-p] [-n] [-M] [-r REV]...'
3081 ' [--bundle FILENAME] [SOURCE]')),
3081 ' [--bundle FILENAME] [SOURCE]')),
3082 "^init": (init, [], _('hg init [DEST]')),
3082 "^init": (init, [], _('hg init [DEST]')),
3083 "locate":
3083 "locate":
3084 (locate,
3084 (locate,
3085 [('r', 'rev', '', _('search the repository as it stood at rev')),
3085 [('r', 'rev', '', _('search the repository as it stood at rev')),
3086 ('0', 'print0', None,
3086 ('0', 'print0', None,
3087 _('end filenames with NUL, for use with xargs')),
3087 _('end filenames with NUL, for use with xargs')),
3088 ('f', 'fullpath', None,
3088 ('f', 'fullpath', None,
3089 _('print complete paths from the filesystem root')),
3089 _('print complete paths from the filesystem root')),
3090 ('I', 'include', [], _('include names matching the given patterns')),
3090 ('I', 'include', [], _('include names matching the given patterns')),
3091 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3091 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3092 _('hg locate [OPTION]... [PATTERN]...')),
3092 _('hg locate [OPTION]... [PATTERN]...')),
3093 "^log|history":
3093 "^log|history":
3094 (log,
3094 (log,
3095 [('b', 'branches', None, _('show branches')),
3095 [('b', 'branches', None, _('show branches')),
3096 ('k', 'keyword', [], _('search for a keyword')),
3096 ('k', 'keyword', [], _('search for a keyword')),
3097 ('l', 'limit', '', _('limit number of changes displayed')),
3097 ('l', 'limit', '', _('limit number of changes displayed')),
3098 ('r', 'rev', [], _('show the specified revision or range')),
3098 ('r', 'rev', [], _('show the specified revision or range')),
3099 ('M', 'no-merges', None, _('do not show merges')),
3099 ('M', 'no-merges', None, _('do not show merges')),
3100 ('', 'style', '', _('display using template map file')),
3100 ('', 'style', '', _('display using template map file')),
3101 ('m', 'only-merges', None, _('show only merges')),
3101 ('m', 'only-merges', None, _('show only merges')),
3102 ('p', 'patch', None, _('show patch')),
3102 ('p', 'patch', None, _('show patch')),
3103 ('', 'template', '', _('display with template')),
3103 ('', 'template', '', _('display with template')),
3104 ('I', 'include', [], _('include names matching the given patterns')),
3104 ('I', 'include', [], _('include names matching the given patterns')),
3105 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3105 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3106 _('hg log [OPTION]... [FILE]')),
3106 _('hg log [OPTION]... [FILE]')),
3107 "manifest": (manifest, [], _('hg manifest [REV]')),
3107 "manifest": (manifest, [], _('hg manifest [REV]')),
3108 "merge":
3108 "merge":
3109 (merge,
3109 (merge,
3110 [('b', 'branch', '', _('merge with head of a specific branch')),
3110 [('b', 'branch', '', _('merge with head of a specific branch')),
3111 ('f', 'force', None, _('force a merge with outstanding changes'))],
3111 ('f', 'force', None, _('force a merge with outstanding changes'))],
3112 _('hg merge [-b TAG] [-f] [REV]')),
3112 _('hg merge [-b TAG] [-f] [REV]')),
3113 "outgoing|out": (outgoing,
3113 "outgoing|out": (outgoing,
3114 [('M', 'no-merges', None, _('do not show merges')),
3114 [('M', 'no-merges', None, _('do not show merges')),
3115 ('f', 'force', None,
3115 ('f', 'force', None,
3116 _('run even when remote repository is unrelated')),
3116 _('run even when remote repository is unrelated')),
3117 ('p', 'patch', None, _('show patch')),
3117 ('p', 'patch', None, _('show patch')),
3118 ('', 'style', '', _('display using template map file')),
3118 ('', 'style', '', _('display using template map file')),
3119 ('r', 'rev', [], _('a specific revision you would like to push')),
3119 ('r', 'rev', [], _('a specific revision you would like to push')),
3120 ('n', 'newest-first', None, _('show newest record first')),
3120 ('n', 'newest-first', None, _('show newest record first')),
3121 ('', 'template', '', _('display with template')),
3121 ('', 'template', '', _('display with template')),
3122 ('e', 'ssh', '', _('specify ssh command to use')),
3122 ('e', 'ssh', '', _('specify ssh command to use')),
3123 ('', 'remotecmd', '',
3123 ('', 'remotecmd', '',
3124 _('specify hg command to run on the remote side'))],
3124 _('specify hg command to run on the remote side'))],
3125 _('hg outgoing [-M] [-p] [-n] [-r REV]... [DEST]')),
3125 _('hg outgoing [-M] [-p] [-n] [-r REV]... [DEST]')),
3126 "^parents":
3126 "^parents":
3127 (parents,
3127 (parents,
3128 [('b', 'branches', None, _('show branches')),
3128 [('b', 'branches', None, _('show branches')),
3129 ('', 'style', '', _('display using template map file')),
3129 ('', 'style', '', _('display using template map file')),
3130 ('', 'template', '', _('display with template'))],
3130 ('', 'template', '', _('display with template'))],
3131 _('hg parents [-b] [REV]')),
3131 _('hg parents [-b] [REV]')),
3132 "paths": (paths, [], _('hg paths [NAME]')),
3132 "paths": (paths, [], _('hg paths [NAME]')),
3133 "^pull":
3133 "^pull":
3134 (pull,
3134 (pull,
3135 [('u', 'update', None,
3135 [('u', 'update', None,
3136 _('update the working directory to tip after pull')),
3136 _('update the working directory to tip after pull')),
3137 ('e', 'ssh', '', _('specify ssh command to use')),
3137 ('e', 'ssh', '', _('specify ssh command to use')),
3138 ('f', 'force', None,
3138 ('f', 'force', None,
3139 _('run even when remote repository is unrelated')),
3139 _('run even when remote repository is unrelated')),
3140 ('r', 'rev', [], _('a specific revision you would like to pull')),
3140 ('r', 'rev', [], _('a specific revision you would like to pull')),
3141 ('', 'remotecmd', '',
3141 ('', 'remotecmd', '',
3142 _('specify hg command to run on the remote side'))],
3142 _('specify hg command to run on the remote side'))],
3143 _('hg pull [-u] [-e FILE] [-r REV]... [--remotecmd FILE] [SOURCE]')),
3143 _('hg pull [-u] [-e FILE] [-r REV]... [--remotecmd FILE] [SOURCE]')),
3144 "^push":
3144 "^push":
3145 (push,
3145 (push,
3146 [('f', 'force', None, _('force push')),
3146 [('f', 'force', None, _('force push')),
3147 ('e', 'ssh', '', _('specify ssh command to use')),
3147 ('e', 'ssh', '', _('specify ssh command to use')),
3148 ('r', 'rev', [], _('a specific revision you would like to push')),
3148 ('r', 'rev', [], _('a specific revision you would like to push')),
3149 ('', 'remotecmd', '',
3149 ('', 'remotecmd', '',
3150 _('specify hg command to run on the remote side'))],
3150 _('specify hg command to run on the remote side'))],
3151 _('hg push [-f] [-e FILE] [-r REV]... [--remotecmd FILE] [DEST]')),
3151 _('hg push [-f] [-e FILE] [-r REV]... [--remotecmd FILE] [DEST]')),
3152 "debugrawcommit|rawcommit":
3152 "debugrawcommit|rawcommit":
3153 (rawcommit,
3153 (rawcommit,
3154 [('p', 'parent', [], _('parent')),
3154 [('p', 'parent', [], _('parent')),
3155 ('d', 'date', '', _('date code')),
3155 ('d', 'date', '', _('date code')),
3156 ('u', 'user', '', _('user')),
3156 ('u', 'user', '', _('user')),
3157 ('F', 'files', '', _('file list')),
3157 ('F', 'files', '', _('file list')),
3158 ('m', 'message', '', _('commit message')),
3158 ('m', 'message', '', _('commit message')),
3159 ('l', 'logfile', '', _('commit message file'))],
3159 ('l', 'logfile', '', _('commit message file'))],
3160 _('hg debugrawcommit [OPTION]... [FILE]...')),
3160 _('hg debugrawcommit [OPTION]... [FILE]...')),
3161 "recover": (recover, [], _('hg recover')),
3161 "recover": (recover, [], _('hg recover')),
3162 "^remove|rm":
3162 "^remove|rm":
3163 (remove,
3163 (remove,
3164 [('A', 'after', None, _('record remove that has already occurred')),
3164 [('A', 'after', None, _('record remove that has already occurred')),
3165 ('f', 'force', None, _('remove file even if modified')),
3165 ('f', 'force', None, _('remove file even if modified')),
3166 ('I', 'include', [], _('include names matching the given patterns')),
3166 ('I', 'include', [], _('include names matching the given patterns')),
3167 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3167 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3168 _('hg remove [OPTION]... FILE...')),
3168 _('hg remove [OPTION]... FILE...')),
3169 "rename|mv":
3169 "rename|mv":
3170 (rename,
3170 (rename,
3171 [('A', 'after', None, _('record a rename that has already occurred')),
3171 [('A', 'after', None, _('record a rename that has already occurred')),
3172 ('f', 'force', None,
3172 ('f', 'force', None,
3173 _('forcibly copy over an existing managed file')),
3173 _('forcibly copy over an existing managed file')),
3174 ('I', 'include', [], _('include names matching the given patterns')),
3174 ('I', 'include', [], _('include names matching the given patterns')),
3175 ('X', 'exclude', [], _('exclude names matching the given patterns')),
3175 ('X', 'exclude', [], _('exclude names matching the given patterns')),
3176 ('n', 'dry-run', None, _('do not perform actions, just print output'))],
3176 ('n', 'dry-run', None, _('do not perform actions, just print output'))],
3177 _('hg rename [OPTION]... SOURCE... DEST')),
3177 _('hg rename [OPTION]... SOURCE... DEST')),
3178 "^revert":
3178 "^revert":
3179 (revert,
3179 (revert,
3180 [('r', 'rev', '', _('revision to revert to')),
3180 [('r', 'rev', '', _('revision to revert to')),
3181 ('', 'no-backup', None, _('do not save backup copies of files')),
3181 ('', 'no-backup', None, _('do not save backup copies of files')),
3182 ('I', 'include', [], _('include names matching given patterns')),
3182 ('I', 'include', [], _('include names matching given patterns')),
3183 ('X', 'exclude', [], _('exclude names matching given patterns')),
3183 ('X', 'exclude', [], _('exclude names matching given patterns')),
3184 ('n', 'dry-run', None, _('do not perform actions, just print output'))],
3184 ('n', 'dry-run', None, _('do not perform actions, just print output'))],
3185 _('hg revert [-r REV] [NAME]...')),
3185 _('hg revert [-r REV] [NAME]...')),
3186 "rollback": (rollback, [], _('hg rollback')),
3186 "rollback": (rollback, [], _('hg rollback')),
3187 "root": (root, [], _('hg root')),
3187 "root": (root, [], _('hg root')),
3188 "^serve":
3188 "^serve":
3189 (serve,
3189 (serve,
3190 [('A', 'accesslog', '', _('name of access log file to write to')),
3190 [('A', 'accesslog', '', _('name of access log file to write to')),
3191 ('d', 'daemon', None, _('run server in background')),
3191 ('d', 'daemon', None, _('run server in background')),
3192 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3192 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3193 ('E', 'errorlog', '', _('name of error log file to write to')),
3193 ('E', 'errorlog', '', _('name of error log file to write to')),
3194 ('p', 'port', 0, _('port to use (default: 8000)')),
3194 ('p', 'port', 0, _('port to use (default: 8000)')),
3195 ('a', 'address', '', _('address to use')),
3195 ('a', 'address', '', _('address to use')),
3196 ('n', 'name', '',
3196 ('n', 'name', '',
3197 _('name to show in web pages (default: working dir)')),
3197 _('name to show in web pages (default: working dir)')),
3198 ('', 'webdir-conf', '', _('name of the webdir config file'
3198 ('', 'webdir-conf', '', _('name of the webdir config file'
3199 ' (serve more than one repo)')),
3199 ' (serve more than one repo)')),
3200 ('', 'pid-file', '', _('name of file to write process ID to')),
3200 ('', 'pid-file', '', _('name of file to write process ID to')),
3201 ('', 'stdio', None, _('for remote clients')),
3201 ('', 'stdio', None, _('for remote clients')),
3202 ('t', 'templates', '', _('web templates to use')),
3202 ('t', 'templates', '', _('web templates to use')),
3203 ('', 'style', '', _('template style to use')),
3203 ('', 'style', '', _('template style to use')),
3204 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
3204 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
3205 _('hg serve [OPTION]...')),
3205 _('hg serve [OPTION]...')),
3206 "^status|st":
3206 "^status|st":
3207 (status,
3207 (status,
3208 [('m', 'modified', None, _('show only modified files')),
3208 [('m', 'modified', None, _('show only modified files')),
3209 ('a', 'added', None, _('show only added files')),
3209 ('a', 'added', None, _('show only added files')),
3210 ('r', 'removed', None, _('show only removed files')),
3210 ('r', 'removed', None, _('show only removed files')),
3211 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3211 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3212 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3212 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3213 ('i', 'ignored', None, _('show ignored files')),
3213 ('i', 'ignored', None, _('show ignored files')),
3214 ('n', 'no-status', None, _('hide status prefix')),
3214 ('n', 'no-status', None, _('hide status prefix')),
3215 ('0', 'print0', None,
3215 ('0', 'print0', None,
3216 _('end filenames with NUL, for use with xargs')),
3216 _('end filenames with NUL, for use with xargs')),
3217 ('I', 'include', [], _('include names matching the given patterns')),
3217 ('I', 'include', [], _('include names matching the given patterns')),
3218 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3218 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3219 _('hg status [OPTION]... [FILE]...')),
3219 _('hg status [OPTION]... [FILE]...')),
3220 "tag":
3220 "tag":
3221 (tag,
3221 (tag,
3222 [('l', 'local', None, _('make the tag local')),
3222 [('l', 'local', None, _('make the tag local')),
3223 ('m', 'message', '', _('message for tag commit log entry')),
3223 ('m', 'message', '', _('message for tag commit log entry')),
3224 ('d', 'date', '', _('record datecode as commit date')),
3224 ('d', 'date', '', _('record datecode as commit date')),
3225 ('u', 'user', '', _('record user as commiter')),
3225 ('u', 'user', '', _('record user as commiter')),
3226 ('r', 'rev', '', _('revision to tag'))],
3226 ('r', 'rev', '', _('revision to tag'))],
3227 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
3227 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
3228 "tags": (tags, [], _('hg tags')),
3228 "tags": (tags, [], _('hg tags')),
3229 "tip":
3229 "tip":
3230 (tip,
3230 (tip,
3231 [('b', 'branches', None, _('show branches')),
3231 [('b', 'branches', None, _('show branches')),
3232 ('', 'style', '', _('display using template map file')),
3232 ('', 'style', '', _('display using template map file')),
3233 ('p', 'patch', None, _('show patch')),
3233 ('p', 'patch', None, _('show patch')),
3234 ('', 'template', '', _('display with template'))],
3234 ('', 'template', '', _('display with template'))],
3235 _('hg tip [-b] [-p]')),
3235 _('hg tip [-b] [-p]')),
3236 "unbundle":
3236 "unbundle":
3237 (unbundle,
3237 (unbundle,
3238 [('u', 'update', None,
3238 [('u', 'update', None,
3239 _('update the working directory to tip after unbundle'))],
3239 _('update the working directory to tip after unbundle'))],
3240 _('hg unbundle [-u] FILE')),
3240 _('hg unbundle [-u] FILE')),
3241 "debugundo|undo": (undo, [], _('hg undo')),
3241 "debugundo|undo": (undo, [], _('hg undo')),
3242 "^update|up|checkout|co":
3242 "^update|up|checkout|co":
3243 (update,
3243 (update,
3244 [('b', 'branch', '', _('checkout the head of a specific branch')),
3244 [('b', 'branch', '', _('checkout the head of a specific branch')),
3245 ('m', 'merge', None, _('allow merging of branches (DEPRECATED)')),
3245 ('m', 'merge', None, _('allow merging of branches (DEPRECATED)')),
3246 ('C', 'clean', None, _('overwrite locally modified files')),
3246 ('C', 'clean', None, _('overwrite locally modified files')),
3247 ('f', 'force', None, _('force a merge with outstanding changes'))],
3247 ('f', 'force', None, _('force a merge with outstanding changes'))],
3248 _('hg update [-b TAG] [-m] [-C] [-f] [REV]')),
3248 _('hg update [-b TAG] [-m] [-C] [-f] [REV]')),
3249 "verify": (verify, [], _('hg verify')),
3249 "verify": (verify, [], _('hg verify')),
3250 "version": (show_version, [], _('hg version')),
3250 "version": (show_version, [], _('hg version')),
3251 }
3251 }
3252
3252
3253 globalopts = [
3253 globalopts = [
3254 ('R', 'repository', '',
3254 ('R', 'repository', '',
3255 _('repository root directory or symbolic path name')),
3255 _('repository root directory or symbolic path name')),
3256 ('', 'cwd', '', _('change working directory')),
3256 ('', 'cwd', '', _('change working directory')),
3257 ('y', 'noninteractive', None,
3257 ('y', 'noninteractive', None,
3258 _('do not prompt, assume \'yes\' for any required answers')),
3258 _('do not prompt, assume \'yes\' for any required answers')),
3259 ('q', 'quiet', None, _('suppress output')),
3259 ('q', 'quiet', None, _('suppress output')),
3260 ('v', 'verbose', None, _('enable additional output')),
3260 ('v', 'verbose', None, _('enable additional output')),
3261 ('', 'config', [], _('set/override config option')),
3261 ('', 'config', [], _('set/override config option')),
3262 ('', 'debug', None, _('enable debugging output')),
3262 ('', 'debug', None, _('enable debugging output')),
3263 ('', 'debugger', None, _('start debugger')),
3263 ('', 'debugger', None, _('start debugger')),
3264 ('', 'lsprof', None, _('print improved command execution profile')),
3264 ('', 'lsprof', None, _('print improved command execution profile')),
3265 ('', 'traceback', None, _('print traceback on exception')),
3265 ('', 'traceback', None, _('print traceback on exception')),
3266 ('', 'time', None, _('time how long the command takes')),
3266 ('', 'time', None, _('time how long the command takes')),
3267 ('', 'profile', None, _('print command execution profile')),
3267 ('', 'profile', None, _('print command execution profile')),
3268 ('', 'version', None, _('output version information and exit')),
3268 ('', 'version', None, _('output version information and exit')),
3269 ('h', 'help', None, _('display help and exit')),
3269 ('h', 'help', None, _('display help and exit')),
3270 ]
3270 ]
3271
3271
3272 norepo = ("clone init version help debugancestor debugcomplete debugdata"
3272 norepo = ("clone init version help debugancestor debugcomplete debugdata"
3273 " debugindex debugindexdot")
3273 " debugindex debugindexdot")
3274 optionalrepo = ("paths serve debugconfig")
3274 optionalrepo = ("paths serve debugconfig")
3275
3275
3276 def findpossible(cmd):
3276 def findpossible(cmd):
3277 """
3277 """
3278 Return cmd -> (aliases, command table entry)
3278 Return cmd -> (aliases, command table entry)
3279 for each matching command.
3279 for each matching command.
3280 Return debug commands (or their aliases) only if no normal command matches.
3280 Return debug commands (or their aliases) only if no normal command matches.
3281 """
3281 """
3282 choice = {}
3282 choice = {}
3283 debugchoice = {}
3283 debugchoice = {}
3284 for e in table.keys():
3284 for e in table.keys():
3285 aliases = e.lstrip("^").split("|")
3285 aliases = e.lstrip("^").split("|")
3286 found = None
3286 found = None
3287 if cmd in aliases:
3287 if cmd in aliases:
3288 found = cmd
3288 found = cmd
3289 else:
3289 else:
3290 for a in aliases:
3290 for a in aliases:
3291 if a.startswith(cmd):
3291 if a.startswith(cmd):
3292 found = a
3292 found = a
3293 break
3293 break
3294 if found is not None:
3294 if found is not None:
3295 if aliases[0].startswith("debug"):
3295 if aliases[0].startswith("debug"):
3296 debugchoice[found] = (aliases, table[e])
3296 debugchoice[found] = (aliases, table[e])
3297 else:
3297 else:
3298 choice[found] = (aliases, table[e])
3298 choice[found] = (aliases, table[e])
3299
3299
3300 if not choice and debugchoice:
3300 if not choice and debugchoice:
3301 choice = debugchoice
3301 choice = debugchoice
3302
3302
3303 return choice
3303 return choice
3304
3304
3305 def findcmd(cmd):
3305 def findcmd(cmd):
3306 """Return (aliases, command table entry) for command string."""
3306 """Return (aliases, command table entry) for command string."""
3307 choice = findpossible(cmd)
3307 choice = findpossible(cmd)
3308
3308
3309 if choice.has_key(cmd):
3309 if choice.has_key(cmd):
3310 return choice[cmd]
3310 return choice[cmd]
3311
3311
3312 if len(choice) > 1:
3312 if len(choice) > 1:
3313 clist = choice.keys()
3313 clist = choice.keys()
3314 clist.sort()
3314 clist.sort()
3315 raise AmbiguousCommand(cmd, clist)
3315 raise AmbiguousCommand(cmd, clist)
3316
3316
3317 if choice:
3317 if choice:
3318 return choice.values()[0]
3318 return choice.values()[0]
3319
3319
3320 raise UnknownCommand(cmd)
3320 raise UnknownCommand(cmd)
3321
3321
3322 def catchterm(*args):
3322 def catchterm(*args):
3323 raise util.SignalInterrupt
3323 raise util.SignalInterrupt
3324
3324
3325 def run():
3325 def run():
3326 sys.exit(dispatch(sys.argv[1:]))
3326 sys.exit(dispatch(sys.argv[1:]))
3327
3327
3328 class ParseError(Exception):
3328 class ParseError(Exception):
3329 """Exception raised on errors in parsing the command line."""
3329 """Exception raised on errors in parsing the command line."""
3330
3330
3331 def parse(ui, args):
3331 def parse(ui, args):
3332 options = {}
3332 options = {}
3333 cmdoptions = {}
3333 cmdoptions = {}
3334
3334
3335 try:
3335 try:
3336 args = fancyopts.fancyopts(args, globalopts, options)
3336 args = fancyopts.fancyopts(args, globalopts, options)
3337 except fancyopts.getopt.GetoptError, inst:
3337 except fancyopts.getopt.GetoptError, inst:
3338 raise ParseError(None, inst)
3338 raise ParseError(None, inst)
3339
3339
3340 if args:
3340 if args:
3341 cmd, args = args[0], args[1:]
3341 cmd, args = args[0], args[1:]
3342 aliases, i = findcmd(cmd)
3342 aliases, i = findcmd(cmd)
3343 cmd = aliases[0]
3343 cmd = aliases[0]
3344 defaults = ui.config("defaults", cmd)
3344 defaults = ui.config("defaults", cmd)
3345 if defaults:
3345 if defaults:
3346 args = defaults.split() + args
3346 args = defaults.split() + args
3347 c = list(i[1])
3347 c = list(i[1])
3348 else:
3348 else:
3349 cmd = None
3349 cmd = None
3350 c = []
3350 c = []
3351
3351
3352 # combine global options into local
3352 # combine global options into local
3353 for o in globalopts:
3353 for o in globalopts:
3354 c.append((o[0], o[1], options[o[1]], o[3]))
3354 c.append((o[0], o[1], options[o[1]], o[3]))
3355
3355
3356 try:
3356 try:
3357 args = fancyopts.fancyopts(args, c, cmdoptions)
3357 args = fancyopts.fancyopts(args, c, cmdoptions)
3358 except fancyopts.getopt.GetoptError, inst:
3358 except fancyopts.getopt.GetoptError, inst:
3359 raise ParseError(cmd, inst)
3359 raise ParseError(cmd, inst)
3360
3360
3361 # separate global options back out
3361 # separate global options back out
3362 for o in globalopts:
3362 for o in globalopts:
3363 n = o[1]
3363 n = o[1]
3364 options[n] = cmdoptions[n]
3364 options[n] = cmdoptions[n]
3365 del cmdoptions[n]
3365 del cmdoptions[n]
3366
3366
3367 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
3367 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
3368
3368
3369 external = {}
3369 external = {}
3370
3370
3371 def findext(name):
3371 def findext(name):
3372 '''return module with given extension name'''
3372 '''return module with given extension name'''
3373 try:
3373 try:
3374 return sys.modules[external[name]]
3374 return sys.modules[external[name]]
3375 except KeyError:
3375 except KeyError:
3376 dotname = '.' + name
3376 dotname = '.' + name
3377 for k, v in external.iteritems():
3377 for k, v in external.iteritems():
3378 if k.endswith('.' + name) or v == name:
3378 if k.endswith('.' + name) or v == name:
3379 return sys.modules[v]
3379 return sys.modules[v]
3380 raise KeyError(name)
3380 raise KeyError(name)
3381
3381
3382 def dispatch(args):
3382 def dispatch(args):
3383 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
3383 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
3384 num = getattr(signal, name, None)
3384 num = getattr(signal, name, None)
3385 if num: signal.signal(num, catchterm)
3385 if num: signal.signal(num, catchterm)
3386
3386
3387 try:
3387 try:
3388 u = ui.ui(traceback='--traceback' in sys.argv[1:])
3388 u = ui.ui(traceback='--traceback' in sys.argv[1:])
3389 except util.Abort, inst:
3389 except util.Abort, inst:
3390 sys.stderr.write(_("abort: %s\n") % inst)
3390 sys.stderr.write(_("abort: %s\n") % inst)
3391 return -1
3391 return -1
3392
3392
3393 for ext_name, load_from_name in u.extensions():
3393 for ext_name, load_from_name in u.extensions():
3394 try:
3394 try:
3395 if load_from_name:
3395 if load_from_name:
3396 # the module will be loaded in sys.modules
3396 # the module will be loaded in sys.modules
3397 # choose an unique name so that it doesn't
3397 # choose an unique name so that it doesn't
3398 # conflicts with other modules
3398 # conflicts with other modules
3399 module_name = "hgext_%s" % ext_name.replace('.', '_')
3399 module_name = "hgext_%s" % ext_name.replace('.', '_')
3400 mod = imp.load_source(module_name, load_from_name)
3400 mod = imp.load_source(module_name, load_from_name)
3401 else:
3401 else:
3402 def importh(name):
3402 def importh(name):
3403 mod = __import__(name)
3403 mod = __import__(name)
3404 components = name.split('.')
3404 components = name.split('.')
3405 for comp in components[1:]:
3405 for comp in components[1:]:
3406 mod = getattr(mod, comp)
3406 mod = getattr(mod, comp)
3407 return mod
3407 return mod
3408 try:
3408 try:
3409 mod = importh("hgext.%s" % ext_name)
3409 mod = importh("hgext.%s" % ext_name)
3410 except ImportError:
3410 except ImportError:
3411 mod = importh(ext_name)
3411 mod = importh(ext_name)
3412 external[ext_name] = mod.__name__
3412 external[ext_name] = mod.__name__
3413 except (util.SignalInterrupt, KeyboardInterrupt):
3413 except (util.SignalInterrupt, KeyboardInterrupt):
3414 raise
3414 raise
3415 except Exception, inst:
3415 except Exception, inst:
3416 u.warn(_("*** failed to import extension %s: %s\n") % (x[0], inst))
3416 u.warn(_("*** failed to import extension %s: %s\n") % (x[0], inst))
3417 if u.print_exc():
3417 if u.print_exc():
3418 return 1
3418 return 1
3419
3419
3420 for name in external.itervalues():
3420 for name in external.itervalues():
3421 mod = sys.modules[name]
3421 mod = sys.modules[name]
3422 uisetup = getattr(mod, 'uisetup', None)
3422 uisetup = getattr(mod, 'uisetup', None)
3423 if uisetup:
3423 if uisetup:
3424 uisetup(u)
3424 uisetup(u)
3425 cmdtable = getattr(mod, 'cmdtable', {})
3425 cmdtable = getattr(mod, 'cmdtable', {})
3426 for t in cmdtable:
3426 for t in cmdtable:
3427 if t in table:
3427 if t in table:
3428 u.warn(_("module %s overrides %s\n") % (name, t))
3428 u.warn(_("module %s overrides %s\n") % (name, t))
3429 table.update(cmdtable)
3429 table.update(cmdtable)
3430
3430
3431 try:
3431 try:
3432 cmd, func, args, options, cmdoptions = parse(u, args)
3432 cmd, func, args, options, cmdoptions = parse(u, args)
3433 if options["time"]:
3433 if options["time"]:
3434 def get_times():
3434 def get_times():
3435 t = os.times()
3435 t = os.times()
3436 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
3436 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
3437 t = (t[0], t[1], t[2], t[3], time.clock())
3437 t = (t[0], t[1], t[2], t[3], time.clock())
3438 return t
3438 return t
3439 s = get_times()
3439 s = get_times()
3440 def print_time():
3440 def print_time():
3441 t = get_times()
3441 t = get_times()
3442 u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
3442 u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
3443 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
3443 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
3444 atexit.register(print_time)
3444 atexit.register(print_time)
3445
3445
3446 u.updateopts(options["verbose"], options["debug"], options["quiet"],
3446 u.updateopts(options["verbose"], options["debug"], options["quiet"],
3447 not options["noninteractive"], options["traceback"],
3447 not options["noninteractive"], options["traceback"],
3448 options["config"])
3448 options["config"])
3449
3449
3450 # enter the debugger before command execution
3450 # enter the debugger before command execution
3451 if options['debugger']:
3451 if options['debugger']:
3452 pdb.set_trace()
3452 pdb.set_trace()
3453
3453
3454 try:
3454 try:
3455 if options['cwd']:
3455 if options['cwd']:
3456 try:
3456 try:
3457 os.chdir(options['cwd'])
3457 os.chdir(options['cwd'])
3458 except OSError, inst:
3458 except OSError, inst:
3459 raise util.Abort('%s: %s' %
3459 raise util.Abort('%s: %s' %
3460 (options['cwd'], inst.strerror))
3460 (options['cwd'], inst.strerror))
3461
3461
3462 path = u.expandpath(options["repository"]) or ""
3462 path = u.expandpath(options["repository"]) or ""
3463 repo = path and hg.repository(u, path=path) or None
3463 repo = path and hg.repository(u, path=path) or None
3464
3464
3465 if options['help']:
3465 if options['help']:
3466 return help_(u, cmd, options['version'])
3466 return help_(u, cmd, options['version'])
3467 elif options['version']:
3467 elif options['version']:
3468 return show_version(u)
3468 return show_version(u)
3469 elif not cmd:
3469 elif not cmd:
3470 return help_(u, 'shortlist')
3470 return help_(u, 'shortlist')
3471
3471
3472 if cmd not in norepo.split():
3472 if cmd not in norepo.split():
3473 try:
3473 try:
3474 if not repo:
3474 if not repo:
3475 repo = hg.repository(u, path=path)
3475 repo = hg.repository(u, path=path)
3476 u = repo.ui
3476 u = repo.ui
3477 for name in external.itervalues():
3477 for name in external.itervalues():
3478 mod = sys.modules[name]
3478 mod = sys.modules[name]
3479 if hasattr(mod, 'reposetup'):
3479 if hasattr(mod, 'reposetup'):
3480 mod.reposetup(u, repo)
3480 mod.reposetup(u, repo)
3481 except hg.RepoError:
3481 except hg.RepoError:
3482 if cmd not in optionalrepo.split():
3482 if cmd not in optionalrepo.split():
3483 raise
3483 raise
3484 d = lambda: func(u, repo, *args, **cmdoptions)
3484 d = lambda: func(u, repo, *args, **cmdoptions)
3485 else:
3485 else:
3486 d = lambda: func(u, *args, **cmdoptions)
3486 d = lambda: func(u, *args, **cmdoptions)
3487
3487
3488 try:
3488 try:
3489 if options['profile']:
3489 if options['profile']:
3490 import hotshot, hotshot.stats
3490 import hotshot, hotshot.stats
3491 prof = hotshot.Profile("hg.prof")
3491 prof = hotshot.Profile("hg.prof")
3492 try:
3492 try:
3493 try:
3493 try:
3494 return prof.runcall(d)
3494 return prof.runcall(d)
3495 except:
3495 except:
3496 try:
3496 try:
3497 u.warn(_('exception raised - generating '
3497 u.warn(_('exception raised - generating '
3498 'profile anyway\n'))
3498 'profile anyway\n'))
3499 except:
3499 except:
3500 pass
3500 pass
3501 raise
3501 raise
3502 finally:
3502 finally:
3503 prof.close()
3503 prof.close()
3504 stats = hotshot.stats.load("hg.prof")
3504 stats = hotshot.stats.load("hg.prof")
3505 stats.strip_dirs()
3505 stats.strip_dirs()
3506 stats.sort_stats('time', 'calls')
3506 stats.sort_stats('time', 'calls')
3507 stats.print_stats(40)
3507 stats.print_stats(40)
3508 elif options['lsprof']:
3508 elif options['lsprof']:
3509 try:
3509 try:
3510 from mercurial import lsprof
3510 from mercurial import lsprof
3511 except ImportError:
3511 except ImportError:
3512 raise util.Abort(_(
3512 raise util.Abort(_(
3513 'lsprof not available - install from '
3513 'lsprof not available - install from '
3514 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
3514 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
3515 p = lsprof.Profiler()
3515 p = lsprof.Profiler()
3516 p.enable(subcalls=True)
3516 p.enable(subcalls=True)
3517 try:
3517 try:
3518 return d()
3518 return d()
3519 finally:
3519 finally:
3520 p.disable()
3520 p.disable()
3521 stats = lsprof.Stats(p.getstats())
3521 stats = lsprof.Stats(p.getstats())
3522 stats.sort()
3522 stats.sort()
3523 stats.pprint(top=10, file=sys.stderr, climit=5)
3523 stats.pprint(top=10, file=sys.stderr, climit=5)
3524 else:
3524 else:
3525 return d()
3525 return d()
3526 finally:
3526 finally:
3527 u.flush()
3527 u.flush()
3528 except:
3528 except:
3529 # enter the debugger when we hit an exception
3529 # enter the debugger when we hit an exception
3530 if options['debugger']:
3530 if options['debugger']:
3531 pdb.post_mortem(sys.exc_info()[2])
3531 pdb.post_mortem(sys.exc_info()[2])
3532 u.print_exc()
3532 u.print_exc()
3533 raise
3533 raise
3534 except ParseError, inst:
3534 except ParseError, inst:
3535 if inst.args[0]:
3535 if inst.args[0]:
3536 u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
3536 u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
3537 help_(u, inst.args[0])
3537 help_(u, inst.args[0])
3538 else:
3538 else:
3539 u.warn(_("hg: %s\n") % inst.args[1])
3539 u.warn(_("hg: %s\n") % inst.args[1])
3540 help_(u, 'shortlist')
3540 help_(u, 'shortlist')
3541 except AmbiguousCommand, inst:
3541 except AmbiguousCommand, inst:
3542 u.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
3542 u.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
3543 (inst.args[0], " ".join(inst.args[1])))
3543 (inst.args[0], " ".join(inst.args[1])))
3544 except UnknownCommand, inst:
3544 except UnknownCommand, inst:
3545 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
3545 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
3546 help_(u, 'shortlist')
3546 help_(u, 'shortlist')
3547 except hg.RepoError, inst:
3547 except hg.RepoError, inst:
3548 u.warn(_("abort: %s!\n") % inst)
3548 u.warn(_("abort: %s!\n") % inst)
3549 except lock.LockHeld, inst:
3549 except lock.LockHeld, inst:
3550 if inst.errno == errno.ETIMEDOUT:
3550 if inst.errno == errno.ETIMEDOUT:
3551 reason = _('timed out waiting for lock held by %s') % inst.locker
3551 reason = _('timed out waiting for lock held by %s') % inst.locker
3552 else:
3552 else:
3553 reason = _('lock held by %s') % inst.locker
3553 reason = _('lock held by %s') % inst.locker
3554 u.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
3554 u.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
3555 except lock.LockUnavailable, inst:
3555 except lock.LockUnavailable, inst:
3556 u.warn(_("abort: could not lock %s: %s\n") %
3556 u.warn(_("abort: could not lock %s: %s\n") %
3557 (inst.desc or inst.filename, inst.strerror))
3557 (inst.desc or inst.filename, inst.strerror))
3558 except revlog.RevlogError, inst:
3558 except revlog.RevlogError, inst:
3559 u.warn(_("abort: "), inst, "!\n")
3559 u.warn(_("abort: "), inst, "!\n")
3560 except util.SignalInterrupt:
3560 except util.SignalInterrupt:
3561 u.warn(_("killed!\n"))
3561 u.warn(_("killed!\n"))
3562 except KeyboardInterrupt:
3562 except KeyboardInterrupt:
3563 try:
3563 try:
3564 u.warn(_("interrupted!\n"))
3564 u.warn(_("interrupted!\n"))
3565 except IOError, inst:
3565 except IOError, inst:
3566 if inst.errno == errno.EPIPE:
3566 if inst.errno == errno.EPIPE:
3567 if u.debugflag:
3567 if u.debugflag:
3568 u.warn(_("\nbroken pipe\n"))
3568 u.warn(_("\nbroken pipe\n"))
3569 else:
3569 else:
3570 raise
3570 raise
3571 except IOError, inst:
3571 except IOError, inst:
3572 if hasattr(inst, "code"):
3572 if hasattr(inst, "code"):
3573 u.warn(_("abort: %s\n") % inst)
3573 u.warn(_("abort: %s\n") % inst)
3574 elif hasattr(inst, "reason"):
3574 elif hasattr(inst, "reason"):
3575 u.warn(_("abort: error: %s\n") % inst.reason[1])
3575 u.warn(_("abort: error: %s\n") % inst.reason[1])
3576 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
3576 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
3577 if u.debugflag:
3577 if u.debugflag:
3578 u.warn(_("broken pipe\n"))
3578 u.warn(_("broken pipe\n"))
3579 elif getattr(inst, "strerror", None):
3579 elif getattr(inst, "strerror", None):
3580 if getattr(inst, "filename", None):
3580 if getattr(inst, "filename", None):
3581 u.warn(_("abort: %s - %s\n") % (inst.strerror, inst.filename))
3581 u.warn(_("abort: %s - %s\n") % (inst.strerror, inst.filename))
3582 else:
3582 else:
3583 u.warn(_("abort: %s\n") % inst.strerror)
3583 u.warn(_("abort: %s\n") % inst.strerror)
3584 else:
3584 else:
3585 raise
3585 raise
3586 except OSError, inst:
3586 except OSError, inst:
3587 if hasattr(inst, "filename"):
3587 if hasattr(inst, "filename"):
3588 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
3588 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
3589 else:
3589 else:
3590 u.warn(_("abort: %s\n") % inst.strerror)
3590 u.warn(_("abort: %s\n") % inst.strerror)
3591 except util.Abort, inst:
3591 except util.Abort, inst:
3592 u.warn(_('abort: '), inst.args[0] % inst.args[1:], '\n')
3592 u.warn(_('abort: '), inst.args[0] % inst.args[1:], '\n')
3593 except TypeError, inst:
3593 except TypeError, inst:
3594 # was this an argument error?
3594 # was this an argument error?
3595 tb = traceback.extract_tb(sys.exc_info()[2])
3595 tb = traceback.extract_tb(sys.exc_info()[2])
3596 if len(tb) > 2: # no
3596 if len(tb) > 2: # no
3597 raise
3597 raise
3598 u.debug(inst, "\n")
3598 u.debug(inst, "\n")
3599 u.warn(_("%s: invalid arguments\n") % cmd)
3599 u.warn(_("%s: invalid arguments\n") % cmd)
3600 help_(u, cmd)
3600 help_(u, cmd)
3601 except SystemExit, inst:
3601 except SystemExit, inst:
3602 # Commands shouldn't sys.exit directly, but give a return code.
3602 # Commands shouldn't sys.exit directly, but give a return code.
3603 # Just in case catch this and and pass exit code to caller.
3603 # Just in case catch this and and pass exit code to caller.
3604 return inst.code
3604 return inst.code
3605 except:
3605 except:
3606 u.warn(_("** unknown exception encountered, details follow\n"))
3606 u.warn(_("** unknown exception encountered, details follow\n"))
3607 u.warn(_("** report bug details to mercurial@selenic.com\n"))
3607 u.warn(_("** report bug details to mercurial@selenic.com\n"))
3608 u.warn(_("** Mercurial Distributed SCM (version %s)\n")
3608 u.warn(_("** Mercurial Distributed SCM (version %s)\n")
3609 % version.get_version())
3609 % version.get_version())
3610 raise
3610 raise
3611
3611
3612 return -1
3612 return -1
@@ -1,107 +1,107 b''
1 # filelog.py - file history class for mercurial
1 # filelog.py - file history class for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from revlog import *
8 from revlog import *
9 from demandload import *
9 from demandload import *
10 demandload(globals(), "bdiff os")
10 demandload(globals(), "bdiff os")
11
11
12 class filelog(revlog):
12 class filelog(revlog):
13 def __init__(self, opener, path, defversion=REVLOG_DEFAULT_VERSION):
13 def __init__(self, opener, path, defversion=REVLOG_DEFAULT_VERSION):
14 revlog.__init__(self, opener,
14 revlog.__init__(self, opener,
15 os.path.join("data", self.encodedir(path + ".i")),
15 os.path.join("data", self.encodedir(path + ".i")),
16 os.path.join("data", self.encodedir(path + ".d")),
16 os.path.join("data", self.encodedir(path + ".d")),
17 defversion)
17 defversion)
18
18
19 # This avoids a collision between a file named foo and a dir named
19 # This avoids a collision between a file named foo and a dir named
20 # foo.i or foo.d
20 # foo.i or foo.d
21 def encodedir(self, path):
21 def encodedir(self, path):
22 return (path
22 return (path
23 .replace(".hg/", ".hg.hg/")
23 .replace(".hg/", ".hg.hg/")
24 .replace(".i/", ".i.hg/")
24 .replace(".i/", ".i.hg/")
25 .replace(".d/", ".d.hg/"))
25 .replace(".d/", ".d.hg/"))
26
26
27 def decodedir(self, path):
27 def decodedir(self, path):
28 return (path
28 return (path
29 .replace(".d.hg/", ".d/")
29 .replace(".d.hg/", ".d/")
30 .replace(".i.hg/", ".i/")
30 .replace(".i.hg/", ".i/")
31 .replace(".hg.hg/", ".hg/"))
31 .replace(".hg.hg/", ".hg/"))
32
32
33 def read(self, node):
33 def read(self, node):
34 t = self.revision(node)
34 t = self.revision(node)
35 if not t.startswith('\1\n'):
35 if not t.startswith('\1\n'):
36 return t
36 return t
37 s = t.find('\1\n', 2)
37 s = t.index('\1\n', 2)
38 return t[s+2:]
38 return t[s+2:]
39
39
40 def readmeta(self, node):
40 def readmeta(self, node):
41 t = self.revision(node)
41 t = self.revision(node)
42 if not t.startswith('\1\n'):
42 if not t.startswith('\1\n'):
43 return {}
43 return {}
44 s = t.find('\1\n', 2)
44 s = t.index('\1\n', 2)
45 mt = t[2:s]
45 mt = t[2:s]
46 m = {}
46 m = {}
47 for l in mt.splitlines():
47 for l in mt.splitlines():
48 k, v = l.split(": ", 1)
48 k, v = l.split(": ", 1)
49 m[k] = v
49 m[k] = v
50 return m
50 return m
51
51
52 def add(self, text, meta, transaction, link, p1=None, p2=None):
52 def add(self, text, meta, transaction, link, p1=None, p2=None):
53 if meta or text.startswith('\1\n'):
53 if meta or text.startswith('\1\n'):
54 mt = ""
54 mt = ""
55 if meta:
55 if meta:
56 mt = [ "%s: %s\n" % (k, v) for k,v in meta.items() ]
56 mt = [ "%s: %s\n" % (k, v) for k,v in meta.items() ]
57 text = "\1\n%s\1\n%s" % ("".join(mt), text)
57 text = "\1\n%s\1\n%s" % ("".join(mt), text)
58 return self.addrevision(text, transaction, link, p1, p2)
58 return self.addrevision(text, transaction, link, p1, p2)
59
59
60 def renamed(self, node):
60 def renamed(self, node):
61 if self.parents(node)[0] != nullid:
61 if self.parents(node)[0] != nullid:
62 return False
62 return False
63 m = self.readmeta(node)
63 m = self.readmeta(node)
64 if m and m.has_key("copy"):
64 if m and m.has_key("copy"):
65 return (m["copy"], bin(m["copyrev"]))
65 return (m["copy"], bin(m["copyrev"]))
66 return False
66 return False
67
67
68 def annotate(self, node):
68 def annotate(self, node):
69
69
70 def decorate(text, rev):
70 def decorate(text, rev):
71 return ([rev] * len(text.splitlines()), text)
71 return ([rev] * len(text.splitlines()), text)
72
72
73 def pair(parent, child):
73 def pair(parent, child):
74 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
74 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
75 child[0][b1:b2] = parent[0][a1:a2]
75 child[0][b1:b2] = parent[0][a1:a2]
76 return child
76 return child
77
77
78 # find all ancestors
78 # find all ancestors
79 needed = {node:1}
79 needed = {node:1}
80 visit = [node]
80 visit = [node]
81 while visit:
81 while visit:
82 n = visit.pop(0)
82 n = visit.pop(0)
83 for p in self.parents(n):
83 for p in self.parents(n):
84 if p not in needed:
84 if p not in needed:
85 needed[p] = 1
85 needed[p] = 1
86 visit.append(p)
86 visit.append(p)
87 else:
87 else:
88 # count how many times we'll use this
88 # count how many times we'll use this
89 needed[p] += 1
89 needed[p] += 1
90
90
91 # sort by revision which is a topological order
91 # sort by revision which is a topological order
92 visit = [ (self.rev(n), n) for n in needed.keys() ]
92 visit = [ (self.rev(n), n) for n in needed.keys() ]
93 visit.sort()
93 visit.sort()
94 hist = {}
94 hist = {}
95
95
96 for r,n in visit:
96 for r,n in visit:
97 curr = decorate(self.read(n), self.linkrev(n))
97 curr = decorate(self.read(n), self.linkrev(n))
98 for p in self.parents(n):
98 for p in self.parents(n):
99 if p != nullid:
99 if p != nullid:
100 curr = pair(hist[p], curr)
100 curr = pair(hist[p], curr)
101 # trim the history of unneeded revs
101 # trim the history of unneeded revs
102 needed[p] -= 1
102 needed[p] -= 1
103 if not needed[p]:
103 if not needed[p]:
104 del hist[p]
104 del hist[p]
105 hist[n] = curr
105 hist[n] = curr
106
106
107 return zip(hist[n][0], hist[n][1].splitlines(1))
107 return zip(hist[n][0], hist[n][1].splitlines(1))
@@ -1,73 +1,72 b''
1 # hg.py - repository classes for mercurial
1 # hg.py - repository classes for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from node import *
8 from node import *
9 from repo import *
9 from repo import *
10 from demandload import *
10 from demandload import *
11 from i18n import gettext as _
11 from i18n import gettext as _
12 demandload(globals(), "localrepo bundlerepo httprepo sshrepo statichttprepo")
12 demandload(globals(), "localrepo bundlerepo httprepo sshrepo statichttprepo")
13 demandload(globals(), "os util")
13 demandload(globals(), "os util")
14
14
15 def bundle(ui, path):
15 def bundle(ui, path):
16 if path.startswith('bundle://'):
16 if path.startswith('bundle://'):
17 path = path[9:]
17 path = path[9:]
18 else:
18 else:
19 path = path[7:]
19 path = path[7:]
20 s = path.split("+", 1)
20 s = path.split("+", 1)
21 if len(s) == 1:
21 if len(s) == 1:
22 repopath, bundlename = "", s[0]
22 repopath, bundlename = "", s[0]
23 else:
23 else:
24 repopath, bundlename = s
24 repopath, bundlename = s
25 return bundlerepo.bundlerepository(ui, repopath, bundlename)
25 return bundlerepo.bundlerepository(ui, repopath, bundlename)
26
26
27 def hg(ui, path):
27 def hg(ui, path):
28 ui.warn(_("hg:// syntax is deprecated, please use http:// instead\n"))
28 ui.warn(_("hg:// syntax is deprecated, please use http:// instead\n"))
29 return httprepo.httprepository(ui, path.replace("hg://", "http://"))
29 return httprepo.httprepository(ui, path.replace("hg://", "http://"))
30
30
31 def local_(ui, path, create=0):
31 def local_(ui, path, create=0):
32 if path.startswith('file:'):
32 if path.startswith('file:'):
33 path = path[5:]
33 path = path[5:]
34 return localrepo.localrepository(ui, path, create)
34 return localrepo.localrepository(ui, path, create)
35
35
36 def ssh_(ui, path, create=0):
36 def ssh_(ui, path, create=0):
37 return sshrepo.sshrepository(ui, path, create)
37 return sshrepo.sshrepository(ui, path, create)
38
38
39 def old_http(ui, path):
39 def old_http(ui, path):
40 ui.warn(_("old-http:// syntax is deprecated, "
40 ui.warn(_("old-http:// syntax is deprecated, "
41 "please use static-http:// instead\n"))
41 "please use static-http:// instead\n"))
42 return statichttprepo.statichttprepository(
42 return statichttprepo.statichttprepository(
43 ui, path.replace("old-http://", "http://"))
43 ui, path.replace("old-http://", "http://"))
44
44
45 def static_http(ui, path):
45 def static_http(ui, path):
46 return statichttprepo.statichttprepository(
46 return statichttprepo.statichttprepository(
47 ui, path.replace("static-http://", "http://"))
47 ui, path.replace("static-http://", "http://"))
48
48
49 schemes = {
49 schemes = {
50 'bundle': bundle,
50 'bundle': bundle,
51 'file': local_,
51 'file': local_,
52 'hg': hg,
52 'hg': hg,
53 'http': lambda ui, path: httprepo.httprepository(ui, path),
53 'http': lambda ui, path: httprepo.httprepository(ui, path),
54 'https': lambda ui, path: httprepo.httpsrepository(ui, path),
54 'https': lambda ui, path: httprepo.httpsrepository(ui, path),
55 'old-http': old_http,
55 'old-http': old_http,
56 'ssh': ssh_,
56 'ssh': ssh_,
57 'static-http': static_http,
57 'static-http': static_http,
58 }
58 }
59
59
60 def repository(ui, path=None, create=0):
60 def repository(ui, path=None, create=0):
61 if not path: path = ''
61 if not path: path = ''
62 scheme = path
62 scheme = path
63 if scheme:
63 if scheme:
64 c = scheme.find(':')
64 scheme = scheme.split(":", 1)[0]
65 scheme = c >= 0 and scheme[:c]
66 ctor = schemes.get(scheme) or schemes['file']
65 ctor = schemes.get(scheme) or schemes['file']
67 if create:
66 if create:
68 try:
67 try:
69 return ctor(ui, path, create)
68 return ctor(ui, path, create)
70 except TypeError:
69 except TypeError:
71 raise util.Abort(_('cannot create new repository over "%s" protocol') %
70 raise util.Abort(_('cannot create new repository over "%s" protocol') %
72 scheme)
71 scheme)
73 return ctor(ui, path)
72 return ctor(ui, path)
@@ -1,944 +1,944 b''
1 # hgweb/hgweb_mod.py - Web interface for a repository.
1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os
9 import os
10 import os.path
10 import os.path
11 import mimetypes
11 import mimetypes
12 from mercurial.demandload import demandload
12 from mercurial.demandload import demandload
13 demandload(globals(), "re zlib ConfigParser mimetools cStringIO sys tempfile")
13 demandload(globals(), "re zlib ConfigParser mimetools cStringIO sys tempfile")
14 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater")
14 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater")
15 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
15 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
16 from mercurial.node import *
16 from mercurial.node import *
17 from mercurial.i18n import gettext as _
17 from mercurial.i18n import gettext as _
18
18
19 def _up(p):
19 def _up(p):
20 if p[0] != "/":
20 if p[0] != "/":
21 p = "/" + p
21 p = "/" + p
22 if p[-1] == "/":
22 if p[-1] == "/":
23 p = p[:-1]
23 p = p[:-1]
24 up = os.path.dirname(p)
24 up = os.path.dirname(p)
25 if up == "/":
25 if up == "/":
26 return "/"
26 return "/"
27 return up + "/"
27 return up + "/"
28
28
29 class hgweb(object):
29 class hgweb(object):
30 def __init__(self, repo, name=None):
30 def __init__(self, repo, name=None):
31 if type(repo) == type(""):
31 if type(repo) == type(""):
32 self.repo = hg.repository(ui.ui(), repo)
32 self.repo = hg.repository(ui.ui(), repo)
33 else:
33 else:
34 self.repo = repo
34 self.repo = repo
35
35
36 self.mtime = -1
36 self.mtime = -1
37 self.reponame = name
37 self.reponame = name
38 self.archives = 'zip', 'gz', 'bz2'
38 self.archives = 'zip', 'gz', 'bz2'
39 self.templatepath = self.repo.ui.config("web", "templates",
39 self.templatepath = self.repo.ui.config("web", "templates",
40 templater.templatepath())
40 templater.templatepath())
41
41
42 def refresh(self):
42 def refresh(self):
43 mtime = get_mtime(self.repo.root)
43 mtime = get_mtime(self.repo.root)
44 if mtime != self.mtime:
44 if mtime != self.mtime:
45 self.mtime = mtime
45 self.mtime = mtime
46 self.repo = hg.repository(self.repo.ui, self.repo.root)
46 self.repo = hg.repository(self.repo.ui, self.repo.root)
47 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
47 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
48 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
48 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
49 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
49 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
50
50
51 def archivelist(self, nodeid):
51 def archivelist(self, nodeid):
52 allowed = self.repo.ui.configlist("web", "allow_archive")
52 allowed = self.repo.ui.configlist("web", "allow_archive")
53 for i in self.archives:
53 for i in self.archives:
54 if i in allowed or self.repo.ui.configbool("web", "allow" + i):
54 if i in allowed or self.repo.ui.configbool("web", "allow" + i):
55 yield {"type" : i, "node" : nodeid, "url": ""}
55 yield {"type" : i, "node" : nodeid, "url": ""}
56
56
57 def listfiles(self, files, mf):
57 def listfiles(self, files, mf):
58 for f in files[:self.maxfiles]:
58 for f in files[:self.maxfiles]:
59 yield self.t("filenodelink", node=hex(mf[f]), file=f)
59 yield self.t("filenodelink", node=hex(mf[f]), file=f)
60 if len(files) > self.maxfiles:
60 if len(files) > self.maxfiles:
61 yield self.t("fileellipses")
61 yield self.t("fileellipses")
62
62
63 def listfilediffs(self, files, changeset):
63 def listfilediffs(self, files, changeset):
64 for f in files[:self.maxfiles]:
64 for f in files[:self.maxfiles]:
65 yield self.t("filedifflink", node=hex(changeset), file=f)
65 yield self.t("filedifflink", node=hex(changeset), file=f)
66 if len(files) > self.maxfiles:
66 if len(files) > self.maxfiles:
67 yield self.t("fileellipses")
67 yield self.t("fileellipses")
68
68
69 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
69 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
70 if not rev:
70 if not rev:
71 rev = lambda x: ""
71 rev = lambda x: ""
72 siblings = [s for s in siblings if s != nullid]
72 siblings = [s for s in siblings if s != nullid]
73 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
73 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
74 return
74 return
75 for s in siblings:
75 for s in siblings:
76 yield dict(node=hex(s), rev=rev(s), **args)
76 yield dict(node=hex(s), rev=rev(s), **args)
77
77
78 def renamelink(self, fl, node):
78 def renamelink(self, fl, node):
79 r = fl.renamed(node)
79 r = fl.renamed(node)
80 if r:
80 if r:
81 return [dict(file=r[0], node=hex(r[1]))]
81 return [dict(file=r[0], node=hex(r[1]))]
82 return []
82 return []
83
83
84 def showtag(self, t1, node=nullid, **args):
84 def showtag(self, t1, node=nullid, **args):
85 for t in self.repo.nodetags(node):
85 for t in self.repo.nodetags(node):
86 yield self.t(t1, tag=t, **args)
86 yield self.t(t1, tag=t, **args)
87
87
88 def diff(self, node1, node2, files):
88 def diff(self, node1, node2, files):
89 def filterfiles(filters, files):
89 def filterfiles(filters, files):
90 l = [x for x in files if x in filters]
90 l = [x for x in files if x in filters]
91
91
92 for t in filters:
92 for t in filters:
93 if t and t[-1] != os.sep:
93 if t and t[-1] != os.sep:
94 t += os.sep
94 t += os.sep
95 l += [x for x in files if x.startswith(t)]
95 l += [x for x in files if x.startswith(t)]
96 return l
96 return l
97
97
98 parity = [0]
98 parity = [0]
99 def diffblock(diff, f, fn):
99 def diffblock(diff, f, fn):
100 yield self.t("diffblock",
100 yield self.t("diffblock",
101 lines=prettyprintlines(diff),
101 lines=prettyprintlines(diff),
102 parity=parity[0],
102 parity=parity[0],
103 file=f,
103 file=f,
104 filenode=hex(fn or nullid))
104 filenode=hex(fn or nullid))
105 parity[0] = 1 - parity[0]
105 parity[0] = 1 - parity[0]
106
106
107 def prettyprintlines(diff):
107 def prettyprintlines(diff):
108 for l in diff.splitlines(1):
108 for l in diff.splitlines(1):
109 if l.startswith('+'):
109 if l.startswith('+'):
110 yield self.t("difflineplus", line=l)
110 yield self.t("difflineplus", line=l)
111 elif l.startswith('-'):
111 elif l.startswith('-'):
112 yield self.t("difflineminus", line=l)
112 yield self.t("difflineminus", line=l)
113 elif l.startswith('@'):
113 elif l.startswith('@'):
114 yield self.t("difflineat", line=l)
114 yield self.t("difflineat", line=l)
115 else:
115 else:
116 yield self.t("diffline", line=l)
116 yield self.t("diffline", line=l)
117
117
118 r = self.repo
118 r = self.repo
119 cl = r.changelog
119 cl = r.changelog
120 mf = r.manifest
120 mf = r.manifest
121 change1 = cl.read(node1)
121 change1 = cl.read(node1)
122 change2 = cl.read(node2)
122 change2 = cl.read(node2)
123 mmap1 = mf.read(change1[0])
123 mmap1 = mf.read(change1[0])
124 mmap2 = mf.read(change2[0])
124 mmap2 = mf.read(change2[0])
125 date1 = util.datestr(change1[2])
125 date1 = util.datestr(change1[2])
126 date2 = util.datestr(change2[2])
126 date2 = util.datestr(change2[2])
127
127
128 modified, added, removed, deleted, unknown = r.changes(node1, node2)
128 modified, added, removed, deleted, unknown = r.changes(node1, node2)
129 if files:
129 if files:
130 modified, added, removed = map(lambda x: filterfiles(files, x),
130 modified, added, removed = map(lambda x: filterfiles(files, x),
131 (modified, added, removed))
131 (modified, added, removed))
132
132
133 diffopts = self.repo.ui.diffopts()
133 diffopts = self.repo.ui.diffopts()
134 showfunc = diffopts['showfunc']
134 showfunc = diffopts['showfunc']
135 ignorews = diffopts['ignorews']
135 ignorews = diffopts['ignorews']
136 for f in modified:
136 for f in modified:
137 to = r.file(f).read(mmap1[f])
137 to = r.file(f).read(mmap1[f])
138 tn = r.file(f).read(mmap2[f])
138 tn = r.file(f).read(mmap2[f])
139 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
139 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
140 showfunc=showfunc, ignorews=ignorews), f, tn)
140 showfunc=showfunc, ignorews=ignorews), f, tn)
141 for f in added:
141 for f in added:
142 to = None
142 to = None
143 tn = r.file(f).read(mmap2[f])
143 tn = r.file(f).read(mmap2[f])
144 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
144 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
145 showfunc=showfunc, ignorews=ignorews), f, tn)
145 showfunc=showfunc, ignorews=ignorews), f, tn)
146 for f in removed:
146 for f in removed:
147 to = r.file(f).read(mmap1[f])
147 to = r.file(f).read(mmap1[f])
148 tn = None
148 tn = None
149 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
149 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
150 showfunc=showfunc, ignorews=ignorews), f, tn)
150 showfunc=showfunc, ignorews=ignorews), f, tn)
151
151
152 def changelog(self, pos):
152 def changelog(self, pos):
153 def changenav(**map):
153 def changenav(**map):
154 def seq(factor, maxchanges=None):
154 def seq(factor, maxchanges=None):
155 if maxchanges:
155 if maxchanges:
156 yield maxchanges
156 yield maxchanges
157 if maxchanges >= 20 and maxchanges <= 40:
157 if maxchanges >= 20 and maxchanges <= 40:
158 yield 50
158 yield 50
159 else:
159 else:
160 yield 1 * factor
160 yield 1 * factor
161 yield 3 * factor
161 yield 3 * factor
162 for f in seq(factor * 10):
162 for f in seq(factor * 10):
163 yield f
163 yield f
164
164
165 l = []
165 l = []
166 last = 0
166 last = 0
167 for f in seq(1, self.maxchanges):
167 for f in seq(1, self.maxchanges):
168 if f < self.maxchanges or f <= last:
168 if f < self.maxchanges or f <= last:
169 continue
169 continue
170 if f > count:
170 if f > count:
171 break
171 break
172 last = f
172 last = f
173 r = "%d" % f
173 r = "%d" % f
174 if pos + f < count:
174 if pos + f < count:
175 l.append(("+" + r, pos + f))
175 l.append(("+" + r, pos + f))
176 if pos - f >= 0:
176 if pos - f >= 0:
177 l.insert(0, ("-" + r, pos - f))
177 l.insert(0, ("-" + r, pos - f))
178
178
179 yield {"rev": 0, "label": "(0)"}
179 yield {"rev": 0, "label": "(0)"}
180
180
181 for label, rev in l:
181 for label, rev in l:
182 yield {"label": label, "rev": rev}
182 yield {"label": label, "rev": rev}
183
183
184 yield {"label": "tip", "rev": "tip"}
184 yield {"label": "tip", "rev": "tip"}
185
185
186 def changelist(**map):
186 def changelist(**map):
187 parity = (start - end) & 1
187 parity = (start - end) & 1
188 cl = self.repo.changelog
188 cl = self.repo.changelog
189 l = [] # build a list in forward order for efficiency
189 l = [] # build a list in forward order for efficiency
190 for i in range(start, end):
190 for i in range(start, end):
191 n = cl.node(i)
191 n = cl.node(i)
192 changes = cl.read(n)
192 changes = cl.read(n)
193 hn = hex(n)
193 hn = hex(n)
194
194
195 l.insert(0, {"parity": parity,
195 l.insert(0, {"parity": parity,
196 "author": changes[1],
196 "author": changes[1],
197 "parent": self.siblings(cl.parents(n), cl.rev,
197 "parent": self.siblings(cl.parents(n), cl.rev,
198 cl.rev(n) - 1),
198 cl.rev(n) - 1),
199 "child": self.siblings(cl.children(n), cl.rev,
199 "child": self.siblings(cl.children(n), cl.rev,
200 cl.rev(n) + 1),
200 cl.rev(n) + 1),
201 "changelogtag": self.showtag("changelogtag",n),
201 "changelogtag": self.showtag("changelogtag",n),
202 "manifest": hex(changes[0]),
202 "manifest": hex(changes[0]),
203 "desc": changes[4],
203 "desc": changes[4],
204 "date": changes[2],
204 "date": changes[2],
205 "files": self.listfilediffs(changes[3], n),
205 "files": self.listfilediffs(changes[3], n),
206 "rev": i,
206 "rev": i,
207 "node": hn})
207 "node": hn})
208 parity = 1 - parity
208 parity = 1 - parity
209
209
210 for e in l:
210 for e in l:
211 yield e
211 yield e
212
212
213 cl = self.repo.changelog
213 cl = self.repo.changelog
214 mf = cl.read(cl.tip())[0]
214 mf = cl.read(cl.tip())[0]
215 count = cl.count()
215 count = cl.count()
216 start = max(0, pos - self.maxchanges + 1)
216 start = max(0, pos - self.maxchanges + 1)
217 end = min(count, start + self.maxchanges)
217 end = min(count, start + self.maxchanges)
218 pos = end - 1
218 pos = end - 1
219
219
220 yield self.t('changelog',
220 yield self.t('changelog',
221 changenav=changenav,
221 changenav=changenav,
222 manifest=hex(mf),
222 manifest=hex(mf),
223 rev=pos, changesets=count, entries=changelist,
223 rev=pos, changesets=count, entries=changelist,
224 archives=self.archivelist("tip"))
224 archives=self.archivelist("tip"))
225
225
226 def search(self, query):
226 def search(self, query):
227
227
228 def changelist(**map):
228 def changelist(**map):
229 cl = self.repo.changelog
229 cl = self.repo.changelog
230 count = 0
230 count = 0
231 qw = query.lower().split()
231 qw = query.lower().split()
232
232
233 def revgen():
233 def revgen():
234 for i in range(cl.count() - 1, 0, -100):
234 for i in range(cl.count() - 1, 0, -100):
235 l = []
235 l = []
236 for j in range(max(0, i - 100), i):
236 for j in range(max(0, i - 100), i):
237 n = cl.node(j)
237 n = cl.node(j)
238 changes = cl.read(n)
238 changes = cl.read(n)
239 l.append((n, j, changes))
239 l.append((n, j, changes))
240 l.reverse()
240 l.reverse()
241 for e in l:
241 for e in l:
242 yield e
242 yield e
243
243
244 for n, i, changes in revgen():
244 for n, i, changes in revgen():
245 miss = 0
245 miss = 0
246 for q in qw:
246 for q in qw:
247 if not (q in changes[1].lower() or
247 if not (q in changes[1].lower() or
248 q in changes[4].lower() or
248 q in changes[4].lower() or
249 q in " ".join(changes[3][:20]).lower()):
249 q in " ".join(changes[3][:20]).lower()):
250 miss = 1
250 miss = 1
251 break
251 break
252 if miss:
252 if miss:
253 continue
253 continue
254
254
255 count += 1
255 count += 1
256 hn = hex(n)
256 hn = hex(n)
257
257
258 yield self.t('searchentry',
258 yield self.t('searchentry',
259 parity=count & 1,
259 parity=count & 1,
260 author=changes[1],
260 author=changes[1],
261 parent=self.siblings(cl.parents(n), cl.rev),
261 parent=self.siblings(cl.parents(n), cl.rev),
262 child=self.siblings(cl.children(n), cl.rev),
262 child=self.siblings(cl.children(n), cl.rev),
263 changelogtag=self.showtag("changelogtag",n),
263 changelogtag=self.showtag("changelogtag",n),
264 manifest=hex(changes[0]),
264 manifest=hex(changes[0]),
265 desc=changes[4],
265 desc=changes[4],
266 date=changes[2],
266 date=changes[2],
267 files=self.listfilediffs(changes[3], n),
267 files=self.listfilediffs(changes[3], n),
268 rev=i,
268 rev=i,
269 node=hn)
269 node=hn)
270
270
271 if count >= self.maxchanges:
271 if count >= self.maxchanges:
272 break
272 break
273
273
274 cl = self.repo.changelog
274 cl = self.repo.changelog
275 mf = cl.read(cl.tip())[0]
275 mf = cl.read(cl.tip())[0]
276
276
277 yield self.t('search',
277 yield self.t('search',
278 query=query,
278 query=query,
279 manifest=hex(mf),
279 manifest=hex(mf),
280 entries=changelist)
280 entries=changelist)
281
281
282 def changeset(self, nodeid):
282 def changeset(self, nodeid):
283 cl = self.repo.changelog
283 cl = self.repo.changelog
284 n = self.repo.lookup(nodeid)
284 n = self.repo.lookup(nodeid)
285 nodeid = hex(n)
285 nodeid = hex(n)
286 changes = cl.read(n)
286 changes = cl.read(n)
287 p1 = cl.parents(n)[0]
287 p1 = cl.parents(n)[0]
288
288
289 files = []
289 files = []
290 mf = self.repo.manifest.read(changes[0])
290 mf = self.repo.manifest.read(changes[0])
291 for f in changes[3]:
291 for f in changes[3]:
292 files.append(self.t("filenodelink",
292 files.append(self.t("filenodelink",
293 filenode=hex(mf.get(f, nullid)), file=f))
293 filenode=hex(mf.get(f, nullid)), file=f))
294
294
295 def diff(**map):
295 def diff(**map):
296 yield self.diff(p1, n, None)
296 yield self.diff(p1, n, None)
297
297
298 yield self.t('changeset',
298 yield self.t('changeset',
299 diff=diff,
299 diff=diff,
300 rev=cl.rev(n),
300 rev=cl.rev(n),
301 node=nodeid,
301 node=nodeid,
302 parent=self.siblings(cl.parents(n), cl.rev),
302 parent=self.siblings(cl.parents(n), cl.rev),
303 child=self.siblings(cl.children(n), cl.rev),
303 child=self.siblings(cl.children(n), cl.rev),
304 changesettag=self.showtag("changesettag",n),
304 changesettag=self.showtag("changesettag",n),
305 manifest=hex(changes[0]),
305 manifest=hex(changes[0]),
306 author=changes[1],
306 author=changes[1],
307 desc=changes[4],
307 desc=changes[4],
308 date=changes[2],
308 date=changes[2],
309 files=files,
309 files=files,
310 archives=self.archivelist(nodeid))
310 archives=self.archivelist(nodeid))
311
311
312 def filelog(self, f, filenode):
312 def filelog(self, f, filenode):
313 cl = self.repo.changelog
313 cl = self.repo.changelog
314 fl = self.repo.file(f)
314 fl = self.repo.file(f)
315 filenode = hex(fl.lookup(filenode))
315 filenode = hex(fl.lookup(filenode))
316 count = fl.count()
316 count = fl.count()
317
317
318 def entries(**map):
318 def entries(**map):
319 l = []
319 l = []
320 parity = (count - 1) & 1
320 parity = (count - 1) & 1
321
321
322 for i in range(count):
322 for i in range(count):
323 n = fl.node(i)
323 n = fl.node(i)
324 lr = fl.linkrev(n)
324 lr = fl.linkrev(n)
325 cn = cl.node(lr)
325 cn = cl.node(lr)
326 cs = cl.read(cl.node(lr))
326 cs = cl.read(cl.node(lr))
327
327
328 l.insert(0, {"parity": parity,
328 l.insert(0, {"parity": parity,
329 "filenode": hex(n),
329 "filenode": hex(n),
330 "filerev": i,
330 "filerev": i,
331 "file": f,
331 "file": f,
332 "node": hex(cn),
332 "node": hex(cn),
333 "author": cs[1],
333 "author": cs[1],
334 "date": cs[2],
334 "date": cs[2],
335 "rename": self.renamelink(fl, n),
335 "rename": self.renamelink(fl, n),
336 "parent": self.siblings(fl.parents(n),
336 "parent": self.siblings(fl.parents(n),
337 fl.rev, file=f),
337 fl.rev, file=f),
338 "child": self.siblings(fl.children(n),
338 "child": self.siblings(fl.children(n),
339 fl.rev, file=f),
339 fl.rev, file=f),
340 "desc": cs[4]})
340 "desc": cs[4]})
341 parity = 1 - parity
341 parity = 1 - parity
342
342
343 for e in l:
343 for e in l:
344 yield e
344 yield e
345
345
346 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
346 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
347
347
348 def filerevision(self, f, node):
348 def filerevision(self, f, node):
349 fl = self.repo.file(f)
349 fl = self.repo.file(f)
350 n = fl.lookup(node)
350 n = fl.lookup(node)
351 node = hex(n)
351 node = hex(n)
352 text = fl.read(n)
352 text = fl.read(n)
353 changerev = fl.linkrev(n)
353 changerev = fl.linkrev(n)
354 cl = self.repo.changelog
354 cl = self.repo.changelog
355 cn = cl.node(changerev)
355 cn = cl.node(changerev)
356 cs = cl.read(cn)
356 cs = cl.read(cn)
357 mfn = cs[0]
357 mfn = cs[0]
358
358
359 mt = mimetypes.guess_type(f)[0]
359 mt = mimetypes.guess_type(f)[0]
360 rawtext = text
360 rawtext = text
361 if util.binary(text):
361 if util.binary(text):
362 mt = mt or 'application/octet-stream'
362 mt = mt or 'application/octet-stream'
363 text = "(binary:%s)" % mt
363 text = "(binary:%s)" % mt
364 mt = mt or 'text/plain'
364 mt = mt or 'text/plain'
365
365
366 def lines():
366 def lines():
367 for l, t in enumerate(text.splitlines(1)):
367 for l, t in enumerate(text.splitlines(1)):
368 yield {"line": t,
368 yield {"line": t,
369 "linenumber": "% 6d" % (l + 1),
369 "linenumber": "% 6d" % (l + 1),
370 "parity": l & 1}
370 "parity": l & 1}
371
371
372 yield self.t("filerevision",
372 yield self.t("filerevision",
373 file=f,
373 file=f,
374 filenode=node,
374 filenode=node,
375 path=_up(f),
375 path=_up(f),
376 text=lines(),
376 text=lines(),
377 raw=rawtext,
377 raw=rawtext,
378 mimetype=mt,
378 mimetype=mt,
379 rev=changerev,
379 rev=changerev,
380 node=hex(cn),
380 node=hex(cn),
381 manifest=hex(mfn),
381 manifest=hex(mfn),
382 author=cs[1],
382 author=cs[1],
383 date=cs[2],
383 date=cs[2],
384 parent=self.siblings(fl.parents(n), fl.rev, file=f),
384 parent=self.siblings(fl.parents(n), fl.rev, file=f),
385 child=self.siblings(fl.children(n), fl.rev, file=f),
385 child=self.siblings(fl.children(n), fl.rev, file=f),
386 rename=self.renamelink(fl, n),
386 rename=self.renamelink(fl, n),
387 permissions=self.repo.manifest.readflags(mfn)[f])
387 permissions=self.repo.manifest.readflags(mfn)[f])
388
388
389 def fileannotate(self, f, node):
389 def fileannotate(self, f, node):
390 bcache = {}
390 bcache = {}
391 ncache = {}
391 ncache = {}
392 fl = self.repo.file(f)
392 fl = self.repo.file(f)
393 n = fl.lookup(node)
393 n = fl.lookup(node)
394 node = hex(n)
394 node = hex(n)
395 changerev = fl.linkrev(n)
395 changerev = fl.linkrev(n)
396
396
397 cl = self.repo.changelog
397 cl = self.repo.changelog
398 cn = cl.node(changerev)
398 cn = cl.node(changerev)
399 cs = cl.read(cn)
399 cs = cl.read(cn)
400 mfn = cs[0]
400 mfn = cs[0]
401
401
402 def annotate(**map):
402 def annotate(**map):
403 parity = 1
403 parity = 1
404 last = None
404 last = None
405 for r, l in fl.annotate(n):
405 for r, l in fl.annotate(n):
406 try:
406 try:
407 cnode = ncache[r]
407 cnode = ncache[r]
408 except KeyError:
408 except KeyError:
409 cnode = ncache[r] = self.repo.changelog.node(r)
409 cnode = ncache[r] = self.repo.changelog.node(r)
410
410
411 try:
411 try:
412 name = bcache[r]
412 name = bcache[r]
413 except KeyError:
413 except KeyError:
414 cl = self.repo.changelog.read(cnode)
414 cl = self.repo.changelog.read(cnode)
415 bcache[r] = name = self.repo.ui.shortuser(cl[1])
415 bcache[r] = name = self.repo.ui.shortuser(cl[1])
416
416
417 if last != cnode:
417 if last != cnode:
418 parity = 1 - parity
418 parity = 1 - parity
419 last = cnode
419 last = cnode
420
420
421 yield {"parity": parity,
421 yield {"parity": parity,
422 "node": hex(cnode),
422 "node": hex(cnode),
423 "rev": r,
423 "rev": r,
424 "author": name,
424 "author": name,
425 "file": f,
425 "file": f,
426 "line": l}
426 "line": l}
427
427
428 yield self.t("fileannotate",
428 yield self.t("fileannotate",
429 file=f,
429 file=f,
430 filenode=node,
430 filenode=node,
431 annotate=annotate,
431 annotate=annotate,
432 path=_up(f),
432 path=_up(f),
433 rev=changerev,
433 rev=changerev,
434 node=hex(cn),
434 node=hex(cn),
435 manifest=hex(mfn),
435 manifest=hex(mfn),
436 author=cs[1],
436 author=cs[1],
437 date=cs[2],
437 date=cs[2],
438 rename=self.renamelink(fl, n),
438 rename=self.renamelink(fl, n),
439 parent=self.siblings(fl.parents(n), fl.rev, file=f),
439 parent=self.siblings(fl.parents(n), fl.rev, file=f),
440 child=self.siblings(fl.children(n), fl.rev, file=f),
440 child=self.siblings(fl.children(n), fl.rev, file=f),
441 permissions=self.repo.manifest.readflags(mfn)[f])
441 permissions=self.repo.manifest.readflags(mfn)[f])
442
442
443 def manifest(self, mnode, path):
443 def manifest(self, mnode, path):
444 man = self.repo.manifest
444 man = self.repo.manifest
445 mn = man.lookup(mnode)
445 mn = man.lookup(mnode)
446 mnode = hex(mn)
446 mnode = hex(mn)
447 mf = man.read(mn)
447 mf = man.read(mn)
448 rev = man.rev(mn)
448 rev = man.rev(mn)
449 changerev = man.linkrev(mn)
449 changerev = man.linkrev(mn)
450 node = self.repo.changelog.node(changerev)
450 node = self.repo.changelog.node(changerev)
451 mff = man.readflags(mn)
451 mff = man.readflags(mn)
452
452
453 files = {}
453 files = {}
454
454
455 p = path[1:]
455 p = path[1:]
456 if p and p[-1] != "/":
456 if p and p[-1] != "/":
457 p += "/"
457 p += "/"
458 l = len(p)
458 l = len(p)
459
459
460 for f,n in mf.items():
460 for f,n in mf.items():
461 if f[:l] != p:
461 if f[:l] != p:
462 continue
462 continue
463 remain = f[l:]
463 remain = f[l:]
464 if "/" in remain:
464 if "/" in remain:
465 short = remain[:remain.find("/") + 1] # bleah
465 short = remain[:remain.index("/") + 1] # bleah
466 files[short] = (f, None)
466 files[short] = (f, None)
467 else:
467 else:
468 short = os.path.basename(remain)
468 short = os.path.basename(remain)
469 files[short] = (f, n)
469 files[short] = (f, n)
470
470
471 def filelist(**map):
471 def filelist(**map):
472 parity = 0
472 parity = 0
473 fl = files.keys()
473 fl = files.keys()
474 fl.sort()
474 fl.sort()
475 for f in fl:
475 for f in fl:
476 full, fnode = files[f]
476 full, fnode = files[f]
477 if not fnode:
477 if not fnode:
478 continue
478 continue
479
479
480 yield {"file": full,
480 yield {"file": full,
481 "manifest": mnode,
481 "manifest": mnode,
482 "filenode": hex(fnode),
482 "filenode": hex(fnode),
483 "parity": parity,
483 "parity": parity,
484 "basename": f,
484 "basename": f,
485 "permissions": mff[full]}
485 "permissions": mff[full]}
486 parity = 1 - parity
486 parity = 1 - parity
487
487
488 def dirlist(**map):
488 def dirlist(**map):
489 parity = 0
489 parity = 0
490 fl = files.keys()
490 fl = files.keys()
491 fl.sort()
491 fl.sort()
492 for f in fl:
492 for f in fl:
493 full, fnode = files[f]
493 full, fnode = files[f]
494 if fnode:
494 if fnode:
495 continue
495 continue
496
496
497 yield {"parity": parity,
497 yield {"parity": parity,
498 "path": os.path.join(path, f),
498 "path": os.path.join(path, f),
499 "manifest": mnode,
499 "manifest": mnode,
500 "basename": f[:-1]}
500 "basename": f[:-1]}
501 parity = 1 - parity
501 parity = 1 - parity
502
502
503 yield self.t("manifest",
503 yield self.t("manifest",
504 manifest=mnode,
504 manifest=mnode,
505 rev=rev,
505 rev=rev,
506 node=hex(node),
506 node=hex(node),
507 path=path,
507 path=path,
508 up=_up(path),
508 up=_up(path),
509 fentries=filelist,
509 fentries=filelist,
510 dentries=dirlist,
510 dentries=dirlist,
511 archives=self.archivelist(hex(node)))
511 archives=self.archivelist(hex(node)))
512
512
513 def tags(self):
513 def tags(self):
514 cl = self.repo.changelog
514 cl = self.repo.changelog
515 mf = cl.read(cl.tip())[0]
515 mf = cl.read(cl.tip())[0]
516
516
517 i = self.repo.tagslist()
517 i = self.repo.tagslist()
518 i.reverse()
518 i.reverse()
519
519
520 def entries(notip=False, **map):
520 def entries(notip=False, **map):
521 parity = 0
521 parity = 0
522 for k,n in i:
522 for k,n in i:
523 if notip and k == "tip": continue
523 if notip and k == "tip": continue
524 yield {"parity": parity,
524 yield {"parity": parity,
525 "tag": k,
525 "tag": k,
526 "tagmanifest": hex(cl.read(n)[0]),
526 "tagmanifest": hex(cl.read(n)[0]),
527 "date": cl.read(n)[2],
527 "date": cl.read(n)[2],
528 "node": hex(n)}
528 "node": hex(n)}
529 parity = 1 - parity
529 parity = 1 - parity
530
530
531 yield self.t("tags",
531 yield self.t("tags",
532 manifest=hex(mf),
532 manifest=hex(mf),
533 entries=lambda **x: entries(False, **x),
533 entries=lambda **x: entries(False, **x),
534 entriesnotip=lambda **x: entries(True, **x))
534 entriesnotip=lambda **x: entries(True, **x))
535
535
536 def summary(self):
536 def summary(self):
537 cl = self.repo.changelog
537 cl = self.repo.changelog
538 mf = cl.read(cl.tip())[0]
538 mf = cl.read(cl.tip())[0]
539
539
540 i = self.repo.tagslist()
540 i = self.repo.tagslist()
541 i.reverse()
541 i.reverse()
542
542
543 def tagentries(**map):
543 def tagentries(**map):
544 parity = 0
544 parity = 0
545 count = 0
545 count = 0
546 for k,n in i:
546 for k,n in i:
547 if k == "tip": # skip tip
547 if k == "tip": # skip tip
548 continue;
548 continue;
549
549
550 count += 1
550 count += 1
551 if count > 10: # limit to 10 tags
551 if count > 10: # limit to 10 tags
552 break;
552 break;
553
553
554 c = cl.read(n)
554 c = cl.read(n)
555 m = c[0]
555 m = c[0]
556 t = c[2]
556 t = c[2]
557
557
558 yield self.t("tagentry",
558 yield self.t("tagentry",
559 parity = parity,
559 parity = parity,
560 tag = k,
560 tag = k,
561 node = hex(n),
561 node = hex(n),
562 date = t,
562 date = t,
563 tagmanifest = hex(m))
563 tagmanifest = hex(m))
564 parity = 1 - parity
564 parity = 1 - parity
565
565
566 def changelist(**map):
566 def changelist(**map):
567 parity = 0
567 parity = 0
568 cl = self.repo.changelog
568 cl = self.repo.changelog
569 l = [] # build a list in forward order for efficiency
569 l = [] # build a list in forward order for efficiency
570 for i in range(start, end):
570 for i in range(start, end):
571 n = cl.node(i)
571 n = cl.node(i)
572 changes = cl.read(n)
572 changes = cl.read(n)
573 hn = hex(n)
573 hn = hex(n)
574 t = changes[2]
574 t = changes[2]
575
575
576 l.insert(0, self.t(
576 l.insert(0, self.t(
577 'shortlogentry',
577 'shortlogentry',
578 parity = parity,
578 parity = parity,
579 author = changes[1],
579 author = changes[1],
580 manifest = hex(changes[0]),
580 manifest = hex(changes[0]),
581 desc = changes[4],
581 desc = changes[4],
582 date = t,
582 date = t,
583 rev = i,
583 rev = i,
584 node = hn))
584 node = hn))
585 parity = 1 - parity
585 parity = 1 - parity
586
586
587 yield l
587 yield l
588
588
589 cl = self.repo.changelog
589 cl = self.repo.changelog
590 mf = cl.read(cl.tip())[0]
590 mf = cl.read(cl.tip())[0]
591 count = cl.count()
591 count = cl.count()
592 start = max(0, count - self.maxchanges)
592 start = max(0, count - self.maxchanges)
593 end = min(count, start + self.maxchanges)
593 end = min(count, start + self.maxchanges)
594
594
595 yield self.t("summary",
595 yield self.t("summary",
596 desc = self.repo.ui.config("web", "description", "unknown"),
596 desc = self.repo.ui.config("web", "description", "unknown"),
597 owner = (self.repo.ui.config("ui", "username") or # preferred
597 owner = (self.repo.ui.config("ui", "username") or # preferred
598 self.repo.ui.config("web", "contact") or # deprecated
598 self.repo.ui.config("web", "contact") or # deprecated
599 self.repo.ui.config("web", "author", "unknown")), # also
599 self.repo.ui.config("web", "author", "unknown")), # also
600 lastchange = (0, 0), # FIXME
600 lastchange = (0, 0), # FIXME
601 manifest = hex(mf),
601 manifest = hex(mf),
602 tags = tagentries,
602 tags = tagentries,
603 shortlog = changelist)
603 shortlog = changelist)
604
604
605 def filediff(self, file, changeset):
605 def filediff(self, file, changeset):
606 cl = self.repo.changelog
606 cl = self.repo.changelog
607 n = self.repo.lookup(changeset)
607 n = self.repo.lookup(changeset)
608 changeset = hex(n)
608 changeset = hex(n)
609 p1 = cl.parents(n)[0]
609 p1 = cl.parents(n)[0]
610 cs = cl.read(n)
610 cs = cl.read(n)
611 mf = self.repo.manifest.read(cs[0])
611 mf = self.repo.manifest.read(cs[0])
612
612
613 def diff(**map):
613 def diff(**map):
614 yield self.diff(p1, n, [file])
614 yield self.diff(p1, n, [file])
615
615
616 yield self.t("filediff",
616 yield self.t("filediff",
617 file=file,
617 file=file,
618 filenode=hex(mf.get(file, nullid)),
618 filenode=hex(mf.get(file, nullid)),
619 node=changeset,
619 node=changeset,
620 rev=self.repo.changelog.rev(n),
620 rev=self.repo.changelog.rev(n),
621 parent=self.siblings(cl.parents(n), cl.rev),
621 parent=self.siblings(cl.parents(n), cl.rev),
622 child=self.siblings(cl.children(n), cl.rev),
622 child=self.siblings(cl.children(n), cl.rev),
623 diff=diff)
623 diff=diff)
624
624
625 archive_specs = {
625 archive_specs = {
626 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
626 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
627 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
627 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
628 'zip': ('application/zip', 'zip', '.zip', None),
628 'zip': ('application/zip', 'zip', '.zip', None),
629 }
629 }
630
630
631 def archive(self, req, cnode, type_):
631 def archive(self, req, cnode, type_):
632 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
632 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
633 name = "%s-%s" % (reponame, short(cnode))
633 name = "%s-%s" % (reponame, short(cnode))
634 mimetype, artype, extension, encoding = self.archive_specs[type_]
634 mimetype, artype, extension, encoding = self.archive_specs[type_]
635 headers = [('Content-type', mimetype),
635 headers = [('Content-type', mimetype),
636 ('Content-disposition', 'attachment; filename=%s%s' %
636 ('Content-disposition', 'attachment; filename=%s%s' %
637 (name, extension))]
637 (name, extension))]
638 if encoding:
638 if encoding:
639 headers.append(('Content-encoding', encoding))
639 headers.append(('Content-encoding', encoding))
640 req.header(headers)
640 req.header(headers)
641 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
641 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
642
642
643 # add tags to things
643 # add tags to things
644 # tags -> list of changesets corresponding to tags
644 # tags -> list of changesets corresponding to tags
645 # find tag, changeset, file
645 # find tag, changeset, file
646
646
647 def cleanpath(self, path):
647 def cleanpath(self, path):
648 p = util.normpath(path)
648 p = util.normpath(path)
649 if p[:2] == "..":
649 if p[:2] == "..":
650 raise Exception("suspicious path")
650 raise Exception("suspicious path")
651 return p
651 return p
652
652
653 def run(self):
653 def run(self):
654 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
654 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
655 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
655 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
656 import mercurial.hgweb.wsgicgi as wsgicgi
656 import mercurial.hgweb.wsgicgi as wsgicgi
657 from request import wsgiapplication
657 from request import wsgiapplication
658 def make_web_app():
658 def make_web_app():
659 return self
659 return self
660 wsgicgi.launch(wsgiapplication(make_web_app))
660 wsgicgi.launch(wsgiapplication(make_web_app))
661
661
662 def run_wsgi(self, req):
662 def run_wsgi(self, req):
663 def header(**map):
663 def header(**map):
664 header_file = cStringIO.StringIO(''.join(self.t("header", **map)))
664 header_file = cStringIO.StringIO(''.join(self.t("header", **map)))
665 msg = mimetools.Message(header_file, 0)
665 msg = mimetools.Message(header_file, 0)
666 req.header(msg.items())
666 req.header(msg.items())
667 yield header_file.read()
667 yield header_file.read()
668
668
669 def rawfileheader(**map):
669 def rawfileheader(**map):
670 req.header([('Content-type', map['mimetype']),
670 req.header([('Content-type', map['mimetype']),
671 ('Content-disposition', 'filename=%s' % map['file']),
671 ('Content-disposition', 'filename=%s' % map['file']),
672 ('Content-length', str(len(map['raw'])))])
672 ('Content-length', str(len(map['raw'])))])
673 yield ''
673 yield ''
674
674
675 def footer(**map):
675 def footer(**map):
676 yield self.t("footer",
676 yield self.t("footer",
677 motd=self.repo.ui.config("web", "motd", ""),
677 motd=self.repo.ui.config("web", "motd", ""),
678 **map)
678 **map)
679
679
680 def expand_form(form):
680 def expand_form(form):
681 shortcuts = {
681 shortcuts = {
682 'cl': [('cmd', ['changelog']), ('rev', None)],
682 'cl': [('cmd', ['changelog']), ('rev', None)],
683 'cs': [('cmd', ['changeset']), ('node', None)],
683 'cs': [('cmd', ['changeset']), ('node', None)],
684 'f': [('cmd', ['file']), ('filenode', None)],
684 'f': [('cmd', ['file']), ('filenode', None)],
685 'fl': [('cmd', ['filelog']), ('filenode', None)],
685 'fl': [('cmd', ['filelog']), ('filenode', None)],
686 'fd': [('cmd', ['filediff']), ('node', None)],
686 'fd': [('cmd', ['filediff']), ('node', None)],
687 'fa': [('cmd', ['annotate']), ('filenode', None)],
687 'fa': [('cmd', ['annotate']), ('filenode', None)],
688 'mf': [('cmd', ['manifest']), ('manifest', None)],
688 'mf': [('cmd', ['manifest']), ('manifest', None)],
689 'ca': [('cmd', ['archive']), ('node', None)],
689 'ca': [('cmd', ['archive']), ('node', None)],
690 'tags': [('cmd', ['tags'])],
690 'tags': [('cmd', ['tags'])],
691 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
691 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
692 'static': [('cmd', ['static']), ('file', None)]
692 'static': [('cmd', ['static']), ('file', None)]
693 }
693 }
694
694
695 for k in shortcuts.iterkeys():
695 for k in shortcuts.iterkeys():
696 if form.has_key(k):
696 if form.has_key(k):
697 for name, value in shortcuts[k]:
697 for name, value in shortcuts[k]:
698 if value is None:
698 if value is None:
699 value = form[k]
699 value = form[k]
700 form[name] = value
700 form[name] = value
701 del form[k]
701 del form[k]
702
702
703 self.refresh()
703 self.refresh()
704
704
705 expand_form(req.form)
705 expand_form(req.form)
706
706
707 m = os.path.join(self.templatepath, "map")
707 m = os.path.join(self.templatepath, "map")
708 style = self.repo.ui.config("web", "style", "")
708 style = self.repo.ui.config("web", "style", "")
709 if req.form.has_key('style'):
709 if req.form.has_key('style'):
710 style = req.form['style'][0]
710 style = req.form['style'][0]
711 if style:
711 if style:
712 b = os.path.basename("map-" + style)
712 b = os.path.basename("map-" + style)
713 p = os.path.join(self.templatepath, b)
713 p = os.path.join(self.templatepath, b)
714 if os.path.isfile(p):
714 if os.path.isfile(p):
715 m = p
715 m = p
716
716
717 port = req.env["SERVER_PORT"]
717 port = req.env["SERVER_PORT"]
718 port = port != "80" and (":" + port) or ""
718 port = port != "80" and (":" + port) or ""
719 uri = req.env["REQUEST_URI"]
719 uri = req.env["REQUEST_URI"]
720 if "?" in uri:
720 if "?" in uri:
721 uri = uri.split("?")[0]
721 uri = uri.split("?")[0]
722 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
722 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
723 if not self.reponame:
723 if not self.reponame:
724 self.reponame = (self.repo.ui.config("web", "name")
724 self.reponame = (self.repo.ui.config("web", "name")
725 or uri.strip('/') or self.repo.root)
725 or uri.strip('/') or self.repo.root)
726
726
727 self.t = templater.templater(m, templater.common_filters,
727 self.t = templater.templater(m, templater.common_filters,
728 defaults={"url": url,
728 defaults={"url": url,
729 "repo": self.reponame,
729 "repo": self.reponame,
730 "header": header,
730 "header": header,
731 "footer": footer,
731 "footer": footer,
732 "rawfileheader": rawfileheader,
732 "rawfileheader": rawfileheader,
733 })
733 })
734
734
735 if not req.form.has_key('cmd'):
735 if not req.form.has_key('cmd'):
736 req.form['cmd'] = [self.t.cache['default'],]
736 req.form['cmd'] = [self.t.cache['default'],]
737
737
738 cmd = req.form['cmd'][0]
738 cmd = req.form['cmd'][0]
739
739
740 method = getattr(self, 'do_' + cmd, None)
740 method = getattr(self, 'do_' + cmd, None)
741 if method:
741 if method:
742 method(req)
742 method(req)
743 else:
743 else:
744 req.write(self.t("error"))
744 req.write(self.t("error"))
745
745
746 def do_changelog(self, req):
746 def do_changelog(self, req):
747 hi = self.repo.changelog.count() - 1
747 hi = self.repo.changelog.count() - 1
748 if req.form.has_key('rev'):
748 if req.form.has_key('rev'):
749 hi = req.form['rev'][0]
749 hi = req.form['rev'][0]
750 try:
750 try:
751 hi = self.repo.changelog.rev(self.repo.lookup(hi))
751 hi = self.repo.changelog.rev(self.repo.lookup(hi))
752 except hg.RepoError:
752 except hg.RepoError:
753 req.write(self.search(hi)) # XXX redirect to 404 page?
753 req.write(self.search(hi)) # XXX redirect to 404 page?
754 return
754 return
755
755
756 req.write(self.changelog(hi))
756 req.write(self.changelog(hi))
757
757
758 def do_changeset(self, req):
758 def do_changeset(self, req):
759 req.write(self.changeset(req.form['node'][0]))
759 req.write(self.changeset(req.form['node'][0]))
760
760
761 def do_manifest(self, req):
761 def do_manifest(self, req):
762 req.write(self.manifest(req.form['manifest'][0],
762 req.write(self.manifest(req.form['manifest'][0],
763 self.cleanpath(req.form['path'][0])))
763 self.cleanpath(req.form['path'][0])))
764
764
765 def do_tags(self, req):
765 def do_tags(self, req):
766 req.write(self.tags())
766 req.write(self.tags())
767
767
768 def do_summary(self, req):
768 def do_summary(self, req):
769 req.write(self.summary())
769 req.write(self.summary())
770
770
771 def do_filediff(self, req):
771 def do_filediff(self, req):
772 req.write(self.filediff(self.cleanpath(req.form['file'][0]),
772 req.write(self.filediff(self.cleanpath(req.form['file'][0]),
773 req.form['node'][0]))
773 req.form['node'][0]))
774
774
775 def do_file(self, req):
775 def do_file(self, req):
776 req.write(self.filerevision(self.cleanpath(req.form['file'][0]),
776 req.write(self.filerevision(self.cleanpath(req.form['file'][0]),
777 req.form['filenode'][0]))
777 req.form['filenode'][0]))
778
778
779 def do_annotate(self, req):
779 def do_annotate(self, req):
780 req.write(self.fileannotate(self.cleanpath(req.form['file'][0]),
780 req.write(self.fileannotate(self.cleanpath(req.form['file'][0]),
781 req.form['filenode'][0]))
781 req.form['filenode'][0]))
782
782
783 def do_filelog(self, req):
783 def do_filelog(self, req):
784 req.write(self.filelog(self.cleanpath(req.form['file'][0]),
784 req.write(self.filelog(self.cleanpath(req.form['file'][0]),
785 req.form['filenode'][0]))
785 req.form['filenode'][0]))
786
786
787 def do_heads(self, req):
787 def do_heads(self, req):
788 resp = " ".join(map(hex, self.repo.heads())) + "\n"
788 resp = " ".join(map(hex, self.repo.heads())) + "\n"
789 req.httphdr("application/mercurial-0.1", length=len(resp))
789 req.httphdr("application/mercurial-0.1", length=len(resp))
790 req.write(resp)
790 req.write(resp)
791
791
792 def do_branches(self, req):
792 def do_branches(self, req):
793 nodes = []
793 nodes = []
794 if req.form.has_key('nodes'):
794 if req.form.has_key('nodes'):
795 nodes = map(bin, req.form['nodes'][0].split(" "))
795 nodes = map(bin, req.form['nodes'][0].split(" "))
796 resp = cStringIO.StringIO()
796 resp = cStringIO.StringIO()
797 for b in self.repo.branches(nodes):
797 for b in self.repo.branches(nodes):
798 resp.write(" ".join(map(hex, b)) + "\n")
798 resp.write(" ".join(map(hex, b)) + "\n")
799 resp = resp.getvalue()
799 resp = resp.getvalue()
800 req.httphdr("application/mercurial-0.1", length=len(resp))
800 req.httphdr("application/mercurial-0.1", length=len(resp))
801 req.write(resp)
801 req.write(resp)
802
802
803 def do_between(self, req):
803 def do_between(self, req):
804 nodes = []
804 nodes = []
805 if req.form.has_key('pairs'):
805 if req.form.has_key('pairs'):
806 pairs = [map(bin, p.split("-"))
806 pairs = [map(bin, p.split("-"))
807 for p in req.form['pairs'][0].split(" ")]
807 for p in req.form['pairs'][0].split(" ")]
808 resp = cStringIO.StringIO()
808 resp = cStringIO.StringIO()
809 for b in self.repo.between(pairs):
809 for b in self.repo.between(pairs):
810 resp.write(" ".join(map(hex, b)) + "\n")
810 resp.write(" ".join(map(hex, b)) + "\n")
811 resp = resp.getvalue()
811 resp = resp.getvalue()
812 req.httphdr("application/mercurial-0.1", length=len(resp))
812 req.httphdr("application/mercurial-0.1", length=len(resp))
813 req.write(resp)
813 req.write(resp)
814
814
815 def do_changegroup(self, req):
815 def do_changegroup(self, req):
816 req.httphdr("application/mercurial-0.1")
816 req.httphdr("application/mercurial-0.1")
817 nodes = []
817 nodes = []
818 if not self.allowpull:
818 if not self.allowpull:
819 return
819 return
820
820
821 if req.form.has_key('roots'):
821 if req.form.has_key('roots'):
822 nodes = map(bin, req.form['roots'][0].split(" "))
822 nodes = map(bin, req.form['roots'][0].split(" "))
823
823
824 z = zlib.compressobj()
824 z = zlib.compressobj()
825 f = self.repo.changegroup(nodes, 'serve')
825 f = self.repo.changegroup(nodes, 'serve')
826 while 1:
826 while 1:
827 chunk = f.read(4096)
827 chunk = f.read(4096)
828 if not chunk:
828 if not chunk:
829 break
829 break
830 req.write(z.compress(chunk))
830 req.write(z.compress(chunk))
831
831
832 req.write(z.flush())
832 req.write(z.flush())
833
833
834 def do_archive(self, req):
834 def do_archive(self, req):
835 changeset = self.repo.lookup(req.form['node'][0])
835 changeset = self.repo.lookup(req.form['node'][0])
836 type_ = req.form['type'][0]
836 type_ = req.form['type'][0]
837 allowed = self.repo.ui.configlist("web", "allow_archive")
837 allowed = self.repo.ui.configlist("web", "allow_archive")
838 if (type_ in self.archives and (type_ in allowed or
838 if (type_ in self.archives and (type_ in allowed or
839 self.repo.ui.configbool("web", "allow" + type_, False))):
839 self.repo.ui.configbool("web", "allow" + type_, False))):
840 self.archive(req, changeset, type_)
840 self.archive(req, changeset, type_)
841 return
841 return
842
842
843 req.write(self.t("error"))
843 req.write(self.t("error"))
844
844
845 def do_static(self, req):
845 def do_static(self, req):
846 fname = req.form['file'][0]
846 fname = req.form['file'][0]
847 static = self.repo.ui.config("web", "static",
847 static = self.repo.ui.config("web", "static",
848 os.path.join(self.templatepath,
848 os.path.join(self.templatepath,
849 "static"))
849 "static"))
850 req.write(staticfile(static, fname, req)
850 req.write(staticfile(static, fname, req)
851 or self.t("error", error="%r not found" % fname))
851 or self.t("error", error="%r not found" % fname))
852
852
853 def do_capabilities(self, req):
853 def do_capabilities(self, req):
854 resp = 'unbundle'
854 resp = 'unbundle'
855 req.httphdr("application/mercurial-0.1", length=len(resp))
855 req.httphdr("application/mercurial-0.1", length=len(resp))
856 req.write(resp)
856 req.write(resp)
857
857
858 def check_perm(self, req, op, default):
858 def check_perm(self, req, op, default):
859 '''check permission for operation based on user auth.
859 '''check permission for operation based on user auth.
860 return true if op allowed, else false.
860 return true if op allowed, else false.
861 default is policy to use if no config given.'''
861 default is policy to use if no config given.'''
862
862
863 user = req.env.get('REMOTE_USER')
863 user = req.env.get('REMOTE_USER')
864
864
865 deny = self.repo.ui.configlist('web', 'deny_' + op)
865 deny = self.repo.ui.configlist('web', 'deny_' + op)
866 if deny and (not user or deny == ['*'] or user in deny):
866 if deny and (not user or deny == ['*'] or user in deny):
867 return False
867 return False
868
868
869 allow = self.repo.ui.configlist('web', 'allow_' + op)
869 allow = self.repo.ui.configlist('web', 'allow_' + op)
870 return (allow and (allow == ['*'] or user in allow)) or default
870 return (allow and (allow == ['*'] or user in allow)) or default
871
871
872 def do_unbundle(self, req):
872 def do_unbundle(self, req):
873 def bail(response, headers={}):
873 def bail(response, headers={}):
874 length = int(req.env['CONTENT_LENGTH'])
874 length = int(req.env['CONTENT_LENGTH'])
875 for s in util.filechunkiter(req, limit=length):
875 for s in util.filechunkiter(req, limit=length):
876 # drain incoming bundle, else client will not see
876 # drain incoming bundle, else client will not see
877 # response when run outside cgi script
877 # response when run outside cgi script
878 pass
878 pass
879 req.httphdr("application/mercurial-0.1", headers=headers)
879 req.httphdr("application/mercurial-0.1", headers=headers)
880 req.write('0\n')
880 req.write('0\n')
881 req.write(response)
881 req.write(response)
882
882
883 # require ssl by default, auth info cannot be sniffed and
883 # require ssl by default, auth info cannot be sniffed and
884 # replayed
884 # replayed
885 ssl_req = self.repo.ui.configbool('web', 'push_ssl', True)
885 ssl_req = self.repo.ui.configbool('web', 'push_ssl', True)
886 if ssl_req and not req.env.get('HTTPS'):
886 if ssl_req and not req.env.get('HTTPS'):
887 bail(_('ssl required\n'))
887 bail(_('ssl required\n'))
888 return
888 return
889
889
890 # do not allow push unless explicitly allowed
890 # do not allow push unless explicitly allowed
891 if not self.check_perm(req, 'push', False):
891 if not self.check_perm(req, 'push', False):
892 bail(_('push not authorized\n'),
892 bail(_('push not authorized\n'),
893 headers={'status': '401 Unauthorized'})
893 headers={'status': '401 Unauthorized'})
894 return
894 return
895
895
896 req.httphdr("application/mercurial-0.1")
896 req.httphdr("application/mercurial-0.1")
897
897
898 their_heads = req.form['heads'][0].split(' ')
898 their_heads = req.form['heads'][0].split(' ')
899
899
900 def check_heads():
900 def check_heads():
901 heads = map(hex, self.repo.heads())
901 heads = map(hex, self.repo.heads())
902 return their_heads == [hex('force')] or their_heads == heads
902 return their_heads == [hex('force')] or their_heads == heads
903
903
904 # fail early if possible
904 # fail early if possible
905 if not check_heads():
905 if not check_heads():
906 bail(_('unsynced changes\n'))
906 bail(_('unsynced changes\n'))
907 return
907 return
908
908
909 # do not lock repo until all changegroup data is
909 # do not lock repo until all changegroup data is
910 # streamed. save to temporary file.
910 # streamed. save to temporary file.
911
911
912 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
912 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
913 fp = os.fdopen(fd, 'wb+')
913 fp = os.fdopen(fd, 'wb+')
914 try:
914 try:
915 length = int(req.env['CONTENT_LENGTH'])
915 length = int(req.env['CONTENT_LENGTH'])
916 for s in util.filechunkiter(req, limit=length):
916 for s in util.filechunkiter(req, limit=length):
917 fp.write(s)
917 fp.write(s)
918
918
919 lock = self.repo.lock()
919 lock = self.repo.lock()
920 try:
920 try:
921 if not check_heads():
921 if not check_heads():
922 req.write('0\n')
922 req.write('0\n')
923 req.write(_('unsynced changes\n'))
923 req.write(_('unsynced changes\n'))
924 return
924 return
925
925
926 fp.seek(0)
926 fp.seek(0)
927
927
928 # send addchangegroup output to client
928 # send addchangegroup output to client
929
929
930 old_stdout = sys.stdout
930 old_stdout = sys.stdout
931 sys.stdout = cStringIO.StringIO()
931 sys.stdout = cStringIO.StringIO()
932
932
933 try:
933 try:
934 ret = self.repo.addchangegroup(fp, 'serve')
934 ret = self.repo.addchangegroup(fp, 'serve')
935 finally:
935 finally:
936 val = sys.stdout.getvalue()
936 val = sys.stdout.getvalue()
937 sys.stdout = old_stdout
937 sys.stdout = old_stdout
938 req.write('%d\n' % ret)
938 req.write('%d\n' % ret)
939 req.write(val)
939 req.write(val)
940 finally:
940 finally:
941 lock.release()
941 lock.release()
942 finally:
942 finally:
943 fp.close()
943 fp.close()
944 os.unlink(tempname)
944 os.unlink(tempname)
@@ -1,118 +1,118 b''
1 # lock.py - simple locking scheme for mercurial
1 # lock.py - simple locking scheme for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from demandload import *
8 from demandload import *
9 demandload(globals(), 'errno os socket time util')
9 demandload(globals(), 'errno os socket time util')
10
10
11 class LockException(IOError):
11 class LockException(IOError):
12 def __init__(self, errno, strerror, filename, desc):
12 def __init__(self, errno, strerror, filename, desc):
13 IOError.__init__(self, errno, strerror, filename)
13 IOError.__init__(self, errno, strerror, filename)
14 self.desc = desc
14 self.desc = desc
15
15
16 class LockHeld(LockException):
16 class LockHeld(LockException):
17 def __init__(self, errno, filename, desc, locker):
17 def __init__(self, errno, filename, desc, locker):
18 LockException.__init__(self, errno, 'Lock held', filename, desc)
18 LockException.__init__(self, errno, 'Lock held', filename, desc)
19 self.locker = locker
19 self.locker = locker
20
20
21 class LockUnavailable(LockException):
21 class LockUnavailable(LockException):
22 pass
22 pass
23
23
24 class lock(object):
24 class lock(object):
25 # lock is symlink on platforms that support it, file on others.
25 # lock is symlink on platforms that support it, file on others.
26
26
27 # symlink is used because create of directory entry and contents
27 # symlink is used because create of directory entry and contents
28 # are atomic even over nfs.
28 # are atomic even over nfs.
29
29
30 # old-style lock: symlink to pid
30 # old-style lock: symlink to pid
31 # new-style lock: symlink to hostname:pid
31 # new-style lock: symlink to hostname:pid
32
32
33 def __init__(self, file, timeout=-1, releasefn=None, desc=None):
33 def __init__(self, file, timeout=-1, releasefn=None, desc=None):
34 self.f = file
34 self.f = file
35 self.held = 0
35 self.held = 0
36 self.timeout = timeout
36 self.timeout = timeout
37 self.releasefn = releasefn
37 self.releasefn = releasefn
38 self.id = None
38 self.id = None
39 self.host = None
39 self.host = None
40 self.pid = None
40 self.pid = None
41 self.desc = desc
41 self.desc = desc
42 self.lock()
42 self.lock()
43
43
44 def __del__(self):
44 def __del__(self):
45 self.release()
45 self.release()
46
46
47 def lock(self):
47 def lock(self):
48 timeout = self.timeout
48 timeout = self.timeout
49 while 1:
49 while 1:
50 try:
50 try:
51 self.trylock()
51 self.trylock()
52 return 1
52 return 1
53 except LockHeld, inst:
53 except LockHeld, inst:
54 if timeout != 0:
54 if timeout != 0:
55 time.sleep(1)
55 time.sleep(1)
56 if timeout > 0:
56 if timeout > 0:
57 timeout -= 1
57 timeout -= 1
58 continue
58 continue
59 raise LockHeld(errno.ETIMEDOUT, inst.filename, self.desc,
59 raise LockHeld(errno.ETIMEDOUT, inst.filename, self.desc,
60 inst.locker)
60 inst.locker)
61
61
62 def trylock(self):
62 def trylock(self):
63 if self.id is None:
63 if self.id is None:
64 self.host = socket.gethostname()
64 self.host = socket.gethostname()
65 self.pid = os.getpid()
65 self.pid = os.getpid()
66 self.id = '%s:%s' % (self.host, self.pid)
66 self.id = '%s:%s' % (self.host, self.pid)
67 while not self.held:
67 while not self.held:
68 try:
68 try:
69 util.makelock(self.id, self.f)
69 util.makelock(self.id, self.f)
70 self.held = 1
70 self.held = 1
71 except (OSError, IOError), why:
71 except (OSError, IOError), why:
72 if why.errno == errno.EEXIST:
72 if why.errno == errno.EEXIST:
73 locker = self.testlock()
73 locker = self.testlock()
74 if locker:
74 if locker:
75 raise LockHeld(errno.EAGAIN, self.f, self.desc,
75 raise LockHeld(errno.EAGAIN, self.f, self.desc,
76 locker)
76 locker)
77 else:
77 else:
78 raise LockUnavailable(why.errno, why.strerror,
78 raise LockUnavailable(why.errno, why.strerror,
79 why.filename, self.desc)
79 why.filename, self.desc)
80
80
81 def testlock(self):
81 def testlock(self):
82 '''return id of locker if lock is valid, else None.'''
82 '''return id of locker if lock is valid, else None.'''
83 # if old-style lock, we cannot tell what machine locker is on.
83 # if old-style lock, we cannot tell what machine locker is on.
84 # with new-style lock, if locker is on this machine, we can
84 # with new-style lock, if locker is on this machine, we can
85 # see if locker is alive. if locker is on this machine but
85 # see if locker is alive. if locker is on this machine but
86 # not alive, we can safely break lock.
86 # not alive, we can safely break lock.
87 locker = util.readlock(self.f)
87 locker = util.readlock(self.f)
88 c = locker.find(':')
88 try:
89 if c == -1:
89 host, pid = locker.split(":", 1)
90 except ValueError:
90 return locker
91 return locker
91 host = locker[:c]
92 if host != self.host:
92 if host != self.host:
93 return locker
93 return locker
94 try:
94 try:
95 pid = int(locker[c+1:])
95 pid = int(pid)
96 except:
96 except:
97 return locker
97 return locker
98 if util.testpid(pid):
98 if util.testpid(pid):
99 return locker
99 return locker
100 # if locker dead, break lock. must do this with another lock
100 # if locker dead, break lock. must do this with another lock
101 # held, or can race and break valid lock.
101 # held, or can race and break valid lock.
102 try:
102 try:
103 l = lock(self.f + '.break')
103 l = lock(self.f + '.break')
104 l.trylock()
104 l.trylock()
105 os.unlink(self.f)
105 os.unlink(self.f)
106 l.release()
106 l.release()
107 except (LockHeld, LockUnavailable):
107 except (LockHeld, LockUnavailable):
108 return locker
108 return locker
109
109
110 def release(self):
110 def release(self):
111 if self.held:
111 if self.held:
112 self.held = 0
112 self.held = 0
113 if self.releasefn:
113 if self.releasefn:
114 self.releasefn()
114 self.releasefn()
115 try:
115 try:
116 os.unlink(self.f)
116 os.unlink(self.f)
117 except: pass
117 except: pass
118
118
@@ -1,355 +1,355 b''
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from i18n import gettext as _
8 from i18n import gettext as _
9 from demandload import *
9 from demandload import *
10 demandload(globals(), "errno getpass os re smtplib socket sys tempfile")
10 demandload(globals(), "errno getpass os re smtplib socket sys tempfile")
11 demandload(globals(), "ConfigParser templater traceback util")
11 demandload(globals(), "ConfigParser templater traceback util")
12
12
13 class ui(object):
13 class ui(object):
14 def __init__(self, verbose=False, debug=False, quiet=False,
14 def __init__(self, verbose=False, debug=False, quiet=False,
15 interactive=True, traceback=False, parentui=None):
15 interactive=True, traceback=False, parentui=None):
16 self.overlay = {}
16 self.overlay = {}
17 if parentui is None:
17 if parentui is None:
18 # this is the parent of all ui children
18 # this is the parent of all ui children
19 self.parentui = None
19 self.parentui = None
20 self.cdata = ConfigParser.SafeConfigParser()
20 self.cdata = ConfigParser.SafeConfigParser()
21 self.readconfig(util.rcpath())
21 self.readconfig(util.rcpath())
22
22
23 self.quiet = self.configbool("ui", "quiet")
23 self.quiet = self.configbool("ui", "quiet")
24 self.verbose = self.configbool("ui", "verbose")
24 self.verbose = self.configbool("ui", "verbose")
25 self.debugflag = self.configbool("ui", "debug")
25 self.debugflag = self.configbool("ui", "debug")
26 self.interactive = self.configbool("ui", "interactive", True)
26 self.interactive = self.configbool("ui", "interactive", True)
27 self.traceback = traceback
27 self.traceback = traceback
28
28
29 self.updateopts(verbose, debug, quiet, interactive)
29 self.updateopts(verbose, debug, quiet, interactive)
30 self.diffcache = None
30 self.diffcache = None
31 self.header = []
31 self.header = []
32 self.prev_header = []
32 self.prev_header = []
33 self.revlogopts = self.configrevlog()
33 self.revlogopts = self.configrevlog()
34 else:
34 else:
35 # parentui may point to an ui object which is already a child
35 # parentui may point to an ui object which is already a child
36 self.parentui = parentui.parentui or parentui
36 self.parentui = parentui.parentui or parentui
37 parent_cdata = self.parentui.cdata
37 parent_cdata = self.parentui.cdata
38 self.cdata = ConfigParser.SafeConfigParser(parent_cdata.defaults())
38 self.cdata = ConfigParser.SafeConfigParser(parent_cdata.defaults())
39 # make interpolation work
39 # make interpolation work
40 for section in parent_cdata.sections():
40 for section in parent_cdata.sections():
41 self.cdata.add_section(section)
41 self.cdata.add_section(section)
42 for name, value in parent_cdata.items(section, raw=True):
42 for name, value in parent_cdata.items(section, raw=True):
43 self.cdata.set(section, name, value)
43 self.cdata.set(section, name, value)
44
44
45 def __getattr__(self, key):
45 def __getattr__(self, key):
46 return getattr(self.parentui, key)
46 return getattr(self.parentui, key)
47
47
48 def updateopts(self, verbose=False, debug=False, quiet=False,
48 def updateopts(self, verbose=False, debug=False, quiet=False,
49 interactive=True, traceback=False, config=[]):
49 interactive=True, traceback=False, config=[]):
50 self.quiet = (self.quiet or quiet) and not verbose and not debug
50 self.quiet = (self.quiet or quiet) and not verbose and not debug
51 self.verbose = (self.verbose or verbose) or debug
51 self.verbose = (self.verbose or verbose) or debug
52 self.debugflag = (self.debugflag or debug)
52 self.debugflag = (self.debugflag or debug)
53 self.interactive = (self.interactive and interactive)
53 self.interactive = (self.interactive and interactive)
54 self.traceback = self.traceback or traceback
54 self.traceback = self.traceback or traceback
55 for cfg in config:
55 for cfg in config:
56 try:
56 try:
57 name, value = cfg.split('=', 1)
57 name, value = cfg.split('=', 1)
58 section, name = name.split('.', 1)
58 section, name = name.split('.', 1)
59 if not self.cdata.has_section(section):
59 if not self.cdata.has_section(section):
60 self.cdata.add_section(section)
60 self.cdata.add_section(section)
61 if not section or not name:
61 if not section or not name:
62 raise IndexError
62 raise IndexError
63 self.cdata.set(section, name, value)
63 self.cdata.set(section, name, value)
64 except (IndexError, ValueError):
64 except (IndexError, ValueError):
65 raise util.Abort(_('malformed --config option: %s') % cfg)
65 raise util.Abort(_('malformed --config option: %s') % cfg)
66
66
67 def readconfig(self, fn, root=None):
67 def readconfig(self, fn, root=None):
68 if isinstance(fn, basestring):
68 if isinstance(fn, basestring):
69 fn = [fn]
69 fn = [fn]
70 for f in fn:
70 for f in fn:
71 try:
71 try:
72 self.cdata.read(f)
72 self.cdata.read(f)
73 except ConfigParser.ParsingError, inst:
73 except ConfigParser.ParsingError, inst:
74 raise util.Abort(_("Failed to parse %s\n%s") % (f, inst))
74 raise util.Abort(_("Failed to parse %s\n%s") % (f, inst))
75 # translate paths relative to root (or home) into absolute paths
75 # translate paths relative to root (or home) into absolute paths
76 if root is None:
76 if root is None:
77 root = os.path.expanduser('~')
77 root = os.path.expanduser('~')
78 for name, path in self.configitems("paths"):
78 for name, path in self.configitems("paths"):
79 if path and path.find("://") == -1 and not os.path.isabs(path):
79 if path and "://" not in path and not os.path.isabs(path):
80 self.cdata.set("paths", name, os.path.join(root, path))
80 self.cdata.set("paths", name, os.path.join(root, path))
81
81
82 def setconfig(self, section, name, val):
82 def setconfig(self, section, name, val):
83 self.overlay[(section, name)] = val
83 self.overlay[(section, name)] = val
84
84
85 def config(self, section, name, default=None):
85 def config(self, section, name, default=None):
86 if self.overlay.has_key((section, name)):
86 if self.overlay.has_key((section, name)):
87 return self.overlay[(section, name)]
87 return self.overlay[(section, name)]
88 if self.cdata.has_option(section, name):
88 if self.cdata.has_option(section, name):
89 try:
89 try:
90 return self.cdata.get(section, name)
90 return self.cdata.get(section, name)
91 except ConfigParser.InterpolationError, inst:
91 except ConfigParser.InterpolationError, inst:
92 raise util.Abort(_("Error in configuration:\n%s") % inst)
92 raise util.Abort(_("Error in configuration:\n%s") % inst)
93 if self.parentui is None:
93 if self.parentui is None:
94 return default
94 return default
95 else:
95 else:
96 return self.parentui.config(section, name, default)
96 return self.parentui.config(section, name, default)
97
97
98 def configlist(self, section, name, default=None):
98 def configlist(self, section, name, default=None):
99 """Return a list of comma/space separated strings"""
99 """Return a list of comma/space separated strings"""
100 result = self.config(section, name)
100 result = self.config(section, name)
101 if result is None:
101 if result is None:
102 result = default or []
102 result = default or []
103 if isinstance(result, basestring):
103 if isinstance(result, basestring):
104 result = result.replace(",", " ").split()
104 result = result.replace(",", " ").split()
105 return result
105 return result
106
106
107 def configbool(self, section, name, default=False):
107 def configbool(self, section, name, default=False):
108 if self.overlay.has_key((section, name)):
108 if self.overlay.has_key((section, name)):
109 return self.overlay[(section, name)]
109 return self.overlay[(section, name)]
110 if self.cdata.has_option(section, name):
110 if self.cdata.has_option(section, name):
111 try:
111 try:
112 return self.cdata.getboolean(section, name)
112 return self.cdata.getboolean(section, name)
113 except ConfigParser.InterpolationError, inst:
113 except ConfigParser.InterpolationError, inst:
114 raise util.Abort(_("Error in configuration:\n%s") % inst)
114 raise util.Abort(_("Error in configuration:\n%s") % inst)
115 if self.parentui is None:
115 if self.parentui is None:
116 return default
116 return default
117 else:
117 else:
118 return self.parentui.configbool(section, name, default)
118 return self.parentui.configbool(section, name, default)
119
119
120 def has_config(self, section):
120 def has_config(self, section):
121 '''tell whether section exists in config.'''
121 '''tell whether section exists in config.'''
122 return self.cdata.has_section(section)
122 return self.cdata.has_section(section)
123
123
124 def configitems(self, section):
124 def configitems(self, section):
125 items = {}
125 items = {}
126 if self.parentui is not None:
126 if self.parentui is not None:
127 items = dict(self.parentui.configitems(section))
127 items = dict(self.parentui.configitems(section))
128 if self.cdata.has_section(section):
128 if self.cdata.has_section(section):
129 try:
129 try:
130 items.update(dict(self.cdata.items(section)))
130 items.update(dict(self.cdata.items(section)))
131 except ConfigParser.InterpolationError, inst:
131 except ConfigParser.InterpolationError, inst:
132 raise util.Abort(_("Error in configuration:\n%s") % inst)
132 raise util.Abort(_("Error in configuration:\n%s") % inst)
133 x = items.items()
133 x = items.items()
134 x.sort()
134 x.sort()
135 return x
135 return x
136
136
137 def walkconfig(self, seen=None):
137 def walkconfig(self, seen=None):
138 if seen is None:
138 if seen is None:
139 seen = {}
139 seen = {}
140 for (section, name), value in self.overlay.iteritems():
140 for (section, name), value in self.overlay.iteritems():
141 yield section, name, value
141 yield section, name, value
142 seen[section, name] = 1
142 seen[section, name] = 1
143 for section in self.cdata.sections():
143 for section in self.cdata.sections():
144 for name, value in self.cdata.items(section):
144 for name, value in self.cdata.items(section):
145 if (section, name) in seen: continue
145 if (section, name) in seen: continue
146 yield section, name, value.replace('\n', '\\n')
146 yield section, name, value.replace('\n', '\\n')
147 seen[section, name] = 1
147 seen[section, name] = 1
148 if self.parentui is not None:
148 if self.parentui is not None:
149 for parent in self.parentui.walkconfig(seen):
149 for parent in self.parentui.walkconfig(seen):
150 yield parent
150 yield parent
151
151
152 def extensions(self):
152 def extensions(self):
153 result = self.configitems("extensions")
153 result = self.configitems("extensions")
154 for i, (key, value) in enumerate(result):
154 for i, (key, value) in enumerate(result):
155 if value:
155 if value:
156 result[i] = (key, os.path.expanduser(value))
156 result[i] = (key, os.path.expanduser(value))
157 return result
157 return result
158
158
159 def hgignorefiles(self):
159 def hgignorefiles(self):
160 result = []
160 result = []
161 for key, value in self.configitems("ui"):
161 for key, value in self.configitems("ui"):
162 if key == 'ignore' or key.startswith('ignore.'):
162 if key == 'ignore' or key.startswith('ignore.'):
163 result.append(os.path.expanduser(value))
163 result.append(os.path.expanduser(value))
164 return result
164 return result
165
165
166 def configrevlog(self):
166 def configrevlog(self):
167 result = {}
167 result = {}
168 for key, value in self.configitems("revlog"):
168 for key, value in self.configitems("revlog"):
169 result[key.lower()] = value
169 result[key.lower()] = value
170 return result
170 return result
171
171
172 def diffopts(self):
172 def diffopts(self):
173 if self.diffcache:
173 if self.diffcache:
174 return self.diffcache
174 return self.diffcache
175 result = {'showfunc': True, 'ignorews': False}
175 result = {'showfunc': True, 'ignorews': False}
176 for key, value in self.configitems("diff"):
176 for key, value in self.configitems("diff"):
177 if value:
177 if value:
178 result[key.lower()] = (value.lower() == 'true')
178 result[key.lower()] = (value.lower() == 'true')
179 self.diffcache = result
179 self.diffcache = result
180 return result
180 return result
181
181
182 def username(self):
182 def username(self):
183 """Return default username to be used in commits.
183 """Return default username to be used in commits.
184
184
185 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
185 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
186 and stop searching if one of these is set.
186 and stop searching if one of these is set.
187 Abort if found username is an empty string to force specifying
187 Abort if found username is an empty string to force specifying
188 the commit user elsewhere, e.g. with line option or repo hgrc.
188 the commit user elsewhere, e.g. with line option or repo hgrc.
189 If not found, use ($LOGNAME or $USER or $LNAME or
189 If not found, use ($LOGNAME or $USER or $LNAME or
190 $USERNAME) +"@full.hostname".
190 $USERNAME) +"@full.hostname".
191 """
191 """
192 user = os.environ.get("HGUSER")
192 user = os.environ.get("HGUSER")
193 if user is None:
193 if user is None:
194 user = self.config("ui", "username")
194 user = self.config("ui", "username")
195 if user is None:
195 if user is None:
196 user = os.environ.get("EMAIL")
196 user = os.environ.get("EMAIL")
197 if user is None:
197 if user is None:
198 try:
198 try:
199 user = '%s@%s' % (getpass.getuser(), socket.getfqdn())
199 user = '%s@%s' % (getpass.getuser(), socket.getfqdn())
200 except KeyError:
200 except KeyError:
201 raise util.Abort(_("Please specify a username."))
201 raise util.Abort(_("Please specify a username."))
202 return user
202 return user
203
203
204 def shortuser(self, user):
204 def shortuser(self, user):
205 """Return a short representation of a user name or email address."""
205 """Return a short representation of a user name or email address."""
206 if not self.verbose: user = util.shortuser(user)
206 if not self.verbose: user = util.shortuser(user)
207 return user
207 return user
208
208
209 def expandpath(self, loc, default=None):
209 def expandpath(self, loc, default=None):
210 """Return repository location relative to cwd or from [paths]"""
210 """Return repository location relative to cwd or from [paths]"""
211 if loc.find("://") != -1 or os.path.exists(loc):
211 if "://" in loc or os.path.exists(loc):
212 return loc
212 return loc
213
213
214 path = self.config("paths", loc)
214 path = self.config("paths", loc)
215 if not path and default is not None:
215 if not path and default is not None:
216 path = self.config("paths", default)
216 path = self.config("paths", default)
217 return path or loc
217 return path or loc
218
218
219 def write(self, *args):
219 def write(self, *args):
220 if self.header:
220 if self.header:
221 if self.header != self.prev_header:
221 if self.header != self.prev_header:
222 self.prev_header = self.header
222 self.prev_header = self.header
223 self.write(*self.header)
223 self.write(*self.header)
224 self.header = []
224 self.header = []
225 for a in args:
225 for a in args:
226 sys.stdout.write(str(a))
226 sys.stdout.write(str(a))
227
227
228 def write_header(self, *args):
228 def write_header(self, *args):
229 for a in args:
229 for a in args:
230 self.header.append(str(a))
230 self.header.append(str(a))
231
231
232 def write_err(self, *args):
232 def write_err(self, *args):
233 try:
233 try:
234 if not sys.stdout.closed: sys.stdout.flush()
234 if not sys.stdout.closed: sys.stdout.flush()
235 for a in args:
235 for a in args:
236 sys.stderr.write(str(a))
236 sys.stderr.write(str(a))
237 except IOError, inst:
237 except IOError, inst:
238 if inst.errno != errno.EPIPE:
238 if inst.errno != errno.EPIPE:
239 raise
239 raise
240
240
241 def flush(self):
241 def flush(self):
242 try: sys.stdout.flush()
242 try: sys.stdout.flush()
243 except: pass
243 except: pass
244 try: sys.stderr.flush()
244 try: sys.stderr.flush()
245 except: pass
245 except: pass
246
246
247 def readline(self):
247 def readline(self):
248 return sys.stdin.readline()[:-1]
248 return sys.stdin.readline()[:-1]
249 def prompt(self, msg, pat=None, default="y"):
249 def prompt(self, msg, pat=None, default="y"):
250 if not self.interactive: return default
250 if not self.interactive: return default
251 while 1:
251 while 1:
252 self.write(msg, " ")
252 self.write(msg, " ")
253 r = self.readline()
253 r = self.readline()
254 if not pat or re.match(pat, r):
254 if not pat or re.match(pat, r):
255 return r
255 return r
256 else:
256 else:
257 self.write(_("unrecognized response\n"))
257 self.write(_("unrecognized response\n"))
258 def getpass(self, prompt=None, default=None):
258 def getpass(self, prompt=None, default=None):
259 if not self.interactive: return default
259 if not self.interactive: return default
260 return getpass.getpass(prompt or _('password: '))
260 return getpass.getpass(prompt or _('password: '))
261 def status(self, *msg):
261 def status(self, *msg):
262 if not self.quiet: self.write(*msg)
262 if not self.quiet: self.write(*msg)
263 def warn(self, *msg):
263 def warn(self, *msg):
264 self.write_err(*msg)
264 self.write_err(*msg)
265 def note(self, *msg):
265 def note(self, *msg):
266 if self.verbose: self.write(*msg)
266 if self.verbose: self.write(*msg)
267 def debug(self, *msg):
267 def debug(self, *msg):
268 if self.debugflag: self.write(*msg)
268 if self.debugflag: self.write(*msg)
269 def edit(self, text, user):
269 def edit(self, text, user):
270 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
270 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
271 text=True)
271 text=True)
272 try:
272 try:
273 f = os.fdopen(fd, "w")
273 f = os.fdopen(fd, "w")
274 f.write(text)
274 f.write(text)
275 f.close()
275 f.close()
276
276
277 editor = (os.environ.get("HGEDITOR") or
277 editor = (os.environ.get("HGEDITOR") or
278 self.config("ui", "editor") or
278 self.config("ui", "editor") or
279 os.environ.get("EDITOR", "vi"))
279 os.environ.get("EDITOR", "vi"))
280
280
281 util.system("%s \"%s\"" % (editor, name),
281 util.system("%s \"%s\"" % (editor, name),
282 environ={'HGUSER': user},
282 environ={'HGUSER': user},
283 onerr=util.Abort, errprefix=_("edit failed"))
283 onerr=util.Abort, errprefix=_("edit failed"))
284
284
285 f = open(name)
285 f = open(name)
286 t = f.read()
286 t = f.read()
287 f.close()
287 f.close()
288 t = re.sub("(?m)^HG:.*\n", "", t)
288 t = re.sub("(?m)^HG:.*\n", "", t)
289 finally:
289 finally:
290 os.unlink(name)
290 os.unlink(name)
291
291
292 return t
292 return t
293
293
294 def sendmail(self):
294 def sendmail(self):
295 '''send mail message. object returned has one method, sendmail.
295 '''send mail message. object returned has one method, sendmail.
296 call as sendmail(sender, list-of-recipients, msg).'''
296 call as sendmail(sender, list-of-recipients, msg).'''
297
297
298 def smtp():
298 def smtp():
299 '''send mail using smtp.'''
299 '''send mail using smtp.'''
300
300
301 s = smtplib.SMTP()
301 s = smtplib.SMTP()
302 mailhost = self.config('smtp', 'host')
302 mailhost = self.config('smtp', 'host')
303 if not mailhost:
303 if not mailhost:
304 raise util.Abort(_('no [smtp]host in hgrc - cannot send mail'))
304 raise util.Abort(_('no [smtp]host in hgrc - cannot send mail'))
305 mailport = int(self.config('smtp', 'port', 25))
305 mailport = int(self.config('smtp', 'port', 25))
306 self.note(_('sending mail: smtp host %s, port %s\n') %
306 self.note(_('sending mail: smtp host %s, port %s\n') %
307 (mailhost, mailport))
307 (mailhost, mailport))
308 s.connect(host=mailhost, port=mailport)
308 s.connect(host=mailhost, port=mailport)
309 if self.configbool('smtp', 'tls'):
309 if self.configbool('smtp', 'tls'):
310 self.note(_('(using tls)\n'))
310 self.note(_('(using tls)\n'))
311 s.ehlo()
311 s.ehlo()
312 s.starttls()
312 s.starttls()
313 s.ehlo()
313 s.ehlo()
314 username = self.config('smtp', 'username')
314 username = self.config('smtp', 'username')
315 password = self.config('smtp', 'password')
315 password = self.config('smtp', 'password')
316 if username and password:
316 if username and password:
317 self.note(_('(authenticating to mail server as %s)\n') %
317 self.note(_('(authenticating to mail server as %s)\n') %
318 (username))
318 (username))
319 s.login(username, password)
319 s.login(username, password)
320 return s
320 return s
321
321
322 class sendmail(object):
322 class sendmail(object):
323 '''send mail using sendmail.'''
323 '''send mail using sendmail.'''
324
324
325 def __init__(self, ui, program):
325 def __init__(self, ui, program):
326 self.ui = ui
326 self.ui = ui
327 self.program = program
327 self.program = program
328
328
329 def sendmail(self, sender, recipients, msg):
329 def sendmail(self, sender, recipients, msg):
330 cmdline = '%s -f %s %s' % (
330 cmdline = '%s -f %s %s' % (
331 self.program, templater.email(sender),
331 self.program, templater.email(sender),
332 ' '.join(map(templater.email, recipients)))
332 ' '.join(map(templater.email, recipients)))
333 self.ui.note(_('sending mail: %s\n') % cmdline)
333 self.ui.note(_('sending mail: %s\n') % cmdline)
334 fp = os.popen(cmdline, 'w')
334 fp = os.popen(cmdline, 'w')
335 fp.write(msg)
335 fp.write(msg)
336 ret = fp.close()
336 ret = fp.close()
337 if ret:
337 if ret:
338 raise util.Abort('%s %s' % (
338 raise util.Abort('%s %s' % (
339 os.path.basename(self.program.split(None, 1)[0]),
339 os.path.basename(self.program.split(None, 1)[0]),
340 util.explain_exit(ret)[0]))
340 util.explain_exit(ret)[0]))
341
341
342 method = self.config('email', 'method', 'smtp')
342 method = self.config('email', 'method', 'smtp')
343 if method == 'smtp':
343 if method == 'smtp':
344 mail = smtp()
344 mail = smtp()
345 else:
345 else:
346 mail = sendmail(self, method)
346 mail = sendmail(self, method)
347 return mail
347 return mail
348
348
349 def print_exc(self):
349 def print_exc(self):
350 '''print exception traceback if traceback printing enabled.
350 '''print exception traceback if traceback printing enabled.
351 only to call in exception handler. returns true if traceback
351 only to call in exception handler. returns true if traceback
352 printed.'''
352 printed.'''
353 if self.traceback:
353 if self.traceback:
354 traceback.print_exc()
354 traceback.print_exc()
355 return self.traceback
355 return self.traceback
@@ -1,950 +1,950 b''
1 """
1 """
2 util.py - Mercurial utility functions and platform specfic implementations
2 util.py - Mercurial utility functions and platform specfic implementations
3
3
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5
5
6 This software may be used and distributed according to the terms
6 This software may be used and distributed according to the terms
7 of the GNU General Public License, incorporated herein by reference.
7 of the GNU General Public License, incorporated herein by reference.
8
8
9 This contains helper routines that are independent of the SCM core and hide
9 This contains helper routines that are independent of the SCM core and hide
10 platform-specific details from the core.
10 platform-specific details from the core.
11 """
11 """
12
12
13 from i18n import gettext as _
13 from i18n import gettext as _
14 from demandload import *
14 from demandload import *
15 demandload(globals(), "cStringIO errno popen2 re shutil sys tempfile")
15 demandload(globals(), "cStringIO errno popen2 re shutil sys tempfile")
16 demandload(globals(), "os threading time")
16 demandload(globals(), "os threading time")
17
17
18 class SignalInterrupt(Exception):
18 class SignalInterrupt(Exception):
19 """Exception raised on SIGTERM and SIGHUP."""
19 """Exception raised on SIGTERM and SIGHUP."""
20
20
21 def pipefilter(s, cmd):
21 def pipefilter(s, cmd):
22 '''filter string S through command CMD, returning its output'''
22 '''filter string S through command CMD, returning its output'''
23 (pout, pin) = popen2.popen2(cmd, -1, 'b')
23 (pout, pin) = popen2.popen2(cmd, -1, 'b')
24 def writer():
24 def writer():
25 try:
25 try:
26 pin.write(s)
26 pin.write(s)
27 pin.close()
27 pin.close()
28 except IOError, inst:
28 except IOError, inst:
29 if inst.errno != errno.EPIPE:
29 if inst.errno != errno.EPIPE:
30 raise
30 raise
31
31
32 # we should use select instead on UNIX, but this will work on most
32 # we should use select instead on UNIX, but this will work on most
33 # systems, including Windows
33 # systems, including Windows
34 w = threading.Thread(target=writer)
34 w = threading.Thread(target=writer)
35 w.start()
35 w.start()
36 f = pout.read()
36 f = pout.read()
37 pout.close()
37 pout.close()
38 w.join()
38 w.join()
39 return f
39 return f
40
40
41 def tempfilter(s, cmd):
41 def tempfilter(s, cmd):
42 '''filter string S through a pair of temporary files with CMD.
42 '''filter string S through a pair of temporary files with CMD.
43 CMD is used as a template to create the real command to be run,
43 CMD is used as a template to create the real command to be run,
44 with the strings INFILE and OUTFILE replaced by the real names of
44 with the strings INFILE and OUTFILE replaced by the real names of
45 the temporary files generated.'''
45 the temporary files generated.'''
46 inname, outname = None, None
46 inname, outname = None, None
47 try:
47 try:
48 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
48 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
49 fp = os.fdopen(infd, 'wb')
49 fp = os.fdopen(infd, 'wb')
50 fp.write(s)
50 fp.write(s)
51 fp.close()
51 fp.close()
52 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
52 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
53 os.close(outfd)
53 os.close(outfd)
54 cmd = cmd.replace('INFILE', inname)
54 cmd = cmd.replace('INFILE', inname)
55 cmd = cmd.replace('OUTFILE', outname)
55 cmd = cmd.replace('OUTFILE', outname)
56 code = os.system(cmd)
56 code = os.system(cmd)
57 if code: raise Abort(_("command '%s' failed: %s") %
57 if code: raise Abort(_("command '%s' failed: %s") %
58 (cmd, explain_exit(code)))
58 (cmd, explain_exit(code)))
59 return open(outname, 'rb').read()
59 return open(outname, 'rb').read()
60 finally:
60 finally:
61 try:
61 try:
62 if inname: os.unlink(inname)
62 if inname: os.unlink(inname)
63 except: pass
63 except: pass
64 try:
64 try:
65 if outname: os.unlink(outname)
65 if outname: os.unlink(outname)
66 except: pass
66 except: pass
67
67
68 filtertable = {
68 filtertable = {
69 'tempfile:': tempfilter,
69 'tempfile:': tempfilter,
70 'pipe:': pipefilter,
70 'pipe:': pipefilter,
71 }
71 }
72
72
73 def filter(s, cmd):
73 def filter(s, cmd):
74 "filter a string through a command that transforms its input to its output"
74 "filter a string through a command that transforms its input to its output"
75 for name, fn in filtertable.iteritems():
75 for name, fn in filtertable.iteritems():
76 if cmd.startswith(name):
76 if cmd.startswith(name):
77 return fn(s, cmd[len(name):].lstrip())
77 return fn(s, cmd[len(name):].lstrip())
78 return pipefilter(s, cmd)
78 return pipefilter(s, cmd)
79
79
80 def find_in_path(name, path, default=None):
80 def find_in_path(name, path, default=None):
81 '''find name in search path. path can be string (will be split
81 '''find name in search path. path can be string (will be split
82 with os.pathsep), or iterable thing that returns strings. if name
82 with os.pathsep), or iterable thing that returns strings. if name
83 found, return path to name. else return default.'''
83 found, return path to name. else return default.'''
84 if isinstance(path, str):
84 if isinstance(path, str):
85 path = path.split(os.pathsep)
85 path = path.split(os.pathsep)
86 for p in path:
86 for p in path:
87 p_name = os.path.join(p, name)
87 p_name = os.path.join(p, name)
88 if os.path.exists(p_name):
88 if os.path.exists(p_name):
89 return p_name
89 return p_name
90 return default
90 return default
91
91
92 def patch(strip, patchname, ui):
92 def patch(strip, patchname, ui):
93 """apply the patch <patchname> to the working directory.
93 """apply the patch <patchname> to the working directory.
94 a list of patched files is returned"""
94 a list of patched files is returned"""
95 patcher = find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
95 patcher = find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
96 fp = os.popen('%s -p%d < "%s"' % (patcher, strip, patchname))
96 fp = os.popen('%s -p%d < "%s"' % (patcher, strip, patchname))
97 files = {}
97 files = {}
98 for line in fp:
98 for line in fp:
99 line = line.rstrip()
99 line = line.rstrip()
100 ui.status("%s\n" % line)
100 ui.status("%s\n" % line)
101 if line.startswith('patching file '):
101 if line.startswith('patching file '):
102 pf = parse_patch_output(line)
102 pf = parse_patch_output(line)
103 files.setdefault(pf, 1)
103 files.setdefault(pf, 1)
104 code = fp.close()
104 code = fp.close()
105 if code:
105 if code:
106 raise Abort(_("patch command failed: %s") % explain_exit(code)[0])
106 raise Abort(_("patch command failed: %s") % explain_exit(code)[0])
107 return files.keys()
107 return files.keys()
108
108
109 def binary(s):
109 def binary(s):
110 """return true if a string is binary data using diff's heuristic"""
110 """return true if a string is binary data using diff's heuristic"""
111 if s and '\0' in s[:4096]:
111 if s and '\0' in s[:4096]:
112 return True
112 return True
113 return False
113 return False
114
114
115 def unique(g):
115 def unique(g):
116 """return the uniq elements of iterable g"""
116 """return the uniq elements of iterable g"""
117 seen = {}
117 seen = {}
118 for f in g:
118 for f in g:
119 if f not in seen:
119 if f not in seen:
120 seen[f] = 1
120 seen[f] = 1
121 yield f
121 yield f
122
122
123 class Abort(Exception):
123 class Abort(Exception):
124 """Raised if a command needs to print an error and exit."""
124 """Raised if a command needs to print an error and exit."""
125
125
126 def always(fn): return True
126 def always(fn): return True
127 def never(fn): return False
127 def never(fn): return False
128
128
129 def patkind(name, dflt_pat='glob'):
129 def patkind(name, dflt_pat='glob'):
130 """Split a string into an optional pattern kind prefix and the
130 """Split a string into an optional pattern kind prefix and the
131 actual pattern."""
131 actual pattern."""
132 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
132 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
133 if name.startswith(prefix + ':'): return name.split(':', 1)
133 if name.startswith(prefix + ':'): return name.split(':', 1)
134 return dflt_pat, name
134 return dflt_pat, name
135
135
136 def globre(pat, head='^', tail='$'):
136 def globre(pat, head='^', tail='$'):
137 "convert a glob pattern into a regexp"
137 "convert a glob pattern into a regexp"
138 i, n = 0, len(pat)
138 i, n = 0, len(pat)
139 res = ''
139 res = ''
140 group = False
140 group = False
141 def peek(): return i < n and pat[i]
141 def peek(): return i < n and pat[i]
142 while i < n:
142 while i < n:
143 c = pat[i]
143 c = pat[i]
144 i = i+1
144 i = i+1
145 if c == '*':
145 if c == '*':
146 if peek() == '*':
146 if peek() == '*':
147 i += 1
147 i += 1
148 res += '.*'
148 res += '.*'
149 else:
149 else:
150 res += '[^/]*'
150 res += '[^/]*'
151 elif c == '?':
151 elif c == '?':
152 res += '.'
152 res += '.'
153 elif c == '[':
153 elif c == '[':
154 j = i
154 j = i
155 if j < n and pat[j] in '!]':
155 if j < n and pat[j] in '!]':
156 j += 1
156 j += 1
157 while j < n and pat[j] != ']':
157 while j < n and pat[j] != ']':
158 j += 1
158 j += 1
159 if j >= n:
159 if j >= n:
160 res += '\\['
160 res += '\\['
161 else:
161 else:
162 stuff = pat[i:j].replace('\\','\\\\')
162 stuff = pat[i:j].replace('\\','\\\\')
163 i = j + 1
163 i = j + 1
164 if stuff[0] == '!':
164 if stuff[0] == '!':
165 stuff = '^' + stuff[1:]
165 stuff = '^' + stuff[1:]
166 elif stuff[0] == '^':
166 elif stuff[0] == '^':
167 stuff = '\\' + stuff
167 stuff = '\\' + stuff
168 res = '%s[%s]' % (res, stuff)
168 res = '%s[%s]' % (res, stuff)
169 elif c == '{':
169 elif c == '{':
170 group = True
170 group = True
171 res += '(?:'
171 res += '(?:'
172 elif c == '}' and group:
172 elif c == '}' and group:
173 res += ')'
173 res += ')'
174 group = False
174 group = False
175 elif c == ',' and group:
175 elif c == ',' and group:
176 res += '|'
176 res += '|'
177 elif c == '\\':
177 elif c == '\\':
178 p = peek()
178 p = peek()
179 if p:
179 if p:
180 i += 1
180 i += 1
181 res += re.escape(p)
181 res += re.escape(p)
182 else:
182 else:
183 res += re.escape(c)
183 res += re.escape(c)
184 else:
184 else:
185 res += re.escape(c)
185 res += re.escape(c)
186 return head + res + tail
186 return head + res + tail
187
187
188 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
188 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
189
189
190 def pathto(n1, n2):
190 def pathto(n1, n2):
191 '''return the relative path from one place to another.
191 '''return the relative path from one place to another.
192 this returns a path in the form used by the local filesystem, not hg.'''
192 this returns a path in the form used by the local filesystem, not hg.'''
193 if not n1: return localpath(n2)
193 if not n1: return localpath(n2)
194 a, b = n1.split('/'), n2.split('/')
194 a, b = n1.split('/'), n2.split('/')
195 a.reverse()
195 a.reverse()
196 b.reverse()
196 b.reverse()
197 while a and b and a[-1] == b[-1]:
197 while a and b and a[-1] == b[-1]:
198 a.pop()
198 a.pop()
199 b.pop()
199 b.pop()
200 b.reverse()
200 b.reverse()
201 return os.sep.join((['..'] * len(a)) + b)
201 return os.sep.join((['..'] * len(a)) + b)
202
202
203 def canonpath(root, cwd, myname):
203 def canonpath(root, cwd, myname):
204 """return the canonical path of myname, given cwd and root"""
204 """return the canonical path of myname, given cwd and root"""
205 if root == os.sep:
205 if root == os.sep:
206 rootsep = os.sep
206 rootsep = os.sep
207 elif root.endswith(os.sep):
207 elif root.endswith(os.sep):
208 rootsep = root
208 rootsep = root
209 else:
209 else:
210 rootsep = root + os.sep
210 rootsep = root + os.sep
211 name = myname
211 name = myname
212 if not os.path.isabs(name):
212 if not os.path.isabs(name):
213 name = os.path.join(root, cwd, name)
213 name = os.path.join(root, cwd, name)
214 name = os.path.normpath(name)
214 name = os.path.normpath(name)
215 if name != rootsep and name.startswith(rootsep):
215 if name != rootsep and name.startswith(rootsep):
216 name = name[len(rootsep):]
216 name = name[len(rootsep):]
217 audit_path(name)
217 audit_path(name)
218 return pconvert(name)
218 return pconvert(name)
219 elif name == root:
219 elif name == root:
220 return ''
220 return ''
221 else:
221 else:
222 # Determine whether `name' is in the hierarchy at or beneath `root',
222 # Determine whether `name' is in the hierarchy at or beneath `root',
223 # by iterating name=dirname(name) until that causes no change (can't
223 # by iterating name=dirname(name) until that causes no change (can't
224 # check name == '/', because that doesn't work on windows). For each
224 # check name == '/', because that doesn't work on windows). For each
225 # `name', compare dev/inode numbers. If they match, the list `rel'
225 # `name', compare dev/inode numbers. If they match, the list `rel'
226 # holds the reversed list of components making up the relative file
226 # holds the reversed list of components making up the relative file
227 # name we want.
227 # name we want.
228 root_st = os.stat(root)
228 root_st = os.stat(root)
229 rel = []
229 rel = []
230 while True:
230 while True:
231 try:
231 try:
232 name_st = os.stat(name)
232 name_st = os.stat(name)
233 except OSError:
233 except OSError:
234 break
234 break
235 if samestat(name_st, root_st):
235 if samestat(name_st, root_st):
236 rel.reverse()
236 rel.reverse()
237 name = os.path.join(*rel)
237 name = os.path.join(*rel)
238 audit_path(name)
238 audit_path(name)
239 return pconvert(name)
239 return pconvert(name)
240 dirname, basename = os.path.split(name)
240 dirname, basename = os.path.split(name)
241 rel.append(basename)
241 rel.append(basename)
242 if dirname == name:
242 if dirname == name:
243 break
243 break
244 name = dirname
244 name = dirname
245
245
246 raise Abort('%s not under root' % myname)
246 raise Abort('%s not under root' % myname)
247
247
248 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
248 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
249 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
249 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
250
250
251 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
251 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
252 if os.name == 'nt':
252 if os.name == 'nt':
253 dflt_pat = 'glob'
253 dflt_pat = 'glob'
254 else:
254 else:
255 dflt_pat = 'relpath'
255 dflt_pat = 'relpath'
256 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
256 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
257
257
258 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
258 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
259 """build a function to match a set of file patterns
259 """build a function to match a set of file patterns
260
260
261 arguments:
261 arguments:
262 canonroot - the canonical root of the tree you're matching against
262 canonroot - the canonical root of the tree you're matching against
263 cwd - the current working directory, if relevant
263 cwd - the current working directory, if relevant
264 names - patterns to find
264 names - patterns to find
265 inc - patterns to include
265 inc - patterns to include
266 exc - patterns to exclude
266 exc - patterns to exclude
267 head - a regex to prepend to patterns to control whether a match is rooted
267 head - a regex to prepend to patterns to control whether a match is rooted
268
268
269 a pattern is one of:
269 a pattern is one of:
270 'glob:<rooted glob>'
270 'glob:<rooted glob>'
271 're:<rooted regexp>'
271 're:<rooted regexp>'
272 'path:<rooted path>'
272 'path:<rooted path>'
273 'relglob:<relative glob>'
273 'relglob:<relative glob>'
274 'relpath:<relative path>'
274 'relpath:<relative path>'
275 'relre:<relative regexp>'
275 'relre:<relative regexp>'
276 '<rooted path or regexp>'
276 '<rooted path or regexp>'
277
277
278 returns:
278 returns:
279 a 3-tuple containing
279 a 3-tuple containing
280 - list of explicit non-pattern names passed in
280 - list of explicit non-pattern names passed in
281 - a bool match(filename) function
281 - a bool match(filename) function
282 - a bool indicating if any patterns were passed in
282 - a bool indicating if any patterns were passed in
283
283
284 todo:
284 todo:
285 make head regex a rooted bool
285 make head regex a rooted bool
286 """
286 """
287
287
288 def contains_glob(name):
288 def contains_glob(name):
289 for c in name:
289 for c in name:
290 if c in _globchars: return True
290 if c in _globchars: return True
291 return False
291 return False
292
292
293 def regex(kind, name, tail):
293 def regex(kind, name, tail):
294 '''convert a pattern into a regular expression'''
294 '''convert a pattern into a regular expression'''
295 if kind == 're':
295 if kind == 're':
296 return name
296 return name
297 elif kind == 'path':
297 elif kind == 'path':
298 return '^' + re.escape(name) + '(?:/|$)'
298 return '^' + re.escape(name) + '(?:/|$)'
299 elif kind == 'relglob':
299 elif kind == 'relglob':
300 return head + globre(name, '(?:|.*/)', tail)
300 return head + globre(name, '(?:|.*/)', tail)
301 elif kind == 'relpath':
301 elif kind == 'relpath':
302 return head + re.escape(name) + tail
302 return head + re.escape(name) + tail
303 elif kind == 'relre':
303 elif kind == 'relre':
304 if name.startswith('^'):
304 if name.startswith('^'):
305 return name
305 return name
306 return '.*' + name
306 return '.*' + name
307 return head + globre(name, '', tail)
307 return head + globre(name, '', tail)
308
308
309 def matchfn(pats, tail):
309 def matchfn(pats, tail):
310 """build a matching function from a set of patterns"""
310 """build a matching function from a set of patterns"""
311 if not pats:
311 if not pats:
312 return
312 return
313 matches = []
313 matches = []
314 for k, p in pats:
314 for k, p in pats:
315 try:
315 try:
316 pat = '(?:%s)' % regex(k, p, tail)
316 pat = '(?:%s)' % regex(k, p, tail)
317 matches.append(re.compile(pat).match)
317 matches.append(re.compile(pat).match)
318 except re.error:
318 except re.error:
319 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
319 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
320 else: raise Abort("invalid pattern (%s): %s" % (k, p))
320 else: raise Abort("invalid pattern (%s): %s" % (k, p))
321
321
322 def buildfn(text):
322 def buildfn(text):
323 for m in matches:
323 for m in matches:
324 r = m(text)
324 r = m(text)
325 if r:
325 if r:
326 return r
326 return r
327
327
328 return buildfn
328 return buildfn
329
329
330 def globprefix(pat):
330 def globprefix(pat):
331 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
331 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
332 root = []
332 root = []
333 for p in pat.split(os.sep):
333 for p in pat.split(os.sep):
334 if contains_glob(p): break
334 if contains_glob(p): break
335 root.append(p)
335 root.append(p)
336 return '/'.join(root)
336 return '/'.join(root)
337
337
338 pats = []
338 pats = []
339 files = []
339 files = []
340 roots = []
340 roots = []
341 for kind, name in [patkind(p, dflt_pat) for p in names]:
341 for kind, name in [patkind(p, dflt_pat) for p in names]:
342 if kind in ('glob', 'relpath'):
342 if kind in ('glob', 'relpath'):
343 name = canonpath(canonroot, cwd, name)
343 name = canonpath(canonroot, cwd, name)
344 if name == '':
344 if name == '':
345 kind, name = 'glob', '**'
345 kind, name = 'glob', '**'
346 if kind in ('glob', 'path', 're'):
346 if kind in ('glob', 'path', 're'):
347 pats.append((kind, name))
347 pats.append((kind, name))
348 if kind == 'glob':
348 if kind == 'glob':
349 root = globprefix(name)
349 root = globprefix(name)
350 if root: roots.append(root)
350 if root: roots.append(root)
351 elif kind == 'relpath':
351 elif kind == 'relpath':
352 files.append((kind, name))
352 files.append((kind, name))
353 roots.append(name)
353 roots.append(name)
354
354
355 patmatch = matchfn(pats, '$') or always
355 patmatch = matchfn(pats, '$') or always
356 filematch = matchfn(files, '(?:/|$)') or always
356 filematch = matchfn(files, '(?:/|$)') or always
357 incmatch = always
357 incmatch = always
358 if inc:
358 if inc:
359 inckinds = [patkind(canonpath(canonroot, cwd, i)) for i in inc]
359 inckinds = [patkind(canonpath(canonroot, cwd, i)) for i in inc]
360 incmatch = matchfn(inckinds, '(?:/|$)')
360 incmatch = matchfn(inckinds, '(?:/|$)')
361 excmatch = lambda fn: False
361 excmatch = lambda fn: False
362 if exc:
362 if exc:
363 exckinds = [patkind(canonpath(canonroot, cwd, x)) for x in exc]
363 exckinds = [patkind(canonpath(canonroot, cwd, x)) for x in exc]
364 excmatch = matchfn(exckinds, '(?:/|$)')
364 excmatch = matchfn(exckinds, '(?:/|$)')
365
365
366 return (roots,
366 return (roots,
367 lambda fn: (incmatch(fn) and not excmatch(fn) and
367 lambda fn: (incmatch(fn) and not excmatch(fn) and
368 (fn.endswith('/') or
368 (fn.endswith('/') or
369 (not pats and not files) or
369 (not pats and not files) or
370 (pats and patmatch(fn)) or
370 (pats and patmatch(fn)) or
371 (files and filematch(fn)))),
371 (files and filematch(fn)))),
372 (inc or exc or (pats and pats != [('glob', '**')])) and True)
372 (inc or exc or (pats and pats != [('glob', '**')])) and True)
373
373
374 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
374 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
375 '''enhanced shell command execution.
375 '''enhanced shell command execution.
376 run with environment maybe modified, maybe in different dir.
376 run with environment maybe modified, maybe in different dir.
377
377
378 if command fails and onerr is None, return status. if ui object,
378 if command fails and onerr is None, return status. if ui object,
379 print error message and return status, else raise onerr object as
379 print error message and return status, else raise onerr object as
380 exception.'''
380 exception.'''
381 oldenv = {}
381 oldenv = {}
382 for k in environ:
382 for k in environ:
383 oldenv[k] = os.environ.get(k)
383 oldenv[k] = os.environ.get(k)
384 if cwd is not None:
384 if cwd is not None:
385 oldcwd = os.getcwd()
385 oldcwd = os.getcwd()
386 try:
386 try:
387 for k, v in environ.iteritems():
387 for k, v in environ.iteritems():
388 os.environ[k] = str(v)
388 os.environ[k] = str(v)
389 if cwd is not None and oldcwd != cwd:
389 if cwd is not None and oldcwd != cwd:
390 os.chdir(cwd)
390 os.chdir(cwd)
391 rc = os.system(cmd)
391 rc = os.system(cmd)
392 if rc and onerr:
392 if rc and onerr:
393 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
393 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
394 explain_exit(rc)[0])
394 explain_exit(rc)[0])
395 if errprefix:
395 if errprefix:
396 errmsg = '%s: %s' % (errprefix, errmsg)
396 errmsg = '%s: %s' % (errprefix, errmsg)
397 try:
397 try:
398 onerr.warn(errmsg + '\n')
398 onerr.warn(errmsg + '\n')
399 except AttributeError:
399 except AttributeError:
400 raise onerr(errmsg)
400 raise onerr(errmsg)
401 return rc
401 return rc
402 finally:
402 finally:
403 for k, v in oldenv.iteritems():
403 for k, v in oldenv.iteritems():
404 if v is None:
404 if v is None:
405 del os.environ[k]
405 del os.environ[k]
406 else:
406 else:
407 os.environ[k] = v
407 os.environ[k] = v
408 if cwd is not None and oldcwd != cwd:
408 if cwd is not None and oldcwd != cwd:
409 os.chdir(oldcwd)
409 os.chdir(oldcwd)
410
410
411 def rename(src, dst):
411 def rename(src, dst):
412 """forcibly rename a file"""
412 """forcibly rename a file"""
413 try:
413 try:
414 os.rename(src, dst)
414 os.rename(src, dst)
415 except OSError, err:
415 except OSError, err:
416 # on windows, rename to existing file is not allowed, so we
416 # on windows, rename to existing file is not allowed, so we
417 # must delete destination first. but if file is open, unlink
417 # must delete destination first. but if file is open, unlink
418 # schedules it for delete but does not delete it. rename
418 # schedules it for delete but does not delete it. rename
419 # happens immediately even for open files, so we create
419 # happens immediately even for open files, so we create
420 # temporary file, delete it, rename destination to that name,
420 # temporary file, delete it, rename destination to that name,
421 # then delete that. then rename is safe to do.
421 # then delete that. then rename is safe to do.
422 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
422 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
423 os.close(fd)
423 os.close(fd)
424 os.unlink(temp)
424 os.unlink(temp)
425 os.rename(dst, temp)
425 os.rename(dst, temp)
426 os.unlink(temp)
426 os.unlink(temp)
427 os.rename(src, dst)
427 os.rename(src, dst)
428
428
429 def unlink(f):
429 def unlink(f):
430 """unlink and remove the directory if it is empty"""
430 """unlink and remove the directory if it is empty"""
431 os.unlink(f)
431 os.unlink(f)
432 # try removing directories that might now be empty
432 # try removing directories that might now be empty
433 try:
433 try:
434 os.removedirs(os.path.dirname(f))
434 os.removedirs(os.path.dirname(f))
435 except OSError:
435 except OSError:
436 pass
436 pass
437
437
438 def copyfiles(src, dst, hardlink=None):
438 def copyfiles(src, dst, hardlink=None):
439 """Copy a directory tree using hardlinks if possible"""
439 """Copy a directory tree using hardlinks if possible"""
440
440
441 if hardlink is None:
441 if hardlink is None:
442 hardlink = (os.stat(src).st_dev ==
442 hardlink = (os.stat(src).st_dev ==
443 os.stat(os.path.dirname(dst)).st_dev)
443 os.stat(os.path.dirname(dst)).st_dev)
444
444
445 if os.path.isdir(src):
445 if os.path.isdir(src):
446 os.mkdir(dst)
446 os.mkdir(dst)
447 for name in os.listdir(src):
447 for name in os.listdir(src):
448 srcname = os.path.join(src, name)
448 srcname = os.path.join(src, name)
449 dstname = os.path.join(dst, name)
449 dstname = os.path.join(dst, name)
450 copyfiles(srcname, dstname, hardlink)
450 copyfiles(srcname, dstname, hardlink)
451 else:
451 else:
452 if hardlink:
452 if hardlink:
453 try:
453 try:
454 os_link(src, dst)
454 os_link(src, dst)
455 except (IOError, OSError):
455 except (IOError, OSError):
456 hardlink = False
456 hardlink = False
457 shutil.copy(src, dst)
457 shutil.copy(src, dst)
458 else:
458 else:
459 shutil.copy(src, dst)
459 shutil.copy(src, dst)
460
460
461 def audit_path(path):
461 def audit_path(path):
462 """Abort if path contains dangerous components"""
462 """Abort if path contains dangerous components"""
463 parts = os.path.normcase(path).split(os.sep)
463 parts = os.path.normcase(path).split(os.sep)
464 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
464 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
465 or os.pardir in parts):
465 or os.pardir in parts):
466 raise Abort(_("path contains illegal component: %s\n") % path)
466 raise Abort(_("path contains illegal component: %s\n") % path)
467
467
468 def _makelock_file(info, pathname):
468 def _makelock_file(info, pathname):
469 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
469 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
470 os.write(ld, info)
470 os.write(ld, info)
471 os.close(ld)
471 os.close(ld)
472
472
473 def _readlock_file(pathname):
473 def _readlock_file(pathname):
474 return posixfile(pathname).read()
474 return posixfile(pathname).read()
475
475
476 def nlinks(pathname):
476 def nlinks(pathname):
477 """Return number of hardlinks for the given file."""
477 """Return number of hardlinks for the given file."""
478 return os.lstat(pathname).st_nlink
478 return os.lstat(pathname).st_nlink
479
479
480 if hasattr(os, 'link'):
480 if hasattr(os, 'link'):
481 os_link = os.link
481 os_link = os.link
482 else:
482 else:
483 def os_link(src, dst):
483 def os_link(src, dst):
484 raise OSError(0, _("Hardlinks not supported"))
484 raise OSError(0, _("Hardlinks not supported"))
485
485
486 def fstat(fp):
486 def fstat(fp):
487 '''stat file object that may not have fileno method.'''
487 '''stat file object that may not have fileno method.'''
488 try:
488 try:
489 return os.fstat(fp.fileno())
489 return os.fstat(fp.fileno())
490 except AttributeError:
490 except AttributeError:
491 return os.stat(fp.name)
491 return os.stat(fp.name)
492
492
493 posixfile = file
493 posixfile = file
494
494
495 def is_win_9x():
495 def is_win_9x():
496 '''return true if run on windows 95, 98 or me.'''
496 '''return true if run on windows 95, 98 or me.'''
497 try:
497 try:
498 return sys.getwindowsversion()[3] == 1
498 return sys.getwindowsversion()[3] == 1
499 except AttributeError:
499 except AttributeError:
500 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
500 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
501
501
502 # Platform specific variants
502 # Platform specific variants
503 if os.name == 'nt':
503 if os.name == 'nt':
504 demandload(globals(), "msvcrt")
504 demandload(globals(), "msvcrt")
505 nulldev = 'NUL:'
505 nulldev = 'NUL:'
506
506
507 class winstdout:
507 class winstdout:
508 '''stdout on windows misbehaves if sent through a pipe'''
508 '''stdout on windows misbehaves if sent through a pipe'''
509
509
510 def __init__(self, fp):
510 def __init__(self, fp):
511 self.fp = fp
511 self.fp = fp
512
512
513 def __getattr__(self, key):
513 def __getattr__(self, key):
514 return getattr(self.fp, key)
514 return getattr(self.fp, key)
515
515
516 def close(self):
516 def close(self):
517 try:
517 try:
518 self.fp.close()
518 self.fp.close()
519 except: pass
519 except: pass
520
520
521 def write(self, s):
521 def write(self, s):
522 try:
522 try:
523 return self.fp.write(s)
523 return self.fp.write(s)
524 except IOError, inst:
524 except IOError, inst:
525 if inst.errno != 0: raise
525 if inst.errno != 0: raise
526 self.close()
526 self.close()
527 raise IOError(errno.EPIPE, 'Broken pipe')
527 raise IOError(errno.EPIPE, 'Broken pipe')
528
528
529 sys.stdout = winstdout(sys.stdout)
529 sys.stdout = winstdout(sys.stdout)
530
530
531 def system_rcpath():
531 def system_rcpath():
532 try:
532 try:
533 return system_rcpath_win32()
533 return system_rcpath_win32()
534 except:
534 except:
535 return [r'c:\mercurial\mercurial.ini']
535 return [r'c:\mercurial\mercurial.ini']
536
536
537 def os_rcpath():
537 def os_rcpath():
538 '''return default os-specific hgrc search path'''
538 '''return default os-specific hgrc search path'''
539 path = system_rcpath()
539 path = system_rcpath()
540 path.append(user_rcpath())
540 path.append(user_rcpath())
541 userprofile = os.environ.get('USERPROFILE')
541 userprofile = os.environ.get('USERPROFILE')
542 if userprofile:
542 if userprofile:
543 path.append(os.path.join(userprofile, 'mercurial.ini'))
543 path.append(os.path.join(userprofile, 'mercurial.ini'))
544 return path
544 return path
545
545
546 def user_rcpath():
546 def user_rcpath():
547 '''return os-specific hgrc search path to the user dir'''
547 '''return os-specific hgrc search path to the user dir'''
548 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
548 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
549
549
550 def parse_patch_output(output_line):
550 def parse_patch_output(output_line):
551 """parses the output produced by patch and returns the file name"""
551 """parses the output produced by patch and returns the file name"""
552 pf = output_line[14:]
552 pf = output_line[14:]
553 if pf[0] == '`':
553 if pf[0] == '`':
554 pf = pf[1:-1] # Remove the quotes
554 pf = pf[1:-1] # Remove the quotes
555 return pf
555 return pf
556
556
557 def testpid(pid):
557 def testpid(pid):
558 '''return False if pid dead, True if running or not known'''
558 '''return False if pid dead, True if running or not known'''
559 return True
559 return True
560
560
561 def is_exec(f, last):
561 def is_exec(f, last):
562 return last
562 return last
563
563
564 def set_exec(f, mode):
564 def set_exec(f, mode):
565 pass
565 pass
566
566
567 def set_binary(fd):
567 def set_binary(fd):
568 msvcrt.setmode(fd.fileno(), os.O_BINARY)
568 msvcrt.setmode(fd.fileno(), os.O_BINARY)
569
569
570 def pconvert(path):
570 def pconvert(path):
571 return path.replace("\\", "/")
571 return path.replace("\\", "/")
572
572
573 def localpath(path):
573 def localpath(path):
574 return path.replace('/', '\\')
574 return path.replace('/', '\\')
575
575
576 def normpath(path):
576 def normpath(path):
577 return pconvert(os.path.normpath(path))
577 return pconvert(os.path.normpath(path))
578
578
579 makelock = _makelock_file
579 makelock = _makelock_file
580 readlock = _readlock_file
580 readlock = _readlock_file
581
581
582 def samestat(s1, s2):
582 def samestat(s1, s2):
583 return False
583 return False
584
584
585 def explain_exit(code):
585 def explain_exit(code):
586 return _("exited with status %d") % code, code
586 return _("exited with status %d") % code, code
587
587
588 try:
588 try:
589 # override functions with win32 versions if possible
589 # override functions with win32 versions if possible
590 from util_win32 import *
590 from util_win32 import *
591 if not is_win_9x():
591 if not is_win_9x():
592 posixfile = posixfile_nt
592 posixfile = posixfile_nt
593 except ImportError:
593 except ImportError:
594 pass
594 pass
595
595
596 else:
596 else:
597 nulldev = '/dev/null'
597 nulldev = '/dev/null'
598
598
599 def rcfiles(path):
599 def rcfiles(path):
600 rcs = [os.path.join(path, 'hgrc')]
600 rcs = [os.path.join(path, 'hgrc')]
601 rcdir = os.path.join(path, 'hgrc.d')
601 rcdir = os.path.join(path, 'hgrc.d')
602 try:
602 try:
603 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
603 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
604 if f.endswith(".rc")])
604 if f.endswith(".rc")])
605 except OSError, inst: pass
605 except OSError, inst: pass
606 return rcs
606 return rcs
607
607
608 def os_rcpath():
608 def os_rcpath():
609 '''return default os-specific hgrc search path'''
609 '''return default os-specific hgrc search path'''
610 path = []
610 path = []
611 # old mod_python does not set sys.argv
611 # old mod_python does not set sys.argv
612 if len(getattr(sys, 'argv', [])) > 0:
612 if len(getattr(sys, 'argv', [])) > 0:
613 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
613 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
614 '/../etc/mercurial'))
614 '/../etc/mercurial'))
615 path.extend(rcfiles('/etc/mercurial'))
615 path.extend(rcfiles('/etc/mercurial'))
616 path.append(os.path.expanduser('~/.hgrc'))
616 path.append(os.path.expanduser('~/.hgrc'))
617 path = [os.path.normpath(f) for f in path]
617 path = [os.path.normpath(f) for f in path]
618 return path
618 return path
619
619
620 def parse_patch_output(output_line):
620 def parse_patch_output(output_line):
621 """parses the output produced by patch and returns the file name"""
621 """parses the output produced by patch and returns the file name"""
622 pf = output_line[14:]
622 pf = output_line[14:]
623 if pf.startswith("'") and pf.endswith("'") and pf.find(" ") >= 0:
623 if pf.startswith("'") and pf.endswith("'") and " " in pf:
624 pf = pf[1:-1] # Remove the quotes
624 pf = pf[1:-1] # Remove the quotes
625 return pf
625 return pf
626
626
627 def is_exec(f, last):
627 def is_exec(f, last):
628 """check whether a file is executable"""
628 """check whether a file is executable"""
629 return (os.lstat(f).st_mode & 0100 != 0)
629 return (os.lstat(f).st_mode & 0100 != 0)
630
630
631 def set_exec(f, mode):
631 def set_exec(f, mode):
632 s = os.lstat(f).st_mode
632 s = os.lstat(f).st_mode
633 if (s & 0100 != 0) == mode:
633 if (s & 0100 != 0) == mode:
634 return
634 return
635 if mode:
635 if mode:
636 # Turn on +x for every +r bit when making a file executable
636 # Turn on +x for every +r bit when making a file executable
637 # and obey umask.
637 # and obey umask.
638 umask = os.umask(0)
638 umask = os.umask(0)
639 os.umask(umask)
639 os.umask(umask)
640 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
640 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
641 else:
641 else:
642 os.chmod(f, s & 0666)
642 os.chmod(f, s & 0666)
643
643
644 def set_binary(fd):
644 def set_binary(fd):
645 pass
645 pass
646
646
647 def pconvert(path):
647 def pconvert(path):
648 return path
648 return path
649
649
650 def localpath(path):
650 def localpath(path):
651 return path
651 return path
652
652
653 normpath = os.path.normpath
653 normpath = os.path.normpath
654 samestat = os.path.samestat
654 samestat = os.path.samestat
655
655
656 def makelock(info, pathname):
656 def makelock(info, pathname):
657 try:
657 try:
658 os.symlink(info, pathname)
658 os.symlink(info, pathname)
659 except OSError, why:
659 except OSError, why:
660 if why.errno == errno.EEXIST:
660 if why.errno == errno.EEXIST:
661 raise
661 raise
662 else:
662 else:
663 _makelock_file(info, pathname)
663 _makelock_file(info, pathname)
664
664
665 def readlock(pathname):
665 def readlock(pathname):
666 try:
666 try:
667 return os.readlink(pathname)
667 return os.readlink(pathname)
668 except OSError, why:
668 except OSError, why:
669 if why.errno == errno.EINVAL:
669 if why.errno == errno.EINVAL:
670 return _readlock_file(pathname)
670 return _readlock_file(pathname)
671 else:
671 else:
672 raise
672 raise
673
673
674 def testpid(pid):
674 def testpid(pid):
675 '''return False if pid dead, True if running or not sure'''
675 '''return False if pid dead, True if running or not sure'''
676 try:
676 try:
677 os.kill(pid, 0)
677 os.kill(pid, 0)
678 return True
678 return True
679 except OSError, inst:
679 except OSError, inst:
680 return inst.errno != errno.ESRCH
680 return inst.errno != errno.ESRCH
681
681
682 def explain_exit(code):
682 def explain_exit(code):
683 """return a 2-tuple (desc, code) describing a process's status"""
683 """return a 2-tuple (desc, code) describing a process's status"""
684 if os.WIFEXITED(code):
684 if os.WIFEXITED(code):
685 val = os.WEXITSTATUS(code)
685 val = os.WEXITSTATUS(code)
686 return _("exited with status %d") % val, val
686 return _("exited with status %d") % val, val
687 elif os.WIFSIGNALED(code):
687 elif os.WIFSIGNALED(code):
688 val = os.WTERMSIG(code)
688 val = os.WTERMSIG(code)
689 return _("killed by signal %d") % val, val
689 return _("killed by signal %d") % val, val
690 elif os.WIFSTOPPED(code):
690 elif os.WIFSTOPPED(code):
691 val = os.WSTOPSIG(code)
691 val = os.WSTOPSIG(code)
692 return _("stopped by signal %d") % val, val
692 return _("stopped by signal %d") % val, val
693 raise ValueError(_("invalid exit code"))
693 raise ValueError(_("invalid exit code"))
694
694
695 def opener(base, audit=True):
695 def opener(base, audit=True):
696 """
696 """
697 return a function that opens files relative to base
697 return a function that opens files relative to base
698
698
699 this function is used to hide the details of COW semantics and
699 this function is used to hide the details of COW semantics and
700 remote file access from higher level code.
700 remote file access from higher level code.
701 """
701 """
702 p = base
702 p = base
703 audit_p = audit
703 audit_p = audit
704
704
705 def mktempcopy(name):
705 def mktempcopy(name):
706 d, fn = os.path.split(name)
706 d, fn = os.path.split(name)
707 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
707 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
708 os.close(fd)
708 os.close(fd)
709 ofp = posixfile(temp, "wb")
709 ofp = posixfile(temp, "wb")
710 try:
710 try:
711 try:
711 try:
712 ifp = posixfile(name, "rb")
712 ifp = posixfile(name, "rb")
713 except IOError, inst:
713 except IOError, inst:
714 if not getattr(inst, 'filename', None):
714 if not getattr(inst, 'filename', None):
715 inst.filename = name
715 inst.filename = name
716 raise
716 raise
717 for chunk in filechunkiter(ifp):
717 for chunk in filechunkiter(ifp):
718 ofp.write(chunk)
718 ofp.write(chunk)
719 ifp.close()
719 ifp.close()
720 ofp.close()
720 ofp.close()
721 except:
721 except:
722 try: os.unlink(temp)
722 try: os.unlink(temp)
723 except: pass
723 except: pass
724 raise
724 raise
725 st = os.lstat(name)
725 st = os.lstat(name)
726 os.chmod(temp, st.st_mode)
726 os.chmod(temp, st.st_mode)
727 return temp
727 return temp
728
728
729 class atomictempfile(posixfile):
729 class atomictempfile(posixfile):
730 """the file will only be copied when rename is called"""
730 """the file will only be copied when rename is called"""
731 def __init__(self, name, mode):
731 def __init__(self, name, mode):
732 self.__name = name
732 self.__name = name
733 self.temp = mktempcopy(name)
733 self.temp = mktempcopy(name)
734 posixfile.__init__(self, self.temp, mode)
734 posixfile.__init__(self, self.temp, mode)
735 def rename(self):
735 def rename(self):
736 if not self.closed:
736 if not self.closed:
737 posixfile.close(self)
737 posixfile.close(self)
738 rename(self.temp, localpath(self.__name))
738 rename(self.temp, localpath(self.__name))
739 def __del__(self):
739 def __del__(self):
740 if not self.closed:
740 if not self.closed:
741 try:
741 try:
742 os.unlink(self.temp)
742 os.unlink(self.temp)
743 except: pass
743 except: pass
744 posixfile.close(self)
744 posixfile.close(self)
745
745
746 class atomicfile(atomictempfile):
746 class atomicfile(atomictempfile):
747 """the file will only be copied on close"""
747 """the file will only be copied on close"""
748 def __init__(self, name, mode):
748 def __init__(self, name, mode):
749 atomictempfile.__init__(self, name, mode)
749 atomictempfile.__init__(self, name, mode)
750 def close(self):
750 def close(self):
751 self.rename()
751 self.rename()
752 def __del__(self):
752 def __del__(self):
753 self.rename()
753 self.rename()
754
754
755 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
755 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
756 if audit_p:
756 if audit_p:
757 audit_path(path)
757 audit_path(path)
758 f = os.path.join(p, path)
758 f = os.path.join(p, path)
759
759
760 if not text:
760 if not text:
761 mode += "b" # for that other OS
761 mode += "b" # for that other OS
762
762
763 if mode[0] != "r":
763 if mode[0] != "r":
764 try:
764 try:
765 nlink = nlinks(f)
765 nlink = nlinks(f)
766 except OSError:
766 except OSError:
767 d = os.path.dirname(f)
767 d = os.path.dirname(f)
768 if not os.path.isdir(d):
768 if not os.path.isdir(d):
769 os.makedirs(d)
769 os.makedirs(d)
770 else:
770 else:
771 if atomic:
771 if atomic:
772 return atomicfile(f, mode)
772 return atomicfile(f, mode)
773 elif atomictemp:
773 elif atomictemp:
774 return atomictempfile(f, mode)
774 return atomictempfile(f, mode)
775 if nlink > 1:
775 if nlink > 1:
776 rename(mktempcopy(f), f)
776 rename(mktempcopy(f), f)
777 return posixfile(f, mode)
777 return posixfile(f, mode)
778
778
779 return o
779 return o
780
780
781 class chunkbuffer(object):
781 class chunkbuffer(object):
782 """Allow arbitrary sized chunks of data to be efficiently read from an
782 """Allow arbitrary sized chunks of data to be efficiently read from an
783 iterator over chunks of arbitrary size."""
783 iterator over chunks of arbitrary size."""
784
784
785 def __init__(self, in_iter, targetsize = 2**16):
785 def __init__(self, in_iter, targetsize = 2**16):
786 """in_iter is the iterator that's iterating over the input chunks.
786 """in_iter is the iterator that's iterating over the input chunks.
787 targetsize is how big a buffer to try to maintain."""
787 targetsize is how big a buffer to try to maintain."""
788 self.in_iter = iter(in_iter)
788 self.in_iter = iter(in_iter)
789 self.buf = ''
789 self.buf = ''
790 self.targetsize = int(targetsize)
790 self.targetsize = int(targetsize)
791 if self.targetsize <= 0:
791 if self.targetsize <= 0:
792 raise ValueError(_("targetsize must be greater than 0, was %d") %
792 raise ValueError(_("targetsize must be greater than 0, was %d") %
793 targetsize)
793 targetsize)
794 self.iterempty = False
794 self.iterempty = False
795
795
796 def fillbuf(self):
796 def fillbuf(self):
797 """Ignore target size; read every chunk from iterator until empty."""
797 """Ignore target size; read every chunk from iterator until empty."""
798 if not self.iterempty:
798 if not self.iterempty:
799 collector = cStringIO.StringIO()
799 collector = cStringIO.StringIO()
800 collector.write(self.buf)
800 collector.write(self.buf)
801 for ch in self.in_iter:
801 for ch in self.in_iter:
802 collector.write(ch)
802 collector.write(ch)
803 self.buf = collector.getvalue()
803 self.buf = collector.getvalue()
804 self.iterempty = True
804 self.iterempty = True
805
805
806 def read(self, l):
806 def read(self, l):
807 """Read L bytes of data from the iterator of chunks of data.
807 """Read L bytes of data from the iterator of chunks of data.
808 Returns less than L bytes if the iterator runs dry."""
808 Returns less than L bytes if the iterator runs dry."""
809 if l > len(self.buf) and not self.iterempty:
809 if l > len(self.buf) and not self.iterempty:
810 # Clamp to a multiple of self.targetsize
810 # Clamp to a multiple of self.targetsize
811 targetsize = self.targetsize * ((l // self.targetsize) + 1)
811 targetsize = self.targetsize * ((l // self.targetsize) + 1)
812 collector = cStringIO.StringIO()
812 collector = cStringIO.StringIO()
813 collector.write(self.buf)
813 collector.write(self.buf)
814 collected = len(self.buf)
814 collected = len(self.buf)
815 for chunk in self.in_iter:
815 for chunk in self.in_iter:
816 collector.write(chunk)
816 collector.write(chunk)
817 collected += len(chunk)
817 collected += len(chunk)
818 if collected >= targetsize:
818 if collected >= targetsize:
819 break
819 break
820 if collected < targetsize:
820 if collected < targetsize:
821 self.iterempty = True
821 self.iterempty = True
822 self.buf = collector.getvalue()
822 self.buf = collector.getvalue()
823 s, self.buf = self.buf[:l], buffer(self.buf, l)
823 s, self.buf = self.buf[:l], buffer(self.buf, l)
824 return s
824 return s
825
825
826 def filechunkiter(f, size=65536, limit=None):
826 def filechunkiter(f, size=65536, limit=None):
827 """Create a generator that produces the data in the file size
827 """Create a generator that produces the data in the file size
828 (default 65536) bytes at a time, up to optional limit (default is
828 (default 65536) bytes at a time, up to optional limit (default is
829 to read all data). Chunks may be less than size bytes if the
829 to read all data). Chunks may be less than size bytes if the
830 chunk is the last chunk in the file, or the file is a socket or
830 chunk is the last chunk in the file, or the file is a socket or
831 some other type of file that sometimes reads less data than is
831 some other type of file that sometimes reads less data than is
832 requested."""
832 requested."""
833 assert size >= 0
833 assert size >= 0
834 assert limit is None or limit >= 0
834 assert limit is None or limit >= 0
835 while True:
835 while True:
836 if limit is None: nbytes = size
836 if limit is None: nbytes = size
837 else: nbytes = min(limit, size)
837 else: nbytes = min(limit, size)
838 s = nbytes and f.read(nbytes)
838 s = nbytes and f.read(nbytes)
839 if not s: break
839 if not s: break
840 if limit: limit -= len(s)
840 if limit: limit -= len(s)
841 yield s
841 yield s
842
842
843 def makedate():
843 def makedate():
844 lt = time.localtime()
844 lt = time.localtime()
845 if lt[8] == 1 and time.daylight:
845 if lt[8] == 1 and time.daylight:
846 tz = time.altzone
846 tz = time.altzone
847 else:
847 else:
848 tz = time.timezone
848 tz = time.timezone
849 return time.mktime(lt), tz
849 return time.mktime(lt), tz
850
850
851 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
851 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
852 """represent a (unixtime, offset) tuple as a localized time.
852 """represent a (unixtime, offset) tuple as a localized time.
853 unixtime is seconds since the epoch, and offset is the time zone's
853 unixtime is seconds since the epoch, and offset is the time zone's
854 number of seconds away from UTC. if timezone is false, do not
854 number of seconds away from UTC. if timezone is false, do not
855 append time zone to string."""
855 append time zone to string."""
856 t, tz = date or makedate()
856 t, tz = date or makedate()
857 s = time.strftime(format, time.gmtime(float(t) - tz))
857 s = time.strftime(format, time.gmtime(float(t) - tz))
858 if timezone:
858 if timezone:
859 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
859 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
860 return s
860 return s
861
861
862 def strdate(string, format='%a %b %d %H:%M:%S %Y'):
862 def strdate(string, format='%a %b %d %H:%M:%S %Y'):
863 """parse a localized time string and return a (unixtime, offset) tuple.
863 """parse a localized time string and return a (unixtime, offset) tuple.
864 if the string cannot be parsed, ValueError is raised."""
864 if the string cannot be parsed, ValueError is raised."""
865 def hastimezone(string):
865 def hastimezone(string):
866 return (string[-4:].isdigit() and
866 return (string[-4:].isdigit() and
867 (string[-5] == '+' or string[-5] == '-') and
867 (string[-5] == '+' or string[-5] == '-') and
868 string[-6].isspace())
868 string[-6].isspace())
869
869
870 if hastimezone(string):
870 if hastimezone(string):
871 date, tz = string[:-6], string[-5:]
871 date, tz = string[:-6], string[-5:]
872 tz = int(tz)
872 tz = int(tz)
873 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
873 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
874 else:
874 else:
875 date, offset = string, 0
875 date, offset = string, 0
876 when = int(time.mktime(time.strptime(date, format))) + offset
876 when = int(time.mktime(time.strptime(date, format))) + offset
877 return when, offset
877 return when, offset
878
878
879 def parsedate(string, formats=('%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M')):
879 def parsedate(string, formats=('%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M')):
880 """parse a localized time string and return a (unixtime, offset) tuple.
880 """parse a localized time string and return a (unixtime, offset) tuple.
881 The date may be a "unixtime offset" string or in one of the specified
881 The date may be a "unixtime offset" string or in one of the specified
882 formats."""
882 formats."""
883 try:
883 try:
884 when, offset = map(int, string.split(' '))
884 when, offset = map(int, string.split(' '))
885 except ValueError:
885 except ValueError:
886 for format in formats:
886 for format in formats:
887 try:
887 try:
888 when, offset = strdate(string, format)
888 when, offset = strdate(string, format)
889 except ValueError:
889 except ValueError:
890 pass
890 pass
891 else:
891 else:
892 break
892 break
893 else:
893 else:
894 raise ValueError(_('invalid date: %r') % string)
894 raise ValueError(_('invalid date: %r') % string)
895 # validate explicit (probably user-specified) date and
895 # validate explicit (probably user-specified) date and
896 # time zone offset. values must fit in signed 32 bits for
896 # time zone offset. values must fit in signed 32 bits for
897 # current 32-bit linux runtimes. timezones go from UTC-12
897 # current 32-bit linux runtimes. timezones go from UTC-12
898 # to UTC+14
898 # to UTC+14
899 if abs(when) > 0x7fffffff:
899 if abs(when) > 0x7fffffff:
900 raise ValueError(_('date exceeds 32 bits: %d') % when)
900 raise ValueError(_('date exceeds 32 bits: %d') % when)
901 if offset < -50400 or offset > 43200:
901 if offset < -50400 or offset > 43200:
902 raise ValueError(_('impossible time zone offset: %d') % offset)
902 raise ValueError(_('impossible time zone offset: %d') % offset)
903 return when, offset
903 return when, offset
904
904
905 def shortuser(user):
905 def shortuser(user):
906 """Return a short representation of a user name or email address."""
906 """Return a short representation of a user name or email address."""
907 f = user.find('@')
907 f = user.find('@')
908 if f >= 0:
908 if f >= 0:
909 user = user[:f]
909 user = user[:f]
910 f = user.find('<')
910 f = user.find('<')
911 if f >= 0:
911 if f >= 0:
912 user = user[f+1:]
912 user = user[f+1:]
913 return user
913 return user
914
914
915 def walkrepos(path):
915 def walkrepos(path):
916 '''yield every hg repository under path, recursively.'''
916 '''yield every hg repository under path, recursively.'''
917 def errhandler(err):
917 def errhandler(err):
918 if err.filename == path:
918 if err.filename == path:
919 raise err
919 raise err
920
920
921 for root, dirs, files in os.walk(path, onerror=errhandler):
921 for root, dirs, files in os.walk(path, onerror=errhandler):
922 for d in dirs:
922 for d in dirs:
923 if d == '.hg':
923 if d == '.hg':
924 yield root
924 yield root
925 dirs[:] = []
925 dirs[:] = []
926 break
926 break
927
927
928 _rcpath = None
928 _rcpath = None
929
929
930 def rcpath():
930 def rcpath():
931 '''return hgrc search path. if env var HGRCPATH is set, use it.
931 '''return hgrc search path. if env var HGRCPATH is set, use it.
932 for each item in path, if directory, use files ending in .rc,
932 for each item in path, if directory, use files ending in .rc,
933 else use item.
933 else use item.
934 make HGRCPATH empty to only look in .hg/hgrc of current repo.
934 make HGRCPATH empty to only look in .hg/hgrc of current repo.
935 if no HGRCPATH, use default os-specific path.'''
935 if no HGRCPATH, use default os-specific path.'''
936 global _rcpath
936 global _rcpath
937 if _rcpath is None:
937 if _rcpath is None:
938 if 'HGRCPATH' in os.environ:
938 if 'HGRCPATH' in os.environ:
939 _rcpath = []
939 _rcpath = []
940 for p in os.environ['HGRCPATH'].split(os.pathsep):
940 for p in os.environ['HGRCPATH'].split(os.pathsep):
941 if not p: continue
941 if not p: continue
942 if os.path.isdir(p):
942 if os.path.isdir(p):
943 for f in os.listdir(p):
943 for f in os.listdir(p):
944 if f.endswith('.rc'):
944 if f.endswith('.rc'):
945 _rcpath.append(os.path.join(p, f))
945 _rcpath.append(os.path.join(p, f))
946 else:
946 else:
947 _rcpath.append(p)
947 _rcpath.append(p)
948 else:
948 else:
949 _rcpath = os_rcpath()
949 _rcpath = os_rcpath()
950 return _rcpath
950 return _rcpath
General Comments 0
You need to be logged in to leave comments. Login now