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