##// END OF EJS Templates
Final stage of the hgweb split up....
Eric Hopper -
r2356:2db831b3 default
parent child Browse files
Show More
@@ -1,3479 +1,3481 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 templater bundlerepo")
12 demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo")
13 demandload(globals(), "fnmatch hgweb mdiff random signal tempfile time")
13 demandload(globals(), "fnmatch mdiff random signal tempfile time")
14 demandload(globals(), "traceback errno socket version struct atexit sets bz2")
14 demandload(globals(), "traceback errno socket version struct atexit sets bz2")
15 demandload(globals(), "archival changegroup")
15 demandload(globals(), "archival changegroup")
16 demandload(globals(), "mercurial.hgweb.server:create_server")
17 demandload(globals(), "mercurial.hgweb:hgweb,hgwebdir")
16
18
17 class UnknownCommand(Exception):
19 class UnknownCommand(Exception):
18 """Exception raised if command is not in the command table."""
20 """Exception raised if command is not in the command table."""
19 class AmbiguousCommand(Exception):
21 class AmbiguousCommand(Exception):
20 """Exception raised if command shortcut matches more than one command."""
22 """Exception raised if command shortcut matches more than one command."""
21
23
22 def bail_if_changed(repo):
24 def bail_if_changed(repo):
23 modified, added, removed, deleted, unknown = repo.changes()
25 modified, added, removed, deleted, unknown = repo.changes()
24 if modified or added or removed or deleted:
26 if modified or added or removed or deleted:
25 raise util.Abort(_("outstanding uncommitted changes"))
27 raise util.Abort(_("outstanding uncommitted changes"))
26
28
27 def filterfiles(filters, files):
29 def filterfiles(filters, files):
28 l = [x for x in files if x in filters]
30 l = [x for x in files if x in filters]
29
31
30 for t in filters:
32 for t in filters:
31 if t and t[-1] != "/":
33 if t and t[-1] != "/":
32 t += "/"
34 t += "/"
33 l += [x for x in files if x.startswith(t)]
35 l += [x for x in files if x.startswith(t)]
34 return l
36 return l
35
37
36 def relpath(repo, args):
38 def relpath(repo, args):
37 cwd = repo.getcwd()
39 cwd = repo.getcwd()
38 if cwd:
40 if cwd:
39 return [util.normpath(os.path.join(cwd, x)) for x in args]
41 return [util.normpath(os.path.join(cwd, x)) for x in args]
40 return args
42 return args
41
43
42 def matchpats(repo, pats=[], opts={}, head=''):
44 def matchpats(repo, pats=[], opts={}, head=''):
43 cwd = repo.getcwd()
45 cwd = repo.getcwd()
44 if not pats and cwd:
46 if not pats and cwd:
45 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
47 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
46 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
48 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
47 cwd = ''
49 cwd = ''
48 return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
50 return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
49 opts.get('exclude'), head)
51 opts.get('exclude'), head)
50
52
51 def makewalk(repo, pats, opts, node=None, head='', badmatch=None):
53 def makewalk(repo, pats, opts, node=None, head='', badmatch=None):
52 files, matchfn, anypats = matchpats(repo, pats, opts, head)
54 files, matchfn, anypats = matchpats(repo, pats, opts, head)
53 exact = dict(zip(files, files))
55 exact = dict(zip(files, files))
54 def walk():
56 def walk():
55 for src, fn in repo.walk(node=node, files=files, match=matchfn,
57 for src, fn in repo.walk(node=node, files=files, match=matchfn,
56 badmatch=badmatch):
58 badmatch=badmatch):
57 yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
59 yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
58 return files, matchfn, walk()
60 return files, matchfn, walk()
59
61
60 def walk(repo, pats, opts, node=None, head='', badmatch=None):
62 def walk(repo, pats, opts, node=None, head='', badmatch=None):
61 files, matchfn, results = makewalk(repo, pats, opts, node, head, badmatch)
63 files, matchfn, results = makewalk(repo, pats, opts, node, head, badmatch)
62 for r in results:
64 for r in results:
63 yield r
65 yield r
64
66
65 def walkchangerevs(ui, repo, pats, opts):
67 def walkchangerevs(ui, repo, pats, opts):
66 '''Iterate over files and the revs they changed in.
68 '''Iterate over files and the revs they changed in.
67
69
68 Callers most commonly need to iterate backwards over the history
70 Callers most commonly need to iterate backwards over the history
69 it is interested in. Doing so has awful (quadratic-looking)
71 it is interested in. Doing so has awful (quadratic-looking)
70 performance, so we use iterators in a "windowed" way.
72 performance, so we use iterators in a "windowed" way.
71
73
72 We walk a window of revisions in the desired order. Within the
74 We walk a window of revisions in the desired order. Within the
73 window, we first walk forwards to gather data, then in the desired
75 window, we first walk forwards to gather data, then in the desired
74 order (usually backwards) to display it.
76 order (usually backwards) to display it.
75
77
76 This function returns an (iterator, getchange, matchfn) tuple. The
78 This function returns an (iterator, getchange, matchfn) tuple. The
77 getchange function returns the changelog entry for a numeric
79 getchange function returns the changelog entry for a numeric
78 revision. The iterator yields 3-tuples. They will be of one of
80 revision. The iterator yields 3-tuples. They will be of one of
79 the following forms:
81 the following forms:
80
82
81 "window", incrementing, lastrev: stepping through a window,
83 "window", incrementing, lastrev: stepping through a window,
82 positive if walking forwards through revs, last rev in the
84 positive if walking forwards through revs, last rev in the
83 sequence iterated over - use to reset state for the current window
85 sequence iterated over - use to reset state for the current window
84
86
85 "add", rev, fns: out-of-order traversal of the given file names
87 "add", rev, fns: out-of-order traversal of the given file names
86 fns, which changed during revision rev - use to gather data for
88 fns, which changed during revision rev - use to gather data for
87 possible display
89 possible display
88
90
89 "iter", rev, None: in-order traversal of the revs earlier iterated
91 "iter", rev, None: in-order traversal of the revs earlier iterated
90 over with "add" - use to display data'''
92 over with "add" - use to display data'''
91
93
92 def increasing_windows(start, end, windowsize=8, sizelimit=512):
94 def increasing_windows(start, end, windowsize=8, sizelimit=512):
93 if start < end:
95 if start < end:
94 while start < end:
96 while start < end:
95 yield start, min(windowsize, end-start)
97 yield start, min(windowsize, end-start)
96 start += windowsize
98 start += windowsize
97 if windowsize < sizelimit:
99 if windowsize < sizelimit:
98 windowsize *= 2
100 windowsize *= 2
99 else:
101 else:
100 while start > end:
102 while start > end:
101 yield start, min(windowsize, start-end-1)
103 yield start, min(windowsize, start-end-1)
102 start -= windowsize
104 start -= windowsize
103 if windowsize < sizelimit:
105 if windowsize < sizelimit:
104 windowsize *= 2
106 windowsize *= 2
105
107
106
108
107 files, matchfn, anypats = matchpats(repo, pats, opts)
109 files, matchfn, anypats = matchpats(repo, pats, opts)
108
110
109 if repo.changelog.count() == 0:
111 if repo.changelog.count() == 0:
110 return [], False, matchfn
112 return [], False, matchfn
111
113
112 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
114 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
113 wanted = {}
115 wanted = {}
114 slowpath = anypats
116 slowpath = anypats
115 fncache = {}
117 fncache = {}
116
118
117 chcache = {}
119 chcache = {}
118 def getchange(rev):
120 def getchange(rev):
119 ch = chcache.get(rev)
121 ch = chcache.get(rev)
120 if ch is None:
122 if ch is None:
121 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
123 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
122 return ch
124 return ch
123
125
124 if not slowpath and not files:
126 if not slowpath and not files:
125 # No files, no patterns. Display all revs.
127 # No files, no patterns. Display all revs.
126 wanted = dict(zip(revs, revs))
128 wanted = dict(zip(revs, revs))
127 if not slowpath:
129 if not slowpath:
128 # Only files, no patterns. Check the history of each file.
130 # Only files, no patterns. Check the history of each file.
129 def filerevgen(filelog):
131 def filerevgen(filelog):
130 for i, window in increasing_windows(filelog.count()-1, -1):
132 for i, window in increasing_windows(filelog.count()-1, -1):
131 revs = []
133 revs = []
132 for j in xrange(i - window, i + 1):
134 for j in xrange(i - window, i + 1):
133 revs.append(filelog.linkrev(filelog.node(j)))
135 revs.append(filelog.linkrev(filelog.node(j)))
134 revs.reverse()
136 revs.reverse()
135 for rev in revs:
137 for rev in revs:
136 yield rev
138 yield rev
137
139
138 minrev, maxrev = min(revs), max(revs)
140 minrev, maxrev = min(revs), max(revs)
139 for file_ in files:
141 for file_ in files:
140 filelog = repo.file(file_)
142 filelog = repo.file(file_)
141 # A zero count may be a directory or deleted file, so
143 # A zero count may be a directory or deleted file, so
142 # try to find matching entries on the slow path.
144 # try to find matching entries on the slow path.
143 if filelog.count() == 0:
145 if filelog.count() == 0:
144 slowpath = True
146 slowpath = True
145 break
147 break
146 for rev in filerevgen(filelog):
148 for rev in filerevgen(filelog):
147 if rev <= maxrev:
149 if rev <= maxrev:
148 if rev < minrev:
150 if rev < minrev:
149 break
151 break
150 fncache.setdefault(rev, [])
152 fncache.setdefault(rev, [])
151 fncache[rev].append(file_)
153 fncache[rev].append(file_)
152 wanted[rev] = 1
154 wanted[rev] = 1
153 if slowpath:
155 if slowpath:
154 # The slow path checks files modified in every changeset.
156 # The slow path checks files modified in every changeset.
155 def changerevgen():
157 def changerevgen():
156 for i, window in increasing_windows(repo.changelog.count()-1, -1):
158 for i, window in increasing_windows(repo.changelog.count()-1, -1):
157 for j in xrange(i - window, i + 1):
159 for j in xrange(i - window, i + 1):
158 yield j, getchange(j)[3]
160 yield j, getchange(j)[3]
159
161
160 for rev, changefiles in changerevgen():
162 for rev, changefiles in changerevgen():
161 matches = filter(matchfn, changefiles)
163 matches = filter(matchfn, changefiles)
162 if matches:
164 if matches:
163 fncache[rev] = matches
165 fncache[rev] = matches
164 wanted[rev] = 1
166 wanted[rev] = 1
165
167
166 def iterate():
168 def iterate():
167 for i, window in increasing_windows(0, len(revs)):
169 for i, window in increasing_windows(0, len(revs)):
168 yield 'window', revs[0] < revs[-1], revs[-1]
170 yield 'window', revs[0] < revs[-1], revs[-1]
169 nrevs = [rev for rev in revs[i:i+window]
171 nrevs = [rev for rev in revs[i:i+window]
170 if rev in wanted]
172 if rev in wanted]
171 srevs = list(nrevs)
173 srevs = list(nrevs)
172 srevs.sort()
174 srevs.sort()
173 for rev in srevs:
175 for rev in srevs:
174 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
176 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
175 yield 'add', rev, fns
177 yield 'add', rev, fns
176 for rev in nrevs:
178 for rev in nrevs:
177 yield 'iter', rev, None
179 yield 'iter', rev, None
178 return iterate(), getchange, matchfn
180 return iterate(), getchange, matchfn
179
181
180 revrangesep = ':'
182 revrangesep = ':'
181
183
182 def revfix(repo, val, defval):
184 def revfix(repo, val, defval):
183 '''turn user-level id of changeset into rev number.
185 '''turn user-level id of changeset into rev number.
184 user-level id can be tag, changeset, rev number, or negative rev
186 user-level id can be tag, changeset, rev number, or negative rev
185 number relative to number of revs (-1 is tip, etc).'''
187 number relative to number of revs (-1 is tip, etc).'''
186 if not val:
188 if not val:
187 return defval
189 return defval
188 try:
190 try:
189 num = int(val)
191 num = int(val)
190 if str(num) != val:
192 if str(num) != val:
191 raise ValueError
193 raise ValueError
192 if num < 0:
194 if num < 0:
193 num += repo.changelog.count()
195 num += repo.changelog.count()
194 if num < 0:
196 if num < 0:
195 num = 0
197 num = 0
196 elif num >= repo.changelog.count():
198 elif num >= repo.changelog.count():
197 raise ValueError
199 raise ValueError
198 except ValueError:
200 except ValueError:
199 try:
201 try:
200 num = repo.changelog.rev(repo.lookup(val))
202 num = repo.changelog.rev(repo.lookup(val))
201 except KeyError:
203 except KeyError:
202 raise util.Abort(_('invalid revision identifier %s'), val)
204 raise util.Abort(_('invalid revision identifier %s'), val)
203 return num
205 return num
204
206
205 def revpair(ui, repo, revs):
207 def revpair(ui, repo, revs):
206 '''return pair of nodes, given list of revisions. second item can
208 '''return pair of nodes, given list of revisions. second item can
207 be None, meaning use working dir.'''
209 be None, meaning use working dir.'''
208 if not revs:
210 if not revs:
209 return repo.dirstate.parents()[0], None
211 return repo.dirstate.parents()[0], None
210 end = None
212 end = None
211 if len(revs) == 1:
213 if len(revs) == 1:
212 start = revs[0]
214 start = revs[0]
213 if revrangesep in start:
215 if revrangesep in start:
214 start, end = start.split(revrangesep, 1)
216 start, end = start.split(revrangesep, 1)
215 start = revfix(repo, start, 0)
217 start = revfix(repo, start, 0)
216 end = revfix(repo, end, repo.changelog.count() - 1)
218 end = revfix(repo, end, repo.changelog.count() - 1)
217 else:
219 else:
218 start = revfix(repo, start, None)
220 start = revfix(repo, start, None)
219 elif len(revs) == 2:
221 elif len(revs) == 2:
220 if revrangesep in revs[0] or revrangesep in revs[1]:
222 if revrangesep in revs[0] or revrangesep in revs[1]:
221 raise util.Abort(_('too many revisions specified'))
223 raise util.Abort(_('too many revisions specified'))
222 start = revfix(repo, revs[0], None)
224 start = revfix(repo, revs[0], None)
223 end = revfix(repo, revs[1], None)
225 end = revfix(repo, revs[1], None)
224 else:
226 else:
225 raise util.Abort(_('too many revisions specified'))
227 raise util.Abort(_('too many revisions specified'))
226 if end is not None: end = repo.lookup(str(end))
228 if end is not None: end = repo.lookup(str(end))
227 return repo.lookup(str(start)), end
229 return repo.lookup(str(start)), end
228
230
229 def revrange(ui, repo, revs):
231 def revrange(ui, repo, revs):
230 """Yield revision as strings from a list of revision specifications."""
232 """Yield revision as strings from a list of revision specifications."""
231 seen = {}
233 seen = {}
232 for spec in revs:
234 for spec in revs:
233 if spec.find(revrangesep) >= 0:
235 if spec.find(revrangesep) >= 0:
234 start, end = spec.split(revrangesep, 1)
236 start, end = spec.split(revrangesep, 1)
235 start = revfix(repo, start, 0)
237 start = revfix(repo, start, 0)
236 end = revfix(repo, end, repo.changelog.count() - 1)
238 end = revfix(repo, end, repo.changelog.count() - 1)
237 step = start > end and -1 or 1
239 step = start > end and -1 or 1
238 for rev in xrange(start, end+step, step):
240 for rev in xrange(start, end+step, step):
239 if rev in seen:
241 if rev in seen:
240 continue
242 continue
241 seen[rev] = 1
243 seen[rev] = 1
242 yield str(rev)
244 yield str(rev)
243 else:
245 else:
244 rev = revfix(repo, spec, None)
246 rev = revfix(repo, spec, None)
245 if rev in seen:
247 if rev in seen:
246 continue
248 continue
247 seen[rev] = 1
249 seen[rev] = 1
248 yield str(rev)
250 yield str(rev)
249
251
250 def make_filename(repo, r, pat, node=None,
252 def make_filename(repo, r, pat, node=None,
251 total=None, seqno=None, revwidth=None, pathname=None):
253 total=None, seqno=None, revwidth=None, pathname=None):
252 node_expander = {
254 node_expander = {
253 'H': lambda: hex(node),
255 'H': lambda: hex(node),
254 'R': lambda: str(r.rev(node)),
256 'R': lambda: str(r.rev(node)),
255 'h': lambda: short(node),
257 'h': lambda: short(node),
256 }
258 }
257 expander = {
259 expander = {
258 '%': lambda: '%',
260 '%': lambda: '%',
259 'b': lambda: os.path.basename(repo.root),
261 'b': lambda: os.path.basename(repo.root),
260 }
262 }
261
263
262 try:
264 try:
263 if node:
265 if node:
264 expander.update(node_expander)
266 expander.update(node_expander)
265 if node and revwidth is not None:
267 if node and revwidth is not None:
266 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
268 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
267 if total is not None:
269 if total is not None:
268 expander['N'] = lambda: str(total)
270 expander['N'] = lambda: str(total)
269 if seqno is not None:
271 if seqno is not None:
270 expander['n'] = lambda: str(seqno)
272 expander['n'] = lambda: str(seqno)
271 if total is not None and seqno is not None:
273 if total is not None and seqno is not None:
272 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
274 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
273 if pathname is not None:
275 if pathname is not None:
274 expander['s'] = lambda: os.path.basename(pathname)
276 expander['s'] = lambda: os.path.basename(pathname)
275 expander['d'] = lambda: os.path.dirname(pathname) or '.'
277 expander['d'] = lambda: os.path.dirname(pathname) or '.'
276 expander['p'] = lambda: pathname
278 expander['p'] = lambda: pathname
277
279
278 newname = []
280 newname = []
279 patlen = len(pat)
281 patlen = len(pat)
280 i = 0
282 i = 0
281 while i < patlen:
283 while i < patlen:
282 c = pat[i]
284 c = pat[i]
283 if c == '%':
285 if c == '%':
284 i += 1
286 i += 1
285 c = pat[i]
287 c = pat[i]
286 c = expander[c]()
288 c = expander[c]()
287 newname.append(c)
289 newname.append(c)
288 i += 1
290 i += 1
289 return ''.join(newname)
291 return ''.join(newname)
290 except KeyError, inst:
292 except KeyError, inst:
291 raise util.Abort(_("invalid format spec '%%%s' in output file name"),
293 raise util.Abort(_("invalid format spec '%%%s' in output file name"),
292 inst.args[0])
294 inst.args[0])
293
295
294 def make_file(repo, r, pat, node=None,
296 def make_file(repo, r, pat, node=None,
295 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
297 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
296 if not pat or pat == '-':
298 if not pat or pat == '-':
297 return 'w' in mode and sys.stdout or sys.stdin
299 return 'w' in mode and sys.stdout or sys.stdin
298 if hasattr(pat, 'write') and 'w' in mode:
300 if hasattr(pat, 'write') and 'w' in mode:
299 return pat
301 return pat
300 if hasattr(pat, 'read') and 'r' in mode:
302 if hasattr(pat, 'read') and 'r' in mode:
301 return pat
303 return pat
302 return open(make_filename(repo, r, pat, node, total, seqno, revwidth,
304 return open(make_filename(repo, r, pat, node, total, seqno, revwidth,
303 pathname),
305 pathname),
304 mode)
306 mode)
305
307
306 def write_bundle(cg, filename=None, compress=True):
308 def write_bundle(cg, filename=None, compress=True):
307 """Write a bundle file and return its filename.
309 """Write a bundle file and return its filename.
308
310
309 Existing files will not be overwritten.
311 Existing files will not be overwritten.
310 If no filename is specified, a temporary file is created.
312 If no filename is specified, a temporary file is created.
311 bz2 compression can be turned off.
313 bz2 compression can be turned off.
312 The bundle file will be deleted in case of errors.
314 The bundle file will be deleted in case of errors.
313 """
315 """
314 class nocompress(object):
316 class nocompress(object):
315 def compress(self, x):
317 def compress(self, x):
316 return x
318 return x
317 def flush(self):
319 def flush(self):
318 return ""
320 return ""
319
321
320 fh = None
322 fh = None
321 cleanup = None
323 cleanup = None
322 try:
324 try:
323 if filename:
325 if filename:
324 if os.path.exists(filename):
326 if os.path.exists(filename):
325 raise util.Abort(_("file '%s' already exists"), filename)
327 raise util.Abort(_("file '%s' already exists"), filename)
326 fh = open(filename, "wb")
328 fh = open(filename, "wb")
327 else:
329 else:
328 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
330 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
329 fh = os.fdopen(fd, "wb")
331 fh = os.fdopen(fd, "wb")
330 cleanup = filename
332 cleanup = filename
331
333
332 if compress:
334 if compress:
333 fh.write("HG10")
335 fh.write("HG10")
334 z = bz2.BZ2Compressor(9)
336 z = bz2.BZ2Compressor(9)
335 else:
337 else:
336 fh.write("HG10UN")
338 fh.write("HG10UN")
337 z = nocompress()
339 z = nocompress()
338 # parse the changegroup data, otherwise we will block
340 # parse the changegroup data, otherwise we will block
339 # in case of sshrepo because we don't know the end of the stream
341 # in case of sshrepo because we don't know the end of the stream
340
342
341 # an empty chunkiter is the end of the changegroup
343 # an empty chunkiter is the end of the changegroup
342 empty = False
344 empty = False
343 while not empty:
345 while not empty:
344 empty = True
346 empty = True
345 for chunk in changegroup.chunkiter(cg):
347 for chunk in changegroup.chunkiter(cg):
346 empty = False
348 empty = False
347 fh.write(z.compress(changegroup.genchunk(chunk)))
349 fh.write(z.compress(changegroup.genchunk(chunk)))
348 fh.write(z.compress(changegroup.closechunk()))
350 fh.write(z.compress(changegroup.closechunk()))
349 fh.write(z.flush())
351 fh.write(z.flush())
350 cleanup = None
352 cleanup = None
351 return filename
353 return filename
352 finally:
354 finally:
353 if fh is not None:
355 if fh is not None:
354 fh.close()
356 fh.close()
355 if cleanup is not None:
357 if cleanup is not None:
356 os.unlink(cleanup)
358 os.unlink(cleanup)
357
359
358 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
360 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
359 changes=None, text=False, opts={}):
361 changes=None, text=False, opts={}):
360 if not node1:
362 if not node1:
361 node1 = repo.dirstate.parents()[0]
363 node1 = repo.dirstate.parents()[0]
362 # reading the data for node1 early allows it to play nicely
364 # reading the data for node1 early allows it to play nicely
363 # with repo.changes and the revlog cache.
365 # with repo.changes and the revlog cache.
364 change = repo.changelog.read(node1)
366 change = repo.changelog.read(node1)
365 mmap = repo.manifest.read(change[0])
367 mmap = repo.manifest.read(change[0])
366 date1 = util.datestr(change[2])
368 date1 = util.datestr(change[2])
367
369
368 if not changes:
370 if not changes:
369 changes = repo.changes(node1, node2, files, match=match)
371 changes = repo.changes(node1, node2, files, match=match)
370 modified, added, removed, deleted, unknown = changes
372 modified, added, removed, deleted, unknown = changes
371 if files:
373 if files:
372 modified, added, removed = map(lambda x: filterfiles(files, x),
374 modified, added, removed = map(lambda x: filterfiles(files, x),
373 (modified, added, removed))
375 (modified, added, removed))
374
376
375 if not modified and not added and not removed:
377 if not modified and not added and not removed:
376 return
378 return
377
379
378 if node2:
380 if node2:
379 change = repo.changelog.read(node2)
381 change = repo.changelog.read(node2)
380 mmap2 = repo.manifest.read(change[0])
382 mmap2 = repo.manifest.read(change[0])
381 date2 = util.datestr(change[2])
383 date2 = util.datestr(change[2])
382 def read(f):
384 def read(f):
383 return repo.file(f).read(mmap2[f])
385 return repo.file(f).read(mmap2[f])
384 else:
386 else:
385 date2 = util.datestr()
387 date2 = util.datestr()
386 def read(f):
388 def read(f):
387 return repo.wread(f)
389 return repo.wread(f)
388
390
389 if ui.quiet:
391 if ui.quiet:
390 r = None
392 r = None
391 else:
393 else:
392 hexfunc = ui.verbose and hex or short
394 hexfunc = ui.verbose and hex or short
393 r = [hexfunc(node) for node in [node1, node2] if node]
395 r = [hexfunc(node) for node in [node1, node2] if node]
394
396
395 diffopts = ui.diffopts()
397 diffopts = ui.diffopts()
396 showfunc = opts.get('show_function') or diffopts['showfunc']
398 showfunc = opts.get('show_function') or diffopts['showfunc']
397 ignorews = opts.get('ignore_all_space') or diffopts['ignorews']
399 ignorews = opts.get('ignore_all_space') or diffopts['ignorews']
398 for f in modified:
400 for f in modified:
399 to = None
401 to = None
400 if f in mmap:
402 if f in mmap:
401 to = repo.file(f).read(mmap[f])
403 to = repo.file(f).read(mmap[f])
402 tn = read(f)
404 tn = read(f)
403 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
405 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
404 showfunc=showfunc, ignorews=ignorews))
406 showfunc=showfunc, ignorews=ignorews))
405 for f in added:
407 for f in added:
406 to = None
408 to = None
407 tn = read(f)
409 tn = read(f)
408 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
410 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
409 showfunc=showfunc, ignorews=ignorews))
411 showfunc=showfunc, ignorews=ignorews))
410 for f in removed:
412 for f in removed:
411 to = repo.file(f).read(mmap[f])
413 to = repo.file(f).read(mmap[f])
412 tn = None
414 tn = None
413 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
415 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
414 showfunc=showfunc, ignorews=ignorews))
416 showfunc=showfunc, ignorews=ignorews))
415
417
416 def trimuser(ui, name, rev, revcache):
418 def trimuser(ui, name, rev, revcache):
417 """trim the name of the user who committed a change"""
419 """trim the name of the user who committed a change"""
418 user = revcache.get(rev)
420 user = revcache.get(rev)
419 if user is None:
421 if user is None:
420 user = revcache[rev] = ui.shortuser(name)
422 user = revcache[rev] = ui.shortuser(name)
421 return user
423 return user
422
424
423 class changeset_printer(object):
425 class changeset_printer(object):
424 '''show changeset information when templating not requested.'''
426 '''show changeset information when templating not requested.'''
425
427
426 def __init__(self, ui, repo):
428 def __init__(self, ui, repo):
427 self.ui = ui
429 self.ui = ui
428 self.repo = repo
430 self.repo = repo
429
431
430 def show(self, rev=0, changenode=None, brinfo=None):
432 def show(self, rev=0, changenode=None, brinfo=None):
431 '''show a single changeset or file revision'''
433 '''show a single changeset or file revision'''
432 log = self.repo.changelog
434 log = self.repo.changelog
433 if changenode is None:
435 if changenode is None:
434 changenode = log.node(rev)
436 changenode = log.node(rev)
435 elif not rev:
437 elif not rev:
436 rev = log.rev(changenode)
438 rev = log.rev(changenode)
437
439
438 if self.ui.quiet:
440 if self.ui.quiet:
439 self.ui.write("%d:%s\n" % (rev, short(changenode)))
441 self.ui.write("%d:%s\n" % (rev, short(changenode)))
440 return
442 return
441
443
442 changes = log.read(changenode)
444 changes = log.read(changenode)
443 date = util.datestr(changes[2])
445 date = util.datestr(changes[2])
444
446
445 parents = [(log.rev(p), self.ui.verbose and hex(p) or short(p))
447 parents = [(log.rev(p), self.ui.verbose and hex(p) or short(p))
446 for p in log.parents(changenode)
448 for p in log.parents(changenode)
447 if self.ui.debugflag or p != nullid]
449 if self.ui.debugflag or p != nullid]
448 if (not self.ui.debugflag and len(parents) == 1 and
450 if (not self.ui.debugflag and len(parents) == 1 and
449 parents[0][0] == rev-1):
451 parents[0][0] == rev-1):
450 parents = []
452 parents = []
451
453
452 if self.ui.verbose:
454 if self.ui.verbose:
453 self.ui.write(_("changeset: %d:%s\n") % (rev, hex(changenode)))
455 self.ui.write(_("changeset: %d:%s\n") % (rev, hex(changenode)))
454 else:
456 else:
455 self.ui.write(_("changeset: %d:%s\n") % (rev, short(changenode)))
457 self.ui.write(_("changeset: %d:%s\n") % (rev, short(changenode)))
456
458
457 for tag in self.repo.nodetags(changenode):
459 for tag in self.repo.nodetags(changenode):
458 self.ui.status(_("tag: %s\n") % tag)
460 self.ui.status(_("tag: %s\n") % tag)
459 for parent in parents:
461 for parent in parents:
460 self.ui.write(_("parent: %d:%s\n") % parent)
462 self.ui.write(_("parent: %d:%s\n") % parent)
461
463
462 if brinfo and changenode in brinfo:
464 if brinfo and changenode in brinfo:
463 br = brinfo[changenode]
465 br = brinfo[changenode]
464 self.ui.write(_("branch: %s\n") % " ".join(br))
466 self.ui.write(_("branch: %s\n") % " ".join(br))
465
467
466 self.ui.debug(_("manifest: %d:%s\n") %
468 self.ui.debug(_("manifest: %d:%s\n") %
467 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
469 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
468 self.ui.status(_("user: %s\n") % changes[1])
470 self.ui.status(_("user: %s\n") % changes[1])
469 self.ui.status(_("date: %s\n") % date)
471 self.ui.status(_("date: %s\n") % date)
470
472
471 if self.ui.debugflag:
473 if self.ui.debugflag:
472 files = self.repo.changes(log.parents(changenode)[0], changenode)
474 files = self.repo.changes(log.parents(changenode)[0], changenode)
473 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
475 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
474 files):
476 files):
475 if value:
477 if value:
476 self.ui.note("%-12s %s\n" % (key, " ".join(value)))
478 self.ui.note("%-12s %s\n" % (key, " ".join(value)))
477 else:
479 else:
478 self.ui.note(_("files: %s\n") % " ".join(changes[3]))
480 self.ui.note(_("files: %s\n") % " ".join(changes[3]))
479
481
480 description = changes[4].strip()
482 description = changes[4].strip()
481 if description:
483 if description:
482 if self.ui.verbose:
484 if self.ui.verbose:
483 self.ui.status(_("description:\n"))
485 self.ui.status(_("description:\n"))
484 self.ui.status(description)
486 self.ui.status(description)
485 self.ui.status("\n\n")
487 self.ui.status("\n\n")
486 else:
488 else:
487 self.ui.status(_("summary: %s\n") %
489 self.ui.status(_("summary: %s\n") %
488 description.splitlines()[0])
490 description.splitlines()[0])
489 self.ui.status("\n")
491 self.ui.status("\n")
490
492
491 def show_changeset(ui, repo, opts):
493 def show_changeset(ui, repo, opts):
492 '''show one changeset. uses template or regular display. caller
494 '''show one changeset. uses template or regular display. caller
493 can pass in 'style' and 'template' options in opts.'''
495 can pass in 'style' and 'template' options in opts.'''
494
496
495 tmpl = opts.get('template')
497 tmpl = opts.get('template')
496 if tmpl:
498 if tmpl:
497 tmpl = templater.parsestring(tmpl, quoted=False)
499 tmpl = templater.parsestring(tmpl, quoted=False)
498 else:
500 else:
499 tmpl = ui.config('ui', 'logtemplate')
501 tmpl = ui.config('ui', 'logtemplate')
500 if tmpl: tmpl = templater.parsestring(tmpl)
502 if tmpl: tmpl = templater.parsestring(tmpl)
501 mapfile = opts.get('style') or ui.config('ui', 'style')
503 mapfile = opts.get('style') or ui.config('ui', 'style')
502 if tmpl or mapfile:
504 if tmpl or mapfile:
503 if mapfile:
505 if mapfile:
504 if not os.path.isfile(mapfile):
506 if not os.path.isfile(mapfile):
505 mapname = templater.templatepath('map-cmdline.' + mapfile)
507 mapname = templater.templatepath('map-cmdline.' + mapfile)
506 if not mapname: mapname = templater.templatepath(mapfile)
508 if not mapname: mapname = templater.templatepath(mapfile)
507 if mapname: mapfile = mapname
509 if mapname: mapfile = mapname
508 try:
510 try:
509 t = templater.changeset_templater(ui, repo, mapfile)
511 t = templater.changeset_templater(ui, repo, mapfile)
510 except SyntaxError, inst:
512 except SyntaxError, inst:
511 raise util.Abort(inst.args[0])
513 raise util.Abort(inst.args[0])
512 if tmpl: t.use_template(tmpl)
514 if tmpl: t.use_template(tmpl)
513 return t
515 return t
514 return changeset_printer(ui, repo)
516 return changeset_printer(ui, repo)
515
517
516 def show_version(ui):
518 def show_version(ui):
517 """output version and copyright information"""
519 """output version and copyright information"""
518 ui.write(_("Mercurial Distributed SCM (version %s)\n")
520 ui.write(_("Mercurial Distributed SCM (version %s)\n")
519 % version.get_version())
521 % version.get_version())
520 ui.status(_(
522 ui.status(_(
521 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
523 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
522 "This is free software; see the source for copying conditions. "
524 "This is free software; see the source for copying conditions. "
523 "There is NO\nwarranty; "
525 "There is NO\nwarranty; "
524 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
526 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
525 ))
527 ))
526
528
527 def help_(ui, cmd=None, with_version=False):
529 def help_(ui, cmd=None, with_version=False):
528 """show help for a given command or all commands"""
530 """show help for a given command or all commands"""
529 option_lists = []
531 option_lists = []
530 if cmd and cmd != 'shortlist':
532 if cmd and cmd != 'shortlist':
531 if with_version:
533 if with_version:
532 show_version(ui)
534 show_version(ui)
533 ui.write('\n')
535 ui.write('\n')
534 aliases, i = find(cmd)
536 aliases, i = find(cmd)
535 # synopsis
537 # synopsis
536 ui.write("%s\n\n" % i[2])
538 ui.write("%s\n\n" % i[2])
537
539
538 # description
540 # description
539 doc = i[0].__doc__
541 doc = i[0].__doc__
540 if not doc:
542 if not doc:
541 doc = _("(No help text available)")
543 doc = _("(No help text available)")
542 if ui.quiet:
544 if ui.quiet:
543 doc = doc.splitlines(0)[0]
545 doc = doc.splitlines(0)[0]
544 ui.write("%s\n" % doc.rstrip())
546 ui.write("%s\n" % doc.rstrip())
545
547
546 if not ui.quiet:
548 if not ui.quiet:
547 # aliases
549 # aliases
548 if len(aliases) > 1:
550 if len(aliases) > 1:
549 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
551 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
550
552
551 # options
553 # options
552 if i[1]:
554 if i[1]:
553 option_lists.append(("options", i[1]))
555 option_lists.append(("options", i[1]))
554
556
555 else:
557 else:
556 # program name
558 # program name
557 if ui.verbose or with_version:
559 if ui.verbose or with_version:
558 show_version(ui)
560 show_version(ui)
559 else:
561 else:
560 ui.status(_("Mercurial Distributed SCM\n"))
562 ui.status(_("Mercurial Distributed SCM\n"))
561 ui.status('\n')
563 ui.status('\n')
562
564
563 # list of commands
565 # list of commands
564 if cmd == "shortlist":
566 if cmd == "shortlist":
565 ui.status(_('basic commands (use "hg help" '
567 ui.status(_('basic commands (use "hg help" '
566 'for the full list or option "-v" for details):\n\n'))
568 'for the full list or option "-v" for details):\n\n'))
567 elif ui.verbose:
569 elif ui.verbose:
568 ui.status(_('list of commands:\n\n'))
570 ui.status(_('list of commands:\n\n'))
569 else:
571 else:
570 ui.status(_('list of commands (use "hg help -v" '
572 ui.status(_('list of commands (use "hg help -v" '
571 'to show aliases and global options):\n\n'))
573 'to show aliases and global options):\n\n'))
572
574
573 h = {}
575 h = {}
574 cmds = {}
576 cmds = {}
575 for c, e in table.items():
577 for c, e in table.items():
576 f = c.split("|")[0]
578 f = c.split("|")[0]
577 if cmd == "shortlist" and not f.startswith("^"):
579 if cmd == "shortlist" and not f.startswith("^"):
578 continue
580 continue
579 f = f.lstrip("^")
581 f = f.lstrip("^")
580 if not ui.debugflag and f.startswith("debug"):
582 if not ui.debugflag and f.startswith("debug"):
581 continue
583 continue
582 doc = e[0].__doc__
584 doc = e[0].__doc__
583 if not doc:
585 if not doc:
584 doc = _("(No help text available)")
586 doc = _("(No help text available)")
585 h[f] = doc.splitlines(0)[0].rstrip()
587 h[f] = doc.splitlines(0)[0].rstrip()
586 cmds[f] = c.lstrip("^")
588 cmds[f] = c.lstrip("^")
587
589
588 fns = h.keys()
590 fns = h.keys()
589 fns.sort()
591 fns.sort()
590 m = max(map(len, fns))
592 m = max(map(len, fns))
591 for f in fns:
593 for f in fns:
592 if ui.verbose:
594 if ui.verbose:
593 commands = cmds[f].replace("|",", ")
595 commands = cmds[f].replace("|",", ")
594 ui.write(" %s:\n %s\n"%(commands, h[f]))
596 ui.write(" %s:\n %s\n"%(commands, h[f]))
595 else:
597 else:
596 ui.write(' %-*s %s\n' % (m, f, h[f]))
598 ui.write(' %-*s %s\n' % (m, f, h[f]))
597
599
598 # global options
600 # global options
599 if ui.verbose:
601 if ui.verbose:
600 option_lists.append(("global options", globalopts))
602 option_lists.append(("global options", globalopts))
601
603
602 # list all option lists
604 # list all option lists
603 opt_output = []
605 opt_output = []
604 for title, options in option_lists:
606 for title, options in option_lists:
605 opt_output.append(("\n%s:\n" % title, None))
607 opt_output.append(("\n%s:\n" % title, None))
606 for shortopt, longopt, default, desc in options:
608 for shortopt, longopt, default, desc in options:
607 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
609 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
608 longopt and " --%s" % longopt),
610 longopt and " --%s" % longopt),
609 "%s%s" % (desc,
611 "%s%s" % (desc,
610 default
612 default
611 and _(" (default: %s)") % default
613 and _(" (default: %s)") % default
612 or "")))
614 or "")))
613
615
614 if opt_output:
616 if opt_output:
615 opts_len = max([len(line[0]) for line in opt_output if line[1]])
617 opts_len = max([len(line[0]) for line in opt_output if line[1]])
616 for first, second in opt_output:
618 for first, second in opt_output:
617 if second:
619 if second:
618 ui.write(" %-*s %s\n" % (opts_len, first, second))
620 ui.write(" %-*s %s\n" % (opts_len, first, second))
619 else:
621 else:
620 ui.write("%s\n" % first)
622 ui.write("%s\n" % first)
621
623
622 # Commands start here, listed alphabetically
624 # Commands start here, listed alphabetically
623
625
624 def add(ui, repo, *pats, **opts):
626 def add(ui, repo, *pats, **opts):
625 """add the specified files on the next commit
627 """add the specified files on the next commit
626
628
627 Schedule files to be version controlled and added to the repository.
629 Schedule files to be version controlled and added to the repository.
628
630
629 The files will be added to the repository at the next commit.
631 The files will be added to the repository at the next commit.
630
632
631 If no names are given, add all files in the repository.
633 If no names are given, add all files in the repository.
632 """
634 """
633
635
634 names = []
636 names = []
635 for src, abs, rel, exact in walk(repo, pats, opts):
637 for src, abs, rel, exact in walk(repo, pats, opts):
636 if exact:
638 if exact:
637 if ui.verbose:
639 if ui.verbose:
638 ui.status(_('adding %s\n') % rel)
640 ui.status(_('adding %s\n') % rel)
639 names.append(abs)
641 names.append(abs)
640 elif repo.dirstate.state(abs) == '?':
642 elif repo.dirstate.state(abs) == '?':
641 ui.status(_('adding %s\n') % rel)
643 ui.status(_('adding %s\n') % rel)
642 names.append(abs)
644 names.append(abs)
643 repo.add(names)
645 repo.add(names)
644
646
645 def addremove(ui, repo, *pats, **opts):
647 def addremove(ui, repo, *pats, **opts):
646 """add all new files, delete all missing files (DEPRECATED)
648 """add all new files, delete all missing files (DEPRECATED)
647
649
648 (DEPRECATED)
650 (DEPRECATED)
649 Add all new files and remove all missing files from the repository.
651 Add all new files and remove all missing files from the repository.
650
652
651 New files are ignored if they match any of the patterns in .hgignore. As
653 New files are ignored if they match any of the patterns in .hgignore. As
652 with add, these changes take effect at the next commit.
654 with add, these changes take effect at the next commit.
653
655
654 This command is now deprecated and will be removed in a future
656 This command is now deprecated and will be removed in a future
655 release. Please use add and remove --after instead.
657 release. Please use add and remove --after instead.
656 """
658 """
657 ui.warn(_('(the addremove command is deprecated; use add and remove '
659 ui.warn(_('(the addremove command is deprecated; use add and remove '
658 '--after instead)\n'))
660 '--after instead)\n'))
659 return addremove_lock(ui, repo, pats, opts)
661 return addremove_lock(ui, repo, pats, opts)
660
662
661 def addremove_lock(ui, repo, pats, opts, wlock=None):
663 def addremove_lock(ui, repo, pats, opts, wlock=None):
662 add, remove = [], []
664 add, remove = [], []
663 for src, abs, rel, exact in walk(repo, pats, opts):
665 for src, abs, rel, exact in walk(repo, pats, opts):
664 if src == 'f' and repo.dirstate.state(abs) == '?':
666 if src == 'f' and repo.dirstate.state(abs) == '?':
665 add.append(abs)
667 add.append(abs)
666 if ui.verbose or not exact:
668 if ui.verbose or not exact:
667 ui.status(_('adding %s\n') % ((pats and rel) or abs))
669 ui.status(_('adding %s\n') % ((pats and rel) or abs))
668 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
670 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
669 remove.append(abs)
671 remove.append(abs)
670 if ui.verbose or not exact:
672 if ui.verbose or not exact:
671 ui.status(_('removing %s\n') % ((pats and rel) or abs))
673 ui.status(_('removing %s\n') % ((pats and rel) or abs))
672 repo.add(add, wlock=wlock)
674 repo.add(add, wlock=wlock)
673 repo.remove(remove, wlock=wlock)
675 repo.remove(remove, wlock=wlock)
674
676
675 def annotate(ui, repo, *pats, **opts):
677 def annotate(ui, repo, *pats, **opts):
676 """show changeset information per file line
678 """show changeset information per file line
677
679
678 List changes in files, showing the revision id responsible for each line
680 List changes in files, showing the revision id responsible for each line
679
681
680 This command is useful to discover who did a change or when a change took
682 This command is useful to discover who did a change or when a change took
681 place.
683 place.
682
684
683 Without the -a option, annotate will avoid processing files it
685 Without the -a option, annotate will avoid processing files it
684 detects as binary. With -a, annotate will generate an annotation
686 detects as binary. With -a, annotate will generate an annotation
685 anyway, probably with undesirable results.
687 anyway, probably with undesirable results.
686 """
688 """
687 def getnode(rev):
689 def getnode(rev):
688 return short(repo.changelog.node(rev))
690 return short(repo.changelog.node(rev))
689
691
690 ucache = {}
692 ucache = {}
691 def getname(rev):
693 def getname(rev):
692 cl = repo.changelog.read(repo.changelog.node(rev))
694 cl = repo.changelog.read(repo.changelog.node(rev))
693 return trimuser(ui, cl[1], rev, ucache)
695 return trimuser(ui, cl[1], rev, ucache)
694
696
695 dcache = {}
697 dcache = {}
696 def getdate(rev):
698 def getdate(rev):
697 datestr = dcache.get(rev)
699 datestr = dcache.get(rev)
698 if datestr is None:
700 if datestr is None:
699 cl = repo.changelog.read(repo.changelog.node(rev))
701 cl = repo.changelog.read(repo.changelog.node(rev))
700 datestr = dcache[rev] = util.datestr(cl[2])
702 datestr = dcache[rev] = util.datestr(cl[2])
701 return datestr
703 return datestr
702
704
703 if not pats:
705 if not pats:
704 raise util.Abort(_('at least one file name or pattern required'))
706 raise util.Abort(_('at least one file name or pattern required'))
705
707
706 opmap = [['user', getname], ['number', str], ['changeset', getnode],
708 opmap = [['user', getname], ['number', str], ['changeset', getnode],
707 ['date', getdate]]
709 ['date', getdate]]
708 if not opts['user'] and not opts['changeset'] and not opts['date']:
710 if not opts['user'] and not opts['changeset'] and not opts['date']:
709 opts['number'] = 1
711 opts['number'] = 1
710
712
711 if opts['rev']:
713 if opts['rev']:
712 node = repo.changelog.lookup(opts['rev'])
714 node = repo.changelog.lookup(opts['rev'])
713 else:
715 else:
714 node = repo.dirstate.parents()[0]
716 node = repo.dirstate.parents()[0]
715 change = repo.changelog.read(node)
717 change = repo.changelog.read(node)
716 mmap = repo.manifest.read(change[0])
718 mmap = repo.manifest.read(change[0])
717
719
718 for src, abs, rel, exact in walk(repo, pats, opts, node=node):
720 for src, abs, rel, exact in walk(repo, pats, opts, node=node):
719 f = repo.file(abs)
721 f = repo.file(abs)
720 if not opts['text'] and util.binary(f.read(mmap[abs])):
722 if not opts['text'] and util.binary(f.read(mmap[abs])):
721 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
723 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
722 continue
724 continue
723
725
724 lines = f.annotate(mmap[abs])
726 lines = f.annotate(mmap[abs])
725 pieces = []
727 pieces = []
726
728
727 for o, f in opmap:
729 for o, f in opmap:
728 if opts[o]:
730 if opts[o]:
729 l = [f(n) for n, dummy in lines]
731 l = [f(n) for n, dummy in lines]
730 if l:
732 if l:
731 m = max(map(len, l))
733 m = max(map(len, l))
732 pieces.append(["%*s" % (m, x) for x in l])
734 pieces.append(["%*s" % (m, x) for x in l])
733
735
734 if pieces:
736 if pieces:
735 for p, l in zip(zip(*pieces), lines):
737 for p, l in zip(zip(*pieces), lines):
736 ui.write("%s: %s" % (" ".join(p), l[1]))
738 ui.write("%s: %s" % (" ".join(p), l[1]))
737
739
738 def archive(ui, repo, dest, **opts):
740 def archive(ui, repo, dest, **opts):
739 '''create unversioned archive of a repository revision
741 '''create unversioned archive of a repository revision
740
742
741 By default, the revision used is the parent of the working
743 By default, the revision used is the parent of the working
742 directory; use "-r" to specify a different revision.
744 directory; use "-r" to specify a different revision.
743
745
744 To specify the type of archive to create, use "-t". Valid
746 To specify the type of archive to create, use "-t". Valid
745 types are:
747 types are:
746
748
747 "files" (default): a directory full of files
749 "files" (default): a directory full of files
748 "tar": tar archive, uncompressed
750 "tar": tar archive, uncompressed
749 "tbz2": tar archive, compressed using bzip2
751 "tbz2": tar archive, compressed using bzip2
750 "tgz": tar archive, compressed using gzip
752 "tgz": tar archive, compressed using gzip
751 "uzip": zip archive, uncompressed
753 "uzip": zip archive, uncompressed
752 "zip": zip archive, compressed using deflate
754 "zip": zip archive, compressed using deflate
753
755
754 The exact name of the destination archive or directory is given
756 The exact name of the destination archive or directory is given
755 using a format string; see "hg help export" for details.
757 using a format string; see "hg help export" for details.
756
758
757 Each member added to an archive file has a directory prefix
759 Each member added to an archive file has a directory prefix
758 prepended. Use "-p" to specify a format string for the prefix.
760 prepended. Use "-p" to specify a format string for the prefix.
759 The default is the basename of the archive, with suffixes removed.
761 The default is the basename of the archive, with suffixes removed.
760 '''
762 '''
761
763
762 if opts['rev']:
764 if opts['rev']:
763 node = repo.lookup(opts['rev'])
765 node = repo.lookup(opts['rev'])
764 else:
766 else:
765 node, p2 = repo.dirstate.parents()
767 node, p2 = repo.dirstate.parents()
766 if p2 != nullid:
768 if p2 != nullid:
767 raise util.Abort(_('uncommitted merge - please provide a '
769 raise util.Abort(_('uncommitted merge - please provide a '
768 'specific revision'))
770 'specific revision'))
769
771
770 dest = make_filename(repo, repo.changelog, dest, node)
772 dest = make_filename(repo, repo.changelog, dest, node)
771 prefix = make_filename(repo, repo.changelog, opts['prefix'], node)
773 prefix = make_filename(repo, repo.changelog, opts['prefix'], node)
772 if os.path.realpath(dest) == repo.root:
774 if os.path.realpath(dest) == repo.root:
773 raise util.Abort(_('repository root cannot be destination'))
775 raise util.Abort(_('repository root cannot be destination'))
774 dummy, matchfn, dummy = matchpats(repo, [], opts)
776 dummy, matchfn, dummy = matchpats(repo, [], opts)
775 archival.archive(repo, dest, node, opts.get('type') or 'files',
777 archival.archive(repo, dest, node, opts.get('type') or 'files',
776 not opts['no_decode'], matchfn, prefix)
778 not opts['no_decode'], matchfn, prefix)
777
779
778 def backout(ui, repo, rev, **opts):
780 def backout(ui, repo, rev, **opts):
779 '''reverse effect of earlier changeset
781 '''reverse effect of earlier changeset
780
782
781 Commit the backed out changes as a new changeset. The new
783 Commit the backed out changes as a new changeset. The new
782 changeset is a child of the backed out changeset.
784 changeset is a child of the backed out changeset.
783
785
784 If you back out a changeset other than the tip, a new head is
786 If you back out a changeset other than the tip, a new head is
785 created. This head is the parent of the working directory. If
787 created. This head is the parent of the working directory. If
786 you back out an old changeset, your working directory will appear
788 you back out an old changeset, your working directory will appear
787 old after the backout. You should merge the backout changeset
789 old after the backout. You should merge the backout changeset
788 with another head.
790 with another head.
789
791
790 The --merge option remembers the parent of the working directory
792 The --merge option remembers the parent of the working directory
791 before starting the backout, then merges the new head with that
793 before starting the backout, then merges the new head with that
792 changeset afterwards. This saves you from doing the merge by
794 changeset afterwards. This saves you from doing the merge by
793 hand. The result of this merge is not committed, as for a normal
795 hand. The result of this merge is not committed, as for a normal
794 merge.'''
796 merge.'''
795
797
796 bail_if_changed(repo)
798 bail_if_changed(repo)
797 op1, op2 = repo.dirstate.parents()
799 op1, op2 = repo.dirstate.parents()
798 if op2 != nullid:
800 if op2 != nullid:
799 raise util.Abort(_('outstanding uncommitted merge'))
801 raise util.Abort(_('outstanding uncommitted merge'))
800 node = repo.lookup(rev)
802 node = repo.lookup(rev)
801 parent, p2 = repo.changelog.parents(node)
803 parent, p2 = repo.changelog.parents(node)
802 if parent == nullid:
804 if parent == nullid:
803 raise util.Abort(_('cannot back out a change with no parents'))
805 raise util.Abort(_('cannot back out a change with no parents'))
804 if p2 != nullid:
806 if p2 != nullid:
805 raise util.Abort(_('cannot back out a merge'))
807 raise util.Abort(_('cannot back out a merge'))
806 repo.update(node, force=True, show_stats=False)
808 repo.update(node, force=True, show_stats=False)
807 revert_opts = opts.copy()
809 revert_opts = opts.copy()
808 revert_opts['rev'] = hex(parent)
810 revert_opts['rev'] = hex(parent)
809 revert(ui, repo, **revert_opts)
811 revert(ui, repo, **revert_opts)
810 commit_opts = opts.copy()
812 commit_opts = opts.copy()
811 commit_opts['addremove'] = False
813 commit_opts['addremove'] = False
812 if not commit_opts['message'] and not commit_opts['logfile']:
814 if not commit_opts['message'] and not commit_opts['logfile']:
813 commit_opts['message'] = _("Backed out changeset %s") % (hex(node))
815 commit_opts['message'] = _("Backed out changeset %s") % (hex(node))
814 commit_opts['force_editor'] = True
816 commit_opts['force_editor'] = True
815 commit(ui, repo, **commit_opts)
817 commit(ui, repo, **commit_opts)
816 def nice(node):
818 def nice(node):
817 return '%d:%s' % (repo.changelog.rev(node), short(node))
819 return '%d:%s' % (repo.changelog.rev(node), short(node))
818 ui.status(_('changeset %s backs out changeset %s\n') %
820 ui.status(_('changeset %s backs out changeset %s\n') %
819 (nice(repo.changelog.tip()), nice(node)))
821 (nice(repo.changelog.tip()), nice(node)))
820 if opts['merge'] and op1 != node:
822 if opts['merge'] and op1 != node:
821 ui.status(_('merging with changeset %s\n') % nice(op1))
823 ui.status(_('merging with changeset %s\n') % nice(op1))
822 doupdate(ui, repo, hex(op1), **opts)
824 doupdate(ui, repo, hex(op1), **opts)
823
825
824 def bundle(ui, repo, fname, dest="default-push", **opts):
826 def bundle(ui, repo, fname, dest="default-push", **opts):
825 """create a changegroup file
827 """create a changegroup file
826
828
827 Generate a compressed changegroup file collecting all changesets
829 Generate a compressed changegroup file collecting all changesets
828 not found in the other repository.
830 not found in the other repository.
829
831
830 This file can then be transferred using conventional means and
832 This file can then be transferred using conventional means and
831 applied to another repository with the unbundle command. This is
833 applied to another repository with the unbundle command. This is
832 useful when native push and pull are not available or when
834 useful when native push and pull are not available or when
833 exporting an entire repository is undesirable. The standard file
835 exporting an entire repository is undesirable. The standard file
834 extension is ".hg".
836 extension is ".hg".
835
837
836 Unlike import/export, this exactly preserves all changeset
838 Unlike import/export, this exactly preserves all changeset
837 contents including permissions, rename data, and revision history.
839 contents including permissions, rename data, and revision history.
838 """
840 """
839 dest = ui.expandpath(dest)
841 dest = ui.expandpath(dest)
840 other = hg.repository(ui, dest)
842 other = hg.repository(ui, dest)
841 o = repo.findoutgoing(other, force=opts['force'])
843 o = repo.findoutgoing(other, force=opts['force'])
842 cg = repo.changegroup(o, 'bundle')
844 cg = repo.changegroup(o, 'bundle')
843 write_bundle(cg, fname)
845 write_bundle(cg, fname)
844
846
845 def cat(ui, repo, file1, *pats, **opts):
847 def cat(ui, repo, file1, *pats, **opts):
846 """output the latest or given revisions of files
848 """output the latest or given revisions of files
847
849
848 Print the specified files as they were at the given revision.
850 Print the specified files as they were at the given revision.
849 If no revision is given then the tip is used.
851 If no revision is given then the tip is used.
850
852
851 Output may be to a file, in which case the name of the file is
853 Output may be to a file, in which case the name of the file is
852 given using a format string. The formatting rules are the same as
854 given using a format string. The formatting rules are the same as
853 for the export command, with the following additions:
855 for the export command, with the following additions:
854
856
855 %s basename of file being printed
857 %s basename of file being printed
856 %d dirname of file being printed, or '.' if in repo root
858 %d dirname of file being printed, or '.' if in repo root
857 %p root-relative path name of file being printed
859 %p root-relative path name of file being printed
858 """
860 """
859 mf = {}
861 mf = {}
860 rev = opts['rev']
862 rev = opts['rev']
861 if rev:
863 if rev:
862 node = repo.lookup(rev)
864 node = repo.lookup(rev)
863 else:
865 else:
864 node = repo.changelog.tip()
866 node = repo.changelog.tip()
865 change = repo.changelog.read(node)
867 change = repo.changelog.read(node)
866 mf = repo.manifest.read(change[0])
868 mf = repo.manifest.read(change[0])
867 for src, abs, rel, exact in walk(repo, (file1,) + pats, opts, node):
869 for src, abs, rel, exact in walk(repo, (file1,) + pats, opts, node):
868 r = repo.file(abs)
870 r = repo.file(abs)
869 n = mf[abs]
871 n = mf[abs]
870 fp = make_file(repo, r, opts['output'], node=n, pathname=abs)
872 fp = make_file(repo, r, opts['output'], node=n, pathname=abs)
871 fp.write(r.read(n))
873 fp.write(r.read(n))
872
874
873 def clone(ui, source, dest=None, **opts):
875 def clone(ui, source, dest=None, **opts):
874 """make a copy of an existing repository
876 """make a copy of an existing repository
875
877
876 Create a copy of an existing repository in a new directory.
878 Create a copy of an existing repository in a new directory.
877
879
878 If no destination directory name is specified, it defaults to the
880 If no destination directory name is specified, it defaults to the
879 basename of the source.
881 basename of the source.
880
882
881 The location of the source is added to the new repository's
883 The location of the source is added to the new repository's
882 .hg/hgrc file, as the default to be used for future pulls.
884 .hg/hgrc file, as the default to be used for future pulls.
883
885
884 For efficiency, hardlinks are used for cloning whenever the source
886 For efficiency, hardlinks are used for cloning whenever the source
885 and destination are on the same filesystem. Some filesystems,
887 and destination are on the same filesystem. Some filesystems,
886 such as AFS, implement hardlinking incorrectly, but do not report
888 such as AFS, implement hardlinking incorrectly, but do not report
887 errors. In these cases, use the --pull option to avoid
889 errors. In these cases, use the --pull option to avoid
888 hardlinking.
890 hardlinking.
889
891
890 See pull for valid source format details.
892 See pull for valid source format details.
891 """
893 """
892 if dest is None:
894 if dest is None:
893 dest = os.path.basename(os.path.normpath(source))
895 dest = os.path.basename(os.path.normpath(source))
894
896
895 if os.path.exists(dest):
897 if os.path.exists(dest):
896 raise util.Abort(_("destination '%s' already exists"), dest)
898 raise util.Abort(_("destination '%s' already exists"), dest)
897
899
898 dest = os.path.realpath(dest)
900 dest = os.path.realpath(dest)
899
901
900 class Dircleanup(object):
902 class Dircleanup(object):
901 def __init__(self, dir_):
903 def __init__(self, dir_):
902 self.rmtree = shutil.rmtree
904 self.rmtree = shutil.rmtree
903 self.dir_ = dir_
905 self.dir_ = dir_
904 os.mkdir(dir_)
906 os.mkdir(dir_)
905 def close(self):
907 def close(self):
906 self.dir_ = None
908 self.dir_ = None
907 def __del__(self):
909 def __del__(self):
908 if self.dir_:
910 if self.dir_:
909 self.rmtree(self.dir_, True)
911 self.rmtree(self.dir_, True)
910
912
911 if opts['ssh']:
913 if opts['ssh']:
912 ui.setconfig("ui", "ssh", opts['ssh'])
914 ui.setconfig("ui", "ssh", opts['ssh'])
913 if opts['remotecmd']:
915 if opts['remotecmd']:
914 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
916 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
915
917
916 source = ui.expandpath(source)
918 source = ui.expandpath(source)
917
919
918 d = Dircleanup(dest)
920 d = Dircleanup(dest)
919 abspath = source
921 abspath = source
920 other = hg.repository(ui, source)
922 other = hg.repository(ui, source)
921
923
922 copy = False
924 copy = False
923 if other.dev() != -1:
925 if other.dev() != -1:
924 abspath = os.path.abspath(source)
926 abspath = os.path.abspath(source)
925 if not opts['pull'] and not opts['rev']:
927 if not opts['pull'] and not opts['rev']:
926 copy = True
928 copy = True
927
929
928 if copy:
930 if copy:
929 try:
931 try:
930 # we use a lock here because if we race with commit, we
932 # we use a lock here because if we race with commit, we
931 # can end up with extra data in the cloned revlogs that's
933 # can end up with extra data in the cloned revlogs that's
932 # not pointed to by changesets, thus causing verify to
934 # not pointed to by changesets, thus causing verify to
933 # fail
935 # fail
934 l1 = other.lock()
936 l1 = other.lock()
935 except lock.LockException:
937 except lock.LockException:
936 copy = False
938 copy = False
937
939
938 if copy:
940 if copy:
939 # we lock here to avoid premature writing to the target
941 # we lock here to avoid premature writing to the target
940 os.mkdir(os.path.join(dest, ".hg"))
942 os.mkdir(os.path.join(dest, ".hg"))
941 l2 = lock.lock(os.path.join(dest, ".hg", "lock"))
943 l2 = lock.lock(os.path.join(dest, ".hg", "lock"))
942
944
943 files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
945 files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
944 for f in files.split():
946 for f in files.split():
945 src = os.path.join(source, ".hg", f)
947 src = os.path.join(source, ".hg", f)
946 dst = os.path.join(dest, ".hg", f)
948 dst = os.path.join(dest, ".hg", f)
947 try:
949 try:
948 util.copyfiles(src, dst)
950 util.copyfiles(src, dst)
949 except OSError, inst:
951 except OSError, inst:
950 if inst.errno != errno.ENOENT:
952 if inst.errno != errno.ENOENT:
951 raise
953 raise
952
954
953 repo = hg.repository(ui, dest)
955 repo = hg.repository(ui, dest)
954
956
955 else:
957 else:
956 revs = None
958 revs = None
957 if opts['rev']:
959 if opts['rev']:
958 if not other.local():
960 if not other.local():
959 error = _("clone -r not supported yet for remote repositories.")
961 error = _("clone -r not supported yet for remote repositories.")
960 raise util.Abort(error)
962 raise util.Abort(error)
961 else:
963 else:
962 revs = [other.lookup(rev) for rev in opts['rev']]
964 revs = [other.lookup(rev) for rev in opts['rev']]
963 repo = hg.repository(ui, dest, create=1)
965 repo = hg.repository(ui, dest, create=1)
964 repo.pull(other, heads = revs)
966 repo.pull(other, heads = revs)
965
967
966 f = repo.opener("hgrc", "w", text=True)
968 f = repo.opener("hgrc", "w", text=True)
967 f.write("[paths]\n")
969 f.write("[paths]\n")
968 f.write("default = %s\n" % abspath)
970 f.write("default = %s\n" % abspath)
969 f.close()
971 f.close()
970
972
971 if not opts['noupdate']:
973 if not opts['noupdate']:
972 doupdate(repo.ui, repo)
974 doupdate(repo.ui, repo)
973
975
974 d.close()
976 d.close()
975
977
976 def commit(ui, repo, *pats, **opts):
978 def commit(ui, repo, *pats, **opts):
977 """commit the specified files or all outstanding changes
979 """commit the specified files or all outstanding changes
978
980
979 Commit changes to the given files into the repository.
981 Commit changes to the given files into the repository.
980
982
981 If a list of files is omitted, all changes reported by "hg status"
983 If a list of files is omitted, all changes reported by "hg status"
982 will be committed.
984 will be committed.
983
985
984 If no commit message is specified, the editor configured in your hgrc
986 If no commit message is specified, the editor configured in your hgrc
985 or in the EDITOR environment variable is started to enter a message.
987 or in the EDITOR environment variable is started to enter a message.
986 """
988 """
987 message = opts['message']
989 message = opts['message']
988 logfile = opts['logfile']
990 logfile = opts['logfile']
989
991
990 if message and logfile:
992 if message and logfile:
991 raise util.Abort(_('options --message and --logfile are mutually '
993 raise util.Abort(_('options --message and --logfile are mutually '
992 'exclusive'))
994 'exclusive'))
993 if not message and logfile:
995 if not message and logfile:
994 try:
996 try:
995 if logfile == '-':
997 if logfile == '-':
996 message = sys.stdin.read()
998 message = sys.stdin.read()
997 else:
999 else:
998 message = open(logfile).read()
1000 message = open(logfile).read()
999 except IOError, inst:
1001 except IOError, inst:
1000 raise util.Abort(_("can't read commit message '%s': %s") %
1002 raise util.Abort(_("can't read commit message '%s': %s") %
1001 (logfile, inst.strerror))
1003 (logfile, inst.strerror))
1002
1004
1003 if opts['addremove']:
1005 if opts['addremove']:
1004 addremove_lock(ui, repo, pats, opts)
1006 addremove_lock(ui, repo, pats, opts)
1005 fns, match, anypats = matchpats(repo, pats, opts)
1007 fns, match, anypats = matchpats(repo, pats, opts)
1006 if pats:
1008 if pats:
1007 modified, added, removed, deleted, unknown = (
1009 modified, added, removed, deleted, unknown = (
1008 repo.changes(files=fns, match=match))
1010 repo.changes(files=fns, match=match))
1009 files = modified + added + removed
1011 files = modified + added + removed
1010 else:
1012 else:
1011 files = []
1013 files = []
1012 try:
1014 try:
1013 repo.commit(files, message, opts['user'], opts['date'], match,
1015 repo.commit(files, message, opts['user'], opts['date'], match,
1014 force_editor=opts.get('force_editor'))
1016 force_editor=opts.get('force_editor'))
1015 except ValueError, inst:
1017 except ValueError, inst:
1016 raise util.Abort(str(inst))
1018 raise util.Abort(str(inst))
1017
1019
1018 def docopy(ui, repo, pats, opts, wlock):
1020 def docopy(ui, repo, pats, opts, wlock):
1019 # called with the repo lock held
1021 # called with the repo lock held
1020 cwd = repo.getcwd()
1022 cwd = repo.getcwd()
1021 errors = 0
1023 errors = 0
1022 copied = []
1024 copied = []
1023 targets = {}
1025 targets = {}
1024
1026
1025 def okaytocopy(abs, rel, exact):
1027 def okaytocopy(abs, rel, exact):
1026 reasons = {'?': _('is not managed'),
1028 reasons = {'?': _('is not managed'),
1027 'a': _('has been marked for add'),
1029 'a': _('has been marked for add'),
1028 'r': _('has been marked for remove')}
1030 'r': _('has been marked for remove')}
1029 state = repo.dirstate.state(abs)
1031 state = repo.dirstate.state(abs)
1030 reason = reasons.get(state)
1032 reason = reasons.get(state)
1031 if reason:
1033 if reason:
1032 if state == 'a':
1034 if state == 'a':
1033 origsrc = repo.dirstate.copied(abs)
1035 origsrc = repo.dirstate.copied(abs)
1034 if origsrc is not None:
1036 if origsrc is not None:
1035 return origsrc
1037 return origsrc
1036 if exact:
1038 if exact:
1037 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
1039 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
1038 else:
1040 else:
1039 return abs
1041 return abs
1040
1042
1041 def copy(origsrc, abssrc, relsrc, target, exact):
1043 def copy(origsrc, abssrc, relsrc, target, exact):
1042 abstarget = util.canonpath(repo.root, cwd, target)
1044 abstarget = util.canonpath(repo.root, cwd, target)
1043 reltarget = util.pathto(cwd, abstarget)
1045 reltarget = util.pathto(cwd, abstarget)
1044 prevsrc = targets.get(abstarget)
1046 prevsrc = targets.get(abstarget)
1045 if prevsrc is not None:
1047 if prevsrc is not None:
1046 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1048 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1047 (reltarget, abssrc, prevsrc))
1049 (reltarget, abssrc, prevsrc))
1048 return
1050 return
1049 if (not opts['after'] and os.path.exists(reltarget) or
1051 if (not opts['after'] and os.path.exists(reltarget) or
1050 opts['after'] and repo.dirstate.state(abstarget) not in '?r'):
1052 opts['after'] and repo.dirstate.state(abstarget) not in '?r'):
1051 if not opts['force']:
1053 if not opts['force']:
1052 ui.warn(_('%s: not overwriting - file exists\n') %
1054 ui.warn(_('%s: not overwriting - file exists\n') %
1053 reltarget)
1055 reltarget)
1054 return
1056 return
1055 if not opts['after']:
1057 if not opts['after']:
1056 os.unlink(reltarget)
1058 os.unlink(reltarget)
1057 if opts['after']:
1059 if opts['after']:
1058 if not os.path.exists(reltarget):
1060 if not os.path.exists(reltarget):
1059 return
1061 return
1060 else:
1062 else:
1061 targetdir = os.path.dirname(reltarget) or '.'
1063 targetdir = os.path.dirname(reltarget) or '.'
1062 if not os.path.isdir(targetdir):
1064 if not os.path.isdir(targetdir):
1063 os.makedirs(targetdir)
1065 os.makedirs(targetdir)
1064 try:
1066 try:
1065 restore = repo.dirstate.state(abstarget) == 'r'
1067 restore = repo.dirstate.state(abstarget) == 'r'
1066 if restore:
1068 if restore:
1067 repo.undelete([abstarget], wlock)
1069 repo.undelete([abstarget], wlock)
1068 try:
1070 try:
1069 shutil.copyfile(relsrc, reltarget)
1071 shutil.copyfile(relsrc, reltarget)
1070 shutil.copymode(relsrc, reltarget)
1072 shutil.copymode(relsrc, reltarget)
1071 restore = False
1073 restore = False
1072 finally:
1074 finally:
1073 if restore:
1075 if restore:
1074 repo.remove([abstarget], wlock)
1076 repo.remove([abstarget], wlock)
1075 except shutil.Error, inst:
1077 except shutil.Error, inst:
1076 raise util.Abort(str(inst))
1078 raise util.Abort(str(inst))
1077 except IOError, inst:
1079 except IOError, inst:
1078 if inst.errno == errno.ENOENT:
1080 if inst.errno == errno.ENOENT:
1079 ui.warn(_('%s: deleted in working copy\n') % relsrc)
1081 ui.warn(_('%s: deleted in working copy\n') % relsrc)
1080 else:
1082 else:
1081 ui.warn(_('%s: cannot copy - %s\n') %
1083 ui.warn(_('%s: cannot copy - %s\n') %
1082 (relsrc, inst.strerror))
1084 (relsrc, inst.strerror))
1083 errors += 1
1085 errors += 1
1084 return
1086 return
1085 if ui.verbose or not exact:
1087 if ui.verbose or not exact:
1086 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1088 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1087 targets[abstarget] = abssrc
1089 targets[abstarget] = abssrc
1088 if abstarget != origsrc:
1090 if abstarget != origsrc:
1089 repo.copy(origsrc, abstarget, wlock)
1091 repo.copy(origsrc, abstarget, wlock)
1090 copied.append((abssrc, relsrc, exact))
1092 copied.append((abssrc, relsrc, exact))
1091
1093
1092 def targetpathfn(pat, dest, srcs):
1094 def targetpathfn(pat, dest, srcs):
1093 if os.path.isdir(pat):
1095 if os.path.isdir(pat):
1094 abspfx = util.canonpath(repo.root, cwd, pat)
1096 abspfx = util.canonpath(repo.root, cwd, pat)
1095 if destdirexists:
1097 if destdirexists:
1096 striplen = len(os.path.split(abspfx)[0])
1098 striplen = len(os.path.split(abspfx)[0])
1097 else:
1099 else:
1098 striplen = len(abspfx)
1100 striplen = len(abspfx)
1099 if striplen:
1101 if striplen:
1100 striplen += len(os.sep)
1102 striplen += len(os.sep)
1101 res = lambda p: os.path.join(dest, p[striplen:])
1103 res = lambda p: os.path.join(dest, p[striplen:])
1102 elif destdirexists:
1104 elif destdirexists:
1103 res = lambda p: os.path.join(dest, os.path.basename(p))
1105 res = lambda p: os.path.join(dest, os.path.basename(p))
1104 else:
1106 else:
1105 res = lambda p: dest
1107 res = lambda p: dest
1106 return res
1108 return res
1107
1109
1108 def targetpathafterfn(pat, dest, srcs):
1110 def targetpathafterfn(pat, dest, srcs):
1109 if util.patkind(pat, None)[0]:
1111 if util.patkind(pat, None)[0]:
1110 # a mercurial pattern
1112 # a mercurial pattern
1111 res = lambda p: os.path.join(dest, os.path.basename(p))
1113 res = lambda p: os.path.join(dest, os.path.basename(p))
1112 else:
1114 else:
1113 abspfx = util.canonpath(repo.root, cwd, pat)
1115 abspfx = util.canonpath(repo.root, cwd, pat)
1114 if len(abspfx) < len(srcs[0][0]):
1116 if len(abspfx) < len(srcs[0][0]):
1115 # A directory. Either the target path contains the last
1117 # A directory. Either the target path contains the last
1116 # component of the source path or it does not.
1118 # component of the source path or it does not.
1117 def evalpath(striplen):
1119 def evalpath(striplen):
1118 score = 0
1120 score = 0
1119 for s in srcs:
1121 for s in srcs:
1120 t = os.path.join(dest, s[0][striplen:])
1122 t = os.path.join(dest, s[0][striplen:])
1121 if os.path.exists(t):
1123 if os.path.exists(t):
1122 score += 1
1124 score += 1
1123 return score
1125 return score
1124
1126
1125 striplen = len(abspfx)
1127 striplen = len(abspfx)
1126 if striplen:
1128 if striplen:
1127 striplen += len(os.sep)
1129 striplen += len(os.sep)
1128 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1130 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1129 score = evalpath(striplen)
1131 score = evalpath(striplen)
1130 striplen1 = len(os.path.split(abspfx)[0])
1132 striplen1 = len(os.path.split(abspfx)[0])
1131 if striplen1:
1133 if striplen1:
1132 striplen1 += len(os.sep)
1134 striplen1 += len(os.sep)
1133 if evalpath(striplen1) > score:
1135 if evalpath(striplen1) > score:
1134 striplen = striplen1
1136 striplen = striplen1
1135 res = lambda p: os.path.join(dest, p[striplen:])
1137 res = lambda p: os.path.join(dest, p[striplen:])
1136 else:
1138 else:
1137 # a file
1139 # a file
1138 if destdirexists:
1140 if destdirexists:
1139 res = lambda p: os.path.join(dest, os.path.basename(p))
1141 res = lambda p: os.path.join(dest, os.path.basename(p))
1140 else:
1142 else:
1141 res = lambda p: dest
1143 res = lambda p: dest
1142 return res
1144 return res
1143
1145
1144
1146
1145 pats = list(pats)
1147 pats = list(pats)
1146 if not pats:
1148 if not pats:
1147 raise util.Abort(_('no source or destination specified'))
1149 raise util.Abort(_('no source or destination specified'))
1148 if len(pats) == 1:
1150 if len(pats) == 1:
1149 raise util.Abort(_('no destination specified'))
1151 raise util.Abort(_('no destination specified'))
1150 dest = pats.pop()
1152 dest = pats.pop()
1151 destdirexists = os.path.isdir(dest)
1153 destdirexists = os.path.isdir(dest)
1152 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
1154 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
1153 raise util.Abort(_('with multiple sources, destination must be an '
1155 raise util.Abort(_('with multiple sources, destination must be an '
1154 'existing directory'))
1156 'existing directory'))
1155 if opts['after']:
1157 if opts['after']:
1156 tfn = targetpathafterfn
1158 tfn = targetpathafterfn
1157 else:
1159 else:
1158 tfn = targetpathfn
1160 tfn = targetpathfn
1159 copylist = []
1161 copylist = []
1160 for pat in pats:
1162 for pat in pats:
1161 srcs = []
1163 srcs = []
1162 for tag, abssrc, relsrc, exact in walk(repo, [pat], opts):
1164 for tag, abssrc, relsrc, exact in walk(repo, [pat], opts):
1163 origsrc = okaytocopy(abssrc, relsrc, exact)
1165 origsrc = okaytocopy(abssrc, relsrc, exact)
1164 if origsrc:
1166 if origsrc:
1165 srcs.append((origsrc, abssrc, relsrc, exact))
1167 srcs.append((origsrc, abssrc, relsrc, exact))
1166 if not srcs:
1168 if not srcs:
1167 continue
1169 continue
1168 copylist.append((tfn(pat, dest, srcs), srcs))
1170 copylist.append((tfn(pat, dest, srcs), srcs))
1169 if not copylist:
1171 if not copylist:
1170 raise util.Abort(_('no files to copy'))
1172 raise util.Abort(_('no files to copy'))
1171
1173
1172 for targetpath, srcs in copylist:
1174 for targetpath, srcs in copylist:
1173 for origsrc, abssrc, relsrc, exact in srcs:
1175 for origsrc, abssrc, relsrc, exact in srcs:
1174 copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
1176 copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
1175
1177
1176 if errors:
1178 if errors:
1177 ui.warn(_('(consider using --after)\n'))
1179 ui.warn(_('(consider using --after)\n'))
1178 return errors, copied
1180 return errors, copied
1179
1181
1180 def copy(ui, repo, *pats, **opts):
1182 def copy(ui, repo, *pats, **opts):
1181 """mark files as copied for the next commit
1183 """mark files as copied for the next commit
1182
1184
1183 Mark dest as having copies of source files. If dest is a
1185 Mark dest as having copies of source files. If dest is a
1184 directory, copies are put in that directory. If dest is a file,
1186 directory, copies are put in that directory. If dest is a file,
1185 there can only be one source.
1187 there can only be one source.
1186
1188
1187 By default, this command copies the contents of files as they
1189 By default, this command copies the contents of files as they
1188 stand in the working directory. If invoked with --after, the
1190 stand in the working directory. If invoked with --after, the
1189 operation is recorded, but no copying is performed.
1191 operation is recorded, but no copying is performed.
1190
1192
1191 This command takes effect in the next commit.
1193 This command takes effect in the next commit.
1192
1194
1193 NOTE: This command should be treated as experimental. While it
1195 NOTE: This command should be treated as experimental. While it
1194 should properly record copied files, this information is not yet
1196 should properly record copied files, this information is not yet
1195 fully used by merge, nor fully reported by log.
1197 fully used by merge, nor fully reported by log.
1196 """
1198 """
1197 wlock = repo.wlock(0)
1199 wlock = repo.wlock(0)
1198 errs, copied = docopy(ui, repo, pats, opts, wlock)
1200 errs, copied = docopy(ui, repo, pats, opts, wlock)
1199 return errs
1201 return errs
1200
1202
1201 def debugancestor(ui, index, rev1, rev2):
1203 def debugancestor(ui, index, rev1, rev2):
1202 """find the ancestor revision of two revisions in a given index"""
1204 """find the ancestor revision of two revisions in a given index"""
1203 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index, "", 0)
1205 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index, "", 0)
1204 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
1206 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
1205 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1207 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1206
1208
1207 def debugcomplete(ui, cmd='', **opts):
1209 def debugcomplete(ui, cmd='', **opts):
1208 """returns the completion list associated with the given command"""
1210 """returns the completion list associated with the given command"""
1209
1211
1210 if opts['options']:
1212 if opts['options']:
1211 options = []
1213 options = []
1212 otables = [globalopts]
1214 otables = [globalopts]
1213 if cmd:
1215 if cmd:
1214 aliases, entry = find(cmd)
1216 aliases, entry = find(cmd)
1215 otables.append(entry[1])
1217 otables.append(entry[1])
1216 for t in otables:
1218 for t in otables:
1217 for o in t:
1219 for o in t:
1218 if o[0]:
1220 if o[0]:
1219 options.append('-%s' % o[0])
1221 options.append('-%s' % o[0])
1220 options.append('--%s' % o[1])
1222 options.append('--%s' % o[1])
1221 ui.write("%s\n" % "\n".join(options))
1223 ui.write("%s\n" % "\n".join(options))
1222 return
1224 return
1223
1225
1224 clist = findpossible(cmd).keys()
1226 clist = findpossible(cmd).keys()
1225 clist.sort()
1227 clist.sort()
1226 ui.write("%s\n" % "\n".join(clist))
1228 ui.write("%s\n" % "\n".join(clist))
1227
1229
1228 def debugrebuildstate(ui, repo, rev=None):
1230 def debugrebuildstate(ui, repo, rev=None):
1229 """rebuild the dirstate as it would look like for the given revision"""
1231 """rebuild the dirstate as it would look like for the given revision"""
1230 if not rev:
1232 if not rev:
1231 rev = repo.changelog.tip()
1233 rev = repo.changelog.tip()
1232 else:
1234 else:
1233 rev = repo.lookup(rev)
1235 rev = repo.lookup(rev)
1234 change = repo.changelog.read(rev)
1236 change = repo.changelog.read(rev)
1235 n = change[0]
1237 n = change[0]
1236 files = repo.manifest.readflags(n)
1238 files = repo.manifest.readflags(n)
1237 wlock = repo.wlock()
1239 wlock = repo.wlock()
1238 repo.dirstate.rebuild(rev, files.iteritems())
1240 repo.dirstate.rebuild(rev, files.iteritems())
1239
1241
1240 def debugcheckstate(ui, repo):
1242 def debugcheckstate(ui, repo):
1241 """validate the correctness of the current dirstate"""
1243 """validate the correctness of the current dirstate"""
1242 parent1, parent2 = repo.dirstate.parents()
1244 parent1, parent2 = repo.dirstate.parents()
1243 repo.dirstate.read()
1245 repo.dirstate.read()
1244 dc = repo.dirstate.map
1246 dc = repo.dirstate.map
1245 keys = dc.keys()
1247 keys = dc.keys()
1246 keys.sort()
1248 keys.sort()
1247 m1n = repo.changelog.read(parent1)[0]
1249 m1n = repo.changelog.read(parent1)[0]
1248 m2n = repo.changelog.read(parent2)[0]
1250 m2n = repo.changelog.read(parent2)[0]
1249 m1 = repo.manifest.read(m1n)
1251 m1 = repo.manifest.read(m1n)
1250 m2 = repo.manifest.read(m2n)
1252 m2 = repo.manifest.read(m2n)
1251 errors = 0
1253 errors = 0
1252 for f in dc:
1254 for f in dc:
1253 state = repo.dirstate.state(f)
1255 state = repo.dirstate.state(f)
1254 if state in "nr" and f not in m1:
1256 if state in "nr" and f not in m1:
1255 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1257 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1256 errors += 1
1258 errors += 1
1257 if state in "a" and f in m1:
1259 if state in "a" and f in m1:
1258 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1260 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1259 errors += 1
1261 errors += 1
1260 if state in "m" and f not in m1 and f not in m2:
1262 if state in "m" and f not in m1 and f not in m2:
1261 ui.warn(_("%s in state %s, but not in either manifest\n") %
1263 ui.warn(_("%s in state %s, but not in either manifest\n") %
1262 (f, state))
1264 (f, state))
1263 errors += 1
1265 errors += 1
1264 for f in m1:
1266 for f in m1:
1265 state = repo.dirstate.state(f)
1267 state = repo.dirstate.state(f)
1266 if state not in "nrm":
1268 if state not in "nrm":
1267 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1269 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1268 errors += 1
1270 errors += 1
1269 if errors:
1271 if errors:
1270 error = _(".hg/dirstate inconsistent with current parent's manifest")
1272 error = _(".hg/dirstate inconsistent with current parent's manifest")
1271 raise util.Abort(error)
1273 raise util.Abort(error)
1272
1274
1273 def debugconfig(ui, repo):
1275 def debugconfig(ui, repo):
1274 """show combined config settings from all hgrc files"""
1276 """show combined config settings from all hgrc files"""
1275 for section, name, value in ui.walkconfig():
1277 for section, name, value in ui.walkconfig():
1276 ui.write('%s.%s=%s\n' % (section, name, value))
1278 ui.write('%s.%s=%s\n' % (section, name, value))
1277
1279
1278 def debugsetparents(ui, repo, rev1, rev2=None):
1280 def debugsetparents(ui, repo, rev1, rev2=None):
1279 """manually set the parents of the current working directory
1281 """manually set the parents of the current working directory
1280
1282
1281 This is useful for writing repository conversion tools, but should
1283 This is useful for writing repository conversion tools, but should
1282 be used with care.
1284 be used with care.
1283 """
1285 """
1284
1286
1285 if not rev2:
1287 if not rev2:
1286 rev2 = hex(nullid)
1288 rev2 = hex(nullid)
1287
1289
1288 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1290 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1289
1291
1290 def debugstate(ui, repo):
1292 def debugstate(ui, repo):
1291 """show the contents of the current dirstate"""
1293 """show the contents of the current dirstate"""
1292 repo.dirstate.read()
1294 repo.dirstate.read()
1293 dc = repo.dirstate.map
1295 dc = repo.dirstate.map
1294 keys = dc.keys()
1296 keys = dc.keys()
1295 keys.sort()
1297 keys.sort()
1296 for file_ in keys:
1298 for file_ in keys:
1297 ui.write("%c %3o %10d %s %s\n"
1299 ui.write("%c %3o %10d %s %s\n"
1298 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
1300 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
1299 time.strftime("%x %X",
1301 time.strftime("%x %X",
1300 time.localtime(dc[file_][3])), file_))
1302 time.localtime(dc[file_][3])), file_))
1301 for f in repo.dirstate.copies:
1303 for f in repo.dirstate.copies:
1302 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copies[f], f))
1304 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copies[f], f))
1303
1305
1304 def debugdata(ui, file_, rev):
1306 def debugdata(ui, file_, rev):
1305 """dump the contents of an data file revision"""
1307 """dump the contents of an data file revision"""
1306 r = revlog.revlog(util.opener(os.getcwd(), audit=False),
1308 r = revlog.revlog(util.opener(os.getcwd(), audit=False),
1307 file_[:-2] + ".i", file_, 0)
1309 file_[:-2] + ".i", file_, 0)
1308 try:
1310 try:
1309 ui.write(r.revision(r.lookup(rev)))
1311 ui.write(r.revision(r.lookup(rev)))
1310 except KeyError:
1312 except KeyError:
1311 raise util.Abort(_('invalid revision identifier %s'), rev)
1313 raise util.Abort(_('invalid revision identifier %s'), rev)
1312
1314
1313 def debugindex(ui, file_):
1315 def debugindex(ui, file_):
1314 """dump the contents of an index file"""
1316 """dump the contents of an index file"""
1315 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1317 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1316 ui.write(" rev offset length base linkrev" +
1318 ui.write(" rev offset length base linkrev" +
1317 " nodeid p1 p2\n")
1319 " nodeid p1 p2\n")
1318 for i in range(r.count()):
1320 for i in range(r.count()):
1319 node = r.node(i)
1321 node = r.node(i)
1320 pp = r.parents(node)
1322 pp = r.parents(node)
1321 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1323 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1322 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
1324 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
1323 short(node), short(pp[0]), short(pp[1])))
1325 short(node), short(pp[0]), short(pp[1])))
1324
1326
1325 def debugindexdot(ui, file_):
1327 def debugindexdot(ui, file_):
1326 """dump an index DAG as a .dot file"""
1328 """dump an index DAG as a .dot file"""
1327 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1329 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1328 ui.write("digraph G {\n")
1330 ui.write("digraph G {\n")
1329 for i in range(r.count()):
1331 for i in range(r.count()):
1330 node = r.node(i)
1332 node = r.node(i)
1331 pp = r.parents(node)
1333 pp = r.parents(node)
1332 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1334 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1333 if pp[1] != nullid:
1335 if pp[1] != nullid:
1334 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1336 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1335 ui.write("}\n")
1337 ui.write("}\n")
1336
1338
1337 def debugrename(ui, repo, file, rev=None):
1339 def debugrename(ui, repo, file, rev=None):
1338 """dump rename information"""
1340 """dump rename information"""
1339 r = repo.file(relpath(repo, [file])[0])
1341 r = repo.file(relpath(repo, [file])[0])
1340 if rev:
1342 if rev:
1341 try:
1343 try:
1342 # assume all revision numbers are for changesets
1344 # assume all revision numbers are for changesets
1343 n = repo.lookup(rev)
1345 n = repo.lookup(rev)
1344 change = repo.changelog.read(n)
1346 change = repo.changelog.read(n)
1345 m = repo.manifest.read(change[0])
1347 m = repo.manifest.read(change[0])
1346 n = m[relpath(repo, [file])[0]]
1348 n = m[relpath(repo, [file])[0]]
1347 except (hg.RepoError, KeyError):
1349 except (hg.RepoError, KeyError):
1348 n = r.lookup(rev)
1350 n = r.lookup(rev)
1349 else:
1351 else:
1350 n = r.tip()
1352 n = r.tip()
1351 m = r.renamed(n)
1353 m = r.renamed(n)
1352 if m:
1354 if m:
1353 ui.write(_("renamed from %s:%s\n") % (m[0], hex(m[1])))
1355 ui.write(_("renamed from %s:%s\n") % (m[0], hex(m[1])))
1354 else:
1356 else:
1355 ui.write(_("not renamed\n"))
1357 ui.write(_("not renamed\n"))
1356
1358
1357 def debugwalk(ui, repo, *pats, **opts):
1359 def debugwalk(ui, repo, *pats, **opts):
1358 """show how files match on given patterns"""
1360 """show how files match on given patterns"""
1359 items = list(walk(repo, pats, opts))
1361 items = list(walk(repo, pats, opts))
1360 if not items:
1362 if not items:
1361 return
1363 return
1362 fmt = '%%s %%-%ds %%-%ds %%s' % (
1364 fmt = '%%s %%-%ds %%-%ds %%s' % (
1363 max([len(abs) for (src, abs, rel, exact) in items]),
1365 max([len(abs) for (src, abs, rel, exact) in items]),
1364 max([len(rel) for (src, abs, rel, exact) in items]))
1366 max([len(rel) for (src, abs, rel, exact) in items]))
1365 for src, abs, rel, exact in items:
1367 for src, abs, rel, exact in items:
1366 line = fmt % (src, abs, rel, exact and 'exact' or '')
1368 line = fmt % (src, abs, rel, exact and 'exact' or '')
1367 ui.write("%s\n" % line.rstrip())
1369 ui.write("%s\n" % line.rstrip())
1368
1370
1369 def diff(ui, repo, *pats, **opts):
1371 def diff(ui, repo, *pats, **opts):
1370 """diff repository (or selected files)
1372 """diff repository (or selected files)
1371
1373
1372 Show differences between revisions for the specified files.
1374 Show differences between revisions for the specified files.
1373
1375
1374 Differences between files are shown using the unified diff format.
1376 Differences between files are shown using the unified diff format.
1375
1377
1376 When two revision arguments are given, then changes are shown
1378 When two revision arguments are given, then changes are shown
1377 between those revisions. If only one revision is specified then
1379 between those revisions. If only one revision is specified then
1378 that revision is compared to the working directory, and, when no
1380 that revision is compared to the working directory, and, when no
1379 revisions are specified, the working directory files are compared
1381 revisions are specified, the working directory files are compared
1380 to its parent.
1382 to its parent.
1381
1383
1382 Without the -a option, diff will avoid generating diffs of files
1384 Without the -a option, diff will avoid generating diffs of files
1383 it detects as binary. With -a, diff will generate a diff anyway,
1385 it detects as binary. With -a, diff will generate a diff anyway,
1384 probably with undesirable results.
1386 probably with undesirable results.
1385 """
1387 """
1386 node1, node2 = revpair(ui, repo, opts['rev'])
1388 node1, node2 = revpair(ui, repo, opts['rev'])
1387
1389
1388 fns, matchfn, anypats = matchpats(repo, pats, opts)
1390 fns, matchfn, anypats = matchpats(repo, pats, opts)
1389
1391
1390 dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn,
1392 dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn,
1391 text=opts['text'], opts=opts)
1393 text=opts['text'], opts=opts)
1392
1394
1393 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
1395 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
1394 node = repo.lookup(changeset)
1396 node = repo.lookup(changeset)
1395 parents = [p for p in repo.changelog.parents(node) if p != nullid]
1397 parents = [p for p in repo.changelog.parents(node) if p != nullid]
1396 if opts['switch_parent']:
1398 if opts['switch_parent']:
1397 parents.reverse()
1399 parents.reverse()
1398 prev = (parents and parents[0]) or nullid
1400 prev = (parents and parents[0]) or nullid
1399 change = repo.changelog.read(node)
1401 change = repo.changelog.read(node)
1400
1402
1401 fp = make_file(repo, repo.changelog, opts['output'],
1403 fp = make_file(repo, repo.changelog, opts['output'],
1402 node=node, total=total, seqno=seqno,
1404 node=node, total=total, seqno=seqno,
1403 revwidth=revwidth)
1405 revwidth=revwidth)
1404 if fp != sys.stdout:
1406 if fp != sys.stdout:
1405 ui.note("%s\n" % fp.name)
1407 ui.note("%s\n" % fp.name)
1406
1408
1407 fp.write("# HG changeset patch\n")
1409 fp.write("# HG changeset patch\n")
1408 fp.write("# User %s\n" % change[1])
1410 fp.write("# User %s\n" % change[1])
1409 fp.write("# Date %d %d\n" % change[2])
1411 fp.write("# Date %d %d\n" % change[2])
1410 fp.write("# Node ID %s\n" % hex(node))
1412 fp.write("# Node ID %s\n" % hex(node))
1411 fp.write("# Parent %s\n" % hex(prev))
1413 fp.write("# Parent %s\n" % hex(prev))
1412 if len(parents) > 1:
1414 if len(parents) > 1:
1413 fp.write("# Parent %s\n" % hex(parents[1]))
1415 fp.write("# Parent %s\n" % hex(parents[1]))
1414 fp.write(change[4].rstrip())
1416 fp.write(change[4].rstrip())
1415 fp.write("\n\n")
1417 fp.write("\n\n")
1416
1418
1417 dodiff(fp, ui, repo, prev, node, text=opts['text'])
1419 dodiff(fp, ui, repo, prev, node, text=opts['text'])
1418 if fp != sys.stdout:
1420 if fp != sys.stdout:
1419 fp.close()
1421 fp.close()
1420
1422
1421 def export(ui, repo, *changesets, **opts):
1423 def export(ui, repo, *changesets, **opts):
1422 """dump the header and diffs for one or more changesets
1424 """dump the header and diffs for one or more changesets
1423
1425
1424 Print the changeset header and diffs for one or more revisions.
1426 Print the changeset header and diffs for one or more revisions.
1425
1427
1426 The information shown in the changeset header is: author,
1428 The information shown in the changeset header is: author,
1427 changeset hash, parent and commit comment.
1429 changeset hash, parent and commit comment.
1428
1430
1429 Output may be to a file, in which case the name of the file is
1431 Output may be to a file, in which case the name of the file is
1430 given using a format string. The formatting rules are as follows:
1432 given using a format string. The formatting rules are as follows:
1431
1433
1432 %% literal "%" character
1434 %% literal "%" character
1433 %H changeset hash (40 bytes of hexadecimal)
1435 %H changeset hash (40 bytes of hexadecimal)
1434 %N number of patches being generated
1436 %N number of patches being generated
1435 %R changeset revision number
1437 %R changeset revision number
1436 %b basename of the exporting repository
1438 %b basename of the exporting repository
1437 %h short-form changeset hash (12 bytes of hexadecimal)
1439 %h short-form changeset hash (12 bytes of hexadecimal)
1438 %n zero-padded sequence number, starting at 1
1440 %n zero-padded sequence number, starting at 1
1439 %r zero-padded changeset revision number
1441 %r zero-padded changeset revision number
1440
1442
1441 Without the -a option, export will avoid generating diffs of files
1443 Without the -a option, export will avoid generating diffs of files
1442 it detects as binary. With -a, export will generate a diff anyway,
1444 it detects as binary. With -a, export will generate a diff anyway,
1443 probably with undesirable results.
1445 probably with undesirable results.
1444
1446
1445 With the --switch-parent option, the diff will be against the second
1447 With the --switch-parent option, the diff will be against the second
1446 parent. It can be useful to review a merge.
1448 parent. It can be useful to review a merge.
1447 """
1449 """
1448 if not changesets:
1450 if not changesets:
1449 raise util.Abort(_("export requires at least one changeset"))
1451 raise util.Abort(_("export requires at least one changeset"))
1450 seqno = 0
1452 seqno = 0
1451 revs = list(revrange(ui, repo, changesets))
1453 revs = list(revrange(ui, repo, changesets))
1452 total = len(revs)
1454 total = len(revs)
1453 revwidth = max(map(len, revs))
1455 revwidth = max(map(len, revs))
1454 msg = len(revs) > 1 and _("Exporting patches:\n") or _("Exporting patch:\n")
1456 msg = len(revs) > 1 and _("Exporting patches:\n") or _("Exporting patch:\n")
1455 ui.note(msg)
1457 ui.note(msg)
1456 for cset in revs:
1458 for cset in revs:
1457 seqno += 1
1459 seqno += 1
1458 doexport(ui, repo, cset, seqno, total, revwidth, opts)
1460 doexport(ui, repo, cset, seqno, total, revwidth, opts)
1459
1461
1460 def forget(ui, repo, *pats, **opts):
1462 def forget(ui, repo, *pats, **opts):
1461 """don't add the specified files on the next commit (DEPRECATED)
1463 """don't add the specified files on the next commit (DEPRECATED)
1462
1464
1463 (DEPRECATED)
1465 (DEPRECATED)
1464 Undo an 'hg add' scheduled for the next commit.
1466 Undo an 'hg add' scheduled for the next commit.
1465
1467
1466 This command is now deprecated and will be removed in a future
1468 This command is now deprecated and will be removed in a future
1467 release. Please use revert instead.
1469 release. Please use revert instead.
1468 """
1470 """
1469 ui.warn(_("(the forget command is deprecated; use revert instead)\n"))
1471 ui.warn(_("(the forget command is deprecated; use revert instead)\n"))
1470 forget = []
1472 forget = []
1471 for src, abs, rel, exact in walk(repo, pats, opts):
1473 for src, abs, rel, exact in walk(repo, pats, opts):
1472 if repo.dirstate.state(abs) == 'a':
1474 if repo.dirstate.state(abs) == 'a':
1473 forget.append(abs)
1475 forget.append(abs)
1474 if ui.verbose or not exact:
1476 if ui.verbose or not exact:
1475 ui.status(_('forgetting %s\n') % ((pats and rel) or abs))
1477 ui.status(_('forgetting %s\n') % ((pats and rel) or abs))
1476 repo.forget(forget)
1478 repo.forget(forget)
1477
1479
1478 def grep(ui, repo, pattern, *pats, **opts):
1480 def grep(ui, repo, pattern, *pats, **opts):
1479 """search for a pattern in specified files and revisions
1481 """search for a pattern in specified files and revisions
1480
1482
1481 Search revisions of files for a regular expression.
1483 Search revisions of files for a regular expression.
1482
1484
1483 This command behaves differently than Unix grep. It only accepts
1485 This command behaves differently than Unix grep. It only accepts
1484 Python/Perl regexps. It searches repository history, not the
1486 Python/Perl regexps. It searches repository history, not the
1485 working directory. It always prints the revision number in which
1487 working directory. It always prints the revision number in which
1486 a match appears.
1488 a match appears.
1487
1489
1488 By default, grep only prints output for the first revision of a
1490 By default, grep only prints output for the first revision of a
1489 file in which it finds a match. To get it to print every revision
1491 file in which it finds a match. To get it to print every revision
1490 that contains a change in match status ("-" for a match that
1492 that contains a change in match status ("-" for a match that
1491 becomes a non-match, or "+" for a non-match that becomes a match),
1493 becomes a non-match, or "+" for a non-match that becomes a match),
1492 use the --all flag.
1494 use the --all flag.
1493 """
1495 """
1494 reflags = 0
1496 reflags = 0
1495 if opts['ignore_case']:
1497 if opts['ignore_case']:
1496 reflags |= re.I
1498 reflags |= re.I
1497 regexp = re.compile(pattern, reflags)
1499 regexp = re.compile(pattern, reflags)
1498 sep, eol = ':', '\n'
1500 sep, eol = ':', '\n'
1499 if opts['print0']:
1501 if opts['print0']:
1500 sep = eol = '\0'
1502 sep = eol = '\0'
1501
1503
1502 fcache = {}
1504 fcache = {}
1503 def getfile(fn):
1505 def getfile(fn):
1504 if fn not in fcache:
1506 if fn not in fcache:
1505 fcache[fn] = repo.file(fn)
1507 fcache[fn] = repo.file(fn)
1506 return fcache[fn]
1508 return fcache[fn]
1507
1509
1508 def matchlines(body):
1510 def matchlines(body):
1509 begin = 0
1511 begin = 0
1510 linenum = 0
1512 linenum = 0
1511 while True:
1513 while True:
1512 match = regexp.search(body, begin)
1514 match = regexp.search(body, begin)
1513 if not match:
1515 if not match:
1514 break
1516 break
1515 mstart, mend = match.span()
1517 mstart, mend = match.span()
1516 linenum += body.count('\n', begin, mstart) + 1
1518 linenum += body.count('\n', begin, mstart) + 1
1517 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1519 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1518 lend = body.find('\n', mend)
1520 lend = body.find('\n', mend)
1519 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1521 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1520 begin = lend + 1
1522 begin = lend + 1
1521
1523
1522 class linestate(object):
1524 class linestate(object):
1523 def __init__(self, line, linenum, colstart, colend):
1525 def __init__(self, line, linenum, colstart, colend):
1524 self.line = line
1526 self.line = line
1525 self.linenum = linenum
1527 self.linenum = linenum
1526 self.colstart = colstart
1528 self.colstart = colstart
1527 self.colend = colend
1529 self.colend = colend
1528 def __eq__(self, other):
1530 def __eq__(self, other):
1529 return self.line == other.line
1531 return self.line == other.line
1530 def __hash__(self):
1532 def __hash__(self):
1531 return hash(self.line)
1533 return hash(self.line)
1532
1534
1533 matches = {}
1535 matches = {}
1534 def grepbody(fn, rev, body):
1536 def grepbody(fn, rev, body):
1535 matches[rev].setdefault(fn, {})
1537 matches[rev].setdefault(fn, {})
1536 m = matches[rev][fn]
1538 m = matches[rev][fn]
1537 for lnum, cstart, cend, line in matchlines(body):
1539 for lnum, cstart, cend, line in matchlines(body):
1538 s = linestate(line, lnum, cstart, cend)
1540 s = linestate(line, lnum, cstart, cend)
1539 m[s] = s
1541 m[s] = s
1540
1542
1541 # FIXME: prev isn't used, why ?
1543 # FIXME: prev isn't used, why ?
1542 prev = {}
1544 prev = {}
1543 ucache = {}
1545 ucache = {}
1544 def display(fn, rev, states, prevstates):
1546 def display(fn, rev, states, prevstates):
1545 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
1547 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
1546 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
1548 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
1547 counts = {'-': 0, '+': 0}
1549 counts = {'-': 0, '+': 0}
1548 filerevmatches = {}
1550 filerevmatches = {}
1549 for l in diff:
1551 for l in diff:
1550 if incrementing or not opts['all']:
1552 if incrementing or not opts['all']:
1551 change = ((l in prevstates) and '-') or '+'
1553 change = ((l in prevstates) and '-') or '+'
1552 r = rev
1554 r = rev
1553 else:
1555 else:
1554 change = ((l in states) and '-') or '+'
1556 change = ((l in states) and '-') or '+'
1555 r = prev[fn]
1557 r = prev[fn]
1556 cols = [fn, str(rev)]
1558 cols = [fn, str(rev)]
1557 if opts['line_number']:
1559 if opts['line_number']:
1558 cols.append(str(l.linenum))
1560 cols.append(str(l.linenum))
1559 if opts['all']:
1561 if opts['all']:
1560 cols.append(change)
1562 cols.append(change)
1561 if opts['user']:
1563 if opts['user']:
1562 cols.append(trimuser(ui, getchange(rev)[1], rev,
1564 cols.append(trimuser(ui, getchange(rev)[1], rev,
1563 ucache))
1565 ucache))
1564 if opts['files_with_matches']:
1566 if opts['files_with_matches']:
1565 c = (fn, rev)
1567 c = (fn, rev)
1566 if c in filerevmatches:
1568 if c in filerevmatches:
1567 continue
1569 continue
1568 filerevmatches[c] = 1
1570 filerevmatches[c] = 1
1569 else:
1571 else:
1570 cols.append(l.line)
1572 cols.append(l.line)
1571 ui.write(sep.join(cols), eol)
1573 ui.write(sep.join(cols), eol)
1572 counts[change] += 1
1574 counts[change] += 1
1573 return counts['+'], counts['-']
1575 return counts['+'], counts['-']
1574
1576
1575 fstate = {}
1577 fstate = {}
1576 skip = {}
1578 skip = {}
1577 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1579 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1578 count = 0
1580 count = 0
1579 incrementing = False
1581 incrementing = False
1580 for st, rev, fns in changeiter:
1582 for st, rev, fns in changeiter:
1581 if st == 'window':
1583 if st == 'window':
1582 incrementing = rev
1584 incrementing = rev
1583 matches.clear()
1585 matches.clear()
1584 elif st == 'add':
1586 elif st == 'add':
1585 change = repo.changelog.read(repo.lookup(str(rev)))
1587 change = repo.changelog.read(repo.lookup(str(rev)))
1586 mf = repo.manifest.read(change[0])
1588 mf = repo.manifest.read(change[0])
1587 matches[rev] = {}
1589 matches[rev] = {}
1588 for fn in fns:
1590 for fn in fns:
1589 if fn in skip:
1591 if fn in skip:
1590 continue
1592 continue
1591 fstate.setdefault(fn, {})
1593 fstate.setdefault(fn, {})
1592 try:
1594 try:
1593 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1595 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1594 except KeyError:
1596 except KeyError:
1595 pass
1597 pass
1596 elif st == 'iter':
1598 elif st == 'iter':
1597 states = matches[rev].items()
1599 states = matches[rev].items()
1598 states.sort()
1600 states.sort()
1599 for fn, m in states:
1601 for fn, m in states:
1600 if fn in skip:
1602 if fn in skip:
1601 continue
1603 continue
1602 if incrementing or not opts['all'] or fstate[fn]:
1604 if incrementing or not opts['all'] or fstate[fn]:
1603 pos, neg = display(fn, rev, m, fstate[fn])
1605 pos, neg = display(fn, rev, m, fstate[fn])
1604 count += pos + neg
1606 count += pos + neg
1605 if pos and not opts['all']:
1607 if pos and not opts['all']:
1606 skip[fn] = True
1608 skip[fn] = True
1607 fstate[fn] = m
1609 fstate[fn] = m
1608 prev[fn] = rev
1610 prev[fn] = rev
1609
1611
1610 if not incrementing:
1612 if not incrementing:
1611 fstate = fstate.items()
1613 fstate = fstate.items()
1612 fstate.sort()
1614 fstate.sort()
1613 for fn, state in fstate:
1615 for fn, state in fstate:
1614 if fn in skip:
1616 if fn in skip:
1615 continue
1617 continue
1616 display(fn, rev, {}, state)
1618 display(fn, rev, {}, state)
1617 return (count == 0 and 1) or 0
1619 return (count == 0 and 1) or 0
1618
1620
1619 def heads(ui, repo, **opts):
1621 def heads(ui, repo, **opts):
1620 """show current repository heads
1622 """show current repository heads
1621
1623
1622 Show all repository head changesets.
1624 Show all repository head changesets.
1623
1625
1624 Repository "heads" are changesets that don't have children
1626 Repository "heads" are changesets that don't have children
1625 changesets. They are where development generally takes place and
1627 changesets. They are where development generally takes place and
1626 are the usual targets for update and merge operations.
1628 are the usual targets for update and merge operations.
1627 """
1629 """
1628 if opts['rev']:
1630 if opts['rev']:
1629 heads = repo.heads(repo.lookup(opts['rev']))
1631 heads = repo.heads(repo.lookup(opts['rev']))
1630 else:
1632 else:
1631 heads = repo.heads()
1633 heads = repo.heads()
1632 br = None
1634 br = None
1633 if opts['branches']:
1635 if opts['branches']:
1634 br = repo.branchlookup(heads)
1636 br = repo.branchlookup(heads)
1635 displayer = show_changeset(ui, repo, opts)
1637 displayer = show_changeset(ui, repo, opts)
1636 for n in heads:
1638 for n in heads:
1637 displayer.show(changenode=n, brinfo=br)
1639 displayer.show(changenode=n, brinfo=br)
1638
1640
1639 def identify(ui, repo):
1641 def identify(ui, repo):
1640 """print information about the working copy
1642 """print information about the working copy
1641
1643
1642 Print a short summary of the current state of the repo.
1644 Print a short summary of the current state of the repo.
1643
1645
1644 This summary identifies the repository state using one or two parent
1646 This summary identifies the repository state using one or two parent
1645 hash identifiers, followed by a "+" if there are uncommitted changes
1647 hash identifiers, followed by a "+" if there are uncommitted changes
1646 in the working directory, followed by a list of tags for this revision.
1648 in the working directory, followed by a list of tags for this revision.
1647 """
1649 """
1648 parents = [p for p in repo.dirstate.parents() if p != nullid]
1650 parents = [p for p in repo.dirstate.parents() if p != nullid]
1649 if not parents:
1651 if not parents:
1650 ui.write(_("unknown\n"))
1652 ui.write(_("unknown\n"))
1651 return
1653 return
1652
1654
1653 hexfunc = ui.verbose and hex or short
1655 hexfunc = ui.verbose and hex or short
1654 modified, added, removed, deleted, unknown = repo.changes()
1656 modified, added, removed, deleted, unknown = repo.changes()
1655 output = ["%s%s" %
1657 output = ["%s%s" %
1656 ('+'.join([hexfunc(parent) for parent in parents]),
1658 ('+'.join([hexfunc(parent) for parent in parents]),
1657 (modified or added or removed or deleted) and "+" or "")]
1659 (modified or added or removed or deleted) and "+" or "")]
1658
1660
1659 if not ui.quiet:
1661 if not ui.quiet:
1660 # multiple tags for a single parent separated by '/'
1662 # multiple tags for a single parent separated by '/'
1661 parenttags = ['/'.join(tags)
1663 parenttags = ['/'.join(tags)
1662 for tags in map(repo.nodetags, parents) if tags]
1664 for tags in map(repo.nodetags, parents) if tags]
1663 # tags for multiple parents separated by ' + '
1665 # tags for multiple parents separated by ' + '
1664 if parenttags:
1666 if parenttags:
1665 output.append(' + '.join(parenttags))
1667 output.append(' + '.join(parenttags))
1666
1668
1667 ui.write("%s\n" % ' '.join(output))
1669 ui.write("%s\n" % ' '.join(output))
1668
1670
1669 def import_(ui, repo, patch1, *patches, **opts):
1671 def import_(ui, repo, patch1, *patches, **opts):
1670 """import an ordered set of patches
1672 """import an ordered set of patches
1671
1673
1672 Import a list of patches and commit them individually.
1674 Import a list of patches and commit them individually.
1673
1675
1674 If there are outstanding changes in the working directory, import
1676 If there are outstanding changes in the working directory, import
1675 will abort unless given the -f flag.
1677 will abort unless given the -f flag.
1676
1678
1677 If a patch looks like a mail message (its first line starts with
1679 If a patch looks like a mail message (its first line starts with
1678 "From " or looks like an RFC822 header), it will not be applied
1680 "From " or looks like an RFC822 header), it will not be applied
1679 unless the -f option is used. The importer neither parses nor
1681 unless the -f option is used. The importer neither parses nor
1680 discards mail headers, so use -f only to override the "mailness"
1682 discards mail headers, so use -f only to override the "mailness"
1681 safety check, not to import a real mail message.
1683 safety check, not to import a real mail message.
1682 """
1684 """
1683 patches = (patch1,) + patches
1685 patches = (patch1,) + patches
1684
1686
1685 if not opts['force']:
1687 if not opts['force']:
1686 bail_if_changed(repo)
1688 bail_if_changed(repo)
1687
1689
1688 d = opts["base"]
1690 d = opts["base"]
1689 strip = opts["strip"]
1691 strip = opts["strip"]
1690
1692
1691 mailre = re.compile(r'(?:From |[\w-]+:)')
1693 mailre = re.compile(r'(?:From |[\w-]+:)')
1692
1694
1693 # attempt to detect the start of a patch
1695 # attempt to detect the start of a patch
1694 # (this heuristic is borrowed from quilt)
1696 # (this heuristic is borrowed from quilt)
1695 diffre = re.compile(r'(?:Index:[ \t]|diff[ \t]|RCS file: |' +
1697 diffre = re.compile(r'(?:Index:[ \t]|diff[ \t]|RCS file: |' +
1696 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
1698 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
1697 '(---|\*\*\*)[ \t])')
1699 '(---|\*\*\*)[ \t])')
1698
1700
1699 for patch in patches:
1701 for patch in patches:
1700 ui.status(_("applying %s\n") % patch)
1702 ui.status(_("applying %s\n") % patch)
1701 pf = os.path.join(d, patch)
1703 pf = os.path.join(d, patch)
1702
1704
1703 message = []
1705 message = []
1704 user = None
1706 user = None
1705 date = None
1707 date = None
1706 hgpatch = False
1708 hgpatch = False
1707 for line in file(pf):
1709 for line in file(pf):
1708 line = line.rstrip()
1710 line = line.rstrip()
1709 if (not message and not hgpatch and
1711 if (not message and not hgpatch and
1710 mailre.match(line) and not opts['force']):
1712 mailre.match(line) and not opts['force']):
1711 if len(line) > 35:
1713 if len(line) > 35:
1712 line = line[:32] + '...'
1714 line = line[:32] + '...'
1713 raise util.Abort(_('first line looks like a '
1715 raise util.Abort(_('first line looks like a '
1714 'mail header: ') + line)
1716 'mail header: ') + line)
1715 if diffre.match(line):
1717 if diffre.match(line):
1716 break
1718 break
1717 elif hgpatch:
1719 elif hgpatch:
1718 # parse values when importing the result of an hg export
1720 # parse values when importing the result of an hg export
1719 if line.startswith("# User "):
1721 if line.startswith("# User "):
1720 user = line[7:]
1722 user = line[7:]
1721 ui.debug(_('User: %s\n') % user)
1723 ui.debug(_('User: %s\n') % user)
1722 elif line.startswith("# Date "):
1724 elif line.startswith("# Date "):
1723 date = line[7:]
1725 date = line[7:]
1724 elif not line.startswith("# ") and line:
1726 elif not line.startswith("# ") and line:
1725 message.append(line)
1727 message.append(line)
1726 hgpatch = False
1728 hgpatch = False
1727 elif line == '# HG changeset patch':
1729 elif line == '# HG changeset patch':
1728 hgpatch = True
1730 hgpatch = True
1729 message = [] # We may have collected garbage
1731 message = [] # We may have collected garbage
1730 elif message or line:
1732 elif message or line:
1731 message.append(line)
1733 message.append(line)
1732
1734
1733 # make sure message isn't empty
1735 # make sure message isn't empty
1734 if not message:
1736 if not message:
1735 message = _("imported patch %s\n") % patch
1737 message = _("imported patch %s\n") % patch
1736 else:
1738 else:
1737 message = '\n'.join(message).rstrip()
1739 message = '\n'.join(message).rstrip()
1738 ui.debug(_('message:\n%s\n') % message)
1740 ui.debug(_('message:\n%s\n') % message)
1739
1741
1740 files = util.patch(strip, pf, ui)
1742 files = util.patch(strip, pf, ui)
1741
1743
1742 if len(files) > 0:
1744 if len(files) > 0:
1743 addremove_lock(ui, repo, files, {})
1745 addremove_lock(ui, repo, files, {})
1744 repo.commit(files, message, user, date)
1746 repo.commit(files, message, user, date)
1745
1747
1746 def incoming(ui, repo, source="default", **opts):
1748 def incoming(ui, repo, source="default", **opts):
1747 """show new changesets found in source
1749 """show new changesets found in source
1748
1750
1749 Show new changesets found in the specified path/URL or the default
1751 Show new changesets found in the specified path/URL or the default
1750 pull location. These are the changesets that would be pulled if a pull
1752 pull location. These are the changesets that would be pulled if a pull
1751 was requested.
1753 was requested.
1752
1754
1753 For remote repository, using --bundle avoids downloading the changesets
1755 For remote repository, using --bundle avoids downloading the changesets
1754 twice if the incoming is followed by a pull.
1756 twice if the incoming is followed by a pull.
1755
1757
1756 See pull for valid source format details.
1758 See pull for valid source format details.
1757 """
1759 """
1758 source = ui.expandpath(source)
1760 source = ui.expandpath(source)
1759 if opts['ssh']:
1761 if opts['ssh']:
1760 ui.setconfig("ui", "ssh", opts['ssh'])
1762 ui.setconfig("ui", "ssh", opts['ssh'])
1761 if opts['remotecmd']:
1763 if opts['remotecmd']:
1762 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1764 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1763
1765
1764 other = hg.repository(ui, source)
1766 other = hg.repository(ui, source)
1765 incoming = repo.findincoming(other, force=opts["force"])
1767 incoming = repo.findincoming(other, force=opts["force"])
1766 if not incoming:
1768 if not incoming:
1767 ui.status(_("no changes found\n"))
1769 ui.status(_("no changes found\n"))
1768 return
1770 return
1769
1771
1770 cleanup = None
1772 cleanup = None
1771 try:
1773 try:
1772 fname = opts["bundle"]
1774 fname = opts["bundle"]
1773 if fname or not other.local():
1775 if fname or not other.local():
1774 # create a bundle (uncompressed if other repo is not local)
1776 # create a bundle (uncompressed if other repo is not local)
1775 cg = other.changegroup(incoming, "incoming")
1777 cg = other.changegroup(incoming, "incoming")
1776 fname = cleanup = write_bundle(cg, fname, compress=other.local())
1778 fname = cleanup = write_bundle(cg, fname, compress=other.local())
1777 # keep written bundle?
1779 # keep written bundle?
1778 if opts["bundle"]:
1780 if opts["bundle"]:
1779 cleanup = None
1781 cleanup = None
1780 if not other.local():
1782 if not other.local():
1781 # use the created uncompressed bundlerepo
1783 # use the created uncompressed bundlerepo
1782 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1784 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1783
1785
1784 o = other.changelog.nodesbetween(incoming)[0]
1786 o = other.changelog.nodesbetween(incoming)[0]
1785 if opts['newest_first']:
1787 if opts['newest_first']:
1786 o.reverse()
1788 o.reverse()
1787 displayer = show_changeset(ui, other, opts)
1789 displayer = show_changeset(ui, other, opts)
1788 for n in o:
1790 for n in o:
1789 parents = [p for p in other.changelog.parents(n) if p != nullid]
1791 parents = [p for p in other.changelog.parents(n) if p != nullid]
1790 if opts['no_merges'] and len(parents) == 2:
1792 if opts['no_merges'] and len(parents) == 2:
1791 continue
1793 continue
1792 displayer.show(changenode=n)
1794 displayer.show(changenode=n)
1793 if opts['patch']:
1795 if opts['patch']:
1794 prev = (parents and parents[0]) or nullid
1796 prev = (parents and parents[0]) or nullid
1795 dodiff(ui, ui, other, prev, n)
1797 dodiff(ui, ui, other, prev, n)
1796 ui.write("\n")
1798 ui.write("\n")
1797 finally:
1799 finally:
1798 if hasattr(other, 'close'):
1800 if hasattr(other, 'close'):
1799 other.close()
1801 other.close()
1800 if cleanup:
1802 if cleanup:
1801 os.unlink(cleanup)
1803 os.unlink(cleanup)
1802
1804
1803 def init(ui, dest="."):
1805 def init(ui, dest="."):
1804 """create a new repository in the given directory
1806 """create a new repository in the given directory
1805
1807
1806 Initialize a new repository in the given directory. If the given
1808 Initialize a new repository in the given directory. If the given
1807 directory does not exist, it is created.
1809 directory does not exist, it is created.
1808
1810
1809 If no directory is given, the current directory is used.
1811 If no directory is given, the current directory is used.
1810 """
1812 """
1811 if not os.path.exists(dest):
1813 if not os.path.exists(dest):
1812 os.mkdir(dest)
1814 os.mkdir(dest)
1813 hg.repository(ui, dest, create=1)
1815 hg.repository(ui, dest, create=1)
1814
1816
1815 def locate(ui, repo, *pats, **opts):
1817 def locate(ui, repo, *pats, **opts):
1816 """locate files matching specific patterns
1818 """locate files matching specific patterns
1817
1819
1818 Print all files under Mercurial control whose names match the
1820 Print all files under Mercurial control whose names match the
1819 given patterns.
1821 given patterns.
1820
1822
1821 This command searches the current directory and its
1823 This command searches the current directory and its
1822 subdirectories. To search an entire repository, move to the root
1824 subdirectories. To search an entire repository, move to the root
1823 of the repository.
1825 of the repository.
1824
1826
1825 If no patterns are given to match, this command prints all file
1827 If no patterns are given to match, this command prints all file
1826 names.
1828 names.
1827
1829
1828 If you want to feed the output of this command into the "xargs"
1830 If you want to feed the output of this command into the "xargs"
1829 command, use the "-0" option to both this command and "xargs".
1831 command, use the "-0" option to both this command and "xargs".
1830 This will avoid the problem of "xargs" treating single filenames
1832 This will avoid the problem of "xargs" treating single filenames
1831 that contain white space as multiple filenames.
1833 that contain white space as multiple filenames.
1832 """
1834 """
1833 end = opts['print0'] and '\0' or '\n'
1835 end = opts['print0'] and '\0' or '\n'
1834 rev = opts['rev']
1836 rev = opts['rev']
1835 if rev:
1837 if rev:
1836 node = repo.lookup(rev)
1838 node = repo.lookup(rev)
1837 else:
1839 else:
1838 node = None
1840 node = None
1839
1841
1840 for src, abs, rel, exact in walk(repo, pats, opts, node=node,
1842 for src, abs, rel, exact in walk(repo, pats, opts, node=node,
1841 head='(?:.*/|)'):
1843 head='(?:.*/|)'):
1842 if not node and repo.dirstate.state(abs) == '?':
1844 if not node and repo.dirstate.state(abs) == '?':
1843 continue
1845 continue
1844 if opts['fullpath']:
1846 if opts['fullpath']:
1845 ui.write(os.path.join(repo.root, abs), end)
1847 ui.write(os.path.join(repo.root, abs), end)
1846 else:
1848 else:
1847 ui.write(((pats and rel) or abs), end)
1849 ui.write(((pats and rel) or abs), end)
1848
1850
1849 def log(ui, repo, *pats, **opts):
1851 def log(ui, repo, *pats, **opts):
1850 """show revision history of entire repository or files
1852 """show revision history of entire repository or files
1851
1853
1852 Print the revision history of the specified files or the entire project.
1854 Print the revision history of the specified files or the entire project.
1853
1855
1854 By default this command outputs: changeset id and hash, tags,
1856 By default this command outputs: changeset id and hash, tags,
1855 non-trivial parents, user, date and time, and a summary for each
1857 non-trivial parents, user, date and time, and a summary for each
1856 commit. When the -v/--verbose switch is used, the list of changed
1858 commit. When the -v/--verbose switch is used, the list of changed
1857 files and full commit message is shown.
1859 files and full commit message is shown.
1858 """
1860 """
1859 class dui(object):
1861 class dui(object):
1860 # Implement and delegate some ui protocol. Save hunks of
1862 # Implement and delegate some ui protocol. Save hunks of
1861 # output for later display in the desired order.
1863 # output for later display in the desired order.
1862 def __init__(self, ui):
1864 def __init__(self, ui):
1863 self.ui = ui
1865 self.ui = ui
1864 self.hunk = {}
1866 self.hunk = {}
1865 self.header = {}
1867 self.header = {}
1866 def bump(self, rev):
1868 def bump(self, rev):
1867 self.rev = rev
1869 self.rev = rev
1868 self.hunk[rev] = []
1870 self.hunk[rev] = []
1869 self.header[rev] = []
1871 self.header[rev] = []
1870 def note(self, *args):
1872 def note(self, *args):
1871 if self.verbose:
1873 if self.verbose:
1872 self.write(*args)
1874 self.write(*args)
1873 def status(self, *args):
1875 def status(self, *args):
1874 if not self.quiet:
1876 if not self.quiet:
1875 self.write(*args)
1877 self.write(*args)
1876 def write(self, *args):
1878 def write(self, *args):
1877 self.hunk[self.rev].append(args)
1879 self.hunk[self.rev].append(args)
1878 def write_header(self, *args):
1880 def write_header(self, *args):
1879 self.header[self.rev].append(args)
1881 self.header[self.rev].append(args)
1880 def debug(self, *args):
1882 def debug(self, *args):
1881 if self.debugflag:
1883 if self.debugflag:
1882 self.write(*args)
1884 self.write(*args)
1883 def __getattr__(self, key):
1885 def __getattr__(self, key):
1884 return getattr(self.ui, key)
1886 return getattr(self.ui, key)
1885
1887
1886 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1888 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1887
1889
1888 if opts['limit']:
1890 if opts['limit']:
1889 try:
1891 try:
1890 limit = int(opts['limit'])
1892 limit = int(opts['limit'])
1891 except ValueError:
1893 except ValueError:
1892 raise util.Abort(_('limit must be a positive integer'))
1894 raise util.Abort(_('limit must be a positive integer'))
1893 if limit <= 0: raise util.Abort(_('limit must be positive'))
1895 if limit <= 0: raise util.Abort(_('limit must be positive'))
1894 else:
1896 else:
1895 limit = sys.maxint
1897 limit = sys.maxint
1896 count = 0
1898 count = 0
1897
1899
1898 displayer = show_changeset(ui, repo, opts)
1900 displayer = show_changeset(ui, repo, opts)
1899 for st, rev, fns in changeiter:
1901 for st, rev, fns in changeiter:
1900 if st == 'window':
1902 if st == 'window':
1901 du = dui(ui)
1903 du = dui(ui)
1902 displayer.ui = du
1904 displayer.ui = du
1903 elif st == 'add':
1905 elif st == 'add':
1904 du.bump(rev)
1906 du.bump(rev)
1905 changenode = repo.changelog.node(rev)
1907 changenode = repo.changelog.node(rev)
1906 parents = [p for p in repo.changelog.parents(changenode)
1908 parents = [p for p in repo.changelog.parents(changenode)
1907 if p != nullid]
1909 if p != nullid]
1908 if opts['no_merges'] and len(parents) == 2:
1910 if opts['no_merges'] and len(parents) == 2:
1909 continue
1911 continue
1910 if opts['only_merges'] and len(parents) != 2:
1912 if opts['only_merges'] and len(parents) != 2:
1911 continue
1913 continue
1912
1914
1913 if opts['keyword']:
1915 if opts['keyword']:
1914 changes = getchange(rev)
1916 changes = getchange(rev)
1915 miss = 0
1917 miss = 0
1916 for k in [kw.lower() for kw in opts['keyword']]:
1918 for k in [kw.lower() for kw in opts['keyword']]:
1917 if not (k in changes[1].lower() or
1919 if not (k in changes[1].lower() or
1918 k in changes[4].lower() or
1920 k in changes[4].lower() or
1919 k in " ".join(changes[3][:20]).lower()):
1921 k in " ".join(changes[3][:20]).lower()):
1920 miss = 1
1922 miss = 1
1921 break
1923 break
1922 if miss:
1924 if miss:
1923 continue
1925 continue
1924
1926
1925 br = None
1927 br = None
1926 if opts['branches']:
1928 if opts['branches']:
1927 br = repo.branchlookup([repo.changelog.node(rev)])
1929 br = repo.branchlookup([repo.changelog.node(rev)])
1928
1930
1929 displayer.show(rev, brinfo=br)
1931 displayer.show(rev, brinfo=br)
1930 if opts['patch']:
1932 if opts['patch']:
1931 prev = (parents and parents[0]) or nullid
1933 prev = (parents and parents[0]) or nullid
1932 dodiff(du, du, repo, prev, changenode, match=matchfn)
1934 dodiff(du, du, repo, prev, changenode, match=matchfn)
1933 du.write("\n\n")
1935 du.write("\n\n")
1934 elif st == 'iter':
1936 elif st == 'iter':
1935 if count == limit: break
1937 if count == limit: break
1936 if du.header[rev]:
1938 if du.header[rev]:
1937 for args in du.header[rev]:
1939 for args in du.header[rev]:
1938 ui.write_header(*args)
1940 ui.write_header(*args)
1939 if du.hunk[rev]:
1941 if du.hunk[rev]:
1940 count += 1
1942 count += 1
1941 for args in du.hunk[rev]:
1943 for args in du.hunk[rev]:
1942 ui.write(*args)
1944 ui.write(*args)
1943
1945
1944 def manifest(ui, repo, rev=None):
1946 def manifest(ui, repo, rev=None):
1945 """output the latest or given revision of the project manifest
1947 """output the latest or given revision of the project manifest
1946
1948
1947 Print a list of version controlled files for the given revision.
1949 Print a list of version controlled files for the given revision.
1948
1950
1949 The manifest is the list of files being version controlled. If no revision
1951 The manifest is the list of files being version controlled. If no revision
1950 is given then the tip is used.
1952 is given then the tip is used.
1951 """
1953 """
1952 if rev:
1954 if rev:
1953 try:
1955 try:
1954 # assume all revision numbers are for changesets
1956 # assume all revision numbers are for changesets
1955 n = repo.lookup(rev)
1957 n = repo.lookup(rev)
1956 change = repo.changelog.read(n)
1958 change = repo.changelog.read(n)
1957 n = change[0]
1959 n = change[0]
1958 except hg.RepoError:
1960 except hg.RepoError:
1959 n = repo.manifest.lookup(rev)
1961 n = repo.manifest.lookup(rev)
1960 else:
1962 else:
1961 n = repo.manifest.tip()
1963 n = repo.manifest.tip()
1962 m = repo.manifest.read(n)
1964 m = repo.manifest.read(n)
1963 mf = repo.manifest.readflags(n)
1965 mf = repo.manifest.readflags(n)
1964 files = m.keys()
1966 files = m.keys()
1965 files.sort()
1967 files.sort()
1966
1968
1967 for f in files:
1969 for f in files:
1968 ui.write("%40s %3s %s\n" % (hex(m[f]), mf[f] and "755" or "644", f))
1970 ui.write("%40s %3s %s\n" % (hex(m[f]), mf[f] and "755" or "644", f))
1969
1971
1970 def merge(ui, repo, node=None, **opts):
1972 def merge(ui, repo, node=None, **opts):
1971 """Merge working directory with another revision
1973 """Merge working directory with another revision
1972
1974
1973 Merge the contents of the current working directory and the
1975 Merge the contents of the current working directory and the
1974 requested revision. Files that changed between either parent are
1976 requested revision. Files that changed between either parent are
1975 marked as changed for the next commit and a commit must be
1977 marked as changed for the next commit and a commit must be
1976 performed before any further updates are allowed.
1978 performed before any further updates are allowed.
1977 """
1979 """
1978 return doupdate(ui, repo, node=node, merge=True, **opts)
1980 return doupdate(ui, repo, node=node, merge=True, **opts)
1979
1981
1980 def outgoing(ui, repo, dest="default-push", **opts):
1982 def outgoing(ui, repo, dest="default-push", **opts):
1981 """show changesets not found in destination
1983 """show changesets not found in destination
1982
1984
1983 Show changesets not found in the specified destination repository or
1985 Show changesets not found in the specified destination repository or
1984 the default push location. These are the changesets that would be pushed
1986 the default push location. These are the changesets that would be pushed
1985 if a push was requested.
1987 if a push was requested.
1986
1988
1987 See pull for valid destination format details.
1989 See pull for valid destination format details.
1988 """
1990 """
1989 dest = ui.expandpath(dest)
1991 dest = ui.expandpath(dest)
1990 if opts['ssh']:
1992 if opts['ssh']:
1991 ui.setconfig("ui", "ssh", opts['ssh'])
1993 ui.setconfig("ui", "ssh", opts['ssh'])
1992 if opts['remotecmd']:
1994 if opts['remotecmd']:
1993 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1995 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1994
1996
1995 other = hg.repository(ui, dest)
1997 other = hg.repository(ui, dest)
1996 o = repo.findoutgoing(other, force=opts['force'])
1998 o = repo.findoutgoing(other, force=opts['force'])
1997 if not o:
1999 if not o:
1998 ui.status(_("no changes found\n"))
2000 ui.status(_("no changes found\n"))
1999 return
2001 return
2000 o = repo.changelog.nodesbetween(o)[0]
2002 o = repo.changelog.nodesbetween(o)[0]
2001 if opts['newest_first']:
2003 if opts['newest_first']:
2002 o.reverse()
2004 o.reverse()
2003 displayer = show_changeset(ui, repo, opts)
2005 displayer = show_changeset(ui, repo, opts)
2004 for n in o:
2006 for n in o:
2005 parents = [p for p in repo.changelog.parents(n) if p != nullid]
2007 parents = [p for p in repo.changelog.parents(n) if p != nullid]
2006 if opts['no_merges'] and len(parents) == 2:
2008 if opts['no_merges'] and len(parents) == 2:
2007 continue
2009 continue
2008 displayer.show(changenode=n)
2010 displayer.show(changenode=n)
2009 if opts['patch']:
2011 if opts['patch']:
2010 prev = (parents and parents[0]) or nullid
2012 prev = (parents and parents[0]) or nullid
2011 dodiff(ui, ui, repo, prev, n)
2013 dodiff(ui, ui, repo, prev, n)
2012 ui.write("\n")
2014 ui.write("\n")
2013
2015
2014 def parents(ui, repo, rev=None, branches=None, **opts):
2016 def parents(ui, repo, rev=None, branches=None, **opts):
2015 """show the parents of the working dir or revision
2017 """show the parents of the working dir or revision
2016
2018
2017 Print the working directory's parent revisions.
2019 Print the working directory's parent revisions.
2018 """
2020 """
2019 if rev:
2021 if rev:
2020 p = repo.changelog.parents(repo.lookup(rev))
2022 p = repo.changelog.parents(repo.lookup(rev))
2021 else:
2023 else:
2022 p = repo.dirstate.parents()
2024 p = repo.dirstate.parents()
2023
2025
2024 br = None
2026 br = None
2025 if branches is not None:
2027 if branches is not None:
2026 br = repo.branchlookup(p)
2028 br = repo.branchlookup(p)
2027 displayer = show_changeset(ui, repo, opts)
2029 displayer = show_changeset(ui, repo, opts)
2028 for n in p:
2030 for n in p:
2029 if n != nullid:
2031 if n != nullid:
2030 displayer.show(changenode=n, brinfo=br)
2032 displayer.show(changenode=n, brinfo=br)
2031
2033
2032 def paths(ui, repo, search=None):
2034 def paths(ui, repo, search=None):
2033 """show definition of symbolic path names
2035 """show definition of symbolic path names
2034
2036
2035 Show definition of symbolic path name NAME. If no name is given, show
2037 Show definition of symbolic path name NAME. If no name is given, show
2036 definition of available names.
2038 definition of available names.
2037
2039
2038 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2040 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2039 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2041 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2040 """
2042 """
2041 if search:
2043 if search:
2042 for name, path in ui.configitems("paths"):
2044 for name, path in ui.configitems("paths"):
2043 if name == search:
2045 if name == search:
2044 ui.write("%s\n" % path)
2046 ui.write("%s\n" % path)
2045 return
2047 return
2046 ui.warn(_("not found!\n"))
2048 ui.warn(_("not found!\n"))
2047 return 1
2049 return 1
2048 else:
2050 else:
2049 for name, path in ui.configitems("paths"):
2051 for name, path in ui.configitems("paths"):
2050 ui.write("%s = %s\n" % (name, path))
2052 ui.write("%s = %s\n" % (name, path))
2051
2053
2052 def postincoming(ui, repo, modheads, optupdate):
2054 def postincoming(ui, repo, modheads, optupdate):
2053 if modheads == 0:
2055 if modheads == 0:
2054 return
2056 return
2055 if optupdate:
2057 if optupdate:
2056 if modheads == 1:
2058 if modheads == 1:
2057 return doupdate(ui, repo)
2059 return doupdate(ui, repo)
2058 else:
2060 else:
2059 ui.status(_("not updating, since new heads added\n"))
2061 ui.status(_("not updating, since new heads added\n"))
2060 if modheads > 1:
2062 if modheads > 1:
2061 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2063 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2062 else:
2064 else:
2063 ui.status(_("(run 'hg update' to get a working copy)\n"))
2065 ui.status(_("(run 'hg update' to get a working copy)\n"))
2064
2066
2065 def pull(ui, repo, source="default", **opts):
2067 def pull(ui, repo, source="default", **opts):
2066 """pull changes from the specified source
2068 """pull changes from the specified source
2067
2069
2068 Pull changes from a remote repository to a local one.
2070 Pull changes from a remote repository to a local one.
2069
2071
2070 This finds all changes from the repository at the specified path
2072 This finds all changes from the repository at the specified path
2071 or URL and adds them to the local repository. By default, this
2073 or URL and adds them to the local repository. By default, this
2072 does not update the copy of the project in the working directory.
2074 does not update the copy of the project in the working directory.
2073
2075
2074 Valid URLs are of the form:
2076 Valid URLs are of the form:
2075
2077
2076 local/filesystem/path
2078 local/filesystem/path
2077 http://[user@]host[:port][/path]
2079 http://[user@]host[:port][/path]
2078 https://[user@]host[:port][/path]
2080 https://[user@]host[:port][/path]
2079 ssh://[user@]host[:port][/path]
2081 ssh://[user@]host[:port][/path]
2080
2082
2081 Some notes about using SSH with Mercurial:
2083 Some notes about using SSH with Mercurial:
2082 - SSH requires an accessible shell account on the destination machine
2084 - SSH requires an accessible shell account on the destination machine
2083 and a copy of hg in the remote path or specified with as remotecmd.
2085 and a copy of hg in the remote path or specified with as remotecmd.
2084 - /path is relative to the remote user's home directory by default.
2086 - /path is relative to the remote user's home directory by default.
2085 Use two slashes at the start of a path to specify an absolute path.
2087 Use two slashes at the start of a path to specify an absolute path.
2086 - Mercurial doesn't use its own compression via SSH; the right thing
2088 - Mercurial doesn't use its own compression via SSH; the right thing
2087 to do is to configure it in your ~/.ssh/ssh_config, e.g.:
2089 to do is to configure it in your ~/.ssh/ssh_config, e.g.:
2088 Host *.mylocalnetwork.example.com
2090 Host *.mylocalnetwork.example.com
2089 Compression off
2091 Compression off
2090 Host *
2092 Host *
2091 Compression on
2093 Compression on
2092 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2094 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2093 with the --ssh command line option.
2095 with the --ssh command line option.
2094 """
2096 """
2095 source = ui.expandpath(source)
2097 source = ui.expandpath(source)
2096 ui.status(_('pulling from %s\n') % (source))
2098 ui.status(_('pulling from %s\n') % (source))
2097
2099
2098 if opts['ssh']:
2100 if opts['ssh']:
2099 ui.setconfig("ui", "ssh", opts['ssh'])
2101 ui.setconfig("ui", "ssh", opts['ssh'])
2100 if opts['remotecmd']:
2102 if opts['remotecmd']:
2101 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
2103 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
2102
2104
2103 other = hg.repository(ui, source)
2105 other = hg.repository(ui, source)
2104 revs = None
2106 revs = None
2105 if opts['rev'] and not other.local():
2107 if opts['rev'] and not other.local():
2106 raise util.Abort(_("pull -r doesn't work for remote repositories yet"))
2108 raise util.Abort(_("pull -r doesn't work for remote repositories yet"))
2107 elif opts['rev']:
2109 elif opts['rev']:
2108 revs = [other.lookup(rev) for rev in opts['rev']]
2110 revs = [other.lookup(rev) for rev in opts['rev']]
2109 modheads = repo.pull(other, heads=revs, force=opts['force'])
2111 modheads = repo.pull(other, heads=revs, force=opts['force'])
2110 return postincoming(ui, repo, modheads, opts['update'])
2112 return postincoming(ui, repo, modheads, opts['update'])
2111
2113
2112 def push(ui, repo, dest="default-push", **opts):
2114 def push(ui, repo, dest="default-push", **opts):
2113 """push changes to the specified destination
2115 """push changes to the specified destination
2114
2116
2115 Push changes from the local repository to the given destination.
2117 Push changes from the local repository to the given destination.
2116
2118
2117 This is the symmetrical operation for pull. It helps to move
2119 This is the symmetrical operation for pull. It helps to move
2118 changes from the current repository to a different one. If the
2120 changes from the current repository to a different one. If the
2119 destination is local this is identical to a pull in that directory
2121 destination is local this is identical to a pull in that directory
2120 from the current one.
2122 from the current one.
2121
2123
2122 By default, push will refuse to run if it detects the result would
2124 By default, push will refuse to run if it detects the result would
2123 increase the number of remote heads. This generally indicates the
2125 increase the number of remote heads. This generally indicates the
2124 the client has forgotten to sync and merge before pushing.
2126 the client has forgotten to sync and merge before pushing.
2125
2127
2126 Valid URLs are of the form:
2128 Valid URLs are of the form:
2127
2129
2128 local/filesystem/path
2130 local/filesystem/path
2129 ssh://[user@]host[:port][/path]
2131 ssh://[user@]host[:port][/path]
2130
2132
2131 Look at the help text for the pull command for important details
2133 Look at the help text for the pull command for important details
2132 about ssh:// URLs.
2134 about ssh:// URLs.
2133 """
2135 """
2134 dest = ui.expandpath(dest)
2136 dest = ui.expandpath(dest)
2135 ui.status('pushing to %s\n' % (dest))
2137 ui.status('pushing to %s\n' % (dest))
2136
2138
2137 if opts['ssh']:
2139 if opts['ssh']:
2138 ui.setconfig("ui", "ssh", opts['ssh'])
2140 ui.setconfig("ui", "ssh", opts['ssh'])
2139 if opts['remotecmd']:
2141 if opts['remotecmd']:
2140 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
2142 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
2141
2143
2142 other = hg.repository(ui, dest)
2144 other = hg.repository(ui, dest)
2143 revs = None
2145 revs = None
2144 if opts['rev']:
2146 if opts['rev']:
2145 revs = [repo.lookup(rev) for rev in opts['rev']]
2147 revs = [repo.lookup(rev) for rev in opts['rev']]
2146 r = repo.push(other, opts['force'], revs=revs)
2148 r = repo.push(other, opts['force'], revs=revs)
2147 return r == 0
2149 return r == 0
2148
2150
2149 def rawcommit(ui, repo, *flist, **rc):
2151 def rawcommit(ui, repo, *flist, **rc):
2150 """raw commit interface (DEPRECATED)
2152 """raw commit interface (DEPRECATED)
2151
2153
2152 (DEPRECATED)
2154 (DEPRECATED)
2153 Lowlevel commit, for use in helper scripts.
2155 Lowlevel commit, for use in helper scripts.
2154
2156
2155 This command is not intended to be used by normal users, as it is
2157 This command is not intended to be used by normal users, as it is
2156 primarily useful for importing from other SCMs.
2158 primarily useful for importing from other SCMs.
2157
2159
2158 This command is now deprecated and will be removed in a future
2160 This command is now deprecated and will be removed in a future
2159 release, please use debugsetparents and commit instead.
2161 release, please use debugsetparents and commit instead.
2160 """
2162 """
2161
2163
2162 ui.warn(_("(the rawcommit command is deprecated)\n"))
2164 ui.warn(_("(the rawcommit command is deprecated)\n"))
2163
2165
2164 message = rc['message']
2166 message = rc['message']
2165 if not message and rc['logfile']:
2167 if not message and rc['logfile']:
2166 try:
2168 try:
2167 message = open(rc['logfile']).read()
2169 message = open(rc['logfile']).read()
2168 except IOError:
2170 except IOError:
2169 pass
2171 pass
2170 if not message and not rc['logfile']:
2172 if not message and not rc['logfile']:
2171 raise util.Abort(_("missing commit message"))
2173 raise util.Abort(_("missing commit message"))
2172
2174
2173 files = relpath(repo, list(flist))
2175 files = relpath(repo, list(flist))
2174 if rc['files']:
2176 if rc['files']:
2175 files += open(rc['files']).read().splitlines()
2177 files += open(rc['files']).read().splitlines()
2176
2178
2177 rc['parent'] = map(repo.lookup, rc['parent'])
2179 rc['parent'] = map(repo.lookup, rc['parent'])
2178
2180
2179 try:
2181 try:
2180 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
2182 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
2181 except ValueError, inst:
2183 except ValueError, inst:
2182 raise util.Abort(str(inst))
2184 raise util.Abort(str(inst))
2183
2185
2184 def recover(ui, repo):
2186 def recover(ui, repo):
2185 """roll back an interrupted transaction
2187 """roll back an interrupted transaction
2186
2188
2187 Recover from an interrupted commit or pull.
2189 Recover from an interrupted commit or pull.
2188
2190
2189 This command tries to fix the repository status after an interrupted
2191 This command tries to fix the repository status after an interrupted
2190 operation. It should only be necessary when Mercurial suggests it.
2192 operation. It should only be necessary when Mercurial suggests it.
2191 """
2193 """
2192 if repo.recover():
2194 if repo.recover():
2193 return repo.verify()
2195 return repo.verify()
2194 return 1
2196 return 1
2195
2197
2196 def remove(ui, repo, *pats, **opts):
2198 def remove(ui, repo, *pats, **opts):
2197 """remove the specified files on the next commit
2199 """remove the specified files on the next commit
2198
2200
2199 Schedule the indicated files for removal from the repository.
2201 Schedule the indicated files for removal from the repository.
2200
2202
2201 This command schedules the files to be removed at the next commit.
2203 This command schedules the files to be removed at the next commit.
2202 This only removes files from the current branch, not from the
2204 This only removes files from the current branch, not from the
2203 entire project history. If the files still exist in the working
2205 entire project history. If the files still exist in the working
2204 directory, they will be deleted from it. If invoked with --after,
2206 directory, they will be deleted from it. If invoked with --after,
2205 files that have been manually deleted are marked as removed.
2207 files that have been manually deleted are marked as removed.
2206
2208
2207 Modified files and added files are not removed by default. To
2209 Modified files and added files are not removed by default. To
2208 remove them, use the -f/--force option.
2210 remove them, use the -f/--force option.
2209 """
2211 """
2210 names = []
2212 names = []
2211 if not opts['after'] and not pats:
2213 if not opts['after'] and not pats:
2212 raise util.Abort(_('no files specified'))
2214 raise util.Abort(_('no files specified'))
2213 files, matchfn, anypats = matchpats(repo, pats, opts)
2215 files, matchfn, anypats = matchpats(repo, pats, opts)
2214 exact = dict.fromkeys(files)
2216 exact = dict.fromkeys(files)
2215 mardu = map(dict.fromkeys, repo.changes(files=files, match=matchfn))
2217 mardu = map(dict.fromkeys, repo.changes(files=files, match=matchfn))
2216 modified, added, removed, deleted, unknown = mardu
2218 modified, added, removed, deleted, unknown = mardu
2217 remove, forget = [], []
2219 remove, forget = [], []
2218 for src, abs, rel, exact in walk(repo, pats, opts):
2220 for src, abs, rel, exact in walk(repo, pats, opts):
2219 reason = None
2221 reason = None
2220 if abs not in deleted and opts['after']:
2222 if abs not in deleted and opts['after']:
2221 reason = _('is still present')
2223 reason = _('is still present')
2222 elif abs in modified and not opts['force']:
2224 elif abs in modified and not opts['force']:
2223 reason = _('is modified (use -f to force removal)')
2225 reason = _('is modified (use -f to force removal)')
2224 elif abs in added:
2226 elif abs in added:
2225 if opts['force']:
2227 if opts['force']:
2226 forget.append(abs)
2228 forget.append(abs)
2227 continue
2229 continue
2228 reason = _('has been marked for add (use -f to force removal)')
2230 reason = _('has been marked for add (use -f to force removal)')
2229 elif abs in unknown:
2231 elif abs in unknown:
2230 reason = _('is not managed')
2232 reason = _('is not managed')
2231 elif abs in removed:
2233 elif abs in removed:
2232 continue
2234 continue
2233 if reason:
2235 if reason:
2234 if exact:
2236 if exact:
2235 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2237 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2236 else:
2238 else:
2237 if ui.verbose or not exact:
2239 if ui.verbose or not exact:
2238 ui.status(_('removing %s\n') % rel)
2240 ui.status(_('removing %s\n') % rel)
2239 remove.append(abs)
2241 remove.append(abs)
2240 repo.forget(forget)
2242 repo.forget(forget)
2241 repo.remove(remove, unlink=not opts['after'])
2243 repo.remove(remove, unlink=not opts['after'])
2242
2244
2243 def rename(ui, repo, *pats, **opts):
2245 def rename(ui, repo, *pats, **opts):
2244 """rename files; equivalent of copy + remove
2246 """rename files; equivalent of copy + remove
2245
2247
2246 Mark dest as copies of sources; mark sources for deletion. If
2248 Mark dest as copies of sources; mark sources for deletion. If
2247 dest is a directory, copies are put in that directory. If dest is
2249 dest is a directory, copies are put in that directory. If dest is
2248 a file, there can only be one source.
2250 a file, there can only be one source.
2249
2251
2250 By default, this command copies the contents of files as they
2252 By default, this command copies the contents of files as they
2251 stand in the working directory. If invoked with --after, the
2253 stand in the working directory. If invoked with --after, the
2252 operation is recorded, but no copying is performed.
2254 operation is recorded, but no copying is performed.
2253
2255
2254 This command takes effect in the next commit.
2256 This command takes effect in the next commit.
2255
2257
2256 NOTE: This command should be treated as experimental. While it
2258 NOTE: This command should be treated as experimental. While it
2257 should properly record rename files, this information is not yet
2259 should properly record rename files, this information is not yet
2258 fully used by merge, nor fully reported by log.
2260 fully used by merge, nor fully reported by log.
2259 """
2261 """
2260 wlock = repo.wlock(0)
2262 wlock = repo.wlock(0)
2261 errs, copied = docopy(ui, repo, pats, opts, wlock)
2263 errs, copied = docopy(ui, repo, pats, opts, wlock)
2262 names = []
2264 names = []
2263 for abs, rel, exact in copied:
2265 for abs, rel, exact in copied:
2264 if ui.verbose or not exact:
2266 if ui.verbose or not exact:
2265 ui.status(_('removing %s\n') % rel)
2267 ui.status(_('removing %s\n') % rel)
2266 names.append(abs)
2268 names.append(abs)
2267 repo.remove(names, True, wlock)
2269 repo.remove(names, True, wlock)
2268 return errs
2270 return errs
2269
2271
2270 def revert(ui, repo, *pats, **opts):
2272 def revert(ui, repo, *pats, **opts):
2271 """revert files or dirs to their states as of some revision
2273 """revert files or dirs to their states as of some revision
2272
2274
2273 With no revision specified, revert the named files or directories
2275 With no revision specified, revert the named files or directories
2274 to the contents they had in the parent of the working directory.
2276 to the contents they had in the parent of the working directory.
2275 This restores the contents of the affected files to an unmodified
2277 This restores the contents of the affected files to an unmodified
2276 state. If the working directory has two parents, you must
2278 state. If the working directory has two parents, you must
2277 explicitly specify the revision to revert to.
2279 explicitly specify the revision to revert to.
2278
2280
2279 Modified files are saved with a .orig suffix before reverting.
2281 Modified files are saved with a .orig suffix before reverting.
2280 To disable these backups, use --no-backup.
2282 To disable these backups, use --no-backup.
2281
2283
2282 Using the -r option, revert the given files or directories to
2284 Using the -r option, revert the given files or directories to
2283 their contents as of a specific revision. This can be helpful to"roll
2285 their contents as of a specific revision. This can be helpful to"roll
2284 back" some or all of a change that should not have been committed.
2286 back" some or all of a change that should not have been committed.
2285
2287
2286 Revert modifies the working directory. It does not commit any
2288 Revert modifies the working directory. It does not commit any
2287 changes, or change the parent of the working directory. If you
2289 changes, or change the parent of the working directory. If you
2288 revert to a revision other than the parent of the working
2290 revert to a revision other than the parent of the working
2289 directory, the reverted files will thus appear modified
2291 directory, the reverted files will thus appear modified
2290 afterwards.
2292 afterwards.
2291
2293
2292 If a file has been deleted, it is recreated. If the executable
2294 If a file has been deleted, it is recreated. If the executable
2293 mode of a file was changed, it is reset.
2295 mode of a file was changed, it is reset.
2294
2296
2295 If names are given, all files matching the names are reverted.
2297 If names are given, all files matching the names are reverted.
2296
2298
2297 If no arguments are given, all files in the repository are reverted.
2299 If no arguments are given, all files in the repository are reverted.
2298 """
2300 """
2299 parent, p2 = repo.dirstate.parents()
2301 parent, p2 = repo.dirstate.parents()
2300 if opts['rev']:
2302 if opts['rev']:
2301 node = repo.lookup(opts['rev'])
2303 node = repo.lookup(opts['rev'])
2302 elif p2 != nullid:
2304 elif p2 != nullid:
2303 raise util.Abort(_('working dir has two parents; '
2305 raise util.Abort(_('working dir has two parents; '
2304 'you must specify the revision to revert to'))
2306 'you must specify the revision to revert to'))
2305 else:
2307 else:
2306 node = parent
2308 node = parent
2307 pmf = None
2309 pmf = None
2308 mf = repo.manifest.read(repo.changelog.read(node)[0])
2310 mf = repo.manifest.read(repo.changelog.read(node)[0])
2309
2311
2310 wlock = repo.wlock()
2312 wlock = repo.wlock()
2311
2313
2312 # need all matching names in dirstate and manifest of target rev,
2314 # need all matching names in dirstate and manifest of target rev,
2313 # so have to walk both. do not print errors if files exist in one
2315 # so have to walk both. do not print errors if files exist in one
2314 # but not other.
2316 # but not other.
2315
2317
2316 names = {}
2318 names = {}
2317 target_only = {}
2319 target_only = {}
2318
2320
2319 # walk dirstate.
2321 # walk dirstate.
2320
2322
2321 for src, abs, rel, exact in walk(repo, pats, opts, badmatch=mf.has_key):
2323 for src, abs, rel, exact in walk(repo, pats, opts, badmatch=mf.has_key):
2322 names[abs] = (rel, exact)
2324 names[abs] = (rel, exact)
2323 if src == 'b':
2325 if src == 'b':
2324 target_only[abs] = True
2326 target_only[abs] = True
2325
2327
2326 # walk target manifest.
2328 # walk target manifest.
2327
2329
2328 for src, abs, rel, exact in walk(repo, pats, opts, node=node,
2330 for src, abs, rel, exact in walk(repo, pats, opts, node=node,
2329 badmatch=names.has_key):
2331 badmatch=names.has_key):
2330 if abs in names: continue
2332 if abs in names: continue
2331 names[abs] = (rel, exact)
2333 names[abs] = (rel, exact)
2332 target_only[abs] = True
2334 target_only[abs] = True
2333
2335
2334 changes = repo.changes(match=names.has_key, wlock=wlock)
2336 changes = repo.changes(match=names.has_key, wlock=wlock)
2335 modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
2337 modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
2336
2338
2337 revert = ([], _('reverting %s\n'))
2339 revert = ([], _('reverting %s\n'))
2338 add = ([], _('adding %s\n'))
2340 add = ([], _('adding %s\n'))
2339 remove = ([], _('removing %s\n'))
2341 remove = ([], _('removing %s\n'))
2340 forget = ([], _('forgetting %s\n'))
2342 forget = ([], _('forgetting %s\n'))
2341 undelete = ([], _('undeleting %s\n'))
2343 undelete = ([], _('undeleting %s\n'))
2342 update = {}
2344 update = {}
2343
2345
2344 disptable = (
2346 disptable = (
2345 # dispatch table:
2347 # dispatch table:
2346 # file state
2348 # file state
2347 # action if in target manifest
2349 # action if in target manifest
2348 # action if not in target manifest
2350 # action if not in target manifest
2349 # make backup if in target manifest
2351 # make backup if in target manifest
2350 # make backup if not in target manifest
2352 # make backup if not in target manifest
2351 (modified, revert, remove, True, True),
2353 (modified, revert, remove, True, True),
2352 (added, revert, forget, True, False),
2354 (added, revert, forget, True, False),
2353 (removed, undelete, None, False, False),
2355 (removed, undelete, None, False, False),
2354 (deleted, revert, remove, False, False),
2356 (deleted, revert, remove, False, False),
2355 (unknown, add, None, True, False),
2357 (unknown, add, None, True, False),
2356 (target_only, add, None, False, False),
2358 (target_only, add, None, False, False),
2357 )
2359 )
2358
2360
2359 entries = names.items()
2361 entries = names.items()
2360 entries.sort()
2362 entries.sort()
2361
2363
2362 for abs, (rel, exact) in entries:
2364 for abs, (rel, exact) in entries:
2363 in_mf = abs in mf
2365 in_mf = abs in mf
2364 def handle(xlist, dobackup):
2366 def handle(xlist, dobackup):
2365 xlist[0].append(abs)
2367 xlist[0].append(abs)
2366 if dobackup and not opts['no_backup'] and os.path.exists(rel):
2368 if dobackup and not opts['no_backup'] and os.path.exists(rel):
2367 bakname = "%s.orig" % rel
2369 bakname = "%s.orig" % rel
2368 ui.note(_('saving current version of %s as %s\n') %
2370 ui.note(_('saving current version of %s as %s\n') %
2369 (rel, bakname))
2371 (rel, bakname))
2370 shutil.copyfile(rel, bakname)
2372 shutil.copyfile(rel, bakname)
2371 shutil.copymode(rel, bakname)
2373 shutil.copymode(rel, bakname)
2372 if ui.verbose or not exact:
2374 if ui.verbose or not exact:
2373 ui.status(xlist[1] % rel)
2375 ui.status(xlist[1] % rel)
2374 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2376 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2375 if abs not in table: continue
2377 if abs not in table: continue
2376 # file has changed in dirstate
2378 # file has changed in dirstate
2377 if in_mf:
2379 if in_mf:
2378 handle(hitlist, backuphit)
2380 handle(hitlist, backuphit)
2379 elif misslist is not None:
2381 elif misslist is not None:
2380 handle(misslist, backupmiss)
2382 handle(misslist, backupmiss)
2381 else:
2383 else:
2382 if exact: ui.warn(_('file not managed: %s\n' % rel))
2384 if exact: ui.warn(_('file not managed: %s\n' % rel))
2383 break
2385 break
2384 else:
2386 else:
2385 # file has not changed in dirstate
2387 # file has not changed in dirstate
2386 if node == parent:
2388 if node == parent:
2387 if exact: ui.warn(_('no changes needed to %s\n' % rel))
2389 if exact: ui.warn(_('no changes needed to %s\n' % rel))
2388 continue
2390 continue
2389 if not in_mf:
2391 if not in_mf:
2390 if pmf is None:
2392 if pmf is None:
2391 # only need parent manifest in this unlikely case,
2393 # only need parent manifest in this unlikely case,
2392 # so do not read by default
2394 # so do not read by default
2393 pmf = repo.manifest.read(repo.changelog.read(parent)[0])
2395 pmf = repo.manifest.read(repo.changelog.read(parent)[0])
2394 if abs in pmf:
2396 if abs in pmf:
2395 handle(remove, False)
2397 handle(remove, False)
2396 update[abs] = True
2398 update[abs] = True
2397
2399
2398 repo.dirstate.forget(forget[0])
2400 repo.dirstate.forget(forget[0])
2399 r = repo.update(node, False, True, update.has_key, False, wlock=wlock,
2401 r = repo.update(node, False, True, update.has_key, False, wlock=wlock,
2400 show_stats=False)
2402 show_stats=False)
2401 repo.dirstate.update(add[0], 'a')
2403 repo.dirstate.update(add[0], 'a')
2402 repo.dirstate.update(undelete[0], 'n')
2404 repo.dirstate.update(undelete[0], 'n')
2403 repo.dirstate.update(remove[0], 'r')
2405 repo.dirstate.update(remove[0], 'r')
2404 return r
2406 return r
2405
2407
2406 def rollback(ui, repo):
2408 def rollback(ui, repo):
2407 """roll back the last transaction in this repository
2409 """roll back the last transaction in this repository
2408
2410
2409 Roll back the last transaction in this repository, restoring the
2411 Roll back the last transaction in this repository, restoring the
2410 project to its state prior to the transaction.
2412 project to its state prior to the transaction.
2411
2413
2412 Transactions are used to encapsulate the effects of all commands
2414 Transactions are used to encapsulate the effects of all commands
2413 that create new changesets or propagate existing changesets into a
2415 that create new changesets or propagate existing changesets into a
2414 repository. For example, the following commands are transactional,
2416 repository. For example, the following commands are transactional,
2415 and their effects can be rolled back:
2417 and their effects can be rolled back:
2416
2418
2417 commit
2419 commit
2418 import
2420 import
2419 pull
2421 pull
2420 push (with this repository as destination)
2422 push (with this repository as destination)
2421 unbundle
2423 unbundle
2422
2424
2423 This command should be used with care. There is only one level of
2425 This command should be used with care. There is only one level of
2424 rollback, and there is no way to undo a rollback.
2426 rollback, and there is no way to undo a rollback.
2425
2427
2426 This command is not intended for use on public repositories. Once
2428 This command is not intended for use on public repositories. Once
2427 changes are visible for pull by other users, rolling a transaction
2429 changes are visible for pull by other users, rolling a transaction
2428 back locally is ineffective (someone else may already have pulled
2430 back locally is ineffective (someone else may already have pulled
2429 the changes). Furthermore, a race is possible with readers of the
2431 the changes). Furthermore, a race is possible with readers of the
2430 repository; for example an in-progress pull from the repository
2432 repository; for example an in-progress pull from the repository
2431 may fail if a rollback is performed.
2433 may fail if a rollback is performed.
2432 """
2434 """
2433 repo.undo()
2435 repo.undo()
2434
2436
2435 def root(ui, repo):
2437 def root(ui, repo):
2436 """print the root (top) of the current working dir
2438 """print the root (top) of the current working dir
2437
2439
2438 Print the root directory of the current repository.
2440 Print the root directory of the current repository.
2439 """
2441 """
2440 ui.write(repo.root + "\n")
2442 ui.write(repo.root + "\n")
2441
2443
2442 def serve(ui, repo, **opts):
2444 def serve(ui, repo, **opts):
2443 """export the repository via HTTP
2445 """export the repository via HTTP
2444
2446
2445 Start a local HTTP repository browser and pull server.
2447 Start a local HTTP repository browser and pull server.
2446
2448
2447 By default, the server logs accesses to stdout and errors to
2449 By default, the server logs accesses to stdout and errors to
2448 stderr. Use the "-A" and "-E" options to log to files.
2450 stderr. Use the "-A" and "-E" options to log to files.
2449 """
2451 """
2450
2452
2451 if opts["stdio"]:
2453 if opts["stdio"]:
2452 if repo is None:
2454 if repo is None:
2453 raise hg.RepoError(_('no repo found'))
2455 raise hg.RepoError(_('no repo found'))
2454 fin, fout = sys.stdin, sys.stdout
2456 fin, fout = sys.stdin, sys.stdout
2455 sys.stdout = sys.stderr
2457 sys.stdout = sys.stderr
2456
2458
2457 # Prevent insertion/deletion of CRs
2459 # Prevent insertion/deletion of CRs
2458 util.set_binary(fin)
2460 util.set_binary(fin)
2459 util.set_binary(fout)
2461 util.set_binary(fout)
2460
2462
2461 def getarg():
2463 def getarg():
2462 argline = fin.readline()[:-1]
2464 argline = fin.readline()[:-1]
2463 arg, l = argline.split()
2465 arg, l = argline.split()
2464 val = fin.read(int(l))
2466 val = fin.read(int(l))
2465 return arg, val
2467 return arg, val
2466 def respond(v):
2468 def respond(v):
2467 fout.write("%d\n" % len(v))
2469 fout.write("%d\n" % len(v))
2468 fout.write(v)
2470 fout.write(v)
2469 fout.flush()
2471 fout.flush()
2470
2472
2471 lock = None
2473 lock = None
2472
2474
2473 while 1:
2475 while 1:
2474 cmd = fin.readline()[:-1]
2476 cmd = fin.readline()[:-1]
2475 if cmd == '':
2477 if cmd == '':
2476 return
2478 return
2477 if cmd == "heads":
2479 if cmd == "heads":
2478 h = repo.heads()
2480 h = repo.heads()
2479 respond(" ".join(map(hex, h)) + "\n")
2481 respond(" ".join(map(hex, h)) + "\n")
2480 if cmd == "lock":
2482 if cmd == "lock":
2481 lock = repo.lock()
2483 lock = repo.lock()
2482 respond("")
2484 respond("")
2483 if cmd == "unlock":
2485 if cmd == "unlock":
2484 if lock:
2486 if lock:
2485 lock.release()
2487 lock.release()
2486 lock = None
2488 lock = None
2487 respond("")
2489 respond("")
2488 elif cmd == "branches":
2490 elif cmd == "branches":
2489 arg, nodes = getarg()
2491 arg, nodes = getarg()
2490 nodes = map(bin, nodes.split(" "))
2492 nodes = map(bin, nodes.split(" "))
2491 r = []
2493 r = []
2492 for b in repo.branches(nodes):
2494 for b in repo.branches(nodes):
2493 r.append(" ".join(map(hex, b)) + "\n")
2495 r.append(" ".join(map(hex, b)) + "\n")
2494 respond("".join(r))
2496 respond("".join(r))
2495 elif cmd == "between":
2497 elif cmd == "between":
2496 arg, pairs = getarg()
2498 arg, pairs = getarg()
2497 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
2499 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
2498 r = []
2500 r = []
2499 for b in repo.between(pairs):
2501 for b in repo.between(pairs):
2500 r.append(" ".join(map(hex, b)) + "\n")
2502 r.append(" ".join(map(hex, b)) + "\n")
2501 respond("".join(r))
2503 respond("".join(r))
2502 elif cmd == "changegroup":
2504 elif cmd == "changegroup":
2503 nodes = []
2505 nodes = []
2504 arg, roots = getarg()
2506 arg, roots = getarg()
2505 nodes = map(bin, roots.split(" "))
2507 nodes = map(bin, roots.split(" "))
2506
2508
2507 cg = repo.changegroup(nodes, 'serve')
2509 cg = repo.changegroup(nodes, 'serve')
2508 while 1:
2510 while 1:
2509 d = cg.read(4096)
2511 d = cg.read(4096)
2510 if not d:
2512 if not d:
2511 break
2513 break
2512 fout.write(d)
2514 fout.write(d)
2513
2515
2514 fout.flush()
2516 fout.flush()
2515
2517
2516 elif cmd == "addchangegroup":
2518 elif cmd == "addchangegroup":
2517 if not lock:
2519 if not lock:
2518 respond("not locked")
2520 respond("not locked")
2519 continue
2521 continue
2520 respond("")
2522 respond("")
2521
2523
2522 r = repo.addchangegroup(fin, 'serve')
2524 r = repo.addchangegroup(fin, 'serve')
2523 respond(str(r))
2525 respond(str(r))
2524
2526
2525 optlist = ("name templates style address port ipv6"
2527 optlist = ("name templates style address port ipv6"
2526 " accesslog errorlog webdir_conf")
2528 " accesslog errorlog webdir_conf")
2527 for o in optlist.split():
2529 for o in optlist.split():
2528 if opts[o]:
2530 if opts[o]:
2529 ui.setconfig("web", o, opts[o])
2531 ui.setconfig("web", o, opts[o])
2530
2532
2531 if repo is None and not ui.config("web", "webdir_conf"):
2533 if repo is None and not ui.config("web", "webdir_conf"):
2532 raise hg.RepoError(_('no repo found'))
2534 raise hg.RepoError(_('no repo found'))
2533
2535
2534 if opts['daemon'] and not opts['daemon_pipefds']:
2536 if opts['daemon'] and not opts['daemon_pipefds']:
2535 rfd, wfd = os.pipe()
2537 rfd, wfd = os.pipe()
2536 args = sys.argv[:]
2538 args = sys.argv[:]
2537 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
2539 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
2538 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
2540 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
2539 args[0], args)
2541 args[0], args)
2540 os.close(wfd)
2542 os.close(wfd)
2541 os.read(rfd, 1)
2543 os.read(rfd, 1)
2542 os._exit(0)
2544 os._exit(0)
2543
2545
2544 try:
2546 try:
2545 httpd = hgweb.create_server(ui, repo, hgweb.hgwebdir, hgweb.hgweb)
2547 httpd = create_server(ui, repo, hgwebdir, hgweb)
2546 except socket.error, inst:
2548 except socket.error, inst:
2547 raise util.Abort(_('cannot start server: ') + inst.args[1])
2549 raise util.Abort(_('cannot start server: ') + inst.args[1])
2548
2550
2549 if ui.verbose:
2551 if ui.verbose:
2550 addr, port = httpd.socket.getsockname()
2552 addr, port = httpd.socket.getsockname()
2551 if addr == '0.0.0.0':
2553 if addr == '0.0.0.0':
2552 addr = socket.gethostname()
2554 addr = socket.gethostname()
2553 else:
2555 else:
2554 try:
2556 try:
2555 addr = socket.gethostbyaddr(addr)[0]
2557 addr = socket.gethostbyaddr(addr)[0]
2556 except socket.error:
2558 except socket.error:
2557 pass
2559 pass
2558 if port != 80:
2560 if port != 80:
2559 ui.status(_('listening at http://%s:%d/\n') % (addr, port))
2561 ui.status(_('listening at http://%s:%d/\n') % (addr, port))
2560 else:
2562 else:
2561 ui.status(_('listening at http://%s/\n') % addr)
2563 ui.status(_('listening at http://%s/\n') % addr)
2562
2564
2563 if opts['pid_file']:
2565 if opts['pid_file']:
2564 fp = open(opts['pid_file'], 'w')
2566 fp = open(opts['pid_file'], 'w')
2565 fp.write(str(os.getpid()))
2567 fp.write(str(os.getpid()))
2566 fp.close()
2568 fp.close()
2567
2569
2568 if opts['daemon_pipefds']:
2570 if opts['daemon_pipefds']:
2569 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
2571 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
2570 os.close(rfd)
2572 os.close(rfd)
2571 os.write(wfd, 'y')
2573 os.write(wfd, 'y')
2572 os.close(wfd)
2574 os.close(wfd)
2573 sys.stdout.flush()
2575 sys.stdout.flush()
2574 sys.stderr.flush()
2576 sys.stderr.flush()
2575 fd = os.open(util.nulldev, os.O_RDWR)
2577 fd = os.open(util.nulldev, os.O_RDWR)
2576 if fd != 0: os.dup2(fd, 0)
2578 if fd != 0: os.dup2(fd, 0)
2577 if fd != 1: os.dup2(fd, 1)
2579 if fd != 1: os.dup2(fd, 1)
2578 if fd != 2: os.dup2(fd, 2)
2580 if fd != 2: os.dup2(fd, 2)
2579 if fd not in (0, 1, 2): os.close(fd)
2581 if fd not in (0, 1, 2): os.close(fd)
2580
2582
2581 httpd.serve_forever()
2583 httpd.serve_forever()
2582
2584
2583 def status(ui, repo, *pats, **opts):
2585 def status(ui, repo, *pats, **opts):
2584 """show changed files in the working directory
2586 """show changed files in the working directory
2585
2587
2586 Show changed files in the repository. If names are
2588 Show changed files in the repository. If names are
2587 given, only files that match are shown.
2589 given, only files that match are shown.
2588
2590
2589 The codes used to show the status of files are:
2591 The codes used to show the status of files are:
2590 M = modified
2592 M = modified
2591 A = added
2593 A = added
2592 R = removed
2594 R = removed
2593 ! = deleted, but still tracked
2595 ! = deleted, but still tracked
2594 ? = not tracked
2596 ? = not tracked
2595 I = ignored (not shown by default)
2597 I = ignored (not shown by default)
2596 """
2598 """
2597
2599
2598 show_ignored = opts['ignored'] and True or False
2600 show_ignored = opts['ignored'] and True or False
2599 files, matchfn, anypats = matchpats(repo, pats, opts)
2601 files, matchfn, anypats = matchpats(repo, pats, opts)
2600 cwd = (pats and repo.getcwd()) or ''
2602 cwd = (pats and repo.getcwd()) or ''
2601 modified, added, removed, deleted, unknown, ignored = [
2603 modified, added, removed, deleted, unknown, ignored = [
2602 [util.pathto(cwd, x) for x in n]
2604 [util.pathto(cwd, x) for x in n]
2603 for n in repo.changes(files=files, match=matchfn,
2605 for n in repo.changes(files=files, match=matchfn,
2604 show_ignored=show_ignored)]
2606 show_ignored=show_ignored)]
2605
2607
2606 changetypes = [('modified', 'M', modified),
2608 changetypes = [('modified', 'M', modified),
2607 ('added', 'A', added),
2609 ('added', 'A', added),
2608 ('removed', 'R', removed),
2610 ('removed', 'R', removed),
2609 ('deleted', '!', deleted),
2611 ('deleted', '!', deleted),
2610 ('unknown', '?', unknown),
2612 ('unknown', '?', unknown),
2611 ('ignored', 'I', ignored)]
2613 ('ignored', 'I', ignored)]
2612
2614
2613 end = opts['print0'] and '\0' or '\n'
2615 end = opts['print0'] and '\0' or '\n'
2614
2616
2615 for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]]
2617 for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]]
2616 or changetypes):
2618 or changetypes):
2617 if opts['no_status']:
2619 if opts['no_status']:
2618 format = "%%s%s" % end
2620 format = "%%s%s" % end
2619 else:
2621 else:
2620 format = "%s %%s%s" % (char, end)
2622 format = "%s %%s%s" % (char, end)
2621
2623
2622 for f in changes:
2624 for f in changes:
2623 ui.write(format % f)
2625 ui.write(format % f)
2624
2626
2625 def tag(ui, repo, name, rev_=None, **opts):
2627 def tag(ui, repo, name, rev_=None, **opts):
2626 """add a tag for the current tip or a given revision
2628 """add a tag for the current tip or a given revision
2627
2629
2628 Name a particular revision using <name>.
2630 Name a particular revision using <name>.
2629
2631
2630 Tags are used to name particular revisions of the repository and are
2632 Tags are used to name particular revisions of the repository and are
2631 very useful to compare different revision, to go back to significant
2633 very useful to compare different revision, to go back to significant
2632 earlier versions or to mark branch points as releases, etc.
2634 earlier versions or to mark branch points as releases, etc.
2633
2635
2634 If no revision is given, the tip is used.
2636 If no revision is given, the tip is used.
2635
2637
2636 To facilitate version control, distribution, and merging of tags,
2638 To facilitate version control, distribution, and merging of tags,
2637 they are stored as a file named ".hgtags" which is managed
2639 they are stored as a file named ".hgtags" which is managed
2638 similarly to other project files and can be hand-edited if
2640 similarly to other project files and can be hand-edited if
2639 necessary. The file '.hg/localtags' is used for local tags (not
2641 necessary. The file '.hg/localtags' is used for local tags (not
2640 shared among repositories).
2642 shared among repositories).
2641 """
2643 """
2642 if name == "tip":
2644 if name == "tip":
2643 raise util.Abort(_("the name 'tip' is reserved"))
2645 raise util.Abort(_("the name 'tip' is reserved"))
2644 if rev_ is not None:
2646 if rev_ is not None:
2645 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2647 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2646 "please use 'hg tag [-r REV] NAME' instead\n"))
2648 "please use 'hg tag [-r REV] NAME' instead\n"))
2647 if opts['rev']:
2649 if opts['rev']:
2648 raise util.Abort(_("use only one form to specify the revision"))
2650 raise util.Abort(_("use only one form to specify the revision"))
2649 if opts['rev']:
2651 if opts['rev']:
2650 rev_ = opts['rev']
2652 rev_ = opts['rev']
2651 if rev_:
2653 if rev_:
2652 r = hex(repo.lookup(rev_))
2654 r = hex(repo.lookup(rev_))
2653 else:
2655 else:
2654 r = hex(repo.changelog.tip())
2656 r = hex(repo.changelog.tip())
2655
2657
2656 disallowed = (revrangesep, '\r', '\n')
2658 disallowed = (revrangesep, '\r', '\n')
2657 for c in disallowed:
2659 for c in disallowed:
2658 if name.find(c) >= 0:
2660 if name.find(c) >= 0:
2659 raise util.Abort(_("%s cannot be used in a tag name") % repr(c))
2661 raise util.Abort(_("%s cannot be used in a tag name") % repr(c))
2660
2662
2661 repo.hook('pretag', throw=True, node=r, tag=name,
2663 repo.hook('pretag', throw=True, node=r, tag=name,
2662 local=int(not not opts['local']))
2664 local=int(not not opts['local']))
2663
2665
2664 if opts['local']:
2666 if opts['local']:
2665 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
2667 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
2666 repo.hook('tag', node=r, tag=name, local=1)
2668 repo.hook('tag', node=r, tag=name, local=1)
2667 return
2669 return
2668
2670
2669 for x in repo.changes():
2671 for x in repo.changes():
2670 if ".hgtags" in x:
2672 if ".hgtags" in x:
2671 raise util.Abort(_("working copy of .hgtags is changed "
2673 raise util.Abort(_("working copy of .hgtags is changed "
2672 "(please commit .hgtags manually)"))
2674 "(please commit .hgtags manually)"))
2673
2675
2674 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
2676 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
2675 if repo.dirstate.state(".hgtags") == '?':
2677 if repo.dirstate.state(".hgtags") == '?':
2676 repo.add([".hgtags"])
2678 repo.add([".hgtags"])
2677
2679
2678 message = (opts['message'] or
2680 message = (opts['message'] or
2679 _("Added tag %s for changeset %s") % (name, r))
2681 _("Added tag %s for changeset %s") % (name, r))
2680 try:
2682 try:
2681 repo.commit([".hgtags"], message, opts['user'], opts['date'])
2683 repo.commit([".hgtags"], message, opts['user'], opts['date'])
2682 repo.hook('tag', node=r, tag=name, local=0)
2684 repo.hook('tag', node=r, tag=name, local=0)
2683 except ValueError, inst:
2685 except ValueError, inst:
2684 raise util.Abort(str(inst))
2686 raise util.Abort(str(inst))
2685
2687
2686 def tags(ui, repo):
2688 def tags(ui, repo):
2687 """list repository tags
2689 """list repository tags
2688
2690
2689 List the repository tags.
2691 List the repository tags.
2690
2692
2691 This lists both regular and local tags.
2693 This lists both regular and local tags.
2692 """
2694 """
2693
2695
2694 l = repo.tagslist()
2696 l = repo.tagslist()
2695 l.reverse()
2697 l.reverse()
2696 for t, n in l:
2698 for t, n in l:
2697 try:
2699 try:
2698 r = "%5d:%s" % (repo.changelog.rev(n), hex(n))
2700 r = "%5d:%s" % (repo.changelog.rev(n), hex(n))
2699 except KeyError:
2701 except KeyError:
2700 r = " ?:?"
2702 r = " ?:?"
2701 if ui.quiet:
2703 if ui.quiet:
2702 ui.write("%s\n" % t)
2704 ui.write("%s\n" % t)
2703 else:
2705 else:
2704 ui.write("%-30s %s\n" % (t, r))
2706 ui.write("%-30s %s\n" % (t, r))
2705
2707
2706 def tip(ui, repo, **opts):
2708 def tip(ui, repo, **opts):
2707 """show the tip revision
2709 """show the tip revision
2708
2710
2709 Show the tip revision.
2711 Show the tip revision.
2710 """
2712 """
2711 n = repo.changelog.tip()
2713 n = repo.changelog.tip()
2712 br = None
2714 br = None
2713 if opts['branches']:
2715 if opts['branches']:
2714 br = repo.branchlookup([n])
2716 br = repo.branchlookup([n])
2715 show_changeset(ui, repo, opts).show(changenode=n, brinfo=br)
2717 show_changeset(ui, repo, opts).show(changenode=n, brinfo=br)
2716 if opts['patch']:
2718 if opts['patch']:
2717 dodiff(ui, ui, repo, repo.changelog.parents(n)[0], n)
2719 dodiff(ui, ui, repo, repo.changelog.parents(n)[0], n)
2718
2720
2719 def unbundle(ui, repo, fname, **opts):
2721 def unbundle(ui, repo, fname, **opts):
2720 """apply a changegroup file
2722 """apply a changegroup file
2721
2723
2722 Apply a compressed changegroup file generated by the bundle
2724 Apply a compressed changegroup file generated by the bundle
2723 command.
2725 command.
2724 """
2726 """
2725 f = urllib.urlopen(fname)
2727 f = urllib.urlopen(fname)
2726
2728
2727 header = f.read(6)
2729 header = f.read(6)
2728 if not header.startswith("HG"):
2730 if not header.startswith("HG"):
2729 raise util.Abort(_("%s: not a Mercurial bundle file") % fname)
2731 raise util.Abort(_("%s: not a Mercurial bundle file") % fname)
2730 elif not header.startswith("HG10"):
2732 elif not header.startswith("HG10"):
2731 raise util.Abort(_("%s: unknown bundle version") % fname)
2733 raise util.Abort(_("%s: unknown bundle version") % fname)
2732 elif header == "HG10BZ":
2734 elif header == "HG10BZ":
2733 def generator(f):
2735 def generator(f):
2734 zd = bz2.BZ2Decompressor()
2736 zd = bz2.BZ2Decompressor()
2735 zd.decompress("BZ")
2737 zd.decompress("BZ")
2736 for chunk in f:
2738 for chunk in f:
2737 yield zd.decompress(chunk)
2739 yield zd.decompress(chunk)
2738 elif header == "HG10UN":
2740 elif header == "HG10UN":
2739 def generator(f):
2741 def generator(f):
2740 for chunk in f:
2742 for chunk in f:
2741 yield chunk
2743 yield chunk
2742 else:
2744 else:
2743 raise util.Abort(_("%s: unknown bundle compression type")
2745 raise util.Abort(_("%s: unknown bundle compression type")
2744 % fname)
2746 % fname)
2745 gen = generator(util.filechunkiter(f, 4096))
2747 gen = generator(util.filechunkiter(f, 4096))
2746 modheads = repo.addchangegroup(util.chunkbuffer(gen), 'unbundle')
2748 modheads = repo.addchangegroup(util.chunkbuffer(gen), 'unbundle')
2747 return postincoming(ui, repo, modheads, opts['update'])
2749 return postincoming(ui, repo, modheads, opts['update'])
2748
2750
2749 def undo(ui, repo):
2751 def undo(ui, repo):
2750 """undo the last commit or pull (DEPRECATED)
2752 """undo the last commit or pull (DEPRECATED)
2751
2753
2752 (DEPRECATED)
2754 (DEPRECATED)
2753 This command is now deprecated and will be removed in a future
2755 This command is now deprecated and will be removed in a future
2754 release. Please use the rollback command instead. For usage
2756 release. Please use the rollback command instead. For usage
2755 instructions, see the rollback command.
2757 instructions, see the rollback command.
2756 """
2758 """
2757 ui.warn(_('(the undo command is deprecated; use rollback instead)\n'))
2759 ui.warn(_('(the undo command is deprecated; use rollback instead)\n'))
2758 repo.undo()
2760 repo.undo()
2759
2761
2760 def update(ui, repo, node=None, merge=False, clean=False, force=None,
2762 def update(ui, repo, node=None, merge=False, clean=False, force=None,
2761 branch=None, **opts):
2763 branch=None, **opts):
2762 """update or merge working directory
2764 """update or merge working directory
2763
2765
2764 Update the working directory to the specified revision.
2766 Update the working directory to the specified revision.
2765
2767
2766 If there are no outstanding changes in the working directory and
2768 If there are no outstanding changes in the working directory and
2767 there is a linear relationship between the current version and the
2769 there is a linear relationship between the current version and the
2768 requested version, the result is the requested version.
2770 requested version, the result is the requested version.
2769
2771
2770 To merge the working directory with another revision, use the
2772 To merge the working directory with another revision, use the
2771 merge command.
2773 merge command.
2772
2774
2773 By default, update will refuse to run if doing so would require
2775 By default, update will refuse to run if doing so would require
2774 merging or discarding local changes.
2776 merging or discarding local changes.
2775 """
2777 """
2776 if merge:
2778 if merge:
2777 ui.warn(_('(the -m/--merge option is deprecated; '
2779 ui.warn(_('(the -m/--merge option is deprecated; '
2778 'use the merge command instead)\n'))
2780 'use the merge command instead)\n'))
2779 return doupdate(ui, repo, node, merge, clean, force, branch, **opts)
2781 return doupdate(ui, repo, node, merge, clean, force, branch, **opts)
2780
2782
2781 def doupdate(ui, repo, node=None, merge=False, clean=False, force=None,
2783 def doupdate(ui, repo, node=None, merge=False, clean=False, force=None,
2782 branch=None, **opts):
2784 branch=None, **opts):
2783 if branch:
2785 if branch:
2784 br = repo.branchlookup(branch=branch)
2786 br = repo.branchlookup(branch=branch)
2785 found = []
2787 found = []
2786 for x in br:
2788 for x in br:
2787 if branch in br[x]:
2789 if branch in br[x]:
2788 found.append(x)
2790 found.append(x)
2789 if len(found) > 1:
2791 if len(found) > 1:
2790 ui.warn(_("Found multiple heads for %s\n") % branch)
2792 ui.warn(_("Found multiple heads for %s\n") % branch)
2791 for x in found:
2793 for x in found:
2792 show_changeset(ui, repo, opts).show(changenode=x, brinfo=br)
2794 show_changeset(ui, repo, opts).show(changenode=x, brinfo=br)
2793 return 1
2795 return 1
2794 if len(found) == 1:
2796 if len(found) == 1:
2795 node = found[0]
2797 node = found[0]
2796 ui.warn(_("Using head %s for branch %s\n") % (short(node), branch))
2798 ui.warn(_("Using head %s for branch %s\n") % (short(node), branch))
2797 else:
2799 else:
2798 ui.warn(_("branch %s not found\n") % (branch))
2800 ui.warn(_("branch %s not found\n") % (branch))
2799 return 1
2801 return 1
2800 else:
2802 else:
2801 node = node and repo.lookup(node) or repo.changelog.tip()
2803 node = node and repo.lookup(node) or repo.changelog.tip()
2802 return repo.update(node, allow=merge, force=clean, forcemerge=force)
2804 return repo.update(node, allow=merge, force=clean, forcemerge=force)
2803
2805
2804 def verify(ui, repo):
2806 def verify(ui, repo):
2805 """verify the integrity of the repository
2807 """verify the integrity of the repository
2806
2808
2807 Verify the integrity of the current repository.
2809 Verify the integrity of the current repository.
2808
2810
2809 This will perform an extensive check of the repository's
2811 This will perform an extensive check of the repository's
2810 integrity, validating the hashes and checksums of each entry in
2812 integrity, validating the hashes and checksums of each entry in
2811 the changelog, manifest, and tracked files, as well as the
2813 the changelog, manifest, and tracked files, as well as the
2812 integrity of their crosslinks and indices.
2814 integrity of their crosslinks and indices.
2813 """
2815 """
2814 return repo.verify()
2816 return repo.verify()
2815
2817
2816 # Command options and aliases are listed here, alphabetically
2818 # Command options and aliases are listed here, alphabetically
2817
2819
2818 table = {
2820 table = {
2819 "^add":
2821 "^add":
2820 (add,
2822 (add,
2821 [('I', 'include', [], _('include names matching the given patterns')),
2823 [('I', 'include', [], _('include names matching the given patterns')),
2822 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2824 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2823 _('hg add [OPTION]... [FILE]...')),
2825 _('hg add [OPTION]... [FILE]...')),
2824 "debugaddremove|addremove":
2826 "debugaddremove|addremove":
2825 (addremove,
2827 (addremove,
2826 [('I', 'include', [], _('include names matching the given patterns')),
2828 [('I', 'include', [], _('include names matching the given patterns')),
2827 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2829 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2828 _('hg addremove [OPTION]... [FILE]...')),
2830 _('hg addremove [OPTION]... [FILE]...')),
2829 "^annotate":
2831 "^annotate":
2830 (annotate,
2832 (annotate,
2831 [('r', 'rev', '', _('annotate the specified revision')),
2833 [('r', 'rev', '', _('annotate the specified revision')),
2832 ('a', 'text', None, _('treat all files as text')),
2834 ('a', 'text', None, _('treat all files as text')),
2833 ('u', 'user', None, _('list the author')),
2835 ('u', 'user', None, _('list the author')),
2834 ('d', 'date', None, _('list the date')),
2836 ('d', 'date', None, _('list the date')),
2835 ('n', 'number', None, _('list the revision number (default)')),
2837 ('n', 'number', None, _('list the revision number (default)')),
2836 ('c', 'changeset', None, _('list the changeset')),
2838 ('c', 'changeset', None, _('list the changeset')),
2837 ('I', 'include', [], _('include names matching the given patterns')),
2839 ('I', 'include', [], _('include names matching the given patterns')),
2838 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2840 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2839 _('hg annotate [-r REV] [-a] [-u] [-d] [-n] [-c] FILE...')),
2841 _('hg annotate [-r REV] [-a] [-u] [-d] [-n] [-c] FILE...')),
2840 "archive":
2842 "archive":
2841 (archive,
2843 (archive,
2842 [('', 'no-decode', None, _('do not pass files through decoders')),
2844 [('', 'no-decode', None, _('do not pass files through decoders')),
2843 ('p', 'prefix', '', _('directory prefix for files in archive')),
2845 ('p', 'prefix', '', _('directory prefix for files in archive')),
2844 ('r', 'rev', '', _('revision to distribute')),
2846 ('r', 'rev', '', _('revision to distribute')),
2845 ('t', 'type', '', _('type of distribution to create')),
2847 ('t', 'type', '', _('type of distribution to create')),
2846 ('I', 'include', [], _('include names matching the given patterns')),
2848 ('I', 'include', [], _('include names matching the given patterns')),
2847 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2849 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2848 _('hg archive [OPTION]... DEST')),
2850 _('hg archive [OPTION]... DEST')),
2849 "backout":
2851 "backout":
2850 (backout,
2852 (backout,
2851 [('', 'merge', None,
2853 [('', 'merge', None,
2852 _('merge with old dirstate parent after backout')),
2854 _('merge with old dirstate parent after backout')),
2853 ('m', 'message', '', _('use <text> as commit message')),
2855 ('m', 'message', '', _('use <text> as commit message')),
2854 ('l', 'logfile', '', _('read commit message from <file>')),
2856 ('l', 'logfile', '', _('read commit message from <file>')),
2855 ('d', 'date', '', _('record datecode as commit date')),
2857 ('d', 'date', '', _('record datecode as commit date')),
2856 ('u', 'user', '', _('record user as committer')),
2858 ('u', 'user', '', _('record user as committer')),
2857 ('I', 'include', [], _('include names matching the given patterns')),
2859 ('I', 'include', [], _('include names matching the given patterns')),
2858 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2860 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2859 _('hg backout [OPTION]... REV')),
2861 _('hg backout [OPTION]... REV')),
2860 "bundle":
2862 "bundle":
2861 (bundle,
2863 (bundle,
2862 [('f', 'force', None,
2864 [('f', 'force', None,
2863 _('run even when remote repository is unrelated'))],
2865 _('run even when remote repository is unrelated'))],
2864 _('hg bundle FILE DEST')),
2866 _('hg bundle FILE DEST')),
2865 "cat":
2867 "cat":
2866 (cat,
2868 (cat,
2867 [('o', 'output', '', _('print output to file with formatted name')),
2869 [('o', 'output', '', _('print output to file with formatted name')),
2868 ('r', 'rev', '', _('print the given revision')),
2870 ('r', 'rev', '', _('print the given revision')),
2869 ('I', 'include', [], _('include names matching the given patterns')),
2871 ('I', 'include', [], _('include names matching the given patterns')),
2870 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2872 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2871 _('hg cat [OPTION]... FILE...')),
2873 _('hg cat [OPTION]... FILE...')),
2872 "^clone":
2874 "^clone":
2873 (clone,
2875 (clone,
2874 [('U', 'noupdate', None, _('do not update the new working directory')),
2876 [('U', 'noupdate', None, _('do not update the new working directory')),
2875 ('r', 'rev', [],
2877 ('r', 'rev', [],
2876 _('a changeset you would like to have after cloning')),
2878 _('a changeset you would like to have after cloning')),
2877 ('', 'pull', None, _('use pull protocol to copy metadata')),
2879 ('', 'pull', None, _('use pull protocol to copy metadata')),
2878 ('e', 'ssh', '', _('specify ssh command to use')),
2880 ('e', 'ssh', '', _('specify ssh command to use')),
2879 ('', 'remotecmd', '',
2881 ('', 'remotecmd', '',
2880 _('specify hg command to run on the remote side'))],
2882 _('specify hg command to run on the remote side'))],
2881 _('hg clone [OPTION]... SOURCE [DEST]')),
2883 _('hg clone [OPTION]... SOURCE [DEST]')),
2882 "^commit|ci":
2884 "^commit|ci":
2883 (commit,
2885 (commit,
2884 [('A', 'addremove', None,
2886 [('A', 'addremove', None,
2885 _('mark new/missing files as added/removed before committing')),
2887 _('mark new/missing files as added/removed before committing')),
2886 ('m', 'message', '', _('use <text> as commit message')),
2888 ('m', 'message', '', _('use <text> as commit message')),
2887 ('l', 'logfile', '', _('read the commit message from <file>')),
2889 ('l', 'logfile', '', _('read the commit message from <file>')),
2888 ('d', 'date', '', _('record datecode as commit date')),
2890 ('d', 'date', '', _('record datecode as commit date')),
2889 ('u', 'user', '', _('record user as commiter')),
2891 ('u', 'user', '', _('record user as commiter')),
2890 ('I', 'include', [], _('include names matching the given patterns')),
2892 ('I', 'include', [], _('include names matching the given patterns')),
2891 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2893 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2892 _('hg commit [OPTION]... [FILE]...')),
2894 _('hg commit [OPTION]... [FILE]...')),
2893 "copy|cp":
2895 "copy|cp":
2894 (copy,
2896 (copy,
2895 [('A', 'after', None, _('record a copy that has already occurred')),
2897 [('A', 'after', None, _('record a copy that has already occurred')),
2896 ('f', 'force', None,
2898 ('f', 'force', None,
2897 _('forcibly copy over an existing managed file')),
2899 _('forcibly copy over an existing managed file')),
2898 ('I', 'include', [], _('include names matching the given patterns')),
2900 ('I', 'include', [], _('include names matching the given patterns')),
2899 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2901 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2900 _('hg copy [OPTION]... [SOURCE]... DEST')),
2902 _('hg copy [OPTION]... [SOURCE]... DEST')),
2901 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2903 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2902 "debugcomplete":
2904 "debugcomplete":
2903 (debugcomplete,
2905 (debugcomplete,
2904 [('o', 'options', None, _('show the command options'))],
2906 [('o', 'options', None, _('show the command options'))],
2905 _('debugcomplete [-o] CMD')),
2907 _('debugcomplete [-o] CMD')),
2906 "debugrebuildstate":
2908 "debugrebuildstate":
2907 (debugrebuildstate,
2909 (debugrebuildstate,
2908 [('r', 'rev', '', _('revision to rebuild to'))],
2910 [('r', 'rev', '', _('revision to rebuild to'))],
2909 _('debugrebuildstate [-r REV] [REV]')),
2911 _('debugrebuildstate [-r REV] [REV]')),
2910 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2912 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2911 "debugconfig": (debugconfig, [], _('debugconfig')),
2913 "debugconfig": (debugconfig, [], _('debugconfig')),
2912 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2914 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2913 "debugstate": (debugstate, [], _('debugstate')),
2915 "debugstate": (debugstate, [], _('debugstate')),
2914 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2916 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2915 "debugindex": (debugindex, [], _('debugindex FILE')),
2917 "debugindex": (debugindex, [], _('debugindex FILE')),
2916 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2918 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2917 "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
2919 "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
2918 "debugwalk":
2920 "debugwalk":
2919 (debugwalk,
2921 (debugwalk,
2920 [('I', 'include', [], _('include names matching the given patterns')),
2922 [('I', 'include', [], _('include names matching the given patterns')),
2921 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2923 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2922 _('debugwalk [OPTION]... [FILE]...')),
2924 _('debugwalk [OPTION]... [FILE]...')),
2923 "^diff":
2925 "^diff":
2924 (diff,
2926 (diff,
2925 [('r', 'rev', [], _('revision')),
2927 [('r', 'rev', [], _('revision')),
2926 ('a', 'text', None, _('treat all files as text')),
2928 ('a', 'text', None, _('treat all files as text')),
2927 ('p', 'show-function', None,
2929 ('p', 'show-function', None,
2928 _('show which function each change is in')),
2930 _('show which function each change is in')),
2929 ('w', 'ignore-all-space', None,
2931 ('w', 'ignore-all-space', None,
2930 _('ignore white space when comparing lines')),
2932 _('ignore white space when comparing lines')),
2931 ('I', 'include', [], _('include names matching the given patterns')),
2933 ('I', 'include', [], _('include names matching the given patterns')),
2932 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2934 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2933 _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')),
2935 _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')),
2934 "^export":
2936 "^export":
2935 (export,
2937 (export,
2936 [('o', 'output', '', _('print output to file with formatted name')),
2938 [('o', 'output', '', _('print output to file with formatted name')),
2937 ('a', 'text', None, _('treat all files as text')),
2939 ('a', 'text', None, _('treat all files as text')),
2938 ('', 'switch-parent', None, _('diff against the second parent'))],
2940 ('', 'switch-parent', None, _('diff against the second parent'))],
2939 _('hg export [-a] [-o OUTFILESPEC] REV...')),
2941 _('hg export [-a] [-o OUTFILESPEC] REV...')),
2940 "debugforget|forget":
2942 "debugforget|forget":
2941 (forget,
2943 (forget,
2942 [('I', 'include', [], _('include names matching the given patterns')),
2944 [('I', 'include', [], _('include names matching the given patterns')),
2943 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2945 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2944 _('hg forget [OPTION]... FILE...')),
2946 _('hg forget [OPTION]... FILE...')),
2945 "grep":
2947 "grep":
2946 (grep,
2948 (grep,
2947 [('0', 'print0', None, _('end fields with NUL')),
2949 [('0', 'print0', None, _('end fields with NUL')),
2948 ('', 'all', None, _('print all revisions that match')),
2950 ('', 'all', None, _('print all revisions that match')),
2949 ('i', 'ignore-case', None, _('ignore case when matching')),
2951 ('i', 'ignore-case', None, _('ignore case when matching')),
2950 ('l', 'files-with-matches', None,
2952 ('l', 'files-with-matches', None,
2951 _('print only filenames and revs that match')),
2953 _('print only filenames and revs that match')),
2952 ('n', 'line-number', None, _('print matching line numbers')),
2954 ('n', 'line-number', None, _('print matching line numbers')),
2953 ('r', 'rev', [], _('search in given revision range')),
2955 ('r', 'rev', [], _('search in given revision range')),
2954 ('u', 'user', None, _('print user who committed change')),
2956 ('u', 'user', None, _('print user who committed change')),
2955 ('I', 'include', [], _('include names matching the given patterns')),
2957 ('I', 'include', [], _('include names matching the given patterns')),
2956 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2958 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2957 _('hg grep [OPTION]... PATTERN [FILE]...')),
2959 _('hg grep [OPTION]... PATTERN [FILE]...')),
2958 "heads":
2960 "heads":
2959 (heads,
2961 (heads,
2960 [('b', 'branches', None, _('show branches')),
2962 [('b', 'branches', None, _('show branches')),
2961 ('', 'style', '', _('display using template map file')),
2963 ('', 'style', '', _('display using template map file')),
2962 ('r', 'rev', '', _('show only heads which are descendants of rev')),
2964 ('r', 'rev', '', _('show only heads which are descendants of rev')),
2963 ('', 'template', '', _('display with template'))],
2965 ('', 'template', '', _('display with template'))],
2964 _('hg heads [-b] [-r <rev>]')),
2966 _('hg heads [-b] [-r <rev>]')),
2965 "help": (help_, [], _('hg help [COMMAND]')),
2967 "help": (help_, [], _('hg help [COMMAND]')),
2966 "identify|id": (identify, [], _('hg identify')),
2968 "identify|id": (identify, [], _('hg identify')),
2967 "import|patch":
2969 "import|patch":
2968 (import_,
2970 (import_,
2969 [('p', 'strip', 1,
2971 [('p', 'strip', 1,
2970 _('directory strip option for patch. This has the same\n'
2972 _('directory strip option for patch. This has the same\n'
2971 'meaning as the corresponding patch option')),
2973 'meaning as the corresponding patch option')),
2972 ('b', 'base', '', _('base path')),
2974 ('b', 'base', '', _('base path')),
2973 ('f', 'force', None,
2975 ('f', 'force', None,
2974 _('skip check for outstanding uncommitted changes'))],
2976 _('skip check for outstanding uncommitted changes'))],
2975 _('hg import [-p NUM] [-b BASE] [-f] PATCH...')),
2977 _('hg import [-p NUM] [-b BASE] [-f] PATCH...')),
2976 "incoming|in": (incoming,
2978 "incoming|in": (incoming,
2977 [('M', 'no-merges', None, _('do not show merges')),
2979 [('M', 'no-merges', None, _('do not show merges')),
2978 ('f', 'force', None,
2980 ('f', 'force', None,
2979 _('run even when remote repository is unrelated')),
2981 _('run even when remote repository is unrelated')),
2980 ('', 'style', '', _('display using template map file')),
2982 ('', 'style', '', _('display using template map file')),
2981 ('n', 'newest-first', None, _('show newest record first')),
2983 ('n', 'newest-first', None, _('show newest record first')),
2982 ('', 'bundle', '', _('file to store the bundles into')),
2984 ('', 'bundle', '', _('file to store the bundles into')),
2983 ('p', 'patch', None, _('show patch')),
2985 ('p', 'patch', None, _('show patch')),
2984 ('', 'template', '', _('display with template')),
2986 ('', 'template', '', _('display with template')),
2985 ('e', 'ssh', '', _('specify ssh command to use')),
2987 ('e', 'ssh', '', _('specify ssh command to use')),
2986 ('', 'remotecmd', '',
2988 ('', 'remotecmd', '',
2987 _('specify hg command to run on the remote side'))],
2989 _('specify hg command to run on the remote side'))],
2988 _('hg incoming [-p] [-n] [-M] [--bundle FILENAME] [SOURCE]')),
2990 _('hg incoming [-p] [-n] [-M] [--bundle FILENAME] [SOURCE]')),
2989 "^init": (init, [], _('hg init [DEST]')),
2991 "^init": (init, [], _('hg init [DEST]')),
2990 "locate":
2992 "locate":
2991 (locate,
2993 (locate,
2992 [('r', 'rev', '', _('search the repository as it stood at rev')),
2994 [('r', 'rev', '', _('search the repository as it stood at rev')),
2993 ('0', 'print0', None,
2995 ('0', 'print0', None,
2994 _('end filenames with NUL, for use with xargs')),
2996 _('end filenames with NUL, for use with xargs')),
2995 ('f', 'fullpath', None,
2997 ('f', 'fullpath', None,
2996 _('print complete paths from the filesystem root')),
2998 _('print complete paths from the filesystem root')),
2997 ('I', 'include', [], _('include names matching the given patterns')),
2999 ('I', 'include', [], _('include names matching the given patterns')),
2998 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3000 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2999 _('hg locate [OPTION]... [PATTERN]...')),
3001 _('hg locate [OPTION]... [PATTERN]...')),
3000 "^log|history":
3002 "^log|history":
3001 (log,
3003 (log,
3002 [('b', 'branches', None, _('show branches')),
3004 [('b', 'branches', None, _('show branches')),
3003 ('k', 'keyword', [], _('search for a keyword')),
3005 ('k', 'keyword', [], _('search for a keyword')),
3004 ('l', 'limit', '', _('limit number of changes displayed')),
3006 ('l', 'limit', '', _('limit number of changes displayed')),
3005 ('r', 'rev', [], _('show the specified revision or range')),
3007 ('r', 'rev', [], _('show the specified revision or range')),
3006 ('M', 'no-merges', None, _('do not show merges')),
3008 ('M', 'no-merges', None, _('do not show merges')),
3007 ('', 'style', '', _('display using template map file')),
3009 ('', 'style', '', _('display using template map file')),
3008 ('m', 'only-merges', None, _('show only merges')),
3010 ('m', 'only-merges', None, _('show only merges')),
3009 ('p', 'patch', None, _('show patch')),
3011 ('p', 'patch', None, _('show patch')),
3010 ('', 'template', '', _('display with template')),
3012 ('', 'template', '', _('display with template')),
3011 ('I', 'include', [], _('include names matching the given patterns')),
3013 ('I', 'include', [], _('include names matching the given patterns')),
3012 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3014 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3013 _('hg log [OPTION]... [FILE]')),
3015 _('hg log [OPTION]... [FILE]')),
3014 "manifest": (manifest, [], _('hg manifest [REV]')),
3016 "manifest": (manifest, [], _('hg manifest [REV]')),
3015 "merge":
3017 "merge":
3016 (merge,
3018 (merge,
3017 [('b', 'branch', '', _('merge with head of a specific branch')),
3019 [('b', 'branch', '', _('merge with head of a specific branch')),
3018 ('f', 'force', None, _('force a merge with outstanding changes'))],
3020 ('f', 'force', None, _('force a merge with outstanding changes'))],
3019 _('hg merge [-b TAG] [-f] [REV]')),
3021 _('hg merge [-b TAG] [-f] [REV]')),
3020 "outgoing|out": (outgoing,
3022 "outgoing|out": (outgoing,
3021 [('M', 'no-merges', None, _('do not show merges')),
3023 [('M', 'no-merges', None, _('do not show merges')),
3022 ('f', 'force', None,
3024 ('f', 'force', None,
3023 _('run even when remote repository is unrelated')),
3025 _('run even when remote repository is unrelated')),
3024 ('p', 'patch', None, _('show patch')),
3026 ('p', 'patch', None, _('show patch')),
3025 ('', 'style', '', _('display using template map file')),
3027 ('', 'style', '', _('display using template map file')),
3026 ('n', 'newest-first', None, _('show newest record first')),
3028 ('n', 'newest-first', None, _('show newest record first')),
3027 ('', 'template', '', _('display with template')),
3029 ('', 'template', '', _('display with template')),
3028 ('e', 'ssh', '', _('specify ssh command to use')),
3030 ('e', 'ssh', '', _('specify ssh command to use')),
3029 ('', 'remotecmd', '',
3031 ('', 'remotecmd', '',
3030 _('specify hg command to run on the remote side'))],
3032 _('specify hg command to run on the remote side'))],
3031 _('hg outgoing [-M] [-p] [-n] [DEST]')),
3033 _('hg outgoing [-M] [-p] [-n] [DEST]')),
3032 "^parents":
3034 "^parents":
3033 (parents,
3035 (parents,
3034 [('b', 'branches', None, _('show branches')),
3036 [('b', 'branches', None, _('show branches')),
3035 ('', 'style', '', _('display using template map file')),
3037 ('', 'style', '', _('display using template map file')),
3036 ('', 'template', '', _('display with template'))],
3038 ('', 'template', '', _('display with template'))],
3037 _('hg parents [-b] [REV]')),
3039 _('hg parents [-b] [REV]')),
3038 "paths": (paths, [], _('hg paths [NAME]')),
3040 "paths": (paths, [], _('hg paths [NAME]')),
3039 "^pull":
3041 "^pull":
3040 (pull,
3042 (pull,
3041 [('u', 'update', None,
3043 [('u', 'update', None,
3042 _('update the working directory to tip after pull')),
3044 _('update the working directory to tip after pull')),
3043 ('e', 'ssh', '', _('specify ssh command to use')),
3045 ('e', 'ssh', '', _('specify ssh command to use')),
3044 ('f', 'force', None,
3046 ('f', 'force', None,
3045 _('run even when remote repository is unrelated')),
3047 _('run even when remote repository is unrelated')),
3046 ('r', 'rev', [], _('a specific revision you would like to pull')),
3048 ('r', 'rev', [], _('a specific revision you would like to pull')),
3047 ('', 'remotecmd', '',
3049 ('', 'remotecmd', '',
3048 _('specify hg command to run on the remote side'))],
3050 _('specify hg command to run on the remote side'))],
3049 _('hg pull [-u] [-e FILE] [-r REV]... [--remotecmd FILE] [SOURCE]')),
3051 _('hg pull [-u] [-e FILE] [-r REV]... [--remotecmd FILE] [SOURCE]')),
3050 "^push":
3052 "^push":
3051 (push,
3053 (push,
3052 [('f', 'force', None, _('force push')),
3054 [('f', 'force', None, _('force push')),
3053 ('e', 'ssh', '', _('specify ssh command to use')),
3055 ('e', 'ssh', '', _('specify ssh command to use')),
3054 ('r', 'rev', [], _('a specific revision you would like to push')),
3056 ('r', 'rev', [], _('a specific revision you would like to push')),
3055 ('', 'remotecmd', '',
3057 ('', 'remotecmd', '',
3056 _('specify hg command to run on the remote side'))],
3058 _('specify hg command to run on the remote side'))],
3057 _('hg push [-f] [-e FILE] [-r REV]... [--remotecmd FILE] [DEST]')),
3059 _('hg push [-f] [-e FILE] [-r REV]... [--remotecmd FILE] [DEST]')),
3058 "debugrawcommit|rawcommit":
3060 "debugrawcommit|rawcommit":
3059 (rawcommit,
3061 (rawcommit,
3060 [('p', 'parent', [], _('parent')),
3062 [('p', 'parent', [], _('parent')),
3061 ('d', 'date', '', _('date code')),
3063 ('d', 'date', '', _('date code')),
3062 ('u', 'user', '', _('user')),
3064 ('u', 'user', '', _('user')),
3063 ('F', 'files', '', _('file list')),
3065 ('F', 'files', '', _('file list')),
3064 ('m', 'message', '', _('commit message')),
3066 ('m', 'message', '', _('commit message')),
3065 ('l', 'logfile', '', _('commit message file'))],
3067 ('l', 'logfile', '', _('commit message file'))],
3066 _('hg debugrawcommit [OPTION]... [FILE]...')),
3068 _('hg debugrawcommit [OPTION]... [FILE]...')),
3067 "recover": (recover, [], _('hg recover')),
3069 "recover": (recover, [], _('hg recover')),
3068 "^remove|rm":
3070 "^remove|rm":
3069 (remove,
3071 (remove,
3070 [('A', 'after', None, _('record remove that has already occurred')),
3072 [('A', 'after', None, _('record remove that has already occurred')),
3071 ('f', 'force', None, _('remove file even if modified')),
3073 ('f', 'force', None, _('remove file even if modified')),
3072 ('I', 'include', [], _('include names matching the given patterns')),
3074 ('I', 'include', [], _('include names matching the given patterns')),
3073 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3075 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3074 _('hg remove [OPTION]... FILE...')),
3076 _('hg remove [OPTION]... FILE...')),
3075 "rename|mv":
3077 "rename|mv":
3076 (rename,
3078 (rename,
3077 [('A', 'after', None, _('record a rename that has already occurred')),
3079 [('A', 'after', None, _('record a rename that has already occurred')),
3078 ('f', 'force', None,
3080 ('f', 'force', None,
3079 _('forcibly copy over an existing managed file')),
3081 _('forcibly copy over an existing managed file')),
3080 ('I', 'include', [], _('include names matching the given patterns')),
3082 ('I', 'include', [], _('include names matching the given patterns')),
3081 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3083 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3082 _('hg rename [OPTION]... SOURCE... DEST')),
3084 _('hg rename [OPTION]... SOURCE... DEST')),
3083 "^revert":
3085 "^revert":
3084 (revert,
3086 (revert,
3085 [('r', 'rev', '', _('revision to revert to')),
3087 [('r', 'rev', '', _('revision to revert to')),
3086 ('', 'no-backup', None, _('do not save backup copies of files')),
3088 ('', 'no-backup', None, _('do not save backup copies of files')),
3087 ('I', 'include', [], _('include names matching given patterns')),
3089 ('I', 'include', [], _('include names matching given patterns')),
3088 ('X', 'exclude', [], _('exclude names matching given patterns'))],
3090 ('X', 'exclude', [], _('exclude names matching given patterns'))],
3089 _('hg revert [-r REV] [NAME]...')),
3091 _('hg revert [-r REV] [NAME]...')),
3090 "rollback": (rollback, [], _('hg rollback')),
3092 "rollback": (rollback, [], _('hg rollback')),
3091 "root": (root, [], _('hg root')),
3093 "root": (root, [], _('hg root')),
3092 "^serve":
3094 "^serve":
3093 (serve,
3095 (serve,
3094 [('A', 'accesslog', '', _('name of access log file to write to')),
3096 [('A', 'accesslog', '', _('name of access log file to write to')),
3095 ('d', 'daemon', None, _('run server in background')),
3097 ('d', 'daemon', None, _('run server in background')),
3096 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3098 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3097 ('E', 'errorlog', '', _('name of error log file to write to')),
3099 ('E', 'errorlog', '', _('name of error log file to write to')),
3098 ('p', 'port', 0, _('port to use (default: 8000)')),
3100 ('p', 'port', 0, _('port to use (default: 8000)')),
3099 ('a', 'address', '', _('address to use')),
3101 ('a', 'address', '', _('address to use')),
3100 ('n', 'name', '',
3102 ('n', 'name', '',
3101 _('name to show in web pages (default: working dir)')),
3103 _('name to show in web pages (default: working dir)')),
3102 ('', 'webdir-conf', '', _('name of the webdir config file'
3104 ('', 'webdir-conf', '', _('name of the webdir config file'
3103 ' (serve more than one repo)')),
3105 ' (serve more than one repo)')),
3104 ('', 'pid-file', '', _('name of file to write process ID to')),
3106 ('', 'pid-file', '', _('name of file to write process ID to')),
3105 ('', 'stdio', None, _('for remote clients')),
3107 ('', 'stdio', None, _('for remote clients')),
3106 ('t', 'templates', '', _('web templates to use')),
3108 ('t', 'templates', '', _('web templates to use')),
3107 ('', 'style', '', _('template style to use')),
3109 ('', 'style', '', _('template style to use')),
3108 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
3110 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
3109 _('hg serve [OPTION]...')),
3111 _('hg serve [OPTION]...')),
3110 "^status|st":
3112 "^status|st":
3111 (status,
3113 (status,
3112 [('m', 'modified', None, _('show only modified files')),
3114 [('m', 'modified', None, _('show only modified files')),
3113 ('a', 'added', None, _('show only added files')),
3115 ('a', 'added', None, _('show only added files')),
3114 ('r', 'removed', None, _('show only removed files')),
3116 ('r', 'removed', None, _('show only removed files')),
3115 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3117 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3116 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3118 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3117 ('i', 'ignored', None, _('show ignored files')),
3119 ('i', 'ignored', None, _('show ignored files')),
3118 ('n', 'no-status', None, _('hide status prefix')),
3120 ('n', 'no-status', None, _('hide status prefix')),
3119 ('0', 'print0', None,
3121 ('0', 'print0', None,
3120 _('end filenames with NUL, for use with xargs')),
3122 _('end filenames with NUL, for use with xargs')),
3121 ('I', 'include', [], _('include names matching the given patterns')),
3123 ('I', 'include', [], _('include names matching the given patterns')),
3122 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3124 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3123 _('hg status [OPTION]... [FILE]...')),
3125 _('hg status [OPTION]... [FILE]...')),
3124 "tag":
3126 "tag":
3125 (tag,
3127 (tag,
3126 [('l', 'local', None, _('make the tag local')),
3128 [('l', 'local', None, _('make the tag local')),
3127 ('m', 'message', '', _('message for tag commit log entry')),
3129 ('m', 'message', '', _('message for tag commit log entry')),
3128 ('d', 'date', '', _('record datecode as commit date')),
3130 ('d', 'date', '', _('record datecode as commit date')),
3129 ('u', 'user', '', _('record user as commiter')),
3131 ('u', 'user', '', _('record user as commiter')),
3130 ('r', 'rev', '', _('revision to tag'))],
3132 ('r', 'rev', '', _('revision to tag'))],
3131 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
3133 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
3132 "tags": (tags, [], _('hg tags')),
3134 "tags": (tags, [], _('hg tags')),
3133 "tip":
3135 "tip":
3134 (tip,
3136 (tip,
3135 [('b', 'branches', None, _('show branches')),
3137 [('b', 'branches', None, _('show branches')),
3136 ('', 'style', '', _('display using template map file')),
3138 ('', 'style', '', _('display using template map file')),
3137 ('p', 'patch', None, _('show patch')),
3139 ('p', 'patch', None, _('show patch')),
3138 ('', 'template', '', _('display with template'))],
3140 ('', 'template', '', _('display with template'))],
3139 _('hg tip [-b] [-p]')),
3141 _('hg tip [-b] [-p]')),
3140 "unbundle":
3142 "unbundle":
3141 (unbundle,
3143 (unbundle,
3142 [('u', 'update', None,
3144 [('u', 'update', None,
3143 _('update the working directory to tip after unbundle'))],
3145 _('update the working directory to tip after unbundle'))],
3144 _('hg unbundle [-u] FILE')),
3146 _('hg unbundle [-u] FILE')),
3145 "debugundo|undo": (undo, [], _('hg undo')),
3147 "debugundo|undo": (undo, [], _('hg undo')),
3146 "^update|up|checkout|co":
3148 "^update|up|checkout|co":
3147 (update,
3149 (update,
3148 [('b', 'branch', '', _('checkout the head of a specific branch')),
3150 [('b', 'branch', '', _('checkout the head of a specific branch')),
3149 ('m', 'merge', None, _('allow merging of branches (DEPRECATED)')),
3151 ('m', 'merge', None, _('allow merging of branches (DEPRECATED)')),
3150 ('C', 'clean', None, _('overwrite locally modified files')),
3152 ('C', 'clean', None, _('overwrite locally modified files')),
3151 ('f', 'force', None, _('force a merge with outstanding changes'))],
3153 ('f', 'force', None, _('force a merge with outstanding changes'))],
3152 _('hg update [-b TAG] [-m] [-C] [-f] [REV]')),
3154 _('hg update [-b TAG] [-m] [-C] [-f] [REV]')),
3153 "verify": (verify, [], _('hg verify')),
3155 "verify": (verify, [], _('hg verify')),
3154 "version": (show_version, [], _('hg version')),
3156 "version": (show_version, [], _('hg version')),
3155 }
3157 }
3156
3158
3157 globalopts = [
3159 globalopts = [
3158 ('R', 'repository', '',
3160 ('R', 'repository', '',
3159 _('repository root directory or symbolic path name')),
3161 _('repository root directory or symbolic path name')),
3160 ('', 'cwd', '', _('change working directory')),
3162 ('', 'cwd', '', _('change working directory')),
3161 ('y', 'noninteractive', None,
3163 ('y', 'noninteractive', None,
3162 _('do not prompt, assume \'yes\' for any required answers')),
3164 _('do not prompt, assume \'yes\' for any required answers')),
3163 ('q', 'quiet', None, _('suppress output')),
3165 ('q', 'quiet', None, _('suppress output')),
3164 ('v', 'verbose', None, _('enable additional output')),
3166 ('v', 'verbose', None, _('enable additional output')),
3165 ('', 'config', [], _('set/override config option')),
3167 ('', 'config', [], _('set/override config option')),
3166 ('', 'debug', None, _('enable debugging output')),
3168 ('', 'debug', None, _('enable debugging output')),
3167 ('', 'debugger', None, _('start debugger')),
3169 ('', 'debugger', None, _('start debugger')),
3168 ('', 'traceback', None, _('print traceback on exception')),
3170 ('', 'traceback', None, _('print traceback on exception')),
3169 ('', 'time', None, _('time how long the command takes')),
3171 ('', 'time', None, _('time how long the command takes')),
3170 ('', 'profile', None, _('print command execution profile')),
3172 ('', 'profile', None, _('print command execution profile')),
3171 ('', 'version', None, _('output version information and exit')),
3173 ('', 'version', None, _('output version information and exit')),
3172 ('h', 'help', None, _('display help and exit')),
3174 ('h', 'help', None, _('display help and exit')),
3173 ]
3175 ]
3174
3176
3175 norepo = ("clone init version help debugancestor debugcomplete debugdata"
3177 norepo = ("clone init version help debugancestor debugcomplete debugdata"
3176 " debugindex debugindexdot")
3178 " debugindex debugindexdot")
3177 optionalrepo = ("paths serve debugconfig")
3179 optionalrepo = ("paths serve debugconfig")
3178
3180
3179 def findpossible(cmd):
3181 def findpossible(cmd):
3180 """
3182 """
3181 Return cmd -> (aliases, command table entry)
3183 Return cmd -> (aliases, command table entry)
3182 for each matching command.
3184 for each matching command.
3183 Return debug commands (or their aliases) only if no normal command matches.
3185 Return debug commands (or their aliases) only if no normal command matches.
3184 """
3186 """
3185 choice = {}
3187 choice = {}
3186 debugchoice = {}
3188 debugchoice = {}
3187 for e in table.keys():
3189 for e in table.keys():
3188 aliases = e.lstrip("^").split("|")
3190 aliases = e.lstrip("^").split("|")
3189 found = None
3191 found = None
3190 if cmd in aliases:
3192 if cmd in aliases:
3191 found = cmd
3193 found = cmd
3192 else:
3194 else:
3193 for a in aliases:
3195 for a in aliases:
3194 if a.startswith(cmd):
3196 if a.startswith(cmd):
3195 found = a
3197 found = a
3196 break
3198 break
3197 if found is not None:
3199 if found is not None:
3198 if aliases[0].startswith("debug"):
3200 if aliases[0].startswith("debug"):
3199 debugchoice[found] = (aliases, table[e])
3201 debugchoice[found] = (aliases, table[e])
3200 else:
3202 else:
3201 choice[found] = (aliases, table[e])
3203 choice[found] = (aliases, table[e])
3202
3204
3203 if not choice and debugchoice:
3205 if not choice and debugchoice:
3204 choice = debugchoice
3206 choice = debugchoice
3205
3207
3206 return choice
3208 return choice
3207
3209
3208 def find(cmd):
3210 def find(cmd):
3209 """Return (aliases, command table entry) for command string."""
3211 """Return (aliases, command table entry) for command string."""
3210 choice = findpossible(cmd)
3212 choice = findpossible(cmd)
3211
3213
3212 if choice.has_key(cmd):
3214 if choice.has_key(cmd):
3213 return choice[cmd]
3215 return choice[cmd]
3214
3216
3215 if len(choice) > 1:
3217 if len(choice) > 1:
3216 clist = choice.keys()
3218 clist = choice.keys()
3217 clist.sort()
3219 clist.sort()
3218 raise AmbiguousCommand(cmd, clist)
3220 raise AmbiguousCommand(cmd, clist)
3219
3221
3220 if choice:
3222 if choice:
3221 return choice.values()[0]
3223 return choice.values()[0]
3222
3224
3223 raise UnknownCommand(cmd)
3225 raise UnknownCommand(cmd)
3224
3226
3225 def catchterm(*args):
3227 def catchterm(*args):
3226 raise util.SignalInterrupt
3228 raise util.SignalInterrupt
3227
3229
3228 def run():
3230 def run():
3229 sys.exit(dispatch(sys.argv[1:]))
3231 sys.exit(dispatch(sys.argv[1:]))
3230
3232
3231 class ParseError(Exception):
3233 class ParseError(Exception):
3232 """Exception raised on errors in parsing the command line."""
3234 """Exception raised on errors in parsing the command line."""
3233
3235
3234 def parse(ui, args):
3236 def parse(ui, args):
3235 options = {}
3237 options = {}
3236 cmdoptions = {}
3238 cmdoptions = {}
3237
3239
3238 try:
3240 try:
3239 args = fancyopts.fancyopts(args, globalopts, options)
3241 args = fancyopts.fancyopts(args, globalopts, options)
3240 except fancyopts.getopt.GetoptError, inst:
3242 except fancyopts.getopt.GetoptError, inst:
3241 raise ParseError(None, inst)
3243 raise ParseError(None, inst)
3242
3244
3243 if args:
3245 if args:
3244 cmd, args = args[0], args[1:]
3246 cmd, args = args[0], args[1:]
3245 aliases, i = find(cmd)
3247 aliases, i = find(cmd)
3246 cmd = aliases[0]
3248 cmd = aliases[0]
3247 defaults = ui.config("defaults", cmd)
3249 defaults = ui.config("defaults", cmd)
3248 if defaults:
3250 if defaults:
3249 args = defaults.split() + args
3251 args = defaults.split() + args
3250 c = list(i[1])
3252 c = list(i[1])
3251 else:
3253 else:
3252 cmd = None
3254 cmd = None
3253 c = []
3255 c = []
3254
3256
3255 # combine global options into local
3257 # combine global options into local
3256 for o in globalopts:
3258 for o in globalopts:
3257 c.append((o[0], o[1], options[o[1]], o[3]))
3259 c.append((o[0], o[1], options[o[1]], o[3]))
3258
3260
3259 try:
3261 try:
3260 args = fancyopts.fancyopts(args, c, cmdoptions)
3262 args = fancyopts.fancyopts(args, c, cmdoptions)
3261 except fancyopts.getopt.GetoptError, inst:
3263 except fancyopts.getopt.GetoptError, inst:
3262 raise ParseError(cmd, inst)
3264 raise ParseError(cmd, inst)
3263
3265
3264 # separate global options back out
3266 # separate global options back out
3265 for o in globalopts:
3267 for o in globalopts:
3266 n = o[1]
3268 n = o[1]
3267 options[n] = cmdoptions[n]
3269 options[n] = cmdoptions[n]
3268 del cmdoptions[n]
3270 del cmdoptions[n]
3269
3271
3270 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
3272 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
3271
3273
3272 def dispatch(args):
3274 def dispatch(args):
3273 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
3275 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
3274 num = getattr(signal, name, None)
3276 num = getattr(signal, name, None)
3275 if num: signal.signal(num, catchterm)
3277 if num: signal.signal(num, catchterm)
3276
3278
3277 try:
3279 try:
3278 u = ui.ui(traceback='--traceback' in sys.argv[1:])
3280 u = ui.ui(traceback='--traceback' in sys.argv[1:])
3279 except util.Abort, inst:
3281 except util.Abort, inst:
3280 sys.stderr.write(_("abort: %s\n") % inst)
3282 sys.stderr.write(_("abort: %s\n") % inst)
3281 return -1
3283 return -1
3282
3284
3283 external = []
3285 external = []
3284 for x in u.extensions():
3286 for x in u.extensions():
3285 try:
3287 try:
3286 if x[1]:
3288 if x[1]:
3287 mod = imp.load_source(x[0], x[1])
3289 mod = imp.load_source(x[0], x[1])
3288 else:
3290 else:
3289 def importh(name):
3291 def importh(name):
3290 mod = __import__(name)
3292 mod = __import__(name)
3291 components = name.split('.')
3293 components = name.split('.')
3292 for comp in components[1:]:
3294 for comp in components[1:]:
3293 mod = getattr(mod, comp)
3295 mod = getattr(mod, comp)
3294 return mod
3296 return mod
3295 try:
3297 try:
3296 mod = importh("hgext." + x[0])
3298 mod = importh("hgext." + x[0])
3297 except ImportError:
3299 except ImportError:
3298 mod = importh(x[0])
3300 mod = importh(x[0])
3299 external.append(mod)
3301 external.append(mod)
3300 except Exception, inst:
3302 except Exception, inst:
3301 u.warn(_("*** failed to import extension %s: %s\n") % (x[0], inst))
3303 u.warn(_("*** failed to import extension %s: %s\n") % (x[0], inst))
3302 if u.print_exc():
3304 if u.print_exc():
3303 return 1
3305 return 1
3304
3306
3305 for x in external:
3307 for x in external:
3306 uisetup = getattr(x, 'uisetup', None)
3308 uisetup = getattr(x, 'uisetup', None)
3307 if uisetup:
3309 if uisetup:
3308 uisetup(u)
3310 uisetup(u)
3309 cmdtable = getattr(x, 'cmdtable', {})
3311 cmdtable = getattr(x, 'cmdtable', {})
3310 for t in cmdtable:
3312 for t in cmdtable:
3311 if t in table:
3313 if t in table:
3312 u.warn(_("module %s overrides %s\n") % (x.__name__, t))
3314 u.warn(_("module %s overrides %s\n") % (x.__name__, t))
3313 table.update(cmdtable)
3315 table.update(cmdtable)
3314
3316
3315 try:
3317 try:
3316 cmd, func, args, options, cmdoptions = parse(u, args)
3318 cmd, func, args, options, cmdoptions = parse(u, args)
3317 if options["time"]:
3319 if options["time"]:
3318 def get_times():
3320 def get_times():
3319 t = os.times()
3321 t = os.times()
3320 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
3322 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
3321 t = (t[0], t[1], t[2], t[3], time.clock())
3323 t = (t[0], t[1], t[2], t[3], time.clock())
3322 return t
3324 return t
3323 s = get_times()
3325 s = get_times()
3324 def print_time():
3326 def print_time():
3325 t = get_times()
3327 t = get_times()
3326 u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
3328 u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
3327 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
3329 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
3328 atexit.register(print_time)
3330 atexit.register(print_time)
3329
3331
3330 u.updateopts(options["verbose"], options["debug"], options["quiet"],
3332 u.updateopts(options["verbose"], options["debug"], options["quiet"],
3331 not options["noninteractive"], options["traceback"],
3333 not options["noninteractive"], options["traceback"],
3332 options["config"])
3334 options["config"])
3333
3335
3334 # enter the debugger before command execution
3336 # enter the debugger before command execution
3335 if options['debugger']:
3337 if options['debugger']:
3336 pdb.set_trace()
3338 pdb.set_trace()
3337
3339
3338 try:
3340 try:
3339 if options['cwd']:
3341 if options['cwd']:
3340 try:
3342 try:
3341 os.chdir(options['cwd'])
3343 os.chdir(options['cwd'])
3342 except OSError, inst:
3344 except OSError, inst:
3343 raise util.Abort('%s: %s' %
3345 raise util.Abort('%s: %s' %
3344 (options['cwd'], inst.strerror))
3346 (options['cwd'], inst.strerror))
3345
3347
3346 path = u.expandpath(options["repository"]) or ""
3348 path = u.expandpath(options["repository"]) or ""
3347 repo = path and hg.repository(u, path=path) or None
3349 repo = path and hg.repository(u, path=path) or None
3348
3350
3349 if options['help']:
3351 if options['help']:
3350 return help_(u, cmd, options['version'])
3352 return help_(u, cmd, options['version'])
3351 elif options['version']:
3353 elif options['version']:
3352 return show_version(u)
3354 return show_version(u)
3353 elif not cmd:
3355 elif not cmd:
3354 return help_(u, 'shortlist')
3356 return help_(u, 'shortlist')
3355
3357
3356 if cmd not in norepo.split():
3358 if cmd not in norepo.split():
3357 try:
3359 try:
3358 if not repo:
3360 if not repo:
3359 repo = hg.repository(u, path=path)
3361 repo = hg.repository(u, path=path)
3360 u = repo.ui
3362 u = repo.ui
3361 for x in external:
3363 for x in external:
3362 if hasattr(x, 'reposetup'):
3364 if hasattr(x, 'reposetup'):
3363 x.reposetup(u, repo)
3365 x.reposetup(u, repo)
3364 except hg.RepoError:
3366 except hg.RepoError:
3365 if cmd not in optionalrepo.split():
3367 if cmd not in optionalrepo.split():
3366 raise
3368 raise
3367 d = lambda: func(u, repo, *args, **cmdoptions)
3369 d = lambda: func(u, repo, *args, **cmdoptions)
3368 else:
3370 else:
3369 d = lambda: func(u, *args, **cmdoptions)
3371 d = lambda: func(u, *args, **cmdoptions)
3370
3372
3371 try:
3373 try:
3372 if options['profile']:
3374 if options['profile']:
3373 import hotshot, hotshot.stats
3375 import hotshot, hotshot.stats
3374 prof = hotshot.Profile("hg.prof")
3376 prof = hotshot.Profile("hg.prof")
3375 try:
3377 try:
3376 try:
3378 try:
3377 return prof.runcall(d)
3379 return prof.runcall(d)
3378 except:
3380 except:
3379 try:
3381 try:
3380 u.warn(_('exception raised - generating '
3382 u.warn(_('exception raised - generating '
3381 'profile anyway\n'))
3383 'profile anyway\n'))
3382 except:
3384 except:
3383 pass
3385 pass
3384 raise
3386 raise
3385 finally:
3387 finally:
3386 prof.close()
3388 prof.close()
3387 stats = hotshot.stats.load("hg.prof")
3389 stats = hotshot.stats.load("hg.prof")
3388 stats.strip_dirs()
3390 stats.strip_dirs()
3389 stats.sort_stats('time', 'calls')
3391 stats.sort_stats('time', 'calls')
3390 stats.print_stats(40)
3392 stats.print_stats(40)
3391 else:
3393 else:
3392 return d()
3394 return d()
3393 finally:
3395 finally:
3394 u.flush()
3396 u.flush()
3395 except:
3397 except:
3396 # enter the debugger when we hit an exception
3398 # enter the debugger when we hit an exception
3397 if options['debugger']:
3399 if options['debugger']:
3398 pdb.post_mortem(sys.exc_info()[2])
3400 pdb.post_mortem(sys.exc_info()[2])
3399 u.print_exc()
3401 u.print_exc()
3400 raise
3402 raise
3401 except ParseError, inst:
3403 except ParseError, inst:
3402 if inst.args[0]:
3404 if inst.args[0]:
3403 u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
3405 u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
3404 help_(u, inst.args[0])
3406 help_(u, inst.args[0])
3405 else:
3407 else:
3406 u.warn(_("hg: %s\n") % inst.args[1])
3408 u.warn(_("hg: %s\n") % inst.args[1])
3407 help_(u, 'shortlist')
3409 help_(u, 'shortlist')
3408 except AmbiguousCommand, inst:
3410 except AmbiguousCommand, inst:
3409 u.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
3411 u.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
3410 (inst.args[0], " ".join(inst.args[1])))
3412 (inst.args[0], " ".join(inst.args[1])))
3411 except UnknownCommand, inst:
3413 except UnknownCommand, inst:
3412 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
3414 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
3413 help_(u, 'shortlist')
3415 help_(u, 'shortlist')
3414 except hg.RepoError, inst:
3416 except hg.RepoError, inst:
3415 u.warn(_("abort: %s!\n") % inst)
3417 u.warn(_("abort: %s!\n") % inst)
3416 except lock.LockHeld, inst:
3418 except lock.LockHeld, inst:
3417 if inst.errno == errno.ETIMEDOUT:
3419 if inst.errno == errno.ETIMEDOUT:
3418 reason = _('timed out waiting for lock held by %s') % inst.locker
3420 reason = _('timed out waiting for lock held by %s') % inst.locker
3419 else:
3421 else:
3420 reason = _('lock held by %s') % inst.locker
3422 reason = _('lock held by %s') % inst.locker
3421 u.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
3423 u.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
3422 except lock.LockUnavailable, inst:
3424 except lock.LockUnavailable, inst:
3423 u.warn(_("abort: could not lock %s: %s\n") %
3425 u.warn(_("abort: could not lock %s: %s\n") %
3424 (inst.desc or inst.filename, inst.strerror))
3426 (inst.desc or inst.filename, inst.strerror))
3425 except revlog.RevlogError, inst:
3427 except revlog.RevlogError, inst:
3426 u.warn(_("abort: "), inst, "!\n")
3428 u.warn(_("abort: "), inst, "!\n")
3427 except util.SignalInterrupt:
3429 except util.SignalInterrupt:
3428 u.warn(_("killed!\n"))
3430 u.warn(_("killed!\n"))
3429 except KeyboardInterrupt:
3431 except KeyboardInterrupt:
3430 try:
3432 try:
3431 u.warn(_("interrupted!\n"))
3433 u.warn(_("interrupted!\n"))
3432 except IOError, inst:
3434 except IOError, inst:
3433 if inst.errno == errno.EPIPE:
3435 if inst.errno == errno.EPIPE:
3434 if u.debugflag:
3436 if u.debugflag:
3435 u.warn(_("\nbroken pipe\n"))
3437 u.warn(_("\nbroken pipe\n"))
3436 else:
3438 else:
3437 raise
3439 raise
3438 except IOError, inst:
3440 except IOError, inst:
3439 if hasattr(inst, "code"):
3441 if hasattr(inst, "code"):
3440 u.warn(_("abort: %s\n") % inst)
3442 u.warn(_("abort: %s\n") % inst)
3441 elif hasattr(inst, "reason"):
3443 elif hasattr(inst, "reason"):
3442 u.warn(_("abort: error: %s\n") % inst.reason[1])
3444 u.warn(_("abort: error: %s\n") % inst.reason[1])
3443 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
3445 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
3444 if u.debugflag:
3446 if u.debugflag:
3445 u.warn(_("broken pipe\n"))
3447 u.warn(_("broken pipe\n"))
3446 elif getattr(inst, "strerror", None):
3448 elif getattr(inst, "strerror", None):
3447 if getattr(inst, "filename", None):
3449 if getattr(inst, "filename", None):
3448 u.warn(_("abort: %s - %s\n") % (inst.strerror, inst.filename))
3450 u.warn(_("abort: %s - %s\n") % (inst.strerror, inst.filename))
3449 else:
3451 else:
3450 u.warn(_("abort: %s\n") % inst.strerror)
3452 u.warn(_("abort: %s\n") % inst.strerror)
3451 else:
3453 else:
3452 raise
3454 raise
3453 except OSError, inst:
3455 except OSError, inst:
3454 if hasattr(inst, "filename"):
3456 if hasattr(inst, "filename"):
3455 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
3457 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
3456 else:
3458 else:
3457 u.warn(_("abort: %s\n") % inst.strerror)
3459 u.warn(_("abort: %s\n") % inst.strerror)
3458 except util.Abort, inst:
3460 except util.Abort, inst:
3459 u.warn(_('abort: '), inst.args[0] % inst.args[1:], '\n')
3461 u.warn(_('abort: '), inst.args[0] % inst.args[1:], '\n')
3460 except TypeError, inst:
3462 except TypeError, inst:
3461 # was this an argument error?
3463 # was this an argument error?
3462 tb = traceback.extract_tb(sys.exc_info()[2])
3464 tb = traceback.extract_tb(sys.exc_info()[2])
3463 if len(tb) > 2: # no
3465 if len(tb) > 2: # no
3464 raise
3466 raise
3465 u.debug(inst, "\n")
3467 u.debug(inst, "\n")
3466 u.warn(_("%s: invalid arguments\n") % cmd)
3468 u.warn(_("%s: invalid arguments\n") % cmd)
3467 help_(u, cmd)
3469 help_(u, cmd)
3468 except SystemExit, inst:
3470 except SystemExit, inst:
3469 # Commands shouldn't sys.exit directly, but give a return code.
3471 # Commands shouldn't sys.exit directly, but give a return code.
3470 # Just in case catch this and and pass exit code to caller.
3472 # Just in case catch this and and pass exit code to caller.
3471 return inst.code
3473 return inst.code
3472 except:
3474 except:
3473 u.warn(_("** unknown exception encountered, details follow\n"))
3475 u.warn(_("** unknown exception encountered, details follow\n"))
3474 u.warn(_("** report bug details to mercurial@selenic.com\n"))
3476 u.warn(_("** report bug details to mercurial@selenic.com\n"))
3475 u.warn(_("** Mercurial Distributed SCM (version %s)\n")
3477 u.warn(_("** Mercurial Distributed SCM (version %s)\n")
3476 % version.get_version())
3478 % version.get_version())
3477 raise
3479 raise
3478
3480
3479 return -1
3481 return -1
This diff has been collapsed as it changes many lines, (981 lines changed) Show them Hide them
@@ -1,988 +1,11 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
10 import mimetypes
11 from mercurial.demandload import demandload
9 from mercurial.demandload import demandload
12 demandload(globals(), "time re socket zlib errno ConfigParser tempfile")
10 demandload(globals(), "mercurial.hgweb.hgweb_mod:hgweb")
13 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater")
11 demandload(globals(), "mercurial.hgweb.hgwebdir_mod:hgwebdir")
14 demandload(globals(), "mercurial.hgweb.request:hgrequest")
15 demandload(globals(), "mercurial.hgweb.server:create_server")
16 from mercurial.node import *
17 from mercurial.i18n import gettext as _
18
19 def up(p):
20 if p[0] != "/":
21 p = "/" + p
22 if p[-1] == "/":
23 p = p[:-1]
24 up = os.path.dirname(p)
25 if up == "/":
26 return "/"
27 return up + "/"
28
29 def get_mtime(repo_path):
30 hg_path = os.path.join(repo_path, ".hg")
31 cl_path = os.path.join(hg_path, "00changelog.i")
32 if os.path.exists(os.path.join(cl_path)):
33 return os.stat(cl_path).st_mtime
34 else:
35 return os.stat(hg_path).st_mtime
36
37 def staticfile(directory, fname):
38 """return a file inside directory with guessed content-type header
39
40 fname always uses '/' as directory separator and isn't allowed to
41 contain unusual path components.
42 Content-type is guessed using the mimetypes module.
43 Return an empty string if fname is illegal or file not found.
44
45 """
46 parts = fname.split('/')
47 path = directory
48 for part in parts:
49 if (part in ('', os.curdir, os.pardir) or
50 os.sep in part or os.altsep is not None and os.altsep in part):
51 return ""
52 path = os.path.join(path, part)
53 try:
54 os.stat(path)
55 ct = mimetypes.guess_type(path)[0] or "text/plain"
56 return "Content-type: %s\n\n%s" % (ct, file(path).read())
57 except (TypeError, OSError):
58 # illegal fname or unreadable file
59 return ""
60
61 class hgweb(object):
62 def __init__(self, repo, name=None):
63 if type(repo) == type(""):
64 self.repo = hg.repository(ui.ui(), repo)
65 else:
66 self.repo = repo
67
68 self.mtime = -1
69 self.reponame = name
70 self.archives = 'zip', 'gz', 'bz2'
71
72 def refresh(self):
73 mtime = get_mtime(self.repo.root)
74 if mtime != self.mtime:
75 self.mtime = mtime
76 self.repo = hg.repository(self.repo.ui, self.repo.root)
77 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
78 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
79 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
80
81 def archivelist(self, nodeid):
82 for i in self.archives:
83 if self.repo.ui.configbool("web", "allow" + i, False):
84 yield {"type" : i, "node" : nodeid, "url": ""}
85
86 def listfiles(self, files, mf):
87 for f in files[:self.maxfiles]:
88 yield self.t("filenodelink", node=hex(mf[f]), file=f)
89 if len(files) > self.maxfiles:
90 yield self.t("fileellipses")
91
92 def listfilediffs(self, files, changeset):
93 for f in files[:self.maxfiles]:
94 yield self.t("filedifflink", node=hex(changeset), file=f)
95 if len(files) > self.maxfiles:
96 yield self.t("fileellipses")
97
98 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
99 if not rev:
100 rev = lambda x: ""
101 siblings = [s for s in siblings if s != nullid]
102 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
103 return
104 for s in siblings:
105 yield dict(node=hex(s), rev=rev(s), **args)
106
107 def renamelink(self, fl, node):
108 r = fl.renamed(node)
109 if r:
110 return [dict(file=r[0], node=hex(r[1]))]
111 return []
112
113 def showtag(self, t1, node=nullid, **args):
114 for t in self.repo.nodetags(node):
115 yield self.t(t1, tag=t, **args)
116
117 def diff(self, node1, node2, files):
118 def filterfiles(filters, files):
119 l = [x for x in files if x in filters]
120
121 for t in filters:
122 if t and t[-1] != os.sep:
123 t += os.sep
124 l += [x for x in files if x.startswith(t)]
125 return l
126
127 parity = [0]
128 def diffblock(diff, f, fn):
129 yield self.t("diffblock",
130 lines=prettyprintlines(diff),
131 parity=parity[0],
132 file=f,
133 filenode=hex(fn or nullid))
134 parity[0] = 1 - parity[0]
135
136 def prettyprintlines(diff):
137 for l in diff.splitlines(1):
138 if l.startswith('+'):
139 yield self.t("difflineplus", line=l)
140 elif l.startswith('-'):
141 yield self.t("difflineminus", line=l)
142 elif l.startswith('@'):
143 yield self.t("difflineat", line=l)
144 else:
145 yield self.t("diffline", line=l)
146
147 r = self.repo
148 cl = r.changelog
149 mf = r.manifest
150 change1 = cl.read(node1)
151 change2 = cl.read(node2)
152 mmap1 = mf.read(change1[0])
153 mmap2 = mf.read(change2[0])
154 date1 = util.datestr(change1[2])
155 date2 = util.datestr(change2[2])
156
157 modified, added, removed, deleted, unknown = r.changes(node1, node2)
158 if files:
159 modified, added, removed = map(lambda x: filterfiles(files, x),
160 (modified, added, removed))
161
162 diffopts = self.repo.ui.diffopts()
163 showfunc = diffopts['showfunc']
164 ignorews = diffopts['ignorews']
165 for f in modified:
166 to = r.file(f).read(mmap1[f])
167 tn = r.file(f).read(mmap2[f])
168 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
169 showfunc=showfunc, ignorews=ignorews), f, tn)
170 for f in added:
171 to = None
172 tn = r.file(f).read(mmap2[f])
173 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
174 showfunc=showfunc, ignorews=ignorews), f, tn)
175 for f in removed:
176 to = r.file(f).read(mmap1[f])
177 tn = None
178 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
179 showfunc=showfunc, ignorews=ignorews), f, tn)
180
181 def changelog(self, pos):
182 def changenav(**map):
183 def seq(factor, maxchanges=None):
184 if maxchanges:
185 yield maxchanges
186 if maxchanges >= 20 and maxchanges <= 40:
187 yield 50
188 else:
189 yield 1 * factor
190 yield 3 * factor
191 for f in seq(factor * 10):
192 yield f
193
194 l = []
195 last = 0
196 for f in seq(1, self.maxchanges):
197 if f < self.maxchanges or f <= last:
198 continue
199 if f > count:
200 break
201 last = f
202 r = "%d" % f
203 if pos + f < count:
204 l.append(("+" + r, pos + f))
205 if pos - f >= 0:
206 l.insert(0, ("-" + r, pos - f))
207
208 yield {"rev": 0, "label": "(0)"}
209
210 for label, rev in l:
211 yield {"label": label, "rev": rev}
212
213 yield {"label": "tip", "rev": "tip"}
214
215 def changelist(**map):
216 parity = (start - end) & 1
217 cl = self.repo.changelog
218 l = [] # build a list in forward order for efficiency
219 for i in range(start, end):
220 n = cl.node(i)
221 changes = cl.read(n)
222 hn = hex(n)
223
224 l.insert(0, {"parity": parity,
225 "author": changes[1],
226 "parent": self.siblings(cl.parents(n), cl.rev,
227 cl.rev(n) - 1),
228 "child": self.siblings(cl.children(n), cl.rev,
229 cl.rev(n) + 1),
230 "changelogtag": self.showtag("changelogtag",n),
231 "manifest": hex(changes[0]),
232 "desc": changes[4],
233 "date": changes[2],
234 "files": self.listfilediffs(changes[3], n),
235 "rev": i,
236 "node": hn})
237 parity = 1 - parity
238
239 for e in l:
240 yield e
241
242 cl = self.repo.changelog
243 mf = cl.read(cl.tip())[0]
244 count = cl.count()
245 start = max(0, pos - self.maxchanges + 1)
246 end = min(count, start + self.maxchanges)
247 pos = end - 1
248
249 yield self.t('changelog',
250 changenav=changenav,
251 manifest=hex(mf),
252 rev=pos, changesets=count, entries=changelist,
253 archives=self.archivelist("tip"))
254
255 def search(self, query):
256
257 def changelist(**map):
258 cl = self.repo.changelog
259 count = 0
260 qw = query.lower().split()
261
262 def revgen():
263 for i in range(cl.count() - 1, 0, -100):
264 l = []
265 for j in range(max(0, i - 100), i):
266 n = cl.node(j)
267 changes = cl.read(n)
268 l.append((n, j, changes))
269 l.reverse()
270 for e in l:
271 yield e
272
273 for n, i, changes in revgen():
274 miss = 0
275 for q in qw:
276 if not (q in changes[1].lower() or
277 q in changes[4].lower() or
278 q in " ".join(changes[3][:20]).lower()):
279 miss = 1
280 break
281 if miss:
282 continue
283
284 count += 1
285 hn = hex(n)
286
287 yield self.t('searchentry',
288 parity=count & 1,
289 author=changes[1],
290 parent=self.siblings(cl.parents(n), cl.rev),
291 child=self.siblings(cl.children(n), cl.rev),
292 changelogtag=self.showtag("changelogtag",n),
293 manifest=hex(changes[0]),
294 desc=changes[4],
295 date=changes[2],
296 files=self.listfilediffs(changes[3], n),
297 rev=i,
298 node=hn)
299
300 if count >= self.maxchanges:
301 break
302
303 cl = self.repo.changelog
304 mf = cl.read(cl.tip())[0]
305
306 yield self.t('search',
307 query=query,
308 manifest=hex(mf),
309 entries=changelist)
310
311 def changeset(self, nodeid):
312 cl = self.repo.changelog
313 n = self.repo.lookup(nodeid)
314 nodeid = hex(n)
315 changes = cl.read(n)
316 p1 = cl.parents(n)[0]
317
318 files = []
319 mf = self.repo.manifest.read(changes[0])
320 for f in changes[3]:
321 files.append(self.t("filenodelink",
322 filenode=hex(mf.get(f, nullid)), file=f))
323
324 def diff(**map):
325 yield self.diff(p1, n, None)
326
327 yield self.t('changeset',
328 diff=diff,
329 rev=cl.rev(n),
330 node=nodeid,
331 parent=self.siblings(cl.parents(n), cl.rev),
332 child=self.siblings(cl.children(n), cl.rev),
333 changesettag=self.showtag("changesettag",n),
334 manifest=hex(changes[0]),
335 author=changes[1],
336 desc=changes[4],
337 date=changes[2],
338 files=files,
339 archives=self.archivelist(nodeid))
340
341 def filelog(self, f, filenode):
342 cl = self.repo.changelog
343 fl = self.repo.file(f)
344 filenode = hex(fl.lookup(filenode))
345 count = fl.count()
346
347 def entries(**map):
348 l = []
349 parity = (count - 1) & 1
350
351 for i in range(count):
352 n = fl.node(i)
353 lr = fl.linkrev(n)
354 cn = cl.node(lr)
355 cs = cl.read(cl.node(lr))
356
357 l.insert(0, {"parity": parity,
358 "filenode": hex(n),
359 "filerev": i,
360 "file": f,
361 "node": hex(cn),
362 "author": cs[1],
363 "date": cs[2],
364 "rename": self.renamelink(fl, n),
365 "parent": self.siblings(fl.parents(n),
366 fl.rev, file=f),
367 "child": self.siblings(fl.children(n),
368 fl.rev, file=f),
369 "desc": cs[4]})
370 parity = 1 - parity
371
372 for e in l:
373 yield e
374
375 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
376
377 def filerevision(self, f, node):
378 fl = self.repo.file(f)
379 n = fl.lookup(node)
380 node = hex(n)
381 text = fl.read(n)
382 changerev = fl.linkrev(n)
383 cl = self.repo.changelog
384 cn = cl.node(changerev)
385 cs = cl.read(cn)
386 mfn = cs[0]
387
388 mt = mimetypes.guess_type(f)[0]
389 rawtext = text
390 if util.binary(text):
391 mt = mt or 'application/octet-stream'
392 text = "(binary:%s)" % mt
393 mt = mt or 'text/plain'
394
395 def lines():
396 for l, t in enumerate(text.splitlines(1)):
397 yield {"line": t,
398 "linenumber": "% 6d" % (l + 1),
399 "parity": l & 1}
400
401 yield self.t("filerevision",
402 file=f,
403 filenode=node,
404 path=up(f),
405 text=lines(),
406 raw=rawtext,
407 mimetype=mt,
408 rev=changerev,
409 node=hex(cn),
410 manifest=hex(mfn),
411 author=cs[1],
412 date=cs[2],
413 parent=self.siblings(fl.parents(n), fl.rev, file=f),
414 child=self.siblings(fl.children(n), fl.rev, file=f),
415 rename=self.renamelink(fl, n),
416 permissions=self.repo.manifest.readflags(mfn)[f])
417
418 def fileannotate(self, f, node):
419 bcache = {}
420 ncache = {}
421 fl = self.repo.file(f)
422 n = fl.lookup(node)
423 node = hex(n)
424 changerev = fl.linkrev(n)
425
426 cl = self.repo.changelog
427 cn = cl.node(changerev)
428 cs = cl.read(cn)
429 mfn = cs[0]
430
431 def annotate(**map):
432 parity = 1
433 last = None
434 for r, l in fl.annotate(n):
435 try:
436 cnode = ncache[r]
437 except KeyError:
438 cnode = ncache[r] = self.repo.changelog.node(r)
439
440 try:
441 name = bcache[r]
442 except KeyError:
443 cl = self.repo.changelog.read(cnode)
444 bcache[r] = name = self.repo.ui.shortuser(cl[1])
445
446 if last != cnode:
447 parity = 1 - parity
448 last = cnode
449
450 yield {"parity": parity,
451 "node": hex(cnode),
452 "rev": r,
453 "author": name,
454 "file": f,
455 "line": l}
456
457 yield self.t("fileannotate",
458 file=f,
459 filenode=node,
460 annotate=annotate,
461 path=up(f),
462 rev=changerev,
463 node=hex(cn),
464 manifest=hex(mfn),
465 author=cs[1],
466 date=cs[2],
467 rename=self.renamelink(fl, n),
468 parent=self.siblings(fl.parents(n), fl.rev, file=f),
469 child=self.siblings(fl.children(n), fl.rev, file=f),
470 permissions=self.repo.manifest.readflags(mfn)[f])
471
472 def manifest(self, mnode, path):
473 man = self.repo.manifest
474 mn = man.lookup(mnode)
475 mnode = hex(mn)
476 mf = man.read(mn)
477 rev = man.rev(mn)
478 changerev = man.linkrev(mn)
479 node = self.repo.changelog.node(changerev)
480 mff = man.readflags(mn)
481
482 files = {}
483
484 p = path[1:]
485 if p and p[-1] != "/":
486 p += "/"
487 l = len(p)
488
489 for f,n in mf.items():
490 if f[:l] != p:
491 continue
492 remain = f[l:]
493 if "/" in remain:
494 short = remain[:remain.find("/") + 1] # bleah
495 files[short] = (f, None)
496 else:
497 short = os.path.basename(remain)
498 files[short] = (f, n)
499
500 def filelist(**map):
501 parity = 0
502 fl = files.keys()
503 fl.sort()
504 for f in fl:
505 full, fnode = files[f]
506 if not fnode:
507 continue
508
509 yield {"file": full,
510 "manifest": mnode,
511 "filenode": hex(fnode),
512 "parity": parity,
513 "basename": f,
514 "permissions": mff[full]}
515 parity = 1 - parity
516
517 def dirlist(**map):
518 parity = 0
519 fl = files.keys()
520 fl.sort()
521 for f in fl:
522 full, fnode = files[f]
523 if fnode:
524 continue
525
526 yield {"parity": parity,
527 "path": os.path.join(path, f),
528 "manifest": mnode,
529 "basename": f[:-1]}
530 parity = 1 - parity
531
532 yield self.t("manifest",
533 manifest=mnode,
534 rev=rev,
535 node=hex(node),
536 path=path,
537 up=up(path),
538 fentries=filelist,
539 dentries=dirlist,
540 archives=self.archivelist(hex(node)))
541
542 def tags(self):
543 cl = self.repo.changelog
544 mf = cl.read(cl.tip())[0]
545
546 i = self.repo.tagslist()
547 i.reverse()
548
549 def entries(notip=False, **map):
550 parity = 0
551 for k,n in i:
552 if notip and k == "tip": continue
553 yield {"parity": parity,
554 "tag": k,
555 "tagmanifest": hex(cl.read(n)[0]),
556 "date": cl.read(n)[2],
557 "node": hex(n)}
558 parity = 1 - parity
559
560 yield self.t("tags",
561 manifest=hex(mf),
562 entries=lambda **x: entries(False, **x),
563 entriesnotip=lambda **x: entries(True, **x))
564
565 def summary(self):
566 cl = self.repo.changelog
567 mf = cl.read(cl.tip())[0]
568
569 i = self.repo.tagslist()
570 i.reverse()
571
572 def tagentries(**map):
573 parity = 0
574 count = 0
575 for k,n in i:
576 if k == "tip": # skip tip
577 continue;
578
579 count += 1
580 if count > 10: # limit to 10 tags
581 break;
582
583 c = cl.read(n)
584 m = c[0]
585 t = c[2]
586
587 yield self.t("tagentry",
588 parity = parity,
589 tag = k,
590 node = hex(n),
591 date = t,
592 tagmanifest = hex(m))
593 parity = 1 - parity
594
595 def changelist(**map):
596 parity = 0
597 cl = self.repo.changelog
598 l = [] # build a list in forward order for efficiency
599 for i in range(start, end):
600 n = cl.node(i)
601 changes = cl.read(n)
602 hn = hex(n)
603 t = changes[2]
604
605 l.insert(0, self.t(
606 'shortlogentry',
607 parity = parity,
608 author = changes[1],
609 manifest = hex(changes[0]),
610 desc = changes[4],
611 date = t,
612 rev = i,
613 node = hn))
614 parity = 1 - parity
615
616 yield l
617
618 cl = self.repo.changelog
619 mf = cl.read(cl.tip())[0]
620 count = cl.count()
621 start = max(0, count - self.maxchanges)
622 end = min(count, start + self.maxchanges)
623 pos = end - 1
624
625 yield self.t("summary",
626 desc = self.repo.ui.config("web", "description", "unknown"),
627 owner = (self.repo.ui.config("ui", "username") or # preferred
628 self.repo.ui.config("web", "contact") or # deprecated
629 self.repo.ui.config("web", "author", "unknown")), # also
630 lastchange = (0, 0), # FIXME
631 manifest = hex(mf),
632 tags = tagentries,
633 shortlog = changelist)
634
635 def filediff(self, file, changeset):
636 cl = self.repo.changelog
637 n = self.repo.lookup(changeset)
638 changeset = hex(n)
639 p1 = cl.parents(n)[0]
640 cs = cl.read(n)
641 mf = self.repo.manifest.read(cs[0])
642
643 def diff(**map):
644 yield self.diff(p1, n, [file])
645
646 yield self.t("filediff",
647 file=file,
648 filenode=hex(mf.get(file, nullid)),
649 node=changeset,
650 rev=self.repo.changelog.rev(n),
651 parent=self.siblings(cl.parents(n), cl.rev),
652 child=self.siblings(cl.children(n), cl.rev),
653 diff=diff)
654
655 archive_specs = {
656 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', 'x-bzip2'),
657 'gz': ('application/x-tar', 'tgz', '.tar.gz', 'x-gzip'),
658 'zip': ('application/zip', 'zip', '.zip', None),
659 }
660
661 def archive(self, req, cnode, type):
662 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
663 name = "%s-%s" % (reponame, short(cnode))
664 mimetype, artype, extension, encoding = self.archive_specs[type]
665 headers = [('Content-type', mimetype),
666 ('Content-disposition', 'attachment; filename=%s%s' %
667 (name, extension))]
668 if encoding:
669 headers.append(('Content-encoding', encoding))
670 req.header(headers)
671 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
672
673 # add tags to things
674 # tags -> list of changesets corresponding to tags
675 # find tag, changeset, file
676
677 def run(self, req=hgrequest()):
678 def clean(path):
679 p = util.normpath(path)
680 if p[:2] == "..":
681 raise "suspicious path"
682 return p
683
684 def header(**map):
685 yield self.t("header", **map)
686
687 def footer(**map):
688 yield self.t("footer",
689 motd=self.repo.ui.config("web", "motd", ""),
690 **map)
691
692 def expand_form(form):
693 shortcuts = {
694 'cl': [('cmd', ['changelog']), ('rev', None)],
695 'cs': [('cmd', ['changeset']), ('node', None)],
696 'f': [('cmd', ['file']), ('filenode', None)],
697 'fl': [('cmd', ['filelog']), ('filenode', None)],
698 'fd': [('cmd', ['filediff']), ('node', None)],
699 'fa': [('cmd', ['annotate']), ('filenode', None)],
700 'mf': [('cmd', ['manifest']), ('manifest', None)],
701 'ca': [('cmd', ['archive']), ('node', None)],
702 'tags': [('cmd', ['tags'])],
703 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
704 'static': [('cmd', ['static']), ('file', None)]
705 }
706
707 for k in shortcuts.iterkeys():
708 if form.has_key(k):
709 for name, value in shortcuts[k]:
710 if value is None:
711 value = form[k]
712 form[name] = value
713 del form[k]
714
715 self.refresh()
716
717 expand_form(req.form)
718
719 t = self.repo.ui.config("web", "templates", templater.templatepath())
720 static = self.repo.ui.config("web", "static", os.path.join(t,"static"))
721 m = os.path.join(t, "map")
722 style = self.repo.ui.config("web", "style", "")
723 if req.form.has_key('style'):
724 style = req.form['style'][0]
725 if style:
726 b = os.path.basename("map-" + style)
727 p = os.path.join(t, b)
728 if os.path.isfile(p):
729 m = p
730
731 port = req.env["SERVER_PORT"]
732 port = port != "80" and (":" + port) or ""
733 uri = req.env["REQUEST_URI"]
734 if "?" in uri:
735 uri = uri.split("?")[0]
736 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
737 if not self.reponame:
738 self.reponame = (self.repo.ui.config("web", "name")
739 or uri.strip('/') or self.repo.root)
740
741 self.t = templater.templater(m, templater.common_filters,
742 defaults={"url": url,
743 "repo": self.reponame,
744 "header": header,
745 "footer": footer,
746 })
747
748 if not req.form.has_key('cmd'):
749 req.form['cmd'] = [self.t.cache['default'],]
750
751 cmd = req.form['cmd'][0]
752 if cmd == 'changelog':
753 hi = self.repo.changelog.count() - 1
754 if req.form.has_key('rev'):
755 hi = req.form['rev'][0]
756 try:
757 hi = self.repo.changelog.rev(self.repo.lookup(hi))
758 except hg.RepoError:
759 req.write(self.search(hi)) # XXX redirect to 404 page?
760 return
761
762 req.write(self.changelog(hi))
763
764 elif cmd == 'changeset':
765 req.write(self.changeset(req.form['node'][0]))
766
767 elif cmd == 'manifest':
768 req.write(self.manifest(req.form['manifest'][0],
769 clean(req.form['path'][0])))
770
771 elif cmd == 'tags':
772 req.write(self.tags())
773
774 elif cmd == 'summary':
775 req.write(self.summary())
776
777 elif cmd == 'filediff':
778 req.write(self.filediff(clean(req.form['file'][0]),
779 req.form['node'][0]))
780
781 elif cmd == 'file':
782 req.write(self.filerevision(clean(req.form['file'][0]),
783 req.form['filenode'][0]))
784
785 elif cmd == 'annotate':
786 req.write(self.fileannotate(clean(req.form['file'][0]),
787 req.form['filenode'][0]))
788
789 elif cmd == 'filelog':
790 req.write(self.filelog(clean(req.form['file'][0]),
791 req.form['filenode'][0]))
792
793 elif cmd == 'heads':
794 req.httphdr("application/mercurial-0.1")
795 h = self.repo.heads()
796 req.write(" ".join(map(hex, h)) + "\n")
797
798 elif cmd == 'branches':
799 req.httphdr("application/mercurial-0.1")
800 nodes = []
801 if req.form.has_key('nodes'):
802 nodes = map(bin, req.form['nodes'][0].split(" "))
803 for b in self.repo.branches(nodes):
804 req.write(" ".join(map(hex, b)) + "\n")
805
806 elif cmd == 'between':
807 req.httphdr("application/mercurial-0.1")
808 nodes = []
809 if req.form.has_key('pairs'):
810 pairs = [map(bin, p.split("-"))
811 for p in req.form['pairs'][0].split(" ")]
812 for b in self.repo.between(pairs):
813 req.write(" ".join(map(hex, b)) + "\n")
814
815 elif cmd == 'changegroup':
816 req.httphdr("application/mercurial-0.1")
817 nodes = []
818 if not self.allowpull:
819 return
820
821 if req.form.has_key('roots'):
822 nodes = map(bin, req.form['roots'][0].split(" "))
823
824 z = zlib.compressobj()
825 f = self.repo.changegroup(nodes, 'serve')
826 while 1:
827 chunk = f.read(4096)
828 if not chunk:
829 break
830 req.write(z.compress(chunk))
831
832 req.write(z.flush())
833
834 elif cmd == 'archive':
835 changeset = self.repo.lookup(req.form['node'][0])
836 type = req.form['type'][0]
837 if (type in self.archives and
838 self.repo.ui.configbool("web", "allow" + type, False)):
839 self.archive(req, changeset, type)
840 return
841
842 req.write(self.t("error"))
843
844 elif cmd == 'static':
845 fname = req.form['file'][0]
846 req.write(staticfile(static, fname)
847 or self.t("error", error="%r not found" % fname))
848
849 else:
850 req.write(self.t("error"))
851
852 # This is a stopgap
853 class hgwebdir(object):
854 def __init__(self, config):
855 def cleannames(items):
856 return [(name.strip(os.sep), path) for name, path in items]
857
858 self.motd = ""
859 self.repos_sorted = ('name', False)
860 if isinstance(config, (list, tuple)):
861 self.repos = cleannames(config)
862 self.repos_sorted = ('', False)
863 elif isinstance(config, dict):
864 self.repos = cleannames(config.items())
865 self.repos.sort()
866 else:
867 cp = ConfigParser.SafeConfigParser()
868 cp.read(config)
869 self.repos = []
870 if cp.has_section('web') and cp.has_option('web', 'motd'):
871 self.motd = cp.get('web', 'motd')
872 if cp.has_section('paths'):
873 self.repos.extend(cleannames(cp.items('paths')))
874 if cp.has_section('collections'):
875 for prefix, root in cp.items('collections'):
876 for path in util.walkrepos(root):
877 repo = os.path.normpath(path)
878 name = repo
879 if name.startswith(prefix):
880 name = name[len(prefix):]
881 self.repos.append((name.lstrip(os.sep), repo))
882 self.repos.sort()
883
884 def run(self, req=hgrequest()):
885 def header(**map):
886 yield tmpl("header", **map)
887
888 def footer(**map):
889 yield tmpl("footer", motd=self.motd, **map)
890
891 m = os.path.join(templater.templatepath(), "map")
892 tmpl = templater.templater(m, templater.common_filters,
893 defaults={"header": header,
894 "footer": footer})
895
896 def archivelist(ui, nodeid, url):
897 for i in ['zip', 'gz', 'bz2']:
898 if ui.configbool("web", "allow" + i, False):
899 yield {"type" : i, "node": nodeid, "url": url}
900
901 def entries(sortcolumn="", descending=False, **map):
902 rows = []
903 parity = 0
904 for name, path in self.repos:
905 u = ui.ui()
906 try:
907 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
908 except IOError:
909 pass
910 get = u.config
911
912 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
913 .replace("//", "/"))
914
915 # update time with local timezone
916 try:
917 d = (get_mtime(path), util.makedate()[1])
918 except OSError:
919 continue
920
921 contact = (get("ui", "username") or # preferred
922 get("web", "contact") or # deprecated
923 get("web", "author", "")) # also
924 description = get("web", "description", "")
925 name = get("web", "name", name)
926 row = dict(contact=contact or "unknown",
927 contact_sort=contact.upper() or "unknown",
928 name=name,
929 name_sort=name,
930 url=url,
931 description=description or "unknown",
932 description_sort=description.upper() or "unknown",
933 lastchange=d,
934 lastchange_sort=d[1]-d[0],
935 archives=archivelist(u, "tip", url))
936 if (not sortcolumn
937 or (sortcolumn, descending) == self.repos_sorted):
938 # fast path for unsorted output
939 row['parity'] = parity
940 parity = 1 - parity
941 yield row
942 else:
943 rows.append((row["%s_sort" % sortcolumn], row))
944 if rows:
945 rows.sort()
946 if descending:
947 rows.reverse()
948 for key, row in rows:
949 row['parity'] = parity
950 parity = 1 - parity
951 yield row
952
953 virtual = req.env.get("PATH_INFO", "").strip('/')
954 if virtual:
955 real = dict(self.repos).get(virtual)
956 if real:
957 try:
958 hgweb(real).run(req)
959 except IOError, inst:
960 req.write(tmpl("error", error=inst.strerror))
961 except hg.RepoError, inst:
962 req.write(tmpl("error", error=str(inst)))
963 else:
964 req.write(tmpl("notfound", repo=virtual))
965 else:
966 if req.form.has_key('static'):
967 static = os.path.join(templater.templatepath(), "static")
968 fname = req.form['static'][0]
969 req.write(staticfile(static, fname)
970 or tmpl("error", error="%r not found" % fname))
971 else:
972 sortable = ["name", "description", "contact", "lastchange"]
973 sortcolumn, descending = self.repos_sorted
974 if req.form.has_key('sort'):
975 sortcolumn = req.form['sort'][0]
976 descending = sortcolumn.startswith('-')
977 if descending:
978 sortcolumn = sortcolumn[1:]
979 if sortcolumn not in sortable:
980 sortcolumn = ""
981
982 sort = [("sort_%s" % column,
983 "%s%s" % ((not descending and column == sortcolumn)
984 and "-" or "", column))
985 for column in sortable]
986 req.write(tmpl("index", entries=entries,
987 sortcolumn=sortcolumn, descending=descending,
988 **dict(sort)))
This diff has been collapsed as it changes many lines, (950 lines changed) Show them Hide them
@@ -1,988 +1,42 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, mimetypes
10 import mimetypes
10 import os.path
11 from mercurial.demandload import demandload
12 demandload(globals(), "time re socket zlib errno ConfigParser tempfile")
13 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater")
14 demandload(globals(), "mercurial.hgweb.request:hgrequest")
15 demandload(globals(), "mercurial.hgweb.server:create_server")
16 from mercurial.node import *
17 from mercurial.i18n import gettext as _
18
19 def up(p):
20 if p[0] != "/":
21 p = "/" + p
22 if p[-1] == "/":
23 p = p[:-1]
24 up = os.path.dirname(p)
25 if up == "/":
26 return "/"
27 return up + "/"
28
11
29 def get_mtime(repo_path):
12 def get_mtime(repo_path):
30 hg_path = os.path.join(repo_path, ".hg")
13 hg_path = os.path.join(repo_path, ".hg")
31 cl_path = os.path.join(hg_path, "00changelog.i")
14 cl_path = os.path.join(hg_path, "00changelog.i")
32 if os.path.exists(os.path.join(cl_path)):
15 if os.path.exists(os.path.join(cl_path)):
33 return os.stat(cl_path).st_mtime
16 return os.stat(cl_path).st_mtime
34 else:
17 else:
35 return os.stat(hg_path).st_mtime
18 return os.stat(hg_path).st_mtime
36
19
37 def staticfile(directory, fname):
20 def staticfile(directory, fname):
38 """return a file inside directory with guessed content-type header
21 """return a file inside directory with guessed content-type header
39
22
40 fname always uses '/' as directory separator and isn't allowed to
23 fname always uses '/' as directory separator and isn't allowed to
41 contain unusual path components.
24 contain unusual path components.
42 Content-type is guessed using the mimetypes module.
25 Content-type is guessed using the mimetypes module.
43 Return an empty string if fname is illegal or file not found.
26 Return an empty string if fname is illegal or file not found.
44
27
45 """
28 """
46 parts = fname.split('/')
29 parts = fname.split('/')
47 path = directory
30 path = directory
48 for part in parts:
31 for part in parts:
49 if (part in ('', os.curdir, os.pardir) or
32 if (part in ('', os.curdir, os.pardir) or
50 os.sep in part or os.altsep is not None and os.altsep in part):
33 os.sep in part or os.altsep is not None and os.altsep in part):
51 return ""
34 return ""
52 path = os.path.join(path, part)
35 path = os.path.join(path, part)
53 try:
36 try:
54 os.stat(path)
37 os.stat(path)
55 ct = mimetypes.guess_type(path)[0] or "text/plain"
38 ct = mimetypes.guess_type(path)[0] or "text/plain"
56 return "Content-type: %s\n\n%s" % (ct, file(path).read())
39 return "Content-type: %s\n\n%s" % (ct, file(path).read())
57 except (TypeError, OSError):
40 except (TypeError, OSError):
58 # illegal fname or unreadable file
41 # illegal fname or unreadable file
59 return ""
42 return ""
60
61 class hgweb(object):
62 def __init__(self, repo, name=None):
63 if type(repo) == type(""):
64 self.repo = hg.repository(ui.ui(), repo)
65 else:
66 self.repo = repo
67
68 self.mtime = -1
69 self.reponame = name
70 self.archives = 'zip', 'gz', 'bz2'
71
72 def refresh(self):
73 mtime = get_mtime(self.repo.root)
74 if mtime != self.mtime:
75 self.mtime = mtime
76 self.repo = hg.repository(self.repo.ui, self.repo.root)
77 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
78 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
79 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
80
81 def archivelist(self, nodeid):
82 for i in self.archives:
83 if self.repo.ui.configbool("web", "allow" + i, False):
84 yield {"type" : i, "node" : nodeid, "url": ""}
85
86 def listfiles(self, files, mf):
87 for f in files[:self.maxfiles]:
88 yield self.t("filenodelink", node=hex(mf[f]), file=f)
89 if len(files) > self.maxfiles:
90 yield self.t("fileellipses")
91
92 def listfilediffs(self, files, changeset):
93 for f in files[:self.maxfiles]:
94 yield self.t("filedifflink", node=hex(changeset), file=f)
95 if len(files) > self.maxfiles:
96 yield self.t("fileellipses")
97
98 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
99 if not rev:
100 rev = lambda x: ""
101 siblings = [s for s in siblings if s != nullid]
102 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
103 return
104 for s in siblings:
105 yield dict(node=hex(s), rev=rev(s), **args)
106
107 def renamelink(self, fl, node):
108 r = fl.renamed(node)
109 if r:
110 return [dict(file=r[0], node=hex(r[1]))]
111 return []
112
113 def showtag(self, t1, node=nullid, **args):
114 for t in self.repo.nodetags(node):
115 yield self.t(t1, tag=t, **args)
116
117 def diff(self, node1, node2, files):
118 def filterfiles(filters, files):
119 l = [x for x in files if x in filters]
120
121 for t in filters:
122 if t and t[-1] != os.sep:
123 t += os.sep
124 l += [x for x in files if x.startswith(t)]
125 return l
126
127 parity = [0]
128 def diffblock(diff, f, fn):
129 yield self.t("diffblock",
130 lines=prettyprintlines(diff),
131 parity=parity[0],
132 file=f,
133 filenode=hex(fn or nullid))
134 parity[0] = 1 - parity[0]
135
136 def prettyprintlines(diff):
137 for l in diff.splitlines(1):
138 if l.startswith('+'):
139 yield self.t("difflineplus", line=l)
140 elif l.startswith('-'):
141 yield self.t("difflineminus", line=l)
142 elif l.startswith('@'):
143 yield self.t("difflineat", line=l)
144 else:
145 yield self.t("diffline", line=l)
146
147 r = self.repo
148 cl = r.changelog
149 mf = r.manifest
150 change1 = cl.read(node1)
151 change2 = cl.read(node2)
152 mmap1 = mf.read(change1[0])
153 mmap2 = mf.read(change2[0])
154 date1 = util.datestr(change1[2])
155 date2 = util.datestr(change2[2])
156
157 modified, added, removed, deleted, unknown = r.changes(node1, node2)
158 if files:
159 modified, added, removed = map(lambda x: filterfiles(files, x),
160 (modified, added, removed))
161
162 diffopts = self.repo.ui.diffopts()
163 showfunc = diffopts['showfunc']
164 ignorews = diffopts['ignorews']
165 for f in modified:
166 to = r.file(f).read(mmap1[f])
167 tn = r.file(f).read(mmap2[f])
168 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
169 showfunc=showfunc, ignorews=ignorews), f, tn)
170 for f in added:
171 to = None
172 tn = r.file(f).read(mmap2[f])
173 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
174 showfunc=showfunc, ignorews=ignorews), f, tn)
175 for f in removed:
176 to = r.file(f).read(mmap1[f])
177 tn = None
178 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
179 showfunc=showfunc, ignorews=ignorews), f, tn)
180
181 def changelog(self, pos):
182 def changenav(**map):
183 def seq(factor, maxchanges=None):
184 if maxchanges:
185 yield maxchanges
186 if maxchanges >= 20 and maxchanges <= 40:
187 yield 50
188 else:
189 yield 1 * factor
190 yield 3 * factor
191 for f in seq(factor * 10):
192 yield f
193
194 l = []
195 last = 0
196 for f in seq(1, self.maxchanges):
197 if f < self.maxchanges or f <= last:
198 continue
199 if f > count:
200 break
201 last = f
202 r = "%d" % f
203 if pos + f < count:
204 l.append(("+" + r, pos + f))
205 if pos - f >= 0:
206 l.insert(0, ("-" + r, pos - f))
207
208 yield {"rev": 0, "label": "(0)"}
209
210 for label, rev in l:
211 yield {"label": label, "rev": rev}
212
213 yield {"label": "tip", "rev": "tip"}
214
215 def changelist(**map):
216 parity = (start - end) & 1
217 cl = self.repo.changelog
218 l = [] # build a list in forward order for efficiency
219 for i in range(start, end):
220 n = cl.node(i)
221 changes = cl.read(n)
222 hn = hex(n)
223
224 l.insert(0, {"parity": parity,
225 "author": changes[1],
226 "parent": self.siblings(cl.parents(n), cl.rev,
227 cl.rev(n) - 1),
228 "child": self.siblings(cl.children(n), cl.rev,
229 cl.rev(n) + 1),
230 "changelogtag": self.showtag("changelogtag",n),
231 "manifest": hex(changes[0]),
232 "desc": changes[4],
233 "date": changes[2],
234 "files": self.listfilediffs(changes[3], n),
235 "rev": i,
236 "node": hn})
237 parity = 1 - parity
238
239 for e in l:
240 yield e
241
242 cl = self.repo.changelog
243 mf = cl.read(cl.tip())[0]
244 count = cl.count()
245 start = max(0, pos - self.maxchanges + 1)
246 end = min(count, start + self.maxchanges)
247 pos = end - 1
248
249 yield self.t('changelog',
250 changenav=changenav,
251 manifest=hex(mf),
252 rev=pos, changesets=count, entries=changelist,
253 archives=self.archivelist("tip"))
254
255 def search(self, query):
256
257 def changelist(**map):
258 cl = self.repo.changelog
259 count = 0
260 qw = query.lower().split()
261
262 def revgen():
263 for i in range(cl.count() - 1, 0, -100):
264 l = []
265 for j in range(max(0, i - 100), i):
266 n = cl.node(j)
267 changes = cl.read(n)
268 l.append((n, j, changes))
269 l.reverse()
270 for e in l:
271 yield e
272
273 for n, i, changes in revgen():
274 miss = 0
275 for q in qw:
276 if not (q in changes[1].lower() or
277 q in changes[4].lower() or
278 q in " ".join(changes[3][:20]).lower()):
279 miss = 1
280 break
281 if miss:
282 continue
283
284 count += 1
285 hn = hex(n)
286
287 yield self.t('searchentry',
288 parity=count & 1,
289 author=changes[1],
290 parent=self.siblings(cl.parents(n), cl.rev),
291 child=self.siblings(cl.children(n), cl.rev),
292 changelogtag=self.showtag("changelogtag",n),
293 manifest=hex(changes[0]),
294 desc=changes[4],
295 date=changes[2],
296 files=self.listfilediffs(changes[3], n),
297 rev=i,
298 node=hn)
299
300 if count >= self.maxchanges:
301 break
302
303 cl = self.repo.changelog
304 mf = cl.read(cl.tip())[0]
305
306 yield self.t('search',
307 query=query,
308 manifest=hex(mf),
309 entries=changelist)
310
311 def changeset(self, nodeid):
312 cl = self.repo.changelog
313 n = self.repo.lookup(nodeid)
314 nodeid = hex(n)
315 changes = cl.read(n)
316 p1 = cl.parents(n)[0]
317
318 files = []
319 mf = self.repo.manifest.read(changes[0])
320 for f in changes[3]:
321 files.append(self.t("filenodelink",
322 filenode=hex(mf.get(f, nullid)), file=f))
323
324 def diff(**map):
325 yield self.diff(p1, n, None)
326
327 yield self.t('changeset',
328 diff=diff,
329 rev=cl.rev(n),
330 node=nodeid,
331 parent=self.siblings(cl.parents(n), cl.rev),
332 child=self.siblings(cl.children(n), cl.rev),
333 changesettag=self.showtag("changesettag",n),
334 manifest=hex(changes[0]),
335 author=changes[1],
336 desc=changes[4],
337 date=changes[2],
338 files=files,
339 archives=self.archivelist(nodeid))
340
341 def filelog(self, f, filenode):
342 cl = self.repo.changelog
343 fl = self.repo.file(f)
344 filenode = hex(fl.lookup(filenode))
345 count = fl.count()
346
347 def entries(**map):
348 l = []
349 parity = (count - 1) & 1
350
351 for i in range(count):
352 n = fl.node(i)
353 lr = fl.linkrev(n)
354 cn = cl.node(lr)
355 cs = cl.read(cl.node(lr))
356
357 l.insert(0, {"parity": parity,
358 "filenode": hex(n),
359 "filerev": i,
360 "file": f,
361 "node": hex(cn),
362 "author": cs[1],
363 "date": cs[2],
364 "rename": self.renamelink(fl, n),
365 "parent": self.siblings(fl.parents(n),
366 fl.rev, file=f),
367 "child": self.siblings(fl.children(n),
368 fl.rev, file=f),
369 "desc": cs[4]})
370 parity = 1 - parity
371
372 for e in l:
373 yield e
374
375 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
376
377 def filerevision(self, f, node):
378 fl = self.repo.file(f)
379 n = fl.lookup(node)
380 node = hex(n)
381 text = fl.read(n)
382 changerev = fl.linkrev(n)
383 cl = self.repo.changelog
384 cn = cl.node(changerev)
385 cs = cl.read(cn)
386 mfn = cs[0]
387
388 mt = mimetypes.guess_type(f)[0]
389 rawtext = text
390 if util.binary(text):
391 mt = mt or 'application/octet-stream'
392 text = "(binary:%s)" % mt
393 mt = mt or 'text/plain'
394
395 def lines():
396 for l, t in enumerate(text.splitlines(1)):
397 yield {"line": t,
398 "linenumber": "% 6d" % (l + 1),
399 "parity": l & 1}
400
401 yield self.t("filerevision",
402 file=f,
403 filenode=node,
404 path=up(f),
405 text=lines(),
406 raw=rawtext,
407 mimetype=mt,
408 rev=changerev,
409 node=hex(cn),
410 manifest=hex(mfn),
411 author=cs[1],
412 date=cs[2],
413 parent=self.siblings(fl.parents(n), fl.rev, file=f),
414 child=self.siblings(fl.children(n), fl.rev, file=f),
415 rename=self.renamelink(fl, n),
416 permissions=self.repo.manifest.readflags(mfn)[f])
417
418 def fileannotate(self, f, node):
419 bcache = {}
420 ncache = {}
421 fl = self.repo.file(f)
422 n = fl.lookup(node)
423 node = hex(n)
424 changerev = fl.linkrev(n)
425
426 cl = self.repo.changelog
427 cn = cl.node(changerev)
428 cs = cl.read(cn)
429 mfn = cs[0]
430
431 def annotate(**map):
432 parity = 1
433 last = None
434 for r, l in fl.annotate(n):
435 try:
436 cnode = ncache[r]
437 except KeyError:
438 cnode = ncache[r] = self.repo.changelog.node(r)
439
440 try:
441 name = bcache[r]
442 except KeyError:
443 cl = self.repo.changelog.read(cnode)
444 bcache[r] = name = self.repo.ui.shortuser(cl[1])
445
446 if last != cnode:
447 parity = 1 - parity
448 last = cnode
449
450 yield {"parity": parity,
451 "node": hex(cnode),
452 "rev": r,
453 "author": name,
454 "file": f,
455 "line": l}
456
457 yield self.t("fileannotate",
458 file=f,
459 filenode=node,
460 annotate=annotate,
461 path=up(f),
462 rev=changerev,
463 node=hex(cn),
464 manifest=hex(mfn),
465 author=cs[1],
466 date=cs[2],
467 rename=self.renamelink(fl, n),
468 parent=self.siblings(fl.parents(n), fl.rev, file=f),
469 child=self.siblings(fl.children(n), fl.rev, file=f),
470 permissions=self.repo.manifest.readflags(mfn)[f])
471
472 def manifest(self, mnode, path):
473 man = self.repo.manifest
474 mn = man.lookup(mnode)
475 mnode = hex(mn)
476 mf = man.read(mn)
477 rev = man.rev(mn)
478 changerev = man.linkrev(mn)
479 node = self.repo.changelog.node(changerev)
480 mff = man.readflags(mn)
481
482 files = {}
483
484 p = path[1:]
485 if p and p[-1] != "/":
486 p += "/"
487 l = len(p)
488
489 for f,n in mf.items():
490 if f[:l] != p:
491 continue
492 remain = f[l:]
493 if "/" in remain:
494 short = remain[:remain.find("/") + 1] # bleah
495 files[short] = (f, None)
496 else:
497 short = os.path.basename(remain)
498 files[short] = (f, n)
499
500 def filelist(**map):
501 parity = 0
502 fl = files.keys()
503 fl.sort()
504 for f in fl:
505 full, fnode = files[f]
506 if not fnode:
507 continue
508
509 yield {"file": full,
510 "manifest": mnode,
511 "filenode": hex(fnode),
512 "parity": parity,
513 "basename": f,
514 "permissions": mff[full]}
515 parity = 1 - parity
516
517 def dirlist(**map):
518 parity = 0
519 fl = files.keys()
520 fl.sort()
521 for f in fl:
522 full, fnode = files[f]
523 if fnode:
524 continue
525
526 yield {"parity": parity,
527 "path": os.path.join(path, f),
528 "manifest": mnode,
529 "basename": f[:-1]}
530 parity = 1 - parity
531
532 yield self.t("manifest",
533 manifest=mnode,
534 rev=rev,
535 node=hex(node),
536 path=path,
537 up=up(path),
538 fentries=filelist,
539 dentries=dirlist,
540 archives=self.archivelist(hex(node)))
541
542 def tags(self):
543 cl = self.repo.changelog
544 mf = cl.read(cl.tip())[0]
545
546 i = self.repo.tagslist()
547 i.reverse()
548
549 def entries(notip=False, **map):
550 parity = 0
551 for k,n in i:
552 if notip and k == "tip": continue
553 yield {"parity": parity,
554 "tag": k,
555 "tagmanifest": hex(cl.read(n)[0]),
556 "date": cl.read(n)[2],
557 "node": hex(n)}
558 parity = 1 - parity
559
560 yield self.t("tags",
561 manifest=hex(mf),
562 entries=lambda **x: entries(False, **x),
563 entriesnotip=lambda **x: entries(True, **x))
564
565 def summary(self):
566 cl = self.repo.changelog
567 mf = cl.read(cl.tip())[0]
568
569 i = self.repo.tagslist()
570 i.reverse()
571
572 def tagentries(**map):
573 parity = 0
574 count = 0
575 for k,n in i:
576 if k == "tip": # skip tip
577 continue;
578
579 count += 1
580 if count > 10: # limit to 10 tags
581 break;
582
583 c = cl.read(n)
584 m = c[0]
585 t = c[2]
586
587 yield self.t("tagentry",
588 parity = parity,
589 tag = k,
590 node = hex(n),
591 date = t,
592 tagmanifest = hex(m))
593 parity = 1 - parity
594
595 def changelist(**map):
596 parity = 0
597 cl = self.repo.changelog
598 l = [] # build a list in forward order for efficiency
599 for i in range(start, end):
600 n = cl.node(i)
601 changes = cl.read(n)
602 hn = hex(n)
603 t = changes[2]
604
605 l.insert(0, self.t(
606 'shortlogentry',
607 parity = parity,
608 author = changes[1],
609 manifest = hex(changes[0]),
610 desc = changes[4],
611 date = t,
612 rev = i,
613 node = hn))
614 parity = 1 - parity
615
616 yield l
617
618 cl = self.repo.changelog
619 mf = cl.read(cl.tip())[0]
620 count = cl.count()
621 start = max(0, count - self.maxchanges)
622 end = min(count, start + self.maxchanges)
623 pos = end - 1
624
625 yield self.t("summary",
626 desc = self.repo.ui.config("web", "description", "unknown"),
627 owner = (self.repo.ui.config("ui", "username") or # preferred
628 self.repo.ui.config("web", "contact") or # deprecated
629 self.repo.ui.config("web", "author", "unknown")), # also
630 lastchange = (0, 0), # FIXME
631 manifest = hex(mf),
632 tags = tagentries,
633 shortlog = changelist)
634
635 def filediff(self, file, changeset):
636 cl = self.repo.changelog
637 n = self.repo.lookup(changeset)
638 changeset = hex(n)
639 p1 = cl.parents(n)[0]
640 cs = cl.read(n)
641 mf = self.repo.manifest.read(cs[0])
642
643 def diff(**map):
644 yield self.diff(p1, n, [file])
645
646 yield self.t("filediff",
647 file=file,
648 filenode=hex(mf.get(file, nullid)),
649 node=changeset,
650 rev=self.repo.changelog.rev(n),
651 parent=self.siblings(cl.parents(n), cl.rev),
652 child=self.siblings(cl.children(n), cl.rev),
653 diff=diff)
654
655 archive_specs = {
656 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', 'x-bzip2'),
657 'gz': ('application/x-tar', 'tgz', '.tar.gz', 'x-gzip'),
658 'zip': ('application/zip', 'zip', '.zip', None),
659 }
660
661 def archive(self, req, cnode, type):
662 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
663 name = "%s-%s" % (reponame, short(cnode))
664 mimetype, artype, extension, encoding = self.archive_specs[type]
665 headers = [('Content-type', mimetype),
666 ('Content-disposition', 'attachment; filename=%s%s' %
667 (name, extension))]
668 if encoding:
669 headers.append(('Content-encoding', encoding))
670 req.header(headers)
671 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
672
673 # add tags to things
674 # tags -> list of changesets corresponding to tags
675 # find tag, changeset, file
676
677 def run(self, req=hgrequest()):
678 def clean(path):
679 p = util.normpath(path)
680 if p[:2] == "..":
681 raise "suspicious path"
682 return p
683
684 def header(**map):
685 yield self.t("header", **map)
686
687 def footer(**map):
688 yield self.t("footer",
689 motd=self.repo.ui.config("web", "motd", ""),
690 **map)
691
692 def expand_form(form):
693 shortcuts = {
694 'cl': [('cmd', ['changelog']), ('rev', None)],
695 'cs': [('cmd', ['changeset']), ('node', None)],
696 'f': [('cmd', ['file']), ('filenode', None)],
697 'fl': [('cmd', ['filelog']), ('filenode', None)],
698 'fd': [('cmd', ['filediff']), ('node', None)],
699 'fa': [('cmd', ['annotate']), ('filenode', None)],
700 'mf': [('cmd', ['manifest']), ('manifest', None)],
701 'ca': [('cmd', ['archive']), ('node', None)],
702 'tags': [('cmd', ['tags'])],
703 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
704 'static': [('cmd', ['static']), ('file', None)]
705 }
706
707 for k in shortcuts.iterkeys():
708 if form.has_key(k):
709 for name, value in shortcuts[k]:
710 if value is None:
711 value = form[k]
712 form[name] = value
713 del form[k]
714
715 self.refresh()
716
717 expand_form(req.form)
718
719 t = self.repo.ui.config("web", "templates", templater.templatepath())
720 static = self.repo.ui.config("web", "static", os.path.join(t,"static"))
721 m = os.path.join(t, "map")
722 style = self.repo.ui.config("web", "style", "")
723 if req.form.has_key('style'):
724 style = req.form['style'][0]
725 if style:
726 b = os.path.basename("map-" + style)
727 p = os.path.join(t, b)
728 if os.path.isfile(p):
729 m = p
730
731 port = req.env["SERVER_PORT"]
732 port = port != "80" and (":" + port) or ""
733 uri = req.env["REQUEST_URI"]
734 if "?" in uri:
735 uri = uri.split("?")[0]
736 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
737 if not self.reponame:
738 self.reponame = (self.repo.ui.config("web", "name")
739 or uri.strip('/') or self.repo.root)
740
741 self.t = templater.templater(m, templater.common_filters,
742 defaults={"url": url,
743 "repo": self.reponame,
744 "header": header,
745 "footer": footer,
746 })
747
748 if not req.form.has_key('cmd'):
749 req.form['cmd'] = [self.t.cache['default'],]
750
751 cmd = req.form['cmd'][0]
752 if cmd == 'changelog':
753 hi = self.repo.changelog.count() - 1
754 if req.form.has_key('rev'):
755 hi = req.form['rev'][0]
756 try:
757 hi = self.repo.changelog.rev(self.repo.lookup(hi))
758 except hg.RepoError:
759 req.write(self.search(hi)) # XXX redirect to 404 page?
760 return
761
762 req.write(self.changelog(hi))
763
764 elif cmd == 'changeset':
765 req.write(self.changeset(req.form['node'][0]))
766
767 elif cmd == 'manifest':
768 req.write(self.manifest(req.form['manifest'][0],
769 clean(req.form['path'][0])))
770
771 elif cmd == 'tags':
772 req.write(self.tags())
773
774 elif cmd == 'summary':
775 req.write(self.summary())
776
777 elif cmd == 'filediff':
778 req.write(self.filediff(clean(req.form['file'][0]),
779 req.form['node'][0]))
780
781 elif cmd == 'file':
782 req.write(self.filerevision(clean(req.form['file'][0]),
783 req.form['filenode'][0]))
784
785 elif cmd == 'annotate':
786 req.write(self.fileannotate(clean(req.form['file'][0]),
787 req.form['filenode'][0]))
788
789 elif cmd == 'filelog':
790 req.write(self.filelog(clean(req.form['file'][0]),
791 req.form['filenode'][0]))
792
793 elif cmd == 'heads':
794 req.httphdr("application/mercurial-0.1")
795 h = self.repo.heads()
796 req.write(" ".join(map(hex, h)) + "\n")
797
798 elif cmd == 'branches':
799 req.httphdr("application/mercurial-0.1")
800 nodes = []
801 if req.form.has_key('nodes'):
802 nodes = map(bin, req.form['nodes'][0].split(" "))
803 for b in self.repo.branches(nodes):
804 req.write(" ".join(map(hex, b)) + "\n")
805
806 elif cmd == 'between':
807 req.httphdr("application/mercurial-0.1")
808 nodes = []
809 if req.form.has_key('pairs'):
810 pairs = [map(bin, p.split("-"))
811 for p in req.form['pairs'][0].split(" ")]
812 for b in self.repo.between(pairs):
813 req.write(" ".join(map(hex, b)) + "\n")
814
815 elif cmd == 'changegroup':
816 req.httphdr("application/mercurial-0.1")
817 nodes = []
818 if not self.allowpull:
819 return
820
821 if req.form.has_key('roots'):
822 nodes = map(bin, req.form['roots'][0].split(" "))
823
824 z = zlib.compressobj()
825 f = self.repo.changegroup(nodes, 'serve')
826 while 1:
827 chunk = f.read(4096)
828 if not chunk:
829 break
830 req.write(z.compress(chunk))
831
832 req.write(z.flush())
833
834 elif cmd == 'archive':
835 changeset = self.repo.lookup(req.form['node'][0])
836 type = req.form['type'][0]
837 if (type in self.archives and
838 self.repo.ui.configbool("web", "allow" + type, False)):
839 self.archive(req, changeset, type)
840 return
841
842 req.write(self.t("error"))
843
844 elif cmd == 'static':
845 fname = req.form['file'][0]
846 req.write(staticfile(static, fname)
847 or self.t("error", error="%r not found" % fname))
848
849 else:
850 req.write(self.t("error"))
851
852 # This is a stopgap
853 class hgwebdir(object):
854 def __init__(self, config):
855 def cleannames(items):
856 return [(name.strip(os.sep), path) for name, path in items]
857
858 self.motd = ""
859 self.repos_sorted = ('name', False)
860 if isinstance(config, (list, tuple)):
861 self.repos = cleannames(config)
862 self.repos_sorted = ('', False)
863 elif isinstance(config, dict):
864 self.repos = cleannames(config.items())
865 self.repos.sort()
866 else:
867 cp = ConfigParser.SafeConfigParser()
868 cp.read(config)
869 self.repos = []
870 if cp.has_section('web') and cp.has_option('web', 'motd'):
871 self.motd = cp.get('web', 'motd')
872 if cp.has_section('paths'):
873 self.repos.extend(cleannames(cp.items('paths')))
874 if cp.has_section('collections'):
875 for prefix, root in cp.items('collections'):
876 for path in util.walkrepos(root):
877 repo = os.path.normpath(path)
878 name = repo
879 if name.startswith(prefix):
880 name = name[len(prefix):]
881 self.repos.append((name.lstrip(os.sep), repo))
882 self.repos.sort()
883
884 def run(self, req=hgrequest()):
885 def header(**map):
886 yield tmpl("header", **map)
887
888 def footer(**map):
889 yield tmpl("footer", motd=self.motd, **map)
890
891 m = os.path.join(templater.templatepath(), "map")
892 tmpl = templater.templater(m, templater.common_filters,
893 defaults={"header": header,
894 "footer": footer})
895
896 def archivelist(ui, nodeid, url):
897 for i in ['zip', 'gz', 'bz2']:
898 if ui.configbool("web", "allow" + i, False):
899 yield {"type" : i, "node": nodeid, "url": url}
900
901 def entries(sortcolumn="", descending=False, **map):
902 rows = []
903 parity = 0
904 for name, path in self.repos:
905 u = ui.ui()
906 try:
907 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
908 except IOError:
909 pass
910 get = u.config
911
912 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
913 .replace("//", "/"))
914
915 # update time with local timezone
916 try:
917 d = (get_mtime(path), util.makedate()[1])
918 except OSError:
919 continue
920
921 contact = (get("ui", "username") or # preferred
922 get("web", "contact") or # deprecated
923 get("web", "author", "")) # also
924 description = get("web", "description", "")
925 name = get("web", "name", name)
926 row = dict(contact=contact or "unknown",
927 contact_sort=contact.upper() or "unknown",
928 name=name,
929 name_sort=name,
930 url=url,
931 description=description or "unknown",
932 description_sort=description.upper() or "unknown",
933 lastchange=d,
934 lastchange_sort=d[1]-d[0],
935 archives=archivelist(u, "tip", url))
936 if (not sortcolumn
937 or (sortcolumn, descending) == self.repos_sorted):
938 # fast path for unsorted output
939 row['parity'] = parity
940 parity = 1 - parity
941 yield row
942 else:
943 rows.append((row["%s_sort" % sortcolumn], row))
944 if rows:
945 rows.sort()
946 if descending:
947 rows.reverse()
948 for key, row in rows:
949 row['parity'] = parity
950 parity = 1 - parity
951 yield row
952
953 virtual = req.env.get("PATH_INFO", "").strip('/')
954 if virtual:
955 real = dict(self.repos).get(virtual)
956 if real:
957 try:
958 hgweb(real).run(req)
959 except IOError, inst:
960 req.write(tmpl("error", error=inst.strerror))
961 except hg.RepoError, inst:
962 req.write(tmpl("error", error=str(inst)))
963 else:
964 req.write(tmpl("notfound", repo=virtual))
965 else:
966 if req.form.has_key('static'):
967 static = os.path.join(templater.templatepath(), "static")
968 fname = req.form['static'][0]
969 req.write(staticfile(static, fname)
970 or tmpl("error", error="%r not found" % fname))
971 else:
972 sortable = ["name", "description", "contact", "lastchange"]
973 sortcolumn, descending = self.repos_sorted
974 if req.form.has_key('sort'):
975 sortcolumn = req.form['sort'][0]
976 descending = sortcolumn.startswith('-')
977 if descending:
978 sortcolumn = sortcolumn[1:]
979 if sortcolumn not in sortable:
980 sortcolumn = ""
981
982 sort = [("sort_%s" % column,
983 "%s%s" % ((not descending and column == sortcolumn)
984 and "-" or "", column))
985 for column in sortable]
986 req.write(tmpl("index", entries=entries,
987 sortcolumn=sortcolumn, descending=descending,
988 **dict(sort)))
@@ -1,988 +1,819 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
10 import os.path
10 import mimetypes
11 import mimetypes
11 from mercurial.demandload import demandload
12 from mercurial.demandload import demandload
12 demandload(globals(), "time re socket zlib errno ConfigParser tempfile")
13 demandload(globals(), "re zlib ConfigParser")
13 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater")
14 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater")
14 demandload(globals(), "mercurial.hgweb.request:hgrequest")
15 demandload(globals(), "mercurial.hgweb.request:hgrequest")
15 demandload(globals(), "mercurial.hgweb.server:create_server")
16 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
16 from mercurial.node import *
17 from mercurial.node import *
17 from mercurial.i18n import gettext as _
18 from mercurial.i18n import gettext as _
18
19
19 def up(p):
20 def _up(p):
20 if p[0] != "/":
21 if p[0] != "/":
21 p = "/" + p
22 p = "/" + p
22 if p[-1] == "/":
23 if p[-1] == "/":
23 p = p[:-1]
24 p = p[:-1]
24 up = os.path.dirname(p)
25 up = os.path.dirname(p)
25 if up == "/":
26 if up == "/":
26 return "/"
27 return "/"
27 return up + "/"
28 return up + "/"
28
29
29 def get_mtime(repo_path):
30 hg_path = os.path.join(repo_path, ".hg")
31 cl_path = os.path.join(hg_path, "00changelog.i")
32 if os.path.exists(os.path.join(cl_path)):
33 return os.stat(cl_path).st_mtime
34 else:
35 return os.stat(hg_path).st_mtime
36
37 def staticfile(directory, fname):
38 """return a file inside directory with guessed content-type header
39
40 fname always uses '/' as directory separator and isn't allowed to
41 contain unusual path components.
42 Content-type is guessed using the mimetypes module.
43 Return an empty string if fname is illegal or file not found.
44
45 """
46 parts = fname.split('/')
47 path = directory
48 for part in parts:
49 if (part in ('', os.curdir, os.pardir) or
50 os.sep in part or os.altsep is not None and os.altsep in part):
51 return ""
52 path = os.path.join(path, part)
53 try:
54 os.stat(path)
55 ct = mimetypes.guess_type(path)[0] or "text/plain"
56 return "Content-type: %s\n\n%s" % (ct, file(path).read())
57 except (TypeError, OSError):
58 # illegal fname or unreadable file
59 return ""
60
61 class hgweb(object):
30 class hgweb(object):
62 def __init__(self, repo, name=None):
31 def __init__(self, repo, name=None):
63 if type(repo) == type(""):
32 if type(repo) == type(""):
64 self.repo = hg.repository(ui.ui(), repo)
33 self.repo = hg.repository(ui.ui(), repo)
65 else:
34 else:
66 self.repo = repo
35 self.repo = repo
67
36
68 self.mtime = -1
37 self.mtime = -1
69 self.reponame = name
38 self.reponame = name
70 self.archives = 'zip', 'gz', 'bz2'
39 self.archives = 'zip', 'gz', 'bz2'
71
40
72 def refresh(self):
41 def refresh(self):
73 mtime = get_mtime(self.repo.root)
42 mtime = get_mtime(self.repo.root)
74 if mtime != self.mtime:
43 if mtime != self.mtime:
75 self.mtime = mtime
44 self.mtime = mtime
76 self.repo = hg.repository(self.repo.ui, self.repo.root)
45 self.repo = hg.repository(self.repo.ui, self.repo.root)
77 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
46 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
78 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
47 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
79 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
48 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
80
49
81 def archivelist(self, nodeid):
50 def archivelist(self, nodeid):
82 for i in self.archives:
51 for i in self.archives:
83 if self.repo.ui.configbool("web", "allow" + i, False):
52 if self.repo.ui.configbool("web", "allow" + i, False):
84 yield {"type" : i, "node" : nodeid, "url": ""}
53 yield {"type" : i, "node" : nodeid, "url": ""}
85
54
86 def listfiles(self, files, mf):
55 def listfiles(self, files, mf):
87 for f in files[:self.maxfiles]:
56 for f in files[:self.maxfiles]:
88 yield self.t("filenodelink", node=hex(mf[f]), file=f)
57 yield self.t("filenodelink", node=hex(mf[f]), file=f)
89 if len(files) > self.maxfiles:
58 if len(files) > self.maxfiles:
90 yield self.t("fileellipses")
59 yield self.t("fileellipses")
91
60
92 def listfilediffs(self, files, changeset):
61 def listfilediffs(self, files, changeset):
93 for f in files[:self.maxfiles]:
62 for f in files[:self.maxfiles]:
94 yield self.t("filedifflink", node=hex(changeset), file=f)
63 yield self.t("filedifflink", node=hex(changeset), file=f)
95 if len(files) > self.maxfiles:
64 if len(files) > self.maxfiles:
96 yield self.t("fileellipses")
65 yield self.t("fileellipses")
97
66
98 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
67 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
99 if not rev:
68 if not rev:
100 rev = lambda x: ""
69 rev = lambda x: ""
101 siblings = [s for s in siblings if s != nullid]
70 siblings = [s for s in siblings if s != nullid]
102 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
71 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
103 return
72 return
104 for s in siblings:
73 for s in siblings:
105 yield dict(node=hex(s), rev=rev(s), **args)
74 yield dict(node=hex(s), rev=rev(s), **args)
106
75
107 def renamelink(self, fl, node):
76 def renamelink(self, fl, node):
108 r = fl.renamed(node)
77 r = fl.renamed(node)
109 if r:
78 if r:
110 return [dict(file=r[0], node=hex(r[1]))]
79 return [dict(file=r[0], node=hex(r[1]))]
111 return []
80 return []
112
81
113 def showtag(self, t1, node=nullid, **args):
82 def showtag(self, t1, node=nullid, **args):
114 for t in self.repo.nodetags(node):
83 for t in self.repo.nodetags(node):
115 yield self.t(t1, tag=t, **args)
84 yield self.t(t1, tag=t, **args)
116
85
117 def diff(self, node1, node2, files):
86 def diff(self, node1, node2, files):
118 def filterfiles(filters, files):
87 def filterfiles(filters, files):
119 l = [x for x in files if x in filters]
88 l = [x for x in files if x in filters]
120
89
121 for t in filters:
90 for t in filters:
122 if t and t[-1] != os.sep:
91 if t and t[-1] != os.sep:
123 t += os.sep
92 t += os.sep
124 l += [x for x in files if x.startswith(t)]
93 l += [x for x in files if x.startswith(t)]
125 return l
94 return l
126
95
127 parity = [0]
96 parity = [0]
128 def diffblock(diff, f, fn):
97 def diffblock(diff, f, fn):
129 yield self.t("diffblock",
98 yield self.t("diffblock",
130 lines=prettyprintlines(diff),
99 lines=prettyprintlines(diff),
131 parity=parity[0],
100 parity=parity[0],
132 file=f,
101 file=f,
133 filenode=hex(fn or nullid))
102 filenode=hex(fn or nullid))
134 parity[0] = 1 - parity[0]
103 parity[0] = 1 - parity[0]
135
104
136 def prettyprintlines(diff):
105 def prettyprintlines(diff):
137 for l in diff.splitlines(1):
106 for l in diff.splitlines(1):
138 if l.startswith('+'):
107 if l.startswith('+'):
139 yield self.t("difflineplus", line=l)
108 yield self.t("difflineplus", line=l)
140 elif l.startswith('-'):
109 elif l.startswith('-'):
141 yield self.t("difflineminus", line=l)
110 yield self.t("difflineminus", line=l)
142 elif l.startswith('@'):
111 elif l.startswith('@'):
143 yield self.t("difflineat", line=l)
112 yield self.t("difflineat", line=l)
144 else:
113 else:
145 yield self.t("diffline", line=l)
114 yield self.t("diffline", line=l)
146
115
147 r = self.repo
116 r = self.repo
148 cl = r.changelog
117 cl = r.changelog
149 mf = r.manifest
118 mf = r.manifest
150 change1 = cl.read(node1)
119 change1 = cl.read(node1)
151 change2 = cl.read(node2)
120 change2 = cl.read(node2)
152 mmap1 = mf.read(change1[0])
121 mmap1 = mf.read(change1[0])
153 mmap2 = mf.read(change2[0])
122 mmap2 = mf.read(change2[0])
154 date1 = util.datestr(change1[2])
123 date1 = util.datestr(change1[2])
155 date2 = util.datestr(change2[2])
124 date2 = util.datestr(change2[2])
156
125
157 modified, added, removed, deleted, unknown = r.changes(node1, node2)
126 modified, added, removed, deleted, unknown = r.changes(node1, node2)
158 if files:
127 if files:
159 modified, added, removed = map(lambda x: filterfiles(files, x),
128 modified, added, removed = map(lambda x: filterfiles(files, x),
160 (modified, added, removed))
129 (modified, added, removed))
161
130
162 diffopts = self.repo.ui.diffopts()
131 diffopts = self.repo.ui.diffopts()
163 showfunc = diffopts['showfunc']
132 showfunc = diffopts['showfunc']
164 ignorews = diffopts['ignorews']
133 ignorews = diffopts['ignorews']
165 for f in modified:
134 for f in modified:
166 to = r.file(f).read(mmap1[f])
135 to = r.file(f).read(mmap1[f])
167 tn = r.file(f).read(mmap2[f])
136 tn = r.file(f).read(mmap2[f])
168 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
137 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
169 showfunc=showfunc, ignorews=ignorews), f, tn)
138 showfunc=showfunc, ignorews=ignorews), f, tn)
170 for f in added:
139 for f in added:
171 to = None
140 to = None
172 tn = r.file(f).read(mmap2[f])
141 tn = r.file(f).read(mmap2[f])
173 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
142 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
174 showfunc=showfunc, ignorews=ignorews), f, tn)
143 showfunc=showfunc, ignorews=ignorews), f, tn)
175 for f in removed:
144 for f in removed:
176 to = r.file(f).read(mmap1[f])
145 to = r.file(f).read(mmap1[f])
177 tn = None
146 tn = None
178 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
147 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
179 showfunc=showfunc, ignorews=ignorews), f, tn)
148 showfunc=showfunc, ignorews=ignorews), f, tn)
180
149
181 def changelog(self, pos):
150 def changelog(self, pos):
182 def changenav(**map):
151 def changenav(**map):
183 def seq(factor, maxchanges=None):
152 def seq(factor, maxchanges=None):
184 if maxchanges:
153 if maxchanges:
185 yield maxchanges
154 yield maxchanges
186 if maxchanges >= 20 and maxchanges <= 40:
155 if maxchanges >= 20 and maxchanges <= 40:
187 yield 50
156 yield 50
188 else:
157 else:
189 yield 1 * factor
158 yield 1 * factor
190 yield 3 * factor
159 yield 3 * factor
191 for f in seq(factor * 10):
160 for f in seq(factor * 10):
192 yield f
161 yield f
193
162
194 l = []
163 l = []
195 last = 0
164 last = 0
196 for f in seq(1, self.maxchanges):
165 for f in seq(1, self.maxchanges):
197 if f < self.maxchanges or f <= last:
166 if f < self.maxchanges or f <= last:
198 continue
167 continue
199 if f > count:
168 if f > count:
200 break
169 break
201 last = f
170 last = f
202 r = "%d" % f
171 r = "%d" % f
203 if pos + f < count:
172 if pos + f < count:
204 l.append(("+" + r, pos + f))
173 l.append(("+" + r, pos + f))
205 if pos - f >= 0:
174 if pos - f >= 0:
206 l.insert(0, ("-" + r, pos - f))
175 l.insert(0, ("-" + r, pos - f))
207
176
208 yield {"rev": 0, "label": "(0)"}
177 yield {"rev": 0, "label": "(0)"}
209
178
210 for label, rev in l:
179 for label, rev in l:
211 yield {"label": label, "rev": rev}
180 yield {"label": label, "rev": rev}
212
181
213 yield {"label": "tip", "rev": "tip"}
182 yield {"label": "tip", "rev": "tip"}
214
183
215 def changelist(**map):
184 def changelist(**map):
216 parity = (start - end) & 1
185 parity = (start - end) & 1
217 cl = self.repo.changelog
186 cl = self.repo.changelog
218 l = [] # build a list in forward order for efficiency
187 l = [] # build a list in forward order for efficiency
219 for i in range(start, end):
188 for i in range(start, end):
220 n = cl.node(i)
189 n = cl.node(i)
221 changes = cl.read(n)
190 changes = cl.read(n)
222 hn = hex(n)
191 hn = hex(n)
223
192
224 l.insert(0, {"parity": parity,
193 l.insert(0, {"parity": parity,
225 "author": changes[1],
194 "author": changes[1],
226 "parent": self.siblings(cl.parents(n), cl.rev,
195 "parent": self.siblings(cl.parents(n), cl.rev,
227 cl.rev(n) - 1),
196 cl.rev(n) - 1),
228 "child": self.siblings(cl.children(n), cl.rev,
197 "child": self.siblings(cl.children(n), cl.rev,
229 cl.rev(n) + 1),
198 cl.rev(n) + 1),
230 "changelogtag": self.showtag("changelogtag",n),
199 "changelogtag": self.showtag("changelogtag",n),
231 "manifest": hex(changes[0]),
200 "manifest": hex(changes[0]),
232 "desc": changes[4],
201 "desc": changes[4],
233 "date": changes[2],
202 "date": changes[2],
234 "files": self.listfilediffs(changes[3], n),
203 "files": self.listfilediffs(changes[3], n),
235 "rev": i,
204 "rev": i,
236 "node": hn})
205 "node": hn})
237 parity = 1 - parity
206 parity = 1 - parity
238
207
239 for e in l:
208 for e in l:
240 yield e
209 yield e
241
210
242 cl = self.repo.changelog
211 cl = self.repo.changelog
243 mf = cl.read(cl.tip())[0]
212 mf = cl.read(cl.tip())[0]
244 count = cl.count()
213 count = cl.count()
245 start = max(0, pos - self.maxchanges + 1)
214 start = max(0, pos - self.maxchanges + 1)
246 end = min(count, start + self.maxchanges)
215 end = min(count, start + self.maxchanges)
247 pos = end - 1
216 pos = end - 1
248
217
249 yield self.t('changelog',
218 yield self.t('changelog',
250 changenav=changenav,
219 changenav=changenav,
251 manifest=hex(mf),
220 manifest=hex(mf),
252 rev=pos, changesets=count, entries=changelist,
221 rev=pos, changesets=count, entries=changelist,
253 archives=self.archivelist("tip"))
222 archives=self.archivelist("tip"))
254
223
255 def search(self, query):
224 def search(self, query):
256
225
257 def changelist(**map):
226 def changelist(**map):
258 cl = self.repo.changelog
227 cl = self.repo.changelog
259 count = 0
228 count = 0
260 qw = query.lower().split()
229 qw = query.lower().split()
261
230
262 def revgen():
231 def revgen():
263 for i in range(cl.count() - 1, 0, -100):
232 for i in range(cl.count() - 1, 0, -100):
264 l = []
233 l = []
265 for j in range(max(0, i - 100), i):
234 for j in range(max(0, i - 100), i):
266 n = cl.node(j)
235 n = cl.node(j)
267 changes = cl.read(n)
236 changes = cl.read(n)
268 l.append((n, j, changes))
237 l.append((n, j, changes))
269 l.reverse()
238 l.reverse()
270 for e in l:
239 for e in l:
271 yield e
240 yield e
272
241
273 for n, i, changes in revgen():
242 for n, i, changes in revgen():
274 miss = 0
243 miss = 0
275 for q in qw:
244 for q in qw:
276 if not (q in changes[1].lower() or
245 if not (q in changes[1].lower() or
277 q in changes[4].lower() or
246 q in changes[4].lower() or
278 q in " ".join(changes[3][:20]).lower()):
247 q in " ".join(changes[3][:20]).lower()):
279 miss = 1
248 miss = 1
280 break
249 break
281 if miss:
250 if miss:
282 continue
251 continue
283
252
284 count += 1
253 count += 1
285 hn = hex(n)
254 hn = hex(n)
286
255
287 yield self.t('searchentry',
256 yield self.t('searchentry',
288 parity=count & 1,
257 parity=count & 1,
289 author=changes[1],
258 author=changes[1],
290 parent=self.siblings(cl.parents(n), cl.rev),
259 parent=self.siblings(cl.parents(n), cl.rev),
291 child=self.siblings(cl.children(n), cl.rev),
260 child=self.siblings(cl.children(n), cl.rev),
292 changelogtag=self.showtag("changelogtag",n),
261 changelogtag=self.showtag("changelogtag",n),
293 manifest=hex(changes[0]),
262 manifest=hex(changes[0]),
294 desc=changes[4],
263 desc=changes[4],
295 date=changes[2],
264 date=changes[2],
296 files=self.listfilediffs(changes[3], n),
265 files=self.listfilediffs(changes[3], n),
297 rev=i,
266 rev=i,
298 node=hn)
267 node=hn)
299
268
300 if count >= self.maxchanges:
269 if count >= self.maxchanges:
301 break
270 break
302
271
303 cl = self.repo.changelog
272 cl = self.repo.changelog
304 mf = cl.read(cl.tip())[0]
273 mf = cl.read(cl.tip())[0]
305
274
306 yield self.t('search',
275 yield self.t('search',
307 query=query,
276 query=query,
308 manifest=hex(mf),
277 manifest=hex(mf),
309 entries=changelist)
278 entries=changelist)
310
279
311 def changeset(self, nodeid):
280 def changeset(self, nodeid):
312 cl = self.repo.changelog
281 cl = self.repo.changelog
313 n = self.repo.lookup(nodeid)
282 n = self.repo.lookup(nodeid)
314 nodeid = hex(n)
283 nodeid = hex(n)
315 changes = cl.read(n)
284 changes = cl.read(n)
316 p1 = cl.parents(n)[0]
285 p1 = cl.parents(n)[0]
317
286
318 files = []
287 files = []
319 mf = self.repo.manifest.read(changes[0])
288 mf = self.repo.manifest.read(changes[0])
320 for f in changes[3]:
289 for f in changes[3]:
321 files.append(self.t("filenodelink",
290 files.append(self.t("filenodelink",
322 filenode=hex(mf.get(f, nullid)), file=f))
291 filenode=hex(mf.get(f, nullid)), file=f))
323
292
324 def diff(**map):
293 def diff(**map):
325 yield self.diff(p1, n, None)
294 yield self.diff(p1, n, None)
326
295
327 yield self.t('changeset',
296 yield self.t('changeset',
328 diff=diff,
297 diff=diff,
329 rev=cl.rev(n),
298 rev=cl.rev(n),
330 node=nodeid,
299 node=nodeid,
331 parent=self.siblings(cl.parents(n), cl.rev),
300 parent=self.siblings(cl.parents(n), cl.rev),
332 child=self.siblings(cl.children(n), cl.rev),
301 child=self.siblings(cl.children(n), cl.rev),
333 changesettag=self.showtag("changesettag",n),
302 changesettag=self.showtag("changesettag",n),
334 manifest=hex(changes[0]),
303 manifest=hex(changes[0]),
335 author=changes[1],
304 author=changes[1],
336 desc=changes[4],
305 desc=changes[4],
337 date=changes[2],
306 date=changes[2],
338 files=files,
307 files=files,
339 archives=self.archivelist(nodeid))
308 archives=self.archivelist(nodeid))
340
309
341 def filelog(self, f, filenode):
310 def filelog(self, f, filenode):
342 cl = self.repo.changelog
311 cl = self.repo.changelog
343 fl = self.repo.file(f)
312 fl = self.repo.file(f)
344 filenode = hex(fl.lookup(filenode))
313 filenode = hex(fl.lookup(filenode))
345 count = fl.count()
314 count = fl.count()
346
315
347 def entries(**map):
316 def entries(**map):
348 l = []
317 l = []
349 parity = (count - 1) & 1
318 parity = (count - 1) & 1
350
319
351 for i in range(count):
320 for i in range(count):
352 n = fl.node(i)
321 n = fl.node(i)
353 lr = fl.linkrev(n)
322 lr = fl.linkrev(n)
354 cn = cl.node(lr)
323 cn = cl.node(lr)
355 cs = cl.read(cl.node(lr))
324 cs = cl.read(cl.node(lr))
356
325
357 l.insert(0, {"parity": parity,
326 l.insert(0, {"parity": parity,
358 "filenode": hex(n),
327 "filenode": hex(n),
359 "filerev": i,
328 "filerev": i,
360 "file": f,
329 "file": f,
361 "node": hex(cn),
330 "node": hex(cn),
362 "author": cs[1],
331 "author": cs[1],
363 "date": cs[2],
332 "date": cs[2],
364 "rename": self.renamelink(fl, n),
333 "rename": self.renamelink(fl, n),
365 "parent": self.siblings(fl.parents(n),
334 "parent": self.siblings(fl.parents(n),
366 fl.rev, file=f),
335 fl.rev, file=f),
367 "child": self.siblings(fl.children(n),
336 "child": self.siblings(fl.children(n),
368 fl.rev, file=f),
337 fl.rev, file=f),
369 "desc": cs[4]})
338 "desc": cs[4]})
370 parity = 1 - parity
339 parity = 1 - parity
371
340
372 for e in l:
341 for e in l:
373 yield e
342 yield e
374
343
375 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
344 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
376
345
377 def filerevision(self, f, node):
346 def filerevision(self, f, node):
378 fl = self.repo.file(f)
347 fl = self.repo.file(f)
379 n = fl.lookup(node)
348 n = fl.lookup(node)
380 node = hex(n)
349 node = hex(n)
381 text = fl.read(n)
350 text = fl.read(n)
382 changerev = fl.linkrev(n)
351 changerev = fl.linkrev(n)
383 cl = self.repo.changelog
352 cl = self.repo.changelog
384 cn = cl.node(changerev)
353 cn = cl.node(changerev)
385 cs = cl.read(cn)
354 cs = cl.read(cn)
386 mfn = cs[0]
355 mfn = cs[0]
387
356
388 mt = mimetypes.guess_type(f)[0]
357 mt = mimetypes.guess_type(f)[0]
389 rawtext = text
358 rawtext = text
390 if util.binary(text):
359 if util.binary(text):
391 mt = mt or 'application/octet-stream'
360 mt = mt or 'application/octet-stream'
392 text = "(binary:%s)" % mt
361 text = "(binary:%s)" % mt
393 mt = mt or 'text/plain'
362 mt = mt or 'text/plain'
394
363
395 def lines():
364 def lines():
396 for l, t in enumerate(text.splitlines(1)):
365 for l, t in enumerate(text.splitlines(1)):
397 yield {"line": t,
366 yield {"line": t,
398 "linenumber": "% 6d" % (l + 1),
367 "linenumber": "% 6d" % (l + 1),
399 "parity": l & 1}
368 "parity": l & 1}
400
369
401 yield self.t("filerevision",
370 yield self.t("filerevision",
402 file=f,
371 file=f,
403 filenode=node,
372 filenode=node,
404 path=up(f),
373 path=_up(f),
405 text=lines(),
374 text=lines(),
406 raw=rawtext,
375 raw=rawtext,
407 mimetype=mt,
376 mimetype=mt,
408 rev=changerev,
377 rev=changerev,
409 node=hex(cn),
378 node=hex(cn),
410 manifest=hex(mfn),
379 manifest=hex(mfn),
411 author=cs[1],
380 author=cs[1],
412 date=cs[2],
381 date=cs[2],
413 parent=self.siblings(fl.parents(n), fl.rev, file=f),
382 parent=self.siblings(fl.parents(n), fl.rev, file=f),
414 child=self.siblings(fl.children(n), fl.rev, file=f),
383 child=self.siblings(fl.children(n), fl.rev, file=f),
415 rename=self.renamelink(fl, n),
384 rename=self.renamelink(fl, n),
416 permissions=self.repo.manifest.readflags(mfn)[f])
385 permissions=self.repo.manifest.readflags(mfn)[f])
417
386
418 def fileannotate(self, f, node):
387 def fileannotate(self, f, node):
419 bcache = {}
388 bcache = {}
420 ncache = {}
389 ncache = {}
421 fl = self.repo.file(f)
390 fl = self.repo.file(f)
422 n = fl.lookup(node)
391 n = fl.lookup(node)
423 node = hex(n)
392 node = hex(n)
424 changerev = fl.linkrev(n)
393 changerev = fl.linkrev(n)
425
394
426 cl = self.repo.changelog
395 cl = self.repo.changelog
427 cn = cl.node(changerev)
396 cn = cl.node(changerev)
428 cs = cl.read(cn)
397 cs = cl.read(cn)
429 mfn = cs[0]
398 mfn = cs[0]
430
399
431 def annotate(**map):
400 def annotate(**map):
432 parity = 1
401 parity = 1
433 last = None
402 last = None
434 for r, l in fl.annotate(n):
403 for r, l in fl.annotate(n):
435 try:
404 try:
436 cnode = ncache[r]
405 cnode = ncache[r]
437 except KeyError:
406 except KeyError:
438 cnode = ncache[r] = self.repo.changelog.node(r)
407 cnode = ncache[r] = self.repo.changelog.node(r)
439
408
440 try:
409 try:
441 name = bcache[r]
410 name = bcache[r]
442 except KeyError:
411 except KeyError:
443 cl = self.repo.changelog.read(cnode)
412 cl = self.repo.changelog.read(cnode)
444 bcache[r] = name = self.repo.ui.shortuser(cl[1])
413 bcache[r] = name = self.repo.ui.shortuser(cl[1])
445
414
446 if last != cnode:
415 if last != cnode:
447 parity = 1 - parity
416 parity = 1 - parity
448 last = cnode
417 last = cnode
449
418
450 yield {"parity": parity,
419 yield {"parity": parity,
451 "node": hex(cnode),
420 "node": hex(cnode),
452 "rev": r,
421 "rev": r,
453 "author": name,
422 "author": name,
454 "file": f,
423 "file": f,
455 "line": l}
424 "line": l}
456
425
457 yield self.t("fileannotate",
426 yield self.t("fileannotate",
458 file=f,
427 file=f,
459 filenode=node,
428 filenode=node,
460 annotate=annotate,
429 annotate=annotate,
461 path=up(f),
430 path=_up(f),
462 rev=changerev,
431 rev=changerev,
463 node=hex(cn),
432 node=hex(cn),
464 manifest=hex(mfn),
433 manifest=hex(mfn),
465 author=cs[1],
434 author=cs[1],
466 date=cs[2],
435 date=cs[2],
467 rename=self.renamelink(fl, n),
436 rename=self.renamelink(fl, n),
468 parent=self.siblings(fl.parents(n), fl.rev, file=f),
437 parent=self.siblings(fl.parents(n), fl.rev, file=f),
469 child=self.siblings(fl.children(n), fl.rev, file=f),
438 child=self.siblings(fl.children(n), fl.rev, file=f),
470 permissions=self.repo.manifest.readflags(mfn)[f])
439 permissions=self.repo.manifest.readflags(mfn)[f])
471
440
472 def manifest(self, mnode, path):
441 def manifest(self, mnode, path):
473 man = self.repo.manifest
442 man = self.repo.manifest
474 mn = man.lookup(mnode)
443 mn = man.lookup(mnode)
475 mnode = hex(mn)
444 mnode = hex(mn)
476 mf = man.read(mn)
445 mf = man.read(mn)
477 rev = man.rev(mn)
446 rev = man.rev(mn)
478 changerev = man.linkrev(mn)
447 changerev = man.linkrev(mn)
479 node = self.repo.changelog.node(changerev)
448 node = self.repo.changelog.node(changerev)
480 mff = man.readflags(mn)
449 mff = man.readflags(mn)
481
450
482 files = {}
451 files = {}
483
452
484 p = path[1:]
453 p = path[1:]
485 if p and p[-1] != "/":
454 if p and p[-1] != "/":
486 p += "/"
455 p += "/"
487 l = len(p)
456 l = len(p)
488
457
489 for f,n in mf.items():
458 for f,n in mf.items():
490 if f[:l] != p:
459 if f[:l] != p:
491 continue
460 continue
492 remain = f[l:]
461 remain = f[l:]
493 if "/" in remain:
462 if "/" in remain:
494 short = remain[:remain.find("/") + 1] # bleah
463 short = remain[:remain.find("/") + 1] # bleah
495 files[short] = (f, None)
464 files[short] = (f, None)
496 else:
465 else:
497 short = os.path.basename(remain)
466 short = os.path.basename(remain)
498 files[short] = (f, n)
467 files[short] = (f, n)
499
468
500 def filelist(**map):
469 def filelist(**map):
501 parity = 0
470 parity = 0
502 fl = files.keys()
471 fl = files.keys()
503 fl.sort()
472 fl.sort()
504 for f in fl:
473 for f in fl:
505 full, fnode = files[f]
474 full, fnode = files[f]
506 if not fnode:
475 if not fnode:
507 continue
476 continue
508
477
509 yield {"file": full,
478 yield {"file": full,
510 "manifest": mnode,
479 "manifest": mnode,
511 "filenode": hex(fnode),
480 "filenode": hex(fnode),
512 "parity": parity,
481 "parity": parity,
513 "basename": f,
482 "basename": f,
514 "permissions": mff[full]}
483 "permissions": mff[full]}
515 parity = 1 - parity
484 parity = 1 - parity
516
485
517 def dirlist(**map):
486 def dirlist(**map):
518 parity = 0
487 parity = 0
519 fl = files.keys()
488 fl = files.keys()
520 fl.sort()
489 fl.sort()
521 for f in fl:
490 for f in fl:
522 full, fnode = files[f]
491 full, fnode = files[f]
523 if fnode:
492 if fnode:
524 continue
493 continue
525
494
526 yield {"parity": parity,
495 yield {"parity": parity,
527 "path": os.path.join(path, f),
496 "path": os.path.join(path, f),
528 "manifest": mnode,
497 "manifest": mnode,
529 "basename": f[:-1]}
498 "basename": f[:-1]}
530 parity = 1 - parity
499 parity = 1 - parity
531
500
532 yield self.t("manifest",
501 yield self.t("manifest",
533 manifest=mnode,
502 manifest=mnode,
534 rev=rev,
503 rev=rev,
535 node=hex(node),
504 node=hex(node),
536 path=path,
505 path=path,
537 up=up(path),
506 up=_up(path),
538 fentries=filelist,
507 fentries=filelist,
539 dentries=dirlist,
508 dentries=dirlist,
540 archives=self.archivelist(hex(node)))
509 archives=self.archivelist(hex(node)))
541
510
542 def tags(self):
511 def tags(self):
543 cl = self.repo.changelog
512 cl = self.repo.changelog
544 mf = cl.read(cl.tip())[0]
513 mf = cl.read(cl.tip())[0]
545
514
546 i = self.repo.tagslist()
515 i = self.repo.tagslist()
547 i.reverse()
516 i.reverse()
548
517
549 def entries(notip=False, **map):
518 def entries(notip=False, **map):
550 parity = 0
519 parity = 0
551 for k,n in i:
520 for k,n in i:
552 if notip and k == "tip": continue
521 if notip and k == "tip": continue
553 yield {"parity": parity,
522 yield {"parity": parity,
554 "tag": k,
523 "tag": k,
555 "tagmanifest": hex(cl.read(n)[0]),
524 "tagmanifest": hex(cl.read(n)[0]),
556 "date": cl.read(n)[2],
525 "date": cl.read(n)[2],
557 "node": hex(n)}
526 "node": hex(n)}
558 parity = 1 - parity
527 parity = 1 - parity
559
528
560 yield self.t("tags",
529 yield self.t("tags",
561 manifest=hex(mf),
530 manifest=hex(mf),
562 entries=lambda **x: entries(False, **x),
531 entries=lambda **x: entries(False, **x),
563 entriesnotip=lambda **x: entries(True, **x))
532 entriesnotip=lambda **x: entries(True, **x))
564
533
565 def summary(self):
534 def summary(self):
566 cl = self.repo.changelog
535 cl = self.repo.changelog
567 mf = cl.read(cl.tip())[0]
536 mf = cl.read(cl.tip())[0]
568
537
569 i = self.repo.tagslist()
538 i = self.repo.tagslist()
570 i.reverse()
539 i.reverse()
571
540
572 def tagentries(**map):
541 def tagentries(**map):
573 parity = 0
542 parity = 0
574 count = 0
543 count = 0
575 for k,n in i:
544 for k,n in i:
576 if k == "tip": # skip tip
545 if k == "tip": # skip tip
577 continue;
546 continue;
578
547
579 count += 1
548 count += 1
580 if count > 10: # limit to 10 tags
549 if count > 10: # limit to 10 tags
581 break;
550 break;
582
551
583 c = cl.read(n)
552 c = cl.read(n)
584 m = c[0]
553 m = c[0]
585 t = c[2]
554 t = c[2]
586
555
587 yield self.t("tagentry",
556 yield self.t("tagentry",
588 parity = parity,
557 parity = parity,
589 tag = k,
558 tag = k,
590 node = hex(n),
559 node = hex(n),
591 date = t,
560 date = t,
592 tagmanifest = hex(m))
561 tagmanifest = hex(m))
593 parity = 1 - parity
562 parity = 1 - parity
594
563
595 def changelist(**map):
564 def changelist(**map):
596 parity = 0
565 parity = 0
597 cl = self.repo.changelog
566 cl = self.repo.changelog
598 l = [] # build a list in forward order for efficiency
567 l = [] # build a list in forward order for efficiency
599 for i in range(start, end):
568 for i in range(start, end):
600 n = cl.node(i)
569 n = cl.node(i)
601 changes = cl.read(n)
570 changes = cl.read(n)
602 hn = hex(n)
571 hn = hex(n)
603 t = changes[2]
572 t = changes[2]
604
573
605 l.insert(0, self.t(
574 l.insert(0, self.t(
606 'shortlogentry',
575 'shortlogentry',
607 parity = parity,
576 parity = parity,
608 author = changes[1],
577 author = changes[1],
609 manifest = hex(changes[0]),
578 manifest = hex(changes[0]),
610 desc = changes[4],
579 desc = changes[4],
611 date = t,
580 date = t,
612 rev = i,
581 rev = i,
613 node = hn))
582 node = hn))
614 parity = 1 - parity
583 parity = 1 - parity
615
584
616 yield l
585 yield l
617
586
618 cl = self.repo.changelog
587 cl = self.repo.changelog
619 mf = cl.read(cl.tip())[0]
588 mf = cl.read(cl.tip())[0]
620 count = cl.count()
589 count = cl.count()
621 start = max(0, count - self.maxchanges)
590 start = max(0, count - self.maxchanges)
622 end = min(count, start + self.maxchanges)
591 end = min(count, start + self.maxchanges)
623 pos = end - 1
592 pos = end - 1
624
593
625 yield self.t("summary",
594 yield self.t("summary",
626 desc = self.repo.ui.config("web", "description", "unknown"),
595 desc = self.repo.ui.config("web", "description", "unknown"),
627 owner = (self.repo.ui.config("ui", "username") or # preferred
596 owner = (self.repo.ui.config("ui", "username") or # preferred
628 self.repo.ui.config("web", "contact") or # deprecated
597 self.repo.ui.config("web", "contact") or # deprecated
629 self.repo.ui.config("web", "author", "unknown")), # also
598 self.repo.ui.config("web", "author", "unknown")), # also
630 lastchange = (0, 0), # FIXME
599 lastchange = (0, 0), # FIXME
631 manifest = hex(mf),
600 manifest = hex(mf),
632 tags = tagentries,
601 tags = tagentries,
633 shortlog = changelist)
602 shortlog = changelist)
634
603
635 def filediff(self, file, changeset):
604 def filediff(self, file, changeset):
636 cl = self.repo.changelog
605 cl = self.repo.changelog
637 n = self.repo.lookup(changeset)
606 n = self.repo.lookup(changeset)
638 changeset = hex(n)
607 changeset = hex(n)
639 p1 = cl.parents(n)[0]
608 p1 = cl.parents(n)[0]
640 cs = cl.read(n)
609 cs = cl.read(n)
641 mf = self.repo.manifest.read(cs[0])
610 mf = self.repo.manifest.read(cs[0])
642
611
643 def diff(**map):
612 def diff(**map):
644 yield self.diff(p1, n, [file])
613 yield self.diff(p1, n, [file])
645
614
646 yield self.t("filediff",
615 yield self.t("filediff",
647 file=file,
616 file=file,
648 filenode=hex(mf.get(file, nullid)),
617 filenode=hex(mf.get(file, nullid)),
649 node=changeset,
618 node=changeset,
650 rev=self.repo.changelog.rev(n),
619 rev=self.repo.changelog.rev(n),
651 parent=self.siblings(cl.parents(n), cl.rev),
620 parent=self.siblings(cl.parents(n), cl.rev),
652 child=self.siblings(cl.children(n), cl.rev),
621 child=self.siblings(cl.children(n), cl.rev),
653 diff=diff)
622 diff=diff)
654
623
655 archive_specs = {
624 archive_specs = {
656 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', 'x-bzip2'),
625 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', 'x-bzip2'),
657 'gz': ('application/x-tar', 'tgz', '.tar.gz', 'x-gzip'),
626 'gz': ('application/x-tar', 'tgz', '.tar.gz', 'x-gzip'),
658 'zip': ('application/zip', 'zip', '.zip', None),
627 'zip': ('application/zip', 'zip', '.zip', None),
659 }
628 }
660
629
661 def archive(self, req, cnode, type):
630 def archive(self, req, cnode, type):
662 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
631 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
663 name = "%s-%s" % (reponame, short(cnode))
632 name = "%s-%s" % (reponame, short(cnode))
664 mimetype, artype, extension, encoding = self.archive_specs[type]
633 mimetype, artype, extension, encoding = self.archive_specs[type]
665 headers = [('Content-type', mimetype),
634 headers = [('Content-type', mimetype),
666 ('Content-disposition', 'attachment; filename=%s%s' %
635 ('Content-disposition', 'attachment; filename=%s%s' %
667 (name, extension))]
636 (name, extension))]
668 if encoding:
637 if encoding:
669 headers.append(('Content-encoding', encoding))
638 headers.append(('Content-encoding', encoding))
670 req.header(headers)
639 req.header(headers)
671 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
640 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
672
641
673 # add tags to things
642 # add tags to things
674 # tags -> list of changesets corresponding to tags
643 # tags -> list of changesets corresponding to tags
675 # find tag, changeset, file
644 # find tag, changeset, file
676
645
677 def run(self, req=hgrequest()):
646 def run(self, req=hgrequest()):
678 def clean(path):
647 def clean(path):
679 p = util.normpath(path)
648 p = util.normpath(path)
680 if p[:2] == "..":
649 if p[:2] == "..":
681 raise "suspicious path"
650 raise "suspicious path"
682 return p
651 return p
683
652
684 def header(**map):
653 def header(**map):
685 yield self.t("header", **map)
654 yield self.t("header", **map)
686
655
687 def footer(**map):
656 def footer(**map):
688 yield self.t("footer",
657 yield self.t("footer",
689 motd=self.repo.ui.config("web", "motd", ""),
658 motd=self.repo.ui.config("web", "motd", ""),
690 **map)
659 **map)
691
660
692 def expand_form(form):
661 def expand_form(form):
693 shortcuts = {
662 shortcuts = {
694 'cl': [('cmd', ['changelog']), ('rev', None)],
663 'cl': [('cmd', ['changelog']), ('rev', None)],
695 'cs': [('cmd', ['changeset']), ('node', None)],
664 'cs': [('cmd', ['changeset']), ('node', None)],
696 'f': [('cmd', ['file']), ('filenode', None)],
665 'f': [('cmd', ['file']), ('filenode', None)],
697 'fl': [('cmd', ['filelog']), ('filenode', None)],
666 'fl': [('cmd', ['filelog']), ('filenode', None)],
698 'fd': [('cmd', ['filediff']), ('node', None)],
667 'fd': [('cmd', ['filediff']), ('node', None)],
699 'fa': [('cmd', ['annotate']), ('filenode', None)],
668 'fa': [('cmd', ['annotate']), ('filenode', None)],
700 'mf': [('cmd', ['manifest']), ('manifest', None)],
669 'mf': [('cmd', ['manifest']), ('manifest', None)],
701 'ca': [('cmd', ['archive']), ('node', None)],
670 'ca': [('cmd', ['archive']), ('node', None)],
702 'tags': [('cmd', ['tags'])],
671 'tags': [('cmd', ['tags'])],
703 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
672 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
704 'static': [('cmd', ['static']), ('file', None)]
673 'static': [('cmd', ['static']), ('file', None)]
705 }
674 }
706
675
707 for k in shortcuts.iterkeys():
676 for k in shortcuts.iterkeys():
708 if form.has_key(k):
677 if form.has_key(k):
709 for name, value in shortcuts[k]:
678 for name, value in shortcuts[k]:
710 if value is None:
679 if value is None:
711 value = form[k]
680 value = form[k]
712 form[name] = value
681 form[name] = value
713 del form[k]
682 del form[k]
714
683
715 self.refresh()
684 self.refresh()
716
685
717 expand_form(req.form)
686 expand_form(req.form)
718
687
719 t = self.repo.ui.config("web", "templates", templater.templatepath())
688 t = self.repo.ui.config("web", "templates", templater.templatepath())
720 static = self.repo.ui.config("web", "static", os.path.join(t,"static"))
689 static = self.repo.ui.config("web", "static", os.path.join(t,"static"))
721 m = os.path.join(t, "map")
690 m = os.path.join(t, "map")
722 style = self.repo.ui.config("web", "style", "")
691 style = self.repo.ui.config("web", "style", "")
723 if req.form.has_key('style'):
692 if req.form.has_key('style'):
724 style = req.form['style'][0]
693 style = req.form['style'][0]
725 if style:
694 if style:
726 b = os.path.basename("map-" + style)
695 b = os.path.basename("map-" + style)
727 p = os.path.join(t, b)
696 p = os.path.join(t, b)
728 if os.path.isfile(p):
697 if os.path.isfile(p):
729 m = p
698 m = p
730
699
731 port = req.env["SERVER_PORT"]
700 port = req.env["SERVER_PORT"]
732 port = port != "80" and (":" + port) or ""
701 port = port != "80" and (":" + port) or ""
733 uri = req.env["REQUEST_URI"]
702 uri = req.env["REQUEST_URI"]
734 if "?" in uri:
703 if "?" in uri:
735 uri = uri.split("?")[0]
704 uri = uri.split("?")[0]
736 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
705 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
737 if not self.reponame:
706 if not self.reponame:
738 self.reponame = (self.repo.ui.config("web", "name")
707 self.reponame = (self.repo.ui.config("web", "name")
739 or uri.strip('/') or self.repo.root)
708 or uri.strip('/') or self.repo.root)
740
709
741 self.t = templater.templater(m, templater.common_filters,
710 self.t = templater.templater(m, templater.common_filters,
742 defaults={"url": url,
711 defaults={"url": url,
743 "repo": self.reponame,
712 "repo": self.reponame,
744 "header": header,
713 "header": header,
745 "footer": footer,
714 "footer": footer,
746 })
715 })
747
716
748 if not req.form.has_key('cmd'):
717 if not req.form.has_key('cmd'):
749 req.form['cmd'] = [self.t.cache['default'],]
718 req.form['cmd'] = [self.t.cache['default'],]
750
719
751 cmd = req.form['cmd'][0]
720 cmd = req.form['cmd'][0]
752 if cmd == 'changelog':
721 if cmd == 'changelog':
753 hi = self.repo.changelog.count() - 1
722 hi = self.repo.changelog.count() - 1
754 if req.form.has_key('rev'):
723 if req.form.has_key('rev'):
755 hi = req.form['rev'][0]
724 hi = req.form['rev'][0]
756 try:
725 try:
757 hi = self.repo.changelog.rev(self.repo.lookup(hi))
726 hi = self.repo.changelog.rev(self.repo.lookup(hi))
758 except hg.RepoError:
727 except hg.RepoError:
759 req.write(self.search(hi)) # XXX redirect to 404 page?
728 req.write(self.search(hi)) # XXX redirect to 404 page?
760 return
729 return
761
730
762 req.write(self.changelog(hi))
731 req.write(self.changelog(hi))
763
732
764 elif cmd == 'changeset':
733 elif cmd == 'changeset':
765 req.write(self.changeset(req.form['node'][0]))
734 req.write(self.changeset(req.form['node'][0]))
766
735
767 elif cmd == 'manifest':
736 elif cmd == 'manifest':
768 req.write(self.manifest(req.form['manifest'][0],
737 req.write(self.manifest(req.form['manifest'][0],
769 clean(req.form['path'][0])))
738 clean(req.form['path'][0])))
770
739
771 elif cmd == 'tags':
740 elif cmd == 'tags':
772 req.write(self.tags())
741 req.write(self.tags())
773
742
774 elif cmd == 'summary':
743 elif cmd == 'summary':
775 req.write(self.summary())
744 req.write(self.summary())
776
745
777 elif cmd == 'filediff':
746 elif cmd == 'filediff':
778 req.write(self.filediff(clean(req.form['file'][0]),
747 req.write(self.filediff(clean(req.form['file'][0]),
779 req.form['node'][0]))
748 req.form['node'][0]))
780
749
781 elif cmd == 'file':
750 elif cmd == 'file':
782 req.write(self.filerevision(clean(req.form['file'][0]),
751 req.write(self.filerevision(clean(req.form['file'][0]),
783 req.form['filenode'][0]))
752 req.form['filenode'][0]))
784
753
785 elif cmd == 'annotate':
754 elif cmd == 'annotate':
786 req.write(self.fileannotate(clean(req.form['file'][0]),
755 req.write(self.fileannotate(clean(req.form['file'][0]),
787 req.form['filenode'][0]))
756 req.form['filenode'][0]))
788
757
789 elif cmd == 'filelog':
758 elif cmd == 'filelog':
790 req.write(self.filelog(clean(req.form['file'][0]),
759 req.write(self.filelog(clean(req.form['file'][0]),
791 req.form['filenode'][0]))
760 req.form['filenode'][0]))
792
761
793 elif cmd == 'heads':
762 elif cmd == 'heads':
794 req.httphdr("application/mercurial-0.1")
763 req.httphdr("application/mercurial-0.1")
795 h = self.repo.heads()
764 h = self.repo.heads()
796 req.write(" ".join(map(hex, h)) + "\n")
765 req.write(" ".join(map(hex, h)) + "\n")
797
766
798 elif cmd == 'branches':
767 elif cmd == 'branches':
799 req.httphdr("application/mercurial-0.1")
768 req.httphdr("application/mercurial-0.1")
800 nodes = []
769 nodes = []
801 if req.form.has_key('nodes'):
770 if req.form.has_key('nodes'):
802 nodes = map(bin, req.form['nodes'][0].split(" "))
771 nodes = map(bin, req.form['nodes'][0].split(" "))
803 for b in self.repo.branches(nodes):
772 for b in self.repo.branches(nodes):
804 req.write(" ".join(map(hex, b)) + "\n")
773 req.write(" ".join(map(hex, b)) + "\n")
805
774
806 elif cmd == 'between':
775 elif cmd == 'between':
807 req.httphdr("application/mercurial-0.1")
776 req.httphdr("application/mercurial-0.1")
808 nodes = []
777 nodes = []
809 if req.form.has_key('pairs'):
778 if req.form.has_key('pairs'):
810 pairs = [map(bin, p.split("-"))
779 pairs = [map(bin, p.split("-"))
811 for p in req.form['pairs'][0].split(" ")]
780 for p in req.form['pairs'][0].split(" ")]
812 for b in self.repo.between(pairs):
781 for b in self.repo.between(pairs):
813 req.write(" ".join(map(hex, b)) + "\n")
782 req.write(" ".join(map(hex, b)) + "\n")
814
783
815 elif cmd == 'changegroup':
784 elif cmd == 'changegroup':
816 req.httphdr("application/mercurial-0.1")
785 req.httphdr("application/mercurial-0.1")
817 nodes = []
786 nodes = []
818 if not self.allowpull:
787 if not self.allowpull:
819 return
788 return
820
789
821 if req.form.has_key('roots'):
790 if req.form.has_key('roots'):
822 nodes = map(bin, req.form['roots'][0].split(" "))
791 nodes = map(bin, req.form['roots'][0].split(" "))
823
792
824 z = zlib.compressobj()
793 z = zlib.compressobj()
825 f = self.repo.changegroup(nodes, 'serve')
794 f = self.repo.changegroup(nodes, 'serve')
826 while 1:
795 while 1:
827 chunk = f.read(4096)
796 chunk = f.read(4096)
828 if not chunk:
797 if not chunk:
829 break
798 break
830 req.write(z.compress(chunk))
799 req.write(z.compress(chunk))
831
800
832 req.write(z.flush())
801 req.write(z.flush())
833
802
834 elif cmd == 'archive':
803 elif cmd == 'archive':
835 changeset = self.repo.lookup(req.form['node'][0])
804 changeset = self.repo.lookup(req.form['node'][0])
836 type = req.form['type'][0]
805 type = req.form['type'][0]
837 if (type in self.archives and
806 if (type in self.archives and
838 self.repo.ui.configbool("web", "allow" + type, False)):
807 self.repo.ui.configbool("web", "allow" + type, False)):
839 self.archive(req, changeset, type)
808 self.archive(req, changeset, type)
840 return
809 return
841
810
842 req.write(self.t("error"))
811 req.write(self.t("error"))
843
812
844 elif cmd == 'static':
813 elif cmd == 'static':
845 fname = req.form['file'][0]
814 fname = req.form['file'][0]
846 req.write(staticfile(static, fname)
815 req.write(staticfile(static, fname)
847 or self.t("error", error="%r not found" % fname))
816 or self.t("error", error="%r not found" % fname))
848
817
849 else:
818 else:
850 req.write(self.t("error"))
819 req.write(self.t("error"))
851
852 # This is a stopgap
853 class hgwebdir(object):
854 def __init__(self, config):
855 def cleannames(items):
856 return [(name.strip(os.sep), path) for name, path in items]
857
858 self.motd = ""
859 self.repos_sorted = ('name', False)
860 if isinstance(config, (list, tuple)):
861 self.repos = cleannames(config)
862 self.repos_sorted = ('', False)
863 elif isinstance(config, dict):
864 self.repos = cleannames(config.items())
865 self.repos.sort()
866 else:
867 cp = ConfigParser.SafeConfigParser()
868 cp.read(config)
869 self.repos = []
870 if cp.has_section('web') and cp.has_option('web', 'motd'):
871 self.motd = cp.get('web', 'motd')
872 if cp.has_section('paths'):
873 self.repos.extend(cleannames(cp.items('paths')))
874 if cp.has_section('collections'):
875 for prefix, root in cp.items('collections'):
876 for path in util.walkrepos(root):
877 repo = os.path.normpath(path)
878 name = repo
879 if name.startswith(prefix):
880 name = name[len(prefix):]
881 self.repos.append((name.lstrip(os.sep), repo))
882 self.repos.sort()
883
884 def run(self, req=hgrequest()):
885 def header(**map):
886 yield tmpl("header", **map)
887
888 def footer(**map):
889 yield tmpl("footer", motd=self.motd, **map)
890
891 m = os.path.join(templater.templatepath(), "map")
892 tmpl = templater.templater(m, templater.common_filters,
893 defaults={"header": header,
894 "footer": footer})
895
896 def archivelist(ui, nodeid, url):
897 for i in ['zip', 'gz', 'bz2']:
898 if ui.configbool("web", "allow" + i, False):
899 yield {"type" : i, "node": nodeid, "url": url}
900
901 def entries(sortcolumn="", descending=False, **map):
902 rows = []
903 parity = 0
904 for name, path in self.repos:
905 u = ui.ui()
906 try:
907 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
908 except IOError:
909 pass
910 get = u.config
911
912 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
913 .replace("//", "/"))
914
915 # update time with local timezone
916 try:
917 d = (get_mtime(path), util.makedate()[1])
918 except OSError:
919 continue
920
921 contact = (get("ui", "username") or # preferred
922 get("web", "contact") or # deprecated
923 get("web", "author", "")) # also
924 description = get("web", "description", "")
925 name = get("web", "name", name)
926 row = dict(contact=contact or "unknown",
927 contact_sort=contact.upper() or "unknown",
928 name=name,
929 name_sort=name,
930 url=url,
931 description=description or "unknown",
932 description_sort=description.upper() or "unknown",
933 lastchange=d,
934 lastchange_sort=d[1]-d[0],
935 archives=archivelist(u, "tip", url))
936 if (not sortcolumn
937 or (sortcolumn, descending) == self.repos_sorted):
938 # fast path for unsorted output
939 row['parity'] = parity
940 parity = 1 - parity
941 yield row
942 else:
943 rows.append((row["%s_sort" % sortcolumn], row))
944 if rows:
945 rows.sort()
946 if descending:
947 rows.reverse()
948 for key, row in rows:
949 row['parity'] = parity
950 parity = 1 - parity
951 yield row
952
953 virtual = req.env.get("PATH_INFO", "").strip('/')
954 if virtual:
955 real = dict(self.repos).get(virtual)
956 if real:
957 try:
958 hgweb(real).run(req)
959 except IOError, inst:
960 req.write(tmpl("error", error=inst.strerror))
961 except hg.RepoError, inst:
962 req.write(tmpl("error", error=str(inst)))
963 else:
964 req.write(tmpl("notfound", repo=virtual))
965 else:
966 if req.form.has_key('static'):
967 static = os.path.join(templater.templatepath(), "static")
968 fname = req.form['static'][0]
969 req.write(staticfile(static, fname)
970 or tmpl("error", error="%r not found" % fname))
971 else:
972 sortable = ["name", "description", "contact", "lastchange"]
973 sortcolumn, descending = self.repos_sorted
974 if req.form.has_key('sort'):
975 sortcolumn = req.form['sort'][0]
976 descending = sortcolumn.startswith('-')
977 if descending:
978 sortcolumn = sortcolumn[1:]
979 if sortcolumn not in sortable:
980 sortcolumn = ""
981
982 sort = [("sort_%s" % column,
983 "%s%s" % ((not descending and column == sortcolumn)
984 and "-" or "", column))
985 for column in sortable]
986 req.write(tmpl("index", entries=entries,
987 sortcolumn=sortcolumn, descending=descending,
988 **dict(sort)))
This diff has been collapsed as it changes many lines, (842 lines changed) Show them Hide them
@@ -1,988 +1,152 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
10 import mimetypes
11 from mercurial.demandload import demandload
10 from mercurial.demandload import demandload
12 demandload(globals(), "time re socket zlib errno ConfigParser tempfile")
11 demandload(globals(), "ConfigParser")
13 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater")
12 demandload(globals(), "mercurial:ui,hg,util,templater")
14 demandload(globals(), "mercurial.hgweb.request:hgrequest")
13 demandload(globals(), "mercurial.hgweb.request:hgrequest")
15 demandload(globals(), "mercurial.hgweb.server:create_server")
16 from mercurial.node import *
17 from mercurial.i18n import gettext as _
14 from mercurial.i18n import gettext as _
18
15
19 def up(p):
20 if p[0] != "/":
21 p = "/" + p
22 if p[-1] == "/":
23 p = p[:-1]
24 up = os.path.dirname(p)
25 if up == "/":
26 return "/"
27 return up + "/"
28
29 def get_mtime(repo_path):
30 hg_path = os.path.join(repo_path, ".hg")
31 cl_path = os.path.join(hg_path, "00changelog.i")
32 if os.path.exists(os.path.join(cl_path)):
33 return os.stat(cl_path).st_mtime
34 else:
35 return os.stat(hg_path).st_mtime
36
37 def staticfile(directory, fname):
38 """return a file inside directory with guessed content-type header
39
40 fname always uses '/' as directory separator and isn't allowed to
41 contain unusual path components.
42 Content-type is guessed using the mimetypes module.
43 Return an empty string if fname is illegal or file not found.
44
45 """
46 parts = fname.split('/')
47 path = directory
48 for part in parts:
49 if (part in ('', os.curdir, os.pardir) or
50 os.sep in part or os.altsep is not None and os.altsep in part):
51 return ""
52 path = os.path.join(path, part)
53 try:
54 os.stat(path)
55 ct = mimetypes.guess_type(path)[0] or "text/plain"
56 return "Content-type: %s\n\n%s" % (ct, file(path).read())
57 except (TypeError, OSError):
58 # illegal fname or unreadable file
59 return ""
60
61 class hgweb(object):
62 def __init__(self, repo, name=None):
63 if type(repo) == type(""):
64 self.repo = hg.repository(ui.ui(), repo)
65 else:
66 self.repo = repo
67
68 self.mtime = -1
69 self.reponame = name
70 self.archives = 'zip', 'gz', 'bz2'
71
72 def refresh(self):
73 mtime = get_mtime(self.repo.root)
74 if mtime != self.mtime:
75 self.mtime = mtime
76 self.repo = hg.repository(self.repo.ui, self.repo.root)
77 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
78 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
79 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
80
81 def archivelist(self, nodeid):
82 for i in self.archives:
83 if self.repo.ui.configbool("web", "allow" + i, False):
84 yield {"type" : i, "node" : nodeid, "url": ""}
85
86 def listfiles(self, files, mf):
87 for f in files[:self.maxfiles]:
88 yield self.t("filenodelink", node=hex(mf[f]), file=f)
89 if len(files) > self.maxfiles:
90 yield self.t("fileellipses")
91
92 def listfilediffs(self, files, changeset):
93 for f in files[:self.maxfiles]:
94 yield self.t("filedifflink", node=hex(changeset), file=f)
95 if len(files) > self.maxfiles:
96 yield self.t("fileellipses")
97
98 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
99 if not rev:
100 rev = lambda x: ""
101 siblings = [s for s in siblings if s != nullid]
102 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
103 return
104 for s in siblings:
105 yield dict(node=hex(s), rev=rev(s), **args)
106
107 def renamelink(self, fl, node):
108 r = fl.renamed(node)
109 if r:
110 return [dict(file=r[0], node=hex(r[1]))]
111 return []
112
113 def showtag(self, t1, node=nullid, **args):
114 for t in self.repo.nodetags(node):
115 yield self.t(t1, tag=t, **args)
116
117 def diff(self, node1, node2, files):
118 def filterfiles(filters, files):
119 l = [x for x in files if x in filters]
120
121 for t in filters:
122 if t and t[-1] != os.sep:
123 t += os.sep
124 l += [x for x in files if x.startswith(t)]
125 return l
126
127 parity = [0]
128 def diffblock(diff, f, fn):
129 yield self.t("diffblock",
130 lines=prettyprintlines(diff),
131 parity=parity[0],
132 file=f,
133 filenode=hex(fn or nullid))
134 parity[0] = 1 - parity[0]
135
136 def prettyprintlines(diff):
137 for l in diff.splitlines(1):
138 if l.startswith('+'):
139 yield self.t("difflineplus", line=l)
140 elif l.startswith('-'):
141 yield self.t("difflineminus", line=l)
142 elif l.startswith('@'):
143 yield self.t("difflineat", line=l)
144 else:
145 yield self.t("diffline", line=l)
146
147 r = self.repo
148 cl = r.changelog
149 mf = r.manifest
150 change1 = cl.read(node1)
151 change2 = cl.read(node2)
152 mmap1 = mf.read(change1[0])
153 mmap2 = mf.read(change2[0])
154 date1 = util.datestr(change1[2])
155 date2 = util.datestr(change2[2])
156
157 modified, added, removed, deleted, unknown = r.changes(node1, node2)
158 if files:
159 modified, added, removed = map(lambda x: filterfiles(files, x),
160 (modified, added, removed))
161
162 diffopts = self.repo.ui.diffopts()
163 showfunc = diffopts['showfunc']
164 ignorews = diffopts['ignorews']
165 for f in modified:
166 to = r.file(f).read(mmap1[f])
167 tn = r.file(f).read(mmap2[f])
168 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
169 showfunc=showfunc, ignorews=ignorews), f, tn)
170 for f in added:
171 to = None
172 tn = r.file(f).read(mmap2[f])
173 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
174 showfunc=showfunc, ignorews=ignorews), f, tn)
175 for f in removed:
176 to = r.file(f).read(mmap1[f])
177 tn = None
178 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
179 showfunc=showfunc, ignorews=ignorews), f, tn)
180
181 def changelog(self, pos):
182 def changenav(**map):
183 def seq(factor, maxchanges=None):
184 if maxchanges:
185 yield maxchanges
186 if maxchanges >= 20 and maxchanges <= 40:
187 yield 50
188 else:
189 yield 1 * factor
190 yield 3 * factor
191 for f in seq(factor * 10):
192 yield f
193
194 l = []
195 last = 0
196 for f in seq(1, self.maxchanges):
197 if f < self.maxchanges or f <= last:
198 continue
199 if f > count:
200 break
201 last = f
202 r = "%d" % f
203 if pos + f < count:
204 l.append(("+" + r, pos + f))
205 if pos - f >= 0:
206 l.insert(0, ("-" + r, pos - f))
207
208 yield {"rev": 0, "label": "(0)"}
209
210 for label, rev in l:
211 yield {"label": label, "rev": rev}
212
213 yield {"label": "tip", "rev": "tip"}
214
215 def changelist(**map):
216 parity = (start - end) & 1
217 cl = self.repo.changelog
218 l = [] # build a list in forward order for efficiency
219 for i in range(start, end):
220 n = cl.node(i)
221 changes = cl.read(n)
222 hn = hex(n)
223
224 l.insert(0, {"parity": parity,
225 "author": changes[1],
226 "parent": self.siblings(cl.parents(n), cl.rev,
227 cl.rev(n) - 1),
228 "child": self.siblings(cl.children(n), cl.rev,
229 cl.rev(n) + 1),
230 "changelogtag": self.showtag("changelogtag",n),
231 "manifest": hex(changes[0]),
232 "desc": changes[4],
233 "date": changes[2],
234 "files": self.listfilediffs(changes[3], n),
235 "rev": i,
236 "node": hn})
237 parity = 1 - parity
238
239 for e in l:
240 yield e
241
242 cl = self.repo.changelog
243 mf = cl.read(cl.tip())[0]
244 count = cl.count()
245 start = max(0, pos - self.maxchanges + 1)
246 end = min(count, start + self.maxchanges)
247 pos = end - 1
248
249 yield self.t('changelog',
250 changenav=changenav,
251 manifest=hex(mf),
252 rev=pos, changesets=count, entries=changelist,
253 archives=self.archivelist("tip"))
254
255 def search(self, query):
256
257 def changelist(**map):
258 cl = self.repo.changelog
259 count = 0
260 qw = query.lower().split()
261
262 def revgen():
263 for i in range(cl.count() - 1, 0, -100):
264 l = []
265 for j in range(max(0, i - 100), i):
266 n = cl.node(j)
267 changes = cl.read(n)
268 l.append((n, j, changes))
269 l.reverse()
270 for e in l:
271 yield e
272
273 for n, i, changes in revgen():
274 miss = 0
275 for q in qw:
276 if not (q in changes[1].lower() or
277 q in changes[4].lower() or
278 q in " ".join(changes[3][:20]).lower()):
279 miss = 1
280 break
281 if miss:
282 continue
283
284 count += 1
285 hn = hex(n)
286
287 yield self.t('searchentry',
288 parity=count & 1,
289 author=changes[1],
290 parent=self.siblings(cl.parents(n), cl.rev),
291 child=self.siblings(cl.children(n), cl.rev),
292 changelogtag=self.showtag("changelogtag",n),
293 manifest=hex(changes[0]),
294 desc=changes[4],
295 date=changes[2],
296 files=self.listfilediffs(changes[3], n),
297 rev=i,
298 node=hn)
299
300 if count >= self.maxchanges:
301 break
302
303 cl = self.repo.changelog
304 mf = cl.read(cl.tip())[0]
305
306 yield self.t('search',
307 query=query,
308 manifest=hex(mf),
309 entries=changelist)
310
311 def changeset(self, nodeid):
312 cl = self.repo.changelog
313 n = self.repo.lookup(nodeid)
314 nodeid = hex(n)
315 changes = cl.read(n)
316 p1 = cl.parents(n)[0]
317
318 files = []
319 mf = self.repo.manifest.read(changes[0])
320 for f in changes[3]:
321 files.append(self.t("filenodelink",
322 filenode=hex(mf.get(f, nullid)), file=f))
323
324 def diff(**map):
325 yield self.diff(p1, n, None)
326
327 yield self.t('changeset',
328 diff=diff,
329 rev=cl.rev(n),
330 node=nodeid,
331 parent=self.siblings(cl.parents(n), cl.rev),
332 child=self.siblings(cl.children(n), cl.rev),
333 changesettag=self.showtag("changesettag",n),
334 manifest=hex(changes[0]),
335 author=changes[1],
336 desc=changes[4],
337 date=changes[2],
338 files=files,
339 archives=self.archivelist(nodeid))
340
341 def filelog(self, f, filenode):
342 cl = self.repo.changelog
343 fl = self.repo.file(f)
344 filenode = hex(fl.lookup(filenode))
345 count = fl.count()
346
347 def entries(**map):
348 l = []
349 parity = (count - 1) & 1
350
351 for i in range(count):
352 n = fl.node(i)
353 lr = fl.linkrev(n)
354 cn = cl.node(lr)
355 cs = cl.read(cl.node(lr))
356
357 l.insert(0, {"parity": parity,
358 "filenode": hex(n),
359 "filerev": i,
360 "file": f,
361 "node": hex(cn),
362 "author": cs[1],
363 "date": cs[2],
364 "rename": self.renamelink(fl, n),
365 "parent": self.siblings(fl.parents(n),
366 fl.rev, file=f),
367 "child": self.siblings(fl.children(n),
368 fl.rev, file=f),
369 "desc": cs[4]})
370 parity = 1 - parity
371
372 for e in l:
373 yield e
374
375 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
376
377 def filerevision(self, f, node):
378 fl = self.repo.file(f)
379 n = fl.lookup(node)
380 node = hex(n)
381 text = fl.read(n)
382 changerev = fl.linkrev(n)
383 cl = self.repo.changelog
384 cn = cl.node(changerev)
385 cs = cl.read(cn)
386 mfn = cs[0]
387
388 mt = mimetypes.guess_type(f)[0]
389 rawtext = text
390 if util.binary(text):
391 mt = mt or 'application/octet-stream'
392 text = "(binary:%s)" % mt
393 mt = mt or 'text/plain'
394
395 def lines():
396 for l, t in enumerate(text.splitlines(1)):
397 yield {"line": t,
398 "linenumber": "% 6d" % (l + 1),
399 "parity": l & 1}
400
401 yield self.t("filerevision",
402 file=f,
403 filenode=node,
404 path=up(f),
405 text=lines(),
406 raw=rawtext,
407 mimetype=mt,
408 rev=changerev,
409 node=hex(cn),
410 manifest=hex(mfn),
411 author=cs[1],
412 date=cs[2],
413 parent=self.siblings(fl.parents(n), fl.rev, file=f),
414 child=self.siblings(fl.children(n), fl.rev, file=f),
415 rename=self.renamelink(fl, n),
416 permissions=self.repo.manifest.readflags(mfn)[f])
417
418 def fileannotate(self, f, node):
419 bcache = {}
420 ncache = {}
421 fl = self.repo.file(f)
422 n = fl.lookup(node)
423 node = hex(n)
424 changerev = fl.linkrev(n)
425
426 cl = self.repo.changelog
427 cn = cl.node(changerev)
428 cs = cl.read(cn)
429 mfn = cs[0]
430
431 def annotate(**map):
432 parity = 1
433 last = None
434 for r, l in fl.annotate(n):
435 try:
436 cnode = ncache[r]
437 except KeyError:
438 cnode = ncache[r] = self.repo.changelog.node(r)
439
440 try:
441 name = bcache[r]
442 except KeyError:
443 cl = self.repo.changelog.read(cnode)
444 bcache[r] = name = self.repo.ui.shortuser(cl[1])
445
446 if last != cnode:
447 parity = 1 - parity
448 last = cnode
449
450 yield {"parity": parity,
451 "node": hex(cnode),
452 "rev": r,
453 "author": name,
454 "file": f,
455 "line": l}
456
457 yield self.t("fileannotate",
458 file=f,
459 filenode=node,
460 annotate=annotate,
461 path=up(f),
462 rev=changerev,
463 node=hex(cn),
464 manifest=hex(mfn),
465 author=cs[1],
466 date=cs[2],
467 rename=self.renamelink(fl, n),
468 parent=self.siblings(fl.parents(n), fl.rev, file=f),
469 child=self.siblings(fl.children(n), fl.rev, file=f),
470 permissions=self.repo.manifest.readflags(mfn)[f])
471
472 def manifest(self, mnode, path):
473 man = self.repo.manifest
474 mn = man.lookup(mnode)
475 mnode = hex(mn)
476 mf = man.read(mn)
477 rev = man.rev(mn)
478 changerev = man.linkrev(mn)
479 node = self.repo.changelog.node(changerev)
480 mff = man.readflags(mn)
481
482 files = {}
483
484 p = path[1:]
485 if p and p[-1] != "/":
486 p += "/"
487 l = len(p)
488
489 for f,n in mf.items():
490 if f[:l] != p:
491 continue
492 remain = f[l:]
493 if "/" in remain:
494 short = remain[:remain.find("/") + 1] # bleah
495 files[short] = (f, None)
496 else:
497 short = os.path.basename(remain)
498 files[short] = (f, n)
499
500 def filelist(**map):
501 parity = 0
502 fl = files.keys()
503 fl.sort()
504 for f in fl:
505 full, fnode = files[f]
506 if not fnode:
507 continue
508
509 yield {"file": full,
510 "manifest": mnode,
511 "filenode": hex(fnode),
512 "parity": parity,
513 "basename": f,
514 "permissions": mff[full]}
515 parity = 1 - parity
516
517 def dirlist(**map):
518 parity = 0
519 fl = files.keys()
520 fl.sort()
521 for f in fl:
522 full, fnode = files[f]
523 if fnode:
524 continue
525
526 yield {"parity": parity,
527 "path": os.path.join(path, f),
528 "manifest": mnode,
529 "basename": f[:-1]}
530 parity = 1 - parity
531
532 yield self.t("manifest",
533 manifest=mnode,
534 rev=rev,
535 node=hex(node),
536 path=path,
537 up=up(path),
538 fentries=filelist,
539 dentries=dirlist,
540 archives=self.archivelist(hex(node)))
541
542 def tags(self):
543 cl = self.repo.changelog
544 mf = cl.read(cl.tip())[0]
545
546 i = self.repo.tagslist()
547 i.reverse()
548
549 def entries(notip=False, **map):
550 parity = 0
551 for k,n in i:
552 if notip and k == "tip": continue
553 yield {"parity": parity,
554 "tag": k,
555 "tagmanifest": hex(cl.read(n)[0]),
556 "date": cl.read(n)[2],
557 "node": hex(n)}
558 parity = 1 - parity
559
560 yield self.t("tags",
561 manifest=hex(mf),
562 entries=lambda **x: entries(False, **x),
563 entriesnotip=lambda **x: entries(True, **x))
564
565 def summary(self):
566 cl = self.repo.changelog
567 mf = cl.read(cl.tip())[0]
568
569 i = self.repo.tagslist()
570 i.reverse()
571
572 def tagentries(**map):
573 parity = 0
574 count = 0
575 for k,n in i:
576 if k == "tip": # skip tip
577 continue;
578
579 count += 1
580 if count > 10: # limit to 10 tags
581 break;
582
583 c = cl.read(n)
584 m = c[0]
585 t = c[2]
586
587 yield self.t("tagentry",
588 parity = parity,
589 tag = k,
590 node = hex(n),
591 date = t,
592 tagmanifest = hex(m))
593 parity = 1 - parity
594
595 def changelist(**map):
596 parity = 0
597 cl = self.repo.changelog
598 l = [] # build a list in forward order for efficiency
599 for i in range(start, end):
600 n = cl.node(i)
601 changes = cl.read(n)
602 hn = hex(n)
603 t = changes[2]
604
605 l.insert(0, self.t(
606 'shortlogentry',
607 parity = parity,
608 author = changes[1],
609 manifest = hex(changes[0]),
610 desc = changes[4],
611 date = t,
612 rev = i,
613 node = hn))
614 parity = 1 - parity
615
616 yield l
617
618 cl = self.repo.changelog
619 mf = cl.read(cl.tip())[0]
620 count = cl.count()
621 start = max(0, count - self.maxchanges)
622 end = min(count, start + self.maxchanges)
623 pos = end - 1
624
625 yield self.t("summary",
626 desc = self.repo.ui.config("web", "description", "unknown"),
627 owner = (self.repo.ui.config("ui", "username") or # preferred
628 self.repo.ui.config("web", "contact") or # deprecated
629 self.repo.ui.config("web", "author", "unknown")), # also
630 lastchange = (0, 0), # FIXME
631 manifest = hex(mf),
632 tags = tagentries,
633 shortlog = changelist)
634
635 def filediff(self, file, changeset):
636 cl = self.repo.changelog
637 n = self.repo.lookup(changeset)
638 changeset = hex(n)
639 p1 = cl.parents(n)[0]
640 cs = cl.read(n)
641 mf = self.repo.manifest.read(cs[0])
642
643 def diff(**map):
644 yield self.diff(p1, n, [file])
645
646 yield self.t("filediff",
647 file=file,
648 filenode=hex(mf.get(file, nullid)),
649 node=changeset,
650 rev=self.repo.changelog.rev(n),
651 parent=self.siblings(cl.parents(n), cl.rev),
652 child=self.siblings(cl.children(n), cl.rev),
653 diff=diff)
654
655 archive_specs = {
656 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', 'x-bzip2'),
657 'gz': ('application/x-tar', 'tgz', '.tar.gz', 'x-gzip'),
658 'zip': ('application/zip', 'zip', '.zip', None),
659 }
660
661 def archive(self, req, cnode, type):
662 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
663 name = "%s-%s" % (reponame, short(cnode))
664 mimetype, artype, extension, encoding = self.archive_specs[type]
665 headers = [('Content-type', mimetype),
666 ('Content-disposition', 'attachment; filename=%s%s' %
667 (name, extension))]
668 if encoding:
669 headers.append(('Content-encoding', encoding))
670 req.header(headers)
671 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
672
673 # add tags to things
674 # tags -> list of changesets corresponding to tags
675 # find tag, changeset, file
676
677 def run(self, req=hgrequest()):
678 def clean(path):
679 p = util.normpath(path)
680 if p[:2] == "..":
681 raise "suspicious path"
682 return p
683
684 def header(**map):
685 yield self.t("header", **map)
686
687 def footer(**map):
688 yield self.t("footer",
689 motd=self.repo.ui.config("web", "motd", ""),
690 **map)
691
692 def expand_form(form):
693 shortcuts = {
694 'cl': [('cmd', ['changelog']), ('rev', None)],
695 'cs': [('cmd', ['changeset']), ('node', None)],
696 'f': [('cmd', ['file']), ('filenode', None)],
697 'fl': [('cmd', ['filelog']), ('filenode', None)],
698 'fd': [('cmd', ['filediff']), ('node', None)],
699 'fa': [('cmd', ['annotate']), ('filenode', None)],
700 'mf': [('cmd', ['manifest']), ('manifest', None)],
701 'ca': [('cmd', ['archive']), ('node', None)],
702 'tags': [('cmd', ['tags'])],
703 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
704 'static': [('cmd', ['static']), ('file', None)]
705 }
706
707 for k in shortcuts.iterkeys():
708 if form.has_key(k):
709 for name, value in shortcuts[k]:
710 if value is None:
711 value = form[k]
712 form[name] = value
713 del form[k]
714
715 self.refresh()
716
717 expand_form(req.form)
718
719 t = self.repo.ui.config("web", "templates", templater.templatepath())
720 static = self.repo.ui.config("web", "static", os.path.join(t,"static"))
721 m = os.path.join(t, "map")
722 style = self.repo.ui.config("web", "style", "")
723 if req.form.has_key('style'):
724 style = req.form['style'][0]
725 if style:
726 b = os.path.basename("map-" + style)
727 p = os.path.join(t, b)
728 if os.path.isfile(p):
729 m = p
730
731 port = req.env["SERVER_PORT"]
732 port = port != "80" and (":" + port) or ""
733 uri = req.env["REQUEST_URI"]
734 if "?" in uri:
735 uri = uri.split("?")[0]
736 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
737 if not self.reponame:
738 self.reponame = (self.repo.ui.config("web", "name")
739 or uri.strip('/') or self.repo.root)
740
741 self.t = templater.templater(m, templater.common_filters,
742 defaults={"url": url,
743 "repo": self.reponame,
744 "header": header,
745 "footer": footer,
746 })
747
748 if not req.form.has_key('cmd'):
749 req.form['cmd'] = [self.t.cache['default'],]
750
751 cmd = req.form['cmd'][0]
752 if cmd == 'changelog':
753 hi = self.repo.changelog.count() - 1
754 if req.form.has_key('rev'):
755 hi = req.form['rev'][0]
756 try:
757 hi = self.repo.changelog.rev(self.repo.lookup(hi))
758 except hg.RepoError:
759 req.write(self.search(hi)) # XXX redirect to 404 page?
760 return
761
762 req.write(self.changelog(hi))
763
764 elif cmd == 'changeset':
765 req.write(self.changeset(req.form['node'][0]))
766
767 elif cmd == 'manifest':
768 req.write(self.manifest(req.form['manifest'][0],
769 clean(req.form['path'][0])))
770
771 elif cmd == 'tags':
772 req.write(self.tags())
773
774 elif cmd == 'summary':
775 req.write(self.summary())
776
777 elif cmd == 'filediff':
778 req.write(self.filediff(clean(req.form['file'][0]),
779 req.form['node'][0]))
780
781 elif cmd == 'file':
782 req.write(self.filerevision(clean(req.form['file'][0]),
783 req.form['filenode'][0]))
784
785 elif cmd == 'annotate':
786 req.write(self.fileannotate(clean(req.form['file'][0]),
787 req.form['filenode'][0]))
788
789 elif cmd == 'filelog':
790 req.write(self.filelog(clean(req.form['file'][0]),
791 req.form['filenode'][0]))
792
793 elif cmd == 'heads':
794 req.httphdr("application/mercurial-0.1")
795 h = self.repo.heads()
796 req.write(" ".join(map(hex, h)) + "\n")
797
798 elif cmd == 'branches':
799 req.httphdr("application/mercurial-0.1")
800 nodes = []
801 if req.form.has_key('nodes'):
802 nodes = map(bin, req.form['nodes'][0].split(" "))
803 for b in self.repo.branches(nodes):
804 req.write(" ".join(map(hex, b)) + "\n")
805
806 elif cmd == 'between':
807 req.httphdr("application/mercurial-0.1")
808 nodes = []
809 if req.form.has_key('pairs'):
810 pairs = [map(bin, p.split("-"))
811 for p in req.form['pairs'][0].split(" ")]
812 for b in self.repo.between(pairs):
813 req.write(" ".join(map(hex, b)) + "\n")
814
815 elif cmd == 'changegroup':
816 req.httphdr("application/mercurial-0.1")
817 nodes = []
818 if not self.allowpull:
819 return
820
821 if req.form.has_key('roots'):
822 nodes = map(bin, req.form['roots'][0].split(" "))
823
824 z = zlib.compressobj()
825 f = self.repo.changegroup(nodes, 'serve')
826 while 1:
827 chunk = f.read(4096)
828 if not chunk:
829 break
830 req.write(z.compress(chunk))
831
832 req.write(z.flush())
833
834 elif cmd == 'archive':
835 changeset = self.repo.lookup(req.form['node'][0])
836 type = req.form['type'][0]
837 if (type in self.archives and
838 self.repo.ui.configbool("web", "allow" + type, False)):
839 self.archive(req, changeset, type)
840 return
841
842 req.write(self.t("error"))
843
844 elif cmd == 'static':
845 fname = req.form['file'][0]
846 req.write(staticfile(static, fname)
847 or self.t("error", error="%r not found" % fname))
848
849 else:
850 req.write(self.t("error"))
851
852 # This is a stopgap
16 # This is a stopgap
853 class hgwebdir(object):
17 class hgwebdir(object):
854 def __init__(self, config):
18 def __init__(self, config):
855 def cleannames(items):
19 def cleannames(items):
856 return [(name.strip(os.sep), path) for name, path in items]
20 return [(name.strip(os.sep), path) for name, path in items]
857
21
858 self.motd = ""
22 self.motd = ""
859 self.repos_sorted = ('name', False)
23 self.repos_sorted = ('name', False)
860 if isinstance(config, (list, tuple)):
24 if isinstance(config, (list, tuple)):
861 self.repos = cleannames(config)
25 self.repos = cleannames(config)
862 self.repos_sorted = ('', False)
26 self.repos_sorted = ('', False)
863 elif isinstance(config, dict):
27 elif isinstance(config, dict):
864 self.repos = cleannames(config.items())
28 self.repos = cleannames(config.items())
865 self.repos.sort()
29 self.repos.sort()
866 else:
30 else:
867 cp = ConfigParser.SafeConfigParser()
31 cp = ConfigParser.SafeConfigParser()
868 cp.read(config)
32 cp.read(config)
869 self.repos = []
33 self.repos = []
870 if cp.has_section('web') and cp.has_option('web', 'motd'):
34 if cp.has_section('web') and cp.has_option('web', 'motd'):
871 self.motd = cp.get('web', 'motd')
35 self.motd = cp.get('web', 'motd')
872 if cp.has_section('paths'):
36 if cp.has_section('paths'):
873 self.repos.extend(cleannames(cp.items('paths')))
37 self.repos.extend(cleannames(cp.items('paths')))
874 if cp.has_section('collections'):
38 if cp.has_section('collections'):
875 for prefix, root in cp.items('collections'):
39 for prefix, root in cp.items('collections'):
876 for path in util.walkrepos(root):
40 for path in util.walkrepos(root):
877 repo = os.path.normpath(path)
41 repo = os.path.normpath(path)
878 name = repo
42 name = repo
879 if name.startswith(prefix):
43 if name.startswith(prefix):
880 name = name[len(prefix):]
44 name = name[len(prefix):]
881 self.repos.append((name.lstrip(os.sep), repo))
45 self.repos.append((name.lstrip(os.sep), repo))
882 self.repos.sort()
46 self.repos.sort()
883
47
884 def run(self, req=hgrequest()):
48 def run(self, req=hgrequest()):
885 def header(**map):
49 def header(**map):
886 yield tmpl("header", **map)
50 yield tmpl("header", **map)
887
51
888 def footer(**map):
52 def footer(**map):
889 yield tmpl("footer", motd=self.motd, **map)
53 yield tmpl("footer", motd=self.motd, **map)
890
54
891 m = os.path.join(templater.templatepath(), "map")
55 m = os.path.join(templater.templatepath(), "map")
892 tmpl = templater.templater(m, templater.common_filters,
56 tmpl = templater.templater(m, templater.common_filters,
893 defaults={"header": header,
57 defaults={"header": header,
894 "footer": footer})
58 "footer": footer})
895
59
896 def archivelist(ui, nodeid, url):
60 def archivelist(ui, nodeid, url):
897 for i in ['zip', 'gz', 'bz2']:
61 for i in ['zip', 'gz', 'bz2']:
898 if ui.configbool("web", "allow" + i, False):
62 if ui.configbool("web", "allow" + i, False):
899 yield {"type" : i, "node": nodeid, "url": url}
63 yield {"type" : i, "node": nodeid, "url": url}
900
64
901 def entries(sortcolumn="", descending=False, **map):
65 def entries(sortcolumn="", descending=False, **map):
902 rows = []
66 rows = []
903 parity = 0
67 parity = 0
904 for name, path in self.repos:
68 for name, path in self.repos:
905 u = ui.ui()
69 u = ui.ui()
906 try:
70 try:
907 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
71 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
908 except IOError:
72 except IOError:
909 pass
73 pass
910 get = u.config
74 get = u.config
911
75
912 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
76 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
913 .replace("//", "/"))
77 .replace("//", "/"))
914
78
915 # update time with local timezone
79 # update time with local timezone
916 try:
80 try:
917 d = (get_mtime(path), util.makedate()[1])
81 d = (get_mtime(path), util.makedate()[1])
918 except OSError:
82 except OSError:
919 continue
83 continue
920
84
921 contact = (get("ui", "username") or # preferred
85 contact = (get("ui", "username") or # preferred
922 get("web", "contact") or # deprecated
86 get("web", "contact") or # deprecated
923 get("web", "author", "")) # also
87 get("web", "author", "")) # also
924 description = get("web", "description", "")
88 description = get("web", "description", "")
925 name = get("web", "name", name)
89 name = get("web", "name", name)
926 row = dict(contact=contact or "unknown",
90 row = dict(contact=contact or "unknown",
927 contact_sort=contact.upper() or "unknown",
91 contact_sort=contact.upper() or "unknown",
928 name=name,
92 name=name,
929 name_sort=name,
93 name_sort=name,
930 url=url,
94 url=url,
931 description=description or "unknown",
95 description=description or "unknown",
932 description_sort=description.upper() or "unknown",
96 description_sort=description.upper() or "unknown",
933 lastchange=d,
97 lastchange=d,
934 lastchange_sort=d[1]-d[0],
98 lastchange_sort=d[1]-d[0],
935 archives=archivelist(u, "tip", url))
99 archives=archivelist(u, "tip", url))
936 if (not sortcolumn
100 if (not sortcolumn
937 or (sortcolumn, descending) == self.repos_sorted):
101 or (sortcolumn, descending) == self.repos_sorted):
938 # fast path for unsorted output
102 # fast path for unsorted output
939 row['parity'] = parity
103 row['parity'] = parity
940 parity = 1 - parity
104 parity = 1 - parity
941 yield row
105 yield row
942 else:
106 else:
943 rows.append((row["%s_sort" % sortcolumn], row))
107 rows.append((row["%s_sort" % sortcolumn], row))
944 if rows:
108 if rows:
945 rows.sort()
109 rows.sort()
946 if descending:
110 if descending:
947 rows.reverse()
111 rows.reverse()
948 for key, row in rows:
112 for key, row in rows:
949 row['parity'] = parity
113 row['parity'] = parity
950 parity = 1 - parity
114 parity = 1 - parity
951 yield row
115 yield row
952
116
953 virtual = req.env.get("PATH_INFO", "").strip('/')
117 virtual = req.env.get("PATH_INFO", "").strip('/')
954 if virtual:
118 if virtual:
955 real = dict(self.repos).get(virtual)
119 real = dict(self.repos).get(virtual)
956 if real:
120 if real:
957 try:
121 try:
958 hgweb(real).run(req)
122 hgweb(real).run(req)
959 except IOError, inst:
123 except IOError, inst:
960 req.write(tmpl("error", error=inst.strerror))
124 req.write(tmpl("error", error=inst.strerror))
961 except hg.RepoError, inst:
125 except hg.RepoError, inst:
962 req.write(tmpl("error", error=str(inst)))
126 req.write(tmpl("error", error=str(inst)))
963 else:
127 else:
964 req.write(tmpl("notfound", repo=virtual))
128 req.write(tmpl("notfound", repo=virtual))
965 else:
129 else:
966 if req.form.has_key('static'):
130 if req.form.has_key('static'):
967 static = os.path.join(templater.templatepath(), "static")
131 static = os.path.join(templater.templatepath(), "static")
968 fname = req.form['static'][0]
132 fname = req.form['static'][0]
969 req.write(staticfile(static, fname)
133 req.write(staticfile(static, fname)
970 or tmpl("error", error="%r not found" % fname))
134 or tmpl("error", error="%r not found" % fname))
971 else:
135 else:
972 sortable = ["name", "description", "contact", "lastchange"]
136 sortable = ["name", "description", "contact", "lastchange"]
973 sortcolumn, descending = self.repos_sorted
137 sortcolumn, descending = self.repos_sorted
974 if req.form.has_key('sort'):
138 if req.form.has_key('sort'):
975 sortcolumn = req.form['sort'][0]
139 sortcolumn = req.form['sort'][0]
976 descending = sortcolumn.startswith('-')
140 descending = sortcolumn.startswith('-')
977 if descending:
141 if descending:
978 sortcolumn = sortcolumn[1:]
142 sortcolumn = sortcolumn[1:]
979 if sortcolumn not in sortable:
143 if sortcolumn not in sortable:
980 sortcolumn = ""
144 sortcolumn = ""
981
145
982 sort = [("sort_%s" % column,
146 sort = [("sort_%s" % column,
983 "%s%s" % ((not descending and column == sortcolumn)
147 "%s%s" % ((not descending and column == sortcolumn)
984 and "-" or "", column))
148 and "-" or "", column))
985 for column in sortable]
149 for column in sortable]
986 req.write(tmpl("index", entries=entries,
150 req.write(tmpl("index", entries=entries,
987 sortcolumn=sortcolumn, descending=descending,
151 sortcolumn=sortcolumn, descending=descending,
988 **dict(sort)))
152 **dict(sort)))
General Comments 0
You need to be logged in to leave comments. Login now