##// END OF EJS Templates
pep-0008 cleanup...
benoit.boissinot@ens-lyon.fr -
r1062:6d5a62a5 default
parent child Browse files
Show More
@@ -1,1838 +1,1838
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 demandload(globals(), "os re sys signal shutil")
9 demandload(globals(), "os re sys signal shutil")
10 demandload(globals(), "fancyopts ui hg util lock")
10 demandload(globals(), "fancyopts ui hg util lock")
11 demandload(globals(), "fnmatch hgweb mdiff random signal time traceback")
11 demandload(globals(), "fnmatch hgweb mdiff random signal time traceback")
12 demandload(globals(), "errno socket version struct atexit sets")
12 demandload(globals(), "errno socket version struct atexit sets")
13
13
14 class UnknownCommand(Exception):
14 class UnknownCommand(Exception):
15 """Exception raised if command is not in the command table."""
15 """Exception raised if command is not in the command table."""
16
16
17 def filterfiles(filters, files):
17 def filterfiles(filters, files):
18 l = [x for x in files if x in filters]
18 l = [x for x in files if x in filters]
19
19
20 for t in filters:
20 for t in filters:
21 if t and t[-1] != "/":
21 if t and t[-1] != "/":
22 t += "/"
22 t += "/"
23 l += [x for x in files if x.startswith(t)]
23 l += [x for x in files if x.startswith(t)]
24 return l
24 return l
25
25
26 def relpath(repo, args):
26 def relpath(repo, args):
27 cwd = repo.getcwd()
27 cwd = repo.getcwd()
28 if cwd:
28 if cwd:
29 return [util.normpath(os.path.join(cwd, x)) for x in args]
29 return [util.normpath(os.path.join(cwd, x)) for x in args]
30 return args
30 return args
31
31
32 def matchpats(repo, cwd, pats = [], opts = {}, head = ''):
32 def matchpats(repo, cwd, pats=[], opts={}, head=''):
33 return util.matcher(repo, cwd, pats or ['.'], opts.get('include'),
33 return util.matcher(repo, cwd, pats or ['.'], opts.get('include'),
34 opts.get('exclude'), head)
34 opts.get('exclude'), head)
35
35
36 def makewalk(repo, pats, opts, head = ''):
36 def makewalk(repo, pats, opts, head=''):
37 cwd = repo.getcwd()
37 cwd = repo.getcwd()
38 files, matchfn, anypats = matchpats(repo, cwd, pats, opts, head)
38 files, matchfn, anypats = matchpats(repo, cwd, pats, opts, head)
39 exact = dict(zip(files, files))
39 exact = dict(zip(files, files))
40 def walk():
40 def walk():
41 for src, fn in repo.walk(files = files, match = matchfn):
41 for src, fn in repo.walk(files=files, match=matchfn):
42 yield src, fn, util.pathto(cwd, fn), fn in exact
42 yield src, fn, util.pathto(cwd, fn), fn in exact
43 return files, matchfn, walk()
43 return files, matchfn, walk()
44
44
45 def walk(repo, pats, opts, head = ''):
45 def walk(repo, pats, opts, head=''):
46 files, matchfn, results = makewalk(repo, pats, opts, head)
46 files, matchfn, results = makewalk(repo, pats, opts, head)
47 for r in results: yield r
47 for r in results: yield r
48
48
49 def walkchangerevs(ui, repo, cwd, pats, opts):
49 def walkchangerevs(ui, repo, cwd, pats, opts):
50 # This code most commonly needs to iterate backwards over the
50 # This code most commonly needs to iterate backwards over the
51 # history it is interested in. Doing so has awful
51 # history it is interested in. Doing so has awful
52 # (quadratic-looking) performance, so we use iterators in a
52 # (quadratic-looking) performance, so we use iterators in a
53 # "windowed" way. Walk forwards through a window of revisions,
53 # "windowed" way. Walk forwards through a window of revisions,
54 # yielding them in the desired order, and walk the windows
54 # yielding them in the desired order, and walk the windows
55 # themselves backwards.
55 # themselves backwards.
56 cwd = repo.getcwd()
56 cwd = repo.getcwd()
57 if not pats and cwd:
57 if not pats and cwd:
58 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
58 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
59 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
59 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
60 files, matchfn, anypats = matchpats(repo, (pats and cwd) or '',
60 files, matchfn, anypats = matchpats(repo, (pats and cwd) or '',
61 pats, opts)
61 pats, opts)
62 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
62 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
63 wanted = {}
63 wanted = {}
64 slowpath = anypats
64 slowpath = anypats
65 window = 300
65 window = 300
66 fncache = {}
66 fncache = {}
67 if not slowpath and not files:
67 if not slowpath and not files:
68 # No files, no patterns. Display all revs.
68 # No files, no patterns. Display all revs.
69 wanted = dict(zip(revs, revs))
69 wanted = dict(zip(revs, revs))
70 if not slowpath:
70 if not slowpath:
71 # Only files, no patterns. Check the history of each file.
71 # Only files, no patterns. Check the history of each file.
72 def filerevgen(filelog):
72 def filerevgen(filelog):
73 for i in xrange(filelog.count() - 1, -1, -window):
73 for i in xrange(filelog.count() - 1, -1, -window):
74 revs = []
74 revs = []
75 for j in xrange(max(0, i - window), i + 1):
75 for j in xrange(max(0, i - window), i + 1):
76 revs.append(filelog.linkrev(filelog.node(j)))
76 revs.append(filelog.linkrev(filelog.node(j)))
77 revs.reverse()
77 revs.reverse()
78 for rev in revs:
78 for rev in revs:
79 yield rev
79 yield rev
80
80
81 minrev, maxrev = min(revs), max(revs)
81 minrev, maxrev = min(revs), max(revs)
82 for file in files:
82 for file in files:
83 filelog = repo.file(file)
83 filelog = repo.file(file)
84 # A zero count may be a directory or deleted file, so
84 # A zero count may be a directory or deleted file, so
85 # try to find matching entries on the slow path.
85 # try to find matching entries on the slow path.
86 if filelog.count() == 0:
86 if filelog.count() == 0:
87 slowpath = True
87 slowpath = True
88 break
88 break
89 for rev in filerevgen(filelog):
89 for rev in filerevgen(filelog):
90 if rev <= maxrev:
90 if rev <= maxrev:
91 if rev < minrev: break
91 if rev < minrev: break
92 fncache.setdefault(rev, [])
92 fncache.setdefault(rev, [])
93 fncache[rev].append(file)
93 fncache[rev].append(file)
94 wanted[rev] = 1
94 wanted[rev] = 1
95 if slowpath:
95 if slowpath:
96 # The slow path checks files modified in every changeset.
96 # The slow path checks files modified in every changeset.
97 def changerevgen():
97 def changerevgen():
98 for i in xrange(repo.changelog.count() - 1, -1, -window):
98 for i in xrange(repo.changelog.count() - 1, -1, -window):
99 for j in xrange(max(0, i - window), i + 1):
99 for j in xrange(max(0, i - window), i + 1):
100 yield j, repo.changelog.read(repo.lookup(str(j)))[3]
100 yield j, repo.changelog.read(repo.lookup(str(j)))[3]
101
101
102 for rev, changefiles in changerevgen():
102 for rev, changefiles in changerevgen():
103 matches = filter(matchfn, changefiles)
103 matches = filter(matchfn, changefiles)
104 if matches:
104 if matches:
105 fncache[rev] = matches
105 fncache[rev] = matches
106 wanted[rev] = 1
106 wanted[rev] = 1
107
107
108 for i in xrange(0, len(revs), window):
108 for i in xrange(0, len(revs), window):
109 yield 'window', revs[0] < revs[-1], revs[-1]
109 yield 'window', revs[0] < revs[-1], revs[-1]
110 nrevs = [rev for rev in revs[i : min(i + window, len(revs))]
110 nrevs = [rev for rev in revs[i : min(i + window, len(revs))]
111 if rev in wanted]
111 if rev in wanted]
112 srevs = list(nrevs)
112 srevs = list(nrevs)
113 srevs.sort()
113 srevs.sort()
114 for rev in srevs:
114 for rev in srevs:
115 fns = fncache.get(rev)
115 fns = fncache.get(rev)
116 if not fns:
116 if not fns:
117 fns = repo.changelog.read(repo.lookup(str(rev)))[3]
117 fns = repo.changelog.read(repo.lookup(str(rev)))[3]
118 fns = filter(matchfn, fns)
118 fns = filter(matchfn, fns)
119 yield 'add', rev, fns
119 yield 'add', rev, fns
120 for rev in nrevs:
120 for rev in nrevs:
121 yield 'iter', rev, None
121 yield 'iter', rev, None
122
122
123 revrangesep = ':'
123 revrangesep = ':'
124
124
125 def revrange(ui, repo, revs, revlog=None):
125 def revrange(ui, repo, revs, revlog=None):
126 if revlog is None:
126 if revlog is None:
127 revlog = repo.changelog
127 revlog = repo.changelog
128 revcount = revlog.count()
128 revcount = revlog.count()
129 def fix(val, defval):
129 def fix(val, defval):
130 if not val:
130 if not val:
131 return defval
131 return defval
132 try:
132 try:
133 num = int(val)
133 num = int(val)
134 if str(num) != val:
134 if str(num) != val:
135 raise ValueError
135 raise ValueError
136 if num < 0:
136 if num < 0:
137 num += revcount
137 num += revcount
138 if not (0 <= num < revcount):
138 if not (0 <= num < revcount):
139 raise ValueError
139 raise ValueError
140 except ValueError:
140 except ValueError:
141 try:
141 try:
142 num = repo.changelog.rev(repo.lookup(val))
142 num = repo.changelog.rev(repo.lookup(val))
143 except KeyError:
143 except KeyError:
144 try:
144 try:
145 num = revlog.rev(revlog.lookup(val))
145 num = revlog.rev(revlog.lookup(val))
146 except KeyError:
146 except KeyError:
147 raise util.Abort('invalid revision identifier %s', val)
147 raise util.Abort('invalid revision identifier %s', val)
148 return num
148 return num
149 for spec in revs:
149 for spec in revs:
150 if spec.find(revrangesep) >= 0:
150 if spec.find(revrangesep) >= 0:
151 start, end = spec.split(revrangesep, 1)
151 start, end = spec.split(revrangesep, 1)
152 start = fix(start, 0)
152 start = fix(start, 0)
153 end = fix(end, revcount - 1)
153 end = fix(end, revcount - 1)
154 if end > start:
154 if end > start:
155 end += 1
155 end += 1
156 step = 1
156 step = 1
157 else:
157 else:
158 end -= 1
158 end -= 1
159 step = -1
159 step = -1
160 for rev in xrange(start, end, step):
160 for rev in xrange(start, end, step):
161 yield str(rev)
161 yield str(rev)
162 else:
162 else:
163 yield str(fix(spec, None))
163 yield str(fix(spec, None))
164
164
165 def make_filename(repo, r, pat, node=None,
165 def make_filename(repo, r, pat, node=None,
166 total=None, seqno=None, revwidth=None):
166 total=None, seqno=None, revwidth=None):
167 node_expander = {
167 node_expander = {
168 'H': lambda: hg.hex(node),
168 'H': lambda: hg.hex(node),
169 'R': lambda: str(r.rev(node)),
169 'R': lambda: str(r.rev(node)),
170 'h': lambda: hg.short(node),
170 'h': lambda: hg.short(node),
171 }
171 }
172 expander = {
172 expander = {
173 '%': lambda: '%',
173 '%': lambda: '%',
174 'b': lambda: os.path.basename(repo.root),
174 'b': lambda: os.path.basename(repo.root),
175 }
175 }
176
176
177 try:
177 try:
178 if node:
178 if node:
179 expander.update(node_expander)
179 expander.update(node_expander)
180 if node and revwidth is not None:
180 if node and revwidth is not None:
181 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
181 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
182 if total is not None:
182 if total is not None:
183 expander['N'] = lambda: str(total)
183 expander['N'] = lambda: str(total)
184 if seqno is not None:
184 if seqno is not None:
185 expander['n'] = lambda: str(seqno)
185 expander['n'] = lambda: str(seqno)
186 if total is not None and seqno is not None:
186 if total is not None and seqno is not None:
187 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
187 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
188
188
189 newname = []
189 newname = []
190 patlen = len(pat)
190 patlen = len(pat)
191 i = 0
191 i = 0
192 while i < patlen:
192 while i < patlen:
193 c = pat[i]
193 c = pat[i]
194 if c == '%':
194 if c == '%':
195 i += 1
195 i += 1
196 c = pat[i]
196 c = pat[i]
197 c = expander[c]()
197 c = expander[c]()
198 newname.append(c)
198 newname.append(c)
199 i += 1
199 i += 1
200 return ''.join(newname)
200 return ''.join(newname)
201 except KeyError, inst:
201 except KeyError, inst:
202 raise util.Abort("invalid format spec '%%%s' in output file name",
202 raise util.Abort("invalid format spec '%%%s' in output file name",
203 inst.args[0])
203 inst.args[0])
204
204
205 def make_file(repo, r, pat, node=None,
205 def make_file(repo, r, pat, node=None,
206 total=None, seqno=None, revwidth=None, mode='wb'):
206 total=None, seqno=None, revwidth=None, mode='wb'):
207 if not pat or pat == '-':
207 if not pat or pat == '-':
208 if 'w' in mode: return sys.stdout
208 if 'w' in mode: return sys.stdout
209 else: return sys.stdin
209 else: return sys.stdin
210 if hasattr(pat, 'write') and 'w' in mode:
210 if hasattr(pat, 'write') and 'w' in mode:
211 return pat
211 return pat
212 if hasattr(pat, 'read') and 'r' in mode:
212 if hasattr(pat, 'read') and 'r' in mode:
213 return pat
213 return pat
214 return open(make_filename(repo, r, pat, node, total, seqno, revwidth),
214 return open(make_filename(repo, r, pat, node, total, seqno, revwidth),
215 mode)
215 mode)
216
216
217 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
217 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
218 changes=None, text=False):
218 changes=None, text=False):
219 def date(c):
219 def date(c):
220 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
220 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
221
221
222 if not changes:
222 if not changes:
223 (c, a, d, u) = repo.changes(node1, node2, files, match = match)
223 (c, a, d, u) = repo.changes(node1, node2, files, match=match)
224 else:
224 else:
225 (c, a, d, u) = changes
225 (c, a, d, u) = changes
226 if files:
226 if files:
227 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
227 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
228
228
229 if not c and not a and not d:
229 if not c and not a and not d:
230 return
230 return
231
231
232 if node2:
232 if node2:
233 change = repo.changelog.read(node2)
233 change = repo.changelog.read(node2)
234 mmap2 = repo.manifest.read(change[0])
234 mmap2 = repo.manifest.read(change[0])
235 date2 = date(change)
235 date2 = date(change)
236 def read(f):
236 def read(f):
237 return repo.file(f).read(mmap2[f])
237 return repo.file(f).read(mmap2[f])
238 else:
238 else:
239 date2 = time.asctime()
239 date2 = time.asctime()
240 if not node1:
240 if not node1:
241 node1 = repo.dirstate.parents()[0]
241 node1 = repo.dirstate.parents()[0]
242 def read(f):
242 def read(f):
243 return repo.wfile(f).read()
243 return repo.wfile(f).read()
244
244
245 if ui.quiet:
245 if ui.quiet:
246 r = None
246 r = None
247 else:
247 else:
248 hexfunc = ui.verbose and hg.hex or hg.short
248 hexfunc = ui.verbose and hg.hex or hg.short
249 r = [hexfunc(node) for node in [node1, node2] if node]
249 r = [hexfunc(node) for node in [node1, node2] if node]
250
250
251 change = repo.changelog.read(node1)
251 change = repo.changelog.read(node1)
252 mmap = repo.manifest.read(change[0])
252 mmap = repo.manifest.read(change[0])
253 date1 = date(change)
253 date1 = date(change)
254
254
255 for f in c:
255 for f in c:
256 to = None
256 to = None
257 if f in mmap:
257 if f in mmap:
258 to = repo.file(f).read(mmap[f])
258 to = repo.file(f).read(mmap[f])
259 tn = read(f)
259 tn = read(f)
260 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
260 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
261 for f in a:
261 for f in a:
262 to = None
262 to = None
263 tn = read(f)
263 tn = read(f)
264 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
264 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
265 for f in d:
265 for f in d:
266 to = repo.file(f).read(mmap[f])
266 to = repo.file(f).read(mmap[f])
267 tn = None
267 tn = None
268 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
268 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
269
269
270 def show_changeset(ui, repo, rev=0, changenode=None, brinfo=None):
270 def show_changeset(ui, repo, rev=0, changenode=None, brinfo=None):
271 """show a single changeset or file revision"""
271 """show a single changeset or file revision"""
272 log = repo.changelog
272 log = repo.changelog
273 if changenode is None:
273 if changenode is None:
274 changenode = log.node(rev)
274 changenode = log.node(rev)
275 elif not rev:
275 elif not rev:
276 rev = log.rev(changenode)
276 rev = log.rev(changenode)
277
277
278 if ui.quiet:
278 if ui.quiet:
279 ui.write("%d:%s\n" % (rev, hg.short(changenode)))
279 ui.write("%d:%s\n" % (rev, hg.short(changenode)))
280 return
280 return
281
281
282 changes = log.read(changenode)
282 changes = log.read(changenode)
283
283
284 t, tz = changes[2].split(' ')
284 t, tz = changes[2].split(' ')
285 # a conversion tool was sticking non-integer offsets into repos
285 # a conversion tool was sticking non-integer offsets into repos
286 try:
286 try:
287 tz = int(tz)
287 tz = int(tz)
288 except ValueError:
288 except ValueError:
289 tz = 0
289 tz = 0
290 date = time.asctime(time.localtime(float(t))) + " %+05d" % (int(tz)/-36)
290 date = time.asctime(time.localtime(float(t))) + " %+05d" % (int(tz)/-36)
291
291
292 parents = [(log.rev(p), ui.verbose and hg.hex(p) or hg.short(p))
292 parents = [(log.rev(p), ui.verbose and hg.hex(p) or hg.short(p))
293 for p in log.parents(changenode)
293 for p in log.parents(changenode)
294 if ui.debugflag or p != hg.nullid]
294 if ui.debugflag or p != hg.nullid]
295 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
295 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
296 parents = []
296 parents = []
297
297
298 if ui.verbose:
298 if ui.verbose:
299 ui.write("changeset: %d:%s\n" % (rev, hg.hex(changenode)))
299 ui.write("changeset: %d:%s\n" % (rev, hg.hex(changenode)))
300 else:
300 else:
301 ui.write("changeset: %d:%s\n" % (rev, hg.short(changenode)))
301 ui.write("changeset: %d:%s\n" % (rev, hg.short(changenode)))
302
302
303 for tag in repo.nodetags(changenode):
303 for tag in repo.nodetags(changenode):
304 ui.status("tag: %s\n" % tag)
304 ui.status("tag: %s\n" % tag)
305 for parent in parents:
305 for parent in parents:
306 ui.write("parent: %d:%s\n" % parent)
306 ui.write("parent: %d:%s\n" % parent)
307
307
308 if brinfo and changenode in brinfo:
308 if brinfo and changenode in brinfo:
309 br = brinfo[changenode]
309 br = brinfo[changenode]
310 ui.write("branch: %s\n" % " ".join(br))
310 ui.write("branch: %s\n" % " ".join(br))
311
311
312 ui.debug("manifest: %d:%s\n" % (repo.manifest.rev(changes[0]),
312 ui.debug("manifest: %d:%s\n" % (repo.manifest.rev(changes[0]),
313 hg.hex(changes[0])))
313 hg.hex(changes[0])))
314 ui.status("user: %s\n" % changes[1])
314 ui.status("user: %s\n" % changes[1])
315 ui.status("date: %s\n" % date)
315 ui.status("date: %s\n" % date)
316
316
317 if ui.debugflag:
317 if ui.debugflag:
318 files = repo.changes(log.parents(changenode)[0], changenode)
318 files = repo.changes(log.parents(changenode)[0], changenode)
319 for key, value in zip(["files:", "files+:", "files-:"], files):
319 for key, value in zip(["files:", "files+:", "files-:"], files):
320 if value:
320 if value:
321 ui.note("%-12s %s\n" % (key, " ".join(value)))
321 ui.note("%-12s %s\n" % (key, " ".join(value)))
322 else:
322 else:
323 ui.note("files: %s\n" % " ".join(changes[3]))
323 ui.note("files: %s\n" % " ".join(changes[3]))
324
324
325 description = changes[4].strip()
325 description = changes[4].strip()
326 if description:
326 if description:
327 if ui.verbose:
327 if ui.verbose:
328 ui.status("description:\n")
328 ui.status("description:\n")
329 ui.status(description)
329 ui.status(description)
330 ui.status("\n\n")
330 ui.status("\n\n")
331 else:
331 else:
332 ui.status("summary: %s\n" % description.splitlines()[0])
332 ui.status("summary: %s\n" % description.splitlines()[0])
333 ui.status("\n")
333 ui.status("\n")
334
334
335 def show_version(ui):
335 def show_version(ui):
336 """output version and copyright information"""
336 """output version and copyright information"""
337 ui.write("Mercurial Distributed SCM (version %s)\n"
337 ui.write("Mercurial Distributed SCM (version %s)\n"
338 % version.get_version())
338 % version.get_version())
339 ui.status(
339 ui.status(
340 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
340 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
341 "This is free software; see the source for copying conditions. "
341 "This is free software; see the source for copying conditions. "
342 "There is NO\nwarranty; "
342 "There is NO\nwarranty; "
343 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
343 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
344 )
344 )
345
345
346 def help_(ui, cmd=None, with_version=False):
346 def help_(ui, cmd=None, with_version=False):
347 """show help for a given command or all commands"""
347 """show help for a given command or all commands"""
348 option_lists = []
348 option_lists = []
349 if cmd and cmd != 'shortlist':
349 if cmd and cmd != 'shortlist':
350 if with_version:
350 if with_version:
351 show_version(ui)
351 show_version(ui)
352 ui.write('\n')
352 ui.write('\n')
353 key, i = find(cmd)
353 key, i = find(cmd)
354 # synopsis
354 # synopsis
355 ui.write("%s\n\n" % i[2])
355 ui.write("%s\n\n" % i[2])
356
356
357 # description
357 # description
358 doc = i[0].__doc__
358 doc = i[0].__doc__
359 if ui.quiet:
359 if ui.quiet:
360 doc = doc.splitlines(0)[0]
360 doc = doc.splitlines(0)[0]
361 ui.write("%s\n" % doc.rstrip())
361 ui.write("%s\n" % doc.rstrip())
362
362
363 if not ui.quiet:
363 if not ui.quiet:
364 # aliases
364 # aliases
365 aliases = ', '.join(key.split('|')[1:])
365 aliases = ', '.join(key.split('|')[1:])
366 if aliases:
366 if aliases:
367 ui.write("\naliases: %s\n" % aliases)
367 ui.write("\naliases: %s\n" % aliases)
368
368
369 # options
369 # options
370 if i[1]:
370 if i[1]:
371 option_lists.append(("options", i[1]))
371 option_lists.append(("options", i[1]))
372
372
373 else:
373 else:
374 # program name
374 # program name
375 if ui.verbose or with_version:
375 if ui.verbose or with_version:
376 show_version(ui)
376 show_version(ui)
377 else:
377 else:
378 ui.status("Mercurial Distributed SCM\n")
378 ui.status("Mercurial Distributed SCM\n")
379 ui.status('\n')
379 ui.status('\n')
380
380
381 # list of commands
381 # list of commands
382 if cmd == "shortlist":
382 if cmd == "shortlist":
383 ui.status('basic commands (use "hg help" '
383 ui.status('basic commands (use "hg help" '
384 'for the full list or option "-v" for details):\n\n')
384 'for the full list or option "-v" for details):\n\n')
385 elif ui.verbose:
385 elif ui.verbose:
386 ui.status('list of commands:\n\n')
386 ui.status('list of commands:\n\n')
387 else:
387 else:
388 ui.status('list of commands (use "hg help -v" '
388 ui.status('list of commands (use "hg help -v" '
389 'to show aliases and global options):\n\n')
389 'to show aliases and global options):\n\n')
390
390
391 h = {}
391 h = {}
392 cmds = {}
392 cmds = {}
393 for c, e in table.items():
393 for c, e in table.items():
394 f = c.split("|")[0]
394 f = c.split("|")[0]
395 if cmd == "shortlist" and not f.startswith("^"):
395 if cmd == "shortlist" and not f.startswith("^"):
396 continue
396 continue
397 f = f.lstrip("^")
397 f = f.lstrip("^")
398 if not ui.debugflag and f.startswith("debug"):
398 if not ui.debugflag and f.startswith("debug"):
399 continue
399 continue
400 d = ""
400 d = ""
401 if e[0].__doc__:
401 if e[0].__doc__:
402 d = e[0].__doc__.splitlines(0)[0].rstrip()
402 d = e[0].__doc__.splitlines(0)[0].rstrip()
403 h[f] = d
403 h[f] = d
404 cmds[f]=c.lstrip("^")
404 cmds[f]=c.lstrip("^")
405
405
406 fns = h.keys()
406 fns = h.keys()
407 fns.sort()
407 fns.sort()
408 m = max(map(len, fns))
408 m = max(map(len, fns))
409 for f in fns:
409 for f in fns:
410 if ui.verbose:
410 if ui.verbose:
411 commands = cmds[f].replace("|",", ")
411 commands = cmds[f].replace("|",", ")
412 ui.write(" %s:\n %s\n"%(commands,h[f]))
412 ui.write(" %s:\n %s\n"%(commands,h[f]))
413 else:
413 else:
414 ui.write(' %-*s %s\n' % (m, f, h[f]))
414 ui.write(' %-*s %s\n' % (m, f, h[f]))
415
415
416 # global options
416 # global options
417 if ui.verbose:
417 if ui.verbose:
418 option_lists.append(("global options", globalopts))
418 option_lists.append(("global options", globalopts))
419
419
420 # list all option lists
420 # list all option lists
421 opt_output = []
421 opt_output = []
422 for title, options in option_lists:
422 for title, options in option_lists:
423 opt_output.append(("\n%s:\n" % title, None))
423 opt_output.append(("\n%s:\n" % title, None))
424 for shortopt, longopt, default, desc in options:
424 for shortopt, longopt, default, desc in options:
425 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
425 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
426 longopt and " --%s" % longopt),
426 longopt and " --%s" % longopt),
427 "%s%s" % (desc,
427 "%s%s" % (desc,
428 default and " (default: %s)" % default
428 default and " (default: %s)" % default
429 or "")))
429 or "")))
430
430
431 if opt_output:
431 if opt_output:
432 opts_len = max([len(line[0]) for line in opt_output if line[1]])
432 opts_len = max([len(line[0]) for line in opt_output if line[1]])
433 for first, second in opt_output:
433 for first, second in opt_output:
434 if second:
434 if second:
435 ui.write(" %-*s %s\n" % (opts_len, first, second))
435 ui.write(" %-*s %s\n" % (opts_len, first, second))
436 else:
436 else:
437 ui.write("%s\n" % first)
437 ui.write("%s\n" % first)
438
438
439 # Commands start here, listed alphabetically
439 # Commands start here, listed alphabetically
440
440
441 def add(ui, repo, *pats, **opts):
441 def add(ui, repo, *pats, **opts):
442 '''add the specified files on the next commit'''
442 '''add the specified files on the next commit'''
443 names = []
443 names = []
444 for src, abs, rel, exact in walk(repo, pats, opts):
444 for src, abs, rel, exact in walk(repo, pats, opts):
445 if exact:
445 if exact:
446 names.append(abs)
446 names.append(abs)
447 elif repo.dirstate.state(abs) == '?':
447 elif repo.dirstate.state(abs) == '?':
448 ui.status('adding %s\n' % rel)
448 ui.status('adding %s\n' % rel)
449 names.append(abs)
449 names.append(abs)
450 repo.add(names)
450 repo.add(names)
451
451
452 def addremove(ui, repo, *pats, **opts):
452 def addremove(ui, repo, *pats, **opts):
453 """add all new files, delete all missing files"""
453 """add all new files, delete all missing files"""
454 add, remove = [], []
454 add, remove = [], []
455 for src, abs, rel, exact in walk(repo, pats, opts):
455 for src, abs, rel, exact in walk(repo, pats, opts):
456 if src == 'f' and repo.dirstate.state(abs) == '?':
456 if src == 'f' and repo.dirstate.state(abs) == '?':
457 add.append(abs)
457 add.append(abs)
458 if not exact: ui.status('adding ', rel, '\n')
458 if not exact: ui.status('adding ', rel, '\n')
459 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
459 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
460 remove.append(abs)
460 remove.append(abs)
461 if not exact: ui.status('removing ', rel, '\n')
461 if not exact: ui.status('removing ', rel, '\n')
462 repo.add(add)
462 repo.add(add)
463 repo.remove(remove)
463 repo.remove(remove)
464
464
465 def annotate(ui, repo, *pats, **opts):
465 def annotate(ui, repo, *pats, **opts):
466 """show changeset information per file line"""
466 """show changeset information per file line"""
467 def getnode(rev):
467 def getnode(rev):
468 return hg.short(repo.changelog.node(rev))
468 return hg.short(repo.changelog.node(rev))
469
469
470 def getname(rev):
470 def getname(rev):
471 try:
471 try:
472 return bcache[rev]
472 return bcache[rev]
473 except KeyError:
473 except KeyError:
474 cl = repo.changelog.read(repo.changelog.node(rev))
474 cl = repo.changelog.read(repo.changelog.node(rev))
475 name = cl[1]
475 name = cl[1]
476 f = name.find('@')
476 f = name.find('@')
477 if f >= 0:
477 if f >= 0:
478 name = name[:f]
478 name = name[:f]
479 f = name.find('<')
479 f = name.find('<')
480 if f >= 0:
480 if f >= 0:
481 name = name[f+1:]
481 name = name[f+1:]
482 bcache[rev] = name
482 bcache[rev] = name
483 return name
483 return name
484
484
485 if not pats:
485 if not pats:
486 raise util.Abort('at least one file name or pattern required')
486 raise util.Abort('at least one file name or pattern required')
487
487
488 bcache = {}
488 bcache = {}
489 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
489 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
490 if not opts['user'] and not opts['changeset']:
490 if not opts['user'] and not opts['changeset']:
491 opts['number'] = 1
491 opts['number'] = 1
492
492
493 if opts['rev']:
493 if opts['rev']:
494 node = repo.changelog.lookup(opts['rev'])
494 node = repo.changelog.lookup(opts['rev'])
495 else:
495 else:
496 node = repo.dirstate.parents()[0]
496 node = repo.dirstate.parents()[0]
497 change = repo.changelog.read(node)
497 change = repo.changelog.read(node)
498 mmap = repo.manifest.read(change[0])
498 mmap = repo.manifest.read(change[0])
499
499
500 for src, abs, rel, exact in walk(repo, pats, opts):
500 for src, abs, rel, exact in walk(repo, pats, opts):
501 if abs not in mmap:
501 if abs not in mmap:
502 ui.warn("warning: %s is not in the repository!\n" % rel)
502 ui.warn("warning: %s is not in the repository!\n" % rel)
503 continue
503 continue
504
504
505 f = repo.file(abs)
505 f = repo.file(abs)
506 if not opts['text'] and util.binary(f.read(mmap[abs])):
506 if not opts['text'] and util.binary(f.read(mmap[abs])):
507 ui.write("%s: binary file\n" % rel)
507 ui.write("%s: binary file\n" % rel)
508 continue
508 continue
509
509
510 lines = f.annotate(mmap[abs])
510 lines = f.annotate(mmap[abs])
511 pieces = []
511 pieces = []
512
512
513 for o, f in opmap:
513 for o, f in opmap:
514 if opts[o]:
514 if opts[o]:
515 l = [f(n) for n, dummy in lines]
515 l = [f(n) for n, dummy in lines]
516 if l:
516 if l:
517 m = max(map(len, l))
517 m = max(map(len, l))
518 pieces.append(["%*s" % (m, x) for x in l])
518 pieces.append(["%*s" % (m, x) for x in l])
519
519
520 if pieces:
520 if pieces:
521 for p, l in zip(zip(*pieces), lines):
521 for p, l in zip(zip(*pieces), lines):
522 ui.write("%s: %s" % (" ".join(p), l[1]))
522 ui.write("%s: %s" % (" ".join(p), l[1]))
523
523
524 def cat(ui, repo, file1, rev=None, **opts):
524 def cat(ui, repo, file1, rev=None, **opts):
525 """output the latest or given revision of a file"""
525 """output the latest or given revision of a file"""
526 r = repo.file(relpath(repo, [file1])[0])
526 r = repo.file(relpath(repo, [file1])[0])
527 if rev:
527 if rev:
528 try:
528 try:
529 # assume all revision numbers are for changesets
529 # assume all revision numbers are for changesets
530 n = repo.lookup(rev)
530 n = repo.lookup(rev)
531 change = repo.changelog.read(n)
531 change = repo.changelog.read(n)
532 m = repo.manifest.read(change[0])
532 m = repo.manifest.read(change[0])
533 n = m[relpath(repo, [file1])[0]]
533 n = m[relpath(repo, [file1])[0]]
534 except hg.RepoError, KeyError:
534 except hg.RepoError, KeyError:
535 n = r.lookup(rev)
535 n = r.lookup(rev)
536 else:
536 else:
537 n = r.tip()
537 n = r.tip()
538 fp = make_file(repo, r, opts['output'], node=n)
538 fp = make_file(repo, r, opts['output'], node=n)
539 fp.write(r.read(n))
539 fp.write(r.read(n))
540
540
541 def clone(ui, source, dest=None, **opts):
541 def clone(ui, source, dest=None, **opts):
542 """make a copy of an existing repository"""
542 """make a copy of an existing repository"""
543 if dest is None:
543 if dest is None:
544 dest = os.path.basename(os.path.normpath(source))
544 dest = os.path.basename(os.path.normpath(source))
545
545
546 if os.path.exists(dest):
546 if os.path.exists(dest):
547 ui.warn("abort: destination '%s' already exists\n" % dest)
547 ui.warn("abort: destination '%s' already exists\n" % dest)
548 return 1
548 return 1
549
549
550 dest = os.path.realpath(dest)
550 dest = os.path.realpath(dest)
551
551
552 class Dircleanup:
552 class Dircleanup:
553 def __init__(self, dir_):
553 def __init__(self, dir_):
554 self.rmtree = shutil.rmtree
554 self.rmtree = shutil.rmtree
555 self.dir_ = dir_
555 self.dir_ = dir_
556 os.mkdir(dir_)
556 os.mkdir(dir_)
557 def close(self):
557 def close(self):
558 self.dir_ = None
558 self.dir_ = None
559 def __del__(self):
559 def __del__(self):
560 if self.dir_:
560 if self.dir_:
561 self.rmtree(self.dir_, True)
561 self.rmtree(self.dir_, True)
562
562
563 if opts['ssh']:
563 if opts['ssh']:
564 ui.setconfig("ui", "ssh", opts['ssh'])
564 ui.setconfig("ui", "ssh", opts['ssh'])
565 if opts['remotecmd']:
565 if opts['remotecmd']:
566 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
566 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
567
567
568 d = Dircleanup(dest)
568 d = Dircleanup(dest)
569 source = ui.expandpath(source)
569 source = ui.expandpath(source)
570 abspath = source
570 abspath = source
571 other = hg.repository(ui, source)
571 other = hg.repository(ui, source)
572
572
573 if other.dev() != -1:
573 if other.dev() != -1:
574 abspath = os.path.abspath(source)
574 abspath = os.path.abspath(source)
575 copyfile = (os.stat(dest).st_dev == other.dev()
575 copyfile = (os.stat(dest).st_dev == other.dev()
576 and getattr(os, 'link', None) or shutil.copy2)
576 and getattr(os, 'link', None) or shutil.copy2)
577 if copyfile is not shutil.copy2:
577 if copyfile is not shutil.copy2:
578 ui.note("cloning by hardlink\n")
578 ui.note("cloning by hardlink\n")
579 # we use a lock here because because we're not nicely ordered
579 # we use a lock here because because we're not nicely ordered
580 l = lock.lock(os.path.join(source, ".hg", "lock"))
580 l = lock.lock(os.path.join(source, ".hg", "lock"))
581
581
582 util.copytree(os.path.join(source, ".hg"), os.path.join(dest, ".hg"),
582 util.copytree(os.path.join(source, ".hg"), os.path.join(dest, ".hg"),
583 copyfile)
583 copyfile)
584 try:
584 try:
585 os.unlink(os.path.join(dest, ".hg", "dirstate"))
585 os.unlink(os.path.join(dest, ".hg", "dirstate"))
586 except OSError:
586 except OSError:
587 pass
587 pass
588
588
589 repo = hg.repository(ui, dest)
589 repo = hg.repository(ui, dest)
590
590
591 else:
591 else:
592 repo = hg.repository(ui, dest, create=1)
592 repo = hg.repository(ui, dest, create=1)
593 repo.pull(other)
593 repo.pull(other)
594
594
595 f = repo.opener("hgrc", "w")
595 f = repo.opener("hgrc", "w")
596 f.write("[paths]\n")
596 f.write("[paths]\n")
597 f.write("default = %s\n" % abspath)
597 f.write("default = %s\n" % abspath)
598
598
599 if not opts['noupdate']:
599 if not opts['noupdate']:
600 update(ui, repo)
600 update(ui, repo)
601
601
602 d.close()
602 d.close()
603
603
604 def commit(ui, repo, *pats, **opts):
604 def commit(ui, repo, *pats, **opts):
605 """commit the specified files or all outstanding changes"""
605 """commit the specified files or all outstanding changes"""
606 if opts['text']:
606 if opts['text']:
607 ui.warn("Warning: -t and --text is deprecated,"
607 ui.warn("Warning: -t and --text is deprecated,"
608 " please use -m or --message instead.\n")
608 " please use -m or --message instead.\n")
609 message = opts['message'] or opts['text']
609 message = opts['message'] or opts['text']
610 logfile = opts['logfile']
610 logfile = opts['logfile']
611 if not message and logfile:
611 if not message and logfile:
612 try:
612 try:
613 if logfile == '-':
613 if logfile == '-':
614 message = sys.stdin.read()
614 message = sys.stdin.read()
615 else:
615 else:
616 message = open(logfile).read()
616 message = open(logfile).read()
617 except IOError, why:
617 except IOError, why:
618 ui.warn("Can't read commit message %s: %s\n" % (logfile, why))
618 ui.warn("Can't read commit message %s: %s\n" % (logfile, why))
619
619
620 if opts['addremove']:
620 if opts['addremove']:
621 addremove(ui, repo, *pats, **opts)
621 addremove(ui, repo, *pats, **opts)
622 cwd = repo.getcwd()
622 cwd = repo.getcwd()
623 if not pats and cwd:
623 if not pats and cwd:
624 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
624 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
625 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
625 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
626 fns, match, anypats = matchpats(repo, (pats and repo.getcwd()) or '',
626 fns, match, anypats = matchpats(repo, (pats and repo.getcwd()) or '',
627 pats, opts)
627 pats, opts)
628 if pats:
628 if pats:
629 c, a, d, u = repo.changes(files = fns, match = match)
629 c, a, d, u = repo.changes(files=fns, match=match)
630 files = c + a + [fn for fn in d if repo.dirstate.state(fn) == 'r']
630 files = c + a + [fn for fn in d if repo.dirstate.state(fn) == 'r']
631 else:
631 else:
632 files = []
632 files = []
633 repo.commit(files, message, opts['user'], opts['date'], match)
633 repo.commit(files, message, opts['user'], opts['date'], match)
634
634
635 def copy(ui, repo, source, dest):
635 def copy(ui, repo, source, dest):
636 """mark a file as copied or renamed for the next commit"""
636 """mark a file as copied or renamed for the next commit"""
637 return repo.copy(*relpath(repo, (source, dest)))
637 return repo.copy(*relpath(repo, (source, dest)))
638
638
639 def debugcheckstate(ui, repo):
639 def debugcheckstate(ui, repo):
640 """validate the correctness of the current dirstate"""
640 """validate the correctness of the current dirstate"""
641 parent1, parent2 = repo.dirstate.parents()
641 parent1, parent2 = repo.dirstate.parents()
642 repo.dirstate.read()
642 repo.dirstate.read()
643 dc = repo.dirstate.map
643 dc = repo.dirstate.map
644 keys = dc.keys()
644 keys = dc.keys()
645 keys.sort()
645 keys.sort()
646 m1n = repo.changelog.read(parent1)[0]
646 m1n = repo.changelog.read(parent1)[0]
647 m2n = repo.changelog.read(parent2)[0]
647 m2n = repo.changelog.read(parent2)[0]
648 m1 = repo.manifest.read(m1n)
648 m1 = repo.manifest.read(m1n)
649 m2 = repo.manifest.read(m2n)
649 m2 = repo.manifest.read(m2n)
650 errors = 0
650 errors = 0
651 for f in dc:
651 for f in dc:
652 state = repo.dirstate.state(f)
652 state = repo.dirstate.state(f)
653 if state in "nr" and f not in m1:
653 if state in "nr" and f not in m1:
654 ui.warn("%s in state %s, but not in manifest1\n" % (f, state))
654 ui.warn("%s in state %s, but not in manifest1\n" % (f, state))
655 errors += 1
655 errors += 1
656 if state in "a" and f in m1:
656 if state in "a" and f in m1:
657 ui.warn("%s in state %s, but also in manifest1\n" % (f, state))
657 ui.warn("%s in state %s, but also in manifest1\n" % (f, state))
658 errors += 1
658 errors += 1
659 if state in "m" and f not in m1 and f not in m2:
659 if state in "m" and f not in m1 and f not in m2:
660 ui.warn("%s in state %s, but not in either manifest\n" %
660 ui.warn("%s in state %s, but not in either manifest\n" %
661 (f, state))
661 (f, state))
662 errors += 1
662 errors += 1
663 for f in m1:
663 for f in m1:
664 state = repo.dirstate.state(f)
664 state = repo.dirstate.state(f)
665 if state not in "nrm":
665 if state not in "nrm":
666 ui.warn("%s in manifest1, but listed as state %s" % (f, state))
666 ui.warn("%s in manifest1, but listed as state %s" % (f, state))
667 errors += 1
667 errors += 1
668 if errors:
668 if errors:
669 raise util.Abort(".hg/dirstate inconsistent with current parent's manifest")
669 raise util.Abort(".hg/dirstate inconsistent with current parent's manifest")
670
670
671 def debugconfig(ui):
671 def debugconfig(ui):
672 """show combined config settings from all hgrc files"""
672 """show combined config settings from all hgrc files"""
673 try:
673 try:
674 repo = hg.repository(ui)
674 repo = hg.repository(ui)
675 except: pass
675 except: pass
676 for section, name, value in ui.walkconfig():
676 for section, name, value in ui.walkconfig():
677 ui.write('%s.%s=%s\n' % (section, name, value))
677 ui.write('%s.%s=%s\n' % (section, name, value))
678
678
679 def debugstate(ui, repo):
679 def debugstate(ui, repo):
680 """show the contents of the current dirstate"""
680 """show the contents of the current dirstate"""
681 repo.dirstate.read()
681 repo.dirstate.read()
682 dc = repo.dirstate.map
682 dc = repo.dirstate.map
683 keys = dc.keys()
683 keys = dc.keys()
684 keys.sort()
684 keys.sort()
685 for file_ in keys:
685 for file_ in keys:
686 ui.write("%c %3o %10d %s %s\n"
686 ui.write("%c %3o %10d %s %s\n"
687 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
687 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
688 time.strftime("%x %X",
688 time.strftime("%x %X",
689 time.localtime(dc[file_][3])), file_))
689 time.localtime(dc[file_][3])), file_))
690
690
691 def debugdata(ui, file_, rev):
691 def debugdata(ui, file_, rev):
692 """dump the contents of an data file revision"""
692 """dump the contents of an data file revision"""
693 r = hg.revlog(hg.opener(""), file_[:-2] + ".i", file_)
693 r = hg.revlog(hg.opener(""), file_[:-2] + ".i", file_)
694 ui.write(r.revision(r.lookup(rev)))
694 ui.write(r.revision(r.lookup(rev)))
695
695
696 def debugindex(ui, file_):
696 def debugindex(ui, file_):
697 """dump the contents of an index file"""
697 """dump the contents of an index file"""
698 r = hg.revlog(hg.opener(""), file_, "")
698 r = hg.revlog(hg.opener(""), file_, "")
699 ui.write(" rev offset length base linkrev" +
699 ui.write(" rev offset length base linkrev" +
700 " nodeid p1 p2\n")
700 " nodeid p1 p2\n")
701 for i in range(r.count()):
701 for i in range(r.count()):
702 e = r.index[i]
702 e = r.index[i]
703 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
703 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
704 i, e[0], e[1], e[2], e[3],
704 i, e[0], e[1], e[2], e[3],
705 hg.short(e[6]), hg.short(e[4]), hg.short(e[5])))
705 hg.short(e[6]), hg.short(e[4]), hg.short(e[5])))
706
706
707 def debugindexdot(ui, file_):
707 def debugindexdot(ui, file_):
708 """dump an index DAG as a .dot file"""
708 """dump an index DAG as a .dot file"""
709 r = hg.revlog(hg.opener(""), file_, "")
709 r = hg.revlog(hg.opener(""), file_, "")
710 ui.write("digraph G {\n")
710 ui.write("digraph G {\n")
711 for i in range(r.count()):
711 for i in range(r.count()):
712 e = r.index[i]
712 e = r.index[i]
713 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
713 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
714 if e[5] != hg.nullid:
714 if e[5] != hg.nullid:
715 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
715 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
716 ui.write("}\n")
716 ui.write("}\n")
717
717
718 def debugwalk(ui, repo, *pats, **opts):
718 def debugwalk(ui, repo, *pats, **opts):
719 """show how files match on given patterns"""
719 """show how files match on given patterns"""
720 items = list(walk(repo, pats, opts))
720 items = list(walk(repo, pats, opts))
721 if not items: return
721 if not items: return
722 fmt = '%%s %%-%ds %%-%ds %%s\n' % (
722 fmt = '%%s %%-%ds %%-%ds %%s\n' % (
723 max([len(abs) for (src, abs, rel, exact) in items]),
723 max([len(abs) for (src, abs, rel, exact) in items]),
724 max([len(rel) for (src, abs, rel, exact) in items]))
724 max([len(rel) for (src, abs, rel, exact) in items]))
725 exactly = {True: 'exact', False: ''}
725 exactly = {True: 'exact', False: ''}
726 for src, abs, rel, exact in items:
726 for src, abs, rel, exact in items:
727 ui.write(fmt % (src, abs, rel, exactly[exact]))
727 ui.write(fmt % (src, abs, rel, exactly[exact]))
728
728
729 def diff(ui, repo, *pats, **opts):
729 def diff(ui, repo, *pats, **opts):
730 """diff working directory (or selected files)"""
730 """diff working directory (or selected files)"""
731 node1, node2 = None, None
731 node1, node2 = None, None
732 revs = [repo.lookup(x) for x in opts['rev']]
732 revs = [repo.lookup(x) for x in opts['rev']]
733
733
734 if len(revs) > 0:
734 if len(revs) > 0:
735 node1 = revs[0]
735 node1 = revs[0]
736 if len(revs) > 1:
736 if len(revs) > 1:
737 node2 = revs[1]
737 node2 = revs[1]
738 if len(revs) > 2:
738 if len(revs) > 2:
739 raise util.Abort("too many revisions to diff")
739 raise util.Abort("too many revisions to diff")
740
740
741 files = []
741 files = []
742 match = util.always
742 match = util.always
743 if pats:
743 if pats:
744 roots, match, results = makewalk(repo, pats, opts)
744 roots, match, results = makewalk(repo, pats, opts)
745 for src, abs, rel, exact in results:
745 for src, abs, rel, exact in results:
746 files.append(abs)
746 files.append(abs)
747
747
748 dodiff(sys.stdout, ui, repo, node1, node2, files, match=match,
748 dodiff(sys.stdout, ui, repo, node1, node2, files, match=match,
749 text=opts['text'])
749 text=opts['text'])
750
750
751 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
751 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
752 node = repo.lookup(changeset)
752 node = repo.lookup(changeset)
753 prev, other = repo.changelog.parents(node)
753 prev, other = repo.changelog.parents(node)
754 change = repo.changelog.read(node)
754 change = repo.changelog.read(node)
755
755
756 fp = make_file(repo, repo.changelog, opts['output'],
756 fp = make_file(repo, repo.changelog, opts['output'],
757 node=node, total=total, seqno=seqno,
757 node=node, total=total, seqno=seqno,
758 revwidth=revwidth)
758 revwidth=revwidth)
759 if fp != sys.stdout:
759 if fp != sys.stdout:
760 ui.note("%s\n" % fp.name)
760 ui.note("%s\n" % fp.name)
761
761
762 fp.write("# HG changeset patch\n")
762 fp.write("# HG changeset patch\n")
763 fp.write("# User %s\n" % change[1])
763 fp.write("# User %s\n" % change[1])
764 fp.write("# Node ID %s\n" % hg.hex(node))
764 fp.write("# Node ID %s\n" % hg.hex(node))
765 fp.write("# Parent %s\n" % hg.hex(prev))
765 fp.write("# Parent %s\n" % hg.hex(prev))
766 if other != hg.nullid:
766 if other != hg.nullid:
767 fp.write("# Parent %s\n" % hg.hex(other))
767 fp.write("# Parent %s\n" % hg.hex(other))
768 fp.write(change[4].rstrip())
768 fp.write(change[4].rstrip())
769 fp.write("\n\n")
769 fp.write("\n\n")
770
770
771 dodiff(fp, ui, repo, prev, node, text=opts['text'])
771 dodiff(fp, ui, repo, prev, node, text=opts['text'])
772 if fp != sys.stdout: fp.close()
772 if fp != sys.stdout: fp.close()
773
773
774 def export(ui, repo, *changesets, **opts):
774 def export(ui, repo, *changesets, **opts):
775 """dump the header and diffs for one or more changesets"""
775 """dump the header and diffs for one or more changesets"""
776 if not changesets:
776 if not changesets:
777 raise util.Abort("export requires at least one changeset")
777 raise util.Abort("export requires at least one changeset")
778 seqno = 0
778 seqno = 0
779 revs = list(revrange(ui, repo, changesets))
779 revs = list(revrange(ui, repo, changesets))
780 total = len(revs)
780 total = len(revs)
781 revwidth = max(len(revs[0]), len(revs[-1]))
781 revwidth = max(len(revs[0]), len(revs[-1]))
782 ui.note(len(revs) > 1 and "Exporting patches:\n" or "Exporting patch:\n")
782 ui.note(len(revs) > 1 and "Exporting patches:\n" or "Exporting patch:\n")
783 for cset in revs:
783 for cset in revs:
784 seqno += 1
784 seqno += 1
785 doexport(ui, repo, cset, seqno, total, revwidth, opts)
785 doexport(ui, repo, cset, seqno, total, revwidth, opts)
786
786
787 def forget(ui, repo, *pats, **opts):
787 def forget(ui, repo, *pats, **opts):
788 """don't add the specified files on the next commit"""
788 """don't add the specified files on the next commit"""
789 forget = []
789 forget = []
790 for src, abs, rel, exact in walk(repo, pats, opts):
790 for src, abs, rel, exact in walk(repo, pats, opts):
791 if repo.dirstate.state(abs) == 'a':
791 if repo.dirstate.state(abs) == 'a':
792 forget.append(abs)
792 forget.append(abs)
793 if not exact: ui.status('forgetting ', rel, '\n')
793 if not exact: ui.status('forgetting ', rel, '\n')
794 repo.forget(forget)
794 repo.forget(forget)
795
795
796 def grep(ui, repo, pattern = None, *pats, **opts):
796 def grep(ui, repo, pattern=None, *pats, **opts):
797 """search for a pattern in specified files and revisions"""
797 """search for a pattern in specified files and revisions"""
798 if pattern is None: pattern = opts['regexp']
798 if pattern is None: pattern = opts['regexp']
799 if not pattern: raise util.Abort('no pattern to search for')
799 if not pattern: raise util.Abort('no pattern to search for')
800 reflags = 0
800 reflags = 0
801 if opts['ignore_case']: reflags |= re.I
801 if opts['ignore_case']: reflags |= re.I
802 regexp = re.compile(pattern, reflags)
802 regexp = re.compile(pattern, reflags)
803 sep, end = ':', '\n'
803 sep, end = ':', '\n'
804 if opts['null'] or opts['print0']: sep = end = '\0'
804 if opts['null'] or opts['print0']: sep = end = '\0'
805
805
806 fcache = {}
806 fcache = {}
807 def getfile(fn):
807 def getfile(fn):
808 if fn not in fcache:
808 if fn not in fcache:
809 fcache[fn] = repo.file(fn)
809 fcache[fn] = repo.file(fn)
810 return fcache[fn]
810 return fcache[fn]
811
811
812 def matchlines(body):
812 def matchlines(body):
813 begin = 0
813 begin = 0
814 linenum = 0
814 linenum = 0
815 while True:
815 while True:
816 match = regexp.search(body, begin)
816 match = regexp.search(body, begin)
817 if not match: break
817 if not match: break
818 mstart, mend = match.span()
818 mstart, mend = match.span()
819 linenum += body.count('\n', begin, mstart) + 1
819 linenum += body.count('\n', begin, mstart) + 1
820 lstart = body.rfind('\n', begin, mstart) + 1 or begin
820 lstart = body.rfind('\n', begin, mstart) + 1 or begin
821 lend = body.find('\n', mend)
821 lend = body.find('\n', mend)
822 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
822 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
823 begin = lend + 1
823 begin = lend + 1
824
824
825 class linestate:
825 class linestate:
826 def __init__(self, line, linenum, colstart, colend):
826 def __init__(self, line, linenum, colstart, colend):
827 self.line = line
827 self.line = line
828 self.linenum = linenum
828 self.linenum = linenum
829 self.colstart = colstart
829 self.colstart = colstart
830 self.colend = colend
830 self.colend = colend
831 def __eq__(self, other): return self.line == other.line
831 def __eq__(self, other): return self.line == other.line
832 def __hash__(self): return hash(self.line)
832 def __hash__(self): return hash(self.line)
833
833
834 matches = {}
834 matches = {}
835 def grepbody(fn, rev, body):
835 def grepbody(fn, rev, body):
836 matches[rev].setdefault(fn, {})
836 matches[rev].setdefault(fn, {})
837 m = matches[rev][fn]
837 m = matches[rev][fn]
838 for lnum, cstart, cend, line in matchlines(body):
838 for lnum, cstart, cend, line in matchlines(body):
839 s = linestate(line, lnum, cstart, cend)
839 s = linestate(line, lnum, cstart, cend)
840 m[s] = s
840 m[s] = s
841
841
842 prev = {}
842 prev = {}
843 def display(fn, rev, states, prevstates):
843 def display(fn, rev, states, prevstates):
844 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
844 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
845 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
845 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
846 for l in diff:
846 for l in diff:
847 if incrementing:
847 if incrementing:
848 change = ((l in prevstates) and '-') or '+'
848 change = ((l in prevstates) and '-') or '+'
849 r = rev
849 r = rev
850 else:
850 else:
851 change = ((l in states) and '-') or '+'
851 change = ((l in states) and '-') or '+'
852 r = prev[fn]
852 r = prev[fn]
853 ui.write('%s:%s:%s:%s%s\n' % (fn, r, l.linenum, change, l.line))
853 ui.write('%s:%s:%s:%s%s\n' % (fn, r, l.linenum, change, l.line))
854
854
855 fstate = {}
855 fstate = {}
856 for st, rev, fns in walkchangerevs(ui, repo, repo.getcwd(), pats, opts):
856 for st, rev, fns in walkchangerevs(ui, repo, repo.getcwd(), pats, opts):
857 if st == 'window':
857 if st == 'window':
858 incrementing = rev
858 incrementing = rev
859 matches.clear()
859 matches.clear()
860 elif st == 'add':
860 elif st == 'add':
861 change = repo.changelog.read(repo.lookup(str(rev)))
861 change = repo.changelog.read(repo.lookup(str(rev)))
862 mf = repo.manifest.read(change[0])
862 mf = repo.manifest.read(change[0])
863 matches[rev] = {}
863 matches[rev] = {}
864 for fn in fns:
864 for fn in fns:
865 fstate.setdefault(fn, {})
865 fstate.setdefault(fn, {})
866 try:
866 try:
867 grepbody(fn, rev, getfile(fn).read(mf[fn]))
867 grepbody(fn, rev, getfile(fn).read(mf[fn]))
868 except KeyError:
868 except KeyError:
869 pass
869 pass
870 elif st == 'iter':
870 elif st == 'iter':
871 states = matches[rev].items()
871 states = matches[rev].items()
872 states.sort()
872 states.sort()
873 for fn, m in states:
873 for fn, m in states:
874 if incrementing or fstate[fn]:
874 if incrementing or fstate[fn]:
875 display(fn, rev, m, fstate[fn])
875 display(fn, rev, m, fstate[fn])
876 fstate[fn] = m
876 fstate[fn] = m
877 prev[fn] = rev
877 prev[fn] = rev
878
878
879 if not incrementing:
879 if not incrementing:
880 fstate = fstate.items()
880 fstate = fstate.items()
881 fstate.sort()
881 fstate.sort()
882 for fn, state in fstate:
882 for fn, state in fstate:
883 display(fn, rev, {}, state)
883 display(fn, rev, {}, state)
884
884
885 def heads(ui, repo, **opts):
885 def heads(ui, repo, **opts):
886 """show current repository heads"""
886 """show current repository heads"""
887 heads = repo.changelog.heads()
887 heads = repo.changelog.heads()
888 br = None
888 br = None
889 if opts['branches']:
889 if opts['branches']:
890 br = repo.branchlookup(heads)
890 br = repo.branchlookup(heads)
891 for n in repo.changelog.heads():
891 for n in repo.changelog.heads():
892 show_changeset(ui, repo, changenode=n, brinfo=br)
892 show_changeset(ui, repo, changenode=n, brinfo=br)
893
893
894 def identify(ui, repo):
894 def identify(ui, repo):
895 """print information about the working copy"""
895 """print information about the working copy"""
896 parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
896 parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
897 if not parents:
897 if not parents:
898 ui.write("unknown\n")
898 ui.write("unknown\n")
899 return
899 return
900
900
901 hexfunc = ui.verbose and hg.hex or hg.short
901 hexfunc = ui.verbose and hg.hex or hg.short
902 (c, a, d, u) = repo.changes()
902 (c, a, d, u) = repo.changes()
903 output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
903 output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
904 (c or a or d) and "+" or "")]
904 (c or a or d) and "+" or "")]
905
905
906 if not ui.quiet:
906 if not ui.quiet:
907 # multiple tags for a single parent separated by '/'
907 # multiple tags for a single parent separated by '/'
908 parenttags = ['/'.join(tags)
908 parenttags = ['/'.join(tags)
909 for tags in map(repo.nodetags, parents) if tags]
909 for tags in map(repo.nodetags, parents) if tags]
910 # tags for multiple parents separated by ' + '
910 # tags for multiple parents separated by ' + '
911 if parenttags:
911 if parenttags:
912 output.append(' + '.join(parenttags))
912 output.append(' + '.join(parenttags))
913
913
914 ui.write("%s\n" % ' '.join(output))
914 ui.write("%s\n" % ' '.join(output))
915
915
916 def import_(ui, repo, patch1, *patches, **opts):
916 def import_(ui, repo, patch1, *patches, **opts):
917 """import an ordered set of patches"""
917 """import an ordered set of patches"""
918 patches = (patch1,) + patches
918 patches = (patch1,) + patches
919
919
920 if not opts['force']:
920 if not opts['force']:
921 (c, a, d, u) = repo.changes()
921 (c, a, d, u) = repo.changes()
922 if c or a or d:
922 if c or a or d:
923 ui.warn("abort: outstanding uncommitted changes!\n")
923 ui.warn("abort: outstanding uncommitted changes!\n")
924 return 1
924 return 1
925
925
926 d = opts["base"]
926 d = opts["base"]
927 strip = opts["strip"]
927 strip = opts["strip"]
928
928
929 for patch in patches:
929 for patch in patches:
930 ui.status("applying %s\n" % patch)
930 ui.status("applying %s\n" % patch)
931 pf = os.path.join(d, patch)
931 pf = os.path.join(d, patch)
932
932
933 message = []
933 message = []
934 user = None
934 user = None
935 hgpatch = False
935 hgpatch = False
936 for line in file(pf):
936 for line in file(pf):
937 line = line.rstrip()
937 line = line.rstrip()
938 if line.startswith("--- ") or line.startswith("diff -r"):
938 if line.startswith("--- ") or line.startswith("diff -r"):
939 break
939 break
940 elif hgpatch:
940 elif hgpatch:
941 # parse values when importing the result of an hg export
941 # parse values when importing the result of an hg export
942 if line.startswith("# User "):
942 if line.startswith("# User "):
943 user = line[7:]
943 user = line[7:]
944 ui.debug('User: %s\n' % user)
944 ui.debug('User: %s\n' % user)
945 elif not line.startswith("# ") and line:
945 elif not line.startswith("# ") and line:
946 message.append(line)
946 message.append(line)
947 hgpatch = False
947 hgpatch = False
948 elif line == '# HG changeset patch':
948 elif line == '# HG changeset patch':
949 hgpatch = True
949 hgpatch = True
950 message = [] # We may have collected garbage
950 message = [] # We may have collected garbage
951 else:
951 else:
952 message.append(line)
952 message.append(line)
953
953
954 # make sure message isn't empty
954 # make sure message isn't empty
955 if not message:
955 if not message:
956 message = "imported patch %s\n" % patch
956 message = "imported patch %s\n" % patch
957 else:
957 else:
958 message = "%s\n" % '\n'.join(message)
958 message = "%s\n" % '\n'.join(message)
959 ui.debug('message:\n%s\n' % message)
959 ui.debug('message:\n%s\n' % message)
960
960
961 f = os.popen("patch -p%d < '%s'" % (strip, pf))
961 f = os.popen("patch -p%d < '%s'" % (strip, pf))
962 files = []
962 files = []
963 for l in f.read().splitlines():
963 for l in f.read().splitlines():
964 l.rstrip('\r\n');
964 l.rstrip('\r\n');
965 ui.status("%s\n" % l)
965 ui.status("%s\n" % l)
966 if l.startswith('patching file '):
966 if l.startswith('patching file '):
967 pf = l[14:]
967 pf = l[14:]
968 if pf not in files:
968 if pf not in files:
969 files.append(pf)
969 files.append(pf)
970 patcherr = f.close()
970 patcherr = f.close()
971 if patcherr:
971 if patcherr:
972 raise util.Abort("patch failed")
972 raise util.Abort("patch failed")
973
973
974 if len(files) > 0:
974 if len(files) > 0:
975 addremove(ui, repo, *files)
975 addremove(ui, repo, *files)
976 repo.commit(files, message, user)
976 repo.commit(files, message, user)
977
977
978 def incoming(ui, repo, source="default"):
978 def incoming(ui, repo, source="default"):
979 """show new changesets found in source"""
979 """show new changesets found in source"""
980 source = ui.expandpath(source)
980 source = ui.expandpath(source)
981 other = hg.repository(ui, source)
981 other = hg.repository(ui, source)
982 if not other.local():
982 if not other.local():
983 ui.warn("abort: incoming doesn't work for remote"
983 ui.warn("abort: incoming doesn't work for remote"
984 + " repositories yet, sorry!\n")
984 + " repositories yet, sorry!\n")
985 return 1
985 return 1
986 o = repo.findincoming(other)
986 o = repo.findincoming(other)
987 if not o:
987 if not o:
988 return
988 return
989 o = other.newer(o)
989 o = other.newer(o)
990 o.reverse()
990 o.reverse()
991 for n in o:
991 for n in o:
992 show_changeset(ui, other, changenode=n)
992 show_changeset(ui, other, changenode=n)
993
993
994 def init(ui, dest="."):
994 def init(ui, dest="."):
995 """create a new repository in the given directory"""
995 """create a new repository in the given directory"""
996 if not os.path.exists(dest):
996 if not os.path.exists(dest):
997 os.mkdir(dest)
997 os.mkdir(dest)
998 hg.repository(ui, dest, create=1)
998 hg.repository(ui, dest, create=1)
999
999
1000 def locate(ui, repo, *pats, **opts):
1000 def locate(ui, repo, *pats, **opts):
1001 """locate files matching specific patterns"""
1001 """locate files matching specific patterns"""
1002 end = '\n'
1002 end = '\n'
1003 if opts['print0']: end = '\0'
1003 if opts['print0']: end = '\0'
1004
1004
1005 for src, abs, rel, exact in walk(repo, pats, opts, '(?:.*/|)'):
1005 for src, abs, rel, exact in walk(repo, pats, opts, '(?:.*/|)'):
1006 if repo.dirstate.state(abs) == '?': continue
1006 if repo.dirstate.state(abs) == '?': continue
1007 if opts['fullpath']:
1007 if opts['fullpath']:
1008 ui.write(os.path.join(repo.root, abs), end)
1008 ui.write(os.path.join(repo.root, abs), end)
1009 else:
1009 else:
1010 ui.write(rel, end)
1010 ui.write(rel, end)
1011
1011
1012 def log(ui, repo, *pats, **opts):
1012 def log(ui, repo, *pats, **opts):
1013 """show revision history of entire repository or files"""
1013 """show revision history of entire repository or files"""
1014 class dui:
1014 class dui:
1015 # Implement and delegate some ui protocol. Save hunks of
1015 # Implement and delegate some ui protocol. Save hunks of
1016 # output for later display in the desired order.
1016 # output for later display in the desired order.
1017 def __init__(self, ui):
1017 def __init__(self, ui):
1018 self.ui = ui
1018 self.ui = ui
1019 self.hunk = {}
1019 self.hunk = {}
1020 def bump(self, rev):
1020 def bump(self, rev):
1021 self.rev = rev
1021 self.rev = rev
1022 self.hunk[rev] = []
1022 self.hunk[rev] = []
1023 def note(self, *args):
1023 def note(self, *args):
1024 if self.verbose: self.write(*args)
1024 if self.verbose: self.write(*args)
1025 def status(self, *args):
1025 def status(self, *args):
1026 if not self.quiet: self.write(*args)
1026 if not self.quiet: self.write(*args)
1027 def write(self, *args):
1027 def write(self, *args):
1028 self.hunk[self.rev].append(args)
1028 self.hunk[self.rev].append(args)
1029 def __getattr__(self, key):
1029 def __getattr__(self, key):
1030 return getattr(self.ui, key)
1030 return getattr(self.ui, key)
1031 cwd = repo.getcwd()
1031 cwd = repo.getcwd()
1032 if not pats and cwd:
1032 if not pats and cwd:
1033 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
1033 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
1034 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
1034 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
1035 for st, rev, fns in walkchangerevs(ui, repo, (pats and cwd) or '', pats,
1035 for st, rev, fns in walkchangerevs(ui, repo, (pats and cwd) or '', pats,
1036 opts):
1036 opts):
1037 if st == 'window':
1037 if st == 'window':
1038 du = dui(ui)
1038 du = dui(ui)
1039 elif st == 'add':
1039 elif st == 'add':
1040 du.bump(rev)
1040 du.bump(rev)
1041 show_changeset(du, repo, rev)
1041 show_changeset(du, repo, rev)
1042 if opts['patch']:
1042 if opts['patch']:
1043 changenode = repo.changelog.node(rev)
1043 changenode = repo.changelog.node(rev)
1044 prev, other = repo.changelog.parents(changenode)
1044 prev, other = repo.changelog.parents(changenode)
1045 dodiff(du, du, repo, prev, changenode, fns)
1045 dodiff(du, du, repo, prev, changenode, fns)
1046 du.write("\n\n")
1046 du.write("\n\n")
1047 elif st == 'iter':
1047 elif st == 'iter':
1048 for args in du.hunk[rev]:
1048 for args in du.hunk[rev]:
1049 ui.write(*args)
1049 ui.write(*args)
1050
1050
1051 def manifest(ui, repo, rev=None):
1051 def manifest(ui, repo, rev=None):
1052 """output the latest or given revision of the project manifest"""
1052 """output the latest or given revision of the project manifest"""
1053 if rev:
1053 if rev:
1054 try:
1054 try:
1055 # assume all revision numbers are for changesets
1055 # assume all revision numbers are for changesets
1056 n = repo.lookup(rev)
1056 n = repo.lookup(rev)
1057 change = repo.changelog.read(n)
1057 change = repo.changelog.read(n)
1058 n = change[0]
1058 n = change[0]
1059 except hg.RepoError:
1059 except hg.RepoError:
1060 n = repo.manifest.lookup(rev)
1060 n = repo.manifest.lookup(rev)
1061 else:
1061 else:
1062 n = repo.manifest.tip()
1062 n = repo.manifest.tip()
1063 m = repo.manifest.read(n)
1063 m = repo.manifest.read(n)
1064 mf = repo.manifest.readflags(n)
1064 mf = repo.manifest.readflags(n)
1065 files = m.keys()
1065 files = m.keys()
1066 files.sort()
1066 files.sort()
1067
1067
1068 for f in files:
1068 for f in files:
1069 ui.write("%40s %3s %s\n" % (hg.hex(m[f]), mf[f] and "755" or "644", f))
1069 ui.write("%40s %3s %s\n" % (hg.hex(m[f]), mf[f] and "755" or "644", f))
1070
1070
1071 def outgoing(ui, repo, dest="default-push"):
1071 def outgoing(ui, repo, dest="default-push"):
1072 """show changesets not found in destination"""
1072 """show changesets not found in destination"""
1073 dest = ui.expandpath(dest)
1073 dest = ui.expandpath(dest)
1074 other = hg.repository(ui, dest)
1074 other = hg.repository(ui, dest)
1075 o = repo.findoutgoing(other)
1075 o = repo.findoutgoing(other)
1076 o = repo.newer(o)
1076 o = repo.newer(o)
1077 o.reverse()
1077 o.reverse()
1078 for n in o:
1078 for n in o:
1079 show_changeset(ui, repo, changenode=n)
1079 show_changeset(ui, repo, changenode=n)
1080
1080
1081 def parents(ui, repo, rev=None):
1081 def parents(ui, repo, rev=None):
1082 """show the parents of the working dir or revision"""
1082 """show the parents of the working dir or revision"""
1083 if rev:
1083 if rev:
1084 p = repo.changelog.parents(repo.lookup(rev))
1084 p = repo.changelog.parents(repo.lookup(rev))
1085 else:
1085 else:
1086 p = repo.dirstate.parents()
1086 p = repo.dirstate.parents()
1087
1087
1088 for n in p:
1088 for n in p:
1089 if n != hg.nullid:
1089 if n != hg.nullid:
1090 show_changeset(ui, repo, changenode=n)
1090 show_changeset(ui, repo, changenode=n)
1091
1091
1092 def paths(ui, search = None):
1092 def paths(ui, search=None):
1093 """show definition of symbolic path names"""
1093 """show definition of symbolic path names"""
1094 try:
1094 try:
1095 repo = hg.repository(ui=ui)
1095 repo = hg.repository(ui=ui)
1096 except:
1096 except:
1097 pass
1097 pass
1098
1098
1099 if search:
1099 if search:
1100 for name, path in ui.configitems("paths"):
1100 for name, path in ui.configitems("paths"):
1101 if name == search:
1101 if name == search:
1102 ui.write("%s\n" % path)
1102 ui.write("%s\n" % path)
1103 return
1103 return
1104 ui.warn("not found!\n")
1104 ui.warn("not found!\n")
1105 return 1
1105 return 1
1106 else:
1106 else:
1107 for name, path in ui.configitems("paths"):
1107 for name, path in ui.configitems("paths"):
1108 ui.write("%s = %s\n" % (name, path))
1108 ui.write("%s = %s\n" % (name, path))
1109
1109
1110 def pull(ui, repo, source="default", **opts):
1110 def pull(ui, repo, source="default", **opts):
1111 """pull changes from the specified source"""
1111 """pull changes from the specified source"""
1112 source = ui.expandpath(source)
1112 source = ui.expandpath(source)
1113 ui.status('pulling from %s\n' % (source))
1113 ui.status('pulling from %s\n' % (source))
1114
1114
1115 if opts['ssh']:
1115 if opts['ssh']:
1116 ui.setconfig("ui", "ssh", opts['ssh'])
1116 ui.setconfig("ui", "ssh", opts['ssh'])
1117 if opts['remotecmd']:
1117 if opts['remotecmd']:
1118 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1118 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1119
1119
1120 other = hg.repository(ui, source)
1120 other = hg.repository(ui, source)
1121 r = repo.pull(other)
1121 r = repo.pull(other)
1122 if not r:
1122 if not r:
1123 if opts['update']:
1123 if opts['update']:
1124 return update(ui, repo)
1124 return update(ui, repo)
1125 else:
1125 else:
1126 ui.status("(run 'hg update' to get a working copy)\n")
1126 ui.status("(run 'hg update' to get a working copy)\n")
1127
1127
1128 return r
1128 return r
1129
1129
1130 def push(ui, repo, dest="default-push", force=False, ssh=None, remotecmd=None):
1130 def push(ui, repo, dest="default-push", force=False, ssh=None, remotecmd=None):
1131 """push changes to the specified destination"""
1131 """push changes to the specified destination"""
1132 dest = ui.expandpath(dest)
1132 dest = ui.expandpath(dest)
1133 ui.status('pushing to %s\n' % (dest))
1133 ui.status('pushing to %s\n' % (dest))
1134
1134
1135 if ssh:
1135 if ssh:
1136 ui.setconfig("ui", "ssh", ssh)
1136 ui.setconfig("ui", "ssh", ssh)
1137 if remotecmd:
1137 if remotecmd:
1138 ui.setconfig("ui", "remotecmd", remotecmd)
1138 ui.setconfig("ui", "remotecmd", remotecmd)
1139
1139
1140 other = hg.repository(ui, dest)
1140 other = hg.repository(ui, dest)
1141 r = repo.push(other, force)
1141 r = repo.push(other, force)
1142 return r
1142 return r
1143
1143
1144 def rawcommit(ui, repo, *flist, **rc):
1144 def rawcommit(ui, repo, *flist, **rc):
1145 "raw commit interface"
1145 "raw commit interface"
1146 if rc['text']:
1146 if rc['text']:
1147 ui.warn("Warning: -t and --text is deprecated,"
1147 ui.warn("Warning: -t and --text is deprecated,"
1148 " please use -m or --message instead.\n")
1148 " please use -m or --message instead.\n")
1149 message = rc['message'] or rc['text']
1149 message = rc['message'] or rc['text']
1150 if not message and rc['logfile']:
1150 if not message and rc['logfile']:
1151 try:
1151 try:
1152 message = open(rc['logfile']).read()
1152 message = open(rc['logfile']).read()
1153 except IOError:
1153 except IOError:
1154 pass
1154 pass
1155 if not message and not rc['logfile']:
1155 if not message and not rc['logfile']:
1156 ui.warn("abort: missing commit message\n")
1156 ui.warn("abort: missing commit message\n")
1157 return 1
1157 return 1
1158
1158
1159 files = relpath(repo, list(flist))
1159 files = relpath(repo, list(flist))
1160 if rc['files']:
1160 if rc['files']:
1161 files += open(rc['files']).read().splitlines()
1161 files += open(rc['files']).read().splitlines()
1162
1162
1163 rc['parent'] = map(repo.lookup, rc['parent'])
1163 rc['parent'] = map(repo.lookup, rc['parent'])
1164
1164
1165 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
1165 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
1166
1166
1167 def recover(ui, repo):
1167 def recover(ui, repo):
1168 """roll back an interrupted transaction"""
1168 """roll back an interrupted transaction"""
1169 repo.recover()
1169 repo.recover()
1170
1170
1171 def remove(ui, repo, file1, *files):
1171 def remove(ui, repo, file1, *files):
1172 """remove the specified files on the next commit"""
1172 """remove the specified files on the next commit"""
1173 repo.remove(relpath(repo, (file1,) + files))
1173 repo.remove(relpath(repo, (file1,) + files))
1174
1174
1175 def revert(ui, repo, *names, **opts):
1175 def revert(ui, repo, *names, **opts):
1176 """revert modified files or dirs back to their unmodified states"""
1176 """revert modified files or dirs back to their unmodified states"""
1177 node = opts['rev'] and repo.lookup(opts['rev']) or \
1177 node = opts['rev'] and repo.lookup(opts['rev']) or \
1178 repo.dirstate.parents()[0]
1178 repo.dirstate.parents()[0]
1179 root = os.path.realpath(repo.root)
1179 root = os.path.realpath(repo.root)
1180
1180
1181 def trimpath(p):
1181 def trimpath(p):
1182 p = os.path.realpath(p)
1182 p = os.path.realpath(p)
1183 if p.startswith(root):
1183 if p.startswith(root):
1184 rest = p[len(root):]
1184 rest = p[len(root):]
1185 if not rest:
1185 if not rest:
1186 return rest
1186 return rest
1187 if p.startswith(os.sep):
1187 if p.startswith(os.sep):
1188 return rest[1:]
1188 return rest[1:]
1189 return p
1189 return p
1190
1190
1191 relnames = map(trimpath, names or [os.getcwd()])
1191 relnames = map(trimpath, names or [os.getcwd()])
1192 chosen = {}
1192 chosen = {}
1193
1193
1194 def choose(name):
1194 def choose(name):
1195 def body(name):
1195 def body(name):
1196 for r in relnames:
1196 for r in relnames:
1197 if not name.startswith(r):
1197 if not name.startswith(r):
1198 continue
1198 continue
1199 rest = name[len(r):]
1199 rest = name[len(r):]
1200 if not rest:
1200 if not rest:
1201 return r, True
1201 return r, True
1202 depth = rest.count(os.sep)
1202 depth = rest.count(os.sep)
1203 if not r:
1203 if not r:
1204 if depth == 0 or not opts['nonrecursive']:
1204 if depth == 0 or not opts['nonrecursive']:
1205 return r, True
1205 return r, True
1206 elif rest[0] == os.sep:
1206 elif rest[0] == os.sep:
1207 if depth == 1 or not opts['nonrecursive']:
1207 if depth == 1 or not opts['nonrecursive']:
1208 return r, True
1208 return r, True
1209 return None, False
1209 return None, False
1210 relname, ret = body(name)
1210 relname, ret = body(name)
1211 if ret:
1211 if ret:
1212 chosen[relname] = 1
1212 chosen[relname] = 1
1213 return ret
1213 return ret
1214
1214
1215 r = repo.update(node, False, True, choose, False)
1215 r = repo.update(node, False, True, choose, False)
1216 for n in relnames:
1216 for n in relnames:
1217 if n not in chosen:
1217 if n not in chosen:
1218 ui.warn('error: no matches for %s\n' % n)
1218 ui.warn('error: no matches for %s\n' % n)
1219 r = 1
1219 r = 1
1220 sys.stdout.flush()
1220 sys.stdout.flush()
1221 return r
1221 return r
1222
1222
1223 def root(ui, repo):
1223 def root(ui, repo):
1224 """print the root (top) of the current working dir"""
1224 """print the root (top) of the current working dir"""
1225 ui.write(repo.root + "\n")
1225 ui.write(repo.root + "\n")
1226
1226
1227 def serve(ui, repo, **opts):
1227 def serve(ui, repo, **opts):
1228 """export the repository via HTTP"""
1228 """export the repository via HTTP"""
1229
1229
1230 if opts["stdio"]:
1230 if opts["stdio"]:
1231 fin, fout = sys.stdin, sys.stdout
1231 fin, fout = sys.stdin, sys.stdout
1232 sys.stdout = sys.stderr
1232 sys.stdout = sys.stderr
1233
1233
1234 def getarg():
1234 def getarg():
1235 argline = fin.readline()[:-1]
1235 argline = fin.readline()[:-1]
1236 arg, l = argline.split()
1236 arg, l = argline.split()
1237 val = fin.read(int(l))
1237 val = fin.read(int(l))
1238 return arg, val
1238 return arg, val
1239 def respond(v):
1239 def respond(v):
1240 fout.write("%d\n" % len(v))
1240 fout.write("%d\n" % len(v))
1241 fout.write(v)
1241 fout.write(v)
1242 fout.flush()
1242 fout.flush()
1243
1243
1244 lock = None
1244 lock = None
1245
1245
1246 while 1:
1246 while 1:
1247 cmd = fin.readline()[:-1]
1247 cmd = fin.readline()[:-1]
1248 if cmd == '':
1248 if cmd == '':
1249 return
1249 return
1250 if cmd == "heads":
1250 if cmd == "heads":
1251 h = repo.heads()
1251 h = repo.heads()
1252 respond(" ".join(map(hg.hex, h)) + "\n")
1252 respond(" ".join(map(hg.hex, h)) + "\n")
1253 if cmd == "lock":
1253 if cmd == "lock":
1254 lock = repo.lock()
1254 lock = repo.lock()
1255 respond("")
1255 respond("")
1256 if cmd == "unlock":
1256 if cmd == "unlock":
1257 if lock:
1257 if lock:
1258 lock.release()
1258 lock.release()
1259 lock = None
1259 lock = None
1260 respond("")
1260 respond("")
1261 elif cmd == "branches":
1261 elif cmd == "branches":
1262 arg, nodes = getarg()
1262 arg, nodes = getarg()
1263 nodes = map(hg.bin, nodes.split(" "))
1263 nodes = map(hg.bin, nodes.split(" "))
1264 r = []
1264 r = []
1265 for b in repo.branches(nodes):
1265 for b in repo.branches(nodes):
1266 r.append(" ".join(map(hg.hex, b)) + "\n")
1266 r.append(" ".join(map(hg.hex, b)) + "\n")
1267 respond("".join(r))
1267 respond("".join(r))
1268 elif cmd == "between":
1268 elif cmd == "between":
1269 arg, pairs = getarg()
1269 arg, pairs = getarg()
1270 pairs = [map(hg.bin, p.split("-")) for p in pairs.split(" ")]
1270 pairs = [map(hg.bin, p.split("-")) for p in pairs.split(" ")]
1271 r = []
1271 r = []
1272 for b in repo.between(pairs):
1272 for b in repo.between(pairs):
1273 r.append(" ".join(map(hg.hex, b)) + "\n")
1273 r.append(" ".join(map(hg.hex, b)) + "\n")
1274 respond("".join(r))
1274 respond("".join(r))
1275 elif cmd == "changegroup":
1275 elif cmd == "changegroup":
1276 nodes = []
1276 nodes = []
1277 arg, roots = getarg()
1277 arg, roots = getarg()
1278 nodes = map(hg.bin, roots.split(" "))
1278 nodes = map(hg.bin, roots.split(" "))
1279
1279
1280 cg = repo.changegroup(nodes)
1280 cg = repo.changegroup(nodes)
1281 while 1:
1281 while 1:
1282 d = cg.read(4096)
1282 d = cg.read(4096)
1283 if not d:
1283 if not d:
1284 break
1284 break
1285 fout.write(d)
1285 fout.write(d)
1286
1286
1287 fout.flush()
1287 fout.flush()
1288
1288
1289 elif cmd == "addchangegroup":
1289 elif cmd == "addchangegroup":
1290 if not lock:
1290 if not lock:
1291 respond("not locked")
1291 respond("not locked")
1292 continue
1292 continue
1293 respond("")
1293 respond("")
1294
1294
1295 r = repo.addchangegroup(fin)
1295 r = repo.addchangegroup(fin)
1296 respond("")
1296 respond("")
1297
1297
1298 optlist = "name templates style address port ipv6 accesslog errorlog"
1298 optlist = "name templates style address port ipv6 accesslog errorlog"
1299 for o in optlist.split():
1299 for o in optlist.split():
1300 if opts[o]:
1300 if opts[o]:
1301 ui.setconfig("web", o, opts[o])
1301 ui.setconfig("web", o, opts[o])
1302
1302
1303 httpd = hgweb.create_server(repo)
1303 httpd = hgweb.create_server(repo)
1304
1304
1305 if ui.verbose:
1305 if ui.verbose:
1306 addr, port = httpd.socket.getsockname()
1306 addr, port = httpd.socket.getsockname()
1307 if addr == '0.0.0.0':
1307 if addr == '0.0.0.0':
1308 addr = socket.gethostname()
1308 addr = socket.gethostname()
1309 else:
1309 else:
1310 try:
1310 try:
1311 addr = socket.gethostbyaddr(addr)[0]
1311 addr = socket.gethostbyaddr(addr)[0]
1312 except socket.error:
1312 except socket.error:
1313 pass
1313 pass
1314 if port != 80:
1314 if port != 80:
1315 ui.status('listening at http://%s:%d/\n' % (addr, port))
1315 ui.status('listening at http://%s:%d/\n' % (addr, port))
1316 else:
1316 else:
1317 ui.status('listening at http://%s/\n' % addr)
1317 ui.status('listening at http://%s/\n' % addr)
1318 httpd.serve_forever()
1318 httpd.serve_forever()
1319
1319
1320 def status(ui, repo, *pats, **opts):
1320 def status(ui, repo, *pats, **opts):
1321 '''show changed files in the working directory
1321 '''show changed files in the working directory
1322
1322
1323 M = modified
1323 M = modified
1324 A = added
1324 A = added
1325 R = removed
1325 R = removed
1326 ? = not tracked
1326 ? = not tracked
1327 '''
1327 '''
1328
1328
1329 cwd = repo.getcwd()
1329 cwd = repo.getcwd()
1330 files, matchfn, anypats = matchpats(repo, cwd, pats, opts)
1330 files, matchfn, anypats = matchpats(repo, cwd, pats, opts)
1331 (c, a, d, u) = [[util.pathto(cwd, x) for x in n]
1331 (c, a, d, u) = [[util.pathto(cwd, x) for x in n]
1332 for n in repo.changes(files=files, match=matchfn)]
1332 for n in repo.changes(files=files, match=matchfn)]
1333
1333
1334 changetypes = [('modified', 'M', c),
1334 changetypes = [('modified', 'M', c),
1335 ('added', 'A', a),
1335 ('added', 'A', a),
1336 ('removed', 'R', d),
1336 ('removed', 'R', d),
1337 ('unknown', '?', u)]
1337 ('unknown', '?', u)]
1338
1338
1339 for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]]
1339 for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]]
1340 or changetypes):
1340 or changetypes):
1341 for f in changes:
1341 for f in changes:
1342 ui.write("%s %s\n" % (char, f))
1342 ui.write("%s %s\n" % (char, f))
1343
1343
1344 def tag(ui, repo, name, rev=None, **opts):
1344 def tag(ui, repo, name, rev=None, **opts):
1345 """add a tag for the current tip or a given revision"""
1345 """add a tag for the current tip or a given revision"""
1346 if opts['text']:
1346 if opts['text']:
1347 ui.warn("Warning: -t and --text is deprecated,"
1347 ui.warn("Warning: -t and --text is deprecated,"
1348 " please use -m or --message instead.\n")
1348 " please use -m or --message instead.\n")
1349 if name == "tip":
1349 if name == "tip":
1350 ui.warn("abort: 'tip' is a reserved name!\n")
1350 ui.warn("abort: 'tip' is a reserved name!\n")
1351 return -1
1351 return -1
1352 if rev:
1352 if rev:
1353 r = hg.hex(repo.lookup(rev))
1353 r = hg.hex(repo.lookup(rev))
1354 else:
1354 else:
1355 r = hg.hex(repo.changelog.tip())
1355 r = hg.hex(repo.changelog.tip())
1356
1356
1357 if name.find(revrangesep) >= 0:
1357 if name.find(revrangesep) >= 0:
1358 ui.warn("abort: '%s' cannot be used in a tag name\n" % revrangesep)
1358 ui.warn("abort: '%s' cannot be used in a tag name\n" % revrangesep)
1359 return -1
1359 return -1
1360
1360
1361 if opts['local']:
1361 if opts['local']:
1362 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
1362 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
1363 return
1363 return
1364
1364
1365 (c, a, d, u) = repo.changes()
1365 (c, a, d, u) = repo.changes()
1366 for x in (c, a, d, u):
1366 for x in (c, a, d, u):
1367 if ".hgtags" in x:
1367 if ".hgtags" in x:
1368 ui.warn("abort: working copy of .hgtags is changed!\n")
1368 ui.warn("abort: working copy of .hgtags is changed!\n")
1369 ui.status("(please commit .hgtags manually)\n")
1369 ui.status("(please commit .hgtags manually)\n")
1370 return -1
1370 return -1
1371
1371
1372 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
1372 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
1373 if repo.dirstate.state(".hgtags") == '?':
1373 if repo.dirstate.state(".hgtags") == '?':
1374 repo.add([".hgtags"])
1374 repo.add([".hgtags"])
1375
1375
1376 message = (opts['message'] or opts['text'] or
1376 message = (opts['message'] or opts['text'] or
1377 "Added tag %s for changeset %s" % (name, r))
1377 "Added tag %s for changeset %s" % (name, r))
1378 repo.commit([".hgtags"], message, opts['user'], opts['date'])
1378 repo.commit([".hgtags"], message, opts['user'], opts['date'])
1379
1379
1380 def tags(ui, repo):
1380 def tags(ui, repo):
1381 """list repository tags"""
1381 """list repository tags"""
1382
1382
1383 l = repo.tagslist()
1383 l = repo.tagslist()
1384 l.reverse()
1384 l.reverse()
1385 for t, n in l:
1385 for t, n in l:
1386 try:
1386 try:
1387 r = "%5d:%s" % (repo.changelog.rev(n), hg.hex(n))
1387 r = "%5d:%s" % (repo.changelog.rev(n), hg.hex(n))
1388 except KeyError:
1388 except KeyError:
1389 r = " ?:?"
1389 r = " ?:?"
1390 ui.write("%-30s %s\n" % (t, r))
1390 ui.write("%-30s %s\n" % (t, r))
1391
1391
1392 def tip(ui, repo):
1392 def tip(ui, repo):
1393 """show the tip revision"""
1393 """show the tip revision"""
1394 n = repo.changelog.tip()
1394 n = repo.changelog.tip()
1395 show_changeset(ui, repo, changenode=n)
1395 show_changeset(ui, repo, changenode=n)
1396
1396
1397 def undo(ui, repo):
1397 def undo(ui, repo):
1398 """undo the last commit or pull
1398 """undo the last commit or pull
1399
1399
1400 Roll back the last pull or commit transaction on the
1400 Roll back the last pull or commit transaction on the
1401 repository, restoring the project to its earlier state.
1401 repository, restoring the project to its earlier state.
1402
1402
1403 This command should be used with care. There is only one level of
1403 This command should be used with care. There is only one level of
1404 undo and there is no redo.
1404 undo and there is no redo.
1405
1405
1406 This command is not intended for use on public repositories. Once
1406 This command is not intended for use on public repositories. Once
1407 a change is visible for pull by other users, undoing it locally is
1407 a change is visible for pull by other users, undoing it locally is
1408 ineffective.
1408 ineffective.
1409 """
1409 """
1410 repo.undo()
1410 repo.undo()
1411
1411
1412 def update(ui, repo, node=None, merge=False, clean=False, branch=None):
1412 def update(ui, repo, node=None, merge=False, clean=False, branch=None):
1413 '''update or merge working directory
1413 '''update or merge working directory
1414
1414
1415 If there are no outstanding changes in the working directory and
1415 If there are no outstanding changes in the working directory and
1416 there is a linear relationship between the current version and the
1416 there is a linear relationship between the current version and the
1417 requested version, the result is the requested version.
1417 requested version, the result is the requested version.
1418
1418
1419 Otherwise the result is a merge between the contents of the
1419 Otherwise the result is a merge between the contents of the
1420 current working directory and the requested version. Files that
1420 current working directory and the requested version. Files that
1421 changed between either parent are marked as changed for the next
1421 changed between either parent are marked as changed for the next
1422 commit and a commit must be performed before any further updates
1422 commit and a commit must be performed before any further updates
1423 are allowed.
1423 are allowed.
1424 '''
1424 '''
1425 if branch:
1425 if branch:
1426 br = repo.branchlookup(branch=branch)
1426 br = repo.branchlookup(branch=branch)
1427 found = []
1427 found = []
1428 for x in br:
1428 for x in br:
1429 if branch in br[x]:
1429 if branch in br[x]:
1430 found.append(x)
1430 found.append(x)
1431 if len(found) > 1:
1431 if len(found) > 1:
1432 ui.warn("Found multiple heads for %s\n" % branch)
1432 ui.warn("Found multiple heads for %s\n" % branch)
1433 for x in found:
1433 for x in found:
1434 show_changeset(ui, repo, changenode=x, brinfo=br)
1434 show_changeset(ui, repo, changenode=x, brinfo=br)
1435 return 1
1435 return 1
1436 if len(found) == 1:
1436 if len(found) == 1:
1437 node = found[0]
1437 node = found[0]
1438 ui.warn("Using head %s for branch %s\n" % (hg.short(node), branch))
1438 ui.warn("Using head %s for branch %s\n" % (hg.short(node), branch))
1439 else:
1439 else:
1440 ui.warn("branch %s not found\n" % (branch))
1440 ui.warn("branch %s not found\n" % (branch))
1441 return 1
1441 return 1
1442 else:
1442 else:
1443 node = node and repo.lookup(node) or repo.changelog.tip()
1443 node = node and repo.lookup(node) or repo.changelog.tip()
1444 return repo.update(node, allow=merge, force=clean)
1444 return repo.update(node, allow=merge, force=clean)
1445
1445
1446 def verify(ui, repo):
1446 def verify(ui, repo):
1447 """verify the integrity of the repository"""
1447 """verify the integrity of the repository"""
1448 return repo.verify()
1448 return repo.verify()
1449
1449
1450 # Command options and aliases are listed here, alphabetically
1450 # Command options and aliases are listed here, alphabetically
1451
1451
1452 table = {
1452 table = {
1453 "^add":
1453 "^add":
1454 (add,
1454 (add,
1455 [('I', 'include', [], 'include path in search'),
1455 [('I', 'include', [], 'include path in search'),
1456 ('X', 'exclude', [], 'exclude path from search')],
1456 ('X', 'exclude', [], 'exclude path from search')],
1457 "hg add [OPTION]... [FILE]..."),
1457 "hg add [OPTION]... [FILE]..."),
1458 "addremove":
1458 "addremove":
1459 (addremove,
1459 (addremove,
1460 [('I', 'include', [], 'include path in search'),
1460 [('I', 'include', [], 'include path in search'),
1461 ('X', 'exclude', [], 'exclude path from search')],
1461 ('X', 'exclude', [], 'exclude path from search')],
1462 "hg addremove [OPTION]... [FILE]..."),
1462 "hg addremove [OPTION]... [FILE]..."),
1463 "^annotate":
1463 "^annotate":
1464 (annotate,
1464 (annotate,
1465 [('r', 'rev', '', 'revision'),
1465 [('r', 'rev', '', 'revision'),
1466 ('a', 'text', None, 'treat all files as text'),
1466 ('a', 'text', None, 'treat all files as text'),
1467 ('u', 'user', None, 'show user'),
1467 ('u', 'user', None, 'show user'),
1468 ('n', 'number', None, 'show revision number'),
1468 ('n', 'number', None, 'show revision number'),
1469 ('c', 'changeset', None, 'show changeset'),
1469 ('c', 'changeset', None, 'show changeset'),
1470 ('I', 'include', [], 'include path in search'),
1470 ('I', 'include', [], 'include path in search'),
1471 ('X', 'exclude', [], 'exclude path from search')],
1471 ('X', 'exclude', [], 'exclude path from search')],
1472 'hg annotate [OPTION]... FILE...'),
1472 'hg annotate [OPTION]... FILE...'),
1473 "cat":
1473 "cat":
1474 (cat,
1474 (cat,
1475 [('o', 'output', "", 'output to file')],
1475 [('o', 'output', "", 'output to file')],
1476 'hg cat [-o OUTFILE] FILE [REV]'),
1476 'hg cat [-o OUTFILE] FILE [REV]'),
1477 "^clone":
1477 "^clone":
1478 (clone,
1478 (clone,
1479 [('U', 'noupdate', None, 'skip update after cloning'),
1479 [('U', 'noupdate', None, 'skip update after cloning'),
1480 ('e', 'ssh', "", 'ssh command'),
1480 ('e', 'ssh', "", 'ssh command'),
1481 ('', 'remotecmd', "", 'remote hg command')],
1481 ('', 'remotecmd', "", 'remote hg command')],
1482 'hg clone [OPTION]... SOURCE [DEST]'),
1482 'hg clone [OPTION]... SOURCE [DEST]'),
1483 "^commit|ci":
1483 "^commit|ci":
1484 (commit,
1484 (commit,
1485 [('A', 'addremove', None, 'run add/remove during commit'),
1485 [('A', 'addremove', None, 'run add/remove during commit'),
1486 ('I', 'include', [], 'include path in search'),
1486 ('I', 'include', [], 'include path in search'),
1487 ('X', 'exclude', [], 'exclude path from search'),
1487 ('X', 'exclude', [], 'exclude path from search'),
1488 ('m', 'message', "", 'commit message'),
1488 ('m', 'message', "", 'commit message'),
1489 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1489 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1490 ('l', 'logfile', "", 'commit message file'),
1490 ('l', 'logfile', "", 'commit message file'),
1491 ('d', 'date', "", 'date code'),
1491 ('d', 'date', "", 'date code'),
1492 ('u', 'user', "", 'user')],
1492 ('u', 'user', "", 'user')],
1493 'hg commit [OPTION]... [FILE]...'),
1493 'hg commit [OPTION]... [FILE]...'),
1494 "copy": (copy, [], 'hg copy SOURCE DEST'),
1494 "copy": (copy, [], 'hg copy SOURCE DEST'),
1495 "debugcheckstate": (debugcheckstate, [], 'debugcheckstate'),
1495 "debugcheckstate": (debugcheckstate, [], 'debugcheckstate'),
1496 "debugconfig": (debugconfig, [], 'debugconfig'),
1496 "debugconfig": (debugconfig, [], 'debugconfig'),
1497 "debugstate": (debugstate, [], 'debugstate'),
1497 "debugstate": (debugstate, [], 'debugstate'),
1498 "debugdata": (debugdata, [], 'debugdata FILE REV'),
1498 "debugdata": (debugdata, [], 'debugdata FILE REV'),
1499 "debugindex": (debugindex, [], 'debugindex FILE'),
1499 "debugindex": (debugindex, [], 'debugindex FILE'),
1500 "debugindexdot": (debugindexdot, [], 'debugindexdot FILE'),
1500 "debugindexdot": (debugindexdot, [], 'debugindexdot FILE'),
1501 "debugwalk":
1501 "debugwalk":
1502 (debugwalk,
1502 (debugwalk,
1503 [('I', 'include', [], 'include path in search'),
1503 [('I', 'include', [], 'include path in search'),
1504 ('X', 'exclude', [], 'exclude path from search')],
1504 ('X', 'exclude', [], 'exclude path from search')],
1505 'debugwalk [OPTION]... [FILE]...'),
1505 'debugwalk [OPTION]... [FILE]...'),
1506 "^diff":
1506 "^diff":
1507 (diff,
1507 (diff,
1508 [('r', 'rev', [], 'revision'),
1508 [('r', 'rev', [], 'revision'),
1509 ('a', 'text', None, 'treat all files as text'),
1509 ('a', 'text', None, 'treat all files as text'),
1510 ('I', 'include', [], 'include path in search'),
1510 ('I', 'include', [], 'include path in search'),
1511 ('X', 'exclude', [], 'exclude path from search')],
1511 ('X', 'exclude', [], 'exclude path from search')],
1512 'hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...'),
1512 'hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...'),
1513 "^export":
1513 "^export":
1514 (export,
1514 (export,
1515 [('o', 'output', "", 'output to file'),
1515 [('o', 'output', "", 'output to file'),
1516 ('a', 'text', None, 'treat all files as text')],
1516 ('a', 'text', None, 'treat all files as text')],
1517 "hg export [-a] [-o OUTFILE] REV..."),
1517 "hg export [-a] [-o OUTFILE] REV..."),
1518 "forget":
1518 "forget":
1519 (forget,
1519 (forget,
1520 [('I', 'include', [], 'include path in search'),
1520 [('I', 'include', [], 'include path in search'),
1521 ('X', 'exclude', [], 'exclude path from search')],
1521 ('X', 'exclude', [], 'exclude path from search')],
1522 "hg forget [OPTION]... FILE..."),
1522 "hg forget [OPTION]... FILE..."),
1523 "grep": (grep,
1523 "grep": (grep,
1524 [('0', 'print0', None, 'terminate file names with NUL'),
1524 [('0', 'print0', None, 'terminate file names with NUL'),
1525 ('I', 'include', [], 'include path in search'),
1525 ('I', 'include', [], 'include path in search'),
1526 ('X', 'exclude', [], 'include path in search'),
1526 ('X', 'exclude', [], 'include path in search'),
1527 ('Z', 'null', None, 'terminate file names with NUL'),
1527 ('Z', 'null', None, 'terminate file names with NUL'),
1528 ('a', 'all-revs', '', 'search all revs'),
1528 ('a', 'all-revs', '', 'search all revs'),
1529 ('e', 'regexp', '', 'pattern to search for'),
1529 ('e', 'regexp', '', 'pattern to search for'),
1530 ('f', 'full-path', None, 'print complete paths'),
1530 ('f', 'full-path', None, 'print complete paths'),
1531 ('i', 'ignore-case', None, 'ignore case when matching'),
1531 ('i', 'ignore-case', None, 'ignore case when matching'),
1532 ('l', 'files-with-matches', None, 'print names of files with matches'),
1532 ('l', 'files-with-matches', None, 'print names of files with matches'),
1533 ('n', 'line-number', '', 'print line numbers'),
1533 ('n', 'line-number', '', 'print line numbers'),
1534 ('r', 'rev', [], 'search in revision rev'),
1534 ('r', 'rev', [], 'search in revision rev'),
1535 ('s', 'no-messages', None, 'do not print error messages'),
1535 ('s', 'no-messages', None, 'do not print error messages'),
1536 ('v', 'invert-match', None, 'select non-matching lines')],
1536 ('v', 'invert-match', None, 'select non-matching lines')],
1537 "hg grep [options] [pat] [files]"),
1537 "hg grep [options] [pat] [files]"),
1538 "heads":
1538 "heads":
1539 (heads,
1539 (heads,
1540 [('b', 'branches', None, 'find branch info')],
1540 [('b', 'branches', None, 'find branch info')],
1541 'hg heads [-b]'),
1541 'hg heads [-b]'),
1542 "help": (help_, [], 'hg help [COMMAND]'),
1542 "help": (help_, [], 'hg help [COMMAND]'),
1543 "identify|id": (identify, [], 'hg identify'),
1543 "identify|id": (identify, [], 'hg identify'),
1544 "import|patch":
1544 "import|patch":
1545 (import_,
1545 (import_,
1546 [('p', 'strip', 1, 'path strip'),
1546 [('p', 'strip', 1, 'path strip'),
1547 ('f', 'force', None, 'skip check for outstanding changes'),
1547 ('f', 'force', None, 'skip check for outstanding changes'),
1548 ('b', 'base', "", 'base path')],
1548 ('b', 'base', "", 'base path')],
1549 "hg import [-f] [-p NUM] [-b BASE] PATCH..."),
1549 "hg import [-f] [-p NUM] [-b BASE] PATCH..."),
1550 "incoming|in": (incoming, [], 'hg incoming [SOURCE]'),
1550 "incoming|in": (incoming, [], 'hg incoming [SOURCE]'),
1551 "^init": (init, [], 'hg init [DEST]'),
1551 "^init": (init, [], 'hg init [DEST]'),
1552 "locate":
1552 "locate":
1553 (locate,
1553 (locate,
1554 [('r', 'rev', '', 'revision'),
1554 [('r', 'rev', '', 'revision'),
1555 ('0', 'print0', None, 'end records with NUL'),
1555 ('0', 'print0', None, 'end records with NUL'),
1556 ('f', 'fullpath', None, 'print complete paths'),
1556 ('f', 'fullpath', None, 'print complete paths'),
1557 ('I', 'include', [], 'include path in search'),
1557 ('I', 'include', [], 'include path in search'),
1558 ('X', 'exclude', [], 'exclude path from search')],
1558 ('X', 'exclude', [], 'exclude path from search')],
1559 'hg locate [OPTION]... [PATTERN]...'),
1559 'hg locate [OPTION]... [PATTERN]...'),
1560 "^log|history":
1560 "^log|history":
1561 (log,
1561 (log,
1562 [('I', 'include', [], 'include path in search'),
1562 [('I', 'include', [], 'include path in search'),
1563 ('X', 'exclude', [], 'exclude path from search'),
1563 ('X', 'exclude', [], 'exclude path from search'),
1564 ('r', 'rev', [], 'revision'),
1564 ('r', 'rev', [], 'revision'),
1565 ('p', 'patch', None, 'show patch')],
1565 ('p', 'patch', None, 'show patch')],
1566 'hg log [-I] [-X] [-r REV]... [-p] [FILE]'),
1566 'hg log [-I] [-X] [-r REV]... [-p] [FILE]'),
1567 "manifest": (manifest, [], 'hg manifest [REV]'),
1567 "manifest": (manifest, [], 'hg manifest [REV]'),
1568 "outgoing|out": (outgoing, [], 'hg outgoing [DEST]'),
1568 "outgoing|out": (outgoing, [], 'hg outgoing [DEST]'),
1569 "parents": (parents, [], 'hg parents [REV]'),
1569 "parents": (parents, [], 'hg parents [REV]'),
1570 "paths": (paths, [], 'hg paths [NAME]'),
1570 "paths": (paths, [], 'hg paths [NAME]'),
1571 "^pull":
1571 "^pull":
1572 (pull,
1572 (pull,
1573 [('u', 'update', None, 'update working directory'),
1573 [('u', 'update', None, 'update working directory'),
1574 ('e', 'ssh', "", 'ssh command'),
1574 ('e', 'ssh', "", 'ssh command'),
1575 ('', 'remotecmd', "", 'remote hg command')],
1575 ('', 'remotecmd', "", 'remote hg command')],
1576 'hg pull [-u] [-e FILE] [--remotecmd FILE] [SOURCE]'),
1576 'hg pull [-u] [-e FILE] [--remotecmd FILE] [SOURCE]'),
1577 "^push":
1577 "^push":
1578 (push,
1578 (push,
1579 [('f', 'force', None, 'force push'),
1579 [('f', 'force', None, 'force push'),
1580 ('e', 'ssh', "", 'ssh command'),
1580 ('e', 'ssh', "", 'ssh command'),
1581 ('', 'remotecmd', "", 'remote hg command')],
1581 ('', 'remotecmd', "", 'remote hg command')],
1582 'hg push [-f] [-e FILE] [--remotecmd FILE] [DEST]'),
1582 'hg push [-f] [-e FILE] [--remotecmd FILE] [DEST]'),
1583 "rawcommit":
1583 "rawcommit":
1584 (rawcommit,
1584 (rawcommit,
1585 [('p', 'parent', [], 'parent'),
1585 [('p', 'parent', [], 'parent'),
1586 ('d', 'date', "", 'date code'),
1586 ('d', 'date', "", 'date code'),
1587 ('u', 'user', "", 'user'),
1587 ('u', 'user', "", 'user'),
1588 ('F', 'files', "", 'file list'),
1588 ('F', 'files', "", 'file list'),
1589 ('m', 'message', "", 'commit message'),
1589 ('m', 'message', "", 'commit message'),
1590 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1590 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1591 ('l', 'logfile', "", 'commit message file')],
1591 ('l', 'logfile', "", 'commit message file')],
1592 'hg rawcommit [OPTION]... [FILE]...'),
1592 'hg rawcommit [OPTION]... [FILE]...'),
1593 "recover": (recover, [], "hg recover"),
1593 "recover": (recover, [], "hg recover"),
1594 "^remove|rm": (remove, [], "hg remove FILE..."),
1594 "^remove|rm": (remove, [], "hg remove FILE..."),
1595 "^revert":
1595 "^revert":
1596 (revert,
1596 (revert,
1597 [("n", "nonrecursive", None, "don't recurse into subdirs"),
1597 [("n", "nonrecursive", None, "don't recurse into subdirs"),
1598 ("r", "rev", "", "revision")],
1598 ("r", "rev", "", "revision")],
1599 "hg revert [-n] [-r REV] [NAME]..."),
1599 "hg revert [-n] [-r REV] [NAME]..."),
1600 "root": (root, [], "hg root"),
1600 "root": (root, [], "hg root"),
1601 "^serve":
1601 "^serve":
1602 (serve,
1602 (serve,
1603 [('A', 'accesslog', '', 'access log file'),
1603 [('A', 'accesslog', '', 'access log file'),
1604 ('E', 'errorlog', '', 'error log file'),
1604 ('E', 'errorlog', '', 'error log file'),
1605 ('p', 'port', 0, 'listen port'),
1605 ('p', 'port', 0, 'listen port'),
1606 ('a', 'address', '', 'interface address'),
1606 ('a', 'address', '', 'interface address'),
1607 ('n', 'name', "", 'repository name'),
1607 ('n', 'name', "", 'repository name'),
1608 ('', 'stdio', None, 'for remote clients'),
1608 ('', 'stdio', None, 'for remote clients'),
1609 ('t', 'templates', "", 'template directory'),
1609 ('t', 'templates', "", 'template directory'),
1610 ('', 'style', "", 'template style'),
1610 ('', 'style', "", 'template style'),
1611 ('6', 'ipv6', None, 'use IPv6 in addition to IPv4')],
1611 ('6', 'ipv6', None, 'use IPv6 in addition to IPv4')],
1612 "hg serve [OPTION]..."),
1612 "hg serve [OPTION]..."),
1613 "^status":
1613 "^status":
1614 (status,
1614 (status,
1615 [('m', 'modified', None, 'show only modified files'),
1615 [('m', 'modified', None, 'show only modified files'),
1616 ('a', 'added', None, 'show only added files'),
1616 ('a', 'added', None, 'show only added files'),
1617 ('r', 'removed', None, 'show only removed files'),
1617 ('r', 'removed', None, 'show only removed files'),
1618 ('u', 'unknown', None, 'show only unknown (not tracked) files'),
1618 ('u', 'unknown', None, 'show only unknown (not tracked) files'),
1619 ('I', 'include', [], 'include path in search'),
1619 ('I', 'include', [], 'include path in search'),
1620 ('X', 'exclude', [], 'exclude path from search')],
1620 ('X', 'exclude', [], 'exclude path from search')],
1621 "hg status [OPTION]... [FILE]..."),
1621 "hg status [OPTION]... [FILE]..."),
1622 "tag":
1622 "tag":
1623 (tag,
1623 (tag,
1624 [('l', 'local', None, 'make the tag local'),
1624 [('l', 'local', None, 'make the tag local'),
1625 ('m', 'message', "", 'commit message'),
1625 ('m', 'message', "", 'commit message'),
1626 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1626 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1627 ('d', 'date', "", 'date code'),
1627 ('d', 'date', "", 'date code'),
1628 ('u', 'user', "", 'user')],
1628 ('u', 'user', "", 'user')],
1629 'hg tag [OPTION]... NAME [REV]'),
1629 'hg tag [OPTION]... NAME [REV]'),
1630 "tags": (tags, [], 'hg tags'),
1630 "tags": (tags, [], 'hg tags'),
1631 "tip": (tip, [], 'hg tip'),
1631 "tip": (tip, [], 'hg tip'),
1632 "undo": (undo, [], 'hg undo'),
1632 "undo": (undo, [], 'hg undo'),
1633 "^update|up|checkout|co":
1633 "^update|up|checkout|co":
1634 (update,
1634 (update,
1635 [('b', 'branch', "", 'checkout the head of a specific branch'),
1635 [('b', 'branch', "", 'checkout the head of a specific branch'),
1636 ('m', 'merge', None, 'allow merging of conflicts'),
1636 ('m', 'merge', None, 'allow merging of conflicts'),
1637 ('C', 'clean', None, 'overwrite locally modified files')],
1637 ('C', 'clean', None, 'overwrite locally modified files')],
1638 'hg update [-b TAG] [-m] [-C] [REV]'),
1638 'hg update [-b TAG] [-m] [-C] [REV]'),
1639 "verify": (verify, [], 'hg verify'),
1639 "verify": (verify, [], 'hg verify'),
1640 "version": (show_version, [], 'hg version'),
1640 "version": (show_version, [], 'hg version'),
1641 }
1641 }
1642
1642
1643 globalopts = [
1643 globalopts = [
1644 ('R', 'repository', "", 'repository root directory'),
1644 ('R', 'repository', "", 'repository root directory'),
1645 ('', 'cwd', '', 'change working directory'),
1645 ('', 'cwd', '', 'change working directory'),
1646 ('y', 'noninteractive', None, 'run non-interactively'),
1646 ('y', 'noninteractive', None, 'run non-interactively'),
1647 ('q', 'quiet', None, 'quiet mode'),
1647 ('q', 'quiet', None, 'quiet mode'),
1648 ('v', 'verbose', None, 'verbose mode'),
1648 ('v', 'verbose', None, 'verbose mode'),
1649 ('', 'debug', None, 'debug mode'),
1649 ('', 'debug', None, 'debug mode'),
1650 ('', 'traceback', None, 'print traceback on exception'),
1650 ('', 'traceback', None, 'print traceback on exception'),
1651 ('', 'time', None, 'time how long the command takes'),
1651 ('', 'time', None, 'time how long the command takes'),
1652 ('', 'profile', None, 'profile'),
1652 ('', 'profile', None, 'profile'),
1653 ('', 'version', None, 'output version information and exit'),
1653 ('', 'version', None, 'output version information and exit'),
1654 ('h', 'help', None, 'display help and exit'),
1654 ('h', 'help', None, 'display help and exit'),
1655 ]
1655 ]
1656
1656
1657 norepo = "clone init version help debugconfig debugdata" + \
1657 norepo = "clone init version help debugconfig debugdata" + \
1658 " debugindex debugindexdot paths"
1658 " debugindex debugindexdot paths"
1659
1659
1660 def find(cmd):
1660 def find(cmd):
1661 for e in table.keys():
1661 for e in table.keys():
1662 if re.match("(%s)$" % e, cmd):
1662 if re.match("(%s)$" % e, cmd):
1663 return e, table[e]
1663 return e, table[e]
1664
1664
1665 raise UnknownCommand(cmd)
1665 raise UnknownCommand(cmd)
1666
1666
1667 class SignalInterrupt(Exception):
1667 class SignalInterrupt(Exception):
1668 """Exception raised on SIGTERM and SIGHUP."""
1668 """Exception raised on SIGTERM and SIGHUP."""
1669
1669
1670 def catchterm(*args):
1670 def catchterm(*args):
1671 raise SignalInterrupt
1671 raise SignalInterrupt
1672
1672
1673 def run():
1673 def run():
1674 sys.exit(dispatch(sys.argv[1:]))
1674 sys.exit(dispatch(sys.argv[1:]))
1675
1675
1676 class ParseError(Exception):
1676 class ParseError(Exception):
1677 """Exception raised on errors in parsing the command line."""
1677 """Exception raised on errors in parsing the command line."""
1678
1678
1679 def parse(args):
1679 def parse(args):
1680 options = {}
1680 options = {}
1681 cmdoptions = {}
1681 cmdoptions = {}
1682
1682
1683 try:
1683 try:
1684 args = fancyopts.fancyopts(args, globalopts, options)
1684 args = fancyopts.fancyopts(args, globalopts, options)
1685 except fancyopts.getopt.GetoptError, inst:
1685 except fancyopts.getopt.GetoptError, inst:
1686 raise ParseError(None, inst)
1686 raise ParseError(None, inst)
1687
1687
1688 if args:
1688 if args:
1689 cmd, args = args[0], args[1:]
1689 cmd, args = args[0], args[1:]
1690 i = find(cmd)[1]
1690 i = find(cmd)[1]
1691 c = list(i[1])
1691 c = list(i[1])
1692 else:
1692 else:
1693 cmd = None
1693 cmd = None
1694 c = []
1694 c = []
1695
1695
1696 # combine global options into local
1696 # combine global options into local
1697 for o in globalopts:
1697 for o in globalopts:
1698 c.append((o[0], o[1], options[o[1]], o[3]))
1698 c.append((o[0], o[1], options[o[1]], o[3]))
1699
1699
1700 try:
1700 try:
1701 args = fancyopts.fancyopts(args, c, cmdoptions)
1701 args = fancyopts.fancyopts(args, c, cmdoptions)
1702 except fancyopts.getopt.GetoptError, inst:
1702 except fancyopts.getopt.GetoptError, inst:
1703 raise ParseError(cmd, inst)
1703 raise ParseError(cmd, inst)
1704
1704
1705 # separate global options back out
1705 # separate global options back out
1706 for o in globalopts:
1706 for o in globalopts:
1707 n = o[1]
1707 n = o[1]
1708 options[n] = cmdoptions[n]
1708 options[n] = cmdoptions[n]
1709 del cmdoptions[n]
1709 del cmdoptions[n]
1710
1710
1711 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
1711 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
1712
1712
1713 def dispatch(args):
1713 def dispatch(args):
1714 signal.signal(signal.SIGTERM, catchterm)
1714 signal.signal(signal.SIGTERM, catchterm)
1715 try:
1715 try:
1716 signal.signal(signal.SIGHUP, catchterm)
1716 signal.signal(signal.SIGHUP, catchterm)
1717 except AttributeError:
1717 except AttributeError:
1718 pass
1718 pass
1719
1719
1720 try:
1720 try:
1721 cmd, func, args, options, cmdoptions = parse(args)
1721 cmd, func, args, options, cmdoptions = parse(args)
1722 except ParseError, inst:
1722 except ParseError, inst:
1723 u = ui.ui()
1723 u = ui.ui()
1724 if inst.args[0]:
1724 if inst.args[0]:
1725 u.warn("hg %s: %s\n" % (inst.args[0], inst.args[1]))
1725 u.warn("hg %s: %s\n" % (inst.args[0], inst.args[1]))
1726 help_(u, inst.args[0])
1726 help_(u, inst.args[0])
1727 else:
1727 else:
1728 u.warn("hg: %s\n" % inst.args[1])
1728 u.warn("hg: %s\n" % inst.args[1])
1729 help_(u, 'shortlist')
1729 help_(u, 'shortlist')
1730 sys.exit(-1)
1730 sys.exit(-1)
1731 except UnknownCommand, inst:
1731 except UnknownCommand, inst:
1732 u = ui.ui()
1732 u = ui.ui()
1733 u.warn("hg: unknown command '%s'\n" % inst.args[0])
1733 u.warn("hg: unknown command '%s'\n" % inst.args[0])
1734 help_(u, 'shortlist')
1734 help_(u, 'shortlist')
1735 sys.exit(1)
1735 sys.exit(1)
1736
1736
1737 if options["time"]:
1737 if options["time"]:
1738 def get_times():
1738 def get_times():
1739 t = os.times()
1739 t = os.times()
1740 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
1740 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
1741 t = (t[0], t[1], t[2], t[3], time.clock())
1741 t = (t[0], t[1], t[2], t[3], time.clock())
1742 return t
1742 return t
1743 s = get_times()
1743 s = get_times()
1744 def print_time():
1744 def print_time():
1745 t = get_times()
1745 t = get_times()
1746 u = ui.ui()
1746 u = ui.ui()
1747 u.warn("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n" %
1747 u.warn("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n" %
1748 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
1748 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
1749 atexit.register(print_time)
1749 atexit.register(print_time)
1750
1750
1751 u = ui.ui(options["verbose"], options["debug"], options["quiet"],
1751 u = ui.ui(options["verbose"], options["debug"], options["quiet"],
1752 not options["noninteractive"])
1752 not options["noninteractive"])
1753
1753
1754 try:
1754 try:
1755 try:
1755 try:
1756 if options['help']:
1756 if options['help']:
1757 help_(u, cmd, options['version'])
1757 help_(u, cmd, options['version'])
1758 sys.exit(0)
1758 sys.exit(0)
1759 elif options['version']:
1759 elif options['version']:
1760 show_version(u)
1760 show_version(u)
1761 sys.exit(0)
1761 sys.exit(0)
1762 elif not cmd:
1762 elif not cmd:
1763 help_(u, 'shortlist')
1763 help_(u, 'shortlist')
1764 sys.exit(0)
1764 sys.exit(0)
1765
1765
1766 if options['cwd']:
1766 if options['cwd']:
1767 try:
1767 try:
1768 os.chdir(options['cwd'])
1768 os.chdir(options['cwd'])
1769 except OSError, inst:
1769 except OSError, inst:
1770 u.warn('abort: %s: %s\n' % (options['cwd'], inst.strerror))
1770 u.warn('abort: %s: %s\n' % (options['cwd'], inst.strerror))
1771 sys.exit(1)
1771 sys.exit(1)
1772
1772
1773 if cmd not in norepo.split():
1773 if cmd not in norepo.split():
1774 path = options["repository"] or ""
1774 path = options["repository"] or ""
1775 repo = hg.repository(ui=u, path=path)
1775 repo = hg.repository(ui=u, path=path)
1776 d = lambda: func(u, repo, *args, **cmdoptions)
1776 d = lambda: func(u, repo, *args, **cmdoptions)
1777 else:
1777 else:
1778 d = lambda: func(u, *args, **cmdoptions)
1778 d = lambda: func(u, *args, **cmdoptions)
1779
1779
1780 if options['profile']:
1780 if options['profile']:
1781 import hotshot, hotshot.stats
1781 import hotshot, hotshot.stats
1782 prof = hotshot.Profile("hg.prof")
1782 prof = hotshot.Profile("hg.prof")
1783 r = prof.runcall(d)
1783 r = prof.runcall(d)
1784 prof.close()
1784 prof.close()
1785 stats = hotshot.stats.load("hg.prof")
1785 stats = hotshot.stats.load("hg.prof")
1786 stats.strip_dirs()
1786 stats.strip_dirs()
1787 stats.sort_stats('time', 'calls')
1787 stats.sort_stats('time', 'calls')
1788 stats.print_stats(40)
1788 stats.print_stats(40)
1789 return r
1789 return r
1790 else:
1790 else:
1791 return d()
1791 return d()
1792 except:
1792 except:
1793 if options['traceback']:
1793 if options['traceback']:
1794 traceback.print_exc()
1794 traceback.print_exc()
1795 raise
1795 raise
1796 except hg.RepoError, inst:
1796 except hg.RepoError, inst:
1797 u.warn("abort: ", inst, "!\n")
1797 u.warn("abort: ", inst, "!\n")
1798 except SignalInterrupt:
1798 except SignalInterrupt:
1799 u.warn("killed!\n")
1799 u.warn("killed!\n")
1800 except KeyboardInterrupt:
1800 except KeyboardInterrupt:
1801 try:
1801 try:
1802 u.warn("interrupted!\n")
1802 u.warn("interrupted!\n")
1803 except IOError, inst:
1803 except IOError, inst:
1804 if inst.errno == errno.EPIPE:
1804 if inst.errno == errno.EPIPE:
1805 if u.debugflag:
1805 if u.debugflag:
1806 u.warn("\nbroken pipe\n")
1806 u.warn("\nbroken pipe\n")
1807 else:
1807 else:
1808 raise
1808 raise
1809 except IOError, inst:
1809 except IOError, inst:
1810 if hasattr(inst, "code"):
1810 if hasattr(inst, "code"):
1811 u.warn("abort: %s\n" % inst)
1811 u.warn("abort: %s\n" % inst)
1812 elif hasattr(inst, "reason"):
1812 elif hasattr(inst, "reason"):
1813 u.warn("abort: error: %s\n" % inst.reason[1])
1813 u.warn("abort: error: %s\n" % inst.reason[1])
1814 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
1814 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
1815 if u.debugflag: u.warn("broken pipe\n")
1815 if u.debugflag: u.warn("broken pipe\n")
1816 else:
1816 else:
1817 raise
1817 raise
1818 except OSError, inst:
1818 except OSError, inst:
1819 if hasattr(inst, "filename"):
1819 if hasattr(inst, "filename"):
1820 u.warn("abort: %s: %s\n" % (inst.strerror, inst.filename))
1820 u.warn("abort: %s: %s\n" % (inst.strerror, inst.filename))
1821 else:
1821 else:
1822 u.warn("abort: %s\n" % inst.strerror)
1822 u.warn("abort: %s\n" % inst.strerror)
1823 except util.Abort, inst:
1823 except util.Abort, inst:
1824 u.warn('abort: ', inst.args[0] % inst.args[1:], '\n')
1824 u.warn('abort: ', inst.args[0] % inst.args[1:], '\n')
1825 sys.exit(1)
1825 sys.exit(1)
1826 except TypeError, inst:
1826 except TypeError, inst:
1827 # was this an argument error?
1827 # was this an argument error?
1828 tb = traceback.extract_tb(sys.exc_info()[2])
1828 tb = traceback.extract_tb(sys.exc_info()[2])
1829 if len(tb) > 2: # no
1829 if len(tb) > 2: # no
1830 raise
1830 raise
1831 u.debug(inst, "\n")
1831 u.debug(inst, "\n")
1832 u.warn("%s: invalid arguments\n" % cmd)
1832 u.warn("%s: invalid arguments\n" % cmd)
1833 help_(u, cmd)
1833 help_(u, cmd)
1834 except UnknownCommand, inst:
1834 except UnknownCommand, inst:
1835 u.warn("hg: unknown command '%s'\n" % inst.args[0])
1835 u.warn("hg: unknown command '%s'\n" % inst.args[0])
1836 help_(u, 'shortlist')
1836 help_(u, 'shortlist')
1837
1837
1838 sys.exit(-1)
1838 sys.exit(-1)
@@ -1,2294 +1,2294
1 # hg.py - repository classes for mercurial
1 # hg.py - repository classes for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 import sys, struct, os
8 import sys, struct, os
9 import util
9 import util
10 from revlog import *
10 from revlog import *
11 from demandload import *
11 from demandload import *
12 demandload(globals(), "re lock urllib urllib2 transaction time socket")
12 demandload(globals(), "re lock urllib urllib2 transaction time socket")
13 demandload(globals(), "tempfile httprangereader bdiff urlparse")
13 demandload(globals(), "tempfile httprangereader bdiff urlparse")
14 demandload(globals(), "bisect errno select stat")
14 demandload(globals(), "bisect errno select stat")
15
15
16 class filelog(revlog):
16 class filelog(revlog):
17 def __init__(self, opener, path):
17 def __init__(self, opener, path):
18 revlog.__init__(self, opener,
18 revlog.__init__(self, opener,
19 os.path.join("data", self.encodedir(path + ".i")),
19 os.path.join("data", self.encodedir(path + ".i")),
20 os.path.join("data", self.encodedir(path + ".d")))
20 os.path.join("data", self.encodedir(path + ".d")))
21
21
22 # This avoids a collision between a file named foo and a dir named
22 # This avoids a collision between a file named foo and a dir named
23 # foo.i or foo.d
23 # foo.i or foo.d
24 def encodedir(self, path):
24 def encodedir(self, path):
25 return (path
25 return (path
26 .replace(".hg/", ".hg.hg/")
26 .replace(".hg/", ".hg.hg/")
27 .replace(".i/", ".i.hg/")
27 .replace(".i/", ".i.hg/")
28 .replace(".d/", ".d.hg/"))
28 .replace(".d/", ".d.hg/"))
29
29
30 def decodedir(self, path):
30 def decodedir(self, path):
31 return (path
31 return (path
32 .replace(".d.hg/", ".d/")
32 .replace(".d.hg/", ".d/")
33 .replace(".i.hg/", ".i/")
33 .replace(".i.hg/", ".i/")
34 .replace(".hg.hg/", ".hg/"))
34 .replace(".hg.hg/", ".hg/"))
35
35
36 def read(self, node):
36 def read(self, node):
37 t = self.revision(node)
37 t = self.revision(node)
38 if not t.startswith('\1\n'):
38 if not t.startswith('\1\n'):
39 return t
39 return t
40 s = t.find('\1\n', 2)
40 s = t.find('\1\n', 2)
41 return t[s+2:]
41 return t[s+2:]
42
42
43 def readmeta(self, node):
43 def readmeta(self, node):
44 t = self.revision(node)
44 t = self.revision(node)
45 if not t.startswith('\1\n'):
45 if not t.startswith('\1\n'):
46 return t
46 return t
47 s = t.find('\1\n', 2)
47 s = t.find('\1\n', 2)
48 mt = t[2:s]
48 mt = t[2:s]
49 for l in mt.splitlines():
49 for l in mt.splitlines():
50 k, v = l.split(": ", 1)
50 k, v = l.split(": ", 1)
51 m[k] = v
51 m[k] = v
52 return m
52 return m
53
53
54 def add(self, text, meta, transaction, link, p1=None, p2=None):
54 def add(self, text, meta, transaction, link, p1=None, p2=None):
55 if meta or text.startswith('\1\n'):
55 if meta or text.startswith('\1\n'):
56 mt = ""
56 mt = ""
57 if meta:
57 if meta:
58 mt = [ "%s: %s\n" % (k, v) for k,v in meta.items() ]
58 mt = [ "%s: %s\n" % (k, v) for k,v in meta.items() ]
59 text = "\1\n" + "".join(mt) + "\1\n" + text
59 text = "\1\n" + "".join(mt) + "\1\n" + text
60 return self.addrevision(text, transaction, link, p1, p2)
60 return self.addrevision(text, transaction, link, p1, p2)
61
61
62 def annotate(self, node):
62 def annotate(self, node):
63
63
64 def decorate(text, rev):
64 def decorate(text, rev):
65 return ([rev] * len(text.splitlines()), text)
65 return ([rev] * len(text.splitlines()), text)
66
66
67 def pair(parent, child):
67 def pair(parent, child):
68 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
68 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
69 child[0][b1:b2] = parent[0][a1:a2]
69 child[0][b1:b2] = parent[0][a1:a2]
70 return child
70 return child
71
71
72 # find all ancestors
72 # find all ancestors
73 needed = {node:1}
73 needed = {node:1}
74 visit = [node]
74 visit = [node]
75 while visit:
75 while visit:
76 n = visit.pop(0)
76 n = visit.pop(0)
77 for p in self.parents(n):
77 for p in self.parents(n):
78 if p not in needed:
78 if p not in needed:
79 needed[p] = 1
79 needed[p] = 1
80 visit.append(p)
80 visit.append(p)
81 else:
81 else:
82 # count how many times we'll use this
82 # count how many times we'll use this
83 needed[p] += 1
83 needed[p] += 1
84
84
85 # sort by revision which is a topological order
85 # sort by revision which is a topological order
86 visit = [ (self.rev(n), n) for n in needed.keys() ]
86 visit = [ (self.rev(n), n) for n in needed.keys() ]
87 visit.sort()
87 visit.sort()
88 hist = {}
88 hist = {}
89
89
90 for r,n in visit:
90 for r,n in visit:
91 curr = decorate(self.read(n), self.linkrev(n))
91 curr = decorate(self.read(n), self.linkrev(n))
92 for p in self.parents(n):
92 for p in self.parents(n):
93 if p != nullid:
93 if p != nullid:
94 curr = pair(hist[p], curr)
94 curr = pair(hist[p], curr)
95 # trim the history of unneeded revs
95 # trim the history of unneeded revs
96 needed[p] -= 1
96 needed[p] -= 1
97 if not needed[p]:
97 if not needed[p]:
98 del hist[p]
98 del hist[p]
99 hist[n] = curr
99 hist[n] = curr
100
100
101 return zip(hist[n][0], hist[n][1].splitlines(1))
101 return zip(hist[n][0], hist[n][1].splitlines(1))
102
102
103 class manifest(revlog):
103 class manifest(revlog):
104 def __init__(self, opener):
104 def __init__(self, opener):
105 self.mapcache = None
105 self.mapcache = None
106 self.listcache = None
106 self.listcache = None
107 self.addlist = None
107 self.addlist = None
108 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
108 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
109
109
110 def read(self, node):
110 def read(self, node):
111 if node == nullid: return {} # don't upset local cache
111 if node == nullid: return {} # don't upset local cache
112 if self.mapcache and self.mapcache[0] == node:
112 if self.mapcache and self.mapcache[0] == node:
113 return self.mapcache[1]
113 return self.mapcache[1]
114 text = self.revision(node)
114 text = self.revision(node)
115 map = {}
115 map = {}
116 flag = {}
116 flag = {}
117 self.listcache = (text, text.splitlines(1))
117 self.listcache = (text, text.splitlines(1))
118 for l in self.listcache[1]:
118 for l in self.listcache[1]:
119 (f, n) = l.split('\0')
119 (f, n) = l.split('\0')
120 map[f] = bin(n[:40])
120 map[f] = bin(n[:40])
121 flag[f] = (n[40:-1] == "x")
121 flag[f] = (n[40:-1] == "x")
122 self.mapcache = (node, map, flag)
122 self.mapcache = (node, map, flag)
123 return map
123 return map
124
124
125 def readflags(self, node):
125 def readflags(self, node):
126 if node == nullid: return {} # don't upset local cache
126 if node == nullid: return {} # don't upset local cache
127 if not self.mapcache or self.mapcache[0] != node:
127 if not self.mapcache or self.mapcache[0] != node:
128 self.read(node)
128 self.read(node)
129 return self.mapcache[2]
129 return self.mapcache[2]
130
130
131 def diff(self, a, b):
131 def diff(self, a, b):
132 # this is sneaky, as we're not actually using a and b
132 # this is sneaky, as we're not actually using a and b
133 if self.listcache and self.addlist and self.listcache[0] == a:
133 if self.listcache and self.addlist and self.listcache[0] == a:
134 d = mdiff.diff(self.listcache[1], self.addlist, 1)
134 d = mdiff.diff(self.listcache[1], self.addlist, 1)
135 if mdiff.patch(a, d) != b:
135 if mdiff.patch(a, d) != b:
136 sys.stderr.write("*** sortdiff failed, falling back ***\n")
136 sys.stderr.write("*** sortdiff failed, falling back ***\n")
137 return mdiff.textdiff(a, b)
137 return mdiff.textdiff(a, b)
138 return d
138 return d
139 else:
139 else:
140 return mdiff.textdiff(a, b)
140 return mdiff.textdiff(a, b)
141
141
142 def add(self, map, flags, transaction, link, p1=None, p2=None,
142 def add(self, map, flags, transaction, link, p1=None, p2=None,
143 changed=None):
143 changed=None):
144 # directly generate the mdiff delta from the data collected during
144 # directly generate the mdiff delta from the data collected during
145 # the bisect loop below
145 # the bisect loop below
146 def gendelta(delta):
146 def gendelta(delta):
147 i = 0
147 i = 0
148 result = []
148 result = []
149 while i < len(delta):
149 while i < len(delta):
150 start = delta[i][2]
150 start = delta[i][2]
151 end = delta[i][3]
151 end = delta[i][3]
152 l = delta[i][4]
152 l = delta[i][4]
153 if l == None:
153 if l == None:
154 l = ""
154 l = ""
155 while i < len(delta) - 1 and start <= delta[i+1][2] \
155 while i < len(delta) - 1 and start <= delta[i+1][2] \
156 and end >= delta[i+1][2]:
156 and end >= delta[i+1][2]:
157 if delta[i+1][3] > end:
157 if delta[i+1][3] > end:
158 end = delta[i+1][3]
158 end = delta[i+1][3]
159 if delta[i+1][4]:
159 if delta[i+1][4]:
160 l += delta[i+1][4]
160 l += delta[i+1][4]
161 i += 1
161 i += 1
162 result.append(struct.pack(">lll", start, end, len(l)) + l)
162 result.append(struct.pack(">lll", start, end, len(l)) + l)
163 i += 1
163 i += 1
164 return result
164 return result
165
165
166 # apply the changes collected during the bisect loop to our addlist
166 # apply the changes collected during the bisect loop to our addlist
167 def addlistdelta(addlist, delta):
167 def addlistdelta(addlist, delta):
168 # apply the deltas to the addlist. start from the bottom up
168 # apply the deltas to the addlist. start from the bottom up
169 # so changes to the offsets don't mess things up.
169 # so changes to the offsets don't mess things up.
170 i = len(delta)
170 i = len(delta)
171 while i > 0:
171 while i > 0:
172 i -= 1
172 i -= 1
173 start = delta[i][0]
173 start = delta[i][0]
174 end = delta[i][1]
174 end = delta[i][1]
175 if delta[i][4]:
175 if delta[i][4]:
176 addlist[start:end] = [delta[i][4]]
176 addlist[start:end] = [delta[i][4]]
177 else:
177 else:
178 del addlist[start:end]
178 del addlist[start:end]
179 return addlist
179 return addlist
180
180
181 # calculate the byte offset of the start of each line in the
181 # calculate the byte offset of the start of each line in the
182 # manifest
182 # manifest
183 def calcoffsets(addlist):
183 def calcoffsets(addlist):
184 offsets = [0] * (len(addlist) + 1)
184 offsets = [0] * (len(addlist) + 1)
185 offset = 0
185 offset = 0
186 i = 0
186 i = 0
187 while i < len(addlist):
187 while i < len(addlist):
188 offsets[i] = offset
188 offsets[i] = offset
189 offset += len(addlist[i])
189 offset += len(addlist[i])
190 i += 1
190 i += 1
191 offsets[i] = offset
191 offsets[i] = offset
192 return offsets
192 return offsets
193
193
194 # if we're using the listcache, make sure it is valid and
194 # if we're using the listcache, make sure it is valid and
195 # parented by the same node we're diffing against
195 # parented by the same node we're diffing against
196 if not changed or not self.listcache or not p1 or \
196 if not changed or not self.listcache or not p1 or \
197 self.mapcache[0] != p1:
197 self.mapcache[0] != p1:
198 files = map.keys()
198 files = map.keys()
199 files.sort()
199 files.sort()
200
200
201 self.addlist = ["%s\000%s%s\n" %
201 self.addlist = ["%s\000%s%s\n" %
202 (f, hex(map[f]), flags[f] and "x" or '')
202 (f, hex(map[f]), flags[f] and "x" or '')
203 for f in files]
203 for f in files]
204 cachedelta = None
204 cachedelta = None
205 else:
205 else:
206 addlist = self.listcache[1]
206 addlist = self.listcache[1]
207
207
208 # find the starting offset for each line in the add list
208 # find the starting offset for each line in the add list
209 offsets = calcoffsets(addlist)
209 offsets = calcoffsets(addlist)
210
210
211 # combine the changed lists into one list for sorting
211 # combine the changed lists into one list for sorting
212 work = [[x, 0] for x in changed[0]]
212 work = [[x, 0] for x in changed[0]]
213 work[len(work):] = [[x, 1] for x in changed[1]]
213 work[len(work):] = [[x, 1] for x in changed[1]]
214 work.sort()
214 work.sort()
215
215
216 delta = []
216 delta = []
217 bs = 0
217 bs = 0
218
218
219 for w in work:
219 for w in work:
220 f = w[0]
220 f = w[0]
221 # bs will either be the index of the item or the insert point
221 # bs will either be the index of the item or the insert point
222 bs = bisect.bisect(addlist, f, bs)
222 bs = bisect.bisect(addlist, f, bs)
223 if bs < len(addlist):
223 if bs < len(addlist):
224 fn = addlist[bs][:addlist[bs].index('\0')]
224 fn = addlist[bs][:addlist[bs].index('\0')]
225 else:
225 else:
226 fn = None
226 fn = None
227 if w[1] == 0:
227 if w[1] == 0:
228 l = "%s\000%s%s\n" % (f, hex(map[f]),
228 l = "%s\000%s%s\n" % (f, hex(map[f]),
229 flags[f] and "x" or '')
229 flags[f] and "x" or '')
230 else:
230 else:
231 l = None
231 l = None
232 start = bs
232 start = bs
233 if fn != f:
233 if fn != f:
234 # item not found, insert a new one
234 # item not found, insert a new one
235 end = bs
235 end = bs
236 if w[1] == 1:
236 if w[1] == 1:
237 sys.stderr.write("failed to remove %s from manifest\n"
237 sys.stderr.write("failed to remove %s from manifest\n"
238 % f)
238 % f)
239 sys.exit(1)
239 sys.exit(1)
240 else:
240 else:
241 # item is found, replace/delete the existing line
241 # item is found, replace/delete the existing line
242 end = bs + 1
242 end = bs + 1
243 delta.append([start, end, offsets[start], offsets[end], l])
243 delta.append([start, end, offsets[start], offsets[end], l])
244
244
245 self.addlist = addlistdelta(addlist, delta)
245 self.addlist = addlistdelta(addlist, delta)
246 if self.mapcache[0] == self.tip():
246 if self.mapcache[0] == self.tip():
247 cachedelta = "".join(gendelta(delta))
247 cachedelta = "".join(gendelta(delta))
248 else:
248 else:
249 cachedelta = None
249 cachedelta = None
250
250
251 text = "".join(self.addlist)
251 text = "".join(self.addlist)
252 if cachedelta and mdiff.patch(self.listcache[0], cachedelta) != text:
252 if cachedelta and mdiff.patch(self.listcache[0], cachedelta) != text:
253 sys.stderr.write("manifest delta failure\n")
253 sys.stderr.write("manifest delta failure\n")
254 sys.exit(1)
254 sys.exit(1)
255 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
255 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
256 self.mapcache = (n, map, flags)
256 self.mapcache = (n, map, flags)
257 self.listcache = (text, self.addlist)
257 self.listcache = (text, self.addlist)
258 self.addlist = None
258 self.addlist = None
259
259
260 return n
260 return n
261
261
262 class changelog(revlog):
262 class changelog(revlog):
263 def __init__(self, opener):
263 def __init__(self, opener):
264 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
264 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
265
265
266 def extract(self, text):
266 def extract(self, text):
267 if not text:
267 if not text:
268 return (nullid, "", "0", [], "")
268 return (nullid, "", "0", [], "")
269 last = text.index("\n\n")
269 last = text.index("\n\n")
270 desc = text[last + 2:]
270 desc = text[last + 2:]
271 l = text[:last].splitlines()
271 l = text[:last].splitlines()
272 manifest = bin(l[0])
272 manifest = bin(l[0])
273 user = l[1]
273 user = l[1]
274 date = l[2]
274 date = l[2]
275 if " " not in date:
275 if " " not in date:
276 date += " 0" # some tools used -d without a timezone
276 date += " 0" # some tools used -d without a timezone
277 files = l[3:]
277 files = l[3:]
278 return (manifest, user, date, files, desc)
278 return (manifest, user, date, files, desc)
279
279
280 def read(self, node):
280 def read(self, node):
281 return self.extract(self.revision(node))
281 return self.extract(self.revision(node))
282
282
283 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
283 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
284 user=None, date=None):
284 user=None, date=None):
285 if not date:
285 if not date:
286 if time.daylight: offset = time.altzone
286 if time.daylight: offset = time.altzone
287 else: offset = time.timezone
287 else: offset = time.timezone
288 date = "%d %d" % (time.time(), offset)
288 date = "%d %d" % (time.time(), offset)
289 list.sort()
289 list.sort()
290 l = [hex(manifest), user, date] + list + ["", desc]
290 l = [hex(manifest), user, date] + list + ["", desc]
291 text = "\n".join(l)
291 text = "\n".join(l)
292 return self.addrevision(text, transaction, self.count(), p1, p2)
292 return self.addrevision(text, transaction, self.count(), p1, p2)
293
293
294 class dirstate:
294 class dirstate:
295 def __init__(self, opener, ui, root):
295 def __init__(self, opener, ui, root):
296 self.opener = opener
296 self.opener = opener
297 self.root = root
297 self.root = root
298 self.dirty = 0
298 self.dirty = 0
299 self.ui = ui
299 self.ui = ui
300 self.map = None
300 self.map = None
301 self.pl = None
301 self.pl = None
302 self.copies = {}
302 self.copies = {}
303 self.ignorefunc = None
303 self.ignorefunc = None
304
304
305 def wjoin(self, f):
305 def wjoin(self, f):
306 return os.path.join(self.root, f)
306 return os.path.join(self.root, f)
307
307
308 def getcwd(self):
308 def getcwd(self):
309 cwd = os.getcwd()
309 cwd = os.getcwd()
310 if cwd == self.root: return ''
310 if cwd == self.root: return ''
311 return cwd[len(self.root) + 1:]
311 return cwd[len(self.root) + 1:]
312
312
313 def ignore(self, f):
313 def ignore(self, f):
314 if not self.ignorefunc:
314 if not self.ignorefunc:
315 bigpat = []
315 bigpat = []
316 try:
316 try:
317 l = file(self.wjoin(".hgignore"))
317 l = file(self.wjoin(".hgignore"))
318 for pat in l:
318 for pat in l:
319 p = pat.rstrip()
319 p = pat.rstrip()
320 if p:
320 if p:
321 try:
321 try:
322 re.compile(p)
322 re.compile(p)
323 except:
323 except:
324 self.ui.warn("ignoring invalid ignore"
324 self.ui.warn("ignoring invalid ignore"
325 + " regular expression '%s'\n" % p)
325 + " regular expression '%s'\n" % p)
326 else:
326 else:
327 bigpat.append(p)
327 bigpat.append(p)
328 except IOError: pass
328 except IOError: pass
329
329
330 if bigpat:
330 if bigpat:
331 s = "(?:%s)" % (")|(?:".join(bigpat))
331 s = "(?:%s)" % (")|(?:".join(bigpat))
332 r = re.compile(s)
332 r = re.compile(s)
333 self.ignorefunc = r.search
333 self.ignorefunc = r.search
334 else:
334 else:
335 self.ignorefunc = util.never
335 self.ignorefunc = util.never
336
336
337 return self.ignorefunc(f)
337 return self.ignorefunc(f)
338
338
339 def __del__(self):
339 def __del__(self):
340 if self.dirty:
340 if self.dirty:
341 self.write()
341 self.write()
342
342
343 def __getitem__(self, key):
343 def __getitem__(self, key):
344 try:
344 try:
345 return self.map[key]
345 return self.map[key]
346 except TypeError:
346 except TypeError:
347 self.read()
347 self.read()
348 return self[key]
348 return self[key]
349
349
350 def __contains__(self, key):
350 def __contains__(self, key):
351 if not self.map: self.read()
351 if not self.map: self.read()
352 return key in self.map
352 return key in self.map
353
353
354 def parents(self):
354 def parents(self):
355 if not self.pl:
355 if not self.pl:
356 self.read()
356 self.read()
357 return self.pl
357 return self.pl
358
358
359 def markdirty(self):
359 def markdirty(self):
360 if not self.dirty:
360 if not self.dirty:
361 self.dirty = 1
361 self.dirty = 1
362
362
363 def setparents(self, p1, p2 = nullid):
363 def setparents(self, p1, p2=nullid):
364 self.markdirty()
364 self.markdirty()
365 self.pl = p1, p2
365 self.pl = p1, p2
366
366
367 def state(self, key):
367 def state(self, key):
368 try:
368 try:
369 return self[key][0]
369 return self[key][0]
370 except KeyError:
370 except KeyError:
371 return "?"
371 return "?"
372
372
373 def read(self):
373 def read(self):
374 if self.map is not None: return self.map
374 if self.map is not None: return self.map
375
375
376 self.map = {}
376 self.map = {}
377 self.pl = [nullid, nullid]
377 self.pl = [nullid, nullid]
378 try:
378 try:
379 st = self.opener("dirstate").read()
379 st = self.opener("dirstate").read()
380 if not st: return
380 if not st: return
381 except: return
381 except: return
382
382
383 self.pl = [st[:20], st[20: 40]]
383 self.pl = [st[:20], st[20: 40]]
384
384
385 pos = 40
385 pos = 40
386 while pos < len(st):
386 while pos < len(st):
387 e = struct.unpack(">cllll", st[pos:pos+17])
387 e = struct.unpack(">cllll", st[pos:pos+17])
388 l = e[4]
388 l = e[4]
389 pos += 17
389 pos += 17
390 f = st[pos:pos + l]
390 f = st[pos:pos + l]
391 if '\0' in f:
391 if '\0' in f:
392 f, c = f.split('\0')
392 f, c = f.split('\0')
393 self.copies[f] = c
393 self.copies[f] = c
394 self.map[f] = e[:4]
394 self.map[f] = e[:4]
395 pos += l
395 pos += l
396
396
397 def copy(self, source, dest):
397 def copy(self, source, dest):
398 self.read()
398 self.read()
399 self.markdirty()
399 self.markdirty()
400 self.copies[dest] = source
400 self.copies[dest] = source
401
401
402 def copied(self, file):
402 def copied(self, file):
403 return self.copies.get(file, None)
403 return self.copies.get(file, None)
404
404
405 def update(self, files, state, **kw):
405 def update(self, files, state, **kw):
406 ''' current states:
406 ''' current states:
407 n normal
407 n normal
408 m needs merging
408 m needs merging
409 r marked for removal
409 r marked for removal
410 a marked for addition'''
410 a marked for addition'''
411
411
412 if not files: return
412 if not files: return
413 self.read()
413 self.read()
414 self.markdirty()
414 self.markdirty()
415 for f in files:
415 for f in files:
416 if state == "r":
416 if state == "r":
417 self.map[f] = ('r', 0, 0, 0)
417 self.map[f] = ('r', 0, 0, 0)
418 else:
418 else:
419 s = os.stat(os.path.join(self.root, f))
419 s = os.stat(os.path.join(self.root, f))
420 st_size = kw.get('st_size', s.st_size)
420 st_size = kw.get('st_size', s.st_size)
421 st_mtime = kw.get('st_mtime', s.st_mtime)
421 st_mtime = kw.get('st_mtime', s.st_mtime)
422 self.map[f] = (state, s.st_mode, st_size, st_mtime)
422 self.map[f] = (state, s.st_mode, st_size, st_mtime)
423
423
424 def forget(self, files):
424 def forget(self, files):
425 if not files: return
425 if not files: return
426 self.read()
426 self.read()
427 self.markdirty()
427 self.markdirty()
428 for f in files:
428 for f in files:
429 try:
429 try:
430 del self.map[f]
430 del self.map[f]
431 except KeyError:
431 except KeyError:
432 self.ui.warn("not in dirstate: %s!\n" % f)
432 self.ui.warn("not in dirstate: %s!\n" % f)
433 pass
433 pass
434
434
435 def clear(self):
435 def clear(self):
436 self.map = {}
436 self.map = {}
437 self.markdirty()
437 self.markdirty()
438
438
439 def write(self):
439 def write(self):
440 st = self.opener("dirstate", "w")
440 st = self.opener("dirstate", "w")
441 st.write("".join(self.pl))
441 st.write("".join(self.pl))
442 for f, e in self.map.items():
442 for f, e in self.map.items():
443 c = self.copied(f)
443 c = self.copied(f)
444 if c:
444 if c:
445 f = f + "\0" + c
445 f = f + "\0" + c
446 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
446 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
447 st.write(e + f)
447 st.write(e + f)
448 self.dirty = 0
448 self.dirty = 0
449
449
450 def filterfiles(self, files):
450 def filterfiles(self, files):
451 ret = {}
451 ret = {}
452 unknown = []
452 unknown = []
453
453
454 for x in files:
454 for x in files:
455 if x is '.':
455 if x is '.':
456 return self.map.copy()
456 return self.map.copy()
457 if x not in self.map:
457 if x not in self.map:
458 unknown.append(x)
458 unknown.append(x)
459 else:
459 else:
460 ret[x] = self.map[x]
460 ret[x] = self.map[x]
461
461
462 if not unknown:
462 if not unknown:
463 return ret
463 return ret
464
464
465 b = self.map.keys()
465 b = self.map.keys()
466 b.sort()
466 b.sort()
467 blen = len(b)
467 blen = len(b)
468
468
469 for x in unknown:
469 for x in unknown:
470 bs = bisect.bisect(b, x)
470 bs = bisect.bisect(b, x)
471 if bs != 0 and b[bs-1] == x:
471 if bs != 0 and b[bs-1] == x:
472 ret[x] = self.map[x]
472 ret[x] = self.map[x]
473 continue
473 continue
474 while bs < blen:
474 while bs < blen:
475 s = b[bs]
475 s = b[bs]
476 if len(s) > len(x) and s.startswith(x) and s[len(x)] == '/':
476 if len(s) > len(x) and s.startswith(x) and s[len(x)] == '/':
477 ret[s] = self.map[s]
477 ret[s] = self.map[s]
478 else:
478 else:
479 break
479 break
480 bs += 1
480 bs += 1
481 return ret
481 return ret
482
482
483 def walk(self, files = None, match = util.always, dc=None):
483 def walk(self, files=None, match=util.always, dc=None):
484 self.read()
484 self.read()
485
485
486 # walk all files by default
486 # walk all files by default
487 if not files:
487 if not files:
488 files = [self.root]
488 files = [self.root]
489 if not dc:
489 if not dc:
490 dc = self.map.copy()
490 dc = self.map.copy()
491 elif not dc:
491 elif not dc:
492 dc = self.filterfiles(files)
492 dc = self.filterfiles(files)
493
493
494 known = {'.hg': 1}
494 known = {'.hg': 1}
495 def seen(fn):
495 def seen(fn):
496 if fn in known: return True
496 if fn in known: return True
497 known[fn] = 1
497 known[fn] = 1
498 def traverse():
498 def traverse():
499 for ff in util.unique(files):
499 for ff in util.unique(files):
500 f = os.path.join(self.root, ff)
500 f = os.path.join(self.root, ff)
501 try:
501 try:
502 st = os.stat(f)
502 st = os.stat(f)
503 except OSError, inst:
503 except OSError, inst:
504 if ff not in dc: self.ui.warn('%s: %s\n' % (
504 if ff not in dc: self.ui.warn('%s: %s\n' % (
505 util.pathto(self.getcwd(), ff),
505 util.pathto(self.getcwd(), ff),
506 inst.strerror))
506 inst.strerror))
507 continue
507 continue
508 if stat.S_ISDIR(st.st_mode):
508 if stat.S_ISDIR(st.st_mode):
509 for dir, subdirs, fl in os.walk(f):
509 for dir, subdirs, fl in os.walk(f):
510 d = dir[len(self.root) + 1:]
510 d = dir[len(self.root) + 1:]
511 nd = util.normpath(d)
511 nd = util.normpath(d)
512 if nd == '.': nd = ''
512 if nd == '.': nd = ''
513 if seen(nd):
513 if seen(nd):
514 subdirs[:] = []
514 subdirs[:] = []
515 continue
515 continue
516 for sd in subdirs:
516 for sd in subdirs:
517 ds = os.path.join(nd, sd +'/')
517 ds = os.path.join(nd, sd +'/')
518 if self.ignore(ds) or not match(ds):
518 if self.ignore(ds) or not match(ds):
519 subdirs.remove(sd)
519 subdirs.remove(sd)
520 subdirs.sort()
520 subdirs.sort()
521 fl.sort()
521 fl.sort()
522 for fn in fl:
522 for fn in fl:
523 fn = util.pconvert(os.path.join(d, fn))
523 fn = util.pconvert(os.path.join(d, fn))
524 yield 'f', fn
524 yield 'f', fn
525 elif stat.S_ISREG(st.st_mode):
525 elif stat.S_ISREG(st.st_mode):
526 yield 'f', ff
526 yield 'f', ff
527 else:
527 else:
528 kind = 'unknown'
528 kind = 'unknown'
529 if stat.S_ISCHR(st.st_mode): kind = 'character device'
529 if stat.S_ISCHR(st.st_mode): kind = 'character device'
530 elif stat.S_ISBLK(st.st_mode): kind = 'block device'
530 elif stat.S_ISBLK(st.st_mode): kind = 'block device'
531 elif stat.S_ISFIFO(st.st_mode): kind = 'fifo'
531 elif stat.S_ISFIFO(st.st_mode): kind = 'fifo'
532 elif stat.S_ISLNK(st.st_mode): kind = 'symbolic link'
532 elif stat.S_ISLNK(st.st_mode): kind = 'symbolic link'
533 elif stat.S_ISSOCK(st.st_mode): kind = 'socket'
533 elif stat.S_ISSOCK(st.st_mode): kind = 'socket'
534 self.ui.warn('%s: unsupported file type (type is %s)\n' % (
534 self.ui.warn('%s: unsupported file type (type is %s)\n' % (
535 util.pathto(self.getcwd(), ff),
535 util.pathto(self.getcwd(), ff),
536 kind))
536 kind))
537
537
538 ks = dc.keys()
538 ks = dc.keys()
539 ks.sort()
539 ks.sort()
540 for k in ks:
540 for k in ks:
541 yield 'm', k
541 yield 'm', k
542
542
543 # yield only files that match: all in dirstate, others only if
543 # yield only files that match: all in dirstate, others only if
544 # not in .hgignore
544 # not in .hgignore
545
545
546 for src, fn in util.unique(traverse()):
546 for src, fn in util.unique(traverse()):
547 fn = util.normpath(fn)
547 fn = util.normpath(fn)
548 if seen(fn): continue
548 if seen(fn): continue
549 if fn not in dc and self.ignore(fn):
549 if fn not in dc and self.ignore(fn):
550 continue
550 continue
551 if match(fn):
551 if match(fn):
552 yield src, fn
552 yield src, fn
553
553
554 def changes(self, files=None, match=util.always):
554 def changes(self, files=None, match=util.always):
555 self.read()
555 self.read()
556 if not files:
556 if not files:
557 dc = self.map.copy()
557 dc = self.map.copy()
558 else:
558 else:
559 dc = self.filterfiles(files)
559 dc = self.filterfiles(files)
560 lookup, modified, added, unknown = [], [], [], []
560 lookup, modified, added, unknown = [], [], [], []
561 removed, deleted = [], []
561 removed, deleted = [], []
562
562
563 for src, fn in self.walk(files, match, dc=dc):
563 for src, fn in self.walk(files, match, dc=dc):
564 try:
564 try:
565 s = os.stat(os.path.join(self.root, fn))
565 s = os.stat(os.path.join(self.root, fn))
566 except OSError:
566 except OSError:
567 continue
567 continue
568 if not stat.S_ISREG(s.st_mode):
568 if not stat.S_ISREG(s.st_mode):
569 continue
569 continue
570 c = dc.get(fn)
570 c = dc.get(fn)
571 if c:
571 if c:
572 del dc[fn]
572 del dc[fn]
573 if c[0] == 'm':
573 if c[0] == 'm':
574 modified.append(fn)
574 modified.append(fn)
575 elif c[0] == 'a':
575 elif c[0] == 'a':
576 added.append(fn)
576 added.append(fn)
577 elif c[0] == 'r':
577 elif c[0] == 'r':
578 unknown.append(fn)
578 unknown.append(fn)
579 elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
579 elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
580 modified.append(fn)
580 modified.append(fn)
581 elif c[3] != s.st_mtime:
581 elif c[3] != s.st_mtime:
582 lookup.append(fn)
582 lookup.append(fn)
583 else:
583 else:
584 unknown.append(fn)
584 unknown.append(fn)
585
585
586 for fn, c in [(fn, c) for fn, c in dc.items() if match(fn)]:
586 for fn, c in [(fn, c) for fn, c in dc.items() if match(fn)]:
587 if c[0] == 'r':
587 if c[0] == 'r':
588 removed.append(fn)
588 removed.append(fn)
589 else:
589 else:
590 deleted.append(fn)
590 deleted.append(fn)
591 return (lookup, modified, added, removed + deleted, unknown)
591 return (lookup, modified, added, removed + deleted, unknown)
592
592
593 # used to avoid circular references so destructors work
593 # used to avoid circular references so destructors work
594 def opener(base):
594 def opener(base):
595 p = base
595 p = base
596 def o(path, mode="r"):
596 def o(path, mode="r"):
597 if p.startswith("http://"):
597 if p.startswith("http://"):
598 f = os.path.join(p, urllib.quote(path))
598 f = os.path.join(p, urllib.quote(path))
599 return httprangereader.httprangereader(f)
599 return httprangereader.httprangereader(f)
600
600
601 f = os.path.join(p, path)
601 f = os.path.join(p, path)
602
602
603 mode += "b" # for that other OS
603 mode += "b" # for that other OS
604
604
605 if mode[0] != "r":
605 if mode[0] != "r":
606 try:
606 try:
607 s = os.stat(f)
607 s = os.stat(f)
608 except OSError:
608 except OSError:
609 d = os.path.dirname(f)
609 d = os.path.dirname(f)
610 if not os.path.isdir(d):
610 if not os.path.isdir(d):
611 os.makedirs(d)
611 os.makedirs(d)
612 else:
612 else:
613 if s.st_nlink > 1:
613 if s.st_nlink > 1:
614 file(f + ".tmp", "wb").write(file(f, "rb").read())
614 file(f + ".tmp", "wb").write(file(f, "rb").read())
615 util.rename(f+".tmp", f)
615 util.rename(f+".tmp", f)
616
616
617 return file(f, mode)
617 return file(f, mode)
618
618
619 return o
619 return o
620
620
621 class RepoError(Exception): pass
621 class RepoError(Exception): pass
622
622
623 class localrepository:
623 class localrepository:
624 def __init__(self, ui, path=None, create=0):
624 def __init__(self, ui, path=None, create=0):
625 self.remote = 0
625 self.remote = 0
626 if path and path.startswith("http://"):
626 if path and path.startswith("http://"):
627 self.remote = 1
627 self.remote = 1
628 self.path = path
628 self.path = path
629 else:
629 else:
630 if not path:
630 if not path:
631 p = os.getcwd()
631 p = os.getcwd()
632 while not os.path.isdir(os.path.join(p, ".hg")):
632 while not os.path.isdir(os.path.join(p, ".hg")):
633 oldp = p
633 oldp = p
634 p = os.path.dirname(p)
634 p = os.path.dirname(p)
635 if p == oldp: raise RepoError("no repo found")
635 if p == oldp: raise RepoError("no repo found")
636 path = p
636 path = p
637 self.path = os.path.join(path, ".hg")
637 self.path = os.path.join(path, ".hg")
638
638
639 if not create and not os.path.isdir(self.path):
639 if not create and not os.path.isdir(self.path):
640 raise RepoError("repository %s not found" % self.path)
640 raise RepoError("repository %s not found" % self.path)
641
641
642 self.root = os.path.abspath(path)
642 self.root = os.path.abspath(path)
643 self.ui = ui
643 self.ui = ui
644
644
645 if create:
645 if create:
646 os.mkdir(self.path)
646 os.mkdir(self.path)
647 os.mkdir(self.join("data"))
647 os.mkdir(self.join("data"))
648
648
649 self.opener = opener(self.path)
649 self.opener = opener(self.path)
650 self.wopener = opener(self.root)
650 self.wopener = opener(self.root)
651 self.manifest = manifest(self.opener)
651 self.manifest = manifest(self.opener)
652 self.changelog = changelog(self.opener)
652 self.changelog = changelog(self.opener)
653 self.tagscache = None
653 self.tagscache = None
654 self.nodetagscache = None
654 self.nodetagscache = None
655
655
656 if not self.remote:
656 if not self.remote:
657 self.dirstate = dirstate(self.opener, ui, self.root)
657 self.dirstate = dirstate(self.opener, ui, self.root)
658 try:
658 try:
659 self.ui.readconfig(self.opener("hgrc"))
659 self.ui.readconfig(self.opener("hgrc"))
660 except IOError: pass
660 except IOError: pass
661
661
662 def hook(self, name, **args):
662 def hook(self, name, **args):
663 s = self.ui.config("hooks", name)
663 s = self.ui.config("hooks", name)
664 if s:
664 if s:
665 self.ui.note("running hook %s: %s\n" % (name, s))
665 self.ui.note("running hook %s: %s\n" % (name, s))
666 old = {}
666 old = {}
667 for k, v in args.items():
667 for k, v in args.items():
668 k = k.upper()
668 k = k.upper()
669 old[k] = os.environ.get(k, None)
669 old[k] = os.environ.get(k, None)
670 os.environ[k] = v
670 os.environ[k] = v
671
671
672 r = os.system(s)
672 r = os.system(s)
673
673
674 for k, v in old.items():
674 for k, v in old.items():
675 if v != None:
675 if v != None:
676 os.environ[k] = v
676 os.environ[k] = v
677 else:
677 else:
678 del os.environ[k]
678 del os.environ[k]
679
679
680 if r:
680 if r:
681 self.ui.warn("abort: %s hook failed with status %d!\n" %
681 self.ui.warn("abort: %s hook failed with status %d!\n" %
682 (name, r))
682 (name, r))
683 return False
683 return False
684 return True
684 return True
685
685
686 def tags(self):
686 def tags(self):
687 '''return a mapping of tag to node'''
687 '''return a mapping of tag to node'''
688 if not self.tagscache:
688 if not self.tagscache:
689 self.tagscache = {}
689 self.tagscache = {}
690 def addtag(self, k, n):
690 def addtag(self, k, n):
691 try:
691 try:
692 bin_n = bin(n)
692 bin_n = bin(n)
693 except TypeError:
693 except TypeError:
694 bin_n = ''
694 bin_n = ''
695 self.tagscache[k.strip()] = bin_n
695 self.tagscache[k.strip()] = bin_n
696
696
697 try:
697 try:
698 # read each head of the tags file, ending with the tip
698 # read each head of the tags file, ending with the tip
699 # and add each tag found to the map, with "newer" ones
699 # and add each tag found to the map, with "newer" ones
700 # taking precedence
700 # taking precedence
701 fl = self.file(".hgtags")
701 fl = self.file(".hgtags")
702 h = fl.heads()
702 h = fl.heads()
703 h.reverse()
703 h.reverse()
704 for r in h:
704 for r in h:
705 for l in fl.read(r).splitlines():
705 for l in fl.read(r).splitlines():
706 if l:
706 if l:
707 n, k = l.split(" ", 1)
707 n, k = l.split(" ", 1)
708 addtag(self, k, n)
708 addtag(self, k, n)
709 except KeyError:
709 except KeyError:
710 pass
710 pass
711
711
712 try:
712 try:
713 f = self.opener("localtags")
713 f = self.opener("localtags")
714 for l in f:
714 for l in f:
715 n, k = l.split(" ", 1)
715 n, k = l.split(" ", 1)
716 addtag(self, k, n)
716 addtag(self, k, n)
717 except IOError:
717 except IOError:
718 pass
718 pass
719
719
720 self.tagscache['tip'] = self.changelog.tip()
720 self.tagscache['tip'] = self.changelog.tip()
721
721
722 return self.tagscache
722 return self.tagscache
723
723
724 def tagslist(self):
724 def tagslist(self):
725 '''return a list of tags ordered by revision'''
725 '''return a list of tags ordered by revision'''
726 l = []
726 l = []
727 for t, n in self.tags().items():
727 for t, n in self.tags().items():
728 try:
728 try:
729 r = self.changelog.rev(n)
729 r = self.changelog.rev(n)
730 except:
730 except:
731 r = -2 # sort to the beginning of the list if unknown
731 r = -2 # sort to the beginning of the list if unknown
732 l.append((r,t,n))
732 l.append((r,t,n))
733 l.sort()
733 l.sort()
734 return [(t,n) for r,t,n in l]
734 return [(t,n) for r,t,n in l]
735
735
736 def nodetags(self, node):
736 def nodetags(self, node):
737 '''return the tags associated with a node'''
737 '''return the tags associated with a node'''
738 if not self.nodetagscache:
738 if not self.nodetagscache:
739 self.nodetagscache = {}
739 self.nodetagscache = {}
740 for t,n in self.tags().items():
740 for t,n in self.tags().items():
741 self.nodetagscache.setdefault(n,[]).append(t)
741 self.nodetagscache.setdefault(n,[]).append(t)
742 return self.nodetagscache.get(node, [])
742 return self.nodetagscache.get(node, [])
743
743
744 def lookup(self, key):
744 def lookup(self, key):
745 try:
745 try:
746 return self.tags()[key]
746 return self.tags()[key]
747 except KeyError:
747 except KeyError:
748 try:
748 try:
749 return self.changelog.lookup(key)
749 return self.changelog.lookup(key)
750 except:
750 except:
751 raise RepoError("unknown revision '%s'" % key)
751 raise RepoError("unknown revision '%s'" % key)
752
752
753 def dev(self):
753 def dev(self):
754 if self.remote: return -1
754 if self.remote: return -1
755 return os.stat(self.path).st_dev
755 return os.stat(self.path).st_dev
756
756
757 def local(self):
757 def local(self):
758 return not self.remote
758 return not self.remote
759
759
760 def join(self, f):
760 def join(self, f):
761 return os.path.join(self.path, f)
761 return os.path.join(self.path, f)
762
762
763 def wjoin(self, f):
763 def wjoin(self, f):
764 return os.path.join(self.root, f)
764 return os.path.join(self.root, f)
765
765
766 def file(self, f):
766 def file(self, f):
767 if f[0] == '/': f = f[1:]
767 if f[0] == '/': f = f[1:]
768 return filelog(self.opener, f)
768 return filelog(self.opener, f)
769
769
770 def getcwd(self):
770 def getcwd(self):
771 return self.dirstate.getcwd()
771 return self.dirstate.getcwd()
772
772
773 def wfile(self, f, mode='r'):
773 def wfile(self, f, mode='r'):
774 return self.wopener(f, mode)
774 return self.wopener(f, mode)
775
775
776 def wread(self, filename):
776 def wread(self, filename):
777 return self.wopener(filename, 'r').read()
777 return self.wopener(filename, 'r').read()
778
778
779 def wwrite(self, filename, data, fd=None):
779 def wwrite(self, filename, data, fd=None):
780 if fd:
780 if fd:
781 return fd.write(data)
781 return fd.write(data)
782 return self.wopener(filename, 'w').write(data)
782 return self.wopener(filename, 'w').write(data)
783
783
784 def transaction(self):
784 def transaction(self):
785 # save dirstate for undo
785 # save dirstate for undo
786 try:
786 try:
787 ds = self.opener("dirstate").read()
787 ds = self.opener("dirstate").read()
788 except IOError:
788 except IOError:
789 ds = ""
789 ds = ""
790 self.opener("journal.dirstate", "w").write(ds)
790 self.opener("journal.dirstate", "w").write(ds)
791
791
792 def after():
792 def after():
793 util.rename(self.join("journal"), self.join("undo"))
793 util.rename(self.join("journal"), self.join("undo"))
794 util.rename(self.join("journal.dirstate"),
794 util.rename(self.join("journal.dirstate"),
795 self.join("undo.dirstate"))
795 self.join("undo.dirstate"))
796
796
797 return transaction.transaction(self.ui.warn, self.opener,
797 return transaction.transaction(self.ui.warn, self.opener,
798 self.join("journal"), after)
798 self.join("journal"), after)
799
799
800 def recover(self):
800 def recover(self):
801 lock = self.lock()
801 lock = self.lock()
802 if os.path.exists(self.join("journal")):
802 if os.path.exists(self.join("journal")):
803 self.ui.status("rolling back interrupted transaction\n")
803 self.ui.status("rolling back interrupted transaction\n")
804 return transaction.rollback(self.opener, self.join("journal"))
804 return transaction.rollback(self.opener, self.join("journal"))
805 else:
805 else:
806 self.ui.warn("no interrupted transaction available\n")
806 self.ui.warn("no interrupted transaction available\n")
807
807
808 def undo(self):
808 def undo(self):
809 lock = self.lock()
809 lock = self.lock()
810 if os.path.exists(self.join("undo")):
810 if os.path.exists(self.join("undo")):
811 self.ui.status("rolling back last transaction\n")
811 self.ui.status("rolling back last transaction\n")
812 transaction.rollback(self.opener, self.join("undo"))
812 transaction.rollback(self.opener, self.join("undo"))
813 self.dirstate = None
813 self.dirstate = None
814 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
814 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
815 self.dirstate = dirstate(self.opener, self.ui, self.root)
815 self.dirstate = dirstate(self.opener, self.ui, self.root)
816 else:
816 else:
817 self.ui.warn("no undo information available\n")
817 self.ui.warn("no undo information available\n")
818
818
819 def lock(self, wait = 1):
819 def lock(self, wait=1):
820 try:
820 try:
821 return lock.lock(self.join("lock"), 0)
821 return lock.lock(self.join("lock"), 0)
822 except lock.LockHeld, inst:
822 except lock.LockHeld, inst:
823 if wait:
823 if wait:
824 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
824 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
825 return lock.lock(self.join("lock"), wait)
825 return lock.lock(self.join("lock"), wait)
826 raise inst
826 raise inst
827
827
828 def rawcommit(self, files, text, user, date, p1=None, p2=None):
828 def rawcommit(self, files, text, user, date, p1=None, p2=None):
829 orig_parent = self.dirstate.parents()[0] or nullid
829 orig_parent = self.dirstate.parents()[0] or nullid
830 p1 = p1 or self.dirstate.parents()[0] or nullid
830 p1 = p1 or self.dirstate.parents()[0] or nullid
831 p2 = p2 or self.dirstate.parents()[1] or nullid
831 p2 = p2 or self.dirstate.parents()[1] or nullid
832 c1 = self.changelog.read(p1)
832 c1 = self.changelog.read(p1)
833 c2 = self.changelog.read(p2)
833 c2 = self.changelog.read(p2)
834 m1 = self.manifest.read(c1[0])
834 m1 = self.manifest.read(c1[0])
835 mf1 = self.manifest.readflags(c1[0])
835 mf1 = self.manifest.readflags(c1[0])
836 m2 = self.manifest.read(c2[0])
836 m2 = self.manifest.read(c2[0])
837 changed = []
837 changed = []
838
838
839 if orig_parent == p1:
839 if orig_parent == p1:
840 update_dirstate = 1
840 update_dirstate = 1
841 else:
841 else:
842 update_dirstate = 0
842 update_dirstate = 0
843
843
844 tr = self.transaction()
844 tr = self.transaction()
845 mm = m1.copy()
845 mm = m1.copy()
846 mfm = mf1.copy()
846 mfm = mf1.copy()
847 linkrev = self.changelog.count()
847 linkrev = self.changelog.count()
848 for f in files:
848 for f in files:
849 try:
849 try:
850 t = self.wread(f)
850 t = self.wread(f)
851 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
851 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
852 r = self.file(f)
852 r = self.file(f)
853 mfm[f] = tm
853 mfm[f] = tm
854
854
855 fp1 = m1.get(f, nullid)
855 fp1 = m1.get(f, nullid)
856 fp2 = m2.get(f, nullid)
856 fp2 = m2.get(f, nullid)
857
857
858 # is the same revision on two branches of a merge?
858 # is the same revision on two branches of a merge?
859 if fp2 == fp1:
859 if fp2 == fp1:
860 fp2 = nullid
860 fp2 = nullid
861
861
862 if fp2 != nullid:
862 if fp2 != nullid:
863 # is one parent an ancestor of the other?
863 # is one parent an ancestor of the other?
864 fpa = r.ancestor(fp1, fp2)
864 fpa = r.ancestor(fp1, fp2)
865 if fpa == fp1:
865 if fpa == fp1:
866 fp1, fp2 = fp2, nullid
866 fp1, fp2 = fp2, nullid
867 elif fpa == fp2:
867 elif fpa == fp2:
868 fp2 = nullid
868 fp2 = nullid
869
869
870 # is the file unmodified from the parent?
870 # is the file unmodified from the parent?
871 if t == r.read(fp1):
871 if t == r.read(fp1):
872 # record the proper existing parent in manifest
872 # record the proper existing parent in manifest
873 # no need to add a revision
873 # no need to add a revision
874 mm[f] = fp1
874 mm[f] = fp1
875 continue
875 continue
876
876
877 mm[f] = r.add(t, {}, tr, linkrev, fp1, fp2)
877 mm[f] = r.add(t, {}, tr, linkrev, fp1, fp2)
878 changed.append(f)
878 changed.append(f)
879 if update_dirstate:
879 if update_dirstate:
880 self.dirstate.update([f], "n")
880 self.dirstate.update([f], "n")
881 except IOError:
881 except IOError:
882 try:
882 try:
883 del mm[f]
883 del mm[f]
884 del mfm[f]
884 del mfm[f]
885 if update_dirstate:
885 if update_dirstate:
886 self.dirstate.forget([f])
886 self.dirstate.forget([f])
887 except:
887 except:
888 # deleted from p2?
888 # deleted from p2?
889 pass
889 pass
890
890
891 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
891 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
892 user = user or self.ui.username()
892 user = user or self.ui.username()
893 n = self.changelog.add(mnode, changed, text, tr, p1, p2, user, date)
893 n = self.changelog.add(mnode, changed, text, tr, p1, p2, user, date)
894 tr.close()
894 tr.close()
895 if update_dirstate:
895 if update_dirstate:
896 self.dirstate.setparents(n, nullid)
896 self.dirstate.setparents(n, nullid)
897
897
898 def commit(self, files = None, text = "", user = None, date = None,
898 def commit(self, files = None, text = "", user = None, date = None,
899 match = util.always, force=False):
899 match = util.always, force=False):
900 commit = []
900 commit = []
901 remove = []
901 remove = []
902 changed = []
902 changed = []
903
903
904 if files:
904 if files:
905 for f in files:
905 for f in files:
906 s = self.dirstate.state(f)
906 s = self.dirstate.state(f)
907 if s in 'nmai':
907 if s in 'nmai':
908 commit.append(f)
908 commit.append(f)
909 elif s == 'r':
909 elif s == 'r':
910 remove.append(f)
910 remove.append(f)
911 else:
911 else:
912 self.ui.warn("%s not tracked!\n" % f)
912 self.ui.warn("%s not tracked!\n" % f)
913 else:
913 else:
914 (c, a, d, u) = self.changes(match = match)
914 (c, a, d, u) = self.changes(match=match)
915 commit = c + a
915 commit = c + a
916 remove = d
916 remove = d
917
917
918 p1, p2 = self.dirstate.parents()
918 p1, p2 = self.dirstate.parents()
919 c1 = self.changelog.read(p1)
919 c1 = self.changelog.read(p1)
920 c2 = self.changelog.read(p2)
920 c2 = self.changelog.read(p2)
921 m1 = self.manifest.read(c1[0])
921 m1 = self.manifest.read(c1[0])
922 mf1 = self.manifest.readflags(c1[0])
922 mf1 = self.manifest.readflags(c1[0])
923 m2 = self.manifest.read(c2[0])
923 m2 = self.manifest.read(c2[0])
924
924
925 if not commit and not remove and not force and p2 == nullid:
925 if not commit and not remove and not force and p2 == nullid:
926 self.ui.status("nothing changed\n")
926 self.ui.status("nothing changed\n")
927 return None
927 return None
928
928
929 if not self.hook("precommit"):
929 if not self.hook("precommit"):
930 return None
930 return None
931
931
932 lock = self.lock()
932 lock = self.lock()
933 tr = self.transaction()
933 tr = self.transaction()
934
934
935 # check in files
935 # check in files
936 new = {}
936 new = {}
937 linkrev = self.changelog.count()
937 linkrev = self.changelog.count()
938 commit.sort()
938 commit.sort()
939 for f in commit:
939 for f in commit:
940 self.ui.note(f + "\n")
940 self.ui.note(f + "\n")
941 try:
941 try:
942 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
942 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
943 t = self.wread(f)
943 t = self.wread(f)
944 except IOError:
944 except IOError:
945 self.ui.warn("trouble committing %s!\n" % f)
945 self.ui.warn("trouble committing %s!\n" % f)
946 raise
946 raise
947
947
948 meta = {}
948 meta = {}
949 cp = self.dirstate.copied(f)
949 cp = self.dirstate.copied(f)
950 if cp:
950 if cp:
951 meta["copy"] = cp
951 meta["copy"] = cp
952 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
952 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
953 self.ui.debug(" %s: copy %s:%s\n" % (f, cp, meta["copyrev"]))
953 self.ui.debug(" %s: copy %s:%s\n" % (f, cp, meta["copyrev"]))
954
954
955 r = self.file(f)
955 r = self.file(f)
956 fp1 = m1.get(f, nullid)
956 fp1 = m1.get(f, nullid)
957 fp2 = m2.get(f, nullid)
957 fp2 = m2.get(f, nullid)
958
958
959 # is the same revision on two branches of a merge?
959 # is the same revision on two branches of a merge?
960 if fp2 == fp1:
960 if fp2 == fp1:
961 fp2 = nullid
961 fp2 = nullid
962
962
963 if fp2 != nullid:
963 if fp2 != nullid:
964 # is one parent an ancestor of the other?
964 # is one parent an ancestor of the other?
965 fpa = r.ancestor(fp1, fp2)
965 fpa = r.ancestor(fp1, fp2)
966 if fpa == fp1:
966 if fpa == fp1:
967 fp1, fp2 = fp2, nullid
967 fp1, fp2 = fp2, nullid
968 elif fpa == fp2:
968 elif fpa == fp2:
969 fp2 = nullid
969 fp2 = nullid
970
970
971 # is the file unmodified from the parent?
971 # is the file unmodified from the parent?
972 if not meta and t == r.read(fp1):
972 if not meta and t == r.read(fp1):
973 # record the proper existing parent in manifest
973 # record the proper existing parent in manifest
974 # no need to add a revision
974 # no need to add a revision
975 new[f] = fp1
975 new[f] = fp1
976 continue
976 continue
977
977
978 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
978 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
979 # remember what we've added so that we can later calculate
979 # remember what we've added so that we can later calculate
980 # the files to pull from a set of changesets
980 # the files to pull from a set of changesets
981 changed.append(f)
981 changed.append(f)
982
982
983 # update manifest
983 # update manifest
984 m1.update(new)
984 m1.update(new)
985 for f in remove:
985 for f in remove:
986 if f in m1:
986 if f in m1:
987 del m1[f]
987 del m1[f]
988 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0],
988 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0],
989 (new, remove))
989 (new, remove))
990
990
991 # add changeset
991 # add changeset
992 new = new.keys()
992 new = new.keys()
993 new.sort()
993 new.sort()
994
994
995 if not text:
995 if not text:
996 edittext = ""
996 edittext = ""
997 if p2 != nullid:
997 if p2 != nullid:
998 edittext += "HG: branch merge\n"
998 edittext += "HG: branch merge\n"
999 edittext += "\n" + "HG: manifest hash %s\n" % hex(mn)
999 edittext += "\n" + "HG: manifest hash %s\n" % hex(mn)
1000 edittext += "".join(["HG: changed %s\n" % f for f in changed])
1000 edittext += "".join(["HG: changed %s\n" % f for f in changed])
1001 edittext += "".join(["HG: removed %s\n" % f for f in remove])
1001 edittext += "".join(["HG: removed %s\n" % f for f in remove])
1002 if not changed and not remove:
1002 if not changed and not remove:
1003 edittext += "HG: no files changed\n"
1003 edittext += "HG: no files changed\n"
1004 edittext = self.ui.edit(edittext)
1004 edittext = self.ui.edit(edittext)
1005 if not edittext.rstrip():
1005 if not edittext.rstrip():
1006 return None
1006 return None
1007 text = edittext
1007 text = edittext
1008
1008
1009 user = user or self.ui.username()
1009 user = user or self.ui.username()
1010 n = self.changelog.add(mn, changed, text, tr, p1, p2, user, date)
1010 n = self.changelog.add(mn, changed, text, tr, p1, p2, user, date)
1011 tr.close()
1011 tr.close()
1012
1012
1013 self.dirstate.setparents(n)
1013 self.dirstate.setparents(n)
1014 self.dirstate.update(new, "n")
1014 self.dirstate.update(new, "n")
1015 self.dirstate.forget(remove)
1015 self.dirstate.forget(remove)
1016
1016
1017 if not self.hook("commit", node=hex(n)):
1017 if not self.hook("commit", node=hex(n)):
1018 return None
1018 return None
1019 return n
1019 return n
1020
1020
1021 def walk(self, node = None, files = [], match = util.always):
1021 def walk(self, node=None, files=[], match=util.always):
1022 if node:
1022 if node:
1023 for fn in self.manifest.read(self.changelog.read(node)[0]):
1023 for fn in self.manifest.read(self.changelog.read(node)[0]):
1024 if match(fn): yield 'm', fn
1024 if match(fn): yield 'm', fn
1025 else:
1025 else:
1026 for src, fn in self.dirstate.walk(files, match):
1026 for src, fn in self.dirstate.walk(files, match):
1027 yield src, fn
1027 yield src, fn
1028
1028
1029 def changes(self, node1 = None, node2 = None, files = [],
1029 def changes(self, node1 = None, node2 = None, files = [],
1030 match = util.always):
1030 match = util.always):
1031 mf2, u = None, []
1031 mf2, u = None, []
1032
1032
1033 def fcmp(fn, mf):
1033 def fcmp(fn, mf):
1034 t1 = self.wread(fn)
1034 t1 = self.wread(fn)
1035 t2 = self.file(fn).read(mf.get(fn, nullid))
1035 t2 = self.file(fn).read(mf.get(fn, nullid))
1036 return cmp(t1, t2)
1036 return cmp(t1, t2)
1037
1037
1038 def mfmatches(node):
1038 def mfmatches(node):
1039 mf = dict(self.manifest.read(node))
1039 mf = dict(self.manifest.read(node))
1040 for fn in mf.keys():
1040 for fn in mf.keys():
1041 if not match(fn):
1041 if not match(fn):
1042 del mf[fn]
1042 del mf[fn]
1043 return mf
1043 return mf
1044
1044
1045 # are we comparing the working directory?
1045 # are we comparing the working directory?
1046 if not node2:
1046 if not node2:
1047 l, c, a, d, u = self.dirstate.changes(files, match)
1047 l, c, a, d, u = self.dirstate.changes(files, match)
1048
1048
1049 # are we comparing working dir against its parent?
1049 # are we comparing working dir against its parent?
1050 if not node1:
1050 if not node1:
1051 if l:
1051 if l:
1052 # do a full compare of any files that might have changed
1052 # do a full compare of any files that might have changed
1053 change = self.changelog.read(self.dirstate.parents()[0])
1053 change = self.changelog.read(self.dirstate.parents()[0])
1054 mf2 = mfmatches(change[0])
1054 mf2 = mfmatches(change[0])
1055 for f in l:
1055 for f in l:
1056 if fcmp(f, mf2):
1056 if fcmp(f, mf2):
1057 c.append(f)
1057 c.append(f)
1058
1058
1059 for l in c, a, d, u:
1059 for l in c, a, d, u:
1060 l.sort()
1060 l.sort()
1061
1061
1062 return (c, a, d, u)
1062 return (c, a, d, u)
1063
1063
1064 # are we comparing working dir against non-tip?
1064 # are we comparing working dir against non-tip?
1065 # generate a pseudo-manifest for the working dir
1065 # generate a pseudo-manifest for the working dir
1066 if not node2:
1066 if not node2:
1067 if not mf2:
1067 if not mf2:
1068 change = self.changelog.read(self.dirstate.parents()[0])
1068 change = self.changelog.read(self.dirstate.parents()[0])
1069 mf2 = mfmatches(change[0])
1069 mf2 = mfmatches(change[0])
1070 for f in a + c + l:
1070 for f in a + c + l:
1071 mf2[f] = ""
1071 mf2[f] = ""
1072 for f in d:
1072 for f in d:
1073 if f in mf2: del mf2[f]
1073 if f in mf2: del mf2[f]
1074 else:
1074 else:
1075 change = self.changelog.read(node2)
1075 change = self.changelog.read(node2)
1076 mf2 = mfmatches(change[0])
1076 mf2 = mfmatches(change[0])
1077
1077
1078 # flush lists from dirstate before comparing manifests
1078 # flush lists from dirstate before comparing manifests
1079 c, a = [], []
1079 c, a = [], []
1080
1080
1081 change = self.changelog.read(node1)
1081 change = self.changelog.read(node1)
1082 mf1 = mfmatches(change[0])
1082 mf1 = mfmatches(change[0])
1083
1083
1084 for fn in mf2:
1084 for fn in mf2:
1085 if mf1.has_key(fn):
1085 if mf1.has_key(fn):
1086 if mf1[fn] != mf2[fn]:
1086 if mf1[fn] != mf2[fn]:
1087 if mf2[fn] != "" or fcmp(fn, mf1):
1087 if mf2[fn] != "" or fcmp(fn, mf1):
1088 c.append(fn)
1088 c.append(fn)
1089 del mf1[fn]
1089 del mf1[fn]
1090 else:
1090 else:
1091 a.append(fn)
1091 a.append(fn)
1092
1092
1093 d = mf1.keys()
1093 d = mf1.keys()
1094
1094
1095 for l in c, a, d, u:
1095 for l in c, a, d, u:
1096 l.sort()
1096 l.sort()
1097
1097
1098 return (c, a, d, u)
1098 return (c, a, d, u)
1099
1099
1100 def add(self, list):
1100 def add(self, list):
1101 for f in list:
1101 for f in list:
1102 p = self.wjoin(f)
1102 p = self.wjoin(f)
1103 if not os.path.exists(p):
1103 if not os.path.exists(p):
1104 self.ui.warn("%s does not exist!\n" % f)
1104 self.ui.warn("%s does not exist!\n" % f)
1105 elif not os.path.isfile(p):
1105 elif not os.path.isfile(p):
1106 self.ui.warn("%s not added: only files supported currently\n" % f)
1106 self.ui.warn("%s not added: only files supported currently\n" % f)
1107 elif self.dirstate.state(f) in 'an':
1107 elif self.dirstate.state(f) in 'an':
1108 self.ui.warn("%s already tracked!\n" % f)
1108 self.ui.warn("%s already tracked!\n" % f)
1109 else:
1109 else:
1110 self.dirstate.update([f], "a")
1110 self.dirstate.update([f], "a")
1111
1111
1112 def forget(self, list):
1112 def forget(self, list):
1113 for f in list:
1113 for f in list:
1114 if self.dirstate.state(f) not in 'ai':
1114 if self.dirstate.state(f) not in 'ai':
1115 self.ui.warn("%s not added!\n" % f)
1115 self.ui.warn("%s not added!\n" % f)
1116 else:
1116 else:
1117 self.dirstate.forget([f])
1117 self.dirstate.forget([f])
1118
1118
1119 def remove(self, list):
1119 def remove(self, list):
1120 for f in list:
1120 for f in list:
1121 p = self.wjoin(f)
1121 p = self.wjoin(f)
1122 if os.path.exists(p):
1122 if os.path.exists(p):
1123 self.ui.warn("%s still exists!\n" % f)
1123 self.ui.warn("%s still exists!\n" % f)
1124 elif self.dirstate.state(f) == 'a':
1124 elif self.dirstate.state(f) == 'a':
1125 self.ui.warn("%s never committed!\n" % f)
1125 self.ui.warn("%s never committed!\n" % f)
1126 self.dirstate.forget([f])
1126 self.dirstate.forget([f])
1127 elif f not in self.dirstate:
1127 elif f not in self.dirstate:
1128 self.ui.warn("%s not tracked!\n" % f)
1128 self.ui.warn("%s not tracked!\n" % f)
1129 else:
1129 else:
1130 self.dirstate.update([f], "r")
1130 self.dirstate.update([f], "r")
1131
1131
1132 def copy(self, source, dest):
1132 def copy(self, source, dest):
1133 p = self.wjoin(dest)
1133 p = self.wjoin(dest)
1134 if not os.path.exists(p):
1134 if not os.path.exists(p):
1135 self.ui.warn("%s does not exist!\n" % dest)
1135 self.ui.warn("%s does not exist!\n" % dest)
1136 elif not os.path.isfile(p):
1136 elif not os.path.isfile(p):
1137 self.ui.warn("copy failed: %s is not a file\n" % dest)
1137 self.ui.warn("copy failed: %s is not a file\n" % dest)
1138 else:
1138 else:
1139 if self.dirstate.state(dest) == '?':
1139 if self.dirstate.state(dest) == '?':
1140 self.dirstate.update([dest], "a")
1140 self.dirstate.update([dest], "a")
1141 self.dirstate.copy(source, dest)
1141 self.dirstate.copy(source, dest)
1142
1142
1143 def heads(self):
1143 def heads(self):
1144 return self.changelog.heads()
1144 return self.changelog.heads()
1145
1145
1146 # branchlookup returns a dict giving a list of branches for
1146 # branchlookup returns a dict giving a list of branches for
1147 # each head. A branch is defined as the tag of a node or
1147 # each head. A branch is defined as the tag of a node or
1148 # the branch of the node's parents. If a node has multiple
1148 # the branch of the node's parents. If a node has multiple
1149 # branch tags, tags are eliminated if they are visible from other
1149 # branch tags, tags are eliminated if they are visible from other
1150 # branch tags.
1150 # branch tags.
1151 #
1151 #
1152 # So, for this graph: a->b->c->d->e
1152 # So, for this graph: a->b->c->d->e
1153 # \ /
1153 # \ /
1154 # aa -----/
1154 # aa -----/
1155 # a has tag 2.6.12
1155 # a has tag 2.6.12
1156 # d has tag 2.6.13
1156 # d has tag 2.6.13
1157 # e would have branch tags for 2.6.12 and 2.6.13. Because the node
1157 # e would have branch tags for 2.6.12 and 2.6.13. Because the node
1158 # for 2.6.12 can be reached from the node 2.6.13, that is eliminated
1158 # for 2.6.12 can be reached from the node 2.6.13, that is eliminated
1159 # from the list.
1159 # from the list.
1160 #
1160 #
1161 # It is possible that more than one head will have the same branch tag.
1161 # It is possible that more than one head will have the same branch tag.
1162 # callers need to check the result for multiple heads under the same
1162 # callers need to check the result for multiple heads under the same
1163 # branch tag if that is a problem for them (ie checkout of a specific
1163 # branch tag if that is a problem for them (ie checkout of a specific
1164 # branch).
1164 # branch).
1165 #
1165 #
1166 # passing in a specific branch will limit the depth of the search
1166 # passing in a specific branch will limit the depth of the search
1167 # through the parents. It won't limit the branches returned in the
1167 # through the parents. It won't limit the branches returned in the
1168 # result though.
1168 # result though.
1169 def branchlookup(self, heads=None, branch=None):
1169 def branchlookup(self, heads=None, branch=None):
1170 if not heads:
1170 if not heads:
1171 heads = self.heads()
1171 heads = self.heads()
1172 headt = [ h for h in heads ]
1172 headt = [ h for h in heads ]
1173 chlog = self.changelog
1173 chlog = self.changelog
1174 branches = {}
1174 branches = {}
1175 merges = []
1175 merges = []
1176 seenmerge = {}
1176 seenmerge = {}
1177
1177
1178 # traverse the tree once for each head, recording in the branches
1178 # traverse the tree once for each head, recording in the branches
1179 # dict which tags are visible from this head. The branches
1179 # dict which tags are visible from this head. The branches
1180 # dict also records which tags are visible from each tag
1180 # dict also records which tags are visible from each tag
1181 # while we traverse.
1181 # while we traverse.
1182 while headt or merges:
1182 while headt or merges:
1183 if merges:
1183 if merges:
1184 n, found = merges.pop()
1184 n, found = merges.pop()
1185 visit = [n]
1185 visit = [n]
1186 else:
1186 else:
1187 h = headt.pop()
1187 h = headt.pop()
1188 visit = [h]
1188 visit = [h]
1189 found = [h]
1189 found = [h]
1190 seen = {}
1190 seen = {}
1191 while visit:
1191 while visit:
1192 n = visit.pop()
1192 n = visit.pop()
1193 if n in seen:
1193 if n in seen:
1194 continue
1194 continue
1195 pp = chlog.parents(n)
1195 pp = chlog.parents(n)
1196 tags = self.nodetags(n)
1196 tags = self.nodetags(n)
1197 if tags:
1197 if tags:
1198 for x in tags:
1198 for x in tags:
1199 if x == 'tip':
1199 if x == 'tip':
1200 continue
1200 continue
1201 for f in found:
1201 for f in found:
1202 branches.setdefault(f, {})[n] = 1
1202 branches.setdefault(f, {})[n] = 1
1203 branches.setdefault(n, {})[n] = 1
1203 branches.setdefault(n, {})[n] = 1
1204 break
1204 break
1205 if n not in found:
1205 if n not in found:
1206 found.append(n)
1206 found.append(n)
1207 if branch in tags:
1207 if branch in tags:
1208 continue
1208 continue
1209 seen[n] = 1
1209 seen[n] = 1
1210 if pp[1] != nullid and n not in seenmerge:
1210 if pp[1] != nullid and n not in seenmerge:
1211 merges.append((pp[1], [x for x in found]))
1211 merges.append((pp[1], [x for x in found]))
1212 seenmerge[n] = 1
1212 seenmerge[n] = 1
1213 if pp[0] != nullid:
1213 if pp[0] != nullid:
1214 visit.append(pp[0])
1214 visit.append(pp[0])
1215 # traverse the branches dict, eliminating branch tags from each
1215 # traverse the branches dict, eliminating branch tags from each
1216 # head that are visible from another branch tag for that head.
1216 # head that are visible from another branch tag for that head.
1217 out = {}
1217 out = {}
1218 viscache = {}
1218 viscache = {}
1219 for h in heads:
1219 for h in heads:
1220 def visible(node):
1220 def visible(node):
1221 if node in viscache:
1221 if node in viscache:
1222 return viscache[node]
1222 return viscache[node]
1223 ret = {}
1223 ret = {}
1224 visit = [node]
1224 visit = [node]
1225 while visit:
1225 while visit:
1226 x = visit.pop()
1226 x = visit.pop()
1227 if x in viscache:
1227 if x in viscache:
1228 ret.update(viscache[x])
1228 ret.update(viscache[x])
1229 elif x not in ret:
1229 elif x not in ret:
1230 ret[x] = 1
1230 ret[x] = 1
1231 if x in branches:
1231 if x in branches:
1232 visit[len(visit):] = branches[x].keys()
1232 visit[len(visit):] = branches[x].keys()
1233 viscache[node] = ret
1233 viscache[node] = ret
1234 return ret
1234 return ret
1235 if h not in branches:
1235 if h not in branches:
1236 continue
1236 continue
1237 # O(n^2), but somewhat limited. This only searches the
1237 # O(n^2), but somewhat limited. This only searches the
1238 # tags visible from a specific head, not all the tags in the
1238 # tags visible from a specific head, not all the tags in the
1239 # whole repo.
1239 # whole repo.
1240 for b in branches[h]:
1240 for b in branches[h]:
1241 vis = False
1241 vis = False
1242 for bb in branches[h].keys():
1242 for bb in branches[h].keys():
1243 if b != bb:
1243 if b != bb:
1244 if b in visible(bb):
1244 if b in visible(bb):
1245 vis = True
1245 vis = True
1246 break
1246 break
1247 if not vis:
1247 if not vis:
1248 l = out.setdefault(h, [])
1248 l = out.setdefault(h, [])
1249 l[len(l):] = self.nodetags(b)
1249 l[len(l):] = self.nodetags(b)
1250 return out
1250 return out
1251
1251
1252 def branches(self, nodes):
1252 def branches(self, nodes):
1253 if not nodes: nodes = [self.changelog.tip()]
1253 if not nodes: nodes = [self.changelog.tip()]
1254 b = []
1254 b = []
1255 for n in nodes:
1255 for n in nodes:
1256 t = n
1256 t = n
1257 while n:
1257 while n:
1258 p = self.changelog.parents(n)
1258 p = self.changelog.parents(n)
1259 if p[1] != nullid or p[0] == nullid:
1259 if p[1] != nullid or p[0] == nullid:
1260 b.append((t, n, p[0], p[1]))
1260 b.append((t, n, p[0], p[1]))
1261 break
1261 break
1262 n = p[0]
1262 n = p[0]
1263 return b
1263 return b
1264
1264
1265 def between(self, pairs):
1265 def between(self, pairs):
1266 r = []
1266 r = []
1267
1267
1268 for top, bottom in pairs:
1268 for top, bottom in pairs:
1269 n, l, i = top, [], 0
1269 n, l, i = top, [], 0
1270 f = 1
1270 f = 1
1271
1271
1272 while n != bottom:
1272 while n != bottom:
1273 p = self.changelog.parents(n)[0]
1273 p = self.changelog.parents(n)[0]
1274 if i == f:
1274 if i == f:
1275 l.append(n)
1275 l.append(n)
1276 f = f * 2
1276 f = f * 2
1277 n = p
1277 n = p
1278 i += 1
1278 i += 1
1279
1279
1280 r.append(l)
1280 r.append(l)
1281
1281
1282 return r
1282 return r
1283
1283
1284 def newer(self, nodes):
1284 def newer(self, nodes):
1285 m = {}
1285 m = {}
1286 nl = []
1286 nl = []
1287 pm = {}
1287 pm = {}
1288 cl = self.changelog
1288 cl = self.changelog
1289 t = l = cl.count()
1289 t = l = cl.count()
1290
1290
1291 # find the lowest numbered node
1291 # find the lowest numbered node
1292 for n in nodes:
1292 for n in nodes:
1293 l = min(l, cl.rev(n))
1293 l = min(l, cl.rev(n))
1294 m[n] = 1
1294 m[n] = 1
1295
1295
1296 for i in xrange(l, t):
1296 for i in xrange(l, t):
1297 n = cl.node(i)
1297 n = cl.node(i)
1298 if n in m: # explicitly listed
1298 if n in m: # explicitly listed
1299 pm[n] = 1
1299 pm[n] = 1
1300 nl.append(n)
1300 nl.append(n)
1301 continue
1301 continue
1302 for p in cl.parents(n):
1302 for p in cl.parents(n):
1303 if p in pm: # parent listed
1303 if p in pm: # parent listed
1304 pm[n] = 1
1304 pm[n] = 1
1305 nl.append(n)
1305 nl.append(n)
1306 break
1306 break
1307
1307
1308 return nl
1308 return nl
1309
1309
1310 def findincoming(self, remote, base=None, heads=None):
1310 def findincoming(self, remote, base=None, heads=None):
1311 m = self.changelog.nodemap
1311 m = self.changelog.nodemap
1312 search = []
1312 search = []
1313 fetch = []
1313 fetch = []
1314 seen = {}
1314 seen = {}
1315 seenbranch = {}
1315 seenbranch = {}
1316 if base == None:
1316 if base == None:
1317 base = {}
1317 base = {}
1318
1318
1319 # assume we're closer to the tip than the root
1319 # assume we're closer to the tip than the root
1320 # and start by examining the heads
1320 # and start by examining the heads
1321 self.ui.status("searching for changes\n")
1321 self.ui.status("searching for changes\n")
1322
1322
1323 if not heads:
1323 if not heads:
1324 heads = remote.heads()
1324 heads = remote.heads()
1325
1325
1326 unknown = []
1326 unknown = []
1327 for h in heads:
1327 for h in heads:
1328 if h not in m:
1328 if h not in m:
1329 unknown.append(h)
1329 unknown.append(h)
1330 else:
1330 else:
1331 base[h] = 1
1331 base[h] = 1
1332
1332
1333 if not unknown:
1333 if not unknown:
1334 return None
1334 return None
1335
1335
1336 rep = {}
1336 rep = {}
1337 reqcnt = 0
1337 reqcnt = 0
1338
1338
1339 # search through remote branches
1339 # search through remote branches
1340 # a 'branch' here is a linear segment of history, with four parts:
1340 # a 'branch' here is a linear segment of history, with four parts:
1341 # head, root, first parent, second parent
1341 # head, root, first parent, second parent
1342 # (a branch always has two parents (or none) by definition)
1342 # (a branch always has two parents (or none) by definition)
1343 unknown = remote.branches(unknown)
1343 unknown = remote.branches(unknown)
1344 while unknown:
1344 while unknown:
1345 r = []
1345 r = []
1346 while unknown:
1346 while unknown:
1347 n = unknown.pop(0)
1347 n = unknown.pop(0)
1348 if n[0] in seen:
1348 if n[0] in seen:
1349 continue
1349 continue
1350
1350
1351 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
1351 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
1352 if n[0] == nullid:
1352 if n[0] == nullid:
1353 break
1353 break
1354 if n in seenbranch:
1354 if n in seenbranch:
1355 self.ui.debug("branch already found\n")
1355 self.ui.debug("branch already found\n")
1356 continue
1356 continue
1357 if n[1] and n[1] in m: # do we know the base?
1357 if n[1] and n[1] in m: # do we know the base?
1358 self.ui.debug("found incomplete branch %s:%s\n"
1358 self.ui.debug("found incomplete branch %s:%s\n"
1359 % (short(n[0]), short(n[1])))
1359 % (short(n[0]), short(n[1])))
1360 search.append(n) # schedule branch range for scanning
1360 search.append(n) # schedule branch range for scanning
1361 seenbranch[n] = 1
1361 seenbranch[n] = 1
1362 else:
1362 else:
1363 if n[1] not in seen and n[1] not in fetch:
1363 if n[1] not in seen and n[1] not in fetch:
1364 if n[2] in m and n[3] in m:
1364 if n[2] in m and n[3] in m:
1365 self.ui.debug("found new changeset %s\n" %
1365 self.ui.debug("found new changeset %s\n" %
1366 short(n[1]))
1366 short(n[1]))
1367 fetch.append(n[1]) # earliest unknown
1367 fetch.append(n[1]) # earliest unknown
1368 base[n[2]] = 1 # latest known
1368 base[n[2]] = 1 # latest known
1369 continue
1369 continue
1370
1370
1371 for a in n[2:4]:
1371 for a in n[2:4]:
1372 if a not in rep:
1372 if a not in rep:
1373 r.append(a)
1373 r.append(a)
1374 rep[a] = 1
1374 rep[a] = 1
1375
1375
1376 seen[n[0]] = 1
1376 seen[n[0]] = 1
1377
1377
1378 if r:
1378 if r:
1379 reqcnt += 1
1379 reqcnt += 1
1380 self.ui.debug("request %d: %s\n" %
1380 self.ui.debug("request %d: %s\n" %
1381 (reqcnt, " ".join(map(short, r))))
1381 (reqcnt, " ".join(map(short, r))))
1382 for p in range(0, len(r), 10):
1382 for p in range(0, len(r), 10):
1383 for b in remote.branches(r[p:p+10]):
1383 for b in remote.branches(r[p:p+10]):
1384 self.ui.debug("received %s:%s\n" %
1384 self.ui.debug("received %s:%s\n" %
1385 (short(b[0]), short(b[1])))
1385 (short(b[0]), short(b[1])))
1386 if b[0] not in m and b[0] not in seen:
1386 if b[0] not in m and b[0] not in seen:
1387 unknown.append(b)
1387 unknown.append(b)
1388
1388
1389 # do binary search on the branches we found
1389 # do binary search on the branches we found
1390 while search:
1390 while search:
1391 n = search.pop(0)
1391 n = search.pop(0)
1392 reqcnt += 1
1392 reqcnt += 1
1393 l = remote.between([(n[0], n[1])])[0]
1393 l = remote.between([(n[0], n[1])])[0]
1394 l.append(n[1])
1394 l.append(n[1])
1395 p = n[0]
1395 p = n[0]
1396 f = 1
1396 f = 1
1397 for i in l:
1397 for i in l:
1398 self.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
1398 self.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
1399 if i in m:
1399 if i in m:
1400 if f <= 2:
1400 if f <= 2:
1401 self.ui.debug("found new branch changeset %s\n" %
1401 self.ui.debug("found new branch changeset %s\n" %
1402 short(p))
1402 short(p))
1403 fetch.append(p)
1403 fetch.append(p)
1404 base[i] = 1
1404 base[i] = 1
1405 else:
1405 else:
1406 self.ui.debug("narrowed branch search to %s:%s\n"
1406 self.ui.debug("narrowed branch search to %s:%s\n"
1407 % (short(p), short(i)))
1407 % (short(p), short(i)))
1408 search.append((p, i))
1408 search.append((p, i))
1409 break
1409 break
1410 p, f = i, f * 2
1410 p, f = i, f * 2
1411
1411
1412 # sanity check our fetch list
1412 # sanity check our fetch list
1413 for f in fetch:
1413 for f in fetch:
1414 if f in m:
1414 if f in m:
1415 raise RepoError("already have changeset " + short(f[:4]))
1415 raise RepoError("already have changeset " + short(f[:4]))
1416
1416
1417 if base.keys() == [nullid]:
1417 if base.keys() == [nullid]:
1418 self.ui.warn("warning: pulling from an unrelated repository!\n")
1418 self.ui.warn("warning: pulling from an unrelated repository!\n")
1419
1419
1420 self.ui.note("adding new changesets starting at " +
1420 self.ui.note("adding new changesets starting at " +
1421 " ".join([short(f) for f in fetch]) + "\n")
1421 " ".join([short(f) for f in fetch]) + "\n")
1422
1422
1423 self.ui.debug("%d total queries\n" % reqcnt)
1423 self.ui.debug("%d total queries\n" % reqcnt)
1424
1424
1425 return fetch
1425 return fetch
1426
1426
1427 def findoutgoing(self, remote, base=None, heads=None):
1427 def findoutgoing(self, remote, base=None, heads=None):
1428 if base == None:
1428 if base == None:
1429 base = {}
1429 base = {}
1430 self.findincoming(remote, base, heads)
1430 self.findincoming(remote, base, heads)
1431
1431
1432 remain = dict.fromkeys(self.changelog.nodemap)
1432 remain = dict.fromkeys(self.changelog.nodemap)
1433
1433
1434 # prune everything remote has from the tree
1434 # prune everything remote has from the tree
1435 del remain[nullid]
1435 del remain[nullid]
1436 remove = base.keys()
1436 remove = base.keys()
1437 while remove:
1437 while remove:
1438 n = remove.pop(0)
1438 n = remove.pop(0)
1439 if n in remain:
1439 if n in remain:
1440 del remain[n]
1440 del remain[n]
1441 for p in self.changelog.parents(n):
1441 for p in self.changelog.parents(n):
1442 remove.append(p)
1442 remove.append(p)
1443
1443
1444 # find every node whose parents have been pruned
1444 # find every node whose parents have been pruned
1445 subset = []
1445 subset = []
1446 for n in remain:
1446 for n in remain:
1447 p1, p2 = self.changelog.parents(n)
1447 p1, p2 = self.changelog.parents(n)
1448 if p1 not in remain and p2 not in remain:
1448 if p1 not in remain and p2 not in remain:
1449 subset.append(n)
1449 subset.append(n)
1450
1450
1451 # this is the set of all roots we have to push
1451 # this is the set of all roots we have to push
1452 return subset
1452 return subset
1453
1453
1454 def pull(self, remote):
1454 def pull(self, remote):
1455 lock = self.lock()
1455 lock = self.lock()
1456
1456
1457 # if we have an empty repo, fetch everything
1457 # if we have an empty repo, fetch everything
1458 if self.changelog.tip() == nullid:
1458 if self.changelog.tip() == nullid:
1459 self.ui.status("requesting all changes\n")
1459 self.ui.status("requesting all changes\n")
1460 fetch = [nullid]
1460 fetch = [nullid]
1461 else:
1461 else:
1462 fetch = self.findincoming(remote)
1462 fetch = self.findincoming(remote)
1463
1463
1464 if not fetch:
1464 if not fetch:
1465 self.ui.status("no changes found\n")
1465 self.ui.status("no changes found\n")
1466 return 1
1466 return 1
1467
1467
1468 cg = remote.changegroup(fetch)
1468 cg = remote.changegroup(fetch)
1469 return self.addchangegroup(cg)
1469 return self.addchangegroup(cg)
1470
1470
1471 def push(self, remote, force=False):
1471 def push(self, remote, force=False):
1472 lock = remote.lock()
1472 lock = remote.lock()
1473
1473
1474 base = {}
1474 base = {}
1475 heads = remote.heads()
1475 heads = remote.heads()
1476 inc = self.findincoming(remote, base, heads)
1476 inc = self.findincoming(remote, base, heads)
1477 if not force and inc:
1477 if not force and inc:
1478 self.ui.warn("abort: unsynced remote changes!\n")
1478 self.ui.warn("abort: unsynced remote changes!\n")
1479 self.ui.status("(did you forget to sync? use push -f to force)\n")
1479 self.ui.status("(did you forget to sync? use push -f to force)\n")
1480 return 1
1480 return 1
1481
1481
1482 update = self.findoutgoing(remote, base)
1482 update = self.findoutgoing(remote, base)
1483 if not update:
1483 if not update:
1484 self.ui.status("no changes found\n")
1484 self.ui.status("no changes found\n")
1485 return 1
1485 return 1
1486 elif not force:
1486 elif not force:
1487 if len(heads) < len(self.changelog.heads()):
1487 if len(heads) < len(self.changelog.heads()):
1488 self.ui.warn("abort: push creates new remote branches!\n")
1488 self.ui.warn("abort: push creates new remote branches!\n")
1489 self.ui.status("(did you forget to merge?" +
1489 self.ui.status("(did you forget to merge?" +
1490 " use push -f to force)\n")
1490 " use push -f to force)\n")
1491 return 1
1491 return 1
1492
1492
1493 cg = self.changegroup(update)
1493 cg = self.changegroup(update)
1494 return remote.addchangegroup(cg)
1494 return remote.addchangegroup(cg)
1495
1495
1496 def changegroup(self, basenodes):
1496 def changegroup(self, basenodes):
1497 class genread:
1497 class genread:
1498 def __init__(self, generator):
1498 def __init__(self, generator):
1499 self.g = generator
1499 self.g = generator
1500 self.buf = ""
1500 self.buf = ""
1501 def fillbuf(self):
1501 def fillbuf(self):
1502 self.buf += "".join(self.g)
1502 self.buf += "".join(self.g)
1503
1503
1504 def read(self, l):
1504 def read(self, l):
1505 while l > len(self.buf):
1505 while l > len(self.buf):
1506 try:
1506 try:
1507 self.buf += self.g.next()
1507 self.buf += self.g.next()
1508 except StopIteration:
1508 except StopIteration:
1509 break
1509 break
1510 d, self.buf = self.buf[:l], self.buf[l:]
1510 d, self.buf = self.buf[:l], self.buf[l:]
1511 return d
1511 return d
1512
1512
1513 def gengroup():
1513 def gengroup():
1514 nodes = self.newer(basenodes)
1514 nodes = self.newer(basenodes)
1515
1515
1516 # construct the link map
1516 # construct the link map
1517 linkmap = {}
1517 linkmap = {}
1518 for n in nodes:
1518 for n in nodes:
1519 linkmap[self.changelog.rev(n)] = n
1519 linkmap[self.changelog.rev(n)] = n
1520
1520
1521 # construct a list of all changed files
1521 # construct a list of all changed files
1522 changed = {}
1522 changed = {}
1523 for n in nodes:
1523 for n in nodes:
1524 c = self.changelog.read(n)
1524 c = self.changelog.read(n)
1525 for f in c[3]:
1525 for f in c[3]:
1526 changed[f] = 1
1526 changed[f] = 1
1527 changed = changed.keys()
1527 changed = changed.keys()
1528 changed.sort()
1528 changed.sort()
1529
1529
1530 # the changegroup is changesets + manifests + all file revs
1530 # the changegroup is changesets + manifests + all file revs
1531 revs = [ self.changelog.rev(n) for n in nodes ]
1531 revs = [ self.changelog.rev(n) for n in nodes ]
1532
1532
1533 for y in self.changelog.group(linkmap): yield y
1533 for y in self.changelog.group(linkmap): yield y
1534 for y in self.manifest.group(linkmap): yield y
1534 for y in self.manifest.group(linkmap): yield y
1535 for f in changed:
1535 for f in changed:
1536 yield struct.pack(">l", len(f) + 4) + f
1536 yield struct.pack(">l", len(f) + 4) + f
1537 g = self.file(f).group(linkmap)
1537 g = self.file(f).group(linkmap)
1538 for y in g:
1538 for y in g:
1539 yield y
1539 yield y
1540
1540
1541 yield struct.pack(">l", 0)
1541 yield struct.pack(">l", 0)
1542
1542
1543 return genread(gengroup())
1543 return genread(gengroup())
1544
1544
1545 def addchangegroup(self, source):
1545 def addchangegroup(self, source):
1546
1546
1547 def getchunk():
1547 def getchunk():
1548 d = source.read(4)
1548 d = source.read(4)
1549 if not d: return ""
1549 if not d: return ""
1550 l = struct.unpack(">l", d)[0]
1550 l = struct.unpack(">l", d)[0]
1551 if l <= 4: return ""
1551 if l <= 4: return ""
1552 return source.read(l - 4)
1552 return source.read(l - 4)
1553
1553
1554 def getgroup():
1554 def getgroup():
1555 while 1:
1555 while 1:
1556 c = getchunk()
1556 c = getchunk()
1557 if not c: break
1557 if not c: break
1558 yield c
1558 yield c
1559
1559
1560 def csmap(x):
1560 def csmap(x):
1561 self.ui.debug("add changeset %s\n" % short(x))
1561 self.ui.debug("add changeset %s\n" % short(x))
1562 return self.changelog.count()
1562 return self.changelog.count()
1563
1563
1564 def revmap(x):
1564 def revmap(x):
1565 return self.changelog.rev(x)
1565 return self.changelog.rev(x)
1566
1566
1567 if not source: return
1567 if not source: return
1568 changesets = files = revisions = 0
1568 changesets = files = revisions = 0
1569
1569
1570 tr = self.transaction()
1570 tr = self.transaction()
1571
1571
1572 oldheads = len(self.changelog.heads())
1572 oldheads = len(self.changelog.heads())
1573
1573
1574 # pull off the changeset group
1574 # pull off the changeset group
1575 self.ui.status("adding changesets\n")
1575 self.ui.status("adding changesets\n")
1576 co = self.changelog.tip()
1576 co = self.changelog.tip()
1577 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
1577 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
1578 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
1578 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
1579
1579
1580 # pull off the manifest group
1580 # pull off the manifest group
1581 self.ui.status("adding manifests\n")
1581 self.ui.status("adding manifests\n")
1582 mm = self.manifest.tip()
1582 mm = self.manifest.tip()
1583 mo = self.manifest.addgroup(getgroup(), revmap, tr)
1583 mo = self.manifest.addgroup(getgroup(), revmap, tr)
1584
1584
1585 # process the files
1585 # process the files
1586 self.ui.status("adding file changes\n")
1586 self.ui.status("adding file changes\n")
1587 while 1:
1587 while 1:
1588 f = getchunk()
1588 f = getchunk()
1589 if not f: break
1589 if not f: break
1590 self.ui.debug("adding %s revisions\n" % f)
1590 self.ui.debug("adding %s revisions\n" % f)
1591 fl = self.file(f)
1591 fl = self.file(f)
1592 o = fl.count()
1592 o = fl.count()
1593 n = fl.addgroup(getgroup(), revmap, tr)
1593 n = fl.addgroup(getgroup(), revmap, tr)
1594 revisions += fl.count() - o
1594 revisions += fl.count() - o
1595 files += 1
1595 files += 1
1596
1596
1597 newheads = len(self.changelog.heads())
1597 newheads = len(self.changelog.heads())
1598 heads = ""
1598 heads = ""
1599 if oldheads and newheads > oldheads:
1599 if oldheads and newheads > oldheads:
1600 heads = " (+%d heads)" % (newheads - oldheads)
1600 heads = " (+%d heads)" % (newheads - oldheads)
1601
1601
1602 self.ui.status(("added %d changesets" +
1602 self.ui.status(("added %d changesets" +
1603 " with %d changes to %d files%s\n")
1603 " with %d changes to %d files%s\n")
1604 % (changesets, revisions, files, heads))
1604 % (changesets, revisions, files, heads))
1605
1605
1606 tr.close()
1606 tr.close()
1607
1607
1608 if not self.hook("changegroup"):
1608 if not self.hook("changegroup"):
1609 return 1
1609 return 1
1610
1610
1611 return
1611 return
1612
1612
1613 def update(self, node, allow=False, force=False, choose=None,
1613 def update(self, node, allow=False, force=False, choose=None,
1614 moddirstate=True):
1614 moddirstate=True):
1615 pl = self.dirstate.parents()
1615 pl = self.dirstate.parents()
1616 if not force and pl[1] != nullid:
1616 if not force and pl[1] != nullid:
1617 self.ui.warn("aborting: outstanding uncommitted merges\n")
1617 self.ui.warn("aborting: outstanding uncommitted merges\n")
1618 return 1
1618 return 1
1619
1619
1620 p1, p2 = pl[0], node
1620 p1, p2 = pl[0], node
1621 pa = self.changelog.ancestor(p1, p2)
1621 pa = self.changelog.ancestor(p1, p2)
1622 m1n = self.changelog.read(p1)[0]
1622 m1n = self.changelog.read(p1)[0]
1623 m2n = self.changelog.read(p2)[0]
1623 m2n = self.changelog.read(p2)[0]
1624 man = self.manifest.ancestor(m1n, m2n)
1624 man = self.manifest.ancestor(m1n, m2n)
1625 m1 = self.manifest.read(m1n)
1625 m1 = self.manifest.read(m1n)
1626 mf1 = self.manifest.readflags(m1n)
1626 mf1 = self.manifest.readflags(m1n)
1627 m2 = self.manifest.read(m2n)
1627 m2 = self.manifest.read(m2n)
1628 mf2 = self.manifest.readflags(m2n)
1628 mf2 = self.manifest.readflags(m2n)
1629 ma = self.manifest.read(man)
1629 ma = self.manifest.read(man)
1630 mfa = self.manifest.readflags(man)
1630 mfa = self.manifest.readflags(man)
1631
1631
1632 (c, a, d, u) = self.changes()
1632 (c, a, d, u) = self.changes()
1633
1633
1634 # is this a jump, or a merge? i.e. is there a linear path
1634 # is this a jump, or a merge? i.e. is there a linear path
1635 # from p1 to p2?
1635 # from p1 to p2?
1636 linear_path = (pa == p1 or pa == p2)
1636 linear_path = (pa == p1 or pa == p2)
1637
1637
1638 # resolve the manifest to determine which files
1638 # resolve the manifest to determine which files
1639 # we care about merging
1639 # we care about merging
1640 self.ui.note("resolving manifests\n")
1640 self.ui.note("resolving manifests\n")
1641 self.ui.debug(" force %s allow %s moddirstate %s linear %s\n" %
1641 self.ui.debug(" force %s allow %s moddirstate %s linear %s\n" %
1642 (force, allow, moddirstate, linear_path))
1642 (force, allow, moddirstate, linear_path))
1643 self.ui.debug(" ancestor %s local %s remote %s\n" %
1643 self.ui.debug(" ancestor %s local %s remote %s\n" %
1644 (short(man), short(m1n), short(m2n)))
1644 (short(man), short(m1n), short(m2n)))
1645
1645
1646 merge = {}
1646 merge = {}
1647 get = {}
1647 get = {}
1648 remove = []
1648 remove = []
1649
1649
1650 # construct a working dir manifest
1650 # construct a working dir manifest
1651 mw = m1.copy()
1651 mw = m1.copy()
1652 mfw = mf1.copy()
1652 mfw = mf1.copy()
1653 umap = dict.fromkeys(u)
1653 umap = dict.fromkeys(u)
1654
1654
1655 for f in a + c + u:
1655 for f in a + c + u:
1656 mw[f] = ""
1656 mw[f] = ""
1657 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1657 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1658
1658
1659 for f in d:
1659 for f in d:
1660 if f in mw: del mw[f]
1660 if f in mw: del mw[f]
1661
1661
1662 # If we're jumping between revisions (as opposed to merging),
1662 # If we're jumping between revisions (as opposed to merging),
1663 # and if neither the working directory nor the target rev has
1663 # and if neither the working directory nor the target rev has
1664 # the file, then we need to remove it from the dirstate, to
1664 # the file, then we need to remove it from the dirstate, to
1665 # prevent the dirstate from listing the file when it is no
1665 # prevent the dirstate from listing the file when it is no
1666 # longer in the manifest.
1666 # longer in the manifest.
1667 if moddirstate and linear_path and f not in m2:
1667 if moddirstate and linear_path and f not in m2:
1668 self.dirstate.forget((f,))
1668 self.dirstate.forget((f,))
1669
1669
1670 # Compare manifests
1670 # Compare manifests
1671 for f, n in mw.iteritems():
1671 for f, n in mw.iteritems():
1672 if choose and not choose(f): continue
1672 if choose and not choose(f): continue
1673 if f in m2:
1673 if f in m2:
1674 s = 0
1674 s = 0
1675
1675
1676 # is the wfile new since m1, and match m2?
1676 # is the wfile new since m1, and match m2?
1677 if f not in m1:
1677 if f not in m1:
1678 t1 = self.wread(f)
1678 t1 = self.wread(f)
1679 t2 = self.file(f).read(m2[f])
1679 t2 = self.file(f).read(m2[f])
1680 if cmp(t1, t2) == 0:
1680 if cmp(t1, t2) == 0:
1681 n = m2[f]
1681 n = m2[f]
1682 del t1, t2
1682 del t1, t2
1683
1683
1684 # are files different?
1684 # are files different?
1685 if n != m2[f]:
1685 if n != m2[f]:
1686 a = ma.get(f, nullid)
1686 a = ma.get(f, nullid)
1687 # are both different from the ancestor?
1687 # are both different from the ancestor?
1688 if n != a and m2[f] != a:
1688 if n != a and m2[f] != a:
1689 self.ui.debug(" %s versions differ, resolve\n" % f)
1689 self.ui.debug(" %s versions differ, resolve\n" % f)
1690 # merge executable bits
1690 # merge executable bits
1691 # "if we changed or they changed, change in merge"
1691 # "if we changed or they changed, change in merge"
1692 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1692 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1693 mode = ((a^b) | (a^c)) ^ a
1693 mode = ((a^b) | (a^c)) ^ a
1694 merge[f] = (m1.get(f, nullid), m2[f], mode)
1694 merge[f] = (m1.get(f, nullid), m2[f], mode)
1695 s = 1
1695 s = 1
1696 # are we clobbering?
1696 # are we clobbering?
1697 # is remote's version newer?
1697 # is remote's version newer?
1698 # or are we going back in time?
1698 # or are we going back in time?
1699 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1699 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1700 self.ui.debug(" remote %s is newer, get\n" % f)
1700 self.ui.debug(" remote %s is newer, get\n" % f)
1701 get[f] = m2[f]
1701 get[f] = m2[f]
1702 s = 1
1702 s = 1
1703 elif f in umap:
1703 elif f in umap:
1704 # this unknown file is the same as the checkout
1704 # this unknown file is the same as the checkout
1705 get[f] = m2[f]
1705 get[f] = m2[f]
1706
1706
1707 if not s and mfw[f] != mf2[f]:
1707 if not s and mfw[f] != mf2[f]:
1708 if force:
1708 if force:
1709 self.ui.debug(" updating permissions for %s\n" % f)
1709 self.ui.debug(" updating permissions for %s\n" % f)
1710 util.set_exec(self.wjoin(f), mf2[f])
1710 util.set_exec(self.wjoin(f), mf2[f])
1711 else:
1711 else:
1712 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1712 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1713 mode = ((a^b) | (a^c)) ^ a
1713 mode = ((a^b) | (a^c)) ^ a
1714 if mode != b:
1714 if mode != b:
1715 self.ui.debug(" updating permissions for %s\n" % f)
1715 self.ui.debug(" updating permissions for %s\n" % f)
1716 util.set_exec(self.wjoin(f), mode)
1716 util.set_exec(self.wjoin(f), mode)
1717 del m2[f]
1717 del m2[f]
1718 elif f in ma:
1718 elif f in ma:
1719 if n != ma[f]:
1719 if n != ma[f]:
1720 r = "d"
1720 r = "d"
1721 if not force and (linear_path or allow):
1721 if not force and (linear_path or allow):
1722 r = self.ui.prompt(
1722 r = self.ui.prompt(
1723 (" local changed %s which remote deleted\n" % f) +
1723 (" local changed %s which remote deleted\n" % f) +
1724 "(k)eep or (d)elete?", "[kd]", "k")
1724 "(k)eep or (d)elete?", "[kd]", "k")
1725 if r == "d":
1725 if r == "d":
1726 remove.append(f)
1726 remove.append(f)
1727 else:
1727 else:
1728 self.ui.debug("other deleted %s\n" % f)
1728 self.ui.debug("other deleted %s\n" % f)
1729 remove.append(f) # other deleted it
1729 remove.append(f) # other deleted it
1730 else:
1730 else:
1731 if n == m1.get(f, nullid): # same as parent
1731 if n == m1.get(f, nullid): # same as parent
1732 if p2 == pa: # going backwards?
1732 if p2 == pa: # going backwards?
1733 self.ui.debug("remote deleted %s\n" % f)
1733 self.ui.debug("remote deleted %s\n" % f)
1734 remove.append(f)
1734 remove.append(f)
1735 else:
1735 else:
1736 self.ui.debug("local created %s, keeping\n" % f)
1736 self.ui.debug("local created %s, keeping\n" % f)
1737 else:
1737 else:
1738 self.ui.debug("working dir created %s, keeping\n" % f)
1738 self.ui.debug("working dir created %s, keeping\n" % f)
1739
1739
1740 for f, n in m2.iteritems():
1740 for f, n in m2.iteritems():
1741 if choose and not choose(f): continue
1741 if choose and not choose(f): continue
1742 if f[0] == "/": continue
1742 if f[0] == "/": continue
1743 if f in ma and n != ma[f]:
1743 if f in ma and n != ma[f]:
1744 r = "k"
1744 r = "k"
1745 if not force and (linear_path or allow):
1745 if not force and (linear_path or allow):
1746 r = self.ui.prompt(
1746 r = self.ui.prompt(
1747 ("remote changed %s which local deleted\n" % f) +
1747 ("remote changed %s which local deleted\n" % f) +
1748 "(k)eep or (d)elete?", "[kd]", "k")
1748 "(k)eep or (d)elete?", "[kd]", "k")
1749 if r == "k": get[f] = n
1749 if r == "k": get[f] = n
1750 elif f not in ma:
1750 elif f not in ma:
1751 self.ui.debug("remote created %s\n" % f)
1751 self.ui.debug("remote created %s\n" % f)
1752 get[f] = n
1752 get[f] = n
1753 else:
1753 else:
1754 if force or p2 == pa: # going backwards?
1754 if force or p2 == pa: # going backwards?
1755 self.ui.debug("local deleted %s, recreating\n" % f)
1755 self.ui.debug("local deleted %s, recreating\n" % f)
1756 get[f] = n
1756 get[f] = n
1757 else:
1757 else:
1758 self.ui.debug("local deleted %s\n" % f)
1758 self.ui.debug("local deleted %s\n" % f)
1759
1759
1760 del mw, m1, m2, ma
1760 del mw, m1, m2, ma
1761
1761
1762 if force:
1762 if force:
1763 for f in merge:
1763 for f in merge:
1764 get[f] = merge[f][1]
1764 get[f] = merge[f][1]
1765 merge = {}
1765 merge = {}
1766
1766
1767 if linear_path or force:
1767 if linear_path or force:
1768 # we don't need to do any magic, just jump to the new rev
1768 # we don't need to do any magic, just jump to the new rev
1769 branch_merge = False
1769 branch_merge = False
1770 p1, p2 = p2, nullid
1770 p1, p2 = p2, nullid
1771 else:
1771 else:
1772 if not allow:
1772 if not allow:
1773 self.ui.status("this update spans a branch" +
1773 self.ui.status("this update spans a branch" +
1774 " affecting the following files:\n")
1774 " affecting the following files:\n")
1775 fl = merge.keys() + get.keys()
1775 fl = merge.keys() + get.keys()
1776 fl.sort()
1776 fl.sort()
1777 for f in fl:
1777 for f in fl:
1778 cf = ""
1778 cf = ""
1779 if f in merge: cf = " (resolve)"
1779 if f in merge: cf = " (resolve)"
1780 self.ui.status(" %s%s\n" % (f, cf))
1780 self.ui.status(" %s%s\n" % (f, cf))
1781 self.ui.warn("aborting update spanning branches!\n")
1781 self.ui.warn("aborting update spanning branches!\n")
1782 self.ui.status("(use update -m to merge across branches" +
1782 self.ui.status("(use update -m to merge across branches" +
1783 " or -C to lose changes)\n")
1783 " or -C to lose changes)\n")
1784 return 1
1784 return 1
1785 branch_merge = True
1785 branch_merge = True
1786
1786
1787 if moddirstate:
1787 if moddirstate:
1788 self.dirstate.setparents(p1, p2)
1788 self.dirstate.setparents(p1, p2)
1789
1789
1790 # get the files we don't need to change
1790 # get the files we don't need to change
1791 files = get.keys()
1791 files = get.keys()
1792 files.sort()
1792 files.sort()
1793 for f in files:
1793 for f in files:
1794 if f[0] == "/": continue
1794 if f[0] == "/": continue
1795 self.ui.note("getting %s\n" % f)
1795 self.ui.note("getting %s\n" % f)
1796 t = self.file(f).read(get[f])
1796 t = self.file(f).read(get[f])
1797 try:
1797 try:
1798 self.wwrite(f, t)
1798 self.wwrite(f, t)
1799 except IOError:
1799 except IOError:
1800 os.makedirs(os.path.dirname(self.wjoin(f)))
1800 os.makedirs(os.path.dirname(self.wjoin(f)))
1801 self.wwrite(f, t)
1801 self.wwrite(f, t)
1802 util.set_exec(self.wjoin(f), mf2[f])
1802 util.set_exec(self.wjoin(f), mf2[f])
1803 if moddirstate:
1803 if moddirstate:
1804 if branch_merge:
1804 if branch_merge:
1805 self.dirstate.update([f], 'n', st_mtime=-1)
1805 self.dirstate.update([f], 'n', st_mtime=-1)
1806 else:
1806 else:
1807 self.dirstate.update([f], 'n')
1807 self.dirstate.update([f], 'n')
1808
1808
1809 # merge the tricky bits
1809 # merge the tricky bits
1810 files = merge.keys()
1810 files = merge.keys()
1811 files.sort()
1811 files.sort()
1812 for f in files:
1812 for f in files:
1813 self.ui.status("merging %s\n" % f)
1813 self.ui.status("merging %s\n" % f)
1814 my, other, flag = merge[f]
1814 my, other, flag = merge[f]
1815 self.merge3(f, my, other)
1815 self.merge3(f, my, other)
1816 util.set_exec(self.wjoin(f), flag)
1816 util.set_exec(self.wjoin(f), flag)
1817 if moddirstate:
1817 if moddirstate:
1818 if branch_merge:
1818 if branch_merge:
1819 # We've done a branch merge, mark this file as merged
1819 # We've done a branch merge, mark this file as merged
1820 # so that we properly record the merger later
1820 # so that we properly record the merger later
1821 self.dirstate.update([f], 'm')
1821 self.dirstate.update([f], 'm')
1822 else:
1822 else:
1823 # We've update-merged a locally modified file, so
1823 # We've update-merged a locally modified file, so
1824 # we set the dirstate to emulate a normal checkout
1824 # we set the dirstate to emulate a normal checkout
1825 # of that file some time in the past. Thus our
1825 # of that file some time in the past. Thus our
1826 # merge will appear as a normal local file
1826 # merge will appear as a normal local file
1827 # modification.
1827 # modification.
1828 f_len = len(self.file(f).read(other))
1828 f_len = len(self.file(f).read(other))
1829 self.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
1829 self.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
1830
1830
1831 remove.sort()
1831 remove.sort()
1832 for f in remove:
1832 for f in remove:
1833 self.ui.note("removing %s\n" % f)
1833 self.ui.note("removing %s\n" % f)
1834 try:
1834 try:
1835 os.unlink(self.wjoin(f))
1835 os.unlink(self.wjoin(f))
1836 except OSError, inst:
1836 except OSError, inst:
1837 self.ui.warn("update failed to remove %s: %s!\n" % (f, inst))
1837 self.ui.warn("update failed to remove %s: %s!\n" % (f, inst))
1838 # try removing directories that might now be empty
1838 # try removing directories that might now be empty
1839 try: os.removedirs(os.path.dirname(self.wjoin(f)))
1839 try: os.removedirs(os.path.dirname(self.wjoin(f)))
1840 except: pass
1840 except: pass
1841 if moddirstate:
1841 if moddirstate:
1842 if branch_merge:
1842 if branch_merge:
1843 self.dirstate.update(remove, 'r')
1843 self.dirstate.update(remove, 'r')
1844 else:
1844 else:
1845 self.dirstate.forget(remove)
1845 self.dirstate.forget(remove)
1846
1846
1847 def merge3(self, fn, my, other):
1847 def merge3(self, fn, my, other):
1848 """perform a 3-way merge in the working directory"""
1848 """perform a 3-way merge in the working directory"""
1849
1849
1850 def temp(prefix, node):
1850 def temp(prefix, node):
1851 pre = "%s~%s." % (os.path.basename(fn), prefix)
1851 pre = "%s~%s." % (os.path.basename(fn), prefix)
1852 (fd, name) = tempfile.mkstemp("", pre)
1852 (fd, name) = tempfile.mkstemp("", pre)
1853 f = os.fdopen(fd, "wb")
1853 f = os.fdopen(fd, "wb")
1854 self.wwrite(fn, fl.read(node), f)
1854 self.wwrite(fn, fl.read(node), f)
1855 f.close()
1855 f.close()
1856 return name
1856 return name
1857
1857
1858 fl = self.file(fn)
1858 fl = self.file(fn)
1859 base = fl.ancestor(my, other)
1859 base = fl.ancestor(my, other)
1860 a = self.wjoin(fn)
1860 a = self.wjoin(fn)
1861 b = temp("base", base)
1861 b = temp("base", base)
1862 c = temp("other", other)
1862 c = temp("other", other)
1863
1863
1864 self.ui.note("resolving %s\n" % fn)
1864 self.ui.note("resolving %s\n" % fn)
1865 self.ui.debug("file %s: other %s ancestor %s\n" %
1865 self.ui.debug("file %s: other %s ancestor %s\n" %
1866 (fn, short(other), short(base)))
1866 (fn, short(other), short(base)))
1867
1867
1868 cmd = (os.environ.get("HGMERGE") or self.ui.config("ui", "merge")
1868 cmd = (os.environ.get("HGMERGE") or self.ui.config("ui", "merge")
1869 or "hgmerge")
1869 or "hgmerge")
1870 r = os.system("%s %s %s %s" % (cmd, a, b, c))
1870 r = os.system("%s %s %s %s" % (cmd, a, b, c))
1871 if r:
1871 if r:
1872 self.ui.warn("merging %s failed!\n" % fn)
1872 self.ui.warn("merging %s failed!\n" % fn)
1873
1873
1874 os.unlink(b)
1874 os.unlink(b)
1875 os.unlink(c)
1875 os.unlink(c)
1876
1876
1877 def verify(self):
1877 def verify(self):
1878 filelinkrevs = {}
1878 filelinkrevs = {}
1879 filenodes = {}
1879 filenodes = {}
1880 changesets = revisions = files = 0
1880 changesets = revisions = files = 0
1881 errors = 0
1881 errors = 0
1882
1882
1883 seen = {}
1883 seen = {}
1884 self.ui.status("checking changesets\n")
1884 self.ui.status("checking changesets\n")
1885 for i in range(self.changelog.count()):
1885 for i in range(self.changelog.count()):
1886 changesets += 1
1886 changesets += 1
1887 n = self.changelog.node(i)
1887 n = self.changelog.node(i)
1888 if n in seen:
1888 if n in seen:
1889 self.ui.warn("duplicate changeset at revision %d\n" % i)
1889 self.ui.warn("duplicate changeset at revision %d\n" % i)
1890 errors += 1
1890 errors += 1
1891 seen[n] = 1
1891 seen[n] = 1
1892
1892
1893 for p in self.changelog.parents(n):
1893 for p in self.changelog.parents(n):
1894 if p not in self.changelog.nodemap:
1894 if p not in self.changelog.nodemap:
1895 self.ui.warn("changeset %s has unknown parent %s\n" %
1895 self.ui.warn("changeset %s has unknown parent %s\n" %
1896 (short(n), short(p)))
1896 (short(n), short(p)))
1897 errors += 1
1897 errors += 1
1898 try:
1898 try:
1899 changes = self.changelog.read(n)
1899 changes = self.changelog.read(n)
1900 except Exception, inst:
1900 except Exception, inst:
1901 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1901 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1902 errors += 1
1902 errors += 1
1903
1903
1904 for f in changes[3]:
1904 for f in changes[3]:
1905 filelinkrevs.setdefault(f, []).append(i)
1905 filelinkrevs.setdefault(f, []).append(i)
1906
1906
1907 seen = {}
1907 seen = {}
1908 self.ui.status("checking manifests\n")
1908 self.ui.status("checking manifests\n")
1909 for i in range(self.manifest.count()):
1909 for i in range(self.manifest.count()):
1910 n = self.manifest.node(i)
1910 n = self.manifest.node(i)
1911 if n in seen:
1911 if n in seen:
1912 self.ui.warn("duplicate manifest at revision %d\n" % i)
1912 self.ui.warn("duplicate manifest at revision %d\n" % i)
1913 errors += 1
1913 errors += 1
1914 seen[n] = 1
1914 seen[n] = 1
1915
1915
1916 for p in self.manifest.parents(n):
1916 for p in self.manifest.parents(n):
1917 if p not in self.manifest.nodemap:
1917 if p not in self.manifest.nodemap:
1918 self.ui.warn("manifest %s has unknown parent %s\n" %
1918 self.ui.warn("manifest %s has unknown parent %s\n" %
1919 (short(n), short(p)))
1919 (short(n), short(p)))
1920 errors += 1
1920 errors += 1
1921
1921
1922 try:
1922 try:
1923 delta = mdiff.patchtext(self.manifest.delta(n))
1923 delta = mdiff.patchtext(self.manifest.delta(n))
1924 except KeyboardInterrupt:
1924 except KeyboardInterrupt:
1925 self.ui.warn("aborted")
1925 self.ui.warn("aborted")
1926 sys.exit(0)
1926 sys.exit(0)
1927 except Exception, inst:
1927 except Exception, inst:
1928 self.ui.warn("unpacking manifest %s: %s\n"
1928 self.ui.warn("unpacking manifest %s: %s\n"
1929 % (short(n), inst))
1929 % (short(n), inst))
1930 errors += 1
1930 errors += 1
1931
1931
1932 ff = [ l.split('\0') for l in delta.splitlines() ]
1932 ff = [ l.split('\0') for l in delta.splitlines() ]
1933 for f, fn in ff:
1933 for f, fn in ff:
1934 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1934 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1935
1935
1936 self.ui.status("crosschecking files in changesets and manifests\n")
1936 self.ui.status("crosschecking files in changesets and manifests\n")
1937 for f in filenodes:
1937 for f in filenodes:
1938 if f not in filelinkrevs:
1938 if f not in filelinkrevs:
1939 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1939 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1940 errors += 1
1940 errors += 1
1941
1941
1942 for f in filelinkrevs:
1942 for f in filelinkrevs:
1943 if f not in filenodes:
1943 if f not in filenodes:
1944 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1944 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1945 errors += 1
1945 errors += 1
1946
1946
1947 self.ui.status("checking files\n")
1947 self.ui.status("checking files\n")
1948 ff = filenodes.keys()
1948 ff = filenodes.keys()
1949 ff.sort()
1949 ff.sort()
1950 for f in ff:
1950 for f in ff:
1951 if f == "/dev/null": continue
1951 if f == "/dev/null": continue
1952 files += 1
1952 files += 1
1953 fl = self.file(f)
1953 fl = self.file(f)
1954 nodes = { nullid: 1 }
1954 nodes = { nullid: 1 }
1955 seen = {}
1955 seen = {}
1956 for i in range(fl.count()):
1956 for i in range(fl.count()):
1957 revisions += 1
1957 revisions += 1
1958 n = fl.node(i)
1958 n = fl.node(i)
1959
1959
1960 if n in seen:
1960 if n in seen:
1961 self.ui.warn("%s: duplicate revision %d\n" % (f, i))
1961 self.ui.warn("%s: duplicate revision %d\n" % (f, i))
1962 errors += 1
1962 errors += 1
1963
1963
1964 if n not in filenodes[f]:
1964 if n not in filenodes[f]:
1965 self.ui.warn("%s: %d:%s not in manifests\n"
1965 self.ui.warn("%s: %d:%s not in manifests\n"
1966 % (f, i, short(n)))
1966 % (f, i, short(n)))
1967 errors += 1
1967 errors += 1
1968 else:
1968 else:
1969 del filenodes[f][n]
1969 del filenodes[f][n]
1970
1970
1971 flr = fl.linkrev(n)
1971 flr = fl.linkrev(n)
1972 if flr not in filelinkrevs[f]:
1972 if flr not in filelinkrevs[f]:
1973 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1973 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1974 % (f, short(n), fl.linkrev(n)))
1974 % (f, short(n), fl.linkrev(n)))
1975 errors += 1
1975 errors += 1
1976 else:
1976 else:
1977 filelinkrevs[f].remove(flr)
1977 filelinkrevs[f].remove(flr)
1978
1978
1979 # verify contents
1979 # verify contents
1980 try:
1980 try:
1981 t = fl.read(n)
1981 t = fl.read(n)
1982 except Exception, inst:
1982 except Exception, inst:
1983 self.ui.warn("unpacking file %s %s: %s\n"
1983 self.ui.warn("unpacking file %s %s: %s\n"
1984 % (f, short(n), inst))
1984 % (f, short(n), inst))
1985 errors += 1
1985 errors += 1
1986
1986
1987 # verify parents
1987 # verify parents
1988 (p1, p2) = fl.parents(n)
1988 (p1, p2) = fl.parents(n)
1989 if p1 not in nodes:
1989 if p1 not in nodes:
1990 self.ui.warn("file %s:%s unknown parent 1 %s" %
1990 self.ui.warn("file %s:%s unknown parent 1 %s" %
1991 (f, short(n), short(p1)))
1991 (f, short(n), short(p1)))
1992 errors += 1
1992 errors += 1
1993 if p2 not in nodes:
1993 if p2 not in nodes:
1994 self.ui.warn("file %s:%s unknown parent 2 %s" %
1994 self.ui.warn("file %s:%s unknown parent 2 %s" %
1995 (f, short(n), short(p1)))
1995 (f, short(n), short(p1)))
1996 errors += 1
1996 errors += 1
1997 nodes[n] = 1
1997 nodes[n] = 1
1998
1998
1999 # cross-check
1999 # cross-check
2000 for node in filenodes[f]:
2000 for node in filenodes[f]:
2001 self.ui.warn("node %s in manifests not in %s\n"
2001 self.ui.warn("node %s in manifests not in %s\n"
2002 % (hex(node), f))
2002 % (hex(node), f))
2003 errors += 1
2003 errors += 1
2004
2004
2005 self.ui.status("%d files, %d changesets, %d total revisions\n" %
2005 self.ui.status("%d files, %d changesets, %d total revisions\n" %
2006 (files, changesets, revisions))
2006 (files, changesets, revisions))
2007
2007
2008 if errors:
2008 if errors:
2009 self.ui.warn("%d integrity errors encountered!\n" % errors)
2009 self.ui.warn("%d integrity errors encountered!\n" % errors)
2010 return 1
2010 return 1
2011
2011
2012 class remoterepository:
2012 class remoterepository:
2013 def local(self):
2013 def local(self):
2014 return False
2014 return False
2015
2015
2016 class httprepository(remoterepository):
2016 class httprepository(remoterepository):
2017 def __init__(self, ui, path):
2017 def __init__(self, ui, path):
2018 # fix missing / after hostname
2018 # fix missing / after hostname
2019 s = urlparse.urlsplit(path)
2019 s = urlparse.urlsplit(path)
2020 partial = s[2]
2020 partial = s[2]
2021 if not partial: partial = "/"
2021 if not partial: partial = "/"
2022 self.url = urlparse.urlunsplit((s[0], s[1], partial, '', ''))
2022 self.url = urlparse.urlunsplit((s[0], s[1], partial, '', ''))
2023 self.ui = ui
2023 self.ui = ui
2024 no_list = [ "localhost", "127.0.0.1" ]
2024 no_list = [ "localhost", "127.0.0.1" ]
2025 host = ui.config("http_proxy", "host")
2025 host = ui.config("http_proxy", "host")
2026 if host is None:
2026 if host is None:
2027 host = os.environ.get("http_proxy")
2027 host = os.environ.get("http_proxy")
2028 if host and host.startswith('http://'):
2028 if host and host.startswith('http://'):
2029 host = host[7:]
2029 host = host[7:]
2030 user = ui.config("http_proxy", "user")
2030 user = ui.config("http_proxy", "user")
2031 passwd = ui.config("http_proxy", "passwd")
2031 passwd = ui.config("http_proxy", "passwd")
2032 no = ui.config("http_proxy", "no")
2032 no = ui.config("http_proxy", "no")
2033 if no is None:
2033 if no is None:
2034 no = os.environ.get("no_proxy")
2034 no = os.environ.get("no_proxy")
2035 if no:
2035 if no:
2036 no_list = no_list + no.split(",")
2036 no_list = no_list + no.split(",")
2037
2037
2038 no_proxy = 0
2038 no_proxy = 0
2039 for h in no_list:
2039 for h in no_list:
2040 if (path.startswith("http://" + h + "/") or
2040 if (path.startswith("http://" + h + "/") or
2041 path.startswith("http://" + h + ":") or
2041 path.startswith("http://" + h + ":") or
2042 path == "http://" + h):
2042 path == "http://" + h):
2043 no_proxy = 1
2043 no_proxy = 1
2044
2044
2045 # Note: urllib2 takes proxy values from the environment and those will
2045 # Note: urllib2 takes proxy values from the environment and those will
2046 # take precedence
2046 # take precedence
2047 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
2047 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
2048 try:
2048 try:
2049 if os.environ.has_key(env):
2049 if os.environ.has_key(env):
2050 del os.environ[env]
2050 del os.environ[env]
2051 except OSError:
2051 except OSError:
2052 pass
2052 pass
2053
2053
2054 proxy_handler = urllib2.BaseHandler()
2054 proxy_handler = urllib2.BaseHandler()
2055 if host and not no_proxy:
2055 if host and not no_proxy:
2056 proxy_handler = urllib2.ProxyHandler({"http" : "http://" + host})
2056 proxy_handler = urllib2.ProxyHandler({"http" : "http://" + host})
2057
2057
2058 authinfo = None
2058 authinfo = None
2059 if user and passwd:
2059 if user and passwd:
2060 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
2060 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
2061 passmgr.add_password(None, host, user, passwd)
2061 passmgr.add_password(None, host, user, passwd)
2062 authinfo = urllib2.ProxyBasicAuthHandler(passmgr)
2062 authinfo = urllib2.ProxyBasicAuthHandler(passmgr)
2063
2063
2064 opener = urllib2.build_opener(proxy_handler, authinfo)
2064 opener = urllib2.build_opener(proxy_handler, authinfo)
2065 urllib2.install_opener(opener)
2065 urllib2.install_opener(opener)
2066
2066
2067 def dev(self):
2067 def dev(self):
2068 return -1
2068 return -1
2069
2069
2070 def do_cmd(self, cmd, **args):
2070 def do_cmd(self, cmd, **args):
2071 self.ui.debug("sending %s command\n" % cmd)
2071 self.ui.debug("sending %s command\n" % cmd)
2072 q = {"cmd": cmd}
2072 q = {"cmd": cmd}
2073 q.update(args)
2073 q.update(args)
2074 qs = urllib.urlencode(q)
2074 qs = urllib.urlencode(q)
2075 cu = "%s?%s" % (self.url, qs)
2075 cu = "%s?%s" % (self.url, qs)
2076 resp = urllib2.urlopen(cu)
2076 resp = urllib2.urlopen(cu)
2077 proto = resp.headers['content-type']
2077 proto = resp.headers['content-type']
2078
2078
2079 # accept old "text/plain" and "application/hg-changegroup" for now
2079 # accept old "text/plain" and "application/hg-changegroup" for now
2080 if not proto.startswith('application/mercurial') and \
2080 if not proto.startswith('application/mercurial') and \
2081 not proto.startswith('text/plain') and \
2081 not proto.startswith('text/plain') and \
2082 not proto.startswith('application/hg-changegroup'):
2082 not proto.startswith('application/hg-changegroup'):
2083 raise RepoError("'%s' does not appear to be an hg repository"
2083 raise RepoError("'%s' does not appear to be an hg repository"
2084 % self.url)
2084 % self.url)
2085
2085
2086 if proto.startswith('application/mercurial'):
2086 if proto.startswith('application/mercurial'):
2087 version = proto[22:]
2087 version = proto[22:]
2088 if float(version) > 0.1:
2088 if float(version) > 0.1:
2089 raise RepoError("'%s' uses newer protocol %s" %
2089 raise RepoError("'%s' uses newer protocol %s" %
2090 (self.url, version))
2090 (self.url, version))
2091
2091
2092 return resp
2092 return resp
2093
2093
2094 def heads(self):
2094 def heads(self):
2095 d = self.do_cmd("heads").read()
2095 d = self.do_cmd("heads").read()
2096 try:
2096 try:
2097 return map(bin, d[:-1].split(" "))
2097 return map(bin, d[:-1].split(" "))
2098 except:
2098 except:
2099 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
2099 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
2100 raise
2100 raise
2101
2101
2102 def branches(self, nodes):
2102 def branches(self, nodes):
2103 n = " ".join(map(hex, nodes))
2103 n = " ".join(map(hex, nodes))
2104 d = self.do_cmd("branches", nodes=n).read()
2104 d = self.do_cmd("branches", nodes=n).read()
2105 try:
2105 try:
2106 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
2106 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
2107 return br
2107 return br
2108 except:
2108 except:
2109 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
2109 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
2110 raise
2110 raise
2111
2111
2112 def between(self, pairs):
2112 def between(self, pairs):
2113 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
2113 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
2114 d = self.do_cmd("between", pairs=n).read()
2114 d = self.do_cmd("between", pairs=n).read()
2115 try:
2115 try:
2116 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
2116 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
2117 return p
2117 return p
2118 except:
2118 except:
2119 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
2119 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
2120 raise
2120 raise
2121
2121
2122 def changegroup(self, nodes):
2122 def changegroup(self, nodes):
2123 n = " ".join(map(hex, nodes))
2123 n = " ".join(map(hex, nodes))
2124 f = self.do_cmd("changegroup", roots=n)
2124 f = self.do_cmd("changegroup", roots=n)
2125 bytes = 0
2125 bytes = 0
2126
2126
2127 class zread:
2127 class zread:
2128 def __init__(self, f):
2128 def __init__(self, f):
2129 self.zd = zlib.decompressobj()
2129 self.zd = zlib.decompressobj()
2130 self.f = f
2130 self.f = f
2131 self.buf = ""
2131 self.buf = ""
2132 def read(self, l):
2132 def read(self, l):
2133 while l > len(self.buf):
2133 while l > len(self.buf):
2134 r = self.f.read(4096)
2134 r = self.f.read(4096)
2135 if r:
2135 if r:
2136 self.buf += self.zd.decompress(r)
2136 self.buf += self.zd.decompress(r)
2137 else:
2137 else:
2138 self.buf += self.zd.flush()
2138 self.buf += self.zd.flush()
2139 break
2139 break
2140 d, self.buf = self.buf[:l], self.buf[l:]
2140 d, self.buf = self.buf[:l], self.buf[l:]
2141 return d
2141 return d
2142
2142
2143 return zread(f)
2143 return zread(f)
2144
2144
2145 class remotelock:
2145 class remotelock:
2146 def __init__(self, repo):
2146 def __init__(self, repo):
2147 self.repo = repo
2147 self.repo = repo
2148 def release(self):
2148 def release(self):
2149 self.repo.unlock()
2149 self.repo.unlock()
2150 self.repo = None
2150 self.repo = None
2151 def __del__(self):
2151 def __del__(self):
2152 if self.repo:
2152 if self.repo:
2153 self.release()
2153 self.release()
2154
2154
2155 class sshrepository(remoterepository):
2155 class sshrepository(remoterepository):
2156 def __init__(self, ui, path):
2156 def __init__(self, ui, path):
2157 self.url = path
2157 self.url = path
2158 self.ui = ui
2158 self.ui = ui
2159
2159
2160 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))', path)
2160 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))', path)
2161 if not m:
2161 if not m:
2162 raise RepoError("couldn't parse destination %s" % path)
2162 raise RepoError("couldn't parse destination %s" % path)
2163
2163
2164 self.user = m.group(2)
2164 self.user = m.group(2)
2165 self.host = m.group(3)
2165 self.host = m.group(3)
2166 self.port = m.group(5)
2166 self.port = m.group(5)
2167 self.path = m.group(7)
2167 self.path = m.group(7)
2168
2168
2169 args = self.user and ("%s@%s" % (self.user, self.host)) or self.host
2169 args = self.user and ("%s@%s" % (self.user, self.host)) or self.host
2170 args = self.port and ("%s -p %s") % (args, self.port) or args
2170 args = self.port and ("%s -p %s") % (args, self.port) or args
2171 path = self.path or ""
2171 path = self.path or ""
2172
2172
2173 if not path:
2173 if not path:
2174 raise RepoError("no remote repository path specified")
2174 raise RepoError("no remote repository path specified")
2175
2175
2176 sshcmd = self.ui.config("ui", "ssh", "ssh")
2176 sshcmd = self.ui.config("ui", "ssh", "ssh")
2177 remotecmd = self.ui.config("ui", "remotecmd", "hg")
2177 remotecmd = self.ui.config("ui", "remotecmd", "hg")
2178 cmd = "%s %s '%s -R %s serve --stdio'"
2178 cmd = "%s %s '%s -R %s serve --stdio'"
2179 cmd = cmd % (sshcmd, args, remotecmd, path)
2179 cmd = cmd % (sshcmd, args, remotecmd, path)
2180
2180
2181 self.pipeo, self.pipei, self.pipee = os.popen3(cmd)
2181 self.pipeo, self.pipei, self.pipee = os.popen3(cmd)
2182
2182
2183 def readerr(self):
2183 def readerr(self):
2184 while 1:
2184 while 1:
2185 r,w,x = select.select([self.pipee], [], [], 0)
2185 r,w,x = select.select([self.pipee], [], [], 0)
2186 if not r: break
2186 if not r: break
2187 l = self.pipee.readline()
2187 l = self.pipee.readline()
2188 if not l: break
2188 if not l: break
2189 self.ui.status("remote: ", l)
2189 self.ui.status("remote: ", l)
2190
2190
2191 def __del__(self):
2191 def __del__(self):
2192 try:
2192 try:
2193 self.pipeo.close()
2193 self.pipeo.close()
2194 self.pipei.close()
2194 self.pipei.close()
2195 for l in self.pipee:
2195 for l in self.pipee:
2196 self.ui.status("remote: ", l)
2196 self.ui.status("remote: ", l)
2197 self.pipee.close()
2197 self.pipee.close()
2198 except:
2198 except:
2199 pass
2199 pass
2200
2200
2201 def dev(self):
2201 def dev(self):
2202 return -1
2202 return -1
2203
2203
2204 def do_cmd(self, cmd, **args):
2204 def do_cmd(self, cmd, **args):
2205 self.ui.debug("sending %s command\n" % cmd)
2205 self.ui.debug("sending %s command\n" % cmd)
2206 self.pipeo.write("%s\n" % cmd)
2206 self.pipeo.write("%s\n" % cmd)
2207 for k, v in args.items():
2207 for k, v in args.items():
2208 self.pipeo.write("%s %d\n" % (k, len(v)))
2208 self.pipeo.write("%s %d\n" % (k, len(v)))
2209 self.pipeo.write(v)
2209 self.pipeo.write(v)
2210 self.pipeo.flush()
2210 self.pipeo.flush()
2211
2211
2212 return self.pipei
2212 return self.pipei
2213
2213
2214 def call(self, cmd, **args):
2214 def call(self, cmd, **args):
2215 r = self.do_cmd(cmd, **args)
2215 r = self.do_cmd(cmd, **args)
2216 l = r.readline()
2216 l = r.readline()
2217 self.readerr()
2217 self.readerr()
2218 try:
2218 try:
2219 l = int(l)
2219 l = int(l)
2220 except:
2220 except:
2221 raise RepoError("unexpected response '%s'" % l)
2221 raise RepoError("unexpected response '%s'" % l)
2222 return r.read(l)
2222 return r.read(l)
2223
2223
2224 def lock(self):
2224 def lock(self):
2225 self.call("lock")
2225 self.call("lock")
2226 return remotelock(self)
2226 return remotelock(self)
2227
2227
2228 def unlock(self):
2228 def unlock(self):
2229 self.call("unlock")
2229 self.call("unlock")
2230
2230
2231 def heads(self):
2231 def heads(self):
2232 d = self.call("heads")
2232 d = self.call("heads")
2233 try:
2233 try:
2234 return map(bin, d[:-1].split(" "))
2234 return map(bin, d[:-1].split(" "))
2235 except:
2235 except:
2236 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
2236 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
2237
2237
2238 def branches(self, nodes):
2238 def branches(self, nodes):
2239 n = " ".join(map(hex, nodes))
2239 n = " ".join(map(hex, nodes))
2240 d = self.call("branches", nodes=n)
2240 d = self.call("branches", nodes=n)
2241 try:
2241 try:
2242 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
2242 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
2243 return br
2243 return br
2244 except:
2244 except:
2245 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
2245 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
2246
2246
2247 def between(self, pairs):
2247 def between(self, pairs):
2248 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
2248 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
2249 d = self.call("between", pairs=n)
2249 d = self.call("between", pairs=n)
2250 try:
2250 try:
2251 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
2251 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
2252 return p
2252 return p
2253 except:
2253 except:
2254 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
2254 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
2255
2255
2256 def changegroup(self, nodes):
2256 def changegroup(self, nodes):
2257 n = " ".join(map(hex, nodes))
2257 n = " ".join(map(hex, nodes))
2258 f = self.do_cmd("changegroup", roots=n)
2258 f = self.do_cmd("changegroup", roots=n)
2259 return self.pipei
2259 return self.pipei
2260
2260
2261 def addchangegroup(self, cg):
2261 def addchangegroup(self, cg):
2262 d = self.call("addchangegroup")
2262 d = self.call("addchangegroup")
2263 if d:
2263 if d:
2264 raise RepoError("push refused: %s", d)
2264 raise RepoError("push refused: %s", d)
2265
2265
2266 while 1:
2266 while 1:
2267 d = cg.read(4096)
2267 d = cg.read(4096)
2268 if not d: break
2268 if not d: break
2269 self.pipeo.write(d)
2269 self.pipeo.write(d)
2270 self.readerr()
2270 self.readerr()
2271
2271
2272 self.pipeo.flush()
2272 self.pipeo.flush()
2273
2273
2274 self.readerr()
2274 self.readerr()
2275 l = int(self.pipei.readline())
2275 l = int(self.pipei.readline())
2276 return self.pipei.read(l) != ""
2276 return self.pipei.read(l) != ""
2277
2277
2278 class httpsrepository(httprepository):
2278 class httpsrepository(httprepository):
2279 pass
2279 pass
2280
2280
2281 def repository(ui, path=None, create=0):
2281 def repository(ui, path=None, create=0):
2282 if path:
2282 if path:
2283 if path.startswith("http://"):
2283 if path.startswith("http://"):
2284 return httprepository(ui, path)
2284 return httprepository(ui, path)
2285 if path.startswith("https://"):
2285 if path.startswith("https://"):
2286 return httpsrepository(ui, path)
2286 return httpsrepository(ui, path)
2287 if path.startswith("hg://"):
2287 if path.startswith("hg://"):
2288 return httprepository(ui, path.replace("hg://", "http://"))
2288 return httprepository(ui, path.replace("hg://", "http://"))
2289 if path.startswith("old-http://"):
2289 if path.startswith("old-http://"):
2290 return localrepository(ui, path.replace("old-http://", "http://"))
2290 return localrepository(ui, path.replace("old-http://", "http://"))
2291 if path.startswith("ssh://"):
2291 if path.startswith("ssh://"):
2292 return sshrepository(ui, path)
2292 return sshrepository(ui, path)
2293
2293
2294 return localrepository(ui, path, create)
2294 return localrepository(ui, path, create)
@@ -1,892 +1,892
1 # hgweb.py - web interface to a mercurial repository
1 # hgweb.py - web interface to a mercurial repository
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os, cgi, time, re, difflib, socket, sys, zlib
9 import os, cgi, time, re, difflib, socket, sys, zlib
10 from mercurial.hg import *
10 from mercurial.hg import *
11 from mercurial.ui import *
11 from mercurial.ui import *
12
12
13 def templatepath():
13 def templatepath():
14 for f in "templates", "../templates":
14 for f in "templates", "../templates":
15 p = os.path.join(os.path.dirname(__file__), f)
15 p = os.path.join(os.path.dirname(__file__), f)
16 if os.path.isdir(p): return p
16 if os.path.isdir(p): return p
17
17
18 def age(t):
18 def age(t):
19 def plural(t, c):
19 def plural(t, c):
20 if c == 1: return t
20 if c == 1: return t
21 return t + "s"
21 return t + "s"
22 def fmt(t, c):
22 def fmt(t, c):
23 return "%d %s" % (c, plural(t, c))
23 return "%d %s" % (c, plural(t, c))
24
24
25 now = time.time()
25 now = time.time()
26 delta = max(1, int(now - t))
26 delta = max(1, int(now - t))
27
27
28 scales = [["second", 1],
28 scales = [["second", 1],
29 ["minute", 60],
29 ["minute", 60],
30 ["hour", 3600],
30 ["hour", 3600],
31 ["day", 3600 * 24],
31 ["day", 3600 * 24],
32 ["week", 3600 * 24 * 7],
32 ["week", 3600 * 24 * 7],
33 ["month", 3600 * 24 * 30],
33 ["month", 3600 * 24 * 30],
34 ["year", 3600 * 24 * 365]]
34 ["year", 3600 * 24 * 365]]
35
35
36 scales.reverse()
36 scales.reverse()
37
37
38 for t, s in scales:
38 for t, s in scales:
39 n = delta / s
39 n = delta / s
40 if n >= 2 or s == 1: return fmt(t, n)
40 if n >= 2 or s == 1: return fmt(t, n)
41
41
42 def nl2br(text):
42 def nl2br(text):
43 return text.replace('\n', '<br/>\n')
43 return text.replace('\n', '<br/>\n')
44
44
45 def obfuscate(text):
45 def obfuscate(text):
46 return ''.join([ '&#%d;' % ord(c) for c in text ])
46 return ''.join([ '&#%d;' % ord(c) for c in text ])
47
47
48 def up(p):
48 def up(p):
49 if p[0] != "/": p = "/" + p
49 if p[0] != "/": p = "/" + p
50 if p[-1] == "/": p = p[:-1]
50 if p[-1] == "/": p = p[:-1]
51 up = os.path.dirname(p)
51 up = os.path.dirname(p)
52 if up == "/":
52 if up == "/":
53 return "/"
53 return "/"
54 return up + "/"
54 return up + "/"
55
55
56 def httphdr(type):
56 def httphdr(type):
57 sys.stdout.write('Content-type: %s\n\n' % type)
57 sys.stdout.write('Content-type: %s\n\n' % type)
58
58
59 def write(*things):
59 def write(*things):
60 for thing in things:
60 for thing in things:
61 if hasattr(thing, "__iter__"):
61 if hasattr(thing, "__iter__"):
62 for part in thing:
62 for part in thing:
63 write(part)
63 write(part)
64 else:
64 else:
65 sys.stdout.write(str(thing))
65 sys.stdout.write(str(thing))
66
66
67 class templater:
67 class templater:
68 def __init__(self, mapfile, filters = {}, defaults = {}):
68 def __init__(self, mapfile, filters={}, defaults={}):
69 self.cache = {}
69 self.cache = {}
70 self.map = {}
70 self.map = {}
71 self.base = os.path.dirname(mapfile)
71 self.base = os.path.dirname(mapfile)
72 self.filters = filters
72 self.filters = filters
73 self.defaults = defaults
73 self.defaults = defaults
74
74
75 for l in file(mapfile):
75 for l in file(mapfile):
76 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
76 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
77 if m:
77 if m:
78 self.cache[m.group(1)] = m.group(2)
78 self.cache[m.group(1)] = m.group(2)
79 else:
79 else:
80 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
80 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
81 if m:
81 if m:
82 self.map[m.group(1)] = os.path.join(self.base, m.group(2))
82 self.map[m.group(1)] = os.path.join(self.base, m.group(2))
83 else:
83 else:
84 raise "unknown map entry '%s'" % l
84 raise "unknown map entry '%s'" % l
85
85
86 def __call__(self, t, **map):
86 def __call__(self, t, **map):
87 m = self.defaults.copy()
87 m = self.defaults.copy()
88 m.update(map)
88 m.update(map)
89 try:
89 try:
90 tmpl = self.cache[t]
90 tmpl = self.cache[t]
91 except KeyError:
91 except KeyError:
92 tmpl = self.cache[t] = file(self.map[t]).read()
92 tmpl = self.cache[t] = file(self.map[t]).read()
93 return self.template(tmpl, self.filters, **m)
93 return self.template(tmpl, self.filters, **m)
94
94
95 def template(self, tmpl, filters = {}, **map):
95 def template(self, tmpl, filters={}, **map):
96 while tmpl:
96 while tmpl:
97 m = re.search(r"#([a-zA-Z0-9]+)((%[a-zA-Z0-9]+)*)((\|[a-zA-Z0-9]+)*)#", tmpl)
97 m = re.search(r"#([a-zA-Z0-9]+)((%[a-zA-Z0-9]+)*)((\|[a-zA-Z0-9]+)*)#", tmpl)
98 if m:
98 if m:
99 yield tmpl[:m.start(0)]
99 yield tmpl[:m.start(0)]
100 v = map.get(m.group(1), "")
100 v = map.get(m.group(1), "")
101 v = callable(v) and v(**map) or v
101 v = callable(v) and v(**map) or v
102
102
103 format = m.group(2)
103 format = m.group(2)
104 fl = m.group(4)
104 fl = m.group(4)
105
105
106 if format:
106 if format:
107 q = v.__iter__
107 q = v.__iter__
108 for i in q():
108 for i in q():
109 lm = map.copy()
109 lm = map.copy()
110 lm.update(i)
110 lm.update(i)
111 yield self(format[1:], **lm)
111 yield self(format[1:], **lm)
112
112
113 v = ""
113 v = ""
114
114
115 elif fl:
115 elif fl:
116 for f in fl.split("|")[1:]:
116 for f in fl.split("|")[1:]:
117 v = filters[f](v)
117 v = filters[f](v)
118
118
119 yield v
119 yield v
120 tmpl = tmpl[m.end(0):]
120 tmpl = tmpl[m.end(0):]
121 else:
121 else:
122 yield tmpl
122 yield tmpl
123 return
123 return
124
124
125 def rfc822date(x):
125 def rfc822date(x):
126 return time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime(x))
126 return time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime(x))
127
127
128 common_filters = {
128 common_filters = {
129 "escape": cgi.escape,
129 "escape": cgi.escape,
130 "age": age,
130 "age": age,
131 "date": (lambda x: time.asctime(time.gmtime(x))),
131 "date": (lambda x: time.asctime(time.gmtime(x))),
132 "addbreaks": nl2br,
132 "addbreaks": nl2br,
133 "obfuscate": obfuscate,
133 "obfuscate": obfuscate,
134 "short": (lambda x: x[:12]),
134 "short": (lambda x: x[:12]),
135 "firstline": (lambda x: x.splitlines(1)[0]),
135 "firstline": (lambda x: x.splitlines(1)[0]),
136 "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"),
136 "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"),
137 "rfc822date": rfc822date,
137 "rfc822date": rfc822date,
138 }
138 }
139
139
140 class hgweb:
140 class hgweb:
141 def __init__(self, repo, name=None):
141 def __init__(self, repo, name=None):
142 if type(repo) == type(""):
142 if type(repo) == type(""):
143 self.repo = repository(ui(), repo)
143 self.repo = repository(ui(), repo)
144 else:
144 else:
145 self.repo = repo
145 self.repo = repo
146
146
147 self.mtime = -1
147 self.mtime = -1
148 self.reponame = name or self.repo.ui.config("web", "name",
148 self.reponame = name or self.repo.ui.config("web", "name",
149 self.repo.root)
149 self.repo.root)
150
150
151 def refresh(self):
151 def refresh(self):
152 s = os.stat(os.path.join(self.repo.root, ".hg", "00changelog.i"))
152 s = os.stat(os.path.join(self.repo.root, ".hg", "00changelog.i"))
153 if s.st_mtime != self.mtime:
153 if s.st_mtime != self.mtime:
154 self.mtime = s.st_mtime
154 self.mtime = s.st_mtime
155 self.repo = repository(self.repo.ui, self.repo.root)
155 self.repo = repository(self.repo.ui, self.repo.root)
156 self.maxchanges = self.repo.ui.config("web", "maxchanges", 10)
156 self.maxchanges = self.repo.ui.config("web", "maxchanges", 10)
157 self.maxfiles = self.repo.ui.config("web", "maxchanges", 10)
157 self.maxfiles = self.repo.ui.config("web", "maxchanges", 10)
158 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
158 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
159
159
160 def date(self, cs):
160 def date(self, cs):
161 return time.asctime(time.gmtime(float(cs[2].split(' ')[0])))
161 return time.asctime(time.gmtime(float(cs[2].split(' ')[0])))
162
162
163 def listfiles(self, files, mf):
163 def listfiles(self, files, mf):
164 for f in files[:self.maxfiles]:
164 for f in files[:self.maxfiles]:
165 yield self.t("filenodelink", node = hex(mf[f]), file = f)
165 yield self.t("filenodelink", node=hex(mf[f]), file=f)
166 if len(files) > self.maxfiles:
166 if len(files) > self.maxfiles:
167 yield self.t("fileellipses")
167 yield self.t("fileellipses")
168
168
169 def listfilediffs(self, files, changeset):
169 def listfilediffs(self, files, changeset):
170 for f in files[:self.maxfiles]:
170 for f in files[:self.maxfiles]:
171 yield self.t("filedifflink", node = hex(changeset), file = f)
171 yield self.t("filedifflink", node=hex(changeset), file=f)
172 if len(files) > self.maxfiles:
172 if len(files) > self.maxfiles:
173 yield self.t("fileellipses")
173 yield self.t("fileellipses")
174
174
175 def parents(self, t1, nodes=[], rev=None,**args):
175 def parents(self, t1, nodes=[], rev=None,**args):
176 if not rev: rev = lambda x: ""
176 if not rev: rev = lambda x: ""
177 for node in nodes:
177 for node in nodes:
178 if node != nullid:
178 if node != nullid:
179 yield self.t(t1, node = hex(node), rev = rev(node), **args)
179 yield self.t(t1, node=hex(node), rev=rev(node), **args)
180
180
181 def showtag(self, t1, node=nullid, **args):
181 def showtag(self, t1, node=nullid, **args):
182 for t in self.repo.nodetags(node):
182 for t in self.repo.nodetags(node):
183 yield self.t(t1, tag = t, **args)
183 yield self.t(t1, tag=t, **args)
184
184
185 def diff(self, node1, node2, files):
185 def diff(self, node1, node2, files):
186 def filterfiles(list, files):
186 def filterfiles(list, files):
187 l = [ x for x in list if x in files ]
187 l = [ x for x in list if x in files ]
188
188
189 for f in files:
189 for f in files:
190 if f[-1] != os.sep: f += os.sep
190 if f[-1] != os.sep: f += os.sep
191 l += [ x for x in list if x.startswith(f) ]
191 l += [ x for x in list if x.startswith(f) ]
192 return l
192 return l
193
193
194 parity = [0]
194 parity = [0]
195 def diffblock(diff, f, fn):
195 def diffblock(diff, f, fn):
196 yield self.t("diffblock",
196 yield self.t("diffblock",
197 lines = prettyprintlines(diff),
197 lines = prettyprintlines(diff),
198 parity = parity[0],
198 parity = parity[0],
199 file = f,
199 file = f,
200 filenode = hex(fn or nullid))
200 filenode = hex(fn or nullid))
201 parity[0] = 1 - parity[0]
201 parity[0] = 1 - parity[0]
202
202
203 def prettyprintlines(diff):
203 def prettyprintlines(diff):
204 for l in diff.splitlines(1):
204 for l in diff.splitlines(1):
205 if l.startswith('+'):
205 if l.startswith('+'):
206 yield self.t("difflineplus", line = l)
206 yield self.t("difflineplus", line=l)
207 elif l.startswith('-'):
207 elif l.startswith('-'):
208 yield self.t("difflineminus", line = l)
208 yield self.t("difflineminus", line=l)
209 elif l.startswith('@'):
209 elif l.startswith('@'):
210 yield self.t("difflineat", line = l)
210 yield self.t("difflineat", line=l)
211 else:
211 else:
212 yield self.t("diffline", line = l)
212 yield self.t("diffline", line=l)
213
213
214 r = self.repo
214 r = self.repo
215 cl = r.changelog
215 cl = r.changelog
216 mf = r.manifest
216 mf = r.manifest
217 change1 = cl.read(node1)
217 change1 = cl.read(node1)
218 change2 = cl.read(node2)
218 change2 = cl.read(node2)
219 mmap1 = mf.read(change1[0])
219 mmap1 = mf.read(change1[0])
220 mmap2 = mf.read(change2[0])
220 mmap2 = mf.read(change2[0])
221 date1 = self.date(change1)
221 date1 = self.date(change1)
222 date2 = self.date(change2)
222 date2 = self.date(change2)
223
223
224 c, a, d, u = r.changes(node1, node2)
224 c, a, d, u = r.changes(node1, node2)
225 if files:
225 if files:
226 c, a, d = map(lambda x: filterfiles(x, files), (c, a, d))
226 c, a, d = map(lambda x: filterfiles(x, files), (c, a, d))
227
227
228 for f in c:
228 for f in c:
229 to = r.file(f).read(mmap1[f])
229 to = r.file(f).read(mmap1[f])
230 tn = r.file(f).read(mmap2[f])
230 tn = r.file(f).read(mmap2[f])
231 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
231 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
232 for f in a:
232 for f in a:
233 to = None
233 to = None
234 tn = r.file(f).read(mmap2[f])
234 tn = r.file(f).read(mmap2[f])
235 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
235 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
236 for f in d:
236 for f in d:
237 to = r.file(f).read(mmap1[f])
237 to = r.file(f).read(mmap1[f])
238 tn = None
238 tn = None
239 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
239 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
240
240
241 def changelog(self, pos):
241 def changelog(self, pos):
242 def changenav(**map):
242 def changenav(**map):
243 def seq(factor = 1):
243 def seq(factor=1):
244 yield 1 * factor
244 yield 1 * factor
245 yield 3 * factor
245 yield 3 * factor
246 #yield 5 * factor
246 #yield 5 * factor
247 for f in seq(factor * 10):
247 for f in seq(factor * 10):
248 yield f
248 yield f
249
249
250 l = []
250 l = []
251 for f in seq():
251 for f in seq():
252 if f < self.maxchanges / 2: continue
252 if f < self.maxchanges / 2: continue
253 if f > count: break
253 if f > count: break
254 r = "%d" % f
254 r = "%d" % f
255 if pos + f < count: l.append(("+" + r, pos + f))
255 if pos + f < count: l.append(("+" + r, pos + f))
256 if pos - f >= 0: l.insert(0, ("-" + r, pos - f))
256 if pos - f >= 0: l.insert(0, ("-" + r, pos - f))
257
257
258 yield {"rev": 0, "label": "(0)"}
258 yield {"rev": 0, "label": "(0)"}
259
259
260 for label, rev in l:
260 for label, rev in l:
261 yield {"label": label, "rev": rev}
261 yield {"label": label, "rev": rev}
262
262
263 yield {"label": "tip", "rev": ""}
263 yield {"label": "tip", "rev": ""}
264
264
265 def changelist(**map):
265 def changelist(**map):
266 parity = (start - end) & 1
266 parity = (start - end) & 1
267 cl = self.repo.changelog
267 cl = self.repo.changelog
268 l = [] # build a list in forward order for efficiency
268 l = [] # build a list in forward order for efficiency
269 for i in range(start, end):
269 for i in range(start, end):
270 n = cl.node(i)
270 n = cl.node(i)
271 changes = cl.read(n)
271 changes = cl.read(n)
272 hn = hex(n)
272 hn = hex(n)
273 t = float(changes[2].split(' ')[0])
273 t = float(changes[2].split(' ')[0])
274
274
275 l.insert(0, {
275 l.insert(0, {
276 "parity": parity,
276 "parity": parity,
277 "author": changes[1],
277 "author": changes[1],
278 "parent": self.parents("changelogparent",
278 "parent": self.parents("changelogparent",
279 cl.parents(n), cl.rev),
279 cl.parents(n), cl.rev),
280 "changelogtag": self.showtag("changelogtag",n),
280 "changelogtag": self.showtag("changelogtag",n),
281 "manifest": hex(changes[0]),
281 "manifest": hex(changes[0]),
282 "desc": changes[4],
282 "desc": changes[4],
283 "date": t,
283 "date": t,
284 "files": self.listfilediffs(changes[3], n),
284 "files": self.listfilediffs(changes[3], n),
285 "rev": i,
285 "rev": i,
286 "node": hn})
286 "node": hn})
287 parity = 1 - parity
287 parity = 1 - parity
288
288
289 for e in l: yield e
289 for e in l: yield e
290
290
291 cl = self.repo.changelog
291 cl = self.repo.changelog
292 mf = cl.read(cl.tip())[0]
292 mf = cl.read(cl.tip())[0]
293 count = cl.count()
293 count = cl.count()
294 start = max(0, pos - self.maxchanges + 1)
294 start = max(0, pos - self.maxchanges + 1)
295 end = min(count, start + self.maxchanges)
295 end = min(count, start + self.maxchanges)
296 pos = end - 1
296 pos = end - 1
297
297
298 yield self.t('changelog',
298 yield self.t('changelog',
299 changenav = changenav,
299 changenav=changenav,
300 manifest = hex(mf),
300 manifest=hex(mf),
301 rev = pos, changesets = count, entries = changelist)
301 rev=pos, changesets=count, entries=changelist)
302
302
303 def search(self, query):
303 def search(self, query):
304
304
305 def changelist(**map):
305 def changelist(**map):
306 cl = self.repo.changelog
306 cl = self.repo.changelog
307 count = 0
307 count = 0
308 qw = query.lower().split()
308 qw = query.lower().split()
309
309
310 def revgen():
310 def revgen():
311 for i in range(cl.count() - 1, 0, -100):
311 for i in range(cl.count() - 1, 0, -100):
312 l = []
312 l = []
313 for j in range(max(0, i - 100), i):
313 for j in range(max(0, i - 100), i):
314 n = cl.node(j)
314 n = cl.node(j)
315 changes = cl.read(n)
315 changes = cl.read(n)
316 l.append((n, j, changes))
316 l.append((n, j, changes))
317 l.reverse()
317 l.reverse()
318 for e in l:
318 for e in l:
319 yield e
319 yield e
320
320
321 for n, i, changes in revgen():
321 for n, i, changes in revgen():
322 miss = 0
322 miss = 0
323 for q in qw:
323 for q in qw:
324 if not (q in changes[1].lower() or
324 if not (q in changes[1].lower() or
325 q in changes[4].lower() or
325 q in changes[4].lower() or
326 q in " ".join(changes[3][:20]).lower()):
326 q in " ".join(changes[3][:20]).lower()):
327 miss = 1
327 miss = 1
328 break
328 break
329 if miss: continue
329 if miss: continue
330
330
331 count += 1
331 count += 1
332 hn = hex(n)
332 hn = hex(n)
333 t = float(changes[2].split(' ')[0])
333 t = float(changes[2].split(' ')[0])
334
334
335 yield self.t(
335 yield self.t(
336 'searchentry',
336 'searchentry',
337 parity = count & 1,
337 parity=count & 1,
338 author = changes[1],
338 author=changes[1],
339 parent = self.parents("changelogparent",
339 parent=self.parents("changelogparent",
340 cl.parents(n), cl.rev),
340 cl.parents(n), cl.rev),
341 changelogtag = self.showtag("changelogtag",n),
341 changelogtag=self.showtag("changelogtag",n),
342 manifest = hex(changes[0]),
342 manifest=hex(changes[0]),
343 desc = changes[4],
343 desc=changes[4],
344 date = t,
344 date=t,
345 files = self.listfilediffs(changes[3], n),
345 files=self.listfilediffs(changes[3], n),
346 rev = i,
346 rev=i,
347 node = hn)
347 node=hn)
348
348
349 if count >= self.maxchanges: break
349 if count >= self.maxchanges: break
350
350
351 cl = self.repo.changelog
351 cl = self.repo.changelog
352 mf = cl.read(cl.tip())[0]
352 mf = cl.read(cl.tip())[0]
353
353
354 yield self.t('search',
354 yield self.t('search',
355 query = query,
355 query=query,
356 manifest = hex(mf),
356 manifest=hex(mf),
357 entries = changelist)
357 entries=changelist)
358
358
359 def changeset(self, nodeid):
359 def changeset(self, nodeid):
360 n = bin(nodeid)
360 n = bin(nodeid)
361 cl = self.repo.changelog
361 cl = self.repo.changelog
362 changes = cl.read(n)
362 changes = cl.read(n)
363 p1 = cl.parents(n)[0]
363 p1 = cl.parents(n)[0]
364 t = float(changes[2].split(' ')[0])
364 t = float(changes[2].split(' ')[0])
365
365
366 files = []
366 files = []
367 mf = self.repo.manifest.read(changes[0])
367 mf = self.repo.manifest.read(changes[0])
368 for f in changes[3]:
368 for f in changes[3]:
369 files.append(self.t("filenodelink",
369 files.append(self.t("filenodelink",
370 filenode = hex(mf.get(f, nullid)), file = f))
370 filenode = hex(mf.get(f, nullid)), file=f))
371
371
372 def diff(**map):
372 def diff(**map):
373 yield self.diff(p1, n, None)
373 yield self.diff(p1, n, None)
374
374
375 yield self.t('changeset',
375 yield self.t('changeset',
376 diff = diff,
376 diff=diff,
377 rev = cl.rev(n),
377 rev=cl.rev(n),
378 node = nodeid,
378 node=nodeid,
379 parent = self.parents("changesetparent",
379 parent=self.parents("changesetparent",
380 cl.parents(n), cl.rev),
380 cl.parents(n), cl.rev),
381 changesettag = self.showtag("changesettag",n),
381 changesettag=self.showtag("changesettag",n),
382 manifest = hex(changes[0]),
382 manifest=hex(changes[0]),
383 author = changes[1],
383 author=changes[1],
384 desc = changes[4],
384 desc=changes[4],
385 date = t,
385 date=t,
386 files = files)
386 files=files)
387
387
388 def filelog(self, f, filenode):
388 def filelog(self, f, filenode):
389 cl = self.repo.changelog
389 cl = self.repo.changelog
390 fl = self.repo.file(f)
390 fl = self.repo.file(f)
391 count = fl.count()
391 count = fl.count()
392
392
393 def entries(**map):
393 def entries(**map):
394 l = []
394 l = []
395 parity = (count - 1) & 1
395 parity = (count - 1) & 1
396
396
397 for i in range(count):
397 for i in range(count):
398
398
399 n = fl.node(i)
399 n = fl.node(i)
400 lr = fl.linkrev(n)
400 lr = fl.linkrev(n)
401 cn = cl.node(lr)
401 cn = cl.node(lr)
402 cs = cl.read(cl.node(lr))
402 cs = cl.read(cl.node(lr))
403 t = float(cs[2].split(' ')[0])
403 t = float(cs[2].split(' ')[0])
404
404
405 l.insert(0, {"parity": parity,
405 l.insert(0, {"parity": parity,
406 "filenode": hex(n),
406 "filenode": hex(n),
407 "filerev": i,
407 "filerev": i,
408 "file": f,
408 "file": f,
409 "node": hex(cn),
409 "node": hex(cn),
410 "author": cs[1],
410 "author": cs[1],
411 "date": t,
411 "date": t,
412 "parent": self.parents("filelogparent",
412 "parent": self.parents("filelogparent",
413 fl.parents(n), fl.rev, file=f),
413 fl.parents(n), fl.rev, file=f),
414 "desc": cs[4]})
414 "desc": cs[4]})
415 parity = 1 - parity
415 parity = 1 - parity
416
416
417 for e in l: yield e
417 for e in l: yield e
418
418
419 yield self.t("filelog",
419 yield self.t("filelog",
420 file = f,
420 file=f,
421 filenode = filenode,
421 filenode=filenode,
422 entries = entries)
422 entries=entries)
423
423
424 def filerevision(self, f, node):
424 def filerevision(self, f, node):
425 fl = self.repo.file(f)
425 fl = self.repo.file(f)
426 n = bin(node)
426 n = bin(node)
427 text = fl.read(n)
427 text = fl.read(n)
428 changerev = fl.linkrev(n)
428 changerev = fl.linkrev(n)
429 cl = self.repo.changelog
429 cl = self.repo.changelog
430 cn = cl.node(changerev)
430 cn = cl.node(changerev)
431 cs = cl.read(cn)
431 cs = cl.read(cn)
432 t = float(cs[2].split(' ')[0])
432 t = float(cs[2].split(' ')[0])
433 mfn = cs[0]
433 mfn = cs[0]
434
434
435 def lines():
435 def lines():
436 for l, t in enumerate(text.splitlines(1)):
436 for l, t in enumerate(text.splitlines(1)):
437 yield {"line": t,
437 yield {"line": t,
438 "linenumber": "% 6d" % (l + 1),
438 "linenumber": "% 6d" % (l + 1),
439 "parity": l & 1}
439 "parity": l & 1}
440
440
441 yield self.t("filerevision", file = f,
441 yield self.t("filerevision", file=f,
442 filenode = node,
442 filenode=node,
443 path = up(f),
443 path=up(f),
444 text = lines(),
444 text=lines(),
445 rev = changerev,
445 rev=changerev,
446 node = hex(cn),
446 node=hex(cn),
447 manifest = hex(mfn),
447 manifest=hex(mfn),
448 author = cs[1],
448 author=cs[1],
449 date = t,
449 date=t,
450 parent = self.parents("filerevparent",
450 parent=self.parents("filerevparent",
451 fl.parents(n), fl.rev, file=f),
451 fl.parents(n), fl.rev, file=f),
452 permissions = self.repo.manifest.readflags(mfn)[f])
452 permissions=self.repo.manifest.readflags(mfn)[f])
453
453
454 def fileannotate(self, f, node):
454 def fileannotate(self, f, node):
455 bcache = {}
455 bcache = {}
456 ncache = {}
456 ncache = {}
457 fl = self.repo.file(f)
457 fl = self.repo.file(f)
458 n = bin(node)
458 n = bin(node)
459 changerev = fl.linkrev(n)
459 changerev = fl.linkrev(n)
460
460
461 cl = self.repo.changelog
461 cl = self.repo.changelog
462 cn = cl.node(changerev)
462 cn = cl.node(changerev)
463 cs = cl.read(cn)
463 cs = cl.read(cn)
464 t = float(cs[2].split(' ')[0])
464 t = float(cs[2].split(' ')[0])
465 mfn = cs[0]
465 mfn = cs[0]
466
466
467 def annotate(**map):
467 def annotate(**map):
468 parity = 1
468 parity = 1
469 last = None
469 last = None
470 for r, l in fl.annotate(n):
470 for r, l in fl.annotate(n):
471 try:
471 try:
472 cnode = ncache[r]
472 cnode = ncache[r]
473 except KeyError:
473 except KeyError:
474 cnode = ncache[r] = self.repo.changelog.node(r)
474 cnode = ncache[r] = self.repo.changelog.node(r)
475
475
476 try:
476 try:
477 name = bcache[r]
477 name = bcache[r]
478 except KeyError:
478 except KeyError:
479 cl = self.repo.changelog.read(cnode)
479 cl = self.repo.changelog.read(cnode)
480 name = cl[1]
480 name = cl[1]
481 f = name.find('@')
481 f = name.find('@')
482 if f >= 0:
482 if f >= 0:
483 name = name[:f]
483 name = name[:f]
484 f = name.find('<')
484 f = name.find('<')
485 if f >= 0:
485 if f >= 0:
486 name = name[f+1:]
486 name = name[f+1:]
487 bcache[r] = name
487 bcache[r] = name
488
488
489 if last != cnode:
489 if last != cnode:
490 parity = 1 - parity
490 parity = 1 - parity
491 last = cnode
491 last = cnode
492
492
493 yield {"parity": parity,
493 yield {"parity": parity,
494 "node": hex(cnode),
494 "node": hex(cnode),
495 "rev": r,
495 "rev": r,
496 "author": name,
496 "author": name,
497 "file": f,
497 "file": f,
498 "line": l}
498 "line": l}
499
499
500 yield self.t("fileannotate",
500 yield self.t("fileannotate",
501 file = f,
501 file = f,
502 filenode = node,
502 filenode = node,
503 annotate = annotate,
503 annotate = annotate,
504 path = up(f),
504 path = up(f),
505 rev = changerev,
505 rev = changerev,
506 node = hex(cn),
506 node = hex(cn),
507 manifest = hex(mfn),
507 manifest = hex(mfn),
508 author = cs[1],
508 author = cs[1],
509 date = t,
509 date = t,
510 parent = self.parents("fileannotateparent",
510 parent = self.parents("fileannotateparent",
511 fl.parents(n), fl.rev, file=f),
511 fl.parents(n), fl.rev, file=f),
512 permissions = self.repo.manifest.readflags(mfn)[f])
512 permissions = self.repo.manifest.readflags(mfn)[f])
513
513
514 def manifest(self, mnode, path):
514 def manifest(self, mnode, path):
515 mf = self.repo.manifest.read(bin(mnode))
515 mf = self.repo.manifest.read(bin(mnode))
516 rev = self.repo.manifest.rev(bin(mnode))
516 rev = self.repo.manifest.rev(bin(mnode))
517 node = self.repo.changelog.node(rev)
517 node = self.repo.changelog.node(rev)
518 mff=self.repo.manifest.readflags(bin(mnode))
518 mff=self.repo.manifest.readflags(bin(mnode))
519
519
520 files = {}
520 files = {}
521
521
522 p = path[1:]
522 p = path[1:]
523 l = len(p)
523 l = len(p)
524
524
525 for f,n in mf.items():
525 for f,n in mf.items():
526 if f[:l] != p:
526 if f[:l] != p:
527 continue
527 continue
528 remain = f[l:]
528 remain = f[l:]
529 if "/" in remain:
529 if "/" in remain:
530 short = remain[:remain.find("/") + 1] # bleah
530 short = remain[:remain.find("/") + 1] # bleah
531 files[short] = (f, None)
531 files[short] = (f, None)
532 else:
532 else:
533 short = os.path.basename(remain)
533 short = os.path.basename(remain)
534 files[short] = (f, n)
534 files[short] = (f, n)
535
535
536 def filelist(**map):
536 def filelist(**map):
537 parity = 0
537 parity = 0
538 fl = files.keys()
538 fl = files.keys()
539 fl.sort()
539 fl.sort()
540 for f in fl:
540 for f in fl:
541 full, fnode = files[f]
541 full, fnode = files[f]
542 if not fnode:
542 if not fnode:
543 continue
543 continue
544
544
545 yield {"file": full,
545 yield {"file": full,
546 "manifest": mnode,
546 "manifest": mnode,
547 "filenode": hex(fnode),
547 "filenode": hex(fnode),
548 "parity": parity,
548 "parity": parity,
549 "basename": f,
549 "basename": f,
550 "permissions": mff[full]}
550 "permissions": mff[full]}
551 parity = 1 - parity
551 parity = 1 - parity
552
552
553 def dirlist(**map):
553 def dirlist(**map):
554 parity = 0
554 parity = 0
555 fl = files.keys()
555 fl = files.keys()
556 fl.sort()
556 fl.sort()
557 for f in fl:
557 for f in fl:
558 full, fnode = files[f]
558 full, fnode = files[f]
559 if fnode:
559 if fnode:
560 continue
560 continue
561
561
562 yield {"parity": parity,
562 yield {"parity": parity,
563 "path": os.path.join(path, f),
563 "path": os.path.join(path, f),
564 "manifest": mnode,
564 "manifest": mnode,
565 "basename": f[:-1]}
565 "basename": f[:-1]}
566 parity = 1 - parity
566 parity = 1 - parity
567
567
568 yield self.t("manifest",
568 yield self.t("manifest",
569 manifest = mnode,
569 manifest=mnode,
570 rev = rev,
570 rev=rev,
571 node = hex(node),
571 node=hex(node),
572 path = path,
572 path=path,
573 up = up(path),
573 up=up(path),
574 fentries = filelist,
574 fentries=filelist,
575 dentries = dirlist)
575 dentries=dirlist)
576
576
577 def tags(self):
577 def tags(self):
578 cl = self.repo.changelog
578 cl = self.repo.changelog
579 mf = cl.read(cl.tip())[0]
579 mf = cl.read(cl.tip())[0]
580
580
581 i = self.repo.tagslist()
581 i = self.repo.tagslist()
582 i.reverse()
582 i.reverse()
583
583
584 def entries(**map):
584 def entries(**map):
585 parity = 0
585 parity = 0
586 for k,n in i:
586 for k,n in i:
587 yield {"parity": parity,
587 yield {"parity": parity,
588 "tag": k,
588 "tag": k,
589 "node": hex(n)}
589 "node": hex(n)}
590 parity = 1 - parity
590 parity = 1 - parity
591
591
592 yield self.t("tags",
592 yield self.t("tags",
593 manifest = hex(mf),
593 manifest=hex(mf),
594 entries = entries)
594 entries=entries)
595
595
596 def filediff(self, file, changeset):
596 def filediff(self, file, changeset):
597 n = bin(changeset)
597 n = bin(changeset)
598 cl = self.repo.changelog
598 cl = self.repo.changelog
599 p1 = cl.parents(n)[0]
599 p1 = cl.parents(n)[0]
600 cs = cl.read(n)
600 cs = cl.read(n)
601 mf = self.repo.manifest.read(cs[0])
601 mf = self.repo.manifest.read(cs[0])
602
602
603 def diff(**map):
603 def diff(**map):
604 yield self.diff(p1, n, file)
604 yield self.diff(p1, n, file)
605
605
606 yield self.t("filediff",
606 yield self.t("filediff",
607 file = file,
607 file=file,
608 filenode = hex(mf.get(file, nullid)),
608 filenode=hex(mf.get(file, nullid)),
609 node = changeset,
609 node=changeset,
610 rev = self.repo.changelog.rev(n),
610 rev=self.repo.changelog.rev(n),
611 parent = self.parents("filediffparent",
611 parent=self.parents("filediffparent",
612 cl.parents(n), cl.rev),
612 cl.parents(n), cl.rev),
613 diff = diff)
613 diff=diff)
614
614
615 # add tags to things
615 # add tags to things
616 # tags -> list of changesets corresponding to tags
616 # tags -> list of changesets corresponding to tags
617 # find tag, changeset, file
617 # find tag, changeset, file
618
618
619 def run(self):
619 def run(self):
620 def header(**map):
620 def header(**map):
621 yield self.t("header", **map)
621 yield self.t("header", **map)
622
622
623 def footer(**map):
623 def footer(**map):
624 yield self.t("footer", **map)
624 yield self.t("footer", **map)
625
625
626 self.refresh()
626 self.refresh()
627 args = cgi.parse()
627 args = cgi.parse()
628
628
629 t = self.repo.ui.config("web", "templates", templatepath())
629 t = self.repo.ui.config("web", "templates", templatepath())
630 m = os.path.join(t, "map")
630 m = os.path.join(t, "map")
631 style = self.repo.ui.config("web", "style", "")
631 style = self.repo.ui.config("web", "style", "")
632 if args.has_key('style'):
632 if args.has_key('style'):
633 style = args['style'][0]
633 style = args['style'][0]
634 if style:
634 if style:
635 b = os.path.basename("map-" + style)
635 b = os.path.basename("map-" + style)
636 p = os.path.join(t, b)
636 p = os.path.join(t, b)
637 if os.path.isfile(p): m = p
637 if os.path.isfile(p): m = p
638
638
639 port = os.environ["SERVER_PORT"]
639 port = os.environ["SERVER_PORT"]
640 port = port != "80" and (":" + port) or ""
640 port = port != "80" and (":" + port) or ""
641 uri = os.environ["REQUEST_URI"]
641 uri = os.environ["REQUEST_URI"]
642 if "?" in uri: uri = uri.split("?")[0]
642 if "?" in uri: uri = uri.split("?")[0]
643 url = "http://%s%s%s" % (os.environ["SERVER_NAME"], port, uri)
643 url = "http://%s%s%s" % (os.environ["SERVER_NAME"], port, uri)
644
644
645 self.t = templater(m, common_filters,
645 self.t = templater(m, common_filters,
646 {"url":url,
646 {"url": url,
647 "repo":self.reponame,
647 "repo": self.reponame,
648 "header":header,
648 "header": header,
649 "footer":footer,
649 "footer": footer,
650 })
650 })
651
651
652 if not args.has_key('cmd'):
652 if not args.has_key('cmd'):
653 args['cmd'] = [self.t.cache['default'],]
653 args['cmd'] = [self.t.cache['default'],]
654
654
655 if args['cmd'][0] == 'changelog':
655 if args['cmd'][0] == 'changelog':
656 c = self.repo.changelog.count() - 1
656 c = self.repo.changelog.count() - 1
657 hi = c
657 hi = c
658 if args.has_key('rev'):
658 if args.has_key('rev'):
659 hi = args['rev'][0]
659 hi = args['rev'][0]
660 try:
660 try:
661 hi = self.repo.changelog.rev(self.repo.lookup(hi))
661 hi = self.repo.changelog.rev(self.repo.lookup(hi))
662 except RepoError:
662 except RepoError:
663 write(self.search(hi))
663 write(self.search(hi))
664 return
664 return
665
665
666 write(self.changelog(hi))
666 write(self.changelog(hi))
667
667
668 elif args['cmd'][0] == 'changeset':
668 elif args['cmd'][0] == 'changeset':
669 write(self.changeset(args['node'][0]))
669 write(self.changeset(args['node'][0]))
670
670
671 elif args['cmd'][0] == 'manifest':
671 elif args['cmd'][0] == 'manifest':
672 write(self.manifest(args['manifest'][0], args['path'][0]))
672 write(self.manifest(args['manifest'][0], args['path'][0]))
673
673
674 elif args['cmd'][0] == 'tags':
674 elif args['cmd'][0] == 'tags':
675 write(self.tags())
675 write(self.tags())
676
676
677 elif args['cmd'][0] == 'filediff':
677 elif args['cmd'][0] == 'filediff':
678 write(self.filediff(args['file'][0], args['node'][0]))
678 write(self.filediff(args['file'][0], args['node'][0]))
679
679
680 elif args['cmd'][0] == 'file':
680 elif args['cmd'][0] == 'file':
681 write(self.filerevision(args['file'][0], args['filenode'][0]))
681 write(self.filerevision(args['file'][0], args['filenode'][0]))
682
682
683 elif args['cmd'][0] == 'annotate':
683 elif args['cmd'][0] == 'annotate':
684 write(self.fileannotate(args['file'][0], args['filenode'][0]))
684 write(self.fileannotate(args['file'][0], args['filenode'][0]))
685
685
686 elif args['cmd'][0] == 'filelog':
686 elif args['cmd'][0] == 'filelog':
687 write(self.filelog(args['file'][0], args['filenode'][0]))
687 write(self.filelog(args['file'][0], args['filenode'][0]))
688
688
689 elif args['cmd'][0] == 'heads':
689 elif args['cmd'][0] == 'heads':
690 httphdr("application/mercurial-0.1")
690 httphdr("application/mercurial-0.1")
691 h = self.repo.heads()
691 h = self.repo.heads()
692 sys.stdout.write(" ".join(map(hex, h)) + "\n")
692 sys.stdout.write(" ".join(map(hex, h)) + "\n")
693
693
694 elif args['cmd'][0] == 'branches':
694 elif args['cmd'][0] == 'branches':
695 httphdr("application/mercurial-0.1")
695 httphdr("application/mercurial-0.1")
696 nodes = []
696 nodes = []
697 if args.has_key('nodes'):
697 if args.has_key('nodes'):
698 nodes = map(bin, args['nodes'][0].split(" "))
698 nodes = map(bin, args['nodes'][0].split(" "))
699 for b in self.repo.branches(nodes):
699 for b in self.repo.branches(nodes):
700 sys.stdout.write(" ".join(map(hex, b)) + "\n")
700 sys.stdout.write(" ".join(map(hex, b)) + "\n")
701
701
702 elif args['cmd'][0] == 'between':
702 elif args['cmd'][0] == 'between':
703 httphdr("application/mercurial-0.1")
703 httphdr("application/mercurial-0.1")
704 nodes = []
704 nodes = []
705 if args.has_key('pairs'):
705 if args.has_key('pairs'):
706 pairs = [ map(bin, p.split("-"))
706 pairs = [ map(bin, p.split("-"))
707 for p in args['pairs'][0].split(" ") ]
707 for p in args['pairs'][0].split(" ") ]
708 for b in self.repo.between(pairs):
708 for b in self.repo.between(pairs):
709 sys.stdout.write(" ".join(map(hex, b)) + "\n")
709 sys.stdout.write(" ".join(map(hex, b)) + "\n")
710
710
711 elif args['cmd'][0] == 'changegroup':
711 elif args['cmd'][0] == 'changegroup':
712 httphdr("application/mercurial-0.1")
712 httphdr("application/mercurial-0.1")
713 nodes = []
713 nodes = []
714 if not self.allowpull:
714 if not self.allowpull:
715 return
715 return
716
716
717 if args.has_key('roots'):
717 if args.has_key('roots'):
718 nodes = map(bin, args['roots'][0].split(" "))
718 nodes = map(bin, args['roots'][0].split(" "))
719
719
720 z = zlib.compressobj()
720 z = zlib.compressobj()
721 f = self.repo.changegroup(nodes)
721 f = self.repo.changegroup(nodes)
722 while 1:
722 while 1:
723 chunk = f.read(4096)
723 chunk = f.read(4096)
724 if not chunk: break
724 if not chunk: break
725 sys.stdout.write(z.compress(chunk))
725 sys.stdout.write(z.compress(chunk))
726
726
727 sys.stdout.write(z.flush())
727 sys.stdout.write(z.flush())
728
728
729 else:
729 else:
730 write(self.t("error"))
730 write(self.t("error"))
731
731
732 def create_server(repo):
732 def create_server(repo):
733
733
734 def openlog(opt, default):
734 def openlog(opt, default):
735 if opt and opt != '-':
735 if opt and opt != '-':
736 return open(opt, 'w')
736 return open(opt, 'w')
737 return default
737 return default
738
738
739 address = repo.ui.config("web", "address", "")
739 address = repo.ui.config("web", "address", "")
740 port = int(repo.ui.config("web", "port", 8000))
740 port = int(repo.ui.config("web", "port", 8000))
741 use_ipv6 = repo.ui.configbool("web", "ipv6")
741 use_ipv6 = repo.ui.configbool("web", "ipv6")
742 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
742 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
743 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
743 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
744
744
745 import BaseHTTPServer
745 import BaseHTTPServer
746
746
747 class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
747 class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
748 address_family = getattr(socket, 'AF_INET6', None)
748 address_family = getattr(socket, 'AF_INET6', None)
749
749
750 def __init__(self, *args, **kwargs):
750 def __init__(self, *args, **kwargs):
751 if self.address_family is None:
751 if self.address_family is None:
752 raise RepoError('IPv6 not available on this system')
752 raise RepoError('IPv6 not available on this system')
753 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
753 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
754
754
755 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
755 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
756 def log_error(self, format, *args):
756 def log_error(self, format, *args):
757 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
757 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
758 self.log_date_time_string(),
758 self.log_date_time_string(),
759 format % args))
759 format % args))
760
760
761 def log_message(self, format, *args):
761 def log_message(self, format, *args):
762 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
762 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
763 self.log_date_time_string(),
763 self.log_date_time_string(),
764 format % args))
764 format % args))
765
765
766 def do_POST(self):
766 def do_POST(self):
767 try:
767 try:
768 self.do_hgweb()
768 self.do_hgweb()
769 except socket.error, inst:
769 except socket.error, inst:
770 if inst.args[0] != 32: raise
770 if inst.args[0] != 32: raise
771
771
772 def do_GET(self):
772 def do_GET(self):
773 self.do_POST()
773 self.do_POST()
774
774
775 def do_hgweb(self):
775 def do_hgweb(self):
776 query = ""
776 query = ""
777 p = self.path.find("?")
777 p = self.path.find("?")
778 if p:
778 if p:
779 query = self.path[p + 1:]
779 query = self.path[p + 1:]
780 query = query.replace('+', ' ')
780 query = query.replace('+', ' ')
781
781
782 env = {}
782 env = {}
783 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
783 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
784 env['REQUEST_METHOD'] = self.command
784 env['REQUEST_METHOD'] = self.command
785 env['SERVER_NAME'] = self.server.server_name
785 env['SERVER_NAME'] = self.server.server_name
786 env['SERVER_PORT'] = str(self.server.server_port)
786 env['SERVER_PORT'] = str(self.server.server_port)
787 env['REQUEST_URI'] = "/"
787 env['REQUEST_URI'] = "/"
788 if query:
788 if query:
789 env['QUERY_STRING'] = query
789 env['QUERY_STRING'] = query
790 host = self.address_string()
790 host = self.address_string()
791 if host != self.client_address[0]:
791 if host != self.client_address[0]:
792 env['REMOTE_HOST'] = host
792 env['REMOTE_HOST'] = host
793 env['REMOTE_ADDR'] = self.client_address[0]
793 env['REMOTE_ADDR'] = self.client_address[0]
794
794
795 if self.headers.typeheader is None:
795 if self.headers.typeheader is None:
796 env['CONTENT_TYPE'] = self.headers.type
796 env['CONTENT_TYPE'] = self.headers.type
797 else:
797 else:
798 env['CONTENT_TYPE'] = self.headers.typeheader
798 env['CONTENT_TYPE'] = self.headers.typeheader
799 length = self.headers.getheader('content-length')
799 length = self.headers.getheader('content-length')
800 if length:
800 if length:
801 env['CONTENT_LENGTH'] = length
801 env['CONTENT_LENGTH'] = length
802 accept = []
802 accept = []
803 for line in self.headers.getallmatchingheaders('accept'):
803 for line in self.headers.getallmatchingheaders('accept'):
804 if line[:1] in "\t\n\r ":
804 if line[:1] in "\t\n\r ":
805 accept.append(line.strip())
805 accept.append(line.strip())
806 else:
806 else:
807 accept = accept + line[7:].split(',')
807 accept = accept + line[7:].split(',')
808 env['HTTP_ACCEPT'] = ','.join(accept)
808 env['HTTP_ACCEPT'] = ','.join(accept)
809
809
810 os.environ.update(env)
810 os.environ.update(env)
811
811
812 save = sys.argv, sys.stdin, sys.stdout, sys.stderr
812 save = sys.argv, sys.stdin, sys.stdout, sys.stderr
813 try:
813 try:
814 sys.stdin = self.rfile
814 sys.stdin = self.rfile
815 sys.stdout = self.wfile
815 sys.stdout = self.wfile
816 sys.argv = ["hgweb.py"]
816 sys.argv = ["hgweb.py"]
817 if '=' not in query:
817 if '=' not in query:
818 sys.argv.append(query)
818 sys.argv.append(query)
819 self.send_response(200, "Script output follows")
819 self.send_response(200, "Script output follows")
820 hg.run()
820 hg.run()
821 finally:
821 finally:
822 sys.argv, sys.stdin, sys.stdout, sys.stderr = save
822 sys.argv, sys.stdin, sys.stdout, sys.stderr = save
823
823
824 hg = hgweb(repo)
824 hg = hgweb(repo)
825 if use_ipv6:
825 if use_ipv6:
826 return IPv6HTTPServer((address, port), hgwebhandler)
826 return IPv6HTTPServer((address, port), hgwebhandler)
827 else:
827 else:
828 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
828 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
829
829
830 def server(path, name, templates, address, port, use_ipv6 = False,
830 def server(path, name, templates, address, port, use_ipv6=False,
831 accesslog = sys.stdout, errorlog = sys.stderr):
831 accesslog=sys.stdout, errorlog=sys.stderr):
832 httpd = create_server(path, name, templates, address, port, use_ipv6,
832 httpd = create_server(path, name, templates, address, port, use_ipv6,
833 accesslog, errorlog)
833 accesslog, errorlog)
834 httpd.serve_forever()
834 httpd.serve_forever()
835
835
836 # This is a stopgap
836 # This is a stopgap
837 class hgwebdir:
837 class hgwebdir:
838 def __init__(self, config):
838 def __init__(self, config):
839 self.cp = ConfigParser.SafeConfigParser()
839 self.cp = ConfigParser.SafeConfigParser()
840 self.cp.read(config)
840 self.cp.read(config)
841
841
842 def run(self):
842 def run(self):
843 try:
843 try:
844 virtual = os.environ["PATH_INFO"]
844 virtual = os.environ["PATH_INFO"]
845 except:
845 except:
846 virtual = ""
846 virtual = ""
847
847
848 if virtual[1:]:
848 if virtual[1:]:
849 real = self.cp.get("paths", virtual[1:])
849 real = self.cp.get("paths", virtual[1:])
850 h = hgweb(real)
850 h = hgweb(real)
851 h.run()
851 h.run()
852 return
852 return
853
853
854 def header(**map):
854 def header(**map):
855 yield tmpl("header", **map)
855 yield tmpl("header", **map)
856
856
857 def footer(**map):
857 def footer(**map):
858 yield tmpl("footer", **map)
858 yield tmpl("footer", **map)
859
859
860 templates = templatepath()
860 templates = templatepath()
861 m = os.path.join(templates, "map")
861 m = os.path.join(templates, "map")
862 tmpl = templater(m, common_filters,
862 tmpl = templater(m, common_filters,
863 {"header": header, "footer": footer})
863 {"header": header, "footer": footer})
864
864
865 def entries(**map):
865 def entries(**map):
866 parity = 0
866 parity = 0
867 l = self.cp.items("paths")
867 l = self.cp.items("paths")
868 l.sort()
868 l.sort()
869 for v,r in l:
869 for v,r in l:
870 cp2 = ConfigParser.SafeConfigParser()
870 cp2 = ConfigParser.SafeConfigParser()
871 cp2.read(os.path.join(r, ".hg", "hgrc"))
871 cp2.read(os.path.join(r, ".hg", "hgrc"))
872
872
873 def get(sec, val, default):
873 def get(sec, val, default):
874 try:
874 try:
875 return cp2.get(sec, val)
875 return cp2.get(sec, val)
876 except:
876 except:
877 return default
877 return default
878
878
879 url = os.environ["REQUEST_URI"] + "/" + v
879 url = os.environ["REQUEST_URI"] + "/" + v
880 url = url.replace("//", "/")
880 url = url.replace("//", "/")
881
881
882 yield dict(author = get("web", "author", "unknown"),
882 yield dict(author=get("web", "author", "unknown"),
883 name = get("web", "name", v),
883 name=get("web", "name", v),
884 url = url,
884 url=url,
885 parity = parity,
885 parity=parity,
886 shortdesc = get("web", "description", "unknown"),
886 shortdesc=get("web", "description", "unknown"),
887 lastupdate = os.stat(os.path.join(r, ".hg",
887 lastupdate=os.stat(os.path.join(r, ".hg",
888 "00changelog.d")).st_mtime)
888 "00changelog.d")).st_mtime)
889
889
890 parity = 1 - parity
890 parity = 1 - parity
891
891
892 write(tmpl("index", entries = entries))
892 write(tmpl("index", entries=entries))
@@ -1,49 +1,49
1 # lock.py - simple locking scheme for mercurial
1 # lock.py - simple locking scheme for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 import os, time
8 import os, time
9 import util
9 import util
10
10
11 class LockHeld(Exception):
11 class LockHeld(Exception):
12 pass
12 pass
13
13
14 class lock:
14 class lock:
15 def __init__(self, file, wait = 1):
15 def __init__(self, file, wait=1):
16 self.f = file
16 self.f = file
17 self.held = 0
17 self.held = 0
18 self.wait = wait
18 self.wait = wait
19 self.lock()
19 self.lock()
20
20
21 def __del__(self):
21 def __del__(self):
22 self.release()
22 self.release()
23
23
24 def lock(self):
24 def lock(self):
25 while 1:
25 while 1:
26 try:
26 try:
27 self.trylock()
27 self.trylock()
28 return 1
28 return 1
29 except LockHeld, inst:
29 except LockHeld, inst:
30 if self.wait:
30 if self.wait:
31 time.sleep(1)
31 time.sleep(1)
32 continue
32 continue
33 raise inst
33 raise inst
34
34
35 def trylock(self):
35 def trylock(self):
36 pid = os.getpid()
36 pid = os.getpid()
37 try:
37 try:
38 util.makelock(str(pid), self.f)
38 util.makelock(str(pid), self.f)
39 self.held = 1
39 self.held = 1
40 except (OSError, IOError):
40 except (OSError, IOError):
41 raise LockHeld(util.readlock(self.f))
41 raise LockHeld(util.readlock(self.f))
42
42
43 def release(self):
43 def release(self):
44 if self.held:
44 if self.held:
45 self.held = 0
45 self.held = 0
46 try:
46 try:
47 os.unlink(self.f)
47 os.unlink(self.f)
48 except: pass
48 except: pass
49
49
@@ -1,551 +1,551
1 # revlog.py - storage back-end for mercurial
1 # revlog.py - storage back-end for mercurial
2 #
2 #
3 # This provides efficient delta storage with O(1) retrieve and append
3 # This provides efficient delta storage with O(1) retrieve and append
4 # and O(changes) merge between branches
4 # and O(changes) merge between branches
5 #
5 #
6 # Copyright 2005 Matt Mackall <mpm@selenic.com>
6 # Copyright 2005 Matt Mackall <mpm@selenic.com>
7 #
7 #
8 # This software may be used and distributed according to the terms
8 # This software may be used and distributed according to the terms
9 # of the GNU General Public License, incorporated herein by reference.
9 # of the GNU General Public License, incorporated herein by reference.
10
10
11 import zlib, struct, sha, binascii, heapq
11 import zlib, struct, sha, binascii, heapq
12 from mercurial import mdiff
12 from mercurial import mdiff
13
13
14 def hex(node): return binascii.hexlify(node)
14 def hex(node): return binascii.hexlify(node)
15 def bin(node): return binascii.unhexlify(node)
15 def bin(node): return binascii.unhexlify(node)
16 def short(node): return hex(node[:6])
16 def short(node): return hex(node[:6])
17
17
18 def compress(text):
18 def compress(text):
19 if not text: return text
19 if not text: return text
20 if len(text) < 44:
20 if len(text) < 44:
21 if text[0] == '\0': return text
21 if text[0] == '\0': return text
22 return 'u' + text
22 return 'u' + text
23 bin = zlib.compress(text)
23 bin = zlib.compress(text)
24 if len(bin) > len(text):
24 if len(bin) > len(text):
25 if text[0] == '\0': return text
25 if text[0] == '\0': return text
26 return 'u' + text
26 return 'u' + text
27 return bin
27 return bin
28
28
29 def decompress(bin):
29 def decompress(bin):
30 if not bin: return bin
30 if not bin: return bin
31 t = bin[0]
31 t = bin[0]
32 if t == '\0': return bin
32 if t == '\0': return bin
33 if t == 'x': return zlib.decompress(bin)
33 if t == 'x': return zlib.decompress(bin)
34 if t == 'u': return bin[1:]
34 if t == 'u': return bin[1:]
35 raise "unknown compression type %s" % t
35 raise "unknown compression type %s" % t
36
36
37 def hash(text, p1, p2):
37 def hash(text, p1, p2):
38 l = [p1, p2]
38 l = [p1, p2]
39 l.sort()
39 l.sort()
40 s = sha.new(l[0])
40 s = sha.new(l[0])
41 s.update(l[1])
41 s.update(l[1])
42 s.update(text)
42 s.update(text)
43 return s.digest()
43 return s.digest()
44
44
45 nullid = "\0" * 20
45 nullid = "\0" * 20
46 indexformat = ">4l20s20s20s"
46 indexformat = ">4l20s20s20s"
47
47
48 class lazyparser:
48 class lazyparser:
49 def __init__(self, data, revlog):
49 def __init__(self, data, revlog):
50 self.data = data
50 self.data = data
51 self.s = struct.calcsize(indexformat)
51 self.s = struct.calcsize(indexformat)
52 self.l = len(data)/self.s
52 self.l = len(data)/self.s
53 self.index = [None] * self.l
53 self.index = [None] * self.l
54 self.map = {nullid: -1}
54 self.map = {nullid: -1}
55 self.all = 0
55 self.all = 0
56 self.revlog = revlog
56 self.revlog = revlog
57
57
58 def load(self, pos=None):
58 def load(self, pos=None):
59 if self.all: return
59 if self.all: return
60 if pos is not None:
60 if pos is not None:
61 block = pos / 1000
61 block = pos / 1000
62 i = block * 1000
62 i = block * 1000
63 end = min(self.l, i + 1000)
63 end = min(self.l, i + 1000)
64 else:
64 else:
65 self.all = 1
65 self.all = 1
66 i = 0
66 i = 0
67 end = self.l
67 end = self.l
68 self.revlog.index = self.index
68 self.revlog.index = self.index
69 self.revlog.nodemap = self.map
69 self.revlog.nodemap = self.map
70
70
71 while i < end:
71 while i < end:
72 d = self.data[i * self.s: (i + 1) * self.s]
72 d = self.data[i * self.s: (i + 1) * self.s]
73 e = struct.unpack(indexformat, d)
73 e = struct.unpack(indexformat, d)
74 self.index[i] = e
74 self.index[i] = e
75 self.map[e[6]] = i
75 self.map[e[6]] = i
76 i += 1
76 i += 1
77
77
78 class lazyindex:
78 class lazyindex:
79 def __init__(self, parser):
79 def __init__(self, parser):
80 self.p = parser
80 self.p = parser
81 def __len__(self):
81 def __len__(self):
82 return len(self.p.index)
82 return len(self.p.index)
83 def load(self, pos):
83 def load(self, pos):
84 self.p.load(pos)
84 self.p.load(pos)
85 return self.p.index[pos]
85 return self.p.index[pos]
86 def __getitem__(self, pos):
86 def __getitem__(self, pos):
87 return self.p.index[pos] or self.load(pos)
87 return self.p.index[pos] or self.load(pos)
88 def append(self, e):
88 def append(self, e):
89 self.p.index.append(e)
89 self.p.index.append(e)
90
90
91 class lazymap:
91 class lazymap:
92 def __init__(self, parser):
92 def __init__(self, parser):
93 self.p = parser
93 self.p = parser
94 def load(self, key):
94 def load(self, key):
95 if self.p.all: return
95 if self.p.all: return
96 n = self.p.data.find(key)
96 n = self.p.data.find(key)
97 if n < 0: raise KeyError("node " + hex(key))
97 if n < 0: raise KeyError("node " + hex(key))
98 pos = n / self.p.s
98 pos = n / self.p.s
99 self.p.load(pos)
99 self.p.load(pos)
100 def __contains__(self, key):
100 def __contains__(self, key):
101 self.p.load()
101 self.p.load()
102 return key in self.p.map
102 return key in self.p.map
103 def __iter__(self):
103 def __iter__(self):
104 yield nullid
104 yield nullid
105 for i in xrange(self.p.l):
105 for i in xrange(self.p.l):
106 try:
106 try:
107 yield self.p.index[i][6]
107 yield self.p.index[i][6]
108 except:
108 except:
109 self.p.load(i)
109 self.p.load(i)
110 yield self.p.index[i][6]
110 yield self.p.index[i][6]
111 def __getitem__(self, key):
111 def __getitem__(self, key):
112 try:
112 try:
113 return self.p.map[key]
113 return self.p.map[key]
114 except KeyError:
114 except KeyError:
115 try:
115 try:
116 self.load(key)
116 self.load(key)
117 return self.p.map[key]
117 return self.p.map[key]
118 except KeyError:
118 except KeyError:
119 raise KeyError("node " + hex(key))
119 raise KeyError("node " + hex(key))
120 def __setitem__(self, key, val):
120 def __setitem__(self, key, val):
121 self.p.map[key] = val
121 self.p.map[key] = val
122
122
123 class revlog:
123 class revlog:
124 def __init__(self, opener, indexfile, datafile):
124 def __init__(self, opener, indexfile, datafile):
125 self.indexfile = indexfile
125 self.indexfile = indexfile
126 self.datafile = datafile
126 self.datafile = datafile
127 self.opener = opener
127 self.opener = opener
128 self.cache = None
128 self.cache = None
129
129
130 try:
130 try:
131 i = self.opener(self.indexfile).read()
131 i = self.opener(self.indexfile).read()
132 except IOError:
132 except IOError:
133 i = ""
133 i = ""
134
134
135 if len(i) > 10000:
135 if len(i) > 10000:
136 # big index, let's parse it on demand
136 # big index, let's parse it on demand
137 parser = lazyparser(i, self)
137 parser = lazyparser(i, self)
138 self.index = lazyindex(parser)
138 self.index = lazyindex(parser)
139 self.nodemap = lazymap(parser)
139 self.nodemap = lazymap(parser)
140 else:
140 else:
141 s = struct.calcsize(indexformat)
141 s = struct.calcsize(indexformat)
142 l = len(i) / s
142 l = len(i) / s
143 self.index = [None] * l
143 self.index = [None] * l
144 m = [None] * l
144 m = [None] * l
145
145
146 n = 0
146 n = 0
147 for f in xrange(0, len(i), s):
147 for f in xrange(0, len(i), s):
148 # offset, size, base, linkrev, p1, p2, nodeid
148 # offset, size, base, linkrev, p1, p2, nodeid
149 e = struct.unpack(indexformat, i[f:f + s])
149 e = struct.unpack(indexformat, i[f:f + s])
150 m[n] = (e[6], n)
150 m[n] = (e[6], n)
151 self.index[n] = e
151 self.index[n] = e
152 n += 1
152 n += 1
153
153
154 self.nodemap = dict(m)
154 self.nodemap = dict(m)
155 self.nodemap[nullid] = -1
155 self.nodemap[nullid] = -1
156
156
157 def tip(self): return self.node(len(self.index) - 1)
157 def tip(self): return self.node(len(self.index) - 1)
158 def count(self): return len(self.index)
158 def count(self): return len(self.index)
159 def node(self, rev): return (rev < 0) and nullid or self.index[rev][6]
159 def node(self, rev): return (rev < 0) and nullid or self.index[rev][6]
160 def rev(self, node): return self.nodemap[node]
160 def rev(self, node): return self.nodemap[node]
161 def linkrev(self, node): return self.index[self.nodemap[node]][3]
161 def linkrev(self, node): return self.index[self.nodemap[node]][3]
162 def parents(self, node):
162 def parents(self, node):
163 if node == nullid: return (nullid, nullid)
163 if node == nullid: return (nullid, nullid)
164 return self.index[self.nodemap[node]][4:6]
164 return self.index[self.nodemap[node]][4:6]
165
165
166 def start(self, rev): return self.index[rev][0]
166 def start(self, rev): return self.index[rev][0]
167 def length(self, rev): return self.index[rev][1]
167 def length(self, rev): return self.index[rev][1]
168 def end(self, rev): return self.start(rev) + self.length(rev)
168 def end(self, rev): return self.start(rev) + self.length(rev)
169 def base(self, rev): return self.index[rev][2]
169 def base(self, rev): return self.index[rev][2]
170
170
171 def heads(self, stop=None):
171 def heads(self, stop=None):
172 p = {}
172 p = {}
173 h = []
173 h = []
174 stoprev = 0
174 stoprev = 0
175 if stop and stop in self.nodemap:
175 if stop and stop in self.nodemap:
176 stoprev = self.rev(stop)
176 stoprev = self.rev(stop)
177
177
178 for r in range(self.count() - 1, -1, -1):
178 for r in range(self.count() - 1, -1, -1):
179 n = self.node(r)
179 n = self.node(r)
180 if n not in p:
180 if n not in p:
181 h.append(n)
181 h.append(n)
182 if n == stop:
182 if n == stop:
183 break
183 break
184 if r < stoprev:
184 if r < stoprev:
185 break
185 break
186 for pn in self.parents(n):
186 for pn in self.parents(n):
187 p[pn] = 1
187 p[pn] = 1
188 return h
188 return h
189
189
190 def children(self, node):
190 def children(self, node):
191 c = []
191 c = []
192 p = self.rev(node)
192 p = self.rev(node)
193 for r in range(p + 1, self.count()):
193 for r in range(p + 1, self.count()):
194 n = self.node(r)
194 n = self.node(r)
195 for pn in self.parents(n):
195 for pn in self.parents(n):
196 if pn == node:
196 if pn == node:
197 c.append(n)
197 c.append(n)
198 continue
198 continue
199 elif pn == nullid:
199 elif pn == nullid:
200 continue
200 continue
201 return c
201 return c
202
202
203 def lookup(self, id):
203 def lookup(self, id):
204 try:
204 try:
205 rev = int(id)
205 rev = int(id)
206 if str(rev) != id: raise ValueError
206 if str(rev) != id: raise ValueError
207 if rev < 0: rev = self.count() + rev
207 if rev < 0: rev = self.count() + rev
208 if rev < 0 or rev >= self.count(): raise ValueError
208 if rev < 0 or rev >= self.count(): raise ValueError
209 return self.node(rev)
209 return self.node(rev)
210 except (ValueError, OverflowError):
210 except (ValueError, OverflowError):
211 c = []
211 c = []
212 for n in self.nodemap:
212 for n in self.nodemap:
213 if hex(n).startswith(id):
213 if hex(n).startswith(id):
214 c.append(n)
214 c.append(n)
215 if len(c) > 1: raise KeyError("Ambiguous identifier")
215 if len(c) > 1: raise KeyError("Ambiguous identifier")
216 if len(c) < 1: raise KeyError("No match found")
216 if len(c) < 1: raise KeyError("No match found")
217 return c[0]
217 return c[0]
218
218
219 return None
219 return None
220
220
221 def diff(self, a, b):
221 def diff(self, a, b):
222 return mdiff.textdiff(a, b)
222 return mdiff.textdiff(a, b)
223
223
224 def patches(self, t, pl):
224 def patches(self, t, pl):
225 return mdiff.patches(t, pl)
225 return mdiff.patches(t, pl)
226
226
227 def delta(self, node):
227 def delta(self, node):
228 r = self.rev(node)
228 r = self.rev(node)
229 b = self.base(r)
229 b = self.base(r)
230 if r == b:
230 if r == b:
231 return self.diff(self.revision(self.node(r - 1)),
231 return self.diff(self.revision(self.node(r - 1)),
232 self.revision(node))
232 self.revision(node))
233 else:
233 else:
234 f = self.opener(self.datafile)
234 f = self.opener(self.datafile)
235 f.seek(self.start(r))
235 f.seek(self.start(r))
236 data = f.read(self.length(r))
236 data = f.read(self.length(r))
237 return decompress(data)
237 return decompress(data)
238
238
239 def revision(self, node):
239 def revision(self, node):
240 if node == nullid: return ""
240 if node == nullid: return ""
241 if self.cache and self.cache[0] == node: return self.cache[2]
241 if self.cache and self.cache[0] == node: return self.cache[2]
242
242
243 text = None
243 text = None
244 rev = self.rev(node)
244 rev = self.rev(node)
245 start, length, base, link, p1, p2, node = self.index[rev]
245 start, length, base, link, p1, p2, node = self.index[rev]
246 end = start + length
246 end = start + length
247 if base != rev: start = self.start(base)
247 if base != rev: start = self.start(base)
248
248
249 if self.cache and self.cache[1] >= base and self.cache[1] < rev:
249 if self.cache and self.cache[1] >= base and self.cache[1] < rev:
250 base = self.cache[1]
250 base = self.cache[1]
251 start = self.start(base + 1)
251 start = self.start(base + 1)
252 text = self.cache[2]
252 text = self.cache[2]
253 last = 0
253 last = 0
254
254
255 f = self.opener(self.datafile)
255 f = self.opener(self.datafile)
256 f.seek(start)
256 f.seek(start)
257 data = f.read(end - start)
257 data = f.read(end - start)
258
258
259 if text is None:
259 if text is None:
260 last = self.length(base)
260 last = self.length(base)
261 text = decompress(data[:last])
261 text = decompress(data[:last])
262
262
263 bins = []
263 bins = []
264 for r in xrange(base + 1, rev + 1):
264 for r in xrange(base + 1, rev + 1):
265 s = self.length(r)
265 s = self.length(r)
266 bins.append(decompress(data[last:last + s]))
266 bins.append(decompress(data[last:last + s]))
267 last = last + s
267 last = last + s
268
268
269 text = mdiff.patches(text, bins)
269 text = mdiff.patches(text, bins)
270
270
271 if node != hash(text, p1, p2):
271 if node != hash(text, p1, p2):
272 raise IOError("integrity check failed on %s:%d"
272 raise IOError("integrity check failed on %s:%d"
273 % (self.datafile, rev))
273 % (self.datafile, rev))
274
274
275 self.cache = (node, rev, text)
275 self.cache = (node, rev, text)
276 return text
276 return text
277
277
278 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
278 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
279 if text is None: text = ""
279 if text is None: text = ""
280 if p1 is None: p1 = self.tip()
280 if p1 is None: p1 = self.tip()
281 if p2 is None: p2 = nullid
281 if p2 is None: p2 = nullid
282
282
283 node = hash(text, p1, p2)
283 node = hash(text, p1, p2)
284
284
285 if node in self.nodemap:
285 if node in self.nodemap:
286 return node
286 return node
287
287
288 n = self.count()
288 n = self.count()
289 t = n - 1
289 t = n - 1
290
290
291 if n:
291 if n:
292 base = self.base(t)
292 base = self.base(t)
293 start = self.start(base)
293 start = self.start(base)
294 end = self.end(t)
294 end = self.end(t)
295 if not d:
295 if not d:
296 prev = self.revision(self.tip())
296 prev = self.revision(self.tip())
297 d = self.diff(prev, text)
297 d = self.diff(prev, text)
298 data = compress(d)
298 data = compress(d)
299 dist = end - start + len(data)
299 dist = end - start + len(data)
300
300
301 # full versions are inserted when the needed deltas
301 # full versions are inserted when the needed deltas
302 # become comparable to the uncompressed text
302 # become comparable to the uncompressed text
303 if not n or dist > len(text) * 2:
303 if not n or dist > len(text) * 2:
304 data = compress(text)
304 data = compress(text)
305 base = n
305 base = n
306 else:
306 else:
307 base = self.base(t)
307 base = self.base(t)
308
308
309 offset = 0
309 offset = 0
310 if t >= 0:
310 if t >= 0:
311 offset = self.end(t)
311 offset = self.end(t)
312
312
313 e = (offset, len(data), base, link, p1, p2, node)
313 e = (offset, len(data), base, link, p1, p2, node)
314
314
315 self.index.append(e)
315 self.index.append(e)
316 self.nodemap[node] = n
316 self.nodemap[node] = n
317 entry = struct.pack(indexformat, *e)
317 entry = struct.pack(indexformat, *e)
318
318
319 transaction.add(self.datafile, e[0])
319 transaction.add(self.datafile, e[0])
320 self.opener(self.datafile, "a").write(data)
320 self.opener(self.datafile, "a").write(data)
321 transaction.add(self.indexfile, n * len(entry))
321 transaction.add(self.indexfile, n * len(entry))
322 self.opener(self.indexfile, "a").write(entry)
322 self.opener(self.indexfile, "a").write(entry)
323
323
324 self.cache = (node, n, text)
324 self.cache = (node, n, text)
325 return node
325 return node
326
326
327 def ancestor(self, a, b):
327 def ancestor(self, a, b):
328 # calculate the distance of every node from root
328 # calculate the distance of every node from root
329 dist = {nullid: 0}
329 dist = {nullid: 0}
330 for i in xrange(self.count()):
330 for i in xrange(self.count()):
331 n = self.node(i)
331 n = self.node(i)
332 p1, p2 = self.parents(n)
332 p1, p2 = self.parents(n)
333 dist[n] = max(dist[p1], dist[p2]) + 1
333 dist[n] = max(dist[p1], dist[p2]) + 1
334
334
335 # traverse ancestors in order of decreasing distance from root
335 # traverse ancestors in order of decreasing distance from root
336 def ancestors(node):
336 def ancestors(node):
337 # we store negative distances because heap returns smallest member
337 # we store negative distances because heap returns smallest member
338 h = [(-dist[node], node)]
338 h = [(-dist[node], node)]
339 seen = {}
339 seen = {}
340 earliest = self.count()
340 earliest = self.count()
341 while h:
341 while h:
342 d, n = heapq.heappop(h)
342 d, n = heapq.heappop(h)
343 if n not in seen:
343 if n not in seen:
344 seen[n] = 1
344 seen[n] = 1
345 r = self.rev(n)
345 r = self.rev(n)
346 yield (-d, r, n)
346 yield (-d, r, n)
347 for p in self.parents(n):
347 for p in self.parents(n):
348 heapq.heappush(h, (-dist[p], p))
348 heapq.heappush(h, (-dist[p], p))
349
349
350 x = ancestors(a)
350 x = ancestors(a)
351 y = ancestors(b)
351 y = ancestors(b)
352 lx = x.next()
352 lx = x.next()
353 ly = y.next()
353 ly = y.next()
354
354
355 # increment each ancestor list until it is closer to root than
355 # increment each ancestor list until it is closer to root than
356 # the other, or they match
356 # the other, or they match
357 while 1:
357 while 1:
358 if lx == ly:
358 if lx == ly:
359 return lx[2]
359 return lx[2]
360 elif lx < ly:
360 elif lx < ly:
361 ly = y.next()
361 ly = y.next()
362 elif lx > ly:
362 elif lx > ly:
363 lx = x.next()
363 lx = x.next()
364
364
365 def group(self, linkmap):
365 def group(self, linkmap):
366 # given a list of changeset revs, return a set of deltas and
366 # given a list of changeset revs, return a set of deltas and
367 # metadata corresponding to nodes. the first delta is
367 # metadata corresponding to nodes. the first delta is
368 # parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
368 # parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
369 # have this parent as it has all history before these
369 # have this parent as it has all history before these
370 # changesets. parent is parent[0]
370 # changesets. parent is parent[0]
371
371
372 revs = []
372 revs = []
373 needed = {}
373 needed = {}
374
374
375 # find file nodes/revs that match changeset revs
375 # find file nodes/revs that match changeset revs
376 for i in xrange(0, self.count()):
376 for i in xrange(0, self.count()):
377 if self.index[i][3] in linkmap:
377 if self.index[i][3] in linkmap:
378 revs.append(i)
378 revs.append(i)
379 needed[i] = 1
379 needed[i] = 1
380
380
381 # if we don't have any revisions touched by these changesets, bail
381 # if we don't have any revisions touched by these changesets, bail
382 if not revs:
382 if not revs:
383 yield struct.pack(">l", 0)
383 yield struct.pack(">l", 0)
384 return
384 return
385
385
386 # add the parent of the first rev
386 # add the parent of the first rev
387 p = self.parents(self.node(revs[0]))[0]
387 p = self.parents(self.node(revs[0]))[0]
388 revs.insert(0, self.rev(p))
388 revs.insert(0, self.rev(p))
389
389
390 # for each delta that isn't contiguous in the log, we need to
390 # for each delta that isn't contiguous in the log, we need to
391 # reconstruct the base, reconstruct the result, and then
391 # reconstruct the base, reconstruct the result, and then
392 # calculate the delta. We also need to do this where we've
392 # calculate the delta. We also need to do this where we've
393 # stored a full version and not a delta
393 # stored a full version and not a delta
394 for i in xrange(0, len(revs) - 1):
394 for i in xrange(0, len(revs) - 1):
395 a, b = revs[i], revs[i + 1]
395 a, b = revs[i], revs[i + 1]
396 if a + 1 != b or self.base(b) == b:
396 if a + 1 != b or self.base(b) == b:
397 for j in xrange(self.base(a), a + 1):
397 for j in xrange(self.base(a), a + 1):
398 needed[j] = 1
398 needed[j] = 1
399 for j in xrange(self.base(b), b + 1):
399 for j in xrange(self.base(b), b + 1):
400 needed[j] = 1
400 needed[j] = 1
401
401
402 # calculate spans to retrieve from datafile
402 # calculate spans to retrieve from datafile
403 needed = needed.keys()
403 needed = needed.keys()
404 needed.sort()
404 needed.sort()
405 spans = []
405 spans = []
406 oo = -1
406 oo = -1
407 ol = 0
407 ol = 0
408 for n in needed:
408 for n in needed:
409 if n < 0: continue
409 if n < 0: continue
410 o = self.start(n)
410 o = self.start(n)
411 l = self.length(n)
411 l = self.length(n)
412 if oo + ol == o: # can we merge with the previous?
412 if oo + ol == o: # can we merge with the previous?
413 nl = spans[-1][2]
413 nl = spans[-1][2]
414 nl.append((n, l))
414 nl.append((n, l))
415 ol += l
415 ol += l
416 spans[-1] = (oo, ol, nl)
416 spans[-1] = (oo, ol, nl)
417 else:
417 else:
418 oo = o
418 oo = o
419 ol = l
419 ol = l
420 spans.append((oo, ol, [(n, l)]))
420 spans.append((oo, ol, [(n, l)]))
421
421
422 # read spans in, divide up chunks
422 # read spans in, divide up chunks
423 chunks = {}
423 chunks = {}
424 for span in spans:
424 for span in spans:
425 # we reopen the file for each span to make http happy for now
425 # we reopen the file for each span to make http happy for now
426 f = self.opener(self.datafile)
426 f = self.opener(self.datafile)
427 f.seek(span[0])
427 f.seek(span[0])
428 data = f.read(span[1])
428 data = f.read(span[1])
429
429
430 # divide up the span
430 # divide up the span
431 pos = 0
431 pos = 0
432 for r, l in span[2]:
432 for r, l in span[2]:
433 chunks[r] = decompress(data[pos: pos + l])
433 chunks[r] = decompress(data[pos: pos + l])
434 pos += l
434 pos += l
435
435
436 # helper to reconstruct intermediate versions
436 # helper to reconstruct intermediate versions
437 def construct(text, base, rev):
437 def construct(text, base, rev):
438 bins = [chunks[r] for r in xrange(base + 1, rev + 1)]
438 bins = [chunks[r] for r in xrange(base + 1, rev + 1)]
439 return mdiff.patches(text, bins)
439 return mdiff.patches(text, bins)
440
440
441 # build deltas
441 # build deltas
442 deltas = []
442 deltas = []
443 for d in xrange(0, len(revs) - 1):
443 for d in xrange(0, len(revs) - 1):
444 a, b = revs[d], revs[d + 1]
444 a, b = revs[d], revs[d + 1]
445 n = self.node(b)
445 n = self.node(b)
446
446
447 # do we need to construct a new delta?
447 # do we need to construct a new delta?
448 if a + 1 != b or self.base(b) == b:
448 if a + 1 != b or self.base(b) == b:
449 if a >= 0:
449 if a >= 0:
450 base = self.base(a)
450 base = self.base(a)
451 ta = chunks[self.base(a)]
451 ta = chunks[self.base(a)]
452 ta = construct(ta, base, a)
452 ta = construct(ta, base, a)
453 else:
453 else:
454 ta = ""
454 ta = ""
455
455
456 base = self.base(b)
456 base = self.base(b)
457 if a > base:
457 if a > base:
458 base = a
458 base = a
459 tb = ta
459 tb = ta
460 else:
460 else:
461 tb = chunks[self.base(b)]
461 tb = chunks[self.base(b)]
462 tb = construct(tb, base, b)
462 tb = construct(tb, base, b)
463 d = self.diff(ta, tb)
463 d = self.diff(ta, tb)
464 else:
464 else:
465 d = chunks[b]
465 d = chunks[b]
466
466
467 p = self.parents(n)
467 p = self.parents(n)
468 meta = n + p[0] + p[1] + linkmap[self.linkrev(n)]
468 meta = n + p[0] + p[1] + linkmap[self.linkrev(n)]
469 l = struct.pack(">l", len(meta) + len(d) + 4)
469 l = struct.pack(">l", len(meta) + len(d) + 4)
470 yield l
470 yield l
471 yield meta
471 yield meta
472 yield d
472 yield d
473
473
474 yield struct.pack(">l", 0)
474 yield struct.pack(">l", 0)
475
475
476 def addgroup(self, revs, linkmapper, transaction, unique = 0):
476 def addgroup(self, revs, linkmapper, transaction, unique=0):
477 # given a set of deltas, add them to the revision log. the
477 # given a set of deltas, add them to the revision log. the
478 # first delta is against its parent, which should be in our
478 # first delta is against its parent, which should be in our
479 # log, the rest are against the previous delta.
479 # log, the rest are against the previous delta.
480
480
481 # track the base of the current delta log
481 # track the base of the current delta log
482 r = self.count()
482 r = self.count()
483 t = r - 1
483 t = r - 1
484 node = nullid
484 node = nullid
485
485
486 base = prev = -1
486 base = prev = -1
487 start = end = measure = 0
487 start = end = measure = 0
488 if r:
488 if r:
489 start = self.start(self.base(t))
489 start = self.start(self.base(t))
490 end = self.end(t)
490 end = self.end(t)
491 measure = self.length(self.base(t))
491 measure = self.length(self.base(t))
492 base = self.base(t)
492 base = self.base(t)
493 prev = self.tip()
493 prev = self.tip()
494
494
495 transaction.add(self.datafile, end)
495 transaction.add(self.datafile, end)
496 transaction.add(self.indexfile, r * struct.calcsize(indexformat))
496 transaction.add(self.indexfile, r * struct.calcsize(indexformat))
497 dfh = self.opener(self.datafile, "a")
497 dfh = self.opener(self.datafile, "a")
498 ifh = self.opener(self.indexfile, "a")
498 ifh = self.opener(self.indexfile, "a")
499
499
500 # loop through our set of deltas
500 # loop through our set of deltas
501 chain = None
501 chain = None
502 for chunk in revs:
502 for chunk in revs:
503 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
503 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
504 link = linkmapper(cs)
504 link = linkmapper(cs)
505 if node in self.nodemap:
505 if node in self.nodemap:
506 # this can happen if two branches make the same change
506 # this can happen if two branches make the same change
507 if unique:
507 if unique:
508 raise "already have %s" % hex(node[:4])
508 raise "already have %s" % hex(node[:4])
509 chain = node
509 chain = node
510 continue
510 continue
511 delta = chunk[80:]
511 delta = chunk[80:]
512
512
513 if not chain:
513 if not chain:
514 # retrieve the parent revision of the delta chain
514 # retrieve the parent revision of the delta chain
515 chain = p1
515 chain = p1
516 if not chain in self.nodemap:
516 if not chain in self.nodemap:
517 raise "unknown base %s" % short(chain[:4])
517 raise "unknown base %s" % short(chain[:4])
518
518
519 # full versions are inserted when the needed deltas become
519 # full versions are inserted when the needed deltas become
520 # comparable to the uncompressed text or when the previous
520 # comparable to the uncompressed text or when the previous
521 # version is not the one we have a delta against. We use
521 # version is not the one we have a delta against. We use
522 # the size of the previous full rev as a proxy for the
522 # the size of the previous full rev as a proxy for the
523 # current size.
523 # current size.
524
524
525 if chain == prev:
525 if chain == prev:
526 cdelta = compress(delta)
526 cdelta = compress(delta)
527
527
528 if chain != prev or (end - start + len(cdelta)) > measure * 2:
528 if chain != prev or (end - start + len(cdelta)) > measure * 2:
529 # flush our writes here so we can read it in revision
529 # flush our writes here so we can read it in revision
530 dfh.flush()
530 dfh.flush()
531 ifh.flush()
531 ifh.flush()
532 text = self.revision(chain)
532 text = self.revision(chain)
533 text = self.patches(text, [delta])
533 text = self.patches(text, [delta])
534 chk = self.addrevision(text, transaction, link, p1, p2)
534 chk = self.addrevision(text, transaction, link, p1, p2)
535 if chk != node:
535 if chk != node:
536 raise "consistency error adding group"
536 raise "consistency error adding group"
537 measure = len(text)
537 measure = len(text)
538 else:
538 else:
539 e = (end, len(cdelta), self.base(t), link, p1, p2, node)
539 e = (end, len(cdelta), self.base(t), link, p1, p2, node)
540 self.index.append(e)
540 self.index.append(e)
541 self.nodemap[node] = r
541 self.nodemap[node] = r
542 dfh.write(cdelta)
542 dfh.write(cdelta)
543 ifh.write(struct.pack(indexformat, *e))
543 ifh.write(struct.pack(indexformat, *e))
544
544
545 t, r, chain, prev = r, r + 1, node, node
545 t, r, chain, prev = r, r + 1, node, node
546 start = self.start(self.base(t))
546 start = self.start(self.base(t))
547 end = self.end(t)
547 end = self.end(t)
548
548
549 dfh.close()
549 dfh.close()
550 ifh.close()
550 ifh.close()
551 return node
551 return node
@@ -1,78 +1,78
1 # transaction.py - simple journalling scheme for mercurial
1 # transaction.py - simple journalling scheme for mercurial
2 #
2 #
3 # This transaction scheme is intended to gracefully handle program
3 # This transaction scheme is intended to gracefully handle program
4 # errors and interruptions. More serious failures like system crashes
4 # errors and interruptions. More serious failures like system crashes
5 # can be recovered with an fsck-like tool. As the whole repository is
5 # can be recovered with an fsck-like tool. As the whole repository is
6 # effectively log-structured, this should amount to simply truncating
6 # effectively log-structured, this should amount to simply truncating
7 # anything that isn't referenced in the changelog.
7 # anything that isn't referenced in the changelog.
8 #
8 #
9 # Copyright 2005 Matt Mackall <mpm@selenic.com>
9 # Copyright 2005 Matt Mackall <mpm@selenic.com>
10 #
10 #
11 # This software may be used and distributed according to the terms
11 # This software may be used and distributed according to the terms
12 # of the GNU General Public License, incorporated herein by reference.
12 # of the GNU General Public License, incorporated herein by reference.
13
13
14 import os
14 import os
15 import util
15 import util
16
16
17 class transaction:
17 class transaction:
18 def __init__(self, report, opener, journal, after = None):
18 def __init__(self, report, opener, journal, after=None):
19 self.journal = None
19 self.journal = None
20
20
21 # abort here if the journal already exists
21 # abort here if the journal already exists
22 if os.path.exists(journal):
22 if os.path.exists(journal):
23 raise "journal already exists - run hg recover"
23 raise "journal already exists - run hg recover"
24
24
25 self.report = report
25 self.report = report
26 self.opener = opener
26 self.opener = opener
27 self.after = after
27 self.after = after
28 self.entries = []
28 self.entries = []
29 self.map = {}
29 self.map = {}
30 self.journal = journal
30 self.journal = journal
31
31
32 self.file = open(self.journal, "w")
32 self.file = open(self.journal, "w")
33
33
34 def __del__(self):
34 def __del__(self):
35 if self.journal:
35 if self.journal:
36 if self.entries: self.abort()
36 if self.entries: self.abort()
37 self.file.close()
37 self.file.close()
38 try: os.unlink(self.journal)
38 try: os.unlink(self.journal)
39 except: pass
39 except: pass
40
40
41 def add(self, file, offset):
41 def add(self, file, offset):
42 if file in self.map: return
42 if file in self.map: return
43 self.entries.append((file, offset))
43 self.entries.append((file, offset))
44 self.map[file] = 1
44 self.map[file] = 1
45 # add enough data to the journal to do the truncate
45 # add enough data to the journal to do the truncate
46 self.file.write("%s\0%d\n" % (file, offset))
46 self.file.write("%s\0%d\n" % (file, offset))
47 self.file.flush()
47 self.file.flush()
48
48
49 def close(self):
49 def close(self):
50 self.file.close()
50 self.file.close()
51 self.entries = []
51 self.entries = []
52 if self.after:
52 if self.after:
53 self.after()
53 self.after()
54 else:
54 else:
55 os.unlink(self.journal)
55 os.unlink(self.journal)
56 self.journal = None
56 self.journal = None
57
57
58 def abort(self):
58 def abort(self):
59 if not self.entries: return
59 if not self.entries: return
60
60
61 self.report("transaction abort!\n")
61 self.report("transaction abort!\n")
62
62
63 for f, o in self.entries:
63 for f, o in self.entries:
64 try:
64 try:
65 self.opener(f, "a").truncate(o)
65 self.opener(f, "a").truncate(o)
66 except:
66 except:
67 self.report("failed to truncate %s\n" % f)
67 self.report("failed to truncate %s\n" % f)
68
68
69 self.entries = []
69 self.entries = []
70
70
71 self.report("rollback completed\n")
71 self.report("rollback completed\n")
72
72
73 def rollback(opener, file):
73 def rollback(opener, file):
74 for l in open(file).readlines():
74 for l in open(file).readlines():
75 f, o = l.split('\0')
75 f, o = l.split('\0')
76 opener(f, "a").truncate(int(o))
76 opener(f, "a").truncate(int(o))
77 os.unlink(file)
77 os.unlink(file)
78
78
@@ -1,128 +1,128
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 import os, ConfigParser
8 import os, ConfigParser
9 from demandload import *
9 from demandload import *
10 demandload(globals(), "re socket sys util")
10 demandload(globals(), "re socket sys util")
11
11
12 class ui:
12 class ui:
13 def __init__(self, verbose=False, debug=False, quiet=False,
13 def __init__(self, verbose=False, debug=False, quiet=False,
14 interactive=True):
14 interactive=True):
15 self.overlay = {}
15 self.overlay = {}
16 self.cdata = ConfigParser.SafeConfigParser()
16 self.cdata = ConfigParser.SafeConfigParser()
17 self.cdata.read([os.path.normpath(hgrc) for hgrc in
17 self.cdata.read([os.path.normpath(hgrc) for hgrc in
18 "/etc/mercurial/hgrc", os.path.expanduser("~/.hgrc")])
18 "/etc/mercurial/hgrc", os.path.expanduser("~/.hgrc")])
19
19
20 self.quiet = self.configbool("ui", "quiet")
20 self.quiet = self.configbool("ui", "quiet")
21 self.verbose = self.configbool("ui", "verbose")
21 self.verbose = self.configbool("ui", "verbose")
22 self.debugflag = self.configbool("ui", "debug")
22 self.debugflag = self.configbool("ui", "debug")
23 self.interactive = self.configbool("ui", "interactive", True)
23 self.interactive = self.configbool("ui", "interactive", True)
24
24
25 self.quiet = (self.quiet or quiet) and not verbose and not debug
25 self.quiet = (self.quiet or quiet) and not verbose and not debug
26 self.verbose = (self.verbose or verbose) or debug
26 self.verbose = (self.verbose or verbose) or debug
27 self.debugflag = (self.debugflag or debug)
27 self.debugflag = (self.debugflag or debug)
28 self.interactive = (self.interactive and interactive)
28 self.interactive = (self.interactive and interactive)
29
29
30 def readconfig(self, fp):
30 def readconfig(self, fp):
31 self.cdata.readfp(fp)
31 self.cdata.readfp(fp)
32
32
33 def setconfig(self, section, name, val):
33 def setconfig(self, section, name, val):
34 self.overlay[(section, name)] = val
34 self.overlay[(section, name)] = val
35
35
36 def config(self, section, name, default=None):
36 def config(self, section, name, default=None):
37 if self.overlay.has_key((section, name)):
37 if self.overlay.has_key((section, name)):
38 return self.overlay[(section, name)]
38 return self.overlay[(section, name)]
39 if self.cdata.has_option(section, name):
39 if self.cdata.has_option(section, name):
40 return self.cdata.get(section, name)
40 return self.cdata.get(section, name)
41 return default
41 return default
42
42
43 def configbool(self, section, name, default=False):
43 def configbool(self, section, name, default=False):
44 if self.overlay.has_key((section, name)):
44 if self.overlay.has_key((section, name)):
45 return self.overlay[(section, name)]
45 return self.overlay[(section, name)]
46 if self.cdata.has_option(section, name):
46 if self.cdata.has_option(section, name):
47 return self.cdata.getboolean(section, name)
47 return self.cdata.getboolean(section, name)
48 return default
48 return default
49
49
50 def configitems(self, section):
50 def configitems(self, section):
51 if self.cdata.has_section(section):
51 if self.cdata.has_section(section):
52 return self.cdata.items(section)
52 return self.cdata.items(section)
53 return []
53 return []
54
54
55 def walkconfig(self):
55 def walkconfig(self):
56 seen = {}
56 seen = {}
57 for (section, name), value in self.overlay.iteritems():
57 for (section, name), value in self.overlay.iteritems():
58 yield section, name, value
58 yield section, name, value
59 seen[section, name] = 1
59 seen[section, name] = 1
60 for section in self.cdata.sections():
60 for section in self.cdata.sections():
61 for name, value in self.cdata.items(section):
61 for name, value in self.cdata.items(section):
62 if (section, name) in seen: continue
62 if (section, name) in seen: continue
63 yield section, name, value.replace('\n', '\\n')
63 yield section, name, value.replace('\n', '\\n')
64 seen[section, name] = 1
64 seen[section, name] = 1
65
65
66 def username(self):
66 def username(self):
67 return (os.environ.get("HGUSER") or
67 return (os.environ.get("HGUSER") or
68 self.config("ui", "username") or
68 self.config("ui", "username") or
69 os.environ.get("EMAIL") or
69 os.environ.get("EMAIL") or
70 (os.environ.get("LOGNAME",
70 (os.environ.get("LOGNAME",
71 os.environ.get("USERNAME", "unknown"))
71 os.environ.get("USERNAME", "unknown"))
72 + '@' + socket.getfqdn()))
72 + '@' + socket.getfqdn()))
73
73
74 def expandpath(self, loc):
74 def expandpath(self, loc):
75 paths = {}
75 paths = {}
76 for name, path in self.configitems("paths"):
76 for name, path in self.configitems("paths"):
77 paths[name] = path
77 paths[name] = path
78
78
79 return paths.get(loc, loc)
79 return paths.get(loc, loc)
80
80
81 def write(self, *args):
81 def write(self, *args):
82 for a in args:
82 for a in args:
83 sys.stdout.write(str(a))
83 sys.stdout.write(str(a))
84
84
85 def write_err(self, *args):
85 def write_err(self, *args):
86 sys.stdout.flush()
86 sys.stdout.flush()
87 for a in args:
87 for a in args:
88 sys.stderr.write(str(a))
88 sys.stderr.write(str(a))
89
89
90 def readline(self):
90 def readline(self):
91 return sys.stdin.readline()[:-1]
91 return sys.stdin.readline()[:-1]
92 def prompt(self, msg, pat, default = "y"):
92 def prompt(self, msg, pat, default="y"):
93 if not self.interactive: return default
93 if not self.interactive: return default
94 while 1:
94 while 1:
95 self.write(msg, " ")
95 self.write(msg, " ")
96 r = self.readline()
96 r = self.readline()
97 if re.match(pat, r):
97 if re.match(pat, r):
98 return r
98 return r
99 else:
99 else:
100 self.write("unrecognized response\n")
100 self.write("unrecognized response\n")
101 def status(self, *msg):
101 def status(self, *msg):
102 if not self.quiet: self.write(*msg)
102 if not self.quiet: self.write(*msg)
103 def warn(self, *msg):
103 def warn(self, *msg):
104 self.write_err(*msg)
104 self.write_err(*msg)
105 def note(self, *msg):
105 def note(self, *msg):
106 if self.verbose: self.write(*msg)
106 if self.verbose: self.write(*msg)
107 def debug(self, *msg):
107 def debug(self, *msg):
108 if self.debugflag: self.write(*msg)
108 if self.debugflag: self.write(*msg)
109 def edit(self, text):
109 def edit(self, text):
110 import tempfile
110 import tempfile
111 (fd, name) = tempfile.mkstemp("hg")
111 (fd, name) = tempfile.mkstemp("hg")
112 f = os.fdopen(fd, "w")
112 f = os.fdopen(fd, "w")
113 f.write(text)
113 f.write(text)
114 f.close()
114 f.close()
115
115
116 editor = (os.environ.get("HGEDITOR") or
116 editor = (os.environ.get("HGEDITOR") or
117 self.config("ui", "editor") or
117 self.config("ui", "editor") or
118 os.environ.get("EDITOR", "vi"))
118 os.environ.get("EDITOR", "vi"))
119
119
120 os.environ["HGUSER"] = self.username()
120 os.environ["HGUSER"] = self.username()
121 util.system("%s %s" % (editor, name), errprefix = "edit failed")
121 util.system("%s %s" % (editor, name), errprefix="edit failed")
122
122
123 t = open(name).read()
123 t = open(name).read()
124 t = re.sub("(?m)^HG:.*\n", "", t)
124 t = re.sub("(?m)^HG:.*\n", "", t)
125
125
126 os.unlink(name)
126 os.unlink(name)
127
127
128 return t
128 return t
@@ -1,288 +1,288
1 # util.py - utility functions and platform specfic implementations
1 # util.py - utility functions and platform specfic implementations
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.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 import os, errno
8 import os, errno
9 from demandload import *
9 from demandload import *
10 demandload(globals(), "re")
10 demandload(globals(), "re")
11
11
12 def binary(s):
12 def binary(s):
13 if s and '\0' in s[:4096]:
13 if s and '\0' in s[:4096]:
14 return True
14 return True
15 return False
15 return False
16
16
17 def unique(g):
17 def unique(g):
18 seen = {}
18 seen = {}
19 for f in g:
19 for f in g:
20 if f not in seen:
20 if f not in seen:
21 seen[f] = 1
21 seen[f] = 1
22 yield f
22 yield f
23
23
24 class Abort(Exception):
24 class Abort(Exception):
25 """Raised if a command needs to print an error and exit."""
25 """Raised if a command needs to print an error and exit."""
26
26
27 def always(fn): return True
27 def always(fn): return True
28 def never(fn): return False
28 def never(fn): return False
29
29
30 def globre(pat, head = '^', tail = '$'):
30 def globre(pat, head='^', tail='$'):
31 "convert a glob pattern into a regexp"
31 "convert a glob pattern into a regexp"
32 i, n = 0, len(pat)
32 i, n = 0, len(pat)
33 res = ''
33 res = ''
34 group = False
34 group = False
35 def peek(): return i < n and pat[i]
35 def peek(): return i < n and pat[i]
36 while i < n:
36 while i < n:
37 c = pat[i]
37 c = pat[i]
38 i = i+1
38 i = i+1
39 if c == '*':
39 if c == '*':
40 if peek() == '*':
40 if peek() == '*':
41 i += 1
41 i += 1
42 res += '.*'
42 res += '.*'
43 else:
43 else:
44 res += '[^/]*'
44 res += '[^/]*'
45 elif c == '?':
45 elif c == '?':
46 res += '.'
46 res += '.'
47 elif c == '[':
47 elif c == '[':
48 j = i
48 j = i
49 if j < n and pat[j] in '!]':
49 if j < n and pat[j] in '!]':
50 j += 1
50 j += 1
51 while j < n and pat[j] != ']':
51 while j < n and pat[j] != ']':
52 j += 1
52 j += 1
53 if j >= n:
53 if j >= n:
54 res += '\\['
54 res += '\\['
55 else:
55 else:
56 stuff = pat[i:j].replace('\\','\\\\')
56 stuff = pat[i:j].replace('\\','\\\\')
57 i = j + 1
57 i = j + 1
58 if stuff[0] == '!':
58 if stuff[0] == '!':
59 stuff = '^' + stuff[1:]
59 stuff = '^' + stuff[1:]
60 elif stuff[0] == '^':
60 elif stuff[0] == '^':
61 stuff = '\\' + stuff
61 stuff = '\\' + stuff
62 res = '%s[%s]' % (res, stuff)
62 res = '%s[%s]' % (res, stuff)
63 elif c == '{':
63 elif c == '{':
64 group = True
64 group = True
65 res += '(?:'
65 res += '(?:'
66 elif c == '}' and group:
66 elif c == '}' and group:
67 res += ')'
67 res += ')'
68 group = False
68 group = False
69 elif c == ',' and group:
69 elif c == ',' and group:
70 res += '|'
70 res += '|'
71 else:
71 else:
72 res += re.escape(c)
72 res += re.escape(c)
73 return head + res + tail
73 return head + res + tail
74
74
75 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
75 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
76
76
77 def pathto(n1, n2):
77 def pathto(n1, n2):
78 '''return the relative path from one place to another.
78 '''return the relative path from one place to another.
79 this returns a path in the form used by the local filesystem, not hg.'''
79 this returns a path in the form used by the local filesystem, not hg.'''
80 if not n1: return localpath(n2)
80 if not n1: return localpath(n2)
81 a, b = n1.split('/'), n2.split('/')
81 a, b = n1.split('/'), n2.split('/')
82 a.reverse(), b.reverse()
82 a.reverse(), b.reverse()
83 while a and b and a[-1] == b[-1]:
83 while a and b and a[-1] == b[-1]:
84 a.pop(), b.pop()
84 a.pop(), b.pop()
85 b.reverse()
85 b.reverse()
86 return os.sep.join((['..'] * len(a)) + b)
86 return os.sep.join((['..'] * len(a)) + b)
87
87
88 def canonpath(repo, cwd, myname):
88 def canonpath(repo, cwd, myname):
89 rootsep = repo.root + os.sep
89 rootsep = repo.root + os.sep
90 name = myname
90 name = myname
91 if not name.startswith(os.sep):
91 if not name.startswith(os.sep):
92 name = os.path.join(repo.root, cwd, name)
92 name = os.path.join(repo.root, cwd, name)
93 name = os.path.normpath(name)
93 name = os.path.normpath(name)
94 if name.startswith(rootsep):
94 if name.startswith(rootsep):
95 return pconvert(name[len(rootsep):])
95 return pconvert(name[len(rootsep):])
96 elif name == repo.root:
96 elif name == repo.root:
97 return ''
97 return ''
98 else:
98 else:
99 raise Abort('%s not under repository root' % myname)
99 raise Abort('%s not under repository root' % myname)
100
100
101 def matcher(repo, cwd, names, inc, exc, head = ''):
101 def matcher(repo, cwd, names, inc, exc, head=''):
102 def patkind(name):
102 def patkind(name):
103 for prefix in 're:', 'glob:', 'path:', 'relpath:':
103 for prefix in 're:', 'glob:', 'path:', 'relpath:':
104 if name.startswith(prefix): return name.split(':', 1)
104 if name.startswith(prefix): return name.split(':', 1)
105 for c in name:
105 for c in name:
106 if c in _globchars: return 'glob', name
106 if c in _globchars: return 'glob', name
107 return 'relpath', name
107 return 'relpath', name
108
108
109 def regex(kind, name, tail):
109 def regex(kind, name, tail):
110 '''convert a pattern into a regular expression'''
110 '''convert a pattern into a regular expression'''
111 if kind == 're':
111 if kind == 're':
112 return name
112 return name
113 elif kind == 'path':
113 elif kind == 'path':
114 return '^' + re.escape(name) + '(?:/|$)'
114 return '^' + re.escape(name) + '(?:/|$)'
115 elif kind == 'relpath':
115 elif kind == 'relpath':
116 return head + re.escape(name) + tail
116 return head + re.escape(name) + tail
117 return head + globre(name, '', tail)
117 return head + globre(name, '', tail)
118
118
119 def matchfn(pats, tail):
119 def matchfn(pats, tail):
120 """build a matching function from a set of patterns"""
120 """build a matching function from a set of patterns"""
121 if pats:
121 if pats:
122 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
122 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
123 return re.compile(pat).match
123 return re.compile(pat).match
124
124
125 def globprefix(pat):
125 def globprefix(pat):
126 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
126 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
127 root = []
127 root = []
128 for p in pat.split(os.sep):
128 for p in pat.split(os.sep):
129 if patkind(p)[0] == 'glob': break
129 if patkind(p)[0] == 'glob': break
130 root.append(p)
130 root.append(p)
131 return '/'.join(root)
131 return '/'.join(root)
132
132
133 pats = []
133 pats = []
134 files = []
134 files = []
135 roots = []
135 roots = []
136 for kind, name in map(patkind, names):
136 for kind, name in map(patkind, names):
137 if kind in ('glob', 'relpath'):
137 if kind in ('glob', 'relpath'):
138 name = canonpath(repo, cwd, name)
138 name = canonpath(repo, cwd, name)
139 if name == '':
139 if name == '':
140 kind, name = 'glob', '**'
140 kind, name = 'glob', '**'
141 if kind in ('glob', 'path', 're'):
141 if kind in ('glob', 'path', 're'):
142 pats.append((kind, name))
142 pats.append((kind, name))
143 if kind == 'glob':
143 if kind == 'glob':
144 root = globprefix(name)
144 root = globprefix(name)
145 if root: roots.append(root)
145 if root: roots.append(root)
146 elif kind == 'relpath':
146 elif kind == 'relpath':
147 files.append((kind, name))
147 files.append((kind, name))
148 roots.append(name)
148 roots.append(name)
149
149
150 patmatch = matchfn(pats, '$') or always
150 patmatch = matchfn(pats, '$') or always
151 filematch = matchfn(files, '(?:/|$)') or always
151 filematch = matchfn(files, '(?:/|$)') or always
152 incmatch = always
152 incmatch = always
153 if inc:
153 if inc:
154 incmatch = matchfn(map(patkind, inc), '(?:/|$)')
154 incmatch = matchfn(map(patkind, inc), '(?:/|$)')
155 excmatch = lambda fn: False
155 excmatch = lambda fn: False
156 if exc:
156 if exc:
157 excmatch = matchfn(map(patkind, exc), '(?:/|$)')
157 excmatch = matchfn(map(patkind, exc), '(?:/|$)')
158
158
159 return (roots,
159 return (roots,
160 lambda fn: (incmatch(fn) and not excmatch(fn) and
160 lambda fn: (incmatch(fn) and not excmatch(fn) and
161 (fn.endswith('/') or
161 (fn.endswith('/') or
162 (not pats and not files) or
162 (not pats and not files) or
163 (pats and patmatch(fn)) or
163 (pats and patmatch(fn)) or
164 (files and filematch(fn)))),
164 (files and filematch(fn)))),
165 (inc or exc or (pats and pats != [('glob', '**')])) and True)
165 (inc or exc or (pats and pats != [('glob', '**')])) and True)
166
166
167 def system(cmd, errprefix=None):
167 def system(cmd, errprefix=None):
168 """execute a shell command that must succeed"""
168 """execute a shell command that must succeed"""
169 rc = os.system(cmd)
169 rc = os.system(cmd)
170 if rc:
170 if rc:
171 errmsg = "%s %s" % (os.path.basename(cmd.split(None, 1)[0]),
171 errmsg = "%s %s" % (os.path.basename(cmd.split(None, 1)[0]),
172 explain_exit(rc)[0])
172 explain_exit(rc)[0])
173 if errprefix:
173 if errprefix:
174 errmsg = "%s: %s" % (errprefix, errmsg)
174 errmsg = "%s: %s" % (errprefix, errmsg)
175 raise Abort(errmsg)
175 raise Abort(errmsg)
176
176
177 def rename(src, dst):
177 def rename(src, dst):
178 try:
178 try:
179 os.rename(src, dst)
179 os.rename(src, dst)
180 except:
180 except:
181 os.unlink(dst)
181 os.unlink(dst)
182 os.rename(src, dst)
182 os.rename(src, dst)
183
183
184 def copytree(src, dst, copyfile):
184 def copytree(src, dst, copyfile):
185 """Copy a directory tree, files are copied using 'copyfile'."""
185 """Copy a directory tree, files are copied using 'copyfile'."""
186 names = os.listdir(src)
186 names = os.listdir(src)
187 os.mkdir(dst)
187 os.mkdir(dst)
188
188
189 for name in names:
189 for name in names:
190 srcname = os.path.join(src, name)
190 srcname = os.path.join(src, name)
191 dstname = os.path.join(dst, name)
191 dstname = os.path.join(dst, name)
192 if os.path.isdir(srcname):
192 if os.path.isdir(srcname):
193 copytree(srcname, dstname, copyfile)
193 copytree(srcname, dstname, copyfile)
194 elif os.path.isfile(srcname):
194 elif os.path.isfile(srcname):
195 copyfile(srcname, dstname)
195 copyfile(srcname, dstname)
196 else:
196 else:
197 pass
197 pass
198
198
199 def _makelock_file(info, pathname):
199 def _makelock_file(info, pathname):
200 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
200 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
201 os.write(ld, info)
201 os.write(ld, info)
202 os.close(ld)
202 os.close(ld)
203
203
204 def _readlock_file(pathname):
204 def _readlock_file(pathname):
205 return file(pathname).read()
205 return file(pathname).read()
206
206
207 # Platfor specific varients
207 # Platfor specific varients
208 if os.name == 'nt':
208 if os.name == 'nt':
209 nulldev = 'NUL:'
209 nulldev = 'NUL:'
210
210
211 def is_exec(f, last):
211 def is_exec(f, last):
212 return last
212 return last
213
213
214 def set_exec(f, mode):
214 def set_exec(f, mode):
215 pass
215 pass
216
216
217 def pconvert(path):
217 def pconvert(path):
218 return path.replace("\\", "/")
218 return path.replace("\\", "/")
219
219
220 def localpath(path):
220 def localpath(path):
221 return path.replace('/', '\\')
221 return path.replace('/', '\\')
222
222
223 def normpath(path):
223 def normpath(path):
224 return pconvert(os.path.normpath(path))
224 return pconvert(os.path.normpath(path))
225
225
226 makelock = _makelock_file
226 makelock = _makelock_file
227 readlock = _readlock_file
227 readlock = _readlock_file
228
228
229 def explain_exit(code):
229 def explain_exit(code):
230 return "exited with status %d" % code, code
230 return "exited with status %d" % code, code
231
231
232 else:
232 else:
233 nulldev = '/dev/null'
233 nulldev = '/dev/null'
234
234
235 def is_exec(f, last):
235 def is_exec(f, last):
236 return (os.stat(f).st_mode & 0100 != 0)
236 return (os.stat(f).st_mode & 0100 != 0)
237
237
238 def set_exec(f, mode):
238 def set_exec(f, mode):
239 s = os.stat(f).st_mode
239 s = os.stat(f).st_mode
240 if (s & 0100 != 0) == mode:
240 if (s & 0100 != 0) == mode:
241 return
241 return
242 if mode:
242 if mode:
243 # Turn on +x for every +r bit when making a file executable
243 # Turn on +x for every +r bit when making a file executable
244 # and obey umask.
244 # and obey umask.
245 umask = os.umask(0)
245 umask = os.umask(0)
246 os.umask(umask)
246 os.umask(umask)
247 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
247 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
248 else:
248 else:
249 os.chmod(f, s & 0666)
249 os.chmod(f, s & 0666)
250
250
251 def pconvert(path):
251 def pconvert(path):
252 return path
252 return path
253
253
254 def localpath(path):
254 def localpath(path):
255 return path
255 return path
256
256
257 normpath = os.path.normpath
257 normpath = os.path.normpath
258
258
259 def makelock(info, pathname):
259 def makelock(info, pathname):
260 try:
260 try:
261 os.symlink(info, pathname)
261 os.symlink(info, pathname)
262 except OSError, why:
262 except OSError, why:
263 if why.errno == errno.EEXIST:
263 if why.errno == errno.EEXIST:
264 raise
264 raise
265 else:
265 else:
266 _makelock_file(info, pathname)
266 _makelock_file(info, pathname)
267
267
268 def readlock(pathname):
268 def readlock(pathname):
269 try:
269 try:
270 return os.readlink(pathname)
270 return os.readlink(pathname)
271 except OSError, why:
271 except OSError, why:
272 if why.errno == errno.EINVAL:
272 if why.errno == errno.EINVAL:
273 return _readlock_file(pathname)
273 return _readlock_file(pathname)
274 else:
274 else:
275 raise
275 raise
276
276
277 def explain_exit(code):
277 def explain_exit(code):
278 """return a 2-tuple (desc, code) describing a process's status"""
278 """return a 2-tuple (desc, code) describing a process's status"""
279 if os.WIFEXITED(code):
279 if os.WIFEXITED(code):
280 val = os.WEXITSTATUS(code)
280 val = os.WEXITSTATUS(code)
281 return "exited with status %d" % val, val
281 return "exited with status %d" % val, val
282 elif os.WIFSIGNALED(code):
282 elif os.WIFSIGNALED(code):
283 val = os.WTERMSIG(code)
283 val = os.WTERMSIG(code)
284 return "killed by signal %d" % val, val
284 return "killed by signal %d" % val, val
285 elif os.WIFSTOPPED(code):
285 elif os.WIFSTOPPED(code):
286 val = os.WSTOPSIG(code)
286 val = os.WSTOPSIG(code)
287 return "stopped by signal %d" % val, val
287 return "stopped by signal %d" % val, val
288 raise ValueError("invalid exit code")
288 raise ValueError("invalid exit code")
General Comments 0
You need to be logged in to leave comments. Login now