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