##// END OF EJS Templates
context: generate file ancestors in reverse revision order (issue2642)...
Matt Mackall -
r13468:d1007023 stable
parent child Browse files
Show More
@@ -1,1106 +1,1110 b''
1 # context.py - changeset and file context objects for mercurial
1 # context.py - changeset and file context objects for mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 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 from node import nullid, nullrev, short, hex
8 from node import nullid, nullrev, short, hex
9 from i18n import _
9 from i18n import _
10 import ancestor, bdiff, error, util, subrepo, patch, encoding
10 import ancestor, bdiff, error, util, subrepo, patch, encoding
11 import os, errno, stat
11 import os, errno, stat
12
12
13 propertycache = util.propertycache
13 propertycache = util.propertycache
14
14
15 class changectx(object):
15 class changectx(object):
16 """A changecontext object makes access to data related to a particular
16 """A changecontext object makes access to data related to a particular
17 changeset convenient."""
17 changeset convenient."""
18 def __init__(self, repo, changeid=''):
18 def __init__(self, repo, changeid=''):
19 """changeid is a revision number, node, or tag"""
19 """changeid is a revision number, node, or tag"""
20 if changeid == '':
20 if changeid == '':
21 changeid = '.'
21 changeid = '.'
22 self._repo = repo
22 self._repo = repo
23 if isinstance(changeid, (long, int)):
23 if isinstance(changeid, (long, int)):
24 self._rev = changeid
24 self._rev = changeid
25 self._node = self._repo.changelog.node(changeid)
25 self._node = self._repo.changelog.node(changeid)
26 else:
26 else:
27 self._node = self._repo.lookup(changeid)
27 self._node = self._repo.lookup(changeid)
28 self._rev = self._repo.changelog.rev(self._node)
28 self._rev = self._repo.changelog.rev(self._node)
29
29
30 def __str__(self):
30 def __str__(self):
31 return short(self.node())
31 return short(self.node())
32
32
33 def __int__(self):
33 def __int__(self):
34 return self.rev()
34 return self.rev()
35
35
36 def __repr__(self):
36 def __repr__(self):
37 return "<changectx %s>" % str(self)
37 return "<changectx %s>" % str(self)
38
38
39 def __hash__(self):
39 def __hash__(self):
40 try:
40 try:
41 return hash(self._rev)
41 return hash(self._rev)
42 except AttributeError:
42 except AttributeError:
43 return id(self)
43 return id(self)
44
44
45 def __eq__(self, other):
45 def __eq__(self, other):
46 try:
46 try:
47 return self._rev == other._rev
47 return self._rev == other._rev
48 except AttributeError:
48 except AttributeError:
49 return False
49 return False
50
50
51 def __ne__(self, other):
51 def __ne__(self, other):
52 return not (self == other)
52 return not (self == other)
53
53
54 def __nonzero__(self):
54 def __nonzero__(self):
55 return self._rev != nullrev
55 return self._rev != nullrev
56
56
57 @propertycache
57 @propertycache
58 def _changeset(self):
58 def _changeset(self):
59 return self._repo.changelog.read(self.node())
59 return self._repo.changelog.read(self.node())
60
60
61 @propertycache
61 @propertycache
62 def _manifest(self):
62 def _manifest(self):
63 return self._repo.manifest.read(self._changeset[0])
63 return self._repo.manifest.read(self._changeset[0])
64
64
65 @propertycache
65 @propertycache
66 def _manifestdelta(self):
66 def _manifestdelta(self):
67 return self._repo.manifest.readdelta(self._changeset[0])
67 return self._repo.manifest.readdelta(self._changeset[0])
68
68
69 @propertycache
69 @propertycache
70 def _parents(self):
70 def _parents(self):
71 p = self._repo.changelog.parentrevs(self._rev)
71 p = self._repo.changelog.parentrevs(self._rev)
72 if p[1] == nullrev:
72 if p[1] == nullrev:
73 p = p[:-1]
73 p = p[:-1]
74 return [changectx(self._repo, x) for x in p]
74 return [changectx(self._repo, x) for x in p]
75
75
76 @propertycache
76 @propertycache
77 def substate(self):
77 def substate(self):
78 return subrepo.state(self, self._repo.ui)
78 return subrepo.state(self, self._repo.ui)
79
79
80 def __contains__(self, key):
80 def __contains__(self, key):
81 return key in self._manifest
81 return key in self._manifest
82
82
83 def __getitem__(self, key):
83 def __getitem__(self, key):
84 return self.filectx(key)
84 return self.filectx(key)
85
85
86 def __iter__(self):
86 def __iter__(self):
87 for f in sorted(self._manifest):
87 for f in sorted(self._manifest):
88 yield f
88 yield f
89
89
90 def changeset(self):
90 def changeset(self):
91 return self._changeset
91 return self._changeset
92 def manifest(self):
92 def manifest(self):
93 return self._manifest
93 return self._manifest
94 def manifestnode(self):
94 def manifestnode(self):
95 return self._changeset[0]
95 return self._changeset[0]
96
96
97 def rev(self):
97 def rev(self):
98 return self._rev
98 return self._rev
99 def node(self):
99 def node(self):
100 return self._node
100 return self._node
101 def hex(self):
101 def hex(self):
102 return hex(self._node)
102 return hex(self._node)
103 def user(self):
103 def user(self):
104 return self._changeset[1]
104 return self._changeset[1]
105 def date(self):
105 def date(self):
106 return self._changeset[2]
106 return self._changeset[2]
107 def files(self):
107 def files(self):
108 return self._changeset[3]
108 return self._changeset[3]
109 def description(self):
109 def description(self):
110 return self._changeset[4]
110 return self._changeset[4]
111 def branch(self):
111 def branch(self):
112 return encoding.tolocal(self._changeset[5].get("branch"))
112 return encoding.tolocal(self._changeset[5].get("branch"))
113 def extra(self):
113 def extra(self):
114 return self._changeset[5]
114 return self._changeset[5]
115 def tags(self):
115 def tags(self):
116 return self._repo.nodetags(self._node)
116 return self._repo.nodetags(self._node)
117 def bookmarks(self):
117 def bookmarks(self):
118 return self._repo.nodebookmarks(self._node)
118 return self._repo.nodebookmarks(self._node)
119
119
120 def parents(self):
120 def parents(self):
121 """return contexts for each parent changeset"""
121 """return contexts for each parent changeset"""
122 return self._parents
122 return self._parents
123
123
124 def p1(self):
124 def p1(self):
125 return self._parents[0]
125 return self._parents[0]
126
126
127 def p2(self):
127 def p2(self):
128 if len(self._parents) == 2:
128 if len(self._parents) == 2:
129 return self._parents[1]
129 return self._parents[1]
130 return changectx(self._repo, -1)
130 return changectx(self._repo, -1)
131
131
132 def children(self):
132 def children(self):
133 """return contexts for each child changeset"""
133 """return contexts for each child changeset"""
134 c = self._repo.changelog.children(self._node)
134 c = self._repo.changelog.children(self._node)
135 return [changectx(self._repo, x) for x in c]
135 return [changectx(self._repo, x) for x in c]
136
136
137 def ancestors(self):
137 def ancestors(self):
138 for a in self._repo.changelog.ancestors(self._rev):
138 for a in self._repo.changelog.ancestors(self._rev):
139 yield changectx(self._repo, a)
139 yield changectx(self._repo, a)
140
140
141 def descendants(self):
141 def descendants(self):
142 for d in self._repo.changelog.descendants(self._rev):
142 for d in self._repo.changelog.descendants(self._rev):
143 yield changectx(self._repo, d)
143 yield changectx(self._repo, d)
144
144
145 def _fileinfo(self, path):
145 def _fileinfo(self, path):
146 if '_manifest' in self.__dict__:
146 if '_manifest' in self.__dict__:
147 try:
147 try:
148 return self._manifest[path], self._manifest.flags(path)
148 return self._manifest[path], self._manifest.flags(path)
149 except KeyError:
149 except KeyError:
150 raise error.LookupError(self._node, path,
150 raise error.LookupError(self._node, path,
151 _('not found in manifest'))
151 _('not found in manifest'))
152 if '_manifestdelta' in self.__dict__ or path in self.files():
152 if '_manifestdelta' in self.__dict__ or path in self.files():
153 if path in self._manifestdelta:
153 if path in self._manifestdelta:
154 return self._manifestdelta[path], self._manifestdelta.flags(path)
154 return self._manifestdelta[path], self._manifestdelta.flags(path)
155 node, flag = self._repo.manifest.find(self._changeset[0], path)
155 node, flag = self._repo.manifest.find(self._changeset[0], path)
156 if not node:
156 if not node:
157 raise error.LookupError(self._node, path,
157 raise error.LookupError(self._node, path,
158 _('not found in manifest'))
158 _('not found in manifest'))
159
159
160 return node, flag
160 return node, flag
161
161
162 def filenode(self, path):
162 def filenode(self, path):
163 return self._fileinfo(path)[0]
163 return self._fileinfo(path)[0]
164
164
165 def flags(self, path):
165 def flags(self, path):
166 try:
166 try:
167 return self._fileinfo(path)[1]
167 return self._fileinfo(path)[1]
168 except error.LookupError:
168 except error.LookupError:
169 return ''
169 return ''
170
170
171 def filectx(self, path, fileid=None, filelog=None):
171 def filectx(self, path, fileid=None, filelog=None):
172 """get a file context from this changeset"""
172 """get a file context from this changeset"""
173 if fileid is None:
173 if fileid is None:
174 fileid = self.filenode(path)
174 fileid = self.filenode(path)
175 return filectx(self._repo, path, fileid=fileid,
175 return filectx(self._repo, path, fileid=fileid,
176 changectx=self, filelog=filelog)
176 changectx=self, filelog=filelog)
177
177
178 def ancestor(self, c2):
178 def ancestor(self, c2):
179 """
179 """
180 return the ancestor context of self and c2
180 return the ancestor context of self and c2
181 """
181 """
182 # deal with workingctxs
182 # deal with workingctxs
183 n2 = c2._node
183 n2 = c2._node
184 if n2 is None:
184 if n2 is None:
185 n2 = c2._parents[0]._node
185 n2 = c2._parents[0]._node
186 n = self._repo.changelog.ancestor(self._node, n2)
186 n = self._repo.changelog.ancestor(self._node, n2)
187 return changectx(self._repo, n)
187 return changectx(self._repo, n)
188
188
189 def walk(self, match):
189 def walk(self, match):
190 fset = set(match.files())
190 fset = set(match.files())
191 # for dirstate.walk, files=['.'] means "walk the whole tree".
191 # for dirstate.walk, files=['.'] means "walk the whole tree".
192 # follow that here, too
192 # follow that here, too
193 fset.discard('.')
193 fset.discard('.')
194 for fn in self:
194 for fn in self:
195 for ffn in fset:
195 for ffn in fset:
196 # match if the file is the exact name or a directory
196 # match if the file is the exact name or a directory
197 if ffn == fn or fn.startswith("%s/" % ffn):
197 if ffn == fn or fn.startswith("%s/" % ffn):
198 fset.remove(ffn)
198 fset.remove(ffn)
199 break
199 break
200 if match(fn):
200 if match(fn):
201 yield fn
201 yield fn
202 for fn in sorted(fset):
202 for fn in sorted(fset):
203 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
203 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
204 yield fn
204 yield fn
205
205
206 def sub(self, path):
206 def sub(self, path):
207 return subrepo.subrepo(self, path)
207 return subrepo.subrepo(self, path)
208
208
209 def diff(self, ctx2=None, match=None, **opts):
209 def diff(self, ctx2=None, match=None, **opts):
210 """Returns a diff generator for the given contexts and matcher"""
210 """Returns a diff generator for the given contexts and matcher"""
211 if ctx2 is None:
211 if ctx2 is None:
212 ctx2 = self.p1()
212 ctx2 = self.p1()
213 if ctx2 is not None and not isinstance(ctx2, changectx):
213 if ctx2 is not None and not isinstance(ctx2, changectx):
214 ctx2 = self._repo[ctx2]
214 ctx2 = self._repo[ctx2]
215 diffopts = patch.diffopts(self._repo.ui, opts)
215 diffopts = patch.diffopts(self._repo.ui, opts)
216 return patch.diff(self._repo, ctx2.node(), self.node(),
216 return patch.diff(self._repo, ctx2.node(), self.node(),
217 match=match, opts=diffopts)
217 match=match, opts=diffopts)
218
218
219 class filectx(object):
219 class filectx(object):
220 """A filecontext object makes access to data related to a particular
220 """A filecontext object makes access to data related to a particular
221 filerevision convenient."""
221 filerevision convenient."""
222 def __init__(self, repo, path, changeid=None, fileid=None,
222 def __init__(self, repo, path, changeid=None, fileid=None,
223 filelog=None, changectx=None):
223 filelog=None, changectx=None):
224 """changeid can be a changeset revision, node, or tag.
224 """changeid can be a changeset revision, node, or tag.
225 fileid can be a file revision or node."""
225 fileid can be a file revision or node."""
226 self._repo = repo
226 self._repo = repo
227 self._path = path
227 self._path = path
228
228
229 assert (changeid is not None
229 assert (changeid is not None
230 or fileid is not None
230 or fileid is not None
231 or changectx is not None), \
231 or changectx is not None), \
232 ("bad args: changeid=%r, fileid=%r, changectx=%r"
232 ("bad args: changeid=%r, fileid=%r, changectx=%r"
233 % (changeid, fileid, changectx))
233 % (changeid, fileid, changectx))
234
234
235 if filelog:
235 if filelog:
236 self._filelog = filelog
236 self._filelog = filelog
237
237
238 if changeid is not None:
238 if changeid is not None:
239 self._changeid = changeid
239 self._changeid = changeid
240 if changectx is not None:
240 if changectx is not None:
241 self._changectx = changectx
241 self._changectx = changectx
242 if fileid is not None:
242 if fileid is not None:
243 self._fileid = fileid
243 self._fileid = fileid
244
244
245 @propertycache
245 @propertycache
246 def _changectx(self):
246 def _changectx(self):
247 return changectx(self._repo, self._changeid)
247 return changectx(self._repo, self._changeid)
248
248
249 @propertycache
249 @propertycache
250 def _filelog(self):
250 def _filelog(self):
251 return self._repo.file(self._path)
251 return self._repo.file(self._path)
252
252
253 @propertycache
253 @propertycache
254 def _changeid(self):
254 def _changeid(self):
255 if '_changectx' in self.__dict__:
255 if '_changectx' in self.__dict__:
256 return self._changectx.rev()
256 return self._changectx.rev()
257 else:
257 else:
258 return self._filelog.linkrev(self._filerev)
258 return self._filelog.linkrev(self._filerev)
259
259
260 @propertycache
260 @propertycache
261 def _filenode(self):
261 def _filenode(self):
262 if '_fileid' in self.__dict__:
262 if '_fileid' in self.__dict__:
263 return self._filelog.lookup(self._fileid)
263 return self._filelog.lookup(self._fileid)
264 else:
264 else:
265 return self._changectx.filenode(self._path)
265 return self._changectx.filenode(self._path)
266
266
267 @propertycache
267 @propertycache
268 def _filerev(self):
268 def _filerev(self):
269 return self._filelog.rev(self._filenode)
269 return self._filelog.rev(self._filenode)
270
270
271 @propertycache
271 @propertycache
272 def _repopath(self):
272 def _repopath(self):
273 return self._path
273 return self._path
274
274
275 def __nonzero__(self):
275 def __nonzero__(self):
276 try:
276 try:
277 self._filenode
277 self._filenode
278 return True
278 return True
279 except error.LookupError:
279 except error.LookupError:
280 # file is missing
280 # file is missing
281 return False
281 return False
282
282
283 def __str__(self):
283 def __str__(self):
284 return "%s@%s" % (self.path(), short(self.node()))
284 return "%s@%s" % (self.path(), short(self.node()))
285
285
286 def __repr__(self):
286 def __repr__(self):
287 return "<filectx %s>" % str(self)
287 return "<filectx %s>" % str(self)
288
288
289 def __hash__(self):
289 def __hash__(self):
290 try:
290 try:
291 return hash((self._path, self._filenode))
291 return hash((self._path, self._filenode))
292 except AttributeError:
292 except AttributeError:
293 return id(self)
293 return id(self)
294
294
295 def __eq__(self, other):
295 def __eq__(self, other):
296 try:
296 try:
297 return (self._path == other._path
297 return (self._path == other._path
298 and self._filenode == other._filenode)
298 and self._filenode == other._filenode)
299 except AttributeError:
299 except AttributeError:
300 return False
300 return False
301
301
302 def __ne__(self, other):
302 def __ne__(self, other):
303 return not (self == other)
303 return not (self == other)
304
304
305 def filectx(self, fileid):
305 def filectx(self, fileid):
306 '''opens an arbitrary revision of the file without
306 '''opens an arbitrary revision of the file without
307 opening a new filelog'''
307 opening a new filelog'''
308 return filectx(self._repo, self._path, fileid=fileid,
308 return filectx(self._repo, self._path, fileid=fileid,
309 filelog=self._filelog)
309 filelog=self._filelog)
310
310
311 def filerev(self):
311 def filerev(self):
312 return self._filerev
312 return self._filerev
313 def filenode(self):
313 def filenode(self):
314 return self._filenode
314 return self._filenode
315 def flags(self):
315 def flags(self):
316 return self._changectx.flags(self._path)
316 return self._changectx.flags(self._path)
317 def filelog(self):
317 def filelog(self):
318 return self._filelog
318 return self._filelog
319
319
320 def rev(self):
320 def rev(self):
321 if '_changectx' in self.__dict__:
321 if '_changectx' in self.__dict__:
322 return self._changectx.rev()
322 return self._changectx.rev()
323 if '_changeid' in self.__dict__:
323 if '_changeid' in self.__dict__:
324 return self._changectx.rev()
324 return self._changectx.rev()
325 return self._filelog.linkrev(self._filerev)
325 return self._filelog.linkrev(self._filerev)
326
326
327 def linkrev(self):
327 def linkrev(self):
328 return self._filelog.linkrev(self._filerev)
328 return self._filelog.linkrev(self._filerev)
329 def node(self):
329 def node(self):
330 return self._changectx.node()
330 return self._changectx.node()
331 def hex(self):
331 def hex(self):
332 return hex(self.node())
332 return hex(self.node())
333 def user(self):
333 def user(self):
334 return self._changectx.user()
334 return self._changectx.user()
335 def date(self):
335 def date(self):
336 return self._changectx.date()
336 return self._changectx.date()
337 def files(self):
337 def files(self):
338 return self._changectx.files()
338 return self._changectx.files()
339 def description(self):
339 def description(self):
340 return self._changectx.description()
340 return self._changectx.description()
341 def branch(self):
341 def branch(self):
342 return self._changectx.branch()
342 return self._changectx.branch()
343 def extra(self):
343 def extra(self):
344 return self._changectx.extra()
344 return self._changectx.extra()
345 def manifest(self):
345 def manifest(self):
346 return self._changectx.manifest()
346 return self._changectx.manifest()
347 def changectx(self):
347 def changectx(self):
348 return self._changectx
348 return self._changectx
349
349
350 def data(self):
350 def data(self):
351 return self._filelog.read(self._filenode)
351 return self._filelog.read(self._filenode)
352 def path(self):
352 def path(self):
353 return self._path
353 return self._path
354 def size(self):
354 def size(self):
355 return self._filelog.size(self._filerev)
355 return self._filelog.size(self._filerev)
356
356
357 def cmp(self, fctx):
357 def cmp(self, fctx):
358 """compare with other file context
358 """compare with other file context
359
359
360 returns True if different than fctx.
360 returns True if different than fctx.
361 """
361 """
362 if (fctx._filerev is None and self._repo._encodefilterpats
362 if (fctx._filerev is None and self._repo._encodefilterpats
363 or self.size() == fctx.size()):
363 or self.size() == fctx.size()):
364 return self._filelog.cmp(self._filenode, fctx.data())
364 return self._filelog.cmp(self._filenode, fctx.data())
365
365
366 return True
366 return True
367
367
368 def renamed(self):
368 def renamed(self):
369 """check if file was actually renamed in this changeset revision
369 """check if file was actually renamed in this changeset revision
370
370
371 If rename logged in file revision, we report copy for changeset only
371 If rename logged in file revision, we report copy for changeset only
372 if file revisions linkrev points back to the changeset in question
372 if file revisions linkrev points back to the changeset in question
373 or both changeset parents contain different file revisions.
373 or both changeset parents contain different file revisions.
374 """
374 """
375
375
376 renamed = self._filelog.renamed(self._filenode)
376 renamed = self._filelog.renamed(self._filenode)
377 if not renamed:
377 if not renamed:
378 return renamed
378 return renamed
379
379
380 if self.rev() == self.linkrev():
380 if self.rev() == self.linkrev():
381 return renamed
381 return renamed
382
382
383 name = self.path()
383 name = self.path()
384 fnode = self._filenode
384 fnode = self._filenode
385 for p in self._changectx.parents():
385 for p in self._changectx.parents():
386 try:
386 try:
387 if fnode == p.filenode(name):
387 if fnode == p.filenode(name):
388 return None
388 return None
389 except error.LookupError:
389 except error.LookupError:
390 pass
390 pass
391 return renamed
391 return renamed
392
392
393 def parents(self):
393 def parents(self):
394 p = self._path
394 p = self._path
395 fl = self._filelog
395 fl = self._filelog
396 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
396 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
397
397
398 r = self._filelog.renamed(self._filenode)
398 r = self._filelog.renamed(self._filenode)
399 if r:
399 if r:
400 pl[0] = (r[0], r[1], None)
400 pl[0] = (r[0], r[1], None)
401
401
402 return [filectx(self._repo, p, fileid=n, filelog=l)
402 return [filectx(self._repo, p, fileid=n, filelog=l)
403 for p, n, l in pl if n != nullid]
403 for p, n, l in pl if n != nullid]
404
404
405 def children(self):
405 def children(self):
406 # hard for renames
406 # hard for renames
407 c = self._filelog.children(self._filenode)
407 c = self._filelog.children(self._filenode)
408 return [filectx(self._repo, self._path, fileid=x,
408 return [filectx(self._repo, self._path, fileid=x,
409 filelog=self._filelog) for x in c]
409 filelog=self._filelog) for x in c]
410
410
411 def annotate(self, follow=False, linenumber=None):
411 def annotate(self, follow=False, linenumber=None):
412 '''returns a list of tuples of (ctx, line) for each line
412 '''returns a list of tuples of (ctx, line) for each line
413 in the file, where ctx is the filectx of the node where
413 in the file, where ctx is the filectx of the node where
414 that line was last changed.
414 that line was last changed.
415 This returns tuples of ((ctx, linenumber), line) for each line,
415 This returns tuples of ((ctx, linenumber), line) for each line,
416 if "linenumber" parameter is NOT "None".
416 if "linenumber" parameter is NOT "None".
417 In such tuples, linenumber means one at the first appearance
417 In such tuples, linenumber means one at the first appearance
418 in the managed file.
418 in the managed file.
419 To reduce annotation cost,
419 To reduce annotation cost,
420 this returns fixed value(False is used) as linenumber,
420 this returns fixed value(False is used) as linenumber,
421 if "linenumber" parameter is "False".'''
421 if "linenumber" parameter is "False".'''
422
422
423 def decorate_compat(text, rev):
423 def decorate_compat(text, rev):
424 return ([rev] * len(text.splitlines()), text)
424 return ([rev] * len(text.splitlines()), text)
425
425
426 def without_linenumber(text, rev):
426 def without_linenumber(text, rev):
427 return ([(rev, False)] * len(text.splitlines()), text)
427 return ([(rev, False)] * len(text.splitlines()), text)
428
428
429 def with_linenumber(text, rev):
429 def with_linenumber(text, rev):
430 size = len(text.splitlines())
430 size = len(text.splitlines())
431 return ([(rev, i) for i in xrange(1, size + 1)], text)
431 return ([(rev, i) for i in xrange(1, size + 1)], text)
432
432
433 decorate = (((linenumber is None) and decorate_compat) or
433 decorate = (((linenumber is None) and decorate_compat) or
434 (linenumber and with_linenumber) or
434 (linenumber and with_linenumber) or
435 without_linenumber)
435 without_linenumber)
436
436
437 def pair(parent, child):
437 def pair(parent, child):
438 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
438 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
439 child[0][b1:b2] = parent[0][a1:a2]
439 child[0][b1:b2] = parent[0][a1:a2]
440 return child
440 return child
441
441
442 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
442 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
443 def getctx(path, fileid):
443 def getctx(path, fileid):
444 log = path == self._path and self._filelog or getlog(path)
444 log = path == self._path and self._filelog or getlog(path)
445 return filectx(self._repo, path, fileid=fileid, filelog=log)
445 return filectx(self._repo, path, fileid=fileid, filelog=log)
446 getctx = util.lrucachefunc(getctx)
446 getctx = util.lrucachefunc(getctx)
447
447
448 def parents(f):
448 def parents(f):
449 # we want to reuse filectx objects as much as possible
449 # we want to reuse filectx objects as much as possible
450 p = f._path
450 p = f._path
451 if f._filerev is None: # working dir
451 if f._filerev is None: # working dir
452 pl = [(n.path(), n.filerev()) for n in f.parents()]
452 pl = [(n.path(), n.filerev()) for n in f.parents()]
453 else:
453 else:
454 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
454 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
455
455
456 if follow:
456 if follow:
457 r = f.renamed()
457 r = f.renamed()
458 if r:
458 if r:
459 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
459 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
460
460
461 return [getctx(p, n) for p, n in pl if n != nullrev]
461 return [getctx(p, n) for p, n in pl if n != nullrev]
462
462
463 # use linkrev to find the first changeset where self appeared
463 # use linkrev to find the first changeset where self appeared
464 if self.rev() != self.linkrev():
464 if self.rev() != self.linkrev():
465 base = self.filectx(self.filerev())
465 base = self.filectx(self.filerev())
466 else:
466 else:
467 base = self
467 base = self
468
468
469 # find all ancestors
469 # find all ancestors
470 needed = {base: 1}
470 needed = {base: 1}
471 visit = [base]
471 visit = [base]
472 files = [base._path]
472 files = [base._path]
473 while visit:
473 while visit:
474 f = visit.pop(0)
474 f = visit.pop(0)
475 for p in parents(f):
475 for p in parents(f):
476 if p not in needed:
476 if p not in needed:
477 needed[p] = 1
477 needed[p] = 1
478 visit.append(p)
478 visit.append(p)
479 if p._path not in files:
479 if p._path not in files:
480 files.append(p._path)
480 files.append(p._path)
481 else:
481 else:
482 # count how many times we'll use this
482 # count how many times we'll use this
483 needed[p] += 1
483 needed[p] += 1
484
484
485 # sort by revision (per file) which is a topological order
485 # sort by revision (per file) which is a topological order
486 visit = []
486 visit = []
487 for f in files:
487 for f in files:
488 visit.extend(n for n in needed if n._path == f)
488 visit.extend(n for n in needed if n._path == f)
489
489
490 hist = {}
490 hist = {}
491 for f in sorted(visit, key=lambda x: x.rev()):
491 for f in sorted(visit, key=lambda x: x.rev()):
492 curr = decorate(f.data(), f)
492 curr = decorate(f.data(), f)
493 for p in parents(f):
493 for p in parents(f):
494 curr = pair(hist[p], curr)
494 curr = pair(hist[p], curr)
495 # trim the history of unneeded revs
495 # trim the history of unneeded revs
496 needed[p] -= 1
496 needed[p] -= 1
497 if not needed[p]:
497 if not needed[p]:
498 del hist[p]
498 del hist[p]
499 hist[f] = curr
499 hist[f] = curr
500
500
501 return zip(hist[f][0], hist[f][1].splitlines(True))
501 return zip(hist[f][0], hist[f][1].splitlines(True))
502
502
503 def ancestor(self, fc2, actx=None):
503 def ancestor(self, fc2, actx=None):
504 """
504 """
505 find the common ancestor file context, if any, of self, and fc2
505 find the common ancestor file context, if any, of self, and fc2
506
506
507 If actx is given, it must be the changectx of the common ancestor
507 If actx is given, it must be the changectx of the common ancestor
508 of self's and fc2's respective changesets.
508 of self's and fc2's respective changesets.
509 """
509 """
510
510
511 if actx is None:
511 if actx is None:
512 actx = self.changectx().ancestor(fc2.changectx())
512 actx = self.changectx().ancestor(fc2.changectx())
513
513
514 # the trivial case: changesets are unrelated, files must be too
514 # the trivial case: changesets are unrelated, files must be too
515 if not actx:
515 if not actx:
516 return None
516 return None
517
517
518 # the easy case: no (relevant) renames
518 # the easy case: no (relevant) renames
519 if fc2.path() == self.path() and self.path() in actx:
519 if fc2.path() == self.path() and self.path() in actx:
520 return actx[self.path()]
520 return actx[self.path()]
521 acache = {}
521 acache = {}
522
522
523 # prime the ancestor cache for the working directory
523 # prime the ancestor cache for the working directory
524 for c in (self, fc2):
524 for c in (self, fc2):
525 if c._filerev is None:
525 if c._filerev is None:
526 pl = [(n.path(), n.filenode()) for n in c.parents()]
526 pl = [(n.path(), n.filenode()) for n in c.parents()]
527 acache[(c._path, None)] = pl
527 acache[(c._path, None)] = pl
528
528
529 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
529 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
530 def parents(vertex):
530 def parents(vertex):
531 if vertex in acache:
531 if vertex in acache:
532 return acache[vertex]
532 return acache[vertex]
533 f, n = vertex
533 f, n = vertex
534 if f not in flcache:
534 if f not in flcache:
535 flcache[f] = self._repo.file(f)
535 flcache[f] = self._repo.file(f)
536 fl = flcache[f]
536 fl = flcache[f]
537 pl = [(f, p) for p in fl.parents(n) if p != nullid]
537 pl = [(f, p) for p in fl.parents(n) if p != nullid]
538 re = fl.renamed(n)
538 re = fl.renamed(n)
539 if re:
539 if re:
540 pl.append(re)
540 pl.append(re)
541 acache[vertex] = pl
541 acache[vertex] = pl
542 return pl
542 return pl
543
543
544 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
544 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
545 v = ancestor.ancestor(a, b, parents)
545 v = ancestor.ancestor(a, b, parents)
546 if v:
546 if v:
547 f, n = v
547 f, n = v
548 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
548 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
549
549
550 return None
550 return None
551
551
552 def ancestors(self):
552 def ancestors(self):
553 seen = set(str(self))
553 seen = set()
554 visit = [self]
554 visit = [self]
555 while visit:
555 while visit:
556 for parent in visit.pop(0).parents():
556 parents = visit.pop(0).parents()
557 if len(parents) > 1 and parents[1].rev() > parents[0].rev():
558 # make sure we return ancestors in reverse revision order
559 parents = reversed(parents)
560 for parent in parents:
557 s = str(parent)
561 s = str(parent)
558 if s not in seen:
562 if s not in seen:
559 visit.append(parent)
563 visit.append(parent)
560 seen.add(s)
564 seen.add(s)
561 yield parent
565 yield parent
562
566
563 class workingctx(changectx):
567 class workingctx(changectx):
564 """A workingctx object makes access to data related to
568 """A workingctx object makes access to data related to
565 the current working directory convenient.
569 the current working directory convenient.
566 date - any valid date string or (unixtime, offset), or None.
570 date - any valid date string or (unixtime, offset), or None.
567 user - username string, or None.
571 user - username string, or None.
568 extra - a dictionary of extra values, or None.
572 extra - a dictionary of extra values, or None.
569 changes - a list of file lists as returned by localrepo.status()
573 changes - a list of file lists as returned by localrepo.status()
570 or None to use the repository status.
574 or None to use the repository status.
571 """
575 """
572 def __init__(self, repo, text="", user=None, date=None, extra=None,
576 def __init__(self, repo, text="", user=None, date=None, extra=None,
573 changes=None):
577 changes=None):
574 self._repo = repo
578 self._repo = repo
575 self._rev = None
579 self._rev = None
576 self._node = None
580 self._node = None
577 self._text = text
581 self._text = text
578 if date:
582 if date:
579 self._date = util.parsedate(date)
583 self._date = util.parsedate(date)
580 if user:
584 if user:
581 self._user = user
585 self._user = user
582 if changes:
586 if changes:
583 self._status = list(changes[:4])
587 self._status = list(changes[:4])
584 self._unknown = changes[4]
588 self._unknown = changes[4]
585 self._ignored = changes[5]
589 self._ignored = changes[5]
586 self._clean = changes[6]
590 self._clean = changes[6]
587 else:
591 else:
588 self._unknown = None
592 self._unknown = None
589 self._ignored = None
593 self._ignored = None
590 self._clean = None
594 self._clean = None
591
595
592 self._extra = {}
596 self._extra = {}
593 if extra:
597 if extra:
594 self._extra = extra.copy()
598 self._extra = extra.copy()
595 if 'branch' not in self._extra:
599 if 'branch' not in self._extra:
596 try:
600 try:
597 branch = encoding.fromlocal(self._repo.dirstate.branch())
601 branch = encoding.fromlocal(self._repo.dirstate.branch())
598 except UnicodeDecodeError:
602 except UnicodeDecodeError:
599 raise util.Abort(_('branch name not in UTF-8!'))
603 raise util.Abort(_('branch name not in UTF-8!'))
600 self._extra['branch'] = branch
604 self._extra['branch'] = branch
601 if self._extra['branch'] == '':
605 if self._extra['branch'] == '':
602 self._extra['branch'] = 'default'
606 self._extra['branch'] = 'default'
603
607
604 def __str__(self):
608 def __str__(self):
605 return str(self._parents[0]) + "+"
609 return str(self._parents[0]) + "+"
606
610
607 def __repr__(self):
611 def __repr__(self):
608 return "<workingctx %s>" % str(self)
612 return "<workingctx %s>" % str(self)
609
613
610 def __nonzero__(self):
614 def __nonzero__(self):
611 return True
615 return True
612
616
613 def __contains__(self, key):
617 def __contains__(self, key):
614 return self._repo.dirstate[key] not in "?r"
618 return self._repo.dirstate[key] not in "?r"
615
619
616 @propertycache
620 @propertycache
617 def _manifest(self):
621 def _manifest(self):
618 """generate a manifest corresponding to the working directory"""
622 """generate a manifest corresponding to the working directory"""
619
623
620 if self._unknown is None:
624 if self._unknown is None:
621 self.status(unknown=True)
625 self.status(unknown=True)
622
626
623 man = self._parents[0].manifest().copy()
627 man = self._parents[0].manifest().copy()
624 copied = self._repo.dirstate.copies()
628 copied = self._repo.dirstate.copies()
625 if len(self._parents) > 1:
629 if len(self._parents) > 1:
626 man2 = self.p2().manifest()
630 man2 = self.p2().manifest()
627 def getman(f):
631 def getman(f):
628 if f in man:
632 if f in man:
629 return man
633 return man
630 return man2
634 return man2
631 else:
635 else:
632 getman = lambda f: man
636 getman = lambda f: man
633 def cf(f):
637 def cf(f):
634 f = copied.get(f, f)
638 f = copied.get(f, f)
635 return getman(f).flags(f)
639 return getman(f).flags(f)
636 ff = self._repo.dirstate.flagfunc(cf)
640 ff = self._repo.dirstate.flagfunc(cf)
637 modified, added, removed, deleted = self._status
641 modified, added, removed, deleted = self._status
638 unknown = self._unknown
642 unknown = self._unknown
639 for i, l in (("a", added), ("m", modified), ("u", unknown)):
643 for i, l in (("a", added), ("m", modified), ("u", unknown)):
640 for f in l:
644 for f in l:
641 orig = copied.get(f, f)
645 orig = copied.get(f, f)
642 man[f] = getman(orig).get(orig, nullid) + i
646 man[f] = getman(orig).get(orig, nullid) + i
643 try:
647 try:
644 man.set(f, ff(f))
648 man.set(f, ff(f))
645 except OSError:
649 except OSError:
646 pass
650 pass
647
651
648 for f in deleted + removed:
652 for f in deleted + removed:
649 if f in man:
653 if f in man:
650 del man[f]
654 del man[f]
651
655
652 return man
656 return man
653
657
654 @propertycache
658 @propertycache
655 def _status(self):
659 def _status(self):
656 return self._repo.status()[:4]
660 return self._repo.status()[:4]
657
661
658 @propertycache
662 @propertycache
659 def _user(self):
663 def _user(self):
660 return self._repo.ui.username()
664 return self._repo.ui.username()
661
665
662 @propertycache
666 @propertycache
663 def _date(self):
667 def _date(self):
664 return util.makedate()
668 return util.makedate()
665
669
666 @propertycache
670 @propertycache
667 def _parents(self):
671 def _parents(self):
668 p = self._repo.dirstate.parents()
672 p = self._repo.dirstate.parents()
669 if p[1] == nullid:
673 if p[1] == nullid:
670 p = p[:-1]
674 p = p[:-1]
671 self._parents = [changectx(self._repo, x) for x in p]
675 self._parents = [changectx(self._repo, x) for x in p]
672 return self._parents
676 return self._parents
673
677
674 def status(self, ignored=False, clean=False, unknown=False):
678 def status(self, ignored=False, clean=False, unknown=False):
675 """Explicit status query
679 """Explicit status query
676 Unless this method is used to query the working copy status, the
680 Unless this method is used to query the working copy status, the
677 _status property will implicitly read the status using its default
681 _status property will implicitly read the status using its default
678 arguments."""
682 arguments."""
679 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
683 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
680 self._unknown = self._ignored = self._clean = None
684 self._unknown = self._ignored = self._clean = None
681 if unknown:
685 if unknown:
682 self._unknown = stat[4]
686 self._unknown = stat[4]
683 if ignored:
687 if ignored:
684 self._ignored = stat[5]
688 self._ignored = stat[5]
685 if clean:
689 if clean:
686 self._clean = stat[6]
690 self._clean = stat[6]
687 self._status = stat[:4]
691 self._status = stat[:4]
688 return stat
692 return stat
689
693
690 def manifest(self):
694 def manifest(self):
691 return self._manifest
695 return self._manifest
692 def user(self):
696 def user(self):
693 return self._user or self._repo.ui.username()
697 return self._user or self._repo.ui.username()
694 def date(self):
698 def date(self):
695 return self._date
699 return self._date
696 def description(self):
700 def description(self):
697 return self._text
701 return self._text
698 def files(self):
702 def files(self):
699 return sorted(self._status[0] + self._status[1] + self._status[2])
703 return sorted(self._status[0] + self._status[1] + self._status[2])
700
704
701 def modified(self):
705 def modified(self):
702 return self._status[0]
706 return self._status[0]
703 def added(self):
707 def added(self):
704 return self._status[1]
708 return self._status[1]
705 def removed(self):
709 def removed(self):
706 return self._status[2]
710 return self._status[2]
707 def deleted(self):
711 def deleted(self):
708 return self._status[3]
712 return self._status[3]
709 def unknown(self):
713 def unknown(self):
710 assert self._unknown is not None # must call status first
714 assert self._unknown is not None # must call status first
711 return self._unknown
715 return self._unknown
712 def ignored(self):
716 def ignored(self):
713 assert self._ignored is not None # must call status first
717 assert self._ignored is not None # must call status first
714 return self._ignored
718 return self._ignored
715 def clean(self):
719 def clean(self):
716 assert self._clean is not None # must call status first
720 assert self._clean is not None # must call status first
717 return self._clean
721 return self._clean
718 def branch(self):
722 def branch(self):
719 return encoding.tolocal(self._extra['branch'])
723 return encoding.tolocal(self._extra['branch'])
720 def extra(self):
724 def extra(self):
721 return self._extra
725 return self._extra
722
726
723 def tags(self):
727 def tags(self):
724 t = []
728 t = []
725 for p in self.parents():
729 for p in self.parents():
726 t.extend(p.tags())
730 t.extend(p.tags())
727 return t
731 return t
728
732
729 def children(self):
733 def children(self):
730 return []
734 return []
731
735
732 def flags(self, path):
736 def flags(self, path):
733 if '_manifest' in self.__dict__:
737 if '_manifest' in self.__dict__:
734 try:
738 try:
735 return self._manifest.flags(path)
739 return self._manifest.flags(path)
736 except KeyError:
740 except KeyError:
737 return ''
741 return ''
738
742
739 orig = self._repo.dirstate.copies().get(path, path)
743 orig = self._repo.dirstate.copies().get(path, path)
740
744
741 def findflag(ctx):
745 def findflag(ctx):
742 mnode = ctx.changeset()[0]
746 mnode = ctx.changeset()[0]
743 node, flag = self._repo.manifest.find(mnode, orig)
747 node, flag = self._repo.manifest.find(mnode, orig)
744 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
748 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
745 try:
749 try:
746 return ff(path)
750 return ff(path)
747 except OSError:
751 except OSError:
748 pass
752 pass
749
753
750 flag = findflag(self._parents[0])
754 flag = findflag(self._parents[0])
751 if flag is None and len(self.parents()) > 1:
755 if flag is None and len(self.parents()) > 1:
752 flag = findflag(self._parents[1])
756 flag = findflag(self._parents[1])
753 if flag is None or self._repo.dirstate[path] == 'r':
757 if flag is None or self._repo.dirstate[path] == 'r':
754 return ''
758 return ''
755 return flag
759 return flag
756
760
757 def filectx(self, path, filelog=None):
761 def filectx(self, path, filelog=None):
758 """get a file context from the working directory"""
762 """get a file context from the working directory"""
759 return workingfilectx(self._repo, path, workingctx=self,
763 return workingfilectx(self._repo, path, workingctx=self,
760 filelog=filelog)
764 filelog=filelog)
761
765
762 def ancestor(self, c2):
766 def ancestor(self, c2):
763 """return the ancestor context of self and c2"""
767 """return the ancestor context of self and c2"""
764 return self._parents[0].ancestor(c2) # punt on two parents for now
768 return self._parents[0].ancestor(c2) # punt on two parents for now
765
769
766 def walk(self, match):
770 def walk(self, match):
767 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
771 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
768 True, False))
772 True, False))
769
773
770 def dirty(self, missing=False):
774 def dirty(self, missing=False):
771 "check whether a working directory is modified"
775 "check whether a working directory is modified"
772 # check subrepos first
776 # check subrepos first
773 for s in self.substate:
777 for s in self.substate:
774 if self.sub(s).dirty():
778 if self.sub(s).dirty():
775 return True
779 return True
776 # check current working dir
780 # check current working dir
777 return (self.p2() or self.branch() != self.p1().branch() or
781 return (self.p2() or self.branch() != self.p1().branch() or
778 self.modified() or self.added() or self.removed() or
782 self.modified() or self.added() or self.removed() or
779 (missing and self.deleted()))
783 (missing and self.deleted()))
780
784
781 def add(self, list, prefix=""):
785 def add(self, list, prefix=""):
782 join = lambda f: os.path.join(prefix, f)
786 join = lambda f: os.path.join(prefix, f)
783 wlock = self._repo.wlock()
787 wlock = self._repo.wlock()
784 ui, ds = self._repo.ui, self._repo.dirstate
788 ui, ds = self._repo.ui, self._repo.dirstate
785 try:
789 try:
786 rejected = []
790 rejected = []
787 for f in list:
791 for f in list:
788 p = self._repo.wjoin(f)
792 p = self._repo.wjoin(f)
789 try:
793 try:
790 st = os.lstat(p)
794 st = os.lstat(p)
791 except:
795 except:
792 ui.warn(_("%s does not exist!\n") % join(f))
796 ui.warn(_("%s does not exist!\n") % join(f))
793 rejected.append(f)
797 rejected.append(f)
794 continue
798 continue
795 if st.st_size > 10000000:
799 if st.st_size > 10000000:
796 ui.warn(_("%s: up to %d MB of RAM may be required "
800 ui.warn(_("%s: up to %d MB of RAM may be required "
797 "to manage this file\n"
801 "to manage this file\n"
798 "(use 'hg revert %s' to cancel the "
802 "(use 'hg revert %s' to cancel the "
799 "pending addition)\n")
803 "pending addition)\n")
800 % (f, 3 * st.st_size // 1000000, join(f)))
804 % (f, 3 * st.st_size // 1000000, join(f)))
801 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
805 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
802 ui.warn(_("%s not added: only files and symlinks "
806 ui.warn(_("%s not added: only files and symlinks "
803 "supported currently\n") % join(f))
807 "supported currently\n") % join(f))
804 rejected.append(p)
808 rejected.append(p)
805 elif ds[f] in 'amn':
809 elif ds[f] in 'amn':
806 ui.warn(_("%s already tracked!\n") % join(f))
810 ui.warn(_("%s already tracked!\n") % join(f))
807 elif ds[f] == 'r':
811 elif ds[f] == 'r':
808 ds.normallookup(f)
812 ds.normallookup(f)
809 else:
813 else:
810 ds.add(f)
814 ds.add(f)
811 return rejected
815 return rejected
812 finally:
816 finally:
813 wlock.release()
817 wlock.release()
814
818
815 def forget(self, list):
819 def forget(self, list):
816 wlock = self._repo.wlock()
820 wlock = self._repo.wlock()
817 try:
821 try:
818 for f in list:
822 for f in list:
819 if self._repo.dirstate[f] != 'a':
823 if self._repo.dirstate[f] != 'a':
820 self._repo.ui.warn(_("%s not added!\n") % f)
824 self._repo.ui.warn(_("%s not added!\n") % f)
821 else:
825 else:
822 self._repo.dirstate.forget(f)
826 self._repo.dirstate.forget(f)
823 finally:
827 finally:
824 wlock.release()
828 wlock.release()
825
829
826 def ancestors(self):
830 def ancestors(self):
827 for a in self._repo.changelog.ancestors(
831 for a in self._repo.changelog.ancestors(
828 *[p.rev() for p in self._parents]):
832 *[p.rev() for p in self._parents]):
829 yield changectx(self._repo, a)
833 yield changectx(self._repo, a)
830
834
831 def remove(self, list, unlink=False):
835 def remove(self, list, unlink=False):
832 if unlink:
836 if unlink:
833 for f in list:
837 for f in list:
834 try:
838 try:
835 util.unlinkpath(self._repo.wjoin(f))
839 util.unlinkpath(self._repo.wjoin(f))
836 except OSError, inst:
840 except OSError, inst:
837 if inst.errno != errno.ENOENT:
841 if inst.errno != errno.ENOENT:
838 raise
842 raise
839 wlock = self._repo.wlock()
843 wlock = self._repo.wlock()
840 try:
844 try:
841 for f in list:
845 for f in list:
842 if unlink and os.path.lexists(self._repo.wjoin(f)):
846 if unlink and os.path.lexists(self._repo.wjoin(f)):
843 self._repo.ui.warn(_("%s still exists!\n") % f)
847 self._repo.ui.warn(_("%s still exists!\n") % f)
844 elif self._repo.dirstate[f] == 'a':
848 elif self._repo.dirstate[f] == 'a':
845 self._repo.dirstate.forget(f)
849 self._repo.dirstate.forget(f)
846 elif f not in self._repo.dirstate:
850 elif f not in self._repo.dirstate:
847 self._repo.ui.warn(_("%s not tracked!\n") % f)
851 self._repo.ui.warn(_("%s not tracked!\n") % f)
848 else:
852 else:
849 self._repo.dirstate.remove(f)
853 self._repo.dirstate.remove(f)
850 finally:
854 finally:
851 wlock.release()
855 wlock.release()
852
856
853 def undelete(self, list):
857 def undelete(self, list):
854 pctxs = self.parents()
858 pctxs = self.parents()
855 wlock = self._repo.wlock()
859 wlock = self._repo.wlock()
856 try:
860 try:
857 for f in list:
861 for f in list:
858 if self._repo.dirstate[f] != 'r':
862 if self._repo.dirstate[f] != 'r':
859 self._repo.ui.warn(_("%s not removed!\n") % f)
863 self._repo.ui.warn(_("%s not removed!\n") % f)
860 else:
864 else:
861 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
865 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
862 t = fctx.data()
866 t = fctx.data()
863 self._repo.wwrite(f, t, fctx.flags())
867 self._repo.wwrite(f, t, fctx.flags())
864 self._repo.dirstate.normal(f)
868 self._repo.dirstate.normal(f)
865 finally:
869 finally:
866 wlock.release()
870 wlock.release()
867
871
868 def copy(self, source, dest):
872 def copy(self, source, dest):
869 p = self._repo.wjoin(dest)
873 p = self._repo.wjoin(dest)
870 if not os.path.lexists(p):
874 if not os.path.lexists(p):
871 self._repo.ui.warn(_("%s does not exist!\n") % dest)
875 self._repo.ui.warn(_("%s does not exist!\n") % dest)
872 elif not (os.path.isfile(p) or os.path.islink(p)):
876 elif not (os.path.isfile(p) or os.path.islink(p)):
873 self._repo.ui.warn(_("copy failed: %s is not a file or a "
877 self._repo.ui.warn(_("copy failed: %s is not a file or a "
874 "symbolic link\n") % dest)
878 "symbolic link\n") % dest)
875 else:
879 else:
876 wlock = self._repo.wlock()
880 wlock = self._repo.wlock()
877 try:
881 try:
878 if self._repo.dirstate[dest] in '?r':
882 if self._repo.dirstate[dest] in '?r':
879 self._repo.dirstate.add(dest)
883 self._repo.dirstate.add(dest)
880 self._repo.dirstate.copy(source, dest)
884 self._repo.dirstate.copy(source, dest)
881 finally:
885 finally:
882 wlock.release()
886 wlock.release()
883
887
884 class workingfilectx(filectx):
888 class workingfilectx(filectx):
885 """A workingfilectx object makes access to data related to a particular
889 """A workingfilectx object makes access to data related to a particular
886 file in the working directory convenient."""
890 file in the working directory convenient."""
887 def __init__(self, repo, path, filelog=None, workingctx=None):
891 def __init__(self, repo, path, filelog=None, workingctx=None):
888 """changeid can be a changeset revision, node, or tag.
892 """changeid can be a changeset revision, node, or tag.
889 fileid can be a file revision or node."""
893 fileid can be a file revision or node."""
890 self._repo = repo
894 self._repo = repo
891 self._path = path
895 self._path = path
892 self._changeid = None
896 self._changeid = None
893 self._filerev = self._filenode = None
897 self._filerev = self._filenode = None
894
898
895 if filelog:
899 if filelog:
896 self._filelog = filelog
900 self._filelog = filelog
897 if workingctx:
901 if workingctx:
898 self._changectx = workingctx
902 self._changectx = workingctx
899
903
900 @propertycache
904 @propertycache
901 def _changectx(self):
905 def _changectx(self):
902 return workingctx(self._repo)
906 return workingctx(self._repo)
903
907
904 def __nonzero__(self):
908 def __nonzero__(self):
905 return True
909 return True
906
910
907 def __str__(self):
911 def __str__(self):
908 return "%s@%s" % (self.path(), self._changectx)
912 return "%s@%s" % (self.path(), self._changectx)
909
913
910 def __repr__(self):
914 def __repr__(self):
911 return "<workingfilectx %s>" % str(self)
915 return "<workingfilectx %s>" % str(self)
912
916
913 def data(self):
917 def data(self):
914 return self._repo.wread(self._path)
918 return self._repo.wread(self._path)
915 def renamed(self):
919 def renamed(self):
916 rp = self._repo.dirstate.copied(self._path)
920 rp = self._repo.dirstate.copied(self._path)
917 if not rp:
921 if not rp:
918 return None
922 return None
919 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
923 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
920
924
921 def parents(self):
925 def parents(self):
922 '''return parent filectxs, following copies if necessary'''
926 '''return parent filectxs, following copies if necessary'''
923 def filenode(ctx, path):
927 def filenode(ctx, path):
924 return ctx._manifest.get(path, nullid)
928 return ctx._manifest.get(path, nullid)
925
929
926 path = self._path
930 path = self._path
927 fl = self._filelog
931 fl = self._filelog
928 pcl = self._changectx._parents
932 pcl = self._changectx._parents
929 renamed = self.renamed()
933 renamed = self.renamed()
930
934
931 if renamed:
935 if renamed:
932 pl = [renamed + (None,)]
936 pl = [renamed + (None,)]
933 else:
937 else:
934 pl = [(path, filenode(pcl[0], path), fl)]
938 pl = [(path, filenode(pcl[0], path), fl)]
935
939
936 for pc in pcl[1:]:
940 for pc in pcl[1:]:
937 pl.append((path, filenode(pc, path), fl))
941 pl.append((path, filenode(pc, path), fl))
938
942
939 return [filectx(self._repo, p, fileid=n, filelog=l)
943 return [filectx(self._repo, p, fileid=n, filelog=l)
940 for p, n, l in pl if n != nullid]
944 for p, n, l in pl if n != nullid]
941
945
942 def children(self):
946 def children(self):
943 return []
947 return []
944
948
945 def size(self):
949 def size(self):
946 return os.lstat(self._repo.wjoin(self._path)).st_size
950 return os.lstat(self._repo.wjoin(self._path)).st_size
947 def date(self):
951 def date(self):
948 t, tz = self._changectx.date()
952 t, tz = self._changectx.date()
949 try:
953 try:
950 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
954 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
951 except OSError, err:
955 except OSError, err:
952 if err.errno != errno.ENOENT:
956 if err.errno != errno.ENOENT:
953 raise
957 raise
954 return (t, tz)
958 return (t, tz)
955
959
956 def cmp(self, fctx):
960 def cmp(self, fctx):
957 """compare with other file context
961 """compare with other file context
958
962
959 returns True if different than fctx.
963 returns True if different than fctx.
960 """
964 """
961 # fctx should be a filectx (not a wfctx)
965 # fctx should be a filectx (not a wfctx)
962 # invert comparison to reuse the same code path
966 # invert comparison to reuse the same code path
963 return fctx.cmp(self)
967 return fctx.cmp(self)
964
968
965 class memctx(object):
969 class memctx(object):
966 """Use memctx to perform in-memory commits via localrepo.commitctx().
970 """Use memctx to perform in-memory commits via localrepo.commitctx().
967
971
968 Revision information is supplied at initialization time while
972 Revision information is supplied at initialization time while
969 related files data and is made available through a callback
973 related files data and is made available through a callback
970 mechanism. 'repo' is the current localrepo, 'parents' is a
974 mechanism. 'repo' is the current localrepo, 'parents' is a
971 sequence of two parent revisions identifiers (pass None for every
975 sequence of two parent revisions identifiers (pass None for every
972 missing parent), 'text' is the commit message and 'files' lists
976 missing parent), 'text' is the commit message and 'files' lists
973 names of files touched by the revision (normalized and relative to
977 names of files touched by the revision (normalized and relative to
974 repository root).
978 repository root).
975
979
976 filectxfn(repo, memctx, path) is a callable receiving the
980 filectxfn(repo, memctx, path) is a callable receiving the
977 repository, the current memctx object and the normalized path of
981 repository, the current memctx object and the normalized path of
978 requested file, relative to repository root. It is fired by the
982 requested file, relative to repository root. It is fired by the
979 commit function for every file in 'files', but calls order is
983 commit function for every file in 'files', but calls order is
980 undefined. If the file is available in the revision being
984 undefined. If the file is available in the revision being
981 committed (updated or added), filectxfn returns a memfilectx
985 committed (updated or added), filectxfn returns a memfilectx
982 object. If the file was removed, filectxfn raises an
986 object. If the file was removed, filectxfn raises an
983 IOError. Moved files are represented by marking the source file
987 IOError. Moved files are represented by marking the source file
984 removed and the new file added with copy information (see
988 removed and the new file added with copy information (see
985 memfilectx).
989 memfilectx).
986
990
987 user receives the committer name and defaults to current
991 user receives the committer name and defaults to current
988 repository username, date is the commit date in any format
992 repository username, date is the commit date in any format
989 supported by util.parsedate() and defaults to current date, extra
993 supported by util.parsedate() and defaults to current date, extra
990 is a dictionary of metadata or is left empty.
994 is a dictionary of metadata or is left empty.
991 """
995 """
992 def __init__(self, repo, parents, text, files, filectxfn, user=None,
996 def __init__(self, repo, parents, text, files, filectxfn, user=None,
993 date=None, extra=None):
997 date=None, extra=None):
994 self._repo = repo
998 self._repo = repo
995 self._rev = None
999 self._rev = None
996 self._node = None
1000 self._node = None
997 self._text = text
1001 self._text = text
998 self._date = date and util.parsedate(date) or util.makedate()
1002 self._date = date and util.parsedate(date) or util.makedate()
999 self._user = user
1003 self._user = user
1000 parents = [(p or nullid) for p in parents]
1004 parents = [(p or nullid) for p in parents]
1001 p1, p2 = parents
1005 p1, p2 = parents
1002 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1006 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1003 files = sorted(set(files))
1007 files = sorted(set(files))
1004 self._status = [files, [], [], [], []]
1008 self._status = [files, [], [], [], []]
1005 self._filectxfn = filectxfn
1009 self._filectxfn = filectxfn
1006
1010
1007 self._extra = extra and extra.copy() or {}
1011 self._extra = extra and extra.copy() or {}
1008 if 'branch' not in self._extra:
1012 if 'branch' not in self._extra:
1009 self._extra['branch'] = 'default'
1013 self._extra['branch'] = 'default'
1010 elif self._extra.get('branch') == '':
1014 elif self._extra.get('branch') == '':
1011 self._extra['branch'] = 'default'
1015 self._extra['branch'] = 'default'
1012
1016
1013 def __str__(self):
1017 def __str__(self):
1014 return str(self._parents[0]) + "+"
1018 return str(self._parents[0]) + "+"
1015
1019
1016 def __int__(self):
1020 def __int__(self):
1017 return self._rev
1021 return self._rev
1018
1022
1019 def __nonzero__(self):
1023 def __nonzero__(self):
1020 return True
1024 return True
1021
1025
1022 def __getitem__(self, key):
1026 def __getitem__(self, key):
1023 return self.filectx(key)
1027 return self.filectx(key)
1024
1028
1025 def p1(self):
1029 def p1(self):
1026 return self._parents[0]
1030 return self._parents[0]
1027 def p2(self):
1031 def p2(self):
1028 return self._parents[1]
1032 return self._parents[1]
1029
1033
1030 def user(self):
1034 def user(self):
1031 return self._user or self._repo.ui.username()
1035 return self._user or self._repo.ui.username()
1032 def date(self):
1036 def date(self):
1033 return self._date
1037 return self._date
1034 def description(self):
1038 def description(self):
1035 return self._text
1039 return self._text
1036 def files(self):
1040 def files(self):
1037 return self.modified()
1041 return self.modified()
1038 def modified(self):
1042 def modified(self):
1039 return self._status[0]
1043 return self._status[0]
1040 def added(self):
1044 def added(self):
1041 return self._status[1]
1045 return self._status[1]
1042 def removed(self):
1046 def removed(self):
1043 return self._status[2]
1047 return self._status[2]
1044 def deleted(self):
1048 def deleted(self):
1045 return self._status[3]
1049 return self._status[3]
1046 def unknown(self):
1050 def unknown(self):
1047 return self._status[4]
1051 return self._status[4]
1048 def ignored(self):
1052 def ignored(self):
1049 return self._status[5]
1053 return self._status[5]
1050 def clean(self):
1054 def clean(self):
1051 return self._status[6]
1055 return self._status[6]
1052 def branch(self):
1056 def branch(self):
1053 return encoding.tolocal(self._extra['branch'])
1057 return encoding.tolocal(self._extra['branch'])
1054 def extra(self):
1058 def extra(self):
1055 return self._extra
1059 return self._extra
1056 def flags(self, f):
1060 def flags(self, f):
1057 return self[f].flags()
1061 return self[f].flags()
1058
1062
1059 def parents(self):
1063 def parents(self):
1060 """return contexts for each parent changeset"""
1064 """return contexts for each parent changeset"""
1061 return self._parents
1065 return self._parents
1062
1066
1063 def filectx(self, path, filelog=None):
1067 def filectx(self, path, filelog=None):
1064 """get a file context from the working directory"""
1068 """get a file context from the working directory"""
1065 return self._filectxfn(self._repo, self, path)
1069 return self._filectxfn(self._repo, self, path)
1066
1070
1067 def commit(self):
1071 def commit(self):
1068 """commit context to the repo"""
1072 """commit context to the repo"""
1069 return self._repo.commitctx(self)
1073 return self._repo.commitctx(self)
1070
1074
1071 class memfilectx(object):
1075 class memfilectx(object):
1072 """memfilectx represents an in-memory file to commit.
1076 """memfilectx represents an in-memory file to commit.
1073
1077
1074 See memctx for more details.
1078 See memctx for more details.
1075 """
1079 """
1076 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1080 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1077 """
1081 """
1078 path is the normalized file path relative to repository root.
1082 path is the normalized file path relative to repository root.
1079 data is the file content as a string.
1083 data is the file content as a string.
1080 islink is True if the file is a symbolic link.
1084 islink is True if the file is a symbolic link.
1081 isexec is True if the file is executable.
1085 isexec is True if the file is executable.
1082 copied is the source file path if current file was copied in the
1086 copied is the source file path if current file was copied in the
1083 revision being committed, or None."""
1087 revision being committed, or None."""
1084 self._path = path
1088 self._path = path
1085 self._data = data
1089 self._data = data
1086 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1090 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1087 self._copied = None
1091 self._copied = None
1088 if copied:
1092 if copied:
1089 self._copied = (copied, nullid)
1093 self._copied = (copied, nullid)
1090
1094
1091 def __nonzero__(self):
1095 def __nonzero__(self):
1092 return True
1096 return True
1093 def __str__(self):
1097 def __str__(self):
1094 return "%s@%s" % (self.path(), self._changectx)
1098 return "%s@%s" % (self.path(), self._changectx)
1095 def path(self):
1099 def path(self):
1096 return self._path
1100 return self._path
1097 def data(self):
1101 def data(self):
1098 return self._data
1102 return self._data
1099 def flags(self):
1103 def flags(self):
1100 return self._flags
1104 return self._flags
1101 def isexec(self):
1105 def isexec(self):
1102 return 'x' in self._flags
1106 return 'x' in self._flags
1103 def islink(self):
1107 def islink(self):
1104 return 'l' in self._flags
1108 return 'l' in self._flags
1105 def renamed(self):
1109 def renamed(self):
1106 return self._copied
1110 return self._copied
@@ -1,101 +1,127 b''
1 $ hg init
1 $ hg init
2
2
3 $ echo "[merge]" >> .hg/hgrc
3 $ echo "[merge]" >> .hg/hgrc
4 $ echo "followcopies = 1" >> .hg/hgrc
4 $ echo "followcopies = 1" >> .hg/hgrc
5
5
6 $ echo foo > a
6 $ echo foo > a
7 $ echo foo > a2
7 $ echo foo > a2
8 $ hg add a a2
8 $ hg add a a2
9 $ hg ci -m "start"
9 $ hg ci -m "start"
10
10
11 $ hg mv a b
11 $ hg mv a b
12 $ hg mv a2 b2
12 $ hg mv a2 b2
13 $ hg ci -m "rename"
13 $ hg ci -m "rename"
14
14
15 $ hg co 0
15 $ hg co 0
16 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
16 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
17
17
18 $ echo blahblah > a
18 $ echo blahblah > a
19 $ echo blahblah > a2
19 $ echo blahblah > a2
20 $ hg mv a2 c2
20 $ hg mv a2 c2
21 $ hg ci -m "modify"
21 $ hg ci -m "modify"
22 created new head
22 created new head
23
23
24 $ hg merge -y --debug
24 $ hg merge -y --debug
25 searching for copies back to rev 1
25 searching for copies back to rev 1
26 unmatched files in local:
26 unmatched files in local:
27 c2
27 c2
28 unmatched files in other:
28 unmatched files in other:
29 b
29 b
30 b2
30 b2
31 all copies found (* = to merge, ! = divergent):
31 all copies found (* = to merge, ! = divergent):
32 c2 -> a2 !
32 c2 -> a2 !
33 b -> a *
33 b -> a *
34 b2 -> a2 !
34 b2 -> a2 !
35 checking for directory renames
35 checking for directory renames
36 a2: divergent renames -> dr
36 a2: divergent renames -> dr
37 resolving manifests
37 resolving manifests
38 overwrite None partial False
38 overwrite None partial False
39 ancestor af1939970a1c local 044f8520aeeb+ remote 85c198ef2f6c
39 ancestor af1939970a1c local 044f8520aeeb+ remote 85c198ef2f6c
40 a: remote moved to b -> m
40 a: remote moved to b -> m
41 b2: remote created -> g
41 b2: remote created -> g
42 preserving a for resolve of b
42 preserving a for resolve of b
43 removing a
43 removing a
44 updating: a 1/3 files (33.33%)
44 updating: a 1/3 files (33.33%)
45 picked tool 'internal:merge' for b (binary False symlink False)
45 picked tool 'internal:merge' for b (binary False symlink False)
46 merging a and b to b
46 merging a and b to b
47 my b@044f8520aeeb+ other b@85c198ef2f6c ancestor a@af1939970a1c
47 my b@044f8520aeeb+ other b@85c198ef2f6c ancestor a@af1939970a1c
48 premerge successful
48 premerge successful
49 updating: a2 2/3 files (66.67%)
49 updating: a2 2/3 files (66.67%)
50 note: possible conflict - a2 was renamed multiple times to:
50 note: possible conflict - a2 was renamed multiple times to:
51 c2
51 c2
52 b2
52 b2
53 updating: b2 3/3 files (100.00%)
53 updating: b2 3/3 files (100.00%)
54 getting b2
54 getting b2
55 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
55 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
56 (branch merge, don't forget to commit)
56 (branch merge, don't forget to commit)
57
57
58 $ hg status -AC
58 $ hg status -AC
59 M b
59 M b
60 a
60 a
61 M b2
61 M b2
62 R a
62 R a
63 C c2
63 C c2
64
64
65 $ cat b
65 $ cat b
66 blahblah
66 blahblah
67
67
68 $ hg ci -m "merge"
68 $ hg ci -m "merge"
69
69
70 $ hg debugindex .hg/store/data/b.i
70 $ hg debugindex .hg/store/data/b.i
71 rev offset length base linkrev nodeid p1 p2
71 rev offset length base linkrev nodeid p1 p2
72 0 0 67 0 1 57eacc201a7f 000000000000 000000000000
72 0 0 67 0 1 57eacc201a7f 000000000000 000000000000
73 1 67 72 1 3 4727ba907962 000000000000 57eacc201a7f
73 1 67 72 1 3 4727ba907962 000000000000 57eacc201a7f
74
74
75 $ hg debugrename b
75 $ hg debugrename b
76 b renamed from a:dd03b83622e78778b403775d0d074b9ac7387a66
76 b renamed from a:dd03b83622e78778b403775d0d074b9ac7387a66
77
77
78 This used to trigger a "divergent renames" warning, despite no renames
78 This used to trigger a "divergent renames" warning, despite no renames
79
79
80 $ hg cp b b3
80 $ hg cp b b3
81 $ hg cp b b4
81 $ hg cp b b4
82 $ hg ci -A -m 'copy b twice'
82 $ hg ci -A -m 'copy b twice'
83 $ hg up eb92d88a9712
83 $ hg up eb92d88a9712
84 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
84 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
85 $ hg up
85 $ hg up
86 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
86 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
87 $ hg rm b3 b4
87 $ hg rm b3 b4
88 $ hg ci -m 'clean up a bit of our mess'
88 $ hg ci -m 'clean up a bit of our mess'
89
89
90 We'd rather not warn on divergent renames done in the same changeset (issue2113)
90 We'd rather not warn on divergent renames done in the same changeset (issue2113)
91
91
92 $ hg cp b b3
92 $ hg cp b b3
93 $ hg mv b b4
93 $ hg mv b b4
94 $ hg ci -A -m 'divergent renames in same changeset'
94 $ hg ci -A -m 'divergent renames in same changeset'
95 $ hg up c761c6948de0
95 $ hg up c761c6948de0
96 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
96 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
97 $ hg up
97 $ hg up
98 note: possible conflict - b was renamed multiple times to:
98 note: possible conflict - b was renamed multiple times to:
99 b3
99 b3
100 b4
100 b4
101 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
101 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
102
103 Check for issue2642
104
105 $ hg init t
106 $ cd t
107
108 $ echo c0 > f1
109 $ hg ci -Aqm0
110
111 $ hg up null -q
112 $ echo c1 > f1 # backport
113 $ hg ci -Aqm1
114 $ hg mv f1 f2
115 $ hg ci -qm2
116
117 $ hg up 0 -q
118 $ hg merge 1 -q --tool internal:local
119 $ hg ci -qm3
120
121 $ hg merge 2
122 merging f1 and f2 to f2
123 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
124 (branch merge, don't forget to commit)
125
126 $ cat f2
127 c0
General Comments 0
You need to be logged in to leave comments. Login now