##// END OF EJS Templates
make the order of the arguments for filterfiles consistent...
Benoit Boissinot -
r1626:f2b1df3d default
parent child Browse files
Show More
@@ -1,339 +1,339 b''
1 # Minimal support for git commands on an hg repository
1 # Minimal support for git commands on an hg repository
2 #
2 #
3 # Copyright 2005 Chris Mason <mason@suse.com>
3 # Copyright 2005 Chris Mason <mason@suse.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 time, sys, signal, os
8 import time, sys, signal, os
9 from mercurial import hg, mdiff, fancyopts, commands, ui, util
9 from mercurial import hg, mdiff, fancyopts, commands, ui, util
10
10
11 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
11 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
12 changes=None, text=False):
12 changes=None, text=False):
13 def date(c):
13 def date(c):
14 return time.asctime(time.gmtime(c[2][0]))
14 return time.asctime(time.gmtime(c[2][0]))
15
15
16 if not changes:
16 if not changes:
17 changes = repo.changes(node1, node2, files, match=match)
17 changes = repo.changes(node1, node2, files, match=match)
18 modified, added, removed, deleted, unknown = changes
18 modified, added, removed, deleted, unknown = changes
19 if files:
19 if files:
20 modified, added, removed = map(lambda x: filterfiles(x, files),
20 modified, added, removed = map(lambda x: filterfiles(files, x),
21 (modified, added, removed))
21 (modified, added, removed))
22
22
23 if not modified and not added and not removed:
23 if not modified and not added and not removed:
24 return
24 return
25
25
26 if node2:
26 if node2:
27 change = repo.changelog.read(node2)
27 change = repo.changelog.read(node2)
28 mmap2 = repo.manifest.read(change[0])
28 mmap2 = repo.manifest.read(change[0])
29 date2 = date(change)
29 date2 = date(change)
30 def read(f):
30 def read(f):
31 return repo.file(f).read(mmap2[f])
31 return repo.file(f).read(mmap2[f])
32 else:
32 else:
33 date2 = time.asctime()
33 date2 = time.asctime()
34 if not node1:
34 if not node1:
35 node1 = repo.dirstate.parents()[0]
35 node1 = repo.dirstate.parents()[0]
36 def read(f):
36 def read(f):
37 return repo.wfile(f).read()
37 return repo.wfile(f).read()
38
38
39 change = repo.changelog.read(node1)
39 change = repo.changelog.read(node1)
40 mmap = repo.manifest.read(change[0])
40 mmap = repo.manifest.read(change[0])
41 date1 = date(change)
41 date1 = date(change)
42
42
43 for f in modified:
43 for f in modified:
44 to = None
44 to = None
45 if f in mmap:
45 if f in mmap:
46 to = repo.file(f).read(mmap[f])
46 to = repo.file(f).read(mmap[f])
47 tn = read(f)
47 tn = read(f)
48 fp.write("diff --git a/%s b/%s\n" % (f, f))
48 fp.write("diff --git a/%s b/%s\n" % (f, f))
49 fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
49 fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
50 for f in added:
50 for f in added:
51 to = None
51 to = None
52 tn = read(f)
52 tn = read(f)
53 fp.write("diff --git /dev/null b/%s\n" % (f))
53 fp.write("diff --git /dev/null b/%s\n" % (f))
54 fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
54 fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
55 for f in removed:
55 for f in removed:
56 to = repo.file(f).read(mmap[f])
56 to = repo.file(f).read(mmap[f])
57 tn = None
57 tn = None
58 fp.write("diff --git a/%s /dev/null\n" % (f))
58 fp.write("diff --git a/%s /dev/null\n" % (f))
59 fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
59 fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
60
60
61 def difftree(ui, repo, node1=None, node2=None, **opts):
61 def difftree(ui, repo, node1=None, node2=None, **opts):
62 """diff trees from two commits"""
62 """diff trees from two commits"""
63 def __difftree(repo, node1, node2):
63 def __difftree(repo, node1, node2):
64 def date(c):
64 def date(c):
65 return time.asctime(time.gmtime(c[2][0]))
65 return time.asctime(time.gmtime(c[2][0]))
66
66
67 if node2:
67 if node2:
68 change = repo.changelog.read(node2)
68 change = repo.changelog.read(node2)
69 mmap2 = repo.manifest.read(change[0])
69 mmap2 = repo.manifest.read(change[0])
70 modified, added, removed, deleted, unknown = repo.changes(node1, node2)
70 modified, added, removed, deleted, unknown = repo.changes(node1, node2)
71 def read(f): return repo.file(f).read(mmap2[f])
71 def read(f): return repo.file(f).read(mmap2[f])
72 date2 = date(change)
72 date2 = date(change)
73 else:
73 else:
74 date2 = time.asctime()
74 date2 = time.asctime()
75 modified, added, removed, deleted, unknown = repo.changes(node1)
75 modified, added, removed, deleted, unknown = repo.changes(node1)
76 if not node1:
76 if not node1:
77 node1 = repo.dirstate.parents()[0]
77 node1 = repo.dirstate.parents()[0]
78 def read(f): return file(os.path.join(repo.root, f)).read()
78 def read(f): return file(os.path.join(repo.root, f)).read()
79
79
80 change = repo.changelog.read(node1)
80 change = repo.changelog.read(node1)
81 mmap = repo.manifest.read(change[0])
81 mmap = repo.manifest.read(change[0])
82 date1 = date(change)
82 date1 = date(change)
83 empty = "0" * 40;
83 empty = "0" * 40;
84
84
85 for f in modified:
85 for f in modified:
86 # TODO get file permissions
86 # TODO get file permissions
87 print ":100664 100664 %s %s M\t%s\t%s" % (hg.hex(mmap[f]),
87 print ":100664 100664 %s %s M\t%s\t%s" % (hg.hex(mmap[f]),
88 hg.hex(mmap2[f]), f, f)
88 hg.hex(mmap2[f]), f, f)
89 for f in added:
89 for f in added:
90 print ":000000 100664 %s %s N\t%s\t%s" % (empty, hg.hex(mmap2[f]), f, f)
90 print ":000000 100664 %s %s N\t%s\t%s" % (empty, hg.hex(mmap2[f]), f, f)
91 for f in removed:
91 for f in removed:
92 print ":100664 000000 %s %s D\t%s\t%s" % (hg.hex(mmap[f]), empty, f, f)
92 print ":100664 000000 %s %s D\t%s\t%s" % (hg.hex(mmap[f]), empty, f, f)
93 ##
93 ##
94
94
95 while True:
95 while True:
96 if opts['stdin']:
96 if opts['stdin']:
97 try:
97 try:
98 line = raw_input().split(' ')
98 line = raw_input().split(' ')
99 node1 = line[0]
99 node1 = line[0]
100 if len(line) > 1:
100 if len(line) > 1:
101 node2 = line[1]
101 node2 = line[1]
102 else:
102 else:
103 node2 = None
103 node2 = None
104 except EOFError:
104 except EOFError:
105 break
105 break
106 node1 = repo.lookup(node1)
106 node1 = repo.lookup(node1)
107 if node2:
107 if node2:
108 node2 = repo.lookup(node2)
108 node2 = repo.lookup(node2)
109 else:
109 else:
110 node2 = node1
110 node2 = node1
111 node1 = repo.changelog.parents(node1)[0]
111 node1 = repo.changelog.parents(node1)[0]
112 if opts['patch']:
112 if opts['patch']:
113 if opts['pretty']:
113 if opts['pretty']:
114 catcommit(repo, node2, "")
114 catcommit(repo, node2, "")
115 dodiff(sys.stdout, ui, repo, node1, node2)
115 dodiff(sys.stdout, ui, repo, node1, node2)
116 else:
116 else:
117 __difftree(repo, node1, node2)
117 __difftree(repo, node1, node2)
118 if not opts['stdin']:
118 if not opts['stdin']:
119 break
119 break
120
120
121 def catcommit(repo, n, prefix, changes=None):
121 def catcommit(repo, n, prefix, changes=None):
122 nlprefix = '\n' + prefix;
122 nlprefix = '\n' + prefix;
123 (p1, p2) = repo.changelog.parents(n)
123 (p1, p2) = repo.changelog.parents(n)
124 (h, h1, h2) = map(hg.hex, (n, p1, p2))
124 (h, h1, h2) = map(hg.hex, (n, p1, p2))
125 (i1, i2) = map(repo.changelog.rev, (p1, p2))
125 (i1, i2) = map(repo.changelog.rev, (p1, p2))
126 if not changes:
126 if not changes:
127 changes = repo.changelog.read(n)
127 changes = repo.changelog.read(n)
128 print "tree %s" % (hg.hex(changes[0]))
128 print "tree %s" % (hg.hex(changes[0]))
129 if i1 != -1: print "parent %s" % (h1)
129 if i1 != -1: print "parent %s" % (h1)
130 if i2 != -1: print "parent %s" % (h2)
130 if i2 != -1: print "parent %s" % (h2)
131 date_ar = changes[2]
131 date_ar = changes[2]
132 date = int(float(date_ar[0]))
132 date = int(float(date_ar[0]))
133 lines = changes[4].splitlines()
133 lines = changes[4].splitlines()
134 if lines[-1].startswith('committer:'):
134 if lines[-1].startswith('committer:'):
135 committer = lines[-1].split(': ')[1].rstrip()
135 committer = lines[-1].split(': ')[1].rstrip()
136 else:
136 else:
137 committer = "%s %s %s" % (changes[1], date, date_ar[1])
137 committer = "%s %s %s" % (changes[1], date, date_ar[1])
138
138
139 print "author %s %s %s" % (changes[1], date, date_ar[1])
139 print "author %s %s %s" % (changes[1], date, date_ar[1])
140 print "committer %s" % (committer)
140 print "committer %s" % (committer)
141 print ""
141 print ""
142 if prefix != "":
142 if prefix != "":
143 print "%s%s" % (prefix, changes[4].replace('\n', nlprefix).strip())
143 print "%s%s" % (prefix, changes[4].replace('\n', nlprefix).strip())
144 else:
144 else:
145 print changes[4]
145 print changes[4]
146 if prefix:
146 if prefix:
147 sys.stdout.write('\0')
147 sys.stdout.write('\0')
148
148
149 def base(ui, repo, node1, node2):
149 def base(ui, repo, node1, node2):
150 """Output common ancestor information"""
150 """Output common ancestor information"""
151 node1 = repo.lookup(node1)
151 node1 = repo.lookup(node1)
152 node2 = repo.lookup(node2)
152 node2 = repo.lookup(node2)
153 n = repo.changelog.ancestor(node1, node2)
153 n = repo.changelog.ancestor(node1, node2)
154 print hg.hex(n)
154 print hg.hex(n)
155
155
156 def catfile(ui, repo, type=None, r=None, **opts):
156 def catfile(ui, repo, type=None, r=None, **opts):
157 """cat a specific revision"""
157 """cat a specific revision"""
158 # in stdin mode, every line except the commit is prefixed with two
158 # in stdin mode, every line except the commit is prefixed with two
159 # spaces. This way the our caller can find the commit without magic
159 # spaces. This way the our caller can find the commit without magic
160 # strings
160 # strings
161 #
161 #
162 prefix = ""
162 prefix = ""
163 if opts['stdin']:
163 if opts['stdin']:
164 try:
164 try:
165 (type, r) = raw_input().split(' ');
165 (type, r) = raw_input().split(' ');
166 prefix = " "
166 prefix = " "
167 except EOFError:
167 except EOFError:
168 return
168 return
169
169
170 else:
170 else:
171 if not type or not r:
171 if not type or not r:
172 ui.warn("cat-file: type or revision not supplied\n")
172 ui.warn("cat-file: type or revision not supplied\n")
173 commands.help_(ui, 'cat-file')
173 commands.help_(ui, 'cat-file')
174
174
175 while r:
175 while r:
176 if type != "commit":
176 if type != "commit":
177 sys.stderr.write("aborting hg cat-file only understands commits\n")
177 sys.stderr.write("aborting hg cat-file only understands commits\n")
178 sys.exit(1);
178 sys.exit(1);
179 n = repo.lookup(r)
179 n = repo.lookup(r)
180 catcommit(repo, n, prefix)
180 catcommit(repo, n, prefix)
181 if opts['stdin']:
181 if opts['stdin']:
182 try:
182 try:
183 (type, r) = raw_input().split(' ');
183 (type, r) = raw_input().split(' ');
184 except EOFError:
184 except EOFError:
185 break
185 break
186 else:
186 else:
187 break
187 break
188
188
189 # git rev-tree is a confusing thing. You can supply a number of
189 # git rev-tree is a confusing thing. You can supply a number of
190 # commit sha1s on the command line, and it walks the commit history
190 # commit sha1s on the command line, and it walks the commit history
191 # telling you which commits are reachable from the supplied ones via
191 # telling you which commits are reachable from the supplied ones via
192 # a bitmask based on arg position.
192 # a bitmask based on arg position.
193 # you can specify a commit to stop at by starting the sha1 with ^
193 # you can specify a commit to stop at by starting the sha1 with ^
194 def revtree(args, repo, full="tree", maxnr=0, parents=False):
194 def revtree(args, repo, full="tree", maxnr=0, parents=False):
195 def chlogwalk():
195 def chlogwalk():
196 ch = repo.changelog
196 ch = repo.changelog
197 count = ch.count()
197 count = ch.count()
198 i = count
198 i = count
199 l = [0] * 100
199 l = [0] * 100
200 chunk = 100
200 chunk = 100
201 while True:
201 while True:
202 if chunk > i:
202 if chunk > i:
203 chunk = i
203 chunk = i
204 i = 0
204 i = 0
205 else:
205 else:
206 i -= chunk
206 i -= chunk
207
207
208 for x in xrange(0, chunk):
208 for x in xrange(0, chunk):
209 if i + x >= count:
209 if i + x >= count:
210 l[chunk - x:] = [0] * (chunk - x)
210 l[chunk - x:] = [0] * (chunk - x)
211 break
211 break
212 if full != None:
212 if full != None:
213 l[x] = ch.read(ch.node(i + x))
213 l[x] = ch.read(ch.node(i + x))
214 else:
214 else:
215 l[x] = 1
215 l[x] = 1
216 for x in xrange(chunk-1, -1, -1):
216 for x in xrange(chunk-1, -1, -1):
217 if l[x] != 0:
217 if l[x] != 0:
218 yield (i + x, full != None and l[x] or None)
218 yield (i + x, full != None and l[x] or None)
219 if i == 0:
219 if i == 0:
220 break
220 break
221
221
222 # calculate and return the reachability bitmask for sha
222 # calculate and return the reachability bitmask for sha
223 def is_reachable(ar, reachable, sha):
223 def is_reachable(ar, reachable, sha):
224 if len(ar) == 0:
224 if len(ar) == 0:
225 return 1
225 return 1
226 mask = 0
226 mask = 0
227 for i in range(len(ar)):
227 for i in range(len(ar)):
228 if sha in reachable[i]:
228 if sha in reachable[i]:
229 mask |= 1 << i
229 mask |= 1 << i
230
230
231 return mask
231 return mask
232
232
233 reachable = []
233 reachable = []
234 stop_sha1 = []
234 stop_sha1 = []
235 want_sha1 = []
235 want_sha1 = []
236 count = 0
236 count = 0
237
237
238 # figure out which commits they are asking for and which ones they
238 # figure out which commits they are asking for and which ones they
239 # want us to stop on
239 # want us to stop on
240 for i in range(len(args)):
240 for i in range(len(args)):
241 if args[i].startswith('^'):
241 if args[i].startswith('^'):
242 s = repo.lookup(args[i][1:])
242 s = repo.lookup(args[i][1:])
243 stop_sha1.append(s)
243 stop_sha1.append(s)
244 want_sha1.append(s)
244 want_sha1.append(s)
245 elif args[i] != 'HEAD':
245 elif args[i] != 'HEAD':
246 want_sha1.append(repo.lookup(args[i]))
246 want_sha1.append(repo.lookup(args[i]))
247
247
248 # calculate the graph for the supplied commits
248 # calculate the graph for the supplied commits
249 for i in range(len(want_sha1)):
249 for i in range(len(want_sha1)):
250 reachable.append({});
250 reachable.append({});
251 n = want_sha1[i];
251 n = want_sha1[i];
252 visit = [n];
252 visit = [n];
253 reachable[i][n] = 1
253 reachable[i][n] = 1
254 while visit:
254 while visit:
255 n = visit.pop(0)
255 n = visit.pop(0)
256 if n in stop_sha1:
256 if n in stop_sha1:
257 continue
257 continue
258 for p in repo.changelog.parents(n):
258 for p in repo.changelog.parents(n):
259 if p not in reachable[i]:
259 if p not in reachable[i]:
260 reachable[i][p] = 1
260 reachable[i][p] = 1
261 visit.append(p)
261 visit.append(p)
262 if p in stop_sha1:
262 if p in stop_sha1:
263 continue
263 continue
264
264
265 # walk the repository looking for commits that are in our
265 # walk the repository looking for commits that are in our
266 # reachability graph
266 # reachability graph
267 #for i in range(repo.changelog.count()-1, -1, -1):
267 #for i in range(repo.changelog.count()-1, -1, -1):
268 for i, changes in chlogwalk():
268 for i, changes in chlogwalk():
269 n = repo.changelog.node(i)
269 n = repo.changelog.node(i)
270 mask = is_reachable(want_sha1, reachable, n)
270 mask = is_reachable(want_sha1, reachable, n)
271 if mask:
271 if mask:
272 parentstr = ""
272 parentstr = ""
273 if parents:
273 if parents:
274 pp = repo.changelog.parents(n)
274 pp = repo.changelog.parents(n)
275 if pp[0] != hg.nullid:
275 if pp[0] != hg.nullid:
276 parentstr += " " + hg.hex(pp[0])
276 parentstr += " " + hg.hex(pp[0])
277 if pp[1] != hg.nullid:
277 if pp[1] != hg.nullid:
278 parentstr += " " + hg.hex(pp[1])
278 parentstr += " " + hg.hex(pp[1])
279 if not full:
279 if not full:
280 print hg.hex(n) + parentstr
280 print hg.hex(n) + parentstr
281 elif full is "commit":
281 elif full is "commit":
282 print hg.hex(n) + parentstr
282 print hg.hex(n) + parentstr
283 catcommit(repo, n, ' ', changes)
283 catcommit(repo, n, ' ', changes)
284 else:
284 else:
285 (p1, p2) = repo.changelog.parents(n)
285 (p1, p2) = repo.changelog.parents(n)
286 (h, h1, h2) = map(hg.hex, (n, p1, p2))
286 (h, h1, h2) = map(hg.hex, (n, p1, p2))
287 (i1, i2) = map(repo.changelog.rev, (p1, p2))
287 (i1, i2) = map(repo.changelog.rev, (p1, p2))
288
288
289 date = changes[2][0]
289 date = changes[2][0]
290 print "%s %s:%s" % (date, h, mask),
290 print "%s %s:%s" % (date, h, mask),
291 mask = is_reachable(want_sha1, reachable, p1)
291 mask = is_reachable(want_sha1, reachable, p1)
292 if i1 != -1 and mask > 0:
292 if i1 != -1 and mask > 0:
293 print "%s:%s " % (h1, mask),
293 print "%s:%s " % (h1, mask),
294 mask = is_reachable(want_sha1, reachable, p2)
294 mask = is_reachable(want_sha1, reachable, p2)
295 if i2 != -1 and mask > 0:
295 if i2 != -1 and mask > 0:
296 print "%s:%s " % (h2, mask),
296 print "%s:%s " % (h2, mask),
297 print ""
297 print ""
298 if maxnr and count >= maxnr:
298 if maxnr and count >= maxnr:
299 break
299 break
300 count += 1
300 count += 1
301
301
302 # git rev-list tries to order things by date, and has the ability to stop
302 # git rev-list tries to order things by date, and has the ability to stop
303 # at a given commit without walking the whole repo. TODO add the stop
303 # at a given commit without walking the whole repo. TODO add the stop
304 # parameter
304 # parameter
305 def revlist(ui, repo, *revs, **opts):
305 def revlist(ui, repo, *revs, **opts):
306 """print revisions"""
306 """print revisions"""
307 if opts['header']:
307 if opts['header']:
308 full = "commit"
308 full = "commit"
309 else:
309 else:
310 full = None
310 full = None
311 copy = [x for x in revs]
311 copy = [x for x in revs]
312 revtree(copy, repo, full, opts['max_count'], opts['parents'])
312 revtree(copy, repo, full, opts['max_count'], opts['parents'])
313
313
314 def view(ui, repo, *etc):
314 def view(ui, repo, *etc):
315 "start interactive history viewer"
315 "start interactive history viewer"
316 os.chdir(repo.root)
316 os.chdir(repo.root)
317 os.system(ui.config("hgk", "path", "hgk") + " " + " ".join(etc))
317 os.system(ui.config("hgk", "path", "hgk") + " " + " ".join(etc))
318
318
319 cmdtable = {
319 cmdtable = {
320 "view": (view, [], 'hg view'),
320 "view": (view, [], 'hg view'),
321 "debug-diff-tree": (difftree, [('p', 'patch', None, 'generate patch'),
321 "debug-diff-tree": (difftree, [('p', 'patch', None, 'generate patch'),
322 ('r', 'recursive', None, 'recursive'),
322 ('r', 'recursive', None, 'recursive'),
323 ('P', 'pretty', None, 'pretty'),
323 ('P', 'pretty', None, 'pretty'),
324 ('s', 'stdin', None, 'stdin'),
324 ('s', 'stdin', None, 'stdin'),
325 ('C', 'copy', None, 'detect copies'),
325 ('C', 'copy', None, 'detect copies'),
326 ('S', 'search', "", 'search')],
326 ('S', 'search', "", 'search')],
327 "hg git-diff-tree [options] node1 node2"),
327 "hg git-diff-tree [options] node1 node2"),
328 "debug-cat-file": (catfile, [('s', 'stdin', None, 'stdin')],
328 "debug-cat-file": (catfile, [('s', 'stdin', None, 'stdin')],
329 "hg debug-cat-file [options] type file"),
329 "hg debug-cat-file [options] type file"),
330 "debug-merge-base": (base, [], "hg debug-merge-base node node"),
330 "debug-merge-base": (base, [], "hg debug-merge-base node node"),
331 "debug-rev-list": (revlist, [('H', 'header', None, 'header'),
331 "debug-rev-list": (revlist, [('H', 'header', None, 'header'),
332 ('t', 'topo-order', None, 'topo-order'),
332 ('t', 'topo-order', None, 'topo-order'),
333 ('p', 'parents', None, 'parents'),
333 ('p', 'parents', None, 'parents'),
334 ('n', 'max-count', 0, 'max-count')],
334 ('n', 'max-count', 0, 'max-count')],
335 "hg debug-rev-list [options] revs"),
335 "hg debug-rev-list [options] revs"),
336 }
336 }
337
337
338 def reposetup(ui, repo):
338 def reposetup(ui, repo):
339 pass
339 pass
@@ -1,2768 +1,2768 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from demandload import demandload
8 from demandload import demandload
9 from node import *
9 from node import *
10 from i18n import gettext as _
10 from i18n import gettext as _
11 demandload(globals(), "os re sys signal shutil imp urllib pdb")
11 demandload(globals(), "os re sys signal shutil imp urllib pdb")
12 demandload(globals(), "fancyopts ui hg util lock revlog")
12 demandload(globals(), "fancyopts ui hg util lock revlog")
13 demandload(globals(), "fnmatch hgweb mdiff random signal time traceback")
13 demandload(globals(), "fnmatch hgweb mdiff random signal time traceback")
14 demandload(globals(), "errno socket version struct atexit sets bz2")
14 demandload(globals(), "errno socket version struct atexit sets bz2")
15
15
16 class UnknownCommand(Exception):
16 class UnknownCommand(Exception):
17 """Exception raised if command is not in the command table."""
17 """Exception raised if command is not in the command table."""
18 class AmbiguousCommand(Exception):
18 class AmbiguousCommand(Exception):
19 """Exception raised if command shortcut matches more than one command."""
19 """Exception raised if command shortcut matches more than one command."""
20
20
21 def filterfiles(filters, files):
21 def filterfiles(filters, files):
22 l = [x for x in files if x in filters]
22 l = [x for x in files if x in filters]
23
23
24 for t in filters:
24 for t in filters:
25 if t and t[-1] != "/":
25 if t and t[-1] != "/":
26 t += "/"
26 t += "/"
27 l += [x for x in files if x.startswith(t)]
27 l += [x for x in files if x.startswith(t)]
28 return l
28 return l
29
29
30 def relpath(repo, args):
30 def relpath(repo, args):
31 cwd = repo.getcwd()
31 cwd = repo.getcwd()
32 if cwd:
32 if cwd:
33 return [util.normpath(os.path.join(cwd, x)) for x in args]
33 return [util.normpath(os.path.join(cwd, x)) for x in args]
34 return args
34 return args
35
35
36 def matchpats(repo, pats=[], opts={}, head=''):
36 def matchpats(repo, pats=[], opts={}, head=''):
37 cwd = repo.getcwd()
37 cwd = repo.getcwd()
38 if not pats and cwd:
38 if not pats and cwd:
39 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
39 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
40 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
40 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
41 cwd = ''
41 cwd = ''
42 return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
42 return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
43 opts.get('exclude'), head)
43 opts.get('exclude'), head)
44
44
45 def makewalk(repo, pats, opts, node=None, head=''):
45 def makewalk(repo, pats, opts, node=None, head=''):
46 files, matchfn, anypats = matchpats(repo, pats, opts, head)
46 files, matchfn, anypats = matchpats(repo, pats, opts, head)
47 exact = dict(zip(files, files))
47 exact = dict(zip(files, files))
48 def walk():
48 def walk():
49 for src, fn in repo.walk(node=node, files=files, match=matchfn):
49 for src, fn in repo.walk(node=node, files=files, match=matchfn):
50 yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
50 yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
51 return files, matchfn, walk()
51 return files, matchfn, walk()
52
52
53 def walk(repo, pats, opts, node=None, head=''):
53 def walk(repo, pats, opts, node=None, head=''):
54 files, matchfn, results = makewalk(repo, pats, opts, node, head)
54 files, matchfn, results = makewalk(repo, pats, opts, node, head)
55 for r in results:
55 for r in results:
56 yield r
56 yield r
57
57
58 def walkchangerevs(ui, repo, pats, opts):
58 def walkchangerevs(ui, repo, pats, opts):
59 '''Iterate over files and the revs they changed in.
59 '''Iterate over files and the revs they changed in.
60
60
61 Callers most commonly need to iterate backwards over the history
61 Callers most commonly need to iterate backwards over the history
62 it is interested in. Doing so has awful (quadratic-looking)
62 it is interested in. Doing so has awful (quadratic-looking)
63 performance, so we use iterators in a "windowed" way.
63 performance, so we use iterators in a "windowed" way.
64
64
65 We walk a window of revisions in the desired order. Within the
65 We walk a window of revisions in the desired order. Within the
66 window, we first walk forwards to gather data, then in the desired
66 window, we first walk forwards to gather data, then in the desired
67 order (usually backwards) to display it.
67 order (usually backwards) to display it.
68
68
69 This function returns an (iterator, getchange, matchfn) tuple. The
69 This function returns an (iterator, getchange, matchfn) tuple. The
70 getchange function returns the changelog entry for a numeric
70 getchange function returns the changelog entry for a numeric
71 revision. The iterator yields 3-tuples. They will be of one of
71 revision. The iterator yields 3-tuples. They will be of one of
72 the following forms:
72 the following forms:
73
73
74 "window", incrementing, lastrev: stepping through a window,
74 "window", incrementing, lastrev: stepping through a window,
75 positive if walking forwards through revs, last rev in the
75 positive if walking forwards through revs, last rev in the
76 sequence iterated over - use to reset state for the current window
76 sequence iterated over - use to reset state for the current window
77
77
78 "add", rev, fns: out-of-order traversal of the given file names
78 "add", rev, fns: out-of-order traversal of the given file names
79 fns, which changed during revision rev - use to gather data for
79 fns, which changed during revision rev - use to gather data for
80 possible display
80 possible display
81
81
82 "iter", rev, None: in-order traversal of the revs earlier iterated
82 "iter", rev, None: in-order traversal of the revs earlier iterated
83 over with "add" - use to display data'''
83 over with "add" - use to display data'''
84
84
85 files, matchfn, anypats = matchpats(repo, pats, opts)
85 files, matchfn, anypats = matchpats(repo, pats, opts)
86
86
87 if repo.changelog.count() == 0:
87 if repo.changelog.count() == 0:
88 return [], False, matchfn
88 return [], False, matchfn
89
89
90 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
90 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
91 wanted = {}
91 wanted = {}
92 slowpath = anypats
92 slowpath = anypats
93 window = 300
93 window = 300
94 fncache = {}
94 fncache = {}
95
95
96 chcache = {}
96 chcache = {}
97 def getchange(rev):
97 def getchange(rev):
98 ch = chcache.get(rev)
98 ch = chcache.get(rev)
99 if ch is None:
99 if ch is None:
100 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
100 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
101 return ch
101 return ch
102
102
103 if not slowpath and not files:
103 if not slowpath and not files:
104 # No files, no patterns. Display all revs.
104 # No files, no patterns. Display all revs.
105 wanted = dict(zip(revs, revs))
105 wanted = dict(zip(revs, revs))
106 if not slowpath:
106 if not slowpath:
107 # Only files, no patterns. Check the history of each file.
107 # Only files, no patterns. Check the history of each file.
108 def filerevgen(filelog):
108 def filerevgen(filelog):
109 for i in xrange(filelog.count() - 1, -1, -window):
109 for i in xrange(filelog.count() - 1, -1, -window):
110 revs = []
110 revs = []
111 for j in xrange(max(0, i - window), i + 1):
111 for j in xrange(max(0, i - window), i + 1):
112 revs.append(filelog.linkrev(filelog.node(j)))
112 revs.append(filelog.linkrev(filelog.node(j)))
113 revs.reverse()
113 revs.reverse()
114 for rev in revs:
114 for rev in revs:
115 yield rev
115 yield rev
116
116
117 minrev, maxrev = min(revs), max(revs)
117 minrev, maxrev = min(revs), max(revs)
118 for file in files:
118 for file in files:
119 filelog = repo.file(file)
119 filelog = repo.file(file)
120 # A zero count may be a directory or deleted file, so
120 # A zero count may be a directory or deleted file, so
121 # try to find matching entries on the slow path.
121 # try to find matching entries on the slow path.
122 if filelog.count() == 0:
122 if filelog.count() == 0:
123 slowpath = True
123 slowpath = True
124 break
124 break
125 for rev in filerevgen(filelog):
125 for rev in filerevgen(filelog):
126 if rev <= maxrev:
126 if rev <= maxrev:
127 if rev < minrev:
127 if rev < minrev:
128 break
128 break
129 fncache.setdefault(rev, [])
129 fncache.setdefault(rev, [])
130 fncache[rev].append(file)
130 fncache[rev].append(file)
131 wanted[rev] = 1
131 wanted[rev] = 1
132 if slowpath:
132 if slowpath:
133 # The slow path checks files modified in every changeset.
133 # The slow path checks files modified in every changeset.
134 def changerevgen():
134 def changerevgen():
135 for i in xrange(repo.changelog.count() - 1, -1, -window):
135 for i in xrange(repo.changelog.count() - 1, -1, -window):
136 for j in xrange(max(0, i - window), i + 1):
136 for j in xrange(max(0, i - window), i + 1):
137 yield j, getchange(j)[3]
137 yield j, getchange(j)[3]
138
138
139 for rev, changefiles in changerevgen():
139 for rev, changefiles in changerevgen():
140 matches = filter(matchfn, changefiles)
140 matches = filter(matchfn, changefiles)
141 if matches:
141 if matches:
142 fncache[rev] = matches
142 fncache[rev] = matches
143 wanted[rev] = 1
143 wanted[rev] = 1
144
144
145 def iterate():
145 def iterate():
146 for i in xrange(0, len(revs), window):
146 for i in xrange(0, len(revs), window):
147 yield 'window', revs[0] < revs[-1], revs[-1]
147 yield 'window', revs[0] < revs[-1], revs[-1]
148 nrevs = [rev for rev in revs[i:min(i+window, len(revs))]
148 nrevs = [rev for rev in revs[i:min(i+window, len(revs))]
149 if rev in wanted]
149 if rev in wanted]
150 srevs = list(nrevs)
150 srevs = list(nrevs)
151 srevs.sort()
151 srevs.sort()
152 for rev in srevs:
152 for rev in srevs:
153 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
153 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
154 yield 'add', rev, fns
154 yield 'add', rev, fns
155 for rev in nrevs:
155 for rev in nrevs:
156 yield 'iter', rev, None
156 yield 'iter', rev, None
157 return iterate(), getchange, matchfn
157 return iterate(), getchange, matchfn
158
158
159 revrangesep = ':'
159 revrangesep = ':'
160
160
161 def revrange(ui, repo, revs, revlog=None):
161 def revrange(ui, repo, revs, revlog=None):
162 """Yield revision as strings from a list of revision specifications."""
162 """Yield revision as strings from a list of revision specifications."""
163 if revlog is None:
163 if revlog is None:
164 revlog = repo.changelog
164 revlog = repo.changelog
165 revcount = revlog.count()
165 revcount = revlog.count()
166 def fix(val, defval):
166 def fix(val, defval):
167 if not val:
167 if not val:
168 return defval
168 return defval
169 try:
169 try:
170 num = int(val)
170 num = int(val)
171 if str(num) != val:
171 if str(num) != val:
172 raise ValueError
172 raise ValueError
173 if num < 0:
173 if num < 0:
174 num += revcount
174 num += revcount
175 if num < 0:
175 if num < 0:
176 num = 0
176 num = 0
177 elif num >= revcount:
177 elif num >= revcount:
178 raise ValueError
178 raise ValueError
179 except ValueError:
179 except ValueError:
180 try:
180 try:
181 num = repo.changelog.rev(repo.lookup(val))
181 num = repo.changelog.rev(repo.lookup(val))
182 except KeyError:
182 except KeyError:
183 try:
183 try:
184 num = revlog.rev(revlog.lookup(val))
184 num = revlog.rev(revlog.lookup(val))
185 except KeyError:
185 except KeyError:
186 raise util.Abort(_('invalid revision identifier %s'), val)
186 raise util.Abort(_('invalid revision identifier %s'), val)
187 return num
187 return num
188 seen = {}
188 seen = {}
189 for spec in revs:
189 for spec in revs:
190 if spec.find(revrangesep) >= 0:
190 if spec.find(revrangesep) >= 0:
191 start, end = spec.split(revrangesep, 1)
191 start, end = spec.split(revrangesep, 1)
192 start = fix(start, 0)
192 start = fix(start, 0)
193 end = fix(end, revcount - 1)
193 end = fix(end, revcount - 1)
194 step = start > end and -1 or 1
194 step = start > end and -1 or 1
195 for rev in xrange(start, end+step, step):
195 for rev in xrange(start, end+step, step):
196 if rev in seen:
196 if rev in seen:
197 continue
197 continue
198 seen[rev] = 1
198 seen[rev] = 1
199 yield str(rev)
199 yield str(rev)
200 else:
200 else:
201 rev = fix(spec, None)
201 rev = fix(spec, None)
202 if rev in seen:
202 if rev in seen:
203 continue
203 continue
204 seen[rev] = 1
204 seen[rev] = 1
205 yield str(rev)
205 yield str(rev)
206
206
207 def make_filename(repo, r, pat, node=None,
207 def make_filename(repo, r, pat, node=None,
208 total=None, seqno=None, revwidth=None, pathname=None):
208 total=None, seqno=None, revwidth=None, pathname=None):
209 node_expander = {
209 node_expander = {
210 'H': lambda: hex(node),
210 'H': lambda: hex(node),
211 'R': lambda: str(r.rev(node)),
211 'R': lambda: str(r.rev(node)),
212 'h': lambda: short(node),
212 'h': lambda: short(node),
213 }
213 }
214 expander = {
214 expander = {
215 '%': lambda: '%',
215 '%': lambda: '%',
216 'b': lambda: os.path.basename(repo.root),
216 'b': lambda: os.path.basename(repo.root),
217 }
217 }
218
218
219 try:
219 try:
220 if node:
220 if node:
221 expander.update(node_expander)
221 expander.update(node_expander)
222 if node and revwidth is not None:
222 if node and revwidth is not None:
223 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
223 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
224 if total is not None:
224 if total is not None:
225 expander['N'] = lambda: str(total)
225 expander['N'] = lambda: str(total)
226 if seqno is not None:
226 if seqno is not None:
227 expander['n'] = lambda: str(seqno)
227 expander['n'] = lambda: str(seqno)
228 if total is not None and seqno is not None:
228 if total is not None and seqno is not None:
229 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
229 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
230 if pathname is not None:
230 if pathname is not None:
231 expander['s'] = lambda: os.path.basename(pathname)
231 expander['s'] = lambda: os.path.basename(pathname)
232 expander['d'] = lambda: os.path.dirname(pathname) or '.'
232 expander['d'] = lambda: os.path.dirname(pathname) or '.'
233 expander['p'] = lambda: pathname
233 expander['p'] = lambda: pathname
234
234
235 newname = []
235 newname = []
236 patlen = len(pat)
236 patlen = len(pat)
237 i = 0
237 i = 0
238 while i < patlen:
238 while i < patlen:
239 c = pat[i]
239 c = pat[i]
240 if c == '%':
240 if c == '%':
241 i += 1
241 i += 1
242 c = pat[i]
242 c = pat[i]
243 c = expander[c]()
243 c = expander[c]()
244 newname.append(c)
244 newname.append(c)
245 i += 1
245 i += 1
246 return ''.join(newname)
246 return ''.join(newname)
247 except KeyError, inst:
247 except KeyError, inst:
248 raise util.Abort(_("invalid format spec '%%%s' in output file name"),
248 raise util.Abort(_("invalid format spec '%%%s' in output file name"),
249 inst.args[0])
249 inst.args[0])
250
250
251 def make_file(repo, r, pat, node=None,
251 def make_file(repo, r, pat, node=None,
252 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
252 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
253 if not pat or pat == '-':
253 if not pat or pat == '-':
254 return 'w' in mode and sys.stdout or sys.stdin
254 return 'w' in mode and sys.stdout or sys.stdin
255 if hasattr(pat, 'write') and 'w' in mode:
255 if hasattr(pat, 'write') and 'w' in mode:
256 return pat
256 return pat
257 if hasattr(pat, 'read') and 'r' in mode:
257 if hasattr(pat, 'read') and 'r' in mode:
258 return pat
258 return pat
259 return open(make_filename(repo, r, pat, node, total, seqno, revwidth,
259 return open(make_filename(repo, r, pat, node, total, seqno, revwidth,
260 pathname),
260 pathname),
261 mode)
261 mode)
262
262
263 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
263 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
264 changes=None, text=False):
264 changes=None, text=False):
265 if not changes:
265 if not changes:
266 changes = repo.changes(node1, node2, files, match=match)
266 changes = repo.changes(node1, node2, files, match=match)
267 modified, added, removed, deleted, unknown = changes
267 modified, added, removed, deleted, unknown = changes
268 if files:
268 if files:
269 modified, added, removed = map(lambda x: filterfiles(x, files),
269 modified, added, removed = map(lambda x: filterfiles(files, x),
270 (modified, added, removed))
270 (modified, added, removed))
271
271
272 if not modified and not added and not removed:
272 if not modified and not added and not removed:
273 return
273 return
274
274
275 if node2:
275 if node2:
276 change = repo.changelog.read(node2)
276 change = repo.changelog.read(node2)
277 mmap2 = repo.manifest.read(change[0])
277 mmap2 = repo.manifest.read(change[0])
278 date2 = util.datestr(change[2])
278 date2 = util.datestr(change[2])
279 def read(f):
279 def read(f):
280 return repo.file(f).read(mmap2[f])
280 return repo.file(f).read(mmap2[f])
281 else:
281 else:
282 date2 = util.datestr()
282 date2 = util.datestr()
283 if not node1:
283 if not node1:
284 node1 = repo.dirstate.parents()[0]
284 node1 = repo.dirstate.parents()[0]
285 def read(f):
285 def read(f):
286 return repo.wfile(f).read()
286 return repo.wfile(f).read()
287
287
288 if ui.quiet:
288 if ui.quiet:
289 r = None
289 r = None
290 else:
290 else:
291 hexfunc = ui.verbose and hex or short
291 hexfunc = ui.verbose and hex or short
292 r = [hexfunc(node) for node in [node1, node2] if node]
292 r = [hexfunc(node) for node in [node1, node2] if node]
293
293
294 change = repo.changelog.read(node1)
294 change = repo.changelog.read(node1)
295 mmap = repo.manifest.read(change[0])
295 mmap = repo.manifest.read(change[0])
296 date1 = util.datestr(change[2])
296 date1 = util.datestr(change[2])
297
297
298 for f in modified:
298 for f in modified:
299 to = None
299 to = None
300 if f in mmap:
300 if f in mmap:
301 to = repo.file(f).read(mmap[f])
301 to = repo.file(f).read(mmap[f])
302 tn = read(f)
302 tn = read(f)
303 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
303 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
304 for f in added:
304 for f in added:
305 to = None
305 to = None
306 tn = read(f)
306 tn = read(f)
307 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
307 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
308 for f in removed:
308 for f in removed:
309 to = repo.file(f).read(mmap[f])
309 to = repo.file(f).read(mmap[f])
310 tn = None
310 tn = None
311 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
311 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
312
312
313 def trimuser(ui, name, rev, revcache):
313 def trimuser(ui, name, rev, revcache):
314 """trim the name of the user who committed a change"""
314 """trim the name of the user who committed a change"""
315 user = revcache.get(rev)
315 user = revcache.get(rev)
316 if user is None:
316 if user is None:
317 user = revcache[rev] = ui.shortuser(name)
317 user = revcache[rev] = ui.shortuser(name)
318 return user
318 return user
319
319
320 def show_changeset(ui, repo, rev=0, changenode=None, brinfo=None):
320 def show_changeset(ui, repo, rev=0, changenode=None, brinfo=None):
321 """show a single changeset or file revision"""
321 """show a single changeset or file revision"""
322 log = repo.changelog
322 log = repo.changelog
323 if changenode is None:
323 if changenode is None:
324 changenode = log.node(rev)
324 changenode = log.node(rev)
325 elif not rev:
325 elif not rev:
326 rev = log.rev(changenode)
326 rev = log.rev(changenode)
327
327
328 if ui.quiet:
328 if ui.quiet:
329 ui.write("%d:%s\n" % (rev, short(changenode)))
329 ui.write("%d:%s\n" % (rev, short(changenode)))
330 return
330 return
331
331
332 changes = log.read(changenode)
332 changes = log.read(changenode)
333 date = util.datestr(changes[2])
333 date = util.datestr(changes[2])
334
334
335 parents = [(log.rev(p), ui.verbose and hex(p) or short(p))
335 parents = [(log.rev(p), ui.verbose and hex(p) or short(p))
336 for p in log.parents(changenode)
336 for p in log.parents(changenode)
337 if ui.debugflag or p != nullid]
337 if ui.debugflag or p != nullid]
338 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
338 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
339 parents = []
339 parents = []
340
340
341 if ui.verbose:
341 if ui.verbose:
342 ui.write(_("changeset: %d:%s\n") % (rev, hex(changenode)))
342 ui.write(_("changeset: %d:%s\n") % (rev, hex(changenode)))
343 else:
343 else:
344 ui.write(_("changeset: %d:%s\n") % (rev, short(changenode)))
344 ui.write(_("changeset: %d:%s\n") % (rev, short(changenode)))
345
345
346 for tag in repo.nodetags(changenode):
346 for tag in repo.nodetags(changenode):
347 ui.status(_("tag: %s\n") % tag)
347 ui.status(_("tag: %s\n") % tag)
348 for parent in parents:
348 for parent in parents:
349 ui.write(_("parent: %d:%s\n") % parent)
349 ui.write(_("parent: %d:%s\n") % parent)
350
350
351 if brinfo and changenode in brinfo:
351 if brinfo and changenode in brinfo:
352 br = brinfo[changenode]
352 br = brinfo[changenode]
353 ui.write(_("branch: %s\n") % " ".join(br))
353 ui.write(_("branch: %s\n") % " ".join(br))
354
354
355 ui.debug(_("manifest: %d:%s\n") % (repo.manifest.rev(changes[0]),
355 ui.debug(_("manifest: %d:%s\n") % (repo.manifest.rev(changes[0]),
356 hex(changes[0])))
356 hex(changes[0])))
357 ui.status(_("user: %s\n") % changes[1])
357 ui.status(_("user: %s\n") % changes[1])
358 ui.status(_("date: %s\n") % date)
358 ui.status(_("date: %s\n") % date)
359
359
360 if ui.debugflag:
360 if ui.debugflag:
361 files = repo.changes(log.parents(changenode)[0], changenode)
361 files = repo.changes(log.parents(changenode)[0], changenode)
362 for key, value in zip([_("files:"), _("files+:"), _("files-:")], files):
362 for key, value in zip([_("files:"), _("files+:"), _("files-:")], files):
363 if value:
363 if value:
364 ui.note("%-12s %s\n" % (key, " ".join(value)))
364 ui.note("%-12s %s\n" % (key, " ".join(value)))
365 else:
365 else:
366 ui.note(_("files: %s\n") % " ".join(changes[3]))
366 ui.note(_("files: %s\n") % " ".join(changes[3]))
367
367
368 description = changes[4].strip()
368 description = changes[4].strip()
369 if description:
369 if description:
370 if ui.verbose:
370 if ui.verbose:
371 ui.status(_("description:\n"))
371 ui.status(_("description:\n"))
372 ui.status(description)
372 ui.status(description)
373 ui.status("\n\n")
373 ui.status("\n\n")
374 else:
374 else:
375 ui.status(_("summary: %s\n") % description.splitlines()[0])
375 ui.status(_("summary: %s\n") % description.splitlines()[0])
376 ui.status("\n")
376 ui.status("\n")
377
377
378 def show_version(ui):
378 def show_version(ui):
379 """output version and copyright information"""
379 """output version and copyright information"""
380 ui.write(_("Mercurial Distributed SCM (version %s)\n")
380 ui.write(_("Mercurial Distributed SCM (version %s)\n")
381 % version.get_version())
381 % version.get_version())
382 ui.status(_(
382 ui.status(_(
383 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
383 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
384 "This is free software; see the source for copying conditions. "
384 "This is free software; see the source for copying conditions. "
385 "There is NO\nwarranty; "
385 "There is NO\nwarranty; "
386 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
386 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
387 ))
387 ))
388
388
389 def help_(ui, cmd=None, with_version=False):
389 def help_(ui, cmd=None, with_version=False):
390 """show help for a given command or all commands"""
390 """show help for a given command or all commands"""
391 option_lists = []
391 option_lists = []
392 if cmd and cmd != 'shortlist':
392 if cmd and cmd != 'shortlist':
393 if with_version:
393 if with_version:
394 show_version(ui)
394 show_version(ui)
395 ui.write('\n')
395 ui.write('\n')
396 aliases, i = find(cmd)
396 aliases, i = find(cmd)
397 # synopsis
397 # synopsis
398 ui.write("%s\n\n" % i[2])
398 ui.write("%s\n\n" % i[2])
399
399
400 # description
400 # description
401 doc = i[0].__doc__
401 doc = i[0].__doc__
402 if ui.quiet:
402 if ui.quiet:
403 doc = doc.splitlines(0)[0]
403 doc = doc.splitlines(0)[0]
404 ui.write("%s\n" % doc.rstrip())
404 ui.write("%s\n" % doc.rstrip())
405
405
406 if not ui.quiet:
406 if not ui.quiet:
407 # aliases
407 # aliases
408 if len(aliases) > 1:
408 if len(aliases) > 1:
409 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
409 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
410
410
411 # options
411 # options
412 if i[1]:
412 if i[1]:
413 option_lists.append(("options", i[1]))
413 option_lists.append(("options", i[1]))
414
414
415 else:
415 else:
416 # program name
416 # program name
417 if ui.verbose or with_version:
417 if ui.verbose or with_version:
418 show_version(ui)
418 show_version(ui)
419 else:
419 else:
420 ui.status(_("Mercurial Distributed SCM\n"))
420 ui.status(_("Mercurial Distributed SCM\n"))
421 ui.status('\n')
421 ui.status('\n')
422
422
423 # list of commands
423 # list of commands
424 if cmd == "shortlist":
424 if cmd == "shortlist":
425 ui.status(_('basic commands (use "hg help" '
425 ui.status(_('basic commands (use "hg help" '
426 'for the full list or option "-v" for details):\n\n'))
426 'for the full list or option "-v" for details):\n\n'))
427 elif ui.verbose:
427 elif ui.verbose:
428 ui.status(_('list of commands:\n\n'))
428 ui.status(_('list of commands:\n\n'))
429 else:
429 else:
430 ui.status(_('list of commands (use "hg help -v" '
430 ui.status(_('list of commands (use "hg help -v" '
431 'to show aliases and global options):\n\n'))
431 'to show aliases and global options):\n\n'))
432
432
433 h = {}
433 h = {}
434 cmds = {}
434 cmds = {}
435 for c, e in table.items():
435 for c, e in table.items():
436 f = c.split("|")[0]
436 f = c.split("|")[0]
437 if cmd == "shortlist" and not f.startswith("^"):
437 if cmd == "shortlist" and not f.startswith("^"):
438 continue
438 continue
439 f = f.lstrip("^")
439 f = f.lstrip("^")
440 if not ui.debugflag and f.startswith("debug"):
440 if not ui.debugflag and f.startswith("debug"):
441 continue
441 continue
442 d = ""
442 d = ""
443 if e[0].__doc__:
443 if e[0].__doc__:
444 d = e[0].__doc__.splitlines(0)[0].rstrip()
444 d = e[0].__doc__.splitlines(0)[0].rstrip()
445 h[f] = d
445 h[f] = d
446 cmds[f] = c.lstrip("^")
446 cmds[f] = c.lstrip("^")
447
447
448 fns = h.keys()
448 fns = h.keys()
449 fns.sort()
449 fns.sort()
450 m = max(map(len, fns))
450 m = max(map(len, fns))
451 for f in fns:
451 for f in fns:
452 if ui.verbose:
452 if ui.verbose:
453 commands = cmds[f].replace("|",", ")
453 commands = cmds[f].replace("|",", ")
454 ui.write(" %s:\n %s\n"%(commands, h[f]))
454 ui.write(" %s:\n %s\n"%(commands, h[f]))
455 else:
455 else:
456 ui.write(' %-*s %s\n' % (m, f, h[f]))
456 ui.write(' %-*s %s\n' % (m, f, h[f]))
457
457
458 # global options
458 # global options
459 if ui.verbose:
459 if ui.verbose:
460 option_lists.append(("global options", globalopts))
460 option_lists.append(("global options", globalopts))
461
461
462 # list all option lists
462 # list all option lists
463 opt_output = []
463 opt_output = []
464 for title, options in option_lists:
464 for title, options in option_lists:
465 opt_output.append(("\n%s:\n" % title, None))
465 opt_output.append(("\n%s:\n" % title, None))
466 for shortopt, longopt, default, desc in options:
466 for shortopt, longopt, default, desc in options:
467 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
467 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
468 longopt and " --%s" % longopt),
468 longopt and " --%s" % longopt),
469 "%s%s" % (desc,
469 "%s%s" % (desc,
470 default
470 default
471 and _(" (default: %s)") % default
471 and _(" (default: %s)") % default
472 or "")))
472 or "")))
473
473
474 if opt_output:
474 if opt_output:
475 opts_len = max([len(line[0]) for line in opt_output if line[1]])
475 opts_len = max([len(line[0]) for line in opt_output if line[1]])
476 for first, second in opt_output:
476 for first, second in opt_output:
477 if second:
477 if second:
478 ui.write(" %-*s %s\n" % (opts_len, first, second))
478 ui.write(" %-*s %s\n" % (opts_len, first, second))
479 else:
479 else:
480 ui.write("%s\n" % first)
480 ui.write("%s\n" % first)
481
481
482 # Commands start here, listed alphabetically
482 # Commands start here, listed alphabetically
483
483
484 def add(ui, repo, *pats, **opts):
484 def add(ui, repo, *pats, **opts):
485 """add the specified files on the next commit
485 """add the specified files on the next commit
486
486
487 Schedule files to be version controlled and added to the repository.
487 Schedule files to be version controlled and added to the repository.
488
488
489 The files will be added to the repository at the next commit.
489 The files will be added to the repository at the next commit.
490
490
491 If no names are given, add all files in the repository.
491 If no names are given, add all files in the repository.
492 """
492 """
493
493
494 names = []
494 names = []
495 for src, abs, rel, exact in walk(repo, pats, opts):
495 for src, abs, rel, exact in walk(repo, pats, opts):
496 if exact:
496 if exact:
497 if ui.verbose:
497 if ui.verbose:
498 ui.status(_('adding %s\n') % rel)
498 ui.status(_('adding %s\n') % rel)
499 names.append(abs)
499 names.append(abs)
500 elif repo.dirstate.state(abs) == '?':
500 elif repo.dirstate.state(abs) == '?':
501 ui.status(_('adding %s\n') % rel)
501 ui.status(_('adding %s\n') % rel)
502 names.append(abs)
502 names.append(abs)
503 repo.add(names)
503 repo.add(names)
504
504
505 def addremove(ui, repo, *pats, **opts):
505 def addremove(ui, repo, *pats, **opts):
506 """add all new files, delete all missing files
506 """add all new files, delete all missing files
507
507
508 Add all new files and remove all missing files from the repository.
508 Add all new files and remove all missing files from the repository.
509
509
510 New files are ignored if they match any of the patterns in .hgignore. As
510 New files are ignored if they match any of the patterns in .hgignore. As
511 with add, these changes take effect at the next commit.
511 with add, these changes take effect at the next commit.
512 """
512 """
513 add, remove = [], []
513 add, remove = [], []
514 for src, abs, rel, exact in walk(repo, pats, opts):
514 for src, abs, rel, exact in walk(repo, pats, opts):
515 if src == 'f' and repo.dirstate.state(abs) == '?':
515 if src == 'f' and repo.dirstate.state(abs) == '?':
516 add.append(abs)
516 add.append(abs)
517 if ui.verbose or not exact:
517 if ui.verbose or not exact:
518 ui.status(_('adding %s\n') % ((pats and rel) or abs))
518 ui.status(_('adding %s\n') % ((pats and rel) or abs))
519 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
519 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
520 remove.append(abs)
520 remove.append(abs)
521 if ui.verbose or not exact:
521 if ui.verbose or not exact:
522 ui.status(_('removing %s\n') % ((pats and rel) or abs))
522 ui.status(_('removing %s\n') % ((pats and rel) or abs))
523 repo.add(add)
523 repo.add(add)
524 repo.remove(remove)
524 repo.remove(remove)
525
525
526 def annotate(ui, repo, *pats, **opts):
526 def annotate(ui, repo, *pats, **opts):
527 """show changeset information per file line
527 """show changeset information per file line
528
528
529 List changes in files, showing the revision id responsible for each line
529 List changes in files, showing the revision id responsible for each line
530
530
531 This command is useful to discover who did a change or when a change took
531 This command is useful to discover who did a change or when a change took
532 place.
532 place.
533
533
534 Without the -a option, annotate will avoid processing files it
534 Without the -a option, annotate will avoid processing files it
535 detects as binary. With -a, annotate will generate an annotation
535 detects as binary. With -a, annotate will generate an annotation
536 anyway, probably with undesirable results.
536 anyway, probably with undesirable results.
537 """
537 """
538 def getnode(rev):
538 def getnode(rev):
539 return short(repo.changelog.node(rev))
539 return short(repo.changelog.node(rev))
540
540
541 ucache = {}
541 ucache = {}
542 def getname(rev):
542 def getname(rev):
543 cl = repo.changelog.read(repo.changelog.node(rev))
543 cl = repo.changelog.read(repo.changelog.node(rev))
544 return trimuser(ui, cl[1], rev, ucache)
544 return trimuser(ui, cl[1], rev, ucache)
545
545
546 dcache = {}
546 dcache = {}
547 def getdate(rev):
547 def getdate(rev):
548 datestr = dcache.get(rev)
548 datestr = dcache.get(rev)
549 if datestr is None:
549 if datestr is None:
550 cl = repo.changelog.read(repo.changelog.node(rev))
550 cl = repo.changelog.read(repo.changelog.node(rev))
551 datestr = dcache[rev] = util.datestr(cl[2])
551 datestr = dcache[rev] = util.datestr(cl[2])
552 return datestr
552 return datestr
553
553
554 if not pats:
554 if not pats:
555 raise util.Abort(_('at least one file name or pattern required'))
555 raise util.Abort(_('at least one file name or pattern required'))
556
556
557 opmap = [['user', getname], ['number', str], ['changeset', getnode],
557 opmap = [['user', getname], ['number', str], ['changeset', getnode],
558 ['date', getdate]]
558 ['date', getdate]]
559 if not opts['user'] and not opts['changeset'] and not opts['date']:
559 if not opts['user'] and not opts['changeset'] and not opts['date']:
560 opts['number'] = 1
560 opts['number'] = 1
561
561
562 if opts['rev']:
562 if opts['rev']:
563 node = repo.changelog.lookup(opts['rev'])
563 node = repo.changelog.lookup(opts['rev'])
564 else:
564 else:
565 node = repo.dirstate.parents()[0]
565 node = repo.dirstate.parents()[0]
566 change = repo.changelog.read(node)
566 change = repo.changelog.read(node)
567 mmap = repo.manifest.read(change[0])
567 mmap = repo.manifest.read(change[0])
568
568
569 for src, abs, rel, exact in walk(repo, pats, opts):
569 for src, abs, rel, exact in walk(repo, pats, opts):
570 if abs not in mmap:
570 if abs not in mmap:
571 ui.warn(_("warning: %s is not in the repository!\n") %
571 ui.warn(_("warning: %s is not in the repository!\n") %
572 ((pats and rel) or abs))
572 ((pats and rel) or abs))
573 continue
573 continue
574
574
575 f = repo.file(abs)
575 f = repo.file(abs)
576 if not opts['text'] and util.binary(f.read(mmap[abs])):
576 if not opts['text'] and util.binary(f.read(mmap[abs])):
577 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
577 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
578 continue
578 continue
579
579
580 lines = f.annotate(mmap[abs])
580 lines = f.annotate(mmap[abs])
581 pieces = []
581 pieces = []
582
582
583 for o, f in opmap:
583 for o, f in opmap:
584 if opts[o]:
584 if opts[o]:
585 l = [f(n) for n, dummy in lines]
585 l = [f(n) for n, dummy in lines]
586 if l:
586 if l:
587 m = max(map(len, l))
587 m = max(map(len, l))
588 pieces.append(["%*s" % (m, x) for x in l])
588 pieces.append(["%*s" % (m, x) for x in l])
589
589
590 if pieces:
590 if pieces:
591 for p, l in zip(zip(*pieces), lines):
591 for p, l in zip(zip(*pieces), lines):
592 ui.write("%s: %s" % (" ".join(p), l[1]))
592 ui.write("%s: %s" % (" ".join(p), l[1]))
593
593
594 def bundle(ui, repo, fname, dest="default-push", **opts):
594 def bundle(ui, repo, fname, dest="default-push", **opts):
595 """create a changegroup file
595 """create a changegroup file
596
596
597 Generate a compressed changegroup file collecting all changesets
597 Generate a compressed changegroup file collecting all changesets
598 not found in the other repository.
598 not found in the other repository.
599
599
600 This file can then be transferred using conventional means and
600 This file can then be transferred using conventional means and
601 applied to another repository with the unbundle command. This is
601 applied to another repository with the unbundle command. This is
602 useful when native push and pull are not available or when
602 useful when native push and pull are not available or when
603 exporting an entire repository is undesirable. The standard file
603 exporting an entire repository is undesirable. The standard file
604 extension is ".hg".
604 extension is ".hg".
605
605
606 Unlike import/export, this exactly preserves all changeset
606 Unlike import/export, this exactly preserves all changeset
607 contents including permissions, rename data, and revision history.
607 contents including permissions, rename data, and revision history.
608 """
608 """
609 f = open(fname, "wb")
609 f = open(fname, "wb")
610 dest = ui.expandpath(dest, repo.root)
610 dest = ui.expandpath(dest, repo.root)
611 other = hg.repository(ui, dest)
611 other = hg.repository(ui, dest)
612 o = repo.findoutgoing(other)
612 o = repo.findoutgoing(other)
613 cg = repo.changegroup(o)
613 cg = repo.changegroup(o)
614
614
615 try:
615 try:
616 f.write("HG10")
616 f.write("HG10")
617 z = bz2.BZ2Compressor(9)
617 z = bz2.BZ2Compressor(9)
618 while 1:
618 while 1:
619 chunk = cg.read(4096)
619 chunk = cg.read(4096)
620 if not chunk:
620 if not chunk:
621 break
621 break
622 f.write(z.compress(chunk))
622 f.write(z.compress(chunk))
623 f.write(z.flush())
623 f.write(z.flush())
624 except:
624 except:
625 os.unlink(fname)
625 os.unlink(fname)
626 raise
626 raise
627
627
628 def cat(ui, repo, file1, *pats, **opts):
628 def cat(ui, repo, file1, *pats, **opts):
629 """output the latest or given revisions of files
629 """output the latest or given revisions of files
630
630
631 Print the specified files as they were at the given revision.
631 Print the specified files as they were at the given revision.
632 If no revision is given then the tip is used.
632 If no revision is given then the tip is used.
633
633
634 Output may be to a file, in which case the name of the file is
634 Output may be to a file, in which case the name of the file is
635 given using a format string. The formatting rules are the same as
635 given using a format string. The formatting rules are the same as
636 for the export command, with the following additions:
636 for the export command, with the following additions:
637
637
638 %s basename of file being printed
638 %s basename of file being printed
639 %d dirname of file being printed, or '.' if in repo root
639 %d dirname of file being printed, or '.' if in repo root
640 %p root-relative path name of file being printed
640 %p root-relative path name of file being printed
641 """
641 """
642 mf = {}
642 mf = {}
643 rev = opts['rev']
643 rev = opts['rev']
644 if rev:
644 if rev:
645 node = repo.lookup(rev)
645 node = repo.lookup(rev)
646 else:
646 else:
647 node = repo.changelog.tip()
647 node = repo.changelog.tip()
648 change = repo.changelog.read(node)
648 change = repo.changelog.read(node)
649 mf = repo.manifest.read(change[0])
649 mf = repo.manifest.read(change[0])
650 for src, abs, rel, exact in walk(repo, (file1,) + pats, opts, node):
650 for src, abs, rel, exact in walk(repo, (file1,) + pats, opts, node):
651 r = repo.file(abs)
651 r = repo.file(abs)
652 n = mf[abs]
652 n = mf[abs]
653 fp = make_file(repo, r, opts['output'], node=n, pathname=abs)
653 fp = make_file(repo, r, opts['output'], node=n, pathname=abs)
654 fp.write(r.read(n))
654 fp.write(r.read(n))
655
655
656 def clone(ui, source, dest=None, **opts):
656 def clone(ui, source, dest=None, **opts):
657 """make a copy of an existing repository
657 """make a copy of an existing repository
658
658
659 Create a copy of an existing repository in a new directory.
659 Create a copy of an existing repository in a new directory.
660
660
661 If no destination directory name is specified, it defaults to the
661 If no destination directory name is specified, it defaults to the
662 basename of the source.
662 basename of the source.
663
663
664 The location of the source is added to the new repository's
664 The location of the source is added to the new repository's
665 .hg/hgrc file, as the default to be used for future pulls.
665 .hg/hgrc file, as the default to be used for future pulls.
666
666
667 For efficiency, hardlinks are used for cloning whenever the source
667 For efficiency, hardlinks are used for cloning whenever the source
668 and destination are on the same filesystem. Some filesystems,
668 and destination are on the same filesystem. Some filesystems,
669 such as AFS, implement hardlinking incorrectly, but do not report
669 such as AFS, implement hardlinking incorrectly, but do not report
670 errors. In these cases, use the --pull option to avoid
670 errors. In these cases, use the --pull option to avoid
671 hardlinking.
671 hardlinking.
672 """
672 """
673 if dest is None:
673 if dest is None:
674 dest = os.path.basename(os.path.normpath(source))
674 dest = os.path.basename(os.path.normpath(source))
675
675
676 if os.path.exists(dest):
676 if os.path.exists(dest):
677 raise util.Abort(_("destination '%s' already exists"), dest)
677 raise util.Abort(_("destination '%s' already exists"), dest)
678
678
679 dest = os.path.realpath(dest)
679 dest = os.path.realpath(dest)
680
680
681 class Dircleanup(object):
681 class Dircleanup(object):
682 def __init__(self, dir_):
682 def __init__(self, dir_):
683 self.rmtree = shutil.rmtree
683 self.rmtree = shutil.rmtree
684 self.dir_ = dir_
684 self.dir_ = dir_
685 os.mkdir(dir_)
685 os.mkdir(dir_)
686 def close(self):
686 def close(self):
687 self.dir_ = None
687 self.dir_ = None
688 def __del__(self):
688 def __del__(self):
689 if self.dir_:
689 if self.dir_:
690 self.rmtree(self.dir_, True)
690 self.rmtree(self.dir_, True)
691
691
692 if opts['ssh']:
692 if opts['ssh']:
693 ui.setconfig("ui", "ssh", opts['ssh'])
693 ui.setconfig("ui", "ssh", opts['ssh'])
694 if opts['remotecmd']:
694 if opts['remotecmd']:
695 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
695 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
696
696
697 if not os.path.exists(source):
697 if not os.path.exists(source):
698 source = ui.expandpath(source)
698 source = ui.expandpath(source)
699
699
700 d = Dircleanup(dest)
700 d = Dircleanup(dest)
701 abspath = source
701 abspath = source
702 other = hg.repository(ui, source)
702 other = hg.repository(ui, source)
703
703
704 copy = False
704 copy = False
705 if other.dev() != -1:
705 if other.dev() != -1:
706 abspath = os.path.abspath(source)
706 abspath = os.path.abspath(source)
707 if not opts['pull'] and not opts['rev']:
707 if not opts['pull'] and not opts['rev']:
708 copy = True
708 copy = True
709
709
710 if copy:
710 if copy:
711 try:
711 try:
712 # we use a lock here because if we race with commit, we
712 # we use a lock here because if we race with commit, we
713 # can end up with extra data in the cloned revlogs that's
713 # can end up with extra data in the cloned revlogs that's
714 # not pointed to by changesets, thus causing verify to
714 # not pointed to by changesets, thus causing verify to
715 # fail
715 # fail
716 l1 = lock.lock(os.path.join(source, ".hg", "lock"))
716 l1 = lock.lock(os.path.join(source, ".hg", "lock"))
717 except OSError:
717 except OSError:
718 copy = False
718 copy = False
719
719
720 if copy:
720 if copy:
721 # we lock here to avoid premature writing to the target
721 # we lock here to avoid premature writing to the target
722 os.mkdir(os.path.join(dest, ".hg"))
722 os.mkdir(os.path.join(dest, ".hg"))
723 l2 = lock.lock(os.path.join(dest, ".hg", "lock"))
723 l2 = lock.lock(os.path.join(dest, ".hg", "lock"))
724
724
725 files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
725 files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
726 for f in files.split():
726 for f in files.split():
727 src = os.path.join(source, ".hg", f)
727 src = os.path.join(source, ".hg", f)
728 dst = os.path.join(dest, ".hg", f)
728 dst = os.path.join(dest, ".hg", f)
729 try:
729 try:
730 util.copyfiles(src, dst)
730 util.copyfiles(src, dst)
731 except OSError, inst:
731 except OSError, inst:
732 if inst.errno != errno.ENOENT:
732 if inst.errno != errno.ENOENT:
733 raise
733 raise
734
734
735 repo = hg.repository(ui, dest)
735 repo = hg.repository(ui, dest)
736
736
737 else:
737 else:
738 revs = None
738 revs = None
739 if opts['rev']:
739 if opts['rev']:
740 if not other.local():
740 if not other.local():
741 error = _("clone -r not supported yet for remote repositories.")
741 error = _("clone -r not supported yet for remote repositories.")
742 raise util.Abort(error)
742 raise util.Abort(error)
743 else:
743 else:
744 revs = [other.lookup(rev) for rev in opts['rev']]
744 revs = [other.lookup(rev) for rev in opts['rev']]
745 repo = hg.repository(ui, dest, create=1)
745 repo = hg.repository(ui, dest, create=1)
746 repo.pull(other, heads = revs)
746 repo.pull(other, heads = revs)
747
747
748 f = repo.opener("hgrc", "w", text=True)
748 f = repo.opener("hgrc", "w", text=True)
749 f.write("[paths]\n")
749 f.write("[paths]\n")
750 f.write("default = %s\n" % abspath)
750 f.write("default = %s\n" % abspath)
751 f.close()
751 f.close()
752
752
753 if not opts['noupdate']:
753 if not opts['noupdate']:
754 update(ui, repo)
754 update(ui, repo)
755
755
756 d.close()
756 d.close()
757
757
758 def commit(ui, repo, *pats, **opts):
758 def commit(ui, repo, *pats, **opts):
759 """commit the specified files or all outstanding changes
759 """commit the specified files or all outstanding changes
760
760
761 Commit changes to the given files into the repository.
761 Commit changes to the given files into the repository.
762
762
763 If a list of files is omitted, all changes reported by "hg status"
763 If a list of files is omitted, all changes reported by "hg status"
764 will be commited.
764 will be commited.
765
765
766 The HGEDITOR or EDITOR environment variables are used to start an
766 The HGEDITOR or EDITOR environment variables are used to start an
767 editor to add a commit comment.
767 editor to add a commit comment.
768 """
768 """
769 message = opts['message']
769 message = opts['message']
770 logfile = opts['logfile']
770 logfile = opts['logfile']
771
771
772 if message and logfile:
772 if message and logfile:
773 raise util.Abort(_('options --message and --logfile are mutually '
773 raise util.Abort(_('options --message and --logfile are mutually '
774 'exclusive'))
774 'exclusive'))
775 if not message and logfile:
775 if not message and logfile:
776 try:
776 try:
777 if logfile == '-':
777 if logfile == '-':
778 message = sys.stdin.read()
778 message = sys.stdin.read()
779 else:
779 else:
780 message = open(logfile).read()
780 message = open(logfile).read()
781 except IOError, inst:
781 except IOError, inst:
782 raise util.Abort(_("can't read commit message '%s': %s") %
782 raise util.Abort(_("can't read commit message '%s': %s") %
783 (logfile, inst.strerror))
783 (logfile, inst.strerror))
784
784
785 if opts['addremove']:
785 if opts['addremove']:
786 addremove(ui, repo, *pats, **opts)
786 addremove(ui, repo, *pats, **opts)
787 fns, match, anypats = matchpats(repo, pats, opts)
787 fns, match, anypats = matchpats(repo, pats, opts)
788 if pats:
788 if pats:
789 modified, added, removed, deleted, unknown = (
789 modified, added, removed, deleted, unknown = (
790 repo.changes(files=fns, match=match))
790 repo.changes(files=fns, match=match))
791 files = modified + added + removed
791 files = modified + added + removed
792 else:
792 else:
793 files = []
793 files = []
794 try:
794 try:
795 repo.commit(files, message, opts['user'], opts['date'], match)
795 repo.commit(files, message, opts['user'], opts['date'], match)
796 except ValueError, inst:
796 except ValueError, inst:
797 raise util.Abort(str(inst))
797 raise util.Abort(str(inst))
798
798
799 def docopy(ui, repo, pats, opts):
799 def docopy(ui, repo, pats, opts):
800 cwd = repo.getcwd()
800 cwd = repo.getcwd()
801 errors = 0
801 errors = 0
802 copied = []
802 copied = []
803 targets = {}
803 targets = {}
804
804
805 def okaytocopy(abs, rel, exact):
805 def okaytocopy(abs, rel, exact):
806 reasons = {'?': _('is not managed'),
806 reasons = {'?': _('is not managed'),
807 'a': _('has been marked for add')}
807 'a': _('has been marked for add')}
808 reason = reasons.get(repo.dirstate.state(abs))
808 reason = reasons.get(repo.dirstate.state(abs))
809 if reason:
809 if reason:
810 if exact:
810 if exact:
811 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
811 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
812 else:
812 else:
813 return True
813 return True
814
814
815 def copy(abssrc, relsrc, target, exact):
815 def copy(abssrc, relsrc, target, exact):
816 abstarget = util.canonpath(repo.root, cwd, target)
816 abstarget = util.canonpath(repo.root, cwd, target)
817 reltarget = util.pathto(cwd, abstarget)
817 reltarget = util.pathto(cwd, abstarget)
818 prevsrc = targets.get(abstarget)
818 prevsrc = targets.get(abstarget)
819 if prevsrc is not None:
819 if prevsrc is not None:
820 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
820 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
821 (reltarget, abssrc, prevsrc))
821 (reltarget, abssrc, prevsrc))
822 return
822 return
823 if (not opts['after'] and os.path.exists(reltarget) or
823 if (not opts['after'] and os.path.exists(reltarget) or
824 opts['after'] and repo.dirstate.state(abstarget) not in '?r'):
824 opts['after'] and repo.dirstate.state(abstarget) not in '?r'):
825 if not opts['force']:
825 if not opts['force']:
826 ui.warn(_('%s: not overwriting - file exists\n') %
826 ui.warn(_('%s: not overwriting - file exists\n') %
827 reltarget)
827 reltarget)
828 return
828 return
829 if not opts['after']:
829 if not opts['after']:
830 os.unlink(reltarget)
830 os.unlink(reltarget)
831 if opts['after']:
831 if opts['after']:
832 if not os.path.exists(reltarget):
832 if not os.path.exists(reltarget):
833 return
833 return
834 else:
834 else:
835 targetdir = os.path.dirname(reltarget) or '.'
835 targetdir = os.path.dirname(reltarget) or '.'
836 if not os.path.isdir(targetdir):
836 if not os.path.isdir(targetdir):
837 os.makedirs(targetdir)
837 os.makedirs(targetdir)
838 try:
838 try:
839 shutil.copyfile(relsrc, reltarget)
839 shutil.copyfile(relsrc, reltarget)
840 shutil.copymode(relsrc, reltarget)
840 shutil.copymode(relsrc, reltarget)
841 except shutil.Error, inst:
841 except shutil.Error, inst:
842 raise util.Abort(str(inst))
842 raise util.Abort(str(inst))
843 except IOError, inst:
843 except IOError, inst:
844 if inst.errno == errno.ENOENT:
844 if inst.errno == errno.ENOENT:
845 ui.warn(_('%s: deleted in working copy\n') % relsrc)
845 ui.warn(_('%s: deleted in working copy\n') % relsrc)
846 else:
846 else:
847 ui.warn(_('%s: cannot copy - %s\n') %
847 ui.warn(_('%s: cannot copy - %s\n') %
848 (relsrc, inst.strerror))
848 (relsrc, inst.strerror))
849 errors += 1
849 errors += 1
850 return
850 return
851 if ui.verbose or not exact:
851 if ui.verbose or not exact:
852 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
852 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
853 targets[abstarget] = abssrc
853 targets[abstarget] = abssrc
854 repo.copy(abssrc, abstarget)
854 repo.copy(abssrc, abstarget)
855 copied.append((abssrc, relsrc, exact))
855 copied.append((abssrc, relsrc, exact))
856
856
857 def targetpathfn(pat, dest, srcs):
857 def targetpathfn(pat, dest, srcs):
858 if os.path.isdir(pat):
858 if os.path.isdir(pat):
859 if pat.endswith(os.sep):
859 if pat.endswith(os.sep):
860 pat = pat[:-len(os.sep)]
860 pat = pat[:-len(os.sep)]
861 if destdirexists:
861 if destdirexists:
862 striplen = len(os.path.split(pat)[0])
862 striplen = len(os.path.split(pat)[0])
863 else:
863 else:
864 striplen = len(pat)
864 striplen = len(pat)
865 if striplen:
865 if striplen:
866 striplen += len(os.sep)
866 striplen += len(os.sep)
867 res = lambda p: os.path.join(dest, p[striplen:])
867 res = lambda p: os.path.join(dest, p[striplen:])
868 elif destdirexists:
868 elif destdirexists:
869 res = lambda p: os.path.join(dest, os.path.basename(p))
869 res = lambda p: os.path.join(dest, os.path.basename(p))
870 else:
870 else:
871 res = lambda p: dest
871 res = lambda p: dest
872 return res
872 return res
873
873
874 def targetpathafterfn(pat, dest, srcs):
874 def targetpathafterfn(pat, dest, srcs):
875 if util.patkind(pat, None)[0]:
875 if util.patkind(pat, None)[0]:
876 # a mercurial pattern
876 # a mercurial pattern
877 res = lambda p: os.path.join(dest, os.path.basename(p))
877 res = lambda p: os.path.join(dest, os.path.basename(p))
878 elif len(util.canonpath(repo.root, cwd, pat)) < len(srcs[0][0]):
878 elif len(util.canonpath(repo.root, cwd, pat)) < len(srcs[0][0]):
879 # A directory. Either the target path contains the last
879 # A directory. Either the target path contains the last
880 # component of the source path or it does not.
880 # component of the source path or it does not.
881 def evalpath(striplen):
881 def evalpath(striplen):
882 score = 0
882 score = 0
883 for s in srcs:
883 for s in srcs:
884 t = os.path.join(dest, s[1][striplen:])
884 t = os.path.join(dest, s[1][striplen:])
885 if os.path.exists(t):
885 if os.path.exists(t):
886 score += 1
886 score += 1
887 return score
887 return score
888
888
889 if pat.endswith(os.sep):
889 if pat.endswith(os.sep):
890 pat = pat[:-len(os.sep)]
890 pat = pat[:-len(os.sep)]
891 striplen = len(pat) + len(os.sep)
891 striplen = len(pat) + len(os.sep)
892 if os.path.isdir(os.path.join(dest, os.path.split(pat)[1])):
892 if os.path.isdir(os.path.join(dest, os.path.split(pat)[1])):
893 score = evalpath(striplen)
893 score = evalpath(striplen)
894 striplen1 = len(os.path.split(pat)[0])
894 striplen1 = len(os.path.split(pat)[0])
895 if striplen1:
895 if striplen1:
896 striplen1 += len(os.sep)
896 striplen1 += len(os.sep)
897 if evalpath(striplen1) > score:
897 if evalpath(striplen1) > score:
898 striplen = striplen1
898 striplen = striplen1
899 res = lambda p: os.path.join(dest, p[striplen:])
899 res = lambda p: os.path.join(dest, p[striplen:])
900 else:
900 else:
901 # a file
901 # a file
902 if destdirexists:
902 if destdirexists:
903 res = lambda p: os.path.join(dest, os.path.basename(p))
903 res = lambda p: os.path.join(dest, os.path.basename(p))
904 else:
904 else:
905 res = lambda p: dest
905 res = lambda p: dest
906 return res
906 return res
907
907
908
908
909 pats = list(pats)
909 pats = list(pats)
910 if not pats:
910 if not pats:
911 raise util.Abort(_('no source or destination specified'))
911 raise util.Abort(_('no source or destination specified'))
912 if len(pats) == 1:
912 if len(pats) == 1:
913 raise util.Abort(_('no destination specified'))
913 raise util.Abort(_('no destination specified'))
914 dest = pats.pop()
914 dest = pats.pop()
915 destdirexists = os.path.isdir(dest)
915 destdirexists = os.path.isdir(dest)
916 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
916 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
917 raise util.Abort(_('with multiple sources, destination must be an '
917 raise util.Abort(_('with multiple sources, destination must be an '
918 'existing directory'))
918 'existing directory'))
919 if opts['after']:
919 if opts['after']:
920 tfn = targetpathafterfn
920 tfn = targetpathafterfn
921 else:
921 else:
922 tfn = targetpathfn
922 tfn = targetpathfn
923 copylist = []
923 copylist = []
924 for pat in pats:
924 for pat in pats:
925 srcs = []
925 srcs = []
926 for tag, abssrc, relsrc, exact in walk(repo, [pat], opts):
926 for tag, abssrc, relsrc, exact in walk(repo, [pat], opts):
927 if okaytocopy(abssrc, relsrc, exact):
927 if okaytocopy(abssrc, relsrc, exact):
928 srcs.append((abssrc, relsrc, exact))
928 srcs.append((abssrc, relsrc, exact))
929 if not srcs:
929 if not srcs:
930 continue
930 continue
931 copylist.append((tfn(pat, dest, srcs), srcs))
931 copylist.append((tfn(pat, dest, srcs), srcs))
932 if not copylist:
932 if not copylist:
933 raise util.Abort(_('no files to copy'))
933 raise util.Abort(_('no files to copy'))
934
934
935 for targetpath, srcs in copylist:
935 for targetpath, srcs in copylist:
936 for abssrc, relsrc, exact in srcs:
936 for abssrc, relsrc, exact in srcs:
937 copy(abssrc, relsrc, targetpath(relsrc), exact)
937 copy(abssrc, relsrc, targetpath(relsrc), exact)
938
938
939 if errors:
939 if errors:
940 ui.warn(_('(consider using --after)\n'))
940 ui.warn(_('(consider using --after)\n'))
941 return errors, copied
941 return errors, copied
942
942
943 def copy(ui, repo, *pats, **opts):
943 def copy(ui, repo, *pats, **opts):
944 """mark files as copied for the next commit
944 """mark files as copied for the next commit
945
945
946 Mark dest as having copies of source files. If dest is a
946 Mark dest as having copies of source files. If dest is a
947 directory, copies are put in that directory. If dest is a file,
947 directory, copies are put in that directory. If dest is a file,
948 there can only be one source.
948 there can only be one source.
949
949
950 By default, this command copies the contents of files as they
950 By default, this command copies the contents of files as they
951 stand in the working directory. If invoked with --after, the
951 stand in the working directory. If invoked with --after, the
952 operation is recorded, but no copying is performed.
952 operation is recorded, but no copying is performed.
953
953
954 This command takes effect in the next commit.
954 This command takes effect in the next commit.
955
955
956 NOTE: This command should be treated as experimental. While it
956 NOTE: This command should be treated as experimental. While it
957 should properly record copied files, this information is not yet
957 should properly record copied files, this information is not yet
958 fully used by merge, nor fully reported by log.
958 fully used by merge, nor fully reported by log.
959 """
959 """
960 errs, copied = docopy(ui, repo, pats, opts)
960 errs, copied = docopy(ui, repo, pats, opts)
961 return errs
961 return errs
962
962
963 def debugancestor(ui, index, rev1, rev2):
963 def debugancestor(ui, index, rev1, rev2):
964 """find the ancestor revision of two revisions in a given index"""
964 """find the ancestor revision of two revisions in a given index"""
965 r = revlog.revlog(util.opener(os.getcwd()), index, "")
965 r = revlog.revlog(util.opener(os.getcwd()), index, "")
966 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
966 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
967 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
967 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
968
968
969 def debugcheckstate(ui, repo):
969 def debugcheckstate(ui, repo):
970 """validate the correctness of the current dirstate"""
970 """validate the correctness of the current dirstate"""
971 parent1, parent2 = repo.dirstate.parents()
971 parent1, parent2 = repo.dirstate.parents()
972 repo.dirstate.read()
972 repo.dirstate.read()
973 dc = repo.dirstate.map
973 dc = repo.dirstate.map
974 keys = dc.keys()
974 keys = dc.keys()
975 keys.sort()
975 keys.sort()
976 m1n = repo.changelog.read(parent1)[0]
976 m1n = repo.changelog.read(parent1)[0]
977 m2n = repo.changelog.read(parent2)[0]
977 m2n = repo.changelog.read(parent2)[0]
978 m1 = repo.manifest.read(m1n)
978 m1 = repo.manifest.read(m1n)
979 m2 = repo.manifest.read(m2n)
979 m2 = repo.manifest.read(m2n)
980 errors = 0
980 errors = 0
981 for f in dc:
981 for f in dc:
982 state = repo.dirstate.state(f)
982 state = repo.dirstate.state(f)
983 if state in "nr" and f not in m1:
983 if state in "nr" and f not in m1:
984 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
984 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
985 errors += 1
985 errors += 1
986 if state in "a" and f in m1:
986 if state in "a" and f in m1:
987 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
987 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
988 errors += 1
988 errors += 1
989 if state in "m" and f not in m1 and f not in m2:
989 if state in "m" and f not in m1 and f not in m2:
990 ui.warn(_("%s in state %s, but not in either manifest\n") %
990 ui.warn(_("%s in state %s, but not in either manifest\n") %
991 (f, state))
991 (f, state))
992 errors += 1
992 errors += 1
993 for f in m1:
993 for f in m1:
994 state = repo.dirstate.state(f)
994 state = repo.dirstate.state(f)
995 if state not in "nrm":
995 if state not in "nrm":
996 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
996 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
997 errors += 1
997 errors += 1
998 if errors:
998 if errors:
999 error = _(".hg/dirstate inconsistent with current parent's manifest")
999 error = _(".hg/dirstate inconsistent with current parent's manifest")
1000 raise util.Abort(error)
1000 raise util.Abort(error)
1001
1001
1002 def debugconfig(ui):
1002 def debugconfig(ui):
1003 """show combined config settings from all hgrc files"""
1003 """show combined config settings from all hgrc files"""
1004 try:
1004 try:
1005 repo = hg.repository(ui)
1005 repo = hg.repository(ui)
1006 except hg.RepoError:
1006 except hg.RepoError:
1007 pass
1007 pass
1008 for section, name, value in ui.walkconfig():
1008 for section, name, value in ui.walkconfig():
1009 ui.write('%s.%s=%s\n' % (section, name, value))
1009 ui.write('%s.%s=%s\n' % (section, name, value))
1010
1010
1011 def debugsetparents(ui, repo, rev1, rev2=None):
1011 def debugsetparents(ui, repo, rev1, rev2=None):
1012 """manually set the parents of the current working directory
1012 """manually set the parents of the current working directory
1013
1013
1014 This is useful for writing repository conversion tools, but should
1014 This is useful for writing repository conversion tools, but should
1015 be used with care.
1015 be used with care.
1016 """
1016 """
1017
1017
1018 if not rev2:
1018 if not rev2:
1019 rev2 = hex(nullid)
1019 rev2 = hex(nullid)
1020
1020
1021 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1021 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1022
1022
1023 def debugstate(ui, repo):
1023 def debugstate(ui, repo):
1024 """show the contents of the current dirstate"""
1024 """show the contents of the current dirstate"""
1025 repo.dirstate.read()
1025 repo.dirstate.read()
1026 dc = repo.dirstate.map
1026 dc = repo.dirstate.map
1027 keys = dc.keys()
1027 keys = dc.keys()
1028 keys.sort()
1028 keys.sort()
1029 for file_ in keys:
1029 for file_ in keys:
1030 ui.write("%c %3o %10d %s %s\n"
1030 ui.write("%c %3o %10d %s %s\n"
1031 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
1031 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
1032 time.strftime("%x %X",
1032 time.strftime("%x %X",
1033 time.localtime(dc[file_][3])), file_))
1033 time.localtime(dc[file_][3])), file_))
1034 for f in repo.dirstate.copies:
1034 for f in repo.dirstate.copies:
1035 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copies[f], f))
1035 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copies[f], f))
1036
1036
1037 def debugdata(ui, file_, rev):
1037 def debugdata(ui, file_, rev):
1038 """dump the contents of an data file revision"""
1038 """dump the contents of an data file revision"""
1039 r = revlog.revlog(util.opener(os.getcwd()), file_[:-2] + ".i", file_)
1039 r = revlog.revlog(util.opener(os.getcwd()), file_[:-2] + ".i", file_)
1040 try:
1040 try:
1041 ui.write(r.revision(r.lookup(rev)))
1041 ui.write(r.revision(r.lookup(rev)))
1042 except KeyError:
1042 except KeyError:
1043 raise util.Abort(_('invalid revision identifier %s'), rev)
1043 raise util.Abort(_('invalid revision identifier %s'), rev)
1044
1044
1045 def debugindex(ui, file_):
1045 def debugindex(ui, file_):
1046 """dump the contents of an index file"""
1046 """dump the contents of an index file"""
1047 r = revlog.revlog(util.opener(os.getcwd()), file_, "")
1047 r = revlog.revlog(util.opener(os.getcwd()), file_, "")
1048 ui.write(" rev offset length base linkrev" +
1048 ui.write(" rev offset length base linkrev" +
1049 " nodeid p1 p2\n")
1049 " nodeid p1 p2\n")
1050 for i in range(r.count()):
1050 for i in range(r.count()):
1051 e = r.index[i]
1051 e = r.index[i]
1052 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1052 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1053 i, e[0], e[1], e[2], e[3],
1053 i, e[0], e[1], e[2], e[3],
1054 short(e[6]), short(e[4]), short(e[5])))
1054 short(e[6]), short(e[4]), short(e[5])))
1055
1055
1056 def debugindexdot(ui, file_):
1056 def debugindexdot(ui, file_):
1057 """dump an index DAG as a .dot file"""
1057 """dump an index DAG as a .dot file"""
1058 r = revlog.revlog(util.opener(os.getcwd()), file_, "")
1058 r = revlog.revlog(util.opener(os.getcwd()), file_, "")
1059 ui.write("digraph G {\n")
1059 ui.write("digraph G {\n")
1060 for i in range(r.count()):
1060 for i in range(r.count()):
1061 e = r.index[i]
1061 e = r.index[i]
1062 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
1062 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
1063 if e[5] != nullid:
1063 if e[5] != nullid:
1064 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
1064 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
1065 ui.write("}\n")
1065 ui.write("}\n")
1066
1066
1067 def debugrename(ui, repo, file, rev=None):
1067 def debugrename(ui, repo, file, rev=None):
1068 """dump rename information"""
1068 """dump rename information"""
1069 r = repo.file(relpath(repo, [file])[0])
1069 r = repo.file(relpath(repo, [file])[0])
1070 if rev:
1070 if rev:
1071 try:
1071 try:
1072 # assume all revision numbers are for changesets
1072 # assume all revision numbers are for changesets
1073 n = repo.lookup(rev)
1073 n = repo.lookup(rev)
1074 change = repo.changelog.read(n)
1074 change = repo.changelog.read(n)
1075 m = repo.manifest.read(change[0])
1075 m = repo.manifest.read(change[0])
1076 n = m[relpath(repo, [file])[0]]
1076 n = m[relpath(repo, [file])[0]]
1077 except (hg.RepoError, KeyError):
1077 except (hg.RepoError, KeyError):
1078 n = r.lookup(rev)
1078 n = r.lookup(rev)
1079 else:
1079 else:
1080 n = r.tip()
1080 n = r.tip()
1081 m = r.renamed(n)
1081 m = r.renamed(n)
1082 if m:
1082 if m:
1083 ui.write(_("renamed from %s:%s\n") % (m[0], hex(m[1])))
1083 ui.write(_("renamed from %s:%s\n") % (m[0], hex(m[1])))
1084 else:
1084 else:
1085 ui.write(_("not renamed\n"))
1085 ui.write(_("not renamed\n"))
1086
1086
1087 def debugwalk(ui, repo, *pats, **opts):
1087 def debugwalk(ui, repo, *pats, **opts):
1088 """show how files match on given patterns"""
1088 """show how files match on given patterns"""
1089 items = list(walk(repo, pats, opts))
1089 items = list(walk(repo, pats, opts))
1090 if not items:
1090 if not items:
1091 return
1091 return
1092 fmt = '%%s %%-%ds %%-%ds %%s' % (
1092 fmt = '%%s %%-%ds %%-%ds %%s' % (
1093 max([len(abs) for (src, abs, rel, exact) in items]),
1093 max([len(abs) for (src, abs, rel, exact) in items]),
1094 max([len(rel) for (src, abs, rel, exact) in items]))
1094 max([len(rel) for (src, abs, rel, exact) in items]))
1095 for src, abs, rel, exact in items:
1095 for src, abs, rel, exact in items:
1096 line = fmt % (src, abs, rel, exact and 'exact' or '')
1096 line = fmt % (src, abs, rel, exact and 'exact' or '')
1097 ui.write("%s\n" % line.rstrip())
1097 ui.write("%s\n" % line.rstrip())
1098
1098
1099 def diff(ui, repo, *pats, **opts):
1099 def diff(ui, repo, *pats, **opts):
1100 """diff repository (or selected files)
1100 """diff repository (or selected files)
1101
1101
1102 Show differences between revisions for the specified files.
1102 Show differences between revisions for the specified files.
1103
1103
1104 Differences between files are shown using the unified diff format.
1104 Differences between files are shown using the unified diff format.
1105
1105
1106 When two revision arguments are given, then changes are shown
1106 When two revision arguments are given, then changes are shown
1107 between those revisions. If only one revision is specified then
1107 between those revisions. If only one revision is specified then
1108 that revision is compared to the working directory, and, when no
1108 that revision is compared to the working directory, and, when no
1109 revisions are specified, the working directory files are compared
1109 revisions are specified, the working directory files are compared
1110 to its parent.
1110 to its parent.
1111
1111
1112 Without the -a option, diff will avoid generating diffs of files
1112 Without the -a option, diff will avoid generating diffs of files
1113 it detects as binary. With -a, diff will generate a diff anyway,
1113 it detects as binary. With -a, diff will generate a diff anyway,
1114 probably with undesirable results.
1114 probably with undesirable results.
1115 """
1115 """
1116 node1, node2 = None, None
1116 node1, node2 = None, None
1117 revs = [repo.lookup(x) for x in opts['rev']]
1117 revs = [repo.lookup(x) for x in opts['rev']]
1118
1118
1119 if len(revs) > 0:
1119 if len(revs) > 0:
1120 node1 = revs[0]
1120 node1 = revs[0]
1121 if len(revs) > 1:
1121 if len(revs) > 1:
1122 node2 = revs[1]
1122 node2 = revs[1]
1123 if len(revs) > 2:
1123 if len(revs) > 2:
1124 raise util.Abort(_("too many revisions to diff"))
1124 raise util.Abort(_("too many revisions to diff"))
1125
1125
1126 fns, matchfn, anypats = matchpats(repo, pats, opts)
1126 fns, matchfn, anypats = matchpats(repo, pats, opts)
1127
1127
1128 dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn,
1128 dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn,
1129 text=opts['text'])
1129 text=opts['text'])
1130
1130
1131 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
1131 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
1132 node = repo.lookup(changeset)
1132 node = repo.lookup(changeset)
1133 parents = [p for p in repo.changelog.parents(node) if p != nullid]
1133 parents = [p for p in repo.changelog.parents(node) if p != nullid]
1134 if opts['switch_parent']:
1134 if opts['switch_parent']:
1135 parents.reverse()
1135 parents.reverse()
1136 prev = (parents and parents[0]) or nullid
1136 prev = (parents and parents[0]) or nullid
1137 change = repo.changelog.read(node)
1137 change = repo.changelog.read(node)
1138
1138
1139 fp = make_file(repo, repo.changelog, opts['output'],
1139 fp = make_file(repo, repo.changelog, opts['output'],
1140 node=node, total=total, seqno=seqno,
1140 node=node, total=total, seqno=seqno,
1141 revwidth=revwidth)
1141 revwidth=revwidth)
1142 if fp != sys.stdout:
1142 if fp != sys.stdout:
1143 ui.note("%s\n" % fp.name)
1143 ui.note("%s\n" % fp.name)
1144
1144
1145 fp.write("# HG changeset patch\n")
1145 fp.write("# HG changeset patch\n")
1146 fp.write("# User %s\n" % change[1])
1146 fp.write("# User %s\n" % change[1])
1147 fp.write("# Node ID %s\n" % hex(node))
1147 fp.write("# Node ID %s\n" % hex(node))
1148 fp.write("# Parent %s\n" % hex(prev))
1148 fp.write("# Parent %s\n" % hex(prev))
1149 if len(parents) > 1:
1149 if len(parents) > 1:
1150 fp.write("# Parent %s\n" % hex(parents[1]))
1150 fp.write("# Parent %s\n" % hex(parents[1]))
1151 fp.write(change[4].rstrip())
1151 fp.write(change[4].rstrip())
1152 fp.write("\n\n")
1152 fp.write("\n\n")
1153
1153
1154 dodiff(fp, ui, repo, prev, node, text=opts['text'])
1154 dodiff(fp, ui, repo, prev, node, text=opts['text'])
1155 if fp != sys.stdout:
1155 if fp != sys.stdout:
1156 fp.close()
1156 fp.close()
1157
1157
1158 def export(ui, repo, *changesets, **opts):
1158 def export(ui, repo, *changesets, **opts):
1159 """dump the header and diffs for one or more changesets
1159 """dump the header and diffs for one or more changesets
1160
1160
1161 Print the changeset header and diffs for one or more revisions.
1161 Print the changeset header and diffs for one or more revisions.
1162
1162
1163 The information shown in the changeset header is: author,
1163 The information shown in the changeset header is: author,
1164 changeset hash, parent and commit comment.
1164 changeset hash, parent and commit comment.
1165
1165
1166 Output may be to a file, in which case the name of the file is
1166 Output may be to a file, in which case the name of the file is
1167 given using a format string. The formatting rules are as follows:
1167 given using a format string. The formatting rules are as follows:
1168
1168
1169 %% literal "%" character
1169 %% literal "%" character
1170 %H changeset hash (40 bytes of hexadecimal)
1170 %H changeset hash (40 bytes of hexadecimal)
1171 %N number of patches being generated
1171 %N number of patches being generated
1172 %R changeset revision number
1172 %R changeset revision number
1173 %b basename of the exporting repository
1173 %b basename of the exporting repository
1174 %h short-form changeset hash (12 bytes of hexadecimal)
1174 %h short-form changeset hash (12 bytes of hexadecimal)
1175 %n zero-padded sequence number, starting at 1
1175 %n zero-padded sequence number, starting at 1
1176 %r zero-padded changeset revision number
1176 %r zero-padded changeset revision number
1177
1177
1178 Without the -a option, export will avoid generating diffs of files
1178 Without the -a option, export will avoid generating diffs of files
1179 it detects as binary. With -a, export will generate a diff anyway,
1179 it detects as binary. With -a, export will generate a diff anyway,
1180 probably with undesirable results.
1180 probably with undesirable results.
1181
1181
1182 With the --switch-parent option, the diff will be against the second
1182 With the --switch-parent option, the diff will be against the second
1183 parent. It can be useful to review a merge.
1183 parent. It can be useful to review a merge.
1184 """
1184 """
1185 if not changesets:
1185 if not changesets:
1186 raise util.Abort(_("export requires at least one changeset"))
1186 raise util.Abort(_("export requires at least one changeset"))
1187 seqno = 0
1187 seqno = 0
1188 revs = list(revrange(ui, repo, changesets))
1188 revs = list(revrange(ui, repo, changesets))
1189 total = len(revs)
1189 total = len(revs)
1190 revwidth = max(map(len, revs))
1190 revwidth = max(map(len, revs))
1191 msg = len(revs) > 1 and _("Exporting patches:\n") or _("Exporting patch:\n")
1191 msg = len(revs) > 1 and _("Exporting patches:\n") or _("Exporting patch:\n")
1192 ui.note(msg)
1192 ui.note(msg)
1193 for cset in revs:
1193 for cset in revs:
1194 seqno += 1
1194 seqno += 1
1195 doexport(ui, repo, cset, seqno, total, revwidth, opts)
1195 doexport(ui, repo, cset, seqno, total, revwidth, opts)
1196
1196
1197 def forget(ui, repo, *pats, **opts):
1197 def forget(ui, repo, *pats, **opts):
1198 """don't add the specified files on the next commit
1198 """don't add the specified files on the next commit
1199
1199
1200 Undo an 'hg add' scheduled for the next commit.
1200 Undo an 'hg add' scheduled for the next commit.
1201 """
1201 """
1202 forget = []
1202 forget = []
1203 for src, abs, rel, exact in walk(repo, pats, opts):
1203 for src, abs, rel, exact in walk(repo, pats, opts):
1204 if repo.dirstate.state(abs) == 'a':
1204 if repo.dirstate.state(abs) == 'a':
1205 forget.append(abs)
1205 forget.append(abs)
1206 if ui.verbose or not exact:
1206 if ui.verbose or not exact:
1207 ui.status(_('forgetting %s\n') % ((pats and rel) or abs))
1207 ui.status(_('forgetting %s\n') % ((pats and rel) or abs))
1208 repo.forget(forget)
1208 repo.forget(forget)
1209
1209
1210 def grep(ui, repo, pattern, *pats, **opts):
1210 def grep(ui, repo, pattern, *pats, **opts):
1211 """search for a pattern in specified files and revisions
1211 """search for a pattern in specified files and revisions
1212
1212
1213 Search revisions of files for a regular expression.
1213 Search revisions of files for a regular expression.
1214
1214
1215 This command behaves differently than Unix grep. It only accepts
1215 This command behaves differently than Unix grep. It only accepts
1216 Python/Perl regexps. It searches repository history, not the
1216 Python/Perl regexps. It searches repository history, not the
1217 working directory. It always prints the revision number in which
1217 working directory. It always prints the revision number in which
1218 a match appears.
1218 a match appears.
1219
1219
1220 By default, grep only prints output for the first revision of a
1220 By default, grep only prints output for the first revision of a
1221 file in which it finds a match. To get it to print every revision
1221 file in which it finds a match. To get it to print every revision
1222 that contains a change in match status ("-" for a match that
1222 that contains a change in match status ("-" for a match that
1223 becomes a non-match, or "+" for a non-match that becomes a match),
1223 becomes a non-match, or "+" for a non-match that becomes a match),
1224 use the --all flag.
1224 use the --all flag.
1225 """
1225 """
1226 reflags = 0
1226 reflags = 0
1227 if opts['ignore_case']:
1227 if opts['ignore_case']:
1228 reflags |= re.I
1228 reflags |= re.I
1229 regexp = re.compile(pattern, reflags)
1229 regexp = re.compile(pattern, reflags)
1230 sep, eol = ':', '\n'
1230 sep, eol = ':', '\n'
1231 if opts['print0']:
1231 if opts['print0']:
1232 sep = eol = '\0'
1232 sep = eol = '\0'
1233
1233
1234 fcache = {}
1234 fcache = {}
1235 def getfile(fn):
1235 def getfile(fn):
1236 if fn not in fcache:
1236 if fn not in fcache:
1237 fcache[fn] = repo.file(fn)
1237 fcache[fn] = repo.file(fn)
1238 return fcache[fn]
1238 return fcache[fn]
1239
1239
1240 def matchlines(body):
1240 def matchlines(body):
1241 begin = 0
1241 begin = 0
1242 linenum = 0
1242 linenum = 0
1243 while True:
1243 while True:
1244 match = regexp.search(body, begin)
1244 match = regexp.search(body, begin)
1245 if not match:
1245 if not match:
1246 break
1246 break
1247 mstart, mend = match.span()
1247 mstart, mend = match.span()
1248 linenum += body.count('\n', begin, mstart) + 1
1248 linenum += body.count('\n', begin, mstart) + 1
1249 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1249 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1250 lend = body.find('\n', mend)
1250 lend = body.find('\n', mend)
1251 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1251 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1252 begin = lend + 1
1252 begin = lend + 1
1253
1253
1254 class linestate(object):
1254 class linestate(object):
1255 def __init__(self, line, linenum, colstart, colend):
1255 def __init__(self, line, linenum, colstart, colend):
1256 self.line = line
1256 self.line = line
1257 self.linenum = linenum
1257 self.linenum = linenum
1258 self.colstart = colstart
1258 self.colstart = colstart
1259 self.colend = colend
1259 self.colend = colend
1260 def __eq__(self, other):
1260 def __eq__(self, other):
1261 return self.line == other.line
1261 return self.line == other.line
1262 def __hash__(self):
1262 def __hash__(self):
1263 return hash(self.line)
1263 return hash(self.line)
1264
1264
1265 matches = {}
1265 matches = {}
1266 def grepbody(fn, rev, body):
1266 def grepbody(fn, rev, body):
1267 matches[rev].setdefault(fn, {})
1267 matches[rev].setdefault(fn, {})
1268 m = matches[rev][fn]
1268 m = matches[rev][fn]
1269 for lnum, cstart, cend, line in matchlines(body):
1269 for lnum, cstart, cend, line in matchlines(body):
1270 s = linestate(line, lnum, cstart, cend)
1270 s = linestate(line, lnum, cstart, cend)
1271 m[s] = s
1271 m[s] = s
1272
1272
1273 prev = {}
1273 prev = {}
1274 ucache = {}
1274 ucache = {}
1275 def display(fn, rev, states, prevstates):
1275 def display(fn, rev, states, prevstates):
1276 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
1276 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
1277 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
1277 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
1278 counts = {'-': 0, '+': 0}
1278 counts = {'-': 0, '+': 0}
1279 filerevmatches = {}
1279 filerevmatches = {}
1280 for l in diff:
1280 for l in diff:
1281 if incrementing or not opts['all']:
1281 if incrementing or not opts['all']:
1282 change = ((l in prevstates) and '-') or '+'
1282 change = ((l in prevstates) and '-') or '+'
1283 r = rev
1283 r = rev
1284 else:
1284 else:
1285 change = ((l in states) and '-') or '+'
1285 change = ((l in states) and '-') or '+'
1286 r = prev[fn]
1286 r = prev[fn]
1287 cols = [fn, str(rev)]
1287 cols = [fn, str(rev)]
1288 if opts['line_number']:
1288 if opts['line_number']:
1289 cols.append(str(l.linenum))
1289 cols.append(str(l.linenum))
1290 if opts['all']:
1290 if opts['all']:
1291 cols.append(change)
1291 cols.append(change)
1292 if opts['user']:
1292 if opts['user']:
1293 cols.append(trimuser(ui, getchange(rev)[1], rev,
1293 cols.append(trimuser(ui, getchange(rev)[1], rev,
1294 ucache))
1294 ucache))
1295 if opts['files_with_matches']:
1295 if opts['files_with_matches']:
1296 c = (fn, rev)
1296 c = (fn, rev)
1297 if c in filerevmatches:
1297 if c in filerevmatches:
1298 continue
1298 continue
1299 filerevmatches[c] = 1
1299 filerevmatches[c] = 1
1300 else:
1300 else:
1301 cols.append(l.line)
1301 cols.append(l.line)
1302 ui.write(sep.join(cols), eol)
1302 ui.write(sep.join(cols), eol)
1303 counts[change] += 1
1303 counts[change] += 1
1304 return counts['+'], counts['-']
1304 return counts['+'], counts['-']
1305
1305
1306 fstate = {}
1306 fstate = {}
1307 skip = {}
1307 skip = {}
1308 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1308 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1309 count = 0
1309 count = 0
1310 incrementing = False
1310 incrementing = False
1311 for st, rev, fns in changeiter:
1311 for st, rev, fns in changeiter:
1312 if st == 'window':
1312 if st == 'window':
1313 incrementing = rev
1313 incrementing = rev
1314 matches.clear()
1314 matches.clear()
1315 elif st == 'add':
1315 elif st == 'add':
1316 change = repo.changelog.read(repo.lookup(str(rev)))
1316 change = repo.changelog.read(repo.lookup(str(rev)))
1317 mf = repo.manifest.read(change[0])
1317 mf = repo.manifest.read(change[0])
1318 matches[rev] = {}
1318 matches[rev] = {}
1319 for fn in fns:
1319 for fn in fns:
1320 if fn in skip:
1320 if fn in skip:
1321 continue
1321 continue
1322 fstate.setdefault(fn, {})
1322 fstate.setdefault(fn, {})
1323 try:
1323 try:
1324 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1324 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1325 except KeyError:
1325 except KeyError:
1326 pass
1326 pass
1327 elif st == 'iter':
1327 elif st == 'iter':
1328 states = matches[rev].items()
1328 states = matches[rev].items()
1329 states.sort()
1329 states.sort()
1330 for fn, m in states:
1330 for fn, m in states:
1331 if fn in skip:
1331 if fn in skip:
1332 continue
1332 continue
1333 if incrementing or not opts['all'] or fstate[fn]:
1333 if incrementing or not opts['all'] or fstate[fn]:
1334 pos, neg = display(fn, rev, m, fstate[fn])
1334 pos, neg = display(fn, rev, m, fstate[fn])
1335 count += pos + neg
1335 count += pos + neg
1336 if pos and not opts['all']:
1336 if pos and not opts['all']:
1337 skip[fn] = True
1337 skip[fn] = True
1338 fstate[fn] = m
1338 fstate[fn] = m
1339 prev[fn] = rev
1339 prev[fn] = rev
1340
1340
1341 if not incrementing:
1341 if not incrementing:
1342 fstate = fstate.items()
1342 fstate = fstate.items()
1343 fstate.sort()
1343 fstate.sort()
1344 for fn, state in fstate:
1344 for fn, state in fstate:
1345 if fn in skip:
1345 if fn in skip:
1346 continue
1346 continue
1347 display(fn, rev, {}, state)
1347 display(fn, rev, {}, state)
1348 return (count == 0 and 1) or 0
1348 return (count == 0 and 1) or 0
1349
1349
1350 def heads(ui, repo, **opts):
1350 def heads(ui, repo, **opts):
1351 """show current repository heads
1351 """show current repository heads
1352
1352
1353 Show all repository head changesets.
1353 Show all repository head changesets.
1354
1354
1355 Repository "heads" are changesets that don't have children
1355 Repository "heads" are changesets that don't have children
1356 changesets. They are where development generally takes place and
1356 changesets. They are where development generally takes place and
1357 are the usual targets for update and merge operations.
1357 are the usual targets for update and merge operations.
1358 """
1358 """
1359 if opts['rev']:
1359 if opts['rev']:
1360 heads = repo.heads(repo.lookup(opts['rev']))
1360 heads = repo.heads(repo.lookup(opts['rev']))
1361 else:
1361 else:
1362 heads = repo.heads()
1362 heads = repo.heads()
1363 br = None
1363 br = None
1364 if opts['branches']:
1364 if opts['branches']:
1365 br = repo.branchlookup(heads)
1365 br = repo.branchlookup(heads)
1366 for n in heads:
1366 for n in heads:
1367 show_changeset(ui, repo, changenode=n, brinfo=br)
1367 show_changeset(ui, repo, changenode=n, brinfo=br)
1368
1368
1369 def identify(ui, repo):
1369 def identify(ui, repo):
1370 """print information about the working copy
1370 """print information about the working copy
1371
1371
1372 Print a short summary of the current state of the repo.
1372 Print a short summary of the current state of the repo.
1373
1373
1374 This summary identifies the repository state using one or two parent
1374 This summary identifies the repository state using one or two parent
1375 hash identifiers, followed by a "+" if there are uncommitted changes
1375 hash identifiers, followed by a "+" if there are uncommitted changes
1376 in the working directory, followed by a list of tags for this revision.
1376 in the working directory, followed by a list of tags for this revision.
1377 """
1377 """
1378 parents = [p for p in repo.dirstate.parents() if p != nullid]
1378 parents = [p for p in repo.dirstate.parents() if p != nullid]
1379 if not parents:
1379 if not parents:
1380 ui.write(_("unknown\n"))
1380 ui.write(_("unknown\n"))
1381 return
1381 return
1382
1382
1383 hexfunc = ui.verbose and hex or short
1383 hexfunc = ui.verbose and hex or short
1384 modified, added, removed, deleted, unknown = repo.changes()
1384 modified, added, removed, deleted, unknown = repo.changes()
1385 output = ["%s%s" %
1385 output = ["%s%s" %
1386 ('+'.join([hexfunc(parent) for parent in parents]),
1386 ('+'.join([hexfunc(parent) for parent in parents]),
1387 (modified or added or removed or deleted) and "+" or "")]
1387 (modified or added or removed or deleted) and "+" or "")]
1388
1388
1389 if not ui.quiet:
1389 if not ui.quiet:
1390 # multiple tags for a single parent separated by '/'
1390 # multiple tags for a single parent separated by '/'
1391 parenttags = ['/'.join(tags)
1391 parenttags = ['/'.join(tags)
1392 for tags in map(repo.nodetags, parents) if tags]
1392 for tags in map(repo.nodetags, parents) if tags]
1393 # tags for multiple parents separated by ' + '
1393 # tags for multiple parents separated by ' + '
1394 if parenttags:
1394 if parenttags:
1395 output.append(' + '.join(parenttags))
1395 output.append(' + '.join(parenttags))
1396
1396
1397 ui.write("%s\n" % ' '.join(output))
1397 ui.write("%s\n" % ' '.join(output))
1398
1398
1399 def import_(ui, repo, patch1, *patches, **opts):
1399 def import_(ui, repo, patch1, *patches, **opts):
1400 """import an ordered set of patches
1400 """import an ordered set of patches
1401
1401
1402 Import a list of patches and commit them individually.
1402 Import a list of patches and commit them individually.
1403
1403
1404 If there are outstanding changes in the working directory, import
1404 If there are outstanding changes in the working directory, import
1405 will abort unless given the -f flag.
1405 will abort unless given the -f flag.
1406
1406
1407 If a patch looks like a mail message (its first line starts with
1407 If a patch looks like a mail message (its first line starts with
1408 "From " or looks like an RFC822 header), it will not be applied
1408 "From " or looks like an RFC822 header), it will not be applied
1409 unless the -f option is used. The importer neither parses nor
1409 unless the -f option is used. The importer neither parses nor
1410 discards mail headers, so use -f only to override the "mailness"
1410 discards mail headers, so use -f only to override the "mailness"
1411 safety check, not to import a real mail message.
1411 safety check, not to import a real mail message.
1412 """
1412 """
1413 patches = (patch1,) + patches
1413 patches = (patch1,) + patches
1414
1414
1415 if not opts['force']:
1415 if not opts['force']:
1416 modified, added, removed, deleted, unknown = repo.changes()
1416 modified, added, removed, deleted, unknown = repo.changes()
1417 if modified or added or removed or deleted:
1417 if modified or added or removed or deleted:
1418 raise util.Abort(_("outstanding uncommitted changes"))
1418 raise util.Abort(_("outstanding uncommitted changes"))
1419
1419
1420 d = opts["base"]
1420 d = opts["base"]
1421 strip = opts["strip"]
1421 strip = opts["strip"]
1422
1422
1423 mailre = re.compile(r'(?:From |[\w-]+:)')
1423 mailre = re.compile(r'(?:From |[\w-]+:)')
1424
1424
1425 # attempt to detect the start of a patch
1425 # attempt to detect the start of a patch
1426 # (this heuristic is borrowed from quilt)
1426 # (this heuristic is borrowed from quilt)
1427 diffre = re.compile(r'(?:Index:[ \t]|diff[ \t]|RCS file: |' +
1427 diffre = re.compile(r'(?:Index:[ \t]|diff[ \t]|RCS file: |' +
1428 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
1428 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
1429 '(---|\*\*\*)[ \t])')
1429 '(---|\*\*\*)[ \t])')
1430
1430
1431 for patch in patches:
1431 for patch in patches:
1432 ui.status(_("applying %s\n") % patch)
1432 ui.status(_("applying %s\n") % patch)
1433 pf = os.path.join(d, patch)
1433 pf = os.path.join(d, patch)
1434
1434
1435 message = []
1435 message = []
1436 user = None
1436 user = None
1437 hgpatch = False
1437 hgpatch = False
1438 for line in file(pf):
1438 for line in file(pf):
1439 line = line.rstrip()
1439 line = line.rstrip()
1440 if (not message and not hgpatch and
1440 if (not message and not hgpatch and
1441 mailre.match(line) and not opts['force']):
1441 mailre.match(line) and not opts['force']):
1442 if len(line) > 35:
1442 if len(line) > 35:
1443 line = line[:32] + '...'
1443 line = line[:32] + '...'
1444 raise util.Abort(_('first line looks like a '
1444 raise util.Abort(_('first line looks like a '
1445 'mail header: ') + line)
1445 'mail header: ') + line)
1446 if diffre.match(line):
1446 if diffre.match(line):
1447 break
1447 break
1448 elif hgpatch:
1448 elif hgpatch:
1449 # parse values when importing the result of an hg export
1449 # parse values when importing the result of an hg export
1450 if line.startswith("# User "):
1450 if line.startswith("# User "):
1451 user = line[7:]
1451 user = line[7:]
1452 ui.debug(_('User: %s\n') % user)
1452 ui.debug(_('User: %s\n') % user)
1453 elif not line.startswith("# ") and line:
1453 elif not line.startswith("# ") and line:
1454 message.append(line)
1454 message.append(line)
1455 hgpatch = False
1455 hgpatch = False
1456 elif line == '# HG changeset patch':
1456 elif line == '# HG changeset patch':
1457 hgpatch = True
1457 hgpatch = True
1458 message = [] # We may have collected garbage
1458 message = [] # We may have collected garbage
1459 else:
1459 else:
1460 message.append(line)
1460 message.append(line)
1461
1461
1462 # make sure message isn't empty
1462 # make sure message isn't empty
1463 if not message:
1463 if not message:
1464 message = _("imported patch %s\n") % patch
1464 message = _("imported patch %s\n") % patch
1465 else:
1465 else:
1466 message = "%s\n" % '\n'.join(message)
1466 message = "%s\n" % '\n'.join(message)
1467 ui.debug(_('message:\n%s\n') % message)
1467 ui.debug(_('message:\n%s\n') % message)
1468
1468
1469 files = util.patch(strip, pf, ui)
1469 files = util.patch(strip, pf, ui)
1470
1470
1471 if len(files) > 0:
1471 if len(files) > 0:
1472 addremove(ui, repo, *files)
1472 addremove(ui, repo, *files)
1473 repo.commit(files, message, user)
1473 repo.commit(files, message, user)
1474
1474
1475 def incoming(ui, repo, source="default", **opts):
1475 def incoming(ui, repo, source="default", **opts):
1476 """show new changesets found in source
1476 """show new changesets found in source
1477
1477
1478 Show new changesets found in the specified repo or the default
1478 Show new changesets found in the specified repo or the default
1479 pull repo. These are the changesets that would be pulled if a pull
1479 pull repo. These are the changesets that would be pulled if a pull
1480 was requested.
1480 was requested.
1481
1481
1482 Currently only local repositories are supported.
1482 Currently only local repositories are supported.
1483 """
1483 """
1484 source = ui.expandpath(source, repo.root)
1484 source = ui.expandpath(source, repo.root)
1485 other = hg.repository(ui, source)
1485 other = hg.repository(ui, source)
1486 if not other.local():
1486 if not other.local():
1487 raise util.Abort(_("incoming doesn't work for remote repositories yet"))
1487 raise util.Abort(_("incoming doesn't work for remote repositories yet"))
1488 o = repo.findincoming(other)
1488 o = repo.findincoming(other)
1489 if not o:
1489 if not o:
1490 return
1490 return
1491 o = other.changelog.nodesbetween(o)[0]
1491 o = other.changelog.nodesbetween(o)[0]
1492 if opts['newest_first']:
1492 if opts['newest_first']:
1493 o.reverse()
1493 o.reverse()
1494 for n in o:
1494 for n in o:
1495 parents = [p for p in other.changelog.parents(n) if p != nullid]
1495 parents = [p for p in other.changelog.parents(n) if p != nullid]
1496 if opts['no_merges'] and len(parents) == 2:
1496 if opts['no_merges'] and len(parents) == 2:
1497 continue
1497 continue
1498 show_changeset(ui, other, changenode=n)
1498 show_changeset(ui, other, changenode=n)
1499 if opts['patch']:
1499 if opts['patch']:
1500 prev = (parents and parents[0]) or nullid
1500 prev = (parents and parents[0]) or nullid
1501 dodiff(ui, ui, other, prev, n)
1501 dodiff(ui, ui, other, prev, n)
1502 ui.write("\n")
1502 ui.write("\n")
1503
1503
1504 def init(ui, dest="."):
1504 def init(ui, dest="."):
1505 """create a new repository in the given directory
1505 """create a new repository in the given directory
1506
1506
1507 Initialize a new repository in the given directory. If the given
1507 Initialize a new repository in the given directory. If the given
1508 directory does not exist, it is created.
1508 directory does not exist, it is created.
1509
1509
1510 If no directory is given, the current directory is used.
1510 If no directory is given, the current directory is used.
1511 """
1511 """
1512 if not os.path.exists(dest):
1512 if not os.path.exists(dest):
1513 os.mkdir(dest)
1513 os.mkdir(dest)
1514 hg.repository(ui, dest, create=1)
1514 hg.repository(ui, dest, create=1)
1515
1515
1516 def locate(ui, repo, *pats, **opts):
1516 def locate(ui, repo, *pats, **opts):
1517 """locate files matching specific patterns
1517 """locate files matching specific patterns
1518
1518
1519 Print all files under Mercurial control whose names match the
1519 Print all files under Mercurial control whose names match the
1520 given patterns.
1520 given patterns.
1521
1521
1522 This command searches the current directory and its
1522 This command searches the current directory and its
1523 subdirectories. To search an entire repository, move to the root
1523 subdirectories. To search an entire repository, move to the root
1524 of the repository.
1524 of the repository.
1525
1525
1526 If no patterns are given to match, this command prints all file
1526 If no patterns are given to match, this command prints all file
1527 names.
1527 names.
1528
1528
1529 If you want to feed the output of this command into the "xargs"
1529 If you want to feed the output of this command into the "xargs"
1530 command, use the "-0" option to both this command and "xargs".
1530 command, use the "-0" option to both this command and "xargs".
1531 This will avoid the problem of "xargs" treating single filenames
1531 This will avoid the problem of "xargs" treating single filenames
1532 that contain white space as multiple filenames.
1532 that contain white space as multiple filenames.
1533 """
1533 """
1534 end = opts['print0'] and '\0' or '\n'
1534 end = opts['print0'] and '\0' or '\n'
1535 rev = opts['rev']
1535 rev = opts['rev']
1536 if rev:
1536 if rev:
1537 node = repo.lookup(rev)
1537 node = repo.lookup(rev)
1538 else:
1538 else:
1539 node = None
1539 node = None
1540
1540
1541 for src, abs, rel, exact in walk(repo, pats, opts, node=node,
1541 for src, abs, rel, exact in walk(repo, pats, opts, node=node,
1542 head='(?:.*/|)'):
1542 head='(?:.*/|)'):
1543 if not node and repo.dirstate.state(abs) == '?':
1543 if not node and repo.dirstate.state(abs) == '?':
1544 continue
1544 continue
1545 if opts['fullpath']:
1545 if opts['fullpath']:
1546 ui.write(os.path.join(repo.root, abs), end)
1546 ui.write(os.path.join(repo.root, abs), end)
1547 else:
1547 else:
1548 ui.write(((pats and rel) or abs), end)
1548 ui.write(((pats and rel) or abs), end)
1549
1549
1550 def log(ui, repo, *pats, **opts):
1550 def log(ui, repo, *pats, **opts):
1551 """show revision history of entire repository or files
1551 """show revision history of entire repository or files
1552
1552
1553 Print the revision history of the specified files or the entire project.
1553 Print the revision history of the specified files or the entire project.
1554
1554
1555 By default this command outputs: changeset id and hash, tags,
1555 By default this command outputs: changeset id and hash, tags,
1556 non-trivial parents, user, date and time, and a summary for each
1556 non-trivial parents, user, date and time, and a summary for each
1557 commit. When the -v/--verbose switch is used, the list of changed
1557 commit. When the -v/--verbose switch is used, the list of changed
1558 files and full commit message is shown.
1558 files and full commit message is shown.
1559 """
1559 """
1560 class dui(object):
1560 class dui(object):
1561 # Implement and delegate some ui protocol. Save hunks of
1561 # Implement and delegate some ui protocol. Save hunks of
1562 # output for later display in the desired order.
1562 # output for later display in the desired order.
1563 def __init__(self, ui):
1563 def __init__(self, ui):
1564 self.ui = ui
1564 self.ui = ui
1565 self.hunk = {}
1565 self.hunk = {}
1566 def bump(self, rev):
1566 def bump(self, rev):
1567 self.rev = rev
1567 self.rev = rev
1568 self.hunk[rev] = []
1568 self.hunk[rev] = []
1569 def note(self, *args):
1569 def note(self, *args):
1570 if self.verbose:
1570 if self.verbose:
1571 self.write(*args)
1571 self.write(*args)
1572 def status(self, *args):
1572 def status(self, *args):
1573 if not self.quiet:
1573 if not self.quiet:
1574 self.write(*args)
1574 self.write(*args)
1575 def write(self, *args):
1575 def write(self, *args):
1576 self.hunk[self.rev].append(args)
1576 self.hunk[self.rev].append(args)
1577 def debug(self, *args):
1577 def debug(self, *args):
1578 if self.debugflag:
1578 if self.debugflag:
1579 self.write(*args)
1579 self.write(*args)
1580 def __getattr__(self, key):
1580 def __getattr__(self, key):
1581 return getattr(self.ui, key)
1581 return getattr(self.ui, key)
1582 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1582 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1583 for st, rev, fns in changeiter:
1583 for st, rev, fns in changeiter:
1584 if st == 'window':
1584 if st == 'window':
1585 du = dui(ui)
1585 du = dui(ui)
1586 elif st == 'add':
1586 elif st == 'add':
1587 du.bump(rev)
1587 du.bump(rev)
1588 changenode = repo.changelog.node(rev)
1588 changenode = repo.changelog.node(rev)
1589 parents = [p for p in repo.changelog.parents(changenode)
1589 parents = [p for p in repo.changelog.parents(changenode)
1590 if p != nullid]
1590 if p != nullid]
1591 if opts['no_merges'] and len(parents) == 2:
1591 if opts['no_merges'] and len(parents) == 2:
1592 continue
1592 continue
1593 if opts['only_merges'] and len(parents) != 2:
1593 if opts['only_merges'] and len(parents) != 2:
1594 continue
1594 continue
1595
1595
1596 br = None
1596 br = None
1597 if opts['keyword']:
1597 if opts['keyword']:
1598 changes = getchange(rev)
1598 changes = getchange(rev)
1599 miss = 0
1599 miss = 0
1600 for k in [kw.lower() for kw in opts['keyword']]:
1600 for k in [kw.lower() for kw in opts['keyword']]:
1601 if not (k in changes[1].lower() or
1601 if not (k in changes[1].lower() or
1602 k in changes[4].lower() or
1602 k in changes[4].lower() or
1603 k in " ".join(changes[3][:20]).lower()):
1603 k in " ".join(changes[3][:20]).lower()):
1604 miss = 1
1604 miss = 1
1605 break
1605 break
1606 if miss:
1606 if miss:
1607 continue
1607 continue
1608
1608
1609 if opts['branch']:
1609 if opts['branch']:
1610 br = repo.branchlookup([repo.changelog.node(rev)])
1610 br = repo.branchlookup([repo.changelog.node(rev)])
1611
1611
1612 show_changeset(du, repo, rev, brinfo=br)
1612 show_changeset(du, repo, rev, brinfo=br)
1613 if opts['patch']:
1613 if opts['patch']:
1614 prev = (parents and parents[0]) or nullid
1614 prev = (parents and parents[0]) or nullid
1615 dodiff(du, du, repo, prev, changenode, match=matchfn)
1615 dodiff(du, du, repo, prev, changenode, match=matchfn)
1616 du.write("\n\n")
1616 du.write("\n\n")
1617 elif st == 'iter':
1617 elif st == 'iter':
1618 for args in du.hunk[rev]:
1618 for args in du.hunk[rev]:
1619 ui.write(*args)
1619 ui.write(*args)
1620
1620
1621 def manifest(ui, repo, rev=None):
1621 def manifest(ui, repo, rev=None):
1622 """output the latest or given revision of the project manifest
1622 """output the latest or given revision of the project manifest
1623
1623
1624 Print a list of version controlled files for the given revision.
1624 Print a list of version controlled files for the given revision.
1625
1625
1626 The manifest is the list of files being version controlled. If no revision
1626 The manifest is the list of files being version controlled. If no revision
1627 is given then the tip is used.
1627 is given then the tip is used.
1628 """
1628 """
1629 if rev:
1629 if rev:
1630 try:
1630 try:
1631 # assume all revision numbers are for changesets
1631 # assume all revision numbers are for changesets
1632 n = repo.lookup(rev)
1632 n = repo.lookup(rev)
1633 change = repo.changelog.read(n)
1633 change = repo.changelog.read(n)
1634 n = change[0]
1634 n = change[0]
1635 except hg.RepoError:
1635 except hg.RepoError:
1636 n = repo.manifest.lookup(rev)
1636 n = repo.manifest.lookup(rev)
1637 else:
1637 else:
1638 n = repo.manifest.tip()
1638 n = repo.manifest.tip()
1639 m = repo.manifest.read(n)
1639 m = repo.manifest.read(n)
1640 mf = repo.manifest.readflags(n)
1640 mf = repo.manifest.readflags(n)
1641 files = m.keys()
1641 files = m.keys()
1642 files.sort()
1642 files.sort()
1643
1643
1644 for f in files:
1644 for f in files:
1645 ui.write("%40s %3s %s\n" % (hex(m[f]), mf[f] and "755" or "644", f))
1645 ui.write("%40s %3s %s\n" % (hex(m[f]), mf[f] and "755" or "644", f))
1646
1646
1647 def outgoing(ui, repo, dest="default-push", **opts):
1647 def outgoing(ui, repo, dest="default-push", **opts):
1648 """show changesets not found in destination
1648 """show changesets not found in destination
1649
1649
1650 Show changesets not found in the specified destination repo or the
1650 Show changesets not found in the specified destination repo or the
1651 default push repo. These are the changesets that would be pushed
1651 default push repo. These are the changesets that would be pushed
1652 if a push was requested.
1652 if a push was requested.
1653 """
1653 """
1654 dest = ui.expandpath(dest, repo.root)
1654 dest = ui.expandpath(dest, repo.root)
1655 other = hg.repository(ui, dest)
1655 other = hg.repository(ui, dest)
1656 o = repo.findoutgoing(other)
1656 o = repo.findoutgoing(other)
1657 o = repo.changelog.nodesbetween(o)[0]
1657 o = repo.changelog.nodesbetween(o)[0]
1658 if opts['newest_first']:
1658 if opts['newest_first']:
1659 o.reverse()
1659 o.reverse()
1660 for n in o:
1660 for n in o:
1661 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1661 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1662 if opts['no_merges'] and len(parents) == 2:
1662 if opts['no_merges'] and len(parents) == 2:
1663 continue
1663 continue
1664 show_changeset(ui, repo, changenode=n)
1664 show_changeset(ui, repo, changenode=n)
1665 if opts['patch']:
1665 if opts['patch']:
1666 prev = (parents and parents[0]) or nullid
1666 prev = (parents and parents[0]) or nullid
1667 dodiff(ui, ui, repo, prev, n)
1667 dodiff(ui, ui, repo, prev, n)
1668 ui.write("\n")
1668 ui.write("\n")
1669
1669
1670 def parents(ui, repo, rev=None):
1670 def parents(ui, repo, rev=None):
1671 """show the parents of the working dir or revision
1671 """show the parents of the working dir or revision
1672
1672
1673 Print the working directory's parent revisions.
1673 Print the working directory's parent revisions.
1674 """
1674 """
1675 if rev:
1675 if rev:
1676 p = repo.changelog.parents(repo.lookup(rev))
1676 p = repo.changelog.parents(repo.lookup(rev))
1677 else:
1677 else:
1678 p = repo.dirstate.parents()
1678 p = repo.dirstate.parents()
1679
1679
1680 for n in p:
1680 for n in p:
1681 if n != nullid:
1681 if n != nullid:
1682 show_changeset(ui, repo, changenode=n)
1682 show_changeset(ui, repo, changenode=n)
1683
1683
1684 def paths(ui, search=None):
1684 def paths(ui, search=None):
1685 """show definition of symbolic path names
1685 """show definition of symbolic path names
1686
1686
1687 Show definition of symbolic path name NAME. If no name is given, show
1687 Show definition of symbolic path name NAME. If no name is given, show
1688 definition of available names.
1688 definition of available names.
1689
1689
1690 Path names are defined in the [paths] section of /etc/mercurial/hgrc
1690 Path names are defined in the [paths] section of /etc/mercurial/hgrc
1691 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
1691 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
1692 """
1692 """
1693 try:
1693 try:
1694 repo = hg.repository(ui=ui)
1694 repo = hg.repository(ui=ui)
1695 except hg.RepoError:
1695 except hg.RepoError:
1696 pass
1696 pass
1697
1697
1698 if search:
1698 if search:
1699 for name, path in ui.configitems("paths"):
1699 for name, path in ui.configitems("paths"):
1700 if name == search:
1700 if name == search:
1701 ui.write("%s\n" % path)
1701 ui.write("%s\n" % path)
1702 return
1702 return
1703 ui.warn(_("not found!\n"))
1703 ui.warn(_("not found!\n"))
1704 return 1
1704 return 1
1705 else:
1705 else:
1706 for name, path in ui.configitems("paths"):
1706 for name, path in ui.configitems("paths"):
1707 ui.write("%s = %s\n" % (name, path))
1707 ui.write("%s = %s\n" % (name, path))
1708
1708
1709 def pull(ui, repo, source="default", **opts):
1709 def pull(ui, repo, source="default", **opts):
1710 """pull changes from the specified source
1710 """pull changes from the specified source
1711
1711
1712 Pull changes from a remote repository to a local one.
1712 Pull changes from a remote repository to a local one.
1713
1713
1714 This finds all changes from the repository at the specified path
1714 This finds all changes from the repository at the specified path
1715 or URL and adds them to the local repository. By default, this
1715 or URL and adds them to the local repository. By default, this
1716 does not update the copy of the project in the working directory.
1716 does not update the copy of the project in the working directory.
1717
1717
1718 Valid URLs are of the form:
1718 Valid URLs are of the form:
1719
1719
1720 local/filesystem/path
1720 local/filesystem/path
1721 http://[user@]host[:port][/path]
1721 http://[user@]host[:port][/path]
1722 https://[user@]host[:port][/path]
1722 https://[user@]host[:port][/path]
1723 ssh://[user@]host[:port][/path]
1723 ssh://[user@]host[:port][/path]
1724
1724
1725 SSH requires an accessible shell account on the destination machine
1725 SSH requires an accessible shell account on the destination machine
1726 and a copy of hg in the remote path. With SSH, paths are relative
1726 and a copy of hg in the remote path. With SSH, paths are relative
1727 to the remote user's home directory by default; use two slashes at
1727 to the remote user's home directory by default; use two slashes at
1728 the start of a path to specify it as relative to the filesystem root.
1728 the start of a path to specify it as relative to the filesystem root.
1729 """
1729 """
1730 source = ui.expandpath(source, repo.root)
1730 source = ui.expandpath(source, repo.root)
1731 ui.status(_('pulling from %s\n') % (source))
1731 ui.status(_('pulling from %s\n') % (source))
1732
1732
1733 if opts['ssh']:
1733 if opts['ssh']:
1734 ui.setconfig("ui", "ssh", opts['ssh'])
1734 ui.setconfig("ui", "ssh", opts['ssh'])
1735 if opts['remotecmd']:
1735 if opts['remotecmd']:
1736 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1736 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1737
1737
1738 other = hg.repository(ui, source)
1738 other = hg.repository(ui, source)
1739 revs = None
1739 revs = None
1740 if opts['rev'] and not other.local():
1740 if opts['rev'] and not other.local():
1741 raise util.Abort(_("pull -r doesn't work for remote repositories yet"))
1741 raise util.Abort(_("pull -r doesn't work for remote repositories yet"))
1742 elif opts['rev']:
1742 elif opts['rev']:
1743 revs = [other.lookup(rev) for rev in opts['rev']]
1743 revs = [other.lookup(rev) for rev in opts['rev']]
1744 r = repo.pull(other, heads=revs)
1744 r = repo.pull(other, heads=revs)
1745 if not r:
1745 if not r:
1746 if opts['update']:
1746 if opts['update']:
1747 return update(ui, repo)
1747 return update(ui, repo)
1748 else:
1748 else:
1749 ui.status(_("(run 'hg update' to get a working copy)\n"))
1749 ui.status(_("(run 'hg update' to get a working copy)\n"))
1750
1750
1751 return r
1751 return r
1752
1752
1753 def push(ui, repo, dest="default-push", force=False, ssh=None, remotecmd=None):
1753 def push(ui, repo, dest="default-push", force=False, ssh=None, remotecmd=None):
1754 """push changes to the specified destination
1754 """push changes to the specified destination
1755
1755
1756 Push changes from the local repository to the given destination.
1756 Push changes from the local repository to the given destination.
1757
1757
1758 This is the symmetrical operation for pull. It helps to move
1758 This is the symmetrical operation for pull. It helps to move
1759 changes from the current repository to a different one. If the
1759 changes from the current repository to a different one. If the
1760 destination is local this is identical to a pull in that directory
1760 destination is local this is identical to a pull in that directory
1761 from the current one.
1761 from the current one.
1762
1762
1763 By default, push will refuse to run if it detects the result would
1763 By default, push will refuse to run if it detects the result would
1764 increase the number of remote heads. This generally indicates the
1764 increase the number of remote heads. This generally indicates the
1765 the client has forgotten to sync and merge before pushing.
1765 the client has forgotten to sync and merge before pushing.
1766
1766
1767 Valid URLs are of the form:
1767 Valid URLs are of the form:
1768
1768
1769 local/filesystem/path
1769 local/filesystem/path
1770 ssh://[user@]host[:port][/path]
1770 ssh://[user@]host[:port][/path]
1771
1771
1772 SSH requires an accessible shell account on the destination
1772 SSH requires an accessible shell account on the destination
1773 machine and a copy of hg in the remote path.
1773 machine and a copy of hg in the remote path.
1774 """
1774 """
1775 dest = ui.expandpath(dest, repo.root)
1775 dest = ui.expandpath(dest, repo.root)
1776 ui.status('pushing to %s\n' % (dest))
1776 ui.status('pushing to %s\n' % (dest))
1777
1777
1778 if ssh:
1778 if ssh:
1779 ui.setconfig("ui", "ssh", ssh)
1779 ui.setconfig("ui", "ssh", ssh)
1780 if remotecmd:
1780 if remotecmd:
1781 ui.setconfig("ui", "remotecmd", remotecmd)
1781 ui.setconfig("ui", "remotecmd", remotecmd)
1782
1782
1783 other = hg.repository(ui, dest)
1783 other = hg.repository(ui, dest)
1784 r = repo.push(other, force)
1784 r = repo.push(other, force)
1785 return r
1785 return r
1786
1786
1787 def rawcommit(ui, repo, *flist, **rc):
1787 def rawcommit(ui, repo, *flist, **rc):
1788 """raw commit interface
1788 """raw commit interface
1789
1789
1790 Lowlevel commit, for use in helper scripts.
1790 Lowlevel commit, for use in helper scripts.
1791
1791
1792 This command is not intended to be used by normal users, as it is
1792 This command is not intended to be used by normal users, as it is
1793 primarily useful for importing from other SCMs.
1793 primarily useful for importing from other SCMs.
1794 """
1794 """
1795 message = rc['message']
1795 message = rc['message']
1796 if not message and rc['logfile']:
1796 if not message and rc['logfile']:
1797 try:
1797 try:
1798 message = open(rc['logfile']).read()
1798 message = open(rc['logfile']).read()
1799 except IOError:
1799 except IOError:
1800 pass
1800 pass
1801 if not message and not rc['logfile']:
1801 if not message and not rc['logfile']:
1802 raise util.Abort(_("missing commit message"))
1802 raise util.Abort(_("missing commit message"))
1803
1803
1804 files = relpath(repo, list(flist))
1804 files = relpath(repo, list(flist))
1805 if rc['files']:
1805 if rc['files']:
1806 files += open(rc['files']).read().splitlines()
1806 files += open(rc['files']).read().splitlines()
1807
1807
1808 rc['parent'] = map(repo.lookup, rc['parent'])
1808 rc['parent'] = map(repo.lookup, rc['parent'])
1809
1809
1810 try:
1810 try:
1811 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
1811 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
1812 except ValueError, inst:
1812 except ValueError, inst:
1813 raise util.Abort(str(inst))
1813 raise util.Abort(str(inst))
1814
1814
1815 def recover(ui, repo):
1815 def recover(ui, repo):
1816 """roll back an interrupted transaction
1816 """roll back an interrupted transaction
1817
1817
1818 Recover from an interrupted commit or pull.
1818 Recover from an interrupted commit or pull.
1819
1819
1820 This command tries to fix the repository status after an interrupted
1820 This command tries to fix the repository status after an interrupted
1821 operation. It should only be necessary when Mercurial suggests it.
1821 operation. It should only be necessary when Mercurial suggests it.
1822 """
1822 """
1823 if repo.recover():
1823 if repo.recover():
1824 return repo.verify()
1824 return repo.verify()
1825 return False
1825 return False
1826
1826
1827 def remove(ui, repo, pat, *pats, **opts):
1827 def remove(ui, repo, pat, *pats, **opts):
1828 """remove the specified files on the next commit
1828 """remove the specified files on the next commit
1829
1829
1830 Schedule the indicated files for removal from the repository.
1830 Schedule the indicated files for removal from the repository.
1831
1831
1832 This command schedules the files to be removed at the next commit.
1832 This command schedules the files to be removed at the next commit.
1833 This only removes files from the current branch, not from the
1833 This only removes files from the current branch, not from the
1834 entire project history. If the files still exist in the working
1834 entire project history. If the files still exist in the working
1835 directory, they will be deleted from it.
1835 directory, they will be deleted from it.
1836 """
1836 """
1837 names = []
1837 names = []
1838 def okaytoremove(abs, rel, exact):
1838 def okaytoremove(abs, rel, exact):
1839 modified, added, removed, deleted, unknown = repo.changes(files=[abs])
1839 modified, added, removed, deleted, unknown = repo.changes(files=[abs])
1840 reason = None
1840 reason = None
1841 if modified:
1841 if modified:
1842 reason = _('is modified')
1842 reason = _('is modified')
1843 elif added:
1843 elif added:
1844 reason = _('has been marked for add')
1844 reason = _('has been marked for add')
1845 elif unknown:
1845 elif unknown:
1846 reason = _('is not managed')
1846 reason = _('is not managed')
1847 if reason:
1847 if reason:
1848 if exact:
1848 if exact:
1849 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
1849 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
1850 else:
1850 else:
1851 return True
1851 return True
1852 for src, abs, rel, exact in walk(repo, (pat,) + pats, opts):
1852 for src, abs, rel, exact in walk(repo, (pat,) + pats, opts):
1853 if okaytoremove(abs, rel, exact):
1853 if okaytoremove(abs, rel, exact):
1854 if ui.verbose or not exact:
1854 if ui.verbose or not exact:
1855 ui.status(_('removing %s\n') % rel)
1855 ui.status(_('removing %s\n') % rel)
1856 names.append(abs)
1856 names.append(abs)
1857 repo.remove(names, unlink=True)
1857 repo.remove(names, unlink=True)
1858
1858
1859 def rename(ui, repo, *pats, **opts):
1859 def rename(ui, repo, *pats, **opts):
1860 """rename files; equivalent of copy + remove
1860 """rename files; equivalent of copy + remove
1861
1861
1862 Mark dest as copies of sources; mark sources for deletion. If
1862 Mark dest as copies of sources; mark sources for deletion. If
1863 dest is a directory, copies are put in that directory. If dest is
1863 dest is a directory, copies are put in that directory. If dest is
1864 a file, there can only be one source.
1864 a file, there can only be one source.
1865
1865
1866 By default, this command copies the contents of files as they
1866 By default, this command copies the contents of files as they
1867 stand in the working directory. If invoked with --after, the
1867 stand in the working directory. If invoked with --after, the
1868 operation is recorded, but no copying is performed.
1868 operation is recorded, but no copying is performed.
1869
1869
1870 This command takes effect in the next commit.
1870 This command takes effect in the next commit.
1871
1871
1872 NOTE: This command should be treated as experimental. While it
1872 NOTE: This command should be treated as experimental. While it
1873 should properly record rename files, this information is not yet
1873 should properly record rename files, this information is not yet
1874 fully used by merge, nor fully reported by log.
1874 fully used by merge, nor fully reported by log.
1875 """
1875 """
1876 errs, copied = docopy(ui, repo, pats, opts)
1876 errs, copied = docopy(ui, repo, pats, opts)
1877 names = []
1877 names = []
1878 for abs, rel, exact in copied:
1878 for abs, rel, exact in copied:
1879 if ui.verbose or not exact:
1879 if ui.verbose or not exact:
1880 ui.status(_('removing %s\n') % rel)
1880 ui.status(_('removing %s\n') % rel)
1881 names.append(abs)
1881 names.append(abs)
1882 repo.remove(names, unlink=True)
1882 repo.remove(names, unlink=True)
1883 return errs
1883 return errs
1884
1884
1885 def revert(ui, repo, *pats, **opts):
1885 def revert(ui, repo, *pats, **opts):
1886 """revert modified files or dirs back to their unmodified states
1886 """revert modified files or dirs back to their unmodified states
1887
1887
1888 Revert any uncommitted modifications made to the named files or
1888 Revert any uncommitted modifications made to the named files or
1889 directories. This restores the contents of the affected files to
1889 directories. This restores the contents of the affected files to
1890 an unmodified state.
1890 an unmodified state.
1891
1891
1892 If a file has been deleted, it is recreated. If the executable
1892 If a file has been deleted, it is recreated. If the executable
1893 mode of a file was changed, it is reset.
1893 mode of a file was changed, it is reset.
1894
1894
1895 If names are given, all files matching the names are reverted.
1895 If names are given, all files matching the names are reverted.
1896
1896
1897 If no arguments are given, all files in the repository are reverted.
1897 If no arguments are given, all files in the repository are reverted.
1898 """
1898 """
1899 node = opts['rev'] and repo.lookup(opts['rev']) or \
1899 node = opts['rev'] and repo.lookup(opts['rev']) or \
1900 repo.dirstate.parents()[0]
1900 repo.dirstate.parents()[0]
1901
1901
1902 files, choose, anypats = matchpats(repo, pats, opts)
1902 files, choose, anypats = matchpats(repo, pats, opts)
1903 modified, added, removed, deleted, unknown = repo.changes(match=choose)
1903 modified, added, removed, deleted, unknown = repo.changes(match=choose)
1904 repo.forget(added)
1904 repo.forget(added)
1905 repo.undelete(removed + deleted)
1905 repo.undelete(removed + deleted)
1906
1906
1907 return repo.update(node, False, True, choose, False)
1907 return repo.update(node, False, True, choose, False)
1908
1908
1909 def root(ui, repo):
1909 def root(ui, repo):
1910 """print the root (top) of the current working dir
1910 """print the root (top) of the current working dir
1911
1911
1912 Print the root directory of the current repository.
1912 Print the root directory of the current repository.
1913 """
1913 """
1914 ui.write(repo.root + "\n")
1914 ui.write(repo.root + "\n")
1915
1915
1916 def serve(ui, repo, **opts):
1916 def serve(ui, repo, **opts):
1917 """export the repository via HTTP
1917 """export the repository via HTTP
1918
1918
1919 Start a local HTTP repository browser and pull server.
1919 Start a local HTTP repository browser and pull server.
1920
1920
1921 By default, the server logs accesses to stdout and errors to
1921 By default, the server logs accesses to stdout and errors to
1922 stderr. Use the "-A" and "-E" options to log to files.
1922 stderr. Use the "-A" and "-E" options to log to files.
1923 """
1923 """
1924
1924
1925 if opts["stdio"]:
1925 if opts["stdio"]:
1926 fin, fout = sys.stdin, sys.stdout
1926 fin, fout = sys.stdin, sys.stdout
1927 sys.stdout = sys.stderr
1927 sys.stdout = sys.stderr
1928
1928
1929 # Prevent insertion/deletion of CRs
1929 # Prevent insertion/deletion of CRs
1930 util.set_binary(fin)
1930 util.set_binary(fin)
1931 util.set_binary(fout)
1931 util.set_binary(fout)
1932
1932
1933 def getarg():
1933 def getarg():
1934 argline = fin.readline()[:-1]
1934 argline = fin.readline()[:-1]
1935 arg, l = argline.split()
1935 arg, l = argline.split()
1936 val = fin.read(int(l))
1936 val = fin.read(int(l))
1937 return arg, val
1937 return arg, val
1938 def respond(v):
1938 def respond(v):
1939 fout.write("%d\n" % len(v))
1939 fout.write("%d\n" % len(v))
1940 fout.write(v)
1940 fout.write(v)
1941 fout.flush()
1941 fout.flush()
1942
1942
1943 lock = None
1943 lock = None
1944
1944
1945 while 1:
1945 while 1:
1946 cmd = fin.readline()[:-1]
1946 cmd = fin.readline()[:-1]
1947 if cmd == '':
1947 if cmd == '':
1948 return
1948 return
1949 if cmd == "heads":
1949 if cmd == "heads":
1950 h = repo.heads()
1950 h = repo.heads()
1951 respond(" ".join(map(hex, h)) + "\n")
1951 respond(" ".join(map(hex, h)) + "\n")
1952 if cmd == "lock":
1952 if cmd == "lock":
1953 lock = repo.lock()
1953 lock = repo.lock()
1954 respond("")
1954 respond("")
1955 if cmd == "unlock":
1955 if cmd == "unlock":
1956 if lock:
1956 if lock:
1957 lock.release()
1957 lock.release()
1958 lock = None
1958 lock = None
1959 respond("")
1959 respond("")
1960 elif cmd == "branches":
1960 elif cmd == "branches":
1961 arg, nodes = getarg()
1961 arg, nodes = getarg()
1962 nodes = map(bin, nodes.split(" "))
1962 nodes = map(bin, nodes.split(" "))
1963 r = []
1963 r = []
1964 for b in repo.branches(nodes):
1964 for b in repo.branches(nodes):
1965 r.append(" ".join(map(hex, b)) + "\n")
1965 r.append(" ".join(map(hex, b)) + "\n")
1966 respond("".join(r))
1966 respond("".join(r))
1967 elif cmd == "between":
1967 elif cmd == "between":
1968 arg, pairs = getarg()
1968 arg, pairs = getarg()
1969 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
1969 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
1970 r = []
1970 r = []
1971 for b in repo.between(pairs):
1971 for b in repo.between(pairs):
1972 r.append(" ".join(map(hex, b)) + "\n")
1972 r.append(" ".join(map(hex, b)) + "\n")
1973 respond("".join(r))
1973 respond("".join(r))
1974 elif cmd == "changegroup":
1974 elif cmd == "changegroup":
1975 nodes = []
1975 nodes = []
1976 arg, roots = getarg()
1976 arg, roots = getarg()
1977 nodes = map(bin, roots.split(" "))
1977 nodes = map(bin, roots.split(" "))
1978
1978
1979 cg = repo.changegroup(nodes)
1979 cg = repo.changegroup(nodes)
1980 while 1:
1980 while 1:
1981 d = cg.read(4096)
1981 d = cg.read(4096)
1982 if not d:
1982 if not d:
1983 break
1983 break
1984 fout.write(d)
1984 fout.write(d)
1985
1985
1986 fout.flush()
1986 fout.flush()
1987
1987
1988 elif cmd == "addchangegroup":
1988 elif cmd == "addchangegroup":
1989 if not lock:
1989 if not lock:
1990 respond("not locked")
1990 respond("not locked")
1991 continue
1991 continue
1992 respond("")
1992 respond("")
1993
1993
1994 r = repo.addchangegroup(fin)
1994 r = repo.addchangegroup(fin)
1995 respond("")
1995 respond("")
1996
1996
1997 optlist = "name templates style address port ipv6 accesslog errorlog"
1997 optlist = "name templates style address port ipv6 accesslog errorlog"
1998 for o in optlist.split():
1998 for o in optlist.split():
1999 if opts[o]:
1999 if opts[o]:
2000 ui.setconfig("web", o, opts[o])
2000 ui.setconfig("web", o, opts[o])
2001
2001
2002 try:
2002 try:
2003 httpd = hgweb.create_server(repo)
2003 httpd = hgweb.create_server(repo)
2004 except socket.error, inst:
2004 except socket.error, inst:
2005 raise util.Abort(_('cannot start server: ') + inst.args[1])
2005 raise util.Abort(_('cannot start server: ') + inst.args[1])
2006
2006
2007 if ui.verbose:
2007 if ui.verbose:
2008 addr, port = httpd.socket.getsockname()
2008 addr, port = httpd.socket.getsockname()
2009 if addr == '0.0.0.0':
2009 if addr == '0.0.0.0':
2010 addr = socket.gethostname()
2010 addr = socket.gethostname()
2011 else:
2011 else:
2012 try:
2012 try:
2013 addr = socket.gethostbyaddr(addr)[0]
2013 addr = socket.gethostbyaddr(addr)[0]
2014 except socket.error:
2014 except socket.error:
2015 pass
2015 pass
2016 if port != 80:
2016 if port != 80:
2017 ui.status(_('listening at http://%s:%d/\n') % (addr, port))
2017 ui.status(_('listening at http://%s:%d/\n') % (addr, port))
2018 else:
2018 else:
2019 ui.status(_('listening at http://%s/\n') % addr)
2019 ui.status(_('listening at http://%s/\n') % addr)
2020 httpd.serve_forever()
2020 httpd.serve_forever()
2021
2021
2022 def status(ui, repo, *pats, **opts):
2022 def status(ui, repo, *pats, **opts):
2023 """show changed files in the working directory
2023 """show changed files in the working directory
2024
2024
2025 Show changed files in the repository. If names are
2025 Show changed files in the repository. If names are
2026 given, only files that match are shown.
2026 given, only files that match are shown.
2027
2027
2028 The codes used to show the status of files are:
2028 The codes used to show the status of files are:
2029 M = modified
2029 M = modified
2030 A = added
2030 A = added
2031 R = removed
2031 R = removed
2032 ! = deleted, but still tracked
2032 ! = deleted, but still tracked
2033 ? = not tracked
2033 ? = not tracked
2034 """
2034 """
2035
2035
2036 files, matchfn, anypats = matchpats(repo, pats, opts)
2036 files, matchfn, anypats = matchpats(repo, pats, opts)
2037 cwd = (pats and repo.getcwd()) or ''
2037 cwd = (pats and repo.getcwd()) or ''
2038 modified, added, removed, deleted, unknown = [
2038 modified, added, removed, deleted, unknown = [
2039 [util.pathto(cwd, x) for x in n]
2039 [util.pathto(cwd, x) for x in n]
2040 for n in repo.changes(files=files, match=matchfn)]
2040 for n in repo.changes(files=files, match=matchfn)]
2041
2041
2042 changetypes = [(_('modified'), 'M', modified),
2042 changetypes = [(_('modified'), 'M', modified),
2043 (_('added'), 'A', added),
2043 (_('added'), 'A', added),
2044 (_('removed'), 'R', removed),
2044 (_('removed'), 'R', removed),
2045 (_('deleted'), '!', deleted),
2045 (_('deleted'), '!', deleted),
2046 (_('unknown'), '?', unknown)]
2046 (_('unknown'), '?', unknown)]
2047
2047
2048 end = opts['print0'] and '\0' or '\n'
2048 end = opts['print0'] and '\0' or '\n'
2049
2049
2050 for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]]
2050 for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]]
2051 or changetypes):
2051 or changetypes):
2052 if opts['no_status']:
2052 if opts['no_status']:
2053 format = "%%s%s" % end
2053 format = "%%s%s" % end
2054 else:
2054 else:
2055 format = "%s %%s%s" % (char, end);
2055 format = "%s %%s%s" % (char, end);
2056
2056
2057 for f in changes:
2057 for f in changes:
2058 ui.write(format % f)
2058 ui.write(format % f)
2059
2059
2060 def tag(ui, repo, name, rev_=None, **opts):
2060 def tag(ui, repo, name, rev_=None, **opts):
2061 """add a tag for the current tip or a given revision
2061 """add a tag for the current tip or a given revision
2062
2062
2063 Name a particular revision using <name>.
2063 Name a particular revision using <name>.
2064
2064
2065 Tags are used to name particular revisions of the repository and are
2065 Tags are used to name particular revisions of the repository and are
2066 very useful to compare different revision, to go back to significant
2066 very useful to compare different revision, to go back to significant
2067 earlier versions or to mark branch points as releases, etc.
2067 earlier versions or to mark branch points as releases, etc.
2068
2068
2069 If no revision is given, the tip is used.
2069 If no revision is given, the tip is used.
2070
2070
2071 To facilitate version control, distribution, and merging of tags,
2071 To facilitate version control, distribution, and merging of tags,
2072 they are stored as a file named ".hgtags" which is managed
2072 they are stored as a file named ".hgtags" which is managed
2073 similarly to other project files and can be hand-edited if
2073 similarly to other project files and can be hand-edited if
2074 necessary.
2074 necessary.
2075 """
2075 """
2076 if name == "tip":
2076 if name == "tip":
2077 raise util.Abort(_("the name 'tip' is reserved"))
2077 raise util.Abort(_("the name 'tip' is reserved"))
2078 if opts['rev']:
2078 if opts['rev']:
2079 rev_ = opts['rev']
2079 rev_ = opts['rev']
2080 if rev_:
2080 if rev_:
2081 r = hex(repo.lookup(rev_))
2081 r = hex(repo.lookup(rev_))
2082 else:
2082 else:
2083 r = hex(repo.changelog.tip())
2083 r = hex(repo.changelog.tip())
2084
2084
2085 disallowed = (revrangesep, '\r', '\n')
2085 disallowed = (revrangesep, '\r', '\n')
2086 for c in disallowed:
2086 for c in disallowed:
2087 if name.find(c) >= 0:
2087 if name.find(c) >= 0:
2088 raise util.Abort(_("%s cannot be used in a tag name") % repr(c))
2088 raise util.Abort(_("%s cannot be used in a tag name") % repr(c))
2089
2089
2090 if opts['local']:
2090 if opts['local']:
2091 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
2091 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
2092 return
2092 return
2093
2093
2094 for x in repo.changes():
2094 for x in repo.changes():
2095 if ".hgtags" in x:
2095 if ".hgtags" in x:
2096 raise util.Abort(_("working copy of .hgtags is changed "
2096 raise util.Abort(_("working copy of .hgtags is changed "
2097 "(please commit .hgtags manually)"))
2097 "(please commit .hgtags manually)"))
2098
2098
2099 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
2099 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
2100 if repo.dirstate.state(".hgtags") == '?':
2100 if repo.dirstate.state(".hgtags") == '?':
2101 repo.add([".hgtags"])
2101 repo.add([".hgtags"])
2102
2102
2103 message = (opts['message'] or
2103 message = (opts['message'] or
2104 _("Added tag %s for changeset %s") % (name, r))
2104 _("Added tag %s for changeset %s") % (name, r))
2105 try:
2105 try:
2106 repo.commit([".hgtags"], message, opts['user'], opts['date'])
2106 repo.commit([".hgtags"], message, opts['user'], opts['date'])
2107 except ValueError, inst:
2107 except ValueError, inst:
2108 raise util.Abort(str(inst))
2108 raise util.Abort(str(inst))
2109
2109
2110 def tags(ui, repo):
2110 def tags(ui, repo):
2111 """list repository tags
2111 """list repository tags
2112
2112
2113 List the repository tags.
2113 List the repository tags.
2114
2114
2115 This lists both regular and local tags.
2115 This lists both regular and local tags.
2116 """
2116 """
2117
2117
2118 l = repo.tagslist()
2118 l = repo.tagslist()
2119 l.reverse()
2119 l.reverse()
2120 for t, n in l:
2120 for t, n in l:
2121 try:
2121 try:
2122 r = "%5d:%s" % (repo.changelog.rev(n), hex(n))
2122 r = "%5d:%s" % (repo.changelog.rev(n), hex(n))
2123 except KeyError:
2123 except KeyError:
2124 r = " ?:?"
2124 r = " ?:?"
2125 ui.write("%-30s %s\n" % (t, r))
2125 ui.write("%-30s %s\n" % (t, r))
2126
2126
2127 def tip(ui, repo):
2127 def tip(ui, repo):
2128 """show the tip revision
2128 """show the tip revision
2129
2129
2130 Show the tip revision.
2130 Show the tip revision.
2131 """
2131 """
2132 n = repo.changelog.tip()
2132 n = repo.changelog.tip()
2133 show_changeset(ui, repo, changenode=n)
2133 show_changeset(ui, repo, changenode=n)
2134
2134
2135 def unbundle(ui, repo, fname, **opts):
2135 def unbundle(ui, repo, fname, **opts):
2136 """apply a changegroup file
2136 """apply a changegroup file
2137
2137
2138 Apply a compressed changegroup file generated by the bundle
2138 Apply a compressed changegroup file generated by the bundle
2139 command.
2139 command.
2140 """
2140 """
2141 f = urllib.urlopen(fname)
2141 f = urllib.urlopen(fname)
2142
2142
2143 if f.read(4) != "HG10":
2143 if f.read(4) != "HG10":
2144 raise util.Abort(_("%s: not a Mercurial bundle file") % fname)
2144 raise util.Abort(_("%s: not a Mercurial bundle file") % fname)
2145
2145
2146 def bzgenerator(f):
2146 def bzgenerator(f):
2147 zd = bz2.BZ2Decompressor()
2147 zd = bz2.BZ2Decompressor()
2148 for chunk in f:
2148 for chunk in f:
2149 yield zd.decompress(chunk)
2149 yield zd.decompress(chunk)
2150
2150
2151 bzgen = bzgenerator(util.filechunkiter(f, 4096))
2151 bzgen = bzgenerator(util.filechunkiter(f, 4096))
2152 if repo.addchangegroup(util.chunkbuffer(bzgen)):
2152 if repo.addchangegroup(util.chunkbuffer(bzgen)):
2153 return 1
2153 return 1
2154
2154
2155 if opts['update']:
2155 if opts['update']:
2156 return update(ui, repo)
2156 return update(ui, repo)
2157 else:
2157 else:
2158 ui.status(_("(run 'hg update' to get a working copy)\n"))
2158 ui.status(_("(run 'hg update' to get a working copy)\n"))
2159
2159
2160 def undo(ui, repo):
2160 def undo(ui, repo):
2161 """undo the last commit or pull
2161 """undo the last commit or pull
2162
2162
2163 Roll back the last pull or commit transaction on the
2163 Roll back the last pull or commit transaction on the
2164 repository, restoring the project to its earlier state.
2164 repository, restoring the project to its earlier state.
2165
2165
2166 This command should be used with care. There is only one level of
2166 This command should be used with care. There is only one level of
2167 undo and there is no redo.
2167 undo and there is no redo.
2168
2168
2169 This command is not intended for use on public repositories. Once
2169 This command is not intended for use on public repositories. Once
2170 a change is visible for pull by other users, undoing it locally is
2170 a change is visible for pull by other users, undoing it locally is
2171 ineffective.
2171 ineffective.
2172 """
2172 """
2173 repo.undo()
2173 repo.undo()
2174
2174
2175 def update(ui, repo, node=None, merge=False, clean=False, force=None,
2175 def update(ui, repo, node=None, merge=False, clean=False, force=None,
2176 branch=None):
2176 branch=None):
2177 """update or merge working directory
2177 """update or merge working directory
2178
2178
2179 Update the working directory to the specified revision.
2179 Update the working directory to the specified revision.
2180
2180
2181 If there are no outstanding changes in the working directory and
2181 If there are no outstanding changes in the working directory and
2182 there is a linear relationship between the current version and the
2182 there is a linear relationship between the current version and the
2183 requested version, the result is the requested version.
2183 requested version, the result is the requested version.
2184
2184
2185 Otherwise the result is a merge between the contents of the
2185 Otherwise the result is a merge between the contents of the
2186 current working directory and the requested version. Files that
2186 current working directory and the requested version. Files that
2187 changed between either parent are marked as changed for the next
2187 changed between either parent are marked as changed for the next
2188 commit and a commit must be performed before any further updates
2188 commit and a commit must be performed before any further updates
2189 are allowed.
2189 are allowed.
2190
2190
2191 By default, update will refuse to run if doing so would require
2191 By default, update will refuse to run if doing so would require
2192 merging or discarding local changes.
2192 merging or discarding local changes.
2193 """
2193 """
2194 if branch:
2194 if branch:
2195 br = repo.branchlookup(branch=branch)
2195 br = repo.branchlookup(branch=branch)
2196 found = []
2196 found = []
2197 for x in br:
2197 for x in br:
2198 if branch in br[x]:
2198 if branch in br[x]:
2199 found.append(x)
2199 found.append(x)
2200 if len(found) > 1:
2200 if len(found) > 1:
2201 ui.warn(_("Found multiple heads for %s\n") % branch)
2201 ui.warn(_("Found multiple heads for %s\n") % branch)
2202 for x in found:
2202 for x in found:
2203 show_changeset(ui, repo, changenode=x, brinfo=br)
2203 show_changeset(ui, repo, changenode=x, brinfo=br)
2204 return 1
2204 return 1
2205 if len(found) == 1:
2205 if len(found) == 1:
2206 node = found[0]
2206 node = found[0]
2207 ui.warn(_("Using head %s for branch %s\n") % (short(node), branch))
2207 ui.warn(_("Using head %s for branch %s\n") % (short(node), branch))
2208 else:
2208 else:
2209 ui.warn(_("branch %s not found\n") % (branch))
2209 ui.warn(_("branch %s not found\n") % (branch))
2210 return 1
2210 return 1
2211 else:
2211 else:
2212 node = node and repo.lookup(node) or repo.changelog.tip()
2212 node = node and repo.lookup(node) or repo.changelog.tip()
2213 return repo.update(node, allow=merge, force=clean, forcemerge=force)
2213 return repo.update(node, allow=merge, force=clean, forcemerge=force)
2214
2214
2215 def verify(ui, repo):
2215 def verify(ui, repo):
2216 """verify the integrity of the repository
2216 """verify the integrity of the repository
2217
2217
2218 Verify the integrity of the current repository.
2218 Verify the integrity of the current repository.
2219
2219
2220 This will perform an extensive check of the repository's
2220 This will perform an extensive check of the repository's
2221 integrity, validating the hashes and checksums of each entry in
2221 integrity, validating the hashes and checksums of each entry in
2222 the changelog, manifest, and tracked files, as well as the
2222 the changelog, manifest, and tracked files, as well as the
2223 integrity of their crosslinks and indices.
2223 integrity of their crosslinks and indices.
2224 """
2224 """
2225 return repo.verify()
2225 return repo.verify()
2226
2226
2227 # Command options and aliases are listed here, alphabetically
2227 # Command options and aliases are listed here, alphabetically
2228
2228
2229 table = {
2229 table = {
2230 "^add":
2230 "^add":
2231 (add,
2231 (add,
2232 [('I', 'include', [], _('include names matching the given patterns')),
2232 [('I', 'include', [], _('include names matching the given patterns')),
2233 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2233 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2234 _('hg add [OPTION]... [FILE]...')),
2234 _('hg add [OPTION]... [FILE]...')),
2235 "addremove":
2235 "addremove":
2236 (addremove,
2236 (addremove,
2237 [('I', 'include', [], _('include names matching the given patterns')),
2237 [('I', 'include', [], _('include names matching the given patterns')),
2238 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2238 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2239 _('hg addremove [OPTION]... [FILE]...')),
2239 _('hg addremove [OPTION]... [FILE]...')),
2240 "^annotate":
2240 "^annotate":
2241 (annotate,
2241 (annotate,
2242 [('r', 'rev', '', _('annotate the specified revision')),
2242 [('r', 'rev', '', _('annotate the specified revision')),
2243 ('a', 'text', None, _('treat all files as text')),
2243 ('a', 'text', None, _('treat all files as text')),
2244 ('u', 'user', None, _('list the author')),
2244 ('u', 'user', None, _('list the author')),
2245 ('d', 'date', None, _('list the date')),
2245 ('d', 'date', None, _('list the date')),
2246 ('n', 'number', None, _('list the revision number (default)')),
2246 ('n', 'number', None, _('list the revision number (default)')),
2247 ('c', 'changeset', None, _('list the changeset')),
2247 ('c', 'changeset', None, _('list the changeset')),
2248 ('I', 'include', [], _('include names matching the given patterns')),
2248 ('I', 'include', [], _('include names matching the given patterns')),
2249 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2249 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2250 _('hg annotate [OPTION]... FILE...')),
2250 _('hg annotate [OPTION]... FILE...')),
2251 "bundle":
2251 "bundle":
2252 (bundle,
2252 (bundle,
2253 [],
2253 [],
2254 _('hg bundle FILE DEST')),
2254 _('hg bundle FILE DEST')),
2255 "cat":
2255 "cat":
2256 (cat,
2256 (cat,
2257 [('I', 'include', [], _('include names matching the given patterns')),
2257 [('I', 'include', [], _('include names matching the given patterns')),
2258 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2258 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2259 ('o', 'output', '', _('print output to file with formatted name')),
2259 ('o', 'output', '', _('print output to file with formatted name')),
2260 ('r', 'rev', '', _('print the given revision'))],
2260 ('r', 'rev', '', _('print the given revision'))],
2261 _('hg cat [OPTION]... FILE...')),
2261 _('hg cat [OPTION]... FILE...')),
2262 "^clone":
2262 "^clone":
2263 (clone,
2263 (clone,
2264 [('U', 'noupdate', None, _('do not update the new working directory')),
2264 [('U', 'noupdate', None, _('do not update the new working directory')),
2265 ('e', 'ssh', '', _('specify ssh command to use')),
2265 ('e', 'ssh', '', _('specify ssh command to use')),
2266 ('', 'pull', None, _('use pull protocol to copy metadata')),
2266 ('', 'pull', None, _('use pull protocol to copy metadata')),
2267 ('r', 'rev', [],
2267 ('r', 'rev', [],
2268 _('a changeset you would like to have after cloning')),
2268 _('a changeset you would like to have after cloning')),
2269 ('', 'remotecmd', '',
2269 ('', 'remotecmd', '',
2270 _('specify hg command to run on the remote side'))],
2270 _('specify hg command to run on the remote side'))],
2271 _('hg clone [OPTION]... SOURCE [DEST]')),
2271 _('hg clone [OPTION]... SOURCE [DEST]')),
2272 "^commit|ci":
2272 "^commit|ci":
2273 (commit,
2273 (commit,
2274 [('A', 'addremove', None, _('run addremove during commit')),
2274 [('A', 'addremove', None, _('run addremove during commit')),
2275 ('I', 'include', [], _('include names matching the given patterns')),
2275 ('I', 'include', [], _('include names matching the given patterns')),
2276 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2276 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2277 ('m', 'message', '', _('use <text> as commit message')),
2277 ('m', 'message', '', _('use <text> as commit message')),
2278 ('l', 'logfile', '', _('read the commit message from <file>')),
2278 ('l', 'logfile', '', _('read the commit message from <file>')),
2279 ('d', 'date', '', _('record datecode as commit date')),
2279 ('d', 'date', '', _('record datecode as commit date')),
2280 ('u', 'user', '', _('record user as commiter'))],
2280 ('u', 'user', '', _('record user as commiter'))],
2281 _('hg commit [OPTION]... [FILE]...')),
2281 _('hg commit [OPTION]... [FILE]...')),
2282 "copy|cp":
2282 "copy|cp":
2283 (copy,
2283 (copy,
2284 [('I', 'include', [], _('include names matching the given patterns')),
2284 [('I', 'include', [], _('include names matching the given patterns')),
2285 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2285 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2286 ('A', 'after', None, _('record a copy that has already occurred')),
2286 ('A', 'after', None, _('record a copy that has already occurred')),
2287 ('f', 'force', None,
2287 ('f', 'force', None,
2288 _('forcibly copy over an existing managed file'))],
2288 _('forcibly copy over an existing managed file'))],
2289 _('hg copy [OPTION]... [SOURCE]... DEST')),
2289 _('hg copy [OPTION]... [SOURCE]... DEST')),
2290 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2290 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2291 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2291 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2292 "debugconfig": (debugconfig, [], _('debugconfig')),
2292 "debugconfig": (debugconfig, [], _('debugconfig')),
2293 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2293 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2294 "debugstate": (debugstate, [], _('debugstate')),
2294 "debugstate": (debugstate, [], _('debugstate')),
2295 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2295 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2296 "debugindex": (debugindex, [], _('debugindex FILE')),
2296 "debugindex": (debugindex, [], _('debugindex FILE')),
2297 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2297 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2298 "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
2298 "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
2299 "debugwalk":
2299 "debugwalk":
2300 (debugwalk,
2300 (debugwalk,
2301 [('I', 'include', [], _('include names matching the given patterns')),
2301 [('I', 'include', [], _('include names matching the given patterns')),
2302 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2302 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2303 _('debugwalk [OPTION]... [FILE]...')),
2303 _('debugwalk [OPTION]... [FILE]...')),
2304 "^diff":
2304 "^diff":
2305 (diff,
2305 (diff,
2306 [('r', 'rev', [], _('revision')),
2306 [('r', 'rev', [], _('revision')),
2307 ('a', 'text', None, _('treat all files as text')),
2307 ('a', 'text', None, _('treat all files as text')),
2308 ('I', 'include', [], _('include names matching the given patterns')),
2308 ('I', 'include', [], _('include names matching the given patterns')),
2309 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2309 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2310 _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')),
2310 _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')),
2311 "^export":
2311 "^export":
2312 (export,
2312 (export,
2313 [('o', 'output', '', _('print output to file with formatted name')),
2313 [('o', 'output', '', _('print output to file with formatted name')),
2314 ('a', 'text', None, _('treat all files as text')),
2314 ('a', 'text', None, _('treat all files as text')),
2315 ('', 'switch-parent', None, _('diff against the second parent'))],
2315 ('', 'switch-parent', None, _('diff against the second parent'))],
2316 _('hg export [-a] [-o OUTFILE] REV...')),
2316 _('hg export [-a] [-o OUTFILE] REV...')),
2317 "forget":
2317 "forget":
2318 (forget,
2318 (forget,
2319 [('I', 'include', [], _('include names matching the given patterns')),
2319 [('I', 'include', [], _('include names matching the given patterns')),
2320 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2320 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2321 _('hg forget [OPTION]... FILE...')),
2321 _('hg forget [OPTION]... FILE...')),
2322 "grep":
2322 "grep":
2323 (grep,
2323 (grep,
2324 [('0', 'print0', None, _('end fields with NUL')),
2324 [('0', 'print0', None, _('end fields with NUL')),
2325 ('I', 'include', [], _('include names matching the given patterns')),
2325 ('I', 'include', [], _('include names matching the given patterns')),
2326 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2326 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2327 ('', 'all', None, _('print all revisions that match')),
2327 ('', 'all', None, _('print all revisions that match')),
2328 ('i', 'ignore-case', None, _('ignore case when matching')),
2328 ('i', 'ignore-case', None, _('ignore case when matching')),
2329 ('l', 'files-with-matches', None,
2329 ('l', 'files-with-matches', None,
2330 _('print only filenames and revs that match')),
2330 _('print only filenames and revs that match')),
2331 ('n', 'line-number', None, _('print matching line numbers')),
2331 ('n', 'line-number', None, _('print matching line numbers')),
2332 ('r', 'rev', [], _('search in given revision range')),
2332 ('r', 'rev', [], _('search in given revision range')),
2333 ('u', 'user', None, _('print user who committed change'))],
2333 ('u', 'user', None, _('print user who committed change'))],
2334 _('hg grep [OPTION]... PATTERN [FILE]...')),
2334 _('hg grep [OPTION]... PATTERN [FILE]...')),
2335 "heads":
2335 "heads":
2336 (heads,
2336 (heads,
2337 [('b', 'branches', None, _('find branch info')),
2337 [('b', 'branches', None, _('find branch info')),
2338 ('r', 'rev', '', _('show only heads which are descendants of rev'))],
2338 ('r', 'rev', '', _('show only heads which are descendants of rev'))],
2339 _('hg heads [-b] [-r <rev>]')),
2339 _('hg heads [-b] [-r <rev>]')),
2340 "help": (help_, [], _('hg help [COMMAND]')),
2340 "help": (help_, [], _('hg help [COMMAND]')),
2341 "identify|id": (identify, [], _('hg identify')),
2341 "identify|id": (identify, [], _('hg identify')),
2342 "import|patch":
2342 "import|patch":
2343 (import_,
2343 (import_,
2344 [('p', 'strip', 1,
2344 [('p', 'strip', 1,
2345 _('directory strip option for patch. This has the same\n') +
2345 _('directory strip option for patch. This has the same\n') +
2346 _('meaning as the corresponding patch option')),
2346 _('meaning as the corresponding patch option')),
2347 ('f', 'force', None,
2347 ('f', 'force', None,
2348 _('skip check for outstanding uncommitted changes')),
2348 _('skip check for outstanding uncommitted changes')),
2349 ('b', 'base', '', _('base path'))],
2349 ('b', 'base', '', _('base path'))],
2350 _('hg import [-f] [-p NUM] [-b BASE] PATCH...')),
2350 _('hg import [-f] [-p NUM] [-b BASE] PATCH...')),
2351 "incoming|in": (incoming,
2351 "incoming|in": (incoming,
2352 [('M', 'no-merges', None, _('do not show merges')),
2352 [('M', 'no-merges', None, _('do not show merges')),
2353 ('p', 'patch', None, _('show patch')),
2353 ('p', 'patch', None, _('show patch')),
2354 ('n', 'newest-first', None, _('show newest record first'))],
2354 ('n', 'newest-first', None, _('show newest record first'))],
2355 _('hg incoming [-p] [-n] [-M] [SOURCE]')),
2355 _('hg incoming [-p] [-n] [-M] [SOURCE]')),
2356 "^init": (init, [], _('hg init [DEST]')),
2356 "^init": (init, [], _('hg init [DEST]')),
2357 "locate":
2357 "locate":
2358 (locate,
2358 (locate,
2359 [('r', 'rev', '', _('search the repository as it stood at rev')),
2359 [('r', 'rev', '', _('search the repository as it stood at rev')),
2360 ('0', 'print0', None,
2360 ('0', 'print0', None,
2361 _('end filenames with NUL, for use with xargs')),
2361 _('end filenames with NUL, for use with xargs')),
2362 ('f', 'fullpath', None,
2362 ('f', 'fullpath', None,
2363 _('print complete paths from the filesystem root')),
2363 _('print complete paths from the filesystem root')),
2364 ('I', 'include', [], _('include names matching the given patterns')),
2364 ('I', 'include', [], _('include names matching the given patterns')),
2365 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2365 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2366 _('hg locate [OPTION]... [PATTERN]...')),
2366 _('hg locate [OPTION]... [PATTERN]...')),
2367 "^log|history":
2367 "^log|history":
2368 (log,
2368 (log,
2369 [('I', 'include', [], _('include names matching the given patterns')),
2369 [('I', 'include', [], _('include names matching the given patterns')),
2370 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2370 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2371 ('b', 'branch', None, _('show branches')),
2371 ('b', 'branch', None, _('show branches')),
2372 ('k', 'keyword', [], _('search for a keyword')),
2372 ('k', 'keyword', [], _('search for a keyword')),
2373 ('r', 'rev', [], _('show the specified revision or range')),
2373 ('r', 'rev', [], _('show the specified revision or range')),
2374 ('M', 'no-merges', None, _('do not show merges')),
2374 ('M', 'no-merges', None, _('do not show merges')),
2375 ('m', 'only-merges', None, _('show only merges')),
2375 ('m', 'only-merges', None, _('show only merges')),
2376 ('p', 'patch', None, _('show patch'))],
2376 ('p', 'patch', None, _('show patch'))],
2377 _('hg log [-I] [-X] [-r REV]... [-p] [FILE]')),
2377 _('hg log [-I] [-X] [-r REV]... [-p] [FILE]')),
2378 "manifest": (manifest, [], _('hg manifest [REV]')),
2378 "manifest": (manifest, [], _('hg manifest [REV]')),
2379 "outgoing|out": (outgoing,
2379 "outgoing|out": (outgoing,
2380 [('M', 'no-merges', None, _('do not show merges')),
2380 [('M', 'no-merges', None, _('do not show merges')),
2381 ('p', 'patch', None, _('show patch')),
2381 ('p', 'patch', None, _('show patch')),
2382 ('n', 'newest-first', None, _('show newest record first'))],
2382 ('n', 'newest-first', None, _('show newest record first'))],
2383 _('hg outgoing [-p] [-n] [-M] [DEST]')),
2383 _('hg outgoing [-p] [-n] [-M] [DEST]')),
2384 "^parents": (parents, [], _('hg parents [REV]')),
2384 "^parents": (parents, [], _('hg parents [REV]')),
2385 "paths": (paths, [], _('hg paths [NAME]')),
2385 "paths": (paths, [], _('hg paths [NAME]')),
2386 "^pull":
2386 "^pull":
2387 (pull,
2387 (pull,
2388 [('u', 'update', None,
2388 [('u', 'update', None,
2389 _('update the working directory to tip after pull')),
2389 _('update the working directory to tip after pull')),
2390 ('e', 'ssh', '', _('specify ssh command to use')),
2390 ('e', 'ssh', '', _('specify ssh command to use')),
2391 ('r', 'rev', [], _('a specific revision you would like to pull')),
2391 ('r', 'rev', [], _('a specific revision you would like to pull')),
2392 ('', 'remotecmd', '',
2392 ('', 'remotecmd', '',
2393 _('specify hg command to run on the remote side'))],
2393 _('specify hg command to run on the remote side'))],
2394 _('hg pull [-u] [-e FILE] [-r rev] [--remotecmd FILE] [SOURCE]')),
2394 _('hg pull [-u] [-e FILE] [-r rev] [--remotecmd FILE] [SOURCE]')),
2395 "^push":
2395 "^push":
2396 (push,
2396 (push,
2397 [('f', 'force', None, _('force push')),
2397 [('f', 'force', None, _('force push')),
2398 ('e', 'ssh', '', _('specify ssh command to use')),
2398 ('e', 'ssh', '', _('specify ssh command to use')),
2399 ('', 'remotecmd', '',
2399 ('', 'remotecmd', '',
2400 _('specify hg command to run on the remote side'))],
2400 _('specify hg command to run on the remote side'))],
2401 _('hg push [-f] [-e FILE] [--remotecmd FILE] [DEST]')),
2401 _('hg push [-f] [-e FILE] [--remotecmd FILE] [DEST]')),
2402 "rawcommit":
2402 "rawcommit":
2403 (rawcommit,
2403 (rawcommit,
2404 [('p', 'parent', [], _('parent')),
2404 [('p', 'parent', [], _('parent')),
2405 ('d', 'date', '', _('date code')),
2405 ('d', 'date', '', _('date code')),
2406 ('u', 'user', '', _('user')),
2406 ('u', 'user', '', _('user')),
2407 ('F', 'files', '', _('file list')),
2407 ('F', 'files', '', _('file list')),
2408 ('m', 'message', '', _('commit message')),
2408 ('m', 'message', '', _('commit message')),
2409 ('l', 'logfile', '', _('commit message file'))],
2409 ('l', 'logfile', '', _('commit message file'))],
2410 _('hg rawcommit [OPTION]... [FILE]...')),
2410 _('hg rawcommit [OPTION]... [FILE]...')),
2411 "recover": (recover, [], _('hg recover')),
2411 "recover": (recover, [], _('hg recover')),
2412 "^remove|rm":
2412 "^remove|rm":
2413 (remove,
2413 (remove,
2414 [('I', 'include', [], _('include names matching the given patterns')),
2414 [('I', 'include', [], _('include names matching the given patterns')),
2415 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2415 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2416 _('hg remove [OPTION]... FILE...')),
2416 _('hg remove [OPTION]... FILE...')),
2417 "rename|mv":
2417 "rename|mv":
2418 (rename,
2418 (rename,
2419 [('I', 'include', [], _('include names matching the given patterns')),
2419 [('I', 'include', [], _('include names matching the given patterns')),
2420 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2420 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2421 ('A', 'after', None, _('record a rename that has already occurred')),
2421 ('A', 'after', None, _('record a rename that has already occurred')),
2422 ('f', 'force', None,
2422 ('f', 'force', None,
2423 _('forcibly copy over an existing managed file'))],
2423 _('forcibly copy over an existing managed file'))],
2424 _('hg rename [OPTION]... [SOURCE]... DEST')),
2424 _('hg rename [OPTION]... [SOURCE]... DEST')),
2425 "^revert":
2425 "^revert":
2426 (revert,
2426 (revert,
2427 [('I', 'include', [], _('include names matching the given patterns')),
2427 [('I', 'include', [], _('include names matching the given patterns')),
2428 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2428 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2429 ('r', 'rev', '', _('revision to revert to'))],
2429 ('r', 'rev', '', _('revision to revert to'))],
2430 _('hg revert [-n] [-r REV] [NAME]...')),
2430 _('hg revert [-n] [-r REV] [NAME]...')),
2431 "root": (root, [], _('hg root')),
2431 "root": (root, [], _('hg root')),
2432 "^serve":
2432 "^serve":
2433 (serve,
2433 (serve,
2434 [('A', 'accesslog', '', _('name of access log file to write to')),
2434 [('A', 'accesslog', '', _('name of access log file to write to')),
2435 ('E', 'errorlog', '', _('name of error log file to write to')),
2435 ('E', 'errorlog', '', _('name of error log file to write to')),
2436 ('p', 'port', 0, _('port to use (default: 8000)')),
2436 ('p', 'port', 0, _('port to use (default: 8000)')),
2437 ('a', 'address', '', _('address to use')),
2437 ('a', 'address', '', _('address to use')),
2438 ('n', 'name', '',
2438 ('n', 'name', '',
2439 _('name to show in web pages (default: working dir)')),
2439 _('name to show in web pages (default: working dir)')),
2440 ('', 'stdio', None, _('for remote clients')),
2440 ('', 'stdio', None, _('for remote clients')),
2441 ('t', 'templates', '', _('web templates to use')),
2441 ('t', 'templates', '', _('web templates to use')),
2442 ('', 'style', '', _('template style to use')),
2442 ('', 'style', '', _('template style to use')),
2443 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
2443 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
2444 _('hg serve [OPTION]...')),
2444 _('hg serve [OPTION]...')),
2445 "^status|st":
2445 "^status|st":
2446 (status,
2446 (status,
2447 [('m', 'modified', None, _('show only modified files')),
2447 [('m', 'modified', None, _('show only modified files')),
2448 ('a', 'added', None, _('show only added files')),
2448 ('a', 'added', None, _('show only added files')),
2449 ('r', 'removed', None, _('show only removed files')),
2449 ('r', 'removed', None, _('show only removed files')),
2450 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
2450 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
2451 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
2451 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
2452 ('n', 'no-status', None, _('hide status prefix')),
2452 ('n', 'no-status', None, _('hide status prefix')),
2453 ('0', 'print0', None,
2453 ('0', 'print0', None,
2454 _('end filenames with NUL, for use with xargs')),
2454 _('end filenames with NUL, for use with xargs')),
2455 ('I', 'include', [], _('include names matching the given patterns')),
2455 ('I', 'include', [], _('include names matching the given patterns')),
2456 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2456 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2457 _('hg status [OPTION]... [FILE]...')),
2457 _('hg status [OPTION]... [FILE]...')),
2458 "tag":
2458 "tag":
2459 (tag,
2459 (tag,
2460 [('l', 'local', None, _('make the tag local')),
2460 [('l', 'local', None, _('make the tag local')),
2461 ('m', 'message', '', _('message for tag commit log entry')),
2461 ('m', 'message', '', _('message for tag commit log entry')),
2462 ('d', 'date', '', _('record datecode as commit date')),
2462 ('d', 'date', '', _('record datecode as commit date')),
2463 ('u', 'user', '', _('record user as commiter')),
2463 ('u', 'user', '', _('record user as commiter')),
2464 ('r', 'rev', '', _('revision to tag'))],
2464 ('r', 'rev', '', _('revision to tag'))],
2465 _('hg tag [OPTION]... NAME [REV]')),
2465 _('hg tag [OPTION]... NAME [REV]')),
2466 "tags": (tags, [], _('hg tags')),
2466 "tags": (tags, [], _('hg tags')),
2467 "tip": (tip, [], _('hg tip')),
2467 "tip": (tip, [], _('hg tip')),
2468 "unbundle":
2468 "unbundle":
2469 (unbundle,
2469 (unbundle,
2470 [('u', 'update', None,
2470 [('u', 'update', None,
2471 _('update the working directory to tip after unbundle'))],
2471 _('update the working directory to tip after unbundle'))],
2472 _('hg unbundle [-u] FILE')),
2472 _('hg unbundle [-u] FILE')),
2473 "undo": (undo, [], _('hg undo')),
2473 "undo": (undo, [], _('hg undo')),
2474 "^update|up|checkout|co":
2474 "^update|up|checkout|co":
2475 (update,
2475 (update,
2476 [('b', 'branch', '', _('checkout the head of a specific branch')),
2476 [('b', 'branch', '', _('checkout the head of a specific branch')),
2477 ('m', 'merge', None, _('allow merging of branches')),
2477 ('m', 'merge', None, _('allow merging of branches')),
2478 ('C', 'clean', None, _('overwrite locally modified files')),
2478 ('C', 'clean', None, _('overwrite locally modified files')),
2479 ('f', 'force', None, _('force a merge with outstanding changes'))],
2479 ('f', 'force', None, _('force a merge with outstanding changes'))],
2480 _('hg update [-b TAG] [-m] [-C] [-f] [REV]')),
2480 _('hg update [-b TAG] [-m] [-C] [-f] [REV]')),
2481 "verify": (verify, [], _('hg verify')),
2481 "verify": (verify, [], _('hg verify')),
2482 "version": (show_version, [], _('hg version')),
2482 "version": (show_version, [], _('hg version')),
2483 }
2483 }
2484
2484
2485 globalopts = [
2485 globalopts = [
2486 ('R', 'repository', '', _('repository root directory')),
2486 ('R', 'repository', '', _('repository root directory')),
2487 ('', 'cwd', '', _('change working directory')),
2487 ('', 'cwd', '', _('change working directory')),
2488 ('y', 'noninteractive', None,
2488 ('y', 'noninteractive', None,
2489 _('do not prompt, assume \'yes\' for any required answers')),
2489 _('do not prompt, assume \'yes\' for any required answers')),
2490 ('q', 'quiet', None, _('suppress output')),
2490 ('q', 'quiet', None, _('suppress output')),
2491 ('v', 'verbose', None, _('enable additional output')),
2491 ('v', 'verbose', None, _('enable additional output')),
2492 ('', 'debug', None, _('enable debugging output')),
2492 ('', 'debug', None, _('enable debugging output')),
2493 ('', 'debugger', None, _('start debugger')),
2493 ('', 'debugger', None, _('start debugger')),
2494 ('', 'traceback', None, _('print traceback on exception')),
2494 ('', 'traceback', None, _('print traceback on exception')),
2495 ('', 'time', None, _('time how long the command takes')),
2495 ('', 'time', None, _('time how long the command takes')),
2496 ('', 'profile', None, _('print command execution profile')),
2496 ('', 'profile', None, _('print command execution profile')),
2497 ('', 'version', None, _('output version information and exit')),
2497 ('', 'version', None, _('output version information and exit')),
2498 ('h', 'help', None, _('display help and exit')),
2498 ('h', 'help', None, _('display help and exit')),
2499 ]
2499 ]
2500
2500
2501 norepo = ("clone init version help debugancestor debugconfig debugdata"
2501 norepo = ("clone init version help debugancestor debugconfig debugdata"
2502 " debugindex debugindexdot paths")
2502 " debugindex debugindexdot paths")
2503
2503
2504 def find(cmd):
2504 def find(cmd):
2505 """Return (aliases, command table entry) for command string."""
2505 """Return (aliases, command table entry) for command string."""
2506 choice = None
2506 choice = None
2507 for e in table.keys():
2507 for e in table.keys():
2508 aliases = e.lstrip("^").split("|")
2508 aliases = e.lstrip("^").split("|")
2509 if cmd in aliases:
2509 if cmd in aliases:
2510 return aliases, table[e]
2510 return aliases, table[e]
2511 for a in aliases:
2511 for a in aliases:
2512 if a.startswith(cmd):
2512 if a.startswith(cmd):
2513 if choice:
2513 if choice:
2514 raise AmbiguousCommand(cmd)
2514 raise AmbiguousCommand(cmd)
2515 else:
2515 else:
2516 choice = aliases, table[e]
2516 choice = aliases, table[e]
2517 break
2517 break
2518 if choice:
2518 if choice:
2519 return choice
2519 return choice
2520
2520
2521 raise UnknownCommand(cmd)
2521 raise UnknownCommand(cmd)
2522
2522
2523 class SignalInterrupt(Exception):
2523 class SignalInterrupt(Exception):
2524 """Exception raised on SIGTERM and SIGHUP."""
2524 """Exception raised on SIGTERM and SIGHUP."""
2525
2525
2526 def catchterm(*args):
2526 def catchterm(*args):
2527 raise SignalInterrupt
2527 raise SignalInterrupt
2528
2528
2529 def run():
2529 def run():
2530 sys.exit(dispatch(sys.argv[1:]))
2530 sys.exit(dispatch(sys.argv[1:]))
2531
2531
2532 class ParseError(Exception):
2532 class ParseError(Exception):
2533 """Exception raised on errors in parsing the command line."""
2533 """Exception raised on errors in parsing the command line."""
2534
2534
2535 def parse(ui, args):
2535 def parse(ui, args):
2536 options = {}
2536 options = {}
2537 cmdoptions = {}
2537 cmdoptions = {}
2538
2538
2539 try:
2539 try:
2540 args = fancyopts.fancyopts(args, globalopts, options)
2540 args = fancyopts.fancyopts(args, globalopts, options)
2541 except fancyopts.getopt.GetoptError, inst:
2541 except fancyopts.getopt.GetoptError, inst:
2542 raise ParseError(None, inst)
2542 raise ParseError(None, inst)
2543
2543
2544 if args:
2544 if args:
2545 cmd, args = args[0], args[1:]
2545 cmd, args = args[0], args[1:]
2546 aliases, i = find(cmd)
2546 aliases, i = find(cmd)
2547 cmd = aliases[0]
2547 cmd = aliases[0]
2548 defaults = ui.config("defaults", cmd)
2548 defaults = ui.config("defaults", cmd)
2549 if defaults:
2549 if defaults:
2550 args = defaults.split() + args
2550 args = defaults.split() + args
2551 c = list(i[1])
2551 c = list(i[1])
2552 else:
2552 else:
2553 cmd = None
2553 cmd = None
2554 c = []
2554 c = []
2555
2555
2556 # combine global options into local
2556 # combine global options into local
2557 for o in globalopts:
2557 for o in globalopts:
2558 c.append((o[0], o[1], options[o[1]], o[3]))
2558 c.append((o[0], o[1], options[o[1]], o[3]))
2559
2559
2560 try:
2560 try:
2561 args = fancyopts.fancyopts(args, c, cmdoptions)
2561 args = fancyopts.fancyopts(args, c, cmdoptions)
2562 except fancyopts.getopt.GetoptError, inst:
2562 except fancyopts.getopt.GetoptError, inst:
2563 raise ParseError(cmd, inst)
2563 raise ParseError(cmd, inst)
2564
2564
2565 # separate global options back out
2565 # separate global options back out
2566 for o in globalopts:
2566 for o in globalopts:
2567 n = o[1]
2567 n = o[1]
2568 options[n] = cmdoptions[n]
2568 options[n] = cmdoptions[n]
2569 del cmdoptions[n]
2569 del cmdoptions[n]
2570
2570
2571 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
2571 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
2572
2572
2573 def dispatch(args):
2573 def dispatch(args):
2574 signal.signal(signal.SIGTERM, catchterm)
2574 signal.signal(signal.SIGTERM, catchterm)
2575 try:
2575 try:
2576 signal.signal(signal.SIGHUP, catchterm)
2576 signal.signal(signal.SIGHUP, catchterm)
2577 except AttributeError:
2577 except AttributeError:
2578 pass
2578 pass
2579
2579
2580 try:
2580 try:
2581 u = ui.ui()
2581 u = ui.ui()
2582 except util.Abort, inst:
2582 except util.Abort, inst:
2583 sys.stderr.write(_("abort: %s\n") % inst)
2583 sys.stderr.write(_("abort: %s\n") % inst)
2584 sys.exit(1)
2584 sys.exit(1)
2585
2585
2586 external = []
2586 external = []
2587 for x in u.extensions():
2587 for x in u.extensions():
2588 def on_exception(exc, inst):
2588 def on_exception(exc, inst):
2589 u.warn(_("*** failed to import extension %s\n") % x[1])
2589 u.warn(_("*** failed to import extension %s\n") % x[1])
2590 u.warn("%s\n" % inst)
2590 u.warn("%s\n" % inst)
2591 if "--traceback" in sys.argv[1:]:
2591 if "--traceback" in sys.argv[1:]:
2592 traceback.print_exc()
2592 traceback.print_exc()
2593 if x[1]:
2593 if x[1]:
2594 try:
2594 try:
2595 mod = imp.load_source(x[0], x[1])
2595 mod = imp.load_source(x[0], x[1])
2596 except Exception, inst:
2596 except Exception, inst:
2597 on_exception(Exception, inst)
2597 on_exception(Exception, inst)
2598 continue
2598 continue
2599 else:
2599 else:
2600 def importh(name):
2600 def importh(name):
2601 mod = __import__(name)
2601 mod = __import__(name)
2602 components = name.split('.')
2602 components = name.split('.')
2603 for comp in components[1:]:
2603 for comp in components[1:]:
2604 mod = getattr(mod, comp)
2604 mod = getattr(mod, comp)
2605 return mod
2605 return mod
2606 try:
2606 try:
2607 mod = importh(x[0])
2607 mod = importh(x[0])
2608 except Exception, inst:
2608 except Exception, inst:
2609 on_exception(Exception, inst)
2609 on_exception(Exception, inst)
2610 continue
2610 continue
2611
2611
2612 external.append(mod)
2612 external.append(mod)
2613 for x in external:
2613 for x in external:
2614 cmdtable = getattr(x, 'cmdtable', {})
2614 cmdtable = getattr(x, 'cmdtable', {})
2615 for t in cmdtable:
2615 for t in cmdtable:
2616 if t in table:
2616 if t in table:
2617 u.warn(_("module %s overrides %s\n") % (x.__name__, t))
2617 u.warn(_("module %s overrides %s\n") % (x.__name__, t))
2618 table.update(cmdtable)
2618 table.update(cmdtable)
2619
2619
2620 try:
2620 try:
2621 cmd, func, args, options, cmdoptions = parse(u, args)
2621 cmd, func, args, options, cmdoptions = parse(u, args)
2622 except ParseError, inst:
2622 except ParseError, inst:
2623 if inst.args[0]:
2623 if inst.args[0]:
2624 u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
2624 u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
2625 help_(u, inst.args[0])
2625 help_(u, inst.args[0])
2626 else:
2626 else:
2627 u.warn(_("hg: %s\n") % inst.args[1])
2627 u.warn(_("hg: %s\n") % inst.args[1])
2628 help_(u, 'shortlist')
2628 help_(u, 'shortlist')
2629 sys.exit(-1)
2629 sys.exit(-1)
2630 except AmbiguousCommand, inst:
2630 except AmbiguousCommand, inst:
2631 u.warn(_("hg: command '%s' is ambiguous.\n") % inst.args[0])
2631 u.warn(_("hg: command '%s' is ambiguous.\n") % inst.args[0])
2632 sys.exit(1)
2632 sys.exit(1)
2633 except UnknownCommand, inst:
2633 except UnknownCommand, inst:
2634 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
2634 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
2635 help_(u, 'shortlist')
2635 help_(u, 'shortlist')
2636 sys.exit(1)
2636 sys.exit(1)
2637
2637
2638 if options["time"]:
2638 if options["time"]:
2639 def get_times():
2639 def get_times():
2640 t = os.times()
2640 t = os.times()
2641 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
2641 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
2642 t = (t[0], t[1], t[2], t[3], time.clock())
2642 t = (t[0], t[1], t[2], t[3], time.clock())
2643 return t
2643 return t
2644 s = get_times()
2644 s = get_times()
2645 def print_time():
2645 def print_time():
2646 t = get_times()
2646 t = get_times()
2647 u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
2647 u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
2648 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
2648 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
2649 atexit.register(print_time)
2649 atexit.register(print_time)
2650
2650
2651 u.updateopts(options["verbose"], options["debug"], options["quiet"],
2651 u.updateopts(options["verbose"], options["debug"], options["quiet"],
2652 not options["noninteractive"])
2652 not options["noninteractive"])
2653
2653
2654 # enter the debugger before command execution
2654 # enter the debugger before command execution
2655 if options['debugger']:
2655 if options['debugger']:
2656 pdb.set_trace()
2656 pdb.set_trace()
2657
2657
2658 try:
2658 try:
2659 try:
2659 try:
2660 if options['help']:
2660 if options['help']:
2661 help_(u, cmd, options['version'])
2661 help_(u, cmd, options['version'])
2662 sys.exit(0)
2662 sys.exit(0)
2663 elif options['version']:
2663 elif options['version']:
2664 show_version(u)
2664 show_version(u)
2665 sys.exit(0)
2665 sys.exit(0)
2666 elif not cmd:
2666 elif not cmd:
2667 help_(u, 'shortlist')
2667 help_(u, 'shortlist')
2668 sys.exit(0)
2668 sys.exit(0)
2669
2669
2670 if options['cwd']:
2670 if options['cwd']:
2671 try:
2671 try:
2672 os.chdir(options['cwd'])
2672 os.chdir(options['cwd'])
2673 except OSError, inst:
2673 except OSError, inst:
2674 raise util.Abort('%s: %s' %
2674 raise util.Abort('%s: %s' %
2675 (options['cwd'], inst.strerror))
2675 (options['cwd'], inst.strerror))
2676
2676
2677 if cmd not in norepo.split():
2677 if cmd not in norepo.split():
2678 path = options["repository"] or ""
2678 path = options["repository"] or ""
2679 repo = hg.repository(ui=u, path=path)
2679 repo = hg.repository(ui=u, path=path)
2680 for x in external:
2680 for x in external:
2681 if hasattr(x, 'reposetup'):
2681 if hasattr(x, 'reposetup'):
2682 x.reposetup(u, repo)
2682 x.reposetup(u, repo)
2683 d = lambda: func(u, repo, *args, **cmdoptions)
2683 d = lambda: func(u, repo, *args, **cmdoptions)
2684 else:
2684 else:
2685 d = lambda: func(u, *args, **cmdoptions)
2685 d = lambda: func(u, *args, **cmdoptions)
2686
2686
2687 if options['profile']:
2687 if options['profile']:
2688 import hotshot, hotshot.stats
2688 import hotshot, hotshot.stats
2689 prof = hotshot.Profile("hg.prof")
2689 prof = hotshot.Profile("hg.prof")
2690 r = prof.runcall(d)
2690 r = prof.runcall(d)
2691 prof.close()
2691 prof.close()
2692 stats = hotshot.stats.load("hg.prof")
2692 stats = hotshot.stats.load("hg.prof")
2693 stats.strip_dirs()
2693 stats.strip_dirs()
2694 stats.sort_stats('time', 'calls')
2694 stats.sort_stats('time', 'calls')
2695 stats.print_stats(40)
2695 stats.print_stats(40)
2696 return r
2696 return r
2697 else:
2697 else:
2698 return d()
2698 return d()
2699 except:
2699 except:
2700 # enter the debugger when we hit an exception
2700 # enter the debugger when we hit an exception
2701 if options['debugger']:
2701 if options['debugger']:
2702 pdb.post_mortem(sys.exc_info()[2])
2702 pdb.post_mortem(sys.exc_info()[2])
2703 if options['traceback']:
2703 if options['traceback']:
2704 traceback.print_exc()
2704 traceback.print_exc()
2705 raise
2705 raise
2706 except hg.RepoError, inst:
2706 except hg.RepoError, inst:
2707 u.warn(_("abort: "), inst, "!\n")
2707 u.warn(_("abort: "), inst, "!\n")
2708 except revlog.RevlogError, inst:
2708 except revlog.RevlogError, inst:
2709 u.warn(_("abort: "), inst, "!\n")
2709 u.warn(_("abort: "), inst, "!\n")
2710 except SignalInterrupt:
2710 except SignalInterrupt:
2711 u.warn(_("killed!\n"))
2711 u.warn(_("killed!\n"))
2712 except KeyboardInterrupt:
2712 except KeyboardInterrupt:
2713 try:
2713 try:
2714 u.warn(_("interrupted!\n"))
2714 u.warn(_("interrupted!\n"))
2715 except IOError, inst:
2715 except IOError, inst:
2716 if inst.errno == errno.EPIPE:
2716 if inst.errno == errno.EPIPE:
2717 if u.debugflag:
2717 if u.debugflag:
2718 u.warn(_("\nbroken pipe\n"))
2718 u.warn(_("\nbroken pipe\n"))
2719 else:
2719 else:
2720 raise
2720 raise
2721 except IOError, inst:
2721 except IOError, inst:
2722 if hasattr(inst, "code"):
2722 if hasattr(inst, "code"):
2723 u.warn(_("abort: %s\n") % inst)
2723 u.warn(_("abort: %s\n") % inst)
2724 elif hasattr(inst, "reason"):
2724 elif hasattr(inst, "reason"):
2725 u.warn(_("abort: error: %s\n") % inst.reason[1])
2725 u.warn(_("abort: error: %s\n") % inst.reason[1])
2726 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
2726 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
2727 if u.debugflag:
2727 if u.debugflag:
2728 u.warn(_("broken pipe\n"))
2728 u.warn(_("broken pipe\n"))
2729 elif getattr(inst, "strerror", None):
2729 elif getattr(inst, "strerror", None):
2730 if getattr(inst, "filename", None):
2730 if getattr(inst, "filename", None):
2731 u.warn(_("abort: %s - %s\n") % (inst.strerror, inst.filename))
2731 u.warn(_("abort: %s - %s\n") % (inst.strerror, inst.filename))
2732 else:
2732 else:
2733 u.warn(_("abort: %s\n") % inst.strerror)
2733 u.warn(_("abort: %s\n") % inst.strerror)
2734 else:
2734 else:
2735 raise
2735 raise
2736 except OSError, inst:
2736 except OSError, inst:
2737 if hasattr(inst, "filename"):
2737 if hasattr(inst, "filename"):
2738 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
2738 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
2739 else:
2739 else:
2740 u.warn(_("abort: %s\n") % inst.strerror)
2740 u.warn(_("abort: %s\n") % inst.strerror)
2741 except util.Abort, inst:
2741 except util.Abort, inst:
2742 u.warn(_('abort: '), inst.args[0] % inst.args[1:], '\n')
2742 u.warn(_('abort: '), inst.args[0] % inst.args[1:], '\n')
2743 sys.exit(1)
2743 sys.exit(1)
2744 except TypeError, inst:
2744 except TypeError, inst:
2745 # was this an argument error?
2745 # was this an argument error?
2746 tb = traceback.extract_tb(sys.exc_info()[2])
2746 tb = traceback.extract_tb(sys.exc_info()[2])
2747 if len(tb) > 2: # no
2747 if len(tb) > 2: # no
2748 raise
2748 raise
2749 u.debug(inst, "\n")
2749 u.debug(inst, "\n")
2750 u.warn(_("%s: invalid arguments\n") % cmd)
2750 u.warn(_("%s: invalid arguments\n") % cmd)
2751 help_(u, cmd)
2751 help_(u, cmd)
2752 except AmbiguousCommand, inst:
2752 except AmbiguousCommand, inst:
2753 u.warn(_("hg: command '%s' is ambiguous.\n") % inst.args[0])
2753 u.warn(_("hg: command '%s' is ambiguous.\n") % inst.args[0])
2754 help_(u, 'shortlist')
2754 help_(u, 'shortlist')
2755 except UnknownCommand, inst:
2755 except UnknownCommand, inst:
2756 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
2756 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
2757 help_(u, 'shortlist')
2757 help_(u, 'shortlist')
2758 except SystemExit:
2758 except SystemExit:
2759 # don't catch this in the catch-all below
2759 # don't catch this in the catch-all below
2760 raise
2760 raise
2761 except:
2761 except:
2762 u.warn(_("** unknown exception encountered, details follow\n"))
2762 u.warn(_("** unknown exception encountered, details follow\n"))
2763 u.warn(_("** report bug details to mercurial@selenic.com\n"))
2763 u.warn(_("** report bug details to mercurial@selenic.com\n"))
2764 u.warn(_("** Mercurial Distributed SCM (version %s)\n")
2764 u.warn(_("** Mercurial Distributed SCM (version %s)\n")
2765 % version.get_version())
2765 % version.get_version())
2766 raise
2766 raise
2767
2767
2768 sys.exit(-1)
2768 sys.exit(-1)
@@ -1,1110 +1,1110 b''
1 # hgweb.py - web interface to a mercurial repository
1 # hgweb.py - web interface to a mercurial repository
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os, cgi, sys
9 import os, cgi, sys
10 from demandload import demandload
10 from demandload import demandload
11 demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser")
11 demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser")
12 demandload(globals(), "zipfile tempfile StringIO tarfile BaseHTTPServer util")
12 demandload(globals(), "zipfile tempfile StringIO tarfile BaseHTTPServer util")
13 demandload(globals(), "mimetypes")
13 demandload(globals(), "mimetypes")
14 from node import *
14 from node import *
15 from i18n import gettext as _
15 from i18n import gettext as _
16
16
17 def templatepath():
17 def templatepath():
18 for f in "templates", "../templates":
18 for f in "templates", "../templates":
19 p = os.path.join(os.path.dirname(__file__), f)
19 p = os.path.join(os.path.dirname(__file__), f)
20 if os.path.isdir(p):
20 if os.path.isdir(p):
21 return p
21 return p
22
22
23 def age(x):
23 def age(x):
24 def plural(t, c):
24 def plural(t, c):
25 if c == 1:
25 if c == 1:
26 return t
26 return t
27 return t + "s"
27 return t + "s"
28 def fmt(t, c):
28 def fmt(t, c):
29 return "%d %s" % (c, plural(t, c))
29 return "%d %s" % (c, plural(t, c))
30
30
31 now = time.time()
31 now = time.time()
32 then = x[0]
32 then = x[0]
33 delta = max(1, int(now - then))
33 delta = max(1, int(now - then))
34
34
35 scales = [["second", 1],
35 scales = [["second", 1],
36 ["minute", 60],
36 ["minute", 60],
37 ["hour", 3600],
37 ["hour", 3600],
38 ["day", 3600 * 24],
38 ["day", 3600 * 24],
39 ["week", 3600 * 24 * 7],
39 ["week", 3600 * 24 * 7],
40 ["month", 3600 * 24 * 30],
40 ["month", 3600 * 24 * 30],
41 ["year", 3600 * 24 * 365]]
41 ["year", 3600 * 24 * 365]]
42
42
43 scales.reverse()
43 scales.reverse()
44
44
45 for t, s in scales:
45 for t, s in scales:
46 n = delta / s
46 n = delta / s
47 if n >= 2 or s == 1:
47 if n >= 2 or s == 1:
48 return fmt(t, n)
48 return fmt(t, n)
49
49
50 def nl2br(text):
50 def nl2br(text):
51 return text.replace('\n', '<br/>\n')
51 return text.replace('\n', '<br/>\n')
52
52
53 def obfuscate(text):
53 def obfuscate(text):
54 return ''.join(['&#%d;' % ord(c) for c in text])
54 return ''.join(['&#%d;' % ord(c) for c in text])
55
55
56 def up(p):
56 def up(p):
57 if p[0] != "/":
57 if p[0] != "/":
58 p = "/" + p
58 p = "/" + p
59 if p[-1] == "/":
59 if p[-1] == "/":
60 p = p[:-1]
60 p = p[:-1]
61 up = os.path.dirname(p)
61 up = os.path.dirname(p)
62 if up == "/":
62 if up == "/":
63 return "/"
63 return "/"
64 return up + "/"
64 return up + "/"
65
65
66 def get_mtime(repo_path):
66 def get_mtime(repo_path):
67 hg_path = os.path.join(repo_path, ".hg")
67 hg_path = os.path.join(repo_path, ".hg")
68 cl_path = os.path.join(hg_path, "00changelog.i")
68 cl_path = os.path.join(hg_path, "00changelog.i")
69 if os.path.exists(os.path.join(cl_path)):
69 if os.path.exists(os.path.join(cl_path)):
70 return os.stat(cl_path).st_mtime
70 return os.stat(cl_path).st_mtime
71 else:
71 else:
72 return os.stat(hg_path).st_mtime
72 return os.stat(hg_path).st_mtime
73
73
74 class hgrequest(object):
74 class hgrequest(object):
75 def __init__(self, inp=None, out=None, env=None):
75 def __init__(self, inp=None, out=None, env=None):
76 self.inp = inp or sys.stdin
76 self.inp = inp or sys.stdin
77 self.out = out or sys.stdout
77 self.out = out or sys.stdout
78 self.env = env or os.environ
78 self.env = env or os.environ
79 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
79 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
80
80
81 def write(self, *things):
81 def write(self, *things):
82 for thing in things:
82 for thing in things:
83 if hasattr(thing, "__iter__"):
83 if hasattr(thing, "__iter__"):
84 for part in thing:
84 for part in thing:
85 self.write(part)
85 self.write(part)
86 else:
86 else:
87 try:
87 try:
88 self.out.write(str(thing))
88 self.out.write(str(thing))
89 except socket.error, inst:
89 except socket.error, inst:
90 if inst[0] != errno.ECONNRESET:
90 if inst[0] != errno.ECONNRESET:
91 raise
91 raise
92
92
93 def header(self, headers=[('Content-type','text/html')]):
93 def header(self, headers=[('Content-type','text/html')]):
94 for header in headers:
94 for header in headers:
95 self.out.write("%s: %s\r\n" % header)
95 self.out.write("%s: %s\r\n" % header)
96 self.out.write("\r\n")
96 self.out.write("\r\n")
97
97
98 def httphdr(self, type, file="", size=0):
98 def httphdr(self, type, file="", size=0):
99
99
100 headers = [('Content-type', type)]
100 headers = [('Content-type', type)]
101 if file:
101 if file:
102 headers.append(('Content-disposition', 'attachment; filename=%s' % file))
102 headers.append(('Content-disposition', 'attachment; filename=%s' % file))
103 if size > 0:
103 if size > 0:
104 headers.append(('Content-length', str(size)))
104 headers.append(('Content-length', str(size)))
105 self.header(headers)
105 self.header(headers)
106
106
107 class templater(object):
107 class templater(object):
108 def __init__(self, mapfile, filters={}, defaults={}):
108 def __init__(self, mapfile, filters={}, defaults={}):
109 self.cache = {}
109 self.cache = {}
110 self.map = {}
110 self.map = {}
111 self.base = os.path.dirname(mapfile)
111 self.base = os.path.dirname(mapfile)
112 self.filters = filters
112 self.filters = filters
113 self.defaults = defaults
113 self.defaults = defaults
114
114
115 for l in file(mapfile):
115 for l in file(mapfile):
116 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
116 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
117 if m:
117 if m:
118 self.cache[m.group(1)] = m.group(2)
118 self.cache[m.group(1)] = m.group(2)
119 else:
119 else:
120 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
120 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
121 if m:
121 if m:
122 self.map[m.group(1)] = os.path.join(self.base, m.group(2))
122 self.map[m.group(1)] = os.path.join(self.base, m.group(2))
123 else:
123 else:
124 raise LookupError(_("unknown map entry '%s'") % l)
124 raise LookupError(_("unknown map entry '%s'") % l)
125
125
126 def __call__(self, t, **map):
126 def __call__(self, t, **map):
127 m = self.defaults.copy()
127 m = self.defaults.copy()
128 m.update(map)
128 m.update(map)
129 try:
129 try:
130 tmpl = self.cache[t]
130 tmpl = self.cache[t]
131 except KeyError:
131 except KeyError:
132 tmpl = self.cache[t] = file(self.map[t]).read()
132 tmpl = self.cache[t] = file(self.map[t]).read()
133 return self.template(tmpl, self.filters, **m)
133 return self.template(tmpl, self.filters, **m)
134
134
135 def template(self, tmpl, filters={}, **map):
135 def template(self, tmpl, filters={}, **map):
136 while tmpl:
136 while tmpl:
137 m = re.search(r"#([a-zA-Z0-9]+)((%[a-zA-Z0-9]+)*)((\|[a-zA-Z0-9]+)*)#", tmpl)
137 m = re.search(r"#([a-zA-Z0-9]+)((%[a-zA-Z0-9]+)*)((\|[a-zA-Z0-9]+)*)#", tmpl)
138 if m:
138 if m:
139 yield tmpl[:m.start(0)]
139 yield tmpl[:m.start(0)]
140 v = map.get(m.group(1), "")
140 v = map.get(m.group(1), "")
141 v = callable(v) and v(**map) or v
141 v = callable(v) and v(**map) or v
142
142
143 format = m.group(2)
143 format = m.group(2)
144 fl = m.group(4)
144 fl = m.group(4)
145
145
146 if format:
146 if format:
147 q = v.__iter__
147 q = v.__iter__
148 for i in q():
148 for i in q():
149 lm = map.copy()
149 lm = map.copy()
150 lm.update(i)
150 lm.update(i)
151 yield self(format[1:], **lm)
151 yield self(format[1:], **lm)
152
152
153 v = ""
153 v = ""
154
154
155 elif fl:
155 elif fl:
156 for f in fl.split("|")[1:]:
156 for f in fl.split("|")[1:]:
157 v = filters[f](v)
157 v = filters[f](v)
158
158
159 yield v
159 yield v
160 tmpl = tmpl[m.end(0):]
160 tmpl = tmpl[m.end(0):]
161 else:
161 else:
162 yield tmpl
162 yield tmpl
163 return
163 return
164
164
165 common_filters = {
165 common_filters = {
166 "escape": cgi.escape,
166 "escape": cgi.escape,
167 "strip": lambda x: x.strip(),
167 "strip": lambda x: x.strip(),
168 "age": age,
168 "age": age,
169 "date": lambda x: util.datestr(x),
169 "date": lambda x: util.datestr(x),
170 "addbreaks": nl2br,
170 "addbreaks": nl2br,
171 "obfuscate": obfuscate,
171 "obfuscate": obfuscate,
172 "short": (lambda x: x[:12]),
172 "short": (lambda x: x[:12]),
173 "firstline": (lambda x: x.splitlines(1)[0]),
173 "firstline": (lambda x: x.splitlines(1)[0]),
174 "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"),
174 "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"),
175 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
175 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
176 }
176 }
177
177
178 class hgweb(object):
178 class hgweb(object):
179 def __init__(self, repo, name=None):
179 def __init__(self, repo, name=None):
180 if type(repo) == type(""):
180 if type(repo) == type(""):
181 self.repo = hg.repository(ui.ui(), repo)
181 self.repo = hg.repository(ui.ui(), repo)
182 else:
182 else:
183 self.repo = repo
183 self.repo = repo
184
184
185 self.mtime = -1
185 self.mtime = -1
186 self.reponame = name
186 self.reponame = name
187 self.archives = 'zip', 'gz', 'bz2'
187 self.archives = 'zip', 'gz', 'bz2'
188
188
189 def refresh(self):
189 def refresh(self):
190 mtime = get_mtime(self.repo.root)
190 mtime = get_mtime(self.repo.root)
191 if mtime != self.mtime:
191 if mtime != self.mtime:
192 self.mtime = mtime
192 self.mtime = mtime
193 self.repo = hg.repository(self.repo.ui, self.repo.root)
193 self.repo = hg.repository(self.repo.ui, self.repo.root)
194 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
194 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
195 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
195 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
196 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
196 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
197
197
198 def archivelist(self, nodeid):
198 def archivelist(self, nodeid):
199 for i in self.archives:
199 for i in self.archives:
200 if self.repo.ui.configbool("web", "allow" + i, False):
200 if self.repo.ui.configbool("web", "allow" + i, False):
201 yield {"type" : i, "node" : nodeid}
201 yield {"type" : i, "node" : nodeid}
202
202
203 def listfiles(self, files, mf):
203 def listfiles(self, files, mf):
204 for f in files[:self.maxfiles]:
204 for f in files[:self.maxfiles]:
205 yield self.t("filenodelink", node=hex(mf[f]), file=f)
205 yield self.t("filenodelink", node=hex(mf[f]), file=f)
206 if len(files) > self.maxfiles:
206 if len(files) > self.maxfiles:
207 yield self.t("fileellipses")
207 yield self.t("fileellipses")
208
208
209 def listfilediffs(self, files, changeset):
209 def listfilediffs(self, files, changeset):
210 for f in files[:self.maxfiles]:
210 for f in files[:self.maxfiles]:
211 yield self.t("filedifflink", node=hex(changeset), file=f)
211 yield self.t("filedifflink", node=hex(changeset), file=f)
212 if len(files) > self.maxfiles:
212 if len(files) > self.maxfiles:
213 yield self.t("fileellipses")
213 yield self.t("fileellipses")
214
214
215 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
215 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
216 if not rev:
216 if not rev:
217 rev = lambda x: ""
217 rev = lambda x: ""
218 siblings = [s for s in siblings if s != nullid]
218 siblings = [s for s in siblings if s != nullid]
219 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
219 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
220 return
220 return
221 for s in siblings:
221 for s in siblings:
222 yield dict(node=hex(s), rev=rev(s), **args)
222 yield dict(node=hex(s), rev=rev(s), **args)
223
223
224 def showtag(self, t1, node=nullid, **args):
224 def showtag(self, t1, node=nullid, **args):
225 for t in self.repo.nodetags(node):
225 for t in self.repo.nodetags(node):
226 yield self.t(t1, tag=t, **args)
226 yield self.t(t1, tag=t, **args)
227
227
228 def diff(self, node1, node2, files):
228 def diff(self, node1, node2, files):
229 def filterfiles(list, files):
229 def filterfiles(filters, files):
230 l = [x for x in list if x in files]
230 l = [x for x in list if x in filters]
231
231
232 for f in files:
232 for t in filters:
233 if f[-1] != os.sep:
233 if t[-1] != os.sep:
234 f += os.sep
234 t += os.sep
235 l += [x for x in list if x.startswith(f)]
235 l += [x for x in files if x.startswith(t)]
236 return l
236 return l
237
237
238 parity = [0]
238 parity = [0]
239 def diffblock(diff, f, fn):
239 def diffblock(diff, f, fn):
240 yield self.t("diffblock",
240 yield self.t("diffblock",
241 lines=prettyprintlines(diff),
241 lines=prettyprintlines(diff),
242 parity=parity[0],
242 parity=parity[0],
243 file=f,
243 file=f,
244 filenode=hex(fn or nullid))
244 filenode=hex(fn or nullid))
245 parity[0] = 1 - parity[0]
245 parity[0] = 1 - parity[0]
246
246
247 def prettyprintlines(diff):
247 def prettyprintlines(diff):
248 for l in diff.splitlines(1):
248 for l in diff.splitlines(1):
249 if l.startswith('+'):
249 if l.startswith('+'):
250 yield self.t("difflineplus", line=l)
250 yield self.t("difflineplus", line=l)
251 elif l.startswith('-'):
251 elif l.startswith('-'):
252 yield self.t("difflineminus", line=l)
252 yield self.t("difflineminus", line=l)
253 elif l.startswith('@'):
253 elif l.startswith('@'):
254 yield self.t("difflineat", line=l)
254 yield self.t("difflineat", line=l)
255 else:
255 else:
256 yield self.t("diffline", line=l)
256 yield self.t("diffline", line=l)
257
257
258 r = self.repo
258 r = self.repo
259 cl = r.changelog
259 cl = r.changelog
260 mf = r.manifest
260 mf = r.manifest
261 change1 = cl.read(node1)
261 change1 = cl.read(node1)
262 change2 = cl.read(node2)
262 change2 = cl.read(node2)
263 mmap1 = mf.read(change1[0])
263 mmap1 = mf.read(change1[0])
264 mmap2 = mf.read(change2[0])
264 mmap2 = mf.read(change2[0])
265 date1 = util.datestr(change1[2])
265 date1 = util.datestr(change1[2])
266 date2 = util.datestr(change2[2])
266 date2 = util.datestr(change2[2])
267
267
268 modified, added, removed, deleted, unknown = r.changes(node1, node2)
268 modified, added, removed, deleted, unknown = r.changes(node1, node2)
269 if files:
269 if files:
270 modified, added, removed = map(lambda x: filterfiles(x, files),
270 modified, added, removed = map(lambda x: filterfiles(files, x),
271 (modified, added, removed))
271 (modified, added, removed))
272
272
273 for f in modified:
273 for f in modified:
274 to = r.file(f).read(mmap1[f])
274 to = r.file(f).read(mmap1[f])
275 tn = r.file(f).read(mmap2[f])
275 tn = r.file(f).read(mmap2[f])
276 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
276 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
277 for f in added:
277 for f in added:
278 to = None
278 to = None
279 tn = r.file(f).read(mmap2[f])
279 tn = r.file(f).read(mmap2[f])
280 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
280 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
281 for f in removed:
281 for f in removed:
282 to = r.file(f).read(mmap1[f])
282 to = r.file(f).read(mmap1[f])
283 tn = None
283 tn = None
284 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
284 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
285
285
286 def changelog(self, pos):
286 def changelog(self, pos):
287 def changenav(**map):
287 def changenav(**map):
288 def seq(factor=1):
288 def seq(factor=1):
289 yield 1 * factor
289 yield 1 * factor
290 yield 3 * factor
290 yield 3 * factor
291 #yield 5 * factor
291 #yield 5 * factor
292 for f in seq(factor * 10):
292 for f in seq(factor * 10):
293 yield f
293 yield f
294
294
295 l = []
295 l = []
296 for f in seq():
296 for f in seq():
297 if f < self.maxchanges / 2:
297 if f < self.maxchanges / 2:
298 continue
298 continue
299 if f > count:
299 if f > count:
300 break
300 break
301 r = "%d" % f
301 r = "%d" % f
302 if pos + f < count:
302 if pos + f < count:
303 l.append(("+" + r, pos + f))
303 l.append(("+" + r, pos + f))
304 if pos - f >= 0:
304 if pos - f >= 0:
305 l.insert(0, ("-" + r, pos - f))
305 l.insert(0, ("-" + r, pos - f))
306
306
307 yield {"rev": 0, "label": "(0)"}
307 yield {"rev": 0, "label": "(0)"}
308
308
309 for label, rev in l:
309 for label, rev in l:
310 yield {"label": label, "rev": rev}
310 yield {"label": label, "rev": rev}
311
311
312 yield {"label": "tip", "rev": "tip"}
312 yield {"label": "tip", "rev": "tip"}
313
313
314 def changelist(**map):
314 def changelist(**map):
315 parity = (start - end) & 1
315 parity = (start - end) & 1
316 cl = self.repo.changelog
316 cl = self.repo.changelog
317 l = [] # build a list in forward order for efficiency
317 l = [] # build a list in forward order for efficiency
318 for i in range(start, end):
318 for i in range(start, end):
319 n = cl.node(i)
319 n = cl.node(i)
320 changes = cl.read(n)
320 changes = cl.read(n)
321 hn = hex(n)
321 hn = hex(n)
322
322
323 l.insert(0, {"parity": parity,
323 l.insert(0, {"parity": parity,
324 "author": changes[1],
324 "author": changes[1],
325 "parent": self.siblings(cl.parents(n), cl.rev,
325 "parent": self.siblings(cl.parents(n), cl.rev,
326 cl.rev(n) - 1),
326 cl.rev(n) - 1),
327 "child": self.siblings(cl.children(n), cl.rev,
327 "child": self.siblings(cl.children(n), cl.rev,
328 cl.rev(n) + 1),
328 cl.rev(n) + 1),
329 "changelogtag": self.showtag("changelogtag",n),
329 "changelogtag": self.showtag("changelogtag",n),
330 "manifest": hex(changes[0]),
330 "manifest": hex(changes[0]),
331 "desc": changes[4],
331 "desc": changes[4],
332 "date": changes[2],
332 "date": changes[2],
333 "files": self.listfilediffs(changes[3], n),
333 "files": self.listfilediffs(changes[3], n),
334 "rev": i,
334 "rev": i,
335 "node": hn})
335 "node": hn})
336 parity = 1 - parity
336 parity = 1 - parity
337
337
338 for e in l:
338 for e in l:
339 yield e
339 yield e
340
340
341 cl = self.repo.changelog
341 cl = self.repo.changelog
342 mf = cl.read(cl.tip())[0]
342 mf = cl.read(cl.tip())[0]
343 count = cl.count()
343 count = cl.count()
344 start = max(0, pos - self.maxchanges + 1)
344 start = max(0, pos - self.maxchanges + 1)
345 end = min(count, start + self.maxchanges)
345 end = min(count, start + self.maxchanges)
346 pos = end - 1
346 pos = end - 1
347
347
348 yield self.t('changelog',
348 yield self.t('changelog',
349 changenav=changenav,
349 changenav=changenav,
350 manifest=hex(mf),
350 manifest=hex(mf),
351 rev=pos, changesets=count, entries=changelist)
351 rev=pos, changesets=count, entries=changelist)
352
352
353 def search(self, query):
353 def search(self, query):
354
354
355 def changelist(**map):
355 def changelist(**map):
356 cl = self.repo.changelog
356 cl = self.repo.changelog
357 count = 0
357 count = 0
358 qw = query.lower().split()
358 qw = query.lower().split()
359
359
360 def revgen():
360 def revgen():
361 for i in range(cl.count() - 1, 0, -100):
361 for i in range(cl.count() - 1, 0, -100):
362 l = []
362 l = []
363 for j in range(max(0, i - 100), i):
363 for j in range(max(0, i - 100), i):
364 n = cl.node(j)
364 n = cl.node(j)
365 changes = cl.read(n)
365 changes = cl.read(n)
366 l.append((n, j, changes))
366 l.append((n, j, changes))
367 l.reverse()
367 l.reverse()
368 for e in l:
368 for e in l:
369 yield e
369 yield e
370
370
371 for n, i, changes in revgen():
371 for n, i, changes in revgen():
372 miss = 0
372 miss = 0
373 for q in qw:
373 for q in qw:
374 if not (q in changes[1].lower() or
374 if not (q in changes[1].lower() or
375 q in changes[4].lower() or
375 q in changes[4].lower() or
376 q in " ".join(changes[3][:20]).lower()):
376 q in " ".join(changes[3][:20]).lower()):
377 miss = 1
377 miss = 1
378 break
378 break
379 if miss:
379 if miss:
380 continue
380 continue
381
381
382 count += 1
382 count += 1
383 hn = hex(n)
383 hn = hex(n)
384
384
385 yield self.t('searchentry',
385 yield self.t('searchentry',
386 parity=count & 1,
386 parity=count & 1,
387 author=changes[1],
387 author=changes[1],
388 parent=self.siblings(cl.parents(n), cl.rev),
388 parent=self.siblings(cl.parents(n), cl.rev),
389 child=self.siblings(cl.children(n), cl.rev),
389 child=self.siblings(cl.children(n), cl.rev),
390 changelogtag=self.showtag("changelogtag",n),
390 changelogtag=self.showtag("changelogtag",n),
391 manifest=hex(changes[0]),
391 manifest=hex(changes[0]),
392 desc=changes[4],
392 desc=changes[4],
393 date=changes[2],
393 date=changes[2],
394 files=self.listfilediffs(changes[3], n),
394 files=self.listfilediffs(changes[3], n),
395 rev=i,
395 rev=i,
396 node=hn)
396 node=hn)
397
397
398 if count >= self.maxchanges:
398 if count >= self.maxchanges:
399 break
399 break
400
400
401 cl = self.repo.changelog
401 cl = self.repo.changelog
402 mf = cl.read(cl.tip())[0]
402 mf = cl.read(cl.tip())[0]
403
403
404 yield self.t('search',
404 yield self.t('search',
405 query=query,
405 query=query,
406 manifest=hex(mf),
406 manifest=hex(mf),
407 entries=changelist)
407 entries=changelist)
408
408
409 def changeset(self, nodeid):
409 def changeset(self, nodeid):
410 cl = self.repo.changelog
410 cl = self.repo.changelog
411 n = self.repo.lookup(nodeid)
411 n = self.repo.lookup(nodeid)
412 nodeid = hex(n)
412 nodeid = hex(n)
413 changes = cl.read(n)
413 changes = cl.read(n)
414 p1 = cl.parents(n)[0]
414 p1 = cl.parents(n)[0]
415
415
416 files = []
416 files = []
417 mf = self.repo.manifest.read(changes[0])
417 mf = self.repo.manifest.read(changes[0])
418 for f in changes[3]:
418 for f in changes[3]:
419 files.append(self.t("filenodelink",
419 files.append(self.t("filenodelink",
420 filenode=hex(mf.get(f, nullid)), file=f))
420 filenode=hex(mf.get(f, nullid)), file=f))
421
421
422 def diff(**map):
422 def diff(**map):
423 yield self.diff(p1, n, None)
423 yield self.diff(p1, n, None)
424
424
425 yield self.t('changeset',
425 yield self.t('changeset',
426 diff=diff,
426 diff=diff,
427 rev=cl.rev(n),
427 rev=cl.rev(n),
428 node=nodeid,
428 node=nodeid,
429 parent=self.siblings(cl.parents(n), cl.rev),
429 parent=self.siblings(cl.parents(n), cl.rev),
430 child=self.siblings(cl.children(n), cl.rev),
430 child=self.siblings(cl.children(n), cl.rev),
431 changesettag=self.showtag("changesettag",n),
431 changesettag=self.showtag("changesettag",n),
432 manifest=hex(changes[0]),
432 manifest=hex(changes[0]),
433 author=changes[1],
433 author=changes[1],
434 desc=changes[4],
434 desc=changes[4],
435 date=changes[2],
435 date=changes[2],
436 files=files,
436 files=files,
437 archives=self.archivelist(nodeid))
437 archives=self.archivelist(nodeid))
438
438
439 def filelog(self, f, filenode):
439 def filelog(self, f, filenode):
440 cl = self.repo.changelog
440 cl = self.repo.changelog
441 fl = self.repo.file(f)
441 fl = self.repo.file(f)
442 filenode = hex(fl.lookup(filenode))
442 filenode = hex(fl.lookup(filenode))
443 count = fl.count()
443 count = fl.count()
444
444
445 def entries(**map):
445 def entries(**map):
446 l = []
446 l = []
447 parity = (count - 1) & 1
447 parity = (count - 1) & 1
448
448
449 for i in range(count):
449 for i in range(count):
450 n = fl.node(i)
450 n = fl.node(i)
451 lr = fl.linkrev(n)
451 lr = fl.linkrev(n)
452 cn = cl.node(lr)
452 cn = cl.node(lr)
453 cs = cl.read(cl.node(lr))
453 cs = cl.read(cl.node(lr))
454
454
455 l.insert(0, {"parity": parity,
455 l.insert(0, {"parity": parity,
456 "filenode": hex(n),
456 "filenode": hex(n),
457 "filerev": i,
457 "filerev": i,
458 "file": f,
458 "file": f,
459 "node": hex(cn),
459 "node": hex(cn),
460 "author": cs[1],
460 "author": cs[1],
461 "date": cs[2],
461 "date": cs[2],
462 "parent": self.siblings(fl.parents(n),
462 "parent": self.siblings(fl.parents(n),
463 fl.rev, file=f),
463 fl.rev, file=f),
464 "child": self.siblings(fl.children(n),
464 "child": self.siblings(fl.children(n),
465 fl.rev, file=f),
465 fl.rev, file=f),
466 "desc": cs[4]})
466 "desc": cs[4]})
467 parity = 1 - parity
467 parity = 1 - parity
468
468
469 for e in l:
469 for e in l:
470 yield e
470 yield e
471
471
472 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
472 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
473
473
474 def filerevision(self, f, node):
474 def filerevision(self, f, node):
475 fl = self.repo.file(f)
475 fl = self.repo.file(f)
476 n = fl.lookup(node)
476 n = fl.lookup(node)
477 node = hex(n)
477 node = hex(n)
478 text = fl.read(n)
478 text = fl.read(n)
479 changerev = fl.linkrev(n)
479 changerev = fl.linkrev(n)
480 cl = self.repo.changelog
480 cl = self.repo.changelog
481 cn = cl.node(changerev)
481 cn = cl.node(changerev)
482 cs = cl.read(cn)
482 cs = cl.read(cn)
483 mfn = cs[0]
483 mfn = cs[0]
484
484
485 mt = mimetypes.guess_type(f)[0]
485 mt = mimetypes.guess_type(f)[0]
486 rawtext = text
486 rawtext = text
487 if util.binary(text):
487 if util.binary(text):
488 text = "(binary:%s)" % mt
488 text = "(binary:%s)" % mt
489
489
490 def lines():
490 def lines():
491 for l, t in enumerate(text.splitlines(1)):
491 for l, t in enumerate(text.splitlines(1)):
492 yield {"line": t,
492 yield {"line": t,
493 "linenumber": "% 6d" % (l + 1),
493 "linenumber": "% 6d" % (l + 1),
494 "parity": l & 1}
494 "parity": l & 1}
495
495
496 yield self.t("filerevision",
496 yield self.t("filerevision",
497 file=f,
497 file=f,
498 filenode=node,
498 filenode=node,
499 path=up(f),
499 path=up(f),
500 text=lines(),
500 text=lines(),
501 raw=rawtext,
501 raw=rawtext,
502 mimetype=mt,
502 mimetype=mt,
503 rev=changerev,
503 rev=changerev,
504 node=hex(cn),
504 node=hex(cn),
505 manifest=hex(mfn),
505 manifest=hex(mfn),
506 author=cs[1],
506 author=cs[1],
507 date=cs[2],
507 date=cs[2],
508 parent=self.siblings(fl.parents(n), fl.rev, file=f),
508 parent=self.siblings(fl.parents(n), fl.rev, file=f),
509 child=self.siblings(fl.children(n), fl.rev, file=f),
509 child=self.siblings(fl.children(n), fl.rev, file=f),
510 permissions=self.repo.manifest.readflags(mfn)[f])
510 permissions=self.repo.manifest.readflags(mfn)[f])
511
511
512 def fileannotate(self, f, node):
512 def fileannotate(self, f, node):
513 bcache = {}
513 bcache = {}
514 ncache = {}
514 ncache = {}
515 fl = self.repo.file(f)
515 fl = self.repo.file(f)
516 n = fl.lookup(node)
516 n = fl.lookup(node)
517 node = hex(n)
517 node = hex(n)
518 changerev = fl.linkrev(n)
518 changerev = fl.linkrev(n)
519
519
520 cl = self.repo.changelog
520 cl = self.repo.changelog
521 cn = cl.node(changerev)
521 cn = cl.node(changerev)
522 cs = cl.read(cn)
522 cs = cl.read(cn)
523 mfn = cs[0]
523 mfn = cs[0]
524
524
525 def annotate(**map):
525 def annotate(**map):
526 parity = 1
526 parity = 1
527 last = None
527 last = None
528 for r, l in fl.annotate(n):
528 for r, l in fl.annotate(n):
529 try:
529 try:
530 cnode = ncache[r]
530 cnode = ncache[r]
531 except KeyError:
531 except KeyError:
532 cnode = ncache[r] = self.repo.changelog.node(r)
532 cnode = ncache[r] = self.repo.changelog.node(r)
533
533
534 try:
534 try:
535 name = bcache[r]
535 name = bcache[r]
536 except KeyError:
536 except KeyError:
537 cl = self.repo.changelog.read(cnode)
537 cl = self.repo.changelog.read(cnode)
538 bcache[r] = name = self.repo.ui.shortuser(cl[1])
538 bcache[r] = name = self.repo.ui.shortuser(cl[1])
539
539
540 if last != cnode:
540 if last != cnode:
541 parity = 1 - parity
541 parity = 1 - parity
542 last = cnode
542 last = cnode
543
543
544 yield {"parity": parity,
544 yield {"parity": parity,
545 "node": hex(cnode),
545 "node": hex(cnode),
546 "rev": r,
546 "rev": r,
547 "author": name,
547 "author": name,
548 "file": f,
548 "file": f,
549 "line": l}
549 "line": l}
550
550
551 yield self.t("fileannotate",
551 yield self.t("fileannotate",
552 file=f,
552 file=f,
553 filenode=node,
553 filenode=node,
554 annotate=annotate,
554 annotate=annotate,
555 path=up(f),
555 path=up(f),
556 rev=changerev,
556 rev=changerev,
557 node=hex(cn),
557 node=hex(cn),
558 manifest=hex(mfn),
558 manifest=hex(mfn),
559 author=cs[1],
559 author=cs[1],
560 date=cs[2],
560 date=cs[2],
561 parent=self.siblings(fl.parents(n), fl.rev, file=f),
561 parent=self.siblings(fl.parents(n), fl.rev, file=f),
562 child=self.siblings(fl.children(n), fl.rev, file=f),
562 child=self.siblings(fl.children(n), fl.rev, file=f),
563 permissions=self.repo.manifest.readflags(mfn)[f])
563 permissions=self.repo.manifest.readflags(mfn)[f])
564
564
565 def manifest(self, mnode, path):
565 def manifest(self, mnode, path):
566 man = self.repo.manifest
566 man = self.repo.manifest
567 mn = man.lookup(mnode)
567 mn = man.lookup(mnode)
568 mnode = hex(mn)
568 mnode = hex(mn)
569 mf = man.read(mn)
569 mf = man.read(mn)
570 rev = man.rev(mn)
570 rev = man.rev(mn)
571 node = self.repo.changelog.node(rev)
571 node = self.repo.changelog.node(rev)
572 mff = man.readflags(mn)
572 mff = man.readflags(mn)
573
573
574 files = {}
574 files = {}
575
575
576 p = path[1:]
576 p = path[1:]
577 l = len(p)
577 l = len(p)
578
578
579 for f,n in mf.items():
579 for f,n in mf.items():
580 if f[:l] != p:
580 if f[:l] != p:
581 continue
581 continue
582 remain = f[l:]
582 remain = f[l:]
583 if "/" in remain:
583 if "/" in remain:
584 short = remain[:remain.find("/") + 1] # bleah
584 short = remain[:remain.find("/") + 1] # bleah
585 files[short] = (f, None)
585 files[short] = (f, None)
586 else:
586 else:
587 short = os.path.basename(remain)
587 short = os.path.basename(remain)
588 files[short] = (f, n)
588 files[short] = (f, n)
589
589
590 def filelist(**map):
590 def filelist(**map):
591 parity = 0
591 parity = 0
592 fl = files.keys()
592 fl = files.keys()
593 fl.sort()
593 fl.sort()
594 for f in fl:
594 for f in fl:
595 full, fnode = files[f]
595 full, fnode = files[f]
596 if not fnode:
596 if not fnode:
597 continue
597 continue
598
598
599 yield {"file": full,
599 yield {"file": full,
600 "manifest": mnode,
600 "manifest": mnode,
601 "filenode": hex(fnode),
601 "filenode": hex(fnode),
602 "parity": parity,
602 "parity": parity,
603 "basename": f,
603 "basename": f,
604 "permissions": mff[full]}
604 "permissions": mff[full]}
605 parity = 1 - parity
605 parity = 1 - parity
606
606
607 def dirlist(**map):
607 def dirlist(**map):
608 parity = 0
608 parity = 0
609 fl = files.keys()
609 fl = files.keys()
610 fl.sort()
610 fl.sort()
611 for f in fl:
611 for f in fl:
612 full, fnode = files[f]
612 full, fnode = files[f]
613 if fnode:
613 if fnode:
614 continue
614 continue
615
615
616 yield {"parity": parity,
616 yield {"parity": parity,
617 "path": os.path.join(path, f),
617 "path": os.path.join(path, f),
618 "manifest": mnode,
618 "manifest": mnode,
619 "basename": f[:-1]}
619 "basename": f[:-1]}
620 parity = 1 - parity
620 parity = 1 - parity
621
621
622 yield self.t("manifest",
622 yield self.t("manifest",
623 manifest=mnode,
623 manifest=mnode,
624 rev=rev,
624 rev=rev,
625 node=hex(node),
625 node=hex(node),
626 path=path,
626 path=path,
627 up=up(path),
627 up=up(path),
628 fentries=filelist,
628 fentries=filelist,
629 dentries=dirlist,
629 dentries=dirlist,
630 archives=self.archivelist(hex(node)))
630 archives=self.archivelist(hex(node)))
631
631
632 def tags(self):
632 def tags(self):
633 cl = self.repo.changelog
633 cl = self.repo.changelog
634 mf = cl.read(cl.tip())[0]
634 mf = cl.read(cl.tip())[0]
635
635
636 i = self.repo.tagslist()
636 i = self.repo.tagslist()
637 i.reverse()
637 i.reverse()
638
638
639 def entries(**map):
639 def entries(**map):
640 parity = 0
640 parity = 0
641 for k,n in i:
641 for k,n in i:
642 yield {"parity": parity,
642 yield {"parity": parity,
643 "tag": k,
643 "tag": k,
644 "tagmanifest": hex(cl.read(n)[0]),
644 "tagmanifest": hex(cl.read(n)[0]),
645 "date": cl.read(n)[2],
645 "date": cl.read(n)[2],
646 "node": hex(n)}
646 "node": hex(n)}
647 parity = 1 - parity
647 parity = 1 - parity
648
648
649 yield self.t("tags",
649 yield self.t("tags",
650 manifest=hex(mf),
650 manifest=hex(mf),
651 entries=entries)
651 entries=entries)
652
652
653 def summary(self):
653 def summary(self):
654 cl = self.repo.changelog
654 cl = self.repo.changelog
655 mf = cl.read(cl.tip())[0]
655 mf = cl.read(cl.tip())[0]
656
656
657 i = self.repo.tagslist()
657 i = self.repo.tagslist()
658 i.reverse()
658 i.reverse()
659
659
660 def tagentries(**map):
660 def tagentries(**map):
661 parity = 0
661 parity = 0
662 count = 0
662 count = 0
663 for k,n in i:
663 for k,n in i:
664 if k == "tip": # skip tip
664 if k == "tip": # skip tip
665 continue;
665 continue;
666
666
667 count += 1
667 count += 1
668 if count > 10: # limit to 10 tags
668 if count > 10: # limit to 10 tags
669 break;
669 break;
670
670
671 c = cl.read(n)
671 c = cl.read(n)
672 m = c[0]
672 m = c[0]
673 t = c[2]
673 t = c[2]
674
674
675 yield self.t("tagentry",
675 yield self.t("tagentry",
676 parity = parity,
676 parity = parity,
677 tag = k,
677 tag = k,
678 node = hex(n),
678 node = hex(n),
679 date = t,
679 date = t,
680 tagmanifest = hex(m))
680 tagmanifest = hex(m))
681 parity = 1 - parity
681 parity = 1 - parity
682
682
683 def changelist(**map):
683 def changelist(**map):
684 parity = 0
684 parity = 0
685 cl = self.repo.changelog
685 cl = self.repo.changelog
686 l = [] # build a list in forward order for efficiency
686 l = [] # build a list in forward order for efficiency
687 for i in range(start, end):
687 for i in range(start, end):
688 n = cl.node(i)
688 n = cl.node(i)
689 changes = cl.read(n)
689 changes = cl.read(n)
690 hn = hex(n)
690 hn = hex(n)
691 t = changes[2]
691 t = changes[2]
692
692
693 l.insert(0, self.t(
693 l.insert(0, self.t(
694 'shortlogentry',
694 'shortlogentry',
695 parity = parity,
695 parity = parity,
696 author = changes[1],
696 author = changes[1],
697 manifest = hex(changes[0]),
697 manifest = hex(changes[0]),
698 desc = changes[4],
698 desc = changes[4],
699 date = t,
699 date = t,
700 rev = i,
700 rev = i,
701 node = hn))
701 node = hn))
702 parity = 1 - parity
702 parity = 1 - parity
703
703
704 yield l
704 yield l
705
705
706 cl = self.repo.changelog
706 cl = self.repo.changelog
707 mf = cl.read(cl.tip())[0]
707 mf = cl.read(cl.tip())[0]
708 count = cl.count()
708 count = cl.count()
709 start = max(0, count - self.maxchanges)
709 start = max(0, count - self.maxchanges)
710 end = min(count, start + self.maxchanges)
710 end = min(count, start + self.maxchanges)
711 pos = end - 1
711 pos = end - 1
712
712
713 yield self.t("summary",
713 yield self.t("summary",
714 desc = self.repo.ui.config("web", "description", "unknown"),
714 desc = self.repo.ui.config("web", "description", "unknown"),
715 owner = (self.repo.ui.config("ui", "username") or # preferred
715 owner = (self.repo.ui.config("ui", "username") or # preferred
716 self.repo.ui.config("web", "contact") or # deprecated
716 self.repo.ui.config("web", "contact") or # deprecated
717 self.repo.ui.config("web", "author", "unknown")), # also
717 self.repo.ui.config("web", "author", "unknown")), # also
718 lastchange = (0, 0), # FIXME
718 lastchange = (0, 0), # FIXME
719 manifest = hex(mf),
719 manifest = hex(mf),
720 tags = tagentries,
720 tags = tagentries,
721 shortlog = changelist)
721 shortlog = changelist)
722
722
723 def filediff(self, file, changeset):
723 def filediff(self, file, changeset):
724 cl = self.repo.changelog
724 cl = self.repo.changelog
725 n = self.repo.lookup(changeset)
725 n = self.repo.lookup(changeset)
726 changeset = hex(n)
726 changeset = hex(n)
727 p1 = cl.parents(n)[0]
727 p1 = cl.parents(n)[0]
728 cs = cl.read(n)
728 cs = cl.read(n)
729 mf = self.repo.manifest.read(cs[0])
729 mf = self.repo.manifest.read(cs[0])
730
730
731 def diff(**map):
731 def diff(**map):
732 yield self.diff(p1, n, file)
732 yield self.diff(p1, n, file)
733
733
734 yield self.t("filediff",
734 yield self.t("filediff",
735 file=file,
735 file=file,
736 filenode=hex(mf.get(file, nullid)),
736 filenode=hex(mf.get(file, nullid)),
737 node=changeset,
737 node=changeset,
738 rev=self.repo.changelog.rev(n),
738 rev=self.repo.changelog.rev(n),
739 parent=self.siblings(cl.parents(n), cl.rev),
739 parent=self.siblings(cl.parents(n), cl.rev),
740 child=self.siblings(cl.children(n), cl.rev),
740 child=self.siblings(cl.children(n), cl.rev),
741 diff=diff)
741 diff=diff)
742
742
743 def archive(self, req, cnode, type):
743 def archive(self, req, cnode, type):
744 cs = self.repo.changelog.read(cnode)
744 cs = self.repo.changelog.read(cnode)
745 mnode = cs[0]
745 mnode = cs[0]
746 mf = self.repo.manifest.read(mnode)
746 mf = self.repo.manifest.read(mnode)
747 rev = self.repo.manifest.rev(mnode)
747 rev = self.repo.manifest.rev(mnode)
748 reponame = re.sub(r"\W+", "-", self.reponame)
748 reponame = re.sub(r"\W+", "-", self.reponame)
749 name = "%s-%s/" % (reponame, short(cnode))
749 name = "%s-%s/" % (reponame, short(cnode))
750
750
751 files = mf.keys()
751 files = mf.keys()
752 files.sort()
752 files.sort()
753
753
754 if type == 'zip':
754 if type == 'zip':
755 tmp = tempfile.mkstemp()[1]
755 tmp = tempfile.mkstemp()[1]
756 try:
756 try:
757 zf = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED)
757 zf = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED)
758
758
759 for f in files:
759 for f in files:
760 zf.writestr(name + f, self.repo.file(f).read(mf[f]))
760 zf.writestr(name + f, self.repo.file(f).read(mf[f]))
761 zf.close()
761 zf.close()
762
762
763 f = open(tmp, 'r')
763 f = open(tmp, 'r')
764 req.httphdr('application/zip', name[:-1] + '.zip',
764 req.httphdr('application/zip', name[:-1] + '.zip',
765 os.path.getsize(tmp))
765 os.path.getsize(tmp))
766 req.write(f.read())
766 req.write(f.read())
767 f.close()
767 f.close()
768 finally:
768 finally:
769 os.unlink(tmp)
769 os.unlink(tmp)
770
770
771 else:
771 else:
772 tf = tarfile.TarFile.open(mode='w|' + type, fileobj=req.out)
772 tf = tarfile.TarFile.open(mode='w|' + type, fileobj=req.out)
773 mff = self.repo.manifest.readflags(mnode)
773 mff = self.repo.manifest.readflags(mnode)
774 mtime = int(time.time())
774 mtime = int(time.time())
775
775
776 if type == "gz":
776 if type == "gz":
777 encoding = "gzip"
777 encoding = "gzip"
778 else:
778 else:
779 encoding = "x-bzip2"
779 encoding = "x-bzip2"
780 req.header([('Content-type', 'application/x-tar'),
780 req.header([('Content-type', 'application/x-tar'),
781 ('Content-disposition', 'attachment; filename=%s%s%s' %
781 ('Content-disposition', 'attachment; filename=%s%s%s' %
782 (name[:-1], '.tar.', type)),
782 (name[:-1], '.tar.', type)),
783 ('Content-encoding', encoding)])
783 ('Content-encoding', encoding)])
784 for fname in files:
784 for fname in files:
785 rcont = self.repo.file(fname).read(mf[fname])
785 rcont = self.repo.file(fname).read(mf[fname])
786 finfo = tarfile.TarInfo(name + fname)
786 finfo = tarfile.TarInfo(name + fname)
787 finfo.mtime = mtime
787 finfo.mtime = mtime
788 finfo.size = len(rcont)
788 finfo.size = len(rcont)
789 finfo.mode = mff[fname] and 0755 or 0644
789 finfo.mode = mff[fname] and 0755 or 0644
790 tf.addfile(finfo, StringIO.StringIO(rcont))
790 tf.addfile(finfo, StringIO.StringIO(rcont))
791 tf.close()
791 tf.close()
792
792
793 # add tags to things
793 # add tags to things
794 # tags -> list of changesets corresponding to tags
794 # tags -> list of changesets corresponding to tags
795 # find tag, changeset, file
795 # find tag, changeset, file
796
796
797 def run(self, req=hgrequest()):
797 def run(self, req=hgrequest()):
798 def header(**map):
798 def header(**map):
799 yield self.t("header", **map)
799 yield self.t("header", **map)
800
800
801 def footer(**map):
801 def footer(**map):
802 yield self.t("footer", **map)
802 yield self.t("footer", **map)
803
803
804 def expand_form(form):
804 def expand_form(form):
805 shortcuts = {
805 shortcuts = {
806 'cl': [('cmd', ['changelog']), ('rev', None)],
806 'cl': [('cmd', ['changelog']), ('rev', None)],
807 'cs': [('cmd', ['changeset']), ('node', None)],
807 'cs': [('cmd', ['changeset']), ('node', None)],
808 'f': [('cmd', ['file']), ('filenode', None)],
808 'f': [('cmd', ['file']), ('filenode', None)],
809 'fl': [('cmd', ['filelog']), ('filenode', None)],
809 'fl': [('cmd', ['filelog']), ('filenode', None)],
810 'fd': [('cmd', ['filediff']), ('node', None)],
810 'fd': [('cmd', ['filediff']), ('node', None)],
811 'fa': [('cmd', ['annotate']), ('filenode', None)],
811 'fa': [('cmd', ['annotate']), ('filenode', None)],
812 'mf': [('cmd', ['manifest']), ('manifest', None)],
812 'mf': [('cmd', ['manifest']), ('manifest', None)],
813 'ca': [('cmd', ['archive']), ('node', None)],
813 'ca': [('cmd', ['archive']), ('node', None)],
814 'tags': [('cmd', ['tags'])],
814 'tags': [('cmd', ['tags'])],
815 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
815 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
816 }
816 }
817
817
818 for k in shortcuts.iterkeys():
818 for k in shortcuts.iterkeys():
819 if form.has_key(k):
819 if form.has_key(k):
820 for name, value in shortcuts[k]:
820 for name, value in shortcuts[k]:
821 if value is None:
821 if value is None:
822 value = form[k]
822 value = form[k]
823 form[name] = value
823 form[name] = value
824 del form[k]
824 del form[k]
825
825
826 self.refresh()
826 self.refresh()
827
827
828 expand_form(req.form)
828 expand_form(req.form)
829
829
830 t = self.repo.ui.config("web", "templates", templatepath())
830 t = self.repo.ui.config("web", "templates", templatepath())
831 m = os.path.join(t, "map")
831 m = os.path.join(t, "map")
832 style = self.repo.ui.config("web", "style", "")
832 style = self.repo.ui.config("web", "style", "")
833 if req.form.has_key('style'):
833 if req.form.has_key('style'):
834 style = req.form['style'][0]
834 style = req.form['style'][0]
835 if style:
835 if style:
836 b = os.path.basename("map-" + style)
836 b = os.path.basename("map-" + style)
837 p = os.path.join(t, b)
837 p = os.path.join(t, b)
838 if os.path.isfile(p):
838 if os.path.isfile(p):
839 m = p
839 m = p
840
840
841 port = req.env["SERVER_PORT"]
841 port = req.env["SERVER_PORT"]
842 port = port != "80" and (":" + port) or ""
842 port = port != "80" and (":" + port) or ""
843 uri = req.env["REQUEST_URI"]
843 uri = req.env["REQUEST_URI"]
844 if "?" in uri:
844 if "?" in uri:
845 uri = uri.split("?")[0]
845 uri = uri.split("?")[0]
846 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
846 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
847 if not self.reponame:
847 if not self.reponame:
848 self.reponame = (self.repo.ui.config("web", "name")
848 self.reponame = (self.repo.ui.config("web", "name")
849 or uri.strip('/') or self.repo.root)
849 or uri.strip('/') or self.repo.root)
850
850
851 self.t = templater(m, common_filters,
851 self.t = templater(m, common_filters,
852 {"url": url,
852 {"url": url,
853 "repo": self.reponame,
853 "repo": self.reponame,
854 "header": header,
854 "header": header,
855 "footer": footer,
855 "footer": footer,
856 })
856 })
857
857
858 if not req.form.has_key('cmd'):
858 if not req.form.has_key('cmd'):
859 req.form['cmd'] = [self.t.cache['default'],]
859 req.form['cmd'] = [self.t.cache['default'],]
860
860
861 if req.form['cmd'][0] == 'changelog':
861 if req.form['cmd'][0] == 'changelog':
862 c = self.repo.changelog.count() - 1
862 c = self.repo.changelog.count() - 1
863 hi = c
863 hi = c
864 if req.form.has_key('rev'):
864 if req.form.has_key('rev'):
865 hi = req.form['rev'][0]
865 hi = req.form['rev'][0]
866 try:
866 try:
867 hi = self.repo.changelog.rev(self.repo.lookup(hi))
867 hi = self.repo.changelog.rev(self.repo.lookup(hi))
868 except hg.RepoError:
868 except hg.RepoError:
869 req.write(self.search(hi))
869 req.write(self.search(hi))
870 return
870 return
871
871
872 req.write(self.changelog(hi))
872 req.write(self.changelog(hi))
873
873
874 elif req.form['cmd'][0] == 'changeset':
874 elif req.form['cmd'][0] == 'changeset':
875 req.write(self.changeset(req.form['node'][0]))
875 req.write(self.changeset(req.form['node'][0]))
876
876
877 elif req.form['cmd'][0] == 'manifest':
877 elif req.form['cmd'][0] == 'manifest':
878 req.write(self.manifest(req.form['manifest'][0], req.form['path'][0]))
878 req.write(self.manifest(req.form['manifest'][0], req.form['path'][0]))
879
879
880 elif req.form['cmd'][0] == 'tags':
880 elif req.form['cmd'][0] == 'tags':
881 req.write(self.tags())
881 req.write(self.tags())
882
882
883 elif req.form['cmd'][0] == 'summary':
883 elif req.form['cmd'][0] == 'summary':
884 req.write(self.summary())
884 req.write(self.summary())
885
885
886 elif req.form['cmd'][0] == 'filediff':
886 elif req.form['cmd'][0] == 'filediff':
887 req.write(self.filediff(req.form['file'][0], req.form['node'][0]))
887 req.write(self.filediff(req.form['file'][0], req.form['node'][0]))
888
888
889 elif req.form['cmd'][0] == 'file':
889 elif req.form['cmd'][0] == 'file':
890 req.write(self.filerevision(req.form['file'][0], req.form['filenode'][0]))
890 req.write(self.filerevision(req.form['file'][0], req.form['filenode'][0]))
891
891
892 elif req.form['cmd'][0] == 'annotate':
892 elif req.form['cmd'][0] == 'annotate':
893 req.write(self.fileannotate(req.form['file'][0], req.form['filenode'][0]))
893 req.write(self.fileannotate(req.form['file'][0], req.form['filenode'][0]))
894
894
895 elif req.form['cmd'][0] == 'filelog':
895 elif req.form['cmd'][0] == 'filelog':
896 req.write(self.filelog(req.form['file'][0], req.form['filenode'][0]))
896 req.write(self.filelog(req.form['file'][0], req.form['filenode'][0]))
897
897
898 elif req.form['cmd'][0] == 'heads':
898 elif req.form['cmd'][0] == 'heads':
899 req.httphdr("application/mercurial-0.1")
899 req.httphdr("application/mercurial-0.1")
900 h = self.repo.heads()
900 h = self.repo.heads()
901 req.write(" ".join(map(hex, h)) + "\n")
901 req.write(" ".join(map(hex, h)) + "\n")
902
902
903 elif req.form['cmd'][0] == 'branches':
903 elif req.form['cmd'][0] == 'branches':
904 req.httphdr("application/mercurial-0.1")
904 req.httphdr("application/mercurial-0.1")
905 nodes = []
905 nodes = []
906 if req.form.has_key('nodes'):
906 if req.form.has_key('nodes'):
907 nodes = map(bin, req.form['nodes'][0].split(" "))
907 nodes = map(bin, req.form['nodes'][0].split(" "))
908 for b in self.repo.branches(nodes):
908 for b in self.repo.branches(nodes):
909 req.write(" ".join(map(hex, b)) + "\n")
909 req.write(" ".join(map(hex, b)) + "\n")
910
910
911 elif req.form['cmd'][0] == 'between':
911 elif req.form['cmd'][0] == 'between':
912 req.httphdr("application/mercurial-0.1")
912 req.httphdr("application/mercurial-0.1")
913 nodes = []
913 nodes = []
914 if req.form.has_key('pairs'):
914 if req.form.has_key('pairs'):
915 pairs = [map(bin, p.split("-"))
915 pairs = [map(bin, p.split("-"))
916 for p in req.form['pairs'][0].split(" ")]
916 for p in req.form['pairs'][0].split(" ")]
917 for b in self.repo.between(pairs):
917 for b in self.repo.between(pairs):
918 req.write(" ".join(map(hex, b)) + "\n")
918 req.write(" ".join(map(hex, b)) + "\n")
919
919
920 elif req.form['cmd'][0] == 'changegroup':
920 elif req.form['cmd'][0] == 'changegroup':
921 req.httphdr("application/mercurial-0.1")
921 req.httphdr("application/mercurial-0.1")
922 nodes = []
922 nodes = []
923 if not self.allowpull:
923 if not self.allowpull:
924 return
924 return
925
925
926 if req.form.has_key('roots'):
926 if req.form.has_key('roots'):
927 nodes = map(bin, req.form['roots'][0].split(" "))
927 nodes = map(bin, req.form['roots'][0].split(" "))
928
928
929 z = zlib.compressobj()
929 z = zlib.compressobj()
930 f = self.repo.changegroup(nodes)
930 f = self.repo.changegroup(nodes)
931 while 1:
931 while 1:
932 chunk = f.read(4096)
932 chunk = f.read(4096)
933 if not chunk:
933 if not chunk:
934 break
934 break
935 req.write(z.compress(chunk))
935 req.write(z.compress(chunk))
936
936
937 req.write(z.flush())
937 req.write(z.flush())
938
938
939 elif req.form['cmd'][0] == 'archive':
939 elif req.form['cmd'][0] == 'archive':
940 changeset = self.repo.lookup(req.form['node'][0])
940 changeset = self.repo.lookup(req.form['node'][0])
941 type = req.form['type'][0]
941 type = req.form['type'][0]
942 if (type in self.archives and
942 if (type in self.archives and
943 self.repo.ui.configbool("web", "allow" + type, False)):
943 self.repo.ui.configbool("web", "allow" + type, False)):
944 self.archive(req, changeset, type)
944 self.archive(req, changeset, type)
945 return
945 return
946
946
947 req.write(self.t("error"))
947 req.write(self.t("error"))
948
948
949 else:
949 else:
950 req.write(self.t("error"))
950 req.write(self.t("error"))
951
951
952 def create_server(repo):
952 def create_server(repo):
953
953
954 def openlog(opt, default):
954 def openlog(opt, default):
955 if opt and opt != '-':
955 if opt and opt != '-':
956 return open(opt, 'w')
956 return open(opt, 'w')
957 return default
957 return default
958
958
959 address = repo.ui.config("web", "address", "")
959 address = repo.ui.config("web", "address", "")
960 port = int(repo.ui.config("web", "port", 8000))
960 port = int(repo.ui.config("web", "port", 8000))
961 use_ipv6 = repo.ui.configbool("web", "ipv6")
961 use_ipv6 = repo.ui.configbool("web", "ipv6")
962 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
962 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
963 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
963 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
964
964
965 class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
965 class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
966 address_family = getattr(socket, 'AF_INET6', None)
966 address_family = getattr(socket, 'AF_INET6', None)
967
967
968 def __init__(self, *args, **kwargs):
968 def __init__(self, *args, **kwargs):
969 if self.address_family is None:
969 if self.address_family is None:
970 raise hg.RepoError(_('IPv6 not available on this system'))
970 raise hg.RepoError(_('IPv6 not available on this system'))
971 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
971 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
972
972
973 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
973 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
974 def log_error(self, format, *args):
974 def log_error(self, format, *args):
975 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
975 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
976 self.log_date_time_string(),
976 self.log_date_time_string(),
977 format % args))
977 format % args))
978
978
979 def log_message(self, format, *args):
979 def log_message(self, format, *args):
980 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
980 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
981 self.log_date_time_string(),
981 self.log_date_time_string(),
982 format % args))
982 format % args))
983
983
984 def do_POST(self):
984 def do_POST(self):
985 try:
985 try:
986 self.do_hgweb()
986 self.do_hgweb()
987 except socket.error, inst:
987 except socket.error, inst:
988 if inst[0] != errno.EPIPE:
988 if inst[0] != errno.EPIPE:
989 raise
989 raise
990
990
991 def do_GET(self):
991 def do_GET(self):
992 self.do_POST()
992 self.do_POST()
993
993
994 def do_hgweb(self):
994 def do_hgweb(self):
995 query = ""
995 query = ""
996 p = self.path.find("?")
996 p = self.path.find("?")
997 if p:
997 if p:
998 query = self.path[p + 1:]
998 query = self.path[p + 1:]
999 query = query.replace('+', ' ')
999 query = query.replace('+', ' ')
1000
1000
1001 env = {}
1001 env = {}
1002 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
1002 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
1003 env['REQUEST_METHOD'] = self.command
1003 env['REQUEST_METHOD'] = self.command
1004 env['SERVER_NAME'] = self.server.server_name
1004 env['SERVER_NAME'] = self.server.server_name
1005 env['SERVER_PORT'] = str(self.server.server_port)
1005 env['SERVER_PORT'] = str(self.server.server_port)
1006 env['REQUEST_URI'] = "/"
1006 env['REQUEST_URI'] = "/"
1007 if query:
1007 if query:
1008 env['QUERY_STRING'] = query
1008 env['QUERY_STRING'] = query
1009 host = self.address_string()
1009 host = self.address_string()
1010 if host != self.client_address[0]:
1010 if host != self.client_address[0]:
1011 env['REMOTE_HOST'] = host
1011 env['REMOTE_HOST'] = host
1012 env['REMOTE_ADDR'] = self.client_address[0]
1012 env['REMOTE_ADDR'] = self.client_address[0]
1013
1013
1014 if self.headers.typeheader is None:
1014 if self.headers.typeheader is None:
1015 env['CONTENT_TYPE'] = self.headers.type
1015 env['CONTENT_TYPE'] = self.headers.type
1016 else:
1016 else:
1017 env['CONTENT_TYPE'] = self.headers.typeheader
1017 env['CONTENT_TYPE'] = self.headers.typeheader
1018 length = self.headers.getheader('content-length')
1018 length = self.headers.getheader('content-length')
1019 if length:
1019 if length:
1020 env['CONTENT_LENGTH'] = length
1020 env['CONTENT_LENGTH'] = length
1021 accept = []
1021 accept = []
1022 for line in self.headers.getallmatchingheaders('accept'):
1022 for line in self.headers.getallmatchingheaders('accept'):
1023 if line[:1] in "\t\n\r ":
1023 if line[:1] in "\t\n\r ":
1024 accept.append(line.strip())
1024 accept.append(line.strip())
1025 else:
1025 else:
1026 accept = accept + line[7:].split(',')
1026 accept = accept + line[7:].split(',')
1027 env['HTTP_ACCEPT'] = ','.join(accept)
1027 env['HTTP_ACCEPT'] = ','.join(accept)
1028
1028
1029 req = hgrequest(self.rfile, self.wfile, env)
1029 req = hgrequest(self.rfile, self.wfile, env)
1030 self.send_response(200, "Script output follows")
1030 self.send_response(200, "Script output follows")
1031 hg.run(req)
1031 hg.run(req)
1032
1032
1033 hg = hgweb(repo)
1033 hg = hgweb(repo)
1034 if use_ipv6:
1034 if use_ipv6:
1035 return IPv6HTTPServer((address, port), hgwebhandler)
1035 return IPv6HTTPServer((address, port), hgwebhandler)
1036 else:
1036 else:
1037 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
1037 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
1038
1038
1039 # This is a stopgap
1039 # This is a stopgap
1040 class hgwebdir(object):
1040 class hgwebdir(object):
1041 def __init__(self, config):
1041 def __init__(self, config):
1042 def cleannames(items):
1042 def cleannames(items):
1043 return [(name.strip('/'), path) for name, path in items]
1043 return [(name.strip('/'), path) for name, path in items]
1044
1044
1045 if type(config) == type([]):
1045 if type(config) == type([]):
1046 self.repos = cleannames(config)
1046 self.repos = cleannames(config)
1047 elif type(config) == type({}):
1047 elif type(config) == type({}):
1048 self.repos = cleannames(config.items())
1048 self.repos = cleannames(config.items())
1049 self.repos.sort()
1049 self.repos.sort()
1050 else:
1050 else:
1051 cp = ConfigParser.SafeConfigParser()
1051 cp = ConfigParser.SafeConfigParser()
1052 cp.read(config)
1052 cp.read(config)
1053 self.repos = cleannames(cp.items("paths"))
1053 self.repos = cleannames(cp.items("paths"))
1054 self.repos.sort()
1054 self.repos.sort()
1055
1055
1056 def run(self, req=hgrequest()):
1056 def run(self, req=hgrequest()):
1057 def header(**map):
1057 def header(**map):
1058 yield tmpl("header", **map)
1058 yield tmpl("header", **map)
1059
1059
1060 def footer(**map):
1060 def footer(**map):
1061 yield tmpl("footer", **map)
1061 yield tmpl("footer", **map)
1062
1062
1063 m = os.path.join(templatepath(), "map")
1063 m = os.path.join(templatepath(), "map")
1064 tmpl = templater(m, common_filters,
1064 tmpl = templater(m, common_filters,
1065 {"header": header, "footer": footer})
1065 {"header": header, "footer": footer})
1066
1066
1067 def entries(**map):
1067 def entries(**map):
1068 parity = 0
1068 parity = 0
1069 for name, path in self.repos:
1069 for name, path in self.repos:
1070 u = ui.ui()
1070 u = ui.ui()
1071 try:
1071 try:
1072 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
1072 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
1073 except IOError:
1073 except IOError:
1074 pass
1074 pass
1075 get = u.config
1075 get = u.config
1076
1076
1077 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
1077 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
1078 .replace("//", "/"))
1078 .replace("//", "/"))
1079
1079
1080 # update time with local timezone
1080 # update time with local timezone
1081 try:
1081 try:
1082 d = (get_mtime(path), util.makedate()[1])
1082 d = (get_mtime(path), util.makedate()[1])
1083 except OSError:
1083 except OSError:
1084 continue
1084 continue
1085
1085
1086 yield dict(contact=(get("ui", "username") or # preferred
1086 yield dict(contact=(get("ui", "username") or # preferred
1087 get("web", "contact") or # deprecated
1087 get("web", "contact") or # deprecated
1088 get("web", "author", "unknown")), # also
1088 get("web", "author", "unknown")), # also
1089 name=get("web", "name", name),
1089 name=get("web", "name", name),
1090 url=url,
1090 url=url,
1091 parity=parity,
1091 parity=parity,
1092 shortdesc=get("web", "description", "unknown"),
1092 shortdesc=get("web", "description", "unknown"),
1093 lastupdate=d)
1093 lastupdate=d)
1094
1094
1095 parity = 1 - parity
1095 parity = 1 - parity
1096
1096
1097 virtual = req.env.get("PATH_INFO", "").strip('/')
1097 virtual = req.env.get("PATH_INFO", "").strip('/')
1098 if virtual:
1098 if virtual:
1099 real = dict(self.repos).get(virtual)
1099 real = dict(self.repos).get(virtual)
1100 if real:
1100 if real:
1101 try:
1101 try:
1102 hgweb(real).run(req)
1102 hgweb(real).run(req)
1103 except IOError, inst:
1103 except IOError, inst:
1104 req.write(tmpl("error", error=inst.strerror))
1104 req.write(tmpl("error", error=inst.strerror))
1105 except hg.RepoError, inst:
1105 except hg.RepoError, inst:
1106 req.write(tmpl("error", error=str(inst)))
1106 req.write(tmpl("error", error=str(inst)))
1107 else:
1107 else:
1108 req.write(tmpl("notfound", repo=virtual))
1108 req.write(tmpl("notfound", repo=virtual))
1109 else:
1109 else:
1110 req.write(tmpl("index", entries=entries))
1110 req.write(tmpl("index", entries=entries))
General Comments 0
You need to be logged in to leave comments. Login now