##// END OF EJS Templates
filectx: handle some other simple cases for finding merge ancestor
Matt Mackall -
r16601:0c98820b default
parent child Browse files
Show More
@@ -1,1259 +1,1266 b''
1 # context.py - changeset and file context objects for mercurial
1 # context.py - changeset and file context objects for mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import nullid, nullrev, short, hex, bin
8 from node import nullid, nullrev, short, hex, bin
9 from i18n import _
9 from i18n import _
10 import ancestor, mdiff, error, util, scmutil, subrepo, patch, encoding, phases
10 import ancestor, 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
13
14 propertycache = util.propertycache
14 propertycache = util.propertycache
15
15
16 class changectx(object):
16 class changectx(object):
17 """A changecontext object makes access to data related to a particular
17 """A changecontext object makes access to data related to a particular
18 changeset convenient."""
18 changeset convenient."""
19 def __init__(self, repo, changeid=''):
19 def __init__(self, repo, changeid=''):
20 """changeid is a revision number, node, or tag"""
20 """changeid is a revision number, node, or tag"""
21 if changeid == '':
21 if changeid == '':
22 changeid = '.'
22 changeid = '.'
23 self._repo = repo
23 self._repo = repo
24
24
25 if isinstance(changeid, int):
25 if isinstance(changeid, int):
26 self._rev = changeid
26 self._rev = changeid
27 self._node = repo.changelog.node(changeid)
27 self._node = repo.changelog.node(changeid)
28 return
28 return
29 if changeid == '.':
29 if changeid == '.':
30 self._node = repo.dirstate.p1()
30 self._node = repo.dirstate.p1()
31 self._rev = repo.changelog.rev(self._node)
31 self._rev = repo.changelog.rev(self._node)
32 return
32 return
33 if changeid == 'null':
33 if changeid == 'null':
34 self._node = nullid
34 self._node = nullid
35 self._rev = nullrev
35 self._rev = nullrev
36 return
36 return
37 if changeid == 'tip':
37 if changeid == 'tip':
38 self._rev = len(repo.changelog) - 1
38 self._rev = len(repo.changelog) - 1
39 self._node = repo.changelog.node(self._rev)
39 self._node = repo.changelog.node(self._rev)
40 return
40 return
41 if len(changeid) == 20:
41 if len(changeid) == 20:
42 try:
42 try:
43 self._node = changeid
43 self._node = changeid
44 self._rev = repo.changelog.rev(changeid)
44 self._rev = repo.changelog.rev(changeid)
45 return
45 return
46 except LookupError:
46 except LookupError:
47 pass
47 pass
48
48
49 try:
49 try:
50 r = int(changeid)
50 r = int(changeid)
51 if str(r) != changeid:
51 if str(r) != changeid:
52 raise ValueError
52 raise ValueError
53 l = len(repo.changelog)
53 l = len(repo.changelog)
54 if r < 0:
54 if r < 0:
55 r += l
55 r += l
56 if r < 0 or r >= l:
56 if r < 0 or r >= l:
57 raise ValueError
57 raise ValueError
58 self._rev = r
58 self._rev = r
59 self._node = repo.changelog.node(r)
59 self._node = repo.changelog.node(r)
60 return
60 return
61 except (ValueError, OverflowError):
61 except (ValueError, OverflowError):
62 pass
62 pass
63
63
64 if len(changeid) == 40:
64 if len(changeid) == 40:
65 try:
65 try:
66 self._node = bin(changeid)
66 self._node = bin(changeid)
67 self._rev = repo.changelog.rev(self._node)
67 self._rev = repo.changelog.rev(self._node)
68 return
68 return
69 except (TypeError, LookupError):
69 except (TypeError, LookupError):
70 pass
70 pass
71
71
72 if changeid in repo._bookmarks:
72 if changeid in repo._bookmarks:
73 self._node = repo._bookmarks[changeid]
73 self._node = repo._bookmarks[changeid]
74 self._rev = repo.changelog.rev(self._node)
74 self._rev = repo.changelog.rev(self._node)
75 return
75 return
76 if changeid in repo._tagscache.tags:
76 if changeid in repo._tagscache.tags:
77 self._node = repo._tagscache.tags[changeid]
77 self._node = repo._tagscache.tags[changeid]
78 self._rev = repo.changelog.rev(self._node)
78 self._rev = repo.changelog.rev(self._node)
79 return
79 return
80 if changeid in repo.branchtags():
80 if changeid in repo.branchtags():
81 self._node = repo.branchtags()[changeid]
81 self._node = repo.branchtags()[changeid]
82 self._rev = repo.changelog.rev(self._node)
82 self._rev = repo.changelog.rev(self._node)
83 return
83 return
84
84
85 self._node = repo.changelog._partialmatch(changeid)
85 self._node = repo.changelog._partialmatch(changeid)
86 if self._node is not None:
86 if self._node is not None:
87 self._rev = repo.changelog.rev(self._node)
87 self._rev = repo.changelog.rev(self._node)
88 return
88 return
89
89
90 # lookup failed
90 # lookup failed
91 # check if it might have come from damaged dirstate
91 # check if it might have come from damaged dirstate
92 if changeid in repo.dirstate.parents():
92 if changeid in repo.dirstate.parents():
93 raise error.Abort(_("working directory has unknown parent '%s'!")
93 raise error.Abort(_("working directory has unknown parent '%s'!")
94 % short(changeid))
94 % short(changeid))
95 try:
95 try:
96 if len(changeid) == 20:
96 if len(changeid) == 20:
97 changeid = hex(changeid)
97 changeid = hex(changeid)
98 except TypeError:
98 except TypeError:
99 pass
99 pass
100 raise error.RepoLookupError(
100 raise error.RepoLookupError(
101 _("unknown revision '%s'") % changeid)
101 _("unknown revision '%s'") % changeid)
102
102
103 def __str__(self):
103 def __str__(self):
104 return short(self.node())
104 return short(self.node())
105
105
106 def __int__(self):
106 def __int__(self):
107 return self.rev()
107 return self.rev()
108
108
109 def __repr__(self):
109 def __repr__(self):
110 return "<changectx %s>" % str(self)
110 return "<changectx %s>" % str(self)
111
111
112 def __hash__(self):
112 def __hash__(self):
113 try:
113 try:
114 return hash(self._rev)
114 return hash(self._rev)
115 except AttributeError:
115 except AttributeError:
116 return id(self)
116 return id(self)
117
117
118 def __eq__(self, other):
118 def __eq__(self, other):
119 try:
119 try:
120 return self._rev == other._rev
120 return self._rev == other._rev
121 except AttributeError:
121 except AttributeError:
122 return False
122 return False
123
123
124 def __ne__(self, other):
124 def __ne__(self, other):
125 return not (self == other)
125 return not (self == other)
126
126
127 def __nonzero__(self):
127 def __nonzero__(self):
128 return self._rev != nullrev
128 return self._rev != nullrev
129
129
130 @propertycache
130 @propertycache
131 def _changeset(self):
131 def _changeset(self):
132 return self._repo.changelog.read(self.rev())
132 return self._repo.changelog.read(self.rev())
133
133
134 @propertycache
134 @propertycache
135 def _manifest(self):
135 def _manifest(self):
136 return self._repo.manifest.read(self._changeset[0])
136 return self._repo.manifest.read(self._changeset[0])
137
137
138 @propertycache
138 @propertycache
139 def _manifestdelta(self):
139 def _manifestdelta(self):
140 return self._repo.manifest.readdelta(self._changeset[0])
140 return self._repo.manifest.readdelta(self._changeset[0])
141
141
142 @propertycache
142 @propertycache
143 def _parents(self):
143 def _parents(self):
144 p = self._repo.changelog.parentrevs(self._rev)
144 p = self._repo.changelog.parentrevs(self._rev)
145 if p[1] == nullrev:
145 if p[1] == nullrev:
146 p = p[:-1]
146 p = p[:-1]
147 return [changectx(self._repo, x) for x in p]
147 return [changectx(self._repo, x) for x in p]
148
148
149 @propertycache
149 @propertycache
150 def substate(self):
150 def substate(self):
151 return subrepo.state(self, self._repo.ui)
151 return subrepo.state(self, self._repo.ui)
152
152
153 def __contains__(self, key):
153 def __contains__(self, key):
154 return key in self._manifest
154 return key in self._manifest
155
155
156 def __getitem__(self, key):
156 def __getitem__(self, key):
157 return self.filectx(key)
157 return self.filectx(key)
158
158
159 def __iter__(self):
159 def __iter__(self):
160 for f in sorted(self._manifest):
160 for f in sorted(self._manifest):
161 yield f
161 yield f
162
162
163 def changeset(self):
163 def changeset(self):
164 return self._changeset
164 return self._changeset
165 def manifest(self):
165 def manifest(self):
166 return self._manifest
166 return self._manifest
167 def manifestnode(self):
167 def manifestnode(self):
168 return self._changeset[0]
168 return self._changeset[0]
169
169
170 def rev(self):
170 def rev(self):
171 return self._rev
171 return self._rev
172 def node(self):
172 def node(self):
173 return self._node
173 return self._node
174 def hex(self):
174 def hex(self):
175 return hex(self._node)
175 return hex(self._node)
176 def user(self):
176 def user(self):
177 return self._changeset[1]
177 return self._changeset[1]
178 def date(self):
178 def date(self):
179 return self._changeset[2]
179 return self._changeset[2]
180 def files(self):
180 def files(self):
181 return self._changeset[3]
181 return self._changeset[3]
182 def description(self):
182 def description(self):
183 return self._changeset[4]
183 return self._changeset[4]
184 def branch(self):
184 def branch(self):
185 return encoding.tolocal(self._changeset[5].get("branch"))
185 return encoding.tolocal(self._changeset[5].get("branch"))
186 def extra(self):
186 def extra(self):
187 return self._changeset[5]
187 return self._changeset[5]
188 def tags(self):
188 def tags(self):
189 return self._repo.nodetags(self._node)
189 return self._repo.nodetags(self._node)
190 def bookmarks(self):
190 def bookmarks(self):
191 return self._repo.nodebookmarks(self._node)
191 return self._repo.nodebookmarks(self._node)
192 def phase(self):
192 def phase(self):
193 if self._rev == -1:
193 if self._rev == -1:
194 return phases.public
194 return phases.public
195 if self._rev >= len(self._repo._phaserev):
195 if self._rev >= len(self._repo._phaserev):
196 # outdated cache
196 # outdated cache
197 del self._repo._phaserev
197 del self._repo._phaserev
198 return self._repo._phaserev[self._rev]
198 return self._repo._phaserev[self._rev]
199 def phasestr(self):
199 def phasestr(self):
200 return phases.phasenames[self.phase()]
200 return phases.phasenames[self.phase()]
201 def mutable(self):
201 def mutable(self):
202 return self.phase() > phases.public
202 return self.phase() > phases.public
203 def hidden(self):
203 def hidden(self):
204 return self._rev in self._repo.changelog.hiddenrevs
204 return self._rev in self._repo.changelog.hiddenrevs
205
205
206 def parents(self):
206 def parents(self):
207 """return contexts for each parent changeset"""
207 """return contexts for each parent changeset"""
208 return self._parents
208 return self._parents
209
209
210 def p1(self):
210 def p1(self):
211 return self._parents[0]
211 return self._parents[0]
212
212
213 def p2(self):
213 def p2(self):
214 if len(self._parents) == 2:
214 if len(self._parents) == 2:
215 return self._parents[1]
215 return self._parents[1]
216 return changectx(self._repo, -1)
216 return changectx(self._repo, -1)
217
217
218 def children(self):
218 def children(self):
219 """return contexts for each child changeset"""
219 """return contexts for each child changeset"""
220 c = self._repo.changelog.children(self._node)
220 c = self._repo.changelog.children(self._node)
221 return [changectx(self._repo, x) for x in c]
221 return [changectx(self._repo, x) for x in c]
222
222
223 def ancestors(self):
223 def ancestors(self):
224 for a in self._repo.changelog.ancestors(self._rev):
224 for a in self._repo.changelog.ancestors(self._rev):
225 yield changectx(self._repo, a)
225 yield changectx(self._repo, a)
226
226
227 def descendants(self):
227 def descendants(self):
228 for d in self._repo.changelog.descendants(self._rev):
228 for d in self._repo.changelog.descendants(self._rev):
229 yield changectx(self._repo, d)
229 yield changectx(self._repo, d)
230
230
231 def _fileinfo(self, path):
231 def _fileinfo(self, path):
232 if '_manifest' in self.__dict__:
232 if '_manifest' in self.__dict__:
233 try:
233 try:
234 return self._manifest[path], self._manifest.flags(path)
234 return self._manifest[path], self._manifest.flags(path)
235 except KeyError:
235 except KeyError:
236 raise error.LookupError(self._node, path,
236 raise error.LookupError(self._node, path,
237 _('not found in manifest'))
237 _('not found in manifest'))
238 if '_manifestdelta' in self.__dict__ or path in self.files():
238 if '_manifestdelta' in self.__dict__ or path in self.files():
239 if path in self._manifestdelta:
239 if path in self._manifestdelta:
240 return self._manifestdelta[path], self._manifestdelta.flags(path)
240 return self._manifestdelta[path], self._manifestdelta.flags(path)
241 node, flag = self._repo.manifest.find(self._changeset[0], path)
241 node, flag = self._repo.manifest.find(self._changeset[0], path)
242 if not node:
242 if not node:
243 raise error.LookupError(self._node, path,
243 raise error.LookupError(self._node, path,
244 _('not found in manifest'))
244 _('not found in manifest'))
245
245
246 return node, flag
246 return node, flag
247
247
248 def filenode(self, path):
248 def filenode(self, path):
249 return self._fileinfo(path)[0]
249 return self._fileinfo(path)[0]
250
250
251 def flags(self, path):
251 def flags(self, path):
252 try:
252 try:
253 return self._fileinfo(path)[1]
253 return self._fileinfo(path)[1]
254 except error.LookupError:
254 except error.LookupError:
255 return ''
255 return ''
256
256
257 def filectx(self, path, fileid=None, filelog=None):
257 def filectx(self, path, fileid=None, filelog=None):
258 """get a file context from this changeset"""
258 """get a file context from this changeset"""
259 if fileid is None:
259 if fileid is None:
260 fileid = self.filenode(path)
260 fileid = self.filenode(path)
261 return filectx(self._repo, path, fileid=fileid,
261 return filectx(self._repo, path, fileid=fileid,
262 changectx=self, filelog=filelog)
262 changectx=self, filelog=filelog)
263
263
264 def ancestor(self, c2):
264 def ancestor(self, c2):
265 """
265 """
266 return the ancestor context of self and c2
266 return the ancestor context of self and c2
267 """
267 """
268 # deal with workingctxs
268 # deal with workingctxs
269 n2 = c2._node
269 n2 = c2._node
270 if n2 is None:
270 if n2 is None:
271 n2 = c2._parents[0]._node
271 n2 = c2._parents[0]._node
272 n = self._repo.changelog.ancestor(self._node, n2)
272 n = self._repo.changelog.ancestor(self._node, n2)
273 return changectx(self._repo, n)
273 return changectx(self._repo, n)
274
274
275 def walk(self, match):
275 def walk(self, match):
276 fset = set(match.files())
276 fset = set(match.files())
277 # for dirstate.walk, files=['.'] means "walk the whole tree".
277 # for dirstate.walk, files=['.'] means "walk the whole tree".
278 # follow that here, too
278 # follow that here, too
279 fset.discard('.')
279 fset.discard('.')
280 for fn in self:
280 for fn in self:
281 if fn in fset:
281 if fn in fset:
282 # specified pattern is the exact name
282 # specified pattern is the exact name
283 fset.remove(fn)
283 fset.remove(fn)
284 if match(fn):
284 if match(fn):
285 yield fn
285 yield fn
286 for fn in sorted(fset):
286 for fn in sorted(fset):
287 if fn in self._dirs:
287 if fn in self._dirs:
288 # specified pattern is a directory
288 # specified pattern is a directory
289 continue
289 continue
290 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
290 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
291 yield fn
291 yield fn
292
292
293 def sub(self, path):
293 def sub(self, path):
294 return subrepo.subrepo(self, path)
294 return subrepo.subrepo(self, path)
295
295
296 def match(self, pats=[], include=None, exclude=None, default='glob'):
296 def match(self, pats=[], include=None, exclude=None, default='glob'):
297 r = self._repo
297 r = self._repo
298 return matchmod.match(r.root, r.getcwd(), pats,
298 return matchmod.match(r.root, r.getcwd(), pats,
299 include, exclude, default,
299 include, exclude, default,
300 auditor=r.auditor, ctx=self)
300 auditor=r.auditor, ctx=self)
301
301
302 def diff(self, ctx2=None, match=None, **opts):
302 def diff(self, ctx2=None, match=None, **opts):
303 """Returns a diff generator for the given contexts and matcher"""
303 """Returns a diff generator for the given contexts and matcher"""
304 if ctx2 is None:
304 if ctx2 is None:
305 ctx2 = self.p1()
305 ctx2 = self.p1()
306 if ctx2 is not None and not isinstance(ctx2, changectx):
306 if ctx2 is not None and not isinstance(ctx2, changectx):
307 ctx2 = self._repo[ctx2]
307 ctx2 = self._repo[ctx2]
308 diffopts = patch.diffopts(self._repo.ui, opts)
308 diffopts = patch.diffopts(self._repo.ui, opts)
309 return patch.diff(self._repo, ctx2.node(), self.node(),
309 return patch.diff(self._repo, ctx2.node(), self.node(),
310 match=match, opts=diffopts)
310 match=match, opts=diffopts)
311
311
312 @propertycache
312 @propertycache
313 def _dirs(self):
313 def _dirs(self):
314 dirs = set()
314 dirs = set()
315 for f in self._manifest:
315 for f in self._manifest:
316 pos = f.rfind('/')
316 pos = f.rfind('/')
317 while pos != -1:
317 while pos != -1:
318 f = f[:pos]
318 f = f[:pos]
319 if f in dirs:
319 if f in dirs:
320 break # dirs already contains this and above
320 break # dirs already contains this and above
321 dirs.add(f)
321 dirs.add(f)
322 pos = f.rfind('/')
322 pos = f.rfind('/')
323 return dirs
323 return dirs
324
324
325 def dirs(self):
325 def dirs(self):
326 return self._dirs
326 return self._dirs
327
327
328 class filectx(object):
328 class filectx(object):
329 """A filecontext object makes access to data related to a particular
329 """A filecontext object makes access to data related to a particular
330 filerevision convenient."""
330 filerevision convenient."""
331 def __init__(self, repo, path, changeid=None, fileid=None,
331 def __init__(self, repo, path, changeid=None, fileid=None,
332 filelog=None, changectx=None):
332 filelog=None, changectx=None):
333 """changeid can be a changeset revision, node, or tag.
333 """changeid can be a changeset revision, node, or tag.
334 fileid can be a file revision or node."""
334 fileid can be a file revision or node."""
335 self._repo = repo
335 self._repo = repo
336 self._path = path
336 self._path = path
337
337
338 assert (changeid is not None
338 assert (changeid is not None
339 or fileid is not None
339 or fileid is not None
340 or changectx is not None), \
340 or changectx is not None), \
341 ("bad args: changeid=%r, fileid=%r, changectx=%r"
341 ("bad args: changeid=%r, fileid=%r, changectx=%r"
342 % (changeid, fileid, changectx))
342 % (changeid, fileid, changectx))
343
343
344 if filelog:
344 if filelog:
345 self._filelog = filelog
345 self._filelog = filelog
346
346
347 if changeid is not None:
347 if changeid is not None:
348 self._changeid = changeid
348 self._changeid = changeid
349 if changectx is not None:
349 if changectx is not None:
350 self._changectx = changectx
350 self._changectx = changectx
351 if fileid is not None:
351 if fileid is not None:
352 self._fileid = fileid
352 self._fileid = fileid
353
353
354 @propertycache
354 @propertycache
355 def _changectx(self):
355 def _changectx(self):
356 return changectx(self._repo, self._changeid)
356 return changectx(self._repo, self._changeid)
357
357
358 @propertycache
358 @propertycache
359 def _filelog(self):
359 def _filelog(self):
360 return self._repo.file(self._path)
360 return self._repo.file(self._path)
361
361
362 @propertycache
362 @propertycache
363 def _changeid(self):
363 def _changeid(self):
364 if '_changectx' in self.__dict__:
364 if '_changectx' in self.__dict__:
365 return self._changectx.rev()
365 return self._changectx.rev()
366 else:
366 else:
367 return self._filelog.linkrev(self._filerev)
367 return self._filelog.linkrev(self._filerev)
368
368
369 @propertycache
369 @propertycache
370 def _filenode(self):
370 def _filenode(self):
371 if '_fileid' in self.__dict__:
371 if '_fileid' in self.__dict__:
372 return self._filelog.lookup(self._fileid)
372 return self._filelog.lookup(self._fileid)
373 else:
373 else:
374 return self._changectx.filenode(self._path)
374 return self._changectx.filenode(self._path)
375
375
376 @propertycache
376 @propertycache
377 def _filerev(self):
377 def _filerev(self):
378 return self._filelog.rev(self._filenode)
378 return self._filelog.rev(self._filenode)
379
379
380 @propertycache
380 @propertycache
381 def _repopath(self):
381 def _repopath(self):
382 return self._path
382 return self._path
383
383
384 def __nonzero__(self):
384 def __nonzero__(self):
385 try:
385 try:
386 self._filenode
386 self._filenode
387 return True
387 return True
388 except error.LookupError:
388 except error.LookupError:
389 # file is missing
389 # file is missing
390 return False
390 return False
391
391
392 def __str__(self):
392 def __str__(self):
393 return "%s@%s" % (self.path(), short(self.node()))
393 return "%s@%s" % (self.path(), short(self.node()))
394
394
395 def __repr__(self):
395 def __repr__(self):
396 return "<filectx %s>" % str(self)
396 return "<filectx %s>" % str(self)
397
397
398 def __hash__(self):
398 def __hash__(self):
399 try:
399 try:
400 return hash((self._path, self._filenode))
400 return hash((self._path, self._filenode))
401 except AttributeError:
401 except AttributeError:
402 return id(self)
402 return id(self)
403
403
404 def __eq__(self, other):
404 def __eq__(self, other):
405 try:
405 try:
406 return (self._path == other._path
406 return (self._path == other._path
407 and self._filenode == other._filenode)
407 and self._filenode == other._filenode)
408 except AttributeError:
408 except AttributeError:
409 return False
409 return False
410
410
411 def __ne__(self, other):
411 def __ne__(self, other):
412 return not (self == other)
412 return not (self == other)
413
413
414 def filectx(self, fileid):
414 def filectx(self, fileid):
415 '''opens an arbitrary revision of the file without
415 '''opens an arbitrary revision of the file without
416 opening a new filelog'''
416 opening a new filelog'''
417 return filectx(self._repo, self._path, fileid=fileid,
417 return filectx(self._repo, self._path, fileid=fileid,
418 filelog=self._filelog)
418 filelog=self._filelog)
419
419
420 def filerev(self):
420 def filerev(self):
421 return self._filerev
421 return self._filerev
422 def filenode(self):
422 def filenode(self):
423 return self._filenode
423 return self._filenode
424 def flags(self):
424 def flags(self):
425 return self._changectx.flags(self._path)
425 return self._changectx.flags(self._path)
426 def filelog(self):
426 def filelog(self):
427 return self._filelog
427 return self._filelog
428
428
429 def rev(self):
429 def rev(self):
430 if '_changectx' in self.__dict__:
430 if '_changectx' in self.__dict__:
431 return self._changectx.rev()
431 return self._changectx.rev()
432 if '_changeid' in self.__dict__:
432 if '_changeid' in self.__dict__:
433 return self._changectx.rev()
433 return self._changectx.rev()
434 return self._filelog.linkrev(self._filerev)
434 return self._filelog.linkrev(self._filerev)
435
435
436 def linkrev(self):
436 def linkrev(self):
437 return self._filelog.linkrev(self._filerev)
437 return self._filelog.linkrev(self._filerev)
438 def node(self):
438 def node(self):
439 return self._changectx.node()
439 return self._changectx.node()
440 def hex(self):
440 def hex(self):
441 return hex(self.node())
441 return hex(self.node())
442 def user(self):
442 def user(self):
443 return self._changectx.user()
443 return self._changectx.user()
444 def date(self):
444 def date(self):
445 return self._changectx.date()
445 return self._changectx.date()
446 def files(self):
446 def files(self):
447 return self._changectx.files()
447 return self._changectx.files()
448 def description(self):
448 def description(self):
449 return self._changectx.description()
449 return self._changectx.description()
450 def branch(self):
450 def branch(self):
451 return self._changectx.branch()
451 return self._changectx.branch()
452 def extra(self):
452 def extra(self):
453 return self._changectx.extra()
453 return self._changectx.extra()
454 def manifest(self):
454 def manifest(self):
455 return self._changectx.manifest()
455 return self._changectx.manifest()
456 def changectx(self):
456 def changectx(self):
457 return self._changectx
457 return self._changectx
458
458
459 def data(self):
459 def data(self):
460 return self._filelog.read(self._filenode)
460 return self._filelog.read(self._filenode)
461 def path(self):
461 def path(self):
462 return self._path
462 return self._path
463 def size(self):
463 def size(self):
464 return self._filelog.size(self._filerev)
464 return self._filelog.size(self._filerev)
465
465
466 def isbinary(self):
466 def isbinary(self):
467 try:
467 try:
468 return util.binary(self.data())
468 return util.binary(self.data())
469 except IOError:
469 except IOError:
470 return False
470 return False
471
471
472 def cmp(self, fctx):
472 def cmp(self, fctx):
473 """compare with other file context
473 """compare with other file context
474
474
475 returns True if different than fctx.
475 returns True if different than fctx.
476 """
476 """
477 if (fctx._filerev is None
477 if (fctx._filerev is None
478 and (self._repo._encodefilterpats
478 and (self._repo._encodefilterpats
479 # if file data starts with '\1\n', empty metadata block is
479 # if file data starts with '\1\n', empty metadata block is
480 # prepended, which adds 4 bytes to filelog.size().
480 # prepended, which adds 4 bytes to filelog.size().
481 or self.size() - 4 == fctx.size())
481 or self.size() - 4 == fctx.size())
482 or self.size() == fctx.size()):
482 or self.size() == fctx.size()):
483 return self._filelog.cmp(self._filenode, fctx.data())
483 return self._filelog.cmp(self._filenode, fctx.data())
484
484
485 return True
485 return True
486
486
487 def renamed(self):
487 def renamed(self):
488 """check if file was actually renamed in this changeset revision
488 """check if file was actually renamed in this changeset revision
489
489
490 If rename logged in file revision, we report copy for changeset only
490 If rename logged in file revision, we report copy for changeset only
491 if file revisions linkrev points back to the changeset in question
491 if file revisions linkrev points back to the changeset in question
492 or both changeset parents contain different file revisions.
492 or both changeset parents contain different file revisions.
493 """
493 """
494
494
495 renamed = self._filelog.renamed(self._filenode)
495 renamed = self._filelog.renamed(self._filenode)
496 if not renamed:
496 if not renamed:
497 return renamed
497 return renamed
498
498
499 if self.rev() == self.linkrev():
499 if self.rev() == self.linkrev():
500 return renamed
500 return renamed
501
501
502 name = self.path()
502 name = self.path()
503 fnode = self._filenode
503 fnode = self._filenode
504 for p in self._changectx.parents():
504 for p in self._changectx.parents():
505 try:
505 try:
506 if fnode == p.filenode(name):
506 if fnode == p.filenode(name):
507 return None
507 return None
508 except error.LookupError:
508 except error.LookupError:
509 pass
509 pass
510 return renamed
510 return renamed
511
511
512 def parents(self):
512 def parents(self):
513 p = self._path
513 p = self._path
514 fl = self._filelog
514 fl = self._filelog
515 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
515 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
516
516
517 r = self._filelog.renamed(self._filenode)
517 r = self._filelog.renamed(self._filenode)
518 if r:
518 if r:
519 pl[0] = (r[0], r[1], None)
519 pl[0] = (r[0], r[1], None)
520
520
521 return [filectx(self._repo, p, fileid=n, filelog=l)
521 return [filectx(self._repo, p, fileid=n, filelog=l)
522 for p, n, l in pl if n != nullid]
522 for p, n, l in pl if n != nullid]
523
523
524 def p1(self):
524 def p1(self):
525 return self.parents()[0]
525 return self.parents()[0]
526
526
527 def p2(self):
527 def p2(self):
528 p = self.parents()
528 p = self.parents()
529 if len(p) == 2:
529 if len(p) == 2:
530 return p[1]
530 return p[1]
531 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
531 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
532
532
533 def children(self):
533 def children(self):
534 # hard for renames
534 # hard for renames
535 c = self._filelog.children(self._filenode)
535 c = self._filelog.children(self._filenode)
536 return [filectx(self._repo, self._path, fileid=x,
536 return [filectx(self._repo, self._path, fileid=x,
537 filelog=self._filelog) for x in c]
537 filelog=self._filelog) for x in c]
538
538
539 def annotate(self, follow=False, linenumber=None, diffopts=None):
539 def annotate(self, follow=False, linenumber=None, diffopts=None):
540 '''returns a list of tuples of (ctx, line) for each line
540 '''returns a list of tuples of (ctx, line) for each line
541 in the file, where ctx is the filectx of the node where
541 in the file, where ctx is the filectx of the node where
542 that line was last changed.
542 that line was last changed.
543 This returns tuples of ((ctx, linenumber), line) for each line,
543 This returns tuples of ((ctx, linenumber), line) for each line,
544 if "linenumber" parameter is NOT "None".
544 if "linenumber" parameter is NOT "None".
545 In such tuples, linenumber means one at the first appearance
545 In such tuples, linenumber means one at the first appearance
546 in the managed file.
546 in the managed file.
547 To reduce annotation cost,
547 To reduce annotation cost,
548 this returns fixed value(False is used) as linenumber,
548 this returns fixed value(False is used) as linenumber,
549 if "linenumber" parameter is "False".'''
549 if "linenumber" parameter is "False".'''
550
550
551 def decorate_compat(text, rev):
551 def decorate_compat(text, rev):
552 return ([rev] * len(text.splitlines()), text)
552 return ([rev] * len(text.splitlines()), text)
553
553
554 def without_linenumber(text, rev):
554 def without_linenumber(text, rev):
555 return ([(rev, False)] * len(text.splitlines()), text)
555 return ([(rev, False)] * len(text.splitlines()), text)
556
556
557 def with_linenumber(text, rev):
557 def with_linenumber(text, rev):
558 size = len(text.splitlines())
558 size = len(text.splitlines())
559 return ([(rev, i) for i in xrange(1, size + 1)], text)
559 return ([(rev, i) for i in xrange(1, size + 1)], text)
560
560
561 decorate = (((linenumber is None) and decorate_compat) or
561 decorate = (((linenumber is None) and decorate_compat) or
562 (linenumber and with_linenumber) or
562 (linenumber and with_linenumber) or
563 without_linenumber)
563 without_linenumber)
564
564
565 def pair(parent, child):
565 def pair(parent, child):
566 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
566 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
567 refine=True)
567 refine=True)
568 for (a1, a2, b1, b2), t in blocks:
568 for (a1, a2, b1, b2), t in blocks:
569 # Changed blocks ('!') or blocks made only of blank lines ('~')
569 # Changed blocks ('!') or blocks made only of blank lines ('~')
570 # belong to the child.
570 # belong to the child.
571 if t == '=':
571 if t == '=':
572 child[0][b1:b2] = parent[0][a1:a2]
572 child[0][b1:b2] = parent[0][a1:a2]
573 return child
573 return child
574
574
575 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
575 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
576 def getctx(path, fileid):
576 def getctx(path, fileid):
577 log = path == self._path and self._filelog or getlog(path)
577 log = path == self._path and self._filelog or getlog(path)
578 return filectx(self._repo, path, fileid=fileid, filelog=log)
578 return filectx(self._repo, path, fileid=fileid, filelog=log)
579 getctx = util.lrucachefunc(getctx)
579 getctx = util.lrucachefunc(getctx)
580
580
581 def parents(f):
581 def parents(f):
582 # we want to reuse filectx objects as much as possible
582 # we want to reuse filectx objects as much as possible
583 p = f._path
583 p = f._path
584 if f._filerev is None: # working dir
584 if f._filerev is None: # working dir
585 pl = [(n.path(), n.filerev()) for n in f.parents()]
585 pl = [(n.path(), n.filerev()) for n in f.parents()]
586 else:
586 else:
587 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
587 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
588
588
589 if follow:
589 if follow:
590 r = f.renamed()
590 r = f.renamed()
591 if r:
591 if r:
592 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
592 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
593
593
594 return [getctx(p, n) for p, n in pl if n != nullrev]
594 return [getctx(p, n) for p, n in pl if n != nullrev]
595
595
596 # use linkrev to find the first changeset where self appeared
596 # use linkrev to find the first changeset where self appeared
597 if self.rev() != self.linkrev():
597 if self.rev() != self.linkrev():
598 base = self.filectx(self.filerev())
598 base = self.filectx(self.filerev())
599 else:
599 else:
600 base = self
600 base = self
601
601
602 # This algorithm would prefer to be recursive, but Python is a
602 # This algorithm would prefer to be recursive, but Python is a
603 # bit recursion-hostile. Instead we do an iterative
603 # bit recursion-hostile. Instead we do an iterative
604 # depth-first search.
604 # depth-first search.
605
605
606 visit = [base]
606 visit = [base]
607 hist = {}
607 hist = {}
608 pcache = {}
608 pcache = {}
609 needed = {base: 1}
609 needed = {base: 1}
610 while visit:
610 while visit:
611 f = visit[-1]
611 f = visit[-1]
612 if f not in pcache:
612 if f not in pcache:
613 pcache[f] = parents(f)
613 pcache[f] = parents(f)
614
614
615 ready = True
615 ready = True
616 pl = pcache[f]
616 pl = pcache[f]
617 for p in pl:
617 for p in pl:
618 if p not in hist:
618 if p not in hist:
619 ready = False
619 ready = False
620 visit.append(p)
620 visit.append(p)
621 needed[p] = needed.get(p, 0) + 1
621 needed[p] = needed.get(p, 0) + 1
622 if ready:
622 if ready:
623 visit.pop()
623 visit.pop()
624 curr = decorate(f.data(), f)
624 curr = decorate(f.data(), f)
625 for p in pl:
625 for p in pl:
626 curr = pair(hist[p], curr)
626 curr = pair(hist[p], curr)
627 if needed[p] == 1:
627 if needed[p] == 1:
628 del hist[p]
628 del hist[p]
629 else:
629 else:
630 needed[p] -= 1
630 needed[p] -= 1
631
631
632 hist[f] = curr
632 hist[f] = curr
633 pcache[f] = []
633 pcache[f] = []
634
634
635 return zip(hist[base][0], hist[base][1].splitlines(True))
635 return zip(hist[base][0], hist[base][1].splitlines(True))
636
636
637 def ancestor(self, fc2, actx):
637 def ancestor(self, fc2, actx):
638 """
638 """
639 find the common ancestor file context, if any, of self, and fc2
639 find the common ancestor file context, if any, of self, and fc2
640
640
641 actx must be the changectx of the common ancestor
641 actx must be the changectx of the common ancestor
642 of self's and fc2's respective changesets.
642 of self's and fc2's respective changesets.
643 """
643 """
644
644
645 # the easy case: no (relevant) renames
645 # the easy case: no (relevant) renames
646 if fc2.path() == self.path() and self.path() in actx:
646 if fc2.path() == self.path() and self.path() in actx:
647 return actx[self.path()]
647 return actx[self.path()]
648 acache = {}
648
649 # the next easiest cases: unambiguous predecessor (name trumps
650 # history)
651 if self.path() in actx and fc2.path() not in actx:
652 return actx[self.path()]
653 if fc2.path() in actx and self.path() not in actx:
654 return actx[fc2.path()]
649
655
650 # prime the ancestor cache for the working directory
656 # prime the ancestor cache for the working directory
657 acache = {}
651 for c in (self, fc2):
658 for c in (self, fc2):
652 if c._filerev is None:
659 if c._filerev is None:
653 pl = [(n.path(), n.filenode()) for n in c.parents()]
660 pl = [(n.path(), n.filenode()) for n in c.parents()]
654 acache[(c._path, None)] = pl
661 acache[(c._path, None)] = pl
655
662
656 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
663 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
657 def parents(vertex):
664 def parents(vertex):
658 if vertex in acache:
665 if vertex in acache:
659 return acache[vertex]
666 return acache[vertex]
660 f, n = vertex
667 f, n = vertex
661 if f not in flcache:
668 if f not in flcache:
662 flcache[f] = self._repo.file(f)
669 flcache[f] = self._repo.file(f)
663 fl = flcache[f]
670 fl = flcache[f]
664 pl = [(f, p) for p in fl.parents(n) if p != nullid]
671 pl = [(f, p) for p in fl.parents(n) if p != nullid]
665 re = fl.renamed(n)
672 re = fl.renamed(n)
666 if re:
673 if re:
667 pl.append(re)
674 pl.append(re)
668 acache[vertex] = pl
675 acache[vertex] = pl
669 return pl
676 return pl
670
677
671 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
678 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
672 v = ancestor.ancestor(a, b, parents)
679 v = ancestor.ancestor(a, b, parents)
673 if v:
680 if v:
674 f, n = v
681 f, n = v
675 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
682 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
676
683
677 return None
684 return None
678
685
679 def ancestors(self, followfirst=False):
686 def ancestors(self, followfirst=False):
680 visit = {}
687 visit = {}
681 c = self
688 c = self
682 cut = followfirst and 1 or None
689 cut = followfirst and 1 or None
683 while True:
690 while True:
684 for parent in c.parents()[:cut]:
691 for parent in c.parents()[:cut]:
685 visit[(parent.rev(), parent.node())] = parent
692 visit[(parent.rev(), parent.node())] = parent
686 if not visit:
693 if not visit:
687 break
694 break
688 c = visit.pop(max(visit))
695 c = visit.pop(max(visit))
689 yield c
696 yield c
690
697
691 class workingctx(changectx):
698 class workingctx(changectx):
692 """A workingctx object makes access to data related to
699 """A workingctx object makes access to data related to
693 the current working directory convenient.
700 the current working directory convenient.
694 date - any valid date string or (unixtime, offset), or None.
701 date - any valid date string or (unixtime, offset), or None.
695 user - username string, or None.
702 user - username string, or None.
696 extra - a dictionary of extra values, or None.
703 extra - a dictionary of extra values, or None.
697 changes - a list of file lists as returned by localrepo.status()
704 changes - a list of file lists as returned by localrepo.status()
698 or None to use the repository status.
705 or None to use the repository status.
699 """
706 """
700 def __init__(self, repo, text="", user=None, date=None, extra=None,
707 def __init__(self, repo, text="", user=None, date=None, extra=None,
701 changes=None):
708 changes=None):
702 self._repo = repo
709 self._repo = repo
703 self._rev = None
710 self._rev = None
704 self._node = None
711 self._node = None
705 self._text = text
712 self._text = text
706 if date:
713 if date:
707 self._date = util.parsedate(date)
714 self._date = util.parsedate(date)
708 if user:
715 if user:
709 self._user = user
716 self._user = user
710 if changes:
717 if changes:
711 self._status = list(changes[:4])
718 self._status = list(changes[:4])
712 self._unknown = changes[4]
719 self._unknown = changes[4]
713 self._ignored = changes[5]
720 self._ignored = changes[5]
714 self._clean = changes[6]
721 self._clean = changes[6]
715 else:
722 else:
716 self._unknown = None
723 self._unknown = None
717 self._ignored = None
724 self._ignored = None
718 self._clean = None
725 self._clean = None
719
726
720 self._extra = {}
727 self._extra = {}
721 if extra:
728 if extra:
722 self._extra = extra.copy()
729 self._extra = extra.copy()
723 if 'branch' not in self._extra:
730 if 'branch' not in self._extra:
724 try:
731 try:
725 branch = encoding.fromlocal(self._repo.dirstate.branch())
732 branch = encoding.fromlocal(self._repo.dirstate.branch())
726 except UnicodeDecodeError:
733 except UnicodeDecodeError:
727 raise util.Abort(_('branch name not in UTF-8!'))
734 raise util.Abort(_('branch name not in UTF-8!'))
728 self._extra['branch'] = branch
735 self._extra['branch'] = branch
729 if self._extra['branch'] == '':
736 if self._extra['branch'] == '':
730 self._extra['branch'] = 'default'
737 self._extra['branch'] = 'default'
731
738
732 def __str__(self):
739 def __str__(self):
733 return str(self._parents[0]) + "+"
740 return str(self._parents[0]) + "+"
734
741
735 def __repr__(self):
742 def __repr__(self):
736 return "<workingctx %s>" % str(self)
743 return "<workingctx %s>" % str(self)
737
744
738 def __nonzero__(self):
745 def __nonzero__(self):
739 return True
746 return True
740
747
741 def __contains__(self, key):
748 def __contains__(self, key):
742 return self._repo.dirstate[key] not in "?r"
749 return self._repo.dirstate[key] not in "?r"
743
750
744 def _buildflagfunc(self):
751 def _buildflagfunc(self):
745 # Create a fallback function for getting file flags when the
752 # Create a fallback function for getting file flags when the
746 # filesystem doesn't support them
753 # filesystem doesn't support them
747
754
748 copiesget = self._repo.dirstate.copies().get
755 copiesget = self._repo.dirstate.copies().get
749
756
750 if len(self._parents) < 2:
757 if len(self._parents) < 2:
751 # when we have one parent, it's easy: copy from parent
758 # when we have one parent, it's easy: copy from parent
752 man = self._parents[0].manifest()
759 man = self._parents[0].manifest()
753 def func(f):
760 def func(f):
754 f = copiesget(f, f)
761 f = copiesget(f, f)
755 return man.flags(f)
762 return man.flags(f)
756 else:
763 else:
757 # merges are tricky: we try to reconstruct the unstored
764 # merges are tricky: we try to reconstruct the unstored
758 # result from the merge (issue1802)
765 # result from the merge (issue1802)
759 p1, p2 = self._parents
766 p1, p2 = self._parents
760 pa = p1.ancestor(p2)
767 pa = p1.ancestor(p2)
761 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
768 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
762
769
763 def func(f):
770 def func(f):
764 f = copiesget(f, f) # may be wrong for merges with copies
771 f = copiesget(f, f) # may be wrong for merges with copies
765 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
772 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
766 if fl1 == fl2:
773 if fl1 == fl2:
767 return fl1
774 return fl1
768 if fl1 == fla:
775 if fl1 == fla:
769 return fl2
776 return fl2
770 if fl2 == fla:
777 if fl2 == fla:
771 return fl1
778 return fl1
772 return '' # punt for conflicts
779 return '' # punt for conflicts
773
780
774 return func
781 return func
775
782
776 @propertycache
783 @propertycache
777 def _flagfunc(self):
784 def _flagfunc(self):
778 return self._repo.dirstate.flagfunc(self._buildflagfunc)
785 return self._repo.dirstate.flagfunc(self._buildflagfunc)
779
786
780 @propertycache
787 @propertycache
781 def _manifest(self):
788 def _manifest(self):
782 """generate a manifest corresponding to the working directory"""
789 """generate a manifest corresponding to the working directory"""
783
790
784 man = self._parents[0].manifest().copy()
791 man = self._parents[0].manifest().copy()
785 if len(self._parents) > 1:
792 if len(self._parents) > 1:
786 man2 = self.p2().manifest()
793 man2 = self.p2().manifest()
787 def getman(f):
794 def getman(f):
788 if f in man:
795 if f in man:
789 return man
796 return man
790 return man2
797 return man2
791 else:
798 else:
792 getman = lambda f: man
799 getman = lambda f: man
793
800
794 copied = self._repo.dirstate.copies()
801 copied = self._repo.dirstate.copies()
795 ff = self._flagfunc
802 ff = self._flagfunc
796 modified, added, removed, deleted = self._status
803 modified, added, removed, deleted = self._status
797 for i, l in (("a", added), ("m", modified)):
804 for i, l in (("a", added), ("m", modified)):
798 for f in l:
805 for f in l:
799 orig = copied.get(f, f)
806 orig = copied.get(f, f)
800 man[f] = getman(orig).get(orig, nullid) + i
807 man[f] = getman(orig).get(orig, nullid) + i
801 try:
808 try:
802 man.set(f, ff(f))
809 man.set(f, ff(f))
803 except OSError:
810 except OSError:
804 pass
811 pass
805
812
806 for f in deleted + removed:
813 for f in deleted + removed:
807 if f in man:
814 if f in man:
808 del man[f]
815 del man[f]
809
816
810 return man
817 return man
811
818
812 def __iter__(self):
819 def __iter__(self):
813 d = self._repo.dirstate
820 d = self._repo.dirstate
814 for f in d:
821 for f in d:
815 if d[f] != 'r':
822 if d[f] != 'r':
816 yield f
823 yield f
817
824
818 @propertycache
825 @propertycache
819 def _status(self):
826 def _status(self):
820 return self._repo.status()[:4]
827 return self._repo.status()[:4]
821
828
822 @propertycache
829 @propertycache
823 def _user(self):
830 def _user(self):
824 return self._repo.ui.username()
831 return self._repo.ui.username()
825
832
826 @propertycache
833 @propertycache
827 def _date(self):
834 def _date(self):
828 return util.makedate()
835 return util.makedate()
829
836
830 @propertycache
837 @propertycache
831 def _parents(self):
838 def _parents(self):
832 p = self._repo.dirstate.parents()
839 p = self._repo.dirstate.parents()
833 if p[1] == nullid:
840 if p[1] == nullid:
834 p = p[:-1]
841 p = p[:-1]
835 self._parents = [changectx(self._repo, x) for x in p]
842 self._parents = [changectx(self._repo, x) for x in p]
836 return self._parents
843 return self._parents
837
844
838 def status(self, ignored=False, clean=False, unknown=False):
845 def status(self, ignored=False, clean=False, unknown=False):
839 """Explicit status query
846 """Explicit status query
840 Unless this method is used to query the working copy status, the
847 Unless this method is used to query the working copy status, the
841 _status property will implicitly read the status using its default
848 _status property will implicitly read the status using its default
842 arguments."""
849 arguments."""
843 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
850 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
844 self._unknown = self._ignored = self._clean = None
851 self._unknown = self._ignored = self._clean = None
845 if unknown:
852 if unknown:
846 self._unknown = stat[4]
853 self._unknown = stat[4]
847 if ignored:
854 if ignored:
848 self._ignored = stat[5]
855 self._ignored = stat[5]
849 if clean:
856 if clean:
850 self._clean = stat[6]
857 self._clean = stat[6]
851 self._status = stat[:4]
858 self._status = stat[:4]
852 return stat
859 return stat
853
860
854 def manifest(self):
861 def manifest(self):
855 return self._manifest
862 return self._manifest
856 def user(self):
863 def user(self):
857 return self._user or self._repo.ui.username()
864 return self._user or self._repo.ui.username()
858 def date(self):
865 def date(self):
859 return self._date
866 return self._date
860 def description(self):
867 def description(self):
861 return self._text
868 return self._text
862 def files(self):
869 def files(self):
863 return sorted(self._status[0] + self._status[1] + self._status[2])
870 return sorted(self._status[0] + self._status[1] + self._status[2])
864
871
865 def modified(self):
872 def modified(self):
866 return self._status[0]
873 return self._status[0]
867 def added(self):
874 def added(self):
868 return self._status[1]
875 return self._status[1]
869 def removed(self):
876 def removed(self):
870 return self._status[2]
877 return self._status[2]
871 def deleted(self):
878 def deleted(self):
872 return self._status[3]
879 return self._status[3]
873 def unknown(self):
880 def unknown(self):
874 assert self._unknown is not None # must call status first
881 assert self._unknown is not None # must call status first
875 return self._unknown
882 return self._unknown
876 def ignored(self):
883 def ignored(self):
877 assert self._ignored is not None # must call status first
884 assert self._ignored is not None # must call status first
878 return self._ignored
885 return self._ignored
879 def clean(self):
886 def clean(self):
880 assert self._clean is not None # must call status first
887 assert self._clean is not None # must call status first
881 return self._clean
888 return self._clean
882 def branch(self):
889 def branch(self):
883 return encoding.tolocal(self._extra['branch'])
890 return encoding.tolocal(self._extra['branch'])
884 def extra(self):
891 def extra(self):
885 return self._extra
892 return self._extra
886
893
887 def tags(self):
894 def tags(self):
888 t = []
895 t = []
889 for p in self.parents():
896 for p in self.parents():
890 t.extend(p.tags())
897 t.extend(p.tags())
891 return t
898 return t
892
899
893 def bookmarks(self):
900 def bookmarks(self):
894 b = []
901 b = []
895 for p in self.parents():
902 for p in self.parents():
896 b.extend(p.bookmarks())
903 b.extend(p.bookmarks())
897 return b
904 return b
898
905
899 def phase(self):
906 def phase(self):
900 phase = phases.draft # default phase to draft
907 phase = phases.draft # default phase to draft
901 for p in self.parents():
908 for p in self.parents():
902 phase = max(phase, p.phase())
909 phase = max(phase, p.phase())
903 return phase
910 return phase
904
911
905 def hidden(self):
912 def hidden(self):
906 return False
913 return False
907
914
908 def children(self):
915 def children(self):
909 return []
916 return []
910
917
911 def flags(self, path):
918 def flags(self, path):
912 if '_manifest' in self.__dict__:
919 if '_manifest' in self.__dict__:
913 try:
920 try:
914 return self._manifest.flags(path)
921 return self._manifest.flags(path)
915 except KeyError:
922 except KeyError:
916 return ''
923 return ''
917
924
918 try:
925 try:
919 return self._flagfunc(path)
926 return self._flagfunc(path)
920 except OSError:
927 except OSError:
921 return ''
928 return ''
922
929
923 def filectx(self, path, filelog=None):
930 def filectx(self, path, filelog=None):
924 """get a file context from the working directory"""
931 """get a file context from the working directory"""
925 return workingfilectx(self._repo, path, workingctx=self,
932 return workingfilectx(self._repo, path, workingctx=self,
926 filelog=filelog)
933 filelog=filelog)
927
934
928 def ancestor(self, c2):
935 def ancestor(self, c2):
929 """return the ancestor context of self and c2"""
936 """return the ancestor context of self and c2"""
930 return self._parents[0].ancestor(c2) # punt on two parents for now
937 return self._parents[0].ancestor(c2) # punt on two parents for now
931
938
932 def walk(self, match):
939 def walk(self, match):
933 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
940 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
934 True, False))
941 True, False))
935
942
936 def dirty(self, missing=False, merge=True, branch=True):
943 def dirty(self, missing=False, merge=True, branch=True):
937 "check whether a working directory is modified"
944 "check whether a working directory is modified"
938 # check subrepos first
945 # check subrepos first
939 for s in self.substate:
946 for s in self.substate:
940 if self.sub(s).dirty():
947 if self.sub(s).dirty():
941 return True
948 return True
942 # check current working dir
949 # check current working dir
943 return ((merge and self.p2()) or
950 return ((merge and self.p2()) or
944 (branch and self.branch() != self.p1().branch()) or
951 (branch and self.branch() != self.p1().branch()) or
945 self.modified() or self.added() or self.removed() or
952 self.modified() or self.added() or self.removed() or
946 (missing and self.deleted()))
953 (missing and self.deleted()))
947
954
948 def add(self, list, prefix=""):
955 def add(self, list, prefix=""):
949 join = lambda f: os.path.join(prefix, f)
956 join = lambda f: os.path.join(prefix, f)
950 wlock = self._repo.wlock()
957 wlock = self._repo.wlock()
951 ui, ds = self._repo.ui, self._repo.dirstate
958 ui, ds = self._repo.ui, self._repo.dirstate
952 try:
959 try:
953 rejected = []
960 rejected = []
954 for f in list:
961 for f in list:
955 scmutil.checkportable(ui, join(f))
962 scmutil.checkportable(ui, join(f))
956 p = self._repo.wjoin(f)
963 p = self._repo.wjoin(f)
957 try:
964 try:
958 st = os.lstat(p)
965 st = os.lstat(p)
959 except OSError:
966 except OSError:
960 ui.warn(_("%s does not exist!\n") % join(f))
967 ui.warn(_("%s does not exist!\n") % join(f))
961 rejected.append(f)
968 rejected.append(f)
962 continue
969 continue
963 if st.st_size > 10000000:
970 if st.st_size > 10000000:
964 ui.warn(_("%s: up to %d MB of RAM may be required "
971 ui.warn(_("%s: up to %d MB of RAM may be required "
965 "to manage this file\n"
972 "to manage this file\n"
966 "(use 'hg revert %s' to cancel the "
973 "(use 'hg revert %s' to cancel the "
967 "pending addition)\n")
974 "pending addition)\n")
968 % (f, 3 * st.st_size // 1000000, join(f)))
975 % (f, 3 * st.st_size // 1000000, join(f)))
969 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
976 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
970 ui.warn(_("%s not added: only files and symlinks "
977 ui.warn(_("%s not added: only files and symlinks "
971 "supported currently\n") % join(f))
978 "supported currently\n") % join(f))
972 rejected.append(p)
979 rejected.append(p)
973 elif ds[f] in 'amn':
980 elif ds[f] in 'amn':
974 ui.warn(_("%s already tracked!\n") % join(f))
981 ui.warn(_("%s already tracked!\n") % join(f))
975 elif ds[f] == 'r':
982 elif ds[f] == 'r':
976 ds.normallookup(f)
983 ds.normallookup(f)
977 else:
984 else:
978 ds.add(f)
985 ds.add(f)
979 return rejected
986 return rejected
980 finally:
987 finally:
981 wlock.release()
988 wlock.release()
982
989
983 def forget(self, files, prefix=""):
990 def forget(self, files, prefix=""):
984 join = lambda f: os.path.join(prefix, f)
991 join = lambda f: os.path.join(prefix, f)
985 wlock = self._repo.wlock()
992 wlock = self._repo.wlock()
986 try:
993 try:
987 rejected = []
994 rejected = []
988 for f in files:
995 for f in files:
989 if f not in self._repo.dirstate:
996 if f not in self._repo.dirstate:
990 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
997 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
991 rejected.append(f)
998 rejected.append(f)
992 elif self._repo.dirstate[f] != 'a':
999 elif self._repo.dirstate[f] != 'a':
993 self._repo.dirstate.remove(f)
1000 self._repo.dirstate.remove(f)
994 else:
1001 else:
995 self._repo.dirstate.drop(f)
1002 self._repo.dirstate.drop(f)
996 return rejected
1003 return rejected
997 finally:
1004 finally:
998 wlock.release()
1005 wlock.release()
999
1006
1000 def ancestors(self):
1007 def ancestors(self):
1001 for a in self._repo.changelog.ancestors(
1008 for a in self._repo.changelog.ancestors(
1002 *[p.rev() for p in self._parents]):
1009 *[p.rev() for p in self._parents]):
1003 yield changectx(self._repo, a)
1010 yield changectx(self._repo, a)
1004
1011
1005 def undelete(self, list):
1012 def undelete(self, list):
1006 pctxs = self.parents()
1013 pctxs = self.parents()
1007 wlock = self._repo.wlock()
1014 wlock = self._repo.wlock()
1008 try:
1015 try:
1009 for f in list:
1016 for f in list:
1010 if self._repo.dirstate[f] != 'r':
1017 if self._repo.dirstate[f] != 'r':
1011 self._repo.ui.warn(_("%s not removed!\n") % f)
1018 self._repo.ui.warn(_("%s not removed!\n") % f)
1012 else:
1019 else:
1013 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1020 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1014 t = fctx.data()
1021 t = fctx.data()
1015 self._repo.wwrite(f, t, fctx.flags())
1022 self._repo.wwrite(f, t, fctx.flags())
1016 self._repo.dirstate.normal(f)
1023 self._repo.dirstate.normal(f)
1017 finally:
1024 finally:
1018 wlock.release()
1025 wlock.release()
1019
1026
1020 def copy(self, source, dest):
1027 def copy(self, source, dest):
1021 p = self._repo.wjoin(dest)
1028 p = self._repo.wjoin(dest)
1022 if not os.path.lexists(p):
1029 if not os.path.lexists(p):
1023 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1030 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1024 elif not (os.path.isfile(p) or os.path.islink(p)):
1031 elif not (os.path.isfile(p) or os.path.islink(p)):
1025 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1032 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1026 "symbolic link\n") % dest)
1033 "symbolic link\n") % dest)
1027 else:
1034 else:
1028 wlock = self._repo.wlock()
1035 wlock = self._repo.wlock()
1029 try:
1036 try:
1030 if self._repo.dirstate[dest] in '?r':
1037 if self._repo.dirstate[dest] in '?r':
1031 self._repo.dirstate.add(dest)
1038 self._repo.dirstate.add(dest)
1032 self._repo.dirstate.copy(source, dest)
1039 self._repo.dirstate.copy(source, dest)
1033 finally:
1040 finally:
1034 wlock.release()
1041 wlock.release()
1035
1042
1036 def dirs(self):
1043 def dirs(self):
1037 return self._repo.dirstate.dirs()
1044 return self._repo.dirstate.dirs()
1038
1045
1039 class workingfilectx(filectx):
1046 class workingfilectx(filectx):
1040 """A workingfilectx object makes access to data related to a particular
1047 """A workingfilectx object makes access to data related to a particular
1041 file in the working directory convenient."""
1048 file in the working directory convenient."""
1042 def __init__(self, repo, path, filelog=None, workingctx=None):
1049 def __init__(self, repo, path, filelog=None, workingctx=None):
1043 """changeid can be a changeset revision, node, or tag.
1050 """changeid can be a changeset revision, node, or tag.
1044 fileid can be a file revision or node."""
1051 fileid can be a file revision or node."""
1045 self._repo = repo
1052 self._repo = repo
1046 self._path = path
1053 self._path = path
1047 self._changeid = None
1054 self._changeid = None
1048 self._filerev = self._filenode = None
1055 self._filerev = self._filenode = None
1049
1056
1050 if filelog:
1057 if filelog:
1051 self._filelog = filelog
1058 self._filelog = filelog
1052 if workingctx:
1059 if workingctx:
1053 self._changectx = workingctx
1060 self._changectx = workingctx
1054
1061
1055 @propertycache
1062 @propertycache
1056 def _changectx(self):
1063 def _changectx(self):
1057 return workingctx(self._repo)
1064 return workingctx(self._repo)
1058
1065
1059 def __nonzero__(self):
1066 def __nonzero__(self):
1060 return True
1067 return True
1061
1068
1062 def __str__(self):
1069 def __str__(self):
1063 return "%s@%s" % (self.path(), self._changectx)
1070 return "%s@%s" % (self.path(), self._changectx)
1064
1071
1065 def __repr__(self):
1072 def __repr__(self):
1066 return "<workingfilectx %s>" % str(self)
1073 return "<workingfilectx %s>" % str(self)
1067
1074
1068 def data(self):
1075 def data(self):
1069 return self._repo.wread(self._path)
1076 return self._repo.wread(self._path)
1070 def renamed(self):
1077 def renamed(self):
1071 rp = self._repo.dirstate.copied(self._path)
1078 rp = self._repo.dirstate.copied(self._path)
1072 if not rp:
1079 if not rp:
1073 return None
1080 return None
1074 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1081 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1075
1082
1076 def parents(self):
1083 def parents(self):
1077 '''return parent filectxs, following copies if necessary'''
1084 '''return parent filectxs, following copies if necessary'''
1078 def filenode(ctx, path):
1085 def filenode(ctx, path):
1079 return ctx._manifest.get(path, nullid)
1086 return ctx._manifest.get(path, nullid)
1080
1087
1081 path = self._path
1088 path = self._path
1082 fl = self._filelog
1089 fl = self._filelog
1083 pcl = self._changectx._parents
1090 pcl = self._changectx._parents
1084 renamed = self.renamed()
1091 renamed = self.renamed()
1085
1092
1086 if renamed:
1093 if renamed:
1087 pl = [renamed + (None,)]
1094 pl = [renamed + (None,)]
1088 else:
1095 else:
1089 pl = [(path, filenode(pcl[0], path), fl)]
1096 pl = [(path, filenode(pcl[0], path), fl)]
1090
1097
1091 for pc in pcl[1:]:
1098 for pc in pcl[1:]:
1092 pl.append((path, filenode(pc, path), fl))
1099 pl.append((path, filenode(pc, path), fl))
1093
1100
1094 return [filectx(self._repo, p, fileid=n, filelog=l)
1101 return [filectx(self._repo, p, fileid=n, filelog=l)
1095 for p, n, l in pl if n != nullid]
1102 for p, n, l in pl if n != nullid]
1096
1103
1097 def children(self):
1104 def children(self):
1098 return []
1105 return []
1099
1106
1100 def size(self):
1107 def size(self):
1101 return os.lstat(self._repo.wjoin(self._path)).st_size
1108 return os.lstat(self._repo.wjoin(self._path)).st_size
1102 def date(self):
1109 def date(self):
1103 t, tz = self._changectx.date()
1110 t, tz = self._changectx.date()
1104 try:
1111 try:
1105 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
1112 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
1106 except OSError, err:
1113 except OSError, err:
1107 if err.errno != errno.ENOENT:
1114 if err.errno != errno.ENOENT:
1108 raise
1115 raise
1109 return (t, tz)
1116 return (t, tz)
1110
1117
1111 def cmp(self, fctx):
1118 def cmp(self, fctx):
1112 """compare with other file context
1119 """compare with other file context
1113
1120
1114 returns True if different than fctx.
1121 returns True if different than fctx.
1115 """
1122 """
1116 # fctx should be a filectx (not a wfctx)
1123 # fctx should be a filectx (not a wfctx)
1117 # invert comparison to reuse the same code path
1124 # invert comparison to reuse the same code path
1118 return fctx.cmp(self)
1125 return fctx.cmp(self)
1119
1126
1120 class memctx(object):
1127 class memctx(object):
1121 """Use memctx to perform in-memory commits via localrepo.commitctx().
1128 """Use memctx to perform in-memory commits via localrepo.commitctx().
1122
1129
1123 Revision information is supplied at initialization time while
1130 Revision information is supplied at initialization time while
1124 related files data and is made available through a callback
1131 related files data and is made available through a callback
1125 mechanism. 'repo' is the current localrepo, 'parents' is a
1132 mechanism. 'repo' is the current localrepo, 'parents' is a
1126 sequence of two parent revisions identifiers (pass None for every
1133 sequence of two parent revisions identifiers (pass None for every
1127 missing parent), 'text' is the commit message and 'files' lists
1134 missing parent), 'text' is the commit message and 'files' lists
1128 names of files touched by the revision (normalized and relative to
1135 names of files touched by the revision (normalized and relative to
1129 repository root).
1136 repository root).
1130
1137
1131 filectxfn(repo, memctx, path) is a callable receiving the
1138 filectxfn(repo, memctx, path) is a callable receiving the
1132 repository, the current memctx object and the normalized path of
1139 repository, the current memctx object and the normalized path of
1133 requested file, relative to repository root. It is fired by the
1140 requested file, relative to repository root. It is fired by the
1134 commit function for every file in 'files', but calls order is
1141 commit function for every file in 'files', but calls order is
1135 undefined. If the file is available in the revision being
1142 undefined. If the file is available in the revision being
1136 committed (updated or added), filectxfn returns a memfilectx
1143 committed (updated or added), filectxfn returns a memfilectx
1137 object. If the file was removed, filectxfn raises an
1144 object. If the file was removed, filectxfn raises an
1138 IOError. Moved files are represented by marking the source file
1145 IOError. Moved files are represented by marking the source file
1139 removed and the new file added with copy information (see
1146 removed and the new file added with copy information (see
1140 memfilectx).
1147 memfilectx).
1141
1148
1142 user receives the committer name and defaults to current
1149 user receives the committer name and defaults to current
1143 repository username, date is the commit date in any format
1150 repository username, date is the commit date in any format
1144 supported by util.parsedate() and defaults to current date, extra
1151 supported by util.parsedate() and defaults to current date, extra
1145 is a dictionary of metadata or is left empty.
1152 is a dictionary of metadata or is left empty.
1146 """
1153 """
1147 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1154 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1148 date=None, extra=None):
1155 date=None, extra=None):
1149 self._repo = repo
1156 self._repo = repo
1150 self._rev = None
1157 self._rev = None
1151 self._node = None
1158 self._node = None
1152 self._text = text
1159 self._text = text
1153 self._date = date and util.parsedate(date) or util.makedate()
1160 self._date = date and util.parsedate(date) or util.makedate()
1154 self._user = user
1161 self._user = user
1155 parents = [(p or nullid) for p in parents]
1162 parents = [(p or nullid) for p in parents]
1156 p1, p2 = parents
1163 p1, p2 = parents
1157 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1164 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1158 files = sorted(set(files))
1165 files = sorted(set(files))
1159 self._status = [files, [], [], [], []]
1166 self._status = [files, [], [], [], []]
1160 self._filectxfn = filectxfn
1167 self._filectxfn = filectxfn
1161
1168
1162 self._extra = extra and extra.copy() or {}
1169 self._extra = extra and extra.copy() or {}
1163 if self._extra.get('branch', '') == '':
1170 if self._extra.get('branch', '') == '':
1164 self._extra['branch'] = 'default'
1171 self._extra['branch'] = 'default'
1165
1172
1166 def __str__(self):
1173 def __str__(self):
1167 return str(self._parents[0]) + "+"
1174 return str(self._parents[0]) + "+"
1168
1175
1169 def __int__(self):
1176 def __int__(self):
1170 return self._rev
1177 return self._rev
1171
1178
1172 def __nonzero__(self):
1179 def __nonzero__(self):
1173 return True
1180 return True
1174
1181
1175 def __getitem__(self, key):
1182 def __getitem__(self, key):
1176 return self.filectx(key)
1183 return self.filectx(key)
1177
1184
1178 def p1(self):
1185 def p1(self):
1179 return self._parents[0]
1186 return self._parents[0]
1180 def p2(self):
1187 def p2(self):
1181 return self._parents[1]
1188 return self._parents[1]
1182
1189
1183 def user(self):
1190 def user(self):
1184 return self._user or self._repo.ui.username()
1191 return self._user or self._repo.ui.username()
1185 def date(self):
1192 def date(self):
1186 return self._date
1193 return self._date
1187 def description(self):
1194 def description(self):
1188 return self._text
1195 return self._text
1189 def files(self):
1196 def files(self):
1190 return self.modified()
1197 return self.modified()
1191 def modified(self):
1198 def modified(self):
1192 return self._status[0]
1199 return self._status[0]
1193 def added(self):
1200 def added(self):
1194 return self._status[1]
1201 return self._status[1]
1195 def removed(self):
1202 def removed(self):
1196 return self._status[2]
1203 return self._status[2]
1197 def deleted(self):
1204 def deleted(self):
1198 return self._status[3]
1205 return self._status[3]
1199 def unknown(self):
1206 def unknown(self):
1200 return self._status[4]
1207 return self._status[4]
1201 def ignored(self):
1208 def ignored(self):
1202 return self._status[5]
1209 return self._status[5]
1203 def clean(self):
1210 def clean(self):
1204 return self._status[6]
1211 return self._status[6]
1205 def branch(self):
1212 def branch(self):
1206 return encoding.tolocal(self._extra['branch'])
1213 return encoding.tolocal(self._extra['branch'])
1207 def extra(self):
1214 def extra(self):
1208 return self._extra
1215 return self._extra
1209 def flags(self, f):
1216 def flags(self, f):
1210 return self[f].flags()
1217 return self[f].flags()
1211
1218
1212 def parents(self):
1219 def parents(self):
1213 """return contexts for each parent changeset"""
1220 """return contexts for each parent changeset"""
1214 return self._parents
1221 return self._parents
1215
1222
1216 def filectx(self, path, filelog=None):
1223 def filectx(self, path, filelog=None):
1217 """get a file context from the working directory"""
1224 """get a file context from the working directory"""
1218 return self._filectxfn(self._repo, self, path)
1225 return self._filectxfn(self._repo, self, path)
1219
1226
1220 def commit(self):
1227 def commit(self):
1221 """commit context to the repo"""
1228 """commit context to the repo"""
1222 return self._repo.commitctx(self)
1229 return self._repo.commitctx(self)
1223
1230
1224 class memfilectx(object):
1231 class memfilectx(object):
1225 """memfilectx represents an in-memory file to commit.
1232 """memfilectx represents an in-memory file to commit.
1226
1233
1227 See memctx for more details.
1234 See memctx for more details.
1228 """
1235 """
1229 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1236 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1230 """
1237 """
1231 path is the normalized file path relative to repository root.
1238 path is the normalized file path relative to repository root.
1232 data is the file content as a string.
1239 data is the file content as a string.
1233 islink is True if the file is a symbolic link.
1240 islink is True if the file is a symbolic link.
1234 isexec is True if the file is executable.
1241 isexec is True if the file is executable.
1235 copied is the source file path if current file was copied in the
1242 copied is the source file path if current file was copied in the
1236 revision being committed, or None."""
1243 revision being committed, or None."""
1237 self._path = path
1244 self._path = path
1238 self._data = data
1245 self._data = data
1239 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1246 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1240 self._copied = None
1247 self._copied = None
1241 if copied:
1248 if copied:
1242 self._copied = (copied, nullid)
1249 self._copied = (copied, nullid)
1243
1250
1244 def __nonzero__(self):
1251 def __nonzero__(self):
1245 return True
1252 return True
1246 def __str__(self):
1253 def __str__(self):
1247 return "%s@%s" % (self.path(), self._changectx)
1254 return "%s@%s" % (self.path(), self._changectx)
1248 def path(self):
1255 def path(self):
1249 return self._path
1256 return self._path
1250 def data(self):
1257 def data(self):
1251 return self._data
1258 return self._data
1252 def flags(self):
1259 def flags(self):
1253 return self._flags
1260 return self._flags
1254 def isexec(self):
1261 def isexec(self):
1255 return 'x' in self._flags
1262 return 'x' in self._flags
1256 def islink(self):
1263 def islink(self):
1257 return 'l' in self._flags
1264 return 'l' in self._flags
1258 def renamed(self):
1265 def renamed(self):
1259 return self._copied
1266 return self._copied
@@ -1,278 +1,279 b''
1 Create a repo with some stuff in it:
1 Create a repo with some stuff in it:
2
2
3 $ hg init a
3 $ hg init a
4 $ cd a
4 $ cd a
5 $ echo a > a
5 $ echo a > a
6 $ echo a > d
6 $ echo a > d
7 $ echo a > e
7 $ echo a > e
8 $ hg ci -qAm0
8 $ hg ci -qAm0
9 $ echo b > a
9 $ echo b > a
10 $ hg ci -m1 -u bar
10 $ hg ci -m1 -u bar
11 $ hg mv a b
11 $ hg mv a b
12 $ hg ci -m2
12 $ hg ci -m2
13 $ hg cp b c
13 $ hg cp b c
14 $ hg ci -m3 -u baz
14 $ hg ci -m3 -u baz
15 $ echo b > d
15 $ echo b > d
16 $ echo f > e
16 $ echo f > e
17 $ hg ci -m4
17 $ hg ci -m4
18 $ hg up -q 3
18 $ hg up -q 3
19 $ echo b > e
19 $ echo b > e
20 $ hg branch -q stable
20 $ hg branch -q stable
21 $ hg ci -m5
21 $ hg ci -m5
22 $ hg merge -q default --tool internal:local
22 $ hg merge -q default --tool internal:local
23 $ hg branch -q default
23 $ hg branch -q default
24 $ hg ci -m6
24 $ hg ci -m6
25 $ hg phase --public 3
25 $ hg phase --public 3
26 $ hg phase --force --secret 6
26 $ hg phase --force --secret 6
27
27
28 $ hg --config extensions.graphlog= log -G --template '{author}@{rev}.{phase}: {desc}\n'
28 $ hg --config extensions.graphlog= log -G --template '{author}@{rev}.{phase}: {desc}\n'
29 @ test@6.secret: 6
29 @ test@6.secret: 6
30 |\
30 |\
31 | o test@5.draft: 5
31 | o test@5.draft: 5
32 | |
32 | |
33 o | test@4.draft: 4
33 o | test@4.draft: 4
34 |/
34 |/
35 o baz@3.public: 3
35 o baz@3.public: 3
36 |
36 |
37 o test@2.public: 2
37 o test@2.public: 2
38 |
38 |
39 o bar@1.public: 1
39 o bar@1.public: 1
40 |
40 |
41 o test@0.public: 0
41 o test@0.public: 0
42
42
43
43
44 Need to specify a rev:
44 Need to specify a rev:
45
45
46 $ hg graft
46 $ hg graft
47 abort: no revisions specified
47 abort: no revisions specified
48 [255]
48 [255]
49
49
50 Can't graft ancestor:
50 Can't graft ancestor:
51
51
52 $ hg graft 1 2
52 $ hg graft 1 2
53 skipping ancestor revision 1
53 skipping ancestor revision 1
54 skipping ancestor revision 2
54 skipping ancestor revision 2
55 [255]
55 [255]
56
56
57 Can't graft with dirty wd:
57 Can't graft with dirty wd:
58
58
59 $ hg up -q 0
59 $ hg up -q 0
60 $ echo foo > a
60 $ echo foo > a
61 $ hg graft 1
61 $ hg graft 1
62 abort: outstanding uncommitted changes
62 abort: outstanding uncommitted changes
63 [255]
63 [255]
64 $ hg revert a
64 $ hg revert a
65
65
66 Graft a rename:
66 Graft a rename:
67
67
68 $ hg graft 2 -u foo
68 $ hg graft 2 -u foo
69 grafting revision 2
69 grafting revision 2
70 merging a and b to b
70 merging a and b to b
71 $ hg export tip --git
71 $ hg export tip --git
72 # HG changeset patch
72 # HG changeset patch
73 # User foo
73 # User foo
74 # Date 0 0
74 # Date 0 0
75 # Node ID d2e44c99fd3f31c176ea4efb9eca9f6306c81756
75 # Node ID ef0ef43d49e79e81ddafdc7997401ba0041efc82
76 # Parent 68795b066622ca79a25816a662041d8f78f3cd9e
76 # Parent 68795b066622ca79a25816a662041d8f78f3cd9e
77 2
77 2
78
78
79 diff --git a/a b/b
79 diff --git a/a b/b
80 rename from a
80 rename from a
81 rename to b
81 rename to b
82 --- a/a
83 +++ b/b
84 @@ -1,1 +1,1 @@
85 -a
86 +b
87
82
88 Look for extra:source
83 Look for extra:source
89
84
90 $ hg log --debug -r tip
85 $ hg log --debug -r tip
91 changeset: 7:d2e44c99fd3f31c176ea4efb9eca9f6306c81756
86 changeset: 7:ef0ef43d49e79e81ddafdc7997401ba0041efc82
92 tag: tip
87 tag: tip
93 phase: draft
88 phase: draft
94 parent: 0:68795b066622ca79a25816a662041d8f78f3cd9e
89 parent: 0:68795b066622ca79a25816a662041d8f78f3cd9e
95 parent: -1:0000000000000000000000000000000000000000
90 parent: -1:0000000000000000000000000000000000000000
96 manifest: 7:5d59766436fd8fbcd38e7bebef0f6eaf3eebe637
91 manifest: 7:e59b6b228f9cbf9903d5e9abf996e083a1f533eb
97 user: foo
92 user: foo
98 date: Thu Jan 01 00:00:00 1970 +0000
93 date: Thu Jan 01 00:00:00 1970 +0000
99 files+: b
94 files+: b
100 files-: a
95 files-: a
101 extra: branch=default
96 extra: branch=default
102 extra: source=5c095ad7e90f871700f02dd1fa5012cb4498a2d4
97 extra: source=5c095ad7e90f871700f02dd1fa5012cb4498a2d4
103 description:
98 description:
104 2
99 2
105
100
106
101
107
102
108 Graft out of order, skipping a merge and a duplicate
103 Graft out of order, skipping a merge and a duplicate
109
104
110 $ hg graft 1 5 4 3 'merge()' 2 -n
105 $ hg graft 1 5 4 3 'merge()' 2 -n
111 skipping ungraftable merge revision 6
106 skipping ungraftable merge revision 6
112 skipping already grafted revision 2
107 skipping already grafted revision 2
113 grafting revision 1
108 grafting revision 1
114 grafting revision 5
109 grafting revision 5
115 grafting revision 4
110 grafting revision 4
116 grafting revision 3
111 grafting revision 3
117
112
118 $ hg graft 1 5 4 3 'merge()' 2 --debug
113 $ hg graft 1 5 4 3 'merge()' 2 --debug
119 skipping ungraftable merge revision 6
114 skipping ungraftable merge revision 6
120 scanning for duplicate grafts
115 scanning for duplicate grafts
121 skipping already grafted revision 2
116 skipping already grafted revision 2
122 grafting revision 1
117 grafting revision 1
123 searching for copies back to rev 1
118 searching for copies back to rev 1
124 unmatched files in local:
119 unmatched files in local:
125 b
120 b
126 all copies found (* = to merge, ! = divergent):
121 all copies found (* = to merge, ! = divergent):
127 b -> a *
122 b -> a *
128 checking for directory renames
123 checking for directory renames
129 resolving manifests
124 resolving manifests
130 overwrite: False, partial: False
125 overwrite: False, partial: False
131 ancestor: 68795b066622, local: d2e44c99fd3f+, remote: 5d205f8b35b6
126 ancestor: 68795b066622, local: ef0ef43d49e7+, remote: 5d205f8b35b6
132 b: local copied/moved to a -> m
127 b: local copied/moved to a -> m
133 preserving b for resolve of b
128 preserving b for resolve of b
134 updating: b 1/1 files (100.00%)
129 updating: b 1/1 files (100.00%)
135 graft for revision 1 is empty
130 picked tool 'internal:merge' for b (binary False symlink False)
131 merging b and a to b
132 my b@ef0ef43d49e7+ other a@5d205f8b35b6 ancestor a@68795b066622
133 premerge successful
134 b
136 grafting revision 5
135 grafting revision 5
137 searching for copies back to rev 1
136 searching for copies back to rev 1
138 resolving manifests
137 resolving manifests
139 overwrite: False, partial: False
138 overwrite: False, partial: False
140 ancestor: 4c60f11aa304, local: d2e44c99fd3f+, remote: 97f8bfe72746
139 ancestor: 4c60f11aa304, local: 6b9e5368ca4e+, remote: 97f8bfe72746
141 e: remote is newer -> g
140 e: remote is newer -> g
142 updating: e 1/1 files (100.00%)
141 updating: e 1/1 files (100.00%)
143 getting e
142 getting e
144 e
143 e
145 grafting revision 4
144 grafting revision 4
146 searching for copies back to rev 1
145 searching for copies back to rev 1
147 resolving manifests
146 resolving manifests
148 overwrite: False, partial: False
147 overwrite: False, partial: False
149 ancestor: 4c60f11aa304, local: 839a7e8fcf80+, remote: 9c233e8e184d
148 ancestor: 4c60f11aa304, local: 1905859650ec+, remote: 9c233e8e184d
150 e: versions differ -> m
149 e: versions differ -> m
151 d: remote is newer -> g
150 d: remote is newer -> g
152 preserving e for resolve of e
151 preserving e for resolve of e
153 updating: d 1/2 files (50.00%)
152 updating: d 1/2 files (50.00%)
154 getting d
153 getting d
155 updating: e 2/2 files (100.00%)
154 updating: e 2/2 files (100.00%)
156 picked tool 'internal:merge' for e (binary False symlink False)
155 picked tool 'internal:merge' for e (binary False symlink False)
157 merging e
156 merging e
158 my e@839a7e8fcf80+ other e@9c233e8e184d ancestor e@68795b066622
157 my e@1905859650ec+ other e@9c233e8e184d ancestor e@68795b066622
159 warning: conflicts during merge.
158 warning: conflicts during merge.
160 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
159 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
161 abort: unresolved conflicts, can't continue
160 abort: unresolved conflicts, can't continue
162 (use hg resolve and hg graft --continue)
161 (use hg resolve and hg graft --continue)
163 [255]
162 [255]
164
163
165 Continue without resolve should fail:
164 Continue without resolve should fail:
166
165
167 $ hg graft -c
166 $ hg graft -c
168 grafting revision 4
167 grafting revision 4
169 abort: unresolved merge conflicts (see hg help resolve)
168 abort: unresolved merge conflicts (see hg help resolve)
170 [255]
169 [255]
171
170
172 Fix up:
171 Fix up:
173
172
174 $ echo b > e
173 $ echo b > e
175 $ hg resolve -m e
174 $ hg resolve -m e
176
175
177 Continue with a revision should fail:
176 Continue with a revision should fail:
178
177
179 $ hg graft -c 6
178 $ hg graft -c 6
180 abort: can't specify --continue and revisions
179 abort: can't specify --continue and revisions
181 [255]
180 [255]
182
181
183 Continue for real, clobber usernames
182 Continue for real, clobber usernames
184
183
185 $ hg graft -c -U
184 $ hg graft -c -U
186 grafting revision 4
185 grafting revision 4
187 grafting revision 3
186 grafting revision 3
188
187
189 Compare with original:
188 Compare with original:
190
189
191 $ hg diff -r 6
190 $ hg diff -r 6
192 $ hg status --rev 0:. -C
191 $ hg status --rev 0:. -C
193 M d
192 M d
194 M e
193 M e
195 A b
194 A b
196 a
195 a
197 A c
196 A c
198 a
197 a
199 R a
198 R a
200
199
201 View graph:
200 View graph:
202
201
203 $ hg --config extensions.graphlog= log -G --template '{author}@{rev}.{phase}: {desc}\n'
202 $ hg --config extensions.graphlog= log -G --template '{author}@{rev}.{phase}: {desc}\n'
204 @ test@10.draft: 3
203 @ test@11.draft: 3
204 |
205 o test@10.draft: 4
205 |
206 |
206 o test@9.draft: 4
207 o test@9.draft: 5
207 |
208 |
208 o test@8.draft: 5
209 o bar@8.draft: 1
209 |
210 |
210 o foo@7.draft: 2
211 o foo@7.draft: 2
211 |
212 |
212 | o test@6.secret: 6
213 | o test@6.secret: 6
213 | |\
214 | |\
214 | | o test@5.draft: 5
215 | | o test@5.draft: 5
215 | | |
216 | | |
216 | o | test@4.draft: 4
217 | o | test@4.draft: 4
217 | |/
218 | |/
218 | o baz@3.public: 3
219 | o baz@3.public: 3
219 | |
220 | |
220 | o test@2.public: 2
221 | o test@2.public: 2
221 | |
222 | |
222 | o bar@1.public: 1
223 | o bar@1.public: 1
223 |/
224 |/
224 o test@0.public: 0
225 o test@0.public: 0
225
226
226 Graft again onto another branch should preserve the original source
227 Graft again onto another branch should preserve the original source
227 $ hg up -q 0
228 $ hg up -q 0
228 $ echo 'g'>g
229 $ echo 'g'>g
229 $ hg add g
230 $ hg add g
230 $ hg ci -m 7
231 $ hg ci -m 7
231 created new head
232 created new head
232 $ hg graft 7
233 $ hg graft 7
233 grafting revision 7
234 grafting revision 7
234
235
235 $ hg log -r 7 --template '{rev}:{node}\n'
236 $ hg log -r 7 --template '{rev}:{node}\n'
236 7:d2e44c99fd3f31c176ea4efb9eca9f6306c81756
237 7:ef0ef43d49e79e81ddafdc7997401ba0041efc82
237 $ hg log -r 2 --template '{rev}:{node}\n'
238 $ hg log -r 2 --template '{rev}:{node}\n'
238 2:5c095ad7e90f871700f02dd1fa5012cb4498a2d4
239 2:5c095ad7e90f871700f02dd1fa5012cb4498a2d4
239
240
240 $ hg log --debug -r tip
241 $ hg log --debug -r tip
241 changeset: 12:95adbe5de6b10f376b699ece9ed5a57cd7b4b0f6
242 changeset: 13:9db0f28fd3747e92c57d015f53b5593aeec53c2d
242 tag: tip
243 tag: tip
243 phase: draft
244 phase: draft
244 parent: 11:b592ea63bb0c19a6c5c44685ee29a2284f9f1b8f
245 parent: 12:b592ea63bb0c19a6c5c44685ee29a2284f9f1b8f
245 parent: -1:0000000000000000000000000000000000000000
246 parent: -1:0000000000000000000000000000000000000000
246 manifest: 12:9944044f82a462bbaccc9bdf7e0ac5b811db7d1b
247 manifest: 13:dc313617b8c32457c0d589e0dbbedfe71f3cd637
247 user: foo
248 user: foo
248 date: Thu Jan 01 00:00:00 1970 +0000
249 date: Thu Jan 01 00:00:00 1970 +0000
249 files+: b
250 files+: b
250 files-: a
251 files-: a
251 extra: branch=default
252 extra: branch=default
252 extra: source=5c095ad7e90f871700f02dd1fa5012cb4498a2d4
253 extra: source=5c095ad7e90f871700f02dd1fa5012cb4498a2d4
253 description:
254 description:
254 2
255 2
255
256
256
257
257 Disallow grafting an already grafted cset onto its original branch
258 Disallow grafting an already grafted cset onto its original branch
258 $ hg up -q 6
259 $ hg up -q 6
259 $ hg graft 7
260 $ hg graft 7
260 skipping already grafted revision 7 (was grafted from 2)
261 skipping already grafted revision 7 (was grafted from 2)
261 [255]
262 [255]
262
263
263 Disallow grafting already grafted csets with the same origin onto each other
264 Disallow grafting already grafted csets with the same origin onto each other
264 $ hg up -q 12
265 $ hg up -q 13
265 $ hg graft 2
266 $ hg graft 2
266 skipping already grafted revision 2
267 skipping already grafted revision 2
267 [255]
268 [255]
268 $ hg graft 7
269 $ hg graft 7
269 skipping already grafted revision 7 (same origin 2)
270 skipping already grafted revision 7 (same origin 2)
270 [255]
271 [255]
271
272
272 $ hg up -q 7
273 $ hg up -q 7
273 $ hg graft 2
274 $ hg graft 2
274 skipping already grafted revision 2
275 skipping already grafted revision 2
275 [255]
276 [255]
276 $ hg graft tip
277 $ hg graft tip
277 skipping already grafted revision 12 (same origin 2)
278 skipping already grafted revision 13 (same origin 2)
278 [255]
279 [255]
General Comments 0
You need to be logged in to leave comments. Login now