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