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