##// END OF EJS Templates
Protocol switch from using generators to stream-like objects....
Matt Mackall -
r635:85e2209d default
parent child Browse files
Show More
@@ -1,1293 +1,1285 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from demandload import *
8 from demandload import *
9 demandload(globals(), "os re sys signal")
9 demandload(globals(), "os re sys signal")
10 demandload(globals(), "fancyopts ui hg util")
10 demandload(globals(), "fancyopts ui hg util")
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")
12 demandload(globals(), "errno socket version struct")
13
13
14 class UnknownCommand(Exception): pass
14 class UnknownCommand(Exception): pass
15
15
16 def filterfiles(filters, files):
16 def filterfiles(filters, files):
17 l = [ x for x in files if x in filters ]
17 l = [ x for x in files if x in filters ]
18
18
19 for t in filters:
19 for t in filters:
20 if t and t[-1] != "/": t += "/"
20 if t and t[-1] != "/": t += "/"
21 l += [ x for x in files if x.startswith(t) ]
21 l += [ x for x in files if x.startswith(t) ]
22 return l
22 return l
23
23
24 def relfilter(repo, files):
24 def relfilter(repo, files):
25 cwd = repo.getcwd()
25 cwd = repo.getcwd()
26 if cwd:
26 if cwd:
27 return filterfiles([util.pconvert(cwd)], files)
27 return filterfiles([util.pconvert(cwd)], files)
28 return files
28 return files
29
29
30 def relpath(repo, args):
30 def relpath(repo, args):
31 cwd = repo.getcwd()
31 cwd = repo.getcwd()
32 if cwd:
32 if cwd:
33 return [ util.pconvert(os.path.normpath(os.path.join(cwd, x))) for x in args ]
33 return [ util.pconvert(os.path.normpath(os.path.join(cwd, x))) for x in args ]
34 return args
34 return args
35
35
36 revrangesep = ':'
36 revrangesep = ':'
37
37
38 def revrange(ui, repo, revs = [], revlog = None):
38 def revrange(ui, repo, revs = [], revlog = None):
39 if revlog is None:
39 if revlog is None:
40 revlog = repo.changelog
40 revlog = repo.changelog
41 revcount = revlog.count()
41 revcount = revlog.count()
42 def fix(val, defval):
42 def fix(val, defval):
43 if not val: return defval
43 if not val: return defval
44 try:
44 try:
45 num = int(val)
45 num = int(val)
46 if str(num) != val: raise ValueError
46 if str(num) != val: raise ValueError
47 if num < 0: num += revcount
47 if num < 0: num += revcount
48 if not (0 <= num < revcount):
48 if not (0 <= num < revcount):
49 raise ValueError
49 raise ValueError
50 except ValueError:
50 except ValueError:
51 try:
51 try:
52 num = repo.changelog.rev(repo.lookup(val))
52 num = repo.changelog.rev(repo.lookup(val))
53 except KeyError:
53 except KeyError:
54 try:
54 try:
55 num = revlog.rev(revlog.lookup(val))
55 num = revlog.rev(revlog.lookup(val))
56 except KeyError:
56 except KeyError:
57 ui.warn('abort: invalid revision identifier %s\n' % val)
57 ui.warn('abort: invalid revision identifier %s\n' % val)
58 sys.exit(1)
58 sys.exit(1)
59 return num
59 return num
60 for spec in revs:
60 for spec in revs:
61 if spec.find(revrangesep) >= 0:
61 if spec.find(revrangesep) >= 0:
62 start, end = spec.split(revrangesep, 1)
62 start, end = spec.split(revrangesep, 1)
63 start = fix(start, 0)
63 start = fix(start, 0)
64 end = fix(end, revcount - 1)
64 end = fix(end, revcount - 1)
65 if end > start:
65 if end > start:
66 end += 1
66 end += 1
67 step = 1
67 step = 1
68 else:
68 else:
69 end -= 1
69 end -= 1
70 step = -1
70 step = -1
71 for rev in xrange(start, end, step):
71 for rev in xrange(start, end, step):
72 yield str(rev)
72 yield str(rev)
73 else:
73 else:
74 yield spec
74 yield spec
75
75
76 def make_filename(repo, r, pat, node=None,
76 def make_filename(repo, r, pat, node=None,
77 total=None, seqno=None, revwidth=None):
77 total=None, seqno=None, revwidth=None):
78 node_expander = {
78 node_expander = {
79 'H': lambda: hg.hex(node),
79 'H': lambda: hg.hex(node),
80 'R': lambda: str(r.rev(node)),
80 'R': lambda: str(r.rev(node)),
81 'h': lambda: hg.short(node),
81 'h': lambda: hg.short(node),
82 }
82 }
83 expander = {
83 expander = {
84 '%': lambda: '%',
84 '%': lambda: '%',
85 'b': lambda: os.path.basename(repo.root),
85 'b': lambda: os.path.basename(repo.root),
86 }
86 }
87
87
88 if node: expander.update(node_expander)
88 if node: expander.update(node_expander)
89 if node and revwidth is not None:
89 if node and revwidth is not None:
90 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
90 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
91 if total is not None: expander['N'] = lambda: str(total)
91 if total is not None: expander['N'] = lambda: str(total)
92 if seqno is not None: expander['n'] = lambda: str(seqno)
92 if seqno is not None: expander['n'] = lambda: str(seqno)
93 if total is not None and seqno is not None:
93 if total is not None and seqno is not None:
94 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
94 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
95
95
96 newname = []
96 newname = []
97 patlen = len(pat)
97 patlen = len(pat)
98 i = 0
98 i = 0
99 while i < patlen:
99 while i < patlen:
100 c = pat[i]
100 c = pat[i]
101 if c == '%':
101 if c == '%':
102 i += 1
102 i += 1
103 c = pat[i]
103 c = pat[i]
104 c = expander[c]()
104 c = expander[c]()
105 newname.append(c)
105 newname.append(c)
106 i += 1
106 i += 1
107 return ''.join(newname)
107 return ''.join(newname)
108
108
109 def dodiff(fp, ui, repo, files = None, node1 = None, node2 = None):
109 def dodiff(fp, ui, repo, files = None, node1 = None, node2 = None):
110 def date(c):
110 def date(c):
111 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
111 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
112
112
113 (c, a, d, u) = repo.changes(node1, node2, files)
113 (c, a, d, u) = repo.changes(node1, node2, files)
114 if files:
114 if files:
115 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
115 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
116
116
117 if not c and not a and not d:
117 if not c and not a and not d:
118 return
118 return
119
119
120 if node2:
120 if node2:
121 change = repo.changelog.read(node2)
121 change = repo.changelog.read(node2)
122 mmap2 = repo.manifest.read(change[0])
122 mmap2 = repo.manifest.read(change[0])
123 def read(f): return repo.file(f).read(mmap2[f])
123 def read(f): return repo.file(f).read(mmap2[f])
124 date2 = date(change)
124 date2 = date(change)
125 else:
125 else:
126 date2 = time.asctime()
126 date2 = time.asctime()
127 if not node1:
127 if not node1:
128 node1 = repo.dirstate.parents()[0]
128 node1 = repo.dirstate.parents()[0]
129 def read(f): return repo.wfile(f).read()
129 def read(f): return repo.wfile(f).read()
130
130
131 if ui.quiet:
131 if ui.quiet:
132 r = None
132 r = None
133 else:
133 else:
134 hexfunc = ui.verbose and hg.hex or hg.short
134 hexfunc = ui.verbose and hg.hex or hg.short
135 r = [hexfunc(node) for node in [node1, node2] if node]
135 r = [hexfunc(node) for node in [node1, node2] if node]
136
136
137 change = repo.changelog.read(node1)
137 change = repo.changelog.read(node1)
138 mmap = repo.manifest.read(change[0])
138 mmap = repo.manifest.read(change[0])
139 date1 = date(change)
139 date1 = date(change)
140
140
141 for f in c:
141 for f in c:
142 to = None
142 to = None
143 if f in mmap:
143 if f in mmap:
144 to = repo.file(f).read(mmap[f])
144 to = repo.file(f).read(mmap[f])
145 tn = read(f)
145 tn = read(f)
146 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r))
146 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r))
147 for f in a:
147 for f in a:
148 to = None
148 to = None
149 tn = read(f)
149 tn = read(f)
150 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r))
150 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r))
151 for f in d:
151 for f in d:
152 to = repo.file(f).read(mmap[f])
152 to = repo.file(f).read(mmap[f])
153 tn = None
153 tn = None
154 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r))
154 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r))
155
155
156 def show_changeset(ui, repo, rev=0, changenode=None, filelog=None):
156 def show_changeset(ui, repo, rev=0, changenode=None, filelog=None):
157 """show a single changeset or file revision"""
157 """show a single changeset or file revision"""
158 changelog = repo.changelog
158 changelog = repo.changelog
159 if filelog:
159 if filelog:
160 log = filelog
160 log = filelog
161 filerev = rev
161 filerev = rev
162 node = filenode = filelog.node(filerev)
162 node = filenode = filelog.node(filerev)
163 changerev = filelog.linkrev(filenode)
163 changerev = filelog.linkrev(filenode)
164 changenode = changenode or changelog.node(changerev)
164 changenode = changenode or changelog.node(changerev)
165 else:
165 else:
166 log = changelog
166 log = changelog
167 changerev = rev
167 changerev = rev
168 if changenode is None:
168 if changenode is None:
169 changenode = changelog.node(changerev)
169 changenode = changelog.node(changerev)
170 elif not changerev:
170 elif not changerev:
171 rev = changerev = changelog.rev(changenode)
171 rev = changerev = changelog.rev(changenode)
172 node = changenode
172 node = changenode
173
173
174 if ui.quiet:
174 if ui.quiet:
175 ui.write("%d:%s\n" % (rev, hg.hex(node)))
175 ui.write("%d:%s\n" % (rev, hg.hex(node)))
176 return
176 return
177
177
178 changes = changelog.read(changenode)
178 changes = changelog.read(changenode)
179
179
180 parents = [(log.rev(parent), hg.hex(parent))
180 parents = [(log.rev(parent), hg.hex(parent))
181 for parent in log.parents(node)
181 for parent in log.parents(node)
182 if ui.debugflag or parent != hg.nullid]
182 if ui.debugflag or parent != hg.nullid]
183 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
183 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
184 parents = []
184 parents = []
185
185
186 if filelog:
186 if filelog:
187 ui.write("revision: %d:%s\n" % (filerev, hg.hex(filenode)))
187 ui.write("revision: %d:%s\n" % (filerev, hg.hex(filenode)))
188 for parent in parents:
188 for parent in parents:
189 ui.write("parent: %d:%s\n" % parent)
189 ui.write("parent: %d:%s\n" % parent)
190 ui.status("changeset: %d:%s\n" % (changerev, hg.hex(changenode)))
190 ui.status("changeset: %d:%s\n" % (changerev, hg.hex(changenode)))
191 else:
191 else:
192 ui.write("changeset: %d:%s\n" % (changerev, hg.hex(changenode)))
192 ui.write("changeset: %d:%s\n" % (changerev, hg.hex(changenode)))
193 for tag in repo.nodetags(changenode):
193 for tag in repo.nodetags(changenode):
194 ui.status("tag: %s\n" % tag)
194 ui.status("tag: %s\n" % tag)
195 for parent in parents:
195 for parent in parents:
196 ui.write("parent: %d:%s\n" % parent)
196 ui.write("parent: %d:%s\n" % parent)
197 ui.note("manifest: %d:%s\n" % (repo.manifest.rev(changes[0]),
197 ui.note("manifest: %d:%s\n" % (repo.manifest.rev(changes[0]),
198 hg.hex(changes[0])))
198 hg.hex(changes[0])))
199 ui.status("user: %s\n" % changes[1])
199 ui.status("user: %s\n" % changes[1])
200 ui.status("date: %s\n" % time.asctime(
200 ui.status("date: %s\n" % time.asctime(
201 time.localtime(float(changes[2].split(' ')[0]))))
201 time.localtime(float(changes[2].split(' ')[0]))))
202 if ui.debugflag:
202 if ui.debugflag:
203 files = repo.changes(changelog.parents(changenode)[0], changenode)
203 files = repo.changes(changelog.parents(changenode)[0], changenode)
204 for key, value in zip(["files:", "files+:", "files-:"], files):
204 for key, value in zip(["files:", "files+:", "files-:"], files):
205 if value:
205 if value:
206 ui.note("%-12s %s\n" % (key, " ".join(value)))
206 ui.note("%-12s %s\n" % (key, " ".join(value)))
207 else:
207 else:
208 ui.note("files: %s\n" % " ".join(changes[3]))
208 ui.note("files: %s\n" % " ".join(changes[3]))
209 description = changes[4].strip()
209 description = changes[4].strip()
210 if description:
210 if description:
211 if ui.verbose:
211 if ui.verbose:
212 ui.status("description:\n")
212 ui.status("description:\n")
213 ui.status(description)
213 ui.status(description)
214 ui.status("\n\n")
214 ui.status("\n\n")
215 else:
215 else:
216 ui.status("summary: %s\n" % description.splitlines()[0])
216 ui.status("summary: %s\n" % description.splitlines()[0])
217 ui.status("\n")
217 ui.status("\n")
218
218
219 def show_version(ui):
219 def show_version(ui):
220 """output version and copyright information"""
220 """output version and copyright information"""
221 ui.write("Mercurial version %s\n" % version.get_version())
221 ui.write("Mercurial version %s\n" % version.get_version())
222 ui.status(
222 ui.status(
223 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
223 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
224 "This is free software; see the source for copying conditions. "
224 "This is free software; see the source for copying conditions. "
225 "There is NO\nwarranty; "
225 "There is NO\nwarranty; "
226 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
226 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
227 )
227 )
228
228
229 def help(ui, cmd=None):
229 def help(ui, cmd=None):
230 '''show help for a given command or all commands'''
230 '''show help for a given command or all commands'''
231 if cmd:
231 if cmd:
232 try:
232 try:
233 i = find(cmd)
233 i = find(cmd)
234 ui.write("%s\n\n" % i[2])
234 ui.write("%s\n\n" % i[2])
235
235
236 if i[1]:
236 if i[1]:
237 for s, l, d, c in i[1]:
237 for s, l, d, c in i[1]:
238 opt=' '
238 opt=' '
239 if s: opt = opt + '-' + s + ' '
239 if s: opt = opt + '-' + s + ' '
240 if l: opt = opt + '--' + l + ' '
240 if l: opt = opt + '--' + l + ' '
241 if d: opt = opt + '(' + str(d) + ')'
241 if d: opt = opt + '(' + str(d) + ')'
242 ui.write(opt, "\n")
242 ui.write(opt, "\n")
243 if c: ui.write(' %s\n' % c)
243 if c: ui.write(' %s\n' % c)
244 ui.write("\n")
244 ui.write("\n")
245
245
246 ui.write(i[0].__doc__, "\n")
246 ui.write(i[0].__doc__, "\n")
247 except UnknownCommand:
247 except UnknownCommand:
248 ui.warn("hg: unknown command %s\n" % cmd)
248 ui.warn("hg: unknown command %s\n" % cmd)
249 sys.exit(0)
249 sys.exit(0)
250 else:
250 else:
251 if ui.verbose:
251 if ui.verbose:
252 show_version(ui)
252 show_version(ui)
253 ui.write('\n')
253 ui.write('\n')
254 if ui.verbose:
254 if ui.verbose:
255 ui.write('hg commands:\n\n')
255 ui.write('hg commands:\n\n')
256 else:
256 else:
257 ui.write('basic hg commands (use "hg help -v" for more):\n\n')
257 ui.write('basic hg commands (use "hg help -v" for more):\n\n')
258
258
259 h = {}
259 h = {}
260 for c, e in table.items():
260 for c, e in table.items():
261 f = c.split("|")[0]
261 f = c.split("|")[0]
262 if not ui.verbose and not f.startswith("^"):
262 if not ui.verbose and not f.startswith("^"):
263 continue
263 continue
264 if not ui.debugflag and f.startswith("debug"):
264 if not ui.debugflag and f.startswith("debug"):
265 continue
265 continue
266 f = f.lstrip("^")
266 f = f.lstrip("^")
267 d = ""
267 d = ""
268 if e[0].__doc__:
268 if e[0].__doc__:
269 d = e[0].__doc__.splitlines(0)[0].rstrip()
269 d = e[0].__doc__.splitlines(0)[0].rstrip()
270 h[f] = d
270 h[f] = d
271
271
272 fns = h.keys()
272 fns = h.keys()
273 fns.sort()
273 fns.sort()
274 m = max(map(len, fns))
274 m = max(map(len, fns))
275 for f in fns:
275 for f in fns:
276 ui.write(' %-*s %s\n' % (m, f, h[f]))
276 ui.write(' %-*s %s\n' % (m, f, h[f]))
277
277
278 # Commands start here, listed alphabetically
278 # Commands start here, listed alphabetically
279
279
280 def add(ui, repo, file, *files):
280 def add(ui, repo, file, *files):
281 '''add the specified files on the next commit'''
281 '''add the specified files on the next commit'''
282 repo.add(relpath(repo, (file,) + files))
282 repo.add(relpath(repo, (file,) + files))
283
283
284 def addremove(ui, repo, *files):
284 def addremove(ui, repo, *files):
285 """add all new files, delete all missing files"""
285 """add all new files, delete all missing files"""
286 if files:
286 if files:
287 files = relpath(repo, files)
287 files = relpath(repo, files)
288 d = []
288 d = []
289 u = []
289 u = []
290 for f in files:
290 for f in files:
291 p = repo.wjoin(f)
291 p = repo.wjoin(f)
292 s = repo.dirstate.state(f)
292 s = repo.dirstate.state(f)
293 isfile = os.path.isfile(p)
293 isfile = os.path.isfile(p)
294 if s != 'r' and not isfile:
294 if s != 'r' and not isfile:
295 d.append(f)
295 d.append(f)
296 elif s not in 'nmai' and isfile:
296 elif s not in 'nmai' and isfile:
297 u.append(f)
297 u.append(f)
298 else:
298 else:
299 (c, a, d, u) = repo.changes(None, None)
299 (c, a, d, u) = repo.changes(None, None)
300 repo.add(u)
300 repo.add(u)
301 repo.remove(d)
301 repo.remove(d)
302
302
303 def annotate(u, repo, file, *files, **ops):
303 def annotate(u, repo, file, *files, **ops):
304 """show changeset information per file line"""
304 """show changeset information per file line"""
305 def getnode(rev):
305 def getnode(rev):
306 return hg.short(repo.changelog.node(rev))
306 return hg.short(repo.changelog.node(rev))
307
307
308 def getname(rev):
308 def getname(rev):
309 try:
309 try:
310 return bcache[rev]
310 return bcache[rev]
311 except KeyError:
311 except KeyError:
312 cl = repo.changelog.read(repo.changelog.node(rev))
312 cl = repo.changelog.read(repo.changelog.node(rev))
313 name = cl[1]
313 name = cl[1]
314 f = name.find('@')
314 f = name.find('@')
315 if f >= 0:
315 if f >= 0:
316 name = name[:f]
316 name = name[:f]
317 f = name.find('<')
317 f = name.find('<')
318 if f >= 0:
318 if f >= 0:
319 name = name[f+1:]
319 name = name[f+1:]
320 bcache[rev] = name
320 bcache[rev] = name
321 return name
321 return name
322
322
323 bcache = {}
323 bcache = {}
324 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
324 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
325 if not ops['user'] and not ops['changeset']:
325 if not ops['user'] and not ops['changeset']:
326 ops['number'] = 1
326 ops['number'] = 1
327
327
328 node = repo.dirstate.parents()[0]
328 node = repo.dirstate.parents()[0]
329 if ops['revision']:
329 if ops['revision']:
330 node = repo.changelog.lookup(ops['revision'])
330 node = repo.changelog.lookup(ops['revision'])
331 change = repo.changelog.read(node)
331 change = repo.changelog.read(node)
332 mmap = repo.manifest.read(change[0])
332 mmap = repo.manifest.read(change[0])
333 for f in relpath(repo, (file,) + files):
333 for f in relpath(repo, (file,) + files):
334 lines = repo.file(f).annotate(mmap[f])
334 lines = repo.file(f).annotate(mmap[f])
335 pieces = []
335 pieces = []
336
336
337 for o, f in opmap:
337 for o, f in opmap:
338 if ops[o]:
338 if ops[o]:
339 l = [ f(n) for n,t in lines ]
339 l = [ f(n) for n,t in lines ]
340 m = max(map(len, l))
340 m = max(map(len, l))
341 pieces.append([ "%*s" % (m, x) for x in l])
341 pieces.append([ "%*s" % (m, x) for x in l])
342
342
343 for p,l in zip(zip(*pieces), lines):
343 for p,l in zip(zip(*pieces), lines):
344 u.write(" ".join(p) + ": " + l[1])
344 u.write(" ".join(p) + ": " + l[1])
345
345
346 def cat(ui, repo, file, rev = [], **opts):
346 def cat(ui, repo, file, rev = [], **opts):
347 """output the latest or given revision of a file"""
347 """output the latest or given revision of a file"""
348 r = repo.file(relpath(repo, [file])[0])
348 r = repo.file(relpath(repo, [file])[0])
349 n = r.tip()
349 n = r.tip()
350 if rev: n = r.lookup(rev)
350 if rev: n = r.lookup(rev)
351 if opts['output'] and opts['output'] != '-':
351 if opts['output'] and opts['output'] != '-':
352 try:
352 try:
353 outname = make_filename(repo, r, opts['output'], node=n)
353 outname = make_filename(repo, r, opts['output'], node=n)
354 fp = open(outname, 'wb')
354 fp = open(outname, 'wb')
355 except KeyError, inst:
355 except KeyError, inst:
356 ui.warn("error: invlaid format spec '%%%s' in output file name\n" %
356 ui.warn("error: invlaid format spec '%%%s' in output file name\n" %
357 inst.args[0])
357 inst.args[0])
358 sys.exit(1);
358 sys.exit(1);
359 else:
359 else:
360 fp = sys.stdout
360 fp = sys.stdout
361 fp.write(r.read(n))
361 fp.write(r.read(n))
362
362
363 def clone(ui, source, dest = None, **opts):
363 def clone(ui, source, dest = None, **opts):
364 """make a copy of an existing repository"""
364 """make a copy of an existing repository"""
365 if dest is None:
365 if dest is None:
366 dest = os.path.basename(os.path.normpath(source))
366 dest = os.path.basename(os.path.normpath(source))
367
367
368 if os.path.exists(dest):
368 if os.path.exists(dest):
369 ui.warn("abort: destination '%s' already exists\n" % dest)
369 ui.warn("abort: destination '%s' already exists\n" % dest)
370 return 1
370 return 1
371
371
372 class dircleanup:
372 class dircleanup:
373 def __init__(self, dir):
373 def __init__(self, dir):
374 import shutil
374 import shutil
375 self.rmtree = shutil.rmtree
375 self.rmtree = shutil.rmtree
376 self.dir = dir
376 self.dir = dir
377 os.mkdir(dir)
377 os.mkdir(dir)
378 def close(self):
378 def close(self):
379 self.dir = None
379 self.dir = None
380 def __del__(self):
380 def __del__(self):
381 if self.dir:
381 if self.dir:
382 self.rmtree(self.dir, True)
382 self.rmtree(self.dir, True)
383
383
384 d = dircleanup(dest)
384 d = dircleanup(dest)
385 link = 0
385 link = 0
386 abspath = source
386 abspath = source
387 source = ui.expandpath(source)
387 source = ui.expandpath(source)
388 other = hg.repository(ui, source)
388 other = hg.repository(ui, source)
389
389
390 if other.dev() != -1 and os.stat(dest).st_dev == other.dev():
390 if other.dev() != -1 and os.stat(dest).st_dev == other.dev():
391 ui.status("cloning by hardlink\n")
391 ui.status("cloning by hardlink\n")
392 util.system("cp -al '%s'/.hg '%s'/.hg" % (source, dest))
392 util.system("cp -al '%s'/.hg '%s'/.hg" % (source, dest))
393 try:
393 try:
394 os.remove(os.path.join(dest, ".hg", "dirstate"))
394 os.remove(os.path.join(dest, ".hg", "dirstate"))
395 except: pass
395 except: pass
396
396
397 repo = hg.repository(ui, dest)
397 repo = hg.repository(ui, dest)
398
398
399 else:
399 else:
400 repo = hg.repository(ui, dest, create=1)
400 repo = hg.repository(ui, dest, create=1)
401 repo.pull(other)
401 repo.pull(other)
402
402
403 f = repo.opener("hgrc", "w")
403 f = repo.opener("hgrc", "w")
404 f.write("[paths]\n")
404 f.write("[paths]\n")
405 f.write("default = %s\n" % abspath)
405 f.write("default = %s\n" % abspath)
406
406
407 if not opts['noupdate']:
407 if not opts['noupdate']:
408 update(ui, repo)
408 update(ui, repo)
409
409
410 d.close()
410 d.close()
411
411
412 def commit(ui, repo, *files, **opts):
412 def commit(ui, repo, *files, **opts):
413 """commit the specified files or all outstanding changes"""
413 """commit the specified files or all outstanding changes"""
414 text = opts['text']
414 text = opts['text']
415 if not text and opts['logfile']:
415 if not text and opts['logfile']:
416 try: text = open(opts['logfile']).read()
416 try: text = open(opts['logfile']).read()
417 except IOError: pass
417 except IOError: pass
418
418
419 if opts['addremove']:
419 if opts['addremove']:
420 addremove(ui, repo, *files)
420 addremove(ui, repo, *files)
421 repo.commit(relpath(repo, files), text, opts['user'], opts['date'])
421 repo.commit(relpath(repo, files), text, opts['user'], opts['date'])
422
422
423 def copy(ui, repo, source, dest):
423 def copy(ui, repo, source, dest):
424 """mark a file as copied or renamed for the next commit"""
424 """mark a file as copied or renamed for the next commit"""
425 return repo.copy(*relpath(repo, (source, dest)))
425 return repo.copy(*relpath(repo, (source, dest)))
426
426
427 def debugcheckstate(ui, repo):
427 def debugcheckstate(ui, repo):
428 """validate the correctness of the current dirstate"""
428 """validate the correctness of the current dirstate"""
429 parent1, parent2 = repo.dirstate.parents()
429 parent1, parent2 = repo.dirstate.parents()
430 repo.dirstate.read()
430 repo.dirstate.read()
431 dc = repo.dirstate.map
431 dc = repo.dirstate.map
432 keys = dc.keys()
432 keys = dc.keys()
433 keys.sort()
433 keys.sort()
434 m1n = repo.changelog.read(parent1)[0]
434 m1n = repo.changelog.read(parent1)[0]
435 m2n = repo.changelog.read(parent2)[0]
435 m2n = repo.changelog.read(parent2)[0]
436 m1 = repo.manifest.read(m1n)
436 m1 = repo.manifest.read(m1n)
437 m2 = repo.manifest.read(m2n)
437 m2 = repo.manifest.read(m2n)
438 errors = 0
438 errors = 0
439 for f in dc:
439 for f in dc:
440 state = repo.dirstate.state(f)
440 state = repo.dirstate.state(f)
441 if state in "nr" and f not in m1:
441 if state in "nr" and f not in m1:
442 ui.warn("%s in state %s, but not in manifest1\n" % (f, state))
442 ui.warn("%s in state %s, but not in manifest1\n" % (f, state))
443 errors += 1
443 errors += 1
444 if state in "a" and f in m1:
444 if state in "a" and f in m1:
445 ui.warn("%s in state %s, but also in manifest1\n" % (f, state))
445 ui.warn("%s in state %s, but also in manifest1\n" % (f, state))
446 errors += 1
446 errors += 1
447 if state in "m" and f not in m1 and f not in m2:
447 if state in "m" and f not in m1 and f not in m2:
448 ui.warn("%s in state %s, but not in either manifest\n" %
448 ui.warn("%s in state %s, but not in either manifest\n" %
449 (f, state))
449 (f, state))
450 errors += 1
450 errors += 1
451 for f in m1:
451 for f in m1:
452 state = repo.dirstate.state(f)
452 state = repo.dirstate.state(f)
453 if state not in "nrm":
453 if state not in "nrm":
454 ui.warn("%s in manifest1, but listed as state %s" % (f, state))
454 ui.warn("%s in manifest1, but listed as state %s" % (f, state))
455 errors += 1
455 errors += 1
456 if errors:
456 if errors:
457 ui.warn(".hg/dirstate inconsistent with current parent's manifest\n")
457 ui.warn(".hg/dirstate inconsistent with current parent's manifest\n")
458 sys.exit(1)
458 sys.exit(1)
459
459
460 def debugstate(ui, repo):
460 def debugstate(ui, repo):
461 """show the contents of the current dirstate"""
461 """show the contents of the current dirstate"""
462 repo.dirstate.read()
462 repo.dirstate.read()
463 dc = repo.dirstate.map
463 dc = repo.dirstate.map
464 keys = dc.keys()
464 keys = dc.keys()
465 keys.sort()
465 keys.sort()
466 for file in keys:
466 for file in keys:
467 ui.write("%c %s\n" % (dc[file][0], file))
467 ui.write("%c %s\n" % (dc[file][0], file))
468
468
469 def debugindex(ui, file):
469 def debugindex(ui, file):
470 """dump the contents of an index file"""
470 """dump the contents of an index file"""
471 r = hg.revlog(hg.opener(""), file, "")
471 r = hg.revlog(hg.opener(""), file, "")
472 ui.write(" rev offset length base linkrev" +
472 ui.write(" rev offset length base linkrev" +
473 " p1 p2 nodeid\n")
473 " p1 p2 nodeid\n")
474 for i in range(r.count()):
474 for i in range(r.count()):
475 e = r.index[i]
475 e = r.index[i]
476 ui.write("% 6d % 9d % 7d % 6d % 7d %s.. %s.. %s..\n" % (
476 ui.write("% 6d % 9d % 7d % 6d % 7d %s.. %s.. %s..\n" % (
477 i, e[0], e[1], e[2], e[3],
477 i, e[0], e[1], e[2], e[3],
478 hg.hex(e[4][:5]), hg.hex(e[5][:5]), hg.hex(e[6][:5])))
478 hg.hex(e[4][:5]), hg.hex(e[5][:5]), hg.hex(e[6][:5])))
479
479
480 def debugindexdot(ui, file):
480 def debugindexdot(ui, file):
481 """dump an index DAG as a .dot file"""
481 """dump an index DAG as a .dot file"""
482 r = hg.revlog(hg.opener(""), file, "")
482 r = hg.revlog(hg.opener(""), file, "")
483 ui.write("digraph G {\n")
483 ui.write("digraph G {\n")
484 for i in range(r.count()):
484 for i in range(r.count()):
485 e = r.index[i]
485 e = r.index[i]
486 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
486 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
487 if e[5] != hg.nullid:
487 if e[5] != hg.nullid:
488 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
488 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
489 ui.write("}\n")
489 ui.write("}\n")
490
490
491 def diff(ui, repo, *files, **opts):
491 def diff(ui, repo, *files, **opts):
492 """diff working directory (or selected files)"""
492 """diff working directory (or selected files)"""
493 revs = []
493 revs = []
494 if opts['rev']:
494 if opts['rev']:
495 revs = map(lambda x: repo.lookup(x), opts['rev'])
495 revs = map(lambda x: repo.lookup(x), opts['rev'])
496
496
497 if len(revs) > 2:
497 if len(revs) > 2:
498 ui.warn("too many revisions to diff\n")
498 ui.warn("too many revisions to diff\n")
499 sys.exit(1)
499 sys.exit(1)
500
500
501 if files:
501 if files:
502 files = relpath(repo, files)
502 files = relpath(repo, files)
503 else:
503 else:
504 files = relpath(repo, [""])
504 files = relpath(repo, [""])
505
505
506 dodiff(sys.stdout, ui, repo, files, *revs)
506 dodiff(sys.stdout, ui, repo, files, *revs)
507
507
508 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
508 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
509 node = repo.lookup(changeset)
509 node = repo.lookup(changeset)
510 prev, other = repo.changelog.parents(node)
510 prev, other = repo.changelog.parents(node)
511 change = repo.changelog.read(node)
511 change = repo.changelog.read(node)
512
512
513 if opts['output'] and opts['output'] != '-':
513 if opts['output'] and opts['output'] != '-':
514 try:
514 try:
515 outname = make_filename(repo, repo.changelog, opts['output'],
515 outname = make_filename(repo, repo.changelog, opts['output'],
516 node=node, total=total, seqno=seqno,
516 node=node, total=total, seqno=seqno,
517 revwidth=revwidth)
517 revwidth=revwidth)
518 fp = open(outname, 'wb')
518 fp = open(outname, 'wb')
519 except KeyError, inst:
519 except KeyError, inst:
520 ui.warn("error: invalid format spec '%%%s' in output file name\n" %
520 ui.warn("error: invalid format spec '%%%s' in output file name\n" %
521 inst.args[0])
521 inst.args[0])
522 sys.exit(1)
522 sys.exit(1)
523 else:
523 else:
524 fp = sys.stdout
524 fp = sys.stdout
525
525
526 fp.write("# HG changeset patch\n")
526 fp.write("# HG changeset patch\n")
527 fp.write("# User %s\n" % change[1])
527 fp.write("# User %s\n" % change[1])
528 fp.write("# Node ID %s\n" % hg.hex(node))
528 fp.write("# Node ID %s\n" % hg.hex(node))
529 fp.write("# Parent %s\n" % hg.hex(prev))
529 fp.write("# Parent %s\n" % hg.hex(prev))
530 if other != hg.nullid:
530 if other != hg.nullid:
531 fp.write("# Parent %s\n" % hg.hex(other))
531 fp.write("# Parent %s\n" % hg.hex(other))
532 fp.write(change[4].rstrip())
532 fp.write(change[4].rstrip())
533 fp.write("\n\n")
533 fp.write("\n\n")
534
534
535 dodiff(fp, ui, repo, None, prev, node)
535 dodiff(fp, ui, repo, None, prev, node)
536
536
537 def export(ui, repo, *changesets, **opts):
537 def export(ui, repo, *changesets, **opts):
538 """dump the header and diffs for one or more changesets"""
538 """dump the header and diffs for one or more changesets"""
539 if not changesets:
539 if not changesets:
540 ui.warn("error: export requires at least one changeset\n")
540 ui.warn("error: export requires at least one changeset\n")
541 sys.exit(1)
541 sys.exit(1)
542 seqno = 0
542 seqno = 0
543 revs = list(revrange(ui, repo, changesets))
543 revs = list(revrange(ui, repo, changesets))
544 total = len(revs)
544 total = len(revs)
545 revwidth = max(len(revs[0]), len(revs[-1]))
545 revwidth = max(len(revs[0]), len(revs[-1]))
546 for cset in revs:
546 for cset in revs:
547 seqno += 1
547 seqno += 1
548 doexport(ui, repo, cset, seqno, total, revwidth, opts)
548 doexport(ui, repo, cset, seqno, total, revwidth, opts)
549
549
550 def forget(ui, repo, file, *files):
550 def forget(ui, repo, file, *files):
551 """don't add the specified files on the next commit"""
551 """don't add the specified files on the next commit"""
552 repo.forget(relpath(repo, (file,) + files))
552 repo.forget(relpath(repo, (file,) + files))
553
553
554 def heads(ui, repo):
554 def heads(ui, repo):
555 """show current repository heads"""
555 """show current repository heads"""
556 for n in repo.changelog.heads():
556 for n in repo.changelog.heads():
557 show_changeset(ui, repo, changenode=n)
557 show_changeset(ui, repo, changenode=n)
558
558
559 def identify(ui, repo):
559 def identify(ui, repo):
560 """print information about the working copy"""
560 """print information about the working copy"""
561 parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
561 parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
562 if not parents:
562 if not parents:
563 ui.write("unknown\n")
563 ui.write("unknown\n")
564 return
564 return
565
565
566 hexfunc = ui.verbose and hg.hex or hg.short
566 hexfunc = ui.verbose and hg.hex or hg.short
567 (c, a, d, u) = repo.changes(None, None)
567 (c, a, d, u) = repo.changes(None, None)
568 output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
568 output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
569 (c or a or d) and "+" or "")]
569 (c or a or d) and "+" or "")]
570
570
571 if not ui.quiet:
571 if not ui.quiet:
572 # multiple tags for a single parent separated by '/'
572 # multiple tags for a single parent separated by '/'
573 parenttags = ['/'.join(tags)
573 parenttags = ['/'.join(tags)
574 for tags in map(repo.nodetags, parents) if tags]
574 for tags in map(repo.nodetags, parents) if tags]
575 # tags for multiple parents separated by ' + '
575 # tags for multiple parents separated by ' + '
576 output.append(' + '.join(parenttags))
576 output.append(' + '.join(parenttags))
577
577
578 ui.write("%s\n" % ' '.join(output))
578 ui.write("%s\n" % ' '.join(output))
579
579
580 def import_(ui, repo, patch1, *patches, **opts):
580 def import_(ui, repo, patch1, *patches, **opts):
581 """import an ordered set of patches"""
581 """import an ordered set of patches"""
582 try:
582 try:
583 import psyco
583 import psyco
584 psyco.full()
584 psyco.full()
585 except:
585 except:
586 pass
586 pass
587
587
588 patches = (patch1,) + patches
588 patches = (patch1,) + patches
589
589
590 d = opts["base"]
590 d = opts["base"]
591 strip = opts["strip"]
591 strip = opts["strip"]
592
592
593 for patch in patches:
593 for patch in patches:
594 ui.status("applying %s\n" % patch)
594 ui.status("applying %s\n" % patch)
595 pf = os.path.join(d, patch)
595 pf = os.path.join(d, patch)
596
596
597 text = ""
597 text = ""
598 for l in file(pf):
598 for l in file(pf):
599 if l.startswith("--- ") or l.startswith("diff -r"): break
599 if l.startswith("--- ") or l.startswith("diff -r"): break
600 text += l
600 text += l
601
601
602 # parse values that exist when importing the result of an hg export
602 # parse values that exist when importing the result of an hg export
603 hgpatch = user = snippet = None
603 hgpatch = user = snippet = None
604 ui.debug('text:\n')
604 ui.debug('text:\n')
605 for t in text.splitlines():
605 for t in text.splitlines():
606 ui.debug(t,'\n')
606 ui.debug(t,'\n')
607 if t == '# HG changeset patch' or hgpatch == True:
607 if t == '# HG changeset patch' or hgpatch == True:
608 hgpatch = True
608 hgpatch = True
609 if t[:7] == "# User ":
609 if t[:7] == "# User ":
610 user = t[7:]
610 user = t[7:]
611 ui.debug('User: %s\n' % user)
611 ui.debug('User: %s\n' % user)
612 if t[:2] <> "# " and t.strip() and not snippet: snippet = t
612 if t[:2] <> "# " and t.strip() and not snippet: snippet = t
613 if snippet: text = snippet + '\n' + text
613 if snippet: text = snippet + '\n' + text
614 ui.debug('text:\n%s\n' % text)
614 ui.debug('text:\n%s\n' % text)
615
615
616 # make sure text isn't empty
616 # make sure text isn't empty
617 if not text: text = "imported patch %s\n" % patch
617 if not text: text = "imported patch %s\n" % patch
618
618
619 f = os.popen("patch -p%d < %s" % (strip, pf))
619 f = os.popen("patch -p%d < %s" % (strip, pf))
620 files = []
620 files = []
621 for l in f.read().splitlines():
621 for l in f.read().splitlines():
622 l.rstrip('\r\n');
622 l.rstrip('\r\n');
623 ui.status("%s\n" % l)
623 ui.status("%s\n" % l)
624 if l[:14] == 'patching file ':
624 if l[:14] == 'patching file ':
625 pf = l[14:]
625 pf = l[14:]
626 if pf not in files:
626 if pf not in files:
627 files.append(pf)
627 files.append(pf)
628 patcherr = f.close()
628 patcherr = f.close()
629 if patcherr:
629 if patcherr:
630 sys.stderr.write("patch failed")
630 sys.stderr.write("patch failed")
631 sys.exit(1)
631 sys.exit(1)
632
632
633 if len(files) > 0:
633 if len(files) > 0:
634 addremove(ui, repo, *files)
634 addremove(ui, repo, *files)
635 repo.commit(files, text, user)
635 repo.commit(files, text, user)
636
636
637 def init(ui, source=None):
637 def init(ui, source=None):
638 """create a new repository in the current directory"""
638 """create a new repository in the current directory"""
639
639
640 if source:
640 if source:
641 ui.warn("no longer supported: use \"hg clone\" instead\n")
641 ui.warn("no longer supported: use \"hg clone\" instead\n")
642 sys.exit(1)
642 sys.exit(1)
643 repo = hg.repository(ui, ".", create=1)
643 repo = hg.repository(ui, ".", create=1)
644
644
645 def locate(ui, repo, *pats, **opts):
645 def locate(ui, repo, *pats, **opts):
646 """locate files matching specific patterns"""
646 """locate files matching specific patterns"""
647 if [p for p in pats if os.sep in p]:
647 if [p for p in pats if os.sep in p]:
648 ui.warn("error: patterns may not contain '%s'\n" % os.sep)
648 ui.warn("error: patterns may not contain '%s'\n" % os.sep)
649 ui.warn("use '-i <dir>' instead\n")
649 ui.warn("use '-i <dir>' instead\n")
650 sys.exit(1)
650 sys.exit(1)
651 def compile(pats, head = '^', tail = os.sep, on_empty = True):
651 def compile(pats, head = '^', tail = os.sep, on_empty = True):
652 if not pats:
652 if not pats:
653 class c:
653 class c:
654 def match(self, x): return on_empty
654 def match(self, x): return on_empty
655 return c()
655 return c()
656 regexp = r'%s(?:%s)%s' % (
656 regexp = r'%s(?:%s)%s' % (
657 head,
657 head,
658 '|'.join([fnmatch.translate(os.path.normpath(os.path.normcase(p)))[:-1]
658 '|'.join([fnmatch.translate(os.path.normpath(os.path.normcase(p)))[:-1]
659 for p in pats]),
659 for p in pats]),
660 tail)
660 tail)
661 return re.compile(regexp)
661 return re.compile(regexp)
662 exclude = compile(opts['exclude'], on_empty = False)
662 exclude = compile(opts['exclude'], on_empty = False)
663 include = compile(opts['include'])
663 include = compile(opts['include'])
664 pat = compile([os.path.normcase(p) for p in pats], head = '', tail = '$')
664 pat = compile([os.path.normcase(p) for p in pats], head = '', tail = '$')
665 end = '\n'
665 end = '\n'
666 if opts['print0']: end = '\0'
666 if opts['print0']: end = '\0'
667 if opts['rev']: node = repo.manifest.lookup(opts['rev'])
667 if opts['rev']: node = repo.manifest.lookup(opts['rev'])
668 else: node = repo.manifest.tip()
668 else: node = repo.manifest.tip()
669 manifest = repo.manifest.read(node)
669 manifest = repo.manifest.read(node)
670 cwd = repo.getcwd()
670 cwd = repo.getcwd()
671 cwd_plus = cwd and (cwd + os.sep)
671 cwd_plus = cwd and (cwd + os.sep)
672 found = []
672 found = []
673 for f in manifest:
673 for f in manifest:
674 f = os.path.normcase(f)
674 f = os.path.normcase(f)
675 if exclude.match(f) or not(include.match(f) and
675 if exclude.match(f) or not(include.match(f) and
676 f.startswith(cwd_plus) and
676 f.startswith(cwd_plus) and
677 pat.match(os.path.basename(f))): continue
677 pat.match(os.path.basename(f))): continue
678 if opts['fullpath']: f = os.path.join(repo.root, f)
678 if opts['fullpath']: f = os.path.join(repo.root, f)
679 elif cwd: f = f[len(cwd_plus):]
679 elif cwd: f = f[len(cwd_plus):]
680 found.append(f)
680 found.append(f)
681 found.sort()
681 found.sort()
682 for f in found: ui.write(f, end)
682 for f in found: ui.write(f, end)
683
683
684 def log(ui, repo, f=None, **opts):
684 def log(ui, repo, f=None, **opts):
685 """show the revision history of the repository or a single file"""
685 """show the revision history of the repository or a single file"""
686 if f:
686 if f:
687 files = relpath(repo, [f])
687 files = relpath(repo, [f])
688 filelog = repo.file(files[0])
688 filelog = repo.file(files[0])
689 log = filelog
689 log = filelog
690 lookup = filelog.lookup
690 lookup = filelog.lookup
691 else:
691 else:
692 files = None
692 files = None
693 filelog = None
693 filelog = None
694 log = repo.changelog
694 log = repo.changelog
695 lookup = repo.lookup
695 lookup = repo.lookup
696 revlist = []
696 revlist = []
697 revs = [log.rev(lookup(rev)) for rev in opts['rev']]
697 revs = [log.rev(lookup(rev)) for rev in opts['rev']]
698 while revs:
698 while revs:
699 if len(revs) == 1:
699 if len(revs) == 1:
700 revlist.append(revs.pop(0))
700 revlist.append(revs.pop(0))
701 else:
701 else:
702 a = revs.pop(0)
702 a = revs.pop(0)
703 b = revs.pop(0)
703 b = revs.pop(0)
704 off = a > b and -1 or 1
704 off = a > b and -1 or 1
705 revlist.extend(range(a, b + off, off))
705 revlist.extend(range(a, b + off, off))
706
706
707 for i in revlist or range(log.count() - 1, -1, -1):
707 for i in revlist or range(log.count() - 1, -1, -1):
708 show_changeset(ui, repo, filelog=filelog, rev=i)
708 show_changeset(ui, repo, filelog=filelog, rev=i)
709 if opts['patch']:
709 if opts['patch']:
710 if filelog:
710 if filelog:
711 filenode = filelog.node(i)
711 filenode = filelog.node(i)
712 i = filelog.linkrev(filenode)
712 i = filelog.linkrev(filenode)
713 changenode = repo.changelog.node(i)
713 changenode = repo.changelog.node(i)
714 prev, other = repo.changelog.parents(changenode)
714 prev, other = repo.changelog.parents(changenode)
715 dodiff(sys.stdout, ui, repo, files, prev, changenode)
715 dodiff(sys.stdout, ui, repo, files, prev, changenode)
716 ui.write("\n")
716 ui.write("\n")
717 ui.write("\n")
717 ui.write("\n")
718
718
719 def manifest(ui, repo, rev = []):
719 def manifest(ui, repo, rev = []):
720 """output the latest or given revision of the project manifest"""
720 """output the latest or given revision of the project manifest"""
721 n = repo.manifest.tip()
721 n = repo.manifest.tip()
722 if rev:
722 if rev:
723 n = repo.manifest.lookup(rev)
723 n = repo.manifest.lookup(rev)
724 m = repo.manifest.read(n)
724 m = repo.manifest.read(n)
725 mf = repo.manifest.readflags(n)
725 mf = repo.manifest.readflags(n)
726 files = m.keys()
726 files = m.keys()
727 files.sort()
727 files.sort()
728
728
729 for f in files:
729 for f in files:
730 ui.write("%40s %3s %s\n" % (hg.hex(m[f]), mf[f] and "755" or "644", f))
730 ui.write("%40s %3s %s\n" % (hg.hex(m[f]), mf[f] and "755" or "644", f))
731
731
732 def parents(ui, repo, node = None):
732 def parents(ui, repo, node = None):
733 '''show the parents of the current working dir'''
733 '''show the parents of the current working dir'''
734 if node:
734 if node:
735 p = repo.changelog.parents(repo.lookup(hg.bin(node)))
735 p = repo.changelog.parents(repo.lookup(hg.bin(node)))
736 else:
736 else:
737 p = repo.dirstate.parents()
737 p = repo.dirstate.parents()
738
738
739 for n in p:
739 for n in p:
740 if n != hg.nullid:
740 if n != hg.nullid:
741 show_changeset(ui, repo, changenode=n)
741 show_changeset(ui, repo, changenode=n)
742
742
743 def pull(ui, repo, source="default", **opts):
743 def pull(ui, repo, source="default", **opts):
744 """pull changes from the specified source"""
744 """pull changes from the specified source"""
745 source = ui.expandpath(source)
745 source = ui.expandpath(source)
746 ui.status('pulling from %s\n' % (source))
746 ui.status('pulling from %s\n' % (source))
747
747
748 other = hg.repository(ui, source)
748 other = hg.repository(ui, source)
749 r = repo.pull(other)
749 r = repo.pull(other)
750 if not r:
750 if not r:
751 if opts['update']:
751 if opts['update']:
752 return update(ui, repo)
752 return update(ui, repo)
753 else:
753 else:
754 ui.status("(run 'hg update' to get a working copy)\n")
754 ui.status("(run 'hg update' to get a working copy)\n")
755
755
756 return r
756 return r
757
757
758 def push(ui, repo, dest="default-push"):
758 def push(ui, repo, dest="default-push"):
759 """push changes to the specified destination"""
759 """push changes to the specified destination"""
760 dest = ui.expandpath(dest)
760 dest = ui.expandpath(dest)
761
761
762 if not dest.startswith("ssh://"):
762 if not dest.startswith("ssh://"):
763 ui.warn("abort: can only push to ssh:// destinations currently\n")
763 ui.warn("abort: can only push to ssh:// destinations currently\n")
764 return 1
764 return 1
765
765
766 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?', dest)
766 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?', dest)
767 if not m:
767 if not m:
768 ui.warn("abort: couldn't parse destination %s\n" % dest)
768 ui.warn("abort: couldn't parse destination %s\n" % dest)
769 return 1
769 return 1
770
770
771 user, host, port, path = map(m.group, (2, 3, 5, 7))
771 user, host, port, path = map(m.group, (2, 3, 5, 7))
772 uhost = user and ("%s@%s" % (user, host)) or host
772 uhost = user and ("%s@%s" % (user, host)) or host
773 port = port and (" -p %s") % port or ""
773 port = port and (" -p %s") % port or ""
774 path = path or ""
774 path = path or ""
775
775
776 sport = random.randrange(30000, 60000)
776 sport = random.randrange(30000, 60000)
777 cmd = "ssh %s%s -R %d:localhost:%d 'cd %s; hg pull http://localhost:%d/'"
777 cmd = "ssh %s%s -R %d:localhost:%d 'cd %s; hg pull http://localhost:%d/'"
778 cmd = cmd % (uhost, port, sport+1, sport, path, sport+1)
778 cmd = cmd % (uhost, port, sport+1, sport, path, sport+1)
779
779
780 child = os.fork()
780 child = os.fork()
781 if not child:
781 if not child:
782 sys.stdout = file("/dev/null", "w")
782 sys.stdout = file("/dev/null", "w")
783 sys.stderr = sys.stdout
783 sys.stderr = sys.stdout
784 hgweb.server(repo.root, "pull", "", "localhost", sport)
784 hgweb.server(repo.root, "pull", "", "localhost", sport)
785 else:
785 else:
786 ui.status("connecting to %s\n" % host)
786 ui.status("connecting to %s\n" % host)
787 r = os.system(cmd)
787 r = os.system(cmd)
788 os.kill(child, signal.SIGTERM)
788 os.kill(child, signal.SIGTERM)
789 return r
789 return r
790
790
791 def rawcommit(ui, repo, *flist, **rc):
791 def rawcommit(ui, repo, *flist, **rc):
792 "raw commit interface"
792 "raw commit interface"
793
793
794 text = rc['text']
794 text = rc['text']
795 if not text and rc['logfile']:
795 if not text and rc['logfile']:
796 try: text = open(rc['logfile']).read()
796 try: text = open(rc['logfile']).read()
797 except IOError: pass
797 except IOError: pass
798 if not text and not rc['logfile']:
798 if not text and not rc['logfile']:
799 ui.warn("abort: missing commit text\n")
799 ui.warn("abort: missing commit text\n")
800 return 1
800 return 1
801
801
802 files = relpath(repo, list(flist))
802 files = relpath(repo, list(flist))
803 if rc['files']:
803 if rc['files']:
804 files += open(rc['files']).read().splitlines()
804 files += open(rc['files']).read().splitlines()
805
805
806 rc['parent'] = map(repo.lookup, rc['parent'])
806 rc['parent'] = map(repo.lookup, rc['parent'])
807
807
808 repo.rawcommit(files, text, rc['user'], rc['date'], *rc['parent'])
808 repo.rawcommit(files, text, rc['user'], rc['date'], *rc['parent'])
809
809
810 def recover(ui, repo):
810 def recover(ui, repo):
811 """roll back an interrupted transaction"""
811 """roll back an interrupted transaction"""
812 repo.recover()
812 repo.recover()
813
813
814 def remove(ui, repo, file, *files):
814 def remove(ui, repo, file, *files):
815 """remove the specified files on the next commit"""
815 """remove the specified files on the next commit"""
816 repo.remove(relpath(repo, (file,) + files))
816 repo.remove(relpath(repo, (file,) + files))
817
817
818 def revert(ui, repo, *names, **opts):
818 def revert(ui, repo, *names, **opts):
819 """revert modified files or dirs back to their unmodified states"""
819 """revert modified files or dirs back to their unmodified states"""
820 node = opts['rev'] and repo.lookup(opts['rev']) or \
820 node = opts['rev'] and repo.lookup(opts['rev']) or \
821 repo.dirstate.parents()[0]
821 repo.dirstate.parents()[0]
822 root = os.path.realpath(repo.root)
822 root = os.path.realpath(repo.root)
823
823
824 def trimpath(p):
824 def trimpath(p):
825 p = os.path.realpath(p)
825 p = os.path.realpath(p)
826 if p.startswith(root):
826 if p.startswith(root):
827 rest = p[len(root):]
827 rest = p[len(root):]
828 if not rest:
828 if not rest:
829 return rest
829 return rest
830 if p.startswith(os.sep):
830 if p.startswith(os.sep):
831 return rest[1:]
831 return rest[1:]
832 return p
832 return p
833
833
834 relnames = map(trimpath, names or [os.getcwd()])
834 relnames = map(trimpath, names or [os.getcwd()])
835 chosen = {}
835 chosen = {}
836
836
837 def choose(name):
837 def choose(name):
838 def body(name):
838 def body(name):
839 for r in relnames:
839 for r in relnames:
840 if not name.startswith(r): continue
840 if not name.startswith(r): continue
841 rest = name[len(r):]
841 rest = name[len(r):]
842 if not rest: return r, True
842 if not rest: return r, True
843 depth = rest.count(os.sep)
843 depth = rest.count(os.sep)
844 if not r:
844 if not r:
845 if depth == 0 or not opts['nonrecursive']: return r, True
845 if depth == 0 or not opts['nonrecursive']: return r, True
846 elif rest[0] == os.sep:
846 elif rest[0] == os.sep:
847 if depth == 1 or not opts['nonrecursive']: return r, True
847 if depth == 1 or not opts['nonrecursive']: return r, True
848 return None, False
848 return None, False
849 relname, ret = body(name)
849 relname, ret = body(name)
850 if ret:
850 if ret:
851 chosen[relname] = 1
851 chosen[relname] = 1
852 return ret
852 return ret
853
853
854 r = repo.update(node, False, True, choose, False)
854 r = repo.update(node, False, True, choose, False)
855 for n in relnames:
855 for n in relnames:
856 if n not in chosen:
856 if n not in chosen:
857 ui.warn('error: no matches for %s\n' % n)
857 ui.warn('error: no matches for %s\n' % n)
858 r = 1
858 r = 1
859 sys.stdout.flush()
859 sys.stdout.flush()
860 return r
860 return r
861
861
862 def root(ui, repo):
862 def root(ui, repo):
863 """print the root (top) of the current working dir"""
863 """print the root (top) of the current working dir"""
864 ui.write(repo.root + "\n")
864 ui.write(repo.root + "\n")
865
865
866 def serve(ui, repo, **opts):
866 def serve(ui, repo, **opts):
867 """export the repository via HTTP"""
867 """export the repository via HTTP"""
868
868
869 if opts["stdio"]:
869 if opts["stdio"]:
870 fin, fout = sys.stdin, sys.stdout
871 sys.stdout = sys.stderr
872
870 def getarg():
873 def getarg():
871 argline = sys.stdin.readline()[:-1]
874 argline = fin.readline()[:-1]
872 arg, l = argline.split()
875 arg, l = argline.split()
873 val = sys.stdin.read(int(l))
876 val = fin.read(int(l))
874 return arg, val
877 return arg, val
875 def respond(v):
878 def respond(v):
876 sys.stdout.write("%d\n" % len(v))
879 fout.write("%d\n" % len(v))
877 sys.stdout.write(v)
880 fout.write(v)
878 sys.stdout.flush()
881 fout.flush()
879
882
880 while 1:
883 while 1:
881 cmd = sys.stdin.readline()[:-1]
884 cmd = fin.readline()[:-1]
882 if cmd == '':
885 if cmd == '':
883 return
886 return
884 if cmd == "heads":
887 if cmd == "heads":
885 h = repo.heads()
888 h = repo.heads()
886 respond(" ".join(map(hg.hex, h)) + "\n")
889 respond(" ".join(map(hg.hex, h)) + "\n")
887 elif cmd == "branches":
890 elif cmd == "branches":
888 arg, nodes = getarg()
891 arg, nodes = getarg()
889 nodes = map(hg.bin, nodes.split(" "))
892 nodes = map(hg.bin, nodes.split(" "))
890 r = []
893 r = []
891 for b in repo.branches(nodes):
894 for b in repo.branches(nodes):
892 r.append(" ".join(map(hg.hex, b)) + "\n")
895 r.append(" ".join(map(hg.hex, b)) + "\n")
893 respond("".join(r))
896 respond("".join(r))
894 elif cmd == "between":
897 elif cmd == "between":
895 arg, pairs = getarg()
898 arg, pairs = getarg()
896 pairs = [ map(hg.bin, p.split("-")) for p in pairs.split(" ") ]
899 pairs = [ map(hg.bin, p.split("-")) for p in pairs.split(" ") ]
897 r = []
900 r = []
898 for b in repo.between(pairs):
901 for b in repo.between(pairs):
899 r.append(" ".join(map(hg.hex, b)) + "\n")
902 r.append(" ".join(map(hg.hex, b)) + "\n")
900 respond("".join(r))
903 respond("".join(r))
901 elif cmd == "changegroup":
904 elif cmd == "changegroup":
902 nodes = []
905 nodes = []
903 arg, roots = getarg()
906 arg, roots = getarg()
904 nodes = map(hg.bin, roots.split(" "))
907 nodes = map(hg.bin, roots.split(" "))
905
908
906 b = []
909 cg = repo.changegroup(nodes)
907 t = 0
910 while 1:
908 for chunk in repo.changegroup(nodes):
911 d = cg.read(4096)
909 t += len(chunk)
912 if not d: break
910 b.append(chunk)
913 fout.write(d)
911 if t > 4096:
912 sys.stdout.write(struct.pack(">l", t))
913 for c in b:
914 sys.stdout.write(c)
915 t = 0
916 b = []
917
914
918 sys.stdout.write(struct.pack(">l", t))
915 out.flush()
919 for c in b:
920 sys.stdout.write(c)
921
922 sys.stdout.write(struct.pack(">l", -1))
923 sys.stdout.flush()
924
916
925 def openlog(opt, default):
917 def openlog(opt, default):
926 if opts[opt] and opts[opt] != '-': return open(opts[opt], 'w')
918 if opts[opt] and opts[opt] != '-': return open(opts[opt], 'w')
927 else: return default
919 else: return default
928
920
929 httpd = hgweb.create_server(repo.root, opts["name"], opts["templates"],
921 httpd = hgweb.create_server(repo.root, opts["name"], opts["templates"],
930 opts["address"], opts["port"],
922 opts["address"], opts["port"],
931 openlog('accesslog', sys.stdout),
923 openlog('accesslog', sys.stdout),
932 openlog('errorlog', sys.stderr))
924 openlog('errorlog', sys.stderr))
933 if ui.verbose:
925 if ui.verbose:
934 addr, port = httpd.socket.getsockname()
926 addr, port = httpd.socket.getsockname()
935 if addr == '0.0.0.0':
927 if addr == '0.0.0.0':
936 addr = socket.gethostname()
928 addr = socket.gethostname()
937 else:
929 else:
938 try:
930 try:
939 addr = socket.gethostbyaddr(addr)[0]
931 addr = socket.gethostbyaddr(addr)[0]
940 except: pass
932 except: pass
941 if port != 80:
933 if port != 80:
942 ui.status('listening at http://%s:%d/\n' % (addr, port))
934 ui.status('listening at http://%s:%d/\n' % (addr, port))
943 else:
935 else:
944 ui.status('listening at http://%s/\n' % addr)
936 ui.status('listening at http://%s/\n' % addr)
945 httpd.serve_forever()
937 httpd.serve_forever()
946
938
947 def status(ui, repo):
939 def status(ui, repo):
948 '''show changed files in the working directory
940 '''show changed files in the working directory
949
941
950 C = changed
942 C = changed
951 A = added
943 A = added
952 R = removed
944 R = removed
953 ? = not tracked'''
945 ? = not tracked'''
954
946
955 (c, a, d, u) = repo.changes(None, None)
947 (c, a, d, u) = repo.changes(None, None)
956 (c, a, d, u) = map(lambda x: relfilter(repo, x), (c, a, d, u))
948 (c, a, d, u) = map(lambda x: relfilter(repo, x), (c, a, d, u))
957
949
958 for f in c: ui.write("C ", f, "\n")
950 for f in c: ui.write("C ", f, "\n")
959 for f in a: ui.write("A ", f, "\n")
951 for f in a: ui.write("A ", f, "\n")
960 for f in d: ui.write("R ", f, "\n")
952 for f in d: ui.write("R ", f, "\n")
961 for f in u: ui.write("? ", f, "\n")
953 for f in u: ui.write("? ", f, "\n")
962
954
963 def tag(ui, repo, name, rev = None, **opts):
955 def tag(ui, repo, name, rev = None, **opts):
964 """add a tag for the current tip or a given revision"""
956 """add a tag for the current tip or a given revision"""
965
957
966 if name == "tip":
958 if name == "tip":
967 ui.warn("abort: 'tip' is a reserved name!\n")
959 ui.warn("abort: 'tip' is a reserved name!\n")
968 return -1
960 return -1
969 if rev:
961 if rev:
970 r = hg.hex(repo.lookup(rev))
962 r = hg.hex(repo.lookup(rev))
971 else:
963 else:
972 r = hg.hex(repo.changelog.tip())
964 r = hg.hex(repo.changelog.tip())
973
965
974 if name.find(revrangesep) >= 0:
966 if name.find(revrangesep) >= 0:
975 ui.warn("abort: '%s' cannot be used in a tag name\n" % revrangesep)
967 ui.warn("abort: '%s' cannot be used in a tag name\n" % revrangesep)
976 return -1
968 return -1
977
969
978 if opts['local']:
970 if opts['local']:
979 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
971 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
980 return
972 return
981
973
982 (c, a, d, u) = repo.changes(None, None)
974 (c, a, d, u) = repo.changes(None, None)
983 for x in (c, a, d, u):
975 for x in (c, a, d, u):
984 if ".hgtags" in x:
976 if ".hgtags" in x:
985 ui.warn("abort: working copy of .hgtags is changed!\n")
977 ui.warn("abort: working copy of .hgtags is changed!\n")
986 ui.status("(please commit .hgtags manually)\n")
978 ui.status("(please commit .hgtags manually)\n")
987 return -1
979 return -1
988
980
989 add = 0
981 add = 0
990 if not os.path.exists(repo.wjoin(".hgtags")): add = 1
982 if not os.path.exists(repo.wjoin(".hgtags")): add = 1
991 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
983 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
992 if add: repo.add([".hgtags"])
984 if add: repo.add([".hgtags"])
993
985
994 if not opts['text']:
986 if not opts['text']:
995 opts['text'] = "Added tag %s for changeset %s" % (name, r)
987 opts['text'] = "Added tag %s for changeset %s" % (name, r)
996
988
997 repo.commit([".hgtags"], opts['text'], opts['user'], opts['date'])
989 repo.commit([".hgtags"], opts['text'], opts['user'], opts['date'])
998
990
999 def tags(ui, repo):
991 def tags(ui, repo):
1000 """list repository tags"""
992 """list repository tags"""
1001
993
1002 l = repo.tagslist()
994 l = repo.tagslist()
1003 l.reverse()
995 l.reverse()
1004 for t, n in l:
996 for t, n in l:
1005 try:
997 try:
1006 r = "%5d:%s" % (repo.changelog.rev(n), hg.hex(n))
998 r = "%5d:%s" % (repo.changelog.rev(n), hg.hex(n))
1007 except KeyError:
999 except KeyError:
1008 r = " ?:?"
1000 r = " ?:?"
1009 ui.write("%-30s %s\n" % (t, r))
1001 ui.write("%-30s %s\n" % (t, r))
1010
1002
1011 def tip(ui, repo):
1003 def tip(ui, repo):
1012 """show the tip revision"""
1004 """show the tip revision"""
1013 n = repo.changelog.tip()
1005 n = repo.changelog.tip()
1014 show_changeset(ui, repo, changenode=n)
1006 show_changeset(ui, repo, changenode=n)
1015
1007
1016 def undo(ui, repo):
1008 def undo(ui, repo):
1017 """undo the last commit or pull
1009 """undo the last commit or pull
1018
1010
1019 Roll back the last pull or commit transaction on the
1011 Roll back the last pull or commit transaction on the
1020 repository, restoring the project to its earlier state.
1012 repository, restoring the project to its earlier state.
1021
1013
1022 This command should be used with care. There is only one level of
1014 This command should be used with care. There is only one level of
1023 undo and there is no redo.
1015 undo and there is no redo.
1024
1016
1025 This command is not intended for use on public repositories. Once
1017 This command is not intended for use on public repositories. Once
1026 a change is visible for pull by other users, undoing it locally is
1018 a change is visible for pull by other users, undoing it locally is
1027 ineffective.
1019 ineffective.
1028 """
1020 """
1029 repo.undo()
1021 repo.undo()
1030
1022
1031 def update(ui, repo, node=None, merge=False, clean=False):
1023 def update(ui, repo, node=None, merge=False, clean=False):
1032 '''update or merge working directory
1024 '''update or merge working directory
1033
1025
1034 If there are no outstanding changes in the working directory and
1026 If there are no outstanding changes in the working directory and
1035 there is a linear relationship between the current version and the
1027 there is a linear relationship between the current version and the
1036 requested version, the result is the requested version.
1028 requested version, the result is the requested version.
1037
1029
1038 Otherwise the result is a merge between the contents of the
1030 Otherwise the result is a merge between the contents of the
1039 current working directory and the requested version. Files that
1031 current working directory and the requested version. Files that
1040 changed between either parent are marked as changed for the next
1032 changed between either parent are marked as changed for the next
1041 commit and a commit must be performed before any further updates
1033 commit and a commit must be performed before any further updates
1042 are allowed.
1034 are allowed.
1043 '''
1035 '''
1044 node = node and repo.lookup(node) or repo.changelog.tip()
1036 node = node and repo.lookup(node) or repo.changelog.tip()
1045 return repo.update(node, allow=merge, force=clean)
1037 return repo.update(node, allow=merge, force=clean)
1046
1038
1047 def verify(ui, repo):
1039 def verify(ui, repo):
1048 """verify the integrity of the repository"""
1040 """verify the integrity of the repository"""
1049 return repo.verify()
1041 return repo.verify()
1050
1042
1051 # Command options and aliases are listed here, alphabetically
1043 # Command options and aliases are listed here, alphabetically
1052
1044
1053 table = {
1045 table = {
1054 "^add": (add, [], "hg add [files]"),
1046 "^add": (add, [], "hg add [files]"),
1055 "addremove": (addremove, [], "hg addremove [files]"),
1047 "addremove": (addremove, [], "hg addremove [files]"),
1056 "^annotate": (annotate,
1048 "^annotate": (annotate,
1057 [('r', 'revision', '', 'revision'),
1049 [('r', 'revision', '', 'revision'),
1058 ('u', 'user', None, 'show user'),
1050 ('u', 'user', None, 'show user'),
1059 ('n', 'number', None, 'show revision number'),
1051 ('n', 'number', None, 'show revision number'),
1060 ('c', 'changeset', None, 'show changeset')],
1052 ('c', 'changeset', None, 'show changeset')],
1061 'hg annotate [-u] [-c] [-n] [-r id] [files]'),
1053 'hg annotate [-u] [-c] [-n] [-r id] [files]'),
1062 "cat": (cat, [('o', 'output', "", 'output to file')], 'hg cat [-o outfile] file> [rev]'),
1054 "cat": (cat, [('o', 'output', "", 'output to file')], 'hg cat [-o outfile] file> [rev]'),
1063 "^clone": (clone, [('U', 'noupdate', None, 'skip update after cloning')],
1055 "^clone": (clone, [('U', 'noupdate', None, 'skip update after cloning')],
1064 'hg clone [options] <source> [dest]'),
1056 'hg clone [options] <source> [dest]'),
1065 "^commit|ci": (commit,
1057 "^commit|ci": (commit,
1066 [('t', 'text', "", 'commit text'),
1058 [('t', 'text', "", 'commit text'),
1067 ('A', 'addremove', None, 'run add/remove during commit'),
1059 ('A', 'addremove', None, 'run add/remove during commit'),
1068 ('l', 'logfile', "", 'commit text file'),
1060 ('l', 'logfile', "", 'commit text file'),
1069 ('d', 'date', "", 'date code'),
1061 ('d', 'date', "", 'date code'),
1070 ('u', 'user', "", 'user')],
1062 ('u', 'user', "", 'user')],
1071 'hg commit [files]'),
1063 'hg commit [files]'),
1072 "copy": (copy, [], 'hg copy <source> <dest>'),
1064 "copy": (copy, [], 'hg copy <source> <dest>'),
1073 "debugcheckstate": (debugcheckstate, [], 'debugcheckstate'),
1065 "debugcheckstate": (debugcheckstate, [], 'debugcheckstate'),
1074 "debugstate": (debugstate, [], 'debugstate'),
1066 "debugstate": (debugstate, [], 'debugstate'),
1075 "debugindex": (debugindex, [], 'debugindex <file>'),
1067 "debugindex": (debugindex, [], 'debugindex <file>'),
1076 "debugindexdot": (debugindexdot, [], 'debugindexdot <file>'),
1068 "debugindexdot": (debugindexdot, [], 'debugindexdot <file>'),
1077 "^diff": (diff, [('r', 'rev', [], 'revision')],
1069 "^diff": (diff, [('r', 'rev', [], 'revision')],
1078 'hg diff [-r A] [-r B] [files]'),
1070 'hg diff [-r A] [-r B] [files]'),
1079 "^export": (export, [('o', 'output', "", 'output to file')],
1071 "^export": (export, [('o', 'output', "", 'output to file')],
1080 "hg export [-o file] <changeset> ..."),
1072 "hg export [-o file] <changeset> ..."),
1081 "forget": (forget, [], "hg forget [files]"),
1073 "forget": (forget, [], "hg forget [files]"),
1082 "heads": (heads, [], 'hg heads'),
1074 "heads": (heads, [], 'hg heads'),
1083 "help": (help, [], 'hg help [command]'),
1075 "help": (help, [], 'hg help [command]'),
1084 "identify|id": (identify, [], 'hg identify'),
1076 "identify|id": (identify, [], 'hg identify'),
1085 "import|patch": (import_,
1077 "import|patch": (import_,
1086 [('p', 'strip', 1, 'path strip'),
1078 [('p', 'strip', 1, 'path strip'),
1087 ('b', 'base', "", 'base path')],
1079 ('b', 'base', "", 'base path')],
1088 "hg import [options] <patches>"),
1080 "hg import [options] <patches>"),
1089 "^init": (init, [], 'hg init'),
1081 "^init": (init, [], 'hg init'),
1090 "locate": (locate,
1082 "locate": (locate,
1091 [('0', 'print0', None, 'end records with NUL'),
1083 [('0', 'print0', None, 'end records with NUL'),
1092 ('f', 'fullpath', None, 'print complete paths'),
1084 ('f', 'fullpath', None, 'print complete paths'),
1093 ('i', 'include', [], 'include path in search'),
1085 ('i', 'include', [], 'include path in search'),
1094 ('r', 'rev', '', 'revision'),
1086 ('r', 'rev', '', 'revision'),
1095 ('x', 'exclude', [], 'exclude path from search')],
1087 ('x', 'exclude', [], 'exclude path from search')],
1096 'hg locate [options] [files]'),
1088 'hg locate [options] [files]'),
1097 "^log|history": (log,
1089 "^log|history": (log,
1098 [('r', 'rev', [], 'revision'),
1090 [('r', 'rev', [], 'revision'),
1099 ('p', 'patch', None, 'show patch')],
1091 ('p', 'patch', None, 'show patch')],
1100 'hg log [-r A] [-r B] [-p] [file]'),
1092 'hg log [-r A] [-r B] [-p] [file]'),
1101 "manifest": (manifest, [], 'hg manifest [rev]'),
1093 "manifest": (manifest, [], 'hg manifest [rev]'),
1102 "parents": (parents, [], 'hg parents [node]'),
1094 "parents": (parents, [], 'hg parents [node]'),
1103 "^pull": (pull,
1095 "^pull": (pull,
1104 [('u', 'update', None, 'update working directory')],
1096 [('u', 'update', None, 'update working directory')],
1105 'hg pull [options] [source]'),
1097 'hg pull [options] [source]'),
1106 "^push": (push, [], 'hg push <destination>'),
1098 "^push": (push, [], 'hg push <destination>'),
1107 "rawcommit": (rawcommit,
1099 "rawcommit": (rawcommit,
1108 [('p', 'parent', [], 'parent'),
1100 [('p', 'parent', [], 'parent'),
1109 ('d', 'date', "", 'date code'),
1101 ('d', 'date', "", 'date code'),
1110 ('u', 'user', "", 'user'),
1102 ('u', 'user', "", 'user'),
1111 ('F', 'files', "", 'file list'),
1103 ('F', 'files', "", 'file list'),
1112 ('t', 'text', "", 'commit text'),
1104 ('t', 'text', "", 'commit text'),
1113 ('l', 'logfile', "", 'commit text file')],
1105 ('l', 'logfile', "", 'commit text file')],
1114 'hg rawcommit [options] [files]'),
1106 'hg rawcommit [options] [files]'),
1115 "recover": (recover, [], "hg recover"),
1107 "recover": (recover, [], "hg recover"),
1116 "^remove|rm": (remove, [], "hg remove [files]"),
1108 "^remove|rm": (remove, [], "hg remove [files]"),
1117 "^revert": (revert,
1109 "^revert": (revert,
1118 [("n", "nonrecursive", None, "don't recurse into subdirs"),
1110 [("n", "nonrecursive", None, "don't recurse into subdirs"),
1119 ("r", "rev", "", "revision")],
1111 ("r", "rev", "", "revision")],
1120 "hg revert [files|dirs]"),
1112 "hg revert [files|dirs]"),
1121 "root": (root, [], "hg root"),
1113 "root": (root, [], "hg root"),
1122 "^serve": (serve, [('A', 'accesslog', '', 'access log file'),
1114 "^serve": (serve, [('A', 'accesslog', '', 'access log file'),
1123 ('E', 'errorlog', '', 'error log file'),
1115 ('E', 'errorlog', '', 'error log file'),
1124 ('p', 'port', 8000, 'listen port'),
1116 ('p', 'port', 8000, 'listen port'),
1125 ('a', 'address', '', 'interface address'),
1117 ('a', 'address', '', 'interface address'),
1126 ('n', 'name', os.getcwd(), 'repository name'),
1118 ('n', 'name', os.getcwd(), 'repository name'),
1127 ('', 'stdio', None, 'for remote clients'),
1119 ('', 'stdio', None, 'for remote clients'),
1128 ('t', 'templates', "", 'template map')],
1120 ('t', 'templates', "", 'template map')],
1129 "hg serve [options]"),
1121 "hg serve [options]"),
1130 "^status": (status, [], 'hg status'),
1122 "^status": (status, [], 'hg status'),
1131 "tag": (tag, [('l', 'local', None, 'make the tag local'),
1123 "tag": (tag, [('l', 'local', None, 'make the tag local'),
1132 ('t', 'text', "", 'commit text'),
1124 ('t', 'text', "", 'commit text'),
1133 ('d', 'date', "", 'date code'),
1125 ('d', 'date', "", 'date code'),
1134 ('u', 'user', "", 'user')],
1126 ('u', 'user', "", 'user')],
1135 'hg tag [options] <name> [rev]'),
1127 'hg tag [options] <name> [rev]'),
1136 "tags": (tags, [], 'hg tags'),
1128 "tags": (tags, [], 'hg tags'),
1137 "tip": (tip, [], 'hg tip'),
1129 "tip": (tip, [], 'hg tip'),
1138 "undo": (undo, [], 'hg undo'),
1130 "undo": (undo, [], 'hg undo'),
1139 "^update|up|checkout|co":
1131 "^update|up|checkout|co":
1140 (update,
1132 (update,
1141 [('m', 'merge', None, 'allow merging of conflicts'),
1133 [('m', 'merge', None, 'allow merging of conflicts'),
1142 ('C', 'clean', None, 'overwrite locally modified files')],
1134 ('C', 'clean', None, 'overwrite locally modified files')],
1143 'hg update [options] [node]'),
1135 'hg update [options] [node]'),
1144 "verify": (verify, [], 'hg verify'),
1136 "verify": (verify, [], 'hg verify'),
1145 "version": (show_version, [], 'hg version'),
1137 "version": (show_version, [], 'hg version'),
1146 }
1138 }
1147
1139
1148 globalopts = [('v', 'verbose', None, 'verbose'),
1140 globalopts = [('v', 'verbose', None, 'verbose'),
1149 ('', 'debug', None, 'debug'),
1141 ('', 'debug', None, 'debug'),
1150 ('q', 'quiet', None, 'quiet'),
1142 ('q', 'quiet', None, 'quiet'),
1151 ('', 'profile', None, 'profile'),
1143 ('', 'profile', None, 'profile'),
1152 ('R', 'repository', "", 'repository root directory'),
1144 ('R', 'repository', "", 'repository root directory'),
1153 ('', 'traceback', None, 'print traceback on exception'),
1145 ('', 'traceback', None, 'print traceback on exception'),
1154 ('y', 'noninteractive', None, 'run non-interactively'),
1146 ('y', 'noninteractive', None, 'run non-interactively'),
1155 ('', 'version', None, 'output version information and exit'),
1147 ('', 'version', None, 'output version information and exit'),
1156 ]
1148 ]
1157
1149
1158 norepo = "clone init version help debugindex debugindexdot"
1150 norepo = "clone init version help debugindex debugindexdot"
1159
1151
1160 def find(cmd):
1152 def find(cmd):
1161 for e in table.keys():
1153 for e in table.keys():
1162 if re.match("(%s)$" % e, cmd):
1154 if re.match("(%s)$" % e, cmd):
1163 return table[e]
1155 return table[e]
1164
1156
1165 raise UnknownCommand(cmd)
1157 raise UnknownCommand(cmd)
1166
1158
1167 class SignalInterrupt(Exception): pass
1159 class SignalInterrupt(Exception): pass
1168
1160
1169 def catchterm(*args):
1161 def catchterm(*args):
1170 raise SignalInterrupt
1162 raise SignalInterrupt
1171
1163
1172 def run():
1164 def run():
1173 sys.exit(dispatch(sys.argv[1:]))
1165 sys.exit(dispatch(sys.argv[1:]))
1174
1166
1175 class ParseError(Exception): pass
1167 class ParseError(Exception): pass
1176
1168
1177 def parse(args):
1169 def parse(args):
1178 options = {}
1170 options = {}
1179 cmdoptions = {}
1171 cmdoptions = {}
1180
1172
1181 try:
1173 try:
1182 args = fancyopts.fancyopts(args, globalopts, options)
1174 args = fancyopts.fancyopts(args, globalopts, options)
1183 except fancyopts.getopt.GetoptError, inst:
1175 except fancyopts.getopt.GetoptError, inst:
1184 raise ParseError(None, inst)
1176 raise ParseError(None, inst)
1185
1177
1186 if options["version"]:
1178 if options["version"]:
1187 return ("version", show_version, [], options, cmdoptions)
1179 return ("version", show_version, [], options, cmdoptions)
1188 elif not args:
1180 elif not args:
1189 return ("help", help, [], options, cmdoptions)
1181 return ("help", help, [], options, cmdoptions)
1190 else:
1182 else:
1191 cmd, args = args[0], args[1:]
1183 cmd, args = args[0], args[1:]
1192
1184
1193 i = find(cmd)
1185 i = find(cmd)
1194
1186
1195 # combine global options into local
1187 # combine global options into local
1196 c = list(i[1])
1188 c = list(i[1])
1197 l = len(c)
1189 l = len(c)
1198 for o in globalopts:
1190 for o in globalopts:
1199 c.append((o[0], o[1], options[o[1]], o[3]))
1191 c.append((o[0], o[1], options[o[1]], o[3]))
1200
1192
1201 try:
1193 try:
1202 args = fancyopts.fancyopts(args, c, cmdoptions)
1194 args = fancyopts.fancyopts(args, c, cmdoptions)
1203 except fancyopts.getopt.GetoptError, inst:
1195 except fancyopts.getopt.GetoptError, inst:
1204 raise ParseError(cmd, inst)
1196 raise ParseError(cmd, inst)
1205
1197
1206 # separate global options back out
1198 # separate global options back out
1207 for o in globalopts:
1199 for o in globalopts:
1208 n = o[1]
1200 n = o[1]
1209 options[n] = cmdoptions[n]
1201 options[n] = cmdoptions[n]
1210 del cmdoptions[n]
1202 del cmdoptions[n]
1211
1203
1212 return (cmd, i[0], args, options, cmdoptions)
1204 return (cmd, i[0], args, options, cmdoptions)
1213
1205
1214 def dispatch(args):
1206 def dispatch(args):
1215 signal.signal(signal.SIGTERM, catchterm)
1207 signal.signal(signal.SIGTERM, catchterm)
1216
1208
1217 try:
1209 try:
1218 cmd, func, args, options, cmdoptions = parse(args)
1210 cmd, func, args, options, cmdoptions = parse(args)
1219 except ParseError, inst:
1211 except ParseError, inst:
1220 u = ui.ui()
1212 u = ui.ui()
1221 if inst.args[0]:
1213 if inst.args[0]:
1222 u.warn("hg %s: %s\n" % (inst.args[0], inst.args[1]))
1214 u.warn("hg %s: %s\n" % (inst.args[0], inst.args[1]))
1223 help(u, inst.args[0])
1215 help(u, inst.args[0])
1224 else:
1216 else:
1225 u.warn("hg: %s\n" % inst.args[1])
1217 u.warn("hg: %s\n" % inst.args[1])
1226 help(u)
1218 help(u)
1227 sys.exit(-1)
1219 sys.exit(-1)
1228 except UnknownCommand, inst:
1220 except UnknownCommand, inst:
1229 u = ui.ui()
1221 u = ui.ui()
1230 u.warn("hg: unknown command '%s'\n" % inst.args[0])
1222 u.warn("hg: unknown command '%s'\n" % inst.args[0])
1231 help(u)
1223 help(u)
1232 sys.exit(1)
1224 sys.exit(1)
1233
1225
1234 u = ui.ui(options["verbose"], options["debug"], options["quiet"],
1226 u = ui.ui(options["verbose"], options["debug"], options["quiet"],
1235 not options["noninteractive"])
1227 not options["noninteractive"])
1236
1228
1237 try:
1229 try:
1238 try:
1230 try:
1239 if cmd not in norepo.split():
1231 if cmd not in norepo.split():
1240 path = options["repository"] or ""
1232 path = options["repository"] or ""
1241 repo = hg.repository(ui=u, path=path)
1233 repo = hg.repository(ui=u, path=path)
1242 d = lambda: func(u, repo, *args, **cmdoptions)
1234 d = lambda: func(u, repo, *args, **cmdoptions)
1243 else:
1235 else:
1244 d = lambda: func(u, *args, **cmdoptions)
1236 d = lambda: func(u, *args, **cmdoptions)
1245
1237
1246 if options['profile']:
1238 if options['profile']:
1247 import hotshot, hotshot.stats
1239 import hotshot, hotshot.stats
1248 prof = hotshot.Profile("hg.prof")
1240 prof = hotshot.Profile("hg.prof")
1249 r = prof.runcall(d)
1241 r = prof.runcall(d)
1250 prof.close()
1242 prof.close()
1251 stats = hotshot.stats.load("hg.prof")
1243 stats = hotshot.stats.load("hg.prof")
1252 stats.strip_dirs()
1244 stats.strip_dirs()
1253 stats.sort_stats('time', 'calls')
1245 stats.sort_stats('time', 'calls')
1254 stats.print_stats(40)
1246 stats.print_stats(40)
1255 return r
1247 return r
1256 else:
1248 else:
1257 return d()
1249 return d()
1258 except:
1250 except:
1259 if options['traceback']:
1251 if options['traceback']:
1260 traceback.print_exc()
1252 traceback.print_exc()
1261 raise
1253 raise
1262 except util.CommandError, inst:
1254 except util.CommandError, inst:
1263 u.warn("abort: %s\n" % inst.args)
1255 u.warn("abort: %s\n" % inst.args)
1264 except hg.RepoError, inst:
1256 except hg.RepoError, inst:
1265 u.warn("abort: ", inst, "!\n")
1257 u.warn("abort: ", inst, "!\n")
1266 except SignalInterrupt:
1258 except SignalInterrupt:
1267 u.warn("killed!\n")
1259 u.warn("killed!\n")
1268 except KeyboardInterrupt:
1260 except KeyboardInterrupt:
1269 u.warn("interrupted!\n")
1261 u.warn("interrupted!\n")
1270 except IOError, inst:
1262 except IOError, inst:
1271 if hasattr(inst, "code"):
1263 if hasattr(inst, "code"):
1272 u.warn("abort: %s\n" % inst)
1264 u.warn("abort: %s\n" % inst)
1273 elif hasattr(inst, "reason"):
1265 elif hasattr(inst, "reason"):
1274 u.warn("abort: error %d: %s\n" % (inst.reason[0], inst.reason[1]))
1266 u.warn("abort: error %d: %s\n" % (inst.reason[0], inst.reason[1]))
1275 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
1267 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
1276 u.warn("broken pipe\n")
1268 u.warn("broken pipe\n")
1277 else:
1269 else:
1278 raise
1270 raise
1279 except OSError, inst:
1271 except OSError, inst:
1280 if hasattr(inst, "filename"):
1272 if hasattr(inst, "filename"):
1281 u.warn("abort: %s: %s\n" % (inst.strerror, inst.filename))
1273 u.warn("abort: %s: %s\n" % (inst.strerror, inst.filename))
1282 else:
1274 else:
1283 u.warn("abort: %s\n" % inst.strerror)
1275 u.warn("abort: %s\n" % inst.strerror)
1284 except TypeError, inst:
1276 except TypeError, inst:
1285 # was this an argument error?
1277 # was this an argument error?
1286 tb = traceback.extract_tb(sys.exc_info()[2])
1278 tb = traceback.extract_tb(sys.exc_info()[2])
1287 if len(tb) > 2: # no
1279 if len(tb) > 2: # no
1288 raise
1280 raise
1289 u.debug(inst, "\n")
1281 u.debug(inst, "\n")
1290 u.warn("%s: invalid arguments\n" % cmd)
1282 u.warn("%s: invalid arguments\n" % cmd)
1291 help(u, cmd)
1283 help(u, cmd)
1292
1284
1293 sys.exit(-1)
1285 sys.exit(-1)
@@ -1,1703 +1,1710 b''
1 # hg.py - repository classes for mercurial
1 # hg.py - repository classes for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 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")
13 demandload(globals(), "tempfile httprangereader bdiff")
14
14
15 class filelog(revlog):
15 class filelog(revlog):
16 def __init__(self, opener, path):
16 def __init__(self, opener, path):
17 revlog.__init__(self, opener,
17 revlog.__init__(self, opener,
18 os.path.join("data", path + ".i"),
18 os.path.join("data", path + ".i"),
19 os.path.join("data", path + ".d"))
19 os.path.join("data", path + ".d"))
20
20
21 def read(self, node):
21 def read(self, node):
22 t = self.revision(node)
22 t = self.revision(node)
23 if t[:2] != '\1\n':
23 if t[:2] != '\1\n':
24 return t
24 return t
25 s = t.find('\1\n', 2)
25 s = t.find('\1\n', 2)
26 return t[s+2:]
26 return t[s+2:]
27
27
28 def readmeta(self, node):
28 def readmeta(self, node):
29 t = self.revision(node)
29 t = self.revision(node)
30 if t[:2] != '\1\n':
30 if t[:2] != '\1\n':
31 return t
31 return t
32 s = t.find('\1\n', 2)
32 s = t.find('\1\n', 2)
33 mt = t[2:s]
33 mt = t[2:s]
34 for l in mt.splitlines():
34 for l in mt.splitlines():
35 k, v = l.split(": ", 1)
35 k, v = l.split(": ", 1)
36 m[k] = v
36 m[k] = v
37 return m
37 return m
38
38
39 def add(self, text, meta, transaction, link, p1=None, p2=None):
39 def add(self, text, meta, transaction, link, p1=None, p2=None):
40 if meta or text[:2] == '\1\n':
40 if meta or text[:2] == '\1\n':
41 mt = ""
41 mt = ""
42 if meta:
42 if meta:
43 mt = [ "%s: %s\n" % (k, v) for k,v in meta.items() ]
43 mt = [ "%s: %s\n" % (k, v) for k,v in meta.items() ]
44 text = "\1\n" + "".join(mt) + "\1\n" + text
44 text = "\1\n" + "".join(mt) + "\1\n" + text
45 return self.addrevision(text, transaction, link, p1, p2)
45 return self.addrevision(text, transaction, link, p1, p2)
46
46
47 def annotate(self, node):
47 def annotate(self, node):
48
48
49 def decorate(text, rev):
49 def decorate(text, rev):
50 return ([rev] * len(text.splitlines()), text)
50 return ([rev] * len(text.splitlines()), text)
51
51
52 def pair(parent, child):
52 def pair(parent, child):
53 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
53 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
54 child[0][b1:b2] = parent[0][a1:a2]
54 child[0][b1:b2] = parent[0][a1:a2]
55 return child
55 return child
56
56
57 # find all ancestors
57 # find all ancestors
58 needed = {node:1}
58 needed = {node:1}
59 visit = [node]
59 visit = [node]
60 while visit:
60 while visit:
61 n = visit.pop(0)
61 n = visit.pop(0)
62 for p in self.parents(n):
62 for p in self.parents(n):
63 if p not in needed:
63 if p not in needed:
64 needed[p] = 1
64 needed[p] = 1
65 visit.append(p)
65 visit.append(p)
66 else:
66 else:
67 # count how many times we'll use this
67 # count how many times we'll use this
68 needed[p] += 1
68 needed[p] += 1
69
69
70 # sort by revision which is a topological order
70 # sort by revision which is a topological order
71 visit = [ (self.rev(n), n) for n in needed.keys() ]
71 visit = [ (self.rev(n), n) for n in needed.keys() ]
72 visit.sort()
72 visit.sort()
73 hist = {}
73 hist = {}
74
74
75 for r,n in visit:
75 for r,n in visit:
76 curr = decorate(self.read(n), self.linkrev(n))
76 curr = decorate(self.read(n), self.linkrev(n))
77 for p in self.parents(n):
77 for p in self.parents(n):
78 if p != nullid:
78 if p != nullid:
79 curr = pair(hist[p], curr)
79 curr = pair(hist[p], curr)
80 # trim the history of unneeded revs
80 # trim the history of unneeded revs
81 needed[p] -= 1
81 needed[p] -= 1
82 if not needed[p]:
82 if not needed[p]:
83 del hist[p]
83 del hist[p]
84 hist[n] = curr
84 hist[n] = curr
85
85
86 return zip(hist[n][0], hist[n][1].splitlines(1))
86 return zip(hist[n][0], hist[n][1].splitlines(1))
87
87
88 class manifest(revlog):
88 class manifest(revlog):
89 def __init__(self, opener):
89 def __init__(self, opener):
90 self.mapcache = None
90 self.mapcache = None
91 self.listcache = None
91 self.listcache = None
92 self.addlist = None
92 self.addlist = None
93 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
93 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
94
94
95 def read(self, node):
95 def read(self, node):
96 if node == nullid: return {} # don't upset local cache
96 if node == nullid: return {} # don't upset local cache
97 if self.mapcache and self.mapcache[0] == node:
97 if self.mapcache and self.mapcache[0] == node:
98 return self.mapcache[1]
98 return self.mapcache[1]
99 text = self.revision(node)
99 text = self.revision(node)
100 map = {}
100 map = {}
101 flag = {}
101 flag = {}
102 self.listcache = (text, text.splitlines(1))
102 self.listcache = (text, text.splitlines(1))
103 for l in self.listcache[1]:
103 for l in self.listcache[1]:
104 (f, n) = l.split('\0')
104 (f, n) = l.split('\0')
105 map[f] = bin(n[:40])
105 map[f] = bin(n[:40])
106 flag[f] = (n[40:-1] == "x")
106 flag[f] = (n[40:-1] == "x")
107 self.mapcache = (node, map, flag)
107 self.mapcache = (node, map, flag)
108 return map
108 return map
109
109
110 def readflags(self, node):
110 def readflags(self, node):
111 if node == nullid: return {} # don't upset local cache
111 if node == nullid: return {} # don't upset local cache
112 if not self.mapcache or self.mapcache[0] != node:
112 if not self.mapcache or self.mapcache[0] != node:
113 self.read(node)
113 self.read(node)
114 return self.mapcache[2]
114 return self.mapcache[2]
115
115
116 def diff(self, a, b):
116 def diff(self, a, b):
117 # this is sneaky, as we're not actually using a and b
117 # this is sneaky, as we're not actually using a and b
118 if self.listcache and self.addlist and self.listcache[0] == a:
118 if self.listcache and self.addlist and self.listcache[0] == a:
119 d = mdiff.diff(self.listcache[1], self.addlist, 1)
119 d = mdiff.diff(self.listcache[1], self.addlist, 1)
120 if mdiff.patch(a, d) != b:
120 if mdiff.patch(a, d) != b:
121 sys.stderr.write("*** sortdiff failed, falling back ***\n")
121 sys.stderr.write("*** sortdiff failed, falling back ***\n")
122 return mdiff.textdiff(a, b)
122 return mdiff.textdiff(a, b)
123 return d
123 return d
124 else:
124 else:
125 return mdiff.textdiff(a, b)
125 return mdiff.textdiff(a, b)
126
126
127 def add(self, map, flags, transaction, link, p1=None, p2=None):
127 def add(self, map, flags, transaction, link, p1=None, p2=None):
128 files = map.keys()
128 files = map.keys()
129 files.sort()
129 files.sort()
130
130
131 self.addlist = ["%s\000%s%s\n" %
131 self.addlist = ["%s\000%s%s\n" %
132 (f, hex(map[f]), flags[f] and "x" or '')
132 (f, hex(map[f]), flags[f] and "x" or '')
133 for f in files]
133 for f in files]
134 text = "".join(self.addlist)
134 text = "".join(self.addlist)
135
135
136 n = self.addrevision(text, transaction, link, p1, p2)
136 n = self.addrevision(text, transaction, link, p1, p2)
137 self.mapcache = (n, map, flags)
137 self.mapcache = (n, map, flags)
138 self.listcache = (text, self.addlist)
138 self.listcache = (text, self.addlist)
139 self.addlist = None
139 self.addlist = None
140
140
141 return n
141 return n
142
142
143 class changelog(revlog):
143 class changelog(revlog):
144 def __init__(self, opener):
144 def __init__(self, opener):
145 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
145 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
146
146
147 def extract(self, text):
147 def extract(self, text):
148 if not text:
148 if not text:
149 return (nullid, "", "0", [], "")
149 return (nullid, "", "0", [], "")
150 last = text.index("\n\n")
150 last = text.index("\n\n")
151 desc = text[last + 2:]
151 desc = text[last + 2:]
152 l = text[:last].splitlines()
152 l = text[:last].splitlines()
153 manifest = bin(l[0])
153 manifest = bin(l[0])
154 user = l[1]
154 user = l[1]
155 date = l[2]
155 date = l[2]
156 files = l[3:]
156 files = l[3:]
157 return (manifest, user, date, files, desc)
157 return (manifest, user, date, files, desc)
158
158
159 def read(self, node):
159 def read(self, node):
160 return self.extract(self.revision(node))
160 return self.extract(self.revision(node))
161
161
162 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
162 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
163 user=None, date=None):
163 user=None, date=None):
164 date = date or "%d %d" % (time.time(), time.timezone)
164 date = date or "%d %d" % (time.time(), time.timezone)
165 list.sort()
165 list.sort()
166 l = [hex(manifest), user, date] + list + ["", desc]
166 l = [hex(manifest), user, date] + list + ["", desc]
167 text = "\n".join(l)
167 text = "\n".join(l)
168 return self.addrevision(text, transaction, self.count(), p1, p2)
168 return self.addrevision(text, transaction, self.count(), p1, p2)
169
169
170 class dirstate:
170 class dirstate:
171 def __init__(self, opener, ui, root):
171 def __init__(self, opener, ui, root):
172 self.opener = opener
172 self.opener = opener
173 self.root = root
173 self.root = root
174 self.dirty = 0
174 self.dirty = 0
175 self.ui = ui
175 self.ui = ui
176 self.map = None
176 self.map = None
177 self.pl = None
177 self.pl = None
178 self.copies = {}
178 self.copies = {}
179
179
180 def __del__(self):
180 def __del__(self):
181 if self.dirty:
181 if self.dirty:
182 self.write()
182 self.write()
183
183
184 def __getitem__(self, key):
184 def __getitem__(self, key):
185 try:
185 try:
186 return self.map[key]
186 return self.map[key]
187 except TypeError:
187 except TypeError:
188 self.read()
188 self.read()
189 return self[key]
189 return self[key]
190
190
191 def __contains__(self, key):
191 def __contains__(self, key):
192 if not self.map: self.read()
192 if not self.map: self.read()
193 return key in self.map
193 return key in self.map
194
194
195 def parents(self):
195 def parents(self):
196 if not self.pl:
196 if not self.pl:
197 self.read()
197 self.read()
198 return self.pl
198 return self.pl
199
199
200 def setparents(self, p1, p2 = nullid):
200 def setparents(self, p1, p2 = nullid):
201 self.dirty = 1
201 self.dirty = 1
202 self.pl = p1, p2
202 self.pl = p1, p2
203
203
204 def state(self, key):
204 def state(self, key):
205 try:
205 try:
206 return self[key][0]
206 return self[key][0]
207 except KeyError:
207 except KeyError:
208 return "?"
208 return "?"
209
209
210 def read(self):
210 def read(self):
211 if self.map is not None: return self.map
211 if self.map is not None: return self.map
212
212
213 self.map = {}
213 self.map = {}
214 self.pl = [nullid, nullid]
214 self.pl = [nullid, nullid]
215 try:
215 try:
216 st = self.opener("dirstate").read()
216 st = self.opener("dirstate").read()
217 if not st: return
217 if not st: return
218 except: return
218 except: return
219
219
220 self.pl = [st[:20], st[20: 40]]
220 self.pl = [st[:20], st[20: 40]]
221
221
222 pos = 40
222 pos = 40
223 while pos < len(st):
223 while pos < len(st):
224 e = struct.unpack(">cllll", st[pos:pos+17])
224 e = struct.unpack(">cllll", st[pos:pos+17])
225 l = e[4]
225 l = e[4]
226 pos += 17
226 pos += 17
227 f = st[pos:pos + l]
227 f = st[pos:pos + l]
228 if '\0' in f:
228 if '\0' in f:
229 f, c = f.split('\0')
229 f, c = f.split('\0')
230 self.copies[f] = c
230 self.copies[f] = c
231 self.map[f] = e[:4]
231 self.map[f] = e[:4]
232 pos += l
232 pos += l
233
233
234 def copy(self, source, dest):
234 def copy(self, source, dest):
235 self.read()
235 self.read()
236 self.dirty = 1
236 self.dirty = 1
237 self.copies[dest] = source
237 self.copies[dest] = source
238
238
239 def copied(self, file):
239 def copied(self, file):
240 return self.copies.get(file, None)
240 return self.copies.get(file, None)
241
241
242 def update(self, files, state):
242 def update(self, files, state):
243 ''' current states:
243 ''' current states:
244 n normal
244 n normal
245 m needs merging
245 m needs merging
246 r marked for removal
246 r marked for removal
247 a marked for addition'''
247 a marked for addition'''
248
248
249 if not files: return
249 if not files: return
250 self.read()
250 self.read()
251 self.dirty = 1
251 self.dirty = 1
252 for f in files:
252 for f in files:
253 if state == "r":
253 if state == "r":
254 self.map[f] = ('r', 0, 0, 0)
254 self.map[f] = ('r', 0, 0, 0)
255 else:
255 else:
256 s = os.stat(os.path.join(self.root, f))
256 s = os.stat(os.path.join(self.root, f))
257 self.map[f] = (state, s.st_mode, s.st_size, s.st_mtime)
257 self.map[f] = (state, s.st_mode, s.st_size, s.st_mtime)
258
258
259 def forget(self, files):
259 def forget(self, files):
260 if not files: return
260 if not files: return
261 self.read()
261 self.read()
262 self.dirty = 1
262 self.dirty = 1
263 for f in files:
263 for f in files:
264 try:
264 try:
265 del self.map[f]
265 del self.map[f]
266 except KeyError:
266 except KeyError:
267 self.ui.warn("not in dirstate: %s!\n" % f)
267 self.ui.warn("not in dirstate: %s!\n" % f)
268 pass
268 pass
269
269
270 def clear(self):
270 def clear(self):
271 self.map = {}
271 self.map = {}
272 self.dirty = 1
272 self.dirty = 1
273
273
274 def write(self):
274 def write(self):
275 st = self.opener("dirstate", "w")
275 st = self.opener("dirstate", "w")
276 st.write("".join(self.pl))
276 st.write("".join(self.pl))
277 for f, e in self.map.items():
277 for f, e in self.map.items():
278 c = self.copied(f)
278 c = self.copied(f)
279 if c:
279 if c:
280 f = f + "\0" + c
280 f = f + "\0" + c
281 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
281 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
282 st.write(e + f)
282 st.write(e + f)
283 self.dirty = 0
283 self.dirty = 0
284
284
285 def changes(self, files, ignore):
285 def changes(self, files, ignore):
286 self.read()
286 self.read()
287 dc = self.map.copy()
287 dc = self.map.copy()
288 lookup, changed, added, unknown = [], [], [], []
288 lookup, changed, added, unknown = [], [], [], []
289
289
290 # compare all files by default
290 # compare all files by default
291 if not files: files = [self.root]
291 if not files: files = [self.root]
292
292
293 # recursive generator of all files listed
293 # recursive generator of all files listed
294 def walk(files):
294 def walk(files):
295 for f in util.unique(files):
295 for f in util.unique(files):
296 f = os.path.join(self.root, f)
296 f = os.path.join(self.root, f)
297 if os.path.isdir(f):
297 if os.path.isdir(f):
298 for dir, subdirs, fl in os.walk(f):
298 for dir, subdirs, fl in os.walk(f):
299 d = dir[len(self.root) + 1:]
299 d = dir[len(self.root) + 1:]
300 if ".hg" in subdirs: subdirs.remove(".hg")
300 if ".hg" in subdirs: subdirs.remove(".hg")
301 for fn in fl:
301 for fn in fl:
302 fn = util.pconvert(os.path.join(d, fn))
302 fn = util.pconvert(os.path.join(d, fn))
303 yield fn
303 yield fn
304 else:
304 else:
305 yield f[len(self.root) + 1:]
305 yield f[len(self.root) + 1:]
306
306
307 for fn in util.unique(walk(files)):
307 for fn in util.unique(walk(files)):
308 try: s = os.stat(os.path.join(self.root, fn))
308 try: s = os.stat(os.path.join(self.root, fn))
309 except: continue
309 except: continue
310
310
311 if fn in dc:
311 if fn in dc:
312 c = dc[fn]
312 c = dc[fn]
313 del dc[fn]
313 del dc[fn]
314
314
315 if c[0] == 'm':
315 if c[0] == 'm':
316 changed.append(fn)
316 changed.append(fn)
317 elif c[0] == 'a':
317 elif c[0] == 'a':
318 added.append(fn)
318 added.append(fn)
319 elif c[0] == 'r':
319 elif c[0] == 'r':
320 unknown.append(fn)
320 unknown.append(fn)
321 elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
321 elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
322 changed.append(fn)
322 changed.append(fn)
323 elif c[1] != s.st_mode or c[3] != s.st_mtime:
323 elif c[1] != s.st_mode or c[3] != s.st_mtime:
324 lookup.append(fn)
324 lookup.append(fn)
325 else:
325 else:
326 if not ignore(fn): unknown.append(fn)
326 if not ignore(fn): unknown.append(fn)
327
327
328 return (lookup, changed, added, dc.keys(), unknown)
328 return (lookup, changed, added, dc.keys(), unknown)
329
329
330 # used to avoid circular references so destructors work
330 # used to avoid circular references so destructors work
331 def opener(base):
331 def opener(base):
332 p = base
332 p = base
333 def o(path, mode="r"):
333 def o(path, mode="r"):
334 if p[:7] == "http://":
334 if p[:7] == "http://":
335 f = os.path.join(p, urllib.quote(path))
335 f = os.path.join(p, urllib.quote(path))
336 return httprangereader.httprangereader(f)
336 return httprangereader.httprangereader(f)
337
337
338 f = os.path.join(p, path)
338 f = os.path.join(p, path)
339
339
340 mode += "b" # for that other OS
340 mode += "b" # for that other OS
341
341
342 if mode[0] != "r":
342 if mode[0] != "r":
343 try:
343 try:
344 s = os.stat(f)
344 s = os.stat(f)
345 except OSError:
345 except OSError:
346 d = os.path.dirname(f)
346 d = os.path.dirname(f)
347 if not os.path.isdir(d):
347 if not os.path.isdir(d):
348 os.makedirs(d)
348 os.makedirs(d)
349 else:
349 else:
350 if s.st_nlink > 1:
350 if s.st_nlink > 1:
351 file(f + ".tmp", "wb").write(file(f, "rb").read())
351 file(f + ".tmp", "wb").write(file(f, "rb").read())
352 util.rename(f+".tmp", f)
352 util.rename(f+".tmp", f)
353
353
354 return file(f, mode)
354 return file(f, mode)
355
355
356 return o
356 return o
357
357
358 class RepoError(Exception): pass
358 class RepoError(Exception): pass
359
359
360 class localrepository:
360 class localrepository:
361 def __init__(self, ui, path=None, create=0):
361 def __init__(self, ui, path=None, create=0):
362 self.remote = 0
362 self.remote = 0
363 if path and path[:7] == "http://":
363 if path and path[:7] == "http://":
364 self.remote = 1
364 self.remote = 1
365 self.path = path
365 self.path = path
366 else:
366 else:
367 if not path:
367 if not path:
368 p = os.getcwd()
368 p = os.getcwd()
369 while not os.path.isdir(os.path.join(p, ".hg")):
369 while not os.path.isdir(os.path.join(p, ".hg")):
370 oldp = p
370 oldp = p
371 p = os.path.dirname(p)
371 p = os.path.dirname(p)
372 if p == oldp: raise RepoError("no repo found")
372 if p == oldp: raise RepoError("no repo found")
373 path = p
373 path = p
374 self.path = os.path.join(path, ".hg")
374 self.path = os.path.join(path, ".hg")
375
375
376 if not create and not os.path.isdir(self.path):
376 if not create and not os.path.isdir(self.path):
377 raise RepoError("repository %s not found" % self.path)
377 raise RepoError("repository %s not found" % self.path)
378
378
379 self.root = path
379 self.root = path
380 self.ui = ui
380 self.ui = ui
381
381
382 if create:
382 if create:
383 os.mkdir(self.path)
383 os.mkdir(self.path)
384 os.mkdir(self.join("data"))
384 os.mkdir(self.join("data"))
385
385
386 self.opener = opener(self.path)
386 self.opener = opener(self.path)
387 self.wopener = opener(self.root)
387 self.wopener = opener(self.root)
388 self.manifest = manifest(self.opener)
388 self.manifest = manifest(self.opener)
389 self.changelog = changelog(self.opener)
389 self.changelog = changelog(self.opener)
390 self.ignorelist = None
390 self.ignorelist = None
391 self.tagscache = None
391 self.tagscache = None
392 self.nodetagscache = None
392 self.nodetagscache = None
393
393
394 if not self.remote:
394 if not self.remote:
395 self.dirstate = dirstate(self.opener, ui, self.root)
395 self.dirstate = dirstate(self.opener, ui, self.root)
396 try:
396 try:
397 self.ui.readconfig(self.opener("hgrc"))
397 self.ui.readconfig(self.opener("hgrc"))
398 except IOError: pass
398 except IOError: pass
399
399
400 def ignore(self, f):
400 def ignore(self, f):
401 if self.ignorelist is None:
401 if self.ignorelist is None:
402 self.ignorelist = []
402 self.ignorelist = []
403 try:
403 try:
404 l = file(self.wjoin(".hgignore"))
404 l = file(self.wjoin(".hgignore"))
405 for pat in l:
405 for pat in l:
406 if pat != "\n":
406 if pat != "\n":
407 self.ignorelist.append(re.compile(util.pconvert(pat[:-1])))
407 self.ignorelist.append(re.compile(util.pconvert(pat[:-1])))
408 except IOError: pass
408 except IOError: pass
409 for pat in self.ignorelist:
409 for pat in self.ignorelist:
410 if pat.search(f): return True
410 if pat.search(f): return True
411 return False
411 return False
412
412
413 def hook(self, name, **args):
413 def hook(self, name, **args):
414 s = self.ui.config("hooks", name)
414 s = self.ui.config("hooks", name)
415 if s:
415 if s:
416 self.ui.note("running hook %s: %s\n" % (name, s))
416 self.ui.note("running hook %s: %s\n" % (name, s))
417 old = {}
417 old = {}
418 for k, v in args.items():
418 for k, v in args.items():
419 k = k.upper()
419 k = k.upper()
420 old[k] = os.environ.get(k, None)
420 old[k] = os.environ.get(k, None)
421 os.environ[k] = v
421 os.environ[k] = v
422
422
423 r = os.system(s)
423 r = os.system(s)
424
424
425 for k, v in old.items():
425 for k, v in old.items():
426 if v != None:
426 if v != None:
427 os.environ[k] = v
427 os.environ[k] = v
428 else:
428 else:
429 del os.environ[k]
429 del os.environ[k]
430
430
431 if r:
431 if r:
432 self.ui.warn("abort: %s hook failed with status %d!\n" %
432 self.ui.warn("abort: %s hook failed with status %d!\n" %
433 (name, r))
433 (name, r))
434 return False
434 return False
435 return True
435 return True
436
436
437 def tags(self):
437 def tags(self):
438 '''return a mapping of tag to node'''
438 '''return a mapping of tag to node'''
439 if not self.tagscache:
439 if not self.tagscache:
440 self.tagscache = {}
440 self.tagscache = {}
441 def addtag(self, k, n):
441 def addtag(self, k, n):
442 try:
442 try:
443 bin_n = bin(n)
443 bin_n = bin(n)
444 except TypeError:
444 except TypeError:
445 bin_n = ''
445 bin_n = ''
446 self.tagscache[k.strip()] = bin_n
446 self.tagscache[k.strip()] = bin_n
447
447
448 try:
448 try:
449 # read each head of the tags file, ending with the tip
449 # read each head of the tags file, ending with the tip
450 # and add each tag found to the map, with "newer" ones
450 # and add each tag found to the map, with "newer" ones
451 # taking precedence
451 # taking precedence
452 fl = self.file(".hgtags")
452 fl = self.file(".hgtags")
453 h = fl.heads()
453 h = fl.heads()
454 h.reverse()
454 h.reverse()
455 for r in h:
455 for r in h:
456 for l in fl.revision(r).splitlines():
456 for l in fl.revision(r).splitlines():
457 if l:
457 if l:
458 n, k = l.split(" ", 1)
458 n, k = l.split(" ", 1)
459 addtag(self, k, n)
459 addtag(self, k, n)
460 except KeyError:
460 except KeyError:
461 pass
461 pass
462
462
463 try:
463 try:
464 f = self.opener("localtags")
464 f = self.opener("localtags")
465 for l in f:
465 for l in f:
466 n, k = l.split(" ", 1)
466 n, k = l.split(" ", 1)
467 addtag(self, k, n)
467 addtag(self, k, n)
468 except IOError:
468 except IOError:
469 pass
469 pass
470
470
471 self.tagscache['tip'] = self.changelog.tip()
471 self.tagscache['tip'] = self.changelog.tip()
472
472
473 return self.tagscache
473 return self.tagscache
474
474
475 def tagslist(self):
475 def tagslist(self):
476 '''return a list of tags ordered by revision'''
476 '''return a list of tags ordered by revision'''
477 l = []
477 l = []
478 for t, n in self.tags().items():
478 for t, n in self.tags().items():
479 try:
479 try:
480 r = self.changelog.rev(n)
480 r = self.changelog.rev(n)
481 except:
481 except:
482 r = -2 # sort to the beginning of the list if unknown
482 r = -2 # sort to the beginning of the list if unknown
483 l.append((r,t,n))
483 l.append((r,t,n))
484 l.sort()
484 l.sort()
485 return [(t,n) for r,t,n in l]
485 return [(t,n) for r,t,n in l]
486
486
487 def nodetags(self, node):
487 def nodetags(self, node):
488 '''return the tags associated with a node'''
488 '''return the tags associated with a node'''
489 if not self.nodetagscache:
489 if not self.nodetagscache:
490 self.nodetagscache = {}
490 self.nodetagscache = {}
491 for t,n in self.tags().items():
491 for t,n in self.tags().items():
492 self.nodetagscache.setdefault(n,[]).append(t)
492 self.nodetagscache.setdefault(n,[]).append(t)
493 return self.nodetagscache.get(node, [])
493 return self.nodetagscache.get(node, [])
494
494
495 def lookup(self, key):
495 def lookup(self, key):
496 try:
496 try:
497 return self.tags()[key]
497 return self.tags()[key]
498 except KeyError:
498 except KeyError:
499 return self.changelog.lookup(key)
499 return self.changelog.lookup(key)
500
500
501 def dev(self):
501 def dev(self):
502 if self.remote: return -1
502 if self.remote: return -1
503 return os.stat(self.path).st_dev
503 return os.stat(self.path).st_dev
504
504
505 def join(self, f):
505 def join(self, f):
506 return os.path.join(self.path, f)
506 return os.path.join(self.path, f)
507
507
508 def wjoin(self, f):
508 def wjoin(self, f):
509 return os.path.join(self.root, f)
509 return os.path.join(self.root, f)
510
510
511 def file(self, f):
511 def file(self, f):
512 if f[0] == '/': f = f[1:]
512 if f[0] == '/': f = f[1:]
513 return filelog(self.opener, f)
513 return filelog(self.opener, f)
514
514
515 def getcwd(self):
515 def getcwd(self):
516 cwd = os.getcwd()
516 cwd = os.getcwd()
517 if cwd == self.root: return ''
517 if cwd == self.root: return ''
518 return cwd[len(self.root) + 1:]
518 return cwd[len(self.root) + 1:]
519
519
520 def wfile(self, f, mode='r'):
520 def wfile(self, f, mode='r'):
521 return self.wopener(f, mode)
521 return self.wopener(f, mode)
522
522
523 def transaction(self):
523 def transaction(self):
524 # save dirstate for undo
524 # save dirstate for undo
525 try:
525 try:
526 ds = self.opener("dirstate").read()
526 ds = self.opener("dirstate").read()
527 except IOError:
527 except IOError:
528 ds = ""
528 ds = ""
529 self.opener("undo.dirstate", "w").write(ds)
529 self.opener("undo.dirstate", "w").write(ds)
530
530
531 return transaction.transaction(self.ui.warn,
531 return transaction.transaction(self.ui.warn,
532 self.opener, self.join("journal"),
532 self.opener, self.join("journal"),
533 self.join("undo"))
533 self.join("undo"))
534
534
535 def recover(self):
535 def recover(self):
536 lock = self.lock()
536 lock = self.lock()
537 if os.path.exists(self.join("journal")):
537 if os.path.exists(self.join("journal")):
538 self.ui.status("rolling back interrupted transaction\n")
538 self.ui.status("rolling back interrupted transaction\n")
539 return transaction.rollback(self.opener, self.join("journal"))
539 return transaction.rollback(self.opener, self.join("journal"))
540 else:
540 else:
541 self.ui.warn("no interrupted transaction available\n")
541 self.ui.warn("no interrupted transaction available\n")
542
542
543 def undo(self):
543 def undo(self):
544 lock = self.lock()
544 lock = self.lock()
545 if os.path.exists(self.join("undo")):
545 if os.path.exists(self.join("undo")):
546 self.ui.status("rolling back last transaction\n")
546 self.ui.status("rolling back last transaction\n")
547 transaction.rollback(self.opener, self.join("undo"))
547 transaction.rollback(self.opener, self.join("undo"))
548 self.dirstate = None
548 self.dirstate = None
549 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
549 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
550 self.dirstate = dirstate(self.opener, self.ui, self.root)
550 self.dirstate = dirstate(self.opener, self.ui, self.root)
551 else:
551 else:
552 self.ui.warn("no undo information available\n")
552 self.ui.warn("no undo information available\n")
553
553
554 def lock(self, wait = 1):
554 def lock(self, wait = 1):
555 try:
555 try:
556 return lock.lock(self.join("lock"), 0)
556 return lock.lock(self.join("lock"), 0)
557 except lock.LockHeld, inst:
557 except lock.LockHeld, inst:
558 if wait:
558 if wait:
559 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
559 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
560 return lock.lock(self.join("lock"), wait)
560 return lock.lock(self.join("lock"), wait)
561 raise inst
561 raise inst
562
562
563 def rawcommit(self, files, text, user, date, p1=None, p2=None):
563 def rawcommit(self, files, text, user, date, p1=None, p2=None):
564 orig_parent = self.dirstate.parents()[0] or nullid
564 orig_parent = self.dirstate.parents()[0] or nullid
565 p1 = p1 or self.dirstate.parents()[0] or nullid
565 p1 = p1 or self.dirstate.parents()[0] or nullid
566 p2 = p2 or self.dirstate.parents()[1] or nullid
566 p2 = p2 or self.dirstate.parents()[1] or nullid
567 c1 = self.changelog.read(p1)
567 c1 = self.changelog.read(p1)
568 c2 = self.changelog.read(p2)
568 c2 = self.changelog.read(p2)
569 m1 = self.manifest.read(c1[0])
569 m1 = self.manifest.read(c1[0])
570 mf1 = self.manifest.readflags(c1[0])
570 mf1 = self.manifest.readflags(c1[0])
571 m2 = self.manifest.read(c2[0])
571 m2 = self.manifest.read(c2[0])
572
572
573 if orig_parent == p1:
573 if orig_parent == p1:
574 update_dirstate = 1
574 update_dirstate = 1
575 else:
575 else:
576 update_dirstate = 0
576 update_dirstate = 0
577
577
578 tr = self.transaction()
578 tr = self.transaction()
579 mm = m1.copy()
579 mm = m1.copy()
580 mfm = mf1.copy()
580 mfm = mf1.copy()
581 linkrev = self.changelog.count()
581 linkrev = self.changelog.count()
582 for f in files:
582 for f in files:
583 try:
583 try:
584 t = self.wfile(f).read()
584 t = self.wfile(f).read()
585 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
585 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
586 r = self.file(f)
586 r = self.file(f)
587 mfm[f] = tm
587 mfm[f] = tm
588 mm[f] = r.add(t, {}, tr, linkrev,
588 mm[f] = r.add(t, {}, tr, linkrev,
589 m1.get(f, nullid), m2.get(f, nullid))
589 m1.get(f, nullid), m2.get(f, nullid))
590 if update_dirstate:
590 if update_dirstate:
591 self.dirstate.update([f], "n")
591 self.dirstate.update([f], "n")
592 except IOError:
592 except IOError:
593 try:
593 try:
594 del mm[f]
594 del mm[f]
595 del mfm[f]
595 del mfm[f]
596 if update_dirstate:
596 if update_dirstate:
597 self.dirstate.forget([f])
597 self.dirstate.forget([f])
598 except:
598 except:
599 # deleted from p2?
599 # deleted from p2?
600 pass
600 pass
601
601
602 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
602 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
603 user = user or self.ui.username()
603 user = user or self.ui.username()
604 n = self.changelog.add(mnode, files, text, tr, p1, p2, user, date)
604 n = self.changelog.add(mnode, files, text, tr, p1, p2, user, date)
605 tr.close()
605 tr.close()
606 if update_dirstate:
606 if update_dirstate:
607 self.dirstate.setparents(n, nullid)
607 self.dirstate.setparents(n, nullid)
608
608
609 def commit(self, files = None, text = "", user = None, date = None):
609 def commit(self, files = None, text = "", user = None, date = None):
610 commit = []
610 commit = []
611 remove = []
611 remove = []
612 if files:
612 if files:
613 for f in files:
613 for f in files:
614 s = self.dirstate.state(f)
614 s = self.dirstate.state(f)
615 if s in 'nmai':
615 if s in 'nmai':
616 commit.append(f)
616 commit.append(f)
617 elif s == 'r':
617 elif s == 'r':
618 remove.append(f)
618 remove.append(f)
619 else:
619 else:
620 self.ui.warn("%s not tracked!\n" % f)
620 self.ui.warn("%s not tracked!\n" % f)
621 else:
621 else:
622 (c, a, d, u) = self.changes(None, None)
622 (c, a, d, u) = self.changes(None, None)
623 commit = c + a
623 commit = c + a
624 remove = d
624 remove = d
625
625
626 if not commit and not remove:
626 if not commit and not remove:
627 self.ui.status("nothing changed\n")
627 self.ui.status("nothing changed\n")
628 return
628 return
629
629
630 if not self.hook("precommit"):
630 if not self.hook("precommit"):
631 return 1
631 return 1
632
632
633 p1, p2 = self.dirstate.parents()
633 p1, p2 = self.dirstate.parents()
634 c1 = self.changelog.read(p1)
634 c1 = self.changelog.read(p1)
635 c2 = self.changelog.read(p2)
635 c2 = self.changelog.read(p2)
636 m1 = self.manifest.read(c1[0])
636 m1 = self.manifest.read(c1[0])
637 mf1 = self.manifest.readflags(c1[0])
637 mf1 = self.manifest.readflags(c1[0])
638 m2 = self.manifest.read(c2[0])
638 m2 = self.manifest.read(c2[0])
639 lock = self.lock()
639 lock = self.lock()
640 tr = self.transaction()
640 tr = self.transaction()
641
641
642 # check in files
642 # check in files
643 new = {}
643 new = {}
644 linkrev = self.changelog.count()
644 linkrev = self.changelog.count()
645 commit.sort()
645 commit.sort()
646 for f in commit:
646 for f in commit:
647 self.ui.note(f + "\n")
647 self.ui.note(f + "\n")
648 try:
648 try:
649 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
649 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
650 t = self.wfile(f).read()
650 t = self.wfile(f).read()
651 except IOError:
651 except IOError:
652 self.warn("trouble committing %s!\n" % f)
652 self.warn("trouble committing %s!\n" % f)
653 raise
653 raise
654
654
655 meta = {}
655 meta = {}
656 cp = self.dirstate.copied(f)
656 cp = self.dirstate.copied(f)
657 if cp:
657 if cp:
658 meta["copy"] = cp
658 meta["copy"] = cp
659 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
659 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
660 self.ui.debug(" %s: copy %s:%s\n" % (f, cp, meta["copyrev"]))
660 self.ui.debug(" %s: copy %s:%s\n" % (f, cp, meta["copyrev"]))
661
661
662 r = self.file(f)
662 r = self.file(f)
663 fp1 = m1.get(f, nullid)
663 fp1 = m1.get(f, nullid)
664 fp2 = m2.get(f, nullid)
664 fp2 = m2.get(f, nullid)
665 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
665 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
666
666
667 # update manifest
667 # update manifest
668 m1.update(new)
668 m1.update(new)
669 for f in remove:
669 for f in remove:
670 if f in m1:
670 if f in m1:
671 del m1[f]
671 del m1[f]
672 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0])
672 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0])
673
673
674 # add changeset
674 # add changeset
675 new = new.keys()
675 new = new.keys()
676 new.sort()
676 new.sort()
677
677
678 if not text:
678 if not text:
679 edittext = "\n" + "HG: manifest hash %s\n" % hex(mn)
679 edittext = "\n" + "HG: manifest hash %s\n" % hex(mn)
680 edittext += "".join(["HG: changed %s\n" % f for f in new])
680 edittext += "".join(["HG: changed %s\n" % f for f in new])
681 edittext += "".join(["HG: removed %s\n" % f for f in remove])
681 edittext += "".join(["HG: removed %s\n" % f for f in remove])
682 edittext = self.ui.edit(edittext)
682 edittext = self.ui.edit(edittext)
683 if not edittext.rstrip():
683 if not edittext.rstrip():
684 return 1
684 return 1
685 text = edittext
685 text = edittext
686
686
687 user = user or self.ui.username()
687 user = user or self.ui.username()
688 n = self.changelog.add(mn, new, text, tr, p1, p2, user, date)
688 n = self.changelog.add(mn, new, text, tr, p1, p2, user, date)
689
689
690 if not self.hook("commit", node=hex(n)):
690 if not self.hook("commit", node=hex(n)):
691 return 1
691 return 1
692
692
693 tr.close()
693 tr.close()
694
694
695 self.dirstate.setparents(n)
695 self.dirstate.setparents(n)
696 self.dirstate.update(new, "n")
696 self.dirstate.update(new, "n")
697 self.dirstate.forget(remove)
697 self.dirstate.forget(remove)
698
698
699 def changes(self, node1, node2, files=None):
699 def changes(self, node1, node2, files=None):
700 mf2, u = None, []
700 mf2, u = None, []
701
701
702 def fcmp(fn, mf):
702 def fcmp(fn, mf):
703 t1 = self.wfile(fn).read()
703 t1 = self.wfile(fn).read()
704 t2 = self.file(fn).revision(mf[fn])
704 t2 = self.file(fn).revision(mf[fn])
705 return cmp(t1, t2)
705 return cmp(t1, t2)
706
706
707 # are we comparing the working directory?
707 # are we comparing the working directory?
708 if not node2:
708 if not node2:
709 l, c, a, d, u = self.dirstate.changes(files, self.ignore)
709 l, c, a, d, u = self.dirstate.changes(files, self.ignore)
710
710
711 # are we comparing working dir against its parent?
711 # are we comparing working dir against its parent?
712 if not node1:
712 if not node1:
713 if l:
713 if l:
714 # do a full compare of any files that might have changed
714 # do a full compare of any files that might have changed
715 change = self.changelog.read(self.dirstate.parents()[0])
715 change = self.changelog.read(self.dirstate.parents()[0])
716 mf2 = self.manifest.read(change[0])
716 mf2 = self.manifest.read(change[0])
717 for f in l:
717 for f in l:
718 if fcmp(f, mf2):
718 if fcmp(f, mf2):
719 c.append(f)
719 c.append(f)
720
720
721 for l in c, a, d, u:
721 for l in c, a, d, u:
722 l.sort()
722 l.sort()
723
723
724 return (c, a, d, u)
724 return (c, a, d, u)
725
725
726 # are we comparing working dir against non-tip?
726 # are we comparing working dir against non-tip?
727 # generate a pseudo-manifest for the working dir
727 # generate a pseudo-manifest for the working dir
728 if not node2:
728 if not node2:
729 if not mf2:
729 if not mf2:
730 change = self.changelog.read(self.dirstate.parents()[0])
730 change = self.changelog.read(self.dirstate.parents()[0])
731 mf2 = self.manifest.read(change[0]).copy()
731 mf2 = self.manifest.read(change[0]).copy()
732 for f in a + c + l:
732 for f in a + c + l:
733 mf2[f] = ""
733 mf2[f] = ""
734 for f in d:
734 for f in d:
735 if f in mf2: del mf2[f]
735 if f in mf2: del mf2[f]
736 else:
736 else:
737 change = self.changelog.read(node2)
737 change = self.changelog.read(node2)
738 mf2 = self.manifest.read(change[0])
738 mf2 = self.manifest.read(change[0])
739
739
740 # flush lists from dirstate before comparing manifests
740 # flush lists from dirstate before comparing manifests
741 c, a = [], []
741 c, a = [], []
742
742
743 change = self.changelog.read(node1)
743 change = self.changelog.read(node1)
744 mf1 = self.manifest.read(change[0]).copy()
744 mf1 = self.manifest.read(change[0]).copy()
745
745
746 for fn in mf2:
746 for fn in mf2:
747 if mf1.has_key(fn):
747 if mf1.has_key(fn):
748 if mf1[fn] != mf2[fn]:
748 if mf1[fn] != mf2[fn]:
749 if mf2[fn] != "" or fcmp(fn, mf1):
749 if mf2[fn] != "" or fcmp(fn, mf1):
750 c.append(fn)
750 c.append(fn)
751 del mf1[fn]
751 del mf1[fn]
752 else:
752 else:
753 a.append(fn)
753 a.append(fn)
754
754
755 d = mf1.keys()
755 d = mf1.keys()
756
756
757 for l in c, a, d, u:
757 for l in c, a, d, u:
758 l.sort()
758 l.sort()
759
759
760 return (c, a, d, u)
760 return (c, a, d, u)
761
761
762 def add(self, list):
762 def add(self, list):
763 for f in list:
763 for f in list:
764 p = self.wjoin(f)
764 p = self.wjoin(f)
765 if not os.path.exists(p):
765 if not os.path.exists(p):
766 self.ui.warn("%s does not exist!\n" % f)
766 self.ui.warn("%s does not exist!\n" % f)
767 elif not os.path.isfile(p):
767 elif not os.path.isfile(p):
768 self.ui.warn("%s not added: mercurial only supports files currently\n" % f)
768 self.ui.warn("%s not added: mercurial only supports files currently\n" % f)
769 elif self.dirstate.state(f) == 'n':
769 elif self.dirstate.state(f) == 'n':
770 self.ui.warn("%s already tracked!\n" % f)
770 self.ui.warn("%s already tracked!\n" % f)
771 else:
771 else:
772 self.dirstate.update([f], "a")
772 self.dirstate.update([f], "a")
773
773
774 def forget(self, list):
774 def forget(self, list):
775 for f in list:
775 for f in list:
776 if self.dirstate.state(f) not in 'ai':
776 if self.dirstate.state(f) not in 'ai':
777 self.ui.warn("%s not added!\n" % f)
777 self.ui.warn("%s not added!\n" % f)
778 else:
778 else:
779 self.dirstate.forget([f])
779 self.dirstate.forget([f])
780
780
781 def remove(self, list):
781 def remove(self, list):
782 for f in list:
782 for f in list:
783 p = self.wjoin(f)
783 p = self.wjoin(f)
784 if os.path.exists(p):
784 if os.path.exists(p):
785 self.ui.warn("%s still exists!\n" % f)
785 self.ui.warn("%s still exists!\n" % f)
786 elif self.dirstate.state(f) == 'a':
786 elif self.dirstate.state(f) == 'a':
787 self.ui.warn("%s never committed!\n" % f)
787 self.ui.warn("%s never committed!\n" % f)
788 self.dirstate.forget(f)
788 self.dirstate.forget(f)
789 elif f not in self.dirstate:
789 elif f not in self.dirstate:
790 self.ui.warn("%s not tracked!\n" % f)
790 self.ui.warn("%s not tracked!\n" % f)
791 else:
791 else:
792 self.dirstate.update([f], "r")
792 self.dirstate.update([f], "r")
793
793
794 def copy(self, source, dest):
794 def copy(self, source, dest):
795 p = self.wjoin(dest)
795 p = self.wjoin(dest)
796 if not os.path.exists(dest):
796 if not os.path.exists(dest):
797 self.ui.warn("%s does not exist!\n" % dest)
797 self.ui.warn("%s does not exist!\n" % dest)
798 elif not os.path.isfile(dest):
798 elif not os.path.isfile(dest):
799 self.ui.warn("copy failed: %s is not a file\n" % dest)
799 self.ui.warn("copy failed: %s is not a file\n" % dest)
800 else:
800 else:
801 if self.dirstate.state(dest) == '?':
801 if self.dirstate.state(dest) == '?':
802 self.dirstate.update([dest], "a")
802 self.dirstate.update([dest], "a")
803 self.dirstate.copy(source, dest)
803 self.dirstate.copy(source, dest)
804
804
805 def heads(self):
805 def heads(self):
806 return self.changelog.heads()
806 return self.changelog.heads()
807
807
808 def branches(self, nodes):
808 def branches(self, nodes):
809 if not nodes: nodes = [self.changelog.tip()]
809 if not nodes: nodes = [self.changelog.tip()]
810 b = []
810 b = []
811 for n in nodes:
811 for n in nodes:
812 t = n
812 t = n
813 while n:
813 while n:
814 p = self.changelog.parents(n)
814 p = self.changelog.parents(n)
815 if p[1] != nullid or p[0] == nullid:
815 if p[1] != nullid or p[0] == nullid:
816 b.append((t, n, p[0], p[1]))
816 b.append((t, n, p[0], p[1]))
817 break
817 break
818 n = p[0]
818 n = p[0]
819 return b
819 return b
820
820
821 def between(self, pairs):
821 def between(self, pairs):
822 r = []
822 r = []
823
823
824 for top, bottom in pairs:
824 for top, bottom in pairs:
825 n, l, i = top, [], 0
825 n, l, i = top, [], 0
826 f = 1
826 f = 1
827
827
828 while n != bottom:
828 while n != bottom:
829 p = self.changelog.parents(n)[0]
829 p = self.changelog.parents(n)[0]
830 if i == f:
830 if i == f:
831 l.append(n)
831 l.append(n)
832 f = f * 2
832 f = f * 2
833 n = p
833 n = p
834 i += 1
834 i += 1
835
835
836 r.append(l)
836 r.append(l)
837
837
838 return r
838 return r
839
839
840 def newer(self, nodes):
840 def newer(self, nodes):
841 m = {}
841 m = {}
842 nl = []
842 nl = []
843 pm = {}
843 pm = {}
844 cl = self.changelog
844 cl = self.changelog
845 t = l = cl.count()
845 t = l = cl.count()
846
846
847 # find the lowest numbered node
847 # find the lowest numbered node
848 for n in nodes:
848 for n in nodes:
849 l = min(l, cl.rev(n))
849 l = min(l, cl.rev(n))
850 m[n] = 1
850 m[n] = 1
851
851
852 for i in xrange(l, t):
852 for i in xrange(l, t):
853 n = cl.node(i)
853 n = cl.node(i)
854 if n in m: # explicitly listed
854 if n in m: # explicitly listed
855 pm[n] = 1
855 pm[n] = 1
856 nl.append(n)
856 nl.append(n)
857 continue
857 continue
858 for p in cl.parents(n):
858 for p in cl.parents(n):
859 if p in pm: # parent listed
859 if p in pm: # parent listed
860 pm[n] = 1
860 pm[n] = 1
861 nl.append(n)
861 nl.append(n)
862 break
862 break
863
863
864 return nl
864 return nl
865
865
866 def findincoming(self, remote, base={}):
866 def findincoming(self, remote, base={}):
867 m = self.changelog.nodemap
867 m = self.changelog.nodemap
868 search = []
868 search = []
869 fetch = []
869 fetch = []
870 seen = {}
870 seen = {}
871 seenbranch = {}
871 seenbranch = {}
872
872
873 # if we have an empty repo, fetch everything
873 # if we have an empty repo, fetch everything
874 if self.changelog.tip() == nullid:
874 if self.changelog.tip() == nullid:
875 self.ui.status("requesting all changes\n")
875 self.ui.status("requesting all changes\n")
876 return [nullid]
876 return [nullid]
877
877
878 # otherwise, assume we're closer to the tip than the root
878 # otherwise, assume we're closer to the tip than the root
879 # and start by examining the heads
879 # and start by examining the heads
880 self.ui.status("searching for changes\n")
880 self.ui.status("searching for changes\n")
881 heads = remote.heads()
881 heads = remote.heads()
882 unknown = []
882 unknown = []
883 for h in heads:
883 for h in heads:
884 if h not in m:
884 if h not in m:
885 unknown.append(h)
885 unknown.append(h)
886 else:
886 else:
887 base[h] = 1
887 base[h] = 1
888
888
889 if not unknown:
889 if not unknown:
890 return None
890 return None
891
891
892 rep = {}
892 rep = {}
893 reqcnt = 0
893 reqcnt = 0
894
894
895 # search through remote branches
895 # search through remote branches
896 # a 'branch' here is a linear segment of history, with four parts:
896 # a 'branch' here is a linear segment of history, with four parts:
897 # head, root, first parent, second parent
897 # head, root, first parent, second parent
898 # (a branch always has two parents (or none) by definition)
898 # (a branch always has two parents (or none) by definition)
899 unknown = remote.branches(unknown)
899 unknown = remote.branches(unknown)
900 while unknown:
900 while unknown:
901 r = []
901 r = []
902 while unknown:
902 while unknown:
903 n = unknown.pop(0)
903 n = unknown.pop(0)
904 if n[0] in seen:
904 if n[0] in seen:
905 continue
905 continue
906
906
907 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
907 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
908 if n[0] == nullid:
908 if n[0] == nullid:
909 break
909 break
910 if n in seenbranch:
910 if n in seenbranch:
911 self.ui.debug("branch already found\n")
911 self.ui.debug("branch already found\n")
912 continue
912 continue
913 if n[1] and n[1] in m: # do we know the base?
913 if n[1] and n[1] in m: # do we know the base?
914 self.ui.debug("found incomplete branch %s:%s\n"
914 self.ui.debug("found incomplete branch %s:%s\n"
915 % (short(n[0]), short(n[1])))
915 % (short(n[0]), short(n[1])))
916 search.append(n) # schedule branch range for scanning
916 search.append(n) # schedule branch range for scanning
917 seenbranch[n] = 1
917 seenbranch[n] = 1
918 else:
918 else:
919 if n[1] not in seen and n[1] not in fetch:
919 if n[1] not in seen and n[1] not in fetch:
920 if n[2] in m and n[3] in m:
920 if n[2] in m and n[3] in m:
921 self.ui.debug("found new changeset %s\n" %
921 self.ui.debug("found new changeset %s\n" %
922 short(n[1]))
922 short(n[1]))
923 fetch.append(n[1]) # earliest unknown
923 fetch.append(n[1]) # earliest unknown
924 base[n[2]] = 1 # latest known
924 base[n[2]] = 1 # latest known
925 continue
925 continue
926
926
927 for a in n[2:4]:
927 for a in n[2:4]:
928 if a not in rep:
928 if a not in rep:
929 r.append(a)
929 r.append(a)
930 rep[a] = 1
930 rep[a] = 1
931
931
932 seen[n[0]] = 1
932 seen[n[0]] = 1
933
933
934 if r:
934 if r:
935 reqcnt += 1
935 reqcnt += 1
936 self.ui.debug("request %d: %s\n" %
936 self.ui.debug("request %d: %s\n" %
937 (reqcnt, " ".join(map(short, r))))
937 (reqcnt, " ".join(map(short, r))))
938 for p in range(0, len(r), 10):
938 for p in range(0, len(r), 10):
939 for b in remote.branches(r[p:p+10]):
939 for b in remote.branches(r[p:p+10]):
940 self.ui.debug("received %s:%s\n" %
940 self.ui.debug("received %s:%s\n" %
941 (short(b[0]), short(b[1])))
941 (short(b[0]), short(b[1])))
942 if b[0] not in m and b[0] not in seen:
942 if b[0] not in m and b[0] not in seen:
943 unknown.append(b)
943 unknown.append(b)
944
944
945 # do binary search on the branches we found
945 # do binary search on the branches we found
946 while search:
946 while search:
947 n = search.pop(0)
947 n = search.pop(0)
948 reqcnt += 1
948 reqcnt += 1
949 l = remote.between([(n[0], n[1])])[0]
949 l = remote.between([(n[0], n[1])])[0]
950 l.append(n[1])
950 l.append(n[1])
951 p = n[0]
951 p = n[0]
952 f = 1
952 f = 1
953 for i in l:
953 for i in l:
954 self.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
954 self.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
955 if i in m:
955 if i in m:
956 if f <= 2:
956 if f <= 2:
957 self.ui.debug("found new branch changeset %s\n" %
957 self.ui.debug("found new branch changeset %s\n" %
958 short(p))
958 short(p))
959 fetch.append(p)
959 fetch.append(p)
960 base[i] = 1
960 base[i] = 1
961 else:
961 else:
962 self.ui.debug("narrowed branch search to %s:%s\n"
962 self.ui.debug("narrowed branch search to %s:%s\n"
963 % (short(p), short(i)))
963 % (short(p), short(i)))
964 search.append((p, i))
964 search.append((p, i))
965 break
965 break
966 p, f = i, f * 2
966 p, f = i, f * 2
967
967
968 # sanity check our fetch list
968 # sanity check our fetch list
969 for f in fetch:
969 for f in fetch:
970 if f in m:
970 if f in m:
971 raise RepoError("already have changeset " + short(f[:4]))
971 raise RepoError("already have changeset " + short(f[:4]))
972
972
973 if base.keys() == [nullid]:
973 if base.keys() == [nullid]:
974 self.ui.warn("warning: pulling from an unrelated repository!\n")
974 self.ui.warn("warning: pulling from an unrelated repository!\n")
975
975
976 self.ui.note("adding new changesets starting at " +
976 self.ui.note("adding new changesets starting at " +
977 " ".join([short(f) for f in fetch]) + "\n")
977 " ".join([short(f) for f in fetch]) + "\n")
978
978
979 self.ui.debug("%d total queries\n" % reqcnt)
979 self.ui.debug("%d total queries\n" % reqcnt)
980
980
981 return fetch
981 return fetch
982
982
983 def findoutgoing(self, remote):
983 def findoutgoing(self, remote):
984 base = {}
984 base = {}
985 findincoming(self, remote, base)
985 findincoming(self, remote, base)
986 remain = dict.fromkeys(self.changelog.nodemap)
986 remain = dict.fromkeys(self.changelog.nodemap)
987
987
988 # prune everything remote has from the tree
988 # prune everything remote has from the tree
989 remove = base.keys()
989 remove = base.keys()
990 while remove:
990 while remove:
991 n = remove.pop(0)
991 n = remove.pop(0)
992 if n in remain:
992 if n in remain:
993 del remain[n]
993 del remain[n]
994 for p in self.changelog.parents(n):
994 for p in self.changelog.parents(n):
995 remain.append(p)
995 remain.append(p)
996
996
997 # find every node whose parents have been pruned
997 # find every node whose parents have been pruned
998 subset = []
998 subset = []
999 for n in remain:
999 for n in remain:
1000 p1, p2 = self.changelog.parents(n)
1000 p1, p2 = self.changelog.parents(n)
1001 if p1 not in remain and p2 not in remain:
1001 if p1 not in remain and p2 not in remain:
1002 subset.append(n)
1002 subset.append(n)
1003
1003
1004 # this is the set of all roots we have to push
1004 # this is the set of all roots we have to push
1005 return subset
1005 return subset
1006
1006
1007 def pull(self, remote):
1007 def pull(self, remote):
1008 lock = self.lock()
1008 lock = self.lock()
1009 fetch = self.findincoming(remote)
1009 fetch = self.findincoming(remote)
1010 if not fetch:
1010 if not fetch:
1011 self.ui.status("no changes found\n")
1011 self.ui.status("no changes found\n")
1012 return 1
1012 return 1
1013
1013
1014 cg = remote.changegroup(fetch)
1014 cg = remote.changegroup(fetch)
1015 return self.addchangegroup(cg)
1015 return self.addchangegroup(cg)
1016
1016
1017 def push(self, remote):
1017 def push(self, remote):
1018 lock = remote.lock()
1018 lock = remote.lock()
1019 update = self.findoutgoing(remote)
1019 update = self.findoutgoing(remote)
1020 if not update:
1020 if not update:
1021 self.ui.status("no changes found\n")
1021 self.ui.status("no changes found\n")
1022 return 1
1022 return 1
1023
1023
1024 cg = self.changegroup(update)
1024 cg = self.changegroup(update)
1025 return remote.addchangegroup(cg)
1025 return remote.addchangegroup(cg)
1026
1026
1027 def changegroup(self, basenodes):
1027 def changegroup(self, basenodes):
1028 nodes = self.newer(basenodes)
1029
1030 # construct the link map
1031 linkmap = {}
1032 for n in nodes:
1033 linkmap[self.changelog.rev(n)] = n
1034
1035 # construct a list of all changed files
1036 changed = {}
1037 for n in nodes:
1038 c = self.changelog.read(n)
1039 for f in c[3]:
1040 changed[f] = 1
1041 changed = changed.keys()
1042 changed.sort()
1043
1044 # the changegroup is changesets + manifests + all file revs
1045 revs = [ self.changelog.rev(n) for n in nodes ]
1046
1047 for y in self.changelog.group(linkmap): yield y
1048 for y in self.manifest.group(linkmap): yield y
1049 for f in changed:
1050 yield struct.pack(">l", len(f) + 4) + f
1051 g = self.file(f).group(linkmap)
1052 for y in g:
1053 yield y
1054
1055 def addchangegroup(self, generator):
1056
1057 class genread:
1028 class genread:
1058 def __init__(self, generator):
1029 def __init__(self, generator):
1059 self.g = generator
1030 self.g = generator
1060 self.buf = ""
1031 self.buf = ""
1061 def read(self, l):
1032 def read(self, l):
1062 while l > len(self.buf):
1033 while l > len(self.buf):
1063 try:
1034 try:
1064 self.buf += self.g.next()
1035 self.buf += self.g.next()
1065 except StopIteration:
1036 except StopIteration:
1066 break
1037 break
1067 d, self.buf = self.buf[:l], self.buf[l:]
1038 d, self.buf = self.buf[:l], self.buf[l:]
1068 return d
1039 return d
1069
1040
1041 def gengroup():
1042 nodes = self.newer(basenodes)
1043
1044 # construct the link map
1045 linkmap = {}
1046 for n in nodes:
1047 linkmap[self.changelog.rev(n)] = n
1048
1049 # construct a list of all changed files
1050 changed = {}
1051 for n in nodes:
1052 c = self.changelog.read(n)
1053 for f in c[3]:
1054 changed[f] = 1
1055 changed = changed.keys()
1056 changed.sort()
1057
1058 # the changegroup is changesets + manifests + all file revs
1059 revs = [ self.changelog.rev(n) for n in nodes ]
1060
1061 for y in self.changelog.group(linkmap): yield y
1062 for y in self.manifest.group(linkmap): yield y
1063 for f in changed:
1064 yield struct.pack(">l", len(f) + 4) + f
1065 g = self.file(f).group(linkmap)
1066 for y in g:
1067 yield y
1068
1069 yield struct.pack(">l", 0)
1070
1071 return genread(gengroup())
1072
1073 def addchangegroup(self, source):
1074
1070 def getchunk():
1075 def getchunk():
1071 d = source.read(4)
1076 d = source.read(4)
1072 if not d: return ""
1077 if not d: return ""
1073 l = struct.unpack(">l", d)[0]
1078 l = struct.unpack(">l", d)[0]
1074 if l <= 4: return ""
1079 if l <= 4: return ""
1075 return source.read(l - 4)
1080 return source.read(l - 4)
1076
1081
1077 def getgroup():
1082 def getgroup():
1078 while 1:
1083 while 1:
1079 c = getchunk()
1084 c = getchunk()
1080 if not c: break
1085 if not c: break
1081 yield c
1086 yield c
1082
1087
1083 def csmap(x):
1088 def csmap(x):
1084 self.ui.debug("add changeset %s\n" % short(x))
1089 self.ui.debug("add changeset %s\n" % short(x))
1085 return self.changelog.count()
1090 return self.changelog.count()
1086
1091
1087 def revmap(x):
1092 def revmap(x):
1088 return self.changelog.rev(x)
1093 return self.changelog.rev(x)
1089
1094
1090 if not generator: return
1095 if not source: return
1091 changesets = files = revisions = 0
1096 changesets = files = revisions = 0
1092
1097
1093 source = genread(generator)
1094 tr = self.transaction()
1098 tr = self.transaction()
1095
1099
1096 # pull off the changeset group
1100 # pull off the changeset group
1097 self.ui.status("adding changesets\n")
1101 self.ui.status("adding changesets\n")
1098 co = self.changelog.tip()
1102 co = self.changelog.tip()
1099 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
1103 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
1100 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
1104 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
1101
1105
1102 # pull off the manifest group
1106 # pull off the manifest group
1103 self.ui.status("adding manifests\n")
1107 self.ui.status("adding manifests\n")
1104 mm = self.manifest.tip()
1108 mm = self.manifest.tip()
1105 mo = self.manifest.addgroup(getgroup(), revmap, tr)
1109 mo = self.manifest.addgroup(getgroup(), revmap, tr)
1106
1110
1107 # process the files
1111 # process the files
1108 self.ui.status("adding file revisions\n")
1112 self.ui.status("adding file revisions\n")
1109 while 1:
1113 while 1:
1110 f = getchunk()
1114 f = getchunk()
1111 if not f: break
1115 if not f: break
1112 self.ui.debug("adding %s revisions\n" % f)
1116 self.ui.debug("adding %s revisions\n" % f)
1113 fl = self.file(f)
1117 fl = self.file(f)
1114 o = fl.count()
1118 o = fl.count()
1115 n = fl.addgroup(getgroup(), revmap, tr)
1119 n = fl.addgroup(getgroup(), revmap, tr)
1116 revisions += fl.count() - o
1120 revisions += fl.count() - o
1117 files += 1
1121 files += 1
1118
1122
1119 self.ui.status(("modified %d files, added %d changesets" +
1123 self.ui.status(("modified %d files, added %d changesets" +
1120 " and %d new revisions\n")
1124 " and %d new revisions\n")
1121 % (files, changesets, revisions))
1125 % (files, changesets, revisions))
1122
1126
1123 tr.close()
1127 tr.close()
1124 return
1128 return
1125
1129
1126 def update(self, node, allow=False, force=False, choose=None,
1130 def update(self, node, allow=False, force=False, choose=None,
1127 moddirstate=True):
1131 moddirstate=True):
1128 pl = self.dirstate.parents()
1132 pl = self.dirstate.parents()
1129 if not force and pl[1] != nullid:
1133 if not force and pl[1] != nullid:
1130 self.ui.warn("aborting: outstanding uncommitted merges\n")
1134 self.ui.warn("aborting: outstanding uncommitted merges\n")
1131 return
1135 return
1132
1136
1133 p1, p2 = pl[0], node
1137 p1, p2 = pl[0], node
1134 pa = self.changelog.ancestor(p1, p2)
1138 pa = self.changelog.ancestor(p1, p2)
1135 m1n = self.changelog.read(p1)[0]
1139 m1n = self.changelog.read(p1)[0]
1136 m2n = self.changelog.read(p2)[0]
1140 m2n = self.changelog.read(p2)[0]
1137 man = self.manifest.ancestor(m1n, m2n)
1141 man = self.manifest.ancestor(m1n, m2n)
1138 m1 = self.manifest.read(m1n)
1142 m1 = self.manifest.read(m1n)
1139 mf1 = self.manifest.readflags(m1n)
1143 mf1 = self.manifest.readflags(m1n)
1140 m2 = self.manifest.read(m2n)
1144 m2 = self.manifest.read(m2n)
1141 mf2 = self.manifest.readflags(m2n)
1145 mf2 = self.manifest.readflags(m2n)
1142 ma = self.manifest.read(man)
1146 ma = self.manifest.read(man)
1143 mfa = self.manifest.readflags(man)
1147 mfa = self.manifest.readflags(man)
1144
1148
1145 (c, a, d, u) = self.changes(None, None)
1149 (c, a, d, u) = self.changes(None, None)
1146
1150
1147 # is this a jump, or a merge? i.e. is there a linear path
1151 # is this a jump, or a merge? i.e. is there a linear path
1148 # from p1 to p2?
1152 # from p1 to p2?
1149 linear_path = (pa == p1 or pa == p2)
1153 linear_path = (pa == p1 or pa == p2)
1150
1154
1151 # resolve the manifest to determine which files
1155 # resolve the manifest to determine which files
1152 # we care about merging
1156 # we care about merging
1153 self.ui.note("resolving manifests\n")
1157 self.ui.note("resolving manifests\n")
1154 self.ui.debug(" ancestor %s local %s remote %s\n" %
1158 self.ui.debug(" ancestor %s local %s remote %s\n" %
1155 (short(man), short(m1n), short(m2n)))
1159 (short(man), short(m1n), short(m2n)))
1156
1160
1157 merge = {}
1161 merge = {}
1158 get = {}
1162 get = {}
1159 remove = []
1163 remove = []
1160 mark = {}
1164 mark = {}
1161
1165
1162 # construct a working dir manifest
1166 # construct a working dir manifest
1163 mw = m1.copy()
1167 mw = m1.copy()
1164 mfw = mf1.copy()
1168 mfw = mf1.copy()
1165 umap = dict.fromkeys(u)
1169 umap = dict.fromkeys(u)
1166
1170
1167 for f in a + c + u:
1171 for f in a + c + u:
1168 mw[f] = ""
1172 mw[f] = ""
1169 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1173 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1170
1174
1171 for f in d:
1175 for f in d:
1172 if f in mw: del mw[f]
1176 if f in mw: del mw[f]
1173
1177
1174 # If we're jumping between revisions (as opposed to merging),
1178 # If we're jumping between revisions (as opposed to merging),
1175 # and if neither the working directory nor the target rev has
1179 # and if neither the working directory nor the target rev has
1176 # the file, then we need to remove it from the dirstate, to
1180 # the file, then we need to remove it from the dirstate, to
1177 # prevent the dirstate from listing the file when it is no
1181 # prevent the dirstate from listing the file when it is no
1178 # longer in the manifest.
1182 # longer in the manifest.
1179 if moddirstate and linear_path and f not in m2:
1183 if moddirstate and linear_path and f not in m2:
1180 self.dirstate.forget((f,))
1184 self.dirstate.forget((f,))
1181
1185
1182 # Compare manifests
1186 # Compare manifests
1183 for f, n in mw.iteritems():
1187 for f, n in mw.iteritems():
1184 if choose and not choose(f): continue
1188 if choose and not choose(f): continue
1185 if f in m2:
1189 if f in m2:
1186 s = 0
1190 s = 0
1187
1191
1188 # is the wfile new since m1, and match m2?
1192 # is the wfile new since m1, and match m2?
1189 if f not in m1:
1193 if f not in m1:
1190 t1 = self.wfile(f).read()
1194 t1 = self.wfile(f).read()
1191 t2 = self.file(f).revision(m2[f])
1195 t2 = self.file(f).revision(m2[f])
1192 if cmp(t1, t2) == 0:
1196 if cmp(t1, t2) == 0:
1193 mark[f] = 1
1197 mark[f] = 1
1194 n = m2[f]
1198 n = m2[f]
1195 del t1, t2
1199 del t1, t2
1196
1200
1197 # are files different?
1201 # are files different?
1198 if n != m2[f]:
1202 if n != m2[f]:
1199 a = ma.get(f, nullid)
1203 a = ma.get(f, nullid)
1200 # are both different from the ancestor?
1204 # are both different from the ancestor?
1201 if n != a and m2[f] != a:
1205 if n != a and m2[f] != a:
1202 self.ui.debug(" %s versions differ, resolve\n" % f)
1206 self.ui.debug(" %s versions differ, resolve\n" % f)
1203 # merge executable bits
1207 # merge executable bits
1204 # "if we changed or they changed, change in merge"
1208 # "if we changed or they changed, change in merge"
1205 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1209 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1206 mode = ((a^b) | (a^c)) ^ a
1210 mode = ((a^b) | (a^c)) ^ a
1207 merge[f] = (m1.get(f, nullid), m2[f], mode)
1211 merge[f] = (m1.get(f, nullid), m2[f], mode)
1208 s = 1
1212 s = 1
1209 # are we clobbering?
1213 # are we clobbering?
1210 # is remote's version newer?
1214 # is remote's version newer?
1211 # or are we going back in time?
1215 # or are we going back in time?
1212 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1216 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1213 self.ui.debug(" remote %s is newer, get\n" % f)
1217 self.ui.debug(" remote %s is newer, get\n" % f)
1214 get[f] = m2[f]
1218 get[f] = m2[f]
1215 s = 1
1219 s = 1
1216 else:
1220 else:
1217 mark[f] = 1
1221 mark[f] = 1
1218 elif f in umap:
1222 elif f in umap:
1219 # this unknown file is the same as the checkout
1223 # this unknown file is the same as the checkout
1220 get[f] = m2[f]
1224 get[f] = m2[f]
1221
1225
1222 if not s and mfw[f] != mf2[f]:
1226 if not s and mfw[f] != mf2[f]:
1223 if force:
1227 if force:
1224 self.ui.debug(" updating permissions for %s\n" % f)
1228 self.ui.debug(" updating permissions for %s\n" % f)
1225 util.set_exec(self.wjoin(f), mf2[f])
1229 util.set_exec(self.wjoin(f), mf2[f])
1226 else:
1230 else:
1227 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1231 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1228 mode = ((a^b) | (a^c)) ^ a
1232 mode = ((a^b) | (a^c)) ^ a
1229 if mode != b:
1233 if mode != b:
1230 self.ui.debug(" updating permissions for %s\n" % f)
1234 self.ui.debug(" updating permissions for %s\n" % f)
1231 util.set_exec(self.wjoin(f), mode)
1235 util.set_exec(self.wjoin(f), mode)
1232 mark[f] = 1
1236 mark[f] = 1
1233 del m2[f]
1237 del m2[f]
1234 elif f in ma:
1238 elif f in ma:
1235 if n != ma[f]:
1239 if n != ma[f]:
1236 r = "d"
1240 r = "d"
1237 if not force and (linear_path or allow):
1241 if not force and (linear_path or allow):
1238 r = self.ui.prompt(
1242 r = self.ui.prompt(
1239 (" local changed %s which remote deleted\n" % f) +
1243 (" local changed %s which remote deleted\n" % f) +
1240 "(k)eep or (d)elete?", "[kd]", "k")
1244 "(k)eep or (d)elete?", "[kd]", "k")
1241 if r == "d":
1245 if r == "d":
1242 remove.append(f)
1246 remove.append(f)
1243 else:
1247 else:
1244 self.ui.debug("other deleted %s\n" % f)
1248 self.ui.debug("other deleted %s\n" % f)
1245 remove.append(f) # other deleted it
1249 remove.append(f) # other deleted it
1246 else:
1250 else:
1247 if n == m1.get(f, nullid): # same as parent
1251 if n == m1.get(f, nullid): # same as parent
1248 if p2 == pa: # going backwards?
1252 if p2 == pa: # going backwards?
1249 self.ui.debug("remote deleted %s\n" % f)
1253 self.ui.debug("remote deleted %s\n" % f)
1250 remove.append(f)
1254 remove.append(f)
1251 else:
1255 else:
1252 self.ui.debug("local created %s, keeping\n" % f)
1256 self.ui.debug("local created %s, keeping\n" % f)
1253 else:
1257 else:
1254 self.ui.debug("working dir created %s, keeping\n" % f)
1258 self.ui.debug("working dir created %s, keeping\n" % f)
1255
1259
1256 for f, n in m2.iteritems():
1260 for f, n in m2.iteritems():
1257 if choose and not choose(f): continue
1261 if choose and not choose(f): continue
1258 if f[0] == "/": continue
1262 if f[0] == "/": continue
1259 if f in ma and n != ma[f]:
1263 if f in ma and n != ma[f]:
1260 r = "k"
1264 r = "k"
1261 if not force and (linear_path or allow):
1265 if not force and (linear_path or allow):
1262 r = self.ui.prompt(
1266 r = self.ui.prompt(
1263 ("remote changed %s which local deleted\n" % f) +
1267 ("remote changed %s which local deleted\n" % f) +
1264 "(k)eep or (d)elete?", "[kd]", "k")
1268 "(k)eep or (d)elete?", "[kd]", "k")
1265 if r == "k": get[f] = n
1269 if r == "k": get[f] = n
1266 elif f not in ma:
1270 elif f not in ma:
1267 self.ui.debug("remote created %s\n" % f)
1271 self.ui.debug("remote created %s\n" % f)
1268 get[f] = n
1272 get[f] = n
1269 else:
1273 else:
1270 self.ui.debug("local deleted %s\n" % f)
1274 self.ui.debug("local deleted %s\n" % f)
1271
1275
1272 del mw, m1, m2, ma
1276 del mw, m1, m2, ma
1273
1277
1274 if force:
1278 if force:
1275 for f in merge:
1279 for f in merge:
1276 get[f] = merge[f][1]
1280 get[f] = merge[f][1]
1277 merge = {}
1281 merge = {}
1278
1282
1279 if linear_path:
1283 if linear_path:
1280 # we don't need to do any magic, just jump to the new rev
1284 # we don't need to do any magic, just jump to the new rev
1281 mode = 'n'
1285 mode = 'n'
1282 p1, p2 = p2, nullid
1286 p1, p2 = p2, nullid
1283 else:
1287 else:
1284 if not allow:
1288 if not allow:
1285 self.ui.status("this update spans a branch" +
1289 self.ui.status("this update spans a branch" +
1286 " affecting the following files:\n")
1290 " affecting the following files:\n")
1287 fl = merge.keys() + get.keys()
1291 fl = merge.keys() + get.keys()
1288 fl.sort()
1292 fl.sort()
1289 for f in fl:
1293 for f in fl:
1290 cf = ""
1294 cf = ""
1291 if f in merge: cf = " (resolve)"
1295 if f in merge: cf = " (resolve)"
1292 self.ui.status(" %s%s\n" % (f, cf))
1296 self.ui.status(" %s%s\n" % (f, cf))
1293 self.ui.warn("aborting update spanning branches!\n")
1297 self.ui.warn("aborting update spanning branches!\n")
1294 self.ui.status("(use update -m to perform a branch merge)\n")
1298 self.ui.status("(use update -m to perform a branch merge)\n")
1295 return 1
1299 return 1
1296 # we have to remember what files we needed to get/change
1300 # we have to remember what files we needed to get/change
1297 # because any file that's different from either one of its
1301 # because any file that's different from either one of its
1298 # parents must be in the changeset
1302 # parents must be in the changeset
1299 mode = 'm'
1303 mode = 'm'
1300 if moddirstate:
1304 if moddirstate:
1301 self.dirstate.update(mark.keys(), "m")
1305 self.dirstate.update(mark.keys(), "m")
1302
1306
1303 if moddirstate:
1307 if moddirstate:
1304 self.dirstate.setparents(p1, p2)
1308 self.dirstate.setparents(p1, p2)
1305
1309
1306 # get the files we don't need to change
1310 # get the files we don't need to change
1307 files = get.keys()
1311 files = get.keys()
1308 files.sort()
1312 files.sort()
1309 for f in files:
1313 for f in files:
1310 if f[0] == "/": continue
1314 if f[0] == "/": continue
1311 self.ui.note("getting %s\n" % f)
1315 self.ui.note("getting %s\n" % f)
1312 t = self.file(f).read(get[f])
1316 t = self.file(f).read(get[f])
1313 try:
1317 try:
1314 self.wfile(f, "w").write(t)
1318 self.wfile(f, "w").write(t)
1315 except IOError:
1319 except IOError:
1316 os.makedirs(os.path.dirname(self.wjoin(f)))
1320 os.makedirs(os.path.dirname(self.wjoin(f)))
1317 self.wfile(f, "w").write(t)
1321 self.wfile(f, "w").write(t)
1318 util.set_exec(self.wjoin(f), mf2[f])
1322 util.set_exec(self.wjoin(f), mf2[f])
1319 if moddirstate:
1323 if moddirstate:
1320 self.dirstate.update([f], mode)
1324 self.dirstate.update([f], mode)
1321
1325
1322 # merge the tricky bits
1326 # merge the tricky bits
1323 files = merge.keys()
1327 files = merge.keys()
1324 files.sort()
1328 files.sort()
1325 for f in files:
1329 for f in files:
1326 self.ui.status("merging %s\n" % f)
1330 self.ui.status("merging %s\n" % f)
1327 m, o, flag = merge[f]
1331 m, o, flag = merge[f]
1328 self.merge3(f, m, o)
1332 self.merge3(f, m, o)
1329 util.set_exec(self.wjoin(f), flag)
1333 util.set_exec(self.wjoin(f), flag)
1330 if moddirstate:
1334 if moddirstate:
1331 self.dirstate.update([f], 'm')
1335 self.dirstate.update([f], 'm')
1332
1336
1333 for f in remove:
1337 for f in remove:
1334 self.ui.note("removing %s\n" % f)
1338 self.ui.note("removing %s\n" % f)
1335 os.unlink(f)
1339 os.unlink(f)
1336 # try removing directories that might now be empty
1340 # try removing directories that might now be empty
1337 try: os.removedirs(os.path.dirname(f))
1341 try: os.removedirs(os.path.dirname(f))
1338 except: pass
1342 except: pass
1339 if moddirstate:
1343 if moddirstate:
1340 if mode == 'n':
1344 if mode == 'n':
1341 self.dirstate.forget(remove)
1345 self.dirstate.forget(remove)
1342 else:
1346 else:
1343 self.dirstate.update(remove, 'r')
1347 self.dirstate.update(remove, 'r')
1344
1348
1345 def merge3(self, fn, my, other):
1349 def merge3(self, fn, my, other):
1346 """perform a 3-way merge in the working directory"""
1350 """perform a 3-way merge in the working directory"""
1347
1351
1348 def temp(prefix, node):
1352 def temp(prefix, node):
1349 pre = "%s~%s." % (os.path.basename(fn), prefix)
1353 pre = "%s~%s." % (os.path.basename(fn), prefix)
1350 (fd, name) = tempfile.mkstemp("", pre)
1354 (fd, name) = tempfile.mkstemp("", pre)
1351 f = os.fdopen(fd, "wb")
1355 f = os.fdopen(fd, "wb")
1352 f.write(fl.revision(node))
1356 f.write(fl.revision(node))
1353 f.close()
1357 f.close()
1354 return name
1358 return name
1355
1359
1356 fl = self.file(fn)
1360 fl = self.file(fn)
1357 base = fl.ancestor(my, other)
1361 base = fl.ancestor(my, other)
1358 a = self.wjoin(fn)
1362 a = self.wjoin(fn)
1359 b = temp("base", base)
1363 b = temp("base", base)
1360 c = temp("other", other)
1364 c = temp("other", other)
1361
1365
1362 self.ui.note("resolving %s\n" % fn)
1366 self.ui.note("resolving %s\n" % fn)
1363 self.ui.debug("file %s: other %s ancestor %s\n" %
1367 self.ui.debug("file %s: other %s ancestor %s\n" %
1364 (fn, short(other), short(base)))
1368 (fn, short(other), short(base)))
1365
1369
1366 cmd = self.ui.config("ui", "merge") or \
1370 cmd = self.ui.config("ui", "merge") or \
1367 os.environ.get("HGMERGE", "hgmerge")
1371 os.environ.get("HGMERGE", "hgmerge")
1368 r = os.system("%s %s %s %s" % (cmd, a, b, c))
1372 r = os.system("%s %s %s %s" % (cmd, a, b, c))
1369 if r:
1373 if r:
1370 self.ui.warn("merging %s failed!\n" % fn)
1374 self.ui.warn("merging %s failed!\n" % fn)
1371
1375
1372 os.unlink(b)
1376 os.unlink(b)
1373 os.unlink(c)
1377 os.unlink(c)
1374
1378
1375 def verify(self):
1379 def verify(self):
1376 filelinkrevs = {}
1380 filelinkrevs = {}
1377 filenodes = {}
1381 filenodes = {}
1378 changesets = revisions = files = 0
1382 changesets = revisions = files = 0
1379 errors = 0
1383 errors = 0
1380
1384
1381 seen = {}
1385 seen = {}
1382 self.ui.status("checking changesets\n")
1386 self.ui.status("checking changesets\n")
1383 for i in range(self.changelog.count()):
1387 for i in range(self.changelog.count()):
1384 changesets += 1
1388 changesets += 1
1385 n = self.changelog.node(i)
1389 n = self.changelog.node(i)
1386 if n in seen:
1390 if n in seen:
1387 self.ui.warn("duplicate changeset at revision %d\n" % i)
1391 self.ui.warn("duplicate changeset at revision %d\n" % i)
1388 errors += 1
1392 errors += 1
1389 seen[n] = 1
1393 seen[n] = 1
1390
1394
1391 for p in self.changelog.parents(n):
1395 for p in self.changelog.parents(n):
1392 if p not in self.changelog.nodemap:
1396 if p not in self.changelog.nodemap:
1393 self.ui.warn("changeset %s has unknown parent %s\n" %
1397 self.ui.warn("changeset %s has unknown parent %s\n" %
1394 (short(n), short(p)))
1398 (short(n), short(p)))
1395 errors += 1
1399 errors += 1
1396 try:
1400 try:
1397 changes = self.changelog.read(n)
1401 changes = self.changelog.read(n)
1398 except Exception, inst:
1402 except Exception, inst:
1399 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1403 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1400 errors += 1
1404 errors += 1
1401
1405
1402 for f in changes[3]:
1406 for f in changes[3]:
1403 filelinkrevs.setdefault(f, []).append(i)
1407 filelinkrevs.setdefault(f, []).append(i)
1404
1408
1405 seen = {}
1409 seen = {}
1406 self.ui.status("checking manifests\n")
1410 self.ui.status("checking manifests\n")
1407 for i in range(self.manifest.count()):
1411 for i in range(self.manifest.count()):
1408 n = self.manifest.node(i)
1412 n = self.manifest.node(i)
1409 if n in seen:
1413 if n in seen:
1410 self.ui.warn("duplicate manifest at revision %d\n" % i)
1414 self.ui.warn("duplicate manifest at revision %d\n" % i)
1411 errors += 1
1415 errors += 1
1412 seen[n] = 1
1416 seen[n] = 1
1413
1417
1414 for p in self.manifest.parents(n):
1418 for p in self.manifest.parents(n):
1415 if p not in self.manifest.nodemap:
1419 if p not in self.manifest.nodemap:
1416 self.ui.warn("manifest %s has unknown parent %s\n" %
1420 self.ui.warn("manifest %s has unknown parent %s\n" %
1417 (short(n), short(p)))
1421 (short(n), short(p)))
1418 errors += 1
1422 errors += 1
1419
1423
1420 try:
1424 try:
1421 delta = mdiff.patchtext(self.manifest.delta(n))
1425 delta = mdiff.patchtext(self.manifest.delta(n))
1422 except KeyboardInterrupt:
1426 except KeyboardInterrupt:
1423 self.ui.warn("aborted")
1427 self.ui.warn("aborted")
1424 sys.exit(0)
1428 sys.exit(0)
1425 except Exception, inst:
1429 except Exception, inst:
1426 self.ui.warn("unpacking manifest %s: %s\n"
1430 self.ui.warn("unpacking manifest %s: %s\n"
1427 % (short(n), inst))
1431 % (short(n), inst))
1428 errors += 1
1432 errors += 1
1429
1433
1430 ff = [ l.split('\0') for l in delta.splitlines() ]
1434 ff = [ l.split('\0') for l in delta.splitlines() ]
1431 for f, fn in ff:
1435 for f, fn in ff:
1432 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1436 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1433
1437
1434 self.ui.status("crosschecking files in changesets and manifests\n")
1438 self.ui.status("crosschecking files in changesets and manifests\n")
1435 for f in filenodes:
1439 for f in filenodes:
1436 if f not in filelinkrevs:
1440 if f not in filelinkrevs:
1437 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1441 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1438 errors += 1
1442 errors += 1
1439
1443
1440 for f in filelinkrevs:
1444 for f in filelinkrevs:
1441 if f not in filenodes:
1445 if f not in filenodes:
1442 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1446 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1443 errors += 1
1447 errors += 1
1444
1448
1445 self.ui.status("checking files\n")
1449 self.ui.status("checking files\n")
1446 ff = filenodes.keys()
1450 ff = filenodes.keys()
1447 ff.sort()
1451 ff.sort()
1448 for f in ff:
1452 for f in ff:
1449 if f == "/dev/null": continue
1453 if f == "/dev/null": continue
1450 files += 1
1454 files += 1
1451 fl = self.file(f)
1455 fl = self.file(f)
1452 nodes = { nullid: 1 }
1456 nodes = { nullid: 1 }
1453 seen = {}
1457 seen = {}
1454 for i in range(fl.count()):
1458 for i in range(fl.count()):
1455 revisions += 1
1459 revisions += 1
1456 n = fl.node(i)
1460 n = fl.node(i)
1457
1461
1458 if n in seen:
1462 if n in seen:
1459 self.ui.warn("%s: duplicate revision %d\n" % (f, i))
1463 self.ui.warn("%s: duplicate revision %d\n" % (f, i))
1460 errors += 1
1464 errors += 1
1461
1465
1462 if n not in filenodes[f]:
1466 if n not in filenodes[f]:
1463 self.ui.warn("%s: %d:%s not in manifests\n"
1467 self.ui.warn("%s: %d:%s not in manifests\n"
1464 % (f, i, short(n)))
1468 % (f, i, short(n)))
1465 errors += 1
1469 errors += 1
1466 else:
1470 else:
1467 del filenodes[f][n]
1471 del filenodes[f][n]
1468
1472
1469 flr = fl.linkrev(n)
1473 flr = fl.linkrev(n)
1470 if flr not in filelinkrevs[f]:
1474 if flr not in filelinkrevs[f]:
1471 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1475 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1472 % (f, short(n), fl.linkrev(n)))
1476 % (f, short(n), fl.linkrev(n)))
1473 errors += 1
1477 errors += 1
1474 else:
1478 else:
1475 filelinkrevs[f].remove(flr)
1479 filelinkrevs[f].remove(flr)
1476
1480
1477 # verify contents
1481 # verify contents
1478 try:
1482 try:
1479 t = fl.read(n)
1483 t = fl.read(n)
1480 except Exception, inst:
1484 except Exception, inst:
1481 self.ui.warn("unpacking file %s %s: %s\n"
1485 self.ui.warn("unpacking file %s %s: %s\n"
1482 % (f, short(n), inst))
1486 % (f, short(n), inst))
1483 errors += 1
1487 errors += 1
1484
1488
1485 # verify parents
1489 # verify parents
1486 (p1, p2) = fl.parents(n)
1490 (p1, p2) = fl.parents(n)
1487 if p1 not in nodes:
1491 if p1 not in nodes:
1488 self.ui.warn("file %s:%s unknown parent 1 %s" %
1492 self.ui.warn("file %s:%s unknown parent 1 %s" %
1489 (f, short(n), short(p1)))
1493 (f, short(n), short(p1)))
1490 errors += 1
1494 errors += 1
1491 if p2 not in nodes:
1495 if p2 not in nodes:
1492 self.ui.warn("file %s:%s unknown parent 2 %s" %
1496 self.ui.warn("file %s:%s unknown parent 2 %s" %
1493 (f, short(n), short(p1)))
1497 (f, short(n), short(p1)))
1494 errors += 1
1498 errors += 1
1495 nodes[n] = 1
1499 nodes[n] = 1
1496
1500
1497 # cross-check
1501 # cross-check
1498 for node in filenodes[f]:
1502 for node in filenodes[f]:
1499 self.ui.warn("node %s in manifests not in %s\n"
1503 self.ui.warn("node %s in manifests not in %s\n"
1500 % (hex(n), f))
1504 % (hex(n), f))
1501 errors += 1
1505 errors += 1
1502
1506
1503 self.ui.status("%d files, %d changesets, %d total revisions\n" %
1507 self.ui.status("%d files, %d changesets, %d total revisions\n" %
1504 (files, changesets, revisions))
1508 (files, changesets, revisions))
1505
1509
1506 if errors:
1510 if errors:
1507 self.ui.warn("%d integrity errors encountered!\n" % errors)
1511 self.ui.warn("%d integrity errors encountered!\n" % errors)
1508 return 1
1512 return 1
1509
1513
1510 class httprepository:
1514 class httprepository:
1511 def __init__(self, ui, path):
1515 def __init__(self, ui, path):
1512 self.url = path
1516 self.url = path
1513 self.ui = ui
1517 self.ui = ui
1514 no_list = [ "localhost", "127.0.0.1" ]
1518 no_list = [ "localhost", "127.0.0.1" ]
1515 host = ui.config("http_proxy", "host")
1519 host = ui.config("http_proxy", "host")
1516 if host is None:
1520 if host is None:
1517 host = os.environ.get("http_proxy")
1521 host = os.environ.get("http_proxy")
1518 if host and host.startswith('http://'):
1522 if host and host.startswith('http://'):
1519 host = host[7:]
1523 host = host[7:]
1520 user = ui.config("http_proxy", "user")
1524 user = ui.config("http_proxy", "user")
1521 passwd = ui.config("http_proxy", "passwd")
1525 passwd = ui.config("http_proxy", "passwd")
1522 no = ui.config("http_proxy", "no")
1526 no = ui.config("http_proxy", "no")
1523 if no is None:
1527 if no is None:
1524 no = os.environ.get("no_proxy")
1528 no = os.environ.get("no_proxy")
1525 if no:
1529 if no:
1526 no_list = no_list + no.split(",")
1530 no_list = no_list + no.split(",")
1527
1531
1528 no_proxy = 0
1532 no_proxy = 0
1529 for h in no_list:
1533 for h in no_list:
1530 if (path.startswith("http://" + h + "/") or
1534 if (path.startswith("http://" + h + "/") or
1531 path.startswith("http://" + h + ":") or
1535 path.startswith("http://" + h + ":") or
1532 path == "http://" + h):
1536 path == "http://" + h):
1533 no_proxy = 1
1537 no_proxy = 1
1534
1538
1535 # Note: urllib2 takes proxy values from the environment and those will
1539 # Note: urllib2 takes proxy values from the environment and those will
1536 # take precedence
1540 # take precedence
1537 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
1541 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
1538 if os.environ.has_key(env):
1542 if os.environ.has_key(env):
1539 del os.environ[env]
1543 del os.environ[env]
1540
1544
1541 proxy_handler = urllib2.BaseHandler()
1545 proxy_handler = urllib2.BaseHandler()
1542 if host and not no_proxy:
1546 if host and not no_proxy:
1543 proxy_handler = urllib2.ProxyHandler({"http" : "http://" + host})
1547 proxy_handler = urllib2.ProxyHandler({"http" : "http://" + host})
1544
1548
1545 authinfo = None
1549 authinfo = None
1546 if user and passwd:
1550 if user and passwd:
1547 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
1551 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
1548 passmgr.add_password(None, host, user, passwd)
1552 passmgr.add_password(None, host, user, passwd)
1549 authinfo = urllib2.ProxyBasicAuthHandler(passmgr)
1553 authinfo = urllib2.ProxyBasicAuthHandler(passmgr)
1550
1554
1551 opener = urllib2.build_opener(proxy_handler, authinfo)
1555 opener = urllib2.build_opener(proxy_handler, authinfo)
1552 urllib2.install_opener(opener)
1556 urllib2.install_opener(opener)
1553
1557
1554 def dev(self):
1558 def dev(self):
1555 return -1
1559 return -1
1556
1560
1557 def do_cmd(self, cmd, **args):
1561 def do_cmd(self, cmd, **args):
1558 self.ui.debug("sending %s command\n" % cmd)
1562 self.ui.debug("sending %s command\n" % cmd)
1559 q = {"cmd": cmd}
1563 q = {"cmd": cmd}
1560 q.update(args)
1564 q.update(args)
1561 qs = urllib.urlencode(q)
1565 qs = urllib.urlencode(q)
1562 cu = "%s?%s" % (self.url, qs)
1566 cu = "%s?%s" % (self.url, qs)
1563 return urllib2.urlopen(cu)
1567 return urllib2.urlopen(cu)
1564
1568
1565 def heads(self):
1569 def heads(self):
1566 d = self.do_cmd("heads").read()
1570 d = self.do_cmd("heads").read()
1567 try:
1571 try:
1568 return map(bin, d[:-1].split(" "))
1572 return map(bin, d[:-1].split(" "))
1569 except:
1573 except:
1570 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1574 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1571 raise
1575 raise
1572
1576
1573 def branches(self, nodes):
1577 def branches(self, nodes):
1574 n = " ".join(map(hex, nodes))
1578 n = " ".join(map(hex, nodes))
1575 d = self.do_cmd("branches", nodes=n).read()
1579 d = self.do_cmd("branches", nodes=n).read()
1576 try:
1580 try:
1577 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1581 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1578 return br
1582 return br
1579 except:
1583 except:
1580 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1584 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1581 raise
1585 raise
1582
1586
1583 def between(self, pairs):
1587 def between(self, pairs):
1584 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1588 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1585 d = self.do_cmd("between", pairs=n).read()
1589 d = self.do_cmd("between", pairs=n).read()
1586 try:
1590 try:
1587 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1591 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1588 return p
1592 return p
1589 except:
1593 except:
1590 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1594 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1591 raise
1595 raise
1592
1596
1593 def changegroup(self, nodes):
1597 def changegroup(self, nodes):
1594 n = " ".join(map(hex, nodes))
1598 n = " ".join(map(hex, nodes))
1595 zd = zlib.decompressobj()
1596 f = self.do_cmd("changegroup", roots=n)
1599 f = self.do_cmd("changegroup", roots=n)
1597 bytes = 0
1600 bytes = 0
1598 while 1:
1601
1599 d = f.read(4096)
1602 class zread:
1600 bytes += len(d)
1603 def __init__(self, f):
1601 if not d:
1604 self.zd = zlib.decompressobj()
1602 yield zd.flush()
1605 self.f = f
1603 break
1606 self.buf = ""
1604 yield zd.decompress(d)
1607 def read(self, l):
1605 self.ui.note("%d bytes of data transfered\n" % bytes)
1608 while l > len(self.buf):
1609 r = f.read(4096)
1610 if r:
1611 self.buf += self.zd.decompress(r)
1612 else:
1613 self.buf += self.zd.flush()
1614 break
1615 d, self.buf = self.buf[:l], self.buf[l:]
1616 return d
1617
1618 return zread(f)
1619
1606
1620
1607 class sshrepository:
1621 class sshrepository:
1608 def __init__(self, ui, path):
1622 def __init__(self, ui, path):
1609 self.url = path
1623 self.url = path
1610 self.ui = ui
1624 self.ui = ui
1611
1625
1612 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?', path)
1626 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?', path)
1613 if not m:
1627 if not m:
1614 raise RepoError("couldn't parse destination %s\n" % path)
1628 raise RepoError("couldn't parse destination %s\n" % path)
1615
1629
1616 self.user = m.group(2)
1630 self.user = m.group(2)
1617 self.host = m.group(3)
1631 self.host = m.group(3)
1618 self.port = m.group(5)
1632 self.port = m.group(5)
1619 self.path = m.group(7)
1633 self.path = m.group(7)
1620
1634
1621 args = self.user and ("%s@%s" % (self.user, self.host)) or self.host
1635 args = self.user and ("%s@%s" % (self.user, self.host)) or self.host
1622 args = self.port and ("%s -p %s") % (args, self.port) or args
1636 args = self.port and ("%s -p %s") % (args, self.port) or args
1623 path = self.path or ""
1637 path = self.path or ""
1624
1638
1625 cmd = "ssh %s 'hg -R %s serve --stdio'"
1639 cmd = "ssh %s 'hg -R %s serve --stdio'"
1626 cmd = cmd % (args, path)
1640 cmd = cmd % (args, path)
1627
1641
1628 self.pipeo, self.pipei = os.popen2(cmd)
1642 self.pipeo, self.pipei = os.popen2(cmd)
1629
1643
1630 def __del__(self):
1644 def __del__(self):
1631 self.pipeo.close()
1645 self.pipeo.close()
1632 self.pipei.close()
1646 self.pipei.close()
1633
1647
1634 def dev(self):
1648 def dev(self):
1635 return -1
1649 return -1
1636
1650
1637 def do_cmd(self, cmd, **args):
1651 def do_cmd(self, cmd, **args):
1638 self.ui.debug("sending %s command\n" % cmd)
1652 self.ui.debug("sending %s command\n" % cmd)
1639 self.pipeo.write("%s\n" % cmd)
1653 self.pipeo.write("%s\n" % cmd)
1640 for k, v in args.items():
1654 for k, v in args.items():
1641 self.pipeo.write("%s %d\n" % (k, len(v)))
1655 self.pipeo.write("%s %d\n" % (k, len(v)))
1642 self.pipeo.write(v)
1656 self.pipeo.write(v)
1643 self.pipeo.flush()
1657 self.pipeo.flush()
1644
1658
1645 return self.pipei
1659 return self.pipei
1646
1660
1647 def call(self, cmd, **args):
1661 def call(self, cmd, **args):
1648 r = self.do_cmd(cmd, **args)
1662 r = self.do_cmd(cmd, **args)
1649 l = int(r.readline())
1663 l = int(r.readline())
1650 return r.read(l)
1664 return r.read(l)
1651
1665
1652 def heads(self):
1666 def heads(self):
1653 d = self.call("heads")
1667 d = self.call("heads")
1654 try:
1668 try:
1655 return map(bin, d[:-1].split(" "))
1669 return map(bin, d[:-1].split(" "))
1656 except:
1670 except:
1657 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1671 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1658 raise
1672 raise
1659
1673
1660 def branches(self, nodes):
1674 def branches(self, nodes):
1661 n = " ".join(map(hex, nodes))
1675 n = " ".join(map(hex, nodes))
1662 d = self.call("branches", nodes=n)
1676 d = self.call("branches", nodes=n)
1663 try:
1677 try:
1664 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1678 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1665 return br
1679 return br
1666 except:
1680 except:
1667 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1681 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1668 raise
1682 raise
1669
1683
1670 def between(self, pairs):
1684 def between(self, pairs):
1671 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1685 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1672 d = self.call("between", pairs=n)
1686 d = self.call("between", pairs=n)
1673 try:
1687 try:
1674 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1688 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1675 return p
1689 return p
1676 except:
1690 except:
1677 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1691 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1678 raise
1692 raise
1679
1693
1680 def changegroup(self, nodes):
1694 def changegroup(self, nodes):
1681 n = " ".join(map(hex, nodes))
1695 n = " ".join(map(hex, nodes))
1682 f = self.do_cmd("changegroup", roots=n)
1696 f = self.do_cmd("changegroup", roots=n)
1683 bytes = 0
1697 return self.pipei
1684 while 1:
1685 l = struct.unpack(">l", f.read(4))[0]
1686 if l == -1: break
1687 d = f.read(l)
1688 bytes += len(d)
1689 yield d
1690 self.ui.note("%d bytes of data transfered\n" % bytes)
1691
1698
1692 def repository(ui, path=None, create=0):
1699 def repository(ui, path=None, create=0):
1693 if path:
1700 if path:
1694 if path.startswith("http://"):
1701 if path.startswith("http://"):
1695 return httprepository(ui, path)
1702 return httprepository(ui, path)
1696 if path.startswith("hg://"):
1703 if path.startswith("hg://"):
1697 return httprepository(ui, path.replace("hg://", "http://"))
1704 return httprepository(ui, path.replace("hg://", "http://"))
1698 if path.startswith("old-http://"):
1705 if path.startswith("old-http://"):
1699 return localrepository(ui, path.replace("old-http://", "http://"))
1706 return localrepository(ui, path.replace("old-http://", "http://"))
1700 if path.startswith("ssh://"):
1707 if path.startswith("ssh://"):
1701 return sshrepository(ui, path)
1708 return sshrepository(ui, path)
1702
1709
1703 return localrepository(ui, path, create)
1710 return localrepository(ui, path, create)
@@ -1,779 +1,782 b''
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, sys, zlib
9 import os, cgi, time, re, difflib, 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 def template(tmpl, filters = {}, **map):
67 def template(tmpl, filters = {}, **map):
68 while tmpl:
68 while tmpl:
69 m = re.search(r"#([a-zA-Z0-9]+)((\|[a-zA-Z0-9]+)*)#", tmpl)
69 m = re.search(r"#([a-zA-Z0-9]+)((\|[a-zA-Z0-9]+)*)#", tmpl)
70 if m:
70 if m:
71 yield tmpl[:m.start(0)]
71 yield tmpl[:m.start(0)]
72 v = map.get(m.group(1), "")
72 v = map.get(m.group(1), "")
73 v = callable(v) and v() or v
73 v = callable(v) and v() or v
74
74
75 fl = m.group(2)
75 fl = m.group(2)
76 if fl:
76 if fl:
77 for f in fl.split("|")[1:]:
77 for f in fl.split("|")[1:]:
78 v = filters[f](v)
78 v = filters[f](v)
79
79
80 yield v
80 yield v
81 tmpl = tmpl[m.end(0):]
81 tmpl = tmpl[m.end(0):]
82 else:
82 else:
83 yield tmpl
83 yield tmpl
84 return
84 return
85
85
86 class templater:
86 class templater:
87 def __init__(self, mapfile, filters = {}, defaults = {}):
87 def __init__(self, mapfile, filters = {}, defaults = {}):
88 self.cache = {}
88 self.cache = {}
89 self.map = {}
89 self.map = {}
90 self.base = os.path.dirname(mapfile)
90 self.base = os.path.dirname(mapfile)
91 self.filters = filters
91 self.filters = filters
92 self.defaults = defaults
92 self.defaults = defaults
93
93
94 for l in file(mapfile):
94 for l in file(mapfile):
95 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
95 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
96 if m:
96 if m:
97 self.cache[m.group(1)] = m.group(2)
97 self.cache[m.group(1)] = m.group(2)
98 else:
98 else:
99 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
99 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
100 if m:
100 if m:
101 self.map[m.group(1)] = os.path.join(self.base, m.group(2))
101 self.map[m.group(1)] = os.path.join(self.base, m.group(2))
102 else:
102 else:
103 raise "unknown map entry '%s'" % l
103 raise "unknown map entry '%s'" % l
104
104
105 def __call__(self, t, **map):
105 def __call__(self, t, **map):
106 m = self.defaults.copy()
106 m = self.defaults.copy()
107 m.update(map)
107 m.update(map)
108 try:
108 try:
109 tmpl = self.cache[t]
109 tmpl = self.cache[t]
110 except KeyError:
110 except KeyError:
111 tmpl = self.cache[t] = file(self.map[t]).read()
111 tmpl = self.cache[t] = file(self.map[t]).read()
112 return template(tmpl, self.filters, **m)
112 return template(tmpl, self.filters, **m)
113
113
114 def rfc822date(x):
114 def rfc822date(x):
115 return time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime(x))
115 return time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime(x))
116
116
117 class hgweb:
117 class hgweb:
118 maxchanges = 10
118 maxchanges = 10
119 maxfiles = 10
119 maxfiles = 10
120
120
121 def __init__(self, path, name, templates = ""):
121 def __init__(self, path, name, templates = ""):
122 self.templates = templates or templatepath()
122 self.templates = templates or templatepath()
123 self.reponame = name
123 self.reponame = name
124 self.path = path
124 self.path = path
125 self.mtime = -1
125 self.mtime = -1
126 self.viewonly = 0
126 self.viewonly = 0
127
127
128 self.filters = {
128 self.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 def refresh(self):
140 def refresh(self):
141 s = os.stat(os.path.join(self.path, ".hg", "00changelog.i"))
141 s = os.stat(os.path.join(self.path, ".hg", "00changelog.i"))
142 if s.st_mtime != self.mtime:
142 if s.st_mtime != self.mtime:
143 self.mtime = s.st_mtime
143 self.mtime = s.st_mtime
144 self.repo = repository(ui(), self.path)
144 self.repo = repository(ui(), self.path)
145
145
146 def date(self, cs):
146 def date(self, cs):
147 return time.asctime(time.gmtime(float(cs[2].split(' ')[0])))
147 return time.asctime(time.gmtime(float(cs[2].split(' ')[0])))
148
148
149 def listfiles(self, files, mf):
149 def listfiles(self, files, mf):
150 for f in files[:self.maxfiles]:
150 for f in files[:self.maxfiles]:
151 yield self.t("filenodelink", node = hex(mf[f]), file = f)
151 yield self.t("filenodelink", node = hex(mf[f]), file = f)
152 if len(files) > self.maxfiles:
152 if len(files) > self.maxfiles:
153 yield self.t("fileellipses")
153 yield self.t("fileellipses")
154
154
155 def listfilediffs(self, files, changeset):
155 def listfilediffs(self, files, changeset):
156 for f in files[:self.maxfiles]:
156 for f in files[:self.maxfiles]:
157 yield self.t("filedifflink", node = hex(changeset), file = f)
157 yield self.t("filedifflink", node = hex(changeset), file = f)
158 if len(files) > self.maxfiles:
158 if len(files) > self.maxfiles:
159 yield self.t("fileellipses")
159 yield self.t("fileellipses")
160
160
161 def parents(self, t1, nodes=[], rev=None,**args):
161 def parents(self, t1, nodes=[], rev=None,**args):
162 if not rev: rev = lambda x: ""
162 if not rev: rev = lambda x: ""
163 for node in nodes:
163 for node in nodes:
164 if node != nullid:
164 if node != nullid:
165 yield self.t(t1, node = hex(node), rev = rev(node), **args)
165 yield self.t(t1, node = hex(node), rev = rev(node), **args)
166
166
167 def showtag(self, t1, node=nullid, **args):
167 def showtag(self, t1, node=nullid, **args):
168 for t in self.repo.nodetags(node):
168 for t in self.repo.nodetags(node):
169 yield self.t(t1, tag = t, **args)
169 yield self.t(t1, tag = t, **args)
170
170
171 def diff(self, node1, node2, files):
171 def diff(self, node1, node2, files):
172 def filterfiles(list, files):
172 def filterfiles(list, files):
173 l = [ x for x in list if x in files ]
173 l = [ x for x in list if x in files ]
174
174
175 for f in files:
175 for f in files:
176 if f[-1] != os.sep: f += os.sep
176 if f[-1] != os.sep: f += os.sep
177 l += [ x for x in list if x.startswith(f) ]
177 l += [ x for x in list if x.startswith(f) ]
178 return l
178 return l
179
179
180 parity = [0]
180 parity = [0]
181 def diffblock(diff, f, fn):
181 def diffblock(diff, f, fn):
182 yield self.t("diffblock",
182 yield self.t("diffblock",
183 lines = prettyprintlines(diff),
183 lines = prettyprintlines(diff),
184 parity = parity[0],
184 parity = parity[0],
185 file = f,
185 file = f,
186 filenode = hex(fn or nullid))
186 filenode = hex(fn or nullid))
187 parity[0] = 1 - parity[0]
187 parity[0] = 1 - parity[0]
188
188
189 def prettyprintlines(diff):
189 def prettyprintlines(diff):
190 for l in diff.splitlines(1):
190 for l in diff.splitlines(1):
191 if l.startswith('+'):
191 if l.startswith('+'):
192 yield self.t("difflineplus", line = l)
192 yield self.t("difflineplus", line = l)
193 elif l.startswith('-'):
193 elif l.startswith('-'):
194 yield self.t("difflineminus", line = l)
194 yield self.t("difflineminus", line = l)
195 elif l.startswith('@'):
195 elif l.startswith('@'):
196 yield self.t("difflineat", line = l)
196 yield self.t("difflineat", line = l)
197 else:
197 else:
198 yield self.t("diffline", line = l)
198 yield self.t("diffline", line = l)
199
199
200 r = self.repo
200 r = self.repo
201 cl = r.changelog
201 cl = r.changelog
202 mf = r.manifest
202 mf = r.manifest
203 change1 = cl.read(node1)
203 change1 = cl.read(node1)
204 change2 = cl.read(node2)
204 change2 = cl.read(node2)
205 mmap1 = mf.read(change1[0])
205 mmap1 = mf.read(change1[0])
206 mmap2 = mf.read(change2[0])
206 mmap2 = mf.read(change2[0])
207 date1 = self.date(change1)
207 date1 = self.date(change1)
208 date2 = self.date(change2)
208 date2 = self.date(change2)
209
209
210 c, a, d, u = r.changes(node1, node2)
210 c, a, d, u = r.changes(node1, node2)
211 c, a, d = map(lambda x: filterfiles(x, files), (c, a, d))
211 c, a, d = map(lambda x: filterfiles(x, files), (c, a, d))
212
212
213 for f in c:
213 for f in c:
214 to = r.file(f).read(mmap1[f])
214 to = r.file(f).read(mmap1[f])
215 tn = r.file(f).read(mmap2[f])
215 tn = r.file(f).read(mmap2[f])
216 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
216 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
217 for f in a:
217 for f in a:
218 to = None
218 to = None
219 tn = r.file(f).read(mmap2[f])
219 tn = r.file(f).read(mmap2[f])
220 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
220 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
221 for f in d:
221 for f in d:
222 to = r.file(f).read(mmap1[f])
222 to = r.file(f).read(mmap1[f])
223 tn = None
223 tn = None
224 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
224 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
225
225
226 def header(self):
226 def header(self):
227 yield self.t("header")
227 yield self.t("header")
228
228
229 def footer(self):
229 def footer(self):
230 yield self.t("footer")
230 yield self.t("footer")
231
231
232 def changelog(self, pos):
232 def changelog(self, pos):
233 def changenav():
233 def changenav():
234 def seq(factor = 1):
234 def seq(factor = 1):
235 yield 1 * factor
235 yield 1 * factor
236 yield 3 * factor
236 yield 3 * factor
237 #yield 5 * factor
237 #yield 5 * factor
238 for f in seq(factor * 10):
238 for f in seq(factor * 10):
239 yield f
239 yield f
240
240
241 l = []
241 l = []
242 for f in seq():
242 for f in seq():
243 if f < self.maxchanges / 2: continue
243 if f < self.maxchanges / 2: continue
244 if f > count: break
244 if f > count: break
245 r = "%d" % f
245 r = "%d" % f
246 if pos + f < count: l.append(("+" + r, pos + f))
246 if pos + f < count: l.append(("+" + r, pos + f))
247 if pos - f >= 0: l.insert(0, ("-" + r, pos - f))
247 if pos - f >= 0: l.insert(0, ("-" + r, pos - f))
248
248
249 yield self.t("naventry", rev = 0, label="(0)")
249 yield self.t("naventry", rev = 0, label="(0)")
250
250
251 for label, rev in l:
251 for label, rev in l:
252 yield self.t("naventry", label = label, rev = rev)
252 yield self.t("naventry", label = label, rev = rev)
253
253
254 yield self.t("naventry", label="tip")
254 yield self.t("naventry", label="tip")
255
255
256 def changelist():
256 def changelist():
257 parity = (start - end) & 1
257 parity = (start - end) & 1
258 cl = self.repo.changelog
258 cl = self.repo.changelog
259 l = [] # build a list in forward order for efficiency
259 l = [] # build a list in forward order for efficiency
260 for i in range(start, end):
260 for i in range(start, end):
261 n = cl.node(i)
261 n = cl.node(i)
262 changes = cl.read(n)
262 changes = cl.read(n)
263 hn = hex(n)
263 hn = hex(n)
264 t = float(changes[2].split(' ')[0])
264 t = float(changes[2].split(' ')[0])
265
265
266 l.insert(0, self.t(
266 l.insert(0, self.t(
267 'changelogentry',
267 'changelogentry',
268 parity = parity,
268 parity = parity,
269 author = changes[1],
269 author = changes[1],
270 parent = self.parents("changelogparent",
270 parent = self.parents("changelogparent",
271 cl.parents(n), cl.rev),
271 cl.parents(n), cl.rev),
272 changelogtag = self.showtag("changelogtag",n),
272 changelogtag = self.showtag("changelogtag",n),
273 manifest = hex(changes[0]),
273 manifest = hex(changes[0]),
274 desc = changes[4],
274 desc = changes[4],
275 date = t,
275 date = t,
276 files = self.listfilediffs(changes[3], n),
276 files = self.listfilediffs(changes[3], n),
277 rev = i,
277 rev = i,
278 node = hn))
278 node = hn))
279 parity = 1 - parity
279 parity = 1 - parity
280
280
281 yield l
281 yield l
282
282
283 cl = self.repo.changelog
283 cl = self.repo.changelog
284 mf = cl.read(cl.tip())[0]
284 mf = cl.read(cl.tip())[0]
285 count = cl.count()
285 count = cl.count()
286 start = max(0, pos - self.maxchanges + 1)
286 start = max(0, pos - self.maxchanges + 1)
287 end = min(count, start + self.maxchanges)
287 end = min(count, start + self.maxchanges)
288 pos = end - 1
288 pos = end - 1
289
289
290 yield self.t('changelog',
290 yield self.t('changelog',
291 changenav = changenav,
291 changenav = changenav,
292 manifest = hex(mf),
292 manifest = hex(mf),
293 rev = pos, changesets = count, entries = changelist)
293 rev = pos, changesets = count, entries = changelist)
294
294
295 def search(self, query):
295 def search(self, query):
296
296
297 def changelist():
297 def changelist():
298 cl = self.repo.changelog
298 cl = self.repo.changelog
299 count = 0
299 count = 0
300 qw = query.lower().split()
300 qw = query.lower().split()
301
301
302 def revgen():
302 def revgen():
303 for i in range(cl.count() - 1, 0, -100):
303 for i in range(cl.count() - 1, 0, -100):
304 l = []
304 l = []
305 for j in range(max(0, i - 100), i):
305 for j in range(max(0, i - 100), i):
306 n = cl.node(j)
306 n = cl.node(j)
307 changes = cl.read(n)
307 changes = cl.read(n)
308 l.insert(0, (n, j, changes))
308 l.insert(0, (n, j, changes))
309 for e in l:
309 for e in l:
310 yield e
310 yield e
311
311
312 for n, i, changes in revgen():
312 for n, i, changes in revgen():
313 miss = 0
313 miss = 0
314 for q in qw:
314 for q in qw:
315 if not (q in changes[1].lower() or
315 if not (q in changes[1].lower() or
316 q in changes[4].lower() or
316 q in changes[4].lower() or
317 q in " ".join(changes[3][:20]).lower()):
317 q in " ".join(changes[3][:20]).lower()):
318 miss = 1
318 miss = 1
319 break
319 break
320 if miss: continue
320 if miss: continue
321
321
322 count += 1
322 count += 1
323 hn = hex(n)
323 hn = hex(n)
324 t = float(changes[2].split(' ')[0])
324 t = float(changes[2].split(' ')[0])
325
325
326 yield self.t(
326 yield self.t(
327 'searchentry',
327 'searchentry',
328 parity = count & 1,
328 parity = count & 1,
329 author = changes[1],
329 author = changes[1],
330 parent = self.parents("changelogparent",
330 parent = self.parents("changelogparent",
331 cl.parents(n), cl.rev),
331 cl.parents(n), cl.rev),
332 changelogtag = self.showtag("changelogtag",n),
332 changelogtag = self.showtag("changelogtag",n),
333 manifest = hex(changes[0]),
333 manifest = hex(changes[0]),
334 desc = changes[4],
334 desc = changes[4],
335 date = t,
335 date = t,
336 files = self.listfilediffs(changes[3], n),
336 files = self.listfilediffs(changes[3], n),
337 rev = i,
337 rev = i,
338 node = hn)
338 node = hn)
339
339
340 if count >= self.maxchanges: break
340 if count >= self.maxchanges: break
341
341
342 cl = self.repo.changelog
342 cl = self.repo.changelog
343 mf = cl.read(cl.tip())[0]
343 mf = cl.read(cl.tip())[0]
344
344
345 yield self.t('search',
345 yield self.t('search',
346 query = query,
346 query = query,
347 manifest = hex(mf),
347 manifest = hex(mf),
348 entries = changelist)
348 entries = changelist)
349
349
350 def changeset(self, nodeid):
350 def changeset(self, nodeid):
351 n = bin(nodeid)
351 n = bin(nodeid)
352 cl = self.repo.changelog
352 cl = self.repo.changelog
353 changes = cl.read(n)
353 changes = cl.read(n)
354 p1 = cl.parents(n)[0]
354 p1 = cl.parents(n)[0]
355 t = float(changes[2].split(' ')[0])
355 t = float(changes[2].split(' ')[0])
356
356
357 files = []
357 files = []
358 mf = self.repo.manifest.read(changes[0])
358 mf = self.repo.manifest.read(changes[0])
359 for f in changes[3]:
359 for f in changes[3]:
360 files.append(self.t("filenodelink",
360 files.append(self.t("filenodelink",
361 filenode = hex(mf.get(f, nullid)), file = f))
361 filenode = hex(mf.get(f, nullid)), file = f))
362
362
363 def diff():
363 def diff():
364 yield self.diff(p1, n, changes[3])
364 yield self.diff(p1, n, changes[3])
365
365
366 yield self.t('changeset',
366 yield self.t('changeset',
367 diff = diff,
367 diff = diff,
368 rev = cl.rev(n),
368 rev = cl.rev(n),
369 node = nodeid,
369 node = nodeid,
370 parent = self.parents("changesetparent",
370 parent = self.parents("changesetparent",
371 cl.parents(n), cl.rev),
371 cl.parents(n), cl.rev),
372 changesettag = self.showtag("changesettag",n),
372 changesettag = self.showtag("changesettag",n),
373 manifest = hex(changes[0]),
373 manifest = hex(changes[0]),
374 author = changes[1],
374 author = changes[1],
375 desc = changes[4],
375 desc = changes[4],
376 date = t,
376 date = t,
377 files = files)
377 files = files)
378
378
379 def filelog(self, f, filenode):
379 def filelog(self, f, filenode):
380 cl = self.repo.changelog
380 cl = self.repo.changelog
381 fl = self.repo.file(f)
381 fl = self.repo.file(f)
382 count = fl.count()
382 count = fl.count()
383
383
384 def entries():
384 def entries():
385 l = []
385 l = []
386 parity = (count - 1) & 1
386 parity = (count - 1) & 1
387
387
388 for i in range(count):
388 for i in range(count):
389
389
390 n = fl.node(i)
390 n = fl.node(i)
391 lr = fl.linkrev(n)
391 lr = fl.linkrev(n)
392 cn = cl.node(lr)
392 cn = cl.node(lr)
393 cs = cl.read(cl.node(lr))
393 cs = cl.read(cl.node(lr))
394 t = float(cs[2].split(' ')[0])
394 t = float(cs[2].split(' ')[0])
395
395
396 l.insert(0, self.t("filelogentry",
396 l.insert(0, self.t("filelogentry",
397 parity = parity,
397 parity = parity,
398 filenode = hex(n),
398 filenode = hex(n),
399 filerev = i,
399 filerev = i,
400 file = f,
400 file = f,
401 node = hex(cn),
401 node = hex(cn),
402 author = cs[1],
402 author = cs[1],
403 date = t,
403 date = t,
404 parent = self.parents("filelogparent",
404 parent = self.parents("filelogparent",
405 fl.parents(n), fl.rev, file=f),
405 fl.parents(n), fl.rev, file=f),
406 desc = cs[4]))
406 desc = cs[4]))
407 parity = 1 - parity
407 parity = 1 - parity
408
408
409 yield l
409 yield l
410
410
411 yield self.t("filelog",
411 yield self.t("filelog",
412 file = f,
412 file = f,
413 filenode = filenode,
413 filenode = filenode,
414 entries = entries)
414 entries = entries)
415
415
416 def filerevision(self, f, node):
416 def filerevision(self, f, node):
417 fl = self.repo.file(f)
417 fl = self.repo.file(f)
418 n = bin(node)
418 n = bin(node)
419 text = fl.read(n)
419 text = fl.read(n)
420 changerev = fl.linkrev(n)
420 changerev = fl.linkrev(n)
421 cl = self.repo.changelog
421 cl = self.repo.changelog
422 cn = cl.node(changerev)
422 cn = cl.node(changerev)
423 cs = cl.read(cn)
423 cs = cl.read(cn)
424 t = float(cs[2].split(' ')[0])
424 t = float(cs[2].split(' ')[0])
425 mfn = cs[0]
425 mfn = cs[0]
426
426
427 def lines():
427 def lines():
428 for l, t in enumerate(text.splitlines(1)):
428 for l, t in enumerate(text.splitlines(1)):
429 yield self.t("fileline", line = t,
429 yield self.t("fileline", line = t,
430 linenumber = "% 6d" % (l + 1),
430 linenumber = "% 6d" % (l + 1),
431 parity = l & 1)
431 parity = l & 1)
432
432
433 yield self.t("filerevision", file = f,
433 yield self.t("filerevision", file = f,
434 filenode = node,
434 filenode = node,
435 path = up(f),
435 path = up(f),
436 text = lines(),
436 text = lines(),
437 rev = changerev,
437 rev = changerev,
438 node = hex(cn),
438 node = hex(cn),
439 manifest = hex(mfn),
439 manifest = hex(mfn),
440 author = cs[1],
440 author = cs[1],
441 date = t,
441 date = t,
442 parent = self.parents("filerevparent",
442 parent = self.parents("filerevparent",
443 fl.parents(n), fl.rev, file=f),
443 fl.parents(n), fl.rev, file=f),
444 permissions = self.repo.manifest.readflags(mfn)[f])
444 permissions = self.repo.manifest.readflags(mfn)[f])
445
445
446 def fileannotate(self, f, node):
446 def fileannotate(self, f, node):
447 bcache = {}
447 bcache = {}
448 ncache = {}
448 ncache = {}
449 fl = self.repo.file(f)
449 fl = self.repo.file(f)
450 n = bin(node)
450 n = bin(node)
451 changerev = fl.linkrev(n)
451 changerev = fl.linkrev(n)
452
452
453 cl = self.repo.changelog
453 cl = self.repo.changelog
454 cn = cl.node(changerev)
454 cn = cl.node(changerev)
455 cs = cl.read(cn)
455 cs = cl.read(cn)
456 t = float(cs[2].split(' ')[0])
456 t = float(cs[2].split(' ')[0])
457 mfn = cs[0]
457 mfn = cs[0]
458
458
459 def annotate():
459 def annotate():
460 parity = 1
460 parity = 1
461 last = None
461 last = None
462 for r, l in fl.annotate(n):
462 for r, l in fl.annotate(n):
463 try:
463 try:
464 cnode = ncache[r]
464 cnode = ncache[r]
465 except KeyError:
465 except KeyError:
466 cnode = ncache[r] = self.repo.changelog.node(r)
466 cnode = ncache[r] = self.repo.changelog.node(r)
467
467
468 try:
468 try:
469 name = bcache[r]
469 name = bcache[r]
470 except KeyError:
470 except KeyError:
471 cl = self.repo.changelog.read(cnode)
471 cl = self.repo.changelog.read(cnode)
472 name = cl[1]
472 name = cl[1]
473 f = name.find('@')
473 f = name.find('@')
474 if f >= 0:
474 if f >= 0:
475 name = name[:f]
475 name = name[:f]
476 f = name.find('<')
476 f = name.find('<')
477 if f >= 0:
477 if f >= 0:
478 name = name[f+1:]
478 name = name[f+1:]
479 bcache[r] = name
479 bcache[r] = name
480
480
481 if last != cnode:
481 if last != cnode:
482 parity = 1 - parity
482 parity = 1 - parity
483 last = cnode
483 last = cnode
484
484
485 yield self.t("annotateline",
485 yield self.t("annotateline",
486 parity = parity,
486 parity = parity,
487 node = hex(cnode),
487 node = hex(cnode),
488 rev = r,
488 rev = r,
489 author = name,
489 author = name,
490 file = f,
490 file = f,
491 line = l)
491 line = l)
492
492
493 yield self.t("fileannotate",
493 yield self.t("fileannotate",
494 file = f,
494 file = f,
495 filenode = node,
495 filenode = node,
496 annotate = annotate,
496 annotate = annotate,
497 path = up(f),
497 path = up(f),
498 rev = changerev,
498 rev = changerev,
499 node = hex(cn),
499 node = hex(cn),
500 manifest = hex(mfn),
500 manifest = hex(mfn),
501 author = cs[1],
501 author = cs[1],
502 date = t,
502 date = t,
503 parent = self.parents("fileannotateparent",
503 parent = self.parents("fileannotateparent",
504 fl.parents(n), fl.rev, file=f),
504 fl.parents(n), fl.rev, file=f),
505 permissions = self.repo.manifest.readflags(mfn)[f])
505 permissions = self.repo.manifest.readflags(mfn)[f])
506
506
507 def manifest(self, mnode, path):
507 def manifest(self, mnode, path):
508 mf = self.repo.manifest.read(bin(mnode))
508 mf = self.repo.manifest.read(bin(mnode))
509 rev = self.repo.manifest.rev(bin(mnode))
509 rev = self.repo.manifest.rev(bin(mnode))
510 node = self.repo.changelog.node(rev)
510 node = self.repo.changelog.node(rev)
511 mff=self.repo.manifest.readflags(bin(mnode))
511 mff=self.repo.manifest.readflags(bin(mnode))
512
512
513 files = {}
513 files = {}
514
514
515 p = path[1:]
515 p = path[1:]
516 l = len(p)
516 l = len(p)
517
517
518 for f,n in mf.items():
518 for f,n in mf.items():
519 if f[:l] != p:
519 if f[:l] != p:
520 continue
520 continue
521 remain = f[l:]
521 remain = f[l:]
522 if "/" in remain:
522 if "/" in remain:
523 short = remain[:remain.find("/") + 1] # bleah
523 short = remain[:remain.find("/") + 1] # bleah
524 files[short] = (f, None)
524 files[short] = (f, None)
525 else:
525 else:
526 short = os.path.basename(remain)
526 short = os.path.basename(remain)
527 files[short] = (f, n)
527 files[short] = (f, n)
528
528
529 def filelist():
529 def filelist():
530 parity = 0
530 parity = 0
531 fl = files.keys()
531 fl = files.keys()
532 fl.sort()
532 fl.sort()
533 for f in fl:
533 for f in fl:
534 full, fnode = files[f]
534 full, fnode = files[f]
535 if fnode:
535 if fnode:
536 yield self.t("manifestfileentry",
536 yield self.t("manifestfileentry",
537 file = full,
537 file = full,
538 manifest = mnode,
538 manifest = mnode,
539 filenode = hex(fnode),
539 filenode = hex(fnode),
540 parity = parity,
540 parity = parity,
541 basename = f,
541 basename = f,
542 permissions = mff[full])
542 permissions = mff[full])
543 else:
543 else:
544 yield self.t("manifestdirentry",
544 yield self.t("manifestdirentry",
545 parity = parity,
545 parity = parity,
546 path = os.path.join(path, f),
546 path = os.path.join(path, f),
547 manifest = mnode, basename = f[:-1])
547 manifest = mnode, basename = f[:-1])
548 parity = 1 - parity
548 parity = 1 - parity
549
549
550 yield self.t("manifest",
550 yield self.t("manifest",
551 manifest = mnode,
551 manifest = mnode,
552 rev = rev,
552 rev = rev,
553 node = hex(node),
553 node = hex(node),
554 path = path,
554 path = path,
555 up = up(path),
555 up = up(path),
556 entries = filelist)
556 entries = filelist)
557
557
558 def tags(self):
558 def tags(self):
559 cl = self.repo.changelog
559 cl = self.repo.changelog
560 mf = cl.read(cl.tip())[0]
560 mf = cl.read(cl.tip())[0]
561
561
562 i = self.repo.tagslist()
562 i = self.repo.tagslist()
563 i.reverse()
563 i.reverse()
564
564
565 def entries():
565 def entries():
566 parity = 0
566 parity = 0
567 for k,n in i:
567 for k,n in i:
568 yield self.t("tagentry",
568 yield self.t("tagentry",
569 parity = parity,
569 parity = parity,
570 tag = k,
570 tag = k,
571 node = hex(n))
571 node = hex(n))
572 parity = 1 - parity
572 parity = 1 - parity
573
573
574 yield self.t("tags",
574 yield self.t("tags",
575 manifest = hex(mf),
575 manifest = hex(mf),
576 entries = entries)
576 entries = entries)
577
577
578 def filediff(self, file, changeset):
578 def filediff(self, file, changeset):
579 n = bin(changeset)
579 n = bin(changeset)
580 cl = self.repo.changelog
580 cl = self.repo.changelog
581 p1 = cl.parents(n)[0]
581 p1 = cl.parents(n)[0]
582 cs = cl.read(n)
582 cs = cl.read(n)
583 mf = self.repo.manifest.read(cs[0])
583 mf = self.repo.manifest.read(cs[0])
584
584
585 def diff():
585 def diff():
586 yield self.diff(p1, n, file)
586 yield self.diff(p1, n, file)
587
587
588 yield self.t("filediff",
588 yield self.t("filediff",
589 file = file,
589 file = file,
590 filenode = hex(mf.get(file, nullid)),
590 filenode = hex(mf.get(file, nullid)),
591 node = changeset,
591 node = changeset,
592 rev = self.repo.changelog.rev(n),
592 rev = self.repo.changelog.rev(n),
593 parent = self.parents("filediffparent",
593 parent = self.parents("filediffparent",
594 cl.parents(n), cl.rev),
594 cl.parents(n), cl.rev),
595 diff = diff)
595 diff = diff)
596
596
597 # add tags to things
597 # add tags to things
598 # tags -> list of changesets corresponding to tags
598 # tags -> list of changesets corresponding to tags
599 # find tag, changeset, file
599 # find tag, changeset, file
600
600
601 def run(self):
601 def run(self):
602 self.refresh()
602 self.refresh()
603 args = cgi.parse()
603 args = cgi.parse()
604
604
605 m = os.path.join(self.templates, "map")
605 m = os.path.join(self.templates, "map")
606 if args.has_key('style'):
606 if args.has_key('style'):
607 b = os.path.basename("map-" + args['style'][0])
607 b = os.path.basename("map-" + args['style'][0])
608 p = os.path.join(self.templates, b)
608 p = os.path.join(self.templates, b)
609 if os.path.isfile(p): m = p
609 if os.path.isfile(p): m = p
610
610
611 port = os.environ["SERVER_PORT"]
611 port = os.environ["SERVER_PORT"]
612 port = port != "80" and (":" + port) or ""
612 port = port != "80" and (":" + port) or ""
613 uri = os.environ["REQUEST_URI"]
613 uri = os.environ["REQUEST_URI"]
614 if "?" in uri: uri = uri.split("?")[0]
614 if "?" in uri: uri = uri.split("?")[0]
615 url = "http://%s%s%s" % (os.environ["SERVER_NAME"], port, uri)
615 url = "http://%s%s%s" % (os.environ["SERVER_NAME"], port, uri)
616
616
617 self.t = templater(m, self.filters,
617 self.t = templater(m, self.filters,
618 {"url":url,
618 {"url":url,
619 "repo":self.reponame,
619 "repo":self.reponame,
620 "header":self.header(),
620 "header":self.header(),
621 "footer":self.footer(),
621 "footer":self.footer(),
622 })
622 })
623
623
624 if not args.has_key('cmd') or args['cmd'][0] == 'changelog':
624 if not args.has_key('cmd') or args['cmd'][0] == 'changelog':
625 c = self.repo.changelog.count() - 1
625 c = self.repo.changelog.count() - 1
626 hi = c
626 hi = c
627 if args.has_key('rev'):
627 if args.has_key('rev'):
628 hi = args['rev'][0]
628 hi = args['rev'][0]
629 try:
629 try:
630 hi = self.repo.changelog.rev(self.repo.lookup(hi))
630 hi = self.repo.changelog.rev(self.repo.lookup(hi))
631 except KeyError:
631 except KeyError:
632 write(self.search(hi))
632 write(self.search(hi))
633 return
633 return
634
634
635 write(self.changelog(hi))
635 write(self.changelog(hi))
636
636
637 elif args['cmd'][0] == 'changeset':
637 elif args['cmd'][0] == 'changeset':
638 write(self.changeset(args['node'][0]))
638 write(self.changeset(args['node'][0]))
639
639
640 elif args['cmd'][0] == 'manifest':
640 elif args['cmd'][0] == 'manifest':
641 write(self.manifest(args['manifest'][0], args['path'][0]))
641 write(self.manifest(args['manifest'][0], args['path'][0]))
642
642
643 elif args['cmd'][0] == 'tags':
643 elif args['cmd'][0] == 'tags':
644 write(self.tags())
644 write(self.tags())
645
645
646 elif args['cmd'][0] == 'filediff':
646 elif args['cmd'][0] == 'filediff':
647 write(self.filediff(args['file'][0], args['node'][0]))
647 write(self.filediff(args['file'][0], args['node'][0]))
648
648
649 elif args['cmd'][0] == 'file':
649 elif args['cmd'][0] == 'file':
650 write(self.filerevision(args['file'][0], args['filenode'][0]))
650 write(self.filerevision(args['file'][0], args['filenode'][0]))
651
651
652 elif args['cmd'][0] == 'annotate':
652 elif args['cmd'][0] == 'annotate':
653 write(self.fileannotate(args['file'][0], args['filenode'][0]))
653 write(self.fileannotate(args['file'][0], args['filenode'][0]))
654
654
655 elif args['cmd'][0] == 'filelog':
655 elif args['cmd'][0] == 'filelog':
656 write(self.filelog(args['file'][0], args['filenode'][0]))
656 write(self.filelog(args['file'][0], args['filenode'][0]))
657
657
658 elif args['cmd'][0] == 'heads':
658 elif args['cmd'][0] == 'heads':
659 httphdr("text/plain")
659 httphdr("text/plain")
660 h = self.repo.heads()
660 h = self.repo.heads()
661 sys.stdout.write(" ".join(map(hex, h)) + "\n")
661 sys.stdout.write(" ".join(map(hex, h)) + "\n")
662
662
663 elif args['cmd'][0] == 'branches':
663 elif args['cmd'][0] == 'branches':
664 httphdr("text/plain")
664 httphdr("text/plain")
665 nodes = []
665 nodes = []
666 if args.has_key('nodes'):
666 if args.has_key('nodes'):
667 nodes = map(bin, args['nodes'][0].split(" "))
667 nodes = map(bin, args['nodes'][0].split(" "))
668 for b in self.repo.branches(nodes):
668 for b in self.repo.branches(nodes):
669 sys.stdout.write(" ".join(map(hex, b)) + "\n")
669 sys.stdout.write(" ".join(map(hex, b)) + "\n")
670
670
671 elif args['cmd'][0] == 'between':
671 elif args['cmd'][0] == 'between':
672 httphdr("text/plain")
672 httphdr("text/plain")
673 nodes = []
673 nodes = []
674 if args.has_key('pairs'):
674 if args.has_key('pairs'):
675 pairs = [ map(bin, p.split("-"))
675 pairs = [ map(bin, p.split("-"))
676 for p in args['pairs'][0].split(" ") ]
676 for p in args['pairs'][0].split(" ") ]
677 for b in self.repo.between(pairs):
677 for b in self.repo.between(pairs):
678 sys.stdout.write(" ".join(map(hex, b)) + "\n")
678 sys.stdout.write(" ".join(map(hex, b)) + "\n")
679
679
680 elif args['cmd'][0] == 'changegroup':
680 elif args['cmd'][0] == 'changegroup':
681 httphdr("application/hg-changegroup")
681 httphdr("application/hg-changegroup")
682 nodes = []
682 nodes = []
683 if self.viewonly:
683 if self.viewonly:
684 return
684 return
685
685
686 if args.has_key('roots'):
686 if args.has_key('roots'):
687 nodes = map(bin, args['roots'][0].split(" "))
687 nodes = map(bin, args['roots'][0].split(" "))
688
688
689 z = zlib.compressobj()
689 z = zlib.compressobj()
690 for chunk in self.repo.changegroup(nodes):
690 f = self.repo.changegroup(nodes)
691 while 1:
692 chunk = f.read(4096)
693 if not chunk: break
691 sys.stdout.write(z.compress(chunk))
694 sys.stdout.write(z.compress(chunk))
692
695
693 sys.stdout.write(z.flush())
696 sys.stdout.write(z.flush())
694
697
695 else:
698 else:
696 write(self.t("error"))
699 write(self.t("error"))
697
700
698 def create_server(path, name, templates, address, port,
701 def create_server(path, name, templates, address, port,
699 accesslog = sys.stdout, errorlog = sys.stderr):
702 accesslog = sys.stdout, errorlog = sys.stderr):
700
703
701 import BaseHTTPServer
704 import BaseHTTPServer
702
705
703 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
706 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
704 def log_error(self, format, *args):
707 def log_error(self, format, *args):
705 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
708 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
706 self.log_date_time_string(),
709 self.log_date_time_string(),
707 format % args))
710 format % args))
708
711
709 def log_message(self, format, *args):
712 def log_message(self, format, *args):
710 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
713 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
711 self.log_date_time_string(),
714 self.log_date_time_string(),
712 format % args))
715 format % args))
713
716
714 def do_POST(self):
717 def do_POST(self):
715 try:
718 try:
716 self.do_hgweb()
719 self.do_hgweb()
717 except socket.error, inst:
720 except socket.error, inst:
718 if inst.args[0] != 32: raise
721 if inst.args[0] != 32: raise
719
722
720 def do_GET(self):
723 def do_GET(self):
721 self.do_POST()
724 self.do_POST()
722
725
723 def do_hgweb(self):
726 def do_hgweb(self):
724 query = ""
727 query = ""
725 p = self.path.find("?")
728 p = self.path.find("?")
726 if p:
729 if p:
727 query = self.path[p + 1:]
730 query = self.path[p + 1:]
728 query = query.replace('+', ' ')
731 query = query.replace('+', ' ')
729
732
730 env = {}
733 env = {}
731 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
734 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
732 env['REQUEST_METHOD'] = self.command
735 env['REQUEST_METHOD'] = self.command
733 env['SERVER_NAME'] = self.server.server_name
736 env['SERVER_NAME'] = self.server.server_name
734 env['SERVER_PORT'] = str(self.server.server_port)
737 env['SERVER_PORT'] = str(self.server.server_port)
735 env['REQUEST_URI'] = "/"
738 env['REQUEST_URI'] = "/"
736 if query:
739 if query:
737 env['QUERY_STRING'] = query
740 env['QUERY_STRING'] = query
738 host = self.address_string()
741 host = self.address_string()
739 if host != self.client_address[0]:
742 if host != self.client_address[0]:
740 env['REMOTE_HOST'] = host
743 env['REMOTE_HOST'] = host
741 env['REMOTE_ADDR'] = self.client_address[0]
744 env['REMOTE_ADDR'] = self.client_address[0]
742
745
743 if self.headers.typeheader is None:
746 if self.headers.typeheader is None:
744 env['CONTENT_TYPE'] = self.headers.type
747 env['CONTENT_TYPE'] = self.headers.type
745 else:
748 else:
746 env['CONTENT_TYPE'] = self.headers.typeheader
749 env['CONTENT_TYPE'] = self.headers.typeheader
747 length = self.headers.getheader('content-length')
750 length = self.headers.getheader('content-length')
748 if length:
751 if length:
749 env['CONTENT_LENGTH'] = length
752 env['CONTENT_LENGTH'] = length
750 accept = []
753 accept = []
751 for line in self.headers.getallmatchingheaders('accept'):
754 for line in self.headers.getallmatchingheaders('accept'):
752 if line[:1] in "\t\n\r ":
755 if line[:1] in "\t\n\r ":
753 accept.append(line.strip())
756 accept.append(line.strip())
754 else:
757 else:
755 accept = accept + line[7:].split(',')
758 accept = accept + line[7:].split(',')
756 env['HTTP_ACCEPT'] = ','.join(accept)
759 env['HTTP_ACCEPT'] = ','.join(accept)
757
760
758 os.environ.update(env)
761 os.environ.update(env)
759
762
760 save = sys.argv, sys.stdin, sys.stdout, sys.stderr
763 save = sys.argv, sys.stdin, sys.stdout, sys.stderr
761 try:
764 try:
762 sys.stdin = self.rfile
765 sys.stdin = self.rfile
763 sys.stdout = self.wfile
766 sys.stdout = self.wfile
764 sys.argv = ["hgweb.py"]
767 sys.argv = ["hgweb.py"]
765 if '=' not in query:
768 if '=' not in query:
766 sys.argv.append(query)
769 sys.argv.append(query)
767 self.send_response(200, "Script output follows")
770 self.send_response(200, "Script output follows")
768 hg.run()
771 hg.run()
769 finally:
772 finally:
770 sys.argv, sys.stdin, sys.stdout, sys.stderr = save
773 sys.argv, sys.stdin, sys.stdout, sys.stderr = save
771
774
772 hg = hgweb(path, name, templates)
775 hg = hgweb(path, name, templates)
773 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
776 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
774
777
775 def server(path, name, templates, address, port,
778 def server(path, name, templates, address, port,
776 accesslog = sys.stdout, errorlog = sys.stderr):
779 accesslog = sys.stdout, errorlog = sys.stderr):
777 httpd = create_server(path, name, templates, address, port,
780 httpd = create_server(path, name, templates, address, port,
778 accesslog, errorlog)
781 accesslog, errorlog)
779 httpd.serve_forever()
782 httpd.serve_forever()
General Comments 0
You need to be logged in to leave comments. Login now