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