##// END OF EJS Templates
Move urllib error handling from revlog into statichttprepo, where it belongs.
Bryan O'Sullivan -
r1325:57220daf default
parent child Browse files
Show More
@@ -1,2195 +1,2197 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 demandload(globals(), "os re sys signal shutil imp urllib pdb")
10 demandload(globals(), "os re sys signal shutil imp urllib pdb")
11 demandload(globals(), "fancyopts ui hg util lock revlog")
11 demandload(globals(), "fancyopts ui hg util lock revlog")
12 demandload(globals(), "fnmatch hgweb mdiff random signal time traceback")
12 demandload(globals(), "fnmatch hgweb mdiff random signal time traceback")
13 demandload(globals(), "errno socket version struct atexit sets bz2")
13 demandload(globals(), "errno socket version struct atexit sets bz2")
14
14
15 class UnknownCommand(Exception):
15 class UnknownCommand(Exception):
16 """Exception raised if command is not in the command table."""
16 """Exception raised if command is not in the command table."""
17
17
18 def filterfiles(filters, files):
18 def filterfiles(filters, files):
19 l = [x for x in files if x in filters]
19 l = [x for x in files if x in filters]
20
20
21 for t in filters:
21 for t in filters:
22 if t and t[-1] != "/":
22 if t and t[-1] != "/":
23 t += "/"
23 t += "/"
24 l += [x for x in files if x.startswith(t)]
24 l += [x for x in files if x.startswith(t)]
25 return l
25 return l
26
26
27 def relpath(repo, args):
27 def relpath(repo, args):
28 cwd = repo.getcwd()
28 cwd = repo.getcwd()
29 if cwd:
29 if cwd:
30 return [util.normpath(os.path.join(cwd, x)) for x in args]
30 return [util.normpath(os.path.join(cwd, x)) for x in args]
31 return args
31 return args
32
32
33 def matchpats(repo, cwd, pats=[], opts={}, head=''):
33 def matchpats(repo, cwd, pats=[], opts={}, head=''):
34 return util.matcher(repo.root, cwd, pats or ['.'], opts.get('include'),
34 return util.matcher(repo.root, cwd, pats or ['.'], opts.get('include'),
35 opts.get('exclude'), head)
35 opts.get('exclude'), head)
36
36
37 def makewalk(repo, pats, opts, head=''):
37 def makewalk(repo, pats, opts, head=''):
38 cwd = repo.getcwd()
38 cwd = repo.getcwd()
39 files, matchfn, anypats = matchpats(repo, cwd, pats, opts, head)
39 files, matchfn, anypats = matchpats(repo, cwd, pats, opts, head)
40 exact = dict(zip(files, files))
40 exact = dict(zip(files, files))
41 def walk():
41 def walk():
42 for src, fn in repo.walk(files=files, match=matchfn):
42 for src, fn in repo.walk(files=files, match=matchfn):
43 yield src, fn, util.pathto(cwd, fn), fn in exact
43 yield src, fn, util.pathto(cwd, fn), fn in exact
44 return files, matchfn, walk()
44 return files, matchfn, walk()
45
45
46 def walk(repo, pats, opts, head=''):
46 def walk(repo, pats, opts, head=''):
47 files, matchfn, results = makewalk(repo, pats, opts, head)
47 files, matchfn, results = makewalk(repo, pats, opts, head)
48 for r in results:
48 for r in results:
49 yield r
49 yield r
50
50
51 def walkchangerevs(ui, repo, cwd, pats, opts):
51 def walkchangerevs(ui, repo, cwd, pats, opts):
52 '''Iterate over files and the revs they changed in.
52 '''Iterate over files and the revs they changed in.
53
53
54 Callers most commonly need to iterate backwards over the history
54 Callers most commonly need to iterate backwards over the history
55 it is interested in. Doing so has awful (quadratic-looking)
55 it is interested in. Doing so has awful (quadratic-looking)
56 performance, so we use iterators in a "windowed" way.
56 performance, so we use iterators in a "windowed" way.
57
57
58 We walk a window of revisions in the desired order. Within the
58 We walk a window of revisions in the desired order. Within the
59 window, we first walk forwards to gather data, then in the desired
59 window, we first walk forwards to gather data, then in the desired
60 order (usually backwards) to display it.
60 order (usually backwards) to display it.
61
61
62 This function returns an (iterator, getchange) pair. The
62 This function returns an (iterator, getchange) pair. The
63 getchange function returns the changelog entry for a numeric
63 getchange function returns the changelog entry for a numeric
64 revision. The iterator yields 3-tuples. They will be of one of
64 revision. The iterator yields 3-tuples. They will be of one of
65 the following forms:
65 the following forms:
66
66
67 "window", incrementing, lastrev: stepping through a window,
67 "window", incrementing, lastrev: stepping through a window,
68 positive if walking forwards through revs, last rev in the
68 positive if walking forwards through revs, last rev in the
69 sequence iterated over - use to reset state for the current window
69 sequence iterated over - use to reset state for the current window
70
70
71 "add", rev, fns: out-of-order traversal of the given file names
71 "add", rev, fns: out-of-order traversal of the given file names
72 fns, which changed during revision rev - use to gather data for
72 fns, which changed during revision rev - use to gather data for
73 possible display
73 possible display
74
74
75 "iter", rev, None: in-order traversal of the revs earlier iterated
75 "iter", rev, None: in-order traversal of the revs earlier iterated
76 over with "add" - use to display data'''
76 over with "add" - use to display data'''
77 cwd = repo.getcwd()
77 cwd = repo.getcwd()
78 if not pats and cwd:
78 if not pats and cwd:
79 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
79 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
80 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
80 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
81 files, matchfn, anypats = matchpats(repo, (pats and cwd) or '',
81 files, matchfn, anypats = matchpats(repo, (pats and cwd) or '',
82 pats, opts)
82 pats, opts)
83 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
83 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
84 wanted = {}
84 wanted = {}
85 slowpath = anypats
85 slowpath = anypats
86 window = 300
86 window = 300
87 fncache = {}
87 fncache = {}
88
88
89 chcache = {}
89 chcache = {}
90 def getchange(rev):
90 def getchange(rev):
91 ch = chcache.get(rev)
91 ch = chcache.get(rev)
92 if ch is None:
92 if ch is None:
93 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
93 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
94 return ch
94 return ch
95
95
96 if not slowpath and not files:
96 if not slowpath and not files:
97 # No files, no patterns. Display all revs.
97 # No files, no patterns. Display all revs.
98 wanted = dict(zip(revs, revs))
98 wanted = dict(zip(revs, revs))
99 if not slowpath:
99 if not slowpath:
100 # Only files, no patterns. Check the history of each file.
100 # Only files, no patterns. Check the history of each file.
101 def filerevgen(filelog):
101 def filerevgen(filelog):
102 for i in xrange(filelog.count() - 1, -1, -window):
102 for i in xrange(filelog.count() - 1, -1, -window):
103 revs = []
103 revs = []
104 for j in xrange(max(0, i - window), i + 1):
104 for j in xrange(max(0, i - window), i + 1):
105 revs.append(filelog.linkrev(filelog.node(j)))
105 revs.append(filelog.linkrev(filelog.node(j)))
106 revs.reverse()
106 revs.reverse()
107 for rev in revs:
107 for rev in revs:
108 yield rev
108 yield rev
109
109
110 minrev, maxrev = min(revs), max(revs)
110 minrev, maxrev = min(revs), max(revs)
111 for file in files:
111 for file in files:
112 filelog = repo.file(file)
112 filelog = repo.file(file)
113 # A zero count may be a directory or deleted file, so
113 # A zero count may be a directory or deleted file, so
114 # try to find matching entries on the slow path.
114 # try to find matching entries on the slow path.
115 if filelog.count() == 0:
115 if filelog.count() == 0:
116 slowpath = True
116 slowpath = True
117 break
117 break
118 for rev in filerevgen(filelog):
118 for rev in filerevgen(filelog):
119 if rev <= maxrev:
119 if rev <= maxrev:
120 if rev < minrev:
120 if rev < minrev:
121 break
121 break
122 fncache.setdefault(rev, [])
122 fncache.setdefault(rev, [])
123 fncache[rev].append(file)
123 fncache[rev].append(file)
124 wanted[rev] = 1
124 wanted[rev] = 1
125 if slowpath:
125 if slowpath:
126 # The slow path checks files modified in every changeset.
126 # The slow path checks files modified in every changeset.
127 def changerevgen():
127 def changerevgen():
128 for i in xrange(repo.changelog.count() - 1, -1, -window):
128 for i in xrange(repo.changelog.count() - 1, -1, -window):
129 for j in xrange(max(0, i - window), i + 1):
129 for j in xrange(max(0, i - window), i + 1):
130 yield j, getchange(j)[3]
130 yield j, getchange(j)[3]
131
131
132 for rev, changefiles in changerevgen():
132 for rev, changefiles in changerevgen():
133 matches = filter(matchfn, changefiles)
133 matches = filter(matchfn, changefiles)
134 if matches:
134 if matches:
135 fncache[rev] = matches
135 fncache[rev] = matches
136 wanted[rev] = 1
136 wanted[rev] = 1
137
137
138 def iterate():
138 def iterate():
139 for i in xrange(0, len(revs), window):
139 for i in xrange(0, len(revs), window):
140 yield 'window', revs[0] < revs[-1], revs[-1]
140 yield 'window', revs[0] < revs[-1], revs[-1]
141 nrevs = [rev for rev in revs[i:min(i+window, len(revs))]
141 nrevs = [rev for rev in revs[i:min(i+window, len(revs))]
142 if rev in wanted]
142 if rev in wanted]
143 srevs = list(nrevs)
143 srevs = list(nrevs)
144 srevs.sort()
144 srevs.sort()
145 for rev in srevs:
145 for rev in srevs:
146 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
146 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
147 yield 'add', rev, fns
147 yield 'add', rev, fns
148 for rev in nrevs:
148 for rev in nrevs:
149 yield 'iter', rev, None
149 yield 'iter', rev, None
150 return iterate(), getchange
150 return iterate(), getchange
151
151
152 revrangesep = ':'
152 revrangesep = ':'
153
153
154 def revrange(ui, repo, revs, revlog=None):
154 def revrange(ui, repo, revs, revlog=None):
155 """Yield revision as strings from a list of revision specifications."""
155 """Yield revision as strings from a list of revision specifications."""
156 if revlog is None:
156 if revlog is None:
157 revlog = repo.changelog
157 revlog = repo.changelog
158 revcount = revlog.count()
158 revcount = revlog.count()
159 def fix(val, defval):
159 def fix(val, defval):
160 if not val:
160 if not val:
161 return defval
161 return defval
162 try:
162 try:
163 num = int(val)
163 num = int(val)
164 if str(num) != val:
164 if str(num) != val:
165 raise ValueError
165 raise ValueError
166 if num < 0: num += revcount
166 if num < 0: num += revcount
167 if num < 0: num = 0
167 if num < 0: num = 0
168 elif num >= revcount:
168 elif num >= revcount:
169 raise ValueError
169 raise ValueError
170 except ValueError:
170 except ValueError:
171 try:
171 try:
172 num = repo.changelog.rev(repo.lookup(val))
172 num = repo.changelog.rev(repo.lookup(val))
173 except KeyError:
173 except KeyError:
174 try:
174 try:
175 num = revlog.rev(revlog.lookup(val))
175 num = revlog.rev(revlog.lookup(val))
176 except KeyError:
176 except KeyError:
177 raise util.Abort('invalid revision identifier %s', val)
177 raise util.Abort('invalid revision identifier %s', val)
178 return num
178 return num
179 seen = {}
179 seen = {}
180 for spec in revs:
180 for spec in revs:
181 if spec.find(revrangesep) >= 0:
181 if spec.find(revrangesep) >= 0:
182 start, end = spec.split(revrangesep, 1)
182 start, end = spec.split(revrangesep, 1)
183 start = fix(start, 0)
183 start = fix(start, 0)
184 end = fix(end, revcount - 1)
184 end = fix(end, revcount - 1)
185 step = start > end and -1 or 1
185 step = start > end and -1 or 1
186 for rev in xrange(start, end+step, step):
186 for rev in xrange(start, end+step, step):
187 if rev in seen: continue
187 if rev in seen: continue
188 seen[rev] = 1
188 seen[rev] = 1
189 yield str(rev)
189 yield str(rev)
190 else:
190 else:
191 rev = fix(spec, None)
191 rev = fix(spec, None)
192 if rev in seen: continue
192 if rev in seen: continue
193 seen[rev] = 1
193 seen[rev] = 1
194 yield str(rev)
194 yield str(rev)
195
195
196 def make_filename(repo, r, pat, node=None,
196 def make_filename(repo, r, pat, node=None,
197 total=None, seqno=None, revwidth=None, pathname=None):
197 total=None, seqno=None, revwidth=None, pathname=None):
198 node_expander = {
198 node_expander = {
199 'H': lambda: hex(node),
199 'H': lambda: hex(node),
200 'R': lambda: str(r.rev(node)),
200 'R': lambda: str(r.rev(node)),
201 'h': lambda: short(node),
201 'h': lambda: short(node),
202 }
202 }
203 expander = {
203 expander = {
204 '%': lambda: '%',
204 '%': lambda: '%',
205 'b': lambda: os.path.basename(repo.root),
205 'b': lambda: os.path.basename(repo.root),
206 }
206 }
207
207
208 try:
208 try:
209 if node:
209 if node:
210 expander.update(node_expander)
210 expander.update(node_expander)
211 if node and revwidth is not None:
211 if node and revwidth is not None:
212 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
212 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
213 if total is not None:
213 if total is not None:
214 expander['N'] = lambda: str(total)
214 expander['N'] = lambda: str(total)
215 if seqno is not None:
215 if seqno is not None:
216 expander['n'] = lambda: str(seqno)
216 expander['n'] = lambda: str(seqno)
217 if total is not None and seqno is not None:
217 if total is not None and seqno is not None:
218 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
218 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
219 if pathname is not None:
219 if pathname is not None:
220 expander['s'] = lambda: os.path.basename(pathname)
220 expander['s'] = lambda: os.path.basename(pathname)
221 expander['d'] = lambda: os.path.dirname(pathname) or '.'
221 expander['d'] = lambda: os.path.dirname(pathname) or '.'
222 expander['p'] = lambda: pathname
222 expander['p'] = lambda: pathname
223
223
224 newname = []
224 newname = []
225 patlen = len(pat)
225 patlen = len(pat)
226 i = 0
226 i = 0
227 while i < patlen:
227 while i < patlen:
228 c = pat[i]
228 c = pat[i]
229 if c == '%':
229 if c == '%':
230 i += 1
230 i += 1
231 c = pat[i]
231 c = pat[i]
232 c = expander[c]()
232 c = expander[c]()
233 newname.append(c)
233 newname.append(c)
234 i += 1
234 i += 1
235 return ''.join(newname)
235 return ''.join(newname)
236 except KeyError, inst:
236 except KeyError, inst:
237 raise util.Abort("invalid format spec '%%%s' in output file name",
237 raise util.Abort("invalid format spec '%%%s' in output file name",
238 inst.args[0])
238 inst.args[0])
239
239
240 def make_file(repo, r, pat, node=None,
240 def make_file(repo, r, pat, node=None,
241 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
241 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
242 if not pat or pat == '-':
242 if not pat or pat == '-':
243 return 'w' in mode and sys.stdout or sys.stdin
243 return 'w' in mode and sys.stdout or sys.stdin
244 if hasattr(pat, 'write') and 'w' in mode:
244 if hasattr(pat, 'write') and 'w' in mode:
245 return pat
245 return pat
246 if hasattr(pat, 'read') and 'r' in mode:
246 if hasattr(pat, 'read') and 'r' in mode:
247 return pat
247 return pat
248 return open(make_filename(repo, r, pat, node, total, seqno, revwidth,
248 return open(make_filename(repo, r, pat, node, total, seqno, revwidth,
249 pathname),
249 pathname),
250 mode)
250 mode)
251
251
252 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
252 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
253 changes=None, text=False):
253 changes=None, text=False):
254 if not changes:
254 if not changes:
255 (c, a, d, u) = repo.changes(node1, node2, files, match=match)
255 (c, a, d, u) = repo.changes(node1, node2, files, match=match)
256 else:
256 else:
257 (c, a, d, u) = changes
257 (c, a, d, u) = changes
258 if files:
258 if files:
259 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
259 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
260
260
261 if not c and not a and not d:
261 if not c and not a and not d:
262 return
262 return
263
263
264 if node2:
264 if node2:
265 change = repo.changelog.read(node2)
265 change = repo.changelog.read(node2)
266 mmap2 = repo.manifest.read(change[0])
266 mmap2 = repo.manifest.read(change[0])
267 date2 = util.datestr(change[2])
267 date2 = util.datestr(change[2])
268 def read(f):
268 def read(f):
269 return repo.file(f).read(mmap2[f])
269 return repo.file(f).read(mmap2[f])
270 else:
270 else:
271 date2 = util.datestr()
271 date2 = util.datestr()
272 if not node1:
272 if not node1:
273 node1 = repo.dirstate.parents()[0]
273 node1 = repo.dirstate.parents()[0]
274 def read(f):
274 def read(f):
275 return repo.wfile(f).read()
275 return repo.wfile(f).read()
276
276
277 if ui.quiet:
277 if ui.quiet:
278 r = None
278 r = None
279 else:
279 else:
280 hexfunc = ui.verbose and hex or short
280 hexfunc = ui.verbose and hex or short
281 r = [hexfunc(node) for node in [node1, node2] if node]
281 r = [hexfunc(node) for node in [node1, node2] if node]
282
282
283 change = repo.changelog.read(node1)
283 change = repo.changelog.read(node1)
284 mmap = repo.manifest.read(change[0])
284 mmap = repo.manifest.read(change[0])
285 date1 = util.datestr(change[2])
285 date1 = util.datestr(change[2])
286
286
287 for f in c:
287 for f in c:
288 to = None
288 to = None
289 if f in mmap:
289 if f in mmap:
290 to = repo.file(f).read(mmap[f])
290 to = repo.file(f).read(mmap[f])
291 tn = read(f)
291 tn = read(f)
292 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
292 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
293 for f in a:
293 for f in a:
294 to = None
294 to = None
295 tn = read(f)
295 tn = read(f)
296 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
296 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
297 for f in d:
297 for f in d:
298 to = repo.file(f).read(mmap[f])
298 to = repo.file(f).read(mmap[f])
299 tn = None
299 tn = None
300 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
300 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
301
301
302 def trimuser(ui, name, rev, revcache):
302 def trimuser(ui, name, rev, revcache):
303 """trim the name of the user who committed a change"""
303 """trim the name of the user who committed a change"""
304 user = revcache.get(rev)
304 user = revcache.get(rev)
305 if user is None:
305 if user is None:
306 user = revcache[rev] = ui.shortuser(name)
306 user = revcache[rev] = ui.shortuser(name)
307 return user
307 return user
308
308
309 def show_changeset(ui, repo, rev=0, changenode=None, brinfo=None):
309 def show_changeset(ui, repo, rev=0, changenode=None, brinfo=None):
310 """show a single changeset or file revision"""
310 """show a single changeset or file revision"""
311 log = repo.changelog
311 log = repo.changelog
312 if changenode is None:
312 if changenode is None:
313 changenode = log.node(rev)
313 changenode = log.node(rev)
314 elif not rev:
314 elif not rev:
315 rev = log.rev(changenode)
315 rev = log.rev(changenode)
316
316
317 if ui.quiet:
317 if ui.quiet:
318 ui.write("%d:%s\n" % (rev, short(changenode)))
318 ui.write("%d:%s\n" % (rev, short(changenode)))
319 return
319 return
320
320
321 changes = log.read(changenode)
321 changes = log.read(changenode)
322 date = util.datestr(changes[2])
322 date = util.datestr(changes[2])
323
323
324 parents = [(log.rev(p), ui.verbose and hex(p) or short(p))
324 parents = [(log.rev(p), ui.verbose and hex(p) or short(p))
325 for p in log.parents(changenode)
325 for p in log.parents(changenode)
326 if ui.debugflag or p != nullid]
326 if ui.debugflag or p != nullid]
327 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
327 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
328 parents = []
328 parents = []
329
329
330 if ui.verbose:
330 if ui.verbose:
331 ui.write("changeset: %d:%s\n" % (rev, hex(changenode)))
331 ui.write("changeset: %d:%s\n" % (rev, hex(changenode)))
332 else:
332 else:
333 ui.write("changeset: %d:%s\n" % (rev, short(changenode)))
333 ui.write("changeset: %d:%s\n" % (rev, short(changenode)))
334
334
335 for tag in repo.nodetags(changenode):
335 for tag in repo.nodetags(changenode):
336 ui.status("tag: %s\n" % tag)
336 ui.status("tag: %s\n" % tag)
337 for parent in parents:
337 for parent in parents:
338 ui.write("parent: %d:%s\n" % parent)
338 ui.write("parent: %d:%s\n" % parent)
339
339
340 if brinfo and changenode in brinfo:
340 if brinfo and changenode in brinfo:
341 br = brinfo[changenode]
341 br = brinfo[changenode]
342 ui.write("branch: %s\n" % " ".join(br))
342 ui.write("branch: %s\n" % " ".join(br))
343
343
344 ui.debug("manifest: %d:%s\n" % (repo.manifest.rev(changes[0]),
344 ui.debug("manifest: %d:%s\n" % (repo.manifest.rev(changes[0]),
345 hex(changes[0])))
345 hex(changes[0])))
346 ui.status("user: %s\n" % changes[1])
346 ui.status("user: %s\n" % changes[1])
347 ui.status("date: %s\n" % date)
347 ui.status("date: %s\n" % date)
348
348
349 if ui.debugflag:
349 if ui.debugflag:
350 files = repo.changes(log.parents(changenode)[0], changenode)
350 files = repo.changes(log.parents(changenode)[0], changenode)
351 for key, value in zip(["files:", "files+:", "files-:"], files):
351 for key, value in zip(["files:", "files+:", "files-:"], files):
352 if value:
352 if value:
353 ui.note("%-12s %s\n" % (key, " ".join(value)))
353 ui.note("%-12s %s\n" % (key, " ".join(value)))
354 else:
354 else:
355 ui.note("files: %s\n" % " ".join(changes[3]))
355 ui.note("files: %s\n" % " ".join(changes[3]))
356
356
357 description = changes[4].strip()
357 description = changes[4].strip()
358 if description:
358 if description:
359 if ui.verbose:
359 if ui.verbose:
360 ui.status("description:\n")
360 ui.status("description:\n")
361 ui.status(description)
361 ui.status(description)
362 ui.status("\n\n")
362 ui.status("\n\n")
363 else:
363 else:
364 ui.status("summary: %s\n" % description.splitlines()[0])
364 ui.status("summary: %s\n" % description.splitlines()[0])
365 ui.status("\n")
365 ui.status("\n")
366
366
367 def show_version(ui):
367 def show_version(ui):
368 """output version and copyright information"""
368 """output version and copyright information"""
369 ui.write("Mercurial Distributed SCM (version %s)\n"
369 ui.write("Mercurial Distributed SCM (version %s)\n"
370 % version.get_version())
370 % version.get_version())
371 ui.status(
371 ui.status(
372 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
372 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
373 "This is free software; see the source for copying conditions. "
373 "This is free software; see the source for copying conditions. "
374 "There is NO\nwarranty; "
374 "There is NO\nwarranty; "
375 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
375 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
376 )
376 )
377
377
378 def help_(ui, cmd=None, with_version=False):
378 def help_(ui, cmd=None, with_version=False):
379 """show help for a given command or all commands"""
379 """show help for a given command or all commands"""
380 option_lists = []
380 option_lists = []
381 if cmd and cmd != 'shortlist':
381 if cmd and cmd != 'shortlist':
382 if with_version:
382 if with_version:
383 show_version(ui)
383 show_version(ui)
384 ui.write('\n')
384 ui.write('\n')
385 key, i = find(cmd)
385 key, i = find(cmd)
386 # synopsis
386 # synopsis
387 ui.write("%s\n\n" % i[2])
387 ui.write("%s\n\n" % i[2])
388
388
389 # description
389 # description
390 doc = i[0].__doc__
390 doc = i[0].__doc__
391 if ui.quiet:
391 if ui.quiet:
392 doc = doc.splitlines(0)[0]
392 doc = doc.splitlines(0)[0]
393 ui.write("%s\n" % doc.rstrip())
393 ui.write("%s\n" % doc.rstrip())
394
394
395 if not ui.quiet:
395 if not ui.quiet:
396 # aliases
396 # aliases
397 aliases = ', '.join(key.split('|')[1:])
397 aliases = ', '.join(key.split('|')[1:])
398 if aliases:
398 if aliases:
399 ui.write("\naliases: %s\n" % aliases)
399 ui.write("\naliases: %s\n" % aliases)
400
400
401 # options
401 # options
402 if i[1]:
402 if i[1]:
403 option_lists.append(("options", i[1]))
403 option_lists.append(("options", i[1]))
404
404
405 else:
405 else:
406 # program name
406 # program name
407 if ui.verbose or with_version:
407 if ui.verbose or with_version:
408 show_version(ui)
408 show_version(ui)
409 else:
409 else:
410 ui.status("Mercurial Distributed SCM\n")
410 ui.status("Mercurial Distributed SCM\n")
411 ui.status('\n')
411 ui.status('\n')
412
412
413 # list of commands
413 # list of commands
414 if cmd == "shortlist":
414 if cmd == "shortlist":
415 ui.status('basic commands (use "hg help" '
415 ui.status('basic commands (use "hg help" '
416 'for the full list or option "-v" for details):\n\n')
416 'for the full list or option "-v" for details):\n\n')
417 elif ui.verbose:
417 elif ui.verbose:
418 ui.status('list of commands:\n\n')
418 ui.status('list of commands:\n\n')
419 else:
419 else:
420 ui.status('list of commands (use "hg help -v" '
420 ui.status('list of commands (use "hg help -v" '
421 'to show aliases and global options):\n\n')
421 'to show aliases and global options):\n\n')
422
422
423 h = {}
423 h = {}
424 cmds = {}
424 cmds = {}
425 for c, e in table.items():
425 for c, e in table.items():
426 f = c.split("|")[0]
426 f = c.split("|")[0]
427 if cmd == "shortlist" and not f.startswith("^"):
427 if cmd == "shortlist" and not f.startswith("^"):
428 continue
428 continue
429 f = f.lstrip("^")
429 f = f.lstrip("^")
430 if not ui.debugflag and f.startswith("debug"):
430 if not ui.debugflag and f.startswith("debug"):
431 continue
431 continue
432 d = ""
432 d = ""
433 if e[0].__doc__:
433 if e[0].__doc__:
434 d = e[0].__doc__.splitlines(0)[0].rstrip()
434 d = e[0].__doc__.splitlines(0)[0].rstrip()
435 h[f] = d
435 h[f] = d
436 cmds[f]=c.lstrip("^")
436 cmds[f]=c.lstrip("^")
437
437
438 fns = h.keys()
438 fns = h.keys()
439 fns.sort()
439 fns.sort()
440 m = max(map(len, fns))
440 m = max(map(len, fns))
441 for f in fns:
441 for f in fns:
442 if ui.verbose:
442 if ui.verbose:
443 commands = cmds[f].replace("|",", ")
443 commands = cmds[f].replace("|",", ")
444 ui.write(" %s:\n %s\n"%(commands,h[f]))
444 ui.write(" %s:\n %s\n"%(commands,h[f]))
445 else:
445 else:
446 ui.write(' %-*s %s\n' % (m, f, h[f]))
446 ui.write(' %-*s %s\n' % (m, f, h[f]))
447
447
448 # global options
448 # global options
449 if ui.verbose:
449 if ui.verbose:
450 option_lists.append(("global options", globalopts))
450 option_lists.append(("global options", globalopts))
451
451
452 # list all option lists
452 # list all option lists
453 opt_output = []
453 opt_output = []
454 for title, options in option_lists:
454 for title, options in option_lists:
455 opt_output.append(("\n%s:\n" % title, None))
455 opt_output.append(("\n%s:\n" % title, None))
456 for shortopt, longopt, default, desc in options:
456 for shortopt, longopt, default, desc in options:
457 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
457 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
458 longopt and " --%s" % longopt),
458 longopt and " --%s" % longopt),
459 "%s%s" % (desc,
459 "%s%s" % (desc,
460 default and " (default: %s)" % default
460 default and " (default: %s)" % default
461 or "")))
461 or "")))
462
462
463 if opt_output:
463 if opt_output:
464 opts_len = max([len(line[0]) for line in opt_output if line[1]])
464 opts_len = max([len(line[0]) for line in opt_output if line[1]])
465 for first, second in opt_output:
465 for first, second in opt_output:
466 if second:
466 if second:
467 ui.write(" %-*s %s\n" % (opts_len, first, second))
467 ui.write(" %-*s %s\n" % (opts_len, first, second))
468 else:
468 else:
469 ui.write("%s\n" % first)
469 ui.write("%s\n" % first)
470
470
471 # Commands start here, listed alphabetically
471 # Commands start here, listed alphabetically
472
472
473 def add(ui, repo, *pats, **opts):
473 def add(ui, repo, *pats, **opts):
474 '''add the specified files on the next commit'''
474 '''add the specified files on the next commit'''
475 names = []
475 names = []
476 for src, abs, rel, exact in walk(repo, pats, opts):
476 for src, abs, rel, exact in walk(repo, pats, opts):
477 if exact:
477 if exact:
478 if ui.verbose: ui.status('adding %s\n' % rel)
478 if ui.verbose: ui.status('adding %s\n' % rel)
479 names.append(abs)
479 names.append(abs)
480 elif repo.dirstate.state(abs) == '?':
480 elif repo.dirstate.state(abs) == '?':
481 ui.status('adding %s\n' % rel)
481 ui.status('adding %s\n' % rel)
482 names.append(abs)
482 names.append(abs)
483 repo.add(names)
483 repo.add(names)
484
484
485 def addremove(ui, repo, *pats, **opts):
485 def addremove(ui, repo, *pats, **opts):
486 """add all new files, delete all missing files"""
486 """add all new files, delete all missing files"""
487 add, remove = [], []
487 add, remove = [], []
488 for src, abs, rel, exact in walk(repo, pats, opts):
488 for src, abs, rel, exact in walk(repo, pats, opts):
489 if src == 'f' and repo.dirstate.state(abs) == '?':
489 if src == 'f' and repo.dirstate.state(abs) == '?':
490 add.append(abs)
490 add.append(abs)
491 if ui.verbose or not exact:
491 if ui.verbose or not exact:
492 ui.status('adding ', rel, '\n')
492 ui.status('adding ', rel, '\n')
493 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
493 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
494 remove.append(abs)
494 remove.append(abs)
495 if ui.verbose or not exact:
495 if ui.verbose or not exact:
496 ui.status('removing ', rel, '\n')
496 ui.status('removing ', rel, '\n')
497 repo.add(add)
497 repo.add(add)
498 repo.remove(remove)
498 repo.remove(remove)
499
499
500 def annotate(ui, repo, *pats, **opts):
500 def annotate(ui, repo, *pats, **opts):
501 """show changeset information per file line"""
501 """show changeset information per file line"""
502 def getnode(rev):
502 def getnode(rev):
503 return short(repo.changelog.node(rev))
503 return short(repo.changelog.node(rev))
504
504
505 ucache = {}
505 ucache = {}
506 def getname(rev):
506 def getname(rev):
507 cl = repo.changelog.read(repo.changelog.node(rev))
507 cl = repo.changelog.read(repo.changelog.node(rev))
508 return trimuser(ui, cl[1], rev, ucache)
508 return trimuser(ui, cl[1], rev, ucache)
509
509
510 if not pats:
510 if not pats:
511 raise util.Abort('at least one file name or pattern required')
511 raise util.Abort('at least one file name or pattern required')
512
512
513 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
513 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
514 if not opts['user'] and not opts['changeset']:
514 if not opts['user'] and not opts['changeset']:
515 opts['number'] = 1
515 opts['number'] = 1
516
516
517 if opts['rev']:
517 if opts['rev']:
518 node = repo.changelog.lookup(opts['rev'])
518 node = repo.changelog.lookup(opts['rev'])
519 else:
519 else:
520 node = repo.dirstate.parents()[0]
520 node = repo.dirstate.parents()[0]
521 change = repo.changelog.read(node)
521 change = repo.changelog.read(node)
522 mmap = repo.manifest.read(change[0])
522 mmap = repo.manifest.read(change[0])
523
523
524 for src, abs, rel, exact in walk(repo, pats, opts):
524 for src, abs, rel, exact in walk(repo, pats, opts):
525 if abs not in mmap:
525 if abs not in mmap:
526 ui.warn("warning: %s is not in the repository!\n" % rel)
526 ui.warn("warning: %s is not in the repository!\n" % rel)
527 continue
527 continue
528
528
529 f = repo.file(abs)
529 f = repo.file(abs)
530 if not opts['text'] and util.binary(f.read(mmap[abs])):
530 if not opts['text'] and util.binary(f.read(mmap[abs])):
531 ui.write("%s: binary file\n" % rel)
531 ui.write("%s: binary file\n" % rel)
532 continue
532 continue
533
533
534 lines = f.annotate(mmap[abs])
534 lines = f.annotate(mmap[abs])
535 pieces = []
535 pieces = []
536
536
537 for o, f in opmap:
537 for o, f in opmap:
538 if opts[o]:
538 if opts[o]:
539 l = [f(n) for n, dummy in lines]
539 l = [f(n) for n, dummy in lines]
540 if l:
540 if l:
541 m = max(map(len, l))
541 m = max(map(len, l))
542 pieces.append(["%*s" % (m, x) for x in l])
542 pieces.append(["%*s" % (m, x) for x in l])
543
543
544 if pieces:
544 if pieces:
545 for p, l in zip(zip(*pieces), lines):
545 for p, l in zip(zip(*pieces), lines):
546 ui.write("%s: %s" % (" ".join(p), l[1]))
546 ui.write("%s: %s" % (" ".join(p), l[1]))
547
547
548 def bundle(ui, repo, fname, dest="default-push", **opts):
548 def bundle(ui, repo, fname, dest="default-push", **opts):
549 """create a changegroup file"""
549 """create a changegroup file"""
550 f = open(fname, "wb")
550 f = open(fname, "wb")
551 dest = ui.expandpath(dest)
551 dest = ui.expandpath(dest)
552 other = hg.repository(ui, dest)
552 other = hg.repository(ui, dest)
553 o = repo.findoutgoing(other)
553 o = repo.findoutgoing(other)
554 cg = repo.changegroup(o)
554 cg = repo.changegroup(o)
555
555
556 try:
556 try:
557 f.write("HG10")
557 f.write("HG10")
558 z = bz2.BZ2Compressor(9)
558 z = bz2.BZ2Compressor(9)
559 while 1:
559 while 1:
560 chunk = cg.read(4096)
560 chunk = cg.read(4096)
561 if not chunk:
561 if not chunk:
562 break
562 break
563 f.write(z.compress(chunk))
563 f.write(z.compress(chunk))
564 f.write(z.flush())
564 f.write(z.flush())
565 except:
565 except:
566 os.unlink(fname)
566 os.unlink(fname)
567
567
568 def cat(ui, repo, file1, *pats, **opts):
568 def cat(ui, repo, file1, *pats, **opts):
569 """output the latest or given revisions of files"""
569 """output the latest or given revisions of files"""
570 mf = {}
570 mf = {}
571 if opts['rev']:
571 if opts['rev']:
572 change = repo.changelog.read(repo.lookup(opts['rev']))
572 change = repo.changelog.read(repo.lookup(opts['rev']))
573 mf = repo.manifest.read(change[0])
573 mf = repo.manifest.read(change[0])
574 for src, abs, rel, exact in walk(repo, (file1,) + pats, opts):
574 for src, abs, rel, exact in walk(repo, (file1,) + pats, opts):
575 r = repo.file(abs)
575 r = repo.file(abs)
576 if opts['rev']:
576 if opts['rev']:
577 try:
577 try:
578 n = mf[abs]
578 n = mf[abs]
579 except (hg.RepoError, KeyError):
579 except (hg.RepoError, KeyError):
580 try:
580 try:
581 n = r.lookup(rev)
581 n = r.lookup(rev)
582 except KeyError, inst:
582 except KeyError, inst:
583 raise util.Abort('cannot find file %s in rev %s', rel, rev)
583 raise util.Abort('cannot find file %s in rev %s', rel, rev)
584 else:
584 else:
585 n = r.tip()
585 n = r.tip()
586 fp = make_file(repo, r, opts['output'], node=n, pathname=abs)
586 fp = make_file(repo, r, opts['output'], node=n, pathname=abs)
587 fp.write(r.read(n))
587 fp.write(r.read(n))
588
588
589 def clone(ui, source, dest=None, **opts):
589 def clone(ui, source, dest=None, **opts):
590 """make a copy of an existing repository"""
590 """make a copy of an existing repository"""
591 if dest is None:
591 if dest is None:
592 dest = os.path.basename(os.path.normpath(source))
592 dest = os.path.basename(os.path.normpath(source))
593
593
594 if os.path.exists(dest):
594 if os.path.exists(dest):
595 raise util.Abort("destination '%s' already exists", dest)
595 raise util.Abort("destination '%s' already exists", dest)
596
596
597 dest = os.path.realpath(dest)
597 dest = os.path.realpath(dest)
598
598
599 class Dircleanup:
599 class Dircleanup:
600 def __init__(self, dir_):
600 def __init__(self, dir_):
601 self.rmtree = shutil.rmtree
601 self.rmtree = shutil.rmtree
602 self.dir_ = dir_
602 self.dir_ = dir_
603 os.mkdir(dir_)
603 os.mkdir(dir_)
604 def close(self):
604 def close(self):
605 self.dir_ = None
605 self.dir_ = None
606 def __del__(self):
606 def __del__(self):
607 if self.dir_:
607 if self.dir_:
608 self.rmtree(self.dir_, True)
608 self.rmtree(self.dir_, True)
609
609
610 if opts['ssh']:
610 if opts['ssh']:
611 ui.setconfig("ui", "ssh", opts['ssh'])
611 ui.setconfig("ui", "ssh", opts['ssh'])
612 if opts['remotecmd']:
612 if opts['remotecmd']:
613 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
613 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
614
614
615 if not os.path.exists(source):
615 if not os.path.exists(source):
616 source = ui.expandpath(source)
616 source = ui.expandpath(source)
617
617
618 d = Dircleanup(dest)
618 d = Dircleanup(dest)
619 abspath = source
619 abspath = source
620 other = hg.repository(ui, source)
620 other = hg.repository(ui, source)
621
621
622 copy = False
622 copy = False
623 if other.dev() != -1:
623 if other.dev() != -1:
624 abspath = os.path.abspath(source)
624 abspath = os.path.abspath(source)
625 if not opts['pull']:
625 if not opts['pull']:
626 copy = True
626 copy = True
627
627
628 if copy:
628 if copy:
629 try:
629 try:
630 # we use a lock here because if we race with commit, we
630 # we use a lock here because if we race with commit, we
631 # can end up with extra data in the cloned revlogs that's
631 # can end up with extra data in the cloned revlogs that's
632 # not pointed to by changesets, thus causing verify to
632 # not pointed to by changesets, thus causing verify to
633 # fail
633 # fail
634 l1 = lock.lock(os.path.join(source, ".hg", "lock"))
634 l1 = lock.lock(os.path.join(source, ".hg", "lock"))
635 except OSError:
635 except OSError:
636 copy = False
636 copy = False
637
637
638 if copy:
638 if copy:
639 # we lock here to avoid premature writing to the target
639 # we lock here to avoid premature writing to the target
640 os.mkdir(os.path.join(dest, ".hg"))
640 os.mkdir(os.path.join(dest, ".hg"))
641 l2 = lock.lock(os.path.join(dest, ".hg", "lock"))
641 l2 = lock.lock(os.path.join(dest, ".hg", "lock"))
642
642
643 files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
643 files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
644 for f in files.split():
644 for f in files.split():
645 src = os.path.join(source, ".hg", f)
645 src = os.path.join(source, ".hg", f)
646 dst = os.path.join(dest, ".hg", f)
646 dst = os.path.join(dest, ".hg", f)
647 util.copyfiles(src, dst)
647 util.copyfiles(src, dst)
648
648
649 repo = hg.repository(ui, dest)
649 repo = hg.repository(ui, dest)
650
650
651 else:
651 else:
652 repo = hg.repository(ui, dest, create=1)
652 repo = hg.repository(ui, dest, create=1)
653 repo.pull(other)
653 repo.pull(other)
654
654
655 f = repo.opener("hgrc", "w")
655 f = repo.opener("hgrc", "w")
656 f.write("[paths]\n")
656 f.write("[paths]\n")
657 f.write("default = %s\n" % abspath)
657 f.write("default = %s\n" % abspath)
658
658
659 if not opts['noupdate']:
659 if not opts['noupdate']:
660 update(ui, repo)
660 update(ui, repo)
661
661
662 d.close()
662 d.close()
663
663
664 def commit(ui, repo, *pats, **opts):
664 def commit(ui, repo, *pats, **opts):
665 """commit the specified files or all outstanding changes"""
665 """commit the specified files or all outstanding changes"""
666 if opts['text']:
666 if opts['text']:
667 ui.warn("Warning: -t and --text is deprecated,"
667 ui.warn("Warning: -t and --text is deprecated,"
668 " please use -m or --message instead.\n")
668 " please use -m or --message instead.\n")
669 message = opts['message'] or opts['text']
669 message = opts['message'] or opts['text']
670 logfile = opts['logfile']
670 logfile = opts['logfile']
671
671
672 if message and logfile:
672 if message and logfile:
673 raise util.Abort('options --message and --logfile are mutually '
673 raise util.Abort('options --message and --logfile are mutually '
674 'exclusive')
674 'exclusive')
675 if not message and logfile:
675 if not message and logfile:
676 try:
676 try:
677 if logfile == '-':
677 if logfile == '-':
678 message = sys.stdin.read()
678 message = sys.stdin.read()
679 else:
679 else:
680 message = open(logfile).read()
680 message = open(logfile).read()
681 except IOError, inst:
681 except IOError, inst:
682 raise util.Abort("can't read commit message '%s': %s" %
682 raise util.Abort("can't read commit message '%s': %s" %
683 (logfile, inst.strerror))
683 (logfile, inst.strerror))
684
684
685 if opts['addremove']:
685 if opts['addremove']:
686 addremove(ui, repo, *pats, **opts)
686 addremove(ui, repo, *pats, **opts)
687 cwd = repo.getcwd()
687 cwd = repo.getcwd()
688 if not pats and cwd:
688 if not pats and cwd:
689 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
689 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
690 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
690 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
691 fns, match, anypats = matchpats(repo, (pats and repo.getcwd()) or '',
691 fns, match, anypats = matchpats(repo, (pats and repo.getcwd()) or '',
692 pats, opts)
692 pats, opts)
693 if pats:
693 if pats:
694 c, a, d, u = repo.changes(files=fns, match=match)
694 c, a, d, u = repo.changes(files=fns, match=match)
695 files = c + a + [fn for fn in d if repo.dirstate.state(fn) == 'r']
695 files = c + a + [fn for fn in d if repo.dirstate.state(fn) == 'r']
696 else:
696 else:
697 files = []
697 files = []
698 try:
698 try:
699 repo.commit(files, message, opts['user'], opts['date'], match)
699 repo.commit(files, message, opts['user'], opts['date'], match)
700 except ValueError, inst:
700 except ValueError, inst:
701 raise util.Abort(str(inst))
701 raise util.Abort(str(inst))
702
702
703 def docopy(ui, repo, pats, opts):
703 def docopy(ui, repo, pats, opts):
704 if not pats:
704 if not pats:
705 raise util.Abort('no source or destination specified')
705 raise util.Abort('no source or destination specified')
706 elif len(pats) == 1:
706 elif len(pats) == 1:
707 raise util.Abort('no destination specified')
707 raise util.Abort('no destination specified')
708 pats = list(pats)
708 pats = list(pats)
709 dest = pats.pop()
709 dest = pats.pop()
710 sources = []
710 sources = []
711
711
712 def okaytocopy(abs, rel, exact):
712 def okaytocopy(abs, rel, exact):
713 reasons = {'?': 'is not managed',
713 reasons = {'?': 'is not managed',
714 'a': 'has been marked for add'}
714 'a': 'has been marked for add'}
715 reason = reasons.get(repo.dirstate.state(abs))
715 reason = reasons.get(repo.dirstate.state(abs))
716 if reason:
716 if reason:
717 if exact: ui.warn('%s: not copying - file %s\n' % (rel, reason))
717 if exact: ui.warn('%s: not copying - file %s\n' % (rel, reason))
718 else:
718 else:
719 return True
719 return True
720
720
721 for src, abs, rel, exact in walk(repo, pats, opts):
721 for src, abs, rel, exact in walk(repo, pats, opts):
722 if okaytocopy(abs, rel, exact):
722 if okaytocopy(abs, rel, exact):
723 sources.append((abs, rel, exact))
723 sources.append((abs, rel, exact))
724 if not sources:
724 if not sources:
725 raise util.Abort('no files to copy')
725 raise util.Abort('no files to copy')
726
726
727 cwd = repo.getcwd()
727 cwd = repo.getcwd()
728 absdest = util.canonpath(repo.root, cwd, dest)
728 absdest = util.canonpath(repo.root, cwd, dest)
729 reldest = util.pathto(cwd, absdest)
729 reldest = util.pathto(cwd, absdest)
730 if os.path.exists(reldest):
730 if os.path.exists(reldest):
731 destisfile = not os.path.isdir(reldest)
731 destisfile = not os.path.isdir(reldest)
732 else:
732 else:
733 destisfile = len(sources) == 1 or repo.dirstate.state(absdest) != '?'
733 destisfile = len(sources) == 1 or repo.dirstate.state(absdest) != '?'
734
734
735 if destisfile:
735 if destisfile:
736 if opts['parents']:
736 if opts['parents']:
737 raise util.Abort('with --parents, destination must be a directory')
737 raise util.Abort('with --parents, destination must be a directory')
738 elif len(sources) > 1:
738 elif len(sources) > 1:
739 raise util.Abort('with multiple sources, destination must be a '
739 raise util.Abort('with multiple sources, destination must be a '
740 'directory')
740 'directory')
741 errs, copied = 0, []
741 errs, copied = 0, []
742 for abs, rel, exact in sources:
742 for abs, rel, exact in sources:
743 if opts['parents']:
743 if opts['parents']:
744 mydest = os.path.join(dest, rel)
744 mydest = os.path.join(dest, rel)
745 elif destisfile:
745 elif destisfile:
746 mydest = reldest
746 mydest = reldest
747 else:
747 else:
748 mydest = os.path.join(dest, os.path.basename(rel))
748 mydest = os.path.join(dest, os.path.basename(rel))
749 myabsdest = util.canonpath(repo.root, cwd, mydest)
749 myabsdest = util.canonpath(repo.root, cwd, mydest)
750 myreldest = util.pathto(cwd, myabsdest)
750 myreldest = util.pathto(cwd, myabsdest)
751 if not opts['force'] and repo.dirstate.state(myabsdest) not in 'a?':
751 if not opts['force'] and repo.dirstate.state(myabsdest) not in 'a?':
752 ui.warn('%s: not overwriting - file already managed\n' % myreldest)
752 ui.warn('%s: not overwriting - file already managed\n' % myreldest)
753 continue
753 continue
754 mydestdir = os.path.dirname(myreldest) or '.'
754 mydestdir = os.path.dirname(myreldest) or '.'
755 if not opts['after']:
755 if not opts['after']:
756 try:
756 try:
757 if opts['parents']: os.makedirs(mydestdir)
757 if opts['parents']: os.makedirs(mydestdir)
758 elif not destisfile: os.mkdir(mydestdir)
758 elif not destisfile: os.mkdir(mydestdir)
759 except OSError, inst:
759 except OSError, inst:
760 if inst.errno != errno.EEXIST: raise
760 if inst.errno != errno.EEXIST: raise
761 if ui.verbose or not exact:
761 if ui.verbose or not exact:
762 ui.status('copying %s to %s\n' % (rel, myreldest))
762 ui.status('copying %s to %s\n' % (rel, myreldest))
763 if not opts['after']:
763 if not opts['after']:
764 try:
764 try:
765 shutil.copyfile(rel, myreldest)
765 shutil.copyfile(rel, myreldest)
766 n = repo.manifest.tip()
766 n = repo.manifest.tip()
767 mf = repo.manifest.readflags(n)
767 mf = repo.manifest.readflags(n)
768 util.set_exec(myreldest, util.is_exec(rel, mf[abs]))
768 util.set_exec(myreldest, util.is_exec(rel, mf[abs]))
769 except shutil.Error, inst:
769 except shutil.Error, inst:
770 raise util.Abort(str(inst))
770 raise util.Abort(str(inst))
771 except IOError, inst:
771 except IOError, inst:
772 if inst.errno == errno.ENOENT:
772 if inst.errno == errno.ENOENT:
773 ui.warn('%s: deleted in working copy\n' % rel)
773 ui.warn('%s: deleted in working copy\n' % rel)
774 else:
774 else:
775 ui.warn('%s: cannot copy - %s\n' % (rel, inst.strerror))
775 ui.warn('%s: cannot copy - %s\n' % (rel, inst.strerror))
776 errs += 1
776 errs += 1
777 continue
777 continue
778 repo.copy(abs, myabsdest)
778 repo.copy(abs, myabsdest)
779 copied.append((abs, rel, exact))
779 copied.append((abs, rel, exact))
780 if errs:
780 if errs:
781 ui.warn('(consider using --after)\n')
781 ui.warn('(consider using --after)\n')
782 return errs, copied
782 return errs, copied
783
783
784 def copy(ui, repo, *pats, **opts):
784 def copy(ui, repo, *pats, **opts):
785 """mark files as copied for the next commit"""
785 """mark files as copied for the next commit"""
786 errs, copied = docopy(ui, repo, pats, opts)
786 errs, copied = docopy(ui, repo, pats, opts)
787 return errs
787 return errs
788
788
789 def debugancestor(ui, index, rev1, rev2):
789 def debugancestor(ui, index, rev1, rev2):
790 """find the ancestor revision of two revisions in a given index"""
790 """find the ancestor revision of two revisions in a given index"""
791 r = revlog.revlog(file, index, "")
791 r = revlog.revlog(file, index, "")
792 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
792 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
793 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
793 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
794
794
795 def debugcheckstate(ui, repo):
795 def debugcheckstate(ui, repo):
796 """validate the correctness of the current dirstate"""
796 """validate the correctness of the current dirstate"""
797 parent1, parent2 = repo.dirstate.parents()
797 parent1, parent2 = repo.dirstate.parents()
798 repo.dirstate.read()
798 repo.dirstate.read()
799 dc = repo.dirstate.map
799 dc = repo.dirstate.map
800 keys = dc.keys()
800 keys = dc.keys()
801 keys.sort()
801 keys.sort()
802 m1n = repo.changelog.read(parent1)[0]
802 m1n = repo.changelog.read(parent1)[0]
803 m2n = repo.changelog.read(parent2)[0]
803 m2n = repo.changelog.read(parent2)[0]
804 m1 = repo.manifest.read(m1n)
804 m1 = repo.manifest.read(m1n)
805 m2 = repo.manifest.read(m2n)
805 m2 = repo.manifest.read(m2n)
806 errors = 0
806 errors = 0
807 for f in dc:
807 for f in dc:
808 state = repo.dirstate.state(f)
808 state = repo.dirstate.state(f)
809 if state in "nr" and f not in m1:
809 if state in "nr" and f not in m1:
810 ui.warn("%s in state %s, but not in manifest1\n" % (f, state))
810 ui.warn("%s in state %s, but not in manifest1\n" % (f, state))
811 errors += 1
811 errors += 1
812 if state in "a" and f in m1:
812 if state in "a" and f in m1:
813 ui.warn("%s in state %s, but also in manifest1\n" % (f, state))
813 ui.warn("%s in state %s, but also in manifest1\n" % (f, state))
814 errors += 1
814 errors += 1
815 if state in "m" and f not in m1 and f not in m2:
815 if state in "m" and f not in m1 and f not in m2:
816 ui.warn("%s in state %s, but not in either manifest\n" %
816 ui.warn("%s in state %s, but not in either manifest\n" %
817 (f, state))
817 (f, state))
818 errors += 1
818 errors += 1
819 for f in m1:
819 for f in m1:
820 state = repo.dirstate.state(f)
820 state = repo.dirstate.state(f)
821 if state not in "nrm":
821 if state not in "nrm":
822 ui.warn("%s in manifest1, but listed as state %s" % (f, state))
822 ui.warn("%s in manifest1, but listed as state %s" % (f, state))
823 errors += 1
823 errors += 1
824 if errors:
824 if errors:
825 raise util.Abort(".hg/dirstate inconsistent with current parent's manifest")
825 raise util.Abort(".hg/dirstate inconsistent with current parent's manifest")
826
826
827 def debugconfig(ui):
827 def debugconfig(ui):
828 """show combined config settings from all hgrc files"""
828 """show combined config settings from all hgrc files"""
829 try:
829 try:
830 repo = hg.repository(ui)
830 repo = hg.repository(ui)
831 except hg.RepoError:
831 except hg.RepoError:
832 pass
832 pass
833 for section, name, value in ui.walkconfig():
833 for section, name, value in ui.walkconfig():
834 ui.write('%s.%s=%s\n' % (section, name, value))
834 ui.write('%s.%s=%s\n' % (section, name, value))
835
835
836 def debugstate(ui, repo):
836 def debugstate(ui, repo):
837 """show the contents of the current dirstate"""
837 """show the contents of the current dirstate"""
838 repo.dirstate.read()
838 repo.dirstate.read()
839 dc = repo.dirstate.map
839 dc = repo.dirstate.map
840 keys = dc.keys()
840 keys = dc.keys()
841 keys.sort()
841 keys.sort()
842 for file_ in keys:
842 for file_ in keys:
843 ui.write("%c %3o %10d %s %s\n"
843 ui.write("%c %3o %10d %s %s\n"
844 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
844 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
845 time.strftime("%x %X",
845 time.strftime("%x %X",
846 time.localtime(dc[file_][3])), file_))
846 time.localtime(dc[file_][3])), file_))
847 for f in repo.dirstate.copies:
847 for f in repo.dirstate.copies:
848 ui.write("copy: %s -> %s\n" % (repo.dirstate.copies[f], f))
848 ui.write("copy: %s -> %s\n" % (repo.dirstate.copies[f], f))
849
849
850 def debugdata(ui, file_, rev):
850 def debugdata(ui, file_, rev):
851 """dump the contents of an data file revision"""
851 """dump the contents of an data file revision"""
852 r = revlog.revlog(file, file_[:-2] + ".i", file_)
852 r = revlog.revlog(file, file_[:-2] + ".i", file_)
853 try:
853 try:
854 ui.write(r.revision(r.lookup(rev)))
854 ui.write(r.revision(r.lookup(rev)))
855 except KeyError:
855 except KeyError:
856 raise util.Abort('invalid revision identifier %s', rev)
856 raise util.Abort('invalid revision identifier %s', rev)
857
857
858 def debugindex(ui, file_):
858 def debugindex(ui, file_):
859 """dump the contents of an index file"""
859 """dump the contents of an index file"""
860 r = revlog.revlog(file, file_, "")
860 r = revlog.revlog(file, file_, "")
861 ui.write(" rev offset length base linkrev" +
861 ui.write(" rev offset length base linkrev" +
862 " nodeid p1 p2\n")
862 " nodeid p1 p2\n")
863 for i in range(r.count()):
863 for i in range(r.count()):
864 e = r.index[i]
864 e = r.index[i]
865 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
865 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
866 i, e[0], e[1], e[2], e[3],
866 i, e[0], e[1], e[2], e[3],
867 short(e[6]), short(e[4]), short(e[5])))
867 short(e[6]), short(e[4]), short(e[5])))
868
868
869 def debugindexdot(ui, file_):
869 def debugindexdot(ui, file_):
870 """dump an index DAG as a .dot file"""
870 """dump an index DAG as a .dot file"""
871 r = revlog.revlog(file, file_, "")
871 r = revlog.revlog(file, file_, "")
872 ui.write("digraph G {\n")
872 ui.write("digraph G {\n")
873 for i in range(r.count()):
873 for i in range(r.count()):
874 e = r.index[i]
874 e = r.index[i]
875 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
875 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
876 if e[5] != nullid:
876 if e[5] != nullid:
877 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
877 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
878 ui.write("}\n")
878 ui.write("}\n")
879
879
880 def debugrename(ui, repo, file, rev=None):
880 def debugrename(ui, repo, file, rev=None):
881 """dump rename information"""
881 """dump rename information"""
882 r = repo.file(relpath(repo, [file])[0])
882 r = repo.file(relpath(repo, [file])[0])
883 if rev:
883 if rev:
884 try:
884 try:
885 # assume all revision numbers are for changesets
885 # assume all revision numbers are for changesets
886 n = repo.lookup(rev)
886 n = repo.lookup(rev)
887 change = repo.changelog.read(n)
887 change = repo.changelog.read(n)
888 m = repo.manifest.read(change[0])
888 m = repo.manifest.read(change[0])
889 n = m[relpath(repo, [file])[0]]
889 n = m[relpath(repo, [file])[0]]
890 except hg.RepoError, KeyError:
890 except hg.RepoError, KeyError:
891 n = r.lookup(rev)
891 n = r.lookup(rev)
892 else:
892 else:
893 n = r.tip()
893 n = r.tip()
894 m = r.renamed(n)
894 m = r.renamed(n)
895 if m:
895 if m:
896 ui.write("renamed from %s:%s\n" % (m[0], hex(m[1])))
896 ui.write("renamed from %s:%s\n" % (m[0], hex(m[1])))
897 else:
897 else:
898 ui.write("not renamed\n")
898 ui.write("not renamed\n")
899
899
900 def debugwalk(ui, repo, *pats, **opts):
900 def debugwalk(ui, repo, *pats, **opts):
901 """show how files match on given patterns"""
901 """show how files match on given patterns"""
902 items = list(walk(repo, pats, opts))
902 items = list(walk(repo, pats, opts))
903 if not items:
903 if not items:
904 return
904 return
905 fmt = '%%s %%-%ds %%-%ds %%s' % (
905 fmt = '%%s %%-%ds %%-%ds %%s' % (
906 max([len(abs) for (src, abs, rel, exact) in items]),
906 max([len(abs) for (src, abs, rel, exact) in items]),
907 max([len(rel) for (src, abs, rel, exact) in items]))
907 max([len(rel) for (src, abs, rel, exact) in items]))
908 for src, abs, rel, exact in items:
908 for src, abs, rel, exact in items:
909 line = fmt % (src, abs, rel, exact and 'exact' or '')
909 line = fmt % (src, abs, rel, exact and 'exact' or '')
910 ui.write("%s\n" % line.rstrip())
910 ui.write("%s\n" % line.rstrip())
911
911
912 def diff(ui, repo, *pats, **opts):
912 def diff(ui, repo, *pats, **opts):
913 """diff working directory (or selected files)"""
913 """diff working directory (or selected files)"""
914 node1, node2 = None, None
914 node1, node2 = None, None
915 revs = [repo.lookup(x) for x in opts['rev']]
915 revs = [repo.lookup(x) for x in opts['rev']]
916
916
917 if len(revs) > 0:
917 if len(revs) > 0:
918 node1 = revs[0]
918 node1 = revs[0]
919 if len(revs) > 1:
919 if len(revs) > 1:
920 node2 = revs[1]
920 node2 = revs[1]
921 if len(revs) > 2:
921 if len(revs) > 2:
922 raise util.Abort("too many revisions to diff")
922 raise util.Abort("too many revisions to diff")
923
923
924 fns, matchfn, anypats = matchpats(repo, repo.getcwd(), pats, opts)
924 fns, matchfn, anypats = matchpats(repo, repo.getcwd(), pats, opts)
925
925
926 dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn,
926 dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn,
927 text=opts['text'])
927 text=opts['text'])
928
928
929 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
929 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
930 node = repo.lookup(changeset)
930 node = repo.lookup(changeset)
931 prev, other = repo.changelog.parents(node)
931 prev, other = repo.changelog.parents(node)
932 change = repo.changelog.read(node)
932 change = repo.changelog.read(node)
933
933
934 fp = make_file(repo, repo.changelog, opts['output'],
934 fp = make_file(repo, repo.changelog, opts['output'],
935 node=node, total=total, seqno=seqno,
935 node=node, total=total, seqno=seqno,
936 revwidth=revwidth)
936 revwidth=revwidth)
937 if fp != sys.stdout:
937 if fp != sys.stdout:
938 ui.note("%s\n" % fp.name)
938 ui.note("%s\n" % fp.name)
939
939
940 fp.write("# HG changeset patch\n")
940 fp.write("# HG changeset patch\n")
941 fp.write("# User %s\n" % change[1])
941 fp.write("# User %s\n" % change[1])
942 fp.write("# Node ID %s\n" % hex(node))
942 fp.write("# Node ID %s\n" % hex(node))
943 fp.write("# Parent %s\n" % hex(prev))
943 fp.write("# Parent %s\n" % hex(prev))
944 if other != nullid:
944 if other != nullid:
945 fp.write("# Parent %s\n" % hex(other))
945 fp.write("# Parent %s\n" % hex(other))
946 fp.write(change[4].rstrip())
946 fp.write(change[4].rstrip())
947 fp.write("\n\n")
947 fp.write("\n\n")
948
948
949 dodiff(fp, ui, repo, prev, node, text=opts['text'])
949 dodiff(fp, ui, repo, prev, node, text=opts['text'])
950 if fp != sys.stdout:
950 if fp != sys.stdout:
951 fp.close()
951 fp.close()
952
952
953 def export(ui, repo, *changesets, **opts):
953 def export(ui, repo, *changesets, **opts):
954 """dump the header and diffs for one or more changesets"""
954 """dump the header and diffs for one or more changesets"""
955 if not changesets:
955 if not changesets:
956 raise util.Abort("export requires at least one changeset")
956 raise util.Abort("export requires at least one changeset")
957 seqno = 0
957 seqno = 0
958 revs = list(revrange(ui, repo, changesets))
958 revs = list(revrange(ui, repo, changesets))
959 total = len(revs)
959 total = len(revs)
960 revwidth = max(map(len, revs))
960 revwidth = max(map(len, revs))
961 ui.note(len(revs) > 1 and "Exporting patches:\n" or "Exporting patch:\n")
961 ui.note(len(revs) > 1 and "Exporting patches:\n" or "Exporting patch:\n")
962 for cset in revs:
962 for cset in revs:
963 seqno += 1
963 seqno += 1
964 doexport(ui, repo, cset, seqno, total, revwidth, opts)
964 doexport(ui, repo, cset, seqno, total, revwidth, opts)
965
965
966 def forget(ui, repo, *pats, **opts):
966 def forget(ui, repo, *pats, **opts):
967 """don't add the specified files on the next commit"""
967 """don't add the specified files on the next commit"""
968 forget = []
968 forget = []
969 for src, abs, rel, exact in walk(repo, pats, opts):
969 for src, abs, rel, exact in walk(repo, pats, opts):
970 if repo.dirstate.state(abs) == 'a':
970 if repo.dirstate.state(abs) == 'a':
971 forget.append(abs)
971 forget.append(abs)
972 if ui.verbose or not exact:
972 if ui.verbose or not exact:
973 ui.status('forgetting ', rel, '\n')
973 ui.status('forgetting ', rel, '\n')
974 repo.forget(forget)
974 repo.forget(forget)
975
975
976 def grep(ui, repo, pattern, *pats, **opts):
976 def grep(ui, repo, pattern, *pats, **opts):
977 """search for a pattern in specified files and revisions"""
977 """search for a pattern in specified files and revisions"""
978 reflags = 0
978 reflags = 0
979 if opts['ignore_case']:
979 if opts['ignore_case']:
980 reflags |= re.I
980 reflags |= re.I
981 regexp = re.compile(pattern, reflags)
981 regexp = re.compile(pattern, reflags)
982 sep, eol = ':', '\n'
982 sep, eol = ':', '\n'
983 if opts['print0']:
983 if opts['print0']:
984 sep = eol = '\0'
984 sep = eol = '\0'
985
985
986 fcache = {}
986 fcache = {}
987 def getfile(fn):
987 def getfile(fn):
988 if fn not in fcache:
988 if fn not in fcache:
989 fcache[fn] = repo.file(fn)
989 fcache[fn] = repo.file(fn)
990 return fcache[fn]
990 return fcache[fn]
991
991
992 def matchlines(body):
992 def matchlines(body):
993 begin = 0
993 begin = 0
994 linenum = 0
994 linenum = 0
995 while True:
995 while True:
996 match = regexp.search(body, begin)
996 match = regexp.search(body, begin)
997 if not match:
997 if not match:
998 break
998 break
999 mstart, mend = match.span()
999 mstart, mend = match.span()
1000 linenum += body.count('\n', begin, mstart) + 1
1000 linenum += body.count('\n', begin, mstart) + 1
1001 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1001 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1002 lend = body.find('\n', mend)
1002 lend = body.find('\n', mend)
1003 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1003 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1004 begin = lend + 1
1004 begin = lend + 1
1005
1005
1006 class linestate:
1006 class linestate:
1007 def __init__(self, line, linenum, colstart, colend):
1007 def __init__(self, line, linenum, colstart, colend):
1008 self.line = line
1008 self.line = line
1009 self.linenum = linenum
1009 self.linenum = linenum
1010 self.colstart = colstart
1010 self.colstart = colstart
1011 self.colend = colend
1011 self.colend = colend
1012 def __eq__(self, other):
1012 def __eq__(self, other):
1013 return self.line == other.line
1013 return self.line == other.line
1014 def __hash__(self):
1014 def __hash__(self):
1015 return hash(self.line)
1015 return hash(self.line)
1016
1016
1017 matches = {}
1017 matches = {}
1018 def grepbody(fn, rev, body):
1018 def grepbody(fn, rev, body):
1019 matches[rev].setdefault(fn, {})
1019 matches[rev].setdefault(fn, {})
1020 m = matches[rev][fn]
1020 m = matches[rev][fn]
1021 for lnum, cstart, cend, line in matchlines(body):
1021 for lnum, cstart, cend, line in matchlines(body):
1022 s = linestate(line, lnum, cstart, cend)
1022 s = linestate(line, lnum, cstart, cend)
1023 m[s] = s
1023 m[s] = s
1024
1024
1025 prev = {}
1025 prev = {}
1026 ucache = {}
1026 ucache = {}
1027 def display(fn, rev, states, prevstates):
1027 def display(fn, rev, states, prevstates):
1028 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
1028 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
1029 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
1029 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
1030 counts = {'-': 0, '+': 0}
1030 counts = {'-': 0, '+': 0}
1031 filerevmatches = {}
1031 filerevmatches = {}
1032 for l in diff:
1032 for l in diff:
1033 if incrementing or not opts['all']:
1033 if incrementing or not opts['all']:
1034 change = ((l in prevstates) and '-') or '+'
1034 change = ((l in prevstates) and '-') or '+'
1035 r = rev
1035 r = rev
1036 else:
1036 else:
1037 change = ((l in states) and '-') or '+'
1037 change = ((l in states) and '-') or '+'
1038 r = prev[fn]
1038 r = prev[fn]
1039 cols = [fn, str(rev)]
1039 cols = [fn, str(rev)]
1040 if opts['line_number']: cols.append(str(l.linenum))
1040 if opts['line_number']: cols.append(str(l.linenum))
1041 if opts['all']: cols.append(change)
1041 if opts['all']: cols.append(change)
1042 if opts['user']: cols.append(trimuser(ui, getchange(rev)[1], rev,
1042 if opts['user']: cols.append(trimuser(ui, getchange(rev)[1], rev,
1043 ucache))
1043 ucache))
1044 if opts['files_with_matches']:
1044 if opts['files_with_matches']:
1045 c = (fn, rev)
1045 c = (fn, rev)
1046 if c in filerevmatches: continue
1046 if c in filerevmatches: continue
1047 filerevmatches[c] = 1
1047 filerevmatches[c] = 1
1048 else:
1048 else:
1049 cols.append(l.line)
1049 cols.append(l.line)
1050 ui.write(sep.join(cols), eol)
1050 ui.write(sep.join(cols), eol)
1051 counts[change] += 1
1051 counts[change] += 1
1052 return counts['+'], counts['-']
1052 return counts['+'], counts['-']
1053
1053
1054 fstate = {}
1054 fstate = {}
1055 skip = {}
1055 skip = {}
1056 changeiter, getchange = walkchangerevs(ui, repo, repo.getcwd(), pats, opts)
1056 changeiter, getchange = walkchangerevs(ui, repo, repo.getcwd(), pats, opts)
1057 count = 0
1057 count = 0
1058 for st, rev, fns in changeiter:
1058 for st, rev, fns in changeiter:
1059 if st == 'window':
1059 if st == 'window':
1060 incrementing = rev
1060 incrementing = rev
1061 matches.clear()
1061 matches.clear()
1062 elif st == 'add':
1062 elif st == 'add':
1063 change = repo.changelog.read(repo.lookup(str(rev)))
1063 change = repo.changelog.read(repo.lookup(str(rev)))
1064 mf = repo.manifest.read(change[0])
1064 mf = repo.manifest.read(change[0])
1065 matches[rev] = {}
1065 matches[rev] = {}
1066 for fn in fns:
1066 for fn in fns:
1067 if fn in skip: continue
1067 if fn in skip: continue
1068 fstate.setdefault(fn, {})
1068 fstate.setdefault(fn, {})
1069 try:
1069 try:
1070 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1070 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1071 except KeyError:
1071 except KeyError:
1072 pass
1072 pass
1073 elif st == 'iter':
1073 elif st == 'iter':
1074 states = matches[rev].items()
1074 states = matches[rev].items()
1075 states.sort()
1075 states.sort()
1076 for fn, m in states:
1076 for fn, m in states:
1077 if fn in skip: continue
1077 if fn in skip: continue
1078 if incrementing or not opts['all'] or fstate[fn]:
1078 if incrementing or not opts['all'] or fstate[fn]:
1079 pos, neg = display(fn, rev, m, fstate[fn])
1079 pos, neg = display(fn, rev, m, fstate[fn])
1080 count += pos + neg
1080 count += pos + neg
1081 if pos and not opts['all']:
1081 if pos and not opts['all']:
1082 skip[fn] = True
1082 skip[fn] = True
1083 fstate[fn] = m
1083 fstate[fn] = m
1084 prev[fn] = rev
1084 prev[fn] = rev
1085
1085
1086 if not incrementing:
1086 if not incrementing:
1087 fstate = fstate.items()
1087 fstate = fstate.items()
1088 fstate.sort()
1088 fstate.sort()
1089 for fn, state in fstate:
1089 for fn, state in fstate:
1090 if fn in skip: continue
1090 if fn in skip: continue
1091 display(fn, rev, {}, state)
1091 display(fn, rev, {}, state)
1092 return (count == 0 and 1) or 0
1092 return (count == 0 and 1) or 0
1093
1093
1094 def heads(ui, repo, **opts):
1094 def heads(ui, repo, **opts):
1095 """show current repository heads"""
1095 """show current repository heads"""
1096 heads = repo.changelog.heads()
1096 heads = repo.changelog.heads()
1097 br = None
1097 br = None
1098 if opts['branches']:
1098 if opts['branches']:
1099 br = repo.branchlookup(heads)
1099 br = repo.branchlookup(heads)
1100 for n in repo.changelog.heads():
1100 for n in repo.changelog.heads():
1101 show_changeset(ui, repo, changenode=n, brinfo=br)
1101 show_changeset(ui, repo, changenode=n, brinfo=br)
1102
1102
1103 def identify(ui, repo):
1103 def identify(ui, repo):
1104 """print information about the working copy"""
1104 """print information about the working copy"""
1105 parents = [p for p in repo.dirstate.parents() if p != nullid]
1105 parents = [p for p in repo.dirstate.parents() if p != nullid]
1106 if not parents:
1106 if not parents:
1107 ui.write("unknown\n")
1107 ui.write("unknown\n")
1108 return
1108 return
1109
1109
1110 hexfunc = ui.verbose and hex or short
1110 hexfunc = ui.verbose and hex or short
1111 (c, a, d, u) = repo.changes()
1111 (c, a, d, u) = repo.changes()
1112 output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
1112 output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
1113 (c or a or d) and "+" or "")]
1113 (c or a or d) and "+" or "")]
1114
1114
1115 if not ui.quiet:
1115 if not ui.quiet:
1116 # multiple tags for a single parent separated by '/'
1116 # multiple tags for a single parent separated by '/'
1117 parenttags = ['/'.join(tags)
1117 parenttags = ['/'.join(tags)
1118 for tags in map(repo.nodetags, parents) if tags]
1118 for tags in map(repo.nodetags, parents) if tags]
1119 # tags for multiple parents separated by ' + '
1119 # tags for multiple parents separated by ' + '
1120 if parenttags:
1120 if parenttags:
1121 output.append(' + '.join(parenttags))
1121 output.append(' + '.join(parenttags))
1122
1122
1123 ui.write("%s\n" % ' '.join(output))
1123 ui.write("%s\n" % ' '.join(output))
1124
1124
1125 def import_(ui, repo, patch1, *patches, **opts):
1125 def import_(ui, repo, patch1, *patches, **opts):
1126 """import an ordered set of patches"""
1126 """import an ordered set of patches"""
1127 patches = (patch1,) + patches
1127 patches = (patch1,) + patches
1128
1128
1129 if not opts['force']:
1129 if not opts['force']:
1130 (c, a, d, u) = repo.changes()
1130 (c, a, d, u) = repo.changes()
1131 if c or a or d:
1131 if c or a or d:
1132 raise util.Abort("outstanding uncommitted changes")
1132 raise util.Abort("outstanding uncommitted changes")
1133
1133
1134 d = opts["base"]
1134 d = opts["base"]
1135 strip = opts["strip"]
1135 strip = opts["strip"]
1136
1136
1137 mailre = re.compile(r'(?:From |[\w-]+:)')
1137 mailre = re.compile(r'(?:From |[\w-]+:)')
1138 diffre = re.compile(r'(?:diff -|--- .*\s+\w+ \w+ +\d+ \d+:\d+:\d+ \d+)')
1138 diffre = re.compile(r'(?:diff -|--- .*\s+\w+ \w+ +\d+ \d+:\d+:\d+ \d+)')
1139
1139
1140 for patch in patches:
1140 for patch in patches:
1141 ui.status("applying %s\n" % patch)
1141 ui.status("applying %s\n" % patch)
1142 pf = os.path.join(d, patch)
1142 pf = os.path.join(d, patch)
1143
1143
1144 message = []
1144 message = []
1145 user = None
1145 user = None
1146 hgpatch = False
1146 hgpatch = False
1147 for line in file(pf):
1147 for line in file(pf):
1148 line = line.rstrip()
1148 line = line.rstrip()
1149 if (not message and not hgpatch and
1149 if (not message and not hgpatch and
1150 mailre.match(line) and not opts['force']):
1150 mailre.match(line) and not opts['force']):
1151 if len(line) > 35: line = line[:32] + '...'
1151 if len(line) > 35: line = line[:32] + '...'
1152 raise util.Abort('first line looks like a '
1152 raise util.Abort('first line looks like a '
1153 'mail header: ' + line)
1153 'mail header: ' + line)
1154 if diffre.match(line):
1154 if diffre.match(line):
1155 break
1155 break
1156 elif hgpatch:
1156 elif hgpatch:
1157 # parse values when importing the result of an hg export
1157 # parse values when importing the result of an hg export
1158 if line.startswith("# User "):
1158 if line.startswith("# User "):
1159 user = line[7:]
1159 user = line[7:]
1160 ui.debug('User: %s\n' % user)
1160 ui.debug('User: %s\n' % user)
1161 elif not line.startswith("# ") and line:
1161 elif not line.startswith("# ") and line:
1162 message.append(line)
1162 message.append(line)
1163 hgpatch = False
1163 hgpatch = False
1164 elif line == '# HG changeset patch':
1164 elif line == '# HG changeset patch':
1165 hgpatch = True
1165 hgpatch = True
1166 message = [] # We may have collected garbage
1166 message = [] # We may have collected garbage
1167 else:
1167 else:
1168 message.append(line)
1168 message.append(line)
1169
1169
1170 # make sure message isn't empty
1170 # make sure message isn't empty
1171 if not message:
1171 if not message:
1172 message = "imported patch %s\n" % patch
1172 message = "imported patch %s\n" % patch
1173 else:
1173 else:
1174 message = "%s\n" % '\n'.join(message)
1174 message = "%s\n" % '\n'.join(message)
1175 ui.debug('message:\n%s\n' % message)
1175 ui.debug('message:\n%s\n' % message)
1176
1176
1177 files = util.patch(strip, pf, ui)
1177 files = util.patch(strip, pf, ui)
1178
1178
1179 if len(files) > 0:
1179 if len(files) > 0:
1180 addremove(ui, repo, *files)
1180 addremove(ui, repo, *files)
1181 repo.commit(files, message, user)
1181 repo.commit(files, message, user)
1182
1182
1183 def incoming(ui, repo, source="default", **opts):
1183 def incoming(ui, repo, source="default", **opts):
1184 """show new changesets found in source"""
1184 """show new changesets found in source"""
1185 source = ui.expandpath(source)
1185 source = ui.expandpath(source)
1186 other = hg.repository(ui, source)
1186 other = hg.repository(ui, source)
1187 if not other.local():
1187 if not other.local():
1188 raise util.Abort("incoming doesn't work for remote repositories yet")
1188 raise util.Abort("incoming doesn't work for remote repositories yet")
1189 o = repo.findincoming(other)
1189 o = repo.findincoming(other)
1190 if not o:
1190 if not o:
1191 return
1191 return
1192 o = other.newer(o)
1192 o = other.newer(o)
1193 for n in o:
1193 for n in o:
1194 show_changeset(ui, other, changenode=n)
1194 show_changeset(ui, other, changenode=n)
1195 if opts['patch']:
1195 if opts['patch']:
1196 prev = other.changelog.parents(n)[0]
1196 prev = other.changelog.parents(n)[0]
1197 dodiff(ui, ui, other, prev, n)
1197 dodiff(ui, ui, other, prev, n)
1198 ui.write("\n")
1198 ui.write("\n")
1199
1199
1200 def init(ui, dest="."):
1200 def init(ui, dest="."):
1201 """create a new repository in the given directory"""
1201 """create a new repository in the given directory"""
1202 if not os.path.exists(dest):
1202 if not os.path.exists(dest):
1203 os.mkdir(dest)
1203 os.mkdir(dest)
1204 hg.repository(ui, dest, create=1)
1204 hg.repository(ui, dest, create=1)
1205
1205
1206 def locate(ui, repo, *pats, **opts):
1206 def locate(ui, repo, *pats, **opts):
1207 """locate files matching specific patterns"""
1207 """locate files matching specific patterns"""
1208 end = opts['print0'] and '\0' or '\n'
1208 end = opts['print0'] and '\0' or '\n'
1209
1209
1210 for src, abs, rel, exact in walk(repo, pats, opts, '(?:.*/|)'):
1210 for src, abs, rel, exact in walk(repo, pats, opts, '(?:.*/|)'):
1211 if repo.dirstate.state(abs) == '?':
1211 if repo.dirstate.state(abs) == '?':
1212 continue
1212 continue
1213 if opts['fullpath']:
1213 if opts['fullpath']:
1214 ui.write(os.path.join(repo.root, abs), end)
1214 ui.write(os.path.join(repo.root, abs), end)
1215 else:
1215 else:
1216 ui.write(rel, end)
1216 ui.write(rel, end)
1217
1217
1218 def log(ui, repo, *pats, **opts):
1218 def log(ui, repo, *pats, **opts):
1219 """show revision history of entire repository or files"""
1219 """show revision history of entire repository or files"""
1220 class dui:
1220 class dui:
1221 # Implement and delegate some ui protocol. Save hunks of
1221 # Implement and delegate some ui protocol. Save hunks of
1222 # output for later display in the desired order.
1222 # output for later display in the desired order.
1223 def __init__(self, ui):
1223 def __init__(self, ui):
1224 self.ui = ui
1224 self.ui = ui
1225 self.hunk = {}
1225 self.hunk = {}
1226 def bump(self, rev):
1226 def bump(self, rev):
1227 self.rev = rev
1227 self.rev = rev
1228 self.hunk[rev] = []
1228 self.hunk[rev] = []
1229 def note(self, *args):
1229 def note(self, *args):
1230 if self.verbose:
1230 if self.verbose:
1231 self.write(*args)
1231 self.write(*args)
1232 def status(self, *args):
1232 def status(self, *args):
1233 if not self.quiet:
1233 if not self.quiet:
1234 self.write(*args)
1234 self.write(*args)
1235 def write(self, *args):
1235 def write(self, *args):
1236 self.hunk[self.rev].append(args)
1236 self.hunk[self.rev].append(args)
1237 def __getattr__(self, key):
1237 def __getattr__(self, key):
1238 return getattr(self.ui, key)
1238 return getattr(self.ui, key)
1239 cwd = repo.getcwd()
1239 cwd = repo.getcwd()
1240 if not pats and cwd:
1240 if not pats and cwd:
1241 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
1241 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
1242 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
1242 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
1243 changeiter, getchange = walkchangerevs(ui, repo, (pats and cwd) or '',
1243 changeiter, getchange = walkchangerevs(ui, repo, (pats and cwd) or '',
1244 pats, opts)
1244 pats, opts)
1245 for st, rev, fns in changeiter:
1245 for st, rev, fns in changeiter:
1246 if st == 'window':
1246 if st == 'window':
1247 du = dui(ui)
1247 du = dui(ui)
1248 elif st == 'add':
1248 elif st == 'add':
1249 du.bump(rev)
1249 du.bump(rev)
1250 br = None
1250 br = None
1251 if opts['branch']:
1251 if opts['branch']:
1252 br = repo.branchlookup([repo.changelog.node(rev)])
1252 br = repo.branchlookup([repo.changelog.node(rev)])
1253 show_changeset(du, repo, rev, brinfo=br)
1253 show_changeset(du, repo, rev, brinfo=br)
1254 if opts['patch']:
1254 if opts['patch']:
1255 changenode = repo.changelog.node(rev)
1255 changenode = repo.changelog.node(rev)
1256 prev, other = repo.changelog.parents(changenode)
1256 prev, other = repo.changelog.parents(changenode)
1257 dodiff(du, du, repo, prev, changenode, fns)
1257 dodiff(du, du, repo, prev, changenode, fns)
1258 du.write("\n\n")
1258 du.write("\n\n")
1259 elif st == 'iter':
1259 elif st == 'iter':
1260 for args in du.hunk[rev]:
1260 for args in du.hunk[rev]:
1261 ui.write(*args)
1261 ui.write(*args)
1262
1262
1263 def manifest(ui, repo, rev=None):
1263 def manifest(ui, repo, rev=None):
1264 """output the latest or given revision of the project manifest"""
1264 """output the latest or given revision of the project manifest"""
1265 if rev:
1265 if rev:
1266 try:
1266 try:
1267 # assume all revision numbers are for changesets
1267 # assume all revision numbers are for changesets
1268 n = repo.lookup(rev)
1268 n = repo.lookup(rev)
1269 change = repo.changelog.read(n)
1269 change = repo.changelog.read(n)
1270 n = change[0]
1270 n = change[0]
1271 except hg.RepoError:
1271 except hg.RepoError:
1272 n = repo.manifest.lookup(rev)
1272 n = repo.manifest.lookup(rev)
1273 else:
1273 else:
1274 n = repo.manifest.tip()
1274 n = repo.manifest.tip()
1275 m = repo.manifest.read(n)
1275 m = repo.manifest.read(n)
1276 mf = repo.manifest.readflags(n)
1276 mf = repo.manifest.readflags(n)
1277 files = m.keys()
1277 files = m.keys()
1278 files.sort()
1278 files.sort()
1279
1279
1280 for f in files:
1280 for f in files:
1281 ui.write("%40s %3s %s\n" % (hex(m[f]), mf[f] and "755" or "644", f))
1281 ui.write("%40s %3s %s\n" % (hex(m[f]), mf[f] and "755" or "644", f))
1282
1282
1283 def outgoing(ui, repo, dest="default-push", **opts):
1283 def outgoing(ui, repo, dest="default-push", **opts):
1284 """show changesets not found in destination"""
1284 """show changesets not found in destination"""
1285 dest = ui.expandpath(dest)
1285 dest = ui.expandpath(dest)
1286 other = hg.repository(ui, dest)
1286 other = hg.repository(ui, dest)
1287 o = repo.findoutgoing(other)
1287 o = repo.findoutgoing(other)
1288 o = repo.newer(o)
1288 o = repo.newer(o)
1289 for n in o:
1289 for n in o:
1290 show_changeset(ui, repo, changenode=n)
1290 show_changeset(ui, repo, changenode=n)
1291 if opts['patch']:
1291 if opts['patch']:
1292 prev = repo.changelog.parents(n)[0]
1292 prev = repo.changelog.parents(n)[0]
1293 dodiff(ui, ui, repo, prev, n)
1293 dodiff(ui, ui, repo, prev, n)
1294 ui.write("\n")
1294 ui.write("\n")
1295
1295
1296 def parents(ui, repo, rev=None):
1296 def parents(ui, repo, rev=None):
1297 """show the parents of the working dir or revision"""
1297 """show the parents of the working dir or revision"""
1298 if rev:
1298 if rev:
1299 p = repo.changelog.parents(repo.lookup(rev))
1299 p = repo.changelog.parents(repo.lookup(rev))
1300 else:
1300 else:
1301 p = repo.dirstate.parents()
1301 p = repo.dirstate.parents()
1302
1302
1303 for n in p:
1303 for n in p:
1304 if n != nullid:
1304 if n != nullid:
1305 show_changeset(ui, repo, changenode=n)
1305 show_changeset(ui, repo, changenode=n)
1306
1306
1307 def paths(ui, search=None):
1307 def paths(ui, search=None):
1308 """show definition of symbolic path names"""
1308 """show definition of symbolic path names"""
1309 try:
1309 try:
1310 repo = hg.repository(ui=ui)
1310 repo = hg.repository(ui=ui)
1311 except hg.RepoError:
1311 except hg.RepoError:
1312 pass
1312 pass
1313
1313
1314 if search:
1314 if search:
1315 for name, path in ui.configitems("paths"):
1315 for name, path in ui.configitems("paths"):
1316 if name == search:
1316 if name == search:
1317 ui.write("%s\n" % path)
1317 ui.write("%s\n" % path)
1318 return
1318 return
1319 ui.warn("not found!\n")
1319 ui.warn("not found!\n")
1320 return 1
1320 return 1
1321 else:
1321 else:
1322 for name, path in ui.configitems("paths"):
1322 for name, path in ui.configitems("paths"):
1323 ui.write("%s = %s\n" % (name, path))
1323 ui.write("%s = %s\n" % (name, path))
1324
1324
1325 def pull(ui, repo, source="default", **opts):
1325 def pull(ui, repo, source="default", **opts):
1326 """pull changes from the specified source"""
1326 """pull changes from the specified source"""
1327 source = ui.expandpath(source)
1327 source = ui.expandpath(source)
1328 ui.status('pulling from %s\n' % (source))
1328 ui.status('pulling from %s\n' % (source))
1329
1329
1330 if opts['ssh']:
1330 if opts['ssh']:
1331 ui.setconfig("ui", "ssh", opts['ssh'])
1331 ui.setconfig("ui", "ssh", opts['ssh'])
1332 if opts['remotecmd']:
1332 if opts['remotecmd']:
1333 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1333 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1334
1334
1335 other = hg.repository(ui, source)
1335 other = hg.repository(ui, source)
1336 r = repo.pull(other)
1336 r = repo.pull(other)
1337 if not r:
1337 if not r:
1338 if opts['update']:
1338 if opts['update']:
1339 return update(ui, repo)
1339 return update(ui, repo)
1340 else:
1340 else:
1341 ui.status("(run 'hg update' to get a working copy)\n")
1341 ui.status("(run 'hg update' to get a working copy)\n")
1342
1342
1343 return r
1343 return r
1344
1344
1345 def push(ui, repo, dest="default-push", force=False, ssh=None, remotecmd=None):
1345 def push(ui, repo, dest="default-push", force=False, ssh=None, remotecmd=None):
1346 """push changes to the specified destination"""
1346 """push changes to the specified destination"""
1347 dest = ui.expandpath(dest)
1347 dest = ui.expandpath(dest)
1348 ui.status('pushing to %s\n' % (dest))
1348 ui.status('pushing to %s\n' % (dest))
1349
1349
1350 if ssh:
1350 if ssh:
1351 ui.setconfig("ui", "ssh", ssh)
1351 ui.setconfig("ui", "ssh", ssh)
1352 if remotecmd:
1352 if remotecmd:
1353 ui.setconfig("ui", "remotecmd", remotecmd)
1353 ui.setconfig("ui", "remotecmd", remotecmd)
1354
1354
1355 other = hg.repository(ui, dest)
1355 other = hg.repository(ui, dest)
1356 r = repo.push(other, force)
1356 r = repo.push(other, force)
1357 return r
1357 return r
1358
1358
1359 def rawcommit(ui, repo, *flist, **rc):
1359 def rawcommit(ui, repo, *flist, **rc):
1360 "raw commit interface"
1360 "raw commit interface"
1361 if rc['text']:
1361 if rc['text']:
1362 ui.warn("Warning: -t and --text is deprecated,"
1362 ui.warn("Warning: -t and --text is deprecated,"
1363 " please use -m or --message instead.\n")
1363 " please use -m or --message instead.\n")
1364 message = rc['message'] or rc['text']
1364 message = rc['message'] or rc['text']
1365 if not message and rc['logfile']:
1365 if not message and rc['logfile']:
1366 try:
1366 try:
1367 message = open(rc['logfile']).read()
1367 message = open(rc['logfile']).read()
1368 except IOError:
1368 except IOError:
1369 pass
1369 pass
1370 if not message and not rc['logfile']:
1370 if not message and not rc['logfile']:
1371 raise util.Abort("missing commit message")
1371 raise util.Abort("missing commit message")
1372
1372
1373 files = relpath(repo, list(flist))
1373 files = relpath(repo, list(flist))
1374 if rc['files']:
1374 if rc['files']:
1375 files += open(rc['files']).read().splitlines()
1375 files += open(rc['files']).read().splitlines()
1376
1376
1377 rc['parent'] = map(repo.lookup, rc['parent'])
1377 rc['parent'] = map(repo.lookup, rc['parent'])
1378
1378
1379 try:
1379 try:
1380 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
1380 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
1381 except ValueError, inst:
1381 except ValueError, inst:
1382 raise util.Abort(str(inst))
1382 raise util.Abort(str(inst))
1383
1383
1384 def recover(ui, repo):
1384 def recover(ui, repo):
1385 """roll back an interrupted transaction"""
1385 """roll back an interrupted transaction"""
1386 repo.recover()
1386 repo.recover()
1387
1387
1388 def remove(ui, repo, pat, *pats, **opts):
1388 def remove(ui, repo, pat, *pats, **opts):
1389 """remove the specified files on the next commit"""
1389 """remove the specified files on the next commit"""
1390 names = []
1390 names = []
1391 def okaytoremove(abs, rel, exact):
1391 def okaytoremove(abs, rel, exact):
1392 c, a, d, u = repo.changes(files = [abs])
1392 c, a, d, u = repo.changes(files = [abs])
1393 reason = None
1393 reason = None
1394 if c: reason = 'is modified'
1394 if c: reason = 'is modified'
1395 elif a: reason = 'has been marked for add'
1395 elif a: reason = 'has been marked for add'
1396 elif u: reason = 'is not managed'
1396 elif u: reason = 'is not managed'
1397 if reason:
1397 if reason:
1398 if exact: ui.warn('not removing %s: file %s\n' % (rel, reason))
1398 if exact: ui.warn('not removing %s: file %s\n' % (rel, reason))
1399 else:
1399 else:
1400 return True
1400 return True
1401 for src, abs, rel, exact in walk(repo, (pat,) + pats, opts):
1401 for src, abs, rel, exact in walk(repo, (pat,) + pats, opts):
1402 if okaytoremove(abs, rel, exact):
1402 if okaytoremove(abs, rel, exact):
1403 if ui.verbose or not exact: ui.status('removing %s\n' % rel)
1403 if ui.verbose or not exact: ui.status('removing %s\n' % rel)
1404 names.append(abs)
1404 names.append(abs)
1405 for name in names:
1405 for name in names:
1406 try:
1406 try:
1407 os.unlink(name)
1407 os.unlink(name)
1408 except OSError, inst:
1408 except OSError, inst:
1409 if inst.errno != errno.ENOENT: raise
1409 if inst.errno != errno.ENOENT: raise
1410 repo.remove(names)
1410 repo.remove(names)
1411
1411
1412 def rename(ui, repo, *pats, **opts):
1412 def rename(ui, repo, *pats, **opts):
1413 """rename files; equivalent of copy + remove"""
1413 """rename files; equivalent of copy + remove"""
1414 errs, copied = docopy(ui, repo, pats, opts)
1414 errs, copied = docopy(ui, repo, pats, opts)
1415 names = []
1415 names = []
1416 for abs, rel, exact in copied:
1416 for abs, rel, exact in copied:
1417 if ui.verbose or not exact: ui.status('removing %s\n' % rel)
1417 if ui.verbose or not exact: ui.status('removing %s\n' % rel)
1418 try:
1418 try:
1419 os.unlink(rel)
1419 os.unlink(rel)
1420 except OSError, inst:
1420 except OSError, inst:
1421 if inst.errno != errno.ENOENT: raise
1421 if inst.errno != errno.ENOENT: raise
1422 names.append(abs)
1422 names.append(abs)
1423 repo.remove(names)
1423 repo.remove(names)
1424 return errs
1424 return errs
1425
1425
1426 def revert(ui, repo, *names, **opts):
1426 def revert(ui, repo, *names, **opts):
1427 """revert modified files or dirs back to their unmodified states"""
1427 """revert modified files or dirs back to their unmodified states"""
1428 node = opts['rev'] and repo.lookup(opts['rev']) or \
1428 node = opts['rev'] and repo.lookup(opts['rev']) or \
1429 repo.dirstate.parents()[0]
1429 repo.dirstate.parents()[0]
1430 root = os.path.realpath(repo.root)
1430 root = os.path.realpath(repo.root)
1431
1431
1432 def trimpath(p):
1432 def trimpath(p):
1433 p = os.path.realpath(p)
1433 p = os.path.realpath(p)
1434 if p.startswith(root):
1434 if p.startswith(root):
1435 rest = p[len(root):]
1435 rest = p[len(root):]
1436 if not rest:
1436 if not rest:
1437 return rest
1437 return rest
1438 if p.startswith(os.sep):
1438 if p.startswith(os.sep):
1439 return rest[1:]
1439 return rest[1:]
1440 return p
1440 return p
1441
1441
1442 relnames = map(trimpath, names or [os.getcwd()])
1442 relnames = map(trimpath, names or [os.getcwd()])
1443 chosen = {}
1443 chosen = {}
1444
1444
1445 def choose(name):
1445 def choose(name):
1446 def body(name):
1446 def body(name):
1447 for r in relnames:
1447 for r in relnames:
1448 if not name.startswith(r):
1448 if not name.startswith(r):
1449 continue
1449 continue
1450 rest = name[len(r):]
1450 rest = name[len(r):]
1451 if not rest:
1451 if not rest:
1452 return r, True
1452 return r, True
1453 depth = rest.count(os.sep)
1453 depth = rest.count(os.sep)
1454 if not r:
1454 if not r:
1455 if depth == 0 or not opts['nonrecursive']:
1455 if depth == 0 or not opts['nonrecursive']:
1456 return r, True
1456 return r, True
1457 elif rest[0] == os.sep:
1457 elif rest[0] == os.sep:
1458 if depth == 1 or not opts['nonrecursive']:
1458 if depth == 1 or not opts['nonrecursive']:
1459 return r, True
1459 return r, True
1460 return None, False
1460 return None, False
1461 relname, ret = body(name)
1461 relname, ret = body(name)
1462 if ret:
1462 if ret:
1463 chosen[relname] = 1
1463 chosen[relname] = 1
1464 return ret
1464 return ret
1465
1465
1466 r = repo.update(node, False, True, choose, False)
1466 r = repo.update(node, False, True, choose, False)
1467 for n in relnames:
1467 for n in relnames:
1468 if n not in chosen:
1468 if n not in chosen:
1469 ui.warn('error: no matches for %s\n' % n)
1469 ui.warn('error: no matches for %s\n' % n)
1470 r = 1
1470 r = 1
1471 sys.stdout.flush()
1471 sys.stdout.flush()
1472 return r
1472 return r
1473
1473
1474 def root(ui, repo):
1474 def root(ui, repo):
1475 """print the root (top) of the current working dir"""
1475 """print the root (top) of the current working dir"""
1476 ui.write(repo.root + "\n")
1476 ui.write(repo.root + "\n")
1477
1477
1478 def serve(ui, repo, **opts):
1478 def serve(ui, repo, **opts):
1479 """export the repository via HTTP"""
1479 """export the repository via HTTP"""
1480
1480
1481 if opts["stdio"]:
1481 if opts["stdio"]:
1482 fin, fout = sys.stdin, sys.stdout
1482 fin, fout = sys.stdin, sys.stdout
1483 sys.stdout = sys.stderr
1483 sys.stdout = sys.stderr
1484
1484
1485 def getarg():
1485 def getarg():
1486 argline = fin.readline()[:-1]
1486 argline = fin.readline()[:-1]
1487 arg, l = argline.split()
1487 arg, l = argline.split()
1488 val = fin.read(int(l))
1488 val = fin.read(int(l))
1489 return arg, val
1489 return arg, val
1490 def respond(v):
1490 def respond(v):
1491 fout.write("%d\n" % len(v))
1491 fout.write("%d\n" % len(v))
1492 fout.write(v)
1492 fout.write(v)
1493 fout.flush()
1493 fout.flush()
1494
1494
1495 lock = None
1495 lock = None
1496
1496
1497 while 1:
1497 while 1:
1498 cmd = fin.readline()[:-1]
1498 cmd = fin.readline()[:-1]
1499 if cmd == '':
1499 if cmd == '':
1500 return
1500 return
1501 if cmd == "heads":
1501 if cmd == "heads":
1502 h = repo.heads()
1502 h = repo.heads()
1503 respond(" ".join(map(hex, h)) + "\n")
1503 respond(" ".join(map(hex, h)) + "\n")
1504 if cmd == "lock":
1504 if cmd == "lock":
1505 lock = repo.lock()
1505 lock = repo.lock()
1506 respond("")
1506 respond("")
1507 if cmd == "unlock":
1507 if cmd == "unlock":
1508 if lock:
1508 if lock:
1509 lock.release()
1509 lock.release()
1510 lock = None
1510 lock = None
1511 respond("")
1511 respond("")
1512 elif cmd == "branches":
1512 elif cmd == "branches":
1513 arg, nodes = getarg()
1513 arg, nodes = getarg()
1514 nodes = map(bin, nodes.split(" "))
1514 nodes = map(bin, nodes.split(" "))
1515 r = []
1515 r = []
1516 for b in repo.branches(nodes):
1516 for b in repo.branches(nodes):
1517 r.append(" ".join(map(hex, b)) + "\n")
1517 r.append(" ".join(map(hex, b)) + "\n")
1518 respond("".join(r))
1518 respond("".join(r))
1519 elif cmd == "between":
1519 elif cmd == "between":
1520 arg, pairs = getarg()
1520 arg, pairs = getarg()
1521 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
1521 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
1522 r = []
1522 r = []
1523 for b in repo.between(pairs):
1523 for b in repo.between(pairs):
1524 r.append(" ".join(map(hex, b)) + "\n")
1524 r.append(" ".join(map(hex, b)) + "\n")
1525 respond("".join(r))
1525 respond("".join(r))
1526 elif cmd == "changegroup":
1526 elif cmd == "changegroup":
1527 nodes = []
1527 nodes = []
1528 arg, roots = getarg()
1528 arg, roots = getarg()
1529 nodes = map(bin, roots.split(" "))
1529 nodes = map(bin, roots.split(" "))
1530
1530
1531 cg = repo.changegroup(nodes)
1531 cg = repo.changegroup(nodes)
1532 while 1:
1532 while 1:
1533 d = cg.read(4096)
1533 d = cg.read(4096)
1534 if not d:
1534 if not d:
1535 break
1535 break
1536 fout.write(d)
1536 fout.write(d)
1537
1537
1538 fout.flush()
1538 fout.flush()
1539
1539
1540 elif cmd == "addchangegroup":
1540 elif cmd == "addchangegroup":
1541 if not lock:
1541 if not lock:
1542 respond("not locked")
1542 respond("not locked")
1543 continue
1543 continue
1544 respond("")
1544 respond("")
1545
1545
1546 r = repo.addchangegroup(fin)
1546 r = repo.addchangegroup(fin)
1547 respond("")
1547 respond("")
1548
1548
1549 optlist = "name templates style address port ipv6 accesslog errorlog"
1549 optlist = "name templates style address port ipv6 accesslog errorlog"
1550 for o in optlist.split():
1550 for o in optlist.split():
1551 if opts[o]:
1551 if opts[o]:
1552 ui.setconfig("web", o, opts[o])
1552 ui.setconfig("web", o, opts[o])
1553
1553
1554 try:
1554 try:
1555 httpd = hgweb.create_server(repo)
1555 httpd = hgweb.create_server(repo)
1556 except socket.error, inst:
1556 except socket.error, inst:
1557 raise util.Abort('cannot start server: ' + inst.args[1])
1557 raise util.Abort('cannot start server: ' + inst.args[1])
1558
1558
1559 if ui.verbose:
1559 if ui.verbose:
1560 addr, port = httpd.socket.getsockname()
1560 addr, port = httpd.socket.getsockname()
1561 if addr == '0.0.0.0':
1561 if addr == '0.0.0.0':
1562 addr = socket.gethostname()
1562 addr = socket.gethostname()
1563 else:
1563 else:
1564 try:
1564 try:
1565 addr = socket.gethostbyaddr(addr)[0]
1565 addr = socket.gethostbyaddr(addr)[0]
1566 except socket.error:
1566 except socket.error:
1567 pass
1567 pass
1568 if port != 80:
1568 if port != 80:
1569 ui.status('listening at http://%s:%d/\n' % (addr, port))
1569 ui.status('listening at http://%s:%d/\n' % (addr, port))
1570 else:
1570 else:
1571 ui.status('listening at http://%s/\n' % addr)
1571 ui.status('listening at http://%s/\n' % addr)
1572 httpd.serve_forever()
1572 httpd.serve_forever()
1573
1573
1574 def status(ui, repo, *pats, **opts):
1574 def status(ui, repo, *pats, **opts):
1575 '''show changed files in the working directory
1575 '''show changed files in the working directory
1576
1576
1577 M = modified
1577 M = modified
1578 A = added
1578 A = added
1579 R = removed
1579 R = removed
1580 ? = not tracked
1580 ? = not tracked
1581 '''
1581 '''
1582
1582
1583 cwd = repo.getcwd()
1583 cwd = repo.getcwd()
1584 files, matchfn, anypats = matchpats(repo, cwd, pats, opts)
1584 files, matchfn, anypats = matchpats(repo, cwd, pats, opts)
1585 (c, a, d, u) = [[util.pathto(cwd, x) for x in n]
1585 (c, a, d, u) = [[util.pathto(cwd, x) for x in n]
1586 for n in repo.changes(files=files, match=matchfn)]
1586 for n in repo.changes(files=files, match=matchfn)]
1587
1587
1588 changetypes = [('modified', 'M', c),
1588 changetypes = [('modified', 'M', c),
1589 ('added', 'A', a),
1589 ('added', 'A', a),
1590 ('removed', 'R', d),
1590 ('removed', 'R', d),
1591 ('unknown', '?', u)]
1591 ('unknown', '?', u)]
1592
1592
1593 end = opts['print0'] and '\0' or '\n'
1593 end = opts['print0'] and '\0' or '\n'
1594
1594
1595 for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]]
1595 for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]]
1596 or changetypes):
1596 or changetypes):
1597 if opts['no_status']:
1597 if opts['no_status']:
1598 format = "%%s%s" % end
1598 format = "%%s%s" % end
1599 else:
1599 else:
1600 format = "%s %%s%s" % (char, end);
1600 format = "%s %%s%s" % (char, end);
1601
1601
1602 for f in changes:
1602 for f in changes:
1603 ui.write(format % f)
1603 ui.write(format % f)
1604
1604
1605 def tag(ui, repo, name, rev=None, **opts):
1605 def tag(ui, repo, name, rev=None, **opts):
1606 """add a tag for the current tip or a given revision"""
1606 """add a tag for the current tip or a given revision"""
1607 if opts['text']:
1607 if opts['text']:
1608 ui.warn("Warning: -t and --text is deprecated,"
1608 ui.warn("Warning: -t and --text is deprecated,"
1609 " please use -m or --message instead.\n")
1609 " please use -m or --message instead.\n")
1610 if name == "tip":
1610 if name == "tip":
1611 raise util.Abort("the name 'tip' is reserved")
1611 raise util.Abort("the name 'tip' is reserved")
1612 if rev:
1612 if rev:
1613 r = hex(repo.lookup(rev))
1613 r = hex(repo.lookup(rev))
1614 else:
1614 else:
1615 r = hex(repo.changelog.tip())
1615 r = hex(repo.changelog.tip())
1616
1616
1617 if name.find(revrangesep) >= 0:
1617 if name.find(revrangesep) >= 0:
1618 raise util.Abort("'%s' cannot be used in a tag name" % revrangesep)
1618 raise util.Abort("'%s' cannot be used in a tag name" % revrangesep)
1619
1619
1620 if opts['local']:
1620 if opts['local']:
1621 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
1621 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
1622 return
1622 return
1623
1623
1624 (c, a, d, u) = repo.changes()
1624 (c, a, d, u) = repo.changes()
1625 for x in (c, a, d, u):
1625 for x in (c, a, d, u):
1626 if ".hgtags" in x:
1626 if ".hgtags" in x:
1627 raise util.Abort("working copy of .hgtags is changed "
1627 raise util.Abort("working copy of .hgtags is changed "
1628 "(please commit .hgtags manually)")
1628 "(please commit .hgtags manually)")
1629
1629
1630 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
1630 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
1631 if repo.dirstate.state(".hgtags") == '?':
1631 if repo.dirstate.state(".hgtags") == '?':
1632 repo.add([".hgtags"])
1632 repo.add([".hgtags"])
1633
1633
1634 message = (opts['message'] or opts['text'] or
1634 message = (opts['message'] or opts['text'] or
1635 "Added tag %s for changeset %s" % (name, r))
1635 "Added tag %s for changeset %s" % (name, r))
1636 try:
1636 try:
1637 repo.commit([".hgtags"], message, opts['user'], opts['date'])
1637 repo.commit([".hgtags"], message, opts['user'], opts['date'])
1638 except ValueError, inst:
1638 except ValueError, inst:
1639 raise util.Abort(str(inst))
1639 raise util.Abort(str(inst))
1640
1640
1641 def tags(ui, repo):
1641 def tags(ui, repo):
1642 """list repository tags"""
1642 """list repository tags"""
1643
1643
1644 l = repo.tagslist()
1644 l = repo.tagslist()
1645 l.reverse()
1645 l.reverse()
1646 for t, n in l:
1646 for t, n in l:
1647 try:
1647 try:
1648 r = "%5d:%s" % (repo.changelog.rev(n), hex(n))
1648 r = "%5d:%s" % (repo.changelog.rev(n), hex(n))
1649 except KeyError:
1649 except KeyError:
1650 r = " ?:?"
1650 r = " ?:?"
1651 ui.write("%-30s %s\n" % (t, r))
1651 ui.write("%-30s %s\n" % (t, r))
1652
1652
1653 def tip(ui, repo):
1653 def tip(ui, repo):
1654 """show the tip revision"""
1654 """show the tip revision"""
1655 n = repo.changelog.tip()
1655 n = repo.changelog.tip()
1656 show_changeset(ui, repo, changenode=n)
1656 show_changeset(ui, repo, changenode=n)
1657
1657
1658 def unbundle(ui, repo, fname):
1658 def unbundle(ui, repo, fname):
1659 """apply a changegroup file"""
1659 """apply a changegroup file"""
1660 f = urllib.urlopen(fname)
1660 f = urllib.urlopen(fname)
1661
1661
1662 if f.read(4) != "HG10":
1662 if f.read(4) != "HG10":
1663 raise util.Abort("%s: not a Mercurial bundle file" % fname)
1663 raise util.Abort("%s: not a Mercurial bundle file" % fname)
1664
1664
1665 class bzread:
1665 class bzread:
1666 def __init__(self, f):
1666 def __init__(self, f):
1667 self.zd = bz2.BZ2Decompressor()
1667 self.zd = bz2.BZ2Decompressor()
1668 self.f = f
1668 self.f = f
1669 self.buf = ""
1669 self.buf = ""
1670 def read(self, l):
1670 def read(self, l):
1671 while l > len(self.buf):
1671 while l > len(self.buf):
1672 r = self.f.read(4096)
1672 r = self.f.read(4096)
1673 if r:
1673 if r:
1674 self.buf += self.zd.decompress(r)
1674 self.buf += self.zd.decompress(r)
1675 else:
1675 else:
1676 break
1676 break
1677 d, self.buf = self.buf[:l], self.buf[l:]
1677 d, self.buf = self.buf[:l], self.buf[l:]
1678 return d
1678 return d
1679
1679
1680 repo.addchangegroup(bzread(f))
1680 repo.addchangegroup(bzread(f))
1681
1681
1682 def undo(ui, repo):
1682 def undo(ui, repo):
1683 """undo the last commit or pull
1683 """undo the last commit or pull
1684
1684
1685 Roll back the last pull or commit transaction on the
1685 Roll back the last pull or commit transaction on the
1686 repository, restoring the project to its earlier state.
1686 repository, restoring the project to its earlier state.
1687
1687
1688 This command should be used with care. There is only one level of
1688 This command should be used with care. There is only one level of
1689 undo and there is no redo.
1689 undo and there is no redo.
1690
1690
1691 This command is not intended for use on public repositories. Once
1691 This command is not intended for use on public repositories. Once
1692 a change is visible for pull by other users, undoing it locally is
1692 a change is visible for pull by other users, undoing it locally is
1693 ineffective.
1693 ineffective.
1694 """
1694 """
1695 repo.undo()
1695 repo.undo()
1696
1696
1697 def update(ui, repo, node=None, merge=False, clean=False, branch=None):
1697 def update(ui, repo, node=None, merge=False, clean=False, branch=None):
1698 '''update or merge working directory
1698 '''update or merge working directory
1699
1699
1700 If there are no outstanding changes in the working directory and
1700 If there are no outstanding changes in the working directory and
1701 there is a linear relationship between the current version and the
1701 there is a linear relationship between the current version and the
1702 requested version, the result is the requested version.
1702 requested version, the result is the requested version.
1703
1703
1704 Otherwise the result is a merge between the contents of the
1704 Otherwise the result is a merge between the contents of the
1705 current working directory and the requested version. Files that
1705 current working directory and the requested version. Files that
1706 changed between either parent are marked as changed for the next
1706 changed between either parent are marked as changed for the next
1707 commit and a commit must be performed before any further updates
1707 commit and a commit must be performed before any further updates
1708 are allowed.
1708 are allowed.
1709 '''
1709 '''
1710 if branch:
1710 if branch:
1711 br = repo.branchlookup(branch=branch)
1711 br = repo.branchlookup(branch=branch)
1712 found = []
1712 found = []
1713 for x in br:
1713 for x in br:
1714 if branch in br[x]:
1714 if branch in br[x]:
1715 found.append(x)
1715 found.append(x)
1716 if len(found) > 1:
1716 if len(found) > 1:
1717 ui.warn("Found multiple heads for %s\n" % branch)
1717 ui.warn("Found multiple heads for %s\n" % branch)
1718 for x in found:
1718 for x in found:
1719 show_changeset(ui, repo, changenode=x, brinfo=br)
1719 show_changeset(ui, repo, changenode=x, brinfo=br)
1720 return 1
1720 return 1
1721 if len(found) == 1:
1721 if len(found) == 1:
1722 node = found[0]
1722 node = found[0]
1723 ui.warn("Using head %s for branch %s\n" % (short(node), branch))
1723 ui.warn("Using head %s for branch %s\n" % (short(node), branch))
1724 else:
1724 else:
1725 ui.warn("branch %s not found\n" % (branch))
1725 ui.warn("branch %s not found\n" % (branch))
1726 return 1
1726 return 1
1727 else:
1727 else:
1728 node = node and repo.lookup(node) or repo.changelog.tip()
1728 node = node and repo.lookup(node) or repo.changelog.tip()
1729 return repo.update(node, allow=merge, force=clean)
1729 return repo.update(node, allow=merge, force=clean)
1730
1730
1731 def verify(ui, repo):
1731 def verify(ui, repo):
1732 """verify the integrity of the repository"""
1732 """verify the integrity of the repository"""
1733 return repo.verify()
1733 return repo.verify()
1734
1734
1735 # Command options and aliases are listed here, alphabetically
1735 # Command options and aliases are listed here, alphabetically
1736
1736
1737 table = {
1737 table = {
1738 "^add":
1738 "^add":
1739 (add,
1739 (add,
1740 [('I', 'include', [], 'include path in search'),
1740 [('I', 'include', [], 'include path in search'),
1741 ('X', 'exclude', [], 'exclude path from search')],
1741 ('X', 'exclude', [], 'exclude path from search')],
1742 "hg add [OPTION]... [FILE]..."),
1742 "hg add [OPTION]... [FILE]..."),
1743 "addremove":
1743 "addremove":
1744 (addremove,
1744 (addremove,
1745 [('I', 'include', [], 'include path in search'),
1745 [('I', 'include', [], 'include path in search'),
1746 ('X', 'exclude', [], 'exclude path from search')],
1746 ('X', 'exclude', [], 'exclude path from search')],
1747 "hg addremove [OPTION]... [FILE]..."),
1747 "hg addremove [OPTION]... [FILE]..."),
1748 "^annotate":
1748 "^annotate":
1749 (annotate,
1749 (annotate,
1750 [('r', 'rev', '', 'revision'),
1750 [('r', 'rev', '', 'revision'),
1751 ('a', 'text', None, 'treat all files as text'),
1751 ('a', 'text', None, 'treat all files as text'),
1752 ('u', 'user', None, 'show user'),
1752 ('u', 'user', None, 'show user'),
1753 ('n', 'number', None, 'show revision number'),
1753 ('n', 'number', None, 'show revision number'),
1754 ('c', 'changeset', None, 'show changeset'),
1754 ('c', 'changeset', None, 'show changeset'),
1755 ('I', 'include', [], 'include path in search'),
1755 ('I', 'include', [], 'include path in search'),
1756 ('X', 'exclude', [], 'exclude path from search')],
1756 ('X', 'exclude', [], 'exclude path from search')],
1757 'hg annotate [OPTION]... FILE...'),
1757 'hg annotate [OPTION]... FILE...'),
1758 "bundle":
1758 "bundle":
1759 (bundle,
1759 (bundle,
1760 [],
1760 [],
1761 'hg bundle FILE DEST'),
1761 'hg bundle FILE DEST'),
1762 "cat":
1762 "cat":
1763 (cat,
1763 (cat,
1764 [('I', 'include', [], 'include path in search'),
1764 [('I', 'include', [], 'include path in search'),
1765 ('X', 'exclude', [], 'exclude path from search'),
1765 ('X', 'exclude', [], 'exclude path from search'),
1766 ('o', 'output', "", 'output to file'),
1766 ('o', 'output', "", 'output to file'),
1767 ('r', 'rev', '', 'revision')],
1767 ('r', 'rev', '', 'revision')],
1768 'hg cat [OPTION]... FILE...'),
1768 'hg cat [OPTION]... FILE...'),
1769 "^clone":
1769 "^clone":
1770 (clone,
1770 (clone,
1771 [('U', 'noupdate', None, 'skip update after cloning'),
1771 [('U', 'noupdate', None, 'skip update after cloning'),
1772 ('e', 'ssh', "", 'ssh command'),
1772 ('e', 'ssh', "", 'ssh command'),
1773 ('', 'pull', None, 'use pull protocol to copy metadata'),
1773 ('', 'pull', None, 'use pull protocol to copy metadata'),
1774 ('', 'remotecmd', "", 'remote hg command')],
1774 ('', 'remotecmd', "", 'remote hg command')],
1775 'hg clone [OPTION]... SOURCE [DEST]'),
1775 'hg clone [OPTION]... SOURCE [DEST]'),
1776 "^commit|ci":
1776 "^commit|ci":
1777 (commit,
1777 (commit,
1778 [('A', 'addremove', None, 'run add/remove during commit'),
1778 [('A', 'addremove', None, 'run add/remove during commit'),
1779 ('I', 'include', [], 'include path in search'),
1779 ('I', 'include', [], 'include path in search'),
1780 ('X', 'exclude', [], 'exclude path from search'),
1780 ('X', 'exclude', [], 'exclude path from search'),
1781 ('m', 'message', "", 'commit message'),
1781 ('m', 'message', "", 'commit message'),
1782 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1782 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1783 ('l', 'logfile', "", 'commit message file'),
1783 ('l', 'logfile', "", 'commit message file'),
1784 ('d', 'date', "", 'date code'),
1784 ('d', 'date', "", 'date code'),
1785 ('u', 'user', "", 'user')],
1785 ('u', 'user', "", 'user')],
1786 'hg commit [OPTION]... [FILE]...'),
1786 'hg commit [OPTION]... [FILE]...'),
1787 "copy|cp": (copy,
1787 "copy|cp": (copy,
1788 [('I', 'include', [], 'include path in search'),
1788 [('I', 'include', [], 'include path in search'),
1789 ('X', 'exclude', [], 'exclude path from search'),
1789 ('X', 'exclude', [], 'exclude path from search'),
1790 ('A', 'after', None, 'record a copy after it has happened'),
1790 ('A', 'after', None, 'record a copy after it has happened'),
1791 ('f', 'force', None, 'replace destination if it exists'),
1791 ('f', 'force', None, 'replace destination if it exists'),
1792 ('p', 'parents', None, 'append source path to dest')],
1792 ('p', 'parents', None, 'append source path to dest')],
1793 'hg copy [OPTION]... [SOURCE]... DEST'),
1793 'hg copy [OPTION]... [SOURCE]... DEST'),
1794 "debugancestor": (debugancestor, [], 'debugancestor INDEX REV1 REV2'),
1794 "debugancestor": (debugancestor, [], 'debugancestor INDEX REV1 REV2'),
1795 "debugcheckstate": (debugcheckstate, [], 'debugcheckstate'),
1795 "debugcheckstate": (debugcheckstate, [], 'debugcheckstate'),
1796 "debugconfig": (debugconfig, [], 'debugconfig'),
1796 "debugconfig": (debugconfig, [], 'debugconfig'),
1797 "debugstate": (debugstate, [], 'debugstate'),
1797 "debugstate": (debugstate, [], 'debugstate'),
1798 "debugdata": (debugdata, [], 'debugdata FILE REV'),
1798 "debugdata": (debugdata, [], 'debugdata FILE REV'),
1799 "debugindex": (debugindex, [], 'debugindex FILE'),
1799 "debugindex": (debugindex, [], 'debugindex FILE'),
1800 "debugindexdot": (debugindexdot, [], 'debugindexdot FILE'),
1800 "debugindexdot": (debugindexdot, [], 'debugindexdot FILE'),
1801 "debugrename": (debugrename, [], 'debugrename FILE [REV]'),
1801 "debugrename": (debugrename, [], 'debugrename FILE [REV]'),
1802 "debugwalk":
1802 "debugwalk":
1803 (debugwalk,
1803 (debugwalk,
1804 [('I', 'include', [], 'include path in search'),
1804 [('I', 'include', [], 'include path in search'),
1805 ('X', 'exclude', [], 'exclude path from search')],
1805 ('X', 'exclude', [], 'exclude path from search')],
1806 'debugwalk [OPTION]... [FILE]...'),
1806 'debugwalk [OPTION]... [FILE]...'),
1807 "^diff":
1807 "^diff":
1808 (diff,
1808 (diff,
1809 [('r', 'rev', [], 'revision'),
1809 [('r', 'rev', [], 'revision'),
1810 ('a', 'text', None, 'treat all files as text'),
1810 ('a', 'text', None, 'treat all files as text'),
1811 ('I', 'include', [], 'include path in search'),
1811 ('I', 'include', [], 'include path in search'),
1812 ('X', 'exclude', [], 'exclude path from search')],
1812 ('X', 'exclude', [], 'exclude path from search')],
1813 'hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...'),
1813 'hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...'),
1814 "^export":
1814 "^export":
1815 (export,
1815 (export,
1816 [('o', 'output', "", 'output to file'),
1816 [('o', 'output', "", 'output to file'),
1817 ('a', 'text', None, 'treat all files as text')],
1817 ('a', 'text', None, 'treat all files as text')],
1818 "hg export [-a] [-o OUTFILE] REV..."),
1818 "hg export [-a] [-o OUTFILE] REV..."),
1819 "forget":
1819 "forget":
1820 (forget,
1820 (forget,
1821 [('I', 'include', [], 'include path in search'),
1821 [('I', 'include', [], 'include path in search'),
1822 ('X', 'exclude', [], 'exclude path from search')],
1822 ('X', 'exclude', [], 'exclude path from search')],
1823 "hg forget [OPTION]... FILE..."),
1823 "hg forget [OPTION]... FILE..."),
1824 "grep":
1824 "grep":
1825 (grep,
1825 (grep,
1826 [('0', 'print0', None, 'end fields with NUL'),
1826 [('0', 'print0', None, 'end fields with NUL'),
1827 ('I', 'include', [], 'include path in search'),
1827 ('I', 'include', [], 'include path in search'),
1828 ('X', 'exclude', [], 'include path in search'),
1828 ('X', 'exclude', [], 'include path in search'),
1829 ('', 'all', None, 'print all revisions with matches'),
1829 ('', 'all', None, 'print all revisions with matches'),
1830 ('i', 'ignore-case', None, 'ignore case when matching'),
1830 ('i', 'ignore-case', None, 'ignore case when matching'),
1831 ('l', 'files-with-matches', None, 'print names of files and revs with matches'),
1831 ('l', 'files-with-matches', None, 'print names of files and revs with matches'),
1832 ('n', 'line-number', None, 'print line numbers'),
1832 ('n', 'line-number', None, 'print line numbers'),
1833 ('r', 'rev', [], 'search in revision rev'),
1833 ('r', 'rev', [], 'search in revision rev'),
1834 ('u', 'user', None, 'print user who made change')],
1834 ('u', 'user', None, 'print user who made change')],
1835 "hg grep [OPTION]... PATTERN [FILE]..."),
1835 "hg grep [OPTION]... PATTERN [FILE]..."),
1836 "heads":
1836 "heads":
1837 (heads,
1837 (heads,
1838 [('b', 'branches', None, 'find branch info')],
1838 [('b', 'branches', None, 'find branch info')],
1839 'hg heads [-b]'),
1839 'hg heads [-b]'),
1840 "help": (help_, [], 'hg help [COMMAND]'),
1840 "help": (help_, [], 'hg help [COMMAND]'),
1841 "identify|id": (identify, [], 'hg identify'),
1841 "identify|id": (identify, [], 'hg identify'),
1842 "import|patch":
1842 "import|patch":
1843 (import_,
1843 (import_,
1844 [('p', 'strip', 1, 'path strip'),
1844 [('p', 'strip', 1, 'path strip'),
1845 ('f', 'force', None, 'skip check for outstanding changes'),
1845 ('f', 'force', None, 'skip check for outstanding changes'),
1846 ('b', 'base', "", 'base path')],
1846 ('b', 'base', "", 'base path')],
1847 "hg import [-f] [-p NUM] [-b BASE] PATCH..."),
1847 "hg import [-f] [-p NUM] [-b BASE] PATCH..."),
1848 "incoming|in": (incoming,
1848 "incoming|in": (incoming,
1849 [('p', 'patch', None, 'show patch')],
1849 [('p', 'patch', None, 'show patch')],
1850 'hg incoming [-p] [SOURCE]'),
1850 'hg incoming [-p] [SOURCE]'),
1851 "^init": (init, [], 'hg init [DEST]'),
1851 "^init": (init, [], 'hg init [DEST]'),
1852 "locate":
1852 "locate":
1853 (locate,
1853 (locate,
1854 [('r', 'rev', '', 'revision'),
1854 [('r', 'rev', '', 'revision'),
1855 ('0', 'print0', None, 'end filenames with NUL'),
1855 ('0', 'print0', None, 'end filenames with NUL'),
1856 ('f', 'fullpath', None, 'print complete paths'),
1856 ('f', 'fullpath', None, 'print complete paths'),
1857 ('I', 'include', [], 'include path in search'),
1857 ('I', 'include', [], 'include path in search'),
1858 ('X', 'exclude', [], 'exclude path from search')],
1858 ('X', 'exclude', [], 'exclude path from search')],
1859 'hg locate [OPTION]... [PATTERN]...'),
1859 'hg locate [OPTION]... [PATTERN]...'),
1860 "^log|history":
1860 "^log|history":
1861 (log,
1861 (log,
1862 [('I', 'include', [], 'include path in search'),
1862 [('I', 'include', [], 'include path in search'),
1863 ('X', 'exclude', [], 'exclude path from search'),
1863 ('X', 'exclude', [], 'exclude path from search'),
1864 ('b', 'branch', None, 'show branches'),
1864 ('b', 'branch', None, 'show branches'),
1865 ('r', 'rev', [], 'revision'),
1865 ('r', 'rev', [], 'revision'),
1866 ('p', 'patch', None, 'show patch')],
1866 ('p', 'patch', None, 'show patch')],
1867 'hg log [-I] [-X] [-r REV]... [-p] [FILE]'),
1867 'hg log [-I] [-X] [-r REV]... [-p] [FILE]'),
1868 "manifest": (manifest, [], 'hg manifest [REV]'),
1868 "manifest": (manifest, [], 'hg manifest [REV]'),
1869 "outgoing|out": (outgoing,
1869 "outgoing|out": (outgoing,
1870 [('p', 'patch', None, 'show patch')],
1870 [('p', 'patch', None, 'show patch')],
1871 'hg outgoing [-p] [DEST]'),
1871 'hg outgoing [-p] [DEST]'),
1872 "parents": (parents, [], 'hg parents [REV]'),
1872 "parents": (parents, [], 'hg parents [REV]'),
1873 "paths": (paths, [], 'hg paths [NAME]'),
1873 "paths": (paths, [], 'hg paths [NAME]'),
1874 "^pull":
1874 "^pull":
1875 (pull,
1875 (pull,
1876 [('u', 'update', None, 'update working directory'),
1876 [('u', 'update', None, 'update working directory'),
1877 ('e', 'ssh', "", 'ssh command'),
1877 ('e', 'ssh', "", 'ssh command'),
1878 ('', 'remotecmd', "", 'remote hg command')],
1878 ('', 'remotecmd', "", 'remote hg command')],
1879 'hg pull [-u] [-e FILE] [--remotecmd FILE] [SOURCE]'),
1879 'hg pull [-u] [-e FILE] [--remotecmd FILE] [SOURCE]'),
1880 "^push":
1880 "^push":
1881 (push,
1881 (push,
1882 [('f', 'force', None, 'force push'),
1882 [('f', 'force', None, 'force push'),
1883 ('e', 'ssh', "", 'ssh command'),
1883 ('e', 'ssh', "", 'ssh command'),
1884 ('', 'remotecmd', "", 'remote hg command')],
1884 ('', 'remotecmd', "", 'remote hg command')],
1885 'hg push [-f] [-e FILE] [--remotecmd FILE] [DEST]'),
1885 'hg push [-f] [-e FILE] [--remotecmd FILE] [DEST]'),
1886 "rawcommit":
1886 "rawcommit":
1887 (rawcommit,
1887 (rawcommit,
1888 [('p', 'parent', [], 'parent'),
1888 [('p', 'parent', [], 'parent'),
1889 ('d', 'date', "", 'date code'),
1889 ('d', 'date', "", 'date code'),
1890 ('u', 'user', "", 'user'),
1890 ('u', 'user', "", 'user'),
1891 ('F', 'files', "", 'file list'),
1891 ('F', 'files', "", 'file list'),
1892 ('m', 'message', "", 'commit message'),
1892 ('m', 'message', "", 'commit message'),
1893 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1893 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1894 ('l', 'logfile', "", 'commit message file')],
1894 ('l', 'logfile', "", 'commit message file')],
1895 'hg rawcommit [OPTION]... [FILE]...'),
1895 'hg rawcommit [OPTION]... [FILE]...'),
1896 "recover": (recover, [], "hg recover"),
1896 "recover": (recover, [], "hg recover"),
1897 "^remove|rm": (remove,
1897 "^remove|rm": (remove,
1898 [('I', 'include', [], 'include path in search'),
1898 [('I', 'include', [], 'include path in search'),
1899 ('X', 'exclude', [], 'exclude path from search')],
1899 ('X', 'exclude', [], 'exclude path from search')],
1900 "hg remove [OPTION]... FILE..."),
1900 "hg remove [OPTION]... FILE..."),
1901 "rename|mv": (rename,
1901 "rename|mv": (rename,
1902 [('I', 'include', [], 'include path in search'),
1902 [('I', 'include', [], 'include path in search'),
1903 ('X', 'exclude', [], 'exclude path from search'),
1903 ('X', 'exclude', [], 'exclude path from search'),
1904 ('A', 'after', None, 'record a copy after it has happened'),
1904 ('A', 'after', None, 'record a copy after it has happened'),
1905 ('f', 'force', None, 'replace destination if it exists'),
1905 ('f', 'force', None, 'replace destination if it exists'),
1906 ('p', 'parents', None, 'append source path to dest')],
1906 ('p', 'parents', None, 'append source path to dest')],
1907 'hg rename [OPTION]... [SOURCE]... DEST'),
1907 'hg rename [OPTION]... [SOURCE]... DEST'),
1908 "^revert":
1908 "^revert":
1909 (revert,
1909 (revert,
1910 [("n", "nonrecursive", None, "don't recurse into subdirs"),
1910 [("n", "nonrecursive", None, "don't recurse into subdirs"),
1911 ("r", "rev", "", "revision")],
1911 ("r", "rev", "", "revision")],
1912 "hg revert [-n] [-r REV] [NAME]..."),
1912 "hg revert [-n] [-r REV] [NAME]..."),
1913 "root": (root, [], "hg root"),
1913 "root": (root, [], "hg root"),
1914 "^serve":
1914 "^serve":
1915 (serve,
1915 (serve,
1916 [('A', 'accesslog', '', 'access log file'),
1916 [('A', 'accesslog', '', 'access log file'),
1917 ('E', 'errorlog', '', 'error log file'),
1917 ('E', 'errorlog', '', 'error log file'),
1918 ('p', 'port', 0, 'listen port'),
1918 ('p', 'port', 0, 'listen port'),
1919 ('a', 'address', '', 'interface address'),
1919 ('a', 'address', '', 'interface address'),
1920 ('n', 'name', "", 'repository name'),
1920 ('n', 'name', "", 'repository name'),
1921 ('', 'stdio', None, 'for remote clients'),
1921 ('', 'stdio', None, 'for remote clients'),
1922 ('t', 'templates', "", 'template directory'),
1922 ('t', 'templates', "", 'template directory'),
1923 ('', 'style', "", 'template style'),
1923 ('', 'style', "", 'template style'),
1924 ('6', 'ipv6', None, 'use IPv6 in addition to IPv4')],
1924 ('6', 'ipv6', None, 'use IPv6 in addition to IPv4')],
1925 "hg serve [OPTION]..."),
1925 "hg serve [OPTION]..."),
1926 "^status":
1926 "^status":
1927 (status,
1927 (status,
1928 [('m', 'modified', None, 'show only modified files'),
1928 [('m', 'modified', None, 'show only modified files'),
1929 ('a', 'added', None, 'show only added files'),
1929 ('a', 'added', None, 'show only added files'),
1930 ('r', 'removed', None, 'show only removed files'),
1930 ('r', 'removed', None, 'show only removed files'),
1931 ('u', 'unknown', None, 'show only unknown (not tracked) files'),
1931 ('u', 'unknown', None, 'show only unknown (not tracked) files'),
1932 ('n', 'no-status', None, 'hide status prefix'),
1932 ('n', 'no-status', None, 'hide status prefix'),
1933 ('0', 'print0', None, 'end filenames with NUL'),
1933 ('0', 'print0', None, 'end filenames with NUL'),
1934 ('I', 'include', [], 'include path in search'),
1934 ('I', 'include', [], 'include path in search'),
1935 ('X', 'exclude', [], 'exclude path from search')],
1935 ('X', 'exclude', [], 'exclude path from search')],
1936 "hg status [OPTION]... [FILE]..."),
1936 "hg status [OPTION]... [FILE]..."),
1937 "tag":
1937 "tag":
1938 (tag,
1938 (tag,
1939 [('l', 'local', None, 'make the tag local'),
1939 [('l', 'local', None, 'make the tag local'),
1940 ('m', 'message', "", 'commit message'),
1940 ('m', 'message', "", 'commit message'),
1941 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1941 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1942 ('d', 'date', "", 'date code'),
1942 ('d', 'date', "", 'date code'),
1943 ('u', 'user', "", 'user')],
1943 ('u', 'user', "", 'user')],
1944 'hg tag [OPTION]... NAME [REV]'),
1944 'hg tag [OPTION]... NAME [REV]'),
1945 "tags": (tags, [], 'hg tags'),
1945 "tags": (tags, [], 'hg tags'),
1946 "tip": (tip, [], 'hg tip'),
1946 "tip": (tip, [], 'hg tip'),
1947 "unbundle":
1947 "unbundle":
1948 (unbundle,
1948 (unbundle,
1949 [],
1949 [],
1950 'hg unbundle FILE'),
1950 'hg unbundle FILE'),
1951 "undo": (undo, [], 'hg undo'),
1951 "undo": (undo, [], 'hg undo'),
1952 "^update|up|checkout|co":
1952 "^update|up|checkout|co":
1953 (update,
1953 (update,
1954 [('b', 'branch', "", 'checkout the head of a specific branch'),
1954 [('b', 'branch', "", 'checkout the head of a specific branch'),
1955 ('m', 'merge', None, 'allow merging of conflicts'),
1955 ('m', 'merge', None, 'allow merging of conflicts'),
1956 ('C', 'clean', None, 'overwrite locally modified files')],
1956 ('C', 'clean', None, 'overwrite locally modified files')],
1957 'hg update [-b TAG] [-m] [-C] [REV]'),
1957 'hg update [-b TAG] [-m] [-C] [REV]'),
1958 "verify": (verify, [], 'hg verify'),
1958 "verify": (verify, [], 'hg verify'),
1959 "version": (show_version, [], 'hg version'),
1959 "version": (show_version, [], 'hg version'),
1960 }
1960 }
1961
1961
1962 globalopts = [
1962 globalopts = [
1963 ('R', 'repository', "", 'repository root directory'),
1963 ('R', 'repository', "", 'repository root directory'),
1964 ('', 'cwd', '', 'change working directory'),
1964 ('', 'cwd', '', 'change working directory'),
1965 ('y', 'noninteractive', None, 'run non-interactively'),
1965 ('y', 'noninteractive', None, 'run non-interactively'),
1966 ('q', 'quiet', None, 'quiet mode'),
1966 ('q', 'quiet', None, 'quiet mode'),
1967 ('v', 'verbose', None, 'verbose mode'),
1967 ('v', 'verbose', None, 'verbose mode'),
1968 ('', 'debug', None, 'debug mode'),
1968 ('', 'debug', None, 'debug mode'),
1969 ('', 'debugger', None, 'start debugger'),
1969 ('', 'debugger', None, 'start debugger'),
1970 ('', 'traceback', None, 'print traceback on exception'),
1970 ('', 'traceback', None, 'print traceback on exception'),
1971 ('', 'time', None, 'time how long the command takes'),
1971 ('', 'time', None, 'time how long the command takes'),
1972 ('', 'profile', None, 'profile'),
1972 ('', 'profile', None, 'profile'),
1973 ('', 'version', None, 'output version information and exit'),
1973 ('', 'version', None, 'output version information and exit'),
1974 ('h', 'help', None, 'display help and exit'),
1974 ('h', 'help', None, 'display help and exit'),
1975 ]
1975 ]
1976
1976
1977 norepo = ("clone init version help debugancestor debugconfig debugdata"
1977 norepo = ("clone init version help debugancestor debugconfig debugdata"
1978 " debugindex debugindexdot paths")
1978 " debugindex debugindexdot paths")
1979
1979
1980 def find(cmd):
1980 def find(cmd):
1981 for e in table.keys():
1981 for e in table.keys():
1982 if re.match("(%s)$" % e, cmd):
1982 if re.match("(%s)$" % e, cmd):
1983 return e, table[e]
1983 return e, table[e]
1984
1984
1985 raise UnknownCommand(cmd)
1985 raise UnknownCommand(cmd)
1986
1986
1987 class SignalInterrupt(Exception):
1987 class SignalInterrupt(Exception):
1988 """Exception raised on SIGTERM and SIGHUP."""
1988 """Exception raised on SIGTERM and SIGHUP."""
1989
1989
1990 def catchterm(*args):
1990 def catchterm(*args):
1991 raise SignalInterrupt
1991 raise SignalInterrupt
1992
1992
1993 def run():
1993 def run():
1994 sys.exit(dispatch(sys.argv[1:]))
1994 sys.exit(dispatch(sys.argv[1:]))
1995
1995
1996 class ParseError(Exception):
1996 class ParseError(Exception):
1997 """Exception raised on errors in parsing the command line."""
1997 """Exception raised on errors in parsing the command line."""
1998
1998
1999 def parse(args):
1999 def parse(args):
2000 options = {}
2000 options = {}
2001 cmdoptions = {}
2001 cmdoptions = {}
2002
2002
2003 try:
2003 try:
2004 args = fancyopts.fancyopts(args, globalopts, options)
2004 args = fancyopts.fancyopts(args, globalopts, options)
2005 except fancyopts.getopt.GetoptError, inst:
2005 except fancyopts.getopt.GetoptError, inst:
2006 raise ParseError(None, inst)
2006 raise ParseError(None, inst)
2007
2007
2008 if args:
2008 if args:
2009 cmd, args = args[0], args[1:]
2009 cmd, args = args[0], args[1:]
2010 i = find(cmd)[1]
2010 i = find(cmd)[1]
2011 c = list(i[1])
2011 c = list(i[1])
2012 else:
2012 else:
2013 cmd = None
2013 cmd = None
2014 c = []
2014 c = []
2015
2015
2016 # combine global options into local
2016 # combine global options into local
2017 for o in globalopts:
2017 for o in globalopts:
2018 c.append((o[0], o[1], options[o[1]], o[3]))
2018 c.append((o[0], o[1], options[o[1]], o[3]))
2019
2019
2020 try:
2020 try:
2021 args = fancyopts.fancyopts(args, c, cmdoptions)
2021 args = fancyopts.fancyopts(args, c, cmdoptions)
2022 except fancyopts.getopt.GetoptError, inst:
2022 except fancyopts.getopt.GetoptError, inst:
2023 raise ParseError(cmd, inst)
2023 raise ParseError(cmd, inst)
2024
2024
2025 # separate global options back out
2025 # separate global options back out
2026 for o in globalopts:
2026 for o in globalopts:
2027 n = o[1]
2027 n = o[1]
2028 options[n] = cmdoptions[n]
2028 options[n] = cmdoptions[n]
2029 del cmdoptions[n]
2029 del cmdoptions[n]
2030
2030
2031 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
2031 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
2032
2032
2033 def dispatch(args):
2033 def dispatch(args):
2034 signal.signal(signal.SIGTERM, catchterm)
2034 signal.signal(signal.SIGTERM, catchterm)
2035 try:
2035 try:
2036 signal.signal(signal.SIGHUP, catchterm)
2036 signal.signal(signal.SIGHUP, catchterm)
2037 except AttributeError:
2037 except AttributeError:
2038 pass
2038 pass
2039
2039
2040 u = ui.ui()
2040 u = ui.ui()
2041 external = []
2041 external = []
2042 for x in u.extensions():
2042 for x in u.extensions():
2043 if x[1]:
2043 if x[1]:
2044 mod = imp.load_source(x[0], x[1])
2044 mod = imp.load_source(x[0], x[1])
2045 else:
2045 else:
2046 def importh(name):
2046 def importh(name):
2047 mod = __import__(name)
2047 mod = __import__(name)
2048 components = name.split('.')
2048 components = name.split('.')
2049 for comp in components[1:]:
2049 for comp in components[1:]:
2050 mod = getattr(mod, comp)
2050 mod = getattr(mod, comp)
2051 return mod
2051 return mod
2052 mod = importh(x[0])
2052 mod = importh(x[0])
2053 external.append(mod)
2053 external.append(mod)
2054 for x in external:
2054 for x in external:
2055 cmdtable = getattr(x, 'cmdtable', {})
2055 cmdtable = getattr(x, 'cmdtable', {})
2056 for t in cmdtable:
2056 for t in cmdtable:
2057 if t in table:
2057 if t in table:
2058 u.warn("module %s overrides %s\n" % (x.__name__, t))
2058 u.warn("module %s overrides %s\n" % (x.__name__, t))
2059 table.update(cmdtable)
2059 table.update(cmdtable)
2060
2060
2061 try:
2061 try:
2062 cmd, func, args, options, cmdoptions = parse(args)
2062 cmd, func, args, options, cmdoptions = parse(args)
2063 except ParseError, inst:
2063 except ParseError, inst:
2064 if inst.args[0]:
2064 if inst.args[0]:
2065 u.warn("hg %s: %s\n" % (inst.args[0], inst.args[1]))
2065 u.warn("hg %s: %s\n" % (inst.args[0], inst.args[1]))
2066 help_(u, inst.args[0])
2066 help_(u, inst.args[0])
2067 else:
2067 else:
2068 u.warn("hg: %s\n" % inst.args[1])
2068 u.warn("hg: %s\n" % inst.args[1])
2069 help_(u, 'shortlist')
2069 help_(u, 'shortlist')
2070 sys.exit(-1)
2070 sys.exit(-1)
2071 except UnknownCommand, inst:
2071 except UnknownCommand, inst:
2072 u.warn("hg: unknown command '%s'\n" % inst.args[0])
2072 u.warn("hg: unknown command '%s'\n" % inst.args[0])
2073 help_(u, 'shortlist')
2073 help_(u, 'shortlist')
2074 sys.exit(1)
2074 sys.exit(1)
2075
2075
2076 if options["time"]:
2076 if options["time"]:
2077 def get_times():
2077 def get_times():
2078 t = os.times()
2078 t = os.times()
2079 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
2079 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
2080 t = (t[0], t[1], t[2], t[3], time.clock())
2080 t = (t[0], t[1], t[2], t[3], time.clock())
2081 return t
2081 return t
2082 s = get_times()
2082 s = get_times()
2083 def print_time():
2083 def print_time():
2084 t = get_times()
2084 t = get_times()
2085 u.warn("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n" %
2085 u.warn("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n" %
2086 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
2086 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
2087 atexit.register(print_time)
2087 atexit.register(print_time)
2088
2088
2089 u.updateopts(options["verbose"], options["debug"], options["quiet"],
2089 u.updateopts(options["verbose"], options["debug"], options["quiet"],
2090 not options["noninteractive"])
2090 not options["noninteractive"])
2091
2091
2092 # enter the debugger before command execution
2092 # enter the debugger before command execution
2093 if options['debugger']:
2093 if options['debugger']:
2094 pdb.set_trace()
2094 pdb.set_trace()
2095
2095
2096 try:
2096 try:
2097 try:
2097 try:
2098 if options['help']:
2098 if options['help']:
2099 help_(u, cmd, options['version'])
2099 help_(u, cmd, options['version'])
2100 sys.exit(0)
2100 sys.exit(0)
2101 elif options['version']:
2101 elif options['version']:
2102 show_version(u)
2102 show_version(u)
2103 sys.exit(0)
2103 sys.exit(0)
2104 elif not cmd:
2104 elif not cmd:
2105 help_(u, 'shortlist')
2105 help_(u, 'shortlist')
2106 sys.exit(0)
2106 sys.exit(0)
2107
2107
2108 if options['cwd']:
2108 if options['cwd']:
2109 try:
2109 try:
2110 os.chdir(options['cwd'])
2110 os.chdir(options['cwd'])
2111 except OSError, inst:
2111 except OSError, inst:
2112 raise util.Abort('%s: %s' %
2112 raise util.Abort('%s: %s' %
2113 (options['cwd'], inst.strerror))
2113 (options['cwd'], inst.strerror))
2114
2114
2115 if cmd not in norepo.split():
2115 if cmd not in norepo.split():
2116 path = options["repository"] or ""
2116 path = options["repository"] or ""
2117 repo = hg.repository(ui=u, path=path)
2117 repo = hg.repository(ui=u, path=path)
2118 for x in external:
2118 for x in external:
2119 if hasattr(x, 'reposetup'): x.reposetup(u, repo)
2119 if hasattr(x, 'reposetup'): x.reposetup(u, repo)
2120 d = lambda: func(u, repo, *args, **cmdoptions)
2120 d = lambda: func(u, repo, *args, **cmdoptions)
2121 else:
2121 else:
2122 d = lambda: func(u, *args, **cmdoptions)
2122 d = lambda: func(u, *args, **cmdoptions)
2123
2123
2124 if options['profile']:
2124 if options['profile']:
2125 import hotshot, hotshot.stats
2125 import hotshot, hotshot.stats
2126 prof = hotshot.Profile("hg.prof")
2126 prof = hotshot.Profile("hg.prof")
2127 r = prof.runcall(d)
2127 r = prof.runcall(d)
2128 prof.close()
2128 prof.close()
2129 stats = hotshot.stats.load("hg.prof")
2129 stats = hotshot.stats.load("hg.prof")
2130 stats.strip_dirs()
2130 stats.strip_dirs()
2131 stats.sort_stats('time', 'calls')
2131 stats.sort_stats('time', 'calls')
2132 stats.print_stats(40)
2132 stats.print_stats(40)
2133 return r
2133 return r
2134 else:
2134 else:
2135 return d()
2135 return d()
2136 except:
2136 except:
2137 # enter the debugger when we hit an exception
2137 # enter the debugger when we hit an exception
2138 if options['debugger']:
2138 if options['debugger']:
2139 pdb.post_mortem(sys.exc_info()[2])
2139 pdb.post_mortem(sys.exc_info()[2])
2140 if options['traceback']:
2140 if options['traceback']:
2141 traceback.print_exc()
2141 traceback.print_exc()
2142 raise
2142 raise
2143 except hg.RepoError, inst:
2143 except hg.RepoError, inst:
2144 u.warn("abort: ", inst, "!\n")
2144 u.warn("abort: ", inst, "!\n")
2145 except revlog.RevlogError, inst:
2145 except revlog.RevlogError, inst:
2146 u.warn("abort: ", inst, "!\n")
2146 u.warn("abort: ", inst, "!\n")
2147 except SignalInterrupt:
2147 except SignalInterrupt:
2148 u.warn("killed!\n")
2148 u.warn("killed!\n")
2149 except KeyboardInterrupt:
2149 except KeyboardInterrupt:
2150 try:
2150 try:
2151 u.warn("interrupted!\n")
2151 u.warn("interrupted!\n")
2152 except IOError, inst:
2152 except IOError, inst:
2153 if inst.errno == errno.EPIPE:
2153 if inst.errno == errno.EPIPE:
2154 if u.debugflag:
2154 if u.debugflag:
2155 u.warn("\nbroken pipe\n")
2155 u.warn("\nbroken pipe\n")
2156 else:
2156 else:
2157 raise
2157 raise
2158 except IOError, inst:
2158 except IOError, inst:
2159 if hasattr(inst, "code"):
2159 if hasattr(inst, "code"):
2160 u.warn("abort: %s\n" % inst)
2160 u.warn("abort: %s\n" % inst)
2161 elif hasattr(inst, "reason"):
2161 elif hasattr(inst, "reason"):
2162 u.warn("abort: error: %s\n" % inst.reason[1])
2162 u.warn("abort: error: %s\n" % inst.reason[1])
2163 elif getattr(inst, "strerror", None):
2164 u.warn("abort: %s\n" % inst.strerror)
2163 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
2165 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
2164 if u.debugflag:
2166 if u.debugflag:
2165 u.warn("broken pipe\n")
2167 u.warn("broken pipe\n")
2166 else:
2168 else:
2167 raise
2169 raise
2168 except OSError, inst:
2170 except OSError, inst:
2169 if hasattr(inst, "filename"):
2171 if hasattr(inst, "filename"):
2170 u.warn("abort: %s: %s\n" % (inst.strerror, inst.filename))
2172 u.warn("abort: %s: %s\n" % (inst.strerror, inst.filename))
2171 else:
2173 else:
2172 u.warn("abort: %s\n" % inst.strerror)
2174 u.warn("abort: %s\n" % inst.strerror)
2173 except util.Abort, inst:
2175 except util.Abort, inst:
2174 u.warn('abort: ', inst.args[0] % inst.args[1:], '\n')
2176 u.warn('abort: ', inst.args[0] % inst.args[1:], '\n')
2175 sys.exit(1)
2177 sys.exit(1)
2176 except TypeError, inst:
2178 except TypeError, inst:
2177 # was this an argument error?
2179 # was this an argument error?
2178 tb = traceback.extract_tb(sys.exc_info()[2])
2180 tb = traceback.extract_tb(sys.exc_info()[2])
2179 if len(tb) > 2: # no
2181 if len(tb) > 2: # no
2180 raise
2182 raise
2181 u.debug(inst, "\n")
2183 u.debug(inst, "\n")
2182 u.warn("%s: invalid arguments\n" % cmd)
2184 u.warn("%s: invalid arguments\n" % cmd)
2183 help_(u, cmd)
2185 help_(u, cmd)
2184 except UnknownCommand, inst:
2186 except UnknownCommand, inst:
2185 u.warn("hg: unknown command '%s'\n" % inst.args[0])
2187 u.warn("hg: unknown command '%s'\n" % inst.args[0])
2186 help_(u, 'shortlist')
2188 help_(u, 'shortlist')
2187 except SystemExit:
2189 except SystemExit:
2188 # don't catch this in the catch-all below
2190 # don't catch this in the catch-all below
2189 raise
2191 raise
2190 except:
2192 except:
2191 u.warn("** unknown exception encountered, details follow\n")
2193 u.warn("** unknown exception encountered, details follow\n")
2192 u.warn("** report bug details to mercurial@selenic.com\n")
2194 u.warn("** report bug details to mercurial@selenic.com\n")
2193 raise
2195 raise
2194
2196
2195 sys.exit(-1)
2197 sys.exit(-1)
@@ -1,655 +1,653 b''
1 """
1 """
2 revlog.py - storage back-end for mercurial
2 revlog.py - storage back-end for mercurial
3
3
4 This provides efficient delta storage with O(1) retrieve and append
4 This provides efficient delta storage with O(1) retrieve and append
5 and O(changes) merge between branches
5 and O(changes) merge between branches
6
6
7 Copyright 2005 Matt Mackall <mpm@selenic.com>
7 Copyright 2005 Matt Mackall <mpm@selenic.com>
8
8
9 This software may be used and distributed according to the terms
9 This software may be used and distributed according to the terms
10 of the GNU General Public License, incorporated herein by reference.
10 of the GNU General Public License, incorporated herein by reference.
11 """
11 """
12
12
13 from node import *
13 from node import *
14 from demandload import demandload
14 from demandload import demandload
15 demandload(globals(), "binascii errno heapq mdiff sha struct urllib2 zlib")
15 demandload(globals(), "binascii errno heapq mdiff sha struct zlib")
16
16
17 def hash(text, p1, p2):
17 def hash(text, p1, p2):
18 """generate a hash from the given text and its parent hashes
18 """generate a hash from the given text and its parent hashes
19
19
20 This hash combines both the current file contents and its history
20 This hash combines both the current file contents and its history
21 in a manner that makes it easy to distinguish nodes with the same
21 in a manner that makes it easy to distinguish nodes with the same
22 content in the revision graph.
22 content in the revision graph.
23 """
23 """
24 l = [p1, p2]
24 l = [p1, p2]
25 l.sort()
25 l.sort()
26 s = sha.new(l[0])
26 s = sha.new(l[0])
27 s.update(l[1])
27 s.update(l[1])
28 s.update(text)
28 s.update(text)
29 return s.digest()
29 return s.digest()
30
30
31 def compress(text):
31 def compress(text):
32 """ generate a possibly-compressed representation of text """
32 """ generate a possibly-compressed representation of text """
33 if not text: return text
33 if not text: return text
34 if len(text) < 44:
34 if len(text) < 44:
35 if text[0] == '\0': return text
35 if text[0] == '\0': return text
36 return 'u' + text
36 return 'u' + text
37 bin = zlib.compress(text)
37 bin = zlib.compress(text)
38 if len(bin) > len(text):
38 if len(bin) > len(text):
39 if text[0] == '\0': return text
39 if text[0] == '\0': return text
40 return 'u' + text
40 return 'u' + text
41 return bin
41 return bin
42
42
43 def decompress(bin):
43 def decompress(bin):
44 """ decompress the given input """
44 """ decompress the given input """
45 if not bin: return bin
45 if not bin: return bin
46 t = bin[0]
46 t = bin[0]
47 if t == '\0': return bin
47 if t == '\0': return bin
48 if t == 'x': return zlib.decompress(bin)
48 if t == 'x': return zlib.decompress(bin)
49 if t == 'u': return bin[1:]
49 if t == 'u': return bin[1:]
50 raise RevlogError("unknown compression type %s" % t)
50 raise RevlogError("unknown compression type %s" % t)
51
51
52 indexformat = ">4l20s20s20s"
52 indexformat = ">4l20s20s20s"
53
53
54 class lazyparser:
54 class lazyparser:
55 """
55 """
56 this class avoids the need to parse the entirety of large indices
56 this class avoids the need to parse the entirety of large indices
57
57
58 By default we parse and load 1000 entries at a time.
58 By default we parse and load 1000 entries at a time.
59
59
60 If no position is specified, we load the whole index, and replace
60 If no position is specified, we load the whole index, and replace
61 the lazy objects in revlog with the underlying objects for
61 the lazy objects in revlog with the underlying objects for
62 efficiency in cases where we look at most of the nodes.
62 efficiency in cases where we look at most of the nodes.
63 """
63 """
64 def __init__(self, data, revlog):
64 def __init__(self, data, revlog):
65 self.data = data
65 self.data = data
66 self.s = struct.calcsize(indexformat)
66 self.s = struct.calcsize(indexformat)
67 self.l = len(data)/self.s
67 self.l = len(data)/self.s
68 self.index = [None] * self.l
68 self.index = [None] * self.l
69 self.map = {nullid: -1}
69 self.map = {nullid: -1}
70 self.all = 0
70 self.all = 0
71 self.revlog = revlog
71 self.revlog = revlog
72
72
73 def load(self, pos=None):
73 def load(self, pos=None):
74 if self.all: return
74 if self.all: return
75 if pos is not None:
75 if pos is not None:
76 block = pos / 1000
76 block = pos / 1000
77 i = block * 1000
77 i = block * 1000
78 end = min(self.l, i + 1000)
78 end = min(self.l, i + 1000)
79 else:
79 else:
80 self.all = 1
80 self.all = 1
81 i = 0
81 i = 0
82 end = self.l
82 end = self.l
83 self.revlog.index = self.index
83 self.revlog.index = self.index
84 self.revlog.nodemap = self.map
84 self.revlog.nodemap = self.map
85
85
86 while i < end:
86 while i < end:
87 d = self.data[i * self.s: (i + 1) * self.s]
87 d = self.data[i * self.s: (i + 1) * self.s]
88 e = struct.unpack(indexformat, d)
88 e = struct.unpack(indexformat, d)
89 self.index[i] = e
89 self.index[i] = e
90 self.map[e[6]] = i
90 self.map[e[6]] = i
91 i += 1
91 i += 1
92
92
93 class lazyindex:
93 class lazyindex:
94 """a lazy version of the index array"""
94 """a lazy version of the index array"""
95 def __init__(self, parser):
95 def __init__(self, parser):
96 self.p = parser
96 self.p = parser
97 def __len__(self):
97 def __len__(self):
98 return len(self.p.index)
98 return len(self.p.index)
99 def load(self, pos):
99 def load(self, pos):
100 self.p.load(pos)
100 self.p.load(pos)
101 return self.p.index[pos]
101 return self.p.index[pos]
102 def __getitem__(self, pos):
102 def __getitem__(self, pos):
103 return self.p.index[pos] or self.load(pos)
103 return self.p.index[pos] or self.load(pos)
104 def append(self, e):
104 def append(self, e):
105 self.p.index.append(e)
105 self.p.index.append(e)
106
106
107 class lazymap:
107 class lazymap:
108 """a lazy version of the node map"""
108 """a lazy version of the node map"""
109 def __init__(self, parser):
109 def __init__(self, parser):
110 self.p = parser
110 self.p = parser
111 def load(self, key):
111 def load(self, key):
112 if self.p.all: return
112 if self.p.all: return
113 n = self.p.data.find(key)
113 n = self.p.data.find(key)
114 if n < 0:
114 if n < 0:
115 raise KeyError(key)
115 raise KeyError(key)
116 pos = n / self.p.s
116 pos = n / self.p.s
117 self.p.load(pos)
117 self.p.load(pos)
118 def __contains__(self, key):
118 def __contains__(self, key):
119 self.p.load()
119 self.p.load()
120 return key in self.p.map
120 return key in self.p.map
121 def __iter__(self):
121 def __iter__(self):
122 yield nullid
122 yield nullid
123 for i in xrange(self.p.l):
123 for i in xrange(self.p.l):
124 try:
124 try:
125 yield self.p.index[i][6]
125 yield self.p.index[i][6]
126 except:
126 except:
127 self.p.load(i)
127 self.p.load(i)
128 yield self.p.index[i][6]
128 yield self.p.index[i][6]
129 def __getitem__(self, key):
129 def __getitem__(self, key):
130 try:
130 try:
131 return self.p.map[key]
131 return self.p.map[key]
132 except KeyError:
132 except KeyError:
133 try:
133 try:
134 self.load(key)
134 self.load(key)
135 return self.p.map[key]
135 return self.p.map[key]
136 except KeyError:
136 except KeyError:
137 raise KeyError("node " + hex(key))
137 raise KeyError("node " + hex(key))
138 def __setitem__(self, key, val):
138 def __setitem__(self, key, val):
139 self.p.map[key] = val
139 self.p.map[key] = val
140
140
141 class RevlogError(Exception): pass
141 class RevlogError(Exception): pass
142
142
143 class revlog:
143 class revlog:
144 """
144 """
145 the underlying revision storage object
145 the underlying revision storage object
146
146
147 A revlog consists of two parts, an index and the revision data.
147 A revlog consists of two parts, an index and the revision data.
148
148
149 The index is a file with a fixed record size containing
149 The index is a file with a fixed record size containing
150 information on each revision, includings its nodeid (hash), the
150 information on each revision, includings its nodeid (hash), the
151 nodeids of its parents, the position and offset of its data within
151 nodeids of its parents, the position and offset of its data within
152 the data file, and the revision it's based on. Finally, each entry
152 the data file, and the revision it's based on. Finally, each entry
153 contains a linkrev entry that can serve as a pointer to external
153 contains a linkrev entry that can serve as a pointer to external
154 data.
154 data.
155
155
156 The revision data itself is a linear collection of data chunks.
156 The revision data itself is a linear collection of data chunks.
157 Each chunk represents a revision and is usually represented as a
157 Each chunk represents a revision and is usually represented as a
158 delta against the previous chunk. To bound lookup time, runs of
158 delta against the previous chunk. To bound lookup time, runs of
159 deltas are limited to about 2 times the length of the original
159 deltas are limited to about 2 times the length of the original
160 version data. This makes retrieval of a version proportional to
160 version data. This makes retrieval of a version proportional to
161 its size, or O(1) relative to the number of revisions.
161 its size, or O(1) relative to the number of revisions.
162
162
163 Both pieces of the revlog are written to in an append-only
163 Both pieces of the revlog are written to in an append-only
164 fashion, which means we never need to rewrite a file to insert or
164 fashion, which means we never need to rewrite a file to insert or
165 remove data, and can use some simple techniques to avoid the need
165 remove data, and can use some simple techniques to avoid the need
166 for locking while reading.
166 for locking while reading.
167 """
167 """
168 def __init__(self, opener, indexfile, datafile):
168 def __init__(self, opener, indexfile, datafile):
169 """
169 """
170 create a revlog object
170 create a revlog object
171
171
172 opener is a function that abstracts the file opening operation
172 opener is a function that abstracts the file opening operation
173 and can be used to implement COW semantics or the like.
173 and can be used to implement COW semantics or the like.
174 """
174 """
175 self.indexfile = indexfile
175 self.indexfile = indexfile
176 self.datafile = datafile
176 self.datafile = datafile
177 self.opener = opener
177 self.opener = opener
178 self.cache = None
178 self.cache = None
179
179
180 try:
180 try:
181 i = self.opener(self.indexfile).read()
181 i = self.opener(self.indexfile).read()
182 except urllib2.URLError:
183 raise
184 except IOError, inst:
182 except IOError, inst:
185 if inst.errno != errno.ENOENT:
183 if inst.errno != errno.ENOENT:
186 raise
184 raise
187 i = ""
185 i = ""
188
186
189 if len(i) > 10000:
187 if len(i) > 10000:
190 # big index, let's parse it on demand
188 # big index, let's parse it on demand
191 parser = lazyparser(i, self)
189 parser = lazyparser(i, self)
192 self.index = lazyindex(parser)
190 self.index = lazyindex(parser)
193 self.nodemap = lazymap(parser)
191 self.nodemap = lazymap(parser)
194 else:
192 else:
195 s = struct.calcsize(indexformat)
193 s = struct.calcsize(indexformat)
196 l = len(i) / s
194 l = len(i) / s
197 self.index = [None] * l
195 self.index = [None] * l
198 m = [None] * l
196 m = [None] * l
199
197
200 n = 0
198 n = 0
201 for f in xrange(0, len(i), s):
199 for f in xrange(0, len(i), s):
202 # offset, size, base, linkrev, p1, p2, nodeid
200 # offset, size, base, linkrev, p1, p2, nodeid
203 e = struct.unpack(indexformat, i[f:f + s])
201 e = struct.unpack(indexformat, i[f:f + s])
204 m[n] = (e[6], n)
202 m[n] = (e[6], n)
205 self.index[n] = e
203 self.index[n] = e
206 n += 1
204 n += 1
207
205
208 self.nodemap = dict(m)
206 self.nodemap = dict(m)
209 self.nodemap[nullid] = -1
207 self.nodemap[nullid] = -1
210
208
211 def tip(self): return self.node(len(self.index) - 1)
209 def tip(self): return self.node(len(self.index) - 1)
212 def count(self): return len(self.index)
210 def count(self): return len(self.index)
213 def node(self, rev): return (rev < 0) and nullid or self.index[rev][6]
211 def node(self, rev): return (rev < 0) and nullid or self.index[rev][6]
214 def rev(self, node):
212 def rev(self, node):
215 try:
213 try:
216 return self.nodemap[node]
214 return self.nodemap[node]
217 except KeyError:
215 except KeyError:
218 raise RevlogError('%s: no node %s' % (self.indexfile, hex(node)))
216 raise RevlogError('%s: no node %s' % (self.indexfile, hex(node)))
219 def linkrev(self, node): return self.index[self.rev(node)][3]
217 def linkrev(self, node): return self.index[self.rev(node)][3]
220 def parents(self, node):
218 def parents(self, node):
221 if node == nullid: return (nullid, nullid)
219 if node == nullid: return (nullid, nullid)
222 return self.index[self.rev(node)][4:6]
220 return self.index[self.rev(node)][4:6]
223
221
224 def start(self, rev): return self.index[rev][0]
222 def start(self, rev): return self.index[rev][0]
225 def length(self, rev): return self.index[rev][1]
223 def length(self, rev): return self.index[rev][1]
226 def end(self, rev): return self.start(rev) + self.length(rev)
224 def end(self, rev): return self.start(rev) + self.length(rev)
227 def base(self, rev): return self.index[rev][2]
225 def base(self, rev): return self.index[rev][2]
228
226
229 def reachable(self, rev, stop=None):
227 def reachable(self, rev, stop=None):
230 reachable = {}
228 reachable = {}
231 visit = [rev]
229 visit = [rev]
232 reachable[rev] = 1
230 reachable[rev] = 1
233 if stop:
231 if stop:
234 stopn = self.rev(stop)
232 stopn = self.rev(stop)
235 else:
233 else:
236 stopn = 0
234 stopn = 0
237 while visit:
235 while visit:
238 n = visit.pop(0)
236 n = visit.pop(0)
239 if n == stop:
237 if n == stop:
240 continue
238 continue
241 if n == nullid:
239 if n == nullid:
242 continue
240 continue
243 for p in self.parents(n):
241 for p in self.parents(n):
244 if self.rev(p) < stopn:
242 if self.rev(p) < stopn:
245 continue
243 continue
246 if p not in reachable:
244 if p not in reachable:
247 reachable[p] = 1
245 reachable[p] = 1
248 visit.append(p)
246 visit.append(p)
249 return reachable
247 return reachable
250
248
251 def heads(self, stop=None):
249 def heads(self, stop=None):
252 """return the list of all nodes that have no children"""
250 """return the list of all nodes that have no children"""
253 p = {}
251 p = {}
254 h = []
252 h = []
255 stoprev = 0
253 stoprev = 0
256 if stop and stop in self.nodemap:
254 if stop and stop in self.nodemap:
257 stoprev = self.rev(stop)
255 stoprev = self.rev(stop)
258
256
259 for r in range(self.count() - 1, -1, -1):
257 for r in range(self.count() - 1, -1, -1):
260 n = self.node(r)
258 n = self.node(r)
261 if n not in p:
259 if n not in p:
262 h.append(n)
260 h.append(n)
263 if n == stop:
261 if n == stop:
264 break
262 break
265 if r < stoprev:
263 if r < stoprev:
266 break
264 break
267 for pn in self.parents(n):
265 for pn in self.parents(n):
268 p[pn] = 1
266 p[pn] = 1
269 return h
267 return h
270
268
271 def children(self, node):
269 def children(self, node):
272 """find the children of a given node"""
270 """find the children of a given node"""
273 c = []
271 c = []
274 p = self.rev(node)
272 p = self.rev(node)
275 for r in range(p + 1, self.count()):
273 for r in range(p + 1, self.count()):
276 n = self.node(r)
274 n = self.node(r)
277 for pn in self.parents(n):
275 for pn in self.parents(n):
278 if pn == node:
276 if pn == node:
279 c.append(n)
277 c.append(n)
280 continue
278 continue
281 elif pn == nullid:
279 elif pn == nullid:
282 continue
280 continue
283 return c
281 return c
284
282
285 def lookup(self, id):
283 def lookup(self, id):
286 """locate a node based on revision number or subset of hex nodeid"""
284 """locate a node based on revision number or subset of hex nodeid"""
287 try:
285 try:
288 rev = int(id)
286 rev = int(id)
289 if str(rev) != id: raise ValueError
287 if str(rev) != id: raise ValueError
290 if rev < 0: rev = self.count() + rev
288 if rev < 0: rev = self.count() + rev
291 if rev < 0 or rev >= self.count(): raise ValueError
289 if rev < 0 or rev >= self.count(): raise ValueError
292 return self.node(rev)
290 return self.node(rev)
293 except (ValueError, OverflowError):
291 except (ValueError, OverflowError):
294 c = []
292 c = []
295 for n in self.nodemap:
293 for n in self.nodemap:
296 if hex(n).startswith(id):
294 if hex(n).startswith(id):
297 c.append(n)
295 c.append(n)
298 if len(c) > 1: raise KeyError("Ambiguous identifier")
296 if len(c) > 1: raise KeyError("Ambiguous identifier")
299 if len(c) < 1: raise KeyError("No match found")
297 if len(c) < 1: raise KeyError("No match found")
300 return c[0]
298 return c[0]
301
299
302 return None
300 return None
303
301
304 def diff(self, a, b):
302 def diff(self, a, b):
305 """return a delta between two revisions"""
303 """return a delta between two revisions"""
306 return mdiff.textdiff(a, b)
304 return mdiff.textdiff(a, b)
307
305
308 def patches(self, t, pl):
306 def patches(self, t, pl):
309 """apply a list of patches to a string"""
307 """apply a list of patches to a string"""
310 return mdiff.patches(t, pl)
308 return mdiff.patches(t, pl)
311
309
312 def delta(self, node):
310 def delta(self, node):
313 """return or calculate a delta between a node and its predecessor"""
311 """return or calculate a delta between a node and its predecessor"""
314 r = self.rev(node)
312 r = self.rev(node)
315 b = self.base(r)
313 b = self.base(r)
316 if r == b:
314 if r == b:
317 return self.diff(self.revision(self.node(r - 1)),
315 return self.diff(self.revision(self.node(r - 1)),
318 self.revision(node))
316 self.revision(node))
319 else:
317 else:
320 f = self.opener(self.datafile)
318 f = self.opener(self.datafile)
321 f.seek(self.start(r))
319 f.seek(self.start(r))
322 data = f.read(self.length(r))
320 data = f.read(self.length(r))
323 return decompress(data)
321 return decompress(data)
324
322
325 def revision(self, node):
323 def revision(self, node):
326 """return an uncompressed revision of a given"""
324 """return an uncompressed revision of a given"""
327 if node == nullid: return ""
325 if node == nullid: return ""
328 if self.cache and self.cache[0] == node: return self.cache[2]
326 if self.cache and self.cache[0] == node: return self.cache[2]
329
327
330 # look up what we need to read
328 # look up what we need to read
331 text = None
329 text = None
332 rev = self.rev(node)
330 rev = self.rev(node)
333 start, length, base, link, p1, p2, node = self.index[rev]
331 start, length, base, link, p1, p2, node = self.index[rev]
334 end = start + length
332 end = start + length
335 if base != rev: start = self.start(base)
333 if base != rev: start = self.start(base)
336
334
337 # do we have useful data cached?
335 # do we have useful data cached?
338 if self.cache and self.cache[1] >= base and self.cache[1] < rev:
336 if self.cache and self.cache[1] >= base and self.cache[1] < rev:
339 base = self.cache[1]
337 base = self.cache[1]
340 start = self.start(base + 1)
338 start = self.start(base + 1)
341 text = self.cache[2]
339 text = self.cache[2]
342 last = 0
340 last = 0
343
341
344 f = self.opener(self.datafile)
342 f = self.opener(self.datafile)
345 f.seek(start)
343 f.seek(start)
346 data = f.read(end - start)
344 data = f.read(end - start)
347
345
348 if text is None:
346 if text is None:
349 last = self.length(base)
347 last = self.length(base)
350 text = decompress(data[:last])
348 text = decompress(data[:last])
351
349
352 bins = []
350 bins = []
353 for r in xrange(base + 1, rev + 1):
351 for r in xrange(base + 1, rev + 1):
354 s = self.length(r)
352 s = self.length(r)
355 bins.append(decompress(data[last:last + s]))
353 bins.append(decompress(data[last:last + s]))
356 last = last + s
354 last = last + s
357
355
358 text = mdiff.patches(text, bins)
356 text = mdiff.patches(text, bins)
359
357
360 if node != hash(text, p1, p2):
358 if node != hash(text, p1, p2):
361 raise RevlogError("integrity check failed on %s:%d"
359 raise RevlogError("integrity check failed on %s:%d"
362 % (self.datafile, rev))
360 % (self.datafile, rev))
363
361
364 self.cache = (node, rev, text)
362 self.cache = (node, rev, text)
365 return text
363 return text
366
364
367 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
365 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
368 """add a revision to the log
366 """add a revision to the log
369
367
370 text - the revision data to add
368 text - the revision data to add
371 transaction - the transaction object used for rollback
369 transaction - the transaction object used for rollback
372 link - the linkrev data to add
370 link - the linkrev data to add
373 p1, p2 - the parent nodeids of the revision
371 p1, p2 - the parent nodeids of the revision
374 d - an optional precomputed delta
372 d - an optional precomputed delta
375 """
373 """
376 if text is None: text = ""
374 if text is None: text = ""
377 if p1 is None: p1 = self.tip()
375 if p1 is None: p1 = self.tip()
378 if p2 is None: p2 = nullid
376 if p2 is None: p2 = nullid
379
377
380 node = hash(text, p1, p2)
378 node = hash(text, p1, p2)
381
379
382 if node in self.nodemap:
380 if node in self.nodemap:
383 return node
381 return node
384
382
385 n = self.count()
383 n = self.count()
386 t = n - 1
384 t = n - 1
387
385
388 if n:
386 if n:
389 base = self.base(t)
387 base = self.base(t)
390 start = self.start(base)
388 start = self.start(base)
391 end = self.end(t)
389 end = self.end(t)
392 if not d:
390 if not d:
393 prev = self.revision(self.tip())
391 prev = self.revision(self.tip())
394 d = self.diff(prev, text)
392 d = self.diff(prev, text)
395 data = compress(d)
393 data = compress(d)
396 dist = end - start + len(data)
394 dist = end - start + len(data)
397
395
398 # full versions are inserted when the needed deltas
396 # full versions are inserted when the needed deltas
399 # become comparable to the uncompressed text
397 # become comparable to the uncompressed text
400 if not n or dist > len(text) * 2:
398 if not n or dist > len(text) * 2:
401 data = compress(text)
399 data = compress(text)
402 base = n
400 base = n
403 else:
401 else:
404 base = self.base(t)
402 base = self.base(t)
405
403
406 offset = 0
404 offset = 0
407 if t >= 0:
405 if t >= 0:
408 offset = self.end(t)
406 offset = self.end(t)
409
407
410 e = (offset, len(data), base, link, p1, p2, node)
408 e = (offset, len(data), base, link, p1, p2, node)
411
409
412 self.index.append(e)
410 self.index.append(e)
413 self.nodemap[node] = n
411 self.nodemap[node] = n
414 entry = struct.pack(indexformat, *e)
412 entry = struct.pack(indexformat, *e)
415
413
416 transaction.add(self.datafile, e[0])
414 transaction.add(self.datafile, e[0])
417 self.opener(self.datafile, "a").write(data)
415 self.opener(self.datafile, "a").write(data)
418 transaction.add(self.indexfile, n * len(entry))
416 transaction.add(self.indexfile, n * len(entry))
419 self.opener(self.indexfile, "a").write(entry)
417 self.opener(self.indexfile, "a").write(entry)
420
418
421 self.cache = (node, n, text)
419 self.cache = (node, n, text)
422 return node
420 return node
423
421
424 def ancestor(self, a, b):
422 def ancestor(self, a, b):
425 """calculate the least common ancestor of nodes a and b"""
423 """calculate the least common ancestor of nodes a and b"""
426 # calculate the distance of every node from root
424 # calculate the distance of every node from root
427 dist = {nullid: 0}
425 dist = {nullid: 0}
428 for i in xrange(self.count()):
426 for i in xrange(self.count()):
429 n = self.node(i)
427 n = self.node(i)
430 p1, p2 = self.parents(n)
428 p1, p2 = self.parents(n)
431 dist[n] = max(dist[p1], dist[p2]) + 1
429 dist[n] = max(dist[p1], dist[p2]) + 1
432
430
433 # traverse ancestors in order of decreasing distance from root
431 # traverse ancestors in order of decreasing distance from root
434 def ancestors(node):
432 def ancestors(node):
435 # we store negative distances because heap returns smallest member
433 # we store negative distances because heap returns smallest member
436 h = [(-dist[node], node)]
434 h = [(-dist[node], node)]
437 seen = {}
435 seen = {}
438 earliest = self.count()
436 earliest = self.count()
439 while h:
437 while h:
440 d, n = heapq.heappop(h)
438 d, n = heapq.heappop(h)
441 if n not in seen:
439 if n not in seen:
442 seen[n] = 1
440 seen[n] = 1
443 r = self.rev(n)
441 r = self.rev(n)
444 yield (-d, r, n)
442 yield (-d, r, n)
445 for p in self.parents(n):
443 for p in self.parents(n):
446 heapq.heappush(h, (-dist[p], p))
444 heapq.heappush(h, (-dist[p], p))
447
445
448 x = ancestors(a)
446 x = ancestors(a)
449 y = ancestors(b)
447 y = ancestors(b)
450 lx = x.next()
448 lx = x.next()
451 ly = y.next()
449 ly = y.next()
452
450
453 # increment each ancestor list until it is closer to root than
451 # increment each ancestor list until it is closer to root than
454 # the other, or they match
452 # the other, or they match
455 while 1:
453 while 1:
456 if lx == ly:
454 if lx == ly:
457 return lx[2]
455 return lx[2]
458 elif lx < ly:
456 elif lx < ly:
459 ly = y.next()
457 ly = y.next()
460 elif lx > ly:
458 elif lx > ly:
461 lx = x.next()
459 lx = x.next()
462
460
463 def group(self, linkmap):
461 def group(self, linkmap):
464 """calculate a delta group
462 """calculate a delta group
465
463
466 Given a list of changeset revs, return a set of deltas and
464 Given a list of changeset revs, return a set of deltas and
467 metadata corresponding to nodes. the first delta is
465 metadata corresponding to nodes. the first delta is
468 parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
466 parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
469 have this parent as it has all history before these
467 have this parent as it has all history before these
470 changesets. parent is parent[0]
468 changesets. parent is parent[0]
471 """
469 """
472 revs = []
470 revs = []
473 needed = {}
471 needed = {}
474
472
475 # find file nodes/revs that match changeset revs
473 # find file nodes/revs that match changeset revs
476 for i in xrange(0, self.count()):
474 for i in xrange(0, self.count()):
477 if self.index[i][3] in linkmap:
475 if self.index[i][3] in linkmap:
478 revs.append(i)
476 revs.append(i)
479 needed[i] = 1
477 needed[i] = 1
480
478
481 # if we don't have any revisions touched by these changesets, bail
479 # if we don't have any revisions touched by these changesets, bail
482 if not revs:
480 if not revs:
483 yield struct.pack(">l", 0)
481 yield struct.pack(">l", 0)
484 return
482 return
485
483
486 # add the parent of the first rev
484 # add the parent of the first rev
487 p = self.parents(self.node(revs[0]))[0]
485 p = self.parents(self.node(revs[0]))[0]
488 revs.insert(0, self.rev(p))
486 revs.insert(0, self.rev(p))
489
487
490 # for each delta that isn't contiguous in the log, we need to
488 # for each delta that isn't contiguous in the log, we need to
491 # reconstruct the base, reconstruct the result, and then
489 # reconstruct the base, reconstruct the result, and then
492 # calculate the delta. We also need to do this where we've
490 # calculate the delta. We also need to do this where we've
493 # stored a full version and not a delta
491 # stored a full version and not a delta
494 for i in xrange(0, len(revs) - 1):
492 for i in xrange(0, len(revs) - 1):
495 a, b = revs[i], revs[i + 1]
493 a, b = revs[i], revs[i + 1]
496 if a + 1 != b or self.base(b) == b:
494 if a + 1 != b or self.base(b) == b:
497 for j in xrange(self.base(a), a + 1):
495 for j in xrange(self.base(a), a + 1):
498 needed[j] = 1
496 needed[j] = 1
499 for j in xrange(self.base(b), b + 1):
497 for j in xrange(self.base(b), b + 1):
500 needed[j] = 1
498 needed[j] = 1
501
499
502 # calculate spans to retrieve from datafile
500 # calculate spans to retrieve from datafile
503 needed = needed.keys()
501 needed = needed.keys()
504 needed.sort()
502 needed.sort()
505 spans = []
503 spans = []
506 oo = -1
504 oo = -1
507 ol = 0
505 ol = 0
508 for n in needed:
506 for n in needed:
509 if n < 0: continue
507 if n < 0: continue
510 o = self.start(n)
508 o = self.start(n)
511 l = self.length(n)
509 l = self.length(n)
512 if oo + ol == o: # can we merge with the previous?
510 if oo + ol == o: # can we merge with the previous?
513 nl = spans[-1][2]
511 nl = spans[-1][2]
514 nl.append((n, l))
512 nl.append((n, l))
515 ol += l
513 ol += l
516 spans[-1] = (oo, ol, nl)
514 spans[-1] = (oo, ol, nl)
517 else:
515 else:
518 oo = o
516 oo = o
519 ol = l
517 ol = l
520 spans.append((oo, ol, [(n, l)]))
518 spans.append((oo, ol, [(n, l)]))
521
519
522 # read spans in, divide up chunks
520 # read spans in, divide up chunks
523 chunks = {}
521 chunks = {}
524 for span in spans:
522 for span in spans:
525 # we reopen the file for each span to make http happy for now
523 # we reopen the file for each span to make http happy for now
526 f = self.opener(self.datafile)
524 f = self.opener(self.datafile)
527 f.seek(span[0])
525 f.seek(span[0])
528 data = f.read(span[1])
526 data = f.read(span[1])
529
527
530 # divide up the span
528 # divide up the span
531 pos = 0
529 pos = 0
532 for r, l in span[2]:
530 for r, l in span[2]:
533 chunks[r] = decompress(data[pos: pos + l])
531 chunks[r] = decompress(data[pos: pos + l])
534 pos += l
532 pos += l
535
533
536 # helper to reconstruct intermediate versions
534 # helper to reconstruct intermediate versions
537 def construct(text, base, rev):
535 def construct(text, base, rev):
538 bins = [chunks[r] for r in xrange(base + 1, rev + 1)]
536 bins = [chunks[r] for r in xrange(base + 1, rev + 1)]
539 return mdiff.patches(text, bins)
537 return mdiff.patches(text, bins)
540
538
541 # build deltas
539 # build deltas
542 deltas = []
540 deltas = []
543 for d in xrange(0, len(revs) - 1):
541 for d in xrange(0, len(revs) - 1):
544 a, b = revs[d], revs[d + 1]
542 a, b = revs[d], revs[d + 1]
545 n = self.node(b)
543 n = self.node(b)
546
544
547 # do we need to construct a new delta?
545 # do we need to construct a new delta?
548 if a + 1 != b or self.base(b) == b:
546 if a + 1 != b or self.base(b) == b:
549 if a >= 0:
547 if a >= 0:
550 base = self.base(a)
548 base = self.base(a)
551 ta = chunks[self.base(a)]
549 ta = chunks[self.base(a)]
552 ta = construct(ta, base, a)
550 ta = construct(ta, base, a)
553 else:
551 else:
554 ta = ""
552 ta = ""
555
553
556 base = self.base(b)
554 base = self.base(b)
557 if a > base:
555 if a > base:
558 base = a
556 base = a
559 tb = ta
557 tb = ta
560 else:
558 else:
561 tb = chunks[self.base(b)]
559 tb = chunks[self.base(b)]
562 tb = construct(tb, base, b)
560 tb = construct(tb, base, b)
563 d = self.diff(ta, tb)
561 d = self.diff(ta, tb)
564 else:
562 else:
565 d = chunks[b]
563 d = chunks[b]
566
564
567 p = self.parents(n)
565 p = self.parents(n)
568 meta = n + p[0] + p[1] + linkmap[self.linkrev(n)]
566 meta = n + p[0] + p[1] + linkmap[self.linkrev(n)]
569 l = struct.pack(">l", len(meta) + len(d) + 4)
567 l = struct.pack(">l", len(meta) + len(d) + 4)
570 yield l
568 yield l
571 yield meta
569 yield meta
572 yield d
570 yield d
573
571
574 yield struct.pack(">l", 0)
572 yield struct.pack(">l", 0)
575
573
576 def addgroup(self, revs, linkmapper, transaction, unique=0):
574 def addgroup(self, revs, linkmapper, transaction, unique=0):
577 """
575 """
578 add a delta group
576 add a delta group
579
577
580 given a set of deltas, add them to the revision log. the
578 given a set of deltas, add them to the revision log. the
581 first delta is against its parent, which should be in our
579 first delta is against its parent, which should be in our
582 log, the rest are against the previous delta.
580 log, the rest are against the previous delta.
583 """
581 """
584
582
585 #track the base of the current delta log
583 #track the base of the current delta log
586 r = self.count()
584 r = self.count()
587 t = r - 1
585 t = r - 1
588 node = nullid
586 node = nullid
589
587
590 base = prev = -1
588 base = prev = -1
591 start = end = measure = 0
589 start = end = measure = 0
592 if r:
590 if r:
593 start = self.start(self.base(t))
591 start = self.start(self.base(t))
594 end = self.end(t)
592 end = self.end(t)
595 measure = self.length(self.base(t))
593 measure = self.length(self.base(t))
596 base = self.base(t)
594 base = self.base(t)
597 prev = self.tip()
595 prev = self.tip()
598
596
599 transaction.add(self.datafile, end)
597 transaction.add(self.datafile, end)
600 transaction.add(self.indexfile, r * struct.calcsize(indexformat))
598 transaction.add(self.indexfile, r * struct.calcsize(indexformat))
601 dfh = self.opener(self.datafile, "a")
599 dfh = self.opener(self.datafile, "a")
602 ifh = self.opener(self.indexfile, "a")
600 ifh = self.opener(self.indexfile, "a")
603
601
604 # loop through our set of deltas
602 # loop through our set of deltas
605 chain = None
603 chain = None
606 for chunk in revs:
604 for chunk in revs:
607 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
605 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
608 link = linkmapper(cs)
606 link = linkmapper(cs)
609 if node in self.nodemap:
607 if node in self.nodemap:
610 # this can happen if two branches make the same change
608 # this can happen if two branches make the same change
611 # if unique:
609 # if unique:
612 # raise RevlogError("already have %s" % hex(node[:4]))
610 # raise RevlogError("already have %s" % hex(node[:4]))
613 chain = node
611 chain = node
614 continue
612 continue
615 delta = chunk[80:]
613 delta = chunk[80:]
616
614
617 if not chain:
615 if not chain:
618 # retrieve the parent revision of the delta chain
616 # retrieve the parent revision of the delta chain
619 chain = p1
617 chain = p1
620 if not chain in self.nodemap:
618 if not chain in self.nodemap:
621 raise RevlogError("unknown base %s" % short(chain[:4]))
619 raise RevlogError("unknown base %s" % short(chain[:4]))
622
620
623 # full versions are inserted when the needed deltas become
621 # full versions are inserted when the needed deltas become
624 # comparable to the uncompressed text or when the previous
622 # comparable to the uncompressed text or when the previous
625 # version is not the one we have a delta against. We use
623 # version is not the one we have a delta against. We use
626 # the size of the previous full rev as a proxy for the
624 # the size of the previous full rev as a proxy for the
627 # current size.
625 # current size.
628
626
629 if chain == prev:
627 if chain == prev:
630 cdelta = compress(delta)
628 cdelta = compress(delta)
631
629
632 if chain != prev or (end - start + len(cdelta)) > measure * 2:
630 if chain != prev or (end - start + len(cdelta)) > measure * 2:
633 # flush our writes here so we can read it in revision
631 # flush our writes here so we can read it in revision
634 dfh.flush()
632 dfh.flush()
635 ifh.flush()
633 ifh.flush()
636 text = self.revision(chain)
634 text = self.revision(chain)
637 text = self.patches(text, [delta])
635 text = self.patches(text, [delta])
638 chk = self.addrevision(text, transaction, link, p1, p2)
636 chk = self.addrevision(text, transaction, link, p1, p2)
639 if chk != node:
637 if chk != node:
640 raise RevlogError("consistency error adding group")
638 raise RevlogError("consistency error adding group")
641 measure = len(text)
639 measure = len(text)
642 else:
640 else:
643 e = (end, len(cdelta), self.base(t), link, p1, p2, node)
641 e = (end, len(cdelta), self.base(t), link, p1, p2, node)
644 self.index.append(e)
642 self.index.append(e)
645 self.nodemap[node] = r
643 self.nodemap[node] = r
646 dfh.write(cdelta)
644 dfh.write(cdelta)
647 ifh.write(struct.pack(indexformat, *e))
645 ifh.write(struct.pack(indexformat, *e))
648
646
649 t, r, chain, prev = r, r + 1, node, node
647 t, r, chain, prev = r, r + 1, node, node
650 start = self.start(self.base(t))
648 start = self.start(self.base(t))
651 end = self.end(t)
649 end = self.end(t)
652
650
653 dfh.close()
651 dfh.close()
654 ifh.close()
652 ifh.close()
655 return node
653 return node
@@ -1,35 +1,43 b''
1 # statichttprepo.py - simple http repository class for mercurial
1 # statichttprepo.py - simple http repository class for mercurial
2 #
2 #
3 # This provides read-only repo access to repositories exported via static http
3 # This provides read-only repo access to repositories exported via static http
4 #
4 #
5 # Copyright 2005 Matt Mackall <mpm@selenic.com>
5 # Copyright 2005 Matt Mackall <mpm@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms
7 # This software may be used and distributed according to the terms
8 # of the GNU General Public License, incorporated herein by reference.
8 # of the GNU General Public License, incorporated herein by reference.
9
9
10 import os, urllib
10 from demandload import demandload
11 import localrepo, httprangereader, filelog, manifest, changelog
11 demandload(globals(), "changelog filelog httprangereader")
12 demandload(globals(), "localrepo manifest os urllib urllib2")
13
14 class rangereader(httprangereader.httprangereader):
15 def read(self, size=None):
16 try:
17 return httprangereader.httprangereader.read(self, size)
18 except urllib2.URLError, inst:
19 raise IOError(None, str(inst))
12
20
13 def opener(base):
21 def opener(base):
14 """return a function that opens files over http"""
22 """return a function that opens files over http"""
15 p = base
23 p = base
16 def o(path, mode="r"):
24 def o(path, mode="r"):
17 f = os.path.join(p, urllib.quote(path))
25 f = os.path.join(p, urllib.quote(path))
18 return httprangereader.httprangereader(f)
26 return rangereader(f)
19 return o
27 return o
20
28
21 class statichttprepository(localrepo.localrepository):
29 class statichttprepository(localrepo.localrepository):
22 def __init__(self, ui, path):
30 def __init__(self, ui, path):
23 self.path = (path + "/.hg")
31 self.path = (path + "/.hg")
24 self.ui = ui
32 self.ui = ui
25 self.opener = opener(self.path)
33 self.opener = opener(self.path)
26 self.manifest = manifest.manifest(self.opener)
34 self.manifest = manifest.manifest(self.opener)
27 self.changelog = changelog.changelog(self.opener)
35 self.changelog = changelog.changelog(self.opener)
28 self.tagscache = None
36 self.tagscache = None
29 self.nodetagscache = None
37 self.nodetagscache = None
30
38
31 def dev(self):
39 def dev(self):
32 return -1
40 return -1
33
41
34 def local(self):
42 def local(self):
35 return False
43 return False
General Comments 0
You need to be logged in to leave comments. Login now