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