##// END OF EJS Templates
cleanup: use the deque type where appropriate...
Bryan O'Sullivan -
r16803:107a3270 default
parent child Browse files
Show More
@@ -1,258 +1,258 b''
1 # changelog bisection for mercurial
1 # changelog bisection for mercurial
2 #
2 #
3 # Copyright 2007 Matt Mackall
3 # Copyright 2007 Matt Mackall
4 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
4 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
5 #
5 #
6 # Inspired by git bisect, extension skeleton taken from mq.py.
6 # Inspired by git bisect, extension skeleton taken from mq.py.
7 #
7 #
8 # This software may be used and distributed according to the terms of the
8 # This software may be used and distributed according to the terms of the
9 # GNU General Public License version 2 or any later version.
9 # GNU General Public License version 2 or any later version.
10
10
11 import os, error
11 import os, error
12 from i18n import _
12 from i18n import _
13 from node import short, hex
13 from node import short, hex
14 import util
14 import collections, util
15
15
16 def bisect(changelog, state):
16 def bisect(changelog, state):
17 """find the next node (if any) for testing during a bisect search.
17 """find the next node (if any) for testing during a bisect search.
18 returns a (nodes, number, good) tuple.
18 returns a (nodes, number, good) tuple.
19
19
20 'nodes' is the final result of the bisect if 'number' is 0.
20 'nodes' is the final result of the bisect if 'number' is 0.
21 Otherwise 'number' indicates the remaining possible candidates for
21 Otherwise 'number' indicates the remaining possible candidates for
22 the search and 'nodes' contains the next bisect target.
22 the search and 'nodes' contains the next bisect target.
23 'good' is True if bisect is searching for a first good changeset, False
23 'good' is True if bisect is searching for a first good changeset, False
24 if searching for a first bad one.
24 if searching for a first bad one.
25 """
25 """
26
26
27 clparents = changelog.parentrevs
27 clparents = changelog.parentrevs
28 skip = set([changelog.rev(n) for n in state['skip']])
28 skip = set([changelog.rev(n) for n in state['skip']])
29
29
30 def buildancestors(bad, good):
30 def buildancestors(bad, good):
31 # only the earliest bad revision matters
31 # only the earliest bad revision matters
32 badrev = min([changelog.rev(n) for n in bad])
32 badrev = min([changelog.rev(n) for n in bad])
33 goodrevs = [changelog.rev(n) for n in good]
33 goodrevs = [changelog.rev(n) for n in good]
34 goodrev = min(goodrevs)
34 goodrev = min(goodrevs)
35 # build visit array
35 # build visit array
36 ancestors = [None] * (len(changelog) + 1) # an extra for [-1]
36 ancestors = [None] * (len(changelog) + 1) # an extra for [-1]
37
37
38 # set nodes descended from goodrevs
38 # set nodes descended from goodrevs
39 for rev in goodrevs:
39 for rev in goodrevs:
40 ancestors[rev] = []
40 ancestors[rev] = []
41 for rev in xrange(goodrev + 1, len(changelog)):
41 for rev in xrange(goodrev + 1, len(changelog)):
42 for prev in clparents(rev):
42 for prev in clparents(rev):
43 if ancestors[prev] == []:
43 if ancestors[prev] == []:
44 ancestors[rev] = []
44 ancestors[rev] = []
45
45
46 # clear good revs from array
46 # clear good revs from array
47 for rev in goodrevs:
47 for rev in goodrevs:
48 ancestors[rev] = None
48 ancestors[rev] = None
49 for rev in xrange(len(changelog), goodrev, -1):
49 for rev in xrange(len(changelog), goodrev, -1):
50 if ancestors[rev] is None:
50 if ancestors[rev] is None:
51 for prev in clparents(rev):
51 for prev in clparents(rev):
52 ancestors[prev] = None
52 ancestors[prev] = None
53
53
54 if ancestors[badrev] is None:
54 if ancestors[badrev] is None:
55 return badrev, None
55 return badrev, None
56 return badrev, ancestors
56 return badrev, ancestors
57
57
58 good = False
58 good = False
59 badrev, ancestors = buildancestors(state['bad'], state['good'])
59 badrev, ancestors = buildancestors(state['bad'], state['good'])
60 if not ancestors: # looking for bad to good transition?
60 if not ancestors: # looking for bad to good transition?
61 good = True
61 good = True
62 badrev, ancestors = buildancestors(state['good'], state['bad'])
62 badrev, ancestors = buildancestors(state['good'], state['bad'])
63 bad = changelog.node(badrev)
63 bad = changelog.node(badrev)
64 if not ancestors: # now we're confused
64 if not ancestors: # now we're confused
65 if len(state['bad']) == 1 and len(state['good']) == 1:
65 if len(state['bad']) == 1 and len(state['good']) == 1:
66 raise util.Abort(_("starting revisions are not directly related"))
66 raise util.Abort(_("starting revisions are not directly related"))
67 raise util.Abort(_("inconsistent state, %s:%s is good and bad")
67 raise util.Abort(_("inconsistent state, %s:%s is good and bad")
68 % (badrev, short(bad)))
68 % (badrev, short(bad)))
69
69
70 # build children dict
70 # build children dict
71 children = {}
71 children = {}
72 visit = [badrev]
72 visit = collections.deque([badrev])
73 candidates = []
73 candidates = []
74 while visit:
74 while visit:
75 rev = visit.pop(0)
75 rev = visit.popleft()
76 if ancestors[rev] == []:
76 if ancestors[rev] == []:
77 candidates.append(rev)
77 candidates.append(rev)
78 for prev in clparents(rev):
78 for prev in clparents(rev):
79 if prev != -1:
79 if prev != -1:
80 if prev in children:
80 if prev in children:
81 children[prev].append(rev)
81 children[prev].append(rev)
82 else:
82 else:
83 children[prev] = [rev]
83 children[prev] = [rev]
84 visit.append(prev)
84 visit.append(prev)
85
85
86 candidates.sort()
86 candidates.sort()
87 # have we narrowed it down to one entry?
87 # have we narrowed it down to one entry?
88 # or have all other possible candidates besides 'bad' have been skipped?
88 # or have all other possible candidates besides 'bad' have been skipped?
89 tot = len(candidates)
89 tot = len(candidates)
90 unskipped = [c for c in candidates if (c not in skip) and (c != badrev)]
90 unskipped = [c for c in candidates if (c not in skip) and (c != badrev)]
91 if tot == 1 or not unskipped:
91 if tot == 1 or not unskipped:
92 return ([changelog.node(rev) for rev in candidates], 0, good)
92 return ([changelog.node(rev) for rev in candidates], 0, good)
93 perfect = tot // 2
93 perfect = tot // 2
94
94
95 # find the best node to test
95 # find the best node to test
96 best_rev = None
96 best_rev = None
97 best_len = -1
97 best_len = -1
98 poison = set()
98 poison = set()
99 for rev in candidates:
99 for rev in candidates:
100 if rev in poison:
100 if rev in poison:
101 # poison children
101 # poison children
102 poison.update(children.get(rev, []))
102 poison.update(children.get(rev, []))
103 continue
103 continue
104
104
105 a = ancestors[rev] or [rev]
105 a = ancestors[rev] or [rev]
106 ancestors[rev] = None
106 ancestors[rev] = None
107
107
108 x = len(a) # number of ancestors
108 x = len(a) # number of ancestors
109 y = tot - x # number of non-ancestors
109 y = tot - x # number of non-ancestors
110 value = min(x, y) # how good is this test?
110 value = min(x, y) # how good is this test?
111 if value > best_len and rev not in skip:
111 if value > best_len and rev not in skip:
112 best_len = value
112 best_len = value
113 best_rev = rev
113 best_rev = rev
114 if value == perfect: # found a perfect candidate? quit early
114 if value == perfect: # found a perfect candidate? quit early
115 break
115 break
116
116
117 if y < perfect and rev not in skip: # all downhill from here?
117 if y < perfect and rev not in skip: # all downhill from here?
118 # poison children
118 # poison children
119 poison.update(children.get(rev, []))
119 poison.update(children.get(rev, []))
120 continue
120 continue
121
121
122 for c in children.get(rev, []):
122 for c in children.get(rev, []):
123 if ancestors[c]:
123 if ancestors[c]:
124 ancestors[c] = list(set(ancestors[c] + a))
124 ancestors[c] = list(set(ancestors[c] + a))
125 else:
125 else:
126 ancestors[c] = a + [c]
126 ancestors[c] = a + [c]
127
127
128 assert best_rev is not None
128 assert best_rev is not None
129 best_node = changelog.node(best_rev)
129 best_node = changelog.node(best_rev)
130
130
131 return ([best_node], tot, good)
131 return ([best_node], tot, good)
132
132
133
133
134 def load_state(repo):
134 def load_state(repo):
135 state = {'current': [], 'good': [], 'bad': [], 'skip': []}
135 state = {'current': [], 'good': [], 'bad': [], 'skip': []}
136 if os.path.exists(repo.join("bisect.state")):
136 if os.path.exists(repo.join("bisect.state")):
137 for l in repo.opener("bisect.state"):
137 for l in repo.opener("bisect.state"):
138 kind, node = l[:-1].split()
138 kind, node = l[:-1].split()
139 node = repo.lookup(node)
139 node = repo.lookup(node)
140 if kind not in state:
140 if kind not in state:
141 raise util.Abort(_("unknown bisect kind %s") % kind)
141 raise util.Abort(_("unknown bisect kind %s") % kind)
142 state[kind].append(node)
142 state[kind].append(node)
143 return state
143 return state
144
144
145
145
146 def save_state(repo, state):
146 def save_state(repo, state):
147 f = repo.opener("bisect.state", "w", atomictemp=True)
147 f = repo.opener("bisect.state", "w", atomictemp=True)
148 wlock = repo.wlock()
148 wlock = repo.wlock()
149 try:
149 try:
150 for kind in state:
150 for kind in state:
151 for node in state[kind]:
151 for node in state[kind]:
152 f.write("%s %s\n" % (kind, hex(node)))
152 f.write("%s %s\n" % (kind, hex(node)))
153 f.close()
153 f.close()
154 finally:
154 finally:
155 wlock.release()
155 wlock.release()
156
156
157 def get(repo, status):
157 def get(repo, status):
158 """
158 """
159 Return a list of revision(s) that match the given status:
159 Return a list of revision(s) that match the given status:
160
160
161 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
161 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
162 - ``goods``, ``bads`` : csets topologicaly good/bad
162 - ``goods``, ``bads`` : csets topologicaly good/bad
163 - ``range`` : csets taking part in the bisection
163 - ``range`` : csets taking part in the bisection
164 - ``pruned`` : csets that are goods, bads or skipped
164 - ``pruned`` : csets that are goods, bads or skipped
165 - ``untested`` : csets whose fate is yet unknown
165 - ``untested`` : csets whose fate is yet unknown
166 - ``ignored`` : csets ignored due to DAG topology
166 - ``ignored`` : csets ignored due to DAG topology
167 - ``current`` : the cset currently being bisected
167 - ``current`` : the cset currently being bisected
168 """
168 """
169 state = load_state(repo)
169 state = load_state(repo)
170 if status in ('good', 'bad', 'skip', 'current'):
170 if status in ('good', 'bad', 'skip', 'current'):
171 return map(repo.changelog.rev, state[status])
171 return map(repo.changelog.rev, state[status])
172 else:
172 else:
173 # In the floowing sets, we do *not* call 'bisect()' with more
173 # In the floowing sets, we do *not* call 'bisect()' with more
174 # than one level of recusrsion, because that can be very, very
174 # than one level of recusrsion, because that can be very, very
175 # time consuming. Instead, we always develop the expression as
175 # time consuming. Instead, we always develop the expression as
176 # much as possible.
176 # much as possible.
177
177
178 # 'range' is all csets that make the bisection:
178 # 'range' is all csets that make the bisection:
179 # - have a good ancestor and a bad descendant, or conversely
179 # - have a good ancestor and a bad descendant, or conversely
180 # that's because the bisection can go either way
180 # that's because the bisection can go either way
181 range = '( bisect(bad)::bisect(good) | bisect(good)::bisect(bad) )'
181 range = '( bisect(bad)::bisect(good) | bisect(good)::bisect(bad) )'
182
182
183 _t = repo.revs('bisect(good)::bisect(bad)')
183 _t = repo.revs('bisect(good)::bisect(bad)')
184 # The sets of topologically good or bad csets
184 # The sets of topologically good or bad csets
185 if len(_t) == 0:
185 if len(_t) == 0:
186 # Goods are topologically after bads
186 # Goods are topologically after bads
187 goods = 'bisect(good)::' # Pruned good csets
187 goods = 'bisect(good)::' # Pruned good csets
188 bads = '::bisect(bad)' # Pruned bad csets
188 bads = '::bisect(bad)' # Pruned bad csets
189 else:
189 else:
190 # Goods are topologically before bads
190 # Goods are topologically before bads
191 goods = '::bisect(good)' # Pruned good csets
191 goods = '::bisect(good)' # Pruned good csets
192 bads = 'bisect(bad)::' # Pruned bad csets
192 bads = 'bisect(bad)::' # Pruned bad csets
193
193
194 # 'pruned' is all csets whose fate is already known: good, bad, skip
194 # 'pruned' is all csets whose fate is already known: good, bad, skip
195 skips = 'bisect(skip)' # Pruned skipped csets
195 skips = 'bisect(skip)' # Pruned skipped csets
196 pruned = '( (%s) | (%s) | (%s) )' % (goods, bads, skips)
196 pruned = '( (%s) | (%s) | (%s) )' % (goods, bads, skips)
197
197
198 # 'untested' is all cset that are- in 'range', but not in 'pruned'
198 # 'untested' is all cset that are- in 'range', but not in 'pruned'
199 untested = '( (%s) - (%s) )' % (range, pruned)
199 untested = '( (%s) - (%s) )' % (range, pruned)
200
200
201 # 'ignored' is all csets that were not used during the bisection
201 # 'ignored' is all csets that were not used during the bisection
202 # due to DAG topology, but may however have had an impact.
202 # due to DAG topology, but may however have had an impact.
203 # Eg., a branch merged between bads and goods, but whose branch-
203 # Eg., a branch merged between bads and goods, but whose branch-
204 # point is out-side of the range.
204 # point is out-side of the range.
205 iba = '::bisect(bad) - ::bisect(good)' # Ignored bads' ancestors
205 iba = '::bisect(bad) - ::bisect(good)' # Ignored bads' ancestors
206 iga = '::bisect(good) - ::bisect(bad)' # Ignored goods' ancestors
206 iga = '::bisect(good) - ::bisect(bad)' # Ignored goods' ancestors
207 ignored = '( ( (%s) | (%s) ) - (%s) )' % (iba, iga, range)
207 ignored = '( ( (%s) | (%s) ) - (%s) )' % (iba, iga, range)
208
208
209 if status == 'range':
209 if status == 'range':
210 return repo.revs(range)
210 return repo.revs(range)
211 elif status == 'pruned':
211 elif status == 'pruned':
212 return repo.revs(pruned)
212 return repo.revs(pruned)
213 elif status == 'untested':
213 elif status == 'untested':
214 return repo.revs(untested)
214 return repo.revs(untested)
215 elif status == 'ignored':
215 elif status == 'ignored':
216 return repo.revs(ignored)
216 return repo.revs(ignored)
217 elif status == "goods":
217 elif status == "goods":
218 return repo.revs(goods)
218 return repo.revs(goods)
219 elif status == "bads":
219 elif status == "bads":
220 return repo.revs(bads)
220 return repo.revs(bads)
221 else:
221 else:
222 raise error.ParseError(_('invalid bisect state'))
222 raise error.ParseError(_('invalid bisect state'))
223
223
224 def label(repo, node):
224 def label(repo, node):
225 rev = repo.changelog.rev(node)
225 rev = repo.changelog.rev(node)
226
226
227 # Try explicit sets
227 # Try explicit sets
228 if rev in get(repo, 'good'):
228 if rev in get(repo, 'good'):
229 # i18n: bisect changeset status
229 # i18n: bisect changeset status
230 return _('good')
230 return _('good')
231 if rev in get(repo, 'bad'):
231 if rev in get(repo, 'bad'):
232 # i18n: bisect changeset status
232 # i18n: bisect changeset status
233 return _('bad')
233 return _('bad')
234 if rev in get(repo, 'skip'):
234 if rev in get(repo, 'skip'):
235 # i18n: bisect changeset status
235 # i18n: bisect changeset status
236 return _('skipped')
236 return _('skipped')
237 if rev in get(repo, 'untested') or rev in get(repo, 'current'):
237 if rev in get(repo, 'untested') or rev in get(repo, 'current'):
238 # i18n: bisect changeset status
238 # i18n: bisect changeset status
239 return _('untested')
239 return _('untested')
240 if rev in get(repo, 'ignored'):
240 if rev in get(repo, 'ignored'):
241 # i18n: bisect changeset status
241 # i18n: bisect changeset status
242 return _('ignored')
242 return _('ignored')
243
243
244 # Try implicit sets
244 # Try implicit sets
245 if rev in get(repo, 'goods'):
245 if rev in get(repo, 'goods'):
246 # i18n: bisect changeset status
246 # i18n: bisect changeset status
247 return _('good (implicit)')
247 return _('good (implicit)')
248 if rev in get(repo, 'bads'):
248 if rev in get(repo, 'bads'):
249 # i18n: bisect changeset status
249 # i18n: bisect changeset status
250 return _('bad (implicit)')
250 return _('bad (implicit)')
251
251
252 return None
252 return None
253
253
254 def shortlabel(label):
254 def shortlabel(label):
255 if label:
255 if label:
256 return label[0].upper()
256 return label[0].upper()
257
257
258 return None
258 return None
@@ -1,1890 +1,1890 b''
1 # patch.py - patch file parsing routines
1 # patch.py - patch file parsing routines
2 #
2 #
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import cStringIO, email.Parser, os, errno, re
9 import cStringIO, email.Parser, os, errno, re
10 import tempfile, zlib, shutil
10 import tempfile, zlib, shutil
11
11
12 from i18n import _
12 from i18n import _
13 from node import hex, nullid, short
13 from node import hex, nullid, short
14 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
14 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
15 import context
15 import collections, context
16
16
17 gitre = re.compile('diff --git a/(.*) b/(.*)')
17 gitre = re.compile('diff --git a/(.*) b/(.*)')
18
18
19 class PatchError(Exception):
19 class PatchError(Exception):
20 pass
20 pass
21
21
22
22
23 # public functions
23 # public functions
24
24
25 def split(stream):
25 def split(stream):
26 '''return an iterator of individual patches from a stream'''
26 '''return an iterator of individual patches from a stream'''
27 def isheader(line, inheader):
27 def isheader(line, inheader):
28 if inheader and line[0] in (' ', '\t'):
28 if inheader and line[0] in (' ', '\t'):
29 # continuation
29 # continuation
30 return True
30 return True
31 if line[0] in (' ', '-', '+'):
31 if line[0] in (' ', '-', '+'):
32 # diff line - don't check for header pattern in there
32 # diff line - don't check for header pattern in there
33 return False
33 return False
34 l = line.split(': ', 1)
34 l = line.split(': ', 1)
35 return len(l) == 2 and ' ' not in l[0]
35 return len(l) == 2 and ' ' not in l[0]
36
36
37 def chunk(lines):
37 def chunk(lines):
38 return cStringIO.StringIO(''.join(lines))
38 return cStringIO.StringIO(''.join(lines))
39
39
40 def hgsplit(stream, cur):
40 def hgsplit(stream, cur):
41 inheader = True
41 inheader = True
42
42
43 for line in stream:
43 for line in stream:
44 if not line.strip():
44 if not line.strip():
45 inheader = False
45 inheader = False
46 if not inheader and line.startswith('# HG changeset patch'):
46 if not inheader and line.startswith('# HG changeset patch'):
47 yield chunk(cur)
47 yield chunk(cur)
48 cur = []
48 cur = []
49 inheader = True
49 inheader = True
50
50
51 cur.append(line)
51 cur.append(line)
52
52
53 if cur:
53 if cur:
54 yield chunk(cur)
54 yield chunk(cur)
55
55
56 def mboxsplit(stream, cur):
56 def mboxsplit(stream, cur):
57 for line in stream:
57 for line in stream:
58 if line.startswith('From '):
58 if line.startswith('From '):
59 for c in split(chunk(cur[1:])):
59 for c in split(chunk(cur[1:])):
60 yield c
60 yield c
61 cur = []
61 cur = []
62
62
63 cur.append(line)
63 cur.append(line)
64
64
65 if cur:
65 if cur:
66 for c in split(chunk(cur[1:])):
66 for c in split(chunk(cur[1:])):
67 yield c
67 yield c
68
68
69 def mimesplit(stream, cur):
69 def mimesplit(stream, cur):
70 def msgfp(m):
70 def msgfp(m):
71 fp = cStringIO.StringIO()
71 fp = cStringIO.StringIO()
72 g = email.Generator.Generator(fp, mangle_from_=False)
72 g = email.Generator.Generator(fp, mangle_from_=False)
73 g.flatten(m)
73 g.flatten(m)
74 fp.seek(0)
74 fp.seek(0)
75 return fp
75 return fp
76
76
77 for line in stream:
77 for line in stream:
78 cur.append(line)
78 cur.append(line)
79 c = chunk(cur)
79 c = chunk(cur)
80
80
81 m = email.Parser.Parser().parse(c)
81 m = email.Parser.Parser().parse(c)
82 if not m.is_multipart():
82 if not m.is_multipart():
83 yield msgfp(m)
83 yield msgfp(m)
84 else:
84 else:
85 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
85 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
86 for part in m.walk():
86 for part in m.walk():
87 ct = part.get_content_type()
87 ct = part.get_content_type()
88 if ct not in ok_types:
88 if ct not in ok_types:
89 continue
89 continue
90 yield msgfp(part)
90 yield msgfp(part)
91
91
92 def headersplit(stream, cur):
92 def headersplit(stream, cur):
93 inheader = False
93 inheader = False
94
94
95 for line in stream:
95 for line in stream:
96 if not inheader and isheader(line, inheader):
96 if not inheader and isheader(line, inheader):
97 yield chunk(cur)
97 yield chunk(cur)
98 cur = []
98 cur = []
99 inheader = True
99 inheader = True
100 if inheader and not isheader(line, inheader):
100 if inheader and not isheader(line, inheader):
101 inheader = False
101 inheader = False
102
102
103 cur.append(line)
103 cur.append(line)
104
104
105 if cur:
105 if cur:
106 yield chunk(cur)
106 yield chunk(cur)
107
107
108 def remainder(cur):
108 def remainder(cur):
109 yield chunk(cur)
109 yield chunk(cur)
110
110
111 class fiter(object):
111 class fiter(object):
112 def __init__(self, fp):
112 def __init__(self, fp):
113 self.fp = fp
113 self.fp = fp
114
114
115 def __iter__(self):
115 def __iter__(self):
116 return self
116 return self
117
117
118 def next(self):
118 def next(self):
119 l = self.fp.readline()
119 l = self.fp.readline()
120 if not l:
120 if not l:
121 raise StopIteration
121 raise StopIteration
122 return l
122 return l
123
123
124 inheader = False
124 inheader = False
125 cur = []
125 cur = []
126
126
127 mimeheaders = ['content-type']
127 mimeheaders = ['content-type']
128
128
129 if not util.safehasattr(stream, 'next'):
129 if not util.safehasattr(stream, 'next'):
130 # http responses, for example, have readline but not next
130 # http responses, for example, have readline but not next
131 stream = fiter(stream)
131 stream = fiter(stream)
132
132
133 for line in stream:
133 for line in stream:
134 cur.append(line)
134 cur.append(line)
135 if line.startswith('# HG changeset patch'):
135 if line.startswith('# HG changeset patch'):
136 return hgsplit(stream, cur)
136 return hgsplit(stream, cur)
137 elif line.startswith('From '):
137 elif line.startswith('From '):
138 return mboxsplit(stream, cur)
138 return mboxsplit(stream, cur)
139 elif isheader(line, inheader):
139 elif isheader(line, inheader):
140 inheader = True
140 inheader = True
141 if line.split(':', 1)[0].lower() in mimeheaders:
141 if line.split(':', 1)[0].lower() in mimeheaders:
142 # let email parser handle this
142 # let email parser handle this
143 return mimesplit(stream, cur)
143 return mimesplit(stream, cur)
144 elif line.startswith('--- ') and inheader:
144 elif line.startswith('--- ') and inheader:
145 # No evil headers seen by diff start, split by hand
145 # No evil headers seen by diff start, split by hand
146 return headersplit(stream, cur)
146 return headersplit(stream, cur)
147 # Not enough info, keep reading
147 # Not enough info, keep reading
148
148
149 # if we are here, we have a very plain patch
149 # if we are here, we have a very plain patch
150 return remainder(cur)
150 return remainder(cur)
151
151
152 def extract(ui, fileobj):
152 def extract(ui, fileobj):
153 '''extract patch from data read from fileobj.
153 '''extract patch from data read from fileobj.
154
154
155 patch can be a normal patch or contained in an email message.
155 patch can be a normal patch or contained in an email message.
156
156
157 return tuple (filename, message, user, date, branch, node, p1, p2).
157 return tuple (filename, message, user, date, branch, node, p1, p2).
158 Any item in the returned tuple can be None. If filename is None,
158 Any item in the returned tuple can be None. If filename is None,
159 fileobj did not contain a patch. Caller must unlink filename when done.'''
159 fileobj did not contain a patch. Caller must unlink filename when done.'''
160
160
161 # attempt to detect the start of a patch
161 # attempt to detect the start of a patch
162 # (this heuristic is borrowed from quilt)
162 # (this heuristic is borrowed from quilt)
163 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
163 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
164 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
164 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
165 r'---[ \t].*?^\+\+\+[ \t]|'
165 r'---[ \t].*?^\+\+\+[ \t]|'
166 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
166 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
167
167
168 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
168 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
169 tmpfp = os.fdopen(fd, 'w')
169 tmpfp = os.fdopen(fd, 'w')
170 try:
170 try:
171 msg = email.Parser.Parser().parse(fileobj)
171 msg = email.Parser.Parser().parse(fileobj)
172
172
173 subject = msg['Subject']
173 subject = msg['Subject']
174 user = msg['From']
174 user = msg['From']
175 if not subject and not user:
175 if not subject and not user:
176 # Not an email, restore parsed headers if any
176 # Not an email, restore parsed headers if any
177 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
177 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
178
178
179 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
179 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
180 # should try to parse msg['Date']
180 # should try to parse msg['Date']
181 date = None
181 date = None
182 nodeid = None
182 nodeid = None
183 branch = None
183 branch = None
184 parents = []
184 parents = []
185
185
186 if subject:
186 if subject:
187 if subject.startswith('[PATCH'):
187 if subject.startswith('[PATCH'):
188 pend = subject.find(']')
188 pend = subject.find(']')
189 if pend >= 0:
189 if pend >= 0:
190 subject = subject[pend + 1:].lstrip()
190 subject = subject[pend + 1:].lstrip()
191 subject = re.sub(r'\n[ \t]+', ' ', subject)
191 subject = re.sub(r'\n[ \t]+', ' ', subject)
192 ui.debug('Subject: %s\n' % subject)
192 ui.debug('Subject: %s\n' % subject)
193 if user:
193 if user:
194 ui.debug('From: %s\n' % user)
194 ui.debug('From: %s\n' % user)
195 diffs_seen = 0
195 diffs_seen = 0
196 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
196 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
197 message = ''
197 message = ''
198 for part in msg.walk():
198 for part in msg.walk():
199 content_type = part.get_content_type()
199 content_type = part.get_content_type()
200 ui.debug('Content-Type: %s\n' % content_type)
200 ui.debug('Content-Type: %s\n' % content_type)
201 if content_type not in ok_types:
201 if content_type not in ok_types:
202 continue
202 continue
203 payload = part.get_payload(decode=True)
203 payload = part.get_payload(decode=True)
204 m = diffre.search(payload)
204 m = diffre.search(payload)
205 if m:
205 if m:
206 hgpatch = False
206 hgpatch = False
207 hgpatchheader = False
207 hgpatchheader = False
208 ignoretext = False
208 ignoretext = False
209
209
210 ui.debug('found patch at byte %d\n' % m.start(0))
210 ui.debug('found patch at byte %d\n' % m.start(0))
211 diffs_seen += 1
211 diffs_seen += 1
212 cfp = cStringIO.StringIO()
212 cfp = cStringIO.StringIO()
213 for line in payload[:m.start(0)].splitlines():
213 for line in payload[:m.start(0)].splitlines():
214 if line.startswith('# HG changeset patch') and not hgpatch:
214 if line.startswith('# HG changeset patch') and not hgpatch:
215 ui.debug('patch generated by hg export\n')
215 ui.debug('patch generated by hg export\n')
216 hgpatch = True
216 hgpatch = True
217 hgpatchheader = True
217 hgpatchheader = True
218 # drop earlier commit message content
218 # drop earlier commit message content
219 cfp.seek(0)
219 cfp.seek(0)
220 cfp.truncate()
220 cfp.truncate()
221 subject = None
221 subject = None
222 elif hgpatchheader:
222 elif hgpatchheader:
223 if line.startswith('# User '):
223 if line.startswith('# User '):
224 user = line[7:]
224 user = line[7:]
225 ui.debug('From: %s\n' % user)
225 ui.debug('From: %s\n' % user)
226 elif line.startswith("# Date "):
226 elif line.startswith("# Date "):
227 date = line[7:]
227 date = line[7:]
228 elif line.startswith("# Branch "):
228 elif line.startswith("# Branch "):
229 branch = line[9:]
229 branch = line[9:]
230 elif line.startswith("# Node ID "):
230 elif line.startswith("# Node ID "):
231 nodeid = line[10:]
231 nodeid = line[10:]
232 elif line.startswith("# Parent "):
232 elif line.startswith("# Parent "):
233 parents.append(line[9:].lstrip())
233 parents.append(line[9:].lstrip())
234 elif not line.startswith("# "):
234 elif not line.startswith("# "):
235 hgpatchheader = False
235 hgpatchheader = False
236 elif line == '---' and gitsendmail:
236 elif line == '---' and gitsendmail:
237 ignoretext = True
237 ignoretext = True
238 if not hgpatchheader and not ignoretext:
238 if not hgpatchheader and not ignoretext:
239 cfp.write(line)
239 cfp.write(line)
240 cfp.write('\n')
240 cfp.write('\n')
241 message = cfp.getvalue()
241 message = cfp.getvalue()
242 if tmpfp:
242 if tmpfp:
243 tmpfp.write(payload)
243 tmpfp.write(payload)
244 if not payload.endswith('\n'):
244 if not payload.endswith('\n'):
245 tmpfp.write('\n')
245 tmpfp.write('\n')
246 elif not diffs_seen and message and content_type == 'text/plain':
246 elif not diffs_seen and message and content_type == 'text/plain':
247 message += '\n' + payload
247 message += '\n' + payload
248 except: # re-raises
248 except: # re-raises
249 tmpfp.close()
249 tmpfp.close()
250 os.unlink(tmpname)
250 os.unlink(tmpname)
251 raise
251 raise
252
252
253 if subject and not message.startswith(subject):
253 if subject and not message.startswith(subject):
254 message = '%s\n%s' % (subject, message)
254 message = '%s\n%s' % (subject, message)
255 tmpfp.close()
255 tmpfp.close()
256 if not diffs_seen:
256 if not diffs_seen:
257 os.unlink(tmpname)
257 os.unlink(tmpname)
258 return None, message, user, date, branch, None, None, None
258 return None, message, user, date, branch, None, None, None
259 p1 = parents and parents.pop(0) or None
259 p1 = parents and parents.pop(0) or None
260 p2 = parents and parents.pop(0) or None
260 p2 = parents and parents.pop(0) or None
261 return tmpname, message, user, date, branch, nodeid, p1, p2
261 return tmpname, message, user, date, branch, nodeid, p1, p2
262
262
263 class patchmeta(object):
263 class patchmeta(object):
264 """Patched file metadata
264 """Patched file metadata
265
265
266 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
266 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
267 or COPY. 'path' is patched file path. 'oldpath' is set to the
267 or COPY. 'path' is patched file path. 'oldpath' is set to the
268 origin file when 'op' is either COPY or RENAME, None otherwise. If
268 origin file when 'op' is either COPY or RENAME, None otherwise. If
269 file mode is changed, 'mode' is a tuple (islink, isexec) where
269 file mode is changed, 'mode' is a tuple (islink, isexec) where
270 'islink' is True if the file is a symlink and 'isexec' is True if
270 'islink' is True if the file is a symlink and 'isexec' is True if
271 the file is executable. Otherwise, 'mode' is None.
271 the file is executable. Otherwise, 'mode' is None.
272 """
272 """
273 def __init__(self, path):
273 def __init__(self, path):
274 self.path = path
274 self.path = path
275 self.oldpath = None
275 self.oldpath = None
276 self.mode = None
276 self.mode = None
277 self.op = 'MODIFY'
277 self.op = 'MODIFY'
278 self.binary = False
278 self.binary = False
279
279
280 def setmode(self, mode):
280 def setmode(self, mode):
281 islink = mode & 020000
281 islink = mode & 020000
282 isexec = mode & 0100
282 isexec = mode & 0100
283 self.mode = (islink, isexec)
283 self.mode = (islink, isexec)
284
284
285 def copy(self):
285 def copy(self):
286 other = patchmeta(self.path)
286 other = patchmeta(self.path)
287 other.oldpath = self.oldpath
287 other.oldpath = self.oldpath
288 other.mode = self.mode
288 other.mode = self.mode
289 other.op = self.op
289 other.op = self.op
290 other.binary = self.binary
290 other.binary = self.binary
291 return other
291 return other
292
292
293 def _ispatchinga(self, afile):
293 def _ispatchinga(self, afile):
294 if afile == '/dev/null':
294 if afile == '/dev/null':
295 return self.op == 'ADD'
295 return self.op == 'ADD'
296 return afile == 'a/' + (self.oldpath or self.path)
296 return afile == 'a/' + (self.oldpath or self.path)
297
297
298 def _ispatchingb(self, bfile):
298 def _ispatchingb(self, bfile):
299 if bfile == '/dev/null':
299 if bfile == '/dev/null':
300 return self.op == 'DELETE'
300 return self.op == 'DELETE'
301 return bfile == 'b/' + self.path
301 return bfile == 'b/' + self.path
302
302
303 def ispatching(self, afile, bfile):
303 def ispatching(self, afile, bfile):
304 return self._ispatchinga(afile) and self._ispatchingb(bfile)
304 return self._ispatchinga(afile) and self._ispatchingb(bfile)
305
305
306 def __repr__(self):
306 def __repr__(self):
307 return "<patchmeta %s %r>" % (self.op, self.path)
307 return "<patchmeta %s %r>" % (self.op, self.path)
308
308
309 def readgitpatch(lr):
309 def readgitpatch(lr):
310 """extract git-style metadata about patches from <patchname>"""
310 """extract git-style metadata about patches from <patchname>"""
311
311
312 # Filter patch for git information
312 # Filter patch for git information
313 gp = None
313 gp = None
314 gitpatches = []
314 gitpatches = []
315 for line in lr:
315 for line in lr:
316 line = line.rstrip(' \r\n')
316 line = line.rstrip(' \r\n')
317 if line.startswith('diff --git'):
317 if line.startswith('diff --git'):
318 m = gitre.match(line)
318 m = gitre.match(line)
319 if m:
319 if m:
320 if gp:
320 if gp:
321 gitpatches.append(gp)
321 gitpatches.append(gp)
322 dst = m.group(2)
322 dst = m.group(2)
323 gp = patchmeta(dst)
323 gp = patchmeta(dst)
324 elif gp:
324 elif gp:
325 if line.startswith('--- '):
325 if line.startswith('--- '):
326 gitpatches.append(gp)
326 gitpatches.append(gp)
327 gp = None
327 gp = None
328 continue
328 continue
329 if line.startswith('rename from '):
329 if line.startswith('rename from '):
330 gp.op = 'RENAME'
330 gp.op = 'RENAME'
331 gp.oldpath = line[12:]
331 gp.oldpath = line[12:]
332 elif line.startswith('rename to '):
332 elif line.startswith('rename to '):
333 gp.path = line[10:]
333 gp.path = line[10:]
334 elif line.startswith('copy from '):
334 elif line.startswith('copy from '):
335 gp.op = 'COPY'
335 gp.op = 'COPY'
336 gp.oldpath = line[10:]
336 gp.oldpath = line[10:]
337 elif line.startswith('copy to '):
337 elif line.startswith('copy to '):
338 gp.path = line[8:]
338 gp.path = line[8:]
339 elif line.startswith('deleted file'):
339 elif line.startswith('deleted file'):
340 gp.op = 'DELETE'
340 gp.op = 'DELETE'
341 elif line.startswith('new file mode '):
341 elif line.startswith('new file mode '):
342 gp.op = 'ADD'
342 gp.op = 'ADD'
343 gp.setmode(int(line[-6:], 8))
343 gp.setmode(int(line[-6:], 8))
344 elif line.startswith('new mode '):
344 elif line.startswith('new mode '):
345 gp.setmode(int(line[-6:], 8))
345 gp.setmode(int(line[-6:], 8))
346 elif line.startswith('GIT binary patch'):
346 elif line.startswith('GIT binary patch'):
347 gp.binary = True
347 gp.binary = True
348 if gp:
348 if gp:
349 gitpatches.append(gp)
349 gitpatches.append(gp)
350
350
351 return gitpatches
351 return gitpatches
352
352
353 class linereader(object):
353 class linereader(object):
354 # simple class to allow pushing lines back into the input stream
354 # simple class to allow pushing lines back into the input stream
355 def __init__(self, fp):
355 def __init__(self, fp):
356 self.fp = fp
356 self.fp = fp
357 self.buf = []
357 self.buf = []
358
358
359 def push(self, line):
359 def push(self, line):
360 if line is not None:
360 if line is not None:
361 self.buf.append(line)
361 self.buf.append(line)
362
362
363 def readline(self):
363 def readline(self):
364 if self.buf:
364 if self.buf:
365 l = self.buf[0]
365 l = self.buf[0]
366 del self.buf[0]
366 del self.buf[0]
367 return l
367 return l
368 return self.fp.readline()
368 return self.fp.readline()
369
369
370 def __iter__(self):
370 def __iter__(self):
371 while True:
371 while True:
372 l = self.readline()
372 l = self.readline()
373 if not l:
373 if not l:
374 break
374 break
375 yield l
375 yield l
376
376
377 class abstractbackend(object):
377 class abstractbackend(object):
378 def __init__(self, ui):
378 def __init__(self, ui):
379 self.ui = ui
379 self.ui = ui
380
380
381 def getfile(self, fname):
381 def getfile(self, fname):
382 """Return target file data and flags as a (data, (islink,
382 """Return target file data and flags as a (data, (islink,
383 isexec)) tuple.
383 isexec)) tuple.
384 """
384 """
385 raise NotImplementedError
385 raise NotImplementedError
386
386
387 def setfile(self, fname, data, mode, copysource):
387 def setfile(self, fname, data, mode, copysource):
388 """Write data to target file fname and set its mode. mode is a
388 """Write data to target file fname and set its mode. mode is a
389 (islink, isexec) tuple. If data is None, the file content should
389 (islink, isexec) tuple. If data is None, the file content should
390 be left unchanged. If the file is modified after being copied,
390 be left unchanged. If the file is modified after being copied,
391 copysource is set to the original file name.
391 copysource is set to the original file name.
392 """
392 """
393 raise NotImplementedError
393 raise NotImplementedError
394
394
395 def unlink(self, fname):
395 def unlink(self, fname):
396 """Unlink target file."""
396 """Unlink target file."""
397 raise NotImplementedError
397 raise NotImplementedError
398
398
399 def writerej(self, fname, failed, total, lines):
399 def writerej(self, fname, failed, total, lines):
400 """Write rejected lines for fname. total is the number of hunks
400 """Write rejected lines for fname. total is the number of hunks
401 which failed to apply and total the total number of hunks for this
401 which failed to apply and total the total number of hunks for this
402 files.
402 files.
403 """
403 """
404 pass
404 pass
405
405
406 def exists(self, fname):
406 def exists(self, fname):
407 raise NotImplementedError
407 raise NotImplementedError
408
408
409 class fsbackend(abstractbackend):
409 class fsbackend(abstractbackend):
410 def __init__(self, ui, basedir):
410 def __init__(self, ui, basedir):
411 super(fsbackend, self).__init__(ui)
411 super(fsbackend, self).__init__(ui)
412 self.opener = scmutil.opener(basedir)
412 self.opener = scmutil.opener(basedir)
413
413
414 def _join(self, f):
414 def _join(self, f):
415 return os.path.join(self.opener.base, f)
415 return os.path.join(self.opener.base, f)
416
416
417 def getfile(self, fname):
417 def getfile(self, fname):
418 path = self._join(fname)
418 path = self._join(fname)
419 if os.path.islink(path):
419 if os.path.islink(path):
420 return (os.readlink(path), (True, False))
420 return (os.readlink(path), (True, False))
421 isexec = False
421 isexec = False
422 try:
422 try:
423 isexec = os.lstat(path).st_mode & 0100 != 0
423 isexec = os.lstat(path).st_mode & 0100 != 0
424 except OSError, e:
424 except OSError, e:
425 if e.errno != errno.ENOENT:
425 if e.errno != errno.ENOENT:
426 raise
426 raise
427 return (self.opener.read(fname), (False, isexec))
427 return (self.opener.read(fname), (False, isexec))
428
428
429 def setfile(self, fname, data, mode, copysource):
429 def setfile(self, fname, data, mode, copysource):
430 islink, isexec = mode
430 islink, isexec = mode
431 if data is None:
431 if data is None:
432 util.setflags(self._join(fname), islink, isexec)
432 util.setflags(self._join(fname), islink, isexec)
433 return
433 return
434 if islink:
434 if islink:
435 self.opener.symlink(data, fname)
435 self.opener.symlink(data, fname)
436 else:
436 else:
437 self.opener.write(fname, data)
437 self.opener.write(fname, data)
438 if isexec:
438 if isexec:
439 util.setflags(self._join(fname), False, True)
439 util.setflags(self._join(fname), False, True)
440
440
441 def unlink(self, fname):
441 def unlink(self, fname):
442 try:
442 try:
443 util.unlinkpath(self._join(fname))
443 util.unlinkpath(self._join(fname))
444 except OSError, inst:
444 except OSError, inst:
445 if inst.errno != errno.ENOENT:
445 if inst.errno != errno.ENOENT:
446 raise
446 raise
447
447
448 def writerej(self, fname, failed, total, lines):
448 def writerej(self, fname, failed, total, lines):
449 fname = fname + ".rej"
449 fname = fname + ".rej"
450 self.ui.warn(
450 self.ui.warn(
451 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
451 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
452 (failed, total, fname))
452 (failed, total, fname))
453 fp = self.opener(fname, 'w')
453 fp = self.opener(fname, 'w')
454 fp.writelines(lines)
454 fp.writelines(lines)
455 fp.close()
455 fp.close()
456
456
457 def exists(self, fname):
457 def exists(self, fname):
458 return os.path.lexists(self._join(fname))
458 return os.path.lexists(self._join(fname))
459
459
460 class workingbackend(fsbackend):
460 class workingbackend(fsbackend):
461 def __init__(self, ui, repo, similarity):
461 def __init__(self, ui, repo, similarity):
462 super(workingbackend, self).__init__(ui, repo.root)
462 super(workingbackend, self).__init__(ui, repo.root)
463 self.repo = repo
463 self.repo = repo
464 self.similarity = similarity
464 self.similarity = similarity
465 self.removed = set()
465 self.removed = set()
466 self.changed = set()
466 self.changed = set()
467 self.copied = []
467 self.copied = []
468
468
469 def _checkknown(self, fname):
469 def _checkknown(self, fname):
470 if self.repo.dirstate[fname] == '?' and self.exists(fname):
470 if self.repo.dirstate[fname] == '?' and self.exists(fname):
471 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
471 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
472
472
473 def setfile(self, fname, data, mode, copysource):
473 def setfile(self, fname, data, mode, copysource):
474 self._checkknown(fname)
474 self._checkknown(fname)
475 super(workingbackend, self).setfile(fname, data, mode, copysource)
475 super(workingbackend, self).setfile(fname, data, mode, copysource)
476 if copysource is not None:
476 if copysource is not None:
477 self.copied.append((copysource, fname))
477 self.copied.append((copysource, fname))
478 self.changed.add(fname)
478 self.changed.add(fname)
479
479
480 def unlink(self, fname):
480 def unlink(self, fname):
481 self._checkknown(fname)
481 self._checkknown(fname)
482 super(workingbackend, self).unlink(fname)
482 super(workingbackend, self).unlink(fname)
483 self.removed.add(fname)
483 self.removed.add(fname)
484 self.changed.add(fname)
484 self.changed.add(fname)
485
485
486 def close(self):
486 def close(self):
487 wctx = self.repo[None]
487 wctx = self.repo[None]
488 addremoved = set(self.changed)
488 addremoved = set(self.changed)
489 for src, dst in self.copied:
489 for src, dst in self.copied:
490 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
490 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
491 if self.removed:
491 if self.removed:
492 wctx.forget(sorted(self.removed))
492 wctx.forget(sorted(self.removed))
493 for f in self.removed:
493 for f in self.removed:
494 if f not in self.repo.dirstate:
494 if f not in self.repo.dirstate:
495 # File was deleted and no longer belongs to the
495 # File was deleted and no longer belongs to the
496 # dirstate, it was probably marked added then
496 # dirstate, it was probably marked added then
497 # deleted, and should not be considered by
497 # deleted, and should not be considered by
498 # addremove().
498 # addremove().
499 addremoved.discard(f)
499 addremoved.discard(f)
500 if addremoved:
500 if addremoved:
501 cwd = self.repo.getcwd()
501 cwd = self.repo.getcwd()
502 if cwd:
502 if cwd:
503 addremoved = [util.pathto(self.repo.root, cwd, f)
503 addremoved = [util.pathto(self.repo.root, cwd, f)
504 for f in addremoved]
504 for f in addremoved]
505 scmutil.addremove(self.repo, addremoved, similarity=self.similarity)
505 scmutil.addremove(self.repo, addremoved, similarity=self.similarity)
506 return sorted(self.changed)
506 return sorted(self.changed)
507
507
508 class filestore(object):
508 class filestore(object):
509 def __init__(self, maxsize=None):
509 def __init__(self, maxsize=None):
510 self.opener = None
510 self.opener = None
511 self.files = {}
511 self.files = {}
512 self.created = 0
512 self.created = 0
513 self.maxsize = maxsize
513 self.maxsize = maxsize
514 if self.maxsize is None:
514 if self.maxsize is None:
515 self.maxsize = 4*(2**20)
515 self.maxsize = 4*(2**20)
516 self.size = 0
516 self.size = 0
517 self.data = {}
517 self.data = {}
518
518
519 def setfile(self, fname, data, mode, copied=None):
519 def setfile(self, fname, data, mode, copied=None):
520 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
520 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
521 self.data[fname] = (data, mode, copied)
521 self.data[fname] = (data, mode, copied)
522 self.size += len(data)
522 self.size += len(data)
523 else:
523 else:
524 if self.opener is None:
524 if self.opener is None:
525 root = tempfile.mkdtemp(prefix='hg-patch-')
525 root = tempfile.mkdtemp(prefix='hg-patch-')
526 self.opener = scmutil.opener(root)
526 self.opener = scmutil.opener(root)
527 # Avoid filename issues with these simple names
527 # Avoid filename issues with these simple names
528 fn = str(self.created)
528 fn = str(self.created)
529 self.opener.write(fn, data)
529 self.opener.write(fn, data)
530 self.created += 1
530 self.created += 1
531 self.files[fname] = (fn, mode, copied)
531 self.files[fname] = (fn, mode, copied)
532
532
533 def getfile(self, fname):
533 def getfile(self, fname):
534 if fname in self.data:
534 if fname in self.data:
535 return self.data[fname]
535 return self.data[fname]
536 if not self.opener or fname not in self.files:
536 if not self.opener or fname not in self.files:
537 raise IOError
537 raise IOError
538 fn, mode, copied = self.files[fname]
538 fn, mode, copied = self.files[fname]
539 return self.opener.read(fn), mode, copied
539 return self.opener.read(fn), mode, copied
540
540
541 def close(self):
541 def close(self):
542 if self.opener:
542 if self.opener:
543 shutil.rmtree(self.opener.base)
543 shutil.rmtree(self.opener.base)
544
544
545 class repobackend(abstractbackend):
545 class repobackend(abstractbackend):
546 def __init__(self, ui, repo, ctx, store):
546 def __init__(self, ui, repo, ctx, store):
547 super(repobackend, self).__init__(ui)
547 super(repobackend, self).__init__(ui)
548 self.repo = repo
548 self.repo = repo
549 self.ctx = ctx
549 self.ctx = ctx
550 self.store = store
550 self.store = store
551 self.changed = set()
551 self.changed = set()
552 self.removed = set()
552 self.removed = set()
553 self.copied = {}
553 self.copied = {}
554
554
555 def _checkknown(self, fname):
555 def _checkknown(self, fname):
556 if fname not in self.ctx:
556 if fname not in self.ctx:
557 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
557 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
558
558
559 def getfile(self, fname):
559 def getfile(self, fname):
560 try:
560 try:
561 fctx = self.ctx[fname]
561 fctx = self.ctx[fname]
562 except error.LookupError:
562 except error.LookupError:
563 raise IOError
563 raise IOError
564 flags = fctx.flags()
564 flags = fctx.flags()
565 return fctx.data(), ('l' in flags, 'x' in flags)
565 return fctx.data(), ('l' in flags, 'x' in flags)
566
566
567 def setfile(self, fname, data, mode, copysource):
567 def setfile(self, fname, data, mode, copysource):
568 if copysource:
568 if copysource:
569 self._checkknown(copysource)
569 self._checkknown(copysource)
570 if data is None:
570 if data is None:
571 data = self.ctx[fname].data()
571 data = self.ctx[fname].data()
572 self.store.setfile(fname, data, mode, copysource)
572 self.store.setfile(fname, data, mode, copysource)
573 self.changed.add(fname)
573 self.changed.add(fname)
574 if copysource:
574 if copysource:
575 self.copied[fname] = copysource
575 self.copied[fname] = copysource
576
576
577 def unlink(self, fname):
577 def unlink(self, fname):
578 self._checkknown(fname)
578 self._checkknown(fname)
579 self.removed.add(fname)
579 self.removed.add(fname)
580
580
581 def exists(self, fname):
581 def exists(self, fname):
582 return fname in self.ctx
582 return fname in self.ctx
583
583
584 def close(self):
584 def close(self):
585 return self.changed | self.removed
585 return self.changed | self.removed
586
586
587 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
587 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
588 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
588 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
589 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
589 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
590 eolmodes = ['strict', 'crlf', 'lf', 'auto']
590 eolmodes = ['strict', 'crlf', 'lf', 'auto']
591
591
592 class patchfile(object):
592 class patchfile(object):
593 def __init__(self, ui, gp, backend, store, eolmode='strict'):
593 def __init__(self, ui, gp, backend, store, eolmode='strict'):
594 self.fname = gp.path
594 self.fname = gp.path
595 self.eolmode = eolmode
595 self.eolmode = eolmode
596 self.eol = None
596 self.eol = None
597 self.backend = backend
597 self.backend = backend
598 self.ui = ui
598 self.ui = ui
599 self.lines = []
599 self.lines = []
600 self.exists = False
600 self.exists = False
601 self.missing = True
601 self.missing = True
602 self.mode = gp.mode
602 self.mode = gp.mode
603 self.copysource = gp.oldpath
603 self.copysource = gp.oldpath
604 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
604 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
605 self.remove = gp.op == 'DELETE'
605 self.remove = gp.op == 'DELETE'
606 try:
606 try:
607 if self.copysource is None:
607 if self.copysource is None:
608 data, mode = backend.getfile(self.fname)
608 data, mode = backend.getfile(self.fname)
609 self.exists = True
609 self.exists = True
610 else:
610 else:
611 data, mode = store.getfile(self.copysource)[:2]
611 data, mode = store.getfile(self.copysource)[:2]
612 self.exists = backend.exists(self.fname)
612 self.exists = backend.exists(self.fname)
613 self.missing = False
613 self.missing = False
614 if data:
614 if data:
615 self.lines = mdiff.splitnewlines(data)
615 self.lines = mdiff.splitnewlines(data)
616 if self.mode is None:
616 if self.mode is None:
617 self.mode = mode
617 self.mode = mode
618 if self.lines:
618 if self.lines:
619 # Normalize line endings
619 # Normalize line endings
620 if self.lines[0].endswith('\r\n'):
620 if self.lines[0].endswith('\r\n'):
621 self.eol = '\r\n'
621 self.eol = '\r\n'
622 elif self.lines[0].endswith('\n'):
622 elif self.lines[0].endswith('\n'):
623 self.eol = '\n'
623 self.eol = '\n'
624 if eolmode != 'strict':
624 if eolmode != 'strict':
625 nlines = []
625 nlines = []
626 for l in self.lines:
626 for l in self.lines:
627 if l.endswith('\r\n'):
627 if l.endswith('\r\n'):
628 l = l[:-2] + '\n'
628 l = l[:-2] + '\n'
629 nlines.append(l)
629 nlines.append(l)
630 self.lines = nlines
630 self.lines = nlines
631 except IOError:
631 except IOError:
632 if self.create:
632 if self.create:
633 self.missing = False
633 self.missing = False
634 if self.mode is None:
634 if self.mode is None:
635 self.mode = (False, False)
635 self.mode = (False, False)
636 if self.missing:
636 if self.missing:
637 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
637 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
638
638
639 self.hash = {}
639 self.hash = {}
640 self.dirty = 0
640 self.dirty = 0
641 self.offset = 0
641 self.offset = 0
642 self.skew = 0
642 self.skew = 0
643 self.rej = []
643 self.rej = []
644 self.fileprinted = False
644 self.fileprinted = False
645 self.printfile(False)
645 self.printfile(False)
646 self.hunks = 0
646 self.hunks = 0
647
647
648 def writelines(self, fname, lines, mode):
648 def writelines(self, fname, lines, mode):
649 if self.eolmode == 'auto':
649 if self.eolmode == 'auto':
650 eol = self.eol
650 eol = self.eol
651 elif self.eolmode == 'crlf':
651 elif self.eolmode == 'crlf':
652 eol = '\r\n'
652 eol = '\r\n'
653 else:
653 else:
654 eol = '\n'
654 eol = '\n'
655
655
656 if self.eolmode != 'strict' and eol and eol != '\n':
656 if self.eolmode != 'strict' and eol and eol != '\n':
657 rawlines = []
657 rawlines = []
658 for l in lines:
658 for l in lines:
659 if l and l[-1] == '\n':
659 if l and l[-1] == '\n':
660 l = l[:-1] + eol
660 l = l[:-1] + eol
661 rawlines.append(l)
661 rawlines.append(l)
662 lines = rawlines
662 lines = rawlines
663
663
664 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
664 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
665
665
666 def printfile(self, warn):
666 def printfile(self, warn):
667 if self.fileprinted:
667 if self.fileprinted:
668 return
668 return
669 if warn or self.ui.verbose:
669 if warn or self.ui.verbose:
670 self.fileprinted = True
670 self.fileprinted = True
671 s = _("patching file %s\n") % self.fname
671 s = _("patching file %s\n") % self.fname
672 if warn:
672 if warn:
673 self.ui.warn(s)
673 self.ui.warn(s)
674 else:
674 else:
675 self.ui.note(s)
675 self.ui.note(s)
676
676
677
677
678 def findlines(self, l, linenum):
678 def findlines(self, l, linenum):
679 # looks through the hash and finds candidate lines. The
679 # looks through the hash and finds candidate lines. The
680 # result is a list of line numbers sorted based on distance
680 # result is a list of line numbers sorted based on distance
681 # from linenum
681 # from linenum
682
682
683 cand = self.hash.get(l, [])
683 cand = self.hash.get(l, [])
684 if len(cand) > 1:
684 if len(cand) > 1:
685 # resort our list of potentials forward then back.
685 # resort our list of potentials forward then back.
686 cand.sort(key=lambda x: abs(x - linenum))
686 cand.sort(key=lambda x: abs(x - linenum))
687 return cand
687 return cand
688
688
689 def write_rej(self):
689 def write_rej(self):
690 # our rejects are a little different from patch(1). This always
690 # our rejects are a little different from patch(1). This always
691 # creates rejects in the same form as the original patch. A file
691 # creates rejects in the same form as the original patch. A file
692 # header is inserted so that you can run the reject through patch again
692 # header is inserted so that you can run the reject through patch again
693 # without having to type the filename.
693 # without having to type the filename.
694 if not self.rej:
694 if not self.rej:
695 return
695 return
696 base = os.path.basename(self.fname)
696 base = os.path.basename(self.fname)
697 lines = ["--- %s\n+++ %s\n" % (base, base)]
697 lines = ["--- %s\n+++ %s\n" % (base, base)]
698 for x in self.rej:
698 for x in self.rej:
699 for l in x.hunk:
699 for l in x.hunk:
700 lines.append(l)
700 lines.append(l)
701 if l[-1] != '\n':
701 if l[-1] != '\n':
702 lines.append("\n\ No newline at end of file\n")
702 lines.append("\n\ No newline at end of file\n")
703 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
703 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
704
704
705 def apply(self, h):
705 def apply(self, h):
706 if not h.complete():
706 if not h.complete():
707 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
707 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
708 (h.number, h.desc, len(h.a), h.lena, len(h.b),
708 (h.number, h.desc, len(h.a), h.lena, len(h.b),
709 h.lenb))
709 h.lenb))
710
710
711 self.hunks += 1
711 self.hunks += 1
712
712
713 if self.missing:
713 if self.missing:
714 self.rej.append(h)
714 self.rej.append(h)
715 return -1
715 return -1
716
716
717 if self.exists and self.create:
717 if self.exists and self.create:
718 if self.copysource:
718 if self.copysource:
719 self.ui.warn(_("cannot create %s: destination already "
719 self.ui.warn(_("cannot create %s: destination already "
720 "exists\n" % self.fname))
720 "exists\n" % self.fname))
721 else:
721 else:
722 self.ui.warn(_("file %s already exists\n") % self.fname)
722 self.ui.warn(_("file %s already exists\n") % self.fname)
723 self.rej.append(h)
723 self.rej.append(h)
724 return -1
724 return -1
725
725
726 if isinstance(h, binhunk):
726 if isinstance(h, binhunk):
727 if self.remove:
727 if self.remove:
728 self.backend.unlink(self.fname)
728 self.backend.unlink(self.fname)
729 else:
729 else:
730 self.lines[:] = h.new()
730 self.lines[:] = h.new()
731 self.offset += len(h.new())
731 self.offset += len(h.new())
732 self.dirty = True
732 self.dirty = True
733 return 0
733 return 0
734
734
735 horig = h
735 horig = h
736 if (self.eolmode in ('crlf', 'lf')
736 if (self.eolmode in ('crlf', 'lf')
737 or self.eolmode == 'auto' and self.eol):
737 or self.eolmode == 'auto' and self.eol):
738 # If new eols are going to be normalized, then normalize
738 # If new eols are going to be normalized, then normalize
739 # hunk data before patching. Otherwise, preserve input
739 # hunk data before patching. Otherwise, preserve input
740 # line-endings.
740 # line-endings.
741 h = h.getnormalized()
741 h = h.getnormalized()
742
742
743 # fast case first, no offsets, no fuzz
743 # fast case first, no offsets, no fuzz
744 old, oldstart, new, newstart = h.fuzzit(0, False)
744 old, oldstart, new, newstart = h.fuzzit(0, False)
745 oldstart += self.offset
745 oldstart += self.offset
746 orig_start = oldstart
746 orig_start = oldstart
747 # if there's skew we want to emit the "(offset %d lines)" even
747 # if there's skew we want to emit the "(offset %d lines)" even
748 # when the hunk cleanly applies at start + skew, so skip the
748 # when the hunk cleanly applies at start + skew, so skip the
749 # fast case code
749 # fast case code
750 if (self.skew == 0 and
750 if (self.skew == 0 and
751 diffhelpers.testhunk(old, self.lines, oldstart) == 0):
751 diffhelpers.testhunk(old, self.lines, oldstart) == 0):
752 if self.remove:
752 if self.remove:
753 self.backend.unlink(self.fname)
753 self.backend.unlink(self.fname)
754 else:
754 else:
755 self.lines[oldstart:oldstart + len(old)] = new
755 self.lines[oldstart:oldstart + len(old)] = new
756 self.offset += len(new) - len(old)
756 self.offset += len(new) - len(old)
757 self.dirty = True
757 self.dirty = True
758 return 0
758 return 0
759
759
760 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
760 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
761 self.hash = {}
761 self.hash = {}
762 for x, s in enumerate(self.lines):
762 for x, s in enumerate(self.lines):
763 self.hash.setdefault(s, []).append(x)
763 self.hash.setdefault(s, []).append(x)
764
764
765 for fuzzlen in xrange(3):
765 for fuzzlen in xrange(3):
766 for toponly in [True, False]:
766 for toponly in [True, False]:
767 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
767 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
768 oldstart = oldstart + self.offset + self.skew
768 oldstart = oldstart + self.offset + self.skew
769 oldstart = min(oldstart, len(self.lines))
769 oldstart = min(oldstart, len(self.lines))
770 if old:
770 if old:
771 cand = self.findlines(old[0][1:], oldstart)
771 cand = self.findlines(old[0][1:], oldstart)
772 else:
772 else:
773 # Only adding lines with no or fuzzed context, just
773 # Only adding lines with no or fuzzed context, just
774 # take the skew in account
774 # take the skew in account
775 cand = [oldstart]
775 cand = [oldstart]
776
776
777 for l in cand:
777 for l in cand:
778 if not old or diffhelpers.testhunk(old, self.lines, l) == 0:
778 if not old or diffhelpers.testhunk(old, self.lines, l) == 0:
779 self.lines[l : l + len(old)] = new
779 self.lines[l : l + len(old)] = new
780 self.offset += len(new) - len(old)
780 self.offset += len(new) - len(old)
781 self.skew = l - orig_start
781 self.skew = l - orig_start
782 self.dirty = True
782 self.dirty = True
783 offset = l - orig_start - fuzzlen
783 offset = l - orig_start - fuzzlen
784 if fuzzlen:
784 if fuzzlen:
785 msg = _("Hunk #%d succeeded at %d "
785 msg = _("Hunk #%d succeeded at %d "
786 "with fuzz %d "
786 "with fuzz %d "
787 "(offset %d lines).\n")
787 "(offset %d lines).\n")
788 self.printfile(True)
788 self.printfile(True)
789 self.ui.warn(msg %
789 self.ui.warn(msg %
790 (h.number, l + 1, fuzzlen, offset))
790 (h.number, l + 1, fuzzlen, offset))
791 else:
791 else:
792 msg = _("Hunk #%d succeeded at %d "
792 msg = _("Hunk #%d succeeded at %d "
793 "(offset %d lines).\n")
793 "(offset %d lines).\n")
794 self.ui.note(msg % (h.number, l + 1, offset))
794 self.ui.note(msg % (h.number, l + 1, offset))
795 return fuzzlen
795 return fuzzlen
796 self.printfile(True)
796 self.printfile(True)
797 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
797 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
798 self.rej.append(horig)
798 self.rej.append(horig)
799 return -1
799 return -1
800
800
801 def close(self):
801 def close(self):
802 if self.dirty:
802 if self.dirty:
803 self.writelines(self.fname, self.lines, self.mode)
803 self.writelines(self.fname, self.lines, self.mode)
804 self.write_rej()
804 self.write_rej()
805 return len(self.rej)
805 return len(self.rej)
806
806
807 class hunk(object):
807 class hunk(object):
808 def __init__(self, desc, num, lr, context):
808 def __init__(self, desc, num, lr, context):
809 self.number = num
809 self.number = num
810 self.desc = desc
810 self.desc = desc
811 self.hunk = [desc]
811 self.hunk = [desc]
812 self.a = []
812 self.a = []
813 self.b = []
813 self.b = []
814 self.starta = self.lena = None
814 self.starta = self.lena = None
815 self.startb = self.lenb = None
815 self.startb = self.lenb = None
816 if lr is not None:
816 if lr is not None:
817 if context:
817 if context:
818 self.read_context_hunk(lr)
818 self.read_context_hunk(lr)
819 else:
819 else:
820 self.read_unified_hunk(lr)
820 self.read_unified_hunk(lr)
821
821
822 def getnormalized(self):
822 def getnormalized(self):
823 """Return a copy with line endings normalized to LF."""
823 """Return a copy with line endings normalized to LF."""
824
824
825 def normalize(lines):
825 def normalize(lines):
826 nlines = []
826 nlines = []
827 for line in lines:
827 for line in lines:
828 if line.endswith('\r\n'):
828 if line.endswith('\r\n'):
829 line = line[:-2] + '\n'
829 line = line[:-2] + '\n'
830 nlines.append(line)
830 nlines.append(line)
831 return nlines
831 return nlines
832
832
833 # Dummy object, it is rebuilt manually
833 # Dummy object, it is rebuilt manually
834 nh = hunk(self.desc, self.number, None, None)
834 nh = hunk(self.desc, self.number, None, None)
835 nh.number = self.number
835 nh.number = self.number
836 nh.desc = self.desc
836 nh.desc = self.desc
837 nh.hunk = self.hunk
837 nh.hunk = self.hunk
838 nh.a = normalize(self.a)
838 nh.a = normalize(self.a)
839 nh.b = normalize(self.b)
839 nh.b = normalize(self.b)
840 nh.starta = self.starta
840 nh.starta = self.starta
841 nh.startb = self.startb
841 nh.startb = self.startb
842 nh.lena = self.lena
842 nh.lena = self.lena
843 nh.lenb = self.lenb
843 nh.lenb = self.lenb
844 return nh
844 return nh
845
845
846 def read_unified_hunk(self, lr):
846 def read_unified_hunk(self, lr):
847 m = unidesc.match(self.desc)
847 m = unidesc.match(self.desc)
848 if not m:
848 if not m:
849 raise PatchError(_("bad hunk #%d") % self.number)
849 raise PatchError(_("bad hunk #%d") % self.number)
850 self.starta, self.lena, self.startb, self.lenb = m.groups()
850 self.starta, self.lena, self.startb, self.lenb = m.groups()
851 if self.lena is None:
851 if self.lena is None:
852 self.lena = 1
852 self.lena = 1
853 else:
853 else:
854 self.lena = int(self.lena)
854 self.lena = int(self.lena)
855 if self.lenb is None:
855 if self.lenb is None:
856 self.lenb = 1
856 self.lenb = 1
857 else:
857 else:
858 self.lenb = int(self.lenb)
858 self.lenb = int(self.lenb)
859 self.starta = int(self.starta)
859 self.starta = int(self.starta)
860 self.startb = int(self.startb)
860 self.startb = int(self.startb)
861 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a,
861 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a,
862 self.b)
862 self.b)
863 # if we hit eof before finishing out the hunk, the last line will
863 # if we hit eof before finishing out the hunk, the last line will
864 # be zero length. Lets try to fix it up.
864 # be zero length. Lets try to fix it up.
865 while len(self.hunk[-1]) == 0:
865 while len(self.hunk[-1]) == 0:
866 del self.hunk[-1]
866 del self.hunk[-1]
867 del self.a[-1]
867 del self.a[-1]
868 del self.b[-1]
868 del self.b[-1]
869 self.lena -= 1
869 self.lena -= 1
870 self.lenb -= 1
870 self.lenb -= 1
871 self._fixnewline(lr)
871 self._fixnewline(lr)
872
872
873 def read_context_hunk(self, lr):
873 def read_context_hunk(self, lr):
874 self.desc = lr.readline()
874 self.desc = lr.readline()
875 m = contextdesc.match(self.desc)
875 m = contextdesc.match(self.desc)
876 if not m:
876 if not m:
877 raise PatchError(_("bad hunk #%d") % self.number)
877 raise PatchError(_("bad hunk #%d") % self.number)
878 self.starta, aend = m.groups()
878 self.starta, aend = m.groups()
879 self.starta = int(self.starta)
879 self.starta = int(self.starta)
880 if aend is None:
880 if aend is None:
881 aend = self.starta
881 aend = self.starta
882 self.lena = int(aend) - self.starta
882 self.lena = int(aend) - self.starta
883 if self.starta:
883 if self.starta:
884 self.lena += 1
884 self.lena += 1
885 for x in xrange(self.lena):
885 for x in xrange(self.lena):
886 l = lr.readline()
886 l = lr.readline()
887 if l.startswith('---'):
887 if l.startswith('---'):
888 # lines addition, old block is empty
888 # lines addition, old block is empty
889 lr.push(l)
889 lr.push(l)
890 break
890 break
891 s = l[2:]
891 s = l[2:]
892 if l.startswith('- ') or l.startswith('! '):
892 if l.startswith('- ') or l.startswith('! '):
893 u = '-' + s
893 u = '-' + s
894 elif l.startswith(' '):
894 elif l.startswith(' '):
895 u = ' ' + s
895 u = ' ' + s
896 else:
896 else:
897 raise PatchError(_("bad hunk #%d old text line %d") %
897 raise PatchError(_("bad hunk #%d old text line %d") %
898 (self.number, x))
898 (self.number, x))
899 self.a.append(u)
899 self.a.append(u)
900 self.hunk.append(u)
900 self.hunk.append(u)
901
901
902 l = lr.readline()
902 l = lr.readline()
903 if l.startswith('\ '):
903 if l.startswith('\ '):
904 s = self.a[-1][:-1]
904 s = self.a[-1][:-1]
905 self.a[-1] = s
905 self.a[-1] = s
906 self.hunk[-1] = s
906 self.hunk[-1] = s
907 l = lr.readline()
907 l = lr.readline()
908 m = contextdesc.match(l)
908 m = contextdesc.match(l)
909 if not m:
909 if not m:
910 raise PatchError(_("bad hunk #%d") % self.number)
910 raise PatchError(_("bad hunk #%d") % self.number)
911 self.startb, bend = m.groups()
911 self.startb, bend = m.groups()
912 self.startb = int(self.startb)
912 self.startb = int(self.startb)
913 if bend is None:
913 if bend is None:
914 bend = self.startb
914 bend = self.startb
915 self.lenb = int(bend) - self.startb
915 self.lenb = int(bend) - self.startb
916 if self.startb:
916 if self.startb:
917 self.lenb += 1
917 self.lenb += 1
918 hunki = 1
918 hunki = 1
919 for x in xrange(self.lenb):
919 for x in xrange(self.lenb):
920 l = lr.readline()
920 l = lr.readline()
921 if l.startswith('\ '):
921 if l.startswith('\ '):
922 # XXX: the only way to hit this is with an invalid line range.
922 # XXX: the only way to hit this is with an invalid line range.
923 # The no-eol marker is not counted in the line range, but I
923 # The no-eol marker is not counted in the line range, but I
924 # guess there are diff(1) out there which behave differently.
924 # guess there are diff(1) out there which behave differently.
925 s = self.b[-1][:-1]
925 s = self.b[-1][:-1]
926 self.b[-1] = s
926 self.b[-1] = s
927 self.hunk[hunki - 1] = s
927 self.hunk[hunki - 1] = s
928 continue
928 continue
929 if not l:
929 if not l:
930 # line deletions, new block is empty and we hit EOF
930 # line deletions, new block is empty and we hit EOF
931 lr.push(l)
931 lr.push(l)
932 break
932 break
933 s = l[2:]
933 s = l[2:]
934 if l.startswith('+ ') or l.startswith('! '):
934 if l.startswith('+ ') or l.startswith('! '):
935 u = '+' + s
935 u = '+' + s
936 elif l.startswith(' '):
936 elif l.startswith(' '):
937 u = ' ' + s
937 u = ' ' + s
938 elif len(self.b) == 0:
938 elif len(self.b) == 0:
939 # line deletions, new block is empty
939 # line deletions, new block is empty
940 lr.push(l)
940 lr.push(l)
941 break
941 break
942 else:
942 else:
943 raise PatchError(_("bad hunk #%d old text line %d") %
943 raise PatchError(_("bad hunk #%d old text line %d") %
944 (self.number, x))
944 (self.number, x))
945 self.b.append(s)
945 self.b.append(s)
946 while True:
946 while True:
947 if hunki >= len(self.hunk):
947 if hunki >= len(self.hunk):
948 h = ""
948 h = ""
949 else:
949 else:
950 h = self.hunk[hunki]
950 h = self.hunk[hunki]
951 hunki += 1
951 hunki += 1
952 if h == u:
952 if h == u:
953 break
953 break
954 elif h.startswith('-'):
954 elif h.startswith('-'):
955 continue
955 continue
956 else:
956 else:
957 self.hunk.insert(hunki - 1, u)
957 self.hunk.insert(hunki - 1, u)
958 break
958 break
959
959
960 if not self.a:
960 if not self.a:
961 # this happens when lines were only added to the hunk
961 # this happens when lines were only added to the hunk
962 for x in self.hunk:
962 for x in self.hunk:
963 if x.startswith('-') or x.startswith(' '):
963 if x.startswith('-') or x.startswith(' '):
964 self.a.append(x)
964 self.a.append(x)
965 if not self.b:
965 if not self.b:
966 # this happens when lines were only deleted from the hunk
966 # this happens when lines were only deleted from the hunk
967 for x in self.hunk:
967 for x in self.hunk:
968 if x.startswith('+') or x.startswith(' '):
968 if x.startswith('+') or x.startswith(' '):
969 self.b.append(x[1:])
969 self.b.append(x[1:])
970 # @@ -start,len +start,len @@
970 # @@ -start,len +start,len @@
971 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
971 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
972 self.startb, self.lenb)
972 self.startb, self.lenb)
973 self.hunk[0] = self.desc
973 self.hunk[0] = self.desc
974 self._fixnewline(lr)
974 self._fixnewline(lr)
975
975
976 def _fixnewline(self, lr):
976 def _fixnewline(self, lr):
977 l = lr.readline()
977 l = lr.readline()
978 if l.startswith('\ '):
978 if l.startswith('\ '):
979 diffhelpers.fix_newline(self.hunk, self.a, self.b)
979 diffhelpers.fix_newline(self.hunk, self.a, self.b)
980 else:
980 else:
981 lr.push(l)
981 lr.push(l)
982
982
983 def complete(self):
983 def complete(self):
984 return len(self.a) == self.lena and len(self.b) == self.lenb
984 return len(self.a) == self.lena and len(self.b) == self.lenb
985
985
986 def _fuzzit(self, old, new, fuzz, toponly):
986 def _fuzzit(self, old, new, fuzz, toponly):
987 # this removes context lines from the top and bottom of list 'l'. It
987 # this removes context lines from the top and bottom of list 'l'. It
988 # checks the hunk to make sure only context lines are removed, and then
988 # checks the hunk to make sure only context lines are removed, and then
989 # returns a new shortened list of lines.
989 # returns a new shortened list of lines.
990 fuzz = min(fuzz, len(old))
990 fuzz = min(fuzz, len(old))
991 if fuzz:
991 if fuzz:
992 top = 0
992 top = 0
993 bot = 0
993 bot = 0
994 hlen = len(self.hunk)
994 hlen = len(self.hunk)
995 for x in xrange(hlen - 1):
995 for x in xrange(hlen - 1):
996 # the hunk starts with the @@ line, so use x+1
996 # the hunk starts with the @@ line, so use x+1
997 if self.hunk[x + 1][0] == ' ':
997 if self.hunk[x + 1][0] == ' ':
998 top += 1
998 top += 1
999 else:
999 else:
1000 break
1000 break
1001 if not toponly:
1001 if not toponly:
1002 for x in xrange(hlen - 1):
1002 for x in xrange(hlen - 1):
1003 if self.hunk[hlen - bot - 1][0] == ' ':
1003 if self.hunk[hlen - bot - 1][0] == ' ':
1004 bot += 1
1004 bot += 1
1005 else:
1005 else:
1006 break
1006 break
1007
1007
1008 bot = min(fuzz, bot)
1008 bot = min(fuzz, bot)
1009 top = min(fuzz, top)
1009 top = min(fuzz, top)
1010 return old[top:len(old)-bot], new[top:len(new)-bot], top
1010 return old[top:len(old)-bot], new[top:len(new)-bot], top
1011 return old, new, 0
1011 return old, new, 0
1012
1012
1013 def fuzzit(self, fuzz, toponly):
1013 def fuzzit(self, fuzz, toponly):
1014 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1014 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1015 oldstart = self.starta + top
1015 oldstart = self.starta + top
1016 newstart = self.startb + top
1016 newstart = self.startb + top
1017 # zero length hunk ranges already have their start decremented
1017 # zero length hunk ranges already have their start decremented
1018 if self.lena and oldstart > 0:
1018 if self.lena and oldstart > 0:
1019 oldstart -= 1
1019 oldstart -= 1
1020 if self.lenb and newstart > 0:
1020 if self.lenb and newstart > 0:
1021 newstart -= 1
1021 newstart -= 1
1022 return old, oldstart, new, newstart
1022 return old, oldstart, new, newstart
1023
1023
1024 class binhunk(object):
1024 class binhunk(object):
1025 'A binary patch file. Only understands literals so far.'
1025 'A binary patch file. Only understands literals so far.'
1026 def __init__(self, lr, fname):
1026 def __init__(self, lr, fname):
1027 self.text = None
1027 self.text = None
1028 self.hunk = ['GIT binary patch\n']
1028 self.hunk = ['GIT binary patch\n']
1029 self._fname = fname
1029 self._fname = fname
1030 self._read(lr)
1030 self._read(lr)
1031
1031
1032 def complete(self):
1032 def complete(self):
1033 return self.text is not None
1033 return self.text is not None
1034
1034
1035 def new(self):
1035 def new(self):
1036 return [self.text]
1036 return [self.text]
1037
1037
1038 def _read(self, lr):
1038 def _read(self, lr):
1039 def getline(lr, hunk):
1039 def getline(lr, hunk):
1040 l = lr.readline()
1040 l = lr.readline()
1041 hunk.append(l)
1041 hunk.append(l)
1042 return l.rstrip('\r\n')
1042 return l.rstrip('\r\n')
1043
1043
1044 while True:
1044 while True:
1045 line = getline(lr, self.hunk)
1045 line = getline(lr, self.hunk)
1046 if not line:
1046 if not line:
1047 raise PatchError(_('could not extract "%s" binary data')
1047 raise PatchError(_('could not extract "%s" binary data')
1048 % self._fname)
1048 % self._fname)
1049 if line.startswith('literal '):
1049 if line.startswith('literal '):
1050 break
1050 break
1051 size = int(line[8:].rstrip())
1051 size = int(line[8:].rstrip())
1052 dec = []
1052 dec = []
1053 line = getline(lr, self.hunk)
1053 line = getline(lr, self.hunk)
1054 while len(line) > 1:
1054 while len(line) > 1:
1055 l = line[0]
1055 l = line[0]
1056 if l <= 'Z' and l >= 'A':
1056 if l <= 'Z' and l >= 'A':
1057 l = ord(l) - ord('A') + 1
1057 l = ord(l) - ord('A') + 1
1058 else:
1058 else:
1059 l = ord(l) - ord('a') + 27
1059 l = ord(l) - ord('a') + 27
1060 try:
1060 try:
1061 dec.append(base85.b85decode(line[1:])[:l])
1061 dec.append(base85.b85decode(line[1:])[:l])
1062 except ValueError, e:
1062 except ValueError, e:
1063 raise PatchError(_('could not decode "%s" binary patch: %s')
1063 raise PatchError(_('could not decode "%s" binary patch: %s')
1064 % (self._fname, str(e)))
1064 % (self._fname, str(e)))
1065 line = getline(lr, self.hunk)
1065 line = getline(lr, self.hunk)
1066 text = zlib.decompress(''.join(dec))
1066 text = zlib.decompress(''.join(dec))
1067 if len(text) != size:
1067 if len(text) != size:
1068 raise PatchError(_('"%s" length is %d bytes, should be %d')
1068 raise PatchError(_('"%s" length is %d bytes, should be %d')
1069 % (self._fname, len(text), size))
1069 % (self._fname, len(text), size))
1070 self.text = text
1070 self.text = text
1071
1071
1072 def parsefilename(str):
1072 def parsefilename(str):
1073 # --- filename \t|space stuff
1073 # --- filename \t|space stuff
1074 s = str[4:].rstrip('\r\n')
1074 s = str[4:].rstrip('\r\n')
1075 i = s.find('\t')
1075 i = s.find('\t')
1076 if i < 0:
1076 if i < 0:
1077 i = s.find(' ')
1077 i = s.find(' ')
1078 if i < 0:
1078 if i < 0:
1079 return s
1079 return s
1080 return s[:i]
1080 return s[:i]
1081
1081
1082 def pathstrip(path, strip):
1082 def pathstrip(path, strip):
1083 pathlen = len(path)
1083 pathlen = len(path)
1084 i = 0
1084 i = 0
1085 if strip == 0:
1085 if strip == 0:
1086 return '', path.rstrip()
1086 return '', path.rstrip()
1087 count = strip
1087 count = strip
1088 while count > 0:
1088 while count > 0:
1089 i = path.find('/', i)
1089 i = path.find('/', i)
1090 if i == -1:
1090 if i == -1:
1091 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1091 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1092 (count, strip, path))
1092 (count, strip, path))
1093 i += 1
1093 i += 1
1094 # consume '//' in the path
1094 # consume '//' in the path
1095 while i < pathlen - 1 and path[i] == '/':
1095 while i < pathlen - 1 and path[i] == '/':
1096 i += 1
1096 i += 1
1097 count -= 1
1097 count -= 1
1098 return path[:i].lstrip(), path[i:].rstrip()
1098 return path[:i].lstrip(), path[i:].rstrip()
1099
1099
1100 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip):
1100 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip):
1101 nulla = afile_orig == "/dev/null"
1101 nulla = afile_orig == "/dev/null"
1102 nullb = bfile_orig == "/dev/null"
1102 nullb = bfile_orig == "/dev/null"
1103 create = nulla and hunk.starta == 0 and hunk.lena == 0
1103 create = nulla and hunk.starta == 0 and hunk.lena == 0
1104 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1104 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1105 abase, afile = pathstrip(afile_orig, strip)
1105 abase, afile = pathstrip(afile_orig, strip)
1106 gooda = not nulla and backend.exists(afile)
1106 gooda = not nulla and backend.exists(afile)
1107 bbase, bfile = pathstrip(bfile_orig, strip)
1107 bbase, bfile = pathstrip(bfile_orig, strip)
1108 if afile == bfile:
1108 if afile == bfile:
1109 goodb = gooda
1109 goodb = gooda
1110 else:
1110 else:
1111 goodb = not nullb and backend.exists(bfile)
1111 goodb = not nullb and backend.exists(bfile)
1112 missing = not goodb and not gooda and not create
1112 missing = not goodb and not gooda and not create
1113
1113
1114 # some diff programs apparently produce patches where the afile is
1114 # some diff programs apparently produce patches where the afile is
1115 # not /dev/null, but afile starts with bfile
1115 # not /dev/null, but afile starts with bfile
1116 abasedir = afile[:afile.rfind('/') + 1]
1116 abasedir = afile[:afile.rfind('/') + 1]
1117 bbasedir = bfile[:bfile.rfind('/') + 1]
1117 bbasedir = bfile[:bfile.rfind('/') + 1]
1118 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1118 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1119 and hunk.starta == 0 and hunk.lena == 0):
1119 and hunk.starta == 0 and hunk.lena == 0):
1120 create = True
1120 create = True
1121 missing = False
1121 missing = False
1122
1122
1123 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1123 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1124 # diff is between a file and its backup. In this case, the original
1124 # diff is between a file and its backup. In this case, the original
1125 # file should be patched (see original mpatch code).
1125 # file should be patched (see original mpatch code).
1126 isbackup = (abase == bbase and bfile.startswith(afile))
1126 isbackup = (abase == bbase and bfile.startswith(afile))
1127 fname = None
1127 fname = None
1128 if not missing:
1128 if not missing:
1129 if gooda and goodb:
1129 if gooda and goodb:
1130 fname = isbackup and afile or bfile
1130 fname = isbackup and afile or bfile
1131 elif gooda:
1131 elif gooda:
1132 fname = afile
1132 fname = afile
1133
1133
1134 if not fname:
1134 if not fname:
1135 if not nullb:
1135 if not nullb:
1136 fname = isbackup and afile or bfile
1136 fname = isbackup and afile or bfile
1137 elif not nulla:
1137 elif not nulla:
1138 fname = afile
1138 fname = afile
1139 else:
1139 else:
1140 raise PatchError(_("undefined source and destination files"))
1140 raise PatchError(_("undefined source and destination files"))
1141
1141
1142 gp = patchmeta(fname)
1142 gp = patchmeta(fname)
1143 if create:
1143 if create:
1144 gp.op = 'ADD'
1144 gp.op = 'ADD'
1145 elif remove:
1145 elif remove:
1146 gp.op = 'DELETE'
1146 gp.op = 'DELETE'
1147 return gp
1147 return gp
1148
1148
1149 def scangitpatch(lr, firstline):
1149 def scangitpatch(lr, firstline):
1150 """
1150 """
1151 Git patches can emit:
1151 Git patches can emit:
1152 - rename a to b
1152 - rename a to b
1153 - change b
1153 - change b
1154 - copy a to c
1154 - copy a to c
1155 - change c
1155 - change c
1156
1156
1157 We cannot apply this sequence as-is, the renamed 'a' could not be
1157 We cannot apply this sequence as-is, the renamed 'a' could not be
1158 found for it would have been renamed already. And we cannot copy
1158 found for it would have been renamed already. And we cannot copy
1159 from 'b' instead because 'b' would have been changed already. So
1159 from 'b' instead because 'b' would have been changed already. So
1160 we scan the git patch for copy and rename commands so we can
1160 we scan the git patch for copy and rename commands so we can
1161 perform the copies ahead of time.
1161 perform the copies ahead of time.
1162 """
1162 """
1163 pos = 0
1163 pos = 0
1164 try:
1164 try:
1165 pos = lr.fp.tell()
1165 pos = lr.fp.tell()
1166 fp = lr.fp
1166 fp = lr.fp
1167 except IOError:
1167 except IOError:
1168 fp = cStringIO.StringIO(lr.fp.read())
1168 fp = cStringIO.StringIO(lr.fp.read())
1169 gitlr = linereader(fp)
1169 gitlr = linereader(fp)
1170 gitlr.push(firstline)
1170 gitlr.push(firstline)
1171 gitpatches = readgitpatch(gitlr)
1171 gitpatches = readgitpatch(gitlr)
1172 fp.seek(pos)
1172 fp.seek(pos)
1173 return gitpatches
1173 return gitpatches
1174
1174
1175 def iterhunks(fp):
1175 def iterhunks(fp):
1176 """Read a patch and yield the following events:
1176 """Read a patch and yield the following events:
1177 - ("file", afile, bfile, firsthunk): select a new target file.
1177 - ("file", afile, bfile, firsthunk): select a new target file.
1178 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1178 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1179 "file" event.
1179 "file" event.
1180 - ("git", gitchanges): current diff is in git format, gitchanges
1180 - ("git", gitchanges): current diff is in git format, gitchanges
1181 maps filenames to gitpatch records. Unique event.
1181 maps filenames to gitpatch records. Unique event.
1182 """
1182 """
1183 afile = ""
1183 afile = ""
1184 bfile = ""
1184 bfile = ""
1185 state = None
1185 state = None
1186 hunknum = 0
1186 hunknum = 0
1187 emitfile = newfile = False
1187 emitfile = newfile = False
1188 gitpatches = None
1188 gitpatches = None
1189
1189
1190 # our states
1190 # our states
1191 BFILE = 1
1191 BFILE = 1
1192 context = None
1192 context = None
1193 lr = linereader(fp)
1193 lr = linereader(fp)
1194
1194
1195 while True:
1195 while True:
1196 x = lr.readline()
1196 x = lr.readline()
1197 if not x:
1197 if not x:
1198 break
1198 break
1199 if state == BFILE and (
1199 if state == BFILE and (
1200 (not context and x[0] == '@')
1200 (not context and x[0] == '@')
1201 or (context is not False and x.startswith('***************'))
1201 or (context is not False and x.startswith('***************'))
1202 or x.startswith('GIT binary patch')):
1202 or x.startswith('GIT binary patch')):
1203 gp = None
1203 gp = None
1204 if (gitpatches and
1204 if (gitpatches and
1205 gitpatches[-1].ispatching(afile, bfile)):
1205 gitpatches[-1].ispatching(afile, bfile)):
1206 gp = gitpatches.pop()
1206 gp = gitpatches.pop()
1207 if x.startswith('GIT binary patch'):
1207 if x.startswith('GIT binary patch'):
1208 h = binhunk(lr, gp.path)
1208 h = binhunk(lr, gp.path)
1209 else:
1209 else:
1210 if context is None and x.startswith('***************'):
1210 if context is None and x.startswith('***************'):
1211 context = True
1211 context = True
1212 h = hunk(x, hunknum + 1, lr, context)
1212 h = hunk(x, hunknum + 1, lr, context)
1213 hunknum += 1
1213 hunknum += 1
1214 if emitfile:
1214 if emitfile:
1215 emitfile = False
1215 emitfile = False
1216 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1216 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1217 yield 'hunk', h
1217 yield 'hunk', h
1218 elif x.startswith('diff --git'):
1218 elif x.startswith('diff --git'):
1219 m = gitre.match(x.rstrip(' \r\n'))
1219 m = gitre.match(x.rstrip(' \r\n'))
1220 if not m:
1220 if not m:
1221 continue
1221 continue
1222 if gitpatches is None:
1222 if gitpatches is None:
1223 # scan whole input for git metadata
1223 # scan whole input for git metadata
1224 gitpatches = scangitpatch(lr, x)
1224 gitpatches = scangitpatch(lr, x)
1225 yield 'git', [g.copy() for g in gitpatches
1225 yield 'git', [g.copy() for g in gitpatches
1226 if g.op in ('COPY', 'RENAME')]
1226 if g.op in ('COPY', 'RENAME')]
1227 gitpatches.reverse()
1227 gitpatches.reverse()
1228 afile = 'a/' + m.group(1)
1228 afile = 'a/' + m.group(1)
1229 bfile = 'b/' + m.group(2)
1229 bfile = 'b/' + m.group(2)
1230 while gitpatches and not gitpatches[-1].ispatching(afile, bfile):
1230 while gitpatches and not gitpatches[-1].ispatching(afile, bfile):
1231 gp = gitpatches.pop()
1231 gp = gitpatches.pop()
1232 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1232 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1233 if not gitpatches:
1233 if not gitpatches:
1234 raise PatchError(_('failed to synchronize metadata for "%s"')
1234 raise PatchError(_('failed to synchronize metadata for "%s"')
1235 % afile[2:])
1235 % afile[2:])
1236 gp = gitpatches[-1]
1236 gp = gitpatches[-1]
1237 newfile = True
1237 newfile = True
1238 elif x.startswith('---'):
1238 elif x.startswith('---'):
1239 # check for a unified diff
1239 # check for a unified diff
1240 l2 = lr.readline()
1240 l2 = lr.readline()
1241 if not l2.startswith('+++'):
1241 if not l2.startswith('+++'):
1242 lr.push(l2)
1242 lr.push(l2)
1243 continue
1243 continue
1244 newfile = True
1244 newfile = True
1245 context = False
1245 context = False
1246 afile = parsefilename(x)
1246 afile = parsefilename(x)
1247 bfile = parsefilename(l2)
1247 bfile = parsefilename(l2)
1248 elif x.startswith('***'):
1248 elif x.startswith('***'):
1249 # check for a context diff
1249 # check for a context diff
1250 l2 = lr.readline()
1250 l2 = lr.readline()
1251 if not l2.startswith('---'):
1251 if not l2.startswith('---'):
1252 lr.push(l2)
1252 lr.push(l2)
1253 continue
1253 continue
1254 l3 = lr.readline()
1254 l3 = lr.readline()
1255 lr.push(l3)
1255 lr.push(l3)
1256 if not l3.startswith("***************"):
1256 if not l3.startswith("***************"):
1257 lr.push(l2)
1257 lr.push(l2)
1258 continue
1258 continue
1259 newfile = True
1259 newfile = True
1260 context = True
1260 context = True
1261 afile = parsefilename(x)
1261 afile = parsefilename(x)
1262 bfile = parsefilename(l2)
1262 bfile = parsefilename(l2)
1263
1263
1264 if newfile:
1264 if newfile:
1265 newfile = False
1265 newfile = False
1266 emitfile = True
1266 emitfile = True
1267 state = BFILE
1267 state = BFILE
1268 hunknum = 0
1268 hunknum = 0
1269
1269
1270 while gitpatches:
1270 while gitpatches:
1271 gp = gitpatches.pop()
1271 gp = gitpatches.pop()
1272 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1272 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1273
1273
1274 def applydiff(ui, fp, backend, store, strip=1, eolmode='strict'):
1274 def applydiff(ui, fp, backend, store, strip=1, eolmode='strict'):
1275 """Reads a patch from fp and tries to apply it.
1275 """Reads a patch from fp and tries to apply it.
1276
1276
1277 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1277 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1278 there was any fuzz.
1278 there was any fuzz.
1279
1279
1280 If 'eolmode' is 'strict', the patch content and patched file are
1280 If 'eolmode' is 'strict', the patch content and patched file are
1281 read in binary mode. Otherwise, line endings are ignored when
1281 read in binary mode. Otherwise, line endings are ignored when
1282 patching then normalized according to 'eolmode'.
1282 patching then normalized according to 'eolmode'.
1283 """
1283 """
1284 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1284 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1285 eolmode=eolmode)
1285 eolmode=eolmode)
1286
1286
1287 def _applydiff(ui, fp, patcher, backend, store, strip=1,
1287 def _applydiff(ui, fp, patcher, backend, store, strip=1,
1288 eolmode='strict'):
1288 eolmode='strict'):
1289
1289
1290 def pstrip(p):
1290 def pstrip(p):
1291 return pathstrip(p, strip - 1)[1]
1291 return pathstrip(p, strip - 1)[1]
1292
1292
1293 rejects = 0
1293 rejects = 0
1294 err = 0
1294 err = 0
1295 current_file = None
1295 current_file = None
1296
1296
1297 for state, values in iterhunks(fp):
1297 for state, values in iterhunks(fp):
1298 if state == 'hunk':
1298 if state == 'hunk':
1299 if not current_file:
1299 if not current_file:
1300 continue
1300 continue
1301 ret = current_file.apply(values)
1301 ret = current_file.apply(values)
1302 if ret > 0:
1302 if ret > 0:
1303 err = 1
1303 err = 1
1304 elif state == 'file':
1304 elif state == 'file':
1305 if current_file:
1305 if current_file:
1306 rejects += current_file.close()
1306 rejects += current_file.close()
1307 current_file = None
1307 current_file = None
1308 afile, bfile, first_hunk, gp = values
1308 afile, bfile, first_hunk, gp = values
1309 if gp:
1309 if gp:
1310 gp.path = pstrip(gp.path)
1310 gp.path = pstrip(gp.path)
1311 if gp.oldpath:
1311 if gp.oldpath:
1312 gp.oldpath = pstrip(gp.oldpath)
1312 gp.oldpath = pstrip(gp.oldpath)
1313 else:
1313 else:
1314 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
1314 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
1315 if gp.op == 'RENAME':
1315 if gp.op == 'RENAME':
1316 backend.unlink(gp.oldpath)
1316 backend.unlink(gp.oldpath)
1317 if not first_hunk:
1317 if not first_hunk:
1318 if gp.op == 'DELETE':
1318 if gp.op == 'DELETE':
1319 backend.unlink(gp.path)
1319 backend.unlink(gp.path)
1320 continue
1320 continue
1321 data, mode = None, None
1321 data, mode = None, None
1322 if gp.op in ('RENAME', 'COPY'):
1322 if gp.op in ('RENAME', 'COPY'):
1323 data, mode = store.getfile(gp.oldpath)[:2]
1323 data, mode = store.getfile(gp.oldpath)[:2]
1324 if gp.mode:
1324 if gp.mode:
1325 mode = gp.mode
1325 mode = gp.mode
1326 if gp.op == 'ADD':
1326 if gp.op == 'ADD':
1327 # Added files without content have no hunk and
1327 # Added files without content have no hunk and
1328 # must be created
1328 # must be created
1329 data = ''
1329 data = ''
1330 if data or mode:
1330 if data or mode:
1331 if (gp.op in ('ADD', 'RENAME', 'COPY')
1331 if (gp.op in ('ADD', 'RENAME', 'COPY')
1332 and backend.exists(gp.path)):
1332 and backend.exists(gp.path)):
1333 raise PatchError(_("cannot create %s: destination "
1333 raise PatchError(_("cannot create %s: destination "
1334 "already exists") % gp.path)
1334 "already exists") % gp.path)
1335 backend.setfile(gp.path, data, mode, gp.oldpath)
1335 backend.setfile(gp.path, data, mode, gp.oldpath)
1336 continue
1336 continue
1337 try:
1337 try:
1338 current_file = patcher(ui, gp, backend, store,
1338 current_file = patcher(ui, gp, backend, store,
1339 eolmode=eolmode)
1339 eolmode=eolmode)
1340 except PatchError, inst:
1340 except PatchError, inst:
1341 ui.warn(str(inst) + '\n')
1341 ui.warn(str(inst) + '\n')
1342 current_file = None
1342 current_file = None
1343 rejects += 1
1343 rejects += 1
1344 continue
1344 continue
1345 elif state == 'git':
1345 elif state == 'git':
1346 for gp in values:
1346 for gp in values:
1347 path = pstrip(gp.oldpath)
1347 path = pstrip(gp.oldpath)
1348 data, mode = backend.getfile(path)
1348 data, mode = backend.getfile(path)
1349 store.setfile(path, data, mode)
1349 store.setfile(path, data, mode)
1350 else:
1350 else:
1351 raise util.Abort(_('unsupported parser state: %s') % state)
1351 raise util.Abort(_('unsupported parser state: %s') % state)
1352
1352
1353 if current_file:
1353 if current_file:
1354 rejects += current_file.close()
1354 rejects += current_file.close()
1355
1355
1356 if rejects:
1356 if rejects:
1357 return -1
1357 return -1
1358 return err
1358 return err
1359
1359
1360 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1360 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1361 similarity):
1361 similarity):
1362 """use <patcher> to apply <patchname> to the working directory.
1362 """use <patcher> to apply <patchname> to the working directory.
1363 returns whether patch was applied with fuzz factor."""
1363 returns whether patch was applied with fuzz factor."""
1364
1364
1365 fuzz = False
1365 fuzz = False
1366 args = []
1366 args = []
1367 cwd = repo.root
1367 cwd = repo.root
1368 if cwd:
1368 if cwd:
1369 args.append('-d %s' % util.shellquote(cwd))
1369 args.append('-d %s' % util.shellquote(cwd))
1370 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1370 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1371 util.shellquote(patchname)))
1371 util.shellquote(patchname)))
1372 try:
1372 try:
1373 for line in fp:
1373 for line in fp:
1374 line = line.rstrip()
1374 line = line.rstrip()
1375 ui.note(line + '\n')
1375 ui.note(line + '\n')
1376 if line.startswith('patching file '):
1376 if line.startswith('patching file '):
1377 pf = util.parsepatchoutput(line)
1377 pf = util.parsepatchoutput(line)
1378 printed_file = False
1378 printed_file = False
1379 files.add(pf)
1379 files.add(pf)
1380 elif line.find('with fuzz') >= 0:
1380 elif line.find('with fuzz') >= 0:
1381 fuzz = True
1381 fuzz = True
1382 if not printed_file:
1382 if not printed_file:
1383 ui.warn(pf + '\n')
1383 ui.warn(pf + '\n')
1384 printed_file = True
1384 printed_file = True
1385 ui.warn(line + '\n')
1385 ui.warn(line + '\n')
1386 elif line.find('saving rejects to file') >= 0:
1386 elif line.find('saving rejects to file') >= 0:
1387 ui.warn(line + '\n')
1387 ui.warn(line + '\n')
1388 elif line.find('FAILED') >= 0:
1388 elif line.find('FAILED') >= 0:
1389 if not printed_file:
1389 if not printed_file:
1390 ui.warn(pf + '\n')
1390 ui.warn(pf + '\n')
1391 printed_file = True
1391 printed_file = True
1392 ui.warn(line + '\n')
1392 ui.warn(line + '\n')
1393 finally:
1393 finally:
1394 if files:
1394 if files:
1395 cfiles = list(files)
1395 cfiles = list(files)
1396 cwd = repo.getcwd()
1396 cwd = repo.getcwd()
1397 if cwd:
1397 if cwd:
1398 cfiles = [util.pathto(repo.root, cwd, f)
1398 cfiles = [util.pathto(repo.root, cwd, f)
1399 for f in cfiles]
1399 for f in cfiles]
1400 scmutil.addremove(repo, cfiles, similarity=similarity)
1400 scmutil.addremove(repo, cfiles, similarity=similarity)
1401 code = fp.close()
1401 code = fp.close()
1402 if code:
1402 if code:
1403 raise PatchError(_("patch command failed: %s") %
1403 raise PatchError(_("patch command failed: %s") %
1404 util.explainexit(code)[0])
1404 util.explainexit(code)[0])
1405 return fuzz
1405 return fuzz
1406
1406
1407 def patchbackend(ui, backend, patchobj, strip, files=None, eolmode='strict'):
1407 def patchbackend(ui, backend, patchobj, strip, files=None, eolmode='strict'):
1408 if files is None:
1408 if files is None:
1409 files = set()
1409 files = set()
1410 if eolmode is None:
1410 if eolmode is None:
1411 eolmode = ui.config('patch', 'eol', 'strict')
1411 eolmode = ui.config('patch', 'eol', 'strict')
1412 if eolmode.lower() not in eolmodes:
1412 if eolmode.lower() not in eolmodes:
1413 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1413 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1414 eolmode = eolmode.lower()
1414 eolmode = eolmode.lower()
1415
1415
1416 store = filestore()
1416 store = filestore()
1417 try:
1417 try:
1418 fp = open(patchobj, 'rb')
1418 fp = open(patchobj, 'rb')
1419 except TypeError:
1419 except TypeError:
1420 fp = patchobj
1420 fp = patchobj
1421 try:
1421 try:
1422 ret = applydiff(ui, fp, backend, store, strip=strip,
1422 ret = applydiff(ui, fp, backend, store, strip=strip,
1423 eolmode=eolmode)
1423 eolmode=eolmode)
1424 finally:
1424 finally:
1425 if fp != patchobj:
1425 if fp != patchobj:
1426 fp.close()
1426 fp.close()
1427 files.update(backend.close())
1427 files.update(backend.close())
1428 store.close()
1428 store.close()
1429 if ret < 0:
1429 if ret < 0:
1430 raise PatchError(_('patch failed to apply'))
1430 raise PatchError(_('patch failed to apply'))
1431 return ret > 0
1431 return ret > 0
1432
1432
1433 def internalpatch(ui, repo, patchobj, strip, files=None, eolmode='strict',
1433 def internalpatch(ui, repo, patchobj, strip, files=None, eolmode='strict',
1434 similarity=0):
1434 similarity=0):
1435 """use builtin patch to apply <patchobj> to the working directory.
1435 """use builtin patch to apply <patchobj> to the working directory.
1436 returns whether patch was applied with fuzz factor."""
1436 returns whether patch was applied with fuzz factor."""
1437 backend = workingbackend(ui, repo, similarity)
1437 backend = workingbackend(ui, repo, similarity)
1438 return patchbackend(ui, backend, patchobj, strip, files, eolmode)
1438 return patchbackend(ui, backend, patchobj, strip, files, eolmode)
1439
1439
1440 def patchrepo(ui, repo, ctx, store, patchobj, strip, files=None,
1440 def patchrepo(ui, repo, ctx, store, patchobj, strip, files=None,
1441 eolmode='strict'):
1441 eolmode='strict'):
1442 backend = repobackend(ui, repo, ctx, store)
1442 backend = repobackend(ui, repo, ctx, store)
1443 return patchbackend(ui, backend, patchobj, strip, files, eolmode)
1443 return patchbackend(ui, backend, patchobj, strip, files, eolmode)
1444
1444
1445 def makememctx(repo, parents, text, user, date, branch, files, store,
1445 def makememctx(repo, parents, text, user, date, branch, files, store,
1446 editor=None):
1446 editor=None):
1447 def getfilectx(repo, memctx, path):
1447 def getfilectx(repo, memctx, path):
1448 data, (islink, isexec), copied = store.getfile(path)
1448 data, (islink, isexec), copied = store.getfile(path)
1449 return context.memfilectx(path, data, islink=islink, isexec=isexec,
1449 return context.memfilectx(path, data, islink=islink, isexec=isexec,
1450 copied=copied)
1450 copied=copied)
1451 extra = {}
1451 extra = {}
1452 if branch:
1452 if branch:
1453 extra['branch'] = encoding.fromlocal(branch)
1453 extra['branch'] = encoding.fromlocal(branch)
1454 ctx = context.memctx(repo, parents, text, files, getfilectx, user,
1454 ctx = context.memctx(repo, parents, text, files, getfilectx, user,
1455 date, extra)
1455 date, extra)
1456 if editor:
1456 if editor:
1457 ctx._text = editor(repo, ctx, [])
1457 ctx._text = editor(repo, ctx, [])
1458 return ctx
1458 return ctx
1459
1459
1460 def patch(ui, repo, patchname, strip=1, files=None, eolmode='strict',
1460 def patch(ui, repo, patchname, strip=1, files=None, eolmode='strict',
1461 similarity=0):
1461 similarity=0):
1462 """Apply <patchname> to the working directory.
1462 """Apply <patchname> to the working directory.
1463
1463
1464 'eolmode' specifies how end of lines should be handled. It can be:
1464 'eolmode' specifies how end of lines should be handled. It can be:
1465 - 'strict': inputs are read in binary mode, EOLs are preserved
1465 - 'strict': inputs are read in binary mode, EOLs are preserved
1466 - 'crlf': EOLs are ignored when patching and reset to CRLF
1466 - 'crlf': EOLs are ignored when patching and reset to CRLF
1467 - 'lf': EOLs are ignored when patching and reset to LF
1467 - 'lf': EOLs are ignored when patching and reset to LF
1468 - None: get it from user settings, default to 'strict'
1468 - None: get it from user settings, default to 'strict'
1469 'eolmode' is ignored when using an external patcher program.
1469 'eolmode' is ignored when using an external patcher program.
1470
1470
1471 Returns whether patch was applied with fuzz factor.
1471 Returns whether patch was applied with fuzz factor.
1472 """
1472 """
1473 patcher = ui.config('ui', 'patch')
1473 patcher = ui.config('ui', 'patch')
1474 if files is None:
1474 if files is None:
1475 files = set()
1475 files = set()
1476 try:
1476 try:
1477 if patcher:
1477 if patcher:
1478 return _externalpatch(ui, repo, patcher, patchname, strip,
1478 return _externalpatch(ui, repo, patcher, patchname, strip,
1479 files, similarity)
1479 files, similarity)
1480 return internalpatch(ui, repo, patchname, strip, files, eolmode,
1480 return internalpatch(ui, repo, patchname, strip, files, eolmode,
1481 similarity)
1481 similarity)
1482 except PatchError, err:
1482 except PatchError, err:
1483 raise util.Abort(str(err))
1483 raise util.Abort(str(err))
1484
1484
1485 def changedfiles(ui, repo, patchpath, strip=1):
1485 def changedfiles(ui, repo, patchpath, strip=1):
1486 backend = fsbackend(ui, repo.root)
1486 backend = fsbackend(ui, repo.root)
1487 fp = open(patchpath, 'rb')
1487 fp = open(patchpath, 'rb')
1488 try:
1488 try:
1489 changed = set()
1489 changed = set()
1490 for state, values in iterhunks(fp):
1490 for state, values in iterhunks(fp):
1491 if state == 'file':
1491 if state == 'file':
1492 afile, bfile, first_hunk, gp = values
1492 afile, bfile, first_hunk, gp = values
1493 if gp:
1493 if gp:
1494 gp.path = pathstrip(gp.path, strip - 1)[1]
1494 gp.path = pathstrip(gp.path, strip - 1)[1]
1495 if gp.oldpath:
1495 if gp.oldpath:
1496 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1496 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1497 else:
1497 else:
1498 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
1498 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
1499 changed.add(gp.path)
1499 changed.add(gp.path)
1500 if gp.op == 'RENAME':
1500 if gp.op == 'RENAME':
1501 changed.add(gp.oldpath)
1501 changed.add(gp.oldpath)
1502 elif state not in ('hunk', 'git'):
1502 elif state not in ('hunk', 'git'):
1503 raise util.Abort(_('unsupported parser state: %s') % state)
1503 raise util.Abort(_('unsupported parser state: %s') % state)
1504 return changed
1504 return changed
1505 finally:
1505 finally:
1506 fp.close()
1506 fp.close()
1507
1507
1508 def b85diff(to, tn):
1508 def b85diff(to, tn):
1509 '''print base85-encoded binary diff'''
1509 '''print base85-encoded binary diff'''
1510 def gitindex(text):
1510 def gitindex(text):
1511 if not text:
1511 if not text:
1512 return hex(nullid)
1512 return hex(nullid)
1513 l = len(text)
1513 l = len(text)
1514 s = util.sha1('blob %d\0' % l)
1514 s = util.sha1('blob %d\0' % l)
1515 s.update(text)
1515 s.update(text)
1516 return s.hexdigest()
1516 return s.hexdigest()
1517
1517
1518 def fmtline(line):
1518 def fmtline(line):
1519 l = len(line)
1519 l = len(line)
1520 if l <= 26:
1520 if l <= 26:
1521 l = chr(ord('A') + l - 1)
1521 l = chr(ord('A') + l - 1)
1522 else:
1522 else:
1523 l = chr(l - 26 + ord('a') - 1)
1523 l = chr(l - 26 + ord('a') - 1)
1524 return '%c%s\n' % (l, base85.b85encode(line, True))
1524 return '%c%s\n' % (l, base85.b85encode(line, True))
1525
1525
1526 def chunk(text, csize=52):
1526 def chunk(text, csize=52):
1527 l = len(text)
1527 l = len(text)
1528 i = 0
1528 i = 0
1529 while i < l:
1529 while i < l:
1530 yield text[i:i + csize]
1530 yield text[i:i + csize]
1531 i += csize
1531 i += csize
1532
1532
1533 tohash = gitindex(to)
1533 tohash = gitindex(to)
1534 tnhash = gitindex(tn)
1534 tnhash = gitindex(tn)
1535 if tohash == tnhash:
1535 if tohash == tnhash:
1536 return ""
1536 return ""
1537
1537
1538 # TODO: deltas
1538 # TODO: deltas
1539 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1539 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1540 (tohash, tnhash, len(tn))]
1540 (tohash, tnhash, len(tn))]
1541 for l in chunk(zlib.compress(tn)):
1541 for l in chunk(zlib.compress(tn)):
1542 ret.append(fmtline(l))
1542 ret.append(fmtline(l))
1543 ret.append('\n')
1543 ret.append('\n')
1544 return ''.join(ret)
1544 return ''.join(ret)
1545
1545
1546 class GitDiffRequired(Exception):
1546 class GitDiffRequired(Exception):
1547 pass
1547 pass
1548
1548
1549 def diffopts(ui, opts=None, untrusted=False, section='diff'):
1549 def diffopts(ui, opts=None, untrusted=False, section='diff'):
1550 def get(key, name=None, getter=ui.configbool):
1550 def get(key, name=None, getter=ui.configbool):
1551 return ((opts and opts.get(key)) or
1551 return ((opts and opts.get(key)) or
1552 getter(section, name or key, None, untrusted=untrusted))
1552 getter(section, name or key, None, untrusted=untrusted))
1553 return mdiff.diffopts(
1553 return mdiff.diffopts(
1554 text=opts and opts.get('text'),
1554 text=opts and opts.get('text'),
1555 git=get('git'),
1555 git=get('git'),
1556 nodates=get('nodates'),
1556 nodates=get('nodates'),
1557 showfunc=get('show_function', 'showfunc'),
1557 showfunc=get('show_function', 'showfunc'),
1558 ignorews=get('ignore_all_space', 'ignorews'),
1558 ignorews=get('ignore_all_space', 'ignorews'),
1559 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1559 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1560 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1560 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1561 context=get('unified', getter=ui.config))
1561 context=get('unified', getter=ui.config))
1562
1562
1563 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1563 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1564 losedatafn=None, prefix=''):
1564 losedatafn=None, prefix=''):
1565 '''yields diff of changes to files between two nodes, or node and
1565 '''yields diff of changes to files between two nodes, or node and
1566 working directory.
1566 working directory.
1567
1567
1568 if node1 is None, use first dirstate parent instead.
1568 if node1 is None, use first dirstate parent instead.
1569 if node2 is None, compare node1 with working directory.
1569 if node2 is None, compare node1 with working directory.
1570
1570
1571 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1571 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1572 every time some change cannot be represented with the current
1572 every time some change cannot be represented with the current
1573 patch format. Return False to upgrade to git patch format, True to
1573 patch format. Return False to upgrade to git patch format, True to
1574 accept the loss or raise an exception to abort the diff. It is
1574 accept the loss or raise an exception to abort the diff. It is
1575 called with the name of current file being diffed as 'fn'. If set
1575 called with the name of current file being diffed as 'fn'. If set
1576 to None, patches will always be upgraded to git format when
1576 to None, patches will always be upgraded to git format when
1577 necessary.
1577 necessary.
1578
1578
1579 prefix is a filename prefix that is prepended to all filenames on
1579 prefix is a filename prefix that is prepended to all filenames on
1580 display (used for subrepos).
1580 display (used for subrepos).
1581 '''
1581 '''
1582
1582
1583 if opts is None:
1583 if opts is None:
1584 opts = mdiff.defaultopts
1584 opts = mdiff.defaultopts
1585
1585
1586 if not node1 and not node2:
1586 if not node1 and not node2:
1587 node1 = repo.dirstate.p1()
1587 node1 = repo.dirstate.p1()
1588
1588
1589 def lrugetfilectx():
1589 def lrugetfilectx():
1590 cache = {}
1590 cache = {}
1591 order = []
1591 order = collections.deque()
1592 def getfilectx(f, ctx):
1592 def getfilectx(f, ctx):
1593 fctx = ctx.filectx(f, filelog=cache.get(f))
1593 fctx = ctx.filectx(f, filelog=cache.get(f))
1594 if f not in cache:
1594 if f not in cache:
1595 if len(cache) > 20:
1595 if len(cache) > 20:
1596 del cache[order.pop(0)]
1596 del cache[order.popleft()]
1597 cache[f] = fctx.filelog()
1597 cache[f] = fctx.filelog()
1598 else:
1598 else:
1599 order.remove(f)
1599 order.remove(f)
1600 order.append(f)
1600 order.append(f)
1601 return fctx
1601 return fctx
1602 return getfilectx
1602 return getfilectx
1603 getfilectx = lrugetfilectx()
1603 getfilectx = lrugetfilectx()
1604
1604
1605 ctx1 = repo[node1]
1605 ctx1 = repo[node1]
1606 ctx2 = repo[node2]
1606 ctx2 = repo[node2]
1607
1607
1608 if not changes:
1608 if not changes:
1609 changes = repo.status(ctx1, ctx2, match=match)
1609 changes = repo.status(ctx1, ctx2, match=match)
1610 modified, added, removed = changes[:3]
1610 modified, added, removed = changes[:3]
1611
1611
1612 if not modified and not added and not removed:
1612 if not modified and not added and not removed:
1613 return []
1613 return []
1614
1614
1615 revs = None
1615 revs = None
1616 if not repo.ui.quiet:
1616 if not repo.ui.quiet:
1617 hexfunc = repo.ui.debugflag and hex or short
1617 hexfunc = repo.ui.debugflag and hex or short
1618 revs = [hexfunc(node) for node in [node1, node2] if node]
1618 revs = [hexfunc(node) for node in [node1, node2] if node]
1619
1619
1620 copy = {}
1620 copy = {}
1621 if opts.git or opts.upgrade:
1621 if opts.git or opts.upgrade:
1622 copy = copies.pathcopies(ctx1, ctx2)
1622 copy = copies.pathcopies(ctx1, ctx2)
1623
1623
1624 difffn = (lambda opts, losedata:
1624 difffn = (lambda opts, losedata:
1625 trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1625 trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1626 copy, getfilectx, opts, losedata, prefix))
1626 copy, getfilectx, opts, losedata, prefix))
1627 if opts.upgrade and not opts.git:
1627 if opts.upgrade and not opts.git:
1628 try:
1628 try:
1629 def losedata(fn):
1629 def losedata(fn):
1630 if not losedatafn or not losedatafn(fn=fn):
1630 if not losedatafn or not losedatafn(fn=fn):
1631 raise GitDiffRequired
1631 raise GitDiffRequired
1632 # Buffer the whole output until we are sure it can be generated
1632 # Buffer the whole output until we are sure it can be generated
1633 return list(difffn(opts.copy(git=False), losedata))
1633 return list(difffn(opts.copy(git=False), losedata))
1634 except GitDiffRequired:
1634 except GitDiffRequired:
1635 return difffn(opts.copy(git=True), None)
1635 return difffn(opts.copy(git=True), None)
1636 else:
1636 else:
1637 return difffn(opts, None)
1637 return difffn(opts, None)
1638
1638
1639 def difflabel(func, *args, **kw):
1639 def difflabel(func, *args, **kw):
1640 '''yields 2-tuples of (output, label) based on the output of func()'''
1640 '''yields 2-tuples of (output, label) based on the output of func()'''
1641 headprefixes = [('diff', 'diff.diffline'),
1641 headprefixes = [('diff', 'diff.diffline'),
1642 ('copy', 'diff.extended'),
1642 ('copy', 'diff.extended'),
1643 ('rename', 'diff.extended'),
1643 ('rename', 'diff.extended'),
1644 ('old', 'diff.extended'),
1644 ('old', 'diff.extended'),
1645 ('new', 'diff.extended'),
1645 ('new', 'diff.extended'),
1646 ('deleted', 'diff.extended'),
1646 ('deleted', 'diff.extended'),
1647 ('---', 'diff.file_a'),
1647 ('---', 'diff.file_a'),
1648 ('+++', 'diff.file_b')]
1648 ('+++', 'diff.file_b')]
1649 textprefixes = [('@', 'diff.hunk'),
1649 textprefixes = [('@', 'diff.hunk'),
1650 ('-', 'diff.deleted'),
1650 ('-', 'diff.deleted'),
1651 ('+', 'diff.inserted')]
1651 ('+', 'diff.inserted')]
1652 head = False
1652 head = False
1653 for chunk in func(*args, **kw):
1653 for chunk in func(*args, **kw):
1654 lines = chunk.split('\n')
1654 lines = chunk.split('\n')
1655 for i, line in enumerate(lines):
1655 for i, line in enumerate(lines):
1656 if i != 0:
1656 if i != 0:
1657 yield ('\n', '')
1657 yield ('\n', '')
1658 if head:
1658 if head:
1659 if line.startswith('@'):
1659 if line.startswith('@'):
1660 head = False
1660 head = False
1661 else:
1661 else:
1662 if line and line[0] not in ' +-@\\':
1662 if line and line[0] not in ' +-@\\':
1663 head = True
1663 head = True
1664 stripline = line
1664 stripline = line
1665 if not head and line and line[0] in '+-':
1665 if not head and line and line[0] in '+-':
1666 # highlight trailing whitespace, but only in changed lines
1666 # highlight trailing whitespace, but only in changed lines
1667 stripline = line.rstrip()
1667 stripline = line.rstrip()
1668 prefixes = textprefixes
1668 prefixes = textprefixes
1669 if head:
1669 if head:
1670 prefixes = headprefixes
1670 prefixes = headprefixes
1671 for prefix, label in prefixes:
1671 for prefix, label in prefixes:
1672 if stripline.startswith(prefix):
1672 if stripline.startswith(prefix):
1673 yield (stripline, label)
1673 yield (stripline, label)
1674 break
1674 break
1675 else:
1675 else:
1676 yield (line, '')
1676 yield (line, '')
1677 if line != stripline:
1677 if line != stripline:
1678 yield (line[len(stripline):], 'diff.trailingwhitespace')
1678 yield (line[len(stripline):], 'diff.trailingwhitespace')
1679
1679
1680 def diffui(*args, **kw):
1680 def diffui(*args, **kw):
1681 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
1681 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
1682 return difflabel(diff, *args, **kw)
1682 return difflabel(diff, *args, **kw)
1683
1683
1684
1684
1685 def _addmodehdr(header, omode, nmode):
1685 def _addmodehdr(header, omode, nmode):
1686 if omode != nmode:
1686 if omode != nmode:
1687 header.append('old mode %s\n' % omode)
1687 header.append('old mode %s\n' % omode)
1688 header.append('new mode %s\n' % nmode)
1688 header.append('new mode %s\n' % nmode)
1689
1689
1690 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1690 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1691 copy, getfilectx, opts, losedatafn, prefix):
1691 copy, getfilectx, opts, losedatafn, prefix):
1692
1692
1693 def join(f):
1693 def join(f):
1694 return os.path.join(prefix, f)
1694 return os.path.join(prefix, f)
1695
1695
1696 date1 = util.datestr(ctx1.date())
1696 date1 = util.datestr(ctx1.date())
1697 man1 = ctx1.manifest()
1697 man1 = ctx1.manifest()
1698
1698
1699 gone = set()
1699 gone = set()
1700 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1700 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1701
1701
1702 copyto = dict([(v, k) for k, v in copy.items()])
1702 copyto = dict([(v, k) for k, v in copy.items()])
1703
1703
1704 if opts.git:
1704 if opts.git:
1705 revs = None
1705 revs = None
1706
1706
1707 for f in sorted(modified + added + removed):
1707 for f in sorted(modified + added + removed):
1708 to = None
1708 to = None
1709 tn = None
1709 tn = None
1710 dodiff = True
1710 dodiff = True
1711 header = []
1711 header = []
1712 if f in man1:
1712 if f in man1:
1713 to = getfilectx(f, ctx1).data()
1713 to = getfilectx(f, ctx1).data()
1714 if f not in removed:
1714 if f not in removed:
1715 tn = getfilectx(f, ctx2).data()
1715 tn = getfilectx(f, ctx2).data()
1716 a, b = f, f
1716 a, b = f, f
1717 if opts.git or losedatafn:
1717 if opts.git or losedatafn:
1718 if f in added:
1718 if f in added:
1719 mode = gitmode[ctx2.flags(f)]
1719 mode = gitmode[ctx2.flags(f)]
1720 if f in copy or f in copyto:
1720 if f in copy or f in copyto:
1721 if opts.git:
1721 if opts.git:
1722 if f in copy:
1722 if f in copy:
1723 a = copy[f]
1723 a = copy[f]
1724 else:
1724 else:
1725 a = copyto[f]
1725 a = copyto[f]
1726 omode = gitmode[man1.flags(a)]
1726 omode = gitmode[man1.flags(a)]
1727 _addmodehdr(header, omode, mode)
1727 _addmodehdr(header, omode, mode)
1728 if a in removed and a not in gone:
1728 if a in removed and a not in gone:
1729 op = 'rename'
1729 op = 'rename'
1730 gone.add(a)
1730 gone.add(a)
1731 else:
1731 else:
1732 op = 'copy'
1732 op = 'copy'
1733 header.append('%s from %s\n' % (op, join(a)))
1733 header.append('%s from %s\n' % (op, join(a)))
1734 header.append('%s to %s\n' % (op, join(f)))
1734 header.append('%s to %s\n' % (op, join(f)))
1735 to = getfilectx(a, ctx1).data()
1735 to = getfilectx(a, ctx1).data()
1736 else:
1736 else:
1737 losedatafn(f)
1737 losedatafn(f)
1738 else:
1738 else:
1739 if opts.git:
1739 if opts.git:
1740 header.append('new file mode %s\n' % mode)
1740 header.append('new file mode %s\n' % mode)
1741 elif ctx2.flags(f):
1741 elif ctx2.flags(f):
1742 losedatafn(f)
1742 losedatafn(f)
1743 # In theory, if tn was copied or renamed we should check
1743 # In theory, if tn was copied or renamed we should check
1744 # if the source is binary too but the copy record already
1744 # if the source is binary too but the copy record already
1745 # forces git mode.
1745 # forces git mode.
1746 if util.binary(tn):
1746 if util.binary(tn):
1747 if opts.git:
1747 if opts.git:
1748 dodiff = 'binary'
1748 dodiff = 'binary'
1749 else:
1749 else:
1750 losedatafn(f)
1750 losedatafn(f)
1751 if not opts.git and not tn:
1751 if not opts.git and not tn:
1752 # regular diffs cannot represent new empty file
1752 # regular diffs cannot represent new empty file
1753 losedatafn(f)
1753 losedatafn(f)
1754 elif f in removed:
1754 elif f in removed:
1755 if opts.git:
1755 if opts.git:
1756 # have we already reported a copy above?
1756 # have we already reported a copy above?
1757 if ((f in copy and copy[f] in added
1757 if ((f in copy and copy[f] in added
1758 and copyto[copy[f]] == f) or
1758 and copyto[copy[f]] == f) or
1759 (f in copyto and copyto[f] in added
1759 (f in copyto and copyto[f] in added
1760 and copy[copyto[f]] == f)):
1760 and copy[copyto[f]] == f)):
1761 dodiff = False
1761 dodiff = False
1762 else:
1762 else:
1763 header.append('deleted file mode %s\n' %
1763 header.append('deleted file mode %s\n' %
1764 gitmode[man1.flags(f)])
1764 gitmode[man1.flags(f)])
1765 elif not to or util.binary(to):
1765 elif not to or util.binary(to):
1766 # regular diffs cannot represent empty file deletion
1766 # regular diffs cannot represent empty file deletion
1767 losedatafn(f)
1767 losedatafn(f)
1768 else:
1768 else:
1769 oflag = man1.flags(f)
1769 oflag = man1.flags(f)
1770 nflag = ctx2.flags(f)
1770 nflag = ctx2.flags(f)
1771 binary = util.binary(to) or util.binary(tn)
1771 binary = util.binary(to) or util.binary(tn)
1772 if opts.git:
1772 if opts.git:
1773 _addmodehdr(header, gitmode[oflag], gitmode[nflag])
1773 _addmodehdr(header, gitmode[oflag], gitmode[nflag])
1774 if binary:
1774 if binary:
1775 dodiff = 'binary'
1775 dodiff = 'binary'
1776 elif binary or nflag != oflag:
1776 elif binary or nflag != oflag:
1777 losedatafn(f)
1777 losedatafn(f)
1778 if opts.git:
1778 if opts.git:
1779 header.insert(0, mdiff.diffline(revs, join(a), join(b), opts))
1779 header.insert(0, mdiff.diffline(revs, join(a), join(b), opts))
1780
1780
1781 if dodiff:
1781 if dodiff:
1782 if dodiff == 'binary':
1782 if dodiff == 'binary':
1783 text = b85diff(to, tn)
1783 text = b85diff(to, tn)
1784 else:
1784 else:
1785 text = mdiff.unidiff(to, date1,
1785 text = mdiff.unidiff(to, date1,
1786 # ctx2 date may be dynamic
1786 # ctx2 date may be dynamic
1787 tn, util.datestr(ctx2.date()),
1787 tn, util.datestr(ctx2.date()),
1788 join(a), join(b), revs, opts=opts)
1788 join(a), join(b), revs, opts=opts)
1789 if header and (text or len(header) > 1):
1789 if header and (text or len(header) > 1):
1790 yield ''.join(header)
1790 yield ''.join(header)
1791 if text:
1791 if text:
1792 yield text
1792 yield text
1793
1793
1794 def diffstatsum(stats):
1794 def diffstatsum(stats):
1795 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
1795 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
1796 for f, a, r, b in stats:
1796 for f, a, r, b in stats:
1797 maxfile = max(maxfile, encoding.colwidth(f))
1797 maxfile = max(maxfile, encoding.colwidth(f))
1798 maxtotal = max(maxtotal, a + r)
1798 maxtotal = max(maxtotal, a + r)
1799 addtotal += a
1799 addtotal += a
1800 removetotal += r
1800 removetotal += r
1801 binary = binary or b
1801 binary = binary or b
1802
1802
1803 return maxfile, maxtotal, addtotal, removetotal, binary
1803 return maxfile, maxtotal, addtotal, removetotal, binary
1804
1804
1805 def diffstatdata(lines):
1805 def diffstatdata(lines):
1806 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
1806 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
1807
1807
1808 results = []
1808 results = []
1809 filename, adds, removes, isbinary = None, 0, 0, False
1809 filename, adds, removes, isbinary = None, 0, 0, False
1810
1810
1811 def addresult():
1811 def addresult():
1812 if filename:
1812 if filename:
1813 results.append((filename, adds, removes, isbinary))
1813 results.append((filename, adds, removes, isbinary))
1814
1814
1815 for line in lines:
1815 for line in lines:
1816 if line.startswith('diff'):
1816 if line.startswith('diff'):
1817 addresult()
1817 addresult()
1818 # set numbers to 0 anyway when starting new file
1818 # set numbers to 0 anyway when starting new file
1819 adds, removes, isbinary = 0, 0, False
1819 adds, removes, isbinary = 0, 0, False
1820 if line.startswith('diff --git'):
1820 if line.startswith('diff --git'):
1821 filename = gitre.search(line).group(1)
1821 filename = gitre.search(line).group(1)
1822 elif line.startswith('diff -r'):
1822 elif line.startswith('diff -r'):
1823 # format: "diff -r ... -r ... filename"
1823 # format: "diff -r ... -r ... filename"
1824 filename = diffre.search(line).group(1)
1824 filename = diffre.search(line).group(1)
1825 elif line.startswith('+') and not line.startswith('+++ '):
1825 elif line.startswith('+') and not line.startswith('+++ '):
1826 adds += 1
1826 adds += 1
1827 elif line.startswith('-') and not line.startswith('--- '):
1827 elif line.startswith('-') and not line.startswith('--- '):
1828 removes += 1
1828 removes += 1
1829 elif (line.startswith('GIT binary patch') or
1829 elif (line.startswith('GIT binary patch') or
1830 line.startswith('Binary file')):
1830 line.startswith('Binary file')):
1831 isbinary = True
1831 isbinary = True
1832 addresult()
1832 addresult()
1833 return results
1833 return results
1834
1834
1835 def diffstat(lines, width=80, git=False):
1835 def diffstat(lines, width=80, git=False):
1836 output = []
1836 output = []
1837 stats = diffstatdata(lines)
1837 stats = diffstatdata(lines)
1838 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
1838 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
1839
1839
1840 countwidth = len(str(maxtotal))
1840 countwidth = len(str(maxtotal))
1841 if hasbinary and countwidth < 3:
1841 if hasbinary and countwidth < 3:
1842 countwidth = 3
1842 countwidth = 3
1843 graphwidth = width - countwidth - maxname - 6
1843 graphwidth = width - countwidth - maxname - 6
1844 if graphwidth < 10:
1844 if graphwidth < 10:
1845 graphwidth = 10
1845 graphwidth = 10
1846
1846
1847 def scale(i):
1847 def scale(i):
1848 if maxtotal <= graphwidth:
1848 if maxtotal <= graphwidth:
1849 return i
1849 return i
1850 # If diffstat runs out of room it doesn't print anything,
1850 # If diffstat runs out of room it doesn't print anything,
1851 # which isn't very useful, so always print at least one + or -
1851 # which isn't very useful, so always print at least one + or -
1852 # if there were at least some changes.
1852 # if there were at least some changes.
1853 return max(i * graphwidth // maxtotal, int(bool(i)))
1853 return max(i * graphwidth // maxtotal, int(bool(i)))
1854
1854
1855 for filename, adds, removes, isbinary in stats:
1855 for filename, adds, removes, isbinary in stats:
1856 if isbinary:
1856 if isbinary:
1857 count = 'Bin'
1857 count = 'Bin'
1858 else:
1858 else:
1859 count = adds + removes
1859 count = adds + removes
1860 pluses = '+' * scale(adds)
1860 pluses = '+' * scale(adds)
1861 minuses = '-' * scale(removes)
1861 minuses = '-' * scale(removes)
1862 output.append(' %s%s | %*s %s%s\n' %
1862 output.append(' %s%s | %*s %s%s\n' %
1863 (filename, ' ' * (maxname - encoding.colwidth(filename)),
1863 (filename, ' ' * (maxname - encoding.colwidth(filename)),
1864 countwidth, count, pluses, minuses))
1864 countwidth, count, pluses, minuses))
1865
1865
1866 if stats:
1866 if stats:
1867 output.append(_(' %d files changed, %d insertions(+), '
1867 output.append(_(' %d files changed, %d insertions(+), '
1868 '%d deletions(-)\n')
1868 '%d deletions(-)\n')
1869 % (len(stats), totaladds, totalremoves))
1869 % (len(stats), totaladds, totalremoves))
1870
1870
1871 return ''.join(output)
1871 return ''.join(output)
1872
1872
1873 def diffstatui(*args, **kw):
1873 def diffstatui(*args, **kw):
1874 '''like diffstat(), but yields 2-tuples of (output, label) for
1874 '''like diffstat(), but yields 2-tuples of (output, label) for
1875 ui.write()
1875 ui.write()
1876 '''
1876 '''
1877
1877
1878 for line in diffstat(*args, **kw).splitlines():
1878 for line in diffstat(*args, **kw).splitlines():
1879 if line and line[-1] in '+-':
1879 if line and line[-1] in '+-':
1880 name, graph = line.rsplit(' ', 1)
1880 name, graph = line.rsplit(' ', 1)
1881 yield (name + ' ', '')
1881 yield (name + ' ', '')
1882 m = re.search(r'\++', graph)
1882 m = re.search(r'\++', graph)
1883 if m:
1883 if m:
1884 yield (m.group(0), 'diffstat.inserted')
1884 yield (m.group(0), 'diffstat.inserted')
1885 m = re.search(r'-+', graph)
1885 m = re.search(r'-+', graph)
1886 if m:
1886 if m:
1887 yield (m.group(0), 'diffstat.deleted')
1887 yield (m.group(0), 'diffstat.deleted')
1888 else:
1888 else:
1889 yield (line, '')
1889 yield (line, '')
1890 yield ('\n', '')
1890 yield ('\n', '')
@@ -1,1321 +1,1321 b''
1 # revlog.py - storage back-end for mercurial
1 # revlog.py - storage back-end for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """Storage back-end for Mercurial.
8 """Storage back-end for Mercurial.
9
9
10 This provides efficient delta storage with O(1) retrieve and append
10 This provides efficient delta storage with O(1) retrieve and append
11 and O(changes) merge between branches.
11 and O(changes) merge between branches.
12 """
12 """
13
13
14 # import stuff from node for others to import from revlog
14 # import stuff from node for others to import from revlog
15 from node import bin, hex, nullid, nullrev
15 from node import bin, hex, nullid, nullrev
16 from i18n import _
16 from i18n import _
17 import ancestor, mdiff, parsers, error, util, dagutil
17 import ancestor, mdiff, parsers, error, util, dagutil
18 import struct, zlib, errno
18 import struct, zlib, errno, collections
19
19
20 _pack = struct.pack
20 _pack = struct.pack
21 _unpack = struct.unpack
21 _unpack = struct.unpack
22 _compress = zlib.compress
22 _compress = zlib.compress
23 _decompress = zlib.decompress
23 _decompress = zlib.decompress
24 _sha = util.sha1
24 _sha = util.sha1
25
25
26 # revlog header flags
26 # revlog header flags
27 REVLOGV0 = 0
27 REVLOGV0 = 0
28 REVLOGNG = 1
28 REVLOGNG = 1
29 REVLOGNGINLINEDATA = (1 << 16)
29 REVLOGNGINLINEDATA = (1 << 16)
30 REVLOGGENERALDELTA = (1 << 17)
30 REVLOGGENERALDELTA = (1 << 17)
31 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
31 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
32 REVLOG_DEFAULT_FORMAT = REVLOGNG
32 REVLOG_DEFAULT_FORMAT = REVLOGNG
33 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
33 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
34 REVLOGNG_FLAGS = REVLOGNGINLINEDATA | REVLOGGENERALDELTA
34 REVLOGNG_FLAGS = REVLOGNGINLINEDATA | REVLOGGENERALDELTA
35
35
36 # revlog index flags
36 # revlog index flags
37 REVIDX_KNOWN_FLAGS = 0
37 REVIDX_KNOWN_FLAGS = 0
38
38
39 # max size of revlog with inline data
39 # max size of revlog with inline data
40 _maxinline = 131072
40 _maxinline = 131072
41 _chunksize = 1048576
41 _chunksize = 1048576
42
42
43 RevlogError = error.RevlogError
43 RevlogError = error.RevlogError
44 LookupError = error.LookupError
44 LookupError = error.LookupError
45
45
46 def getoffset(q):
46 def getoffset(q):
47 return int(q >> 16)
47 return int(q >> 16)
48
48
49 def gettype(q):
49 def gettype(q):
50 return int(q & 0xFFFF)
50 return int(q & 0xFFFF)
51
51
52 def offset_type(offset, type):
52 def offset_type(offset, type):
53 return long(long(offset) << 16 | type)
53 return long(long(offset) << 16 | type)
54
54
55 nullhash = _sha(nullid)
55 nullhash = _sha(nullid)
56
56
57 def hash(text, p1, p2):
57 def hash(text, p1, p2):
58 """generate a hash from the given text and its parent hashes
58 """generate a hash from the given text and its parent hashes
59
59
60 This hash combines both the current file contents and its history
60 This hash combines both the current file contents and its history
61 in a manner that makes it easy to distinguish nodes with the same
61 in a manner that makes it easy to distinguish nodes with the same
62 content in the revision graph.
62 content in the revision graph.
63 """
63 """
64 # As of now, if one of the parent node is null, p2 is null
64 # As of now, if one of the parent node is null, p2 is null
65 if p2 == nullid:
65 if p2 == nullid:
66 # deep copy of a hash is faster than creating one
66 # deep copy of a hash is faster than creating one
67 s = nullhash.copy()
67 s = nullhash.copy()
68 s.update(p1)
68 s.update(p1)
69 else:
69 else:
70 # none of the parent nodes are nullid
70 # none of the parent nodes are nullid
71 l = [p1, p2]
71 l = [p1, p2]
72 l.sort()
72 l.sort()
73 s = _sha(l[0])
73 s = _sha(l[0])
74 s.update(l[1])
74 s.update(l[1])
75 s.update(text)
75 s.update(text)
76 return s.digest()
76 return s.digest()
77
77
78 def compress(text):
78 def compress(text):
79 """ generate a possibly-compressed representation of text """
79 """ generate a possibly-compressed representation of text """
80 if not text:
80 if not text:
81 return ("", text)
81 return ("", text)
82 l = len(text)
82 l = len(text)
83 bin = None
83 bin = None
84 if l < 44:
84 if l < 44:
85 pass
85 pass
86 elif l > 1000000:
86 elif l > 1000000:
87 # zlib makes an internal copy, thus doubling memory usage for
87 # zlib makes an internal copy, thus doubling memory usage for
88 # large files, so lets do this in pieces
88 # large files, so lets do this in pieces
89 z = zlib.compressobj()
89 z = zlib.compressobj()
90 p = []
90 p = []
91 pos = 0
91 pos = 0
92 while pos < l:
92 while pos < l:
93 pos2 = pos + 2**20
93 pos2 = pos + 2**20
94 p.append(z.compress(text[pos:pos2]))
94 p.append(z.compress(text[pos:pos2]))
95 pos = pos2
95 pos = pos2
96 p.append(z.flush())
96 p.append(z.flush())
97 if sum(map(len, p)) < l:
97 if sum(map(len, p)) < l:
98 bin = "".join(p)
98 bin = "".join(p)
99 else:
99 else:
100 bin = _compress(text)
100 bin = _compress(text)
101 if bin is None or len(bin) > l:
101 if bin is None or len(bin) > l:
102 if text[0] == '\0':
102 if text[0] == '\0':
103 return ("", text)
103 return ("", text)
104 return ('u', text)
104 return ('u', text)
105 return ("", bin)
105 return ("", bin)
106
106
107 def decompress(bin):
107 def decompress(bin):
108 """ decompress the given input """
108 """ decompress the given input """
109 if not bin:
109 if not bin:
110 return bin
110 return bin
111 t = bin[0]
111 t = bin[0]
112 if t == '\0':
112 if t == '\0':
113 return bin
113 return bin
114 if t == 'x':
114 if t == 'x':
115 return _decompress(bin)
115 return _decompress(bin)
116 if t == 'u':
116 if t == 'u':
117 return bin[1:]
117 return bin[1:]
118 raise RevlogError(_("unknown compression type %r") % t)
118 raise RevlogError(_("unknown compression type %r") % t)
119
119
120 indexformatv0 = ">4l20s20s20s"
120 indexformatv0 = ">4l20s20s20s"
121 v0shaoffset = 56
121 v0shaoffset = 56
122
122
123 class revlogoldio(object):
123 class revlogoldio(object):
124 def __init__(self):
124 def __init__(self):
125 self.size = struct.calcsize(indexformatv0)
125 self.size = struct.calcsize(indexformatv0)
126
126
127 def parseindex(self, data, inline):
127 def parseindex(self, data, inline):
128 s = self.size
128 s = self.size
129 index = []
129 index = []
130 nodemap = {nullid: nullrev}
130 nodemap = {nullid: nullrev}
131 n = off = 0
131 n = off = 0
132 l = len(data)
132 l = len(data)
133 while off + s <= l:
133 while off + s <= l:
134 cur = data[off:off + s]
134 cur = data[off:off + s]
135 off += s
135 off += s
136 e = _unpack(indexformatv0, cur)
136 e = _unpack(indexformatv0, cur)
137 # transform to revlogv1 format
137 # transform to revlogv1 format
138 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
138 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
139 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
139 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
140 index.append(e2)
140 index.append(e2)
141 nodemap[e[6]] = n
141 nodemap[e[6]] = n
142 n += 1
142 n += 1
143
143
144 # add the magic null revision at -1
144 # add the magic null revision at -1
145 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
145 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
146
146
147 return index, nodemap, None
147 return index, nodemap, None
148
148
149 def packentry(self, entry, node, version, rev):
149 def packentry(self, entry, node, version, rev):
150 if gettype(entry[0]):
150 if gettype(entry[0]):
151 raise RevlogError(_("index entry flags need RevlogNG"))
151 raise RevlogError(_("index entry flags need RevlogNG"))
152 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
152 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
153 node(entry[5]), node(entry[6]), entry[7])
153 node(entry[5]), node(entry[6]), entry[7])
154 return _pack(indexformatv0, *e2)
154 return _pack(indexformatv0, *e2)
155
155
156 # index ng:
156 # index ng:
157 # 6 bytes: offset
157 # 6 bytes: offset
158 # 2 bytes: flags
158 # 2 bytes: flags
159 # 4 bytes: compressed length
159 # 4 bytes: compressed length
160 # 4 bytes: uncompressed length
160 # 4 bytes: uncompressed length
161 # 4 bytes: base rev
161 # 4 bytes: base rev
162 # 4 bytes: link rev
162 # 4 bytes: link rev
163 # 4 bytes: parent 1 rev
163 # 4 bytes: parent 1 rev
164 # 4 bytes: parent 2 rev
164 # 4 bytes: parent 2 rev
165 # 32 bytes: nodeid
165 # 32 bytes: nodeid
166 indexformatng = ">Qiiiiii20s12x"
166 indexformatng = ">Qiiiiii20s12x"
167 ngshaoffset = 32
167 ngshaoffset = 32
168 versionformat = ">I"
168 versionformat = ">I"
169
169
170 class revlogio(object):
170 class revlogio(object):
171 def __init__(self):
171 def __init__(self):
172 self.size = struct.calcsize(indexformatng)
172 self.size = struct.calcsize(indexformatng)
173
173
174 def parseindex(self, data, inline):
174 def parseindex(self, data, inline):
175 # call the C implementation to parse the index data
175 # call the C implementation to parse the index data
176 index, cache = parsers.parse_index2(data, inline)
176 index, cache = parsers.parse_index2(data, inline)
177 return index, getattr(index, 'nodemap', None), cache
177 return index, getattr(index, 'nodemap', None), cache
178
178
179 def packentry(self, entry, node, version, rev):
179 def packentry(self, entry, node, version, rev):
180 p = _pack(indexformatng, *entry)
180 p = _pack(indexformatng, *entry)
181 if rev == 0:
181 if rev == 0:
182 p = _pack(versionformat, version) + p[4:]
182 p = _pack(versionformat, version) + p[4:]
183 return p
183 return p
184
184
185 class revlog(object):
185 class revlog(object):
186 """
186 """
187 the underlying revision storage object
187 the underlying revision storage object
188
188
189 A revlog consists of two parts, an index and the revision data.
189 A revlog consists of two parts, an index and the revision data.
190
190
191 The index is a file with a fixed record size containing
191 The index is a file with a fixed record size containing
192 information on each revision, including its nodeid (hash), the
192 information on each revision, including its nodeid (hash), the
193 nodeids of its parents, the position and offset of its data within
193 nodeids of its parents, the position and offset of its data within
194 the data file, and the revision it's based on. Finally, each entry
194 the data file, and the revision it's based on. Finally, each entry
195 contains a linkrev entry that can serve as a pointer to external
195 contains a linkrev entry that can serve as a pointer to external
196 data.
196 data.
197
197
198 The revision data itself is a linear collection of data chunks.
198 The revision data itself is a linear collection of data chunks.
199 Each chunk represents a revision and is usually represented as a
199 Each chunk represents a revision and is usually represented as a
200 delta against the previous chunk. To bound lookup time, runs of
200 delta against the previous chunk. To bound lookup time, runs of
201 deltas are limited to about 2 times the length of the original
201 deltas are limited to about 2 times the length of the original
202 version data. This makes retrieval of a version proportional to
202 version data. This makes retrieval of a version proportional to
203 its size, or O(1) relative to the number of revisions.
203 its size, or O(1) relative to the number of revisions.
204
204
205 Both pieces of the revlog are written to in an append-only
205 Both pieces of the revlog are written to in an append-only
206 fashion, which means we never need to rewrite a file to insert or
206 fashion, which means we never need to rewrite a file to insert or
207 remove data, and can use some simple techniques to avoid the need
207 remove data, and can use some simple techniques to avoid the need
208 for locking while reading.
208 for locking while reading.
209 """
209 """
210 def __init__(self, opener, indexfile):
210 def __init__(self, opener, indexfile):
211 """
211 """
212 create a revlog object
212 create a revlog object
213
213
214 opener is a function that abstracts the file opening operation
214 opener is a function that abstracts the file opening operation
215 and can be used to implement COW semantics or the like.
215 and can be used to implement COW semantics or the like.
216 """
216 """
217 self.indexfile = indexfile
217 self.indexfile = indexfile
218 self.datafile = indexfile[:-2] + ".d"
218 self.datafile = indexfile[:-2] + ".d"
219 self.opener = opener
219 self.opener = opener
220 self._cache = None
220 self._cache = None
221 self._basecache = (0, 0)
221 self._basecache = (0, 0)
222 self._chunkcache = (0, '')
222 self._chunkcache = (0, '')
223 self.index = []
223 self.index = []
224 self._pcache = {}
224 self._pcache = {}
225 self._nodecache = {nullid: nullrev}
225 self._nodecache = {nullid: nullrev}
226 self._nodepos = None
226 self._nodepos = None
227
227
228 v = REVLOG_DEFAULT_VERSION
228 v = REVLOG_DEFAULT_VERSION
229 opts = getattr(opener, 'options', None)
229 opts = getattr(opener, 'options', None)
230 if opts is not None:
230 if opts is not None:
231 if 'revlogv1' in opts:
231 if 'revlogv1' in opts:
232 if 'generaldelta' in opts:
232 if 'generaldelta' in opts:
233 v |= REVLOGGENERALDELTA
233 v |= REVLOGGENERALDELTA
234 else:
234 else:
235 v = 0
235 v = 0
236
236
237 i = ''
237 i = ''
238 self._initempty = True
238 self._initempty = True
239 try:
239 try:
240 f = self.opener(self.indexfile)
240 f = self.opener(self.indexfile)
241 i = f.read()
241 i = f.read()
242 f.close()
242 f.close()
243 if len(i) > 0:
243 if len(i) > 0:
244 v = struct.unpack(versionformat, i[:4])[0]
244 v = struct.unpack(versionformat, i[:4])[0]
245 self._initempty = False
245 self._initempty = False
246 except IOError, inst:
246 except IOError, inst:
247 if inst.errno != errno.ENOENT:
247 if inst.errno != errno.ENOENT:
248 raise
248 raise
249
249
250 self.version = v
250 self.version = v
251 self._inline = v & REVLOGNGINLINEDATA
251 self._inline = v & REVLOGNGINLINEDATA
252 self._generaldelta = v & REVLOGGENERALDELTA
252 self._generaldelta = v & REVLOGGENERALDELTA
253 flags = v & ~0xFFFF
253 flags = v & ~0xFFFF
254 fmt = v & 0xFFFF
254 fmt = v & 0xFFFF
255 if fmt == REVLOGV0 and flags:
255 if fmt == REVLOGV0 and flags:
256 raise RevlogError(_("index %s unknown flags %#04x for format v0")
256 raise RevlogError(_("index %s unknown flags %#04x for format v0")
257 % (self.indexfile, flags >> 16))
257 % (self.indexfile, flags >> 16))
258 elif fmt == REVLOGNG and flags & ~REVLOGNG_FLAGS:
258 elif fmt == REVLOGNG and flags & ~REVLOGNG_FLAGS:
259 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
259 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
260 % (self.indexfile, flags >> 16))
260 % (self.indexfile, flags >> 16))
261 elif fmt > REVLOGNG:
261 elif fmt > REVLOGNG:
262 raise RevlogError(_("index %s unknown format %d")
262 raise RevlogError(_("index %s unknown format %d")
263 % (self.indexfile, fmt))
263 % (self.indexfile, fmt))
264
264
265 self._io = revlogio()
265 self._io = revlogio()
266 if self.version == REVLOGV0:
266 if self.version == REVLOGV0:
267 self._io = revlogoldio()
267 self._io = revlogoldio()
268 try:
268 try:
269 d = self._io.parseindex(i, self._inline)
269 d = self._io.parseindex(i, self._inline)
270 except (ValueError, IndexError):
270 except (ValueError, IndexError):
271 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
271 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
272 self.index, nodemap, self._chunkcache = d
272 self.index, nodemap, self._chunkcache = d
273 if nodemap is not None:
273 if nodemap is not None:
274 self.nodemap = self._nodecache = nodemap
274 self.nodemap = self._nodecache = nodemap
275 if not self._chunkcache:
275 if not self._chunkcache:
276 self._chunkclear()
276 self._chunkclear()
277
277
278 def tip(self):
278 def tip(self):
279 return self.node(len(self.index) - 2)
279 return self.node(len(self.index) - 2)
280 def __len__(self):
280 def __len__(self):
281 return len(self.index) - 1
281 return len(self.index) - 1
282 def __iter__(self):
282 def __iter__(self):
283 for i in xrange(len(self)):
283 for i in xrange(len(self)):
284 yield i
284 yield i
285
285
286 @util.propertycache
286 @util.propertycache
287 def nodemap(self):
287 def nodemap(self):
288 self.rev(self.node(0))
288 self.rev(self.node(0))
289 return self._nodecache
289 return self._nodecache
290
290
291 def hasnode(self, node):
291 def hasnode(self, node):
292 try:
292 try:
293 self.rev(node)
293 self.rev(node)
294 return True
294 return True
295 except KeyError:
295 except KeyError:
296 return False
296 return False
297
297
298 def clearcaches(self):
298 def clearcaches(self):
299 try:
299 try:
300 self._nodecache.clearcaches()
300 self._nodecache.clearcaches()
301 except AttributeError:
301 except AttributeError:
302 self._nodecache = {nullid: nullrev}
302 self._nodecache = {nullid: nullrev}
303 self._nodepos = None
303 self._nodepos = None
304
304
305 def rev(self, node):
305 def rev(self, node):
306 try:
306 try:
307 return self._nodecache[node]
307 return self._nodecache[node]
308 except RevlogError:
308 except RevlogError:
309 # parsers.c radix tree lookup failed
309 # parsers.c radix tree lookup failed
310 raise LookupError(node, self.indexfile, _('no node'))
310 raise LookupError(node, self.indexfile, _('no node'))
311 except KeyError:
311 except KeyError:
312 # pure python cache lookup failed
312 # pure python cache lookup failed
313 n = self._nodecache
313 n = self._nodecache
314 i = self.index
314 i = self.index
315 p = self._nodepos
315 p = self._nodepos
316 if p is None:
316 if p is None:
317 p = len(i) - 2
317 p = len(i) - 2
318 for r in xrange(p, -1, -1):
318 for r in xrange(p, -1, -1):
319 v = i[r][7]
319 v = i[r][7]
320 n[v] = r
320 n[v] = r
321 if v == node:
321 if v == node:
322 self._nodepos = r - 1
322 self._nodepos = r - 1
323 return r
323 return r
324 raise LookupError(node, self.indexfile, _('no node'))
324 raise LookupError(node, self.indexfile, _('no node'))
325
325
326 def node(self, rev):
326 def node(self, rev):
327 return self.index[rev][7]
327 return self.index[rev][7]
328 def linkrev(self, rev):
328 def linkrev(self, rev):
329 return self.index[rev][4]
329 return self.index[rev][4]
330 def parents(self, node):
330 def parents(self, node):
331 i = self.index
331 i = self.index
332 d = i[self.rev(node)]
332 d = i[self.rev(node)]
333 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
333 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
334 def parentrevs(self, rev):
334 def parentrevs(self, rev):
335 return self.index[rev][5:7]
335 return self.index[rev][5:7]
336 def start(self, rev):
336 def start(self, rev):
337 return int(self.index[rev][0] >> 16)
337 return int(self.index[rev][0] >> 16)
338 def end(self, rev):
338 def end(self, rev):
339 return self.start(rev) + self.length(rev)
339 return self.start(rev) + self.length(rev)
340 def length(self, rev):
340 def length(self, rev):
341 return self.index[rev][1]
341 return self.index[rev][1]
342 def chainbase(self, rev):
342 def chainbase(self, rev):
343 index = self.index
343 index = self.index
344 base = index[rev][3]
344 base = index[rev][3]
345 while base != rev:
345 while base != rev:
346 rev = base
346 rev = base
347 base = index[rev][3]
347 base = index[rev][3]
348 return base
348 return base
349 def flags(self, rev):
349 def flags(self, rev):
350 return self.index[rev][0] & 0xFFFF
350 return self.index[rev][0] & 0xFFFF
351 def rawsize(self, rev):
351 def rawsize(self, rev):
352 """return the length of the uncompressed text for a given revision"""
352 """return the length of the uncompressed text for a given revision"""
353 l = self.index[rev][2]
353 l = self.index[rev][2]
354 if l >= 0:
354 if l >= 0:
355 return l
355 return l
356
356
357 t = self.revision(self.node(rev))
357 t = self.revision(self.node(rev))
358 return len(t)
358 return len(t)
359 size = rawsize
359 size = rawsize
360
360
361 def reachable(self, node, stop=None):
361 def reachable(self, node, stop=None):
362 """return the set of all nodes ancestral to a given node, including
362 """return the set of all nodes ancestral to a given node, including
363 the node itself, stopping when stop is matched"""
363 the node itself, stopping when stop is matched"""
364 reachable = set((node,))
364 reachable = set((node,))
365 visit = [node]
365 visit = collections.deque([node])
366 if stop:
366 if stop:
367 stopn = self.rev(stop)
367 stopn = self.rev(stop)
368 else:
368 else:
369 stopn = 0
369 stopn = 0
370 while visit:
370 while visit:
371 n = visit.pop(0)
371 n = visit.popleft()
372 if n == stop:
372 if n == stop:
373 continue
373 continue
374 if n == nullid:
374 if n == nullid:
375 continue
375 continue
376 for p in self.parents(n):
376 for p in self.parents(n):
377 if self.rev(p) < stopn:
377 if self.rev(p) < stopn:
378 continue
378 continue
379 if p not in reachable:
379 if p not in reachable:
380 reachable.add(p)
380 reachable.add(p)
381 visit.append(p)
381 visit.append(p)
382 return reachable
382 return reachable
383
383
384 def ancestors(self, *revs):
384 def ancestors(self, *revs):
385 """Generate the ancestors of 'revs' in reverse topological order.
385 """Generate the ancestors of 'revs' in reverse topological order.
386
386
387 Yield a sequence of revision numbers starting with the parents
387 Yield a sequence of revision numbers starting with the parents
388 of each revision in revs, i.e., each revision is *not* considered
388 of each revision in revs, i.e., each revision is *not* considered
389 an ancestor of itself. Results are in breadth-first order:
389 an ancestor of itself. Results are in breadth-first order:
390 parents of each rev in revs, then parents of those, etc. Result
390 parents of each rev in revs, then parents of those, etc. Result
391 does not include the null revision."""
391 does not include the null revision."""
392 visit = list(revs)
392 visit = collections.deque(revs)
393 seen = set([nullrev])
393 seen = set([nullrev])
394 while visit:
394 while visit:
395 for parent in self.parentrevs(visit.pop(0)):
395 for parent in self.parentrevs(visit.popleft()):
396 if parent not in seen:
396 if parent not in seen:
397 visit.append(parent)
397 visit.append(parent)
398 seen.add(parent)
398 seen.add(parent)
399 yield parent
399 yield parent
400
400
401 def descendants(self, *revs):
401 def descendants(self, *revs):
402 """Generate the descendants of 'revs' in revision order.
402 """Generate the descendants of 'revs' in revision order.
403
403
404 Yield a sequence of revision numbers starting with a child of
404 Yield a sequence of revision numbers starting with a child of
405 some rev in revs, i.e., each revision is *not* considered a
405 some rev in revs, i.e., each revision is *not* considered a
406 descendant of itself. Results are ordered by revision number (a
406 descendant of itself. Results are ordered by revision number (a
407 topological sort)."""
407 topological sort)."""
408 first = min(revs)
408 first = min(revs)
409 if first == nullrev:
409 if first == nullrev:
410 for i in self:
410 for i in self:
411 yield i
411 yield i
412 return
412 return
413
413
414 seen = set(revs)
414 seen = set(revs)
415 for i in xrange(first + 1, len(self)):
415 for i in xrange(first + 1, len(self)):
416 for x in self.parentrevs(i):
416 for x in self.parentrevs(i):
417 if x != nullrev and x in seen:
417 if x != nullrev and x in seen:
418 seen.add(i)
418 seen.add(i)
419 yield i
419 yield i
420 break
420 break
421
421
422 def findcommonmissing(self, common=None, heads=None):
422 def findcommonmissing(self, common=None, heads=None):
423 """Return a tuple of the ancestors of common and the ancestors of heads
423 """Return a tuple of the ancestors of common and the ancestors of heads
424 that are not ancestors of common. In revset terminology, we return the
424 that are not ancestors of common. In revset terminology, we return the
425 tuple:
425 tuple:
426
426
427 ::common, (::heads) - (::common)
427 ::common, (::heads) - (::common)
428
428
429 The list is sorted by revision number, meaning it is
429 The list is sorted by revision number, meaning it is
430 topologically sorted.
430 topologically sorted.
431
431
432 'heads' and 'common' are both lists of node IDs. If heads is
432 'heads' and 'common' are both lists of node IDs. If heads is
433 not supplied, uses all of the revlog's heads. If common is not
433 not supplied, uses all of the revlog's heads. If common is not
434 supplied, uses nullid."""
434 supplied, uses nullid."""
435 if common is None:
435 if common is None:
436 common = [nullid]
436 common = [nullid]
437 if heads is None:
437 if heads is None:
438 heads = self.heads()
438 heads = self.heads()
439
439
440 common = [self.rev(n) for n in common]
440 common = [self.rev(n) for n in common]
441 heads = [self.rev(n) for n in heads]
441 heads = [self.rev(n) for n in heads]
442
442
443 # we want the ancestors, but inclusive
443 # we want the ancestors, but inclusive
444 has = set(self.ancestors(*common))
444 has = set(self.ancestors(*common))
445 has.add(nullrev)
445 has.add(nullrev)
446 has.update(common)
446 has.update(common)
447
447
448 # take all ancestors from heads that aren't in has
448 # take all ancestors from heads that aren't in has
449 missing = set()
449 missing = set()
450 visit = [r for r in heads if r not in has]
450 visit = collections.deque(r for r in heads if r not in has)
451 while visit:
451 while visit:
452 r = visit.pop(0)
452 r = visit.popleft()
453 if r in missing:
453 if r in missing:
454 continue
454 continue
455 else:
455 else:
456 missing.add(r)
456 missing.add(r)
457 for p in self.parentrevs(r):
457 for p in self.parentrevs(r):
458 if p not in has:
458 if p not in has:
459 visit.append(p)
459 visit.append(p)
460 missing = list(missing)
460 missing = list(missing)
461 missing.sort()
461 missing.sort()
462 return has, [self.node(r) for r in missing]
462 return has, [self.node(r) for r in missing]
463
463
464 def findmissing(self, common=None, heads=None):
464 def findmissing(self, common=None, heads=None):
465 """Return the ancestors of heads that are not ancestors of common.
465 """Return the ancestors of heads that are not ancestors of common.
466
466
467 More specifically, return a list of nodes N such that every N
467 More specifically, return a list of nodes N such that every N
468 satisfies the following constraints:
468 satisfies the following constraints:
469
469
470 1. N is an ancestor of some node in 'heads'
470 1. N is an ancestor of some node in 'heads'
471 2. N is not an ancestor of any node in 'common'
471 2. N is not an ancestor of any node in 'common'
472
472
473 The list is sorted by revision number, meaning it is
473 The list is sorted by revision number, meaning it is
474 topologically sorted.
474 topologically sorted.
475
475
476 'heads' and 'common' are both lists of node IDs. If heads is
476 'heads' and 'common' are both lists of node IDs. If heads is
477 not supplied, uses all of the revlog's heads. If common is not
477 not supplied, uses all of the revlog's heads. If common is not
478 supplied, uses nullid."""
478 supplied, uses nullid."""
479 _common, missing = self.findcommonmissing(common, heads)
479 _common, missing = self.findcommonmissing(common, heads)
480 return missing
480 return missing
481
481
482 def nodesbetween(self, roots=None, heads=None):
482 def nodesbetween(self, roots=None, heads=None):
483 """Return a topological path from 'roots' to 'heads'.
483 """Return a topological path from 'roots' to 'heads'.
484
484
485 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
485 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
486 topologically sorted list of all nodes N that satisfy both of
486 topologically sorted list of all nodes N that satisfy both of
487 these constraints:
487 these constraints:
488
488
489 1. N is a descendant of some node in 'roots'
489 1. N is a descendant of some node in 'roots'
490 2. N is an ancestor of some node in 'heads'
490 2. N is an ancestor of some node in 'heads'
491
491
492 Every node is considered to be both a descendant and an ancestor
492 Every node is considered to be both a descendant and an ancestor
493 of itself, so every reachable node in 'roots' and 'heads' will be
493 of itself, so every reachable node in 'roots' and 'heads' will be
494 included in 'nodes'.
494 included in 'nodes'.
495
495
496 'outroots' is the list of reachable nodes in 'roots', i.e., the
496 'outroots' is the list of reachable nodes in 'roots', i.e., the
497 subset of 'roots' that is returned in 'nodes'. Likewise,
497 subset of 'roots' that is returned in 'nodes'. Likewise,
498 'outheads' is the subset of 'heads' that is also in 'nodes'.
498 'outheads' is the subset of 'heads' that is also in 'nodes'.
499
499
500 'roots' and 'heads' are both lists of node IDs. If 'roots' is
500 'roots' and 'heads' are both lists of node IDs. If 'roots' is
501 unspecified, uses nullid as the only root. If 'heads' is
501 unspecified, uses nullid as the only root. If 'heads' is
502 unspecified, uses list of all of the revlog's heads."""
502 unspecified, uses list of all of the revlog's heads."""
503 nonodes = ([], [], [])
503 nonodes = ([], [], [])
504 if roots is not None:
504 if roots is not None:
505 roots = list(roots)
505 roots = list(roots)
506 if not roots:
506 if not roots:
507 return nonodes
507 return nonodes
508 lowestrev = min([self.rev(n) for n in roots])
508 lowestrev = min([self.rev(n) for n in roots])
509 else:
509 else:
510 roots = [nullid] # Everybody's a descendant of nullid
510 roots = [nullid] # Everybody's a descendant of nullid
511 lowestrev = nullrev
511 lowestrev = nullrev
512 if (lowestrev == nullrev) and (heads is None):
512 if (lowestrev == nullrev) and (heads is None):
513 # We want _all_ the nodes!
513 # We want _all_ the nodes!
514 return ([self.node(r) for r in self], [nullid], list(self.heads()))
514 return ([self.node(r) for r in self], [nullid], list(self.heads()))
515 if heads is None:
515 if heads is None:
516 # All nodes are ancestors, so the latest ancestor is the last
516 # All nodes are ancestors, so the latest ancestor is the last
517 # node.
517 # node.
518 highestrev = len(self) - 1
518 highestrev = len(self) - 1
519 # Set ancestors to None to signal that every node is an ancestor.
519 # Set ancestors to None to signal that every node is an ancestor.
520 ancestors = None
520 ancestors = None
521 # Set heads to an empty dictionary for later discovery of heads
521 # Set heads to an empty dictionary for later discovery of heads
522 heads = {}
522 heads = {}
523 else:
523 else:
524 heads = list(heads)
524 heads = list(heads)
525 if not heads:
525 if not heads:
526 return nonodes
526 return nonodes
527 ancestors = set()
527 ancestors = set()
528 # Turn heads into a dictionary so we can remove 'fake' heads.
528 # Turn heads into a dictionary so we can remove 'fake' heads.
529 # Also, later we will be using it to filter out the heads we can't
529 # Also, later we will be using it to filter out the heads we can't
530 # find from roots.
530 # find from roots.
531 heads = dict.fromkeys(heads, False)
531 heads = dict.fromkeys(heads, False)
532 # Start at the top and keep marking parents until we're done.
532 # Start at the top and keep marking parents until we're done.
533 nodestotag = set(heads)
533 nodestotag = set(heads)
534 # Remember where the top was so we can use it as a limit later.
534 # Remember where the top was so we can use it as a limit later.
535 highestrev = max([self.rev(n) for n in nodestotag])
535 highestrev = max([self.rev(n) for n in nodestotag])
536 while nodestotag:
536 while nodestotag:
537 # grab a node to tag
537 # grab a node to tag
538 n = nodestotag.pop()
538 n = nodestotag.pop()
539 # Never tag nullid
539 # Never tag nullid
540 if n == nullid:
540 if n == nullid:
541 continue
541 continue
542 # A node's revision number represents its place in a
542 # A node's revision number represents its place in a
543 # topologically sorted list of nodes.
543 # topologically sorted list of nodes.
544 r = self.rev(n)
544 r = self.rev(n)
545 if r >= lowestrev:
545 if r >= lowestrev:
546 if n not in ancestors:
546 if n not in ancestors:
547 # If we are possibly a descendant of one of the roots
547 # If we are possibly a descendant of one of the roots
548 # and we haven't already been marked as an ancestor
548 # and we haven't already been marked as an ancestor
549 ancestors.add(n) # Mark as ancestor
549 ancestors.add(n) # Mark as ancestor
550 # Add non-nullid parents to list of nodes to tag.
550 # Add non-nullid parents to list of nodes to tag.
551 nodestotag.update([p for p in self.parents(n) if
551 nodestotag.update([p for p in self.parents(n) if
552 p != nullid])
552 p != nullid])
553 elif n in heads: # We've seen it before, is it a fake head?
553 elif n in heads: # We've seen it before, is it a fake head?
554 # So it is, real heads should not be the ancestors of
554 # So it is, real heads should not be the ancestors of
555 # any other heads.
555 # any other heads.
556 heads.pop(n)
556 heads.pop(n)
557 if not ancestors:
557 if not ancestors:
558 return nonodes
558 return nonodes
559 # Now that we have our set of ancestors, we want to remove any
559 # Now that we have our set of ancestors, we want to remove any
560 # roots that are not ancestors.
560 # roots that are not ancestors.
561
561
562 # If one of the roots was nullid, everything is included anyway.
562 # If one of the roots was nullid, everything is included anyway.
563 if lowestrev > nullrev:
563 if lowestrev > nullrev:
564 # But, since we weren't, let's recompute the lowest rev to not
564 # But, since we weren't, let's recompute the lowest rev to not
565 # include roots that aren't ancestors.
565 # include roots that aren't ancestors.
566
566
567 # Filter out roots that aren't ancestors of heads
567 # Filter out roots that aren't ancestors of heads
568 roots = [n for n in roots if n in ancestors]
568 roots = [n for n in roots if n in ancestors]
569 # Recompute the lowest revision
569 # Recompute the lowest revision
570 if roots:
570 if roots:
571 lowestrev = min([self.rev(n) for n in roots])
571 lowestrev = min([self.rev(n) for n in roots])
572 else:
572 else:
573 # No more roots? Return empty list
573 # No more roots? Return empty list
574 return nonodes
574 return nonodes
575 else:
575 else:
576 # We are descending from nullid, and don't need to care about
576 # We are descending from nullid, and don't need to care about
577 # any other roots.
577 # any other roots.
578 lowestrev = nullrev
578 lowestrev = nullrev
579 roots = [nullid]
579 roots = [nullid]
580 # Transform our roots list into a set.
580 # Transform our roots list into a set.
581 descendants = set(roots)
581 descendants = set(roots)
582 # Also, keep the original roots so we can filter out roots that aren't
582 # Also, keep the original roots so we can filter out roots that aren't
583 # 'real' roots (i.e. are descended from other roots).
583 # 'real' roots (i.e. are descended from other roots).
584 roots = descendants.copy()
584 roots = descendants.copy()
585 # Our topologically sorted list of output nodes.
585 # Our topologically sorted list of output nodes.
586 orderedout = []
586 orderedout = []
587 # Don't start at nullid since we don't want nullid in our output list,
587 # Don't start at nullid since we don't want nullid in our output list,
588 # and if nullid shows up in descedents, empty parents will look like
588 # and if nullid shows up in descedents, empty parents will look like
589 # they're descendants.
589 # they're descendants.
590 for r in xrange(max(lowestrev, 0), highestrev + 1):
590 for r in xrange(max(lowestrev, 0), highestrev + 1):
591 n = self.node(r)
591 n = self.node(r)
592 isdescendant = False
592 isdescendant = False
593 if lowestrev == nullrev: # Everybody is a descendant of nullid
593 if lowestrev == nullrev: # Everybody is a descendant of nullid
594 isdescendant = True
594 isdescendant = True
595 elif n in descendants:
595 elif n in descendants:
596 # n is already a descendant
596 # n is already a descendant
597 isdescendant = True
597 isdescendant = True
598 # This check only needs to be done here because all the roots
598 # This check only needs to be done here because all the roots
599 # will start being marked is descendants before the loop.
599 # will start being marked is descendants before the loop.
600 if n in roots:
600 if n in roots:
601 # If n was a root, check if it's a 'real' root.
601 # If n was a root, check if it's a 'real' root.
602 p = tuple(self.parents(n))
602 p = tuple(self.parents(n))
603 # If any of its parents are descendants, it's not a root.
603 # If any of its parents are descendants, it's not a root.
604 if (p[0] in descendants) or (p[1] in descendants):
604 if (p[0] in descendants) or (p[1] in descendants):
605 roots.remove(n)
605 roots.remove(n)
606 else:
606 else:
607 p = tuple(self.parents(n))
607 p = tuple(self.parents(n))
608 # A node is a descendant if either of its parents are
608 # A node is a descendant if either of its parents are
609 # descendants. (We seeded the dependents list with the roots
609 # descendants. (We seeded the dependents list with the roots
610 # up there, remember?)
610 # up there, remember?)
611 if (p[0] in descendants) or (p[1] in descendants):
611 if (p[0] in descendants) or (p[1] in descendants):
612 descendants.add(n)
612 descendants.add(n)
613 isdescendant = True
613 isdescendant = True
614 if isdescendant and ((ancestors is None) or (n in ancestors)):
614 if isdescendant and ((ancestors is None) or (n in ancestors)):
615 # Only include nodes that are both descendants and ancestors.
615 # Only include nodes that are both descendants and ancestors.
616 orderedout.append(n)
616 orderedout.append(n)
617 if (ancestors is not None) and (n in heads):
617 if (ancestors is not None) and (n in heads):
618 # We're trying to figure out which heads are reachable
618 # We're trying to figure out which heads are reachable
619 # from roots.
619 # from roots.
620 # Mark this head as having been reached
620 # Mark this head as having been reached
621 heads[n] = True
621 heads[n] = True
622 elif ancestors is None:
622 elif ancestors is None:
623 # Otherwise, we're trying to discover the heads.
623 # Otherwise, we're trying to discover the heads.
624 # Assume this is a head because if it isn't, the next step
624 # Assume this is a head because if it isn't, the next step
625 # will eventually remove it.
625 # will eventually remove it.
626 heads[n] = True
626 heads[n] = True
627 # But, obviously its parents aren't.
627 # But, obviously its parents aren't.
628 for p in self.parents(n):
628 for p in self.parents(n):
629 heads.pop(p, None)
629 heads.pop(p, None)
630 heads = [n for n, flag in heads.iteritems() if flag]
630 heads = [n for n, flag in heads.iteritems() if flag]
631 roots = list(roots)
631 roots = list(roots)
632 assert orderedout
632 assert orderedout
633 assert roots
633 assert roots
634 assert heads
634 assert heads
635 return (orderedout, roots, heads)
635 return (orderedout, roots, heads)
636
636
637 def headrevs(self):
637 def headrevs(self):
638 try:
638 try:
639 return self.index.headrevs()
639 return self.index.headrevs()
640 except AttributeError:
640 except AttributeError:
641 pass
641 pass
642 count = len(self)
642 count = len(self)
643 if not count:
643 if not count:
644 return [nullrev]
644 return [nullrev]
645 ishead = [1] * (count + 1)
645 ishead = [1] * (count + 1)
646 index = self.index
646 index = self.index
647 for r in xrange(count):
647 for r in xrange(count):
648 e = index[r]
648 e = index[r]
649 ishead[e[5]] = ishead[e[6]] = 0
649 ishead[e[5]] = ishead[e[6]] = 0
650 return [r for r in xrange(count) if ishead[r]]
650 return [r for r in xrange(count) if ishead[r]]
651
651
652 def heads(self, start=None, stop=None):
652 def heads(self, start=None, stop=None):
653 """return the list of all nodes that have no children
653 """return the list of all nodes that have no children
654
654
655 if start is specified, only heads that are descendants of
655 if start is specified, only heads that are descendants of
656 start will be returned
656 start will be returned
657 if stop is specified, it will consider all the revs from stop
657 if stop is specified, it will consider all the revs from stop
658 as if they had no children
658 as if they had no children
659 """
659 """
660 if start is None and stop is None:
660 if start is None and stop is None:
661 if not len(self):
661 if not len(self):
662 return [nullid]
662 return [nullid]
663 return [self.node(r) for r in self.headrevs()]
663 return [self.node(r) for r in self.headrevs()]
664
664
665 if start is None:
665 if start is None:
666 start = nullid
666 start = nullid
667 if stop is None:
667 if stop is None:
668 stop = []
668 stop = []
669 stoprevs = set([self.rev(n) for n in stop])
669 stoprevs = set([self.rev(n) for n in stop])
670 startrev = self.rev(start)
670 startrev = self.rev(start)
671 reachable = set((startrev,))
671 reachable = set((startrev,))
672 heads = set((startrev,))
672 heads = set((startrev,))
673
673
674 parentrevs = self.parentrevs
674 parentrevs = self.parentrevs
675 for r in xrange(startrev + 1, len(self)):
675 for r in xrange(startrev + 1, len(self)):
676 for p in parentrevs(r):
676 for p in parentrevs(r):
677 if p in reachable:
677 if p in reachable:
678 if r not in stoprevs:
678 if r not in stoprevs:
679 reachable.add(r)
679 reachable.add(r)
680 heads.add(r)
680 heads.add(r)
681 if p in heads and p not in stoprevs:
681 if p in heads and p not in stoprevs:
682 heads.remove(p)
682 heads.remove(p)
683
683
684 return [self.node(r) for r in heads]
684 return [self.node(r) for r in heads]
685
685
686 def children(self, node):
686 def children(self, node):
687 """find the children of a given node"""
687 """find the children of a given node"""
688 c = []
688 c = []
689 p = self.rev(node)
689 p = self.rev(node)
690 for r in range(p + 1, len(self)):
690 for r in range(p + 1, len(self)):
691 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
691 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
692 if prevs:
692 if prevs:
693 for pr in prevs:
693 for pr in prevs:
694 if pr == p:
694 if pr == p:
695 c.append(self.node(r))
695 c.append(self.node(r))
696 elif p == nullrev:
696 elif p == nullrev:
697 c.append(self.node(r))
697 c.append(self.node(r))
698 return c
698 return c
699
699
700 def descendant(self, start, end):
700 def descendant(self, start, end):
701 if start == nullrev:
701 if start == nullrev:
702 return True
702 return True
703 for i in self.descendants(start):
703 for i in self.descendants(start):
704 if i == end:
704 if i == end:
705 return True
705 return True
706 elif i > end:
706 elif i > end:
707 break
707 break
708 return False
708 return False
709
709
710 def ancestor(self, a, b):
710 def ancestor(self, a, b):
711 """calculate the least common ancestor of nodes a and b"""
711 """calculate the least common ancestor of nodes a and b"""
712
712
713 # fast path, check if it is a descendant
713 # fast path, check if it is a descendant
714 a, b = self.rev(a), self.rev(b)
714 a, b = self.rev(a), self.rev(b)
715 start, end = sorted((a, b))
715 start, end = sorted((a, b))
716 if self.descendant(start, end):
716 if self.descendant(start, end):
717 return self.node(start)
717 return self.node(start)
718
718
719 def parents(rev):
719 def parents(rev):
720 return [p for p in self.parentrevs(rev) if p != nullrev]
720 return [p for p in self.parentrevs(rev) if p != nullrev]
721
721
722 c = ancestor.ancestor(a, b, parents)
722 c = ancestor.ancestor(a, b, parents)
723 if c is None:
723 if c is None:
724 return nullid
724 return nullid
725
725
726 return self.node(c)
726 return self.node(c)
727
727
728 def _match(self, id):
728 def _match(self, id):
729 if isinstance(id, int):
729 if isinstance(id, int):
730 # rev
730 # rev
731 return self.node(id)
731 return self.node(id)
732 if len(id) == 20:
732 if len(id) == 20:
733 # possibly a binary node
733 # possibly a binary node
734 # odds of a binary node being all hex in ASCII are 1 in 10**25
734 # odds of a binary node being all hex in ASCII are 1 in 10**25
735 try:
735 try:
736 node = id
736 node = id
737 self.rev(node) # quick search the index
737 self.rev(node) # quick search the index
738 return node
738 return node
739 except LookupError:
739 except LookupError:
740 pass # may be partial hex id
740 pass # may be partial hex id
741 try:
741 try:
742 # str(rev)
742 # str(rev)
743 rev = int(id)
743 rev = int(id)
744 if str(rev) != id:
744 if str(rev) != id:
745 raise ValueError
745 raise ValueError
746 if rev < 0:
746 if rev < 0:
747 rev = len(self) + rev
747 rev = len(self) + rev
748 if rev < 0 or rev >= len(self):
748 if rev < 0 or rev >= len(self):
749 raise ValueError
749 raise ValueError
750 return self.node(rev)
750 return self.node(rev)
751 except (ValueError, OverflowError):
751 except (ValueError, OverflowError):
752 pass
752 pass
753 if len(id) == 40:
753 if len(id) == 40:
754 try:
754 try:
755 # a full hex nodeid?
755 # a full hex nodeid?
756 node = bin(id)
756 node = bin(id)
757 self.rev(node)
757 self.rev(node)
758 return node
758 return node
759 except (TypeError, LookupError):
759 except (TypeError, LookupError):
760 pass
760 pass
761
761
762 def _partialmatch(self, id):
762 def _partialmatch(self, id):
763 try:
763 try:
764 return self.index.partialmatch(id)
764 return self.index.partialmatch(id)
765 except RevlogError:
765 except RevlogError:
766 # parsers.c radix tree lookup gave multiple matches
766 # parsers.c radix tree lookup gave multiple matches
767 raise LookupError(id, self.indexfile, _("ambiguous identifier"))
767 raise LookupError(id, self.indexfile, _("ambiguous identifier"))
768 except (AttributeError, ValueError):
768 except (AttributeError, ValueError):
769 # we are pure python, or key was too short to search radix tree
769 # we are pure python, or key was too short to search radix tree
770 pass
770 pass
771
771
772 if id in self._pcache:
772 if id in self._pcache:
773 return self._pcache[id]
773 return self._pcache[id]
774
774
775 if len(id) < 40:
775 if len(id) < 40:
776 try:
776 try:
777 # hex(node)[:...]
777 # hex(node)[:...]
778 l = len(id) // 2 # grab an even number of digits
778 l = len(id) // 2 # grab an even number of digits
779 prefix = bin(id[:l * 2])
779 prefix = bin(id[:l * 2])
780 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
780 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
781 nl = [n for n in nl if hex(n).startswith(id)]
781 nl = [n for n in nl if hex(n).startswith(id)]
782 if len(nl) > 0:
782 if len(nl) > 0:
783 if len(nl) == 1:
783 if len(nl) == 1:
784 self._pcache[id] = nl[0]
784 self._pcache[id] = nl[0]
785 return nl[0]
785 return nl[0]
786 raise LookupError(id, self.indexfile,
786 raise LookupError(id, self.indexfile,
787 _('ambiguous identifier'))
787 _('ambiguous identifier'))
788 return None
788 return None
789 except TypeError:
789 except TypeError:
790 pass
790 pass
791
791
792 def lookup(self, id):
792 def lookup(self, id):
793 """locate a node based on:
793 """locate a node based on:
794 - revision number or str(revision number)
794 - revision number or str(revision number)
795 - nodeid or subset of hex nodeid
795 - nodeid or subset of hex nodeid
796 """
796 """
797 n = self._match(id)
797 n = self._match(id)
798 if n is not None:
798 if n is not None:
799 return n
799 return n
800 n = self._partialmatch(id)
800 n = self._partialmatch(id)
801 if n:
801 if n:
802 return n
802 return n
803
803
804 raise LookupError(id, self.indexfile, _('no match found'))
804 raise LookupError(id, self.indexfile, _('no match found'))
805
805
806 def cmp(self, node, text):
806 def cmp(self, node, text):
807 """compare text with a given file revision
807 """compare text with a given file revision
808
808
809 returns True if text is different than what is stored.
809 returns True if text is different than what is stored.
810 """
810 """
811 p1, p2 = self.parents(node)
811 p1, p2 = self.parents(node)
812 return hash(text, p1, p2) != node
812 return hash(text, p1, p2) != node
813
813
814 def _addchunk(self, offset, data):
814 def _addchunk(self, offset, data):
815 o, d = self._chunkcache
815 o, d = self._chunkcache
816 # try to add to existing cache
816 # try to add to existing cache
817 if o + len(d) == offset and len(d) + len(data) < _chunksize:
817 if o + len(d) == offset and len(d) + len(data) < _chunksize:
818 self._chunkcache = o, d + data
818 self._chunkcache = o, d + data
819 else:
819 else:
820 self._chunkcache = offset, data
820 self._chunkcache = offset, data
821
821
822 def _loadchunk(self, offset, length):
822 def _loadchunk(self, offset, length):
823 if self._inline:
823 if self._inline:
824 df = self.opener(self.indexfile)
824 df = self.opener(self.indexfile)
825 else:
825 else:
826 df = self.opener(self.datafile)
826 df = self.opener(self.datafile)
827
827
828 readahead = max(65536, length)
828 readahead = max(65536, length)
829 df.seek(offset)
829 df.seek(offset)
830 d = df.read(readahead)
830 d = df.read(readahead)
831 df.close()
831 df.close()
832 self._addchunk(offset, d)
832 self._addchunk(offset, d)
833 if readahead > length:
833 if readahead > length:
834 return util.buffer(d, 0, length)
834 return util.buffer(d, 0, length)
835 return d
835 return d
836
836
837 def _getchunk(self, offset, length):
837 def _getchunk(self, offset, length):
838 o, d = self._chunkcache
838 o, d = self._chunkcache
839 l = len(d)
839 l = len(d)
840
840
841 # is it in the cache?
841 # is it in the cache?
842 cachestart = offset - o
842 cachestart = offset - o
843 cacheend = cachestart + length
843 cacheend = cachestart + length
844 if cachestart >= 0 and cacheend <= l:
844 if cachestart >= 0 and cacheend <= l:
845 if cachestart == 0 and cacheend == l:
845 if cachestart == 0 and cacheend == l:
846 return d # avoid a copy
846 return d # avoid a copy
847 return util.buffer(d, cachestart, cacheend - cachestart)
847 return util.buffer(d, cachestart, cacheend - cachestart)
848
848
849 return self._loadchunk(offset, length)
849 return self._loadchunk(offset, length)
850
850
851 def _chunkraw(self, startrev, endrev):
851 def _chunkraw(self, startrev, endrev):
852 start = self.start(startrev)
852 start = self.start(startrev)
853 length = self.end(endrev) - start
853 length = self.end(endrev) - start
854 if self._inline:
854 if self._inline:
855 start += (startrev + 1) * self._io.size
855 start += (startrev + 1) * self._io.size
856 return self._getchunk(start, length)
856 return self._getchunk(start, length)
857
857
858 def _chunk(self, rev):
858 def _chunk(self, rev):
859 return decompress(self._chunkraw(rev, rev))
859 return decompress(self._chunkraw(rev, rev))
860
860
861 def _chunkbase(self, rev):
861 def _chunkbase(self, rev):
862 return self._chunk(rev)
862 return self._chunk(rev)
863
863
864 def _chunkclear(self):
864 def _chunkclear(self):
865 self._chunkcache = (0, '')
865 self._chunkcache = (0, '')
866
866
867 def deltaparent(self, rev):
867 def deltaparent(self, rev):
868 """return deltaparent of the given revision"""
868 """return deltaparent of the given revision"""
869 base = self.index[rev][3]
869 base = self.index[rev][3]
870 if base == rev:
870 if base == rev:
871 return nullrev
871 return nullrev
872 elif self._generaldelta:
872 elif self._generaldelta:
873 return base
873 return base
874 else:
874 else:
875 return rev - 1
875 return rev - 1
876
876
877 def revdiff(self, rev1, rev2):
877 def revdiff(self, rev1, rev2):
878 """return or calculate a delta between two revisions"""
878 """return or calculate a delta between two revisions"""
879 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
879 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
880 return str(self._chunk(rev2))
880 return str(self._chunk(rev2))
881
881
882 return mdiff.textdiff(self.revision(rev1),
882 return mdiff.textdiff(self.revision(rev1),
883 self.revision(rev2))
883 self.revision(rev2))
884
884
885 def revision(self, nodeorrev):
885 def revision(self, nodeorrev):
886 """return an uncompressed revision of a given node or revision
886 """return an uncompressed revision of a given node or revision
887 number.
887 number.
888 """
888 """
889 if isinstance(nodeorrev, int):
889 if isinstance(nodeorrev, int):
890 rev = nodeorrev
890 rev = nodeorrev
891 node = self.node(rev)
891 node = self.node(rev)
892 else:
892 else:
893 node = nodeorrev
893 node = nodeorrev
894 rev = None
894 rev = None
895
895
896 cachedrev = None
896 cachedrev = None
897 if node == nullid:
897 if node == nullid:
898 return ""
898 return ""
899 if self._cache:
899 if self._cache:
900 if self._cache[0] == node:
900 if self._cache[0] == node:
901 return self._cache[2]
901 return self._cache[2]
902 cachedrev = self._cache[1]
902 cachedrev = self._cache[1]
903
903
904 # look up what we need to read
904 # look up what we need to read
905 text = None
905 text = None
906 if rev is None:
906 if rev is None:
907 rev = self.rev(node)
907 rev = self.rev(node)
908
908
909 # check rev flags
909 # check rev flags
910 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
910 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
911 raise RevlogError(_('incompatible revision flag %x') %
911 raise RevlogError(_('incompatible revision flag %x') %
912 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
912 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
913
913
914 # build delta chain
914 # build delta chain
915 chain = []
915 chain = []
916 index = self.index # for performance
916 index = self.index # for performance
917 generaldelta = self._generaldelta
917 generaldelta = self._generaldelta
918 iterrev = rev
918 iterrev = rev
919 e = index[iterrev]
919 e = index[iterrev]
920 while iterrev != e[3] and iterrev != cachedrev:
920 while iterrev != e[3] and iterrev != cachedrev:
921 chain.append(iterrev)
921 chain.append(iterrev)
922 if generaldelta:
922 if generaldelta:
923 iterrev = e[3]
923 iterrev = e[3]
924 else:
924 else:
925 iterrev -= 1
925 iterrev -= 1
926 e = index[iterrev]
926 e = index[iterrev]
927 chain.reverse()
927 chain.reverse()
928 base = iterrev
928 base = iterrev
929
929
930 if iterrev == cachedrev:
930 if iterrev == cachedrev:
931 # cache hit
931 # cache hit
932 text = self._cache[2]
932 text = self._cache[2]
933
933
934 # drop cache to save memory
934 # drop cache to save memory
935 self._cache = None
935 self._cache = None
936
936
937 self._chunkraw(base, rev)
937 self._chunkraw(base, rev)
938 if text is None:
938 if text is None:
939 text = str(self._chunkbase(base))
939 text = str(self._chunkbase(base))
940
940
941 bins = [self._chunk(r) for r in chain]
941 bins = [self._chunk(r) for r in chain]
942 text = mdiff.patches(text, bins)
942 text = mdiff.patches(text, bins)
943
943
944 text = self._checkhash(text, node, rev)
944 text = self._checkhash(text, node, rev)
945
945
946 self._cache = (node, rev, text)
946 self._cache = (node, rev, text)
947 return text
947 return text
948
948
949 def _checkhash(self, text, node, rev):
949 def _checkhash(self, text, node, rev):
950 p1, p2 = self.parents(node)
950 p1, p2 = self.parents(node)
951 if node != hash(text, p1, p2):
951 if node != hash(text, p1, p2):
952 raise RevlogError(_("integrity check failed on %s:%d")
952 raise RevlogError(_("integrity check failed on %s:%d")
953 % (self.indexfile, rev))
953 % (self.indexfile, rev))
954 return text
954 return text
955
955
956 def checkinlinesize(self, tr, fp=None):
956 def checkinlinesize(self, tr, fp=None):
957 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
957 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
958 return
958 return
959
959
960 trinfo = tr.find(self.indexfile)
960 trinfo = tr.find(self.indexfile)
961 if trinfo is None:
961 if trinfo is None:
962 raise RevlogError(_("%s not found in the transaction")
962 raise RevlogError(_("%s not found in the transaction")
963 % self.indexfile)
963 % self.indexfile)
964
964
965 trindex = trinfo[2]
965 trindex = trinfo[2]
966 dataoff = self.start(trindex)
966 dataoff = self.start(trindex)
967
967
968 tr.add(self.datafile, dataoff)
968 tr.add(self.datafile, dataoff)
969
969
970 if fp:
970 if fp:
971 fp.flush()
971 fp.flush()
972 fp.close()
972 fp.close()
973
973
974 df = self.opener(self.datafile, 'w')
974 df = self.opener(self.datafile, 'w')
975 try:
975 try:
976 for r in self:
976 for r in self:
977 df.write(self._chunkraw(r, r))
977 df.write(self._chunkraw(r, r))
978 finally:
978 finally:
979 df.close()
979 df.close()
980
980
981 fp = self.opener(self.indexfile, 'w', atomictemp=True)
981 fp = self.opener(self.indexfile, 'w', atomictemp=True)
982 self.version &= ~(REVLOGNGINLINEDATA)
982 self.version &= ~(REVLOGNGINLINEDATA)
983 self._inline = False
983 self._inline = False
984 for i in self:
984 for i in self:
985 e = self._io.packentry(self.index[i], self.node, self.version, i)
985 e = self._io.packentry(self.index[i], self.node, self.version, i)
986 fp.write(e)
986 fp.write(e)
987
987
988 # if we don't call close, the temp file will never replace the
988 # if we don't call close, the temp file will never replace the
989 # real index
989 # real index
990 fp.close()
990 fp.close()
991
991
992 tr.replace(self.indexfile, trindex * self._io.size)
992 tr.replace(self.indexfile, trindex * self._io.size)
993 self._chunkclear()
993 self._chunkclear()
994
994
995 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None):
995 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None):
996 """add a revision to the log
996 """add a revision to the log
997
997
998 text - the revision data to add
998 text - the revision data to add
999 transaction - the transaction object used for rollback
999 transaction - the transaction object used for rollback
1000 link - the linkrev data to add
1000 link - the linkrev data to add
1001 p1, p2 - the parent nodeids of the revision
1001 p1, p2 - the parent nodeids of the revision
1002 cachedelta - an optional precomputed delta
1002 cachedelta - an optional precomputed delta
1003 """
1003 """
1004 node = hash(text, p1, p2)
1004 node = hash(text, p1, p2)
1005 if node in self.nodemap:
1005 if node in self.nodemap:
1006 return node
1006 return node
1007
1007
1008 dfh = None
1008 dfh = None
1009 if not self._inline:
1009 if not self._inline:
1010 dfh = self.opener(self.datafile, "a")
1010 dfh = self.opener(self.datafile, "a")
1011 ifh = self.opener(self.indexfile, "a+")
1011 ifh = self.opener(self.indexfile, "a+")
1012 try:
1012 try:
1013 return self._addrevision(node, text, transaction, link, p1, p2,
1013 return self._addrevision(node, text, transaction, link, p1, p2,
1014 cachedelta, ifh, dfh)
1014 cachedelta, ifh, dfh)
1015 finally:
1015 finally:
1016 if dfh:
1016 if dfh:
1017 dfh.close()
1017 dfh.close()
1018 ifh.close()
1018 ifh.close()
1019
1019
1020 def _addrevision(self, node, text, transaction, link, p1, p2,
1020 def _addrevision(self, node, text, transaction, link, p1, p2,
1021 cachedelta, ifh, dfh):
1021 cachedelta, ifh, dfh):
1022 """internal function to add revisions to the log
1022 """internal function to add revisions to the log
1023
1023
1024 see addrevision for argument descriptions.
1024 see addrevision for argument descriptions.
1025 invariants:
1025 invariants:
1026 - text is optional (can be None); if not set, cachedelta must be set.
1026 - text is optional (can be None); if not set, cachedelta must be set.
1027 if both are set, they must correspond to eachother.
1027 if both are set, they must correspond to eachother.
1028 """
1028 """
1029 btext = [text]
1029 btext = [text]
1030 def buildtext():
1030 def buildtext():
1031 if btext[0] is not None:
1031 if btext[0] is not None:
1032 return btext[0]
1032 return btext[0]
1033 # flush any pending writes here so we can read it in revision
1033 # flush any pending writes here so we can read it in revision
1034 if dfh:
1034 if dfh:
1035 dfh.flush()
1035 dfh.flush()
1036 ifh.flush()
1036 ifh.flush()
1037 basetext = self.revision(self.node(cachedelta[0]))
1037 basetext = self.revision(self.node(cachedelta[0]))
1038 btext[0] = mdiff.patch(basetext, cachedelta[1])
1038 btext[0] = mdiff.patch(basetext, cachedelta[1])
1039 chk = hash(btext[0], p1, p2)
1039 chk = hash(btext[0], p1, p2)
1040 if chk != node:
1040 if chk != node:
1041 raise RevlogError(_("consistency error in delta"))
1041 raise RevlogError(_("consistency error in delta"))
1042 return btext[0]
1042 return btext[0]
1043
1043
1044 def builddelta(rev):
1044 def builddelta(rev):
1045 # can we use the cached delta?
1045 # can we use the cached delta?
1046 if cachedelta and cachedelta[0] == rev:
1046 if cachedelta and cachedelta[0] == rev:
1047 delta = cachedelta[1]
1047 delta = cachedelta[1]
1048 else:
1048 else:
1049 t = buildtext()
1049 t = buildtext()
1050 ptext = self.revision(self.node(rev))
1050 ptext = self.revision(self.node(rev))
1051 delta = mdiff.textdiff(ptext, t)
1051 delta = mdiff.textdiff(ptext, t)
1052 data = compress(delta)
1052 data = compress(delta)
1053 l = len(data[1]) + len(data[0])
1053 l = len(data[1]) + len(data[0])
1054 if basecache[0] == rev:
1054 if basecache[0] == rev:
1055 chainbase = basecache[1]
1055 chainbase = basecache[1]
1056 else:
1056 else:
1057 chainbase = self.chainbase(rev)
1057 chainbase = self.chainbase(rev)
1058 dist = l + offset - self.start(chainbase)
1058 dist = l + offset - self.start(chainbase)
1059 if self._generaldelta:
1059 if self._generaldelta:
1060 base = rev
1060 base = rev
1061 else:
1061 else:
1062 base = chainbase
1062 base = chainbase
1063 return dist, l, data, base, chainbase
1063 return dist, l, data, base, chainbase
1064
1064
1065 curr = len(self)
1065 curr = len(self)
1066 prev = curr - 1
1066 prev = curr - 1
1067 base = chainbase = curr
1067 base = chainbase = curr
1068 offset = self.end(prev)
1068 offset = self.end(prev)
1069 flags = 0
1069 flags = 0
1070 d = None
1070 d = None
1071 basecache = self._basecache
1071 basecache = self._basecache
1072 p1r, p2r = self.rev(p1), self.rev(p2)
1072 p1r, p2r = self.rev(p1), self.rev(p2)
1073
1073
1074 # should we try to build a delta?
1074 # should we try to build a delta?
1075 if prev != nullrev:
1075 if prev != nullrev:
1076 if self._generaldelta:
1076 if self._generaldelta:
1077 if p1r >= basecache[1]:
1077 if p1r >= basecache[1]:
1078 d = builddelta(p1r)
1078 d = builddelta(p1r)
1079 elif p2r >= basecache[1]:
1079 elif p2r >= basecache[1]:
1080 d = builddelta(p2r)
1080 d = builddelta(p2r)
1081 else:
1081 else:
1082 d = builddelta(prev)
1082 d = builddelta(prev)
1083 else:
1083 else:
1084 d = builddelta(prev)
1084 d = builddelta(prev)
1085 dist, l, data, base, chainbase = d
1085 dist, l, data, base, chainbase = d
1086
1086
1087 # full versions are inserted when the needed deltas
1087 # full versions are inserted when the needed deltas
1088 # become comparable to the uncompressed text
1088 # become comparable to the uncompressed text
1089 if text is None:
1089 if text is None:
1090 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1090 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1091 cachedelta[1])
1091 cachedelta[1])
1092 else:
1092 else:
1093 textlen = len(text)
1093 textlen = len(text)
1094 if d is None or dist > textlen * 2:
1094 if d is None or dist > textlen * 2:
1095 text = buildtext()
1095 text = buildtext()
1096 data = compress(text)
1096 data = compress(text)
1097 l = len(data[1]) + len(data[0])
1097 l = len(data[1]) + len(data[0])
1098 base = chainbase = curr
1098 base = chainbase = curr
1099
1099
1100 e = (offset_type(offset, flags), l, textlen,
1100 e = (offset_type(offset, flags), l, textlen,
1101 base, link, p1r, p2r, node)
1101 base, link, p1r, p2r, node)
1102 self.index.insert(-1, e)
1102 self.index.insert(-1, e)
1103 self.nodemap[node] = curr
1103 self.nodemap[node] = curr
1104
1104
1105 entry = self._io.packentry(e, self.node, self.version, curr)
1105 entry = self._io.packentry(e, self.node, self.version, curr)
1106 if not self._inline:
1106 if not self._inline:
1107 transaction.add(self.datafile, offset)
1107 transaction.add(self.datafile, offset)
1108 transaction.add(self.indexfile, curr * len(entry))
1108 transaction.add(self.indexfile, curr * len(entry))
1109 if data[0]:
1109 if data[0]:
1110 dfh.write(data[0])
1110 dfh.write(data[0])
1111 dfh.write(data[1])
1111 dfh.write(data[1])
1112 dfh.flush()
1112 dfh.flush()
1113 ifh.write(entry)
1113 ifh.write(entry)
1114 else:
1114 else:
1115 offset += curr * self._io.size
1115 offset += curr * self._io.size
1116 transaction.add(self.indexfile, offset, curr)
1116 transaction.add(self.indexfile, offset, curr)
1117 ifh.write(entry)
1117 ifh.write(entry)
1118 ifh.write(data[0])
1118 ifh.write(data[0])
1119 ifh.write(data[1])
1119 ifh.write(data[1])
1120 self.checkinlinesize(transaction, ifh)
1120 self.checkinlinesize(transaction, ifh)
1121
1121
1122 if type(text) == str: # only accept immutable objects
1122 if type(text) == str: # only accept immutable objects
1123 self._cache = (node, curr, text)
1123 self._cache = (node, curr, text)
1124 self._basecache = (curr, chainbase)
1124 self._basecache = (curr, chainbase)
1125 return node
1125 return node
1126
1126
1127 def group(self, nodelist, bundler, reorder=None):
1127 def group(self, nodelist, bundler, reorder=None):
1128 """Calculate a delta group, yielding a sequence of changegroup chunks
1128 """Calculate a delta group, yielding a sequence of changegroup chunks
1129 (strings).
1129 (strings).
1130
1130
1131 Given a list of changeset revs, return a set of deltas and
1131 Given a list of changeset revs, return a set of deltas and
1132 metadata corresponding to nodes. The first delta is
1132 metadata corresponding to nodes. The first delta is
1133 first parent(nodelist[0]) -> nodelist[0], the receiver is
1133 first parent(nodelist[0]) -> nodelist[0], the receiver is
1134 guaranteed to have this parent as it has all history before
1134 guaranteed to have this parent as it has all history before
1135 these changesets. In the case firstparent is nullrev the
1135 these changesets. In the case firstparent is nullrev the
1136 changegroup starts with a full revision.
1136 changegroup starts with a full revision.
1137 """
1137 """
1138
1138
1139 # if we don't have any revisions touched by these changesets, bail
1139 # if we don't have any revisions touched by these changesets, bail
1140 if len(nodelist) == 0:
1140 if len(nodelist) == 0:
1141 yield bundler.close()
1141 yield bundler.close()
1142 return
1142 return
1143
1143
1144 # for generaldelta revlogs, we linearize the revs; this will both be
1144 # for generaldelta revlogs, we linearize the revs; this will both be
1145 # much quicker and generate a much smaller bundle
1145 # much quicker and generate a much smaller bundle
1146 if (self._generaldelta and reorder is not False) or reorder:
1146 if (self._generaldelta and reorder is not False) or reorder:
1147 dag = dagutil.revlogdag(self)
1147 dag = dagutil.revlogdag(self)
1148 revs = set(self.rev(n) for n in nodelist)
1148 revs = set(self.rev(n) for n in nodelist)
1149 revs = dag.linearize(revs)
1149 revs = dag.linearize(revs)
1150 else:
1150 else:
1151 revs = sorted([self.rev(n) for n in nodelist])
1151 revs = sorted([self.rev(n) for n in nodelist])
1152
1152
1153 # add the parent of the first rev
1153 # add the parent of the first rev
1154 p = self.parentrevs(revs[0])[0]
1154 p = self.parentrevs(revs[0])[0]
1155 revs.insert(0, p)
1155 revs.insert(0, p)
1156
1156
1157 # build deltas
1157 # build deltas
1158 for r in xrange(len(revs) - 1):
1158 for r in xrange(len(revs) - 1):
1159 prev, curr = revs[r], revs[r + 1]
1159 prev, curr = revs[r], revs[r + 1]
1160 for c in bundler.revchunk(self, curr, prev):
1160 for c in bundler.revchunk(self, curr, prev):
1161 yield c
1161 yield c
1162
1162
1163 yield bundler.close()
1163 yield bundler.close()
1164
1164
1165 def addgroup(self, bundle, linkmapper, transaction):
1165 def addgroup(self, bundle, linkmapper, transaction):
1166 """
1166 """
1167 add a delta group
1167 add a delta group
1168
1168
1169 given a set of deltas, add them to the revision log. the
1169 given a set of deltas, add them to the revision log. the
1170 first delta is against its parent, which should be in our
1170 first delta is against its parent, which should be in our
1171 log, the rest are against the previous delta.
1171 log, the rest are against the previous delta.
1172 """
1172 """
1173
1173
1174 # track the base of the current delta log
1174 # track the base of the current delta log
1175 content = []
1175 content = []
1176 node = None
1176 node = None
1177
1177
1178 r = len(self)
1178 r = len(self)
1179 end = 0
1179 end = 0
1180 if r:
1180 if r:
1181 end = self.end(r - 1)
1181 end = self.end(r - 1)
1182 ifh = self.opener(self.indexfile, "a+")
1182 ifh = self.opener(self.indexfile, "a+")
1183 isize = r * self._io.size
1183 isize = r * self._io.size
1184 if self._inline:
1184 if self._inline:
1185 transaction.add(self.indexfile, end + isize, r)
1185 transaction.add(self.indexfile, end + isize, r)
1186 dfh = None
1186 dfh = None
1187 else:
1187 else:
1188 transaction.add(self.indexfile, isize, r)
1188 transaction.add(self.indexfile, isize, r)
1189 transaction.add(self.datafile, end)
1189 transaction.add(self.datafile, end)
1190 dfh = self.opener(self.datafile, "a")
1190 dfh = self.opener(self.datafile, "a")
1191
1191
1192 try:
1192 try:
1193 # loop through our set of deltas
1193 # loop through our set of deltas
1194 chain = None
1194 chain = None
1195 while True:
1195 while True:
1196 chunkdata = bundle.deltachunk(chain)
1196 chunkdata = bundle.deltachunk(chain)
1197 if not chunkdata:
1197 if not chunkdata:
1198 break
1198 break
1199 node = chunkdata['node']
1199 node = chunkdata['node']
1200 p1 = chunkdata['p1']
1200 p1 = chunkdata['p1']
1201 p2 = chunkdata['p2']
1201 p2 = chunkdata['p2']
1202 cs = chunkdata['cs']
1202 cs = chunkdata['cs']
1203 deltabase = chunkdata['deltabase']
1203 deltabase = chunkdata['deltabase']
1204 delta = chunkdata['delta']
1204 delta = chunkdata['delta']
1205
1205
1206 content.append(node)
1206 content.append(node)
1207
1207
1208 link = linkmapper(cs)
1208 link = linkmapper(cs)
1209 if node in self.nodemap:
1209 if node in self.nodemap:
1210 # this can happen if two branches make the same change
1210 # this can happen if two branches make the same change
1211 chain = node
1211 chain = node
1212 continue
1212 continue
1213
1213
1214 for p in (p1, p2):
1214 for p in (p1, p2):
1215 if p not in self.nodemap:
1215 if p not in self.nodemap:
1216 raise LookupError(p, self.indexfile,
1216 raise LookupError(p, self.indexfile,
1217 _('unknown parent'))
1217 _('unknown parent'))
1218
1218
1219 if deltabase not in self.nodemap:
1219 if deltabase not in self.nodemap:
1220 raise LookupError(deltabase, self.indexfile,
1220 raise LookupError(deltabase, self.indexfile,
1221 _('unknown delta base'))
1221 _('unknown delta base'))
1222
1222
1223 baserev = self.rev(deltabase)
1223 baserev = self.rev(deltabase)
1224 chain = self._addrevision(node, None, transaction, link,
1224 chain = self._addrevision(node, None, transaction, link,
1225 p1, p2, (baserev, delta), ifh, dfh)
1225 p1, p2, (baserev, delta), ifh, dfh)
1226 if not dfh and not self._inline:
1226 if not dfh and not self._inline:
1227 # addrevision switched from inline to conventional
1227 # addrevision switched from inline to conventional
1228 # reopen the index
1228 # reopen the index
1229 ifh.close()
1229 ifh.close()
1230 dfh = self.opener(self.datafile, "a")
1230 dfh = self.opener(self.datafile, "a")
1231 ifh = self.opener(self.indexfile, "a")
1231 ifh = self.opener(self.indexfile, "a")
1232 finally:
1232 finally:
1233 if dfh:
1233 if dfh:
1234 dfh.close()
1234 dfh.close()
1235 ifh.close()
1235 ifh.close()
1236
1236
1237 return content
1237 return content
1238
1238
1239 def strip(self, minlink, transaction):
1239 def strip(self, minlink, transaction):
1240 """truncate the revlog on the first revision with a linkrev >= minlink
1240 """truncate the revlog on the first revision with a linkrev >= minlink
1241
1241
1242 This function is called when we're stripping revision minlink and
1242 This function is called when we're stripping revision minlink and
1243 its descendants from the repository.
1243 its descendants from the repository.
1244
1244
1245 We have to remove all revisions with linkrev >= minlink, because
1245 We have to remove all revisions with linkrev >= minlink, because
1246 the equivalent changelog revisions will be renumbered after the
1246 the equivalent changelog revisions will be renumbered after the
1247 strip.
1247 strip.
1248
1248
1249 So we truncate the revlog on the first of these revisions, and
1249 So we truncate the revlog on the first of these revisions, and
1250 trust that the caller has saved the revisions that shouldn't be
1250 trust that the caller has saved the revisions that shouldn't be
1251 removed and that it'll re-add them after this truncation.
1251 removed and that it'll re-add them after this truncation.
1252 """
1252 """
1253 if len(self) == 0:
1253 if len(self) == 0:
1254 return
1254 return
1255
1255
1256 for rev in self:
1256 for rev in self:
1257 if self.index[rev][4] >= minlink:
1257 if self.index[rev][4] >= minlink:
1258 break
1258 break
1259 else:
1259 else:
1260 return
1260 return
1261
1261
1262 # first truncate the files on disk
1262 # first truncate the files on disk
1263 end = self.start(rev)
1263 end = self.start(rev)
1264 if not self._inline:
1264 if not self._inline:
1265 transaction.add(self.datafile, end)
1265 transaction.add(self.datafile, end)
1266 end = rev * self._io.size
1266 end = rev * self._io.size
1267 else:
1267 else:
1268 end += rev * self._io.size
1268 end += rev * self._io.size
1269
1269
1270 transaction.add(self.indexfile, end)
1270 transaction.add(self.indexfile, end)
1271
1271
1272 # then reset internal state in memory to forget those revisions
1272 # then reset internal state in memory to forget those revisions
1273 self._cache = None
1273 self._cache = None
1274 self._chunkclear()
1274 self._chunkclear()
1275 for x in xrange(rev, len(self)):
1275 for x in xrange(rev, len(self)):
1276 del self.nodemap[self.node(x)]
1276 del self.nodemap[self.node(x)]
1277
1277
1278 del self.index[rev:-1]
1278 del self.index[rev:-1]
1279
1279
1280 def checksize(self):
1280 def checksize(self):
1281 expected = 0
1281 expected = 0
1282 if len(self):
1282 if len(self):
1283 expected = max(0, self.end(len(self) - 1))
1283 expected = max(0, self.end(len(self) - 1))
1284
1284
1285 try:
1285 try:
1286 f = self.opener(self.datafile)
1286 f = self.opener(self.datafile)
1287 f.seek(0, 2)
1287 f.seek(0, 2)
1288 actual = f.tell()
1288 actual = f.tell()
1289 f.close()
1289 f.close()
1290 dd = actual - expected
1290 dd = actual - expected
1291 except IOError, inst:
1291 except IOError, inst:
1292 if inst.errno != errno.ENOENT:
1292 if inst.errno != errno.ENOENT:
1293 raise
1293 raise
1294 dd = 0
1294 dd = 0
1295
1295
1296 try:
1296 try:
1297 f = self.opener(self.indexfile)
1297 f = self.opener(self.indexfile)
1298 f.seek(0, 2)
1298 f.seek(0, 2)
1299 actual = f.tell()
1299 actual = f.tell()
1300 f.close()
1300 f.close()
1301 s = self._io.size
1301 s = self._io.size
1302 i = max(0, actual // s)
1302 i = max(0, actual // s)
1303 di = actual - (i * s)
1303 di = actual - (i * s)
1304 if self._inline:
1304 if self._inline:
1305 databytes = 0
1305 databytes = 0
1306 for r in self:
1306 for r in self:
1307 databytes += max(0, self.length(r))
1307 databytes += max(0, self.length(r))
1308 dd = 0
1308 dd = 0
1309 di = actual - len(self) * s - databytes
1309 di = actual - len(self) * s - databytes
1310 except IOError, inst:
1310 except IOError, inst:
1311 if inst.errno != errno.ENOENT:
1311 if inst.errno != errno.ENOENT:
1312 raise
1312 raise
1313 di = 0
1313 di = 0
1314
1314
1315 return (dd, di)
1315 return (dd, di)
1316
1316
1317 def files(self):
1317 def files(self):
1318 res = [self.indexfile]
1318 res = [self.indexfile]
1319 if not self._inline:
1319 if not self._inline:
1320 res.append(self.datafile)
1320 res.append(self.datafile)
1321 return res
1321 return res
@@ -1,1552 +1,1552 b''
1 # revset.py - revision set queries for mercurial
1 # revset.py - revision set queries for mercurial
2 #
2 #
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import re
8 import re, collections
9 import parser, util, error, discovery, hbisect, phases
9 import parser, util, error, discovery, hbisect, phases
10 import node
10 import node
11 import bookmarks as bookmarksmod
11 import bookmarks as bookmarksmod
12 import match as matchmod
12 import match as matchmod
13 from i18n import _
13 from i18n import _
14 import encoding
14 import encoding
15
15
16 def _revancestors(repo, revs, followfirst):
16 def _revancestors(repo, revs, followfirst):
17 """Like revlog.ancestors(), but supports followfirst."""
17 """Like revlog.ancestors(), but supports followfirst."""
18 cut = followfirst and 1 or None
18 cut = followfirst and 1 or None
19 cl = repo.changelog
19 cl = repo.changelog
20 visit = list(revs)
20 visit = collections.deque(revs)
21 seen = set([node.nullrev])
21 seen = set([node.nullrev])
22 while visit:
22 while visit:
23 for parent in cl.parentrevs(visit.pop(0))[:cut]:
23 for parent in cl.parentrevs(visit.popleft())[:cut]:
24 if parent not in seen:
24 if parent not in seen:
25 visit.append(parent)
25 visit.append(parent)
26 seen.add(parent)
26 seen.add(parent)
27 yield parent
27 yield parent
28
28
29 def _revdescendants(repo, revs, followfirst):
29 def _revdescendants(repo, revs, followfirst):
30 """Like revlog.descendants() but supports followfirst."""
30 """Like revlog.descendants() but supports followfirst."""
31 cut = followfirst and 1 or None
31 cut = followfirst and 1 or None
32 cl = repo.changelog
32 cl = repo.changelog
33 first = min(revs)
33 first = min(revs)
34 nullrev = node.nullrev
34 nullrev = node.nullrev
35 if first == nullrev:
35 if first == nullrev:
36 # Are there nodes with a null first parent and a non-null
36 # Are there nodes with a null first parent and a non-null
37 # second one? Maybe. Do we care? Probably not.
37 # second one? Maybe. Do we care? Probably not.
38 for i in cl:
38 for i in cl:
39 yield i
39 yield i
40 return
40 return
41
41
42 seen = set(revs)
42 seen = set(revs)
43 for i in xrange(first + 1, len(cl)):
43 for i in xrange(first + 1, len(cl)):
44 for x in cl.parentrevs(i)[:cut]:
44 for x in cl.parentrevs(i)[:cut]:
45 if x != nullrev and x in seen:
45 if x != nullrev and x in seen:
46 seen.add(i)
46 seen.add(i)
47 yield i
47 yield i
48 break
48 break
49
49
50 elements = {
50 elements = {
51 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
51 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
52 "~": (18, None, ("ancestor", 18)),
52 "~": (18, None, ("ancestor", 18)),
53 "^": (18, None, ("parent", 18), ("parentpost", 18)),
53 "^": (18, None, ("parent", 18), ("parentpost", 18)),
54 "-": (5, ("negate", 19), ("minus", 5)),
54 "-": (5, ("negate", 19), ("minus", 5)),
55 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
55 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
56 ("dagrangepost", 17)),
56 ("dagrangepost", 17)),
57 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
57 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
58 ("dagrangepost", 17)),
58 ("dagrangepost", 17)),
59 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
59 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
60 "not": (10, ("not", 10)),
60 "not": (10, ("not", 10)),
61 "!": (10, ("not", 10)),
61 "!": (10, ("not", 10)),
62 "and": (5, None, ("and", 5)),
62 "and": (5, None, ("and", 5)),
63 "&": (5, None, ("and", 5)),
63 "&": (5, None, ("and", 5)),
64 "or": (4, None, ("or", 4)),
64 "or": (4, None, ("or", 4)),
65 "|": (4, None, ("or", 4)),
65 "|": (4, None, ("or", 4)),
66 "+": (4, None, ("or", 4)),
66 "+": (4, None, ("or", 4)),
67 ",": (2, None, ("list", 2)),
67 ",": (2, None, ("list", 2)),
68 ")": (0, None, None),
68 ")": (0, None, None),
69 "symbol": (0, ("symbol",), None),
69 "symbol": (0, ("symbol",), None),
70 "string": (0, ("string",), None),
70 "string": (0, ("string",), None),
71 "end": (0, None, None),
71 "end": (0, None, None),
72 }
72 }
73
73
74 keywords = set(['and', 'or', 'not'])
74 keywords = set(['and', 'or', 'not'])
75
75
76 def tokenize(program):
76 def tokenize(program):
77 pos, l = 0, len(program)
77 pos, l = 0, len(program)
78 while pos < l:
78 while pos < l:
79 c = program[pos]
79 c = program[pos]
80 if c.isspace(): # skip inter-token whitespace
80 if c.isspace(): # skip inter-token whitespace
81 pass
81 pass
82 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
82 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
83 yield ('::', None, pos)
83 yield ('::', None, pos)
84 pos += 1 # skip ahead
84 pos += 1 # skip ahead
85 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
85 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
86 yield ('..', None, pos)
86 yield ('..', None, pos)
87 pos += 1 # skip ahead
87 pos += 1 # skip ahead
88 elif c in "():,-|&+!~^": # handle simple operators
88 elif c in "():,-|&+!~^": # handle simple operators
89 yield (c, None, pos)
89 yield (c, None, pos)
90 elif (c in '"\'' or c == 'r' and
90 elif (c in '"\'' or c == 'r' and
91 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
91 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
92 if c == 'r':
92 if c == 'r':
93 pos += 1
93 pos += 1
94 c = program[pos]
94 c = program[pos]
95 decode = lambda x: x
95 decode = lambda x: x
96 else:
96 else:
97 decode = lambda x: x.decode('string-escape')
97 decode = lambda x: x.decode('string-escape')
98 pos += 1
98 pos += 1
99 s = pos
99 s = pos
100 while pos < l: # find closing quote
100 while pos < l: # find closing quote
101 d = program[pos]
101 d = program[pos]
102 if d == '\\': # skip over escaped characters
102 if d == '\\': # skip over escaped characters
103 pos += 2
103 pos += 2
104 continue
104 continue
105 if d == c:
105 if d == c:
106 yield ('string', decode(program[s:pos]), s)
106 yield ('string', decode(program[s:pos]), s)
107 break
107 break
108 pos += 1
108 pos += 1
109 else:
109 else:
110 raise error.ParseError(_("unterminated string"), s)
110 raise error.ParseError(_("unterminated string"), s)
111 # gather up a symbol/keyword
111 # gather up a symbol/keyword
112 elif c.isalnum() or c in '._' or ord(c) > 127:
112 elif c.isalnum() or c in '._' or ord(c) > 127:
113 s = pos
113 s = pos
114 pos += 1
114 pos += 1
115 while pos < l: # find end of symbol
115 while pos < l: # find end of symbol
116 d = program[pos]
116 d = program[pos]
117 if not (d.isalnum() or d in "._/" or ord(d) > 127):
117 if not (d.isalnum() or d in "._/" or ord(d) > 127):
118 break
118 break
119 if d == '.' and program[pos - 1] == '.': # special case for ..
119 if d == '.' and program[pos - 1] == '.': # special case for ..
120 pos -= 1
120 pos -= 1
121 break
121 break
122 pos += 1
122 pos += 1
123 sym = program[s:pos]
123 sym = program[s:pos]
124 if sym in keywords: # operator keywords
124 if sym in keywords: # operator keywords
125 yield (sym, None, s)
125 yield (sym, None, s)
126 else:
126 else:
127 yield ('symbol', sym, s)
127 yield ('symbol', sym, s)
128 pos -= 1
128 pos -= 1
129 else:
129 else:
130 raise error.ParseError(_("syntax error"), pos)
130 raise error.ParseError(_("syntax error"), pos)
131 pos += 1
131 pos += 1
132 yield ('end', None, pos)
132 yield ('end', None, pos)
133
133
134 # helpers
134 # helpers
135
135
136 def getstring(x, err):
136 def getstring(x, err):
137 if x and (x[0] == 'string' or x[0] == 'symbol'):
137 if x and (x[0] == 'string' or x[0] == 'symbol'):
138 return x[1]
138 return x[1]
139 raise error.ParseError(err)
139 raise error.ParseError(err)
140
140
141 def getlist(x):
141 def getlist(x):
142 if not x:
142 if not x:
143 return []
143 return []
144 if x[0] == 'list':
144 if x[0] == 'list':
145 return getlist(x[1]) + [x[2]]
145 return getlist(x[1]) + [x[2]]
146 return [x]
146 return [x]
147
147
148 def getargs(x, min, max, err):
148 def getargs(x, min, max, err):
149 l = getlist(x)
149 l = getlist(x)
150 if len(l) < min or (max >= 0 and len(l) > max):
150 if len(l) < min or (max >= 0 and len(l) > max):
151 raise error.ParseError(err)
151 raise error.ParseError(err)
152 return l
152 return l
153
153
154 def getset(repo, subset, x):
154 def getset(repo, subset, x):
155 if not x:
155 if not x:
156 raise error.ParseError(_("missing argument"))
156 raise error.ParseError(_("missing argument"))
157 return methods[x[0]](repo, subset, *x[1:])
157 return methods[x[0]](repo, subset, *x[1:])
158
158
159 # operator methods
159 # operator methods
160
160
161 def stringset(repo, subset, x):
161 def stringset(repo, subset, x):
162 x = repo[x].rev()
162 x = repo[x].rev()
163 if x == -1 and len(subset) == len(repo):
163 if x == -1 and len(subset) == len(repo):
164 return [-1]
164 return [-1]
165 if len(subset) == len(repo) or x in subset:
165 if len(subset) == len(repo) or x in subset:
166 return [x]
166 return [x]
167 return []
167 return []
168
168
169 def symbolset(repo, subset, x):
169 def symbolset(repo, subset, x):
170 if x in symbols:
170 if x in symbols:
171 raise error.ParseError(_("can't use %s here") % x)
171 raise error.ParseError(_("can't use %s here") % x)
172 return stringset(repo, subset, x)
172 return stringset(repo, subset, x)
173
173
174 def rangeset(repo, subset, x, y):
174 def rangeset(repo, subset, x, y):
175 m = getset(repo, subset, x)
175 m = getset(repo, subset, x)
176 if not m:
176 if not m:
177 m = getset(repo, range(len(repo)), x)
177 m = getset(repo, range(len(repo)), x)
178
178
179 n = getset(repo, subset, y)
179 n = getset(repo, subset, y)
180 if not n:
180 if not n:
181 n = getset(repo, range(len(repo)), y)
181 n = getset(repo, range(len(repo)), y)
182
182
183 if not m or not n:
183 if not m or not n:
184 return []
184 return []
185 m, n = m[0], n[-1]
185 m, n = m[0], n[-1]
186
186
187 if m < n:
187 if m < n:
188 r = range(m, n + 1)
188 r = range(m, n + 1)
189 else:
189 else:
190 r = range(m, n - 1, -1)
190 r = range(m, n - 1, -1)
191 s = set(subset)
191 s = set(subset)
192 return [x for x in r if x in s]
192 return [x for x in r if x in s]
193
193
194 def andset(repo, subset, x, y):
194 def andset(repo, subset, x, y):
195 return getset(repo, getset(repo, subset, x), y)
195 return getset(repo, getset(repo, subset, x), y)
196
196
197 def orset(repo, subset, x, y):
197 def orset(repo, subset, x, y):
198 xl = getset(repo, subset, x)
198 xl = getset(repo, subset, x)
199 s = set(xl)
199 s = set(xl)
200 yl = getset(repo, [r for r in subset if r not in s], y)
200 yl = getset(repo, [r for r in subset if r not in s], y)
201 return xl + yl
201 return xl + yl
202
202
203 def notset(repo, subset, x):
203 def notset(repo, subset, x):
204 s = set(getset(repo, subset, x))
204 s = set(getset(repo, subset, x))
205 return [r for r in subset if r not in s]
205 return [r for r in subset if r not in s]
206
206
207 def listset(repo, subset, a, b):
207 def listset(repo, subset, a, b):
208 raise error.ParseError(_("can't use a list in this context"))
208 raise error.ParseError(_("can't use a list in this context"))
209
209
210 def func(repo, subset, a, b):
210 def func(repo, subset, a, b):
211 if a[0] == 'symbol' and a[1] in symbols:
211 if a[0] == 'symbol' and a[1] in symbols:
212 return symbols[a[1]](repo, subset, b)
212 return symbols[a[1]](repo, subset, b)
213 raise error.ParseError(_("not a function: %s") % a[1])
213 raise error.ParseError(_("not a function: %s") % a[1])
214
214
215 # functions
215 # functions
216
216
217 def adds(repo, subset, x):
217 def adds(repo, subset, x):
218 """``adds(pattern)``
218 """``adds(pattern)``
219 Changesets that add a file matching pattern.
219 Changesets that add a file matching pattern.
220 """
220 """
221 # i18n: "adds" is a keyword
221 # i18n: "adds" is a keyword
222 pat = getstring(x, _("adds requires a pattern"))
222 pat = getstring(x, _("adds requires a pattern"))
223 return checkstatus(repo, subset, pat, 1)
223 return checkstatus(repo, subset, pat, 1)
224
224
225 def ancestor(repo, subset, x):
225 def ancestor(repo, subset, x):
226 """``ancestor(single, single)``
226 """``ancestor(single, single)``
227 Greatest common ancestor of the two changesets.
227 Greatest common ancestor of the two changesets.
228 """
228 """
229 # i18n: "ancestor" is a keyword
229 # i18n: "ancestor" is a keyword
230 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
230 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
231 r = range(len(repo))
231 r = range(len(repo))
232 a = getset(repo, r, l[0])
232 a = getset(repo, r, l[0])
233 b = getset(repo, r, l[1])
233 b = getset(repo, r, l[1])
234 if len(a) != 1 or len(b) != 1:
234 if len(a) != 1 or len(b) != 1:
235 # i18n: "ancestor" is a keyword
235 # i18n: "ancestor" is a keyword
236 raise error.ParseError(_("ancestor arguments must be single revisions"))
236 raise error.ParseError(_("ancestor arguments must be single revisions"))
237 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
237 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
238
238
239 return [r for r in an if r in subset]
239 return [r for r in an if r in subset]
240
240
241 def _ancestors(repo, subset, x, followfirst=False):
241 def _ancestors(repo, subset, x, followfirst=False):
242 args = getset(repo, range(len(repo)), x)
242 args = getset(repo, range(len(repo)), x)
243 if not args:
243 if not args:
244 return []
244 return []
245 s = set(_revancestors(repo, args, followfirst)) | set(args)
245 s = set(_revancestors(repo, args, followfirst)) | set(args)
246 return [r for r in subset if r in s]
246 return [r for r in subset if r in s]
247
247
248 def ancestors(repo, subset, x):
248 def ancestors(repo, subset, x):
249 """``ancestors(set)``
249 """``ancestors(set)``
250 Changesets that are ancestors of a changeset in set.
250 Changesets that are ancestors of a changeset in set.
251 """
251 """
252 return _ancestors(repo, subset, x)
252 return _ancestors(repo, subset, x)
253
253
254 def _firstancestors(repo, subset, x):
254 def _firstancestors(repo, subset, x):
255 # ``_firstancestors(set)``
255 # ``_firstancestors(set)``
256 # Like ``ancestors(set)`` but follows only the first parents.
256 # Like ``ancestors(set)`` but follows only the first parents.
257 return _ancestors(repo, subset, x, followfirst=True)
257 return _ancestors(repo, subset, x, followfirst=True)
258
258
259 def ancestorspec(repo, subset, x, n):
259 def ancestorspec(repo, subset, x, n):
260 """``set~n``
260 """``set~n``
261 Changesets that are the Nth ancestor (first parents only) of a changeset
261 Changesets that are the Nth ancestor (first parents only) of a changeset
262 in set.
262 in set.
263 """
263 """
264 try:
264 try:
265 n = int(n[1])
265 n = int(n[1])
266 except (TypeError, ValueError):
266 except (TypeError, ValueError):
267 raise error.ParseError(_("~ expects a number"))
267 raise error.ParseError(_("~ expects a number"))
268 ps = set()
268 ps = set()
269 cl = repo.changelog
269 cl = repo.changelog
270 for r in getset(repo, subset, x):
270 for r in getset(repo, subset, x):
271 for i in range(n):
271 for i in range(n):
272 r = cl.parentrevs(r)[0]
272 r = cl.parentrevs(r)[0]
273 ps.add(r)
273 ps.add(r)
274 return [r for r in subset if r in ps]
274 return [r for r in subset if r in ps]
275
275
276 def author(repo, subset, x):
276 def author(repo, subset, x):
277 """``author(string)``
277 """``author(string)``
278 Alias for ``user(string)``.
278 Alias for ``user(string)``.
279 """
279 """
280 # i18n: "author" is a keyword
280 # i18n: "author" is a keyword
281 n = encoding.lower(getstring(x, _("author requires a string")))
281 n = encoding.lower(getstring(x, _("author requires a string")))
282 return [r for r in subset if n in encoding.lower(repo[r].user())]
282 return [r for r in subset if n in encoding.lower(repo[r].user())]
283
283
284 def bisect(repo, subset, x):
284 def bisect(repo, subset, x):
285 """``bisect(string)``
285 """``bisect(string)``
286 Changesets marked in the specified bisect status:
286 Changesets marked in the specified bisect status:
287
287
288 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
288 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
289 - ``goods``, ``bads`` : csets topologicaly good/bad
289 - ``goods``, ``bads`` : csets topologicaly good/bad
290 - ``range`` : csets taking part in the bisection
290 - ``range`` : csets taking part in the bisection
291 - ``pruned`` : csets that are goods, bads or skipped
291 - ``pruned`` : csets that are goods, bads or skipped
292 - ``untested`` : csets whose fate is yet unknown
292 - ``untested`` : csets whose fate is yet unknown
293 - ``ignored`` : csets ignored due to DAG topology
293 - ``ignored`` : csets ignored due to DAG topology
294 - ``current`` : the cset currently being bisected
294 - ``current`` : the cset currently being bisected
295 """
295 """
296 status = getstring(x, _("bisect requires a string")).lower()
296 status = getstring(x, _("bisect requires a string")).lower()
297 state = set(hbisect.get(repo, status))
297 state = set(hbisect.get(repo, status))
298 return [r for r in subset if r in state]
298 return [r for r in subset if r in state]
299
299
300 # Backward-compatibility
300 # Backward-compatibility
301 # - no help entry so that we do not advertise it any more
301 # - no help entry so that we do not advertise it any more
302 def bisected(repo, subset, x):
302 def bisected(repo, subset, x):
303 return bisect(repo, subset, x)
303 return bisect(repo, subset, x)
304
304
305 def bookmark(repo, subset, x):
305 def bookmark(repo, subset, x):
306 """``bookmark([name])``
306 """``bookmark([name])``
307 The named bookmark or all bookmarks.
307 The named bookmark or all bookmarks.
308 """
308 """
309 # i18n: "bookmark" is a keyword
309 # i18n: "bookmark" is a keyword
310 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
310 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
311 if args:
311 if args:
312 bm = getstring(args[0],
312 bm = getstring(args[0],
313 # i18n: "bookmark" is a keyword
313 # i18n: "bookmark" is a keyword
314 _('the argument to bookmark must be a string'))
314 _('the argument to bookmark must be a string'))
315 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
315 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
316 if not bmrev:
316 if not bmrev:
317 raise util.Abort(_("bookmark '%s' does not exist") % bm)
317 raise util.Abort(_("bookmark '%s' does not exist") % bm)
318 bmrev = repo[bmrev].rev()
318 bmrev = repo[bmrev].rev()
319 return [r for r in subset if r == bmrev]
319 return [r for r in subset if r == bmrev]
320 bms = set([repo[r].rev()
320 bms = set([repo[r].rev()
321 for r in bookmarksmod.listbookmarks(repo).values()])
321 for r in bookmarksmod.listbookmarks(repo).values()])
322 return [r for r in subset if r in bms]
322 return [r for r in subset if r in bms]
323
323
324 def branch(repo, subset, x):
324 def branch(repo, subset, x):
325 """``branch(string or set)``
325 """``branch(string or set)``
326 All changesets belonging to the given branch or the branches of the given
326 All changesets belonging to the given branch or the branches of the given
327 changesets.
327 changesets.
328 """
328 """
329 try:
329 try:
330 b = getstring(x, '')
330 b = getstring(x, '')
331 if b in repo.branchmap():
331 if b in repo.branchmap():
332 return [r for r in subset if repo[r].branch() == b]
332 return [r for r in subset if repo[r].branch() == b]
333 except error.ParseError:
333 except error.ParseError:
334 # not a string, but another revspec, e.g. tip()
334 # not a string, but another revspec, e.g. tip()
335 pass
335 pass
336
336
337 s = getset(repo, range(len(repo)), x)
337 s = getset(repo, range(len(repo)), x)
338 b = set()
338 b = set()
339 for r in s:
339 for r in s:
340 b.add(repo[r].branch())
340 b.add(repo[r].branch())
341 s = set(s)
341 s = set(s)
342 return [r for r in subset if r in s or repo[r].branch() in b]
342 return [r for r in subset if r in s or repo[r].branch() in b]
343
343
344 def checkstatus(repo, subset, pat, field):
344 def checkstatus(repo, subset, pat, field):
345 m = None
345 m = None
346 s = []
346 s = []
347 hasset = matchmod.patkind(pat) == 'set'
347 hasset = matchmod.patkind(pat) == 'set'
348 fname = None
348 fname = None
349 for r in subset:
349 for r in subset:
350 c = repo[r]
350 c = repo[r]
351 if not m or hasset:
351 if not m or hasset:
352 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
352 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
353 if not m.anypats() and len(m.files()) == 1:
353 if not m.anypats() and len(m.files()) == 1:
354 fname = m.files()[0]
354 fname = m.files()[0]
355 if fname is not None:
355 if fname is not None:
356 if fname not in c.files():
356 if fname not in c.files():
357 continue
357 continue
358 else:
358 else:
359 for f in c.files():
359 for f in c.files():
360 if m(f):
360 if m(f):
361 break
361 break
362 else:
362 else:
363 continue
363 continue
364 files = repo.status(c.p1().node(), c.node())[field]
364 files = repo.status(c.p1().node(), c.node())[field]
365 if fname is not None:
365 if fname is not None:
366 if fname in files:
366 if fname in files:
367 s.append(r)
367 s.append(r)
368 else:
368 else:
369 for f in files:
369 for f in files:
370 if m(f):
370 if m(f):
371 s.append(r)
371 s.append(r)
372 break
372 break
373 return s
373 return s
374
374
375 def _children(repo, narrow, parentset):
375 def _children(repo, narrow, parentset):
376 cs = set()
376 cs = set()
377 pr = repo.changelog.parentrevs
377 pr = repo.changelog.parentrevs
378 for r in narrow:
378 for r in narrow:
379 for p in pr(r):
379 for p in pr(r):
380 if p in parentset:
380 if p in parentset:
381 cs.add(r)
381 cs.add(r)
382 return cs
382 return cs
383
383
384 def children(repo, subset, x):
384 def children(repo, subset, x):
385 """``children(set)``
385 """``children(set)``
386 Child changesets of changesets in set.
386 Child changesets of changesets in set.
387 """
387 """
388 s = set(getset(repo, range(len(repo)), x))
388 s = set(getset(repo, range(len(repo)), x))
389 cs = _children(repo, subset, s)
389 cs = _children(repo, subset, s)
390 return [r for r in subset if r in cs]
390 return [r for r in subset if r in cs]
391
391
392 def closed(repo, subset, x):
392 def closed(repo, subset, x):
393 """``closed()``
393 """``closed()``
394 Changeset is closed.
394 Changeset is closed.
395 """
395 """
396 # i18n: "closed" is a keyword
396 # i18n: "closed" is a keyword
397 getargs(x, 0, 0, _("closed takes no arguments"))
397 getargs(x, 0, 0, _("closed takes no arguments"))
398 return [r for r in subset if repo[r].closesbranch()]
398 return [r for r in subset if repo[r].closesbranch()]
399
399
400 def contains(repo, subset, x):
400 def contains(repo, subset, x):
401 """``contains(pattern)``
401 """``contains(pattern)``
402 Revision contains a file matching pattern. See :hg:`help patterns`
402 Revision contains a file matching pattern. See :hg:`help patterns`
403 for information about file patterns.
403 for information about file patterns.
404 """
404 """
405 # i18n: "contains" is a keyword
405 # i18n: "contains" is a keyword
406 pat = getstring(x, _("contains requires a pattern"))
406 pat = getstring(x, _("contains requires a pattern"))
407 m = None
407 m = None
408 s = []
408 s = []
409 if not matchmod.patkind(pat):
409 if not matchmod.patkind(pat):
410 for r in subset:
410 for r in subset:
411 if pat in repo[r]:
411 if pat in repo[r]:
412 s.append(r)
412 s.append(r)
413 else:
413 else:
414 for r in subset:
414 for r in subset:
415 c = repo[r]
415 c = repo[r]
416 if not m or matchmod.patkind(pat) == 'set':
416 if not m or matchmod.patkind(pat) == 'set':
417 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
417 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
418 for f in c.manifest():
418 for f in c.manifest():
419 if m(f):
419 if m(f):
420 s.append(r)
420 s.append(r)
421 break
421 break
422 return s
422 return s
423
423
424 def date(repo, subset, x):
424 def date(repo, subset, x):
425 """``date(interval)``
425 """``date(interval)``
426 Changesets within the interval, see :hg:`help dates`.
426 Changesets within the interval, see :hg:`help dates`.
427 """
427 """
428 # i18n: "date" is a keyword
428 # i18n: "date" is a keyword
429 ds = getstring(x, _("date requires a string"))
429 ds = getstring(x, _("date requires a string"))
430 dm = util.matchdate(ds)
430 dm = util.matchdate(ds)
431 return [r for r in subset if dm(repo[r].date()[0])]
431 return [r for r in subset if dm(repo[r].date()[0])]
432
432
433 def desc(repo, subset, x):
433 def desc(repo, subset, x):
434 """``desc(string)``
434 """``desc(string)``
435 Search commit message for string. The match is case-insensitive.
435 Search commit message for string. The match is case-insensitive.
436 """
436 """
437 # i18n: "desc" is a keyword
437 # i18n: "desc" is a keyword
438 ds = encoding.lower(getstring(x, _("desc requires a string")))
438 ds = encoding.lower(getstring(x, _("desc requires a string")))
439 l = []
439 l = []
440 for r in subset:
440 for r in subset:
441 c = repo[r]
441 c = repo[r]
442 if ds in encoding.lower(c.description()):
442 if ds in encoding.lower(c.description()):
443 l.append(r)
443 l.append(r)
444 return l
444 return l
445
445
446 def _descendants(repo, subset, x, followfirst=False):
446 def _descendants(repo, subset, x, followfirst=False):
447 args = getset(repo, range(len(repo)), x)
447 args = getset(repo, range(len(repo)), x)
448 if not args:
448 if not args:
449 return []
449 return []
450 s = set(_revdescendants(repo, args, followfirst)) | set(args)
450 s = set(_revdescendants(repo, args, followfirst)) | set(args)
451 return [r for r in subset if r in s]
451 return [r for r in subset if r in s]
452
452
453 def descendants(repo, subset, x):
453 def descendants(repo, subset, x):
454 """``descendants(set)``
454 """``descendants(set)``
455 Changesets which are descendants of changesets in set.
455 Changesets which are descendants of changesets in set.
456 """
456 """
457 return _descendants(repo, subset, x)
457 return _descendants(repo, subset, x)
458
458
459 def _firstdescendants(repo, subset, x):
459 def _firstdescendants(repo, subset, x):
460 # ``_firstdescendants(set)``
460 # ``_firstdescendants(set)``
461 # Like ``descendants(set)`` but follows only the first parents.
461 # Like ``descendants(set)`` but follows only the first parents.
462 return _descendants(repo, subset, x, followfirst=True)
462 return _descendants(repo, subset, x, followfirst=True)
463
463
464 def draft(repo, subset, x):
464 def draft(repo, subset, x):
465 """``draft()``
465 """``draft()``
466 Changeset in draft phase."""
466 Changeset in draft phase."""
467 getargs(x, 0, 0, _("draft takes no arguments"))
467 getargs(x, 0, 0, _("draft takes no arguments"))
468 pc = repo._phasecache
468 pc = repo._phasecache
469 return [r for r in subset if pc.phase(repo, r) == phases.draft]
469 return [r for r in subset if pc.phase(repo, r) == phases.draft]
470
470
471 def extra(repo, subset, x):
471 def extra(repo, subset, x):
472 """``extra(label, [value])``
472 """``extra(label, [value])``
473 Changesets with the given label in the extra metadata, with the given
473 Changesets with the given label in the extra metadata, with the given
474 optional value."""
474 optional value."""
475
475
476 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
476 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
477 label = getstring(l[0], _('first argument to extra must be a string'))
477 label = getstring(l[0], _('first argument to extra must be a string'))
478 value = None
478 value = None
479
479
480 if len(l) > 1:
480 if len(l) > 1:
481 value = getstring(l[1], _('second argument to extra must be a string'))
481 value = getstring(l[1], _('second argument to extra must be a string'))
482
482
483 def _matchvalue(r):
483 def _matchvalue(r):
484 extra = repo[r].extra()
484 extra = repo[r].extra()
485 return label in extra and (value is None or value == extra[label])
485 return label in extra and (value is None or value == extra[label])
486
486
487 return [r for r in subset if _matchvalue(r)]
487 return [r for r in subset if _matchvalue(r)]
488
488
489 def filelog(repo, subset, x):
489 def filelog(repo, subset, x):
490 """``filelog(pattern)``
490 """``filelog(pattern)``
491 Changesets connected to the specified filelog.
491 Changesets connected to the specified filelog.
492 """
492 """
493
493
494 pat = getstring(x, _("filelog requires a pattern"))
494 pat = getstring(x, _("filelog requires a pattern"))
495 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
495 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
496 ctx=repo[None])
496 ctx=repo[None])
497 s = set()
497 s = set()
498
498
499 if not matchmod.patkind(pat):
499 if not matchmod.patkind(pat):
500 for f in m.files():
500 for f in m.files():
501 fl = repo.file(f)
501 fl = repo.file(f)
502 for fr in fl:
502 for fr in fl:
503 s.add(fl.linkrev(fr))
503 s.add(fl.linkrev(fr))
504 else:
504 else:
505 for f in repo[None]:
505 for f in repo[None]:
506 if m(f):
506 if m(f):
507 fl = repo.file(f)
507 fl = repo.file(f)
508 for fr in fl:
508 for fr in fl:
509 s.add(fl.linkrev(fr))
509 s.add(fl.linkrev(fr))
510
510
511 return [r for r in subset if r in s]
511 return [r for r in subset if r in s]
512
512
513 def first(repo, subset, x):
513 def first(repo, subset, x):
514 """``first(set, [n])``
514 """``first(set, [n])``
515 An alias for limit().
515 An alias for limit().
516 """
516 """
517 return limit(repo, subset, x)
517 return limit(repo, subset, x)
518
518
519 def _follow(repo, subset, x, name, followfirst=False):
519 def _follow(repo, subset, x, name, followfirst=False):
520 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
520 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
521 c = repo['.']
521 c = repo['.']
522 if l:
522 if l:
523 x = getstring(l[0], _("%s expected a filename") % name)
523 x = getstring(l[0], _("%s expected a filename") % name)
524 if x in c:
524 if x in c:
525 cx = c[x]
525 cx = c[x]
526 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
526 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
527 # include the revision responsible for the most recent version
527 # include the revision responsible for the most recent version
528 s.add(cx.linkrev())
528 s.add(cx.linkrev())
529 else:
529 else:
530 return []
530 return []
531 else:
531 else:
532 s = set(_revancestors(repo, [c.rev()], followfirst)) | set([c.rev()])
532 s = set(_revancestors(repo, [c.rev()], followfirst)) | set([c.rev()])
533
533
534 return [r for r in subset if r in s]
534 return [r for r in subset if r in s]
535
535
536 def follow(repo, subset, x):
536 def follow(repo, subset, x):
537 """``follow([file])``
537 """``follow([file])``
538 An alias for ``::.`` (ancestors of the working copy's first parent).
538 An alias for ``::.`` (ancestors of the working copy's first parent).
539 If a filename is specified, the history of the given file is followed,
539 If a filename is specified, the history of the given file is followed,
540 including copies.
540 including copies.
541 """
541 """
542 return _follow(repo, subset, x, 'follow')
542 return _follow(repo, subset, x, 'follow')
543
543
544 def _followfirst(repo, subset, x):
544 def _followfirst(repo, subset, x):
545 # ``followfirst([file])``
545 # ``followfirst([file])``
546 # Like ``follow([file])`` but follows only the first parent of
546 # Like ``follow([file])`` but follows only the first parent of
547 # every revision or file revision.
547 # every revision or file revision.
548 return _follow(repo, subset, x, '_followfirst', followfirst=True)
548 return _follow(repo, subset, x, '_followfirst', followfirst=True)
549
549
550 def getall(repo, subset, x):
550 def getall(repo, subset, x):
551 """``all()``
551 """``all()``
552 All changesets, the same as ``0:tip``.
552 All changesets, the same as ``0:tip``.
553 """
553 """
554 # i18n: "all" is a keyword
554 # i18n: "all" is a keyword
555 getargs(x, 0, 0, _("all takes no arguments"))
555 getargs(x, 0, 0, _("all takes no arguments"))
556 return subset
556 return subset
557
557
558 def grep(repo, subset, x):
558 def grep(repo, subset, x):
559 """``grep(regex)``
559 """``grep(regex)``
560 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
560 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
561 to ensure special escape characters are handled correctly. Unlike
561 to ensure special escape characters are handled correctly. Unlike
562 ``keyword(string)``, the match is case-sensitive.
562 ``keyword(string)``, the match is case-sensitive.
563 """
563 """
564 try:
564 try:
565 # i18n: "grep" is a keyword
565 # i18n: "grep" is a keyword
566 gr = re.compile(getstring(x, _("grep requires a string")))
566 gr = re.compile(getstring(x, _("grep requires a string")))
567 except re.error, e:
567 except re.error, e:
568 raise error.ParseError(_('invalid match pattern: %s') % e)
568 raise error.ParseError(_('invalid match pattern: %s') % e)
569 l = []
569 l = []
570 for r in subset:
570 for r in subset:
571 c = repo[r]
571 c = repo[r]
572 for e in c.files() + [c.user(), c.description()]:
572 for e in c.files() + [c.user(), c.description()]:
573 if gr.search(e):
573 if gr.search(e):
574 l.append(r)
574 l.append(r)
575 break
575 break
576 return l
576 return l
577
577
578 def _matchfiles(repo, subset, x):
578 def _matchfiles(repo, subset, x):
579 # _matchfiles takes a revset list of prefixed arguments:
579 # _matchfiles takes a revset list of prefixed arguments:
580 #
580 #
581 # [p:foo, i:bar, x:baz]
581 # [p:foo, i:bar, x:baz]
582 #
582 #
583 # builds a match object from them and filters subset. Allowed
583 # builds a match object from them and filters subset. Allowed
584 # prefixes are 'p:' for regular patterns, 'i:' for include
584 # prefixes are 'p:' for regular patterns, 'i:' for include
585 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
585 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
586 # a revision identifier, or the empty string to reference the
586 # a revision identifier, or the empty string to reference the
587 # working directory, from which the match object is
587 # working directory, from which the match object is
588 # initialized. Use 'd:' to set the default matching mode, default
588 # initialized. Use 'd:' to set the default matching mode, default
589 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
589 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
590
590
591 # i18n: "_matchfiles" is a keyword
591 # i18n: "_matchfiles" is a keyword
592 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
592 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
593 pats, inc, exc = [], [], []
593 pats, inc, exc = [], [], []
594 hasset = False
594 hasset = False
595 rev, default = None, None
595 rev, default = None, None
596 for arg in l:
596 for arg in l:
597 s = getstring(arg, _("_matchfiles requires string arguments"))
597 s = getstring(arg, _("_matchfiles requires string arguments"))
598 prefix, value = s[:2], s[2:]
598 prefix, value = s[:2], s[2:]
599 if prefix == 'p:':
599 if prefix == 'p:':
600 pats.append(value)
600 pats.append(value)
601 elif prefix == 'i:':
601 elif prefix == 'i:':
602 inc.append(value)
602 inc.append(value)
603 elif prefix == 'x:':
603 elif prefix == 'x:':
604 exc.append(value)
604 exc.append(value)
605 elif prefix == 'r:':
605 elif prefix == 'r:':
606 if rev is not None:
606 if rev is not None:
607 raise error.ParseError(_('_matchfiles expected at most one '
607 raise error.ParseError(_('_matchfiles expected at most one '
608 'revision'))
608 'revision'))
609 rev = value
609 rev = value
610 elif prefix == 'd:':
610 elif prefix == 'd:':
611 if default is not None:
611 if default is not None:
612 raise error.ParseError(_('_matchfiles expected at most one '
612 raise error.ParseError(_('_matchfiles expected at most one '
613 'default mode'))
613 'default mode'))
614 default = value
614 default = value
615 else:
615 else:
616 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
616 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
617 if not hasset and matchmod.patkind(value) == 'set':
617 if not hasset and matchmod.patkind(value) == 'set':
618 hasset = True
618 hasset = True
619 if not default:
619 if not default:
620 default = 'glob'
620 default = 'glob'
621 m = None
621 m = None
622 s = []
622 s = []
623 for r in subset:
623 for r in subset:
624 c = repo[r]
624 c = repo[r]
625 if not m or (hasset and rev is None):
625 if not m or (hasset and rev is None):
626 ctx = c
626 ctx = c
627 if rev is not None:
627 if rev is not None:
628 ctx = repo[rev or None]
628 ctx = repo[rev or None]
629 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
629 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
630 exclude=exc, ctx=ctx, default=default)
630 exclude=exc, ctx=ctx, default=default)
631 for f in c.files():
631 for f in c.files():
632 if m(f):
632 if m(f):
633 s.append(r)
633 s.append(r)
634 break
634 break
635 return s
635 return s
636
636
637 def hasfile(repo, subset, x):
637 def hasfile(repo, subset, x):
638 """``file(pattern)``
638 """``file(pattern)``
639 Changesets affecting files matched by pattern.
639 Changesets affecting files matched by pattern.
640 """
640 """
641 # i18n: "file" is a keyword
641 # i18n: "file" is a keyword
642 pat = getstring(x, _("file requires a pattern"))
642 pat = getstring(x, _("file requires a pattern"))
643 return _matchfiles(repo, subset, ('string', 'p:' + pat))
643 return _matchfiles(repo, subset, ('string', 'p:' + pat))
644
644
645 def head(repo, subset, x):
645 def head(repo, subset, x):
646 """``head()``
646 """``head()``
647 Changeset is a named branch head.
647 Changeset is a named branch head.
648 """
648 """
649 # i18n: "head" is a keyword
649 # i18n: "head" is a keyword
650 getargs(x, 0, 0, _("head takes no arguments"))
650 getargs(x, 0, 0, _("head takes no arguments"))
651 hs = set()
651 hs = set()
652 for b, ls in repo.branchmap().iteritems():
652 for b, ls in repo.branchmap().iteritems():
653 hs.update(repo[h].rev() for h in ls)
653 hs.update(repo[h].rev() for h in ls)
654 return [r for r in subset if r in hs]
654 return [r for r in subset if r in hs]
655
655
656 def heads(repo, subset, x):
656 def heads(repo, subset, x):
657 """``heads(set)``
657 """``heads(set)``
658 Members of set with no children in set.
658 Members of set with no children in set.
659 """
659 """
660 s = getset(repo, subset, x)
660 s = getset(repo, subset, x)
661 ps = set(parents(repo, subset, x))
661 ps = set(parents(repo, subset, x))
662 return [r for r in s if r not in ps]
662 return [r for r in s if r not in ps]
663
663
664 def keyword(repo, subset, x):
664 def keyword(repo, subset, x):
665 """``keyword(string)``
665 """``keyword(string)``
666 Search commit message, user name, and names of changed files for
666 Search commit message, user name, and names of changed files for
667 string. The match is case-insensitive.
667 string. The match is case-insensitive.
668 """
668 """
669 # i18n: "keyword" is a keyword
669 # i18n: "keyword" is a keyword
670 kw = encoding.lower(getstring(x, _("keyword requires a string")))
670 kw = encoding.lower(getstring(x, _("keyword requires a string")))
671 l = []
671 l = []
672 for r in subset:
672 for r in subset:
673 c = repo[r]
673 c = repo[r]
674 t = " ".join(c.files() + [c.user(), c.description()])
674 t = " ".join(c.files() + [c.user(), c.description()])
675 if kw in encoding.lower(t):
675 if kw in encoding.lower(t):
676 l.append(r)
676 l.append(r)
677 return l
677 return l
678
678
679 def limit(repo, subset, x):
679 def limit(repo, subset, x):
680 """``limit(set, [n])``
680 """``limit(set, [n])``
681 First n members of set, defaulting to 1.
681 First n members of set, defaulting to 1.
682 """
682 """
683 # i18n: "limit" is a keyword
683 # i18n: "limit" is a keyword
684 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
684 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
685 try:
685 try:
686 lim = 1
686 lim = 1
687 if len(l) == 2:
687 if len(l) == 2:
688 # i18n: "limit" is a keyword
688 # i18n: "limit" is a keyword
689 lim = int(getstring(l[1], _("limit requires a number")))
689 lim = int(getstring(l[1], _("limit requires a number")))
690 except (TypeError, ValueError):
690 except (TypeError, ValueError):
691 # i18n: "limit" is a keyword
691 # i18n: "limit" is a keyword
692 raise error.ParseError(_("limit expects a number"))
692 raise error.ParseError(_("limit expects a number"))
693 ss = set(subset)
693 ss = set(subset)
694 os = getset(repo, range(len(repo)), l[0])[:lim]
694 os = getset(repo, range(len(repo)), l[0])[:lim]
695 return [r for r in os if r in ss]
695 return [r for r in os if r in ss]
696
696
697 def last(repo, subset, x):
697 def last(repo, subset, x):
698 """``last(set, [n])``
698 """``last(set, [n])``
699 Last n members of set, defaulting to 1.
699 Last n members of set, defaulting to 1.
700 """
700 """
701 # i18n: "last" is a keyword
701 # i18n: "last" is a keyword
702 l = getargs(x, 1, 2, _("last requires one or two arguments"))
702 l = getargs(x, 1, 2, _("last requires one or two arguments"))
703 try:
703 try:
704 lim = 1
704 lim = 1
705 if len(l) == 2:
705 if len(l) == 2:
706 # i18n: "last" is a keyword
706 # i18n: "last" is a keyword
707 lim = int(getstring(l[1], _("last requires a number")))
707 lim = int(getstring(l[1], _("last requires a number")))
708 except (TypeError, ValueError):
708 except (TypeError, ValueError):
709 # i18n: "last" is a keyword
709 # i18n: "last" is a keyword
710 raise error.ParseError(_("last expects a number"))
710 raise error.ParseError(_("last expects a number"))
711 ss = set(subset)
711 ss = set(subset)
712 os = getset(repo, range(len(repo)), l[0])[-lim:]
712 os = getset(repo, range(len(repo)), l[0])[-lim:]
713 return [r for r in os if r in ss]
713 return [r for r in os if r in ss]
714
714
715 def maxrev(repo, subset, x):
715 def maxrev(repo, subset, x):
716 """``max(set)``
716 """``max(set)``
717 Changeset with highest revision number in set.
717 Changeset with highest revision number in set.
718 """
718 """
719 os = getset(repo, range(len(repo)), x)
719 os = getset(repo, range(len(repo)), x)
720 if os:
720 if os:
721 m = max(os)
721 m = max(os)
722 if m in subset:
722 if m in subset:
723 return [m]
723 return [m]
724 return []
724 return []
725
725
726 def merge(repo, subset, x):
726 def merge(repo, subset, x):
727 """``merge()``
727 """``merge()``
728 Changeset is a merge changeset.
728 Changeset is a merge changeset.
729 """
729 """
730 # i18n: "merge" is a keyword
730 # i18n: "merge" is a keyword
731 getargs(x, 0, 0, _("merge takes no arguments"))
731 getargs(x, 0, 0, _("merge takes no arguments"))
732 cl = repo.changelog
732 cl = repo.changelog
733 return [r for r in subset if cl.parentrevs(r)[1] != -1]
733 return [r for r in subset if cl.parentrevs(r)[1] != -1]
734
734
735 def minrev(repo, subset, x):
735 def minrev(repo, subset, x):
736 """``min(set)``
736 """``min(set)``
737 Changeset with lowest revision number in set.
737 Changeset with lowest revision number in set.
738 """
738 """
739 os = getset(repo, range(len(repo)), x)
739 os = getset(repo, range(len(repo)), x)
740 if os:
740 if os:
741 m = min(os)
741 m = min(os)
742 if m in subset:
742 if m in subset:
743 return [m]
743 return [m]
744 return []
744 return []
745
745
746 def modifies(repo, subset, x):
746 def modifies(repo, subset, x):
747 """``modifies(pattern)``
747 """``modifies(pattern)``
748 Changesets modifying files matched by pattern.
748 Changesets modifying files matched by pattern.
749 """
749 """
750 # i18n: "modifies" is a keyword
750 # i18n: "modifies" is a keyword
751 pat = getstring(x, _("modifies requires a pattern"))
751 pat = getstring(x, _("modifies requires a pattern"))
752 return checkstatus(repo, subset, pat, 0)
752 return checkstatus(repo, subset, pat, 0)
753
753
754 def node_(repo, subset, x):
754 def node_(repo, subset, x):
755 """``id(string)``
755 """``id(string)``
756 Revision non-ambiguously specified by the given hex string prefix.
756 Revision non-ambiguously specified by the given hex string prefix.
757 """
757 """
758 # i18n: "id" is a keyword
758 # i18n: "id" is a keyword
759 l = getargs(x, 1, 1, _("id requires one argument"))
759 l = getargs(x, 1, 1, _("id requires one argument"))
760 # i18n: "id" is a keyword
760 # i18n: "id" is a keyword
761 n = getstring(l[0], _("id requires a string"))
761 n = getstring(l[0], _("id requires a string"))
762 if len(n) == 40:
762 if len(n) == 40:
763 rn = repo[n].rev()
763 rn = repo[n].rev()
764 else:
764 else:
765 rn = None
765 rn = None
766 pm = repo.changelog._partialmatch(n)
766 pm = repo.changelog._partialmatch(n)
767 if pm is not None:
767 if pm is not None:
768 rn = repo.changelog.rev(pm)
768 rn = repo.changelog.rev(pm)
769
769
770 return [r for r in subset if r == rn]
770 return [r for r in subset if r == rn]
771
771
772 def outgoing(repo, subset, x):
772 def outgoing(repo, subset, x):
773 """``outgoing([path])``
773 """``outgoing([path])``
774 Changesets not found in the specified destination repository, or the
774 Changesets not found in the specified destination repository, or the
775 default push location.
775 default push location.
776 """
776 """
777 import hg # avoid start-up nasties
777 import hg # avoid start-up nasties
778 # i18n: "outgoing" is a keyword
778 # i18n: "outgoing" is a keyword
779 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
779 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
780 # i18n: "outgoing" is a keyword
780 # i18n: "outgoing" is a keyword
781 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
781 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
782 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
782 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
783 dest, branches = hg.parseurl(dest)
783 dest, branches = hg.parseurl(dest)
784 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
784 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
785 if revs:
785 if revs:
786 revs = [repo.lookup(rev) for rev in revs]
786 revs = [repo.lookup(rev) for rev in revs]
787 other = hg.peer(repo, {}, dest)
787 other = hg.peer(repo, {}, dest)
788 repo.ui.pushbuffer()
788 repo.ui.pushbuffer()
789 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
789 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
790 repo.ui.popbuffer()
790 repo.ui.popbuffer()
791 cl = repo.changelog
791 cl = repo.changelog
792 o = set([cl.rev(r) for r in outgoing.missing])
792 o = set([cl.rev(r) for r in outgoing.missing])
793 return [r for r in subset if r in o]
793 return [r for r in subset if r in o]
794
794
795 def p1(repo, subset, x):
795 def p1(repo, subset, x):
796 """``p1([set])``
796 """``p1([set])``
797 First parent of changesets in set, or the working directory.
797 First parent of changesets in set, or the working directory.
798 """
798 """
799 if x is None:
799 if x is None:
800 p = repo[x].p1().rev()
800 p = repo[x].p1().rev()
801 return [r for r in subset if r == p]
801 return [r for r in subset if r == p]
802
802
803 ps = set()
803 ps = set()
804 cl = repo.changelog
804 cl = repo.changelog
805 for r in getset(repo, range(len(repo)), x):
805 for r in getset(repo, range(len(repo)), x):
806 ps.add(cl.parentrevs(r)[0])
806 ps.add(cl.parentrevs(r)[0])
807 return [r for r in subset if r in ps]
807 return [r for r in subset if r in ps]
808
808
809 def p2(repo, subset, x):
809 def p2(repo, subset, x):
810 """``p2([set])``
810 """``p2([set])``
811 Second parent of changesets in set, or the working directory.
811 Second parent of changesets in set, or the working directory.
812 """
812 """
813 if x is None:
813 if x is None:
814 ps = repo[x].parents()
814 ps = repo[x].parents()
815 try:
815 try:
816 p = ps[1].rev()
816 p = ps[1].rev()
817 return [r for r in subset if r == p]
817 return [r for r in subset if r == p]
818 except IndexError:
818 except IndexError:
819 return []
819 return []
820
820
821 ps = set()
821 ps = set()
822 cl = repo.changelog
822 cl = repo.changelog
823 for r in getset(repo, range(len(repo)), x):
823 for r in getset(repo, range(len(repo)), x):
824 ps.add(cl.parentrevs(r)[1])
824 ps.add(cl.parentrevs(r)[1])
825 return [r for r in subset if r in ps]
825 return [r for r in subset if r in ps]
826
826
827 def parents(repo, subset, x):
827 def parents(repo, subset, x):
828 """``parents([set])``
828 """``parents([set])``
829 The set of all parents for all changesets in set, or the working directory.
829 The set of all parents for all changesets in set, or the working directory.
830 """
830 """
831 if x is None:
831 if x is None:
832 ps = tuple(p.rev() for p in repo[x].parents())
832 ps = tuple(p.rev() for p in repo[x].parents())
833 return [r for r in subset if r in ps]
833 return [r for r in subset if r in ps]
834
834
835 ps = set()
835 ps = set()
836 cl = repo.changelog
836 cl = repo.changelog
837 for r in getset(repo, range(len(repo)), x):
837 for r in getset(repo, range(len(repo)), x):
838 ps.update(cl.parentrevs(r))
838 ps.update(cl.parentrevs(r))
839 return [r for r in subset if r in ps]
839 return [r for r in subset if r in ps]
840
840
841 def parentspec(repo, subset, x, n):
841 def parentspec(repo, subset, x, n):
842 """``set^0``
842 """``set^0``
843 The set.
843 The set.
844 ``set^1`` (or ``set^``), ``set^2``
844 ``set^1`` (or ``set^``), ``set^2``
845 First or second parent, respectively, of all changesets in set.
845 First or second parent, respectively, of all changesets in set.
846 """
846 """
847 try:
847 try:
848 n = int(n[1])
848 n = int(n[1])
849 if n not in (0, 1, 2):
849 if n not in (0, 1, 2):
850 raise ValueError
850 raise ValueError
851 except (TypeError, ValueError):
851 except (TypeError, ValueError):
852 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
852 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
853 ps = set()
853 ps = set()
854 cl = repo.changelog
854 cl = repo.changelog
855 for r in getset(repo, subset, x):
855 for r in getset(repo, subset, x):
856 if n == 0:
856 if n == 0:
857 ps.add(r)
857 ps.add(r)
858 elif n == 1:
858 elif n == 1:
859 ps.add(cl.parentrevs(r)[0])
859 ps.add(cl.parentrevs(r)[0])
860 elif n == 2:
860 elif n == 2:
861 parents = cl.parentrevs(r)
861 parents = cl.parentrevs(r)
862 if len(parents) > 1:
862 if len(parents) > 1:
863 ps.add(parents[1])
863 ps.add(parents[1])
864 return [r for r in subset if r in ps]
864 return [r for r in subset if r in ps]
865
865
866 def present(repo, subset, x):
866 def present(repo, subset, x):
867 """``present(set)``
867 """``present(set)``
868 An empty set, if any revision in set isn't found; otherwise,
868 An empty set, if any revision in set isn't found; otherwise,
869 all revisions in set.
869 all revisions in set.
870
870
871 If any of specified revisions is not present in the local repository,
871 If any of specified revisions is not present in the local repository,
872 the query is normally aborted. But this predicate allows the query
872 the query is normally aborted. But this predicate allows the query
873 to continue even in such cases.
873 to continue even in such cases.
874 """
874 """
875 try:
875 try:
876 return getset(repo, subset, x)
876 return getset(repo, subset, x)
877 except error.RepoLookupError:
877 except error.RepoLookupError:
878 return []
878 return []
879
879
880 def public(repo, subset, x):
880 def public(repo, subset, x):
881 """``public()``
881 """``public()``
882 Changeset in public phase."""
882 Changeset in public phase."""
883 getargs(x, 0, 0, _("public takes no arguments"))
883 getargs(x, 0, 0, _("public takes no arguments"))
884 pc = repo._phasecache
884 pc = repo._phasecache
885 return [r for r in subset if pc.phase(repo, r) == phases.public]
885 return [r for r in subset if pc.phase(repo, r) == phases.public]
886
886
887 def remote(repo, subset, x):
887 def remote(repo, subset, x):
888 """``remote([id [,path]])``
888 """``remote([id [,path]])``
889 Local revision that corresponds to the given identifier in a
889 Local revision that corresponds to the given identifier in a
890 remote repository, if present. Here, the '.' identifier is a
890 remote repository, if present. Here, the '.' identifier is a
891 synonym for the current local branch.
891 synonym for the current local branch.
892 """
892 """
893
893
894 import hg # avoid start-up nasties
894 import hg # avoid start-up nasties
895 # i18n: "remote" is a keyword
895 # i18n: "remote" is a keyword
896 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
896 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
897
897
898 q = '.'
898 q = '.'
899 if len(l) > 0:
899 if len(l) > 0:
900 # i18n: "remote" is a keyword
900 # i18n: "remote" is a keyword
901 q = getstring(l[0], _("remote requires a string id"))
901 q = getstring(l[0], _("remote requires a string id"))
902 if q == '.':
902 if q == '.':
903 q = repo['.'].branch()
903 q = repo['.'].branch()
904
904
905 dest = ''
905 dest = ''
906 if len(l) > 1:
906 if len(l) > 1:
907 # i18n: "remote" is a keyword
907 # i18n: "remote" is a keyword
908 dest = getstring(l[1], _("remote requires a repository path"))
908 dest = getstring(l[1], _("remote requires a repository path"))
909 dest = repo.ui.expandpath(dest or 'default')
909 dest = repo.ui.expandpath(dest or 'default')
910 dest, branches = hg.parseurl(dest)
910 dest, branches = hg.parseurl(dest)
911 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
911 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
912 if revs:
912 if revs:
913 revs = [repo.lookup(rev) for rev in revs]
913 revs = [repo.lookup(rev) for rev in revs]
914 other = hg.peer(repo, {}, dest)
914 other = hg.peer(repo, {}, dest)
915 n = other.lookup(q)
915 n = other.lookup(q)
916 if n in repo:
916 if n in repo:
917 r = repo[n].rev()
917 r = repo[n].rev()
918 if r in subset:
918 if r in subset:
919 return [r]
919 return [r]
920 return []
920 return []
921
921
922 def removes(repo, subset, x):
922 def removes(repo, subset, x):
923 """``removes(pattern)``
923 """``removes(pattern)``
924 Changesets which remove files matching pattern.
924 Changesets which remove files matching pattern.
925 """
925 """
926 # i18n: "removes" is a keyword
926 # i18n: "removes" is a keyword
927 pat = getstring(x, _("removes requires a pattern"))
927 pat = getstring(x, _("removes requires a pattern"))
928 return checkstatus(repo, subset, pat, 2)
928 return checkstatus(repo, subset, pat, 2)
929
929
930 def rev(repo, subset, x):
930 def rev(repo, subset, x):
931 """``rev(number)``
931 """``rev(number)``
932 Revision with the given numeric identifier.
932 Revision with the given numeric identifier.
933 """
933 """
934 # i18n: "rev" is a keyword
934 # i18n: "rev" is a keyword
935 l = getargs(x, 1, 1, _("rev requires one argument"))
935 l = getargs(x, 1, 1, _("rev requires one argument"))
936 try:
936 try:
937 # i18n: "rev" is a keyword
937 # i18n: "rev" is a keyword
938 l = int(getstring(l[0], _("rev requires a number")))
938 l = int(getstring(l[0], _("rev requires a number")))
939 except (TypeError, ValueError):
939 except (TypeError, ValueError):
940 # i18n: "rev" is a keyword
940 # i18n: "rev" is a keyword
941 raise error.ParseError(_("rev expects a number"))
941 raise error.ParseError(_("rev expects a number"))
942 return [r for r in subset if r == l]
942 return [r for r in subset if r == l]
943
943
944 def matching(repo, subset, x):
944 def matching(repo, subset, x):
945 """``matching(revision [, field])``
945 """``matching(revision [, field])``
946 Changesets in which a given set of fields match the set of fields in the
946 Changesets in which a given set of fields match the set of fields in the
947 selected revision or set.
947 selected revision or set.
948
948
949 To match more than one field pass the list of fields to match separated
949 To match more than one field pass the list of fields to match separated
950 by spaces (e.g. ``author description``).
950 by spaces (e.g. ``author description``).
951
951
952 Valid fields are most regular revision fields and some special fields.
952 Valid fields are most regular revision fields and some special fields.
953
953
954 Regular revision fields are ``description``, ``author``, ``branch``,
954 Regular revision fields are ``description``, ``author``, ``branch``,
955 ``date``, ``files``, ``phase``, ``parents``, ``substate`` and ``user``.
955 ``date``, ``files``, ``phase``, ``parents``, ``substate`` and ``user``.
956 Note that ``author`` and ``user`` are synonyms.
956 Note that ``author`` and ``user`` are synonyms.
957
957
958 Special fields are ``summary`` and ``metadata``:
958 Special fields are ``summary`` and ``metadata``:
959 ``summary`` matches the first line of the description.
959 ``summary`` matches the first line of the description.
960 ``metadata`` is equivalent to matching ``description user date``
960 ``metadata`` is equivalent to matching ``description user date``
961 (i.e. it matches the main metadata fields).
961 (i.e. it matches the main metadata fields).
962
962
963 ``metadata`` is the default field which is used when no fields are
963 ``metadata`` is the default field which is used when no fields are
964 specified. You can match more than one field at a time.
964 specified. You can match more than one field at a time.
965 """
965 """
966 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
966 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
967
967
968 revs = getset(repo, xrange(len(repo)), l[0])
968 revs = getset(repo, xrange(len(repo)), l[0])
969
969
970 fieldlist = ['metadata']
970 fieldlist = ['metadata']
971 if len(l) > 1:
971 if len(l) > 1:
972 fieldlist = getstring(l[1],
972 fieldlist = getstring(l[1],
973 _("matching requires a string "
973 _("matching requires a string "
974 "as its second argument")).split()
974 "as its second argument")).split()
975
975
976 # Make sure that there are no repeated fields, and expand the
976 # Make sure that there are no repeated fields, and expand the
977 # 'special' 'metadata' field type
977 # 'special' 'metadata' field type
978 fields = []
978 fields = []
979 for field in fieldlist:
979 for field in fieldlist:
980 if field == 'metadata':
980 if field == 'metadata':
981 fields += ['user', 'description', 'date']
981 fields += ['user', 'description', 'date']
982 else:
982 else:
983 if field == 'author':
983 if field == 'author':
984 field = 'user'
984 field = 'user'
985 fields.append(field)
985 fields.append(field)
986 fields = set(fields)
986 fields = set(fields)
987 if 'summary' in fields and 'description' in fields:
987 if 'summary' in fields and 'description' in fields:
988 # If a revision matches its description it also matches its summary
988 # If a revision matches its description it also matches its summary
989 fields.discard('summary')
989 fields.discard('summary')
990
990
991 # We may want to match more than one field
991 # We may want to match more than one field
992 # Not all fields take the same amount of time to be matched
992 # Not all fields take the same amount of time to be matched
993 # Sort the selected fields in order of increasing matching cost
993 # Sort the selected fields in order of increasing matching cost
994 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
994 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
995 'files', 'description', 'substate']
995 'files', 'description', 'substate']
996 def fieldkeyfunc(f):
996 def fieldkeyfunc(f):
997 try:
997 try:
998 return fieldorder.index(f)
998 return fieldorder.index(f)
999 except ValueError:
999 except ValueError:
1000 # assume an unknown field is very costly
1000 # assume an unknown field is very costly
1001 return len(fieldorder)
1001 return len(fieldorder)
1002 fields = list(fields)
1002 fields = list(fields)
1003 fields.sort(key=fieldkeyfunc)
1003 fields.sort(key=fieldkeyfunc)
1004
1004
1005 # Each field will be matched with its own "getfield" function
1005 # Each field will be matched with its own "getfield" function
1006 # which will be added to the getfieldfuncs array of functions
1006 # which will be added to the getfieldfuncs array of functions
1007 getfieldfuncs = []
1007 getfieldfuncs = []
1008 _funcs = {
1008 _funcs = {
1009 'user': lambda r: repo[r].user(),
1009 'user': lambda r: repo[r].user(),
1010 'branch': lambda r: repo[r].branch(),
1010 'branch': lambda r: repo[r].branch(),
1011 'date': lambda r: repo[r].date(),
1011 'date': lambda r: repo[r].date(),
1012 'description': lambda r: repo[r].description(),
1012 'description': lambda r: repo[r].description(),
1013 'files': lambda r: repo[r].files(),
1013 'files': lambda r: repo[r].files(),
1014 'parents': lambda r: repo[r].parents(),
1014 'parents': lambda r: repo[r].parents(),
1015 'phase': lambda r: repo[r].phase(),
1015 'phase': lambda r: repo[r].phase(),
1016 'substate': lambda r: repo[r].substate,
1016 'substate': lambda r: repo[r].substate,
1017 'summary': lambda r: repo[r].description().splitlines()[0],
1017 'summary': lambda r: repo[r].description().splitlines()[0],
1018 }
1018 }
1019 for info in fields:
1019 for info in fields:
1020 getfield = _funcs.get(info, None)
1020 getfield = _funcs.get(info, None)
1021 if getfield is None:
1021 if getfield is None:
1022 raise error.ParseError(
1022 raise error.ParseError(
1023 _("unexpected field name passed to matching: %s") % info)
1023 _("unexpected field name passed to matching: %s") % info)
1024 getfieldfuncs.append(getfield)
1024 getfieldfuncs.append(getfield)
1025 # convert the getfield array of functions into a "getinfo" function
1025 # convert the getfield array of functions into a "getinfo" function
1026 # which returns an array of field values (or a single value if there
1026 # which returns an array of field values (or a single value if there
1027 # is only one field to match)
1027 # is only one field to match)
1028 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1028 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1029
1029
1030 matches = set()
1030 matches = set()
1031 for rev in revs:
1031 for rev in revs:
1032 target = getinfo(rev)
1032 target = getinfo(rev)
1033 for r in subset:
1033 for r in subset:
1034 match = True
1034 match = True
1035 for n, f in enumerate(getfieldfuncs):
1035 for n, f in enumerate(getfieldfuncs):
1036 if target[n] != f(r):
1036 if target[n] != f(r):
1037 match = False
1037 match = False
1038 break
1038 break
1039 if match:
1039 if match:
1040 matches.add(r)
1040 matches.add(r)
1041 return [r for r in subset if r in matches]
1041 return [r for r in subset if r in matches]
1042
1042
1043 def reverse(repo, subset, x):
1043 def reverse(repo, subset, x):
1044 """``reverse(set)``
1044 """``reverse(set)``
1045 Reverse order of set.
1045 Reverse order of set.
1046 """
1046 """
1047 l = getset(repo, subset, x)
1047 l = getset(repo, subset, x)
1048 l.reverse()
1048 l.reverse()
1049 return l
1049 return l
1050
1050
1051 def roots(repo, subset, x):
1051 def roots(repo, subset, x):
1052 """``roots(set)``
1052 """``roots(set)``
1053 Changesets in set with no parent changeset in set.
1053 Changesets in set with no parent changeset in set.
1054 """
1054 """
1055 s = set(getset(repo, xrange(len(repo)), x))
1055 s = set(getset(repo, xrange(len(repo)), x))
1056 subset = [r for r in subset if r in s]
1056 subset = [r for r in subset if r in s]
1057 cs = _children(repo, subset, s)
1057 cs = _children(repo, subset, s)
1058 return [r for r in subset if r not in cs]
1058 return [r for r in subset if r not in cs]
1059
1059
1060 def secret(repo, subset, x):
1060 def secret(repo, subset, x):
1061 """``secret()``
1061 """``secret()``
1062 Changeset in secret phase."""
1062 Changeset in secret phase."""
1063 getargs(x, 0, 0, _("secret takes no arguments"))
1063 getargs(x, 0, 0, _("secret takes no arguments"))
1064 pc = repo._phasecache
1064 pc = repo._phasecache
1065 return [r for r in subset if pc.phase(repo, r) == phases.secret]
1065 return [r for r in subset if pc.phase(repo, r) == phases.secret]
1066
1066
1067 def sort(repo, subset, x):
1067 def sort(repo, subset, x):
1068 """``sort(set[, [-]key...])``
1068 """``sort(set[, [-]key...])``
1069 Sort set by keys. The default sort order is ascending, specify a key
1069 Sort set by keys. The default sort order is ascending, specify a key
1070 as ``-key`` to sort in descending order.
1070 as ``-key`` to sort in descending order.
1071
1071
1072 The keys can be:
1072 The keys can be:
1073
1073
1074 - ``rev`` for the revision number,
1074 - ``rev`` for the revision number,
1075 - ``branch`` for the branch name,
1075 - ``branch`` for the branch name,
1076 - ``desc`` for the commit message (description),
1076 - ``desc`` for the commit message (description),
1077 - ``user`` for user name (``author`` can be used as an alias),
1077 - ``user`` for user name (``author`` can be used as an alias),
1078 - ``date`` for the commit date
1078 - ``date`` for the commit date
1079 """
1079 """
1080 # i18n: "sort" is a keyword
1080 # i18n: "sort" is a keyword
1081 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1081 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1082 keys = "rev"
1082 keys = "rev"
1083 if len(l) == 2:
1083 if len(l) == 2:
1084 keys = getstring(l[1], _("sort spec must be a string"))
1084 keys = getstring(l[1], _("sort spec must be a string"))
1085
1085
1086 s = l[0]
1086 s = l[0]
1087 keys = keys.split()
1087 keys = keys.split()
1088 l = []
1088 l = []
1089 def invert(s):
1089 def invert(s):
1090 return "".join(chr(255 - ord(c)) for c in s)
1090 return "".join(chr(255 - ord(c)) for c in s)
1091 for r in getset(repo, subset, s):
1091 for r in getset(repo, subset, s):
1092 c = repo[r]
1092 c = repo[r]
1093 e = []
1093 e = []
1094 for k in keys:
1094 for k in keys:
1095 if k == 'rev':
1095 if k == 'rev':
1096 e.append(r)
1096 e.append(r)
1097 elif k == '-rev':
1097 elif k == '-rev':
1098 e.append(-r)
1098 e.append(-r)
1099 elif k == 'branch':
1099 elif k == 'branch':
1100 e.append(c.branch())
1100 e.append(c.branch())
1101 elif k == '-branch':
1101 elif k == '-branch':
1102 e.append(invert(c.branch()))
1102 e.append(invert(c.branch()))
1103 elif k == 'desc':
1103 elif k == 'desc':
1104 e.append(c.description())
1104 e.append(c.description())
1105 elif k == '-desc':
1105 elif k == '-desc':
1106 e.append(invert(c.description()))
1106 e.append(invert(c.description()))
1107 elif k in 'user author':
1107 elif k in 'user author':
1108 e.append(c.user())
1108 e.append(c.user())
1109 elif k in '-user -author':
1109 elif k in '-user -author':
1110 e.append(invert(c.user()))
1110 e.append(invert(c.user()))
1111 elif k == 'date':
1111 elif k == 'date':
1112 e.append(c.date()[0])
1112 e.append(c.date()[0])
1113 elif k == '-date':
1113 elif k == '-date':
1114 e.append(-c.date()[0])
1114 e.append(-c.date()[0])
1115 else:
1115 else:
1116 raise error.ParseError(_("unknown sort key %r") % k)
1116 raise error.ParseError(_("unknown sort key %r") % k)
1117 e.append(r)
1117 e.append(r)
1118 l.append(e)
1118 l.append(e)
1119 l.sort()
1119 l.sort()
1120 return [e[-1] for e in l]
1120 return [e[-1] for e in l]
1121
1121
1122 def tag(repo, subset, x):
1122 def tag(repo, subset, x):
1123 """``tag([name])``
1123 """``tag([name])``
1124 The specified tag by name, or all tagged revisions if no name is given.
1124 The specified tag by name, or all tagged revisions if no name is given.
1125 """
1125 """
1126 # i18n: "tag" is a keyword
1126 # i18n: "tag" is a keyword
1127 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1127 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1128 cl = repo.changelog
1128 cl = repo.changelog
1129 if args:
1129 if args:
1130 tn = getstring(args[0],
1130 tn = getstring(args[0],
1131 # i18n: "tag" is a keyword
1131 # i18n: "tag" is a keyword
1132 _('the argument to tag must be a string'))
1132 _('the argument to tag must be a string'))
1133 if not repo.tags().get(tn, None):
1133 if not repo.tags().get(tn, None):
1134 raise util.Abort(_("tag '%s' does not exist") % tn)
1134 raise util.Abort(_("tag '%s' does not exist") % tn)
1135 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
1135 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
1136 else:
1136 else:
1137 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1137 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1138 return [r for r in subset if r in s]
1138 return [r for r in subset if r in s]
1139
1139
1140 def tagged(repo, subset, x):
1140 def tagged(repo, subset, x):
1141 return tag(repo, subset, x)
1141 return tag(repo, subset, x)
1142
1142
1143 def user(repo, subset, x):
1143 def user(repo, subset, x):
1144 """``user(string)``
1144 """``user(string)``
1145 User name contains string. The match is case-insensitive.
1145 User name contains string. The match is case-insensitive.
1146 """
1146 """
1147 return author(repo, subset, x)
1147 return author(repo, subset, x)
1148
1148
1149 # for internal use
1149 # for internal use
1150 def _list(repo, subset, x):
1150 def _list(repo, subset, x):
1151 s = getstring(x, "internal error")
1151 s = getstring(x, "internal error")
1152 if not s:
1152 if not s:
1153 return []
1153 return []
1154 if not isinstance(subset, set):
1154 if not isinstance(subset, set):
1155 subset = set(subset)
1155 subset = set(subset)
1156 ls = [repo[r].rev() for r in s.split('\0')]
1156 ls = [repo[r].rev() for r in s.split('\0')]
1157 return [r for r in ls if r in subset]
1157 return [r for r in ls if r in subset]
1158
1158
1159 symbols = {
1159 symbols = {
1160 "adds": adds,
1160 "adds": adds,
1161 "all": getall,
1161 "all": getall,
1162 "ancestor": ancestor,
1162 "ancestor": ancestor,
1163 "ancestors": ancestors,
1163 "ancestors": ancestors,
1164 "_firstancestors": _firstancestors,
1164 "_firstancestors": _firstancestors,
1165 "author": author,
1165 "author": author,
1166 "bisect": bisect,
1166 "bisect": bisect,
1167 "bisected": bisected,
1167 "bisected": bisected,
1168 "bookmark": bookmark,
1168 "bookmark": bookmark,
1169 "branch": branch,
1169 "branch": branch,
1170 "children": children,
1170 "children": children,
1171 "closed": closed,
1171 "closed": closed,
1172 "contains": contains,
1172 "contains": contains,
1173 "date": date,
1173 "date": date,
1174 "desc": desc,
1174 "desc": desc,
1175 "descendants": descendants,
1175 "descendants": descendants,
1176 "_firstdescendants": _firstdescendants,
1176 "_firstdescendants": _firstdescendants,
1177 "draft": draft,
1177 "draft": draft,
1178 "extra": extra,
1178 "extra": extra,
1179 "file": hasfile,
1179 "file": hasfile,
1180 "filelog": filelog,
1180 "filelog": filelog,
1181 "first": first,
1181 "first": first,
1182 "follow": follow,
1182 "follow": follow,
1183 "_followfirst": _followfirst,
1183 "_followfirst": _followfirst,
1184 "grep": grep,
1184 "grep": grep,
1185 "head": head,
1185 "head": head,
1186 "heads": heads,
1186 "heads": heads,
1187 "id": node_,
1187 "id": node_,
1188 "keyword": keyword,
1188 "keyword": keyword,
1189 "last": last,
1189 "last": last,
1190 "limit": limit,
1190 "limit": limit,
1191 "_matchfiles": _matchfiles,
1191 "_matchfiles": _matchfiles,
1192 "max": maxrev,
1192 "max": maxrev,
1193 "merge": merge,
1193 "merge": merge,
1194 "min": minrev,
1194 "min": minrev,
1195 "modifies": modifies,
1195 "modifies": modifies,
1196 "outgoing": outgoing,
1196 "outgoing": outgoing,
1197 "p1": p1,
1197 "p1": p1,
1198 "p2": p2,
1198 "p2": p2,
1199 "parents": parents,
1199 "parents": parents,
1200 "present": present,
1200 "present": present,
1201 "public": public,
1201 "public": public,
1202 "remote": remote,
1202 "remote": remote,
1203 "removes": removes,
1203 "removes": removes,
1204 "rev": rev,
1204 "rev": rev,
1205 "reverse": reverse,
1205 "reverse": reverse,
1206 "roots": roots,
1206 "roots": roots,
1207 "sort": sort,
1207 "sort": sort,
1208 "secret": secret,
1208 "secret": secret,
1209 "matching": matching,
1209 "matching": matching,
1210 "tag": tag,
1210 "tag": tag,
1211 "tagged": tagged,
1211 "tagged": tagged,
1212 "user": user,
1212 "user": user,
1213 "_list": _list,
1213 "_list": _list,
1214 }
1214 }
1215
1215
1216 methods = {
1216 methods = {
1217 "range": rangeset,
1217 "range": rangeset,
1218 "string": stringset,
1218 "string": stringset,
1219 "symbol": symbolset,
1219 "symbol": symbolset,
1220 "and": andset,
1220 "and": andset,
1221 "or": orset,
1221 "or": orset,
1222 "not": notset,
1222 "not": notset,
1223 "list": listset,
1223 "list": listset,
1224 "func": func,
1224 "func": func,
1225 "ancestor": ancestorspec,
1225 "ancestor": ancestorspec,
1226 "parent": parentspec,
1226 "parent": parentspec,
1227 "parentpost": p1,
1227 "parentpost": p1,
1228 }
1228 }
1229
1229
1230 def optimize(x, small):
1230 def optimize(x, small):
1231 if x is None:
1231 if x is None:
1232 return 0, x
1232 return 0, x
1233
1233
1234 smallbonus = 1
1234 smallbonus = 1
1235 if small:
1235 if small:
1236 smallbonus = .5
1236 smallbonus = .5
1237
1237
1238 op = x[0]
1238 op = x[0]
1239 if op == 'minus':
1239 if op == 'minus':
1240 return optimize(('and', x[1], ('not', x[2])), small)
1240 return optimize(('and', x[1], ('not', x[2])), small)
1241 elif op == 'dagrange':
1241 elif op == 'dagrange':
1242 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
1242 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
1243 ('func', ('symbol', 'ancestors'), x[2])), small)
1243 ('func', ('symbol', 'ancestors'), x[2])), small)
1244 elif op == 'dagrangepre':
1244 elif op == 'dagrangepre':
1245 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1245 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1246 elif op == 'dagrangepost':
1246 elif op == 'dagrangepost':
1247 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1247 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1248 elif op == 'rangepre':
1248 elif op == 'rangepre':
1249 return optimize(('range', ('string', '0'), x[1]), small)
1249 return optimize(('range', ('string', '0'), x[1]), small)
1250 elif op == 'rangepost':
1250 elif op == 'rangepost':
1251 return optimize(('range', x[1], ('string', 'tip')), small)
1251 return optimize(('range', x[1], ('string', 'tip')), small)
1252 elif op == 'negate':
1252 elif op == 'negate':
1253 return optimize(('string',
1253 return optimize(('string',
1254 '-' + getstring(x[1], _("can't negate that"))), small)
1254 '-' + getstring(x[1], _("can't negate that"))), small)
1255 elif op in 'string symbol negate':
1255 elif op in 'string symbol negate':
1256 return smallbonus, x # single revisions are small
1256 return smallbonus, x # single revisions are small
1257 elif op == 'and' or op == 'dagrange':
1257 elif op == 'and' or op == 'dagrange':
1258 wa, ta = optimize(x[1], True)
1258 wa, ta = optimize(x[1], True)
1259 wb, tb = optimize(x[2], True)
1259 wb, tb = optimize(x[2], True)
1260 w = min(wa, wb)
1260 w = min(wa, wb)
1261 if wa > wb:
1261 if wa > wb:
1262 return w, (op, tb, ta)
1262 return w, (op, tb, ta)
1263 return w, (op, ta, tb)
1263 return w, (op, ta, tb)
1264 elif op == 'or':
1264 elif op == 'or':
1265 wa, ta = optimize(x[1], False)
1265 wa, ta = optimize(x[1], False)
1266 wb, tb = optimize(x[2], False)
1266 wb, tb = optimize(x[2], False)
1267 if wb < wa:
1267 if wb < wa:
1268 wb, wa = wa, wb
1268 wb, wa = wa, wb
1269 return max(wa, wb), (op, ta, tb)
1269 return max(wa, wb), (op, ta, tb)
1270 elif op == 'not':
1270 elif op == 'not':
1271 o = optimize(x[1], not small)
1271 o = optimize(x[1], not small)
1272 return o[0], (op, o[1])
1272 return o[0], (op, o[1])
1273 elif op == 'parentpost':
1273 elif op == 'parentpost':
1274 o = optimize(x[1], small)
1274 o = optimize(x[1], small)
1275 return o[0], (op, o[1])
1275 return o[0], (op, o[1])
1276 elif op == 'group':
1276 elif op == 'group':
1277 return optimize(x[1], small)
1277 return optimize(x[1], small)
1278 elif op in 'range list parent ancestorspec':
1278 elif op in 'range list parent ancestorspec':
1279 if op == 'parent':
1279 if op == 'parent':
1280 # x^:y means (x^) : y, not x ^ (:y)
1280 # x^:y means (x^) : y, not x ^ (:y)
1281 post = ('parentpost', x[1])
1281 post = ('parentpost', x[1])
1282 if x[2][0] == 'dagrangepre':
1282 if x[2][0] == 'dagrangepre':
1283 return optimize(('dagrange', post, x[2][1]), small)
1283 return optimize(('dagrange', post, x[2][1]), small)
1284 elif x[2][0] == 'rangepre':
1284 elif x[2][0] == 'rangepre':
1285 return optimize(('range', post, x[2][1]), small)
1285 return optimize(('range', post, x[2][1]), small)
1286
1286
1287 wa, ta = optimize(x[1], small)
1287 wa, ta = optimize(x[1], small)
1288 wb, tb = optimize(x[2], small)
1288 wb, tb = optimize(x[2], small)
1289 return wa + wb, (op, ta, tb)
1289 return wa + wb, (op, ta, tb)
1290 elif op == 'func':
1290 elif op == 'func':
1291 f = getstring(x[1], _("not a symbol"))
1291 f = getstring(x[1], _("not a symbol"))
1292 wa, ta = optimize(x[2], small)
1292 wa, ta = optimize(x[2], small)
1293 if f in ("author branch closed date desc file grep keyword "
1293 if f in ("author branch closed date desc file grep keyword "
1294 "outgoing user"):
1294 "outgoing user"):
1295 w = 10 # slow
1295 w = 10 # slow
1296 elif f in "modifies adds removes":
1296 elif f in "modifies adds removes":
1297 w = 30 # slower
1297 w = 30 # slower
1298 elif f == "contains":
1298 elif f == "contains":
1299 w = 100 # very slow
1299 w = 100 # very slow
1300 elif f == "ancestor":
1300 elif f == "ancestor":
1301 w = 1 * smallbonus
1301 w = 1 * smallbonus
1302 elif f in "reverse limit first":
1302 elif f in "reverse limit first":
1303 w = 0
1303 w = 0
1304 elif f in "sort":
1304 elif f in "sort":
1305 w = 10 # assume most sorts look at changelog
1305 w = 10 # assume most sorts look at changelog
1306 else:
1306 else:
1307 w = 1
1307 w = 1
1308 return w + wa, (op, x[1], ta)
1308 return w + wa, (op, x[1], ta)
1309 return 1, x
1309 return 1, x
1310
1310
1311 _aliasarg = ('func', ('symbol', '_aliasarg'))
1311 _aliasarg = ('func', ('symbol', '_aliasarg'))
1312 def _getaliasarg(tree):
1312 def _getaliasarg(tree):
1313 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
1313 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
1314 return X, None otherwise.
1314 return X, None otherwise.
1315 """
1315 """
1316 if (len(tree) == 3 and tree[:2] == _aliasarg
1316 if (len(tree) == 3 and tree[:2] == _aliasarg
1317 and tree[2][0] == 'string'):
1317 and tree[2][0] == 'string'):
1318 return tree[2][1]
1318 return tree[2][1]
1319 return None
1319 return None
1320
1320
1321 def _checkaliasarg(tree, known=None):
1321 def _checkaliasarg(tree, known=None):
1322 """Check tree contains no _aliasarg construct or only ones which
1322 """Check tree contains no _aliasarg construct or only ones which
1323 value is in known. Used to avoid alias placeholders injection.
1323 value is in known. Used to avoid alias placeholders injection.
1324 """
1324 """
1325 if isinstance(tree, tuple):
1325 if isinstance(tree, tuple):
1326 arg = _getaliasarg(tree)
1326 arg = _getaliasarg(tree)
1327 if arg is not None and (not known or arg not in known):
1327 if arg is not None and (not known or arg not in known):
1328 raise error.ParseError(_("not a function: %s") % '_aliasarg')
1328 raise error.ParseError(_("not a function: %s") % '_aliasarg')
1329 for t in tree:
1329 for t in tree:
1330 _checkaliasarg(t, known)
1330 _checkaliasarg(t, known)
1331
1331
1332 class revsetalias(object):
1332 class revsetalias(object):
1333 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1333 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1334 args = None
1334 args = None
1335
1335
1336 def __init__(self, name, value):
1336 def __init__(self, name, value):
1337 '''Aliases like:
1337 '''Aliases like:
1338
1338
1339 h = heads(default)
1339 h = heads(default)
1340 b($1) = ancestors($1) - ancestors(default)
1340 b($1) = ancestors($1) - ancestors(default)
1341 '''
1341 '''
1342 m = self.funcre.search(name)
1342 m = self.funcre.search(name)
1343 if m:
1343 if m:
1344 self.name = m.group(1)
1344 self.name = m.group(1)
1345 self.tree = ('func', ('symbol', m.group(1)))
1345 self.tree = ('func', ('symbol', m.group(1)))
1346 self.args = [x.strip() for x in m.group(2).split(',')]
1346 self.args = [x.strip() for x in m.group(2).split(',')]
1347 for arg in self.args:
1347 for arg in self.args:
1348 # _aliasarg() is an unknown symbol only used separate
1348 # _aliasarg() is an unknown symbol only used separate
1349 # alias argument placeholders from regular strings.
1349 # alias argument placeholders from regular strings.
1350 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
1350 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
1351 else:
1351 else:
1352 self.name = name
1352 self.name = name
1353 self.tree = ('symbol', name)
1353 self.tree = ('symbol', name)
1354
1354
1355 self.replacement, pos = parse(value)
1355 self.replacement, pos = parse(value)
1356 if pos != len(value):
1356 if pos != len(value):
1357 raise error.ParseError(_('invalid token'), pos)
1357 raise error.ParseError(_('invalid token'), pos)
1358 # Check for placeholder injection
1358 # Check for placeholder injection
1359 _checkaliasarg(self.replacement, self.args)
1359 _checkaliasarg(self.replacement, self.args)
1360
1360
1361 def _getalias(aliases, tree):
1361 def _getalias(aliases, tree):
1362 """If tree looks like an unexpanded alias, return it. Return None
1362 """If tree looks like an unexpanded alias, return it. Return None
1363 otherwise.
1363 otherwise.
1364 """
1364 """
1365 if isinstance(tree, tuple) and tree:
1365 if isinstance(tree, tuple) and tree:
1366 if tree[0] == 'symbol' and len(tree) == 2:
1366 if tree[0] == 'symbol' and len(tree) == 2:
1367 name = tree[1]
1367 name = tree[1]
1368 alias = aliases.get(name)
1368 alias = aliases.get(name)
1369 if alias and alias.args is None and alias.tree == tree:
1369 if alias and alias.args is None and alias.tree == tree:
1370 return alias
1370 return alias
1371 if tree[0] == 'func' and len(tree) > 1:
1371 if tree[0] == 'func' and len(tree) > 1:
1372 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1372 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1373 name = tree[1][1]
1373 name = tree[1][1]
1374 alias = aliases.get(name)
1374 alias = aliases.get(name)
1375 if alias and alias.args is not None and alias.tree == tree[:2]:
1375 if alias and alias.args is not None and alias.tree == tree[:2]:
1376 return alias
1376 return alias
1377 return None
1377 return None
1378
1378
1379 def _expandargs(tree, args):
1379 def _expandargs(tree, args):
1380 """Replace _aliasarg instances with the substitution value of the
1380 """Replace _aliasarg instances with the substitution value of the
1381 same name in args, recursively.
1381 same name in args, recursively.
1382 """
1382 """
1383 if not tree or not isinstance(tree, tuple):
1383 if not tree or not isinstance(tree, tuple):
1384 return tree
1384 return tree
1385 arg = _getaliasarg(tree)
1385 arg = _getaliasarg(tree)
1386 if arg is not None:
1386 if arg is not None:
1387 return args[arg]
1387 return args[arg]
1388 return tuple(_expandargs(t, args) for t in tree)
1388 return tuple(_expandargs(t, args) for t in tree)
1389
1389
1390 def _expandaliases(aliases, tree, expanding):
1390 def _expandaliases(aliases, tree, expanding):
1391 """Expand aliases in tree, recursively.
1391 """Expand aliases in tree, recursively.
1392
1392
1393 'aliases' is a dictionary mapping user defined aliases to
1393 'aliases' is a dictionary mapping user defined aliases to
1394 revsetalias objects.
1394 revsetalias objects.
1395 """
1395 """
1396 if not isinstance(tree, tuple):
1396 if not isinstance(tree, tuple):
1397 # Do not expand raw strings
1397 # Do not expand raw strings
1398 return tree
1398 return tree
1399 alias = _getalias(aliases, tree)
1399 alias = _getalias(aliases, tree)
1400 if alias is not None:
1400 if alias is not None:
1401 if alias in expanding:
1401 if alias in expanding:
1402 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1402 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1403 'detected') % alias.name)
1403 'detected') % alias.name)
1404 expanding.append(alias)
1404 expanding.append(alias)
1405 result = _expandaliases(aliases, alias.replacement, expanding)
1405 result = _expandaliases(aliases, alias.replacement, expanding)
1406 expanding.pop()
1406 expanding.pop()
1407 if alias.args is not None:
1407 if alias.args is not None:
1408 l = getlist(tree[2])
1408 l = getlist(tree[2])
1409 if len(l) != len(alias.args):
1409 if len(l) != len(alias.args):
1410 raise error.ParseError(
1410 raise error.ParseError(
1411 _('invalid number of arguments: %s') % len(l))
1411 _('invalid number of arguments: %s') % len(l))
1412 l = [_expandaliases(aliases, a, []) for a in l]
1412 l = [_expandaliases(aliases, a, []) for a in l]
1413 result = _expandargs(result, dict(zip(alias.args, l)))
1413 result = _expandargs(result, dict(zip(alias.args, l)))
1414 else:
1414 else:
1415 result = tuple(_expandaliases(aliases, t, expanding)
1415 result = tuple(_expandaliases(aliases, t, expanding)
1416 for t in tree)
1416 for t in tree)
1417 return result
1417 return result
1418
1418
1419 def findaliases(ui, tree):
1419 def findaliases(ui, tree):
1420 _checkaliasarg(tree)
1420 _checkaliasarg(tree)
1421 aliases = {}
1421 aliases = {}
1422 for k, v in ui.configitems('revsetalias'):
1422 for k, v in ui.configitems('revsetalias'):
1423 alias = revsetalias(k, v)
1423 alias = revsetalias(k, v)
1424 aliases[alias.name] = alias
1424 aliases[alias.name] = alias
1425 return _expandaliases(aliases, tree, [])
1425 return _expandaliases(aliases, tree, [])
1426
1426
1427 parse = parser.parser(tokenize, elements).parse
1427 parse = parser.parser(tokenize, elements).parse
1428
1428
1429 def match(ui, spec):
1429 def match(ui, spec):
1430 if not spec:
1430 if not spec:
1431 raise error.ParseError(_("empty query"))
1431 raise error.ParseError(_("empty query"))
1432 tree, pos = parse(spec)
1432 tree, pos = parse(spec)
1433 if (pos != len(spec)):
1433 if (pos != len(spec)):
1434 raise error.ParseError(_("invalid token"), pos)
1434 raise error.ParseError(_("invalid token"), pos)
1435 if ui:
1435 if ui:
1436 tree = findaliases(ui, tree)
1436 tree = findaliases(ui, tree)
1437 weight, tree = optimize(tree, True)
1437 weight, tree = optimize(tree, True)
1438 def mfunc(repo, subset):
1438 def mfunc(repo, subset):
1439 return getset(repo, subset, tree)
1439 return getset(repo, subset, tree)
1440 return mfunc
1440 return mfunc
1441
1441
1442 def formatspec(expr, *args):
1442 def formatspec(expr, *args):
1443 '''
1443 '''
1444 This is a convenience function for using revsets internally, and
1444 This is a convenience function for using revsets internally, and
1445 escapes arguments appropriately. Aliases are intentionally ignored
1445 escapes arguments appropriately. Aliases are intentionally ignored
1446 so that intended expression behavior isn't accidentally subverted.
1446 so that intended expression behavior isn't accidentally subverted.
1447
1447
1448 Supported arguments:
1448 Supported arguments:
1449
1449
1450 %r = revset expression, parenthesized
1450 %r = revset expression, parenthesized
1451 %d = int(arg), no quoting
1451 %d = int(arg), no quoting
1452 %s = string(arg), escaped and single-quoted
1452 %s = string(arg), escaped and single-quoted
1453 %b = arg.branch(), escaped and single-quoted
1453 %b = arg.branch(), escaped and single-quoted
1454 %n = hex(arg), single-quoted
1454 %n = hex(arg), single-quoted
1455 %% = a literal '%'
1455 %% = a literal '%'
1456
1456
1457 Prefixing the type with 'l' specifies a parenthesized list of that type.
1457 Prefixing the type with 'l' specifies a parenthesized list of that type.
1458
1458
1459 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1459 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1460 '(10 or 11):: and ((this()) or (that()))'
1460 '(10 or 11):: and ((this()) or (that()))'
1461 >>> formatspec('%d:: and not %d::', 10, 20)
1461 >>> formatspec('%d:: and not %d::', 10, 20)
1462 '10:: and not 20::'
1462 '10:: and not 20::'
1463 >>> formatspec('%ld or %ld', [], [1])
1463 >>> formatspec('%ld or %ld', [], [1])
1464 "_list('') or 1"
1464 "_list('') or 1"
1465 >>> formatspec('keyword(%s)', 'foo\\xe9')
1465 >>> formatspec('keyword(%s)', 'foo\\xe9')
1466 "keyword('foo\\\\xe9')"
1466 "keyword('foo\\\\xe9')"
1467 >>> b = lambda: 'default'
1467 >>> b = lambda: 'default'
1468 >>> b.branch = b
1468 >>> b.branch = b
1469 >>> formatspec('branch(%b)', b)
1469 >>> formatspec('branch(%b)', b)
1470 "branch('default')"
1470 "branch('default')"
1471 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1471 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1472 "root(_list('a\\x00b\\x00c\\x00d'))"
1472 "root(_list('a\\x00b\\x00c\\x00d'))"
1473 '''
1473 '''
1474
1474
1475 def quote(s):
1475 def quote(s):
1476 return repr(str(s))
1476 return repr(str(s))
1477
1477
1478 def argtype(c, arg):
1478 def argtype(c, arg):
1479 if c == 'd':
1479 if c == 'd':
1480 return str(int(arg))
1480 return str(int(arg))
1481 elif c == 's':
1481 elif c == 's':
1482 return quote(arg)
1482 return quote(arg)
1483 elif c == 'r':
1483 elif c == 'r':
1484 parse(arg) # make sure syntax errors are confined
1484 parse(arg) # make sure syntax errors are confined
1485 return '(%s)' % arg
1485 return '(%s)' % arg
1486 elif c == 'n':
1486 elif c == 'n':
1487 return quote(node.hex(arg))
1487 return quote(node.hex(arg))
1488 elif c == 'b':
1488 elif c == 'b':
1489 return quote(arg.branch())
1489 return quote(arg.branch())
1490
1490
1491 def listexp(s, t):
1491 def listexp(s, t):
1492 l = len(s)
1492 l = len(s)
1493 if l == 0:
1493 if l == 0:
1494 return "_list('')"
1494 return "_list('')"
1495 elif l == 1:
1495 elif l == 1:
1496 return argtype(t, s[0])
1496 return argtype(t, s[0])
1497 elif t == 'd':
1497 elif t == 'd':
1498 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1498 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1499 elif t == 's':
1499 elif t == 's':
1500 return "_list('%s')" % "\0".join(s)
1500 return "_list('%s')" % "\0".join(s)
1501 elif t == 'n':
1501 elif t == 'n':
1502 return "_list('%s')" % "\0".join(node.hex(a) for a in s)
1502 return "_list('%s')" % "\0".join(node.hex(a) for a in s)
1503 elif t == 'b':
1503 elif t == 'b':
1504 return "_list('%s')" % "\0".join(a.branch() for a in s)
1504 return "_list('%s')" % "\0".join(a.branch() for a in s)
1505
1505
1506 m = l // 2
1506 m = l // 2
1507 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1507 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1508
1508
1509 ret = ''
1509 ret = ''
1510 pos = 0
1510 pos = 0
1511 arg = 0
1511 arg = 0
1512 while pos < len(expr):
1512 while pos < len(expr):
1513 c = expr[pos]
1513 c = expr[pos]
1514 if c == '%':
1514 if c == '%':
1515 pos += 1
1515 pos += 1
1516 d = expr[pos]
1516 d = expr[pos]
1517 if d == '%':
1517 if d == '%':
1518 ret += d
1518 ret += d
1519 elif d in 'dsnbr':
1519 elif d in 'dsnbr':
1520 ret += argtype(d, args[arg])
1520 ret += argtype(d, args[arg])
1521 arg += 1
1521 arg += 1
1522 elif d == 'l':
1522 elif d == 'l':
1523 # a list of some type
1523 # a list of some type
1524 pos += 1
1524 pos += 1
1525 d = expr[pos]
1525 d = expr[pos]
1526 ret += listexp(list(args[arg]), d)
1526 ret += listexp(list(args[arg]), d)
1527 arg += 1
1527 arg += 1
1528 else:
1528 else:
1529 raise util.Abort('unexpected revspec format character %s' % d)
1529 raise util.Abort('unexpected revspec format character %s' % d)
1530 else:
1530 else:
1531 ret += c
1531 ret += c
1532 pos += 1
1532 pos += 1
1533
1533
1534 return ret
1534 return ret
1535
1535
1536 def prettyformat(tree):
1536 def prettyformat(tree):
1537 def _prettyformat(tree, level, lines):
1537 def _prettyformat(tree, level, lines):
1538 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1538 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1539 lines.append((level, str(tree)))
1539 lines.append((level, str(tree)))
1540 else:
1540 else:
1541 lines.append((level, '(%s' % tree[0]))
1541 lines.append((level, '(%s' % tree[0]))
1542 for s in tree[1:]:
1542 for s in tree[1:]:
1543 _prettyformat(s, level + 1, lines)
1543 _prettyformat(s, level + 1, lines)
1544 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
1544 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
1545
1545
1546 lines = []
1546 lines = []
1547 _prettyformat(tree, 0, lines)
1547 _prettyformat(tree, 0, lines)
1548 output = '\n'.join((' '*l + s) for l, s in lines)
1548 output = '\n'.join((' '*l + s) for l, s in lines)
1549 return output
1549 return output
1550
1550
1551 # tell hggettext to extract docstrings from these functions:
1551 # tell hggettext to extract docstrings from these functions:
1552 i18nfunctions = symbols.values()
1552 i18nfunctions = symbols.values()
@@ -1,150 +1,150 b''
1 # discovery.py - protocol changeset discovery functions
1 # discovery.py - protocol changeset discovery functions
2 #
2 #
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import nullid, short
8 from node import nullid, short
9 from i18n import _
9 from i18n import _
10 import util, error
10 import util, error, collections
11
11
12 def findcommonincoming(repo, remote, heads=None, force=False):
12 def findcommonincoming(repo, remote, heads=None, force=False):
13 """Return a tuple (common, fetch, heads) used to identify the common
13 """Return a tuple (common, fetch, heads) used to identify the common
14 subset of nodes between repo and remote.
14 subset of nodes between repo and remote.
15
15
16 "common" is a list of (at least) the heads of the common subset.
16 "common" is a list of (at least) the heads of the common subset.
17 "fetch" is a list of roots of the nodes that would be incoming, to be
17 "fetch" is a list of roots of the nodes that would be incoming, to be
18 supplied to changegroupsubset.
18 supplied to changegroupsubset.
19 "heads" is either the supplied heads, or else the remote's heads.
19 "heads" is either the supplied heads, or else the remote's heads.
20 """
20 """
21
21
22 m = repo.changelog.nodemap
22 m = repo.changelog.nodemap
23 search = []
23 search = []
24 fetch = set()
24 fetch = set()
25 seen = set()
25 seen = set()
26 seenbranch = set()
26 seenbranch = set()
27 base = set()
27 base = set()
28
28
29 if not heads:
29 if not heads:
30 heads = remote.heads()
30 heads = remote.heads()
31
31
32 if repo.changelog.tip() == nullid:
32 if repo.changelog.tip() == nullid:
33 base.add(nullid)
33 base.add(nullid)
34 if heads != [nullid]:
34 if heads != [nullid]:
35 return [nullid], [nullid], list(heads)
35 return [nullid], [nullid], list(heads)
36 return [nullid], [], heads
36 return [nullid], [], heads
37
37
38 # assume we're closer to the tip than the root
38 # assume we're closer to the tip than the root
39 # and start by examining the heads
39 # and start by examining the heads
40 repo.ui.status(_("searching for changes\n"))
40 repo.ui.status(_("searching for changes\n"))
41
41
42 unknown = []
42 unknown = []
43 for h in heads:
43 for h in heads:
44 if h not in m:
44 if h not in m:
45 unknown.append(h)
45 unknown.append(h)
46 else:
46 else:
47 base.add(h)
47 base.add(h)
48
48
49 if not unknown:
49 if not unknown:
50 return list(base), [], list(heads)
50 return list(base), [], list(heads)
51
51
52 req = set(unknown)
52 req = set(unknown)
53 reqcnt = 0
53 reqcnt = 0
54
54
55 # search through remote branches
55 # search through remote branches
56 # a 'branch' here is a linear segment of history, with four parts:
56 # a 'branch' here is a linear segment of history, with four parts:
57 # head, root, first parent, second parent
57 # head, root, first parent, second parent
58 # (a branch always has two parents (or none) by definition)
58 # (a branch always has two parents (or none) by definition)
59 unknown = remote.branches(unknown)
59 unknown = collections.deque(remote.branches(unknown))
60 while unknown:
60 while unknown:
61 r = []
61 r = []
62 while unknown:
62 while unknown:
63 n = unknown.pop(0)
63 n = unknown.popleft()
64 if n[0] in seen:
64 if n[0] in seen:
65 continue
65 continue
66
66
67 repo.ui.debug("examining %s:%s\n"
67 repo.ui.debug("examining %s:%s\n"
68 % (short(n[0]), short(n[1])))
68 % (short(n[0]), short(n[1])))
69 if n[0] == nullid: # found the end of the branch
69 if n[0] == nullid: # found the end of the branch
70 pass
70 pass
71 elif n in seenbranch:
71 elif n in seenbranch:
72 repo.ui.debug("branch already found\n")
72 repo.ui.debug("branch already found\n")
73 continue
73 continue
74 elif n[1] and n[1] in m: # do we know the base?
74 elif n[1] and n[1] in m: # do we know the base?
75 repo.ui.debug("found incomplete branch %s:%s\n"
75 repo.ui.debug("found incomplete branch %s:%s\n"
76 % (short(n[0]), short(n[1])))
76 % (short(n[0]), short(n[1])))
77 search.append(n[0:2]) # schedule branch range for scanning
77 search.append(n[0:2]) # schedule branch range for scanning
78 seenbranch.add(n)
78 seenbranch.add(n)
79 else:
79 else:
80 if n[1] not in seen and n[1] not in fetch:
80 if n[1] not in seen and n[1] not in fetch:
81 if n[2] in m and n[3] in m:
81 if n[2] in m and n[3] in m:
82 repo.ui.debug("found new changeset %s\n" %
82 repo.ui.debug("found new changeset %s\n" %
83 short(n[1]))
83 short(n[1]))
84 fetch.add(n[1]) # earliest unknown
84 fetch.add(n[1]) # earliest unknown
85 for p in n[2:4]:
85 for p in n[2:4]:
86 if p in m:
86 if p in m:
87 base.add(p) # latest known
87 base.add(p) # latest known
88
88
89 for p in n[2:4]:
89 for p in n[2:4]:
90 if p not in req and p not in m:
90 if p not in req and p not in m:
91 r.append(p)
91 r.append(p)
92 req.add(p)
92 req.add(p)
93 seen.add(n[0])
93 seen.add(n[0])
94
94
95 if r:
95 if r:
96 reqcnt += 1
96 reqcnt += 1
97 repo.ui.progress(_('searching'), reqcnt, unit=_('queries'))
97 repo.ui.progress(_('searching'), reqcnt, unit=_('queries'))
98 repo.ui.debug("request %d: %s\n" %
98 repo.ui.debug("request %d: %s\n" %
99 (reqcnt, " ".join(map(short, r))))
99 (reqcnt, " ".join(map(short, r))))
100 for p in xrange(0, len(r), 10):
100 for p in xrange(0, len(r), 10):
101 for b in remote.branches(r[p:p + 10]):
101 for b in remote.branches(r[p:p + 10]):
102 repo.ui.debug("received %s:%s\n" %
102 repo.ui.debug("received %s:%s\n" %
103 (short(b[0]), short(b[1])))
103 (short(b[0]), short(b[1])))
104 unknown.append(b)
104 unknown.append(b)
105
105
106 # do binary search on the branches we found
106 # do binary search on the branches we found
107 while search:
107 while search:
108 newsearch = []
108 newsearch = []
109 reqcnt += 1
109 reqcnt += 1
110 repo.ui.progress(_('searching'), reqcnt, unit=_('queries'))
110 repo.ui.progress(_('searching'), reqcnt, unit=_('queries'))
111 for n, l in zip(search, remote.between(search)):
111 for n, l in zip(search, remote.between(search)):
112 l.append(n[1])
112 l.append(n[1])
113 p = n[0]
113 p = n[0]
114 f = 1
114 f = 1
115 for i in l:
115 for i in l:
116 repo.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
116 repo.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
117 if i in m:
117 if i in m:
118 if f <= 2:
118 if f <= 2:
119 repo.ui.debug("found new branch changeset %s\n" %
119 repo.ui.debug("found new branch changeset %s\n" %
120 short(p))
120 short(p))
121 fetch.add(p)
121 fetch.add(p)
122 base.add(i)
122 base.add(i)
123 else:
123 else:
124 repo.ui.debug("narrowed branch search to %s:%s\n"
124 repo.ui.debug("narrowed branch search to %s:%s\n"
125 % (short(p), short(i)))
125 % (short(p), short(i)))
126 newsearch.append((p, i))
126 newsearch.append((p, i))
127 break
127 break
128 p, f = i, f * 2
128 p, f = i, f * 2
129 search = newsearch
129 search = newsearch
130
130
131 # sanity check our fetch list
131 # sanity check our fetch list
132 for f in fetch:
132 for f in fetch:
133 if f in m:
133 if f in m:
134 raise error.RepoError(_("already have changeset ")
134 raise error.RepoError(_("already have changeset ")
135 + short(f[:4]))
135 + short(f[:4]))
136
136
137 base = list(base)
137 base = list(base)
138 if base == [nullid]:
138 if base == [nullid]:
139 if force:
139 if force:
140 repo.ui.warn(_("warning: repository is unrelated\n"))
140 repo.ui.warn(_("warning: repository is unrelated\n"))
141 else:
141 else:
142 raise util.Abort(_("repository is unrelated"))
142 raise util.Abort(_("repository is unrelated"))
143
143
144 repo.ui.debug("found new changesets starting at " +
144 repo.ui.debug("found new changesets starting at " +
145 " ".join([short(f) for f in fetch]) + "\n")
145 " ".join([short(f) for f in fetch]) + "\n")
146
146
147 repo.ui.progress(_('searching'), None)
147 repo.ui.progress(_('searching'), None)
148 repo.ui.debug("%d total queries\n" % reqcnt)
148 repo.ui.debug("%d total queries\n" % reqcnt)
149
149
150 return base, list(fetch), heads
150 return base, list(fetch), heads
@@ -1,1766 +1,1767 b''
1 # util.py - Mercurial utility functions and platform specfic implementations
1 # util.py - Mercurial utility functions and platform specfic implementations
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """Mercurial utility functions and platform specfic implementations.
10 """Mercurial utility functions and platform specfic implementations.
11
11
12 This contains helper routines that are independent of the SCM core and
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
13 hide platform-specific details from the core.
14 """
14 """
15
15
16 from i18n import _
16 from i18n import _
17 import error, osutil, encoding
17 import error, osutil, encoding, collections
18 import errno, re, shutil, sys, tempfile, traceback
18 import errno, re, shutil, sys, tempfile, traceback
19 import os, time, datetime, calendar, textwrap, signal
19 import os, time, datetime, calendar, textwrap, signal
20 import imp, socket, urllib
20 import imp, socket, urllib
21
21
22 if os.name == 'nt':
22 if os.name == 'nt':
23 import windows as platform
23 import windows as platform
24 else:
24 else:
25 import posix as platform
25 import posix as platform
26
26
27 platform.encodinglower = encoding.lower
27 platform.encodinglower = encoding.lower
28 platform.encodingupper = encoding.upper
28 platform.encodingupper = encoding.upper
29
29
30 cachestat = platform.cachestat
30 cachestat = platform.cachestat
31 checkexec = platform.checkexec
31 checkexec = platform.checkexec
32 checklink = platform.checklink
32 checklink = platform.checklink
33 copymode = platform.copymode
33 copymode = platform.copymode
34 executablepath = platform.executablepath
34 executablepath = platform.executablepath
35 expandglobs = platform.expandglobs
35 expandglobs = platform.expandglobs
36 explainexit = platform.explainexit
36 explainexit = platform.explainexit
37 findexe = platform.findexe
37 findexe = platform.findexe
38 gethgcmd = platform.gethgcmd
38 gethgcmd = platform.gethgcmd
39 getuser = platform.getuser
39 getuser = platform.getuser
40 groupmembers = platform.groupmembers
40 groupmembers = platform.groupmembers
41 groupname = platform.groupname
41 groupname = platform.groupname
42 hidewindow = platform.hidewindow
42 hidewindow = platform.hidewindow
43 isexec = platform.isexec
43 isexec = platform.isexec
44 isowner = platform.isowner
44 isowner = platform.isowner
45 localpath = platform.localpath
45 localpath = platform.localpath
46 lookupreg = platform.lookupreg
46 lookupreg = platform.lookupreg
47 makedir = platform.makedir
47 makedir = platform.makedir
48 nlinks = platform.nlinks
48 nlinks = platform.nlinks
49 normpath = platform.normpath
49 normpath = platform.normpath
50 normcase = platform.normcase
50 normcase = platform.normcase
51 nulldev = platform.nulldev
51 nulldev = platform.nulldev
52 openhardlinks = platform.openhardlinks
52 openhardlinks = platform.openhardlinks
53 oslink = platform.oslink
53 oslink = platform.oslink
54 parsepatchoutput = platform.parsepatchoutput
54 parsepatchoutput = platform.parsepatchoutput
55 pconvert = platform.pconvert
55 pconvert = platform.pconvert
56 popen = platform.popen
56 popen = platform.popen
57 posixfile = platform.posixfile
57 posixfile = platform.posixfile
58 quotecommand = platform.quotecommand
58 quotecommand = platform.quotecommand
59 realpath = platform.realpath
59 realpath = platform.realpath
60 rename = platform.rename
60 rename = platform.rename
61 samedevice = platform.samedevice
61 samedevice = platform.samedevice
62 samefile = platform.samefile
62 samefile = platform.samefile
63 samestat = platform.samestat
63 samestat = platform.samestat
64 setbinary = platform.setbinary
64 setbinary = platform.setbinary
65 setflags = platform.setflags
65 setflags = platform.setflags
66 setsignalhandler = platform.setsignalhandler
66 setsignalhandler = platform.setsignalhandler
67 shellquote = platform.shellquote
67 shellquote = platform.shellquote
68 spawndetached = platform.spawndetached
68 spawndetached = platform.spawndetached
69 sshargs = platform.sshargs
69 sshargs = platform.sshargs
70 statfiles = platform.statfiles
70 statfiles = platform.statfiles
71 termwidth = platform.termwidth
71 termwidth = platform.termwidth
72 testpid = platform.testpid
72 testpid = platform.testpid
73 umask = platform.umask
73 umask = platform.umask
74 unlink = platform.unlink
74 unlink = platform.unlink
75 unlinkpath = platform.unlinkpath
75 unlinkpath = platform.unlinkpath
76 username = platform.username
76 username = platform.username
77
77
78 # Python compatibility
78 # Python compatibility
79
79
80 _notset = object()
80 _notset = object()
81
81
82 def safehasattr(thing, attr):
82 def safehasattr(thing, attr):
83 return getattr(thing, attr, _notset) is not _notset
83 return getattr(thing, attr, _notset) is not _notset
84
84
85 def sha1(s=''):
85 def sha1(s=''):
86 '''
86 '''
87 Low-overhead wrapper around Python's SHA support
87 Low-overhead wrapper around Python's SHA support
88
88
89 >>> f = _fastsha1
89 >>> f = _fastsha1
90 >>> a = sha1()
90 >>> a = sha1()
91 >>> a = f()
91 >>> a = f()
92 >>> a.hexdigest()
92 >>> a.hexdigest()
93 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
93 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
94 '''
94 '''
95
95
96 return _fastsha1(s)
96 return _fastsha1(s)
97
97
98 def _fastsha1(s=''):
98 def _fastsha1(s=''):
99 # This function will import sha1 from hashlib or sha (whichever is
99 # This function will import sha1 from hashlib or sha (whichever is
100 # available) and overwrite itself with it on the first call.
100 # available) and overwrite itself with it on the first call.
101 # Subsequent calls will go directly to the imported function.
101 # Subsequent calls will go directly to the imported function.
102 if sys.version_info >= (2, 5):
102 if sys.version_info >= (2, 5):
103 from hashlib import sha1 as _sha1
103 from hashlib import sha1 as _sha1
104 else:
104 else:
105 from sha import sha as _sha1
105 from sha import sha as _sha1
106 global _fastsha1, sha1
106 global _fastsha1, sha1
107 _fastsha1 = sha1 = _sha1
107 _fastsha1 = sha1 = _sha1
108 return _sha1(s)
108 return _sha1(s)
109
109
110 try:
110 try:
111 buffer = buffer
111 buffer = buffer
112 except NameError:
112 except NameError:
113 if sys.version_info[0] < 3:
113 if sys.version_info[0] < 3:
114 def buffer(sliceable, offset=0):
114 def buffer(sliceable, offset=0):
115 return sliceable[offset:]
115 return sliceable[offset:]
116 else:
116 else:
117 def buffer(sliceable, offset=0):
117 def buffer(sliceable, offset=0):
118 return memoryview(sliceable)[offset:]
118 return memoryview(sliceable)[offset:]
119
119
120 import subprocess
120 import subprocess
121 closefds = os.name == 'posix'
121 closefds = os.name == 'posix'
122
122
123 def popen2(cmd, env=None, newlines=False):
123 def popen2(cmd, env=None, newlines=False):
124 # Setting bufsize to -1 lets the system decide the buffer size.
124 # Setting bufsize to -1 lets the system decide the buffer size.
125 # The default for bufsize is 0, meaning unbuffered. This leads to
125 # The default for bufsize is 0, meaning unbuffered. This leads to
126 # poor performance on Mac OS X: http://bugs.python.org/issue4194
126 # poor performance on Mac OS X: http://bugs.python.org/issue4194
127 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
127 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
128 close_fds=closefds,
128 close_fds=closefds,
129 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
129 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
130 universal_newlines=newlines,
130 universal_newlines=newlines,
131 env=env)
131 env=env)
132 return p.stdin, p.stdout
132 return p.stdin, p.stdout
133
133
134 def popen3(cmd, env=None, newlines=False):
134 def popen3(cmd, env=None, newlines=False):
135 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
135 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
136 close_fds=closefds,
136 close_fds=closefds,
137 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
137 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
138 stderr=subprocess.PIPE,
138 stderr=subprocess.PIPE,
139 universal_newlines=newlines,
139 universal_newlines=newlines,
140 env=env)
140 env=env)
141 return p.stdin, p.stdout, p.stderr
141 return p.stdin, p.stdout, p.stderr
142
142
143 def version():
143 def version():
144 """Return version information if available."""
144 """Return version information if available."""
145 try:
145 try:
146 import __version__
146 import __version__
147 return __version__.version
147 return __version__.version
148 except ImportError:
148 except ImportError:
149 return 'unknown'
149 return 'unknown'
150
150
151 # used by parsedate
151 # used by parsedate
152 defaultdateformats = (
152 defaultdateformats = (
153 '%Y-%m-%d %H:%M:%S',
153 '%Y-%m-%d %H:%M:%S',
154 '%Y-%m-%d %I:%M:%S%p',
154 '%Y-%m-%d %I:%M:%S%p',
155 '%Y-%m-%d %H:%M',
155 '%Y-%m-%d %H:%M',
156 '%Y-%m-%d %I:%M%p',
156 '%Y-%m-%d %I:%M%p',
157 '%Y-%m-%d',
157 '%Y-%m-%d',
158 '%m-%d',
158 '%m-%d',
159 '%m/%d',
159 '%m/%d',
160 '%m/%d/%y',
160 '%m/%d/%y',
161 '%m/%d/%Y',
161 '%m/%d/%Y',
162 '%a %b %d %H:%M:%S %Y',
162 '%a %b %d %H:%M:%S %Y',
163 '%a %b %d %I:%M:%S%p %Y',
163 '%a %b %d %I:%M:%S%p %Y',
164 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
164 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
165 '%b %d %H:%M:%S %Y',
165 '%b %d %H:%M:%S %Y',
166 '%b %d %I:%M:%S%p %Y',
166 '%b %d %I:%M:%S%p %Y',
167 '%b %d %H:%M:%S',
167 '%b %d %H:%M:%S',
168 '%b %d %I:%M:%S%p',
168 '%b %d %I:%M:%S%p',
169 '%b %d %H:%M',
169 '%b %d %H:%M',
170 '%b %d %I:%M%p',
170 '%b %d %I:%M%p',
171 '%b %d %Y',
171 '%b %d %Y',
172 '%b %d',
172 '%b %d',
173 '%H:%M:%S',
173 '%H:%M:%S',
174 '%I:%M:%S%p',
174 '%I:%M:%S%p',
175 '%H:%M',
175 '%H:%M',
176 '%I:%M%p',
176 '%I:%M%p',
177 )
177 )
178
178
179 extendeddateformats = defaultdateformats + (
179 extendeddateformats = defaultdateformats + (
180 "%Y",
180 "%Y",
181 "%Y-%m",
181 "%Y-%m",
182 "%b",
182 "%b",
183 "%b %Y",
183 "%b %Y",
184 )
184 )
185
185
186 def cachefunc(func):
186 def cachefunc(func):
187 '''cache the result of function calls'''
187 '''cache the result of function calls'''
188 # XXX doesn't handle keywords args
188 # XXX doesn't handle keywords args
189 cache = {}
189 cache = {}
190 if func.func_code.co_argcount == 1:
190 if func.func_code.co_argcount == 1:
191 # we gain a small amount of time because
191 # we gain a small amount of time because
192 # we don't need to pack/unpack the list
192 # we don't need to pack/unpack the list
193 def f(arg):
193 def f(arg):
194 if arg not in cache:
194 if arg not in cache:
195 cache[arg] = func(arg)
195 cache[arg] = func(arg)
196 return cache[arg]
196 return cache[arg]
197 else:
197 else:
198 def f(*args):
198 def f(*args):
199 if args not in cache:
199 if args not in cache:
200 cache[args] = func(*args)
200 cache[args] = func(*args)
201 return cache[args]
201 return cache[args]
202
202
203 return f
203 return f
204
204
205 def lrucachefunc(func):
205 def lrucachefunc(func):
206 '''cache most recent results of function calls'''
206 '''cache most recent results of function calls'''
207 cache = {}
207 cache = {}
208 order = []
208 order = collections.deque()
209 if func.func_code.co_argcount == 1:
209 if func.func_code.co_argcount == 1:
210 def f(arg):
210 def f(arg):
211 if arg not in cache:
211 if arg not in cache:
212 if len(cache) > 20:
212 if len(cache) > 20:
213 del cache[order.pop(0)]
213 del cache[order.popleft()]
214 cache[arg] = func(arg)
214 cache[arg] = func(arg)
215 else:
215 else:
216 order.remove(arg)
216 order.remove(arg)
217 order.append(arg)
217 order.append(arg)
218 return cache[arg]
218 return cache[arg]
219 else:
219 else:
220 def f(*args):
220 def f(*args):
221 if args not in cache:
221 if args not in cache:
222 if len(cache) > 20:
222 if len(cache) > 20:
223 del cache[order.pop(0)]
223 del cache[order.popleft()]
224 cache[args] = func(*args)
224 cache[args] = func(*args)
225 else:
225 else:
226 order.remove(args)
226 order.remove(args)
227 order.append(args)
227 order.append(args)
228 return cache[args]
228 return cache[args]
229
229
230 return f
230 return f
231
231
232 class propertycache(object):
232 class propertycache(object):
233 def __init__(self, func):
233 def __init__(self, func):
234 self.func = func
234 self.func = func
235 self.name = func.__name__
235 self.name = func.__name__
236 def __get__(self, obj, type=None):
236 def __get__(self, obj, type=None):
237 result = self.func(obj)
237 result = self.func(obj)
238 setattr(obj, self.name, result)
238 setattr(obj, self.name, result)
239 return result
239 return result
240
240
241 def pipefilter(s, cmd):
241 def pipefilter(s, cmd):
242 '''filter string S through command CMD, returning its output'''
242 '''filter string S through command CMD, returning its output'''
243 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
243 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
244 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
244 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
245 pout, perr = p.communicate(s)
245 pout, perr = p.communicate(s)
246 return pout
246 return pout
247
247
248 def tempfilter(s, cmd):
248 def tempfilter(s, cmd):
249 '''filter string S through a pair of temporary files with CMD.
249 '''filter string S through a pair of temporary files with CMD.
250 CMD is used as a template to create the real command to be run,
250 CMD is used as a template to create the real command to be run,
251 with the strings INFILE and OUTFILE replaced by the real names of
251 with the strings INFILE and OUTFILE replaced by the real names of
252 the temporary files generated.'''
252 the temporary files generated.'''
253 inname, outname = None, None
253 inname, outname = None, None
254 try:
254 try:
255 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
255 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
256 fp = os.fdopen(infd, 'wb')
256 fp = os.fdopen(infd, 'wb')
257 fp.write(s)
257 fp.write(s)
258 fp.close()
258 fp.close()
259 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
259 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
260 os.close(outfd)
260 os.close(outfd)
261 cmd = cmd.replace('INFILE', inname)
261 cmd = cmd.replace('INFILE', inname)
262 cmd = cmd.replace('OUTFILE', outname)
262 cmd = cmd.replace('OUTFILE', outname)
263 code = os.system(cmd)
263 code = os.system(cmd)
264 if sys.platform == 'OpenVMS' and code & 1:
264 if sys.platform == 'OpenVMS' and code & 1:
265 code = 0
265 code = 0
266 if code:
266 if code:
267 raise Abort(_("command '%s' failed: %s") %
267 raise Abort(_("command '%s' failed: %s") %
268 (cmd, explainexit(code)))
268 (cmd, explainexit(code)))
269 fp = open(outname, 'rb')
269 fp = open(outname, 'rb')
270 r = fp.read()
270 r = fp.read()
271 fp.close()
271 fp.close()
272 return r
272 return r
273 finally:
273 finally:
274 try:
274 try:
275 if inname:
275 if inname:
276 os.unlink(inname)
276 os.unlink(inname)
277 except OSError:
277 except OSError:
278 pass
278 pass
279 try:
279 try:
280 if outname:
280 if outname:
281 os.unlink(outname)
281 os.unlink(outname)
282 except OSError:
282 except OSError:
283 pass
283 pass
284
284
285 filtertable = {
285 filtertable = {
286 'tempfile:': tempfilter,
286 'tempfile:': tempfilter,
287 'pipe:': pipefilter,
287 'pipe:': pipefilter,
288 }
288 }
289
289
290 def filter(s, cmd):
290 def filter(s, cmd):
291 "filter a string through a command that transforms its input to its output"
291 "filter a string through a command that transforms its input to its output"
292 for name, fn in filtertable.iteritems():
292 for name, fn in filtertable.iteritems():
293 if cmd.startswith(name):
293 if cmd.startswith(name):
294 return fn(s, cmd[len(name):].lstrip())
294 return fn(s, cmd[len(name):].lstrip())
295 return pipefilter(s, cmd)
295 return pipefilter(s, cmd)
296
296
297 def binary(s):
297 def binary(s):
298 """return true if a string is binary data"""
298 """return true if a string is binary data"""
299 return bool(s and '\0' in s)
299 return bool(s and '\0' in s)
300
300
301 def increasingchunks(source, min=1024, max=65536):
301 def increasingchunks(source, min=1024, max=65536):
302 '''return no less than min bytes per chunk while data remains,
302 '''return no less than min bytes per chunk while data remains,
303 doubling min after each chunk until it reaches max'''
303 doubling min after each chunk until it reaches max'''
304 def log2(x):
304 def log2(x):
305 if not x:
305 if not x:
306 return 0
306 return 0
307 i = 0
307 i = 0
308 while x:
308 while x:
309 x >>= 1
309 x >>= 1
310 i += 1
310 i += 1
311 return i - 1
311 return i - 1
312
312
313 buf = []
313 buf = []
314 blen = 0
314 blen = 0
315 for chunk in source:
315 for chunk in source:
316 buf.append(chunk)
316 buf.append(chunk)
317 blen += len(chunk)
317 blen += len(chunk)
318 if blen >= min:
318 if blen >= min:
319 if min < max:
319 if min < max:
320 min = min << 1
320 min = min << 1
321 nmin = 1 << log2(blen)
321 nmin = 1 << log2(blen)
322 if nmin > min:
322 if nmin > min:
323 min = nmin
323 min = nmin
324 if min > max:
324 if min > max:
325 min = max
325 min = max
326 yield ''.join(buf)
326 yield ''.join(buf)
327 blen = 0
327 blen = 0
328 buf = []
328 buf = []
329 if buf:
329 if buf:
330 yield ''.join(buf)
330 yield ''.join(buf)
331
331
332 Abort = error.Abort
332 Abort = error.Abort
333
333
334 def always(fn):
334 def always(fn):
335 return True
335 return True
336
336
337 def never(fn):
337 def never(fn):
338 return False
338 return False
339
339
340 def pathto(root, n1, n2):
340 def pathto(root, n1, n2):
341 '''return the relative path from one place to another.
341 '''return the relative path from one place to another.
342 root should use os.sep to separate directories
342 root should use os.sep to separate directories
343 n1 should use os.sep to separate directories
343 n1 should use os.sep to separate directories
344 n2 should use "/" to separate directories
344 n2 should use "/" to separate directories
345 returns an os.sep-separated path.
345 returns an os.sep-separated path.
346
346
347 If n1 is a relative path, it's assumed it's
347 If n1 is a relative path, it's assumed it's
348 relative to root.
348 relative to root.
349 n2 should always be relative to root.
349 n2 should always be relative to root.
350 '''
350 '''
351 if not n1:
351 if not n1:
352 return localpath(n2)
352 return localpath(n2)
353 if os.path.isabs(n1):
353 if os.path.isabs(n1):
354 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
354 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
355 return os.path.join(root, localpath(n2))
355 return os.path.join(root, localpath(n2))
356 n2 = '/'.join((pconvert(root), n2))
356 n2 = '/'.join((pconvert(root), n2))
357 a, b = splitpath(n1), n2.split('/')
357 a, b = splitpath(n1), n2.split('/')
358 a.reverse()
358 a.reverse()
359 b.reverse()
359 b.reverse()
360 while a and b and a[-1] == b[-1]:
360 while a and b and a[-1] == b[-1]:
361 a.pop()
361 a.pop()
362 b.pop()
362 b.pop()
363 b.reverse()
363 b.reverse()
364 return os.sep.join((['..'] * len(a)) + b) or '.'
364 return os.sep.join((['..'] * len(a)) + b) or '.'
365
365
366 _hgexecutable = None
366 _hgexecutable = None
367
367
368 def mainfrozen():
368 def mainfrozen():
369 """return True if we are a frozen executable.
369 """return True if we are a frozen executable.
370
370
371 The code supports py2exe (most common, Windows only) and tools/freeze
371 The code supports py2exe (most common, Windows only) and tools/freeze
372 (portable, not much used).
372 (portable, not much used).
373 """
373 """
374 return (safehasattr(sys, "frozen") or # new py2exe
374 return (safehasattr(sys, "frozen") or # new py2exe
375 safehasattr(sys, "importers") or # old py2exe
375 safehasattr(sys, "importers") or # old py2exe
376 imp.is_frozen("__main__")) # tools/freeze
376 imp.is_frozen("__main__")) # tools/freeze
377
377
378 def hgexecutable():
378 def hgexecutable():
379 """return location of the 'hg' executable.
379 """return location of the 'hg' executable.
380
380
381 Defaults to $HG or 'hg' in the search path.
381 Defaults to $HG or 'hg' in the search path.
382 """
382 """
383 if _hgexecutable is None:
383 if _hgexecutable is None:
384 hg = os.environ.get('HG')
384 hg = os.environ.get('HG')
385 mainmod = sys.modules['__main__']
385 mainmod = sys.modules['__main__']
386 if hg:
386 if hg:
387 _sethgexecutable(hg)
387 _sethgexecutable(hg)
388 elif mainfrozen():
388 elif mainfrozen():
389 _sethgexecutable(sys.executable)
389 _sethgexecutable(sys.executable)
390 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
390 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
391 _sethgexecutable(mainmod.__file__)
391 _sethgexecutable(mainmod.__file__)
392 else:
392 else:
393 exe = findexe('hg') or os.path.basename(sys.argv[0])
393 exe = findexe('hg') or os.path.basename(sys.argv[0])
394 _sethgexecutable(exe)
394 _sethgexecutable(exe)
395 return _hgexecutable
395 return _hgexecutable
396
396
397 def _sethgexecutable(path):
397 def _sethgexecutable(path):
398 """set location of the 'hg' executable"""
398 """set location of the 'hg' executable"""
399 global _hgexecutable
399 global _hgexecutable
400 _hgexecutable = path
400 _hgexecutable = path
401
401
402 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
402 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
403 '''enhanced shell command execution.
403 '''enhanced shell command execution.
404 run with environment maybe modified, maybe in different dir.
404 run with environment maybe modified, maybe in different dir.
405
405
406 if command fails and onerr is None, return status. if ui object,
406 if command fails and onerr is None, return status. if ui object,
407 print error message and return status, else raise onerr object as
407 print error message and return status, else raise onerr object as
408 exception.
408 exception.
409
409
410 if out is specified, it is assumed to be a file-like object that has a
410 if out is specified, it is assumed to be a file-like object that has a
411 write() method. stdout and stderr will be redirected to out.'''
411 write() method. stdout and stderr will be redirected to out.'''
412 try:
412 try:
413 sys.stdout.flush()
413 sys.stdout.flush()
414 except Exception:
414 except Exception:
415 pass
415 pass
416 def py2shell(val):
416 def py2shell(val):
417 'convert python object into string that is useful to shell'
417 'convert python object into string that is useful to shell'
418 if val is None or val is False:
418 if val is None or val is False:
419 return '0'
419 return '0'
420 if val is True:
420 if val is True:
421 return '1'
421 return '1'
422 return str(val)
422 return str(val)
423 origcmd = cmd
423 origcmd = cmd
424 cmd = quotecommand(cmd)
424 cmd = quotecommand(cmd)
425 if sys.platform == 'plan9':
425 if sys.platform == 'plan9':
426 # subprocess kludge to work around issues in half-baked Python
426 # subprocess kludge to work around issues in half-baked Python
427 # ports, notably bichued/python:
427 # ports, notably bichued/python:
428 if not cwd is None:
428 if not cwd is None:
429 os.chdir(cwd)
429 os.chdir(cwd)
430 rc = os.system(cmd)
430 rc = os.system(cmd)
431 else:
431 else:
432 env = dict(os.environ)
432 env = dict(os.environ)
433 env.update((k, py2shell(v)) for k, v in environ.iteritems())
433 env.update((k, py2shell(v)) for k, v in environ.iteritems())
434 env['HG'] = hgexecutable()
434 env['HG'] = hgexecutable()
435 if out is None or out == sys.__stdout__:
435 if out is None or out == sys.__stdout__:
436 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
436 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
437 env=env, cwd=cwd)
437 env=env, cwd=cwd)
438 else:
438 else:
439 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
439 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
440 env=env, cwd=cwd, stdout=subprocess.PIPE,
440 env=env, cwd=cwd, stdout=subprocess.PIPE,
441 stderr=subprocess.STDOUT)
441 stderr=subprocess.STDOUT)
442 for line in proc.stdout:
442 for line in proc.stdout:
443 out.write(line)
443 out.write(line)
444 proc.wait()
444 proc.wait()
445 rc = proc.returncode
445 rc = proc.returncode
446 if sys.platform == 'OpenVMS' and rc & 1:
446 if sys.platform == 'OpenVMS' and rc & 1:
447 rc = 0
447 rc = 0
448 if rc and onerr:
448 if rc and onerr:
449 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
449 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
450 explainexit(rc)[0])
450 explainexit(rc)[0])
451 if errprefix:
451 if errprefix:
452 errmsg = '%s: %s' % (errprefix, errmsg)
452 errmsg = '%s: %s' % (errprefix, errmsg)
453 try:
453 try:
454 onerr.warn(errmsg + '\n')
454 onerr.warn(errmsg + '\n')
455 except AttributeError:
455 except AttributeError:
456 raise onerr(errmsg)
456 raise onerr(errmsg)
457 return rc
457 return rc
458
458
459 def checksignature(func):
459 def checksignature(func):
460 '''wrap a function with code to check for calling errors'''
460 '''wrap a function with code to check for calling errors'''
461 def check(*args, **kwargs):
461 def check(*args, **kwargs):
462 try:
462 try:
463 return func(*args, **kwargs)
463 return func(*args, **kwargs)
464 except TypeError:
464 except TypeError:
465 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
465 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
466 raise error.SignatureError
466 raise error.SignatureError
467 raise
467 raise
468
468
469 return check
469 return check
470
470
471 def copyfile(src, dest):
471 def copyfile(src, dest):
472 "copy a file, preserving mode and atime/mtime"
472 "copy a file, preserving mode and atime/mtime"
473 if os.path.islink(src):
473 if os.path.islink(src):
474 try:
474 try:
475 os.unlink(dest)
475 os.unlink(dest)
476 except OSError:
476 except OSError:
477 pass
477 pass
478 os.symlink(os.readlink(src), dest)
478 os.symlink(os.readlink(src), dest)
479 else:
479 else:
480 try:
480 try:
481 shutil.copyfile(src, dest)
481 shutil.copyfile(src, dest)
482 shutil.copymode(src, dest)
482 shutil.copymode(src, dest)
483 except shutil.Error, inst:
483 except shutil.Error, inst:
484 raise Abort(str(inst))
484 raise Abort(str(inst))
485
485
486 def copyfiles(src, dst, hardlink=None):
486 def copyfiles(src, dst, hardlink=None):
487 """Copy a directory tree using hardlinks if possible"""
487 """Copy a directory tree using hardlinks if possible"""
488
488
489 if hardlink is None:
489 if hardlink is None:
490 hardlink = (os.stat(src).st_dev ==
490 hardlink = (os.stat(src).st_dev ==
491 os.stat(os.path.dirname(dst)).st_dev)
491 os.stat(os.path.dirname(dst)).st_dev)
492
492
493 num = 0
493 num = 0
494 if os.path.isdir(src):
494 if os.path.isdir(src):
495 os.mkdir(dst)
495 os.mkdir(dst)
496 for name, kind in osutil.listdir(src):
496 for name, kind in osutil.listdir(src):
497 srcname = os.path.join(src, name)
497 srcname = os.path.join(src, name)
498 dstname = os.path.join(dst, name)
498 dstname = os.path.join(dst, name)
499 hardlink, n = copyfiles(srcname, dstname, hardlink)
499 hardlink, n = copyfiles(srcname, dstname, hardlink)
500 num += n
500 num += n
501 else:
501 else:
502 if hardlink:
502 if hardlink:
503 try:
503 try:
504 oslink(src, dst)
504 oslink(src, dst)
505 except (IOError, OSError):
505 except (IOError, OSError):
506 hardlink = False
506 hardlink = False
507 shutil.copy(src, dst)
507 shutil.copy(src, dst)
508 else:
508 else:
509 shutil.copy(src, dst)
509 shutil.copy(src, dst)
510 num += 1
510 num += 1
511
511
512 return hardlink, num
512 return hardlink, num
513
513
514 _winreservednames = '''con prn aux nul
514 _winreservednames = '''con prn aux nul
515 com1 com2 com3 com4 com5 com6 com7 com8 com9
515 com1 com2 com3 com4 com5 com6 com7 com8 com9
516 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
516 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
517 _winreservedchars = ':*?"<>|'
517 _winreservedchars = ':*?"<>|'
518 def checkwinfilename(path):
518 def checkwinfilename(path):
519 '''Check that the base-relative path is a valid filename on Windows.
519 '''Check that the base-relative path is a valid filename on Windows.
520 Returns None if the path is ok, or a UI string describing the problem.
520 Returns None if the path is ok, or a UI string describing the problem.
521
521
522 >>> checkwinfilename("just/a/normal/path")
522 >>> checkwinfilename("just/a/normal/path")
523 >>> checkwinfilename("foo/bar/con.xml")
523 >>> checkwinfilename("foo/bar/con.xml")
524 "filename contains 'con', which is reserved on Windows"
524 "filename contains 'con', which is reserved on Windows"
525 >>> checkwinfilename("foo/con.xml/bar")
525 >>> checkwinfilename("foo/con.xml/bar")
526 "filename contains 'con', which is reserved on Windows"
526 "filename contains 'con', which is reserved on Windows"
527 >>> checkwinfilename("foo/bar/xml.con")
527 >>> checkwinfilename("foo/bar/xml.con")
528 >>> checkwinfilename("foo/bar/AUX/bla.txt")
528 >>> checkwinfilename("foo/bar/AUX/bla.txt")
529 "filename contains 'AUX', which is reserved on Windows"
529 "filename contains 'AUX', which is reserved on Windows"
530 >>> checkwinfilename("foo/bar/bla:.txt")
530 >>> checkwinfilename("foo/bar/bla:.txt")
531 "filename contains ':', which is reserved on Windows"
531 "filename contains ':', which is reserved on Windows"
532 >>> checkwinfilename("foo/bar/b\07la.txt")
532 >>> checkwinfilename("foo/bar/b\07la.txt")
533 "filename contains '\\\\x07', which is invalid on Windows"
533 "filename contains '\\\\x07', which is invalid on Windows"
534 >>> checkwinfilename("foo/bar/bla ")
534 >>> checkwinfilename("foo/bar/bla ")
535 "filename ends with ' ', which is not allowed on Windows"
535 "filename ends with ' ', which is not allowed on Windows"
536 >>> checkwinfilename("../bar")
536 >>> checkwinfilename("../bar")
537 '''
537 '''
538 for n in path.replace('\\', '/').split('/'):
538 for n in path.replace('\\', '/').split('/'):
539 if not n:
539 if not n:
540 continue
540 continue
541 for c in n:
541 for c in n:
542 if c in _winreservedchars:
542 if c in _winreservedchars:
543 return _("filename contains '%s', which is reserved "
543 return _("filename contains '%s', which is reserved "
544 "on Windows") % c
544 "on Windows") % c
545 if ord(c) <= 31:
545 if ord(c) <= 31:
546 return _("filename contains %r, which is invalid "
546 return _("filename contains %r, which is invalid "
547 "on Windows") % c
547 "on Windows") % c
548 base = n.split('.')[0]
548 base = n.split('.')[0]
549 if base and base.lower() in _winreservednames:
549 if base and base.lower() in _winreservednames:
550 return _("filename contains '%s', which is reserved "
550 return _("filename contains '%s', which is reserved "
551 "on Windows") % base
551 "on Windows") % base
552 t = n[-1]
552 t = n[-1]
553 if t in '. ' and n not in '..':
553 if t in '. ' and n not in '..':
554 return _("filename ends with '%s', which is not allowed "
554 return _("filename ends with '%s', which is not allowed "
555 "on Windows") % t
555 "on Windows") % t
556
556
557 if os.name == 'nt':
557 if os.name == 'nt':
558 checkosfilename = checkwinfilename
558 checkosfilename = checkwinfilename
559 else:
559 else:
560 checkosfilename = platform.checkosfilename
560 checkosfilename = platform.checkosfilename
561
561
562 def makelock(info, pathname):
562 def makelock(info, pathname):
563 try:
563 try:
564 return os.symlink(info, pathname)
564 return os.symlink(info, pathname)
565 except OSError, why:
565 except OSError, why:
566 if why.errno == errno.EEXIST:
566 if why.errno == errno.EEXIST:
567 raise
567 raise
568 except AttributeError: # no symlink in os
568 except AttributeError: # no symlink in os
569 pass
569 pass
570
570
571 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
571 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
572 os.write(ld, info)
572 os.write(ld, info)
573 os.close(ld)
573 os.close(ld)
574
574
575 def readlock(pathname):
575 def readlock(pathname):
576 try:
576 try:
577 return os.readlink(pathname)
577 return os.readlink(pathname)
578 except OSError, why:
578 except OSError, why:
579 if why.errno not in (errno.EINVAL, errno.ENOSYS):
579 if why.errno not in (errno.EINVAL, errno.ENOSYS):
580 raise
580 raise
581 except AttributeError: # no symlink in os
581 except AttributeError: # no symlink in os
582 pass
582 pass
583 fp = posixfile(pathname)
583 fp = posixfile(pathname)
584 r = fp.read()
584 r = fp.read()
585 fp.close()
585 fp.close()
586 return r
586 return r
587
587
588 def fstat(fp):
588 def fstat(fp):
589 '''stat file object that may not have fileno method.'''
589 '''stat file object that may not have fileno method.'''
590 try:
590 try:
591 return os.fstat(fp.fileno())
591 return os.fstat(fp.fileno())
592 except AttributeError:
592 except AttributeError:
593 return os.stat(fp.name)
593 return os.stat(fp.name)
594
594
595 # File system features
595 # File system features
596
596
597 def checkcase(path):
597 def checkcase(path):
598 """
598 """
599 Check whether the given path is on a case-sensitive filesystem
599 Check whether the given path is on a case-sensitive filesystem
600
600
601 Requires a path (like /foo/.hg) ending with a foldable final
601 Requires a path (like /foo/.hg) ending with a foldable final
602 directory component.
602 directory component.
603 """
603 """
604 s1 = os.stat(path)
604 s1 = os.stat(path)
605 d, b = os.path.split(path)
605 d, b = os.path.split(path)
606 b2 = b.upper()
606 b2 = b.upper()
607 if b == b2:
607 if b == b2:
608 b2 = b.lower()
608 b2 = b.lower()
609 if b == b2:
609 if b == b2:
610 return True # no evidence against case sensitivity
610 return True # no evidence against case sensitivity
611 p2 = os.path.join(d, b2)
611 p2 = os.path.join(d, b2)
612 try:
612 try:
613 s2 = os.stat(p2)
613 s2 = os.stat(p2)
614 if s2 == s1:
614 if s2 == s1:
615 return False
615 return False
616 return True
616 return True
617 except OSError:
617 except OSError:
618 return True
618 return True
619
619
620 _fspathcache = {}
620 _fspathcache = {}
621 def fspath(name, root):
621 def fspath(name, root):
622 '''Get name in the case stored in the filesystem
622 '''Get name in the case stored in the filesystem
623
623
624 The name should be relative to root, and be normcase-ed for efficiency.
624 The name should be relative to root, and be normcase-ed for efficiency.
625
625
626 Note that this function is unnecessary, and should not be
626 Note that this function is unnecessary, and should not be
627 called, for case-sensitive filesystems (simply because it's expensive).
627 called, for case-sensitive filesystems (simply because it's expensive).
628
628
629 The root should be normcase-ed, too.
629 The root should be normcase-ed, too.
630 '''
630 '''
631 def find(p, contents):
631 def find(p, contents):
632 for n in contents:
632 for n in contents:
633 if normcase(n) == p:
633 if normcase(n) == p:
634 return n
634 return n
635 return None
635 return None
636
636
637 seps = os.sep
637 seps = os.sep
638 if os.altsep:
638 if os.altsep:
639 seps = seps + os.altsep
639 seps = seps + os.altsep
640 # Protect backslashes. This gets silly very quickly.
640 # Protect backslashes. This gets silly very quickly.
641 seps.replace('\\','\\\\')
641 seps.replace('\\','\\\\')
642 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
642 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
643 dir = os.path.normpath(root)
643 dir = os.path.normpath(root)
644 result = []
644 result = []
645 for part, sep in pattern.findall(name):
645 for part, sep in pattern.findall(name):
646 if sep:
646 if sep:
647 result.append(sep)
647 result.append(sep)
648 continue
648 continue
649
649
650 if dir not in _fspathcache:
650 if dir not in _fspathcache:
651 _fspathcache[dir] = os.listdir(dir)
651 _fspathcache[dir] = os.listdir(dir)
652 contents = _fspathcache[dir]
652 contents = _fspathcache[dir]
653
653
654 found = find(part, contents)
654 found = find(part, contents)
655 if not found:
655 if not found:
656 # retry "once per directory" per "dirstate.walk" which
656 # retry "once per directory" per "dirstate.walk" which
657 # may take place for each patches of "hg qpush", for example
657 # may take place for each patches of "hg qpush", for example
658 contents = os.listdir(dir)
658 contents = os.listdir(dir)
659 _fspathcache[dir] = contents
659 _fspathcache[dir] = contents
660 found = find(part, contents)
660 found = find(part, contents)
661
661
662 result.append(found or part)
662 result.append(found or part)
663 dir = os.path.join(dir, part)
663 dir = os.path.join(dir, part)
664
664
665 return ''.join(result)
665 return ''.join(result)
666
666
667 def checknlink(testfile):
667 def checknlink(testfile):
668 '''check whether hardlink count reporting works properly'''
668 '''check whether hardlink count reporting works properly'''
669
669
670 # testfile may be open, so we need a separate file for checking to
670 # testfile may be open, so we need a separate file for checking to
671 # work around issue2543 (or testfile may get lost on Samba shares)
671 # work around issue2543 (or testfile may get lost on Samba shares)
672 f1 = testfile + ".hgtmp1"
672 f1 = testfile + ".hgtmp1"
673 if os.path.lexists(f1):
673 if os.path.lexists(f1):
674 return False
674 return False
675 try:
675 try:
676 posixfile(f1, 'w').close()
676 posixfile(f1, 'w').close()
677 except IOError:
677 except IOError:
678 return False
678 return False
679
679
680 f2 = testfile + ".hgtmp2"
680 f2 = testfile + ".hgtmp2"
681 fd = None
681 fd = None
682 try:
682 try:
683 try:
683 try:
684 oslink(f1, f2)
684 oslink(f1, f2)
685 except OSError:
685 except OSError:
686 return False
686 return False
687
687
688 # nlinks() may behave differently for files on Windows shares if
688 # nlinks() may behave differently for files on Windows shares if
689 # the file is open.
689 # the file is open.
690 fd = posixfile(f2)
690 fd = posixfile(f2)
691 return nlinks(f2) > 1
691 return nlinks(f2) > 1
692 finally:
692 finally:
693 if fd is not None:
693 if fd is not None:
694 fd.close()
694 fd.close()
695 for f in (f1, f2):
695 for f in (f1, f2):
696 try:
696 try:
697 os.unlink(f)
697 os.unlink(f)
698 except OSError:
698 except OSError:
699 pass
699 pass
700
700
701 return False
701 return False
702
702
703 def endswithsep(path):
703 def endswithsep(path):
704 '''Check path ends with os.sep or os.altsep.'''
704 '''Check path ends with os.sep or os.altsep.'''
705 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
705 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
706
706
707 def splitpath(path):
707 def splitpath(path):
708 '''Split path by os.sep.
708 '''Split path by os.sep.
709 Note that this function does not use os.altsep because this is
709 Note that this function does not use os.altsep because this is
710 an alternative of simple "xxx.split(os.sep)".
710 an alternative of simple "xxx.split(os.sep)".
711 It is recommended to use os.path.normpath() before using this
711 It is recommended to use os.path.normpath() before using this
712 function if need.'''
712 function if need.'''
713 return path.split(os.sep)
713 return path.split(os.sep)
714
714
715 def gui():
715 def gui():
716 '''Are we running in a GUI?'''
716 '''Are we running in a GUI?'''
717 if sys.platform == 'darwin':
717 if sys.platform == 'darwin':
718 if 'SSH_CONNECTION' in os.environ:
718 if 'SSH_CONNECTION' in os.environ:
719 # handle SSH access to a box where the user is logged in
719 # handle SSH access to a box where the user is logged in
720 return False
720 return False
721 elif getattr(osutil, 'isgui', None):
721 elif getattr(osutil, 'isgui', None):
722 # check if a CoreGraphics session is available
722 # check if a CoreGraphics session is available
723 return osutil.isgui()
723 return osutil.isgui()
724 else:
724 else:
725 # pure build; use a safe default
725 # pure build; use a safe default
726 return True
726 return True
727 else:
727 else:
728 return os.name == "nt" or os.environ.get("DISPLAY")
728 return os.name == "nt" or os.environ.get("DISPLAY")
729
729
730 def mktempcopy(name, emptyok=False, createmode=None):
730 def mktempcopy(name, emptyok=False, createmode=None):
731 """Create a temporary file with the same contents from name
731 """Create a temporary file with the same contents from name
732
732
733 The permission bits are copied from the original file.
733 The permission bits are copied from the original file.
734
734
735 If the temporary file is going to be truncated immediately, you
735 If the temporary file is going to be truncated immediately, you
736 can use emptyok=True as an optimization.
736 can use emptyok=True as an optimization.
737
737
738 Returns the name of the temporary file.
738 Returns the name of the temporary file.
739 """
739 """
740 d, fn = os.path.split(name)
740 d, fn = os.path.split(name)
741 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
741 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
742 os.close(fd)
742 os.close(fd)
743 # Temporary files are created with mode 0600, which is usually not
743 # Temporary files are created with mode 0600, which is usually not
744 # what we want. If the original file already exists, just copy
744 # what we want. If the original file already exists, just copy
745 # its mode. Otherwise, manually obey umask.
745 # its mode. Otherwise, manually obey umask.
746 copymode(name, temp, createmode)
746 copymode(name, temp, createmode)
747 if emptyok:
747 if emptyok:
748 return temp
748 return temp
749 try:
749 try:
750 try:
750 try:
751 ifp = posixfile(name, "rb")
751 ifp = posixfile(name, "rb")
752 except IOError, inst:
752 except IOError, inst:
753 if inst.errno == errno.ENOENT:
753 if inst.errno == errno.ENOENT:
754 return temp
754 return temp
755 if not getattr(inst, 'filename', None):
755 if not getattr(inst, 'filename', None):
756 inst.filename = name
756 inst.filename = name
757 raise
757 raise
758 ofp = posixfile(temp, "wb")
758 ofp = posixfile(temp, "wb")
759 for chunk in filechunkiter(ifp):
759 for chunk in filechunkiter(ifp):
760 ofp.write(chunk)
760 ofp.write(chunk)
761 ifp.close()
761 ifp.close()
762 ofp.close()
762 ofp.close()
763 except: # re-raises
763 except: # re-raises
764 try: os.unlink(temp)
764 try: os.unlink(temp)
765 except OSError: pass
765 except OSError: pass
766 raise
766 raise
767 return temp
767 return temp
768
768
769 class atomictempfile(object):
769 class atomictempfile(object):
770 '''writeable file object that atomically updates a file
770 '''writeable file object that atomically updates a file
771
771
772 All writes will go to a temporary copy of the original file. Call
772 All writes will go to a temporary copy of the original file. Call
773 close() when you are done writing, and atomictempfile will rename
773 close() when you are done writing, and atomictempfile will rename
774 the temporary copy to the original name, making the changes
774 the temporary copy to the original name, making the changes
775 visible. If the object is destroyed without being closed, all your
775 visible. If the object is destroyed without being closed, all your
776 writes are discarded.
776 writes are discarded.
777 '''
777 '''
778 def __init__(self, name, mode='w+b', createmode=None):
778 def __init__(self, name, mode='w+b', createmode=None):
779 self.__name = name # permanent name
779 self.__name = name # permanent name
780 self._tempname = mktempcopy(name, emptyok=('w' in mode),
780 self._tempname = mktempcopy(name, emptyok=('w' in mode),
781 createmode=createmode)
781 createmode=createmode)
782 self._fp = posixfile(self._tempname, mode)
782 self._fp = posixfile(self._tempname, mode)
783
783
784 # delegated methods
784 # delegated methods
785 self.write = self._fp.write
785 self.write = self._fp.write
786 self.fileno = self._fp.fileno
786 self.fileno = self._fp.fileno
787
787
788 def close(self):
788 def close(self):
789 if not self._fp.closed:
789 if not self._fp.closed:
790 self._fp.close()
790 self._fp.close()
791 rename(self._tempname, localpath(self.__name))
791 rename(self._tempname, localpath(self.__name))
792
792
793 def discard(self):
793 def discard(self):
794 if not self._fp.closed:
794 if not self._fp.closed:
795 try:
795 try:
796 os.unlink(self._tempname)
796 os.unlink(self._tempname)
797 except OSError:
797 except OSError:
798 pass
798 pass
799 self._fp.close()
799 self._fp.close()
800
800
801 def __del__(self):
801 def __del__(self):
802 if safehasattr(self, '_fp'): # constructor actually did something
802 if safehasattr(self, '_fp'): # constructor actually did something
803 self.discard()
803 self.discard()
804
804
805 def makedirs(name, mode=None):
805 def makedirs(name, mode=None):
806 """recursive directory creation with parent mode inheritance"""
806 """recursive directory creation with parent mode inheritance"""
807 try:
807 try:
808 os.mkdir(name)
808 os.mkdir(name)
809 except OSError, err:
809 except OSError, err:
810 if err.errno == errno.EEXIST:
810 if err.errno == errno.EEXIST:
811 return
811 return
812 if err.errno != errno.ENOENT or not name:
812 if err.errno != errno.ENOENT or not name:
813 raise
813 raise
814 parent = os.path.dirname(os.path.abspath(name))
814 parent = os.path.dirname(os.path.abspath(name))
815 if parent == name:
815 if parent == name:
816 raise
816 raise
817 makedirs(parent, mode)
817 makedirs(parent, mode)
818 os.mkdir(name)
818 os.mkdir(name)
819 if mode is not None:
819 if mode is not None:
820 os.chmod(name, mode)
820 os.chmod(name, mode)
821
821
822 def readfile(path):
822 def readfile(path):
823 fp = open(path, 'rb')
823 fp = open(path, 'rb')
824 try:
824 try:
825 return fp.read()
825 return fp.read()
826 finally:
826 finally:
827 fp.close()
827 fp.close()
828
828
829 def writefile(path, text):
829 def writefile(path, text):
830 fp = open(path, 'wb')
830 fp = open(path, 'wb')
831 try:
831 try:
832 fp.write(text)
832 fp.write(text)
833 finally:
833 finally:
834 fp.close()
834 fp.close()
835
835
836 def appendfile(path, text):
836 def appendfile(path, text):
837 fp = open(path, 'ab')
837 fp = open(path, 'ab')
838 try:
838 try:
839 fp.write(text)
839 fp.write(text)
840 finally:
840 finally:
841 fp.close()
841 fp.close()
842
842
843 class chunkbuffer(object):
843 class chunkbuffer(object):
844 """Allow arbitrary sized chunks of data to be efficiently read from an
844 """Allow arbitrary sized chunks of data to be efficiently read from an
845 iterator over chunks of arbitrary size."""
845 iterator over chunks of arbitrary size."""
846
846
847 def __init__(self, in_iter):
847 def __init__(self, in_iter):
848 """in_iter is the iterator that's iterating over the input chunks.
848 """in_iter is the iterator that's iterating over the input chunks.
849 targetsize is how big a buffer to try to maintain."""
849 targetsize is how big a buffer to try to maintain."""
850 def splitbig(chunks):
850 def splitbig(chunks):
851 for chunk in chunks:
851 for chunk in chunks:
852 if len(chunk) > 2**20:
852 if len(chunk) > 2**20:
853 pos = 0
853 pos = 0
854 while pos < len(chunk):
854 while pos < len(chunk):
855 end = pos + 2 ** 18
855 end = pos + 2 ** 18
856 yield chunk[pos:end]
856 yield chunk[pos:end]
857 pos = end
857 pos = end
858 else:
858 else:
859 yield chunk
859 yield chunk
860 self.iter = splitbig(in_iter)
860 self.iter = splitbig(in_iter)
861 self._queue = []
861 self._queue = []
862
862
863 def read(self, l):
863 def read(self, l):
864 """Read L bytes of data from the iterator of chunks of data.
864 """Read L bytes of data from the iterator of chunks of data.
865 Returns less than L bytes if the iterator runs dry."""
865 Returns less than L bytes if the iterator runs dry."""
866 left = l
866 left = l
867 buf = ''
867 buf = ''
868 queue = self._queue
868 queue = collections.deque(self._queue)
869 while left > 0:
869 while left > 0:
870 # refill the queue
870 # refill the queue
871 if not queue:
871 if not queue:
872 target = 2**18
872 target = 2**18
873 for chunk in self.iter:
873 for chunk in self.iter:
874 queue.append(chunk)
874 queue.append(chunk)
875 target -= len(chunk)
875 target -= len(chunk)
876 if target <= 0:
876 if target <= 0:
877 break
877 break
878 if not queue:
878 if not queue:
879 break
879 break
880
880
881 chunk = queue.pop(0)
881 chunk = queue.popleft()
882 left -= len(chunk)
882 left -= len(chunk)
883 if left < 0:
883 if left < 0:
884 queue.insert(0, chunk[left:])
884 queue.appendleft(chunk[left:])
885 buf += chunk[:left]
885 buf += chunk[:left]
886 else:
886 else:
887 buf += chunk
887 buf += chunk
888 self._queue = list(queue)
888
889
889 return buf
890 return buf
890
891
891 def filechunkiter(f, size=65536, limit=None):
892 def filechunkiter(f, size=65536, limit=None):
892 """Create a generator that produces the data in the file size
893 """Create a generator that produces the data in the file size
893 (default 65536) bytes at a time, up to optional limit (default is
894 (default 65536) bytes at a time, up to optional limit (default is
894 to read all data). Chunks may be less than size bytes if the
895 to read all data). Chunks may be less than size bytes if the
895 chunk is the last chunk in the file, or the file is a socket or
896 chunk is the last chunk in the file, or the file is a socket or
896 some other type of file that sometimes reads less data than is
897 some other type of file that sometimes reads less data than is
897 requested."""
898 requested."""
898 assert size >= 0
899 assert size >= 0
899 assert limit is None or limit >= 0
900 assert limit is None or limit >= 0
900 while True:
901 while True:
901 if limit is None:
902 if limit is None:
902 nbytes = size
903 nbytes = size
903 else:
904 else:
904 nbytes = min(limit, size)
905 nbytes = min(limit, size)
905 s = nbytes and f.read(nbytes)
906 s = nbytes and f.read(nbytes)
906 if not s:
907 if not s:
907 break
908 break
908 if limit:
909 if limit:
909 limit -= len(s)
910 limit -= len(s)
910 yield s
911 yield s
911
912
912 def makedate():
913 def makedate():
913 ct = time.time()
914 ct = time.time()
914 if ct < 0:
915 if ct < 0:
915 hint = _("check your clock")
916 hint = _("check your clock")
916 raise Abort(_("negative timestamp: %d") % ct, hint=hint)
917 raise Abort(_("negative timestamp: %d") % ct, hint=hint)
917 delta = (datetime.datetime.utcfromtimestamp(ct) -
918 delta = (datetime.datetime.utcfromtimestamp(ct) -
918 datetime.datetime.fromtimestamp(ct))
919 datetime.datetime.fromtimestamp(ct))
919 tz = delta.days * 86400 + delta.seconds
920 tz = delta.days * 86400 + delta.seconds
920 return ct, tz
921 return ct, tz
921
922
922 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
923 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
923 """represent a (unixtime, offset) tuple as a localized time.
924 """represent a (unixtime, offset) tuple as a localized time.
924 unixtime is seconds since the epoch, and offset is the time zone's
925 unixtime is seconds since the epoch, and offset is the time zone's
925 number of seconds away from UTC. if timezone is false, do not
926 number of seconds away from UTC. if timezone is false, do not
926 append time zone to string."""
927 append time zone to string."""
927 t, tz = date or makedate()
928 t, tz = date or makedate()
928 if t < 0:
929 if t < 0:
929 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
930 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
930 tz = 0
931 tz = 0
931 if "%1" in format or "%2" in format:
932 if "%1" in format or "%2" in format:
932 sign = (tz > 0) and "-" or "+"
933 sign = (tz > 0) and "-" or "+"
933 minutes = abs(tz) // 60
934 minutes = abs(tz) // 60
934 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
935 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
935 format = format.replace("%2", "%02d" % (minutes % 60))
936 format = format.replace("%2", "%02d" % (minutes % 60))
936 try:
937 try:
937 t = time.gmtime(float(t) - tz)
938 t = time.gmtime(float(t) - tz)
938 except ValueError:
939 except ValueError:
939 # time was out of range
940 # time was out of range
940 t = time.gmtime(sys.maxint)
941 t = time.gmtime(sys.maxint)
941 s = time.strftime(format, t)
942 s = time.strftime(format, t)
942 return s
943 return s
943
944
944 def shortdate(date=None):
945 def shortdate(date=None):
945 """turn (timestamp, tzoff) tuple into iso 8631 date."""
946 """turn (timestamp, tzoff) tuple into iso 8631 date."""
946 return datestr(date, format='%Y-%m-%d')
947 return datestr(date, format='%Y-%m-%d')
947
948
948 def strdate(string, format, defaults=[]):
949 def strdate(string, format, defaults=[]):
949 """parse a localized time string and return a (unixtime, offset) tuple.
950 """parse a localized time string and return a (unixtime, offset) tuple.
950 if the string cannot be parsed, ValueError is raised."""
951 if the string cannot be parsed, ValueError is raised."""
951 def timezone(string):
952 def timezone(string):
952 tz = string.split()[-1]
953 tz = string.split()[-1]
953 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
954 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
954 sign = (tz[0] == "+") and 1 or -1
955 sign = (tz[0] == "+") and 1 or -1
955 hours = int(tz[1:3])
956 hours = int(tz[1:3])
956 minutes = int(tz[3:5])
957 minutes = int(tz[3:5])
957 return -sign * (hours * 60 + minutes) * 60
958 return -sign * (hours * 60 + minutes) * 60
958 if tz == "GMT" or tz == "UTC":
959 if tz == "GMT" or tz == "UTC":
959 return 0
960 return 0
960 return None
961 return None
961
962
962 # NOTE: unixtime = localunixtime + offset
963 # NOTE: unixtime = localunixtime + offset
963 offset, date = timezone(string), string
964 offset, date = timezone(string), string
964 if offset is not None:
965 if offset is not None:
965 date = " ".join(string.split()[:-1])
966 date = " ".join(string.split()[:-1])
966
967
967 # add missing elements from defaults
968 # add missing elements from defaults
968 usenow = False # default to using biased defaults
969 usenow = False # default to using biased defaults
969 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
970 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
970 found = [True for p in part if ("%"+p) in format]
971 found = [True for p in part if ("%"+p) in format]
971 if not found:
972 if not found:
972 date += "@" + defaults[part][usenow]
973 date += "@" + defaults[part][usenow]
973 format += "@%" + part[0]
974 format += "@%" + part[0]
974 else:
975 else:
975 # We've found a specific time element, less specific time
976 # We've found a specific time element, less specific time
976 # elements are relative to today
977 # elements are relative to today
977 usenow = True
978 usenow = True
978
979
979 timetuple = time.strptime(date, format)
980 timetuple = time.strptime(date, format)
980 localunixtime = int(calendar.timegm(timetuple))
981 localunixtime = int(calendar.timegm(timetuple))
981 if offset is None:
982 if offset is None:
982 # local timezone
983 # local timezone
983 unixtime = int(time.mktime(timetuple))
984 unixtime = int(time.mktime(timetuple))
984 offset = unixtime - localunixtime
985 offset = unixtime - localunixtime
985 else:
986 else:
986 unixtime = localunixtime + offset
987 unixtime = localunixtime + offset
987 return unixtime, offset
988 return unixtime, offset
988
989
989 def parsedate(date, formats=None, bias={}):
990 def parsedate(date, formats=None, bias={}):
990 """parse a localized date/time and return a (unixtime, offset) tuple.
991 """parse a localized date/time and return a (unixtime, offset) tuple.
991
992
992 The date may be a "unixtime offset" string or in one of the specified
993 The date may be a "unixtime offset" string or in one of the specified
993 formats. If the date already is a (unixtime, offset) tuple, it is returned.
994 formats. If the date already is a (unixtime, offset) tuple, it is returned.
994 """
995 """
995 if not date:
996 if not date:
996 return 0, 0
997 return 0, 0
997 if isinstance(date, tuple) and len(date) == 2:
998 if isinstance(date, tuple) and len(date) == 2:
998 return date
999 return date
999 if not formats:
1000 if not formats:
1000 formats = defaultdateformats
1001 formats = defaultdateformats
1001 date = date.strip()
1002 date = date.strip()
1002 try:
1003 try:
1003 when, offset = map(int, date.split(' '))
1004 when, offset = map(int, date.split(' '))
1004 except ValueError:
1005 except ValueError:
1005 # fill out defaults
1006 # fill out defaults
1006 now = makedate()
1007 now = makedate()
1007 defaults = {}
1008 defaults = {}
1008 for part in ("d", "mb", "yY", "HI", "M", "S"):
1009 for part in ("d", "mb", "yY", "HI", "M", "S"):
1009 # this piece is for rounding the specific end of unknowns
1010 # this piece is for rounding the specific end of unknowns
1010 b = bias.get(part)
1011 b = bias.get(part)
1011 if b is None:
1012 if b is None:
1012 if part[0] in "HMS":
1013 if part[0] in "HMS":
1013 b = "00"
1014 b = "00"
1014 else:
1015 else:
1015 b = "0"
1016 b = "0"
1016
1017
1017 # this piece is for matching the generic end to today's date
1018 # this piece is for matching the generic end to today's date
1018 n = datestr(now, "%" + part[0])
1019 n = datestr(now, "%" + part[0])
1019
1020
1020 defaults[part] = (b, n)
1021 defaults[part] = (b, n)
1021
1022
1022 for format in formats:
1023 for format in formats:
1023 try:
1024 try:
1024 when, offset = strdate(date, format, defaults)
1025 when, offset = strdate(date, format, defaults)
1025 except (ValueError, OverflowError):
1026 except (ValueError, OverflowError):
1026 pass
1027 pass
1027 else:
1028 else:
1028 break
1029 break
1029 else:
1030 else:
1030 raise Abort(_('invalid date: %r') % date)
1031 raise Abort(_('invalid date: %r') % date)
1031 # validate explicit (probably user-specified) date and
1032 # validate explicit (probably user-specified) date and
1032 # time zone offset. values must fit in signed 32 bits for
1033 # time zone offset. values must fit in signed 32 bits for
1033 # current 32-bit linux runtimes. timezones go from UTC-12
1034 # current 32-bit linux runtimes. timezones go from UTC-12
1034 # to UTC+14
1035 # to UTC+14
1035 if abs(when) > 0x7fffffff:
1036 if abs(when) > 0x7fffffff:
1036 raise Abort(_('date exceeds 32 bits: %d') % when)
1037 raise Abort(_('date exceeds 32 bits: %d') % when)
1037 if when < 0:
1038 if when < 0:
1038 raise Abort(_('negative date value: %d') % when)
1039 raise Abort(_('negative date value: %d') % when)
1039 if offset < -50400 or offset > 43200:
1040 if offset < -50400 or offset > 43200:
1040 raise Abort(_('impossible time zone offset: %d') % offset)
1041 raise Abort(_('impossible time zone offset: %d') % offset)
1041 return when, offset
1042 return when, offset
1042
1043
1043 def matchdate(date):
1044 def matchdate(date):
1044 """Return a function that matches a given date match specifier
1045 """Return a function that matches a given date match specifier
1045
1046
1046 Formats include:
1047 Formats include:
1047
1048
1048 '{date}' match a given date to the accuracy provided
1049 '{date}' match a given date to the accuracy provided
1049
1050
1050 '<{date}' on or before a given date
1051 '<{date}' on or before a given date
1051
1052
1052 '>{date}' on or after a given date
1053 '>{date}' on or after a given date
1053
1054
1054 >>> p1 = parsedate("10:29:59")
1055 >>> p1 = parsedate("10:29:59")
1055 >>> p2 = parsedate("10:30:00")
1056 >>> p2 = parsedate("10:30:00")
1056 >>> p3 = parsedate("10:30:59")
1057 >>> p3 = parsedate("10:30:59")
1057 >>> p4 = parsedate("10:31:00")
1058 >>> p4 = parsedate("10:31:00")
1058 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1059 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1059 >>> f = matchdate("10:30")
1060 >>> f = matchdate("10:30")
1060 >>> f(p1[0])
1061 >>> f(p1[0])
1061 False
1062 False
1062 >>> f(p2[0])
1063 >>> f(p2[0])
1063 True
1064 True
1064 >>> f(p3[0])
1065 >>> f(p3[0])
1065 True
1066 True
1066 >>> f(p4[0])
1067 >>> f(p4[0])
1067 False
1068 False
1068 >>> f(p5[0])
1069 >>> f(p5[0])
1069 False
1070 False
1070 """
1071 """
1071
1072
1072 def lower(date):
1073 def lower(date):
1073 d = dict(mb="1", d="1")
1074 d = dict(mb="1", d="1")
1074 return parsedate(date, extendeddateformats, d)[0]
1075 return parsedate(date, extendeddateformats, d)[0]
1075
1076
1076 def upper(date):
1077 def upper(date):
1077 d = dict(mb="12", HI="23", M="59", S="59")
1078 d = dict(mb="12", HI="23", M="59", S="59")
1078 for days in ("31", "30", "29"):
1079 for days in ("31", "30", "29"):
1079 try:
1080 try:
1080 d["d"] = days
1081 d["d"] = days
1081 return parsedate(date, extendeddateformats, d)[0]
1082 return parsedate(date, extendeddateformats, d)[0]
1082 except Abort:
1083 except Abort:
1083 pass
1084 pass
1084 d["d"] = "28"
1085 d["d"] = "28"
1085 return parsedate(date, extendeddateformats, d)[0]
1086 return parsedate(date, extendeddateformats, d)[0]
1086
1087
1087 date = date.strip()
1088 date = date.strip()
1088
1089
1089 if not date:
1090 if not date:
1090 raise Abort(_("dates cannot consist entirely of whitespace"))
1091 raise Abort(_("dates cannot consist entirely of whitespace"))
1091 elif date[0] == "<":
1092 elif date[0] == "<":
1092 if not date[1:]:
1093 if not date[1:]:
1093 raise Abort(_("invalid day spec, use '<DATE'"))
1094 raise Abort(_("invalid day spec, use '<DATE'"))
1094 when = upper(date[1:])
1095 when = upper(date[1:])
1095 return lambda x: x <= when
1096 return lambda x: x <= when
1096 elif date[0] == ">":
1097 elif date[0] == ">":
1097 if not date[1:]:
1098 if not date[1:]:
1098 raise Abort(_("invalid day spec, use '>DATE'"))
1099 raise Abort(_("invalid day spec, use '>DATE'"))
1099 when = lower(date[1:])
1100 when = lower(date[1:])
1100 return lambda x: x >= when
1101 return lambda x: x >= when
1101 elif date[0] == "-":
1102 elif date[0] == "-":
1102 try:
1103 try:
1103 days = int(date[1:])
1104 days = int(date[1:])
1104 except ValueError:
1105 except ValueError:
1105 raise Abort(_("invalid day spec: %s") % date[1:])
1106 raise Abort(_("invalid day spec: %s") % date[1:])
1106 if days < 0:
1107 if days < 0:
1107 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1108 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1108 % date[1:])
1109 % date[1:])
1109 when = makedate()[0] - days * 3600 * 24
1110 when = makedate()[0] - days * 3600 * 24
1110 return lambda x: x >= when
1111 return lambda x: x >= when
1111 elif " to " in date:
1112 elif " to " in date:
1112 a, b = date.split(" to ")
1113 a, b = date.split(" to ")
1113 start, stop = lower(a), upper(b)
1114 start, stop = lower(a), upper(b)
1114 return lambda x: x >= start and x <= stop
1115 return lambda x: x >= start and x <= stop
1115 else:
1116 else:
1116 start, stop = lower(date), upper(date)
1117 start, stop = lower(date), upper(date)
1117 return lambda x: x >= start and x <= stop
1118 return lambda x: x >= start and x <= stop
1118
1119
1119 def shortuser(user):
1120 def shortuser(user):
1120 """Return a short representation of a user name or email address."""
1121 """Return a short representation of a user name or email address."""
1121 f = user.find('@')
1122 f = user.find('@')
1122 if f >= 0:
1123 if f >= 0:
1123 user = user[:f]
1124 user = user[:f]
1124 f = user.find('<')
1125 f = user.find('<')
1125 if f >= 0:
1126 if f >= 0:
1126 user = user[f + 1:]
1127 user = user[f + 1:]
1127 f = user.find(' ')
1128 f = user.find(' ')
1128 if f >= 0:
1129 if f >= 0:
1129 user = user[:f]
1130 user = user[:f]
1130 f = user.find('.')
1131 f = user.find('.')
1131 if f >= 0:
1132 if f >= 0:
1132 user = user[:f]
1133 user = user[:f]
1133 return user
1134 return user
1134
1135
1135 def emailuser(user):
1136 def emailuser(user):
1136 """Return the user portion of an email address."""
1137 """Return the user portion of an email address."""
1137 f = user.find('@')
1138 f = user.find('@')
1138 if f >= 0:
1139 if f >= 0:
1139 user = user[:f]
1140 user = user[:f]
1140 f = user.find('<')
1141 f = user.find('<')
1141 if f >= 0:
1142 if f >= 0:
1142 user = user[f + 1:]
1143 user = user[f + 1:]
1143 return user
1144 return user
1144
1145
1145 def email(author):
1146 def email(author):
1146 '''get email of author.'''
1147 '''get email of author.'''
1147 r = author.find('>')
1148 r = author.find('>')
1148 if r == -1:
1149 if r == -1:
1149 r = None
1150 r = None
1150 return author[author.find('<') + 1:r]
1151 return author[author.find('<') + 1:r]
1151
1152
1152 def _ellipsis(text, maxlength):
1153 def _ellipsis(text, maxlength):
1153 if len(text) <= maxlength:
1154 if len(text) <= maxlength:
1154 return text, False
1155 return text, False
1155 else:
1156 else:
1156 return "%s..." % (text[:maxlength - 3]), True
1157 return "%s..." % (text[:maxlength - 3]), True
1157
1158
1158 def ellipsis(text, maxlength=400):
1159 def ellipsis(text, maxlength=400):
1159 """Trim string to at most maxlength (default: 400) characters."""
1160 """Trim string to at most maxlength (default: 400) characters."""
1160 try:
1161 try:
1161 # use unicode not to split at intermediate multi-byte sequence
1162 # use unicode not to split at intermediate multi-byte sequence
1162 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1163 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1163 maxlength)
1164 maxlength)
1164 if not truncated:
1165 if not truncated:
1165 return text
1166 return text
1166 return utext.encode(encoding.encoding)
1167 return utext.encode(encoding.encoding)
1167 except (UnicodeDecodeError, UnicodeEncodeError):
1168 except (UnicodeDecodeError, UnicodeEncodeError):
1168 return _ellipsis(text, maxlength)[0]
1169 return _ellipsis(text, maxlength)[0]
1169
1170
1170 _byteunits = (
1171 _byteunits = (
1171 (100, 1 << 30, _('%.0f GB')),
1172 (100, 1 << 30, _('%.0f GB')),
1172 (10, 1 << 30, _('%.1f GB')),
1173 (10, 1 << 30, _('%.1f GB')),
1173 (1, 1 << 30, _('%.2f GB')),
1174 (1, 1 << 30, _('%.2f GB')),
1174 (100, 1 << 20, _('%.0f MB')),
1175 (100, 1 << 20, _('%.0f MB')),
1175 (10, 1 << 20, _('%.1f MB')),
1176 (10, 1 << 20, _('%.1f MB')),
1176 (1, 1 << 20, _('%.2f MB')),
1177 (1, 1 << 20, _('%.2f MB')),
1177 (100, 1 << 10, _('%.0f KB')),
1178 (100, 1 << 10, _('%.0f KB')),
1178 (10, 1 << 10, _('%.1f KB')),
1179 (10, 1 << 10, _('%.1f KB')),
1179 (1, 1 << 10, _('%.2f KB')),
1180 (1, 1 << 10, _('%.2f KB')),
1180 (1, 1, _('%.0f bytes')),
1181 (1, 1, _('%.0f bytes')),
1181 )
1182 )
1182
1183
1183 def bytecount(nbytes):
1184 def bytecount(nbytes):
1184 '''return byte count formatted as readable string, with units'''
1185 '''return byte count formatted as readable string, with units'''
1185
1186
1186 for multiplier, divisor, format in _byteunits:
1187 for multiplier, divisor, format in _byteunits:
1187 if nbytes >= divisor * multiplier:
1188 if nbytes >= divisor * multiplier:
1188 return format % (nbytes / float(divisor))
1189 return format % (nbytes / float(divisor))
1189 return _byteunits[-1][2] % nbytes
1190 return _byteunits[-1][2] % nbytes
1190
1191
1191 def uirepr(s):
1192 def uirepr(s):
1192 # Avoid double backslash in Windows path repr()
1193 # Avoid double backslash in Windows path repr()
1193 return repr(s).replace('\\\\', '\\')
1194 return repr(s).replace('\\\\', '\\')
1194
1195
1195 # delay import of textwrap
1196 # delay import of textwrap
1196 def MBTextWrapper(**kwargs):
1197 def MBTextWrapper(**kwargs):
1197 class tw(textwrap.TextWrapper):
1198 class tw(textwrap.TextWrapper):
1198 """
1199 """
1199 Extend TextWrapper for width-awareness.
1200 Extend TextWrapper for width-awareness.
1200
1201
1201 Neither number of 'bytes' in any encoding nor 'characters' is
1202 Neither number of 'bytes' in any encoding nor 'characters' is
1202 appropriate to calculate terminal columns for specified string.
1203 appropriate to calculate terminal columns for specified string.
1203
1204
1204 Original TextWrapper implementation uses built-in 'len()' directly,
1205 Original TextWrapper implementation uses built-in 'len()' directly,
1205 so overriding is needed to use width information of each characters.
1206 so overriding is needed to use width information of each characters.
1206
1207
1207 In addition, characters classified into 'ambiguous' width are
1208 In addition, characters classified into 'ambiguous' width are
1208 treated as wide in east asian area, but as narrow in other.
1209 treated as wide in east asian area, but as narrow in other.
1209
1210
1210 This requires use decision to determine width of such characters.
1211 This requires use decision to determine width of such characters.
1211 """
1212 """
1212 def __init__(self, **kwargs):
1213 def __init__(self, **kwargs):
1213 textwrap.TextWrapper.__init__(self, **kwargs)
1214 textwrap.TextWrapper.__init__(self, **kwargs)
1214
1215
1215 # for compatibility between 2.4 and 2.6
1216 # for compatibility between 2.4 and 2.6
1216 if getattr(self, 'drop_whitespace', None) is None:
1217 if getattr(self, 'drop_whitespace', None) is None:
1217 self.drop_whitespace = kwargs.get('drop_whitespace', True)
1218 self.drop_whitespace = kwargs.get('drop_whitespace', True)
1218
1219
1219 def _cutdown(self, ucstr, space_left):
1220 def _cutdown(self, ucstr, space_left):
1220 l = 0
1221 l = 0
1221 colwidth = encoding.ucolwidth
1222 colwidth = encoding.ucolwidth
1222 for i in xrange(len(ucstr)):
1223 for i in xrange(len(ucstr)):
1223 l += colwidth(ucstr[i])
1224 l += colwidth(ucstr[i])
1224 if space_left < l:
1225 if space_left < l:
1225 return (ucstr[:i], ucstr[i:])
1226 return (ucstr[:i], ucstr[i:])
1226 return ucstr, ''
1227 return ucstr, ''
1227
1228
1228 # overriding of base class
1229 # overriding of base class
1229 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1230 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1230 space_left = max(width - cur_len, 1)
1231 space_left = max(width - cur_len, 1)
1231
1232
1232 if self.break_long_words:
1233 if self.break_long_words:
1233 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1234 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1234 cur_line.append(cut)
1235 cur_line.append(cut)
1235 reversed_chunks[-1] = res
1236 reversed_chunks[-1] = res
1236 elif not cur_line:
1237 elif not cur_line:
1237 cur_line.append(reversed_chunks.pop())
1238 cur_line.append(reversed_chunks.pop())
1238
1239
1239 # this overriding code is imported from TextWrapper of python 2.6
1240 # this overriding code is imported from TextWrapper of python 2.6
1240 # to calculate columns of string by 'encoding.ucolwidth()'
1241 # to calculate columns of string by 'encoding.ucolwidth()'
1241 def _wrap_chunks(self, chunks):
1242 def _wrap_chunks(self, chunks):
1242 colwidth = encoding.ucolwidth
1243 colwidth = encoding.ucolwidth
1243
1244
1244 lines = []
1245 lines = []
1245 if self.width <= 0:
1246 if self.width <= 0:
1246 raise ValueError("invalid width %r (must be > 0)" % self.width)
1247 raise ValueError("invalid width %r (must be > 0)" % self.width)
1247
1248
1248 # Arrange in reverse order so items can be efficiently popped
1249 # Arrange in reverse order so items can be efficiently popped
1249 # from a stack of chucks.
1250 # from a stack of chucks.
1250 chunks.reverse()
1251 chunks.reverse()
1251
1252
1252 while chunks:
1253 while chunks:
1253
1254
1254 # Start the list of chunks that will make up the current line.
1255 # Start the list of chunks that will make up the current line.
1255 # cur_len is just the length of all the chunks in cur_line.
1256 # cur_len is just the length of all the chunks in cur_line.
1256 cur_line = []
1257 cur_line = []
1257 cur_len = 0
1258 cur_len = 0
1258
1259
1259 # Figure out which static string will prefix this line.
1260 # Figure out which static string will prefix this line.
1260 if lines:
1261 if lines:
1261 indent = self.subsequent_indent
1262 indent = self.subsequent_indent
1262 else:
1263 else:
1263 indent = self.initial_indent
1264 indent = self.initial_indent
1264
1265
1265 # Maximum width for this line.
1266 # Maximum width for this line.
1266 width = self.width - len(indent)
1267 width = self.width - len(indent)
1267
1268
1268 # First chunk on line is whitespace -- drop it, unless this
1269 # First chunk on line is whitespace -- drop it, unless this
1269 # is the very beginning of the text (ie. no lines started yet).
1270 # is the very beginning of the text (ie. no lines started yet).
1270 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1271 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1271 del chunks[-1]
1272 del chunks[-1]
1272
1273
1273 while chunks:
1274 while chunks:
1274 l = colwidth(chunks[-1])
1275 l = colwidth(chunks[-1])
1275
1276
1276 # Can at least squeeze this chunk onto the current line.
1277 # Can at least squeeze this chunk onto the current line.
1277 if cur_len + l <= width:
1278 if cur_len + l <= width:
1278 cur_line.append(chunks.pop())
1279 cur_line.append(chunks.pop())
1279 cur_len += l
1280 cur_len += l
1280
1281
1281 # Nope, this line is full.
1282 # Nope, this line is full.
1282 else:
1283 else:
1283 break
1284 break
1284
1285
1285 # The current line is full, and the next chunk is too big to
1286 # The current line is full, and the next chunk is too big to
1286 # fit on *any* line (not just this one).
1287 # fit on *any* line (not just this one).
1287 if chunks and colwidth(chunks[-1]) > width:
1288 if chunks and colwidth(chunks[-1]) > width:
1288 self._handle_long_word(chunks, cur_line, cur_len, width)
1289 self._handle_long_word(chunks, cur_line, cur_len, width)
1289
1290
1290 # If the last chunk on this line is all whitespace, drop it.
1291 # If the last chunk on this line is all whitespace, drop it.
1291 if (self.drop_whitespace and
1292 if (self.drop_whitespace and
1292 cur_line and cur_line[-1].strip() == ''):
1293 cur_line and cur_line[-1].strip() == ''):
1293 del cur_line[-1]
1294 del cur_line[-1]
1294
1295
1295 # Convert current line back to a string and store it in list
1296 # Convert current line back to a string and store it in list
1296 # of all lines (return value).
1297 # of all lines (return value).
1297 if cur_line:
1298 if cur_line:
1298 lines.append(indent + ''.join(cur_line))
1299 lines.append(indent + ''.join(cur_line))
1299
1300
1300 return lines
1301 return lines
1301
1302
1302 global MBTextWrapper
1303 global MBTextWrapper
1303 MBTextWrapper = tw
1304 MBTextWrapper = tw
1304 return tw(**kwargs)
1305 return tw(**kwargs)
1305
1306
1306 def wrap(line, width, initindent='', hangindent=''):
1307 def wrap(line, width, initindent='', hangindent=''):
1307 maxindent = max(len(hangindent), len(initindent))
1308 maxindent = max(len(hangindent), len(initindent))
1308 if width <= maxindent:
1309 if width <= maxindent:
1309 # adjust for weird terminal size
1310 # adjust for weird terminal size
1310 width = max(78, maxindent + 1)
1311 width = max(78, maxindent + 1)
1311 line = line.decode(encoding.encoding, encoding.encodingmode)
1312 line = line.decode(encoding.encoding, encoding.encodingmode)
1312 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1313 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1313 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1314 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1314 wrapper = MBTextWrapper(width=width,
1315 wrapper = MBTextWrapper(width=width,
1315 initial_indent=initindent,
1316 initial_indent=initindent,
1316 subsequent_indent=hangindent)
1317 subsequent_indent=hangindent)
1317 return wrapper.fill(line).encode(encoding.encoding)
1318 return wrapper.fill(line).encode(encoding.encoding)
1318
1319
1319 def iterlines(iterator):
1320 def iterlines(iterator):
1320 for chunk in iterator:
1321 for chunk in iterator:
1321 for line in chunk.splitlines():
1322 for line in chunk.splitlines():
1322 yield line
1323 yield line
1323
1324
1324 def expandpath(path):
1325 def expandpath(path):
1325 return os.path.expanduser(os.path.expandvars(path))
1326 return os.path.expanduser(os.path.expandvars(path))
1326
1327
1327 def hgcmd():
1328 def hgcmd():
1328 """Return the command used to execute current hg
1329 """Return the command used to execute current hg
1329
1330
1330 This is different from hgexecutable() because on Windows we want
1331 This is different from hgexecutable() because on Windows we want
1331 to avoid things opening new shell windows like batch files, so we
1332 to avoid things opening new shell windows like batch files, so we
1332 get either the python call or current executable.
1333 get either the python call or current executable.
1333 """
1334 """
1334 if mainfrozen():
1335 if mainfrozen():
1335 return [sys.executable]
1336 return [sys.executable]
1336 return gethgcmd()
1337 return gethgcmd()
1337
1338
1338 def rundetached(args, condfn):
1339 def rundetached(args, condfn):
1339 """Execute the argument list in a detached process.
1340 """Execute the argument list in a detached process.
1340
1341
1341 condfn is a callable which is called repeatedly and should return
1342 condfn is a callable which is called repeatedly and should return
1342 True once the child process is known to have started successfully.
1343 True once the child process is known to have started successfully.
1343 At this point, the child process PID is returned. If the child
1344 At this point, the child process PID is returned. If the child
1344 process fails to start or finishes before condfn() evaluates to
1345 process fails to start or finishes before condfn() evaluates to
1345 True, return -1.
1346 True, return -1.
1346 """
1347 """
1347 # Windows case is easier because the child process is either
1348 # Windows case is easier because the child process is either
1348 # successfully starting and validating the condition or exiting
1349 # successfully starting and validating the condition or exiting
1349 # on failure. We just poll on its PID. On Unix, if the child
1350 # on failure. We just poll on its PID. On Unix, if the child
1350 # process fails to start, it will be left in a zombie state until
1351 # process fails to start, it will be left in a zombie state until
1351 # the parent wait on it, which we cannot do since we expect a long
1352 # the parent wait on it, which we cannot do since we expect a long
1352 # running process on success. Instead we listen for SIGCHLD telling
1353 # running process on success. Instead we listen for SIGCHLD telling
1353 # us our child process terminated.
1354 # us our child process terminated.
1354 terminated = set()
1355 terminated = set()
1355 def handler(signum, frame):
1356 def handler(signum, frame):
1356 terminated.add(os.wait())
1357 terminated.add(os.wait())
1357 prevhandler = None
1358 prevhandler = None
1358 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1359 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1359 if SIGCHLD is not None:
1360 if SIGCHLD is not None:
1360 prevhandler = signal.signal(SIGCHLD, handler)
1361 prevhandler = signal.signal(SIGCHLD, handler)
1361 try:
1362 try:
1362 pid = spawndetached(args)
1363 pid = spawndetached(args)
1363 while not condfn():
1364 while not condfn():
1364 if ((pid in terminated or not testpid(pid))
1365 if ((pid in terminated or not testpid(pid))
1365 and not condfn()):
1366 and not condfn()):
1366 return -1
1367 return -1
1367 time.sleep(0.1)
1368 time.sleep(0.1)
1368 return pid
1369 return pid
1369 finally:
1370 finally:
1370 if prevhandler is not None:
1371 if prevhandler is not None:
1371 signal.signal(signal.SIGCHLD, prevhandler)
1372 signal.signal(signal.SIGCHLD, prevhandler)
1372
1373
1373 try:
1374 try:
1374 any, all = any, all
1375 any, all = any, all
1375 except NameError:
1376 except NameError:
1376 def any(iterable):
1377 def any(iterable):
1377 for i in iterable:
1378 for i in iterable:
1378 if i:
1379 if i:
1379 return True
1380 return True
1380 return False
1381 return False
1381
1382
1382 def all(iterable):
1383 def all(iterable):
1383 for i in iterable:
1384 for i in iterable:
1384 if not i:
1385 if not i:
1385 return False
1386 return False
1386 return True
1387 return True
1387
1388
1388 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1389 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1389 """Return the result of interpolating items in the mapping into string s.
1390 """Return the result of interpolating items in the mapping into string s.
1390
1391
1391 prefix is a single character string, or a two character string with
1392 prefix is a single character string, or a two character string with
1392 a backslash as the first character if the prefix needs to be escaped in
1393 a backslash as the first character if the prefix needs to be escaped in
1393 a regular expression.
1394 a regular expression.
1394
1395
1395 fn is an optional function that will be applied to the replacement text
1396 fn is an optional function that will be applied to the replacement text
1396 just before replacement.
1397 just before replacement.
1397
1398
1398 escape_prefix is an optional flag that allows using doubled prefix for
1399 escape_prefix is an optional flag that allows using doubled prefix for
1399 its escaping.
1400 its escaping.
1400 """
1401 """
1401 fn = fn or (lambda s: s)
1402 fn = fn or (lambda s: s)
1402 patterns = '|'.join(mapping.keys())
1403 patterns = '|'.join(mapping.keys())
1403 if escape_prefix:
1404 if escape_prefix:
1404 patterns += '|' + prefix
1405 patterns += '|' + prefix
1405 if len(prefix) > 1:
1406 if len(prefix) > 1:
1406 prefix_char = prefix[1:]
1407 prefix_char = prefix[1:]
1407 else:
1408 else:
1408 prefix_char = prefix
1409 prefix_char = prefix
1409 mapping[prefix_char] = prefix_char
1410 mapping[prefix_char] = prefix_char
1410 r = re.compile(r'%s(%s)' % (prefix, patterns))
1411 r = re.compile(r'%s(%s)' % (prefix, patterns))
1411 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1412 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1412
1413
1413 def getport(port):
1414 def getport(port):
1414 """Return the port for a given network service.
1415 """Return the port for a given network service.
1415
1416
1416 If port is an integer, it's returned as is. If it's a string, it's
1417 If port is an integer, it's returned as is. If it's a string, it's
1417 looked up using socket.getservbyname(). If there's no matching
1418 looked up using socket.getservbyname(). If there's no matching
1418 service, util.Abort is raised.
1419 service, util.Abort is raised.
1419 """
1420 """
1420 try:
1421 try:
1421 return int(port)
1422 return int(port)
1422 except ValueError:
1423 except ValueError:
1423 pass
1424 pass
1424
1425
1425 try:
1426 try:
1426 return socket.getservbyname(port)
1427 return socket.getservbyname(port)
1427 except socket.error:
1428 except socket.error:
1428 raise Abort(_("no port number associated with service '%s'") % port)
1429 raise Abort(_("no port number associated with service '%s'") % port)
1429
1430
1430 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1431 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1431 '0': False, 'no': False, 'false': False, 'off': False,
1432 '0': False, 'no': False, 'false': False, 'off': False,
1432 'never': False}
1433 'never': False}
1433
1434
1434 def parsebool(s):
1435 def parsebool(s):
1435 """Parse s into a boolean.
1436 """Parse s into a boolean.
1436
1437
1437 If s is not a valid boolean, returns None.
1438 If s is not a valid boolean, returns None.
1438 """
1439 """
1439 return _booleans.get(s.lower(), None)
1440 return _booleans.get(s.lower(), None)
1440
1441
1441 _hexdig = '0123456789ABCDEFabcdef'
1442 _hexdig = '0123456789ABCDEFabcdef'
1442 _hextochr = dict((a + b, chr(int(a + b, 16)))
1443 _hextochr = dict((a + b, chr(int(a + b, 16)))
1443 for a in _hexdig for b in _hexdig)
1444 for a in _hexdig for b in _hexdig)
1444
1445
1445 def _urlunquote(s):
1446 def _urlunquote(s):
1446 """unquote('abc%20def') -> 'abc def'."""
1447 """unquote('abc%20def') -> 'abc def'."""
1447 res = s.split('%')
1448 res = s.split('%')
1448 # fastpath
1449 # fastpath
1449 if len(res) == 1:
1450 if len(res) == 1:
1450 return s
1451 return s
1451 s = res[0]
1452 s = res[0]
1452 for item in res[1:]:
1453 for item in res[1:]:
1453 try:
1454 try:
1454 s += _hextochr[item[:2]] + item[2:]
1455 s += _hextochr[item[:2]] + item[2:]
1455 except KeyError:
1456 except KeyError:
1456 s += '%' + item
1457 s += '%' + item
1457 except UnicodeDecodeError:
1458 except UnicodeDecodeError:
1458 s += unichr(int(item[:2], 16)) + item[2:]
1459 s += unichr(int(item[:2], 16)) + item[2:]
1459 return s
1460 return s
1460
1461
1461 class url(object):
1462 class url(object):
1462 r"""Reliable URL parser.
1463 r"""Reliable URL parser.
1463
1464
1464 This parses URLs and provides attributes for the following
1465 This parses URLs and provides attributes for the following
1465 components:
1466 components:
1466
1467
1467 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1468 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1468
1469
1469 Missing components are set to None. The only exception is
1470 Missing components are set to None. The only exception is
1470 fragment, which is set to '' if present but empty.
1471 fragment, which is set to '' if present but empty.
1471
1472
1472 If parsefragment is False, fragment is included in query. If
1473 If parsefragment is False, fragment is included in query. If
1473 parsequery is False, query is included in path. If both are
1474 parsequery is False, query is included in path. If both are
1474 False, both fragment and query are included in path.
1475 False, both fragment and query are included in path.
1475
1476
1476 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1477 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1477
1478
1478 Note that for backward compatibility reasons, bundle URLs do not
1479 Note that for backward compatibility reasons, bundle URLs do not
1479 take host names. That means 'bundle://../' has a path of '../'.
1480 take host names. That means 'bundle://../' has a path of '../'.
1480
1481
1481 Examples:
1482 Examples:
1482
1483
1483 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1484 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1484 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1485 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1485 >>> url('ssh://[::1]:2200//home/joe/repo')
1486 >>> url('ssh://[::1]:2200//home/joe/repo')
1486 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1487 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1487 >>> url('file:///home/joe/repo')
1488 >>> url('file:///home/joe/repo')
1488 <url scheme: 'file', path: '/home/joe/repo'>
1489 <url scheme: 'file', path: '/home/joe/repo'>
1489 >>> url('file:///c:/temp/foo/')
1490 >>> url('file:///c:/temp/foo/')
1490 <url scheme: 'file', path: 'c:/temp/foo/'>
1491 <url scheme: 'file', path: 'c:/temp/foo/'>
1491 >>> url('bundle:foo')
1492 >>> url('bundle:foo')
1492 <url scheme: 'bundle', path: 'foo'>
1493 <url scheme: 'bundle', path: 'foo'>
1493 >>> url('bundle://../foo')
1494 >>> url('bundle://../foo')
1494 <url scheme: 'bundle', path: '../foo'>
1495 <url scheme: 'bundle', path: '../foo'>
1495 >>> url(r'c:\foo\bar')
1496 >>> url(r'c:\foo\bar')
1496 <url path: 'c:\\foo\\bar'>
1497 <url path: 'c:\\foo\\bar'>
1497 >>> url(r'\\blah\blah\blah')
1498 >>> url(r'\\blah\blah\blah')
1498 <url path: '\\\\blah\\blah\\blah'>
1499 <url path: '\\\\blah\\blah\\blah'>
1499 >>> url(r'\\blah\blah\blah#baz')
1500 >>> url(r'\\blah\blah\blah#baz')
1500 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
1501 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
1501
1502
1502 Authentication credentials:
1503 Authentication credentials:
1503
1504
1504 >>> url('ssh://joe:xyz@x/repo')
1505 >>> url('ssh://joe:xyz@x/repo')
1505 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1506 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1506 >>> url('ssh://joe@x/repo')
1507 >>> url('ssh://joe@x/repo')
1507 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1508 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1508
1509
1509 Query strings and fragments:
1510 Query strings and fragments:
1510
1511
1511 >>> url('http://host/a?b#c')
1512 >>> url('http://host/a?b#c')
1512 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1513 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1513 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1514 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1514 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1515 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1515 """
1516 """
1516
1517
1517 _safechars = "!~*'()+"
1518 _safechars = "!~*'()+"
1518 _safepchars = "/!~*'()+:"
1519 _safepchars = "/!~*'()+:"
1519 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1520 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1520
1521
1521 def __init__(self, path, parsequery=True, parsefragment=True):
1522 def __init__(self, path, parsequery=True, parsefragment=True):
1522 # We slowly chomp away at path until we have only the path left
1523 # We slowly chomp away at path until we have only the path left
1523 self.scheme = self.user = self.passwd = self.host = None
1524 self.scheme = self.user = self.passwd = self.host = None
1524 self.port = self.path = self.query = self.fragment = None
1525 self.port = self.path = self.query = self.fragment = None
1525 self._localpath = True
1526 self._localpath = True
1526 self._hostport = ''
1527 self._hostport = ''
1527 self._origpath = path
1528 self._origpath = path
1528
1529
1529 if parsefragment and '#' in path:
1530 if parsefragment and '#' in path:
1530 path, self.fragment = path.split('#', 1)
1531 path, self.fragment = path.split('#', 1)
1531 if not path:
1532 if not path:
1532 path = None
1533 path = None
1533
1534
1534 # special case for Windows drive letters and UNC paths
1535 # special case for Windows drive letters and UNC paths
1535 if hasdriveletter(path) or path.startswith(r'\\'):
1536 if hasdriveletter(path) or path.startswith(r'\\'):
1536 self.path = path
1537 self.path = path
1537 return
1538 return
1538
1539
1539 # For compatibility reasons, we can't handle bundle paths as
1540 # For compatibility reasons, we can't handle bundle paths as
1540 # normal URLS
1541 # normal URLS
1541 if path.startswith('bundle:'):
1542 if path.startswith('bundle:'):
1542 self.scheme = 'bundle'
1543 self.scheme = 'bundle'
1543 path = path[7:]
1544 path = path[7:]
1544 if path.startswith('//'):
1545 if path.startswith('//'):
1545 path = path[2:]
1546 path = path[2:]
1546 self.path = path
1547 self.path = path
1547 return
1548 return
1548
1549
1549 if self._matchscheme(path):
1550 if self._matchscheme(path):
1550 parts = path.split(':', 1)
1551 parts = path.split(':', 1)
1551 if parts[0]:
1552 if parts[0]:
1552 self.scheme, path = parts
1553 self.scheme, path = parts
1553 self._localpath = False
1554 self._localpath = False
1554
1555
1555 if not path:
1556 if not path:
1556 path = None
1557 path = None
1557 if self._localpath:
1558 if self._localpath:
1558 self.path = ''
1559 self.path = ''
1559 return
1560 return
1560 else:
1561 else:
1561 if self._localpath:
1562 if self._localpath:
1562 self.path = path
1563 self.path = path
1563 return
1564 return
1564
1565
1565 if parsequery and '?' in path:
1566 if parsequery and '?' in path:
1566 path, self.query = path.split('?', 1)
1567 path, self.query = path.split('?', 1)
1567 if not path:
1568 if not path:
1568 path = None
1569 path = None
1569 if not self.query:
1570 if not self.query:
1570 self.query = None
1571 self.query = None
1571
1572
1572 # // is required to specify a host/authority
1573 # // is required to specify a host/authority
1573 if path and path.startswith('//'):
1574 if path and path.startswith('//'):
1574 parts = path[2:].split('/', 1)
1575 parts = path[2:].split('/', 1)
1575 if len(parts) > 1:
1576 if len(parts) > 1:
1576 self.host, path = parts
1577 self.host, path = parts
1577 path = path
1578 path = path
1578 else:
1579 else:
1579 self.host = parts[0]
1580 self.host = parts[0]
1580 path = None
1581 path = None
1581 if not self.host:
1582 if not self.host:
1582 self.host = None
1583 self.host = None
1583 # path of file:///d is /d
1584 # path of file:///d is /d
1584 # path of file:///d:/ is d:/, not /d:/
1585 # path of file:///d:/ is d:/, not /d:/
1585 if path and not hasdriveletter(path):
1586 if path and not hasdriveletter(path):
1586 path = '/' + path
1587 path = '/' + path
1587
1588
1588 if self.host and '@' in self.host:
1589 if self.host and '@' in self.host:
1589 self.user, self.host = self.host.rsplit('@', 1)
1590 self.user, self.host = self.host.rsplit('@', 1)
1590 if ':' in self.user:
1591 if ':' in self.user:
1591 self.user, self.passwd = self.user.split(':', 1)
1592 self.user, self.passwd = self.user.split(':', 1)
1592 if not self.host:
1593 if not self.host:
1593 self.host = None
1594 self.host = None
1594
1595
1595 # Don't split on colons in IPv6 addresses without ports
1596 # Don't split on colons in IPv6 addresses without ports
1596 if (self.host and ':' in self.host and
1597 if (self.host and ':' in self.host and
1597 not (self.host.startswith('[') and self.host.endswith(']'))):
1598 not (self.host.startswith('[') and self.host.endswith(']'))):
1598 self._hostport = self.host
1599 self._hostport = self.host
1599 self.host, self.port = self.host.rsplit(':', 1)
1600 self.host, self.port = self.host.rsplit(':', 1)
1600 if not self.host:
1601 if not self.host:
1601 self.host = None
1602 self.host = None
1602
1603
1603 if (self.host and self.scheme == 'file' and
1604 if (self.host and self.scheme == 'file' and
1604 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1605 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1605 raise Abort(_('file:// URLs can only refer to localhost'))
1606 raise Abort(_('file:// URLs can only refer to localhost'))
1606
1607
1607 self.path = path
1608 self.path = path
1608
1609
1609 # leave the query string escaped
1610 # leave the query string escaped
1610 for a in ('user', 'passwd', 'host', 'port',
1611 for a in ('user', 'passwd', 'host', 'port',
1611 'path', 'fragment'):
1612 'path', 'fragment'):
1612 v = getattr(self, a)
1613 v = getattr(self, a)
1613 if v is not None:
1614 if v is not None:
1614 setattr(self, a, _urlunquote(v))
1615 setattr(self, a, _urlunquote(v))
1615
1616
1616 def __repr__(self):
1617 def __repr__(self):
1617 attrs = []
1618 attrs = []
1618 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1619 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1619 'query', 'fragment'):
1620 'query', 'fragment'):
1620 v = getattr(self, a)
1621 v = getattr(self, a)
1621 if v is not None:
1622 if v is not None:
1622 attrs.append('%s: %r' % (a, v))
1623 attrs.append('%s: %r' % (a, v))
1623 return '<url %s>' % ', '.join(attrs)
1624 return '<url %s>' % ', '.join(attrs)
1624
1625
1625 def __str__(self):
1626 def __str__(self):
1626 r"""Join the URL's components back into a URL string.
1627 r"""Join the URL's components back into a URL string.
1627
1628
1628 Examples:
1629 Examples:
1629
1630
1630 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
1631 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
1631 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
1632 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
1632 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
1633 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
1633 'http://user:pw@host:80/?foo=bar&baz=42'
1634 'http://user:pw@host:80/?foo=bar&baz=42'
1634 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
1635 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
1635 'http://user:pw@host:80/?foo=bar%3dbaz'
1636 'http://user:pw@host:80/?foo=bar%3dbaz'
1636 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1637 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1637 'ssh://user:pw@[::1]:2200//home/joe#'
1638 'ssh://user:pw@[::1]:2200//home/joe#'
1638 >>> str(url('http://localhost:80//'))
1639 >>> str(url('http://localhost:80//'))
1639 'http://localhost:80//'
1640 'http://localhost:80//'
1640 >>> str(url('http://localhost:80/'))
1641 >>> str(url('http://localhost:80/'))
1641 'http://localhost:80/'
1642 'http://localhost:80/'
1642 >>> str(url('http://localhost:80'))
1643 >>> str(url('http://localhost:80'))
1643 'http://localhost:80/'
1644 'http://localhost:80/'
1644 >>> str(url('bundle:foo'))
1645 >>> str(url('bundle:foo'))
1645 'bundle:foo'
1646 'bundle:foo'
1646 >>> str(url('bundle://../foo'))
1647 >>> str(url('bundle://../foo'))
1647 'bundle:../foo'
1648 'bundle:../foo'
1648 >>> str(url('path'))
1649 >>> str(url('path'))
1649 'path'
1650 'path'
1650 >>> str(url('file:///tmp/foo/bar'))
1651 >>> str(url('file:///tmp/foo/bar'))
1651 'file:///tmp/foo/bar'
1652 'file:///tmp/foo/bar'
1652 >>> str(url('file:///c:/tmp/foo/bar'))
1653 >>> str(url('file:///c:/tmp/foo/bar'))
1653 'file:///c:/tmp/foo/bar'
1654 'file:///c:/tmp/foo/bar'
1654 >>> print url(r'bundle:foo\bar')
1655 >>> print url(r'bundle:foo\bar')
1655 bundle:foo\bar
1656 bundle:foo\bar
1656 """
1657 """
1657 if self._localpath:
1658 if self._localpath:
1658 s = self.path
1659 s = self.path
1659 if self.scheme == 'bundle':
1660 if self.scheme == 'bundle':
1660 s = 'bundle:' + s
1661 s = 'bundle:' + s
1661 if self.fragment:
1662 if self.fragment:
1662 s += '#' + self.fragment
1663 s += '#' + self.fragment
1663 return s
1664 return s
1664
1665
1665 s = self.scheme + ':'
1666 s = self.scheme + ':'
1666 if self.user or self.passwd or self.host:
1667 if self.user or self.passwd or self.host:
1667 s += '//'
1668 s += '//'
1668 elif self.scheme and (not self.path or self.path.startswith('/')
1669 elif self.scheme and (not self.path or self.path.startswith('/')
1669 or hasdriveletter(self.path)):
1670 or hasdriveletter(self.path)):
1670 s += '//'
1671 s += '//'
1671 if hasdriveletter(self.path):
1672 if hasdriveletter(self.path):
1672 s += '/'
1673 s += '/'
1673 if self.user:
1674 if self.user:
1674 s += urllib.quote(self.user, safe=self._safechars)
1675 s += urllib.quote(self.user, safe=self._safechars)
1675 if self.passwd:
1676 if self.passwd:
1676 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1677 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1677 if self.user or self.passwd:
1678 if self.user or self.passwd:
1678 s += '@'
1679 s += '@'
1679 if self.host:
1680 if self.host:
1680 if not (self.host.startswith('[') and self.host.endswith(']')):
1681 if not (self.host.startswith('[') and self.host.endswith(']')):
1681 s += urllib.quote(self.host)
1682 s += urllib.quote(self.host)
1682 else:
1683 else:
1683 s += self.host
1684 s += self.host
1684 if self.port:
1685 if self.port:
1685 s += ':' + urllib.quote(self.port)
1686 s += ':' + urllib.quote(self.port)
1686 if self.host:
1687 if self.host:
1687 s += '/'
1688 s += '/'
1688 if self.path:
1689 if self.path:
1689 # TODO: similar to the query string, we should not unescape the
1690 # TODO: similar to the query string, we should not unescape the
1690 # path when we store it, the path might contain '%2f' = '/',
1691 # path when we store it, the path might contain '%2f' = '/',
1691 # which we should *not* escape.
1692 # which we should *not* escape.
1692 s += urllib.quote(self.path, safe=self._safepchars)
1693 s += urllib.quote(self.path, safe=self._safepchars)
1693 if self.query:
1694 if self.query:
1694 # we store the query in escaped form.
1695 # we store the query in escaped form.
1695 s += '?' + self.query
1696 s += '?' + self.query
1696 if self.fragment is not None:
1697 if self.fragment is not None:
1697 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1698 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1698 return s
1699 return s
1699
1700
1700 def authinfo(self):
1701 def authinfo(self):
1701 user, passwd = self.user, self.passwd
1702 user, passwd = self.user, self.passwd
1702 try:
1703 try:
1703 self.user, self.passwd = None, None
1704 self.user, self.passwd = None, None
1704 s = str(self)
1705 s = str(self)
1705 finally:
1706 finally:
1706 self.user, self.passwd = user, passwd
1707 self.user, self.passwd = user, passwd
1707 if not self.user:
1708 if not self.user:
1708 return (s, None)
1709 return (s, None)
1709 # authinfo[1] is passed to urllib2 password manager, and its
1710 # authinfo[1] is passed to urllib2 password manager, and its
1710 # URIs must not contain credentials. The host is passed in the
1711 # URIs must not contain credentials. The host is passed in the
1711 # URIs list because Python < 2.4.3 uses only that to search for
1712 # URIs list because Python < 2.4.3 uses only that to search for
1712 # a password.
1713 # a password.
1713 return (s, (None, (s, self.host),
1714 return (s, (None, (s, self.host),
1714 self.user, self.passwd or ''))
1715 self.user, self.passwd or ''))
1715
1716
1716 def isabs(self):
1717 def isabs(self):
1717 if self.scheme and self.scheme != 'file':
1718 if self.scheme and self.scheme != 'file':
1718 return True # remote URL
1719 return True # remote URL
1719 if hasdriveletter(self.path):
1720 if hasdriveletter(self.path):
1720 return True # absolute for our purposes - can't be joined()
1721 return True # absolute for our purposes - can't be joined()
1721 if self.path.startswith(r'\\'):
1722 if self.path.startswith(r'\\'):
1722 return True # Windows UNC path
1723 return True # Windows UNC path
1723 if self.path.startswith('/'):
1724 if self.path.startswith('/'):
1724 return True # POSIX-style
1725 return True # POSIX-style
1725 return False
1726 return False
1726
1727
1727 def localpath(self):
1728 def localpath(self):
1728 if self.scheme == 'file' or self.scheme == 'bundle':
1729 if self.scheme == 'file' or self.scheme == 'bundle':
1729 path = self.path or '/'
1730 path = self.path or '/'
1730 # For Windows, we need to promote hosts containing drive
1731 # For Windows, we need to promote hosts containing drive
1731 # letters to paths with drive letters.
1732 # letters to paths with drive letters.
1732 if hasdriveletter(self._hostport):
1733 if hasdriveletter(self._hostport):
1733 path = self._hostport + '/' + self.path
1734 path = self._hostport + '/' + self.path
1734 elif (self.host is not None and self.path
1735 elif (self.host is not None and self.path
1735 and not hasdriveletter(path)):
1736 and not hasdriveletter(path)):
1736 path = '/' + path
1737 path = '/' + path
1737 return path
1738 return path
1738 return self._origpath
1739 return self._origpath
1739
1740
1740 def hasscheme(path):
1741 def hasscheme(path):
1741 return bool(url(path).scheme)
1742 return bool(url(path).scheme)
1742
1743
1743 def hasdriveletter(path):
1744 def hasdriveletter(path):
1744 return path and path[1:2] == ':' and path[0:1].isalpha()
1745 return path and path[1:2] == ':' and path[0:1].isalpha()
1745
1746
1746 def urllocalpath(path):
1747 def urllocalpath(path):
1747 return url(path, parsequery=False, parsefragment=False).localpath()
1748 return url(path, parsequery=False, parsefragment=False).localpath()
1748
1749
1749 def hidepassword(u):
1750 def hidepassword(u):
1750 '''hide user credential in a url string'''
1751 '''hide user credential in a url string'''
1751 u = url(u)
1752 u = url(u)
1752 if u.passwd:
1753 if u.passwd:
1753 u.passwd = '***'
1754 u.passwd = '***'
1754 return str(u)
1755 return str(u)
1755
1756
1756 def removeauth(u):
1757 def removeauth(u):
1757 '''remove all authentication information from a url string'''
1758 '''remove all authentication information from a url string'''
1758 u = url(u)
1759 u = url(u)
1759 u.user = u.passwd = None
1760 u.user = u.passwd = None
1760 return str(u)
1761 return str(u)
1761
1762
1762 def isatty(fd):
1763 def isatty(fd):
1763 try:
1764 try:
1764 return fd.isatty()
1765 return fd.isatty()
1765 except AttributeError:
1766 except AttributeError:
1766 return False
1767 return False
General Comments 0
You need to be logged in to leave comments. Login now