##// END OF EJS Templates
hg resolve: merge a given node into the working directory...
mpm@selenic.com -
r232:fc4a6e5b default
parent child Browse files
Show More
@@ -1,252 +1,261
1 1 import os, re, traceback, sys, signal, time
2 2 from mercurial import fancyopts, ui, hg
3 3
4 4 class UnknownCommand(Exception): pass
5 5
6 6 def filterfiles(list, files):
7 7 l = [ x for x in list if x in files ]
8 8
9 9 for f in files:
10 10 if f[-1] != os.sep: f += os.sep
11 11 l += [ x for x in list if x.startswith(f) ]
12 12 return l
13 13
14 14 def relfilter(repo, args):
15 15 if os.getcwd() != repo.root:
16 16 p = os.getcwd()[len(repo.root) + 1: ]
17 17 return filterfiles(p, args)
18 18 return args
19 19
20 20 def relpath(repo, args):
21 21 if os.getcwd() != repo.root:
22 22 p = os.getcwd()[len(repo.root) + 1: ]
23 23 return [ os.path.join(p, x) for x in args ]
24 24 return args
25 25
26 26 def help(ui, cmd=None):
27 27 '''show help'''
28 28 if cmd:
29 29 try:
30 30 i = find(cmd)
31 31 ui.write("%s\n\n" % i[2])
32 32 ui.write(i[0].__doc__, "\n")
33 33 except UnknownCommand:
34 34 ui.warn("unknown command %s", cmd)
35 35 sys.exit(0)
36 36
37 37 ui.status("""\
38 38 hg commands:
39 39
40 40 add [files...] add the given files in the next commit
41 41 addremove add all new files, delete all missing files
42 42 annotate [files...] show changeset number per file line
43 43 branch <path> create a branch of <path> in this directory
44 44 checkout [changeset] checkout the latest or given changeset
45 45 commit commit all changes to the repository
46 46 diff [files...] diff working directory (or selected files)
47 47 dump <file> [rev] dump the latest or given revision of a file
48 48 dumpmanifest [rev] dump the latest or given revision of the manifest
49 49 export <rev> dump the changeset header and diffs for a revision
50 50 history show changeset history
51 51 init create a new repository in this directory
52 52 log <file> show revision history of a single file
53 53 merge <path> merge changes from <path> into local repository
54 54 recover rollback an interrupted transaction
55 55 remove [files...] remove the given files in the next commit
56 56 serve export the repository via HTTP
57 57 status show new, missing, and changed files in working dir
58 58 tags show current changeset tags
59 59 undo undo the last transaction
60 60 """)
61 61
62 62 def init(ui):
63 63 """create a repository"""
64 64 hg.repository(ui, ".", create=1)
65 65
66 66 def branch(ui, path):
67 67 '''branch from a local repository'''
68 68 # this should eventually support remote repos
69 69 os.system("cp -al %s/.hg .hg" % path)
70 70
71 71 def checkout(ui, repo, changeset=None):
72 72 '''checkout a given changeset or the current tip'''
73 73 (c, a, d, u) = repo.diffdir(repo.root)
74 74 if c or a or d:
75 75 ui.warn("aborting (outstanding changes in working directory)\n")
76 76 sys.exit(1)
77 77
78 78 node = repo.changelog.tip()
79 79 if changeset:
80 80 node = repo.lookup(changeset)
81 81 repo.checkout(node)
82 82
83 83 def annotate(u, repo, *args, **ops):
84 84 def getnode(rev):
85 85 return hg.short(repo.changelog.node(rev))
86 86
87 87 def getname(rev):
88 88 try:
89 89 return bcache[rev]
90 90 except KeyError:
91 91 cl = repo.changelog.read(repo.changelog.node(rev))
92 92 name = cl[1]
93 93 f = name.find('@')
94 94 if f >= 0:
95 95 name = name[:f]
96 96 bcache[rev] = name
97 97 return name
98 98
99 99 bcache = {}
100 100 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
101 101 if not ops['user'] and not ops['changeset']:
102 102 ops['number'] = 1
103 103
104 104 args = relpath(repo, args)
105 105 node = repo.dirstate.parents()[0]
106 106 if ops['revision']:
107 107 node = repo.changelog.lookup(ops['revision'])
108 108 change = repo.changelog.read(node)
109 109 mmap = repo.manifest.read(change[0])
110 110 maxuserlen = 0
111 111 maxchangelen = 0
112 112 for f in args:
113 113 lines = repo.file(f).annotate(mmap[f])
114 114 pieces = []
115 115
116 116 for o, f in opmap:
117 117 if ops[o]:
118 118 l = [ f(n) for n,t in lines ]
119 119 m = max(map(len, l))
120 120 pieces.append([ "%*s" % (m, x) for x in l])
121 121
122 122 for p,l in zip(zip(*pieces), lines):
123 123 u.write(" ".join(p) + ": " + l[1])
124 124
125 125 def heads(ui, repo):
126 126 '''show current repository heads'''
127 127 for n in repo.changelog.heads():
128 128 i = repo.changelog.rev(n)
129 129 changes = repo.changelog.read(n)
130 130 (p1, p2) = repo.changelog.parents(n)
131 131 (h, h1, h2) = map(hg.hex, (n, p1, p2))
132 132 (i1, i2) = map(repo.changelog.rev, (p1, p2))
133 133 print "rev: %4d:%s" % (i, h)
134 134 print "parents: %4d:%s" % (i1, h1)
135 135 if i2: print " %4d:%s" % (i2, h2)
136 136 print "manifest: %4d:%s" % (repo.manifest.rev(changes[0]),
137 137 hg.hex(changes[0]))
138 138 print "user:", changes[1]
139 139 print "date:", time.asctime(
140 140 time.localtime(float(changes[2].split(' ')[0])))
141 141 if ui.verbose: print "files:", " ".join(changes[3])
142 142 print "description:"
143 143 print changes[4]
144 144
145 145 def parents(ui, repo, node = None):
146 146 '''show the parents of the current working dir'''
147 147 if node:
148 148 p = repo.changelog.parents(repo.lookup(hg.bin(node)))
149 149 else:
150 150 p = repo.dirstate.parents()
151 151
152 152 for n in p:
153 153 if n != hg.nullid:
154 154 ui.write("%d:%s\n" % (repo.changelog.rev(n), hg.hex(n)))
155 155
156 def resolve(ui, repo, node = None):
157 '''merge a given node or the current tip into the working dir'''
158 if not node:
159 node = repo.changelog.tip()
160 else:
161 node = repo.lookup(node)
162 repo.resolve(node)
163
156 164 def status(ui, repo):
157 165 '''show changed files in the working directory
158 166
159 167 C = changed
160 168 A = added
161 169 R = removed
162 170 ? = not tracked'''
163 171 (c, a, d, u) = repo.diffdir(repo.root)
164 172 (c, a, d, u) = map(lambda x: relfilter(repo, x), (c, a, d, u))
165 173
166 174 for f in c: print "C", f
167 175 for f in a: print "A", f
168 176 for f in d: print "R", f
169 177 for f in u: print "?", f
170 178
171 179 def undo(ui, repo):
172 180 repo.undo()
173 181
174 182 table = {
175 183 "init": (init, [], 'hg init'),
176 184 "branch|clone": (branch, [], 'hg branch [path]'),
177 185 "heads": (heads, [], 'hg heads'),
178 186 "help": (help, [], 'hg help [command]'),
179 187 "checkout|co": (checkout, [], 'hg checkout [changeset]'),
180 188 "ann|annotate": (annotate,
181 189 [('r', 'revision', '', 'revision'),
182 190 ('u', 'user', None, 'show user'),
183 191 ('n', 'number', None, 'show revision number'),
184 192 ('c', 'changeset', None, 'show changeset')],
185 193 'hg annotate [-u] [-c] [-n] [-r id] [files]'),
186 194 "parents": (parents, [], 'hg parents [node]'),
195 "resolve": (resolve, [], 'hg resolve [node]'),
187 196 "status": (status, [], 'hg status'),
188 197 "undo": (undo, [], 'hg undo'),
189 198 }
190 199
191 200 norepo = "init branch help"
192 201
193 202 def find(cmd):
194 203 i = None
195 204 for e in table.keys():
196 205 if re.match(e + "$", cmd):
197 206 return table[e]
198 207
199 208 raise UnknownCommand(cmd)
200 209
201 210 class SignalInterrupt(Exception): pass
202 211
203 212 def catchterm(*args):
204 213 raise SignalInterrupt
205 214
206 215 def dispatch(args):
207 216 options = {}
208 217 opts = [('v', 'verbose', None, 'verbose'),
209 218 ('d', 'debug', None, 'debug'),
210 219 ('q', 'quiet', None, 'quiet'),
211 220 ('y', 'noninteractive', None, 'run non-interactively'),
212 221 ]
213 222
214 223 args = fancyopts.fancyopts(args, opts, options,
215 224 'hg [options] <command> [options] [files]')
216 225
217 226 if not args:
218 227 cmd = "help"
219 228 else:
220 229 cmd, args = args[0], args[1:]
221 230
222 231 u = ui.ui(options["verbose"], options["debug"], options["quiet"],
223 232 not options["noninteractive"])
224 233
225 234 # deal with unfound commands later
226 235 i = find(cmd)
227 236
228 237 signal.signal(signal.SIGTERM, catchterm)
229 238
230 239 cmdoptions = {}
231 240 args = fancyopts.fancyopts(args, i[1], cmdoptions, i[2])
232 241
233 242 if cmd not in norepo.split():
234 243 repo = hg.repository(ui = u)
235 244 d = lambda: i[0](u, repo, *args, **cmdoptions)
236 245 else:
237 246 d = lambda: i[0](u, *args, **cmdoptions)
238 247
239 248 try:
240 249 d()
241 250 except SignalInterrupt:
242 251 u.warn("killed!\n")
243 252 except KeyboardInterrupt:
244 253 u.warn("interrupted!\n")
245 254 except TypeError, inst:
246 255 # was this an argument error?
247 256 tb = traceback.extract_tb(sys.exc_info()[2])
248 257 if len(tb) > 2: # no
249 258 raise
250 259 u.warn("%s: invalid arguments\n" % i[0].__name__)
251 260 u.warn("syntax: %s\n" % i[2])
252 261 sys.exit(-1)
@@ -1,1142 +1,1071
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, re, urllib2, tempfile
9 9 import urllib
10 10 from mercurial import byterange, lock
11 11 from mercurial.transaction import *
12 12 from mercurial.revlog import *
13 13 from difflib import SequenceMatcher
14 14
15 15 class filelog(revlog):
16 16 def __init__(self, opener, path):
17 17 revlog.__init__(self, opener,
18 18 os.path.join("data", path + ".i"),
19 19 os.path.join("data", path + ".d"))
20 20
21 21 def read(self, node):
22 22 return self.revision(node)
23 23 def add(self, text, transaction, link, p1=None, p2=None):
24 24 return self.addrevision(text, transaction, link, p1, p2)
25 25
26 26 def annotate(self, node):
27 27
28 28 def decorate(text, rev):
29 29 return [(rev, l) for l in text.splitlines(1)]
30 30
31 31 def strip(annotation):
32 32 return [e[1] for e in annotation]
33 33
34 34 def pair(parent, child):
35 35 new = []
36 36 sm = SequenceMatcher(None, strip(parent), strip(child))
37 37 for o, m, n, s, t in sm.get_opcodes():
38 38 if o == 'equal':
39 39 new += parent[m:n]
40 40 else:
41 41 new += child[s:t]
42 42 return new
43 43
44 44 # find all ancestors
45 45 needed = {node:1}
46 46 visit = [node]
47 47 while visit:
48 48 n = visit.pop(0)
49 49 for p in self.parents(n):
50 50 if p not in needed:
51 51 needed[p] = 1
52 52 visit.append(p)
53 53 else:
54 54 # count how many times we'll use this
55 55 needed[p] += 1
56 56
57 57 # sort by revision which is a topological order
58 58 visit = needed.keys()
59 59 visit = [ (self.rev(n), n) for n in visit ]
60 60 visit.sort()
61 61 visit = [ p[1] for p in visit ]
62 62 hist = {}
63 63
64 64 for n in visit:
65 65 curr = decorate(self.read(n), self.linkrev(n))
66 66 for p in self.parents(n):
67 67 if p != nullid:
68 68 curr = pair(hist[p], curr)
69 69 # trim the history of unneeded revs
70 70 needed[p] -= 1
71 71 if not needed[p]:
72 72 del hist[p]
73 73 hist[n] = curr
74 74
75 75 return hist[n]
76 76
77 77 class manifest(revlog):
78 78 def __init__(self, opener):
79 79 self.mapcache = None
80 80 self.listcache = None
81 81 self.addlist = None
82 82 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
83 83
84 84 def read(self, node):
85 85 if self.mapcache and self.mapcache[0] == node:
86 86 return self.mapcache[1].copy()
87 87 text = self.revision(node)
88 88 map = {}
89 89 self.listcache = (text, text.splitlines(1))
90 90 for l in self.listcache[1]:
91 91 (f, n) = l.split('\0')
92 92 map[f] = bin(n[:40])
93 93 self.mapcache = (node, map)
94 94 return map
95 95
96 96 def diff(self, a, b):
97 97 # this is sneaky, as we're not actually using a and b
98 98 if self.listcache and self.addlist and self.listcache[0] == a:
99 99 d = mdiff.diff(self.listcache[1], self.addlist, 1)
100 100 if mdiff.patch(a, d) != b:
101 101 sys.stderr.write("*** sortdiff failed, falling back ***\n")
102 102 return mdiff.textdiff(a, b)
103 103 return d
104 104 else:
105 105 return mdiff.textdiff(a, b)
106 106
107 107 def add(self, map, transaction, link, p1=None, p2=None):
108 108 files = map.keys()
109 109 files.sort()
110 110
111 111 self.addlist = ["%s\000%s\n" % (f, hex(map[f])) for f in files]
112 112 text = "".join(self.addlist)
113 113
114 114 n = self.addrevision(text, transaction, link, p1, p2)
115 115 self.mapcache = (n, map)
116 116 self.listcache = (text, self.addlist)
117 117 self.addlist = None
118 118
119 119 return n
120 120
121 121 class changelog(revlog):
122 122 def __init__(self, opener):
123 123 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
124 124
125 125 def extract(self, text):
126 126 if not text:
127 127 return (nullid, "", "0", [], "")
128 128 last = text.index("\n\n")
129 129 desc = text[last + 2:]
130 130 l = text[:last].splitlines()
131 131 manifest = bin(l[0])
132 132 user = l[1]
133 133 date = l[2]
134 134 files = l[3:]
135 135 return (manifest, user, date, files, desc)
136 136
137 137 def read(self, node):
138 138 return self.extract(self.revision(node))
139 139
140 140 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
141 141 user=None, date=None):
142 142 user = (user or
143 143 os.environ.get("HGUSER") or
144 144 os.environ.get("EMAIL") or
145 145 os.environ.get("LOGNAME", "unknown") + '@' + socket.getfqdn())
146 146 date = date or "%d %d" % (time.time(), time.timezone)
147 147 list.sort()
148 148 l = [hex(manifest), user, date] + list + ["", desc]
149 149 text = "\n".join(l)
150 150 return self.addrevision(text, transaction, self.count(), p1, p2)
151 151
152 152 class dirstate:
153 153 def __init__(self, opener, ui):
154 154 self.opener = opener
155 155 self.dirty = 0
156 156 self.ui = ui
157 157 self.map = None
158 158 self.pl = None
159 159
160 160 def __del__(self):
161 161 if self.dirty:
162 162 self.write()
163 163
164 164 def __getitem__(self, key):
165 165 try:
166 166 return self.map[key]
167 167 except TypeError:
168 168 self.read()
169 169 return self[key]
170 170
171 171 def __contains__(self, key):
172 172 if not self.map: self.read()
173 173 return key in self.map
174 174
175 175 def parents(self):
176 176 if not self.pl:
177 177 self.read()
178 178 return self.pl
179 179
180 180 def setparents(self, p1, p2 = nullid):
181 181 self.dirty = 1
182 182 self.pl = p1, p2
183 183
184 184 def state(self, key):
185 185 try:
186 186 return self[key][0]
187 187 except KeyError:
188 188 return "?"
189 189
190 190 def read(self):
191 191 if self.map is not None: return self.map
192 192
193 193 self.map = {}
194 194 self.pl = [nullid, nullid]
195 195 try:
196 196 st = self.opener("dirstate").read()
197 197 except: return
198 198
199 199 self.pl = [st[:20], st[20: 40]]
200 200
201 201 pos = 40
202 202 while pos < len(st):
203 203 e = struct.unpack(">cllll", st[pos:pos+17])
204 204 l = e[4]
205 205 pos += 17
206 206 f = st[pos:pos + l]
207 207 self.map[f] = e[:4]
208 208 pos += l
209 209
210 210 def update(self, files, state):
211 211 ''' current states:
212 212 n normal
213 213 m needs merging
214 214 i invalid
215 215 r marked for removal
216 216 a marked for addition'''
217 217
218 218 if not files: return
219 219 self.read()
220 220 self.dirty = 1
221 221 for f in files:
222 222 if state == "r":
223 223 self.map[f] = ('r', 0, 0, 0)
224 224 else:
225 225 try:
226 226 s = os.stat(f)
227 227 self.map[f] = (state, s.st_mode, s.st_size, s.st_mtime)
228 228 except OSError:
229 229 if state != "i": raise
230 230 self.map[f] = ('r', 0, 0, 0)
231 231
232 232 def forget(self, files):
233 233 if not files: return
234 234 self.read()
235 235 self.dirty = 1
236 236 for f in files:
237 237 try:
238 238 del self.map[f]
239 239 except KeyError:
240 240 self.ui.warn("not in dirstate: %s!\n" % f)
241 241 pass
242 242
243 243 def clear(self):
244 244 self.map = {}
245 245 self.dirty = 1
246 246
247 247 def write(self):
248 248 st = self.opener("dirstate", "w")
249 249 st.write("".join(self.pl))
250 250 for f, e in self.map.items():
251 251 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
252 252 st.write(e + f)
253 253 self.dirty = 0
254 254
255 255 def copy(self):
256 256 self.read()
257 257 return self.map.copy()
258 258
259 259 # used to avoid circular references so destructors work
260 260 def opener(base):
261 261 p = base
262 262 def o(path, mode="r"):
263 263 if p[:7] == "http://":
264 264 f = os.path.join(p, urllib.quote(path))
265 265 return httprangereader(f)
266 266
267 267 f = os.path.join(p, path)
268 268
269 269 if mode != "r":
270 270 try:
271 271 s = os.stat(f)
272 272 except OSError:
273 273 d = os.path.dirname(f)
274 274 if not os.path.isdir(d):
275 275 os.makedirs(d)
276 276 else:
277 277 if s.st_nlink > 1:
278 278 file(f + ".tmp", "w").write(file(f).read())
279 279 os.rename(f+".tmp", f)
280 280
281 281 return file(f, mode)
282 282
283 283 return o
284 284
285 285 class localrepository:
286 286 def __init__(self, ui, path=None, create=0):
287 287 self.remote = 0
288 288 if path and path[:7] == "http://":
289 289 self.remote = 1
290 290 self.path = path
291 291 else:
292 292 if not path:
293 293 p = os.getcwd()
294 294 while not os.path.isdir(os.path.join(p, ".hg")):
295 295 p = os.path.dirname(p)
296 296 if p == "/": raise "No repo found"
297 297 path = p
298 298 self.path = os.path.join(path, ".hg")
299 299
300 300 self.root = path
301 301 self.ui = ui
302 302
303 303 if create:
304 304 os.mkdir(self.path)
305 305 os.mkdir(self.join("data"))
306 306
307 307 self.opener = opener(self.path)
308 308 self.manifest = manifest(self.opener)
309 309 self.changelog = changelog(self.opener)
310 310 self.ignorelist = None
311 311 self.tags = None
312 312
313 313 if not self.remote:
314 314 self.dirstate = dirstate(self.opener, ui)
315 315
316 316 def ignore(self, f):
317 317 if self.ignorelist is None:
318 318 self.ignorelist = []
319 319 try:
320 320 l = open(os.path.join(self.root, ".hgignore"))
321 321 for pat in l:
322 322 if pat != "\n":
323 323 self.ignorelist.append(re.compile(pat[:-1]))
324 324 except IOError: pass
325 325 for pat in self.ignorelist:
326 326 if pat.search(f): return True
327 327 return False
328 328
329 329 def lookup(self, key):
330 330 if self.tags is None:
331 331 self.tags = {}
332 332 try:
333 333 fl = self.file(".hgtags")
334 334 for l in fl.revision(fl.tip()).splitlines():
335 335 if l:
336 336 n, k = l.split(" ")
337 337 self.tags[k] = bin(n)
338 338 except KeyError: pass
339 339 try:
340 340 return self.tags[key]
341 341 except KeyError:
342 342 return self.changelog.lookup(key)
343 343
344 344 def join(self, f):
345 345 return os.path.join(self.path, f)
346 346
347 347 def file(self, f):
348 348 if f[0] == '/': f = f[1:]
349 349 return filelog(self.opener, f)
350 350
351 351 def transaction(self):
352 352 return transaction(self.opener, self.join("journal"),
353 353 self.join("undo"))
354 354
355 355 def recover(self):
356 356 lock = self.lock()
357 357 if os.path.exists(self.join("recover")):
358 358 self.ui.status("attempting to rollback interrupted transaction\n")
359 359 return rollback(self.opener, self.join("recover"))
360 360 else:
361 361 self.ui.warn("no interrupted transaction available\n")
362 362
363 363 def undo(self):
364 364 lock = self.lock()
365 365 if os.path.exists(self.join("undo")):
366 366 f = self.changelog.read(self.changelog.tip())[3]
367 367 self.ui.status("attempting to rollback last transaction\n")
368 368 rollback(self.opener, self.join("undo"))
369 369 self.manifest = manifest(self.opener)
370 370 self.changelog = changelog(self.opener)
371 371
372 372 self.ui.status("discarding dirstate\n")
373 373 node = self.changelog.tip()
374 374 f.sort()
375 375
376 376 self.dirstate.setparents(node)
377 377 self.dirstate.update(f, 'i')
378 378
379 379 else:
380 380 self.ui.warn("no undo information available\n")
381 381
382 382 def lock(self, wait = 1):
383 383 try:
384 384 return lock.lock(self.join("lock"), 0)
385 385 except lock.LockHeld, inst:
386 386 if wait:
387 387 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
388 388 return lock.lock(self.join("lock"), wait)
389 389 raise inst
390 390
391 391 def rawcommit(self, files, text, user, date, p1=None, p2=None):
392 392 p1 = p1 or self.dirstate.parents()[0] or nullid
393 393 p2 = p2 or self.dirstate.parents()[1] or nullid
394 394 pchange = self.changelog.read(p1)
395 395 pmmap = self.manifest.read(pchange[0])
396 396 tr = self.transaction()
397 397 mmap = {}
398 398 linkrev = self.changelog.count()
399 399 for f in files:
400 400 try:
401 401 t = file(f).read()
402 402 except IOError:
403 403 self.ui.warn("Read file %s error, skipped\n" % f)
404 404 continue
405 405 r = self.file(f)
406 406 # FIXME - need to find both parents properly
407 407 prev = pmmap.get(f, nullid)
408 408 mmap[f] = r.add(t, tr, linkrev, prev)
409 409
410 410 mnode = self.manifest.add(mmap, tr, linkrev, pchange[0])
411 411 n = self.changelog.add(mnode, files, text, tr, p1, p2, user ,date, )
412 412 tr.close()
413 413 self.dirstate.setparents(p1, p2)
414 414 self.dirstate.clear()
415 415 self.dirstate.update(mmap.keys(), "n")
416 416
417 417 def commit(self, files = None, text = ""):
418 418 commit = []
419 419 remove = []
420 420 if files:
421 421 for f in files:
422 422 s = self.dirstate.state(f)
423 423 if s in 'cai':
424 424 commit.append(f)
425 425 elif s == 'r':
426 426 remove.append(f)
427 427 else:
428 428 self.warn("%s not tracked!\n")
429 429 else:
430 430 (c, a, d, u) = self.diffdir(self.root)
431 431 commit = c + a
432 432 remove = d
433 433
434 434 if not commit and not remove:
435 435 self.ui.status("nothing changed\n")
436 436 return
437 437
438 438 p1, p2 = self.dirstate.parents()
439 439 c1 = self.changelog.read(p1)
440 440 c2 = self.changelog.read(p2)
441 441 m1 = self.manifest.read(c1[0])
442 442 m2 = self.manifest.read(c2[0])
443 443 lock = self.lock()
444 444 tr = self.transaction()
445 445
446 446 # check in files
447 447 new = {}
448 448 linkrev = self.changelog.count()
449 449 commit.sort()
450 450 for f in commit:
451 451 self.ui.note(f + "\n")
452 452 try:
453 453 t = file(f).read()
454 454 except IOError:
455 455 self.warn("trouble committing %s!\n" % f)
456 456 raise
457 457
458 458 r = self.file(f)
459 459 fp1 = m1.get(f, nullid)
460 460 fp2 = m2.get(f, nullid)
461 461 new[f] = r.add(t, tr, linkrev, fp1, fp2)
462 462
463 463 # update manifest
464 464 m1.update(new)
465 465 for f in remove: del m1[f]
466 466 mn = self.manifest.add(m1, tr, linkrev, c1[0], c2[0])
467 467
468 468 # add changeset
469 469 new = new.keys()
470 470 new.sort()
471 471
472 472 edittext = text + "\n" + "HG: manifest hash %s\n" % hex(mn)
473 473 edittext += "".join(["HG: changed %s\n" % f for f in new])
474 474 edittext += "".join(["HG: removed %s\n" % f for f in remove])
475 475 edittext = self.ui.edit(edittext)
476 476
477 477 n = self.changelog.add(mn, new, edittext, tr, p1, p2)
478 478 tr.close()
479 479
480 480 self.dirstate.setparents(n)
481 481 self.dirstate.update(new, "n")
482 482 self.dirstate.forget(remove)
483 483
484 484 def checkout(self, node):
485 485 # checkout is really dumb at the moment
486 486 # it ought to basically merge
487 487 change = self.changelog.read(node)
488 488 l = self.manifest.read(change[0]).items()
489 489 l.sort()
490 490
491 491 for f,n in l:
492 492 if f[0] == "/": continue
493 493 self.ui.note(f, "\n")
494 494 t = self.file(f).revision(n)
495 495 try:
496 496 file(f, "w").write(t)
497 497 except IOError:
498 498 os.makedirs(os.path.dirname(f))
499 499 file(f, "w").write(t)
500 500
501 501 self.dirstate.setparents(node)
502 502 self.dirstate.clear()
503 503 self.dirstate.update([f for f,n in l], "n")
504 504
505 505 def diffdir(self, path, changeset = None):
506 506 changed = []
507 507 added = []
508 508 unknown = []
509 509 mf = {}
510 510
511 511 if changeset:
512 512 change = self.changelog.read(changeset)
513 513 mf = self.manifest.read(change[0])
514 514 dc = dict.fromkeys(mf)
515 515 else:
516 516 changeset = self.dirstate.parents()[0]
517 517 change = self.changelog.read(changeset)
518 518 mf = self.manifest.read(change[0])
519 519 dc = self.dirstate.copy()
520 520
521 521 def fcmp(fn):
522 522 t1 = file(os.path.join(self.root, fn)).read()
523 523 t2 = self.file(fn).revision(mf[fn])
524 524 return cmp(t1, t2)
525 525
526 526 for dir, subdirs, files in os.walk(self.root):
527 527 d = dir[len(self.root)+1:]
528 528 if ".hg" in subdirs: subdirs.remove(".hg")
529 529
530 530 for f in files:
531 531 fn = os.path.join(d, f)
532 532 try: s = os.stat(os.path.join(self.root, fn))
533 533 except: continue
534 534 if fn in dc:
535 535 c = dc[fn]
536 536 del dc[fn]
537 537 if not c:
538 538 if fcmp(fn):
539 539 changed.append(fn)
540 540 elif c[0] == 'i':
541 541 if fn not in mf:
542 542 added.append(fn)
543 543 elif fcmp(fn):
544 544 changed.append(fn)
545 545 elif c[0] == 'm':
546 546 changed.append(fn)
547 547 elif c[0] == 'a':
548 548 added.append(fn)
549 549 elif c[0] == 'r':
550 550 unknown.append(fn)
551 551 elif c[2] != s.st_size:
552 552 changed.append(fn)
553 553 elif c[1] != s.st_mode or c[3] != s.st_mtime:
554 554 if fcmp(fn):
555 555 changed.append(fn)
556 556 else:
557 557 if self.ignore(fn): continue
558 558 unknown.append(fn)
559 559
560 560 deleted = dc.keys()
561 561 deleted.sort()
562 562
563 563 return (changed, added, deleted, unknown)
564 564
565 565 def diffrevs(self, node1, node2):
566 566 changed, added = [], []
567 567
568 568 change = self.changelog.read(node1)
569 569 mf1 = self.manifest.read(change[0])
570 570 change = self.changelog.read(node2)
571 571 mf2 = self.manifest.read(change[0])
572 572
573 573 for fn in mf2:
574 574 if mf1.has_key(fn):
575 575 if mf1[fn] != mf2[fn]:
576 576 changed.append(fn)
577 577 del mf1[fn]
578 578 else:
579 579 added.append(fn)
580 580
581 581 deleted = mf1.keys()
582 582 deleted.sort()
583 583
584 584 return (changed, added, deleted)
585 585
586 586 def add(self, list):
587 587 for f in list:
588 588 p = os.path.join(self.root, f)
589 589 if not os.path.isfile(p):
590 590 self.ui.warn("%s does not exist!\n" % f)
591 591 elif self.dirstate.state(f) == 'n':
592 592 self.ui.warn("%s already tracked!\n" % f)
593 593 else:
594 594 self.dirstate.update([f], "a")
595 595
596 596 def forget(self, list):
597 597 for f in list:
598 598 if self.dirstate.state(f) not in 'ai':
599 599 self.ui.warn("%s not added!\n" % f)
600 600 else:
601 601 self.dirstate.forget([f])
602 602
603 603 def remove(self, list):
604 604 for f in list:
605 605 p = os.path.join(self.root, f)
606 606 if os.path.isfile(p):
607 607 self.ui.warn("%s still exists!\n" % f)
608 608 elif f not in self.dirstate:
609 609 self.ui.warn("%s not tracked!\n" % f)
610 610 else:
611 611 self.dirstate.update([f], "r")
612 612
613 613 def heads(self):
614 614 return self.changelog.heads()
615 615
616 616 def branches(self, nodes):
617 617 if not nodes: nodes = [self.changelog.tip()]
618 618 b = []
619 619 for n in nodes:
620 620 t = n
621 621 while n:
622 622 p = self.changelog.parents(n)
623 623 if p[1] != nullid or p[0] == nullid:
624 624 b.append((t, n, p[0], p[1]))
625 625 break
626 626 n = p[0]
627 627 return b
628 628
629 629 def between(self, pairs):
630 630 r = []
631 631
632 632 for top, bottom in pairs:
633 633 n, l, i = top, [], 0
634 634 f = 1
635 635
636 636 while n != bottom:
637 637 p = self.changelog.parents(n)[0]
638 638 if i == f:
639 639 l.append(n)
640 640 f = f * 2
641 641 n = p
642 642 i += 1
643 643
644 644 r.append(l)
645 645
646 646 return r
647 647
648 648 def newer(self, nodes):
649 649 m = {}
650 650 nl = []
651 651 pm = {}
652 652 cl = self.changelog
653 653 t = l = cl.count()
654 654
655 655 # find the lowest numbered node
656 656 for n in nodes:
657 657 l = min(l, cl.rev(n))
658 658 m[n] = 1
659 659
660 660 for i in xrange(l, t):
661 661 n = cl.node(i)
662 662 if n in m: # explicitly listed
663 663 pm[n] = 1
664 664 nl.append(n)
665 665 continue
666 666 for p in cl.parents(n):
667 667 if p in pm: # parent listed
668 668 pm[n] = 1
669 669 nl.append(n)
670 670 break
671 671
672 672 return nl
673 673
674 674 def getchangegroup(self, remote):
675 675 m = self.changelog.nodemap
676 676 search = []
677 677 fetch = []
678 678 seen = {}
679 679 seenbranch = {}
680 680
681 681 # if we have an empty repo, fetch everything
682 682 if self.changelog.tip() == nullid:
683 683 self.ui.status("requesting all changes\n")
684 684 return remote.changegroup([nullid])
685 685
686 686 # otherwise, assume we're closer to the tip than the root
687 687 self.ui.status("searching for changes\n")
688 688 heads = remote.heads()
689 689 unknown = []
690 690 for h in heads:
691 691 if h not in m:
692 692 unknown.append(h)
693 693
694 694 if not unknown:
695 695 self.ui.status("nothing to do!\n")
696 696 return None
697 697
698 698 unknown = remote.branches(unknown)
699 699 while unknown:
700 700 n = unknown.pop(0)
701 701 seen[n[0]] = 1
702 702
703 703 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
704 704 if n == nullid: break
705 705 if n in seenbranch:
706 706 self.ui.debug("branch already found\n")
707 707 continue
708 708 if n[1] and n[1] in m: # do we know the base?
709 709 self.ui.debug("found incomplete branch %s:%s\n"
710 710 % (short(n[0]), short(n[1])))
711 711 search.append(n) # schedule branch range for scanning
712 712 seenbranch[n] = 1
713 713 else:
714 714 if n[2] in m and n[3] in m:
715 715 if n[1] not in fetch:
716 716 self.ui.debug("found new changeset %s\n" %
717 717 short(n[1]))
718 718 fetch.append(n[1]) # earliest unknown
719 719 continue
720 720
721 721 r = []
722 722 for a in n[2:4]:
723 723 if a not in seen: r.append(a)
724 724
725 725 if r:
726 726 self.ui.debug("requesting %s\n" %
727 727 " ".join(map(short, r)))
728 728 for b in remote.branches(r):
729 729 self.ui.debug("received %s:%s\n" %
730 730 (short(b[0]), short(b[1])))
731 731 if b[0] not in m and b[0] not in seen:
732 732 unknown.append(b)
733 733
734 734 while search:
735 735 n = search.pop(0)
736 736 l = remote.between([(n[0], n[1])])[0]
737 737 p = n[0]
738 738 f = 1
739 739 for i in l + [n[1]]:
740 740 if i in m:
741 741 if f <= 2:
742 742 self.ui.debug("found new branch changeset %s\n" %
743 743 short(p))
744 744 fetch.append(p)
745 745 else:
746 746 self.ui.debug("narrowed branch search to %s:%s\n"
747 747 % (short(p), short(i)))
748 748 search.append((p, i))
749 749 break
750 750 p, f = i, f * 2
751 751
752 752 for f in fetch:
753 753 if f in m:
754 754 raise "already have", short(f[:4])
755 755
756 756 self.ui.note("adding new changesets starting at " +
757 757 " ".join([short(f) for f in fetch]) + "\n")
758 758
759 759 return remote.changegroup(fetch)
760 760
761 761 def changegroup(self, basenodes):
762 762 nodes = self.newer(basenodes)
763 763
764 764 # construct the link map
765 765 linkmap = {}
766 766 for n in nodes:
767 767 linkmap[self.changelog.rev(n)] = n
768 768
769 769 # construct a list of all changed files
770 770 changed = {}
771 771 for n in nodes:
772 772 c = self.changelog.read(n)
773 773 for f in c[3]:
774 774 changed[f] = 1
775 775 changed = changed.keys()
776 776 changed.sort()
777 777
778 778 # the changegroup is changesets + manifests + all file revs
779 779 revs = [ self.changelog.rev(n) for n in nodes ]
780 780
781 781 for y in self.changelog.group(linkmap): yield y
782 782 for y in self.manifest.group(linkmap): yield y
783 783 for f in changed:
784 784 yield struct.pack(">l", len(f) + 4) + f
785 785 g = self.file(f).group(linkmap)
786 786 for y in g:
787 787 yield y
788 788
789 789 def addchangegroup(self, generator):
790 790
791 791 class genread:
792 792 def __init__(self, generator):
793 793 self.g = generator
794 794 self.buf = ""
795 795 def read(self, l):
796 796 while l > len(self.buf):
797 797 try:
798 798 self.buf += self.g.next()
799 799 except StopIteration:
800 800 break
801 801 d, self.buf = self.buf[:l], self.buf[l:]
802 802 return d
803 803
804 804 def getchunk():
805 805 d = source.read(4)
806 806 if not d: return ""
807 807 l = struct.unpack(">l", d)[0]
808 808 if l <= 4: return ""
809 809 return source.read(l - 4)
810 810
811 811 def getgroup():
812 812 while 1:
813 813 c = getchunk()
814 814 if not c: break
815 815 yield c
816 816
817 817 def csmap(x):
818 818 self.ui.debug("add changeset %s\n" % short(x))
819 819 return self.changelog.count()
820 820
821 821 def revmap(x):
822 822 return self.changelog.rev(x)
823 823
824 824 if not generator: return
825 825 changesets = files = revisions = 0
826 826
827 827 source = genread(generator)
828 828 lock = self.lock()
829 829 tr = self.transaction()
830 830
831 831 # pull off the changeset group
832 832 self.ui.status("adding changesets\n")
833 833 co = self.changelog.tip()
834 834 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
835 835 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
836 836
837 837 # pull off the manifest group
838 838 self.ui.status("adding manifests\n")
839 839 mm = self.manifest.tip()
840 840 mo = self.manifest.addgroup(getgroup(), revmap, tr)
841 841
842 842 # process the files
843 843 self.ui.status("adding file revisions\n")
844 844 while 1:
845 845 f = getchunk()
846 846 if not f: break
847 847 self.ui.debug("adding %s revisions\n" % f)
848 848 fl = self.file(f)
849 849 o = fl.tip()
850 850 n = fl.addgroup(getgroup(), revmap, tr)
851 851 revisions += fl.rev(n) - fl.rev(o)
852 852 files += 1
853 853
854 854 self.ui.status(("modified %d files, added %d changesets" +
855 855 " and %d new revisions\n")
856 856 % (files, changesets, revisions))
857 857
858 858 tr.close()
859 859 return
860 860
861 def merge(self, generator):
862 changesets = files = revisions = 0
863
864 self.lock()
865 class genread:
866 def __init__(self, generator):
867 self.g = generator
868 self.buf = ""
869 def read(self, l):
870 while l > len(self.buf):
871 try:
872 self.buf += self.g.next()
873 except StopIteration:
874 break
875 d, self.buf = self.buf[:l], self.buf[l:]
876 return d
877
878 if not generator: return
879 source = genread(generator)
880
881 def getchunk():
882 d = source.read(4)
883 if not d: return ""
884 l = struct.unpack(">l", d)[0]
885 if l <= 4: return ""
886 return source.read(l - 4)
887
888 def getgroup():
889 while 1:
890 c = getchunk()
891 if not c: break
892 yield c
893
894 tr = self.transaction()
895 simple = True
896 need = {}
897
898 self.ui.status("adding changesets\n")
899 # pull off the changeset group
900 def report(x):
901 self.ui.debug("add changeset %s\n" % short(x))
902 return self.changelog.count()
903
904 co = self.changelog.tip()
905 cn = self.changelog.addgroup(getgroup(), report, tr)
906
907 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
908
909 self.ui.status("adding manifests\n")
910 # pull off the manifest group
911 mm = self.manifest.tip()
912 mo = self.manifest.addgroup(getgroup(),
913 lambda x: self.changelog.rev(x), tr)
914
915 # do we need a resolve?
916 if self.changelog.ancestor(co, cn) != co:
917 simple = False
918 resolverev = self.changelog.count()
919
920 # resolve the manifest to determine which files
921 # we care about merging
922 self.ui.status("resolving manifests\n")
923 ma = self.manifest.ancestor(mm, mo)
924 omap = self.manifest.read(mo) # other
925 amap = self.manifest.read(ma) # ancestor
926 mmap = self.manifest.read(mm) # mine
927 nmap = {}
928
929 self.ui.debug(" ancestor %s local %s remote %s\n" %
930 (short(ma), short(mm), short(mo)))
931
932 for f, mid in mmap.iteritems():
933 if f in omap:
934 if mid != omap[f]:
935 self.ui.debug(" %s versions differ, do resolve\n" % f)
936 need[f] = mid # use merged version or local version
937 else:
938 nmap[f] = mid # keep ours
939 del omap[f]
940 elif f in amap:
941 if mid != amap[f]:
942 r = self.ui.prompt(
943 (" local changed %s which remote deleted\n" % f) +
944 "(k)eep or (d)elete?", "[kd]", "k")
945 if r == "k": nmap[f] = mid
946 else:
947 self.ui.debug("other deleted %s\n" % f)
948 pass # other deleted it
949 else:
950 self.ui.debug("local created %s\n" %f)
951 nmap[f] = mid # we created it
952
953 del mmap
954
955 for f, oid in omap.iteritems():
956 if f in amap:
957 if oid != amap[f]:
958 r = self.ui.prompt(
959 ("remote changed %s which local deleted\n" % f) +
960 "(k)eep or (d)elete?", "[kd]", "k")
961 if r == "k": nmap[f] = oid
962 else:
963 pass # probably safe
964 else:
965 self.ui.debug("remote created %s, do resolve\n" % f)
966 need[f] = oid
967
968 del omap
969 del amap
970
971 new = need.keys()
972 new.sort()
973
974 # process the files
975 self.ui.status("adding files\n")
976 while 1:
977 f = getchunk()
978 if not f: break
979 self.ui.debug("adding %s revisions\n" % f)
980 fl = self.file(f)
981 o = fl.tip()
982 n = fl.addgroup(getgroup(), lambda x: self.changelog.rev(x), tr)
983 revisions += fl.rev(n) - fl.rev(o)
984 files += 1
985 if f in need:
986 del need[f]
987 # manifest resolve determined we need to merge the tips
988 nmap[f] = self.merge3(fl, f, o, n, tr, resolverev)
989
990 if need:
991 # we need to do trivial merges on local files
992 for f in new:
993 if f not in need: continue
994 fl = self.file(f)
995 nmap[f] = self.merge3(fl, f, need[f], fl.tip(), tr, resolverev)
996 revisions += 1
997
998 # For simple merges, we don't need to resolve manifests or changesets
999 if simple:
1000 self.ui.debug("simple merge, skipping resolve\n")
1001 self.ui.status(("modified %d files, added %d changesets" +
1002 " and %d new revisions\n")
1003 % (files, changesets, revisions))
1004 tr.close()
861 def resolve(self, node):
862 pl = self.dirstate.parents()
863 if pl[1] != nullid:
864 self.ui.warn("last merge not committed")
1005 865 return
1006 866
1007 node = self.manifest.add(nmap, tr, resolverev, mm, mo)
1008 revisions += 1
867 p1, p2 = pl[0], node
868 m1n = self.changelog.read(p1)[0]
869 m2n = self.changelog.read(p2)[0]
870 man = self.manifest.ancestor(m1n, m2n)
871 m1 = self.manifest.read(m1n)
872 m2 = self.manifest.read(m2n)
873 ma = self.manifest.read(man)
874
875 (c, a, d, u) = self.diffdir(self.root)
876
877 # resolve the manifest to determine which files
878 # we care about merging
879 self.ui.status("resolving manifests\n")
880 self.ui.debug(" ancestor %s local %s remote %s\n" %
881 (short(man), short(m1n), short(m2n)))
882
883 merge = {}
884 get = {}
885 remove = []
1009 886
1010 # Now all files and manifests are merged, we add the changed files
1011 # and manifest id to the changelog
1012 self.ui.status("committing merge changeset\n")
1013 if co == cn: cn = -1
887 # construct a working dir manifest
888 mw = m1.copy()
889 for f in a + c:
890 mw[f] = nullid
891 for f in d:
892 del mw[f]
893
894 for f, n in mw.iteritems():
895 if f in m2:
896 if n != m2[f]:
897 self.ui.debug(" %s versions differ, do resolve\n" % f)
898 merge[f] = (m1.get(f, nullid), m2[f])
899 del m2[f]
900 elif f in ma:
901 if n != ma[f]:
902 r = self.ui.prompt(
903 (" local changed %s which remote deleted\n" % f) +
904 "(k)eep or (d)elete?", "[kd]", "k")
905 if r == "d":
906 remove.append(f)
907 else:
908 self.ui.debug("other deleted %s\n" % f)
909 pass # other deleted it
910 else:
911 self.ui.debug("local created %s\n" %f)
1014 912
1015 edittext = "\nHG: merge resolve\n" + \
1016 "HG: manifest hash %s\n" % hex(node) + \
1017 "".join(["HG: changed %s\n" % f for f in new])
1018 edittext = self.ui.edit(edittext)
1019 n = self.changelog.add(node, new, edittext, tr, co, cn)
1020 revisions += 1
913 for f, n in m2.iteritems():
914 if f in ma:
915 if n != ma[f]:
916 r = self.ui.prompt(
917 ("remote changed %s which local deleted\n" % f) +
918 "(k)eep or (d)elete?", "[kd]", "k")
919 if r == "d": remove.append(f)
920 else:
921 pass # probably safe
922 else:
923 self.ui.debug("remote created %s, do resolve\n" % f)
924 get[f] = n
925
926 del mw, m1, m2, ma
927
928 self.dirstate.setparents(p1, p2)
1021 929
1022 self.ui.status("added %d changesets, %d files, and %d new revisions\n"
1023 % (changesets, files, revisions))
930 # get the files we don't need to change
931 files = get.keys()
932 files.sort()
933 for f in files:
934 if f[0] == "/": continue
935 self.ui.note(f, "\n")
936 t = self.file(f).revision(get[f])
937 try:
938 file(f, "w").write(t)
939 except IOError:
940 os.makedirs(os.path.dirname(f))
941 file(f, "w").write(t)
942
943 # we have to remember what files we needed to get/change
944 # because any file that's different from either one of its
945 # parents must be in the changeset
946 self.dirstate.update(files, 'm')
1024 947
1025 tr.close()
948 # merge the tricky bits
949 files = merge.keys()
950 files.sort()
951 for f in files:
952 m, o = merge[f]
953 self.merge3(f, m, o)
1026 954
1027 def merge3(self, fl, fn, my, other, transaction, link):
1028 """perform a 3-way merge and append the result"""
955 # same here
956 self.dirstate.update(files, 'm')
957
958 for f in remove:
959 self.ui.note("removing %s\n" % f)
960 #os.unlink(f)
961 self.dirstate.update(remove, 'r')
962
963 def merge3(self, fn, my, other):
964 """perform a 3-way merge in the working directory"""
1029 965
1030 966 def temp(prefix, node):
1031 967 pre = "%s~%s." % (os.path.basename(fn), prefix)
1032 968 (fd, name) = tempfile.mkstemp("", pre)
1033 969 f = os.fdopen(fd, "w")
1034 970 f.write(fl.revision(node))
1035 971 f.close()
1036 972 return name
1037 973
974 fl = self.file(fn)
1038 975 base = fl.ancestor(my, other)
1039 self.ui.note("resolving %s\n" % fn)
1040 self.ui.debug("local %s remote %s ancestor %s\n" %
1041 (short(my), short(other), short(base)))
1042
1043 if my == base:
1044 text = fl.revision(other)
1045 else:
1046 a = temp("local", my)
1047 b = temp("remote", other)
1048 c = temp("parent", base)
976 a = fn
977 b = temp("other", other)
978 c = temp("base", base)
1049 979
1050 cmd = os.environ["HGMERGE"]
1051 self.ui.debug("invoking merge with %s\n" % cmd)
1052 r = os.system("%s %s %s %s %s" % (cmd, a, b, c, fn))
1053 if r:
1054 raise "Merge failed!"
980 self.ui.note("resolving %s\n" % fn)
981 self.ui.debug("file %s: other %s ancestor %s\n" %
982 (fn, short(other), short(base)))
1055 983
1056 text = open(a).read()
1057 os.unlink(a)
1058 os.unlink(b)
1059 os.unlink(c)
1060
1061 return fl.add(text, transaction, link, my, other)
984 cmd = os.environ["HGMERGE"]
985 r = os.system("%s %s %s %s %s" % (cmd, a, b, c, fn))
986 if r:
987 self.ui.warn("merging %s failed!\n" % f)
988
989 os.unlink(b)
990 os.unlink(c)
1062 991
1063 992 class remoterepository:
1064 993 def __init__(self, ui, path):
1065 994 self.url = path
1066 995 self.ui = ui
1067 996
1068 997 def do_cmd(self, cmd, **args):
1069 998 self.ui.debug("sending %s command\n" % cmd)
1070 999 q = {"cmd": cmd}
1071 1000 q.update(args)
1072 1001 qs = urllib.urlencode(q)
1073 1002 cu = "%s?%s" % (self.url, qs)
1074 1003 return urllib.urlopen(cu)
1075 1004
1076 1005 def heads(self):
1077 1006 d = self.do_cmd("heads").read()
1078 1007 try:
1079 1008 return map(bin, d[:-1].split(" "))
1080 1009 except:
1081 1010 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1082 1011 raise
1083 1012
1084 1013 def branches(self, nodes):
1085 1014 n = " ".join(map(hex, nodes))
1086 1015 d = self.do_cmd("branches", nodes=n).read()
1087 1016 try:
1088 1017 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1089 1018 return br
1090 1019 except:
1091 1020 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1092 1021 raise
1093 1022
1094 1023 def between(self, pairs):
1095 1024 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1096 1025 d = self.do_cmd("between", pairs=n).read()
1097 1026 try:
1098 1027 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1099 1028 return p
1100 1029 except:
1101 1030 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1102 1031 raise
1103 1032
1104 1033 def changegroup(self, nodes):
1105 1034 n = " ".join(map(hex, nodes))
1106 1035 zd = zlib.decompressobj()
1107 1036 f = self.do_cmd("changegroup", roots=n)
1108 1037 bytes = 0
1109 1038 while 1:
1110 1039 d = f.read(4096)
1111 1040 bytes += len(d)
1112 1041 if not d:
1113 1042 yield zd.flush()
1114 1043 break
1115 1044 yield zd.decompress(d)
1116 1045 self.ui.note("%d bytes of data transfered\n" % bytes)
1117 1046
1118 1047 def repository(ui, path=None, create=0):
1119 1048 if path and path[:7] == "http://":
1120 1049 return remoterepository(ui, path)
1121 1050 if path and path[:5] == "hg://":
1122 1051 return remoterepository(ui, path.replace("hg://", "http://"))
1123 1052 if path and path[:11] == "old-http://":
1124 1053 return localrepository(ui, path.replace("old-http://", "http://"))
1125 1054 else:
1126 1055 return localrepository(ui, path, create)
1127 1056
1128 1057 class httprangereader:
1129 1058 def __init__(self, url):
1130 1059 self.url = url
1131 1060 self.pos = 0
1132 1061 def seek(self, pos):
1133 1062 self.pos = pos
1134 1063 def read(self, bytes=None):
1135 1064 opener = urllib2.build_opener(byterange.HTTPRangeHandler())
1136 1065 urllib2.install_opener(opener)
1137 1066 req = urllib2.Request(self.url)
1138 1067 end = ''
1139 1068 if bytes: end = self.pos + bytes
1140 1069 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
1141 1070 f = urllib2.urlopen(req)
1142 1071 return f.read()
General Comments 0
You need to be logged in to leave comments. Login now