##// END OF EJS Templates
Fix some bugs introduced during the manifest refactoring
Alexis S. L. Carvalho -
r2857:18cf5349 default
parent child Browse files
Show More
@@ -1,173 +1,174 b''
1 1 # archival.py - revision archival for mercurial
2 2 #
3 3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of
6 6 # the GNU General Public License, incorporated herein by reference.
7 7
8 8 from demandload import *
9 9 from i18n import gettext as _
10 10 from node import *
11 11 demandload(globals(), 'cStringIO os stat tarfile time util zipfile')
12 12
13 13 def tidyprefix(dest, prefix, suffixes):
14 14 '''choose prefix to use for names in archive. make sure prefix is
15 15 safe for consumers.'''
16 16
17 17 if prefix:
18 18 prefix = prefix.replace('\\', '/')
19 19 else:
20 20 if not isinstance(dest, str):
21 21 raise ValueError('dest must be string if no prefix')
22 22 prefix = os.path.basename(dest)
23 23 lower = prefix.lower()
24 24 for sfx in suffixes:
25 25 if lower.endswith(sfx):
26 26 prefix = prefix[:-len(sfx)]
27 27 break
28 28 lpfx = os.path.normpath(util.localpath(prefix))
29 29 prefix = util.pconvert(lpfx)
30 30 if not prefix.endswith('/'):
31 31 prefix += '/'
32 32 if prefix.startswith('../') or os.path.isabs(lpfx) or '/../' in prefix:
33 33 raise util.Abort(_('archive prefix contains illegal components'))
34 34 return prefix
35 35
36 36 class tarit:
37 37 '''write archive to tar file or stream. can write uncompressed,
38 38 or compress with gzip or bzip2.'''
39 39
40 40 def __init__(self, dest, prefix, mtime, kind=''):
41 41 self.prefix = tidyprefix(dest, prefix, ['.tar', '.tar.bz2', '.tar.gz',
42 42 '.tgz', 'tbz2'])
43 43 self.mtime = mtime
44 44 if isinstance(dest, str):
45 45 self.z = tarfile.open(dest, mode='w:'+kind)
46 46 else:
47 47 self.z = tarfile.open(mode='w|'+kind, fileobj=dest)
48 48
49 49 def addfile(self, name, mode, data):
50 50 i = tarfile.TarInfo(self.prefix + name)
51 51 i.mtime = self.mtime
52 52 i.size = len(data)
53 53 i.mode = mode
54 54 self.z.addfile(i, cStringIO.StringIO(data))
55 55
56 56 def done(self):
57 57 self.z.close()
58 58
59 59 class tellable:
60 60 '''provide tell method for zipfile.ZipFile when writing to http
61 61 response file object.'''
62 62
63 63 def __init__(self, fp):
64 64 self.fp = fp
65 65 self.offset = 0
66 66
67 67 def __getattr__(self, key):
68 68 return getattr(self.fp, key)
69 69
70 70 def write(self, s):
71 71 self.fp.write(s)
72 72 self.offset += len(s)
73 73
74 74 def tell(self):
75 75 return self.offset
76 76
77 77 class zipit:
78 78 '''write archive to zip file or stream. can write uncompressed,
79 79 or compressed with deflate.'''
80 80
81 81 def __init__(self, dest, prefix, mtime, compress=True):
82 82 self.prefix = tidyprefix(dest, prefix, ('.zip',))
83 83 if not isinstance(dest, str):
84 84 try:
85 85 dest.tell()
86 86 except (AttributeError, IOError):
87 87 dest = tellable(dest)
88 88 self.z = zipfile.ZipFile(dest, 'w',
89 89 compress and zipfile.ZIP_DEFLATED or
90 90 zipfile.ZIP_STORED)
91 91 self.date_time = time.gmtime(mtime)[:6]
92 92
93 93 def addfile(self, name, mode, data):
94 94 i = zipfile.ZipInfo(self.prefix + name, self.date_time)
95 95 i.compress_type = self.z.compression
96 96 i.flag_bits = 0x08
97 97 # unzip will not honor unix file modes unless file creator is
98 98 # set to unix (id 3).
99 99 i.create_system = 3
100 100 i.external_attr = (mode | stat.S_IFREG) << 16L
101 101 self.z.writestr(i, data)
102 102
103 103 def done(self):
104 104 self.z.close()
105 105
106 106 class fileit:
107 107 '''write archive as files in directory.'''
108 108
109 109 def __init__(self, name, prefix, mtime):
110 110 if prefix:
111 111 raise util.Abort(_('cannot give prefix when archiving to files'))
112 112 self.basedir = name
113 113 self.dirs = {}
114 114 self.oflags = (os.O_CREAT | os.O_EXCL | os.O_WRONLY |
115 115 getattr(os, 'O_BINARY', 0) |
116 116 getattr(os, 'O_NOFOLLOW', 0))
117 117
118 118 def addfile(self, name, mode, data):
119 119 destfile = os.path.join(self.basedir, name)
120 120 destdir = os.path.dirname(destfile)
121 121 if destdir not in self.dirs:
122 122 if not os.path.isdir(destdir):
123 123 os.makedirs(destdir)
124 124 self.dirs[destdir] = 1
125 125 os.fdopen(os.open(destfile, self.oflags, mode), 'wb').write(data)
126 126
127 127 def done(self):
128 128 pass
129 129
130 130 archivers = {
131 131 'files': fileit,
132 132 'tar': tarit,
133 133 'tbz2': lambda name, prefix, mtime: tarit(name, prefix, mtime, 'bz2'),
134 134 'tgz': lambda name, prefix, mtime: tarit(name, prefix, mtime, 'gz'),
135 135 'uzip': lambda name, prefix, mtime: zipit(name, prefix, mtime, False),
136 136 'zip': zipit,
137 137 }
138 138
139 139 def archive(repo, dest, node, kind, decode=True, matchfn=None,
140 140 prefix=None, mtime=None):
141 141 '''create archive of repo as it was at node.
142 142
143 143 dest can be name of directory, name of archive file, or file
144 144 object to write archive to.
145 145
146 146 kind is type of archive to create.
147 147
148 148 decode tells whether to put files through decode filters from
149 149 hgrc.
150 150
151 151 matchfn is function to filter names of files to write to archive.
152 152
153 153 prefix is name of path to put before every archive member.'''
154 154
155 155 def write(name, mode, data):
156 156 if matchfn and not matchfn(name): return
157 157 if decode:
158 158 fp = cStringIO.StringIO()
159 159 repo.wwrite(name, data, fp)
160 160 data = fp.getvalue()
161 161 archiver.addfile(name, mode, data)
162 162
163 163 change = repo.changelog.read(node)
164 164 mn = change[0]
165 165 archiver = archivers[kind](dest, prefix, mtime or change[2][0])
166 mf = repo.manifest.read(mn).items()
167 mf.sort()
166 m = repo.manifest.read(mn)
167 items = m.items()
168 items.sort()
168 169 write('.hg_archival.txt', 0644,
169 170 'repo: %s\nnode: %s\n' % (hex(repo.changelog.node(0)), hex(node)))
170 for filename, filenode in mf:
171 write(filename, mf.execf(filename) and 0755 or 0644,
171 for filename, filenode in items:
172 write(filename, m.execf(filename) and 0755 or 0644,
172 173 repo.file(filename).read(filenode))
173 174 archiver.done()
@@ -1,991 +1,991 b''
1 1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import os
10 10 import os.path
11 11 import mimetypes
12 12 from mercurial.demandload import demandload
13 13 demandload(globals(), "re zlib ConfigParser mimetools cStringIO sys tempfile")
14 14 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,streamclone")
15 15 demandload(globals(), "mercurial:templater")
16 16 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
17 17 from mercurial.node import *
18 18 from mercurial.i18n import gettext as _
19 19
20 20 def _up(p):
21 21 if p[0] != "/":
22 22 p = "/" + p
23 23 if p[-1] == "/":
24 24 p = p[:-1]
25 25 up = os.path.dirname(p)
26 26 if up == "/":
27 27 return "/"
28 28 return up + "/"
29 29
30 30 class hgweb(object):
31 31 def __init__(self, repo, name=None):
32 32 if type(repo) == type(""):
33 33 self.repo = hg.repository(ui.ui(), repo)
34 34 else:
35 35 self.repo = repo
36 36
37 37 self.mtime = -1
38 38 self.reponame = name
39 39 self.archives = 'zip', 'gz', 'bz2'
40 40 self.stripecount = 1
41 41 self.templatepath = self.repo.ui.config("web", "templates",
42 42 templater.templatepath())
43 43
44 44 def refresh(self):
45 45 mtime = get_mtime(self.repo.root)
46 46 if mtime != self.mtime:
47 47 self.mtime = mtime
48 48 self.repo = hg.repository(self.repo.ui, self.repo.root)
49 49 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
50 50 self.stripecount = int(self.repo.ui.config("web", "stripes", 1))
51 51 self.maxshortchanges = int(self.repo.ui.config("web", "maxshortchanges", 60))
52 52 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
53 53 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
54 54
55 55 def archivelist(self, nodeid):
56 56 allowed = self.repo.ui.configlist("web", "allow_archive")
57 57 for i in self.archives:
58 58 if i in allowed or self.repo.ui.configbool("web", "allow" + i):
59 59 yield {"type" : i, "node" : nodeid, "url": ""}
60 60
61 61 def listfiles(self, files, mf):
62 62 for f in files[:self.maxfiles]:
63 63 yield self.t("filenodelink", node=hex(mf[f]), file=f)
64 64 if len(files) > self.maxfiles:
65 65 yield self.t("fileellipses")
66 66
67 67 def listfilediffs(self, files, changeset):
68 68 for f in files[:self.maxfiles]:
69 69 yield self.t("filedifflink", node=hex(changeset), file=f)
70 70 if len(files) > self.maxfiles:
71 71 yield self.t("fileellipses")
72 72
73 73 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
74 74 if not rev:
75 75 rev = lambda x: ""
76 76 siblings = [s for s in siblings if s != nullid]
77 77 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
78 78 return
79 79 for s in siblings:
80 80 yield dict(node=hex(s), rev=rev(s), **args)
81 81
82 82 def renamelink(self, fl, node):
83 83 r = fl.renamed(node)
84 84 if r:
85 85 return [dict(file=r[0], node=hex(r[1]))]
86 86 return []
87 87
88 88 def showtag(self, t1, node=nullid, **args):
89 89 for t in self.repo.nodetags(node):
90 90 yield self.t(t1, tag=t, **args)
91 91
92 92 def diff(self, node1, node2, files):
93 93 def filterfiles(filters, files):
94 94 l = [x for x in files if x in filters]
95 95
96 96 for t in filters:
97 97 if t and t[-1] != os.sep:
98 98 t += os.sep
99 99 l += [x for x in files if x.startswith(t)]
100 100 return l
101 101
102 102 parity = [0]
103 103 def diffblock(diff, f, fn):
104 104 yield self.t("diffblock",
105 105 lines=prettyprintlines(diff),
106 106 parity=parity[0],
107 107 file=f,
108 108 filenode=hex(fn or nullid))
109 109 parity[0] = 1 - parity[0]
110 110
111 111 def prettyprintlines(diff):
112 112 for l in diff.splitlines(1):
113 113 if l.startswith('+'):
114 114 yield self.t("difflineplus", line=l)
115 115 elif l.startswith('-'):
116 116 yield self.t("difflineminus", line=l)
117 117 elif l.startswith('@'):
118 118 yield self.t("difflineat", line=l)
119 119 else:
120 120 yield self.t("diffline", line=l)
121 121
122 122 r = self.repo
123 123 cl = r.changelog
124 124 mf = r.manifest
125 125 change1 = cl.read(node1)
126 126 change2 = cl.read(node2)
127 127 mmap1 = mf.read(change1[0])
128 128 mmap2 = mf.read(change2[0])
129 129 date1 = util.datestr(change1[2])
130 130 date2 = util.datestr(change2[2])
131 131
132 132 modified, added, removed, deleted, unknown = r.changes(node1, node2)
133 133 if files:
134 134 modified, added, removed = map(lambda x: filterfiles(files, x),
135 135 (modified, added, removed))
136 136
137 137 diffopts = self.repo.ui.diffopts()
138 138 showfunc = diffopts['showfunc']
139 139 ignorews = diffopts['ignorews']
140 140 ignorewsamount = diffopts['ignorewsamount']
141 141 ignoreblanklines = diffopts['ignoreblanklines']
142 142 for f in modified:
143 143 to = r.file(f).read(mmap1[f])
144 144 tn = r.file(f).read(mmap2[f])
145 145 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
146 146 showfunc=showfunc, ignorews=ignorews,
147 147 ignorewsamount=ignorewsamount,
148 148 ignoreblanklines=ignoreblanklines), f, tn)
149 149 for f in added:
150 150 to = None
151 151 tn = r.file(f).read(mmap2[f])
152 152 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
153 153 showfunc=showfunc, ignorews=ignorews,
154 154 ignorewsamount=ignorewsamount,
155 155 ignoreblanklines=ignoreblanklines), f, tn)
156 156 for f in removed:
157 157 to = r.file(f).read(mmap1[f])
158 158 tn = None
159 159 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
160 160 showfunc=showfunc, ignorews=ignorews,
161 161 ignorewsamount=ignorewsamount,
162 162 ignoreblanklines=ignoreblanklines), f, tn)
163 163
164 164 def changelog(self, pos, shortlog=False):
165 165 def changenav(**map):
166 166 def seq(factor, maxchanges=None):
167 167 if maxchanges:
168 168 yield maxchanges
169 169 if maxchanges >= 20 and maxchanges <= 40:
170 170 yield 50
171 171 else:
172 172 yield 1 * factor
173 173 yield 3 * factor
174 174 for f in seq(factor * 10):
175 175 yield f
176 176
177 177 l = []
178 178 last = 0
179 179 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
180 180 for f in seq(1, maxchanges):
181 181 if f < maxchanges or f <= last:
182 182 continue
183 183 if f > count:
184 184 break
185 185 last = f
186 186 r = "%d" % f
187 187 if pos + f < count:
188 188 l.append(("+" + r, pos + f))
189 189 if pos - f >= 0:
190 190 l.insert(0, ("-" + r, pos - f))
191 191
192 192 yield {"rev": 0, "label": "(0)"}
193 193
194 194 for label, rev in l:
195 195 yield {"label": label, "rev": rev}
196 196
197 197 yield {"label": "tip", "rev": "tip"}
198 198
199 199 def changelist(**map):
200 200 parity = (start - end) & 1
201 201 cl = self.repo.changelog
202 202 l = [] # build a list in forward order for efficiency
203 203 for i in range(start, end):
204 204 n = cl.node(i)
205 205 changes = cl.read(n)
206 206 hn = hex(n)
207 207
208 208 l.insert(0, {"parity": parity,
209 209 "author": changes[1],
210 210 "parent": self.siblings(cl.parents(n), cl.rev,
211 211 cl.rev(n) - 1),
212 212 "child": self.siblings(cl.children(n), cl.rev,
213 213 cl.rev(n) + 1),
214 214 "changelogtag": self.showtag("changelogtag",n),
215 215 "manifest": hex(changes[0]),
216 216 "desc": changes[4],
217 217 "date": changes[2],
218 218 "files": self.listfilediffs(changes[3], n),
219 219 "rev": i,
220 220 "node": hn})
221 221 parity = 1 - parity
222 222
223 223 for e in l:
224 224 yield e
225 225
226 226 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
227 227 cl = self.repo.changelog
228 228 mf = cl.read(cl.tip())[0]
229 229 count = cl.count()
230 230 start = max(0, pos - maxchanges + 1)
231 231 end = min(count, start + maxchanges)
232 232 pos = end - 1
233 233
234 234 yield self.t(shortlog and 'shortlog' or 'changelog',
235 235 changenav=changenav,
236 236 manifest=hex(mf),
237 237 rev=pos, changesets=count, entries=changelist,
238 238 archives=self.archivelist("tip"))
239 239
240 240 def search(self, query):
241 241
242 242 def changelist(**map):
243 243 cl = self.repo.changelog
244 244 count = 0
245 245 qw = query.lower().split()
246 246
247 247 def revgen():
248 248 for i in range(cl.count() - 1, 0, -100):
249 249 l = []
250 250 for j in range(max(0, i - 100), i):
251 251 n = cl.node(j)
252 252 changes = cl.read(n)
253 253 l.append((n, j, changes))
254 254 l.reverse()
255 255 for e in l:
256 256 yield e
257 257
258 258 for n, i, changes in revgen():
259 259 miss = 0
260 260 for q in qw:
261 261 if not (q in changes[1].lower() or
262 262 q in changes[4].lower() or
263 263 q in " ".join(changes[3][:20]).lower()):
264 264 miss = 1
265 265 break
266 266 if miss:
267 267 continue
268 268
269 269 count += 1
270 270 hn = hex(n)
271 271
272 272 yield self.t('searchentry',
273 273 parity=self.stripes(count),
274 274 author=changes[1],
275 275 parent=self.siblings(cl.parents(n), cl.rev),
276 276 child=self.siblings(cl.children(n), cl.rev),
277 277 changelogtag=self.showtag("changelogtag",n),
278 278 manifest=hex(changes[0]),
279 279 desc=changes[4],
280 280 date=changes[2],
281 281 files=self.listfilediffs(changes[3], n),
282 282 rev=i,
283 283 node=hn)
284 284
285 285 if count >= self.maxchanges:
286 286 break
287 287
288 288 cl = self.repo.changelog
289 289 mf = cl.read(cl.tip())[0]
290 290
291 291 yield self.t('search',
292 292 query=query,
293 293 manifest=hex(mf),
294 294 entries=changelist)
295 295
296 296 def changeset(self, nodeid):
297 297 cl = self.repo.changelog
298 298 n = self.repo.lookup(nodeid)
299 299 nodeid = hex(n)
300 300 changes = cl.read(n)
301 301 p1 = cl.parents(n)[0]
302 302
303 303 files = []
304 304 mf = self.repo.manifest.read(changes[0])
305 305 for f in changes[3]:
306 306 files.append(self.t("filenodelink",
307 307 filenode=hex(mf.get(f, nullid)), file=f))
308 308
309 309 def diff(**map):
310 310 yield self.diff(p1, n, None)
311 311
312 312 yield self.t('changeset',
313 313 diff=diff,
314 314 rev=cl.rev(n),
315 315 node=nodeid,
316 316 parent=self.siblings(cl.parents(n), cl.rev),
317 317 child=self.siblings(cl.children(n), cl.rev),
318 318 changesettag=self.showtag("changesettag",n),
319 319 manifest=hex(changes[0]),
320 320 author=changes[1],
321 321 desc=changes[4],
322 322 date=changes[2],
323 323 files=files,
324 324 archives=self.archivelist(nodeid))
325 325
326 326 def filelog(self, f, filenode):
327 327 cl = self.repo.changelog
328 328 fl = self.repo.file(f)
329 329 filenode = hex(fl.lookup(filenode))
330 330 count = fl.count()
331 331
332 332 def entries(**map):
333 333 l = []
334 334 parity = (count - 1) & 1
335 335
336 336 for i in range(count):
337 337 n = fl.node(i)
338 338 lr = fl.linkrev(n)
339 339 cn = cl.node(lr)
340 340 cs = cl.read(cl.node(lr))
341 341
342 342 l.insert(0, {"parity": parity,
343 343 "filenode": hex(n),
344 344 "filerev": i,
345 345 "file": f,
346 346 "node": hex(cn),
347 347 "author": cs[1],
348 348 "date": cs[2],
349 349 "rename": self.renamelink(fl, n),
350 350 "parent": self.siblings(fl.parents(n),
351 351 fl.rev, file=f),
352 352 "child": self.siblings(fl.children(n),
353 353 fl.rev, file=f),
354 354 "desc": cs[4]})
355 355 parity = 1 - parity
356 356
357 357 for e in l:
358 358 yield e
359 359
360 360 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
361 361
362 362 def filerevision(self, f, node):
363 363 fl = self.repo.file(f)
364 364 n = fl.lookup(node)
365 365 node = hex(n)
366 366 text = fl.read(n)
367 367 changerev = fl.linkrev(n)
368 368 cl = self.repo.changelog
369 369 cn = cl.node(changerev)
370 370 cs = cl.read(cn)
371 371 mfn = cs[0]
372 372
373 373 mt = mimetypes.guess_type(f)[0]
374 374 rawtext = text
375 375 if util.binary(text):
376 376 mt = mt or 'application/octet-stream'
377 377 text = "(binary:%s)" % mt
378 378 mt = mt or 'text/plain'
379 379
380 380 def lines():
381 381 for l, t in enumerate(text.splitlines(1)):
382 382 yield {"line": t,
383 383 "linenumber": "% 6d" % (l + 1),
384 384 "parity": self.stripes(l)}
385 385
386 386 yield self.t("filerevision",
387 387 file=f,
388 388 filenode=node,
389 389 path=_up(f),
390 390 text=lines(),
391 391 raw=rawtext,
392 392 mimetype=mt,
393 393 rev=changerev,
394 394 node=hex(cn),
395 395 manifest=hex(mfn),
396 396 author=cs[1],
397 397 date=cs[2],
398 398 parent=self.siblings(fl.parents(n), fl.rev, file=f),
399 399 child=self.siblings(fl.children(n), fl.rev, file=f),
400 400 rename=self.renamelink(fl, n),
401 permissions=self.repo.manifest.read(mfn).execf[f])
401 permissions=self.repo.manifest.read(mfn).execf(f))
402 402
403 403 def fileannotate(self, f, node):
404 404 bcache = {}
405 405 ncache = {}
406 406 fl = self.repo.file(f)
407 407 n = fl.lookup(node)
408 408 node = hex(n)
409 409 changerev = fl.linkrev(n)
410 410
411 411 cl = self.repo.changelog
412 412 cn = cl.node(changerev)
413 413 cs = cl.read(cn)
414 414 mfn = cs[0]
415 415
416 416 def annotate(**map):
417 417 parity = 0
418 418 last = None
419 419 for r, l in fl.annotate(n):
420 420 try:
421 421 cnode = ncache[r]
422 422 except KeyError:
423 423 cnode = ncache[r] = self.repo.changelog.node(r)
424 424
425 425 try:
426 426 name = bcache[r]
427 427 except KeyError:
428 428 cl = self.repo.changelog.read(cnode)
429 429 bcache[r] = name = self.repo.ui.shortuser(cl[1])
430 430
431 431 if last != cnode:
432 432 parity = 1 - parity
433 433 last = cnode
434 434
435 435 yield {"parity": parity,
436 436 "node": hex(cnode),
437 437 "rev": r,
438 438 "author": name,
439 439 "file": f,
440 440 "line": l}
441 441
442 442 yield self.t("fileannotate",
443 443 file=f,
444 444 filenode=node,
445 445 annotate=annotate,
446 446 path=_up(f),
447 447 rev=changerev,
448 448 node=hex(cn),
449 449 manifest=hex(mfn),
450 450 author=cs[1],
451 451 date=cs[2],
452 452 rename=self.renamelink(fl, n),
453 453 parent=self.siblings(fl.parents(n), fl.rev, file=f),
454 454 child=self.siblings(fl.children(n), fl.rev, file=f),
455 permissions=self.repo.manifest.read(mfn).execf[f])
455 permissions=self.repo.manifest.read(mfn).execf(f))
456 456
457 457 def manifest(self, mnode, path):
458 458 man = self.repo.manifest
459 459 mn = man.lookup(mnode)
460 460 mnode = hex(mn)
461 461 mf = man.read(mn)
462 462 rev = man.rev(mn)
463 463 changerev = man.linkrev(mn)
464 464 node = self.repo.changelog.node(changerev)
465 465
466 466 files = {}
467 467
468 468 p = path[1:]
469 469 if p and p[-1] != "/":
470 470 p += "/"
471 471 l = len(p)
472 472
473 473 for f,n in mf.items():
474 474 if f[:l] != p:
475 475 continue
476 476 remain = f[l:]
477 477 if "/" in remain:
478 478 short = remain[:remain.index("/") + 1] # bleah
479 479 files[short] = (f, None)
480 480 else:
481 481 short = os.path.basename(remain)
482 482 files[short] = (f, n)
483 483
484 484 def filelist(**map):
485 485 parity = 0
486 486 fl = files.keys()
487 487 fl.sort()
488 488 for f in fl:
489 489 full, fnode = files[f]
490 490 if not fnode:
491 491 continue
492 492
493 493 yield {"file": full,
494 494 "manifest": mnode,
495 495 "filenode": hex(fnode),
496 496 "parity": self.stripes(parity),
497 497 "basename": f,
498 "permissions": mf.execf[full]}
498 "permissions": mf.execf(full)}
499 499 parity += 1
500 500
501 501 def dirlist(**map):
502 502 parity = 0
503 503 fl = files.keys()
504 504 fl.sort()
505 505 for f in fl:
506 506 full, fnode = files[f]
507 507 if fnode:
508 508 continue
509 509
510 510 yield {"parity": self.stripes(parity),
511 511 "path": os.path.join(path, f),
512 512 "manifest": mnode,
513 513 "basename": f[:-1]}
514 514 parity += 1
515 515
516 516 yield self.t("manifest",
517 517 manifest=mnode,
518 518 rev=rev,
519 519 node=hex(node),
520 520 path=path,
521 521 up=_up(path),
522 522 fentries=filelist,
523 523 dentries=dirlist,
524 524 archives=self.archivelist(hex(node)))
525 525
526 526 def tags(self):
527 527 cl = self.repo.changelog
528 528 mf = cl.read(cl.tip())[0]
529 529
530 530 i = self.repo.tagslist()
531 531 i.reverse()
532 532
533 533 def entries(notip=False, **map):
534 534 parity = 0
535 535 for k,n in i:
536 536 if notip and k == "tip": continue
537 537 yield {"parity": self.stripes(parity),
538 538 "tag": k,
539 539 "tagmanifest": hex(cl.read(n)[0]),
540 540 "date": cl.read(n)[2],
541 541 "node": hex(n)}
542 542 parity += 1
543 543
544 544 yield self.t("tags",
545 545 manifest=hex(mf),
546 546 entries=lambda **x: entries(False, **x),
547 547 entriesnotip=lambda **x: entries(True, **x))
548 548
549 549 def summary(self):
550 550 cl = self.repo.changelog
551 551 mf = cl.read(cl.tip())[0]
552 552
553 553 i = self.repo.tagslist()
554 554 i.reverse()
555 555
556 556 def tagentries(**map):
557 557 parity = 0
558 558 count = 0
559 559 for k,n in i:
560 560 if k == "tip": # skip tip
561 561 continue;
562 562
563 563 count += 1
564 564 if count > 10: # limit to 10 tags
565 565 break;
566 566
567 567 c = cl.read(n)
568 568 m = c[0]
569 569 t = c[2]
570 570
571 571 yield self.t("tagentry",
572 572 parity = self.stripes(parity),
573 573 tag = k,
574 574 node = hex(n),
575 575 date = t,
576 576 tagmanifest = hex(m))
577 577 parity += 1
578 578
579 579 def changelist(**map):
580 580 parity = 0
581 581 cl = self.repo.changelog
582 582 l = [] # build a list in forward order for efficiency
583 583 for i in range(start, end):
584 584 n = cl.node(i)
585 585 changes = cl.read(n)
586 586 hn = hex(n)
587 587 t = changes[2]
588 588
589 589 l.insert(0, self.t(
590 590 'shortlogentry',
591 591 parity = parity,
592 592 author = changes[1],
593 593 manifest = hex(changes[0]),
594 594 desc = changes[4],
595 595 date = t,
596 596 rev = i,
597 597 node = hn))
598 598 parity = 1 - parity
599 599
600 600 yield l
601 601
602 602 cl = self.repo.changelog
603 603 mf = cl.read(cl.tip())[0]
604 604 count = cl.count()
605 605 start = max(0, count - self.maxchanges)
606 606 end = min(count, start + self.maxchanges)
607 607
608 608 yield self.t("summary",
609 609 desc = self.repo.ui.config("web", "description", "unknown"),
610 610 owner = (self.repo.ui.config("ui", "username") or # preferred
611 611 self.repo.ui.config("web", "contact") or # deprecated
612 612 self.repo.ui.config("web", "author", "unknown")), # also
613 613 lastchange = (0, 0), # FIXME
614 614 manifest = hex(mf),
615 615 tags = tagentries,
616 616 shortlog = changelist,
617 617 archives=self.archivelist("tip"))
618 618
619 619 def filediff(self, file, changeset):
620 620 cl = self.repo.changelog
621 621 n = self.repo.lookup(changeset)
622 622 changeset = hex(n)
623 623 p1 = cl.parents(n)[0]
624 624 cs = cl.read(n)
625 625 mf = self.repo.manifest.read(cs[0])
626 626
627 627 def diff(**map):
628 628 yield self.diff(p1, n, [file])
629 629
630 630 yield self.t("filediff",
631 631 file=file,
632 632 filenode=hex(mf.get(file, nullid)),
633 633 node=changeset,
634 634 rev=self.repo.changelog.rev(n),
635 635 parent=self.siblings(cl.parents(n), cl.rev),
636 636 child=self.siblings(cl.children(n), cl.rev),
637 637 diff=diff)
638 638
639 639 archive_specs = {
640 640 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
641 641 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
642 642 'zip': ('application/zip', 'zip', '.zip', None),
643 643 }
644 644
645 645 def archive(self, req, cnode, type_):
646 646 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
647 647 name = "%s-%s" % (reponame, short(cnode))
648 648 mimetype, artype, extension, encoding = self.archive_specs[type_]
649 649 headers = [('Content-type', mimetype),
650 650 ('Content-disposition', 'attachment; filename=%s%s' %
651 651 (name, extension))]
652 652 if encoding:
653 653 headers.append(('Content-encoding', encoding))
654 654 req.header(headers)
655 655 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
656 656
657 657 # add tags to things
658 658 # tags -> list of changesets corresponding to tags
659 659 # find tag, changeset, file
660 660
661 661 def cleanpath(self, path):
662 662 p = util.normpath(path)
663 663 if p[:2] == "..":
664 664 raise Exception("suspicious path")
665 665 return p
666 666
667 667 def run(self):
668 668 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
669 669 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
670 670 import mercurial.hgweb.wsgicgi as wsgicgi
671 671 from request import wsgiapplication
672 672 def make_web_app():
673 673 return self
674 674 wsgicgi.launch(wsgiapplication(make_web_app))
675 675
676 676 def run_wsgi(self, req):
677 677 def header(**map):
678 678 header_file = cStringIO.StringIO(''.join(self.t("header", **map)))
679 679 msg = mimetools.Message(header_file, 0)
680 680 req.header(msg.items())
681 681 yield header_file.read()
682 682
683 683 def rawfileheader(**map):
684 684 req.header([('Content-type', map['mimetype']),
685 685 ('Content-disposition', 'filename=%s' % map['file']),
686 686 ('Content-length', str(len(map['raw'])))])
687 687 yield ''
688 688
689 689 def footer(**map):
690 690 yield self.t("footer",
691 691 motd=self.repo.ui.config("web", "motd", ""),
692 692 **map)
693 693
694 694 def expand_form(form):
695 695 shortcuts = {
696 696 'cl': [('cmd', ['changelog']), ('rev', None)],
697 697 'sl': [('cmd', ['shortlog']), ('rev', None)],
698 698 'cs': [('cmd', ['changeset']), ('node', None)],
699 699 'f': [('cmd', ['file']), ('filenode', None)],
700 700 'fl': [('cmd', ['filelog']), ('filenode', None)],
701 701 'fd': [('cmd', ['filediff']), ('node', None)],
702 702 'fa': [('cmd', ['annotate']), ('filenode', None)],
703 703 'mf': [('cmd', ['manifest']), ('manifest', None)],
704 704 'ca': [('cmd', ['archive']), ('node', None)],
705 705 'tags': [('cmd', ['tags'])],
706 706 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
707 707 'static': [('cmd', ['static']), ('file', None)]
708 708 }
709 709
710 710 for k in shortcuts.iterkeys():
711 711 if form.has_key(k):
712 712 for name, value in shortcuts[k]:
713 713 if value is None:
714 714 value = form[k]
715 715 form[name] = value
716 716 del form[k]
717 717
718 718 self.refresh()
719 719
720 720 expand_form(req.form)
721 721
722 722 m = os.path.join(self.templatepath, "map")
723 723 style = self.repo.ui.config("web", "style", "")
724 724 if req.form.has_key('style'):
725 725 style = req.form['style'][0]
726 726 if style:
727 727 b = os.path.basename("map-" + style)
728 728 p = os.path.join(self.templatepath, b)
729 729 if os.path.isfile(p):
730 730 m = p
731 731
732 732 port = req.env["SERVER_PORT"]
733 733 port = port != "80" and (":" + port) or ""
734 734 uri = req.env["REQUEST_URI"]
735 735 if "?" in uri:
736 736 uri = uri.split("?")[0]
737 737 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
738 738 if not self.reponame:
739 739 self.reponame = (self.repo.ui.config("web", "name")
740 740 or uri.strip('/') or self.repo.root)
741 741
742 742 self.t = templater.templater(m, templater.common_filters,
743 743 defaults={"url": url,
744 744 "repo": self.reponame,
745 745 "header": header,
746 746 "footer": footer,
747 747 "rawfileheader": rawfileheader,
748 748 })
749 749
750 750 if not req.form.has_key('cmd'):
751 751 req.form['cmd'] = [self.t.cache['default'],]
752 752
753 753 cmd = req.form['cmd'][0]
754 754
755 755 method = getattr(self, 'do_' + cmd, None)
756 756 if method:
757 757 method(req)
758 758 else:
759 759 req.write(self.t("error"))
760 760
761 761 def stripes(self, parity):
762 762 "make horizontal stripes for easier reading"
763 763 if self.stripecount:
764 764 return (1 + parity / self.stripecount) & 1
765 765 else:
766 766 return 0
767 767
768 768 def do_changelog(self, req):
769 769 hi = self.repo.changelog.count() - 1
770 770 if req.form.has_key('rev'):
771 771 hi = req.form['rev'][0]
772 772 try:
773 773 hi = self.repo.changelog.rev(self.repo.lookup(hi))
774 774 except hg.RepoError:
775 775 req.write(self.search(hi)) # XXX redirect to 404 page?
776 776 return
777 777
778 778 req.write(self.changelog(hi))
779 779
780 780 def do_shortlog(self, req):
781 781 hi = self.repo.changelog.count() - 1
782 782 if req.form.has_key('rev'):
783 783 hi = req.form['rev'][0]
784 784 try:
785 785 hi = self.repo.changelog.rev(self.repo.lookup(hi))
786 786 except hg.RepoError:
787 787 req.write(self.search(hi)) # XXX redirect to 404 page?
788 788 return
789 789
790 790 req.write(self.changelog(hi, shortlog = True))
791 791
792 792 def do_changeset(self, req):
793 793 req.write(self.changeset(req.form['node'][0]))
794 794
795 795 def do_manifest(self, req):
796 796 req.write(self.manifest(req.form['manifest'][0],
797 797 self.cleanpath(req.form['path'][0])))
798 798
799 799 def do_tags(self, req):
800 800 req.write(self.tags())
801 801
802 802 def do_summary(self, req):
803 803 req.write(self.summary())
804 804
805 805 def do_filediff(self, req):
806 806 req.write(self.filediff(self.cleanpath(req.form['file'][0]),
807 807 req.form['node'][0]))
808 808
809 809 def do_file(self, req):
810 810 req.write(self.filerevision(self.cleanpath(req.form['file'][0]),
811 811 req.form['filenode'][0]))
812 812
813 813 def do_annotate(self, req):
814 814 req.write(self.fileannotate(self.cleanpath(req.form['file'][0]),
815 815 req.form['filenode'][0]))
816 816
817 817 def do_filelog(self, req):
818 818 req.write(self.filelog(self.cleanpath(req.form['file'][0]),
819 819 req.form['filenode'][0]))
820 820
821 821 def do_heads(self, req):
822 822 resp = " ".join(map(hex, self.repo.heads())) + "\n"
823 823 req.httphdr("application/mercurial-0.1", length=len(resp))
824 824 req.write(resp)
825 825
826 826 def do_branches(self, req):
827 827 nodes = []
828 828 if req.form.has_key('nodes'):
829 829 nodes = map(bin, req.form['nodes'][0].split(" "))
830 830 resp = cStringIO.StringIO()
831 831 for b in self.repo.branches(nodes):
832 832 resp.write(" ".join(map(hex, b)) + "\n")
833 833 resp = resp.getvalue()
834 834 req.httphdr("application/mercurial-0.1", length=len(resp))
835 835 req.write(resp)
836 836
837 837 def do_between(self, req):
838 838 nodes = []
839 839 if req.form.has_key('pairs'):
840 840 pairs = [map(bin, p.split("-"))
841 841 for p in req.form['pairs'][0].split(" ")]
842 842 resp = cStringIO.StringIO()
843 843 for b in self.repo.between(pairs):
844 844 resp.write(" ".join(map(hex, b)) + "\n")
845 845 resp = resp.getvalue()
846 846 req.httphdr("application/mercurial-0.1", length=len(resp))
847 847 req.write(resp)
848 848
849 849 def do_changegroup(self, req):
850 850 req.httphdr("application/mercurial-0.1")
851 851 nodes = []
852 852 if not self.allowpull:
853 853 return
854 854
855 855 if req.form.has_key('roots'):
856 856 nodes = map(bin, req.form['roots'][0].split(" "))
857 857
858 858 z = zlib.compressobj()
859 859 f = self.repo.changegroup(nodes, 'serve')
860 860 while 1:
861 861 chunk = f.read(4096)
862 862 if not chunk:
863 863 break
864 864 req.write(z.compress(chunk))
865 865
866 866 req.write(z.flush())
867 867
868 868 def do_archive(self, req):
869 869 changeset = self.repo.lookup(req.form['node'][0])
870 870 type_ = req.form['type'][0]
871 871 allowed = self.repo.ui.configlist("web", "allow_archive")
872 872 if (type_ in self.archives and (type_ in allowed or
873 873 self.repo.ui.configbool("web", "allow" + type_, False))):
874 874 self.archive(req, changeset, type_)
875 875 return
876 876
877 877 req.write(self.t("error"))
878 878
879 879 def do_static(self, req):
880 880 fname = req.form['file'][0]
881 881 static = self.repo.ui.config("web", "static",
882 882 os.path.join(self.templatepath,
883 883 "static"))
884 884 req.write(staticfile(static, fname, req)
885 885 or self.t("error", error="%r not found" % fname))
886 886
887 887 def do_capabilities(self, req):
888 888 caps = ['unbundle']
889 889 if self.repo.ui.configbool('server', 'uncompressed'):
890 890 caps.append('stream=%d' % self.repo.revlogversion)
891 891 resp = ' '.join(caps)
892 892 req.httphdr("application/mercurial-0.1", length=len(resp))
893 893 req.write(resp)
894 894
895 895 def check_perm(self, req, op, default):
896 896 '''check permission for operation based on user auth.
897 897 return true if op allowed, else false.
898 898 default is policy to use if no config given.'''
899 899
900 900 user = req.env.get('REMOTE_USER')
901 901
902 902 deny = self.repo.ui.configlist('web', 'deny_' + op)
903 903 if deny and (not user or deny == ['*'] or user in deny):
904 904 return False
905 905
906 906 allow = self.repo.ui.configlist('web', 'allow_' + op)
907 907 return (allow and (allow == ['*'] or user in allow)) or default
908 908
909 909 def do_unbundle(self, req):
910 910 def bail(response, headers={}):
911 911 length = int(req.env['CONTENT_LENGTH'])
912 912 for s in util.filechunkiter(req, limit=length):
913 913 # drain incoming bundle, else client will not see
914 914 # response when run outside cgi script
915 915 pass
916 916 req.httphdr("application/mercurial-0.1", headers=headers)
917 917 req.write('0\n')
918 918 req.write(response)
919 919
920 920 # require ssl by default, auth info cannot be sniffed and
921 921 # replayed
922 922 ssl_req = self.repo.ui.configbool('web', 'push_ssl', True)
923 923 if ssl_req:
924 924 if not req.env.get('HTTPS'):
925 925 bail(_('ssl required\n'))
926 926 return
927 927 proto = 'https'
928 928 else:
929 929 proto = 'http'
930 930
931 931 # do not allow push unless explicitly allowed
932 932 if not self.check_perm(req, 'push', False):
933 933 bail(_('push not authorized\n'),
934 934 headers={'status': '401 Unauthorized'})
935 935 return
936 936
937 937 req.httphdr("application/mercurial-0.1")
938 938
939 939 their_heads = req.form['heads'][0].split(' ')
940 940
941 941 def check_heads():
942 942 heads = map(hex, self.repo.heads())
943 943 return their_heads == [hex('force')] or their_heads == heads
944 944
945 945 # fail early if possible
946 946 if not check_heads():
947 947 bail(_('unsynced changes\n'))
948 948 return
949 949
950 950 # do not lock repo until all changegroup data is
951 951 # streamed. save to temporary file.
952 952
953 953 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
954 954 fp = os.fdopen(fd, 'wb+')
955 955 try:
956 956 length = int(req.env['CONTENT_LENGTH'])
957 957 for s in util.filechunkiter(req, limit=length):
958 958 fp.write(s)
959 959
960 960 lock = self.repo.lock()
961 961 try:
962 962 if not check_heads():
963 963 req.write('0\n')
964 964 req.write(_('unsynced changes\n'))
965 965 return
966 966
967 967 fp.seek(0)
968 968
969 969 # send addchangegroup output to client
970 970
971 971 old_stdout = sys.stdout
972 972 sys.stdout = cStringIO.StringIO()
973 973
974 974 try:
975 975 url = 'remote:%s:%s' % (proto,
976 976 req.env.get('REMOTE_HOST', ''))
977 977 ret = self.repo.addchangegroup(fp, 'serve', url)
978 978 finally:
979 979 val = sys.stdout.getvalue()
980 980 sys.stdout = old_stdout
981 981 req.write('%d\n' % ret)
982 982 req.write(val)
983 983 finally:
984 984 lock.release()
985 985 finally:
986 986 fp.close()
987 987 os.unlink(tempname)
988 988
989 989 def do_stream_out(self, req):
990 990 req.httphdr("application/mercurial-0.1")
991 991 streamclone.stream_out(self.repo, req)
@@ -1,1758 +1,1757 b''
1 1 # localrepo.py - read/write repository class for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import *
9 9 from i18n import gettext as _
10 10 from demandload import *
11 11 import repo
12 12 demandload(globals(), "appendfile changegroup")
13 13 demandload(globals(), "changelog dirstate filelog manifest context")
14 14 demandload(globals(), "re lock transaction tempfile stat mdiff errno ui")
15 15 demandload(globals(), "os revlog time util")
16 16
17 17 class localrepository(repo.repository):
18 18 capabilities = ()
19 19
20 20 def __del__(self):
21 21 self.transhandle = None
22 22 def __init__(self, parentui, path=None, create=0):
23 23 repo.repository.__init__(self)
24 24 if not path:
25 25 p = os.getcwd()
26 26 while not os.path.isdir(os.path.join(p, ".hg")):
27 27 oldp = p
28 28 p = os.path.dirname(p)
29 29 if p == oldp:
30 30 raise repo.RepoError(_("no repo found"))
31 31 path = p
32 32 self.path = os.path.join(path, ".hg")
33 33
34 34 if not create and not os.path.isdir(self.path):
35 35 raise repo.RepoError(_("repository %s not found") % path)
36 36
37 37 self.root = os.path.abspath(path)
38 38 self.origroot = path
39 39 self.ui = ui.ui(parentui=parentui)
40 40 self.opener = util.opener(self.path)
41 41 self.wopener = util.opener(self.root)
42 42
43 43 try:
44 44 self.ui.readconfig(self.join("hgrc"), self.root)
45 45 except IOError:
46 46 pass
47 47
48 48 v = self.ui.revlogopts
49 49 self.revlogversion = int(v.get('format', revlog.REVLOG_DEFAULT_FORMAT))
50 50 self.revlogv1 = self.revlogversion != revlog.REVLOGV0
51 51 fl = v.get('flags', None)
52 52 flags = 0
53 53 if fl != None:
54 54 for x in fl.split():
55 55 flags |= revlog.flagstr(x)
56 56 elif self.revlogv1:
57 57 flags = revlog.REVLOG_DEFAULT_FLAGS
58 58
59 59 v = self.revlogversion | flags
60 60 self.manifest = manifest.manifest(self.opener, v)
61 61 self.changelog = changelog.changelog(self.opener, v)
62 62
63 63 # the changelog might not have the inline index flag
64 64 # on. If the format of the changelog is the same as found in
65 65 # .hgrc, apply any flags found in the .hgrc as well.
66 66 # Otherwise, just version from the changelog
67 67 v = self.changelog.version
68 68 if v == self.revlogversion:
69 69 v |= flags
70 70 self.revlogversion = v
71 71
72 72 self.tagscache = None
73 73 self.nodetagscache = None
74 74 self.encodepats = None
75 75 self.decodepats = None
76 76 self.transhandle = None
77 77
78 78 if create:
79 79 if not os.path.exists(path):
80 80 os.mkdir(path)
81 81 os.mkdir(self.path)
82 82 os.mkdir(self.join("data"))
83 83
84 84 self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
85 85
86 86 def url(self):
87 87 return 'file:' + self.root
88 88
89 89 def hook(self, name, throw=False, **args):
90 90 def callhook(hname, funcname):
91 91 '''call python hook. hook is callable object, looked up as
92 92 name in python module. if callable returns "true", hook
93 93 fails, else passes. if hook raises exception, treated as
94 94 hook failure. exception propagates if throw is "true".
95 95
96 96 reason for "true" meaning "hook failed" is so that
97 97 unmodified commands (e.g. mercurial.commands.update) can
98 98 be run as hooks without wrappers to convert return values.'''
99 99
100 100 self.ui.note(_("calling hook %s: %s\n") % (hname, funcname))
101 101 d = funcname.rfind('.')
102 102 if d == -1:
103 103 raise util.Abort(_('%s hook is invalid ("%s" not in a module)')
104 104 % (hname, funcname))
105 105 modname = funcname[:d]
106 106 try:
107 107 obj = __import__(modname)
108 108 except ImportError:
109 109 try:
110 110 # extensions are loaded with hgext_ prefix
111 111 obj = __import__("hgext_%s" % modname)
112 112 except ImportError:
113 113 raise util.Abort(_('%s hook is invalid '
114 114 '(import of "%s" failed)') %
115 115 (hname, modname))
116 116 try:
117 117 for p in funcname.split('.')[1:]:
118 118 obj = getattr(obj, p)
119 119 except AttributeError, err:
120 120 raise util.Abort(_('%s hook is invalid '
121 121 '("%s" is not defined)') %
122 122 (hname, funcname))
123 123 if not callable(obj):
124 124 raise util.Abort(_('%s hook is invalid '
125 125 '("%s" is not callable)') %
126 126 (hname, funcname))
127 127 try:
128 128 r = obj(ui=self.ui, repo=self, hooktype=name, **args)
129 129 except (KeyboardInterrupt, util.SignalInterrupt):
130 130 raise
131 131 except Exception, exc:
132 132 if isinstance(exc, util.Abort):
133 133 self.ui.warn(_('error: %s hook failed: %s\n') %
134 134 (hname, exc.args[0] % exc.args[1:]))
135 135 else:
136 136 self.ui.warn(_('error: %s hook raised an exception: '
137 137 '%s\n') % (hname, exc))
138 138 if throw:
139 139 raise
140 140 self.ui.print_exc()
141 141 return True
142 142 if r:
143 143 if throw:
144 144 raise util.Abort(_('%s hook failed') % hname)
145 145 self.ui.warn(_('warning: %s hook failed\n') % hname)
146 146 return r
147 147
148 148 def runhook(name, cmd):
149 149 self.ui.note(_("running hook %s: %s\n") % (name, cmd))
150 150 env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()])
151 151 r = util.system(cmd, environ=env, cwd=self.root)
152 152 if r:
153 153 desc, r = util.explain_exit(r)
154 154 if throw:
155 155 raise util.Abort(_('%s hook %s') % (name, desc))
156 156 self.ui.warn(_('warning: %s hook %s\n') % (name, desc))
157 157 return r
158 158
159 159 r = False
160 160 hooks = [(hname, cmd) for hname, cmd in self.ui.configitems("hooks")
161 161 if hname.split(".", 1)[0] == name and cmd]
162 162 hooks.sort()
163 163 for hname, cmd in hooks:
164 164 if cmd.startswith('python:'):
165 165 r = callhook(hname, cmd[7:].strip()) or r
166 166 else:
167 167 r = runhook(hname, cmd) or r
168 168 return r
169 169
170 170 tag_disallowed = ':\r\n'
171 171
172 172 def tag(self, name, node, local=False, message=None, user=None, date=None):
173 173 '''tag a revision with a symbolic name.
174 174
175 175 if local is True, the tag is stored in a per-repository file.
176 176 otherwise, it is stored in the .hgtags file, and a new
177 177 changeset is committed with the change.
178 178
179 179 keyword arguments:
180 180
181 181 local: whether to store tag in non-version-controlled file
182 182 (default False)
183 183
184 184 message: commit message to use if committing
185 185
186 186 user: name of user to use if committing
187 187
188 188 date: date tuple to use if committing'''
189 189
190 190 for c in self.tag_disallowed:
191 191 if c in name:
192 192 raise util.Abort(_('%r cannot be used in a tag name') % c)
193 193
194 194 self.hook('pretag', throw=True, node=node, tag=name, local=local)
195 195
196 196 if local:
197 197 self.opener('localtags', 'a').write('%s %s\n' % (node, name))
198 198 self.hook('tag', node=node, tag=name, local=local)
199 199 return
200 200
201 201 for x in self.changes():
202 202 if '.hgtags' in x:
203 203 raise util.Abort(_('working copy of .hgtags is changed '
204 204 '(please commit .hgtags manually)'))
205 205
206 206 self.wfile('.hgtags', 'ab').write('%s %s\n' % (node, name))
207 207 if self.dirstate.state('.hgtags') == '?':
208 208 self.add(['.hgtags'])
209 209
210 210 if not message:
211 211 message = _('Added tag %s for changeset %s') % (name, node)
212 212
213 213 self.commit(['.hgtags'], message, user, date)
214 214 self.hook('tag', node=node, tag=name, local=local)
215 215
216 216 def tags(self):
217 217 '''return a mapping of tag to node'''
218 218 if not self.tagscache:
219 219 self.tagscache = {}
220 220
221 221 def parsetag(line, context):
222 222 if not line:
223 223 return
224 224 s = l.split(" ", 1)
225 225 if len(s) != 2:
226 226 self.ui.warn(_("%s: cannot parse entry\n") % context)
227 227 return
228 228 node, key = s
229 229 key = key.strip()
230 230 try:
231 231 bin_n = bin(node)
232 232 except TypeError:
233 233 self.ui.warn(_("%s: node '%s' is not well formed\n") %
234 234 (context, node))
235 235 return
236 236 if bin_n not in self.changelog.nodemap:
237 237 self.ui.warn(_("%s: tag '%s' refers to unknown node\n") %
238 238 (context, key))
239 239 return
240 240 self.tagscache[key] = bin_n
241 241
242 242 # read the tags file from each head, ending with the tip,
243 243 # and add each tag found to the map, with "newer" ones
244 244 # taking precedence
245 245 heads = self.heads()
246 246 heads.reverse()
247 247 fl = self.file(".hgtags")
248 248 for node in heads:
249 249 change = self.changelog.read(node)
250 250 rev = self.changelog.rev(node)
251 251 fn, ff = self.manifest.find(change[0], '.hgtags')
252 252 if fn is None: continue
253 253 count = 0
254 254 for l in fl.read(fn).splitlines():
255 255 count += 1
256 256 parsetag(l, _(".hgtags (rev %d:%s), line %d") %
257 257 (rev, short(node), count))
258 258 try:
259 259 f = self.opener("localtags")
260 260 count = 0
261 261 for l in f:
262 262 count += 1
263 263 parsetag(l, _("localtags, line %d") % count)
264 264 except IOError:
265 265 pass
266 266
267 267 self.tagscache['tip'] = self.changelog.tip()
268 268
269 269 return self.tagscache
270 270
271 271 def tagslist(self):
272 272 '''return a list of tags ordered by revision'''
273 273 l = []
274 274 for t, n in self.tags().items():
275 275 try:
276 276 r = self.changelog.rev(n)
277 277 except:
278 278 r = -2 # sort to the beginning of the list if unknown
279 279 l.append((r, t, n))
280 280 l.sort()
281 281 return [(t, n) for r, t, n in l]
282 282
283 283 def nodetags(self, node):
284 284 '''return the tags associated with a node'''
285 285 if not self.nodetagscache:
286 286 self.nodetagscache = {}
287 287 for t, n in self.tags().items():
288 288 self.nodetagscache.setdefault(n, []).append(t)
289 289 return self.nodetagscache.get(node, [])
290 290
291 291 def lookup(self, key):
292 292 try:
293 293 return self.tags()[key]
294 294 except KeyError:
295 295 if key == '.':
296 296 key = self.dirstate.parents()[0]
297 297 if key == nullid:
298 298 raise repo.RepoError(_("no revision checked out"))
299 299 try:
300 300 return self.changelog.lookup(key)
301 301 except:
302 302 raise repo.RepoError(_("unknown revision '%s'") % key)
303 303
304 304 def dev(self):
305 305 return os.lstat(self.path).st_dev
306 306
307 307 def local(self):
308 308 return True
309 309
310 310 def join(self, f):
311 311 return os.path.join(self.path, f)
312 312
313 313 def wjoin(self, f):
314 314 return os.path.join(self.root, f)
315 315
316 316 def file(self, f):
317 317 if f[0] == '/':
318 318 f = f[1:]
319 319 return filelog.filelog(self.opener, f, self.revlogversion)
320 320
321 321 def changectx(self, changeid):
322 322 return context.changectx(self, changeid)
323 323
324 324 def filectx(self, path, changeid=None, fileid=None):
325 325 """changeid can be a changeset revision, node, or tag.
326 326 fileid can be a file revision or node."""
327 327 return context.filectx(self, path, changeid, fileid)
328 328
329 329 def getcwd(self):
330 330 return self.dirstate.getcwd()
331 331
332 332 def wfile(self, f, mode='r'):
333 333 return self.wopener(f, mode)
334 334
335 335 def wread(self, filename):
336 336 if self.encodepats == None:
337 337 l = []
338 338 for pat, cmd in self.ui.configitems("encode"):
339 339 mf = util.matcher(self.root, "", [pat], [], [])[1]
340 340 l.append((mf, cmd))
341 341 self.encodepats = l
342 342
343 343 data = self.wopener(filename, 'r').read()
344 344
345 345 for mf, cmd in self.encodepats:
346 346 if mf(filename):
347 347 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
348 348 data = util.filter(data, cmd)
349 349 break
350 350
351 351 return data
352 352
353 353 def wwrite(self, filename, data, fd=None):
354 354 if self.decodepats == None:
355 355 l = []
356 356 for pat, cmd in self.ui.configitems("decode"):
357 357 mf = util.matcher(self.root, "", [pat], [], [])[1]
358 358 l.append((mf, cmd))
359 359 self.decodepats = l
360 360
361 361 for mf, cmd in self.decodepats:
362 362 if mf(filename):
363 363 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
364 364 data = util.filter(data, cmd)
365 365 break
366 366
367 367 if fd:
368 368 return fd.write(data)
369 369 return self.wopener(filename, 'w').write(data)
370 370
371 371 def transaction(self):
372 372 tr = self.transhandle
373 373 if tr != None and tr.running():
374 374 return tr.nest()
375 375
376 376 # save dirstate for rollback
377 377 try:
378 378 ds = self.opener("dirstate").read()
379 379 except IOError:
380 380 ds = ""
381 381 self.opener("journal.dirstate", "w").write(ds)
382 382
383 383 tr = transaction.transaction(self.ui.warn, self.opener,
384 384 self.join("journal"),
385 385 aftertrans(self.path))
386 386 self.transhandle = tr
387 387 return tr
388 388
389 389 def recover(self):
390 390 l = self.lock()
391 391 if os.path.exists(self.join("journal")):
392 392 self.ui.status(_("rolling back interrupted transaction\n"))
393 393 transaction.rollback(self.opener, self.join("journal"))
394 394 self.reload()
395 395 return True
396 396 else:
397 397 self.ui.warn(_("no interrupted transaction available\n"))
398 398 return False
399 399
400 400 def rollback(self, wlock=None):
401 401 if not wlock:
402 402 wlock = self.wlock()
403 403 l = self.lock()
404 404 if os.path.exists(self.join("undo")):
405 405 self.ui.status(_("rolling back last transaction\n"))
406 406 transaction.rollback(self.opener, self.join("undo"))
407 407 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
408 408 self.reload()
409 409 self.wreload()
410 410 else:
411 411 self.ui.warn(_("no rollback information available\n"))
412 412
413 413 def wreload(self):
414 414 self.dirstate.read()
415 415
416 416 def reload(self):
417 417 self.changelog.load()
418 418 self.manifest.load()
419 419 self.tagscache = None
420 420 self.nodetagscache = None
421 421
422 422 def do_lock(self, lockname, wait, releasefn=None, acquirefn=None,
423 423 desc=None):
424 424 try:
425 425 l = lock.lock(self.join(lockname), 0, releasefn, desc=desc)
426 426 except lock.LockHeld, inst:
427 427 if not wait:
428 428 raise
429 429 self.ui.warn(_("waiting for lock on %s held by %s\n") %
430 430 (desc, inst.args[0]))
431 431 # default to 600 seconds timeout
432 432 l = lock.lock(self.join(lockname),
433 433 int(self.ui.config("ui", "timeout") or 600),
434 434 releasefn, desc=desc)
435 435 if acquirefn:
436 436 acquirefn()
437 437 return l
438 438
439 439 def lock(self, wait=1):
440 440 return self.do_lock("lock", wait, acquirefn=self.reload,
441 441 desc=_('repository %s') % self.origroot)
442 442
443 443 def wlock(self, wait=1):
444 444 return self.do_lock("wlock", wait, self.dirstate.write,
445 445 self.wreload,
446 446 desc=_('working directory of %s') % self.origroot)
447 447
448 448 def checkfilemerge(self, filename, text, filelog, manifest1, manifest2):
449 449 "determine whether a new filenode is needed"
450 450 fp1 = manifest1.get(filename, nullid)
451 451 fp2 = manifest2.get(filename, nullid)
452 452
453 453 if fp2 != nullid:
454 454 # is one parent an ancestor of the other?
455 455 fpa = filelog.ancestor(fp1, fp2)
456 456 if fpa == fp1:
457 457 fp1, fp2 = fp2, nullid
458 458 elif fpa == fp2:
459 459 fp2 = nullid
460 460
461 461 # is the file unmodified from the parent? report existing entry
462 462 if fp2 == nullid and text == filelog.read(fp1):
463 463 return (fp1, None, None)
464 464
465 465 return (None, fp1, fp2)
466 466
467 467 def rawcommit(self, files, text, user, date, p1=None, p2=None, wlock=None):
468 468 orig_parent = self.dirstate.parents()[0] or nullid
469 469 p1 = p1 or self.dirstate.parents()[0] or nullid
470 470 p2 = p2 or self.dirstate.parents()[1] or nullid
471 471 c1 = self.changelog.read(p1)
472 472 c2 = self.changelog.read(p2)
473 473 m1 = self.manifest.read(c1[0]).copy()
474 474 m2 = self.manifest.read(c2[0])
475 475 changed = []
476 476
477 477 if orig_parent == p1:
478 478 update_dirstate = 1
479 479 else:
480 480 update_dirstate = 0
481 481
482 482 if not wlock:
483 483 wlock = self.wlock()
484 484 l = self.lock()
485 485 tr = self.transaction()
486 486 linkrev = self.changelog.count()
487 487 for f in files:
488 488 try:
489 489 t = self.wread(f)
490 490 m1.set(f, util.is_exec(self.wjoin(f), m1.execf(f)))
491 491 r = self.file(f)
492 492
493 493 (entry, fp1, fp2) = self.checkfilemerge(f, t, r, m1, m2)
494 494 if entry:
495 495 m1[f] = entry
496 496 continue
497 497
498 498 m1[f] = r.add(t, {}, tr, linkrev, fp1, fp2)
499 499 changed.append(f)
500 500 if update_dirstate:
501 501 self.dirstate.update([f], "n")
502 502 except IOError:
503 503 try:
504 504 del m1[f]
505 del m1[f]
506 505 if update_dirstate:
507 506 self.dirstate.forget([f])
508 507 except:
509 508 # deleted from p2?
510 509 pass
511 510
512 511 mnode = self.manifest.add(m1, tr, linkrev, c1[0], c2[0])
513 512 user = user or self.ui.username()
514 513 n = self.changelog.add(mnode, changed, text, tr, p1, p2, user, date)
515 514 tr.close()
516 515 if update_dirstate:
517 516 self.dirstate.setparents(n, nullid)
518 517
519 518 def commit(self, files=None, text="", user=None, date=None,
520 519 match=util.always, force=False, lock=None, wlock=None,
521 520 force_editor=False):
522 521 commit = []
523 522 remove = []
524 523 changed = []
525 524
526 525 if files:
527 526 for f in files:
528 527 s = self.dirstate.state(f)
529 528 if s in 'nmai':
530 529 commit.append(f)
531 530 elif s == 'r':
532 531 remove.append(f)
533 532 else:
534 533 self.ui.warn(_("%s not tracked!\n") % f)
535 534 else:
536 535 modified, added, removed, deleted, unknown = self.changes(match=match)
537 536 commit = modified + added
538 537 remove = removed
539 538
540 539 p1, p2 = self.dirstate.parents()
541 540 c1 = self.changelog.read(p1)
542 541 c2 = self.changelog.read(p2)
543 542 m1 = self.manifest.read(c1[0]).copy()
544 543 m2 = self.manifest.read(c2[0])
545 544
546 545 if not commit and not remove and not force and p2 == nullid:
547 546 self.ui.status(_("nothing changed\n"))
548 547 return None
549 548
550 549 xp1 = hex(p1)
551 550 if p2 == nullid: xp2 = ''
552 551 else: xp2 = hex(p2)
553 552
554 553 self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
555 554
556 555 if not wlock:
557 556 wlock = self.wlock()
558 557 if not lock:
559 558 lock = self.lock()
560 559 tr = self.transaction()
561 560
562 561 # check in files
563 562 new = {}
564 563 linkrev = self.changelog.count()
565 564 commit.sort()
566 565 for f in commit:
567 566 self.ui.note(f + "\n")
568 567 try:
569 568 m1.set(f, util.is_exec(self.wjoin(f), m1.execf(f)))
570 569 t = self.wread(f)
571 570 except IOError:
572 571 self.ui.warn(_("trouble committing %s!\n") % f)
573 572 raise
574 573
575 574 r = self.file(f)
576 575
577 576 meta = {}
578 577 cp = self.dirstate.copied(f)
579 578 if cp:
580 579 meta["copy"] = cp
581 580 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
582 581 self.ui.debug(_(" %s: copy %s:%s\n") % (f, cp, meta["copyrev"]))
583 582 fp1, fp2 = nullid, nullid
584 583 else:
585 584 entry, fp1, fp2 = self.checkfilemerge(f, t, r, m1, m2)
586 585 if entry:
587 586 new[f] = entry
588 587 continue
589 588
590 589 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
591 590 # remember what we've added so that we can later calculate
592 591 # the files to pull from a set of changesets
593 592 changed.append(f)
594 593
595 594 # update manifest
596 595 m1.update(new)
597 596 for f in remove:
598 597 if f in m1:
599 598 del m1[f]
600 599 mn = self.manifest.add(m1, tr, linkrev, c1[0], c2[0],
601 600 (new, remove))
602 601
603 602 # add changeset
604 603 new = new.keys()
605 604 new.sort()
606 605
607 606 user = user or self.ui.username()
608 607 if not text or force_editor:
609 608 edittext = []
610 609 if text:
611 610 edittext.append(text)
612 611 edittext.append("")
613 612 if p2 != nullid:
614 613 edittext.append("HG: branch merge")
615 614 edittext.extend(["HG: changed %s" % f for f in changed])
616 615 edittext.extend(["HG: removed %s" % f for f in remove])
617 616 if not changed and not remove:
618 617 edittext.append("HG: no files changed")
619 618 edittext.append("")
620 619 # run editor in the repository root
621 620 olddir = os.getcwd()
622 621 os.chdir(self.root)
623 622 text = self.ui.edit("\n".join(edittext), user)
624 623 os.chdir(olddir)
625 624
626 625 lines = [line.rstrip() for line in text.rstrip().splitlines()]
627 626 while lines and not lines[0]:
628 627 del lines[0]
629 628 if not lines:
630 629 return None
631 630 text = '\n'.join(lines)
632 631 n = self.changelog.add(mn, changed + remove, text, tr, p1, p2, user, date)
633 632 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
634 633 parent2=xp2)
635 634 tr.close()
636 635
637 636 self.dirstate.setparents(n)
638 637 self.dirstate.update(new, "n")
639 638 self.dirstate.forget(remove)
640 639
641 640 self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
642 641 return n
643 642
644 643 def walk(self, node=None, files=[], match=util.always, badmatch=None):
645 644 if node:
646 645 fdict = dict.fromkeys(files)
647 646 for fn in self.manifest.read(self.changelog.read(node)[0]):
648 647 fdict.pop(fn, None)
649 648 if match(fn):
650 649 yield 'm', fn
651 650 for fn in fdict:
652 651 if badmatch and badmatch(fn):
653 652 if match(fn):
654 653 yield 'b', fn
655 654 else:
656 655 self.ui.warn(_('%s: No such file in rev %s\n') % (
657 656 util.pathto(self.getcwd(), fn), short(node)))
658 657 else:
659 658 for src, fn in self.dirstate.walk(files, match, badmatch=badmatch):
660 659 yield src, fn
661 660
662 661 def status(self, node1=None, node2=None, files=[], match=util.always,
663 662 wlock=None, list_ignored=False, list_clean=False):
664 663 """return status of files between two nodes or node and working directory
665 664
666 665 If node1 is None, use the first dirstate parent instead.
667 666 If node2 is None, compare node1 with working directory.
668 667 """
669 668
670 669 def fcmp(fn, mf):
671 670 t1 = self.wread(fn)
672 671 t2 = self.file(fn).read(mf.get(fn, nullid))
673 672 return cmp(t1, t2)
674 673
675 674 def mfmatches(node):
676 675 change = self.changelog.read(node)
677 676 mf = dict(self.manifest.read(change[0]))
678 677 for fn in mf.keys():
679 678 if not match(fn):
680 679 del mf[fn]
681 680 return mf
682 681
683 682 modified, added, removed, deleted, unknown = [], [], [], [], []
684 683 ignored, clean = [], []
685 684
686 685 compareworking = False
687 686 if not node1 or (not node2 and node1 == self.dirstate.parents()[0]):
688 687 compareworking = True
689 688
690 689 if not compareworking:
691 690 # read the manifest from node1 before the manifest from node2,
692 691 # so that we'll hit the manifest cache if we're going through
693 692 # all the revisions in parent->child order.
694 693 mf1 = mfmatches(node1)
695 694
696 695 # are we comparing the working directory?
697 696 if not node2:
698 697 if not wlock:
699 698 try:
700 699 wlock = self.wlock(wait=0)
701 700 except lock.LockException:
702 701 wlock = None
703 702 (lookup, modified, added, removed, deleted, unknown,
704 703 ignored, clean) = self.dirstate.status(files, match,
705 704 list_ignored, list_clean)
706 705
707 706 # are we comparing working dir against its parent?
708 707 if compareworking:
709 708 if lookup:
710 709 # do a full compare of any files that might have changed
711 710 mf2 = mfmatches(self.dirstate.parents()[0])
712 711 for f in lookup:
713 712 if fcmp(f, mf2):
714 713 modified.append(f)
715 714 elif wlock is not None:
716 715 self.dirstate.update([f], "n")
717 716 else:
718 717 # we are comparing working dir against non-parent
719 718 # generate a pseudo-manifest for the working dir
720 719 mf2 = mfmatches(self.dirstate.parents()[0])
721 720 for f in lookup + modified + added:
722 721 mf2[f] = ""
723 722 for f in removed:
724 723 if f in mf2:
725 724 del mf2[f]
726 725 else:
727 726 # we are comparing two revisions
728 727 mf2 = mfmatches(node2)
729 728
730 729 if not compareworking:
731 730 # flush lists from dirstate before comparing manifests
732 731 modified, added, clean = [], [], []
733 732
734 733 # make sure to sort the files so we talk to the disk in a
735 734 # reasonable order
736 735 mf2keys = mf2.keys()
737 736 mf2keys.sort()
738 737 for fn in mf2keys:
739 738 if mf1.has_key(fn):
740 739 if mf1[fn] != mf2[fn] and (mf2[fn] != "" or fcmp(fn, mf1)):
741 740 modified.append(fn)
742 741 elif list_clean:
743 742 clean.append(fn)
744 743 del mf1[fn]
745 744 else:
746 745 added.append(fn)
747 746
748 747 removed = mf1.keys()
749 748
750 749 # sort and return results:
751 750 for l in modified, added, removed, deleted, unknown, ignored, clean:
752 751 l.sort()
753 752 return (modified, added, removed, deleted, unknown, ignored, clean)
754 753
755 754 def changes(self, node1=None, node2=None, files=[], match=util.always,
756 755 wlock=None, list_ignored=False, list_clean=False):
757 756 '''DEPRECATED - use status instead'''
758 757 marduit = self.status(node1, node2, files, match, wlock,
759 758 list_ignored, list_clean)
760 759 if list_ignored:
761 760 return marduit[:-1]
762 761 else:
763 762 return marduit[:-2]
764 763
765 764 def add(self, list, wlock=None):
766 765 if not wlock:
767 766 wlock = self.wlock()
768 767 for f in list:
769 768 p = self.wjoin(f)
770 769 if not os.path.exists(p):
771 770 self.ui.warn(_("%s does not exist!\n") % f)
772 771 elif not os.path.isfile(p):
773 772 self.ui.warn(_("%s not added: only files supported currently\n")
774 773 % f)
775 774 elif self.dirstate.state(f) in 'an':
776 775 self.ui.warn(_("%s already tracked!\n") % f)
777 776 else:
778 777 self.dirstate.update([f], "a")
779 778
780 779 def forget(self, list, wlock=None):
781 780 if not wlock:
782 781 wlock = self.wlock()
783 782 for f in list:
784 783 if self.dirstate.state(f) not in 'ai':
785 784 self.ui.warn(_("%s not added!\n") % f)
786 785 else:
787 786 self.dirstate.forget([f])
788 787
789 788 def remove(self, list, unlink=False, wlock=None):
790 789 if unlink:
791 790 for f in list:
792 791 try:
793 792 util.unlink(self.wjoin(f))
794 793 except OSError, inst:
795 794 if inst.errno != errno.ENOENT:
796 795 raise
797 796 if not wlock:
798 797 wlock = self.wlock()
799 798 for f in list:
800 799 p = self.wjoin(f)
801 800 if os.path.exists(p):
802 801 self.ui.warn(_("%s still exists!\n") % f)
803 802 elif self.dirstate.state(f) == 'a':
804 803 self.dirstate.forget([f])
805 804 elif f not in self.dirstate:
806 805 self.ui.warn(_("%s not tracked!\n") % f)
807 806 else:
808 807 self.dirstate.update([f], "r")
809 808
810 809 def undelete(self, list, wlock=None):
811 810 p = self.dirstate.parents()[0]
812 811 mn = self.changelog.read(p)[0]
813 812 m = self.manifest.read(mn)
814 813 if not wlock:
815 814 wlock = self.wlock()
816 815 for f in list:
817 816 if self.dirstate.state(f) not in "r":
818 817 self.ui.warn("%s not removed!\n" % f)
819 818 else:
820 819 t = self.file(f).read(m[f])
821 820 self.wwrite(f, t)
822 821 util.set_exec(self.wjoin(f), m.execf(f))
823 822 self.dirstate.update([f], "n")
824 823
825 824 def copy(self, source, dest, wlock=None):
826 825 p = self.wjoin(dest)
827 826 if not os.path.exists(p):
828 827 self.ui.warn(_("%s does not exist!\n") % dest)
829 828 elif not os.path.isfile(p):
830 829 self.ui.warn(_("copy failed: %s is not a file\n") % dest)
831 830 else:
832 831 if not wlock:
833 832 wlock = self.wlock()
834 833 if self.dirstate.state(dest) == '?':
835 834 self.dirstate.update([dest], "a")
836 835 self.dirstate.copy(source, dest)
837 836
838 837 def heads(self, start=None):
839 838 heads = self.changelog.heads(start)
840 839 # sort the output in rev descending order
841 840 heads = [(-self.changelog.rev(h), h) for h in heads]
842 841 heads.sort()
843 842 return [n for (r, n) in heads]
844 843
845 844 # branchlookup returns a dict giving a list of branches for
846 845 # each head. A branch is defined as the tag of a node or
847 846 # the branch of the node's parents. If a node has multiple
848 847 # branch tags, tags are eliminated if they are visible from other
849 848 # branch tags.
850 849 #
851 850 # So, for this graph: a->b->c->d->e
852 851 # \ /
853 852 # aa -----/
854 853 # a has tag 2.6.12
855 854 # d has tag 2.6.13
856 855 # e would have branch tags for 2.6.12 and 2.6.13. Because the node
857 856 # for 2.6.12 can be reached from the node 2.6.13, that is eliminated
858 857 # from the list.
859 858 #
860 859 # It is possible that more than one head will have the same branch tag.
861 860 # callers need to check the result for multiple heads under the same
862 861 # branch tag if that is a problem for them (ie checkout of a specific
863 862 # branch).
864 863 #
865 864 # passing in a specific branch will limit the depth of the search
866 865 # through the parents. It won't limit the branches returned in the
867 866 # result though.
868 867 def branchlookup(self, heads=None, branch=None):
869 868 if not heads:
870 869 heads = self.heads()
871 870 headt = [ h for h in heads ]
872 871 chlog = self.changelog
873 872 branches = {}
874 873 merges = []
875 874 seenmerge = {}
876 875
877 876 # traverse the tree once for each head, recording in the branches
878 877 # dict which tags are visible from this head. The branches
879 878 # dict also records which tags are visible from each tag
880 879 # while we traverse.
881 880 while headt or merges:
882 881 if merges:
883 882 n, found = merges.pop()
884 883 visit = [n]
885 884 else:
886 885 h = headt.pop()
887 886 visit = [h]
888 887 found = [h]
889 888 seen = {}
890 889 while visit:
891 890 n = visit.pop()
892 891 if n in seen:
893 892 continue
894 893 pp = chlog.parents(n)
895 894 tags = self.nodetags(n)
896 895 if tags:
897 896 for x in tags:
898 897 if x == 'tip':
899 898 continue
900 899 for f in found:
901 900 branches.setdefault(f, {})[n] = 1
902 901 branches.setdefault(n, {})[n] = 1
903 902 break
904 903 if n not in found:
905 904 found.append(n)
906 905 if branch in tags:
907 906 continue
908 907 seen[n] = 1
909 908 if pp[1] != nullid and n not in seenmerge:
910 909 merges.append((pp[1], [x for x in found]))
911 910 seenmerge[n] = 1
912 911 if pp[0] != nullid:
913 912 visit.append(pp[0])
914 913 # traverse the branches dict, eliminating branch tags from each
915 914 # head that are visible from another branch tag for that head.
916 915 out = {}
917 916 viscache = {}
918 917 for h in heads:
919 918 def visible(node):
920 919 if node in viscache:
921 920 return viscache[node]
922 921 ret = {}
923 922 visit = [node]
924 923 while visit:
925 924 x = visit.pop()
926 925 if x in viscache:
927 926 ret.update(viscache[x])
928 927 elif x not in ret:
929 928 ret[x] = 1
930 929 if x in branches:
931 930 visit[len(visit):] = branches[x].keys()
932 931 viscache[node] = ret
933 932 return ret
934 933 if h not in branches:
935 934 continue
936 935 # O(n^2), but somewhat limited. This only searches the
937 936 # tags visible from a specific head, not all the tags in the
938 937 # whole repo.
939 938 for b in branches[h]:
940 939 vis = False
941 940 for bb in branches[h].keys():
942 941 if b != bb:
943 942 if b in visible(bb):
944 943 vis = True
945 944 break
946 945 if not vis:
947 946 l = out.setdefault(h, [])
948 947 l[len(l):] = self.nodetags(b)
949 948 return out
950 949
951 950 def branches(self, nodes):
952 951 if not nodes:
953 952 nodes = [self.changelog.tip()]
954 953 b = []
955 954 for n in nodes:
956 955 t = n
957 956 while 1:
958 957 p = self.changelog.parents(n)
959 958 if p[1] != nullid or p[0] == nullid:
960 959 b.append((t, n, p[0], p[1]))
961 960 break
962 961 n = p[0]
963 962 return b
964 963
965 964 def between(self, pairs):
966 965 r = []
967 966
968 967 for top, bottom in pairs:
969 968 n, l, i = top, [], 0
970 969 f = 1
971 970
972 971 while n != bottom:
973 972 p = self.changelog.parents(n)[0]
974 973 if i == f:
975 974 l.append(n)
976 975 f = f * 2
977 976 n = p
978 977 i += 1
979 978
980 979 r.append(l)
981 980
982 981 return r
983 982
984 983 def findincoming(self, remote, base=None, heads=None, force=False):
985 984 """Return list of roots of the subsets of missing nodes from remote
986 985
987 986 If base dict is specified, assume that these nodes and their parents
988 987 exist on the remote side and that no child of a node of base exists
989 988 in both remote and self.
990 989 Furthermore base will be updated to include the nodes that exists
991 990 in self and remote but no children exists in self and remote.
992 991 If a list of heads is specified, return only nodes which are heads
993 992 or ancestors of these heads.
994 993
995 994 All the ancestors of base are in self and in remote.
996 995 All the descendants of the list returned are missing in self.
997 996 (and so we know that the rest of the nodes are missing in remote, see
998 997 outgoing)
999 998 """
1000 999 m = self.changelog.nodemap
1001 1000 search = []
1002 1001 fetch = {}
1003 1002 seen = {}
1004 1003 seenbranch = {}
1005 1004 if base == None:
1006 1005 base = {}
1007 1006
1008 1007 if not heads:
1009 1008 heads = remote.heads()
1010 1009
1011 1010 if self.changelog.tip() == nullid:
1012 1011 base[nullid] = 1
1013 1012 if heads != [nullid]:
1014 1013 return [nullid]
1015 1014 return []
1016 1015
1017 1016 # assume we're closer to the tip than the root
1018 1017 # and start by examining the heads
1019 1018 self.ui.status(_("searching for changes\n"))
1020 1019
1021 1020 unknown = []
1022 1021 for h in heads:
1023 1022 if h not in m:
1024 1023 unknown.append(h)
1025 1024 else:
1026 1025 base[h] = 1
1027 1026
1028 1027 if not unknown:
1029 1028 return []
1030 1029
1031 1030 req = dict.fromkeys(unknown)
1032 1031 reqcnt = 0
1033 1032
1034 1033 # search through remote branches
1035 1034 # a 'branch' here is a linear segment of history, with four parts:
1036 1035 # head, root, first parent, second parent
1037 1036 # (a branch always has two parents (or none) by definition)
1038 1037 unknown = remote.branches(unknown)
1039 1038 while unknown:
1040 1039 r = []
1041 1040 while unknown:
1042 1041 n = unknown.pop(0)
1043 1042 if n[0] in seen:
1044 1043 continue
1045 1044
1046 1045 self.ui.debug(_("examining %s:%s\n")
1047 1046 % (short(n[0]), short(n[1])))
1048 1047 if n[0] == nullid: # found the end of the branch
1049 1048 pass
1050 1049 elif n in seenbranch:
1051 1050 self.ui.debug(_("branch already found\n"))
1052 1051 continue
1053 1052 elif n[1] and n[1] in m: # do we know the base?
1054 1053 self.ui.debug(_("found incomplete branch %s:%s\n")
1055 1054 % (short(n[0]), short(n[1])))
1056 1055 search.append(n) # schedule branch range for scanning
1057 1056 seenbranch[n] = 1
1058 1057 else:
1059 1058 if n[1] not in seen and n[1] not in fetch:
1060 1059 if n[2] in m and n[3] in m:
1061 1060 self.ui.debug(_("found new changeset %s\n") %
1062 1061 short(n[1]))
1063 1062 fetch[n[1]] = 1 # earliest unknown
1064 1063 for p in n[2:4]:
1065 1064 if p in m:
1066 1065 base[p] = 1 # latest known
1067 1066
1068 1067 for p in n[2:4]:
1069 1068 if p not in req and p not in m:
1070 1069 r.append(p)
1071 1070 req[p] = 1
1072 1071 seen[n[0]] = 1
1073 1072
1074 1073 if r:
1075 1074 reqcnt += 1
1076 1075 self.ui.debug(_("request %d: %s\n") %
1077 1076 (reqcnt, " ".join(map(short, r))))
1078 1077 for p in range(0, len(r), 10):
1079 1078 for b in remote.branches(r[p:p+10]):
1080 1079 self.ui.debug(_("received %s:%s\n") %
1081 1080 (short(b[0]), short(b[1])))
1082 1081 unknown.append(b)
1083 1082
1084 1083 # do binary search on the branches we found
1085 1084 while search:
1086 1085 n = search.pop(0)
1087 1086 reqcnt += 1
1088 1087 l = remote.between([(n[0], n[1])])[0]
1089 1088 l.append(n[1])
1090 1089 p = n[0]
1091 1090 f = 1
1092 1091 for i in l:
1093 1092 self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
1094 1093 if i in m:
1095 1094 if f <= 2:
1096 1095 self.ui.debug(_("found new branch changeset %s\n") %
1097 1096 short(p))
1098 1097 fetch[p] = 1
1099 1098 base[i] = 1
1100 1099 else:
1101 1100 self.ui.debug(_("narrowed branch search to %s:%s\n")
1102 1101 % (short(p), short(i)))
1103 1102 search.append((p, i))
1104 1103 break
1105 1104 p, f = i, f * 2
1106 1105
1107 1106 # sanity check our fetch list
1108 1107 for f in fetch.keys():
1109 1108 if f in m:
1110 1109 raise repo.RepoError(_("already have changeset ") + short(f[:4]))
1111 1110
1112 1111 if base.keys() == [nullid]:
1113 1112 if force:
1114 1113 self.ui.warn(_("warning: repository is unrelated\n"))
1115 1114 else:
1116 1115 raise util.Abort(_("repository is unrelated"))
1117 1116
1118 1117 self.ui.note(_("found new changesets starting at ") +
1119 1118 " ".join([short(f) for f in fetch]) + "\n")
1120 1119
1121 1120 self.ui.debug(_("%d total queries\n") % reqcnt)
1122 1121
1123 1122 return fetch.keys()
1124 1123
1125 1124 def findoutgoing(self, remote, base=None, heads=None, force=False):
1126 1125 """Return list of nodes that are roots of subsets not in remote
1127 1126
1128 1127 If base dict is specified, assume that these nodes and their parents
1129 1128 exist on the remote side.
1130 1129 If a list of heads is specified, return only nodes which are heads
1131 1130 or ancestors of these heads, and return a second element which
1132 1131 contains all remote heads which get new children.
1133 1132 """
1134 1133 if base == None:
1135 1134 base = {}
1136 1135 self.findincoming(remote, base, heads, force=force)
1137 1136
1138 1137 self.ui.debug(_("common changesets up to ")
1139 1138 + " ".join(map(short, base.keys())) + "\n")
1140 1139
1141 1140 remain = dict.fromkeys(self.changelog.nodemap)
1142 1141
1143 1142 # prune everything remote has from the tree
1144 1143 del remain[nullid]
1145 1144 remove = base.keys()
1146 1145 while remove:
1147 1146 n = remove.pop(0)
1148 1147 if n in remain:
1149 1148 del remain[n]
1150 1149 for p in self.changelog.parents(n):
1151 1150 remove.append(p)
1152 1151
1153 1152 # find every node whose parents have been pruned
1154 1153 subset = []
1155 1154 # find every remote head that will get new children
1156 1155 updated_heads = {}
1157 1156 for n in remain:
1158 1157 p1, p2 = self.changelog.parents(n)
1159 1158 if p1 not in remain and p2 not in remain:
1160 1159 subset.append(n)
1161 1160 if heads:
1162 1161 if p1 in heads:
1163 1162 updated_heads[p1] = True
1164 1163 if p2 in heads:
1165 1164 updated_heads[p2] = True
1166 1165
1167 1166 # this is the set of all roots we have to push
1168 1167 if heads:
1169 1168 return subset, updated_heads.keys()
1170 1169 else:
1171 1170 return subset
1172 1171
1173 1172 def pull(self, remote, heads=None, force=False, lock=None):
1174 1173 mylock = False
1175 1174 if not lock:
1176 1175 lock = self.lock()
1177 1176 mylock = True
1178 1177
1179 1178 try:
1180 1179 fetch = self.findincoming(remote, force=force)
1181 1180 if fetch == [nullid]:
1182 1181 self.ui.status(_("requesting all changes\n"))
1183 1182
1184 1183 if not fetch:
1185 1184 self.ui.status(_("no changes found\n"))
1186 1185 return 0
1187 1186
1188 1187 if heads is None:
1189 1188 cg = remote.changegroup(fetch, 'pull')
1190 1189 else:
1191 1190 cg = remote.changegroupsubset(fetch, heads, 'pull')
1192 1191 return self.addchangegroup(cg, 'pull', remote.url())
1193 1192 finally:
1194 1193 if mylock:
1195 1194 lock.release()
1196 1195
1197 1196 def push(self, remote, force=False, revs=None):
1198 1197 # there are two ways to push to remote repo:
1199 1198 #
1200 1199 # addchangegroup assumes local user can lock remote
1201 1200 # repo (local filesystem, old ssh servers).
1202 1201 #
1203 1202 # unbundle assumes local user cannot lock remote repo (new ssh
1204 1203 # servers, http servers).
1205 1204
1206 1205 if remote.capable('unbundle'):
1207 1206 return self.push_unbundle(remote, force, revs)
1208 1207 return self.push_addchangegroup(remote, force, revs)
1209 1208
1210 1209 def prepush(self, remote, force, revs):
1211 1210 base = {}
1212 1211 remote_heads = remote.heads()
1213 1212 inc = self.findincoming(remote, base, remote_heads, force=force)
1214 1213 if not force and inc:
1215 1214 self.ui.warn(_("abort: unsynced remote changes!\n"))
1216 1215 self.ui.status(_("(did you forget to sync?"
1217 1216 " use push -f to force)\n"))
1218 1217 return None, 1
1219 1218
1220 1219 update, updated_heads = self.findoutgoing(remote, base, remote_heads)
1221 1220 if revs is not None:
1222 1221 msng_cl, bases, heads = self.changelog.nodesbetween(update, revs)
1223 1222 else:
1224 1223 bases, heads = update, self.changelog.heads()
1225 1224
1226 1225 if not bases:
1227 1226 self.ui.status(_("no changes found\n"))
1228 1227 return None, 1
1229 1228 elif not force:
1230 1229 # FIXME we don't properly detect creation of new heads
1231 1230 # in the push -r case, assume the user knows what he's doing
1232 1231 if not revs and len(remote_heads) < len(heads) \
1233 1232 and remote_heads != [nullid]:
1234 1233 self.ui.warn(_("abort: push creates new remote branches!\n"))
1235 1234 self.ui.status(_("(did you forget to merge?"
1236 1235 " use push -f to force)\n"))
1237 1236 return None, 1
1238 1237
1239 1238 if revs is None:
1240 1239 cg = self.changegroup(update, 'push')
1241 1240 else:
1242 1241 cg = self.changegroupsubset(update, revs, 'push')
1243 1242 return cg, remote_heads
1244 1243
1245 1244 def push_addchangegroup(self, remote, force, revs):
1246 1245 lock = remote.lock()
1247 1246
1248 1247 ret = self.prepush(remote, force, revs)
1249 1248 if ret[0] is not None:
1250 1249 cg, remote_heads = ret
1251 1250 return remote.addchangegroup(cg, 'push', self.url())
1252 1251 return ret[1]
1253 1252
1254 1253 def push_unbundle(self, remote, force, revs):
1255 1254 # local repo finds heads on server, finds out what revs it
1256 1255 # must push. once revs transferred, if server finds it has
1257 1256 # different heads (someone else won commit/push race), server
1258 1257 # aborts.
1259 1258
1260 1259 ret = self.prepush(remote, force, revs)
1261 1260 if ret[0] is not None:
1262 1261 cg, remote_heads = ret
1263 1262 if force: remote_heads = ['force']
1264 1263 return remote.unbundle(cg, remote_heads, 'push')
1265 1264 return ret[1]
1266 1265
1267 1266 def changegroupsubset(self, bases, heads, source):
1268 1267 """This function generates a changegroup consisting of all the nodes
1269 1268 that are descendents of any of the bases, and ancestors of any of
1270 1269 the heads.
1271 1270
1272 1271 It is fairly complex as determining which filenodes and which
1273 1272 manifest nodes need to be included for the changeset to be complete
1274 1273 is non-trivial.
1275 1274
1276 1275 Another wrinkle is doing the reverse, figuring out which changeset in
1277 1276 the changegroup a particular filenode or manifestnode belongs to."""
1278 1277
1279 1278 self.hook('preoutgoing', throw=True, source=source)
1280 1279
1281 1280 # Set up some initial variables
1282 1281 # Make it easy to refer to self.changelog
1283 1282 cl = self.changelog
1284 1283 # msng is short for missing - compute the list of changesets in this
1285 1284 # changegroup.
1286 1285 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
1287 1286 # Some bases may turn out to be superfluous, and some heads may be
1288 1287 # too. nodesbetween will return the minimal set of bases and heads
1289 1288 # necessary to re-create the changegroup.
1290 1289
1291 1290 # Known heads are the list of heads that it is assumed the recipient
1292 1291 # of this changegroup will know about.
1293 1292 knownheads = {}
1294 1293 # We assume that all parents of bases are known heads.
1295 1294 for n in bases:
1296 1295 for p in cl.parents(n):
1297 1296 if p != nullid:
1298 1297 knownheads[p] = 1
1299 1298 knownheads = knownheads.keys()
1300 1299 if knownheads:
1301 1300 # Now that we know what heads are known, we can compute which
1302 1301 # changesets are known. The recipient must know about all
1303 1302 # changesets required to reach the known heads from the null
1304 1303 # changeset.
1305 1304 has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
1306 1305 junk = None
1307 1306 # Transform the list into an ersatz set.
1308 1307 has_cl_set = dict.fromkeys(has_cl_set)
1309 1308 else:
1310 1309 # If there were no known heads, the recipient cannot be assumed to
1311 1310 # know about any changesets.
1312 1311 has_cl_set = {}
1313 1312
1314 1313 # Make it easy to refer to self.manifest
1315 1314 mnfst = self.manifest
1316 1315 # We don't know which manifests are missing yet
1317 1316 msng_mnfst_set = {}
1318 1317 # Nor do we know which filenodes are missing.
1319 1318 msng_filenode_set = {}
1320 1319
1321 1320 junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex
1322 1321 junk = None
1323 1322
1324 1323 # A changeset always belongs to itself, so the changenode lookup
1325 1324 # function for a changenode is identity.
1326 1325 def identity(x):
1327 1326 return x
1328 1327
1329 1328 # A function generating function. Sets up an environment for the
1330 1329 # inner function.
1331 1330 def cmp_by_rev_func(revlog):
1332 1331 # Compare two nodes by their revision number in the environment's
1333 1332 # revision history. Since the revision number both represents the
1334 1333 # most efficient order to read the nodes in, and represents a
1335 1334 # topological sorting of the nodes, this function is often useful.
1336 1335 def cmp_by_rev(a, b):
1337 1336 return cmp(revlog.rev(a), revlog.rev(b))
1338 1337 return cmp_by_rev
1339 1338
1340 1339 # If we determine that a particular file or manifest node must be a
1341 1340 # node that the recipient of the changegroup will already have, we can
1342 1341 # also assume the recipient will have all the parents. This function
1343 1342 # prunes them from the set of missing nodes.
1344 1343 def prune_parents(revlog, hasset, msngset):
1345 1344 haslst = hasset.keys()
1346 1345 haslst.sort(cmp_by_rev_func(revlog))
1347 1346 for node in haslst:
1348 1347 parentlst = [p for p in revlog.parents(node) if p != nullid]
1349 1348 while parentlst:
1350 1349 n = parentlst.pop()
1351 1350 if n not in hasset:
1352 1351 hasset[n] = 1
1353 1352 p = [p for p in revlog.parents(n) if p != nullid]
1354 1353 parentlst.extend(p)
1355 1354 for n in hasset:
1356 1355 msngset.pop(n, None)
1357 1356
1358 1357 # This is a function generating function used to set up an environment
1359 1358 # for the inner function to execute in.
1360 1359 def manifest_and_file_collector(changedfileset):
1361 1360 # This is an information gathering function that gathers
1362 1361 # information from each changeset node that goes out as part of
1363 1362 # the changegroup. The information gathered is a list of which
1364 1363 # manifest nodes are potentially required (the recipient may
1365 1364 # already have them) and total list of all files which were
1366 1365 # changed in any changeset in the changegroup.
1367 1366 #
1368 1367 # We also remember the first changenode we saw any manifest
1369 1368 # referenced by so we can later determine which changenode 'owns'
1370 1369 # the manifest.
1371 1370 def collect_manifests_and_files(clnode):
1372 1371 c = cl.read(clnode)
1373 1372 for f in c[3]:
1374 1373 # This is to make sure we only have one instance of each
1375 1374 # filename string for each filename.
1376 1375 changedfileset.setdefault(f, f)
1377 1376 msng_mnfst_set.setdefault(c[0], clnode)
1378 1377 return collect_manifests_and_files
1379 1378
1380 1379 # Figure out which manifest nodes (of the ones we think might be part
1381 1380 # of the changegroup) the recipient must know about and remove them
1382 1381 # from the changegroup.
1383 1382 def prune_manifests():
1384 1383 has_mnfst_set = {}
1385 1384 for n in msng_mnfst_set:
1386 1385 # If a 'missing' manifest thinks it belongs to a changenode
1387 1386 # the recipient is assumed to have, obviously the recipient
1388 1387 # must have that manifest.
1389 1388 linknode = cl.node(mnfst.linkrev(n))
1390 1389 if linknode in has_cl_set:
1391 1390 has_mnfst_set[n] = 1
1392 1391 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
1393 1392
1394 1393 # Use the information collected in collect_manifests_and_files to say
1395 1394 # which changenode any manifestnode belongs to.
1396 1395 def lookup_manifest_link(mnfstnode):
1397 1396 return msng_mnfst_set[mnfstnode]
1398 1397
1399 1398 # A function generating function that sets up the initial environment
1400 1399 # the inner function.
1401 1400 def filenode_collector(changedfiles):
1402 1401 next_rev = [0]
1403 1402 # This gathers information from each manifestnode included in the
1404 1403 # changegroup about which filenodes the manifest node references
1405 1404 # so we can include those in the changegroup too.
1406 1405 #
1407 1406 # It also remembers which changenode each filenode belongs to. It
1408 1407 # does this by assuming the a filenode belongs to the changenode
1409 1408 # the first manifest that references it belongs to.
1410 1409 def collect_msng_filenodes(mnfstnode):
1411 1410 r = mnfst.rev(mnfstnode)
1412 1411 if r == next_rev[0]:
1413 1412 # If the last rev we looked at was the one just previous,
1414 1413 # we only need to see a diff.
1415 1414 delta = mdiff.patchtext(mnfst.delta(mnfstnode))
1416 1415 # For each line in the delta
1417 1416 for dline in delta.splitlines():
1418 1417 # get the filename and filenode for that line
1419 1418 f, fnode = dline.split('\0')
1420 1419 fnode = bin(fnode[:40])
1421 1420 f = changedfiles.get(f, None)
1422 1421 # And if the file is in the list of files we care
1423 1422 # about.
1424 1423 if f is not None:
1425 1424 # Get the changenode this manifest belongs to
1426 1425 clnode = msng_mnfst_set[mnfstnode]
1427 1426 # Create the set of filenodes for the file if
1428 1427 # there isn't one already.
1429 1428 ndset = msng_filenode_set.setdefault(f, {})
1430 1429 # And set the filenode's changelog node to the
1431 1430 # manifest's if it hasn't been set already.
1432 1431 ndset.setdefault(fnode, clnode)
1433 1432 else:
1434 1433 # Otherwise we need a full manifest.
1435 1434 m = mnfst.read(mnfstnode)
1436 1435 # For every file in we care about.
1437 1436 for f in changedfiles:
1438 1437 fnode = m.get(f, None)
1439 1438 # If it's in the manifest
1440 1439 if fnode is not None:
1441 1440 # See comments above.
1442 1441 clnode = msng_mnfst_set[mnfstnode]
1443 1442 ndset = msng_filenode_set.setdefault(f, {})
1444 1443 ndset.setdefault(fnode, clnode)
1445 1444 # Remember the revision we hope to see next.
1446 1445 next_rev[0] = r + 1
1447 1446 return collect_msng_filenodes
1448 1447
1449 1448 # We have a list of filenodes we think we need for a file, lets remove
1450 1449 # all those we now the recipient must have.
1451 1450 def prune_filenodes(f, filerevlog):
1452 1451 msngset = msng_filenode_set[f]
1453 1452 hasset = {}
1454 1453 # If a 'missing' filenode thinks it belongs to a changenode we
1455 1454 # assume the recipient must have, then the recipient must have
1456 1455 # that filenode.
1457 1456 for n in msngset:
1458 1457 clnode = cl.node(filerevlog.linkrev(n))
1459 1458 if clnode in has_cl_set:
1460 1459 hasset[n] = 1
1461 1460 prune_parents(filerevlog, hasset, msngset)
1462 1461
1463 1462 # A function generator function that sets up the a context for the
1464 1463 # inner function.
1465 1464 def lookup_filenode_link_func(fname):
1466 1465 msngset = msng_filenode_set[fname]
1467 1466 # Lookup the changenode the filenode belongs to.
1468 1467 def lookup_filenode_link(fnode):
1469 1468 return msngset[fnode]
1470 1469 return lookup_filenode_link
1471 1470
1472 1471 # Now that we have all theses utility functions to help out and
1473 1472 # logically divide up the task, generate the group.
1474 1473 def gengroup():
1475 1474 # The set of changed files starts empty.
1476 1475 changedfiles = {}
1477 1476 # Create a changenode group generator that will call our functions
1478 1477 # back to lookup the owning changenode and collect information.
1479 1478 group = cl.group(msng_cl_lst, identity,
1480 1479 manifest_and_file_collector(changedfiles))
1481 1480 for chnk in group:
1482 1481 yield chnk
1483 1482
1484 1483 # The list of manifests has been collected by the generator
1485 1484 # calling our functions back.
1486 1485 prune_manifests()
1487 1486 msng_mnfst_lst = msng_mnfst_set.keys()
1488 1487 # Sort the manifestnodes by revision number.
1489 1488 msng_mnfst_lst.sort(cmp_by_rev_func(mnfst))
1490 1489 # Create a generator for the manifestnodes that calls our lookup
1491 1490 # and data collection functions back.
1492 1491 group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
1493 1492 filenode_collector(changedfiles))
1494 1493 for chnk in group:
1495 1494 yield chnk
1496 1495
1497 1496 # These are no longer needed, dereference and toss the memory for
1498 1497 # them.
1499 1498 msng_mnfst_lst = None
1500 1499 msng_mnfst_set.clear()
1501 1500
1502 1501 changedfiles = changedfiles.keys()
1503 1502 changedfiles.sort()
1504 1503 # Go through all our files in order sorted by name.
1505 1504 for fname in changedfiles:
1506 1505 filerevlog = self.file(fname)
1507 1506 # Toss out the filenodes that the recipient isn't really
1508 1507 # missing.
1509 1508 if msng_filenode_set.has_key(fname):
1510 1509 prune_filenodes(fname, filerevlog)
1511 1510 msng_filenode_lst = msng_filenode_set[fname].keys()
1512 1511 else:
1513 1512 msng_filenode_lst = []
1514 1513 # If any filenodes are left, generate the group for them,
1515 1514 # otherwise don't bother.
1516 1515 if len(msng_filenode_lst) > 0:
1517 1516 yield changegroup.genchunk(fname)
1518 1517 # Sort the filenodes by their revision #
1519 1518 msng_filenode_lst.sort(cmp_by_rev_func(filerevlog))
1520 1519 # Create a group generator and only pass in a changenode
1521 1520 # lookup function as we need to collect no information
1522 1521 # from filenodes.
1523 1522 group = filerevlog.group(msng_filenode_lst,
1524 1523 lookup_filenode_link_func(fname))
1525 1524 for chnk in group:
1526 1525 yield chnk
1527 1526 if msng_filenode_set.has_key(fname):
1528 1527 # Don't need this anymore, toss it to free memory.
1529 1528 del msng_filenode_set[fname]
1530 1529 # Signal that no more groups are left.
1531 1530 yield changegroup.closechunk()
1532 1531
1533 1532 if msng_cl_lst:
1534 1533 self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
1535 1534
1536 1535 return util.chunkbuffer(gengroup())
1537 1536
1538 1537 def changegroup(self, basenodes, source):
1539 1538 """Generate a changegroup of all nodes that we have that a recipient
1540 1539 doesn't.
1541 1540
1542 1541 This is much easier than the previous function as we can assume that
1543 1542 the recipient has any changenode we aren't sending them."""
1544 1543
1545 1544 self.hook('preoutgoing', throw=True, source=source)
1546 1545
1547 1546 cl = self.changelog
1548 1547 nodes = cl.nodesbetween(basenodes, None)[0]
1549 1548 revset = dict.fromkeys([cl.rev(n) for n in nodes])
1550 1549
1551 1550 def identity(x):
1552 1551 return x
1553 1552
1554 1553 def gennodelst(revlog):
1555 1554 for r in xrange(0, revlog.count()):
1556 1555 n = revlog.node(r)
1557 1556 if revlog.linkrev(n) in revset:
1558 1557 yield n
1559 1558
1560 1559 def changed_file_collector(changedfileset):
1561 1560 def collect_changed_files(clnode):
1562 1561 c = cl.read(clnode)
1563 1562 for fname in c[3]:
1564 1563 changedfileset[fname] = 1
1565 1564 return collect_changed_files
1566 1565
1567 1566 def lookuprevlink_func(revlog):
1568 1567 def lookuprevlink(n):
1569 1568 return cl.node(revlog.linkrev(n))
1570 1569 return lookuprevlink
1571 1570
1572 1571 def gengroup():
1573 1572 # construct a list of all changed files
1574 1573 changedfiles = {}
1575 1574
1576 1575 for chnk in cl.group(nodes, identity,
1577 1576 changed_file_collector(changedfiles)):
1578 1577 yield chnk
1579 1578 changedfiles = changedfiles.keys()
1580 1579 changedfiles.sort()
1581 1580
1582 1581 mnfst = self.manifest
1583 1582 nodeiter = gennodelst(mnfst)
1584 1583 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1585 1584 yield chnk
1586 1585
1587 1586 for fname in changedfiles:
1588 1587 filerevlog = self.file(fname)
1589 1588 nodeiter = gennodelst(filerevlog)
1590 1589 nodeiter = list(nodeiter)
1591 1590 if nodeiter:
1592 1591 yield changegroup.genchunk(fname)
1593 1592 lookup = lookuprevlink_func(filerevlog)
1594 1593 for chnk in filerevlog.group(nodeiter, lookup):
1595 1594 yield chnk
1596 1595
1597 1596 yield changegroup.closechunk()
1598 1597
1599 1598 if nodes:
1600 1599 self.hook('outgoing', node=hex(nodes[0]), source=source)
1601 1600
1602 1601 return util.chunkbuffer(gengroup())
1603 1602
1604 1603 def addchangegroup(self, source, srctype, url):
1605 1604 """add changegroup to repo.
1606 1605 returns number of heads modified or added + 1."""
1607 1606
1608 1607 def csmap(x):
1609 1608 self.ui.debug(_("add changeset %s\n") % short(x))
1610 1609 return cl.count()
1611 1610
1612 1611 def revmap(x):
1613 1612 return cl.rev(x)
1614 1613
1615 1614 if not source:
1616 1615 return 0
1617 1616
1618 1617 self.hook('prechangegroup', throw=True, source=srctype, url=url)
1619 1618
1620 1619 changesets = files = revisions = 0
1621 1620
1622 1621 tr = self.transaction()
1623 1622
1624 1623 # write changelog data to temp files so concurrent readers will not see
1625 1624 # inconsistent view
1626 1625 cl = None
1627 1626 try:
1628 1627 cl = appendfile.appendchangelog(self.opener, self.changelog.version)
1629 1628
1630 1629 oldheads = len(cl.heads())
1631 1630
1632 1631 # pull off the changeset group
1633 1632 self.ui.status(_("adding changesets\n"))
1634 1633 cor = cl.count() - 1
1635 1634 chunkiter = changegroup.chunkiter(source)
1636 1635 if cl.addgroup(chunkiter, csmap, tr, 1) is None:
1637 1636 raise util.Abort(_("received changelog group is empty"))
1638 1637 cnr = cl.count() - 1
1639 1638 changesets = cnr - cor
1640 1639
1641 1640 # pull off the manifest group
1642 1641 self.ui.status(_("adding manifests\n"))
1643 1642 chunkiter = changegroup.chunkiter(source)
1644 1643 # no need to check for empty manifest group here:
1645 1644 # if the result of the merge of 1 and 2 is the same in 3 and 4,
1646 1645 # no new manifest will be created and the manifest group will
1647 1646 # be empty during the pull
1648 1647 self.manifest.addgroup(chunkiter, revmap, tr)
1649 1648
1650 1649 # process the files
1651 1650 self.ui.status(_("adding file changes\n"))
1652 1651 while 1:
1653 1652 f = changegroup.getchunk(source)
1654 1653 if not f:
1655 1654 break
1656 1655 self.ui.debug(_("adding %s revisions\n") % f)
1657 1656 fl = self.file(f)
1658 1657 o = fl.count()
1659 1658 chunkiter = changegroup.chunkiter(source)
1660 1659 if fl.addgroup(chunkiter, revmap, tr) is None:
1661 1660 raise util.Abort(_("received file revlog group is empty"))
1662 1661 revisions += fl.count() - o
1663 1662 files += 1
1664 1663
1665 1664 cl.writedata()
1666 1665 finally:
1667 1666 if cl:
1668 1667 cl.cleanup()
1669 1668
1670 1669 # make changelog see real files again
1671 1670 self.changelog = changelog.changelog(self.opener, self.changelog.version)
1672 1671 self.changelog.checkinlinesize(tr)
1673 1672
1674 1673 newheads = len(self.changelog.heads())
1675 1674 heads = ""
1676 1675 if oldheads and newheads != oldheads:
1677 1676 heads = _(" (%+d heads)") % (newheads - oldheads)
1678 1677
1679 1678 self.ui.status(_("added %d changesets"
1680 1679 " with %d changes to %d files%s\n")
1681 1680 % (changesets, revisions, files, heads))
1682 1681
1683 1682 if changesets > 0:
1684 1683 self.hook('pretxnchangegroup', throw=True,
1685 1684 node=hex(self.changelog.node(cor+1)), source=srctype,
1686 1685 url=url)
1687 1686
1688 1687 tr.close()
1689 1688
1690 1689 if changesets > 0:
1691 1690 self.hook("changegroup", node=hex(self.changelog.node(cor+1)),
1692 1691 source=srctype, url=url)
1693 1692
1694 1693 for i in range(cor + 1, cnr + 1):
1695 1694 self.hook("incoming", node=hex(self.changelog.node(i)),
1696 1695 source=srctype, url=url)
1697 1696
1698 1697 return newheads - oldheads + 1
1699 1698
1700 1699
1701 1700 def stream_in(self, remote):
1702 1701 fp = remote.stream_out()
1703 1702 resp = int(fp.readline())
1704 1703 if resp != 0:
1705 1704 raise util.Abort(_('operation forbidden by server'))
1706 1705 self.ui.status(_('streaming all changes\n'))
1707 1706 total_files, total_bytes = map(int, fp.readline().split(' ', 1))
1708 1707 self.ui.status(_('%d files to transfer, %s of data\n') %
1709 1708 (total_files, util.bytecount(total_bytes)))
1710 1709 start = time.time()
1711 1710 for i in xrange(total_files):
1712 1711 name, size = fp.readline().split('\0', 1)
1713 1712 size = int(size)
1714 1713 self.ui.debug('adding %s (%s)\n' % (name, util.bytecount(size)))
1715 1714 ofp = self.opener(name, 'w')
1716 1715 for chunk in util.filechunkiter(fp, limit=size):
1717 1716 ofp.write(chunk)
1718 1717 ofp.close()
1719 1718 elapsed = time.time() - start
1720 1719 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
1721 1720 (util.bytecount(total_bytes), elapsed,
1722 1721 util.bytecount(total_bytes / elapsed)))
1723 1722 self.reload()
1724 1723 return len(self.heads()) + 1
1725 1724
1726 1725 def clone(self, remote, heads=[], stream=False):
1727 1726 '''clone remote repository.
1728 1727
1729 1728 keyword arguments:
1730 1729 heads: list of revs to clone (forces use of pull)
1731 1730 stream: use streaming clone if possible'''
1732 1731
1733 1732 # now, all clients that can request uncompressed clones can
1734 1733 # read repo formats supported by all servers that can serve
1735 1734 # them.
1736 1735
1737 1736 # if revlog format changes, client will have to check version
1738 1737 # and format flags on "stream" capability, and use
1739 1738 # uncompressed only if compatible.
1740 1739
1741 1740 if stream and not heads and remote.capable('stream'):
1742 1741 return self.stream_in(remote)
1743 1742 return self.pull(remote, heads)
1744 1743
1745 1744 # used to avoid circular references so destructors work
1746 1745 def aftertrans(base):
1747 1746 p = base
1748 1747 def a():
1749 1748 util.rename(os.path.join(p, "journal"), os.path.join(p, "undo"))
1750 1749 util.rename(os.path.join(p, "journal.dirstate"),
1751 1750 os.path.join(p, "undo.dirstate"))
1752 1751 return a
1753 1752
1754 1753 def instance(ui, path, create):
1755 1754 return localrepository(ui, util.drop_scheme('file', path), create)
1756 1755
1757 1756 def islocal(path):
1758 1757 return True
@@ -1,199 +1,202 b''
1 1 # manifest.py - manifest revision class for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from revlog import *
9 9 from i18n import gettext as _
10 10 from demandload import *
11 11 demandload(globals(), "array bisect struct")
12 12
13 13 class manifestdict(dict):
14 def __init__(self, mapping={}, flags={}):
14 def __init__(self, mapping=None, flags=None):
15 if mapping is None: mapping = {}
16 if flags is None: flags = {}
15 17 dict.__init__(self, mapping)
16 18 self._flags = flags
17 19 def flags(self, f):
18 20 return self._flags.get(f, "")
19 21 def execf(self, f):
20 22 "test for executable in manifest flags"
21 23 return "x" in self.flags(f)
22 24 def linkf(self, f):
23 25 "test for symlink in manifest flags"
24 26 return "l" in self.flags(f)
25 27 def rawset(self, f, entry):
26 28 self[f] = bin(entry[:40])
27 29 fl = entry[40:-1]
28 30 if fl: self._flags[f] = fl
29 31 def set(self, f, execf=False, linkf=False):
30 if execf: self._flags[f] = "x"
31 if linkf: self._flags[f] = "x"
32 if linkf: self._flags[f] = "l"
33 elif execf: self._flags[f] = "x"
34 else: self._flags[f] = ""
32 35 def copy(self):
33 36 return manifestdict(dict.copy(self), dict.copy(self._flags))
34 37
35 38 class manifest(revlog):
36 39 def __init__(self, opener, defversion=REVLOGV0):
37 40 self.mapcache = None
38 41 self.listcache = None
39 42 revlog.__init__(self, opener, "00manifest.i", "00manifest.d",
40 43 defversion)
41 44
42 45 def read(self, node):
43 46 if node == nullid: return manifestdict() # don't upset local cache
44 47 if self.mapcache and self.mapcache[0] == node:
45 48 return self.mapcache[1]
46 49 text = self.revision(node)
47 50 self.listcache = array.array('c', text)
48 51 lines = text.splitlines(1)
49 52 mapping = manifestdict()
50 53 for l in lines:
51 54 (f, n) = l.split('\0')
52 55 mapping.rawset(f, n)
53 56 self.mapcache = (node, mapping)
54 57 return mapping
55 58
56 59 def diff(self, a, b):
57 60 return mdiff.textdiff(str(a), str(b))
58 61
59 62 def _search(self, m, s, lo=0, hi=None):
60 63 '''return a tuple (start, end) that says where to find s within m.
61 64
62 65 If the string is found m[start:end] are the line containing
63 66 that string. If start == end the string was not found and
64 67 they indicate the proper sorted insertion point. This was
65 68 taken from bisect_left, and modified to find line start/end as
66 69 it goes along.
67 70
68 71 m should be a buffer or a string
69 72 s is a string'''
70 73 def advance(i, c):
71 74 while i < lenm and m[i] != c:
72 75 i += 1
73 76 return i
74 77 lenm = len(m)
75 78 if not hi:
76 79 hi = lenm
77 80 while lo < hi:
78 81 mid = (lo + hi) // 2
79 82 start = mid
80 83 while start > 0 and m[start-1] != '\n':
81 84 start -= 1
82 85 end = advance(start, '\0')
83 86 if m[start:end] < s:
84 87 # we know that after the null there are 40 bytes of sha1
85 88 # this translates to the bisect lo = mid + 1
86 89 lo = advance(end + 40, '\n') + 1
87 90 else:
88 91 # this translates to the bisect hi = mid
89 92 hi = start
90 93 end = advance(lo, '\0')
91 94 found = m[lo:end]
92 95 if cmp(s, found) == 0:
93 96 # we know that after the null there are 40 bytes of sha1
94 97 end = advance(end + 40, '\n')
95 98 return (lo, end+1)
96 99 else:
97 100 return (lo, lo)
98 101
99 102 def find(self, node, f):
100 103 '''look up entry for a single file efficiently.
101 104 return (node, flag) pair if found, (None, None) if not.'''
102 105 if self.mapcache and node == self.mapcache[0]:
103 106 return self.mapcache[1].get(f), self.mapcache[1].flags(f)
104 107 text = self.revision(node)
105 108 start, end = self._search(text, f)
106 109 if start == end:
107 110 return None, None
108 111 l = text[start:end]
109 112 f, n = l.split('\0')
110 113 return bin(n[:40]), n[40:-1] == 'x'
111 114
112 115 def add(self, map, transaction, link, p1=None, p2=None,
113 116 changed=None):
114 117 # apply the changes collected during the bisect loop to our addlist
115 118 # return a delta suitable for addrevision
116 119 def addlistdelta(addlist, x):
117 120 # start from the bottom up
118 121 # so changes to the offsets don't mess things up.
119 122 i = len(x)
120 123 while i > 0:
121 124 i -= 1
122 125 start = x[i][0]
123 126 end = x[i][1]
124 127 if x[i][2]:
125 128 addlist[start:end] = array.array('c', x[i][2])
126 129 else:
127 130 del addlist[start:end]
128 131 return "".join([struct.pack(">lll", d[0], d[1], len(d[2])) + d[2] \
129 132 for d in x ])
130 133
131 134 # if we're using the listcache, make sure it is valid and
132 135 # parented by the same node we're diffing against
133 136 if not changed or not self.listcache or not p1 or \
134 137 self.mapcache[0] != p1:
135 138 files = map.keys()
136 139 files.sort()
137 140
138 141 # if this is changed to support newlines in filenames,
139 142 # be sure to check the templates/ dir again (especially *-raw.tmpl)
140 143 text = ["%s\000%s%s\n" % (f, hex(map[f]), map.flags(f)) for f in files]
141 144 self.listcache = array.array('c', "".join(text))
142 145 cachedelta = None
143 146 else:
144 147 addlist = self.listcache
145 148
146 149 # combine the changed lists into one list for sorting
147 150 work = [[x, 0] for x in changed[0]]
148 151 work[len(work):] = [[x, 1] for x in changed[1]]
149 152 work.sort()
150 153
151 154 delta = []
152 155 dstart = None
153 156 dend = None
154 157 dline = [""]
155 158 start = 0
156 159 # zero copy representation of addlist as a buffer
157 160 addbuf = buffer(addlist)
158 161
159 162 # start with a readonly loop that finds the offset of
160 163 # each line and creates the deltas
161 164 for w in work:
162 165 f = w[0]
163 166 # bs will either be the index of the item or the insert point
164 167 start, end = self._search(addbuf, f, start)
165 168 if w[1] == 0:
166 169 l = "%s\000%s%s\n" % (f, hex(map[f]), map.flags(f))
167 170 else:
168 171 l = ""
169 172 if start == end and w[1] == 1:
170 173 # item we want to delete was not found, error out
171 174 raise AssertionError(
172 175 _("failed to remove %s from manifest\n") % f)
173 176 if dstart != None and dstart <= start and dend >= start:
174 177 if dend < end:
175 178 dend = end
176 179 if l:
177 180 dline.append(l)
178 181 else:
179 182 if dstart != None:
180 183 delta.append([dstart, dend, "".join(dline)])
181 184 dstart = start
182 185 dend = end
183 186 dline = [l]
184 187
185 188 if dstart != None:
186 189 delta.append([dstart, dend, "".join(dline)])
187 190 # apply the delta to the addlist, and get a delta for addrevision
188 191 cachedelta = addlistdelta(addlist, delta)
189 192
190 193 # the delta is only valid if we've been processing the tip revision
191 194 if self.mapcache[0] != self.tip():
192 195 cachedelta = None
193 196 self.listcache = addlist
194 197
195 198 n = self.addrevision(buffer(self.listcache), transaction, link, p1, \
196 199 p2, cachedelta)
197 200 self.mapcache = (n, map)
198 201
199 202 return n
General Comments 0
You need to be logged in to leave comments. Login now