##// END OF EJS Templates
Merge with stable...
Matt Mackall -
r6876:077f1e63 merge default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -0,0 +1,25 b''
1 #!/usr/bin/env python
2 # Dump revlogs as raw data stream
3 # $ find .hg/store/ -name "*.i" | xargs dumprevlog > repo.dump
4
5 import sys
6 from mercurial import revlog, node, util
7
8 for fp in (sys.stdin, sys.stdout, sys.stderr):
9 util.set_binary(fp)
10
11 for f in sys.argv[1:]:
12 binopen = lambda fn: open(fn, 'rb')
13 r = revlog.revlog(binopen, f)
14 print "file:", f
15 for i in r:
16 n = r.node(i)
17 p = r.parents(n)
18 d = r.revision(n)
19 print "node:", node.hex(n)
20 print "linkrev:", r.linkrev(n)
21 print "parents:", node.hex(p[0]), node.hex(p[1])
22 print "length:", len(d)
23 print "-start-"
24 print d
25 print "-end-"
@@ -0,0 +1,37 b''
1 #!/usr/bin/env python
2 # Undump a dump from dumprevlog
3 # $ hg init
4 # $ undumprevlog < repo.dump
5
6 import sys
7 from mercurial import revlog, node, util, transaction
8
9 for fp in (sys.stdin, sys.stdout, sys.stderr):
10 util.set_binary(fp)
11
12 opener = util.opener('.', False)
13 tr = transaction.transaction(sys.stderr.write, opener, "undump.journal")
14 while 1:
15 l = sys.stdin.readline()
16 if not l:
17 break
18 if l.startswith("file:"):
19 f = l[6:-1]
20 r = revlog.revlog(opener, f)
21 print f
22 elif l.startswith("node:"):
23 n = node.bin(l[6:-1])
24 elif l.startswith("linkrev:"):
25 lr = int(l[9:-1])
26 elif l.startswith("parents:"):
27 p = l[9:-1].split()
28 p1 = node.bin(p[0])
29 p2 = node.bin(p[1])
30 elif l.startswith("length:"):
31 length = int(l[8:-1])
32 sys.stdin.readline() # start marker
33 d = sys.stdin.read(length)
34 sys.stdin.readline() # end marker
35 r.addrevision(d, tr, lr, p1, p2)
36
37 tr.close()
@@ -0,0 +1,12 b''
1 @echo off
2 rem Windows Driver script for Mercurial
3
4 setlocal
5 set HG=%~f0
6
7 rem Use a full path to Python (relative to this script) as the standard Python
8 rem install does not put python.exe on the PATH...
9 rem %~dp0 is the directory of this script
10
11 %~dp0..\python "%~dp0hg" %*
12 endlocal
@@ -0,0 +1,154 b''
1 #!/usr/bin/env python
2 #
3 # Commandline front-end for cvsps.py
4 #
5 # Copyright 2008, Frank Kingswood <frank@kingswood-consulting.co.uk>
6 #
7 # This software may be used and distributed according to the terms
8 # of the GNU General Public License, incorporated herein by reference.
9
10 import sys
11 from mercurial import util
12 from mercurial.i18n import _
13 from optparse import OptionParser, SUPPRESS_HELP
14 from hgext.convert.cvsps import createlog, createchangeset, logerror
15
16 def main():
17 '''Main program to mimic cvsps.'''
18
19 op = OptionParser(usage='%prog [-bpruvxz] path',
20 description='Read CVS rlog for current directory or named '
21 'path in repository, and convert the log to changesets '
22 'based on matching commit log entries and dates.')
23
24 # Options that are ignored for compatibility with cvsps-2.1
25 op.add_option('-A', dest='Ignore', action='store_true', help=SUPPRESS_HELP)
26 op.add_option('--cvs-direct', dest='Ignore', action='store_true', help=SUPPRESS_HELP)
27 op.add_option('-q', dest='Ignore', action='store_true', help=SUPPRESS_HELP)
28
29 # Main options shared with cvsps-2.1
30 op.add_option('-b', dest='Branches', action='append', default=[],
31 help='Only return changes on specified branches')
32 op.add_option('-p', dest='Prefix', action='store', default='',
33 help='Prefix to remove from file names')
34 op.add_option('-r', dest='Revisions', action='append', default=[],
35 help='Only return changes after or between specified tags')
36 op.add_option('-u', dest='Cache', action='store_const', const='update',
37 help="Update cvs log cache")
38 op.add_option('-v', dest='Verbose', action='count', default=0,
39 help='Be verbose')
40 op.add_option('-x', dest='Cache', action='store_const', const='write',
41 help="Create new cvs log cache")
42 op.add_option('-z', dest='Fuzz', action='store', type='int', default=60,
43 help='Set commit time fuzz', metavar='seconds')
44 op.add_option('--root', dest='Root', action='store', default='',
45 help='Specify cvsroot', metavar='cvsroot')
46
47 # Options specific to this version
48 op.add_option('--parents', dest='Parents', action='store_true',
49 help='Show parent changesets')
50 op.add_option('--ancestors', dest='Ancestors', action='store_true',
51 help='Show current changeset in ancestor branches')
52
53 options, args = op.parse_args()
54
55 # Create a ui object for printing progress messages
56 class UI:
57 def __init__(self, verbose):
58 if verbose:
59 self.status = self.message
60 if verbose>1:
61 self.note = self.message
62 if verbose>2:
63 self.debug = self.message
64 def message(self, msg):
65 sys.stderr.write(msg)
66 def nomessage(self, msg):
67 pass
68 status = nomessage
69 note = nomessage
70 debug = nomessage
71 ui = UI(options.Verbose)
72
73 try:
74 if args:
75 log = []
76 for d in args:
77 log += createlog(ui, d, root=options.Root, cache=options.Cache)
78 else:
79 log = createlog(ui, root=options.Root, cache=options.Cache)
80 except logerror, e:
81 print e
82 return
83
84 changesets = createchangeset(ui, log, options.Fuzz)
85 del log
86
87 # Print changesets (optionally filtered)
88
89 off = len(options.Revisions)
90 branches = {} # latest version number in each branch
91 ancestors = {} # parent branch
92 for cs in changesets:
93
94 if options.Ancestors:
95 if cs.branch not in branches and cs.parents and cs.parents[0].id:
96 ancestors[cs.branch] = changesets[cs.parents[0].id-1].branch, cs.parents[0].id
97 branches[cs.branch] = cs.id
98
99 # limit by branches
100 if options.Branches and (cs.branch or 'HEAD') not in options.Branches:
101 continue
102
103 if not off:
104 # Note: trailing spaces on several lines here are needed to have
105 # bug-for-bug compatibility with cvsps.
106 print '---------------------'
107 print 'PatchSet %d ' % cs.id
108 print 'Date: %s' % util.datestr(cs.date, '%Y/%m/%d %H:%M:%S %1%2')
109 print 'Author: %s' % cs.author
110 print 'Branch: %s' % (cs.branch or 'HEAD')
111 print 'Tag%s: %s ' % (['', 's'][len(cs.tags)>1],
112 ','.join(cs.tags) or '(none)')
113 if options.Parents and cs.parents:
114 if len(cs.parents)>1:
115 print 'Parents: %s' % (','.join([str(p.id) for p in cs.parents]))
116 else:
117 print 'Parent: %d' % cs.parents[0].id
118
119 if options.Ancestors:
120 b = cs.branch
121 r = []
122 while b:
123 b, c = ancestors[b]
124 r.append('%s:%d:%d' % (b or "HEAD", c, branches[b]))
125 if r:
126 print 'Ancestors: %s' % (','.join(r))
127
128 print 'Log:'
129 print cs.comment
130 print
131 print 'Members: '
132 for f in cs.entries:
133 fn = f.file
134 if fn.startswith(options.Prefix):
135 fn = fn[len(options.Prefix):]
136 print '\t%s:%s->%s%s ' % (fn, '.'.join([str(x) for x in f.parent]) or 'INITIAL',
137 '.'.join([str(x) for x in f.revision]), ['', '(DEAD)'][f.dead])
138 print
139
140 # have we seen the start tag?
141 if options.Revisions and off:
142 if options.Revisions[0] == str(cs.id) or \
143 options.Revisions[0] in cs.tags:
144 off = False
145
146 # see if we reached the end tag
147 if len(options.Revisions)>1 and not off:
148 if options.Revisions[1] == str(cs.id) or \
149 options.Revisions[1] in cs.tags:
150 break
151
152
153 if __name__ == '__main__':
154 main()
This diff has been collapsed as it changes many lines, (548 lines changed) Show them Hide them
@@ -0,0 +1,548 b''
1 #
2 # Mercurial built-in replacement for cvsps.
3 #
4 # Copyright 2008, Frank Kingswood <frank@kingswood-consulting.co.uk>
5 #
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
8
9 import os
10 import re
11 import sys
12 import cPickle as pickle
13 from mercurial import util
14 from mercurial.i18n import _
15
16 def listsort(list, key):
17 "helper to sort by key in Python 2.3"
18 try:
19 list.sort(key=key)
20 except TypeError:
21 list.sort(lambda l, r: cmp(key(l), key(r)))
22
23 class logentry(object):
24 '''Class logentry has the following attributes:
25 .author - author name as CVS knows it
26 .branch - name of branch this revision is on
27 .branches - revision tuple of branches starting at this revision
28 .comment - commit message
29 .date - the commit date as a (time, tz) tuple
30 .dead - true if file revision is dead
31 .file - Name of file
32 .lines - a tuple (+lines, -lines) or None
33 .parent - Previous revision of this entry
34 .rcs - name of file as returned from CVS
35 .revision - revision number as tuple
36 .tags - list of tags on the file
37 '''
38 def __init__(self, **entries):
39 self.__dict__.update(entries)
40
41 class logerror(Exception):
42 pass
43
44 def createlog(ui, directory=None, root="", rlog=True, cache=None):
45 '''Collect the CVS rlog'''
46
47 # Because we store many duplicate commit log messages, reusing strings
48 # saves a lot of memory and pickle storage space.
49 _scache = {}
50 def scache(s):
51 "return a shared version of a string"
52 return _scache.setdefault(s, s)
53
54 ui.status(_('collecting CVS rlog\n'))
55
56 log = [] # list of logentry objects containing the CVS state
57
58 # patterns to match in CVS (r)log output, by state of use
59 re_00 = re.compile('RCS file: (.+)$')
60 re_01 = re.compile('cvs \\[r?log aborted\\]: (.+)$')
61 re_02 = re.compile('cvs (r?log|server): (.+)\n$')
62 re_03 = re.compile("(Cannot access.+CVSROOT)|(can't create temporary directory.+)$")
63 re_10 = re.compile('Working file: (.+)$')
64 re_20 = re.compile('symbolic names:')
65 re_30 = re.compile('\t(.+): ([\\d.]+)$')
66 re_31 = re.compile('----------------------------$')
67 re_32 = re.compile('=============================================================================$')
68 re_50 = re.compile('revision ([\\d.]+)(\s+locked by:\s+.+;)?$')
69 re_60 = re.compile(r'date:\s+(.+);\s+author:\s+(.+);\s+state:\s+(.+?);(\s+lines:\s+(\+\d+)?\s+(-\d+)?;)?')
70 re_70 = re.compile('branches: (.+);$')
71
72 prefix = '' # leading path to strip of what we get from CVS
73
74 if directory is None:
75 # Current working directory
76
77 # Get the real directory in the repository
78 try:
79 prefix = file(os.path.join('CVS','Repository')).read().strip()
80 if prefix == ".":
81 prefix = ""
82 directory = prefix
83 except IOError:
84 raise logerror('Not a CVS sandbox')
85
86 if prefix and not prefix.endswith('/'):
87 prefix += '/'
88
89 # Use the Root file in the sandbox, if it exists
90 try:
91 root = file(os.path.join('CVS','Root')).read().strip()
92 except IOError:
93 pass
94
95 if not root:
96 root = os.environ.get('CVSROOT', '')
97
98 # read log cache if one exists
99 oldlog = []
100 date = None
101
102 if cache:
103 cachedir = os.path.expanduser('~/.hg.cvsps')
104 if not os.path.exists(cachedir):
105 os.mkdir(cachedir)
106
107 # The cvsps cache pickle needs a uniquified name, based on the
108 # repository location. The address may have all sort of nasties
109 # in it, slashes, colons and such. So here we take just the
110 # alphanumerics, concatenated in a way that does not mix up the
111 # various components, so that
112 # :pserver:user@server:/path
113 # and
114 # /pserver/user/server/path
115 # are mapped to different cache file names.
116 cachefile = root.split(":") + [directory, "cache"]
117 cachefile = ['-'.join(re.findall(r'\w+', s)) for s in cachefile if s]
118 cachefile = os.path.join(cachedir,
119 '.'.join([s for s in cachefile if s]))
120
121 if cache == 'update':
122 try:
123 ui.note(_('reading cvs log cache %s\n') % cachefile)
124 oldlog = pickle.load(file(cachefile))
125 ui.note(_('cache has %d log entries\n') % len(oldlog))
126 except Exception, e:
127 ui.note(_('error reading cache: %r\n') % e)
128
129 if oldlog:
130 date = oldlog[-1].date # last commit date as a (time,tz) tuple
131 date = util.datestr(date, '%Y/%m/%d %H:%M:%S %1%2')
132
133 # build the CVS commandline
134 cmd = ['cvs', '-q']
135 if root:
136 cmd.append('-d%s' % root)
137 p = root.split(':')[-1]
138 if not p.endswith('/'):
139 p += '/'
140 prefix = p + prefix
141 cmd.append(['log', 'rlog'][rlog])
142 if date:
143 # no space between option and date string
144 cmd.append('-d>%s' % date)
145 cmd.append(directory)
146
147 # state machine begins here
148 tags = {} # dictionary of revisions on current file with their tags
149 state = 0
150 store = False # set when a new record can be appended
151
152 cmd = [util.shellquote(arg) for arg in cmd]
153 ui.note("running %s\n" % (' '.join(cmd)))
154 ui.debug("prefix=%r directory=%r root=%r\n" % (prefix, directory, root))
155
156 for line in util.popen(' '.join(cmd)):
157 if line.endswith('\n'):
158 line = line[:-1]
159 #ui.debug('state=%d line=%r\n' % (state, line))
160
161 if state == 0:
162 # initial state, consume input until we see 'RCS file'
163 match = re_00.match(line)
164 if match:
165 rcs = match.group(1)
166 tags = {}
167 if rlog:
168 filename = rcs[:-2]
169 if filename.startswith(prefix):
170 filename = filename[len(prefix):]
171 if filename.startswith('/'):
172 filename = filename[1:]
173 if filename.startswith('Attic/'):
174 filename = filename[6:]
175 else:
176 filename = filename.replace('/Attic/', '/')
177 state = 2
178 continue
179 state = 1
180 continue
181 match = re_01.match(line)
182 if match:
183 raise Exception(match.group(1))
184 match = re_02.match(line)
185 if match:
186 raise Exception(match.group(2))
187 if re_03.match(line):
188 raise Exception(line)
189
190 elif state == 1:
191 # expect 'Working file' (only when using log instead of rlog)
192 match = re_10.match(line)
193 assert match, _('RCS file must be followed by working file')
194 filename = match.group(1)
195 state = 2
196
197 elif state == 2:
198 # expect 'symbolic names'
199 if re_20.match(line):
200 state = 3
201
202 elif state == 3:
203 # read the symbolic names and store as tags
204 match = re_30.match(line)
205 if match:
206 rev = [int(x) for x in match.group(2).split('.')]
207
208 # Convert magic branch number to an odd-numbered one
209 revn = len(rev)
210 if revn > 3 and (revn % 2) == 0 and rev[-2] == 0:
211 rev = rev[:-2] + rev[-1:]
212 rev = tuple(rev)
213
214 if rev not in tags:
215 tags[rev] = []
216 tags[rev].append(match.group(1))
217
218 elif re_31.match(line):
219 state = 5
220 elif re_32.match(line):
221 state = 0
222
223 elif state == 4:
224 # expecting '------' separator before first revision
225 if re_31.match(line):
226 state = 5
227 else:
228 assert not re_32.match(line), _('Must have at least some revisions')
229
230 elif state == 5:
231 # expecting revision number and possibly (ignored) lock indication
232 # we create the logentry here from values stored in states 0 to 4,
233 # as this state is re-entered for subsequent revisions of a file.
234 match = re_50.match(line)
235 assert match, _('expected revision number')
236 e = logentry(rcs=scache(rcs), file=scache(filename),
237 revision=tuple([int(x) for x in match.group(1).split('.')]),
238 branches=[], parent=None)
239 state = 6
240
241 elif state == 6:
242 # expecting date, author, state, lines changed
243 match = re_60.match(line)
244 assert match, _('revision must be followed by date line')
245 d = match.group(1)
246 if d[2] == '/':
247 # Y2K
248 d = '19' + d
249
250 if len(d.split()) != 3:
251 # cvs log dates always in GMT
252 d = d + ' UTC'
253 e.date = util.parsedate(d, ['%y/%m/%d %H:%M:%S', '%Y/%m/%d %H:%M:%S', '%Y-%m-%d %H:%M:%S'])
254 e.author = scache(match.group(2))
255 e.dead = match.group(3).lower() == 'dead'
256
257 if match.group(5):
258 if match.group(6):
259 e.lines = (int(match.group(5)), int(match.group(6)))
260 else:
261 e.lines = (int(match.group(5)), 0)
262 elif match.group(6):
263 e.lines = (0, int(match.group(6)))
264 else:
265 e.lines = None
266 e.comment = []
267 state = 7
268
269 elif state == 7:
270 # read the revision numbers of branches that start at this revision
271 # or store the commit log message otherwise
272 m = re_70.match(line)
273 if m:
274 e.branches = [tuple([int(y) for y in x.strip().split('.')])
275 for x in m.group(1).split(';')]
276 state = 8
277 elif re_31.match(line):
278 state = 5
279 store = True
280 elif re_32.match(line):
281 state = 0
282 store = True
283 else:
284 e.comment.append(line)
285
286 elif state == 8:
287 # store commit log message
288 if re_31.match(line):
289 state = 5
290 store = True
291 elif re_32.match(line):
292 state = 0
293 store = True
294 else:
295 e.comment.append(line)
296
297 if store:
298 # clean up the results and save in the log.
299 store = False
300 e.tags = util.sort([scache(x) for x in tags.get(e.revision, [])])
301 e.comment = scache('\n'.join(e.comment))
302
303 revn = len(e.revision)
304 if revn > 3 and (revn % 2) == 0:
305 e.branch = tags.get(e.revision[:-1], [None])[0]
306 else:
307 e.branch = None
308
309 log.append(e)
310
311 if len(log) % 100 == 0:
312 ui.status(util.ellipsis('%d %s' % (len(log), e.file), 80)+'\n')
313
314 listsort(log, key=lambda x:(x.rcs, x.revision))
315
316 # find parent revisions of individual files
317 versions = {}
318 for e in log:
319 branch = e.revision[:-1]
320 p = versions.get((e.rcs, branch), None)
321 if p is None:
322 p = e.revision[:-2]
323 e.parent = p
324 versions[(e.rcs, branch)] = e.revision
325
326 # update the log cache
327 if cache:
328 if log:
329 # join up the old and new logs
330 listsort(log, key=lambda x:x.date)
331
332 if oldlog and oldlog[-1].date >= log[0].date:
333 raise logerror('Log cache overlaps with new log entries,'
334 ' re-run without cache.')
335
336 log = oldlog + log
337
338 # write the new cachefile
339 ui.note(_('writing cvs log cache %s\n') % cachefile)
340 pickle.dump(log, file(cachefile, 'w'))
341 else:
342 log = oldlog
343
344 ui.status(_('%d log entries\n') % len(log))
345
346 return log
347
348
349 class changeset(object):
350 '''Class changeset has the following attributes:
351 .author - author name as CVS knows it
352 .branch - name of branch this changeset is on, or None
353 .comment - commit message
354 .date - the commit date as a (time,tz) tuple
355 .entries - list of logentry objects in this changeset
356 .parents - list of one or two parent changesets
357 .tags - list of tags on this changeset
358 '''
359 def __init__(self, **entries):
360 self.__dict__.update(entries)
361
362 def createchangeset(ui, log, fuzz=60, mergefrom=None, mergeto=None):
363 '''Convert log into changesets.'''
364
365 ui.status(_('creating changesets\n'))
366
367 # Merge changesets
368
369 listsort(log, key=lambda x:(x.comment, x.author, x.branch, x.date))
370
371 changesets = []
372 files = {}
373 c = None
374 for i, e in enumerate(log):
375
376 # Check if log entry belongs to the current changeset or not.
377 if not (c and
378 e.comment == c.comment and
379 e.author == c.author and
380 e.branch == c.branch and
381 ((c.date[0] + c.date[1]) <=
382 (e.date[0] + e.date[1]) <=
383 (c.date[0] + c.date[1]) + fuzz) and
384 e.file not in files):
385 c = changeset(comment=e.comment, author=e.author,
386 branch=e.branch, date=e.date, entries=[])
387 changesets.append(c)
388 files = {}
389 if len(changesets) % 100 == 0:
390 t = '%d %s' % (len(changesets), repr(e.comment)[1:-1])
391 ui.status(util.ellipsis(t, 80) + '\n')
392
393 c.entries.append(e)
394 files[e.file] = True
395 c.date = e.date # changeset date is date of latest commit in it
396
397 # Sort files in each changeset
398
399 for c in changesets:
400 def pathcompare(l, r):
401 'Mimic cvsps sorting order'
402 l = l.split('/')
403 r = r.split('/')
404 nl = len(l)
405 nr = len(r)
406 n = min(nl, nr)
407 for i in range(n):
408 if i + 1 == nl and nl < nr:
409 return -1
410 elif i + 1 == nr and nl > nr:
411 return +1
412 elif l[i] < r[i]:
413 return -1
414 elif l[i] > r[i]:
415 return +1
416 return 0
417 def entitycompare(l, r):
418 return pathcompare(l.file, r.file)
419
420 c.entries.sort(entitycompare)
421
422 # Sort changesets by date
423
424 def cscmp(l, r):
425 d = sum(l.date) - sum(r.date)
426 if d:
427 return d
428
429 # detect vendor branches and initial commits on a branch
430 le = {}
431 for e in l.entries:
432 le[e.rcs] = e.revision
433 re = {}
434 for e in r.entries:
435 re[e.rcs] = e.revision
436
437 d = 0
438 for e in l.entries:
439 if re.get(e.rcs, None) == e.parent:
440 assert not d
441 d = 1
442 break
443
444 for e in r.entries:
445 if le.get(e.rcs, None) == e.parent:
446 assert not d
447 d = -1
448 break
449
450 return d
451
452 changesets.sort(cscmp)
453
454 # Collect tags
455
456 globaltags = {}
457 for c in changesets:
458 tags = {}
459 for e in c.entries:
460 for tag in e.tags:
461 # remember which is the latest changeset to have this tag
462 globaltags[tag] = c
463
464 for c in changesets:
465 tags = {}
466 for e in c.entries:
467 for tag in e.tags:
468 tags[tag] = True
469 # remember tags only if this is the latest changeset to have it
470 c.tags = util.sort([tag for tag in tags if globaltags[tag] is c])
471
472 # Find parent changesets, handle {{mergetobranch BRANCHNAME}}
473 # by inserting dummy changesets with two parents, and handle
474 # {{mergefrombranch BRANCHNAME}} by setting two parents.
475
476 if mergeto is None:
477 mergeto = r'{{mergetobranch ([-\w]+)}}'
478 if mergeto:
479 mergeto = re.compile(mergeto)
480
481 if mergefrom is None:
482 mergefrom = r'{{mergefrombranch ([-\w]+)}}'
483 if mergefrom:
484 mergefrom = re.compile(mergefrom)
485
486 versions = {} # changeset index where we saw any particular file version
487 branches = {} # changeset index where we saw a branch
488 n = len(changesets)
489 i = 0
490 while i<n:
491 c = changesets[i]
492
493 for f in c.entries:
494 versions[(f.rcs, f.revision)] = i
495
496 p = None
497 if c.branch in branches:
498 p = branches[c.branch]
499 else:
500 for f in c.entries:
501 p = max(p, versions.get((f.rcs, f.parent), None))
502
503 c.parents = []
504 if p is not None:
505 c.parents.append(changesets[p])
506
507 if mergefrom:
508 m = mergefrom.search(c.comment)
509 if m:
510 m = m.group(1)
511 if m == 'HEAD':
512 m = None
513 if m in branches and c.branch != m:
514 c.parents.append(changesets[branches[m]])
515
516 if mergeto:
517 m = mergeto.search(c.comment)
518 if m:
519 try:
520 m = m.group(1)
521 if m == 'HEAD':
522 m = None
523 except:
524 m = None # if no group found then merge to HEAD
525 if m in branches and c.branch != m:
526 # insert empty changeset for merge
527 cc = changeset(author=c.author, branch=m, date=c.date,
528 comment='convert-repo: CVS merge from branch %s' % c.branch,
529 entries=[], tags=[], parents=[changesets[branches[m]], c])
530 changesets.insert(i + 1, cc)
531 branches[m] = i + 1
532
533 # adjust our loop counters now we have inserted a new entry
534 n += 1
535 i += 2
536 continue
537
538 branches[c.branch] = i
539 i += 1
540
541 # Number changesets
542
543 for i, c in enumerate(changesets):
544 c.id = i + 1
545
546 ui.status(_('%d changeset entries\n') % len(changesets))
547
548 return changesets
@@ -0,0 +1,74 b''
1 # Revision graph generator for Mercurial
2 #
3 # Copyright 2008 Dirkjan Ochtman <dirkjan@ochtman.nl>
4 # Copyright 2007 Joel Rosdahl <joel@rosdahl.net>
5 #
6 # This software may be used and distributed according to the terms of
7 # the GNU General Public License, incorporated herein by reference.
8
9 from node import nullrev, short
10 import ui, hg, util, templatefilters
11
12 def graph(repo, start_rev, stop_rev):
13 """incremental revision grapher
14
15 This generator function walks through the revision history from
16 revision start_rev to revision stop_rev (which must be less than
17 or equal to start_rev) and for each revision emits tuples with the
18 following elements:
19
20 - Current node
21 - Column and color for the current node
22 - Edges; a list of (col, next_col, color) indicating the edges between
23 the current node and its parents.
24 - First line of the changeset description
25 - The changeset author
26 - The changeset date/time
27 """
28
29 assert start_rev >= stop_rev
30 curr_rev = start_rev
31 revs = []
32 cl = repo.changelog
33 colors = {}
34 new_color = 1
35
36 while curr_rev >= stop_rev:
37 node = cl.node(curr_rev)
38
39 # Compute revs and next_revs
40 if curr_rev not in revs:
41 revs.append(curr_rev) # new head
42 colors[curr_rev] = new_color
43 new_color += 1
44
45 idx = revs.index(curr_rev)
46 color = colors.pop(curr_rev)
47 next = revs[:]
48
49 # Add parents to next_revs
50 parents = [x for x in cl.parentrevs(curr_rev) if x != nullrev]
51 addparents = [p for p in parents if p not in next]
52 next[idx:idx + 1] = addparents
53
54 # Set colors for the parents
55 for i, p in enumerate(addparents):
56 if not i:
57 colors[p] = color
58 else:
59 colors[p] = new_color
60 new_color += 1
61
62 # Add edges to the graph
63 edges = []
64 for col, r in enumerate(revs):
65 if r in next:
66 edges.append((col, next.index(r), colors[r]))
67 elif r == curr_rev:
68 for p in parents:
69 edges.append((col, next.index(p), colors[p]))
70
71 # Yield and move on
72 yield (repo[curr_rev], (idx, color), edges)
73 revs = next
74 curr_rev -= 1
@@ -0,0 +1,140 b''
1 # hgweb/webutil.py - utility library for the web interface.
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 #
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
8
9 import os
10 from mercurial.node import hex, nullid
11 from mercurial.repo import RepoError
12 from mercurial import util
13
14 def up(p):
15 if p[0] != "/":
16 p = "/" + p
17 if p[-1] == "/":
18 p = p[:-1]
19 up = os.path.dirname(p)
20 if up == "/":
21 return "/"
22 return up + "/"
23
24 def revnavgen(pos, pagelen, limit, nodefunc):
25 def seq(factor, limit=None):
26 if limit:
27 yield limit
28 if limit >= 20 and limit <= 40:
29 yield 50
30 else:
31 yield 1 * factor
32 yield 3 * factor
33 for f in seq(factor * 10):
34 yield f
35
36 def nav(**map):
37 l = []
38 last = 0
39 for f in seq(1, pagelen):
40 if f < pagelen or f <= last:
41 continue
42 if f > limit:
43 break
44 last = f
45 if pos + f < limit:
46 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
47 if pos - f >= 0:
48 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
49
50 try:
51 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
52
53 for label, node in l:
54 yield {"label": label, "node": node}
55
56 yield {"label": "tip", "node": "tip"}
57 except RepoError:
58 pass
59
60 return nav
61
62 def siblings(siblings=[], hiderev=None, **args):
63 siblings = [s for s in siblings if s.node() != nullid]
64 if len(siblings) == 1 and siblings[0].rev() == hiderev:
65 return
66 for s in siblings:
67 d = {'node': hex(s.node()), 'rev': s.rev()}
68 if hasattr(s, 'path'):
69 d['file'] = s.path()
70 d.update(args)
71 yield d
72
73 def renamelink(fctx):
74 r = fctx.renamed()
75 if r:
76 return [dict(file=r[0], node=hex(r[1]))]
77 return []
78
79 def nodetagsdict(repo, node):
80 return [{"name": i} for i in repo.nodetags(node)]
81
82 def nodebranchdict(repo, ctx):
83 branches = []
84 branch = ctx.branch()
85 # If this is an empty repo, ctx.node() == nullid,
86 # ctx.branch() == 'default', but branchtags() is
87 # an empty dict. Using dict.get avoids a traceback.
88 if repo.branchtags().get(branch) == ctx.node():
89 branches.append({"name": branch})
90 return branches
91
92 def nodeinbranch(repo, ctx):
93 branches = []
94 branch = ctx.branch()
95 if branch != 'default' and repo.branchtags().get(branch) != ctx.node():
96 branches.append({"name": branch})
97 return branches
98
99 def nodebranchnodefault(ctx):
100 branches = []
101 branch = ctx.branch()
102 if branch != 'default':
103 branches.append({"name": branch})
104 return branches
105
106 def showtag(repo, tmpl, t1, node=nullid, **args):
107 for t in repo.nodetags(node):
108 yield tmpl(t1, tag=t, **args)
109
110 def cleanpath(repo, path):
111 path = path.lstrip('/')
112 return util.canonpath(repo.root, '', path)
113
114 def changectx(repo, req):
115 changeid = "tip"
116 if 'node' in req.form:
117 changeid = req.form['node'][0]
118 elif 'manifest' in req.form:
119 changeid = req.form['manifest'][0]
120
121 try:
122 ctx = repo[changeid]
123 except RepoError:
124 man = repo.manifest
125 ctx = repo[man.linkrev(man.lookup(changeid))]
126
127 return ctx
128
129 def filectx(repo, req):
130 path = cleanpath(repo, req.form['file'][0])
131 if 'node' in req.form:
132 changeid = req.form['node'][0]
133 else:
134 changeid = req.form['filenode'][0]
135 try:
136 fctx = repo[changeid][path]
137 except RepoError:
138 fctx = repo.filectx(path, fileid=changeid)
139
140 return fctx
@@ -0,0 +1,47 b''
1 import util
2
3 class _match(object):
4 def __init__(self, root, cwd, files, mf, ap):
5 self._root = root
6 self._cwd = cwd
7 self._files = files
8 self._fmap = dict.fromkeys(files)
9 self.matchfn = mf
10 self._anypats = ap
11 def __call__(self, fn):
12 return self.matchfn(fn)
13 def __iter__(self):
14 for f in self._files:
15 yield f
16 def bad(self, f, msg):
17 return True
18 def dir(self, f):
19 pass
20 def missing(self, f):
21 pass
22 def exact(self, f):
23 return f in self._fmap
24 def rel(self, f):
25 return util.pathto(self._root, self._cwd, f)
26 def files(self):
27 return self._files
28 def anypats(self):
29 return self._anypats
30
31 class always(_match):
32 def __init__(self, root, cwd):
33 _match.__init__(self, root, cwd, [], lambda f: True, False)
34
35 class never(_match):
36 def __init__(self, root, cwd):
37 _match.__init__(self, root, cwd, [], lambda f: False, False)
38
39 class exact(_match):
40 def __init__(self, root, cwd, files):
41 _match.__init__(self, root, cwd, files, lambda f: f in files, False)
42
43 class match(_match):
44 def __init__(self, root, cwd, patterns, include, exclude, default):
45 f, mf, ap = util.matcher(root, cwd, patterns, include, exclude,
46 None, default)
47 _match.__init__(self, root, cwd, f, mf, ap)
@@ -0,0 +1,169 b''
1 /*
2 parsers.c - efficient content parsing
3
4 Copyright 2008 Matt Mackall <mpm@selenic.com> and others
5
6 This software may be used and distributed according to the terms of
7 the GNU General Public License, incorporated herein by reference.
8 */
9
10 #include <Python.h>
11 #include <ctype.h>
12 #include <string.h>
13
14 static int hexdigit(char c)
15 {
16 if (c >= '0' && c <= '9')
17 return c - '0';
18
19 if (c >= 'A' && c <= 'F')
20 return c - 'A' + 10;
21
22 if (c >= 'a' && c <= 'f')
23 return c - 'a' + 10;
24
25 return -1;
26 }
27
28 /*
29 * Turn a hex-encoded string into binary.
30 */
31 static PyObject *unhexlify(const char *str, int len)
32 {
33 PyObject *ret = NULL;
34 const char *c;
35 char *d;
36
37 if (len % 2) {
38 PyErr_SetString(PyExc_ValueError,
39 "input is not even in length");
40 goto bail;
41 }
42
43 ret = PyString_FromStringAndSize(NULL, len / 2);
44 if (!ret)
45 goto bail;
46
47 d = PyString_AsString(ret);
48 if (!d)
49 goto bail;
50
51 for (c = str; c < str + len;) {
52 int hi = hexdigit(*c++);
53 int lo = hexdigit(*c++);
54
55 if (hi == -1 || lo == -1) {
56 PyErr_SetString(PyExc_ValueError,
57 "input contains non-hex character");
58 goto bail;
59 }
60
61 *d++ = (hi << 4) | lo;
62 }
63
64 goto done;
65
66 bail:
67 Py_XDECREF(ret);
68 ret = NULL;
69 done:
70 return ret;
71 }
72
73 /*
74 * This code assumes that a manifest is stitched together with newline
75 * ('\n') characters.
76 */
77 static PyObject *parse_manifest(PyObject *self, PyObject *args)
78 {
79 PyObject *mfdict, *fdict;
80 char *str, *cur, *start, *zero;
81 int len;
82
83 if (!PyArg_ParseTuple(args, "O!O!s#:parse_manifest",
84 &PyDict_Type, &mfdict,
85 &PyDict_Type, &fdict,
86 &str, &len))
87 goto quit;
88
89 for (start = cur = str, zero = NULL; cur < str + len; cur++) {
90 PyObject *file = NULL, *node = NULL;
91 PyObject *flags = NULL;
92 int nlen;
93
94 if (!*cur) {
95 zero = cur;
96 continue;
97 }
98 else if (*cur != '\n')
99 continue;
100
101 if (!zero) {
102 PyErr_SetString(PyExc_ValueError,
103 "manifest entry has no separator");
104 goto quit;
105 }
106
107 file = PyString_FromStringAndSize(start, zero - start);
108 if (!file)
109 goto bail;
110
111 nlen = cur - zero - 1;
112
113 node = unhexlify(zero + 1, nlen > 40 ? 40 : nlen);
114 if (!node)
115 goto bail;
116
117 if (nlen > 40) {
118 PyObject *flags;
119
120 flags = PyString_FromStringAndSize(zero + 41,
121 nlen - 40);
122 if (!flags)
123 goto bail;
124
125 if (PyDict_SetItem(fdict, file, flags) == -1)
126 goto bail;
127 }
128
129 if (PyDict_SetItem(mfdict, file, node) == -1)
130 goto bail;
131
132 start = cur + 1;
133 zero = NULL;
134
135 Py_XDECREF(flags);
136 Py_XDECREF(node);
137 Py_XDECREF(file);
138 continue;
139 bail:
140 Py_XDECREF(flags);
141 Py_XDECREF(node);
142 Py_XDECREF(file);
143 goto quit;
144 }
145
146 if (len > 0 && *(cur - 1) != '\n') {
147 PyErr_SetString(PyExc_ValueError,
148 "manifest contains trailing garbage");
149 goto quit;
150 }
151
152 Py_INCREF(Py_None);
153 return Py_None;
154
155 quit:
156 return NULL;
157 }
158
159 static char parsers_doc[] = "Efficient content parsing.";
160
161 static PyMethodDef methods[] = {
162 {"parse_manifest", parse_manifest, METH_VARARGS, "parse a manifest\n"},
163 {NULL, NULL}
164 };
165
166 PyMODINIT_FUNC initparsers(void)
167 {
168 Py_InitModule3("parsers", methods, parsers_doc);
169 }
@@ -0,0 +1,125 b''
1 # store.py - repository store handling for Mercurial
2 #
3 # Copyright 2008 Matt Mackall <mpm@selenic.com>
4 #
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
7
8 import os, stat, osutil, util
9
10 def _buildencodefun():
11 e = '_'
12 win_reserved = [ord(x) for x in '\\:*?"<>|']
13 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
14 for x in (range(32) + range(126, 256) + win_reserved):
15 cmap[chr(x)] = "~%02x" % x
16 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
17 cmap[chr(x)] = e + chr(x).lower()
18 dmap = {}
19 for k, v in cmap.iteritems():
20 dmap[v] = k
21 def decode(s):
22 i = 0
23 while i < len(s):
24 for l in xrange(1, 4):
25 try:
26 yield dmap[s[i:i+l]]
27 i += l
28 break
29 except KeyError:
30 pass
31 else:
32 raise KeyError
33 return (lambda s: "".join([cmap[c] for c in s]),
34 lambda s: "".join(list(decode(s))))
35
36 encodefilename, decodefilename = _buildencodefun()
37
38 def _dirwalk(path, recurse):
39 '''yields (filename, size)'''
40 for e, kind, st in osutil.listdir(path, stat=True):
41 pe = os.path.join(path, e)
42 if kind == stat.S_IFDIR:
43 if recurse:
44 for x in _dirwalk(pe, True):
45 yield x
46 elif kind == stat.S_IFREG:
47 yield pe, st.st_size
48
49 class _store:
50 '''base class for local repository stores'''
51 def __init__(self, path):
52 self.path = path
53 try:
54 # files in .hg/ will be created using this mode
55 mode = os.stat(self.path).st_mode
56 # avoid some useless chmods
57 if (0777 & ~util._umask) == (0777 & mode):
58 mode = None
59 except OSError:
60 mode = None
61 self.createmode = mode
62
63 def join(self, f):
64 return os.path.join(self.path, f)
65
66 def _revlogfiles(self, relpath='', recurse=False):
67 '''yields (filename, size)'''
68 if relpath:
69 path = os.path.join(self.path, relpath)
70 else:
71 path = self.path
72 striplen = len(self.path) + len(os.sep)
73 filetypes = ('.d', '.i')
74 for f, size in _dirwalk(path, recurse):
75 if (len(f) > 2) and f[-2:] in filetypes:
76 yield util.pconvert(f[striplen:]), size
77
78 def _datafiles(self):
79 for x in self._revlogfiles('data', True):
80 yield x
81
82 def walk(self):
83 '''yields (direncoded filename, size)'''
84 # yield data files first
85 for x in self._datafiles():
86 yield x
87 # yield manifest before changelog
88 meta = util.sort(self._revlogfiles())
89 meta.reverse()
90 for x in meta:
91 yield x
92
93 class directstore(_store):
94 def __init__(self, path):
95 _store.__init__(self, path)
96 self.encodefn = lambda x: x
97 self.opener = util.opener(self.path)
98 self.opener.createmode = self.createmode
99
100 class encodedstore(_store):
101 def __init__(self, path):
102 _store.__init__(self, os.path.join(path, 'store'))
103 self.encodefn = encodefilename
104 op = util.opener(self.path)
105 op.createmode = self.createmode
106 self.opener = lambda f, *args, **kw: op(self.encodefn(f), *args, **kw)
107
108 def _datafiles(self):
109 for f, size in self._revlogfiles('data', True):
110 yield decodefilename(f), size
111
112 def join(self, f):
113 return os.path.join(self.path, self.encodefn(f))
114
115 def encodefn(requirements):
116 if 'store' not in requirements:
117 return lambda x: x
118 else:
119 return encodefilename
120
121 def store(requirements, path):
122 if 'store' not in requirements:
123 return directstore(path)
124 else:
125 return encodedstore(path)
@@ -0,0 +1,72 b''
1 {header}
2 <title>{repo|escape}: {node|short}</title>
3 </head>
4 <body>
5 <div class="container">
6 <div class="menu">
7 <div class="logo">
8 <a href="http://www.selenic.com/mercurial/">
9 <img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
10 </div>
11 <ul>
12 <li><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
13 <li><a href="{url}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
14 <li><a href="{url}tags{sessionvars%urlparameter}">tags</a></li>
15 </ul>
16 <ul>
17 <li class="active">changeset</li>
18 <li><a href="{url}file/{node|short}{sessionvars%urlparameter}">browse</a></li>
19 </ul>
20 <ul>
21 {archives%archiveentry}</ul>
22 </ul>
23 </div>
24
25 <div class="main">
26
27 <h2>{repo|escape}</h2>
28 <h3>changeset {rev}:{node|short} {changesettag}</h3>
29
30 <form class="search" action="{url}log">
31 {sessionvars%hiddenformentry}
32 <p><input name="rev" id="search1" type="text" size="30"></p>
33 </form>
34
35 <div class="description">{desc|strip|escape|addbreaks}</div>
36
37 <table id="changesetEntry">
38 <tr>
39 <th class="author">author</th>
40 <td class="author">{author|obfuscate}</td>
41 </tr>
42 <tr>
43 <th class="date">date</th>
44 <td class="date">{date|date} ({date|age} ago)</td></tr>
45 <tr>
46 <th class="author">parents</th>
47 <td class="author">{parent%changesetparent}</td>
48 </tr>
49 <tr>
50 <th class="author">children</th>
51 <td class="author">{child%changesetchild}</td>
52 </tr>
53 <tr>
54 <th class="files">files</th>
55 <td class="files">{files}</td></tr>
56 </tr>
57 </table>
58 <tr>
59
60 <div class="overflow">
61 <table class="bigtable">
62 <tr>
63 <th class="lineno">line</th>
64 <th class="source">diff</th>
65 </tr>
66 </table>
67 {diff}
68 </div>
69 </div>
70 {footer}
71
72
@@ -0,0 +1,40 b''
1 {header}
2 <title>{repo|escape}: error</title>
3 </head>
4 <body>
5
6 <div class="content">
7 <div class="menu">
8 <div class="logo">
9 <a href="http://www.selenic.com/mercurial/">
10 <img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
11 </div>
12 <ul>
13 <li><a href="{url}log{sessionvars%urlparameter}">log</a></li>
14 <li><a href="{url}graph{sessionvars%urlparameter}">graph</a></li>
15 <li><a href="{url}tags{sessionvars%urlparameter}">tags</a></li>
16 </ul>
17 </div>
18
19 <div class="main">
20
21 <h2>{repo|escape}</h2>
22 <h3>error</h3>
23
24 <form class="search" action="{url}log">
25 {sessionvars%hiddenformentry}
26 <p><input name="rev" id="search1" type="text" size="30"></p>
27 </form>
28
29 <div class="description">
30 <p>
31 An error occurred while processing your request:
32 </p>
33 <p>
34 {error|escape}
35 </p>
36 </div>
37 </div>
38 </div>
39
40 {footer}
@@ -0,0 +1,77 b''
1 {header}
2 <title>{repo|escape}: {file|escape} annotate</title>
3 </head>
4 <body>
5
6 <div class="container">
7 <div class="menu">
8 <div class="logo">
9 <a href="http://www.selenic.com/mercurial/">
10 <img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
11 </div>
12 <ul>
13 <li><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
14 <li><a href="{url}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
15 <li><a href="{url}tags{sessionvars%urlparameter}">tags</a></li>
16 </ul>
17
18 <ul>
19 <li><a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a></li>
20 <li><a href="{url}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
21 </ul>
22 <ul>
23 <li><a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a></li>
24 <li><a href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a></li>
25 <li class="active">annotate</li>
26 <li><a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file log</a></li>
27 <li><a href="{url}raw-annotate/{node|short}/{file|urlescape}">raw</a></li>
28 </ul>
29 </div>
30
31 <div class="main">
32 <h2>{repo|escape}</h2>
33 <h3>annotate {file|escape} @ {rev}:{node|short}</h2>
34
35 <form class="search" action="{url}log">
36 {sessionvars%hiddenformentry}
37 <p><input name="rev" id="search1" type="text" size="30"></p>
38 </form>
39
40 <div class="description">{desc|strip|escape|addbreaks}</div>
41
42 <table id="changesetEntry">
43 <tr>
44 <th class="author">author</th>
45 <td class="author">{author|obfuscate}</td>
46 </tr>
47 <tr>
48 <th class="date">date</th>
49 <td class="date">{date|date} ({date|age} ago)</td>
50 </tr>
51 <tr>
52 <th class="author">parents</th>
53 <td class="author">{parent%filerevparent}</td>
54 </tr>
55 <tr>
56 <th class="author">children</th>
57 <td class="author">{child%filerevchild}</td>
58 </tr>
59 {changesettag}
60 </table>
61
62 <br/>
63
64 <div class="overflow">
65 <table class="bigtable">
66 <tr>
67 <th class="annotate">rev</th>
68 <th class="lineno">line</th>
69 <th class="line">source</th>
70 </tr>
71 {annotate%annotateline}
72 </table>
73 </div>
74 </div>
75 </div>
76
77 {footer}
@@ -0,0 +1,75 b''
1 {header}
2 <title>{repo|escape}: {file|escape} diff</title>
3 </head>
4 <body>
5
6 <div class="container">
7 <div class="menu">
8 <div class="logo">
9 <a href="http://www.selenic.com/mercurial/">
10 <img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
11 </div>
12 <ul>
13 <li><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
14 <li><a href="{url}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
15 <li><a href="{url}tags{sessionvars%urlparameter}">tags</a></li>
16 </ul>
17 <ul>
18 <li><a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a></li>
19 <li><a href="{url}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
20 </ul>
21 <ul>
22 <li><a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a></li>
23 <li class="active">diff</li>
24 <li><a href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a></li>
25 <li><a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file log</a></li>
26 <li><a href="{url}raw-file/{node|short}/{file|urlescape}">raw</a></li>
27 </ul>
28 </div>
29
30 <div class="main">
31 <h2>{repo|escape}</h2>
32 <h3>diff {file|escape} @ {rev}:{node|short}</h3>
33
34 <form class="search" action="{url}log">
35 {sessionvars%hiddenformentry}
36 <p><input name="rev" id="search1" type="text" size="30"></p>
37 </form>
38
39 <div class="description">{desc|strip|escape|addbreaks}</div>
40
41 <table id="changesetEntry">
42 <tr>
43 <th>author</th>
44 <td>{author|obfuscate}</td>
45 </tr>
46 <tr>
47 <th>date</th>
48 <td>{date|date} ({date|age} ago)</td>
49 </tr>
50 <tr>
51 <th>parents</th>
52 <td>{parent%filerevparent}</td>
53 </tr>
54 <tr>
55 <th>children</th>
56 <td>{child%filerevchild}</td>
57 </tr>
58 {changesettag}
59 </table>
60
61 <div class="overflow">
62 <table class="bigtable">
63 <tr>
64 <th class="lineno">line</th>
65 <th class="source">diff</th>
66 </tr>
67 <table>
68 {diff}
69 </div>
70 </div>
71 </div>
72
73 {footer}
74
75
@@ -0,0 +1,59 b''
1 {header}
2 <title>{repo|escape}: {file|escape} history</title>
3 <link rel="alternate" type="application/atom+xml"
4 href="{url}atom-log/tip/{file|urlescape}" title="Atom feed for {repo|escape}:{file}">
5 <link rel="alternate" type="application/rss+xml"
6 href="{url}rss-log/tip/{file|urlescape}" title="RSS feed for {repo|escape}:{file}">
7 </head>
8 </head>
9 <body>
10
11 <div class="container">
12 <div class="menu">
13 <div class="logo">
14 <a href="http://www.selenic.com/mercurial/">
15 <img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
16 </div>
17 <ul>
18 <li><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
19 <li><a href="{url}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
20 <li><a href="{url}tags{sessionvars%urlparameter}">tags</a></li>
21 </ul>
22 <ul>
23 <li><a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a></li>
24 <li><a href="{url}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
25 </ul>
26 <ul>
27 <li><a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a></li>
28 <li><a href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a></li>
29 <li><a href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a></li>
30 <li class="active">file log</li>
31 <li><a href="{url}raw-file/{node|short}/{file|urlescape}">raw</a></li>
32 </ul>
33 </div>
34
35 <div class="main">
36
37 <h2>{repo|escape}</h2>
38 <h3>log {file|escape}</h3>
39
40 <form class="search" action="{url}log">
41 {sessionvars%hiddenformentry}
42 <p><input name="rev" id="search1" type="text" size="30"></p>
43 </form>
44
45 <div class="navigate">{nav%filenaventry}</div>
46
47 <table class="bigtable">
48 <tr>
49 <th class="age">age</td>
50 <th class="author">author</td>
51 <th class="description">description</td>
52 </tr>
53 {entries%filelogentry}
54 </table>
55
56 </div>
57 </div>
58
59 {footer}
@@ -0,0 +1,5 b''
1 <tr class="parity{parity}">
2 <td class="age">{date|age}</td>
3 <td class="author">{author|person}</td>
4 <td class="description"><a href="{url}rev/{node|short}{sessionvars%urlparameter}">{desc|strip|firstline|escape}</a></td>
5 </tr>
@@ -0,0 +1,74 b''
1 {header}
2 <title>{repo|escape}: {node|short} {file|escape}</title>
3 </head>
4 <body>
5
6 <div class="container">
7 <div class="menu">
8 <div class="logo">
9 <a href="http://www.selenic.com/mercurial/">
10 <img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
11 </div>
12 <ul>
13 <li><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
14 <li><a href="{url}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
15 <li><a href="{url}tags{sessionvars%urlparameter}">tags</a></li>
16 </ul>
17 <ul>
18 <li><a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a></li>
19 <li><a href="{url}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
20 </ul>
21 <ul>
22 <li class="active">file</li>
23 <li><a href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a></li>
24 <li><a href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a></li>
25 <li><a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file log</a></li>
26 <li><a href="{url}raw-file/{node|short}/{file|urlescape}">raw</a></li>
27 </ul>
28 </div>
29
30 <div class="main">
31
32 <h2>{repo|escape}</h2>
33 <h3>view {file|escape} @ {rev}:{node|short}</h3>
34
35 <form class="search" action="{url}log">
36 {sessionvars%hiddenformentry}
37 <p><input name="rev" id="search1" type="text" size="30"></p>
38 </form>
39
40 <div class="description">{desc|strip|escape|addbreaks}</div>
41
42 <table id="changesetEntry">
43 <tr>
44 <th class="author">author</th>
45 <td class="author">{author|obfuscate}</td>
46 </tr>
47 <tr>
48 <th class="date">date</th>
49 <td class="date">{date|date} ({date|age} ago)</td>
50 </tr>
51 <tr>
52 <th class="author">parents</th>
53 <td class="author">{parent%filerevparent}</td>
54 </tr>
55 <tr>
56 <th class="author">children</th>
57 <td class="author">{child%filerevchild}</td>
58 </tr>
59 {changesettag}
60 </table>
61
62 <div class="overflow">
63 <table class="bigtable">
64 <tr>
65 <th class="lineno">line</th>
66 <th class="source">source</th>
67 </tr>
68 {text%fileline}
69 </table>
70 </div>
71 </div>
72 </div>
73
74 {footer}
@@ -0,0 +1,4 b''
1 {motd}
2
3 </body>
4 </html>
@@ -0,0 +1,113 b''
1 {header}
2 <title>{repo|escape}: revision graph</title>
3 <link rel="alternate" type="application/atom+xml"
4 href="{url}atom-log" title="Atom feed for {repo|escape}: log">
5 <link rel="alternate" type="application/rss+xml"
6 href="{url}rss-log" title="RSS feed for {repo|escape}: log">
7 <!--[if IE]><script type="text/javascript" src="{staticurl}excanvas.js"></script><![endif]-->
8 </head>
9 <body>
10
11 <div class="container">
12 <div class="menu">
13 <div class="logo">
14 <a href="http://www.selenic.com/mercurial/">
15 <img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
16 </div>
17 <ul>
18 <li><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
19 <li class="active">graph</li>
20 <li><a href="{url}tags{sessionvars%urlparameter}">tags</a></li>
21 </ul>
22 <ul>
23 <li><a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a></li>
24 <li><a href="{url}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
25 </ul>
26 </div>
27
28 <div class="main">
29 <h2>{repo|escape}</h2>
30 <h3>graph</h3>
31
32 <form class="search" action="{url}log">
33 {sessionvars%hiddenformentry}
34 <p><input name="rev" id="search1" type="text" size="30"></p>
35 </form>
36
37 <div class="navigate">
38 <a href="{url}graph/{uprev}{sessionvars%urlparameter}?revcount={revcountless}">less</a>
39 <a href="{url}graph/{uprev}{sessionvars%urlparameter}?revcount={revcountmore}">more</a>
40 | {changenav%navgraphentry}
41 </div>
42
43 <div id="noscript">The revision graph only works with JavaScript-enabled browsers.</div>
44
45 <div id="wrapper">
46 <ul id="nodebgs"></ul>
47 <canvas id="graph" width="224" height="{canvasheight}"></canvas>
48 <ul id="graphnodes"></ul>
49 </div>
50
51 <script type="text/javascript" src="{staticurl}graph.js"></script>
52 <script>
53 <!-- hide script content
54
55 document.getElementById('noscript').style.display = 'none';
56
57 var data = {jsdata|json};
58 var graph = new Graph();
59 graph.scale({bg_height});
60
61 graph.edge = function(x0, y0, x1, y1, color) {
62
63 this.setColor(color, 0.0, 0.65);
64 this.ctx.beginPath();
65 this.ctx.moveTo(x0, y0);
66 this.ctx.lineTo(x1, y1);
67 this.ctx.stroke();
68
69 }
70
71 var revlink = '<li style="_STYLE"><span class="desc">';
72 revlink += '<a href="{url}rev/_NODEID{sessionvars%urlparameter}" title="_NODEID">_DESC</a>';
73 revlink += '</span><span class="tag">_TAGS</span>';
74 revlink += '<span class="info">_DATE ago, by _USER</span></li>';
75
76 graph.vertex = function(x, y, color, parity, cur) {
77
78 this.ctx.beginPath();
79 color = this.setColor(color, 0.25, 0.75);
80 this.ctx.arc(x, y, radius, 0, Math.PI * 2, true);
81 this.ctx.fill();
82
83 var bg = '<li class="bg parity' + parity + '"></li>';
84 var left = (this.columns + 1) * this.bg_height;
85 var nstyle = 'padding-left: ' + left + 'px;';
86 var item = revlink.replace(/_STYLE/, nstyle);
87 item = item.replace(/_PARITY/, 'parity' + parity);
88 item = item.replace(/_NODEID/, cur[0]);
89 item = item.replace(/_NODEID/, cur[0]);
90 item = item.replace(/_DESC/, cur[3]);
91 item = item.replace(/_USER/, cur[4]);
92 item = item.replace(/_DATE/, cur[5]);
93 item = item.replace(/_TAGS/, cur[7].join('&nbsp; '));
94
95 return [bg, item];
96
97 }
98
99 graph.render(data);
100
101 // stop hiding script -->
102 </script>
103
104 <div class="navigate">
105 <a href="{url}graph/{uprev}{sessionvars%urlparameter}?revcount={revcountless}">less</a>
106 <a href="{url}graph/{uprev}{sessionvars%urlparameter}?revcount={revcountmore}">more</a>
107 | {changenav%navgraphentry}
108 </div>
109
110 </div>
111 </div>
112
113 {footer}
@@ -0,0 +1,7 b''
1 <!-- quirksmode -->
2 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
3 <html>
4 <head>
5 <link rel="icon" href="{staticurl}hgicon.png" type="image/png">
6 <meta name="robots" content="index, nofollow" />
7 <link rel="stylesheet" href="{staticurl}style-coal.css" type="text/css" />
@@ -0,0 +1,26 b''
1 {header}
2 <title>Mercurial repositories index</title>
3 </head>
4 <body>
5
6 <div class="container">
7 <div class="menu">
8 <a href="http://www.selenic.com/mercurial/">
9 <img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
10 </div>
11 <div class="main">
12 <h2>Mercurial Repositories</h2>
13
14 <table class="bigtable">
15 <tr>
16 <th><a href="?sort={sort_name}">Name</a></th>
17 <th><a href="?sort={sort_description}">Description</a></th>
18 <th><a href="?sort={sort_contact}">Contact</a></th>
19 <th><a href="?sort={sort_lastchange}">Last change</a></th>
20 <th>&nbsp;</th>
21 <tr>
22 {entries%indexentry}
23 </table>
24 </div>
25 </div>
26 {footer}
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -7,6 +7,7 b' syntax: glob'
7 7 *.mergebackup
8 8 *.o
9 9 *.so
10 *.pyd
10 11 *.pyc
11 12 *.swp
12 13 *.prof
@@ -35,8 +35,10 b''
35 35 ;; This code has been developed under XEmacs 21.5, and may not work as
36 36 ;; well under GNU Emacs (albeit tested under 21.4). Patches to
37 37 ;; enhance the portability of this code, fix bugs, and add features
38 ;; are most welcome. You can clone a Mercurial repository for this
39 ;; package from http://www.serpentine.com/hg/hg-emacs
38 ;; are most welcome.
39
40 ;; As of version 22.3, GNU Emacs's VC mode has direct support for
41 ;; Mercurial, so this package may not prove as useful there.
40 42
41 43 ;; Please send problem reports and suggestions to bos@serpentine.com.
42 44
@@ -205,8 +205,7 b' typeset -A _hg_cmd_globals'
205 205
206 206 _hg_config() {
207 207 typeset -a items
208 local line
209 items=(${${(%f)"$(_hg_cmd showconfig)"}%%\=*})
208 items=(${${(%f)"$(_call_program hg hg showconfig)"}%%\=*})
210 209 (( $#items )) && _describe -t config 'config item' items
211 210 }
212 211
@@ -291,10 +290,14 b' typeset -A _hg_cmd_globals'
291 290 '--cwd[change working directory]:new working directory:_files -/'
292 291 '(--noninteractive -y)'{-y,--noninteractive}'[do not prompt, assume yes for any required answers]'
293 292 '(--verbose -v)'{-v,--verbose}'[enable additional output]'
293 '*--config[set/override config option]:defined config items:_hg_config'
294 294 '(--quiet -q)'{-q,--quiet}'[suppress output]'
295 295 '(--help -h)'{-h,--help}'[display help and exit]'
296 296 '--debug[debug mode]'
297 297 '--debugger[start debugger]'
298 '--encoding[set the charset encoding (default: UTF8)]'
299 '--encodingmode[set the charset encoding mode (default: strict)]'
300 '--lsprof[print improved command execution profile]'
298 301 '--traceback[print traceback on exception]'
299 302 '--time[time how long the command takes]'
300 303 '--profile[profile]'
@@ -69,6 +69,7 b' def show_doc(ui):'
69 69 if f.startswith("debug"): continue
70 70 d = get_cmd(h[f])
71 71 # synopsis
72 ui.write("[[%s]]\n" % d['cmd'])
72 73 ui.write("%s::\n" % d['synopsis'].replace("hg ","", 1))
73 74 # description
74 75 ui.write("%s\n\n" % d['desc'][1])
@@ -91,11 +92,10 b' def show_doc(ui):'
91 92 ui.write(_(" aliases: %s\n\n") % " ".join(d['aliases']))
92 93
93 94 # print topics
94 for t in helptable:
95 for t, doc in helptable:
95 96 l = t.split("|")
96 97 section = l[-1]
97 98 underlined(_(section).upper())
98 doc = helptable[t]
99 99 if callable(doc):
100 100 doc = doc()
101 101 ui.write(_(doc))
@@ -30,65 +30,13 b' revision::'
30 30
31 31 repository path::
32 32 either the pathname of a local repository or the URI of a remote
33 repository. There are two available URI protocols, http:// which is
33 repository. There are two available URI protocols, http:// which is
34 34 fast and the static-http:// protocol which is much slower but does not
35 35 require a special server on the web host.
36 36
37 37
38 38 include::hg.1.gendoc.txt[]
39 39
40 SPECIFYING SINGLE REVISIONS
41 ---------------------------
42
43 Mercurial accepts several notations for identifying individual
44 revisions.
45
46 A plain integer is treated as a revision number. Negative
47 integers are treated as offsets from the tip, with -1 denoting the
48 tip.
49
50 A 40-digit hexadecimal string is treated as a unique revision
51 identifier.
52
53 A hexadecimal string less than 40 characters long is treated as a
54 unique revision identifier, and referred to as a short-form
55 identifier. A short-form identifier is only valid if it is the
56 prefix of one full-length identifier.
57
58 Any other string is treated as a tag name, which is a symbolic
59 name associated with a revision identifier. Tag names may not
60 contain the ":" character.
61
62 The reserved name "tip" is a special tag that always identifies
63 the most recent revision.
64
65 The reserved name "null" indicates the null revision. This is the
66 revision of an empty repository, and the parent of revision 0.
67
68 The reserved name "." indicates the working directory parent. If
69 no working directory is checked out, it is equivalent to null.
70 If an uncommitted merge is in progress, "." is the revision of
71 the first parent.
72
73 SPECIFYING MULTIPLE REVISIONS
74 -----------------------------
75
76 When Mercurial accepts more than one revision, they may be
77 specified individually, or provided as a continuous range,
78 separated by the ":" character.
79
80 The syntax of range notation is [BEGIN]:[END], where BEGIN and END
81 are revision identifiers. Both BEGIN and END are optional. If
82 BEGIN is not specified, it defaults to revision number 0. If END
83 is not specified, it defaults to the tip. The range ":" thus
84 means "all revisions".
85
86 If BEGIN is greater than END, revisions are treated in reverse
87 order.
88
89 A range acts as a closed interval. This means that a range of 3:5
90 gives 3, 4 and 5. Similarly, a range of 4:2 gives 4, 3, and 2.
91
92 40 FILES
93 41 -----
94 42 .hgignore::
@@ -103,7 +51,7 b' FILES'
103 51 /etc/mercurial/hgrc, $HOME/.hgrc, .hg/hgrc::
104 52 This file contains defaults and configuration. Values in .hg/hgrc
105 53 override those in $HOME/.hgrc, and these override settings made in the
106 global /etc/mercurial/hgrc configuration. See hgrc(5) for details of
54 global /etc/mercurial/hgrc configuration. See hgrc(5) for details of
107 55 the contents and format of these files.
108 56
109 57 Some commands (e.g. revert) produce backup files ending in .orig, if
@@ -17,25 +17,25 b' DESCRIPTION'
17 17 -----------
18 18
19 19 Mercurial ignores every unmanaged file that matches any pattern in an
20 ignore file. The patterns in an ignore file do not apply to files
21 managed by Mercurial. To control Mercurial's handling of files that
22 it manages, see the hg(1) man page. Look for the "-I" and "-X"
20 ignore file. The patterns in an ignore file do not apply to files
21 managed by Mercurial. To control Mercurial's handling of files that
22 it manages, see the hg(1) man page. Look for the "-I" and "-X"
23 23 options.
24 24
25 25 In addition, a Mercurial configuration file can point to a set of
26 per-user or global ignore files. See the hgrc(5) man page for details
27 of how to configure these files. Look for the "ignore" entry in the
26 per-user or global ignore files. See the hgrc(5) man page for details
27 of how to configure these files. Look for the "ignore" entry in the
28 28 "ui" section.
29 29
30 30 SYNTAX
31 31 ------
32 32
33 33 An ignore file is a plain text file consisting of a list of patterns,
34 with one pattern per line. Empty lines are skipped. The "#"
34 with one pattern per line. Empty lines are skipped. The "#"
35 35 character is treated as a comment character, and the "\" character is
36 36 treated as an escape character.
37 37
38 Mercurial supports several pattern syntaxes. The default syntax used
38 Mercurial supports several pattern syntaxes. The default syntax used
39 39 is Python/Perl-style regular expressions.
40 40
41 41 To change the syntax used, use a line of the following form:
@@ -52,9 +52,9 b' glob::'
52 52 The chosen syntax stays in effect when parsing all patterns that
53 53 follow, until another syntax is selected.
54 54
55 Neither glob nor regexp patterns are rooted. A glob-syntax pattern of
55 Neither glob nor regexp patterns are rooted. A glob-syntax pattern of
56 56 the form "*.c" will match a file ending in ".c" in any directory, and
57 a regexp pattern of the form "\.c$" will do the same. To root a
57 a regexp pattern of the form "\.c$" will do the same. To root a
58 58 regexp pattern, start it with "^".
59 59
60 60 EXAMPLE
@@ -17,26 +17,26 b' FILES'
17 17
18 18 Mercurial reads configuration data from several files, if they exist.
19 19 The names of these files depend on the system on which Mercurial is
20 installed. *.rc files from a single directory are read in
21 alphabetical order, later ones overriding earlier ones. Where
20 installed. *.rc files from a single directory are read in
21 alphabetical order, later ones overriding earlier ones. Where
22 22 multiple paths are given below, settings from later paths override
23 23 earlier ones.
24 24
25 25 (Unix) <install-root>/etc/mercurial/hgrc.d/*.rc::
26 26 (Unix) <install-root>/etc/mercurial/hgrc::
27 27 Per-installation configuration files, searched for in the
28 directory where Mercurial is installed. <install-root> is the
28 directory where Mercurial is installed. <install-root> is the
29 29 parent directory of the hg executable (or symlink) being run.
30 30 For example, if installed in /shared/tools/bin/hg, Mercurial will
31 look in /shared/tools/etc/mercurial/hgrc. Options in these files
31 look in /shared/tools/etc/mercurial/hgrc. Options in these files
32 32 apply to all Mercurial commands executed by any user in any
33 33 directory.
34 34
35 35 (Unix) /etc/mercurial/hgrc.d/*.rc::
36 36 (Unix) /etc/mercurial/hgrc::
37 37 Per-system configuration files, for the system on which Mercurial
38 is running. Options in these files apply to all Mercurial
39 commands executed by any user in any directory. Options in these
38 is running. Options in these files apply to all Mercurial
39 commands executed by any user in any directory. Options in these
40 40 files override per-installation options.
41 41
42 42 (Windows) <install-dir>\Mercurial.ini::
@@ -45,7 +45,7 b' earlier ones.'
45 45 or else::
46 46 (Windows) C:\Mercurial\Mercurial.ini::
47 47 Per-installation/system configuration files, for the system on
48 which Mercurial is running. Options in these files apply to all
48 which Mercurial is running. Options in these files apply to all
49 49 Mercurial commands executed by any user in any directory.
50 50 Registry keys contain PATH-like strings, every part of which must
51 51 reference a Mercurial.ini file or be a directory where *.rc files
@@ -59,16 +59,16 b' earlier ones.'
59 59 Per-user configuration file(s), for the user running Mercurial.
60 60 On Windows 9x, %HOME% is replaced by %APPDATA%.
61 61 Options in these files apply to all Mercurial commands executed
62 by this user in any directory. Options in thes files override
62 by this user in any directory. Options in thes files override
63 63 per-installation and per-system options.
64 64
65 65 (Unix, Windows) <repo>/.hg/hgrc::
66 66 Per-repository configuration options that only apply in a
67 particular repository. This file is not version-controlled, and
68 will not get transferred during a "clone" operation. Options in
67 particular repository. This file is not version-controlled, and
68 will not get transferred during a "clone" operation. Options in
69 69 this file override options in all other configuration files.
70 70 On Unix, most of this file will be ignored if it doesn't belong
71 to a trusted user or to a trusted group. See the documentation
71 to a trusted user or to a trusted group. See the documentation
72 72 for the trusted section below for more details.
73 73
74 74 SYNTAX
@@ -82,10 +82,10 b' and followed by "name: value" entries; "'
82 82 green=
83 83 eggs
84 84
85 Each line contains one entry. If the lines that follow are indented,
85 Each line contains one entry. If the lines that follow are indented,
86 86 they are treated as continuations of that entry.
87 87
88 Leading whitespace is removed from values. Empty lines are skipped.
88 Leading whitespace is removed from values. Empty lines are skipped.
89 89
90 90 The optional values can contain format strings which refer to other
91 91 values in the same section, or values in a special DEFAULT section.
@@ -100,6 +100,7 b' This section describes the different sec'
100 100 Mercurial "hgrc" file, the purpose of each section, its possible
101 101 keys, and their possible values.
102 102
103 [[decode]]
103 104 decode/encode::
104 105 Filters for transforming files on checkout/checkin. This would
105 106 typically be used for newline processing or other
@@ -107,12 +108,12 b' decode/encode::'
107 108
108 109 Filters consist of a filter pattern followed by a filter command.
109 110 Filter patterns are globs by default, rooted at the repository
110 root. For example, to match any file ending in ".txt" in the root
111 directory only, use the pattern "*.txt". To match any file ending
111 root. For example, to match any file ending in ".txt" in the root
112 directory only, use the pattern "*.txt". To match any file ending
112 113 in ".c" anywhere in the repository, use the pattern "**.c".
113 114
114 115 The filter command can start with a specifier, either "pipe:" or
115 "tempfile:". If no specifier is given, "pipe:" is used by default.
116 "tempfile:". If no specifier is given, "pipe:" is used by default.
116 117
117 118 A "pipe:" command must accept data on stdin and return the
118 119 transformed data on stdout.
@@ -129,9 +130,9 b' decode/encode::'
129 130 # can safely omit "pipe:", because it's the default)
130 131 *.gz = gzip
131 132
132 A "tempfile:" command is a template. The string INFILE is replaced
133 A "tempfile:" command is a template. The string INFILE is replaced
133 134 with the name of a temporary file that contains the data to be
134 filtered by the command. The string OUTFILE is replaced with the
135 filtered by the command. The string OUTFILE is replaced with the
135 136 name of an empty temporary file, where the filtered data must be
136 137 written by the command.
137 138
@@ -158,6 +159,7 b' decode/encode::'
158 159 [decode]
159 160 **.txt = dumbdecode:
160 161
162 [[defaults]]
161 163 defaults::
162 164 Use the [defaults] section to define command defaults, i.e. the
163 165 default options/arguments to pass to the specified commands.
@@ -173,6 +175,7 b' defaults::'
173 175 defining command defaults. The command defaults will also be
174 176 applied to the aliases of the commands defined.
175 177
178 [[diff]]
176 179 diff::
177 180 Settings used when displaying diffs. They are all boolean and
178 181 defaults to False.
@@ -189,25 +192,26 b' diff::'
189 192 ignoreblanklines;;
190 193 Ignore changes whose lines are all blank.
191 194
195 [[email]]
192 196 email::
193 197 Settings for extensions that send email messages.
194 198 from;;
195 Optional. Email address to use in "From" header and SMTP envelope
199 Optional. Email address to use in "From" header and SMTP envelope
196 200 of outgoing messages.
197 201 to;;
198 Optional. Comma-separated list of recipients' email addresses.
202 Optional. Comma-separated list of recipients' email addresses.
199 203 cc;;
200 Optional. Comma-separated list of carbon copy recipients'
204 Optional. Comma-separated list of carbon copy recipients'
201 205 email addresses.
202 206 bcc;;
203 Optional. Comma-separated list of blind carbon copy
204 recipients' email addresses. Cannot be set interactively.
207 Optional. Comma-separated list of blind carbon copy
208 recipients' email addresses. Cannot be set interactively.
205 209 method;;
206 Optional. Method to use to send email messages. If value is
210 Optional. Method to use to send email messages. If value is
207 211 "smtp" (default), use SMTP (see section "[smtp]" for
208 configuration). Otherwise, use as name of program to run that
212 configuration). Otherwise, use as name of program to run that
209 213 acts like sendmail (takes "-f" option for sender, list of
210 recipients on command line, message on stdin). Normally, setting
214 recipients on command line, message on stdin). Normally, setting
211 215 this to "sendmail" or "/usr/sbin/sendmail" is enough to use
212 216 sendmail to send messages.
213 217
@@ -217,6 +221,7 b' email::'
217 221 from = Joseph User <joe.user@example.com>
218 222 method = /usr/sbin/sendmail
219 223
224 [[extensions]]
220 225 extensions::
221 226 Mercurial has an extension mechanism for adding new features. To
222 227 enable an extension, create an entry for it in this section.
@@ -241,6 +246,7 b' extensions::'
241 246 # (this extension will get loaded from the file specified)
242 247 myfeature = ~/.hgext/myfeature.py
243 248
249 [[format]]
244 250 format::
245 251
246 252 usestore;;
@@ -250,6 +256,7 b' format::'
250 256 you to store longer filenames in some situations at the expense of
251 257 compatibility.
252 258
259 [[merge-patterns]]
253 260 merge-patterns::
254 261 This section specifies merge tools to associate with particular file
255 262 patterns. Tools matched here will take precedence over the default
@@ -261,6 +268,7 b' merge-patterns::'
261 268 **.c = kdiff3
262 269 **.jpg = myimgmerge
263 270
271 [[merge-tools]]
264 272 merge-tools::
265 273 This section configures external merge tools to use for file-level
266 274 merges.
@@ -281,6 +289,7 b' merge-tools::'
281 289 myHtmlTool.priority = 1
282 290
283 291 Supported arguments:
292
284 293 priority;;
285 294 The priority in which to evaluate this tool.
286 295 Default: 0.
@@ -297,10 +306,10 b' merge-tools::'
297 306 launching external tool.
298 307 Default: True
299 308 binary;;
300 This tool can merge binary files. Defaults to False, unless tool
309 This tool can merge binary files. Defaults to False, unless tool
301 310 was selected by file pattern match.
302 311 symlink;;
303 This tool can merge symlinks. Defaults to False, even if tool was
312 This tool can merge symlinks. Defaults to False, even if tool was
304 313 selected by file pattern match.
305 314 checkconflicts;;
306 315 Check whether there are conflicts even though the tool reported
@@ -313,19 +322,20 b' merge-tools::'
313 322 fixeol;;
314 323 Attempt to fix up EOL changes caused by the merge tool.
315 324 Default: False
316 gui:;
325 gui;;
317 326 This tool requires a graphical interface to run. Default: False
318 327 regkey;;
319 328 Windows registry key which describes install location of this tool.
320 329 Mercurial will search for this key first under HKEY_CURRENT_USER and
321 then under HKEY_LOCAL_MACHINE. Default: None
330 then under HKEY_LOCAL_MACHINE. Default: None
322 331 regname;;
323 Name of value to read from specified registry key. Defaults to the
332 Name of value to read from specified registry key. Defaults to the
324 333 unnamed (default) value.
325 334 regappend;;
326 335 String to append to the value read from the registry, typically the
327 executable name of the tool. Default: None
336 executable name of the tool. Default: None
328 337
338 [[hooks]]
329 339 hooks::
330 340 Commands or Python functions that get automatically executed by
331 341 various actions such as starting or finishing a commit. Multiple
@@ -342,24 +352,24 b' hooks::'
342 352 incoming.autobuild = /my/build/hook
343 353
344 354 Most hooks are run with environment variables set that give added
345 useful information. For each hook below, the environment variables
355 useful information. For each hook below, the environment variables
346 356 it is passed are listed with names of the form "$HG_foo".
347 357
348 358 changegroup;;
349 359 Run after a changegroup has been added via push, pull or
350 unbundle. ID of the first new changeset is in $HG_NODE. URL from
360 unbundle. ID of the first new changeset is in $HG_NODE. URL from
351 361 which changes came is in $HG_URL.
352 362 commit;;
353 363 Run after a changeset has been created in the local repository.
354 ID of the newly created changeset is in $HG_NODE. Parent
364 ID of the newly created changeset is in $HG_NODE. Parent
355 365 changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
356 366 incoming;;
357 367 Run after a changeset has been pulled, pushed, or unbundled into
358 the local repository. The ID of the newly arrived changeset is in
359 $HG_NODE. URL that was source of changes came is in $HG_URL.
368 the local repository. The ID of the newly arrived changeset is in
369 $HG_NODE. URL that was source of changes came is in $HG_URL.
360 370 outgoing;;
361 Run after sending changes from local repository to another. ID of
362 first changeset sent is in $HG_NODE. Source of operation is in
371 Run after sending changes from local repository to another. ID of
372 first changeset sent is in $HG_NODE. Source of operation is in
363 373 $HG_SOURCE; see "preoutgoing" hook for description.
364 374 post-<command>;;
365 375 Run after successful invocations of the associated command. The
@@ -371,56 +381,56 b' hooks::'
371 381 the command doesn't execute and Mercurial returns the failure code.
372 382 prechangegroup;;
373 383 Run before a changegroup is added via push, pull or unbundle.
374 Exit status 0 allows the changegroup to proceed. Non-zero status
375 will cause the push, pull or unbundle to fail. URL from which
384 Exit status 0 allows the changegroup to proceed. Non-zero status
385 will cause the push, pull or unbundle to fail. URL from which
376 386 changes will come is in $HG_URL.
377 387 precommit;;
378 Run before starting a local commit. Exit status 0 allows the
379 commit to proceed. Non-zero status will cause the commit to fail.
388 Run before starting a local commit. Exit status 0 allows the
389 commit to proceed. Non-zero status will cause the commit to fail.
380 390 Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
381 391 preoutgoing;;
382 392 Run before collecting changes to send from the local repository to
383 another. Non-zero status will cause failure. This lets you
384 prevent pull over http or ssh. Also prevents against local pull,
393 another. Non-zero status will cause failure. This lets you
394 prevent pull over http or ssh. Also prevents against local pull,
385 395 push (outbound) or bundle commands, but not effective, since you
386 can just copy files instead then. Source of operation is in
387 $HG_SOURCE. If "serve", operation is happening on behalf of
388 remote ssh or http repository. If "push", "pull" or "bundle",
396 can just copy files instead then. Source of operation is in
397 $HG_SOURCE. If "serve", operation is happening on behalf of
398 remote ssh or http repository. If "push", "pull" or "bundle",
389 399 operation is happening on behalf of repository on same system.
390 400 pretag;;
391 Run before creating a tag. Exit status 0 allows the tag to be
392 created. Non-zero status will cause the tag to fail. ID of
393 changeset to tag is in $HG_NODE. Name of tag is in $HG_TAG. Tag
401 Run before creating a tag. Exit status 0 allows the tag to be
402 created. Non-zero status will cause the tag to fail. ID of
403 changeset to tag is in $HG_NODE. Name of tag is in $HG_TAG. Tag
394 404 is local if $HG_LOCAL=1, in repo if $HG_LOCAL=0.
395 405 pretxnchangegroup;;
396 406 Run after a changegroup has been added via push, pull or unbundle,
397 but before the transaction has been committed. Changegroup is
398 visible to hook program. This lets you validate incoming changes
399 before accepting them. Passed the ID of the first new changeset
400 in $HG_NODE. Exit status 0 allows the transaction to commit.
407 but before the transaction has been committed. Changegroup is
408 visible to hook program. This lets you validate incoming changes
409 before accepting them. Passed the ID of the first new changeset
410 in $HG_NODE. Exit status 0 allows the transaction to commit.
401 411 Non-zero status will cause the transaction to be rolled back and
402 the push, pull or unbundle will fail. URL that was source of
412 the push, pull or unbundle will fail. URL that was source of
403 413 changes is in $HG_URL.
404 414 pretxncommit;;
405 415 Run after a changeset has been created but the transaction not yet
406 committed. Changeset is visible to hook program. This lets you
407 validate commit message and changes. Exit status 0 allows the
408 commit to proceed. Non-zero status will cause the transaction to
409 be rolled back. ID of changeset is in $HG_NODE. Parent changeset
416 committed. Changeset is visible to hook program. This lets you
417 validate commit message and changes. Exit status 0 allows the
418 commit to proceed. Non-zero status will cause the transaction to
419 be rolled back. ID of changeset is in $HG_NODE. Parent changeset
410 420 IDs are in $HG_PARENT1 and $HG_PARENT2.
411 421 preupdate;;
412 Run before updating the working directory. Exit status 0 allows
413 the update to proceed. Non-zero status will prevent the update.
414 Changeset ID of first new parent is in $HG_PARENT1. If merge, ID
422 Run before updating the working directory. Exit status 0 allows
423 the update to proceed. Non-zero status will prevent the update.
424 Changeset ID of first new parent is in $HG_PARENT1. If merge, ID
415 425 of second new parent is in $HG_PARENT2.
416 426 tag;;
417 Run after a tag is created. ID of tagged changeset is in
418 $HG_NODE. Name of tag is in $HG_TAG. Tag is local if
427 Run after a tag is created. ID of tagged changeset is in
428 $HG_NODE. Name of tag is in $HG_TAG. Tag is local if
419 429 $HG_LOCAL=1, in repo if $HG_LOCAL=0.
420 430 update;;
421 Run after updating the working directory. Changeset ID of first
422 new parent is in $HG_PARENT1. If merge, ID of second new parent
423 is in $HG_PARENT2. If update succeeded, $HG_ERROR=0. If update
431 Run after updating the working directory. Changeset ID of first
432 new parent is in $HG_PARENT1. If merge, ID of second new parent
433 is in $HG_PARENT2. If update succeeded, $HG_ERROR=0. If update
424 434 failed (e.g. because conflicts not resolved), $HG_ERROR=1.
425 435
426 436 Note: it is generally better to use standard hooks rather than the
@@ -438,16 +448,17 b' hooks::'
438 448
439 449 hookname = python:modulename.submodule.callable
440 450
441 Python hooks are run within the Mercurial process. Each hook is
451 Python hooks are run within the Mercurial process. Each hook is
442 452 called with at least three keyword arguments: a ui object (keyword
443 453 "ui"), a repository object (keyword "repo"), and a "hooktype"
444 keyword that tells what kind of hook is used. Arguments listed as
454 keyword that tells what kind of hook is used. Arguments listed as
445 455 environment variables above are passed as keyword arguments, with no
446 456 "HG_" prefix, and names in lower case.
447 457
448 458 If a Python hook returns a "true" value or raises an exception, this
449 459 is treated as failure of the hook.
450 460
461 [[http_proxy]]
451 462 http_proxy::
452 463 Used to access web-based Mercurial repositories through a HTTP
453 464 proxy.
@@ -455,68 +466,72 b' http_proxy::'
455 466 Host name and (optional) port of the proxy server, for example
456 467 "myproxy:8000".
457 468 no;;
458 Optional. Comma-separated list of host names that should bypass
469 Optional. Comma-separated list of host names that should bypass
459 470 the proxy.
460 471 passwd;;
461 Optional. Password to authenticate with at the proxy server.
472 Optional. Password to authenticate with at the proxy server.
462 473 user;;
463 Optional. User name to authenticate with at the proxy server.
474 Optional. User name to authenticate with at the proxy server.
464 475
476 [[smtp]]
465 477 smtp::
466 478 Configuration for extensions that need to send email messages.
467 479 host;;
468 480 Host name of mail server, e.g. "mail.example.com".
469 481 port;;
470 Optional. Port to connect to on mail server. Default: 25.
482 Optional. Port to connect to on mail server. Default: 25.
471 483 tls;;
472 Optional. Whether to connect to mail server using TLS. True or
473 False. Default: False.
484 Optional. Whether to connect to mail server using TLS. True or
485 False. Default: False.
474 486 username;;
475 Optional. User name to authenticate to SMTP server with.
487 Optional. User name to authenticate to SMTP server with.
476 488 If username is specified, password must also be specified.
477 489 Default: none.
478 490 password;;
479 Optional. Password to authenticate to SMTP server with.
491 Optional. Password to authenticate to SMTP server with.
480 492 If username is specified, password must also be specified.
481 493 Default: none.
482 494 local_hostname;;
483 Optional. It's the hostname that the sender can use to identify itself
495 Optional. It's the hostname that the sender can use to identify itself
484 496 to the MTA.
485 497
498 [[paths]]
486 499 paths::
487 Assigns symbolic names to repositories. The left side is the
500 Assigns symbolic names to repositories. The left side is the
488 501 symbolic name, and the right gives the directory or URL that is the
489 location of the repository. Default paths can be declared by
502 location of the repository. Default paths can be declared by
490 503 setting the following entries.
491 504 default;;
492 505 Directory or URL to use when pulling if no source is specified.
493 506 Default is set to repository from which the current repository
494 507 was cloned.
495 508 default-push;;
496 Optional. Directory or URL to use when pushing if no destination
509 Optional. Directory or URL to use when pushing if no destination
497 510 is specified.
498 511
512 [[server]]
499 513 server::
500 514 Controls generic server settings.
501 515 uncompressed;;
502 516 Whether to allow clients to clone a repo using the uncompressed
503 streaming protocol. This transfers about 40% more data than a
517 streaming protocol. This transfers about 40% more data than a
504 518 regular clone, but uses less memory and CPU on both server and
505 client. Over a LAN (100Mbps or better) or a very fast WAN, an
519 client. Over a LAN (100Mbps or better) or a very fast WAN, an
506 520 uncompressed streaming clone is a lot faster (~10x) than a regular
507 clone. Over most WAN connections (anything slower than about
521 clone. Over most WAN connections (anything slower than about
508 522 6Mbps), uncompressed streaming is slower, because of the extra
509 data transfer overhead. Default is False.
523 data transfer overhead. Default is False.
510 524
525 [[trusted]]
511 526 trusted::
512 527 For security reasons, Mercurial will not use the settings in
513 528 the .hg/hgrc file from a repository if it doesn't belong to a
514 trusted user or to a trusted group. The main exception is the
529 trusted user or to a trusted group. The main exception is the
515 530 web interface, which automatically uses some safe settings, since
516 531 it's common to serve repositories from different users.
517 532
518 This section specifies what users and groups are trusted. The
519 current user is always trusted. To trust everybody, list a user
533 This section specifies what users and groups are trusted. The
534 current user is always trusted. To trust everybody, list a user
520 535 or a group with name "*".
521 536
522 537 users;;
@@ -524,6 +539,7 b' trusted::'
524 539 groups;;
525 540 Comma-separated list of trusted groups.
526 541
542 [[ui]]
527 543 ui::
528 544 User interface controls.
529 545 archivemeta;;
@@ -531,13 +547,19 b' ui::'
531 547 (hashes for the repository base and for tip) in archives created by
532 548 the hg archive command or downloaded via hgweb.
533 549 Default is true.
550 askusername;;
551 Whether to prompt for a username when committing. If True, and
552 neither $HGUSER nor $EMAIL has been specified, then the user will
553 be prompted to enter a username. If no username is entered, the
554 default USER@HOST is used instead.
555 Default is False.
534 556 debug;;
535 Print debugging information. True or False. Default is False.
557 Print debugging information. True or False. Default is False.
536 558 editor;;
537 The editor to use during a commit. Default is $EDITOR or "vi".
559 The editor to use during a commit. Default is $EDITOR or "vi".
538 560 fallbackencoding;;
539 561 Encoding to try if it's not possible to decode the changelog using
540 UTF-8. Default is ISO-8859-1.
562 UTF-8. Default is ISO-8859-1.
541 563 ignore;;
542 564 A file to read per-user ignore patterns from. This file should be in
543 565 the same format as a repository-wide .hgignore file. This option
@@ -546,7 +568,7 b' ui::'
546 568 "ignore.other = ~/.hgignore2". For details of the ignore file
547 569 format, see the hgignore(5) man page.
548 570 interactive;;
549 Allow to prompt the user. True or False. Default is True.
571 Allow to prompt the user. True or False. Default is True.
550 572 logtemplate;;
551 573 Template string for commands that print changesets.
552 574 merge;;
@@ -563,18 +585,19 b' ui::'
563 585 fail to merge
564 586
565 587 See the merge-tools section for more information on configuring tools.
588
566 589 patch;;
567 590 command to use to apply patches. Look for 'gpatch' or 'patch' in PATH if
568 591 unset.
569 592 quiet;;
570 Reduce the amount of output printed. True or False. Default is False.
593 Reduce the amount of output printed. True or False. Default is False.
571 594 remotecmd;;
572 595 remote command to use for clone/push/pull operations. Default is 'hg'.
573 596 report_untrusted;;
574 597 Warn if a .hg/hgrc file is ignored due to not being owned by a
575 trusted user or group. True or False. Default is True.
598 trusted user or group. True or False. Default is True.
576 599 slash;;
577 Display paths using a slash ("/") as the path separator. This only
600 Display paths using a slash ("/") as the path separator. This only
578 601 makes a difference on systems where the default path separator is not
579 602 the slash character (e.g. Windows uses the backslash character ("\")).
580 603 Default is False.
@@ -582,7 +605,7 b' ui::'
582 605 command to use for SSH connections. Default is 'ssh'.
583 606 strict;;
584 607 Require exact command names, instead of allowing unambiguous
585 abbreviations. True or False. Default is False.
608 abbreviations. True or False. Default is False.
586 609 style;;
587 610 Name of style to use for command output.
588 611 timeout;;
@@ -591,14 +614,15 b' ui::'
591 614 username;;
592 615 The committer of a changeset created when running "commit".
593 616 Typically a person's name and email address, e.g. "Fred Widget
594 <fred@example.com>". Default is $EMAIL or username@hostname.
617 <fred@example.com>". Default is $EMAIL or username@hostname.
595 618 If the username in hgrc is empty, it has to be specified manually or
596 619 in a different hgrc file (e.g. $HOME/.hgrc, if the admin set "username ="
597 620 in the system hgrc).
598 621 verbose;;
599 Increase the amount of output printed. True or False. Default is False.
622 Increase the amount of output printed. True or False. Default is False.
600 623
601 624
625 [[web]]
602 626 web::
603 627 Web interface configuration.
604 628 accesslog;;
@@ -617,9 +641,9 b' web::'
617 641 allowpull;;
618 642 Whether to allow pulling from the repository. Default is true.
619 643 allow_push;;
620 Whether to allow pushing to the repository. If empty or not set,
621 push is not allowed. If the special value "*", any remote user
622 can push, including unauthenticated users. Otherwise, the remote
644 Whether to allow pushing to the repository. If empty or not set,
645 push is not allowed. If the special value "*", any remote user
646 can push, including unauthenticated users. Otherwise, the remote
623 647 user must have been authenticated, and the authenticated user name
624 648 must be present in this list (separated by whitespace or ",").
625 649 The contents of the allow_push list are examined after the
@@ -635,11 +659,11 b' web::'
635 659 Name or email address of the person in charge of the repository.
636 660 Defaults to ui.username or $EMAIL or "unknown" if unset or empty.
637 661 deny_push;;
638 Whether to deny pushing to the repository. If empty or not set,
639 push is not denied. If the special value "*", all remote users
640 are denied push. Otherwise, unauthenticated users are all denied,
662 Whether to deny pushing to the repository. If empty or not set,
663 push is not denied. If the special value "*", all remote users
664 are denied push. Otherwise, unauthenticated users are all denied,
641 665 and any authenticated user name present in this list (separated by
642 whitespace or ",") is also denied. The contents of the deny_push
666 whitespace or ",") is also denied. The contents of the deny_push
643 667 list are examined before the allow_push list.
644 668 description;;
645 669 Textual description of the repository's purpose or contents.
@@ -666,7 +690,7 b' web::'
666 690 Prefix path to serve from. Default is '' (server root).
667 691 push_ssl;;
668 692 Whether to require that inbound pushes be transported over SSL to
669 prevent password sniffing. Default is true.
693 prevent password sniffing. Default is true.
670 694 staticurl;;
671 695 Base URL to use for static files. If unset, static files (e.g.
672 696 the hgicon.png favicon) will be served by the CGI script itself.
@@ -46,79 +46,45 b''
46 46 # ** = user6
47 47
48 48 from mercurial.i18n import _
49 from mercurial.node import bin, short
50 49 from mercurial import util
51 50 import getpass
52 51
53 class checker(object):
54 '''acl checker.'''
55
56 def buildmatch(self, key):
57 '''return tuple of (match function, list enabled).'''
58 if not self.ui.has_section(key):
59 self.ui.debug(_('acl: %s not enabled\n') % key)
60 return None, False
61
62 thisuser = self.getuser()
63 pats = [pat for pat, users in self.ui.configitems(key)
64 if thisuser in users.replace(',', ' ').split()]
65 self.ui.debug(_('acl: %s enabled, %d entries for user %s\n') %
66 (key, len(pats), thisuser))
67 if pats:
68 match = util.matcher(self.repo.root, names=pats)[1]
69 else:
70 match = util.never
71 return match, True
72
73 def getuser(self):
74 '''return name of authenticated user.'''
75 return self.user
52 def buildmatch(ui, repo, user, key):
53 '''return tuple of (match function, list enabled).'''
54 if not ui.has_section(key):
55 ui.debug(_('acl: %s not enabled\n') % key)
56 return None
76 57
77 def __init__(self, ui, repo):
78 self.ui = ui
79 self.repo = repo
80 self.user = getpass.getuser()
81 cfg = self.ui.config('acl', 'config')
82 if cfg:
83 self.ui.readsections(cfg, 'acl.allow', 'acl.deny')
84 self.allow, self.allowable = self.buildmatch('acl.allow')
85 self.deny, self.deniable = self.buildmatch('acl.deny')
86
87 def skipsource(self, source):
88 '''true if incoming changes from this source should be skipped.'''
89 ok_sources = self.ui.config('acl', 'sources', 'serve').split()
90 return source not in ok_sources
91
92 def check(self, node):
93 '''return if access allowed, raise exception if not.'''
94 files = self.repo.changectx(node).files()
95 if self.deniable:
96 for f in files:
97 if self.deny(f):
98 self.ui.debug(_('acl: user %s denied on %s\n') %
99 (self.getuser(), f))
100 raise util.Abort(_('acl: access denied for changeset %s') %
101 short(node))
102 if self.allowable:
103 for f in files:
104 if not self.allow(f):
105 self.ui.debug(_('acl: user %s not allowed on %s\n') %
106 (self.getuser(), f))
107 raise util.Abort(_('acl: access denied for changeset %s') %
108 short(node))
109 self.ui.debug(_('acl: allowing changeset %s\n') % short(node))
58 pats = [pat for pat, users in ui.configitems(key)
59 if user in users.replace(',', ' ').split()]
60 ui.debug(_('acl: %s enabled, %d entries for user %s\n') %
61 (key, len(pats), user))
62 if pats:
63 return util.matcher(repo.root, names=pats)[1]
64 return util.never
110 65
111 66 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
112 67 if hooktype != 'pretxnchangegroup':
113 68 raise util.Abort(_('config error - hook type "%s" cannot stop '
114 69 'incoming changesets') % hooktype)
115
116 c = checker(ui, repo)
117 if c.skipsource(source):
70 if source not in ui.config('acl', 'sources', 'serve').split():
118 71 ui.debug(_('acl: changes have source "%s" - skipping\n') % source)
119 72 return
120 73
121 start = repo.changelog.rev(bin(node))
122 end = repo.changelog.count()
123 for rev in xrange(start, end):
124 c.check(repo.changelog.node(rev))
74 user = getpass.getuser()
75 cfg = ui.config('acl', 'config')
76 if cfg:
77 ui.readsections(cfg, 'acl.allow', 'acl.deny')
78 allow = buildmatch(ui, repo, user, 'acl.allow')
79 deny = buildmatch(ui, repo, user, 'acl.deny')
80
81 for rev in xrange(repo[node], len(repo)):
82 ctx = repo[rev]
83 for f in ctx.files():
84 if deny and deny(f):
85 ui.debug(_('acl: user %s denied on %s\n') % (user, f))
86 raise util.Abort(_('acl: access denied for changeset %s') % ctx)
87 if allow and not allow(f):
88 ui.debug(_('acl: user %s not allowed on %s\n') % (user, f))
89 raise util.Abort(_('acl: access denied for changeset %s') % ctx)
90 ui.debug(_('acl: allowing changeset %s\n') % ctx)
@@ -55,7 +55,7 b''
55 55 from mercurial.i18n import _
56 56 from mercurial.node import short
57 57 from mercurial import cmdutil, templater, util
58 import os, re, time
58 import re, time
59 59
60 60 MySQLdb = None
61 61
@@ -99,9 +99,7 b' class bugzilla_2_16(object):'
99 99 def filter_real_bug_ids(self, ids):
100 100 '''filter not-existing bug ids from list.'''
101 101 self.run('select bug_id from bugs where bug_id in %s' % buglist(ids))
102 ids = [c[0] for c in self.cursor.fetchall()]
103 ids.sort()
104 return ids
102 return util.sort([c[0] for c in self.cursor.fetchall()])
105 103
106 104 def filter_unknown_bug_ids(self, node, ids):
107 105 '''filter bug ids from list that already refer to this changeset.'''
@@ -114,9 +112,7 b' class bugzilla_2_16(object):'
114 112 self.ui.status(_('bug %d already knows about changeset %s\n') %
115 113 (id, short(node)))
116 114 unknown.pop(id, None)
117 ids = unknown.keys()
118 ids.sort()
119 return ids
115 return util.sort(unknown.keys())
120 116
121 117 def notify(self, ids):
122 118 '''tell bugzilla to send mail.'''
@@ -127,7 +123,7 b' class bugzilla_2_16(object):'
127 123 cmd = self.ui.config('bugzilla', 'notify',
128 124 'cd /var/www/html/bugzilla && '
129 125 './processmail %s nobody@nowhere.com') % id
130 fp = os.popen('(%s) 2>&1' % cmd)
126 fp = util.popen('(%s) 2>&1' % cmd)
131 127 out = fp.read()
132 128 ret = fp.close()
133 129 if ret:
@@ -300,7 +296,7 b' def hook(ui, repo, hooktype, node=None, '
300 296 hooktype)
301 297 try:
302 298 bz = bugzilla(ui, repo)
303 ctx = repo.changectx(node)
299 ctx = repo[node]
304 300 ids = bz.find_bug_ids(ctx)
305 301 if ids:
306 302 for id in ids:
@@ -25,7 +25,7 b' def children(ui, repo, file_=None, **opt'
25 25 if file_:
26 26 ctx = repo.filectx(file_, changeid=rev)
27 27 else:
28 ctx = repo.changectx(rev)
28 ctx = repo[rev]
29 29
30 30 displayer = cmdutil.show_changeset(ui, repo, opts)
31 31 for node in [cp.node() for cp in ctx.children()]:
@@ -4,15 +4,10 b''
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 #
8 #
9 # Aliases map file format is simple one alias per line in the following
10 # format:
11 #
12 # <alias email> <actual email>
7 '''allow graphing the number of lines changed per contributor'''
13 8
14 9 from mercurial.i18n import gettext as _
15 from mercurial import mdiff, cmdutil, util, node
10 from mercurial import patch, cmdutil, util, node
16 11 import os, sys
17 12
18 13 def get_tty_width():
@@ -36,98 +31,41 b' def get_tty_width():'
36 31 pass
37 32 return 80
38 33
39 def __gather(ui, repo, node1, node2):
40 def dirtywork(f, mmap1, mmap2):
41 lines = 0
42
43 to = mmap1 and repo.file(f).read(mmap1[f]) or None
44 tn = mmap2 and repo.file(f).read(mmap2[f]) or None
45
46 diff = mdiff.unidiff(to, "", tn, "", f, f).split("\n")
47
48 for line in diff:
49 if not line:
50 continue # skip EOF
51 if line.startswith(" "):
52 continue # context line
53 if line.startswith("--- ") or line.startswith("+++ "):
54 continue # begining of diff
55 if line.startswith("@@ "):
56 continue # info line
57
58 # changed lines
59 lines += 1
60
61 return lines
62
63 ##
64
65 lines = 0
66
67 changes = repo.status(node1, node2, None, util.always)[:5]
68
69 modified, added, removed, deleted, unknown = changes
70
71 who = repo.changelog.read(node2)[1]
72 who = util.email(who) # get the email of the person
73
74 mmap1 = repo.manifest.read(repo.changelog.read(node1)[0])
75 mmap2 = repo.manifest.read(repo.changelog.read(node2)[0])
76 for f in modified:
77 lines += dirtywork(f, mmap1, mmap2)
78
79 for f in added:
80 lines += dirtywork(f, None, mmap2)
81
82 for f in removed:
83 lines += dirtywork(f, mmap1, None)
84
85 for f in deleted:
86 lines += dirtywork(f, mmap1, mmap2)
87
88 for f in unknown:
89 lines += dirtywork(f, mmap1, mmap2)
90
91 return (who, lines)
92
93 def gather_stats(ui, repo, amap, revs=None, progress=False):
34 def countrevs(ui, repo, amap, revs, progress=False):
94 35 stats = {}
95
96 cl = repo.changelog
97
36 count = pct = 0
98 37 if not revs:
99 revs = range(0, cl.count())
100
101 nr_revs = len(revs)
102 cur_rev = 0
38 revs = range(len(repo))
103 39
104 40 for rev in revs:
105 cur_rev += 1 # next revision
106
107 node2 = cl.node(rev)
108 node1 = cl.parents(node2)[0]
109
110 if cl.parents(node2)[1] != node.nullid:
41 ctx2 = repo[rev]
42 parents = ctx2.parents()
43 if len(parents) > 1:
111 44 ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
112 45 continue
113 46
114 who, lines = __gather(ui, repo, node1, node2)
47 ctx1 = parents[0]
48 lines = 0
49 ui.pushbuffer()
50 patch.diff(repo, ctx1.node(), ctx2.node())
51 diff = ui.popbuffer()
115 52
116 # remap the owner if possible
117 if who in amap:
118 ui.note("using '%s' alias for '%s'\n" % (amap[who], who))
119 who = amap[who]
53 for l in diff.split('\n'):
54 if (l.startswith("+") and not l.startswith("+++ ") or
55 l.startswith("-") and not l.startswith("--- ")):
56 lines += 1
120 57
121 if not who in stats:
122 stats[who] = 0
123 stats[who] += lines
124
125 ui.note("rev %d: %d lines by %s\n" % (rev, lines, who))
58 user = util.email(ctx2.user())
59 user = amap.get(user, user) # remap
60 stats[user] = stats.get(user, 0) + lines
61 ui.debug("rev %d: %d lines by %s\n" % (rev, lines, user))
126 62
127 63 if progress:
128 nr_revs = max(nr_revs, 1)
129 if int(100.0*(cur_rev - 1)/nr_revs) < int(100.0*cur_rev/nr_revs):
130 ui.write("\rGenerating stats: %d%%" % (int(100.0*cur_rev/nr_revs),))
64 count += 1
65 newpct = int(100.0 * count / max(len(revs), 1))
66 if pct < newpct:
67 pct = newpct
68 ui.write("\rGenerating stats: %d%%" % pct)
131 69 sys.stdout.flush()
132 70
133 71 if progress:
@@ -137,64 +75,39 b' def gather_stats(ui, repo, amap, revs=No'
137 75 return stats
138 76
139 77 def churn(ui, repo, **opts):
140 "Graphs the number of lines changed"
78 '''graphs the number of lines changed
79
80 The map file format used to specify aliases is fairly simple:
81
82 <alias email> <actual email>'''
141 83
142 84 def pad(s, l):
143 if len(s) < l:
144 return s + " " * (l-len(s))
145 return s[0:l]
146
147 def graph(n, maximum, width, char):
148 maximum = max(1, maximum)
149 n = int(n * width / float(maximum))
150
151 return char * (n)
152
153 def get_aliases(f):
154 aliases = {}
155
156 for l in f.readlines():
157 l = l.strip()
158 alias, actual = l.split()
159 aliases[alias] = actual
160
161 return aliases
85 return (s + " " * l)[:l]
162 86
163 87 amap = {}
164 88 aliases = opts.get('aliases')
165 89 if aliases:
166 try:
167 f = open(aliases,"r")
168 except OSError, e:
169 print "Error: " + e
170 return
90 for l in open(aliases, "r"):
91 l = l.strip()
92 alias, actual = l.split()
93 amap[alias] = actual
171 94
172 amap = get_aliases(f)
173 f.close()
174
175 revs = [int(r) for r in cmdutil.revrange(repo, opts['rev'])]
176 revs.sort()
177 stats = gather_stats(ui, repo, amap, revs, opts.get('progress'))
95 revs = util.sort([int(r) for r in cmdutil.revrange(repo, opts['rev'])])
96 stats = countrevs(ui, repo, amap, revs, opts.get('progress'))
97 if not stats:
98 return
178 99
179 # make a list of tuples (name, lines) and sort it in descending order
180 ordered = stats.items()
181 if not ordered:
182 return
183 ordered.sort(lambda x, y: cmp(y[1], x[1]))
184 max_churn = ordered[0][1]
100 stats = util.sort([(-l, u, l) for u,l in stats.items()])
101 maxchurn = float(max(1, stats[0][2]))
102 maxuser = max([len(u) for k, u, l in stats])
185 103
186 tty_width = get_tty_width()
187 ui.note(_("assuming %i character terminal\n") % tty_width)
188 tty_width -= 1
189
190 max_user_width = max([len(user) for user, churn in ordered])
104 ttywidth = get_tty_width()
105 ui.debug(_("assuming %i character terminal\n") % ttywidth)
106 width = ttywidth - maxuser - 2 - 6 - 2 - 2
191 107
192 graph_width = tty_width - max_user_width - 1 - 6 - 2 - 2
193
194 for user, churn in ordered:
195 print "%s %6d %s" % (pad(user, max_user_width),
196 churn,
197 graph(churn, max_churn, graph_width, '*'))
108 for k, user, churn in stats:
109 print "%s %6d %s" % (pad(user, maxuser), churn,
110 "*" * int(churn * width / maxchurn))
198 111
199 112 cmdtable = {
200 113 "churn":
@@ -4,6 +4,7 b''
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 '''converting foreign VCS repositories to Mercurial'''
7 8
8 9 import convcmd
9 10 from mercurial import commands
@@ -85,6 +86,50 b' def convert(ui, src, dest=None, revmapfi'
85 86 --config convert.hg.saverev=True (boolean)
86 87 allow target to preserve source revision ID
87 88
89 CVS Source
90 ----------
91
92 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
93 to indicate the starting point of what will be converted. Direct
94 access to the repository files is not needed, unless of course
95 the repository is :local:. The conversion uses the top level
96 directory in the sandbox to find the CVS repository, and then uses
97 CVS rlog commands to find files to convert. This means that unless
98 a filemap is given, all files under the starting directory will be
99 converted, and that any directory reorganisation in the CVS
100 sandbox is ignored.
101
102 Because CVS does not have changesets, it is necessary to collect
103 individual commits to CVS and merge them into changesets. CVS source
104 can use the external 'cvsps' program (this is a legacy option and may
105 be removed in future) or use its internal changeset merging code.
106 External cvsps is default, and options may be passed to it by setting
107 --config convert.cvsps='cvsps -A -u --cvs-direct -q'
108 The options shown are the defaults.
109
110 Internal cvsps is selected by setting
111 --config convert.cvsps=builtin
112 and has a few more configurable options:
113 --config convert.cvsps.fuzz=60 (integer)
114 Specify the maximum time (in seconds) that is allowed between
115 commits with identical user and log message in a single
116 changeset. When very large files were checked in as part
117 of a changeset then the default may not be long enough.
118 --config convert.cvsps.mergeto='{{mergetobranch ([-\w]+)}}'
119 Specify a regular expression to which commit log messages are
120 matched. If a match occurs, then the conversion process will
121 insert a dummy revision merging the branch on which this log
122 message occurs to the branch indicated in the regex.
123 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\w]+)}}'
124 Specify a regular expression to which commit log messages are
125 matched. If a match occurs, then the conversion process will
126 add the most recent revision on the branch indicated in the
127 regex as the second parent of the changeset.
128
129 The hgext/convert/cvsps wrapper script allows the builtin changeset
130 merging code to be run without doing a conversion. Its parameters and
131 output are similar to that of cvsps 2.1.
132
88 133 Subversion Source
89 134 -----------------
90 135
@@ -153,26 +153,18 b' class converter_sink(object):'
153 153 mapping equivalent authors identifiers for each system."""
154 154 return None
155 155
156 def putfile(self, f, e, data):
157 """Put file for next putcommit().
158 f: path to file
159 e: '', 'x', or 'l' (regular file, executable, or symlink)
160 data: file contents"""
161 raise NotImplementedError()
162
163 def delfile(self, f):
164 """Delete file for next putcommit().
165 f: path to file"""
166 raise NotImplementedError()
167
168 def putcommit(self, files, parents, commit):
156 def putcommit(self, files, copies, parents, commit, source):
169 157 """Create a revision with all changed files listed in 'files'
170 158 and having listed parents. 'commit' is a commit object containing
171 159 at a minimum the author, date, and message for this changeset.
172 Called after putfile() and delfile() calls. Note that the sink
173 repository is not told to update itself to a particular revision
174 (or even what that revision would be) before it receives the
175 file data."""
160 'files' is a list of (path, version) tuples, 'copies'is a dictionary
161 mapping destinations to sources, and 'source' is the source repository.
162 Only getfile() and getmode() should be called on 'source'.
163
164 Note that the sink repository is not told to update itself to
165 a particular revision (or even what that revision would be)
166 before it receives the file data.
167 """
176 168 raise NotImplementedError()
177 169
178 170 def puttags(self, tags):
@@ -181,7 +173,7 b' class converter_sink(object):'
181 173 raise NotImplementedError()
182 174
183 175 def setbranch(self, branch, pbranches):
184 """Set the current branch name. Called before the first putfile
176 """Set the current branch name. Called before the first putcommit
185 177 on the branch.
186 178 branch: branch name for subsequent commits
187 179 pbranches: (converted parent revision, parent branch) tuples"""
@@ -221,8 +221,6 b' class converter(object):'
221 221
222 222 def copy(self, rev):
223 223 commit = self.commitcache[rev]
224 do_copies = hasattr(self.dest, 'copyfile')
225 filenames = []
226 224
227 225 changes = self.source.getchanges(rev)
228 226 if isinstance(changes, basestring):
@@ -241,21 +239,6 b' class converter(object):'
241 239 pbranches.append((self.map[prev],
242 240 self.commitcache[prev].branch))
243 241 self.dest.setbranch(commit.branch, pbranches)
244 for f, v in files:
245 filenames.append(f)
246 try:
247 data = self.source.getfile(f, v)
248 except IOError, inst:
249 self.dest.delfile(f)
250 else:
251 e = self.source.getmode(f, v)
252 self.dest.putfile(f, e, data)
253 if do_copies:
254 if f in copies:
255 copyf = copies[f]
256 # Merely marks that a copy happened.
257 self.dest.copyfile(copyf, f)
258
259 242 try:
260 243 parents = self.splicemap[rev].replace(',', ' ').split()
261 244 self.ui.status('spliced in %s as parents of %s\n' %
@@ -263,7 +246,7 b' class converter(object):'
263 246 parents = [self.map.get(p, p) for p in parents]
264 247 except KeyError:
265 248 parents = [b[0] for b in pbranches]
266 newnode = self.dest.putcommit(filenames, parents, commit)
249 newnode = self.dest.putcommit(files, copies, parents, commit, self.source)
267 250 self.source.converted(rev, newnode)
268 251 self.map[rev] = newnode
269 252
@@ -3,8 +3,10 b''
3 3 import os, locale, re, socket
4 4 from cStringIO import StringIO
5 5 from mercurial import util
6 from mercurial.i18n import _
6 7
7 8 from common import NoRepo, commit, converter_source, checktool
9 import cvsps
8 10
9 11 class convert_cvs(converter_source):
10 12 def __init__(self, ui, path, rev=None):
@@ -14,10 +16,13 b' class convert_cvs(converter_source):'
14 16 if not os.path.exists(cvs):
15 17 raise NoRepo("%s does not look like a CVS checkout" % path)
16 18
19 checktool('cvs')
17 20 self.cmd = ui.config('convert', 'cvsps', 'cvsps -A -u --cvs-direct -q')
18 21 cvspsexe = self.cmd.split(None, 1)[0]
19 for tool in (cvspsexe, 'cvs'):
20 checktool(tool)
22 self.builtin = cvspsexe == 'builtin'
23
24 if not self.builtin:
25 checktool(cvspsexe)
21 26
22 27 self.changeset = {}
23 28 self.files = {}
@@ -28,10 +33,11 b' class convert_cvs(converter_source):'
28 33 self.cvsroot = file(os.path.join(cvs, "Root")).read()[:-1]
29 34 self.cvsrepo = file(os.path.join(cvs, "Repository")).read()[:-1]
30 35 self.encoding = locale.getpreferredencoding()
31 self._parse()
36
37 self._parse(ui)
32 38 self._connect()
33 39
34 def _parse(self):
40 def _parse(self, ui):
35 41 if self.changeset:
36 42 return
37 43
@@ -56,80 +62,114 b' class convert_cvs(converter_source):'
56 62 id = None
57 63 state = 0
58 64 filerevids = {}
59 for l in util.popen(cmd):
60 if state == 0: # header
61 if l.startswith("PatchSet"):
62 id = l[9:-2]
63 if maxrev and int(id) > maxrev:
64 # ignore everything
65 state = 3
66 elif l.startswith("Date"):
67 date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"])
68 date = util.datestr(date)
69 elif l.startswith("Branch"):
70 branch = l[8:-1]
71 self.parent[id] = self.lastbranch.get(branch, 'bad')
72 self.lastbranch[branch] = id
73 elif l.startswith("Ancestor branch"):
74 ancestor = l[17:-1]
75 # figure out the parent later
76 self.parent[id] = self.lastbranch[ancestor]
77 elif l.startswith("Author"):
78 author = self.recode(l[8:-1])
79 elif l.startswith("Tag:") or l.startswith("Tags:"):
80 t = l[l.index(':')+1:]
81 t = [ut.strip() for ut in t.split(',')]
82 if (len(t) > 1) or (t[0] and (t[0] != "(none)")):
83 self.tags.update(dict.fromkeys(t, id))
84 elif l.startswith("Log:"):
85 # switch to gathering log
86 state = 1
87 log = ""
88 elif state == 1: # log
89 if l == "Members: \n":
90 # switch to gathering members
91 files = {}
92 oldrevs = []
93 log = self.recode(log[:-1])
94 state = 2
95 else:
96 # gather log
97 log += l
98 elif state == 2: # members
99 if l == "\n": # start of next entry
100 state = 0
101 p = [self.parent[id]]
102 if id == "1":
103 p = []
104 if branch == "HEAD":
105 branch = ""
106 if branch:
107 latest = None
108 # the last changeset that contains a base
109 # file is our parent
110 for r in oldrevs:
111 latest = max(filerevids.get(r, None), latest)
112 if latest:
113 p = [latest]
65
66 if self.builtin:
67 # builtin cvsps code
68 ui.status(_('using builtin cvsps\n'))
69
70 db = cvsps.createlog(ui, cache='update')
71 db = cvsps.createchangeset(ui, db,
72 fuzz=int(ui.config('convert', 'cvsps.fuzz', 60)),
73 mergeto=ui.config('convert', 'cvsps.mergeto', None),
74 mergefrom=ui.config('convert', 'cvsps.mergefrom', None))
75
76 for cs in db:
77 if maxrev and cs.id>maxrev:
78 break
79 id = str(cs.id)
80 cs.author = self.recode(cs.author)
81 self.lastbranch[cs.branch] = id
82 cs.comment = self.recode(cs.comment)
83 date = util.datestr(cs.date)
84 self.tags.update(dict.fromkeys(cs.tags, id))
85
86 files = {}
87 for f in cs.entries:
88 files[f.file] = "%s%s" % ('.'.join([str(x) for x in f.revision]),
89 ['', '(DEAD)'][f.dead])
114 90
115 # add current commit to set
116 c = commit(author=author, date=date, parents=p,
117 desc=log, branch=branch)
118 self.changeset[id] = c
119 self.files[id] = files
120 else:
121 colon = l.rfind(':')
122 file = l[1:colon]
123 rev = l[colon+1:-2]
124 oldrev, rev = rev.split("->")
125 files[file] = rev
91 # add current commit to set
92 c = commit(author=cs.author, date=date,
93 parents=[str(p.id) for p in cs.parents],
94 desc=cs.comment, branch=cs.branch or '')
95 self.changeset[id] = c
96 self.files[id] = files
97 else:
98 # external cvsps
99 for l in util.popen(cmd):
100 if state == 0: # header
101 if l.startswith("PatchSet"):
102 id = l[9:-2]
103 if maxrev and int(id) > maxrev:
104 # ignore everything
105 state = 3
106 elif l.startswith("Date"):
107 date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"])
108 date = util.datestr(date)
109 elif l.startswith("Branch"):
110 branch = l[8:-1]
111 self.parent[id] = self.lastbranch.get(branch, 'bad')
112 self.lastbranch[branch] = id
113 elif l.startswith("Ancestor branch"):
114 ancestor = l[17:-1]
115 # figure out the parent later
116 self.parent[id] = self.lastbranch[ancestor]
117 elif l.startswith("Author"):
118 author = self.recode(l[8:-1])
119 elif l.startswith("Tag:") or l.startswith("Tags:"):
120 t = l[l.index(':')+1:]
121 t = [ut.strip() for ut in t.split(',')]
122 if (len(t) > 1) or (t[0] and (t[0] != "(none)")):
123 self.tags.update(dict.fromkeys(t, id))
124 elif l.startswith("Log:"):
125 # switch to gathering log
126 state = 1
127 log = ""
128 elif state == 1: # log
129 if l == "Members: \n":
130 # switch to gathering members
131 files = {}
132 oldrevs = []
133 log = self.recode(log[:-1])
134 state = 2
135 else:
136 # gather log
137 log += l
138 elif state == 2: # members
139 if l == "\n": # start of next entry
140 state = 0
141 p = [self.parent[id]]
142 if id == "1":
143 p = []
144 if branch == "HEAD":
145 branch = ""
146 if branch:
147 latest = None
148 # the last changeset that contains a base
149 # file is our parent
150 for r in oldrevs:
151 latest = max(filerevids.get(r, None), latest)
152 if latest:
153 p = [latest]
126 154
127 # save some information for identifying branch points
128 oldrevs.append("%s:%s" % (oldrev, file))
129 filerevids["%s:%s" % (rev, file)] = id
130 elif state == 3:
131 # swallow all input
132 continue
155 # add current commit to set
156 c = commit(author=author, date=date, parents=p,
157 desc=log, branch=branch)
158 self.changeset[id] = c
159 self.files[id] = files
160 else:
161 colon = l.rfind(':')
162 file = l[1:colon]
163 rev = l[colon+1:-2]
164 oldrev, rev = rev.split("->")
165 files[file] = rev
166
167 # save some information for identifying branch points
168 oldrevs.append("%s:%s" % (oldrev, file))
169 filerevids["%s:%s" % (rev, file)] = id
170 elif state == 3:
171 # swallow all input
172 continue
133 173
134 174 self.heads = self.lastbranch.values()
135 175 finally:
@@ -297,10 +337,7 b' class convert_cvs(converter_source):'
297 337
298 338 def getchanges(self, rev):
299 339 self.modecache = {}
300 files = self.files[rev]
301 cl = files.items()
302 cl.sort()
303 return (cl, {})
340 return util.sort(self.files[rev].items()), {}
304 341
305 342 def getcommit(self, rev):
306 343 return self.changeset[rev]
@@ -309,7 +346,4 b' class convert_cvs(converter_source):'
309 346 return self.tags
310 347
311 348 def getchangedfiles(self, rev, i):
312 files = self.files[rev].keys()
313 files.sort()
314 return files
315
349 return util.sort(self.files[rev].keys())
@@ -110,9 +110,8 b' class darcs_source(converter_source, com'
110 110 copies[elt.get('from')] = elt.get('to')
111 111 else:
112 112 changes.append((elt.text.strip(), rev))
113 changes.sort()
114 113 self.lastrev = rev
115 return changes, copies
114 return util.sort(changes), copies
116 115
117 116 def getfile(self, name, rev):
118 117 if rev != self.lastrev:
@@ -130,10 +130,8 b' class gnuarch_source(converter_source, c'
130 130 for c in cps:
131 131 copies[c] = cps[c]
132 132
133 changes.sort()
134 133 self.lastrev = rev
135
136 return changes, copies
134 return util.sort(changes), copies
137 135
138 136 def getcommit(self, rev):
139 137 changes = self.changes[rev]
@@ -17,7 +17,7 b' import os, time'
17 17 from mercurial.i18n import _
18 18 from mercurial.repo import RepoError
19 19 from mercurial.node import bin, hex, nullid
20 from mercurial import hg, revlog, util
20 from mercurial import hg, revlog, util, context
21 21
22 22 from common import NoRepo, commit, converter_source, converter_sink
23 23
@@ -54,11 +54,9 b' class mercurial_sink(converter_sink):'
54 54 self.ui.debug(_('run hg sink pre-conversion action\n'))
55 55 self.wlock = self.repo.wlock()
56 56 self.lock = self.repo.lock()
57 self.repo.dirstate.clear()
58 57
59 58 def after(self):
60 59 self.ui.debug(_('run hg sink post-conversion action\n'))
61 self.repo.dirstate.invalidate()
62 60 self.lock = None
63 61 self.wlock = None
64 62
@@ -72,21 +70,6 b' class mercurial_sink(converter_sink):'
72 70 h = self.repo.changelog.heads()
73 71 return [ hex(x) for x in h ]
74 72
75 def putfile(self, f, e, data):
76 self.repo.wwrite(f, data, e)
77 if f not in self.repo.dirstate:
78 self.repo.dirstate.normallookup(f)
79
80 def copyfile(self, source, dest):
81 self.repo.copy(source, dest)
82
83 def delfile(self, f):
84 try:
85 util.unlink(self.repo.wjoin(f))
86 #self.repo.remove([f])
87 except OSError:
88 pass
89
90 73 def setbranch(self, branch, pbranches):
91 74 if not self.clonebranches:
92 75 return
@@ -125,13 +108,19 b' class mercurial_sink(converter_sink):'
125 108 self.repo.pull(prepo, [prepo.lookup(h) for h in heads])
126 109 self.before()
127 110
128 def putcommit(self, files, parents, commit):
129 seen = {}
111 def putcommit(self, files, copies, parents, commit, source):
112
113 files = dict(files)
114 def getfilectx(repo, memctx, f):
115 v = files[f]
116 data = source.getfile(f, v)
117 e = source.getmode(f, v)
118 return context.memfilectx(f, data, 'l' in e, 'x' in e, copies.get(f))
119
130 120 pl = []
131 121 for p in parents:
132 if p not in seen:
122 if p not in pl:
133 123 pl.append(p)
134 seen[p] = 1
135 124 parents = pl
136 125 nparents = len(parents)
137 126 if self.filemapmode and nparents == 1:
@@ -152,9 +141,9 b' class mercurial_sink(converter_sink):'
152 141 while parents:
153 142 p1 = p2
154 143 p2 = parents.pop(0)
155 a = self.repo.rawcommit(files, text, commit.author, commit.date,
156 bin(p1), bin(p2), extra=extra)
157 self.repo.dirstate.clear()
144 ctx = context.memctx(self.repo, (p1, p2), text, files.keys(), getfilectx,
145 commit.author, commit.date, extra)
146 a = self.repo.commitctx(ctx)
158 147 text = "(octopus merge fixup)\n"
159 148 p2 = hex(self.repo.changelog.tip())
160 149
@@ -163,43 +152,39 b' class mercurial_sink(converter_sink):'
163 152 mnode = self.repo.changelog.read(bin(p2))[0]
164 153 if not man.cmp(m1node, man.revision(mnode)):
165 154 self.repo.rollback()
166 self.repo.dirstate.clear()
167 155 return parent
168 156 return p2
169 157
170 158 def puttags(self, tags):
171 try:
172 old = self.repo.wfile(".hgtags").read()
173 oldlines = old.splitlines(1)
174 oldlines.sort()
175 except:
176 oldlines = []
159 try:
160 parentctx = self.repo[self.tagsbranch]
161 tagparent = parentctx.node()
162 except RepoError, inst:
163 parentctx = None
164 tagparent = nullid
177 165
178 k = tags.keys()
179 k.sort()
180 newlines = []
181 for tag in k:
182 newlines.append("%s %s\n" % (tags[tag], tag))
166 try:
167 oldlines = util.sort(parentctx['.hgtags'].data().splitlines(1))
168 except:
169 oldlines = []
183 170
184 newlines.sort()
171 newlines = util.sort([("%s %s\n" % (tags[tag], tag)) for tag in tags])
185 172
186 if newlines != oldlines:
187 self.ui.status("updating tags\n")
188 f = self.repo.wfile(".hgtags", "w")
189 f.write("".join(newlines))
190 f.close()
191 if not oldlines: self.repo.add([".hgtags"])
192 date = "%s 0" % int(time.mktime(time.gmtime()))
193 extra = {}
194 if self.tagsbranch != 'default':
195 extra['branch'] = self.tagsbranch
196 try:
197 tagparent = self.repo.changectx(self.tagsbranch).node()
198 except RepoError, inst:
199 tagparent = nullid
200 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
201 date, tagparent, nullid, extra=extra)
202 return hex(self.repo.changelog.tip())
173 if newlines == oldlines:
174 return None
175 data = "".join(newlines)
176
177 def getfilectx(repo, memctx, f):
178 return context.memfilectx(f, data, False, False, None)
179
180 self.ui.status("updating tags\n")
181 date = "%s 0" % int(time.mktime(time.gmtime()))
182 extra = {'branch': self.tagsbranch}
183 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
184 [".hgtags"], getfilectx, "convert-repo", date,
185 extra)
186 self.repo.commitctx(ctx)
187 return hex(self.repo.changelog.tip())
203 188
204 189 def setfilemapmode(self, active):
205 190 self.filemapmode = active
@@ -224,25 +209,24 b' class mercurial_source(converter_source)'
224 209
225 210 def changectx(self, rev):
226 211 if self.lastrev != rev:
227 self.lastctx = self.repo.changectx(rev)
212 self.lastctx = self.repo[rev]
228 213 self.lastrev = rev
229 214 return self.lastctx
230 215
231 216 def getheads(self):
232 217 if self.rev:
233 return [hex(self.repo.changectx(self.rev).node())]
218 return [hex(self.repo[self.rev].node())]
234 219 else:
235 220 return [hex(node) for node in self.repo.heads()]
236 221
237 222 def getfile(self, name, rev):
238 223 try:
239 return self.changectx(rev).filectx(name).data()
224 return self.changectx(rev)[name].data()
240 225 except revlog.LookupError, err:
241 226 raise IOError(err)
242 227
243 228 def getmode(self, name, rev):
244 m = self.changectx(rev).manifest()
245 return (m.execf(name) and 'x' or '') + (m.linkf(name) and 'l' or '')
229 return self.changectx(rev).manifest().flags(name)
246 230
247 231 def getchanges(self, rev):
248 232 ctx = self.changectx(rev)
@@ -251,8 +235,7 b' class mercurial_source(converter_source)'
251 235 else:
252 236 m, a, r = self.repo.status(ctx.parents()[0].node(), ctx.node())[:3]
253 237 changes = [(name, rev) for name in m + a + r]
254 changes.sort()
255 return (changes, self.getcopies(ctx, m + a))
238 return util.sort(changes), self.getcopies(ctx, m + a)
256 239
257 240 def getcopies(self, ctx, files):
258 241 copies = {}
@@ -655,8 +655,7 b' class svn_source(converter_source):'
655 655 # This will fail if a directory was copied
656 656 # from another branch and then some of its files
657 657 # were deleted in the same transaction.
658 children = self._find_children(path, revnum)
659 children.sort()
658 children = util.sort(self._find_children(path, revnum))
660 659 for child in children:
661 660 # Can we move a child directory and its
662 661 # parent in the same commit? (probably can). Could
@@ -729,8 +728,7 b' class svn_source(converter_source):'
729 728 parents = []
730 729 # check whether this revision is the start of a branch or part
731 730 # of a branch renaming
732 orig_paths = orig_paths.items()
733 orig_paths.sort()
731 orig_paths = util.sort(orig_paths.items())
734 732 root_paths = [(p,e) for p,e in orig_paths if self.module.startswith(p)]
735 733 if root_paths:
736 734 path, ent = root_paths[-1]
@@ -1034,12 +1032,6 b' class svn_sink(converter_sink, commandli'
1034 1032 if 'x' in flags:
1035 1033 self.setexec.append(filename)
1036 1034
1037 def delfile(self, name):
1038 self.delete.append(name)
1039
1040 def copyfile(self, source, dest):
1041 self.copies.append([source, dest])
1042
1043 1035 def _copyfile(self, source, dest):
1044 1036 # SVN's copy command pukes if the destination file exists, but
1045 1037 # our copyfile method expects to record a copy that has
@@ -1072,10 +1064,9 b' class svn_sink(converter_sink, commandli'
1072 1064 return dirs
1073 1065
1074 1066 def add_dirs(self, files):
1075 add_dirs = [d for d in self.dirs_of(files)
1067 add_dirs = [d for d in util.sort(self.dirs_of(files))
1076 1068 if not os.path.exists(self.wjoin(d, '.svn', 'entries'))]
1077 1069 if add_dirs:
1078 add_dirs.sort()
1079 1070 self.xargs(add_dirs, 'add', non_recursive=True, quiet=True)
1080 1071 return add_dirs
1081 1072
@@ -1085,8 +1076,7 b' class svn_sink(converter_sink, commandli'
1085 1076 return files
1086 1077
1087 1078 def tidy_dirs(self, names):
1088 dirs = list(self.dirs_of(names))
1089 dirs.sort()
1079 dirs = util.sort(self.dirs_of(names))
1090 1080 dirs.reverse()
1091 1081 deleted = []
1092 1082 for d in dirs:
@@ -1102,7 +1092,20 b' class svn_sink(converter_sink, commandli'
1102 1092 def revid(self, rev):
1103 1093 return u"svn:%s@%s" % (self.uuid, rev)
1104 1094
1105 def putcommit(self, files, parents, commit):
1095 def putcommit(self, files, copies, parents, commit, source):
1096 # Apply changes to working copy
1097 for f, v in files:
1098 try:
1099 data = source.getfile(f, v)
1100 except IOError, inst:
1101 self.delete.append(f)
1102 else:
1103 e = source.getmode(f, v)
1104 self.putfile(f, e, data)
1105 if f in copies:
1106 self.copies.append([copies[f], f])
1107 files = [f[0] for f in files]
1108
1106 1109 for parent in parents:
1107 1110 try:
1108 1111 return self.revid(self.childmap[parent])
@@ -52,7 +52,6 b' import os, shlex, shutil, tempfile'
52 52
53 53 def snapshot_node(ui, repo, files, node, tmproot):
54 54 '''snapshot files as of some revision'''
55 mf = repo.changectx(node).manifest()
56 55 dirname = os.path.basename(repo.root)
57 56 if dirname == "":
58 57 dirname = "root"
@@ -61,17 +60,18 b' def snapshot_node(ui, repo, files, node,'
61 60 os.mkdir(base)
62 61 ui.note(_('making snapshot of %d files from rev %s\n') %
63 62 (len(files), short(node)))
63 ctx = repo[node]
64 64 for fn in files:
65 if not fn in mf:
65 wfn = util.pconvert(fn)
66 if not wfn in ctx:
66 67 # skipping new file after a merge ?
67 68 continue
68 wfn = util.pconvert(fn)
69 69 ui.note(' %s\n' % wfn)
70 70 dest = os.path.join(base, wfn)
71 71 destdir = os.path.dirname(dest)
72 72 if not os.path.isdir(destdir):
73 73 os.makedirs(destdir)
74 data = repo.wwritedata(wfn, repo.file(wfn).read(mf[wfn]))
74 data = repo.wwritedata(wfn, ctx[wfn].data())
75 75 open(dest, 'wb').write(data)
76 76 return dirname
77 77
@@ -121,9 +121,8 b' def dodiff(ui, repo, diffcmd, diffopts, '
121 121 - just invoke the diff for a single file in the working dir
122 122 '''
123 123 node1, node2 = cmdutil.revpair(repo, opts['rev'])
124 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
125 modified, added, removed, deleted, unknown = repo.status(
126 node1, node2, files, match=matchfn)[:5]
124 matcher = cmdutil.match(repo, pats, opts)
125 modified, added, removed = repo.status(node1, node2, matcher)[:3]
127 126 if not (modified or added or removed):
128 127 return 0
129 128
@@ -4,6 +4,7 b''
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 '''pulling, updating and merging in one command'''
7 8
8 9 from mercurial.i18n import _
9 10 from mercurial.node import nullid, short
@@ -239,7 +239,7 b' def sign(ui, repo, *revs, **opts):'
239 239 repo.opener("localsigs", "ab").write(sigmessage)
240 240 return
241 241
242 for x in repo.status()[:5]:
242 for x in repo.status(unknown=True)[:5]:
243 243 if ".hgsigs" in x and not opts["force"]:
244 244 raise util.Abort(_("working copy of .hgsigs is changed "
245 245 "(please commit .hgsigs manually "
@@ -4,6 +4,7 b''
4 4 #
5 5 # This software may be used and distributed according to the terms of
6 6 # the GNU General Public License, incorporated herein by reference.
7 '''show revision graphs in terminal windows'''
7 8
8 9 import os
9 10 import sys
@@ -12,6 +13,7 b' from mercurial.commands import templateo'
12 13 from mercurial.i18n import _
13 14 from mercurial.node import nullrev
14 15 from mercurial.util import Abort, canonpath
16 from mercurial import util
15 17
16 18 def revision_grapher(repo, start_rev, stop_rev):
17 19 """incremental revision grapher
@@ -52,8 +54,7 b' def revision_grapher(repo, start_rev, st'
52 54 for parent in parents:
53 55 if parent not in next_revs:
54 56 parents_to_add.append(parent)
55 parents_to_add.sort()
56 next_revs[rev_index:rev_index + 1] = parents_to_add
57 next_revs[rev_index:rev_index + 1] = util.sort(parents_to_add)
57 58
58 59 edges = []
59 60 for parent in parents:
@@ -88,7 +89,7 b' def filelog_grapher(repo, path, start_re'
88 89 assert start_rev >= stop_rev
89 90 curr_rev = start_rev
90 91 revs = []
91 filerev = repo.file(path).count() - 1
92 filerev = len(repo.file(path)) - 1
92 93 while filerev >= 0:
93 94 fctx = repo.filectx(path, fileid=filerev)
94 95
@@ -104,8 +105,7 b' def filelog_grapher(repo, path, start_re'
104 105 for parent in parents:
105 106 if parent not in next_revs:
106 107 parents_to_add.append(parent)
107 parents_to_add.sort()
108 next_revs[rev_index:rev_index + 1] = parents_to_add
108 next_revs[rev_index:rev_index + 1] = util.sort(parents_to_add)
109 109
110 110 edges = []
111 111 for parent in parents:
@@ -197,7 +197,7 b' def get_revs(repo, rev_opt):'
197 197 revs = revrange(repo, rev_opt)
198 198 return (max(revs), min(revs))
199 199 else:
200 return (repo.changelog.count() - 1, 0)
200 return (len(repo) - 1, 0)
201 201
202 202 def graphlog(ui, repo, path=None, **opts):
203 203 """show revision history alongside an ASCII revision graph
@@ -4,60 +4,58 b''
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 #
8 # The hgk extension allows browsing the history of a repository in a
9 # graphical way. It requires Tcl/Tk version 8.4 or later. (Tcl/Tk is
10 # not distributed with Mercurial.)
11 #
12 # hgk consists of two parts: a Tcl script that does the displaying and
13 # querying of information, and an extension to mercurial named hgk.py,
14 # which provides hooks for hgk to get information. hgk can be found in
15 # the contrib directory, and hgk.py can be found in the hgext
16 # directory.
17 #
18 # To load the hgext.py extension, add it to your .hgrc file (you have
19 # to use your global $HOME/.hgrc file, not one in a repository). You
20 # can specify an absolute path:
21 #
22 # [extensions]
23 # hgk=/usr/local/lib/hgk.py
24 #
25 # Mercurial can also scan the default python library path for a file
26 # named 'hgk.py' if you set hgk empty:
27 #
28 # [extensions]
29 # hgk=
30 #
31 # The hg view command will launch the hgk Tcl script. For this command
32 # to work, hgk must be in your search path. Alternately, you can
33 # specify the path to hgk in your .hgrc file:
34 #
35 # [hgk]
36 # path=/location/of/hgk
37 #
38 # hgk can make use of the extdiff extension to visualize
39 # revisions. Assuming you had already configured extdiff vdiff
40 # command, just add:
41 #
42 # [hgk]
43 # vdiff=vdiff
44 #
45 # Revisions context menu will now display additional entries to fire
46 # vdiff on hovered and selected revisions.
7 '''browsing the repository in a graphical way
8
9 The hgk extension allows browsing the history of a repository in a
10 graphical way. It requires Tcl/Tk version 8.4 or later. (Tcl/Tk is
11 not distributed with Mercurial.)
12
13 hgk consists of two parts: a Tcl script that does the displaying and
14 querying of information, and an extension to mercurial named hgk.py,
15 which provides hooks for hgk to get information. hgk can be found in
16 the contrib directory, and hgk.py can be found in the hgext directory.
17
18 To load the hgext.py extension, add it to your .hgrc file (you have
19 to use your global $HOME/.hgrc file, not one in a repository). You
20 can specify an absolute path:
21
22 [extensions]
23 hgk=/usr/local/lib/hgk.py
24
25 Mercurial can also scan the default python library path for a file
26 named 'hgk.py' if you set hgk empty:
27
28 [extensions]
29 hgk=
30
31 The hg view command will launch the hgk Tcl script. For this command
32 to work, hgk must be in your search path. Alternately, you can
33 specify the path to hgk in your .hgrc file:
34
35 [hgk]
36 path=/location/of/hgk
37
38 hgk can make use of the extdiff extension to visualize revisions.
39 Assuming you had already configured extdiff vdiff command, just add:
40
41 [hgk]
42 vdiff=vdiff
43
44 Revisions context menu will now display additional entries to fire
45 vdiff on hovered and selected revisions.'''
47 46
48 47 import os
49 from mercurial import commands, util, patch, revlog
48 from mercurial import commands, util, patch, revlog, cmdutil
50 49 from mercurial.node import nullid, nullrev, short
51 50
52 51 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
53 52 """diff trees from two commits"""
54 53 def __difftree(repo, node1, node2, files=[]):
55 54 assert node2 is not None
56 mmap = repo.changectx(node1).manifest()
57 mmap2 = repo.changectx(node2).manifest()
58 status = repo.status(node1, node2, files=files)[:5]
59 modified, added, removed, deleted, unknown = status
60
55 mmap = repo[node1].manifest()
56 mmap2 = repo[node2].manifest()
57 m = cmdutil.match(repo, files)
58 modified, added, removed = repo.status(node1, node2, m)[:3]
61 59 empty = short(nullid)
62 60
63 61 for f in modified:
@@ -92,8 +90,8 b' def difftree(ui, repo, node1=None, node2'
92 90 if opts['patch']:
93 91 if opts['pretty']:
94 92 catcommit(ui, repo, node2, "")
95 patch.diff(repo, node1, node2,
96 files=files,
93 m = cmdutil.match(repo, files)
94 patch.diff(repo, node1, node2, match=m,
97 95 opts=patch.diffopts(ui, {'git': True}))
98 96 else:
99 97 __difftree(repo, node1, node2, files=files)
@@ -103,11 +101,11 b' def difftree(ui, repo, node1=None, node2'
103 101 def catcommit(ui, repo, n, prefix, ctx=None):
104 102 nlprefix = '\n' + prefix;
105 103 if ctx is None:
106 ctx = repo.changectx(n)
107 (p1, p2) = ctx.parents()
104 ctx = repo[n]
108 105 ui.write("tree %s\n" % short(ctx.changeset()[0])) # use ctx.node() instead ??
109 if p1: ui.write("parent %s\n" % short(p1.node()))
110 if p2: ui.write("parent %s\n" % short(p2.node()))
106 for p in ctx.parents():
107 ui.write("parent %s\n" % p)
108
111 109 date = ctx.date()
112 110 description = ctx.description().replace("\0", "")
113 111 lines = description.splitlines()
@@ -175,7 +173,7 b' def catfile(ui, repo, type=None, r=None,'
175 173 # you can specify a commit to stop at by starting the sha1 with ^
176 174 def revtree(ui, args, repo, full="tree", maxnr=0, parents=False):
177 175 def chlogwalk():
178 count = repo.changelog.count()
176 count = len(repo)
179 177 i = count
180 178 l = [0] * 100
181 179 chunk = 100
@@ -191,7 +189,7 b' def revtree(ui, args, repo, full="tree",'
191 189 l[chunk - x:] = [0] * (chunk - x)
192 190 break
193 191 if full != None:
194 l[x] = repo.changectx(i + x)
192 l[x] = repo[i + x]
195 193 l[x].changeset() # force reading
196 194 else:
197 195 l[x] = 1
@@ -1,6 +1,4 b''
1 """
2 This is Mercurial extension for syntax highlighting in the file
3 revision view of hgweb.
1 """a mercurial extension for syntax highlighting in hgweb
4 2
5 3 It depends on the pygments syntax highlighting library:
6 4 http://pygments.org/
@@ -15,23 +13,15 b' There is a single configuration option:'
15 13 [web]
16 14 pygments_style = <style>
17 15
18 The default is 'colorful'. If this is changed the corresponding CSS
19 file should be re-generated by running
20
21 # pygmentize -f html -S <newstyle>
22
16 The default is 'colorful'.
23 17
24 18 -- Adam Hupp <adam@hupp.org>
25
26
27 19 """
28 20
29 21 from mercurial import demandimport
30 demandimport.ignore.extend(['pkgutil',
31 'pkg_resources',
32 '__main__',])
22 demandimport.ignore.extend(['pkgutil', 'pkg_resources', '__main__',])
33 23
34 from mercurial.hgweb.hgweb_mod import hgweb
24 from mercurial.hgweb import webcommands, webutil, common
35 25 from mercurial import util
36 26 from mercurial.templatefilters import filters
37 27
@@ -40,10 +30,11 b' from pygments.util import ClassNotFound'
40 30 from pygments.lexers import guess_lexer, guess_lexer_for_filename, TextLexer
41 31 from pygments.formatters import HtmlFormatter
42 32
43 SYNTAX_CSS = ('\n<link rel="stylesheet" href="#staticurl#highlight.css" '
33 SYNTAX_CSS = ('\n<link rel="stylesheet" href="{url}highlightcss" '
44 34 'type="text/css" />')
45 35
46 def pygmentize(self, tmpl, fctx, field):
36 def pygmentize(field, fctx, style, tmpl):
37
47 38 # append a <link ...> to the syntax highlighting css
48 39 old_header = ''.join(tmpl('header'))
49 40 if SYNTAX_CSS not in old_header:
@@ -54,7 +45,6 b' def pygmentize(self, tmpl, fctx, field):'
54 45 if util.binary(text):
55 46 return
56 47
57 style = self.config("web", "pygments_style", "colorful")
58 48 # To get multi-line strings right, we can't format line-by-line
59 49 try:
60 50 lexer = guess_lexer_for_filename(fctx.path(), text[:1024],
@@ -79,20 +69,30 b' def pygmentize(self, tmpl, fctx, field):'
79 69 newl = oldl.replace('line|escape', 'line|colorize')
80 70 tmpl.cache[field] = newl
81 71
82 def filerevision_highlight(self, tmpl, fctx):
83 pygmentize(self, tmpl, fctx, 'fileline')
72 web_filerevision = webcommands._filerevision
73 web_annotate = webcommands.annotate
84 74
85 return realrevision(self, tmpl, fctx)
75 def filerevision_highlight(web, tmpl, fctx):
76 style = web.config('web', 'pygments_style', 'colorful')
77 pygmentize('fileline', fctx, style, tmpl)
78 return web_filerevision(web, tmpl, fctx)
86 79
87 def fileannotate_highlight(self, tmpl, fctx):
88 pygmentize(self, tmpl, fctx, 'annotateline')
80 def annotate_highlight(web, req, tmpl):
81 fctx = webutil.filectx(web.repo, req)
82 style = web.config('web', 'pygments_style', 'colorful')
83 pygmentize('annotateline', fctx, style, tmpl)
84 return web_annotate(web, req, tmpl)
89 85
90 return realannotate(self, tmpl, fctx)
86 def generate_css(web, req, tmpl):
87 pg_style = web.config('web', 'pygments_style', 'colorful')
88 fmter = HtmlFormatter(style = pg_style)
89 req.respond(common.HTTP_OK, 'text/css')
90 return ['/* pygments_style = %s */\n\n' % pg_style, fmter.get_style_defs('')]
91
91 92
92 93 # monkeypatch in the new version
93 # should be safer than overriding the method in a derived class
94 # and then patching the class
95 realrevision = hgweb.filerevision
96 hgweb.filerevision = filerevision_highlight
97 realannotate = hgweb.fileannotate
98 hgweb.fileannotate = fileannotate_highlight
94
95 webcommands._filerevision = filerevision_highlight
96 webcommands.annotate = annotate_highlight
97 webcommands.highlightcss = generate_css
98 webcommands.__all__.append('highlightcss')
@@ -47,12 +47,12 b' def reposetup(ui, repo):'
47 47 # to recurse.
48 48 inotifyserver = False
49 49
50 def status(self, files, match, list_ignored, list_clean,
51 list_unknown=True):
50 def status(self, match, ignored, clean, unknown=True):
51 files = match.files()
52 52 try:
53 if not list_ignored and not self.inotifyserver:
53 if not ignored and not self.inotifyserver:
54 54 result = client.query(ui, repo, files, match, False,
55 list_clean, list_unknown)
55 clean, unknown)
56 56 if result is not None:
57 57 return result
58 58 except socket.error, err:
@@ -81,15 +81,14 b' def reposetup(ui, repo):'
81 81 if query:
82 82 try:
83 83 return query(ui, repo, files or [], match,
84 list_ignored, list_clean, list_unknown)
84 ignored, clean, unknown)
85 85 except socket.error, err:
86 86 ui.warn(_('could not talk to new inotify '
87 87 'server: %s\n') % err[1])
88 88 ui.print_exc()
89 89
90 90 return super(inotifydirstate, self).status(
91 files, match or util.always, list_ignored, list_clean,
92 list_unknown)
91 match, ignored, clean, unknown)
93 92
94 93 repo.dirstate.__class__ = inotifydirstate
95 94
@@ -11,7 +11,7 b' from mercurial import ui'
11 11 import common
12 12 import os, select, socket, stat, struct, sys
13 13
14 def query(ui, repo, names, match, list_ignored, list_clean, list_unknown=True):
14 def query(ui, repo, names, match, ignored, clean, unknown=True):
15 15 sock = socket.socket(socket.AF_UNIX)
16 16 sockpath = repo.join('inotify.sock')
17 17 sock.connect(sockpath)
@@ -20,10 +20,10 b' def query(ui, repo, names, match, list_i'
20 20 for n in names or []:
21 21 yield n
22 22 states = 'almrx!'
23 if list_ignored:
23 if ignored:
24 24 raise ValueError('this is insanity')
25 if list_clean: states += 'n'
26 if list_unknown: states += '?'
25 if clean: states += 'n'
26 if unknown: states += '?'
27 27 yield states
28 28
29 29 req = '\0'.join(genquery())
@@ -534,9 +534,7 b' class Watcher(object):'
534 534 self.ui.note('%s processing %d deferred events as %d\n' %
535 535 (self.event_time(), self.deferred,
536 536 len(self.eventq)))
537 eventq = self.eventq.items()
538 eventq.sort()
539 for wpath, evts in eventq:
537 for wpath, evts in util.sort(self.eventq.items()):
540 538 for evt in evts:
541 539 self.deferred_event(wpath, evt)
542 540 self.eventq.clear()
@@ -78,7 +78,7 b" like CVS' $Log$, are not supported. A ke"
78 78 "Log = {desc}" expands to the first line of the changeset description.
79 79 '''
80 80
81 from mercurial import commands, cmdutil, context, dispatch, filelog, revlog
81 from mercurial import commands, cmdutil, dispatch, filelog, revlog
82 82 from mercurial import patch, localrepo, templater, templatefilters, util
83 83 from mercurial.hgweb import webcommands
84 84 from mercurial.node import nullid, hex
@@ -88,8 +88,8 b' import re, shutil, tempfile, time'
88 88 commands.optionalrepo += ' kwdemo'
89 89
90 90 # hg commands that do not act on keywords
91 nokwcommands = ('add addremove bundle copy export grep incoming init'
92 ' log outgoing push rename rollback tip'
91 nokwcommands = ('add addremove annotate bundle copy export grep incoming init'
92 ' log outgoing push rename rollback tip verify'
93 93 ' convert email glog')
94 94
95 95 # hg commands that trigger expansion only when writing to working dir,
@@ -100,52 +100,8 b' def utcdate(date):'
100 100 '''Returns hgdate in cvs-like UTC format.'''
101 101 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
102 102
103
104 103 # make keyword tools accessible
105 kwtools = {'templater': None, 'hgcmd': None}
106
107 # store originals of monkeypatches
108 _patchfile_init = patch.patchfile.__init__
109 _patch_diff = patch.diff
110 _dispatch_parse = dispatch._parse
111
112 def _kwpatchfile_init(self, ui, fname, missing=False):
113 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
114 rejects or conflicts due to expanded keywords in working dir.'''
115 _patchfile_init(self, ui, fname, missing=missing)
116 # shrink keywords read from working dir
117 kwt = kwtools['templater']
118 self.lines = kwt.shrinklines(self.fname, self.lines)
119
120 def _kw_diff(repo, node1=None, node2=None, files=None, match=util.always,
121 fp=None, changes=None, opts=None):
122 '''Monkeypatch patch.diff to avoid expansion except when
123 comparing against working dir.'''
124 if node2 is not None:
125 kwtools['templater'].matcher = util.never
126 elif node1 is not None and node1 != repo.changectx().node():
127 kwtools['templater'].restrict = True
128 _patch_diff(repo, node1=node1, node2=node2, files=files, match=match,
129 fp=fp, changes=changes, opts=opts)
130
131 def _kwweb_changeset(web, req, tmpl):
132 '''Wraps webcommands.changeset turning off keyword expansion.'''
133 kwtools['templater'].matcher = util.never
134 return web.changeset(tmpl, web.changectx(req))
135
136 def _kwweb_filediff(web, req, tmpl):
137 '''Wraps webcommands.filediff turning off keyword expansion.'''
138 kwtools['templater'].matcher = util.never
139 return web.filediff(tmpl, web.filectx(req))
140
141 def _kwdispatch_parse(ui, args):
142 '''Monkeypatch dispatch._parse to obtain running hg command.'''
143 cmd, func, args, options, cmdoptions = _dispatch_parse(ui, args)
144 kwtools['hgcmd'] = cmd
145 return cmd, func, args, options, cmdoptions
146
147 # dispatch._parse is run before reposetup, so wrap it here
148 dispatch._parse = _kwdispatch_parse
104 kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']}
149 105
150 106
151 107 class kwtemplater(object):
@@ -163,15 +119,16 b' class kwtemplater(object):'
163 119 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
164 120 }
165 121
166 def __init__(self, ui, repo, inc, exc):
122 def __init__(self, ui, repo):
167 123 self.ui = ui
168 124 self.repo = repo
169 self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
125 self.matcher = util.matcher(repo.root,
126 inc=kwtools['inc'], exc=kwtools['exc'])[1]
170 127 self.restrict = kwtools['hgcmd'] in restricted.split()
171 128
172 129 kwmaps = self.ui.configitems('keywordmaps')
173 130 if kwmaps: # override default templates
174 kwmaps = [(k, templater.parsestring(v, quoted=False))
131 kwmaps = [(k, templater.parsestring(v, False))
175 132 for (k, v) in kwmaps]
176 133 self.templates = dict(kwmaps)
177 134 escaped = map(re.escape, self.templates.keys())
@@ -185,7 +142,7 b' class kwtemplater(object):'
185 142 def getnode(self, path, fnode):
186 143 '''Derives changenode from file path and filenode.'''
187 144 # used by kwfilelog.read and kwexpand
188 c = context.filectx(self.repo, path, fileid=fnode)
145 c = self.repo.filectx(path, fileid=fnode)
189 146 return c.node()
190 147
191 148 def substitute(self, data, path, node, subfunc):
@@ -206,25 +163,26 b' class kwtemplater(object):'
206 163 return self.substitute(data, path, changenode, self.re_kw.sub)
207 164 return data
208 165
209 def iskwfile(self, path, islink):
166 def iskwfile(self, path, flagfunc):
210 167 '''Returns true if path matches [keyword] pattern
211 168 and is not a symbolic link.
212 169 Caveat: localrepository._link fails on Windows.'''
213 return self.matcher(path) and not islink(path)
170 return self.matcher(path) and not 'l' in flagfunc(path)
214 171
215 def overwrite(self, node=None, expand=True, files=None):
172 def overwrite(self, node, expand, files):
216 173 '''Overwrites selected files expanding/shrinking keywords.'''
217 ctx = self.repo.changectx(node)
218 mf = ctx.manifest()
219 174 if node is not None: # commit
175 ctx = self.repo[node]
176 mf = ctx.manifest()
220 177 files = [f for f in ctx.files() if f in mf]
221 178 notify = self.ui.debug
222 179 else: # kwexpand/kwshrink
180 ctx = self.repo['.']
181 mf = ctx.manifest()
223 182 notify = self.ui.note
224 candidates = [f for f in files if self.iskwfile(f, mf.linkf)]
183 candidates = [f for f in files if self.iskwfile(f, ctx.flags)]
225 184 if candidates:
226 185 self.restrict = True # do not expand when reading
227 candidates.sort()
228 186 action = expand and 'expanding' or 'shrinking'
229 187 for f in candidates:
230 188 fp = self.repo.file(f)
@@ -271,9 +229,9 b' class kwfilelog(filelog.filelog):'
271 229 Subclass of filelog to hook into its read, add, cmp methods.
272 230 Keywords are "stored" unexpanded, and processed on reading.
273 231 '''
274 def __init__(self, opener, path):
232 def __init__(self, opener, kwt, path):
275 233 super(kwfilelog, self).__init__(opener, path)
276 self.kwt = kwtools['templater']
234 self.kwt = kwt
277 235 self.path = path
278 236
279 237 def read(self, node):
@@ -284,7 +242,7 b' class kwfilelog(filelog.filelog):'
284 242 def add(self, text, meta, tr, link, p1=None, p2=None):
285 243 '''Removes keyword substitutions when adding to filelog.'''
286 244 text = self.kwt.shrink(self.path, text)
287 return super(kwfilelog, self).add(text, meta, tr, link, p1=p1, p2=p2)
245 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
288 246
289 247 def cmp(self, node, text):
290 248 '''Removes keyword substitutions for comparison.'''
@@ -294,28 +252,30 b' class kwfilelog(filelog.filelog):'
294 252 return t2 != text
295 253 return revlog.revlog.cmp(self, node, text)
296 254
297 def _status(ui, repo, kwt, *pats, **opts):
255 def _status(ui, repo, kwt, unknown, *pats, **opts):
298 256 '''Bails out if [keyword] configuration is not active.
299 257 Returns status of working directory.'''
300 258 if kwt:
301 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
302 return repo.status(files=files, match=match, list_clean=True)
259 matcher = cmdutil.match(repo, pats, opts)
260 return repo.status(match=matcher, unknown=unknown, clean=True)
303 261 if ui.configitems('keyword'):
304 262 raise util.Abort(_('[keyword] patterns cannot match'))
305 263 raise util.Abort(_('no [keyword] patterns configured'))
306 264
307 265 def _kwfwrite(ui, repo, expand, *pats, **opts):
308 266 '''Selects files and passes them to kwtemplater.overwrite.'''
267 if repo.dirstate.parents()[1] != nullid:
268 raise util.Abort(_('outstanding uncommitted merge'))
309 269 kwt = kwtools['templater']
310 status = _status(ui, repo, kwt, *pats, **opts)
311 modified, added, removed, deleted, unknown, ignored, clean = status
270 status = _status(ui, repo, kwt, False, *pats, **opts)
271 modified, added, removed, deleted = status[:4]
312 272 if modified or added or removed or deleted:
313 raise util.Abort(_('outstanding uncommitted changes in given files'))
273 raise util.Abort(_('outstanding uncommitted changes'))
314 274 wlock = lock = None
315 275 try:
316 276 wlock = repo.wlock()
317 277 lock = repo.lock()
318 kwt.overwrite(expand=expand, files=clean)
278 kwt.overwrite(None, expand, status[6])
319 279 finally:
320 280 del wlock, lock
321 281
@@ -345,7 +305,7 b' def demo(ui, repo, *args, **opts):'
345 305 branchname = 'demobranch'
346 306 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
347 307 ui.note(_('creating temporary repo at %s\n') % tmpdir)
348 repo = localrepo.localrepository(ui, path=tmpdir, create=True)
308 repo = localrepo.localrepository(ui, tmpdir, True)
349 309 ui.setconfig('keyword', fn, '')
350 310 if args or opts.get('rcfile'):
351 311 kwstatus = 'custom'
@@ -367,6 +327,7 b' def demo(ui, repo, *args, **opts):'
367 327 ui.readconfig(repo.join('hgrc'))
368 328 if not opts.get('default'):
369 329 kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates
330 uisetup(ui)
370 331 reposetup(ui, repo)
371 332 for k, v in ui.configitems('extensions'):
372 333 if k.endswith('keyword'):
@@ -418,15 +379,11 b' def files(ui, repo, *pats, **opts):'
418 379 That is, files matched by [keyword] config patterns but not symlinks.
419 380 '''
420 381 kwt = kwtools['templater']
421 status = _status(ui, repo, kwt, *pats, **opts)
382 status = _status(ui, repo, kwt, opts.get('untracked'), *pats, **opts)
422 383 modified, added, removed, deleted, unknown, ignored, clean = status
423 files = modified + added + clean
424 if opts.get('untracked'):
425 files += unknown
426 files.sort()
427 wctx = repo.workingctx()
428 islink = lambda p: 'l' in wctx.fileflags(p)
429 kwfiles = [f for f in files if kwt.iskwfile(f, islink)]
384 files = util.sort(modified + added + clean + unknown)
385 wctx = repo[None]
386 kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)]
430 387 cwd = pats and repo.getcwd() or ''
431 388 kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
432 389 if opts.get('all') or opts.get('ignore'):
@@ -448,46 +405,57 b' def shrink(ui, repo, *pats, **opts):'
448 405 _kwfwrite(ui, repo, False, *pats, **opts)
449 406
450 407
408 def uisetup(ui):
409 '''Collects [keyword] config in kwtools.
410 Monkeypatches dispatch._parse if needed.'''
411
412 for pat, opt in ui.configitems('keyword'):
413 if opt != 'ignore':
414 kwtools['inc'].append(pat)
415 else:
416 kwtools['exc'].append(pat)
417
418 if kwtools['inc']:
419 def kwdispatch_parse(ui, args):
420 '''Monkeypatch dispatch._parse to obtain running hg command.'''
421 cmd, func, args, options, cmdoptions = dispatch_parse(ui, args)
422 kwtools['hgcmd'] = cmd
423 return cmd, func, args, options, cmdoptions
424
425 dispatch_parse = dispatch._parse
426 dispatch._parse = kwdispatch_parse
427
451 428 def reposetup(ui, repo):
452 429 '''Sets up repo as kwrepo for keyword substitution.
453 430 Overrides file method to return kwfilelog instead of filelog
454 431 if file matches user configuration.
455 432 Wraps commit to overwrite configured files with updated
456 433 keyword substitutions.
457 This is done for local repos only, and only if there are
458 files configured at all for keyword substitution.'''
434 Monkeypatches patch and webcommands.'''
459 435
460 436 try:
461 if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split()
437 if (not repo.local() or not kwtools['inc']
438 or kwtools['hgcmd'] in nokwcommands.split()
462 439 or '.hg' in util.splitpath(repo.root)
463 440 or repo._url.startswith('bundle:')):
464 441 return
465 442 except AttributeError:
466 443 pass
467 444
468 inc, exc = [], ['.hg*']
469 for pat, opt in ui.configitems('keyword'):
470 if opt != 'ignore':
471 inc.append(pat)
472 else:
473 exc.append(pat)
474 if not inc:
475 return
476
477 kwtools['templater'] = kwt = kwtemplater(ui, repo, inc, exc)
445 kwtools['templater'] = kwt = kwtemplater(ui, repo)
478 446
479 447 class kwrepo(repo.__class__):
480 448 def file(self, f):
481 449 if f[0] == '/':
482 450 f = f[1:]
483 return kwfilelog(self.sopener, f)
451 return kwfilelog(self.sopener, kwt, f)
484 452
485 453 def wread(self, filename):
486 454 data = super(kwrepo, self).wread(filename)
487 455 return kwt.wread(filename, data)
488 456
489 457 def commit(self, files=None, text='', user=None, date=None,
490 match=util.always, force=False, force_editor=False,
458 match=None, force=False, force_editor=False,
491 459 p1=None, p2=None, extra={}, empty_ok=False):
492 460 wlock = lock = None
493 461 _p1 = _p2 = None
@@ -512,28 +480,66 b' def reposetup(ui, repo):'
512 480 else:
513 481 _p2 = hex(_p2)
514 482
515 node = super(kwrepo,
516 self).commit(files=files, text=text, user=user,
517 date=date, match=match, force=force,
518 force_editor=force_editor,
519 p1=p1, p2=p2, extra=extra,
520 empty_ok=empty_ok)
483 n = super(kwrepo, self).commit(files, text, user, date, match,
484 force, force_editor, p1, p2,
485 extra, empty_ok)
521 486
522 487 # restore commit hooks
523 488 for name, cmd in commithooks.iteritems():
524 489 ui.setconfig('hooks', name, cmd)
525 if node is not None:
526 kwt.overwrite(node=node)
527 repo.hook('commit', node=node, parent1=_p1, parent2=_p2)
528 return node
490 if n is not None:
491 kwt.overwrite(n, True, None)
492 repo.hook('commit', node=n, parent1=_p1, parent2=_p2)
493 return n
529 494 finally:
530 495 del wlock, lock
531 496
497 # monkeypatches
498 def kwpatchfile_init(self, ui, fname, missing=False):
499 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
500 rejects or conflicts due to expanded keywords in working dir.'''
501 patchfile_init(self, ui, fname, missing)
502 # shrink keywords read from working dir
503 self.lines = kwt.shrinklines(self.fname, self.lines)
504
505 def kw_diff(repo, node1=None, node2=None, match=None,
506 fp=None, changes=None, opts=None):
507 '''Monkeypatch patch.diff to avoid expansion except when
508 comparing against working dir.'''
509 if node2 is not None:
510 kwt.matcher = util.never
511 elif node1 is not None and node1 != repo['.'].node():
512 kwt.restrict = True
513 patch_diff(repo, node1, node2, match, fp, changes, opts)
514
515 def kwweb_annotate(web, req, tmpl):
516 '''Wraps webcommands.annotate turning off keyword expansion.'''
517 kwt.matcher = util.never
518 return webcommands_annotate(web, req, tmpl)
519
520 def kwweb_changeset(web, req, tmpl):
521 '''Wraps webcommands.changeset turning off keyword expansion.'''
522 kwt.matcher = util.never
523 return webcommands_changeset(web, req, tmpl)
524
525 def kwweb_filediff(web, req, tmpl):
526 '''Wraps webcommands.filediff turning off keyword expansion.'''
527 kwt.matcher = util.never
528 return webcommands_filediff(web, req, tmpl)
529
532 530 repo.__class__ = kwrepo
533 patch.patchfile.__init__ = _kwpatchfile_init
534 patch.diff = _kw_diff
535 webcommands.changeset = webcommands.rev = _kwweb_changeset
536 webcommands.filediff = webcommands.diff = _kwweb_filediff
531
532 patchfile_init = patch.patchfile.__init__
533 patch_diff = patch.diff
534 webcommands_annotate = webcommands.annotate
535 webcommands_changeset = webcommands.changeset
536 webcommands_filediff = webcommands.filediff
537
538 patch.patchfile.__init__ = kwpatchfile_init
539 patch.diff = kw_diff
540 webcommands.annotate = kwweb_annotate
541 webcommands.changeset = webcommands.rev = kwweb_changeset
542 webcommands.filediff = webcommands.diff = kwweb_filediff
537 543
538 544
539 545 cmdtable = {
@@ -143,8 +143,7 b' class queue:'
143 143 bad = self.check_guard(guard)
144 144 if bad:
145 145 raise util.Abort(bad)
146 guards = dict.fromkeys(guards).keys()
147 guards.sort()
146 guards = util.sort(util.unique(guards))
148 147 self.ui.debug('active guards: %s\n' % ' '.join(guards))
149 148 self.active_guards = guards
150 149 self.guards_dirty = True
@@ -322,10 +321,8 b' class queue:'
322 321
323 322 def printdiff(self, repo, node1, node2=None, files=None,
324 323 fp=None, changes=None, opts={}):
325 fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts)
326
327 patch.diff(repo, node1, node2, fns, match=matchfn,
328 fp=fp, changes=changes, opts=self.diffopts())
324 m = cmdutil.match(repo, files, opts)
325 patch.diff(repo, node1, node2, m, fp, changes, self.diffopts())
329 326
330 327 def mergeone(self, repo, mergeq, head, patch, rev):
331 328 # first try just applying the patch
@@ -344,7 +341,7 b' class queue:'
344 341 hg.clean(repo, head)
345 342 self.strip(repo, n, update=False, backup='strip')
346 343
347 ctx = repo.changectx(rev)
344 ctx = repo[rev]
348 345 ret = hg.merge(repo, rev)
349 346 if ret:
350 347 raise util.Abort(_("update returned %d") % ret)
@@ -510,8 +507,10 b' class queue:'
510 507 repo.dirstate.merge(f)
511 508 p1, p2 = repo.dirstate.parents()
512 509 repo.dirstate.setparents(p1, merge)
510
513 511 files = patch.updatedir(self.ui, repo, files)
514 n = repo.commit(files, message, user, date, match=util.never,
512 match = cmdutil.matchfiles(repo, files or [])
513 n = repo.commit(files, message, user, date, match=match,
515 514 force=True)
516 515
517 516 if n == None:
@@ -535,6 +534,40 b' class queue:'
535 534 break
536 535 return (err, n)
537 536
537 def _clean_series(self, patches):
538 indices = util.sort([self.find_series(p) for p in patches])
539 for i in indices[-1::-1]:
540 del self.full_series[i]
541 self.parse_series()
542 self.series_dirty = 1
543
544 def finish(self, repo, revs):
545 revs.sort()
546 firstrev = repo[self.applied[0].rev].rev()
547 appliedbase = 0
548 patches = []
549 for rev in util.sort(revs):
550 if rev < firstrev:
551 raise util.Abort(_('revision %d is not managed') % rev)
552 base = revlog.bin(self.applied[appliedbase].rev)
553 node = repo.changelog.node(rev)
554 if node != base:
555 raise util.Abort(_('cannot delete revision %d above '
556 'applied patches') % rev)
557 patches.append(self.applied[appliedbase].name)
558 appliedbase += 1
559
560 r = self.qrepo()
561 if r:
562 r.remove(patches, True)
563 else:
564 for p in patches:
565 os.unlink(self.join(p))
566
567 del self.applied[:appliedbase]
568 self.applied_dirty = 1
569 self._clean_series(patches)
570
538 571 def delete(self, repo, patches, opts):
539 572 if not patches and not opts.get('rev'):
540 573 raise util.Abort(_('qdelete requires at least one revision or '
@@ -580,12 +613,7 b' class queue:'
580 613 if appliedbase:
581 614 del self.applied[:appliedbase]
582 615 self.applied_dirty = 1
583 indices = [self.find_series(p) for p in realpatches]
584 indices.sort()
585 for i in indices[-1::-1]:
586 del self.full_series[i]
587 self.parse_series()
588 self.series_dirty = 1
616 self._clean_series(realpatches)
589 617
590 618 def check_toppatch(self, repo):
591 619 if len(self.applied) > 0:
@@ -623,11 +651,11 b' class queue:'
623 651 if os.path.exists(self.join(patch)):
624 652 raise util.Abort(_('patch "%s" already exists') % patch)
625 653 if opts.get('include') or opts.get('exclude') or pats:
626 fns, match, anypats = cmdutil.matchpats(repo, pats, opts)
627 m, a, r, d = repo.status(files=fns, match=match)[:4]
654 match = cmdutil.match(repo, pats, opts)
655 m, a, r, d = repo.status(match=match)[:4]
628 656 else:
629 657 m, a, r, d = self.check_localchanges(repo, force)
630 fns, match, anypats = cmdutil.matchpats(repo, m + a + r)
658 match = cmdutil.match(repo, m + a + r)
631 659 commitfiles = m + a + r
632 660 self.check_toppatch(repo)
633 661 wlock = repo.wlock()
@@ -665,14 +693,14 b' class queue:'
665 693 finally:
666 694 del wlock
667 695
668 def strip(self, repo, rev, update=True, backup="all"):
696 def strip(self, repo, rev, update=True, backup="all", force=None):
669 697 wlock = lock = None
670 698 try:
671 699 wlock = repo.wlock()
672 700 lock = repo.lock()
673 701
674 702 if update:
675 self.check_localchanges(repo, refresh=False)
703 self.check_localchanges(repo, force=force, refresh=False)
676 704 urev = self.qparents(repo, rev)
677 705 hg.clean(repo, urev)
678 706 repo.dirstate.write()
@@ -822,7 +850,7 b' class queue:'
822 850 self.ui.warn(_('cleaning up working directory...'))
823 851 node = repo.dirstate.parents()[0]
824 852 hg.revert(repo, node, None)
825 unknown = repo.status()[4]
853 unknown = repo.status(unknown=True)[4]
826 854 # only remove unknown files that we know we touched or
827 855 # created while patching
828 856 for f in unknown:
@@ -903,7 +931,7 b' class queue:'
903 931 qp = self.qparents(repo, rev)
904 932 changes = repo.changelog.read(qp)
905 933 mmap = repo.manifest.read(changes[0])
906 m, a, r, d, u = repo.status(qp, top)[:5]
934 m, a, r, d = repo.status(qp, top)[:4]
907 935 if d:
908 936 raise util.Abort("deletions found between repo revs")
909 937 for f in m:
@@ -937,10 +965,7 b' class queue:'
937 965 self.ui.write("No patches applied\n")
938 966 return
939 967 qp = self.qparents(repo, top)
940 if opts.get('git'):
941 self.diffopts().git = True
942 if opts.get('unified') is not None:
943 self.diffopts().context = opts['unified']
968 self._diffopts = patch.diffopts(self.ui, opts)
944 969 self.printdiff(repo, qp, files=pats, opts=opts)
945 970
946 971 def refresh(self, repo, pats=None, **opts):
@@ -1026,7 +1051,7 b' class queue:'
1026 1051
1027 1052 if opts.get('git'):
1028 1053 self.diffopts().git = True
1029 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1054 matchfn = cmdutil.match(repo, pats, opts)
1030 1055 tip = repo.changelog.tip()
1031 1056 if top == tip:
1032 1057 # if the top of our patch queue is also the tip, there is an
@@ -1039,21 +1064,19 b' class queue:'
1039 1064 # patch already
1040 1065 #
1041 1066 # this should really read:
1042 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
1067 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1043 1068 # but we do it backwards to take advantage of manifest/chlog
1044 1069 # caching against the next repo.status call
1045 1070 #
1046 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
1071 mm, aa, dd, aa2 = repo.status(patchparent, tip)[:4]
1047 1072 changes = repo.changelog.read(tip)
1048 1073 man = repo.manifest.read(changes[0])
1049 1074 aaa = aa[:]
1050 1075 if opts.get('short'):
1051 filelist = mm + aa + dd
1052 match = dict.fromkeys(filelist).__contains__
1076 match = cmdutil.matchfiles(repo, mm + aa + dd)
1053 1077 else:
1054 filelist = None
1055 match = util.always
1056 m, a, r, d, u = repo.status(files=filelist, match=match)[:5]
1078 match = cmdutil.matchall(repo)
1079 m, a, r, d = repo.status(match=match)[:4]
1057 1080
1058 1081 # we might end up with files that were added between
1059 1082 # tip and the dirstate parent, but then changed in the
@@ -1086,9 +1109,9 b' class queue:'
1086 1109 m = util.unique(mm)
1087 1110 r = util.unique(dd)
1088 1111 a = util.unique(aa)
1089 c = [filter(matchfn, l) for l in (m, a, r, [], u)]
1090 filelist = util.unique(c[0] + c[1] + c[2])
1091 patch.diff(repo, patchparent, files=filelist, match=matchfn,
1112 c = [filter(matchfn, l) for l in (m, a, r)]
1113 match = cmdutil.matchfiles(repo, util.unique(c[0] + c[1] + c[2]))
1114 patch.diff(repo, patchparent, match=match,
1092 1115 fp=patchf, changes=c, opts=self.diffopts())
1093 1116 patchf.close()
1094 1117
@@ -1146,7 +1169,7 b' class queue:'
1146 1169 self.applied_dirty = 1
1147 1170 self.strip(repo, top, update=False,
1148 1171 backup='strip')
1149 n = repo.commit(filelist, message, user, date, match=matchfn,
1172 n = repo.commit(match.files(), message, user, date, match=match,
1150 1173 force=1)
1151 1174 self.applied.append(statusentry(revlog.hex(n), patchfn))
1152 1175 self.removeundo(repo)
@@ -1236,8 +1259,7 b' class queue:'
1236 1259 self.guards_path)
1237 1260 and not fl.startswith('.')):
1238 1261 msng_list.append(fl)
1239 msng_list.sort()
1240 for x in msng_list:
1262 for x in util.sort(msng_list):
1241 1263 pfx = self.ui.verbose and ('D ') or ''
1242 1264 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1243 1265
@@ -1499,9 +1521,8 b' def delete(ui, repo, *patches, **opts):'
1499 1521 the --rev parameter. At least one patch or revision is required.
1500 1522
1501 1523 With --rev, mq will stop managing the named revisions (converting
1502 them to regular mercurial changesets). The patches must be applied
1503 and at the base of the stack. This option is useful when the patches
1504 have been applied upstream.
1524 them to regular mercurial changesets). The qfinish command should be
1525 used as an alternative for qdel -r, as the latter option is deprecated.
1505 1526
1506 1527 With --keep, the patch files are preserved in the patch directory."""
1507 1528 q = repo.mq
@@ -2086,7 +2107,7 b' def strip(ui, repo, rev, **opts):'
2086 2107 elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)):
2087 2108 update = False
2088 2109
2089 repo.mq.strip(repo, rev, backup=backup, update=update)
2110 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2090 2111 return 0
2091 2112
2092 2113 def select(ui, repo, *args, **opts):
@@ -2191,6 +2212,34 b' def select(ui, repo, *args, **opts):'
2191 2212 finally:
2192 2213 q.save_dirty()
2193 2214
2215 def finish(ui, repo, *revrange, **opts):
2216 """move applied patches into repository history
2217
2218 Finishes the specified revisions (corresponding to applied patches) by
2219 moving them out of mq control into regular repository history.
2220
2221 Accepts a revision range or the --all option. If --all is specified, all
2222 applied mq revisions are removed from mq control. Otherwise, the given
2223 revisions must be at the base of the stack of applied patches.
2224
2225 This can be especially useful if your changes have been applied to an
2226 upstream repository, or if you are about to push your changes to upstream.
2227 """
2228 if not opts['applied'] and not revrange:
2229 raise util.Abort(_('no revisions specified'))
2230 elif opts['applied']:
2231 revrange = ('qbase:qtip',) + revrange
2232
2233 q = repo.mq
2234 if not q.applied:
2235 ui.status(_('no patches applied\n'))
2236 return 0
2237
2238 revs = cmdutil.revrange(repo, revrange)
2239 q.finish(repo, revs)
2240 q.save_dirty()
2241 return 0
2242
2194 2243 def reposetup(ui, repo):
2195 2244 class mqrepo(repo.__class__):
2196 2245 def abort_if_wdir_patched(self, errmsg, force=False):
@@ -2267,7 +2316,7 b' def reposetup(ui, repo):'
2267 2316 # we might as well use it, but we won't save it.
2268 2317
2269 2318 # update the cache up to the tip
2270 self._updatebranchcache(partial, start, cl.count())
2319 self._updatebranchcache(partial, start, len(cl))
2271 2320
2272 2321 return partial
2273 2322
@@ -2300,10 +2349,8 b' cmdtable = {'
2300 2349 _('hg qcommit [OPTION]... [FILE]...')),
2301 2350 "^qdiff":
2302 2351 (diff,
2303 [('g', 'git', None, _('use git extended diff format')),
2304 ('U', 'unified', 3, _('number of lines of context to show')),
2305 ] + commands.walkopts,
2306 _('hg qdiff [-I] [-X] [-U NUM] [-g] [FILE]...')),
2352 commands.diffopts + commands.diffopts2 + commands.walkopts,
2353 _('hg qdiff [OPTION]... [FILE]...')),
2307 2354 "qdelete|qremove|qrm":
2308 2355 (delete,
2309 2356 [('k', 'keep', None, _('keep patch file')),
@@ -2395,9 +2442,14 b' cmdtable = {'
2395 2442 _('hg qseries [-ms]')),
2396 2443 "^strip":
2397 2444 (strip,
2398 [('b', 'backup', None, _('bundle unrelated changesets')),
2445 [('f', 'force', None, _('force removal with local changes')),
2446 ('b', 'backup', None, _('bundle unrelated changesets')),
2399 2447 ('n', 'nobackup', None, _('no backups'))],
2400 2448 _('hg strip [-f] [-b] [-n] REV')),
2401 2449 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2402 2450 "qunapplied": (unapplied, [] + seriesopts, _('hg qunapplied [-s] [PATCH]')),
2451 "qfinish":
2452 (finish,
2453 [('a', 'applied', None, _('finish all applied changesets'))],
2454 _('hg qfinish [-a] [REV...]')),
2403 2455 }
@@ -156,9 +156,7 b' class notifier(object):'
156 156 if fnmatch.fnmatch(self.repo.root, pat):
157 157 for user in users.split(','):
158 158 subs[self.fixmail(user)] = 1
159 subs = subs.keys()
160 subs.sort()
161 return subs
159 return util.sort(subs)
162 160
163 161 def url(self, path=None):
164 162 return self.ui.config('web', 'baseurl') + (path or self.root)
@@ -269,11 +267,11 b' def hook(ui, repo, hooktype, node=None, '
269 267 node = bin(node)
270 268 ui.pushbuffer()
271 269 if hooktype == 'changegroup':
272 start = repo.changelog.rev(node)
273 end = repo.changelog.count()
270 start = repo[node].rev()
271 end = len(repo)
274 272 count = end - start
275 273 for rev in xrange(start, end):
276 n.node(repo.changelog.node(rev))
274 n.node(repo[node].rev())
277 275 n.diff(node, repo.changelog.tip())
278 276 else:
279 277 count = 1
@@ -10,26 +10,56 b''
10 10 # [extension]
11 11 # hgext.pager =
12 12 #
13 # To set the pager that should be used, set the application variable:
14 #
15 # [pager]
16 # pager = LESS='FSRX' less
17 #
18 # If no pager is set, the pager extensions uses the environment
19 # variable $PAGER. If neither pager.pager, nor $PAGER is set, no pager
20 # is used.
21 #
22 # If you notice "BROKEN PIPE" error messages, you can disable them
23 # by setting:
24 #
25 # [pager]
26 # quiet = True
13 # Run "hg help pager" to get info on configuration.
14
15 '''browse command output with external pager
16
17 To set the pager that should be used, set the application variable:
18
19 [pager]
20 pager = LESS='FSRX' less
21
22 If no pager is set, the pager extensions uses the environment
23 variable $PAGER. If neither pager.pager, nor $PAGER is set, no pager
24 is used.
25
26 If you notice "BROKEN PIPE" error messages, you can disable them
27 by setting:
28
29 [pager]
30 quiet = True
31
32 You can disable the pager for certain commands by adding them to the
33 pager.ignore list:
34
35 [pager]
36 ignore = version, help, update
37
38 You can also enable the pager only for certain commands using pager.attend:
39
40 [pager]
41 attend = log
42
43 If pager.attend is present, pager.ignore will be ignored.
44
45 To ignore global commands like "hg version" or "hg help", you have to specify
46 them in the global .hgrc
47 '''
27 48
28 49 import sys, os, signal
50 from mercurial import dispatch, util
29 51
30 52 def uisetup(ui):
31 p = ui.config("pager", "pager", os.environ.get("PAGER"))
32 if p and sys.stdout.isatty() and '--debugger' not in sys.argv:
33 if ui.configbool('pager', 'quiet'):
34 signal.signal(signal.SIGPIPE, signal.SIG_DFL)
35 sys.stderr = sys.stdout = os.popen(p, "wb")
53 def pagecmd(ui, options, cmd, cmdfunc):
54 p = ui.config("pager", "pager", os.environ.get("PAGER"))
55 if p and sys.stdout.isatty() and '--debugger' not in sys.argv:
56 attend = ui.configlist('pager', 'attend')
57 if (cmd in attend or
58 (cmd not in ui.configlist('pager', 'ignore') and not attend)):
59 sys.stderr = sys.stdout = util.popen(p, "wb")
60 if ui.configbool('pager', 'quiet'):
61 signal.signal(signal.SIGPIPE, signal.SIG_DFL)
62 return oldrun(ui, options, cmd, cmdfunc)
63
64 oldrun = dispatch._runcommand
65 dispatch._runcommand = pagecmd
@@ -1,72 +1,69 b''
1 # Command for sending a collection of Mercurial changesets as a series
2 # of patch emails.
3 #
4 # The series is started off with a "[PATCH 0 of N]" introduction,
5 # which describes the series as a whole.
6 #
7 # Each patch email has a Subject line of "[PATCH M of N] ...", using
8 # the first line of the changeset description as the subject text.
9 # The message contains two or three body parts:
10 #
11 # The remainder of the changeset description.
12 #
13 # [Optional] If the diffstat program is installed, the result of
14 # running diffstat on the patch.
15 #
16 # The patch itself, as generated by "hg export".
17 #
18 # Each message refers to all of its predecessors using the In-Reply-To
19 # and References headers, so they will show up as a sequence in
20 # threaded mail and news readers, and in mail archives.
21 #
22 # For each changeset, you will be prompted with a diffstat summary and
23 # the changeset summary, so you can be sure you are sending the right
24 # changes.
25 #
26 # To enable this extension:
27 #
28 # [extensions]
29 # hgext.patchbomb =
30 #
31 # To configure other defaults, add a section like this to your hgrc
32 # file:
33 #
34 # [email]
35 # from = My Name <my@email>
36 # to = recipient1, recipient2, ...
37 # cc = cc1, cc2, ...
38 # bcc = bcc1, bcc2, ...
39 #
40 # Then you can use the "hg email" command to mail a series of changesets
41 # as a patchbomb.
42 #
43 # To avoid sending patches prematurely, it is a good idea to first run
44 # the "email" command with the "-n" option (test only). You will be
45 # prompted for an email recipient address, a subject an an introductory
46 # message describing the patches of your patchbomb. Then when all is
47 # done, patchbomb messages are displayed. If PAGER environment variable
48 # is set, your pager will be fired up once for each patchbomb message, so
49 # you can verify everything is alright.
50 #
51 # The "-m" (mbox) option is also very useful. Instead of previewing
52 # each patchbomb message in a pager or sending the messages directly,
53 # it will create a UNIX mailbox file with the patch emails. This
54 # mailbox file can be previewed with any mail user agent which supports
55 # UNIX mbox files, i.e. with mutt:
56 #
57 # % mutt -R -f mbox
58 #
59 # When you are previewing the patchbomb messages, you can use `formail'
60 # (a utility that is commonly installed as part of the procmail package),
61 # to send each message out:
62 #
63 # % formail -s sendmail -bm -t < mbox
64 #
65 # That should be all. Now your patchbomb is on its way out.
1 '''sending Mercurial changesets as a series of patch emails
2
3 The series is started off with a "[PATCH 0 of N]" introduction,
4 which describes the series as a whole.
5
6 Each patch email has a Subject line of "[PATCH M of N] ...", using
7 the first line of the changeset description as the subject text.
8 The message contains two or three body parts:
9
10 The remainder of the changeset description.
11
12 [Optional] If the diffstat program is installed, the result of
13 running diffstat on the patch.
14
15 The patch itself, as generated by "hg export".
16
17 Each message refers to all of its predecessors using the In-Reply-To
18 and References headers, so they will show up as a sequence in
19 threaded mail and news readers, and in mail archives.
20
21 For each changeset, you will be prompted with a diffstat summary and
22 the changeset summary, so you can be sure you are sending the right changes.
23
24 To enable this extension:
25
26 [extensions]
27 hgext.patchbomb =
28
29 To configure other defaults, add a section like this to your hgrc file:
66 30
67 import os, errno, socket, tempfile
31 [email]
32 from = My Name <my@email>
33 to = recipient1, recipient2, ...
34 cc = cc1, cc2, ...
35 bcc = bcc1, bcc2, ...
36
37 Then you can use the "hg email" command to mail a series of changesets
38 as a patchbomb.
39
40 To avoid sending patches prematurely, it is a good idea to first run
41 the "email" command with the "-n" option (test only). You will be
42 prompted for an email recipient address, a subject an an introductory
43 message describing the patches of your patchbomb. Then when all is
44 done, patchbomb messages are displayed. If PAGER environment variable
45 is set, your pager will be fired up once for each patchbomb message, so
46 you can verify everything is alright.
47
48 The "-m" (mbox) option is also very useful. Instead of previewing
49 each patchbomb message in a pager or sending the messages directly,
50 it will create a UNIX mailbox file with the patch emails. This
51 mailbox file can be previewed with any mail user agent which supports
52 UNIX mbox files, i.e. with mutt:
53
54 % mutt -R -f mbox
55
56 When you are previewing the patchbomb messages, you can use `formail'
57 (a utility that is commonly installed as part of the procmail package),
58 to send each message out:
59
60 % formail -s sendmail -bm -t < mbox
61
62 That should be all. Now your patchbomb is on its way out.'''
63
64 import os, errno, socket, tempfile, cStringIO
68 65 import email.MIMEMultipart, email.MIMEText, email.MIMEBase
69 import email.Utils, email.Encoders
66 import email.Utils, email.Encoders, email.Generator
70 67 from mercurial import cmdutil, commands, hg, mail, patch, util
71 68 from mercurial.i18n import _
72 69 from mercurial.node import bin
@@ -404,11 +401,12 b' def patchbomb(ui, repo, *revs, **opts):'
404 401 ui.status('Displaying ', m['Subject'], ' ...\n')
405 402 ui.flush()
406 403 if 'PAGER' in os.environ:
407 fp = os.popen(os.environ['PAGER'], 'w')
404 fp = util.popen(os.environ['PAGER'], 'w')
408 405 else:
409 406 fp = ui
407 generator = email.Generator.Generator(fp, mangle_from_=False)
410 408 try:
411 fp.write(m.as_string(0))
409 generator.flatten(m, 0)
412 410 fp.write('\n')
413 411 except IOError, inst:
414 412 if inst.errno != errno.EPIPE:
@@ -418,9 +416,10 b' def patchbomb(ui, repo, *revs, **opts):'
418 416 elif opts.get('mbox'):
419 417 ui.status('Writing ', m['Subject'], ' ...\n')
420 418 fp = open(opts.get('mbox'), 'In-Reply-To' in m and 'ab+' or 'wb+')
419 generator = email.Generator.Generator(fp, mangle_from_=True)
421 420 date = util.datestr(start_time, '%a %b %d %H:%M:%S %Y')
422 421 fp.write('From %s %s\n' % (sender_addr, date))
423 fp.write(m.as_string(0))
422 generator.flatten(m, 0)
424 423 fp.write('\n\n')
425 424 fp.close()
426 425 else:
@@ -429,7 +428,10 b' def patchbomb(ui, repo, *revs, **opts):'
429 428 ui.status('Sending ', m['Subject'], ' ...\n')
430 429 # Exim does not remove the Bcc field
431 430 del m['Bcc']
432 sendmail(sender, to + bcc + cc, m.as_string(0))
431 fp = cStringIO.StringIO()
432 generator = email.Generator.Generator(fp, mangle_from_=False)
433 generator.flatten(m, 0)
434 sendmail(sender, to + bcc + cc, fp.getvalue())
433 435
434 436 cmdtable = {
435 437 "email":
@@ -27,79 +27,10 b''
27 27 # along with this program; if not, write to the Free Software
28 28 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
29 29
30 from mercurial import util, commands
30 from mercurial import util, commands, cmdutil
31 31 from mercurial.i18n import _
32 32 import os
33 33
34 def dopurge(ui, repo, dirs=None, act=True, ignored=False,
35 abort_on_err=False, eol='\n',
36 force=False, include=None, exclude=None):
37 def error(msg):
38 if abort_on_err:
39 raise util.Abort(msg)
40 else:
41 ui.warn(_('warning: %s\n') % msg)
42
43 def remove(remove_func, name):
44 if act:
45 try:
46 remove_func(os.path.join(repo.root, name))
47 except OSError, e:
48 error(_('%s cannot be removed') % name)
49 else:
50 ui.write('%s%s' % (name, eol))
51
52 if not force:
53 _check_fs(ui, repo)
54
55 directories = []
56 files = []
57 missing = []
58 roots, match, anypats = util.cmdmatcher(repo.root, repo.getcwd(), dirs,
59 include, exclude)
60 for src, f, st in repo.dirstate.statwalk(files=roots, match=match,
61 ignored=ignored, directories=True):
62 if src == 'd':
63 directories.append(f)
64 elif src == 'm':
65 missing.append(f)
66 elif src == 'f' and f not in repo.dirstate:
67 files.append(f)
68
69 directories.sort()
70
71 for f in files:
72 if f not in repo.dirstate:
73 ui.note(_('Removing file %s\n') % f)
74 remove(os.remove, f)
75
76 for f in directories[::-1]:
77 if match(f) and not os.listdir(repo.wjoin(f)):
78 ui.note(_('Removing directory %s\n') % f)
79 remove(os.rmdir, f)
80
81 def _check_fs(ui, repo):
82 """Abort if there is the chance of having problems with name-mangling fs
83
84 In a name mangling filesystem (e.g. a case insensitive one)
85 dirstate.walk() can yield filenames different from the ones
86 stored in the dirstate. This already confuses the status and
87 add commands, but with purge this may cause data loss.
88
89 To prevent this, this function will abort if there are uncommitted
90 changes.
91 """
92
93 # We can't use (files, match) to do a partial walk here - we wouldn't
94 # notice a modified README file if the user ran "hg purge readme"
95 modified, added, removed, deleted = repo.status()[:4]
96 if modified or added or removed or deleted:
97 if not util.checkfolding(repo.path) and not ui.quiet:
98 ui.warn(_("Purging on name mangling filesystems is not "
99 "fully supported.\n"))
100 raise util.Abort(_("outstanding uncommitted changes"))
101
102
103 34 def purge(ui, repo, *dirs, **opts):
104 35 '''removes files not tracked by mercurial
105 36
@@ -125,25 +56,42 b' def purge(ui, repo, *dirs, **opts):'
125 56 files that this program would delete use the --print option.
126 57 '''
127 58 act = not opts['print']
128 ignored = bool(opts['all'])
129 abort_on_err = bool(opts['abort_on_err'])
130 eol = opts['print0'] and '\0' or '\n'
131 if eol == '\0':
132 # --print0 implies --print
133 act = False
134 force = bool(opts['force'])
135 include = opts['include']
136 exclude = opts['exclude']
137 dopurge(ui, repo, dirs, act, ignored, abort_on_err,
138 eol, force, include, exclude)
59 eol = '\n'
60 if opts['print0']:
61 eol = '\0'
62 act = False # --print0 implies --print
139 63
64 def remove(remove_func, name):
65 if act:
66 try:
67 remove_func(os.path.join(repo.root, name))
68 except OSError, e:
69 m = _('%s cannot be removed') % name
70 if opts['abort_on_err']:
71 raise util.Abort(m)
72 ui.warn(_('warning: %s\n') % m)
73 else:
74 ui.write('%s%s' % (name, eol))
75
76 directories = []
77 match = cmdutil.match(repo, dirs, opts)
78 match.dir = directories.append
79 status = repo.status(match=match, ignored=opts['all'], unknown=True)
80
81 for f in util.sort(status[4] + status[5]):
82 ui.note(_('Removing file %s\n') % f)
83 remove(os.remove, f)
84
85 for f in util.sort(directories)[::-1]:
86 if match(f) and not os.listdir(repo.wjoin(f)):
87 ui.note(_('Removing directory %s\n') % f)
88 remove(os.rmdir, f)
140 89
141 90 cmdtable = {
142 91 'purge|clean':
143 92 (purge,
144 93 [('a', 'abort-on-err', None, _('abort if an error occurs')),
145 94 ('', 'all', None, _('purge ignored files too')),
146 ('f', 'force', None, _('purge even when there are uncommitted changes')),
147 95 ('p', 'print', None, _('print the file names instead of deleting them')),
148 96 ('0', 'print0', None, _('end filenames with NUL, for use with xargs'
149 97 ' (implies -p)')),
@@ -389,7 +389,7 b' def dorecord(ui, repo, committer, *pats,'
389 389 if not ui.interactive:
390 390 raise util.Abort(_('running non-interactively, use commit instead'))
391 391
392 def recordfunc(ui, repo, files, message, match, opts):
392 def recordfunc(ui, repo, message, match, opts):
393 393 """This is generic record driver.
394 394
395 395 It's job is to interactively filter local changes, and accordingly
@@ -402,16 +402,16 b' def dorecord(ui, repo, committer, *pats,'
402 402 In the end we'll record intresting changes, and everything else will be
403 403 left in place, so the user can continue his work.
404 404 """
405 if files:
405 if match.files():
406 406 changes = None
407 407 else:
408 changes = repo.status(files=files, match=match)[:5]
409 modified, added, removed = changes[:3]
410 files = modified + added + removed
408 changes = repo.status(match=match)[:3]
409 modified, added, removed = changes
410 match = cmdutil.matchfiles(repo, modified + added + removed)
411 411 diffopts = mdiff.diffopts(git=True, nodates=True)
412 412 fp = cStringIO.StringIO()
413 patch.diff(repo, repo.dirstate.parents()[0], files=files,
414 match=match, changes=changes, opts=diffopts, fp=fp)
413 patch.diff(repo, repo.dirstate.parents()[0], match=match,
414 changes=changes, opts=diffopts, fp=fp)
415 415 fp.seek(0)
416 416
417 417 # 1. filter patch, so we have intending-to apply subset of it
@@ -423,14 +423,15 b' def dorecord(ui, repo, committer, *pats,'
423 423 try: contenders.update(dict.fromkeys(h.files()))
424 424 except AttributeError: pass
425 425
426 newfiles = [f for f in files if f in contenders]
426 newfiles = [f for f in match.files() if f in contenders]
427 427
428 428 if not newfiles:
429 429 ui.status(_('no changes to record\n'))
430 430 return 0
431 431
432 432 if changes is None:
433 changes = repo.status(files=newfiles, match=match)[:5]
433 match = cmdutil.matchfiles(repo, newfiles)
434 changes = repo.status(match=match)
434 435 modified = dict.fromkeys(changes[0])
435 436
436 437 # 2. backup changed files, so we can restore them in the end
@@ -88,9 +88,7 b' class transplanter:'
88 88
89 89 def apply(self, repo, source, revmap, merges, opts={}):
90 90 '''apply the revisions in revmap one by one in revision order'''
91 revs = revmap.keys()
92 revs.sort()
93
91 revs = util.sort(revmap)
94 92 p1, p2 = repo.dirstate.parents()
95 93 pulls = []
96 94 diffopts = patch.diffopts(self.ui, opts)
@@ -310,9 +308,7 b' class transplanter:'
310 308 if not os.path.isdir(self.path):
311 309 os.mkdir(self.path)
312 310 series = self.opener('series', 'w')
313 revs = revmap.keys()
314 revs.sort()
315 for rev in revs:
311 for rev in util.sort(revmap):
316 312 series.write(revlog.hex(revmap[rev]) + '\n')
317 313 if merges:
318 314 series.write('# Merges\n')
@@ -572,10 +568,6 b' def transplant(ui, repo, *revs, **opts):'
572 568 for r in merges:
573 569 revmap[source.changelog.rev(r)] = r
574 570
575 revs = revmap.keys()
576 revs.sort()
577 pulls = []
578
579 571 tp.apply(repo, source, revmap, merges, opts)
580 572 finally:
581 573 if bundle:
@@ -1,4 +1,4 b''
1 # win32text.py - LF <-> CRLF translation utilities for Windows users
1 # win32text.py - LF <-> CRLF/CR translation utilities for Windows/Mac users
2 2 #
3 3 # This software may be used and distributed according to the terms
4 4 # of the GNU General Public License, incorporated herein by reference.
@@ -9,95 +9,133 b''
9 9 # hgext.win32text =
10 10 # [encode]
11 11 # ** = cleverencode:
12 # # or ** = macencode:
12 13 # [decode]
13 14 # ** = cleverdecode:
15 # # or ** = macdecode:
14 16 #
15 # If not doing conversion, to make sure you do not commit CRLF by accident:
17 # If not doing conversion, to make sure you do not commit CRLF/CR by accident:
16 18 #
17 19 # [hooks]
18 20 # pretxncommit.crlf = python:hgext.win32text.forbidcrlf
21 # # or pretxncommit.cr = python:hgext.win32text.forbidcr
19 22 #
20 # To do the same check on a server to prevent CRLF from being pushed or pulled:
23 # To do the same check on a server to prevent CRLF/CR from being pushed or
24 # pulled:
21 25 #
22 26 # [hooks]
23 27 # pretxnchangegroup.crlf = python:hgext.win32text.forbidcrlf
28 # # or pretxnchangegroup.cr = python:hgext.win32text.forbidcr
24 29
25 30 from mercurial.i18n import gettext as _
26 31 from mercurial.node import bin, short
32 from mercurial import util
27 33 import re
28 34
29 35 # regexp for single LF without CR preceding.
30 36 re_single_lf = re.compile('(^|[^\r])\n', re.MULTILINE)
31 37
32 def dumbdecode(s, cmd, ui=None, repo=None, filename=None, **kwargs):
33 # warn if already has CRLF in repository.
38 newlinestr = {'\r\n': 'CRLF', '\r': 'CR'}
39 filterstr = {'\r\n': 'clever', '\r': 'mac'}
40
41 def checknewline(s, newline, ui=None, repo=None, filename=None):
42 # warn if already has 'newline' in repository.
34 43 # it might cause unexpected eol conversion.
35 44 # see issue 302:
36 45 # http://www.selenic.com/mercurial/bts/issue302
37 if '\r\n' in s and ui and filename and repo:
38 ui.warn(_('WARNING: %s already has CRLF line endings\n'
46 if newline in s and ui and filename and repo:
47 ui.warn(_('WARNING: %s already has %s line endings\n'
39 48 'and does not need EOL conversion by the win32text plugin.\n'
40 49 'Before your next commit, please reconsider your '
41 50 'encode/decode settings in \nMercurial.ini or %s.\n') %
42 (filename, repo.join('hgrc')))
51 (filename, newlinestr[newline], repo.join('hgrc')))
52
53 def dumbdecode(s, cmd, **kwargs):
54 checknewline(s, '\r\n', **kwargs)
43 55 # replace single LF to CRLF
44 56 return re_single_lf.sub('\\1\r\n', s)
45 57
46 58 def dumbencode(s, cmd):
47 59 return s.replace('\r\n', '\n')
48 60
49 def clevertest(s, cmd):
50 if '\0' in s: return False
51 return True
61 def macdumbdecode(s, cmd, **kwargs):
62 checknewline(s, '\r', **kwargs)
63 return s.replace('\n', '\r')
64
65 def macdumbencode(s, cmd):
66 return s.replace('\r', '\n')
52 67
53 68 def cleverdecode(s, cmd, **kwargs):
54 if clevertest(s, cmd):
69 if not util.binary(s):
55 70 return dumbdecode(s, cmd, **kwargs)
56 71 return s
57 72
58 73 def cleverencode(s, cmd):
59 if clevertest(s, cmd):
74 if not util.binary(s):
60 75 return dumbencode(s, cmd)
61 76 return s
62 77
78 def macdecode(s, cmd, **kwargs):
79 if not util.binary(s):
80 return macdumbdecode(s, cmd, **kwargs)
81 return s
82
83 def macencode(s, cmd):
84 if not util.binary(s):
85 return macdumbencode(s, cmd)
86 return s
87
63 88 _filters = {
64 89 'dumbdecode:': dumbdecode,
65 90 'dumbencode:': dumbencode,
66 91 'cleverdecode:': cleverdecode,
67 92 'cleverencode:': cleverencode,
93 'macdumbdecode:': macdumbdecode,
94 'macdumbencode:': macdumbencode,
95 'macdecode:': macdecode,
96 'macencode:': macencode,
68 97 }
69 98
70 def forbidcrlf(ui, repo, hooktype, node, **kwargs):
99 def forbidnewline(ui, repo, hooktype, node, newline, **kwargs):
71 100 halt = False
72 for rev in xrange(repo.changelog.rev(bin(node)), repo.changelog.count()):
73 c = repo.changectx(rev)
101 for rev in xrange(repo[node].rev(), len(repo)):
102 c = repo[rev]
74 103 for f in c.files():
75 104 if f not in c:
76 105 continue
77 106 data = c[f].data()
78 if '\0' not in data and '\r\n' in data:
107 if not util.binary(data) and newline in data:
79 108 if not halt:
80 109 ui.warn(_('Attempt to commit or push text file(s) '
81 'using CRLF line endings\n'))
110 'using %s line endings\n') %
111 newlinestr[newline])
82 112 ui.warn(_('in %s: %s\n') % (short(c.node()), f))
83 113 halt = True
84 114 if halt and hooktype == 'pretxnchangegroup':
115 crlf = newlinestr[newline].lower()
116 filter = filterstr[newline]
85 117 ui.warn(_('\nTo prevent this mistake in your local repository,\n'
86 118 'add to Mercurial.ini or .hg/hgrc:\n'
87 119 '\n'
88 120 '[hooks]\n'
89 'pretxncommit.crlf = python:hgext.win32text.forbidcrlf\n'
121 'pretxncommit.%s = python:hgext.win32text.forbid%s\n'
90 122 '\n'
91 123 'and also consider adding:\n'
92 124 '\n'
93 125 '[extensions]\n'
94 126 'hgext.win32text =\n'
95 127 '[encode]\n'
96 '** = cleverencode:\n'
128 '** = %sencode:\n'
97 129 '[decode]\n'
98 '** = cleverdecode:\n'))
130 '** = %sdecode:\n') % (crlf, crlf, filter, filter))
99 131 return halt
100 132
133 def forbidcrlf(ui, repo, hooktype, node, **kwargs):
134 return forbidnewline(ui, repo, hooktype, node, '\r\n', **kwargs)
135
136 def forbidcr(ui, repo, hooktype, node, **kwargs):
137 return forbidnewline(ui, repo, hooktype, node, '\r', **kwargs)
138
101 139 def reposetup(ui, repo):
102 140 if not repo.local():
103 141 return
@@ -52,7 +52,8 b' class tarit:'
52 52 def _write_gzip_header(self):
53 53 self.fileobj.write('\037\213') # magic header
54 54 self.fileobj.write('\010') # compression method
55 fname = self.filename[:-3]
55 # Python 2.6 deprecates self.filename
56 fname = getattr(self, 'name', None) or self.filename
56 57 flags = 0
57 58 if fname:
58 59 flags = gzip.FNAME
@@ -207,18 +208,17 b' def archive(repo, dest, node, kind, deco'
207 208 data = repo.wwritedata(name, data)
208 209 archiver.addfile(name, mode, islink, data)
209 210
210 ctx = repo.changectx(node)
211 211 if kind not in archivers:
212 212 raise util.Abort(_("unknown archive type '%s'" % kind))
213
214 ctx = repo[node]
213 215 archiver = archivers[kind](dest, prefix, mtime or ctx.date()[0])
214 m = ctx.manifest()
215 items = m.items()
216 items.sort()
216
217 217 if repo.ui.configbool("ui", "archivemeta", True):
218 218 write('.hg_archival.txt', 0644, False,
219 219 lambda: 'repo: %s\nnode: %s\n' % (
220 220 hex(repo.changelog.node(0)), hex(node)))
221 for filename, filenode in items:
222 write(filename, m.execf(filename) and 0755 or 0644, m.linkf(filename),
223 lambda: repo.file(filename).read(filenode))
221 for f in ctx:
222 ff = ctx.flags(f)
223 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, ctx[f].data)
224 224 archiver.done()
@@ -12,7 +12,7 b' of the GNU General Public License, incor'
12 12
13 13 from node import hex, nullid, short
14 14 from i18n import _
15 import changegroup, util, os, struct, bz2, tempfile, shutil, mdiff
15 import changegroup, util, os, struct, bz2, zlib, tempfile, shutil, mdiff
16 16 import repo, localrepo, changelog, manifest, filelog, revlog
17 17
18 18 class bundlerevlog(revlog.revlog):
@@ -34,7 +34,7 b' class bundlerevlog(revlog.revlog):'
34 34 for chunk in changegroup.chunkiter(bundlefile):
35 35 pos = bundlefile.tell()
36 36 yield chunk, pos - len(chunk)
37 n = self.count()
37 n = len(self)
38 38 prev = None
39 39 for chunk, start in chunkpositer():
40 40 size = len(chunk)
@@ -127,7 +127,7 b' class bundlerevlog(revlog.revlog):'
127 127
128 128 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
129 129 raise NotImplementedError
130 def addgroup(self, revs, linkmapper, transaction, unique=0):
130 def addgroup(self, revs, linkmapper, transaction):
131 131 raise NotImplementedError
132 132 def strip(self, rev, minlink):
133 133 raise NotImplementedError
@@ -173,14 +173,17 b' class bundlerepository(localrepo.localre'
173 173 raise util.Abort(_("%s: not a Mercurial bundle file") % bundlename)
174 174 elif not header.startswith("HG10"):
175 175 raise util.Abort(_("%s: unknown bundle version") % bundlename)
176 elif header == "HG10BZ":
176 elif (header == "HG10BZ") or (header == "HG10GZ"):
177 177 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
178 178 suffix=".hg10un", dir=self.path)
179 179 self.tempfile = temp
180 180 fptemp = os.fdopen(fdtemp, 'wb')
181 181 def generator(f):
182 zd = bz2.BZ2Decompressor()
183 zd.decompress("BZ")
182 if header == "HG10BZ":
183 zd = bz2.BZ2Decompressor()
184 zd.decompress("BZ")
185 elif header == "HG10GZ":
186 zd = zlib.decompressobj()
184 187 for chunk in f:
185 188 yield zd.decompress(chunk)
186 189 gen = generator(util.filechunkiter(self.bundlefile, 4096))
@@ -82,7 +82,7 b' class changelog(revlog):'
82 82 "delay visibility of index updates to other readers"
83 83 self._realopener = self.opener
84 84 self.opener = self._delayopener
85 self._delaycount = self.count()
85 self._delaycount = len(self)
86 86 self._delaybuf = []
87 87 self._delayname = None
88 88
@@ -108,7 +108,7 b' class changelog(revlog):'
108 108 # if we're doing an initial clone, divert to another file
109 109 if self._delaycount == 0:
110 110 self._delayname = fp.name
111 if not self.count():
111 if not len(self):
112 112 # make sure to truncate the file
113 113 mode = mode.replace('a', 'w')
114 114 return self._realopener(name + ".a", mode)
@@ -130,9 +130,7 b' class changelog(revlog):'
130 130
131 131 def encode_extra(self, d):
132 132 # keys must be sorted to produce a deterministic changelog entry
133 keys = d.keys()
134 keys.sort()
135 items = [_string_escape('%s:%s' % (k, d[k])) for k in keys]
133 items = [_string_escape('%s:%s' % (k, d[k])) for k in util.sort(d)]
136 134 return "\0".join(items)
137 135
138 136 def read(self, node):
@@ -175,7 +173,7 b' class changelog(revlog):'
175 173 files = l[3:]
176 174 return (manifest, user, (time, timezone), files, desc, extra)
177 175
178 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
176 def add(self, manifest, files, desc, transaction, p1=None, p2=None,
179 177 user=None, date=None, extra={}):
180 178
181 179 user, desc = util.fromlocal(user), util.fromlocal(desc)
@@ -189,7 +187,6 b' class changelog(revlog):'
189 187 if extra:
190 188 extra = self.encode_extra(extra)
191 189 parseddate = "%s %s" % (parseddate, extra)
192 list.sort()
193 l = [hex(manifest), user, parseddate] + list + ["", desc]
190 l = [hex(manifest), user, parseddate] + util.sort(files) + ["", desc]
194 191 text = "\n".join(l)
195 return self.addrevision(text, transaction, self.count(), p1, p2)
192 return self.addrevision(text, transaction, len(self), p1, p2)
@@ -9,6 +9,7 b' from node import hex, nullid, nullrev, s'
9 9 from i18n import _
10 10 import os, sys, bisect, stat
11 11 import mdiff, bdiff, util, templater, templatefilters, patch, errno
12 import match as _match
12 13
13 14 revrangesep = ':'
14 15
@@ -125,7 +126,7 b' def revpair(repo, revs):'
125 126 if revrangesep in revs[0]:
126 127 start, end = revs[0].split(revrangesep, 1)
127 128 start = revfix(repo, start, 0)
128 end = revfix(repo, end, repo.changelog.count() - 1)
129 end = revfix(repo, end, len(repo) - 1)
129 130 else:
130 131 start = revfix(repo, revs[0], None)
131 132 elif len(revs) == 2:
@@ -150,7 +151,7 b' def revrange(repo, revs):'
150 151 if revrangesep in spec:
151 152 start, end = spec.split(revrangesep, 1)
152 153 start = revfix(repo, start, 0)
153 end = revfix(repo, end, repo.changelog.count() - 1)
154 end = revfix(repo, end, len(repo) - 1)
154 155 step = start > end and -1 or 1
155 156 for rev in xrange(start, end+step, step):
156 157 if rev in seen:
@@ -223,27 +224,28 b' def make_file(repo, pat, node=None,'
223 224 pathname),
224 225 mode)
225 226
226 def matchpats(repo, pats=[], opts={}, globbed=False, default=None):
227 cwd = repo.getcwd()
228 return util.cmdmatcher(repo.root, cwd, pats or [], opts.get('include'),
229 opts.get('exclude'), globbed=globbed,
230 default=default)
227 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
228 if not globbed and default == 'relpath':
229 pats = util.expand_glob(pats or [])
230 m = _match.match(repo.root, repo.getcwd(), pats,
231 opts.get('include'), opts.get('exclude'), default)
232 def badfn(f, msg):
233 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
234 return False
235 m.bad = badfn
236 return m
231 237
232 def walk(repo, pats=[], opts={}, node=None, badmatch=None, globbed=False,
233 default=None):
234 files, matchfn, anypats = matchpats(repo, pats, opts, globbed=globbed,
235 default=default)
236 exact = dict.fromkeys(files)
237 cwd = repo.getcwd()
238 for src, fn in repo.walk(node=node, files=files, match=matchfn,
239 badmatch=badmatch):
240 yield src, fn, repo.pathto(fn, cwd), fn in exact
238 def matchall(repo):
239 return _match.always(repo.root, repo.getcwd())
240
241 def matchfiles(repo, files):
242 return _match.exact(repo.root, repo.getcwd(), files)
241 243
242 244 def findrenames(repo, added=None, removed=None, threshold=0.5):
243 245 '''find renamed files -- yields (before, after, score) tuples'''
244 246 if added is None or removed is None:
245 247 added, removed = repo.status()[1:3]
246 ctx = repo.changectx()
248 ctx = repo['.']
247 249 for a in added:
248 250 aa = repo.wread(a)
249 251 bestname, bestscore = None, threshold
@@ -275,16 +277,19 b' def addremove(repo, pats=[], opts={}, dr'
275 277 add, remove = [], []
276 278 mapping = {}
277 279 audit_path = util.path_auditor(repo.root)
278 for src, abs, rel, exact in walk(repo, pats, opts):
280 m = match(repo, pats, opts)
281 for abs in repo.walk(m):
279 282 target = repo.wjoin(abs)
280 283 good = True
281 284 try:
282 285 audit_path(abs)
283 286 except:
284 287 good = False
285 if src == 'f' and good and abs not in repo.dirstate:
288 rel = m.rel(abs)
289 exact = m.exact(abs)
290 if good and abs not in repo.dirstate:
286 291 add.append(abs)
287 mapping[abs] = rel, exact
292 mapping[abs] = rel, m.exact(abs)
288 293 if repo.ui.verbose or not exact:
289 294 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
290 295 if repo.dirstate[abs] != 'r' and (not good or not util.lexists(target)
@@ -319,8 +324,11 b' def copy(ui, repo, pats, opts, rename=Fa'
319 324
320 325 def walkpat(pat):
321 326 srcs = []
322 for tag, abs, rel, exact in walk(repo, [pat], opts, globbed=True):
327 m = match(repo, [pat], opts, globbed=True)
328 for abs in repo.walk(m):
323 329 state = repo.dirstate[abs]
330 rel = m.rel(abs)
331 exact = m.exact(abs)
324 332 if state in '?r':
325 333 if exact and state == '?':
326 334 ui.warn(_('%s: not copying - file is not managed\n') % rel)
@@ -645,9 +653,7 b' class changeset_printer(object):'
645 653 self.ui.write(_("copies: %s\n") % ' '.join(copies))
646 654
647 655 if extra and self.ui.debugflag:
648 extraitems = extra.items()
649 extraitems.sort()
650 for key, value in extraitems:
656 for key, value in util.sort(extra.items()):
651 657 self.ui.write(_("extra: %s=%s\n")
652 658 % (key, value.encode('string_escape')))
653 659
@@ -791,9 +797,7 b' class changeset_templater(changeset_prin'
791 797 return showlist('tag', self.repo.nodetags(changenode), **args)
792 798
793 799 def showextras(**args):
794 extras = changes[5].items()
795 extras.sort()
796 for key, value in extras:
800 for key, value in util.sort(changes[5].items()):
797 801 args = args.copy()
798 802 args.update(dict(key=key, value=value))
799 803 yield self.t('extra', **args)
@@ -889,7 +893,7 b' def show_changeset(ui, repo, opts, buffe'
889 893 # options
890 894 patch = False
891 895 if opts.get('patch'):
892 patch = matchfn or util.always
896 patch = matchfn or matchall(repo)
893 897
894 898 tmpl = opts.get('template')
895 899 mapfile = None
@@ -922,7 +926,7 b' def show_changeset(ui, repo, opts, buffe'
922 926 def finddate(ui, repo, date):
923 927 """Find the tipmost changeset that matches the given date spec"""
924 928 df = util.matchdate(date)
925 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
929 get = util.cachefunc(lambda r: repo[r].changeset())
926 930 changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
927 931 results = {}
928 932 for st, rev, fns in changeiter:
@@ -977,31 +981,31 b' def walkchangerevs(ui, repo, pats, chang'
977 981 if windowsize < sizelimit:
978 982 windowsize *= 2
979 983
980 files, matchfn, anypats = matchpats(repo, pats, opts)
984 m = match(repo, pats, opts)
981 985 follow = opts.get('follow') or opts.get('follow_first')
982 986
983 if repo.changelog.count() == 0:
984 return [], matchfn
987 if not len(repo):
988 return [], m
985 989
986 990 if follow:
987 defrange = '%s:0' % repo.changectx().rev()
991 defrange = '%s:0' % repo['.'].rev()
988 992 else:
989 993 defrange = '-1:0'
990 994 revs = revrange(repo, opts['rev'] or [defrange])
991 995 wanted = {}
992 slowpath = anypats or opts.get('removed')
996 slowpath = m.anypats() or opts.get('removed')
993 997 fncache = {}
994 998
995 if not slowpath and not files:
999 if not slowpath and not m.files():
996 1000 # No files, no patterns. Display all revs.
997 1001 wanted = dict.fromkeys(revs)
998 1002 copies = []
999 1003 if not slowpath:
1000 1004 # Only files, no patterns. Check the history of each file.
1001 1005 def filerevgen(filelog, node):
1002 cl_count = repo.changelog.count()
1006 cl_count = len(repo)
1003 1007 if node is None:
1004 last = filelog.count() - 1
1008 last = len(filelog) - 1
1005 1009 else:
1006 1010 last = filelog.rev(node)
1007 1011 for i, window in increasing_windows(last, nullrev):
@@ -1017,14 +1021,14 b' def walkchangerevs(ui, repo, pats, chang'
1017 1021 if rev[0] < cl_count:
1018 1022 yield rev
1019 1023 def iterfiles():
1020 for filename in files:
1024 for filename in m.files():
1021 1025 yield filename, None
1022 1026 for filename_node in copies:
1023 1027 yield filename_node
1024 1028 minrev, maxrev = min(revs), max(revs)
1025 1029 for file_, node in iterfiles():
1026 1030 filelog = repo.file(file_)
1027 if filelog.count() == 0:
1031 if not len(filelog):
1028 1032 if node is None:
1029 1033 # A zero count may be a directory or deleted file, so
1030 1034 # try to find matching entries on the slow path.
@@ -1050,13 +1054,12 b' def walkchangerevs(ui, repo, pats, chang'
1050 1054
1051 1055 # The slow path checks files modified in every changeset.
1052 1056 def changerevgen():
1053 for i, window in increasing_windows(repo.changelog.count()-1,
1054 nullrev):
1057 for i, window in increasing_windows(len(repo) - 1, nullrev):
1055 1058 for j in xrange(i - window, i + 1):
1056 1059 yield j, change(j)[3]
1057 1060
1058 1061 for rev, changefiles in changerevgen():
1059 matches = filter(matchfn, changefiles)
1062 matches = filter(m, changefiles)
1060 1063 if matches:
1061 1064 fncache[rev] = matches
1062 1065 wanted[rev] = 1
@@ -1109,7 +1112,7 b' def walkchangerevs(ui, repo, pats, chang'
1109 1112 del wanted[x]
1110 1113
1111 1114 def iterate():
1112 if follow and not files:
1115 if follow and not m.files():
1113 1116 ff = followfilter(onlyfirst=opts.get('follow_first'))
1114 1117 def want(rev):
1115 1118 if ff.match(rev) and rev in wanted:
@@ -1122,20 +1125,18 b' def walkchangerevs(ui, repo, pats, chang'
1122 1125 for i, window in increasing_windows(0, len(revs)):
1123 1126 yield 'window', revs[0] < revs[-1], revs[-1]
1124 1127 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1125 srevs = list(nrevs)
1126 srevs.sort()
1127 for rev in srevs:
1128 for rev in util.sort(list(nrevs)):
1128 1129 fns = fncache.get(rev)
1129 1130 if not fns:
1130 1131 def fns_generator():
1131 1132 for f in change(rev)[3]:
1132 if matchfn(f):
1133 if m(f):
1133 1134 yield f
1134 1135 fns = fns_generator()
1135 1136 yield 'add', rev, fns
1136 1137 for rev in nrevs:
1137 1138 yield 'iter', rev, None
1138 return iterate(), matchfn
1139 return iterate(), m
1139 1140
1140 1141 def commit(ui, repo, commitfunc, pats, opts):
1141 1142 '''commit the specified files or all outstanding changes'''
@@ -1149,13 +1150,12 b' def commit(ui, repo, commitfunc, pats, o'
1149 1150 if opts.get('addremove'):
1150 1151 addremove(repo, pats, opts)
1151 1152
1152 fns, match, anypats = matchpats(repo, pats, opts)
1153 m = match(repo, pats, opts)
1153 1154 if pats:
1154 status = repo.status(files=fns, match=match)
1155 modified, added, removed, deleted, unknown = status[:5]
1156 files = modified + added + removed
1155 modified, added, removed = repo.status(match=m)[:3]
1156 files = util.sort(modified + added + removed)
1157 1157 slist = None
1158 for f in fns:
1158 for f in m.files():
1159 1159 if f == '.':
1160 1160 continue
1161 1161 if f not in files:
@@ -1167,11 +1167,8 b' def commit(ui, repo, commitfunc, pats, o'
1167 1167 raise util.Abort(_("file %s not found!") % rel)
1168 1168 if stat.S_ISDIR(mode):
1169 1169 name = f + '/'
1170 if slist is None:
1171 slist = list(files)
1172 slist.sort()
1173 i = bisect.bisect(slist, name)
1174 if i >= len(slist) or not slist[i].startswith(name):
1170 i = bisect.bisect(files, name)
1171 if i >= len(files) or not files[i].startswith(name):
1175 1172 raise util.Abort(_("no match under directory %s!")
1176 1173 % rel)
1177 1174 elif not (stat.S_ISREG(mode) or stat.S_ISLNK(mode)):
@@ -1179,9 +1176,8 b' def commit(ui, repo, commitfunc, pats, o'
1179 1176 "unsupported file type!") % rel)
1180 1177 elif f not in repo.dirstate:
1181 1178 raise util.Abort(_("file %s not tracked!") % rel)
1182 else:
1183 files = []
1179 m = matchfiles(repo, files)
1184 1180 try:
1185 return commitfunc(ui, repo, files, message, match, opts)
1181 return commitfunc(ui, repo, message, m, opts)
1186 1182 except ValueError, inst:
1187 1183 raise util.Abort(str(inst))
This diff has been collapsed as it changes many lines, (665 lines changed) Show them Hide them
@@ -13,6 +13,7 b' import hg, util, revlog, bundlerepo, ext'
13 13 import difflib, patch, time, help, mdiff, tempfile
14 14 import version, socket
15 15 import archival, changegroup, cmdutil, hgweb.server, sshserver, hbisect
16 import merge as merge_
16 17
17 18 # Commands start here, listed alphabetically
18 19
@@ -30,15 +31,16 b' def add(ui, repo, *pats, **opts):'
30 31 rejected = None
31 32 exacts = {}
32 33 names = []
33 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
34 badmatch=util.always):
35 if exact:
34 m = cmdutil.match(repo, pats, opts)
35 m.bad = lambda x,y: True
36 for abs in repo.walk(m):
37 if m.exact(abs):
36 38 if ui.verbose:
37 ui.status(_('adding %s\n') % rel)
39 ui.status(_('adding %s\n') % m.rel(abs))
38 40 names.append(abs)
39 41 exacts[abs] = 1
40 42 elif abs not in repo.dirstate:
41 ui.status(_('adding %s\n') % rel)
43 ui.status(_('adding %s\n') % m.rel(abs))
42 44 names.append(abs)
43 45 if not opts.get('dry_run'):
44 46 rejected = repo.add(names)
@@ -53,11 +55,11 b' def addremove(ui, repo, *pats, **opts):'
53 55 New files are ignored if they match any of the patterns in .hgignore. As
54 56 with add, these changes take effect at the next commit.
55 57
56 Use the -s option to detect renamed files. With a parameter > 0,
58 Use the -s option to detect renamed files. With a parameter > 0,
57 59 this compares every removed file with every added file and records
58 those similar enough as renames. This option takes a percentage
60 those similar enough as renames. This option takes a percentage
59 61 between 0 (disabled) and 100 (files must be identical) as its
60 parameter. Detecting renamed files this way can be expensive.
62 parameter. Detecting renamed files this way can be expensive.
61 63 """
62 64 try:
63 65 sim = float(opts.get('similarity') or 0)
@@ -105,13 +107,13 b' def annotate(ui, repo, *pats, **opts):'
105 107 lastfunc = funcmap[-1]
106 108 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
107 109
108 ctx = repo.changectx(opts['rev'])
109
110 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
111 node=ctx.node()):
112 fctx = ctx.filectx(abs)
110 ctx = repo[opts['rev']]
111
112 m = cmdutil.match(repo, pats, opts)
113 for abs in ctx.walk(m):
114 fctx = ctx[abs]
113 115 if not opts['text'] and util.binary(fctx.data()):
114 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
116 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
115 117 continue
116 118
117 119 lines = fctx.annotate(follow=opts.get('follow'),
@@ -134,7 +136,7 b' def archive(ui, repo, dest, **opts):'
134 136 By default, the revision used is the parent of the working
135 137 directory; use "-r" to specify a different revision.
136 138
137 To specify the type of archive to create, use "-t". Valid
139 To specify the type of archive to create, use "-t". Valid
138 140 types are:
139 141
140 142 "files" (default): a directory full of files
@@ -148,18 +150,18 b' def archive(ui, repo, dest, **opts):'
148 150 using a format string; see "hg help export" for details.
149 151
150 152 Each member added to an archive file has a directory prefix
151 prepended. Use "-p" to specify a format string for the prefix.
153 prepended. Use "-p" to specify a format string for the prefix.
152 154 The default is the basename of the archive, with suffixes removed.
153 155 '''
154 156
155 ctx = repo.changectx(opts['rev'])
157 ctx = repo[opts['rev']]
156 158 if not ctx:
157 159 raise util.Abort(_('repository has no revisions'))
158 160 node = ctx.node()
159 161 dest = cmdutil.make_filename(repo, dest, node)
160 162 if os.path.realpath(dest) == repo.root:
161 163 raise util.Abort(_('repository root cannot be destination'))
162 dummy, matchfn, dummy = cmdutil.matchpats(repo, [], opts)
164 matchfn = cmdutil.match(repo, [], opts)
163 165 kind = opts.get('type') or 'files'
164 166 prefix = opts['prefix']
165 167 if dest == '-':
@@ -174,20 +176,20 b' def archive(ui, repo, dest, **opts):'
174 176 def backout(ui, repo, node=None, rev=None, **opts):
175 177 '''reverse effect of earlier changeset
176 178
177 Commit the backed out changes as a new changeset. The new
179 Commit the backed out changes as a new changeset. The new
178 180 changeset is a child of the backed out changeset.
179 181
180 182 If you back out a changeset other than the tip, a new head is
181 created. This head will be the new tip and you should merge this
183 created. This head will be the new tip and you should merge this
182 184 backout changeset with another head (current one by default).
183 185
184 186 The --merge option remembers the parent of the working directory
185 187 before starting the backout, then merges the new head with that
186 changeset afterwards. This saves you from doing the merge by
187 hand. The result of this merge is not committed, as for a normal
188 changeset afterwards. This saves you from doing the merge by
189 hand. The result of this merge is not committed, as for a normal
188 190 merge.
189 191
190 See 'hg help dates' for a list of formats valid for -d/--date.
192 See \'hg help dates\' for a list of formats valid for -d/--date.
191 193 '''
192 194 if rev and node:
193 195 raise util.Abort(_("please specify just one revision"))
@@ -368,7 +370,7 b' def branch(ui, repo, label=None, **opts)'
368 370
369 371 if label:
370 372 if not opts.get('force') and label in repo.branchtags():
371 if label not in [p.branch() for p in repo.workingctx().parents()]:
373 if label not in [p.branch() for p in repo.parents()]:
372 374 raise util.Abort(_('a branch of the same name already exists'
373 375 ' (use --force to override)'))
374 376 repo.dirstate.setbranch(util.fromlocal(label))
@@ -380,18 +382,17 b' def branches(ui, repo, active=False):'
380 382 """list repository named branches
381 383
382 384 List the repository's named branches, indicating which ones are
383 inactive. If active is specified, only show active branches.
385 inactive. If active is specified, only show active branches.
384 386
385 387 A branch is considered active if it contains repository heads.
386 388
387 389 Use the command 'hg update' to switch to an existing branch.
388 390 """
389 391 hexfunc = ui.debugflag and hex or short
390 activebranches = [util.tolocal(repo.changectx(n).branch())
392 activebranches = [util.tolocal(repo[n].branch())
391 393 for n in repo.heads()]
392 branches = [(tag in activebranches, repo.changelog.rev(node), tag)
393 for tag, node in repo.branchtags().items()]
394 branches.sort()
394 branches = util.sort([(tag in activebranches, repo.changelog.rev(node), tag)
395 for tag, node in repo.branchtags().items()])
395 396 branches.reverse()
396 397
397 398 for isactive, node, tag in branches:
@@ -412,8 +413,9 b' def bundle(ui, repo, fname, dest=None, *'
412 413
413 414 If no destination repository is specified the destination is
414 415 assumed to have all the nodes specified by one or more --base
415 parameters. To create a bundle containing all changesets, use
416 --all (or --base null).
416 parameters. To create a bundle containing all changesets, use
417 --all (or --base null). To change the compression method applied,
418 use the -t option (by default, bundles are compressed using bz2).
417 419
418 420 The bundle file can then be transferred using conventional means and
419 421 applied to another repository with the unbundle or pull command.
@@ -467,7 +469,14 b' def bundle(ui, repo, fname, dest=None, *'
467 469 cg = repo.changegroupsubset(o, revs, 'bundle')
468 470 else:
469 471 cg = repo.changegroup(o, 'bundle')
470 changegroup.writebundle(cg, fname, "HG10BZ")
472
473 bundletype = opts.get('type', 'bzip2').lower()
474 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
475 bundletype = btypes.get(bundletype)
476 if bundletype not in changegroup.bundletypes:
477 raise util.Abort(_('unknown bundle type specified with --type'))
478
479 changegroup.writebundle(cg, fname, bundletype)
471 480
472 481 def cat(ui, repo, file1, *pats, **opts):
473 482 """output the current or given revision of files
@@ -477,19 +486,19 b' def cat(ui, repo, file1, *pats, **opts):'
477 486 or tip if no revision is checked out.
478 487
479 488 Output may be to a file, in which case the name of the file is
480 given using a format string. The formatting rules are the same as
489 given using a format string. The formatting rules are the same as
481 490 for the export command, with the following additions:
482 491
483 492 %s basename of file being printed
484 493 %d dirname of file being printed, or '.' if in repo root
485 494 %p root-relative path name of file being printed
486 495 """
487 ctx = repo.changectx(opts['rev'])
496 ctx = repo[opts['rev']]
488 497 err = 1
489 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
490 ctx.node()):
498 m = cmdutil.match(repo, (file1,) + pats, opts)
499 for abs in ctx.walk(m):
491 500 fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
492 data = ctx.filectx(abs).data()
501 data = ctx[abs].data()
493 502 if opts.get('decode'):
494 503 data = repo.wwritedata(abs, data)
495 504 fp.write(data)
@@ -509,20 +518,22 b' def clone(ui, source, dest=None, **opts)'
509 518
510 519 For efficiency, hardlinks are used for cloning whenever the source
511 520 and destination are on the same filesystem (note this applies only
512 to the repository data, not to the checked out files). Some
521 to the repository data, not to the checked out files). Some
513 522 filesystems, such as AFS, implement hardlinking incorrectly, but
514 do not report errors. In these cases, use the --pull option to
523 do not report errors. In these cases, use the --pull option to
515 524 avoid hardlinking.
516 525
517 You can safely clone repositories and checked out files using full
518 hardlinks with
526 In some cases, you can clone repositories and checked out files
527 using full hardlinks with
519 528
520 529 $ cp -al REPO REPOCLONE
521 530
522 which is the fastest way to clone. However, the operation is not
523 atomic (making sure REPO is not modified during the operation is
524 up to you) and you have to make sure your editor breaks hardlinks
525 (Emacs and most Linux Kernel tools do so).
531 This is the fastest way to clone, but it is not always safe. The
532 operation is not atomic (making sure REPO is not modified during
533 the operation is up to you) and you have to make sure your editor
534 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
535 this is not compatible with certain extensions that place their
536 metadata under the .hg directory, such as mq.
526 537
527 538 If you use the -r option to clone up to a specific revision, no
528 539 subsequent revisions will be present in the cloned repository.
@@ -561,9 +572,9 b' def commit(ui, repo, *pats, **opts):'
561 572
562 573 See 'hg help dates' for a list of formats valid for -d/--date.
563 574 """
564 def commitfunc(ui, repo, files, message, match, opts):
565 return repo.commit(files, message, opts['user'], opts['date'], match,
566 force_editor=opts.get('force_editor'))
575 def commitfunc(ui, repo, message, match, opts):
576 return repo.commit(match.files(), message, opts['user'], opts['date'],
577 match, force_editor=opts.get('force_editor'))
567 578
568 579 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
569 580 if not node:
@@ -582,12 +593,12 b' def commit(ui, repo, *pats, **opts):'
582 593 def copy(ui, repo, *pats, **opts):
583 594 """mark files as copied for the next commit
584 595
585 Mark dest as having copies of source files. If dest is a
586 directory, copies are put in that directory. If dest is a file,
596 Mark dest as having copies of source files. If dest is a
597 directory, copies are put in that directory. If dest is a file,
587 598 there can only be one source.
588 599
589 600 By default, this command copies the contents of files as they
590 stand in the working directory. If invoked with --after, the
601 stand in the working directory. If invoked with --after, the
591 602 operation is recorded, but no copying is performed.
592 603
593 604 This command takes effect in the next commit. To undo a copy
@@ -634,35 +645,30 b" def debugcomplete(ui, cmd='', **opts):"
634 645 ui.write("%s\n" % "\n".join(options))
635 646 return
636 647
637 clist = cmdutil.findpossible(ui, cmd, table).keys()
638 clist.sort()
639 ui.write("%s\n" % "\n".join(clist))
648 ui.write("%s\n" % "\n".join(util.sort(cmdutil.findpossible(ui, cmd, table))))
640 649
641 650 def debugfsinfo(ui, path = "."):
642 651 file('.debugfsinfo', 'w').write('')
643 652 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
644 653 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
645 ui.write('case-sensitive: %s\n' % (util.checkfolding('.debugfsinfo')
654 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
646 655 and 'yes' or 'no'))
647 656 os.unlink('.debugfsinfo')
648 657
649 def debugrebuildstate(ui, repo, rev=""):
658 def debugrebuildstate(ui, repo, rev="tip"):
650 659 """rebuild the dirstate as it would look like for the given revision"""
651 if rev == "":
652 rev = repo.changelog.tip()
653 ctx = repo.changectx(rev)
654 files = ctx.manifest()
660 ctx = repo[rev]
655 661 wlock = repo.wlock()
656 662 try:
657 repo.dirstate.rebuild(rev, files)
663 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
658 664 finally:
659 665 del wlock
660 666
661 667 def debugcheckstate(ui, repo):
662 668 """validate the correctness of the current dirstate"""
663 669 parent1, parent2 = repo.dirstate.parents()
664 m1 = repo.changectx(parent1).manifest()
665 m2 = repo.changectx(parent2).manifest()
670 m1 = repo[parent1].manifest()
671 m2 = repo[parent2].manifest()
666 672 errors = 0
667 673 for f in repo.dirstate:
668 674 state = repo.dirstate[f]
@@ -729,11 +735,9 b' def debugsetparents(ui, repo, rev1, rev2'
729 735
730 736 def debugstate(ui, repo, nodates=None):
731 737 """show the contents of the current dirstate"""
732 k = repo.dirstate._map.items()
733 k.sort()
734 738 timestr = ""
735 739 showdate = not nodates
736 for file_, ent in k:
740 for file_, ent in util.sort(repo.dirstate._map.items()):
737 741 if showdate:
738 742 if ent[3] == -1:
739 743 # Pad or slice to locale representation
@@ -775,7 +779,7 b' def debugindex(ui, file_):'
775 779 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
776 780 ui.write(" rev offset length base linkrev" +
777 781 " nodeid p1 p2\n")
778 for i in xrange(r.count()):
782 for i in r:
779 783 node = r.node(i)
780 784 try:
781 785 pp = r.parents(node)
@@ -789,7 +793,7 b' def debugindexdot(ui, file_):'
789 793 """dump an index DAG as a .dot file"""
790 794 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
791 795 ui.write("digraph G {\n")
792 for i in xrange(r.count()):
796 for i in r:
793 797 node = r.node(i)
794 798 pp = r.parents(node)
795 799 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
@@ -912,26 +916,28 b' def debuginstall(ui):'
912 916 def debugrename(ui, repo, file1, *pats, **opts):
913 917 """dump rename information"""
914 918
915 ctx = repo.changectx(opts.get('rev', 'tip'))
916 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
917 ctx.node()):
918 fctx = ctx.filectx(abs)
919 m = fctx.filelog().renamed(fctx.filenode())
920 if m:
921 ui.write(_("%s renamed from %s:%s\n") % (rel, m[0], hex(m[1])))
919 ctx = repo[opts.get('rev')]
920 m = cmdutil.match(repo, (file1,) + pats, opts)
921 for abs in ctx.walk(m):
922 fctx = ctx[abs]
923 o = fctx.filelog().renamed(fctx.filenode())
924 rel = m.rel(abs)
925 if o:
926 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
922 927 else:
923 928 ui.write(_("%s not renamed\n") % rel)
924 929
925 930 def debugwalk(ui, repo, *pats, **opts):
926 931 """show how files match on given patterns"""
927 items = list(cmdutil.walk(repo, pats, opts))
932 m = cmdutil.match(repo, pats, opts)
933 items = list(repo.walk(m))
928 934 if not items:
929 935 return
930 fmt = '%%s %%-%ds %%-%ds %%s' % (
931 max([len(abs) for (src, abs, rel, exact) in items]),
932 max([len(rel) for (src, abs, rel, exact) in items]))
933 for src, abs, rel, exact in items:
934 line = fmt % (src, abs, rel, exact and 'exact' or '')
936 fmt = 'f %%-%ds %%-%ds %%s' % (
937 max([len(abs) for abs in items]),
938 max([len(m.rel(abs)) for abs in items]))
939 for abs in items:
940 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
935 941 ui.write("%s\n" % line.rstrip())
936 942
937 943 def diff(ui, repo, *pats, **opts):
@@ -957,10 +963,8 b' def diff(ui, repo, *pats, **opts):'
957 963 """
958 964 node1, node2 = cmdutil.revpair(repo, opts['rev'])
959 965
960 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
961
962 patch.diff(repo, node1, node2, fns, match=matchfn,
963 opts=patch.diffopts(ui, opts))
966 m = cmdutil.match(repo, pats, opts)
967 patch.diff(repo, node1, node2, match=m, opts=patch.diffopts(ui, opts))
964 968
965 969 def export(ui, repo, *changesets, **opts):
966 970 """dump the header and diffs for one or more changesets
@@ -974,7 +978,7 b' def export(ui, repo, *changesets, **opts'
974 978 as it will compare the merge changeset against its first parent only.
975 979
976 980 Output may be to a file, in which case the name of the file is
977 given using a format string. The formatting rules are as follows:
981 given using a format string. The formatting rules are as follows:
978 982
979 983 %% literal "%" character
980 984 %H changeset hash (40 bytes of hexadecimal)
@@ -1008,13 +1012,13 b' def grep(ui, repo, pattern, *pats, **opt'
1008 1012
1009 1013 Search revisions of files for a regular expression.
1010 1014
1011 This command behaves differently than Unix grep. It only accepts
1012 Python/Perl regexps. It searches repository history, not the
1013 working directory. It always prints the revision number in which
1015 This command behaves differently than Unix grep. It only accepts
1016 Python/Perl regexps. It searches repository history, not the
1017 working directory. It always prints the revision number in which
1014 1018 a match appears.
1015 1019
1016 1020 By default, grep only prints output for the first revision of a
1017 file in which it finds a match. To get it to print every revision
1021 file in which it finds a match. To get it to print every revision
1018 1022 that contains a change in match status ("-" for a match that
1019 1023 becomes a non-match, or "+" for a non-match that becomes a match),
1020 1024 use the --all flag.
@@ -1058,6 +1062,9 b' def grep(ui, repo, pattern, *pats, **opt'
1058 1062 self.colstart = colstart
1059 1063 self.colend = colend
1060 1064
1065 def __hash__(self):
1066 return hash((self.linenum, self.line))
1067
1061 1068 def __eq__(self, other):
1062 1069 return self.line == other.line
1063 1070
@@ -1118,7 +1125,7 b' def grep(ui, repo, pattern, *pats, **opt'
1118 1125
1119 1126 fstate = {}
1120 1127 skip = {}
1121 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1128 get = util.cachefunc(lambda r: repo[r].changeset())
1122 1129 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1123 1130 found = False
1124 1131 follow = opts.get('follow')
@@ -1126,7 +1133,7 b' def grep(ui, repo, pattern, *pats, **opt'
1126 1133 if st == 'window':
1127 1134 matches.clear()
1128 1135 elif st == 'add':
1129 ctx = repo.changectx(rev)
1136 ctx = repo[rev]
1130 1137 matches[rev] = {}
1131 1138 for fn in fns:
1132 1139 if fn in skip:
@@ -1141,9 +1148,7 b' def grep(ui, repo, pattern, *pats, **opt'
1141 1148 except revlog.LookupError:
1142 1149 pass
1143 1150 elif st == 'iter':
1144 states = matches[rev].items()
1145 states.sort()
1146 for fn, m in states:
1151 for fn, m in util.sort(matches[rev].items()):
1147 1152 copy = copies.get(rev, {}).get(fn)
1148 1153 if fn in skip:
1149 1154 if copy:
@@ -1161,9 +1166,7 b' def grep(ui, repo, pattern, *pats, **opt'
1161 1166 fstate[copy] = m
1162 1167 prev[fn] = rev
1163 1168
1164 fstate = fstate.items()
1165 fstate.sort()
1166 for fn, state in fstate:
1169 for fn, state in util.sort(fstate.items()):
1167 1170 if fn in skip:
1168 1171 continue
1169 1172 if fn not in copies.get(prev[fn], {}):
@@ -1184,7 +1187,7 b' def heads(ui, repo, *branchrevs, **opts)'
1184 1187 are the usual targets for update and merge operations.
1185 1188
1186 1189 Branch heads are changesets that have a given branch tag, but have
1187 no child changesets with that tag. They are usually where
1190 no child changesets with that tag. They are usually where
1188 1191 development on the given branch takes place.
1189 1192 """
1190 1193 if opts['rev']:
@@ -1198,7 +1201,7 b' def heads(ui, repo, *branchrevs, **opts)'
1198 1201 heads = []
1199 1202 visitedset = util.set()
1200 1203 for branchrev in branchrevs:
1201 branch = repo.changectx(branchrev).branch()
1204 branch = repo[branchrev].branch()
1202 1205 if branch in visitedset:
1203 1206 continue
1204 1207 visitedset.add(branch)
@@ -1250,7 +1253,14 b' def help_(ui, name=None, with_version=Fa'
1250 1253 if with_version:
1251 1254 version_(ui)
1252 1255 ui.write('\n')
1253 aliases, i = cmdutil.findcmd(ui, name, table)
1256
1257 try:
1258 aliases, i = cmdutil.findcmd(ui, name, table)
1259 except cmdutil.AmbiguousCommand, inst:
1260 select = lambda c: c.lstrip('^').startswith(inst.args[0])
1261 helplist(_('list of commands:\n\n'), select)
1262 return
1263
1254 1264 # synopsis
1255 1265 ui.write("%s\n" % i[2])
1256 1266
@@ -1296,8 +1306,7 b' def help_(ui, name=None, with_version=Fa'
1296 1306 return
1297 1307
1298 1308 ui.status(header)
1299 fns = h.keys()
1300 fns.sort()
1309 fns = util.sort(h)
1301 1310 m = max(map(len, fns))
1302 1311 for f in fns:
1303 1312 if ui.verbose:
@@ -1311,16 +1320,16 b' def help_(ui, name=None, with_version=Fa'
1311 1320
1312 1321 def helptopic(name):
1313 1322 v = None
1314 for i in help.helptable:
1323 for i, d in help.helptable:
1315 1324 l = i.split('|')
1316 1325 if name in l:
1317 1326 v = i
1318 1327 header = l[-1]
1328 doc = d
1319 1329 if not v:
1320 1330 raise cmdutil.UnknownCommand(name)
1321 1331
1322 1332 # description
1323 doc = help.helptable[v]
1324 1333 if not doc:
1325 1334 doc = _("(No help text available)")
1326 1335 if callable(doc):
@@ -1391,6 +1400,16 b' def help_(ui, name=None, with_version=Fa'
1391 1400 and _(" (default: %s)") % default
1392 1401 or "")))
1393 1402
1403 if ui.verbose:
1404 ui.write(_("\nspecial help topics:\n"))
1405 topics = []
1406 for i, d in help.helptable:
1407 l = i.split('|')
1408 topics.append((", ".join(l[:-1]), l[-1]))
1409 topics_len = max([len(s[0]) for s in topics])
1410 for t, desc in topics:
1411 ui.write(" %-*s %s\n" % (topics_len, t, desc))
1412
1394 1413 if opt_output:
1395 1414 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1396 1415 for first, second in opt_output:
@@ -1433,7 +1452,7 b' def identify(ui, repo, source=None,'
1433 1452 "can't query remote revision number, branch, or tags")
1434 1453 output = [hexfunc(srepo.lookup(rev))]
1435 1454 elif not rev:
1436 ctx = repo.workingctx()
1455 ctx = repo[None]
1437 1456 parents = ctx.parents()
1438 1457 changed = False
1439 1458 if default or id or num:
@@ -1445,7 +1464,7 b' def identify(ui, repo, source=None,'
1445 1464 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1446 1465 (changed) and "+" or ""))
1447 1466 else:
1448 ctx = repo.changectx(rev)
1467 ctx = repo[rev]
1449 1468 if default or id:
1450 1469 output = [hexfunc(ctx.node())]
1451 1470 if num:
@@ -1477,15 +1496,15 b' def import_(ui, repo, patch1, *patches, '
1477 1496 If there are outstanding changes in the working directory, import
1478 1497 will abort unless given the -f flag.
1479 1498
1480 You can import a patch straight from a mail message. Even patches
1499 You can import a patch straight from a mail message. Even patches
1481 1500 as attachments work (body part must be type text/plain or
1482 text/x-patch to be used). From and Subject headers of email
1483 message are used as default committer and commit message. All
1501 text/x-patch to be used). From and Subject headers of email
1502 message are used as default committer and commit message. All
1484 1503 text/plain body parts before first diff are added to commit
1485 1504 message.
1486 1505
1487 1506 If the imported patch was generated by hg export, user and description
1488 from patch override values from message headers and body. Values
1507 from patch override values from message headers and body. Values
1489 1508 given on command line with -m and -u override these.
1490 1509
1491 1510 If --exact is specified, import will set the working directory
@@ -1542,7 +1561,7 b' def import_(ui, repo, patch1, *patches, '
1542 1561 message = None
1543 1562 ui.debug(_('message:\n%s\n') % message)
1544 1563
1545 wp = repo.workingctx().parents()
1564 wp = repo.parents()
1546 1565 if opts.get('exact'):
1547 1566 if not nodeid or not p1:
1548 1567 raise util.Abort(_('not a mercurial patch'))
@@ -1654,7 +1673,7 b' def incoming(ui, repo, source="default",'
1654 1673 def init(ui, dest=".", **opts):
1655 1674 """create a new repository in the given directory
1656 1675
1657 Initialize a new repository in the given directory. If the given
1676 Initialize a new repository in the given directory. If the given
1658 1677 directory does not exist, it is created.
1659 1678
1660 1679 If no directory is given, the current directory is used.
@@ -1672,7 +1691,7 b' def locate(ui, repo, *pats, **opts):'
1672 1691 Print all files under Mercurial control whose names match the
1673 1692 given patterns.
1674 1693
1675 This command searches the entire repository by default. To search
1694 This command searches the entire repository by default. To search
1676 1695 just the current directory and its subdirectories, use
1677 1696 "--include .".
1678 1697
@@ -1685,24 +1704,18 b' def locate(ui, repo, *pats, **opts):'
1685 1704 that contain white space as multiple filenames.
1686 1705 """
1687 1706 end = opts['print0'] and '\0' or '\n'
1688 rev = opts['rev']
1689 if rev:
1690 node = repo.lookup(rev)
1691 else:
1692 node = None
1707 rev = opts.get('rev') or None
1693 1708
1694 1709 ret = 1
1695 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
1696 badmatch=util.always,
1697 default='relglob'):
1698 if src == 'b':
1699 continue
1700 if not node and abs not in repo.dirstate:
1710 m = cmdutil.match(repo, pats, opts, default='relglob')
1711 m.bad = lambda x,y: False
1712 for abs in repo[rev].walk(m):
1713 if not rev and abs not in repo.dirstate:
1701 1714 continue
1702 1715 if opts['fullpath']:
1703 1716 ui.write(os.path.join(repo.root, abs), end)
1704 1717 else:
1705 ui.write(((pats and rel) or abs), end)
1718 ui.write(((pats and m.rel(abs)) or abs), end)
1706 1719 ret = 0
1707 1720
1708 1721 return ret
@@ -1714,7 +1727,7 b' def log(ui, repo, *pats, **opts):'
1714 1727 project.
1715 1728
1716 1729 File history is shown without following rename or copy history of
1717 files. Use -f/--follow with a file name to follow history across
1730 files. Use -f/--follow with a file name to follow history across
1718 1731 renames and copies. --follow without a file name will only show
1719 1732 ancestors or descendants of the starting revision. --follow-first
1720 1733 only follows the first parent of merge revisions.
@@ -1737,7 +1750,7 b' def log(ui, repo, *pats, **opts):'
1737 1750
1738 1751 """
1739 1752
1740 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1753 get = util.cachefunc(lambda r: repo[r].changeset())
1741 1754 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1742 1755
1743 1756 limit = cmdutil.loglimit(opts)
@@ -1746,7 +1759,7 b' def log(ui, repo, *pats, **opts):'
1746 1759 if opts['copies'] and opts['rev']:
1747 1760 endrev = max(cmdutil.revrange(repo, opts['rev'])) + 1
1748 1761 else:
1749 endrev = repo.changelog.count()
1762 endrev = len(repo)
1750 1763 rcache = {}
1751 1764 ncache = {}
1752 1765 def getrenamed(fn, rev):
@@ -1758,7 +1771,7 b' def log(ui, repo, *pats, **opts):'
1758 1771 rcache[fn] = {}
1759 1772 ncache[fn] = {}
1760 1773 fl = repo.file(fn)
1761 for i in xrange(fl.count()):
1774 for i in fl:
1762 1775 node = fl.node(i)
1763 1776 lr = fl.linkrev(node)
1764 1777 renamed = fl.renamed(node)
@@ -1774,7 +1787,7 b' def log(ui, repo, *pats, **opts):'
1774 1787 # filectx logic.
1775 1788
1776 1789 try:
1777 return repo.changectx(rev).filectx(fn).renamed()
1790 return repo[rev][fn].renamed()
1778 1791 except revlog.LookupError:
1779 1792 pass
1780 1793 return None
@@ -1850,17 +1863,13 b' def manifest(ui, repo, node=None, rev=No'
1850 1863 if not node:
1851 1864 node = rev
1852 1865
1853 m = repo.changectx(node).manifest()
1854 files = m.keys()
1855 files.sort()
1856
1857 for f in files:
1866 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
1867 ctx = repo[node]
1868 for f in ctx:
1858 1869 if ui.debugflag:
1859 ui.write("%40s " % hex(m[f]))
1870 ui.write("%40s " % hex(ctx.manifest()[f]))
1860 1871 if ui.verbose:
1861 type = m.execf(f) and "*" or m.linkf(f) and "@" or " "
1862 perm = m.execf(f) and "755" or "644"
1863 ui.write("%3s %1s " % (perm, type))
1872 ui.write(decor[ctx.flags(f)])
1864 1873 ui.write("%s\n" % f)
1865 1874
1866 1875 def merge(ui, repo, node=None, force=None, rev=None):
@@ -1872,8 +1881,8 b' def merge(ui, repo, node=None, force=Non'
1872 1881 performed before any further updates are allowed.
1873 1882
1874 1883 If no revision is specified, the working directory's parent is a
1875 head revision, and the repository contains exactly one other head,
1876 the other head is merged with by default. Otherwise, an explicit
1884 head revision, and the current branch contains exactly one other head,
1885 the other head is merged with by default. Otherwise, an explicit
1877 1886 revision to merge with must be provided.
1878 1887 """
1879 1888
@@ -1883,22 +1892,28 b' def merge(ui, repo, node=None, force=Non'
1883 1892 node = rev
1884 1893
1885 1894 if not node:
1886 heads = repo.heads()
1887 if len(heads) > 2:
1888 raise util.Abort(_('repo has %d heads - '
1889 'please merge with an explicit rev') %
1890 len(heads))
1895 branch = repo.changectx(None).branch()
1896 bheads = repo.branchheads(branch)
1897 if len(bheads) > 2:
1898 raise util.Abort(_("branch '%s' has %d heads - "
1899 "please merge with an explicit rev") %
1900 (branch, len(bheads)))
1901
1891 1902 parent = repo.dirstate.parents()[0]
1892 if len(heads) == 1:
1903 if len(bheads) == 1:
1904 if len(repo.heads()) > 1:
1905 raise util.Abort(_("branch '%s' has one head - "
1906 "please merge with an explicit rev") %
1907 branch)
1893 1908 msg = _('there is nothing to merge')
1894 if parent != repo.lookup(repo.workingctx().branch()):
1909 if parent != repo.lookup(repo[None].branch()):
1895 1910 msg = _('%s - use "hg update" instead') % msg
1896 1911 raise util.Abort(msg)
1897 1912
1898 if parent not in heads:
1913 if parent not in bheads:
1899 1914 raise util.Abort(_('working dir not at a head rev - '
1900 1915 'use "hg update" or merge with an explicit rev'))
1901 node = parent == heads[0] and heads[-1] or heads[0]
1916 node = parent == bheads[0] and bheads[-1] or bheads[0]
1902 1917 return hg.merge(repo, node, force=force)
1903 1918
1904 1919 def outgoing(ui, repo, dest=None, **opts):
@@ -1948,15 +1963,15 b' def parents(ui, repo, file_=None, **opts'
1948 1963 """
1949 1964 rev = opts.get('rev')
1950 1965 if rev:
1951 ctx = repo.changectx(rev)
1966 ctx = repo[rev]
1952 1967 else:
1953 ctx = repo.workingctx()
1968 ctx = repo[None]
1954 1969
1955 1970 if file_:
1956 files, match, anypats = cmdutil.matchpats(repo, (file_,), opts)
1957 if anypats or len(files) != 1:
1971 m = cmdutil.match(repo, (file_,), opts)
1972 if m.anypats() or len(m.files()) != 1:
1958 1973 raise util.Abort(_('can only specify an explicit file name'))
1959 file_ = files[0]
1974 file_ = m.files()[0]
1960 1975 filenodes = []
1961 1976 for cp in ctx.parents():
1962 1977 if not cp:
@@ -1984,7 +1999,7 b' def paths(ui, repo, search=None):'
1984 1999 definition of available names.
1985 2000
1986 2001 Path names are defined in the [paths] section of /etc/mercurial/hgrc
1987 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2002 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
1988 2003 """
1989 2004 if search:
1990 2005 for name, path in ui.configitems("paths"):
@@ -2022,9 +2037,9 b' def pull(ui, repo, source="default", **o'
2022 2037 Valid URLs are of the form:
2023 2038
2024 2039 local/filesystem/path (or file://local/filesystem/path)
2025 http://[user@]host[:port]/[path]
2026 https://[user@]host[:port]/[path]
2027 ssh://[user@]host[:port]/[path]
2040 http://[user[:pass]@]host[:port]/[path]
2041 https://[user[:pass]@]host[:port]/[path]
2042 ssh://[user[:pass]@]host[:port]/[path]
2028 2043 static-http://host[:port]/[path]
2029 2044
2030 2045 Paths in the local filesystem can either point to Mercurial
@@ -2084,9 +2099,9 b' def push(ui, repo, dest=None, **opts):'
2084 2099 Valid URLs are of the form:
2085 2100
2086 2101 local/filesystem/path (or file://local/filesystem/path)
2087 ssh://[user@]host[:port]/[path]
2088 http://[user@]host[:port]/[path]
2089 https://[user@]host[:port]/[path]
2102 ssh://[user[:pass]@]host[:port]/[path]
2103 http://[user[:pass]@]host[:port]/[path]
2104 https://[user[:pass]@]host[:port]/[path]
2090 2105
2091 2106 An optional identifier after # indicates a particular branch, tag,
2092 2107 or changeset to push. If -r is used, the named changeset and all its
@@ -2126,7 +2141,7 b' def rawcommit(ui, repo, *pats, **opts):'
2126 2141
2127 2142 message = cmdutil.logmessage(opts)
2128 2143
2129 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
2144 files = cmdutil.match(repo, pats, opts).files()
2130 2145 if opts['files']:
2131 2146 files += open(opts['files']).read().splitlines()
2132 2147
@@ -2178,47 +2193,28 b' def remove(ui, repo, *pats, **opts):'
2178 2193 if not pats and not after:
2179 2194 raise util.Abort(_('no files specified'))
2180 2195
2181 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2182 mardu = map(dict.fromkeys, repo.status(files=files, match=matchfn))[:5]
2183 modified, added, removed, deleted, unknown = mardu
2184
2185 remove, forget = [], []
2186 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
2187
2188 reason = None
2189 if abs in removed or abs in unknown:
2190 continue
2191
2192 # last column
2193 elif abs in deleted:
2194 remove.append(abs)
2195
2196 # rest of the third row
2197 elif after and not force:
2198 reason = _('still exists (use -f to force removal)')
2199
2200 # rest of the first column
2201 elif abs in added:
2202 if not force:
2203 reason = _('has been marked for add (use -f to force removal)')
2204 else:
2205 forget.append(abs)
2206
2207 # rest of the third column
2208 elif abs in modified:
2209 if not force:
2210 reason = _('is modified (use -f to force removal)')
2211 else:
2212 remove.append(abs)
2213
2214 # rest of the second column
2215 elif not reason:
2216 remove.append(abs)
2217
2218 if reason:
2219 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2220 elif ui.verbose or not exact:
2221 ui.status(_('removing %s\n') % rel)
2196 m = cmdutil.match(repo, pats, opts)
2197 s = repo.status(match=m, clean=True)
2198 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2199
2200 def warn(files, reason):
2201 for f in files:
2202 ui.warn(_('not removing %s: file %s (use -f to force removal)\n')
2203 % (m.rel(f), reason))
2204
2205 if force:
2206 remove, forget = modified + deleted + clean, added
2207 elif after:
2208 remove, forget = deleted, []
2209 warn(modified + added + clean, _('still exists'))
2210 else:
2211 remove, forget = deleted + clean, []
2212 warn(modified, _('is modified'))
2213 warn(added, _('has been marked for add'))
2214
2215 for f in util.sort(remove + forget):
2216 if ui.verbose or not m.exact(f):
2217 ui.status(_('removing %s\n') % m.rel(f))
2222 2218
2223 2219 repo.forget(forget)
2224 2220 repo.remove(remove, unlink=not after)
@@ -2226,12 +2222,12 b' def remove(ui, repo, *pats, **opts):'
2226 2222 def rename(ui, repo, *pats, **opts):
2227 2223 """rename files; equivalent of copy + remove
2228 2224
2229 Mark dest as copies of sources; mark sources for deletion. If
2230 dest is a directory, copies are put in that directory. If dest is
2225 Mark dest as copies of sources; mark sources for deletion. If
2226 dest is a directory, copies are put in that directory. If dest is
2231 2227 a file, there can only be one source.
2232 2228
2233 2229 By default, this command copies the contents of files as they
2234 stand in the working directory. If invoked with --after, the
2230 stand in the working directory. If invoked with --after, the
2235 2231 operation is recorded, but no copying is performed.
2236 2232
2237 2233 This command takes effect in the next commit. To undo a rename
@@ -2243,6 +2239,39 b' def rename(ui, repo, *pats, **opts):'
2243 2239 finally:
2244 2240 del wlock
2245 2241
2242 def resolve(ui, repo, *pats, **opts):
2243 """resolve file merges from a branch merge or update
2244
2245 This command will attempt to resolve unresolved merges from the
2246 last update or merge command. This will use the local file
2247 revision preserved at the last update or merge to cleanly retry
2248 the file merge attempt. With no file or options specified, this
2249 command will attempt to resolve all unresolved files.
2250
2251 The codes used to show the status of files are:
2252 U = unresolved
2253 R = resolved
2254 """
2255
2256 if len([x for x in opts if opts[x]]) > 1:
2257 raise util.Abort(_("too many options specified"))
2258
2259 ms = merge_.mergestate(repo)
2260 m = cmdutil.match(repo, pats, opts)
2261
2262 for f in ms:
2263 if m(f):
2264 if opts.get("list"):
2265 ui.write("%s %s\n" % (ms[f].upper(), f))
2266 elif opts.get("mark"):
2267 ms.mark(f, "r")
2268 elif opts.get("unmark"):
2269 ms.mark(f, "u")
2270 else:
2271 wctx = repo[None]
2272 mctx = wctx.parents()[-1]
2273 ms.resolve(f, wctx, mctx)
2274
2246 2275 def revert(ui, repo, *pats, **opts):
2247 2276 """restore individual files or dirs to an earlier state
2248 2277
@@ -2261,13 +2290,13 b' def revert(ui, repo, *pats, **opts):'
2261 2290 back" some or all of an earlier change.
2262 2291 See 'hg help dates' for a list of formats valid for -d/--date.
2263 2292
2264 Revert modifies the working directory. It does not commit any
2265 changes, or change the parent of the working directory. If you
2293 Revert modifies the working directory. It does not commit any
2294 changes, or change the parent of the working directory. If you
2266 2295 revert to a revision other than the parent of the working
2267 2296 directory, the reverted files will thus appear modified
2268 2297 afterwards.
2269 2298
2270 If a file has been deleted, it is restored. If the executable
2299 If a file has been deleted, it is restored. If the executable
2271 2300 mode of a file was changed, it is reset.
2272 2301
2273 2302 If names are given, all files matching the names are reverted.
@@ -2290,7 +2319,7 b' def revert(ui, repo, *pats, **opts):'
2290 2319 if not opts['rev'] and p2 != nullid:
2291 2320 raise util.Abort(_('uncommitted merge - please provide a '
2292 2321 'specific revision'))
2293 ctx = repo.changectx(opts['rev'])
2322 ctx = repo[opts['rev']]
2294 2323 node = ctx.node()
2295 2324 mf = ctx.manifest()
2296 2325 if node == parent:
@@ -2308,30 +2337,32 b' def revert(ui, repo, *pats, **opts):'
2308 2337 try:
2309 2338 # walk dirstate.
2310 2339 files = []
2311 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
2312 badmatch=mf.has_key):
2313 names[abs] = (rel, exact)
2314 if src != 'b':
2315 files.append(abs)
2340
2341 m = cmdutil.match(repo, pats, opts)
2342 m.bad = lambda x,y: False
2343 for abs in repo.walk(m):
2344 names[abs] = m.rel(abs), m.exact(abs)
2316 2345
2317 2346 # walk target manifest.
2318 2347
2319 def badmatch(path):
2348 def badfn(path, msg):
2320 2349 if path in names:
2321 return True
2350 return False
2322 2351 path_ = path + '/'
2323 2352 for f in names:
2324 2353 if f.startswith(path_):
2325 return True
2354 return False
2355 repo.ui.warn("%s: %s\n" % (m.rel(path), msg))
2326 2356 return False
2327 2357
2328 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
2329 badmatch=badmatch):
2330 if abs in names or src == 'b':
2331 continue
2332 names[abs] = (rel, exact)
2333
2334 changes = repo.status(files=files, match=names.has_key)[:4]
2358 m = cmdutil.match(repo, pats, opts)
2359 m.bad = badfn
2360 for abs in repo[node].walk(m):
2361 if abs not in names:
2362 names[abs] = m.rel(abs), m.exact(abs)
2363
2364 m = cmdutil.matchfiles(repo, names)
2365 changes = repo.status(match=m)[:4]
2335 2366 modified, added, removed, deleted = map(dict.fromkeys, changes)
2336 2367
2337 2368 # if f is a rename, also revert the source
@@ -2365,10 +2396,7 b' def revert(ui, repo, *pats, **opts):'
2365 2396 (deleted, revert, remove, False, False),
2366 2397 )
2367 2398
2368 entries = names.items()
2369 entries.sort()
2370
2371 for abs, (rel, exact) in entries:
2399 for abs, (rel, exact) in util.sort(names.items()):
2372 2400 mfentry = mf.get(abs)
2373 2401 target = repo.wjoin(abs)
2374 2402 def handle(xlist, dobackup):
@@ -2406,7 +2434,7 b' def revert(ui, repo, *pats, **opts):'
2406 2434 if pmf is None:
2407 2435 # only need parent manifest in this unlikely case,
2408 2436 # so do not read by default
2409 pmf = repo.changectx(parent).manifest()
2437 pmf = repo[parent].manifest()
2410 2438 if abs in pmf:
2411 2439 if mfentry:
2412 2440 # if version of file is same in parent and target
@@ -2420,7 +2448,7 b' def revert(ui, repo, *pats, **opts):'
2420 2448 if not opts.get('dry_run'):
2421 2449 def checkout(f):
2422 2450 fc = ctx[f]
2423 repo.wwrite(f, fc.data(), fc.fileflags())
2451 repo.wwrite(f, fc.data(), fc.flags())
2424 2452
2425 2453 audit_path = util.path_auditor(repo.root)
2426 2454 for f in remove[0]:
@@ -2503,7 +2531,7 b' def serve(ui, repo, **opts):'
2503 2531 Start a local HTTP repository browser and pull server.
2504 2532
2505 2533 By default, the server logs accesses to stdout and errors to
2506 stderr. Use the "-A" and "-E" options to log to files.
2534 stderr. Use the "-A" and "-E" options to log to files.
2507 2535 """
2508 2536
2509 2537 if opts["stdio"]:
@@ -2542,8 +2570,17 b' def serve(ui, repo, **opts):'
2542 2570 if port == ':80':
2543 2571 port = ''
2544 2572
2545 ui.status(_('listening at http://%s%s/%s (%s:%d)\n') %
2546 (self.httpd.fqaddr, port, prefix, self.httpd.addr, self.httpd.port))
2573 bindaddr = self.httpd.addr
2574 if bindaddr == '0.0.0.0':
2575 bindaddr = '*'
2576 elif ':' in bindaddr: # IPv6
2577 bindaddr = '[%s]' % bindaddr
2578
2579 fqaddr = self.httpd.fqaddr
2580 if ':' in fqaddr:
2581 fqaddr = '[%s]' % fqaddr
2582 ui.status(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
2583 (fqaddr, port, prefix, bindaddr, self.httpd.port))
2547 2584
2548 2585 def run(self):
2549 2586 self.httpd.serve_forever()
@@ -2555,10 +2592,10 b' def serve(ui, repo, **opts):'
2555 2592 def status(ui, repo, *pats, **opts):
2556 2593 """show changed files in the working directory
2557 2594
2558 Show status of files in the repository. If names are given, only
2559 files that match are shown. Files that are clean or ignored or
2595 Show status of files in the repository. If names are given, only
2596 files that match are shown. Files that are clean or ignored or
2560 2597 source of a copy/move operation, are not listed unless -c (clean),
2561 -i (ignored), -C (copies) or -A is given. Unless options described
2598 -i (ignored), -C (copies) or -A is given. Unless options described
2562 2599 with "show only ..." are given, the options -mardu are used.
2563 2600
2564 2601 Option -q/--quiet hides untracked (unknown and ignored) files
@@ -2583,65 +2620,45 b' def status(ui, repo, *pats, **opts):'
2583 2620 = the previous added file was copied from here
2584 2621 """
2585 2622
2586 all = opts['all']
2587 2623 node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
2588
2589 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2590 2624 cwd = (pats and repo.getcwd()) or ''
2591 modified, added, removed, deleted, unknown, ignored, clean = [
2592 n for n in repo.status(node1=node1, node2=node2, files=files,
2593 match=matchfn,
2594 list_ignored=opts['ignored']
2595 or all and not ui.quiet,
2596 list_clean=opts['clean'] or all,
2597 list_unknown=opts['unknown']
2598 or not (ui.quiet or
2599 opts['modified'] or
2600 opts['added'] or
2601 opts['removed'] or
2602 opts['deleted'] or
2603 opts['ignored']))]
2604
2605 changetypes = (('modified', 'M', modified),
2606 ('added', 'A', added),
2607 ('removed', 'R', removed),
2608 ('deleted', '!', deleted),
2609 ('unknown', '?', unknown),
2610 ('ignored', 'I', ignored))
2611
2612 explicit_changetypes = changetypes + (('clean', 'C', clean),)
2613
2625 end = opts['print0'] and '\0' or '\n'
2614 2626 copy = {}
2615 showcopy = {}
2616 if ((all or opts.get('copies')) and not opts.get('no_status')):
2617 if opts.get('rev') == []:
2618 # fast path, more correct with merge parents
2619 showcopy = copy = repo.dirstate.copies().copy()
2620 else:
2621 ctxn = repo.changectx(nullid)
2622 ctx1 = repo.changectx(node1)
2623 ctx2 = repo.changectx(node2)
2624 if node2 is None:
2625 ctx2 = repo.workingctx()
2626 copy, diverge = copies.copies(repo, ctx1, ctx2, ctxn)
2627 for k, v in copy.items():
2627 states = 'modified added removed deleted unknown ignored clean'.split()
2628 show = [k for k in states if opts[k]]
2629 if opts['all']:
2630 show += ui.quiet and (states[:4] + ['clean']) or states
2631 if not show:
2632 show = ui.quiet and states[:4] or states[:5]
2633
2634 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
2635 'ignored' in show, 'clean' in show, 'unknown' in show)
2636 changestates = zip(states, 'MAR!?IC', stat)
2637
2638 if (opts['all'] or opts['copies']) and not opts['no_status']:
2639 ctxn = repo[nullid]
2640 ctx1 = repo[node1]
2641 ctx2 = repo[node2]
2642 added = stat[1]
2643 if node2 is None:
2644 added = stat[0] + stat[1] # merged?
2645
2646 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].items():
2647 if k in added:
2648 copy[k] = v
2649 elif v in added:
2628 2650 copy[v] = k
2629 2651
2630 end = opts['print0'] and '\0' or '\n'
2631
2632 for opt, char, changes in ([ct for ct in explicit_changetypes
2633 if all or opts[ct[0]]]
2634 or changetypes):
2635
2636 if opts['no_status']:
2637 format = "%%s%s" % end
2638 else:
2652 for state, char, files in changestates:
2653 if state in show:
2639 2654 format = "%s %%s%s" % (char, end)
2640
2641 for f in changes:
2642 ui.write(format % repo.pathto(f, cwd))
2643 if f in copy and (f in added or f in showcopy):
2644 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end))
2655 if opts['no_status']:
2656 format = "%%s%s" % end
2657
2658 for f in files:
2659 ui.write(format % repo.pathto(f, cwd))
2660 if f in copy:
2661 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end))
2645 2662
2646 2663 def tag(ui, repo, name1, *names, **opts):
2647 2664 """add one or more tags for the current or given revision
@@ -2658,13 +2675,13 b' def tag(ui, repo, name1, *names, **opts)'
2658 2675 To facilitate version control, distribution, and merging of tags,
2659 2676 they are stored as a file named ".hgtags" which is managed
2660 2677 similarly to other project files and can be hand-edited if
2661 necessary. The file '.hg/localtags' is used for local tags (not
2678 necessary. The file '.hg/localtags' is used for local tags (not
2662 2679 shared among repositories).
2663 2680
2664 2681 See 'hg help dates' for a list of formats valid for -d/--date.
2665 2682 """
2666 2683
2667 rev_ = None
2684 rev_ = "."
2668 2685 names = (name1,) + names
2669 2686 if len(names) != len(dict.fromkeys(names)):
2670 2687 raise util.Abort(_('tag names must be unique'))
@@ -2695,7 +2712,7 b' def tag(ui, repo, name1, *names, **opts)'
2695 2712 if not rev_ and repo.dirstate.parents()[1] != nullid:
2696 2713 raise util.Abort(_('uncommitted merge - please provide a '
2697 2714 'specific revision'))
2698 r = repo.changectx(rev_).node()
2715 r = repo[rev_].node()
2699 2716
2700 2717 if not message:
2701 2718 message = (_('Added tag %s for changeset %s') %
@@ -2752,7 +2769,7 b' def tip(ui, repo, **opts):'
2752 2769 that repository becomes the current tip. The "tip" tag is special
2753 2770 and cannot be renamed or assigned to a different changeset.
2754 2771 """
2755 cmdutil.show_changeset(ui, repo, opts).show(nullrev+repo.changelog.count())
2772 cmdutil.show_changeset(ui, repo, opts).show(len(repo) - 1)
2756 2773
2757 2774 def unbundle(ui, repo, fname1, *fnames, **opts):
2758 2775 """apply one or more changegroup files
@@ -2780,8 +2797,8 b' def unbundle(ui, repo, fname1, *fnames, '
2780 2797 def update(ui, repo, node=None, rev=None, clean=False, date=None):
2781 2798 """update working directory
2782 2799
2783 Update the working directory to the specified revision, or the
2784 tip of the current branch if none is specified.
2800 Update the repository's working directory to the specified revision,
2801 or the tip of the current branch if none is specified.
2785 2802
2786 2803 If the requested revision is a descendant of the working
2787 2804 directory, any outstanding changes in the working directory will
@@ -2892,6 +2909,23 b' logopts = ['
2892 2909 ('M', 'no-merges', None, _('do not show merges')),
2893 2910 ] + templateopts
2894 2911
2912 diffopts = [
2913 ('a', 'text', None, _('treat all files as text')),
2914 ('g', 'git', None, _('use git extended diff format')),
2915 ('', 'nodates', None, _("don't include dates in diff headers"))
2916 ]
2917
2918 diffopts2 = [
2919 ('p', 'show-function', None, _('show which function each change is in')),
2920 ('w', 'ignore-all-space', None,
2921 _('ignore white space when comparing lines')),
2922 ('b', 'ignore-space-change', None,
2923 _('ignore changes in the amount of white space')),
2924 ('B', 'ignore-blank-lines', None,
2925 _('ignore changes whose lines are all blank')),
2926 ('U', 'unified', '', _('number of lines of context to show'))
2927 ]
2928
2895 2929 table = {
2896 2930 "^add": (add, walkopts + dryrunopts, _('hg add [OPTION]... [FILE]...')),
2897 2931 "addremove":
@@ -2955,8 +2989,8 b' table = {'
2955 2989 _('a changeset up to which you would like to bundle')),
2956 2990 ('', 'base', [],
2957 2991 _('a base changeset to specify instead of a destination')),
2958 ('a', 'all', None,
2959 _('bundle all changesets in the repository')),
2992 ('a', 'all', None, _('bundle all changesets in the repository')),
2993 ('t', 'type', 'bzip2', _('bundle compression type to use')),
2960 2994 ] + remoteopts,
2961 2995 _('hg bundle [-f] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
2962 2996 "cat":
@@ -3031,29 +3065,14 b' table = {'
3031 3065 "debugwalk": (debugwalk, walkopts, _('hg debugwalk [OPTION]... [FILE]...')),
3032 3066 "^diff":
3033 3067 (diff,
3034 [('r', 'rev', [], _('revision')),
3035 ('a', 'text', None, _('treat all files as text')),
3036 ('p', 'show-function', None,
3037 _('show which function each change is in')),
3038 ('g', 'git', None, _('use git extended diff format')),
3039 ('', 'nodates', None, _("don't include dates in diff headers")),
3040 ('w', 'ignore-all-space', None,
3041 _('ignore white space when comparing lines')),
3042 ('b', 'ignore-space-change', None,
3043 _('ignore changes in the amount of white space')),
3044 ('B', 'ignore-blank-lines', None,
3045 _('ignore changes whose lines are all blank')),
3046 ('U', 'unified', '',
3047 _('number of lines of context to show'))
3048 ] + walkopts,
3068 [('r', 'rev', [], _('revision'))
3069 ] + diffopts + diffopts2 + walkopts,
3049 3070 _('hg diff [OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
3050 3071 "^export":
3051 3072 (export,
3052 3073 [('o', 'output', '', _('print output to file with formatted name')),
3053 ('a', 'text', None, _('treat all files as text')),
3054 ('g', 'git', None, _('use git extended diff format')),
3055 ('', 'nodates', None, _("don't include dates in diff headers")),
3056 ('', 'switch-parent', None, _('diff against the second parent'))],
3074 ('', 'switch-parent', None, _('diff against the second parent'))
3075 ] + diffopts,
3057 3076 _('hg export [OPTION]... [-o OUTFILESPEC] REV...')),
3058 3077 "grep":
3059 3078 (grep,
@@ -3197,6 +3216,12 b' table = {'
3197 3216 _('forcibly copy over an existing managed file')),
3198 3217 ] + walkopts + dryrunopts,
3199 3218 _('hg rename [OPTION]... SOURCE... DEST')),
3219 "resolve":
3220 (resolve,
3221 [('l', 'list', None, _('list state of files needing merge')),
3222 ('m', 'mark', None, _('mark files as resolved')),
3223 ('u', 'unmark', None, _('unmark files as resolved'))],
3224 ('hg resolve [OPTION] [FILES...]')),
3200 3225 "revert":
3201 3226 (revert,
3202 3227 [('a', 'all', None, _('revert all changes when no arguments given')),
@@ -3271,7 +3296,7 b' table = {'
3271 3296 _('hg unbundle [-u] FILE...')),
3272 3297 "^update|up|checkout|co":
3273 3298 (update,
3274 [('C', 'clean', None, _('overwrite locally modified files')),
3299 [('C', 'clean', None, _('overwrite locally modified files (no backup)')),
3275 3300 ('d', 'date', '', _('tipmost revision matching date')),
3276 3301 ('r', 'rev', '', _('revision'))],
3277 3302 _('hg update [-C] [-d DATE] [[-r] REV]')),
@@ -5,35 +5,36 b''
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 from node import nullid, nullrev, short
8 from node import nullid, nullrev, short, hex
9 9 from i18n import _
10 10 import ancestor, bdiff, revlog, util, os, errno
11 11
12 12 class changectx(object):
13 13 """A changecontext object makes access to data related to a particular
14 14 changeset convenient."""
15 def __init__(self, repo, changeid=None):
15 def __init__(self, repo, changeid=''):
16 16 """changeid is a revision number, node, or tag"""
17 if changeid == '':
18 changeid = '.'
17 19 self._repo = repo
18
19 if not changeid and changeid != 0:
20 p1, p2 = self._repo.dirstate.parents()
21 self._rev = self._repo.changelog.rev(p1)
22 if self._rev == -1:
23 changeid = 'tip'
24 else:
25 self._node = p1
26 return
27
28 20 self._node = self._repo.lookup(changeid)
29 21 self._rev = self._repo.changelog.rev(self._node)
30 22
31 23 def __str__(self):
32 24 return short(self.node())
33 25
26 def __int__(self):
27 return self.rev()
28
34 29 def __repr__(self):
35 30 return "<changectx %s>" % str(self)
36 31
32 def __hash__(self):
33 try:
34 return hash(self._rev)
35 except AttributeError:
36 return id(self)
37
37 38 def __eq__(self, other):
38 39 try:
39 40 return self._rev == other._rev
@@ -57,6 +58,12 b' class changectx(object):'
57 58 md = self._repo.manifest.readdelta(self._changeset[0])
58 59 self._manifestdelta = md
59 60 return self._manifestdelta
61 elif name == '_parents':
62 p = self._repo.changelog.parents(self._node)
63 if p[1] == nullid:
64 p = p[:-1]
65 self._parents = [changectx(self._repo, x) for x in p]
66 return self._parents
60 67 else:
61 68 raise AttributeError, name
62 69
@@ -67,9 +74,7 b' class changectx(object):'
67 74 return self.filectx(key)
68 75
69 76 def __iter__(self):
70 a = self._manifest.keys()
71 a.sort()
72 for f in a:
77 for f in util.sort(self._manifest):
73 78 yield f
74 79
75 80 def changeset(self): return self._changeset
@@ -77,6 +82,7 b' class changectx(object):'
77 82
78 83 def rev(self): return self._rev
79 84 def node(self): return self._node
85 def hex(self): return hex(self._node)
80 86 def user(self): return self._changeset[1]
81 87 def date(self): return self._changeset[2]
82 88 def files(self): return self._changeset[3]
@@ -87,14 +93,21 b' class changectx(object):'
87 93
88 94 def parents(self):
89 95 """return contexts for each parent changeset"""
90 p = self._repo.changelog.parents(self._node)
91 return [changectx(self._repo, x) for x in p]
96 return self._parents
92 97
93 98 def children(self):
94 99 """return contexts for each child changeset"""
95 100 c = self._repo.changelog.children(self._node)
96 101 return [changectx(self._repo, x) for x in c]
97 102
103 def ancestors(self):
104 for a in self._repo.changelog.ancestors(self._rev):
105 yield changectx(self._repo, a)
106
107 def descendants(self):
108 for d in self._repo.changelog.descendants(self._rev):
109 yield changectx(self._repo, d)
110
98 111 def _fileinfo(self, path):
99 112 if '_manifest' in self.__dict__:
100 113 try:
@@ -115,7 +128,7 b' class changectx(object):'
115 128 def filenode(self, path):
116 129 return self._fileinfo(path)[0]
117 130
118 def fileflags(self, path):
131 def flags(self, path):
119 132 try:
120 133 return self._fileinfo(path)[1]
121 134 except revlog.LookupError:
@@ -128,15 +141,6 b' class changectx(object):'
128 141 return filectx(self._repo, path, fileid=fileid,
129 142 changectx=self, filelog=filelog)
130 143
131 def filectxs(self):
132 """generate a file context for each file in this changeset's
133 manifest"""
134 mf = self.manifest()
135 m = mf.keys()
136 m.sort()
137 for f in m:
138 yield self.filectx(f, fileid=mf[f])
139
140 144 def ancestor(self, c2):
141 145 """
142 146 return the ancestor context of self and c2
@@ -144,6 +148,23 b' class changectx(object):'
144 148 n = self._repo.changelog.ancestor(self._node, c2._node)
145 149 return changectx(self._repo, n)
146 150
151 def walk(self, match):
152 fdict = dict.fromkeys(match.files())
153 # for dirstate.walk, files=['.'] means "walk the whole tree".
154 # follow that here, too
155 fdict.pop('.', None)
156 for fn in self:
157 for ffn in fdict:
158 # match if the file is the exact name or a directory
159 if ffn == fn or fn.startswith("%s/" % ffn):
160 del fdict[ffn]
161 break
162 if match(fn):
163 yield fn
164 for fn in util.sort(fdict):
165 if match.bad(fn, 'No such file in rev ' + str(self)) and match(fn):
166 yield fn
167
147 168 class filectx(object):
148 169 """A filecontext object makes access to data related to a particular
149 170 filerevision convenient."""
@@ -210,6 +231,12 b' class filectx(object):'
210 231 def __repr__(self):
211 232 return "<filectx %s>" % str(self)
212 233
234 def __hash__(self):
235 try:
236 return hash((self._path, self._fileid))
237 except AttributeError:
238 return id(self)
239
213 240 def __eq__(self, other):
214 241 try:
215 242 return (self._path == other._path
@@ -228,9 +255,7 b' class filectx(object):'
228 255
229 256 def filerev(self): return self._filerev
230 257 def filenode(self): return self._filenode
231 def fileflags(self): return self._changectx.fileflags(self._path)
232 def isexec(self): return 'x' in self.fileflags()
233 def islink(self): return 'l' in self.fileflags()
258 def flags(self): return self._changectx.flags(self._path)
234 259 def filelog(self): return self._filelog
235 260
236 261 def rev(self):
@@ -376,12 +401,11 b' class filectx(object):'
376 401 # sort by revision (per file) which is a topological order
377 402 visit = []
378 403 for f in files:
379 fn = [(n.rev(), n) for n in needed.keys() if n._path == f]
404 fn = [(n.rev(), n) for n in needed if n._path == f]
380 405 visit.extend(fn)
381 visit.sort()
406
382 407 hist = {}
383
384 for r, f in visit:
408 for r, f in util.sort(visit):
385 409 curr = decorate(f.data(), f)
386 410 for p in parents(f):
387 411 if p != nullid:
@@ -432,11 +456,41 b' class filectx(object):'
432 456
433 457 class workingctx(changectx):
434 458 """A workingctx object makes access to data related to
435 the current working directory convenient."""
436 def __init__(self, repo):
459 the current working directory convenient.
460 parents - a pair of parent nodeids, or None to use the dirstate.
461 date - any valid date string or (unixtime, offset), or None.
462 user - username string, or None.
463 extra - a dictionary of extra values, or None.
464 changes - a list of file lists as returned by localrepo.status()
465 or None to use the repository status.
466 """
467 def __init__(self, repo, parents=None, text="", user=None, date=None,
468 extra=None, changes=None):
437 469 self._repo = repo
438 470 self._rev = None
439 471 self._node = None
472 self._text = text
473 if date:
474 self._date = util.parsedate(date)
475 if user:
476 self._user = user
477 if parents:
478 self._parents = [changectx(self._repo, p) for p in parents]
479 if changes:
480 self._status = list(changes)
481
482 self._extra = {}
483 if extra:
484 self._extra = extra.copy()
485 if 'branch' not in self._extra:
486 branch = self._repo.dirstate.branch()
487 try:
488 branch = branch.decode('UTF-8').encode('UTF-8')
489 except UnicodeDecodeError:
490 raise util.Abort(_('branch name not in UTF-8!'))
491 self._extra['branch'] = branch
492 if self._extra['branch'] == '':
493 self._extra['branch'] = 'default'
440 494
441 495 def __str__(self):
442 496 return str(self._parents[0]) + "+"
@@ -444,16 +498,28 b' class workingctx(changectx):'
444 498 def __nonzero__(self):
445 499 return True
446 500
501 def __contains__(self, key):
502 return self._dirstate[key] not in "?r"
503
447 504 def __getattr__(self, name):
448 if name == '_parents':
449 self._parents = self._repo.parents()
450 return self._parents
451 505 if name == '_status':
452 self._status = self._repo.status()
506 self._status = self._repo.status(unknown=True)
453 507 return self._status
508 elif name == '_user':
509 self._user = self._repo.ui.username()
510 return self._user
511 elif name == '_date':
512 self._date = util.makedate()
513 return self._date
454 514 if name == '_manifest':
455 515 self._buildmanifest()
456 516 return self._manifest
517 elif name == '_parents':
518 p = self._repo.dirstate.parents()
519 if p[1] == nullid:
520 p = p[:-1]
521 self._parents = [changectx(self._repo, x) for x in p]
522 return self._parents
457 523 else:
458 524 raise AttributeError, name
459 525
@@ -462,16 +528,14 b' class workingctx(changectx):'
462 528
463 529 man = self._parents[0].manifest().copy()
464 530 copied = self._repo.dirstate.copies()
465 is_exec = util.execfunc(self._repo.root,
466 lambda p: man.execf(copied.get(p,p)))
467 is_link = util.linkfunc(self._repo.root,
468 lambda p: man.linkf(copied.get(p,p)))
531 cf = lambda x: man.flags(copied.get(x, x))
532 ff = self._repo.dirstate.flagfunc(cf)
469 533 modified, added, removed, deleted, unknown = self._status[:5]
470 534 for i, l in (("a", added), ("m", modified), ("u", unknown)):
471 535 for f in l:
472 536 man[f] = man.get(copied.get(f, f), nullid) + i
473 537 try:
474 man.set(f, is_exec(f), is_link(f))
538 man.set(f, ff(f))
475 539 except OSError:
476 540 pass
477 541
@@ -483,13 +547,11 b' class workingctx(changectx):'
483 547
484 548 def manifest(self): return self._manifest
485 549
486 def user(self): return self._repo.ui.username()
487 def date(self): return util.makedate()
488 def description(self): return ""
550 def user(self): return self._user or self._repo.ui.username()
551 def date(self): return self._date
552 def description(self): return self._text
489 553 def files(self):
490 f = self.modified() + self.added() + self.removed()
491 f.sort()
492 return f
554 return util.sort(self._status[0] + self._status[1] + self._status[2])
493 555
494 556 def modified(self): return self._status[0]
495 557 def added(self): return self._status[1]
@@ -497,21 +559,18 b' class workingctx(changectx):'
497 559 def deleted(self): return self._status[3]
498 560 def unknown(self): return self._status[4]
499 561 def clean(self): return self._status[5]
500 def branch(self): return self._repo.dirstate.branch()
562 def branch(self): return self._extra['branch']
563 def extra(self): return self._extra
501 564
502 565 def tags(self):
503 566 t = []
504 567 [t.extend(p.tags()) for p in self.parents()]
505 568 return t
506 569
507 def parents(self):
508 """return contexts for each parent changeset"""
509 return self._parents
510
511 570 def children(self):
512 571 return []
513 572
514 def fileflags(self, path):
573 def flags(self, path):
515 574 if '_manifest' in self.__dict__:
516 575 try:
517 576 return self._manifest.flags(path)
@@ -521,12 +580,9 b' class workingctx(changectx):'
521 580 pnode = self._parents[0].changeset()[0]
522 581 orig = self._repo.dirstate.copies().get(path, path)
523 582 node, flag = self._repo.manifest.find(pnode, orig)
524 is_link = util.linkfunc(self._repo.root,
525 lambda p: flag and 'l' in flag)
526 is_exec = util.execfunc(self._repo.root,
527 lambda p: flag and 'x' in flag)
528 583 try:
529 return (is_link(path) and 'l' or '') + (is_exec(path) and 'x' or '')
584 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
585 return ff(path)
530 586 except OSError:
531 587 pass
532 588
@@ -543,6 +599,9 b' class workingctx(changectx):'
543 599 """return the ancestor context of self and c2"""
544 600 return self._parents[0].ancestor(c2) # punt on two parents for now
545 601
602 def walk(self, match):
603 return util.sort(self._repo.dirstate.walk(match, True, False).keys())
604
546 605 class workingfilectx(filectx):
547 606 """A workingfilectx object makes access to data related to a particular
548 607 file in the working directory convenient."""
@@ -625,3 +684,92 b' class workingfilectx(filectx):'
625 684 return (t, tz)
626 685
627 686 def cmp(self, text): return self._repo.wread(self._path) == text
687
688 class memctx(object):
689 """A memctx is a subset of changectx supposed to be built on memory
690 and passed to commit functions.
691
692 NOTE: this interface and the related memfilectx are experimental and
693 may change without notice.
694
695 parents - a pair of parent nodeids.
696 filectxfn - a callable taking (repo, memctx, path) arguments and
697 returning a memctx object.
698 date - any valid date string or (unixtime, offset), or None.
699 user - username string, or None.
700 extra - a dictionary of extra values, or None.
701 """
702 def __init__(self, repo, parents, text, files, filectxfn, user=None,
703 date=None, extra=None):
704 self._repo = repo
705 self._rev = None
706 self._node = None
707 self._text = text
708 self._date = date and util.parsedate(date) or util.makedate()
709 self._user = user
710 parents = [(p or nullid) for p in parents]
711 p1, p2 = parents
712 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
713 files = util.sort(list(files))
714 self._status = [files, [], [], [], []]
715 self._filectxfn = filectxfn
716
717 self._extra = extra and extra.copy() or {}
718 if 'branch' not in self._extra:
719 self._extra['branch'] = 'default'
720 elif self._extra.get('branch') == '':
721 self._extra['branch'] = 'default'
722
723 def __str__(self):
724 return str(self._parents[0]) + "+"
725
726 def __int__(self):
727 return self._rev
728
729 def __nonzero__(self):
730 return True
731
732 def user(self): return self._user or self._repo.ui.username()
733 def date(self): return self._date
734 def description(self): return self._text
735 def files(self): return self.modified()
736 def modified(self): return self._status[0]
737 def added(self): return self._status[1]
738 def removed(self): return self._status[2]
739 def deleted(self): return self._status[3]
740 def unknown(self): return self._status[4]
741 def clean(self): return self._status[5]
742 def branch(self): return self._extra['branch']
743 def extra(self): return self._extra
744 def flags(self, f): return self[f].flags()
745
746 def parents(self):
747 """return contexts for each parent changeset"""
748 return self._parents
749
750 def filectx(self, path, filelog=None):
751 """get a file context from the working directory"""
752 return self._filectxfn(self._repo, self, path)
753
754 class memfilectx(object):
755 """A memfilectx is a subset of filectx supposed to be built by client
756 code and passed to commit functions.
757 """
758 def __init__(self, path, data, islink, isexec, copied):
759 """copied is the source file path, or None."""
760 self._path = path
761 self._data = data
762 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
763 self._copied = None
764 if copied:
765 self._copied = (copied, nullid)
766
767 def __nonzero__(self): return True
768 def __str__(self): return "%s@%s" % (self.path(), self._changectx)
769 def path(self): return self._path
770 def data(self): return self._data
771 def flags(self): return self._flags
772 def isexec(self): return 'x' in self._flags
773 def islink(self): return 'l' in self._flags
774 def renamed(self): return self._copied
775
@@ -11,9 +11,7 b' import util, heapq'
11 11
12 12 def _nonoverlap(d1, d2, d3):
13 13 "Return list of elements in d1 not in d2 or d3"
14 l = [d for d in d1 if d not in d3 and d not in d2]
15 l.sort()
16 return l
14 return util.sort([d for d in d1 if d not in d3 and d not in d2])
17 15
18 16 def _dirname(f):
19 17 s = f.rfind("/")
@@ -49,9 +47,7 b' def _findoldnames(fctx, limit):'
49 47 visit += [(p, depth - 1) for p in fc.parents()]
50 48
51 49 # return old names sorted by depth
52 old = old.values()
53 old.sort()
54 return [o[1] for o in old]
50 return [o[1] for o in util.sort(old.values())]
55 51
56 52 def _findlimit(repo, a, b):
57 53 "find the earliest revision that's an ancestor of a or b but not both"
@@ -67,7 +63,7 b' def _findlimit(repo, a, b):'
67 63 # - quit when interesting revs is zero
68 64
69 65 cl = repo.changelog
70 working = cl.count() # pseudo rev for the working directory
66 working = len(cl) # pseudo rev for the working directory
71 67 if a is None:
72 68 a = working
73 69 if b is None:
@@ -109,6 +105,10 b' def copies(repo, c1, c2, ca, checkdirs=F'
109 105 if not c1 or not c2 or c1 == c2:
110 106 return {}, {}
111 107
108 # avoid silly behavior for parent -> working dir
109 if c2.node() == None and c1.node() == repo.dirstate.parents()[0]:
110 return repo.dirstate.copies(), {}
111
112 112 limit = _findlimit(repo, c1.rev(), c2.rev())
113 113 m1 = c1.manifest()
114 114 m2 = c2.manifest()
This diff has been collapsed as it changes many lines, (511 lines changed) Show them Hide them
@@ -9,12 +9,20 b' of the GNU General Public License, incor'
9 9
10 10 from node import nullid
11 11 from i18n import _
12 import struct, os, bisect, stat, strutil, util, errno, ignore
12 import struct, os, bisect, stat, util, errno, ignore
13 13 import cStringIO, osutil, sys
14 14
15 15 _unknown = ('?', 0, 0, 0)
16 16 _format = ">cllll"
17 17
18 def _finddirs(path):
19 pos = len(path)
20 while 1:
21 pos = path.rfind('/', 0, pos)
22 if pos == -1:
23 break
24 yield path[:pos]
25
18 26 class dirstate(object):
19 27
20 28 def __init__(self, opener, ui, root):
@@ -31,6 +39,13 b' class dirstate(object):'
31 39 elif name == '_copymap':
32 40 self._read()
33 41 return self._copymap
42 elif name == '_foldmap':
43 _foldmap = {}
44 for name in self._map:
45 norm = os.path.normcase(os.path.normpath(name))
46 _foldmap[norm] = name
47 self._foldmap = _foldmap
48 return self._foldmap
34 49 elif name == '_branch':
35 50 try:
36 51 self._branch = (self._opener("branch").read().strip()
@@ -48,10 +63,12 b' class dirstate(object):'
48 63 if err.errno != errno.ENOENT: raise
49 64 return self._pl
50 65 elif name == '_dirs':
51 self._dirs = {}
52 for f in self._map:
53 if self[f] != 'r':
54 self._incpath(f)
66 dirs = {}
67 for f,s in self._map.items():
68 if s[0] != 'r':
69 for base in _finddirs(f):
70 dirs[base] = dirs.get(base, 0) + 1
71 self._dirs = dirs
55 72 return self._dirs
56 73 elif name == '_ignore':
57 74 files = [self._join('.hgignore')]
@@ -63,15 +80,55 b' class dirstate(object):'
63 80 elif name == '_slash':
64 81 self._slash = self._ui.configbool('ui', 'slash') and os.sep != '/'
65 82 return self._slash
83 elif name == '_checklink':
84 self._checklink = util.checklink(self._root)
85 return self._checklink
66 86 elif name == '_checkexec':
67 87 self._checkexec = util.checkexec(self._root)
68 88 return self._checkexec
89 elif name == '_checkcase':
90 self._checkcase = not util.checkcase(self._join('.hg'))
91 return self._checkcase
92 elif name == 'normalize':
93 if self._checkcase:
94 self.normalize = self._normalize
95 else:
96 self.normalize = lambda x: x
97 return self.normalize
69 98 else:
70 99 raise AttributeError, name
71 100
72 101 def _join(self, f):
73 102 return os.path.join(self._root, f)
74 103
104 def flagfunc(self, fallback):
105 if self._checklink:
106 if self._checkexec:
107 def f(x):
108 p = os.path.join(self._root, x)
109 if os.path.islink(p):
110 return 'l'
111 if util.is_exec(p):
112 return 'x'
113 return ''
114 return f
115 def f(x):
116 if os.path.islink(os.path.join(self._root, x)):
117 return 'l'
118 if 'x' in fallback(x):
119 return 'x'
120 return ''
121 return f
122 if self._checkexec:
123 def f(x):
124 if 'l' in fallback(x):
125 return 'l'
126 if util.is_exec(os.path.join(self._root, x)):
127 return 'x'
128 return ''
129 return f
130 return fallback
131
75 132 def getcwd(self):
76 133 cwd = os.getcwd()
77 134 if cwd == self._root: return ''
@@ -106,9 +163,7 b' class dirstate(object):'
106 163 return key in self._map
107 164
108 165 def __iter__(self):
109 a = self._map.keys()
110 a.sort()
111 for x in a:
166 for x in util.sort(self._map):
112 167 yield x
113 168
114 169 def parents(self):
@@ -161,7 +216,7 b' class dirstate(object):'
161 216 dmap[f] = e # we hold onto e[4] because making a subtuple is slow
162 217
163 218 def invalidate(self):
164 for a in "_map _copymap _branch _pl _dirs _ignore".split():
219 for a in "_map _copymap _foldmap _branch _pl _dirs _ignore".split():
165 220 if a in self.__dict__:
166 221 delattr(self, a)
167 222 self._dirty = False
@@ -178,67 +233,39 b' class dirstate(object):'
178 233 def copies(self):
179 234 return self._copymap
180 235
181 def _incpath(self, path):
182 c = path.rfind('/')
183 if c >= 0:
236 def _droppath(self, f):
237 if self[f] not in "?r" and "_dirs" in self.__dict__:
184 238 dirs = self._dirs
185 base = path[:c]
186 if base not in dirs:
187 self._incpath(base)
188 dirs[base] = 1
189 else:
190 dirs[base] += 1
191
192 def _decpath(self, path):
193 c = path.rfind('/')
194 if c >= 0:
195 base = path[:c]
196 dirs = self._dirs
197 if dirs[base] == 1:
198 del dirs[base]
199 self._decpath(base)
200 else:
201 dirs[base] -= 1
239 for base in _finddirs(f):
240 if dirs[base] == 1:
241 del dirs[base]
242 else:
243 dirs[base] -= 1
202 244
203 def _incpathcheck(self, f):
204 if '\r' in f or '\n' in f:
205 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
206 % f)
207 # shadows
208 if f in self._dirs:
209 raise util.Abort(_('directory %r already in dirstate') % f)
210 for c in strutil.rfindall(f, '/'):
211 d = f[:c]
212 if d in self._dirs:
213 break
214 if d in self._map and self[d] != 'r':
215 raise util.Abort(_('file %r in dirstate clashes with %r') %
216 (d, f))
217 self._incpath(f)
218
219 def _changepath(self, f, newstate, relaxed=False):
220 # handle upcoming path changes
245 def _addpath(self, f, check=False):
221 246 oldstate = self[f]
222 if oldstate not in "?r" and newstate in "?r":
223 if "_dirs" in self.__dict__:
224 self._decpath(f)
225 return
226 if oldstate in "?r" and newstate not in "?r":
227 if relaxed and oldstate == '?':
228 # XXX
229 # in relaxed mode we assume the caller knows
230 # what it is doing, workaround for updating
231 # dir-to-file revisions
232 if "_dirs" in self.__dict__:
233 self._incpath(f)
234 return
235 self._incpathcheck(f)
236 return
247 if check or oldstate == "r":
248 if '\r' in f or '\n' in f:
249 raise util.Abort(
250 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
251 if f in self._dirs:
252 raise util.Abort(_('directory %r already in dirstate') % f)
253 # shadows
254 for d in _finddirs(f):
255 if d in self._dirs:
256 break
257 if d in self._map and self[d] != 'r':
258 raise util.Abort(
259 _('file %r in dirstate clashes with %r') % (d, f))
260 if oldstate in "?r" and "_dirs" in self.__dict__:
261 dirs = self._dirs
262 for base in _finddirs(f):
263 dirs[base] = dirs.get(base, 0) + 1
237 264
238 265 def normal(self, f):
239 266 'mark a file normal and clean'
240 267 self._dirty = True
241 self._changepath(f, 'n', True)
268 self._addpath(f)
242 269 s = os.lstat(self._join(f))
243 270 self._map[f] = ('n', s.st_mode, s.st_size, s.st_mtime, 0)
244 271 if f in self._copymap:
@@ -262,7 +289,7 b' class dirstate(object):'
262 289 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
263 290 return
264 291 self._dirty = True
265 self._changepath(f, 'n', True)
292 self._addpath(f)
266 293 self._map[f] = ('n', 0, -1, -1, 0)
267 294 if f in self._copymap:
268 295 del self._copymap[f]
@@ -270,7 +297,7 b' class dirstate(object):'
270 297 def normaldirty(self, f):
271 298 'mark a file normal, but dirty'
272 299 self._dirty = True
273 self._changepath(f, 'n', True)
300 self._addpath(f)
274 301 self._map[f] = ('n', 0, -2, -1, 0)
275 302 if f in self._copymap:
276 303 del self._copymap[f]
@@ -278,7 +305,7 b' class dirstate(object):'
278 305 def add(self, f):
279 306 'mark a file added'
280 307 self._dirty = True
281 self._changepath(f, 'a')
308 self._addpath(f, True)
282 309 self._map[f] = ('a', 0, -1, -1, 0)
283 310 if f in self._copymap:
284 311 del self._copymap[f]
@@ -286,7 +313,7 b' class dirstate(object):'
286 313 def remove(self, f):
287 314 'mark a file removed'
288 315 self._dirty = True
289 self._changepath(f, 'r')
316 self._droppath(f)
290 317 size = 0
291 318 if self._pl[1] != nullid and f in self._map:
292 319 entry = self._map[f]
@@ -302,7 +329,7 b' class dirstate(object):'
302 329 'mark a file merged'
303 330 self._dirty = True
304 331 s = os.lstat(self._join(f))
305 self._changepath(f, 'm', True)
332 self._addpath(f)
306 333 self._map[f] = ('m', s.st_mode, s.st_size, s.st_mtime, 0)
307 334 if f in self._copymap:
308 335 del self._copymap[f]
@@ -311,11 +338,18 b' class dirstate(object):'
311 338 'forget a file'
312 339 self._dirty = True
313 340 try:
314 self._changepath(f, '?')
341 self._droppath(f)
315 342 del self._map[f]
316 343 except KeyError:
317 344 self._ui.warn(_("not in dirstate: %s\n") % f)
318 345
346 def _normalize(self, path):
347 if path not in self._foldmap:
348 if not os.path.exists(path):
349 return path
350 self._foldmap[path] = util.fspath(path, self._root)
351 return self._foldmap[path]
352
319 353 def clear(self):
320 354 self._map = {}
321 355 if "_dirs" in self.__dict__:
@@ -327,7 +361,7 b' class dirstate(object):'
327 361 def rebuild(self, parent, files):
328 362 self.clear()
329 363 for f in files:
330 if files.execf(f):
364 if 'x' in files.flags(f):
331 365 self._map[f] = ('n', 0777, -1, 0, 0)
332 366 else:
333 367 self._map[f] = ('n', 0666, -1, 0, 0)
@@ -364,40 +398,33 b' class dirstate(object):'
364 398 st.rename()
365 399 self._dirty = self._dirtypl = False
366 400
367 def _filter(self, files):
368 ret = {}
369 unknown = []
370
371 for x in files:
372 if x == '.':
373 return self._map.copy()
374 if x not in self._map:
375 unknown.append(x)
376 else:
377 ret[x] = self._map[x]
378
379 if not unknown:
380 return ret
401 def _dirignore(self, f):
402 if f == '.':
403 return False
404 if self._ignore(f):
405 return True
406 for p in _finddirs(f):
407 if self._ignore(p):
408 return True
409 return False
381 410
382 b = self._map.keys()
383 b.sort()
384 blen = len(b)
411 def walk(self, match, unknown, ignored):
412 '''
413 walk recursively through the directory tree, finding all files
414 matched by the match function
415
416 results are yielded in a tuple (filename, stat), where stat
417 and st is the stat result if the file was found in the directory.
418 '''
385 419
386 for x in unknown:
387 bs = bisect.bisect(b, "%s%s" % (x, '/'))
388 while bs < blen:
389 s = b[bs]
390 if len(s) > len(x) and s.startswith(x):
391 ret[s] = self._map[s]
392 else:
393 break
394 bs += 1
395 return ret
420 def fwarn(f, msg):
421 self._ui.warn('%s: %s\n' % (self.pathto(ff), msg))
422 return False
423 badfn = fwarn
424 if hasattr(match, 'bad'):
425 badfn = match.bad
396 426
397 def _supported(self, f, mode, verbose=False):
398 if stat.S_ISREG(mode) or stat.S_ISLNK(mode):
399 return True
400 if verbose:
427 def badtype(f, mode):
401 428 kind = 'unknown'
402 429 if stat.S_ISCHR(mode): kind = _('character device')
403 430 elif stat.S_ISBLK(mode): kind = _('block device')
@@ -406,173 +433,121 b' class dirstate(object):'
406 433 elif stat.S_ISDIR(mode): kind = _('directory')
407 434 self._ui.warn(_('%s: unsupported file type (type is %s)\n')
408 435 % (self.pathto(f), kind))
409 return False
410
411 def _dirignore(self, f):
412 if f == '.':
413 return False
414 if self._ignore(f):
415 return True
416 for c in strutil.findall(f, '/'):
417 if self._ignore(f[:c]):
418 return True
419 return False
420
421 def walk(self, files=None, match=util.always, badmatch=None):
422 # filter out the stat
423 for src, f, st in self.statwalk(files, match, badmatch=badmatch):
424 yield src, f
425
426 def statwalk(self, files=None, match=util.always, unknown=True,
427 ignored=False, badmatch=None, directories=False):
428 '''
429 walk recursively through the directory tree, finding all files
430 matched by the match function
431
432 results are yielded in a tuple (src, filename, st), where src
433 is one of:
434 'f' the file was found in the directory tree
435 'd' the file is a directory of the tree
436 'm' the file was only in the dirstate and not in the tree
437 'b' file was not found and matched badmatch
438
439 and st is the stat result if the file was found in the directory.
440 '''
441
442 # walk all files by default
443 if not files:
444 files = ['.']
445 dc = self._map.copy()
446 else:
447 files = util.unique(files)
448 dc = self._filter(files)
449
450 def imatch(file_):
451 if file_ not in dc and self._ignore(file_):
452 return False
453 return match(file_)
454 436
455 437 # TODO: don't walk unknown directories if unknown and ignored are False
456 438 ignore = self._ignore
457 439 dirignore = self._dirignore
458 440 if ignored:
459 imatch = match
460 441 ignore = util.never
461 442 dirignore = util.never
462 443
463 # self._root may end with a path separator when self._root == '/'
464 common_prefix_len = len(self._root)
465 if not util.endswithsep(self._root):
466 common_prefix_len += 1
467
444 matchfn = match.matchfn
445 dmap = self._map
468 446 normpath = util.normpath
447 normalize = self.normalize
469 448 listdir = osutil.listdir
470 449 lstat = os.lstat
471 450 bisect_left = bisect.bisect_left
472 isdir = os.path.isdir
473 451 pconvert = util.pconvert
474 join = os.path.join
475 s_isdir = stat.S_ISDIR
476 supported = self._supported
477 _join = self._join
478 known = {'.hg': 1}
452 getkind = stat.S_IFMT
453 dirkind = stat.S_IFDIR
454 regkind = stat.S_IFREG
455 lnkkind = stat.S_IFLNK
456 join = self._join
457 work = []
458 wadd = work.append
459
460 files = util.unique(match.files())
461 if not files or '.' in files:
462 files = ['']
463 results = {'.hg': None}
464
465 # step 1: find all explicit files
466 for ff in util.sort(files):
467 nf = normalize(normpath(ff))
468 if nf in results:
469 continue
479 470
480 # recursion free walker, faster than os.walk.
481 def findfiles(s):
482 work = [s]
483 wadd = work.append
484 found = []
485 add = found.append
486 if directories:
487 add((normpath(s[common_prefix_len:]), 'd', lstat(s)))
488 while work:
489 top = work.pop()
490 entries = listdir(top, stat=True)
491 # nd is the top of the repository dir tree
492 nd = normpath(top[common_prefix_len:])
493 if nd == '.':
494 nd = ''
471 try:
472 st = lstat(join(nf))
473 kind = getkind(st.st_mode)
474 if kind == dirkind:
475 if not dirignore(nf):
476 wadd(nf)
477 elif kind == regkind or kind == lnkkind:
478 results[nf] = st
495 479 else:
496 # do not recurse into a repo contained in this
497 # one. use bisect to find .hg directory so speed
498 # is good on big directory.
499 names = [e[0] for e in entries]
500 hg = bisect_left(names, '.hg')
501 if hg < len(names) and names[hg] == '.hg':
502 if isdir(join(top, '.hg')):
503 continue
504 for f, kind, st in entries:
505 np = pconvert(join(nd, f))
506 if np in known:
480 badtype(ff, kind)
481 if nf in dmap:
482 results[nf] = None
483 except OSError, inst:
484 keep = False
485 prefix = nf + "/"
486 for fn in dmap:
487 if nf == fn or fn.startswith(prefix):
488 keep = True
489 break
490 if not keep:
491 if inst.errno != errno.ENOENT:
492 fwarn(ff, inst.strerror)
493 elif badfn(ff, inst.strerror):
494 if (nf in dmap or not ignore(nf)) and matchfn(nf):
495 results[nf] = None
496
497 # step 2: visit subdirectories
498 while work:
499 nd = work.pop()
500 if hasattr(match, 'dir'):
501 match.dir(nd)
502 entries = listdir(join(nd), stat=True)
503 if nd == '.':
504 nd = ''
505 else:
506 # do not recurse into a repo contained in this
507 # one. use bisect to find .hg directory so speed
508 # is good on big directory.
509 hg = bisect_left(entries, ('.hg'))
510 if hg < len(entries) and entries[hg][0] == '.hg' \
511 and entries[hg][1] == dirkind:
507 512 continue
508 known[np] = 1
509 p = join(top, f)
510 # don't trip over symlinks
511 if kind == stat.S_IFDIR:
512 if not ignore(np):
513 wadd(p)
514 if directories:
515 add((np, 'd', st))
516 if np in dc and match(np):
517 add((np, 'm', st))
518 elif imatch(np):
519 if supported(np, st.st_mode):
520 add((np, 'f', st))
521 elif np in dc:
522 add((np, 'm', st))
523 found.sort()
524 return found
513 for f, kind, st in entries:
514 nf = normalize(nd and (nd + "/" + f) or f)
515 if nf not in results:
516 if kind == dirkind:
517 if not ignore(nf):
518 wadd(nf)
519 if nf in dmap and matchfn(nf):
520 results[nf] = None
521 elif kind == regkind or kind == lnkkind:
522 if nf in dmap:
523 if matchfn(nf):
524 results[nf] = st
525 elif matchfn(nf) and not ignore(nf):
526 results[nf] = st
527 elif nf in dmap and matchfn(nf):
528 results[nf] = None
525 529
526 # step one, find all files that match our criteria
527 files.sort()
528 for ff in files:
529 nf = normpath(ff)
530 f = _join(ff)
530 # step 3: report unseen items in the dmap hash
531 visit = [f for f in dmap if f not in results and match(f)]
532 for nf in util.sort(visit):
533 results[nf] = None
531 534 try:
532 st = lstat(f)
535 st = lstat(join(nf))
536 kind = getkind(st.st_mode)
537 if kind == regkind or kind == lnkkind:
538 results[nf] = st
533 539 except OSError, inst:
534 found = False
535 for fn in dc:
536 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
537 found = True
538 break
539 if not found:
540 if inst.errno != errno.ENOENT or not badmatch:
541 self._ui.warn('%s: %s\n' %
542 (self.pathto(ff), inst.strerror))
543 elif badmatch and badmatch(ff) and imatch(nf):
544 yield 'b', ff, None
545 continue
546 if s_isdir(st.st_mode):
547 if not dirignore(nf):
548 for f, src, st in findfiles(f):
549 yield src, f, st
550 else:
551 if nf in known:
552 continue
553 known[nf] = 1
554 if match(nf):
555 if supported(ff, st.st_mode, verbose=True):
556 yield 'f', nf, st
557 elif ff in dc:
558 yield 'm', nf, st
540 if inst.errno not in (errno.ENOENT, errno.ENOTDIR):
541 raise
559 542
560 # step two run through anything left in the dc hash and yield
561 # if we haven't already seen it
562 ks = dc.keys()
563 ks.sort()
564 for k in ks:
565 if k in known:
566 continue
567 known[k] = 1
568 if imatch(k):
569 yield 'm', k, None
543 del results['.hg']
544 return results
570 545
571 def status(self, files, match, list_ignored, list_clean, list_unknown=True):
546 def status(self, match, ignored, clean, unknown):
547 listignored, listclean, listunknown = ignored, clean, unknown
572 548 lookup, modified, added, unknown, ignored = [], [], [], [], []
573 549 removed, deleted, clean = [], [], []
574 550
575 files = files or []
576 551 _join = self._join
577 552 lstat = os.lstat
578 553 cmap = self._copymap
@@ -586,38 +561,20 b' class dirstate(object):'
586 561 dadd = deleted.append
587 562 cadd = clean.append
588 563
589 for src, fn, st in self.statwalk(files, match, unknown=list_unknown,
590 ignored=list_ignored):
591 if fn in dmap:
592 type_, mode, size, time, foo = dmap[fn]
593 else:
594 if (list_ignored or fn in files) and self._dirignore(fn):
595 if list_ignored:
564 for fn, st in self.walk(match, listunknown, listignored).iteritems():
565 if fn not in dmap:
566 if (listignored or match.exact(fn)) and self._dirignore(fn):
567 if listignored:
596 568 iadd(fn)
597 elif list_unknown:
569 elif listunknown:
598 570 uadd(fn)
599 571 continue
600 if src == 'm':
601 nonexistent = True
602 if not st:
603 try:
604 st = lstat(_join(fn))
605 except OSError, inst:
606 if inst.errno not in (errno.ENOENT, errno.ENOTDIR):
607 raise
608 st = None
609 # We need to re-check that it is a valid file
610 if st and self._supported(fn, st.st_mode):
611 nonexistent = False
612 # XXX: what to do with file no longer present in the fs
613 # who are not removed in the dirstate ?
614 if nonexistent and type_ in "nma":
615 dadd(fn)
616 continue
617 # check the common case first
618 if type_ == 'n':
619 if not st:
620 st = lstat(_join(fn))
572
573 state, mode, size, time, foo = dmap[fn]
574
575 if not st and state in "nma":
576 dadd(fn)
577 elif state == 'n':
621 578 if (size >= 0 and
622 579 (size != st.st_size
623 580 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
@@ -626,13 +583,13 b' class dirstate(object):'
626 583 madd(fn)
627 584 elif time != int(st.st_mtime):
628 585 ladd(fn)
629 elif list_clean:
586 elif listclean:
630 587 cadd(fn)
631 elif type_ == 'm':
588 elif state == 'm':
632 589 madd(fn)
633 elif type_ == 'a':
590 elif state == 'a':
634 591 aadd(fn)
635 elif type_ == 'r':
592 elif state == 'r':
636 593 radd(fn)
637 594
638 595 return (lookup, modified, added, removed, deleted, unknown, ignored,
@@ -5,7 +5,7 b''
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 from node import nullrev
8 from node import nullrev, short
9 9 from i18n import _
10 10 import util, os, tempfile, simplemerge, re, filecmp
11 11
@@ -63,8 +63,7 b' def _picktool(repo, ui, path, binary, sy'
63 63 if t not in tools:
64 64 tools[t] = int(_toolstr(ui, t, "priority", "0"))
65 65 names = tools.keys()
66 tools = [(-p,t) for t,p in tools.items()]
67 tools.sort()
66 tools = util.sort([(-p,t) for t,p in tools.items()])
68 67 uimerge = ui.config("ui", "merge")
69 68 if uimerge:
70 69 if uimerge not in names:
@@ -101,13 +100,14 b' def _matcheol(file, origfile):'
101 100 if newdata != data:
102 101 open(file, "wb").write(newdata)
103 102
104 def filemerge(repo, fw, fd, fo, wctx, mctx):
103 def filemerge(repo, mynode, orig, fcd, fco, fca):
105 104 """perform a 3-way merge in the working directory
106 105
107 fw = original filename in the working directory
108 fd = destination filename in the working directory
109 fo = filename in other parent
110 wctx, mctx = working and merge changecontexts
106 mynode = parent node before merge
107 orig = original local filename before merge
108 fco = other file context
109 fca = ancestor file context
110 fcd = local file context for current/destination file
111 111 """
112 112
113 113 def temp(prefix, ctx):
@@ -125,29 +125,27 b' def filemerge(repo, fw, fd, fo, wctx, mc'
125 125 except IOError:
126 126 return False
127 127
128 fco = mctx.filectx(fo)
129 if not fco.cmp(wctx.filectx(fd).data()): # files identical?
128 if not fco.cmp(fcd.data()): # files identical?
130 129 return None
131 130
132 131 ui = repo.ui
133 fcm = wctx.filectx(fw)
134 fca = fcm.ancestor(fco) or repo.filectx(fw, fileid=nullrev)
135 binary = isbin(fcm) or isbin(fco) or isbin(fca)
136 symlink = fcm.islink() or fco.islink()
137 tool, toolpath = _picktool(repo, ui, fw, binary, symlink)
132 fd = fcd.path()
133 binary = isbin(fcd) or isbin(fco) or isbin(fca)
134 symlink = 'l' in fcd.flags() + fco.flags()
135 tool, toolpath = _picktool(repo, ui, fd, binary, symlink)
138 136 ui.debug(_("picked tool '%s' for %s (binary %s symlink %s)\n") %
139 (tool, fw, binary, symlink))
137 (tool, fd, binary, symlink))
140 138
141 139 if not tool:
142 140 tool = "internal:local"
143 141 if ui.prompt(_(" no tool found to merge %s\n"
144 "keep (l)ocal or take (o)ther?") % fw,
142 "keep (l)ocal or take (o)ther?") % fd,
145 143 _("[lo]"), _("l")) != _("l"):
146 144 tool = "internal:other"
147 145 if tool == "internal:local":
148 146 return 0
149 147 if tool == "internal:other":
150 repo.wwrite(fd, fco.data(), fco.fileflags())
148 repo.wwrite(fd, fco.data(), fco.flags())
151 149 return 0
152 150 if tool == "internal:fail":
153 151 return 1
@@ -160,11 +158,12 b' def filemerge(repo, fw, fd, fo, wctx, mc'
160 158 back = a + ".orig"
161 159 util.copyfile(a, back)
162 160
163 if fw != fo:
164 repo.ui.status(_("merging %s and %s\n") % (fw, fo))
161 if orig != fco.path():
162 repo.ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
165 163 else:
166 repo.ui.status(_("merging %s\n") % fw)
167 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcm, fco, fca))
164 repo.ui.status(_("merging %s\n") % fd)
165
166 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcd, fco, fca))
168 167
169 168 # do we attempt to simplemerge first?
170 169 if _toolbool(ui, tool, "premerge", not (binary or symlink)):
@@ -178,11 +177,11 b' def filemerge(repo, fw, fd, fo, wctx, mc'
178 177 util.copyfile(back, a) # restore from backup and try again
179 178
180 179 env = dict(HG_FILE=fd,
181 HG_MY_NODE=str(wctx.parents()[0]),
182 HG_OTHER_NODE=str(mctx),
183 HG_MY_ISLINK=fcm.islink(),
184 HG_OTHER_ISLINK=fco.islink(),
185 HG_BASE_ISLINK=fca.islink())
180 HG_MY_NODE=short(mynode),
181 HG_OTHER_NODE=str(fco.changectx()),
182 HG_MY_ISLINK='l' in fcd.flags(),
183 HG_OTHER_ISLINK='l' in fco.flags(),
184 HG_BASE_ISLINK='l' in fca.flags())
186 185
187 186 if tool == "internal:merge":
188 187 r = simplemerge.simplemerge(a, b, c, label=['local', 'other'])
@@ -196,7 +195,7 b' def filemerge(repo, fw, fd, fo, wctx, mc'
196 195 r = util.system(toolpath + ' ' + args, cwd=repo.root, environ=env)
197 196
198 197 if not r and _toolbool(ui, tool, "checkconflicts"):
199 if re.match("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcm.data()):
198 if re.match("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data()):
200 199 r = 1
201 200
202 201 if not r and _toolbool(ui, tool, "checkchanged"):
@@ -30,12 +30,12 b' def bisect(changelog, state):'
30 30 badrev = min([changelog.rev(n) for n in bad])
31 31 goodrevs = [changelog.rev(n) for n in good]
32 32 # build ancestors array
33 ancestors = [[]] * (changelog.count() + 1) # an extra for [-1]
33 ancestors = [[]] * (len(changelog) + 1) # an extra for [-1]
34 34
35 35 # clear good revs from array
36 36 for node in goodrevs:
37 37 ancestors[node] = None
38 for rev in xrange(changelog.count(), -1, -1):
38 for rev in xrange(len(changelog), -1, -1):
39 39 if ancestors[rev] is None:
40 40 for prev in clparents(rev):
41 41 ancestors[prev] = None
@@ -5,8 +5,8 b''
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 helptable = {
9 "dates|Date Formats":
8 helptable = (
9 ("dates|Date Formats",
10 10 r'''
11 11 Some commands allow the user to specify a date:
12 12 backout, commit, import, tag: Specify the commit date.
@@ -21,7 +21,7 b' helptable = {'
21 21 "13:18" (today assumed)
22 22 "3:39" (3:39AM assumed)
23 23 "3:39pm" (15:39)
24 "2006-12-6 13:18:29" (ISO 8601 format)
24 "2006-12-06 13:18:29" (ISO 8601 format)
25 25 "2006-12-6 13:18"
26 26 "2006-12-6"
27 27 "12-6"
@@ -43,9 +43,55 b' helptable = {'
43 43 ">{date}" - on or after a given date
44 44 "{date} to {date}" - a date range, inclusive
45 45 "-{days}" - within a given number of days of today
46 ''',
46 '''),
47
48 ("patterns|File Name Patterns",
49 r'''
50 Mercurial accepts several notations for identifying one or more
51 files at a time.
52
53 By default, Mercurial treats filenames as shell-style extended
54 glob patterns.
55
56 Alternate pattern notations must be specified explicitly.
57
58 To use a plain path name without any pattern matching, start a
59 name with "path:". These path names must match completely, from
60 the root of the current repository.
61
62 To use an extended glob, start a name with "glob:". Globs are
63 rooted at the current directory; a glob such as "*.c" will match
64 files ending in ".c" in the current directory only.
65
66 The supported glob syntax extensions are "**" to match any string
67 across path separators, and "{a,b}" to mean "a or b".
47 68
48 'environment|env|Environment Variables':
69 To use a Perl/Python regular expression, start a name with "re:".
70 Regexp pattern matching is anchored at the root of the repository.
71
72 Plain examples:
73
74 path:foo/bar a name bar in a directory named foo in the root of
75 the repository
76 path:path:name a file or directory named "path:name"
77
78 Glob examples:
79
80 glob:*.c any name ending in ".c" in the current directory
81 *.c any name ending in ".c" in the current directory
82 **.c any name ending in ".c" in the current directory, or
83 any subdirectory
84 foo/*.c any name ending in ".c" in the directory foo
85 foo/**.c any name ending in ".c" in the directory foo, or any
86 subdirectory
87
88 Regexp examples:
89
90 re:.*\.c$ any name ending in ".c", anywhere in the repository
91
92 '''),
93
94 ('environment|env|Environment Variables',
49 95 r'''
50 96 HG::
51 97 Path to the 'hg' executable, automatically passed when running hooks,
@@ -114,51 +160,57 b' EDITOR::'
114 160 PYTHONPATH::
115 161 This is used by Python to find imported modules and may need to be set
116 162 appropriately if Mercurial is not installed system-wide.
117 ''',
163 '''),
118 164
119 "patterns|File Name Patterns": r'''
120 Mercurial accepts several notations for identifying one or more
121 files at a time.
165 ('revs|revisions|Specifying Single Revisions',
166 r'''
167 Mercurial accepts several notations for identifying individual
168 revisions.
122 169
123 By default, Mercurial treats filenames as shell-style extended
124 glob patterns.
125
126 Alternate pattern notations must be specified explicitly.
170 A plain integer is treated as a revision number. Negative
171 integers are treated as offsets from the tip, with -1 denoting the
172 tip.
127 173
128 To use a plain path name without any pattern matching, start a
129 name with "path:". These path names must match completely, from
130 the root of the current repository.
174 A 40-digit hexadecimal string is treated as a unique revision
175 identifier.
131 176
132 To use an extended glob, start a name with "glob:". Globs are
133 rooted at the current directory; a glob such as "*.c" will match
134 files ending in ".c" in the current directory only.
177 A hexadecimal string less than 40 characters long is treated as a
178 unique revision identifier, and referred to as a short-form
179 identifier. A short-form identifier is only valid if it is the
180 prefix of one full-length identifier.
135 181
136 The supported glob syntax extensions are "**" to match any string
137 across path separators, and "{a,b}" to mean "a or b".
182 Any other string is treated as a tag name, which is a symbolic
183 name associated with a revision identifier. Tag names may not
184 contain the ":" character.
185
186 The reserved name "tip" is a special tag that always identifies
187 the most recent revision.
138 188
139 To use a Perl/Python regular expression, start a name with "re:".
140 Regexp pattern matching is anchored at the root of the repository.
141
142 Plain examples:
189 The reserved name "null" indicates the null revision. This is the
190 revision of an empty repository, and the parent of revision 0.
143 191
144 path:foo/bar a name bar in a directory named foo in the root of
145 the repository
146 path:path:name a file or directory named "path:name"
147
148 Glob examples:
192 The reserved name "." indicates the working directory parent. If
193 no working directory is checked out, it is equivalent to null.
194 If an uncommitted merge is in progress, "." is the revision of
195 the first parent.
196 '''),
149 197
150 glob:*.c any name ending in ".c" in the current directory
151 *.c any name ending in ".c" in the current directory
152 **.c any name ending in ".c" in the current directory, or
153 any subdirectory
154 foo/*.c any name ending in ".c" in the directory foo
155 foo/**.c any name ending in ".c" in the directory foo, or any
156 subdirectory
198 ('mrevs|multirevs|Specifying Multiple Revisions',
199 r'''
200 When Mercurial accepts more than one revision, they may be
201 specified individually, or provided as a continuous range,
202 separated by the ":" character.
157 203
158 Regexp examples:
159
160 re:.*\.c$ any name ending in ".c", anywhere in the repository
204 The syntax of range notation is [BEGIN]:[END], where BEGIN and END
205 are revision identifiers. Both BEGIN and END are optional. If
206 BEGIN is not specified, it defaults to revision number 0. If END
207 is not specified, it defaults to the tip. The range ":" thus
208 means "all revisions".
161 209
162 ''',
163 }
210 If BEGIN is greater than END, revisions are treated in reverse
211 order.
164 212
213 A range acts as a closed interval. This means that a range of 3:5
214 gives 3, 4 and 5. Similarly, a range of 4:2 gives 4, 3, and 2.
215 '''),
216 )
@@ -16,7 +16,7 b' def _local(path):'
16 16 return (os.path.isfile(util.drop_scheme('file', path)) and
17 17 bundlerepo or localrepo)
18 18
19 def parseurl(url, revs):
19 def parseurl(url, revs=[]):
20 20 '''parse url#branch, returning url, branch + revs'''
21 21
22 22 if '#' not in url:
@@ -69,6 +69,15 b' def defaultdest(source):'
69 69 '''return default destination of clone if none is given'''
70 70 return os.path.basename(os.path.normpath(source))
71 71
72 def localpath(path):
73 if path.startswith('file://localhost/'):
74 return path[16:]
75 if path.startswith('file://'):
76 return path[7:]
77 if path.startswith('file:'):
78 return path[5:]
79 return path
80
72 81 def clone(ui, source, dest=None, pull=False, rev=None, update=True,
73 82 stream=False):
74 83 """Make a copy of an existing repository.
@@ -100,7 +109,8 b' def clone(ui, source, dest=None, pull=Fa'
100 109 rev: revision to clone up to (implies pull=True)
101 110
102 111 update: update working directory after clone completes, if
103 destination is local repository
112 destination is local repository (True means update to default rev,
113 anything else is treated as a revision)
104 114 """
105 115
106 116 if isinstance(source, str):
@@ -116,15 +126,6 b' def clone(ui, source, dest=None, pull=Fa'
116 126 dest = defaultdest(source)
117 127 ui.status(_("destination directory: %s\n") % dest)
118 128
119 def localpath(path):
120 if path.startswith('file://localhost/'):
121 return path[16:]
122 if path.startswith('file://'):
123 return path[7:]
124 if path.startswith('file:'):
125 return path[5:]
126 return path
127
128 129 dest = localpath(dest)
129 130 source = localpath(source)
130 131
@@ -244,7 +245,9 b' def clone(ui, source, dest=None, pull=Fa'
244 245
245 246 if update:
246 247 dest_repo.ui.status(_("updating working directory\n"))
247 if not checkout:
248 if update is not True:
249 checkout = update
250 elif not checkout:
248 251 try:
249 252 checkout = dest_repo.lookup("default")
250 253 except:
@@ -271,15 +274,7 b' def update(repo, node):'
271 274 stats = _merge.update(repo, node, False, False, None)
272 275 _showstats(repo, stats)
273 276 if stats[3]:
274 repo.ui.status(_("There are unresolved merges with"
275 " locally modified files.\n"))
276 if stats[1]:
277 repo.ui.status(_("You can finish the partial merge using:\n"))
278 else:
279 repo.ui.status(_("You can redo the full merge using:\n"))
280 # len(pl)==1, otherwise _merge.update() would have raised util.Abort:
281 repo.ui.status(_(" hg update %s\n hg update %s\n")
282 % (pl[0].rev(), repo.changectx(node).rev()))
277 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
283 278 return stats[3] > 0
284 279
285 280 def clean(repo, node, show_stats=True):
@@ -294,11 +289,7 b' def merge(repo, node, force=None, remind'
294 289 _showstats(repo, stats)
295 290 if stats[3]:
296 291 pl = repo.parents()
297 repo.ui.status(_("There are unresolved merges,"
298 " you can redo the full merge using:\n"
299 " hg update -C %s\n"
300 " hg merge %s\n")
301 % (pl[0].rev(), pl[1].rev()))
292 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
302 293 elif remind:
303 294 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
304 295 return stats[3] > 0
This diff has been collapsed as it changes many lines, (734 lines changed) Show them Hide them
@@ -6,79 +6,22 b''
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 import os, mimetypes, re, mimetools, cStringIO
10 from mercurial.node import hex, nullid, short
9 import os, mimetypes
10 from mercurial.node import hex, nullid
11 11 from mercurial.repo import RepoError
12 from mercurial import mdiff, ui, hg, util, archival, patch, hook
13 from mercurial import revlog, templater, templatefilters, changegroup
14 from common import get_mtime, style_map, paritygen, countgen, get_contact
15 from common import ErrorResponse
12 from mercurial import mdiff, ui, hg, util, patch, hook
13 from mercurial import revlog, templater, templatefilters
14 from common import get_mtime, style_map, paritygen, countgen, ErrorResponse
16 15 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
17 16 from request import wsgirequest
18 import webcommands, protocol
19
20 shortcuts = {
21 'cl': [('cmd', ['changelog']), ('rev', None)],
22 'sl': [('cmd', ['shortlog']), ('rev', None)],
23 'cs': [('cmd', ['changeset']), ('node', None)],
24 'f': [('cmd', ['file']), ('filenode', None)],
25 'fl': [('cmd', ['filelog']), ('filenode', None)],
26 'fd': [('cmd', ['filediff']), ('node', None)],
27 'fa': [('cmd', ['annotate']), ('filenode', None)],
28 'mf': [('cmd', ['manifest']), ('manifest', None)],
29 'ca': [('cmd', ['archive']), ('node', None)],
30 'tags': [('cmd', ['tags'])],
31 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
32 'static': [('cmd', ['static']), ('file', None)]
33 }
34
35 def _up(p):
36 if p[0] != "/":
37 p = "/" + p
38 if p[-1] == "/":
39 p = p[:-1]
40 up = os.path.dirname(p)
41 if up == "/":
42 return "/"
43 return up + "/"
17 import webcommands, protocol, webutil
44 18
45 def revnavgen(pos, pagelen, limit, nodefunc):
46 def seq(factor, limit=None):
47 if limit:
48 yield limit
49 if limit >= 20 and limit <= 40:
50 yield 50
51 else:
52 yield 1 * factor
53 yield 3 * factor
54 for f in seq(factor * 10):
55 yield f
56
57 def nav(**map):
58 l = []
59 last = 0
60 for f in seq(1, pagelen):
61 if f < pagelen or f <= last:
62 continue
63 if f > limit:
64 break
65 last = f
66 if pos + f < limit:
67 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
68 if pos - f >= 0:
69 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
70
71 try:
72 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
73
74 for label, node in l:
75 yield {"label": label, "node": node}
76
77 yield {"label": "tip", "node": "tip"}
78 except RepoError:
79 pass
80
81 return nav
19 perms = {
20 'changegroup': 'pull',
21 'changegroupsubset': 'pull',
22 'unbundle': 'push',
23 'stream_out': 'pull',
24 }
82 25
83 26 class hgweb(object):
84 27 def __init__(self, repo, name=None):
@@ -93,7 +36,6 b' class hgweb(object):'
93 36 self.reponame = name
94 37 self.archives = 'zip', 'gz', 'bz2'
95 38 self.stripecount = 1
96 self._capabilities = None
97 39 # a repo owner may set web.templates in .hg/hgrc to get any file
98 40 # readable by the user running the CGI script
99 41 self.templatepath = self.config("web", "templates",
@@ -125,18 +67,6 b' class hgweb(object):'
125 67 self.maxfiles = int(self.config("web", "maxfiles", 10))
126 68 self.allowpull = self.configbool("web", "allowpull", True)
127 69 self.encoding = self.config("web", "encoding", util._encoding)
128 self._capabilities = None
129
130 def capabilities(self):
131 if self._capabilities is not None:
132 return self._capabilities
133 caps = ['lookup', 'changegroupsubset']
134 if self.configbool('server', 'uncompressed'):
135 caps.append('stream=%d' % self.repo.changelog.version)
136 if changegroup.bundlepriority:
137 caps.append('unbundle=%s' % ','.join(changegroup.bundlepriority))
138 self._capabilities = caps
139 return caps
140 70
141 71 def run(self):
142 72 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
@@ -146,22 +76,22 b' class hgweb(object):'
146 76
147 77 def __call__(self, env, respond):
148 78 req = wsgirequest(env, respond)
149 self.run_wsgi(req)
150 return req
79 return self.run_wsgi(req)
151 80
152 81 def run_wsgi(self, req):
153 82
154 83 self.refresh()
155 84
156 # expand form shortcuts
85 # process this if it's a protocol request
86 # protocol bits don't need to create any URLs
87 # and the clients always use the old URL structure
157 88
158 for k in shortcuts.iterkeys():
159 if k in req.form:
160 for name, value in shortcuts[k]:
161 if value is None:
162 value = req.form[k]
163 req.form[name] = value
164 del req.form[k]
89 cmd = req.form.get('cmd', [''])[0]
90 if cmd and cmd in protocol.__all__:
91 if cmd in perms and not self.check_perm(req, perms[cmd]):
92 return []
93 method = getattr(protocol, cmd)
94 return method(self.repo, req)
165 95
166 96 # work with CGI variables to create coherent structure
167 97 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
@@ -194,8 +124,10 b' class hgweb(object):'
194 124 cmd = cmd[style+1:]
195 125
196 126 # avoid accepting e.g. style parameter as command
197 if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
127 if hasattr(webcommands, cmd):
198 128 req.form['cmd'] = [cmd]
129 else:
130 cmd = ''
199 131
200 132 if args and args[0]:
201 133 node = args.pop(0)
@@ -213,30 +145,13 b' class hgweb(object):'
213 145 req.form['node'] = [fn[:-len(ext)]]
214 146 req.form['type'] = [type_]
215 147
216 # process this if it's a protocol request
217
218 cmd = req.form.get('cmd', [''])[0]
219 if cmd in protocol.__all__:
220 method = getattr(protocol, cmd)
221 method(self, req)
222 return
223
224 148 # process the web interface request
225 149
226 150 try:
227 151
228 152 tmpl = self.templater(req)
229 try:
230 ctype = tmpl('mimetype', encoding=self.encoding)
231 ctype = templater.stringify(ctype)
232 except KeyError:
233 # old templates with inline HTTP headers?
234 if 'mimetype' in tmpl:
235 raise
236 header = tmpl('header', encoding=self.encoding)
237 header_file = cStringIO.StringIO(templater.stringify(header))
238 msg = mimetools.Message(header_file, 0)
239 ctype = msg['content-type']
153 ctype = tmpl('mimetype', encoding=self.encoding)
154 ctype = templater.stringify(ctype)
240 155
241 156 if cmd == '':
242 157 req.form['cmd'] = [tmpl.cache['default']]
@@ -254,6 +169,7 b' class hgweb(object):'
254 169
255 170 req.write(content)
256 171 del tmpl
172 return []
257 173
258 174 except revlog.LookupError, err:
259 175 req.respond(HTTP_NOT_FOUND, ctype)
@@ -261,12 +177,15 b' class hgweb(object):'
261 177 if 'manifest' not in msg:
262 178 msg = 'revision not found: %s' % err.name
263 179 req.write(tmpl('error', error=msg))
180 return []
264 181 except (RepoError, revlog.RevlogError), inst:
265 182 req.respond(HTTP_SERVER_ERROR, ctype)
266 183 req.write(tmpl('error', error=str(inst)))
184 return []
267 185 except ErrorResponse, inst:
268 186 req.respond(inst.code, ctype)
269 187 req.write(tmpl('error', error=inst.message))
188 return []
270 189
271 190 def templater(self, req):
272 191
@@ -291,13 +210,7 b' class hgweb(object):'
291 210 # some functions for the templater
292 211
293 212 def header(**map):
294 header = tmpl('header', encoding=self.encoding, **map)
295 if 'mimetype' not in tmpl:
296 # old template with inline HTTP headers
297 header_file = cStringIO.StringIO(templater.stringify(header))
298 msg = mimetools.Message(header_file, 0)
299 header = header_file.read()
300 yield header
213 yield tmpl('header', encoding=self.encoding, **map)
301 214
302 215 def footer(**map):
303 216 yield tmpl("footer", **map)
@@ -355,54 +268,6 b' class hgweb(object):'
355 268 if len(files) > self.maxfiles:
356 269 yield tmpl("fileellipses")
357 270
358 def siblings(self, siblings=[], hiderev=None, **args):
359 siblings = [s for s in siblings if s.node() != nullid]
360 if len(siblings) == 1 and siblings[0].rev() == hiderev:
361 return
362 for s in siblings:
363 d = {'node': hex(s.node()), 'rev': s.rev()}
364 if hasattr(s, 'path'):
365 d['file'] = s.path()
366 d.update(args)
367 yield d
368
369 def renamelink(self, fl, node):
370 r = fl.renamed(node)
371 if r:
372 return [dict(file=r[0], node=hex(r[1]))]
373 return []
374
375 def nodetagsdict(self, node):
376 return [{"name": i} for i in self.repo.nodetags(node)]
377
378 def nodebranchdict(self, ctx):
379 branches = []
380 branch = ctx.branch()
381 # If this is an empty repo, ctx.node() == nullid,
382 # ctx.branch() == 'default', but branchtags() is
383 # an empty dict. Using dict.get avoids a traceback.
384 if self.repo.branchtags().get(branch) == ctx.node():
385 branches.append({"name": branch})
386 return branches
387
388 def nodeinbranch(self, ctx):
389 branches = []
390 branch = ctx.branch()
391 if branch != 'default' and self.repo.branchtags().get(branch) != ctx.node():
392 branches.append({"name": branch})
393 return branches
394
395 def nodebranchnodefault(self, ctx):
396 branches = []
397 branch = ctx.branch()
398 if branch != 'default':
399 branches.append({"name": branch})
400 return branches
401
402 def showtag(self, tmpl, t1, node=nullid, **args):
403 for t in self.repo.nodetags(node):
404 yield tmpl(t1, tag=t, **args)
405
406 271 def diff(self, tmpl, node1, node2, files):
407 272 def filterfiles(filters, files):
408 273 l = [x for x in files if x in filters]
@@ -443,8 +308,8 b' class hgweb(object):'
443 308 linenumber="% 8s" % lineno)
444 309
445 310 r = self.repo
446 c1 = r.changectx(node1)
447 c2 = r.changectx(node2)
311 c1 = r[node1]
312 c2 = r[node2]
448 313 date1 = util.datestr(c1.date())
449 314 date2 = util.datestr(c2.date())
450 315
@@ -470,524 +335,45 b' class hgweb(object):'
470 335 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
471 336 opts=diffopts), f, tn)
472 337
473 def changelog(self, tmpl, ctx, shortlog=False):
474 def changelist(limit=0,**map):
475 cl = self.repo.changelog
476 l = [] # build a list in forward order for efficiency
477 for i in xrange(start, end):
478 ctx = self.repo.changectx(i)
479 n = ctx.node()
480 showtags = self.showtag(tmpl, 'changelogtag', n)
481
482 l.insert(0, {"parity": parity.next(),
483 "author": ctx.user(),
484 "parent": self.siblings(ctx.parents(), i - 1),
485 "child": self.siblings(ctx.children(), i + 1),
486 "changelogtag": showtags,
487 "desc": ctx.description(),
488 "date": ctx.date(),
489 "files": self.listfilediffs(tmpl, ctx.files(), n),
490 "rev": i,
491 "node": hex(n),
492 "tags": self.nodetagsdict(n),
493 "inbranch": self.nodeinbranch(ctx),
494 "branches": self.nodebranchdict(ctx)})
495
496 if limit > 0:
497 l = l[:limit]
498
499 for e in l:
500 yield e
501
502 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
503 cl = self.repo.changelog
504 count = cl.count()
505 pos = ctx.rev()
506 start = max(0, pos - maxchanges + 1)
507 end = min(count, start + maxchanges)
508 pos = end - 1
509 parity = paritygen(self.stripecount, offset=start-end)
510
511 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
512
513 return tmpl(shortlog and 'shortlog' or 'changelog',
514 changenav=changenav,
515 node=hex(cl.tip()),
516 rev=pos, changesets=count,
517 entries=lambda **x: changelist(limit=0,**x),
518 latestentry=lambda **x: changelist(limit=1,**x),
519 archives=self.archivelist("tip"))
520
521 def search(self, tmpl, query):
522
523 def changelist(**map):
524 cl = self.repo.changelog
525 count = 0
526 qw = query.lower().split()
527
528 def revgen():
529 for i in xrange(cl.count() - 1, 0, -100):
530 l = []
531 for j in xrange(max(0, i - 100), i + 1):
532 ctx = self.repo.changectx(j)
533 l.append(ctx)
534 l.reverse()
535 for e in l:
536 yield e
537
538 for ctx in revgen():
539 miss = 0
540 for q in qw:
541 if not (q in ctx.user().lower() or
542 q in ctx.description().lower() or
543 q in " ".join(ctx.files()).lower()):
544 miss = 1
545 break
546 if miss:
547 continue
548
549 count += 1
550 n = ctx.node()
551 showtags = self.showtag(tmpl, 'changelogtag', n)
552
553 yield tmpl('searchentry',
554 parity=parity.next(),
555 author=ctx.user(),
556 parent=self.siblings(ctx.parents()),
557 child=self.siblings(ctx.children()),
558 changelogtag=showtags,
559 desc=ctx.description(),
560 date=ctx.date(),
561 files=self.listfilediffs(tmpl, ctx.files(), n),
562 rev=ctx.rev(),
563 node=hex(n),
564 tags=self.nodetagsdict(n),
565 inbranch=self.nodeinbranch(ctx),
566 branches=self.nodebranchdict(ctx))
567
568 if count >= self.maxchanges:
569 break
570
571 cl = self.repo.changelog
572 parity = paritygen(self.stripecount)
573
574 return tmpl('search',
575 query=query,
576 node=hex(cl.tip()),
577 entries=changelist,
578 archives=self.archivelist("tip"))
579
580 def changeset(self, tmpl, ctx):
581 n = ctx.node()
582 showtags = self.showtag(tmpl, 'changesettag', n)
583 parents = ctx.parents()
584 p1 = parents[0].node()
585
586 files = []
587 parity = paritygen(self.stripecount)
588 for f in ctx.files():
589 files.append(tmpl("filenodelink",
590 node=hex(n), file=f,
591 parity=parity.next()))
592
593 def diff(**map):
594 yield self.diff(tmpl, p1, n, None)
595
596 return tmpl('changeset',
597 diff=diff,
598 rev=ctx.rev(),
599 node=hex(n),
600 parent=self.siblings(parents),
601 child=self.siblings(ctx.children()),
602 changesettag=showtags,
603 author=ctx.user(),
604 desc=ctx.description(),
605 date=ctx.date(),
606 files=files,
607 archives=self.archivelist(hex(n)),
608 tags=self.nodetagsdict(n),
609 branch=self.nodebranchnodefault(ctx),
610 inbranch=self.nodeinbranch(ctx),
611 branches=self.nodebranchdict(ctx))
612
613 def filelog(self, tmpl, fctx):
614 f = fctx.path()
615 fl = fctx.filelog()
616 count = fl.count()
617 pagelen = self.maxshortchanges
618 pos = fctx.filerev()
619 start = max(0, pos - pagelen + 1)
620 end = min(count, start + pagelen)
621 pos = end - 1
622 parity = paritygen(self.stripecount, offset=start-end)
623
624 def entries(limit=0, **map):
625 l = []
626
627 for i in xrange(start, end):
628 ctx = fctx.filectx(i)
629 n = fl.node(i)
630
631 l.insert(0, {"parity": parity.next(),
632 "filerev": i,
633 "file": f,
634 "node": hex(ctx.node()),
635 "author": ctx.user(),
636 "date": ctx.date(),
637 "rename": self.renamelink(fl, n),
638 "parent": self.siblings(fctx.parents()),
639 "child": self.siblings(fctx.children()),
640 "desc": ctx.description()})
641
642 if limit > 0:
643 l = l[:limit]
644
645 for e in l:
646 yield e
647
648 nodefunc = lambda x: fctx.filectx(fileid=x)
649 nav = revnavgen(pos, pagelen, count, nodefunc)
650 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
651 entries=lambda **x: entries(limit=0, **x),
652 latestentry=lambda **x: entries(limit=1, **x))
653
654 def filerevision(self, tmpl, fctx):
655 f = fctx.path()
656 text = fctx.data()
657 fl = fctx.filelog()
658 n = fctx.filenode()
659 parity = paritygen(self.stripecount)
660
661 if util.binary(text):
662 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
663 text = '(binary:%s)' % mt
664
665 def lines():
666 for lineno, t in enumerate(text.splitlines(1)):
667 yield {"line": t,
668 "lineid": "l%d" % (lineno + 1),
669 "linenumber": "% 6d" % (lineno + 1),
670 "parity": parity.next()}
671
672 return tmpl("filerevision",
673 file=f,
674 path=_up(f),
675 text=lines(),
676 rev=fctx.rev(),
677 node=hex(fctx.node()),
678 author=fctx.user(),
679 date=fctx.date(),
680 desc=fctx.description(),
681 branch=self.nodebranchnodefault(fctx),
682 parent=self.siblings(fctx.parents()),
683 child=self.siblings(fctx.children()),
684 rename=self.renamelink(fl, n),
685 permissions=fctx.manifest().flags(f))
686
687 def fileannotate(self, tmpl, fctx):
688 f = fctx.path()
689 n = fctx.filenode()
690 fl = fctx.filelog()
691 parity = paritygen(self.stripecount)
692
693 def annotate(**map):
694 last = None
695 if util.binary(fctx.data()):
696 mt = (mimetypes.guess_type(fctx.path())[0]
697 or 'application/octet-stream')
698 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
699 '(binary:%s)' % mt)])
700 else:
701 lines = enumerate(fctx.annotate(follow=True, linenumber=True))
702 for lineno, ((f, targetline), l) in lines:
703 fnode = f.filenode()
704 name = self.repo.ui.shortuser(f.user())
705
706 if last != fnode:
707 last = fnode
708
709 yield {"parity": parity.next(),
710 "node": hex(f.node()),
711 "rev": f.rev(),
712 "author": name,
713 "file": f.path(),
714 "targetline": targetline,
715 "line": l,
716 "lineid": "l%d" % (lineno + 1),
717 "linenumber": "% 6d" % (lineno + 1)}
718
719 return tmpl("fileannotate",
720 file=f,
721 annotate=annotate,
722 path=_up(f),
723 rev=fctx.rev(),
724 node=hex(fctx.node()),
725 author=fctx.user(),
726 date=fctx.date(),
727 desc=fctx.description(),
728 rename=self.renamelink(fl, n),
729 branch=self.nodebranchnodefault(fctx),
730 parent=self.siblings(fctx.parents()),
731 child=self.siblings(fctx.children()),
732 permissions=fctx.manifest().flags(f))
733
734 def manifest(self, tmpl, ctx, path):
735 mf = ctx.manifest()
736 node = ctx.node()
737
738 files = {}
739 parity = paritygen(self.stripecount)
740
741 if path and path[-1] != "/":
742 path += "/"
743 l = len(path)
744 abspath = "/" + path
745
746 for f, n in mf.items():
747 if f[:l] != path:
748 continue
749 remain = f[l:]
750 if "/" in remain:
751 short = remain[:remain.index("/") + 1] # bleah
752 files[short] = (f, None)
753 else:
754 short = os.path.basename(remain)
755 files[short] = (f, n)
756
757 if not files:
758 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
759
760 def filelist(**map):
761 fl = files.keys()
762 fl.sort()
763 for f in fl:
764 full, fnode = files[f]
765 if not fnode:
766 continue
767
768 fctx = ctx.filectx(full)
769 yield {"file": full,
770 "parity": parity.next(),
771 "basename": f,
772 "date": fctx.changectx().date(),
773 "size": fctx.size(),
774 "permissions": mf.flags(full)}
775
776 def dirlist(**map):
777 fl = files.keys()
778 fl.sort()
779 for f in fl:
780 full, fnode = files[f]
781 if fnode:
782 continue
783
784 yield {"parity": parity.next(),
785 "path": "%s%s" % (abspath, f),
786 "basename": f[:-1]}
787
788 return tmpl("manifest",
789 rev=ctx.rev(),
790 node=hex(node),
791 path=abspath,
792 up=_up(abspath),
793 upparity=parity.next(),
794 fentries=filelist,
795 dentries=dirlist,
796 archives=self.archivelist(hex(node)),
797 tags=self.nodetagsdict(node),
798 inbranch=self.nodeinbranch(ctx),
799 branches=self.nodebranchdict(ctx))
800
801 def tags(self, tmpl):
802 i = self.repo.tagslist()
803 i.reverse()
804 parity = paritygen(self.stripecount)
805
806 def entries(notip=False,limit=0, **map):
807 count = 0
808 for k, n in i:
809 if notip and k == "tip":
810 continue
811 if limit > 0 and count >= limit:
812 continue
813 count = count + 1
814 yield {"parity": parity.next(),
815 "tag": k,
816 "date": self.repo.changectx(n).date(),
817 "node": hex(n)}
818
819 return tmpl("tags",
820 node=hex(self.repo.changelog.tip()),
821 entries=lambda **x: entries(False,0, **x),
822 entriesnotip=lambda **x: entries(True,0, **x),
823 latestentry=lambda **x: entries(True,1, **x))
824
825 def summary(self, tmpl):
826 i = self.repo.tagslist()
827 i.reverse()
828
829 def tagentries(**map):
830 parity = paritygen(self.stripecount)
831 count = 0
832 for k, n in i:
833 if k == "tip": # skip tip
834 continue;
835
836 count += 1
837 if count > 10: # limit to 10 tags
838 break;
839
840 yield tmpl("tagentry",
841 parity=parity.next(),
842 tag=k,
843 node=hex(n),
844 date=self.repo.changectx(n).date())
845
846
847 def branches(**map):
848 parity = paritygen(self.stripecount)
849
850 b = self.repo.branchtags()
851 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
852 l.sort()
853
854 for r,n,t in l:
855 ctx = self.repo.changectx(n)
856
857 yield {'parity': parity.next(),
858 'branch': t,
859 'node': hex(n),
860 'date': ctx.date()}
861
862 def changelist(**map):
863 parity = paritygen(self.stripecount, offset=start-end)
864 l = [] # build a list in forward order for efficiency
865 for i in xrange(start, end):
866 ctx = self.repo.changectx(i)
867 n = ctx.node()
868 hn = hex(n)
869
870 l.insert(0, tmpl(
871 'shortlogentry',
872 parity=parity.next(),
873 author=ctx.user(),
874 desc=ctx.description(),
875 date=ctx.date(),
876 rev=i,
877 node=hn,
878 tags=self.nodetagsdict(n),
879 inbranch=self.nodeinbranch(ctx),
880 branches=self.nodebranchdict(ctx)))
881
882 yield l
883
884 cl = self.repo.changelog
885 count = cl.count()
886 start = max(0, count - self.maxchanges)
887 end = min(count, start + self.maxchanges)
888
889 return tmpl("summary",
890 desc=self.config("web", "description", "unknown"),
891 owner=get_contact(self.config) or "unknown",
892 lastchange=cl.read(cl.tip())[2],
893 tags=tagentries,
894 branches=branches,
895 shortlog=changelist,
896 node=hex(cl.tip()),
897 archives=self.archivelist("tip"))
898
899 def filediff(self, tmpl, fctx):
900 n = fctx.node()
901 path = fctx.path()
902 parents = fctx.parents()
903 p1 = parents and parents[0].node() or nullid
904
905 def diff(**map):
906 yield self.diff(tmpl, p1, n, [path])
907
908 return tmpl("filediff",
909 file=path,
910 node=hex(n),
911 rev=fctx.rev(),
912 branch=self.nodebranchnodefault(fctx),
913 parent=self.siblings(parents),
914 child=self.siblings(fctx.children()),
915 diff=diff)
916
917 338 archive_specs = {
918 339 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
919 340 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
920 341 'zip': ('application/zip', 'zip', '.zip', None),
921 342 }
922 343
923 def archive(self, tmpl, req, key, type_):
924 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
925 cnode = self.repo.lookup(key)
926 arch_version = key
927 if cnode == key or key == 'tip':
928 arch_version = short(cnode)
929 name = "%s-%s" % (reponame, arch_version)
930 mimetype, artype, extension, encoding = self.archive_specs[type_]
931 headers = [
932 ('Content-Type', mimetype),
933 ('Content-Disposition', 'attachment; filename=%s%s' %
934 (name, extension))
935 ]
936 if encoding:
937 headers.append(('Content-Encoding', encoding))
938 req.header(headers)
939 req.respond(HTTP_OK)
940 archival.archive(self.repo, req, cnode, artype, prefix=name)
344 def check_perm(self, req, op):
345 '''Check permission for operation based on request data (including
346 authentication info. Return true if op allowed, else false.'''
941 347
942 # add tags to things
943 # tags -> list of changesets corresponding to tags
944 # find tag, changeset, file
348 def error(status, message):
349 req.respond(status, protocol.HGTYPE)
350 req.write('0\n%s\n' % message)
945 351
946 def cleanpath(self, path):
947 path = path.lstrip('/')
948 return util.canonpath(self.repo.root, '', path)
352 if op == 'pull':
353 return self.allowpull
949 354
950 def changectx(self, req):
951 if 'node' in req.form:
952 changeid = req.form['node'][0]
953 elif 'manifest' in req.form:
954 changeid = req.form['manifest'][0]
955 else:
956 changeid = self.repo.changelog.count() - 1
957
958 try:
959 ctx = self.repo.changectx(changeid)
960 except RepoError:
961 man = self.repo.manifest
962 mn = man.lookup(changeid)
963 ctx = self.repo.changectx(man.linkrev(mn))
964
965 return ctx
355 # enforce that you can only push using POST requests
356 if req.env['REQUEST_METHOD'] != 'POST':
357 error('405 Method Not Allowed', 'push requires POST request')
358 return False
966 359
967 def filectx(self, req):
968 path = self.cleanpath(req.form['file'][0])
969 if 'node' in req.form:
970 changeid = req.form['node'][0]
971 else:
972 changeid = req.form['filenode'][0]
973 try:
974 ctx = self.repo.changectx(changeid)
975 fctx = ctx.filectx(path)
976 except RepoError:
977 fctx = self.repo.filectx(path, fileid=changeid)
978
979 return fctx
980
981 def check_perm(self, req, op, default):
982 '''check permission for operation based on user auth.
983 return true if op allowed, else false.
984 default is policy to use if no config given.'''
360 # require ssl by default for pushing, auth info cannot be sniffed
361 # and replayed
362 scheme = req.env.get('wsgi.url_scheme')
363 if self.configbool('web', 'push_ssl', True) and scheme != 'https':
364 error(HTTP_OK, 'ssl required')
365 return False
985 366
986 367 user = req.env.get('REMOTE_USER')
987 368
988 deny = self.configlist('web', 'deny_' + op)
369 deny = self.configlist('web', 'deny_push')
989 370 if deny and (not user or deny == ['*'] or user in deny):
371 error('401 Unauthorized', 'push not authorized')
990 372 return False
991 373
992 allow = self.configlist('web', 'allow_' + op)
993 return (allow and (allow == ['*'] or user in allow)) or default
374 allow = self.configlist('web', 'allow_push')
375 result = allow and (allow == ['*'] or user in allow)
376 if not result:
377 error('401 Unauthorized', 'push not authorized')
378
379 return result
@@ -6,7 +6,7 b''
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 import os, mimetools, cStringIO
9 import os
10 10 from mercurial.i18n import gettext as _
11 11 from mercurial.repo import RepoError
12 12 from mercurial import ui, hg, util, templater, templatefilters
@@ -19,8 +19,8 b' from request import wsgirequest'
19 19 class hgwebdir(object):
20 20 def __init__(self, config, parentui=None):
21 21 def cleannames(items):
22 return [(util.pconvert(name).strip('/'), path)
23 for name, path in items]
22 return util.sort([(util.pconvert(name).strip('/'), path)
23 for name, path in items])
24 24
25 25 self.parentui = parentui or ui.ui(report_untrusted=False,
26 26 interactive = False)
@@ -34,7 +34,6 b' class hgwebdir(object):'
34 34 self.repos_sorted = ('', False)
35 35 elif isinstance(config, dict):
36 36 self.repos = cleannames(config.items())
37 self.repos.sort()
38 37 else:
39 38 if isinstance(config, util.configparser):
40 39 cp = config
@@ -71,8 +70,7 b' class hgwebdir(object):'
71 70
72 71 def __call__(self, env, respond):
73 72 req = wsgirequest(env, respond)
74 self.run_wsgi(req)
75 return req
73 return self.run_wsgi(req)
76 74
77 75 def run_wsgi(self, req):
78 76
@@ -81,17 +79,8 b' class hgwebdir(object):'
81 79
82 80 virtual = req.env.get("PATH_INFO", "").strip('/')
83 81 tmpl = self.templater(req)
84 try:
85 ctype = tmpl('mimetype', encoding=util._encoding)
86 ctype = templater.stringify(ctype)
87 except KeyError:
88 # old templates with inline HTTP headers?
89 if 'mimetype' in tmpl:
90 raise
91 header = tmpl('header', encoding=util._encoding)
92 header_file = cStringIO.StringIO(templater.stringify(header))
93 msg = mimetools.Message(header_file, 0)
94 ctype = msg['content-type']
82 ctype = tmpl('mimetype', encoding=util._encoding)
83 ctype = templater.stringify(ctype)
95 84
96 85 # a static file
97 86 if virtual.startswith('static/') or 'static' in req.form:
@@ -101,13 +90,13 b' class hgwebdir(object):'
101 90 else:
102 91 fname = req.form['static'][0]
103 92 req.write(staticfile(static, fname, req))
104 return
93 return []
105 94
106 95 # top-level index
107 96 elif not virtual:
108 97 req.respond(HTTP_OK, ctype)
109 98 req.write(self.makeindex(req, tmpl))
110 return
99 return []
111 100
112 101 # nested indexes and hgwebs
113 102
@@ -118,8 +107,7 b' class hgwebdir(object):'
118 107 req.env['REPO_NAME'] = virtual
119 108 try:
120 109 repo = hg.repository(self.parentui, real)
121 hgweb(repo).run_wsgi(req)
122 return
110 return hgweb(repo).run_wsgi(req)
123 111 except IOError, inst:
124 112 msg = inst.strerror
125 113 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
@@ -131,7 +119,7 b' class hgwebdir(object):'
131 119 if [r for r in repos if r.startswith(subdir)]:
132 120 req.respond(HTTP_OK, ctype)
133 121 req.write(self.makeindex(req, tmpl, subdir))
134 return
122 return []
135 123
136 124 up = virtual.rfind('/')
137 125 if up < 0:
@@ -141,10 +129,12 b' class hgwebdir(object):'
141 129 # prefixes not found
142 130 req.respond(HTTP_NOT_FOUND, ctype)
143 131 req.write(tmpl("notfound", repo=virtual))
132 return []
144 133
145 134 except ErrorResponse, err:
146 135 req.respond(err.code, ctype)
147 136 req.write(tmpl('error', error=err.message or ''))
137 return []
148 138 finally:
149 139 tmpl = None
150 140
@@ -257,13 +247,7 b' class hgwebdir(object):'
257 247 def templater(self, req):
258 248
259 249 def header(**map):
260 header = tmpl('header', encoding=util._encoding, **map)
261 if 'mimetype' not in tmpl:
262 # old template with inline HTTP headers
263 header_file = cStringIO.StringIO(templater.stringify(header))
264 msg = mimetools.Message(header_file, 0)
265 header = header_file.read()
266 yield header
250 yield tmpl('header', encoding=util._encoding, **map)
267 251
268 252 def footer(**map):
269 253 yield tmpl("footer", **map)
@@ -21,69 +21,65 b' from common import HTTP_OK, HTTP_NOT_FOU'
21 21
22 22 HGTYPE = 'application/mercurial-0.1'
23 23
24 def lookup(web, req):
24 def lookup(repo, req):
25 25 try:
26 r = hex(web.repo.lookup(req.form['key'][0]))
26 r = hex(repo.lookup(req.form['key'][0]))
27 27 success = 1
28 28 except Exception,inst:
29 29 r = str(inst)
30 30 success = 0
31 31 resp = "%s %s\n" % (success, r)
32 32 req.respond(HTTP_OK, HGTYPE, length=len(resp))
33 req.write(resp)
33 yield resp
34 34
35 def heads(web, req):
36 resp = " ".join(map(hex, web.repo.heads())) + "\n"
35 def heads(repo, req):
36 resp = " ".join(map(hex, repo.heads())) + "\n"
37 37 req.respond(HTTP_OK, HGTYPE, length=len(resp))
38 req.write(resp)
38 yield resp
39 39
40 def branches(web, req):
40 def branches(repo, req):
41 41 nodes = []
42 42 if 'nodes' in req.form:
43 43 nodes = map(bin, req.form['nodes'][0].split(" "))
44 44 resp = cStringIO.StringIO()
45 for b in web.repo.branches(nodes):
45 for b in repo.branches(nodes):
46 46 resp.write(" ".join(map(hex, b)) + "\n")
47 47 resp = resp.getvalue()
48 48 req.respond(HTTP_OK, HGTYPE, length=len(resp))
49 req.write(resp)
49 yield resp
50 50
51 def between(web, req):
51 def between(repo, req):
52 52 if 'pairs' in req.form:
53 53 pairs = [map(bin, p.split("-"))
54 54 for p in req.form['pairs'][0].split(" ")]
55 55 resp = cStringIO.StringIO()
56 for b in web.repo.between(pairs):
56 for b in repo.between(pairs):
57 57 resp.write(" ".join(map(hex, b)) + "\n")
58 58 resp = resp.getvalue()
59 59 req.respond(HTTP_OK, HGTYPE, length=len(resp))
60 req.write(resp)
60 yield resp
61 61
62 def changegroup(web, req):
62 def changegroup(repo, req):
63 63 req.respond(HTTP_OK, HGTYPE)
64 64 nodes = []
65 if not web.allowpull:
66 return
67 65
68 66 if 'roots' in req.form:
69 67 nodes = map(bin, req.form['roots'][0].split(" "))
70 68
71 69 z = zlib.compressobj()
72 f = web.repo.changegroup(nodes, 'serve')
70 f = repo.changegroup(nodes, 'serve')
73 71 while 1:
74 72 chunk = f.read(4096)
75 73 if not chunk:
76 74 break
77 req.write(z.compress(chunk))
75 yield z.compress(chunk)
78 76
79 req.write(z.flush())
77 yield z.flush()
80 78
81 def changegroupsubset(web, req):
79 def changegroupsubset(repo, req):
82 80 req.respond(HTTP_OK, HGTYPE)
83 81 bases = []
84 82 heads = []
85 if not web.allowpull:
86 return
87 83
88 84 if 'bases' in req.form:
89 85 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
@@ -91,67 +87,44 b' def changegroupsubset(web, req):'
91 87 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
92 88
93 89 z = zlib.compressobj()
94 f = web.repo.changegroupsubset(bases, heads, 'serve')
90 f = repo.changegroupsubset(bases, heads, 'serve')
95 91 while 1:
96 92 chunk = f.read(4096)
97 93 if not chunk:
98 94 break
99 req.write(z.compress(chunk))
95 yield z.compress(chunk)
100 96
101 req.write(z.flush())
97 yield z.flush()
102 98
103 def capabilities(web, req):
104 resp = ' '.join(web.capabilities())
105 req.respond(HTTP_OK, HGTYPE, length=len(resp))
106 req.write(resp)
99 def capabilities(repo, req):
100 caps = ['lookup', 'changegroupsubset']
101 if repo.ui.configbool('server', 'uncompressed', untrusted=True):
102 caps.append('stream=%d' % repo.changelog.version)
103 if changegroupmod.bundlepriority:
104 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
105 rsp = ' '.join(caps)
106 req.respond(HTTP_OK, HGTYPE, length=len(rsp))
107 yield rsp
107 108
108 def unbundle(web, req):
109 def unbundle(repo, req):
110
111 errorfmt = '0\n%s\n'
112 proto = req.env.get('wsgi.url_scheme') or 'http'
113 their_heads = req.form['heads'][0].split(' ')
109 114
110 def bail(response, headers={}):
115 def check_heads():
116 heads = map(hex, repo.heads())
117 return their_heads == [hex('force')] or their_heads == heads
118
119 # fail early if possible
120 if not check_heads():
111 121 length = int(req.env.get('CONTENT_LENGTH', 0))
112 122 for s in util.filechunkiter(req, limit=length):
113 123 # drain incoming bundle, else client will not see
114 124 # response when run outside cgi script
115 125 pass
116
117 status = headers.pop('status', HTTP_OK)
118 req.header(headers.items())
119 req.respond(status, HGTYPE)
120 req.write('0\n')
121 req.write(response)
122
123 # enforce that you can only unbundle with POST requests
124 if req.env['REQUEST_METHOD'] != 'POST':
125 headers = {'status': '405 Method Not Allowed'}
126 bail('unbundle requires POST request\n', headers)
127 return
128
129 # require ssl by default, auth info cannot be sniffed and
130 # replayed
131 ssl_req = web.configbool('web', 'push_ssl', True)
132 if ssl_req:
133 if req.env.get('wsgi.url_scheme') != 'https':
134 bail('ssl required\n')
135 return
136 proto = 'https'
137 else:
138 proto = 'http'
139
140 # do not allow push unless explicitly allowed
141 if not web.check_perm(req, 'push', False):
142 bail('push not authorized\n', headers={'status': '401 Unauthorized'})
143 return
144
145 their_heads = req.form['heads'][0].split(' ')
146
147 def check_heads():
148 heads = map(hex, web.repo.heads())
149 return their_heads == [hex('force')] or their_heads == heads
150
151 # fail early if possible
152 if not check_heads():
153 bail('unsynced changes\n')
154 return
126 req.respond(HTTP_OK, HGTYPE)
127 return errorfmt % 'unsynced changes',
155 128
156 129 req.respond(HTTP_OK, HGTYPE)
157 130
@@ -166,12 +139,10 b' def unbundle(web, req):'
166 139 fp.write(s)
167 140
168 141 try:
169 lock = web.repo.lock()
142 lock = repo.lock()
170 143 try:
171 144 if not check_heads():
172 req.write('0\n')
173 req.write('unsynced changes\n')
174 return
145 return errorfmt % 'unsynced changes',
175 146
176 147 fp.seek(0)
177 148 header = fp.read(6)
@@ -190,26 +161,23 b' def unbundle(web, req):'
190 161 url = 'remote:%s:%s' % (proto,
191 162 req.env.get('REMOTE_HOST', ''))
192 163 try:
193 ret = web.repo.addchangegroup(gen, 'serve', url)
164 ret = repo.addchangegroup(gen, 'serve', url)
194 165 except util.Abort, inst:
195 166 sys.stdout.write("abort: %s\n" % inst)
196 167 ret = 0
197 168 finally:
198 169 val = sys.stdout.getvalue()
199 170 sys.stdout, sys.stderr = oldio
200 req.write('%d\n' % ret)
201 req.write(val)
171 return '%d\n%s' % (ret, val),
202 172 finally:
203 173 del lock
204 174 except ValueError, inst:
205 req.write('0\n')
206 req.write(str(inst) + '\n')
175 return errorfmt % inst,
207 176 except (OSError, IOError), inst:
208 req.write('0\n')
209 177 filename = getattr(inst, 'filename', '')
210 178 # Don't send our filesystem layout to the client
211 if filename.startswith(web.repo.root):
212 filename = filename[len(web.repo.root)+1:]
179 if filename.startswith(repo.root):
180 filename = filename[len(repo.root)+1:]
213 181 else:
214 182 filename = ''
215 183 error = getattr(inst, 'strerror', 'Unknown error')
@@ -218,13 +186,12 b' def unbundle(web, req):'
218 186 else:
219 187 code = HTTP_SERVER_ERROR
220 188 req.respond(code)
221 req.write('%s: %s\n' % (error, filename))
189 return '0\n%s: %s\n' % (error, filename),
222 190 finally:
223 191 fp.close()
224 192 os.unlink(tempname)
225 193
226 def stream_out(web, req):
227 if not web.allowpull:
228 return
194 def stream_out(repo, req):
229 195 req.respond(HTTP_OK, HGTYPE)
230 streamclone.stream_out(web.repo, req, untrusted=True)
196 streamclone.stream_out(repo, req, untrusted=True)
197 return []
@@ -9,6 +9,31 b''
9 9 import socket, cgi, errno
10 10 from common import ErrorResponse, statusmessage
11 11
12 shortcuts = {
13 'cl': [('cmd', ['changelog']), ('rev', None)],
14 'sl': [('cmd', ['shortlog']), ('rev', None)],
15 'cs': [('cmd', ['changeset']), ('node', None)],
16 'f': [('cmd', ['file']), ('filenode', None)],
17 'fl': [('cmd', ['filelog']), ('filenode', None)],
18 'fd': [('cmd', ['filediff']), ('node', None)],
19 'fa': [('cmd', ['annotate']), ('filenode', None)],
20 'mf': [('cmd', ['manifest']), ('manifest', None)],
21 'ca': [('cmd', ['archive']), ('node', None)],
22 'tags': [('cmd', ['tags'])],
23 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
24 'static': [('cmd', ['static']), ('file', None)]
25 }
26
27 def expand(form):
28 for k in shortcuts.iterkeys():
29 if k in form:
30 for name, value in shortcuts[k]:
31 if value is None:
32 value = form[k]
33 form[name] = value
34 del form[k]
35 return form
36
12 37 class wsgirequest(object):
13 38 def __init__(self, wsgienv, start_response):
14 39 version = wsgienv['wsgi.version']
@@ -21,7 +46,7 b' class wsgirequest(object):'
21 46 self.multiprocess = wsgienv['wsgi.multiprocess']
22 47 self.run_once = wsgienv['wsgi.run_once']
23 48 self.env = wsgienv
24 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
49 self.form = expand(cgi.parse(self.inp, self.env, keep_blank_values=1))
25 50 self._start_response = start_response
26 51 self.server_write = None
27 52 self.headers = []
@@ -122,7 +122,8 b' class _hgwebhandler(object, BaseHTTPServ'
122 122 self.saved_headers = []
123 123 self.sent_headers = False
124 124 self.length = None
125 self.server.application(env, self._start_response)
125 for chunk in self.server.application(env, self._start_response):
126 self._write(chunk)
126 127
127 128 def send_headers(self):
128 129 if not self.saved_status:
@@ -268,12 +269,7 b' def create_server(ui, repo):'
268 269
269 270 self.addr, self.port = self.socket.getsockname()[0:2]
270 271 self.prefix = prefix
271
272 272 self.fqaddr = socket.getfqdn(address)
273 try:
274 socket.getaddrbyhost(self.fqaddr)
275 except:
276 fqaddr = address
277 273
278 274 class IPv6HTTPServer(MercurialHTTPServer):
279 275 address_family = getattr(socket, 'AF_INET6', None)
This diff has been collapsed as it changes many lines, (539 lines changed) Show them Hide them
@@ -5,10 +5,15 b''
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 import os, mimetypes
9 from mercurial import revlog, util
8 import os, mimetypes, re, cgi
9 import webutil
10 from mercurial import revlog, archival, templatefilters
11 from mercurial.node import short, hex, nullid
12 from mercurial.util import binary, datestr
10 13 from mercurial.repo import RepoError
11 from common import staticfile, ErrorResponse, HTTP_OK, HTTP_NOT_FOUND
14 from common import paritygen, staticfile, get_contact, ErrorResponse
15 from common import HTTP_OK, HTTP_NOT_FOUND
16 from mercurial import graphmod, util
12 17
13 18 # __all__ is populated with the allowed commands. Be sure to add to it if
14 19 # you're adding a new command, or the new command won't work.
@@ -16,7 +21,7 b' from common import staticfile, ErrorResp'
16 21 __all__ = [
17 22 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
18 23 'manifest', 'tags', 'summary', 'filediff', 'diff', 'annotate', 'filelog',
19 'archive', 'static',
24 'archive', 'static', 'graph',
20 25 ]
21 26
22 27 def log(web, req, tmpl):
@@ -26,17 +31,17 b' def log(web, req, tmpl):'
26 31 return changelog(web, req, tmpl)
27 32
28 33 def rawfile(web, req, tmpl):
29 path = web.cleanpath(req.form.get('file', [''])[0])
34 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
30 35 if not path:
31 content = web.manifest(tmpl, web.changectx(req), path)
36 content = manifest(web, req, tmpl)
32 37 req.respond(HTTP_OK, web.ctype)
33 38 return content
34 39
35 40 try:
36 fctx = web.filectx(req)
41 fctx = webutil.filectx(web.repo, req)
37 42 except revlog.LookupError, inst:
38 43 try:
39 content = web.manifest(tmpl, web.changectx(req), path)
44 content = manifest(web, req, tmpl)
40 45 req.respond(HTTP_OK, web.ctype)
41 46 return content
42 47 except ErrorResponse:
@@ -45,76 +50,514 b' def rawfile(web, req, tmpl):'
45 50 path = fctx.path()
46 51 text = fctx.data()
47 52 mt = mimetypes.guess_type(path)[0]
48 if mt is None or util.binary(text):
53 if mt is None or binary(text):
49 54 mt = mt or 'application/octet-stream'
50 55
51 56 req.respond(HTTP_OK, mt, path, len(text))
52 57 return [text]
53 58
59 def _filerevision(web, tmpl, fctx):
60 f = fctx.path()
61 text = fctx.data()
62 fl = fctx.filelog()
63 n = fctx.filenode()
64 parity = paritygen(web.stripecount)
65
66 if binary(text):
67 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
68 text = '(binary:%s)' % mt
69
70 def lines():
71 for lineno, t in enumerate(text.splitlines(1)):
72 yield {"line": t,
73 "lineid": "l%d" % (lineno + 1),
74 "linenumber": "% 6d" % (lineno + 1),
75 "parity": parity.next()}
76
77 return tmpl("filerevision",
78 file=f,
79 path=webutil.up(f),
80 text=lines(),
81 rev=fctx.rev(),
82 node=hex(fctx.node()),
83 author=fctx.user(),
84 date=fctx.date(),
85 desc=fctx.description(),
86 branch=webutil.nodebranchnodefault(fctx),
87 parent=webutil.siblings(fctx.parents()),
88 child=webutil.siblings(fctx.children()),
89 rename=webutil.renamelink(fctx),
90 permissions=fctx.manifest().flags(f))
91
54 92 def file(web, req, tmpl):
55 path = web.cleanpath(req.form.get('file', [''])[0])
93 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
56 94 if not path:
57 return web.manifest(tmpl, web.changectx(req), path)
95 return manifest(web, req, tmpl)
58 96 try:
59 return web.filerevision(tmpl, web.filectx(req))
97 return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
60 98 except revlog.LookupError, inst:
61 99 try:
62 return web.manifest(tmpl, web.changectx(req), path)
100 return manifest(web, req, tmpl)
63 101 except ErrorResponse:
64 102 raise inst
65 103
104 def _search(web, tmpl, query):
105
106 def changelist(**map):
107 cl = web.repo.changelog
108 count = 0
109 qw = query.lower().split()
110
111 def revgen():
112 for i in xrange(len(cl) - 1, 0, -100):
113 l = []
114 for j in xrange(max(0, i - 100), i + 1):
115 ctx = web.repo[j]
116 l.append(ctx)
117 l.reverse()
118 for e in l:
119 yield e
120
121 for ctx in revgen():
122 miss = 0
123 for q in qw:
124 if not (q in ctx.user().lower() or
125 q in ctx.description().lower() or
126 q in " ".join(ctx.files()).lower()):
127 miss = 1
128 break
129 if miss:
130 continue
131
132 count += 1
133 n = ctx.node()
134 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
135
136 yield tmpl('searchentry',
137 parity=parity.next(),
138 author=ctx.user(),
139 parent=webutil.siblings(ctx.parents()),
140 child=webutil.siblings(ctx.children()),
141 changelogtag=showtags,
142 desc=ctx.description(),
143 date=ctx.date(),
144 files=web.listfilediffs(tmpl, ctx.files(), n),
145 rev=ctx.rev(),
146 node=hex(n),
147 tags=webutil.nodetagsdict(web.repo, n),
148 inbranch=webutil.nodeinbranch(web.repo, ctx),
149 branches=webutil.nodebranchdict(web.repo, ctx))
150
151 if count >= web.maxchanges:
152 break
153
154 cl = web.repo.changelog
155 parity = paritygen(web.stripecount)
156
157 return tmpl('search',
158 query=query,
159 node=hex(cl.tip()),
160 entries=changelist,
161 archives=web.archivelist("tip"))
162
66 163 def changelog(web, req, tmpl, shortlog = False):
67 164 if 'node' in req.form:
68 ctx = web.changectx(req)
165 ctx = webutil.changectx(web.repo, req)
69 166 else:
70 167 if 'rev' in req.form:
71 168 hi = req.form['rev'][0]
72 169 else:
73 hi = web.repo.changelog.count() - 1
170 hi = len(web.repo) - 1
74 171 try:
75 ctx = web.repo.changectx(hi)
172 ctx = web.repo[hi]
76 173 except RepoError:
77 return web.search(tmpl, hi) # XXX redirect to 404 page?
174 return _search(web, tmpl, hi) # XXX redirect to 404 page?
175
176 def changelist(limit=0, **map):
177 cl = web.repo.changelog
178 l = [] # build a list in forward order for efficiency
179 for i in xrange(start, end):
180 ctx = web.repo[i]
181 n = ctx.node()
182 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
183
184 l.insert(0, {"parity": parity.next(),
185 "author": ctx.user(),
186 "parent": webutil.siblings(ctx.parents(), i - 1),
187 "child": webutil.siblings(ctx.children(), i + 1),
188 "changelogtag": showtags,
189 "desc": ctx.description(),
190 "date": ctx.date(),
191 "files": web.listfilediffs(tmpl, ctx.files(), n),
192 "rev": i,
193 "node": hex(n),
194 "tags": webutil.nodetagsdict(web.repo, n),
195 "inbranch": webutil.nodeinbranch(web.repo, ctx),
196 "branches": webutil.nodebranchdict(web.repo, ctx)
197 })
78 198
79 return web.changelog(tmpl, ctx, shortlog = shortlog)
199 if limit > 0:
200 l = l[:limit]
201
202 for e in l:
203 yield e
204
205 maxchanges = shortlog and web.maxshortchanges or web.maxchanges
206 cl = web.repo.changelog
207 count = len(cl)
208 pos = ctx.rev()
209 start = max(0, pos - maxchanges + 1)
210 end = min(count, start + maxchanges)
211 pos = end - 1
212 parity = paritygen(web.stripecount, offset=start-end)
213
214 changenav = webutil.revnavgen(pos, maxchanges, count, web.repo.changectx)
215
216 return tmpl(shortlog and 'shortlog' or 'changelog',
217 changenav=changenav,
218 node=hex(ctx.node()),
219 rev=pos, changesets=count,
220 entries=lambda **x: changelist(limit=0,**x),
221 latestentry=lambda **x: changelist(limit=1,**x),
222 archives=web.archivelist("tip"))
80 223
81 224 def shortlog(web, req, tmpl):
82 225 return changelog(web, req, tmpl, shortlog = True)
83 226
84 227 def changeset(web, req, tmpl):
85 return web.changeset(tmpl, web.changectx(req))
228 ctx = webutil.changectx(web.repo, req)
229 n = ctx.node()
230 showtags = webutil.showtag(web.repo, tmpl, 'changesettag', n)
231 parents = ctx.parents()
232 p1 = parents[0].node()
233
234 files = []
235 parity = paritygen(web.stripecount)
236 for f in ctx.files():
237 files.append(tmpl("filenodelink",
238 node=hex(n), file=f,
239 parity=parity.next()))
240
241 diffs = web.diff(tmpl, p1, n, None)
242 return tmpl('changeset',
243 diff=diffs,
244 rev=ctx.rev(),
245 node=hex(n),
246 parent=webutil.siblings(parents),
247 child=webutil.siblings(ctx.children()),
248 changesettag=showtags,
249 author=ctx.user(),
250 desc=ctx.description(),
251 date=ctx.date(),
252 files=files,
253 archives=web.archivelist(hex(n)),
254 tags=webutil.nodetagsdict(web.repo, n),
255 branch=webutil.nodebranchnodefault(ctx),
256 inbranch=webutil.nodeinbranch(web.repo, ctx),
257 branches=webutil.nodebranchdict(web.repo, ctx))
86 258
87 259 rev = changeset
88 260
89 261 def manifest(web, req, tmpl):
90 return web.manifest(tmpl, web.changectx(req),
91 web.cleanpath(req.form['path'][0]))
262 ctx = webutil.changectx(web.repo, req)
263 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
264 mf = ctx.manifest()
265 node = ctx.node()
266
267 files = {}
268 parity = paritygen(web.stripecount)
269
270 if path and path[-1] != "/":
271 path += "/"
272 l = len(path)
273 abspath = "/" + path
274
275 for f, n in mf.items():
276 if f[:l] != path:
277 continue
278 remain = f[l:]
279 if "/" in remain:
280 short = remain[:remain.index("/") + 1] # bleah
281 files[short] = (f, None)
282 else:
283 short = os.path.basename(remain)
284 files[short] = (f, n)
285
286 if not files:
287 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
288
289 def filelist(**map):
290 for f in util.sort(files):
291 full, fnode = files[f]
292 if not fnode:
293 continue
294
295 fctx = ctx.filectx(full)
296 yield {"file": full,
297 "parity": parity.next(),
298 "basename": f,
299 "date": fctx.date(),
300 "size": fctx.size(),
301 "permissions": mf.flags(full)}
302
303 def dirlist(**map):
304 for f in util.sort(files):
305 full, fnode = files[f]
306 if fnode:
307 continue
308
309 yield {"parity": parity.next(),
310 "path": "%s%s" % (abspath, f),
311 "basename": f[:-1]}
312
313 return tmpl("manifest",
314 rev=ctx.rev(),
315 node=hex(node),
316 path=abspath,
317 up=webutil.up(abspath),
318 upparity=parity.next(),
319 fentries=filelist,
320 dentries=dirlist,
321 archives=web.archivelist(hex(node)),
322 tags=webutil.nodetagsdict(web.repo, node),
323 inbranch=webutil.nodeinbranch(web.repo, ctx),
324 branches=webutil.nodebranchdict(web.repo, ctx))
92 325
93 326 def tags(web, req, tmpl):
94 return web.tags(tmpl)
327 i = web.repo.tagslist()
328 i.reverse()
329 parity = paritygen(web.stripecount)
330
331 def entries(notip=False,limit=0, **map):
332 count = 0
333 for k, n in i:
334 if notip and k == "tip":
335 continue
336 if limit > 0 and count >= limit:
337 continue
338 count = count + 1
339 yield {"parity": parity.next(),
340 "tag": k,
341 "date": web.repo[n].date(),
342 "node": hex(n)}
343
344 return tmpl("tags",
345 node=hex(web.repo.changelog.tip()),
346 entries=lambda **x: entries(False,0, **x),
347 entriesnotip=lambda **x: entries(True,0, **x),
348 latestentry=lambda **x: entries(True,1, **x))
95 349
96 350 def summary(web, req, tmpl):
97 return web.summary(tmpl)
351 i = web.repo.tagslist()
352 i.reverse()
353
354 def tagentries(**map):
355 parity = paritygen(web.stripecount)
356 count = 0
357 for k, n in i:
358 if k == "tip": # skip tip
359 continue
360
361 count += 1
362 if count > 10: # limit to 10 tags
363 break
364
365 yield tmpl("tagentry",
366 parity=parity.next(),
367 tag=k,
368 node=hex(n),
369 date=web.repo[n].date())
370
371 def branches(**map):
372 parity = paritygen(web.stripecount)
373
374 b = web.repo.branchtags()
375 l = [(-web.repo.changelog.rev(n), n, t) for t, n in b.items()]
376 for r,n,t in util.sort(l):
377 yield {'parity': parity.next(),
378 'branch': t,
379 'node': hex(n),
380 'date': web.repo[n].date()}
381
382 def changelist(**map):
383 parity = paritygen(web.stripecount, offset=start-end)
384 l = [] # build a list in forward order for efficiency
385 for i in xrange(start, end):
386 ctx = web.repo[i]
387 n = ctx.node()
388 hn = hex(n)
389
390 l.insert(0, tmpl(
391 'shortlogentry',
392 parity=parity.next(),
393 author=ctx.user(),
394 desc=ctx.description(),
395 date=ctx.date(),
396 rev=i,
397 node=hn,
398 tags=webutil.nodetagsdict(web.repo, n),
399 inbranch=webutil.nodeinbranch(web.repo, ctx),
400 branches=webutil.nodebranchdict(web.repo, ctx)))
401
402 yield l
403
404 cl = web.repo.changelog
405 count = len(cl)
406 start = max(0, count - web.maxchanges)
407 end = min(count, start + web.maxchanges)
408
409 return tmpl("summary",
410 desc=web.config("web", "description", "unknown"),
411 owner=get_contact(web.config) or "unknown",
412 lastchange=cl.read(cl.tip())[2],
413 tags=tagentries,
414 branches=branches,
415 shortlog=changelist,
416 node=hex(cl.tip()),
417 archives=web.archivelist("tip"))
98 418
99 419 def filediff(web, req, tmpl):
100 return web.filediff(tmpl, web.filectx(req))
420 fctx = webutil.filectx(web.repo, req)
421 n = fctx.node()
422 path = fctx.path()
423 parents = fctx.parents()
424 p1 = parents and parents[0].node() or nullid
425
426 diffs = web.diff(tmpl, p1, n, [path])
427 return tmpl("filediff",
428 file=path,
429 node=hex(n),
430 rev=fctx.rev(),
431 date=fctx.date(),
432 desc=fctx.description(),
433 author=fctx.user(),
434 rename=webutil.renamelink(fctx),
435 branch=webutil.nodebranchnodefault(fctx),
436 parent=webutil.siblings(parents),
437 child=webutil.siblings(fctx.children()),
438 diff=diffs)
101 439
102 440 diff = filediff
103 441
104 442 def annotate(web, req, tmpl):
105 return web.fileannotate(tmpl, web.filectx(req))
443 fctx = webutil.filectx(web.repo, req)
444 f = fctx.path()
445 n = fctx.filenode()
446 fl = fctx.filelog()
447 parity = paritygen(web.stripecount)
448
449 def annotate(**map):
450 last = None
451 if binary(fctx.data()):
452 mt = (mimetypes.guess_type(fctx.path())[0]
453 or 'application/octet-stream')
454 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
455 '(binary:%s)' % mt)])
456 else:
457 lines = enumerate(fctx.annotate(follow=True, linenumber=True))
458 for lineno, ((f, targetline), l) in lines:
459 fnode = f.filenode()
460
461 if last != fnode:
462 last = fnode
463
464 yield {"parity": parity.next(),
465 "node": hex(f.node()),
466 "rev": f.rev(),
467 "author": f.user(),
468 "desc": f.description(),
469 "file": f.path(),
470 "targetline": targetline,
471 "line": l,
472 "lineid": "l%d" % (lineno + 1),
473 "linenumber": "% 6d" % (lineno + 1)}
474
475 return tmpl("fileannotate",
476 file=f,
477 annotate=annotate,
478 path=webutil.up(f),
479 rev=fctx.rev(),
480 node=hex(fctx.node()),
481 author=fctx.user(),
482 date=fctx.date(),
483 desc=fctx.description(),
484 rename=webutil.renamelink(fctx),
485 branch=webutil.nodebranchnodefault(fctx),
486 parent=webutil.siblings(fctx.parents()),
487 child=webutil.siblings(fctx.children()),
488 permissions=fctx.manifest().flags(f))
106 489
107 490 def filelog(web, req, tmpl):
108 return web.filelog(tmpl, web.filectx(req))
491 fctx = webutil.filectx(web.repo, req)
492 f = fctx.path()
493 fl = fctx.filelog()
494 count = len(fl)
495 pagelen = web.maxshortchanges
496 pos = fctx.filerev()
497 start = max(0, pos - pagelen + 1)
498 end = min(count, start + pagelen)
499 pos = end - 1
500 parity = paritygen(web.stripecount, offset=start-end)
501
502 def entries(limit=0, **map):
503 l = []
504
505 for i in xrange(start, end):
506 ctx = fctx.filectx(i)
507 n = fl.node(i)
508
509 l.insert(0, {"parity": parity.next(),
510 "filerev": i,
511 "file": f,
512 "node": hex(ctx.node()),
513 "author": ctx.user(),
514 "date": ctx.date(),
515 "rename": webutil.renamelink(fctx),
516 "parent": webutil.siblings(fctx.parents()),
517 "child": webutil.siblings(fctx.children()),
518 "desc": ctx.description()})
519
520 if limit > 0:
521 l = l[:limit]
522
523 for e in l:
524 yield e
525
526 nodefunc = lambda x: fctx.filectx(fileid=x)
527 nav = webutil.revnavgen(pos, pagelen, count, nodefunc)
528 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
529 entries=lambda **x: entries(limit=0, **x),
530 latestentry=lambda **x: entries(limit=1, **x))
531
109 532
110 533 def archive(web, req, tmpl):
111 534 type_ = req.form.get('type', [None])[0]
112 535 allowed = web.configlist("web", "allow_archive")
113 if (type_ in web.archives and (type_ in allowed or
536 key = req.form['node'][0]
537
538 if not (type_ in web.archives and (type_ in allowed or
114 539 web.configbool("web", "allow" + type_, False))):
115 web.archive(tmpl, req, req.form['node'][0], type_)
116 return []
117 raise ErrorResponse(HTTP_NOT_FOUND, 'unsupported archive type: %s' % type_)
540 msg = 'Unsupported archive type: %s' % type_
541 raise ErrorResponse(HTTP_NOT_FOUND, msg)
542
543 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
544 cnode = web.repo.lookup(key)
545 arch_version = key
546 if cnode == key or key == 'tip':
547 arch_version = short(cnode)
548 name = "%s-%s" % (reponame, arch_version)
549 mimetype, artype, extension, encoding = web.archive_specs[type_]
550 headers = [
551 ('Content-Type', mimetype),
552 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
553 ]
554 if encoding:
555 headers.append(('Content-Encoding', encoding))
556 req.header(headers)
557 req.respond(HTTP_OK)
558 archival.archive(web.repo, req, cnode, artype, prefix=name)
559 return []
560
118 561
119 562 def static(web, req, tmpl):
120 563 fname = req.form['file'][0]
@@ -124,3 +567,39 b' def static(web, req, tmpl):'
124 567 os.path.join(web.templatepath, "static"),
125 568 untrusted=False)
126 569 return [staticfile(static, fname, req)]
570
571 def graph(web, req, tmpl):
572 rev = webutil.changectx(web.repo, req).rev()
573 bg_height = 39
574
575 max_rev = len(web.repo) - 1
576 revcount = min(max_rev, int(req.form.get('revcount', [25])[0]))
577 revnode = web.repo.changelog.node(rev)
578 revnode_hex = hex(revnode)
579 uprev = min(max_rev, rev + revcount)
580 downrev = max(0, rev - revcount)
581 lessrev = max(0, rev - revcount / 2)
582
583 maxchanges = web.maxshortchanges or web.maxchanges
584 count = len(web.repo)
585 changenav = webutil.revnavgen(rev, maxchanges, count, web.repo.changectx)
586
587 tree = list(graphmod.graph(web.repo, rev, rev - revcount))
588 canvasheight = (len(tree) + 1) * bg_height - 27;
589
590 data = []
591 for i, (ctx, vtx, edges) in enumerate(tree):
592 node = short(ctx.node())
593 age = templatefilters.age(ctx.date())
594 desc = templatefilters.firstline(ctx.description())
595 desc = cgi.escape(desc)
596 user = cgi.escape(templatefilters.person(ctx.user()))
597 branch = ctx.branch()
598 branch = branch, web.repo.branchtags().get(branch) == ctx.node()
599 data.append((node, vtx, edges, desc, user, age, branch, ctx.tags()))
600
601 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
602 lessrev=lessrev, revcountmore=revcount and 2 * revcount or 1,
603 revcountless=revcount / 2, downrev=downrev,
604 canvasheight=canvasheight, bg_height=bg_height,
605 jsdata=data, node=revnode_hex, changenav=changenav)
@@ -96,10 +96,9 b' def hook(ui, repo, name, throw=False, **'
96 96 oldstdout = os.dup(sys.__stdout__.fileno())
97 97 os.dup2(sys.__stderr__.fileno(), sys.__stdout__.fileno())
98 98
99 hooks = [(hname, cmd) for hname, cmd in ui.configitems("hooks")
100 if hname.split(".", 1)[0] == name and cmd]
101 hooks.sort()
102 for hname, cmd in hooks:
99 for hname, cmd in util.sort(ui.configitems('hooks')):
100 if hname.split('.')[0] != name or not cmd:
101 continue
103 102 if callable(cmd):
104 103 r = _pythonhook(ui, repo, name, hname, cmd, args, throw) or r
105 104 elif cmd.startswith('python:'):
@@ -268,6 +268,7 b' class httprepository(repo.repository):'
268 268
269 269 # 1.0 here is the _protocol_ version
270 270 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
271 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
271 272 urllib2.install_opener(opener)
272 273
273 274 def url(self):
@@ -19,6 +19,8 b''
19 19
20 20 # Modified by Benoit Boissinot:
21 21 # - fix for digest auth (inspired from urllib2.py @ Python v2.4)
22 # Modified by Dirkjan Ochtman:
23 # - import md5 function from a local util module
22 24
23 25 """An HTTP handler for urllib2 that supports HTTP 1.1 and keepalive.
24 26
@@ -450,7 +452,7 b' def error_handler(url):'
450 452 keepalive_handler.close_all()
451 453
452 454 def continuity(url):
453 import md5
455 from util import md5
454 456 format = '%25s: %s'
455 457
456 458 # first fetch the file with the normal http handler
@@ -9,8 +9,9 b' from node import bin, hex, nullid, nullr'
9 9 from i18n import _
10 10 import repo, changegroup
11 11 import changelog, dirstate, filelog, manifest, context, weakref
12 import lock, transaction, stat, errno, ui
12 import lock, transaction, stat, errno, ui, store
13 13 import os, revlog, time, util, extensions, hook, inspect
14 import match as match_
14 15
15 16 class localrepository(repo.repository):
16 17 capabilities = util.set(('lookup', 'changegroupsubset'))
@@ -59,30 +60,13 b' class localrepository(repo.repository):'
59 60 if r not in self.supported:
60 61 raise repo.RepoError(_("requirement '%s' not supported") % r)
61 62
62 # setup store
63 if "store" in requirements:
64 self.encodefn = util.encodefilename
65 self.decodefn = util.decodefilename
66 self.spath = os.path.join(self.path, "store")
67 else:
68 self.encodefn = lambda x: x
69 self.decodefn = lambda x: x
70 self.spath = self.path
63 self.store = store.store(requirements, self.path)
71 64
72 try:
73 # files in .hg/ will be created using this mode
74 mode = os.stat(self.spath).st_mode
75 # avoid some useless chmods
76 if (0777 & ~util._umask) == (0777 & mode):
77 mode = None
78 except OSError:
79 mode = None
80
81 self._createmode = mode
82 self.opener.createmode = mode
83 sopener = util.opener(self.spath)
84 sopener.createmode = mode
85 self.sopener = util.encodedopener(sopener, self.encodefn)
65 self.spath = self.store.path
66 self.sopener = self.store.opener
67 self.sjoin = self.store.join
68 self._createmode = self.store.createmode
69 self.opener.createmode = self.store.createmode
86 70
87 71 self.ui = ui.ui(parentui=parentui)
88 72 try:
@@ -116,6 +100,21 b' class localrepository(repo.repository):'
116 100 else:
117 101 raise AttributeError, name
118 102
103 def __getitem__(self, changeid):
104 if changeid == None:
105 return context.workingctx(self)
106 return context.changectx(self, changeid)
107
108 def __nonzero__(self):
109 return True
110
111 def __len__(self):
112 return len(self.changelog)
113
114 def __iter__(self):
115 for i in xrange(len(self)):
116 yield i
117
119 118 def url(self):
120 119 return 'file:' + self.root
121 120
@@ -146,7 +145,11 b' class localrepository(repo.repository):'
146 145 if prevtags and prevtags[-1] != '\n':
147 146 fp.write('\n')
148 147 for name in names:
149 fp.write('%s %s\n' % (hex(node), munge and munge(name) or name))
148 m = munge and munge(name) or name
149 if self._tagstypecache and name in self._tagstypecache:
150 old = self.tagscache.get(name, nullid)
151 fp.write('%s %s\n' % (hex(old), m))
152 fp.write('%s %s\n' % (hex(node), m))
150 153 fp.close()
151 154
152 155 prevtags = ''
@@ -302,9 +305,8 b' class localrepository(repo.repository):'
302 305 n = nh[0]
303 306 if n != nullid:
304 307 self.tagscache[k] = n
305 self._tagstypecache[k] = tagtypes[k]
308 self._tagstypecache[k] = tagtypes[k]
306 309 self.tagscache['tip'] = self.changelog.tip()
307
308 310 return self.tagscache
309 311
310 312 def tagtype(self, tagname):
@@ -326,7 +328,7 b' class localrepository(repo.repository):'
326 328 last = {}
327 329 ret = []
328 330 for node in heads:
329 c = self.changectx(node)
331 c = self[node]
330 332 rev = c.rev()
331 333 try:
332 334 fnode = c.filenode('.hgtags')
@@ -347,8 +349,7 b' class localrepository(repo.repository):'
347 349 except:
348 350 r = -2 # sort to the beginning of the list if unknown
349 351 l.append((r, t, n))
350 l.sort()
351 return [(t, n) for r, t, n in l]
352 return [(t, n) for r, t, n in util.sort(l)]
352 353
353 354 def nodetags(self, node):
354 355 '''return the tags associated with a node'''
@@ -359,7 +360,7 b' class localrepository(repo.repository):'
359 360 return self.nodetagscache.get(node, [])
360 361
361 362 def _branchtags(self, partial, lrev):
362 tiprev = self.changelog.count() - 1
363 tiprev = len(self) - 1
363 364 if lrev != tiprev:
364 365 self._updatebranchcache(partial, lrev+1, tiprev+1)
365 366 self._writebranchcache(partial, self.changelog.tip(), tiprev)
@@ -404,8 +405,7 b' class localrepository(repo.repository):'
404 405 try:
405 406 last, lrev = lines.pop(0).split(" ", 1)
406 407 last, lrev = bin(last), int(lrev)
407 if not (lrev < self.changelog.count() and
408 self.changelog.node(lrev) == last): # sanity check
408 if lrev >= len(self) or self[lrev].node() != last:
409 409 # invalidate the cache
410 410 raise ValueError('invalidating branch cache (tip differs)')
411 411 for l in lines:
@@ -432,18 +432,13 b' class localrepository(repo.repository):'
432 432
433 433 def _updatebranchcache(self, partial, start, end):
434 434 for r in xrange(start, end):
435 c = self.changectx(r)
435 c = self[r]
436 436 b = c.branch()
437 437 partial[b] = c.node()
438 438
439 439 def lookup(self, key):
440 440 if key == '.':
441 key, second = self.dirstate.parents()
442 if key == nullid:
443 raise repo.RepoError(_("no revision checked out"))
444 if second != nullid:
445 self.ui.warn(_("warning: working directory has two parents, "
446 "tag '.' uses the first\n"))
441 return self.dirstate.parents()[0]
447 442 elif key == 'null':
448 443 return nullid
449 444 n = self.changelog._match(key)
@@ -469,36 +464,23 b' class localrepository(repo.repository):'
469 464 def join(self, f):
470 465 return os.path.join(self.path, f)
471 466
472 def sjoin(self, f):
473 f = self.encodefn(f)
474 return os.path.join(self.spath, f)
475
476 467 def wjoin(self, f):
477 468 return os.path.join(self.root, f)
478 469
470 def rjoin(self, f):
471 return os.path.join(self.root, util.pconvert(f))
472
479 473 def file(self, f):
480 474 if f[0] == '/':
481 475 f = f[1:]
482 476 return filelog.filelog(self.sopener, f)
483 477
484 def changectx(self, changeid=None):
485 return context.changectx(self, changeid)
486
487 def workingctx(self):
488 return context.workingctx(self)
478 def changectx(self, changeid):
479 return self[changeid]
489 480
490 481 def parents(self, changeid=None):
491 '''
492 get list of changectxs for parents of changeid or working directory
493 '''
494 if changeid is None:
495 pl = self.dirstate.parents()
496 else:
497 n = self.changelog.lookup(changeid)
498 pl = self.changelog.parents(n)
499 if pl[1] == nullid:
500 return [self.changectx(pl[0])]
501 return [self.changectx(pl[0]), self.changectx(pl[1])]
482 '''get list of changectxs for parents of changeid'''
483 return self[changeid].parents()
502 484
503 485 def filectx(self, path, changeid=None, fileid=None):
504 486 """changeid can be a changeset revision, node, or tag.
@@ -676,19 +658,20 b' class localrepository(repo.repository):'
676 658 self._wlockref = weakref.ref(l)
677 659 return l
678 660
679 def filecommit(self, fn, manifest1, manifest2, linkrev, tr, changelist):
661 def filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
680 662 """
681 663 commit an individual file as part of a larger transaction
682 664 """
683 665
684 t = self.wread(fn)
666 fn = fctx.path()
667 t = fctx.data()
685 668 fl = self.file(fn)
686 669 fp1 = manifest1.get(fn, nullid)
687 670 fp2 = manifest2.get(fn, nullid)
688 671
689 672 meta = {}
690 cf = self.dirstate.copied(fn)
691 if cf and cf != fn:
673 cp = fctx.renamed()
674 if cp and cp[0] != fn:
692 675 # Mark the new revision of this file as a copy of another
693 676 # file. This copy data will effectively act as a parent
694 677 # of this new revision. If this is a merge, the first
@@ -708,6 +691,7 b' class localrepository(repo.repository):'
708 691 # \- 2 --- 4 as the merge base
709 692 #
710 693
694 cf = cp[0]
711 695 cr = manifest1.get(cf)
712 696 nfp = fp2
713 697
@@ -721,19 +705,10 b' class localrepository(repo.repository):'
721 705 if not cr:
722 706 self.ui.debug(_(" %s: searching for copy revision for %s\n") %
723 707 (fn, cf))
724 p1 = self.dirstate.parents()[0]
725 rev = self.changelog.rev(p1)
726 seen = {-1:None}
727 visit = [rev]
728 while visit:
729 for p in self.changelog.parentrevs(visit.pop(0)):
730 if p not in seen:
731 seen[p] = True
732 visit.append(p)
733 ctx = self.changectx(p)
734 if cf in ctx:
735 cr = ctx[cf].filenode()
736 break
708 for a in self['.'].ancestors():
709 if cf in a:
710 cr = a[cf].filenode()
711 break
737 712
738 713 self.ui.debug(_(" %s: copy %s:%s\n") % (fn, cf, hex(cr)))
739 714 meta["copy"] = cf
@@ -761,66 +736,80 b' class localrepository(repo.repository):'
761 736 p1=p1, p2=p2, extra=extra, empty_ok=True)
762 737
763 738 def commit(self, files=None, text="", user=None, date=None,
764 match=util.always, force=False, force_editor=False,
739 match=None, force=False, force_editor=False,
765 740 p1=None, p2=None, extra={}, empty_ok=False):
766 wlock = lock = tr = None
767 valid = 0 # don't save the dirstate if this isn't set
741 wlock = lock = None
768 742 if files:
769 743 files = util.unique(files)
770 744 try:
771 745 wlock = self.wlock()
772 746 lock = self.lock()
773 commit = []
774 remove = []
775 changed = []
776 747 use_dirstate = (p1 is None) # not rawcommit
777 extra = extra.copy()
778
779 if use_dirstate:
780 if files:
781 for f in files:
782 s = self.dirstate[f]
783 if s in 'nma':
784 commit.append(f)
785 elif s == 'r':
786 remove.append(f)
787 else:
788 self.ui.warn(_("%s not tracked!\n") % f)
789 else:
790 changes = self.status(match=match)[:5]
791 modified, added, removed, deleted, unknown = changes
792 commit = modified + added
793 remove = removed
794 else:
795 commit = files
796 748
797 749 if use_dirstate:
798 750 p1, p2 = self.dirstate.parents()
799 751 update_dirstate = True
800 752
801 753 if (not force and p2 != nullid and
802 (files or match != util.always)):
754 (match and (match.files() or match.anypats()))):
803 755 raise util.Abort(_('cannot partially commit a merge '
804 756 '(do not specify files or patterns)'))
757
758 if files:
759 modified, removed = [], []
760 for f in files:
761 s = self.dirstate[f]
762 if s in 'nma':
763 modified.append(f)
764 elif s == 'r':
765 removed.append(f)
766 else:
767 self.ui.warn(_("%s not tracked!\n") % f)
768 changes = [modified, [], removed, [], []]
769 else:
770 changes = self.status(match=match)
805 771 else:
806 772 p1, p2 = p1, p2 or nullid
807 773 update_dirstate = (self.dirstate.parents()[0] == p1)
774 changes = [files, [], [], [], []]
808 775
776 wctx = context.workingctx(self, (p1, p2), text, user, date,
777 extra, changes)
778 return self._commitctx(wctx, force, force_editor, empty_ok,
779 use_dirstate, update_dirstate)
780 finally:
781 del lock, wlock
782
783 def commitctx(self, ctx):
784 wlock = lock = None
785 try:
786 wlock = self.wlock()
787 lock = self.lock()
788 return self._commitctx(ctx, force=True, force_editor=False,
789 empty_ok=True, use_dirstate=False,
790 update_dirstate=False)
791 finally:
792 del lock, wlock
793
794 def _commitctx(self, wctx, force=False, force_editor=False, empty_ok=False,
795 use_dirstate=True, update_dirstate=True):
796 tr = None
797 valid = 0 # don't save the dirstate if this isn't set
798 try:
799 commit = util.sort(wctx.modified() + wctx.added())
800 remove = wctx.removed()
801 extra = wctx.extra().copy()
802 branchname = extra['branch']
803 user = wctx.user()
804 text = wctx.description()
805
806 p1, p2 = [p.node() for p in wctx.parents()]
809 807 c1 = self.changelog.read(p1)
810 808 c2 = self.changelog.read(p2)
811 809 m1 = self.manifest.read(c1[0]).copy()
812 810 m2 = self.manifest.read(c2[0])
813 811
814 812 if use_dirstate:
815 branchname = self.workingctx().branch()
816 try:
817 branchname = branchname.decode('UTF-8').encode('UTF-8')
818 except UnicodeDecodeError:
819 raise util.Abort(_('branch name not in UTF-8!'))
820 else:
821 branchname = ""
822
823 if use_dirstate:
824 813 oldname = c1[5].get("branch") # stored in UTF-8
825 814 if (not commit and not remove and not force and p2 == nullid
826 815 and branchname == oldname):
@@ -838,26 +827,22 b' class localrepository(repo.repository):'
838 827
839 828 # check in files
840 829 new = {}
841 linkrev = self.changelog.count()
842 commit.sort()
843 is_exec = util.execfunc(self.root, m1.execf)
844 is_link = util.linkfunc(self.root, m1.linkf)
830 changed = []
831 linkrev = len(self)
845 832 for f in commit:
846 833 self.ui.note(f + "\n")
847 834 try:
848 new[f] = self.filecommit(f, m1, m2, linkrev, trp, changed)
849 new_exec = is_exec(f)
850 new_link = is_link(f)
835 fctx = wctx.filectx(f)
836 newflags = fctx.flags()
837 new[f] = self.filecommit(fctx, m1, m2, linkrev, trp, changed)
851 838 if ((not changed or changed[-1] != f) and
852 839 m2.get(f) != new[f]):
853 840 # mention the file in the changelog if some
854 841 # flag changed, even if there was no content
855 842 # change.
856 old_exec = m1.execf(f)
857 old_link = m1.linkf(f)
858 if old_exec != new_exec or old_link != new_link:
843 if m1.flags(f) != newflags:
859 844 changed.append(f)
860 m1.set(f, new_exec, new_link)
845 m1.set(f, newflags)
861 846 if use_dirstate:
862 847 self.dirstate.normal(f)
863 848
@@ -870,10 +855,9 b' class localrepository(repo.repository):'
870 855
871 856 # update manifest
872 857 m1.update(new)
873 remove.sort()
874 858 removed = []
875 859
876 for f in remove:
860 for f in util.sort(remove):
877 861 if f in m1:
878 862 del m1[f]
879 863 removed.append(f)
@@ -883,10 +867,6 b' class localrepository(repo.repository):'
883 867 (new, removed))
884 868
885 869 # add changeset
886 new = new.keys()
887 new.sort()
888
889 user = user or self.ui.username()
890 870 if (not empty_ok and not text) or force_editor:
891 871 edittext = []
892 872 if text:
@@ -911,9 +891,6 b' class localrepository(repo.repository):'
911 891 text = self.ui.edit("\n".join(edittext), user)
912 892 os.chdir(olddir)
913 893
914 if branchname:
915 extra["branch"] = branchname
916
917 894 lines = [line.rstrip() for line in text.rstrip().splitlines()]
918 895 while lines and not lines[0]:
919 896 del lines[0]
@@ -922,7 +899,7 b' class localrepository(repo.repository):'
922 899 text = '\n'.join(lines)
923 900
924 901 n = self.changelog.add(mn, changed + removed, text, trp, p1, p2,
925 user, date, extra)
902 user, wctx.date(), extra)
926 903 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
927 904 parent2=xp2)
928 905 tr.close()
@@ -942,169 +919,103 b' class localrepository(repo.repository):'
942 919 finally:
943 920 if not valid: # don't save our updated dirstate
944 921 self.dirstate.invalidate()
945 del tr, lock, wlock
922 del tr
946 923
947 def walk(self, node=None, files=[], match=util.always, badmatch=None):
924 def walk(self, match, node=None):
948 925 '''
949 926 walk recursively through the directory tree or a given
950 927 changeset, finding all files matched by the match
951 928 function
952
953 results are yielded in a tuple (src, filename), where src
954 is one of:
955 'f' the file was found in the directory tree
956 'm' the file was only in the dirstate and not in the tree
957 'b' file was not found and matched badmatch
958 929 '''
930 return self[node].walk(match)
959 931
960 if node:
961 fdict = dict.fromkeys(files)
962 # for dirstate.walk, files=['.'] means "walk the whole tree".
963 # follow that here, too
964 fdict.pop('.', None)
965 mdict = self.manifest.read(self.changelog.read(node)[0])
966 mfiles = mdict.keys()
967 mfiles.sort()
968 for fn in mfiles:
969 for ffn in fdict:
970 # match if the file is the exact name or a directory
971 if ffn == fn or fn.startswith("%s/" % ffn):
972 del fdict[ffn]
973 break
974 if match(fn):
975 yield 'm', fn
976 ffiles = fdict.keys()
977 ffiles.sort()
978 for fn in ffiles:
979 if badmatch and badmatch(fn):
980 if match(fn):
981 yield 'b', fn
982 else:
983 self.ui.warn(_('%s: No such file in rev %s\n')
984 % (self.pathto(fn), short(node)))
985 else:
986 for src, fn in self.dirstate.walk(files, match, badmatch=badmatch):
987 yield src, fn
988
989 def status(self, node1=None, node2=None, files=[], match=util.always,
990 list_ignored=False, list_clean=False, list_unknown=True):
932 def status(self, node1='.', node2=None, match=None,
933 ignored=False, clean=False, unknown=False):
991 934 """return status of files between two nodes or node and working directory
992 935
993 936 If node1 is None, use the first dirstate parent instead.
994 937 If node2 is None, compare node1 with working directory.
995 938 """
996 939
997 def fcmp(fn, getnode):
998 t1 = self.wread(fn)
999 return self.file(fn).cmp(getnode(fn), t1)
1000
1001 def mfmatches(node):
1002 change = self.changelog.read(node)
1003 mf = self.manifest.read(change[0]).copy()
940 def mfmatches(ctx):
941 mf = ctx.manifest().copy()
1004 942 for fn in mf.keys():
1005 943 if not match(fn):
1006 944 del mf[fn]
1007 945 return mf
1008 946
1009 modified, added, removed, deleted, unknown = [], [], [], [], []
1010 ignored, clean = [], []
1011
1012 compareworking = False
1013 if not node1 or (not node2 and node1 == self.dirstate.parents()[0]):
1014 compareworking = True
947 ctx1 = self[node1]
948 ctx2 = self[node2]
949 working = ctx2 == self[None]
950 parentworking = working and ctx1 == self['.']
951 match = match or match_.always(self.root, self.getcwd())
952 listignored, listclean, listunknown = ignored, clean, unknown
1015 953
1016 if not compareworking:
1017 # read the manifest from node1 before the manifest from node2,
1018 # so that we'll hit the manifest cache if we're going through
1019 # all the revisions in parent->child order.
1020 mf1 = mfmatches(node1)
1021
1022 # are we comparing the working directory?
1023 if not node2:
1024 (lookup, modified, added, removed, deleted, unknown,
1025 ignored, clean) = self.dirstate.status(files, match,
1026 list_ignored, list_clean,
1027 list_unknown)
954 if working: # we need to scan the working dir
955 s = self.dirstate.status(match, listignored, listclean, listunknown)
956 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
1028 957
1029 # are we comparing working dir against its parent?
1030 if compareworking:
1031 if lookup:
1032 fixup = []
1033 # do a full compare of any files that might have changed
1034 ctx = self.changectx()
1035 mexec = lambda f: 'x' in ctx.fileflags(f)
1036 mlink = lambda f: 'l' in ctx.fileflags(f)
1037 is_exec = util.execfunc(self.root, mexec)
1038 is_link = util.linkfunc(self.root, mlink)
1039 def flags(f):
1040 return is_link(f) and 'l' or is_exec(f) and 'x' or ''
1041 for f in lookup:
1042 if (f not in ctx or flags(f) != ctx.fileflags(f)
1043 or ctx[f].cmp(self.wread(f))):
1044 modified.append(f)
1045 else:
1046 fixup.append(f)
1047 if list_clean:
1048 clean.append(f)
958 # check for any possibly clean files
959 if parentworking and cmp:
960 fixup = []
961 # do a full compare of any files that might have changed
962 for f in cmp:
963 if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f)
964 or ctx1[f].cmp(ctx2[f].data())):
965 modified.append(f)
966 else:
967 fixup.append(f)
968
969 if listclean:
970 clean += fixup
1049 971
1050 # update dirstate for files that are actually clean
1051 if fixup:
1052 wlock = None
972 # update dirstate for files that are actually clean
973 if fixup:
974 wlock = None
975 try:
1053 976 try:
1054 try:
1055 wlock = self.wlock(False)
1056 except lock.LockException:
1057 pass
1058 if wlock:
1059 for f in fixup:
1060 self.dirstate.normal(f)
1061 finally:
1062 del wlock
1063 else:
977 wlock = self.wlock(False)
978 for f in fixup:
979 self.dirstate.normal(f)
980 except lock.LockException:
981 pass
982 finally:
983 del wlock
984
985 if not parentworking:
986 mf1 = mfmatches(ctx1)
987 if working:
1064 988 # we are comparing working dir against non-parent
1065 989 # generate a pseudo-manifest for the working dir
1066 # XXX: create it in dirstate.py ?
1067 mf2 = mfmatches(self.dirstate.parents()[0])
1068 is_exec = util.execfunc(self.root, mf2.execf)
1069 is_link = util.linkfunc(self.root, mf2.linkf)
1070 for f in lookup + modified + added:
1071 mf2[f] = ""
1072 mf2.set(f, is_exec(f), is_link(f))
990 mf2 = mfmatches(self['.'])
991 for f in cmp + modified + added:
992 mf2[f] = None
993 mf2.set(f, ctx2.flags(f))
1073 994 for f in removed:
1074 995 if f in mf2:
1075 996 del mf2[f]
997 else:
998 # we are comparing two revisions
999 deleted, unknown, ignored = [], [], []
1000 mf2 = mfmatches(ctx2)
1076 1001
1077 else:
1078 # we are comparing two revisions
1079 mf2 = mfmatches(node2)
1080
1081 if not compareworking:
1082 # flush lists from dirstate before comparing manifests
1083 1002 modified, added, clean = [], [], []
1084
1085 # make sure to sort the files so we talk to the disk in a
1086 # reasonable order
1087 mf2keys = mf2.keys()
1088 mf2keys.sort()
1089 getnode = lambda fn: mf1.get(fn, nullid)
1090 for fn in mf2keys:
1003 for fn in mf2:
1091 1004 if fn in mf1:
1092 1005 if (mf1.flags(fn) != mf2.flags(fn) or
1093 1006 (mf1[fn] != mf2[fn] and
1094 (mf2[fn] != "" or fcmp(fn, getnode)))):
1007 (mf2[fn] or ctx1[fn].cmp(ctx2[fn].data())))):
1095 1008 modified.append(fn)
1096 elif list_clean:
1009 elif listclean:
1097 1010 clean.append(fn)
1098 1011 del mf1[fn]
1099 1012 else:
1100 1013 added.append(fn)
1101
1102 1014 removed = mf1.keys()
1103 1015
1104 # sort and return results:
1105 for l in modified, added, removed, deleted, unknown, ignored, clean:
1106 l.sort()
1107 return (modified, added, removed, deleted, unknown, ignored, clean)
1016 r = modified, added, removed, deleted, unknown, ignored, clean
1017 [l.sort() for l in r]
1018 return r
1108 1019
1109 1020 def add(self, list):
1110 1021 wlock = self.wlock()
@@ -1209,10 +1120,11 b' class localrepository(repo.repository):'
1209 1120 heads = self.changelog.heads(start)
1210 1121 # sort the output in rev descending order
1211 1122 heads = [(-self.changelog.rev(h), h) for h in heads]
1212 heads.sort()
1213 return [n for (r, n) in heads]
1123 return [n for (r, n) in util.sort(heads)]
1214 1124
1215 def branchheads(self, branch, start=None):
1125 def branchheads(self, branch=None, start=None):
1126 if branch is None:
1127 branch = self[None].branch()
1216 1128 branches = self.branchtags()
1217 1129 if branch not in branches:
1218 1130 return []
@@ -1250,7 +1162,7 b' class localrepository(repo.repository):'
1250 1162 if rev in ancestors:
1251 1163 ancestors.update(self.changelog.parentrevs(rev))
1252 1164 ancestors.remove(rev)
1253 elif self.changectx(rev).branch() == branch:
1165 elif self[rev].branch() == branch:
1254 1166 heads.append(rev)
1255 1167 ancestors.update(self.changelog.parentrevs(rev))
1256 1168 heads = [self.changelog.node(rev) for rev in heads]
@@ -1665,7 +1577,7 b' class localrepository(repo.repository):'
1665 1577 # Nor do we know which filenodes are missing.
1666 1578 msng_filenode_set = {}
1667 1579
1668 junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex
1580 junk = mnfst.index[len(mnfst) - 1] # Get around a bug in lazyindex
1669 1581 junk = None
1670 1582
1671 1583 # A changeset always belongs to itself, so the changenode lookup
@@ -1860,12 +1772,10 b' class localrepository(repo.repository):'
1860 1772 add_extra_nodes(fname,
1861 1773 msng_filenode_set.setdefault(fname, {}))
1862 1774 changedfiles[fname] = 1
1863 changedfiles = changedfiles.keys()
1864 changedfiles.sort()
1865 1775 # Go through all our files in order sorted by name.
1866 for fname in changedfiles:
1776 for fname in util.sort(changedfiles):
1867 1777 filerevlog = self.file(fname)
1868 if filerevlog.count() == 0:
1778 if not len(filerevlog):
1869 1779 raise util.Abort(_("empty or missing revlog for %s") % fname)
1870 1780 # Toss out the filenodes that the recipient isn't really
1871 1781 # missing.
@@ -1916,10 +1826,10 b' class localrepository(repo.repository):'
1916 1826 def identity(x):
1917 1827 return x
1918 1828
1919 def gennodelst(revlog):
1920 for r in xrange(0, revlog.count()):
1921 n = revlog.node(r)
1922 if revlog.linkrev(n) in revset:
1829 def gennodelst(log):
1830 for r in log:
1831 n = log.node(r)
1832 if log.linkrev(n) in revset:
1923 1833 yield n
1924 1834
1925 1835 def changed_file_collector(changedfileset):
@@ -1941,17 +1851,15 b' class localrepository(repo.repository):'
1941 1851 for chnk in cl.group(nodes, identity,
1942 1852 changed_file_collector(changedfiles)):
1943 1853 yield chnk
1944 changedfiles = changedfiles.keys()
1945 changedfiles.sort()
1946 1854
1947 1855 mnfst = self.manifest
1948 1856 nodeiter = gennodelst(mnfst)
1949 1857 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1950 1858 yield chnk
1951 1859
1952 for fname in changedfiles:
1860 for fname in util.sort(changedfiles):
1953 1861 filerevlog = self.file(fname)
1954 if filerevlog.count() == 0:
1862 if not len(filerevlog):
1955 1863 raise util.Abort(_("empty or missing revlog for %s") % fname)
1956 1864 nodeiter = gennodelst(filerevlog)
1957 1865 nodeiter = list(nodeiter)
@@ -1980,7 +1888,7 b' class localrepository(repo.repository):'
1980 1888 """
1981 1889 def csmap(x):
1982 1890 self.ui.debug(_("add changeset %s\n") % short(x))
1983 return cl.count()
1891 return len(cl)
1984 1892
1985 1893 def revmap(x):
1986 1894 return cl.rev(x)
@@ -2003,11 +1911,11 b' class localrepository(repo.repository):'
2003 1911 trp = weakref.proxy(tr)
2004 1912 # pull off the changeset group
2005 1913 self.ui.status(_("adding changesets\n"))
2006 cor = cl.count() - 1
1914 cor = len(cl) - 1
2007 1915 chunkiter = changegroup.chunkiter(source)
2008 if cl.addgroup(chunkiter, csmap, trp, 1) is None and not emptyok:
1916 if cl.addgroup(chunkiter, csmap, trp) is None and not emptyok:
2009 1917 raise util.Abort(_("received changelog group is empty"))
2010 cnr = cl.count() - 1
1918 cnr = len(cl) - 1
2011 1919 changesets = cnr - cor
2012 1920
2013 1921 # pull off the manifest group
@@ -2027,11 +1935,11 b' class localrepository(repo.repository):'
2027 1935 break
2028 1936 self.ui.debug(_("adding %s revisions\n") % f)
2029 1937 fl = self.file(f)
2030 o = fl.count()
1938 o = len(fl)
2031 1939 chunkiter = changegroup.chunkiter(source)
2032 1940 if fl.addgroup(chunkiter, revmap, trp) is None:
2033 1941 raise util.Abort(_("received file revlog group is empty"))
2034 revisions += fl.count() - o
1942 revisions += len(fl) - o
2035 1943 files += 1
2036 1944
2037 1945 # make changelog see real files again
@@ -2139,6 +2047,25 b' class localrepository(repo.repository):'
2139 2047 return self.stream_in(remote)
2140 2048 return self.pull(remote, heads)
2141 2049
2050 def storefiles(self):
2051 '''get all *.i and *.d files in the store
2052
2053 Returns (list of (filename, size), total_bytes)'''
2054
2055 lock = None
2056 try:
2057 self.ui.debug('scanning\n')
2058 entries = []
2059 total_bytes = 0
2060 # get consistent snapshot of repo, lock during scan
2061 lock = self.lock()
2062 for name, size in self.store.walk():
2063 entries.append((name, size))
2064 total_bytes += size
2065 return entries, total_bytes
2066 finally:
2067 del lock
2068
2142 2069 # used to avoid circular references so destructors work
2143 2070 def aftertrans(files):
2144 2071 renamefiles = [tuple(t) for t in files]
@@ -6,7 +6,8 b''
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from i18n import _
9 import os, smtplib, util, socket
9 import os, smtplib, socket
10 import util
10 11
11 12 def _smtp(ui):
12 13 '''build an smtp connection and return a function to send mail'''
@@ -53,7 +54,7 b' def _sendmail(ui, sender, recipients, ms'
53 54 cmdline = '%s -f %s %s' % (program, util.email(sender),
54 55 ' '.join(map(util.email, recipients)))
55 56 ui.note(_('sending mail: %s\n') % cmdline)
56 fp = os.popen(cmdline, 'w')
57 fp = util.popen(cmdline, 'w')
57 58 fp.write(msg)
58 59 ret = fp.close()
59 60 if ret:
@@ -8,7 +8,7 b''
8 8 from node import bin, hex, nullid
9 9 from revlog import revlog, RevlogError
10 10 from i18n import _
11 import array, struct, mdiff
11 import array, struct, mdiff, parsers, util
12 12
13 13 class manifestdict(dict):
14 14 def __init__(self, mapping=None, flags=None):
@@ -18,16 +18,8 b' class manifestdict(dict):'
18 18 self._flags = flags
19 19 def flags(self, f):
20 20 return self._flags.get(f, "")
21 def execf(self, f):
22 "test for executable in manifest flags"
23 return "x" in self.flags(f)
24 def linkf(self, f):
25 "test for symlink in manifest flags"
26 return "l" in self.flags(f)
27 def set(self, f, execf=False, linkf=False):
28 if linkf: self._flags[f] = "l"
29 elif execf: self._flags[f] = "x"
30 else: self._flags[f] = ""
21 def set(self, f, flags):
22 self._flags[f] = flags
31 23 def copy(self):
32 24 return manifestdict(dict.copy(self), dict.copy(self._flags))
33 25
@@ -39,14 +31,7 b' class manifest(revlog):'
39 31
40 32 def parse(self, lines):
41 33 mfdict = manifestdict()
42 fdict = mfdict._flags
43 for l in lines.splitlines():
44 f, n = l.split('\0')
45 if len(n) > 40:
46 fdict[f] = n[40:]
47 mfdict[f] = bin(n[:40])
48 else:
49 mfdict[f] = bin(n)
34 parsers.parse_manifest(mfdict, mfdict._flags, lines)
50 35 return mfdict
51 36
52 37 def readdelta(self, node):
@@ -134,18 +119,16 b' class manifest(revlog):'
134 119 return "".join([struct.pack(">lll", d[0], d[1], len(d[2])) + d[2]
135 120 for d in x ])
136 121
137 def checkforbidden(f):
138 if '\n' in f or '\r' in f:
139 raise RevlogError(_("'\\n' and '\\r' disallowed in filenames"))
122 def checkforbidden(l):
123 for f in l:
124 if '\n' in f or '\r' in f:
125 raise RevlogError(_("'\\n' and '\\r' disallowed in filenames"))
140 126
141 127 # if we're using the listcache, make sure it is valid and
142 128 # parented by the same node we're diffing against
143 129 if not (changed and self.listcache and p1 and self.mapcache[0] == p1):
144 files = map.keys()
145 files.sort()
146
147 for f in files:
148 checkforbidden(f)
130 files = util.sort(map)
131 checkforbidden(files)
149 132
150 133 # if this is changed to support newlines in filenames,
151 134 # be sure to check the templates/ dir again (especially *-raw.tmpl)
@@ -156,8 +139,7 b' class manifest(revlog):'
156 139 else:
157 140 addlist = self.listcache
158 141
159 for f in changed[0]:
160 checkforbidden(f)
142 checkforbidden(changed[0])
161 143 # combine the changed lists into one list for sorting
162 144 work = [[x, 0] for x in changed[0]]
163 145 work[len(work):] = [[x, 1] for x in changed[1]]
@@ -6,7 +6,7 b''
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from i18n import _
9 import bdiff, mpatch, re, struct, util, md5
9 import bdiff, mpatch, re, struct, util
10 10
11 11 def splitnewlines(text):
12 12 '''like str.splitlines, but only split on newlines.'''
@@ -78,10 +78,7 b' def unidiff(a, ad, b, bd, fn1, fn2, r=No'
78 78 epoch = util.datestr((0, 0))
79 79
80 80 if not opts.text and (util.binary(a) or util.binary(b)):
81 def h(v):
82 # md5 is used instead of sha1 because md5 is supposedly faster
83 return md5.new(v).digest()
84 if a and b and len(a) == len(b) and h(a) == h(b):
81 if a and b and len(a) == len(b) and a == b:
85 82 return ""
86 83 l = ['Binary file %s has changed\n' % fn1]
87 84 elif not a:
@@ -5,9 +5,70 b''
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 from node import nullid, nullrev
8 from node import nullid, nullrev, hex, bin
9 9 from i18n import _
10 import errno, util, os, filemerge, copies
10 import errno, util, os, filemerge, copies, shutil
11
12 class mergestate(object):
13 '''track 3-way merge state of individual files'''
14 def __init__(self, repo):
15 self._repo = repo
16 self._read()
17 def reset(self, node):
18 self._state = {}
19 self._local = node
20 shutil.rmtree(self._repo.join("merge"), True)
21 def _read(self):
22 self._state = {}
23 try:
24 localnode = None
25 f = self._repo.opener("merge/state")
26 for i, l in enumerate(f):
27 if i == 0:
28 localnode = l[:-1]
29 else:
30 bits = l[:-1].split("\0")
31 self._state[bits[0]] = bits[1:]
32 self._local = bin(localnode)
33 except IOError, err:
34 if err.errno != errno.ENOENT:
35 raise
36 def _write(self):
37 f = self._repo.opener("merge/state", "w")
38 f.write(hex(self._local) + "\n")
39 for d, v in self._state.items():
40 f.write("\0".join([d] + v) + "\n")
41 def add(self, fcl, fco, fca, fd, flags):
42 hash = util.sha1(fcl.path()).hexdigest()
43 self._repo.opener("merge/" + hash, "w").write(fcl.data())
44 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
45 hex(fca.filenode()), fco.path(), flags]
46 self._write()
47 def __contains__(self, dfile):
48 return dfile in self._state
49 def __getitem__(self, dfile):
50 return self._state[dfile][0]
51 def __iter__(self):
52 l = self._state.keys()
53 l.sort()
54 for f in l:
55 yield f
56 def mark(self, dfile, state):
57 self._state[dfile][0] = state
58 self._write()
59 def resolve(self, dfile, wctx, octx):
60 if self[dfile] == 'r':
61 return 0
62 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
63 f = self._repo.opener("merge/" + hash)
64 self._repo.wwrite(dfile, f.read(), flags)
65 fcd = wctx[dfile]
66 fco = octx[ofile]
67 fca = self._repo.filectx(afile, fileid=anode)
68 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
69 if not r:
70 self.mark(dfile, 'r')
71 return r
11 72
12 73 def _checkunknown(wctx, mctx):
13 74 "check for collisions between unknown files and files in mctx"
@@ -197,19 +258,44 b' def manifestmerge(repo, p1, p2, pa, over'
197 258
198 259 return action
199 260
261 def actioncmp(a1, a2):
262 m1 = a1[1]
263 m2 = a2[1]
264 if m1 == m2:
265 return cmp(a1, a2)
266 if m1 == 'r':
267 return -1
268 if m2 == 'r':
269 return 1
270 return cmp(a1, a2)
271
200 272 def applyupdates(repo, action, wctx, mctx):
201 273 "apply the merge action list to the working directory"
202 274
203 275 updated, merged, removed, unresolved = 0, 0, 0, 0
204 action.sort()
205 # prescan for copy/renames
276 ms = mergestate(repo)
277 ms.reset(wctx.parents()[0].node())
278 moves = []
279 action.sort(actioncmp)
280
281 # prescan for merges
206 282 for a in action:
207 283 f, m = a[:2]
208 284 if m == 'm': # merge
209 285 f2, fd, flags, move = a[2:]
210 if f != fd:
211 repo.ui.debug(_("copying %s to %s\n") % (f, fd))
212 repo.wwrite(fd, repo.wread(f), flags)
286 repo.ui.debug(_("preserving %s for resolve of %s\n") % (f, fd))
287 fcl = wctx[f]
288 fco = mctx[f2]
289 fca = fcl.ancestor(fco) or repo.filectx(f, fileid=nullrev)
290 ms.add(fcl, fco, fca, fd, flags)
291 if f != fd and move:
292 moves.append(f)
293
294 # remove renamed files after safely stored
295 for f in moves:
296 if util.lexists(repo.wjoin(f)):
297 repo.ui.debug(_("removing %s\n") % f)
298 os.unlink(repo.wjoin(f))
213 299
214 300 audit_path = util.path_auditor(repo.root)
215 301
@@ -229,7 +315,7 b' def applyupdates(repo, action, wctx, mct'
229 315 removed += 1
230 316 elif m == "m": # merge
231 317 f2, fd, flags, move = a[2:]
232 r = filemerge.filemerge(repo, f, fd, f2, wctx, mctx)
318 r = ms.resolve(fd, wctx, mctx)
233 319 if r > 0:
234 320 unresolved += 1
235 321 else:
@@ -237,10 +323,6 b' def applyupdates(repo, action, wctx, mct'
237 323 updated += 1
238 324 else:
239 325 merged += 1
240 util.set_flags(repo.wjoin(fd), flags)
241 if f != fd and move and util.lexists(repo.wjoin(f)):
242 repo.ui.debug(_("removing %s\n") % f)
243 os.unlink(repo.wjoin(f))
244 326 elif m == "g": # get
245 327 flags = a[2]
246 328 repo.ui.note(_("getting %s\n") % f)
@@ -337,7 +419,7 b' def update(repo, node, branchmerge, forc'
337 419
338 420 wlock = repo.wlock()
339 421 try:
340 wc = repo.workingctx()
422 wc = repo[None]
341 423 if node is None:
342 424 # tip of current branch
343 425 try:
@@ -349,7 +431,7 b' def update(repo, node, branchmerge, forc'
349 431 raise util.Abort(_("branch %s not found") % wc.branch())
350 432 overwrite = force and not branchmerge
351 433 pl = wc.parents()
352 p1, p2 = pl[0], repo.changectx(node)
434 p1, p2 = pl[0], repo[node]
353 435 pa = p1.ancestor(p2)
354 436 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
355 437 fastforward = False
@@ -388,7 +470,7 b' def update(repo, node, branchmerge, forc'
388 470 action = []
389 471 if not force:
390 472 _checkunknown(wc, p2)
391 if not util.checkfolding(repo.path):
473 if not util.checkcase(repo.path):
392 474 _checkcollision(p2)
393 475 action += _forgetremoved(wc, p2, branchmerge)
394 476 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
@@ -8,8 +8,8 b''
8 8
9 9 from i18n import _
10 10 from node import hex, nullid, short
11 import base85, cmdutil, mdiff, util, context, revlog, diffhelpers, copies
12 import cStringIO, email.Parser, os, popen2, re, sha, errno
11 import base85, cmdutil, mdiff, util, revlog, diffhelpers, copies
12 import cStringIO, email.Parser, os, popen2, re, errno
13 13 import sys, tempfile, zlib
14 14
15 15 class PatchError(Exception):
@@ -1094,8 +1094,7 b' def updatedir(ui, repo, patches):'
1094 1094 repo.copy(src, dst)
1095 1095 removes = removes.keys()
1096 1096 if removes:
1097 removes.sort()
1098 repo.remove(removes, True)
1097 repo.remove(util.sort(removes), True)
1099 1098 for f in patches:
1100 1099 ctype, gp = patches[f]
1101 1100 if gp and gp.mode:
@@ -1113,9 +1112,7 b' def updatedir(ui, repo, patches):'
1113 1112 cmdutil.addremove(repo, cfiles)
1114 1113 files = patches.keys()
1115 1114 files.extend([r for r in removes if r not in files])
1116 files.sort()
1117
1118 return files
1115 return util.sort(files)
1119 1116
1120 1117 def b85diff(to, tn):
1121 1118 '''print base85-encoded binary diff'''
@@ -1123,7 +1120,7 b' def b85diff(to, tn):'
1123 1120 if not text:
1124 1121 return '0' * 40
1125 1122 l = len(text)
1126 s = sha.new('blob %d\0' % l)
1123 s = util.sha1('blob %d\0' % l)
1127 1124 s.update(text)
1128 1125 return s.hexdigest()
1129 1126
@@ -1155,7 +1152,7 b' def b85diff(to, tn):'
1155 1152 ret.append('\n')
1156 1153 return ''.join(ret)
1157 1154
1158 def diff(repo, node1=None, node2=None, files=None, match=util.always,
1155 def diff(repo, node1=None, node2=None, match=None,
1159 1156 fp=None, changes=None, opts=None):
1160 1157 '''print diff of changes to files between two nodes, or node and
1161 1158 working directory.
@@ -1163,6 +1160,9 b' def diff(repo, node1=None, node2=None, f'
1163 1160 if node1 is None, use first dirstate parent instead.
1164 1161 if node2 is None, compare node1 with working directory.'''
1165 1162
1163 if not match:
1164 match = cmdutil.matchall(repo)
1165
1166 1166 if opts is None:
1167 1167 opts = mdiff.defaultopts
1168 1168 if fp is None:
@@ -1171,12 +1171,6 b' def diff(repo, node1=None, node2=None, f'
1171 1171 if not node1:
1172 1172 node1 = repo.dirstate.parents()[0]
1173 1173
1174 ccache = {}
1175 def getctx(r):
1176 if r not in ccache:
1177 ccache[r] = context.changectx(repo, r)
1178 return ccache[r]
1179
1180 1174 flcache = {}
1181 1175 def getfilectx(f, ctx):
1182 1176 flctx = ctx.filectx(f, filelog=flcache.get(f))
@@ -1186,30 +1180,19 b' def diff(repo, node1=None, node2=None, f'
1186 1180
1187 1181 # reading the data for node1 early allows it to play nicely
1188 1182 # with repo.status and the revlog cache.
1189 ctx1 = context.changectx(repo, node1)
1183 ctx1 = repo[node1]
1190 1184 # force manifest reading
1191 1185 man1 = ctx1.manifest()
1192 1186 date1 = util.datestr(ctx1.date())
1193 1187
1194 1188 if not changes:
1195 changes = repo.status(node1, node2, files, match=match)[:5]
1196 modified, added, removed, deleted, unknown = changes
1189 changes = repo.status(node1, node2, match=match)
1190 modified, added, removed = changes[:3]
1197 1191
1198 1192 if not modified and not added and not removed:
1199 1193 return
1200 1194
1201 if node2:
1202 ctx2 = context.changectx(repo, node2)
1203 execf2 = ctx2.manifest().execf
1204 linkf2 = ctx2.manifest().linkf
1205 else:
1206 ctx2 = context.workingctx(repo)
1207 execf2 = util.execfunc(repo.root, None)
1208 linkf2 = util.linkfunc(repo.root, None)
1209 if execf2 is None:
1210 mc = ctx2.parents()[0].manifest().copy()
1211 execf2 = mc.execf
1212 linkf2 = mc.linkf
1195 ctx2 = repo[node2]
1213 1196
1214 1197 if repo.ui.quiet:
1215 1198 r = None
@@ -1218,15 +1201,14 b' def diff(repo, node1=None, node2=None, f'
1218 1201 r = [hexfunc(node) for node in [node1, node2] if node]
1219 1202
1220 1203 if opts.git:
1221 copy, diverge = copies.copies(repo, ctx1, ctx2, repo.changectx(nullid))
1204 copy, diverge = copies.copies(repo, ctx1, ctx2, repo[nullid])
1222 1205 for k, v in copy.items():
1223 1206 copy[v] = k
1224 1207
1225 all = modified + added + removed
1226 all.sort()
1227 1208 gone = {}
1209 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1228 1210
1229 for f in all:
1211 for f in util.sort(modified + added + removed):
1230 1212 to = None
1231 1213 tn = None
1232 1214 dodiff = True
@@ -1237,18 +1219,16 b' def diff(repo, node1=None, node2=None, f'
1237 1219 tn = getfilectx(f, ctx2).data()
1238 1220 a, b = f, f
1239 1221 if opts.git:
1240 def gitmode(x, l):
1241 return l and '120000' or (x and '100755' or '100644')
1242 1222 def addmodehdr(header, omode, nmode):
1243 1223 if omode != nmode:
1244 1224 header.append('old mode %s\n' % omode)
1245 1225 header.append('new mode %s\n' % nmode)
1246 1226
1247 1227 if f in added:
1248 mode = gitmode(execf2(f), linkf2(f))
1228 mode = gitmode[ctx2.flags(f)]
1249 1229 if f in copy:
1250 1230 a = copy[f]
1251 omode = gitmode(man1.execf(a), man1.linkf(a))
1231 omode = gitmode[man1.flags(a)]
1252 1232 addmodehdr(header, omode, mode)
1253 1233 if a in removed and a not in gone:
1254 1234 op = 'rename'
@@ -1267,11 +1247,11 b' def diff(repo, node1=None, node2=None, f'
1267 1247 if f in copy and copy[f] in added and copy[copy[f]] == f:
1268 1248 dodiff = False
1269 1249 else:
1270 mode = gitmode(man1.execf(f), man1.linkf(f))
1271 header.append('deleted file mode %s\n' % mode)
1250 header.append('deleted file mode %s\n' %
1251 gitmode[man1.flags(f)])
1272 1252 else:
1273 omode = gitmode(man1.execf(f), man1.linkf(f))
1274 nmode = gitmode(execf2(f), linkf2(f))
1253 omode = gitmode[man1.flags(f)]
1254 nmode = gitmode[ctx2.flags(f)]
1275 1255 addmodehdr(header, omode, nmode)
1276 1256 if util.binary(to) or util.binary(tn):
1277 1257 dodiff = 'binary'
@@ -1297,7 +1277,7 b" def export(repo, revs, template='hg-%h.p"
1297 1277 revwidth = max([len(str(rev)) for rev in revs])
1298 1278
1299 1279 def single(rev, seqno, fp):
1300 ctx = repo.changectx(rev)
1280 ctx = repo[rev]
1301 1281 node = ctx.node()
1302 1282 parents = [p.node() for p in ctx.parents() if p]
1303 1283 branch = ctx.branch()
@@ -23,8 +23,8 b' def _collectfiles(repo, striprev):'
23 23 """find out the filelogs affected by the strip"""
24 24 files = {}
25 25
26 for x in xrange(striprev, repo.changelog.count()):
27 for name in repo.changectx(x).files():
26 for x in xrange(striprev, len(repo)):
27 for name in repo[x].files():
28 28 if name in files:
29 29 continue
30 30 files[name] = 1
@@ -37,7 +37,7 b' def _collectextranodes(repo, files, link'
37 37 """return the nodes that have to be saved before the strip"""
38 38 def collectone(revlog):
39 39 extra = []
40 startrev = count = revlog.count()
40 startrev = count = len(revlog)
41 41 # find the truncation point of the revlog
42 42 for i in xrange(0, count):
43 43 node = revlog.node(i)
@@ -72,7 +72,6 b' def _collectextranodes(repo, files, link'
72 72 def strip(ui, repo, node, backup="all"):
73 73 cl = repo.changelog
74 74 # TODO delete the undo files, and handle undo of merge sets
75 pp = cl.parents(node)
76 75 striprev = cl.rev(node)
77 76
78 77 # Some revisions with rev > striprev may not be descendants of striprev.
@@ -85,7 +84,7 b' def strip(ui, repo, node, backup="all"):'
85 84 tostrip = {striprev: 1}
86 85 saveheads = {}
87 86 savebases = []
88 for r in xrange(striprev + 1, cl.count()):
87 for r in xrange(striprev + 1, len(cl)):
89 88 parents = cl.parentrevs(r)
90 89 if parents[0] in tostrip or parents[1] in tostrip:
91 90 # r is a descendant of striprev
@@ -40,3 +40,9 b' class repository(object):'
40 40
41 41 def cancopy(self):
42 42 return self.local()
43
44 def rjoin(self, path):
45 url = self.url()
46 if url.endswith('/'):
47 return url + path
48 return url + '/' + path
@@ -13,13 +13,13 b' of the GNU General Public License, incor'
13 13 from node import bin, hex, nullid, nullrev, short
14 14 from i18n import _
15 15 import changegroup, errno, ancestor, mdiff
16 import sha, struct, util, zlib
16 import struct, util, zlib
17 17
18 18 _pack = struct.pack
19 19 _unpack = struct.unpack
20 20 _compress = zlib.compress
21 21 _decompress = zlib.decompress
22 _sha = sha.new
22 _sha = util.sha1
23 23
24 24 # revlog flags
25 25 REVLOGV0 = 0
@@ -32,13 +32,16 b' REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_'
32 32 class RevlogError(Exception):
33 33 pass
34 34
35 class LookupError(RevlogError):
35 class LookupError(RevlogError, KeyError):
36 36 def __init__(self, name, index, message):
37 37 self.name = name
38 38 if isinstance(name, str) and len(name) == 20:
39 39 name = short(name)
40 40 RevlogError.__init__(self, _('%s@%s: %s') % (index, name, message))
41 41
42 def __str__(self):
43 return RevlogError.__str__(self)
44
42 45 def getoffset(q):
43 46 return int(q >> 16)
44 47
@@ -512,9 +515,11 b' class revlog(object):'
512 515
513 516 def tip(self):
514 517 return self.node(len(self.index) - 2)
515 def count(self):
518 def __len__(self):
516 519 return len(self.index) - 1
517
520 def __iter__(self):
521 for i in xrange(len(self)):
522 yield i
518 523 def rev(self, node):
519 524 try:
520 525 return self.nodemap[node]
@@ -591,6 +596,27 b' class revlog(object):'
591 596 visit.append(p)
592 597 return reachable
593 598
599 def ancestors(self, *revs):
600 'Generate the ancestors of revs using a breadth-first visit'
601 visit = list(revs)
602 seen = util.set([nullrev])
603 while visit:
604 for parent in self.parentrevs(visit.pop(0)):
605 if parent not in seen:
606 visit.append(parent)
607 seen.add(parent)
608 yield parent
609
610 def descendants(self, *revs):
611 'Generate the descendants of revs in topological order'
612 seen = util.set(revs)
613 for i in xrange(min(revs) + 1, len(self)):
614 for x in self.parentrevs(i):
615 if x != nullrev and x in seen:
616 seen.add(i)
617 yield i
618 break
619
594 620 def nodesbetween(self, roots=None, heads=None):
595 621 """Return a tuple containing three elements. Elements 1 and 2 contain
596 622 a final list bases and heads after all the unreachable ones have been
@@ -617,12 +643,11 b' class revlog(object):'
617 643 lowestrev = nullrev
618 644 if (lowestrev == nullrev) and (heads is None):
619 645 # We want _all_ the nodes!
620 return ([self.node(r) for r in xrange(0, self.count())],
621 [nullid], list(self.heads()))
646 return ([self.node(r) for r in self], [nullid], list(self.heads()))
622 647 if heads is None:
623 648 # All nodes are ancestors, so the latest ancestor is the last
624 649 # node.
625 highestrev = self.count() - 1
650 highestrev = len(self) - 1
626 651 # Set ancestors to None to signal that every node is an ancestor.
627 652 ancestors = None
628 653 # Set heads to an empty dictionary for later discovery of heads
@@ -751,15 +776,15 b' class revlog(object):'
751 776 as if they had no children
752 777 """
753 778 if start is None and stop is None:
754 count = self.count()
779 count = len(self)
755 780 if not count:
756 781 return [nullid]
757 782 ishead = [1] * (count + 1)
758 783 index = self.index
759 for r in xrange(count):
784 for r in self:
760 785 e = index[r]
761 786 ishead[e[5]] = ishead[e[6]] = 0
762 return [self.node(r) for r in xrange(count) if ishead[r]]
787 return [self.node(r) for r in self if ishead[r]]
763 788
764 789 if start is None:
765 790 start = nullid
@@ -771,7 +796,7 b' class revlog(object):'
771 796 heads = {startrev: 1}
772 797
773 798 parentrevs = self.parentrevs
774 for r in xrange(startrev + 1, self.count()):
799 for r in xrange(startrev + 1, len(self)):
775 800 for p in parentrevs(r):
776 801 if p in reachable:
777 802 if r not in stoprevs:
@@ -786,7 +811,7 b' class revlog(object):'
786 811 """find the children of a given node"""
787 812 c = []
788 813 p = self.rev(node)
789 for r in range(p + 1, self.count()):
814 for r in range(p + 1, len(self)):
790 815 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
791 816 if prevs:
792 817 for pr in prevs:
@@ -815,8 +840,8 b' class revlog(object):'
815 840 if str(rev) != id:
816 841 raise ValueError
817 842 if rev < 0:
818 rev = self.count() + rev
819 if rev < 0 or rev >= self.count():
843 rev = len(self) + rev
844 if rev < 0 or rev >= len(self):
820 845 raise ValueError
821 846 return self.node(rev)
822 847 except (ValueError, OverflowError):
@@ -979,7 +1004,7 b' class revlog(object):'
979 1004 df = self.opener(self.datafile, 'w')
980 1005 try:
981 1006 calc = self._io.size
982 for r in xrange(self.count()):
1007 for r in self:
983 1008 start = self.start(r) + (r + 1) * calc
984 1009 length = self.length(r)
985 1010 fp.seek(start)
@@ -992,7 +1017,7 b' class revlog(object):'
992 1017 fp = self.opener(self.indexfile, 'w', atomictemp=True)
993 1018 self.version &= ~(REVLOGNGINLINEDATA)
994 1019 self._inline = False
995 for i in xrange(self.count()):
1020 for i in self:
996 1021 e = self._io.packentry(self.index[i], self.node, self.version, i)
997 1022 fp.write(e)
998 1023
@@ -1028,7 +1053,7 b' class revlog(object):'
1028 1053 if node in self.nodemap:
1029 1054 return node
1030 1055
1031 curr = self.count()
1056 curr = len(self)
1032 1057 prev = curr - 1
1033 1058 base = self.base(prev)
1034 1059 offset = self.end(prev)
@@ -1133,7 +1158,7 b' class revlog(object):'
1133 1158
1134 1159 yield changegroup.closechunk()
1135 1160
1136 def addgroup(self, revs, linkmapper, transaction, unique=0):
1161 def addgroup(self, revs, linkmapper, transaction):
1137 1162 """
1138 1163 add a delta group
1139 1164
@@ -1143,7 +1168,7 b' class revlog(object):'
1143 1168 """
1144 1169
1145 1170 #track the base of the current delta log
1146 r = self.count()
1171 r = len(self)
1147 1172 t = r - 1
1148 1173 node = None
1149 1174
@@ -1170,8 +1195,6 b' class revlog(object):'
1170 1195 link = linkmapper(cs)
1171 1196 if node in self.nodemap:
1172 1197 # this can happen if two branches make the same change
1173 # if unique:
1174 # raise RevlogError(_("already have %s") % hex(node[:4]))
1175 1198 chain = node
1176 1199 continue
1177 1200 delta = buffer(chunk, 80)
@@ -1264,13 +1287,13 b' class revlog(object):'
1264 1287 trust that the caller has saved the revisions that shouldn't be
1265 1288 removed and that it'll readd them after this truncation.
1266 1289 """
1267 if self.count() == 0:
1290 if len(self) == 0:
1268 1291 return
1269 1292
1270 1293 if isinstance(self.index, lazyindex):
1271 1294 self._loadindexmap()
1272 1295
1273 for rev in xrange(0, self.count()):
1296 for rev in self:
1274 1297 if self.index[rev][4] >= minlink:
1275 1298 break
1276 1299 else:
@@ -1291,15 +1314,15 b' class revlog(object):'
1291 1314 # then reset internal state in memory to forget those revisions
1292 1315 self._cache = None
1293 1316 self._chunkcache = None
1294 for x in xrange(rev, self.count()):
1317 for x in xrange(rev, len(self)):
1295 1318 del self.nodemap[self.node(x)]
1296 1319
1297 1320 del self.index[rev:-1]
1298 1321
1299 1322 def checksize(self):
1300 1323 expected = 0
1301 if self.count():
1302 expected = max(0, self.end(self.count() - 1))
1324 if len(self):
1325 expected = max(0, self.end(len(self) - 1))
1303 1326
1304 1327 try:
1305 1328 f = self.opener(self.datafile)
@@ -1320,10 +1343,10 b' class revlog(object):'
1320 1343 di = actual - (i * s)
1321 1344 if self._inline:
1322 1345 databytes = 0
1323 for r in xrange(self.count()):
1346 for r in self:
1324 1347 databytes += max(0, self.length(r))
1325 1348 dd = 0
1326 di = actual - self.count() * s - databytes
1349 di = actual - len(self) * s - databytes
1327 1350 except IOError, inst:
1328 1351 if inst.errno != errno.ENOENT:
1329 1352 raise
@@ -9,7 +9,7 b''
9 9
10 10 from i18n import _
11 11 import changelog, httprangereader
12 import repo, localrepo, manifest, util
12 import repo, localrepo, manifest, util, store
13 13 import urllib, urllib2, errno
14 14
15 15 class rangereader(httprangereader.httprangereader):
@@ -55,14 +55,13 b' class statichttprepository(localrepo.loc'
55 55
56 56 # setup store
57 57 if "store" in requirements:
58 self.encodefn = util.encodefilename
59 self.decodefn = util.decodefilename
60 58 self.spath = self.path + "/store"
61 59 else:
62 self.encodefn = lambda x: x
63 self.decodefn = lambda x: x
64 60 self.spath = self.path
65 self.sopener = util.encodedopener(opener(self.spath), self.encodefn)
61 self.encodefn = store.encodefn(requirements)
62 so = opener(self.spath)
63 self.sopener = lambda path, *args, **kw: so(
64 self.encodefn(path), *args, **kw)
66 65
67 66 self.manifest = manifest.manifest(self.sopener)
68 67 self.changelog = changelog.changelog(self.sopener)
@@ -5,41 +5,12 b''
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 import os, osutil, stat, util, lock
8 import util, lock
9 9
10 10 # if server supports streaming clone, it advertises "stream"
11 11 # capability with value that is version+flags of repo it is serving.
12 12 # client only streams if it can read that repo format.
13 13
14 def walkrepo(root):
15 '''iterate over metadata files in repository.
16 walk in natural (sorted) order.
17 yields 2-tuples: name of .d or .i file, size of file.'''
18
19 strip_count = len(root) + len(os.sep)
20 def walk(path, recurse):
21 for e, kind, st in osutil.listdir(path, stat=True):
22 pe = os.path.join(path, e)
23 if kind == stat.S_IFDIR:
24 if recurse:
25 for x in walk(pe, True):
26 yield x
27 else:
28 if kind != stat.S_IFREG or len(e) < 2:
29 continue
30 sfx = e[-2:]
31 if sfx in ('.d', '.i'):
32 yield pe[strip_count:], st.st_size
33 # write file data first
34 for x in walk(os.path.join(root, 'data'), True):
35 yield x
36 # write manifest before changelog
37 meta = list(walk(root, False))
38 meta.sort()
39 meta.reverse()
40 for x in meta:
41 yield x
42
43 14 # stream file format is simple.
44 15 #
45 16 # server writes out line that says how many files, how many total
@@ -60,28 +31,14 b' def stream_out(repo, fileobj, untrusted='
60 31 fileobj.write('1\n')
61 32 return
62 33
63 # get consistent snapshot of repo. lock during scan so lock not
64 # needed while we stream, and commits can happen.
65 repolock = None
66 34 try:
67 try:
68 repolock = repo.lock()
69 except (lock.LockHeld, lock.LockUnavailable), inst:
70 repo.ui.warn('locking the repository failed: %s\n' % (inst,))
71 fileobj.write('2\n')
72 return
35 entries, total_bytes = repo.storefiles()
36 except (lock.LockHeld, lock.LockUnavailable), inst:
37 repo.ui.warn('locking the repository failed: %s\n' % (inst,))
38 fileobj.write('2\n')
39 return
73 40
74 fileobj.write('0\n')
75 repo.ui.debug('scanning\n')
76 entries = []
77 total_bytes = 0
78 for name, size in walkrepo(repo.spath):
79 name = repo.decodefn(util.pconvert(name))
80 entries.append((name, size))
81 total_bytes += size
82 finally:
83 del repolock
84
41 fileobj.write('0\n')
85 42 repo.ui.debug('%d files, %d bytes to transfer\n' %
86 43 (len(entries), total_bytes))
87 44 fileobj.write('%d %d\n' % (len(entries), total_bytes))
@@ -122,6 +122,36 b' def xmlescape(text):'
122 122 .replace("'", '&#39;')) # &apos; invalid in HTML
123 123 return re.sub('[\x00-\x08\x0B\x0C\x0E-\x1F]', ' ', text)
124 124
125 _escapes = [
126 ('\\', '\\\\'), ('"', '\\"'), ('\t', '\\t'), ('\n', '\\n'),
127 ('\r', '\\r'), ('\f', '\\f'), ('\b', '\\b'),
128 ]
129
130 def json(obj):
131 if obj is None or obj is False or obj is True:
132 return {None: 'null', False: 'false', True: 'true'}[obj]
133 elif isinstance(obj, int) or isinstance(obj, float):
134 return str(obj)
135 elif isinstance(obj, str):
136 for k, v in _escapes:
137 obj = obj.replace(k, v)
138 return '"%s"' % obj
139 elif isinstance(obj, unicode):
140 return json(obj.encode('utf-8'))
141 elif hasattr(obj, 'keys'):
142 out = []
143 for k, v in obj.iteritems():
144 s = '%s: %s' % (json(k), json(v))
145 out.append(s)
146 return '{' + ', '.join(out) + '}'
147 elif hasattr(obj, '__iter__'):
148 out = []
149 for i in obj:
150 out.append(json(i))
151 return '[' + ', '.join(out) + ']'
152 else:
153 raise TypeError('cannot encode type %s' % obj.__class__.__name__)
154
125 155 filters = {
126 156 "addbreaks": nl2br,
127 157 "basename": os.path.basename,
@@ -150,5 +180,5 b' filters = {'
150 180 "user": lambda x: util.shortuser(x),
151 181 "stringescape": lambda x: x.encode('string_escape'),
152 182 "xmlescape": xmlescape,
153 }
154
183 "json": json,
184 }
@@ -81,18 +81,18 b' class templater(object):'
81 81 def __contains__(self, key):
82 82 return key in self.cache or key in self.map
83 83
84 def __call__(self, t, **map):
85 '''perform expansion.
86 t is name of map element to expand.
87 map is added elements to use during expansion.'''
84 def _template(self, t):
85 '''Get the template for the given template name. Use a local cache.'''
88 86 if not t in self.cache:
89 87 try:
90 88 self.cache[t] = file(self.map[t]).read()
91 89 except IOError, inst:
92 90 raise IOError(inst.args[0], _('template file %s: %s') %
93 91 (self.map[t], inst.args[1]))
94 tmpl = self.cache[t]
92 return self.cache[t]
95 93
94 def _process(self, tmpl, map):
95 '''Render a template. Returns a generator.'''
96 96 while tmpl:
97 97 m = self.template_re.search(tmpl)
98 98 if not m:
@@ -114,18 +114,39 b' class templater(object):'
114 114 v = v(**map)
115 115 if format:
116 116 if not hasattr(v, '__iter__'):
117 raise SyntaxError(_("Error expanding '%s%s'")
117 raise SyntaxError(_("Error expanding '%s%%%s'")
118 118 % (key, format))
119 119 lm = map.copy()
120 120 for i in v:
121 121 lm.update(i)
122 yield self(format, **lm)
122 t = self._template(format)
123 yield self._process(t, lm)
123 124 else:
124 125 if fl:
125 126 for f in fl.split("|")[1:]:
126 127 v = self.filters[f](v)
127 128 yield v
128 129
130 def __call__(self, t, **map):
131 '''Perform expansion. t is name of map element to expand. map contains
132 added elements for use during expansion. Is a generator.'''
133 tmpl = self._template(t)
134 iters = [self._process(tmpl, map)]
135 while iters:
136 try:
137 item = iters[0].next()
138 except StopIteration:
139 iters.pop(0)
140 continue
141 if isinstance(item, str):
142 yield item
143 elif item is None:
144 yield ''
145 elif hasattr(item, '__iter__'):
146 iters.insert(0, iter(item))
147 else:
148 yield str(item)
149
129 150 def templatepath(name=None):
130 151 '''return location of template file or directory (if no name).
131 152 returns None if not found.'''
@@ -96,9 +96,13 b' def rollback(opener, file):'
96 96 files = {}
97 97 for l in open(file).readlines():
98 98 f, o = l.split('\0')
99 files[f] = o
99 files[f] = int(o)
100 100 for f in files:
101 101 o = files[f]
102 opener(f, "a").truncate(int(o))
102 if o:
103 opener(f, "a").truncate(int(o))
104 else:
105 fn = opener(f).name
106 os.unlink(fn)
103 107 os.unlink(file)
104 108
@@ -312,15 +312,11 b' class ui(object):'
312 312 items = self._configitems(section, untrusted=untrusted, abort=True)
313 313 if self.debugflag and not untrusted and self.ucdata:
314 314 uitems = self._configitems(section, untrusted=True, abort=False)
315 keys = uitems.keys()
316 keys.sort()
317 for k in keys:
315 for k in util.sort(uitems):
318 316 if uitems[k] != items.get(k):
319 317 self.warn(_("Ignoring untrusted configuration option "
320 318 "%s.%s = %s\n") % (section, k, uitems[k]))
321 x = items.items()
322 x.sort()
323 return x
319 return util.sort(items.items())
324 320
325 321 def walkconfig(self, untrusted=False):
326 322 cdata = self._get_cdata(untrusted)
@@ -335,14 +331,16 b' class ui(object):'
335 331
336 332 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
337 333 and stop searching if one of these is set.
338 If not found, use ($LOGNAME or $USER or $LNAME or
339 $USERNAME) +"@full.hostname".
334 If not found and ui.askusername is True, ask the user, else use
335 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
340 336 """
341 337 user = os.environ.get("HGUSER")
342 338 if user is None:
343 339 user = self.config("ui", "username")
344 340 if user is None:
345 341 user = os.environ.get("EMAIL")
342 if user is None and self.configbool("ui", "askusername"):
343 user = self.prompt(_("Enter a commit username:"), default=None)
346 344 if user is None:
347 345 try:
348 346 user = '%s@%s' % (util.getuser(), socket.getfqdn())
@@ -15,7 +15,9 b' platform-specific details from the core.'
15 15 from i18n import _
16 16 import cStringIO, errno, getpass, re, shutil, sys, tempfile
17 17 import os, stat, threading, time, calendar, ConfigParser, locale, glob, osutil
18 import urlparse
18 import imp, urlparse
19
20 # Python compatibility
19 21
20 22 try:
21 23 set = set
@@ -23,6 +25,30 b' try:'
23 25 except NameError:
24 26 from sets import Set as set, ImmutableSet as frozenset
25 27
28 _md5 = None
29 def md5(s):
30 global _md5
31 if _md5 is None:
32 try:
33 import hashlib
34 _md5 = hashlib.md5
35 except ImportError:
36 import md5
37 _md5 = md5.md5
38 return _md5(s)
39
40 _sha1 = None
41 def sha1(s):
42 global _sha1
43 if _sha1 is None:
44 try:
45 import hashlib
46 _sha1 = hashlib.sha1
47 except ImportError:
48 import sha
49 _sha1 = sha.sha
50 return _sha1(s)
51
26 52 try:
27 53 _encoding = os.environ.get("HGENCODING")
28 54 if sys.platform == 'darwin' and not _encoding:
@@ -217,8 +243,8 b' def filter(s, cmd):'
217 243 return pipefilter(s, cmd)
218 244
219 245 def binary(s):
220 """return true if a string is binary data using diff's heuristic"""
221 if s and '\0' in s[:4096]:
246 """return true if a string is binary data"""
247 if s and '\0' in s:
222 248 return True
223 249 return False
224 250
@@ -226,6 +252,12 b' def unique(g):'
226 252 """return the uniq elements of iterable g"""
227 253 return dict.fromkeys(g).keys()
228 254
255 def sort(l):
256 if not isinstance(l, list):
257 l = list(l)
258 l.sort()
259 return l
260
229 261 class Abort(Exception):
230 262 """Raised if a command needs to print an error and exit."""
231 263
@@ -251,12 +283,12 b' def expand_glob(pats):'
251 283 ret.append(p)
252 284 return ret
253 285
254 def patkind(name, dflt_pat='glob'):
286 def patkind(name, default):
255 287 """Split a string into an optional pattern kind prefix and the
256 288 actual pattern."""
257 289 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
258 290 if name.startswith(prefix + ':'): return name.split(':', 1)
259 return dflt_pat, name
291 return default, name
260 292
261 293 def globre(pat, head='^', tail='$'):
262 294 "convert a glob pattern into a regexp"
@@ -386,17 +418,7 b' def canonpath(root, cwd, myname):'
386 418
387 419 raise Abort('%s not under root' % myname)
388 420
389 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None):
390 return _matcher(canonroot, cwd, names, inc, exc, 'glob', src)
391
392 def cmdmatcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None,
393 globbed=False, default=None):
394 default = default or 'relpath'
395 if default == 'relpath' and not globbed:
396 names = expand_glob(names)
397 return _matcher(canonroot, cwd, names, inc, exc, default, src)
398
399 def _matcher(canonroot, cwd, names, inc, exc, dflt_pat, src):
421 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None, dflt_pat='glob'):
400 422 """build a function to match a set of file patterns
401 423
402 424 arguments:
@@ -537,13 +559,29 b' def _matcher(canonroot, cwd, names, inc,'
537 559
538 560 _hgexecutable = None
539 561
562 def main_is_frozen():
563 """return True if we are a frozen executable.
564
565 The code supports py2exe (most common, Windows only) and tools/freeze
566 (portable, not much used).
567 """
568 return (hasattr(sys, "frozen") or # new py2exe
569 hasattr(sys, "importers") or # old py2exe
570 imp.is_frozen("__main__")) # tools/freeze
571
540 572 def hgexecutable():
541 573 """return location of the 'hg' executable.
542 574
543 575 Defaults to $HG or 'hg' in the search path.
544 576 """
545 577 if _hgexecutable is None:
546 set_hgexecutable(os.environ.get('HG') or find_exe('hg', 'hg'))
578 hg = os.environ.get('HG')
579 if hg:
580 set_hgexecutable(hg)
581 elif main_is_frozen():
582 set_hgexecutable(sys.executable)
583 else:
584 set_hgexecutable(find_exe('hg', 'hg'))
547 585 return _hgexecutable
548 586
549 587 def set_hgexecutable(path):
@@ -807,7 +845,7 b' def groupname(gid=None):'
807 845
808 846 # File system features
809 847
810 def checkfolding(path):
848 def checkcase(path):
811 849 """
812 850 Check whether the given path is on a case-sensitive filesystem
813 851
@@ -827,6 +865,53 b' def checkfolding(path):'
827 865 except:
828 866 return True
829 867
868 _fspathcache = {}
869 def fspath(name, root):
870 '''Get name in the case stored in the filesystem
871
872 The name is either relative to root, or it is an absolute path starting
873 with root. Note that this function is unnecessary, and should not be
874 called, for case-sensitive filesystems (simply because it's expensive).
875 '''
876 # If name is absolute, make it relative
877 if name.lower().startswith(root.lower()):
878 l = len(root)
879 if name[l] == os.sep or name[l] == os.altsep:
880 l = l + 1
881 name = name[l:]
882
883 if not os.path.exists(os.path.join(root, name)):
884 return None
885
886 seps = os.sep
887 if os.altsep:
888 seps = seps + os.altsep
889 # Protect backslashes. This gets silly very quickly.
890 seps.replace('\\','\\\\')
891 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
892 dir = os.path.normcase(os.path.normpath(root))
893 result = []
894 for part, sep in pattern.findall(name):
895 if sep:
896 result.append(sep)
897 continue
898
899 if dir not in _fspathcache:
900 _fspathcache[dir] = os.listdir(dir)
901 contents = _fspathcache[dir]
902
903 lpart = part.lower()
904 for n in contents:
905 if n.lower() == lpart:
906 result.append(n)
907 break
908 else:
909 # Cannot happen, as the file exists!
910 result.append(part)
911 dir = os.path.join(dir, lpart)
912
913 return ''.join(result)
914
830 915 def checkexec(path):
831 916 """
832 917 Check whether the given path is on a filesystem with UNIX-like exec flags
@@ -854,12 +939,6 b' def checkexec(path):'
854 939 return False
855 940 return not (new_file_has_exec or exec_flags_cannot_flip)
856 941
857 def execfunc(path, fallback):
858 '''return an is_exec() function with default to fallback'''
859 if checkexec(path):
860 return lambda x: is_exec(os.path.join(path, x))
861 return fallback
862
863 942 def checklink(path):
864 943 """check whether the given path is on a symlink-capable filesystem"""
865 944 # mktemp is not racy because symlink creation will fail if the
@@ -872,12 +951,6 b' def checklink(path):'
872 951 except (OSError, AttributeError):
873 952 return False
874 953
875 def linkfunc(path, fallback):
876 '''return an is_link() function with default to fallback'''
877 if checklink(path):
878 return lambda x: os.path.islink(os.path.join(path, x))
879 return fallback
880
881 954 _umask = os.umask(0)
882 955 os.umask(_umask)
883 956
@@ -1044,12 +1117,12 b" if os.name == 'nt':"
1044 1117 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
1045 1118 return '"' + cmd + '"'
1046 1119
1047 def popen(command):
1120 def popen(command, mode='r'):
1048 1121 # Work around "popen spawned process may not write to stdout
1049 1122 # under windows"
1050 1123 # http://bugs.python.org/issue1366
1051 1124 command += " 2> %s" % nulldev
1052 return os.popen(quotecommand(command))
1125 return os.popen(quotecommand(command), mode)
1053 1126
1054 1127 def explain_exit(code):
1055 1128 return _("exited with status %d") % code, code
@@ -1210,8 +1283,8 b' else:'
1210 1283 def quotecommand(cmd):
1211 1284 return cmd
1212 1285
1213 def popen(command):
1214 return os.popen(command)
1286 def popen(command, mode='r'):
1287 return os.popen(command, mode)
1215 1288
1216 1289 def testpid(pid):
1217 1290 '''return False if pid dead, True if running or not sure'''
@@ -1272,39 +1345,6 b' def find_exe(name, default=None):'
1272 1345 return name
1273 1346 return find_in_path(name, os.environ.get('PATH', ''), default=default)
1274 1347
1275 def _buildencodefun():
1276 e = '_'
1277 win_reserved = [ord(x) for x in '\\:*?"<>|']
1278 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
1279 for x in (range(32) + range(126, 256) + win_reserved):
1280 cmap[chr(x)] = "~%02x" % x
1281 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
1282 cmap[chr(x)] = e + chr(x).lower()
1283 dmap = {}
1284 for k, v in cmap.iteritems():
1285 dmap[v] = k
1286 def decode(s):
1287 i = 0
1288 while i < len(s):
1289 for l in xrange(1, 4):
1290 try:
1291 yield dmap[s[i:i+l]]
1292 i += l
1293 break
1294 except KeyError:
1295 pass
1296 else:
1297 raise KeyError
1298 return (lambda s: "".join([cmap[c] for c in s]),
1299 lambda s: "".join(list(decode(s))))
1300
1301 encodefilename, decodefilename = _buildencodefun()
1302
1303 def encodedopener(openerfn, fn):
1304 def o(path, *args, **kw):
1305 return openerfn(fn(path), *args, **kw)
1306 return o
1307
1308 1348 def mktempcopy(name, emptyok=False, createmode=None):
1309 1349 """Create a temporary file with the same contents from name
1310 1350
@@ -7,7 +7,7 b''
7 7
8 8 from node import nullid, short
9 9 from i18n import _
10 import revlog
10 import revlog, util
11 11
12 12 def verify(repo):
13 13 lock = repo.lock()
@@ -17,265 +17,201 b' def verify(repo):'
17 17 del lock
18 18
19 19 def _verify(repo):
20 mflinkrevs = {}
20 21 filelinkrevs = {}
21 22 filenodes = {}
22 changesets = revisions = files = 0
23 firstbad = [None]
23 revisions = 0
24 badrevs = {}
24 25 errors = [0]
25 26 warnings = [0]
26 neededmanifests = {}
27 ui = repo.ui
28 cl = repo.changelog
29 mf = repo.manifest
27 30
28 31 def err(linkrev, msg, filename=None):
29 32 if linkrev != None:
30 if firstbad[0] != None:
31 firstbad[0] = min(firstbad[0], linkrev)
32 else:
33 firstbad[0] = linkrev
33 badrevs[linkrev] = True
34 34 else:
35 linkrev = "?"
35 linkrev = '?'
36 36 msg = "%s: %s" % (linkrev, msg)
37 37 if filename:
38 38 msg = "%s@%s" % (filename, msg)
39 repo.ui.warn(" " + msg + "\n")
39 ui.warn(" " + msg + "\n")
40 40 errors[0] += 1
41 41
42 def exc(linkrev, msg, inst, filename=None):
43 if isinstance(inst, KeyboardInterrupt):
44 ui.warn(_("interrupted"))
45 raise
46 err(linkrev, "%s: %s" % (msg, inst), filename)
47
42 48 def warn(msg):
43 repo.ui.warn(msg + "\n")
49 ui.warn(msg + "\n")
44 50 warnings[0] += 1
45 51
46 def checksize(obj, name):
52 def checklog(obj, name):
53 if not len(obj) and (havecl or havemf):
54 err(0, _("empty or missing %s") % name)
55 return
56
47 57 d = obj.checksize()
48 58 if d[0]:
49 59 err(None, _("data length off by %d bytes") % d[0], name)
50 60 if d[1]:
51 61 err(None, _("index contains %d extra bytes") % d[1], name)
52 62
53 def checkversion(obj, name):
54 63 if obj.version != revlog.REVLOGV0:
55 64 if not revlogv1:
56 65 warn(_("warning: `%s' uses revlog format 1") % name)
57 66 elif revlogv1:
58 67 warn(_("warning: `%s' uses revlog format 0") % name)
59 68
60 revlogv1 = repo.changelog.version != revlog.REVLOGV0
61 if repo.ui.verbose or not revlogv1:
62 repo.ui.status(_("repository uses revlog format %d\n") %
69 def checkentry(obj, i, node, seen, linkrevs, f):
70 lr = obj.linkrev(node)
71 if lr < 0 or (havecl and lr not in linkrevs):
72 t = "unexpected"
73 if lr < 0 or lr >= len(cl):
74 t = "nonexistent"
75 err(None, _("rev %d point to %s changeset %d") % (i, t, lr), f)
76 if linkrevs:
77 warn(_(" (expected %s)") % " ".join(map(str,linkrevs)))
78 lr = None # can't be trusted
79
80 try:
81 p1, p2 = obj.parents(node)
82 if p1 not in seen and p1 != nullid:
83 err(lr, _("unknown parent 1 %s of %s") %
84 (short(p1), short(n)), f)
85 if p2 not in seen and p2 != nullid:
86 err(lr, _("unknown parent 2 %s of %s") %
87 (short(p2), short(p1)), f)
88 except Exception, inst:
89 exc(lr, _("checking parents of %s") % short(node), inst, f)
90
91 if node in seen:
92 err(lr, _("duplicate revision %d (%d)") % (i, seen[n]), f)
93 seen[n] = i
94 return lr
95
96 revlogv1 = cl.version != revlog.REVLOGV0
97 if ui.verbose or not revlogv1:
98 ui.status(_("repository uses revlog format %d\n") %
63 99 (revlogv1 and 1 or 0))
64 100
65 havecl = havemf = 1
66 seen = {}
67 repo.ui.status(_("checking changesets\n"))
68 if repo.changelog.count() == 0 and repo.manifest.count() > 1:
69 havecl = 0
70 err(0, _("empty or missing 00changelog.i"))
71 else:
72 checksize(repo.changelog, "changelog")
73
74 for i in xrange(repo.changelog.count()):
75 changesets += 1
76 n = repo.changelog.node(i)
77 l = repo.changelog.linkrev(n)
78 if l != i:
79 err(i, _("incorrect link (%d) for changeset") %(l))
80 if n in seen:
81 err(i, _("duplicates changeset at revision %d") % seen[n])
82 seen[n] = i
83
84 for p in repo.changelog.parents(n):
85 if p not in repo.changelog.nodemap:
86 err(i, _("changeset has unknown parent %s") % short(p))
87 try:
88 changes = repo.changelog.read(n)
89 except KeyboardInterrupt:
90 repo.ui.warn(_("interrupted"))
91 raise
92 except Exception, inst:
93 err(i, _("unpacking changeset: %s") % inst)
94 continue
101 havecl = len(cl) > 0
102 havemf = len(mf) > 0
95 103
96 if changes[0] not in neededmanifests:
97 neededmanifests[changes[0]] = i
98
99 for f in changes[3]:
100 filelinkrevs.setdefault(f, []).append(i)
101
104 ui.status(_("checking changesets\n"))
102 105 seen = {}
103 repo.ui.status(_("checking manifests\n"))
104 if repo.changelog.count() > 0 and repo.manifest.count() == 0:
105 havemf = 0
106 err(0, _("empty or missing 00manifest.i"))
107 else:
108 checkversion(repo.manifest, "manifest")
109 checksize(repo.manifest, "manifest")
110
111 for i in xrange(repo.manifest.count()):
112 n = repo.manifest.node(i)
113 l = repo.manifest.linkrev(n)
114
115 if l < 0 or (havecl and l >= repo.changelog.count()):
116 err(None, _("bad link (%d) at manifest revision %d") % (l, i))
117
118 if n in neededmanifests:
119 del neededmanifests[n]
120
121 if n in seen:
122 err(l, _("duplicates manifest from %d") % seen[n])
123
124 seen[n] = l
125
126 for p in repo.manifest.parents(n):
127 if p not in repo.manifest.nodemap:
128 err(l, _("manifest has unknown parent %s") % short(p))
106 checklog(cl, "changelog")
107 for i in repo:
108 n = cl.node(i)
109 checkentry(cl, i, n, seen, [i], "changelog")
129 110
130 111 try:
131 for f, fn in repo.manifest.readdelta(n).iteritems():
132 fns = filenodes.setdefault(f, {})
133 if fn not in fns:
134 fns[fn] = n
135 except KeyboardInterrupt:
136 repo.ui.warn(_("interrupted"))
137 raise
112 changes = cl.read(n)
113 mflinkrevs.setdefault(changes[0], []).append(i)
114 for f in changes[3]:
115 filelinkrevs.setdefault(f, []).append(i)
138 116 except Exception, inst:
139 err(l, _("reading manifest delta: %s") % inst)
140 continue
141
142 repo.ui.status(_("crosschecking files in changesets and manifests\n"))
117 exc(i, _("unpacking changeset %s") % short(n), inst)
143 118
144 if havemf > 0:
145 nm = [(c, m) for m, c in neededmanifests.items()]
146 nm.sort()
147 for c, m in nm:
148 err(c, _("changeset refers to unknown manifest %s") % short(m))
149 del neededmanifests, nm
119 ui.status(_("checking manifests\n"))
120 seen = {}
121 checklog(mf, "manifest")
122 for i in mf:
123 n = mf.node(i)
124 lr = checkentry(mf, i, n, seen, mflinkrevs.get(n, []), "manifest")
125 if n in mflinkrevs:
126 del mflinkrevs[n]
150 127
151 if havecl:
152 fl = filenodes.keys()
153 fl.sort()
154 for f in fl:
155 if f not in filelinkrevs:
156 lrs = [repo.manifest.linkrev(n) for n in filenodes[f]]
157 lrs.sort()
158 err(lrs[0], _("in manifest but not in changeset"), f)
159 del fl
128 try:
129 for f, fn in mf.readdelta(n).iteritems():
130 if not f:
131 err(lr, _("file without name in manifest"))
132 elif f != "/dev/null":
133 fns = filenodes.setdefault(f, {})
134 if fn not in fns:
135 fns[fn] = n
136 except Exception, inst:
137 exc(lr, _("reading manifest delta %s") % short(n), inst)
138
139 ui.status(_("crosschecking files in changesets and manifests\n"))
160 140
161 141 if havemf:
162 fl = filelinkrevs.keys()
163 fl.sort()
164 for f in fl:
142 for c, m in util.sort([(c, m) for m in mflinkrevs for c in mflinkrevs[m]]):
143 err(c, _("changeset refers to unknown manifest %s") % short(m))
144 del mflinkrevs
145
146 for f in util.sort(filelinkrevs):
165 147 if f not in filenodes:
166 148 lr = filelinkrevs[f][0]
167 149 err(lr, _("in changeset but not in manifest"), f)
168 del fl
169 150
170 repo.ui.status(_("checking files\n"))
171 ff = dict.fromkeys(filenodes.keys() + filelinkrevs.keys()).keys()
172 ff.sort()
173 for f in ff:
174 if f == "/dev/null":
175 continue
176 files += 1
177 if not f:
178 lr = filelinkrevs[f][0]
179 err(lr, _("file without name in manifest"))
180 continue
151 if havecl:
152 for f in util.sort(filenodes):
153 if f not in filelinkrevs:
154 try:
155 lr = min([repo.file(f).linkrev(n) for n in filenodes[f]])
156 except:
157 lr = None
158 err(lr, _("in manifest but not in changeset"), f)
159
160 ui.status(_("checking files\n"))
161 files = util.sort(util.unique(filenodes.keys() + filelinkrevs.keys()))
162 for f in files:
181 163 fl = repo.file(f)
182 checkversion(fl, f)
183 checksize(fl, f)
184
185 if fl.count() == 0:
186 err(filelinkrevs[f][0], _("empty or missing revlog"), f)
187 continue
188
164 checklog(fl, f)
189 165 seen = {}
190 nodes = {nullid: 1}
191 for i in xrange(fl.count()):
166 for i in fl:
192 167 revisions += 1
193 168 n = fl.node(i)
194 flr = fl.linkrev(n)
195
196 if flr < 0 or (havecl and flr not in filelinkrevs.get(f, [])):
197 if flr < 0 or flr >= repo.changelog.count():
198 err(None, _("rev %d point to nonexistent changeset %d")
199 % (i, flr), f)
200 else:
201 err(None, _("rev %d points to unexpected changeset %d")
202 % (i, flr), f)
203 if f in filelinkrevs:
204 warn(_(" (expected %s)") % filelinkrevs[f][0])
205 flr = None # can't be trusted
206 else:
207 if havecl:
208 filelinkrevs[f].remove(flr)
209
210 if n in seen:
211 err(flr, _("duplicate revision %d") % i, f)
169 lr = checkentry(fl, i, n, seen, filelinkrevs.get(f, []), f)
212 170 if f in filenodes:
213 171 if havemf and n not in filenodes[f]:
214 err(flr, _("%s not in manifests") % (short(n)), f)
172 err(lr, _("%s not in manifests") % (short(n)), f)
215 173 else:
216 174 del filenodes[f][n]
217 175
218 176 # verify contents
219 177 try:
220 178 t = fl.read(n)
221 except KeyboardInterrupt:
222 repo.ui.warn(_("interrupted"))
223 raise
179 rp = fl.renamed(n)
180 if len(t) != fl.size(i):
181 if not fl._readmeta(n): # ancient copy?
182 err(lr, _("unpacked size is %s, %s expected") %
183 (len(t), fl.size(i)), f)
224 184 except Exception, inst:
225 err(flr, _("unpacking %s: %s") % (short(n), inst), f)
226
227 # verify parents
228 try:
229 (p1, p2) = fl.parents(n)
230 if p1 not in nodes:
231 err(flr, _("unknown parent 1 %s of %s") %
232 (short(p1), short(n)), f)
233 if p2 not in nodes:
234 err(flr, _("unknown parent 2 %s of %s") %
235 (short(p2), short(p1)), f)
236 except KeyboardInterrupt:
237 repo.ui.warn(_("interrupted"))
238 raise
239 except Exception, inst:
240 err(flr, _("checking parents of %s: %s") % (short(n), inst), f)
241 nodes[n] = 1
185 exc(lr, _("unpacking %s") % short(n), inst, f)
242 186
243 187 # check renames
244 188 try:
245 rp = fl.renamed(n)
246 189 if rp:
247 190 fl2 = repo.file(rp[0])
248 if fl2.count() == 0:
249 err(flr, _("empty or missing copy source revlog %s:%s")
191 if not len(fl2):
192 err(lr, _("empty or missing copy source revlog %s:%s")
250 193 % (rp[0], short(rp[1])), f)
251 194 elif rp[1] == nullid:
252 err(flr, _("copy source revision is nullid %s:%s")
195 err(lr, _("copy source revision is nullid %s:%s")
253 196 % (rp[0], short(rp[1])), f)
254 197 else:
255 198 rev = fl2.rev(rp[1])
256 except KeyboardInterrupt:
257 repo.ui.warn(_("interrupted"))
258 raise
259 199 except Exception, inst:
260 err(flr, _("checking rename of %s: %s") %
261 (short(n), inst), f)
200 exc(lr, _("checking rename of %s") % short(n), inst, f)
262 201
263 202 # cross-check
264 203 if f in filenodes:
265 fns = [(repo.manifest.linkrev(filenodes[f][n]), n)
266 for n in filenodes[f]]
267 fns.sort()
268 for lr, node in fns:
204 fns = [(mf.linkrev(l), n) for n,l in filenodes[f].items()]
205 for lr, node in util.sort(fns):
269 206 err(lr, _("%s in manifests not found") % short(node), f)
270 207
271 repo.ui.status(_("%d files, %d changesets, %d total revisions\n") %
272 (files, changesets, revisions))
273
208 ui.status(_("%d files, %d changesets, %d total revisions\n") %
209 (len(files), len(cl), revisions))
274 210 if warnings[0]:
275 repo.ui.warn(_("%d warnings encountered!\n") % warnings[0])
211 ui.warn(_("%d warnings encountered!\n") % warnings[0])
276 212 if errors[0]:
277 repo.ui.warn(_("%d integrity errors encountered!\n") % errors[0])
278 if firstbad[0]:
279 repo.ui.warn(_("(first damaged changeset appears to be %d)\n")
280 % firstbad[0])
213 ui.warn(_("%d integrity errors encountered!\n") % errors[0])
214 if badrevs:
215 ui.warn(_("(first damaged changeset appears to be %d)\n")
216 % min(badrevs))
281 217 return 1
@@ -19,6 +19,9 b' from distutils.ccompiler import new_comp'
19 19 import mercurial.version
20 20
21 21 extra = {}
22 scripts = ['hg']
23 if os.name == 'nt':
24 scripts.append('contrib/win32/hg.bat')
22 25
23 26 # simplified version of distutils.ccompiler.CCompiler.has_function
24 27 # that actually removes its temporary files.
@@ -88,10 +91,11 b' mercurial.version.remember_version(versi'
88 91 cmdclass = {'install_data': install_package_data}
89 92
90 93 ext_modules=[
91 Extension('mercurial.mpatch', ['mercurial/mpatch.c']),
94 Extension('mercurial.base85', ['mercurial/base85.c']),
92 95 Extension('mercurial.bdiff', ['mercurial/bdiff.c']),
93 Extension('mercurial.base85', ['mercurial/base85.c']),
94 Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c'])
96 Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c']),
97 Extension('mercurial.mpatch', ['mercurial/mpatch.c']),
98 Extension('mercurial.parsers', ['mercurial/parsers.c']),
95 99 ]
96 100
97 101 packages = ['mercurial', 'mercurial.hgweb', 'hgext', 'hgext.convert']
@@ -118,7 +122,7 b" setup(name='mercurial',"
118 122 url='http://selenic.com/mercurial',
119 123 description='Scalable distributed SCM',
120 124 license='GNU GPL',
121 scripts=['hg'],
125 scripts=scripts,
122 126 packages=packages,
123 127 ext_modules=ext_modules,
124 128 data_files=[(os.path.join('mercurial', root),
@@ -9,6 +9,7 b''
9 9
10 10 <div class="buttons">
11 11 <a href="#url#shortlog/#rev#{sessionvars%urlparameter}">shortlog</a>
12 <a href="#url#graph{sessionvars%urlparameter}">graph</a>
12 13 <a href="#url#tags{sessionvars%urlparameter}">tags</a>
13 14 <a href="#url#file/#node|short#{sessionvars%urlparameter}">files</a>
14 15 #archives%archiveentry#
@@ -6,6 +6,7 b''
6 6 <div class="buttons">
7 7 <a href="#url#log/#rev#{sessionvars%urlparameter}">changelog</a>
8 8 <a href="#url#shortlog/#rev#{sessionvars%urlparameter}">shortlog</a>
9 <a href="#url#graph{sessionvars%urlparameter}">graph</a>
9 10 <a href="#url#tags{sessionvars%urlparameter}">tags</a>
10 11 <a href="#url#file/#node|short#{sessionvars%urlparameter}">files</a>
11 12 <a href="#url#raw-rev/#node|short#">raw</a>
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file, binary diff hidden
1 NO CONTENT: modified file, binary diff hidden
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file, binary diff hidden
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file chmod 100755 => 100644
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file copied from tests/test-push-http to tests/test-pull-http
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now