##// END OF EJS Templates
extdiff: correctly handle deleted subrepositories (issue3153)...
Andrew Zwicky -
r27183:0945539a default
parent child Browse files
Show More
@@ -1,1947 +1,1949 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 import re
8 import re
9
9
10 from node import nullid, nullrev, wdirid, short, hex, bin
10 from node import nullid, nullrev, wdirid, short, hex, bin
11 from i18n import _
11 from i18n import _
12 import mdiff, error, util, scmutil, subrepo, patch, encoding, phases
12 import mdiff, error, util, scmutil, subrepo, patch, encoding, phases
13 import match as matchmod
13 import match as matchmod
14 import os, errno, stat
14 import os, errno, stat
15 import obsolete as obsmod
15 import obsolete as obsmod
16 import repoview
16 import repoview
17 import fileset
17 import fileset
18 import revlog
18 import revlog
19
19
20 propertycache = util.propertycache
20 propertycache = util.propertycache
21
21
22 # Phony node value to stand-in for new files in some uses of
22 # Phony node value to stand-in for new files in some uses of
23 # manifests. Manifests support 21-byte hashes for nodes which are
23 # manifests. Manifests support 21-byte hashes for nodes which are
24 # dirty in the working copy.
24 # dirty in the working copy.
25 _newnode = '!' * 21
25 _newnode = '!' * 21
26
26
27 nonascii = re.compile(r'[^\x21-\x7f]').search
27 nonascii = re.compile(r'[^\x21-\x7f]').search
28
28
29 class basectx(object):
29 class basectx(object):
30 """A basectx object represents the common logic for its children:
30 """A basectx object represents the common logic for its children:
31 changectx: read-only context that is already present in the repo,
31 changectx: read-only context that is already present in the repo,
32 workingctx: a context that represents the working directory and can
32 workingctx: a context that represents the working directory and can
33 be committed,
33 be committed,
34 memctx: a context that represents changes in-memory and can also
34 memctx: a context that represents changes in-memory and can also
35 be committed."""
35 be committed."""
36 def __new__(cls, repo, changeid='', *args, **kwargs):
36 def __new__(cls, repo, changeid='', *args, **kwargs):
37 if isinstance(changeid, basectx):
37 if isinstance(changeid, basectx):
38 return changeid
38 return changeid
39
39
40 o = super(basectx, cls).__new__(cls)
40 o = super(basectx, cls).__new__(cls)
41
41
42 o._repo = repo
42 o._repo = repo
43 o._rev = nullrev
43 o._rev = nullrev
44 o._node = nullid
44 o._node = nullid
45
45
46 return o
46 return o
47
47
48 def __str__(self):
48 def __str__(self):
49 return short(self.node())
49 return short(self.node())
50
50
51 def __int__(self):
51 def __int__(self):
52 return self.rev()
52 return self.rev()
53
53
54 def __repr__(self):
54 def __repr__(self):
55 return "<%s %s>" % (type(self).__name__, str(self))
55 return "<%s %s>" % (type(self).__name__, str(self))
56
56
57 def __eq__(self, other):
57 def __eq__(self, other):
58 try:
58 try:
59 return type(self) == type(other) and self._rev == other._rev
59 return type(self) == type(other) and self._rev == other._rev
60 except AttributeError:
60 except AttributeError:
61 return False
61 return False
62
62
63 def __ne__(self, other):
63 def __ne__(self, other):
64 return not (self == other)
64 return not (self == other)
65
65
66 def __contains__(self, key):
66 def __contains__(self, key):
67 return key in self._manifest
67 return key in self._manifest
68
68
69 def __getitem__(self, key):
69 def __getitem__(self, key):
70 return self.filectx(key)
70 return self.filectx(key)
71
71
72 def __iter__(self):
72 def __iter__(self):
73 return iter(self._manifest)
73 return iter(self._manifest)
74
74
75 def _manifestmatches(self, match, s):
75 def _manifestmatches(self, match, s):
76 """generate a new manifest filtered by the match argument
76 """generate a new manifest filtered by the match argument
77
77
78 This method is for internal use only and mainly exists to provide an
78 This method is for internal use only and mainly exists to provide an
79 object oriented way for other contexts to customize the manifest
79 object oriented way for other contexts to customize the manifest
80 generation.
80 generation.
81 """
81 """
82 return self.manifest().matches(match)
82 return self.manifest().matches(match)
83
83
84 def _matchstatus(self, other, match):
84 def _matchstatus(self, other, match):
85 """return match.always if match is none
85 """return match.always if match is none
86
86
87 This internal method provides a way for child objects to override the
87 This internal method provides a way for child objects to override the
88 match operator.
88 match operator.
89 """
89 """
90 return match or matchmod.always(self._repo.root, self._repo.getcwd())
90 return match or matchmod.always(self._repo.root, self._repo.getcwd())
91
91
92 def _buildstatus(self, other, s, match, listignored, listclean,
92 def _buildstatus(self, other, s, match, listignored, listclean,
93 listunknown):
93 listunknown):
94 """build a status with respect to another context"""
94 """build a status with respect to another context"""
95 # Load earliest manifest first for caching reasons. More specifically,
95 # Load earliest manifest first for caching reasons. More specifically,
96 # if you have revisions 1000 and 1001, 1001 is probably stored as a
96 # if you have revisions 1000 and 1001, 1001 is probably stored as a
97 # delta against 1000. Thus, if you read 1000 first, we'll reconstruct
97 # delta against 1000. Thus, if you read 1000 first, we'll reconstruct
98 # 1000 and cache it so that when you read 1001, we just need to apply a
98 # 1000 and cache it so that when you read 1001, we just need to apply a
99 # delta to what's in the cache. So that's one full reconstruction + one
99 # delta to what's in the cache. So that's one full reconstruction + one
100 # delta application.
100 # delta application.
101 if self.rev() is not None and self.rev() < other.rev():
101 if self.rev() is not None and self.rev() < other.rev():
102 self.manifest()
102 self.manifest()
103 mf1 = other._manifestmatches(match, s)
103 mf1 = other._manifestmatches(match, s)
104 mf2 = self._manifestmatches(match, s)
104 mf2 = self._manifestmatches(match, s)
105
105
106 modified, added = [], []
106 modified, added = [], []
107 removed = []
107 removed = []
108 clean = []
108 clean = []
109 deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
109 deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
110 deletedset = set(deleted)
110 deletedset = set(deleted)
111 d = mf1.diff(mf2, clean=listclean)
111 d = mf1.diff(mf2, clean=listclean)
112 for fn, value in d.iteritems():
112 for fn, value in d.iteritems():
113 if fn in deletedset:
113 if fn in deletedset:
114 continue
114 continue
115 if value is None:
115 if value is None:
116 clean.append(fn)
116 clean.append(fn)
117 continue
117 continue
118 (node1, flag1), (node2, flag2) = value
118 (node1, flag1), (node2, flag2) = value
119 if node1 is None:
119 if node1 is None:
120 added.append(fn)
120 added.append(fn)
121 elif node2 is None:
121 elif node2 is None:
122 removed.append(fn)
122 removed.append(fn)
123 elif node2 != _newnode:
123 elif node2 != _newnode:
124 # The file was not a new file in mf2, so an entry
124 # The file was not a new file in mf2, so an entry
125 # from diff is really a difference.
125 # from diff is really a difference.
126 modified.append(fn)
126 modified.append(fn)
127 elif self[fn].cmp(other[fn]):
127 elif self[fn].cmp(other[fn]):
128 # node2 was newnode, but the working file doesn't
128 # node2 was newnode, but the working file doesn't
129 # match the one in mf1.
129 # match the one in mf1.
130 modified.append(fn)
130 modified.append(fn)
131 else:
131 else:
132 clean.append(fn)
132 clean.append(fn)
133
133
134 if removed:
134 if removed:
135 # need to filter files if they are already reported as removed
135 # need to filter files if they are already reported as removed
136 unknown = [fn for fn in unknown if fn not in mf1]
136 unknown = [fn for fn in unknown if fn not in mf1]
137 ignored = [fn for fn in ignored if fn not in mf1]
137 ignored = [fn for fn in ignored if fn not in mf1]
138 # if they're deleted, don't report them as removed
138 # if they're deleted, don't report them as removed
139 removed = [fn for fn in removed if fn not in deletedset]
139 removed = [fn for fn in removed if fn not in deletedset]
140
140
141 return scmutil.status(modified, added, removed, deleted, unknown,
141 return scmutil.status(modified, added, removed, deleted, unknown,
142 ignored, clean)
142 ignored, clean)
143
143
144 @propertycache
144 @propertycache
145 def substate(self):
145 def substate(self):
146 return subrepo.state(self, self._repo.ui)
146 return subrepo.state(self, self._repo.ui)
147
147
148 def subrev(self, subpath):
148 def subrev(self, subpath):
149 return self.substate[subpath][1]
149 return self.substate[subpath][1]
150
150
151 def rev(self):
151 def rev(self):
152 return self._rev
152 return self._rev
153 def node(self):
153 def node(self):
154 return self._node
154 return self._node
155 def hex(self):
155 def hex(self):
156 return hex(self.node())
156 return hex(self.node())
157 def manifest(self):
157 def manifest(self):
158 return self._manifest
158 return self._manifest
159 def repo(self):
159 def repo(self):
160 return self._repo
160 return self._repo
161 def phasestr(self):
161 def phasestr(self):
162 return phases.phasenames[self.phase()]
162 return phases.phasenames[self.phase()]
163 def mutable(self):
163 def mutable(self):
164 return self.phase() > phases.public
164 return self.phase() > phases.public
165
165
166 def getfileset(self, expr):
166 def getfileset(self, expr):
167 return fileset.getfileset(self, expr)
167 return fileset.getfileset(self, expr)
168
168
169 def obsolete(self):
169 def obsolete(self):
170 """True if the changeset is obsolete"""
170 """True if the changeset is obsolete"""
171 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
171 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
172
172
173 def extinct(self):
173 def extinct(self):
174 """True if the changeset is extinct"""
174 """True if the changeset is extinct"""
175 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
175 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
176
176
177 def unstable(self):
177 def unstable(self):
178 """True if the changeset is not obsolete but it's ancestor are"""
178 """True if the changeset is not obsolete but it's ancestor are"""
179 return self.rev() in obsmod.getrevs(self._repo, 'unstable')
179 return self.rev() in obsmod.getrevs(self._repo, 'unstable')
180
180
181 def bumped(self):
181 def bumped(self):
182 """True if the changeset try to be a successor of a public changeset
182 """True if the changeset try to be a successor of a public changeset
183
183
184 Only non-public and non-obsolete changesets may be bumped.
184 Only non-public and non-obsolete changesets may be bumped.
185 """
185 """
186 return self.rev() in obsmod.getrevs(self._repo, 'bumped')
186 return self.rev() in obsmod.getrevs(self._repo, 'bumped')
187
187
188 def divergent(self):
188 def divergent(self):
189 """Is a successors of a changeset with multiple possible successors set
189 """Is a successors of a changeset with multiple possible successors set
190
190
191 Only non-public and non-obsolete changesets may be divergent.
191 Only non-public and non-obsolete changesets may be divergent.
192 """
192 """
193 return self.rev() in obsmod.getrevs(self._repo, 'divergent')
193 return self.rev() in obsmod.getrevs(self._repo, 'divergent')
194
194
195 def troubled(self):
195 def troubled(self):
196 """True if the changeset is either unstable, bumped or divergent"""
196 """True if the changeset is either unstable, bumped or divergent"""
197 return self.unstable() or self.bumped() or self.divergent()
197 return self.unstable() or self.bumped() or self.divergent()
198
198
199 def troubles(self):
199 def troubles(self):
200 """return the list of troubles affecting this changesets.
200 """return the list of troubles affecting this changesets.
201
201
202 Troubles are returned as strings. possible values are:
202 Troubles are returned as strings. possible values are:
203 - unstable,
203 - unstable,
204 - bumped,
204 - bumped,
205 - divergent.
205 - divergent.
206 """
206 """
207 troubles = []
207 troubles = []
208 if self.unstable():
208 if self.unstable():
209 troubles.append('unstable')
209 troubles.append('unstable')
210 if self.bumped():
210 if self.bumped():
211 troubles.append('bumped')
211 troubles.append('bumped')
212 if self.divergent():
212 if self.divergent():
213 troubles.append('divergent')
213 troubles.append('divergent')
214 return troubles
214 return troubles
215
215
216 def parents(self):
216 def parents(self):
217 """return contexts for each parent changeset"""
217 """return contexts for each parent changeset"""
218 return self._parents
218 return self._parents
219
219
220 def p1(self):
220 def p1(self):
221 return self._parents[0]
221 return self._parents[0]
222
222
223 def p2(self):
223 def p2(self):
224 parents = self._parents
224 parents = self._parents
225 if len(parents) == 2:
225 if len(parents) == 2:
226 return parents[1]
226 return parents[1]
227 return changectx(self._repo, nullrev)
227 return changectx(self._repo, nullrev)
228
228
229 def _fileinfo(self, path):
229 def _fileinfo(self, path):
230 if '_manifest' in self.__dict__:
230 if '_manifest' in self.__dict__:
231 try:
231 try:
232 return self._manifest[path], self._manifest.flags(path)
232 return self._manifest[path], self._manifest.flags(path)
233 except KeyError:
233 except KeyError:
234 raise error.ManifestLookupError(self._node, path,
234 raise error.ManifestLookupError(self._node, path,
235 _('not found in manifest'))
235 _('not found in manifest'))
236 if '_manifestdelta' in self.__dict__ or path in self.files():
236 if '_manifestdelta' in self.__dict__ or path in self.files():
237 if path in self._manifestdelta:
237 if path in self._manifestdelta:
238 return (self._manifestdelta[path],
238 return (self._manifestdelta[path],
239 self._manifestdelta.flags(path))
239 self._manifestdelta.flags(path))
240 node, flag = self._repo.manifest.find(self._changeset[0], path)
240 node, flag = self._repo.manifest.find(self._changeset[0], path)
241 if not node:
241 if not node:
242 raise error.ManifestLookupError(self._node, path,
242 raise error.ManifestLookupError(self._node, path,
243 _('not found in manifest'))
243 _('not found in manifest'))
244
244
245 return node, flag
245 return node, flag
246
246
247 def filenode(self, path):
247 def filenode(self, path):
248 return self._fileinfo(path)[0]
248 return self._fileinfo(path)[0]
249
249
250 def flags(self, path):
250 def flags(self, path):
251 try:
251 try:
252 return self._fileinfo(path)[1]
252 return self._fileinfo(path)[1]
253 except error.LookupError:
253 except error.LookupError:
254 return ''
254 return ''
255
255
256 def sub(self, path):
256 def sub(self, path):
257 '''return a subrepo for the stored revision of path, never wdir()'''
257 '''return a subrepo for the stored revision of path, never wdir()'''
258 return subrepo.subrepo(self, path)
258 return subrepo.subrepo(self, path)
259
259
260 def nullsub(self, path, pctx):
260 def nullsub(self, path, pctx):
261 return subrepo.nullsubrepo(self, path, pctx)
261 return subrepo.nullsubrepo(self, path, pctx)
262
262
263 def workingsub(self, path):
263 def workingsub(self, path):
264 '''return a subrepo for the stored revision, or wdir if this is a wdir
264 '''return a subrepo for the stored revision, or wdir if this is a wdir
265 context.
265 context.
266 '''
266 '''
267 return subrepo.subrepo(self, path, allowwdir=True)
267 return subrepo.subrepo(self, path, allowwdir=True)
268
268
269 def match(self, pats=[], include=None, exclude=None, default='glob',
269 def match(self, pats=[], include=None, exclude=None, default='glob',
270 listsubrepos=False, badfn=None):
270 listsubrepos=False, badfn=None):
271 r = self._repo
271 r = self._repo
272 return matchmod.match(r.root, r.getcwd(), pats,
272 return matchmod.match(r.root, r.getcwd(), pats,
273 include, exclude, default,
273 include, exclude, default,
274 auditor=r.auditor, ctx=self,
274 auditor=r.auditor, ctx=self,
275 listsubrepos=listsubrepos, badfn=badfn)
275 listsubrepos=listsubrepos, badfn=badfn)
276
276
277 def diff(self, ctx2=None, match=None, **opts):
277 def diff(self, ctx2=None, match=None, **opts):
278 """Returns a diff generator for the given contexts and matcher"""
278 """Returns a diff generator for the given contexts and matcher"""
279 if ctx2 is None:
279 if ctx2 is None:
280 ctx2 = self.p1()
280 ctx2 = self.p1()
281 if ctx2 is not None:
281 if ctx2 is not None:
282 ctx2 = self._repo[ctx2]
282 ctx2 = self._repo[ctx2]
283 diffopts = patch.diffopts(self._repo.ui, opts)
283 diffopts = patch.diffopts(self._repo.ui, opts)
284 return patch.diff(self._repo, ctx2, self, match=match, opts=diffopts)
284 return patch.diff(self._repo, ctx2, self, match=match, opts=diffopts)
285
285
286 def dirs(self):
286 def dirs(self):
287 return self._manifest.dirs()
287 return self._manifest.dirs()
288
288
289 def hasdir(self, dir):
289 def hasdir(self, dir):
290 return self._manifest.hasdir(dir)
290 return self._manifest.hasdir(dir)
291
291
292 def dirty(self, missing=False, merge=True, branch=True):
292 def dirty(self, missing=False, merge=True, branch=True):
293 return False
293 return False
294
294
295 def status(self, other=None, match=None, listignored=False,
295 def status(self, other=None, match=None, listignored=False,
296 listclean=False, listunknown=False, listsubrepos=False):
296 listclean=False, listunknown=False, listsubrepos=False):
297 """return status of files between two nodes or node and working
297 """return status of files between two nodes or node and working
298 directory.
298 directory.
299
299
300 If other is None, compare this node with working directory.
300 If other is None, compare this node with working directory.
301
301
302 returns (modified, added, removed, deleted, unknown, ignored, clean)
302 returns (modified, added, removed, deleted, unknown, ignored, clean)
303 """
303 """
304
304
305 ctx1 = self
305 ctx1 = self
306 ctx2 = self._repo[other]
306 ctx2 = self._repo[other]
307
307
308 # This next code block is, admittedly, fragile logic that tests for
308 # This next code block is, admittedly, fragile logic that tests for
309 # reversing the contexts and wouldn't need to exist if it weren't for
309 # reversing the contexts and wouldn't need to exist if it weren't for
310 # the fast (and common) code path of comparing the working directory
310 # the fast (and common) code path of comparing the working directory
311 # with its first parent.
311 # with its first parent.
312 #
312 #
313 # What we're aiming for here is the ability to call:
313 # What we're aiming for here is the ability to call:
314 #
314 #
315 # workingctx.status(parentctx)
315 # workingctx.status(parentctx)
316 #
316 #
317 # If we always built the manifest for each context and compared those,
317 # If we always built the manifest for each context and compared those,
318 # then we'd be done. But the special case of the above call means we
318 # then we'd be done. But the special case of the above call means we
319 # just copy the manifest of the parent.
319 # just copy the manifest of the parent.
320 reversed = False
320 reversed = False
321 if (not isinstance(ctx1, changectx)
321 if (not isinstance(ctx1, changectx)
322 and isinstance(ctx2, changectx)):
322 and isinstance(ctx2, changectx)):
323 reversed = True
323 reversed = True
324 ctx1, ctx2 = ctx2, ctx1
324 ctx1, ctx2 = ctx2, ctx1
325
325
326 match = ctx2._matchstatus(ctx1, match)
326 match = ctx2._matchstatus(ctx1, match)
327 r = scmutil.status([], [], [], [], [], [], [])
327 r = scmutil.status([], [], [], [], [], [], [])
328 r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
328 r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
329 listunknown)
329 listunknown)
330
330
331 if reversed:
331 if reversed:
332 # Reverse added and removed. Clear deleted, unknown and ignored as
332 # Reverse added and removed. Clear deleted, unknown and ignored as
333 # these make no sense to reverse.
333 # these make no sense to reverse.
334 r = scmutil.status(r.modified, r.removed, r.added, [], [], [],
334 r = scmutil.status(r.modified, r.removed, r.added, [], [], [],
335 r.clean)
335 r.clean)
336
336
337 if listsubrepos:
337 if listsubrepos:
338 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
338 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
339 try:
339 rev2 = ctx2.subrev(subpath)
340 rev2 = ctx2.subrev(subpath)
340 try:
341 except KeyError:
342 # A subrepo that existed in node1 was deleted between
343 # node1 and node2 (inclusive). Thus, ctx2's substate
344 # won't contain that subpath. The best we can do ignore it.
345 rev2 = None
341 submatch = matchmod.narrowmatcher(subpath, match)
346 submatch = matchmod.narrowmatcher(subpath, match)
342 s = sub.status(rev2, match=submatch, ignored=listignored,
347 s = sub.status(rev2, match=submatch, ignored=listignored,
343 clean=listclean, unknown=listunknown,
348 clean=listclean, unknown=listunknown,
344 listsubrepos=True)
349 listsubrepos=True)
345 for rfiles, sfiles in zip(r, s):
350 for rfiles, sfiles in zip(r, s):
346 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
351 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
347 except error.LookupError:
348 self._repo.ui.status(_("skipping missing "
349 "subrepository: %s\n") % subpath)
350
352
351 for l in r:
353 for l in r:
352 l.sort()
354 l.sort()
353
355
354 return r
356 return r
355
357
356
358
357 def makememctx(repo, parents, text, user, date, branch, files, store,
359 def makememctx(repo, parents, text, user, date, branch, files, store,
358 editor=None, extra=None):
360 editor=None, extra=None):
359 def getfilectx(repo, memctx, path):
361 def getfilectx(repo, memctx, path):
360 data, mode, copied = store.getfile(path)
362 data, mode, copied = store.getfile(path)
361 if data is None:
363 if data is None:
362 return None
364 return None
363 islink, isexec = mode
365 islink, isexec = mode
364 return memfilectx(repo, path, data, islink=islink, isexec=isexec,
366 return memfilectx(repo, path, data, islink=islink, isexec=isexec,
365 copied=copied, memctx=memctx)
367 copied=copied, memctx=memctx)
366 if extra is None:
368 if extra is None:
367 extra = {}
369 extra = {}
368 if branch:
370 if branch:
369 extra['branch'] = encoding.fromlocal(branch)
371 extra['branch'] = encoding.fromlocal(branch)
370 ctx = memctx(repo, parents, text, files, getfilectx, user,
372 ctx = memctx(repo, parents, text, files, getfilectx, user,
371 date, extra, editor)
373 date, extra, editor)
372 return ctx
374 return ctx
373
375
374 class changectx(basectx):
376 class changectx(basectx):
375 """A changecontext object makes access to data related to a particular
377 """A changecontext object makes access to data related to a particular
376 changeset convenient. It represents a read-only context already present in
378 changeset convenient. It represents a read-only context already present in
377 the repo."""
379 the repo."""
378 def __init__(self, repo, changeid=''):
380 def __init__(self, repo, changeid=''):
379 """changeid is a revision number, node, or tag"""
381 """changeid is a revision number, node, or tag"""
380
382
381 # since basectx.__new__ already took care of copying the object, we
383 # since basectx.__new__ already took care of copying the object, we
382 # don't need to do anything in __init__, so we just exit here
384 # don't need to do anything in __init__, so we just exit here
383 if isinstance(changeid, basectx):
385 if isinstance(changeid, basectx):
384 return
386 return
385
387
386 if changeid == '':
388 if changeid == '':
387 changeid = '.'
389 changeid = '.'
388 self._repo = repo
390 self._repo = repo
389
391
390 try:
392 try:
391 if isinstance(changeid, int):
393 if isinstance(changeid, int):
392 self._node = repo.changelog.node(changeid)
394 self._node = repo.changelog.node(changeid)
393 self._rev = changeid
395 self._rev = changeid
394 return
396 return
395 if isinstance(changeid, long):
397 if isinstance(changeid, long):
396 changeid = str(changeid)
398 changeid = str(changeid)
397 if changeid == 'null':
399 if changeid == 'null':
398 self._node = nullid
400 self._node = nullid
399 self._rev = nullrev
401 self._rev = nullrev
400 return
402 return
401 if changeid == 'tip':
403 if changeid == 'tip':
402 self._node = repo.changelog.tip()
404 self._node = repo.changelog.tip()
403 self._rev = repo.changelog.rev(self._node)
405 self._rev = repo.changelog.rev(self._node)
404 return
406 return
405 if changeid == '.' or changeid == repo.dirstate.p1():
407 if changeid == '.' or changeid == repo.dirstate.p1():
406 # this is a hack to delay/avoid loading obsmarkers
408 # this is a hack to delay/avoid loading obsmarkers
407 # when we know that '.' won't be hidden
409 # when we know that '.' won't be hidden
408 self._node = repo.dirstate.p1()
410 self._node = repo.dirstate.p1()
409 self._rev = repo.unfiltered().changelog.rev(self._node)
411 self._rev = repo.unfiltered().changelog.rev(self._node)
410 return
412 return
411 if len(changeid) == 20:
413 if len(changeid) == 20:
412 try:
414 try:
413 self._node = changeid
415 self._node = changeid
414 self._rev = repo.changelog.rev(changeid)
416 self._rev = repo.changelog.rev(changeid)
415 return
417 return
416 except error.FilteredRepoLookupError:
418 except error.FilteredRepoLookupError:
417 raise
419 raise
418 except LookupError:
420 except LookupError:
419 pass
421 pass
420
422
421 try:
423 try:
422 r = int(changeid)
424 r = int(changeid)
423 if str(r) != changeid:
425 if str(r) != changeid:
424 raise ValueError
426 raise ValueError
425 l = len(repo.changelog)
427 l = len(repo.changelog)
426 if r < 0:
428 if r < 0:
427 r += l
429 r += l
428 if r < 0 or r >= l:
430 if r < 0 or r >= l:
429 raise ValueError
431 raise ValueError
430 self._rev = r
432 self._rev = r
431 self._node = repo.changelog.node(r)
433 self._node = repo.changelog.node(r)
432 return
434 return
433 except error.FilteredIndexError:
435 except error.FilteredIndexError:
434 raise
436 raise
435 except (ValueError, OverflowError, IndexError):
437 except (ValueError, OverflowError, IndexError):
436 pass
438 pass
437
439
438 if len(changeid) == 40:
440 if len(changeid) == 40:
439 try:
441 try:
440 self._node = bin(changeid)
442 self._node = bin(changeid)
441 self._rev = repo.changelog.rev(self._node)
443 self._rev = repo.changelog.rev(self._node)
442 return
444 return
443 except error.FilteredLookupError:
445 except error.FilteredLookupError:
444 raise
446 raise
445 except (TypeError, LookupError):
447 except (TypeError, LookupError):
446 pass
448 pass
447
449
448 # lookup bookmarks through the name interface
450 # lookup bookmarks through the name interface
449 try:
451 try:
450 self._node = repo.names.singlenode(repo, changeid)
452 self._node = repo.names.singlenode(repo, changeid)
451 self._rev = repo.changelog.rev(self._node)
453 self._rev = repo.changelog.rev(self._node)
452 return
454 return
453 except KeyError:
455 except KeyError:
454 pass
456 pass
455 except error.FilteredRepoLookupError:
457 except error.FilteredRepoLookupError:
456 raise
458 raise
457 except error.RepoLookupError:
459 except error.RepoLookupError:
458 pass
460 pass
459
461
460 self._node = repo.unfiltered().changelog._partialmatch(changeid)
462 self._node = repo.unfiltered().changelog._partialmatch(changeid)
461 if self._node is not None:
463 if self._node is not None:
462 self._rev = repo.changelog.rev(self._node)
464 self._rev = repo.changelog.rev(self._node)
463 return
465 return
464
466
465 # lookup failed
467 # lookup failed
466 # check if it might have come from damaged dirstate
468 # check if it might have come from damaged dirstate
467 #
469 #
468 # XXX we could avoid the unfiltered if we had a recognizable
470 # XXX we could avoid the unfiltered if we had a recognizable
469 # exception for filtered changeset access
471 # exception for filtered changeset access
470 if changeid in repo.unfiltered().dirstate.parents():
472 if changeid in repo.unfiltered().dirstate.parents():
471 msg = _("working directory has unknown parent '%s'!")
473 msg = _("working directory has unknown parent '%s'!")
472 raise error.Abort(msg % short(changeid))
474 raise error.Abort(msg % short(changeid))
473 try:
475 try:
474 if len(changeid) == 20 and nonascii(changeid):
476 if len(changeid) == 20 and nonascii(changeid):
475 changeid = hex(changeid)
477 changeid = hex(changeid)
476 except TypeError:
478 except TypeError:
477 pass
479 pass
478 except (error.FilteredIndexError, error.FilteredLookupError,
480 except (error.FilteredIndexError, error.FilteredLookupError,
479 error.FilteredRepoLookupError):
481 error.FilteredRepoLookupError):
480 if repo.filtername.startswith('visible'):
482 if repo.filtername.startswith('visible'):
481 msg = _("hidden revision '%s'") % changeid
483 msg = _("hidden revision '%s'") % changeid
482 hint = _('use --hidden to access hidden revisions')
484 hint = _('use --hidden to access hidden revisions')
483 raise error.FilteredRepoLookupError(msg, hint=hint)
485 raise error.FilteredRepoLookupError(msg, hint=hint)
484 msg = _("filtered revision '%s' (not in '%s' subset)")
486 msg = _("filtered revision '%s' (not in '%s' subset)")
485 msg %= (changeid, repo.filtername)
487 msg %= (changeid, repo.filtername)
486 raise error.FilteredRepoLookupError(msg)
488 raise error.FilteredRepoLookupError(msg)
487 except IndexError:
489 except IndexError:
488 pass
490 pass
489 raise error.RepoLookupError(
491 raise error.RepoLookupError(
490 _("unknown revision '%s'") % changeid)
492 _("unknown revision '%s'") % changeid)
491
493
492 def __hash__(self):
494 def __hash__(self):
493 try:
495 try:
494 return hash(self._rev)
496 return hash(self._rev)
495 except AttributeError:
497 except AttributeError:
496 return id(self)
498 return id(self)
497
499
498 def __nonzero__(self):
500 def __nonzero__(self):
499 return self._rev != nullrev
501 return self._rev != nullrev
500
502
501 @propertycache
503 @propertycache
502 def _changeset(self):
504 def _changeset(self):
503 return self._repo.changelog.read(self.rev())
505 return self._repo.changelog.read(self.rev())
504
506
505 @propertycache
507 @propertycache
506 def _manifest(self):
508 def _manifest(self):
507 return self._repo.manifest.read(self._changeset[0])
509 return self._repo.manifest.read(self._changeset[0])
508
510
509 @propertycache
511 @propertycache
510 def _manifestdelta(self):
512 def _manifestdelta(self):
511 return self._repo.manifest.readdelta(self._changeset[0])
513 return self._repo.manifest.readdelta(self._changeset[0])
512
514
513 @propertycache
515 @propertycache
514 def _parents(self):
516 def _parents(self):
515 repo = self._repo
517 repo = self._repo
516 p1, p2 = repo.changelog.parentrevs(self._rev)
518 p1, p2 = repo.changelog.parentrevs(self._rev)
517 if p2 == nullrev:
519 if p2 == nullrev:
518 return [changectx(repo, p1)]
520 return [changectx(repo, p1)]
519 return [changectx(repo, p1), changectx(repo, p2)]
521 return [changectx(repo, p1), changectx(repo, p2)]
520
522
521 def changeset(self):
523 def changeset(self):
522 return self._changeset
524 return self._changeset
523 def manifestnode(self):
525 def manifestnode(self):
524 return self._changeset[0]
526 return self._changeset[0]
525
527
526 def user(self):
528 def user(self):
527 return self._changeset[1]
529 return self._changeset[1]
528 def date(self):
530 def date(self):
529 return self._changeset[2]
531 return self._changeset[2]
530 def files(self):
532 def files(self):
531 return self._changeset[3]
533 return self._changeset[3]
532 def description(self):
534 def description(self):
533 return self._changeset[4]
535 return self._changeset[4]
534 def branch(self):
536 def branch(self):
535 return encoding.tolocal(self._changeset[5].get("branch"))
537 return encoding.tolocal(self._changeset[5].get("branch"))
536 def closesbranch(self):
538 def closesbranch(self):
537 return 'close' in self._changeset[5]
539 return 'close' in self._changeset[5]
538 def extra(self):
540 def extra(self):
539 return self._changeset[5]
541 return self._changeset[5]
540 def tags(self):
542 def tags(self):
541 return self._repo.nodetags(self._node)
543 return self._repo.nodetags(self._node)
542 def bookmarks(self):
544 def bookmarks(self):
543 return self._repo.nodebookmarks(self._node)
545 return self._repo.nodebookmarks(self._node)
544 def phase(self):
546 def phase(self):
545 return self._repo._phasecache.phase(self._repo, self._rev)
547 return self._repo._phasecache.phase(self._repo, self._rev)
546 def hidden(self):
548 def hidden(self):
547 return self._rev in repoview.filterrevs(self._repo, 'visible')
549 return self._rev in repoview.filterrevs(self._repo, 'visible')
548
550
549 def children(self):
551 def children(self):
550 """return contexts for each child changeset"""
552 """return contexts for each child changeset"""
551 c = self._repo.changelog.children(self._node)
553 c = self._repo.changelog.children(self._node)
552 return [changectx(self._repo, x) for x in c]
554 return [changectx(self._repo, x) for x in c]
553
555
554 def ancestors(self):
556 def ancestors(self):
555 for a in self._repo.changelog.ancestors([self._rev]):
557 for a in self._repo.changelog.ancestors([self._rev]):
556 yield changectx(self._repo, a)
558 yield changectx(self._repo, a)
557
559
558 def descendants(self):
560 def descendants(self):
559 for d in self._repo.changelog.descendants([self._rev]):
561 for d in self._repo.changelog.descendants([self._rev]):
560 yield changectx(self._repo, d)
562 yield changectx(self._repo, d)
561
563
562 def filectx(self, path, fileid=None, filelog=None):
564 def filectx(self, path, fileid=None, filelog=None):
563 """get a file context from this changeset"""
565 """get a file context from this changeset"""
564 if fileid is None:
566 if fileid is None:
565 fileid = self.filenode(path)
567 fileid = self.filenode(path)
566 return filectx(self._repo, path, fileid=fileid,
568 return filectx(self._repo, path, fileid=fileid,
567 changectx=self, filelog=filelog)
569 changectx=self, filelog=filelog)
568
570
569 def ancestor(self, c2, warn=False):
571 def ancestor(self, c2, warn=False):
570 """return the "best" ancestor context of self and c2
572 """return the "best" ancestor context of self and c2
571
573
572 If there are multiple candidates, it will show a message and check
574 If there are multiple candidates, it will show a message and check
573 merge.preferancestor configuration before falling back to the
575 merge.preferancestor configuration before falling back to the
574 revlog ancestor."""
576 revlog ancestor."""
575 # deal with workingctxs
577 # deal with workingctxs
576 n2 = c2._node
578 n2 = c2._node
577 if n2 is None:
579 if n2 is None:
578 n2 = c2._parents[0]._node
580 n2 = c2._parents[0]._node
579 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
581 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
580 if not cahs:
582 if not cahs:
581 anc = nullid
583 anc = nullid
582 elif len(cahs) == 1:
584 elif len(cahs) == 1:
583 anc = cahs[0]
585 anc = cahs[0]
584 else:
586 else:
585 # experimental config: merge.preferancestor
587 # experimental config: merge.preferancestor
586 for r in self._repo.ui.configlist('merge', 'preferancestor', ['*']):
588 for r in self._repo.ui.configlist('merge', 'preferancestor', ['*']):
587 try:
589 try:
588 ctx = changectx(self._repo, r)
590 ctx = changectx(self._repo, r)
589 except error.RepoLookupError:
591 except error.RepoLookupError:
590 continue
592 continue
591 anc = ctx.node()
593 anc = ctx.node()
592 if anc in cahs:
594 if anc in cahs:
593 break
595 break
594 else:
596 else:
595 anc = self._repo.changelog.ancestor(self._node, n2)
597 anc = self._repo.changelog.ancestor(self._node, n2)
596 if warn:
598 if warn:
597 self._repo.ui.status(
599 self._repo.ui.status(
598 (_("note: using %s as ancestor of %s and %s\n") %
600 (_("note: using %s as ancestor of %s and %s\n") %
599 (short(anc), short(self._node), short(n2))) +
601 (short(anc), short(self._node), short(n2))) +
600 ''.join(_(" alternatively, use --config "
602 ''.join(_(" alternatively, use --config "
601 "merge.preferancestor=%s\n") %
603 "merge.preferancestor=%s\n") %
602 short(n) for n in sorted(cahs) if n != anc))
604 short(n) for n in sorted(cahs) if n != anc))
603 return changectx(self._repo, anc)
605 return changectx(self._repo, anc)
604
606
605 def descendant(self, other):
607 def descendant(self, other):
606 """True if other is descendant of this changeset"""
608 """True if other is descendant of this changeset"""
607 return self._repo.changelog.descendant(self._rev, other._rev)
609 return self._repo.changelog.descendant(self._rev, other._rev)
608
610
609 def walk(self, match):
611 def walk(self, match):
610 '''Generates matching file names.'''
612 '''Generates matching file names.'''
611
613
612 # Wrap match.bad method to have message with nodeid
614 # Wrap match.bad method to have message with nodeid
613 def bad(fn, msg):
615 def bad(fn, msg):
614 # The manifest doesn't know about subrepos, so don't complain about
616 # The manifest doesn't know about subrepos, so don't complain about
615 # paths into valid subrepos.
617 # paths into valid subrepos.
616 if any(fn == s or fn.startswith(s + '/')
618 if any(fn == s or fn.startswith(s + '/')
617 for s in self.substate):
619 for s in self.substate):
618 return
620 return
619 match.bad(fn, _('no such file in rev %s') % self)
621 match.bad(fn, _('no such file in rev %s') % self)
620
622
621 m = matchmod.badmatch(match, bad)
623 m = matchmod.badmatch(match, bad)
622 return self._manifest.walk(m)
624 return self._manifest.walk(m)
623
625
624 def matches(self, match):
626 def matches(self, match):
625 return self.walk(match)
627 return self.walk(match)
626
628
627 class basefilectx(object):
629 class basefilectx(object):
628 """A filecontext object represents the common logic for its children:
630 """A filecontext object represents the common logic for its children:
629 filectx: read-only access to a filerevision that is already present
631 filectx: read-only access to a filerevision that is already present
630 in the repo,
632 in the repo,
631 workingfilectx: a filecontext that represents files from the working
633 workingfilectx: a filecontext that represents files from the working
632 directory,
634 directory,
633 memfilectx: a filecontext that represents files in-memory."""
635 memfilectx: a filecontext that represents files in-memory."""
634 def __new__(cls, repo, path, *args, **kwargs):
636 def __new__(cls, repo, path, *args, **kwargs):
635 return super(basefilectx, cls).__new__(cls)
637 return super(basefilectx, cls).__new__(cls)
636
638
637 @propertycache
639 @propertycache
638 def _filelog(self):
640 def _filelog(self):
639 return self._repo.file(self._path)
641 return self._repo.file(self._path)
640
642
641 @propertycache
643 @propertycache
642 def _changeid(self):
644 def _changeid(self):
643 if '_changeid' in self.__dict__:
645 if '_changeid' in self.__dict__:
644 return self._changeid
646 return self._changeid
645 elif '_changectx' in self.__dict__:
647 elif '_changectx' in self.__dict__:
646 return self._changectx.rev()
648 return self._changectx.rev()
647 elif '_descendantrev' in self.__dict__:
649 elif '_descendantrev' in self.__dict__:
648 # this file context was created from a revision with a known
650 # this file context was created from a revision with a known
649 # descendant, we can (lazily) correct for linkrev aliases
651 # descendant, we can (lazily) correct for linkrev aliases
650 return self._adjustlinkrev(self._path, self._filelog,
652 return self._adjustlinkrev(self._path, self._filelog,
651 self._filenode, self._descendantrev)
653 self._filenode, self._descendantrev)
652 else:
654 else:
653 return self._filelog.linkrev(self._filerev)
655 return self._filelog.linkrev(self._filerev)
654
656
655 @propertycache
657 @propertycache
656 def _filenode(self):
658 def _filenode(self):
657 if '_fileid' in self.__dict__:
659 if '_fileid' in self.__dict__:
658 return self._filelog.lookup(self._fileid)
660 return self._filelog.lookup(self._fileid)
659 else:
661 else:
660 return self._changectx.filenode(self._path)
662 return self._changectx.filenode(self._path)
661
663
662 @propertycache
664 @propertycache
663 def _filerev(self):
665 def _filerev(self):
664 return self._filelog.rev(self._filenode)
666 return self._filelog.rev(self._filenode)
665
667
666 @propertycache
668 @propertycache
667 def _repopath(self):
669 def _repopath(self):
668 return self._path
670 return self._path
669
671
670 def __nonzero__(self):
672 def __nonzero__(self):
671 try:
673 try:
672 self._filenode
674 self._filenode
673 return True
675 return True
674 except error.LookupError:
676 except error.LookupError:
675 # file is missing
677 # file is missing
676 return False
678 return False
677
679
678 def __str__(self):
680 def __str__(self):
679 return "%s@%s" % (self.path(), self._changectx)
681 return "%s@%s" % (self.path(), self._changectx)
680
682
681 def __repr__(self):
683 def __repr__(self):
682 return "<%s %s>" % (type(self).__name__, str(self))
684 return "<%s %s>" % (type(self).__name__, str(self))
683
685
684 def __hash__(self):
686 def __hash__(self):
685 try:
687 try:
686 return hash((self._path, self._filenode))
688 return hash((self._path, self._filenode))
687 except AttributeError:
689 except AttributeError:
688 return id(self)
690 return id(self)
689
691
690 def __eq__(self, other):
692 def __eq__(self, other):
691 try:
693 try:
692 return (type(self) == type(other) and self._path == other._path
694 return (type(self) == type(other) and self._path == other._path
693 and self._filenode == other._filenode)
695 and self._filenode == other._filenode)
694 except AttributeError:
696 except AttributeError:
695 return False
697 return False
696
698
697 def __ne__(self, other):
699 def __ne__(self, other):
698 return not (self == other)
700 return not (self == other)
699
701
700 def filerev(self):
702 def filerev(self):
701 return self._filerev
703 return self._filerev
702 def filenode(self):
704 def filenode(self):
703 return self._filenode
705 return self._filenode
704 def flags(self):
706 def flags(self):
705 return self._changectx.flags(self._path)
707 return self._changectx.flags(self._path)
706 def filelog(self):
708 def filelog(self):
707 return self._filelog
709 return self._filelog
708 def rev(self):
710 def rev(self):
709 return self._changeid
711 return self._changeid
710 def linkrev(self):
712 def linkrev(self):
711 return self._filelog.linkrev(self._filerev)
713 return self._filelog.linkrev(self._filerev)
712 def node(self):
714 def node(self):
713 return self._changectx.node()
715 return self._changectx.node()
714 def hex(self):
716 def hex(self):
715 return self._changectx.hex()
717 return self._changectx.hex()
716 def user(self):
718 def user(self):
717 return self._changectx.user()
719 return self._changectx.user()
718 def date(self):
720 def date(self):
719 return self._changectx.date()
721 return self._changectx.date()
720 def files(self):
722 def files(self):
721 return self._changectx.files()
723 return self._changectx.files()
722 def description(self):
724 def description(self):
723 return self._changectx.description()
725 return self._changectx.description()
724 def branch(self):
726 def branch(self):
725 return self._changectx.branch()
727 return self._changectx.branch()
726 def extra(self):
728 def extra(self):
727 return self._changectx.extra()
729 return self._changectx.extra()
728 def phase(self):
730 def phase(self):
729 return self._changectx.phase()
731 return self._changectx.phase()
730 def phasestr(self):
732 def phasestr(self):
731 return self._changectx.phasestr()
733 return self._changectx.phasestr()
732 def manifest(self):
734 def manifest(self):
733 return self._changectx.manifest()
735 return self._changectx.manifest()
734 def changectx(self):
736 def changectx(self):
735 return self._changectx
737 return self._changectx
736 def repo(self):
738 def repo(self):
737 return self._repo
739 return self._repo
738
740
739 def path(self):
741 def path(self):
740 return self._path
742 return self._path
741
743
742 def isbinary(self):
744 def isbinary(self):
743 try:
745 try:
744 return util.binary(self.data())
746 return util.binary(self.data())
745 except IOError:
747 except IOError:
746 return False
748 return False
747 def isexec(self):
749 def isexec(self):
748 return 'x' in self.flags()
750 return 'x' in self.flags()
749 def islink(self):
751 def islink(self):
750 return 'l' in self.flags()
752 return 'l' in self.flags()
751
753
752 def isabsent(self):
754 def isabsent(self):
753 """whether this filectx represents a file not in self._changectx
755 """whether this filectx represents a file not in self._changectx
754
756
755 This is mainly for merge code to detect change/delete conflicts. This is
757 This is mainly for merge code to detect change/delete conflicts. This is
756 expected to be True for all subclasses of basectx."""
758 expected to be True for all subclasses of basectx."""
757 return False
759 return False
758
760
759 _customcmp = False
761 _customcmp = False
760 def cmp(self, fctx):
762 def cmp(self, fctx):
761 """compare with other file context
763 """compare with other file context
762
764
763 returns True if different than fctx.
765 returns True if different than fctx.
764 """
766 """
765 if fctx._customcmp:
767 if fctx._customcmp:
766 return fctx.cmp(self)
768 return fctx.cmp(self)
767
769
768 if (fctx._filerev is None
770 if (fctx._filerev is None
769 and (self._repo._encodefilterpats
771 and (self._repo._encodefilterpats
770 # if file data starts with '\1\n', empty metadata block is
772 # if file data starts with '\1\n', empty metadata block is
771 # prepended, which adds 4 bytes to filelog.size().
773 # prepended, which adds 4 bytes to filelog.size().
772 or self.size() - 4 == fctx.size())
774 or self.size() - 4 == fctx.size())
773 or self.size() == fctx.size()):
775 or self.size() == fctx.size()):
774 return self._filelog.cmp(self._filenode, fctx.data())
776 return self._filelog.cmp(self._filenode, fctx.data())
775
777
776 return True
778 return True
777
779
778 def _adjustlinkrev(self, path, filelog, fnode, srcrev, inclusive=False):
780 def _adjustlinkrev(self, path, filelog, fnode, srcrev, inclusive=False):
779 """return the first ancestor of <srcrev> introducing <fnode>
781 """return the first ancestor of <srcrev> introducing <fnode>
780
782
781 If the linkrev of the file revision does not point to an ancestor of
783 If the linkrev of the file revision does not point to an ancestor of
782 srcrev, we'll walk down the ancestors until we find one introducing
784 srcrev, we'll walk down the ancestors until we find one introducing
783 this file revision.
785 this file revision.
784
786
785 :repo: a localrepository object (used to access changelog and manifest)
787 :repo: a localrepository object (used to access changelog and manifest)
786 :path: the file path
788 :path: the file path
787 :fnode: the nodeid of the file revision
789 :fnode: the nodeid of the file revision
788 :filelog: the filelog of this path
790 :filelog: the filelog of this path
789 :srcrev: the changeset revision we search ancestors from
791 :srcrev: the changeset revision we search ancestors from
790 :inclusive: if true, the src revision will also be checked
792 :inclusive: if true, the src revision will also be checked
791 """
793 """
792 repo = self._repo
794 repo = self._repo
793 cl = repo.unfiltered().changelog
795 cl = repo.unfiltered().changelog
794 ma = repo.manifest
796 ma = repo.manifest
795 # fetch the linkrev
797 # fetch the linkrev
796 fr = filelog.rev(fnode)
798 fr = filelog.rev(fnode)
797 lkr = filelog.linkrev(fr)
799 lkr = filelog.linkrev(fr)
798 # hack to reuse ancestor computation when searching for renames
800 # hack to reuse ancestor computation when searching for renames
799 memberanc = getattr(self, '_ancestrycontext', None)
801 memberanc = getattr(self, '_ancestrycontext', None)
800 iteranc = None
802 iteranc = None
801 if srcrev is None:
803 if srcrev is None:
802 # wctx case, used by workingfilectx during mergecopy
804 # wctx case, used by workingfilectx during mergecopy
803 revs = [p.rev() for p in self._repo[None].parents()]
805 revs = [p.rev() for p in self._repo[None].parents()]
804 inclusive = True # we skipped the real (revless) source
806 inclusive = True # we skipped the real (revless) source
805 else:
807 else:
806 revs = [srcrev]
808 revs = [srcrev]
807 if memberanc is None:
809 if memberanc is None:
808 memberanc = iteranc = cl.ancestors(revs, lkr,
810 memberanc = iteranc = cl.ancestors(revs, lkr,
809 inclusive=inclusive)
811 inclusive=inclusive)
810 # check if this linkrev is an ancestor of srcrev
812 # check if this linkrev is an ancestor of srcrev
811 if lkr not in memberanc:
813 if lkr not in memberanc:
812 if iteranc is None:
814 if iteranc is None:
813 iteranc = cl.ancestors(revs, lkr, inclusive=inclusive)
815 iteranc = cl.ancestors(revs, lkr, inclusive=inclusive)
814 for a in iteranc:
816 for a in iteranc:
815 ac = cl.read(a) # get changeset data (we avoid object creation)
817 ac = cl.read(a) # get changeset data (we avoid object creation)
816 if path in ac[3]: # checking the 'files' field.
818 if path in ac[3]: # checking the 'files' field.
817 # The file has been touched, check if the content is
819 # The file has been touched, check if the content is
818 # similar to the one we search for.
820 # similar to the one we search for.
819 if fnode == ma.readfast(ac[0]).get(path):
821 if fnode == ma.readfast(ac[0]).get(path):
820 return a
822 return a
821 # In theory, we should never get out of that loop without a result.
823 # In theory, we should never get out of that loop without a result.
822 # But if manifest uses a buggy file revision (not children of the
824 # But if manifest uses a buggy file revision (not children of the
823 # one it replaces) we could. Such a buggy situation will likely
825 # one it replaces) we could. Such a buggy situation will likely
824 # result is crash somewhere else at to some point.
826 # result is crash somewhere else at to some point.
825 return lkr
827 return lkr
826
828
827 def introrev(self):
829 def introrev(self):
828 """return the rev of the changeset which introduced this file revision
830 """return the rev of the changeset which introduced this file revision
829
831
830 This method is different from linkrev because it take into account the
832 This method is different from linkrev because it take into account the
831 changeset the filectx was created from. It ensures the returned
833 changeset the filectx was created from. It ensures the returned
832 revision is one of its ancestors. This prevents bugs from
834 revision is one of its ancestors. This prevents bugs from
833 'linkrev-shadowing' when a file revision is used by multiple
835 'linkrev-shadowing' when a file revision is used by multiple
834 changesets.
836 changesets.
835 """
837 """
836 lkr = self.linkrev()
838 lkr = self.linkrev()
837 attrs = vars(self)
839 attrs = vars(self)
838 noctx = not ('_changeid' in attrs or '_changectx' in attrs)
840 noctx = not ('_changeid' in attrs or '_changectx' in attrs)
839 if noctx or self.rev() == lkr:
841 if noctx or self.rev() == lkr:
840 return self.linkrev()
842 return self.linkrev()
841 return self._adjustlinkrev(self._path, self._filelog, self._filenode,
843 return self._adjustlinkrev(self._path, self._filelog, self._filenode,
842 self.rev(), inclusive=True)
844 self.rev(), inclusive=True)
843
845
844 def _parentfilectx(self, path, fileid, filelog):
846 def _parentfilectx(self, path, fileid, filelog):
845 """create parent filectx keeping ancestry info for _adjustlinkrev()"""
847 """create parent filectx keeping ancestry info for _adjustlinkrev()"""
846 fctx = filectx(self._repo, path, fileid=fileid, filelog=filelog)
848 fctx = filectx(self._repo, path, fileid=fileid, filelog=filelog)
847 if '_changeid' in vars(self) or '_changectx' in vars(self):
849 if '_changeid' in vars(self) or '_changectx' in vars(self):
848 # If self is associated with a changeset (probably explicitly
850 # If self is associated with a changeset (probably explicitly
849 # fed), ensure the created filectx is associated with a
851 # fed), ensure the created filectx is associated with a
850 # changeset that is an ancestor of self.changectx.
852 # changeset that is an ancestor of self.changectx.
851 # This lets us later use _adjustlinkrev to get a correct link.
853 # This lets us later use _adjustlinkrev to get a correct link.
852 fctx._descendantrev = self.rev()
854 fctx._descendantrev = self.rev()
853 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
855 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
854 elif '_descendantrev' in vars(self):
856 elif '_descendantrev' in vars(self):
855 # Otherwise propagate _descendantrev if we have one associated.
857 # Otherwise propagate _descendantrev if we have one associated.
856 fctx._descendantrev = self._descendantrev
858 fctx._descendantrev = self._descendantrev
857 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
859 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
858 return fctx
860 return fctx
859
861
860 def parents(self):
862 def parents(self):
861 _path = self._path
863 _path = self._path
862 fl = self._filelog
864 fl = self._filelog
863 parents = self._filelog.parents(self._filenode)
865 parents = self._filelog.parents(self._filenode)
864 pl = [(_path, node, fl) for node in parents if node != nullid]
866 pl = [(_path, node, fl) for node in parents if node != nullid]
865
867
866 r = fl.renamed(self._filenode)
868 r = fl.renamed(self._filenode)
867 if r:
869 if r:
868 # - In the simple rename case, both parent are nullid, pl is empty.
870 # - In the simple rename case, both parent are nullid, pl is empty.
869 # - In case of merge, only one of the parent is null id and should
871 # - In case of merge, only one of the parent is null id and should
870 # be replaced with the rename information. This parent is -always-
872 # be replaced with the rename information. This parent is -always-
871 # the first one.
873 # the first one.
872 #
874 #
873 # As null id have always been filtered out in the previous list
875 # As null id have always been filtered out in the previous list
874 # comprehension, inserting to 0 will always result in "replacing
876 # comprehension, inserting to 0 will always result in "replacing
875 # first nullid parent with rename information.
877 # first nullid parent with rename information.
876 pl.insert(0, (r[0], r[1], self._repo.file(r[0])))
878 pl.insert(0, (r[0], r[1], self._repo.file(r[0])))
877
879
878 return [self._parentfilectx(path, fnode, l) for path, fnode, l in pl]
880 return [self._parentfilectx(path, fnode, l) for path, fnode, l in pl]
879
881
880 def p1(self):
882 def p1(self):
881 return self.parents()[0]
883 return self.parents()[0]
882
884
883 def p2(self):
885 def p2(self):
884 p = self.parents()
886 p = self.parents()
885 if len(p) == 2:
887 if len(p) == 2:
886 return p[1]
888 return p[1]
887 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
889 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
888
890
889 def annotate(self, follow=False, linenumber=None, diffopts=None):
891 def annotate(self, follow=False, linenumber=None, diffopts=None):
890 '''returns a list of tuples of (ctx, line) for each line
892 '''returns a list of tuples of (ctx, line) for each line
891 in the file, where ctx is the filectx of the node where
893 in the file, where ctx is the filectx of the node where
892 that line was last changed.
894 that line was last changed.
893 This returns tuples of ((ctx, linenumber), line) for each line,
895 This returns tuples of ((ctx, linenumber), line) for each line,
894 if "linenumber" parameter is NOT "None".
896 if "linenumber" parameter is NOT "None".
895 In such tuples, linenumber means one at the first appearance
897 In such tuples, linenumber means one at the first appearance
896 in the managed file.
898 in the managed file.
897 To reduce annotation cost,
899 To reduce annotation cost,
898 this returns fixed value(False is used) as linenumber,
900 this returns fixed value(False is used) as linenumber,
899 if "linenumber" parameter is "False".'''
901 if "linenumber" parameter is "False".'''
900
902
901 if linenumber is None:
903 if linenumber is None:
902 def decorate(text, rev):
904 def decorate(text, rev):
903 return ([rev] * len(text.splitlines()), text)
905 return ([rev] * len(text.splitlines()), text)
904 elif linenumber:
906 elif linenumber:
905 def decorate(text, rev):
907 def decorate(text, rev):
906 size = len(text.splitlines())
908 size = len(text.splitlines())
907 return ([(rev, i) for i in xrange(1, size + 1)], text)
909 return ([(rev, i) for i in xrange(1, size + 1)], text)
908 else:
910 else:
909 def decorate(text, rev):
911 def decorate(text, rev):
910 return ([(rev, False)] * len(text.splitlines()), text)
912 return ([(rev, False)] * len(text.splitlines()), text)
911
913
912 def pair(parent, child):
914 def pair(parent, child):
913 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
915 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
914 refine=True)
916 refine=True)
915 for (a1, a2, b1, b2), t in blocks:
917 for (a1, a2, b1, b2), t in blocks:
916 # Changed blocks ('!') or blocks made only of blank lines ('~')
918 # Changed blocks ('!') or blocks made only of blank lines ('~')
917 # belong to the child.
919 # belong to the child.
918 if t == '=':
920 if t == '=':
919 child[0][b1:b2] = parent[0][a1:a2]
921 child[0][b1:b2] = parent[0][a1:a2]
920 return child
922 return child
921
923
922 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
924 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
923
925
924 def parents(f):
926 def parents(f):
925 # Cut _descendantrev here to mitigate the penalty of lazy linkrev
927 # Cut _descendantrev here to mitigate the penalty of lazy linkrev
926 # adjustment. Otherwise, p._adjustlinkrev() would walk changelog
928 # adjustment. Otherwise, p._adjustlinkrev() would walk changelog
927 # from the topmost introrev (= srcrev) down to p.linkrev() if it
929 # from the topmost introrev (= srcrev) down to p.linkrev() if it
928 # isn't an ancestor of the srcrev.
930 # isn't an ancestor of the srcrev.
929 f._changeid
931 f._changeid
930 pl = f.parents()
932 pl = f.parents()
931
933
932 # Don't return renamed parents if we aren't following.
934 # Don't return renamed parents if we aren't following.
933 if not follow:
935 if not follow:
934 pl = [p for p in pl if p.path() == f.path()]
936 pl = [p for p in pl if p.path() == f.path()]
935
937
936 # renamed filectx won't have a filelog yet, so set it
938 # renamed filectx won't have a filelog yet, so set it
937 # from the cache to save time
939 # from the cache to save time
938 for p in pl:
940 for p in pl:
939 if not '_filelog' in p.__dict__:
941 if not '_filelog' in p.__dict__:
940 p._filelog = getlog(p.path())
942 p._filelog = getlog(p.path())
941
943
942 return pl
944 return pl
943
945
944 # use linkrev to find the first changeset where self appeared
946 # use linkrev to find the first changeset where self appeared
945 base = self
947 base = self
946 introrev = self.introrev()
948 introrev = self.introrev()
947 if self.rev() != introrev:
949 if self.rev() != introrev:
948 base = self.filectx(self.filenode(), changeid=introrev)
950 base = self.filectx(self.filenode(), changeid=introrev)
949 if getattr(base, '_ancestrycontext', None) is None:
951 if getattr(base, '_ancestrycontext', None) is None:
950 cl = self._repo.changelog
952 cl = self._repo.changelog
951 if introrev is None:
953 if introrev is None:
952 # wctx is not inclusive, but works because _ancestrycontext
954 # wctx is not inclusive, but works because _ancestrycontext
953 # is used to test filelog revisions
955 # is used to test filelog revisions
954 ac = cl.ancestors([p.rev() for p in base.parents()],
956 ac = cl.ancestors([p.rev() for p in base.parents()],
955 inclusive=True)
957 inclusive=True)
956 else:
958 else:
957 ac = cl.ancestors([introrev], inclusive=True)
959 ac = cl.ancestors([introrev], inclusive=True)
958 base._ancestrycontext = ac
960 base._ancestrycontext = ac
959
961
960 # This algorithm would prefer to be recursive, but Python is a
962 # This algorithm would prefer to be recursive, but Python is a
961 # bit recursion-hostile. Instead we do an iterative
963 # bit recursion-hostile. Instead we do an iterative
962 # depth-first search.
964 # depth-first search.
963
965
964 visit = [base]
966 visit = [base]
965 hist = {}
967 hist = {}
966 pcache = {}
968 pcache = {}
967 needed = {base: 1}
969 needed = {base: 1}
968 while visit:
970 while visit:
969 f = visit[-1]
971 f = visit[-1]
970 pcached = f in pcache
972 pcached = f in pcache
971 if not pcached:
973 if not pcached:
972 pcache[f] = parents(f)
974 pcache[f] = parents(f)
973
975
974 ready = True
976 ready = True
975 pl = pcache[f]
977 pl = pcache[f]
976 for p in pl:
978 for p in pl:
977 if p not in hist:
979 if p not in hist:
978 ready = False
980 ready = False
979 visit.append(p)
981 visit.append(p)
980 if not pcached:
982 if not pcached:
981 needed[p] = needed.get(p, 0) + 1
983 needed[p] = needed.get(p, 0) + 1
982 if ready:
984 if ready:
983 visit.pop()
985 visit.pop()
984 reusable = f in hist
986 reusable = f in hist
985 if reusable:
987 if reusable:
986 curr = hist[f]
988 curr = hist[f]
987 else:
989 else:
988 curr = decorate(f.data(), f)
990 curr = decorate(f.data(), f)
989 for p in pl:
991 for p in pl:
990 if not reusable:
992 if not reusable:
991 curr = pair(hist[p], curr)
993 curr = pair(hist[p], curr)
992 if needed[p] == 1:
994 if needed[p] == 1:
993 del hist[p]
995 del hist[p]
994 del needed[p]
996 del needed[p]
995 else:
997 else:
996 needed[p] -= 1
998 needed[p] -= 1
997
999
998 hist[f] = curr
1000 hist[f] = curr
999 pcache[f] = []
1001 pcache[f] = []
1000
1002
1001 return zip(hist[base][0], hist[base][1].splitlines(True))
1003 return zip(hist[base][0], hist[base][1].splitlines(True))
1002
1004
1003 def ancestors(self, followfirst=False):
1005 def ancestors(self, followfirst=False):
1004 visit = {}
1006 visit = {}
1005 c = self
1007 c = self
1006 if followfirst:
1008 if followfirst:
1007 cut = 1
1009 cut = 1
1008 else:
1010 else:
1009 cut = None
1011 cut = None
1010
1012
1011 while True:
1013 while True:
1012 for parent in c.parents()[:cut]:
1014 for parent in c.parents()[:cut]:
1013 visit[(parent.linkrev(), parent.filenode())] = parent
1015 visit[(parent.linkrev(), parent.filenode())] = parent
1014 if not visit:
1016 if not visit:
1015 break
1017 break
1016 c = visit.pop(max(visit))
1018 c = visit.pop(max(visit))
1017 yield c
1019 yield c
1018
1020
1019 class filectx(basefilectx):
1021 class filectx(basefilectx):
1020 """A filecontext object makes access to data related to a particular
1022 """A filecontext object makes access to data related to a particular
1021 filerevision convenient."""
1023 filerevision convenient."""
1022 def __init__(self, repo, path, changeid=None, fileid=None,
1024 def __init__(self, repo, path, changeid=None, fileid=None,
1023 filelog=None, changectx=None):
1025 filelog=None, changectx=None):
1024 """changeid can be a changeset revision, node, or tag.
1026 """changeid can be a changeset revision, node, or tag.
1025 fileid can be a file revision or node."""
1027 fileid can be a file revision or node."""
1026 self._repo = repo
1028 self._repo = repo
1027 self._path = path
1029 self._path = path
1028
1030
1029 assert (changeid is not None
1031 assert (changeid is not None
1030 or fileid is not None
1032 or fileid is not None
1031 or changectx is not None), \
1033 or changectx is not None), \
1032 ("bad args: changeid=%r, fileid=%r, changectx=%r"
1034 ("bad args: changeid=%r, fileid=%r, changectx=%r"
1033 % (changeid, fileid, changectx))
1035 % (changeid, fileid, changectx))
1034
1036
1035 if filelog is not None:
1037 if filelog is not None:
1036 self._filelog = filelog
1038 self._filelog = filelog
1037
1039
1038 if changeid is not None:
1040 if changeid is not None:
1039 self._changeid = changeid
1041 self._changeid = changeid
1040 if changectx is not None:
1042 if changectx is not None:
1041 self._changectx = changectx
1043 self._changectx = changectx
1042 if fileid is not None:
1044 if fileid is not None:
1043 self._fileid = fileid
1045 self._fileid = fileid
1044
1046
1045 @propertycache
1047 @propertycache
1046 def _changectx(self):
1048 def _changectx(self):
1047 try:
1049 try:
1048 return changectx(self._repo, self._changeid)
1050 return changectx(self._repo, self._changeid)
1049 except error.FilteredRepoLookupError:
1051 except error.FilteredRepoLookupError:
1050 # Linkrev may point to any revision in the repository. When the
1052 # Linkrev may point to any revision in the repository. When the
1051 # repository is filtered this may lead to `filectx` trying to build
1053 # repository is filtered this may lead to `filectx` trying to build
1052 # `changectx` for filtered revision. In such case we fallback to
1054 # `changectx` for filtered revision. In such case we fallback to
1053 # creating `changectx` on the unfiltered version of the reposition.
1055 # creating `changectx` on the unfiltered version of the reposition.
1054 # This fallback should not be an issue because `changectx` from
1056 # This fallback should not be an issue because `changectx` from
1055 # `filectx` are not used in complex operations that care about
1057 # `filectx` are not used in complex operations that care about
1056 # filtering.
1058 # filtering.
1057 #
1059 #
1058 # This fallback is a cheap and dirty fix that prevent several
1060 # This fallback is a cheap and dirty fix that prevent several
1059 # crashes. It does not ensure the behavior is correct. However the
1061 # crashes. It does not ensure the behavior is correct. However the
1060 # behavior was not correct before filtering either and "incorrect
1062 # behavior was not correct before filtering either and "incorrect
1061 # behavior" is seen as better as "crash"
1063 # behavior" is seen as better as "crash"
1062 #
1064 #
1063 # Linkrevs have several serious troubles with filtering that are
1065 # Linkrevs have several serious troubles with filtering that are
1064 # complicated to solve. Proper handling of the issue here should be
1066 # complicated to solve. Proper handling of the issue here should be
1065 # considered when solving linkrev issue are on the table.
1067 # considered when solving linkrev issue are on the table.
1066 return changectx(self._repo.unfiltered(), self._changeid)
1068 return changectx(self._repo.unfiltered(), self._changeid)
1067
1069
1068 def filectx(self, fileid, changeid=None):
1070 def filectx(self, fileid, changeid=None):
1069 '''opens an arbitrary revision of the file without
1071 '''opens an arbitrary revision of the file without
1070 opening a new filelog'''
1072 opening a new filelog'''
1071 return filectx(self._repo, self._path, fileid=fileid,
1073 return filectx(self._repo, self._path, fileid=fileid,
1072 filelog=self._filelog, changeid=changeid)
1074 filelog=self._filelog, changeid=changeid)
1073
1075
1074 def data(self):
1076 def data(self):
1075 try:
1077 try:
1076 return self._filelog.read(self._filenode)
1078 return self._filelog.read(self._filenode)
1077 except error.CensoredNodeError:
1079 except error.CensoredNodeError:
1078 if self._repo.ui.config("censor", "policy", "abort") == "ignore":
1080 if self._repo.ui.config("censor", "policy", "abort") == "ignore":
1079 return ""
1081 return ""
1080 raise error.Abort(_("censored node: %s") % short(self._filenode),
1082 raise error.Abort(_("censored node: %s") % short(self._filenode),
1081 hint=_("set censor.policy to ignore errors"))
1083 hint=_("set censor.policy to ignore errors"))
1082
1084
1083 def size(self):
1085 def size(self):
1084 return self._filelog.size(self._filerev)
1086 return self._filelog.size(self._filerev)
1085
1087
1086 def renamed(self):
1088 def renamed(self):
1087 """check if file was actually renamed in this changeset revision
1089 """check if file was actually renamed in this changeset revision
1088
1090
1089 If rename logged in file revision, we report copy for changeset only
1091 If rename logged in file revision, we report copy for changeset only
1090 if file revisions linkrev points back to the changeset in question
1092 if file revisions linkrev points back to the changeset in question
1091 or both changeset parents contain different file revisions.
1093 or both changeset parents contain different file revisions.
1092 """
1094 """
1093
1095
1094 renamed = self._filelog.renamed(self._filenode)
1096 renamed = self._filelog.renamed(self._filenode)
1095 if not renamed:
1097 if not renamed:
1096 return renamed
1098 return renamed
1097
1099
1098 if self.rev() == self.linkrev():
1100 if self.rev() == self.linkrev():
1099 return renamed
1101 return renamed
1100
1102
1101 name = self.path()
1103 name = self.path()
1102 fnode = self._filenode
1104 fnode = self._filenode
1103 for p in self._changectx.parents():
1105 for p in self._changectx.parents():
1104 try:
1106 try:
1105 if fnode == p.filenode(name):
1107 if fnode == p.filenode(name):
1106 return None
1108 return None
1107 except error.LookupError:
1109 except error.LookupError:
1108 pass
1110 pass
1109 return renamed
1111 return renamed
1110
1112
1111 def children(self):
1113 def children(self):
1112 # hard for renames
1114 # hard for renames
1113 c = self._filelog.children(self._filenode)
1115 c = self._filelog.children(self._filenode)
1114 return [filectx(self._repo, self._path, fileid=x,
1116 return [filectx(self._repo, self._path, fileid=x,
1115 filelog=self._filelog) for x in c]
1117 filelog=self._filelog) for x in c]
1116
1118
1117 class committablectx(basectx):
1119 class committablectx(basectx):
1118 """A committablectx object provides common functionality for a context that
1120 """A committablectx object provides common functionality for a context that
1119 wants the ability to commit, e.g. workingctx or memctx."""
1121 wants the ability to commit, e.g. workingctx or memctx."""
1120 def __init__(self, repo, text="", user=None, date=None, extra=None,
1122 def __init__(self, repo, text="", user=None, date=None, extra=None,
1121 changes=None):
1123 changes=None):
1122 self._repo = repo
1124 self._repo = repo
1123 self._rev = None
1125 self._rev = None
1124 self._node = None
1126 self._node = None
1125 self._text = text
1127 self._text = text
1126 if date:
1128 if date:
1127 self._date = util.parsedate(date)
1129 self._date = util.parsedate(date)
1128 if user:
1130 if user:
1129 self._user = user
1131 self._user = user
1130 if changes:
1132 if changes:
1131 self._status = changes
1133 self._status = changes
1132
1134
1133 self._extra = {}
1135 self._extra = {}
1134 if extra:
1136 if extra:
1135 self._extra = extra.copy()
1137 self._extra = extra.copy()
1136 if 'branch' not in self._extra:
1138 if 'branch' not in self._extra:
1137 try:
1139 try:
1138 branch = encoding.fromlocal(self._repo.dirstate.branch())
1140 branch = encoding.fromlocal(self._repo.dirstate.branch())
1139 except UnicodeDecodeError:
1141 except UnicodeDecodeError:
1140 raise error.Abort(_('branch name not in UTF-8!'))
1142 raise error.Abort(_('branch name not in UTF-8!'))
1141 self._extra['branch'] = branch
1143 self._extra['branch'] = branch
1142 if self._extra['branch'] == '':
1144 if self._extra['branch'] == '':
1143 self._extra['branch'] = 'default'
1145 self._extra['branch'] = 'default'
1144
1146
1145 def __str__(self):
1147 def __str__(self):
1146 return str(self._parents[0]) + "+"
1148 return str(self._parents[0]) + "+"
1147
1149
1148 def __nonzero__(self):
1150 def __nonzero__(self):
1149 return True
1151 return True
1150
1152
1151 def _buildflagfunc(self):
1153 def _buildflagfunc(self):
1152 # Create a fallback function for getting file flags when the
1154 # Create a fallback function for getting file flags when the
1153 # filesystem doesn't support them
1155 # filesystem doesn't support them
1154
1156
1155 copiesget = self._repo.dirstate.copies().get
1157 copiesget = self._repo.dirstate.copies().get
1156 parents = self.parents()
1158 parents = self.parents()
1157 if len(parents) < 2:
1159 if len(parents) < 2:
1158 # when we have one parent, it's easy: copy from parent
1160 # when we have one parent, it's easy: copy from parent
1159 man = parents[0].manifest()
1161 man = parents[0].manifest()
1160 def func(f):
1162 def func(f):
1161 f = copiesget(f, f)
1163 f = copiesget(f, f)
1162 return man.flags(f)
1164 return man.flags(f)
1163 else:
1165 else:
1164 # merges are tricky: we try to reconstruct the unstored
1166 # merges are tricky: we try to reconstruct the unstored
1165 # result from the merge (issue1802)
1167 # result from the merge (issue1802)
1166 p1, p2 = parents
1168 p1, p2 = parents
1167 pa = p1.ancestor(p2)
1169 pa = p1.ancestor(p2)
1168 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
1170 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
1169
1171
1170 def func(f):
1172 def func(f):
1171 f = copiesget(f, f) # may be wrong for merges with copies
1173 f = copiesget(f, f) # may be wrong for merges with copies
1172 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
1174 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
1173 if fl1 == fl2:
1175 if fl1 == fl2:
1174 return fl1
1176 return fl1
1175 if fl1 == fla:
1177 if fl1 == fla:
1176 return fl2
1178 return fl2
1177 if fl2 == fla:
1179 if fl2 == fla:
1178 return fl1
1180 return fl1
1179 return '' # punt for conflicts
1181 return '' # punt for conflicts
1180
1182
1181 return func
1183 return func
1182
1184
1183 @propertycache
1185 @propertycache
1184 def _flagfunc(self):
1186 def _flagfunc(self):
1185 return self._repo.dirstate.flagfunc(self._buildflagfunc)
1187 return self._repo.dirstate.flagfunc(self._buildflagfunc)
1186
1188
1187 @propertycache
1189 @propertycache
1188 def _manifest(self):
1190 def _manifest(self):
1189 """generate a manifest corresponding to the values in self._status
1191 """generate a manifest corresponding to the values in self._status
1190
1192
1191 This reuse the file nodeid from parent, but we append an extra letter
1193 This reuse the file nodeid from parent, but we append an extra letter
1192 when modified. Modified files get an extra 'm' while added files get
1194 when modified. Modified files get an extra 'm' while added files get
1193 an extra 'a'. This is used by manifests merge to see that files
1195 an extra 'a'. This is used by manifests merge to see that files
1194 are different and by update logic to avoid deleting newly added files.
1196 are different and by update logic to avoid deleting newly added files.
1195 """
1197 """
1196 parents = self.parents()
1198 parents = self.parents()
1197
1199
1198 man1 = parents[0].manifest()
1200 man1 = parents[0].manifest()
1199 man = man1.copy()
1201 man = man1.copy()
1200 if len(parents) > 1:
1202 if len(parents) > 1:
1201 man2 = self.p2().manifest()
1203 man2 = self.p2().manifest()
1202 def getman(f):
1204 def getman(f):
1203 if f in man1:
1205 if f in man1:
1204 return man1
1206 return man1
1205 return man2
1207 return man2
1206 else:
1208 else:
1207 getman = lambda f: man1
1209 getman = lambda f: man1
1208
1210
1209 copied = self._repo.dirstate.copies()
1211 copied = self._repo.dirstate.copies()
1210 ff = self._flagfunc
1212 ff = self._flagfunc
1211 for i, l in (("a", self._status.added), ("m", self._status.modified)):
1213 for i, l in (("a", self._status.added), ("m", self._status.modified)):
1212 for f in l:
1214 for f in l:
1213 orig = copied.get(f, f)
1215 orig = copied.get(f, f)
1214 man[f] = getman(orig).get(orig, nullid) + i
1216 man[f] = getman(orig).get(orig, nullid) + i
1215 try:
1217 try:
1216 man.setflag(f, ff(f))
1218 man.setflag(f, ff(f))
1217 except OSError:
1219 except OSError:
1218 pass
1220 pass
1219
1221
1220 for f in self._status.deleted + self._status.removed:
1222 for f in self._status.deleted + self._status.removed:
1221 if f in man:
1223 if f in man:
1222 del man[f]
1224 del man[f]
1223
1225
1224 return man
1226 return man
1225
1227
1226 @propertycache
1228 @propertycache
1227 def _status(self):
1229 def _status(self):
1228 return self._repo.status()
1230 return self._repo.status()
1229
1231
1230 @propertycache
1232 @propertycache
1231 def _user(self):
1233 def _user(self):
1232 return self._repo.ui.username()
1234 return self._repo.ui.username()
1233
1235
1234 @propertycache
1236 @propertycache
1235 def _date(self):
1237 def _date(self):
1236 return util.makedate()
1238 return util.makedate()
1237
1239
1238 def subrev(self, subpath):
1240 def subrev(self, subpath):
1239 return None
1241 return None
1240
1242
1241 def manifestnode(self):
1243 def manifestnode(self):
1242 return None
1244 return None
1243 def user(self):
1245 def user(self):
1244 return self._user or self._repo.ui.username()
1246 return self._user or self._repo.ui.username()
1245 def date(self):
1247 def date(self):
1246 return self._date
1248 return self._date
1247 def description(self):
1249 def description(self):
1248 return self._text
1250 return self._text
1249 def files(self):
1251 def files(self):
1250 return sorted(self._status.modified + self._status.added +
1252 return sorted(self._status.modified + self._status.added +
1251 self._status.removed)
1253 self._status.removed)
1252
1254
1253 def modified(self):
1255 def modified(self):
1254 return self._status.modified
1256 return self._status.modified
1255 def added(self):
1257 def added(self):
1256 return self._status.added
1258 return self._status.added
1257 def removed(self):
1259 def removed(self):
1258 return self._status.removed
1260 return self._status.removed
1259 def deleted(self):
1261 def deleted(self):
1260 return self._status.deleted
1262 return self._status.deleted
1261 def branch(self):
1263 def branch(self):
1262 return encoding.tolocal(self._extra['branch'])
1264 return encoding.tolocal(self._extra['branch'])
1263 def closesbranch(self):
1265 def closesbranch(self):
1264 return 'close' in self._extra
1266 return 'close' in self._extra
1265 def extra(self):
1267 def extra(self):
1266 return self._extra
1268 return self._extra
1267
1269
1268 def tags(self):
1270 def tags(self):
1269 return []
1271 return []
1270
1272
1271 def bookmarks(self):
1273 def bookmarks(self):
1272 b = []
1274 b = []
1273 for p in self.parents():
1275 for p in self.parents():
1274 b.extend(p.bookmarks())
1276 b.extend(p.bookmarks())
1275 return b
1277 return b
1276
1278
1277 def phase(self):
1279 def phase(self):
1278 phase = phases.draft # default phase to draft
1280 phase = phases.draft # default phase to draft
1279 for p in self.parents():
1281 for p in self.parents():
1280 phase = max(phase, p.phase())
1282 phase = max(phase, p.phase())
1281 return phase
1283 return phase
1282
1284
1283 def hidden(self):
1285 def hidden(self):
1284 return False
1286 return False
1285
1287
1286 def children(self):
1288 def children(self):
1287 return []
1289 return []
1288
1290
1289 def flags(self, path):
1291 def flags(self, path):
1290 if '_manifest' in self.__dict__:
1292 if '_manifest' in self.__dict__:
1291 try:
1293 try:
1292 return self._manifest.flags(path)
1294 return self._manifest.flags(path)
1293 except KeyError:
1295 except KeyError:
1294 return ''
1296 return ''
1295
1297
1296 try:
1298 try:
1297 return self._flagfunc(path)
1299 return self._flagfunc(path)
1298 except OSError:
1300 except OSError:
1299 return ''
1301 return ''
1300
1302
1301 def ancestor(self, c2):
1303 def ancestor(self, c2):
1302 """return the "best" ancestor context of self and c2"""
1304 """return the "best" ancestor context of self and c2"""
1303 return self._parents[0].ancestor(c2) # punt on two parents for now
1305 return self._parents[0].ancestor(c2) # punt on two parents for now
1304
1306
1305 def walk(self, match):
1307 def walk(self, match):
1306 '''Generates matching file names.'''
1308 '''Generates matching file names.'''
1307 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1309 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1308 True, False))
1310 True, False))
1309
1311
1310 def matches(self, match):
1312 def matches(self, match):
1311 return sorted(self._repo.dirstate.matches(match))
1313 return sorted(self._repo.dirstate.matches(match))
1312
1314
1313 def ancestors(self):
1315 def ancestors(self):
1314 for p in self._parents:
1316 for p in self._parents:
1315 yield p
1317 yield p
1316 for a in self._repo.changelog.ancestors(
1318 for a in self._repo.changelog.ancestors(
1317 [p.rev() for p in self._parents]):
1319 [p.rev() for p in self._parents]):
1318 yield changectx(self._repo, a)
1320 yield changectx(self._repo, a)
1319
1321
1320 def markcommitted(self, node):
1322 def markcommitted(self, node):
1321 """Perform post-commit cleanup necessary after committing this ctx
1323 """Perform post-commit cleanup necessary after committing this ctx
1322
1324
1323 Specifically, this updates backing stores this working context
1325 Specifically, this updates backing stores this working context
1324 wraps to reflect the fact that the changes reflected by this
1326 wraps to reflect the fact that the changes reflected by this
1325 workingctx have been committed. For example, it marks
1327 workingctx have been committed. For example, it marks
1326 modified and added files as normal in the dirstate.
1328 modified and added files as normal in the dirstate.
1327
1329
1328 """
1330 """
1329
1331
1330 self._repo.dirstate.beginparentchange()
1332 self._repo.dirstate.beginparentchange()
1331 for f in self.modified() + self.added():
1333 for f in self.modified() + self.added():
1332 self._repo.dirstate.normal(f)
1334 self._repo.dirstate.normal(f)
1333 for f in self.removed():
1335 for f in self.removed():
1334 self._repo.dirstate.drop(f)
1336 self._repo.dirstate.drop(f)
1335 self._repo.dirstate.setparents(node)
1337 self._repo.dirstate.setparents(node)
1336 self._repo.dirstate.endparentchange()
1338 self._repo.dirstate.endparentchange()
1337
1339
1338 # write changes out explicitly, because nesting wlock at
1340 # write changes out explicitly, because nesting wlock at
1339 # runtime may prevent 'wlock.release()' in 'repo.commit()'
1341 # runtime may prevent 'wlock.release()' in 'repo.commit()'
1340 # from immediately doing so for subsequent changing files
1342 # from immediately doing so for subsequent changing files
1341 self._repo.dirstate.write(self._repo.currenttransaction())
1343 self._repo.dirstate.write(self._repo.currenttransaction())
1342
1344
1343 class workingctx(committablectx):
1345 class workingctx(committablectx):
1344 """A workingctx object makes access to data related to
1346 """A workingctx object makes access to data related to
1345 the current working directory convenient.
1347 the current working directory convenient.
1346 date - any valid date string or (unixtime, offset), or None.
1348 date - any valid date string or (unixtime, offset), or None.
1347 user - username string, or None.
1349 user - username string, or None.
1348 extra - a dictionary of extra values, or None.
1350 extra - a dictionary of extra values, or None.
1349 changes - a list of file lists as returned by localrepo.status()
1351 changes - a list of file lists as returned by localrepo.status()
1350 or None to use the repository status.
1352 or None to use the repository status.
1351 """
1353 """
1352 def __init__(self, repo, text="", user=None, date=None, extra=None,
1354 def __init__(self, repo, text="", user=None, date=None, extra=None,
1353 changes=None):
1355 changes=None):
1354 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1356 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1355
1357
1356 def __iter__(self):
1358 def __iter__(self):
1357 d = self._repo.dirstate
1359 d = self._repo.dirstate
1358 for f in d:
1360 for f in d:
1359 if d[f] != 'r':
1361 if d[f] != 'r':
1360 yield f
1362 yield f
1361
1363
1362 def __contains__(self, key):
1364 def __contains__(self, key):
1363 return self._repo.dirstate[key] not in "?r"
1365 return self._repo.dirstate[key] not in "?r"
1364
1366
1365 def hex(self):
1367 def hex(self):
1366 return hex(wdirid)
1368 return hex(wdirid)
1367
1369
1368 @propertycache
1370 @propertycache
1369 def _parents(self):
1371 def _parents(self):
1370 p = self._repo.dirstate.parents()
1372 p = self._repo.dirstate.parents()
1371 if p[1] == nullid:
1373 if p[1] == nullid:
1372 p = p[:-1]
1374 p = p[:-1]
1373 return [changectx(self._repo, x) for x in p]
1375 return [changectx(self._repo, x) for x in p]
1374
1376
1375 def filectx(self, path, filelog=None):
1377 def filectx(self, path, filelog=None):
1376 """get a file context from the working directory"""
1378 """get a file context from the working directory"""
1377 return workingfilectx(self._repo, path, workingctx=self,
1379 return workingfilectx(self._repo, path, workingctx=self,
1378 filelog=filelog)
1380 filelog=filelog)
1379
1381
1380 def dirty(self, missing=False, merge=True, branch=True):
1382 def dirty(self, missing=False, merge=True, branch=True):
1381 "check whether a working directory is modified"
1383 "check whether a working directory is modified"
1382 # check subrepos first
1384 # check subrepos first
1383 for s in sorted(self.substate):
1385 for s in sorted(self.substate):
1384 if self.sub(s).dirty():
1386 if self.sub(s).dirty():
1385 return True
1387 return True
1386 # check current working dir
1388 # check current working dir
1387 return ((merge and self.p2()) or
1389 return ((merge and self.p2()) or
1388 (branch and self.branch() != self.p1().branch()) or
1390 (branch and self.branch() != self.p1().branch()) or
1389 self.modified() or self.added() or self.removed() or
1391 self.modified() or self.added() or self.removed() or
1390 (missing and self.deleted()))
1392 (missing and self.deleted()))
1391
1393
1392 def add(self, list, prefix=""):
1394 def add(self, list, prefix=""):
1393 join = lambda f: os.path.join(prefix, f)
1395 join = lambda f: os.path.join(prefix, f)
1394 wlock = self._repo.wlock()
1396 wlock = self._repo.wlock()
1395 ui, ds = self._repo.ui, self._repo.dirstate
1397 ui, ds = self._repo.ui, self._repo.dirstate
1396 try:
1398 try:
1397 rejected = []
1399 rejected = []
1398 lstat = self._repo.wvfs.lstat
1400 lstat = self._repo.wvfs.lstat
1399 for f in list:
1401 for f in list:
1400 scmutil.checkportable(ui, join(f))
1402 scmutil.checkportable(ui, join(f))
1401 try:
1403 try:
1402 st = lstat(f)
1404 st = lstat(f)
1403 except OSError:
1405 except OSError:
1404 ui.warn(_("%s does not exist!\n") % join(f))
1406 ui.warn(_("%s does not exist!\n") % join(f))
1405 rejected.append(f)
1407 rejected.append(f)
1406 continue
1408 continue
1407 if st.st_size > 10000000:
1409 if st.st_size > 10000000:
1408 ui.warn(_("%s: up to %d MB of RAM may be required "
1410 ui.warn(_("%s: up to %d MB of RAM may be required "
1409 "to manage this file\n"
1411 "to manage this file\n"
1410 "(use 'hg revert %s' to cancel the "
1412 "(use 'hg revert %s' to cancel the "
1411 "pending addition)\n")
1413 "pending addition)\n")
1412 % (f, 3 * st.st_size // 1000000, join(f)))
1414 % (f, 3 * st.st_size // 1000000, join(f)))
1413 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1415 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1414 ui.warn(_("%s not added: only files and symlinks "
1416 ui.warn(_("%s not added: only files and symlinks "
1415 "supported currently\n") % join(f))
1417 "supported currently\n") % join(f))
1416 rejected.append(f)
1418 rejected.append(f)
1417 elif ds[f] in 'amn':
1419 elif ds[f] in 'amn':
1418 ui.warn(_("%s already tracked!\n") % join(f))
1420 ui.warn(_("%s already tracked!\n") % join(f))
1419 elif ds[f] == 'r':
1421 elif ds[f] == 'r':
1420 ds.normallookup(f)
1422 ds.normallookup(f)
1421 else:
1423 else:
1422 ds.add(f)
1424 ds.add(f)
1423 return rejected
1425 return rejected
1424 finally:
1426 finally:
1425 wlock.release()
1427 wlock.release()
1426
1428
1427 def forget(self, files, prefix=""):
1429 def forget(self, files, prefix=""):
1428 join = lambda f: os.path.join(prefix, f)
1430 join = lambda f: os.path.join(prefix, f)
1429 wlock = self._repo.wlock()
1431 wlock = self._repo.wlock()
1430 try:
1432 try:
1431 rejected = []
1433 rejected = []
1432 for f in files:
1434 for f in files:
1433 if f not in self._repo.dirstate:
1435 if f not in self._repo.dirstate:
1434 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1436 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1435 rejected.append(f)
1437 rejected.append(f)
1436 elif self._repo.dirstate[f] != 'a':
1438 elif self._repo.dirstate[f] != 'a':
1437 self._repo.dirstate.remove(f)
1439 self._repo.dirstate.remove(f)
1438 else:
1440 else:
1439 self._repo.dirstate.drop(f)
1441 self._repo.dirstate.drop(f)
1440 return rejected
1442 return rejected
1441 finally:
1443 finally:
1442 wlock.release()
1444 wlock.release()
1443
1445
1444 def undelete(self, list):
1446 def undelete(self, list):
1445 pctxs = self.parents()
1447 pctxs = self.parents()
1446 wlock = self._repo.wlock()
1448 wlock = self._repo.wlock()
1447 try:
1449 try:
1448 for f in list:
1450 for f in list:
1449 if self._repo.dirstate[f] != 'r':
1451 if self._repo.dirstate[f] != 'r':
1450 self._repo.ui.warn(_("%s not removed!\n") % f)
1452 self._repo.ui.warn(_("%s not removed!\n") % f)
1451 else:
1453 else:
1452 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1454 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1453 t = fctx.data()
1455 t = fctx.data()
1454 self._repo.wwrite(f, t, fctx.flags())
1456 self._repo.wwrite(f, t, fctx.flags())
1455 self._repo.dirstate.normal(f)
1457 self._repo.dirstate.normal(f)
1456 finally:
1458 finally:
1457 wlock.release()
1459 wlock.release()
1458
1460
1459 def copy(self, source, dest):
1461 def copy(self, source, dest):
1460 try:
1462 try:
1461 st = self._repo.wvfs.lstat(dest)
1463 st = self._repo.wvfs.lstat(dest)
1462 except OSError as err:
1464 except OSError as err:
1463 if err.errno != errno.ENOENT:
1465 if err.errno != errno.ENOENT:
1464 raise
1466 raise
1465 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1467 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1466 return
1468 return
1467 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1469 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1468 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1470 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1469 "symbolic link\n") % dest)
1471 "symbolic link\n") % dest)
1470 else:
1472 else:
1471 wlock = self._repo.wlock()
1473 wlock = self._repo.wlock()
1472 try:
1474 try:
1473 if self._repo.dirstate[dest] in '?':
1475 if self._repo.dirstate[dest] in '?':
1474 self._repo.dirstate.add(dest)
1476 self._repo.dirstate.add(dest)
1475 elif self._repo.dirstate[dest] in 'r':
1477 elif self._repo.dirstate[dest] in 'r':
1476 self._repo.dirstate.normallookup(dest)
1478 self._repo.dirstate.normallookup(dest)
1477 self._repo.dirstate.copy(source, dest)
1479 self._repo.dirstate.copy(source, dest)
1478 finally:
1480 finally:
1479 wlock.release()
1481 wlock.release()
1480
1482
1481 def match(self, pats=[], include=None, exclude=None, default='glob',
1483 def match(self, pats=[], include=None, exclude=None, default='glob',
1482 listsubrepos=False, badfn=None):
1484 listsubrepos=False, badfn=None):
1483 r = self._repo
1485 r = self._repo
1484
1486
1485 # Only a case insensitive filesystem needs magic to translate user input
1487 # Only a case insensitive filesystem needs magic to translate user input
1486 # to actual case in the filesystem.
1488 # to actual case in the filesystem.
1487 if not util.checkcase(r.root):
1489 if not util.checkcase(r.root):
1488 return matchmod.icasefsmatcher(r.root, r.getcwd(), pats, include,
1490 return matchmod.icasefsmatcher(r.root, r.getcwd(), pats, include,
1489 exclude, default, r.auditor, self,
1491 exclude, default, r.auditor, self,
1490 listsubrepos=listsubrepos,
1492 listsubrepos=listsubrepos,
1491 badfn=badfn)
1493 badfn=badfn)
1492 return matchmod.match(r.root, r.getcwd(), pats,
1494 return matchmod.match(r.root, r.getcwd(), pats,
1493 include, exclude, default,
1495 include, exclude, default,
1494 auditor=r.auditor, ctx=self,
1496 auditor=r.auditor, ctx=self,
1495 listsubrepos=listsubrepos, badfn=badfn)
1497 listsubrepos=listsubrepos, badfn=badfn)
1496
1498
1497 def _filtersuspectsymlink(self, files):
1499 def _filtersuspectsymlink(self, files):
1498 if not files or self._repo.dirstate._checklink:
1500 if not files or self._repo.dirstate._checklink:
1499 return files
1501 return files
1500
1502
1501 # Symlink placeholders may get non-symlink-like contents
1503 # Symlink placeholders may get non-symlink-like contents
1502 # via user error or dereferencing by NFS or Samba servers,
1504 # via user error or dereferencing by NFS or Samba servers,
1503 # so we filter out any placeholders that don't look like a
1505 # so we filter out any placeholders that don't look like a
1504 # symlink
1506 # symlink
1505 sane = []
1507 sane = []
1506 for f in files:
1508 for f in files:
1507 if self.flags(f) == 'l':
1509 if self.flags(f) == 'l':
1508 d = self[f].data()
1510 d = self[f].data()
1509 if d == '' or len(d) >= 1024 or '\n' in d or util.binary(d):
1511 if d == '' or len(d) >= 1024 or '\n' in d or util.binary(d):
1510 self._repo.ui.debug('ignoring suspect symlink placeholder'
1512 self._repo.ui.debug('ignoring suspect symlink placeholder'
1511 ' "%s"\n' % f)
1513 ' "%s"\n' % f)
1512 continue
1514 continue
1513 sane.append(f)
1515 sane.append(f)
1514 return sane
1516 return sane
1515
1517
1516 def _checklookup(self, files):
1518 def _checklookup(self, files):
1517 # check for any possibly clean files
1519 # check for any possibly clean files
1518 if not files:
1520 if not files:
1519 return [], []
1521 return [], []
1520
1522
1521 modified = []
1523 modified = []
1522 fixup = []
1524 fixup = []
1523 pctx = self._parents[0]
1525 pctx = self._parents[0]
1524 # do a full compare of any files that might have changed
1526 # do a full compare of any files that might have changed
1525 for f in sorted(files):
1527 for f in sorted(files):
1526 if (f not in pctx or self.flags(f) != pctx.flags(f)
1528 if (f not in pctx or self.flags(f) != pctx.flags(f)
1527 or pctx[f].cmp(self[f])):
1529 or pctx[f].cmp(self[f])):
1528 modified.append(f)
1530 modified.append(f)
1529 else:
1531 else:
1530 fixup.append(f)
1532 fixup.append(f)
1531
1533
1532 # update dirstate for files that are actually clean
1534 # update dirstate for files that are actually clean
1533 if fixup:
1535 if fixup:
1534 try:
1536 try:
1535 # updating the dirstate is optional
1537 # updating the dirstate is optional
1536 # so we don't wait on the lock
1538 # so we don't wait on the lock
1537 # wlock can invalidate the dirstate, so cache normal _after_
1539 # wlock can invalidate the dirstate, so cache normal _after_
1538 # taking the lock
1540 # taking the lock
1539 wlock = self._repo.wlock(False)
1541 wlock = self._repo.wlock(False)
1540 normal = self._repo.dirstate.normal
1542 normal = self._repo.dirstate.normal
1541 try:
1543 try:
1542 for f in fixup:
1544 for f in fixup:
1543 normal(f)
1545 normal(f)
1544 # write changes out explicitly, because nesting
1546 # write changes out explicitly, because nesting
1545 # wlock at runtime may prevent 'wlock.release()'
1547 # wlock at runtime may prevent 'wlock.release()'
1546 # below from doing so for subsequent changing files
1548 # below from doing so for subsequent changing files
1547 self._repo.dirstate.write(self._repo.currenttransaction())
1549 self._repo.dirstate.write(self._repo.currenttransaction())
1548 finally:
1550 finally:
1549 wlock.release()
1551 wlock.release()
1550 except error.LockError:
1552 except error.LockError:
1551 pass
1553 pass
1552 return modified, fixup
1554 return modified, fixup
1553
1555
1554 def _manifestmatches(self, match, s):
1556 def _manifestmatches(self, match, s):
1555 """Slow path for workingctx
1557 """Slow path for workingctx
1556
1558
1557 The fast path is when we compare the working directory to its parent
1559 The fast path is when we compare the working directory to its parent
1558 which means this function is comparing with a non-parent; therefore we
1560 which means this function is comparing with a non-parent; therefore we
1559 need to build a manifest and return what matches.
1561 need to build a manifest and return what matches.
1560 """
1562 """
1561 mf = self._repo['.']._manifestmatches(match, s)
1563 mf = self._repo['.']._manifestmatches(match, s)
1562 for f in s.modified + s.added:
1564 for f in s.modified + s.added:
1563 mf[f] = _newnode
1565 mf[f] = _newnode
1564 mf.setflag(f, self.flags(f))
1566 mf.setflag(f, self.flags(f))
1565 for f in s.removed:
1567 for f in s.removed:
1566 if f in mf:
1568 if f in mf:
1567 del mf[f]
1569 del mf[f]
1568 return mf
1570 return mf
1569
1571
1570 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1572 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1571 unknown=False):
1573 unknown=False):
1572 '''Gets the status from the dirstate -- internal use only.'''
1574 '''Gets the status from the dirstate -- internal use only.'''
1573 listignored, listclean, listunknown = ignored, clean, unknown
1575 listignored, listclean, listunknown = ignored, clean, unknown
1574 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1576 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1575 subrepos = []
1577 subrepos = []
1576 if '.hgsub' in self:
1578 if '.hgsub' in self:
1577 subrepos = sorted(self.substate)
1579 subrepos = sorted(self.substate)
1578 cmp, s = self._repo.dirstate.status(match, subrepos, listignored,
1580 cmp, s = self._repo.dirstate.status(match, subrepos, listignored,
1579 listclean, listunknown)
1581 listclean, listunknown)
1580
1582
1581 # check for any possibly clean files
1583 # check for any possibly clean files
1582 if cmp:
1584 if cmp:
1583 modified2, fixup = self._checklookup(cmp)
1585 modified2, fixup = self._checklookup(cmp)
1584 s.modified.extend(modified2)
1586 s.modified.extend(modified2)
1585
1587
1586 # update dirstate for files that are actually clean
1588 # update dirstate for files that are actually clean
1587 if fixup and listclean:
1589 if fixup and listclean:
1588 s.clean.extend(fixup)
1590 s.clean.extend(fixup)
1589
1591
1590 if match.always():
1592 if match.always():
1591 # cache for performance
1593 # cache for performance
1592 if s.unknown or s.ignored or s.clean:
1594 if s.unknown or s.ignored or s.clean:
1593 # "_status" is cached with list*=False in the normal route
1595 # "_status" is cached with list*=False in the normal route
1594 self._status = scmutil.status(s.modified, s.added, s.removed,
1596 self._status = scmutil.status(s.modified, s.added, s.removed,
1595 s.deleted, [], [], [])
1597 s.deleted, [], [], [])
1596 else:
1598 else:
1597 self._status = s
1599 self._status = s
1598
1600
1599 return s
1601 return s
1600
1602
1601 def _buildstatus(self, other, s, match, listignored, listclean,
1603 def _buildstatus(self, other, s, match, listignored, listclean,
1602 listunknown):
1604 listunknown):
1603 """build a status with respect to another context
1605 """build a status with respect to another context
1604
1606
1605 This includes logic for maintaining the fast path of status when
1607 This includes logic for maintaining the fast path of status when
1606 comparing the working directory against its parent, which is to skip
1608 comparing the working directory against its parent, which is to skip
1607 building a new manifest if self (working directory) is not comparing
1609 building a new manifest if self (working directory) is not comparing
1608 against its parent (repo['.']).
1610 against its parent (repo['.']).
1609 """
1611 """
1610 s = self._dirstatestatus(match, listignored, listclean, listunknown)
1612 s = self._dirstatestatus(match, listignored, listclean, listunknown)
1611 # Filter out symlinks that, in the case of FAT32 and NTFS filesystems,
1613 # Filter out symlinks that, in the case of FAT32 and NTFS filesystems,
1612 # might have accidentally ended up with the entire contents of the file
1614 # might have accidentally ended up with the entire contents of the file
1613 # they are supposed to be linking to.
1615 # they are supposed to be linking to.
1614 s.modified[:] = self._filtersuspectsymlink(s.modified)
1616 s.modified[:] = self._filtersuspectsymlink(s.modified)
1615 if other != self._repo['.']:
1617 if other != self._repo['.']:
1616 s = super(workingctx, self)._buildstatus(other, s, match,
1618 s = super(workingctx, self)._buildstatus(other, s, match,
1617 listignored, listclean,
1619 listignored, listclean,
1618 listunknown)
1620 listunknown)
1619 return s
1621 return s
1620
1622
1621 def _matchstatus(self, other, match):
1623 def _matchstatus(self, other, match):
1622 """override the match method with a filter for directory patterns
1624 """override the match method with a filter for directory patterns
1623
1625
1624 We use inheritance to customize the match.bad method only in cases of
1626 We use inheritance to customize the match.bad method only in cases of
1625 workingctx since it belongs only to the working directory when
1627 workingctx since it belongs only to the working directory when
1626 comparing against the parent changeset.
1628 comparing against the parent changeset.
1627
1629
1628 If we aren't comparing against the working directory's parent, then we
1630 If we aren't comparing against the working directory's parent, then we
1629 just use the default match object sent to us.
1631 just use the default match object sent to us.
1630 """
1632 """
1631 superself = super(workingctx, self)
1633 superself = super(workingctx, self)
1632 match = superself._matchstatus(other, match)
1634 match = superself._matchstatus(other, match)
1633 if other != self._repo['.']:
1635 if other != self._repo['.']:
1634 def bad(f, msg):
1636 def bad(f, msg):
1635 # 'f' may be a directory pattern from 'match.files()',
1637 # 'f' may be a directory pattern from 'match.files()',
1636 # so 'f not in ctx1' is not enough
1638 # so 'f not in ctx1' is not enough
1637 if f not in other and not other.hasdir(f):
1639 if f not in other and not other.hasdir(f):
1638 self._repo.ui.warn('%s: %s\n' %
1640 self._repo.ui.warn('%s: %s\n' %
1639 (self._repo.dirstate.pathto(f), msg))
1641 (self._repo.dirstate.pathto(f), msg))
1640 match.bad = bad
1642 match.bad = bad
1641 return match
1643 return match
1642
1644
1643 class committablefilectx(basefilectx):
1645 class committablefilectx(basefilectx):
1644 """A committablefilectx provides common functionality for a file context
1646 """A committablefilectx provides common functionality for a file context
1645 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1647 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1646 def __init__(self, repo, path, filelog=None, ctx=None):
1648 def __init__(self, repo, path, filelog=None, ctx=None):
1647 self._repo = repo
1649 self._repo = repo
1648 self._path = path
1650 self._path = path
1649 self._changeid = None
1651 self._changeid = None
1650 self._filerev = self._filenode = None
1652 self._filerev = self._filenode = None
1651
1653
1652 if filelog is not None:
1654 if filelog is not None:
1653 self._filelog = filelog
1655 self._filelog = filelog
1654 if ctx:
1656 if ctx:
1655 self._changectx = ctx
1657 self._changectx = ctx
1656
1658
1657 def __nonzero__(self):
1659 def __nonzero__(self):
1658 return True
1660 return True
1659
1661
1660 def linkrev(self):
1662 def linkrev(self):
1661 # linked to self._changectx no matter if file is modified or not
1663 # linked to self._changectx no matter if file is modified or not
1662 return self.rev()
1664 return self.rev()
1663
1665
1664 def parents(self):
1666 def parents(self):
1665 '''return parent filectxs, following copies if necessary'''
1667 '''return parent filectxs, following copies if necessary'''
1666 def filenode(ctx, path):
1668 def filenode(ctx, path):
1667 return ctx._manifest.get(path, nullid)
1669 return ctx._manifest.get(path, nullid)
1668
1670
1669 path = self._path
1671 path = self._path
1670 fl = self._filelog
1672 fl = self._filelog
1671 pcl = self._changectx._parents
1673 pcl = self._changectx._parents
1672 renamed = self.renamed()
1674 renamed = self.renamed()
1673
1675
1674 if renamed:
1676 if renamed:
1675 pl = [renamed + (None,)]
1677 pl = [renamed + (None,)]
1676 else:
1678 else:
1677 pl = [(path, filenode(pcl[0], path), fl)]
1679 pl = [(path, filenode(pcl[0], path), fl)]
1678
1680
1679 for pc in pcl[1:]:
1681 for pc in pcl[1:]:
1680 pl.append((path, filenode(pc, path), fl))
1682 pl.append((path, filenode(pc, path), fl))
1681
1683
1682 return [self._parentfilectx(p, fileid=n, filelog=l)
1684 return [self._parentfilectx(p, fileid=n, filelog=l)
1683 for p, n, l in pl if n != nullid]
1685 for p, n, l in pl if n != nullid]
1684
1686
1685 def children(self):
1687 def children(self):
1686 return []
1688 return []
1687
1689
1688 class workingfilectx(committablefilectx):
1690 class workingfilectx(committablefilectx):
1689 """A workingfilectx object makes access to data related to a particular
1691 """A workingfilectx object makes access to data related to a particular
1690 file in the working directory convenient."""
1692 file in the working directory convenient."""
1691 def __init__(self, repo, path, filelog=None, workingctx=None):
1693 def __init__(self, repo, path, filelog=None, workingctx=None):
1692 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1694 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1693
1695
1694 @propertycache
1696 @propertycache
1695 def _changectx(self):
1697 def _changectx(self):
1696 return workingctx(self._repo)
1698 return workingctx(self._repo)
1697
1699
1698 def data(self):
1700 def data(self):
1699 return self._repo.wread(self._path)
1701 return self._repo.wread(self._path)
1700 def renamed(self):
1702 def renamed(self):
1701 rp = self._repo.dirstate.copied(self._path)
1703 rp = self._repo.dirstate.copied(self._path)
1702 if not rp:
1704 if not rp:
1703 return None
1705 return None
1704 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1706 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1705
1707
1706 def size(self):
1708 def size(self):
1707 return self._repo.wvfs.lstat(self._path).st_size
1709 return self._repo.wvfs.lstat(self._path).st_size
1708 def date(self):
1710 def date(self):
1709 t, tz = self._changectx.date()
1711 t, tz = self._changectx.date()
1710 try:
1712 try:
1711 return (self._repo.wvfs.lstat(self._path).st_mtime, tz)
1713 return (self._repo.wvfs.lstat(self._path).st_mtime, tz)
1712 except OSError as err:
1714 except OSError as err:
1713 if err.errno != errno.ENOENT:
1715 if err.errno != errno.ENOENT:
1714 raise
1716 raise
1715 return (t, tz)
1717 return (t, tz)
1716
1718
1717 def cmp(self, fctx):
1719 def cmp(self, fctx):
1718 """compare with other file context
1720 """compare with other file context
1719
1721
1720 returns True if different than fctx.
1722 returns True if different than fctx.
1721 """
1723 """
1722 # fctx should be a filectx (not a workingfilectx)
1724 # fctx should be a filectx (not a workingfilectx)
1723 # invert comparison to reuse the same code path
1725 # invert comparison to reuse the same code path
1724 return fctx.cmp(self)
1726 return fctx.cmp(self)
1725
1727
1726 def remove(self, ignoremissing=False):
1728 def remove(self, ignoremissing=False):
1727 """wraps unlink for a repo's working directory"""
1729 """wraps unlink for a repo's working directory"""
1728 util.unlinkpath(self._repo.wjoin(self._path), ignoremissing)
1730 util.unlinkpath(self._repo.wjoin(self._path), ignoremissing)
1729
1731
1730 def write(self, data, flags):
1732 def write(self, data, flags):
1731 """wraps repo.wwrite"""
1733 """wraps repo.wwrite"""
1732 self._repo.wwrite(self._path, data, flags)
1734 self._repo.wwrite(self._path, data, flags)
1733
1735
1734 class workingcommitctx(workingctx):
1736 class workingcommitctx(workingctx):
1735 """A workingcommitctx object makes access to data related to
1737 """A workingcommitctx object makes access to data related to
1736 the revision being committed convenient.
1738 the revision being committed convenient.
1737
1739
1738 This hides changes in the working directory, if they aren't
1740 This hides changes in the working directory, if they aren't
1739 committed in this context.
1741 committed in this context.
1740 """
1742 """
1741 def __init__(self, repo, changes,
1743 def __init__(self, repo, changes,
1742 text="", user=None, date=None, extra=None):
1744 text="", user=None, date=None, extra=None):
1743 super(workingctx, self).__init__(repo, text, user, date, extra,
1745 super(workingctx, self).__init__(repo, text, user, date, extra,
1744 changes)
1746 changes)
1745
1747
1746 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1748 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1747 unknown=False):
1749 unknown=False):
1748 """Return matched files only in ``self._status``
1750 """Return matched files only in ``self._status``
1749
1751
1750 Uncommitted files appear "clean" via this context, even if
1752 Uncommitted files appear "clean" via this context, even if
1751 they aren't actually so in the working directory.
1753 they aren't actually so in the working directory.
1752 """
1754 """
1753 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1755 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1754 if clean:
1756 if clean:
1755 clean = [f for f in self._manifest if f not in self._changedset]
1757 clean = [f for f in self._manifest if f not in self._changedset]
1756 else:
1758 else:
1757 clean = []
1759 clean = []
1758 return scmutil.status([f for f in self._status.modified if match(f)],
1760 return scmutil.status([f for f in self._status.modified if match(f)],
1759 [f for f in self._status.added if match(f)],
1761 [f for f in self._status.added if match(f)],
1760 [f for f in self._status.removed if match(f)],
1762 [f for f in self._status.removed if match(f)],
1761 [], [], [], clean)
1763 [], [], [], clean)
1762
1764
1763 @propertycache
1765 @propertycache
1764 def _changedset(self):
1766 def _changedset(self):
1765 """Return the set of files changed in this context
1767 """Return the set of files changed in this context
1766 """
1768 """
1767 changed = set(self._status.modified)
1769 changed = set(self._status.modified)
1768 changed.update(self._status.added)
1770 changed.update(self._status.added)
1769 changed.update(self._status.removed)
1771 changed.update(self._status.removed)
1770 return changed
1772 return changed
1771
1773
1772 class memctx(committablectx):
1774 class memctx(committablectx):
1773 """Use memctx to perform in-memory commits via localrepo.commitctx().
1775 """Use memctx to perform in-memory commits via localrepo.commitctx().
1774
1776
1775 Revision information is supplied at initialization time while
1777 Revision information is supplied at initialization time while
1776 related files data and is made available through a callback
1778 related files data and is made available through a callback
1777 mechanism. 'repo' is the current localrepo, 'parents' is a
1779 mechanism. 'repo' is the current localrepo, 'parents' is a
1778 sequence of two parent revisions identifiers (pass None for every
1780 sequence of two parent revisions identifiers (pass None for every
1779 missing parent), 'text' is the commit message and 'files' lists
1781 missing parent), 'text' is the commit message and 'files' lists
1780 names of files touched by the revision (normalized and relative to
1782 names of files touched by the revision (normalized and relative to
1781 repository root).
1783 repository root).
1782
1784
1783 filectxfn(repo, memctx, path) is a callable receiving the
1785 filectxfn(repo, memctx, path) is a callable receiving the
1784 repository, the current memctx object and the normalized path of
1786 repository, the current memctx object and the normalized path of
1785 requested file, relative to repository root. It is fired by the
1787 requested file, relative to repository root. It is fired by the
1786 commit function for every file in 'files', but calls order is
1788 commit function for every file in 'files', but calls order is
1787 undefined. If the file is available in the revision being
1789 undefined. If the file is available in the revision being
1788 committed (updated or added), filectxfn returns a memfilectx
1790 committed (updated or added), filectxfn returns a memfilectx
1789 object. If the file was removed, filectxfn raises an
1791 object. If the file was removed, filectxfn raises an
1790 IOError. Moved files are represented by marking the source file
1792 IOError. Moved files are represented by marking the source file
1791 removed and the new file added with copy information (see
1793 removed and the new file added with copy information (see
1792 memfilectx).
1794 memfilectx).
1793
1795
1794 user receives the committer name and defaults to current
1796 user receives the committer name and defaults to current
1795 repository username, date is the commit date in any format
1797 repository username, date is the commit date in any format
1796 supported by util.parsedate() and defaults to current date, extra
1798 supported by util.parsedate() and defaults to current date, extra
1797 is a dictionary of metadata or is left empty.
1799 is a dictionary of metadata or is left empty.
1798 """
1800 """
1799
1801
1800 # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
1802 # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
1801 # Extensions that need to retain compatibility across Mercurial 3.1 can use
1803 # Extensions that need to retain compatibility across Mercurial 3.1 can use
1802 # this field to determine what to do in filectxfn.
1804 # this field to determine what to do in filectxfn.
1803 _returnnoneformissingfiles = True
1805 _returnnoneformissingfiles = True
1804
1806
1805 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1807 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1806 date=None, extra=None, editor=False):
1808 date=None, extra=None, editor=False):
1807 super(memctx, self).__init__(repo, text, user, date, extra)
1809 super(memctx, self).__init__(repo, text, user, date, extra)
1808 self._rev = None
1810 self._rev = None
1809 self._node = None
1811 self._node = None
1810 parents = [(p or nullid) for p in parents]
1812 parents = [(p or nullid) for p in parents]
1811 p1, p2 = parents
1813 p1, p2 = parents
1812 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1814 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1813 files = sorted(set(files))
1815 files = sorted(set(files))
1814 self._files = files
1816 self._files = files
1815 self.substate = {}
1817 self.substate = {}
1816
1818
1817 # if store is not callable, wrap it in a function
1819 # if store is not callable, wrap it in a function
1818 if not callable(filectxfn):
1820 if not callable(filectxfn):
1819 def getfilectx(repo, memctx, path):
1821 def getfilectx(repo, memctx, path):
1820 fctx = filectxfn[path]
1822 fctx = filectxfn[path]
1821 # this is weird but apparently we only keep track of one parent
1823 # this is weird but apparently we only keep track of one parent
1822 # (why not only store that instead of a tuple?)
1824 # (why not only store that instead of a tuple?)
1823 copied = fctx.renamed()
1825 copied = fctx.renamed()
1824 if copied:
1826 if copied:
1825 copied = copied[0]
1827 copied = copied[0]
1826 return memfilectx(repo, path, fctx.data(),
1828 return memfilectx(repo, path, fctx.data(),
1827 islink=fctx.islink(), isexec=fctx.isexec(),
1829 islink=fctx.islink(), isexec=fctx.isexec(),
1828 copied=copied, memctx=memctx)
1830 copied=copied, memctx=memctx)
1829 self._filectxfn = getfilectx
1831 self._filectxfn = getfilectx
1830 else:
1832 else:
1831 # "util.cachefunc" reduces invocation of possibly expensive
1833 # "util.cachefunc" reduces invocation of possibly expensive
1832 # "filectxfn" for performance (e.g. converting from another VCS)
1834 # "filectxfn" for performance (e.g. converting from another VCS)
1833 self._filectxfn = util.cachefunc(filectxfn)
1835 self._filectxfn = util.cachefunc(filectxfn)
1834
1836
1835 if extra:
1837 if extra:
1836 self._extra = extra.copy()
1838 self._extra = extra.copy()
1837 else:
1839 else:
1838 self._extra = {}
1840 self._extra = {}
1839
1841
1840 if self._extra.get('branch', '') == '':
1842 if self._extra.get('branch', '') == '':
1841 self._extra['branch'] = 'default'
1843 self._extra['branch'] = 'default'
1842
1844
1843 if editor:
1845 if editor:
1844 self._text = editor(self._repo, self, [])
1846 self._text = editor(self._repo, self, [])
1845 self._repo.savecommitmessage(self._text)
1847 self._repo.savecommitmessage(self._text)
1846
1848
1847 def filectx(self, path, filelog=None):
1849 def filectx(self, path, filelog=None):
1848 """get a file context from the working directory
1850 """get a file context from the working directory
1849
1851
1850 Returns None if file doesn't exist and should be removed."""
1852 Returns None if file doesn't exist and should be removed."""
1851 return self._filectxfn(self._repo, self, path)
1853 return self._filectxfn(self._repo, self, path)
1852
1854
1853 def commit(self):
1855 def commit(self):
1854 """commit context to the repo"""
1856 """commit context to the repo"""
1855 return self._repo.commitctx(self)
1857 return self._repo.commitctx(self)
1856
1858
1857 @propertycache
1859 @propertycache
1858 def _manifest(self):
1860 def _manifest(self):
1859 """generate a manifest based on the return values of filectxfn"""
1861 """generate a manifest based on the return values of filectxfn"""
1860
1862
1861 # keep this simple for now; just worry about p1
1863 # keep this simple for now; just worry about p1
1862 pctx = self._parents[0]
1864 pctx = self._parents[0]
1863 man = pctx.manifest().copy()
1865 man = pctx.manifest().copy()
1864
1866
1865 for f in self._status.modified:
1867 for f in self._status.modified:
1866 p1node = nullid
1868 p1node = nullid
1867 p2node = nullid
1869 p2node = nullid
1868 p = pctx[f].parents() # if file isn't in pctx, check p2?
1870 p = pctx[f].parents() # if file isn't in pctx, check p2?
1869 if len(p) > 0:
1871 if len(p) > 0:
1870 p1node = p[0].node()
1872 p1node = p[0].node()
1871 if len(p) > 1:
1873 if len(p) > 1:
1872 p2node = p[1].node()
1874 p2node = p[1].node()
1873 man[f] = revlog.hash(self[f].data(), p1node, p2node)
1875 man[f] = revlog.hash(self[f].data(), p1node, p2node)
1874
1876
1875 for f in self._status.added:
1877 for f in self._status.added:
1876 man[f] = revlog.hash(self[f].data(), nullid, nullid)
1878 man[f] = revlog.hash(self[f].data(), nullid, nullid)
1877
1879
1878 for f in self._status.removed:
1880 for f in self._status.removed:
1879 if f in man:
1881 if f in man:
1880 del man[f]
1882 del man[f]
1881
1883
1882 return man
1884 return man
1883
1885
1884 @propertycache
1886 @propertycache
1885 def _status(self):
1887 def _status(self):
1886 """Calculate exact status from ``files`` specified at construction
1888 """Calculate exact status from ``files`` specified at construction
1887 """
1889 """
1888 man1 = self.p1().manifest()
1890 man1 = self.p1().manifest()
1889 p2 = self._parents[1]
1891 p2 = self._parents[1]
1890 # "1 < len(self._parents)" can't be used for checking
1892 # "1 < len(self._parents)" can't be used for checking
1891 # existence of the 2nd parent, because "memctx._parents" is
1893 # existence of the 2nd parent, because "memctx._parents" is
1892 # explicitly initialized by the list, of which length is 2.
1894 # explicitly initialized by the list, of which length is 2.
1893 if p2.node() != nullid:
1895 if p2.node() != nullid:
1894 man2 = p2.manifest()
1896 man2 = p2.manifest()
1895 managing = lambda f: f in man1 or f in man2
1897 managing = lambda f: f in man1 or f in man2
1896 else:
1898 else:
1897 managing = lambda f: f in man1
1899 managing = lambda f: f in man1
1898
1900
1899 modified, added, removed = [], [], []
1901 modified, added, removed = [], [], []
1900 for f in self._files:
1902 for f in self._files:
1901 if not managing(f):
1903 if not managing(f):
1902 added.append(f)
1904 added.append(f)
1903 elif self[f]:
1905 elif self[f]:
1904 modified.append(f)
1906 modified.append(f)
1905 else:
1907 else:
1906 removed.append(f)
1908 removed.append(f)
1907
1909
1908 return scmutil.status(modified, added, removed, [], [], [], [])
1910 return scmutil.status(modified, added, removed, [], [], [], [])
1909
1911
1910 class memfilectx(committablefilectx):
1912 class memfilectx(committablefilectx):
1911 """memfilectx represents an in-memory file to commit.
1913 """memfilectx represents an in-memory file to commit.
1912
1914
1913 See memctx and committablefilectx for more details.
1915 See memctx and committablefilectx for more details.
1914 """
1916 """
1915 def __init__(self, repo, path, data, islink=False,
1917 def __init__(self, repo, path, data, islink=False,
1916 isexec=False, copied=None, memctx=None):
1918 isexec=False, copied=None, memctx=None):
1917 """
1919 """
1918 path is the normalized file path relative to repository root.
1920 path is the normalized file path relative to repository root.
1919 data is the file content as a string.
1921 data is the file content as a string.
1920 islink is True if the file is a symbolic link.
1922 islink is True if the file is a symbolic link.
1921 isexec is True if the file is executable.
1923 isexec is True if the file is executable.
1922 copied is the source file path if current file was copied in the
1924 copied is the source file path if current file was copied in the
1923 revision being committed, or None."""
1925 revision being committed, or None."""
1924 super(memfilectx, self).__init__(repo, path, None, memctx)
1926 super(memfilectx, self).__init__(repo, path, None, memctx)
1925 self._data = data
1927 self._data = data
1926 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1928 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1927 self._copied = None
1929 self._copied = None
1928 if copied:
1930 if copied:
1929 self._copied = (copied, nullid)
1931 self._copied = (copied, nullid)
1930
1932
1931 def data(self):
1933 def data(self):
1932 return self._data
1934 return self._data
1933 def size(self):
1935 def size(self):
1934 return len(self.data())
1936 return len(self.data())
1935 def flags(self):
1937 def flags(self):
1936 return self._flags
1938 return self._flags
1937 def renamed(self):
1939 def renamed(self):
1938 return self._copied
1940 return self._copied
1939
1941
1940 def remove(self, ignoremissing=False):
1942 def remove(self, ignoremissing=False):
1941 """wraps unlink for a repo's working directory"""
1943 """wraps unlink for a repo's working directory"""
1942 # need to figure out what to do here
1944 # need to figure out what to do here
1943 del self._changectx[self._path]
1945 del self._changectx[self._path]
1944
1946
1945 def write(self, data, flags):
1947 def write(self, data, flags):
1946 """wraps repo.wwrite"""
1948 """wraps repo.wwrite"""
1947 self._data = data
1949 self._data = data
@@ -1,372 +1,391 b''
1 $ echo "[extensions]" >> $HGRCPATH
1 $ echo "[extensions]" >> $HGRCPATH
2 $ echo "extdiff=" >> $HGRCPATH
2 $ echo "extdiff=" >> $HGRCPATH
3
3
4 $ hg init a
4 $ hg init a
5 $ cd a
5 $ cd a
6 $ echo a > a
6 $ echo a > a
7 $ echo b > b
7 $ echo b > b
8 $ hg add
8 $ hg add
9 adding a
9 adding a
10 adding b
10 adding b
11
11
12 Should diff cloned directories:
12 Should diff cloned directories:
13
13
14 $ hg extdiff -o -r $opt
14 $ hg extdiff -o -r $opt
15 Only in a: a
15 Only in a: a
16 Only in a: b
16 Only in a: b
17 [1]
17 [1]
18
18
19 $ cat <<EOF >> $HGRCPATH
19 $ cat <<EOF >> $HGRCPATH
20 > [extdiff]
20 > [extdiff]
21 > cmd.falabala = echo
21 > cmd.falabala = echo
22 > opts.falabala = diffing
22 > opts.falabala = diffing
23 > cmd.edspace = echo
23 > cmd.edspace = echo
24 > opts.edspace = "name <user@example.com>"
24 > opts.edspace = "name <user@example.com>"
25 > EOF
25 > EOF
26
26
27 $ hg falabala
27 $ hg falabala
28 diffing a.000000000000 a
28 diffing a.000000000000 a
29 [1]
29 [1]
30
30
31 $ hg help falabala
31 $ hg help falabala
32 hg falabala [OPTION]... [FILE]...
32 hg falabala [OPTION]... [FILE]...
33
33
34 use 'echo' to diff repository (or selected files)
34 use 'echo' to diff repository (or selected files)
35
35
36 Show differences between revisions for the specified files, using the
36 Show differences between revisions for the specified files, using the
37 'echo' program.
37 'echo' program.
38
38
39 When two revision arguments are given, then changes are shown between
39 When two revision arguments are given, then changes are shown between
40 those revisions. If only one revision is specified then that revision is
40 those revisions. If only one revision is specified then that revision is
41 compared to the working directory, and, when no revisions are specified,
41 compared to the working directory, and, when no revisions are specified,
42 the working directory files are compared to its parent.
42 the working directory files are compared to its parent.
43
43
44 options ([+] can be repeated):
44 options ([+] can be repeated):
45
45
46 -o --option OPT [+] pass option to comparison program
46 -o --option OPT [+] pass option to comparison program
47 -r --rev REV [+] revision
47 -r --rev REV [+] revision
48 -c --change REV change made by revision
48 -c --change REV change made by revision
49 --patch compare patches for two revisions
49 --patch compare patches for two revisions
50 -I --include PATTERN [+] include names matching the given patterns
50 -I --include PATTERN [+] include names matching the given patterns
51 -X --exclude PATTERN [+] exclude names matching the given patterns
51 -X --exclude PATTERN [+] exclude names matching the given patterns
52 -S --subrepos recurse into subrepositories
52 -S --subrepos recurse into subrepositories
53
53
54 (some details hidden, use --verbose to show complete help)
54 (some details hidden, use --verbose to show complete help)
55
55
56 $ hg ci -d '0 0' -mtest1
56 $ hg ci -d '0 0' -mtest1
57
57
58 $ echo b >> a
58 $ echo b >> a
59 $ hg ci -d '1 0' -mtest2
59 $ hg ci -d '1 0' -mtest2
60
60
61 Should diff cloned files directly:
61 Should diff cloned files directly:
62
62
63 #if windows
63 #if windows
64 $ hg falabala -r 0:1
64 $ hg falabala -r 0:1
65 diffing "*\\extdiff.*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob)
65 diffing "*\\extdiff.*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob)
66 [1]
66 [1]
67 #else
67 #else
68 $ hg falabala -r 0:1
68 $ hg falabala -r 0:1
69 diffing */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
69 diffing */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
70 [1]
70 [1]
71 #endif
71 #endif
72
72
73 Specifying an empty revision should abort.
73 Specifying an empty revision should abort.
74
74
75 $ hg extdiff --patch --rev 'ancestor()' --rev 1
75 $ hg extdiff --patch --rev 'ancestor()' --rev 1
76 abort: empty revision on one side of range
76 abort: empty revision on one side of range
77 [255]
77 [255]
78
78
79 Test diff during merge:
79 Test diff during merge:
80
80
81 $ hg update -C 0
81 $ hg update -C 0
82 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
82 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
83 $ echo c >> c
83 $ echo c >> c
84 $ hg add c
84 $ hg add c
85 $ hg ci -m "new branch" -d '1 0'
85 $ hg ci -m "new branch" -d '1 0'
86 created new head
86 created new head
87 $ hg merge 1
87 $ hg merge 1
88 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
88 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
89 (branch merge, don't forget to commit)
89 (branch merge, don't forget to commit)
90
90
91 Should diff cloned file against wc file:
91 Should diff cloned file against wc file:
92
92
93 #if windows
93 #if windows
94 $ hg falabala
94 $ hg falabala
95 diffing "*\\extdiff.*\\a.2a13a4d2da36\\a" "*\\a\\a" (glob)
95 diffing "*\\extdiff.*\\a.2a13a4d2da36\\a" "*\\a\\a" (glob)
96 [1]
96 [1]
97 #else
97 #else
98 $ hg falabala
98 $ hg falabala
99 diffing */extdiff.*/a.2a13a4d2da36/a */a/a (glob)
99 diffing */extdiff.*/a.2a13a4d2da36/a */a/a (glob)
100 [1]
100 [1]
101 #endif
101 #endif
102
102
103
103
104 Test --change option:
104 Test --change option:
105
105
106 $ hg ci -d '2 0' -mtest3
106 $ hg ci -d '2 0' -mtest3
107 #if windows
107 #if windows
108 $ hg falabala -c 1
108 $ hg falabala -c 1
109 diffing "*\\extdiff.*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob)
109 diffing "*\\extdiff.*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob)
110 [1]
110 [1]
111 #else
111 #else
112 $ hg falabala -c 1
112 $ hg falabala -c 1
113 diffing */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
113 diffing */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
114 [1]
114 [1]
115 #endif
115 #endif
116
116
117 Check diff are made from the first parent:
117 Check diff are made from the first parent:
118
118
119 #if windows
119 #if windows
120 $ hg falabala -c 3 || echo "diff-like tools yield a non-zero exit code"
120 $ hg falabala -c 3 || echo "diff-like tools yield a non-zero exit code"
121 diffing "*\\extdiff.*\\a.2a13a4d2da36\\a" "a.46c0e4daeb72\\a" (glob)
121 diffing "*\\extdiff.*\\a.2a13a4d2da36\\a" "a.46c0e4daeb72\\a" (glob)
122 diff-like tools yield a non-zero exit code
122 diff-like tools yield a non-zero exit code
123 #else
123 #else
124 $ hg falabala -c 3 || echo "diff-like tools yield a non-zero exit code"
124 $ hg falabala -c 3 || echo "diff-like tools yield a non-zero exit code"
125 diffing */extdiff.*/a.2a13a4d2da36/a a.46c0e4daeb72/a (glob)
125 diffing */extdiff.*/a.2a13a4d2da36/a a.46c0e4daeb72/a (glob)
126 diff-like tools yield a non-zero exit code
126 diff-like tools yield a non-zero exit code
127 #endif
127 #endif
128
128
129 issue3153: ensure using extdiff with removed subrepos doesn't crash:
130
131 $ hg init suba
132 $ cd suba
133 $ echo suba > suba
134 $ hg add
135 adding suba
136 $ hg ci -m "adding suba file"
137 $ cd ..
138 $ echo suba=suba > .hgsub
139 $ hg add
140 adding .hgsub
141 $ hg ci -Sm "adding subrepo"
142 $ echo > .hgsub
143 $ hg ci -m "removing subrepo"
144 $ hg falabala -r 4 -r 5 -S
145 diffing a.398e36faf9c6 a.5ab95fb166c4
146 [1]
147
129 issue4463: usage of command line configuration without additional quoting
148 issue4463: usage of command line configuration without additional quoting
130
149
131 $ cat <<EOF >> $HGRCPATH
150 $ cat <<EOF >> $HGRCPATH
132 > [extdiff]
151 > [extdiff]
133 > cmd.4463a = echo
152 > cmd.4463a = echo
134 > opts.4463a = a-naked 'single quoted' "double quoted"
153 > opts.4463a = a-naked 'single quoted' "double quoted"
135 > 4463b = echo b-naked 'single quoted' "double quoted"
154 > 4463b = echo b-naked 'single quoted' "double quoted"
136 > echo =
155 > echo =
137 > EOF
156 > EOF
138 $ hg update -q -C 0
157 $ hg update -q -C 0
139 $ echo a >> a
158 $ echo a >> a
140 #if windows
159 #if windows
141 $ hg --debug 4463a | grep '^running'
160 $ hg --debug 4463a | grep '^running'
142 running 'echo a-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob)
161 running 'echo a-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob)
143 $ hg --debug 4463b | grep '^running'
162 $ hg --debug 4463b | grep '^running'
144 running 'echo b-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob)
163 running 'echo b-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob)
145 $ hg --debug echo | grep '^running'
164 $ hg --debug echo | grep '^running'
146 running '*echo* "*\\a" "*\\a"' in */extdiff.* (glob)
165 running '*echo* "*\\a" "*\\a"' in */extdiff.* (glob)
147 #else
166 #else
148 $ hg --debug 4463a | grep '^running'
167 $ hg --debug 4463a | grep '^running'
149 running 'echo a-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob)
168 running 'echo a-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob)
150 $ hg --debug 4463b | grep '^running'
169 $ hg --debug 4463b | grep '^running'
151 running 'echo b-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob)
170 running 'echo b-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob)
152 $ hg --debug echo | grep '^running'
171 $ hg --debug echo | grep '^running'
153 running '*echo */a $TESTTMP/a/a' in */extdiff.* (glob)
172 running '*echo */a $TESTTMP/a/a' in */extdiff.* (glob)
154 #endif
173 #endif
155
174
156 (getting options from other than extdiff section)
175 (getting options from other than extdiff section)
157
176
158 $ cat <<EOF >> $HGRCPATH
177 $ cat <<EOF >> $HGRCPATH
159 > [extdiff]
178 > [extdiff]
160 > # using diff-tools diffargs
179 > # using diff-tools diffargs
161 > 4463b2 = echo
180 > 4463b2 = echo
162 > # using merge-tools diffargs
181 > # using merge-tools diffargs
163 > 4463b3 = echo
182 > 4463b3 = echo
164 > # no diffargs
183 > # no diffargs
165 > 4463b4 = echo
184 > 4463b4 = echo
166 > [diff-tools]
185 > [diff-tools]
167 > 4463b2.diffargs = b2-naked 'single quoted' "double quoted"
186 > 4463b2.diffargs = b2-naked 'single quoted' "double quoted"
168 > [merge-tools]
187 > [merge-tools]
169 > 4463b3.diffargs = b3-naked 'single quoted' "double quoted"
188 > 4463b3.diffargs = b3-naked 'single quoted' "double quoted"
170 > EOF
189 > EOF
171 #if windows
190 #if windows
172 $ hg --debug 4463b2 | grep '^running'
191 $ hg --debug 4463b2 | grep '^running'
173 running 'echo b2-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob)
192 running 'echo b2-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob)
174 $ hg --debug 4463b3 | grep '^running'
193 $ hg --debug 4463b3 | grep '^running'
175 running 'echo b3-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob)
194 running 'echo b3-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob)
176 $ hg --debug 4463b4 | grep '^running'
195 $ hg --debug 4463b4 | grep '^running'
177 running 'echo "*\\a" "*\\a"' in */extdiff.* (glob)
196 running 'echo "*\\a" "*\\a"' in */extdiff.* (glob)
178 $ hg --debug 4463b4 --option b4-naked --option 'being quoted' | grep '^running'
197 $ hg --debug 4463b4 --option b4-naked --option 'being quoted' | grep '^running'
179 running 'echo b4-naked "being quoted" "*\\a" "*\\a"' in */extdiff.* (glob)
198 running 'echo b4-naked "being quoted" "*\\a" "*\\a"' in */extdiff.* (glob)
180 $ hg --debug extdiff -p echo --option echo-naked --option 'being quoted' | grep '^running'
199 $ hg --debug extdiff -p echo --option echo-naked --option 'being quoted' | grep '^running'
181 running 'echo echo-naked "being quoted" "*\\a" "*\\a"' in */extdiff.* (glob)
200 running 'echo echo-naked "being quoted" "*\\a" "*\\a"' in */extdiff.* (glob)
182 #else
201 #else
183 $ hg --debug 4463b2 | grep '^running'
202 $ hg --debug 4463b2 | grep '^running'
184 running 'echo b2-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob)
203 running 'echo b2-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob)
185 $ hg --debug 4463b3 | grep '^running'
204 $ hg --debug 4463b3 | grep '^running'
186 running 'echo b3-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob)
205 running 'echo b3-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob)
187 $ hg --debug 4463b4 | grep '^running'
206 $ hg --debug 4463b4 | grep '^running'
188 running 'echo */a $TESTTMP/a/a' in */extdiff.* (glob)
207 running 'echo */a $TESTTMP/a/a' in */extdiff.* (glob)
189 $ hg --debug 4463b4 --option b4-naked --option 'being quoted' | grep '^running'
208 $ hg --debug 4463b4 --option b4-naked --option 'being quoted' | grep '^running'
190 running "echo b4-naked 'being quoted' */a $TESTTMP/a/a" in */extdiff.* (glob)
209 running "echo b4-naked 'being quoted' */a $TESTTMP/a/a" in */extdiff.* (glob)
191 $ hg --debug extdiff -p echo --option echo-naked --option 'being quoted' | grep '^running'
210 $ hg --debug extdiff -p echo --option echo-naked --option 'being quoted' | grep '^running'
192 running "echo echo-naked 'being quoted' */a $TESTTMP/a/a" in */extdiff.* (glob)
211 running "echo echo-naked 'being quoted' */a $TESTTMP/a/a" in */extdiff.* (glob)
193 #endif
212 #endif
194
213
195 $ touch 'sp ace'
214 $ touch 'sp ace'
196 $ hg add 'sp ace'
215 $ hg add 'sp ace'
197 $ hg ci -m 'sp ace'
216 $ hg ci -m 'sp ace'
198 created new head
217 created new head
199 $ echo > 'sp ace'
218 $ echo > 'sp ace'
200
219
201 Test pre-72a89cf86fcd backward compatibility with half-baked manual quoting
220 Test pre-72a89cf86fcd backward compatibility with half-baked manual quoting
202
221
203 $ cat <<EOF >> $HGRCPATH
222 $ cat <<EOF >> $HGRCPATH
204 > [extdiff]
223 > [extdiff]
205 > odd =
224 > odd =
206 > [merge-tools]
225 > [merge-tools]
207 > odd.diffargs = --foo='\$clabel' '\$clabel' "--bar=\$clabel" "\$clabel"
226 > odd.diffargs = --foo='\$clabel' '\$clabel' "--bar=\$clabel" "\$clabel"
208 > odd.executable = echo
227 > odd.executable = echo
209 > EOF
228 > EOF
210 #if windows
229 #if windows
211 TODO
230 TODO
212 #else
231 #else
213 $ hg --debug odd | grep '^running'
232 $ hg --debug odd | grep '^running'
214 running "*/echo --foo='sp ace' 'sp ace' --bar='sp ace' 'sp ace'" in * (glob)
233 running "*/echo --foo='sp ace' 'sp ace' --bar='sp ace' 'sp ace'" in * (glob)
215 #endif
234 #endif
216
235
217 Empty argument must be quoted
236 Empty argument must be quoted
218
237
219 $ cat <<EOF >> $HGRCPATH
238 $ cat <<EOF >> $HGRCPATH
220 > [extdiff]
239 > [extdiff]
221 > kdiff3 = echo
240 > kdiff3 = echo
222 > [merge-tools]
241 > [merge-tools]
223 > kdiff3.diffargs=--L1 \$plabel1 --L2 \$clabel \$parent \$child
242 > kdiff3.diffargs=--L1 \$plabel1 --L2 \$clabel \$parent \$child
224 > EOF
243 > EOF
225 #if windows
244 #if windows
226 $ hg --debug kdiff3 -r0 | grep '^running'
245 $ hg --debug kdiff3 -r0 | grep '^running'
227 running 'echo --L1 "@0" --L2 "" a.8a5febb7f867 a' in * (glob)
246 running 'echo --L1 "@0" --L2 "" a.8a5febb7f867 a' in * (glob)
228 #else
247 #else
229 $ hg --debug kdiff3 -r0 | grep '^running'
248 $ hg --debug kdiff3 -r0 | grep '^running'
230 running "echo --L1 '@0' --L2 '' a.8a5febb7f867 a" in * (glob)
249 running "echo --L1 '@0' --L2 '' a.8a5febb7f867 a" in * (glob)
231 #endif
250 #endif
232
251
233 #if execbit
252 #if execbit
234
253
235 Test extdiff of multiple files in tmp dir:
254 Test extdiff of multiple files in tmp dir:
236
255
237 $ hg update -C 0 > /dev/null
256 $ hg update -C 0 > /dev/null
238 $ echo changed > a
257 $ echo changed > a
239 $ echo changed > b
258 $ echo changed > b
240 $ chmod +x b
259 $ chmod +x b
241
260
242 Diff in working directory, before:
261 Diff in working directory, before:
243
262
244 $ hg diff --git
263 $ hg diff --git
245 diff --git a/a b/a
264 diff --git a/a b/a
246 --- a/a
265 --- a/a
247 +++ b/a
266 +++ b/a
248 @@ -1,1 +1,1 @@
267 @@ -1,1 +1,1 @@
249 -a
268 -a
250 +changed
269 +changed
251 diff --git a/b b/b
270 diff --git a/b b/b
252 old mode 100644
271 old mode 100644
253 new mode 100755
272 new mode 100755
254 --- a/b
273 --- a/b
255 +++ b/b
274 +++ b/b
256 @@ -1,1 +1,1 @@
275 @@ -1,1 +1,1 @@
257 -b
276 -b
258 +changed
277 +changed
259
278
260
279
261 Edit with extdiff -p:
280 Edit with extdiff -p:
262
281
263 Prepare custom diff/edit tool:
282 Prepare custom diff/edit tool:
264
283
265 $ cat > 'diff tool.py' << EOT
284 $ cat > 'diff tool.py' << EOT
266 > #!/usr/bin/env python
285 > #!/usr/bin/env python
267 > import time
286 > import time
268 > time.sleep(1) # avoid unchanged-timestamp problems
287 > time.sleep(1) # avoid unchanged-timestamp problems
269 > file('a/a', 'ab').write('edited\n')
288 > file('a/a', 'ab').write('edited\n')
270 > file('a/b', 'ab').write('edited\n')
289 > file('a/b', 'ab').write('edited\n')
271 > EOT
290 > EOT
272
291
273 $ chmod +x 'diff tool.py'
292 $ chmod +x 'diff tool.py'
274
293
275 will change to /tmp/extdiff.TMP and populate directories a.TMP and a
294 will change to /tmp/extdiff.TMP and populate directories a.TMP and a
276 and start tool
295 and start tool
277
296
278 $ hg extdiff -p "`pwd`/diff tool.py"
297 $ hg extdiff -p "`pwd`/diff tool.py"
279 [1]
298 [1]
280
299
281 Diff in working directory, after:
300 Diff in working directory, after:
282
301
283 $ hg diff --git
302 $ hg diff --git
284 diff --git a/a b/a
303 diff --git a/a b/a
285 --- a/a
304 --- a/a
286 +++ b/a
305 +++ b/a
287 @@ -1,1 +1,2 @@
306 @@ -1,1 +1,2 @@
288 -a
307 -a
289 +changed
308 +changed
290 +edited
309 +edited
291 diff --git a/b b/b
310 diff --git a/b b/b
292 old mode 100644
311 old mode 100644
293 new mode 100755
312 new mode 100755
294 --- a/b
313 --- a/b
295 +++ b/b
314 +++ b/b
296 @@ -1,1 +1,2 @@
315 @@ -1,1 +1,2 @@
297 -b
316 -b
298 +changed
317 +changed
299 +edited
318 +edited
300
319
301 Test extdiff with --option:
320 Test extdiff with --option:
302
321
303 $ hg extdiff -p echo -o this -c 1
322 $ hg extdiff -p echo -o this -c 1
304 this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
323 this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
305 [1]
324 [1]
306
325
307 $ hg falabala -o this -c 1
326 $ hg falabala -o this -c 1
308 diffing this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
327 diffing this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
309 [1]
328 [1]
310
329
311 Test extdiff's handling of options with spaces in them:
330 Test extdiff's handling of options with spaces in them:
312
331
313 $ hg edspace -c 1
332 $ hg edspace -c 1
314 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
333 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
315 [1]
334 [1]
316
335
317 $ hg extdiff -p echo -o "name <user@example.com>" -c 1
336 $ hg extdiff -p echo -o "name <user@example.com>" -c 1
318 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
337 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
319 [1]
338 [1]
320
339
321 Test with revsets:
340 Test with revsets:
322
341
323 $ hg extdif -p echo -c "rev(1)"
342 $ hg extdif -p echo -c "rev(1)"
324 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
343 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
325 [1]
344 [1]
326
345
327 $ hg extdif -p echo -r "0::1"
346 $ hg extdif -p echo -r "0::1"
328 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
347 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
329 [1]
348 [1]
330
349
331 Fallback to merge-tools.tool.executable|regkey
350 Fallback to merge-tools.tool.executable|regkey
332 $ mkdir dir
351 $ mkdir dir
333 $ cat > 'dir/tool.sh' << EOF
352 $ cat > 'dir/tool.sh' << EOF
334 > #!/bin/sh
353 > #!/bin/sh
335 > echo "** custom diff **"
354 > echo "** custom diff **"
336 > EOF
355 > EOF
337 $ chmod +x dir/tool.sh
356 $ chmod +x dir/tool.sh
338 $ tool=`pwd`/dir/tool.sh
357 $ tool=`pwd`/dir/tool.sh
339 $ hg --debug tl --config extdiff.tl= --config merge-tools.tl.executable=$tool
358 $ hg --debug tl --config extdiff.tl= --config merge-tools.tl.executable=$tool
340 making snapshot of 2 files from rev * (glob)
359 making snapshot of 2 files from rev * (glob)
341 a
360 a
342 b
361 b
343 making snapshot of 2 files from working directory
362 making snapshot of 2 files from working directory
344 a
363 a
345 b
364 b
346 running '$TESTTMP/a/dir/tool.sh a.* a' in */extdiff.* (glob)
365 running '$TESTTMP/a/dir/tool.sh a.* a' in */extdiff.* (glob)
347 ** custom diff **
366 ** custom diff **
348 cleaning up temp directory
367 cleaning up temp directory
349 [1]
368 [1]
350
369
351 $ cd ..
370 $ cd ..
352
371
353 #endif
372 #endif
354
373
355 #if symlink
374 #if symlink
356
375
357 Test symlinks handling (issue1909)
376 Test symlinks handling (issue1909)
358
377
359 $ hg init testsymlinks
378 $ hg init testsymlinks
360 $ cd testsymlinks
379 $ cd testsymlinks
361 $ echo a > a
380 $ echo a > a
362 $ hg ci -Am adda
381 $ hg ci -Am adda
363 adding a
382 adding a
364 $ echo a >> a
383 $ echo a >> a
365 $ ln -s missing linka
384 $ ln -s missing linka
366 $ hg add linka
385 $ hg add linka
367 $ hg falabala -r 0 --traceback
386 $ hg falabala -r 0 --traceback
368 diffing testsymlinks.07f494440405 testsymlinks
387 diffing testsymlinks.07f494440405 testsymlinks
369 [1]
388 [1]
370 $ cd ..
389 $ cd ..
371
390
372 #endif
391 #endif
General Comments 0
You need to be logged in to leave comments. Login now