##// END OF EJS Templates
Fix up representation of dates in hgweb....
Bryan O'Sullivan -
r1320:5f277e73 default
parent child Browse files
Show More
@@ -1,2209 +1,2195 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 demandload(globals(), "os re sys signal shutil imp urllib pdb")
10 demandload(globals(), "os re sys signal shutil imp urllib pdb")
11 demandload(globals(), "fancyopts ui hg util lock revlog")
11 demandload(globals(), "fancyopts ui hg util lock revlog")
12 demandload(globals(), "fnmatch hgweb mdiff random signal time traceback")
12 demandload(globals(), "fnmatch hgweb mdiff random signal time traceback")
13 demandload(globals(), "errno socket version struct atexit sets bz2")
13 demandload(globals(), "errno socket version struct atexit sets bz2")
14
14
15 class UnknownCommand(Exception):
15 class UnknownCommand(Exception):
16 """Exception raised if command is not in the command table."""
16 """Exception raised if command is not in the command table."""
17
17
18 def filterfiles(filters, files):
18 def filterfiles(filters, files):
19 l = [x for x in files if x in filters]
19 l = [x for x in files if x in filters]
20
20
21 for t in filters:
21 for t in filters:
22 if t and t[-1] != "/":
22 if t and t[-1] != "/":
23 t += "/"
23 t += "/"
24 l += [x for x in files if x.startswith(t)]
24 l += [x for x in files if x.startswith(t)]
25 return l
25 return l
26
26
27 def relpath(repo, args):
27 def relpath(repo, args):
28 cwd = repo.getcwd()
28 cwd = repo.getcwd()
29 if cwd:
29 if cwd:
30 return [util.normpath(os.path.join(cwd, x)) for x in args]
30 return [util.normpath(os.path.join(cwd, x)) for x in args]
31 return args
31 return args
32
32
33 def datestr(change=None):
34 if change is None:
35 t = time.time()
36 if time.daylight: tz = time.altzone
37 else: tz = time.timezone
38 else:
39 t, tz = change[2].split(' ')
40 try:
41 # a conversion tool was sticking non-integer offsets into repos
42 tz = int(tz)
43 except ValueError:
44 tz = 0
45 return time.asctime(time.gmtime(float(t) - tz)) + " %+05d" % (int(tz)/-36)
46
47 def matchpats(repo, cwd, pats=[], opts={}, head=''):
33 def matchpats(repo, cwd, pats=[], opts={}, head=''):
48 return util.matcher(repo.root, cwd, pats or ['.'], opts.get('include'),
34 return util.matcher(repo.root, cwd, pats or ['.'], opts.get('include'),
49 opts.get('exclude'), head)
35 opts.get('exclude'), head)
50
36
51 def makewalk(repo, pats, opts, head=''):
37 def makewalk(repo, pats, opts, head=''):
52 cwd = repo.getcwd()
38 cwd = repo.getcwd()
53 files, matchfn, anypats = matchpats(repo, cwd, pats, opts, head)
39 files, matchfn, anypats = matchpats(repo, cwd, pats, opts, head)
54 exact = dict(zip(files, files))
40 exact = dict(zip(files, files))
55 def walk():
41 def walk():
56 for src, fn in repo.walk(files=files, match=matchfn):
42 for src, fn in repo.walk(files=files, match=matchfn):
57 yield src, fn, util.pathto(cwd, fn), fn in exact
43 yield src, fn, util.pathto(cwd, fn), fn in exact
58 return files, matchfn, walk()
44 return files, matchfn, walk()
59
45
60 def walk(repo, pats, opts, head=''):
46 def walk(repo, pats, opts, head=''):
61 files, matchfn, results = makewalk(repo, pats, opts, head)
47 files, matchfn, results = makewalk(repo, pats, opts, head)
62 for r in results:
48 for r in results:
63 yield r
49 yield r
64
50
65 def walkchangerevs(ui, repo, cwd, pats, opts):
51 def walkchangerevs(ui, repo, cwd, pats, opts):
66 '''Iterate over files and the revs they changed in.
52 '''Iterate over files and the revs they changed in.
67
53
68 Callers most commonly need to iterate backwards over the history
54 Callers most commonly need to iterate backwards over the history
69 it is interested in. Doing so has awful (quadratic-looking)
55 it is interested in. Doing so has awful (quadratic-looking)
70 performance, so we use iterators in a "windowed" way.
56 performance, so we use iterators in a "windowed" way.
71
57
72 We walk a window of revisions in the desired order. Within the
58 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
59 window, we first walk forwards to gather data, then in the desired
74 order (usually backwards) to display it.
60 order (usually backwards) to display it.
75
61
76 This function returns an (iterator, getchange) pair. The
62 This function returns an (iterator, getchange) pair. The
77 getchange function returns the changelog entry for a numeric
63 getchange function returns the changelog entry for a numeric
78 revision. The iterator yields 3-tuples. They will be of one of
64 revision. The iterator yields 3-tuples. They will be of one of
79 the following forms:
65 the following forms:
80
66
81 "window", incrementing, lastrev: stepping through a window,
67 "window", incrementing, lastrev: stepping through a window,
82 positive if walking forwards through revs, last rev in the
68 positive if walking forwards through revs, last rev in the
83 sequence iterated over - use to reset state for the current window
69 sequence iterated over - use to reset state for the current window
84
70
85 "add", rev, fns: out-of-order traversal of the given file names
71 "add", rev, fns: out-of-order traversal of the given file names
86 fns, which changed during revision rev - use to gather data for
72 fns, which changed during revision rev - use to gather data for
87 possible display
73 possible display
88
74
89 "iter", rev, None: in-order traversal of the revs earlier iterated
75 "iter", rev, None: in-order traversal of the revs earlier iterated
90 over with "add" - use to display data'''
76 over with "add" - use to display data'''
91 cwd = repo.getcwd()
77 cwd = repo.getcwd()
92 if not pats and cwd:
78 if not pats and cwd:
93 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
79 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
94 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
80 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
95 files, matchfn, anypats = matchpats(repo, (pats and cwd) or '',
81 files, matchfn, anypats = matchpats(repo, (pats and cwd) or '',
96 pats, opts)
82 pats, opts)
97 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
83 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
98 wanted = {}
84 wanted = {}
99 slowpath = anypats
85 slowpath = anypats
100 window = 300
86 window = 300
101 fncache = {}
87 fncache = {}
102
88
103 chcache = {}
89 chcache = {}
104 def getchange(rev):
90 def getchange(rev):
105 ch = chcache.get(rev)
91 ch = chcache.get(rev)
106 if ch is None:
92 if ch is None:
107 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
93 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
108 return ch
94 return ch
109
95
110 if not slowpath and not files:
96 if not slowpath and not files:
111 # No files, no patterns. Display all revs.
97 # No files, no patterns. Display all revs.
112 wanted = dict(zip(revs, revs))
98 wanted = dict(zip(revs, revs))
113 if not slowpath:
99 if not slowpath:
114 # Only files, no patterns. Check the history of each file.
100 # Only files, no patterns. Check the history of each file.
115 def filerevgen(filelog):
101 def filerevgen(filelog):
116 for i in xrange(filelog.count() - 1, -1, -window):
102 for i in xrange(filelog.count() - 1, -1, -window):
117 revs = []
103 revs = []
118 for j in xrange(max(0, i - window), i + 1):
104 for j in xrange(max(0, i - window), i + 1):
119 revs.append(filelog.linkrev(filelog.node(j)))
105 revs.append(filelog.linkrev(filelog.node(j)))
120 revs.reverse()
106 revs.reverse()
121 for rev in revs:
107 for rev in revs:
122 yield rev
108 yield rev
123
109
124 minrev, maxrev = min(revs), max(revs)
110 minrev, maxrev = min(revs), max(revs)
125 for file in files:
111 for file in files:
126 filelog = repo.file(file)
112 filelog = repo.file(file)
127 # A zero count may be a directory or deleted file, so
113 # A zero count may be a directory or deleted file, so
128 # try to find matching entries on the slow path.
114 # try to find matching entries on the slow path.
129 if filelog.count() == 0:
115 if filelog.count() == 0:
130 slowpath = True
116 slowpath = True
131 break
117 break
132 for rev in filerevgen(filelog):
118 for rev in filerevgen(filelog):
133 if rev <= maxrev:
119 if rev <= maxrev:
134 if rev < minrev:
120 if rev < minrev:
135 break
121 break
136 fncache.setdefault(rev, [])
122 fncache.setdefault(rev, [])
137 fncache[rev].append(file)
123 fncache[rev].append(file)
138 wanted[rev] = 1
124 wanted[rev] = 1
139 if slowpath:
125 if slowpath:
140 # The slow path checks files modified in every changeset.
126 # The slow path checks files modified in every changeset.
141 def changerevgen():
127 def changerevgen():
142 for i in xrange(repo.changelog.count() - 1, -1, -window):
128 for i in xrange(repo.changelog.count() - 1, -1, -window):
143 for j in xrange(max(0, i - window), i + 1):
129 for j in xrange(max(0, i - window), i + 1):
144 yield j, getchange(j)[3]
130 yield j, getchange(j)[3]
145
131
146 for rev, changefiles in changerevgen():
132 for rev, changefiles in changerevgen():
147 matches = filter(matchfn, changefiles)
133 matches = filter(matchfn, changefiles)
148 if matches:
134 if matches:
149 fncache[rev] = matches
135 fncache[rev] = matches
150 wanted[rev] = 1
136 wanted[rev] = 1
151
137
152 def iterate():
138 def iterate():
153 for i in xrange(0, len(revs), window):
139 for i in xrange(0, len(revs), window):
154 yield 'window', revs[0] < revs[-1], revs[-1]
140 yield 'window', revs[0] < revs[-1], revs[-1]
155 nrevs = [rev for rev in revs[i:min(i+window, len(revs))]
141 nrevs = [rev for rev in revs[i:min(i+window, len(revs))]
156 if rev in wanted]
142 if rev in wanted]
157 srevs = list(nrevs)
143 srevs = list(nrevs)
158 srevs.sort()
144 srevs.sort()
159 for rev in srevs:
145 for rev in srevs:
160 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
146 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
161 yield 'add', rev, fns
147 yield 'add', rev, fns
162 for rev in nrevs:
148 for rev in nrevs:
163 yield 'iter', rev, None
149 yield 'iter', rev, None
164 return iterate(), getchange
150 return iterate(), getchange
165
151
166 revrangesep = ':'
152 revrangesep = ':'
167
153
168 def revrange(ui, repo, revs, revlog=None):
154 def revrange(ui, repo, revs, revlog=None):
169 """Yield revision as strings from a list of revision specifications."""
155 """Yield revision as strings from a list of revision specifications."""
170 if revlog is None:
156 if revlog is None:
171 revlog = repo.changelog
157 revlog = repo.changelog
172 revcount = revlog.count()
158 revcount = revlog.count()
173 def fix(val, defval):
159 def fix(val, defval):
174 if not val:
160 if not val:
175 return defval
161 return defval
176 try:
162 try:
177 num = int(val)
163 num = int(val)
178 if str(num) != val:
164 if str(num) != val:
179 raise ValueError
165 raise ValueError
180 if num < 0: num += revcount
166 if num < 0: num += revcount
181 if num < 0: num = 0
167 if num < 0: num = 0
182 elif num >= revcount:
168 elif num >= revcount:
183 raise ValueError
169 raise ValueError
184 except ValueError:
170 except ValueError:
185 try:
171 try:
186 num = repo.changelog.rev(repo.lookup(val))
172 num = repo.changelog.rev(repo.lookup(val))
187 except KeyError:
173 except KeyError:
188 try:
174 try:
189 num = revlog.rev(revlog.lookup(val))
175 num = revlog.rev(revlog.lookup(val))
190 except KeyError:
176 except KeyError:
191 raise util.Abort('invalid revision identifier %s', val)
177 raise util.Abort('invalid revision identifier %s', val)
192 return num
178 return num
193 seen = {}
179 seen = {}
194 for spec in revs:
180 for spec in revs:
195 if spec.find(revrangesep) >= 0:
181 if spec.find(revrangesep) >= 0:
196 start, end = spec.split(revrangesep, 1)
182 start, end = spec.split(revrangesep, 1)
197 start = fix(start, 0)
183 start = fix(start, 0)
198 end = fix(end, revcount - 1)
184 end = fix(end, revcount - 1)
199 step = start > end and -1 or 1
185 step = start > end and -1 or 1
200 for rev in xrange(start, end+step, step):
186 for rev in xrange(start, end+step, step):
201 if rev in seen: continue
187 if rev in seen: continue
202 seen[rev] = 1
188 seen[rev] = 1
203 yield str(rev)
189 yield str(rev)
204 else:
190 else:
205 rev = fix(spec, None)
191 rev = fix(spec, None)
206 if rev in seen: continue
192 if rev in seen: continue
207 seen[rev] = 1
193 seen[rev] = 1
208 yield str(rev)
194 yield str(rev)
209
195
210 def make_filename(repo, r, pat, node=None,
196 def make_filename(repo, r, pat, node=None,
211 total=None, seqno=None, revwidth=None, pathname=None):
197 total=None, seqno=None, revwidth=None, pathname=None):
212 node_expander = {
198 node_expander = {
213 'H': lambda: hex(node),
199 'H': lambda: hex(node),
214 'R': lambda: str(r.rev(node)),
200 'R': lambda: str(r.rev(node)),
215 'h': lambda: short(node),
201 'h': lambda: short(node),
216 }
202 }
217 expander = {
203 expander = {
218 '%': lambda: '%',
204 '%': lambda: '%',
219 'b': lambda: os.path.basename(repo.root),
205 'b': lambda: os.path.basename(repo.root),
220 }
206 }
221
207
222 try:
208 try:
223 if node:
209 if node:
224 expander.update(node_expander)
210 expander.update(node_expander)
225 if node and revwidth is not None:
211 if node and revwidth is not None:
226 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
212 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
227 if total is not None:
213 if total is not None:
228 expander['N'] = lambda: str(total)
214 expander['N'] = lambda: str(total)
229 if seqno is not None:
215 if seqno is not None:
230 expander['n'] = lambda: str(seqno)
216 expander['n'] = lambda: str(seqno)
231 if total is not None and seqno is not None:
217 if total is not None and seqno is not None:
232 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
218 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
233 if pathname is not None:
219 if pathname is not None:
234 expander['s'] = lambda: os.path.basename(pathname)
220 expander['s'] = lambda: os.path.basename(pathname)
235 expander['d'] = lambda: os.path.dirname(pathname) or '.'
221 expander['d'] = lambda: os.path.dirname(pathname) or '.'
236 expander['p'] = lambda: pathname
222 expander['p'] = lambda: pathname
237
223
238 newname = []
224 newname = []
239 patlen = len(pat)
225 patlen = len(pat)
240 i = 0
226 i = 0
241 while i < patlen:
227 while i < patlen:
242 c = pat[i]
228 c = pat[i]
243 if c == '%':
229 if c == '%':
244 i += 1
230 i += 1
245 c = pat[i]
231 c = pat[i]
246 c = expander[c]()
232 c = expander[c]()
247 newname.append(c)
233 newname.append(c)
248 i += 1
234 i += 1
249 return ''.join(newname)
235 return ''.join(newname)
250 except KeyError, inst:
236 except KeyError, inst:
251 raise util.Abort("invalid format spec '%%%s' in output file name",
237 raise util.Abort("invalid format spec '%%%s' in output file name",
252 inst.args[0])
238 inst.args[0])
253
239
254 def make_file(repo, r, pat, node=None,
240 def make_file(repo, r, pat, node=None,
255 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
241 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
256 if not pat or pat == '-':
242 if not pat or pat == '-':
257 return 'w' in mode and sys.stdout or sys.stdin
243 return 'w' in mode and sys.stdout or sys.stdin
258 if hasattr(pat, 'write') and 'w' in mode:
244 if hasattr(pat, 'write') and 'w' in mode:
259 return pat
245 return pat
260 if hasattr(pat, 'read') and 'r' in mode:
246 if hasattr(pat, 'read') and 'r' in mode:
261 return pat
247 return pat
262 return open(make_filename(repo, r, pat, node, total, seqno, revwidth,
248 return open(make_filename(repo, r, pat, node, total, seqno, revwidth,
263 pathname),
249 pathname),
264 mode)
250 mode)
265
251
266 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
252 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
267 changes=None, text=False):
253 changes=None, text=False):
268 if not changes:
254 if not changes:
269 (c, a, d, u) = repo.changes(node1, node2, files, match=match)
255 (c, a, d, u) = repo.changes(node1, node2, files, match=match)
270 else:
256 else:
271 (c, a, d, u) = changes
257 (c, a, d, u) = changes
272 if files:
258 if files:
273 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
259 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
274
260
275 if not c and not a and not d:
261 if not c and not a and not d:
276 return
262 return
277
263
278 if node2:
264 if node2:
279 change = repo.changelog.read(node2)
265 change = repo.changelog.read(node2)
280 mmap2 = repo.manifest.read(change[0])
266 mmap2 = repo.manifest.read(change[0])
281 date2 = datestr(change)
267 date2 = util.datestr(change)
282 def read(f):
268 def read(f):
283 return repo.file(f).read(mmap2[f])
269 return repo.file(f).read(mmap2[f])
284 else:
270 else:
285 date2 = datestr()
271 date2 = util.datestr()
286 if not node1:
272 if not node1:
287 node1 = repo.dirstate.parents()[0]
273 node1 = repo.dirstate.parents()[0]
288 def read(f):
274 def read(f):
289 return repo.wfile(f).read()
275 return repo.wfile(f).read()
290
276
291 if ui.quiet:
277 if ui.quiet:
292 r = None
278 r = None
293 else:
279 else:
294 hexfunc = ui.verbose and hex or short
280 hexfunc = ui.verbose and hex or short
295 r = [hexfunc(node) for node in [node1, node2] if node]
281 r = [hexfunc(node) for node in [node1, node2] if node]
296
282
297 change = repo.changelog.read(node1)
283 change = repo.changelog.read(node1)
298 mmap = repo.manifest.read(change[0])
284 mmap = repo.manifest.read(change[0])
299 date1 = datestr(change)
285 date1 = util.datestr(change)
300
286
301 for f in c:
287 for f in c:
302 to = None
288 to = None
303 if f in mmap:
289 if f in mmap:
304 to = repo.file(f).read(mmap[f])
290 to = repo.file(f).read(mmap[f])
305 tn = read(f)
291 tn = read(f)
306 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
292 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
307 for f in a:
293 for f in a:
308 to = None
294 to = None
309 tn = read(f)
295 tn = read(f)
310 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
296 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
311 for f in d:
297 for f in d:
312 to = repo.file(f).read(mmap[f])
298 to = repo.file(f).read(mmap[f])
313 tn = None
299 tn = None
314 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
300 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
315
301
316 def trimuser(ui, name, rev, revcache):
302 def trimuser(ui, name, rev, revcache):
317 """trim the name of the user who committed a change"""
303 """trim the name of the user who committed a change"""
318 user = revcache.get(rev)
304 user = revcache.get(rev)
319 if user is None:
305 if user is None:
320 user = revcache[rev] = ui.shortuser(name)
306 user = revcache[rev] = ui.shortuser(name)
321 return user
307 return user
322
308
323 def show_changeset(ui, repo, rev=0, changenode=None, brinfo=None):
309 def show_changeset(ui, repo, rev=0, changenode=None, brinfo=None):
324 """show a single changeset or file revision"""
310 """show a single changeset or file revision"""
325 log = repo.changelog
311 log = repo.changelog
326 if changenode is None:
312 if changenode is None:
327 changenode = log.node(rev)
313 changenode = log.node(rev)
328 elif not rev:
314 elif not rev:
329 rev = log.rev(changenode)
315 rev = log.rev(changenode)
330
316
331 if ui.quiet:
317 if ui.quiet:
332 ui.write("%d:%s\n" % (rev, short(changenode)))
318 ui.write("%d:%s\n" % (rev, short(changenode)))
333 return
319 return
334
320
335 changes = log.read(changenode)
321 changes = log.read(changenode)
336 date = datestr(changes)
322 date = util.datestr(changes)
337
323
338 parents = [(log.rev(p), ui.verbose and hex(p) or short(p))
324 parents = [(log.rev(p), ui.verbose and hex(p) or short(p))
339 for p in log.parents(changenode)
325 for p in log.parents(changenode)
340 if ui.debugflag or p != nullid]
326 if ui.debugflag or p != nullid]
341 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
327 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
342 parents = []
328 parents = []
343
329
344 if ui.verbose:
330 if ui.verbose:
345 ui.write("changeset: %d:%s\n" % (rev, hex(changenode)))
331 ui.write("changeset: %d:%s\n" % (rev, hex(changenode)))
346 else:
332 else:
347 ui.write("changeset: %d:%s\n" % (rev, short(changenode)))
333 ui.write("changeset: %d:%s\n" % (rev, short(changenode)))
348
334
349 for tag in repo.nodetags(changenode):
335 for tag in repo.nodetags(changenode):
350 ui.status("tag: %s\n" % tag)
336 ui.status("tag: %s\n" % tag)
351 for parent in parents:
337 for parent in parents:
352 ui.write("parent: %d:%s\n" % parent)
338 ui.write("parent: %d:%s\n" % parent)
353
339
354 if brinfo and changenode in brinfo:
340 if brinfo and changenode in brinfo:
355 br = brinfo[changenode]
341 br = brinfo[changenode]
356 ui.write("branch: %s\n" % " ".join(br))
342 ui.write("branch: %s\n" % " ".join(br))
357
343
358 ui.debug("manifest: %d:%s\n" % (repo.manifest.rev(changes[0]),
344 ui.debug("manifest: %d:%s\n" % (repo.manifest.rev(changes[0]),
359 hex(changes[0])))
345 hex(changes[0])))
360 ui.status("user: %s\n" % changes[1])
346 ui.status("user: %s\n" % changes[1])
361 ui.status("date: %s\n" % date)
347 ui.status("date: %s\n" % date)
362
348
363 if ui.debugflag:
349 if ui.debugflag:
364 files = repo.changes(log.parents(changenode)[0], changenode)
350 files = repo.changes(log.parents(changenode)[0], changenode)
365 for key, value in zip(["files:", "files+:", "files-:"], files):
351 for key, value in zip(["files:", "files+:", "files-:"], files):
366 if value:
352 if value:
367 ui.note("%-12s %s\n" % (key, " ".join(value)))
353 ui.note("%-12s %s\n" % (key, " ".join(value)))
368 else:
354 else:
369 ui.note("files: %s\n" % " ".join(changes[3]))
355 ui.note("files: %s\n" % " ".join(changes[3]))
370
356
371 description = changes[4].strip()
357 description = changes[4].strip()
372 if description:
358 if description:
373 if ui.verbose:
359 if ui.verbose:
374 ui.status("description:\n")
360 ui.status("description:\n")
375 ui.status(description)
361 ui.status(description)
376 ui.status("\n\n")
362 ui.status("\n\n")
377 else:
363 else:
378 ui.status("summary: %s\n" % description.splitlines()[0])
364 ui.status("summary: %s\n" % description.splitlines()[0])
379 ui.status("\n")
365 ui.status("\n")
380
366
381 def show_version(ui):
367 def show_version(ui):
382 """output version and copyright information"""
368 """output version and copyright information"""
383 ui.write("Mercurial Distributed SCM (version %s)\n"
369 ui.write("Mercurial Distributed SCM (version %s)\n"
384 % version.get_version())
370 % version.get_version())
385 ui.status(
371 ui.status(
386 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
372 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
387 "This is free software; see the source for copying conditions. "
373 "This is free software; see the source for copying conditions. "
388 "There is NO\nwarranty; "
374 "There is NO\nwarranty; "
389 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
375 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
390 )
376 )
391
377
392 def help_(ui, cmd=None, with_version=False):
378 def help_(ui, cmd=None, with_version=False):
393 """show help for a given command or all commands"""
379 """show help for a given command or all commands"""
394 option_lists = []
380 option_lists = []
395 if cmd and cmd != 'shortlist':
381 if cmd and cmd != 'shortlist':
396 if with_version:
382 if with_version:
397 show_version(ui)
383 show_version(ui)
398 ui.write('\n')
384 ui.write('\n')
399 key, i = find(cmd)
385 key, i = find(cmd)
400 # synopsis
386 # synopsis
401 ui.write("%s\n\n" % i[2])
387 ui.write("%s\n\n" % i[2])
402
388
403 # description
389 # description
404 doc = i[0].__doc__
390 doc = i[0].__doc__
405 if ui.quiet:
391 if ui.quiet:
406 doc = doc.splitlines(0)[0]
392 doc = doc.splitlines(0)[0]
407 ui.write("%s\n" % doc.rstrip())
393 ui.write("%s\n" % doc.rstrip())
408
394
409 if not ui.quiet:
395 if not ui.quiet:
410 # aliases
396 # aliases
411 aliases = ', '.join(key.split('|')[1:])
397 aliases = ', '.join(key.split('|')[1:])
412 if aliases:
398 if aliases:
413 ui.write("\naliases: %s\n" % aliases)
399 ui.write("\naliases: %s\n" % aliases)
414
400
415 # options
401 # options
416 if i[1]:
402 if i[1]:
417 option_lists.append(("options", i[1]))
403 option_lists.append(("options", i[1]))
418
404
419 else:
405 else:
420 # program name
406 # program name
421 if ui.verbose or with_version:
407 if ui.verbose or with_version:
422 show_version(ui)
408 show_version(ui)
423 else:
409 else:
424 ui.status("Mercurial Distributed SCM\n")
410 ui.status("Mercurial Distributed SCM\n")
425 ui.status('\n')
411 ui.status('\n')
426
412
427 # list of commands
413 # list of commands
428 if cmd == "shortlist":
414 if cmd == "shortlist":
429 ui.status('basic commands (use "hg help" '
415 ui.status('basic commands (use "hg help" '
430 'for the full list or option "-v" for details):\n\n')
416 'for the full list or option "-v" for details):\n\n')
431 elif ui.verbose:
417 elif ui.verbose:
432 ui.status('list of commands:\n\n')
418 ui.status('list of commands:\n\n')
433 else:
419 else:
434 ui.status('list of commands (use "hg help -v" '
420 ui.status('list of commands (use "hg help -v" '
435 'to show aliases and global options):\n\n')
421 'to show aliases and global options):\n\n')
436
422
437 h = {}
423 h = {}
438 cmds = {}
424 cmds = {}
439 for c, e in table.items():
425 for c, e in table.items():
440 f = c.split("|")[0]
426 f = c.split("|")[0]
441 if cmd == "shortlist" and not f.startswith("^"):
427 if cmd == "shortlist" and not f.startswith("^"):
442 continue
428 continue
443 f = f.lstrip("^")
429 f = f.lstrip("^")
444 if not ui.debugflag and f.startswith("debug"):
430 if not ui.debugflag and f.startswith("debug"):
445 continue
431 continue
446 d = ""
432 d = ""
447 if e[0].__doc__:
433 if e[0].__doc__:
448 d = e[0].__doc__.splitlines(0)[0].rstrip()
434 d = e[0].__doc__.splitlines(0)[0].rstrip()
449 h[f] = d
435 h[f] = d
450 cmds[f]=c.lstrip("^")
436 cmds[f]=c.lstrip("^")
451
437
452 fns = h.keys()
438 fns = h.keys()
453 fns.sort()
439 fns.sort()
454 m = max(map(len, fns))
440 m = max(map(len, fns))
455 for f in fns:
441 for f in fns:
456 if ui.verbose:
442 if ui.verbose:
457 commands = cmds[f].replace("|",", ")
443 commands = cmds[f].replace("|",", ")
458 ui.write(" %s:\n %s\n"%(commands,h[f]))
444 ui.write(" %s:\n %s\n"%(commands,h[f]))
459 else:
445 else:
460 ui.write(' %-*s %s\n' % (m, f, h[f]))
446 ui.write(' %-*s %s\n' % (m, f, h[f]))
461
447
462 # global options
448 # global options
463 if ui.verbose:
449 if ui.verbose:
464 option_lists.append(("global options", globalopts))
450 option_lists.append(("global options", globalopts))
465
451
466 # list all option lists
452 # list all option lists
467 opt_output = []
453 opt_output = []
468 for title, options in option_lists:
454 for title, options in option_lists:
469 opt_output.append(("\n%s:\n" % title, None))
455 opt_output.append(("\n%s:\n" % title, None))
470 for shortopt, longopt, default, desc in options:
456 for shortopt, longopt, default, desc in options:
471 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
457 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
472 longopt and " --%s" % longopt),
458 longopt and " --%s" % longopt),
473 "%s%s" % (desc,
459 "%s%s" % (desc,
474 default and " (default: %s)" % default
460 default and " (default: %s)" % default
475 or "")))
461 or "")))
476
462
477 if opt_output:
463 if opt_output:
478 opts_len = max([len(line[0]) for line in opt_output if line[1]])
464 opts_len = max([len(line[0]) for line in opt_output if line[1]])
479 for first, second in opt_output:
465 for first, second in opt_output:
480 if second:
466 if second:
481 ui.write(" %-*s %s\n" % (opts_len, first, second))
467 ui.write(" %-*s %s\n" % (opts_len, first, second))
482 else:
468 else:
483 ui.write("%s\n" % first)
469 ui.write("%s\n" % first)
484
470
485 # Commands start here, listed alphabetically
471 # Commands start here, listed alphabetically
486
472
487 def add(ui, repo, *pats, **opts):
473 def add(ui, repo, *pats, **opts):
488 '''add the specified files on the next commit'''
474 '''add the specified files on the next commit'''
489 names = []
475 names = []
490 for src, abs, rel, exact in walk(repo, pats, opts):
476 for src, abs, rel, exact in walk(repo, pats, opts):
491 if exact:
477 if exact:
492 if ui.verbose: ui.status('adding %s\n' % rel)
478 if ui.verbose: ui.status('adding %s\n' % rel)
493 names.append(abs)
479 names.append(abs)
494 elif repo.dirstate.state(abs) == '?':
480 elif repo.dirstate.state(abs) == '?':
495 ui.status('adding %s\n' % rel)
481 ui.status('adding %s\n' % rel)
496 names.append(abs)
482 names.append(abs)
497 repo.add(names)
483 repo.add(names)
498
484
499 def addremove(ui, repo, *pats, **opts):
485 def addremove(ui, repo, *pats, **opts):
500 """add all new files, delete all missing files"""
486 """add all new files, delete all missing files"""
501 add, remove = [], []
487 add, remove = [], []
502 for src, abs, rel, exact in walk(repo, pats, opts):
488 for src, abs, rel, exact in walk(repo, pats, opts):
503 if src == 'f' and repo.dirstate.state(abs) == '?':
489 if src == 'f' and repo.dirstate.state(abs) == '?':
504 add.append(abs)
490 add.append(abs)
505 if ui.verbose or not exact:
491 if ui.verbose or not exact:
506 ui.status('adding ', rel, '\n')
492 ui.status('adding ', rel, '\n')
507 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
493 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
508 remove.append(abs)
494 remove.append(abs)
509 if ui.verbose or not exact:
495 if ui.verbose or not exact:
510 ui.status('removing ', rel, '\n')
496 ui.status('removing ', rel, '\n')
511 repo.add(add)
497 repo.add(add)
512 repo.remove(remove)
498 repo.remove(remove)
513
499
514 def annotate(ui, repo, *pats, **opts):
500 def annotate(ui, repo, *pats, **opts):
515 """show changeset information per file line"""
501 """show changeset information per file line"""
516 def getnode(rev):
502 def getnode(rev):
517 return short(repo.changelog.node(rev))
503 return short(repo.changelog.node(rev))
518
504
519 ucache = {}
505 ucache = {}
520 def getname(rev):
506 def getname(rev):
521 cl = repo.changelog.read(repo.changelog.node(rev))
507 cl = repo.changelog.read(repo.changelog.node(rev))
522 return trimuser(ui, cl[1], rev, ucache)
508 return trimuser(ui, cl[1], rev, ucache)
523
509
524 if not pats:
510 if not pats:
525 raise util.Abort('at least one file name or pattern required')
511 raise util.Abort('at least one file name or pattern required')
526
512
527 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
513 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
528 if not opts['user'] and not opts['changeset']:
514 if not opts['user'] and not opts['changeset']:
529 opts['number'] = 1
515 opts['number'] = 1
530
516
531 if opts['rev']:
517 if opts['rev']:
532 node = repo.changelog.lookup(opts['rev'])
518 node = repo.changelog.lookup(opts['rev'])
533 else:
519 else:
534 node = repo.dirstate.parents()[0]
520 node = repo.dirstate.parents()[0]
535 change = repo.changelog.read(node)
521 change = repo.changelog.read(node)
536 mmap = repo.manifest.read(change[0])
522 mmap = repo.manifest.read(change[0])
537
523
538 for src, abs, rel, exact in walk(repo, pats, opts):
524 for src, abs, rel, exact in walk(repo, pats, opts):
539 if abs not in mmap:
525 if abs not in mmap:
540 ui.warn("warning: %s is not in the repository!\n" % rel)
526 ui.warn("warning: %s is not in the repository!\n" % rel)
541 continue
527 continue
542
528
543 f = repo.file(abs)
529 f = repo.file(abs)
544 if not opts['text'] and util.binary(f.read(mmap[abs])):
530 if not opts['text'] and util.binary(f.read(mmap[abs])):
545 ui.write("%s: binary file\n" % rel)
531 ui.write("%s: binary file\n" % rel)
546 continue
532 continue
547
533
548 lines = f.annotate(mmap[abs])
534 lines = f.annotate(mmap[abs])
549 pieces = []
535 pieces = []
550
536
551 for o, f in opmap:
537 for o, f in opmap:
552 if opts[o]:
538 if opts[o]:
553 l = [f(n) for n, dummy in lines]
539 l = [f(n) for n, dummy in lines]
554 if l:
540 if l:
555 m = max(map(len, l))
541 m = max(map(len, l))
556 pieces.append(["%*s" % (m, x) for x in l])
542 pieces.append(["%*s" % (m, x) for x in l])
557
543
558 if pieces:
544 if pieces:
559 for p, l in zip(zip(*pieces), lines):
545 for p, l in zip(zip(*pieces), lines):
560 ui.write("%s: %s" % (" ".join(p), l[1]))
546 ui.write("%s: %s" % (" ".join(p), l[1]))
561
547
562 def bundle(ui, repo, fname, dest="default-push", **opts):
548 def bundle(ui, repo, fname, dest="default-push", **opts):
563 """create a changegroup file"""
549 """create a changegroup file"""
564 f = open(fname, "wb")
550 f = open(fname, "wb")
565 dest = ui.expandpath(dest)
551 dest = ui.expandpath(dest)
566 other = hg.repository(ui, dest)
552 other = hg.repository(ui, dest)
567 o = repo.findoutgoing(other)
553 o = repo.findoutgoing(other)
568 cg = repo.changegroup(o)
554 cg = repo.changegroup(o)
569
555
570 try:
556 try:
571 f.write("HG10")
557 f.write("HG10")
572 z = bz2.BZ2Compressor(9)
558 z = bz2.BZ2Compressor(9)
573 while 1:
559 while 1:
574 chunk = cg.read(4096)
560 chunk = cg.read(4096)
575 if not chunk:
561 if not chunk:
576 break
562 break
577 f.write(z.compress(chunk))
563 f.write(z.compress(chunk))
578 f.write(z.flush())
564 f.write(z.flush())
579 except:
565 except:
580 os.unlink(fname)
566 os.unlink(fname)
581
567
582 def cat(ui, repo, file1, *pats, **opts):
568 def cat(ui, repo, file1, *pats, **opts):
583 """output the latest or given revisions of files"""
569 """output the latest or given revisions of files"""
584 mf = {}
570 mf = {}
585 if opts['rev']:
571 if opts['rev']:
586 change = repo.changelog.read(repo.lookup(opts['rev']))
572 change = repo.changelog.read(repo.lookup(opts['rev']))
587 mf = repo.manifest.read(change[0])
573 mf = repo.manifest.read(change[0])
588 for src, abs, rel, exact in walk(repo, (file1,) + pats, opts):
574 for src, abs, rel, exact in walk(repo, (file1,) + pats, opts):
589 r = repo.file(abs)
575 r = repo.file(abs)
590 if opts['rev']:
576 if opts['rev']:
591 try:
577 try:
592 n = mf[abs]
578 n = mf[abs]
593 except (hg.RepoError, KeyError):
579 except (hg.RepoError, KeyError):
594 try:
580 try:
595 n = r.lookup(rev)
581 n = r.lookup(rev)
596 except KeyError, inst:
582 except KeyError, inst:
597 raise util.Abort('cannot find file %s in rev %s', rel, rev)
583 raise util.Abort('cannot find file %s in rev %s', rel, rev)
598 else:
584 else:
599 n = r.tip()
585 n = r.tip()
600 fp = make_file(repo, r, opts['output'], node=n, pathname=abs)
586 fp = make_file(repo, r, opts['output'], node=n, pathname=abs)
601 fp.write(r.read(n))
587 fp.write(r.read(n))
602
588
603 def clone(ui, source, dest=None, **opts):
589 def clone(ui, source, dest=None, **opts):
604 """make a copy of an existing repository"""
590 """make a copy of an existing repository"""
605 if dest is None:
591 if dest is None:
606 dest = os.path.basename(os.path.normpath(source))
592 dest = os.path.basename(os.path.normpath(source))
607
593
608 if os.path.exists(dest):
594 if os.path.exists(dest):
609 raise util.Abort("destination '%s' already exists", dest)
595 raise util.Abort("destination '%s' already exists", dest)
610
596
611 dest = os.path.realpath(dest)
597 dest = os.path.realpath(dest)
612
598
613 class Dircleanup:
599 class Dircleanup:
614 def __init__(self, dir_):
600 def __init__(self, dir_):
615 self.rmtree = shutil.rmtree
601 self.rmtree = shutil.rmtree
616 self.dir_ = dir_
602 self.dir_ = dir_
617 os.mkdir(dir_)
603 os.mkdir(dir_)
618 def close(self):
604 def close(self):
619 self.dir_ = None
605 self.dir_ = None
620 def __del__(self):
606 def __del__(self):
621 if self.dir_:
607 if self.dir_:
622 self.rmtree(self.dir_, True)
608 self.rmtree(self.dir_, True)
623
609
624 if opts['ssh']:
610 if opts['ssh']:
625 ui.setconfig("ui", "ssh", opts['ssh'])
611 ui.setconfig("ui", "ssh", opts['ssh'])
626 if opts['remotecmd']:
612 if opts['remotecmd']:
627 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
613 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
628
614
629 if not os.path.exists(source):
615 if not os.path.exists(source):
630 source = ui.expandpath(source)
616 source = ui.expandpath(source)
631
617
632 d = Dircleanup(dest)
618 d = Dircleanup(dest)
633 abspath = source
619 abspath = source
634 other = hg.repository(ui, source)
620 other = hg.repository(ui, source)
635
621
636 copy = False
622 copy = False
637 if other.dev() != -1:
623 if other.dev() != -1:
638 abspath = os.path.abspath(source)
624 abspath = os.path.abspath(source)
639 if not opts['pull']:
625 if not opts['pull']:
640 copy = True
626 copy = True
641
627
642 if copy:
628 if copy:
643 try:
629 try:
644 # we use a lock here because if we race with commit, we
630 # we use a lock here because if we race with commit, we
645 # can end up with extra data in the cloned revlogs that's
631 # can end up with extra data in the cloned revlogs that's
646 # not pointed to by changesets, thus causing verify to
632 # not pointed to by changesets, thus causing verify to
647 # fail
633 # fail
648 l1 = lock.lock(os.path.join(source, ".hg", "lock"))
634 l1 = lock.lock(os.path.join(source, ".hg", "lock"))
649 except OSError:
635 except OSError:
650 copy = False
636 copy = False
651
637
652 if copy:
638 if copy:
653 # we lock here to avoid premature writing to the target
639 # we lock here to avoid premature writing to the target
654 os.mkdir(os.path.join(dest, ".hg"))
640 os.mkdir(os.path.join(dest, ".hg"))
655 l2 = lock.lock(os.path.join(dest, ".hg", "lock"))
641 l2 = lock.lock(os.path.join(dest, ".hg", "lock"))
656
642
657 files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
643 files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
658 for f in files.split():
644 for f in files.split():
659 src = os.path.join(source, ".hg", f)
645 src = os.path.join(source, ".hg", f)
660 dst = os.path.join(dest, ".hg", f)
646 dst = os.path.join(dest, ".hg", f)
661 util.copyfiles(src, dst)
647 util.copyfiles(src, dst)
662
648
663 repo = hg.repository(ui, dest)
649 repo = hg.repository(ui, dest)
664
650
665 else:
651 else:
666 repo = hg.repository(ui, dest, create=1)
652 repo = hg.repository(ui, dest, create=1)
667 repo.pull(other)
653 repo.pull(other)
668
654
669 f = repo.opener("hgrc", "w")
655 f = repo.opener("hgrc", "w")
670 f.write("[paths]\n")
656 f.write("[paths]\n")
671 f.write("default = %s\n" % abspath)
657 f.write("default = %s\n" % abspath)
672
658
673 if not opts['noupdate']:
659 if not opts['noupdate']:
674 update(ui, repo)
660 update(ui, repo)
675
661
676 d.close()
662 d.close()
677
663
678 def commit(ui, repo, *pats, **opts):
664 def commit(ui, repo, *pats, **opts):
679 """commit the specified files or all outstanding changes"""
665 """commit the specified files or all outstanding changes"""
680 if opts['text']:
666 if opts['text']:
681 ui.warn("Warning: -t and --text is deprecated,"
667 ui.warn("Warning: -t and --text is deprecated,"
682 " please use -m or --message instead.\n")
668 " please use -m or --message instead.\n")
683 message = opts['message'] or opts['text']
669 message = opts['message'] or opts['text']
684 logfile = opts['logfile']
670 logfile = opts['logfile']
685
671
686 if message and logfile:
672 if message and logfile:
687 raise util.Abort('options --message and --logfile are mutually '
673 raise util.Abort('options --message and --logfile are mutually '
688 'exclusive')
674 'exclusive')
689 if not message and logfile:
675 if not message and logfile:
690 try:
676 try:
691 if logfile == '-':
677 if logfile == '-':
692 message = sys.stdin.read()
678 message = sys.stdin.read()
693 else:
679 else:
694 message = open(logfile).read()
680 message = open(logfile).read()
695 except IOError, inst:
681 except IOError, inst:
696 raise util.Abort("can't read commit message '%s': %s" %
682 raise util.Abort("can't read commit message '%s': %s" %
697 (logfile, inst.strerror))
683 (logfile, inst.strerror))
698
684
699 if opts['addremove']:
685 if opts['addremove']:
700 addremove(ui, repo, *pats, **opts)
686 addremove(ui, repo, *pats, **opts)
701 cwd = repo.getcwd()
687 cwd = repo.getcwd()
702 if not pats and cwd:
688 if not pats and cwd:
703 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
689 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
704 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
690 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
705 fns, match, anypats = matchpats(repo, (pats and repo.getcwd()) or '',
691 fns, match, anypats = matchpats(repo, (pats and repo.getcwd()) or '',
706 pats, opts)
692 pats, opts)
707 if pats:
693 if pats:
708 c, a, d, u = repo.changes(files=fns, match=match)
694 c, a, d, u = repo.changes(files=fns, match=match)
709 files = c + a + [fn for fn in d if repo.dirstate.state(fn) == 'r']
695 files = c + a + [fn for fn in d if repo.dirstate.state(fn) == 'r']
710 else:
696 else:
711 files = []
697 files = []
712 try:
698 try:
713 repo.commit(files, message, opts['user'], opts['date'], match)
699 repo.commit(files, message, opts['user'], opts['date'], match)
714 except ValueError, inst:
700 except ValueError, inst:
715 raise util.Abort(str(inst))
701 raise util.Abort(str(inst))
716
702
717 def docopy(ui, repo, pats, opts):
703 def docopy(ui, repo, pats, opts):
718 if not pats:
704 if not pats:
719 raise util.Abort('no source or destination specified')
705 raise util.Abort('no source or destination specified')
720 elif len(pats) == 1:
706 elif len(pats) == 1:
721 raise util.Abort('no destination specified')
707 raise util.Abort('no destination specified')
722 pats = list(pats)
708 pats = list(pats)
723 dest = pats.pop()
709 dest = pats.pop()
724 sources = []
710 sources = []
725
711
726 def okaytocopy(abs, rel, exact):
712 def okaytocopy(abs, rel, exact):
727 reasons = {'?': 'is not managed',
713 reasons = {'?': 'is not managed',
728 'a': 'has been marked for add'}
714 'a': 'has been marked for add'}
729 reason = reasons.get(repo.dirstate.state(abs))
715 reason = reasons.get(repo.dirstate.state(abs))
730 if reason:
716 if reason:
731 if exact: ui.warn('%s: not copying - file %s\n' % (rel, reason))
717 if exact: ui.warn('%s: not copying - file %s\n' % (rel, reason))
732 else:
718 else:
733 return True
719 return True
734
720
735 for src, abs, rel, exact in walk(repo, pats, opts):
721 for src, abs, rel, exact in walk(repo, pats, opts):
736 if okaytocopy(abs, rel, exact):
722 if okaytocopy(abs, rel, exact):
737 sources.append((abs, rel, exact))
723 sources.append((abs, rel, exact))
738 if not sources:
724 if not sources:
739 raise util.Abort('no files to copy')
725 raise util.Abort('no files to copy')
740
726
741 cwd = repo.getcwd()
727 cwd = repo.getcwd()
742 absdest = util.canonpath(repo.root, cwd, dest)
728 absdest = util.canonpath(repo.root, cwd, dest)
743 reldest = util.pathto(cwd, absdest)
729 reldest = util.pathto(cwd, absdest)
744 if os.path.exists(reldest):
730 if os.path.exists(reldest):
745 destisfile = not os.path.isdir(reldest)
731 destisfile = not os.path.isdir(reldest)
746 else:
732 else:
747 destisfile = len(sources) == 1 or repo.dirstate.state(absdest) != '?'
733 destisfile = len(sources) == 1 or repo.dirstate.state(absdest) != '?'
748
734
749 if destisfile:
735 if destisfile:
750 if opts['parents']:
736 if opts['parents']:
751 raise util.Abort('with --parents, destination must be a directory')
737 raise util.Abort('with --parents, destination must be a directory')
752 elif len(sources) > 1:
738 elif len(sources) > 1:
753 raise util.Abort('with multiple sources, destination must be a '
739 raise util.Abort('with multiple sources, destination must be a '
754 'directory')
740 'directory')
755 errs, copied = 0, []
741 errs, copied = 0, []
756 for abs, rel, exact in sources:
742 for abs, rel, exact in sources:
757 if opts['parents']:
743 if opts['parents']:
758 mydest = os.path.join(dest, rel)
744 mydest = os.path.join(dest, rel)
759 elif destisfile:
745 elif destisfile:
760 mydest = reldest
746 mydest = reldest
761 else:
747 else:
762 mydest = os.path.join(dest, os.path.basename(rel))
748 mydest = os.path.join(dest, os.path.basename(rel))
763 myabsdest = util.canonpath(repo.root, cwd, mydest)
749 myabsdest = util.canonpath(repo.root, cwd, mydest)
764 myreldest = util.pathto(cwd, myabsdest)
750 myreldest = util.pathto(cwd, myabsdest)
765 if not opts['force'] and repo.dirstate.state(myabsdest) not in 'a?':
751 if not opts['force'] and repo.dirstate.state(myabsdest) not in 'a?':
766 ui.warn('%s: not overwriting - file already managed\n' % myreldest)
752 ui.warn('%s: not overwriting - file already managed\n' % myreldest)
767 continue
753 continue
768 mydestdir = os.path.dirname(myreldest) or '.'
754 mydestdir = os.path.dirname(myreldest) or '.'
769 if not opts['after']:
755 if not opts['after']:
770 try:
756 try:
771 if opts['parents']: os.makedirs(mydestdir)
757 if opts['parents']: os.makedirs(mydestdir)
772 elif not destisfile: os.mkdir(mydestdir)
758 elif not destisfile: os.mkdir(mydestdir)
773 except OSError, inst:
759 except OSError, inst:
774 if inst.errno != errno.EEXIST: raise
760 if inst.errno != errno.EEXIST: raise
775 if ui.verbose or not exact:
761 if ui.verbose or not exact:
776 ui.status('copying %s to %s\n' % (rel, myreldest))
762 ui.status('copying %s to %s\n' % (rel, myreldest))
777 if not opts['after']:
763 if not opts['after']:
778 try:
764 try:
779 shutil.copyfile(rel, myreldest)
765 shutil.copyfile(rel, myreldest)
780 n = repo.manifest.tip()
766 n = repo.manifest.tip()
781 mf = repo.manifest.readflags(n)
767 mf = repo.manifest.readflags(n)
782 util.set_exec(myreldest, util.is_exec(rel, mf[abs]))
768 util.set_exec(myreldest, util.is_exec(rel, mf[abs]))
783 except shutil.Error, inst:
769 except shutil.Error, inst:
784 raise util.Abort(str(inst))
770 raise util.Abort(str(inst))
785 except IOError, inst:
771 except IOError, inst:
786 if inst.errno == errno.ENOENT:
772 if inst.errno == errno.ENOENT:
787 ui.warn('%s: deleted in working copy\n' % rel)
773 ui.warn('%s: deleted in working copy\n' % rel)
788 else:
774 else:
789 ui.warn('%s: cannot copy - %s\n' % (rel, inst.strerror))
775 ui.warn('%s: cannot copy - %s\n' % (rel, inst.strerror))
790 errs += 1
776 errs += 1
791 continue
777 continue
792 repo.copy(abs, myabsdest)
778 repo.copy(abs, myabsdest)
793 copied.append((abs, rel, exact))
779 copied.append((abs, rel, exact))
794 if errs:
780 if errs:
795 ui.warn('(consider using --after)\n')
781 ui.warn('(consider using --after)\n')
796 return errs, copied
782 return errs, copied
797
783
798 def copy(ui, repo, *pats, **opts):
784 def copy(ui, repo, *pats, **opts):
799 """mark files as copied for the next commit"""
785 """mark files as copied for the next commit"""
800 errs, copied = docopy(ui, repo, pats, opts)
786 errs, copied = docopy(ui, repo, pats, opts)
801 return errs
787 return errs
802
788
803 def debugancestor(ui, index, rev1, rev2):
789 def debugancestor(ui, index, rev1, rev2):
804 """find the ancestor revision of two revisions in a given index"""
790 """find the ancestor revision of two revisions in a given index"""
805 r = revlog.revlog(file, index, "")
791 r = revlog.revlog(file, index, "")
806 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
792 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
807 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
793 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
808
794
809 def debugcheckstate(ui, repo):
795 def debugcheckstate(ui, repo):
810 """validate the correctness of the current dirstate"""
796 """validate the correctness of the current dirstate"""
811 parent1, parent2 = repo.dirstate.parents()
797 parent1, parent2 = repo.dirstate.parents()
812 repo.dirstate.read()
798 repo.dirstate.read()
813 dc = repo.dirstate.map
799 dc = repo.dirstate.map
814 keys = dc.keys()
800 keys = dc.keys()
815 keys.sort()
801 keys.sort()
816 m1n = repo.changelog.read(parent1)[0]
802 m1n = repo.changelog.read(parent1)[0]
817 m2n = repo.changelog.read(parent2)[0]
803 m2n = repo.changelog.read(parent2)[0]
818 m1 = repo.manifest.read(m1n)
804 m1 = repo.manifest.read(m1n)
819 m2 = repo.manifest.read(m2n)
805 m2 = repo.manifest.read(m2n)
820 errors = 0
806 errors = 0
821 for f in dc:
807 for f in dc:
822 state = repo.dirstate.state(f)
808 state = repo.dirstate.state(f)
823 if state in "nr" and f not in m1:
809 if state in "nr" and f not in m1:
824 ui.warn("%s in state %s, but not in manifest1\n" % (f, state))
810 ui.warn("%s in state %s, but not in manifest1\n" % (f, state))
825 errors += 1
811 errors += 1
826 if state in "a" and f in m1:
812 if state in "a" and f in m1:
827 ui.warn("%s in state %s, but also in manifest1\n" % (f, state))
813 ui.warn("%s in state %s, but also in manifest1\n" % (f, state))
828 errors += 1
814 errors += 1
829 if state in "m" and f not in m1 and f not in m2:
815 if state in "m" and f not in m1 and f not in m2:
830 ui.warn("%s in state %s, but not in either manifest\n" %
816 ui.warn("%s in state %s, but not in either manifest\n" %
831 (f, state))
817 (f, state))
832 errors += 1
818 errors += 1
833 for f in m1:
819 for f in m1:
834 state = repo.dirstate.state(f)
820 state = repo.dirstate.state(f)
835 if state not in "nrm":
821 if state not in "nrm":
836 ui.warn("%s in manifest1, but listed as state %s" % (f, state))
822 ui.warn("%s in manifest1, but listed as state %s" % (f, state))
837 errors += 1
823 errors += 1
838 if errors:
824 if errors:
839 raise util.Abort(".hg/dirstate inconsistent with current parent's manifest")
825 raise util.Abort(".hg/dirstate inconsistent with current parent's manifest")
840
826
841 def debugconfig(ui):
827 def debugconfig(ui):
842 """show combined config settings from all hgrc files"""
828 """show combined config settings from all hgrc files"""
843 try:
829 try:
844 repo = hg.repository(ui)
830 repo = hg.repository(ui)
845 except hg.RepoError:
831 except hg.RepoError:
846 pass
832 pass
847 for section, name, value in ui.walkconfig():
833 for section, name, value in ui.walkconfig():
848 ui.write('%s.%s=%s\n' % (section, name, value))
834 ui.write('%s.%s=%s\n' % (section, name, value))
849
835
850 def debugstate(ui, repo):
836 def debugstate(ui, repo):
851 """show the contents of the current dirstate"""
837 """show the contents of the current dirstate"""
852 repo.dirstate.read()
838 repo.dirstate.read()
853 dc = repo.dirstate.map
839 dc = repo.dirstate.map
854 keys = dc.keys()
840 keys = dc.keys()
855 keys.sort()
841 keys.sort()
856 for file_ in keys:
842 for file_ in keys:
857 ui.write("%c %3o %10d %s %s\n"
843 ui.write("%c %3o %10d %s %s\n"
858 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
844 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
859 time.strftime("%x %X",
845 time.strftime("%x %X",
860 time.localtime(dc[file_][3])), file_))
846 time.localtime(dc[file_][3])), file_))
861 for f in repo.dirstate.copies:
847 for f in repo.dirstate.copies:
862 ui.write("copy: %s -> %s\n" % (repo.dirstate.copies[f], f))
848 ui.write("copy: %s -> %s\n" % (repo.dirstate.copies[f], f))
863
849
864 def debugdata(ui, file_, rev):
850 def debugdata(ui, file_, rev):
865 """dump the contents of an data file revision"""
851 """dump the contents of an data file revision"""
866 r = revlog.revlog(file, file_[:-2] + ".i", file_)
852 r = revlog.revlog(file, file_[:-2] + ".i", file_)
867 try:
853 try:
868 ui.write(r.revision(r.lookup(rev)))
854 ui.write(r.revision(r.lookup(rev)))
869 except KeyError:
855 except KeyError:
870 raise util.Abort('invalid revision identifier %s', rev)
856 raise util.Abort('invalid revision identifier %s', rev)
871
857
872 def debugindex(ui, file_):
858 def debugindex(ui, file_):
873 """dump the contents of an index file"""
859 """dump the contents of an index file"""
874 r = revlog.revlog(file, file_, "")
860 r = revlog.revlog(file, file_, "")
875 ui.write(" rev offset length base linkrev" +
861 ui.write(" rev offset length base linkrev" +
876 " nodeid p1 p2\n")
862 " nodeid p1 p2\n")
877 for i in range(r.count()):
863 for i in range(r.count()):
878 e = r.index[i]
864 e = r.index[i]
879 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
865 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
880 i, e[0], e[1], e[2], e[3],
866 i, e[0], e[1], e[2], e[3],
881 short(e[6]), short(e[4]), short(e[5])))
867 short(e[6]), short(e[4]), short(e[5])))
882
868
883 def debugindexdot(ui, file_):
869 def debugindexdot(ui, file_):
884 """dump an index DAG as a .dot file"""
870 """dump an index DAG as a .dot file"""
885 r = revlog.revlog(file, file_, "")
871 r = revlog.revlog(file, file_, "")
886 ui.write("digraph G {\n")
872 ui.write("digraph G {\n")
887 for i in range(r.count()):
873 for i in range(r.count()):
888 e = r.index[i]
874 e = r.index[i]
889 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
875 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
890 if e[5] != nullid:
876 if e[5] != nullid:
891 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
877 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
892 ui.write("}\n")
878 ui.write("}\n")
893
879
894 def debugrename(ui, repo, file, rev=None):
880 def debugrename(ui, repo, file, rev=None):
895 """dump rename information"""
881 """dump rename information"""
896 r = repo.file(relpath(repo, [file])[0])
882 r = repo.file(relpath(repo, [file])[0])
897 if rev:
883 if rev:
898 try:
884 try:
899 # assume all revision numbers are for changesets
885 # assume all revision numbers are for changesets
900 n = repo.lookup(rev)
886 n = repo.lookup(rev)
901 change = repo.changelog.read(n)
887 change = repo.changelog.read(n)
902 m = repo.manifest.read(change[0])
888 m = repo.manifest.read(change[0])
903 n = m[relpath(repo, [file])[0]]
889 n = m[relpath(repo, [file])[0]]
904 except hg.RepoError, KeyError:
890 except hg.RepoError, KeyError:
905 n = r.lookup(rev)
891 n = r.lookup(rev)
906 else:
892 else:
907 n = r.tip()
893 n = r.tip()
908 m = r.renamed(n)
894 m = r.renamed(n)
909 if m:
895 if m:
910 ui.write("renamed from %s:%s\n" % (m[0], hex(m[1])))
896 ui.write("renamed from %s:%s\n" % (m[0], hex(m[1])))
911 else:
897 else:
912 ui.write("not renamed\n")
898 ui.write("not renamed\n")
913
899
914 def debugwalk(ui, repo, *pats, **opts):
900 def debugwalk(ui, repo, *pats, **opts):
915 """show how files match on given patterns"""
901 """show how files match on given patterns"""
916 items = list(walk(repo, pats, opts))
902 items = list(walk(repo, pats, opts))
917 if not items:
903 if not items:
918 return
904 return
919 fmt = '%%s %%-%ds %%-%ds %%s' % (
905 fmt = '%%s %%-%ds %%-%ds %%s' % (
920 max([len(abs) for (src, abs, rel, exact) in items]),
906 max([len(abs) for (src, abs, rel, exact) in items]),
921 max([len(rel) for (src, abs, rel, exact) in items]))
907 max([len(rel) for (src, abs, rel, exact) in items]))
922 for src, abs, rel, exact in items:
908 for src, abs, rel, exact in items:
923 line = fmt % (src, abs, rel, exact and 'exact' or '')
909 line = fmt % (src, abs, rel, exact and 'exact' or '')
924 ui.write("%s\n" % line.rstrip())
910 ui.write("%s\n" % line.rstrip())
925
911
926 def diff(ui, repo, *pats, **opts):
912 def diff(ui, repo, *pats, **opts):
927 """diff working directory (or selected files)"""
913 """diff working directory (or selected files)"""
928 node1, node2 = None, None
914 node1, node2 = None, None
929 revs = [repo.lookup(x) for x in opts['rev']]
915 revs = [repo.lookup(x) for x in opts['rev']]
930
916
931 if len(revs) > 0:
917 if len(revs) > 0:
932 node1 = revs[0]
918 node1 = revs[0]
933 if len(revs) > 1:
919 if len(revs) > 1:
934 node2 = revs[1]
920 node2 = revs[1]
935 if len(revs) > 2:
921 if len(revs) > 2:
936 raise util.Abort("too many revisions to diff")
922 raise util.Abort("too many revisions to diff")
937
923
938 fns, matchfn, anypats = matchpats(repo, repo.getcwd(), pats, opts)
924 fns, matchfn, anypats = matchpats(repo, repo.getcwd(), pats, opts)
939
925
940 dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn,
926 dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn,
941 text=opts['text'])
927 text=opts['text'])
942
928
943 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
929 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
944 node = repo.lookup(changeset)
930 node = repo.lookup(changeset)
945 prev, other = repo.changelog.parents(node)
931 prev, other = repo.changelog.parents(node)
946 change = repo.changelog.read(node)
932 change = repo.changelog.read(node)
947
933
948 fp = make_file(repo, repo.changelog, opts['output'],
934 fp = make_file(repo, repo.changelog, opts['output'],
949 node=node, total=total, seqno=seqno,
935 node=node, total=total, seqno=seqno,
950 revwidth=revwidth)
936 revwidth=revwidth)
951 if fp != sys.stdout:
937 if fp != sys.stdout:
952 ui.note("%s\n" % fp.name)
938 ui.note("%s\n" % fp.name)
953
939
954 fp.write("# HG changeset patch\n")
940 fp.write("# HG changeset patch\n")
955 fp.write("# User %s\n" % change[1])
941 fp.write("# User %s\n" % change[1])
956 fp.write("# Node ID %s\n" % hex(node))
942 fp.write("# Node ID %s\n" % hex(node))
957 fp.write("# Parent %s\n" % hex(prev))
943 fp.write("# Parent %s\n" % hex(prev))
958 if other != nullid:
944 if other != nullid:
959 fp.write("# Parent %s\n" % hex(other))
945 fp.write("# Parent %s\n" % hex(other))
960 fp.write(change[4].rstrip())
946 fp.write(change[4].rstrip())
961 fp.write("\n\n")
947 fp.write("\n\n")
962
948
963 dodiff(fp, ui, repo, prev, node, text=opts['text'])
949 dodiff(fp, ui, repo, prev, node, text=opts['text'])
964 if fp != sys.stdout:
950 if fp != sys.stdout:
965 fp.close()
951 fp.close()
966
952
967 def export(ui, repo, *changesets, **opts):
953 def export(ui, repo, *changesets, **opts):
968 """dump the header and diffs for one or more changesets"""
954 """dump the header and diffs for one or more changesets"""
969 if not changesets:
955 if not changesets:
970 raise util.Abort("export requires at least one changeset")
956 raise util.Abort("export requires at least one changeset")
971 seqno = 0
957 seqno = 0
972 revs = list(revrange(ui, repo, changesets))
958 revs = list(revrange(ui, repo, changesets))
973 total = len(revs)
959 total = len(revs)
974 revwidth = max(map(len, revs))
960 revwidth = max(map(len, revs))
975 ui.note(len(revs) > 1 and "Exporting patches:\n" or "Exporting patch:\n")
961 ui.note(len(revs) > 1 and "Exporting patches:\n" or "Exporting patch:\n")
976 for cset in revs:
962 for cset in revs:
977 seqno += 1
963 seqno += 1
978 doexport(ui, repo, cset, seqno, total, revwidth, opts)
964 doexport(ui, repo, cset, seqno, total, revwidth, opts)
979
965
980 def forget(ui, repo, *pats, **opts):
966 def forget(ui, repo, *pats, **opts):
981 """don't add the specified files on the next commit"""
967 """don't add the specified files on the next commit"""
982 forget = []
968 forget = []
983 for src, abs, rel, exact in walk(repo, pats, opts):
969 for src, abs, rel, exact in walk(repo, pats, opts):
984 if repo.dirstate.state(abs) == 'a':
970 if repo.dirstate.state(abs) == 'a':
985 forget.append(abs)
971 forget.append(abs)
986 if ui.verbose or not exact:
972 if ui.verbose or not exact:
987 ui.status('forgetting ', rel, '\n')
973 ui.status('forgetting ', rel, '\n')
988 repo.forget(forget)
974 repo.forget(forget)
989
975
990 def grep(ui, repo, pattern, *pats, **opts):
976 def grep(ui, repo, pattern, *pats, **opts):
991 """search for a pattern in specified files and revisions"""
977 """search for a pattern in specified files and revisions"""
992 reflags = 0
978 reflags = 0
993 if opts['ignore_case']:
979 if opts['ignore_case']:
994 reflags |= re.I
980 reflags |= re.I
995 regexp = re.compile(pattern, reflags)
981 regexp = re.compile(pattern, reflags)
996 sep, eol = ':', '\n'
982 sep, eol = ':', '\n'
997 if opts['print0']:
983 if opts['print0']:
998 sep = eol = '\0'
984 sep = eol = '\0'
999
985
1000 fcache = {}
986 fcache = {}
1001 def getfile(fn):
987 def getfile(fn):
1002 if fn not in fcache:
988 if fn not in fcache:
1003 fcache[fn] = repo.file(fn)
989 fcache[fn] = repo.file(fn)
1004 return fcache[fn]
990 return fcache[fn]
1005
991
1006 def matchlines(body):
992 def matchlines(body):
1007 begin = 0
993 begin = 0
1008 linenum = 0
994 linenum = 0
1009 while True:
995 while True:
1010 match = regexp.search(body, begin)
996 match = regexp.search(body, begin)
1011 if not match:
997 if not match:
1012 break
998 break
1013 mstart, mend = match.span()
999 mstart, mend = match.span()
1014 linenum += body.count('\n', begin, mstart) + 1
1000 linenum += body.count('\n', begin, mstart) + 1
1015 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1001 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1016 lend = body.find('\n', mend)
1002 lend = body.find('\n', mend)
1017 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1003 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1018 begin = lend + 1
1004 begin = lend + 1
1019
1005
1020 class linestate:
1006 class linestate:
1021 def __init__(self, line, linenum, colstart, colend):
1007 def __init__(self, line, linenum, colstart, colend):
1022 self.line = line
1008 self.line = line
1023 self.linenum = linenum
1009 self.linenum = linenum
1024 self.colstart = colstart
1010 self.colstart = colstart
1025 self.colend = colend
1011 self.colend = colend
1026 def __eq__(self, other):
1012 def __eq__(self, other):
1027 return self.line == other.line
1013 return self.line == other.line
1028 def __hash__(self):
1014 def __hash__(self):
1029 return hash(self.line)
1015 return hash(self.line)
1030
1016
1031 matches = {}
1017 matches = {}
1032 def grepbody(fn, rev, body):
1018 def grepbody(fn, rev, body):
1033 matches[rev].setdefault(fn, {})
1019 matches[rev].setdefault(fn, {})
1034 m = matches[rev][fn]
1020 m = matches[rev][fn]
1035 for lnum, cstart, cend, line in matchlines(body):
1021 for lnum, cstart, cend, line in matchlines(body):
1036 s = linestate(line, lnum, cstart, cend)
1022 s = linestate(line, lnum, cstart, cend)
1037 m[s] = s
1023 m[s] = s
1038
1024
1039 prev = {}
1025 prev = {}
1040 ucache = {}
1026 ucache = {}
1041 def display(fn, rev, states, prevstates):
1027 def display(fn, rev, states, prevstates):
1042 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
1028 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
1043 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
1029 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
1044 counts = {'-': 0, '+': 0}
1030 counts = {'-': 0, '+': 0}
1045 filerevmatches = {}
1031 filerevmatches = {}
1046 for l in diff:
1032 for l in diff:
1047 if incrementing or not opts['all']:
1033 if incrementing or not opts['all']:
1048 change = ((l in prevstates) and '-') or '+'
1034 change = ((l in prevstates) and '-') or '+'
1049 r = rev
1035 r = rev
1050 else:
1036 else:
1051 change = ((l in states) and '-') or '+'
1037 change = ((l in states) and '-') or '+'
1052 r = prev[fn]
1038 r = prev[fn]
1053 cols = [fn, str(rev)]
1039 cols = [fn, str(rev)]
1054 if opts['line_number']: cols.append(str(l.linenum))
1040 if opts['line_number']: cols.append(str(l.linenum))
1055 if opts['all']: cols.append(change)
1041 if opts['all']: cols.append(change)
1056 if opts['user']: cols.append(trimuser(ui, getchange(rev)[1], rev,
1042 if opts['user']: cols.append(trimuser(ui, getchange(rev)[1], rev,
1057 ucache))
1043 ucache))
1058 if opts['files_with_matches']:
1044 if opts['files_with_matches']:
1059 c = (fn, rev)
1045 c = (fn, rev)
1060 if c in filerevmatches: continue
1046 if c in filerevmatches: continue
1061 filerevmatches[c] = 1
1047 filerevmatches[c] = 1
1062 else:
1048 else:
1063 cols.append(l.line)
1049 cols.append(l.line)
1064 ui.write(sep.join(cols), eol)
1050 ui.write(sep.join(cols), eol)
1065 counts[change] += 1
1051 counts[change] += 1
1066 return counts['+'], counts['-']
1052 return counts['+'], counts['-']
1067
1053
1068 fstate = {}
1054 fstate = {}
1069 skip = {}
1055 skip = {}
1070 changeiter, getchange = walkchangerevs(ui, repo, repo.getcwd(), pats, opts)
1056 changeiter, getchange = walkchangerevs(ui, repo, repo.getcwd(), pats, opts)
1071 count = 0
1057 count = 0
1072 for st, rev, fns in changeiter:
1058 for st, rev, fns in changeiter:
1073 if st == 'window':
1059 if st == 'window':
1074 incrementing = rev
1060 incrementing = rev
1075 matches.clear()
1061 matches.clear()
1076 elif st == 'add':
1062 elif st == 'add':
1077 change = repo.changelog.read(repo.lookup(str(rev)))
1063 change = repo.changelog.read(repo.lookup(str(rev)))
1078 mf = repo.manifest.read(change[0])
1064 mf = repo.manifest.read(change[0])
1079 matches[rev] = {}
1065 matches[rev] = {}
1080 for fn in fns:
1066 for fn in fns:
1081 if fn in skip: continue
1067 if fn in skip: continue
1082 fstate.setdefault(fn, {})
1068 fstate.setdefault(fn, {})
1083 try:
1069 try:
1084 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1070 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1085 except KeyError:
1071 except KeyError:
1086 pass
1072 pass
1087 elif st == 'iter':
1073 elif st == 'iter':
1088 states = matches[rev].items()
1074 states = matches[rev].items()
1089 states.sort()
1075 states.sort()
1090 for fn, m in states:
1076 for fn, m in states:
1091 if fn in skip: continue
1077 if fn in skip: continue
1092 if incrementing or not opts['all'] or fstate[fn]:
1078 if incrementing or not opts['all'] or fstate[fn]:
1093 pos, neg = display(fn, rev, m, fstate[fn])
1079 pos, neg = display(fn, rev, m, fstate[fn])
1094 count += pos + neg
1080 count += pos + neg
1095 if pos and not opts['all']:
1081 if pos and not opts['all']:
1096 skip[fn] = True
1082 skip[fn] = True
1097 fstate[fn] = m
1083 fstate[fn] = m
1098 prev[fn] = rev
1084 prev[fn] = rev
1099
1085
1100 if not incrementing:
1086 if not incrementing:
1101 fstate = fstate.items()
1087 fstate = fstate.items()
1102 fstate.sort()
1088 fstate.sort()
1103 for fn, state in fstate:
1089 for fn, state in fstate:
1104 if fn in skip: continue
1090 if fn in skip: continue
1105 display(fn, rev, {}, state)
1091 display(fn, rev, {}, state)
1106 return (count == 0 and 1) or 0
1092 return (count == 0 and 1) or 0
1107
1093
1108 def heads(ui, repo, **opts):
1094 def heads(ui, repo, **opts):
1109 """show current repository heads"""
1095 """show current repository heads"""
1110 heads = repo.changelog.heads()
1096 heads = repo.changelog.heads()
1111 br = None
1097 br = None
1112 if opts['branches']:
1098 if opts['branches']:
1113 br = repo.branchlookup(heads)
1099 br = repo.branchlookup(heads)
1114 for n in repo.changelog.heads():
1100 for n in repo.changelog.heads():
1115 show_changeset(ui, repo, changenode=n, brinfo=br)
1101 show_changeset(ui, repo, changenode=n, brinfo=br)
1116
1102
1117 def identify(ui, repo):
1103 def identify(ui, repo):
1118 """print information about the working copy"""
1104 """print information about the working copy"""
1119 parents = [p for p in repo.dirstate.parents() if p != nullid]
1105 parents = [p for p in repo.dirstate.parents() if p != nullid]
1120 if not parents:
1106 if not parents:
1121 ui.write("unknown\n")
1107 ui.write("unknown\n")
1122 return
1108 return
1123
1109
1124 hexfunc = ui.verbose and hex or short
1110 hexfunc = ui.verbose and hex or short
1125 (c, a, d, u) = repo.changes()
1111 (c, a, d, u) = repo.changes()
1126 output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
1112 output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
1127 (c or a or d) and "+" or "")]
1113 (c or a or d) and "+" or "")]
1128
1114
1129 if not ui.quiet:
1115 if not ui.quiet:
1130 # multiple tags for a single parent separated by '/'
1116 # multiple tags for a single parent separated by '/'
1131 parenttags = ['/'.join(tags)
1117 parenttags = ['/'.join(tags)
1132 for tags in map(repo.nodetags, parents) if tags]
1118 for tags in map(repo.nodetags, parents) if tags]
1133 # tags for multiple parents separated by ' + '
1119 # tags for multiple parents separated by ' + '
1134 if parenttags:
1120 if parenttags:
1135 output.append(' + '.join(parenttags))
1121 output.append(' + '.join(parenttags))
1136
1122
1137 ui.write("%s\n" % ' '.join(output))
1123 ui.write("%s\n" % ' '.join(output))
1138
1124
1139 def import_(ui, repo, patch1, *patches, **opts):
1125 def import_(ui, repo, patch1, *patches, **opts):
1140 """import an ordered set of patches"""
1126 """import an ordered set of patches"""
1141 patches = (patch1,) + patches
1127 patches = (patch1,) + patches
1142
1128
1143 if not opts['force']:
1129 if not opts['force']:
1144 (c, a, d, u) = repo.changes()
1130 (c, a, d, u) = repo.changes()
1145 if c or a or d:
1131 if c or a or d:
1146 raise util.Abort("outstanding uncommitted changes")
1132 raise util.Abort("outstanding uncommitted changes")
1147
1133
1148 d = opts["base"]
1134 d = opts["base"]
1149 strip = opts["strip"]
1135 strip = opts["strip"]
1150
1136
1151 mailre = re.compile(r'(?:From |[\w-]+:)')
1137 mailre = re.compile(r'(?:From |[\w-]+:)')
1152 diffre = re.compile(r'(?:diff -|--- .*\s+\w+ \w+ +\d+ \d+:\d+:\d+ \d+)')
1138 diffre = re.compile(r'(?:diff -|--- .*\s+\w+ \w+ +\d+ \d+:\d+:\d+ \d+)')
1153
1139
1154 for patch in patches:
1140 for patch in patches:
1155 ui.status("applying %s\n" % patch)
1141 ui.status("applying %s\n" % patch)
1156 pf = os.path.join(d, patch)
1142 pf = os.path.join(d, patch)
1157
1143
1158 message = []
1144 message = []
1159 user = None
1145 user = None
1160 hgpatch = False
1146 hgpatch = False
1161 for line in file(pf):
1147 for line in file(pf):
1162 line = line.rstrip()
1148 line = line.rstrip()
1163 if (not message and not hgpatch and
1149 if (not message and not hgpatch and
1164 mailre.match(line) and not opts['force']):
1150 mailre.match(line) and not opts['force']):
1165 if len(line) > 35: line = line[:32] + '...'
1151 if len(line) > 35: line = line[:32] + '...'
1166 raise util.Abort('first line looks like a '
1152 raise util.Abort('first line looks like a '
1167 'mail header: ' + line)
1153 'mail header: ' + line)
1168 if diffre.match(line):
1154 if diffre.match(line):
1169 break
1155 break
1170 elif hgpatch:
1156 elif hgpatch:
1171 # parse values when importing the result of an hg export
1157 # parse values when importing the result of an hg export
1172 if line.startswith("# User "):
1158 if line.startswith("# User "):
1173 user = line[7:]
1159 user = line[7:]
1174 ui.debug('User: %s\n' % user)
1160 ui.debug('User: %s\n' % user)
1175 elif not line.startswith("# ") and line:
1161 elif not line.startswith("# ") and line:
1176 message.append(line)
1162 message.append(line)
1177 hgpatch = False
1163 hgpatch = False
1178 elif line == '# HG changeset patch':
1164 elif line == '# HG changeset patch':
1179 hgpatch = True
1165 hgpatch = True
1180 message = [] # We may have collected garbage
1166 message = [] # We may have collected garbage
1181 else:
1167 else:
1182 message.append(line)
1168 message.append(line)
1183
1169
1184 # make sure message isn't empty
1170 # make sure message isn't empty
1185 if not message:
1171 if not message:
1186 message = "imported patch %s\n" % patch
1172 message = "imported patch %s\n" % patch
1187 else:
1173 else:
1188 message = "%s\n" % '\n'.join(message)
1174 message = "%s\n" % '\n'.join(message)
1189 ui.debug('message:\n%s\n' % message)
1175 ui.debug('message:\n%s\n' % message)
1190
1176
1191 files = util.patch(strip, pf, ui)
1177 files = util.patch(strip, pf, ui)
1192
1178
1193 if len(files) > 0:
1179 if len(files) > 0:
1194 addremove(ui, repo, *files)
1180 addremove(ui, repo, *files)
1195 repo.commit(files, message, user)
1181 repo.commit(files, message, user)
1196
1182
1197 def incoming(ui, repo, source="default", **opts):
1183 def incoming(ui, repo, source="default", **opts):
1198 """show new changesets found in source"""
1184 """show new changesets found in source"""
1199 source = ui.expandpath(source)
1185 source = ui.expandpath(source)
1200 other = hg.repository(ui, source)
1186 other = hg.repository(ui, source)
1201 if not other.local():
1187 if not other.local():
1202 raise util.Abort("incoming doesn't work for remote repositories yet")
1188 raise util.Abort("incoming doesn't work for remote repositories yet")
1203 o = repo.findincoming(other)
1189 o = repo.findincoming(other)
1204 if not o:
1190 if not o:
1205 return
1191 return
1206 o = other.newer(o)
1192 o = other.newer(o)
1207 for n in o:
1193 for n in o:
1208 show_changeset(ui, other, changenode=n)
1194 show_changeset(ui, other, changenode=n)
1209 if opts['patch']:
1195 if opts['patch']:
1210 prev = other.changelog.parents(n)[0]
1196 prev = other.changelog.parents(n)[0]
1211 dodiff(ui, ui, other, prev, n)
1197 dodiff(ui, ui, other, prev, n)
1212 ui.write("\n")
1198 ui.write("\n")
1213
1199
1214 def init(ui, dest="."):
1200 def init(ui, dest="."):
1215 """create a new repository in the given directory"""
1201 """create a new repository in the given directory"""
1216 if not os.path.exists(dest):
1202 if not os.path.exists(dest):
1217 os.mkdir(dest)
1203 os.mkdir(dest)
1218 hg.repository(ui, dest, create=1)
1204 hg.repository(ui, dest, create=1)
1219
1205
1220 def locate(ui, repo, *pats, **opts):
1206 def locate(ui, repo, *pats, **opts):
1221 """locate files matching specific patterns"""
1207 """locate files matching specific patterns"""
1222 end = opts['print0'] and '\0' or '\n'
1208 end = opts['print0'] and '\0' or '\n'
1223
1209
1224 for src, abs, rel, exact in walk(repo, pats, opts, '(?:.*/|)'):
1210 for src, abs, rel, exact in walk(repo, pats, opts, '(?:.*/|)'):
1225 if repo.dirstate.state(abs) == '?':
1211 if repo.dirstate.state(abs) == '?':
1226 continue
1212 continue
1227 if opts['fullpath']:
1213 if opts['fullpath']:
1228 ui.write(os.path.join(repo.root, abs), end)
1214 ui.write(os.path.join(repo.root, abs), end)
1229 else:
1215 else:
1230 ui.write(rel, end)
1216 ui.write(rel, end)
1231
1217
1232 def log(ui, repo, *pats, **opts):
1218 def log(ui, repo, *pats, **opts):
1233 """show revision history of entire repository or files"""
1219 """show revision history of entire repository or files"""
1234 class dui:
1220 class dui:
1235 # Implement and delegate some ui protocol. Save hunks of
1221 # Implement and delegate some ui protocol. Save hunks of
1236 # output for later display in the desired order.
1222 # output for later display in the desired order.
1237 def __init__(self, ui):
1223 def __init__(self, ui):
1238 self.ui = ui
1224 self.ui = ui
1239 self.hunk = {}
1225 self.hunk = {}
1240 def bump(self, rev):
1226 def bump(self, rev):
1241 self.rev = rev
1227 self.rev = rev
1242 self.hunk[rev] = []
1228 self.hunk[rev] = []
1243 def note(self, *args):
1229 def note(self, *args):
1244 if self.verbose:
1230 if self.verbose:
1245 self.write(*args)
1231 self.write(*args)
1246 def status(self, *args):
1232 def status(self, *args):
1247 if not self.quiet:
1233 if not self.quiet:
1248 self.write(*args)
1234 self.write(*args)
1249 def write(self, *args):
1235 def write(self, *args):
1250 self.hunk[self.rev].append(args)
1236 self.hunk[self.rev].append(args)
1251 def __getattr__(self, key):
1237 def __getattr__(self, key):
1252 return getattr(self.ui, key)
1238 return getattr(self.ui, key)
1253 cwd = repo.getcwd()
1239 cwd = repo.getcwd()
1254 if not pats and cwd:
1240 if not pats and cwd:
1255 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
1241 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
1256 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
1242 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
1257 changeiter, getchange = walkchangerevs(ui, repo, (pats and cwd) or '',
1243 changeiter, getchange = walkchangerevs(ui, repo, (pats and cwd) or '',
1258 pats, opts)
1244 pats, opts)
1259 for st, rev, fns in changeiter:
1245 for st, rev, fns in changeiter:
1260 if st == 'window':
1246 if st == 'window':
1261 du = dui(ui)
1247 du = dui(ui)
1262 elif st == 'add':
1248 elif st == 'add':
1263 du.bump(rev)
1249 du.bump(rev)
1264 br = None
1250 br = None
1265 if opts['branch']:
1251 if opts['branch']:
1266 br = repo.branchlookup([repo.changelog.node(rev)])
1252 br = repo.branchlookup([repo.changelog.node(rev)])
1267 show_changeset(du, repo, rev, brinfo=br)
1253 show_changeset(du, repo, rev, brinfo=br)
1268 if opts['patch']:
1254 if opts['patch']:
1269 changenode = repo.changelog.node(rev)
1255 changenode = repo.changelog.node(rev)
1270 prev, other = repo.changelog.parents(changenode)
1256 prev, other = repo.changelog.parents(changenode)
1271 dodiff(du, du, repo, prev, changenode, fns)
1257 dodiff(du, du, repo, prev, changenode, fns)
1272 du.write("\n\n")
1258 du.write("\n\n")
1273 elif st == 'iter':
1259 elif st == 'iter':
1274 for args in du.hunk[rev]:
1260 for args in du.hunk[rev]:
1275 ui.write(*args)
1261 ui.write(*args)
1276
1262
1277 def manifest(ui, repo, rev=None):
1263 def manifest(ui, repo, rev=None):
1278 """output the latest or given revision of the project manifest"""
1264 """output the latest or given revision of the project manifest"""
1279 if rev:
1265 if rev:
1280 try:
1266 try:
1281 # assume all revision numbers are for changesets
1267 # assume all revision numbers are for changesets
1282 n = repo.lookup(rev)
1268 n = repo.lookup(rev)
1283 change = repo.changelog.read(n)
1269 change = repo.changelog.read(n)
1284 n = change[0]
1270 n = change[0]
1285 except hg.RepoError:
1271 except hg.RepoError:
1286 n = repo.manifest.lookup(rev)
1272 n = repo.manifest.lookup(rev)
1287 else:
1273 else:
1288 n = repo.manifest.tip()
1274 n = repo.manifest.tip()
1289 m = repo.manifest.read(n)
1275 m = repo.manifest.read(n)
1290 mf = repo.manifest.readflags(n)
1276 mf = repo.manifest.readflags(n)
1291 files = m.keys()
1277 files = m.keys()
1292 files.sort()
1278 files.sort()
1293
1279
1294 for f in files:
1280 for f in files:
1295 ui.write("%40s %3s %s\n" % (hex(m[f]), mf[f] and "755" or "644", f))
1281 ui.write("%40s %3s %s\n" % (hex(m[f]), mf[f] and "755" or "644", f))
1296
1282
1297 def outgoing(ui, repo, dest="default-push", **opts):
1283 def outgoing(ui, repo, dest="default-push", **opts):
1298 """show changesets not found in destination"""
1284 """show changesets not found in destination"""
1299 dest = ui.expandpath(dest)
1285 dest = ui.expandpath(dest)
1300 other = hg.repository(ui, dest)
1286 other = hg.repository(ui, dest)
1301 o = repo.findoutgoing(other)
1287 o = repo.findoutgoing(other)
1302 o = repo.newer(o)
1288 o = repo.newer(o)
1303 for n in o:
1289 for n in o:
1304 show_changeset(ui, repo, changenode=n)
1290 show_changeset(ui, repo, changenode=n)
1305 if opts['patch']:
1291 if opts['patch']:
1306 prev = repo.changelog.parents(n)[0]
1292 prev = repo.changelog.parents(n)[0]
1307 dodiff(ui, ui, repo, prev, n)
1293 dodiff(ui, ui, repo, prev, n)
1308 ui.write("\n")
1294 ui.write("\n")
1309
1295
1310 def parents(ui, repo, rev=None):
1296 def parents(ui, repo, rev=None):
1311 """show the parents of the working dir or revision"""
1297 """show the parents of the working dir or revision"""
1312 if rev:
1298 if rev:
1313 p = repo.changelog.parents(repo.lookup(rev))
1299 p = repo.changelog.parents(repo.lookup(rev))
1314 else:
1300 else:
1315 p = repo.dirstate.parents()
1301 p = repo.dirstate.parents()
1316
1302
1317 for n in p:
1303 for n in p:
1318 if n != nullid:
1304 if n != nullid:
1319 show_changeset(ui, repo, changenode=n)
1305 show_changeset(ui, repo, changenode=n)
1320
1306
1321 def paths(ui, search=None):
1307 def paths(ui, search=None):
1322 """show definition of symbolic path names"""
1308 """show definition of symbolic path names"""
1323 try:
1309 try:
1324 repo = hg.repository(ui=ui)
1310 repo = hg.repository(ui=ui)
1325 except hg.RepoError:
1311 except hg.RepoError:
1326 pass
1312 pass
1327
1313
1328 if search:
1314 if search:
1329 for name, path in ui.configitems("paths"):
1315 for name, path in ui.configitems("paths"):
1330 if name == search:
1316 if name == search:
1331 ui.write("%s\n" % path)
1317 ui.write("%s\n" % path)
1332 return
1318 return
1333 ui.warn("not found!\n")
1319 ui.warn("not found!\n")
1334 return 1
1320 return 1
1335 else:
1321 else:
1336 for name, path in ui.configitems("paths"):
1322 for name, path in ui.configitems("paths"):
1337 ui.write("%s = %s\n" % (name, path))
1323 ui.write("%s = %s\n" % (name, path))
1338
1324
1339 def pull(ui, repo, source="default", **opts):
1325 def pull(ui, repo, source="default", **opts):
1340 """pull changes from the specified source"""
1326 """pull changes from the specified source"""
1341 source = ui.expandpath(source)
1327 source = ui.expandpath(source)
1342 ui.status('pulling from %s\n' % (source))
1328 ui.status('pulling from %s\n' % (source))
1343
1329
1344 if opts['ssh']:
1330 if opts['ssh']:
1345 ui.setconfig("ui", "ssh", opts['ssh'])
1331 ui.setconfig("ui", "ssh", opts['ssh'])
1346 if opts['remotecmd']:
1332 if opts['remotecmd']:
1347 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1333 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1348
1334
1349 other = hg.repository(ui, source)
1335 other = hg.repository(ui, source)
1350 r = repo.pull(other)
1336 r = repo.pull(other)
1351 if not r:
1337 if not r:
1352 if opts['update']:
1338 if opts['update']:
1353 return update(ui, repo)
1339 return update(ui, repo)
1354 else:
1340 else:
1355 ui.status("(run 'hg update' to get a working copy)\n")
1341 ui.status("(run 'hg update' to get a working copy)\n")
1356
1342
1357 return r
1343 return r
1358
1344
1359 def push(ui, repo, dest="default-push", force=False, ssh=None, remotecmd=None):
1345 def push(ui, repo, dest="default-push", force=False, ssh=None, remotecmd=None):
1360 """push changes to the specified destination"""
1346 """push changes to the specified destination"""
1361 dest = ui.expandpath(dest)
1347 dest = ui.expandpath(dest)
1362 ui.status('pushing to %s\n' % (dest))
1348 ui.status('pushing to %s\n' % (dest))
1363
1349
1364 if ssh:
1350 if ssh:
1365 ui.setconfig("ui", "ssh", ssh)
1351 ui.setconfig("ui", "ssh", ssh)
1366 if remotecmd:
1352 if remotecmd:
1367 ui.setconfig("ui", "remotecmd", remotecmd)
1353 ui.setconfig("ui", "remotecmd", remotecmd)
1368
1354
1369 other = hg.repository(ui, dest)
1355 other = hg.repository(ui, dest)
1370 r = repo.push(other, force)
1356 r = repo.push(other, force)
1371 return r
1357 return r
1372
1358
1373 def rawcommit(ui, repo, *flist, **rc):
1359 def rawcommit(ui, repo, *flist, **rc):
1374 "raw commit interface"
1360 "raw commit interface"
1375 if rc['text']:
1361 if rc['text']:
1376 ui.warn("Warning: -t and --text is deprecated,"
1362 ui.warn("Warning: -t and --text is deprecated,"
1377 " please use -m or --message instead.\n")
1363 " please use -m or --message instead.\n")
1378 message = rc['message'] or rc['text']
1364 message = rc['message'] or rc['text']
1379 if not message and rc['logfile']:
1365 if not message and rc['logfile']:
1380 try:
1366 try:
1381 message = open(rc['logfile']).read()
1367 message = open(rc['logfile']).read()
1382 except IOError:
1368 except IOError:
1383 pass
1369 pass
1384 if not message and not rc['logfile']:
1370 if not message and not rc['logfile']:
1385 raise util.Abort("missing commit message")
1371 raise util.Abort("missing commit message")
1386
1372
1387 files = relpath(repo, list(flist))
1373 files = relpath(repo, list(flist))
1388 if rc['files']:
1374 if rc['files']:
1389 files += open(rc['files']).read().splitlines()
1375 files += open(rc['files']).read().splitlines()
1390
1376
1391 rc['parent'] = map(repo.lookup, rc['parent'])
1377 rc['parent'] = map(repo.lookup, rc['parent'])
1392
1378
1393 try:
1379 try:
1394 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
1380 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
1395 except ValueError, inst:
1381 except ValueError, inst:
1396 raise util.Abort(str(inst))
1382 raise util.Abort(str(inst))
1397
1383
1398 def recover(ui, repo):
1384 def recover(ui, repo):
1399 """roll back an interrupted transaction"""
1385 """roll back an interrupted transaction"""
1400 repo.recover()
1386 repo.recover()
1401
1387
1402 def remove(ui, repo, pat, *pats, **opts):
1388 def remove(ui, repo, pat, *pats, **opts):
1403 """remove the specified files on the next commit"""
1389 """remove the specified files on the next commit"""
1404 names = []
1390 names = []
1405 def okaytoremove(abs, rel, exact):
1391 def okaytoremove(abs, rel, exact):
1406 c, a, d, u = repo.changes(files = [abs])
1392 c, a, d, u = repo.changes(files = [abs])
1407 reason = None
1393 reason = None
1408 if c: reason = 'is modified'
1394 if c: reason = 'is modified'
1409 elif a: reason = 'has been marked for add'
1395 elif a: reason = 'has been marked for add'
1410 elif u: reason = 'is not managed'
1396 elif u: reason = 'is not managed'
1411 if reason:
1397 if reason:
1412 if exact: ui.warn('not removing %s: file %s\n' % (rel, reason))
1398 if exact: ui.warn('not removing %s: file %s\n' % (rel, reason))
1413 else:
1399 else:
1414 return True
1400 return True
1415 for src, abs, rel, exact in walk(repo, (pat,) + pats, opts):
1401 for src, abs, rel, exact in walk(repo, (pat,) + pats, opts):
1416 if okaytoremove(abs, rel, exact):
1402 if okaytoremove(abs, rel, exact):
1417 if ui.verbose or not exact: ui.status('removing %s\n' % rel)
1403 if ui.verbose or not exact: ui.status('removing %s\n' % rel)
1418 names.append(abs)
1404 names.append(abs)
1419 for name in names:
1405 for name in names:
1420 try:
1406 try:
1421 os.unlink(name)
1407 os.unlink(name)
1422 except OSError, inst:
1408 except OSError, inst:
1423 if inst.errno != errno.ENOENT: raise
1409 if inst.errno != errno.ENOENT: raise
1424 repo.remove(names)
1410 repo.remove(names)
1425
1411
1426 def rename(ui, repo, *pats, **opts):
1412 def rename(ui, repo, *pats, **opts):
1427 """rename files; equivalent of copy + remove"""
1413 """rename files; equivalent of copy + remove"""
1428 errs, copied = docopy(ui, repo, pats, opts)
1414 errs, copied = docopy(ui, repo, pats, opts)
1429 names = []
1415 names = []
1430 for abs, rel, exact in copied:
1416 for abs, rel, exact in copied:
1431 if ui.verbose or not exact: ui.status('removing %s\n' % rel)
1417 if ui.verbose or not exact: ui.status('removing %s\n' % rel)
1432 try:
1418 try:
1433 os.unlink(rel)
1419 os.unlink(rel)
1434 except OSError, inst:
1420 except OSError, inst:
1435 if inst.errno != errno.ENOENT: raise
1421 if inst.errno != errno.ENOENT: raise
1436 names.append(abs)
1422 names.append(abs)
1437 repo.remove(names)
1423 repo.remove(names)
1438 return errs
1424 return errs
1439
1425
1440 def revert(ui, repo, *names, **opts):
1426 def revert(ui, repo, *names, **opts):
1441 """revert modified files or dirs back to their unmodified states"""
1427 """revert modified files or dirs back to their unmodified states"""
1442 node = opts['rev'] and repo.lookup(opts['rev']) or \
1428 node = opts['rev'] and repo.lookup(opts['rev']) or \
1443 repo.dirstate.parents()[0]
1429 repo.dirstate.parents()[0]
1444 root = os.path.realpath(repo.root)
1430 root = os.path.realpath(repo.root)
1445
1431
1446 def trimpath(p):
1432 def trimpath(p):
1447 p = os.path.realpath(p)
1433 p = os.path.realpath(p)
1448 if p.startswith(root):
1434 if p.startswith(root):
1449 rest = p[len(root):]
1435 rest = p[len(root):]
1450 if not rest:
1436 if not rest:
1451 return rest
1437 return rest
1452 if p.startswith(os.sep):
1438 if p.startswith(os.sep):
1453 return rest[1:]
1439 return rest[1:]
1454 return p
1440 return p
1455
1441
1456 relnames = map(trimpath, names or [os.getcwd()])
1442 relnames = map(trimpath, names or [os.getcwd()])
1457 chosen = {}
1443 chosen = {}
1458
1444
1459 def choose(name):
1445 def choose(name):
1460 def body(name):
1446 def body(name):
1461 for r in relnames:
1447 for r in relnames:
1462 if not name.startswith(r):
1448 if not name.startswith(r):
1463 continue
1449 continue
1464 rest = name[len(r):]
1450 rest = name[len(r):]
1465 if not rest:
1451 if not rest:
1466 return r, True
1452 return r, True
1467 depth = rest.count(os.sep)
1453 depth = rest.count(os.sep)
1468 if not r:
1454 if not r:
1469 if depth == 0 or not opts['nonrecursive']:
1455 if depth == 0 or not opts['nonrecursive']:
1470 return r, True
1456 return r, True
1471 elif rest[0] == os.sep:
1457 elif rest[0] == os.sep:
1472 if depth == 1 or not opts['nonrecursive']:
1458 if depth == 1 or not opts['nonrecursive']:
1473 return r, True
1459 return r, True
1474 return None, False
1460 return None, False
1475 relname, ret = body(name)
1461 relname, ret = body(name)
1476 if ret:
1462 if ret:
1477 chosen[relname] = 1
1463 chosen[relname] = 1
1478 return ret
1464 return ret
1479
1465
1480 r = repo.update(node, False, True, choose, False)
1466 r = repo.update(node, False, True, choose, False)
1481 for n in relnames:
1467 for n in relnames:
1482 if n not in chosen:
1468 if n not in chosen:
1483 ui.warn('error: no matches for %s\n' % n)
1469 ui.warn('error: no matches for %s\n' % n)
1484 r = 1
1470 r = 1
1485 sys.stdout.flush()
1471 sys.stdout.flush()
1486 return r
1472 return r
1487
1473
1488 def root(ui, repo):
1474 def root(ui, repo):
1489 """print the root (top) of the current working dir"""
1475 """print the root (top) of the current working dir"""
1490 ui.write(repo.root + "\n")
1476 ui.write(repo.root + "\n")
1491
1477
1492 def serve(ui, repo, **opts):
1478 def serve(ui, repo, **opts):
1493 """export the repository via HTTP"""
1479 """export the repository via HTTP"""
1494
1480
1495 if opts["stdio"]:
1481 if opts["stdio"]:
1496 fin, fout = sys.stdin, sys.stdout
1482 fin, fout = sys.stdin, sys.stdout
1497 sys.stdout = sys.stderr
1483 sys.stdout = sys.stderr
1498
1484
1499 def getarg():
1485 def getarg():
1500 argline = fin.readline()[:-1]
1486 argline = fin.readline()[:-1]
1501 arg, l = argline.split()
1487 arg, l = argline.split()
1502 val = fin.read(int(l))
1488 val = fin.read(int(l))
1503 return arg, val
1489 return arg, val
1504 def respond(v):
1490 def respond(v):
1505 fout.write("%d\n" % len(v))
1491 fout.write("%d\n" % len(v))
1506 fout.write(v)
1492 fout.write(v)
1507 fout.flush()
1493 fout.flush()
1508
1494
1509 lock = None
1495 lock = None
1510
1496
1511 while 1:
1497 while 1:
1512 cmd = fin.readline()[:-1]
1498 cmd = fin.readline()[:-1]
1513 if cmd == '':
1499 if cmd == '':
1514 return
1500 return
1515 if cmd == "heads":
1501 if cmd == "heads":
1516 h = repo.heads()
1502 h = repo.heads()
1517 respond(" ".join(map(hex, h)) + "\n")
1503 respond(" ".join(map(hex, h)) + "\n")
1518 if cmd == "lock":
1504 if cmd == "lock":
1519 lock = repo.lock()
1505 lock = repo.lock()
1520 respond("")
1506 respond("")
1521 if cmd == "unlock":
1507 if cmd == "unlock":
1522 if lock:
1508 if lock:
1523 lock.release()
1509 lock.release()
1524 lock = None
1510 lock = None
1525 respond("")
1511 respond("")
1526 elif cmd == "branches":
1512 elif cmd == "branches":
1527 arg, nodes = getarg()
1513 arg, nodes = getarg()
1528 nodes = map(bin, nodes.split(" "))
1514 nodes = map(bin, nodes.split(" "))
1529 r = []
1515 r = []
1530 for b in repo.branches(nodes):
1516 for b in repo.branches(nodes):
1531 r.append(" ".join(map(hex, b)) + "\n")
1517 r.append(" ".join(map(hex, b)) + "\n")
1532 respond("".join(r))
1518 respond("".join(r))
1533 elif cmd == "between":
1519 elif cmd == "between":
1534 arg, pairs = getarg()
1520 arg, pairs = getarg()
1535 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
1521 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
1536 r = []
1522 r = []
1537 for b in repo.between(pairs):
1523 for b in repo.between(pairs):
1538 r.append(" ".join(map(hex, b)) + "\n")
1524 r.append(" ".join(map(hex, b)) + "\n")
1539 respond("".join(r))
1525 respond("".join(r))
1540 elif cmd == "changegroup":
1526 elif cmd == "changegroup":
1541 nodes = []
1527 nodes = []
1542 arg, roots = getarg()
1528 arg, roots = getarg()
1543 nodes = map(bin, roots.split(" "))
1529 nodes = map(bin, roots.split(" "))
1544
1530
1545 cg = repo.changegroup(nodes)
1531 cg = repo.changegroup(nodes)
1546 while 1:
1532 while 1:
1547 d = cg.read(4096)
1533 d = cg.read(4096)
1548 if not d:
1534 if not d:
1549 break
1535 break
1550 fout.write(d)
1536 fout.write(d)
1551
1537
1552 fout.flush()
1538 fout.flush()
1553
1539
1554 elif cmd == "addchangegroup":
1540 elif cmd == "addchangegroup":
1555 if not lock:
1541 if not lock:
1556 respond("not locked")
1542 respond("not locked")
1557 continue
1543 continue
1558 respond("")
1544 respond("")
1559
1545
1560 r = repo.addchangegroup(fin)
1546 r = repo.addchangegroup(fin)
1561 respond("")
1547 respond("")
1562
1548
1563 optlist = "name templates style address port ipv6 accesslog errorlog"
1549 optlist = "name templates style address port ipv6 accesslog errorlog"
1564 for o in optlist.split():
1550 for o in optlist.split():
1565 if opts[o]:
1551 if opts[o]:
1566 ui.setconfig("web", o, opts[o])
1552 ui.setconfig("web", o, opts[o])
1567
1553
1568 try:
1554 try:
1569 httpd = hgweb.create_server(repo)
1555 httpd = hgweb.create_server(repo)
1570 except socket.error, inst:
1556 except socket.error, inst:
1571 raise util.Abort('cannot start server: ' + inst.args[1])
1557 raise util.Abort('cannot start server: ' + inst.args[1])
1572
1558
1573 if ui.verbose:
1559 if ui.verbose:
1574 addr, port = httpd.socket.getsockname()
1560 addr, port = httpd.socket.getsockname()
1575 if addr == '0.0.0.0':
1561 if addr == '0.0.0.0':
1576 addr = socket.gethostname()
1562 addr = socket.gethostname()
1577 else:
1563 else:
1578 try:
1564 try:
1579 addr = socket.gethostbyaddr(addr)[0]
1565 addr = socket.gethostbyaddr(addr)[0]
1580 except socket.error:
1566 except socket.error:
1581 pass
1567 pass
1582 if port != 80:
1568 if port != 80:
1583 ui.status('listening at http://%s:%d/\n' % (addr, port))
1569 ui.status('listening at http://%s:%d/\n' % (addr, port))
1584 else:
1570 else:
1585 ui.status('listening at http://%s/\n' % addr)
1571 ui.status('listening at http://%s/\n' % addr)
1586 httpd.serve_forever()
1572 httpd.serve_forever()
1587
1573
1588 def status(ui, repo, *pats, **opts):
1574 def status(ui, repo, *pats, **opts):
1589 '''show changed files in the working directory
1575 '''show changed files in the working directory
1590
1576
1591 M = modified
1577 M = modified
1592 A = added
1578 A = added
1593 R = removed
1579 R = removed
1594 ? = not tracked
1580 ? = not tracked
1595 '''
1581 '''
1596
1582
1597 cwd = repo.getcwd()
1583 cwd = repo.getcwd()
1598 files, matchfn, anypats = matchpats(repo, cwd, pats, opts)
1584 files, matchfn, anypats = matchpats(repo, cwd, pats, opts)
1599 (c, a, d, u) = [[util.pathto(cwd, x) for x in n]
1585 (c, a, d, u) = [[util.pathto(cwd, x) for x in n]
1600 for n in repo.changes(files=files, match=matchfn)]
1586 for n in repo.changes(files=files, match=matchfn)]
1601
1587
1602 changetypes = [('modified', 'M', c),
1588 changetypes = [('modified', 'M', c),
1603 ('added', 'A', a),
1589 ('added', 'A', a),
1604 ('removed', 'R', d),
1590 ('removed', 'R', d),
1605 ('unknown', '?', u)]
1591 ('unknown', '?', u)]
1606
1592
1607 end = opts['print0'] and '\0' or '\n'
1593 end = opts['print0'] and '\0' or '\n'
1608
1594
1609 for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]]
1595 for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]]
1610 or changetypes):
1596 or changetypes):
1611 if opts['no_status']:
1597 if opts['no_status']:
1612 format = "%%s%s" % end
1598 format = "%%s%s" % end
1613 else:
1599 else:
1614 format = "%s %%s%s" % (char, end);
1600 format = "%s %%s%s" % (char, end);
1615
1601
1616 for f in changes:
1602 for f in changes:
1617 ui.write(format % f)
1603 ui.write(format % f)
1618
1604
1619 def tag(ui, repo, name, rev=None, **opts):
1605 def tag(ui, repo, name, rev=None, **opts):
1620 """add a tag for the current tip or a given revision"""
1606 """add a tag for the current tip or a given revision"""
1621 if opts['text']:
1607 if opts['text']:
1622 ui.warn("Warning: -t and --text is deprecated,"
1608 ui.warn("Warning: -t and --text is deprecated,"
1623 " please use -m or --message instead.\n")
1609 " please use -m or --message instead.\n")
1624 if name == "tip":
1610 if name == "tip":
1625 raise util.Abort("the name 'tip' is reserved")
1611 raise util.Abort("the name 'tip' is reserved")
1626 if rev:
1612 if rev:
1627 r = hex(repo.lookup(rev))
1613 r = hex(repo.lookup(rev))
1628 else:
1614 else:
1629 r = hex(repo.changelog.tip())
1615 r = hex(repo.changelog.tip())
1630
1616
1631 if name.find(revrangesep) >= 0:
1617 if name.find(revrangesep) >= 0:
1632 raise util.Abort("'%s' cannot be used in a tag name" % revrangesep)
1618 raise util.Abort("'%s' cannot be used in a tag name" % revrangesep)
1633
1619
1634 if opts['local']:
1620 if opts['local']:
1635 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
1621 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
1636 return
1622 return
1637
1623
1638 (c, a, d, u) = repo.changes()
1624 (c, a, d, u) = repo.changes()
1639 for x in (c, a, d, u):
1625 for x in (c, a, d, u):
1640 if ".hgtags" in x:
1626 if ".hgtags" in x:
1641 raise util.Abort("working copy of .hgtags is changed "
1627 raise util.Abort("working copy of .hgtags is changed "
1642 "(please commit .hgtags manually)")
1628 "(please commit .hgtags manually)")
1643
1629
1644 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
1630 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
1645 if repo.dirstate.state(".hgtags") == '?':
1631 if repo.dirstate.state(".hgtags") == '?':
1646 repo.add([".hgtags"])
1632 repo.add([".hgtags"])
1647
1633
1648 message = (opts['message'] or opts['text'] or
1634 message = (opts['message'] or opts['text'] or
1649 "Added tag %s for changeset %s" % (name, r))
1635 "Added tag %s for changeset %s" % (name, r))
1650 try:
1636 try:
1651 repo.commit([".hgtags"], message, opts['user'], opts['date'])
1637 repo.commit([".hgtags"], message, opts['user'], opts['date'])
1652 except ValueError, inst:
1638 except ValueError, inst:
1653 raise util.Abort(str(inst))
1639 raise util.Abort(str(inst))
1654
1640
1655 def tags(ui, repo):
1641 def tags(ui, repo):
1656 """list repository tags"""
1642 """list repository tags"""
1657
1643
1658 l = repo.tagslist()
1644 l = repo.tagslist()
1659 l.reverse()
1645 l.reverse()
1660 for t, n in l:
1646 for t, n in l:
1661 try:
1647 try:
1662 r = "%5d:%s" % (repo.changelog.rev(n), hex(n))
1648 r = "%5d:%s" % (repo.changelog.rev(n), hex(n))
1663 except KeyError:
1649 except KeyError:
1664 r = " ?:?"
1650 r = " ?:?"
1665 ui.write("%-30s %s\n" % (t, r))
1651 ui.write("%-30s %s\n" % (t, r))
1666
1652
1667 def tip(ui, repo):
1653 def tip(ui, repo):
1668 """show the tip revision"""
1654 """show the tip revision"""
1669 n = repo.changelog.tip()
1655 n = repo.changelog.tip()
1670 show_changeset(ui, repo, changenode=n)
1656 show_changeset(ui, repo, changenode=n)
1671
1657
1672 def unbundle(ui, repo, fname):
1658 def unbundle(ui, repo, fname):
1673 """apply a changegroup file"""
1659 """apply a changegroup file"""
1674 f = urllib.urlopen(fname)
1660 f = urllib.urlopen(fname)
1675
1661
1676 if f.read(4) != "HG10":
1662 if f.read(4) != "HG10":
1677 raise util.Abort("%s: not a Mercurial bundle file" % fname)
1663 raise util.Abort("%s: not a Mercurial bundle file" % fname)
1678
1664
1679 class bzread:
1665 class bzread:
1680 def __init__(self, f):
1666 def __init__(self, f):
1681 self.zd = bz2.BZ2Decompressor()
1667 self.zd = bz2.BZ2Decompressor()
1682 self.f = f
1668 self.f = f
1683 self.buf = ""
1669 self.buf = ""
1684 def read(self, l):
1670 def read(self, l):
1685 while l > len(self.buf):
1671 while l > len(self.buf):
1686 r = self.f.read(4096)
1672 r = self.f.read(4096)
1687 if r:
1673 if r:
1688 self.buf += self.zd.decompress(r)
1674 self.buf += self.zd.decompress(r)
1689 else:
1675 else:
1690 break
1676 break
1691 d, self.buf = self.buf[:l], self.buf[l:]
1677 d, self.buf = self.buf[:l], self.buf[l:]
1692 return d
1678 return d
1693
1679
1694 repo.addchangegroup(bzread(f))
1680 repo.addchangegroup(bzread(f))
1695
1681
1696 def undo(ui, repo):
1682 def undo(ui, repo):
1697 """undo the last commit or pull
1683 """undo the last commit or pull
1698
1684
1699 Roll back the last pull or commit transaction on the
1685 Roll back the last pull or commit transaction on the
1700 repository, restoring the project to its earlier state.
1686 repository, restoring the project to its earlier state.
1701
1687
1702 This command should be used with care. There is only one level of
1688 This command should be used with care. There is only one level of
1703 undo and there is no redo.
1689 undo and there is no redo.
1704
1690
1705 This command is not intended for use on public repositories. Once
1691 This command is not intended for use on public repositories. Once
1706 a change is visible for pull by other users, undoing it locally is
1692 a change is visible for pull by other users, undoing it locally is
1707 ineffective.
1693 ineffective.
1708 """
1694 """
1709 repo.undo()
1695 repo.undo()
1710
1696
1711 def update(ui, repo, node=None, merge=False, clean=False, branch=None):
1697 def update(ui, repo, node=None, merge=False, clean=False, branch=None):
1712 '''update or merge working directory
1698 '''update or merge working directory
1713
1699
1714 If there are no outstanding changes in the working directory and
1700 If there are no outstanding changes in the working directory and
1715 there is a linear relationship between the current version and the
1701 there is a linear relationship between the current version and the
1716 requested version, the result is the requested version.
1702 requested version, the result is the requested version.
1717
1703
1718 Otherwise the result is a merge between the contents of the
1704 Otherwise the result is a merge between the contents of the
1719 current working directory and the requested version. Files that
1705 current working directory and the requested version. Files that
1720 changed between either parent are marked as changed for the next
1706 changed between either parent are marked as changed for the next
1721 commit and a commit must be performed before any further updates
1707 commit and a commit must be performed before any further updates
1722 are allowed.
1708 are allowed.
1723 '''
1709 '''
1724 if branch:
1710 if branch:
1725 br = repo.branchlookup(branch=branch)
1711 br = repo.branchlookup(branch=branch)
1726 found = []
1712 found = []
1727 for x in br:
1713 for x in br:
1728 if branch in br[x]:
1714 if branch in br[x]:
1729 found.append(x)
1715 found.append(x)
1730 if len(found) > 1:
1716 if len(found) > 1:
1731 ui.warn("Found multiple heads for %s\n" % branch)
1717 ui.warn("Found multiple heads for %s\n" % branch)
1732 for x in found:
1718 for x in found:
1733 show_changeset(ui, repo, changenode=x, brinfo=br)
1719 show_changeset(ui, repo, changenode=x, brinfo=br)
1734 return 1
1720 return 1
1735 if len(found) == 1:
1721 if len(found) == 1:
1736 node = found[0]
1722 node = found[0]
1737 ui.warn("Using head %s for branch %s\n" % (short(node), branch))
1723 ui.warn("Using head %s for branch %s\n" % (short(node), branch))
1738 else:
1724 else:
1739 ui.warn("branch %s not found\n" % (branch))
1725 ui.warn("branch %s not found\n" % (branch))
1740 return 1
1726 return 1
1741 else:
1727 else:
1742 node = node and repo.lookup(node) or repo.changelog.tip()
1728 node = node and repo.lookup(node) or repo.changelog.tip()
1743 return repo.update(node, allow=merge, force=clean)
1729 return repo.update(node, allow=merge, force=clean)
1744
1730
1745 def verify(ui, repo):
1731 def verify(ui, repo):
1746 """verify the integrity of the repository"""
1732 """verify the integrity of the repository"""
1747 return repo.verify()
1733 return repo.verify()
1748
1734
1749 # Command options and aliases are listed here, alphabetically
1735 # Command options and aliases are listed here, alphabetically
1750
1736
1751 table = {
1737 table = {
1752 "^add":
1738 "^add":
1753 (add,
1739 (add,
1754 [('I', 'include', [], 'include path in search'),
1740 [('I', 'include', [], 'include path in search'),
1755 ('X', 'exclude', [], 'exclude path from search')],
1741 ('X', 'exclude', [], 'exclude path from search')],
1756 "hg add [OPTION]... [FILE]..."),
1742 "hg add [OPTION]... [FILE]..."),
1757 "addremove":
1743 "addremove":
1758 (addremove,
1744 (addremove,
1759 [('I', 'include', [], 'include path in search'),
1745 [('I', 'include', [], 'include path in search'),
1760 ('X', 'exclude', [], 'exclude path from search')],
1746 ('X', 'exclude', [], 'exclude path from search')],
1761 "hg addremove [OPTION]... [FILE]..."),
1747 "hg addremove [OPTION]... [FILE]..."),
1762 "^annotate":
1748 "^annotate":
1763 (annotate,
1749 (annotate,
1764 [('r', 'rev', '', 'revision'),
1750 [('r', 'rev', '', 'revision'),
1765 ('a', 'text', None, 'treat all files as text'),
1751 ('a', 'text', None, 'treat all files as text'),
1766 ('u', 'user', None, 'show user'),
1752 ('u', 'user', None, 'show user'),
1767 ('n', 'number', None, 'show revision number'),
1753 ('n', 'number', None, 'show revision number'),
1768 ('c', 'changeset', None, 'show changeset'),
1754 ('c', 'changeset', None, 'show changeset'),
1769 ('I', 'include', [], 'include path in search'),
1755 ('I', 'include', [], 'include path in search'),
1770 ('X', 'exclude', [], 'exclude path from search')],
1756 ('X', 'exclude', [], 'exclude path from search')],
1771 'hg annotate [OPTION]... FILE...'),
1757 'hg annotate [OPTION]... FILE...'),
1772 "bundle":
1758 "bundle":
1773 (bundle,
1759 (bundle,
1774 [],
1760 [],
1775 'hg bundle FILE DEST'),
1761 'hg bundle FILE DEST'),
1776 "cat":
1762 "cat":
1777 (cat,
1763 (cat,
1778 [('I', 'include', [], 'include path in search'),
1764 [('I', 'include', [], 'include path in search'),
1779 ('X', 'exclude', [], 'exclude path from search'),
1765 ('X', 'exclude', [], 'exclude path from search'),
1780 ('o', 'output', "", 'output to file'),
1766 ('o', 'output', "", 'output to file'),
1781 ('r', 'rev', '', 'revision')],
1767 ('r', 'rev', '', 'revision')],
1782 'hg cat [OPTION]... FILE...'),
1768 'hg cat [OPTION]... FILE...'),
1783 "^clone":
1769 "^clone":
1784 (clone,
1770 (clone,
1785 [('U', 'noupdate', None, 'skip update after cloning'),
1771 [('U', 'noupdate', None, 'skip update after cloning'),
1786 ('e', 'ssh', "", 'ssh command'),
1772 ('e', 'ssh', "", 'ssh command'),
1787 ('', 'pull', None, 'use pull protocol to copy metadata'),
1773 ('', 'pull', None, 'use pull protocol to copy metadata'),
1788 ('', 'remotecmd', "", 'remote hg command')],
1774 ('', 'remotecmd', "", 'remote hg command')],
1789 'hg clone [OPTION]... SOURCE [DEST]'),
1775 'hg clone [OPTION]... SOURCE [DEST]'),
1790 "^commit|ci":
1776 "^commit|ci":
1791 (commit,
1777 (commit,
1792 [('A', 'addremove', None, 'run add/remove during commit'),
1778 [('A', 'addremove', None, 'run add/remove during commit'),
1793 ('I', 'include', [], 'include path in search'),
1779 ('I', 'include', [], 'include path in search'),
1794 ('X', 'exclude', [], 'exclude path from search'),
1780 ('X', 'exclude', [], 'exclude path from search'),
1795 ('m', 'message', "", 'commit message'),
1781 ('m', 'message', "", 'commit message'),
1796 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1782 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1797 ('l', 'logfile', "", 'commit message file'),
1783 ('l', 'logfile', "", 'commit message file'),
1798 ('d', 'date', "", 'date code'),
1784 ('d', 'date', "", 'date code'),
1799 ('u', 'user', "", 'user')],
1785 ('u', 'user', "", 'user')],
1800 'hg commit [OPTION]... [FILE]...'),
1786 'hg commit [OPTION]... [FILE]...'),
1801 "copy|cp": (copy,
1787 "copy|cp": (copy,
1802 [('I', 'include', [], 'include path in search'),
1788 [('I', 'include', [], 'include path in search'),
1803 ('X', 'exclude', [], 'exclude path from search'),
1789 ('X', 'exclude', [], 'exclude path from search'),
1804 ('A', 'after', None, 'record a copy after it has happened'),
1790 ('A', 'after', None, 'record a copy after it has happened'),
1805 ('f', 'force', None, 'replace destination if it exists'),
1791 ('f', 'force', None, 'replace destination if it exists'),
1806 ('p', 'parents', None, 'append source path to dest')],
1792 ('p', 'parents', None, 'append source path to dest')],
1807 'hg copy [OPTION]... [SOURCE]... DEST'),
1793 'hg copy [OPTION]... [SOURCE]... DEST'),
1808 "debugancestor": (debugancestor, [], 'debugancestor INDEX REV1 REV2'),
1794 "debugancestor": (debugancestor, [], 'debugancestor INDEX REV1 REV2'),
1809 "debugcheckstate": (debugcheckstate, [], 'debugcheckstate'),
1795 "debugcheckstate": (debugcheckstate, [], 'debugcheckstate'),
1810 "debugconfig": (debugconfig, [], 'debugconfig'),
1796 "debugconfig": (debugconfig, [], 'debugconfig'),
1811 "debugstate": (debugstate, [], 'debugstate'),
1797 "debugstate": (debugstate, [], 'debugstate'),
1812 "debugdata": (debugdata, [], 'debugdata FILE REV'),
1798 "debugdata": (debugdata, [], 'debugdata FILE REV'),
1813 "debugindex": (debugindex, [], 'debugindex FILE'),
1799 "debugindex": (debugindex, [], 'debugindex FILE'),
1814 "debugindexdot": (debugindexdot, [], 'debugindexdot FILE'),
1800 "debugindexdot": (debugindexdot, [], 'debugindexdot FILE'),
1815 "debugrename": (debugrename, [], 'debugrename FILE [REV]'),
1801 "debugrename": (debugrename, [], 'debugrename FILE [REV]'),
1816 "debugwalk":
1802 "debugwalk":
1817 (debugwalk,
1803 (debugwalk,
1818 [('I', 'include', [], 'include path in search'),
1804 [('I', 'include', [], 'include path in search'),
1819 ('X', 'exclude', [], 'exclude path from search')],
1805 ('X', 'exclude', [], 'exclude path from search')],
1820 'debugwalk [OPTION]... [FILE]...'),
1806 'debugwalk [OPTION]... [FILE]...'),
1821 "^diff":
1807 "^diff":
1822 (diff,
1808 (diff,
1823 [('r', 'rev', [], 'revision'),
1809 [('r', 'rev', [], 'revision'),
1824 ('a', 'text', None, 'treat all files as text'),
1810 ('a', 'text', None, 'treat all files as text'),
1825 ('I', 'include', [], 'include path in search'),
1811 ('I', 'include', [], 'include path in search'),
1826 ('X', 'exclude', [], 'exclude path from search')],
1812 ('X', 'exclude', [], 'exclude path from search')],
1827 'hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...'),
1813 'hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...'),
1828 "^export":
1814 "^export":
1829 (export,
1815 (export,
1830 [('o', 'output', "", 'output to file'),
1816 [('o', 'output', "", 'output to file'),
1831 ('a', 'text', None, 'treat all files as text')],
1817 ('a', 'text', None, 'treat all files as text')],
1832 "hg export [-a] [-o OUTFILE] REV..."),
1818 "hg export [-a] [-o OUTFILE] REV..."),
1833 "forget":
1819 "forget":
1834 (forget,
1820 (forget,
1835 [('I', 'include', [], 'include path in search'),
1821 [('I', 'include', [], 'include path in search'),
1836 ('X', 'exclude', [], 'exclude path from search')],
1822 ('X', 'exclude', [], 'exclude path from search')],
1837 "hg forget [OPTION]... FILE..."),
1823 "hg forget [OPTION]... FILE..."),
1838 "grep":
1824 "grep":
1839 (grep,
1825 (grep,
1840 [('0', 'print0', None, 'end fields with NUL'),
1826 [('0', 'print0', None, 'end fields with NUL'),
1841 ('I', 'include', [], 'include path in search'),
1827 ('I', 'include', [], 'include path in search'),
1842 ('X', 'exclude', [], 'include path in search'),
1828 ('X', 'exclude', [], 'include path in search'),
1843 ('', 'all', None, 'print all revisions with matches'),
1829 ('', 'all', None, 'print all revisions with matches'),
1844 ('i', 'ignore-case', None, 'ignore case when matching'),
1830 ('i', 'ignore-case', None, 'ignore case when matching'),
1845 ('l', 'files-with-matches', None, 'print names of files and revs with matches'),
1831 ('l', 'files-with-matches', None, 'print names of files and revs with matches'),
1846 ('n', 'line-number', None, 'print line numbers'),
1832 ('n', 'line-number', None, 'print line numbers'),
1847 ('r', 'rev', [], 'search in revision rev'),
1833 ('r', 'rev', [], 'search in revision rev'),
1848 ('u', 'user', None, 'print user who made change')],
1834 ('u', 'user', None, 'print user who made change')],
1849 "hg grep [OPTION]... PATTERN [FILE]..."),
1835 "hg grep [OPTION]... PATTERN [FILE]..."),
1850 "heads":
1836 "heads":
1851 (heads,
1837 (heads,
1852 [('b', 'branches', None, 'find branch info')],
1838 [('b', 'branches', None, 'find branch info')],
1853 'hg heads [-b]'),
1839 'hg heads [-b]'),
1854 "help": (help_, [], 'hg help [COMMAND]'),
1840 "help": (help_, [], 'hg help [COMMAND]'),
1855 "identify|id": (identify, [], 'hg identify'),
1841 "identify|id": (identify, [], 'hg identify'),
1856 "import|patch":
1842 "import|patch":
1857 (import_,
1843 (import_,
1858 [('p', 'strip', 1, 'path strip'),
1844 [('p', 'strip', 1, 'path strip'),
1859 ('f', 'force', None, 'skip check for outstanding changes'),
1845 ('f', 'force', None, 'skip check for outstanding changes'),
1860 ('b', 'base', "", 'base path')],
1846 ('b', 'base', "", 'base path')],
1861 "hg import [-f] [-p NUM] [-b BASE] PATCH..."),
1847 "hg import [-f] [-p NUM] [-b BASE] PATCH..."),
1862 "incoming|in": (incoming,
1848 "incoming|in": (incoming,
1863 [('p', 'patch', None, 'show patch')],
1849 [('p', 'patch', None, 'show patch')],
1864 'hg incoming [-p] [SOURCE]'),
1850 'hg incoming [-p] [SOURCE]'),
1865 "^init": (init, [], 'hg init [DEST]'),
1851 "^init": (init, [], 'hg init [DEST]'),
1866 "locate":
1852 "locate":
1867 (locate,
1853 (locate,
1868 [('r', 'rev', '', 'revision'),
1854 [('r', 'rev', '', 'revision'),
1869 ('0', 'print0', None, 'end filenames with NUL'),
1855 ('0', 'print0', None, 'end filenames with NUL'),
1870 ('f', 'fullpath', None, 'print complete paths'),
1856 ('f', 'fullpath', None, 'print complete paths'),
1871 ('I', 'include', [], 'include path in search'),
1857 ('I', 'include', [], 'include path in search'),
1872 ('X', 'exclude', [], 'exclude path from search')],
1858 ('X', 'exclude', [], 'exclude path from search')],
1873 'hg locate [OPTION]... [PATTERN]...'),
1859 'hg locate [OPTION]... [PATTERN]...'),
1874 "^log|history":
1860 "^log|history":
1875 (log,
1861 (log,
1876 [('I', 'include', [], 'include path in search'),
1862 [('I', 'include', [], 'include path in search'),
1877 ('X', 'exclude', [], 'exclude path from search'),
1863 ('X', 'exclude', [], 'exclude path from search'),
1878 ('b', 'branch', None, 'show branches'),
1864 ('b', 'branch', None, 'show branches'),
1879 ('r', 'rev', [], 'revision'),
1865 ('r', 'rev', [], 'revision'),
1880 ('p', 'patch', None, 'show patch')],
1866 ('p', 'patch', None, 'show patch')],
1881 'hg log [-I] [-X] [-r REV]... [-p] [FILE]'),
1867 'hg log [-I] [-X] [-r REV]... [-p] [FILE]'),
1882 "manifest": (manifest, [], 'hg manifest [REV]'),
1868 "manifest": (manifest, [], 'hg manifest [REV]'),
1883 "outgoing|out": (outgoing,
1869 "outgoing|out": (outgoing,
1884 [('p', 'patch', None, 'show patch')],
1870 [('p', 'patch', None, 'show patch')],
1885 'hg outgoing [-p] [DEST]'),
1871 'hg outgoing [-p] [DEST]'),
1886 "parents": (parents, [], 'hg parents [REV]'),
1872 "parents": (parents, [], 'hg parents [REV]'),
1887 "paths": (paths, [], 'hg paths [NAME]'),
1873 "paths": (paths, [], 'hg paths [NAME]'),
1888 "^pull":
1874 "^pull":
1889 (pull,
1875 (pull,
1890 [('u', 'update', None, 'update working directory'),
1876 [('u', 'update', None, 'update working directory'),
1891 ('e', 'ssh', "", 'ssh command'),
1877 ('e', 'ssh', "", 'ssh command'),
1892 ('', 'remotecmd', "", 'remote hg command')],
1878 ('', 'remotecmd', "", 'remote hg command')],
1893 'hg pull [-u] [-e FILE] [--remotecmd FILE] [SOURCE]'),
1879 'hg pull [-u] [-e FILE] [--remotecmd FILE] [SOURCE]'),
1894 "^push":
1880 "^push":
1895 (push,
1881 (push,
1896 [('f', 'force', None, 'force push'),
1882 [('f', 'force', None, 'force push'),
1897 ('e', 'ssh', "", 'ssh command'),
1883 ('e', 'ssh', "", 'ssh command'),
1898 ('', 'remotecmd', "", 'remote hg command')],
1884 ('', 'remotecmd', "", 'remote hg command')],
1899 'hg push [-f] [-e FILE] [--remotecmd FILE] [DEST]'),
1885 'hg push [-f] [-e FILE] [--remotecmd FILE] [DEST]'),
1900 "rawcommit":
1886 "rawcommit":
1901 (rawcommit,
1887 (rawcommit,
1902 [('p', 'parent', [], 'parent'),
1888 [('p', 'parent', [], 'parent'),
1903 ('d', 'date', "", 'date code'),
1889 ('d', 'date', "", 'date code'),
1904 ('u', 'user', "", 'user'),
1890 ('u', 'user', "", 'user'),
1905 ('F', 'files', "", 'file list'),
1891 ('F', 'files', "", 'file list'),
1906 ('m', 'message', "", 'commit message'),
1892 ('m', 'message', "", 'commit message'),
1907 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1893 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1908 ('l', 'logfile', "", 'commit message file')],
1894 ('l', 'logfile', "", 'commit message file')],
1909 'hg rawcommit [OPTION]... [FILE]...'),
1895 'hg rawcommit [OPTION]... [FILE]...'),
1910 "recover": (recover, [], "hg recover"),
1896 "recover": (recover, [], "hg recover"),
1911 "^remove|rm": (remove,
1897 "^remove|rm": (remove,
1912 [('I', 'include', [], 'include path in search'),
1898 [('I', 'include', [], 'include path in search'),
1913 ('X', 'exclude', [], 'exclude path from search')],
1899 ('X', 'exclude', [], 'exclude path from search')],
1914 "hg remove [OPTION]... FILE..."),
1900 "hg remove [OPTION]... FILE..."),
1915 "rename|mv": (rename,
1901 "rename|mv": (rename,
1916 [('I', 'include', [], 'include path in search'),
1902 [('I', 'include', [], 'include path in search'),
1917 ('X', 'exclude', [], 'exclude path from search'),
1903 ('X', 'exclude', [], 'exclude path from search'),
1918 ('A', 'after', None, 'record a copy after it has happened'),
1904 ('A', 'after', None, 'record a copy after it has happened'),
1919 ('f', 'force', None, 'replace destination if it exists'),
1905 ('f', 'force', None, 'replace destination if it exists'),
1920 ('p', 'parents', None, 'append source path to dest')],
1906 ('p', 'parents', None, 'append source path to dest')],
1921 'hg rename [OPTION]... [SOURCE]... DEST'),
1907 'hg rename [OPTION]... [SOURCE]... DEST'),
1922 "^revert":
1908 "^revert":
1923 (revert,
1909 (revert,
1924 [("n", "nonrecursive", None, "don't recurse into subdirs"),
1910 [("n", "nonrecursive", None, "don't recurse into subdirs"),
1925 ("r", "rev", "", "revision")],
1911 ("r", "rev", "", "revision")],
1926 "hg revert [-n] [-r REV] [NAME]..."),
1912 "hg revert [-n] [-r REV] [NAME]..."),
1927 "root": (root, [], "hg root"),
1913 "root": (root, [], "hg root"),
1928 "^serve":
1914 "^serve":
1929 (serve,
1915 (serve,
1930 [('A', 'accesslog', '', 'access log file'),
1916 [('A', 'accesslog', '', 'access log file'),
1931 ('E', 'errorlog', '', 'error log file'),
1917 ('E', 'errorlog', '', 'error log file'),
1932 ('p', 'port', 0, 'listen port'),
1918 ('p', 'port', 0, 'listen port'),
1933 ('a', 'address', '', 'interface address'),
1919 ('a', 'address', '', 'interface address'),
1934 ('n', 'name', "", 'repository name'),
1920 ('n', 'name', "", 'repository name'),
1935 ('', 'stdio', None, 'for remote clients'),
1921 ('', 'stdio', None, 'for remote clients'),
1936 ('t', 'templates', "", 'template directory'),
1922 ('t', 'templates', "", 'template directory'),
1937 ('', 'style', "", 'template style'),
1923 ('', 'style', "", 'template style'),
1938 ('6', 'ipv6', None, 'use IPv6 in addition to IPv4')],
1924 ('6', 'ipv6', None, 'use IPv6 in addition to IPv4')],
1939 "hg serve [OPTION]..."),
1925 "hg serve [OPTION]..."),
1940 "^status":
1926 "^status":
1941 (status,
1927 (status,
1942 [('m', 'modified', None, 'show only modified files'),
1928 [('m', 'modified', None, 'show only modified files'),
1943 ('a', 'added', None, 'show only added files'),
1929 ('a', 'added', None, 'show only added files'),
1944 ('r', 'removed', None, 'show only removed files'),
1930 ('r', 'removed', None, 'show only removed files'),
1945 ('u', 'unknown', None, 'show only unknown (not tracked) files'),
1931 ('u', 'unknown', None, 'show only unknown (not tracked) files'),
1946 ('n', 'no-status', None, 'hide status prefix'),
1932 ('n', 'no-status', None, 'hide status prefix'),
1947 ('0', 'print0', None, 'end filenames with NUL'),
1933 ('0', 'print0', None, 'end filenames with NUL'),
1948 ('I', 'include', [], 'include path in search'),
1934 ('I', 'include', [], 'include path in search'),
1949 ('X', 'exclude', [], 'exclude path from search')],
1935 ('X', 'exclude', [], 'exclude path from search')],
1950 "hg status [OPTION]... [FILE]..."),
1936 "hg status [OPTION]... [FILE]..."),
1951 "tag":
1937 "tag":
1952 (tag,
1938 (tag,
1953 [('l', 'local', None, 'make the tag local'),
1939 [('l', 'local', None, 'make the tag local'),
1954 ('m', 'message', "", 'commit message'),
1940 ('m', 'message', "", 'commit message'),
1955 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1941 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1956 ('d', 'date', "", 'date code'),
1942 ('d', 'date', "", 'date code'),
1957 ('u', 'user', "", 'user')],
1943 ('u', 'user', "", 'user')],
1958 'hg tag [OPTION]... NAME [REV]'),
1944 'hg tag [OPTION]... NAME [REV]'),
1959 "tags": (tags, [], 'hg tags'),
1945 "tags": (tags, [], 'hg tags'),
1960 "tip": (tip, [], 'hg tip'),
1946 "tip": (tip, [], 'hg tip'),
1961 "unbundle":
1947 "unbundle":
1962 (unbundle,
1948 (unbundle,
1963 [],
1949 [],
1964 'hg unbundle FILE'),
1950 'hg unbundle FILE'),
1965 "undo": (undo, [], 'hg undo'),
1951 "undo": (undo, [], 'hg undo'),
1966 "^update|up|checkout|co":
1952 "^update|up|checkout|co":
1967 (update,
1953 (update,
1968 [('b', 'branch', "", 'checkout the head of a specific branch'),
1954 [('b', 'branch', "", 'checkout the head of a specific branch'),
1969 ('m', 'merge', None, 'allow merging of conflicts'),
1955 ('m', 'merge', None, 'allow merging of conflicts'),
1970 ('C', 'clean', None, 'overwrite locally modified files')],
1956 ('C', 'clean', None, 'overwrite locally modified files')],
1971 'hg update [-b TAG] [-m] [-C] [REV]'),
1957 'hg update [-b TAG] [-m] [-C] [REV]'),
1972 "verify": (verify, [], 'hg verify'),
1958 "verify": (verify, [], 'hg verify'),
1973 "version": (show_version, [], 'hg version'),
1959 "version": (show_version, [], 'hg version'),
1974 }
1960 }
1975
1961
1976 globalopts = [
1962 globalopts = [
1977 ('R', 'repository', "", 'repository root directory'),
1963 ('R', 'repository', "", 'repository root directory'),
1978 ('', 'cwd', '', 'change working directory'),
1964 ('', 'cwd', '', 'change working directory'),
1979 ('y', 'noninteractive', None, 'run non-interactively'),
1965 ('y', 'noninteractive', None, 'run non-interactively'),
1980 ('q', 'quiet', None, 'quiet mode'),
1966 ('q', 'quiet', None, 'quiet mode'),
1981 ('v', 'verbose', None, 'verbose mode'),
1967 ('v', 'verbose', None, 'verbose mode'),
1982 ('', 'debug', None, 'debug mode'),
1968 ('', 'debug', None, 'debug mode'),
1983 ('', 'debugger', None, 'start debugger'),
1969 ('', 'debugger', None, 'start debugger'),
1984 ('', 'traceback', None, 'print traceback on exception'),
1970 ('', 'traceback', None, 'print traceback on exception'),
1985 ('', 'time', None, 'time how long the command takes'),
1971 ('', 'time', None, 'time how long the command takes'),
1986 ('', 'profile', None, 'profile'),
1972 ('', 'profile', None, 'profile'),
1987 ('', 'version', None, 'output version information and exit'),
1973 ('', 'version', None, 'output version information and exit'),
1988 ('h', 'help', None, 'display help and exit'),
1974 ('h', 'help', None, 'display help and exit'),
1989 ]
1975 ]
1990
1976
1991 norepo = ("clone init version help debugancestor debugconfig debugdata"
1977 norepo = ("clone init version help debugancestor debugconfig debugdata"
1992 " debugindex debugindexdot paths")
1978 " debugindex debugindexdot paths")
1993
1979
1994 def find(cmd):
1980 def find(cmd):
1995 for e in table.keys():
1981 for e in table.keys():
1996 if re.match("(%s)$" % e, cmd):
1982 if re.match("(%s)$" % e, cmd):
1997 return e, table[e]
1983 return e, table[e]
1998
1984
1999 raise UnknownCommand(cmd)
1985 raise UnknownCommand(cmd)
2000
1986
2001 class SignalInterrupt(Exception):
1987 class SignalInterrupt(Exception):
2002 """Exception raised on SIGTERM and SIGHUP."""
1988 """Exception raised on SIGTERM and SIGHUP."""
2003
1989
2004 def catchterm(*args):
1990 def catchterm(*args):
2005 raise SignalInterrupt
1991 raise SignalInterrupt
2006
1992
2007 def run():
1993 def run():
2008 sys.exit(dispatch(sys.argv[1:]))
1994 sys.exit(dispatch(sys.argv[1:]))
2009
1995
2010 class ParseError(Exception):
1996 class ParseError(Exception):
2011 """Exception raised on errors in parsing the command line."""
1997 """Exception raised on errors in parsing the command line."""
2012
1998
2013 def parse(args):
1999 def parse(args):
2014 options = {}
2000 options = {}
2015 cmdoptions = {}
2001 cmdoptions = {}
2016
2002
2017 try:
2003 try:
2018 args = fancyopts.fancyopts(args, globalopts, options)
2004 args = fancyopts.fancyopts(args, globalopts, options)
2019 except fancyopts.getopt.GetoptError, inst:
2005 except fancyopts.getopt.GetoptError, inst:
2020 raise ParseError(None, inst)
2006 raise ParseError(None, inst)
2021
2007
2022 if args:
2008 if args:
2023 cmd, args = args[0], args[1:]
2009 cmd, args = args[0], args[1:]
2024 i = find(cmd)[1]
2010 i = find(cmd)[1]
2025 c = list(i[1])
2011 c = list(i[1])
2026 else:
2012 else:
2027 cmd = None
2013 cmd = None
2028 c = []
2014 c = []
2029
2015
2030 # combine global options into local
2016 # combine global options into local
2031 for o in globalopts:
2017 for o in globalopts:
2032 c.append((o[0], o[1], options[o[1]], o[3]))
2018 c.append((o[0], o[1], options[o[1]], o[3]))
2033
2019
2034 try:
2020 try:
2035 args = fancyopts.fancyopts(args, c, cmdoptions)
2021 args = fancyopts.fancyopts(args, c, cmdoptions)
2036 except fancyopts.getopt.GetoptError, inst:
2022 except fancyopts.getopt.GetoptError, inst:
2037 raise ParseError(cmd, inst)
2023 raise ParseError(cmd, inst)
2038
2024
2039 # separate global options back out
2025 # separate global options back out
2040 for o in globalopts:
2026 for o in globalopts:
2041 n = o[1]
2027 n = o[1]
2042 options[n] = cmdoptions[n]
2028 options[n] = cmdoptions[n]
2043 del cmdoptions[n]
2029 del cmdoptions[n]
2044
2030
2045 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
2031 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
2046
2032
2047 def dispatch(args):
2033 def dispatch(args):
2048 signal.signal(signal.SIGTERM, catchterm)
2034 signal.signal(signal.SIGTERM, catchterm)
2049 try:
2035 try:
2050 signal.signal(signal.SIGHUP, catchterm)
2036 signal.signal(signal.SIGHUP, catchterm)
2051 except AttributeError:
2037 except AttributeError:
2052 pass
2038 pass
2053
2039
2054 u = ui.ui()
2040 u = ui.ui()
2055 external = []
2041 external = []
2056 for x in u.extensions():
2042 for x in u.extensions():
2057 if x[1]:
2043 if x[1]:
2058 mod = imp.load_source(x[0], x[1])
2044 mod = imp.load_source(x[0], x[1])
2059 else:
2045 else:
2060 def importh(name):
2046 def importh(name):
2061 mod = __import__(name)
2047 mod = __import__(name)
2062 components = name.split('.')
2048 components = name.split('.')
2063 for comp in components[1:]:
2049 for comp in components[1:]:
2064 mod = getattr(mod, comp)
2050 mod = getattr(mod, comp)
2065 return mod
2051 return mod
2066 mod = importh(x[0])
2052 mod = importh(x[0])
2067 external.append(mod)
2053 external.append(mod)
2068 for x in external:
2054 for x in external:
2069 cmdtable = getattr(x, 'cmdtable', {})
2055 cmdtable = getattr(x, 'cmdtable', {})
2070 for t in cmdtable:
2056 for t in cmdtable:
2071 if t in table:
2057 if t in table:
2072 u.warn("module %s overrides %s\n" % (x.__name__, t))
2058 u.warn("module %s overrides %s\n" % (x.__name__, t))
2073 table.update(cmdtable)
2059 table.update(cmdtable)
2074
2060
2075 try:
2061 try:
2076 cmd, func, args, options, cmdoptions = parse(args)
2062 cmd, func, args, options, cmdoptions = parse(args)
2077 except ParseError, inst:
2063 except ParseError, inst:
2078 if inst.args[0]:
2064 if inst.args[0]:
2079 u.warn("hg %s: %s\n" % (inst.args[0], inst.args[1]))
2065 u.warn("hg %s: %s\n" % (inst.args[0], inst.args[1]))
2080 help_(u, inst.args[0])
2066 help_(u, inst.args[0])
2081 else:
2067 else:
2082 u.warn("hg: %s\n" % inst.args[1])
2068 u.warn("hg: %s\n" % inst.args[1])
2083 help_(u, 'shortlist')
2069 help_(u, 'shortlist')
2084 sys.exit(-1)
2070 sys.exit(-1)
2085 except UnknownCommand, inst:
2071 except UnknownCommand, inst:
2086 u.warn("hg: unknown command '%s'\n" % inst.args[0])
2072 u.warn("hg: unknown command '%s'\n" % inst.args[0])
2087 help_(u, 'shortlist')
2073 help_(u, 'shortlist')
2088 sys.exit(1)
2074 sys.exit(1)
2089
2075
2090 if options["time"]:
2076 if options["time"]:
2091 def get_times():
2077 def get_times():
2092 t = os.times()
2078 t = os.times()
2093 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
2079 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
2094 t = (t[0], t[1], t[2], t[3], time.clock())
2080 t = (t[0], t[1], t[2], t[3], time.clock())
2095 return t
2081 return t
2096 s = get_times()
2082 s = get_times()
2097 def print_time():
2083 def print_time():
2098 t = get_times()
2084 t = get_times()
2099 u.warn("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n" %
2085 u.warn("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n" %
2100 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
2086 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
2101 atexit.register(print_time)
2087 atexit.register(print_time)
2102
2088
2103 u.updateopts(options["verbose"], options["debug"], options["quiet"],
2089 u.updateopts(options["verbose"], options["debug"], options["quiet"],
2104 not options["noninteractive"])
2090 not options["noninteractive"])
2105
2091
2106 # enter the debugger before command execution
2092 # enter the debugger before command execution
2107 if options['debugger']:
2093 if options['debugger']:
2108 pdb.set_trace()
2094 pdb.set_trace()
2109
2095
2110 try:
2096 try:
2111 try:
2097 try:
2112 if options['help']:
2098 if options['help']:
2113 help_(u, cmd, options['version'])
2099 help_(u, cmd, options['version'])
2114 sys.exit(0)
2100 sys.exit(0)
2115 elif options['version']:
2101 elif options['version']:
2116 show_version(u)
2102 show_version(u)
2117 sys.exit(0)
2103 sys.exit(0)
2118 elif not cmd:
2104 elif not cmd:
2119 help_(u, 'shortlist')
2105 help_(u, 'shortlist')
2120 sys.exit(0)
2106 sys.exit(0)
2121
2107
2122 if options['cwd']:
2108 if options['cwd']:
2123 try:
2109 try:
2124 os.chdir(options['cwd'])
2110 os.chdir(options['cwd'])
2125 except OSError, inst:
2111 except OSError, inst:
2126 raise util.Abort('%s: %s' %
2112 raise util.Abort('%s: %s' %
2127 (options['cwd'], inst.strerror))
2113 (options['cwd'], inst.strerror))
2128
2114
2129 if cmd not in norepo.split():
2115 if cmd not in norepo.split():
2130 path = options["repository"] or ""
2116 path = options["repository"] or ""
2131 repo = hg.repository(ui=u, path=path)
2117 repo = hg.repository(ui=u, path=path)
2132 for x in external:
2118 for x in external:
2133 if hasattr(x, 'reposetup'): x.reposetup(u, repo)
2119 if hasattr(x, 'reposetup'): x.reposetup(u, repo)
2134 d = lambda: func(u, repo, *args, **cmdoptions)
2120 d = lambda: func(u, repo, *args, **cmdoptions)
2135 else:
2121 else:
2136 d = lambda: func(u, *args, **cmdoptions)
2122 d = lambda: func(u, *args, **cmdoptions)
2137
2123
2138 if options['profile']:
2124 if options['profile']:
2139 import hotshot, hotshot.stats
2125 import hotshot, hotshot.stats
2140 prof = hotshot.Profile("hg.prof")
2126 prof = hotshot.Profile("hg.prof")
2141 r = prof.runcall(d)
2127 r = prof.runcall(d)
2142 prof.close()
2128 prof.close()
2143 stats = hotshot.stats.load("hg.prof")
2129 stats = hotshot.stats.load("hg.prof")
2144 stats.strip_dirs()
2130 stats.strip_dirs()
2145 stats.sort_stats('time', 'calls')
2131 stats.sort_stats('time', 'calls')
2146 stats.print_stats(40)
2132 stats.print_stats(40)
2147 return r
2133 return r
2148 else:
2134 else:
2149 return d()
2135 return d()
2150 except:
2136 except:
2151 # enter the debugger when we hit an exception
2137 # enter the debugger when we hit an exception
2152 if options['debugger']:
2138 if options['debugger']:
2153 pdb.post_mortem(sys.exc_info()[2])
2139 pdb.post_mortem(sys.exc_info()[2])
2154 if options['traceback']:
2140 if options['traceback']:
2155 traceback.print_exc()
2141 traceback.print_exc()
2156 raise
2142 raise
2157 except hg.RepoError, inst:
2143 except hg.RepoError, inst:
2158 u.warn("abort: ", inst, "!\n")
2144 u.warn("abort: ", inst, "!\n")
2159 except revlog.RevlogError, inst:
2145 except revlog.RevlogError, inst:
2160 u.warn("abort: ", inst, "!\n")
2146 u.warn("abort: ", inst, "!\n")
2161 except SignalInterrupt:
2147 except SignalInterrupt:
2162 u.warn("killed!\n")
2148 u.warn("killed!\n")
2163 except KeyboardInterrupt:
2149 except KeyboardInterrupt:
2164 try:
2150 try:
2165 u.warn("interrupted!\n")
2151 u.warn("interrupted!\n")
2166 except IOError, inst:
2152 except IOError, inst:
2167 if inst.errno == errno.EPIPE:
2153 if inst.errno == errno.EPIPE:
2168 if u.debugflag:
2154 if u.debugflag:
2169 u.warn("\nbroken pipe\n")
2155 u.warn("\nbroken pipe\n")
2170 else:
2156 else:
2171 raise
2157 raise
2172 except IOError, inst:
2158 except IOError, inst:
2173 if hasattr(inst, "code"):
2159 if hasattr(inst, "code"):
2174 u.warn("abort: %s\n" % inst)
2160 u.warn("abort: %s\n" % inst)
2175 elif hasattr(inst, "reason"):
2161 elif hasattr(inst, "reason"):
2176 u.warn("abort: error: %s\n" % inst.reason[1])
2162 u.warn("abort: error: %s\n" % inst.reason[1])
2177 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
2163 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
2178 if u.debugflag:
2164 if u.debugflag:
2179 u.warn("broken pipe\n")
2165 u.warn("broken pipe\n")
2180 else:
2166 else:
2181 raise
2167 raise
2182 except OSError, inst:
2168 except OSError, inst:
2183 if hasattr(inst, "filename"):
2169 if hasattr(inst, "filename"):
2184 u.warn("abort: %s: %s\n" % (inst.strerror, inst.filename))
2170 u.warn("abort: %s: %s\n" % (inst.strerror, inst.filename))
2185 else:
2171 else:
2186 u.warn("abort: %s\n" % inst.strerror)
2172 u.warn("abort: %s\n" % inst.strerror)
2187 except util.Abort, inst:
2173 except util.Abort, inst:
2188 u.warn('abort: ', inst.args[0] % inst.args[1:], '\n')
2174 u.warn('abort: ', inst.args[0] % inst.args[1:], '\n')
2189 sys.exit(1)
2175 sys.exit(1)
2190 except TypeError, inst:
2176 except TypeError, inst:
2191 # was this an argument error?
2177 # was this an argument error?
2192 tb = traceback.extract_tb(sys.exc_info()[2])
2178 tb = traceback.extract_tb(sys.exc_info()[2])
2193 if len(tb) > 2: # no
2179 if len(tb) > 2: # no
2194 raise
2180 raise
2195 u.debug(inst, "\n")
2181 u.debug(inst, "\n")
2196 u.warn("%s: invalid arguments\n" % cmd)
2182 u.warn("%s: invalid arguments\n" % cmd)
2197 help_(u, cmd)
2183 help_(u, cmd)
2198 except UnknownCommand, inst:
2184 except UnknownCommand, inst:
2199 u.warn("hg: unknown command '%s'\n" % inst.args[0])
2185 u.warn("hg: unknown command '%s'\n" % inst.args[0])
2200 help_(u, 'shortlist')
2186 help_(u, 'shortlist')
2201 except SystemExit:
2187 except SystemExit:
2202 # don't catch this in the catch-all below
2188 # don't catch this in the catch-all below
2203 raise
2189 raise
2204 except:
2190 except:
2205 u.warn("** unknown exception encountered, details follow\n")
2191 u.warn("** unknown exception encountered, details follow\n")
2206 u.warn("** report bug details to mercurial@selenic.com\n")
2192 u.warn("** report bug details to mercurial@selenic.com\n")
2207 raise
2193 raise
2208
2194
2209 sys.exit(-1)
2195 sys.exit(-1)
@@ -1,987 +1,977 b''
1 # hgweb.py - web interface to a mercurial repository
1 # hgweb.py - web interface to a mercurial repository
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os, cgi, sys
9 import os, cgi, sys
10 from demandload import demandload
10 from demandload import demandload
11 demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser")
11 demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser")
12 demandload(globals(), "zipfile tempfile StringIO tarfile BaseHTTPServer")
12 demandload(globals(), "zipfile tempfile StringIO tarfile BaseHTTPServer util")
13 from node import *
13 from node import *
14
14
15 def templatepath():
15 def templatepath():
16 for f in "templates", "../templates":
16 for f in "templates", "../templates":
17 p = os.path.join(os.path.dirname(__file__), f)
17 p = os.path.join(os.path.dirname(__file__), f)
18 if os.path.isdir(p):
18 if os.path.isdir(p):
19 return p
19 return p
20
20
21 def age(t):
21 def age(x):
22 def plural(t, c):
22 def plural(t, c):
23 if c == 1:
23 if c == 1:
24 return t
24 return t
25 return t + "s"
25 return t + "s"
26 def fmt(t, c):
26 def fmt(t, c):
27 return "%d %s" % (c, plural(t, c))
27 return "%d %s" % (c, plural(t, c))
28
28
29 now = time.time()
29 now = time.time()
30 delta = max(1, int(now - t))
30 then = int(x[2].split(' ')[0])
31 delta = max(1, int(now - then))
31
32
32 scales = [["second", 1],
33 scales = [["second", 1],
33 ["minute", 60],
34 ["minute", 60],
34 ["hour", 3600],
35 ["hour", 3600],
35 ["day", 3600 * 24],
36 ["day", 3600 * 24],
36 ["week", 3600 * 24 * 7],
37 ["week", 3600 * 24 * 7],
37 ["month", 3600 * 24 * 30],
38 ["month", 3600 * 24 * 30],
38 ["year", 3600 * 24 * 365]]
39 ["year", 3600 * 24 * 365]]
39
40
40 scales.reverse()
41 scales.reverse()
41
42
42 for t, s in scales:
43 for t, s in scales:
43 n = delta / s
44 n = delta / s
44 if n >= 2 or s == 1:
45 if n >= 2 or s == 1:
45 return fmt(t, n)
46 return fmt(t, n)
46
47
47 def nl2br(text):
48 def nl2br(text):
48 return text.replace('\n', '<br/>\n')
49 return text.replace('\n', '<br/>\n')
49
50
50 def obfuscate(text):
51 def obfuscate(text):
51 return ''.join(['&#%d;' % ord(c) for c in text])
52 return ''.join(['&#%d;' % ord(c) for c in text])
52
53
53 def up(p):
54 def up(p):
54 if p[0] != "/":
55 if p[0] != "/":
55 p = "/" + p
56 p = "/" + p
56 if p[-1] == "/":
57 if p[-1] == "/":
57 p = p[:-1]
58 p = p[:-1]
58 up = os.path.dirname(p)
59 up = os.path.dirname(p)
59 if up == "/":
60 if up == "/":
60 return "/"
61 return "/"
61 return up + "/"
62 return up + "/"
62
63
63 class hgrequest:
64 class hgrequest:
64 def __init__(self, inp=None, out=None, env=None):
65 def __init__(self, inp=None, out=None, env=None):
65 self.inp = inp or sys.stdin
66 self.inp = inp or sys.stdin
66 self.out = out or sys.stdout
67 self.out = out or sys.stdout
67 self.env = env or os.environ
68 self.env = env or os.environ
68 self.form = cgi.parse(self.inp, self.env)
69 self.form = cgi.parse(self.inp, self.env)
69
70
70 def write(self, *things):
71 def write(self, *things):
71 for thing in things:
72 for thing in things:
72 if hasattr(thing, "__iter__"):
73 if hasattr(thing, "__iter__"):
73 for part in thing:
74 for part in thing:
74 self.write(part)
75 self.write(part)
75 else:
76 else:
76 try:
77 try:
77 self.out.write(str(thing))
78 self.out.write(str(thing))
78 except socket.error, inst:
79 except socket.error, inst:
79 if inst[0] != errno.ECONNRESET:
80 if inst[0] != errno.ECONNRESET:
80 raise
81 raise
81
82
82 def header(self, headers=[('Content-type','text/html')]):
83 def header(self, headers=[('Content-type','text/html')]):
83 for header in headers:
84 for header in headers:
84 self.out.write("%s: %s\r\n" % header)
85 self.out.write("%s: %s\r\n" % header)
85 self.out.write("\r\n")
86 self.out.write("\r\n")
86
87
87 def httphdr(self, type, file="", size=0):
88 def httphdr(self, type, file="", size=0):
88
89
89 headers = [('Content-type', type)]
90 headers = [('Content-type', type)]
90 if file:
91 if file:
91 headers.append(('Content-disposition', 'attachment; filename=%s' % file))
92 headers.append(('Content-disposition', 'attachment; filename=%s' % file))
92 if size > 0:
93 if size > 0:
93 headers.append(('Content-length', str(size)))
94 headers.append(('Content-length', str(size)))
94 self.header(headers)
95 self.header(headers)
95
96
96 class templater:
97 class templater:
97 def __init__(self, mapfile, filters={}, defaults={}):
98 def __init__(self, mapfile, filters={}, defaults={}):
98 self.cache = {}
99 self.cache = {}
99 self.map = {}
100 self.map = {}
100 self.base = os.path.dirname(mapfile)
101 self.base = os.path.dirname(mapfile)
101 self.filters = filters
102 self.filters = filters
102 self.defaults = defaults
103 self.defaults = defaults
103
104
104 for l in file(mapfile):
105 for l in file(mapfile):
105 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
106 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
106 if m:
107 if m:
107 self.cache[m.group(1)] = m.group(2)
108 self.cache[m.group(1)] = m.group(2)
108 else:
109 else:
109 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
110 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
110 if m:
111 if m:
111 self.map[m.group(1)] = os.path.join(self.base, m.group(2))
112 self.map[m.group(1)] = os.path.join(self.base, m.group(2))
112 else:
113 else:
113 raise LookupError("unknown map entry '%s'" % l)
114 raise LookupError("unknown map entry '%s'" % l)
114
115
115 def __call__(self, t, **map):
116 def __call__(self, t, **map):
116 m = self.defaults.copy()
117 m = self.defaults.copy()
117 m.update(map)
118 m.update(map)
118 try:
119 try:
119 tmpl = self.cache[t]
120 tmpl = self.cache[t]
120 except KeyError:
121 except KeyError:
121 tmpl = self.cache[t] = file(self.map[t]).read()
122 tmpl = self.cache[t] = file(self.map[t]).read()
122 return self.template(tmpl, self.filters, **m)
123 return self.template(tmpl, self.filters, **m)
123
124
124 def template(self, tmpl, filters={}, **map):
125 def template(self, tmpl, filters={}, **map):
125 while tmpl:
126 while tmpl:
126 m = re.search(r"#([a-zA-Z0-9]+)((%[a-zA-Z0-9]+)*)((\|[a-zA-Z0-9]+)*)#", tmpl)
127 m = re.search(r"#([a-zA-Z0-9]+)((%[a-zA-Z0-9]+)*)((\|[a-zA-Z0-9]+)*)#", tmpl)
127 if m:
128 if m:
128 yield tmpl[:m.start(0)]
129 yield tmpl[:m.start(0)]
129 v = map.get(m.group(1), "")
130 v = map.get(m.group(1), "")
130 v = callable(v) and v(**map) or v
131 v = callable(v) and v(**map) or v
131
132
132 format = m.group(2)
133 format = m.group(2)
133 fl = m.group(4)
134 fl = m.group(4)
134
135
135 if format:
136 if format:
136 q = v.__iter__
137 q = v.__iter__
137 for i in q():
138 for i in q():
138 lm = map.copy()
139 lm = map.copy()
139 lm.update(i)
140 lm.update(i)
140 yield self(format[1:], **lm)
141 yield self(format[1:], **lm)
141
142
142 v = ""
143 v = ""
143
144
144 elif fl:
145 elif fl:
145 for f in fl.split("|")[1:]:
146 for f in fl.split("|")[1:]:
146 v = filters[f](v)
147 v = filters[f](v)
147
148
148 yield v
149 yield v
149 tmpl = tmpl[m.end(0):]
150 tmpl = tmpl[m.end(0):]
150 else:
151 else:
151 yield tmpl
152 yield tmpl
152 return
153 return
153
154
154 def rfc822date(x):
155 return time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime(x))
156
157 common_filters = {
155 common_filters = {
158 "escape": cgi.escape,
156 "escape": cgi.escape,
159 "age": age,
157 "age": age,
160 "date": (lambda x: time.asctime(time.gmtime(x))),
158 "date": util.datestr,
161 "addbreaks": nl2br,
159 "addbreaks": nl2br,
162 "obfuscate": obfuscate,
160 "obfuscate": obfuscate,
163 "short": (lambda x: x[:12]),
161 "short": (lambda x: x[:12]),
164 "firstline": (lambda x: x.splitlines(1)[0]),
162 "firstline": (lambda x: x.splitlines(1)[0]),
165 "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"),
163 "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"),
166 "rfc822date": rfc822date,
164 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
167 }
165 }
168
166
169
170
171 class hgweb:
167 class hgweb:
172 def __init__(self, repo, name=None):
168 def __init__(self, repo, name=None):
173 if type(repo) == type(""):
169 if type(repo) == type(""):
174 self.repo = hg.repository(ui.ui(), repo)
170 self.repo = hg.repository(ui.ui(), repo)
175 else:
171 else:
176 self.repo = repo
172 self.repo = repo
177
173
178 self.mtime = -1
174 self.mtime = -1
179 self.reponame = name
175 self.reponame = name
180 self.archives = 'zip', 'gz', 'bz2'
176 self.archives = 'zip', 'gz', 'bz2'
181
177
182 def refresh(self):
178 def refresh(self):
183 s = os.stat(os.path.join(self.repo.root, ".hg", "00changelog.i"))
179 s = os.stat(os.path.join(self.repo.root, ".hg", "00changelog.i"))
184 if s.st_mtime != self.mtime:
180 if s.st_mtime != self.mtime:
185 self.mtime = s.st_mtime
181 self.mtime = s.st_mtime
186 self.repo = hg.repository(self.repo.ui, self.repo.root)
182 self.repo = hg.repository(self.repo.ui, self.repo.root)
187 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
183 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
188 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
184 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
189 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
185 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
190
186
191 def date(self, cs):
187 def date(self, cs):
192 return time.asctime(time.gmtime(float(cs[2].split(' ')[0])))
188 return util.datestr(cs)
193
189
194 def listfiles(self, files, mf):
190 def listfiles(self, files, mf):
195 for f in files[:self.maxfiles]:
191 for f in files[:self.maxfiles]:
196 yield self.t("filenodelink", node=hex(mf[f]), file=f)
192 yield self.t("filenodelink", node=hex(mf[f]), file=f)
197 if len(files) > self.maxfiles:
193 if len(files) > self.maxfiles:
198 yield self.t("fileellipses")
194 yield self.t("fileellipses")
199
195
200 def listfilediffs(self, files, changeset):
196 def listfilediffs(self, files, changeset):
201 for f in files[:self.maxfiles]:
197 for f in files[:self.maxfiles]:
202 yield self.t("filedifflink", node=hex(changeset), file=f)
198 yield self.t("filedifflink", node=hex(changeset), file=f)
203 if len(files) > self.maxfiles:
199 if len(files) > self.maxfiles:
204 yield self.t("fileellipses")
200 yield self.t("fileellipses")
205
201
206 def parents(self, t1, nodes=[], rev=None,**args):
202 def parents(self, t1, nodes=[], rev=None,**args):
207 if not rev:
203 if not rev:
208 rev = lambda x: ""
204 rev = lambda x: ""
209 for node in nodes:
205 for node in nodes:
210 if node != nullid:
206 if node != nullid:
211 yield self.t(t1, node=hex(node), rev=rev(node), **args)
207 yield self.t(t1, node=hex(node), rev=rev(node), **args)
212
208
213 def showtag(self, t1, node=nullid, **args):
209 def showtag(self, t1, node=nullid, **args):
214 for t in self.repo.nodetags(node):
210 for t in self.repo.nodetags(node):
215 yield self.t(t1, tag=t, **args)
211 yield self.t(t1, tag=t, **args)
216
212
217 def diff(self, node1, node2, files):
213 def diff(self, node1, node2, files):
218 def filterfiles(list, files):
214 def filterfiles(list, files):
219 l = [x for x in list if x in files]
215 l = [x for x in list if x in files]
220
216
221 for f in files:
217 for f in files:
222 if f[-1] != os.sep:
218 if f[-1] != os.sep:
223 f += os.sep
219 f += os.sep
224 l += [x for x in list if x.startswith(f)]
220 l += [x for x in list if x.startswith(f)]
225 return l
221 return l
226
222
227 parity = [0]
223 parity = [0]
228 def diffblock(diff, f, fn):
224 def diffblock(diff, f, fn):
229 yield self.t("diffblock",
225 yield self.t("diffblock",
230 lines=prettyprintlines(diff),
226 lines=prettyprintlines(diff),
231 parity=parity[0],
227 parity=parity[0],
232 file=f,
228 file=f,
233 filenode=hex(fn or nullid))
229 filenode=hex(fn or nullid))
234 parity[0] = 1 - parity[0]
230 parity[0] = 1 - parity[0]
235
231
236 def prettyprintlines(diff):
232 def prettyprintlines(diff):
237 for l in diff.splitlines(1):
233 for l in diff.splitlines(1):
238 if l.startswith('+'):
234 if l.startswith('+'):
239 yield self.t("difflineplus", line=l)
235 yield self.t("difflineplus", line=l)
240 elif l.startswith('-'):
236 elif l.startswith('-'):
241 yield self.t("difflineminus", line=l)
237 yield self.t("difflineminus", line=l)
242 elif l.startswith('@'):
238 elif l.startswith('@'):
243 yield self.t("difflineat", line=l)
239 yield self.t("difflineat", line=l)
244 else:
240 else:
245 yield self.t("diffline", line=l)
241 yield self.t("diffline", line=l)
246
242
247 r = self.repo
243 r = self.repo
248 cl = r.changelog
244 cl = r.changelog
249 mf = r.manifest
245 mf = r.manifest
250 change1 = cl.read(node1)
246 change1 = cl.read(node1)
251 change2 = cl.read(node2)
247 change2 = cl.read(node2)
252 mmap1 = mf.read(change1[0])
248 mmap1 = mf.read(change1[0])
253 mmap2 = mf.read(change2[0])
249 mmap2 = mf.read(change2[0])
254 date1 = self.date(change1)
250 date1 = self.date(change1)
255 date2 = self.date(change2)
251 date2 = self.date(change2)
256
252
257 c, a, d, u = r.changes(node1, node2)
253 c, a, d, u = r.changes(node1, node2)
258 if files:
254 if files:
259 c, a, d = map(lambda x: filterfiles(x, files), (c, a, d))
255 c, a, d = map(lambda x: filterfiles(x, files), (c, a, d))
260
256
261 for f in c:
257 for f in c:
262 to = r.file(f).read(mmap1[f])
258 to = r.file(f).read(mmap1[f])
263 tn = r.file(f).read(mmap2[f])
259 tn = r.file(f).read(mmap2[f])
264 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
260 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
265 for f in a:
261 for f in a:
266 to = None
262 to = None
267 tn = r.file(f).read(mmap2[f])
263 tn = r.file(f).read(mmap2[f])
268 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
264 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
269 for f in d:
265 for f in d:
270 to = r.file(f).read(mmap1[f])
266 to = r.file(f).read(mmap1[f])
271 tn = None
267 tn = None
272 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
268 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
273
269
274 def changelog(self, pos):
270 def changelog(self, pos):
275 def changenav(**map):
271 def changenav(**map):
276 def seq(factor=1):
272 def seq(factor=1):
277 yield 1 * factor
273 yield 1 * factor
278 yield 3 * factor
274 yield 3 * factor
279 #yield 5 * factor
275 #yield 5 * factor
280 for f in seq(factor * 10):
276 for f in seq(factor * 10):
281 yield f
277 yield f
282
278
283 l = []
279 l = []
284 for f in seq():
280 for f in seq():
285 if f < self.maxchanges / 2:
281 if f < self.maxchanges / 2:
286 continue
282 continue
287 if f > count:
283 if f > count:
288 break
284 break
289 r = "%d" % f
285 r = "%d" % f
290 if pos + f < count:
286 if pos + f < count:
291 l.append(("+" + r, pos + f))
287 l.append(("+" + r, pos + f))
292 if pos - f >= 0:
288 if pos - f >= 0:
293 l.insert(0, ("-" + r, pos - f))
289 l.insert(0, ("-" + r, pos - f))
294
290
295 yield {"rev": 0, "label": "(0)"}
291 yield {"rev": 0, "label": "(0)"}
296
292
297 for label, rev in l:
293 for label, rev in l:
298 yield {"label": label, "rev": rev}
294 yield {"label": label, "rev": rev}
299
295
300 yield {"label": "tip", "rev": ""}
296 yield {"label": "tip", "rev": ""}
301
297
302 def changelist(**map):
298 def changelist(**map):
303 parity = (start - end) & 1
299 parity = (start - end) & 1
304 cl = self.repo.changelog
300 cl = self.repo.changelog
305 l = [] # build a list in forward order for efficiency
301 l = [] # build a list in forward order for efficiency
306 for i in range(start, end):
302 for i in range(start, end):
307 n = cl.node(i)
303 n = cl.node(i)
308 changes = cl.read(n)
304 changes = cl.read(n)
309 hn = hex(n)
305 hn = hex(n)
310 t = float(changes[2].split(' ')[0])
311
306
312 l.insert(0, {"parity": parity,
307 l.insert(0, {"parity": parity,
313 "author": changes[1],
308 "author": changes[1],
314 "parent": self.parents("changelogparent",
309 "parent": self.parents("changelogparent",
315 cl.parents(n), cl.rev),
310 cl.parents(n), cl.rev),
316 "changelogtag": self.showtag("changelogtag",n),
311 "changelogtag": self.showtag("changelogtag",n),
317 "manifest": hex(changes[0]),
312 "manifest": hex(changes[0]),
318 "desc": changes[4],
313 "desc": changes[4],
319 "date": t,
314 "date": changes,
320 "files": self.listfilediffs(changes[3], n),
315 "files": self.listfilediffs(changes[3], n),
321 "rev": i,
316 "rev": i,
322 "node": hn})
317 "node": hn})
323 parity = 1 - parity
318 parity = 1 - parity
324
319
325 for e in l:
320 for e in l:
326 yield e
321 yield e
327
322
328 cl = self.repo.changelog
323 cl = self.repo.changelog
329 mf = cl.read(cl.tip())[0]
324 mf = cl.read(cl.tip())[0]
330 count = cl.count()
325 count = cl.count()
331 start = max(0, pos - self.maxchanges + 1)
326 start = max(0, pos - self.maxchanges + 1)
332 end = min(count, start + self.maxchanges)
327 end = min(count, start + self.maxchanges)
333 pos = end - 1
328 pos = end - 1
334
329
335 yield self.t('changelog',
330 yield self.t('changelog',
336 changenav=changenav,
331 changenav=changenav,
337 manifest=hex(mf),
332 manifest=hex(mf),
338 rev=pos, changesets=count, entries=changelist)
333 rev=pos, changesets=count, entries=changelist)
339
334
340 def search(self, query):
335 def search(self, query):
341
336
342 def changelist(**map):
337 def changelist(**map):
343 cl = self.repo.changelog
338 cl = self.repo.changelog
344 count = 0
339 count = 0
345 qw = query.lower().split()
340 qw = query.lower().split()
346
341
347 def revgen():
342 def revgen():
348 for i in range(cl.count() - 1, 0, -100):
343 for i in range(cl.count() - 1, 0, -100):
349 l = []
344 l = []
350 for j in range(max(0, i - 100), i):
345 for j in range(max(0, i - 100), i):
351 n = cl.node(j)
346 n = cl.node(j)
352 changes = cl.read(n)
347 changes = cl.read(n)
353 l.append((n, j, changes))
348 l.append((n, j, changes))
354 l.reverse()
349 l.reverse()
355 for e in l:
350 for e in l:
356 yield e
351 yield e
357
352
358 for n, i, changes in revgen():
353 for n, i, changes in revgen():
359 miss = 0
354 miss = 0
360 for q in qw:
355 for q in qw:
361 if not (q in changes[1].lower() or
356 if not (q in changes[1].lower() or
362 q in changes[4].lower() or
357 q in changes[4].lower() or
363 q in " ".join(changes[3][:20]).lower()):
358 q in " ".join(changes[3][:20]).lower()):
364 miss = 1
359 miss = 1
365 break
360 break
366 if miss:
361 if miss:
367 continue
362 continue
368
363
369 count += 1
364 count += 1
370 hn = hex(n)
365 hn = hex(n)
371 t = float(changes[2].split(' ')[0])
372
366
373 yield self.t('searchentry',
367 yield self.t('searchentry',
374 parity=count & 1,
368 parity=count & 1,
375 author=changes[1],
369 author=changes[1],
376 parent=self.parents("changelogparent",
370 parent=self.parents("changelogparent",
377 cl.parents(n), cl.rev),
371 cl.parents(n), cl.rev),
378 changelogtag=self.showtag("changelogtag",n),
372 changelogtag=self.showtag("changelogtag",n),
379 manifest=hex(changes[0]),
373 manifest=hex(changes[0]),
380 desc=changes[4],
374 desc=changes[4],
381 date=t,
375 date=changes,
382 files=self.listfilediffs(changes[3], n),
376 files=self.listfilediffs(changes[3], n),
383 rev=i,
377 rev=i,
384 node=hn)
378 node=hn)
385
379
386 if count >= self.maxchanges:
380 if count >= self.maxchanges:
387 break
381 break
388
382
389 cl = self.repo.changelog
383 cl = self.repo.changelog
390 mf = cl.read(cl.tip())[0]
384 mf = cl.read(cl.tip())[0]
391
385
392 yield self.t('search',
386 yield self.t('search',
393 query=query,
387 query=query,
394 manifest=hex(mf),
388 manifest=hex(mf),
395 entries=changelist)
389 entries=changelist)
396
390
397 def changeset(self, nodeid):
391 def changeset(self, nodeid):
398 n = bin(nodeid)
392 n = bin(nodeid)
399 cl = self.repo.changelog
393 cl = self.repo.changelog
400 changes = cl.read(n)
394 changes = cl.read(n)
401 p1 = cl.parents(n)[0]
395 p1 = cl.parents(n)[0]
402 t = float(changes[2].split(' ')[0])
403
396
404 files = []
397 files = []
405 mf = self.repo.manifest.read(changes[0])
398 mf = self.repo.manifest.read(changes[0])
406 for f in changes[3]:
399 for f in changes[3]:
407 files.append(self.t("filenodelink",
400 files.append(self.t("filenodelink",
408 filenode=hex(mf.get(f, nullid)), file=f))
401 filenode=hex(mf.get(f, nullid)), file=f))
409
402
410 def diff(**map):
403 def diff(**map):
411 yield self.diff(p1, n, None)
404 yield self.diff(p1, n, None)
412
405
413 def archivelist():
406 def archivelist():
414 for i in self.archives:
407 for i in self.archives:
415 if self.repo.ui.configbool("web", "allow" + i, False):
408 if self.repo.ui.configbool("web", "allow" + i, False):
416 yield {"type" : i, "node" : nodeid}
409 yield {"type" : i, "node" : nodeid}
417
410
418 yield self.t('changeset',
411 yield self.t('changeset',
419 diff=diff,
412 diff=diff,
420 rev=cl.rev(n),
413 rev=cl.rev(n),
421 node=nodeid,
414 node=nodeid,
422 parent=self.parents("changesetparent",
415 parent=self.parents("changesetparent",
423 cl.parents(n), cl.rev),
416 cl.parents(n), cl.rev),
424 changesettag=self.showtag("changesettag",n),
417 changesettag=self.showtag("changesettag",n),
425 manifest=hex(changes[0]),
418 manifest=hex(changes[0]),
426 author=changes[1],
419 author=changes[1],
427 desc=changes[4],
420 desc=changes[4],
428 date=t,
421 date=changes,
429 files=files,
422 files=files,
430 archives=archivelist())
423 archives=archivelist())
431
424
432 def filelog(self, f, filenode):
425 def filelog(self, f, filenode):
433 cl = self.repo.changelog
426 cl = self.repo.changelog
434 fl = self.repo.file(f)
427 fl = self.repo.file(f)
435 count = fl.count()
428 count = fl.count()
436
429
437 def entries(**map):
430 def entries(**map):
438 l = []
431 l = []
439 parity = (count - 1) & 1
432 parity = (count - 1) & 1
440
433
441 for i in range(count):
434 for i in range(count):
442 n = fl.node(i)
435 n = fl.node(i)
443 lr = fl.linkrev(n)
436 lr = fl.linkrev(n)
444 cn = cl.node(lr)
437 cn = cl.node(lr)
445 cs = cl.read(cl.node(lr))
438 cs = cl.read(cl.node(lr))
446 t = float(cs[2].split(' ')[0])
447
439
448 l.insert(0, {"parity": parity,
440 l.insert(0, {"parity": parity,
449 "filenode": hex(n),
441 "filenode": hex(n),
450 "filerev": i,
442 "filerev": i,
451 "file": f,
443 "file": f,
452 "node": hex(cn),
444 "node": hex(cn),
453 "author": cs[1],
445 "author": cs[1],
454 "date": t,
446 "date": cs,
455 "parent": self.parents("filelogparent",
447 "parent": self.parents("filelogparent",
456 fl.parents(n),
448 fl.parents(n),
457 fl.rev, file=f),
449 fl.rev, file=f),
458 "desc": cs[4]})
450 "desc": cs[4]})
459 parity = 1 - parity
451 parity = 1 - parity
460
452
461 for e in l:
453 for e in l:
462 yield e
454 yield e
463
455
464 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
456 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
465
457
466 def filerevision(self, f, node):
458 def filerevision(self, f, node):
467 fl = self.repo.file(f)
459 fl = self.repo.file(f)
468 n = bin(node)
460 n = bin(node)
469 text = fl.read(n)
461 text = fl.read(n)
470 changerev = fl.linkrev(n)
462 changerev = fl.linkrev(n)
471 cl = self.repo.changelog
463 cl = self.repo.changelog
472 cn = cl.node(changerev)
464 cn = cl.node(changerev)
473 cs = cl.read(cn)
465 cs = cl.read(cn)
474 t = float(cs[2].split(' ')[0])
475 mfn = cs[0]
466 mfn = cs[0]
476
467
477 def lines():
468 def lines():
478 for l, t in enumerate(text.splitlines(1)):
469 for l, t in enumerate(text.splitlines(1)):
479 yield {"line": t,
470 yield {"line": t,
480 "linenumber": "% 6d" % (l + 1),
471 "linenumber": "% 6d" % (l + 1),
481 "parity": l & 1}
472 "parity": l & 1}
482
473
483 yield self.t("filerevision",
474 yield self.t("filerevision",
484 file=f,
475 file=f,
485 filenode=node,
476 filenode=node,
486 path=up(f),
477 path=up(f),
487 text=lines(),
478 text=lines(),
488 rev=changerev,
479 rev=changerev,
489 node=hex(cn),
480 node=hex(cn),
490 manifest=hex(mfn),
481 manifest=hex(mfn),
491 author=cs[1],
482 author=cs[1],
492 date=t,
483 date=cs,
493 parent=self.parents("filerevparent",
484 parent=self.parents("filerevparent",
494 fl.parents(n), fl.rev, file=f),
485 fl.parents(n), fl.rev, file=f),
495 permissions=self.repo.manifest.readflags(mfn)[f])
486 permissions=self.repo.manifest.readflags(mfn)[f])
496
487
497 def fileannotate(self, f, node):
488 def fileannotate(self, f, node):
498 bcache = {}
489 bcache = {}
499 ncache = {}
490 ncache = {}
500 fl = self.repo.file(f)
491 fl = self.repo.file(f)
501 n = bin(node)
492 n = bin(node)
502 changerev = fl.linkrev(n)
493 changerev = fl.linkrev(n)
503
494
504 cl = self.repo.changelog
495 cl = self.repo.changelog
505 cn = cl.node(changerev)
496 cn = cl.node(changerev)
506 cs = cl.read(cn)
497 cs = cl.read(cn)
507 t = float(cs[2].split(' ')[0])
508 mfn = cs[0]
498 mfn = cs[0]
509
499
510 def annotate(**map):
500 def annotate(**map):
511 parity = 1
501 parity = 1
512 last = None
502 last = None
513 for r, l in fl.annotate(n):
503 for r, l in fl.annotate(n):
514 try:
504 try:
515 cnode = ncache[r]
505 cnode = ncache[r]
516 except KeyError:
506 except KeyError:
517 cnode = ncache[r] = self.repo.changelog.node(r)
507 cnode = ncache[r] = self.repo.changelog.node(r)
518
508
519 try:
509 try:
520 name = bcache[r]
510 name = bcache[r]
521 except KeyError:
511 except KeyError:
522 cl = self.repo.changelog.read(cnode)
512 cl = self.repo.changelog.read(cnode)
523 bcache[r] = name = self.repo.ui.shortuser(cl[1])
513 bcache[r] = name = self.repo.ui.shortuser(cl[1])
524
514
525 if last != cnode:
515 if last != cnode:
526 parity = 1 - parity
516 parity = 1 - parity
527 last = cnode
517 last = cnode
528
518
529 yield {"parity": parity,
519 yield {"parity": parity,
530 "node": hex(cnode),
520 "node": hex(cnode),
531 "rev": r,
521 "rev": r,
532 "author": name,
522 "author": name,
533 "file": f,
523 "file": f,
534 "line": l}
524 "line": l}
535
525
536 yield self.t("fileannotate",
526 yield self.t("fileannotate",
537 file=f,
527 file=f,
538 filenode=node,
528 filenode=node,
539 annotate=annotate,
529 annotate=annotate,
540 path=up(f),
530 path=up(f),
541 rev=changerev,
531 rev=changerev,
542 node=hex(cn),
532 node=hex(cn),
543 manifest=hex(mfn),
533 manifest=hex(mfn),
544 author=cs[1],
534 author=cs[1],
545 date=t,
535 date=cs,
546 parent=self.parents("fileannotateparent",
536 parent=self.parents("fileannotateparent",
547 fl.parents(n), fl.rev, file=f),
537 fl.parents(n), fl.rev, file=f),
548 permissions=self.repo.manifest.readflags(mfn)[f])
538 permissions=self.repo.manifest.readflags(mfn)[f])
549
539
550 def manifest(self, mnode, path):
540 def manifest(self, mnode, path):
551 mf = self.repo.manifest.read(bin(mnode))
541 mf = self.repo.manifest.read(bin(mnode))
552 rev = self.repo.manifest.rev(bin(mnode))
542 rev = self.repo.manifest.rev(bin(mnode))
553 node = self.repo.changelog.node(rev)
543 node = self.repo.changelog.node(rev)
554 mff=self.repo.manifest.readflags(bin(mnode))
544 mff=self.repo.manifest.readflags(bin(mnode))
555
545
556 files = {}
546 files = {}
557
547
558 p = path[1:]
548 p = path[1:]
559 l = len(p)
549 l = len(p)
560
550
561 for f,n in mf.items():
551 for f,n in mf.items():
562 if f[:l] != p:
552 if f[:l] != p:
563 continue
553 continue
564 remain = f[l:]
554 remain = f[l:]
565 if "/" in remain:
555 if "/" in remain:
566 short = remain[:remain.find("/") + 1] # bleah
556 short = remain[:remain.find("/") + 1] # bleah
567 files[short] = (f, None)
557 files[short] = (f, None)
568 else:
558 else:
569 short = os.path.basename(remain)
559 short = os.path.basename(remain)
570 files[short] = (f, n)
560 files[short] = (f, n)
571
561
572 def filelist(**map):
562 def filelist(**map):
573 parity = 0
563 parity = 0
574 fl = files.keys()
564 fl = files.keys()
575 fl.sort()
565 fl.sort()
576 for f in fl:
566 for f in fl:
577 full, fnode = files[f]
567 full, fnode = files[f]
578 if not fnode:
568 if not fnode:
579 continue
569 continue
580
570
581 yield {"file": full,
571 yield {"file": full,
582 "manifest": mnode,
572 "manifest": mnode,
583 "filenode": hex(fnode),
573 "filenode": hex(fnode),
584 "parity": parity,
574 "parity": parity,
585 "basename": f,
575 "basename": f,
586 "permissions": mff[full]}
576 "permissions": mff[full]}
587 parity = 1 - parity
577 parity = 1 - parity
588
578
589 def dirlist(**map):
579 def dirlist(**map):
590 parity = 0
580 parity = 0
591 fl = files.keys()
581 fl = files.keys()
592 fl.sort()
582 fl.sort()
593 for f in fl:
583 for f in fl:
594 full, fnode = files[f]
584 full, fnode = files[f]
595 if fnode:
585 if fnode:
596 continue
586 continue
597
587
598 yield {"parity": parity,
588 yield {"parity": parity,
599 "path": os.path.join(path, f),
589 "path": os.path.join(path, f),
600 "manifest": mnode,
590 "manifest": mnode,
601 "basename": f[:-1]}
591 "basename": f[:-1]}
602 parity = 1 - parity
592 parity = 1 - parity
603
593
604 yield self.t("manifest",
594 yield self.t("manifest",
605 manifest=mnode,
595 manifest=mnode,
606 rev=rev,
596 rev=rev,
607 node=hex(node),
597 node=hex(node),
608 path=path,
598 path=path,
609 up=up(path),
599 up=up(path),
610 fentries=filelist,
600 fentries=filelist,
611 dentries=dirlist)
601 dentries=dirlist)
612
602
613 def tags(self):
603 def tags(self):
614 cl = self.repo.changelog
604 cl = self.repo.changelog
615 mf = cl.read(cl.tip())[0]
605 mf = cl.read(cl.tip())[0]
616
606
617 i = self.repo.tagslist()
607 i = self.repo.tagslist()
618 i.reverse()
608 i.reverse()
619
609
620 def entries(**map):
610 def entries(**map):
621 parity = 0
611 parity = 0
622 for k,n in i:
612 for k,n in i:
623 yield {"parity": parity,
613 yield {"parity": parity,
624 "tag": k,
614 "tag": k,
625 "node": hex(n)}
615 "node": hex(n)}
626 parity = 1 - parity
616 parity = 1 - parity
627
617
628 yield self.t("tags",
618 yield self.t("tags",
629 manifest=hex(mf),
619 manifest=hex(mf),
630 entries=entries)
620 entries=entries)
631
621
632 def filediff(self, file, changeset):
622 def filediff(self, file, changeset):
633 n = bin(changeset)
623 n = bin(changeset)
634 cl = self.repo.changelog
624 cl = self.repo.changelog
635 p1 = cl.parents(n)[0]
625 p1 = cl.parents(n)[0]
636 cs = cl.read(n)
626 cs = cl.read(n)
637 mf = self.repo.manifest.read(cs[0])
627 mf = self.repo.manifest.read(cs[0])
638
628
639 def diff(**map):
629 def diff(**map):
640 yield self.diff(p1, n, file)
630 yield self.diff(p1, n, file)
641
631
642 yield self.t("filediff",
632 yield self.t("filediff",
643 file=file,
633 file=file,
644 filenode=hex(mf.get(file, nullid)),
634 filenode=hex(mf.get(file, nullid)),
645 node=changeset,
635 node=changeset,
646 rev=self.repo.changelog.rev(n),
636 rev=self.repo.changelog.rev(n),
647 parent=self.parents("filediffparent",
637 parent=self.parents("filediffparent",
648 cl.parents(n), cl.rev),
638 cl.parents(n), cl.rev),
649 diff=diff)
639 diff=diff)
650
640
651 def archive(self, req, cnode, type):
641 def archive(self, req, cnode, type):
652 cs = self.repo.changelog.read(cnode)
642 cs = self.repo.changelog.read(cnode)
653 mnode = cs[0]
643 mnode = cs[0]
654 mf = self.repo.manifest.read(mnode)
644 mf = self.repo.manifest.read(mnode)
655 rev = self.repo.manifest.rev(mnode)
645 rev = self.repo.manifest.rev(mnode)
656 reponame = re.sub(r"\W+", "-", self.reponame)
646 reponame = re.sub(r"\W+", "-", self.reponame)
657 name = "%s-%s/" % (reponame, short(cnode))
647 name = "%s-%s/" % (reponame, short(cnode))
658
648
659 files = mf.keys()
649 files = mf.keys()
660 files.sort()
650 files.sort()
661
651
662 if type == 'zip':
652 if type == 'zip':
663 tmp = tempfile.mkstemp()[1]
653 tmp = tempfile.mkstemp()[1]
664 try:
654 try:
665 zf = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED)
655 zf = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED)
666
656
667 for f in files:
657 for f in files:
668 zf.writestr(name + f, self.repo.file(f).read(mf[f]))
658 zf.writestr(name + f, self.repo.file(f).read(mf[f]))
669 zf.close()
659 zf.close()
670
660
671 f = open(tmp, 'r')
661 f = open(tmp, 'r')
672 req.httphdr('application/zip', name[:-1] + '.zip',
662 req.httphdr('application/zip', name[:-1] + '.zip',
673 os.path.getsize(tmp))
663 os.path.getsize(tmp))
674 req.write(f.read())
664 req.write(f.read())
675 f.close()
665 f.close()
676 finally:
666 finally:
677 os.unlink(tmp)
667 os.unlink(tmp)
678
668
679 else:
669 else:
680 tf = tarfile.TarFile.open(mode='w|' + type, fileobj=req.out)
670 tf = tarfile.TarFile.open(mode='w|' + type, fileobj=req.out)
681 mff = self.repo.manifest.readflags(mnode)
671 mff = self.repo.manifest.readflags(mnode)
682 mtime = int(time.time())
672 mtime = int(time.time())
683
673
684 if type == "gz":
674 if type == "gz":
685 encoding = "gzip"
675 encoding = "gzip"
686 else:
676 else:
687 encoding = "x-bzip2"
677 encoding = "x-bzip2"
688 req.header([('Content-type', 'application/x-tar'),
678 req.header([('Content-type', 'application/x-tar'),
689 ('Content-disposition', 'attachment; filename=%s%s%s' %
679 ('Content-disposition', 'attachment; filename=%s%s%s' %
690 (name[:-1], '.tar.', type)),
680 (name[:-1], '.tar.', type)),
691 ('Content-encoding', encoding)])
681 ('Content-encoding', encoding)])
692 for fname in files:
682 for fname in files:
693 rcont = self.repo.file(fname).read(mf[fname])
683 rcont = self.repo.file(fname).read(mf[fname])
694 finfo = tarfile.TarInfo(name + fname)
684 finfo = tarfile.TarInfo(name + fname)
695 finfo.mtime = mtime
685 finfo.mtime = mtime
696 finfo.size = len(rcont)
686 finfo.size = len(rcont)
697 finfo.mode = mff[fname] and 0755 or 0644
687 finfo.mode = mff[fname] and 0755 or 0644
698 tf.addfile(finfo, StringIO.StringIO(rcont))
688 tf.addfile(finfo, StringIO.StringIO(rcont))
699 tf.close()
689 tf.close()
700
690
701 # add tags to things
691 # add tags to things
702 # tags -> list of changesets corresponding to tags
692 # tags -> list of changesets corresponding to tags
703 # find tag, changeset, file
693 # find tag, changeset, file
704
694
705 def run(self, req=hgrequest()):
695 def run(self, req=hgrequest()):
706 def header(**map):
696 def header(**map):
707 yield self.t("header", **map)
697 yield self.t("header", **map)
708
698
709 def footer(**map):
699 def footer(**map):
710 yield self.t("footer", **map)
700 yield self.t("footer", **map)
711
701
712 self.refresh()
702 self.refresh()
713
703
714 t = self.repo.ui.config("web", "templates", templatepath())
704 t = self.repo.ui.config("web", "templates", templatepath())
715 m = os.path.join(t, "map")
705 m = os.path.join(t, "map")
716 style = self.repo.ui.config("web", "style", "")
706 style = self.repo.ui.config("web", "style", "")
717 if req.form.has_key('style'):
707 if req.form.has_key('style'):
718 style = req.form['style'][0]
708 style = req.form['style'][0]
719 if style:
709 if style:
720 b = os.path.basename("map-" + style)
710 b = os.path.basename("map-" + style)
721 p = os.path.join(t, b)
711 p = os.path.join(t, b)
722 if os.path.isfile(p):
712 if os.path.isfile(p):
723 m = p
713 m = p
724
714
725 port = req.env["SERVER_PORT"]
715 port = req.env["SERVER_PORT"]
726 port = port != "80" and (":" + port) or ""
716 port = port != "80" and (":" + port) or ""
727 uri = req.env["REQUEST_URI"]
717 uri = req.env["REQUEST_URI"]
728 if "?" in uri:
718 if "?" in uri:
729 uri = uri.split("?")[0]
719 uri = uri.split("?")[0]
730 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
720 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
731 if not self.reponame:
721 if not self.reponame:
732 self.reponame = (self.repo.ui.config("web", "name")
722 self.reponame = (self.repo.ui.config("web", "name")
733 or uri.strip('/') or self.repo.root)
723 or uri.strip('/') or self.repo.root)
734
724
735 self.t = templater(m, common_filters,
725 self.t = templater(m, common_filters,
736 {"url": url,
726 {"url": url,
737 "repo": self.reponame,
727 "repo": self.reponame,
738 "header": header,
728 "header": header,
739 "footer": footer,
729 "footer": footer,
740 })
730 })
741
731
742 if not req.form.has_key('cmd'):
732 if not req.form.has_key('cmd'):
743 req.form['cmd'] = [self.t.cache['default'],]
733 req.form['cmd'] = [self.t.cache['default'],]
744
734
745 if req.form['cmd'][0] == 'changelog':
735 if req.form['cmd'][0] == 'changelog':
746 c = self.repo.changelog.count() - 1
736 c = self.repo.changelog.count() - 1
747 hi = c
737 hi = c
748 if req.form.has_key('rev'):
738 if req.form.has_key('rev'):
749 hi = req.form['rev'][0]
739 hi = req.form['rev'][0]
750 try:
740 try:
751 hi = self.repo.changelog.rev(self.repo.lookup(hi))
741 hi = self.repo.changelog.rev(self.repo.lookup(hi))
752 except hg.RepoError:
742 except hg.RepoError:
753 req.write(self.search(hi))
743 req.write(self.search(hi))
754 return
744 return
755
745
756 req.write(self.changelog(hi))
746 req.write(self.changelog(hi))
757
747
758 elif req.form['cmd'][0] == 'changeset':
748 elif req.form['cmd'][0] == 'changeset':
759 req.write(self.changeset(req.form['node'][0]))
749 req.write(self.changeset(req.form['node'][0]))
760
750
761 elif req.form['cmd'][0] == 'manifest':
751 elif req.form['cmd'][0] == 'manifest':
762 req.write(self.manifest(req.form['manifest'][0], req.form['path'][0]))
752 req.write(self.manifest(req.form['manifest'][0], req.form['path'][0]))
763
753
764 elif req.form['cmd'][0] == 'tags':
754 elif req.form['cmd'][0] == 'tags':
765 req.write(self.tags())
755 req.write(self.tags())
766
756
767 elif req.form['cmd'][0] == 'filediff':
757 elif req.form['cmd'][0] == 'filediff':
768 req.write(self.filediff(req.form['file'][0], req.form['node'][0]))
758 req.write(self.filediff(req.form['file'][0], req.form['node'][0]))
769
759
770 elif req.form['cmd'][0] == 'file':
760 elif req.form['cmd'][0] == 'file':
771 req.write(self.filerevision(req.form['file'][0], req.form['filenode'][0]))
761 req.write(self.filerevision(req.form['file'][0], req.form['filenode'][0]))
772
762
773 elif req.form['cmd'][0] == 'annotate':
763 elif req.form['cmd'][0] == 'annotate':
774 req.write(self.fileannotate(req.form['file'][0], req.form['filenode'][0]))
764 req.write(self.fileannotate(req.form['file'][0], req.form['filenode'][0]))
775
765
776 elif req.form['cmd'][0] == 'filelog':
766 elif req.form['cmd'][0] == 'filelog':
777 req.write(self.filelog(req.form['file'][0], req.form['filenode'][0]))
767 req.write(self.filelog(req.form['file'][0], req.form['filenode'][0]))
778
768
779 elif req.form['cmd'][0] == 'heads':
769 elif req.form['cmd'][0] == 'heads':
780 req.httphdr("application/mercurial-0.1")
770 req.httphdr("application/mercurial-0.1")
781 h = self.repo.heads()
771 h = self.repo.heads()
782 req.write(" ".join(map(hex, h)) + "\n")
772 req.write(" ".join(map(hex, h)) + "\n")
783
773
784 elif req.form['cmd'][0] == 'branches':
774 elif req.form['cmd'][0] == 'branches':
785 req.httphdr("application/mercurial-0.1")
775 req.httphdr("application/mercurial-0.1")
786 nodes = []
776 nodes = []
787 if req.form.has_key('nodes'):
777 if req.form.has_key('nodes'):
788 nodes = map(bin, req.form['nodes'][0].split(" "))
778 nodes = map(bin, req.form['nodes'][0].split(" "))
789 for b in self.repo.branches(nodes):
779 for b in self.repo.branches(nodes):
790 req.write(" ".join(map(hex, b)) + "\n")
780 req.write(" ".join(map(hex, b)) + "\n")
791
781
792 elif req.form['cmd'][0] == 'between':
782 elif req.form['cmd'][0] == 'between':
793 req.httphdr("application/mercurial-0.1")
783 req.httphdr("application/mercurial-0.1")
794 nodes = []
784 nodes = []
795 if req.form.has_key('pairs'):
785 if req.form.has_key('pairs'):
796 pairs = [map(bin, p.split("-"))
786 pairs = [map(bin, p.split("-"))
797 for p in req.form['pairs'][0].split(" ")]
787 for p in req.form['pairs'][0].split(" ")]
798 for b in self.repo.between(pairs):
788 for b in self.repo.between(pairs):
799 req.write(" ".join(map(hex, b)) + "\n")
789 req.write(" ".join(map(hex, b)) + "\n")
800
790
801 elif req.form['cmd'][0] == 'changegroup':
791 elif req.form['cmd'][0] == 'changegroup':
802 req.httphdr("application/mercurial-0.1")
792 req.httphdr("application/mercurial-0.1")
803 nodes = []
793 nodes = []
804 if not self.allowpull:
794 if not self.allowpull:
805 return
795 return
806
796
807 if req.form.has_key('roots'):
797 if req.form.has_key('roots'):
808 nodes = map(bin, req.form['roots'][0].split(" "))
798 nodes = map(bin, req.form['roots'][0].split(" "))
809
799
810 z = zlib.compressobj()
800 z = zlib.compressobj()
811 f = self.repo.changegroup(nodes)
801 f = self.repo.changegroup(nodes)
812 while 1:
802 while 1:
813 chunk = f.read(4096)
803 chunk = f.read(4096)
814 if not chunk:
804 if not chunk:
815 break
805 break
816 req.write(z.compress(chunk))
806 req.write(z.compress(chunk))
817
807
818 req.write(z.flush())
808 req.write(z.flush())
819
809
820 elif req.form['cmd'][0] == 'archive':
810 elif req.form['cmd'][0] == 'archive':
821 changeset = bin(req.form['node'][0])
811 changeset = bin(req.form['node'][0])
822 type = req.form['type'][0]
812 type = req.form['type'][0]
823 if (type in self.archives and
813 if (type in self.archives and
824 self.repo.ui.configbool("web", "allow" + type, False)):
814 self.repo.ui.configbool("web", "allow" + type, False)):
825 self.archive(req, changeset, type)
815 self.archive(req, changeset, type)
826 return
816 return
827
817
828 req.write(self.t("error"))
818 req.write(self.t("error"))
829
819
830 else:
820 else:
831 req.write(self.t("error"))
821 req.write(self.t("error"))
832
822
833 def create_server(repo):
823 def create_server(repo):
834
824
835 def openlog(opt, default):
825 def openlog(opt, default):
836 if opt and opt != '-':
826 if opt and opt != '-':
837 return open(opt, 'w')
827 return open(opt, 'w')
838 return default
828 return default
839
829
840 address = repo.ui.config("web", "address", "")
830 address = repo.ui.config("web", "address", "")
841 port = int(repo.ui.config("web", "port", 8000))
831 port = int(repo.ui.config("web", "port", 8000))
842 use_ipv6 = repo.ui.configbool("web", "ipv6")
832 use_ipv6 = repo.ui.configbool("web", "ipv6")
843 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
833 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
844 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
834 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
845
835
846 class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
836 class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
847 address_family = getattr(socket, 'AF_INET6', None)
837 address_family = getattr(socket, 'AF_INET6', None)
848
838
849 def __init__(self, *args, **kwargs):
839 def __init__(self, *args, **kwargs):
850 if self.address_family is None:
840 if self.address_family is None:
851 raise hg.RepoError('IPv6 not available on this system')
841 raise hg.RepoError('IPv6 not available on this system')
852 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
842 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
853
843
854 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
844 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
855 def log_error(self, format, *args):
845 def log_error(self, format, *args):
856 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
846 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
857 self.log_date_time_string(),
847 self.log_date_time_string(),
858 format % args))
848 format % args))
859
849
860 def log_message(self, format, *args):
850 def log_message(self, format, *args):
861 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
851 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
862 self.log_date_time_string(),
852 self.log_date_time_string(),
863 format % args))
853 format % args))
864
854
865 def do_POST(self):
855 def do_POST(self):
866 try:
856 try:
867 self.do_hgweb()
857 self.do_hgweb()
868 except socket.error, inst:
858 except socket.error, inst:
869 if inst[0] != errno.EPIPE:
859 if inst[0] != errno.EPIPE:
870 raise
860 raise
871
861
872 def do_GET(self):
862 def do_GET(self):
873 self.do_POST()
863 self.do_POST()
874
864
875 def do_hgweb(self):
865 def do_hgweb(self):
876 query = ""
866 query = ""
877 p = self.path.find("?")
867 p = self.path.find("?")
878 if p:
868 if p:
879 query = self.path[p + 1:]
869 query = self.path[p + 1:]
880 query = query.replace('+', ' ')
870 query = query.replace('+', ' ')
881
871
882 env = {}
872 env = {}
883 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
873 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
884 env['REQUEST_METHOD'] = self.command
874 env['REQUEST_METHOD'] = self.command
885 env['SERVER_NAME'] = self.server.server_name
875 env['SERVER_NAME'] = self.server.server_name
886 env['SERVER_PORT'] = str(self.server.server_port)
876 env['SERVER_PORT'] = str(self.server.server_port)
887 env['REQUEST_URI'] = "/"
877 env['REQUEST_URI'] = "/"
888 if query:
878 if query:
889 env['QUERY_STRING'] = query
879 env['QUERY_STRING'] = query
890 host = self.address_string()
880 host = self.address_string()
891 if host != self.client_address[0]:
881 if host != self.client_address[0]:
892 env['REMOTE_HOST'] = host
882 env['REMOTE_HOST'] = host
893 env['REMOTE_ADDR'] = self.client_address[0]
883 env['REMOTE_ADDR'] = self.client_address[0]
894
884
895 if self.headers.typeheader is None:
885 if self.headers.typeheader is None:
896 env['CONTENT_TYPE'] = self.headers.type
886 env['CONTENT_TYPE'] = self.headers.type
897 else:
887 else:
898 env['CONTENT_TYPE'] = self.headers.typeheader
888 env['CONTENT_TYPE'] = self.headers.typeheader
899 length = self.headers.getheader('content-length')
889 length = self.headers.getheader('content-length')
900 if length:
890 if length:
901 env['CONTENT_LENGTH'] = length
891 env['CONTENT_LENGTH'] = length
902 accept = []
892 accept = []
903 for line in self.headers.getallmatchingheaders('accept'):
893 for line in self.headers.getallmatchingheaders('accept'):
904 if line[:1] in "\t\n\r ":
894 if line[:1] in "\t\n\r ":
905 accept.append(line.strip())
895 accept.append(line.strip())
906 else:
896 else:
907 accept = accept + line[7:].split(',')
897 accept = accept + line[7:].split(',')
908 env['HTTP_ACCEPT'] = ','.join(accept)
898 env['HTTP_ACCEPT'] = ','.join(accept)
909
899
910 req = hgrequest(self.rfile, self.wfile, env)
900 req = hgrequest(self.rfile, self.wfile, env)
911 self.send_response(200, "Script output follows")
901 self.send_response(200, "Script output follows")
912 hg.run(req)
902 hg.run(req)
913
903
914 hg = hgweb(repo)
904 hg = hgweb(repo)
915 if use_ipv6:
905 if use_ipv6:
916 return IPv6HTTPServer((address, port), hgwebhandler)
906 return IPv6HTTPServer((address, port), hgwebhandler)
917 else:
907 else:
918 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
908 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
919
909
920 def server(path, name, templates, address, port, use_ipv6=False,
910 def server(path, name, templates, address, port, use_ipv6=False,
921 accesslog=sys.stdout, errorlog=sys.stderr):
911 accesslog=sys.stdout, errorlog=sys.stderr):
922 httpd = create_server(path, name, templates, address, port, use_ipv6,
912 httpd = create_server(path, name, templates, address, port, use_ipv6,
923 accesslog, errorlog)
913 accesslog, errorlog)
924 httpd.serve_forever()
914 httpd.serve_forever()
925
915
926 # This is a stopgap
916 # This is a stopgap
927 class hgwebdir:
917 class hgwebdir:
928 def __init__(self, config):
918 def __init__(self, config):
929 def cleannames(items):
919 def cleannames(items):
930 return [(name.strip('/'), path) for name, path in items]
920 return [(name.strip('/'), path) for name, path in items]
931
921
932 if type(config) == type([]):
922 if type(config) == type([]):
933 self.repos = cleannames(config)
923 self.repos = cleannames(config)
934 elif type(config) == type({}):
924 elif type(config) == type({}):
935 self.repos = cleannames(config.items())
925 self.repos = cleannames(config.items())
936 self.repos.sort()
926 self.repos.sort()
937 else:
927 else:
938 cp = ConfigParser.SafeConfigParser()
928 cp = ConfigParser.SafeConfigParser()
939 cp.read(config)
929 cp.read(config)
940 self.repos = cleannames(cp.items("paths"))
930 self.repos = cleannames(cp.items("paths"))
941 self.repos.sort()
931 self.repos.sort()
942
932
943 def run(self, req=hgrequest()):
933 def run(self, req=hgrequest()):
944 def header(**map):
934 def header(**map):
945 yield tmpl("header", **map)
935 yield tmpl("header", **map)
946
936
947 def footer(**map):
937 def footer(**map):
948 yield tmpl("footer", **map)
938 yield tmpl("footer", **map)
949
939
950 m = os.path.join(templatepath(), "map")
940 m = os.path.join(templatepath(), "map")
951 tmpl = templater(m, common_filters,
941 tmpl = templater(m, common_filters,
952 {"header": header, "footer": footer})
942 {"header": header, "footer": footer})
953
943
954 def entries(**map):
944 def entries(**map):
955 parity = 0
945 parity = 0
956 for name, path in self.repos:
946 for name, path in self.repos:
957 u = ui.ui()
947 u = ui.ui()
958 try:
948 try:
959 u.readconfig(file(os.path.join(path, '.hg', 'hgrc')))
949 u.readconfig(file(os.path.join(path, '.hg', 'hgrc')))
960 except IOError:
950 except IOError:
961 pass
951 pass
962 get = u.config
952 get = u.config
963
953
964 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
954 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
965 .replace("//", "/"))
955 .replace("//", "/"))
966
956
967 yield dict(contact=(get("ui", "username") or # preferred
957 yield dict(contact=(get("ui", "username") or # preferred
968 get("web", "contact") or # deprecated
958 get("web", "contact") or # deprecated
969 get("web", "author", "unknown")), # also
959 get("web", "author", "unknown")), # also
970 name=get("web", "name", name),
960 name=get("web", "name", name),
971 url=url,
961 url=url,
972 parity=parity,
962 parity=parity,
973 shortdesc=get("web", "description", "unknown"),
963 shortdesc=get("web", "description", "unknown"),
974 lastupdate=os.stat(os.path.join(path, ".hg",
964 lastupdate=os.stat(os.path.join(path, ".hg",
975 "00changelog.d")).st_mtime)
965 "00changelog.d")).st_mtime)
976
966
977 parity = 1 - parity
967 parity = 1 - parity
978
968
979 virtual = req.env.get("PATH_INFO", "").strip('/')
969 virtual = req.env.get("PATH_INFO", "").strip('/')
980 if virtual:
970 if virtual:
981 real = dict(self.repos).get(virtual)
971 real = dict(self.repos).get(virtual)
982 if real:
972 if real:
983 hgweb(real).run(req)
973 hgweb(real).run(req)
984 else:
974 else:
985 req.write(tmpl("notfound", repo=virtual))
975 req.write(tmpl("notfound", repo=virtual))
986 else:
976 else:
987 req.write(tmpl("index", entries=entries))
977 req.write(tmpl("index", entries=entries))
@@ -1,545 +1,565 b''
1 """
1 """
2 util.py - Mercurial utility functions and platform specfic implementations
2 util.py - Mercurial utility functions and platform specfic implementations
3
3
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 Copyright 2005 K. Thananchayan <thananck@yahoo.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 This contains helper routines that are independent of the SCM core and hide
9 This contains helper routines that are independent of the SCM core and hide
10 platform-specific details from the core.
10 platform-specific details from the core.
11 """
11 """
12
12
13 import os, errno
13 import os, errno
14 from demandload import *
14 from demandload import *
15 demandload(globals(), "re cStringIO shutil popen2 tempfile threading")
15 demandload(globals(), "re cStringIO shutil popen2 tempfile threading time")
16
16
17 def pipefilter(s, cmd):
17 def pipefilter(s, cmd):
18 '''filter string S through command CMD, returning its output'''
18 '''filter string S through command CMD, returning its output'''
19 (pout, pin) = popen2.popen2(cmd, -1, 'b')
19 (pout, pin) = popen2.popen2(cmd, -1, 'b')
20 def writer():
20 def writer():
21 pin.write(s)
21 pin.write(s)
22 pin.close()
22 pin.close()
23
23
24 # we should use select instead on UNIX, but this will work on most
24 # we should use select instead on UNIX, but this will work on most
25 # systems, including Windows
25 # systems, including Windows
26 w = threading.Thread(target=writer)
26 w = threading.Thread(target=writer)
27 w.start()
27 w.start()
28 f = pout.read()
28 f = pout.read()
29 pout.close()
29 pout.close()
30 w.join()
30 w.join()
31 return f
31 return f
32
32
33 def tempfilter(s, cmd):
33 def tempfilter(s, cmd):
34 '''filter string S through a pair of temporary files with CMD.
34 '''filter string S through a pair of temporary files with CMD.
35 CMD is used as a template to create the real command to be run,
35 CMD is used as a template to create the real command to be run,
36 with the strings INFILE and OUTFILE replaced by the real names of
36 with the strings INFILE and OUTFILE replaced by the real names of
37 the temporary files generated.'''
37 the temporary files generated.'''
38 inname, outname = None, None
38 inname, outname = None, None
39 try:
39 try:
40 infd, inname = tempfile.mkstemp(prefix='hgfin')
40 infd, inname = tempfile.mkstemp(prefix='hgfin')
41 fp = os.fdopen(infd, 'wb')
41 fp = os.fdopen(infd, 'wb')
42 fp.write(s)
42 fp.write(s)
43 fp.close()
43 fp.close()
44 outfd, outname = tempfile.mkstemp(prefix='hgfout')
44 outfd, outname = tempfile.mkstemp(prefix='hgfout')
45 os.close(outfd)
45 os.close(outfd)
46 cmd = cmd.replace('INFILE', inname)
46 cmd = cmd.replace('INFILE', inname)
47 cmd = cmd.replace('OUTFILE', outname)
47 cmd = cmd.replace('OUTFILE', outname)
48 code = os.system(cmd)
48 code = os.system(cmd)
49 if code: raise Abort("command '%s' failed: %s" %
49 if code: raise Abort("command '%s' failed: %s" %
50 (cmd, explain_exit(code)))
50 (cmd, explain_exit(code)))
51 return open(outname, 'rb').read()
51 return open(outname, 'rb').read()
52 finally:
52 finally:
53 try:
53 try:
54 if inname: os.unlink(inname)
54 if inname: os.unlink(inname)
55 except: pass
55 except: pass
56 try:
56 try:
57 if outname: os.unlink(outname)
57 if outname: os.unlink(outname)
58 except: pass
58 except: pass
59
59
60 filtertable = {
60 filtertable = {
61 'tempfile:': tempfilter,
61 'tempfile:': tempfilter,
62 'pipe:': pipefilter,
62 'pipe:': pipefilter,
63 }
63 }
64
64
65 def filter(s, cmd):
65 def filter(s, cmd):
66 "filter a string through a command that transforms its input to its output"
66 "filter a string through a command that transforms its input to its output"
67 for name, fn in filtertable.iteritems():
67 for name, fn in filtertable.iteritems():
68 if cmd.startswith(name):
68 if cmd.startswith(name):
69 return fn(s, cmd[len(name):].lstrip())
69 return fn(s, cmd[len(name):].lstrip())
70 return pipefilter(s, cmd)
70 return pipefilter(s, cmd)
71
71
72 def patch(strip, patchname, ui):
72 def patch(strip, patchname, ui):
73 """apply the patch <patchname> to the working directory.
73 """apply the patch <patchname> to the working directory.
74 a list of patched files is returned"""
74 a list of patched files is returned"""
75 fp = os.popen('patch -p%d < "%s"' % (strip, patchname))
75 fp = os.popen('patch -p%d < "%s"' % (strip, patchname))
76 files = {}
76 files = {}
77 for line in fp:
77 for line in fp:
78 line = line.rstrip()
78 line = line.rstrip()
79 ui.status("%s\n" % line)
79 ui.status("%s\n" % line)
80 if line.startswith('patching file '):
80 if line.startswith('patching file '):
81 pf = parse_patch_output(line)
81 pf = parse_patch_output(line)
82 files.setdefault(pf, 1)
82 files.setdefault(pf, 1)
83 code = fp.close()
83 code = fp.close()
84 if code:
84 if code:
85 raise Abort("patch command failed: %s" % explain_exit(code))
85 raise Abort("patch command failed: %s" % explain_exit(code))
86 return files.keys()
86 return files.keys()
87
87
88 def binary(s):
88 def binary(s):
89 """return true if a string is binary data using diff's heuristic"""
89 """return true if a string is binary data using diff's heuristic"""
90 if s and '\0' in s[:4096]:
90 if s and '\0' in s[:4096]:
91 return True
91 return True
92 return False
92 return False
93
93
94 def unique(g):
94 def unique(g):
95 """return the uniq elements of iterable g"""
95 """return the uniq elements of iterable g"""
96 seen = {}
96 seen = {}
97 for f in g:
97 for f in g:
98 if f not in seen:
98 if f not in seen:
99 seen[f] = 1
99 seen[f] = 1
100 yield f
100 yield f
101
101
102 class Abort(Exception):
102 class Abort(Exception):
103 """Raised if a command needs to print an error and exit."""
103 """Raised if a command needs to print an error and exit."""
104
104
105 def always(fn): return True
105 def always(fn): return True
106 def never(fn): return False
106 def never(fn): return False
107
107
108 def globre(pat, head='^', tail='$'):
108 def globre(pat, head='^', tail='$'):
109 "convert a glob pattern into a regexp"
109 "convert a glob pattern into a regexp"
110 i, n = 0, len(pat)
110 i, n = 0, len(pat)
111 res = ''
111 res = ''
112 group = False
112 group = False
113 def peek(): return i < n and pat[i]
113 def peek(): return i < n and pat[i]
114 while i < n:
114 while i < n:
115 c = pat[i]
115 c = pat[i]
116 i = i+1
116 i = i+1
117 if c == '*':
117 if c == '*':
118 if peek() == '*':
118 if peek() == '*':
119 i += 1
119 i += 1
120 res += '.*'
120 res += '.*'
121 else:
121 else:
122 res += '[^/]*'
122 res += '[^/]*'
123 elif c == '?':
123 elif c == '?':
124 res += '.'
124 res += '.'
125 elif c == '[':
125 elif c == '[':
126 j = i
126 j = i
127 if j < n and pat[j] in '!]':
127 if j < n and pat[j] in '!]':
128 j += 1
128 j += 1
129 while j < n and pat[j] != ']':
129 while j < n and pat[j] != ']':
130 j += 1
130 j += 1
131 if j >= n:
131 if j >= n:
132 res += '\\['
132 res += '\\['
133 else:
133 else:
134 stuff = pat[i:j].replace('\\','\\\\')
134 stuff = pat[i:j].replace('\\','\\\\')
135 i = j + 1
135 i = j + 1
136 if stuff[0] == '!':
136 if stuff[0] == '!':
137 stuff = '^' + stuff[1:]
137 stuff = '^' + stuff[1:]
138 elif stuff[0] == '^':
138 elif stuff[0] == '^':
139 stuff = '\\' + stuff
139 stuff = '\\' + stuff
140 res = '%s[%s]' % (res, stuff)
140 res = '%s[%s]' % (res, stuff)
141 elif c == '{':
141 elif c == '{':
142 group = True
142 group = True
143 res += '(?:'
143 res += '(?:'
144 elif c == '}' and group:
144 elif c == '}' and group:
145 res += ')'
145 res += ')'
146 group = False
146 group = False
147 elif c == ',' and group:
147 elif c == ',' and group:
148 res += '|'
148 res += '|'
149 else:
149 else:
150 res += re.escape(c)
150 res += re.escape(c)
151 return head + res + tail
151 return head + res + tail
152
152
153 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
153 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
154
154
155 def pathto(n1, n2):
155 def pathto(n1, n2):
156 '''return the relative path from one place to another.
156 '''return the relative path from one place to another.
157 this returns a path in the form used by the local filesystem, not hg.'''
157 this returns a path in the form used by the local filesystem, not hg.'''
158 if not n1: return localpath(n2)
158 if not n1: return localpath(n2)
159 a, b = n1.split('/'), n2.split('/')
159 a, b = n1.split('/'), n2.split('/')
160 a.reverse(), b.reverse()
160 a.reverse(), b.reverse()
161 while a and b and a[-1] == b[-1]:
161 while a and b and a[-1] == b[-1]:
162 a.pop(), b.pop()
162 a.pop(), b.pop()
163 b.reverse()
163 b.reverse()
164 return os.sep.join((['..'] * len(a)) + b)
164 return os.sep.join((['..'] * len(a)) + b)
165
165
166 def canonpath(root, cwd, myname):
166 def canonpath(root, cwd, myname):
167 """return the canonical path of myname, given cwd and root"""
167 """return the canonical path of myname, given cwd and root"""
168 rootsep = root + os.sep
168 rootsep = root + os.sep
169 name = myname
169 name = myname
170 if not name.startswith(os.sep):
170 if not name.startswith(os.sep):
171 name = os.path.join(root, cwd, name)
171 name = os.path.join(root, cwd, name)
172 name = os.path.normpath(name)
172 name = os.path.normpath(name)
173 if name.startswith(rootsep):
173 if name.startswith(rootsep):
174 return pconvert(name[len(rootsep):])
174 return pconvert(name[len(rootsep):])
175 elif name == root:
175 elif name == root:
176 return ''
176 return ''
177 else:
177 else:
178 raise Abort('%s not under root' % myname)
178 raise Abort('%s not under root' % myname)
179
179
180 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head=''):
180 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head=''):
181 """build a function to match a set of file patterns
181 """build a function to match a set of file patterns
182
182
183 arguments:
183 arguments:
184 canonroot - the canonical root of the tree you're matching against
184 canonroot - the canonical root of the tree you're matching against
185 cwd - the current working directory, if relevant
185 cwd - the current working directory, if relevant
186 names - patterns to find
186 names - patterns to find
187 inc - patterns to include
187 inc - patterns to include
188 exc - patterns to exclude
188 exc - patterns to exclude
189 head - a regex to prepend to patterns to control whether a match is rooted
189 head - a regex to prepend to patterns to control whether a match is rooted
190
190
191 a pattern is one of:
191 a pattern is one of:
192 'glob:<rooted glob>'
192 'glob:<rooted glob>'
193 're:<rooted regexp>'
193 're:<rooted regexp>'
194 'path:<rooted path>'
194 'path:<rooted path>'
195 'relglob:<relative glob>'
195 'relglob:<relative glob>'
196 'relpath:<relative path>'
196 'relpath:<relative path>'
197 'relre:<relative regexp>'
197 'relre:<relative regexp>'
198 '<rooted path or regexp>'
198 '<rooted path or regexp>'
199
199
200 returns:
200 returns:
201 a 3-tuple containing
201 a 3-tuple containing
202 - list of explicit non-pattern names passed in
202 - list of explicit non-pattern names passed in
203 - a bool match(filename) function
203 - a bool match(filename) function
204 - a bool indicating if any patterns were passed in
204 - a bool indicating if any patterns were passed in
205
205
206 todo:
206 todo:
207 make head regex a rooted bool
207 make head regex a rooted bool
208 """
208 """
209
209
210 def patkind(name):
210 def patkind(name):
211 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
211 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
212 if name.startswith(prefix + ':'): return name.split(':', 1)
212 if name.startswith(prefix + ':'): return name.split(':', 1)
213 for c in name:
213 for c in name:
214 if c in _globchars: return 'glob', name
214 if c in _globchars: return 'glob', name
215 return 'relpath', name
215 return 'relpath', name
216
216
217 def regex(kind, name, tail):
217 def regex(kind, name, tail):
218 '''convert a pattern into a regular expression'''
218 '''convert a pattern into a regular expression'''
219 if kind == 're':
219 if kind == 're':
220 return name
220 return name
221 elif kind == 'path':
221 elif kind == 'path':
222 return '^' + re.escape(name) + '(?:/|$)'
222 return '^' + re.escape(name) + '(?:/|$)'
223 elif kind == 'relglob':
223 elif kind == 'relglob':
224 return head + globre(name, '(?:|.*/)', tail)
224 return head + globre(name, '(?:|.*/)', tail)
225 elif kind == 'relpath':
225 elif kind == 'relpath':
226 return head + re.escape(name) + tail
226 return head + re.escape(name) + tail
227 elif kind == 'relre':
227 elif kind == 'relre':
228 if name.startswith('^'):
228 if name.startswith('^'):
229 return name
229 return name
230 return '.*' + name
230 return '.*' + name
231 return head + globre(name, '', tail)
231 return head + globre(name, '', tail)
232
232
233 def matchfn(pats, tail):
233 def matchfn(pats, tail):
234 """build a matching function from a set of patterns"""
234 """build a matching function from a set of patterns"""
235 if pats:
235 if pats:
236 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
236 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
237 return re.compile(pat).match
237 return re.compile(pat).match
238
238
239 def globprefix(pat):
239 def globprefix(pat):
240 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
240 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
241 root = []
241 root = []
242 for p in pat.split(os.sep):
242 for p in pat.split(os.sep):
243 if patkind(p)[0] == 'glob': break
243 if patkind(p)[0] == 'glob': break
244 root.append(p)
244 root.append(p)
245 return '/'.join(root)
245 return '/'.join(root)
246
246
247 pats = []
247 pats = []
248 files = []
248 files = []
249 roots = []
249 roots = []
250 for kind, name in map(patkind, names):
250 for kind, name in map(patkind, names):
251 if kind in ('glob', 'relpath'):
251 if kind in ('glob', 'relpath'):
252 name = canonpath(canonroot, cwd, name)
252 name = canonpath(canonroot, cwd, name)
253 if name == '':
253 if name == '':
254 kind, name = 'glob', '**'
254 kind, name = 'glob', '**'
255 if kind in ('glob', 'path', 're'):
255 if kind in ('glob', 'path', 're'):
256 pats.append((kind, name))
256 pats.append((kind, name))
257 if kind == 'glob':
257 if kind == 'glob':
258 root = globprefix(name)
258 root = globprefix(name)
259 if root: roots.append(root)
259 if root: roots.append(root)
260 elif kind == 'relpath':
260 elif kind == 'relpath':
261 files.append((kind, name))
261 files.append((kind, name))
262 roots.append(name)
262 roots.append(name)
263
263
264 patmatch = matchfn(pats, '$') or always
264 patmatch = matchfn(pats, '$') or always
265 filematch = matchfn(files, '(?:/|$)') or always
265 filematch = matchfn(files, '(?:/|$)') or always
266 incmatch = always
266 incmatch = always
267 if inc:
267 if inc:
268 incmatch = matchfn(map(patkind, inc), '(?:/|$)')
268 incmatch = matchfn(map(patkind, inc), '(?:/|$)')
269 excmatch = lambda fn: False
269 excmatch = lambda fn: False
270 if exc:
270 if exc:
271 excmatch = matchfn(map(patkind, exc), '(?:/|$)')
271 excmatch = matchfn(map(patkind, exc), '(?:/|$)')
272
272
273 return (roots,
273 return (roots,
274 lambda fn: (incmatch(fn) and not excmatch(fn) and
274 lambda fn: (incmatch(fn) and not excmatch(fn) and
275 (fn.endswith('/') or
275 (fn.endswith('/') or
276 (not pats and not files) or
276 (not pats and not files) or
277 (pats and patmatch(fn)) or
277 (pats and patmatch(fn)) or
278 (files and filematch(fn)))),
278 (files and filematch(fn)))),
279 (inc or exc or (pats and pats != [('glob', '**')])) and True)
279 (inc or exc or (pats and pats != [('glob', '**')])) and True)
280
280
281 def system(cmd, errprefix=None):
281 def system(cmd, errprefix=None):
282 """execute a shell command that must succeed"""
282 """execute a shell command that must succeed"""
283 rc = os.system(cmd)
283 rc = os.system(cmd)
284 if rc:
284 if rc:
285 errmsg = "%s %s" % (os.path.basename(cmd.split(None, 1)[0]),
285 errmsg = "%s %s" % (os.path.basename(cmd.split(None, 1)[0]),
286 explain_exit(rc)[0])
286 explain_exit(rc)[0])
287 if errprefix:
287 if errprefix:
288 errmsg = "%s: %s" % (errprefix, errmsg)
288 errmsg = "%s: %s" % (errprefix, errmsg)
289 raise Abort(errmsg)
289 raise Abort(errmsg)
290
290
291 def rename(src, dst):
291 def rename(src, dst):
292 """forcibly rename a file"""
292 """forcibly rename a file"""
293 try:
293 try:
294 os.rename(src, dst)
294 os.rename(src, dst)
295 except:
295 except:
296 os.unlink(dst)
296 os.unlink(dst)
297 os.rename(src, dst)
297 os.rename(src, dst)
298
298
299 def copyfiles(src, dst, hardlink=None):
299 def copyfiles(src, dst, hardlink=None):
300 """Copy a directory tree using hardlinks if possible"""
300 """Copy a directory tree using hardlinks if possible"""
301
301
302 if hardlink is None:
302 if hardlink is None:
303 hardlink = (os.stat(src).st_dev ==
303 hardlink = (os.stat(src).st_dev ==
304 os.stat(os.path.dirname(dst)).st_dev)
304 os.stat(os.path.dirname(dst)).st_dev)
305
305
306 if os.path.isdir(src):
306 if os.path.isdir(src):
307 os.mkdir(dst)
307 os.mkdir(dst)
308 for name in os.listdir(src):
308 for name in os.listdir(src):
309 srcname = os.path.join(src, name)
309 srcname = os.path.join(src, name)
310 dstname = os.path.join(dst, name)
310 dstname = os.path.join(dst, name)
311 copyfiles(srcname, dstname, hardlink)
311 copyfiles(srcname, dstname, hardlink)
312 else:
312 else:
313 if hardlink:
313 if hardlink:
314 try:
314 try:
315 os_link(src, dst)
315 os_link(src, dst)
316 except:
316 except:
317 hardlink = False
317 hardlink = False
318 shutil.copy2(src, dst)
318 shutil.copy2(src, dst)
319 else:
319 else:
320 shutil.copy2(src, dst)
320 shutil.copy2(src, dst)
321
321
322 def opener(base):
322 def opener(base):
323 """
323 """
324 return a function that opens files relative to base
324 return a function that opens files relative to base
325
325
326 this function is used to hide the details of COW semantics and
326 this function is used to hide the details of COW semantics and
327 remote file access from higher level code.
327 remote file access from higher level code.
328 """
328 """
329 p = base
329 p = base
330 def o(path, mode="r"):
330 def o(path, mode="r"):
331 f = os.path.join(p, path)
331 f = os.path.join(p, path)
332
332
333 mode += "b" # for that other OS
333 mode += "b" # for that other OS
334
334
335 if mode[0] != "r":
335 if mode[0] != "r":
336 try:
336 try:
337 nlink = nlinks(f)
337 nlink = nlinks(f)
338 except OSError:
338 except OSError:
339 d = os.path.dirname(f)
339 d = os.path.dirname(f)
340 if not os.path.isdir(d):
340 if not os.path.isdir(d):
341 os.makedirs(d)
341 os.makedirs(d)
342 else:
342 else:
343 if nlink > 1:
343 if nlink > 1:
344 file(f + ".tmp", "wb").write(file(f, "rb").read())
344 file(f + ".tmp", "wb").write(file(f, "rb").read())
345 rename(f+".tmp", f)
345 rename(f+".tmp", f)
346
346
347 return file(f, mode)
347 return file(f, mode)
348
348
349 return o
349 return o
350
350
351 def _makelock_file(info, pathname):
351 def _makelock_file(info, pathname):
352 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
352 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
353 os.write(ld, info)
353 os.write(ld, info)
354 os.close(ld)
354 os.close(ld)
355
355
356 def _readlock_file(pathname):
356 def _readlock_file(pathname):
357 return file(pathname).read()
357 return file(pathname).read()
358
358
359 def nlinks(pathname):
359 def nlinks(pathname):
360 """Return number of hardlinks for the given file."""
360 """Return number of hardlinks for the given file."""
361 return os.stat(pathname).st_nlink
361 return os.stat(pathname).st_nlink
362
362
363 if hasattr(os, 'link'):
363 if hasattr(os, 'link'):
364 os_link = os.link
364 os_link = os.link
365 else:
365 else:
366 def os_link(src, dst):
366 def os_link(src, dst):
367 raise OSError(0, "Hardlinks not supported")
367 raise OSError(0, "Hardlinks not supported")
368
368
369 # Platform specific variants
369 # Platform specific variants
370 if os.name == 'nt':
370 if os.name == 'nt':
371 nulldev = 'NUL:'
371 nulldev = 'NUL:'
372
372
373 rcpath = (r'c:\mercurial\mercurial.ini',
373 rcpath = (r'c:\mercurial\mercurial.ini',
374 os.path.join(os.path.expanduser('~'), 'mercurial.ini'))
374 os.path.join(os.path.expanduser('~'), 'mercurial.ini'))
375
375
376 def parse_patch_output(output_line):
376 def parse_patch_output(output_line):
377 """parses the output produced by patch and returns the file name"""
377 """parses the output produced by patch and returns the file name"""
378 pf = output_line[14:]
378 pf = output_line[14:]
379 if pf[0] == '`':
379 if pf[0] == '`':
380 pf = pf[1:-1] # Remove the quotes
380 pf = pf[1:-1] # Remove the quotes
381 return pf
381 return pf
382
382
383 try: # ActivePython can create hard links using win32file module
383 try: # ActivePython can create hard links using win32file module
384 import win32file
384 import win32file
385
385
386 def os_link(src, dst): # NB will only succeed on NTFS
386 def os_link(src, dst): # NB will only succeed on NTFS
387 win32file.CreateHardLink(dst, src)
387 win32file.CreateHardLink(dst, src)
388
388
389 def nlinks(pathname):
389 def nlinks(pathname):
390 """Return number of hardlinks for the given file."""
390 """Return number of hardlinks for the given file."""
391 try:
391 try:
392 fh = win32file.CreateFile(pathname,
392 fh = win32file.CreateFile(pathname,
393 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
393 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
394 None, win32file.OPEN_EXISTING, 0, None)
394 None, win32file.OPEN_EXISTING, 0, None)
395 res = win32file.GetFileInformationByHandle(fh)
395 res = win32file.GetFileInformationByHandle(fh)
396 fh.Close()
396 fh.Close()
397 return res[7]
397 return res[7]
398 except:
398 except:
399 return os.stat(pathname).st_nlink
399 return os.stat(pathname).st_nlink
400
400
401 except ImportError:
401 except ImportError:
402 pass
402 pass
403
403
404 def is_exec(f, last):
404 def is_exec(f, last):
405 return last
405 return last
406
406
407 def set_exec(f, mode):
407 def set_exec(f, mode):
408 pass
408 pass
409
409
410 def pconvert(path):
410 def pconvert(path):
411 return path.replace("\\", "/")
411 return path.replace("\\", "/")
412
412
413 def localpath(path):
413 def localpath(path):
414 return path.replace('/', '\\')
414 return path.replace('/', '\\')
415
415
416 def normpath(path):
416 def normpath(path):
417 return pconvert(os.path.normpath(path))
417 return pconvert(os.path.normpath(path))
418
418
419 makelock = _makelock_file
419 makelock = _makelock_file
420 readlock = _readlock_file
420 readlock = _readlock_file
421
421
422 def explain_exit(code):
422 def explain_exit(code):
423 return "exited with status %d" % code, code
423 return "exited with status %d" % code, code
424
424
425 else:
425 else:
426 nulldev = '/dev/null'
426 nulldev = '/dev/null'
427
427
428 rcpath = map(os.path.normpath,
428 rcpath = map(os.path.normpath,
429 ('/etc/mercurial/hgrc', os.path.expanduser('~/.hgrc')))
429 ('/etc/mercurial/hgrc', os.path.expanduser('~/.hgrc')))
430
430
431 def parse_patch_output(output_line):
431 def parse_patch_output(output_line):
432 """parses the output produced by patch and returns the file name"""
432 """parses the output produced by patch and returns the file name"""
433 return output_line[14:]
433 return output_line[14:]
434
434
435 def is_exec(f, last):
435 def is_exec(f, last):
436 """check whether a file is executable"""
436 """check whether a file is executable"""
437 return (os.stat(f).st_mode & 0100 != 0)
437 return (os.stat(f).st_mode & 0100 != 0)
438
438
439 def set_exec(f, mode):
439 def set_exec(f, mode):
440 s = os.stat(f).st_mode
440 s = os.stat(f).st_mode
441 if (s & 0100 != 0) == mode:
441 if (s & 0100 != 0) == mode:
442 return
442 return
443 if mode:
443 if mode:
444 # Turn on +x for every +r bit when making a file executable
444 # Turn on +x for every +r bit when making a file executable
445 # and obey umask.
445 # and obey umask.
446 umask = os.umask(0)
446 umask = os.umask(0)
447 os.umask(umask)
447 os.umask(umask)
448 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
448 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
449 else:
449 else:
450 os.chmod(f, s & 0666)
450 os.chmod(f, s & 0666)
451
451
452 def pconvert(path):
452 def pconvert(path):
453 return path
453 return path
454
454
455 def localpath(path):
455 def localpath(path):
456 return path
456 return path
457
457
458 normpath = os.path.normpath
458 normpath = os.path.normpath
459
459
460 def makelock(info, pathname):
460 def makelock(info, pathname):
461 try:
461 try:
462 os.symlink(info, pathname)
462 os.symlink(info, pathname)
463 except OSError, why:
463 except OSError, why:
464 if why.errno == errno.EEXIST:
464 if why.errno == errno.EEXIST:
465 raise
465 raise
466 else:
466 else:
467 _makelock_file(info, pathname)
467 _makelock_file(info, pathname)
468
468
469 def readlock(pathname):
469 def readlock(pathname):
470 try:
470 try:
471 return os.readlink(pathname)
471 return os.readlink(pathname)
472 except OSError, why:
472 except OSError, why:
473 if why.errno == errno.EINVAL:
473 if why.errno == errno.EINVAL:
474 return _readlock_file(pathname)
474 return _readlock_file(pathname)
475 else:
475 else:
476 raise
476 raise
477
477
478 def explain_exit(code):
478 def explain_exit(code):
479 """return a 2-tuple (desc, code) describing a process's status"""
479 """return a 2-tuple (desc, code) describing a process's status"""
480 if os.WIFEXITED(code):
480 if os.WIFEXITED(code):
481 val = os.WEXITSTATUS(code)
481 val = os.WEXITSTATUS(code)
482 return "exited with status %d" % val, val
482 return "exited with status %d" % val, val
483 elif os.WIFSIGNALED(code):
483 elif os.WIFSIGNALED(code):
484 val = os.WTERMSIG(code)
484 val = os.WTERMSIG(code)
485 return "killed by signal %d" % val, val
485 return "killed by signal %d" % val, val
486 elif os.WIFSTOPPED(code):
486 elif os.WIFSTOPPED(code):
487 val = os.WSTOPSIG(code)
487 val = os.WSTOPSIG(code)
488 return "stopped by signal %d" % val, val
488 return "stopped by signal %d" % val, val
489 raise ValueError("invalid exit code")
489 raise ValueError("invalid exit code")
490
490
491 class chunkbuffer(object):
491 class chunkbuffer(object):
492 """Allow arbitrary sized chunks of data to be efficiently read from an
492 """Allow arbitrary sized chunks of data to be efficiently read from an
493 iterator over chunks of arbitrary size."""
493 iterator over chunks of arbitrary size."""
494
494
495 def __init__(self, in_iter, targetsize = 2**16):
495 def __init__(self, in_iter, targetsize = 2**16):
496 """in_iter is the iterator that's iterating over the input chunks.
496 """in_iter is the iterator that's iterating over the input chunks.
497 targetsize is how big a buffer to try to maintain."""
497 targetsize is how big a buffer to try to maintain."""
498 self.in_iter = iter(in_iter)
498 self.in_iter = iter(in_iter)
499 self.buf = ''
499 self.buf = ''
500 self.targetsize = int(targetsize)
500 self.targetsize = int(targetsize)
501 if self.targetsize <= 0:
501 if self.targetsize <= 0:
502 raise ValueError("targetsize must be greater than 0, was %d" %
502 raise ValueError("targetsize must be greater than 0, was %d" %
503 targetsize)
503 targetsize)
504 self.iterempty = False
504 self.iterempty = False
505
505
506 def fillbuf(self):
506 def fillbuf(self):
507 """Ignore target size; read every chunk from iterator until empty."""
507 """Ignore target size; read every chunk from iterator until empty."""
508 if not self.iterempty:
508 if not self.iterempty:
509 collector = cStringIO.StringIO()
509 collector = cStringIO.StringIO()
510 collector.write(self.buf)
510 collector.write(self.buf)
511 for ch in self.in_iter:
511 for ch in self.in_iter:
512 collector.write(ch)
512 collector.write(ch)
513 self.buf = collector.getvalue()
513 self.buf = collector.getvalue()
514 self.iterempty = True
514 self.iterempty = True
515
515
516 def read(self, l):
516 def read(self, l):
517 """Read L bytes of data from the iterator of chunks of data.
517 """Read L bytes of data from the iterator of chunks of data.
518 Returns less than L bytes if the iterator runs dry."""
518 Returns less than L bytes if the iterator runs dry."""
519 if l > len(self.buf) and not self.iterempty:
519 if l > len(self.buf) and not self.iterempty:
520 # Clamp to a multiple of self.targetsize
520 # Clamp to a multiple of self.targetsize
521 targetsize = self.targetsize * ((l // self.targetsize) + 1)
521 targetsize = self.targetsize * ((l // self.targetsize) + 1)
522 collector = cStringIO.StringIO()
522 collector = cStringIO.StringIO()
523 collector.write(self.buf)
523 collector.write(self.buf)
524 collected = len(self.buf)
524 collected = len(self.buf)
525 for chunk in self.in_iter:
525 for chunk in self.in_iter:
526 collector.write(chunk)
526 collector.write(chunk)
527 collected += len(chunk)
527 collected += len(chunk)
528 if collected >= targetsize:
528 if collected >= targetsize:
529 break
529 break
530 if collected < targetsize:
530 if collected < targetsize:
531 self.iterempty = True
531 self.iterempty = True
532 self.buf = collector.getvalue()
532 self.buf = collector.getvalue()
533 s, self.buf = self.buf[:l], buffer(self.buf, l)
533 s, self.buf = self.buf[:l], buffer(self.buf, l)
534 return s
534 return s
535
535
536 def filechunkiter(f, size = 65536):
536 def filechunkiter(f, size = 65536):
537 """Create a generator that produces all the data in the file size
537 """Create a generator that produces all the data in the file size
538 (default 65536) bytes at a time. Chunks may be less than size
538 (default 65536) bytes at a time. Chunks may be less than size
539 bytes if the chunk is the last chunk in the file, or the file is a
539 bytes if the chunk is the last chunk in the file, or the file is a
540 socket or some other type of file that sometimes reads less data
540 socket or some other type of file that sometimes reads less data
541 than is requested."""
541 than is requested."""
542 s = f.read(size)
542 s = f.read(size)
543 while len(s) >= 0:
543 while len(s) >= 0:
544 yield s
544 yield s
545 s = f.read(size)
545 s = f.read(size)
546
547 def datestr(change=None, format='%c'):
548 """represent a change date as a localized time.
549 a change date is a 'unixtime offset' string, where unixtime is
550 seconds since the epoch, and offset is seconds away from UTC."""
551 if change is None:
552 t = time.time()
553 if time.daylight: tz = time.altzone
554 else: tz = time.timezone
555 else:
556 t, tz = change[2].split(' ')
557 try:
558 # a conversion tool was sticking non-integer offsets into repos
559 tz = int(tz)
560 except ValueError:
561 tz = 0
562 return ("%s %+03d%02d" %
563 (time.strftime(format, time.gmtime(float(t) - tz)),
564 -tz / 3600,
565 ((-tz % 3600) / 60)))
General Comments 0
You need to be logged in to leave comments. Login now