##// END OF EJS Templates
Support for 0, 1, or 2 diff revs
mpm@selenic.com -
r33:98633e60 default
parent child Browse files
Show More
@@ -1,323 +1,339 b''
1 1 #!/usr/bin/env python
2 2 #
3 3 # mercurial - a minimal scalable distributed SCM
4 4 # v0.4d "oedipa maas"
5 5 #
6 6 # Copyright 2005 Matt Mackall <mpm@selenic.com>
7 7 #
8 8 # This software may be used and distributed according to the terms
9 9 # of the GNU General Public License, incorporated herein by reference.
10 10
11 11 # the psyco compiler makes commits about twice as fast
12 12 try:
13 13 import psyco
14 14 psyco.full()
15 15 except:
16 16 pass
17 17
18 18 import sys, os, time
19 19 from mercurial import hg, mdiff, fancyopts
20 20
21 21 def help():
22 22 print """\
23 23 commands:
24 24
25 25 init create a new repository in this directory
26 26 branch <path> create a branch of <path> in this directory
27 27 merge <path> merge changes from <path> into local repository
28 28 checkout [changeset] checkout the latest or given changeset
29 29 status show new, missing, and changed files in working dir
30 30 add [files...] add the given files in the next commit
31 31 remove [files...] remove the given files in the next commit
32 32 addremove add all new files, delete all missing files
33 33 commit commit all changes to the repository
34 34 history show changeset history
35 35 log <file> show revision history of a single file
36 36 dump <file> [rev] dump the latest or given revision of a file
37 37 dumpmanifest [rev] dump the latest or given revision of the manifest
38 38 diff [files...] diff working directory (or selected files)
39 39 """
40 40
41 def diffdir(node, files = None):
42 (c, a, d) = repo.diffdir(repo.root, node)
41 def filterfiles(list, files):
42 l = [ x for x in list if x in files ]
43 43
44 if args:
45 nc = [ x for x in c if x in args ]
46 na = [ x for x in a if x in args ]
47 nd = [ x for x in d if x in args ]
48 for arg in args:
49 if not os.path.isdir(arg): continue
50 if arg[-1] != os.sep: arg += os.sep
51 nc += [ x for x in c if x.startswith(arg) ]
52 na += [ x for x in a if x.startswith(arg) ]
53 nd += [ x for x in d if x.startswith(arg) ]
54 (c, a, d) = (nc, na, nd)
55
56 return (c, a, d)
57
44 for f in files:
45 if f[-1] != os.sep: f += os.sep
46 l += [ x for x in list if x.startswith(f) ]
47 return l
58 48
59 49 options = {}
60 50 opts = [('v', 'verbose', None, 'verbose'),
61 51 ('d', 'debug', None, 'debug')]
62 52
63 53 args = fancyopts.fancyopts(sys.argv[1:], opts, options,
64 54 'hg [options] <command> [command options] [files]')
65 55
66 56 try:
67 57 cmd = args[0]
68 58 args = args[1:]
69 59 except:
70 60 cmd = ""
71 61
72 62 ui = hg.ui(options["verbose"], options["debug"])
73 63
74 64 if cmd == "init":
75 65 repo = hg.repository(ui, ".", create=1)
76 66 sys.exit(0)
77 67 elif cmd == "branch" or cmd == "clone":
78 68 os.system("cp -al %s/.hg .hg" % args[0])
79 69 sys.exit(0)
80 70 elif cmd == "help":
81 71 help()
82 72 sys.exit(0)
83 73 else:
84 74 try:
85 75 repo = hg.repository(ui=ui)
86 76 except:
87 77 print "Unable to open repository"
88 78 sys.exit(0)
89 79
90 80 if cmd == "checkout" or cmd == "co":
91 81 node = repo.changelog.tip()
92 82 if len(args):
93 83 if len(args[0]) < 40:
94 84 rev = int(args[0])
95 85 node = repo.changelog.node(rev)
96 86 else:
97 87 node = args[0]
98 88 repo.checkout(node)
99 89
100 90 elif cmd == "add":
101 91 repo.add(args)
102 92
103 93 elif cmd == "remove" or cmd == "rm" or cmd == "del" or cmd == "delete":
104 94 repo.remove(args)
105 95
106 96 elif cmd == "commit" or cmd == "checkin" or cmd == "ci":
107 97 if 1:
108 98 if len(args) > 0:
109 99 repo.commit(repo.current, args)
110 100 else:
111 101 repo.commit(repo.current)
112 102
113 103 elif cmd == "import" or cmd == "patch":
114 104 ioptions = {}
115 105 opts = [('p', 'strip', 1, 'path strip'),
116 106 ('b', 'base', "", 'base path')]
117 107
118 108 args = fancyopts.fancyopts(args, opts, ioptions,
119 109 'hg import [options] <patch names>')
120 110 d = ioptions["base"]
121 111 strip = ioptions["strip"]
122 112
123 113 for patch in args:
124 114 ui.status("applying %s\n" % patch)
125 115 pf = d + patch
126 116 os.system("patch -p%d < %s > /dev/null" % (strip, pf))
127 117 f = os.popen("lsdiff --strip %d %s" % (strip, pf))
128 118 files = f.read().splitlines()
129 119 f.close()
130 120 repo.commit(files)
131 121
132 122 elif cmd == "status":
133 (c, a, d) = diffdir(repo.current)
123 (c, a, d) = repo.diffdir(repo.root, repo.current)
134 124 for f in c: print "C", f
135 125 for f in a: print "?", f
136 126 for f in d: print "R", f
137 127
138 128 elif cmd == "diff":
139 (c, a, d) = diffdir(repo.current, args)
129 doptions = {}
130 revs = [repo.current]
131
132 if args:
133 opts = [('r', 'revision', [], 'revision')]
134 args = fancyopts.fancyopts(args, opts, doptions,
135 'hg diff [options] [files]')
136 # revs = [ repo.lookup(x) for x in doptions['revision'] ]
137 revs = [hg.bin(x) for x in doptions['revision']]
140 138
141 mmap = {}
142 if repo.current:
143 change = repo.changelog.read(repo.current)
139 if len(revs) > 2:
140 print "too many revisions to diff"
141 sys.exit(1)
142 elif len(revs) == 2:
143 change = repo.changelog.read(revs[1])
144 mmap2 = repo.manifest.read(change[0])
145 (c, a, d) = repo.diffrevs(revs[0], revs[1])
146 def read(f): return repo.file(f).read(mmap2[f])
147 else:
148 if len(revs) < 1:
149 if not repo.current:
150 sys.exit(0)
151 (c, a, d) = repo.diffdir(repo.root, revs[0])
152 def read(f): return file(f).read()
153
154 change = repo.changelog.read(revs[0])
144 155 mmap = repo.manifest.read(change[0])
145 156
157 if args:
158 c = filterfiles(c, args)
159 a = filterfiles(a, args)
160 d = filterfiles(d, args)
161
146 162 for f in c:
147 163 to = repo.file(f).read(mmap[f])
148 tn = file(f).read()
164 tn = read(f)
149 165 sys.stdout.write(mdiff.unidiff(to, tn, f))
150 166 for f in a:
151 167 to = ""
152 tn = file(f).read()
168 tn = read(f)
153 169 sys.stdout.write(mdiff.unidiff(to, tn, f))
154 170 for f in d:
155 171 to = repo.file(f).read(mmap[f])
156 172 tn = ""
157 173 sys.stdout.write(mdiff.unidiff(to, tn, f))
158 174
159 175 elif cmd == "addremove":
160 176 (c, a, d) = repo.diffdir(repo.root, repo.current)
161 177 repo.add(a)
162 178 repo.remove(d)
163 179
164 180 elif cmd == "history":
165 181 for i in range(repo.changelog.count()):
166 182 n = repo.changelog.node(i)
167 183 changes = repo.changelog.read(n)
168 184 (p1, p2) = repo.changelog.parents(n)
169 185 (h, h1, h2) = map(hg.hex, (n, p1, p2))
170 186 (i1, i2) = map(repo.changelog.rev, (p1, p2))
171 187 print "rev: %4d:%s" % (i, h)
172 188 print "parents: %4d:%s" % (i1, h1)
173 189 if i2: print " %4d:%s" % (i2, h2)
174 190 print "manifest: %4d:%s" % (repo.manifest.rev(changes[0]),
175 191 hg.hex(changes[0]))
176 192 print "user:", changes[1]
177 193 print "date:", time.asctime(
178 194 time.localtime(float(changes[2].split(' ')[0])))
179 195 print "files:", " ".join(changes[3])
180 196 print "description:"
181 197 print changes[4]
182 198
183 199 elif cmd == "log":
184 200 if args:
185 201 r = repo.file(args[0])
186 202 for i in range(r.count()):
187 203 n = r.node(i)
188 204 (p1, p2) = r.parents(n)
189 205 (h, h1, h2) = map(hg.hex, (n, p1, p2))
190 206 (i1, i2) = map(r.rev, (p1, p2))
191 207 cr = r.linkrev(n)
192 208 cn = hg.hex(repo.changelog.node(cr))
193 209 print "rev: %4d:%s" % (i, h)
194 210 print "changeset: %4d:%s" % (cr, cn)
195 211 print "parents: %4d:%s" % (i1, h1)
196 212 if i2: print " %4d:%s" % (i2, h2)
197 213 else:
198 214 print "missing filename"
199 215
200 216 elif cmd == "dump":
201 217 if args:
202 218 r = repo.file(args[0])
203 219 n = r.tip()
204 220 if len(args) > 1: n = hg.bin(args[1])
205 221 sys.stdout.write(r.read(n))
206 222 else:
207 223 print "missing filename"
208 224
209 225 elif cmd == "dumpmanifest":
210 226 n = repo.manifest.tip()
211 227 if len(args) > 0:
212 228 n = hg.bin(args[0])
213 229 m = repo.manifest.read(n)
214 230 files = m.keys()
215 231 files.sort()
216 232
217 233 for f in files:
218 234 print hg.hex(m[f]), f
219 235
220 236 elif cmd == "debughash":
221 237 f = repo.file(args[0])
222 238 print f.encodepath(args[0])
223 239
224 240 elif cmd == "debugindex":
225 241 r = hg.revlog(open, args[0], "")
226 242 print " rev offset length base linkrev"+\
227 243 " p1 p2 nodeid"
228 244 for i in range(r.count()):
229 245 e = r.index[i]
230 246 print "% 6d % 9d % 7d % 5d % 7d %s.. %s.. %s.." % (
231 247 i, e[0], e[1], e[2], e[3],
232 248 hg.hex(e[4][:5]), hg.hex(e[5][:5]), hg.hex(e[6][:5]))
233 249
234 250 elif cmd == "merge":
235 251 if args:
236 252 other = hg.repository(ui, args[0])
237 253 repo.merge(other)
238 254 else:
239 255 print "missing source repository"
240 256
241 257 elif cmd == "verify":
242 258 filelinkrevs = {}
243 259 filenodes = {}
244 260 manifestchangeset = {}
245 261 changesets = revisions = files = 0
246 262
247 263 print "checking changesets"
248 264 for i in range(repo.changelog.count()):
249 265 changesets += 1
250 266 n = repo.changelog.node(i)
251 267 changes = repo.changelog.read(n)
252 268 manifestchangeset[changes[0]] = n
253 269 for f in changes[3]:
254 270 revisions += 1
255 271 filelinkrevs.setdefault(f, []).append(i)
256 272
257 273 print "checking manifests"
258 274 for i in range(repo.manifest.count()):
259 275 n = repo.manifest.node(i)
260 276 ca = repo.changelog.node(repo.manifest.linkrev(n))
261 277 cc = manifestchangeset[n]
262 278 if ca != cc:
263 279 print "manifest %s points to %s, not %s" % \
264 280 (hg.hex(n), hg.hex(ca), hg.hex(cc))
265 281 m = repo.manifest.read(n)
266 282 for f, fn in m.items():
267 283 filenodes.setdefault(f, {})[fn] = 1
268 284
269 285 print "crosschecking files in changesets and manifests"
270 286 for f in filenodes:
271 287 if f not in filelinkrevs:
272 288 print "file %s in manifest but not in changesets"
273 289
274 290 for f in filelinkrevs:
275 291 if f not in filenodes:
276 292 print "file %s in changeset but not in manifest"
277 293
278 294 print "checking files"
279 295 for f in filenodes:
280 296 files += 1
281 297 fl = repo.file(f)
282 298 nodes = {"\0"*20: 1}
283 299 for i in range(fl.count()):
284 300 n = fl.node(i)
285 301
286 302 if n not in filenodes[f]:
287 303 print "%s:%s not in manifests" % (f, hg.hex(n))
288 304 else:
289 305 del filenodes[f][n]
290 306
291 307 flr = fl.linkrev(n)
292 308 if flr not in filelinkrevs[f]:
293 309 print "%s:%s points to unexpected changeset rev %d" \
294 310 % (f, hg.hex(n), fl.linkrev(n))
295 311 else:
296 312 filelinkrevs[f].remove(flr)
297 313
298 314 # verify contents
299 315 t = fl.read(n)
300 316
301 317 # verify parents
302 318 (p1, p2) = fl.parents(n)
303 319 if p1 not in nodes:
304 320 print "%s:%s unknown parent 1 %s" % (f, hg.hex(n), hg.hex(p1))
305 321 if p2 not in nodes:
306 322 print "file %s:%s unknown parent %s" % (f, hg.hex(n), hg.hex(p1))
307 323 nodes[n] = 1
308 324
309 325 # cross-check
310 326 for flr in filelinkrevs[f]:
311 327 print "changeset rev %d not in %s" % (flr, f)
312 328
313 329 for node in filenodes[f]:
314 330 print "node %s in manifests not in %s" % (hg.hex(n), f)
315 331
316 332
317 333 print "%d files, %d changesets, %d total revisions" % (files, changesets,
318 334 revisions)
319 335
320 336 else:
321 337 print "unknown command\n"
322 338 help()
323 339 sys.exit(1)
@@ -1,614 +1,614 b''
1 1 # hg.py - repository classes 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 import sys, struct, sha, socket, os, time, base64, re, urllib2, binascii
9 9 import urllib
10 10 from mercurial import byterange
11 11 from mercurial.transaction import *
12 12 from mercurial.revlog import *
13 13
14 14 def hex(node): return binascii.hexlify(node)
15 15 def bin(node): return binascii.unhexlify(node)
16 16
17 17 class filelog(revlog):
18 18 def __init__(self, opener, path):
19 19 s = self.encodepath(path)
20 20 revlog.__init__(self, opener, os.path.join("data", s + "i"),
21 21 os.path.join("data", s))
22 22
23 23 def encodepath(self, path):
24 24 s = sha.sha(path).digest()
25 25 s = base64.encodestring(s)[:-3]
26 26 s = re.sub("\+", "%", s)
27 27 s = re.sub("/", "_", s)
28 28 return s
29 29
30 30 def read(self, node):
31 31 return self.revision(node)
32 32 def add(self, text, transaction, link, p1=None, p2=None):
33 33 return self.addrevision(text, transaction, link, p1, p2)
34 34
35 35 def resolvedag(self, old, new, transaction, link):
36 36 """resolve unmerged heads in our DAG"""
37 37 if old == new: return None
38 38 a = self.ancestor(old, new)
39 39 if old == a: return new
40 40 return self.merge3(old, new, a, transaction, link)
41 41
42 42 def merge3(self, my, other, base, transaction, link):
43 43 """perform a 3-way merge and append the result"""
44 44 def temp(prefix, node):
45 45 (fd, name) = tempfile.mkstemp(prefix)
46 46 f = os.fdopen(fd, "w")
47 47 f.write(self.revision(node))
48 48 f.close()
49 49 return name
50 50
51 51 a = temp("local", my)
52 52 b = temp("remote", other)
53 53 c = temp("parent", base)
54 54
55 55 cmd = os.environ["HGMERGE"]
56 56 r = os.system("%s %s %s %s" % (cmd, a, b, c))
57 57 if r:
58 58 raise "Merge failed, implement rollback!"
59 59
60 60 t = open(a).read()
61 61 os.unlink(a)
62 62 os.unlink(b)
63 63 os.unlink(c)
64 64 return self.addrevision(t, transaction, link, my, other)
65 65
66 66 def merge(self, other, transaction, linkseq, link):
67 67 """perform a merge and resolve resulting heads"""
68 68 (o, n) = self.mergedag(other, transaction, linkseq)
69 69 return self.resolvedag(o, n, transaction, link)
70 70
71 71 class manifest(revlog):
72 72 def __init__(self, opener):
73 73 self.mapcache = None
74 74 self.listcache = None
75 75 self.addlist = None
76 76 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
77 77
78 78 def read(self, node):
79 79 if self.mapcache and self.mapcache[0] == node:
80 80 return self.mapcache[1]
81 81 text = self.revision(node)
82 82 map = {}
83 83 self.listcache = (text, text.splitlines(1))
84 84 for l in self.listcache[1]:
85 85 (f, n) = l.split('\0')
86 86 map[f] = bin(n[:40])
87 87 self.mapcache = (node, map)
88 88 return map
89 89
90 90 def diff(self, a, b):
91 91 # this is sneaky, as we're not actually using a and b
92 92 if self.listcache and len(self.listcache[0]) == len(a):
93 93 return mdiff.diff(self.listcache[1], self.addlist, 1)
94 94 else:
95 95 return mdiff.diff(a, b)
96 96
97 97 def add(self, map, transaction, link, p1=None, p2=None):
98 98 files = map.keys()
99 99 files.sort()
100 100
101 101 self.addlist = ["%s\000%s\n" % (f, hex(map[f])) for f in files]
102 102 text = "".join(self.addlist)
103 103
104 104 n = self.addrevision(text, transaction, link, p1, p2)
105 105 self.mapcache = (n, map)
106 106 self.listcache = (text, self.addlist)
107 107
108 108 return n
109 109
110 110 class changelog(revlog):
111 111 def __init__(self, opener):
112 112 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
113 113
114 114 def extract(self, text):
115 115 last = text.index("\n\n")
116 116 desc = text[last + 2:]
117 117 l = text[:last].splitlines()
118 118 manifest = bin(l[0])
119 119 user = l[1]
120 120 date = l[2]
121 121 files = l[3:]
122 122 return (manifest, user, date, files, desc)
123 123
124 124 def read(self, node):
125 125 return self.extract(self.revision(node))
126 126
127 127 def add(self, manifest, list, desc, transaction, p1=None, p2=None):
128 128 try: user = os.environ["HGUSER"]
129 129 except: user = os.environ["LOGNAME"] + '@' + socket.getfqdn()
130 130 date = "%d %d" % (time.time(), time.timezone)
131 131 list.sort()
132 132 l = [hex(manifest), user, date] + list + ["", desc]
133 133 text = "\n".join(l)
134 134 return self.addrevision(text, transaction, self.count(), p1, p2)
135 135
136 136 def merge3(self, my, other, base):
137 137 pass
138 138
139 139 class dircache:
140 140 def __init__(self, opener, ui):
141 141 self.opener = opener
142 142 self.dirty = 0
143 143 self.ui = ui
144 144 self.map = None
145 145 def __del__(self):
146 146 if self.dirty: self.write()
147 147 def __getitem__(self, key):
148 148 try:
149 149 return self.map[key]
150 150 except TypeError:
151 151 self.read()
152 152 return self[key]
153 153
154 154 def read(self):
155 155 if self.map is not None: return self.map
156 156
157 157 self.map = {}
158 158 try:
159 159 st = self.opener("dircache").read()
160 160 except: return
161 161
162 162 pos = 0
163 163 while pos < len(st):
164 164 e = struct.unpack(">llll", st[pos:pos+16])
165 165 l = e[3]
166 166 pos += 16
167 167 f = st[pos:pos + l]
168 168 self.map[f] = e[:3]
169 169 pos += l
170 170
171 171 def update(self, files):
172 172 if not files: return
173 173 self.read()
174 174 self.dirty = 1
175 175 for f in files:
176 176 try:
177 177 s = os.stat(f)
178 178 self.map[f] = (s.st_mode, s.st_size, s.st_mtime)
179 179 except IOError:
180 180 self.remove(f)
181 181
182 182 def taint(self, files):
183 183 if not files: return
184 184 self.read()
185 185 self.dirty = 1
186 186 for f in files:
187 187 self.map[f] = (0, -1, 0)
188 188
189 189 def remove(self, files):
190 190 if not files: return
191 191 self.read()
192 192 self.dirty = 1
193 193 for f in files:
194 194 try:
195 195 del self.map[f]
196 196 except KeyError:
197 197 self.ui.warn("Not in dircache: %s\n" % f)
198 198 pass
199 199
200 200 def clear(self):
201 201 self.map = {}
202 202 self.dirty = 1
203 203
204 204 def write(self):
205 205 st = self.opener("dircache", "w")
206 206 for f, e in self.map.items():
207 207 e = struct.pack(">llll", e[0], e[1], e[2], len(f))
208 208 st.write(e + f)
209 209 self.dirty = 0
210 210
211 211 def copy(self):
212 212 self.read()
213 213 return self.map.copy()
214 214
215 215 # used to avoid circular references so destructors work
216 216 def opener(base):
217 217 p = base
218 218 def o(path, mode="r"):
219 219 if p[:7] == "http://":
220 220 f = os.path.join(p, urllib.quote(path))
221 221 return httprangereader(f)
222 222
223 223 f = os.path.join(p, path)
224 224
225 225 if mode != "r" and os.path.isfile(f):
226 226 s = os.stat(f)
227 227 if s.st_nlink > 1:
228 228 file(f + ".tmp", "w").write(file(f).read())
229 229 os.rename(f+".tmp", f)
230 230
231 231 return file(f, mode)
232 232
233 233 return o
234 234
235 235 class repository:
236 236 def __init__(self, ui, path=None, create=0):
237 237 self.remote = 0
238 238 if path and path[:7] == "http://":
239 239 self.remote = 1
240 240 self.path = path
241 241 else:
242 242 if not path:
243 243 p = os.getcwd()
244 244 while not os.path.isdir(os.path.join(p, ".hg")):
245 245 p = os.path.dirname(p)
246 246 if p == "/": raise "No repo found"
247 247 path = p
248 248 self.path = os.path.join(path, ".hg")
249 249
250 250 self.root = path
251 251 self.ui = ui
252 252
253 253 if create:
254 254 os.mkdir(self.path)
255 255 os.mkdir(self.join("data"))
256 256
257 257 self.opener = opener(self.path)
258 258 self.manifest = manifest(self.opener)
259 259 self.changelog = changelog(self.opener)
260 260 self.ignorelist = None
261 261
262 262 if not self.remote:
263 263 self.dircache = dircache(self.opener, ui)
264 264 try:
265 265 self.current = bin(self.opener("current").read())
266 266 except IOError:
267 267 self.current = None
268 268
269 269 def setcurrent(self, node):
270 270 self.current = node
271 271 self.opener("current", "w").write(hex(node))
272 272
273 273 def ignore(self, f):
274 274 if self.ignorelist is None:
275 275 self.ignorelist = []
276 276 try:
277 277 l = open(os.path.join(self.root, ".hgignore")).readlines()
278 278 for pat in l:
279 279 if pat != "\n":
280 280 self.ignorelist.append(re.compile(pat[:-1]))
281 281 except IOError: pass
282 282 for pat in self.ignorelist:
283 283 if pat.search(f): return True
284 284 return False
285 285
286 286 def join(self, f):
287 287 return os.path.join(self.path, f)
288 288
289 289 def file(self, f):
290 290 return filelog(self.opener, f)
291 291
292 292 def transaction(self):
293 293 return transaction(self.opener, self.join("journal"))
294 294
295 295 def merge(self, other):
296 296 tr = self.transaction()
297 297 changed = {}
298 298 new = {}
299 299 seqrev = self.changelog.count()
300 300 # some magic to allow fiddling in nested scope
301 301 nextrev = [seqrev]
302 302
303 303 # helpers for back-linking file revisions to local changeset
304 304 # revisions so we can immediately get to changeset from annotate
305 305 def accumulate(text):
306 306 # track which files are added in which changeset and the
307 307 # corresponding _local_ changeset revision
308 308 files = self.changelog.extract(text)[3]
309 309 for f in files:
310 310 changed.setdefault(f, []).append(nextrev[0])
311 311 nextrev[0] += 1
312 312
313 313 def seq(start):
314 314 while 1:
315 315 yield start
316 316 start += 1
317 317
318 318 def lseq(l):
319 319 for r in l:
320 320 yield r
321 321
322 322 # begin the import/merge of changesets
323 323 self.ui.status("merging new changesets\n")
324 324 (co, cn) = self.changelog.mergedag(other.changelog, tr,
325 325 seq(seqrev), accumulate)
326 326 resolverev = self.changelog.count()
327 327
328 328 # is there anything to do?
329 329 if co == cn:
330 330 tr.close()
331 331 return
332 332
333 333 # do we need to resolve?
334 334 simple = (co == self.changelog.ancestor(co, cn))
335 335
336 336 # merge all files changed by the changesets,
337 337 # keeping track of the new tips
338 338 changelist = changed.keys()
339 339 changelist.sort()
340 340 for f in changelist:
341 341 sys.stdout.write(".")
342 342 sys.stdout.flush()
343 343 r = self.file(f)
344 344 node = r.merge(other.file(f), tr, lseq(changed[f]), resolverev)
345 345 if node:
346 346 new[f] = node
347 347 sys.stdout.write("\n")
348 348
349 349 # begin the merge of the manifest
350 350 self.ui.status("merging manifests\n")
351 351 (mm, mo) = self.manifest.mergedag(other.manifest, tr, seq(seqrev))
352 352
353 353 # For simple merges, we don't need to resolve manifests or changesets
354 354 if simple:
355 355 tr.close()
356 356 return
357 357
358 358 ma = self.manifest.ancestor(mm, mo)
359 359
360 360 # resolve the manifest to point to all the merged files
361 361 self.ui.status("resolving manifests\n")
362 362 mmap = self.manifest.read(mm) # mine
363 363 omap = self.manifest.read(mo) # other
364 364 amap = self.manifest.read(ma) # ancestor
365 365 nmap = {}
366 366
367 367 for f, mid in mmap.iteritems():
368 368 if f in omap:
369 369 if mid != omap[f]:
370 370 nmap[f] = new.get(f, mid) # use merged version
371 371 else:
372 372 nmap[f] = new.get(f, mid) # they're the same
373 373 del omap[f]
374 374 elif f in amap:
375 375 if mid != amap[f]:
376 376 pass # we should prompt here
377 377 else:
378 378 pass # other deleted it
379 379 else:
380 380 nmap[f] = new.get(f, mid) # we created it
381 381
382 382 del mmap
383 383
384 384 for f, oid in omap.iteritems():
385 385 if f in amap:
386 386 if oid != amap[f]:
387 387 pass # this is the nasty case, we should prompt
388 388 else:
389 389 pass # probably safe
390 390 else:
391 391 nmap[f] = new.get(f, oid) # remote created it
392 392
393 393 del omap
394 394 del amap
395 395
396 396 node = self.manifest.add(nmap, tr, resolverev, mm, mo)
397 397
398 398 # Now all files and manifests are merged, we add the changed files
399 399 # and manifest id to the changelog
400 400 self.ui.status("committing merge changeset\n")
401 401 new = new.keys()
402 402 new.sort()
403 403 if co == cn: cn = -1
404 404
405 405 edittext = "\n"+"".join(["HG: changed %s\n" % f for f in new])
406 406 edittext = self.ui.edit(edittext)
407 407 n = self.changelog.add(node, new, edittext, tr, co, cn)
408 408
409 409 tr.close()
410 410
411 411 def commit(self, parent, update = None, text = ""):
412 412 tr = self.transaction()
413 413
414 414 try:
415 415 remove = [ l[:-1] for l in self.opener("to-remove") ]
416 416 os.unlink(self.join("to-remove"))
417 417
418 418 except IOError:
419 419 remove = []
420 420
421 421 if update == None:
422 422 update = self.diffdir(self.root, parent)[0]
423 423
424 424 # check in files
425 425 new = {}
426 426 linkrev = self.changelog.count()
427 427 for f in update:
428 428 try:
429 429 t = file(f).read()
430 430 except IOError:
431 431 remove.append(f)
432 432 continue
433 433 r = self.file(f)
434 434 new[f] = r.add(t, tr, linkrev)
435 435
436 436 # update manifest
437 437 mmap = self.manifest.read(self.manifest.tip())
438 438 mmap.update(new)
439 439 for f in remove:
440 440 del mmap[f]
441 441 mnode = self.manifest.add(mmap, tr, linkrev)
442 442
443 443 # add changeset
444 444 new = new.keys()
445 445 new.sort()
446 446
447 447 edittext = text + "\n"+"".join(["HG: changed %s\n" % f for f in new])
448 448 edittext += "".join(["HG: removed %s\n" % f for f in remove])
449 449 edittext = self.ui.edit(edittext)
450 450
451 451 n = self.changelog.add(mnode, new, edittext, tr)
452 452 tr.close()
453 453
454 454 self.setcurrent(n)
455 455 self.dircache.update(new)
456 456 self.dircache.remove(remove)
457 457
458 458 def checkdir(self, path):
459 459 d = os.path.dirname(path)
460 460 if not d: return
461 461 if not os.path.isdir(d):
462 462 self.checkdir(d)
463 463 os.mkdir(d)
464 464
465 465 def checkout(self, node):
466 466 # checkout is really dumb at the moment
467 467 # it ought to basically merge
468 468 change = self.changelog.read(node)
469 469 mmap = self.manifest.read(change[0])
470 470
471 471 l = mmap.keys()
472 472 l.sort()
473 473 stats = []
474 474 for f in l:
475 475 r = self.file(f)
476 476 t = r.revision(mmap[f])
477 477 try:
478 478 file(f, "w").write(t)
479 479 except:
480 480 self.checkdir(f)
481 481 file(f, "w").write(t)
482 482
483 483 self.setcurrent(node)
484 484 self.dircache.clear()
485 485 self.dircache.update(l)
486 486
487 487 def diffdir(self, path, changeset):
488 488 changed = []
489 489 mf = {}
490 490 added = []
491 491
492 492 if changeset:
493 493 change = self.changelog.read(changeset)
494 494 mf = self.manifest.read(change[0])
495 495
496 496 if changeset == self.current:
497 497 dc = self.dircache.copy()
498 498 else:
499 499 dc = dict.fromkeys(mf)
500 500
501 501 def fcmp(fn):
502 502 t1 = file(fn).read()
503 503 t2 = self.file(fn).revision(mf[fn])
504 504 return cmp(t1, t2)
505 505
506 506 for dir, subdirs, files in os.walk(self.root):
507 507 d = dir[len(self.root)+1:]
508 508 if ".hg" in subdirs: subdirs.remove(".hg")
509 509
510 510 for f in files:
511 511 fn = os.path.join(d, f)
512 512 try: s = os.stat(fn)
513 513 except: continue
514 514 if fn in dc:
515 515 c = dc[fn]
516 516 del dc[fn]
517 517 if not c:
518 518 if fcmp(fn):
519 519 changed.append(fn)
520 if c[1] != s.st_size:
520 elif c[1] != s.st_size:
521 521 changed.append(fn)
522 522 elif c[0] != s.st_mode or c[2] != s.st_mtime:
523 523 if fcmp(fn):
524 524 changed.append(fn)
525 525 else:
526 526 if self.ignore(fn): continue
527 527 added.append(fn)
528 528
529 529 deleted = dc.keys()
530 530 deleted.sort()
531 531
532 532 return (changed, added, deleted)
533 533
534 534 def diffrevs(self, node1, node2):
535 changed, added = [], [], []
535 changed, added = [], []
536 536
537 537 change = self.changelog.read(node1)
538 538 mf1 = self.manifest.read(change[0])
539 change = self.changelog.read(revs[1])
539 change = self.changelog.read(node2)
540 540 mf2 = self.manifest.read(change[0])
541 541
542 542 for fn in mf2:
543 543 if mf1.has_key(fn):
544 544 if mf1[fn] != mf2[fn]:
545 545 changed.append(fn)
546 546 del mf1[fn]
547 547 else:
548 548 added.append(fn)
549 549
550 550 deleted = mf1.keys()
551 551 deleted.sort()
552 552
553 553 return (changed, added, deleted)
554 554
555 555 def add(self, list):
556 556 self.dircache.taint(list)
557 557
558 558 def remove(self, list):
559 559 dl = self.opener("to-remove", "a")
560 560 for f in list:
561 561 dl.write(f + "\n")
562 562
563 563 class ui:
564 564 def __init__(self, verbose=False, debug=False):
565 565 self.verbose = verbose
566 566 def write(self, *args):
567 567 for a in args:
568 568 sys.stdout.write(str(a))
569 569 def prompt(self, msg, pat):
570 570 while 1:
571 571 sys.stdout.write(msg)
572 572 r = sys.stdin.readline()[:-1]
573 573 if re.match(pat, r):
574 574 return r
575 575 def status(self, *msg):
576 576 self.write(*msg)
577 577 def warn(self, msg):
578 578 self.write(*msg)
579 579 def note(self, msg):
580 580 if self.verbose: self.write(*msg)
581 581 def debug(self, msg):
582 582 if self.debug: self.write(*msg)
583 583 def edit(self, text):
584 584 (fd, name) = tempfile.mkstemp("hg")
585 585 f = os.fdopen(fd, "w")
586 586 f.write(text)
587 587 f.close()
588 588
589 589 editor = os.environ.get("EDITOR", "vi")
590 590 r = os.system("%s %s" % (editor, name))
591 591 if r:
592 592 raise "Edit failed!"
593 593
594 594 t = open(name).read()
595 595 t = re.sub("(?m)^HG:.*\n", "", t)
596 596
597 597 return t
598 598
599 599
600 600 class httprangereader:
601 601 def __init__(self, url):
602 602 self.url = url
603 603 self.pos = 0
604 604 def seek(self, pos):
605 605 self.pos = pos
606 606 def read(self, bytes=None):
607 607 opener = urllib2.build_opener(byterange.HTTPRangeHandler())
608 608 urllib2.install_opener(opener)
609 609 req = urllib2.Request(self.url)
610 610 end = ''
611 611 if bytes: end = self.pos + bytes
612 612 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
613 613 f = urllib2.urlopen(req)
614 614 return f.read()
General Comments 0
You need to be logged in to leave comments. Login now