##// END OF EJS Templates
Fix dodiff/changes...
mpm@selenic.com -
r561:cdddf465 default
parent child Browse files
Show More
@@ -1,945 +1,945 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, util
9 import fancyopts, ui, hg, util
10 from demandload import *
10 from demandload import *
11 demandload(globals(), "mdiff time hgweb traceback random signal errno version")
11 demandload(globals(), "mdiff time hgweb traceback random signal errno version")
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] != "/": t += "/"
19 if t and t[-1] != "/": t += "/"
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([util.pconvert(p)], files)
26 return filterfiles([util.pconvert(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 [ util.pconvert(os.path.normpath(os.path.join(p, x)))
32 return [ util.pconvert(os.path.normpath(os.path.join(p, x)))
33 for x in args ]
33 for x in args ]
34 return args
34 return args
35
35
36 def dodiff(ui, repo, files = None, node1 = None, node2 = None):
36 def dodiff(ui, repo, files = None, node1 = None, node2 = None):
37 def date(c):
37 def date(c):
38 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
38 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
39
39
40 (c, a, d, u) = repo.changes(None, node1, files)
40 (c, a, d, u) = repo.changes(node1, node2, files)
41 if files:
41 if files:
42 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
42 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
43
43
44 if not c and not a and not d:
44 if not c and not a and not d:
45 return
45 return
46
46
47 if node2:
47 if node2:
48 change = repo.changelog.read(node2)
48 change = repo.changelog.read(node2)
49 mmap2 = repo.manifest.read(change[0])
49 mmap2 = repo.manifest.read(change[0])
50 def read(f): return repo.file(f).read(mmap2[f])
50 def read(f): return repo.file(f).read(mmap2[f])
51 date2 = date(change)
51 date2 = date(change)
52 else:
52 else:
53 date2 = time.asctime()
53 date2 = time.asctime()
54 if not node1:
54 if not node1:
55 node1 = repo.dirstate.parents()[0]
55 node1 = repo.dirstate.parents()[0]
56 def read(f): return repo.wfile(f).read()
56 def read(f): return repo.wfile(f).read()
57
57
58 if ui.quiet:
58 if ui.quiet:
59 r = None
59 r = None
60 else:
60 else:
61 hexfunc = ui.verbose and hg.hex or hg.short
61 hexfunc = ui.verbose and hg.hex or hg.short
62 r = [hexfunc(node) for node in [node1, node2] if node]
62 r = [hexfunc(node) for node in [node1, node2] if node]
63
63
64 change = repo.changelog.read(node1)
64 change = repo.changelog.read(node1)
65 mmap = repo.manifest.read(change[0])
65 mmap = repo.manifest.read(change[0])
66 date1 = date(change)
66 date1 = date(change)
67
67
68 for f in c:
68 for f in c:
69 to = None
69 to = None
70 if f in mmap:
70 if f in mmap:
71 to = repo.file(f).read(mmap[f])
71 to = repo.file(f).read(mmap[f])
72 tn = read(f)
72 tn = read(f)
73 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f, r))
73 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f, r))
74 for f in a:
74 for f in a:
75 to = None
75 to = None
76 tn = read(f)
76 tn = read(f)
77 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f, r))
77 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f, r))
78 for f in d:
78 for f in d:
79 to = repo.file(f).read(mmap[f])
79 to = repo.file(f).read(mmap[f])
80 tn = None
80 tn = None
81 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f, r))
81 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f, r))
82
82
83 def show_changeset(ui, repo, rev=0, changenode=None, filelog=None):
83 def show_changeset(ui, repo, rev=0, changenode=None, filelog=None):
84 """show a single changeset or file revision"""
84 """show a single changeset or file revision"""
85 changelog = repo.changelog
85 changelog = repo.changelog
86 if filelog:
86 if filelog:
87 log = filelog
87 log = filelog
88 filerev = rev
88 filerev = rev
89 node = filenode = filelog.node(filerev)
89 node = filenode = filelog.node(filerev)
90 changerev = filelog.linkrev(filenode)
90 changerev = filelog.linkrev(filenode)
91 changenode = changenode or changelog.node(changerev)
91 changenode = changenode or changelog.node(changerev)
92 else:
92 else:
93 log = changelog
93 log = changelog
94 changerev = rev
94 changerev = rev
95 if changenode is None:
95 if changenode is None:
96 changenode = changelog.node(changerev)
96 changenode = changelog.node(changerev)
97 elif not changerev:
97 elif not changerev:
98 rev = changerev = changelog.rev(changenode)
98 rev = changerev = changelog.rev(changenode)
99 node = changenode
99 node = changenode
100
100
101 if ui.quiet:
101 if ui.quiet:
102 ui.write("%d:%s\n" % (rev, hg.hex(node)))
102 ui.write("%d:%s\n" % (rev, hg.hex(node)))
103 return
103 return
104
104
105 changes = changelog.read(changenode)
105 changes = changelog.read(changenode)
106
106
107 parents = [(log.rev(parent), hg.hex(parent))
107 parents = [(log.rev(parent), hg.hex(parent))
108 for parent in log.parents(node)
108 for parent in log.parents(node)
109 if ui.debugflag or parent != hg.nullid]
109 if ui.debugflag or parent != hg.nullid]
110 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
110 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
111 parents = []
111 parents = []
112
112
113 if filelog:
113 if filelog:
114 ui.write("revision: %d:%s\n" % (filerev, hg.hex(filenode)))
114 ui.write("revision: %d:%s\n" % (filerev, hg.hex(filenode)))
115 for parent in parents:
115 for parent in parents:
116 ui.write("parent: %d:%s\n" % parent)
116 ui.write("parent: %d:%s\n" % parent)
117 ui.status("changeset: %d:%s\n" % (changerev, hg.hex(changenode)))
117 ui.status("changeset: %d:%s\n" % (changerev, hg.hex(changenode)))
118 else:
118 else:
119 ui.write("changeset: %d:%s\n" % (changerev, hg.hex(changenode)))
119 ui.write("changeset: %d:%s\n" % (changerev, hg.hex(changenode)))
120 for tag in repo.nodetags(changenode):
120 for tag in repo.nodetags(changenode):
121 ui.status("tag: %s\n" % tag)
121 ui.status("tag: %s\n" % tag)
122 for parent in parents:
122 for parent in parents:
123 ui.write("parent: %d:%s\n" % parent)
123 ui.write("parent: %d:%s\n" % parent)
124 ui.note("manifest: %d:%s\n" % (repo.manifest.rev(changes[0]),
124 ui.note("manifest: %d:%s\n" % (repo.manifest.rev(changes[0]),
125 hg.hex(changes[0])))
125 hg.hex(changes[0])))
126 ui.status("user: %s\n" % changes[1])
126 ui.status("user: %s\n" % changes[1])
127 ui.status("date: %s\n" % time.asctime(
127 ui.status("date: %s\n" % time.asctime(
128 time.localtime(float(changes[2].split(' ')[0]))))
128 time.localtime(float(changes[2].split(' ')[0]))))
129 if ui.debugflag:
129 if ui.debugflag:
130 files = repo.changes(changelog.parents(changenode)[0], changenode)
130 files = repo.changes(changelog.parents(changenode)[0], changenode)
131 for key, value in zip(["files:", "files+:", "files-:"], files):
131 for key, value in zip(["files:", "files+:", "files-:"], files):
132 if value:
132 if value:
133 ui.note("%-12s %s\n" % (key, " ".join(value)))
133 ui.note("%-12s %s\n" % (key, " ".join(value)))
134 else:
134 else:
135 ui.note("files: %s\n" % " ".join(changes[3]))
135 ui.note("files: %s\n" % " ".join(changes[3]))
136 description = changes[4].strip()
136 description = changes[4].strip()
137 if description:
137 if description:
138 if ui.verbose:
138 if ui.verbose:
139 ui.status("description:\n")
139 ui.status("description:\n")
140 ui.status(description)
140 ui.status(description)
141 ui.status("\n\n")
141 ui.status("\n\n")
142 else:
142 else:
143 ui.status("summary: %s\n" % description.splitlines()[0])
143 ui.status("summary: %s\n" % description.splitlines()[0])
144 ui.status("\n")
144 ui.status("\n")
145
145
146 def show_version(ui):
146 def show_version(ui):
147 """output version and copyright information"""
147 """output version and copyright information"""
148 ui.write("Mercurial version %s\n" % version.get_version())
148 ui.write("Mercurial version %s\n" % version.get_version())
149 ui.status(
149 ui.status(
150 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
150 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
151 "This is free software; see the source for copying conditions. "
151 "This is free software; see the source for copying conditions. "
152 "There is NO\nwarranty; "
152 "There is NO\nwarranty; "
153 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
153 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
154 )
154 )
155
155
156 def help(ui, cmd=None):
156 def help(ui, cmd=None):
157 '''show help for a given command or all commands'''
157 '''show help for a given command or all commands'''
158 if cmd:
158 if cmd:
159 try:
159 try:
160 i = find(cmd)
160 i = find(cmd)
161 ui.write("%s\n\n" % i[2])
161 ui.write("%s\n\n" % i[2])
162
162
163 if i[1]:
163 if i[1]:
164 for s, l, d, c in i[1]:
164 for s, l, d, c in i[1]:
165 opt=' '
165 opt=' '
166 if s: opt = opt + '-' + s + ' '
166 if s: opt = opt + '-' + s + ' '
167 if l: opt = opt + '--' + l + ' '
167 if l: opt = opt + '--' + l + ' '
168 if d: opt = opt + '(' + str(d) + ')'
168 if d: opt = opt + '(' + str(d) + ')'
169 ui.write(opt, "\n")
169 ui.write(opt, "\n")
170 if c: ui.write(' %s\n' % c)
170 if c: ui.write(' %s\n' % c)
171 ui.write("\n")
171 ui.write("\n")
172
172
173 ui.write(i[0].__doc__, "\n")
173 ui.write(i[0].__doc__, "\n")
174 except UnknownCommand:
174 except UnknownCommand:
175 ui.warn("hg: unknown command %s\n" % cmd)
175 ui.warn("hg: unknown command %s\n" % cmd)
176 sys.exit(0)
176 sys.exit(0)
177 else:
177 else:
178 if not ui.quiet:
178 if not ui.quiet:
179 show_version(ui)
179 show_version(ui)
180 ui.write('\n')
180 ui.write('\n')
181 ui.write('hg commands:\n\n')
181 ui.write('hg commands:\n\n')
182
182
183 h = {}
183 h = {}
184 for c, e in table.items():
184 for c, e in table.items():
185 f = c.split("|")[0]
185 f = c.split("|")[0]
186 if f.startswith("debug"):
186 if f.startswith("debug"):
187 continue
187 continue
188 d = ""
188 d = ""
189 if e[0].__doc__:
189 if e[0].__doc__:
190 d = e[0].__doc__.splitlines(0)[0].rstrip()
190 d = e[0].__doc__.splitlines(0)[0].rstrip()
191 h[f] = d
191 h[f] = d
192
192
193 fns = h.keys()
193 fns = h.keys()
194 fns.sort()
194 fns.sort()
195 m = max(map(len, fns))
195 m = max(map(len, fns))
196 for f in fns:
196 for f in fns:
197 ui.write(' %-*s %s\n' % (m, f, h[f]))
197 ui.write(' %-*s %s\n' % (m, f, h[f]))
198
198
199 # Commands start here, listed alphabetically
199 # Commands start here, listed alphabetically
200
200
201 def add(ui, repo, file, *files):
201 def add(ui, repo, file, *files):
202 '''add the specified files on the next commit'''
202 '''add the specified files on the next commit'''
203 repo.add(relpath(repo, (file,) + files))
203 repo.add(relpath(repo, (file,) + files))
204
204
205 def addremove(ui, repo, *files):
205 def addremove(ui, repo, *files):
206 """add all new files, delete all missing files"""
206 """add all new files, delete all missing files"""
207 if files:
207 if files:
208 files = relpath(repo, files)
208 files = relpath(repo, files)
209 d = []
209 d = []
210 u = []
210 u = []
211 for f in files:
211 for f in files:
212 p = repo.wjoin(f)
212 p = repo.wjoin(f)
213 s = repo.dirstate.state(f)
213 s = repo.dirstate.state(f)
214 isfile = os.path.isfile(p)
214 isfile = os.path.isfile(p)
215 if s != 'r' and not isfile:
215 if s != 'r' and not isfile:
216 d.append(f)
216 d.append(f)
217 elif s not in 'nmai' and isfile:
217 elif s not in 'nmai' and isfile:
218 u.append(f)
218 u.append(f)
219 else:
219 else:
220 (c, a, d, u) = repo.changes(None, None)
220 (c, a, d, u) = repo.changes(None, None)
221 repo.add(u)
221 repo.add(u)
222 repo.remove(d)
222 repo.remove(d)
223
223
224 def annotate(u, repo, file, *files, **ops):
224 def annotate(u, repo, file, *files, **ops):
225 """show changeset information per file line"""
225 """show changeset information per file line"""
226 def getnode(rev):
226 def getnode(rev):
227 return hg.short(repo.changelog.node(rev))
227 return hg.short(repo.changelog.node(rev))
228
228
229 def getname(rev):
229 def getname(rev):
230 try:
230 try:
231 return bcache[rev]
231 return bcache[rev]
232 except KeyError:
232 except KeyError:
233 cl = repo.changelog.read(repo.changelog.node(rev))
233 cl = repo.changelog.read(repo.changelog.node(rev))
234 name = cl[1]
234 name = cl[1]
235 f = name.find('@')
235 f = name.find('@')
236 if f >= 0:
236 if f >= 0:
237 name = name[:f]
237 name = name[:f]
238 f = name.find('<')
238 f = name.find('<')
239 if f >= 0:
239 if f >= 0:
240 name = name[f+1:]
240 name = name[f+1:]
241 bcache[rev] = name
241 bcache[rev] = name
242 return name
242 return name
243
243
244 bcache = {}
244 bcache = {}
245 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
245 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
246 if not ops['user'] and not ops['changeset']:
246 if not ops['user'] and not ops['changeset']:
247 ops['number'] = 1
247 ops['number'] = 1
248
248
249 node = repo.dirstate.parents()[0]
249 node = repo.dirstate.parents()[0]
250 if ops['revision']:
250 if ops['revision']:
251 node = repo.changelog.lookup(ops['revision'])
251 node = repo.changelog.lookup(ops['revision'])
252 change = repo.changelog.read(node)
252 change = repo.changelog.read(node)
253 mmap = repo.manifest.read(change[0])
253 mmap = repo.manifest.read(change[0])
254 for f in relpath(repo, (file,) + files):
254 for f in relpath(repo, (file,) + files):
255 lines = repo.file(f).annotate(mmap[f])
255 lines = repo.file(f).annotate(mmap[f])
256 pieces = []
256 pieces = []
257
257
258 for o, f in opmap:
258 for o, f in opmap:
259 if ops[o]:
259 if ops[o]:
260 l = [ f(n) for n,t in lines ]
260 l = [ f(n) for n,t in lines ]
261 m = max(map(len, l))
261 m = max(map(len, l))
262 pieces.append([ "%*s" % (m, x) for x in l])
262 pieces.append([ "%*s" % (m, x) for x in l])
263
263
264 for p,l in zip(zip(*pieces), lines):
264 for p,l in zip(zip(*pieces), lines):
265 u.write(" ".join(p) + ": " + l[1])
265 u.write(" ".join(p) + ": " + l[1])
266
266
267 def cat(ui, repo, file, rev = []):
267 def cat(ui, repo, file, rev = []):
268 """output the latest or given revision of a file"""
268 """output the latest or given revision of a file"""
269 r = repo.file(relpath(repo, [file])[0])
269 r = repo.file(relpath(repo, [file])[0])
270 n = r.tip()
270 n = r.tip()
271 if rev: n = r.lookup(rev)
271 if rev: n = r.lookup(rev)
272 sys.stdout.write(r.read(n))
272 sys.stdout.write(r.read(n))
273
273
274 def clone(ui, source, dest = None, **opts):
274 def clone(ui, source, dest = None, **opts):
275 """make a copy of an existing repository"""
275 """make a copy of an existing repository"""
276 source = ui.expandpath(source)
276 source = ui.expandpath(source)
277
277
278 if dest is None:
278 if dest is None:
279 dest = os.path.basename(os.path.normpath(source))
279 dest = os.path.basename(os.path.normpath(source))
280
280
281 if os.path.exists(dest):
281 if os.path.exists(dest):
282 ui.warn("abort: destination '%s' already exists\n" % dest)
282 ui.warn("abort: destination '%s' already exists\n" % dest)
283 return 1
283 return 1
284
284
285 class dircleanup:
285 class dircleanup:
286 def __init__(self, dir):
286 def __init__(self, dir):
287 self.dir = dir
287 self.dir = dir
288 os.mkdir(dir)
288 os.mkdir(dir)
289 def close(self):
289 def close(self):
290 self.dir = None
290 self.dir = None
291 def __del__(self):
291 def __del__(self):
292 if self.dir:
292 if self.dir:
293 import shutil
293 import shutil
294 shutil.rmtree(self.dir, True)
294 shutil.rmtree(self.dir, True)
295
295
296 d = dircleanup(dest)
296 d = dircleanup(dest)
297
297
298 link = 0
298 link = 0
299 if not (source.startswith("http://") or
299 if not (source.startswith("http://") or
300 source.startswith("hg://") or
300 source.startswith("hg://") or
301 source.startswith("old-http://")):
301 source.startswith("old-http://")):
302 source = os.path.abspath(source)
302 source = os.path.abspath(source)
303 d1 = os.stat(dest).st_dev
303 d1 = os.stat(dest).st_dev
304 d2 = os.stat(source).st_dev
304 d2 = os.stat(source).st_dev
305 if d1 == d2: link = 1
305 if d1 == d2: link = 1
306
306
307 if link:
307 if link:
308 ui.note("copying by hardlink\n")
308 ui.note("copying by hardlink\n")
309 util.system("cp -al '%s'/.hg '%s'/.hg" % (source, dest))
309 util.system("cp -al '%s'/.hg '%s'/.hg" % (source, dest))
310 try:
310 try:
311 os.remove(os.path.join(dest, ".hg", "dirstate"))
311 os.remove(os.path.join(dest, ".hg", "dirstate"))
312 except: pass
312 except: pass
313
313
314 repo = hg.repository(ui, dest)
314 repo = hg.repository(ui, dest)
315
315
316 else:
316 else:
317 repo = hg.repository(ui, dest, create=1)
317 repo = hg.repository(ui, dest, create=1)
318 other = hg.repository(ui, source)
318 other = hg.repository(ui, source)
319 fetch = repo.findincoming(other)
319 fetch = repo.findincoming(other)
320 if fetch:
320 if fetch:
321 cg = other.changegroup(fetch)
321 cg = other.changegroup(fetch)
322 repo.addchangegroup(cg)
322 repo.addchangegroup(cg)
323
323
324 f = repo.opener("hgrc", "w")
324 f = repo.opener("hgrc", "w")
325 f.write("[paths]\n")
325 f.write("[paths]\n")
326 f.write("default = %s\n" % source)
326 f.write("default = %s\n" % source)
327
327
328 if not opts['noupdate']:
328 if not opts['noupdate']:
329 update(ui, repo)
329 update(ui, repo)
330
330
331 d.close()
331 d.close()
332
332
333 def commit(ui, repo, *files, **opts):
333 def commit(ui, repo, *files, **opts):
334 """commit the specified files or all outstanding changes"""
334 """commit the specified files or all outstanding changes"""
335 text = opts['text']
335 text = opts['text']
336 if not text and opts['logfile']:
336 if not text and opts['logfile']:
337 try: text = open(opts['logfile']).read()
337 try: text = open(opts['logfile']).read()
338 except IOError: pass
338 except IOError: pass
339
339
340 if opts['addremove']:
340 if opts['addremove']:
341 addremove(ui, repo, *files)
341 addremove(ui, repo, *files)
342 repo.commit(relpath(repo, files), text, opts['user'], opts['date'])
342 repo.commit(relpath(repo, files), text, opts['user'], opts['date'])
343
343
344 def copy(ui, repo, source, dest):
344 def copy(ui, repo, source, dest):
345 """mark a file as copied or renamed for the next commit"""
345 """mark a file as copied or renamed for the next commit"""
346 return repo.copy(*relpath(repo, (source, dest)))
346 return repo.copy(*relpath(repo, (source, dest)))
347
347
348 def debugcheckdirstate(ui, repo):
348 def debugcheckdirstate(ui, repo):
349 parent1, parent2 = repo.dirstate.parents()
349 parent1, parent2 = repo.dirstate.parents()
350 repo.dirstate.read()
350 repo.dirstate.read()
351 dc = repo.dirstate.map
351 dc = repo.dirstate.map
352 keys = dc.keys()
352 keys = dc.keys()
353 keys.sort()
353 keys.sort()
354 m1n = repo.changelog.read(parent1)[0]
354 m1n = repo.changelog.read(parent1)[0]
355 m2n = repo.changelog.read(parent2)[0]
355 m2n = repo.changelog.read(parent2)[0]
356 m1 = repo.manifest.read(m1n)
356 m1 = repo.manifest.read(m1n)
357 m2 = repo.manifest.read(m2n)
357 m2 = repo.manifest.read(m2n)
358 errors = 0
358 errors = 0
359 for f in dc:
359 for f in dc:
360 state = repo.dirstate.state(f)
360 state = repo.dirstate.state(f)
361 if state in "nr" and f not in m1:
361 if state in "nr" and f not in m1:
362 print "%s in state %s, but not listed in manifest1" % (f, state)
362 print "%s in state %s, but not listed in manifest1" % (f, state)
363 errors += 1
363 errors += 1
364 if state in "a" and f in m1:
364 if state in "a" and f in m1:
365 print "%s in state %s, but also listed in manifest1" % (f, state)
365 print "%s in state %s, but also listed in manifest1" % (f, state)
366 errors += 1
366 errors += 1
367 if state in "m" and f not in m1 and f not in m2:
367 if state in "m" and f not in m1 and f not in m2:
368 print "%s in state %s, but not listed in either manifest" % \
368 print "%s in state %s, but not listed in either manifest" % \
369 (f, state)
369 (f, state)
370 errors += 1
370 errors += 1
371 for f in m1:
371 for f in m1:
372 state = repo.dirstate.state(f)
372 state = repo.dirstate.state(f)
373 if state not in "nrm":
373 if state not in "nrm":
374 print "%s in manifest1, but listed as state %s" % (f, state)
374 print "%s in manifest1, but listed as state %s" % (f, state)
375 errors += 1
375 errors += 1
376 if errors:
376 if errors:
377 print ".hg/dirstate inconsistent with current parent's manifest"
377 print ".hg/dirstate inconsistent with current parent's manifest"
378 sys.exit(1)
378 sys.exit(1)
379
379
380 def debugdumpdirstate(ui, repo):
380 def debugdumpdirstate(ui, repo):
381 repo.dirstate.read()
381 repo.dirstate.read()
382 dc = repo.dirstate.map
382 dc = repo.dirstate.map
383 keys = dc.keys()
383 keys = dc.keys()
384 keys.sort()
384 keys.sort()
385 for file in keys:
385 for file in keys:
386 print "%s => %c" % (file, dc[file][0])
386 print "%s => %c" % (file, dc[file][0])
387
387
388 def debugindex(ui, file):
388 def debugindex(ui, file):
389 r = hg.revlog(hg.opener(""), file, "")
389 r = hg.revlog(hg.opener(""), file, "")
390 print " rev offset length base linkrev"+\
390 print " rev offset length base linkrev"+\
391 " p1 p2 nodeid"
391 " p1 p2 nodeid"
392 for i in range(r.count()):
392 for i in range(r.count()):
393 e = r.index[i]
393 e = r.index[i]
394 print "% 6d % 9d % 7d % 6d % 7d %s.. %s.. %s.." % (
394 print "% 6d % 9d % 7d % 6d % 7d %s.. %s.. %s.." % (
395 i, e[0], e[1], e[2], e[3],
395 i, e[0], e[1], e[2], e[3],
396 hg.hex(e[4][:5]), hg.hex(e[5][:5]), hg.hex(e[6][:5]))
396 hg.hex(e[4][:5]), hg.hex(e[5][:5]), hg.hex(e[6][:5]))
397
397
398 def debugindexdot(ui, file):
398 def debugindexdot(ui, file):
399 r = hg.revlog(hg.opener(""), file, "")
399 r = hg.revlog(hg.opener(""), file, "")
400 print "digraph G {"
400 print "digraph G {"
401 for i in range(r.count()):
401 for i in range(r.count()):
402 e = r.index[i]
402 e = r.index[i]
403 print "\t%d -> %d" % (r.rev(e[4]), i)
403 print "\t%d -> %d" % (r.rev(e[4]), i)
404 if e[5] != hg.nullid:
404 if e[5] != hg.nullid:
405 print "\t%d -> %d" % (r.rev(e[5]), i)
405 print "\t%d -> %d" % (r.rev(e[5]), i)
406 print "}"
406 print "}"
407
407
408 def diff(ui, repo, *files, **opts):
408 def diff(ui, repo, *files, **opts):
409 """diff working directory (or selected files)"""
409 """diff working directory (or selected files)"""
410 revs = []
410 revs = []
411 if opts['rev']:
411 if opts['rev']:
412 revs = map(lambda x: repo.lookup(x), opts['rev'])
412 revs = map(lambda x: repo.lookup(x), opts['rev'])
413
413
414 if len(revs) > 2:
414 if len(revs) > 2:
415 ui.warn("too many revisions to diff\n")
415 ui.warn("too many revisions to diff\n")
416 sys.exit(1)
416 sys.exit(1)
417
417
418 if files:
418 if files:
419 files = relpath(repo, files)
419 files = relpath(repo, files)
420 else:
420 else:
421 files = relpath(repo, [""])
421 files = relpath(repo, [""])
422
422
423 dodiff(ui, repo, files, *revs)
423 dodiff(ui, repo, files, *revs)
424
424
425 def export(ui, repo, changeset):
425 def export(ui, repo, changeset):
426 """dump the changeset header and diffs for a revision"""
426 """dump the changeset header and diffs for a revision"""
427 node = repo.lookup(changeset)
427 node = repo.lookup(changeset)
428 prev, other = repo.changelog.parents(node)
428 prev, other = repo.changelog.parents(node)
429 change = repo.changelog.read(node)
429 change = repo.changelog.read(node)
430 print "# HG changeset patch"
430 print "# HG changeset patch"
431 print "# User %s" % change[1]
431 print "# User %s" % change[1]
432 print "# Node ID %s" % hg.hex(node)
432 print "# Node ID %s" % hg.hex(node)
433 print "# Parent %s" % hg.hex(prev)
433 print "# Parent %s" % hg.hex(prev)
434 print
434 print
435 if other != hg.nullid:
435 if other != hg.nullid:
436 print "# Parent %s" % hg.hex(other)
436 print "# Parent %s" % hg.hex(other)
437 print change[4].rstrip()
437 print change[4].rstrip()
438 print
438 print
439
439
440 dodiff(ui, repo, None, prev, node)
440 dodiff(ui, repo, None, prev, node)
441
441
442 def forget(ui, repo, file, *files):
442 def forget(ui, repo, file, *files):
443 """don't add the specified files on the next commit"""
443 """don't add the specified files on the next commit"""
444 repo.forget(relpath(repo, (file,) + files))
444 repo.forget(relpath(repo, (file,) + files))
445
445
446 def heads(ui, repo):
446 def heads(ui, repo):
447 """show current repository heads"""
447 """show current repository heads"""
448 for n in repo.changelog.heads():
448 for n in repo.changelog.heads():
449 show_changeset(ui, repo, changenode=n)
449 show_changeset(ui, repo, changenode=n)
450
450
451 def identify(ui, repo):
451 def identify(ui, repo):
452 """print information about the working copy"""
452 """print information about the working copy"""
453 parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
453 parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
454 if not parents:
454 if not parents:
455 ui.write("unknown\n")
455 ui.write("unknown\n")
456 return
456 return
457
457
458 hexfunc = ui.verbose and hg.hex or hg.short
458 hexfunc = ui.verbose and hg.hex or hg.short
459 (c, a, d, u) = repo.changes(None, None)
459 (c, a, d, u) = repo.changes(None, None)
460 output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
460 output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
461 (c or a or d) and "+" or "")]
461 (c or a or d) and "+" or "")]
462
462
463 if not ui.quiet:
463 if not ui.quiet:
464 # multiple tags for a single parent separated by '/'
464 # multiple tags for a single parent separated by '/'
465 parenttags = ['/'.join(tags)
465 parenttags = ['/'.join(tags)
466 for tags in map(repo.nodetags, parents) if tags]
466 for tags in map(repo.nodetags, parents) if tags]
467 # tags for multiple parents separated by ' + '
467 # tags for multiple parents separated by ' + '
468 output.append(' + '.join(parenttags))
468 output.append(' + '.join(parenttags))
469
469
470 ui.write("%s\n" % ' '.join(output))
470 ui.write("%s\n" % ' '.join(output))
471
471
472 def import_(ui, repo, patch1, *patches, **opts):
472 def import_(ui, repo, patch1, *patches, **opts):
473 """import an ordered set of patches"""
473 """import an ordered set of patches"""
474 try:
474 try:
475 import psyco
475 import psyco
476 psyco.full()
476 psyco.full()
477 except:
477 except:
478 pass
478 pass
479
479
480 patches = (patch1,) + patches
480 patches = (patch1,) + patches
481
481
482 d = opts["base"]
482 d = opts["base"]
483 strip = opts["strip"]
483 strip = opts["strip"]
484
484
485 for patch in patches:
485 for patch in patches:
486 ui.status("applying %s\n" % patch)
486 ui.status("applying %s\n" % patch)
487 pf = os.path.join(d, patch)
487 pf = os.path.join(d, patch)
488
488
489 text = ""
489 text = ""
490 for l in file(pf):
490 for l in file(pf):
491 if l[:4] == "--- ": break
491 if l[:4] == "--- ": break
492 text += l
492 text += l
493
493
494 # make sure text isn't empty
494 # make sure text isn't empty
495 if not text: text = "imported patch %s\n" % patch
495 if not text: text = "imported patch %s\n" % patch
496
496
497 f = os.popen("patch -p%d < %s" % (strip, pf))
497 f = os.popen("patch -p%d < %s" % (strip, pf))
498 files = []
498 files = []
499 for l in f.read().splitlines():
499 for l in f.read().splitlines():
500 l.rstrip('\r\n');
500 l.rstrip('\r\n');
501 ui.status("%s\n" % l)
501 ui.status("%s\n" % l)
502 if l[:14] == 'patching file ':
502 if l[:14] == 'patching file ':
503 pf = l[14:]
503 pf = l[14:]
504 if pf not in files:
504 if pf not in files:
505 files.append(pf)
505 files.append(pf)
506 patcherr = f.close()
506 patcherr = f.close()
507 if patcherr:
507 if patcherr:
508 sys.stderr.write("patch failed")
508 sys.stderr.write("patch failed")
509 sys.exit(1)
509 sys.exit(1)
510
510
511 if len(files) > 0:
511 if len(files) > 0:
512 addremove(ui, repo, *files)
512 addremove(ui, repo, *files)
513 repo.commit(files, text)
513 repo.commit(files, text)
514
514
515 def init(ui, source=None):
515 def init(ui, source=None):
516 """create a new repository in the current directory"""
516 """create a new repository in the current directory"""
517
517
518 if source:
518 if source:
519 ui.warn("no longer supported: use \"hg clone\" instead\n")
519 ui.warn("no longer supported: use \"hg clone\" instead\n")
520 sys.exit(1)
520 sys.exit(1)
521 repo = hg.repository(ui, ".", create=1)
521 repo = hg.repository(ui, ".", create=1)
522
522
523 def log(ui, repo, f=None, **opts):
523 def log(ui, repo, f=None, **opts):
524 """show the revision history of the repository or a single file"""
524 """show the revision history of the repository or a single file"""
525 if f:
525 if f:
526 filelog = repo.file(relpath(repo, [f])[0])
526 filelog = repo.file(relpath(repo, [f])[0])
527 log = filelog
527 log = filelog
528 lookup = filelog.lookup
528 lookup = filelog.lookup
529 else:
529 else:
530 filelog = None
530 filelog = None
531 log = repo.changelog
531 log = repo.changelog
532 lookup = repo.lookup
532 lookup = repo.lookup
533 revlist = []
533 revlist = []
534 revs = [log.rev(lookup(rev)) for rev in opts['rev']]
534 revs = [log.rev(lookup(rev)) for rev in opts['rev']]
535 while revs:
535 while revs:
536 if len(revs) == 1:
536 if len(revs) == 1:
537 revlist.append(revs.pop(0))
537 revlist.append(revs.pop(0))
538 else:
538 else:
539 a = revs.pop(0)
539 a = revs.pop(0)
540 b = revs.pop(0)
540 b = revs.pop(0)
541 off = a > b and -1 or 1
541 off = a > b and -1 or 1
542 revlist.extend(range(a, b + off, off))
542 revlist.extend(range(a, b + off, off))
543 for i in revlist or range(log.count() - 1, -1, -1):
543 for i in revlist or range(log.count() - 1, -1, -1):
544 show_changeset(ui, repo, filelog=filelog, rev=i)
544 show_changeset(ui, repo, filelog=filelog, rev=i)
545
545
546 def manifest(ui, repo, rev = []):
546 def manifest(ui, repo, rev = []):
547 """output the latest or given revision of the project manifest"""
547 """output the latest or given revision of the project manifest"""
548 n = repo.manifest.tip()
548 n = repo.manifest.tip()
549 if rev:
549 if rev:
550 n = repo.manifest.lookup(rev)
550 n = repo.manifest.lookup(rev)
551 m = repo.manifest.read(n)
551 m = repo.manifest.read(n)
552 mf = repo.manifest.readflags(n)
552 mf = repo.manifest.readflags(n)
553 files = m.keys()
553 files = m.keys()
554 files.sort()
554 files.sort()
555
555
556 for f in files:
556 for f in files:
557 ui.write("%40s %3s %s\n" % (hg.hex(m[f]), mf[f] and "755" or "644", f))
557 ui.write("%40s %3s %s\n" % (hg.hex(m[f]), mf[f] and "755" or "644", f))
558
558
559 def parents(ui, repo, node = None):
559 def parents(ui, repo, node = None):
560 '''show the parents of the current working dir'''
560 '''show the parents of the current working dir'''
561 if node:
561 if node:
562 p = repo.changelog.parents(repo.lookup(hg.bin(node)))
562 p = repo.changelog.parents(repo.lookup(hg.bin(node)))
563 else:
563 else:
564 p = repo.dirstate.parents()
564 p = repo.dirstate.parents()
565
565
566 for n in p:
566 for n in p:
567 if n != hg.nullid:
567 if n != hg.nullid:
568 show_changeset(ui, repo, changenode=n)
568 show_changeset(ui, repo, changenode=n)
569
569
570 def pull(ui, repo, source="default", **opts):
570 def pull(ui, repo, source="default", **opts):
571 """pull changes from the specified source"""
571 """pull changes from the specified source"""
572 source = ui.expandpath(source)
572 source = ui.expandpath(source)
573
573
574 ui.status('pulling from %s\n' % (source))
574 ui.status('pulling from %s\n' % (source))
575
575
576 other = hg.repository(ui, source)
576 other = hg.repository(ui, source)
577 fetch = repo.findincoming(other)
577 fetch = repo.findincoming(other)
578 if not fetch:
578 if not fetch:
579 ui.status("no changes found\n")
579 ui.status("no changes found\n")
580 return
580 return
581
581
582 cg = other.changegroup(fetch)
582 cg = other.changegroup(fetch)
583 r = repo.addchangegroup(cg)
583 r = repo.addchangegroup(cg)
584 if cg and not r:
584 if cg and not r:
585 if opts['update']:
585 if opts['update']:
586 return update(ui, repo)
586 return update(ui, repo)
587 else:
587 else:
588 ui.status("(run 'hg update' to get a working copy)\n")
588 ui.status("(run 'hg update' to get a working copy)\n")
589
589
590 return r
590 return r
591
591
592 def push(ui, repo, dest="default-push"):
592 def push(ui, repo, dest="default-push"):
593 """push changes to the specified destination"""
593 """push changes to the specified destination"""
594 dest = ui.expandpath(dest)
594 dest = ui.expandpath(dest)
595
595
596 if not dest.startswith("ssh://"):
596 if not dest.startswith("ssh://"):
597 ui.warn("abort: can only push to ssh:// destinations currently\n")
597 ui.warn("abort: can only push to ssh:// destinations currently\n")
598 return 1
598 return 1
599
599
600 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?', dest)
600 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?', dest)
601 if not m:
601 if not m:
602 ui.warn("abort: couldn't parse destination %s\n" % dest)
602 ui.warn("abort: couldn't parse destination %s\n" % dest)
603 return 1
603 return 1
604
604
605 user, host, port, path = map(m.group, (2, 3, 5, 7))
605 user, host, port, path = map(m.group, (2, 3, 5, 7))
606 uhost = user and ("%s@%s" % (user, host)) or host
606 uhost = user and ("%s@%s" % (user, host)) or host
607 port = port and (" -p %s") % port or ""
607 port = port and (" -p %s") % port or ""
608 path = path or ""
608 path = path or ""
609
609
610 sport = random.randrange(30000, 60000)
610 sport = random.randrange(30000, 60000)
611 cmd = "ssh %s%s -R %d:localhost:%d 'cd %s; hg pull http://localhost:%d/'"
611 cmd = "ssh %s%s -R %d:localhost:%d 'cd %s; hg pull http://localhost:%d/'"
612 cmd = cmd % (uhost, port, sport+1, sport, path, sport+1)
612 cmd = cmd % (uhost, port, sport+1, sport, path, sport+1)
613
613
614 child = os.fork()
614 child = os.fork()
615 if not child:
615 if not child:
616 sys.stdout = file("/dev/null", "w")
616 sys.stdout = file("/dev/null", "w")
617 sys.stderr = sys.stdout
617 sys.stderr = sys.stdout
618 hgweb.server(repo.root, "pull", "", "localhost", sport)
618 hgweb.server(repo.root, "pull", "", "localhost", sport)
619 else:
619 else:
620 ui.status("connecting to %s\n" % host)
620 ui.status("connecting to %s\n" % host)
621 r = os.system(cmd)
621 r = os.system(cmd)
622 os.kill(child, signal.SIGTERM)
622 os.kill(child, signal.SIGTERM)
623 return r
623 return r
624
624
625 def rawcommit(ui, repo, *flist, **rc):
625 def rawcommit(ui, repo, *flist, **rc):
626 "raw commit interface"
626 "raw commit interface"
627
627
628 text = rc['text']
628 text = rc['text']
629 if not text and rc['logfile']:
629 if not text and rc['logfile']:
630 try: text = open(rc['logfile']).read()
630 try: text = open(rc['logfile']).read()
631 except IOError: pass
631 except IOError: pass
632 if not text and not rc['logfile']:
632 if not text and not rc['logfile']:
633 print "missing commit text"
633 print "missing commit text"
634 return 1
634 return 1
635
635
636 files = relpath(repo, list(flist))
636 files = relpath(repo, list(flist))
637 if rc['files']:
637 if rc['files']:
638 files += open(rc['files']).read().splitlines()
638 files += open(rc['files']).read().splitlines()
639
639
640 rc['parent'] = map(repo.lookup, rc['parent'])
640 rc['parent'] = map(repo.lookup, rc['parent'])
641
641
642 repo.rawcommit(files, text, rc['user'], rc['date'], *rc['parent'])
642 repo.rawcommit(files, text, rc['user'], rc['date'], *rc['parent'])
643
643
644 def recover(ui, repo):
644 def recover(ui, repo):
645 """roll back an interrupted transaction"""
645 """roll back an interrupted transaction"""
646 repo.recover()
646 repo.recover()
647
647
648 def remove(ui, repo, file, *files):
648 def remove(ui, repo, file, *files):
649 """remove the specified files on the next commit"""
649 """remove the specified files on the next commit"""
650 repo.remove(relpath(repo, (file,) + files))
650 repo.remove(relpath(repo, (file,) + files))
651
651
652 def root(ui, repo):
652 def root(ui, repo):
653 """print the root (top) of the current working dir"""
653 """print the root (top) of the current working dir"""
654 ui.write(repo.root + "\n")
654 ui.write(repo.root + "\n")
655
655
656 def serve(ui, repo, **opts):
656 def serve(ui, repo, **opts):
657 """export the repository via HTTP"""
657 """export the repository via HTTP"""
658 hgweb.server(repo.root, opts["name"], opts["templates"],
658 hgweb.server(repo.root, opts["name"], opts["templates"],
659 opts["address"], opts["port"])
659 opts["address"], opts["port"])
660
660
661 def status(ui, repo):
661 def status(ui, repo):
662 '''show changed files in the working directory
662 '''show changed files in the working directory
663
663
664 C = changed
664 C = changed
665 A = added
665 A = added
666 R = removed
666 R = removed
667 ? = not tracked'''
667 ? = not tracked'''
668
668
669 (c, a, d, u) = repo.changes(None, None)
669 (c, a, d, u) = repo.changes(None, None)
670 (c, a, d, u) = map(lambda x: relfilter(repo, x), (c, a, d, u))
670 (c, a, d, u) = map(lambda x: relfilter(repo, x), (c, a, d, u))
671
671
672 for f in c: print "C", f
672 for f in c: print "C", f
673 for f in a: print "A", f
673 for f in a: print "A", f
674 for f in d: print "R", f
674 for f in d: print "R", f
675 for f in u: print "?", f
675 for f in u: print "?", f
676
676
677 def tag(ui, repo, name, rev = None, **opts):
677 def tag(ui, repo, name, rev = None, **opts):
678 """add a tag for the current tip or a given revision"""
678 """add a tag for the current tip or a given revision"""
679
679
680 if name == "tip":
680 if name == "tip":
681 ui.warn("abort: 'tip' is a reserved name!\n")
681 ui.warn("abort: 'tip' is a reserved name!\n")
682 return -1
682 return -1
683
683
684 (c, a, d, u) = repo.changes(None, None)
684 (c, a, d, u) = repo.changes(None, None)
685 for x in (c, a, d, u):
685 for x in (c, a, d, u):
686 if ".hgtags" in x:
686 if ".hgtags" in x:
687 ui.warn("abort: working copy of .hgtags is changed!\n")
687 ui.warn("abort: working copy of .hgtags is changed!\n")
688 ui.status("(please commit .hgtags manually)\n")
688 ui.status("(please commit .hgtags manually)\n")
689 return -1
689 return -1
690
690
691 if rev:
691 if rev:
692 r = hg.hex(repo.lookup(rev))
692 r = hg.hex(repo.lookup(rev))
693 else:
693 else:
694 r = hg.hex(repo.changelog.tip())
694 r = hg.hex(repo.changelog.tip())
695
695
696 add = 0
696 add = 0
697 if not os.path.exists(repo.wjoin(".hgtags")): add = 1
697 if not os.path.exists(repo.wjoin(".hgtags")): add = 1
698 repo.wfile(".hgtags", "a").write("%s %s\n" % (r, name))
698 repo.wfile(".hgtags", "a").write("%s %s\n" % (r, name))
699 if add: repo.add([".hgtags"])
699 if add: repo.add([".hgtags"])
700
700
701 if not opts['text']:
701 if not opts['text']:
702 opts['text'] = "Added tag %s for changeset %s" % (name, r)
702 opts['text'] = "Added tag %s for changeset %s" % (name, r)
703
703
704 repo.commit([".hgtags"], opts['text'], opts['user'], opts['date'])
704 repo.commit([".hgtags"], opts['text'], opts['user'], opts['date'])
705
705
706 def tags(ui, repo):
706 def tags(ui, repo):
707 """list repository tags"""
707 """list repository tags"""
708
708
709 l = repo.tagslist()
709 l = repo.tagslist()
710 l.reverse()
710 l.reverse()
711 for t, n in l:
711 for t, n in l:
712 try:
712 try:
713 r = "%5d:%s" % (repo.changelog.rev(n), hg.hex(n))
713 r = "%5d:%s" % (repo.changelog.rev(n), hg.hex(n))
714 except KeyError:
714 except KeyError:
715 r = " ?:?"
715 r = " ?:?"
716 ui.write("%-30s %s\n" % (t, r))
716 ui.write("%-30s %s\n" % (t, r))
717
717
718 def tip(ui, repo):
718 def tip(ui, repo):
719 """show the tip revision"""
719 """show the tip revision"""
720 n = repo.changelog.tip()
720 n = repo.changelog.tip()
721 show_changeset(ui, repo, changenode=n)
721 show_changeset(ui, repo, changenode=n)
722
722
723 def undo(ui, repo):
723 def undo(ui, repo):
724 """undo the last transaction"""
724 """undo the last transaction"""
725 repo.undo()
725 repo.undo()
726
726
727 def update(ui, repo, node=None, merge=False, clean=False):
727 def update(ui, repo, node=None, merge=False, clean=False):
728 '''update or merge working directory
728 '''update or merge working directory
729
729
730 If there are no outstanding changes in the working directory and
730 If there are no outstanding changes in the working directory and
731 there is a linear relationship between the current version and the
731 there is a linear relationship between the current version and the
732 requested version, the result is the requested version.
732 requested version, the result is the requested version.
733
733
734 Otherwise the result is a merge between the contents of the
734 Otherwise the result is a merge between the contents of the
735 current working directory and the requested version. Files that
735 current working directory and the requested version. Files that
736 changed between either parent are marked as changed for the next
736 changed between either parent are marked as changed for the next
737 commit and a commit must be performed before any further updates
737 commit and a commit must be performed before any further updates
738 are allowed.
738 are allowed.
739 '''
739 '''
740 node = node and repo.lookup(node) or repo.changelog.tip()
740 node = node and repo.lookup(node) or repo.changelog.tip()
741 return repo.update(node, allow=merge, force=clean)
741 return repo.update(node, allow=merge, force=clean)
742
742
743 def verify(ui, repo):
743 def verify(ui, repo):
744 """verify the integrity of the repository"""
744 """verify the integrity of the repository"""
745 return repo.verify()
745 return repo.verify()
746
746
747 # Command options and aliases are listed here, alphabetically
747 # Command options and aliases are listed here, alphabetically
748
748
749 table = {
749 table = {
750 "add": (add, [], "hg add [files]"),
750 "add": (add, [], "hg add [files]"),
751 "addremove": (addremove, [], "hg addremove [files]"),
751 "addremove": (addremove, [], "hg addremove [files]"),
752 "annotate": (annotate,
752 "annotate": (annotate,
753 [('r', 'revision', '', 'revision'),
753 [('r', 'revision', '', 'revision'),
754 ('u', 'user', None, 'show user'),
754 ('u', 'user', None, 'show user'),
755 ('n', 'number', None, 'show revision number'),
755 ('n', 'number', None, 'show revision number'),
756 ('c', 'changeset', None, 'show changeset')],
756 ('c', 'changeset', None, 'show changeset')],
757 'hg annotate [-u] [-c] [-n] [-r id] [files]'),
757 'hg annotate [-u] [-c] [-n] [-r id] [files]'),
758 "cat": (cat, [], 'hg cat <file> [rev]'),
758 "cat": (cat, [], 'hg cat <file> [rev]'),
759 "clone": (clone, [('U', 'noupdate', None, 'skip update after cloning')],
759 "clone": (clone, [('U', 'noupdate', None, 'skip update after cloning')],
760 'hg clone [options] <source> [dest]'),
760 'hg clone [options] <source> [dest]'),
761 "commit|ci": (commit,
761 "commit|ci": (commit,
762 [('t', 'text', "", 'commit text'),
762 [('t', 'text', "", 'commit text'),
763 ('A', 'addremove', None, 'run add/remove during commit'),
763 ('A', 'addremove', None, 'run add/remove during commit'),
764 ('l', 'logfile', "", 'commit text file'),
764 ('l', 'logfile', "", 'commit text file'),
765 ('d', 'date', "", 'data'),
765 ('d', 'date', "", 'data'),
766 ('u', 'user', "", 'user')],
766 ('u', 'user', "", 'user')],
767 'hg commit [files]'),
767 'hg commit [files]'),
768 "copy": (copy, [], 'hg copy <source> <dest>'),
768 "copy": (copy, [], 'hg copy <source> <dest>'),
769 "debugcheckdirstate": (debugcheckdirstate, [], 'debugcheckdirstate'),
769 "debugcheckdirstate": (debugcheckdirstate, [], 'debugcheckdirstate'),
770 "debugdumpdirstate": (debugdumpdirstate, [], 'debugdumpdirstate'),
770 "debugdumpdirstate": (debugdumpdirstate, [], 'debugdumpdirstate'),
771 "debugindex": (debugindex, [], 'debugindex <file>'),
771 "debugindex": (debugindex, [], 'debugindex <file>'),
772 "debugindexdot": (debugindexdot, [], 'debugindexdot <file>'),
772 "debugindexdot": (debugindexdot, [], 'debugindexdot <file>'),
773 "diff": (diff, [('r', 'rev', [], 'revision')],
773 "diff": (diff, [('r', 'rev', [], 'revision')],
774 'hg diff [-r A] [-r B] [files]'),
774 'hg diff [-r A] [-r B] [files]'),
775 "export": (export, [], "hg export <changeset>"),
775 "export": (export, [], "hg export <changeset>"),
776 "forget": (forget, [], "hg forget [files]"),
776 "forget": (forget, [], "hg forget [files]"),
777 "heads": (heads, [], 'hg heads'),
777 "heads": (heads, [], 'hg heads'),
778 "help": (help, [], 'hg help [command]'),
778 "help": (help, [], 'hg help [command]'),
779 "identify|id": (identify, [], 'hg identify'),
779 "identify|id": (identify, [], 'hg identify'),
780 "import|patch": (import_,
780 "import|patch": (import_,
781 [('p', 'strip', 1, 'path strip'),
781 [('p', 'strip', 1, 'path strip'),
782 ('b', 'base', "", 'base path')],
782 ('b', 'base', "", 'base path')],
783 "hg import [options] <patches>"),
783 "hg import [options] <patches>"),
784 "init": (init, [], 'hg init'),
784 "init": (init, [], 'hg init'),
785 "log|history": (log,
785 "log|history": (log,
786 [('r', 'rev', [], 'revision')],
786 [('r', 'rev', [], 'revision')],
787 'hg log [-r A] [-r B] [file]'),
787 'hg log [-r A] [-r B] [file]'),
788 "manifest": (manifest, [], 'hg manifest [rev]'),
788 "manifest": (manifest, [], 'hg manifest [rev]'),
789 "parents": (parents, [], 'hg parents [node]'),
789 "parents": (parents, [], 'hg parents [node]'),
790 "pull": (pull,
790 "pull": (pull,
791 [('u', 'update', None, 'update working directory')],
791 [('u', 'update', None, 'update working directory')],
792 'hg pull [options] [source]'),
792 'hg pull [options] [source]'),
793 "push": (push, [], 'hg push <destination>'),
793 "push": (push, [], 'hg push <destination>'),
794 "rawcommit": (rawcommit,
794 "rawcommit": (rawcommit,
795 [('p', 'parent', [], 'parent'),
795 [('p', 'parent', [], 'parent'),
796 ('d', 'date', "", 'data'),
796 ('d', 'date', "", 'data'),
797 ('u', 'user', "", 'user'),
797 ('u', 'user', "", 'user'),
798 ('F', 'files', "", 'file list'),
798 ('F', 'files', "", 'file list'),
799 ('t', 'text', "", 'commit text'),
799 ('t', 'text', "", 'commit text'),
800 ('l', 'logfile', "", 'commit text file')],
800 ('l', 'logfile', "", 'commit text file')],
801 'hg rawcommit [options] [files]'),
801 'hg rawcommit [options] [files]'),
802 "recover": (recover, [], "hg recover"),
802 "recover": (recover, [], "hg recover"),
803 "remove|rm": (remove, [], "hg remove [files]"),
803 "remove|rm": (remove, [], "hg remove [files]"),
804 "root": (root, [], "hg root"),
804 "root": (root, [], "hg root"),
805 "serve": (serve, [('p', 'port', 8000, 'listen port'),
805 "serve": (serve, [('p', 'port', 8000, 'listen port'),
806 ('a', 'address', '', 'interface address'),
806 ('a', 'address', '', 'interface address'),
807 ('n', 'name', os.getcwd(), 'repository name'),
807 ('n', 'name', os.getcwd(), 'repository name'),
808 ('t', 'templates', "", 'template map')],
808 ('t', 'templates', "", 'template map')],
809 "hg serve [options]"),
809 "hg serve [options]"),
810 "status": (status, [], 'hg status'),
810 "status": (status, [], 'hg status'),
811 "tag": (tag, [('t', 'text', "", 'commit text'),
811 "tag": (tag, [('t', 'text', "", 'commit text'),
812 ('d', 'date', "", 'date'),
812 ('d', 'date', "", 'date'),
813 ('u', 'user', "", 'user')],
813 ('u', 'user', "", 'user')],
814 'hg tag [options] <name> [rev]'),
814 'hg tag [options] <name> [rev]'),
815 "tags": (tags, [], 'hg tags'),
815 "tags": (tags, [], 'hg tags'),
816 "tip": (tip, [], 'hg tip'),
816 "tip": (tip, [], 'hg tip'),
817 "undo": (undo, [], 'hg undo'),
817 "undo": (undo, [], 'hg undo'),
818 "update|up|checkout|co":
818 "update|up|checkout|co":
819 (update,
819 (update,
820 [('m', 'merge', None, 'allow merging of conflicts'),
820 [('m', 'merge', None, 'allow merging of conflicts'),
821 ('C', 'clean', None, 'overwrite locally modified files')],
821 ('C', 'clean', None, 'overwrite locally modified files')],
822 'hg update [options] [node]'),
822 'hg update [options] [node]'),
823 "verify": (verify, [], 'hg verify'),
823 "verify": (verify, [], 'hg verify'),
824 "version": (show_version, [], 'hg version'),
824 "version": (show_version, [], 'hg version'),
825 }
825 }
826
826
827 norepo = "clone init version help debugindex debugindexdot"
827 norepo = "clone init version help debugindex debugindexdot"
828
828
829 def find(cmd):
829 def find(cmd):
830 for e in table.keys():
830 for e in table.keys():
831 if re.match("(%s)$" % e, cmd):
831 if re.match("(%s)$" % e, cmd):
832 return table[e]
832 return table[e]
833
833
834 raise UnknownCommand(cmd)
834 raise UnknownCommand(cmd)
835
835
836 class SignalInterrupt(Exception): pass
836 class SignalInterrupt(Exception): pass
837
837
838 def catchterm(*args):
838 def catchterm(*args):
839 raise SignalInterrupt
839 raise SignalInterrupt
840
840
841 def run():
841 def run():
842 sys.exit(dispatch(sys.argv[1:]))
842 sys.exit(dispatch(sys.argv[1:]))
843
843
844 def dispatch(args):
844 def dispatch(args):
845 options = {}
845 options = {}
846 opts = [('v', 'verbose', None, 'verbose'),
846 opts = [('v', 'verbose', None, 'verbose'),
847 ('d', 'debug', None, 'debug'),
847 ('d', 'debug', None, 'debug'),
848 ('q', 'quiet', None, 'quiet'),
848 ('q', 'quiet', None, 'quiet'),
849 ('p', 'profile', None, 'profile'),
849 ('p', 'profile', None, 'profile'),
850 ('R', 'repository', "", 'repository root directory'),
850 ('R', 'repository', "", 'repository root directory'),
851 ('', 'traceback', None, 'print traceback on exception'),
851 ('', 'traceback', None, 'print traceback on exception'),
852 ('y', 'noninteractive', None, 'run non-interactively'),
852 ('y', 'noninteractive', None, 'run non-interactively'),
853 ('', 'version', None, 'output version information and exit'),
853 ('', 'version', None, 'output version information and exit'),
854 ]
854 ]
855
855
856 args = fancyopts.fancyopts(args, opts, options,
856 args = fancyopts.fancyopts(args, opts, options,
857 'hg [options] <command> [options] [files]')
857 'hg [options] <command> [options] [files]')
858
858
859 if not args:
859 if not args:
860 cmd = "help"
860 cmd = "help"
861 else:
861 else:
862 cmd, args = args[0], args[1:]
862 cmd, args = args[0], args[1:]
863
863
864 u = ui.ui(options["verbose"], options["debug"], options["quiet"],
864 u = ui.ui(options["verbose"], options["debug"], options["quiet"],
865 not options["noninteractive"])
865 not options["noninteractive"])
866
866
867 if options["version"]:
867 if options["version"]:
868 show_version(u)
868 show_version(u)
869 sys.exit(0)
869 sys.exit(0)
870
870
871 try:
871 try:
872 i = find(cmd)
872 i = find(cmd)
873 except UnknownCommand:
873 except UnknownCommand:
874 u.warn("hg: unknown command '%s'\n" % cmd)
874 u.warn("hg: unknown command '%s'\n" % cmd)
875 help(u)
875 help(u)
876 sys.exit(1)
876 sys.exit(1)
877
877
878 signal.signal(signal.SIGTERM, catchterm)
878 signal.signal(signal.SIGTERM, catchterm)
879
879
880 cmdoptions = {}
880 cmdoptions = {}
881 try:
881 try:
882 args = fancyopts.fancyopts(args, i[1], cmdoptions, i[2])
882 args = fancyopts.fancyopts(args, i[1], cmdoptions, i[2])
883 except fancyopts.getopt.GetoptError, inst:
883 except fancyopts.getopt.GetoptError, inst:
884 u.warn("hg %s: %s\n" % (cmd, inst))
884 u.warn("hg %s: %s\n" % (cmd, inst))
885 help(u, cmd)
885 help(u, cmd)
886 sys.exit(-1)
886 sys.exit(-1)
887
887
888 try:
888 try:
889 try:
889 try:
890 if cmd not in norepo.split():
890 if cmd not in norepo.split():
891 path = options["repository"] or ""
891 path = options["repository"] or ""
892 repo = hg.repository(ui=u, path=path)
892 repo = hg.repository(ui=u, path=path)
893 d = lambda: i[0](u, repo, *args, **cmdoptions)
893 d = lambda: i[0](u, repo, *args, **cmdoptions)
894 else:
894 else:
895 d = lambda: i[0](u, *args, **cmdoptions)
895 d = lambda: i[0](u, *args, **cmdoptions)
896
896
897 if options['profile']:
897 if options['profile']:
898 import hotshot, hotshot.stats
898 import hotshot, hotshot.stats
899 prof = hotshot.Profile("hg.prof")
899 prof = hotshot.Profile("hg.prof")
900 r = prof.runcall(d)
900 r = prof.runcall(d)
901 prof.close()
901 prof.close()
902 stats = hotshot.stats.load("hg.prof")
902 stats = hotshot.stats.load("hg.prof")
903 stats.strip_dirs()
903 stats.strip_dirs()
904 stats.sort_stats('time', 'calls')
904 stats.sort_stats('time', 'calls')
905 stats.print_stats(40)
905 stats.print_stats(40)
906 return r
906 return r
907 else:
907 else:
908 return d()
908 return d()
909 except:
909 except:
910 if options['traceback']:
910 if options['traceback']:
911 traceback.print_exc()
911 traceback.print_exc()
912 raise
912 raise
913 except util.CommandError, inst:
913 except util.CommandError, inst:
914 u.warn("abort: %s\n" % inst.args)
914 u.warn("abort: %s\n" % inst.args)
915 except hg.RepoError, inst:
915 except hg.RepoError, inst:
916 u.warn("abort: ", inst, "!\n")
916 u.warn("abort: ", inst, "!\n")
917 except SignalInterrupt:
917 except SignalInterrupt:
918 u.warn("killed!\n")
918 u.warn("killed!\n")
919 except KeyboardInterrupt:
919 except KeyboardInterrupt:
920 u.warn("interrupted!\n")
920 u.warn("interrupted!\n")
921 except IOError, inst:
921 except IOError, inst:
922 if hasattr(inst, "code"):
922 if hasattr(inst, "code"):
923 u.warn("abort: %s\n" % inst)
923 u.warn("abort: %s\n" % inst)
924 elif hasattr(inst, "reason"):
924 elif hasattr(inst, "reason"):
925 u.warn("abort: error %d: %s\n" % (inst.reason[0], inst.reason[1]))
925 u.warn("abort: error %d: %s\n" % (inst.reason[0], inst.reason[1]))
926 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
926 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
927 u.warn("broken pipe\n")
927 u.warn("broken pipe\n")
928 else:
928 else:
929 raise
929 raise
930 except OSError, inst:
930 except OSError, inst:
931 if hasattr(inst, "filename"):
931 if hasattr(inst, "filename"):
932 u.warn("abort: %s: %s\n" % (inst.strerror, inst.filename))
932 u.warn("abort: %s: %s\n" % (inst.strerror, inst.filename))
933 else:
933 else:
934 u.warn("abort: %s\n" % inst.strerror)
934 u.warn("abort: %s\n" % inst.strerror)
935 except TypeError, inst:
935 except TypeError, inst:
936 # was this an argument error?
936 # was this an argument error?
937 tb = traceback.extract_tb(sys.exc_info()[2])
937 tb = traceback.extract_tb(sys.exc_info()[2])
938 if len(tb) > 2: # no
938 if len(tb) > 2: # no
939 raise
939 raise
940 u.debug(inst, "\n")
940 u.debug(inst, "\n")
941 u.warn("%s: invalid arguments\n" % i[0].__name__)
941 u.warn("%s: invalid arguments\n" % i[0].__name__)
942 help(u, cmd)
942 help(u, cmd)
943
943
944 sys.exit(-1)
944 sys.exit(-1)
945
945
@@ -1,1516 +1,1522 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 import util
9 import util
10 from revlog import *
10 from revlog import *
11 from demandload import *
11 from demandload import *
12 demandload(globals(), "re lock urllib urllib2 transaction time socket")
12 demandload(globals(), "re lock urllib urllib2 transaction time socket")
13 demandload(globals(), "tempfile httprangereader bdiff")
13 demandload(globals(), "tempfile httprangereader bdiff")
14
14
15 class filelog(revlog):
15 class filelog(revlog):
16 def __init__(self, opener, path):
16 def __init__(self, opener, path):
17 revlog.__init__(self, opener,
17 revlog.__init__(self, opener,
18 os.path.join("data", path + ".i"),
18 os.path.join("data", path + ".i"),
19 os.path.join("data", path + ".d"))
19 os.path.join("data", path + ".d"))
20
20
21 def read(self, node):
21 def read(self, node):
22 t = self.revision(node)
22 t = self.revision(node)
23 if t[:2] != '\1\n':
23 if t[:2] != '\1\n':
24 return t
24 return t
25 s = t.find('\1\n', 2)
25 s = t.find('\1\n', 2)
26 return t[s+2:]
26 return t[s+2:]
27
27
28 def readmeta(self, node):
28 def readmeta(self, node):
29 t = self.revision(node)
29 t = self.revision(node)
30 if t[:2] != '\1\n':
30 if t[:2] != '\1\n':
31 return t
31 return t
32 s = t.find('\1\n', 2)
32 s = t.find('\1\n', 2)
33 mt = t[2:s]
33 mt = t[2:s]
34 for l in mt.splitlines():
34 for l in mt.splitlines():
35 k, v = l.split(": ", 1)
35 k, v = l.split(": ", 1)
36 m[k] = v
36 m[k] = v
37 return m
37 return m
38
38
39 def add(self, text, meta, transaction, link, p1=None, p2=None):
39 def add(self, text, meta, transaction, link, p1=None, p2=None):
40 if meta or text[:2] == '\1\n':
40 if meta or text[:2] == '\1\n':
41 mt = ""
41 mt = ""
42 if meta:
42 if meta:
43 mt = [ "%s: %s\n" % (k, v) for k,v in meta.items() ]
43 mt = [ "%s: %s\n" % (k, v) for k,v in meta.items() ]
44 text = "\1\n" + "".join(mt) + "\1\n" + text
44 text = "\1\n" + "".join(mt) + "\1\n" + text
45 return self.addrevision(text, transaction, link, p1, p2)
45 return self.addrevision(text, transaction, link, p1, p2)
46
46
47 def annotate(self, node):
47 def annotate(self, node):
48
48
49 def decorate(text, rev):
49 def decorate(text, rev):
50 return ([rev] * len(text.splitlines()), text)
50 return ([rev] * len(text.splitlines()), text)
51
51
52 def pair(parent, child):
52 def pair(parent, child):
53 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
53 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
54 child[0][b1:b2] = parent[0][a1:a2]
54 child[0][b1:b2] = parent[0][a1:a2]
55 return child
55 return child
56
56
57 # find all ancestors
57 # find all ancestors
58 needed = {node:1}
58 needed = {node:1}
59 visit = [node]
59 visit = [node]
60 while visit:
60 while visit:
61 n = visit.pop(0)
61 n = visit.pop(0)
62 for p in self.parents(n):
62 for p in self.parents(n):
63 if p not in needed:
63 if p not in needed:
64 needed[p] = 1
64 needed[p] = 1
65 visit.append(p)
65 visit.append(p)
66 else:
66 else:
67 # count how many times we'll use this
67 # count how many times we'll use this
68 needed[p] += 1
68 needed[p] += 1
69
69
70 # sort by revision which is a topological order
70 # sort by revision which is a topological order
71 visit = [ (self.rev(n), n) for n in needed.keys() ]
71 visit = [ (self.rev(n), n) for n in needed.keys() ]
72 visit.sort()
72 visit.sort()
73 hist = {}
73 hist = {}
74
74
75 for r,n in visit:
75 for r,n in visit:
76 curr = decorate(self.read(n), self.linkrev(n))
76 curr = decorate(self.read(n), self.linkrev(n))
77 for p in self.parents(n):
77 for p in self.parents(n):
78 if p != nullid:
78 if p != nullid:
79 curr = pair(hist[p], curr)
79 curr = pair(hist[p], curr)
80 # trim the history of unneeded revs
80 # trim the history of unneeded revs
81 needed[p] -= 1
81 needed[p] -= 1
82 if not needed[p]:
82 if not needed[p]:
83 del hist[p]
83 del hist[p]
84 hist[n] = curr
84 hist[n] = curr
85
85
86 return zip(hist[n][0], hist[n][1].splitlines(1))
86 return zip(hist[n][0], hist[n][1].splitlines(1))
87
87
88 class manifest(revlog):
88 class manifest(revlog):
89 def __init__(self, opener):
89 def __init__(self, opener):
90 self.mapcache = None
90 self.mapcache = None
91 self.listcache = None
91 self.listcache = None
92 self.addlist = None
92 self.addlist = None
93 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
93 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
94
94
95 def read(self, node):
95 def read(self, node):
96 if node == nullid: return {} # don't upset local cache
96 if node == nullid: return {} # don't upset local cache
97 if self.mapcache and self.mapcache[0] == node:
97 if self.mapcache and self.mapcache[0] == node:
98 return self.mapcache[1].copy()
98 return self.mapcache[1]
99 text = self.revision(node)
99 text = self.revision(node)
100 map = {}
100 map = {}
101 flag = {}
101 flag = {}
102 self.listcache = (text, text.splitlines(1))
102 self.listcache = (text, text.splitlines(1))
103 for l in self.listcache[1]:
103 for l in self.listcache[1]:
104 (f, n) = l.split('\0')
104 (f, n) = l.split('\0')
105 map[f] = bin(n[:40])
105 map[f] = bin(n[:40])
106 flag[f] = (n[40:-1] == "x")
106 flag[f] = (n[40:-1] == "x")
107 self.mapcache = (node, map, flag)
107 self.mapcache = (node, map, flag)
108 return map
108 return map
109
109
110 def readflags(self, node):
110 def readflags(self, node):
111 if node == nullid: return {} # don't upset local cache
111 if node == nullid: return {} # don't upset local cache
112 if not self.mapcache or self.mapcache[0] != node:
112 if not self.mapcache or self.mapcache[0] != node:
113 self.read(node)
113 self.read(node)
114 return self.mapcache[2]
114 return self.mapcache[2]
115
115
116 def diff(self, a, b):
116 def diff(self, a, b):
117 # this is sneaky, as we're not actually using a and b
117 # this is sneaky, as we're not actually using a and b
118 if self.listcache and self.addlist and self.listcache[0] == a:
118 if self.listcache and self.addlist and self.listcache[0] == a:
119 d = mdiff.diff(self.listcache[1], self.addlist, 1)
119 d = mdiff.diff(self.listcache[1], self.addlist, 1)
120 if mdiff.patch(a, d) != b:
120 if mdiff.patch(a, d) != b:
121 sys.stderr.write("*** sortdiff failed, falling back ***\n")
121 sys.stderr.write("*** sortdiff failed, falling back ***\n")
122 return mdiff.textdiff(a, b)
122 return mdiff.textdiff(a, b)
123 return d
123 return d
124 else:
124 else:
125 return mdiff.textdiff(a, b)
125 return mdiff.textdiff(a, b)
126
126
127 def add(self, map, flags, transaction, link, p1=None, p2=None):
127 def add(self, map, flags, transaction, link, p1=None, p2=None):
128 files = map.keys()
128 files = map.keys()
129 files.sort()
129 files.sort()
130
130
131 self.addlist = ["%s\000%s%s\n" %
131 self.addlist = ["%s\000%s%s\n" %
132 (f, hex(map[f]), flags[f] and "x" or '')
132 (f, hex(map[f]), flags[f] and "x" or '')
133 for f in files]
133 for f in files]
134 text = "".join(self.addlist)
134 text = "".join(self.addlist)
135
135
136 n = self.addrevision(text, transaction, link, p1, p2)
136 n = self.addrevision(text, transaction, link, p1, p2)
137 self.mapcache = (n, map, flags)
137 self.mapcache = (n, map, flags)
138 self.listcache = (text, self.addlist)
138 self.listcache = (text, self.addlist)
139 self.addlist = None
139 self.addlist = None
140
140
141 return n
141 return n
142
142
143 class changelog(revlog):
143 class changelog(revlog):
144 def __init__(self, opener):
144 def __init__(self, opener):
145 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
145 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
146
146
147 def extract(self, text):
147 def extract(self, text):
148 if not text:
148 if not text:
149 return (nullid, "", "0", [], "")
149 return (nullid, "", "0", [], "")
150 last = text.index("\n\n")
150 last = text.index("\n\n")
151 desc = text[last + 2:]
151 desc = text[last + 2:]
152 l = text[:last].splitlines()
152 l = text[:last].splitlines()
153 manifest = bin(l[0])
153 manifest = bin(l[0])
154 user = l[1]
154 user = l[1]
155 date = l[2]
155 date = l[2]
156 files = l[3:]
156 files = l[3:]
157 return (manifest, user, date, files, desc)
157 return (manifest, user, date, files, desc)
158
158
159 def read(self, node):
159 def read(self, node):
160 return self.extract(self.revision(node))
160 return self.extract(self.revision(node))
161
161
162 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
162 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
163 user=None, date=None):
163 user=None, date=None):
164 user = (user or
164 user = (user or
165 os.environ.get("HGUSER") or
165 os.environ.get("HGUSER") or
166 os.environ.get("EMAIL") or
166 os.environ.get("EMAIL") or
167 (os.environ.get("LOGNAME",
167 (os.environ.get("LOGNAME",
168 os.environ.get("USERNAME", "unknown"))
168 os.environ.get("USERNAME", "unknown"))
169 + '@' + socket.getfqdn()))
169 + '@' + socket.getfqdn()))
170 date = date or "%d %d" % (time.time(), time.timezone)
170 date = date or "%d %d" % (time.time(), time.timezone)
171 list.sort()
171 list.sort()
172 l = [hex(manifest), user, date] + list + ["", desc]
172 l = [hex(manifest), user, date] + list + ["", desc]
173 text = "\n".join(l)
173 text = "\n".join(l)
174 return self.addrevision(text, transaction, self.count(), p1, p2)
174 return self.addrevision(text, transaction, self.count(), p1, p2)
175
175
176 class dirstate:
176 class dirstate:
177 def __init__(self, opener, ui, root):
177 def __init__(self, opener, ui, root):
178 self.opener = opener
178 self.opener = opener
179 self.root = root
179 self.root = root
180 self.dirty = 0
180 self.dirty = 0
181 self.ui = ui
181 self.ui = ui
182 self.map = None
182 self.map = None
183 self.pl = None
183 self.pl = None
184 self.copies = {}
184 self.copies = {}
185
185
186 def __del__(self):
186 def __del__(self):
187 if self.dirty:
187 if self.dirty:
188 self.write()
188 self.write()
189
189
190 def __getitem__(self, key):
190 def __getitem__(self, key):
191 try:
191 try:
192 return self.map[key]
192 return self.map[key]
193 except TypeError:
193 except TypeError:
194 self.read()
194 self.read()
195 return self[key]
195 return self[key]
196
196
197 def __contains__(self, key):
197 def __contains__(self, key):
198 if not self.map: self.read()
198 if not self.map: self.read()
199 return key in self.map
199 return key in self.map
200
200
201 def parents(self):
201 def parents(self):
202 if not self.pl:
202 if not self.pl:
203 self.read()
203 self.read()
204 return self.pl
204 return self.pl
205
205
206 def setparents(self, p1, p2 = nullid):
206 def setparents(self, p1, p2 = nullid):
207 self.dirty = 1
207 self.dirty = 1
208 self.pl = p1, p2
208 self.pl = p1, p2
209
209
210 def state(self, key):
210 def state(self, key):
211 try:
211 try:
212 return self[key][0]
212 return self[key][0]
213 except KeyError:
213 except KeyError:
214 return "?"
214 return "?"
215
215
216 def read(self):
216 def read(self):
217 if self.map is not None: return self.map
217 if self.map is not None: return self.map
218
218
219 self.map = {}
219 self.map = {}
220 self.pl = [nullid, nullid]
220 self.pl = [nullid, nullid]
221 try:
221 try:
222 st = self.opener("dirstate").read()
222 st = self.opener("dirstate").read()
223 if not st: return
223 if not st: return
224 except: return
224 except: return
225
225
226 self.pl = [st[:20], st[20: 40]]
226 self.pl = [st[:20], st[20: 40]]
227
227
228 pos = 40
228 pos = 40
229 while pos < len(st):
229 while pos < len(st):
230 e = struct.unpack(">cllll", st[pos:pos+17])
230 e = struct.unpack(">cllll", st[pos:pos+17])
231 l = e[4]
231 l = e[4]
232 pos += 17
232 pos += 17
233 f = st[pos:pos + l]
233 f = st[pos:pos + l]
234 if '\0' in f:
234 if '\0' in f:
235 f, c = f.split('\0')
235 f, c = f.split('\0')
236 self.copies[f] = c
236 self.copies[f] = c
237 self.map[f] = e[:4]
237 self.map[f] = e[:4]
238 pos += l
238 pos += l
239
239
240 def copy(self, source, dest):
240 def copy(self, source, dest):
241 self.read()
241 self.read()
242 self.dirty = 1
242 self.dirty = 1
243 self.copies[dest] = source
243 self.copies[dest] = source
244
244
245 def copied(self, file):
245 def copied(self, file):
246 return self.copies.get(file, None)
246 return self.copies.get(file, None)
247
247
248 def update(self, files, state):
248 def update(self, files, state):
249 ''' current states:
249 ''' current states:
250 n normal
250 n normal
251 m needs merging
251 m needs merging
252 r marked for removal
252 r marked for removal
253 a marked for addition'''
253 a marked for addition'''
254
254
255 if not files: return
255 if not files: return
256 self.read()
256 self.read()
257 self.dirty = 1
257 self.dirty = 1
258 for f in files:
258 for f in files:
259 if state == "r":
259 if state == "r":
260 self.map[f] = ('r', 0, 0, 0)
260 self.map[f] = ('r', 0, 0, 0)
261 else:
261 else:
262 s = os.stat(os.path.join(self.root, f))
262 s = os.stat(os.path.join(self.root, f))
263 self.map[f] = (state, s.st_mode, s.st_size, s.st_mtime)
263 self.map[f] = (state, s.st_mode, s.st_size, s.st_mtime)
264
264
265 def forget(self, files):
265 def forget(self, files):
266 if not files: return
266 if not files: return
267 self.read()
267 self.read()
268 self.dirty = 1
268 self.dirty = 1
269 for f in files:
269 for f in files:
270 try:
270 try:
271 del self.map[f]
271 del self.map[f]
272 except KeyError:
272 except KeyError:
273 self.ui.warn("not in dirstate: %s!\n" % f)
273 self.ui.warn("not in dirstate: %s!\n" % f)
274 pass
274 pass
275
275
276 def clear(self):
276 def clear(self):
277 self.map = {}
277 self.map = {}
278 self.dirty = 1
278 self.dirty = 1
279
279
280 def write(self):
280 def write(self):
281 st = self.opener("dirstate", "w")
281 st = self.opener("dirstate", "w")
282 st.write("".join(self.pl))
282 st.write("".join(self.pl))
283 for f, e in self.map.items():
283 for f, e in self.map.items():
284 c = self.copied(f)
284 c = self.copied(f)
285 if c:
285 if c:
286 f = f + "\0" + c
286 f = f + "\0" + c
287 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
287 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
288 st.write(e + f)
288 st.write(e + f)
289 self.dirty = 0
289 self.dirty = 0
290
290
291 def changes(self, files, ignore):
291 def changes(self, files, ignore):
292 self.read()
292 self.read()
293 dc = self.map.copy()
293 dc = self.map.copy()
294 lookup, changed, added, unknown = [], [], [], []
294 lookup, changed, added, unknown = [], [], [], []
295
295
296 # compare all files by default
296 # compare all files by default
297 if not files: files = [self.root]
297 if not files: files = [self.root]
298
298
299 # recursive generator of all files listed
299 # recursive generator of all files listed
300 def walk(files):
300 def walk(files):
301 for f in util.unique(files):
301 for f in util.unique(files):
302 f = os.path.join(self.root, f)
302 f = os.path.join(self.root, f)
303 if os.path.isdir(f):
303 if os.path.isdir(f):
304 for dir, subdirs, fl in os.walk(f):
304 for dir, subdirs, fl in os.walk(f):
305 d = dir[len(self.root) + 1:]
305 d = dir[len(self.root) + 1:]
306 if ".hg" in subdirs: subdirs.remove(".hg")
306 if ".hg" in subdirs: subdirs.remove(".hg")
307 for fn in fl:
307 for fn in fl:
308 fn = util.pconvert(os.path.join(d, fn))
308 fn = util.pconvert(os.path.join(d, fn))
309 yield fn
309 yield fn
310 else:
310 else:
311 yield f[len(self.root) + 1:]
311 yield f[len(self.root) + 1:]
312
312
313 for fn in util.unique(walk(files)):
313 for fn in util.unique(walk(files)):
314 try: s = os.stat(os.path.join(self.root, fn))
314 try: s = os.stat(os.path.join(self.root, fn))
315 except: continue
315 except: continue
316
316
317 if fn in dc:
317 if fn in dc:
318 c = dc[fn]
318 c = dc[fn]
319 del dc[fn]
319 del dc[fn]
320
320
321 if c[0] == 'm':
321 if c[0] == 'm':
322 changed.append(fn)
322 changed.append(fn)
323 elif c[0] == 'a':
323 elif c[0] == 'a':
324 added.append(fn)
324 added.append(fn)
325 elif c[0] == 'r':
325 elif c[0] == 'r':
326 unknown.append(fn)
326 unknown.append(fn)
327 elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
327 elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
328 changed.append(fn)
328 changed.append(fn)
329 elif c[1] != s.st_mode or c[3] != s.st_mtime:
329 elif c[1] != s.st_mode or c[3] != s.st_mtime:
330 lookup.append(fn)
330 lookup.append(fn)
331 else:
331 else:
332 if not ignore(fn): unknown.append(fn)
332 if not ignore(fn): unknown.append(fn)
333
333
334 return (lookup, changed, added, dc.keys(), unknown)
334 return (lookup, changed, added, dc.keys(), unknown)
335
335
336 # used to avoid circular references so destructors work
336 # used to avoid circular references so destructors work
337 def opener(base):
337 def opener(base):
338 p = base
338 p = base
339 def o(path, mode="r"):
339 def o(path, mode="r"):
340 if p[:7] == "http://":
340 if p[:7] == "http://":
341 f = os.path.join(p, urllib.quote(path))
341 f = os.path.join(p, urllib.quote(path))
342 return httprangereader.httprangereader(f)
342 return httprangereader.httprangereader(f)
343
343
344 f = os.path.join(p, path)
344 f = os.path.join(p, path)
345
345
346 mode += "b" # for that other OS
346 mode += "b" # for that other OS
347
347
348 if mode[0] != "r":
348 if mode[0] != "r":
349 try:
349 try:
350 s = os.stat(f)
350 s = os.stat(f)
351 except OSError:
351 except OSError:
352 d = os.path.dirname(f)
352 d = os.path.dirname(f)
353 if not os.path.isdir(d):
353 if not os.path.isdir(d):
354 os.makedirs(d)
354 os.makedirs(d)
355 else:
355 else:
356 if s.st_nlink > 1:
356 if s.st_nlink > 1:
357 file(f + ".tmp", "wb").write(file(f, "rb").read())
357 file(f + ".tmp", "wb").write(file(f, "rb").read())
358 util.rename(f+".tmp", f)
358 util.rename(f+".tmp", f)
359
359
360 return file(f, mode)
360 return file(f, mode)
361
361
362 return o
362 return o
363
363
364 class RepoError(Exception): pass
364 class RepoError(Exception): pass
365
365
366 class localrepository:
366 class localrepository:
367 def __init__(self, ui, path=None, create=0):
367 def __init__(self, ui, path=None, create=0):
368 self.remote = 0
368 self.remote = 0
369 if path and path[:7] == "http://":
369 if path and path[:7] == "http://":
370 self.remote = 1
370 self.remote = 1
371 self.path = path
371 self.path = path
372 else:
372 else:
373 if not path:
373 if not path:
374 p = os.getcwd()
374 p = os.getcwd()
375 while not os.path.isdir(os.path.join(p, ".hg")):
375 while not os.path.isdir(os.path.join(p, ".hg")):
376 oldp = p
376 oldp = p
377 p = os.path.dirname(p)
377 p = os.path.dirname(p)
378 if p == oldp: raise RepoError("no repo found")
378 if p == oldp: raise RepoError("no repo found")
379 path = p
379 path = p
380 self.path = os.path.join(path, ".hg")
380 self.path = os.path.join(path, ".hg")
381
381
382 if not create and not os.path.isdir(self.path):
382 if not create and not os.path.isdir(self.path):
383 raise RepoError("repository %s not found" % self.path)
383 raise RepoError("repository %s not found" % self.path)
384
384
385 self.root = path
385 self.root = path
386 self.ui = ui
386 self.ui = ui
387
387
388 if create:
388 if create:
389 os.mkdir(self.path)
389 os.mkdir(self.path)
390 os.mkdir(self.join("data"))
390 os.mkdir(self.join("data"))
391
391
392 self.opener = opener(self.path)
392 self.opener = opener(self.path)
393 self.wopener = opener(self.root)
393 self.wopener = opener(self.root)
394 self.manifest = manifest(self.opener)
394 self.manifest = manifest(self.opener)
395 self.changelog = changelog(self.opener)
395 self.changelog = changelog(self.opener)
396 self.ignorelist = None
396 self.ignorelist = None
397 self.tagscache = None
397 self.tagscache = None
398 self.nodetagscache = None
398 self.nodetagscache = None
399
399
400 if not self.remote:
400 if not self.remote:
401 self.dirstate = dirstate(self.opener, ui, self.root)
401 self.dirstate = dirstate(self.opener, ui, self.root)
402 try:
402 try:
403 self.ui.readconfig(self.opener("hgrc"))
403 self.ui.readconfig(self.opener("hgrc"))
404 except IOError: pass
404 except IOError: pass
405
405
406 def ignore(self, f):
406 def ignore(self, f):
407 if self.ignorelist is None:
407 if self.ignorelist is None:
408 self.ignorelist = []
408 self.ignorelist = []
409 try:
409 try:
410 l = file(self.wjoin(".hgignore"))
410 l = file(self.wjoin(".hgignore"))
411 for pat in l:
411 for pat in l:
412 if pat != "\n":
412 if pat != "\n":
413 self.ignorelist.append(re.compile(util.pconvert(pat[:-1])))
413 self.ignorelist.append(re.compile(util.pconvert(pat[:-1])))
414 except IOError: pass
414 except IOError: pass
415 for pat in self.ignorelist:
415 for pat in self.ignorelist:
416 if pat.search(f): return True
416 if pat.search(f): return True
417 return False
417 return False
418
418
419 def hook(self, name, **args):
419 def hook(self, name, **args):
420 s = self.ui.config("hooks", name)
420 s = self.ui.config("hooks", name)
421 if s:
421 if s:
422 self.ui.note("running hook %s: %s\n" % (name, s))
422 self.ui.note("running hook %s: %s\n" % (name, s))
423 old = {}
423 old = {}
424 for k, v in args.items():
424 for k, v in args.items():
425 k = k.upper()
425 k = k.upper()
426 old[k] = os.environ.get(k, None)
426 old[k] = os.environ.get(k, None)
427 os.environ[k] = v
427 os.environ[k] = v
428
428
429 r = os.system(s)
429 r = os.system(s)
430
430
431 for k, v in old.items():
431 for k, v in old.items():
432 if v != None:
432 if v != None:
433 os.environ[k] = v
433 os.environ[k] = v
434 else:
434 else:
435 del os.environ[k]
435 del os.environ[k]
436
436
437 if r:
437 if r:
438 self.ui.warn("abort: %s hook failed with status %d!\n" %
438 self.ui.warn("abort: %s hook failed with status %d!\n" %
439 (name, r))
439 (name, r))
440 return False
440 return False
441 return True
441 return True
442
442
443 def tags(self):
443 def tags(self):
444 '''return a mapping of tag to node'''
444 '''return a mapping of tag to node'''
445 if not self.tagscache:
445 if not self.tagscache:
446 self.tagscache = {}
446 self.tagscache = {}
447 try:
447 try:
448 # read each head of the tags file, ending with the tip
448 # read each head of the tags file, ending with the tip
449 # and add each tag found to the map, with "newer" ones
449 # and add each tag found to the map, with "newer" ones
450 # taking precedence
450 # taking precedence
451 fl = self.file(".hgtags")
451 fl = self.file(".hgtags")
452 h = fl.heads()
452 h = fl.heads()
453 h.reverse()
453 h.reverse()
454 for r in h:
454 for r in h:
455 for l in fl.revision(r).splitlines():
455 for l in fl.revision(r).splitlines():
456 if l:
456 if l:
457 n, k = l.split(" ", 1)
457 n, k = l.split(" ", 1)
458 try:
458 try:
459 bin_n = bin(n)
459 bin_n = bin(n)
460 except TypeError:
460 except TypeError:
461 bin_n = ''
461 bin_n = ''
462 self.tagscache[k.strip()] = bin_n
462 self.tagscache[k.strip()] = bin_n
463 except KeyError:
463 except KeyError:
464 pass
464 pass
465 for k, n in self.ui.configitems("tags"):
465 for k, n in self.ui.configitems("tags"):
466 try:
466 try:
467 bin_n = bin(n)
467 bin_n = bin(n)
468 except TypeError:
468 except TypeError:
469 bin_n = ''
469 bin_n = ''
470 self.tagscache[k] = bin_n
470 self.tagscache[k] = bin_n
471
471
472 self.tagscache['tip'] = self.changelog.tip()
472 self.tagscache['tip'] = self.changelog.tip()
473
473
474 return self.tagscache
474 return self.tagscache
475
475
476 def tagslist(self):
476 def tagslist(self):
477 '''return a list of tags ordered by revision'''
477 '''return a list of tags ordered by revision'''
478 l = []
478 l = []
479 for t, n in self.tags().items():
479 for t, n in self.tags().items():
480 try:
480 try:
481 r = self.changelog.rev(n)
481 r = self.changelog.rev(n)
482 except:
482 except:
483 r = -2 # sort to the beginning of the list if unknown
483 r = -2 # sort to the beginning of the list if unknown
484 l.append((r,t,n))
484 l.append((r,t,n))
485 l.sort()
485 l.sort()
486 return [(t,n) for r,t,n in l]
486 return [(t,n) for r,t,n in l]
487
487
488 def nodetags(self, node):
488 def nodetags(self, node):
489 '''return the tags associated with a node'''
489 '''return the tags associated with a node'''
490 if not self.nodetagscache:
490 if not self.nodetagscache:
491 self.nodetagscache = {}
491 self.nodetagscache = {}
492 for t,n in self.tags().items():
492 for t,n in self.tags().items():
493 self.nodetagscache.setdefault(n,[]).append(t)
493 self.nodetagscache.setdefault(n,[]).append(t)
494 return self.nodetagscache.get(node, [])
494 return self.nodetagscache.get(node, [])
495
495
496 def lookup(self, key):
496 def lookup(self, key):
497 try:
497 try:
498 return self.tags()[key]
498 return self.tags()[key]
499 except KeyError:
499 except KeyError:
500 return self.changelog.lookup(key)
500 return self.changelog.lookup(key)
501
501
502 def join(self, f):
502 def join(self, f):
503 return os.path.join(self.path, f)
503 return os.path.join(self.path, f)
504
504
505 def wjoin(self, f):
505 def wjoin(self, f):
506 return os.path.join(self.root, f)
506 return os.path.join(self.root, f)
507
507
508 def file(self, f):
508 def file(self, f):
509 if f[0] == '/': f = f[1:]
509 if f[0] == '/': f = f[1:]
510 return filelog(self.opener, f)
510 return filelog(self.opener, f)
511
511
512 def wfile(self, f, mode='r'):
512 def wfile(self, f, mode='r'):
513 return self.wopener(f, mode)
513 return self.wopener(f, mode)
514
514
515 def transaction(self):
515 def transaction(self):
516 # save dirstate for undo
516 # save dirstate for undo
517 try:
517 try:
518 ds = self.opener("dirstate").read()
518 ds = self.opener("dirstate").read()
519 except IOError:
519 except IOError:
520 ds = ""
520 ds = ""
521 self.opener("undo.dirstate", "w").write(ds)
521 self.opener("undo.dirstate", "w").write(ds)
522
522
523 return transaction.transaction(self.opener, self.join("journal"),
523 return transaction.transaction(self.opener, self.join("journal"),
524 self.join("undo"))
524 self.join("undo"))
525
525
526 def recover(self):
526 def recover(self):
527 lock = self.lock()
527 lock = self.lock()
528 if os.path.exists(self.join("journal")):
528 if os.path.exists(self.join("journal")):
529 self.ui.status("rolling back interrupted transaction\n")
529 self.ui.status("rolling back interrupted transaction\n")
530 return transaction.rollback(self.opener, self.join("journal"))
530 return transaction.rollback(self.opener, self.join("journal"))
531 else:
531 else:
532 self.ui.warn("no interrupted transaction available\n")
532 self.ui.warn("no interrupted transaction available\n")
533
533
534 def undo(self):
534 def undo(self):
535 lock = self.lock()
535 lock = self.lock()
536 if os.path.exists(self.join("undo")):
536 if os.path.exists(self.join("undo")):
537 self.ui.status("rolling back last transaction\n")
537 self.ui.status("rolling back last transaction\n")
538 transaction.rollback(self.opener, self.join("undo"))
538 transaction.rollback(self.opener, self.join("undo"))
539 self.dirstate = None
539 self.dirstate = None
540 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
540 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
541 self.dirstate = dirstate(self.opener, self.ui, self.root)
541 self.dirstate = dirstate(self.opener, self.ui, self.root)
542 else:
542 else:
543 self.ui.warn("no undo information available\n")
543 self.ui.warn("no undo information available\n")
544
544
545 def lock(self, wait = 1):
545 def lock(self, wait = 1):
546 try:
546 try:
547 return lock.lock(self.join("lock"), 0)
547 return lock.lock(self.join("lock"), 0)
548 except lock.LockHeld, inst:
548 except lock.LockHeld, inst:
549 if wait:
549 if wait:
550 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
550 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
551 return lock.lock(self.join("lock"), wait)
551 return lock.lock(self.join("lock"), wait)
552 raise inst
552 raise inst
553
553
554 def rawcommit(self, files, text, user, date, p1=None, p2=None):
554 def rawcommit(self, files, text, user, date, p1=None, p2=None):
555 orig_parent = self.dirstate.parents()[0] or nullid
555 orig_parent = self.dirstate.parents()[0] or nullid
556 p1 = p1 or self.dirstate.parents()[0] or nullid
556 p1 = p1 or self.dirstate.parents()[0] or nullid
557 p2 = p2 or self.dirstate.parents()[1] or nullid
557 p2 = p2 or self.dirstate.parents()[1] or nullid
558 c1 = self.changelog.read(p1)
558 c1 = self.changelog.read(p1)
559 c2 = self.changelog.read(p2)
559 c2 = self.changelog.read(p2)
560 m1 = self.manifest.read(c1[0])
560 m1 = self.manifest.read(c1[0])
561 mf1 = self.manifest.readflags(c1[0])
561 mf1 = self.manifest.readflags(c1[0])
562 m2 = self.manifest.read(c2[0])
562 m2 = self.manifest.read(c2[0])
563
563
564 if orig_parent == p1:
564 if orig_parent == p1:
565 update_dirstate = 1
565 update_dirstate = 1
566 else:
566 else:
567 update_dirstate = 0
567 update_dirstate = 0
568
568
569 tr = self.transaction()
569 tr = self.transaction()
570 mm = m1.copy()
570 mm = m1.copy()
571 mfm = mf1.copy()
571 mfm = mf1.copy()
572 linkrev = self.changelog.count()
572 linkrev = self.changelog.count()
573 for f in files:
573 for f in files:
574 try:
574 try:
575 t = self.wfile(f).read()
575 t = self.wfile(f).read()
576 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
576 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
577 r = self.file(f)
577 r = self.file(f)
578 mfm[f] = tm
578 mfm[f] = tm
579 mm[f] = r.add(t, {}, tr, linkrev,
579 mm[f] = r.add(t, {}, tr, linkrev,
580 m1.get(f, nullid), m2.get(f, nullid))
580 m1.get(f, nullid), m2.get(f, nullid))
581 if update_dirstate:
581 if update_dirstate:
582 self.dirstate.update([f], "n")
582 self.dirstate.update([f], "n")
583 except IOError:
583 except IOError:
584 try:
584 try:
585 del mm[f]
585 del mm[f]
586 del mfm[f]
586 del mfm[f]
587 if update_dirstate:
587 if update_dirstate:
588 self.dirstate.forget([f])
588 self.dirstate.forget([f])
589 except:
589 except:
590 # deleted from p2?
590 # deleted from p2?
591 pass
591 pass
592
592
593 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
593 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
594 n = self.changelog.add(mnode, files, text, tr, p1, p2, user, date)
594 n = self.changelog.add(mnode, files, text, tr, p1, p2, user, date)
595 tr.close()
595 tr.close()
596 if update_dirstate:
596 if update_dirstate:
597 self.dirstate.setparents(n, nullid)
597 self.dirstate.setparents(n, nullid)
598
598
599 def commit(self, files = None, text = "", user = None, date = None):
599 def commit(self, files = None, text = "", user = None, date = None):
600 commit = []
600 commit = []
601 remove = []
601 remove = []
602 if files:
602 if files:
603 for f in files:
603 for f in files:
604 s = self.dirstate.state(f)
604 s = self.dirstate.state(f)
605 if s in 'nmai':
605 if s in 'nmai':
606 commit.append(f)
606 commit.append(f)
607 elif s == 'r':
607 elif s == 'r':
608 remove.append(f)
608 remove.append(f)
609 else:
609 else:
610 self.ui.warn("%s not tracked!\n" % f)
610 self.ui.warn("%s not tracked!\n" % f)
611 else:
611 else:
612 (c, a, d, u) = self.changes(None, None)
612 (c, a, d, u) = self.changes(None, None)
613 commit = c + a
613 commit = c + a
614 remove = d
614 remove = d
615
615
616 if not commit and not remove:
616 if not commit and not remove:
617 self.ui.status("nothing changed\n")
617 self.ui.status("nothing changed\n")
618 return
618 return
619
619
620 if not self.hook("precommit"):
620 if not self.hook("precommit"):
621 return 1
621 return 1
622
622
623 p1, p2 = self.dirstate.parents()
623 p1, p2 = self.dirstate.parents()
624 c1 = self.changelog.read(p1)
624 c1 = self.changelog.read(p1)
625 c2 = self.changelog.read(p2)
625 c2 = self.changelog.read(p2)
626 m1 = self.manifest.read(c1[0])
626 m1 = self.manifest.read(c1[0])
627 mf1 = self.manifest.readflags(c1[0])
627 mf1 = self.manifest.readflags(c1[0])
628 m2 = self.manifest.read(c2[0])
628 m2 = self.manifest.read(c2[0])
629 lock = self.lock()
629 lock = self.lock()
630 tr = self.transaction()
630 tr = self.transaction()
631
631
632 # check in files
632 # check in files
633 new = {}
633 new = {}
634 linkrev = self.changelog.count()
634 linkrev = self.changelog.count()
635 commit.sort()
635 commit.sort()
636 for f in commit:
636 for f in commit:
637 self.ui.note(f + "\n")
637 self.ui.note(f + "\n")
638 try:
638 try:
639 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
639 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
640 t = self.wfile(f).read()
640 t = self.wfile(f).read()
641 except IOError:
641 except IOError:
642 self.warn("trouble committing %s!\n" % f)
642 self.warn("trouble committing %s!\n" % f)
643 raise
643 raise
644
644
645 meta = {}
645 meta = {}
646 cp = self.dirstate.copied(f)
646 cp = self.dirstate.copied(f)
647 if cp:
647 if cp:
648 meta["copy"] = cp
648 meta["copy"] = cp
649 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
649 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
650 self.ui.debug(" %s: copy %s:%s\n" % (f, cp, meta["copyrev"]))
650 self.ui.debug(" %s: copy %s:%s\n" % (f, cp, meta["copyrev"]))
651
651
652 r = self.file(f)
652 r = self.file(f)
653 fp1 = m1.get(f, nullid)
653 fp1 = m1.get(f, nullid)
654 fp2 = m2.get(f, nullid)
654 fp2 = m2.get(f, nullid)
655 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
655 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
656
656
657 # update manifest
657 # update manifest
658 m1.update(new)
658 m1.update(new)
659 for f in remove:
659 for f in remove:
660 if f in m1:
660 if f in m1:
661 del m1[f]
661 del m1[f]
662 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0])
662 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0])
663
663
664 # add changeset
664 # add changeset
665 new = new.keys()
665 new = new.keys()
666 new.sort()
666 new.sort()
667
667
668 if not text:
668 if not text:
669 edittext = "\n" + "HG: manifest hash %s\n" % hex(mn)
669 edittext = "\n" + "HG: manifest hash %s\n" % hex(mn)
670 edittext += "".join(["HG: changed %s\n" % f for f in new])
670 edittext += "".join(["HG: changed %s\n" % f for f in new])
671 edittext += "".join(["HG: removed %s\n" % f for f in remove])
671 edittext += "".join(["HG: removed %s\n" % f for f in remove])
672 edittext = self.ui.edit(edittext)
672 edittext = self.ui.edit(edittext)
673 if not edittext.rstrip():
673 if not edittext.rstrip():
674 return 1
674 return 1
675 text = edittext
675 text = edittext
676
676
677 n = self.changelog.add(mn, new, text, tr, p1, p2, user, date)
677 n = self.changelog.add(mn, new, text, tr, p1, p2, user, date)
678
678
679 if not self.hook("commit", node=hex(n)):
679 if not self.hook("commit", node=hex(n)):
680 return 1
680 return 1
681
681
682 tr.close()
682 tr.close()
683
683
684 self.dirstate.setparents(n)
684 self.dirstate.setparents(n)
685 self.dirstate.update(new, "n")
685 self.dirstate.update(new, "n")
686 self.dirstate.forget(remove)
686 self.dirstate.forget(remove)
687
687
688 def changes(self, node1, node2, files=None):
688 def changes(self, node1, node2, files=None):
689 # changed, added, deleted, unknown
689 # changed, added, deleted, unknown
690 c, a, d, u, mf1 = [], [], [], [], None
690 c, a, d, u, mf2 = [], [], [], [], None
691
691
692 def fcmp(fn, mf):
692 def fcmp(fn, mf):
693 t1 = self.wfile(fn).read()
693 t1 = self.wfile(fn).read()
694 t2 = self.file(fn).revision(mf[fn])
694 t2 = self.file(fn).revision(mf[fn])
695 return cmp(t1, t2)
695 return cmp(t1, t2)
696
696
697 # are we comparing the working directory?
697 # are we comparing the working directory?
698 if not node1:
698 if not node2:
699 l, c, a, d, u = self.dirstate.changes(files, self.ignore)
699 l, c, a, d, u = self.dirstate.changes(files, self.ignore)
700
700
701 # are we comparing working dir against its parent?
701 # are we comparing working dir against its parent?
702 if not node2:
702 if not node1:
703 if l:
703 if l:
704 # do a full compare of any files that might have changed
704 # do a full compare of any files that might have changed
705 change = self.changelog.read(self.dirstate.parents()[0])
705 change = self.changelog.read(self.dirstate.parents()[0])
706 mf1 = self.manifest.read(change[0])
706 mf2 = self.manifest.read(change[0])
707 for f in l:
707 for f in l:
708 if fcmp(f, mf1):
708 if fcmp(f, mf2):
709 c.append(f)
709 c.append(f)
710
711 for l in c, a, d, u:
712 l.sort()
713
710 return (c, a, d, u)
714 return (c, a, d, u)
711
715
712 # are we comparing working dir against non-tip?
716 # are we comparing working dir against non-tip?
713 # generate a pseudo-manifest for the working dir
717 # generate a pseudo-manifest for the working dir
714 if not node1:
718 if not node2:
715 if not mf1:
719 if not mf2:
716 change = self.changelog.read(self.dirstate.parents()[0])
720 change = self.changelog.read(self.dirstate.parents()[0])
717 mf1 = self.manifest.read(change[0])
721 mf2 = self.manifest.read(change[0]).copy()
718 for f in a + c + l:
722 for f in a + c + l:
719 mf1[f] = ""
723 mf2[f] = ""
720 for f in d:
724 for f in d:
721 if f in mf1: del mf1[f]
725 if f in mf2: del mf2[f]
722 else:
726 else:
723 change = self.changelog.read(node1)
727 change = self.changelog.read(node2)
724 mf1 = self.manifest.read(change[0])
728 mf2 = self.manifest.read(change[0])
725
729
726 change = self.changelog.read(node2)
730 change = self.changelog.read(node1)
727 mf2 = self.manifest.read(change[0])
731 mf1 = self.manifest.read(change[0]).copy()
728
732
729 for fn in mf2:
733 for fn in mf2:
730 if mf1.has_key(fn):
734 if mf1.has_key(fn):
731 if mf1[fn] != mf2[fn]:
735 if mf1[fn] != mf2[fn]:
732 if mf1[fn] != "" or fcmp(fn, mf2):
736 if mf2[fn] != "" or fcmp(fn, mf1):
733 c.append(fn)
737 c.append(fn)
734 del mf1[fn]
738 del mf1[fn]
735 else:
739 else:
736 a.append(fn)
740 a.append(fn)
737
741
738 d = mf1.keys()
742 d = mf1.keys()
739 d.sort()
743
744 for l in c, a, d, u:
745 l.sort()
740
746
741 return (c, a, d, u)
747 return (c, a, d, u)
742
748
743 def add(self, list):
749 def add(self, list):
744 for f in list:
750 for f in list:
745 p = self.wjoin(f)
751 p = self.wjoin(f)
746 if not os.path.isfile(p):
752 if not os.path.isfile(p):
747 self.ui.warn("%s does not exist!\n" % f)
753 self.ui.warn("%s does not exist!\n" % f)
748 elif self.dirstate.state(f) == 'n':
754 elif self.dirstate.state(f) == 'n':
749 self.ui.warn("%s already tracked!\n" % f)
755 self.ui.warn("%s already tracked!\n" % f)
750 else:
756 else:
751 self.dirstate.update([f], "a")
757 self.dirstate.update([f], "a")
752
758
753 def forget(self, list):
759 def forget(self, list):
754 for f in list:
760 for f in list:
755 if self.dirstate.state(f) not in 'ai':
761 if self.dirstate.state(f) not in 'ai':
756 self.ui.warn("%s not added!\n" % f)
762 self.ui.warn("%s not added!\n" % f)
757 else:
763 else:
758 self.dirstate.forget([f])
764 self.dirstate.forget([f])
759
765
760 def remove(self, list):
766 def remove(self, list):
761 for f in list:
767 for f in list:
762 p = self.wjoin(f)
768 p = self.wjoin(f)
763 if os.path.isfile(p):
769 if os.path.isfile(p):
764 self.ui.warn("%s still exists!\n" % f)
770 self.ui.warn("%s still exists!\n" % f)
765 elif self.dirstate.state(f) == 'a':
771 elif self.dirstate.state(f) == 'a':
766 self.ui.warn("%s never committed!\n" % f)
772 self.ui.warn("%s never committed!\n" % f)
767 self.dirstate.forget(f)
773 self.dirstate.forget(f)
768 elif f not in self.dirstate:
774 elif f not in self.dirstate:
769 self.ui.warn("%s not tracked!\n" % f)
775 self.ui.warn("%s not tracked!\n" % f)
770 else:
776 else:
771 self.dirstate.update([f], "r")
777 self.dirstate.update([f], "r")
772
778
773 def copy(self, source, dest):
779 def copy(self, source, dest):
774 p = self.wjoin(dest)
780 p = self.wjoin(dest)
775 if not os.path.isfile(dest):
781 if not os.path.isfile(dest):
776 self.ui.warn("%s does not exist!\n" % dest)
782 self.ui.warn("%s does not exist!\n" % dest)
777 else:
783 else:
778 if self.dirstate.state(dest) == '?':
784 if self.dirstate.state(dest) == '?':
779 self.dirstate.update([dest], "a")
785 self.dirstate.update([dest], "a")
780 self.dirstate.copy(source, dest)
786 self.dirstate.copy(source, dest)
781
787
782 def heads(self):
788 def heads(self):
783 return self.changelog.heads()
789 return self.changelog.heads()
784
790
785 def branches(self, nodes):
791 def branches(self, nodes):
786 if not nodes: nodes = [self.changelog.tip()]
792 if not nodes: nodes = [self.changelog.tip()]
787 b = []
793 b = []
788 for n in nodes:
794 for n in nodes:
789 t = n
795 t = n
790 while n:
796 while n:
791 p = self.changelog.parents(n)
797 p = self.changelog.parents(n)
792 if p[1] != nullid or p[0] == nullid:
798 if p[1] != nullid or p[0] == nullid:
793 b.append((t, n, p[0], p[1]))
799 b.append((t, n, p[0], p[1]))
794 break
800 break
795 n = p[0]
801 n = p[0]
796 return b
802 return b
797
803
798 def between(self, pairs):
804 def between(self, pairs):
799 r = []
805 r = []
800
806
801 for top, bottom in pairs:
807 for top, bottom in pairs:
802 n, l, i = top, [], 0
808 n, l, i = top, [], 0
803 f = 1
809 f = 1
804
810
805 while n != bottom:
811 while n != bottom:
806 p = self.changelog.parents(n)[0]
812 p = self.changelog.parents(n)[0]
807 if i == f:
813 if i == f:
808 l.append(n)
814 l.append(n)
809 f = f * 2
815 f = f * 2
810 n = p
816 n = p
811 i += 1
817 i += 1
812
818
813 r.append(l)
819 r.append(l)
814
820
815 return r
821 return r
816
822
817 def newer(self, nodes):
823 def newer(self, nodes):
818 m = {}
824 m = {}
819 nl = []
825 nl = []
820 pm = {}
826 pm = {}
821 cl = self.changelog
827 cl = self.changelog
822 t = l = cl.count()
828 t = l = cl.count()
823
829
824 # find the lowest numbered node
830 # find the lowest numbered node
825 for n in nodes:
831 for n in nodes:
826 l = min(l, cl.rev(n))
832 l = min(l, cl.rev(n))
827 m[n] = 1
833 m[n] = 1
828
834
829 for i in xrange(l, t):
835 for i in xrange(l, t):
830 n = cl.node(i)
836 n = cl.node(i)
831 if n in m: # explicitly listed
837 if n in m: # explicitly listed
832 pm[n] = 1
838 pm[n] = 1
833 nl.append(n)
839 nl.append(n)
834 continue
840 continue
835 for p in cl.parents(n):
841 for p in cl.parents(n):
836 if p in pm: # parent listed
842 if p in pm: # parent listed
837 pm[n] = 1
843 pm[n] = 1
838 nl.append(n)
844 nl.append(n)
839 break
845 break
840
846
841 return nl
847 return nl
842
848
843 def findincoming(self, remote):
849 def findincoming(self, remote):
844 m = self.changelog.nodemap
850 m = self.changelog.nodemap
845 search = []
851 search = []
846 fetch = []
852 fetch = []
847 seen = {}
853 seen = {}
848 seenbranch = {}
854 seenbranch = {}
849
855
850 # if we have an empty repo, fetch everything
856 # if we have an empty repo, fetch everything
851 if self.changelog.tip() == nullid:
857 if self.changelog.tip() == nullid:
852 self.ui.status("requesting all changes\n")
858 self.ui.status("requesting all changes\n")
853 return [nullid]
859 return [nullid]
854
860
855 # otherwise, assume we're closer to the tip than the root
861 # otherwise, assume we're closer to the tip than the root
856 self.ui.status("searching for changes\n")
862 self.ui.status("searching for changes\n")
857 heads = remote.heads()
863 heads = remote.heads()
858 unknown = []
864 unknown = []
859 for h in heads:
865 for h in heads:
860 if h not in m:
866 if h not in m:
861 unknown.append(h)
867 unknown.append(h)
862
868
863 if not unknown:
869 if not unknown:
864 return None
870 return None
865
871
866 rep = {}
872 rep = {}
867 reqcnt = 0
873 reqcnt = 0
868
874
869 unknown = remote.branches(unknown)
875 unknown = remote.branches(unknown)
870 while unknown:
876 while unknown:
871 r = []
877 r = []
872 while unknown:
878 while unknown:
873 n = unknown.pop(0)
879 n = unknown.pop(0)
874 if n[0] in seen:
880 if n[0] in seen:
875 continue
881 continue
876
882
877 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
883 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
878 if n[0] == nullid:
884 if n[0] == nullid:
879 break
885 break
880 if n in seenbranch:
886 if n in seenbranch:
881 self.ui.debug("branch already found\n")
887 self.ui.debug("branch already found\n")
882 continue
888 continue
883 if n[1] and n[1] in m: # do we know the base?
889 if n[1] and n[1] in m: # do we know the base?
884 self.ui.debug("found incomplete branch %s:%s\n"
890 self.ui.debug("found incomplete branch %s:%s\n"
885 % (short(n[0]), short(n[1])))
891 % (short(n[0]), short(n[1])))
886 search.append(n) # schedule branch range for scanning
892 search.append(n) # schedule branch range for scanning
887 seenbranch[n] = 1
893 seenbranch[n] = 1
888 else:
894 else:
889 if n[1] not in seen and n[1] not in fetch:
895 if n[1] not in seen and n[1] not in fetch:
890 if n[2] in m and n[3] in m:
896 if n[2] in m and n[3] in m:
891 self.ui.debug("found new changeset %s\n" %
897 self.ui.debug("found new changeset %s\n" %
892 short(n[1]))
898 short(n[1]))
893 fetch.append(n[1]) # earliest unknown
899 fetch.append(n[1]) # earliest unknown
894 continue
900 continue
895
901
896 for a in n[2:4]:
902 for a in n[2:4]:
897 if a not in rep:
903 if a not in rep:
898 r.append(a)
904 r.append(a)
899 rep[a] = 1
905 rep[a] = 1
900
906
901 seen[n[0]] = 1
907 seen[n[0]] = 1
902
908
903 if r:
909 if r:
904 reqcnt += 1
910 reqcnt += 1
905 self.ui.debug("request %d: %s\n" %
911 self.ui.debug("request %d: %s\n" %
906 (reqcnt, " ".join(map(short, r))))
912 (reqcnt, " ".join(map(short, r))))
907 for p in range(0, len(r), 10):
913 for p in range(0, len(r), 10):
908 for b in remote.branches(r[p:p+10]):
914 for b in remote.branches(r[p:p+10]):
909 self.ui.debug("received %s:%s\n" %
915 self.ui.debug("received %s:%s\n" %
910 (short(b[0]), short(b[1])))
916 (short(b[0]), short(b[1])))
911 if b[0] not in m and b[0] not in seen:
917 if b[0] not in m and b[0] not in seen:
912 unknown.append(b)
918 unknown.append(b)
913
919
914 while search:
920 while search:
915 n = search.pop(0)
921 n = search.pop(0)
916 reqcnt += 1
922 reqcnt += 1
917 l = remote.between([(n[0], n[1])])[0]
923 l = remote.between([(n[0], n[1])])[0]
918 l.append(n[1])
924 l.append(n[1])
919 p = n[0]
925 p = n[0]
920 f = 1
926 f = 1
921 for i in l:
927 for i in l:
922 self.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
928 self.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
923 if i in m:
929 if i in m:
924 if f <= 2:
930 if f <= 2:
925 self.ui.debug("found new branch changeset %s\n" %
931 self.ui.debug("found new branch changeset %s\n" %
926 short(p))
932 short(p))
927 fetch.append(p)
933 fetch.append(p)
928 else:
934 else:
929 self.ui.debug("narrowed branch search to %s:%s\n"
935 self.ui.debug("narrowed branch search to %s:%s\n"
930 % (short(p), short(i)))
936 % (short(p), short(i)))
931 search.append((p, i))
937 search.append((p, i))
932 break
938 break
933 p, f = i, f * 2
939 p, f = i, f * 2
934
940
935 for f in fetch:
941 for f in fetch:
936 if f in m:
942 if f in m:
937 raise RepoError("already have changeset " + short(f[:4]))
943 raise RepoError("already have changeset " + short(f[:4]))
938
944
939 if fetch == [nullid]:
945 if fetch == [nullid]:
940 self.ui.warn("warning: pulling from an unrelated repository!\n")
946 self.ui.warn("warning: pulling from an unrelated repository!\n")
941
947
942 self.ui.note("adding new changesets starting at " +
948 self.ui.note("adding new changesets starting at " +
943 " ".join([short(f) for f in fetch]) + "\n")
949 " ".join([short(f) for f in fetch]) + "\n")
944
950
945 self.ui.debug("%d total queries\n" % reqcnt)
951 self.ui.debug("%d total queries\n" % reqcnt)
946
952
947 return fetch
953 return fetch
948
954
949 def changegroup(self, basenodes):
955 def changegroup(self, basenodes):
950 nodes = self.newer(basenodes)
956 nodes = self.newer(basenodes)
951
957
952 # construct the link map
958 # construct the link map
953 linkmap = {}
959 linkmap = {}
954 for n in nodes:
960 for n in nodes:
955 linkmap[self.changelog.rev(n)] = n
961 linkmap[self.changelog.rev(n)] = n
956
962
957 # construct a list of all changed files
963 # construct a list of all changed files
958 changed = {}
964 changed = {}
959 for n in nodes:
965 for n in nodes:
960 c = self.changelog.read(n)
966 c = self.changelog.read(n)
961 for f in c[3]:
967 for f in c[3]:
962 changed[f] = 1
968 changed[f] = 1
963 changed = changed.keys()
969 changed = changed.keys()
964 changed.sort()
970 changed.sort()
965
971
966 # the changegroup is changesets + manifests + all file revs
972 # the changegroup is changesets + manifests + all file revs
967 revs = [ self.changelog.rev(n) for n in nodes ]
973 revs = [ self.changelog.rev(n) for n in nodes ]
968
974
969 for y in self.changelog.group(linkmap): yield y
975 for y in self.changelog.group(linkmap): yield y
970 for y in self.manifest.group(linkmap): yield y
976 for y in self.manifest.group(linkmap): yield y
971 for f in changed:
977 for f in changed:
972 yield struct.pack(">l", len(f) + 4) + f
978 yield struct.pack(">l", len(f) + 4) + f
973 g = self.file(f).group(linkmap)
979 g = self.file(f).group(linkmap)
974 for y in g:
980 for y in g:
975 yield y
981 yield y
976
982
977 def addchangegroup(self, generator):
983 def addchangegroup(self, generator):
978
984
979 class genread:
985 class genread:
980 def __init__(self, generator):
986 def __init__(self, generator):
981 self.g = generator
987 self.g = generator
982 self.buf = ""
988 self.buf = ""
983 def read(self, l):
989 def read(self, l):
984 while l > len(self.buf):
990 while l > len(self.buf):
985 try:
991 try:
986 self.buf += self.g.next()
992 self.buf += self.g.next()
987 except StopIteration:
993 except StopIteration:
988 break
994 break
989 d, self.buf = self.buf[:l], self.buf[l:]
995 d, self.buf = self.buf[:l], self.buf[l:]
990 return d
996 return d
991
997
992 def getchunk():
998 def getchunk():
993 d = source.read(4)
999 d = source.read(4)
994 if not d: return ""
1000 if not d: return ""
995 l = struct.unpack(">l", d)[0]
1001 l = struct.unpack(">l", d)[0]
996 if l <= 4: return ""
1002 if l <= 4: return ""
997 return source.read(l - 4)
1003 return source.read(l - 4)
998
1004
999 def getgroup():
1005 def getgroup():
1000 while 1:
1006 while 1:
1001 c = getchunk()
1007 c = getchunk()
1002 if not c: break
1008 if not c: break
1003 yield c
1009 yield c
1004
1010
1005 def csmap(x):
1011 def csmap(x):
1006 self.ui.debug("add changeset %s\n" % short(x))
1012 self.ui.debug("add changeset %s\n" % short(x))
1007 return self.changelog.count()
1013 return self.changelog.count()
1008
1014
1009 def revmap(x):
1015 def revmap(x):
1010 return self.changelog.rev(x)
1016 return self.changelog.rev(x)
1011
1017
1012 if not generator: return
1018 if not generator: return
1013 changesets = files = revisions = 0
1019 changesets = files = revisions = 0
1014
1020
1015 source = genread(generator)
1021 source = genread(generator)
1016 lock = self.lock()
1022 lock = self.lock()
1017 tr = self.transaction()
1023 tr = self.transaction()
1018
1024
1019 # pull off the changeset group
1025 # pull off the changeset group
1020 self.ui.status("adding changesets\n")
1026 self.ui.status("adding changesets\n")
1021 co = self.changelog.tip()
1027 co = self.changelog.tip()
1022 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
1028 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
1023 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
1029 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
1024
1030
1025 # pull off the manifest group
1031 # pull off the manifest group
1026 self.ui.status("adding manifests\n")
1032 self.ui.status("adding manifests\n")
1027 mm = self.manifest.tip()
1033 mm = self.manifest.tip()
1028 mo = self.manifest.addgroup(getgroup(), revmap, tr)
1034 mo = self.manifest.addgroup(getgroup(), revmap, tr)
1029
1035
1030 # process the files
1036 # process the files
1031 self.ui.status("adding file revisions\n")
1037 self.ui.status("adding file revisions\n")
1032 while 1:
1038 while 1:
1033 f = getchunk()
1039 f = getchunk()
1034 if not f: break
1040 if not f: break
1035 self.ui.debug("adding %s revisions\n" % f)
1041 self.ui.debug("adding %s revisions\n" % f)
1036 fl = self.file(f)
1042 fl = self.file(f)
1037 o = fl.count()
1043 o = fl.count()
1038 n = fl.addgroup(getgroup(), revmap, tr)
1044 n = fl.addgroup(getgroup(), revmap, tr)
1039 revisions += fl.count() - o
1045 revisions += fl.count() - o
1040 files += 1
1046 files += 1
1041
1047
1042 self.ui.status(("modified %d files, added %d changesets" +
1048 self.ui.status(("modified %d files, added %d changesets" +
1043 " and %d new revisions\n")
1049 " and %d new revisions\n")
1044 % (files, changesets, revisions))
1050 % (files, changesets, revisions))
1045
1051
1046 tr.close()
1052 tr.close()
1047 return
1053 return
1048
1054
1049 def update(self, node, allow=False, force=False):
1055 def update(self, node, allow=False, force=False):
1050 pl = self.dirstate.parents()
1056 pl = self.dirstate.parents()
1051 if not force and pl[1] != nullid:
1057 if not force and pl[1] != nullid:
1052 self.ui.warn("aborting: outstanding uncommitted merges\n")
1058 self.ui.warn("aborting: outstanding uncommitted merges\n")
1053 return
1059 return
1054
1060
1055 p1, p2 = pl[0], node
1061 p1, p2 = pl[0], node
1056 pa = self.changelog.ancestor(p1, p2)
1062 pa = self.changelog.ancestor(p1, p2)
1057 m1n = self.changelog.read(p1)[0]
1063 m1n = self.changelog.read(p1)[0]
1058 m2n = self.changelog.read(p2)[0]
1064 m2n = self.changelog.read(p2)[0]
1059 man = self.manifest.ancestor(m1n, m2n)
1065 man = self.manifest.ancestor(m1n, m2n)
1060 m1 = self.manifest.read(m1n)
1066 m1 = self.manifest.read(m1n)
1061 mf1 = self.manifest.readflags(m1n)
1067 mf1 = self.manifest.readflags(m1n)
1062 m2 = self.manifest.read(m2n)
1068 m2 = self.manifest.read(m2n)
1063 mf2 = self.manifest.readflags(m2n)
1069 mf2 = self.manifest.readflags(m2n)
1064 ma = self.manifest.read(man)
1070 ma = self.manifest.read(man)
1065 mfa = self.manifest.readflags(man)
1071 mfa = self.manifest.readflags(man)
1066
1072
1067 (c, a, d, u) = self.changes(None, None)
1073 (c, a, d, u) = self.changes(None, None)
1068
1074
1069 # is this a jump, or a merge? i.e. is there a linear path
1075 # is this a jump, or a merge? i.e. is there a linear path
1070 # from p1 to p2?
1076 # from p1 to p2?
1071 linear_path = (pa == p1 or pa == p2)
1077 linear_path = (pa == p1 or pa == p2)
1072
1078
1073 # resolve the manifest to determine which files
1079 # resolve the manifest to determine which files
1074 # we care about merging
1080 # we care about merging
1075 self.ui.note("resolving manifests\n")
1081 self.ui.note("resolving manifests\n")
1076 self.ui.debug(" ancestor %s local %s remote %s\n" %
1082 self.ui.debug(" ancestor %s local %s remote %s\n" %
1077 (short(man), short(m1n), short(m2n)))
1083 (short(man), short(m1n), short(m2n)))
1078
1084
1079 merge = {}
1085 merge = {}
1080 get = {}
1086 get = {}
1081 remove = []
1087 remove = []
1082 mark = {}
1088 mark = {}
1083
1089
1084 # construct a working dir manifest
1090 # construct a working dir manifest
1085 mw = m1.copy()
1091 mw = m1.copy()
1086 mfw = mf1.copy()
1092 mfw = mf1.copy()
1087 for f in a + c + u:
1093 for f in a + c + u:
1088 mw[f] = ""
1094 mw[f] = ""
1089 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1095 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1090 for f in d:
1096 for f in d:
1091 if f in mw: del mw[f]
1097 if f in mw: del mw[f]
1092
1098
1093 # If we're jumping between revisions (as opposed to merging),
1099 # If we're jumping between revisions (as opposed to merging),
1094 # and if neither the working directory nor the target rev has
1100 # and if neither the working directory nor the target rev has
1095 # the file, then we need to remove it from the dirstate, to
1101 # the file, then we need to remove it from the dirstate, to
1096 # prevent the dirstate from listing the file when it is no
1102 # prevent the dirstate from listing the file when it is no
1097 # longer in the manifest.
1103 # longer in the manifest.
1098 if linear_path and f not in m2:
1104 if linear_path and f not in m2:
1099 self.dirstate.forget((f,))
1105 self.dirstate.forget((f,))
1100
1106
1101 for f, n in mw.iteritems():
1107 for f, n in mw.iteritems():
1102 if f in m2:
1108 if f in m2:
1103 s = 0
1109 s = 0
1104
1110
1105 # is the wfile new since m1, and match m2?
1111 # is the wfile new since m1, and match m2?
1106 if f not in m1:
1112 if f not in m1:
1107 t1 = self.wfile(f).read()
1113 t1 = self.wfile(f).read()
1108 t2 = self.file(f).revision(m2[f])
1114 t2 = self.file(f).revision(m2[f])
1109 if cmp(t1, t2) == 0:
1115 if cmp(t1, t2) == 0:
1110 mark[f] = 1
1116 mark[f] = 1
1111 n = m2[f]
1117 n = m2[f]
1112 del t1, t2
1118 del t1, t2
1113
1119
1114 # are files different?
1120 # are files different?
1115 if n != m2[f]:
1121 if n != m2[f]:
1116 a = ma.get(f, nullid)
1122 a = ma.get(f, nullid)
1117 # are both different from the ancestor?
1123 # are both different from the ancestor?
1118 if n != a and m2[f] != a:
1124 if n != a and m2[f] != a:
1119 self.ui.debug(" %s versions differ, resolve\n" % f)
1125 self.ui.debug(" %s versions differ, resolve\n" % f)
1120 # merge executable bits
1126 # merge executable bits
1121 # "if we changed or they changed, change in merge"
1127 # "if we changed or they changed, change in merge"
1122 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1128 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1123 mode = ((a^b) | (a^c)) ^ a
1129 mode = ((a^b) | (a^c)) ^ a
1124 merge[f] = (m1.get(f, nullid), m2[f], mode)
1130 merge[f] = (m1.get(f, nullid), m2[f], mode)
1125 s = 1
1131 s = 1
1126 # are we clobbering?
1132 # are we clobbering?
1127 # is remote's version newer?
1133 # is remote's version newer?
1128 # or are we going back in time?
1134 # or are we going back in time?
1129 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1135 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1130 self.ui.debug(" remote %s is newer, get\n" % f)
1136 self.ui.debug(" remote %s is newer, get\n" % f)
1131 get[f] = m2[f]
1137 get[f] = m2[f]
1132 s = 1
1138 s = 1
1133 else:
1139 else:
1134 mark[f] = 1
1140 mark[f] = 1
1135
1141
1136 if not s and mfw[f] != mf2[f]:
1142 if not s and mfw[f] != mf2[f]:
1137 if force:
1143 if force:
1138 self.ui.debug(" updating permissions for %s\n" % f)
1144 self.ui.debug(" updating permissions for %s\n" % f)
1139 util.set_exec(self.wjoin(f), mf2[f])
1145 util.set_exec(self.wjoin(f), mf2[f])
1140 else:
1146 else:
1141 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1147 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1142 mode = ((a^b) | (a^c)) ^ a
1148 mode = ((a^b) | (a^c)) ^ a
1143 if mode != b:
1149 if mode != b:
1144 self.ui.debug(" updating permissions for %s\n" % f)
1150 self.ui.debug(" updating permissions for %s\n" % f)
1145 util.set_exec(self.wjoin(f), mode)
1151 util.set_exec(self.wjoin(f), mode)
1146 mark[f] = 1
1152 mark[f] = 1
1147 del m2[f]
1153 del m2[f]
1148 elif f in ma:
1154 elif f in ma:
1149 if not force and n != ma[f]:
1155 if not force and n != ma[f]:
1150 r = ""
1156 r = ""
1151 if linear_path or allow:
1157 if linear_path or allow:
1152 r = self.ui.prompt(
1158 r = self.ui.prompt(
1153 (" local changed %s which remote deleted\n" % f) +
1159 (" local changed %s which remote deleted\n" % f) +
1154 "(k)eep or (d)elete?", "[kd]", "k")
1160 "(k)eep or (d)elete?", "[kd]", "k")
1155 if r == "d":
1161 if r == "d":
1156 remove.append(f)
1162 remove.append(f)
1157 else:
1163 else:
1158 self.ui.debug("other deleted %s\n" % f)
1164 self.ui.debug("other deleted %s\n" % f)
1159 remove.append(f) # other deleted it
1165 remove.append(f) # other deleted it
1160 else:
1166 else:
1161 if n == m1.get(f, nullid): # same as parent
1167 if n == m1.get(f, nullid): # same as parent
1162 if p2 == pa: # going backwards?
1168 if p2 == pa: # going backwards?
1163 self.ui.debug("remote deleted %s\n" % f)
1169 self.ui.debug("remote deleted %s\n" % f)
1164 remove.append(f)
1170 remove.append(f)
1165 else:
1171 else:
1166 self.ui.debug("local created %s, keeping\n" % f)
1172 self.ui.debug("local created %s, keeping\n" % f)
1167 else:
1173 else:
1168 self.ui.debug("working dir created %s, keeping\n" % f)
1174 self.ui.debug("working dir created %s, keeping\n" % f)
1169
1175
1170 for f, n in m2.iteritems():
1176 for f, n in m2.iteritems():
1171 if f[0] == "/": continue
1177 if f[0] == "/": continue
1172 if not force and f in ma and n != ma[f]:
1178 if not force and f in ma and n != ma[f]:
1173 r = ""
1179 r = ""
1174 if linear_path or allow:
1180 if linear_path or allow:
1175 r = self.ui.prompt(
1181 r = self.ui.prompt(
1176 ("remote changed %s which local deleted\n" % f) +
1182 ("remote changed %s which local deleted\n" % f) +
1177 "(k)eep or (d)elete?", "[kd]", "k")
1183 "(k)eep or (d)elete?", "[kd]", "k")
1178 if r == "d": remove.append(f)
1184 if r == "d": remove.append(f)
1179 else:
1185 else:
1180 self.ui.debug("remote created %s\n" % f)
1186 self.ui.debug("remote created %s\n" % f)
1181 get[f] = n
1187 get[f] = n
1182
1188
1183 del mw, m1, m2, ma
1189 del mw, m1, m2, ma
1184
1190
1185 if force:
1191 if force:
1186 for f in merge:
1192 for f in merge:
1187 get[f] = merge[f][1]
1193 get[f] = merge[f][1]
1188 merge = {}
1194 merge = {}
1189
1195
1190 if linear_path:
1196 if linear_path:
1191 # we don't need to do any magic, just jump to the new rev
1197 # we don't need to do any magic, just jump to the new rev
1192 mode = 'n'
1198 mode = 'n'
1193 p1, p2 = p2, nullid
1199 p1, p2 = p2, nullid
1194 else:
1200 else:
1195 if not allow:
1201 if not allow:
1196 self.ui.status("this update spans a branch" +
1202 self.ui.status("this update spans a branch" +
1197 " affecting the following files:\n")
1203 " affecting the following files:\n")
1198 fl = merge.keys() + get.keys()
1204 fl = merge.keys() + get.keys()
1199 fl.sort()
1205 fl.sort()
1200 for f in fl:
1206 for f in fl:
1201 cf = ""
1207 cf = ""
1202 if f in merge: cf = " (resolve)"
1208 if f in merge: cf = " (resolve)"
1203 self.ui.status(" %s%s\n" % (f, cf))
1209 self.ui.status(" %s%s\n" % (f, cf))
1204 self.ui.warn("aborting update spanning branches!\n")
1210 self.ui.warn("aborting update spanning branches!\n")
1205 self.ui.status("(use update -m to perform a branch merge)\n")
1211 self.ui.status("(use update -m to perform a branch merge)\n")
1206 return 1
1212 return 1
1207 # we have to remember what files we needed to get/change
1213 # we have to remember what files we needed to get/change
1208 # because any file that's different from either one of its
1214 # because any file that's different from either one of its
1209 # parents must be in the changeset
1215 # parents must be in the changeset
1210 mode = 'm'
1216 mode = 'm'
1211 self.dirstate.update(mark.keys(), "m")
1217 self.dirstate.update(mark.keys(), "m")
1212
1218
1213 self.dirstate.setparents(p1, p2)
1219 self.dirstate.setparents(p1, p2)
1214
1220
1215 # get the files we don't need to change
1221 # get the files we don't need to change
1216 files = get.keys()
1222 files = get.keys()
1217 files.sort()
1223 files.sort()
1218 for f in files:
1224 for f in files:
1219 if f[0] == "/": continue
1225 if f[0] == "/": continue
1220 self.ui.note("getting %s\n" % f)
1226 self.ui.note("getting %s\n" % f)
1221 t = self.file(f).read(get[f])
1227 t = self.file(f).read(get[f])
1222 try:
1228 try:
1223 self.wfile(f, "w").write(t)
1229 self.wfile(f, "w").write(t)
1224 except IOError:
1230 except IOError:
1225 os.makedirs(os.path.dirname(self.wjoin(f)))
1231 os.makedirs(os.path.dirname(self.wjoin(f)))
1226 self.wfile(f, "w").write(t)
1232 self.wfile(f, "w").write(t)
1227 util.set_exec(self.wjoin(f), mf2[f])
1233 util.set_exec(self.wjoin(f), mf2[f])
1228 self.dirstate.update([f], mode)
1234 self.dirstate.update([f], mode)
1229
1235
1230 # merge the tricky bits
1236 # merge the tricky bits
1231 files = merge.keys()
1237 files = merge.keys()
1232 files.sort()
1238 files.sort()
1233 for f in files:
1239 for f in files:
1234 self.ui.status("merging %s\n" % f)
1240 self.ui.status("merging %s\n" % f)
1235 m, o, flag = merge[f]
1241 m, o, flag = merge[f]
1236 self.merge3(f, m, o)
1242 self.merge3(f, m, o)
1237 util.set_exec(self.wjoin(f), flag)
1243 util.set_exec(self.wjoin(f), flag)
1238 self.dirstate.update([f], 'm')
1244 self.dirstate.update([f], 'm')
1239
1245
1240 for f in remove:
1246 for f in remove:
1241 self.ui.note("removing %s\n" % f)
1247 self.ui.note("removing %s\n" % f)
1242 os.unlink(f)
1248 os.unlink(f)
1243 if mode == 'n':
1249 if mode == 'n':
1244 self.dirstate.forget(remove)
1250 self.dirstate.forget(remove)
1245 else:
1251 else:
1246 self.dirstate.update(remove, 'r')
1252 self.dirstate.update(remove, 'r')
1247
1253
1248 def merge3(self, fn, my, other):
1254 def merge3(self, fn, my, other):
1249 """perform a 3-way merge in the working directory"""
1255 """perform a 3-way merge in the working directory"""
1250
1256
1251 def temp(prefix, node):
1257 def temp(prefix, node):
1252 pre = "%s~%s." % (os.path.basename(fn), prefix)
1258 pre = "%s~%s." % (os.path.basename(fn), prefix)
1253 (fd, name) = tempfile.mkstemp("", pre)
1259 (fd, name) = tempfile.mkstemp("", pre)
1254 f = os.fdopen(fd, "wb")
1260 f = os.fdopen(fd, "wb")
1255 f.write(fl.revision(node))
1261 f.write(fl.revision(node))
1256 f.close()
1262 f.close()
1257 return name
1263 return name
1258
1264
1259 fl = self.file(fn)
1265 fl = self.file(fn)
1260 base = fl.ancestor(my, other)
1266 base = fl.ancestor(my, other)
1261 a = self.wjoin(fn)
1267 a = self.wjoin(fn)
1262 b = temp("base", base)
1268 b = temp("base", base)
1263 c = temp("other", other)
1269 c = temp("other", other)
1264
1270
1265 self.ui.note("resolving %s\n" % fn)
1271 self.ui.note("resolving %s\n" % fn)
1266 self.ui.debug("file %s: other %s ancestor %s\n" %
1272 self.ui.debug("file %s: other %s ancestor %s\n" %
1267 (fn, short(other), short(base)))
1273 (fn, short(other), short(base)))
1268
1274
1269 cmd = os.environ.get("HGMERGE", "hgmerge")
1275 cmd = os.environ.get("HGMERGE", "hgmerge")
1270 r = os.system("%s %s %s %s" % (cmd, a, b, c))
1276 r = os.system("%s %s %s %s" % (cmd, a, b, c))
1271 if r:
1277 if r:
1272 self.ui.warn("merging %s failed!\n" % fn)
1278 self.ui.warn("merging %s failed!\n" % fn)
1273
1279
1274 os.unlink(b)
1280 os.unlink(b)
1275 os.unlink(c)
1281 os.unlink(c)
1276
1282
1277 def verify(self):
1283 def verify(self):
1278 filelinkrevs = {}
1284 filelinkrevs = {}
1279 filenodes = {}
1285 filenodes = {}
1280 changesets = revisions = files = 0
1286 changesets = revisions = files = 0
1281 errors = 0
1287 errors = 0
1282
1288
1283 seen = {}
1289 seen = {}
1284 self.ui.status("checking changesets\n")
1290 self.ui.status("checking changesets\n")
1285 for i in range(self.changelog.count()):
1291 for i in range(self.changelog.count()):
1286 changesets += 1
1292 changesets += 1
1287 n = self.changelog.node(i)
1293 n = self.changelog.node(i)
1288 if n in seen:
1294 if n in seen:
1289 self.ui.warn("duplicate changeset at revision %d\n" % i)
1295 self.ui.warn("duplicate changeset at revision %d\n" % i)
1290 errors += 1
1296 errors += 1
1291 seen[n] = 1
1297 seen[n] = 1
1292
1298
1293 for p in self.changelog.parents(n):
1299 for p in self.changelog.parents(n):
1294 if p not in self.changelog.nodemap:
1300 if p not in self.changelog.nodemap:
1295 self.ui.warn("changeset %s has unknown parent %s\n" %
1301 self.ui.warn("changeset %s has unknown parent %s\n" %
1296 (short(n), short(p)))
1302 (short(n), short(p)))
1297 errors += 1
1303 errors += 1
1298 try:
1304 try:
1299 changes = self.changelog.read(n)
1305 changes = self.changelog.read(n)
1300 except Exception, inst:
1306 except Exception, inst:
1301 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1307 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1302 errors += 1
1308 errors += 1
1303
1309
1304 for f in changes[3]:
1310 for f in changes[3]:
1305 filelinkrevs.setdefault(f, []).append(i)
1311 filelinkrevs.setdefault(f, []).append(i)
1306
1312
1307 seen = {}
1313 seen = {}
1308 self.ui.status("checking manifests\n")
1314 self.ui.status("checking manifests\n")
1309 for i in range(self.manifest.count()):
1315 for i in range(self.manifest.count()):
1310 n = self.manifest.node(i)
1316 n = self.manifest.node(i)
1311 if n in seen:
1317 if n in seen:
1312 self.ui.warn("duplicate manifest at revision %d\n" % i)
1318 self.ui.warn("duplicate manifest at revision %d\n" % i)
1313 errors += 1
1319 errors += 1
1314 seen[n] = 1
1320 seen[n] = 1
1315
1321
1316 for p in self.manifest.parents(n):
1322 for p in self.manifest.parents(n):
1317 if p not in self.manifest.nodemap:
1323 if p not in self.manifest.nodemap:
1318 self.ui.warn("manifest %s has unknown parent %s\n" %
1324 self.ui.warn("manifest %s has unknown parent %s\n" %
1319 (short(n), short(p)))
1325 (short(n), short(p)))
1320 errors += 1
1326 errors += 1
1321
1327
1322 try:
1328 try:
1323 delta = mdiff.patchtext(self.manifest.delta(n))
1329 delta = mdiff.patchtext(self.manifest.delta(n))
1324 except KeyboardInterrupt:
1330 except KeyboardInterrupt:
1325 print "aborted"
1331 print "aborted"
1326 sys.exit(0)
1332 sys.exit(0)
1327 except Exception, inst:
1333 except Exception, inst:
1328 self.ui.warn("unpacking manifest %s: %s\n"
1334 self.ui.warn("unpacking manifest %s: %s\n"
1329 % (short(n), inst))
1335 % (short(n), inst))
1330 errors += 1
1336 errors += 1
1331
1337
1332 ff = [ l.split('\0') for l in delta.splitlines() ]
1338 ff = [ l.split('\0') for l in delta.splitlines() ]
1333 for f, fn in ff:
1339 for f, fn in ff:
1334 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1340 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1335
1341
1336 self.ui.status("crosschecking files in changesets and manifests\n")
1342 self.ui.status("crosschecking files in changesets and manifests\n")
1337 for f in filenodes:
1343 for f in filenodes:
1338 if f not in filelinkrevs:
1344 if f not in filelinkrevs:
1339 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1345 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1340 errors += 1
1346 errors += 1
1341
1347
1342 for f in filelinkrevs:
1348 for f in filelinkrevs:
1343 if f not in filenodes:
1349 if f not in filenodes:
1344 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1350 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1345 errors += 1
1351 errors += 1
1346
1352
1347 self.ui.status("checking files\n")
1353 self.ui.status("checking files\n")
1348 ff = filenodes.keys()
1354 ff = filenodes.keys()
1349 ff.sort()
1355 ff.sort()
1350 for f in ff:
1356 for f in ff:
1351 if f == "/dev/null": continue
1357 if f == "/dev/null": continue
1352 files += 1
1358 files += 1
1353 fl = self.file(f)
1359 fl = self.file(f)
1354 nodes = { nullid: 1 }
1360 nodes = { nullid: 1 }
1355 seen = {}
1361 seen = {}
1356 for i in range(fl.count()):
1362 for i in range(fl.count()):
1357 revisions += 1
1363 revisions += 1
1358 n = fl.node(i)
1364 n = fl.node(i)
1359
1365
1360 if n in seen:
1366 if n in seen:
1361 self.ui.warn("%s: duplicate revision %d\n" % (f, i))
1367 self.ui.warn("%s: duplicate revision %d\n" % (f, i))
1362 errors += 1
1368 errors += 1
1363
1369
1364 if n not in filenodes[f]:
1370 if n not in filenodes[f]:
1365 self.ui.warn("%s: %d:%s not in manifests\n"
1371 self.ui.warn("%s: %d:%s not in manifests\n"
1366 % (f, i, short(n)))
1372 % (f, i, short(n)))
1367 print len(filenodes[f].keys()), fl.count(), f
1373 print len(filenodes[f].keys()), fl.count(), f
1368 errors += 1
1374 errors += 1
1369 else:
1375 else:
1370 del filenodes[f][n]
1376 del filenodes[f][n]
1371
1377
1372 flr = fl.linkrev(n)
1378 flr = fl.linkrev(n)
1373 if flr not in filelinkrevs[f]:
1379 if flr not in filelinkrevs[f]:
1374 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1380 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1375 % (f, short(n), fl.linkrev(n)))
1381 % (f, short(n), fl.linkrev(n)))
1376 errors += 1
1382 errors += 1
1377 else:
1383 else:
1378 filelinkrevs[f].remove(flr)
1384 filelinkrevs[f].remove(flr)
1379
1385
1380 # verify contents
1386 # verify contents
1381 try:
1387 try:
1382 t = fl.read(n)
1388 t = fl.read(n)
1383 except Exception, inst:
1389 except Exception, inst:
1384 self.ui.warn("unpacking file %s %s: %s\n"
1390 self.ui.warn("unpacking file %s %s: %s\n"
1385 % (f, short(n), inst))
1391 % (f, short(n), inst))
1386 errors += 1
1392 errors += 1
1387
1393
1388 # verify parents
1394 # verify parents
1389 (p1, p2) = fl.parents(n)
1395 (p1, p2) = fl.parents(n)
1390 if p1 not in nodes:
1396 if p1 not in nodes:
1391 self.ui.warn("file %s:%s unknown parent 1 %s" %
1397 self.ui.warn("file %s:%s unknown parent 1 %s" %
1392 (f, short(n), short(p1)))
1398 (f, short(n), short(p1)))
1393 errors += 1
1399 errors += 1
1394 if p2 not in nodes:
1400 if p2 not in nodes:
1395 self.ui.warn("file %s:%s unknown parent 2 %s" %
1401 self.ui.warn("file %s:%s unknown parent 2 %s" %
1396 (f, short(n), short(p1)))
1402 (f, short(n), short(p1)))
1397 errors += 1
1403 errors += 1
1398 nodes[n] = 1
1404 nodes[n] = 1
1399
1405
1400 # cross-check
1406 # cross-check
1401 for node in filenodes[f]:
1407 for node in filenodes[f]:
1402 self.ui.warn("node %s in manifests not in %s\n"
1408 self.ui.warn("node %s in manifests not in %s\n"
1403 % (hex(n), f))
1409 % (hex(n), f))
1404 errors += 1
1410 errors += 1
1405
1411
1406 self.ui.status("%d files, %d changesets, %d total revisions\n" %
1412 self.ui.status("%d files, %d changesets, %d total revisions\n" %
1407 (files, changesets, revisions))
1413 (files, changesets, revisions))
1408
1414
1409 if errors:
1415 if errors:
1410 self.ui.warn("%d integrity errors encountered!\n" % errors)
1416 self.ui.warn("%d integrity errors encountered!\n" % errors)
1411 return 1
1417 return 1
1412
1418
1413 class remoterepository:
1419 class remoterepository:
1414 def __init__(self, ui, path):
1420 def __init__(self, ui, path):
1415 self.url = path
1421 self.url = path
1416 self.ui = ui
1422 self.ui = ui
1417 no_list = [ "localhost", "127.0.0.1" ]
1423 no_list = [ "localhost", "127.0.0.1" ]
1418 host = ui.config("http_proxy", "host")
1424 host = ui.config("http_proxy", "host")
1419 if host is None:
1425 if host is None:
1420 host = os.environ.get("http_proxy")
1426 host = os.environ.get("http_proxy")
1421 if host and host.startswith('http://'):
1427 if host and host.startswith('http://'):
1422 host = host[7:]
1428 host = host[7:]
1423 user = ui.config("http_proxy", "user")
1429 user = ui.config("http_proxy", "user")
1424 passwd = ui.config("http_proxy", "passwd")
1430 passwd = ui.config("http_proxy", "passwd")
1425 no = ui.config("http_proxy", "no")
1431 no = ui.config("http_proxy", "no")
1426 if no is None:
1432 if no is None:
1427 no = os.environ.get("no_proxy")
1433 no = os.environ.get("no_proxy")
1428 if no:
1434 if no:
1429 no_list = no_list + no.split(",")
1435 no_list = no_list + no.split(",")
1430
1436
1431 no_proxy = 0
1437 no_proxy = 0
1432 for h in no_list:
1438 for h in no_list:
1433 if (path.startswith("http://" + h + "/") or
1439 if (path.startswith("http://" + h + "/") or
1434 path.startswith("http://" + h + ":") or
1440 path.startswith("http://" + h + ":") or
1435 path == "http://" + h):
1441 path == "http://" + h):
1436 no_proxy = 1
1442 no_proxy = 1
1437
1443
1438 # Note: urllib2 takes proxy values from the environment and those will
1444 # Note: urllib2 takes proxy values from the environment and those will
1439 # take precedence
1445 # take precedence
1440 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
1446 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
1441 if os.environ.has_key(env):
1447 if os.environ.has_key(env):
1442 del os.environ[env]
1448 del os.environ[env]
1443
1449
1444 proxy_handler = urllib2.BaseHandler()
1450 proxy_handler = urllib2.BaseHandler()
1445 if host and not no_proxy:
1451 if host and not no_proxy:
1446 proxy_handler = urllib2.ProxyHandler({"http" : "http://" + host})
1452 proxy_handler = urllib2.ProxyHandler({"http" : "http://" + host})
1447
1453
1448 authinfo = None
1454 authinfo = None
1449 if user and passwd:
1455 if user and passwd:
1450 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
1456 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
1451 passmgr.add_password(None, host, user, passwd)
1457 passmgr.add_password(None, host, user, passwd)
1452 authinfo = urllib2.ProxyBasicAuthHandler(passmgr)
1458 authinfo = urllib2.ProxyBasicAuthHandler(passmgr)
1453
1459
1454 opener = urllib2.build_opener(proxy_handler, authinfo)
1460 opener = urllib2.build_opener(proxy_handler, authinfo)
1455 urllib2.install_opener(opener)
1461 urllib2.install_opener(opener)
1456
1462
1457 def do_cmd(self, cmd, **args):
1463 def do_cmd(self, cmd, **args):
1458 self.ui.debug("sending %s command\n" % cmd)
1464 self.ui.debug("sending %s command\n" % cmd)
1459 q = {"cmd": cmd}
1465 q = {"cmd": cmd}
1460 q.update(args)
1466 q.update(args)
1461 qs = urllib.urlencode(q)
1467 qs = urllib.urlencode(q)
1462 cu = "%s?%s" % (self.url, qs)
1468 cu = "%s?%s" % (self.url, qs)
1463 return urllib2.urlopen(cu)
1469 return urllib2.urlopen(cu)
1464
1470
1465 def heads(self):
1471 def heads(self):
1466 d = self.do_cmd("heads").read()
1472 d = self.do_cmd("heads").read()
1467 try:
1473 try:
1468 return map(bin, d[:-1].split(" "))
1474 return map(bin, d[:-1].split(" "))
1469 except:
1475 except:
1470 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1476 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1471 raise
1477 raise
1472
1478
1473 def branches(self, nodes):
1479 def branches(self, nodes):
1474 n = " ".join(map(hex, nodes))
1480 n = " ".join(map(hex, nodes))
1475 d = self.do_cmd("branches", nodes=n).read()
1481 d = self.do_cmd("branches", nodes=n).read()
1476 try:
1482 try:
1477 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1483 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1478 return br
1484 return br
1479 except:
1485 except:
1480 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1486 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1481 raise
1487 raise
1482
1488
1483 def between(self, pairs):
1489 def between(self, pairs):
1484 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1490 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1485 d = self.do_cmd("between", pairs=n).read()
1491 d = self.do_cmd("between", pairs=n).read()
1486 try:
1492 try:
1487 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1493 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1488 return p
1494 return p
1489 except:
1495 except:
1490 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1496 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1491 raise
1497 raise
1492
1498
1493 def changegroup(self, nodes):
1499 def changegroup(self, nodes):
1494 n = " ".join(map(hex, nodes))
1500 n = " ".join(map(hex, nodes))
1495 zd = zlib.decompressobj()
1501 zd = zlib.decompressobj()
1496 f = self.do_cmd("changegroup", roots=n)
1502 f = self.do_cmd("changegroup", roots=n)
1497 bytes = 0
1503 bytes = 0
1498 while 1:
1504 while 1:
1499 d = f.read(4096)
1505 d = f.read(4096)
1500 bytes += len(d)
1506 bytes += len(d)
1501 if not d:
1507 if not d:
1502 yield zd.flush()
1508 yield zd.flush()
1503 break
1509 break
1504 yield zd.decompress(d)
1510 yield zd.decompress(d)
1505 self.ui.note("%d bytes of data transfered\n" % bytes)
1511 self.ui.note("%d bytes of data transfered\n" % bytes)
1506
1512
1507 def repository(ui, path=None, create=0):
1513 def repository(ui, path=None, create=0):
1508 if path and path[:7] == "http://":
1514 if path and path[:7] == "http://":
1509 return remoterepository(ui, path)
1515 return remoterepository(ui, path)
1510 if path and path[:5] == "hg://":
1516 if path and path[:5] == "hg://":
1511 return remoterepository(ui, path.replace("hg://", "http://"))
1517 return remoterepository(ui, path.replace("hg://", "http://"))
1512 if path and path[:11] == "old-http://":
1518 if path and path[:11] == "old-http://":
1513 return localrepository(ui, path.replace("old-http://", "http://"))
1519 return localrepository(ui, path.replace("old-http://", "http://"))
1514 else:
1520 else:
1515 return localrepository(ui, path, create)
1521 return localrepository(ui, path, create)
1516
1522
@@ -1,10 +1,15 b''
1 diff -r 3903775176ed b
1 diff -r 3903775176ed b
2 --- /dev/null
2 --- /dev/null
3 +++ b/b
3 +++ b/b
4 @@ -0,0 +1,1 @@
4 @@ -0,0 +1,1 @@
5 +123
5 +123
6 diff -r 3903775176ed b
6 diff -r 3903775176ed b
7 --- /dev/null
7 --- /dev/null
8 +++ b/b
8 +++ b/b
9 @@ -0,0 +1,1 @@
9 @@ -0,0 +1,1 @@
10 +123
10 +123
11 diff -r 3903775176ed b
12 --- /dev/null
13 +++ b/b
14 @@ -0,0 +1,1 @@
15 +123
@@ -1,47 +1,47 b''
1 + mkdir t
1 + mkdir t
2 + cd t
2 + cd t
3 + hg init
3 + hg init
4 + hg id
4 + hg id
5 unknown
5 unknown
6 + echo a
6 + echo a
7 + hg add a
7 + hg add a
8 + hg commit -t test -u test -d '0 0'
8 + hg commit -t test -u test -d '0 0'
9 + hg co
9 + hg co
10 + hg identify
10 + hg identify
11 acb14030fe0a tip
11 acb14030fe0a tip
12 ++ hg -q tip
12 ++ hg -q tip
13 ++ cut -d : -f 2
13 ++ cut -d : -f 2
14 + T=acb14030fe0a21b60322c440ad2d20cf7685a376
14 + T=acb14030fe0a21b60322c440ad2d20cf7685a376
15 + echo 'acb14030fe0a21b60322c440ad2d20cf7685a376 first'
15 + echo 'acb14030fe0a21b60322c440ad2d20cf7685a376 first'
16 + cat .hgtags
16 + cat .hgtags
17 acb14030fe0a21b60322c440ad2d20cf7685a376 first
17 acb14030fe0a21b60322c440ad2d20cf7685a376 first
18 + hg add .hgtags
18 + hg add .hgtags
19 + hg commit -t 'add tags' -u test -d '0 0'
19 + hg commit -t 'add tags' -u test -d '0 0'
20 + hg tags
20 + hg tags
21 tip 1:b9154636be938d3d431e75a7c906504a079bfe07
21 tip 1:b9154636be938d3d431e75a7c906504a079bfe07
22 first 0:acb14030fe0a21b60322c440ad2d20cf7685a376
22 first 0:acb14030fe0a21b60322c440ad2d20cf7685a376
23 + hg identify
23 + hg identify
24 b9154636be93 tip
24 b9154636be93 tip
25 + echo bb
25 + echo bb
26 + hg status
26 + hg status
27 C a
27 C a
28 + hg identify
28 + hg identify
29 b9154636be93+ tip
29 b9154636be93+ tip
30 + hg co first
30 + hg co first
31 + hg id
31 + hg id
32 acb14030fe0a+ first
32 acb14030fe0a+ first
33 + hg -v id
33 + hg -v id
34 acb14030fe0a21b60322c440ad2d20cf7685a376+ first
34 acb14030fe0a21b60322c440ad2d20cf7685a376+ first
35 + hg status
35 + hg status
36 C a
36 C a
37 + echo 1
37 + echo 1
38 + hg add b
38 + hg add b
39 + hg commit -t branch -u test -d '0 0'
39 + hg commit -t branch -u test -d '0 0'
40 + hg id
40 + hg id
41 c8edf04160c7 tip
41 c8edf04160c7 tip
42 + hg co -m 1
42 + hg co -m 1
43 + hg id
43 + hg id
44 c8edf04160c7+b9154636be93+ tip
44 c8edf04160c7+b9154636be93+ tip
45 + hg status
45 + hg status
46 C .hgtags
46 C a
47 C a
47 C .hgtags
General Comments 0
You need to be logged in to leave comments. Login now