##// END OF EJS Templates
context: remove islink and isexec methods
Matt Mackall -
r6744:d3691d31 default
parent child Browse files
Show More
@@ -1,753 +1,751
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
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from node import nullid, nullrev, short
8 from node import nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import ancestor, bdiff, revlog, util, os, errno
10 import ancestor, bdiff, revlog, util, os, errno
11
11
12 class changectx(object):
12 class changectx(object):
13 """A changecontext object makes access to data related to a particular
13 """A changecontext object makes access to data related to a particular
14 changeset convenient."""
14 changeset convenient."""
15 def __init__(self, repo, changeid=''):
15 def __init__(self, repo, changeid=''):
16 """changeid is a revision number, node, or tag"""
16 """changeid is a revision number, node, or tag"""
17 if changeid == '':
17 if changeid == '':
18 changeid = '.'
18 changeid = '.'
19 self._repo = repo
19 self._repo = repo
20 self._node = self._repo.lookup(changeid)
20 self._node = self._repo.lookup(changeid)
21 self._rev = self._repo.changelog.rev(self._node)
21 self._rev = self._repo.changelog.rev(self._node)
22
22
23 def __str__(self):
23 def __str__(self):
24 return short(self.node())
24 return short(self.node())
25
25
26 def __repr__(self):
26 def __repr__(self):
27 return "<changectx %s>" % str(self)
27 return "<changectx %s>" % str(self)
28
28
29 def __hash__(self):
29 def __hash__(self):
30 try:
30 try:
31 return hash(self._rev)
31 return hash(self._rev)
32 except AttributeError:
32 except AttributeError:
33 return id(self)
33 return id(self)
34
34
35 def __eq__(self, other):
35 def __eq__(self, other):
36 try:
36 try:
37 return self._rev == other._rev
37 return self._rev == other._rev
38 except AttributeError:
38 except AttributeError:
39 return False
39 return False
40
40
41 def __ne__(self, other):
41 def __ne__(self, other):
42 return not (self == other)
42 return not (self == other)
43
43
44 def __nonzero__(self):
44 def __nonzero__(self):
45 return self._rev != nullrev
45 return self._rev != nullrev
46
46
47 def __getattr__(self, name):
47 def __getattr__(self, name):
48 if name == '_changeset':
48 if name == '_changeset':
49 self._changeset = self._repo.changelog.read(self.node())
49 self._changeset = self._repo.changelog.read(self.node())
50 return self._changeset
50 return self._changeset
51 elif name == '_manifest':
51 elif name == '_manifest':
52 self._manifest = self._repo.manifest.read(self._changeset[0])
52 self._manifest = self._repo.manifest.read(self._changeset[0])
53 return self._manifest
53 return self._manifest
54 elif name == '_manifestdelta':
54 elif name == '_manifestdelta':
55 md = self._repo.manifest.readdelta(self._changeset[0])
55 md = self._repo.manifest.readdelta(self._changeset[0])
56 self._manifestdelta = md
56 self._manifestdelta = md
57 return self._manifestdelta
57 return self._manifestdelta
58 elif name == '_parents':
58 elif name == '_parents':
59 p = self._repo.changelog.parents(self._node)
59 p = self._repo.changelog.parents(self._node)
60 if p[1] == nullid:
60 if p[1] == nullid:
61 p = p[:-1]
61 p = p[:-1]
62 self._parents = [changectx(self._repo, x) for x in p]
62 self._parents = [changectx(self._repo, x) for x in p]
63 return self._parents
63 return self._parents
64 else:
64 else:
65 raise AttributeError, name
65 raise AttributeError, name
66
66
67 def __contains__(self, key):
67 def __contains__(self, key):
68 return key in self._manifest
68 return key in self._manifest
69
69
70 def __getitem__(self, key):
70 def __getitem__(self, key):
71 return self.filectx(key)
71 return self.filectx(key)
72
72
73 def __iter__(self):
73 def __iter__(self):
74 a = self._manifest.keys()
74 a = self._manifest.keys()
75 a.sort()
75 a.sort()
76 for f in a:
76 for f in a:
77 yield f
77 yield f
78
78
79 def changeset(self): return self._changeset
79 def changeset(self): return self._changeset
80 def manifest(self): return self._manifest
80 def manifest(self): return self._manifest
81
81
82 def rev(self): return self._rev
82 def rev(self): return self._rev
83 def node(self): return self._node
83 def node(self): return self._node
84 def user(self): return self._changeset[1]
84 def user(self): return self._changeset[1]
85 def date(self): return self._changeset[2]
85 def date(self): return self._changeset[2]
86 def files(self): return self._changeset[3]
86 def files(self): return self._changeset[3]
87 def description(self): return self._changeset[4]
87 def description(self): return self._changeset[4]
88 def branch(self): return self._changeset[5].get("branch")
88 def branch(self): return self._changeset[5].get("branch")
89 def extra(self): return self._changeset[5]
89 def extra(self): return self._changeset[5]
90 def tags(self): return self._repo.nodetags(self._node)
90 def tags(self): return self._repo.nodetags(self._node)
91
91
92 def parents(self):
92 def parents(self):
93 """return contexts for each parent changeset"""
93 """return contexts for each parent changeset"""
94 return self._parents
94 return self._parents
95
95
96 def children(self):
96 def children(self):
97 """return contexts for each child changeset"""
97 """return contexts for each child changeset"""
98 c = self._repo.changelog.children(self._node)
98 c = self._repo.changelog.children(self._node)
99 return [changectx(self._repo, x) for x in c]
99 return [changectx(self._repo, x) for x in c]
100
100
101 def _fileinfo(self, path):
101 def _fileinfo(self, path):
102 if '_manifest' in self.__dict__:
102 if '_manifest' in self.__dict__:
103 try:
103 try:
104 return self._manifest[path], self._manifest.flags(path)
104 return self._manifest[path], self._manifest.flags(path)
105 except KeyError:
105 except KeyError:
106 raise revlog.LookupError(self._node, path,
106 raise revlog.LookupError(self._node, path,
107 _('not found in manifest'))
107 _('not found in manifest'))
108 if '_manifestdelta' in self.__dict__ or path in self.files():
108 if '_manifestdelta' in self.__dict__ or path in self.files():
109 if path in self._manifestdelta:
109 if path in self._manifestdelta:
110 return self._manifestdelta[path], self._manifestdelta.flags(path)
110 return self._manifestdelta[path], self._manifestdelta.flags(path)
111 node, flag = self._repo.manifest.find(self._changeset[0], path)
111 node, flag = self._repo.manifest.find(self._changeset[0], path)
112 if not node:
112 if not node:
113 raise revlog.LookupError(self._node, path,
113 raise revlog.LookupError(self._node, path,
114 _('not found in manifest'))
114 _('not found in manifest'))
115
115
116 return node, flag
116 return node, flag
117
117
118 def filenode(self, path):
118 def filenode(self, path):
119 return self._fileinfo(path)[0]
119 return self._fileinfo(path)[0]
120
120
121 def flags(self, path):
121 def flags(self, path):
122 try:
122 try:
123 return self._fileinfo(path)[1]
123 return self._fileinfo(path)[1]
124 except revlog.LookupError:
124 except revlog.LookupError:
125 return ''
125 return ''
126
126
127 def filectx(self, path, fileid=None, filelog=None):
127 def filectx(self, path, fileid=None, filelog=None):
128 """get a file context from this changeset"""
128 """get a file context from this changeset"""
129 if fileid is None:
129 if fileid is None:
130 fileid = self.filenode(path)
130 fileid = self.filenode(path)
131 return filectx(self._repo, path, fileid=fileid,
131 return filectx(self._repo, path, fileid=fileid,
132 changectx=self, filelog=filelog)
132 changectx=self, filelog=filelog)
133
133
134 def filectxs(self):
134 def filectxs(self):
135 """generate a file context for each file in this changeset's
135 """generate a file context for each file in this changeset's
136 manifest"""
136 manifest"""
137 mf = self.manifest()
137 mf = self.manifest()
138 m = mf.keys()
138 m = mf.keys()
139 m.sort()
139 m.sort()
140 for f in m:
140 for f in m:
141 yield self.filectx(f, fileid=mf[f])
141 yield self.filectx(f, fileid=mf[f])
142
142
143 def ancestor(self, c2):
143 def ancestor(self, c2):
144 """
144 """
145 return the ancestor context of self and c2
145 return the ancestor context of self and c2
146 """
146 """
147 n = self._repo.changelog.ancestor(self._node, c2._node)
147 n = self._repo.changelog.ancestor(self._node, c2._node)
148 return changectx(self._repo, n)
148 return changectx(self._repo, n)
149
149
150 class filectx(object):
150 class filectx(object):
151 """A filecontext object makes access to data related to a particular
151 """A filecontext object makes access to data related to a particular
152 filerevision convenient."""
152 filerevision convenient."""
153 def __init__(self, repo, path, changeid=None, fileid=None,
153 def __init__(self, repo, path, changeid=None, fileid=None,
154 filelog=None, changectx=None):
154 filelog=None, changectx=None):
155 """changeid can be a changeset revision, node, or tag.
155 """changeid can be a changeset revision, node, or tag.
156 fileid can be a file revision or node."""
156 fileid can be a file revision or node."""
157 self._repo = repo
157 self._repo = repo
158 self._path = path
158 self._path = path
159
159
160 assert (changeid is not None
160 assert (changeid is not None
161 or fileid is not None
161 or fileid is not None
162 or changectx is not None)
162 or changectx is not None)
163
163
164 if filelog:
164 if filelog:
165 self._filelog = filelog
165 self._filelog = filelog
166
166
167 if changeid is not None:
167 if changeid is not None:
168 self._changeid = changeid
168 self._changeid = changeid
169 if changectx is not None:
169 if changectx is not None:
170 self._changectx = changectx
170 self._changectx = changectx
171 if fileid is not None:
171 if fileid is not None:
172 self._fileid = fileid
172 self._fileid = fileid
173
173
174 def __getattr__(self, name):
174 def __getattr__(self, name):
175 if name == '_changectx':
175 if name == '_changectx':
176 self._changectx = changectx(self._repo, self._changeid)
176 self._changectx = changectx(self._repo, self._changeid)
177 return self._changectx
177 return self._changectx
178 elif name == '_filelog':
178 elif name == '_filelog':
179 self._filelog = self._repo.file(self._path)
179 self._filelog = self._repo.file(self._path)
180 return self._filelog
180 return self._filelog
181 elif name == '_changeid':
181 elif name == '_changeid':
182 if '_changectx' in self.__dict__:
182 if '_changectx' in self.__dict__:
183 self._changeid = self._changectx.rev()
183 self._changeid = self._changectx.rev()
184 else:
184 else:
185 self._changeid = self._filelog.linkrev(self._filenode)
185 self._changeid = self._filelog.linkrev(self._filenode)
186 return self._changeid
186 return self._changeid
187 elif name == '_filenode':
187 elif name == '_filenode':
188 if '_fileid' in self.__dict__:
188 if '_fileid' in self.__dict__:
189 self._filenode = self._filelog.lookup(self._fileid)
189 self._filenode = self._filelog.lookup(self._fileid)
190 else:
190 else:
191 self._filenode = self._changectx.filenode(self._path)
191 self._filenode = self._changectx.filenode(self._path)
192 return self._filenode
192 return self._filenode
193 elif name == '_filerev':
193 elif name == '_filerev':
194 self._filerev = self._filelog.rev(self._filenode)
194 self._filerev = self._filelog.rev(self._filenode)
195 return self._filerev
195 return self._filerev
196 elif name == '_repopath':
196 elif name == '_repopath':
197 self._repopath = self._path
197 self._repopath = self._path
198 return self._repopath
198 return self._repopath
199 else:
199 else:
200 raise AttributeError, name
200 raise AttributeError, name
201
201
202 def __nonzero__(self):
202 def __nonzero__(self):
203 try:
203 try:
204 n = self._filenode
204 n = self._filenode
205 return True
205 return True
206 except revlog.LookupError:
206 except revlog.LookupError:
207 # file is missing
207 # file is missing
208 return False
208 return False
209
209
210 def __str__(self):
210 def __str__(self):
211 return "%s@%s" % (self.path(), short(self.node()))
211 return "%s@%s" % (self.path(), short(self.node()))
212
212
213 def __repr__(self):
213 def __repr__(self):
214 return "<filectx %s>" % str(self)
214 return "<filectx %s>" % str(self)
215
215
216 def __hash__(self):
216 def __hash__(self):
217 try:
217 try:
218 return hash((self._path, self._fileid))
218 return hash((self._path, self._fileid))
219 except AttributeError:
219 except AttributeError:
220 return id(self)
220 return id(self)
221
221
222 def __eq__(self, other):
222 def __eq__(self, other):
223 try:
223 try:
224 return (self._path == other._path
224 return (self._path == other._path
225 and self._fileid == other._fileid)
225 and self._fileid == other._fileid)
226 except AttributeError:
226 except AttributeError:
227 return False
227 return False
228
228
229 def __ne__(self, other):
229 def __ne__(self, other):
230 return not (self == other)
230 return not (self == other)
231
231
232 def filectx(self, fileid):
232 def filectx(self, fileid):
233 '''opens an arbitrary revision of the file without
233 '''opens an arbitrary revision of the file without
234 opening a new filelog'''
234 opening a new filelog'''
235 return filectx(self._repo, self._path, fileid=fileid,
235 return filectx(self._repo, self._path, fileid=fileid,
236 filelog=self._filelog)
236 filelog=self._filelog)
237
237
238 def filerev(self): return self._filerev
238 def filerev(self): return self._filerev
239 def filenode(self): return self._filenode
239 def filenode(self): return self._filenode
240 def flags(self): return self._changectx.flags(self._path)
240 def flags(self): return self._changectx.flags(self._path)
241 def isexec(self): return 'x' in self.flags()
242 def islink(self): return 'l' in self.flags()
243 def filelog(self): return self._filelog
241 def filelog(self): return self._filelog
244
242
245 def rev(self):
243 def rev(self):
246 if '_changectx' in self.__dict__:
244 if '_changectx' in self.__dict__:
247 return self._changectx.rev()
245 return self._changectx.rev()
248 if '_changeid' in self.__dict__:
246 if '_changeid' in self.__dict__:
249 return self._changectx.rev()
247 return self._changectx.rev()
250 return self._filelog.linkrev(self._filenode)
248 return self._filelog.linkrev(self._filenode)
251
249
252 def linkrev(self): return self._filelog.linkrev(self._filenode)
250 def linkrev(self): return self._filelog.linkrev(self._filenode)
253 def node(self): return self._changectx.node()
251 def node(self): return self._changectx.node()
254 def user(self): return self._changectx.user()
252 def user(self): return self._changectx.user()
255 def date(self): return self._changectx.date()
253 def date(self): return self._changectx.date()
256 def files(self): return self._changectx.files()
254 def files(self): return self._changectx.files()
257 def description(self): return self._changectx.description()
255 def description(self): return self._changectx.description()
258 def branch(self): return self._changectx.branch()
256 def branch(self): return self._changectx.branch()
259 def manifest(self): return self._changectx.manifest()
257 def manifest(self): return self._changectx.manifest()
260 def changectx(self): return self._changectx
258 def changectx(self): return self._changectx
261
259
262 def data(self): return self._filelog.read(self._filenode)
260 def data(self): return self._filelog.read(self._filenode)
263 def path(self): return self._path
261 def path(self): return self._path
264 def size(self): return self._filelog.size(self._filerev)
262 def size(self): return self._filelog.size(self._filerev)
265
263
266 def cmp(self, text): return self._filelog.cmp(self._filenode, text)
264 def cmp(self, text): return self._filelog.cmp(self._filenode, text)
267
265
268 def renamed(self):
266 def renamed(self):
269 """check if file was actually renamed in this changeset revision
267 """check if file was actually renamed in this changeset revision
270
268
271 If rename logged in file revision, we report copy for changeset only
269 If rename logged in file revision, we report copy for changeset only
272 if file revisions linkrev points back to the changeset in question
270 if file revisions linkrev points back to the changeset in question
273 or both changeset parents contain different file revisions.
271 or both changeset parents contain different file revisions.
274 """
272 """
275
273
276 renamed = self._filelog.renamed(self._filenode)
274 renamed = self._filelog.renamed(self._filenode)
277 if not renamed:
275 if not renamed:
278 return renamed
276 return renamed
279
277
280 if self.rev() == self.linkrev():
278 if self.rev() == self.linkrev():
281 return renamed
279 return renamed
282
280
283 name = self.path()
281 name = self.path()
284 fnode = self._filenode
282 fnode = self._filenode
285 for p in self._changectx.parents():
283 for p in self._changectx.parents():
286 try:
284 try:
287 if fnode == p.filenode(name):
285 if fnode == p.filenode(name):
288 return None
286 return None
289 except revlog.LookupError:
287 except revlog.LookupError:
290 pass
288 pass
291 return renamed
289 return renamed
292
290
293 def parents(self):
291 def parents(self):
294 p = self._path
292 p = self._path
295 fl = self._filelog
293 fl = self._filelog
296 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
294 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
297
295
298 r = self._filelog.renamed(self._filenode)
296 r = self._filelog.renamed(self._filenode)
299 if r:
297 if r:
300 pl[0] = (r[0], r[1], None)
298 pl[0] = (r[0], r[1], None)
301
299
302 return [filectx(self._repo, p, fileid=n, filelog=l)
300 return [filectx(self._repo, p, fileid=n, filelog=l)
303 for p,n,l in pl if n != nullid]
301 for p,n,l in pl if n != nullid]
304
302
305 def children(self):
303 def children(self):
306 # hard for renames
304 # hard for renames
307 c = self._filelog.children(self._filenode)
305 c = self._filelog.children(self._filenode)
308 return [filectx(self._repo, self._path, fileid=x,
306 return [filectx(self._repo, self._path, fileid=x,
309 filelog=self._filelog) for x in c]
307 filelog=self._filelog) for x in c]
310
308
311 def annotate(self, follow=False, linenumber=None):
309 def annotate(self, follow=False, linenumber=None):
312 '''returns a list of tuples of (ctx, line) for each line
310 '''returns a list of tuples of (ctx, line) for each line
313 in the file, where ctx is the filectx of the node where
311 in the file, where ctx is the filectx of the node where
314 that line was last changed.
312 that line was last changed.
315 This returns tuples of ((ctx, linenumber), line) for each line,
313 This returns tuples of ((ctx, linenumber), line) for each line,
316 if "linenumber" parameter is NOT "None".
314 if "linenumber" parameter is NOT "None".
317 In such tuples, linenumber means one at the first appearance
315 In such tuples, linenumber means one at the first appearance
318 in the managed file.
316 in the managed file.
319 To reduce annotation cost,
317 To reduce annotation cost,
320 this returns fixed value(False is used) as linenumber,
318 this returns fixed value(False is used) as linenumber,
321 if "linenumber" parameter is "False".'''
319 if "linenumber" parameter is "False".'''
322
320
323 def decorate_compat(text, rev):
321 def decorate_compat(text, rev):
324 return ([rev] * len(text.splitlines()), text)
322 return ([rev] * len(text.splitlines()), text)
325
323
326 def without_linenumber(text, rev):
324 def without_linenumber(text, rev):
327 return ([(rev, False)] * len(text.splitlines()), text)
325 return ([(rev, False)] * len(text.splitlines()), text)
328
326
329 def with_linenumber(text, rev):
327 def with_linenumber(text, rev):
330 size = len(text.splitlines())
328 size = len(text.splitlines())
331 return ([(rev, i) for i in xrange(1, size + 1)], text)
329 return ([(rev, i) for i in xrange(1, size + 1)], text)
332
330
333 decorate = (((linenumber is None) and decorate_compat) or
331 decorate = (((linenumber is None) and decorate_compat) or
334 (linenumber and with_linenumber) or
332 (linenumber and with_linenumber) or
335 without_linenumber)
333 without_linenumber)
336
334
337 def pair(parent, child):
335 def pair(parent, child):
338 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
336 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
339 child[0][b1:b2] = parent[0][a1:a2]
337 child[0][b1:b2] = parent[0][a1:a2]
340 return child
338 return child
341
339
342 getlog = util.cachefunc(lambda x: self._repo.file(x))
340 getlog = util.cachefunc(lambda x: self._repo.file(x))
343 def getctx(path, fileid):
341 def getctx(path, fileid):
344 log = path == self._path and self._filelog or getlog(path)
342 log = path == self._path and self._filelog or getlog(path)
345 return filectx(self._repo, path, fileid=fileid, filelog=log)
343 return filectx(self._repo, path, fileid=fileid, filelog=log)
346 getctx = util.cachefunc(getctx)
344 getctx = util.cachefunc(getctx)
347
345
348 def parents(f):
346 def parents(f):
349 # we want to reuse filectx objects as much as possible
347 # we want to reuse filectx objects as much as possible
350 p = f._path
348 p = f._path
351 if f._filerev is None: # working dir
349 if f._filerev is None: # working dir
352 pl = [(n.path(), n.filerev()) for n in f.parents()]
350 pl = [(n.path(), n.filerev()) for n in f.parents()]
353 else:
351 else:
354 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
352 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
355
353
356 if follow:
354 if follow:
357 r = f.renamed()
355 r = f.renamed()
358 if r:
356 if r:
359 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
357 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
360
358
361 return [getctx(p, n) for p, n in pl if n != nullrev]
359 return [getctx(p, n) for p, n in pl if n != nullrev]
362
360
363 # use linkrev to find the first changeset where self appeared
361 # use linkrev to find the first changeset where self appeared
364 if self.rev() != self.linkrev():
362 if self.rev() != self.linkrev():
365 base = self.filectx(self.filerev())
363 base = self.filectx(self.filerev())
366 else:
364 else:
367 base = self
365 base = self
368
366
369 # find all ancestors
367 # find all ancestors
370 needed = {base: 1}
368 needed = {base: 1}
371 visit = [base]
369 visit = [base]
372 files = [base._path]
370 files = [base._path]
373 while visit:
371 while visit:
374 f = visit.pop(0)
372 f = visit.pop(0)
375 for p in parents(f):
373 for p in parents(f):
376 if p not in needed:
374 if p not in needed:
377 needed[p] = 1
375 needed[p] = 1
378 visit.append(p)
376 visit.append(p)
379 if p._path not in files:
377 if p._path not in files:
380 files.append(p._path)
378 files.append(p._path)
381 else:
379 else:
382 # count how many times we'll use this
380 # count how many times we'll use this
383 needed[p] += 1
381 needed[p] += 1
384
382
385 # sort by revision (per file) which is a topological order
383 # sort by revision (per file) which is a topological order
386 visit = []
384 visit = []
387 for f in files:
385 for f in files:
388 fn = [(n.rev(), n) for n in needed.keys() if n._path == f]
386 fn = [(n.rev(), n) for n in needed.keys() if n._path == f]
389 visit.extend(fn)
387 visit.extend(fn)
390 visit.sort()
388 visit.sort()
391 hist = {}
389 hist = {}
392
390
393 for r, f in visit:
391 for r, f in visit:
394 curr = decorate(f.data(), f)
392 curr = decorate(f.data(), f)
395 for p in parents(f):
393 for p in parents(f):
396 if p != nullid:
394 if p != nullid:
397 curr = pair(hist[p], curr)
395 curr = pair(hist[p], curr)
398 # trim the history of unneeded revs
396 # trim the history of unneeded revs
399 needed[p] -= 1
397 needed[p] -= 1
400 if not needed[p]:
398 if not needed[p]:
401 del hist[p]
399 del hist[p]
402 hist[f] = curr
400 hist[f] = curr
403
401
404 return zip(hist[f][0], hist[f][1].splitlines(1))
402 return zip(hist[f][0], hist[f][1].splitlines(1))
405
403
406 def ancestor(self, fc2):
404 def ancestor(self, fc2):
407 """
405 """
408 find the common ancestor file context, if any, of self, and fc2
406 find the common ancestor file context, if any, of self, and fc2
409 """
407 """
410
408
411 acache = {}
409 acache = {}
412
410
413 # prime the ancestor cache for the working directory
411 # prime the ancestor cache for the working directory
414 for c in (self, fc2):
412 for c in (self, fc2):
415 if c._filerev == None:
413 if c._filerev == None:
416 pl = [(n.path(), n.filenode()) for n in c.parents()]
414 pl = [(n.path(), n.filenode()) for n in c.parents()]
417 acache[(c._path, None)] = pl
415 acache[(c._path, None)] = pl
418
416
419 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
417 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
420 def parents(vertex):
418 def parents(vertex):
421 if vertex in acache:
419 if vertex in acache:
422 return acache[vertex]
420 return acache[vertex]
423 f, n = vertex
421 f, n = vertex
424 if f not in flcache:
422 if f not in flcache:
425 flcache[f] = self._repo.file(f)
423 flcache[f] = self._repo.file(f)
426 fl = flcache[f]
424 fl = flcache[f]
427 pl = [(f, p) for p in fl.parents(n) if p != nullid]
425 pl = [(f, p) for p in fl.parents(n) if p != nullid]
428 re = fl.renamed(n)
426 re = fl.renamed(n)
429 if re:
427 if re:
430 pl.append(re)
428 pl.append(re)
431 acache[vertex] = pl
429 acache[vertex] = pl
432 return pl
430 return pl
433
431
434 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
432 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
435 v = ancestor.ancestor(a, b, parents)
433 v = ancestor.ancestor(a, b, parents)
436 if v:
434 if v:
437 f, n = v
435 f, n = v
438 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
436 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
439
437
440 return None
438 return None
441
439
442 class workingctx(changectx):
440 class workingctx(changectx):
443 """A workingctx object makes access to data related to
441 """A workingctx object makes access to data related to
444 the current working directory convenient.
442 the current working directory convenient.
445 parents - a pair of parent nodeids, or None to use the dirstate.
443 parents - a pair of parent nodeids, or None to use the dirstate.
446 date - any valid date string or (unixtime, offset), or None.
444 date - any valid date string or (unixtime, offset), or None.
447 user - username string, or None.
445 user - username string, or None.
448 extra - a dictionary of extra values, or None.
446 extra - a dictionary of extra values, or None.
449 changes - a list of file lists as returned by localrepo.status()
447 changes - a list of file lists as returned by localrepo.status()
450 or None to use the repository status.
448 or None to use the repository status.
451 """
449 """
452 def __init__(self, repo, parents=None, text="", user=None, date=None,
450 def __init__(self, repo, parents=None, text="", user=None, date=None,
453 extra=None, changes=None):
451 extra=None, changes=None):
454 self._repo = repo
452 self._repo = repo
455 self._rev = None
453 self._rev = None
456 self._node = None
454 self._node = None
457 self._text = text
455 self._text = text
458 if date:
456 if date:
459 self._date = util.parsedate(date)
457 self._date = util.parsedate(date)
460 else:
458 else:
461 self._date = util.makedate()
459 self._date = util.makedate()
462 if user:
460 if user:
463 self._user = user
461 self._user = user
464 else:
462 else:
465 self._user = self._repo.ui.username()
463 self._user = self._repo.ui.username()
466 if parents:
464 if parents:
467 p1, p2 = parents
465 p1, p2 = parents
468 self._parents = [self._repo.changectx(p) for p in (p1, p2)]
466 self._parents = [self._repo.changectx(p) for p in (p1, p2)]
469 if changes:
467 if changes:
470 self._status = list(changes)
468 self._status = list(changes)
471
469
472 self._extra = {}
470 self._extra = {}
473 if extra:
471 if extra:
474 self._extra = extra.copy()
472 self._extra = extra.copy()
475 if 'branch' not in self._extra:
473 if 'branch' not in self._extra:
476 branch = self._repo.dirstate.branch()
474 branch = self._repo.dirstate.branch()
477 try:
475 try:
478 branch = branch.decode('UTF-8').encode('UTF-8')
476 branch = branch.decode('UTF-8').encode('UTF-8')
479 except UnicodeDecodeError:
477 except UnicodeDecodeError:
480 raise util.Abort(_('branch name not in UTF-8!'))
478 raise util.Abort(_('branch name not in UTF-8!'))
481 self._extra['branch'] = branch
479 self._extra['branch'] = branch
482 if self._extra['branch'] == '':
480 if self._extra['branch'] == '':
483 self._extra['branch'] = 'default'
481 self._extra['branch'] = 'default'
484
482
485 def __str__(self):
483 def __str__(self):
486 return str(self._parents[0]) + "+"
484 return str(self._parents[0]) + "+"
487
485
488 def __nonzero__(self):
486 def __nonzero__(self):
489 return True
487 return True
490
488
491 def __getattr__(self, name):
489 def __getattr__(self, name):
492 if name == '_status':
490 if name == '_status':
493 self._status = self._repo.status()
491 self._status = self._repo.status()
494 return self._status
492 return self._status
495 if name == '_manifest':
493 if name == '_manifest':
496 self._buildmanifest()
494 self._buildmanifest()
497 return self._manifest
495 return self._manifest
498 elif name == '_parents':
496 elif name == '_parents':
499 p = self._repo.dirstate.parents()
497 p = self._repo.dirstate.parents()
500 if p[1] == nullid:
498 if p[1] == nullid:
501 p = p[:-1]
499 p = p[:-1]
502 self._parents = [changectx(self._repo, x) for x in p]
500 self._parents = [changectx(self._repo, x) for x in p]
503 return self._parents
501 return self._parents
504 else:
502 else:
505 raise AttributeError, name
503 raise AttributeError, name
506
504
507 def _buildmanifest(self):
505 def _buildmanifest(self):
508 """generate a manifest corresponding to the working directory"""
506 """generate a manifest corresponding to the working directory"""
509
507
510 man = self._parents[0].manifest().copy()
508 man = self._parents[0].manifest().copy()
511 copied = self._repo.dirstate.copies()
509 copied = self._repo.dirstate.copies()
512 cf = lambda x: man.flags(copied.get(x, x))
510 cf = lambda x: man.flags(copied.get(x, x))
513 ff = self._repo.dirstate.flagfunc(cf)
511 ff = self._repo.dirstate.flagfunc(cf)
514 modified, added, removed, deleted, unknown = self._status[:5]
512 modified, added, removed, deleted, unknown = self._status[:5]
515 for i, l in (("a", added), ("m", modified), ("u", unknown)):
513 for i, l in (("a", added), ("m", modified), ("u", unknown)):
516 for f in l:
514 for f in l:
517 man[f] = man.get(copied.get(f, f), nullid) + i
515 man[f] = man.get(copied.get(f, f), nullid) + i
518 try:
516 try:
519 man.set(f, ff(f))
517 man.set(f, ff(f))
520 except OSError:
518 except OSError:
521 pass
519 pass
522
520
523 for f in deleted + removed:
521 for f in deleted + removed:
524 if f in man:
522 if f in man:
525 del man[f]
523 del man[f]
526
524
527 self._manifest = man
525 self._manifest = man
528
526
529 def manifest(self): return self._manifest
527 def manifest(self): return self._manifest
530
528
531 def user(self): return self._user
529 def user(self): return self._user
532 def date(self): return self._date
530 def date(self): return self._date
533 def description(self): return self._text
531 def description(self): return self._text
534 def files(self):
532 def files(self):
535 f = self.modified() + self.added() + self.removed()
533 f = self.modified() + self.added() + self.removed()
536 f.sort()
534 f.sort()
537 return f
535 return f
538
536
539 def modified(self): return self._status[0]
537 def modified(self): return self._status[0]
540 def added(self): return self._status[1]
538 def added(self): return self._status[1]
541 def removed(self): return self._status[2]
539 def removed(self): return self._status[2]
542 def deleted(self): return self._status[3]
540 def deleted(self): return self._status[3]
543 def unknown(self): return self._status[4]
541 def unknown(self): return self._status[4]
544 def clean(self): return self._status[5]
542 def clean(self): return self._status[5]
545 def branch(self): return self._extra['branch']
543 def branch(self): return self._extra['branch']
546 def extra(self): return self._extra
544 def extra(self): return self._extra
547
545
548 def tags(self):
546 def tags(self):
549 t = []
547 t = []
550 [t.extend(p.tags()) for p in self.parents()]
548 [t.extend(p.tags()) for p in self.parents()]
551 return t
549 return t
552
550
553 def children(self):
551 def children(self):
554 return []
552 return []
555
553
556 def flags(self, path):
554 def flags(self, path):
557 if '_manifest' in self.__dict__:
555 if '_manifest' in self.__dict__:
558 try:
556 try:
559 return self._manifest.flags(path)
557 return self._manifest.flags(path)
560 except KeyError:
558 except KeyError:
561 return ''
559 return ''
562
560
563 pnode = self._parents[0].changeset()[0]
561 pnode = self._parents[0].changeset()[0]
564 orig = self._repo.dirstate.copies().get(path, path)
562 orig = self._repo.dirstate.copies().get(path, path)
565 node, flag = self._repo.manifest.find(pnode, orig)
563 node, flag = self._repo.manifest.find(pnode, orig)
566 try:
564 try:
567 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
565 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
568 return ff(path)
566 return ff(path)
569 except OSError:
567 except OSError:
570 pass
568 pass
571
569
572 if not node or path in self.deleted() or path in self.removed():
570 if not node or path in self.deleted() or path in self.removed():
573 return ''
571 return ''
574 return flag
572 return flag
575
573
576 def filectx(self, path, filelog=None):
574 def filectx(self, path, filelog=None):
577 """get a file context from the working directory"""
575 """get a file context from the working directory"""
578 return workingfilectx(self._repo, path, workingctx=self,
576 return workingfilectx(self._repo, path, workingctx=self,
579 filelog=filelog)
577 filelog=filelog)
580
578
581 def ancestor(self, c2):
579 def ancestor(self, c2):
582 """return the ancestor context of self and c2"""
580 """return the ancestor context of self and c2"""
583 return self._parents[0].ancestor(c2) # punt on two parents for now
581 return self._parents[0].ancestor(c2) # punt on two parents for now
584
582
585 class workingfilectx(filectx):
583 class workingfilectx(filectx):
586 """A workingfilectx object makes access to data related to a particular
584 """A workingfilectx object makes access to data related to a particular
587 file in the working directory convenient."""
585 file in the working directory convenient."""
588 def __init__(self, repo, path, filelog=None, workingctx=None):
586 def __init__(self, repo, path, filelog=None, workingctx=None):
589 """changeid can be a changeset revision, node, or tag.
587 """changeid can be a changeset revision, node, or tag.
590 fileid can be a file revision or node."""
588 fileid can be a file revision or node."""
591 self._repo = repo
589 self._repo = repo
592 self._path = path
590 self._path = path
593 self._changeid = None
591 self._changeid = None
594 self._filerev = self._filenode = None
592 self._filerev = self._filenode = None
595
593
596 if filelog:
594 if filelog:
597 self._filelog = filelog
595 self._filelog = filelog
598 if workingctx:
596 if workingctx:
599 self._changectx = workingctx
597 self._changectx = workingctx
600
598
601 def __getattr__(self, name):
599 def __getattr__(self, name):
602 if name == '_changectx':
600 if name == '_changectx':
603 self._changectx = workingctx(self._repo)
601 self._changectx = workingctx(self._repo)
604 return self._changectx
602 return self._changectx
605 elif name == '_repopath':
603 elif name == '_repopath':
606 self._repopath = (self._repo.dirstate.copied(self._path)
604 self._repopath = (self._repo.dirstate.copied(self._path)
607 or self._path)
605 or self._path)
608 return self._repopath
606 return self._repopath
609 elif name == '_filelog':
607 elif name == '_filelog':
610 self._filelog = self._repo.file(self._repopath)
608 self._filelog = self._repo.file(self._repopath)
611 return self._filelog
609 return self._filelog
612 else:
610 else:
613 raise AttributeError, name
611 raise AttributeError, name
614
612
615 def __nonzero__(self):
613 def __nonzero__(self):
616 return True
614 return True
617
615
618 def __str__(self):
616 def __str__(self):
619 return "%s@%s" % (self.path(), self._changectx)
617 return "%s@%s" % (self.path(), self._changectx)
620
618
621 def filectx(self, fileid):
619 def filectx(self, fileid):
622 '''opens an arbitrary revision of the file without
620 '''opens an arbitrary revision of the file without
623 opening a new filelog'''
621 opening a new filelog'''
624 return filectx(self._repo, self._repopath, fileid=fileid,
622 return filectx(self._repo, self._repopath, fileid=fileid,
625 filelog=self._filelog)
623 filelog=self._filelog)
626
624
627 def rev(self):
625 def rev(self):
628 if '_changectx' in self.__dict__:
626 if '_changectx' in self.__dict__:
629 return self._changectx.rev()
627 return self._changectx.rev()
630 return self._filelog.linkrev(self._filenode)
628 return self._filelog.linkrev(self._filenode)
631
629
632 def data(self): return self._repo.wread(self._path)
630 def data(self): return self._repo.wread(self._path)
633 def renamed(self):
631 def renamed(self):
634 rp = self._repopath
632 rp = self._repopath
635 if rp == self._path:
633 if rp == self._path:
636 return None
634 return None
637 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
635 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
638
636
639 def parents(self):
637 def parents(self):
640 '''return parent filectxs, following copies if necessary'''
638 '''return parent filectxs, following copies if necessary'''
641 p = self._path
639 p = self._path
642 rp = self._repopath
640 rp = self._repopath
643 pcl = self._changectx._parents
641 pcl = self._changectx._parents
644 fl = self._filelog
642 fl = self._filelog
645 pl = [(rp, pcl[0]._manifest.get(rp, nullid), fl)]
643 pl = [(rp, pcl[0]._manifest.get(rp, nullid), fl)]
646 if len(pcl) > 1:
644 if len(pcl) > 1:
647 if rp != p:
645 if rp != p:
648 fl = None
646 fl = None
649 pl.append((p, pcl[1]._manifest.get(p, nullid), fl))
647 pl.append((p, pcl[1]._manifest.get(p, nullid), fl))
650
648
651 return [filectx(self._repo, p, fileid=n, filelog=l)
649 return [filectx(self._repo, p, fileid=n, filelog=l)
652 for p,n,l in pl if n != nullid]
650 for p,n,l in pl if n != nullid]
653
651
654 def children(self):
652 def children(self):
655 return []
653 return []
656
654
657 def size(self): return os.stat(self._repo.wjoin(self._path)).st_size
655 def size(self): return os.stat(self._repo.wjoin(self._path)).st_size
658 def date(self):
656 def date(self):
659 t, tz = self._changectx.date()
657 t, tz = self._changectx.date()
660 try:
658 try:
661 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
659 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
662 except OSError, err:
660 except OSError, err:
663 if err.errno != errno.ENOENT: raise
661 if err.errno != errno.ENOENT: raise
664 return (t, tz)
662 return (t, tz)
665
663
666 def cmp(self, text): return self._repo.wread(self._path) == text
664 def cmp(self, text): return self._repo.wread(self._path) == text
667
665
668 class memctx(object):
666 class memctx(object):
669 """A memctx is a subset of changectx supposed to be built on memory
667 """A memctx is a subset of changectx supposed to be built on memory
670 and passed to commit functions.
668 and passed to commit functions.
671
669
672 NOTE: this interface and the related memfilectx are experimental and
670 NOTE: this interface and the related memfilectx are experimental and
673 may change without notice.
671 may change without notice.
674
672
675 parents - a pair of parent nodeids.
673 parents - a pair of parent nodeids.
676 filectxfn - a callable taking (repo, memctx, path) arguments and
674 filectxfn - a callable taking (repo, memctx, path) arguments and
677 returning a memctx object.
675 returning a memctx object.
678 date - any valid date string or (unixtime, offset), or None.
676 date - any valid date string or (unixtime, offset), or None.
679 user - username string, or None.
677 user - username string, or None.
680 extra - a dictionary of extra values, or None.
678 extra - a dictionary of extra values, or None.
681 """
679 """
682 def __init__(self, repo, parents, text, files, filectxfn, user=None,
680 def __init__(self, repo, parents, text, files, filectxfn, user=None,
683 date=None, extra=None):
681 date=None, extra=None):
684 self._repo = repo
682 self._repo = repo
685 self._rev = None
683 self._rev = None
686 self._node = None
684 self._node = None
687 self._text = text
685 self._text = text
688 self._date = date and util.parsedate(date) or util.makedate()
686 self._date = date and util.parsedate(date) or util.makedate()
689 self._user = user or self._repo.ui.username()
687 self._user = user or self._repo.ui.username()
690 parents = [(p or nullid) for p in parents]
688 parents = [(p or nullid) for p in parents]
691 p1, p2 = parents
689 p1, p2 = parents
692 self._parents = [self._repo.changectx(p) for p in (p1, p2)]
690 self._parents = [self._repo.changectx(p) for p in (p1, p2)]
693 files = list(files)
691 files = list(files)
694 files.sort()
692 files.sort()
695 self._status = [files, [], [], [], []]
693 self._status = [files, [], [], [], []]
696 self._filectxfn = filectxfn
694 self._filectxfn = filectxfn
697
695
698 self._extra = extra and extra.copy() or {}
696 self._extra = extra and extra.copy() or {}
699 if 'branch' not in self._extra:
697 if 'branch' not in self._extra:
700 self._extra['branch'] = 'default'
698 self._extra['branch'] = 'default'
701 elif self._extra.get('branch') == '':
699 elif self._extra.get('branch') == '':
702 self._extra['branch'] = 'default'
700 self._extra['branch'] = 'default'
703
701
704 def __str__(self):
702 def __str__(self):
705 return str(self._parents[0]) + "+"
703 return str(self._parents[0]) + "+"
706
704
707 def __nonzero__(self):
705 def __nonzero__(self):
708 return True
706 return True
709
707
710 def user(self): return self._user
708 def user(self): return self._user
711 def date(self): return self._date
709 def date(self): return self._date
712 def description(self): return self._text
710 def description(self): return self._text
713 def files(self): return self.modified()
711 def files(self): return self.modified()
714 def modified(self): return self._status[0]
712 def modified(self): return self._status[0]
715 def added(self): return self._status[1]
713 def added(self): return self._status[1]
716 def removed(self): return self._status[2]
714 def removed(self): return self._status[2]
717 def deleted(self): return self._status[3]
715 def deleted(self): return self._status[3]
718 def unknown(self): return self._status[4]
716 def unknown(self): return self._status[4]
719 def clean(self): return self._status[5]
717 def clean(self): return self._status[5]
720 def branch(self): return self._extra['branch']
718 def branch(self): return self._extra['branch']
721 def extra(self): return self._extra
719 def extra(self): return self._extra
722 def flags(self, f): return self[f].flags()
720 def flags(self, f): return self[f].flags()
723
721
724 def parents(self):
722 def parents(self):
725 """return contexts for each parent changeset"""
723 """return contexts for each parent changeset"""
726 return self._parents
724 return self._parents
727
725
728 def filectx(self, path, filelog=None):
726 def filectx(self, path, filelog=None):
729 """get a file context from the working directory"""
727 """get a file context from the working directory"""
730 return self._filectxfn(self._repo, self, path)
728 return self._filectxfn(self._repo, self, path)
731
729
732 class memfilectx(object):
730 class memfilectx(object):
733 """A memfilectx is a subset of filectx supposed to be built by client
731 """A memfilectx is a subset of filectx supposed to be built by client
734 code and passed to commit functions.
732 code and passed to commit functions.
735 """
733 """
736 def __init__(self, path, data, islink, isexec, copied):
734 def __init__(self, path, data, islink, isexec, copied):
737 """copied is the source file path, or None."""
735 """copied is the source file path, or None."""
738 self._path = path
736 self._path = path
739 self._data = data
737 self._data = data
740 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
738 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
741 self._copied = None
739 self._copied = None
742 if copied:
740 if copied:
743 self._copied = (copied, nullid)
741 self._copied = (copied, nullid)
744
742
745 def __nonzero__(self): return True
743 def __nonzero__(self): return True
746 def __str__(self): return "%s@%s" % (self.path(), self._changectx)
744 def __str__(self): return "%s@%s" % (self.path(), self._changectx)
747 def path(self): return self._path
745 def path(self): return self._path
748 def data(self): return self._data
746 def data(self): return self._data
749 def flags(self): return self._flags
747 def flags(self): return self._flags
750 def isexec(self): return 'x' in self._flags
748 def isexec(self): return 'x' in self._flags
751 def islink(self): return 'l' in self._flags
749 def islink(self): return 'l' in self._flags
752 def renamed(self): return self._copied
750 def renamed(self): return self._copied
753
751
@@ -1,219 +1,219
1 # filemerge.py - file-level merge handling for Mercurial
1 # filemerge.py - file-level merge handling for Mercurial
2 #
2 #
3 # Copyright 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from node import nullrev, short
8 from node import nullrev, short
9 from i18n import _
9 from i18n import _
10 import util, os, tempfile, simplemerge, re, filecmp
10 import util, os, tempfile, simplemerge, re, filecmp
11
11
12 def _toolstr(ui, tool, part, default=""):
12 def _toolstr(ui, tool, part, default=""):
13 return ui.config("merge-tools", tool + "." + part, default)
13 return ui.config("merge-tools", tool + "." + part, default)
14
14
15 def _toolbool(ui, tool, part, default=False):
15 def _toolbool(ui, tool, part, default=False):
16 return ui.configbool("merge-tools", tool + "." + part, default)
16 return ui.configbool("merge-tools", tool + "." + part, default)
17
17
18 def _findtool(ui, tool):
18 def _findtool(ui, tool):
19 if tool in ("internal:fail", "internal:local", "internal:other"):
19 if tool in ("internal:fail", "internal:local", "internal:other"):
20 return tool
20 return tool
21 k = _toolstr(ui, tool, "regkey")
21 k = _toolstr(ui, tool, "regkey")
22 if k:
22 if k:
23 p = util.lookup_reg(k, _toolstr(ui, tool, "regname"))
23 p = util.lookup_reg(k, _toolstr(ui, tool, "regname"))
24 if p:
24 if p:
25 p = util.find_exe(p + _toolstr(ui, tool, "regappend"))
25 p = util.find_exe(p + _toolstr(ui, tool, "regappend"))
26 if p:
26 if p:
27 return p
27 return p
28 return util.find_exe(_toolstr(ui, tool, "executable", tool))
28 return util.find_exe(_toolstr(ui, tool, "executable", tool))
29
29
30 def _picktool(repo, ui, path, binary, symlink):
30 def _picktool(repo, ui, path, binary, symlink):
31 def check(tool, pat, symlink, binary):
31 def check(tool, pat, symlink, binary):
32 tmsg = tool
32 tmsg = tool
33 if pat:
33 if pat:
34 tmsg += " specified for " + pat
34 tmsg += " specified for " + pat
35 if pat and not _findtool(ui, tool): # skip search if not matching
35 if pat and not _findtool(ui, tool): # skip search if not matching
36 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
36 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
37 elif symlink and not _toolbool(ui, tool, "symlink"):
37 elif symlink and not _toolbool(ui, tool, "symlink"):
38 ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
38 ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
39 elif binary and not _toolbool(ui, tool, "binary"):
39 elif binary and not _toolbool(ui, tool, "binary"):
40 ui.warn(_("tool %s can't handle binary\n") % tmsg)
40 ui.warn(_("tool %s can't handle binary\n") % tmsg)
41 elif not util.gui() and _toolbool(ui, tool, "gui"):
41 elif not util.gui() and _toolbool(ui, tool, "gui"):
42 ui.warn(_("tool %s requires a GUI\n") % tmsg)
42 ui.warn(_("tool %s requires a GUI\n") % tmsg)
43 else:
43 else:
44 return True
44 return True
45 return False
45 return False
46
46
47 # HGMERGE takes precedence
47 # HGMERGE takes precedence
48 hgmerge = os.environ.get("HGMERGE")
48 hgmerge = os.environ.get("HGMERGE")
49 if hgmerge:
49 if hgmerge:
50 return (hgmerge, hgmerge)
50 return (hgmerge, hgmerge)
51
51
52 # then patterns
52 # then patterns
53 for pat, tool in ui.configitems("merge-patterns"):
53 for pat, tool in ui.configitems("merge-patterns"):
54 mf = util.matcher(repo.root, "", [pat], [], [])[1]
54 mf = util.matcher(repo.root, "", [pat], [], [])[1]
55 if mf(path) and check(tool, pat, symlink, False):
55 if mf(path) and check(tool, pat, symlink, False):
56 toolpath = _findtool(ui, tool)
56 toolpath = _findtool(ui, tool)
57 return (tool, '"' + toolpath + '"')
57 return (tool, '"' + toolpath + '"')
58
58
59 # then merge tools
59 # then merge tools
60 tools = {}
60 tools = {}
61 for k,v in ui.configitems("merge-tools"):
61 for k,v in ui.configitems("merge-tools"):
62 t = k.split('.')[0]
62 t = k.split('.')[0]
63 if t not in tools:
63 if t not in tools:
64 tools[t] = int(_toolstr(ui, t, "priority", "0"))
64 tools[t] = int(_toolstr(ui, t, "priority", "0"))
65 names = tools.keys()
65 names = tools.keys()
66 tools = [(-p,t) for t,p in tools.items()]
66 tools = [(-p,t) for t,p in tools.items()]
67 tools.sort()
67 tools.sort()
68 uimerge = ui.config("ui", "merge")
68 uimerge = ui.config("ui", "merge")
69 if uimerge:
69 if uimerge:
70 if uimerge not in names:
70 if uimerge not in names:
71 return (uimerge, uimerge)
71 return (uimerge, uimerge)
72 tools.insert(0, (None, uimerge)) # highest priority
72 tools.insert(0, (None, uimerge)) # highest priority
73 tools.append((None, "hgmerge")) # the old default, if found
73 tools.append((None, "hgmerge")) # the old default, if found
74 for p,t in tools:
74 for p,t in tools:
75 toolpath = _findtool(ui, t)
75 toolpath = _findtool(ui, t)
76 if toolpath and check(t, None, symlink, binary):
76 if toolpath and check(t, None, symlink, binary):
77 return (t, '"' + toolpath + '"')
77 return (t, '"' + toolpath + '"')
78 # internal merge as last resort
78 # internal merge as last resort
79 return (not (symlink or binary) and "internal:merge" or None, None)
79 return (not (symlink or binary) and "internal:merge" or None, None)
80
80
81 def _eoltype(data):
81 def _eoltype(data):
82 "Guess the EOL type of a file"
82 "Guess the EOL type of a file"
83 if '\0' in data: # binary
83 if '\0' in data: # binary
84 return None
84 return None
85 if '\r\n' in data: # Windows
85 if '\r\n' in data: # Windows
86 return '\r\n'
86 return '\r\n'
87 if '\r' in data: # Old Mac
87 if '\r' in data: # Old Mac
88 return '\r'
88 return '\r'
89 if '\n' in data: # UNIX
89 if '\n' in data: # UNIX
90 return '\n'
90 return '\n'
91 return None # unknown
91 return None # unknown
92
92
93 def _matcheol(file, origfile):
93 def _matcheol(file, origfile):
94 "Convert EOL markers in a file to match origfile"
94 "Convert EOL markers in a file to match origfile"
95 tostyle = _eoltype(open(origfile, "rb").read())
95 tostyle = _eoltype(open(origfile, "rb").read())
96 if tostyle:
96 if tostyle:
97 data = open(file, "rb").read()
97 data = open(file, "rb").read()
98 style = _eoltype(data)
98 style = _eoltype(data)
99 if style:
99 if style:
100 newdata = data.replace(style, tostyle)
100 newdata = data.replace(style, tostyle)
101 if newdata != data:
101 if newdata != data:
102 open(file, "wb").write(newdata)
102 open(file, "wb").write(newdata)
103
103
104 def filemerge(repo, mynode, orig, fcd, fco, fca):
104 def filemerge(repo, mynode, orig, fcd, fco, fca):
105 """perform a 3-way merge in the working directory
105 """perform a 3-way merge in the working directory
106
106
107 mynode = parent node before merge
107 mynode = parent node before merge
108 orig = original local filename before merge
108 orig = original local filename before merge
109 fco = other file context
109 fco = other file context
110 fca = ancestor file context
110 fca = ancestor file context
111 fcd = local file context for current/destination file
111 fcd = local file context for current/destination file
112 """
112 """
113
113
114 def temp(prefix, ctx):
114 def temp(prefix, ctx):
115 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
115 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
116 (fd, name) = tempfile.mkstemp(prefix=pre)
116 (fd, name) = tempfile.mkstemp(prefix=pre)
117 data = repo.wwritedata(ctx.path(), ctx.data())
117 data = repo.wwritedata(ctx.path(), ctx.data())
118 f = os.fdopen(fd, "wb")
118 f = os.fdopen(fd, "wb")
119 f.write(data)
119 f.write(data)
120 f.close()
120 f.close()
121 return name
121 return name
122
122
123 def isbin(ctx):
123 def isbin(ctx):
124 try:
124 try:
125 return util.binary(ctx.data())
125 return util.binary(ctx.data())
126 except IOError:
126 except IOError:
127 return False
127 return False
128
128
129 if not fco.cmp(fcd.data()): # files identical?
129 if not fco.cmp(fcd.data()): # files identical?
130 return None
130 return None
131
131
132 ui = repo.ui
132 ui = repo.ui
133 fd = fcd.path()
133 fd = fcd.path()
134 binary = isbin(fcd) or isbin(fco) or isbin(fca)
134 binary = isbin(fcd) or isbin(fco) or isbin(fca)
135 symlink = fcd.islink() or fco.islink()
135 symlink = 'l' in fcd.flags() + fco.flags()
136 tool, toolpath = _picktool(repo, ui, fd, binary, symlink)
136 tool, toolpath = _picktool(repo, ui, fd, binary, symlink)
137 ui.debug(_("picked tool '%s' for %s (binary %s symlink %s)\n") %
137 ui.debug(_("picked tool '%s' for %s (binary %s symlink %s)\n") %
138 (tool, fd, binary, symlink))
138 (tool, fd, binary, symlink))
139
139
140 if not tool:
140 if not tool:
141 tool = "internal:local"
141 tool = "internal:local"
142 if ui.prompt(_(" no tool found to merge %s\n"
142 if ui.prompt(_(" no tool found to merge %s\n"
143 "keep (l)ocal or take (o)ther?") % fd,
143 "keep (l)ocal or take (o)ther?") % fd,
144 _("[lo]"), _("l")) != _("l"):
144 _("[lo]"), _("l")) != _("l"):
145 tool = "internal:other"
145 tool = "internal:other"
146 if tool == "internal:local":
146 if tool == "internal:local":
147 return 0
147 return 0
148 if tool == "internal:other":
148 if tool == "internal:other":
149 repo.wwrite(fd, fco.data(), fco.flags())
149 repo.wwrite(fd, fco.data(), fco.flags())
150 return 0
150 return 0
151 if tool == "internal:fail":
151 if tool == "internal:fail":
152 return 1
152 return 1
153
153
154 # do the actual merge
154 # do the actual merge
155 a = repo.wjoin(fd)
155 a = repo.wjoin(fd)
156 b = temp("base", fca)
156 b = temp("base", fca)
157 c = temp("other", fco)
157 c = temp("other", fco)
158 out = ""
158 out = ""
159 back = a + ".orig"
159 back = a + ".orig"
160 util.copyfile(a, back)
160 util.copyfile(a, back)
161
161
162 if orig != fco.path():
162 if orig != fco.path():
163 repo.ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
163 repo.ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
164 else:
164 else:
165 repo.ui.status(_("merging %s\n") % fd)
165 repo.ui.status(_("merging %s\n") % fd)
166
166
167 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcd, fco, fca))
167 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcd, fco, fca))
168
168
169 # do we attempt to simplemerge first?
169 # do we attempt to simplemerge first?
170 if _toolbool(ui, tool, "premerge", not (binary or symlink)):
170 if _toolbool(ui, tool, "premerge", not (binary or symlink)):
171 r = simplemerge.simplemerge(a, b, c, quiet=True)
171 r = simplemerge.simplemerge(a, b, c, quiet=True)
172 if not r:
172 if not r:
173 ui.debug(_(" premerge successful\n"))
173 ui.debug(_(" premerge successful\n"))
174 os.unlink(back)
174 os.unlink(back)
175 os.unlink(b)
175 os.unlink(b)
176 os.unlink(c)
176 os.unlink(c)
177 return 0
177 return 0
178 util.copyfile(back, a) # restore from backup and try again
178 util.copyfile(back, a) # restore from backup and try again
179
179
180 env = dict(HG_FILE=fd,
180 env = dict(HG_FILE=fd,
181 HG_MY_NODE=short(mynode),
181 HG_MY_NODE=short(mynode),
182 HG_OTHER_NODE=str(fco.changectx()),
182 HG_OTHER_NODE=str(fco.changectx()),
183 HG_MY_ISLINK=fcd.islink(),
183 HG_MY_ISLINK='l' in fcd.flags(),
184 HG_OTHER_ISLINK=fco.islink(),
184 HG_OTHER_ISLINK='l' in fco.flags(),
185 HG_BASE_ISLINK=fca.islink())
185 HG_BASE_ISLINK='l' in fca.flags())
186
186
187 if tool == "internal:merge":
187 if tool == "internal:merge":
188 r = simplemerge.simplemerge(a, b, c, label=['local', 'other'])
188 r = simplemerge.simplemerge(a, b, c, label=['local', 'other'])
189 else:
189 else:
190 args = _toolstr(ui, tool, "args", '$local $base $other')
190 args = _toolstr(ui, tool, "args", '$local $base $other')
191 if "$output" in args:
191 if "$output" in args:
192 out, a = a, back # read input from backup, write to original
192 out, a = a, back # read input from backup, write to original
193 replace = dict(local=a, base=b, other=c, output=out)
193 replace = dict(local=a, base=b, other=c, output=out)
194 args = re.sub("\$(local|base|other|output)",
194 args = re.sub("\$(local|base|other|output)",
195 lambda x: '"%s"' % replace[x.group()[1:]], args)
195 lambda x: '"%s"' % replace[x.group()[1:]], args)
196 r = util.system(toolpath + ' ' + args, cwd=repo.root, environ=env)
196 r = util.system(toolpath + ' ' + args, cwd=repo.root, environ=env)
197
197
198 if not r and _toolbool(ui, tool, "checkconflicts"):
198 if not r and _toolbool(ui, tool, "checkconflicts"):
199 if re.match("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data()):
199 if re.match("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data()):
200 r = 1
200 r = 1
201
201
202 if not r and _toolbool(ui, tool, "checkchanged"):
202 if not r and _toolbool(ui, tool, "checkchanged"):
203 if filecmp.cmp(repo.wjoin(fd), back):
203 if filecmp.cmp(repo.wjoin(fd), back):
204 if ui.prompt(_(" output file %s appears unchanged\n"
204 if ui.prompt(_(" output file %s appears unchanged\n"
205 "was merge successful (yn)?") % fd,
205 "was merge successful (yn)?") % fd,
206 _("[yn]"), _("n")) != _("y"):
206 _("[yn]"), _("n")) != _("y"):
207 r = 1
207 r = 1
208
208
209 if _toolbool(ui, tool, "fixeol"):
209 if _toolbool(ui, tool, "fixeol"):
210 _matcheol(repo.wjoin(fd), back)
210 _matcheol(repo.wjoin(fd), back)
211
211
212 if r:
212 if r:
213 repo.ui.warn(_("merging %s failed!\n") % fd)
213 repo.ui.warn(_("merging %s failed!\n") % fd)
214 else:
214 else:
215 os.unlink(back)
215 os.unlink(back)
216
216
217 os.unlink(b)
217 os.unlink(b)
218 os.unlink(c)
218 os.unlink(c)
219 return r
219 return r
General Comments 0
You need to be logged in to leave comments. Login now