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