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