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