##// END OF EJS Templates
memctx: fix manifest for removed files (issue4470)...
Augie Fackler -
r23584:db03ed8c default
parent child Browse files
Show More
@@ -1,1680 +1,1686
1 # context.py - changeset and file context objects for mercurial
1 # context.py - changeset and file context objects for mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import nullid, nullrev, short, hex, bin
8 from node import nullid, nullrev, 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 class basectx(object):
20 class basectx(object):
21 """A basectx object represents the common logic for its children:
21 """A basectx object represents the common logic for its children:
22 changectx: read-only context that is already present in the repo,
22 changectx: read-only context that is already present in the repo,
23 workingctx: a context that represents the working directory and can
23 workingctx: a context that represents the working directory and can
24 be committed,
24 be committed,
25 memctx: a context that represents changes in-memory and can also
25 memctx: a context that represents changes in-memory and can also
26 be committed."""
26 be committed."""
27 def __new__(cls, repo, changeid='', *args, **kwargs):
27 def __new__(cls, repo, changeid='', *args, **kwargs):
28 if isinstance(changeid, basectx):
28 if isinstance(changeid, basectx):
29 return changeid
29 return changeid
30
30
31 o = super(basectx, cls).__new__(cls)
31 o = super(basectx, cls).__new__(cls)
32
32
33 o._repo = repo
33 o._repo = repo
34 o._rev = nullrev
34 o._rev = nullrev
35 o._node = nullid
35 o._node = nullid
36
36
37 return o
37 return o
38
38
39 def __str__(self):
39 def __str__(self):
40 return short(self.node())
40 return short(self.node())
41
41
42 def __int__(self):
42 def __int__(self):
43 return self.rev()
43 return self.rev()
44
44
45 def __repr__(self):
45 def __repr__(self):
46 return "<%s %s>" % (type(self).__name__, str(self))
46 return "<%s %s>" % (type(self).__name__, str(self))
47
47
48 def __eq__(self, other):
48 def __eq__(self, other):
49 try:
49 try:
50 return type(self) == type(other) and self._rev == other._rev
50 return type(self) == type(other) and self._rev == other._rev
51 except AttributeError:
51 except AttributeError:
52 return False
52 return False
53
53
54 def __ne__(self, other):
54 def __ne__(self, other):
55 return not (self == other)
55 return not (self == other)
56
56
57 def __contains__(self, key):
57 def __contains__(self, key):
58 return key in self._manifest
58 return key in self._manifest
59
59
60 def __getitem__(self, key):
60 def __getitem__(self, key):
61 return self.filectx(key)
61 return self.filectx(key)
62
62
63 def __iter__(self):
63 def __iter__(self):
64 for f in sorted(self._manifest):
64 for f in sorted(self._manifest):
65 yield f
65 yield f
66
66
67 def _manifestmatches(self, match, s):
67 def _manifestmatches(self, match, s):
68 """generate a new manifest filtered by the match argument
68 """generate a new manifest filtered by the match argument
69
69
70 This method is for internal use only and mainly exists to provide an
70 This method is for internal use only and mainly exists to provide an
71 object oriented way for other contexts to customize the manifest
71 object oriented way for other contexts to customize the manifest
72 generation.
72 generation.
73 """
73 """
74 return self.manifest().matches(match)
74 return self.manifest().matches(match)
75
75
76 def _matchstatus(self, other, match):
76 def _matchstatus(self, other, match):
77 """return match.always if match is none
77 """return match.always if match is none
78
78
79 This internal method provides a way for child objects to override the
79 This internal method provides a way for child objects to override the
80 match operator.
80 match operator.
81 """
81 """
82 return match or matchmod.always(self._repo.root, self._repo.getcwd())
82 return match or matchmod.always(self._repo.root, self._repo.getcwd())
83
83
84 def _buildstatus(self, other, s, match, listignored, listclean,
84 def _buildstatus(self, other, s, match, listignored, listclean,
85 listunknown):
85 listunknown):
86 """build a status with respect to another context"""
86 """build a status with respect to another context"""
87 # Load earliest manifest first for caching reasons. More specifically,
87 # Load earliest manifest first for caching reasons. More specifically,
88 # if you have revisions 1000 and 1001, 1001 is probably stored as a
88 # if you have revisions 1000 and 1001, 1001 is probably stored as a
89 # delta against 1000. Thus, if you read 1000 first, we'll reconstruct
89 # delta against 1000. Thus, if you read 1000 first, we'll reconstruct
90 # 1000 and cache it so that when you read 1001, we just need to apply a
90 # 1000 and cache it so that when you read 1001, we just need to apply a
91 # delta to what's in the cache. So that's one full reconstruction + one
91 # delta to what's in the cache. So that's one full reconstruction + one
92 # delta application.
92 # delta application.
93 if self.rev() is not None and self.rev() < other.rev():
93 if self.rev() is not None and self.rev() < other.rev():
94 self.manifest()
94 self.manifest()
95 mf1 = other._manifestmatches(match, s)
95 mf1 = other._manifestmatches(match, s)
96 mf2 = self._manifestmatches(match, s)
96 mf2 = self._manifestmatches(match, s)
97
97
98 modified, added, clean = [], [], []
98 modified, added, clean = [], [], []
99 deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
99 deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
100 deletedset = set(deleted)
100 deletedset = set(deleted)
101 withflags = mf1.withflags() | mf2.withflags()
101 withflags = mf1.withflags() | mf2.withflags()
102 for fn, mf2node in mf2.iteritems():
102 for fn, mf2node in mf2.iteritems():
103 if fn in mf1:
103 if fn in mf1:
104 if (fn not in deletedset and
104 if (fn not in deletedset and
105 ((fn in withflags and mf1.flags(fn) != mf2.flags(fn)) or
105 ((fn in withflags and mf1.flags(fn) != mf2.flags(fn)) or
106 (mf1[fn] != mf2node and
106 (mf1[fn] != mf2node and
107 (mf2node or self[fn].cmp(other[fn]))))):
107 (mf2node or self[fn].cmp(other[fn]))))):
108 modified.append(fn)
108 modified.append(fn)
109 elif listclean:
109 elif listclean:
110 clean.append(fn)
110 clean.append(fn)
111 del mf1[fn]
111 del mf1[fn]
112 elif fn not in deletedset:
112 elif fn not in deletedset:
113 added.append(fn)
113 added.append(fn)
114 removed = mf1.keys()
114 removed = mf1.keys()
115 if removed:
115 if removed:
116 # need to filter files if they are already reported as removed
116 # need to filter files if they are already reported as removed
117 unknown = [fn for fn in unknown if fn not in mf1]
117 unknown = [fn for fn in unknown if fn not in mf1]
118 ignored = [fn for fn in ignored if fn not in mf1]
118 ignored = [fn for fn in ignored if fn not in mf1]
119
119
120 return scmutil.status(modified, added, removed, deleted, unknown,
120 return scmutil.status(modified, added, removed, deleted, unknown,
121 ignored, clean)
121 ignored, clean)
122
122
123 @propertycache
123 @propertycache
124 def substate(self):
124 def substate(self):
125 return subrepo.state(self, self._repo.ui)
125 return subrepo.state(self, self._repo.ui)
126
126
127 def subrev(self, subpath):
127 def subrev(self, subpath):
128 return self.substate[subpath][1]
128 return self.substate[subpath][1]
129
129
130 def rev(self):
130 def rev(self):
131 return self._rev
131 return self._rev
132 def node(self):
132 def node(self):
133 return self._node
133 return self._node
134 def hex(self):
134 def hex(self):
135 return hex(self.node())
135 return hex(self.node())
136 def manifest(self):
136 def manifest(self):
137 return self._manifest
137 return self._manifest
138 def phasestr(self):
138 def phasestr(self):
139 return phases.phasenames[self.phase()]
139 return phases.phasenames[self.phase()]
140 def mutable(self):
140 def mutable(self):
141 return self.phase() > phases.public
141 return self.phase() > phases.public
142
142
143 def getfileset(self, expr):
143 def getfileset(self, expr):
144 return fileset.getfileset(self, expr)
144 return fileset.getfileset(self, expr)
145
145
146 def obsolete(self):
146 def obsolete(self):
147 """True if the changeset is obsolete"""
147 """True if the changeset is obsolete"""
148 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
148 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
149
149
150 def extinct(self):
150 def extinct(self):
151 """True if the changeset is extinct"""
151 """True if the changeset is extinct"""
152 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
152 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
153
153
154 def unstable(self):
154 def unstable(self):
155 """True if the changeset is not obsolete but it's ancestor are"""
155 """True if the changeset is not obsolete but it's ancestor are"""
156 return self.rev() in obsmod.getrevs(self._repo, 'unstable')
156 return self.rev() in obsmod.getrevs(self._repo, 'unstable')
157
157
158 def bumped(self):
158 def bumped(self):
159 """True if the changeset try to be a successor of a public changeset
159 """True if the changeset try to be a successor of a public changeset
160
160
161 Only non-public and non-obsolete changesets may be bumped.
161 Only non-public and non-obsolete changesets may be bumped.
162 """
162 """
163 return self.rev() in obsmod.getrevs(self._repo, 'bumped')
163 return self.rev() in obsmod.getrevs(self._repo, 'bumped')
164
164
165 def divergent(self):
165 def divergent(self):
166 """Is a successors of a changeset with multiple possible successors set
166 """Is a successors of a changeset with multiple possible successors set
167
167
168 Only non-public and non-obsolete changesets may be divergent.
168 Only non-public and non-obsolete changesets may be divergent.
169 """
169 """
170 return self.rev() in obsmod.getrevs(self._repo, 'divergent')
170 return self.rev() in obsmod.getrevs(self._repo, 'divergent')
171
171
172 def troubled(self):
172 def troubled(self):
173 """True if the changeset is either unstable, bumped or divergent"""
173 """True if the changeset is either unstable, bumped or divergent"""
174 return self.unstable() or self.bumped() or self.divergent()
174 return self.unstable() or self.bumped() or self.divergent()
175
175
176 def troubles(self):
176 def troubles(self):
177 """return the list of troubles affecting this changesets.
177 """return the list of troubles affecting this changesets.
178
178
179 Troubles are returned as strings. possible values are:
179 Troubles are returned as strings. possible values are:
180 - unstable,
180 - unstable,
181 - bumped,
181 - bumped,
182 - divergent.
182 - divergent.
183 """
183 """
184 troubles = []
184 troubles = []
185 if self.unstable():
185 if self.unstable():
186 troubles.append('unstable')
186 troubles.append('unstable')
187 if self.bumped():
187 if self.bumped():
188 troubles.append('bumped')
188 troubles.append('bumped')
189 if self.divergent():
189 if self.divergent():
190 troubles.append('divergent')
190 troubles.append('divergent')
191 return troubles
191 return troubles
192
192
193 def parents(self):
193 def parents(self):
194 """return contexts for each parent changeset"""
194 """return contexts for each parent changeset"""
195 return self._parents
195 return self._parents
196
196
197 def p1(self):
197 def p1(self):
198 return self._parents[0]
198 return self._parents[0]
199
199
200 def p2(self):
200 def p2(self):
201 if len(self._parents) == 2:
201 if len(self._parents) == 2:
202 return self._parents[1]
202 return self._parents[1]
203 return changectx(self._repo, -1)
203 return changectx(self._repo, -1)
204
204
205 def _fileinfo(self, path):
205 def _fileinfo(self, path):
206 if '_manifest' in self.__dict__:
206 if '_manifest' in self.__dict__:
207 try:
207 try:
208 return self._manifest[path], self._manifest.flags(path)
208 return self._manifest[path], self._manifest.flags(path)
209 except KeyError:
209 except KeyError:
210 raise error.ManifestLookupError(self._node, path,
210 raise error.ManifestLookupError(self._node, path,
211 _('not found in manifest'))
211 _('not found in manifest'))
212 if '_manifestdelta' in self.__dict__ or path in self.files():
212 if '_manifestdelta' in self.__dict__ or path in self.files():
213 if path in self._manifestdelta:
213 if path in self._manifestdelta:
214 return (self._manifestdelta[path],
214 return (self._manifestdelta[path],
215 self._manifestdelta.flags(path))
215 self._manifestdelta.flags(path))
216 node, flag = self._repo.manifest.find(self._changeset[0], path)
216 node, flag = self._repo.manifest.find(self._changeset[0], path)
217 if not node:
217 if not node:
218 raise error.ManifestLookupError(self._node, path,
218 raise error.ManifestLookupError(self._node, path,
219 _('not found in manifest'))
219 _('not found in manifest'))
220
220
221 return node, flag
221 return node, flag
222
222
223 def filenode(self, path):
223 def filenode(self, path):
224 return self._fileinfo(path)[0]
224 return self._fileinfo(path)[0]
225
225
226 def flags(self, path):
226 def flags(self, path):
227 try:
227 try:
228 return self._fileinfo(path)[1]
228 return self._fileinfo(path)[1]
229 except error.LookupError:
229 except error.LookupError:
230 return ''
230 return ''
231
231
232 def sub(self, path):
232 def sub(self, path):
233 return subrepo.subrepo(self, path)
233 return subrepo.subrepo(self, path)
234
234
235 def match(self, pats=[], include=None, exclude=None, default='glob'):
235 def match(self, pats=[], include=None, exclude=None, default='glob'):
236 r = self._repo
236 r = self._repo
237 return matchmod.match(r.root, r.getcwd(), pats,
237 return matchmod.match(r.root, r.getcwd(), pats,
238 include, exclude, default,
238 include, exclude, default,
239 auditor=r.auditor, ctx=self)
239 auditor=r.auditor, ctx=self)
240
240
241 def diff(self, ctx2=None, match=None, **opts):
241 def diff(self, ctx2=None, match=None, **opts):
242 """Returns a diff generator for the given contexts and matcher"""
242 """Returns a diff generator for the given contexts and matcher"""
243 if ctx2 is None:
243 if ctx2 is None:
244 ctx2 = self.p1()
244 ctx2 = self.p1()
245 if ctx2 is not None:
245 if ctx2 is not None:
246 ctx2 = self._repo[ctx2]
246 ctx2 = self._repo[ctx2]
247 diffopts = patch.diffopts(self._repo.ui, opts)
247 diffopts = patch.diffopts(self._repo.ui, opts)
248 return patch.diff(self._repo, ctx2, self, match=match, opts=diffopts)
248 return patch.diff(self._repo, ctx2, self, match=match, opts=diffopts)
249
249
250 @propertycache
250 @propertycache
251 def _dirs(self):
251 def _dirs(self):
252 return scmutil.dirs(self._manifest)
252 return scmutil.dirs(self._manifest)
253
253
254 def dirs(self):
254 def dirs(self):
255 return self._dirs
255 return self._dirs
256
256
257 def dirty(self, missing=False, merge=True, branch=True):
257 def dirty(self, missing=False, merge=True, branch=True):
258 return False
258 return False
259
259
260 def status(self, other=None, match=None, listignored=False,
260 def status(self, other=None, match=None, listignored=False,
261 listclean=False, listunknown=False, listsubrepos=False):
261 listclean=False, listunknown=False, listsubrepos=False):
262 """return status of files between two nodes or node and working
262 """return status of files between two nodes or node and working
263 directory.
263 directory.
264
264
265 If other is None, compare this node with working directory.
265 If other is None, compare this node with working directory.
266
266
267 returns (modified, added, removed, deleted, unknown, ignored, clean)
267 returns (modified, added, removed, deleted, unknown, ignored, clean)
268 """
268 """
269
269
270 ctx1 = self
270 ctx1 = self
271 ctx2 = self._repo[other]
271 ctx2 = self._repo[other]
272
272
273 # This next code block is, admittedly, fragile logic that tests for
273 # This next code block is, admittedly, fragile logic that tests for
274 # reversing the contexts and wouldn't need to exist if it weren't for
274 # reversing the contexts and wouldn't need to exist if it weren't for
275 # the fast (and common) code path of comparing the working directory
275 # the fast (and common) code path of comparing the working directory
276 # with its first parent.
276 # with its first parent.
277 #
277 #
278 # What we're aiming for here is the ability to call:
278 # What we're aiming for here is the ability to call:
279 #
279 #
280 # workingctx.status(parentctx)
280 # workingctx.status(parentctx)
281 #
281 #
282 # If we always built the manifest for each context and compared those,
282 # If we always built the manifest for each context and compared those,
283 # then we'd be done. But the special case of the above call means we
283 # then we'd be done. But the special case of the above call means we
284 # just copy the manifest of the parent.
284 # just copy the manifest of the parent.
285 reversed = False
285 reversed = False
286 if (not isinstance(ctx1, changectx)
286 if (not isinstance(ctx1, changectx)
287 and isinstance(ctx2, changectx)):
287 and isinstance(ctx2, changectx)):
288 reversed = True
288 reversed = True
289 ctx1, ctx2 = ctx2, ctx1
289 ctx1, ctx2 = ctx2, ctx1
290
290
291 match = ctx2._matchstatus(ctx1, match)
291 match = ctx2._matchstatus(ctx1, match)
292 r = scmutil.status([], [], [], [], [], [], [])
292 r = scmutil.status([], [], [], [], [], [], [])
293 r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
293 r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
294 listunknown)
294 listunknown)
295
295
296 if reversed:
296 if reversed:
297 # Reverse added and removed. Clear deleted, unknown and ignored as
297 # Reverse added and removed. Clear deleted, unknown and ignored as
298 # these make no sense to reverse.
298 # these make no sense to reverse.
299 r = scmutil.status(r.modified, r.removed, r.added, [], [], [],
299 r = scmutil.status(r.modified, r.removed, r.added, [], [], [],
300 r.clean)
300 r.clean)
301
301
302 if listsubrepos:
302 if listsubrepos:
303 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
303 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
304 rev2 = ctx2.subrev(subpath)
304 rev2 = ctx2.subrev(subpath)
305 try:
305 try:
306 submatch = matchmod.narrowmatcher(subpath, match)
306 submatch = matchmod.narrowmatcher(subpath, match)
307 s = sub.status(rev2, match=submatch, ignored=listignored,
307 s = sub.status(rev2, match=submatch, ignored=listignored,
308 clean=listclean, unknown=listunknown,
308 clean=listclean, unknown=listunknown,
309 listsubrepos=True)
309 listsubrepos=True)
310 for rfiles, sfiles in zip(r, s):
310 for rfiles, sfiles in zip(r, s):
311 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
311 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
312 except error.LookupError:
312 except error.LookupError:
313 self._repo.ui.status(_("skipping missing "
313 self._repo.ui.status(_("skipping missing "
314 "subrepository: %s\n") % subpath)
314 "subrepository: %s\n") % subpath)
315
315
316 for l in r:
316 for l in r:
317 l.sort()
317 l.sort()
318
318
319 return r
319 return r
320
320
321
321
322 def makememctx(repo, parents, text, user, date, branch, files, store,
322 def makememctx(repo, parents, text, user, date, branch, files, store,
323 editor=None):
323 editor=None):
324 def getfilectx(repo, memctx, path):
324 def getfilectx(repo, memctx, path):
325 data, mode, copied = store.getfile(path)
325 data, mode, copied = store.getfile(path)
326 if data is None:
326 if data is None:
327 return None
327 return None
328 islink, isexec = mode
328 islink, isexec = mode
329 return memfilectx(repo, path, data, islink=islink, isexec=isexec,
329 return memfilectx(repo, path, data, islink=islink, isexec=isexec,
330 copied=copied, memctx=memctx)
330 copied=copied, memctx=memctx)
331 extra = {}
331 extra = {}
332 if branch:
332 if branch:
333 extra['branch'] = encoding.fromlocal(branch)
333 extra['branch'] = encoding.fromlocal(branch)
334 ctx = memctx(repo, parents, text, files, getfilectx, user,
334 ctx = memctx(repo, parents, text, files, getfilectx, user,
335 date, extra, editor)
335 date, extra, editor)
336 return ctx
336 return ctx
337
337
338 class changectx(basectx):
338 class changectx(basectx):
339 """A changecontext object makes access to data related to a particular
339 """A changecontext object makes access to data related to a particular
340 changeset convenient. It represents a read-only context already present in
340 changeset convenient. It represents a read-only context already present in
341 the repo."""
341 the repo."""
342 def __init__(self, repo, changeid=''):
342 def __init__(self, repo, changeid=''):
343 """changeid is a revision number, node, or tag"""
343 """changeid is a revision number, node, or tag"""
344
344
345 # since basectx.__new__ already took care of copying the object, we
345 # since basectx.__new__ already took care of copying the object, we
346 # don't need to do anything in __init__, so we just exit here
346 # don't need to do anything in __init__, so we just exit here
347 if isinstance(changeid, basectx):
347 if isinstance(changeid, basectx):
348 return
348 return
349
349
350 if changeid == '':
350 if changeid == '':
351 changeid = '.'
351 changeid = '.'
352 self._repo = repo
352 self._repo = repo
353
353
354 try:
354 try:
355 if isinstance(changeid, int):
355 if isinstance(changeid, int):
356 self._node = repo.changelog.node(changeid)
356 self._node = repo.changelog.node(changeid)
357 self._rev = changeid
357 self._rev = changeid
358 return
358 return
359 if isinstance(changeid, long):
359 if isinstance(changeid, long):
360 changeid = str(changeid)
360 changeid = str(changeid)
361 if changeid == '.':
361 if changeid == '.':
362 self._node = repo.dirstate.p1()
362 self._node = repo.dirstate.p1()
363 self._rev = repo.changelog.rev(self._node)
363 self._rev = repo.changelog.rev(self._node)
364 return
364 return
365 if changeid == 'null':
365 if changeid == 'null':
366 self._node = nullid
366 self._node = nullid
367 self._rev = nullrev
367 self._rev = nullrev
368 return
368 return
369 if changeid == 'tip':
369 if changeid == 'tip':
370 self._node = repo.changelog.tip()
370 self._node = repo.changelog.tip()
371 self._rev = repo.changelog.rev(self._node)
371 self._rev = repo.changelog.rev(self._node)
372 return
372 return
373 if len(changeid) == 20:
373 if len(changeid) == 20:
374 try:
374 try:
375 self._node = changeid
375 self._node = changeid
376 self._rev = repo.changelog.rev(changeid)
376 self._rev = repo.changelog.rev(changeid)
377 return
377 return
378 except error.FilteredRepoLookupError:
378 except error.FilteredRepoLookupError:
379 raise
379 raise
380 except LookupError:
380 except LookupError:
381 pass
381 pass
382
382
383 try:
383 try:
384 r = int(changeid)
384 r = int(changeid)
385 if str(r) != changeid:
385 if str(r) != changeid:
386 raise ValueError
386 raise ValueError
387 l = len(repo.changelog)
387 l = len(repo.changelog)
388 if r < 0:
388 if r < 0:
389 r += l
389 r += l
390 if r < 0 or r >= l:
390 if r < 0 or r >= l:
391 raise ValueError
391 raise ValueError
392 self._rev = r
392 self._rev = r
393 self._node = repo.changelog.node(r)
393 self._node = repo.changelog.node(r)
394 return
394 return
395 except error.FilteredIndexError:
395 except error.FilteredIndexError:
396 raise
396 raise
397 except (ValueError, OverflowError, IndexError):
397 except (ValueError, OverflowError, IndexError):
398 pass
398 pass
399
399
400 if len(changeid) == 40:
400 if len(changeid) == 40:
401 try:
401 try:
402 self._node = bin(changeid)
402 self._node = bin(changeid)
403 self._rev = repo.changelog.rev(self._node)
403 self._rev = repo.changelog.rev(self._node)
404 return
404 return
405 except error.FilteredLookupError:
405 except error.FilteredLookupError:
406 raise
406 raise
407 except (TypeError, LookupError):
407 except (TypeError, LookupError):
408 pass
408 pass
409
409
410 # lookup bookmarks through the name interface
410 # lookup bookmarks through the name interface
411 try:
411 try:
412 self._node = repo.names.singlenode(repo, changeid)
412 self._node = repo.names.singlenode(repo, changeid)
413 self._rev = repo.changelog.rev(self._node)
413 self._rev = repo.changelog.rev(self._node)
414 return
414 return
415 except KeyError:
415 except KeyError:
416 pass
416 pass
417 except error.FilteredRepoLookupError:
417 except error.FilteredRepoLookupError:
418 raise
418 raise
419 except error.RepoLookupError:
419 except error.RepoLookupError:
420 pass
420 pass
421
421
422 self._node = repo.unfiltered().changelog._partialmatch(changeid)
422 self._node = repo.unfiltered().changelog._partialmatch(changeid)
423 if self._node is not None:
423 if self._node is not None:
424 self._rev = repo.changelog.rev(self._node)
424 self._rev = repo.changelog.rev(self._node)
425 return
425 return
426
426
427 # lookup failed
427 # lookup failed
428 # check if it might have come from damaged dirstate
428 # check if it might have come from damaged dirstate
429 #
429 #
430 # XXX we could avoid the unfiltered if we had a recognizable
430 # XXX we could avoid the unfiltered if we had a recognizable
431 # exception for filtered changeset access
431 # exception for filtered changeset access
432 if changeid in repo.unfiltered().dirstate.parents():
432 if changeid in repo.unfiltered().dirstate.parents():
433 msg = _("working directory has unknown parent '%s'!")
433 msg = _("working directory has unknown parent '%s'!")
434 raise error.Abort(msg % short(changeid))
434 raise error.Abort(msg % short(changeid))
435 try:
435 try:
436 if len(changeid) == 20:
436 if len(changeid) == 20:
437 changeid = hex(changeid)
437 changeid = hex(changeid)
438 except TypeError:
438 except TypeError:
439 pass
439 pass
440 except (error.FilteredIndexError, error.FilteredLookupError,
440 except (error.FilteredIndexError, error.FilteredLookupError,
441 error.FilteredRepoLookupError):
441 error.FilteredRepoLookupError):
442 if repo.filtername == 'visible':
442 if repo.filtername == 'visible':
443 msg = _("hidden revision '%s'") % changeid
443 msg = _("hidden revision '%s'") % changeid
444 hint = _('use --hidden to access hidden revisions')
444 hint = _('use --hidden to access hidden revisions')
445 raise error.FilteredRepoLookupError(msg, hint=hint)
445 raise error.FilteredRepoLookupError(msg, hint=hint)
446 msg = _("filtered revision '%s' (not in '%s' subset)")
446 msg = _("filtered revision '%s' (not in '%s' subset)")
447 msg %= (changeid, repo.filtername)
447 msg %= (changeid, repo.filtername)
448 raise error.FilteredRepoLookupError(msg)
448 raise error.FilteredRepoLookupError(msg)
449 except IndexError:
449 except IndexError:
450 pass
450 pass
451 raise error.RepoLookupError(
451 raise error.RepoLookupError(
452 _("unknown revision '%s'") % changeid)
452 _("unknown revision '%s'") % changeid)
453
453
454 def __hash__(self):
454 def __hash__(self):
455 try:
455 try:
456 return hash(self._rev)
456 return hash(self._rev)
457 except AttributeError:
457 except AttributeError:
458 return id(self)
458 return id(self)
459
459
460 def __nonzero__(self):
460 def __nonzero__(self):
461 return self._rev != nullrev
461 return self._rev != nullrev
462
462
463 @propertycache
463 @propertycache
464 def _changeset(self):
464 def _changeset(self):
465 return self._repo.changelog.read(self.rev())
465 return self._repo.changelog.read(self.rev())
466
466
467 @propertycache
467 @propertycache
468 def _manifest(self):
468 def _manifest(self):
469 return self._repo.manifest.read(self._changeset[0])
469 return self._repo.manifest.read(self._changeset[0])
470
470
471 @propertycache
471 @propertycache
472 def _manifestdelta(self):
472 def _manifestdelta(self):
473 return self._repo.manifest.readdelta(self._changeset[0])
473 return self._repo.manifest.readdelta(self._changeset[0])
474
474
475 @propertycache
475 @propertycache
476 def _parents(self):
476 def _parents(self):
477 p = self._repo.changelog.parentrevs(self._rev)
477 p = self._repo.changelog.parentrevs(self._rev)
478 if p[1] == nullrev:
478 if p[1] == nullrev:
479 p = p[:-1]
479 p = p[:-1]
480 return [changectx(self._repo, x) for x in p]
480 return [changectx(self._repo, x) for x in p]
481
481
482 def changeset(self):
482 def changeset(self):
483 return self._changeset
483 return self._changeset
484 def manifestnode(self):
484 def manifestnode(self):
485 return self._changeset[0]
485 return self._changeset[0]
486
486
487 def user(self):
487 def user(self):
488 return self._changeset[1]
488 return self._changeset[1]
489 def date(self):
489 def date(self):
490 return self._changeset[2]
490 return self._changeset[2]
491 def files(self):
491 def files(self):
492 return self._changeset[3]
492 return self._changeset[3]
493 def description(self):
493 def description(self):
494 return self._changeset[4]
494 return self._changeset[4]
495 def branch(self):
495 def branch(self):
496 return encoding.tolocal(self._changeset[5].get("branch"))
496 return encoding.tolocal(self._changeset[5].get("branch"))
497 def closesbranch(self):
497 def closesbranch(self):
498 return 'close' in self._changeset[5]
498 return 'close' in self._changeset[5]
499 def extra(self):
499 def extra(self):
500 return self._changeset[5]
500 return self._changeset[5]
501 def tags(self):
501 def tags(self):
502 return self._repo.nodetags(self._node)
502 return self._repo.nodetags(self._node)
503 def bookmarks(self):
503 def bookmarks(self):
504 return self._repo.nodebookmarks(self._node)
504 return self._repo.nodebookmarks(self._node)
505 def phase(self):
505 def phase(self):
506 return self._repo._phasecache.phase(self._repo, self._rev)
506 return self._repo._phasecache.phase(self._repo, self._rev)
507 def hidden(self):
507 def hidden(self):
508 return self._rev in repoview.filterrevs(self._repo, 'visible')
508 return self._rev in repoview.filterrevs(self._repo, 'visible')
509
509
510 def children(self):
510 def children(self):
511 """return contexts for each child changeset"""
511 """return contexts for each child changeset"""
512 c = self._repo.changelog.children(self._node)
512 c = self._repo.changelog.children(self._node)
513 return [changectx(self._repo, x) for x in c]
513 return [changectx(self._repo, x) for x in c]
514
514
515 def ancestors(self):
515 def ancestors(self):
516 for a in self._repo.changelog.ancestors([self._rev]):
516 for a in self._repo.changelog.ancestors([self._rev]):
517 yield changectx(self._repo, a)
517 yield changectx(self._repo, a)
518
518
519 def descendants(self):
519 def descendants(self):
520 for d in self._repo.changelog.descendants([self._rev]):
520 for d in self._repo.changelog.descendants([self._rev]):
521 yield changectx(self._repo, d)
521 yield changectx(self._repo, d)
522
522
523 def filectx(self, path, fileid=None, filelog=None):
523 def filectx(self, path, fileid=None, filelog=None):
524 """get a file context from this changeset"""
524 """get a file context from this changeset"""
525 if fileid is None:
525 if fileid is None:
526 fileid = self.filenode(path)
526 fileid = self.filenode(path)
527 return filectx(self._repo, path, fileid=fileid,
527 return filectx(self._repo, path, fileid=fileid,
528 changectx=self, filelog=filelog)
528 changectx=self, filelog=filelog)
529
529
530 def ancestor(self, c2, warn=False):
530 def ancestor(self, c2, warn=False):
531 """return the "best" ancestor context of self and c2
531 """return the "best" ancestor context of self and c2
532
532
533 If there are multiple candidates, it will show a message and check
533 If there are multiple candidates, it will show a message and check
534 merge.preferancestor configuration before falling back to the
534 merge.preferancestor configuration before falling back to the
535 revlog ancestor."""
535 revlog ancestor."""
536 # deal with workingctxs
536 # deal with workingctxs
537 n2 = c2._node
537 n2 = c2._node
538 if n2 is None:
538 if n2 is None:
539 n2 = c2._parents[0]._node
539 n2 = c2._parents[0]._node
540 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
540 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
541 if not cahs:
541 if not cahs:
542 anc = nullid
542 anc = nullid
543 elif len(cahs) == 1:
543 elif len(cahs) == 1:
544 anc = cahs[0]
544 anc = cahs[0]
545 else:
545 else:
546 for r in self._repo.ui.configlist('merge', 'preferancestor'):
546 for r in self._repo.ui.configlist('merge', 'preferancestor'):
547 try:
547 try:
548 ctx = changectx(self._repo, r)
548 ctx = changectx(self._repo, r)
549 except error.RepoLookupError:
549 except error.RepoLookupError:
550 continue
550 continue
551 anc = ctx.node()
551 anc = ctx.node()
552 if anc in cahs:
552 if anc in cahs:
553 break
553 break
554 else:
554 else:
555 anc = self._repo.changelog.ancestor(self._node, n2)
555 anc = self._repo.changelog.ancestor(self._node, n2)
556 if warn:
556 if warn:
557 self._repo.ui.status(
557 self._repo.ui.status(
558 (_("note: using %s as ancestor of %s and %s\n") %
558 (_("note: using %s as ancestor of %s and %s\n") %
559 (short(anc), short(self._node), short(n2))) +
559 (short(anc), short(self._node), short(n2))) +
560 ''.join(_(" alternatively, use --config "
560 ''.join(_(" alternatively, use --config "
561 "merge.preferancestor=%s\n") %
561 "merge.preferancestor=%s\n") %
562 short(n) for n in sorted(cahs) if n != anc))
562 short(n) for n in sorted(cahs) if n != anc))
563 return changectx(self._repo, anc)
563 return changectx(self._repo, anc)
564
564
565 def descendant(self, other):
565 def descendant(self, other):
566 """True if other is descendant of this changeset"""
566 """True if other is descendant of this changeset"""
567 return self._repo.changelog.descendant(self._rev, other._rev)
567 return self._repo.changelog.descendant(self._rev, other._rev)
568
568
569 def walk(self, match):
569 def walk(self, match):
570 fset = set(match.files())
570 fset = set(match.files())
571 # for dirstate.walk, files=['.'] means "walk the whole tree".
571 # for dirstate.walk, files=['.'] means "walk the whole tree".
572 # follow that here, too
572 # follow that here, too
573 fset.discard('.')
573 fset.discard('.')
574
574
575 # avoid the entire walk if we're only looking for specific files
575 # avoid the entire walk if we're only looking for specific files
576 if fset and not match.anypats():
576 if fset and not match.anypats():
577 if util.all([fn in self for fn in fset]):
577 if util.all([fn in self for fn in fset]):
578 for fn in sorted(fset):
578 for fn in sorted(fset):
579 if match(fn):
579 if match(fn):
580 yield fn
580 yield fn
581 raise StopIteration
581 raise StopIteration
582
582
583 for fn in self:
583 for fn in self:
584 if fn in fset:
584 if fn in fset:
585 # specified pattern is the exact name
585 # specified pattern is the exact name
586 fset.remove(fn)
586 fset.remove(fn)
587 if match(fn):
587 if match(fn):
588 yield fn
588 yield fn
589 for fn in sorted(fset):
589 for fn in sorted(fset):
590 if fn in self._dirs:
590 if fn in self._dirs:
591 # specified pattern is a directory
591 # specified pattern is a directory
592 continue
592 continue
593 match.bad(fn, _('no such file in rev %s') % self)
593 match.bad(fn, _('no such file in rev %s') % self)
594
594
595 def matches(self, match):
595 def matches(self, match):
596 return self.walk(match)
596 return self.walk(match)
597
597
598 class basefilectx(object):
598 class basefilectx(object):
599 """A filecontext object represents the common logic for its children:
599 """A filecontext object represents the common logic for its children:
600 filectx: read-only access to a filerevision that is already present
600 filectx: read-only access to a filerevision that is already present
601 in the repo,
601 in the repo,
602 workingfilectx: a filecontext that represents files from the working
602 workingfilectx: a filecontext that represents files from the working
603 directory,
603 directory,
604 memfilectx: a filecontext that represents files in-memory."""
604 memfilectx: a filecontext that represents files in-memory."""
605 def __new__(cls, repo, path, *args, **kwargs):
605 def __new__(cls, repo, path, *args, **kwargs):
606 return super(basefilectx, cls).__new__(cls)
606 return super(basefilectx, cls).__new__(cls)
607
607
608 @propertycache
608 @propertycache
609 def _filelog(self):
609 def _filelog(self):
610 return self._repo.file(self._path)
610 return self._repo.file(self._path)
611
611
612 @propertycache
612 @propertycache
613 def _changeid(self):
613 def _changeid(self):
614 if '_changeid' in self.__dict__:
614 if '_changeid' in self.__dict__:
615 return self._changeid
615 return self._changeid
616 elif '_changectx' in self.__dict__:
616 elif '_changectx' in self.__dict__:
617 return self._changectx.rev()
617 return self._changectx.rev()
618 else:
618 else:
619 return self._filelog.linkrev(self._filerev)
619 return self._filelog.linkrev(self._filerev)
620
620
621 @propertycache
621 @propertycache
622 def _filenode(self):
622 def _filenode(self):
623 if '_fileid' in self.__dict__:
623 if '_fileid' in self.__dict__:
624 return self._filelog.lookup(self._fileid)
624 return self._filelog.lookup(self._fileid)
625 else:
625 else:
626 return self._changectx.filenode(self._path)
626 return self._changectx.filenode(self._path)
627
627
628 @propertycache
628 @propertycache
629 def _filerev(self):
629 def _filerev(self):
630 return self._filelog.rev(self._filenode)
630 return self._filelog.rev(self._filenode)
631
631
632 @propertycache
632 @propertycache
633 def _repopath(self):
633 def _repopath(self):
634 return self._path
634 return self._path
635
635
636 def __nonzero__(self):
636 def __nonzero__(self):
637 try:
637 try:
638 self._filenode
638 self._filenode
639 return True
639 return True
640 except error.LookupError:
640 except error.LookupError:
641 # file is missing
641 # file is missing
642 return False
642 return False
643
643
644 def __str__(self):
644 def __str__(self):
645 return "%s@%s" % (self.path(), self._changectx)
645 return "%s@%s" % (self.path(), self._changectx)
646
646
647 def __repr__(self):
647 def __repr__(self):
648 return "<%s %s>" % (type(self).__name__, str(self))
648 return "<%s %s>" % (type(self).__name__, str(self))
649
649
650 def __hash__(self):
650 def __hash__(self):
651 try:
651 try:
652 return hash((self._path, self._filenode))
652 return hash((self._path, self._filenode))
653 except AttributeError:
653 except AttributeError:
654 return id(self)
654 return id(self)
655
655
656 def __eq__(self, other):
656 def __eq__(self, other):
657 try:
657 try:
658 return (type(self) == type(other) and self._path == other._path
658 return (type(self) == type(other) and self._path == other._path
659 and self._filenode == other._filenode)
659 and self._filenode == other._filenode)
660 except AttributeError:
660 except AttributeError:
661 return False
661 return False
662
662
663 def __ne__(self, other):
663 def __ne__(self, other):
664 return not (self == other)
664 return not (self == other)
665
665
666 def filerev(self):
666 def filerev(self):
667 return self._filerev
667 return self._filerev
668 def filenode(self):
668 def filenode(self):
669 return self._filenode
669 return self._filenode
670 def flags(self):
670 def flags(self):
671 return self._changectx.flags(self._path)
671 return self._changectx.flags(self._path)
672 def filelog(self):
672 def filelog(self):
673 return self._filelog
673 return self._filelog
674 def rev(self):
674 def rev(self):
675 return self._changeid
675 return self._changeid
676 def linkrev(self):
676 def linkrev(self):
677 return self._filelog.linkrev(self._filerev)
677 return self._filelog.linkrev(self._filerev)
678 def node(self):
678 def node(self):
679 return self._changectx.node()
679 return self._changectx.node()
680 def hex(self):
680 def hex(self):
681 return self._changectx.hex()
681 return self._changectx.hex()
682 def user(self):
682 def user(self):
683 return self._changectx.user()
683 return self._changectx.user()
684 def date(self):
684 def date(self):
685 return self._changectx.date()
685 return self._changectx.date()
686 def files(self):
686 def files(self):
687 return self._changectx.files()
687 return self._changectx.files()
688 def description(self):
688 def description(self):
689 return self._changectx.description()
689 return self._changectx.description()
690 def branch(self):
690 def branch(self):
691 return self._changectx.branch()
691 return self._changectx.branch()
692 def extra(self):
692 def extra(self):
693 return self._changectx.extra()
693 return self._changectx.extra()
694 def phase(self):
694 def phase(self):
695 return self._changectx.phase()
695 return self._changectx.phase()
696 def phasestr(self):
696 def phasestr(self):
697 return self._changectx.phasestr()
697 return self._changectx.phasestr()
698 def manifest(self):
698 def manifest(self):
699 return self._changectx.manifest()
699 return self._changectx.manifest()
700 def changectx(self):
700 def changectx(self):
701 return self._changectx
701 return self._changectx
702
702
703 def path(self):
703 def path(self):
704 return self._path
704 return self._path
705
705
706 def isbinary(self):
706 def isbinary(self):
707 try:
707 try:
708 return util.binary(self.data())
708 return util.binary(self.data())
709 except IOError:
709 except IOError:
710 return False
710 return False
711 def isexec(self):
711 def isexec(self):
712 return 'x' in self.flags()
712 return 'x' in self.flags()
713 def islink(self):
713 def islink(self):
714 return 'l' in self.flags()
714 return 'l' in self.flags()
715
715
716 def cmp(self, fctx):
716 def cmp(self, fctx):
717 """compare with other file context
717 """compare with other file context
718
718
719 returns True if different than fctx.
719 returns True if different than fctx.
720 """
720 """
721 if (fctx._filerev is None
721 if (fctx._filerev is None
722 and (self._repo._encodefilterpats
722 and (self._repo._encodefilterpats
723 # if file data starts with '\1\n', empty metadata block is
723 # if file data starts with '\1\n', empty metadata block is
724 # prepended, which adds 4 bytes to filelog.size().
724 # prepended, which adds 4 bytes to filelog.size().
725 or self.size() - 4 == fctx.size())
725 or self.size() - 4 == fctx.size())
726 or self.size() == fctx.size()):
726 or self.size() == fctx.size()):
727 return self._filelog.cmp(self._filenode, fctx.data())
727 return self._filelog.cmp(self._filenode, fctx.data())
728
728
729 return True
729 return True
730
730
731 def parents(self):
731 def parents(self):
732 _path = self._path
732 _path = self._path
733 fl = self._filelog
733 fl = self._filelog
734 pl = [(_path, n, fl) for n in self._filelog.parents(self._filenode)]
734 pl = [(_path, n, fl) for n in self._filelog.parents(self._filenode)]
735
735
736 r = self._filelog.renamed(self._filenode)
736 r = self._filelog.renamed(self._filenode)
737 if r:
737 if r:
738 pl[0] = (r[0], r[1], None)
738 pl[0] = (r[0], r[1], None)
739
739
740 return [filectx(self._repo, p, fileid=n, filelog=l)
740 return [filectx(self._repo, p, fileid=n, filelog=l)
741 for p, n, l in pl if n != nullid]
741 for p, n, l in pl if n != nullid]
742
742
743 def p1(self):
743 def p1(self):
744 return self.parents()[0]
744 return self.parents()[0]
745
745
746 def p2(self):
746 def p2(self):
747 p = self.parents()
747 p = self.parents()
748 if len(p) == 2:
748 if len(p) == 2:
749 return p[1]
749 return p[1]
750 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
750 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
751
751
752 def annotate(self, follow=False, linenumber=None, diffopts=None):
752 def annotate(self, follow=False, linenumber=None, diffopts=None):
753 '''returns a list of tuples of (ctx, line) for each line
753 '''returns a list of tuples of (ctx, line) for each line
754 in the file, where ctx is the filectx of the node where
754 in the file, where ctx is the filectx of the node where
755 that line was last changed.
755 that line was last changed.
756 This returns tuples of ((ctx, linenumber), line) for each line,
756 This returns tuples of ((ctx, linenumber), line) for each line,
757 if "linenumber" parameter is NOT "None".
757 if "linenumber" parameter is NOT "None".
758 In such tuples, linenumber means one at the first appearance
758 In such tuples, linenumber means one at the first appearance
759 in the managed file.
759 in the managed file.
760 To reduce annotation cost,
760 To reduce annotation cost,
761 this returns fixed value(False is used) as linenumber,
761 this returns fixed value(False is used) as linenumber,
762 if "linenumber" parameter is "False".'''
762 if "linenumber" parameter is "False".'''
763
763
764 if linenumber is None:
764 if linenumber is None:
765 def decorate(text, rev):
765 def decorate(text, rev):
766 return ([rev] * len(text.splitlines()), text)
766 return ([rev] * len(text.splitlines()), text)
767 elif linenumber:
767 elif linenumber:
768 def decorate(text, rev):
768 def decorate(text, rev):
769 size = len(text.splitlines())
769 size = len(text.splitlines())
770 return ([(rev, i) for i in xrange(1, size + 1)], text)
770 return ([(rev, i) for i in xrange(1, size + 1)], text)
771 else:
771 else:
772 def decorate(text, rev):
772 def decorate(text, rev):
773 return ([(rev, False)] * len(text.splitlines()), text)
773 return ([(rev, False)] * len(text.splitlines()), text)
774
774
775 def pair(parent, child):
775 def pair(parent, child):
776 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
776 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
777 refine=True)
777 refine=True)
778 for (a1, a2, b1, b2), t in blocks:
778 for (a1, a2, b1, b2), t in blocks:
779 # Changed blocks ('!') or blocks made only of blank lines ('~')
779 # Changed blocks ('!') or blocks made only of blank lines ('~')
780 # belong to the child.
780 # belong to the child.
781 if t == '=':
781 if t == '=':
782 child[0][b1:b2] = parent[0][a1:a2]
782 child[0][b1:b2] = parent[0][a1:a2]
783 return child
783 return child
784
784
785 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
785 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
786
786
787 def parents(f):
787 def parents(f):
788 pl = f.parents()
788 pl = f.parents()
789
789
790 # Don't return renamed parents if we aren't following.
790 # Don't return renamed parents if we aren't following.
791 if not follow:
791 if not follow:
792 pl = [p for p in pl if p.path() == f.path()]
792 pl = [p for p in pl if p.path() == f.path()]
793
793
794 # renamed filectx won't have a filelog yet, so set it
794 # renamed filectx won't have a filelog yet, so set it
795 # from the cache to save time
795 # from the cache to save time
796 for p in pl:
796 for p in pl:
797 if not '_filelog' in p.__dict__:
797 if not '_filelog' in p.__dict__:
798 p._filelog = getlog(p.path())
798 p._filelog = getlog(p.path())
799
799
800 return pl
800 return pl
801
801
802 # use linkrev to find the first changeset where self appeared
802 # use linkrev to find the first changeset where self appeared
803 if self.rev() != self.linkrev():
803 if self.rev() != self.linkrev():
804 base = self.filectx(self.filenode())
804 base = self.filectx(self.filenode())
805 else:
805 else:
806 base = self
806 base = self
807
807
808 # This algorithm would prefer to be recursive, but Python is a
808 # This algorithm would prefer to be recursive, but Python is a
809 # bit recursion-hostile. Instead we do an iterative
809 # bit recursion-hostile. Instead we do an iterative
810 # depth-first search.
810 # depth-first search.
811
811
812 visit = [base]
812 visit = [base]
813 hist = {}
813 hist = {}
814 pcache = {}
814 pcache = {}
815 needed = {base: 1}
815 needed = {base: 1}
816 while visit:
816 while visit:
817 f = visit[-1]
817 f = visit[-1]
818 pcached = f in pcache
818 pcached = f in pcache
819 if not pcached:
819 if not pcached:
820 pcache[f] = parents(f)
820 pcache[f] = parents(f)
821
821
822 ready = True
822 ready = True
823 pl = pcache[f]
823 pl = pcache[f]
824 for p in pl:
824 for p in pl:
825 if p not in hist:
825 if p not in hist:
826 ready = False
826 ready = False
827 visit.append(p)
827 visit.append(p)
828 if not pcached:
828 if not pcached:
829 needed[p] = needed.get(p, 0) + 1
829 needed[p] = needed.get(p, 0) + 1
830 if ready:
830 if ready:
831 visit.pop()
831 visit.pop()
832 reusable = f in hist
832 reusable = f in hist
833 if reusable:
833 if reusable:
834 curr = hist[f]
834 curr = hist[f]
835 else:
835 else:
836 curr = decorate(f.data(), f)
836 curr = decorate(f.data(), f)
837 for p in pl:
837 for p in pl:
838 if not reusable:
838 if not reusable:
839 curr = pair(hist[p], curr)
839 curr = pair(hist[p], curr)
840 if needed[p] == 1:
840 if needed[p] == 1:
841 del hist[p]
841 del hist[p]
842 del needed[p]
842 del needed[p]
843 else:
843 else:
844 needed[p] -= 1
844 needed[p] -= 1
845
845
846 hist[f] = curr
846 hist[f] = curr
847 pcache[f] = []
847 pcache[f] = []
848
848
849 return zip(hist[base][0], hist[base][1].splitlines(True))
849 return zip(hist[base][0], hist[base][1].splitlines(True))
850
850
851 def ancestors(self, followfirst=False):
851 def ancestors(self, followfirst=False):
852 visit = {}
852 visit = {}
853 c = self
853 c = self
854 cut = followfirst and 1 or None
854 cut = followfirst and 1 or None
855 while True:
855 while True:
856 for parent in c.parents()[:cut]:
856 for parent in c.parents()[:cut]:
857 visit[(parent.rev(), parent.node())] = parent
857 visit[(parent.rev(), parent.node())] = parent
858 if not visit:
858 if not visit:
859 break
859 break
860 c = visit.pop(max(visit))
860 c = visit.pop(max(visit))
861 yield c
861 yield c
862
862
863 class filectx(basefilectx):
863 class filectx(basefilectx):
864 """A filecontext object makes access to data related to a particular
864 """A filecontext object makes access to data related to a particular
865 filerevision convenient."""
865 filerevision convenient."""
866 def __init__(self, repo, path, changeid=None, fileid=None,
866 def __init__(self, repo, path, changeid=None, fileid=None,
867 filelog=None, changectx=None):
867 filelog=None, changectx=None):
868 """changeid can be a changeset revision, node, or tag.
868 """changeid can be a changeset revision, node, or tag.
869 fileid can be a file revision or node."""
869 fileid can be a file revision or node."""
870 self._repo = repo
870 self._repo = repo
871 self._path = path
871 self._path = path
872
872
873 assert (changeid is not None
873 assert (changeid is not None
874 or fileid is not None
874 or fileid is not None
875 or changectx is not None), \
875 or changectx is not None), \
876 ("bad args: changeid=%r, fileid=%r, changectx=%r"
876 ("bad args: changeid=%r, fileid=%r, changectx=%r"
877 % (changeid, fileid, changectx))
877 % (changeid, fileid, changectx))
878
878
879 if filelog is not None:
879 if filelog is not None:
880 self._filelog = filelog
880 self._filelog = filelog
881
881
882 if changeid is not None:
882 if changeid is not None:
883 self._changeid = changeid
883 self._changeid = changeid
884 if changectx is not None:
884 if changectx is not None:
885 self._changectx = changectx
885 self._changectx = changectx
886 if fileid is not None:
886 if fileid is not None:
887 self._fileid = fileid
887 self._fileid = fileid
888
888
889 @propertycache
889 @propertycache
890 def _changectx(self):
890 def _changectx(self):
891 try:
891 try:
892 return changectx(self._repo, self._changeid)
892 return changectx(self._repo, self._changeid)
893 except error.RepoLookupError:
893 except error.RepoLookupError:
894 # Linkrev may point to any revision in the repository. When the
894 # Linkrev may point to any revision in the repository. When the
895 # repository is filtered this may lead to `filectx` trying to build
895 # repository is filtered this may lead to `filectx` trying to build
896 # `changectx` for filtered revision. In such case we fallback to
896 # `changectx` for filtered revision. In such case we fallback to
897 # creating `changectx` on the unfiltered version of the reposition.
897 # creating `changectx` on the unfiltered version of the reposition.
898 # This fallback should not be an issue because `changectx` from
898 # This fallback should not be an issue because `changectx` from
899 # `filectx` are not used in complex operations that care about
899 # `filectx` are not used in complex operations that care about
900 # filtering.
900 # filtering.
901 #
901 #
902 # This fallback is a cheap and dirty fix that prevent several
902 # This fallback is a cheap and dirty fix that prevent several
903 # crashes. It does not ensure the behavior is correct. However the
903 # crashes. It does not ensure the behavior is correct. However the
904 # behavior was not correct before filtering either and "incorrect
904 # behavior was not correct before filtering either and "incorrect
905 # behavior" is seen as better as "crash"
905 # behavior" is seen as better as "crash"
906 #
906 #
907 # Linkrevs have several serious troubles with filtering that are
907 # Linkrevs have several serious troubles with filtering that are
908 # complicated to solve. Proper handling of the issue here should be
908 # complicated to solve. Proper handling of the issue here should be
909 # considered when solving linkrev issue are on the table.
909 # considered when solving linkrev issue are on the table.
910 return changectx(self._repo.unfiltered(), self._changeid)
910 return changectx(self._repo.unfiltered(), self._changeid)
911
911
912 def filectx(self, fileid):
912 def filectx(self, fileid):
913 '''opens an arbitrary revision of the file without
913 '''opens an arbitrary revision of the file without
914 opening a new filelog'''
914 opening a new filelog'''
915 return filectx(self._repo, self._path, fileid=fileid,
915 return filectx(self._repo, self._path, fileid=fileid,
916 filelog=self._filelog)
916 filelog=self._filelog)
917
917
918 def data(self):
918 def data(self):
919 try:
919 try:
920 return self._filelog.read(self._filenode)
920 return self._filelog.read(self._filenode)
921 except error.CensoredNodeError:
921 except error.CensoredNodeError:
922 if self._repo.ui.config("censor", "policy", "abort") == "ignore":
922 if self._repo.ui.config("censor", "policy", "abort") == "ignore":
923 return ""
923 return ""
924 raise util.Abort(_("censored node: %s") % short(self._filenode),
924 raise util.Abort(_("censored node: %s") % short(self._filenode),
925 hint=_("set censor.policy to ignore errors"))
925 hint=_("set censor.policy to ignore errors"))
926
926
927 def size(self):
927 def size(self):
928 return self._filelog.size(self._filerev)
928 return self._filelog.size(self._filerev)
929
929
930 def renamed(self):
930 def renamed(self):
931 """check if file was actually renamed in this changeset revision
931 """check if file was actually renamed in this changeset revision
932
932
933 If rename logged in file revision, we report copy for changeset only
933 If rename logged in file revision, we report copy for changeset only
934 if file revisions linkrev points back to the changeset in question
934 if file revisions linkrev points back to the changeset in question
935 or both changeset parents contain different file revisions.
935 or both changeset parents contain different file revisions.
936 """
936 """
937
937
938 renamed = self._filelog.renamed(self._filenode)
938 renamed = self._filelog.renamed(self._filenode)
939 if not renamed:
939 if not renamed:
940 return renamed
940 return renamed
941
941
942 if self.rev() == self.linkrev():
942 if self.rev() == self.linkrev():
943 return renamed
943 return renamed
944
944
945 name = self.path()
945 name = self.path()
946 fnode = self._filenode
946 fnode = self._filenode
947 for p in self._changectx.parents():
947 for p in self._changectx.parents():
948 try:
948 try:
949 if fnode == p.filenode(name):
949 if fnode == p.filenode(name):
950 return None
950 return None
951 except error.LookupError:
951 except error.LookupError:
952 pass
952 pass
953 return renamed
953 return renamed
954
954
955 def children(self):
955 def children(self):
956 # hard for renames
956 # hard for renames
957 c = self._filelog.children(self._filenode)
957 c = self._filelog.children(self._filenode)
958 return [filectx(self._repo, self._path, fileid=x,
958 return [filectx(self._repo, self._path, fileid=x,
959 filelog=self._filelog) for x in c]
959 filelog=self._filelog) for x in c]
960
960
961 class committablectx(basectx):
961 class committablectx(basectx):
962 """A committablectx object provides common functionality for a context that
962 """A committablectx object provides common functionality for a context that
963 wants the ability to commit, e.g. workingctx or memctx."""
963 wants the ability to commit, e.g. workingctx or memctx."""
964 def __init__(self, repo, text="", user=None, date=None, extra=None,
964 def __init__(self, repo, text="", user=None, date=None, extra=None,
965 changes=None):
965 changes=None):
966 self._repo = repo
966 self._repo = repo
967 self._rev = None
967 self._rev = None
968 self._node = None
968 self._node = None
969 self._text = text
969 self._text = text
970 if date:
970 if date:
971 self._date = util.parsedate(date)
971 self._date = util.parsedate(date)
972 if user:
972 if user:
973 self._user = user
973 self._user = user
974 if changes:
974 if changes:
975 self._status = changes
975 self._status = changes
976
976
977 self._extra = {}
977 self._extra = {}
978 if extra:
978 if extra:
979 self._extra = extra.copy()
979 self._extra = extra.copy()
980 if 'branch' not in self._extra:
980 if 'branch' not in self._extra:
981 try:
981 try:
982 branch = encoding.fromlocal(self._repo.dirstate.branch())
982 branch = encoding.fromlocal(self._repo.dirstate.branch())
983 except UnicodeDecodeError:
983 except UnicodeDecodeError:
984 raise util.Abort(_('branch name not in UTF-8!'))
984 raise util.Abort(_('branch name not in UTF-8!'))
985 self._extra['branch'] = branch
985 self._extra['branch'] = branch
986 if self._extra['branch'] == '':
986 if self._extra['branch'] == '':
987 self._extra['branch'] = 'default'
987 self._extra['branch'] = 'default'
988
988
989 def __str__(self):
989 def __str__(self):
990 return str(self._parents[0]) + "+"
990 return str(self._parents[0]) + "+"
991
991
992 def __nonzero__(self):
992 def __nonzero__(self):
993 return True
993 return True
994
994
995 def _buildflagfunc(self):
995 def _buildflagfunc(self):
996 # Create a fallback function for getting file flags when the
996 # Create a fallback function for getting file flags when the
997 # filesystem doesn't support them
997 # filesystem doesn't support them
998
998
999 copiesget = self._repo.dirstate.copies().get
999 copiesget = self._repo.dirstate.copies().get
1000
1000
1001 if len(self._parents) < 2:
1001 if len(self._parents) < 2:
1002 # when we have one parent, it's easy: copy from parent
1002 # when we have one parent, it's easy: copy from parent
1003 man = self._parents[0].manifest()
1003 man = self._parents[0].manifest()
1004 def func(f):
1004 def func(f):
1005 f = copiesget(f, f)
1005 f = copiesget(f, f)
1006 return man.flags(f)
1006 return man.flags(f)
1007 else:
1007 else:
1008 # merges are tricky: we try to reconstruct the unstored
1008 # merges are tricky: we try to reconstruct the unstored
1009 # result from the merge (issue1802)
1009 # result from the merge (issue1802)
1010 p1, p2 = self._parents
1010 p1, p2 = self._parents
1011 pa = p1.ancestor(p2)
1011 pa = p1.ancestor(p2)
1012 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
1012 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
1013
1013
1014 def func(f):
1014 def func(f):
1015 f = copiesget(f, f) # may be wrong for merges with copies
1015 f = copiesget(f, f) # may be wrong for merges with copies
1016 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
1016 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
1017 if fl1 == fl2:
1017 if fl1 == fl2:
1018 return fl1
1018 return fl1
1019 if fl1 == fla:
1019 if fl1 == fla:
1020 return fl2
1020 return fl2
1021 if fl2 == fla:
1021 if fl2 == fla:
1022 return fl1
1022 return fl1
1023 return '' # punt for conflicts
1023 return '' # punt for conflicts
1024
1024
1025 return func
1025 return func
1026
1026
1027 @propertycache
1027 @propertycache
1028 def _flagfunc(self):
1028 def _flagfunc(self):
1029 return self._repo.dirstate.flagfunc(self._buildflagfunc)
1029 return self._repo.dirstate.flagfunc(self._buildflagfunc)
1030
1030
1031 @propertycache
1031 @propertycache
1032 def _manifest(self):
1032 def _manifest(self):
1033 """generate a manifest corresponding to the values in self._status
1033 """generate a manifest corresponding to the values in self._status
1034
1034
1035 This reuse the file nodeid from parent, but we append an extra letter
1035 This reuse the file nodeid from parent, but we append an extra letter
1036 when modified. Modified files get an extra 'm' while added files get
1036 when modified. Modified files get an extra 'm' while added files get
1037 an extra 'a'. This is used by manifests merge to see that files
1037 an extra 'a'. This is used by manifests merge to see that files
1038 are different and by update logic to avoid deleting newly added files.
1038 are different and by update logic to avoid deleting newly added files.
1039 """
1039 """
1040
1040
1041 man1 = self._parents[0].manifest()
1041 man1 = self._parents[0].manifest()
1042 man = man1.copy()
1042 man = man1.copy()
1043 if len(self._parents) > 1:
1043 if len(self._parents) > 1:
1044 man2 = self.p2().manifest()
1044 man2 = self.p2().manifest()
1045 def getman(f):
1045 def getman(f):
1046 if f in man1:
1046 if f in man1:
1047 return man1
1047 return man1
1048 return man2
1048 return man2
1049 else:
1049 else:
1050 getman = lambda f: man1
1050 getman = lambda f: man1
1051
1051
1052 copied = self._repo.dirstate.copies()
1052 copied = self._repo.dirstate.copies()
1053 ff = self._flagfunc
1053 ff = self._flagfunc
1054 for i, l in (("a", self._status.added), ("m", self._status.modified)):
1054 for i, l in (("a", self._status.added), ("m", self._status.modified)):
1055 for f in l:
1055 for f in l:
1056 orig = copied.get(f, f)
1056 orig = copied.get(f, f)
1057 man[f] = getman(orig).get(orig, nullid) + i
1057 man[f] = getman(orig).get(orig, nullid) + i
1058 try:
1058 try:
1059 man.setflag(f, ff(f))
1059 man.setflag(f, ff(f))
1060 except OSError:
1060 except OSError:
1061 pass
1061 pass
1062
1062
1063 for f in self._status.deleted + self._status.removed:
1063 for f in self._status.deleted + self._status.removed:
1064 if f in man:
1064 if f in man:
1065 del man[f]
1065 del man[f]
1066
1066
1067 return man
1067 return man
1068
1068
1069 @propertycache
1069 @propertycache
1070 def _status(self):
1070 def _status(self):
1071 return self._repo.status()
1071 return self._repo.status()
1072
1072
1073 @propertycache
1073 @propertycache
1074 def _user(self):
1074 def _user(self):
1075 return self._repo.ui.username()
1075 return self._repo.ui.username()
1076
1076
1077 @propertycache
1077 @propertycache
1078 def _date(self):
1078 def _date(self):
1079 return util.makedate()
1079 return util.makedate()
1080
1080
1081 def subrev(self, subpath):
1081 def subrev(self, subpath):
1082 return None
1082 return None
1083
1083
1084 def user(self):
1084 def user(self):
1085 return self._user or self._repo.ui.username()
1085 return self._user or self._repo.ui.username()
1086 def date(self):
1086 def date(self):
1087 return self._date
1087 return self._date
1088 def description(self):
1088 def description(self):
1089 return self._text
1089 return self._text
1090 def files(self):
1090 def files(self):
1091 return sorted(self._status.modified + self._status.added +
1091 return sorted(self._status.modified + self._status.added +
1092 self._status.removed)
1092 self._status.removed)
1093
1093
1094 def modified(self):
1094 def modified(self):
1095 return self._status.modified
1095 return self._status.modified
1096 def added(self):
1096 def added(self):
1097 return self._status.added
1097 return self._status.added
1098 def removed(self):
1098 def removed(self):
1099 return self._status.removed
1099 return self._status.removed
1100 def deleted(self):
1100 def deleted(self):
1101 return self._status.deleted
1101 return self._status.deleted
1102 def unknown(self):
1102 def unknown(self):
1103 return self._status.unknown
1103 return self._status.unknown
1104 def ignored(self):
1104 def ignored(self):
1105 return self._status.ignored
1105 return self._status.ignored
1106 def clean(self):
1106 def clean(self):
1107 return self._status.clean
1107 return self._status.clean
1108 def branch(self):
1108 def branch(self):
1109 return encoding.tolocal(self._extra['branch'])
1109 return encoding.tolocal(self._extra['branch'])
1110 def closesbranch(self):
1110 def closesbranch(self):
1111 return 'close' in self._extra
1111 return 'close' in self._extra
1112 def extra(self):
1112 def extra(self):
1113 return self._extra
1113 return self._extra
1114
1114
1115 def tags(self):
1115 def tags(self):
1116 t = []
1116 t = []
1117 for p in self.parents():
1117 for p in self.parents():
1118 t.extend(p.tags())
1118 t.extend(p.tags())
1119 return t
1119 return t
1120
1120
1121 def bookmarks(self):
1121 def bookmarks(self):
1122 b = []
1122 b = []
1123 for p in self.parents():
1123 for p in self.parents():
1124 b.extend(p.bookmarks())
1124 b.extend(p.bookmarks())
1125 return b
1125 return b
1126
1126
1127 def phase(self):
1127 def phase(self):
1128 phase = phases.draft # default phase to draft
1128 phase = phases.draft # default phase to draft
1129 for p in self.parents():
1129 for p in self.parents():
1130 phase = max(phase, p.phase())
1130 phase = max(phase, p.phase())
1131 return phase
1131 return phase
1132
1132
1133 def hidden(self):
1133 def hidden(self):
1134 return False
1134 return False
1135
1135
1136 def children(self):
1136 def children(self):
1137 return []
1137 return []
1138
1138
1139 def flags(self, path):
1139 def flags(self, path):
1140 if '_manifest' in self.__dict__:
1140 if '_manifest' in self.__dict__:
1141 try:
1141 try:
1142 return self._manifest.flags(path)
1142 return self._manifest.flags(path)
1143 except KeyError:
1143 except KeyError:
1144 return ''
1144 return ''
1145
1145
1146 try:
1146 try:
1147 return self._flagfunc(path)
1147 return self._flagfunc(path)
1148 except OSError:
1148 except OSError:
1149 return ''
1149 return ''
1150
1150
1151 def ancestor(self, c2):
1151 def ancestor(self, c2):
1152 """return the "best" ancestor context of self and c2"""
1152 """return the "best" ancestor context of self and c2"""
1153 return self._parents[0].ancestor(c2) # punt on two parents for now
1153 return self._parents[0].ancestor(c2) # punt on two parents for now
1154
1154
1155 def walk(self, match):
1155 def walk(self, match):
1156 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1156 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1157 True, False))
1157 True, False))
1158
1158
1159 def matches(self, match):
1159 def matches(self, match):
1160 return sorted(self._repo.dirstate.matches(match))
1160 return sorted(self._repo.dirstate.matches(match))
1161
1161
1162 def ancestors(self):
1162 def ancestors(self):
1163 for a in self._repo.changelog.ancestors(
1163 for a in self._repo.changelog.ancestors(
1164 [p.rev() for p in self._parents]):
1164 [p.rev() for p in self._parents]):
1165 yield changectx(self._repo, a)
1165 yield changectx(self._repo, a)
1166
1166
1167 def markcommitted(self, node):
1167 def markcommitted(self, node):
1168 """Perform post-commit cleanup necessary after committing this ctx
1168 """Perform post-commit cleanup necessary after committing this ctx
1169
1169
1170 Specifically, this updates backing stores this working context
1170 Specifically, this updates backing stores this working context
1171 wraps to reflect the fact that the changes reflected by this
1171 wraps to reflect the fact that the changes reflected by this
1172 workingctx have been committed. For example, it marks
1172 workingctx have been committed. For example, it marks
1173 modified and added files as normal in the dirstate.
1173 modified and added files as normal in the dirstate.
1174
1174
1175 """
1175 """
1176
1176
1177 self._repo.dirstate.beginparentchange()
1177 self._repo.dirstate.beginparentchange()
1178 for f in self.modified() + self.added():
1178 for f in self.modified() + self.added():
1179 self._repo.dirstate.normal(f)
1179 self._repo.dirstate.normal(f)
1180 for f in self.removed():
1180 for f in self.removed():
1181 self._repo.dirstate.drop(f)
1181 self._repo.dirstate.drop(f)
1182 self._repo.dirstate.setparents(node)
1182 self._repo.dirstate.setparents(node)
1183 self._repo.dirstate.endparentchange()
1183 self._repo.dirstate.endparentchange()
1184
1184
1185 def dirs(self):
1185 def dirs(self):
1186 return self._repo.dirstate.dirs()
1186 return self._repo.dirstate.dirs()
1187
1187
1188 class workingctx(committablectx):
1188 class workingctx(committablectx):
1189 """A workingctx object makes access to data related to
1189 """A workingctx object makes access to data related to
1190 the current working directory convenient.
1190 the current working directory convenient.
1191 date - any valid date string or (unixtime, offset), or None.
1191 date - any valid date string or (unixtime, offset), or None.
1192 user - username string, or None.
1192 user - username string, or None.
1193 extra - a dictionary of extra values, or None.
1193 extra - a dictionary of extra values, or None.
1194 changes - a list of file lists as returned by localrepo.status()
1194 changes - a list of file lists as returned by localrepo.status()
1195 or None to use the repository status.
1195 or None to use the repository status.
1196 """
1196 """
1197 def __init__(self, repo, text="", user=None, date=None, extra=None,
1197 def __init__(self, repo, text="", user=None, date=None, extra=None,
1198 changes=None):
1198 changes=None):
1199 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1199 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1200
1200
1201 def __iter__(self):
1201 def __iter__(self):
1202 d = self._repo.dirstate
1202 d = self._repo.dirstate
1203 for f in d:
1203 for f in d:
1204 if d[f] != 'r':
1204 if d[f] != 'r':
1205 yield f
1205 yield f
1206
1206
1207 def __contains__(self, key):
1207 def __contains__(self, key):
1208 return self._repo.dirstate[key] not in "?r"
1208 return self._repo.dirstate[key] not in "?r"
1209
1209
1210 @propertycache
1210 @propertycache
1211 def _parents(self):
1211 def _parents(self):
1212 p = self._repo.dirstate.parents()
1212 p = self._repo.dirstate.parents()
1213 if p[1] == nullid:
1213 if p[1] == nullid:
1214 p = p[:-1]
1214 p = p[:-1]
1215 return [changectx(self._repo, x) for x in p]
1215 return [changectx(self._repo, x) for x in p]
1216
1216
1217 def filectx(self, path, filelog=None):
1217 def filectx(self, path, filelog=None):
1218 """get a file context from the working directory"""
1218 """get a file context from the working directory"""
1219 return workingfilectx(self._repo, path, workingctx=self,
1219 return workingfilectx(self._repo, path, workingctx=self,
1220 filelog=filelog)
1220 filelog=filelog)
1221
1221
1222 def dirty(self, missing=False, merge=True, branch=True):
1222 def dirty(self, missing=False, merge=True, branch=True):
1223 "check whether a working directory is modified"
1223 "check whether a working directory is modified"
1224 # check subrepos first
1224 # check subrepos first
1225 for s in sorted(self.substate):
1225 for s in sorted(self.substate):
1226 if self.sub(s).dirty():
1226 if self.sub(s).dirty():
1227 return True
1227 return True
1228 # check current working dir
1228 # check current working dir
1229 return ((merge and self.p2()) or
1229 return ((merge and self.p2()) or
1230 (branch and self.branch() != self.p1().branch()) or
1230 (branch and self.branch() != self.p1().branch()) or
1231 self.modified() or self.added() or self.removed() or
1231 self.modified() or self.added() or self.removed() or
1232 (missing and self.deleted()))
1232 (missing and self.deleted()))
1233
1233
1234 def add(self, list, prefix=""):
1234 def add(self, list, prefix=""):
1235 join = lambda f: os.path.join(prefix, f)
1235 join = lambda f: os.path.join(prefix, f)
1236 wlock = self._repo.wlock()
1236 wlock = self._repo.wlock()
1237 ui, ds = self._repo.ui, self._repo.dirstate
1237 ui, ds = self._repo.ui, self._repo.dirstate
1238 try:
1238 try:
1239 rejected = []
1239 rejected = []
1240 lstat = self._repo.wvfs.lstat
1240 lstat = self._repo.wvfs.lstat
1241 for f in list:
1241 for f in list:
1242 scmutil.checkportable(ui, join(f))
1242 scmutil.checkportable(ui, join(f))
1243 try:
1243 try:
1244 st = lstat(f)
1244 st = lstat(f)
1245 except OSError:
1245 except OSError:
1246 ui.warn(_("%s does not exist!\n") % join(f))
1246 ui.warn(_("%s does not exist!\n") % join(f))
1247 rejected.append(f)
1247 rejected.append(f)
1248 continue
1248 continue
1249 if st.st_size > 10000000:
1249 if st.st_size > 10000000:
1250 ui.warn(_("%s: up to %d MB of RAM may be required "
1250 ui.warn(_("%s: up to %d MB of RAM may be required "
1251 "to manage this file\n"
1251 "to manage this file\n"
1252 "(use 'hg revert %s' to cancel the "
1252 "(use 'hg revert %s' to cancel the "
1253 "pending addition)\n")
1253 "pending addition)\n")
1254 % (f, 3 * st.st_size // 1000000, join(f)))
1254 % (f, 3 * st.st_size // 1000000, join(f)))
1255 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1255 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1256 ui.warn(_("%s not added: only files and symlinks "
1256 ui.warn(_("%s not added: only files and symlinks "
1257 "supported currently\n") % join(f))
1257 "supported currently\n") % join(f))
1258 rejected.append(f)
1258 rejected.append(f)
1259 elif ds[f] in 'amn':
1259 elif ds[f] in 'amn':
1260 ui.warn(_("%s already tracked!\n") % join(f))
1260 ui.warn(_("%s already tracked!\n") % join(f))
1261 elif ds[f] == 'r':
1261 elif ds[f] == 'r':
1262 ds.normallookup(f)
1262 ds.normallookup(f)
1263 else:
1263 else:
1264 ds.add(f)
1264 ds.add(f)
1265 return rejected
1265 return rejected
1266 finally:
1266 finally:
1267 wlock.release()
1267 wlock.release()
1268
1268
1269 def forget(self, files, prefix=""):
1269 def forget(self, files, prefix=""):
1270 join = lambda f: os.path.join(prefix, f)
1270 join = lambda f: os.path.join(prefix, f)
1271 wlock = self._repo.wlock()
1271 wlock = self._repo.wlock()
1272 try:
1272 try:
1273 rejected = []
1273 rejected = []
1274 for f in files:
1274 for f in files:
1275 if f not in self._repo.dirstate:
1275 if f not in self._repo.dirstate:
1276 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1276 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1277 rejected.append(f)
1277 rejected.append(f)
1278 elif self._repo.dirstate[f] != 'a':
1278 elif self._repo.dirstate[f] != 'a':
1279 self._repo.dirstate.remove(f)
1279 self._repo.dirstate.remove(f)
1280 else:
1280 else:
1281 self._repo.dirstate.drop(f)
1281 self._repo.dirstate.drop(f)
1282 return rejected
1282 return rejected
1283 finally:
1283 finally:
1284 wlock.release()
1284 wlock.release()
1285
1285
1286 def undelete(self, list):
1286 def undelete(self, list):
1287 pctxs = self.parents()
1287 pctxs = self.parents()
1288 wlock = self._repo.wlock()
1288 wlock = self._repo.wlock()
1289 try:
1289 try:
1290 for f in list:
1290 for f in list:
1291 if self._repo.dirstate[f] != 'r':
1291 if self._repo.dirstate[f] != 'r':
1292 self._repo.ui.warn(_("%s not removed!\n") % f)
1292 self._repo.ui.warn(_("%s not removed!\n") % f)
1293 else:
1293 else:
1294 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1294 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1295 t = fctx.data()
1295 t = fctx.data()
1296 self._repo.wwrite(f, t, fctx.flags())
1296 self._repo.wwrite(f, t, fctx.flags())
1297 self._repo.dirstate.normal(f)
1297 self._repo.dirstate.normal(f)
1298 finally:
1298 finally:
1299 wlock.release()
1299 wlock.release()
1300
1300
1301 def copy(self, source, dest):
1301 def copy(self, source, dest):
1302 try:
1302 try:
1303 st = self._repo.wvfs.lstat(dest)
1303 st = self._repo.wvfs.lstat(dest)
1304 except OSError, err:
1304 except OSError, err:
1305 if err.errno != errno.ENOENT:
1305 if err.errno != errno.ENOENT:
1306 raise
1306 raise
1307 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1307 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1308 return
1308 return
1309 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1309 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1310 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1310 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1311 "symbolic link\n") % dest)
1311 "symbolic link\n") % dest)
1312 else:
1312 else:
1313 wlock = self._repo.wlock()
1313 wlock = self._repo.wlock()
1314 try:
1314 try:
1315 if self._repo.dirstate[dest] in '?':
1315 if self._repo.dirstate[dest] in '?':
1316 self._repo.dirstate.add(dest)
1316 self._repo.dirstate.add(dest)
1317 elif self._repo.dirstate[dest] in 'r':
1317 elif self._repo.dirstate[dest] in 'r':
1318 self._repo.dirstate.normallookup(dest)
1318 self._repo.dirstate.normallookup(dest)
1319 self._repo.dirstate.copy(source, dest)
1319 self._repo.dirstate.copy(source, dest)
1320 finally:
1320 finally:
1321 wlock.release()
1321 wlock.release()
1322
1322
1323 def _filtersuspectsymlink(self, files):
1323 def _filtersuspectsymlink(self, files):
1324 if not files or self._repo.dirstate._checklink:
1324 if not files or self._repo.dirstate._checklink:
1325 return files
1325 return files
1326
1326
1327 # Symlink placeholders may get non-symlink-like contents
1327 # Symlink placeholders may get non-symlink-like contents
1328 # via user error or dereferencing by NFS or Samba servers,
1328 # via user error or dereferencing by NFS or Samba servers,
1329 # so we filter out any placeholders that don't look like a
1329 # so we filter out any placeholders that don't look like a
1330 # symlink
1330 # symlink
1331 sane = []
1331 sane = []
1332 for f in files:
1332 for f in files:
1333 if self.flags(f) == 'l':
1333 if self.flags(f) == 'l':
1334 d = self[f].data()
1334 d = self[f].data()
1335 if d == '' or len(d) >= 1024 or '\n' in d or util.binary(d):
1335 if d == '' or len(d) >= 1024 or '\n' in d or util.binary(d):
1336 self._repo.ui.debug('ignoring suspect symlink placeholder'
1336 self._repo.ui.debug('ignoring suspect symlink placeholder'
1337 ' "%s"\n' % f)
1337 ' "%s"\n' % f)
1338 continue
1338 continue
1339 sane.append(f)
1339 sane.append(f)
1340 return sane
1340 return sane
1341
1341
1342 def _checklookup(self, files):
1342 def _checklookup(self, files):
1343 # check for any possibly clean files
1343 # check for any possibly clean files
1344 if not files:
1344 if not files:
1345 return [], []
1345 return [], []
1346
1346
1347 modified = []
1347 modified = []
1348 fixup = []
1348 fixup = []
1349 pctx = self._parents[0]
1349 pctx = self._parents[0]
1350 # do a full compare of any files that might have changed
1350 # do a full compare of any files that might have changed
1351 for f in sorted(files):
1351 for f in sorted(files):
1352 if (f not in pctx or self.flags(f) != pctx.flags(f)
1352 if (f not in pctx or self.flags(f) != pctx.flags(f)
1353 or pctx[f].cmp(self[f])):
1353 or pctx[f].cmp(self[f])):
1354 modified.append(f)
1354 modified.append(f)
1355 else:
1355 else:
1356 fixup.append(f)
1356 fixup.append(f)
1357
1357
1358 # update dirstate for files that are actually clean
1358 # update dirstate for files that are actually clean
1359 if fixup:
1359 if fixup:
1360 try:
1360 try:
1361 # updating the dirstate is optional
1361 # updating the dirstate is optional
1362 # so we don't wait on the lock
1362 # so we don't wait on the lock
1363 # wlock can invalidate the dirstate, so cache normal _after_
1363 # wlock can invalidate the dirstate, so cache normal _after_
1364 # taking the lock
1364 # taking the lock
1365 wlock = self._repo.wlock(False)
1365 wlock = self._repo.wlock(False)
1366 normal = self._repo.dirstate.normal
1366 normal = self._repo.dirstate.normal
1367 try:
1367 try:
1368 for f in fixup:
1368 for f in fixup:
1369 normal(f)
1369 normal(f)
1370 finally:
1370 finally:
1371 wlock.release()
1371 wlock.release()
1372 except error.LockError:
1372 except error.LockError:
1373 pass
1373 pass
1374 return modified, fixup
1374 return modified, fixup
1375
1375
1376 def _manifestmatches(self, match, s):
1376 def _manifestmatches(self, match, s):
1377 """Slow path for workingctx
1377 """Slow path for workingctx
1378
1378
1379 The fast path is when we compare the working directory to its parent
1379 The fast path is when we compare the working directory to its parent
1380 which means this function is comparing with a non-parent; therefore we
1380 which means this function is comparing with a non-parent; therefore we
1381 need to build a manifest and return what matches.
1381 need to build a manifest and return what matches.
1382 """
1382 """
1383 mf = self._repo['.']._manifestmatches(match, s)
1383 mf = self._repo['.']._manifestmatches(match, s)
1384 for f in s.modified + s.added:
1384 for f in s.modified + s.added:
1385 mf[f] = None
1385 mf[f] = None
1386 mf.setflag(f, self.flags(f))
1386 mf.setflag(f, self.flags(f))
1387 for f in s.removed:
1387 for f in s.removed:
1388 if f in mf:
1388 if f in mf:
1389 del mf[f]
1389 del mf[f]
1390 return mf
1390 return mf
1391
1391
1392 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1392 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1393 unknown=False):
1393 unknown=False):
1394 '''Gets the status from the dirstate -- internal use only.'''
1394 '''Gets the status from the dirstate -- internal use only.'''
1395 listignored, listclean, listunknown = ignored, clean, unknown
1395 listignored, listclean, listunknown = ignored, clean, unknown
1396 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1396 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1397 subrepos = []
1397 subrepos = []
1398 if '.hgsub' in self:
1398 if '.hgsub' in self:
1399 subrepos = sorted(self.substate)
1399 subrepos = sorted(self.substate)
1400 cmp, s = self._repo.dirstate.status(match, subrepos, listignored,
1400 cmp, s = self._repo.dirstate.status(match, subrepos, listignored,
1401 listclean, listunknown)
1401 listclean, listunknown)
1402
1402
1403 # check for any possibly clean files
1403 # check for any possibly clean files
1404 if cmp:
1404 if cmp:
1405 modified2, fixup = self._checklookup(cmp)
1405 modified2, fixup = self._checklookup(cmp)
1406 s.modified.extend(modified2)
1406 s.modified.extend(modified2)
1407
1407
1408 # update dirstate for files that are actually clean
1408 # update dirstate for files that are actually clean
1409 if fixup and listclean:
1409 if fixup and listclean:
1410 s.clean.extend(fixup)
1410 s.clean.extend(fixup)
1411
1411
1412 return s
1412 return s
1413
1413
1414 def _buildstatus(self, other, s, match, listignored, listclean,
1414 def _buildstatus(self, other, s, match, listignored, listclean,
1415 listunknown):
1415 listunknown):
1416 """build a status with respect to another context
1416 """build a status with respect to another context
1417
1417
1418 This includes logic for maintaining the fast path of status when
1418 This includes logic for maintaining the fast path of status when
1419 comparing the working directory against its parent, which is to skip
1419 comparing the working directory against its parent, which is to skip
1420 building a new manifest if self (working directory) is not comparing
1420 building a new manifest if self (working directory) is not comparing
1421 against its parent (repo['.']).
1421 against its parent (repo['.']).
1422 """
1422 """
1423 s = self._dirstatestatus(match, listignored, listclean, listunknown)
1423 s = self._dirstatestatus(match, listignored, listclean, listunknown)
1424 # Filter out symlinks that, in the case of FAT32 and NTFS filesystems,
1424 # Filter out symlinks that, in the case of FAT32 and NTFS filesystems,
1425 # might have accidentally ended up with the entire contents of the file
1425 # might have accidentally ended up with the entire contents of the file
1426 # they are supposed to be linking to.
1426 # they are supposed to be linking to.
1427 s.modified[:] = self._filtersuspectsymlink(s.modified)
1427 s.modified[:] = self._filtersuspectsymlink(s.modified)
1428 if other != self._repo['.']:
1428 if other != self._repo['.']:
1429 s = super(workingctx, self)._buildstatus(other, s, match,
1429 s = super(workingctx, self)._buildstatus(other, s, match,
1430 listignored, listclean,
1430 listignored, listclean,
1431 listunknown)
1431 listunknown)
1432 self._status = s
1432 self._status = s
1433 return s
1433 return s
1434
1434
1435 def _matchstatus(self, other, match):
1435 def _matchstatus(self, other, match):
1436 """override the match method with a filter for directory patterns
1436 """override the match method with a filter for directory patterns
1437
1437
1438 We use inheritance to customize the match.bad method only in cases of
1438 We use inheritance to customize the match.bad method only in cases of
1439 workingctx since it belongs only to the working directory when
1439 workingctx since it belongs only to the working directory when
1440 comparing against the parent changeset.
1440 comparing against the parent changeset.
1441
1441
1442 If we aren't comparing against the working directory's parent, then we
1442 If we aren't comparing against the working directory's parent, then we
1443 just use the default match object sent to us.
1443 just use the default match object sent to us.
1444 """
1444 """
1445 superself = super(workingctx, self)
1445 superself = super(workingctx, self)
1446 match = superself._matchstatus(other, match)
1446 match = superself._matchstatus(other, match)
1447 if other != self._repo['.']:
1447 if other != self._repo['.']:
1448 def bad(f, msg):
1448 def bad(f, msg):
1449 # 'f' may be a directory pattern from 'match.files()',
1449 # 'f' may be a directory pattern from 'match.files()',
1450 # so 'f not in ctx1' is not enough
1450 # so 'f not in ctx1' is not enough
1451 if f not in other and f not in other.dirs():
1451 if f not in other and f not in other.dirs():
1452 self._repo.ui.warn('%s: %s\n' %
1452 self._repo.ui.warn('%s: %s\n' %
1453 (self._repo.dirstate.pathto(f), msg))
1453 (self._repo.dirstate.pathto(f), msg))
1454 match.bad = bad
1454 match.bad = bad
1455 return match
1455 return match
1456
1456
1457 class committablefilectx(basefilectx):
1457 class committablefilectx(basefilectx):
1458 """A committablefilectx provides common functionality for a file context
1458 """A committablefilectx provides common functionality for a file context
1459 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1459 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1460 def __init__(self, repo, path, filelog=None, ctx=None):
1460 def __init__(self, repo, path, filelog=None, ctx=None):
1461 self._repo = repo
1461 self._repo = repo
1462 self._path = path
1462 self._path = path
1463 self._changeid = None
1463 self._changeid = None
1464 self._filerev = self._filenode = None
1464 self._filerev = self._filenode = None
1465
1465
1466 if filelog is not None:
1466 if filelog is not None:
1467 self._filelog = filelog
1467 self._filelog = filelog
1468 if ctx:
1468 if ctx:
1469 self._changectx = ctx
1469 self._changectx = ctx
1470
1470
1471 def __nonzero__(self):
1471 def __nonzero__(self):
1472 return True
1472 return True
1473
1473
1474 def parents(self):
1474 def parents(self):
1475 '''return parent filectxs, following copies if necessary'''
1475 '''return parent filectxs, following copies if necessary'''
1476 def filenode(ctx, path):
1476 def filenode(ctx, path):
1477 return ctx._manifest.get(path, nullid)
1477 return ctx._manifest.get(path, nullid)
1478
1478
1479 path = self._path
1479 path = self._path
1480 fl = self._filelog
1480 fl = self._filelog
1481 pcl = self._changectx._parents
1481 pcl = self._changectx._parents
1482 renamed = self.renamed()
1482 renamed = self.renamed()
1483
1483
1484 if renamed:
1484 if renamed:
1485 pl = [renamed + (None,)]
1485 pl = [renamed + (None,)]
1486 else:
1486 else:
1487 pl = [(path, filenode(pcl[0], path), fl)]
1487 pl = [(path, filenode(pcl[0], path), fl)]
1488
1488
1489 for pc in pcl[1:]:
1489 for pc in pcl[1:]:
1490 pl.append((path, filenode(pc, path), fl))
1490 pl.append((path, filenode(pc, path), fl))
1491
1491
1492 return [filectx(self._repo, p, fileid=n, filelog=l)
1492 return [filectx(self._repo, p, fileid=n, filelog=l)
1493 for p, n, l in pl if n != nullid]
1493 for p, n, l in pl if n != nullid]
1494
1494
1495 def children(self):
1495 def children(self):
1496 return []
1496 return []
1497
1497
1498 class workingfilectx(committablefilectx):
1498 class workingfilectx(committablefilectx):
1499 """A workingfilectx object makes access to data related to a particular
1499 """A workingfilectx object makes access to data related to a particular
1500 file in the working directory convenient."""
1500 file in the working directory convenient."""
1501 def __init__(self, repo, path, filelog=None, workingctx=None):
1501 def __init__(self, repo, path, filelog=None, workingctx=None):
1502 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1502 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1503
1503
1504 @propertycache
1504 @propertycache
1505 def _changectx(self):
1505 def _changectx(self):
1506 return workingctx(self._repo)
1506 return workingctx(self._repo)
1507
1507
1508 def data(self):
1508 def data(self):
1509 return self._repo.wread(self._path)
1509 return self._repo.wread(self._path)
1510 def renamed(self):
1510 def renamed(self):
1511 rp = self._repo.dirstate.copied(self._path)
1511 rp = self._repo.dirstate.copied(self._path)
1512 if not rp:
1512 if not rp:
1513 return None
1513 return None
1514 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1514 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1515
1515
1516 def size(self):
1516 def size(self):
1517 return self._repo.wvfs.lstat(self._path).st_size
1517 return self._repo.wvfs.lstat(self._path).st_size
1518 def date(self):
1518 def date(self):
1519 t, tz = self._changectx.date()
1519 t, tz = self._changectx.date()
1520 try:
1520 try:
1521 return (int(self._repo.wvfs.lstat(self._path).st_mtime), tz)
1521 return (int(self._repo.wvfs.lstat(self._path).st_mtime), tz)
1522 except OSError, err:
1522 except OSError, err:
1523 if err.errno != errno.ENOENT:
1523 if err.errno != errno.ENOENT:
1524 raise
1524 raise
1525 return (t, tz)
1525 return (t, tz)
1526
1526
1527 def cmp(self, fctx):
1527 def cmp(self, fctx):
1528 """compare with other file context
1528 """compare with other file context
1529
1529
1530 returns True if different than fctx.
1530 returns True if different than fctx.
1531 """
1531 """
1532 # fctx should be a filectx (not a workingfilectx)
1532 # fctx should be a filectx (not a workingfilectx)
1533 # invert comparison to reuse the same code path
1533 # invert comparison to reuse the same code path
1534 return fctx.cmp(self)
1534 return fctx.cmp(self)
1535
1535
1536 def remove(self, ignoremissing=False):
1536 def remove(self, ignoremissing=False):
1537 """wraps unlink for a repo's working directory"""
1537 """wraps unlink for a repo's working directory"""
1538 util.unlinkpath(self._repo.wjoin(self._path), ignoremissing)
1538 util.unlinkpath(self._repo.wjoin(self._path), ignoremissing)
1539
1539
1540 def write(self, data, flags):
1540 def write(self, data, flags):
1541 """wraps repo.wwrite"""
1541 """wraps repo.wwrite"""
1542 self._repo.wwrite(self._path, data, flags)
1542 self._repo.wwrite(self._path, data, flags)
1543
1543
1544 class memctx(committablectx):
1544 class memctx(committablectx):
1545 """Use memctx to perform in-memory commits via localrepo.commitctx().
1545 """Use memctx to perform in-memory commits via localrepo.commitctx().
1546
1546
1547 Revision information is supplied at initialization time while
1547 Revision information is supplied at initialization time while
1548 related files data and is made available through a callback
1548 related files data and is made available through a callback
1549 mechanism. 'repo' is the current localrepo, 'parents' is a
1549 mechanism. 'repo' is the current localrepo, 'parents' is a
1550 sequence of two parent revisions identifiers (pass None for every
1550 sequence of two parent revisions identifiers (pass None for every
1551 missing parent), 'text' is the commit message and 'files' lists
1551 missing parent), 'text' is the commit message and 'files' lists
1552 names of files touched by the revision (normalized and relative to
1552 names of files touched by the revision (normalized and relative to
1553 repository root).
1553 repository root).
1554
1554
1555 filectxfn(repo, memctx, path) is a callable receiving the
1555 filectxfn(repo, memctx, path) is a callable receiving the
1556 repository, the current memctx object and the normalized path of
1556 repository, the current memctx object and the normalized path of
1557 requested file, relative to repository root. It is fired by the
1557 requested file, relative to repository root. It is fired by the
1558 commit function for every file in 'files', but calls order is
1558 commit function for every file in 'files', but calls order is
1559 undefined. If the file is available in the revision being
1559 undefined. If the file is available in the revision being
1560 committed (updated or added), filectxfn returns a memfilectx
1560 committed (updated or added), filectxfn returns a memfilectx
1561 object. If the file was removed, filectxfn raises an
1561 object. If the file was removed, filectxfn raises an
1562 IOError. Moved files are represented by marking the source file
1562 IOError. Moved files are represented by marking the source file
1563 removed and the new file added with copy information (see
1563 removed and the new file added with copy information (see
1564 memfilectx).
1564 memfilectx).
1565
1565
1566 user receives the committer name and defaults to current
1566 user receives the committer name and defaults to current
1567 repository username, date is the commit date in any format
1567 repository username, date is the commit date in any format
1568 supported by util.parsedate() and defaults to current date, extra
1568 supported by util.parsedate() and defaults to current date, extra
1569 is a dictionary of metadata or is left empty.
1569 is a dictionary of metadata or is left empty.
1570 """
1570 """
1571
1571
1572 # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
1572 # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
1573 # Extensions that need to retain compatibility across Mercurial 3.1 can use
1573 # Extensions that need to retain compatibility across Mercurial 3.1 can use
1574 # this field to determine what to do in filectxfn.
1574 # this field to determine what to do in filectxfn.
1575 _returnnoneformissingfiles = True
1575 _returnnoneformissingfiles = True
1576
1576
1577 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1577 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1578 date=None, extra=None, editor=False):
1578 date=None, extra=None, editor=False):
1579 super(memctx, self).__init__(repo, text, user, date, extra)
1579 super(memctx, self).__init__(repo, text, user, date, extra)
1580 self._rev = None
1580 self._rev = None
1581 self._node = None
1581 self._node = None
1582 parents = [(p or nullid) for p in parents]
1582 parents = [(p or nullid) for p in parents]
1583 p1, p2 = parents
1583 p1, p2 = parents
1584 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1584 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1585 files = sorted(set(files))
1585 files = sorted(set(files))
1586 self._status = scmutil.status(files, [], [], [], [], [], [])
1586 self._status = scmutil.status(files, [], [], [], [], [], [])
1587 self._filectxfn = filectxfn
1587 self._filectxfn = filectxfn
1588 self.substate = {}
1588 self.substate = {}
1589
1589
1590 # if store is not callable, wrap it in a function
1590 # if store is not callable, wrap it in a function
1591 if not callable(filectxfn):
1591 if not callable(filectxfn):
1592 def getfilectx(repo, memctx, path):
1592 def getfilectx(repo, memctx, path):
1593 fctx = filectxfn[path]
1593 fctx = filectxfn[path]
1594 # this is weird but apparently we only keep track of one parent
1594 # this is weird but apparently we only keep track of one parent
1595 # (why not only store that instead of a tuple?)
1595 # (why not only store that instead of a tuple?)
1596 copied = fctx.renamed()
1596 copied = fctx.renamed()
1597 if copied:
1597 if copied:
1598 copied = copied[0]
1598 copied = copied[0]
1599 return memfilectx(repo, path, fctx.data(),
1599 return memfilectx(repo, path, fctx.data(),
1600 islink=fctx.islink(), isexec=fctx.isexec(),
1600 islink=fctx.islink(), isexec=fctx.isexec(),
1601 copied=copied, memctx=memctx)
1601 copied=copied, memctx=memctx)
1602 self._filectxfn = getfilectx
1602 self._filectxfn = getfilectx
1603
1603
1604 self._extra = extra and extra.copy() or {}
1604 self._extra = extra and extra.copy() or {}
1605 if self._extra.get('branch', '') == '':
1605 if self._extra.get('branch', '') == '':
1606 self._extra['branch'] = 'default'
1606 self._extra['branch'] = 'default'
1607
1607
1608 if editor:
1608 if editor:
1609 self._text = editor(self._repo, self, [])
1609 self._text = editor(self._repo, self, [])
1610 self._repo.savecommitmessage(self._text)
1610 self._repo.savecommitmessage(self._text)
1611
1611
1612 def filectx(self, path, filelog=None):
1612 def filectx(self, path, filelog=None):
1613 """get a file context from the working directory
1613 """get a file context from the working directory
1614
1614
1615 Returns None if file doesn't exist and should be removed."""
1615 Returns None if file doesn't exist and should be removed."""
1616 return self._filectxfn(self._repo, self, path)
1616 return self._filectxfn(self._repo, self, path)
1617
1617
1618 def commit(self):
1618 def commit(self):
1619 """commit context to the repo"""
1619 """commit context to the repo"""
1620 return self._repo.commitctx(self)
1620 return self._repo.commitctx(self)
1621
1621
1622 @propertycache
1622 @propertycache
1623 def _manifest(self):
1623 def _manifest(self):
1624 """generate a manifest based on the return values of filectxfn"""
1624 """generate a manifest based on the return values of filectxfn"""
1625
1625
1626 # keep this simple for now; just worry about p1
1626 # keep this simple for now; just worry about p1
1627 pctx = self._parents[0]
1627 pctx = self._parents[0]
1628 pman = pctx.manifest()
1628 man = pctx.manifest().copy()
1629 man = pctx.manifest().copy()
1629
1630
1630 for f, fnode in man.iteritems():
1631 for f, fnode in pman.iteritems():
1631 p1node = nullid
1632 p1node = nullid
1632 p2node = nullid
1633 p2node = nullid
1633 p = pctx[f].parents() # if file isn't in pctx, check p2?
1634 p = pctx[f].parents() # if file isn't in pctx, check p2?
1634 if len(p) > 0:
1635 if len(p) > 0:
1635 p1node = p[0].node()
1636 p1node = p[0].node()
1636 if len(p) > 1:
1637 if len(p) > 1:
1637 p2node = p[1].node()
1638 p2node = p[1].node()
1638 man[f] = revlog.hash(self[f].data(), p1node, p2node)
1639 fctx = self[f]
1640 if fctx is None:
1641 # removed file
1642 del man[f]
1643 else:
1644 man[f] = revlog.hash(fctx.data(), p1node, p2node)
1639
1645
1640 return man
1646 return man
1641
1647
1642
1648
1643 class memfilectx(committablefilectx):
1649 class memfilectx(committablefilectx):
1644 """memfilectx represents an in-memory file to commit.
1650 """memfilectx represents an in-memory file to commit.
1645
1651
1646 See memctx and committablefilectx for more details.
1652 See memctx and committablefilectx for more details.
1647 """
1653 """
1648 def __init__(self, repo, path, data, islink=False,
1654 def __init__(self, repo, path, data, islink=False,
1649 isexec=False, copied=None, memctx=None):
1655 isexec=False, copied=None, memctx=None):
1650 """
1656 """
1651 path is the normalized file path relative to repository root.
1657 path is the normalized file path relative to repository root.
1652 data is the file content as a string.
1658 data is the file content as a string.
1653 islink is True if the file is a symbolic link.
1659 islink is True if the file is a symbolic link.
1654 isexec is True if the file is executable.
1660 isexec is True if the file is executable.
1655 copied is the source file path if current file was copied in the
1661 copied is the source file path if current file was copied in the
1656 revision being committed, or None."""
1662 revision being committed, or None."""
1657 super(memfilectx, self).__init__(repo, path, None, memctx)
1663 super(memfilectx, self).__init__(repo, path, None, memctx)
1658 self._data = data
1664 self._data = data
1659 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1665 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1660 self._copied = None
1666 self._copied = None
1661 if copied:
1667 if copied:
1662 self._copied = (copied, nullid)
1668 self._copied = (copied, nullid)
1663
1669
1664 def data(self):
1670 def data(self):
1665 return self._data
1671 return self._data
1666 def size(self):
1672 def size(self):
1667 return len(self.data())
1673 return len(self.data())
1668 def flags(self):
1674 def flags(self):
1669 return self._flags
1675 return self._flags
1670 def renamed(self):
1676 def renamed(self):
1671 return self._copied
1677 return self._copied
1672
1678
1673 def remove(self, ignoremissing=False):
1679 def remove(self, ignoremissing=False):
1674 """wraps unlink for a repo's working directory"""
1680 """wraps unlink for a repo's working directory"""
1675 # need to figure out what to do here
1681 # need to figure out what to do here
1676 del self._changectx[self._path]
1682 del self._changectx[self._path]
1677
1683
1678 def write(self, data, flags):
1684 def write(self, data, flags):
1679 """wraps repo.wwrite"""
1685 """wraps repo.wwrite"""
1680 self._data = data
1686 self._data = data
@@ -1,460 +1,521
1 commit date test
1 commit date test
2
2
3 $ hg init test
3 $ hg init test
4 $ cd test
4 $ cd test
5 $ echo foo > foo
5 $ echo foo > foo
6 $ hg add foo
6 $ hg add foo
7 $ cat > $TESTTMP/checkeditform.sh <<EOF
7 $ cat > $TESTTMP/checkeditform.sh <<EOF
8 > env | grep HGEDITFORM
8 > env | grep HGEDITFORM
9 > true
9 > true
10 > EOF
10 > EOF
11 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg commit -m ""
11 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg commit -m ""
12 HGEDITFORM=commit.normal.normal
12 HGEDITFORM=commit.normal.normal
13 abort: empty commit message
13 abort: empty commit message
14 [255]
14 [255]
15 $ hg commit -d '0 0' -m commit-1
15 $ hg commit -d '0 0' -m commit-1
16 $ echo foo >> foo
16 $ echo foo >> foo
17 $ hg commit -d '1 4444444' -m commit-3
17 $ hg commit -d '1 4444444' -m commit-3
18 abort: impossible time zone offset: 4444444
18 abort: impossible time zone offset: 4444444
19 [255]
19 [255]
20 $ hg commit -d '1 15.1' -m commit-4
20 $ hg commit -d '1 15.1' -m commit-4
21 abort: invalid date: '1\t15.1'
21 abort: invalid date: '1\t15.1'
22 [255]
22 [255]
23 $ hg commit -d 'foo bar' -m commit-5
23 $ hg commit -d 'foo bar' -m commit-5
24 abort: invalid date: 'foo bar'
24 abort: invalid date: 'foo bar'
25 [255]
25 [255]
26 $ hg commit -d ' 1 4444' -m commit-6
26 $ hg commit -d ' 1 4444' -m commit-6
27 $ hg commit -d '111111111111 0' -m commit-7
27 $ hg commit -d '111111111111 0' -m commit-7
28 abort: date exceeds 32 bits: 111111111111
28 abort: date exceeds 32 bits: 111111111111
29 [255]
29 [255]
30 $ hg commit -d '-7654321 3600' -m commit-7
30 $ hg commit -d '-7654321 3600' -m commit-7
31 abort: negative date value: -7654321
31 abort: negative date value: -7654321
32 [255]
32 [255]
33
33
34 commit added file that has been deleted
34 commit added file that has been deleted
35
35
36 $ echo bar > bar
36 $ echo bar > bar
37 $ hg add bar
37 $ hg add bar
38 $ rm bar
38 $ rm bar
39 $ hg commit -m commit-8
39 $ hg commit -m commit-8
40 nothing changed (1 missing files, see 'hg status')
40 nothing changed (1 missing files, see 'hg status')
41 [1]
41 [1]
42 $ hg commit -m commit-8-2 bar
42 $ hg commit -m commit-8-2 bar
43 abort: bar: file not found!
43 abort: bar: file not found!
44 [255]
44 [255]
45
45
46 $ hg -q revert -a --no-backup
46 $ hg -q revert -a --no-backup
47
47
48 $ mkdir dir
48 $ mkdir dir
49 $ echo boo > dir/file
49 $ echo boo > dir/file
50 $ hg add
50 $ hg add
51 adding dir/file (glob)
51 adding dir/file (glob)
52 $ hg -v commit -m commit-9 dir
52 $ hg -v commit -m commit-9 dir
53 dir/file
53 dir/file
54 committed changeset 2:d2a76177cb42
54 committed changeset 2:d2a76177cb42
55
55
56 $ echo > dir.file
56 $ echo > dir.file
57 $ hg add
57 $ hg add
58 adding dir.file
58 adding dir.file
59 $ hg commit -m commit-10 dir dir.file
59 $ hg commit -m commit-10 dir dir.file
60 abort: dir: no match under directory!
60 abort: dir: no match under directory!
61 [255]
61 [255]
62
62
63 $ echo >> dir/file
63 $ echo >> dir/file
64 $ mkdir bleh
64 $ mkdir bleh
65 $ mkdir dir2
65 $ mkdir dir2
66 $ cd bleh
66 $ cd bleh
67 $ hg commit -m commit-11 .
67 $ hg commit -m commit-11 .
68 abort: bleh: no match under directory!
68 abort: bleh: no match under directory!
69 [255]
69 [255]
70 $ hg commit -m commit-12 ../dir ../dir2
70 $ hg commit -m commit-12 ../dir ../dir2
71 abort: dir2: no match under directory!
71 abort: dir2: no match under directory!
72 [255]
72 [255]
73 $ hg -v commit -m commit-13 ../dir
73 $ hg -v commit -m commit-13 ../dir
74 dir/file
74 dir/file
75 committed changeset 3:1cd62a2d8db5
75 committed changeset 3:1cd62a2d8db5
76 $ cd ..
76 $ cd ..
77
77
78 $ hg commit -m commit-14 does-not-exist
78 $ hg commit -m commit-14 does-not-exist
79 abort: does-not-exist: * (glob)
79 abort: does-not-exist: * (glob)
80 [255]
80 [255]
81
81
82 #if symlink
82 #if symlink
83 $ ln -s foo baz
83 $ ln -s foo baz
84 $ hg commit -m commit-15 baz
84 $ hg commit -m commit-15 baz
85 abort: baz: file not tracked!
85 abort: baz: file not tracked!
86 [255]
86 [255]
87 #endif
87 #endif
88
88
89 $ touch quux
89 $ touch quux
90 $ hg commit -m commit-16 quux
90 $ hg commit -m commit-16 quux
91 abort: quux: file not tracked!
91 abort: quux: file not tracked!
92 [255]
92 [255]
93 $ echo >> dir/file
93 $ echo >> dir/file
94 $ hg -v commit -m commit-17 dir/file
94 $ hg -v commit -m commit-17 dir/file
95 dir/file
95 dir/file
96 committed changeset 4:49176991390e
96 committed changeset 4:49176991390e
97
97
98 An empty date was interpreted as epoch origin
98 An empty date was interpreted as epoch origin
99
99
100 $ echo foo >> foo
100 $ echo foo >> foo
101 $ hg commit -d '' -m commit-no-date
101 $ hg commit -d '' -m commit-no-date
102 $ hg tip --template '{date|isodate}\n' | grep '1970'
102 $ hg tip --template '{date|isodate}\n' | grep '1970'
103 [1]
103 [1]
104
104
105 Make sure we do not obscure unknown requires file entries (issue2649)
105 Make sure we do not obscure unknown requires file entries (issue2649)
106
106
107 $ echo foo >> foo
107 $ echo foo >> foo
108 $ echo fake >> .hg/requires
108 $ echo fake >> .hg/requires
109 $ hg commit -m bla
109 $ hg commit -m bla
110 abort: repository requires features unknown to this Mercurial: fake!
110 abort: repository requires features unknown to this Mercurial: fake!
111 (see http://mercurial.selenic.com/wiki/MissingRequirement for more information)
111 (see http://mercurial.selenic.com/wiki/MissingRequirement for more information)
112 [255]
112 [255]
113
113
114 $ cd ..
114 $ cd ..
115
115
116
116
117 partial subdir commit test
117 partial subdir commit test
118
118
119 $ hg init test2
119 $ hg init test2
120 $ cd test2
120 $ cd test2
121 $ mkdir foo
121 $ mkdir foo
122 $ echo foo > foo/foo
122 $ echo foo > foo/foo
123 $ mkdir bar
123 $ mkdir bar
124 $ echo bar > bar/bar
124 $ echo bar > bar/bar
125 $ hg add
125 $ hg add
126 adding bar/bar (glob)
126 adding bar/bar (glob)
127 adding foo/foo (glob)
127 adding foo/foo (glob)
128 $ HGEDITOR=cat hg ci -e -m commit-subdir-1 foo
128 $ HGEDITOR=cat hg ci -e -m commit-subdir-1 foo
129 commit-subdir-1
129 commit-subdir-1
130
130
131
131
132 HG: Enter commit message. Lines beginning with 'HG:' are removed.
132 HG: Enter commit message. Lines beginning with 'HG:' are removed.
133 HG: Leave message empty to abort commit.
133 HG: Leave message empty to abort commit.
134 HG: --
134 HG: --
135 HG: user: test
135 HG: user: test
136 HG: branch 'default'
136 HG: branch 'default'
137 HG: added foo/foo
137 HG: added foo/foo
138
138
139
139
140 $ hg ci -m commit-subdir-2 bar
140 $ hg ci -m commit-subdir-2 bar
141
141
142 subdir log 1
142 subdir log 1
143
143
144 $ hg log -v foo
144 $ hg log -v foo
145 changeset: 0:f97e73a25882
145 changeset: 0:f97e73a25882
146 user: test
146 user: test
147 date: Thu Jan 01 00:00:00 1970 +0000
147 date: Thu Jan 01 00:00:00 1970 +0000
148 files: foo/foo
148 files: foo/foo
149 description:
149 description:
150 commit-subdir-1
150 commit-subdir-1
151
151
152
152
153
153
154 subdir log 2
154 subdir log 2
155
155
156 $ hg log -v bar
156 $ hg log -v bar
157 changeset: 1:aa809156d50d
157 changeset: 1:aa809156d50d
158 tag: tip
158 tag: tip
159 user: test
159 user: test
160 date: Thu Jan 01 00:00:00 1970 +0000
160 date: Thu Jan 01 00:00:00 1970 +0000
161 files: bar/bar
161 files: bar/bar
162 description:
162 description:
163 commit-subdir-2
163 commit-subdir-2
164
164
165
165
166
166
167 full log
167 full log
168
168
169 $ hg log -v
169 $ hg log -v
170 changeset: 1:aa809156d50d
170 changeset: 1:aa809156d50d
171 tag: tip
171 tag: tip
172 user: test
172 user: test
173 date: Thu Jan 01 00:00:00 1970 +0000
173 date: Thu Jan 01 00:00:00 1970 +0000
174 files: bar/bar
174 files: bar/bar
175 description:
175 description:
176 commit-subdir-2
176 commit-subdir-2
177
177
178
178
179 changeset: 0:f97e73a25882
179 changeset: 0:f97e73a25882
180 user: test
180 user: test
181 date: Thu Jan 01 00:00:00 1970 +0000
181 date: Thu Jan 01 00:00:00 1970 +0000
182 files: foo/foo
182 files: foo/foo
183 description:
183 description:
184 commit-subdir-1
184 commit-subdir-1
185
185
186
186
187 $ cd ..
187 $ cd ..
188
188
189
189
190 dot and subdir commit test
190 dot and subdir commit test
191
191
192 $ hg init test3
192 $ hg init test3
193 $ echo commit-foo-subdir > commit-log-test
193 $ echo commit-foo-subdir > commit-log-test
194 $ cd test3
194 $ cd test3
195 $ mkdir foo
195 $ mkdir foo
196 $ echo foo content > foo/plain-file
196 $ echo foo content > foo/plain-file
197 $ hg add foo/plain-file
197 $ hg add foo/plain-file
198 $ HGEDITOR=cat hg ci --edit -l ../commit-log-test foo
198 $ HGEDITOR=cat hg ci --edit -l ../commit-log-test foo
199 commit-foo-subdir
199 commit-foo-subdir
200
200
201
201
202 HG: Enter commit message. Lines beginning with 'HG:' are removed.
202 HG: Enter commit message. Lines beginning with 'HG:' are removed.
203 HG: Leave message empty to abort commit.
203 HG: Leave message empty to abort commit.
204 HG: --
204 HG: --
205 HG: user: test
205 HG: user: test
206 HG: branch 'default'
206 HG: branch 'default'
207 HG: added foo/plain-file
207 HG: added foo/plain-file
208
208
209
209
210 $ echo modified foo content > foo/plain-file
210 $ echo modified foo content > foo/plain-file
211 $ hg ci -m commit-foo-dot .
211 $ hg ci -m commit-foo-dot .
212
212
213 full log
213 full log
214
214
215 $ hg log -v
215 $ hg log -v
216 changeset: 1:95b38e3a5b2e
216 changeset: 1:95b38e3a5b2e
217 tag: tip
217 tag: tip
218 user: test
218 user: test
219 date: Thu Jan 01 00:00:00 1970 +0000
219 date: Thu Jan 01 00:00:00 1970 +0000
220 files: foo/plain-file
220 files: foo/plain-file
221 description:
221 description:
222 commit-foo-dot
222 commit-foo-dot
223
223
224
224
225 changeset: 0:65d4e9386227
225 changeset: 0:65d4e9386227
226 user: test
226 user: test
227 date: Thu Jan 01 00:00:00 1970 +0000
227 date: Thu Jan 01 00:00:00 1970 +0000
228 files: foo/plain-file
228 files: foo/plain-file
229 description:
229 description:
230 commit-foo-subdir
230 commit-foo-subdir
231
231
232
232
233
233
234 subdir log
234 subdir log
235
235
236 $ cd foo
236 $ cd foo
237 $ hg log .
237 $ hg log .
238 changeset: 1:95b38e3a5b2e
238 changeset: 1:95b38e3a5b2e
239 tag: tip
239 tag: tip
240 user: test
240 user: test
241 date: Thu Jan 01 00:00:00 1970 +0000
241 date: Thu Jan 01 00:00:00 1970 +0000
242 summary: commit-foo-dot
242 summary: commit-foo-dot
243
243
244 changeset: 0:65d4e9386227
244 changeset: 0:65d4e9386227
245 user: test
245 user: test
246 date: Thu Jan 01 00:00:00 1970 +0000
246 date: Thu Jan 01 00:00:00 1970 +0000
247 summary: commit-foo-subdir
247 summary: commit-foo-subdir
248
248
249 $ cd ..
249 $ cd ..
250 $ cd ..
250 $ cd ..
251
251
252 Issue1049: Hg permits partial commit of merge without warning
252 Issue1049: Hg permits partial commit of merge without warning
253
253
254 $ hg init issue1049
254 $ hg init issue1049
255 $ cd issue1049
255 $ cd issue1049
256 $ echo a > a
256 $ echo a > a
257 $ hg ci -Ama
257 $ hg ci -Ama
258 adding a
258 adding a
259 $ echo a >> a
259 $ echo a >> a
260 $ hg ci -mb
260 $ hg ci -mb
261 $ hg up 0
261 $ hg up 0
262 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
262 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
263 $ echo b >> a
263 $ echo b >> a
264 $ hg ci -mc
264 $ hg ci -mc
265 created new head
265 created new head
266 $ HGMERGE=true hg merge
266 $ HGMERGE=true hg merge
267 merging a
267 merging a
268 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
268 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
269 (branch merge, don't forget to commit)
269 (branch merge, don't forget to commit)
270
270
271 should fail because we are specifying a file name
271 should fail because we are specifying a file name
272
272
273 $ hg ci -mmerge a
273 $ hg ci -mmerge a
274 abort: cannot partially commit a merge (do not specify files or patterns)
274 abort: cannot partially commit a merge (do not specify files or patterns)
275 [255]
275 [255]
276
276
277 should fail because we are specifying a pattern
277 should fail because we are specifying a pattern
278
278
279 $ hg ci -mmerge -I a
279 $ hg ci -mmerge -I a
280 abort: cannot partially commit a merge (do not specify files or patterns)
280 abort: cannot partially commit a merge (do not specify files or patterns)
281 [255]
281 [255]
282
282
283 should succeed
283 should succeed
284
284
285 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg ci -mmerge --edit
285 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg ci -mmerge --edit
286 HGEDITFORM=commit.normal.merge
286 HGEDITFORM=commit.normal.merge
287 $ cd ..
287 $ cd ..
288
288
289
289
290 test commit message content
290 test commit message content
291
291
292 $ hg init commitmsg
292 $ hg init commitmsg
293 $ cd commitmsg
293 $ cd commitmsg
294 $ echo changed > changed
294 $ echo changed > changed
295 $ echo removed > removed
295 $ echo removed > removed
296 $ hg book currentbookmark
296 $ hg book currentbookmark
297 $ hg ci -qAm init
297 $ hg ci -qAm init
298
298
299 $ hg rm removed
299 $ hg rm removed
300 $ echo changed >> changed
300 $ echo changed >> changed
301 $ echo added > added
301 $ echo added > added
302 $ hg add added
302 $ hg add added
303 $ HGEDITOR=cat hg ci -A
303 $ HGEDITOR=cat hg ci -A
304
304
305
305
306 HG: Enter commit message. Lines beginning with 'HG:' are removed.
306 HG: Enter commit message. Lines beginning with 'HG:' are removed.
307 HG: Leave message empty to abort commit.
307 HG: Leave message empty to abort commit.
308 HG: --
308 HG: --
309 HG: user: test
309 HG: user: test
310 HG: branch 'default'
310 HG: branch 'default'
311 HG: bookmark 'currentbookmark'
311 HG: bookmark 'currentbookmark'
312 HG: added added
312 HG: added added
313 HG: changed changed
313 HG: changed changed
314 HG: removed removed
314 HG: removed removed
315 abort: empty commit message
315 abort: empty commit message
316 [255]
316 [255]
317
317
318 test saving last-message.txt
318 test saving last-message.txt
319
319
320 $ hg init sub
320 $ hg init sub
321 $ echo a > sub/a
321 $ echo a > sub/a
322 $ hg -R sub add sub/a
322 $ hg -R sub add sub/a
323 $ cat > sub/.hg/hgrc <<EOF
323 $ cat > sub/.hg/hgrc <<EOF
324 > [hooks]
324 > [hooks]
325 > precommit.test-saving-last-message = false
325 > precommit.test-saving-last-message = false
326 > EOF
326 > EOF
327
327
328 $ echo 'sub = sub' > .hgsub
328 $ echo 'sub = sub' > .hgsub
329 $ hg add .hgsub
329 $ hg add .hgsub
330
330
331 $ cat > $TESTTMP/editor.sh <<EOF
331 $ cat > $TESTTMP/editor.sh <<EOF
332 > echo "==== before editing:"
332 > echo "==== before editing:"
333 > cat \$1
333 > cat \$1
334 > echo "===="
334 > echo "===="
335 > echo "test saving last-message.txt" >> \$1
335 > echo "test saving last-message.txt" >> \$1
336 > EOF
336 > EOF
337
337
338 $ rm -f .hg/last-message.txt
338 $ rm -f .hg/last-message.txt
339 $ HGEDITOR="sh $TESTTMP/editor.sh" hg commit -S -q
339 $ HGEDITOR="sh $TESTTMP/editor.sh" hg commit -S -q
340 ==== before editing:
340 ==== before editing:
341
341
342
342
343 HG: Enter commit message. Lines beginning with 'HG:' are removed.
343 HG: Enter commit message. Lines beginning with 'HG:' are removed.
344 HG: Leave message empty to abort commit.
344 HG: Leave message empty to abort commit.
345 HG: --
345 HG: --
346 HG: user: test
346 HG: user: test
347 HG: branch 'default'
347 HG: branch 'default'
348 HG: bookmark 'currentbookmark'
348 HG: bookmark 'currentbookmark'
349 HG: subrepo sub
349 HG: subrepo sub
350 HG: added .hgsub
350 HG: added .hgsub
351 HG: added added
351 HG: added added
352 HG: changed .hgsubstate
352 HG: changed .hgsubstate
353 HG: changed changed
353 HG: changed changed
354 HG: removed removed
354 HG: removed removed
355 ====
355 ====
356 abort: precommit.test-saving-last-message hook exited with status 1 (in subrepo sub)
356 abort: precommit.test-saving-last-message hook exited with status 1 (in subrepo sub)
357 [255]
357 [255]
358 $ cat .hg/last-message.txt
358 $ cat .hg/last-message.txt
359
359
360
360
361 test saving last-message.txt
361 test saving last-message.txt
362
362
363 test that '[committemplate] changeset' definition and commit log
363 test that '[committemplate] changeset' definition and commit log
364 specific template keywords work well
364 specific template keywords work well
365
365
366 $ cat >> .hg/hgrc <<EOF
366 $ cat >> .hg/hgrc <<EOF
367 > [committemplate]
367 > [committemplate]
368 > changeset.commit.normal = HG: this is "commit.normal" template
368 > changeset.commit.normal = HG: this is "commit.normal" template
369 > HG: {extramsg}
369 > HG: {extramsg}
370 > {if(currentbookmark,
370 > {if(currentbookmark,
371 > "HG: bookmark '{currentbookmark}' is activated\n",
371 > "HG: bookmark '{currentbookmark}' is activated\n",
372 > "HG: no bookmark is activated\n")}{subrepos %
372 > "HG: no bookmark is activated\n")}{subrepos %
373 > "HG: subrepo '{subrepo}' is changed\n"}
373 > "HG: subrepo '{subrepo}' is changed\n"}
374 >
374 >
375 > changeset.commit = HG: this is "commit" template
375 > changeset.commit = HG: this is "commit" template
376 > HG: {extramsg}
376 > HG: {extramsg}
377 > {if(currentbookmark,
377 > {if(currentbookmark,
378 > "HG: bookmark '{currentbookmark}' is activated\n",
378 > "HG: bookmark '{currentbookmark}' is activated\n",
379 > "HG: no bookmark is activated\n")}{subrepos %
379 > "HG: no bookmark is activated\n")}{subrepos %
380 > "HG: subrepo '{subrepo}' is changed\n"}
380 > "HG: subrepo '{subrepo}' is changed\n"}
381 >
381 >
382 > changeset = HG: this is customized commit template
382 > changeset = HG: this is customized commit template
383 > HG: {extramsg}
383 > HG: {extramsg}
384 > {if(currentbookmark,
384 > {if(currentbookmark,
385 > "HG: bookmark '{currentbookmark}' is activated\n",
385 > "HG: bookmark '{currentbookmark}' is activated\n",
386 > "HG: no bookmark is activated\n")}{subrepos %
386 > "HG: no bookmark is activated\n")}{subrepos %
387 > "HG: subrepo '{subrepo}' is changed\n"}
387 > "HG: subrepo '{subrepo}' is changed\n"}
388 > EOF
388 > EOF
389
389
390 $ hg init sub2
390 $ hg init sub2
391 $ echo a > sub2/a
391 $ echo a > sub2/a
392 $ hg -R sub2 add sub2/a
392 $ hg -R sub2 add sub2/a
393 $ echo 'sub2 = sub2' >> .hgsub
393 $ echo 'sub2 = sub2' >> .hgsub
394
394
395 $ HGEDITOR=cat hg commit -S -q
395 $ HGEDITOR=cat hg commit -S -q
396 HG: this is "commit.normal" template
396 HG: this is "commit.normal" template
397 HG: Leave message empty to abort commit.
397 HG: Leave message empty to abort commit.
398 HG: bookmark 'currentbookmark' is activated
398 HG: bookmark 'currentbookmark' is activated
399 HG: subrepo 'sub' is changed
399 HG: subrepo 'sub' is changed
400 HG: subrepo 'sub2' is changed
400 HG: subrepo 'sub2' is changed
401 abort: empty commit message
401 abort: empty commit message
402 [255]
402 [255]
403
403
404 $ cat >> .hg/hgrc <<EOF
404 $ cat >> .hg/hgrc <<EOF
405 > [committemplate]
405 > [committemplate]
406 > changeset.commit.normal =
406 > changeset.commit.normal =
407 > # now, "changeset.commit" should be chosen for "hg commit"
407 > # now, "changeset.commit" should be chosen for "hg commit"
408 > EOF
408 > EOF
409
409
410 $ hg bookmark --inactive currentbookmark
410 $ hg bookmark --inactive currentbookmark
411 $ hg forget .hgsub
411 $ hg forget .hgsub
412 $ HGEDITOR=cat hg commit -q
412 $ HGEDITOR=cat hg commit -q
413 HG: this is "commit" template
413 HG: this is "commit" template
414 HG: Leave message empty to abort commit.
414 HG: Leave message empty to abort commit.
415 HG: no bookmark is activated
415 HG: no bookmark is activated
416 abort: empty commit message
416 abort: empty commit message
417 [255]
417 [255]
418
418
419 $ cat >> .hg/hgrc <<EOF
419 $ cat >> .hg/hgrc <<EOF
420 > [committemplate]
420 > [committemplate]
421 > changeset.commit =
421 > changeset.commit =
422 > # now, "changeset" should be chosen for "hg commit"
422 > # now, "changeset" should be chosen for "hg commit"
423 > EOF
423 > EOF
424
424
425 $ HGEDITOR=cat hg commit -q
425 $ HGEDITOR=cat hg commit -q
426 HG: this is customized commit template
426 HG: this is customized commit template
427 HG: Leave message empty to abort commit.
427 HG: Leave message empty to abort commit.
428 HG: no bookmark is activated
428 HG: no bookmark is activated
429 abort: empty commit message
429 abort: empty commit message
430 [255]
430 [255]
431
431
432 prove that we can show a diff of an amend using committemplate:
433
434 $ hg init issue4470
435 $ cd issue4470
436 $ cat >> .hg/hgrc <<EOF
437 > [committemplate]
438 > changeset = {desc}\n\n
439 > HG: {extramsg}
440 > HG: user: {author}\n{ifeq(p2rev, "-1", "",
441 > "HG: branch merge\n")
442 > }HG: branch '{branch}'\n{if(currentbookmark,
443 > "HG: bookmark '{currentbookmark}'\n") }{subrepos %
444 > "HG: subrepo {subrepo}\n" }
445 > {splitlines(diff()) % 'HG: {line}\n'}
446 > EOF
447 $ echo a > a
448 $ echo b > b
449 $ hg addr
450 adding a
451 adding b
452 $ hg ci -m 'init'
453 $ hg rm b
454 $ hg ci -m 'rm b'
455 $ hg export .
456 # HG changeset patch
457 # User test
458 # Date 0 0
459 # Thu Jan 01 00:00:00 1970 +0000
460 # Node ID 88d0ffa85e7a92ccc7c9cc187f9b17858bd206a7
461 # Parent 9118d25c26b1ca5cab5683b02100e7eb2c0d9471
462 rm b
463
464 diff -r 9118d25c26b1 -r 88d0ffa85e7a b
465 --- a/b Thu Jan 01 00:00:00 1970 +0000
466 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
467 @@ -1,1 +0,0 @@
468 -b
469 $ echo a >> a
470 $ HGEDITOR=cat hg commit --amend
471 rm b
472
473
474 HG: Leave message empty to abort commit.
475 HG: user: test
476 HG: branch 'default'
477
478 HG: diff -r 9118d25c26b1 a
479 HG: --- a/a Thu Jan 01 00:00:00 1970 +0000
480 HG: +++ b/a Thu Jan 01 00:00:00 1970 +0000
481 HG: @@ -1,1 +1,2 @@
482 HG: a
483 HG: +a
484 HG: diff -r 9118d25c26b1 b
485 HG: --- a/b Thu Jan 01 00:00:00 1970 +0000
486 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
487 HG: @@ -1,1 +0,0 @@
488 HG: -b
489 saved backup bundle to $TESTTMP/*/*-amend-backup.hg (glob)
490 $ cd ..
491
492 cleanup
432 $ cat >> .hg/hgrc <<EOF
493 $ cat >> .hg/hgrc <<EOF
433 > # disable customizing for subsequent tests
494 > # disable customizing for subsequent tests
434 > [committemplate]
495 > [committemplate]
435 > changeset =
496 > changeset =
436 > EOF
497 > EOF
437
498
438 $ cd ..
499 $ cd ..
439
500
440
501
441 commit copy
502 commit copy
442
503
443 $ hg init dir2
504 $ hg init dir2
444 $ cd dir2
505 $ cd dir2
445 $ echo bleh > bar
506 $ echo bleh > bar
446 $ hg add bar
507 $ hg add bar
447 $ hg ci -m 'add bar'
508 $ hg ci -m 'add bar'
448
509
449 $ hg cp bar foo
510 $ hg cp bar foo
450 $ echo >> bar
511 $ echo >> bar
451 $ hg ci -m 'cp bar foo; change bar'
512 $ hg ci -m 'cp bar foo; change bar'
452
513
453 $ hg debugrename foo
514 $ hg debugrename foo
454 foo renamed from bar:26d3ca0dfd18e44d796b564e38dd173c9668d3a9
515 foo renamed from bar:26d3ca0dfd18e44d796b564e38dd173c9668d3a9
455 $ hg debugindex bar
516 $ hg debugindex bar
456 rev offset length ..... linkrev nodeid p1 p2 (re)
517 rev offset length ..... linkrev nodeid p1 p2 (re)
457 0 0 6 ..... 0 26d3ca0dfd18 000000000000 000000000000 (re)
518 0 0 6 ..... 0 26d3ca0dfd18 000000000000 000000000000 (re)
458 1 6 7 ..... 1 d267bddd54f7 26d3ca0dfd18 000000000000 (re)
519 1 6 7 ..... 1 d267bddd54f7 26d3ca0dfd18 000000000000 (re)
459
520
460 $ cd ..
521 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now