##// END OF EJS Templates
memctx: calculate exact status being committed from specified files...
FUJIWARA Katsunori -
r23587:8063901e default
parent child Browse files
Show More
@@ -1,1686 +1,1714
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._files = files
1587 self._filectxfn = filectxfn
1588 self.substate = {}
1587 self.substate = {}
1589
1588
1590 # if store is not callable, wrap it in a function
1589 # if store is not callable, wrap it in a function
1591 if not callable(filectxfn):
1590 if not callable(filectxfn):
1592 def getfilectx(repo, memctx, path):
1591 def getfilectx(repo, memctx, path):
1593 fctx = filectxfn[path]
1592 fctx = filectxfn[path]
1594 # this is weird but apparently we only keep track of one parent
1593 # this is weird but apparently we only keep track of one parent
1595 # (why not only store that instead of a tuple?)
1594 # (why not only store that instead of a tuple?)
1596 copied = fctx.renamed()
1595 copied = fctx.renamed()
1597 if copied:
1596 if copied:
1598 copied = copied[0]
1597 copied = copied[0]
1599 return memfilectx(repo, path, fctx.data(),
1598 return memfilectx(repo, path, fctx.data(),
1600 islink=fctx.islink(), isexec=fctx.isexec(),
1599 islink=fctx.islink(), isexec=fctx.isexec(),
1601 copied=copied, memctx=memctx)
1600 copied=copied, memctx=memctx)
1602 self._filectxfn = getfilectx
1601 self._filectxfn = getfilectx
1602 else:
1603 # "util.cachefunc" reduces invocation of possibly expensive
1604 # "filectxfn" for performance (e.g. converting from another VCS)
1605 self._filectxfn = util.cachefunc(filectxfn)
1603
1606
1604 self._extra = extra and extra.copy() or {}
1607 self._extra = extra and extra.copy() or {}
1605 if self._extra.get('branch', '') == '':
1608 if self._extra.get('branch', '') == '':
1606 self._extra['branch'] = 'default'
1609 self._extra['branch'] = 'default'
1607
1610
1608 if editor:
1611 if editor:
1609 self._text = editor(self._repo, self, [])
1612 self._text = editor(self._repo, self, [])
1610 self._repo.savecommitmessage(self._text)
1613 self._repo.savecommitmessage(self._text)
1611
1614
1612 def filectx(self, path, filelog=None):
1615 def filectx(self, path, filelog=None):
1613 """get a file context from the working directory
1616 """get a file context from the working directory
1614
1617
1615 Returns None if file doesn't exist and should be removed."""
1618 Returns None if file doesn't exist and should be removed."""
1616 return self._filectxfn(self._repo, self, path)
1619 return self._filectxfn(self._repo, self, path)
1617
1620
1618 def commit(self):
1621 def commit(self):
1619 """commit context to the repo"""
1622 """commit context to the repo"""
1620 return self._repo.commitctx(self)
1623 return self._repo.commitctx(self)
1621
1624
1622 @propertycache
1625 @propertycache
1623 def _manifest(self):
1626 def _manifest(self):
1624 """generate a manifest based on the return values of filectxfn"""
1627 """generate a manifest based on the return values of filectxfn"""
1625
1628
1626 # keep this simple for now; just worry about p1
1629 # keep this simple for now; just worry about p1
1627 pctx = self._parents[0]
1630 pctx = self._parents[0]
1628 pman = pctx.manifest()
1631 pman = pctx.manifest()
1629 man = pctx.manifest().copy()
1632 man = pctx.manifest().copy()
1630
1633
1631 for f, fnode in pman.iteritems():
1634 for f, fnode in pman.iteritems():
1632 p1node = nullid
1635 p1node = nullid
1633 p2node = nullid
1636 p2node = nullid
1634 p = pctx[f].parents() # if file isn't in pctx, check p2?
1637 p = pctx[f].parents() # if file isn't in pctx, check p2?
1635 if len(p) > 0:
1638 if len(p) > 0:
1636 p1node = p[0].node()
1639 p1node = p[0].node()
1637 if len(p) > 1:
1640 if len(p) > 1:
1638 p2node = p[1].node()
1641 p2node = p[1].node()
1639 fctx = self[f]
1642 fctx = self[f]
1640 if fctx is None:
1643 if fctx is None:
1641 # removed file
1644 # removed file
1642 del man[f]
1645 del man[f]
1643 else:
1646 else:
1644 man[f] = revlog.hash(fctx.data(), p1node, p2node)
1647 man[f] = revlog.hash(fctx.data(), p1node, p2node)
1645
1648
1646 return man
1649 return man
1647
1650
1651 @propertycache
1652 def _status(self):
1653 """Calculate exact status from ``files`` specified at construction
1654 """
1655 man1 = self.p1().manifest()
1656 p2 = self._parents[1]
1657 # "1 < len(self._parents)" can't be used for checking
1658 # existence of the 2nd parent, because "memctx._parents" is
1659 # explicitly initialized by the list, of which length is 2.
1660 if p2.node() != nullid:
1661 man2 = p2.manifest()
1662 managing = lambda f: f in man1 or f in man2
1663 else:
1664 managing = lambda f: f in man1
1665
1666 modified, added, removed = [], [], []
1667 for f in self._files:
1668 if not managing(f):
1669 added.append(f)
1670 elif self[f]:
1671 modified.append(f)
1672 else:
1673 removed.append(f)
1674
1675 return scmutil.status(modified, added, removed, [], [], [], [])
1648
1676
1649 class memfilectx(committablefilectx):
1677 class memfilectx(committablefilectx):
1650 """memfilectx represents an in-memory file to commit.
1678 """memfilectx represents an in-memory file to commit.
1651
1679
1652 See memctx and committablefilectx for more details.
1680 See memctx and committablefilectx for more details.
1653 """
1681 """
1654 def __init__(self, repo, path, data, islink=False,
1682 def __init__(self, repo, path, data, islink=False,
1655 isexec=False, copied=None, memctx=None):
1683 isexec=False, copied=None, memctx=None):
1656 """
1684 """
1657 path is the normalized file path relative to repository root.
1685 path is the normalized file path relative to repository root.
1658 data is the file content as a string.
1686 data is the file content as a string.
1659 islink is True if the file is a symbolic link.
1687 islink is True if the file is a symbolic link.
1660 isexec is True if the file is executable.
1688 isexec is True if the file is executable.
1661 copied is the source file path if current file was copied in the
1689 copied is the source file path if current file was copied in the
1662 revision being committed, or None."""
1690 revision being committed, or None."""
1663 super(memfilectx, self).__init__(repo, path, None, memctx)
1691 super(memfilectx, self).__init__(repo, path, None, memctx)
1664 self._data = data
1692 self._data = data
1665 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1693 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1666 self._copied = None
1694 self._copied = None
1667 if copied:
1695 if copied:
1668 self._copied = (copied, nullid)
1696 self._copied = (copied, nullid)
1669
1697
1670 def data(self):
1698 def data(self):
1671 return self._data
1699 return self._data
1672 def size(self):
1700 def size(self):
1673 return len(self.data())
1701 return len(self.data())
1674 def flags(self):
1702 def flags(self):
1675 return self._flags
1703 return self._flags
1676 def renamed(self):
1704 def renamed(self):
1677 return self._copied
1705 return self._copied
1678
1706
1679 def remove(self, ignoremissing=False):
1707 def remove(self, ignoremissing=False):
1680 """wraps unlink for a repo's working directory"""
1708 """wraps unlink for a repo's working directory"""
1681 # need to figure out what to do here
1709 # need to figure out what to do here
1682 del self._changectx[self._path]
1710 del self._changectx[self._path]
1683
1711
1684 def write(self, data, flags):
1712 def write(self, data, flags):
1685 """wraps repo.wwrite"""
1713 """wraps repo.wwrite"""
1686 self._data = data
1714 self._data = data
@@ -1,914 +1,916
1 $ hg init
1 $ hg init
2
2
3 Setup:
3 Setup:
4
4
5 $ echo a >> a
5 $ echo a >> a
6 $ hg ci -Am 'base'
6 $ hg ci -Am 'base'
7 adding a
7 adding a
8
8
9 Refuse to amend public csets:
9 Refuse to amend public csets:
10
10
11 $ hg phase -r . -p
11 $ hg phase -r . -p
12 $ hg ci --amend
12 $ hg ci --amend
13 abort: cannot amend public changesets
13 abort: cannot amend public changesets
14 [255]
14 [255]
15 $ hg phase -r . -f -d
15 $ hg phase -r . -f -d
16
16
17 $ echo a >> a
17 $ echo a >> a
18 $ hg ci -Am 'base1'
18 $ hg ci -Am 'base1'
19
19
20 Nothing to amend:
20 Nothing to amend:
21
21
22 $ hg ci --amend
22 $ hg ci --amend
23 nothing changed
23 nothing changed
24 [1]
24 [1]
25
25
26 $ cat >> $HGRCPATH <<EOF
26 $ cat >> $HGRCPATH <<EOF
27 > [hooks]
27 > [hooks]
28 > pretxncommit.foo = sh -c "echo \\"pretxncommit \$HG_NODE\\"; hg id -r \$HG_NODE"
28 > pretxncommit.foo = sh -c "echo \\"pretxncommit \$HG_NODE\\"; hg id -r \$HG_NODE"
29 > EOF
29 > EOF
30
30
31 Amending changeset with changes in working dir:
31 Amending changeset with changes in working dir:
32 (and check that --message does not trigger an editor)
32 (and check that --message does not trigger an editor)
33
33
34 $ echo a >> a
34 $ echo a >> a
35 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -m 'amend base1'
35 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -m 'amend base1'
36 pretxncommit 43f1ba15f28a50abf0aae529cf8a16bfced7b149
36 pretxncommit 43f1ba15f28a50abf0aae529cf8a16bfced7b149
37 43f1ba15f28a tip
37 43f1ba15f28a tip
38 saved backup bundle to $TESTTMP/.hg/strip-backup/489edb5b847d-amend-backup.hg (glob)
38 saved backup bundle to $TESTTMP/.hg/strip-backup/489edb5b847d-amend-backup.hg (glob)
39 $ echo 'pretxncommit.foo = ' >> $HGRCPATH
39 $ echo 'pretxncommit.foo = ' >> $HGRCPATH
40 $ hg diff -c .
40 $ hg diff -c .
41 diff -r ad120869acf0 -r 43f1ba15f28a a
41 diff -r ad120869acf0 -r 43f1ba15f28a a
42 --- a/a Thu Jan 01 00:00:00 1970 +0000
42 --- a/a Thu Jan 01 00:00:00 1970 +0000
43 +++ b/a Thu Jan 01 00:00:00 1970 +0000
43 +++ b/a Thu Jan 01 00:00:00 1970 +0000
44 @@ -1,1 +1,3 @@
44 @@ -1,1 +1,3 @@
45 a
45 a
46 +a
46 +a
47 +a
47 +a
48 $ hg log
48 $ hg log
49 changeset: 1:43f1ba15f28a
49 changeset: 1:43f1ba15f28a
50 tag: tip
50 tag: tip
51 user: test
51 user: test
52 date: Thu Jan 01 00:00:00 1970 +0000
52 date: Thu Jan 01 00:00:00 1970 +0000
53 summary: amend base1
53 summary: amend base1
54
54
55 changeset: 0:ad120869acf0
55 changeset: 0:ad120869acf0
56 user: test
56 user: test
57 date: Thu Jan 01 00:00:00 1970 +0000
57 date: Thu Jan 01 00:00:00 1970 +0000
58 summary: base
58 summary: base
59
59
60
60
61 Check proper abort for empty message
61 Check proper abort for empty message
62
62
63 $ cat > editor.sh << '__EOF__'
63 $ cat > editor.sh << '__EOF__'
64 > #!/bin/sh
64 > #!/bin/sh
65 > echo "" > "$1"
65 > echo "" > "$1"
66 > __EOF__
66 > __EOF__
67 $ echo b > b
67 $ echo b > b
68 $ hg add b
68 $ hg add b
69 $ hg summary
69 $ hg summary
70 parent: 1:43f1ba15f28a tip
70 parent: 1:43f1ba15f28a tip
71 amend base1
71 amend base1
72 branch: default
72 branch: default
73 commit: 1 added, 1 unknown
73 commit: 1 added, 1 unknown
74 update: (current)
74 update: (current)
75 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend
75 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend
76 transaction abort!
76 transaction abort!
77 rollback completed
77 rollback completed
78 abort: empty commit message
78 abort: empty commit message
79 [255]
79 [255]
80 $ hg summary
80 $ hg summary
81 parent: 1:43f1ba15f28a tip
81 parent: 1:43f1ba15f28a tip
82 amend base1
82 amend base1
83 branch: default
83 branch: default
84 commit: 1 added, 1 unknown
84 commit: 1 added, 1 unknown
85 update: (current)
85 update: (current)
86
86
87 Add new file:
87 Add new file:
88 $ hg ci --amend -m 'amend base1 new file'
88 $ hg ci --amend -m 'amend base1 new file'
89 saved backup bundle to $TESTTMP/.hg/strip-backup/43f1ba15f28a-amend-backup.hg (glob)
89 saved backup bundle to $TESTTMP/.hg/strip-backup/43f1ba15f28a-amend-backup.hg (glob)
90
90
91 Remove file that was added in amended commit:
91 Remove file that was added in amended commit:
92 (and test logfile option)
92 (and test logfile option)
93 (and test that logfile option do not trigger an editor)
93 (and test that logfile option do not trigger an editor)
94
94
95 $ hg rm b
95 $ hg rm b
96 $ echo 'amend base1 remove new file' > ../logfile
96 $ echo 'amend base1 remove new file' > ../logfile
97 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg ci --amend --logfile ../logfile
97 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg ci --amend --logfile ../logfile
98 saved backup bundle to $TESTTMP/.hg/strip-backup/b8e3cb2b3882-amend-backup.hg (glob)
98 saved backup bundle to $TESTTMP/.hg/strip-backup/b8e3cb2b3882-amend-backup.hg (glob)
99
99
100 $ hg cat b
100 $ hg cat b
101 b: no such file in rev 74609c7f506e
101 b: no such file in rev 74609c7f506e
102 [1]
102 [1]
103
103
104 No changes, just a different message:
104 No changes, just a different message:
105
105
106 $ hg ci -v --amend -m 'no changes, new message'
106 $ hg ci -v --amend -m 'no changes, new message'
107 amending changeset 74609c7f506e
107 amending changeset 74609c7f506e
108 copying changeset 74609c7f506e to ad120869acf0
108 copying changeset 74609c7f506e to ad120869acf0
109 a
109 a
110 stripping amended changeset 74609c7f506e
110 stripping amended changeset 74609c7f506e
111 1 changesets found
111 1 changesets found
112 saved backup bundle to $TESTTMP/.hg/strip-backup/74609c7f506e-amend-backup.hg (glob)
112 saved backup bundle to $TESTTMP/.hg/strip-backup/74609c7f506e-amend-backup.hg (glob)
113 1 changesets found
113 1 changesets found
114 adding branch
114 adding branch
115 adding changesets
115 adding changesets
116 adding manifests
116 adding manifests
117 adding file changes
117 adding file changes
118 added 1 changesets with 1 changes to 1 files
118 added 1 changesets with 1 changes to 1 files
119 committed changeset 1:1cd866679df8
119 committed changeset 1:1cd866679df8
120 $ hg diff -c .
120 $ hg diff -c .
121 diff -r ad120869acf0 -r 1cd866679df8 a
121 diff -r ad120869acf0 -r 1cd866679df8 a
122 --- a/a Thu Jan 01 00:00:00 1970 +0000
122 --- a/a Thu Jan 01 00:00:00 1970 +0000
123 +++ b/a Thu Jan 01 00:00:00 1970 +0000
123 +++ b/a Thu Jan 01 00:00:00 1970 +0000
124 @@ -1,1 +1,3 @@
124 @@ -1,1 +1,3 @@
125 a
125 a
126 +a
126 +a
127 +a
127 +a
128 $ hg log
128 $ hg log
129 changeset: 1:1cd866679df8
129 changeset: 1:1cd866679df8
130 tag: tip
130 tag: tip
131 user: test
131 user: test
132 date: Thu Jan 01 00:00:00 1970 +0000
132 date: Thu Jan 01 00:00:00 1970 +0000
133 summary: no changes, new message
133 summary: no changes, new message
134
134
135 changeset: 0:ad120869acf0
135 changeset: 0:ad120869acf0
136 user: test
136 user: test
137 date: Thu Jan 01 00:00:00 1970 +0000
137 date: Thu Jan 01 00:00:00 1970 +0000
138 summary: base
138 summary: base
139
139
140
140
141 Disable default date on commit so when -d isn't given, the old date is preserved:
141 Disable default date on commit so when -d isn't given, the old date is preserved:
142
142
143 $ echo '[defaults]' >> $HGRCPATH
143 $ echo '[defaults]' >> $HGRCPATH
144 $ echo 'commit=' >> $HGRCPATH
144 $ echo 'commit=' >> $HGRCPATH
145
145
146 Test -u/-d:
146 Test -u/-d:
147
147
148 $ cat > .hg/checkeditform.sh <<EOF
148 $ cat > .hg/checkeditform.sh <<EOF
149 > env | grep HGEDITFORM
149 > env | grep HGEDITFORM
150 > true
150 > true
151 > EOF
151 > EOF
152 $ HGEDITOR="sh .hg/checkeditform.sh" hg ci --amend -u foo -d '1 0'
152 $ HGEDITOR="sh .hg/checkeditform.sh" hg ci --amend -u foo -d '1 0'
153 HGEDITFORM=commit.amend.normal
153 HGEDITFORM=commit.amend.normal
154 saved backup bundle to $TESTTMP/.hg/strip-backup/1cd866679df8-amend-backup.hg (glob)
154 saved backup bundle to $TESTTMP/.hg/strip-backup/1cd866679df8-amend-backup.hg (glob)
155 $ echo a >> a
155 $ echo a >> a
156 $ hg ci --amend -u foo -d '1 0'
156 $ hg ci --amend -u foo -d '1 0'
157 saved backup bundle to $TESTTMP/.hg/strip-backup/780e6f23e03d-amend-backup.hg (glob)
157 saved backup bundle to $TESTTMP/.hg/strip-backup/780e6f23e03d-amend-backup.hg (glob)
158 $ hg log -r .
158 $ hg log -r .
159 changeset: 1:5f357c7560ab
159 changeset: 1:5f357c7560ab
160 tag: tip
160 tag: tip
161 user: foo
161 user: foo
162 date: Thu Jan 01 00:00:01 1970 +0000
162 date: Thu Jan 01 00:00:01 1970 +0000
163 summary: no changes, new message
163 summary: no changes, new message
164
164
165
165
166 Open editor with old commit message if a message isn't given otherwise:
166 Open editor with old commit message if a message isn't given otherwise:
167
167
168 $ cat > editor.sh << '__EOF__'
168 $ cat > editor.sh << '__EOF__'
169 > #!/bin/sh
169 > #!/bin/sh
170 > cat $1
170 > cat $1
171 > echo "another precious commit message" > "$1"
171 > echo "another precious commit message" > "$1"
172 > __EOF__
172 > __EOF__
173
173
174 at first, test saving last-message.txt
174 at first, test saving last-message.txt
175
175
176 $ cat > .hg/hgrc << '__EOF__'
176 $ cat > .hg/hgrc << '__EOF__'
177 > [hooks]
177 > [hooks]
178 > pretxncommit.test-saving-last-message = false
178 > pretxncommit.test-saving-last-message = false
179 > __EOF__
179 > __EOF__
180
180
181 $ rm -f .hg/last-message.txt
181 $ rm -f .hg/last-message.txt
182 $ hg commit --amend -v -m "message given from command line"
182 $ hg commit --amend -v -m "message given from command line"
183 amending changeset 5f357c7560ab
183 amending changeset 5f357c7560ab
184 copying changeset 5f357c7560ab to ad120869acf0
184 copying changeset 5f357c7560ab to ad120869acf0
185 a
185 a
186 running hook pretxncommit.test-saving-last-message: false
186 running hook pretxncommit.test-saving-last-message: false
187 transaction abort!
187 transaction abort!
188 rollback completed
188 rollback completed
189 abort: pretxncommit.test-saving-last-message hook exited with status 1
189 abort: pretxncommit.test-saving-last-message hook exited with status 1
190 [255]
190 [255]
191 $ cat .hg/last-message.txt
191 $ cat .hg/last-message.txt
192 message given from command line (no-eol)
192 message given from command line (no-eol)
193
193
194 $ rm -f .hg/last-message.txt
194 $ rm -f .hg/last-message.txt
195 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
195 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
196 amending changeset 5f357c7560ab
196 amending changeset 5f357c7560ab
197 copying changeset 5f357c7560ab to ad120869acf0
197 copying changeset 5f357c7560ab to ad120869acf0
198 no changes, new message
198 no changes, new message
199
199
200
200
201 HG: Enter commit message. Lines beginning with 'HG:' are removed.
201 HG: Enter commit message. Lines beginning with 'HG:' are removed.
202 HG: Leave message empty to abort commit.
202 HG: Leave message empty to abort commit.
203 HG: --
203 HG: --
204 HG: user: foo
204 HG: user: foo
205 HG: branch 'default'
205 HG: branch 'default'
206 HG: changed a
206 HG: changed a
207 a
207 a
208 running hook pretxncommit.test-saving-last-message: false
208 running hook pretxncommit.test-saving-last-message: false
209 transaction abort!
209 transaction abort!
210 rollback completed
210 rollback completed
211 abort: pretxncommit.test-saving-last-message hook exited with status 1
211 abort: pretxncommit.test-saving-last-message hook exited with status 1
212 [255]
212 [255]
213
213
214 $ cat .hg/last-message.txt
214 $ cat .hg/last-message.txt
215 another precious commit message
215 another precious commit message
216
216
217 $ cat > .hg/hgrc << '__EOF__'
217 $ cat > .hg/hgrc << '__EOF__'
218 > [hooks]
218 > [hooks]
219 > pretxncommit.test-saving-last-message =
219 > pretxncommit.test-saving-last-message =
220 > __EOF__
220 > __EOF__
221
221
222 then, test editing custom commit message
222 then, test editing custom commit message
223
223
224 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
224 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
225 amending changeset 5f357c7560ab
225 amending changeset 5f357c7560ab
226 copying changeset 5f357c7560ab to ad120869acf0
226 copying changeset 5f357c7560ab to ad120869acf0
227 no changes, new message
227 no changes, new message
228
228
229
229
230 HG: Enter commit message. Lines beginning with 'HG:' are removed.
230 HG: Enter commit message. Lines beginning with 'HG:' are removed.
231 HG: Leave message empty to abort commit.
231 HG: Leave message empty to abort commit.
232 HG: --
232 HG: --
233 HG: user: foo
233 HG: user: foo
234 HG: branch 'default'
234 HG: branch 'default'
235 HG: changed a
235 HG: changed a
236 a
236 a
237 stripping amended changeset 5f357c7560ab
237 stripping amended changeset 5f357c7560ab
238 1 changesets found
238 1 changesets found
239 saved backup bundle to $TESTTMP/.hg/strip-backup/5f357c7560ab-amend-backup.hg (glob)
239 saved backup bundle to $TESTTMP/.hg/strip-backup/5f357c7560ab-amend-backup.hg (glob)
240 1 changesets found
240 1 changesets found
241 adding branch
241 adding branch
242 adding changesets
242 adding changesets
243 adding manifests
243 adding manifests
244 adding file changes
244 adding file changes
245 added 1 changesets with 1 changes to 1 files
245 added 1 changesets with 1 changes to 1 files
246 committed changeset 1:7ab3bf440b54
246 committed changeset 1:7ab3bf440b54
247
247
248 Same, but with changes in working dir (different code path):
248 Same, but with changes in working dir (different code path):
249
249
250 $ echo a >> a
250 $ echo a >> a
251 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
251 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
252 amending changeset 7ab3bf440b54
252 amending changeset 7ab3bf440b54
253 a
253 a
254 copying changeset a0ea9b1a4c8c to ad120869acf0
254 copying changeset a0ea9b1a4c8c to ad120869acf0
255 another precious commit message
255 another precious commit message
256
256
257
257
258 HG: Enter commit message. Lines beginning with 'HG:' are removed.
258 HG: Enter commit message. Lines beginning with 'HG:' are removed.
259 HG: Leave message empty to abort commit.
259 HG: Leave message empty to abort commit.
260 HG: --
260 HG: --
261 HG: user: foo
261 HG: user: foo
262 HG: branch 'default'
262 HG: branch 'default'
263 HG: changed a
263 HG: changed a
264 a
264 a
265 stripping intermediate changeset a0ea9b1a4c8c
265 stripping intermediate changeset a0ea9b1a4c8c
266 stripping amended changeset 7ab3bf440b54
266 stripping amended changeset 7ab3bf440b54
267 2 changesets found
267 2 changesets found
268 saved backup bundle to $TESTTMP/.hg/strip-backup/7ab3bf440b54-amend-backup.hg (glob)
268 saved backup bundle to $TESTTMP/.hg/strip-backup/7ab3bf440b54-amend-backup.hg (glob)
269 1 changesets found
269 1 changesets found
270 adding branch
270 adding branch
271 adding changesets
271 adding changesets
272 adding manifests
272 adding manifests
273 adding file changes
273 adding file changes
274 added 1 changesets with 1 changes to 1 files
274 added 1 changesets with 1 changes to 1 files
275 committed changeset 1:ea22a388757c
275 committed changeset 1:ea22a388757c
276
276
277 $ rm editor.sh
277 $ rm editor.sh
278 $ hg log -r .
278 $ hg log -r .
279 changeset: 1:ea22a388757c
279 changeset: 1:ea22a388757c
280 tag: tip
280 tag: tip
281 user: foo
281 user: foo
282 date: Thu Jan 01 00:00:01 1970 +0000
282 date: Thu Jan 01 00:00:01 1970 +0000
283 summary: another precious commit message
283 summary: another precious commit message
284
284
285
285
286 Moving bookmarks, preserve active bookmark:
286 Moving bookmarks, preserve active bookmark:
287
287
288 $ hg book book1
288 $ hg book book1
289 $ hg book book2
289 $ hg book book2
290 $ hg ci --amend -m 'move bookmarks'
290 $ hg ci --amend -m 'move bookmarks'
291 saved backup bundle to $TESTTMP/.hg/strip-backup/ea22a388757c-amend-backup.hg (glob)
291 saved backup bundle to $TESTTMP/.hg/strip-backup/ea22a388757c-amend-backup.hg (glob)
292 $ hg book
292 $ hg book
293 book1 1:6cec5aa930e2
293 book1 1:6cec5aa930e2
294 * book2 1:6cec5aa930e2
294 * book2 1:6cec5aa930e2
295 $ echo a >> a
295 $ echo a >> a
296 $ hg ci --amend -m 'move bookmarks'
296 $ hg ci --amend -m 'move bookmarks'
297 saved backup bundle to $TESTTMP/.hg/strip-backup/6cec5aa930e2-amend-backup.hg (glob)
297 saved backup bundle to $TESTTMP/.hg/strip-backup/6cec5aa930e2-amend-backup.hg (glob)
298 $ hg book
298 $ hg book
299 book1 1:48bb6e53a15f
299 book1 1:48bb6e53a15f
300 * book2 1:48bb6e53a15f
300 * book2 1:48bb6e53a15f
301
301
302 abort does not loose bookmarks
302 abort does not loose bookmarks
303
303
304 $ cat > editor.sh << '__EOF__'
304 $ cat > editor.sh << '__EOF__'
305 > #!/bin/sh
305 > #!/bin/sh
306 > echo "" > "$1"
306 > echo "" > "$1"
307 > __EOF__
307 > __EOF__
308 $ echo a >> a
308 $ echo a >> a
309 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend
309 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend
310 transaction abort!
310 transaction abort!
311 rollback completed
311 rollback completed
312 abort: empty commit message
312 abort: empty commit message
313 [255]
313 [255]
314 $ hg book
314 $ hg book
315 book1 1:48bb6e53a15f
315 book1 1:48bb6e53a15f
316 * book2 1:48bb6e53a15f
316 * book2 1:48bb6e53a15f
317 $ hg revert -Caq
317 $ hg revert -Caq
318 $ rm editor.sh
318 $ rm editor.sh
319
319
320 $ echo '[defaults]' >> $HGRCPATH
320 $ echo '[defaults]' >> $HGRCPATH
321 $ echo "commit=-d '0 0'" >> $HGRCPATH
321 $ echo "commit=-d '0 0'" >> $HGRCPATH
322
322
323 Moving branches:
323 Moving branches:
324
324
325 $ hg branch foo
325 $ hg branch foo
326 marked working directory as branch foo
326 marked working directory as branch foo
327 (branches are permanent and global, did you want a bookmark?)
327 (branches are permanent and global, did you want a bookmark?)
328 $ echo a >> a
328 $ echo a >> a
329 $ hg ci -m 'branch foo'
329 $ hg ci -m 'branch foo'
330 $ hg branch default -f
330 $ hg branch default -f
331 marked working directory as branch default
331 marked working directory as branch default
332 (branches are permanent and global, did you want a bookmark?)
332 (branches are permanent and global, did you want a bookmark?)
333 $ hg ci --amend -m 'back to default'
333 $ hg ci --amend -m 'back to default'
334 saved backup bundle to $TESTTMP/.hg/strip-backup/8ac881fbf49d-amend-backup.hg (glob)
334 saved backup bundle to $TESTTMP/.hg/strip-backup/8ac881fbf49d-amend-backup.hg (glob)
335 $ hg branches
335 $ hg branches
336 default 2:ce12b0b57d46
336 default 2:ce12b0b57d46
337
337
338 Close branch:
338 Close branch:
339
339
340 $ hg up -q 0
340 $ hg up -q 0
341 $ echo b >> b
341 $ echo b >> b
342 $ hg branch foo
342 $ hg branch foo
343 marked working directory as branch foo
343 marked working directory as branch foo
344 (branches are permanent and global, did you want a bookmark?)
344 (branches are permanent and global, did you want a bookmark?)
345 $ hg ci -Am 'fork'
345 $ hg ci -Am 'fork'
346 adding b
346 adding b
347 $ echo b >> b
347 $ echo b >> b
348 $ hg ci -mb
348 $ hg ci -mb
349 $ hg ci --amend --close-branch -m 'closing branch foo'
349 $ hg ci --amend --close-branch -m 'closing branch foo'
350 saved backup bundle to $TESTTMP/.hg/strip-backup/c962248fa264-amend-backup.hg (glob)
350 saved backup bundle to $TESTTMP/.hg/strip-backup/c962248fa264-amend-backup.hg (glob)
351
351
352 Same thing, different code path:
352 Same thing, different code path:
353
353
354 $ echo b >> b
354 $ echo b >> b
355 $ hg ci -m 'reopen branch'
355 $ hg ci -m 'reopen branch'
356 reopening closed branch head 4
356 reopening closed branch head 4
357 $ echo b >> b
357 $ echo b >> b
358 $ hg ci --amend --close-branch
358 $ hg ci --amend --close-branch
359 saved backup bundle to $TESTTMP/.hg/strip-backup/027371728205-amend-backup.hg (glob)
359 saved backup bundle to $TESTTMP/.hg/strip-backup/027371728205-amend-backup.hg (glob)
360 $ hg branches
360 $ hg branches
361 default 2:ce12b0b57d46
361 default 2:ce12b0b57d46
362
362
363 Refuse to amend during a merge:
363 Refuse to amend during a merge:
364
364
365 $ hg up -q default
365 $ hg up -q default
366 $ hg merge foo
366 $ hg merge foo
367 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
367 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
368 (branch merge, don't forget to commit)
368 (branch merge, don't forget to commit)
369 $ hg ci --amend
369 $ hg ci --amend
370 abort: cannot amend while merging
370 abort: cannot amend while merging
371 [255]
371 [255]
372 $ hg ci -m 'merge'
372 $ hg ci -m 'merge'
373
373
374 Follow copies/renames:
374 Follow copies/renames:
375
375
376 $ hg mv b c
376 $ hg mv b c
377 $ hg ci -m 'b -> c'
377 $ hg ci -m 'b -> c'
378 $ hg mv c d
378 $ hg mv c d
379 $ hg ci --amend -m 'b -> d'
379 $ hg ci --amend -m 'b -> d'
380 saved backup bundle to $TESTTMP/.hg/strip-backup/b8c6eac7f12e-amend-backup.hg (glob)
380 saved backup bundle to $TESTTMP/.hg/strip-backup/b8c6eac7f12e-amend-backup.hg (glob)
381 $ hg st --rev '.^' --copies d
381 $ hg st --rev '.^' --copies d
382 A d
382 A d
383 b
383 b
384 $ hg cp d e
384 $ hg cp d e
385 $ hg ci -m 'e = d'
385 $ hg ci -m 'e = d'
386 $ hg cp e f
386 $ hg cp e f
387 $ hg ci --amend -m 'f = d'
387 $ hg ci --amend -m 'f = d'
388 saved backup bundle to $TESTTMP/.hg/strip-backup/7f9761d65613-amend-backup.hg (glob)
388 saved backup bundle to $TESTTMP/.hg/strip-backup/7f9761d65613-amend-backup.hg (glob)
389 $ hg st --rev '.^' --copies f
389 $ hg st --rev '.^' --copies f
390 A f
390 A f
391 d
391 d
392
392
393 $ mv f f.orig
393 $ mv f f.orig
394 $ hg rm -A f
394 $ hg rm -A f
395 $ hg ci -m removef
395 $ hg ci -m removef
396 $ hg cp a f
396 $ hg cp a f
397 $ mv f.orig f
397 $ mv f.orig f
398 $ hg ci --amend -m replacef
398 $ hg ci --amend -m replacef
399 saved backup bundle to $TESTTMP/.hg/strip-backup/9e8c5f7e3d95-amend-backup.hg (glob)
399 saved backup bundle to $TESTTMP/.hg/strip-backup/9e8c5f7e3d95-amend-backup.hg (glob)
400 $ hg st --change . --copies
400 $ hg st --change . --copies
401 $ hg log -r . --template "{file_copies}\n"
401 $ hg log -r . --template "{file_copies}\n"
402
402
403
403
404 Move added file (issue3410):
404 Move added file (issue3410):
405
405
406 $ echo g >> g
406 $ echo g >> g
407 $ hg ci -Am g
407 $ hg ci -Am g
408 adding g
408 adding g
409 $ hg mv g h
409 $ hg mv g h
410 $ hg ci --amend
410 $ hg ci --amend
411 saved backup bundle to $TESTTMP/.hg/strip-backup/24aa8eacce2b-amend-backup.hg (glob)
411 saved backup bundle to $TESTTMP/.hg/strip-backup/24aa8eacce2b-amend-backup.hg (glob)
412 $ hg st --change . --copies h
412 $ hg st --change . --copies h
413 A h
413 A h
414 $ hg log -r . --template "{file_copies}\n"
414 $ hg log -r . --template "{file_copies}\n"
415
415
416
416
417 Can't rollback an amend:
417 Can't rollback an amend:
418
418
419 $ hg rollback
419 $ hg rollback
420 no rollback information available
420 no rollback information available
421 [1]
421 [1]
422
422
423 Preserve extra dict (issue3430):
423 Preserve extra dict (issue3430):
424
424
425 $ hg branch a
425 $ hg branch a
426 marked working directory as branch a
426 marked working directory as branch a
427 (branches are permanent and global, did you want a bookmark?)
427 (branches are permanent and global, did you want a bookmark?)
428 $ echo a >> a
428 $ echo a >> a
429 $ hg ci -ma
429 $ hg ci -ma
430 $ hg ci --amend -m "a'"
430 $ hg ci --amend -m "a'"
431 saved backup bundle to $TESTTMP/.hg/strip-backup/3837aa2a2fdb-amend-backup.hg (glob)
431 saved backup bundle to $TESTTMP/.hg/strip-backup/3837aa2a2fdb-amend-backup.hg (glob)
432 $ hg log -r . --template "{branch}\n"
432 $ hg log -r . --template "{branch}\n"
433 a
433 a
434 $ hg ci --amend -m "a''"
434 $ hg ci --amend -m "a''"
435 saved backup bundle to $TESTTMP/.hg/strip-backup/c05c06be7514-amend-backup.hg (glob)
435 saved backup bundle to $TESTTMP/.hg/strip-backup/c05c06be7514-amend-backup.hg (glob)
436 $ hg log -r . --template "{branch}\n"
436 $ hg log -r . --template "{branch}\n"
437 a
437 a
438
438
439 Also preserve other entries in the dict that are in the old commit,
439 Also preserve other entries in the dict that are in the old commit,
440 first graft something so there's an additional entry:
440 first graft something so there's an additional entry:
441
441
442 $ hg up 0 -q
442 $ hg up 0 -q
443 $ echo z > z
443 $ echo z > z
444 $ hg ci -Am 'fork'
444 $ hg ci -Am 'fork'
445 adding z
445 adding z
446 created new head
446 created new head
447 $ hg up 11
447 $ hg up 11
448 5 files updated, 0 files merged, 1 files removed, 0 files unresolved
448 5 files updated, 0 files merged, 1 files removed, 0 files unresolved
449 $ hg graft 12
449 $ hg graft 12
450 grafting 12:2647734878ef "fork" (tip)
450 grafting 12:2647734878ef "fork" (tip)
451 $ hg ci --amend -m 'graft amend'
451 $ hg ci --amend -m 'graft amend'
452 saved backup bundle to $TESTTMP/.hg/strip-backup/bd010aea3f39-amend-backup.hg (glob)
452 saved backup bundle to $TESTTMP/.hg/strip-backup/bd010aea3f39-amend-backup.hg (glob)
453 $ hg log -r . --debug | grep extra
453 $ hg log -r . --debug | grep extra
454 extra: amend_source=bd010aea3f39f3fb2a2f884b9ccb0471cd77398e
454 extra: amend_source=bd010aea3f39f3fb2a2f884b9ccb0471cd77398e
455 extra: branch=a
455 extra: branch=a
456 extra: source=2647734878ef0236dda712fae9c1651cf694ea8a
456 extra: source=2647734878ef0236dda712fae9c1651cf694ea8a
457
457
458 Preserve phase
458 Preserve phase
459
459
460 $ hg phase '.^::.'
460 $ hg phase '.^::.'
461 11: draft
461 11: draft
462 13: draft
462 13: draft
463 $ hg phase --secret --force .
463 $ hg phase --secret --force .
464 $ hg phase '.^::.'
464 $ hg phase '.^::.'
465 11: draft
465 11: draft
466 13: secret
466 13: secret
467 $ hg commit --amend -m 'amend for phase' -q
467 $ hg commit --amend -m 'amend for phase' -q
468 $ hg phase '.^::.'
468 $ hg phase '.^::.'
469 11: draft
469 11: draft
470 13: secret
470 13: secret
471
471
472 Test amend with obsolete
472 Test amend with obsolete
473 ---------------------------
473 ---------------------------
474
474
475 Enable obsolete
475 Enable obsolete
476
476
477 $ cat >> $HGRCPATH << EOF
477 $ cat >> $HGRCPATH << EOF
478 > [experimental]
478 > [experimental]
479 > evolution=createmarkers,allowunstable
479 > evolution=createmarkers,allowunstable
480 > EOF
480 > EOF
481
481
482 Amend with no files changes
482 Amend with no files changes
483
483
484 $ hg id -n
484 $ hg id -n
485 13
485 13
486 $ hg ci --amend -m 'babar'
486 $ hg ci --amend -m 'babar'
487 $ hg id -n
487 $ hg id -n
488 14
488 14
489 $ hg log -Gl 3 --style=compact
489 $ hg log -Gl 3 --style=compact
490 @ 14[tip]:11 b650e6ee8614 1970-01-01 00:00 +0000 test
490 @ 14[tip]:11 b650e6ee8614 1970-01-01 00:00 +0000 test
491 | babar
491 | babar
492 |
492 |
493 | o 12:0 2647734878ef 1970-01-01 00:00 +0000 test
493 | o 12:0 2647734878ef 1970-01-01 00:00 +0000 test
494 | | fork
494 | | fork
495 | |
495 | |
496 o | 11 3334b7925910 1970-01-01 00:00 +0000 test
496 o | 11 3334b7925910 1970-01-01 00:00 +0000 test
497 | | a''
497 | | a''
498 | |
498 | |
499 $ hg log -Gl 4 --hidden --style=compact
499 $ hg log -Gl 4 --hidden --style=compact
500 @ 14[tip]:11 b650e6ee8614 1970-01-01 00:00 +0000 test
500 @ 14[tip]:11 b650e6ee8614 1970-01-01 00:00 +0000 test
501 | babar
501 | babar
502 |
502 |
503 | x 13:11 68ff8ff97044 1970-01-01 00:00 +0000 test
503 | x 13:11 68ff8ff97044 1970-01-01 00:00 +0000 test
504 |/ amend for phase
504 |/ amend for phase
505 |
505 |
506 | o 12:0 2647734878ef 1970-01-01 00:00 +0000 test
506 | o 12:0 2647734878ef 1970-01-01 00:00 +0000 test
507 | | fork
507 | | fork
508 | |
508 | |
509 o | 11 3334b7925910 1970-01-01 00:00 +0000 test
509 o | 11 3334b7925910 1970-01-01 00:00 +0000 test
510 | | a''
510 | | a''
511 | |
511 | |
512
512
513 Amend with files changes
513 Amend with files changes
514
514
515 (note: the extra commit over 15 is a temporary junk I would be happy to get
515 (note: the extra commit over 15 is a temporary junk I would be happy to get
516 ride of)
516 ride of)
517
517
518 $ echo 'babar' >> a
518 $ echo 'babar' >> a
519 $ hg commit --amend
519 $ hg commit --amend
520 $ hg log -Gl 6 --hidden --style=compact
520 $ hg log -Gl 6 --hidden --style=compact
521 @ 16[tip]:11 9f9e9bccf56c 1970-01-01 00:00 +0000 test
521 @ 16[tip]:11 9f9e9bccf56c 1970-01-01 00:00 +0000 test
522 | babar
522 | babar
523 |
523 |
524 | x 15 90fef497c56f 1970-01-01 00:00 +0000 test
524 | x 15 90fef497c56f 1970-01-01 00:00 +0000 test
525 | | temporary amend commit for b650e6ee8614
525 | | temporary amend commit for b650e6ee8614
526 | |
526 | |
527 | x 14:11 b650e6ee8614 1970-01-01 00:00 +0000 test
527 | x 14:11 b650e6ee8614 1970-01-01 00:00 +0000 test
528 |/ babar
528 |/ babar
529 |
529 |
530 | x 13:11 68ff8ff97044 1970-01-01 00:00 +0000 test
530 | x 13:11 68ff8ff97044 1970-01-01 00:00 +0000 test
531 |/ amend for phase
531 |/ amend for phase
532 |
532 |
533 | o 12:0 2647734878ef 1970-01-01 00:00 +0000 test
533 | o 12:0 2647734878ef 1970-01-01 00:00 +0000 test
534 | | fork
534 | | fork
535 | |
535 | |
536 o | 11 3334b7925910 1970-01-01 00:00 +0000 test
536 o | 11 3334b7925910 1970-01-01 00:00 +0000 test
537 | | a''
537 | | a''
538 | |
538 | |
539
539
540
540
541 Test that amend does not make it easy to create obsolescence cycle
541 Test that amend does not make it easy to create obsolescence cycle
542 ---------------------------------------------------------------------
542 ---------------------------------------------------------------------
543
543
544 $ hg id -r 14 --hidden
544 $ hg id -r 14 --hidden
545 b650e6ee8614 (a)
545 b650e6ee8614 (a)
546 $ hg revert -ar 14 --hidden
546 $ hg revert -ar 14 --hidden
547 reverting a
547 reverting a
548 $ hg commit --amend
548 $ hg commit --amend
549 $ hg id
549 $ hg id
550 b99e5df575f7 (a) tip
550 b99e5df575f7 (a) tip
551
551
552 Test that rewriting leaving instability behind is allowed
552 Test that rewriting leaving instability behind is allowed
553 ---------------------------------------------------------------------
553 ---------------------------------------------------------------------
554
554
555 $ hg up '.^'
555 $ hg up '.^'
556 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
556 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
557 $ echo 'b' >> a
557 $ echo 'b' >> a
558 $ hg log --style compact -r 'children(.)'
558 $ hg log --style compact -r 'children(.)'
559 18[tip]:11 b99e5df575f7 1970-01-01 00:00 +0000 test
559 18[tip]:11 b99e5df575f7 1970-01-01 00:00 +0000 test
560 babar
560 babar
561
561
562 $ hg commit --amend
562 $ hg commit --amend
563 $ hg log -r 'unstable()'
563 $ hg log -r 'unstable()'
564 changeset: 18:b99e5df575f7
564 changeset: 18:b99e5df575f7
565 branch: a
565 branch: a
566 parent: 11:3334b7925910
566 parent: 11:3334b7925910
567 user: test
567 user: test
568 date: Thu Jan 01 00:00:00 1970 +0000
568 date: Thu Jan 01 00:00:00 1970 +0000
569 summary: babar
569 summary: babar
570
570
571
571
572 Amend a merge changeset (with renames and conflicts from the second parent):
572 Amend a merge changeset (with renames and conflicts from the second parent):
573
573
574 $ hg up -q default
574 $ hg up -q default
575 $ hg branch -q bar
575 $ hg branch -q bar
576 $ hg cp a aa
576 $ hg cp a aa
577 $ hg mv z zz
577 $ hg mv z zz
578 $ echo cc > cc
578 $ echo cc > cc
579 $ hg add cc
579 $ hg add cc
580 $ hg ci -m aazzcc
580 $ hg ci -m aazzcc
581 $ hg up -q default
581 $ hg up -q default
582 $ echo a >> a
582 $ echo a >> a
583 $ echo dd > cc
583 $ echo dd > cc
584 $ hg add cc
584 $ hg add cc
585 $ hg ci -m aa
585 $ hg ci -m aa
586 $ hg merge -q bar
586 $ hg merge -q bar
587 warning: conflicts during merge.
587 warning: conflicts during merge.
588 merging cc incomplete! (edit conflicts, then use 'hg resolve --mark')
588 merging cc incomplete! (edit conflicts, then use 'hg resolve --mark')
589 [1]
589 [1]
590 $ hg resolve -m cc
590 $ hg resolve -m cc
591 (no more unresolved files)
591 (no more unresolved files)
592 $ hg ci -m 'merge bar'
592 $ hg ci -m 'merge bar'
593 $ hg log --config diff.git=1 -pr .
593 $ hg log --config diff.git=1 -pr .
594 changeset: 23:93cd4445f720
594 changeset: 23:93cd4445f720
595 tag: tip
595 tag: tip
596 parent: 22:30d96aeaf27b
596 parent: 22:30d96aeaf27b
597 parent: 21:1aa437659d19
597 parent: 21:1aa437659d19
598 user: test
598 user: test
599 date: Thu Jan 01 00:00:00 1970 +0000
599 date: Thu Jan 01 00:00:00 1970 +0000
600 summary: merge bar
600 summary: merge bar
601
601
602 diff --git a/a b/aa
602 diff --git a/a b/aa
603 copy from a
603 copy from a
604 copy to aa
604 copy to aa
605 diff --git a/cc b/cc
605 diff --git a/cc b/cc
606 --- a/cc
606 --- a/cc
607 +++ b/cc
607 +++ b/cc
608 @@ -1,1 +1,5 @@
608 @@ -1,1 +1,5 @@
609 +<<<<<<< local: 30d96aeaf27b - test: aa
609 +<<<<<<< local: 30d96aeaf27b - test: aa
610 dd
610 dd
611 +=======
611 +=======
612 +cc
612 +cc
613 +>>>>>>> other: 1aa437659d19 bar - test: aazzcc
613 +>>>>>>> other: 1aa437659d19 bar - test: aazzcc
614 diff --git a/z b/zz
614 diff --git a/z b/zz
615 rename from z
615 rename from z
616 rename to zz
616 rename to zz
617
617
618 $ hg debugrename aa
618 $ hg debugrename aa
619 aa renamed from a:a80d06849b333b8a3d5c445f8ba3142010dcdc9e
619 aa renamed from a:a80d06849b333b8a3d5c445f8ba3142010dcdc9e
620 $ hg debugrename zz
620 $ hg debugrename zz
621 zz renamed from z:69a1b67522704ec122181c0890bd16e9d3e7516a
621 zz renamed from z:69a1b67522704ec122181c0890bd16e9d3e7516a
622 $ hg debugrename cc
622 $ hg debugrename cc
623 cc not renamed
623 cc not renamed
624 $ HGEDITOR="sh .hg/checkeditform.sh" hg ci --amend -m 'merge bar (amend message)' --edit
624 $ HGEDITOR="sh .hg/checkeditform.sh" hg ci --amend -m 'merge bar (amend message)' --edit
625 HGEDITFORM=commit.amend.merge
625 HGEDITFORM=commit.amend.merge
626 $ hg log --config diff.git=1 -pr .
626 $ hg log --config diff.git=1 -pr .
627 changeset: 24:832b50f2c271
627 changeset: 24:832b50f2c271
628 tag: tip
628 tag: tip
629 parent: 22:30d96aeaf27b
629 parent: 22:30d96aeaf27b
630 parent: 21:1aa437659d19
630 parent: 21:1aa437659d19
631 user: test
631 user: test
632 date: Thu Jan 01 00:00:00 1970 +0000
632 date: Thu Jan 01 00:00:00 1970 +0000
633 summary: merge bar (amend message)
633 summary: merge bar (amend message)
634
634
635 diff --git a/a b/aa
635 diff --git a/a b/aa
636 copy from a
636 copy from a
637 copy to aa
637 copy to aa
638 diff --git a/cc b/cc
638 diff --git a/cc b/cc
639 --- a/cc
639 --- a/cc
640 +++ b/cc
640 +++ b/cc
641 @@ -1,1 +1,5 @@
641 @@ -1,1 +1,5 @@
642 +<<<<<<< local: 30d96aeaf27b - test: aa
642 +<<<<<<< local: 30d96aeaf27b - test: aa
643 dd
643 dd
644 +=======
644 +=======
645 +cc
645 +cc
646 +>>>>>>> other: 1aa437659d19 bar - test: aazzcc
646 +>>>>>>> other: 1aa437659d19 bar - test: aazzcc
647 diff --git a/z b/zz
647 diff --git a/z b/zz
648 rename from z
648 rename from z
649 rename to zz
649 rename to zz
650
650
651 $ hg debugrename aa
651 $ hg debugrename aa
652 aa renamed from a:a80d06849b333b8a3d5c445f8ba3142010dcdc9e
652 aa renamed from a:a80d06849b333b8a3d5c445f8ba3142010dcdc9e
653 $ hg debugrename zz
653 $ hg debugrename zz
654 zz renamed from z:69a1b67522704ec122181c0890bd16e9d3e7516a
654 zz renamed from z:69a1b67522704ec122181c0890bd16e9d3e7516a
655 $ hg debugrename cc
655 $ hg debugrename cc
656 cc not renamed
656 cc not renamed
657 $ hg mv zz z
657 $ hg mv zz z
658 $ hg ci --amend -m 'merge bar (undo rename)'
658 $ hg ci --amend -m 'merge bar (undo rename)'
659 $ hg log --config diff.git=1 -pr .
659 $ hg log --config diff.git=1 -pr .
660 changeset: 26:bdafc5c72f74
660 changeset: 26:bdafc5c72f74
661 tag: tip
661 tag: tip
662 parent: 22:30d96aeaf27b
662 parent: 22:30d96aeaf27b
663 parent: 21:1aa437659d19
663 parent: 21:1aa437659d19
664 user: test
664 user: test
665 date: Thu Jan 01 00:00:00 1970 +0000
665 date: Thu Jan 01 00:00:00 1970 +0000
666 summary: merge bar (undo rename)
666 summary: merge bar (undo rename)
667
667
668 diff --git a/a b/aa
668 diff --git a/a b/aa
669 copy from a
669 copy from a
670 copy to aa
670 copy to aa
671 diff --git a/cc b/cc
671 diff --git a/cc b/cc
672 --- a/cc
672 --- a/cc
673 +++ b/cc
673 +++ b/cc
674 @@ -1,1 +1,5 @@
674 @@ -1,1 +1,5 @@
675 +<<<<<<< local: 30d96aeaf27b - test: aa
675 +<<<<<<< local: 30d96aeaf27b - test: aa
676 dd
676 dd
677 +=======
677 +=======
678 +cc
678 +cc
679 +>>>>>>> other: 1aa437659d19 bar - test: aazzcc
679 +>>>>>>> other: 1aa437659d19 bar - test: aazzcc
680
680
681 $ hg debugrename z
681 $ hg debugrename z
682 z not renamed
682 z not renamed
683
683
684 Amend a merge changeset (with renames during the merge):
684 Amend a merge changeset (with renames during the merge):
685
685
686 $ hg up -q bar
686 $ hg up -q bar
687 $ echo x > x
687 $ echo x > x
688 $ hg add x
688 $ hg add x
689 $ hg ci -m x
689 $ hg ci -m x
690 $ hg up -q default
690 $ hg up -q default
691 $ hg merge -q bar
691 $ hg merge -q bar
692 $ hg mv aa aaa
692 $ hg mv aa aaa
693 $ echo aa >> aaa
693 $ echo aa >> aaa
694 $ hg ci -m 'merge bar again'
694 $ hg ci -m 'merge bar again'
695 $ hg log --config diff.git=1 -pr .
695 $ hg log --config diff.git=1 -pr .
696 changeset: 28:32f19415b634
696 changeset: 28:32f19415b634
697 tag: tip
697 tag: tip
698 parent: 26:bdafc5c72f74
698 parent: 26:bdafc5c72f74
699 parent: 27:4c94d5bc65f5
699 parent: 27:4c94d5bc65f5
700 user: test
700 user: test
701 date: Thu Jan 01 00:00:00 1970 +0000
701 date: Thu Jan 01 00:00:00 1970 +0000
702 summary: merge bar again
702 summary: merge bar again
703
703
704 diff --git a/aa b/aa
704 diff --git a/aa b/aa
705 deleted file mode 100644
705 deleted file mode 100644
706 --- a/aa
706 --- a/aa
707 +++ /dev/null
707 +++ /dev/null
708 @@ -1,2 +0,0 @@
708 @@ -1,2 +0,0 @@
709 -a
709 -a
710 -a
710 -a
711 diff --git a/aaa b/aaa
711 diff --git a/aaa b/aaa
712 new file mode 100644
712 new file mode 100644
713 --- /dev/null
713 --- /dev/null
714 +++ b/aaa
714 +++ b/aaa
715 @@ -0,0 +1,3 @@
715 @@ -0,0 +1,3 @@
716 +a
716 +a
717 +a
717 +a
718 +aa
718 +aa
719 diff --git a/x b/x
719 diff --git a/x b/x
720 new file mode 100644
720 new file mode 100644
721 --- /dev/null
721 --- /dev/null
722 +++ b/x
722 +++ b/x
723 @@ -0,0 +1,1 @@
723 @@ -0,0 +1,1 @@
724 +x
724 +x
725
725
726 $ hg debugrename aaa
726 $ hg debugrename aaa
727 aaa renamed from aa:37d9b5d994eab34eda9c16b195ace52c7b129980
727 aaa renamed from aa:37d9b5d994eab34eda9c16b195ace52c7b129980
728 $ hg mv aaa aa
728 $ hg mv aaa aa
729 $ hg ci --amend -m 'merge bar again (undo rename)'
729 $ hg ci --amend -m 'merge bar again (undo rename)'
730 $ hg log --config diff.git=1 -pr .
730 $ hg log --config diff.git=1 -pr .
731 changeset: 30:1e2a06b3d312
731 changeset: 30:1e2a06b3d312
732 tag: tip
732 tag: tip
733 parent: 26:bdafc5c72f74
733 parent: 26:bdafc5c72f74
734 parent: 27:4c94d5bc65f5
734 parent: 27:4c94d5bc65f5
735 user: test
735 user: test
736 date: Thu Jan 01 00:00:00 1970 +0000
736 date: Thu Jan 01 00:00:00 1970 +0000
737 summary: merge bar again (undo rename)
737 summary: merge bar again (undo rename)
738
738
739 diff --git a/aa b/aa
739 diff --git a/aa b/aa
740 --- a/aa
740 --- a/aa
741 +++ b/aa
741 +++ b/aa
742 @@ -1,2 +1,3 @@
742 @@ -1,2 +1,3 @@
743 a
743 a
744 a
744 a
745 +aa
745 +aa
746 diff --git a/x b/x
746 diff --git a/x b/x
747 new file mode 100644
747 new file mode 100644
748 --- /dev/null
748 --- /dev/null
749 +++ b/x
749 +++ b/x
750 @@ -0,0 +1,1 @@
750 @@ -0,0 +1,1 @@
751 +x
751 +x
752
752
753 $ hg debugrename aa
753 $ hg debugrename aa
754 aa not renamed
754 aa not renamed
755 $ hg debugrename -r '.^' aa
755 $ hg debugrename -r '.^' aa
756 aa renamed from a:a80d06849b333b8a3d5c445f8ba3142010dcdc9e
756 aa renamed from a:a80d06849b333b8a3d5c445f8ba3142010dcdc9e
757
757
758 Amend a merge changeset (with manifest-level conflicts):
758 Amend a merge changeset (with manifest-level conflicts):
759
759
760 $ hg up -q bar
760 $ hg up -q bar
761 $ hg rm aa
761 $ hg rm aa
762 $ hg ci -m 'rm aa'
762 $ hg ci -m 'rm aa'
763 $ hg up -q default
763 $ hg up -q default
764 $ echo aa >> aa
764 $ echo aa >> aa
765 $ hg ci -m aa
765 $ hg ci -m aa
766 $ hg merge -q bar
766 $ hg merge -q bar
767 local changed aa which remote deleted
767 local changed aa which remote deleted
768 use (c)hanged version or (d)elete? c
768 use (c)hanged version or (d)elete? c
769 $ hg ci -m 'merge bar (with conflicts)'
769 $ hg ci -m 'merge bar (with conflicts)'
770 $ hg log --config diff.git=1 -pr .
770 $ hg log --config diff.git=1 -pr .
771 changeset: 33:97a298b0c59f
771 changeset: 33:97a298b0c59f
772 tag: tip
772 tag: tip
773 parent: 32:3d78ce4226b8
773 parent: 32:3d78ce4226b8
774 parent: 31:67db8847a540
774 parent: 31:67db8847a540
775 user: test
775 user: test
776 date: Thu Jan 01 00:00:00 1970 +0000
776 date: Thu Jan 01 00:00:00 1970 +0000
777 summary: merge bar (with conflicts)
777 summary: merge bar (with conflicts)
778
778
779
779
780 $ hg rm aa
780 $ hg rm aa
781 $ hg ci --amend -m 'merge bar (with conflicts, amended)'
781 $ hg ci --amend -m 'merge bar (with conflicts, amended)'
782 $ hg log --config diff.git=1 -pr .
782 $ hg log --config diff.git=1 -pr .
783 changeset: 35:6de0c1bde1c8
783 changeset: 35:6de0c1bde1c8
784 tag: tip
784 tag: tip
785 parent: 32:3d78ce4226b8
785 parent: 32:3d78ce4226b8
786 parent: 31:67db8847a540
786 parent: 31:67db8847a540
787 user: test
787 user: test
788 date: Thu Jan 01 00:00:00 1970 +0000
788 date: Thu Jan 01 00:00:00 1970 +0000
789 summary: merge bar (with conflicts, amended)
789 summary: merge bar (with conflicts, amended)
790
790
791 diff --git a/aa b/aa
791 diff --git a/aa b/aa
792 deleted file mode 100644
792 deleted file mode 100644
793 --- a/aa
793 --- a/aa
794 +++ /dev/null
794 +++ /dev/null
795 @@ -1,4 +0,0 @@
795 @@ -1,4 +0,0 @@
796 -a
796 -a
797 -a
797 -a
798 -aa
798 -aa
799 -aa
799 -aa
800
800
801 Issue 3445: amending with --close-branch a commit that created a new head should fail
801 Issue 3445: amending with --close-branch a commit that created a new head should fail
802 This shouldn't be possible:
802 This shouldn't be possible:
803
803
804 $ hg up -q default
804 $ hg up -q default
805 $ hg branch closewithamend
805 $ hg branch closewithamend
806 marked working directory as branch closewithamend
806 marked working directory as branch closewithamend
807 (branches are permanent and global, did you want a bookmark?)
807 (branches are permanent and global, did you want a bookmark?)
808 $ touch foo
808 $ touch foo
809 $ hg add foo
809 $ hg add foo
810 $ hg ci -m..
810 $ hg ci -m..
811 $ hg ci --amend --close-branch -m 'closing'
811 $ hg ci --amend --close-branch -m 'closing'
812 abort: can only close branch heads
812 abort: can only close branch heads
813 [255]
813 [255]
814
814
815 This silliness fails:
815 This silliness fails:
816
816
817 $ hg branch silliness
817 $ hg branch silliness
818 marked working directory as branch silliness
818 marked working directory as branch silliness
819 (branches are permanent and global, did you want a bookmark?)
819 (branches are permanent and global, did you want a bookmark?)
820 $ echo b >> b
820 $ echo b >> b
821 $ hg ci --close-branch -m'open and close'
821 $ hg ci --close-branch -m'open and close'
822 abort: can only close branch heads
822 abort: can only close branch heads
823 [255]
823 [255]
824
824
825 Test that amend with --secret creates new secret changeset forcibly
825 Test that amend with --secret creates new secret changeset forcibly
826 ---------------------------------------------------------------------
826 ---------------------------------------------------------------------
827
827
828 $ hg phase '.^::.'
828 $ hg phase '.^::.'
829 35: draft
829 35: draft
830 36: draft
830 36: draft
831 $ hg commit --amend --secret -m 'amend as secret' -q
831 $ hg commit --amend --secret -m 'amend as secret' -q
832 $ hg phase '.^::.'
832 $ hg phase '.^::.'
833 35: draft
833 35: draft
834 38: secret
834 38: secret
835
835
836 Test that amend with --edit invokes editor forcibly
836 Test that amend with --edit invokes editor forcibly
837 ---------------------------------------------------
837 ---------------------------------------------------
838
838
839 $ hg parents --template "{desc}\n"
839 $ hg parents --template "{desc}\n"
840 amend as secret
840 amend as secret
841 $ HGEDITOR=cat hg commit --amend -m "editor should be suppressed"
841 $ HGEDITOR=cat hg commit --amend -m "editor should be suppressed"
842 $ hg parents --template "{desc}\n"
842 $ hg parents --template "{desc}\n"
843 editor should be suppressed
843 editor should be suppressed
844
844
845 $ hg status --rev '.^1::.'
846 A foo
845 $ HGEDITOR=cat hg commit --amend -m "editor should be invoked" --edit
847 $ HGEDITOR=cat hg commit --amend -m "editor should be invoked" --edit
846 editor should be invoked
848 editor should be invoked
847
849
848
850
849 HG: Enter commit message. Lines beginning with 'HG:' are removed.
851 HG: Enter commit message. Lines beginning with 'HG:' are removed.
850 HG: Leave message empty to abort commit.
852 HG: Leave message empty to abort commit.
851 HG: --
853 HG: --
852 HG: user: test
854 HG: user: test
853 HG: branch 'silliness'
855 HG: branch 'silliness'
854 HG: changed foo
856 HG: added foo
855 $ hg parents --template "{desc}\n"
857 $ hg parents --template "{desc}\n"
856 editor should be invoked
858 editor should be invoked
857
859
858 Check for issue4405
860 Check for issue4405
859 -------------------
861 -------------------
860
862
861 Setup the repo with a file that gets moved in a second commit.
863 Setup the repo with a file that gets moved in a second commit.
862 $ hg init repo
864 $ hg init repo
863 $ cd repo
865 $ cd repo
864 $ touch a0
866 $ touch a0
865 $ hg add a0
867 $ hg add a0
866 $ hg commit -m a0
868 $ hg commit -m a0
867 $ hg mv a0 a1
869 $ hg mv a0 a1
868 $ hg commit -m a1
870 $ hg commit -m a1
869 $ hg up -q 0
871 $ hg up -q 0
870 $ hg log -G --template '{rev} {desc}'
872 $ hg log -G --template '{rev} {desc}'
871 o 1 a1
873 o 1 a1
872 |
874 |
873 @ 0 a0
875 @ 0 a0
874
876
875
877
876 Now we branch the repro, but re-use the file contents, so we have a divergence
878 Now we branch the repro, but re-use the file contents, so we have a divergence
877 in the file revlog topology and the changelog topology.
879 in the file revlog topology and the changelog topology.
878 $ hg revert --rev 1 --all
880 $ hg revert --rev 1 --all
879 removing a0
881 removing a0
880 adding a1
882 adding a1
881 $ hg ci -qm 'a1-amend'
883 $ hg ci -qm 'a1-amend'
882 $ hg log -G --template '{rev} {desc}'
884 $ hg log -G --template '{rev} {desc}'
883 @ 2 a1-amend
885 @ 2 a1-amend
884 |
886 |
885 | o 1 a1
887 | o 1 a1
886 |/
888 |/
887 o 0 a0
889 o 0 a0
888
890
889
891
890 The way mercurial does amends is to create a temporary commit (rev 3) and then
892 The way mercurial does amends is to create a temporary commit (rev 3) and then
891 fold the new and old commits together into another commit (rev 4). During this
893 fold the new and old commits together into another commit (rev 4). During this
892 process, _findlimit is called to check how far back to look for the transitive
894 process, _findlimit is called to check how far back to look for the transitive
893 closure of file copy information, but due to the divergence of the filelog
895 closure of file copy information, but due to the divergence of the filelog
894 and changelog graph topologies, before _findlimit was fixed, it returned a rev
896 and changelog graph topologies, before _findlimit was fixed, it returned a rev
895 which was not far enough back in this case.
897 which was not far enough back in this case.
896 $ hg mv a1 a2
898 $ hg mv a1 a2
897 $ hg status --copies --rev 0
899 $ hg status --copies --rev 0
898 A a2
900 A a2
899 a0
901 a0
900 R a0
902 R a0
901 $ hg ci --amend -q
903 $ hg ci --amend -q
902 $ hg log -G --template '{rev} {desc}'
904 $ hg log -G --template '{rev} {desc}'
903 @ 4 a1-amend
905 @ 4 a1-amend
904 |
906 |
905 | o 1 a1
907 | o 1 a1
906 |/
908 |/
907 o 0 a0
909 o 0 a0
908
910
909
911
910 Before the fix, the copy information was lost.
912 Before the fix, the copy information was lost.
911 $ hg status --copies --rev 0
913 $ hg status --copies --rev 0
912 A a2
914 A a2
913 a0
915 a0
914 R a0
916 R a0
@@ -1,133 +1,129
1 #require svn svn-bindings
1 #require svn svn-bindings
2
2
3 $ cat >> $HGRCPATH <<EOF
3 $ cat >> $HGRCPATH <<EOF
4 > [extensions]
4 > [extensions]
5 > convert =
5 > convert =
6 > EOF
6 > EOF
7
7
8 $ svnadmin create svn-repo
8 $ svnadmin create svn-repo
9 $ svnadmin load -q svn-repo < "$TESTDIR/svn/encoding.svndump"
9 $ svnadmin load -q svn-repo < "$TESTDIR/svn/encoding.svndump"
10
10
11 Convert while testing all possible outputs
11 Convert while testing all possible outputs
12
12
13 $ hg --debug convert svn-repo A-hg
13 $ hg --debug convert svn-repo A-hg
14 initializing destination A-hg repository
14 initializing destination A-hg repository
15 reparent to file://*/svn-repo (glob)
15 reparent to file://*/svn-repo (glob)
16 run hg sink pre-conversion action
16 run hg sink pre-conversion action
17 scanning source...
17 scanning source...
18 found trunk at 'trunk'
18 found trunk at 'trunk'
19 found tags at 'tags'
19 found tags at 'tags'
20 found branches at 'branches'
20 found branches at 'branches'
21 found branch branch\xc3\xa9 at 5 (esc)
21 found branch branch\xc3\xa9 at 5 (esc)
22 found branch branch\xc3\xa9e at 6 (esc)
22 found branch branch\xc3\xa9e at 6 (esc)
23 scanning: 1/4 revisions (25.00%)
23 scanning: 1/4 revisions (25.00%)
24 reparent to file://*/svn-repo/trunk (glob)
24 reparent to file://*/svn-repo/trunk (glob)
25 fetching revision log for "/trunk" from 4 to 0
25 fetching revision log for "/trunk" from 4 to 0
26 parsing revision 4 (2 changes)
26 parsing revision 4 (2 changes)
27 parsing revision 3 (4 changes)
27 parsing revision 3 (4 changes)
28 parsing revision 2 (3 changes)
28 parsing revision 2 (3 changes)
29 parsing revision 1 (3 changes)
29 parsing revision 1 (3 changes)
30 no copyfrom path, don't know what to do.
30 no copyfrom path, don't know what to do.
31 '/branches' is not under '/trunk', ignoring
31 '/branches' is not under '/trunk', ignoring
32 '/tags' is not under '/trunk', ignoring
32 '/tags' is not under '/trunk', ignoring
33 scanning: 2/4 revisions (50.00%)
33 scanning: 2/4 revisions (50.00%)
34 reparent to file://*/svn-repo/branches/branch%C3%A9 (glob)
34 reparent to file://*/svn-repo/branches/branch%C3%A9 (glob)
35 fetching revision log for "/branches/branch\xc3\xa9" from 5 to 0 (esc)
35 fetching revision log for "/branches/branch\xc3\xa9" from 5 to 0 (esc)
36 parsing revision 5 (1 changes)
36 parsing revision 5 (1 changes)
37 reparent to file://*/svn-repo (glob)
37 reparent to file://*/svn-repo (glob)
38 reparent to file://*/svn-repo/branches/branch%C3%A9 (glob)
38 reparent to file://*/svn-repo/branches/branch%C3%A9 (glob)
39 found parent of branch /branches/branch\xc3\xa9 at 4: /trunk (esc)
39 found parent of branch /branches/branch\xc3\xa9 at 4: /trunk (esc)
40 scanning: 3/4 revisions (75.00%)
40 scanning: 3/4 revisions (75.00%)
41 reparent to file://*/svn-repo/branches/branch%C3%A9e (glob)
41 reparent to file://*/svn-repo/branches/branch%C3%A9e (glob)
42 fetching revision log for "/branches/branch\xc3\xa9e" from 6 to 0 (esc)
42 fetching revision log for "/branches/branch\xc3\xa9e" from 6 to 0 (esc)
43 parsing revision 6 (1 changes)
43 parsing revision 6 (1 changes)
44 reparent to file://*/svn-repo (glob)
44 reparent to file://*/svn-repo (glob)
45 reparent to file://*/svn-repo/branches/branch%C3%A9e (glob)
45 reparent to file://*/svn-repo/branches/branch%C3%A9e (glob)
46 found parent of branch /branches/branch\xc3\xa9e at 5: /branches/branch\xc3\xa9 (esc)
46 found parent of branch /branches/branch\xc3\xa9e at 5: /branches/branch\xc3\xa9 (esc)
47 scanning: 4/4 revisions (100.00%)
47 scanning: 4/4 revisions (100.00%)
48 scanning: 5/4 revisions (125.00%)
48 scanning: 5/4 revisions (125.00%)
49 scanning: 6/4 revisions (150.00%)
49 scanning: 6/4 revisions (150.00%)
50 sorting...
50 sorting...
51 converting...
51 converting...
52 5 init projA
52 5 init projA
53 source: svn:afeb9c47-92ff-4c0c-9f72-e1f6eb8ac9af/trunk@1
53 source: svn:afeb9c47-92ff-4c0c-9f72-e1f6eb8ac9af/trunk@1
54 converting: 0/6 revisions (0.00%)
54 converting: 0/6 revisions (0.00%)
55 4 hello
55 4 hello
56 source: svn:afeb9c47-92ff-4c0c-9f72-e1f6eb8ac9af/trunk@2
56 source: svn:afeb9c47-92ff-4c0c-9f72-e1f6eb8ac9af/trunk@2
57 converting: 1/6 revisions (16.67%)
57 converting: 1/6 revisions (16.67%)
58 reparent to file://*/svn-repo/trunk (glob)
58 reparent to file://*/svn-repo/trunk (glob)
59 scanning paths: /trunk/\xc3\xa0 0/3 (0.00%) (esc)
59 scanning paths: /trunk/\xc3\xa0 0/3 (0.00%) (esc)
60 scanning paths: /trunk/\xc3\xa0/e\xcc\x81 1/3 (33.33%) (esc)
60 scanning paths: /trunk/\xc3\xa0/e\xcc\x81 1/3 (33.33%) (esc)
61 scanning paths: /trunk/\xc3\xa9 2/3 (66.67%) (esc)
61 scanning paths: /trunk/\xc3\xa9 2/3 (66.67%) (esc)
62 \xc3\xa0/e\xcc\x81 (esc)
62 \xc3\xa0/e\xcc\x81 (esc)
63 getting files: \xc3\xa0/e\xcc\x81 1/2 (50.00%) (esc)
63 getting files: \xc3\xa0/e\xcc\x81 1/2 (50.00%) (esc)
64 \xc3\xa9 (esc)
64 \xc3\xa9 (esc)
65 getting files: \xc3\xa9 2/2 (100.00%) (esc)
65 getting files: \xc3\xa9 2/2 (100.00%) (esc)
66 3 copy files
66 3 copy files
67 source: svn:afeb9c47-92ff-4c0c-9f72-e1f6eb8ac9af/trunk@3
67 source: svn:afeb9c47-92ff-4c0c-9f72-e1f6eb8ac9af/trunk@3
68 converting: 2/6 revisions (33.33%)
68 converting: 2/6 revisions (33.33%)
69 scanning paths: /trunk/\xc3\xa0 0/4 (0.00%) (esc)
69 scanning paths: /trunk/\xc3\xa0 0/4 (0.00%) (esc)
70 gone from -1
70 gone from -1
71 reparent to file://*/svn-repo (glob)
71 reparent to file://*/svn-repo (glob)
72 reparent to file://*/svn-repo/trunk (glob)
72 reparent to file://*/svn-repo/trunk (glob)
73 scanning paths: /trunk/\xc3\xa8 1/4 (25.00%) (esc)
73 scanning paths: /trunk/\xc3\xa8 1/4 (25.00%) (esc)
74 copied to \xc3\xa8 from \xc3\xa9@2 (esc)
74 copied to \xc3\xa8 from \xc3\xa9@2 (esc)
75 scanning paths: /trunk/\xc3\xa9 2/4 (50.00%) (esc)
75 scanning paths: /trunk/\xc3\xa9 2/4 (50.00%) (esc)
76 gone from -1
76 gone from -1
77 reparent to file://*/svn-repo (glob)
77 reparent to file://*/svn-repo (glob)
78 reparent to file://*/svn-repo/trunk (glob)
78 reparent to file://*/svn-repo/trunk (glob)
79 scanning paths: /trunk/\xc3\xb9 3/4 (75.00%) (esc)
79 scanning paths: /trunk/\xc3\xb9 3/4 (75.00%) (esc)
80 mark /trunk/\xc3\xb9 came from \xc3\xa0:2 (esc)
80 mark /trunk/\xc3\xb9 came from \xc3\xa0:2 (esc)
81 \xc3\xa0/e\xcc\x81 (esc)
82 getting files: \xc3\xa0/e\xcc\x81 1/4 (25.00%) (esc)
81 getting files: \xc3\xa0/e\xcc\x81 1/4 (25.00%) (esc)
82 getting files: \xc3\xa9 2/4 (50.00%) (esc)
83 \xc3\xa8 (esc)
83 \xc3\xa8 (esc)
84 getting files: \xc3\xa8 2/4 (50.00%) (esc)
84 getting files: \xc3\xa8 3/4 (75.00%) (esc)
85 \xc3\xa8: copy \xc3\xa9:6b67ccefd5ce6de77e7ead4f5292843a0255329f (esc)
85 \xc3\xa8: copy \xc3\xa9:6b67ccefd5ce6de77e7ead4f5292843a0255329f (esc)
86 \xc3\xa9 (esc)
87 getting files: \xc3\xa9 3/4 (75.00%) (esc)
88 \xc3\xb9/e\xcc\x81 (esc)
86 \xc3\xb9/e\xcc\x81 (esc)
89 getting files: \xc3\xb9/e\xcc\x81 4/4 (100.00%) (esc)
87 getting files: \xc3\xb9/e\xcc\x81 4/4 (100.00%) (esc)
90 \xc3\xb9/e\xcc\x81: copy \xc3\xa0/e\xcc\x81:a9092a3d84a37b9993b5c73576f6de29b7ea50f6 (esc)
88 \xc3\xb9/e\xcc\x81: copy \xc3\xa0/e\xcc\x81:a9092a3d84a37b9993b5c73576f6de29b7ea50f6 (esc)
91 2 remove files
89 2 remove files
92 source: svn:afeb9c47-92ff-4c0c-9f72-e1f6eb8ac9af/trunk@4
90 source: svn:afeb9c47-92ff-4c0c-9f72-e1f6eb8ac9af/trunk@4
93 converting: 3/6 revisions (50.00%)
91 converting: 3/6 revisions (50.00%)
94 scanning paths: /trunk/\xc3\xa8 0/2 (0.00%) (esc)
92 scanning paths: /trunk/\xc3\xa8 0/2 (0.00%) (esc)
95 gone from -1
93 gone from -1
96 reparent to file://*/svn-repo (glob)
94 reparent to file://*/svn-repo (glob)
97 reparent to file://*/svn-repo/trunk (glob)
95 reparent to file://*/svn-repo/trunk (glob)
98 scanning paths: /trunk/\xc3\xb9 1/2 (50.00%) (esc)
96 scanning paths: /trunk/\xc3\xb9 1/2 (50.00%) (esc)
99 gone from -1
97 gone from -1
100 reparent to file://*/svn-repo (glob)
98 reparent to file://*/svn-repo (glob)
101 reparent to file://*/svn-repo/trunk (glob)
99 reparent to file://*/svn-repo/trunk (glob)
102 \xc3\xa8 (esc)
103 getting files: \xc3\xa8 1/2 (50.00%) (esc)
100 getting files: \xc3\xa8 1/2 (50.00%) (esc)
104 \xc3\xb9/e\xcc\x81 (esc)
105 getting files: \xc3\xb9/e\xcc\x81 2/2 (100.00%) (esc)
101 getting files: \xc3\xb9/e\xcc\x81 2/2 (100.00%) (esc)
106 1 branch to branch?
102 1 branch to branch?
107 source: svn:afeb9c47-92ff-4c0c-9f72-e1f6eb8ac9af/branches/branch?@5
103 source: svn:afeb9c47-92ff-4c0c-9f72-e1f6eb8ac9af/branches/branch?@5
108 converting: 4/6 revisions (66.67%)
104 converting: 4/6 revisions (66.67%)
109 reparent to file://*/svn-repo/branches/branch%C3%A9 (glob)
105 reparent to file://*/svn-repo/branches/branch%C3%A9 (glob)
110 scanning paths: /branches/branch\xc3\xa9 0/1 (0.00%) (esc)
106 scanning paths: /branches/branch\xc3\xa9 0/1 (0.00%) (esc)
111 0 branch to branch?e
107 0 branch to branch?e
112 source: svn:afeb9c47-92ff-4c0c-9f72-e1f6eb8ac9af/branches/branch?e@6
108 source: svn:afeb9c47-92ff-4c0c-9f72-e1f6eb8ac9af/branches/branch?e@6
113 converting: 5/6 revisions (83.33%)
109 converting: 5/6 revisions (83.33%)
114 reparent to file://*/svn-repo/branches/branch%C3%A9e (glob)
110 reparent to file://*/svn-repo/branches/branch%C3%A9e (glob)
115 scanning paths: /branches/branch\xc3\xa9e 0/1 (0.00%) (esc)
111 scanning paths: /branches/branch\xc3\xa9e 0/1 (0.00%) (esc)
116 reparent to file://*/svn-repo (glob)
112 reparent to file://*/svn-repo (glob)
117 reparent to file://*/svn-repo/branches/branch%C3%A9e (glob)
113 reparent to file://*/svn-repo/branches/branch%C3%A9e (glob)
118 reparent to file://*/svn-repo (glob)
114 reparent to file://*/svn-repo (glob)
119 reparent to file://*/svn-repo/branches/branch%C3%A9e (glob)
115 reparent to file://*/svn-repo/branches/branch%C3%A9e (glob)
120 updating tags
116 updating tags
121 .hgtags
117 .hgtags
122 run hg sink post-conversion action
118 run hg sink post-conversion action
123 $ cd A-hg
119 $ cd A-hg
124 $ hg up
120 $ hg up
125 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
121 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
126
122
127 Check tags are in UTF-8
123 Check tags are in UTF-8
128
124
129 $ cat .hgtags
125 $ cat .hgtags
130 e94e4422020e715add80525e8f0f46c9968689f1 branch\xc3\xa9e (esc)
126 e94e4422020e715add80525e8f0f46c9968689f1 branch\xc3\xa9e (esc)
131 f7e66f98380ed1e53a797c5c7a7a2616a7ab377d branch\xc3\xa9 (esc)
127 f7e66f98380ed1e53a797c5c7a7a2616a7ab377d branch\xc3\xa9 (esc)
132
128
133 $ cd ..
129 $ cd ..
@@ -1,497 +1,503
1 Test histedit extension: Fold commands
1 Test histedit extension: Fold commands
2 ======================================
2 ======================================
3
3
4 This test file is dedicated to testing the fold command in non conflicting
4 This test file is dedicated to testing the fold command in non conflicting
5 case.
5 case.
6
6
7 Initialization
7 Initialization
8 ---------------
8 ---------------
9
9
10
10
11 $ . "$TESTDIR/histedit-helpers.sh"
11 $ . "$TESTDIR/histedit-helpers.sh"
12
12
13 $ cat >> $HGRCPATH <<EOF
13 $ cat >> $HGRCPATH <<EOF
14 > [alias]
14 > [alias]
15 > logt = log --template '{rev}:{node|short} {desc|firstline}\n'
15 > logt = log --template '{rev}:{node|short} {desc|firstline}\n'
16 > [extensions]
16 > [extensions]
17 > histedit=
17 > histedit=
18 > EOF
18 > EOF
19
19
20
20
21 Simple folding
21 Simple folding
22 --------------------
22 --------------------
23 $ initrepo ()
23 $ initrepo ()
24 > {
24 > {
25 > hg init r
25 > hg init r
26 > cd r
26 > cd r
27 > for x in a b c d e f ; do
27 > for x in a b c d e f ; do
28 > echo $x > $x
28 > echo $x > $x
29 > hg add $x
29 > hg add $x
30 > hg ci -m $x
30 > hg ci -m $x
31 > done
31 > done
32 > }
32 > }
33
33
34 $ initrepo
34 $ initrepo
35
35
36 log before edit
36 log before edit
37 $ hg logt --graph
37 $ hg logt --graph
38 @ 5:652413bf663e f
38 @ 5:652413bf663e f
39 |
39 |
40 o 4:e860deea161a e
40 o 4:e860deea161a e
41 |
41 |
42 o 3:055a42cdd887 d
42 o 3:055a42cdd887 d
43 |
43 |
44 o 2:177f92b77385 c
44 o 2:177f92b77385 c
45 |
45 |
46 o 1:d2ae7f538514 b
46 o 1:d2ae7f538514 b
47 |
47 |
48 o 0:cb9a9f314b8b a
48 o 0:cb9a9f314b8b a
49
49
50
50
51 $ hg histedit 177f92b77385 --commands - 2>&1 <<EOF | fixbundle
51 $ hg histedit 177f92b77385 --commands - 2>&1 <<EOF | fixbundle
52 > pick e860deea161a e
52 > pick e860deea161a e
53 > pick 652413bf663e f
53 > pick 652413bf663e f
54 > fold 177f92b77385 c
54 > fold 177f92b77385 c
55 > pick 055a42cdd887 d
55 > pick 055a42cdd887 d
56 > EOF
56 > EOF
57 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
57 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
58 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
58 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
59 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
59 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
60 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
60 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
61 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
61 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
62 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
62 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
63 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
63 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
64
64
65 log after edit
65 log after edit
66 $ hg logt --graph
66 $ hg logt --graph
67 @ 4:9c277da72c9b d
67 @ 4:9c277da72c9b d
68 |
68 |
69 o 3:6de59d13424a f
69 o 3:6de59d13424a f
70 |
70 |
71 o 2:ee283cb5f2d5 e
71 o 2:ee283cb5f2d5 e
72 |
72 |
73 o 1:d2ae7f538514 b
73 o 1:d2ae7f538514 b
74 |
74 |
75 o 0:cb9a9f314b8b a
75 o 0:cb9a9f314b8b a
76
76
77
77
78 post-fold manifest
78 post-fold manifest
79 $ hg manifest
79 $ hg manifest
80 a
80 a
81 b
81 b
82 c
82 c
83 d
83 d
84 e
84 e
85 f
85 f
86
86
87
87
88 check histedit_source
88 check histedit_source
89
89
90 $ hg log --debug --rev 3
90 $ hg log --debug --rev 3
91 changeset: 3:6de59d13424a8a13acd3e975514aed29dd0d9b2d
91 changeset: 3:6de59d13424a8a13acd3e975514aed29dd0d9b2d
92 phase: draft
92 phase: draft
93 parent: 2:ee283cb5f2d5955443f23a27b697a04339e9a39a
93 parent: 2:ee283cb5f2d5955443f23a27b697a04339e9a39a
94 parent: -1:0000000000000000000000000000000000000000
94 parent: -1:0000000000000000000000000000000000000000
95 manifest: 3:81eede616954057198ead0b2c73b41d1f392829a
95 manifest: 3:81eede616954057198ead0b2c73b41d1f392829a
96 user: test
96 user: test
97 date: Thu Jan 01 00:00:00 1970 +0000
97 date: Thu Jan 01 00:00:00 1970 +0000
98 files+: c f
98 files+: c f
99 extra: branch=default
99 extra: branch=default
100 extra: histedit_source=a4f7421b80f79fcc59fff01bcbf4a53d127dd6d3,177f92b773850b59254aa5e923436f921b55483b
100 extra: histedit_source=a4f7421b80f79fcc59fff01bcbf4a53d127dd6d3,177f92b773850b59254aa5e923436f921b55483b
101 description:
101 description:
102 f
102 f
103 ***
103 ***
104 c
104 c
105
105
106
106
107
107
108 rollup will fold without preserving the folded commit's message
108 rollup will fold without preserving the folded commit's message
109
109
110 $ hg histedit d2ae7f538514 --commands - 2>&1 <<EOF | fixbundle
110 $ hg histedit d2ae7f538514 --commands - 2>&1 <<EOF | fixbundle
111 > pick d2ae7f538514 b
111 > pick d2ae7f538514 b
112 > roll ee283cb5f2d5 e
112 > roll ee283cb5f2d5 e
113 > pick 6de59d13424a f
113 > pick 6de59d13424a f
114 > pick 9c277da72c9b d
114 > pick 9c277da72c9b d
115 > EOF
115 > EOF
116 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
116 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
117 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
117 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
118 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
118 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
119 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
119 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
120 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
120 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
121 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
121 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
122
122
123 log after edit
123 log after edit
124 $ hg logt --graph
124 $ hg logt --graph
125 @ 3:c4a9eb7989fc d
125 @ 3:c4a9eb7989fc d
126 |
126 |
127 o 2:8e03a72b6f83 f
127 o 2:8e03a72b6f83 f
128 |
128 |
129 o 1:391ee782c689 b
129 o 1:391ee782c689 b
130 |
130 |
131 o 0:cb9a9f314b8b a
131 o 0:cb9a9f314b8b a
132
132
133
133
134 description is taken from rollup target commit
134 description is taken from rollup target commit
135
135
136 $ hg log --debug --rev 1
136 $ hg log --debug --rev 1
137 changeset: 1:391ee782c68930be438ccf4c6a403daedbfbffa5
137 changeset: 1:391ee782c68930be438ccf4c6a403daedbfbffa5
138 phase: draft
138 phase: draft
139 parent: 0:cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
139 parent: 0:cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
140 parent: -1:0000000000000000000000000000000000000000
140 parent: -1:0000000000000000000000000000000000000000
141 manifest: 1:b5e112a3a8354e269b1524729f0918662d847c38
141 manifest: 1:b5e112a3a8354e269b1524729f0918662d847c38
142 user: test
142 user: test
143 date: Thu Jan 01 00:00:00 1970 +0000
143 date: Thu Jan 01 00:00:00 1970 +0000
144 files+: b e
144 files+: b e
145 extra: branch=default
145 extra: branch=default
146 extra: histedit_source=d2ae7f538514cd87c17547b0de4cea71fe1af9fb,ee283cb5f2d5955443f23a27b697a04339e9a39a
146 extra: histedit_source=d2ae7f538514cd87c17547b0de4cea71fe1af9fb,ee283cb5f2d5955443f23a27b697a04339e9a39a
147 description:
147 description:
148 b
148 b
149
149
150
150
151
151
152 check saving last-message.txt
152 check saving last-message.txt
153
153
154 $ cat > $TESTTMP/abortfolding.py <<EOF
154 $ cat > $TESTTMP/abortfolding.py <<EOF
155 > from mercurial import util
155 > from mercurial import util
156 > def abortfolding(ui, repo, hooktype, **kwargs):
156 > def abortfolding(ui, repo, hooktype, **kwargs):
157 > ctx = repo[kwargs.get('node')]
157 > ctx = repo[kwargs.get('node')]
158 > if set(ctx.files()) == set(['c', 'd', 'f']):
158 > if set(ctx.files()) == set(['c', 'd', 'f']):
159 > return True # abort folding commit only
159 > return True # abort folding commit only
160 > ui.warn('allow non-folding commit\\n')
160 > ui.warn('allow non-folding commit\\n')
161 > EOF
161 > EOF
162 $ cat > .hg/hgrc <<EOF
162 $ cat > .hg/hgrc <<EOF
163 > [hooks]
163 > [hooks]
164 > pretxncommit.abortfolding = python:$TESTTMP/abortfolding.py:abortfolding
164 > pretxncommit.abortfolding = python:$TESTTMP/abortfolding.py:abortfolding
165 > EOF
165 > EOF
166
166
167 $ cat > $TESTTMP/editor.sh << EOF
167 $ cat > $TESTTMP/editor.sh << EOF
168 > echo "==== before editing"
168 > echo "==== before editing"
169 > cat \$1
169 > cat \$1
170 > echo "===="
170 > echo "===="
171 > echo "check saving last-message.txt" >> \$1
171 > echo "check saving last-message.txt" >> \$1
172 > EOF
172 > EOF
173
173
174 $ rm -f .hg/last-message.txt
174 $ rm -f .hg/last-message.txt
175 $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit 8e03a72b6f83 --commands - 2>&1 <<EOF | fixbundle
175 $ hg status --rev '8e03a72b6f83^1::c4a9eb7989fc'
176 A c
177 A d
178 A f
179 $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit 8e03a72b6f83 --commands - 2>&1 <<EOF
176 > pick 8e03a72b6f83 f
180 > pick 8e03a72b6f83 f
177 > fold c4a9eb7989fc d
181 > fold c4a9eb7989fc d
178 > EOF
182 > EOF
179 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
183 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
184 adding d
180 allow non-folding commit
185 allow non-folding commit
181 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
186 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
182 ==== before editing
187 ==== before editing
183 f
188 f
184 ***
189 ***
185 c
190 c
186 ***
191 ***
187 d
192 d
188
193
189
194
190
195
191 HG: Enter commit message. Lines beginning with 'HG:' are removed.
196 HG: Enter commit message. Lines beginning with 'HG:' are removed.
192 HG: Leave message empty to abort commit.
197 HG: Leave message empty to abort commit.
193 HG: --
198 HG: --
194 HG: user: test
199 HG: user: test
195 HG: branch 'default'
200 HG: branch 'default'
196 HG: changed c
201 HG: added c
197 HG: changed d
202 HG: added d
198 HG: changed f
203 HG: added f
199 ====
204 ====
200 transaction abort!
205 transaction abort!
201 rollback completed
206 rollback completed
202 abort: pretxncommit.abortfolding hook failed
207 abort: pretxncommit.abortfolding hook failed
208 [255]
203
209
204 $ cat .hg/last-message.txt
210 $ cat .hg/last-message.txt
205 f
211 f
206 ***
212 ***
207 c
213 c
208 ***
214 ***
209 d
215 d
210
216
211
217
212
218
213 check saving last-message.txt
219 check saving last-message.txt
214
220
215 $ cd ..
221 $ cd ..
216 $ rm -r r
222 $ rm -r r
217
223
218 folding preserves initial author
224 folding preserves initial author
219 --------------------------------
225 --------------------------------
220
226
221 $ initrepo
227 $ initrepo
222
228
223 $ hg ci --user "someone else" --amend --quiet
229 $ hg ci --user "someone else" --amend --quiet
224
230
225 tip before edit
231 tip before edit
226 $ hg log --rev .
232 $ hg log --rev .
227 changeset: 5:a00ad806cb55
233 changeset: 5:a00ad806cb55
228 tag: tip
234 tag: tip
229 user: someone else
235 user: someone else
230 date: Thu Jan 01 00:00:00 1970 +0000
236 date: Thu Jan 01 00:00:00 1970 +0000
231 summary: f
237 summary: f
232
238
233
239
234 $ hg histedit e860deea161a --commands - 2>&1 <<EOF | fixbundle
240 $ hg histedit e860deea161a --commands - 2>&1 <<EOF | fixbundle
235 > pick e860deea161a e
241 > pick e860deea161a e
236 > fold a00ad806cb55 f
242 > fold a00ad806cb55 f
237 > EOF
243 > EOF
238 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
244 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
239 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
245 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
240 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
246 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
241 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
247 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
242
248
243 tip after edit
249 tip after edit
244 $ hg log --rev .
250 $ hg log --rev .
245 changeset: 4:698d4e8040a1
251 changeset: 4:698d4e8040a1
246 tag: tip
252 tag: tip
247 user: test
253 user: test
248 date: Thu Jan 01 00:00:00 1970 +0000
254 date: Thu Jan 01 00:00:00 1970 +0000
249 summary: e
255 summary: e
250
256
251
257
252 $ cd ..
258 $ cd ..
253 $ rm -r r
259 $ rm -r r
254
260
255 folding and creating no new change doesn't break:
261 folding and creating no new change doesn't break:
256 -------------------------------------------------
262 -------------------------------------------------
257
263
258 folded content is dropped during a merge. The folded commit should properly disappear.
264 folded content is dropped during a merge. The folded commit should properly disappear.
259
265
260 $ mkdir fold-to-empty-test
266 $ mkdir fold-to-empty-test
261 $ cd fold-to-empty-test
267 $ cd fold-to-empty-test
262 $ hg init
268 $ hg init
263 $ printf "1\n2\n3\n" > file
269 $ printf "1\n2\n3\n" > file
264 $ hg add file
270 $ hg add file
265 $ hg commit -m '1+2+3'
271 $ hg commit -m '1+2+3'
266 $ echo 4 >> file
272 $ echo 4 >> file
267 $ hg commit -m '+4'
273 $ hg commit -m '+4'
268 $ echo 5 >> file
274 $ echo 5 >> file
269 $ hg commit -m '+5'
275 $ hg commit -m '+5'
270 $ echo 6 >> file
276 $ echo 6 >> file
271 $ hg commit -m '+6'
277 $ hg commit -m '+6'
272 $ hg logt --graph
278 $ hg logt --graph
273 @ 3:251d831eeec5 +6
279 @ 3:251d831eeec5 +6
274 |
280 |
275 o 2:888f9082bf99 +5
281 o 2:888f9082bf99 +5
276 |
282 |
277 o 1:617f94f13c0f +4
283 o 1:617f94f13c0f +4
278 |
284 |
279 o 0:0189ba417d34 1+2+3
285 o 0:0189ba417d34 1+2+3
280
286
281
287
282 $ hg histedit 1 --commands - << EOF
288 $ hg histedit 1 --commands - << EOF
283 > pick 617f94f13c0f 1 +4
289 > pick 617f94f13c0f 1 +4
284 > drop 888f9082bf99 2 +5
290 > drop 888f9082bf99 2 +5
285 > fold 251d831eeec5 3 +6
291 > fold 251d831eeec5 3 +6
286 > EOF
292 > EOF
287 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
293 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
288 merging file
294 merging file
289 warning: conflicts during merge.
295 warning: conflicts during merge.
290 merging file incomplete! (edit conflicts, then use 'hg resolve --mark')
296 merging file incomplete! (edit conflicts, then use 'hg resolve --mark')
291 Fix up the change and run hg histedit --continue
297 Fix up the change and run hg histedit --continue
292 [1]
298 [1]
293 There were conflicts, we keep P1 content. This
299 There were conflicts, we keep P1 content. This
294 should effectively drop the changes from +6.
300 should effectively drop the changes from +6.
295 $ hg status
301 $ hg status
296 M file
302 M file
297 ? file.orig
303 ? file.orig
298 $ hg resolve -l
304 $ hg resolve -l
299 U file
305 U file
300 $ hg revert -r 'p1()' file
306 $ hg revert -r 'p1()' file
301 $ hg resolve --mark file
307 $ hg resolve --mark file
302 (no more unresolved files)
308 (no more unresolved files)
303 $ hg histedit --continue
309 $ hg histedit --continue
304 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
310 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
305 saved backup bundle to $TESTTMP/*-backup.hg (glob)
311 saved backup bundle to $TESTTMP/*-backup.hg (glob)
306 $ hg logt --graph
312 $ hg logt --graph
307 @ 1:617f94f13c0f +4
313 @ 1:617f94f13c0f +4
308 |
314 |
309 o 0:0189ba417d34 1+2+3
315 o 0:0189ba417d34 1+2+3
310
316
311
317
312 $ cd ..
318 $ cd ..
313
319
314
320
315 Test fold through dropped
321 Test fold through dropped
316 -------------------------
322 -------------------------
317
323
318
324
319 Test corner case where folded revision is separated from its parent by a
325 Test corner case where folded revision is separated from its parent by a
320 dropped revision.
326 dropped revision.
321
327
322
328
323 $ hg init fold-with-dropped
329 $ hg init fold-with-dropped
324 $ cd fold-with-dropped
330 $ cd fold-with-dropped
325 $ printf "1\n2\n3\n" > file
331 $ printf "1\n2\n3\n" > file
326 $ hg commit -Am '1+2+3'
332 $ hg commit -Am '1+2+3'
327 adding file
333 adding file
328 $ echo 4 >> file
334 $ echo 4 >> file
329 $ hg commit -m '+4'
335 $ hg commit -m '+4'
330 $ echo 5 >> file
336 $ echo 5 >> file
331 $ hg commit -m '+5'
337 $ hg commit -m '+5'
332 $ echo 6 >> file
338 $ echo 6 >> file
333 $ hg commit -m '+6'
339 $ hg commit -m '+6'
334 $ hg logt -G
340 $ hg logt -G
335 @ 3:251d831eeec5 +6
341 @ 3:251d831eeec5 +6
336 |
342 |
337 o 2:888f9082bf99 +5
343 o 2:888f9082bf99 +5
338 |
344 |
339 o 1:617f94f13c0f +4
345 o 1:617f94f13c0f +4
340 |
346 |
341 o 0:0189ba417d34 1+2+3
347 o 0:0189ba417d34 1+2+3
342
348
343 $ hg histedit 1 --commands - << EOF
349 $ hg histedit 1 --commands - << EOF
344 > pick 617f94f13c0f 1 +4
350 > pick 617f94f13c0f 1 +4
345 > drop 888f9082bf99 2 +5
351 > drop 888f9082bf99 2 +5
346 > fold 251d831eeec5 3 +6
352 > fold 251d831eeec5 3 +6
347 > EOF
353 > EOF
348 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
354 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
349 merging file
355 merging file
350 warning: conflicts during merge.
356 warning: conflicts during merge.
351 merging file incomplete! (edit conflicts, then use 'hg resolve --mark')
357 merging file incomplete! (edit conflicts, then use 'hg resolve --mark')
352 Fix up the change and run hg histedit --continue
358 Fix up the change and run hg histedit --continue
353 [1]
359 [1]
354 $ cat > file << EOF
360 $ cat > file << EOF
355 > 1
361 > 1
356 > 2
362 > 2
357 > 3
363 > 3
358 > 4
364 > 4
359 > 5
365 > 5
360 > EOF
366 > EOF
361 $ hg resolve --mark file
367 $ hg resolve --mark file
362 (no more unresolved files)
368 (no more unresolved files)
363 $ hg commit -m '+5.2'
369 $ hg commit -m '+5.2'
364 created new head
370 created new head
365 $ echo 6 >> file
371 $ echo 6 >> file
366 $ HGEDITOR=cat hg histedit --continue
372 $ HGEDITOR=cat hg histedit --continue
367 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
373 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
368 +4
374 +4
369 ***
375 ***
370 +5.2
376 +5.2
371 ***
377 ***
372 +6
378 +6
373
379
374
380
375
381
376 HG: Enter commit message. Lines beginning with 'HG:' are removed.
382 HG: Enter commit message. Lines beginning with 'HG:' are removed.
377 HG: Leave message empty to abort commit.
383 HG: Leave message empty to abort commit.
378 HG: --
384 HG: --
379 HG: user: test
385 HG: user: test
380 HG: branch 'default'
386 HG: branch 'default'
381 HG: changed file
387 HG: changed file
382 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
388 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
383 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
389 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
384 saved backup bundle to $TESTTMP/fold-with-dropped/.hg/strip-backup/617f94f13c0f-backup.hg (glob)
390 saved backup bundle to $TESTTMP/fold-with-dropped/.hg/strip-backup/617f94f13c0f-backup.hg (glob)
385 $ hg logt -G
391 $ hg logt -G
386 @ 1:10c647b2cdd5 +4
392 @ 1:10c647b2cdd5 +4
387 |
393 |
388 o 0:0189ba417d34 1+2+3
394 o 0:0189ba417d34 1+2+3
389
395
390 $ hg export tip
396 $ hg export tip
391 # HG changeset patch
397 # HG changeset patch
392 # User test
398 # User test
393 # Date 0 0
399 # Date 0 0
394 # Thu Jan 01 00:00:00 1970 +0000
400 # Thu Jan 01 00:00:00 1970 +0000
395 # Node ID 10c647b2cdd54db0603ecb99b2ff5ce66d5a5323
401 # Node ID 10c647b2cdd54db0603ecb99b2ff5ce66d5a5323
396 # Parent 0189ba417d34df9dda55f88b637dcae9917b5964
402 # Parent 0189ba417d34df9dda55f88b637dcae9917b5964
397 +4
403 +4
398 ***
404 ***
399 +5.2
405 +5.2
400 ***
406 ***
401 +6
407 +6
402
408
403 diff -r 0189ba417d34 -r 10c647b2cdd5 file
409 diff -r 0189ba417d34 -r 10c647b2cdd5 file
404 --- a/file Thu Jan 01 00:00:00 1970 +0000
410 --- a/file Thu Jan 01 00:00:00 1970 +0000
405 +++ b/file Thu Jan 01 00:00:00 1970 +0000
411 +++ b/file Thu Jan 01 00:00:00 1970 +0000
406 @@ -1,3 +1,6 @@
412 @@ -1,3 +1,6 @@
407 1
413 1
408 2
414 2
409 3
415 3
410 +4
416 +4
411 +5
417 +5
412 +6
418 +6
413 $ cd ..
419 $ cd ..
414
420
415
421
416 Folding with initial rename (issue3729)
422 Folding with initial rename (issue3729)
417 ---------------------------------------
423 ---------------------------------------
418
424
419 $ hg init fold-rename
425 $ hg init fold-rename
420 $ cd fold-rename
426 $ cd fold-rename
421 $ echo a > a.txt
427 $ echo a > a.txt
422 $ hg add a.txt
428 $ hg add a.txt
423 $ hg commit -m a
429 $ hg commit -m a
424 $ hg rename a.txt b.txt
430 $ hg rename a.txt b.txt
425 $ hg commit -m rename
431 $ hg commit -m rename
426 $ echo b >> b.txt
432 $ echo b >> b.txt
427 $ hg commit -m b
433 $ hg commit -m b
428
434
429 $ hg logt --follow b.txt
435 $ hg logt --follow b.txt
430 2:e0371e0426bc b
436 2:e0371e0426bc b
431 1:1c4f440a8085 rename
437 1:1c4f440a8085 rename
432 0:6c795aa153cb a
438 0:6c795aa153cb a
433
439
434 $ hg histedit 1c4f440a8085 --commands - 2>&1 << EOF | fixbundle
440 $ hg histedit 1c4f440a8085 --commands - 2>&1 << EOF | fixbundle
435 > pick 1c4f440a8085 rename
441 > pick 1c4f440a8085 rename
436 > fold e0371e0426bc b
442 > fold e0371e0426bc b
437 > EOF
443 > EOF
438 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
444 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
439 reverting b.txt
445 reverting b.txt
440 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
446 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
441 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
447 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
442 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
448 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
443
449
444 $ hg logt --follow b.txt
450 $ hg logt --follow b.txt
445 1:cf858d235c76 rename
451 1:cf858d235c76 rename
446 0:6c795aa153cb a
452 0:6c795aa153cb a
447
453
448 $ cd ..
454 $ cd ..
449
455
450 Folding with swapping
456 Folding with swapping
451 ---------------------
457 ---------------------
452
458
453 This is an excuse to test hook with histedit temporary commit (issue4422)
459 This is an excuse to test hook with histedit temporary commit (issue4422)
454
460
455
461
456 $ hg init issue4422
462 $ hg init issue4422
457 $ cd issue4422
463 $ cd issue4422
458 $ echo a > a.txt
464 $ echo a > a.txt
459 $ hg add a.txt
465 $ hg add a.txt
460 $ hg commit -m a
466 $ hg commit -m a
461 $ echo b > b.txt
467 $ echo b > b.txt
462 $ hg add b.txt
468 $ hg add b.txt
463 $ hg commit -m b
469 $ hg commit -m b
464 $ echo c > c.txt
470 $ echo c > c.txt
465 $ hg add c.txt
471 $ hg add c.txt
466 $ hg commit -m c
472 $ hg commit -m c
467
473
468 $ hg logt
474 $ hg logt
469 2:a1a953ffb4b0 c
475 2:a1a953ffb4b0 c
470 1:199b6bb90248 b
476 1:199b6bb90248 b
471 0:6c795aa153cb a
477 0:6c795aa153cb a
472
478
473 Setup the proper environment variable symbol for the platform, to be subbed
479 Setup the proper environment variable symbol for the platform, to be subbed
474 into the hook command.
480 into the hook command.
475 #if windows
481 #if windows
476 $ NODE="%HG_NODE%"
482 $ NODE="%HG_NODE%"
477 #else
483 #else
478 $ NODE="\$HG_NODE"
484 $ NODE="\$HG_NODE"
479 #endif
485 #endif
480 $ hg histedit 6c795aa153cb --config hooks.commit="echo commit $NODE" --commands - 2>&1 << EOF | fixbundle
486 $ hg histedit 6c795aa153cb --config hooks.commit="echo commit $NODE" --commands - 2>&1 << EOF | fixbundle
481 > pick 199b6bb90248 b
487 > pick 199b6bb90248 b
482 > fold a1a953ffb4b0 c
488 > fold a1a953ffb4b0 c
483 > pick 6c795aa153cb a
489 > pick 6c795aa153cb a
484 > EOF
490 > EOF
485 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
491 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
486 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
492 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
487 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
493 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
488 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
494 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
489 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
495 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
490 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
496 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
491 commit 9599899f62c05f4377548c32bf1c9f1a39634b0c
497 commit 9599899f62c05f4377548c32bf1c9f1a39634b0c
492
498
493 $ hg logt
499 $ hg logt
494 1:9599899f62c0 a
500 1:9599899f62c0 a
495 0:79b99e9c8e49 b
501 0:79b99e9c8e49 b
496
502
497 $ cd ..
503 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now