##// END OF EJS Templates
Add hg:// protocol...
mpm@selenic.com -
r60:e32fdbd9 default
parent child Browse files
Show More
@@ -1,841 +1,873 b''
1 # hg.py - repository classes for mercurial
1 # hg.py - repository classes for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 import sys, struct, sha, socket, os, time, base64, re, urllib2
8 import sys, struct, sha, socket, os, time, base64, re, urllib2
9 import urllib
9 import urllib
10 from mercurial import byterange
10 from mercurial import byterange
11 from mercurial.transaction import *
11 from mercurial.transaction import *
12 from mercurial.revlog import *
12 from mercurial.revlog import *
13
13
14 class filelog(revlog):
14 class filelog(revlog):
15 def __init__(self, opener, path):
15 def __init__(self, opener, path):
16 s = self.encodepath(path)
16 s = self.encodepath(path)
17 revlog.__init__(self, opener, os.path.join("data", s + "i"),
17 revlog.__init__(self, opener, os.path.join("data", s + "i"),
18 os.path.join("data", s))
18 os.path.join("data", s))
19
19
20 def encodepath(self, path):
20 def encodepath(self, path):
21 s = sha.sha(path).digest()
21 s = sha.sha(path).digest()
22 s = base64.encodestring(s)[:-3]
22 s = base64.encodestring(s)[:-3]
23 s = re.sub("\+", "%", s)
23 s = re.sub("\+", "%", s)
24 s = re.sub("/", "_", s)
24 s = re.sub("/", "_", s)
25 return s
25 return s
26
26
27 def read(self, node):
27 def read(self, node):
28 return self.revision(node)
28 return self.revision(node)
29 def add(self, text, transaction, link, p1=None, p2=None):
29 def add(self, text, transaction, link, p1=None, p2=None):
30 return self.addrevision(text, transaction, link, p1, p2)
30 return self.addrevision(text, transaction, link, p1, p2)
31
31
32 def resolvedag(self, old, new, transaction, link):
32 def resolvedag(self, old, new, transaction, link):
33 """resolve unmerged heads in our DAG"""
33 """resolve unmerged heads in our DAG"""
34 if old == new: return None
34 if old == new: return None
35 a = self.ancestor(old, new)
35 a = self.ancestor(old, new)
36 if old == a: return None
36 if old == a: return None
37 return self.merge3(old, new, a, transaction, link)
37 return self.merge3(old, new, a, transaction, link)
38
38
39 def merge3(self, my, other, base, transaction, link):
39 def merge3(self, my, other, base, transaction, link):
40 """perform a 3-way merge and append the result"""
40 """perform a 3-way merge and append the result"""
41 def temp(prefix, node):
41 def temp(prefix, node):
42 (fd, name) = tempfile.mkstemp(prefix)
42 (fd, name) = tempfile.mkstemp(prefix)
43 f = os.fdopen(fd, "w")
43 f = os.fdopen(fd, "w")
44 f.write(self.revision(node))
44 f.write(self.revision(node))
45 f.close()
45 f.close()
46 return name
46 return name
47
47
48 a = temp("local", my)
48 a = temp("local", my)
49 b = temp("remote", other)
49 b = temp("remote", other)
50 c = temp("parent", base)
50 c = temp("parent", base)
51
51
52 cmd = os.environ["HGMERGE"]
52 cmd = os.environ["HGMERGE"]
53 r = os.system("%s %s %s %s" % (cmd, a, b, c))
53 r = os.system("%s %s %s %s" % (cmd, a, b, c))
54 if r:
54 if r:
55 raise "Merge failed, implement rollback!"
55 raise "Merge failed, implement rollback!"
56
56
57 t = open(a).read()
57 t = open(a).read()
58 os.unlink(a)
58 os.unlink(a)
59 os.unlink(b)
59 os.unlink(b)
60 os.unlink(c)
60 os.unlink(c)
61 return self.addrevision(t, transaction, link, my, other)
61 return self.addrevision(t, transaction, link, my, other)
62
62
63 def merge(self, other, transaction, linkseq, link):
63 def merge(self, other, transaction, linkseq, link):
64 """perform a merge and resolve resulting heads"""
64 """perform a merge and resolve resulting heads"""
65 (o, n) = self.mergedag(other, transaction, linkseq)
65 (o, n) = self.mergedag(other, transaction, linkseq)
66 return self.resolvedag(o, n, transaction, link)
66 return self.resolvedag(o, n, transaction, link)
67
67
68 class manifest(revlog):
68 class manifest(revlog):
69 def __init__(self, opener):
69 def __init__(self, opener):
70 self.mapcache = None
70 self.mapcache = None
71 self.listcache = None
71 self.listcache = None
72 self.addlist = None
72 self.addlist = None
73 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
73 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
74
74
75 def read(self, node):
75 def read(self, node):
76 if self.mapcache and self.mapcache[0] == node:
76 if self.mapcache and self.mapcache[0] == node:
77 return self.mapcache[1]
77 return self.mapcache[1]
78 text = self.revision(node)
78 text = self.revision(node)
79 map = {}
79 map = {}
80 self.listcache = (text, text.splitlines(1))
80 self.listcache = (text, text.splitlines(1))
81 for l in self.listcache[1]:
81 for l in self.listcache[1]:
82 (f, n) = l.split('\0')
82 (f, n) = l.split('\0')
83 map[f] = bin(n[:40])
83 map[f] = bin(n[:40])
84 self.mapcache = (node, map)
84 self.mapcache = (node, map)
85 return map
85 return map
86
86
87 def diff(self, a, b):
87 def diff(self, a, b):
88 # this is sneaky, as we're not actually using a and b
88 # this is sneaky, as we're not actually using a and b
89 if self.listcache and len(self.listcache[0]) == len(a):
89 if self.listcache and len(self.listcache[0]) == len(a):
90 return mdiff.diff(self.listcache[1], self.addlist, 1)
90 return mdiff.diff(self.listcache[1], self.addlist, 1)
91 else:
91 else:
92 return mdiff.textdiff(a, b)
92 return mdiff.textdiff(a, b)
93
93
94 def add(self, map, transaction, link, p1=None, p2=None):
94 def add(self, map, transaction, link, p1=None, p2=None):
95 files = map.keys()
95 files = map.keys()
96 files.sort()
96 files.sort()
97
97
98 self.addlist = ["%s\000%s\n" % (f, hex(map[f])) for f in files]
98 self.addlist = ["%s\000%s\n" % (f, hex(map[f])) for f in files]
99 text = "".join(self.addlist)
99 text = "".join(self.addlist)
100
100
101 n = self.addrevision(text, transaction, link, p1, p2)
101 n = self.addrevision(text, transaction, link, p1, p2)
102 self.mapcache = (n, map)
102 self.mapcache = (n, map)
103 self.listcache = (text, self.addlist)
103 self.listcache = (text, self.addlist)
104
104
105 return n
105 return n
106
106
107 class changelog(revlog):
107 class changelog(revlog):
108 def __init__(self, opener):
108 def __init__(self, opener):
109 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
109 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
110
110
111 def extract(self, text):
111 def extract(self, text):
112 if not text:
112 if not text:
113 return (nullid, "", "0", [], "")
113 return (nullid, "", "0", [], "")
114 last = text.index("\n\n")
114 last = text.index("\n\n")
115 desc = text[last + 2:]
115 desc = text[last + 2:]
116 l = text[:last].splitlines()
116 l = text[:last].splitlines()
117 manifest = bin(l[0])
117 manifest = bin(l[0])
118 user = l[1]
118 user = l[1]
119 date = l[2]
119 date = l[2]
120 files = l[3:]
120 files = l[3:]
121 return (manifest, user, date, files, desc)
121 return (manifest, user, date, files, desc)
122
122
123 def read(self, node):
123 def read(self, node):
124 return self.extract(self.revision(node))
124 return self.extract(self.revision(node))
125
125
126 def add(self, manifest, list, desc, transaction, p1=None, p2=None):
126 def add(self, manifest, list, desc, transaction, p1=None, p2=None):
127 user = (os.environ.get("HGUSER") or
127 user = (os.environ.get("HGUSER") or
128 os.environ.get("EMAIL") or
128 os.environ.get("EMAIL") or
129 os.environ.get("LOGNAME", "unknown") + '@' + socket.getfqdn())
129 os.environ.get("LOGNAME", "unknown") + '@' + socket.getfqdn())
130 date = "%d %d" % (time.time(), time.timezone)
130 date = "%d %d" % (time.time(), time.timezone)
131 list.sort()
131 list.sort()
132 l = [hex(manifest), user, date] + list + ["", desc]
132 l = [hex(manifest), user, date] + list + ["", desc]
133 text = "\n".join(l)
133 text = "\n".join(l)
134 return self.addrevision(text, transaction, self.count(), p1, p2)
134 return self.addrevision(text, transaction, self.count(), p1, p2)
135
135
136 def merge3(self, my, other, base):
136 def merge3(self, my, other, base):
137 pass
137 pass
138
138
139 class dircache:
139 class dircache:
140 def __init__(self, opener, ui):
140 def __init__(self, opener, ui):
141 self.opener = opener
141 self.opener = opener
142 self.dirty = 0
142 self.dirty = 0
143 self.ui = ui
143 self.ui = ui
144 self.map = None
144 self.map = None
145 def __del__(self):
145 def __del__(self):
146 if self.dirty: self.write()
146 if self.dirty: self.write()
147 def __getitem__(self, key):
147 def __getitem__(self, key):
148 try:
148 try:
149 return self.map[key]
149 return self.map[key]
150 except TypeError:
150 except TypeError:
151 self.read()
151 self.read()
152 return self[key]
152 return self[key]
153
153
154 def read(self):
154 def read(self):
155 if self.map is not None: return self.map
155 if self.map is not None: return self.map
156
156
157 self.map = {}
157 self.map = {}
158 try:
158 try:
159 st = self.opener("dircache").read()
159 st = self.opener("dircache").read()
160 except: return
160 except: return
161
161
162 pos = 0
162 pos = 0
163 while pos < len(st):
163 while pos < len(st):
164 e = struct.unpack(">llll", st[pos:pos+16])
164 e = struct.unpack(">llll", st[pos:pos+16])
165 l = e[3]
165 l = e[3]
166 pos += 16
166 pos += 16
167 f = st[pos:pos + l]
167 f = st[pos:pos + l]
168 self.map[f] = e[:3]
168 self.map[f] = e[:3]
169 pos += l
169 pos += l
170
170
171 def update(self, files):
171 def update(self, files):
172 if not files: return
172 if not files: return
173 self.read()
173 self.read()
174 self.dirty = 1
174 self.dirty = 1
175 for f in files:
175 for f in files:
176 try:
176 try:
177 s = os.stat(f)
177 s = os.stat(f)
178 self.map[f] = (s.st_mode, s.st_size, s.st_mtime)
178 self.map[f] = (s.st_mode, s.st_size, s.st_mtime)
179 except IOError:
179 except IOError:
180 self.remove(f)
180 self.remove(f)
181
181
182 def taint(self, files):
182 def taint(self, files):
183 if not files: return
183 if not files: return
184 self.read()
184 self.read()
185 self.dirty = 1
185 self.dirty = 1
186 for f in files:
186 for f in files:
187 self.map[f] = (0, -1, 0)
187 self.map[f] = (0, -1, 0)
188
188
189 def remove(self, files):
189 def remove(self, files):
190 if not files: return
190 if not files: return
191 self.read()
191 self.read()
192 self.dirty = 1
192 self.dirty = 1
193 for f in files:
193 for f in files:
194 try:
194 try:
195 del self.map[f]
195 del self.map[f]
196 except KeyError:
196 except KeyError:
197 self.ui.warn("Not in dircache: %s\n" % f)
197 self.ui.warn("Not in dircache: %s\n" % f)
198 pass
198 pass
199
199
200 def clear(self):
200 def clear(self):
201 self.map = {}
201 self.map = {}
202 self.dirty = 1
202 self.dirty = 1
203
203
204 def write(self):
204 def write(self):
205 st = self.opener("dircache", "w")
205 st = self.opener("dircache", "w")
206 for f, e in self.map.items():
206 for f, e in self.map.items():
207 e = struct.pack(">llll", e[0], e[1], e[2], len(f))
207 e = struct.pack(">llll", e[0], e[1], e[2], len(f))
208 st.write(e + f)
208 st.write(e + f)
209 self.dirty = 0
209 self.dirty = 0
210
210
211 def copy(self):
211 def copy(self):
212 self.read()
212 self.read()
213 return self.map.copy()
213 return self.map.copy()
214
214
215 # used to avoid circular references so destructors work
215 # used to avoid circular references so destructors work
216 def opener(base):
216 def opener(base):
217 p = base
217 p = base
218 def o(path, mode="r"):
218 def o(path, mode="r"):
219 if p[:7] == "http://":
219 if p[:7] == "http://":
220 f = os.path.join(p, urllib.quote(path))
220 f = os.path.join(p, urllib.quote(path))
221 return httprangereader(f)
221 return httprangereader(f)
222
222
223 f = os.path.join(p, path)
223 f = os.path.join(p, path)
224
224
225 if mode != "r" and os.path.isfile(f):
225 if mode != "r" and os.path.isfile(f):
226 s = os.stat(f)
226 s = os.stat(f)
227 if s.st_nlink > 1:
227 if s.st_nlink > 1:
228 file(f + ".tmp", "w").write(file(f).read())
228 file(f + ".tmp", "w").write(file(f).read())
229 os.rename(f+".tmp", f)
229 os.rename(f+".tmp", f)
230
230
231 return file(f, mode)
231 return file(f, mode)
232
232
233 return o
233 return o
234
234
235 class repository:
235 class localrepository:
236 def __init__(self, ui, path=None, create=0):
236 def __init__(self, ui, path=None, create=0):
237 self.remote = 0
237 self.remote = 0
238 if path and path[:7] == "http://":
238 if path and path[:7] == "http://":
239 self.remote = 1
239 self.remote = 1
240 self.path = path
240 self.path = path
241 else:
241 else:
242 if not path:
242 if not path:
243 p = os.getcwd()
243 p = os.getcwd()
244 while not os.path.isdir(os.path.join(p, ".hg")):
244 while not os.path.isdir(os.path.join(p, ".hg")):
245 p = os.path.dirname(p)
245 p = os.path.dirname(p)
246 if p == "/": raise "No repo found"
246 if p == "/": raise "No repo found"
247 path = p
247 path = p
248 self.path = os.path.join(path, ".hg")
248 self.path = os.path.join(path, ".hg")
249
249
250 self.root = path
250 self.root = path
251 self.ui = ui
251 self.ui = ui
252
252
253 if create:
253 if create:
254 os.mkdir(self.path)
254 os.mkdir(self.path)
255 os.mkdir(self.join("data"))
255 os.mkdir(self.join("data"))
256
256
257 self.opener = opener(self.path)
257 self.opener = opener(self.path)
258 self.manifest = manifest(self.opener)
258 self.manifest = manifest(self.opener)
259 self.changelog = changelog(self.opener)
259 self.changelog = changelog(self.opener)
260 self.ignorelist = None
260 self.ignorelist = None
261
261
262 if not self.remote:
262 if not self.remote:
263 self.dircache = dircache(self.opener, ui)
263 self.dircache = dircache(self.opener, ui)
264 try:
264 try:
265 self.current = bin(self.opener("current").read())
265 self.current = bin(self.opener("current").read())
266 except IOError:
266 except IOError:
267 self.current = None
267 self.current = None
268
268
269 def setcurrent(self, node):
269 def setcurrent(self, node):
270 self.current = node
270 self.current = node
271 self.opener("current", "w").write(hex(node))
271 self.opener("current", "w").write(hex(node))
272
272
273 def ignore(self, f):
273 def ignore(self, f):
274 if self.ignorelist is None:
274 if self.ignorelist is None:
275 self.ignorelist = []
275 self.ignorelist = []
276 try:
276 try:
277 l = open(os.path.join(self.root, ".hgignore")).readlines()
277 l = open(os.path.join(self.root, ".hgignore")).readlines()
278 for pat in l:
278 for pat in l:
279 if pat != "\n":
279 if pat != "\n":
280 self.ignorelist.append(re.compile(pat[:-1]))
280 self.ignorelist.append(re.compile(pat[:-1]))
281 except IOError: pass
281 except IOError: pass
282 for pat in self.ignorelist:
282 for pat in self.ignorelist:
283 if pat.search(f): return True
283 if pat.search(f): return True
284 return False
284 return False
285
285
286 def join(self, f):
286 def join(self, f):
287 return os.path.join(self.path, f)
287 return os.path.join(self.path, f)
288
288
289 def file(self, f):
289 def file(self, f):
290 return filelog(self.opener, f)
290 return filelog(self.opener, f)
291
291
292 def transaction(self):
292 def transaction(self):
293 return transaction(self.opener, self.join("journal"))
293 return transaction(self.opener, self.join("journal"))
294
294
295 def merge(self, other):
295 def merge(self, other):
296 tr = self.transaction()
296 tr = self.transaction()
297 changed = {}
297 changed = {}
298 new = {}
298 new = {}
299 seqrev = self.changelog.count()
299 seqrev = self.changelog.count()
300 # some magic to allow fiddling in nested scope
300 # some magic to allow fiddling in nested scope
301 nextrev = [seqrev]
301 nextrev = [seqrev]
302
302
303 # helpers for back-linking file revisions to local changeset
303 # helpers for back-linking file revisions to local changeset
304 # revisions so we can immediately get to changeset from annotate
304 # revisions so we can immediately get to changeset from annotate
305 def accumulate(text):
305 def accumulate(text):
306 # track which files are added in which changeset and the
306 # track which files are added in which changeset and the
307 # corresponding _local_ changeset revision
307 # corresponding _local_ changeset revision
308 files = self.changelog.extract(text)[3]
308 files = self.changelog.extract(text)[3]
309 for f in files:
309 for f in files:
310 changed.setdefault(f, []).append(nextrev[0])
310 changed.setdefault(f, []).append(nextrev[0])
311 nextrev[0] += 1
311 nextrev[0] += 1
312
312
313 def seq(start):
313 def seq(start):
314 while 1:
314 while 1:
315 yield start
315 yield start
316 start += 1
316 start += 1
317
317
318 def lseq(l):
318 def lseq(l):
319 for r in l:
319 for r in l:
320 yield r
320 yield r
321
321
322 # begin the import/merge of changesets
322 # begin the import/merge of changesets
323 self.ui.status("merging new changesets\n")
323 self.ui.status("merging new changesets\n")
324 (co, cn) = self.changelog.mergedag(other.changelog, tr,
324 (co, cn) = self.changelog.mergedag(other.changelog, tr,
325 seq(seqrev), accumulate)
325 seq(seqrev), accumulate)
326 resolverev = self.changelog.count()
326 resolverev = self.changelog.count()
327
327
328 # is there anything to do?
328 # is there anything to do?
329 if co == cn:
329 if co == cn:
330 tr.close()
330 tr.close()
331 return
331 return
332
332
333 # do we need to resolve?
333 # do we need to resolve?
334 simple = (co == self.changelog.ancestor(co, cn))
334 simple = (co == self.changelog.ancestor(co, cn))
335
335
336 # merge all files changed by the changesets,
336 # merge all files changed by the changesets,
337 # keeping track of the new tips
337 # keeping track of the new tips
338 changelist = changed.keys()
338 changelist = changed.keys()
339 changelist.sort()
339 changelist.sort()
340 for f in changelist:
340 for f in changelist:
341 sys.stdout.write(".")
341 sys.stdout.write(".")
342 sys.stdout.flush()
342 sys.stdout.flush()
343 r = self.file(f)
343 r = self.file(f)
344 node = r.merge(other.file(f), tr, lseq(changed[f]), resolverev)
344 node = r.merge(other.file(f), tr, lseq(changed[f]), resolverev)
345 if node:
345 if node:
346 new[f] = node
346 new[f] = node
347 sys.stdout.write("\n")
347 sys.stdout.write("\n")
348
348
349 # begin the merge of the manifest
349 # begin the merge of the manifest
350 self.ui.status("merging manifests\n")
350 self.ui.status("merging manifests\n")
351 (mm, mo) = self.manifest.mergedag(other.manifest, tr, seq(seqrev))
351 (mm, mo) = self.manifest.mergedag(other.manifest, tr, seq(seqrev))
352
352
353 # For simple merges, we don't need to resolve manifests or changesets
353 # For simple merges, we don't need to resolve manifests or changesets
354 if simple:
354 if simple:
355 tr.close()
355 tr.close()
356 return
356 return
357
357
358 ma = self.manifest.ancestor(mm, mo)
358 ma = self.manifest.ancestor(mm, mo)
359
359
360 # resolve the manifest to point to all the merged files
360 # resolve the manifest to point to all the merged files
361 self.ui.status("resolving manifests\n")
361 self.ui.status("resolving manifests\n")
362 mmap = self.manifest.read(mm) # mine
362 mmap = self.manifest.read(mm) # mine
363 omap = self.manifest.read(mo) # other
363 omap = self.manifest.read(mo) # other
364 amap = self.manifest.read(ma) # ancestor
364 amap = self.manifest.read(ma) # ancestor
365 nmap = {}
365 nmap = {}
366
366
367 for f, mid in mmap.iteritems():
367 for f, mid in mmap.iteritems():
368 if f in omap:
368 if f in omap:
369 if mid != omap[f]:
369 if mid != omap[f]:
370 nmap[f] = new.get(f, mid) # use merged version
370 nmap[f] = new.get(f, mid) # use merged version
371 else:
371 else:
372 nmap[f] = new.get(f, mid) # they're the same
372 nmap[f] = new.get(f, mid) # they're the same
373 del omap[f]
373 del omap[f]
374 elif f in amap:
374 elif f in amap:
375 if mid != amap[f]:
375 if mid != amap[f]:
376 pass # we should prompt here
376 pass # we should prompt here
377 else:
377 else:
378 pass # other deleted it
378 pass # other deleted it
379 else:
379 else:
380 nmap[f] = new.get(f, mid) # we created it
380 nmap[f] = new.get(f, mid) # we created it
381
381
382 del mmap
382 del mmap
383
383
384 for f, oid in omap.iteritems():
384 for f, oid in omap.iteritems():
385 if f in amap:
385 if f in amap:
386 if oid != amap[f]:
386 if oid != amap[f]:
387 pass # this is the nasty case, we should prompt
387 pass # this is the nasty case, we should prompt
388 else:
388 else:
389 pass # probably safe
389 pass # probably safe
390 else:
390 else:
391 nmap[f] = new.get(f, oid) # remote created it
391 nmap[f] = new.get(f, oid) # remote created it
392
392
393 del omap
393 del omap
394 del amap
394 del amap
395
395
396 node = self.manifest.add(nmap, tr, resolverev, mm, mo)
396 node = self.manifest.add(nmap, tr, resolverev, mm, mo)
397
397
398 # Now all files and manifests are merged, we add the changed files
398 # Now all files and manifests are merged, we add the changed files
399 # and manifest id to the changelog
399 # and manifest id to the changelog
400 self.ui.status("committing merge changeset\n")
400 self.ui.status("committing merge changeset\n")
401 new = new.keys()
401 new = new.keys()
402 new.sort()
402 new.sort()
403 if co == cn: cn = -1
403 if co == cn: cn = -1
404
404
405 edittext = "\n"+"".join(["HG: changed %s\n" % f for f in new])
405 edittext = "\n"+"".join(["HG: changed %s\n" % f for f in new])
406 edittext = self.ui.edit(edittext)
406 edittext = self.ui.edit(edittext)
407 n = self.changelog.add(node, new, edittext, tr, co, cn)
407 n = self.changelog.add(node, new, edittext, tr, co, cn)
408
408
409 tr.close()
409 tr.close()
410
410
411 def commit(self, parent, update = None, text = ""):
411 def commit(self, parent, update = None, text = ""):
412 tr = self.transaction()
412 tr = self.transaction()
413
413
414 try:
414 try:
415 remove = [ l[:-1] for l in self.opener("to-remove") ]
415 remove = [ l[:-1] for l in self.opener("to-remove") ]
416 os.unlink(self.join("to-remove"))
416 os.unlink(self.join("to-remove"))
417
417
418 except IOError:
418 except IOError:
419 remove = []
419 remove = []
420
420
421 if update == None:
421 if update == None:
422 update = self.diffdir(self.root, parent)[0]
422 update = self.diffdir(self.root, parent)[0]
423
423
424 # check in files
424 # check in files
425 new = {}
425 new = {}
426 linkrev = self.changelog.count()
426 linkrev = self.changelog.count()
427 for f in update:
427 for f in update:
428 try:
428 try:
429 t = file(f).read()
429 t = file(f).read()
430 except IOError:
430 except IOError:
431 remove.append(f)
431 remove.append(f)
432 continue
432 continue
433 r = self.file(f)
433 r = self.file(f)
434 new[f] = r.add(t, tr, linkrev)
434 new[f] = r.add(t, tr, linkrev)
435
435
436 # update manifest
436 # update manifest
437 mmap = self.manifest.read(self.manifest.tip())
437 mmap = self.manifest.read(self.manifest.tip())
438 mmap.update(new)
438 mmap.update(new)
439 for f in remove:
439 for f in remove:
440 del mmap[f]
440 del mmap[f]
441 mnode = self.manifest.add(mmap, tr, linkrev)
441 mnode = self.manifest.add(mmap, tr, linkrev)
442
442
443 # add changeset
443 # add changeset
444 new = new.keys()
444 new = new.keys()
445 new.sort()
445 new.sort()
446
446
447 edittext = text + "\n"+"".join(["HG: changed %s\n" % f for f in new])
447 edittext = text + "\n"+"".join(["HG: changed %s\n" % f for f in new])
448 edittext += "".join(["HG: removed %s\n" % f for f in remove])
448 edittext += "".join(["HG: removed %s\n" % f for f in remove])
449 edittext = self.ui.edit(edittext)
449 edittext = self.ui.edit(edittext)
450
450
451 n = self.changelog.add(mnode, new, edittext, tr)
451 n = self.changelog.add(mnode, new, edittext, tr)
452 tr.close()
452 tr.close()
453
453
454 self.setcurrent(n)
454 self.setcurrent(n)
455 self.dircache.update(new)
455 self.dircache.update(new)
456 self.dircache.remove(remove)
456 self.dircache.remove(remove)
457
457
458 def checkdir(self, path):
458 def checkdir(self, path):
459 d = os.path.dirname(path)
459 d = os.path.dirname(path)
460 if not d: return
460 if not d: return
461 if not os.path.isdir(d):
461 if not os.path.isdir(d):
462 self.checkdir(d)
462 self.checkdir(d)
463 os.mkdir(d)
463 os.mkdir(d)
464
464
465 def checkout(self, node):
465 def checkout(self, node):
466 # checkout is really dumb at the moment
466 # checkout is really dumb at the moment
467 # it ought to basically merge
467 # it ought to basically merge
468 change = self.changelog.read(node)
468 change = self.changelog.read(node)
469 mmap = self.manifest.read(change[0])
469 mmap = self.manifest.read(change[0])
470
470
471 l = mmap.keys()
471 l = mmap.keys()
472 l.sort()
472 l.sort()
473 stats = []
473 stats = []
474 for f in l:
474 for f in l:
475 r = self.file(f)
475 r = self.file(f)
476 t = r.revision(mmap[f])
476 t = r.revision(mmap[f])
477 try:
477 try:
478 file(f, "w").write(t)
478 file(f, "w").write(t)
479 except:
479 except:
480 self.checkdir(f)
480 self.checkdir(f)
481 file(f, "w").write(t)
481 file(f, "w").write(t)
482
482
483 self.setcurrent(node)
483 self.setcurrent(node)
484 self.dircache.clear()
484 self.dircache.clear()
485 self.dircache.update(l)
485 self.dircache.update(l)
486
486
487 def diffdir(self, path, changeset):
487 def diffdir(self, path, changeset):
488 changed = []
488 changed = []
489 mf = {}
489 mf = {}
490 added = []
490 added = []
491
491
492 if changeset:
492 if changeset:
493 change = self.changelog.read(changeset)
493 change = self.changelog.read(changeset)
494 mf = self.manifest.read(change[0])
494 mf = self.manifest.read(change[0])
495
495
496 if changeset == self.current:
496 if changeset == self.current:
497 dc = self.dircache.copy()
497 dc = self.dircache.copy()
498 else:
498 else:
499 dc = dict.fromkeys(mf)
499 dc = dict.fromkeys(mf)
500
500
501 def fcmp(fn):
501 def fcmp(fn):
502 t1 = file(fn).read()
502 t1 = file(fn).read()
503 t2 = self.file(fn).revision(mf[fn])
503 t2 = self.file(fn).revision(mf[fn])
504 return cmp(t1, t2)
504 return cmp(t1, t2)
505
505
506 for dir, subdirs, files in os.walk(self.root):
506 for dir, subdirs, files in os.walk(self.root):
507 d = dir[len(self.root)+1:]
507 d = dir[len(self.root)+1:]
508 if ".hg" in subdirs: subdirs.remove(".hg")
508 if ".hg" in subdirs: subdirs.remove(".hg")
509
509
510 for f in files:
510 for f in files:
511 fn = os.path.join(d, f)
511 fn = os.path.join(d, f)
512 try: s = os.stat(fn)
512 try: s = os.stat(fn)
513 except: continue
513 except: continue
514 if fn in dc:
514 if fn in dc:
515 c = dc[fn]
515 c = dc[fn]
516 del dc[fn]
516 del dc[fn]
517 if not c:
517 if not c:
518 if fcmp(fn):
518 if fcmp(fn):
519 changed.append(fn)
519 changed.append(fn)
520 elif c[1] != s.st_size:
520 elif c[1] != s.st_size:
521 changed.append(fn)
521 changed.append(fn)
522 elif c[0] != s.st_mode or c[2] != s.st_mtime:
522 elif c[0] != s.st_mode or c[2] != s.st_mtime:
523 if fcmp(fn):
523 if fcmp(fn):
524 changed.append(fn)
524 changed.append(fn)
525 else:
525 else:
526 if self.ignore(fn): continue
526 if self.ignore(fn): continue
527 added.append(fn)
527 added.append(fn)
528
528
529 deleted = dc.keys()
529 deleted = dc.keys()
530 deleted.sort()
530 deleted.sort()
531
531
532 return (changed, added, deleted)
532 return (changed, added, deleted)
533
533
534 def diffrevs(self, node1, node2):
534 def diffrevs(self, node1, node2):
535 changed, added = [], []
535 changed, added = [], []
536
536
537 change = self.changelog.read(node1)
537 change = self.changelog.read(node1)
538 mf1 = self.manifest.read(change[0])
538 mf1 = self.manifest.read(change[0])
539 change = self.changelog.read(node2)
539 change = self.changelog.read(node2)
540 mf2 = self.manifest.read(change[0])
540 mf2 = self.manifest.read(change[0])
541
541
542 for fn in mf2:
542 for fn in mf2:
543 if mf1.has_key(fn):
543 if mf1.has_key(fn):
544 if mf1[fn] != mf2[fn]:
544 if mf1[fn] != mf2[fn]:
545 changed.append(fn)
545 changed.append(fn)
546 del mf1[fn]
546 del mf1[fn]
547 else:
547 else:
548 added.append(fn)
548 added.append(fn)
549
549
550 deleted = mf1.keys()
550 deleted = mf1.keys()
551 deleted.sort()
551 deleted.sort()
552
552
553 return (changed, added, deleted)
553 return (changed, added, deleted)
554
554
555 def add(self, list):
555 def add(self, list):
556 self.dircache.taint(list)
556 self.dircache.taint(list)
557
557
558 def remove(self, list):
558 def remove(self, list):
559 dl = self.opener("to-remove", "a")
559 dl = self.opener("to-remove", "a")
560 for f in list:
560 for f in list:
561 dl.write(f + "\n")
561 dl.write(f + "\n")
562
562
563 def branches(self, nodes):
563 def branches(self, nodes):
564 if not nodes: nodes = [self.changelog.tip()]
564 if not nodes: nodes = [self.changelog.tip()]
565 b = []
565 b = []
566 for n in nodes:
566 for n in nodes:
567 t = n
567 t = n
568 while n:
568 while n:
569 p = self.changelog.parents(n)
569 p = self.changelog.parents(n)
570 if p[1] != nullid or p[0] == nullid:
570 if p[1] != nullid or p[0] == nullid:
571 b.append((t, n, p[0], p[1]))
571 b.append((t, n, p[0], p[1]))
572 break
572 break
573 n = p[0]
573 n = p[0]
574 return b
574 return b
575
575
576 def between(self, pairs):
576 def between(self, pairs):
577 r = []
577 r = []
578
578
579 for top, bottom in pairs:
579 for top, bottom in pairs:
580 n, l, i = top, [], 0
580 n, l, i = top, [], 0
581 f = 1
581 f = 1
582
582
583 while n != bottom:
583 while n != bottom:
584 p = self.changelog.parents(n)[0]
584 p = self.changelog.parents(n)[0]
585 if i == f:
585 if i == f:
586 l.append(n)
586 l.append(n)
587 f = f * 2
587 f = f * 2
588 n = p
588 n = p
589 i += 1
589 i += 1
590
590
591 r.append(l)
591 r.append(l)
592
592
593 return r
593 return r
594
594
595 def newer(self, nodes):
595 def newer(self, nodes):
596 m = {}
596 m = {}
597 nl = []
597 nl = []
598 cl = self.changelog
598 cl = self.changelog
599 t = l = cl.count()
599 t = l = cl.count()
600 for n in nodes:
600 for n in nodes:
601 l = min(l, cl.rev(n))
601 l = min(l, cl.rev(n))
602 for p in cl.parents(n):
602 for p in cl.parents(n):
603 m[p] = 1
603 m[p] = 1
604
604
605 for i in xrange(l, t):
605 for i in xrange(l, t):
606 n = cl.node(i)
606 n = cl.node(i)
607 for p in cl.parents(n):
607 for p in cl.parents(n):
608 if p in m and n not in m:
608 if p in m and n not in m:
609 m[n] = 1
609 m[n] = 1
610 nl.append(n)
610 nl.append(n)
611
611
612 return nl
612 return nl
613
613
614 def getchangegroup(self, remote):
614 def getchangegroup(self, remote):
615 tip = remote.branches([])
615 tip = remote.branches([])[0]
616 cl = self.changelog
616 cl = self.changelog
617 unknown = tip
617 unknown = [tip]
618 search = []
618 search = []
619 fetch = []
619 fetch = []
620
620
621 if tip[0] == self.changelog.tip():
621 if tip[0] == self.changelog.tip():
622 return ""
622 return None
623
623
624 while unknown:
624 while unknown:
625 n = unknown.pop(0)
625 n = unknown.pop(0)
626 if n == nullid: break
626 if n == nullid: break
627 if n[1] and cl.nodemap.has_key(n[1]): # do we know the base?
627 if n[1] and cl.nodemap.has_key(n[1]): # do we know the base?
628 search.append(n) # schedule branch range for scanning
628 search.append(n) # schedule branch range for scanning
629 else:
629 else:
630 for b in remote.branches([n[2], n[3]]):
630 for b in remote.branches([n[2], n[3]]):
631 if cl.nodemap.has_key(b[0]):
631 if cl.nodemap.has_key(b[0]):
632 fetch.append(n[1]) # earliest unknown
632 fetch.append(n[1]) # earliest unknown
633 else:
633 else:
634 unknown.append(b)
634 unknown.append(b)
635
635
636 while search:
636 while search:
637 n = search.pop(0)
637 n = search.pop(0)
638 l = remote.between([(n[0], n[1])])[0]
638 l = remote.between([(n[0], n[1])])[0]
639 p = n[0]
639 p = n[0]
640 f = 1
640 f = 1
641 for i in l + [n[1]]:
641 for i in l + [n[1]]:
642 if self.changelog.nodemap.has_key(i):
642 if self.changelog.nodemap.has_key(i):
643 if f == 1:
643 if f <= 4:
644 fetch.append(p)
644 fetch.append(p)
645 else:
645 else:
646 search.append((p, i))
646 search.append((p, i))
647 p, f = i, f * 2
647 p, f = i, f * 2
648
648
649 return remote.changegroup(fetch)
649 return remote.changegroup(fetch)
650
650
651 def changegroup(self, basenodes):
651 def changegroup(self, basenodes):
652 nodes = self.newer(basenodes)
652 nodes = self.newer(basenodes)
653
653
654 # construct the link map
654 # construct the link map
655 linkmap = {}
655 linkmap = {}
656 for n in nodes:
656 for n in nodes:
657 linkmap[self.changelog.rev(n)] = n
657 linkmap[self.changelog.rev(n)] = n
658
658
659 # construct a list of all changed files
659 # construct a list of all changed files
660 changed = {}
660 changed = {}
661 for n in nodes:
661 for n in nodes:
662 c = self.changelog.read(n)
662 c = self.changelog.read(n)
663 for f in c[3]:
663 for f in c[3]:
664 changed[f] = 1
664 changed[f] = 1
665 changed = changed.keys()
665 changed = changed.keys()
666 changed.sort()
666 changed.sort()
667
667
668 # the changegroup is changesets + manifests + all file revs
668 # the changegroup is changesets + manifests + all file revs
669 cg = []
670 revs = [ self.changelog.rev(n) for n in nodes ]
669 revs = [ self.changelog.rev(n) for n in nodes ]
671
670
672 g = self.changelog.group(linkmap)
671 yield self.changelog.group(linkmap)
673 cg.append(g)
672 yield self.manifest.group(linkmap)
674 g = self.manifest.group(linkmap)
675 cg.append(g)
676
673
677 for f in changed:
674 for f in changed:
678 g = self.file(f).group(linkmap)
675 g = self.file(f).group(linkmap)
679 if not g: raise "couldn't find change to %s" % f
676 if not g: raise "couldn't find change to %s" % f
680 l = struct.pack(">l", len(f))
677 l = struct.pack(">l", len(f))
681 cg += [l, f, g]
678 yield "".join([l, f, g])
682
683 return "".join(cg)
684
679
685 def addchangegroup(self, data):
680 def addchangegroup(self, data):
686 def getlen(data, pos):
681 def getlen(data, pos):
687 return struct.unpack(">l", data[pos:pos + 4])[0]
682 return struct.unpack(">l", data[pos:pos + 4])[0]
683
684 if not data: return
688
685
689 tr = self.transaction()
686 tr = self.transaction()
690 simple = True
687 simple = True
691
688
692 print "merging changesets"
689 print "merging changesets"
693 # pull off the changeset group
690 # pull off the changeset group
694 l = getlen(data, 0)
691 l = getlen(data, 0)
695 csg = data[0:l]
692 csg = data[0:l]
696 pos = l
693 pos = l
697 co = self.changelog.tip()
694 co = self.changelog.tip()
698 cn = self.changelog.addgroup(csg, lambda x: self.changelog.count(), tr)
695 cn = self.changelog.addgroup(csg, lambda x: self.changelog.count(), tr)
699
696
700 print "merging manifests"
697 print "merging manifests"
701 # pull off the manifest group
698 # pull off the manifest group
702 l = getlen(data, pos)
699 l = getlen(data, pos)
703 mfg = data[pos: pos + l]
700 mfg = data[pos: pos + l]
704 pos += l
701 pos += l
705 mo = self.manifest.tip()
702 mo = self.manifest.tip()
706 mn = self.manifest.addgroup(mfg, lambda x: self.changelog.rev(x), tr)
703 mn = self.manifest.addgroup(mfg, lambda x: self.changelog.rev(x), tr)
707
704
708 # do we need a resolve?
705 # do we need a resolve?
709 if self.changelog.ancestor(co, cn) != co:
706 if self.changelog.ancestor(co, cn) != co:
710 print "NEED RESOLVE"
707 print "NEED RESOLVE"
711 simple = False
708 simple = False
712 resolverev = self.changelog.count()
709 resolverev = self.changelog.count()
713
710
714 # process the files
711 # process the files
715 print "merging files"
712 print "merging files"
716 new = {}
713 new = {}
717 while pos < len(data):
714 while pos < len(data):
718 l = getlen(data, pos)
715 l = getlen(data, pos)
719 pos += 4
716 pos += 4
720 f = data[pos:pos + l]
717 f = data[pos:pos + l]
721 pos += l
718 pos += l
722
719
723 l = getlen(data, pos)
720 l = getlen(data, pos)
724 fg = data[pos: pos + l]
721 fg = data[pos: pos + l]
725 pos += l
722 pos += l
726
723
727 fl = self.file(f)
724 fl = self.file(f)
728 o = fl.tip()
725 o = fl.tip()
729 n = fl.addgroup(fg, lambda x: self.changelog.rev(x), tr)
726 n = fl.addgroup(fg, lambda x: self.changelog.rev(x), tr)
730 if not simple:
727 if not simple:
731 new[fl] = fl.resolvedag(o, n, tr, resolverev)
728 new[fl] = fl.resolvedag(o, n, tr, resolverev)
732
729
733 # For simple merges, we don't need to resolve manifests or changesets
730 # For simple merges, we don't need to resolve manifests or changesets
734 if simple:
731 if simple:
735 tr.close()
732 tr.close()
736 return
733 return
737
734
738 # resolve the manifest to point to all the merged files
735 # resolve the manifest to point to all the merged files
739 self.ui.status("resolving manifests\n")
736 self.ui.status("resolving manifests\n")
740 ma = self.manifest.ancestor(mm, mo)
737 ma = self.manifest.ancestor(mm, mo)
741 mmap = self.manifest.read(mm) # mine
738 mmap = self.manifest.read(mm) # mine
742 omap = self.manifest.read(mo) # other
739 omap = self.manifest.read(mo) # other
743 amap = self.manifest.read(ma) # ancestor
740 amap = self.manifest.read(ma) # ancestor
744 nmap = {}
741 nmap = {}
745
742
746 for f, mid in mmap.iteritems():
743 for f, mid in mmap.iteritems():
747 if f in omap:
744 if f in omap:
748 if mid != omap[f]:
745 if mid != omap[f]:
749 nmap[f] = new.get(f, mid) # use merged version
746 nmap[f] = new.get(f, mid) # use merged version
750 else:
747 else:
751 nmap[f] = new.get(f, mid) # they're the same
748 nmap[f] = new.get(f, mid) # they're the same
752 del omap[f]
749 del omap[f]
753 elif f in amap:
750 elif f in amap:
754 if mid != amap[f]:
751 if mid != amap[f]:
755 pass # we should prompt here
752 pass # we should prompt here
756 else:
753 else:
757 pass # other deleted it
754 pass # other deleted it
758 else:
755 else:
759 nmap[f] = new.get(f, mid) # we created it
756 nmap[f] = new.get(f, mid) # we created it
760
757
761 del mmap
758 del mmap
762
759
763 for f, oid in omap.iteritems():
760 for f, oid in omap.iteritems():
764 if f in amap:
761 if f in amap:
765 if oid != amap[f]:
762 if oid != amap[f]:
766 pass # this is the nasty case, we should prompt
763 pass # this is the nasty case, we should prompt
767 else:
764 else:
768 pass # probably safe
765 pass # probably safe
769 else:
766 else:
770 nmap[f] = new.get(f, oid) # remote created it
767 nmap[f] = new.get(f, oid) # remote created it
771
768
772 del omap
769 del omap
773 del amap
770 del amap
774
771
775 node = self.manifest.add(nmap, tr, resolverev, mm, mo)
772 node = self.manifest.add(nmap, tr, resolverev, mm, mo)
776
773
777 # Now all files and manifests are merged, we add the changed files
774 # Now all files and manifests are merged, we add the changed files
778 # and manifest id to the changelog
775 # and manifest id to the changelog
779 self.ui.status("committing merge changeset\n")
776 self.ui.status("committing merge changeset\n")
780 new = new.keys()
777 new = new.keys()
781 new.sort()
778 new.sort()
782 if co == cn: cn = -1
779 if co == cn: cn = -1
783
780
784 edittext = "\n"+"".join(["HG: changed %s\n" % f for f in new])
781 edittext = "\n"+"".join(["HG: changed %s\n" % f for f in new])
785 edittext = self.ui.edit(edittext)
782 edittext = self.ui.edit(edittext)
786 n = self.changelog.add(node, new, edittext, tr, co, cn)
783 n = self.changelog.add(node, new, edittext, tr, co, cn)
787
784
788 tr.close()
785 tr.close()
789
786
787 class remoterepository:
788 def __init__(self, ui, path):
789 self.url = path.replace("hg://", "http://", 1)
790 self.ui = ui
791
792 def do_cmd(self, cmd, **args):
793 q = {"cmd": cmd}
794 q.update(args)
795 qs = urllib.urlencode(q)
796 cu = "%s?%s" % (self.url, qs)
797 return urllib.urlopen(cu).read()
798
799 def branches(self, nodes):
800 n = " ".join(map(hex, nodes))
801 d = self.do_cmd("branches", nodes=n)
802 br = [ map(bin, b.split(" ")) for b in d.splitlines() ]
803 return br
804
805 def between(self, pairs):
806 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
807 d = self.do_cmd("between", pairs=n)
808 p = [ map(bin, l.split(" ")) for l in d.splitlines() ]
809 return p
810
811 def changegroup(self, nodes):
812 n = " ".join(map(hex, nodes))
813 d = self.do_cmd("changegroup", roots=n)
814 return zlib.decompress(d)
815
816 def repository(ui, path=None, create=0):
817 if path and path[:5] == "hg://":
818 return remoterepository(ui, path)
819 else:
820 return localrepository(ui, path, create)
821
790 class ui:
822 class ui:
791 def __init__(self, verbose=False, debug=False):
823 def __init__(self, verbose=False, debug=False):
792 self.verbose = verbose
824 self.verbose = verbose
793 def write(self, *args):
825 def write(self, *args):
794 for a in args:
826 for a in args:
795 sys.stdout.write(str(a))
827 sys.stdout.write(str(a))
796 def prompt(self, msg, pat):
828 def prompt(self, msg, pat):
797 while 1:
829 while 1:
798 sys.stdout.write(msg)
830 sys.stdout.write(msg)
799 r = sys.stdin.readline()[:-1]
831 r = sys.stdin.readline()[:-1]
800 if re.match(pat, r):
832 if re.match(pat, r):
801 return r
833 return r
802 def status(self, *msg):
834 def status(self, *msg):
803 self.write(*msg)
835 self.write(*msg)
804 def warn(self, msg):
836 def warn(self, msg):
805 self.write(*msg)
837 self.write(*msg)
806 def note(self, msg):
838 def note(self, msg):
807 if self.verbose: self.write(*msg)
839 if self.verbose: self.write(*msg)
808 def debug(self, msg):
840 def debug(self, msg):
809 if self.debug: self.write(*msg)
841 if self.debug: self.write(*msg)
810 def edit(self, text):
842 def edit(self, text):
811 (fd, name) = tempfile.mkstemp("hg")
843 (fd, name) = tempfile.mkstemp("hg")
812 f = os.fdopen(fd, "w")
844 f = os.fdopen(fd, "w")
813 f.write(text)
845 f.write(text)
814 f.close()
846 f.close()
815
847
816 editor = os.environ.get("EDITOR", "vi")
848 editor = os.environ.get("EDITOR", "vi")
817 r = os.system("%s %s" % (editor, name))
849 r = os.system("%s %s" % (editor, name))
818 if r:
850 if r:
819 raise "Edit failed!"
851 raise "Edit failed!"
820
852
821 t = open(name).read()
853 t = open(name).read()
822 t = re.sub("(?m)^HG:.*\n", "", t)
854 t = re.sub("(?m)^HG:.*\n", "", t)
823
855
824 return t
856 return t
825
857
826
858
827 class httprangereader:
859 class httprangereader:
828 def __init__(self, url):
860 def __init__(self, url):
829 self.url = url
861 self.url = url
830 self.pos = 0
862 self.pos = 0
831 def seek(self, pos):
863 def seek(self, pos):
832 self.pos = pos
864 self.pos = pos
833 def read(self, bytes=None):
865 def read(self, bytes=None):
834 opener = urllib2.build_opener(byterange.HTTPRangeHandler())
866 opener = urllib2.build_opener(byterange.HTTPRangeHandler())
835 urllib2.install_opener(opener)
867 urllib2.install_opener(opener)
836 req = urllib2.Request(self.url)
868 req = urllib2.Request(self.url)
837 end = ''
869 end = ''
838 if bytes: end = self.pos + bytes
870 if bytes: end = self.pos + bytes
839 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
871 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
840 f = urllib2.urlopen(req)
872 f = urllib2.urlopen(req)
841 return f.read()
873 return f.read()
General Comments 0
You need to be logged in to leave comments. Login now