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