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