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