##// END OF EJS Templates
Merge with crew
Matt Mackall -
r3352:69fe021c merge default
parent child Browse files
Show More
@@ -0,0 +1,34 b''
1 #!/bin/sh
2
3 echo % init foo-base
4 hg init foo-base
5
6 echo % create alpha in first repo
7 cd foo-base
8 echo 'alpha' > alpha
9 hg ci -A -m 'add alpha' -d '1 0'
10 cd ..
11
12 echo % clone foo-base to foo-work
13 hg clone foo-base foo-work
14
15 echo % create beta in second repo
16 cd foo-work
17 echo 'beta' > beta
18 hg ci -A -m 'add beta' -d '2 0'
19 cd ..
20
21 echo % create gamma in first repo
22 cd foo-base
23 echo 'gamma' > gamma
24 hg ci -A -m 'add gamma' -d '3 0'
25 cd ..
26
27 echo % pull into work and merge
28 cd foo-work
29 hg pull -q
30 hg merge
31
32 echo % revert to changeset 1 to simulate a failed merge
33 rm -fr *
34 hg up -C 1
@@ -0,0 +1,14 b''
1 % init foo-base
2 % create alpha in first repo
3 adding alpha
4 % clone foo-base to foo-work
5 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
6 % create beta in second repo
7 adding beta
8 % create gamma in first repo
9 adding gamma
10 % pull into work and merge
11 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
12 (branch merge, don't forget to commit)
13 % revert to changeset 1 to simulate a failed merge
14 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -1,480 +1,481 b''
1 1 # context.py - changeset and file context objects for mercurial
2 2 #
3 3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import *
9 9 from i18n import gettext as _
10 10 from demandload import demandload
11 11 demandload(globals(), "ancestor bdiff repo revlog util os")
12 12
13 13 class changectx(object):
14 14 """A changecontext object makes access to data related to a particular
15 15 changeset convenient."""
16 16 def __init__(self, repo, changeid=None):
17 17 """changeid is a revision number, node, or tag"""
18 18 self._repo = repo
19 19
20 20 if not changeid and changeid != 0:
21 21 p1, p2 = self._repo.dirstate.parents()
22 22 self._rev = self._repo.changelog.rev(p1)
23 23 if self._rev == -1:
24 24 changeid = 'tip'
25 25 else:
26 26 self._node = p1
27 27 return
28 28
29 29 self._node = self._repo.lookup(changeid)
30 30 self._rev = self._repo.changelog.rev(self._node)
31 31
32 32 def __str__(self):
33 33 return short(self.node())
34 34
35 35 def __repr__(self):
36 36 return "<changectx %s>" % str(self)
37 37
38 38 def __eq__(self, other):
39 39 return self._rev == other._rev
40 40
41 41 def __nonzero__(self):
42 42 return self._rev != -1
43 43
44 44 def __getattr__(self, name):
45 45 if name == '_changeset':
46 46 self._changeset = self._repo.changelog.read(self.node())
47 47 return self._changeset
48 48 elif name == '_manifest':
49 49 self._manifest = self._repo.manifest.read(self._changeset[0])
50 50 return self._manifest
51 51 elif name == '_manifestdelta':
52 52 md = self._repo.manifest.readdelta(self._changeset[0])
53 53 self._manifestdelta = md
54 54 return self._manifestdelta
55 55 else:
56 56 raise AttributeError, name
57 57
58 58 def changeset(self): return self._changeset
59 59 def manifest(self): return self._manifest
60 60
61 61 def rev(self): return self._rev
62 62 def node(self): return self._node
63 63 def user(self): return self._changeset[1]
64 64 def date(self): return self._changeset[2]
65 65 def files(self): return self._changeset[3]
66 66 def description(self): return self._changeset[4]
67 67
68 68 def parents(self):
69 69 """return contexts for each parent changeset"""
70 70 p = self._repo.changelog.parents(self._node)
71 71 return [ changectx(self._repo, x) for x in p ]
72 72
73 73 def children(self):
74 74 """return contexts for each child changeset"""
75 75 c = self._repo.changelog.children(self._node)
76 76 return [ changectx(self._repo, x) for x in c ]
77 77
78 78 def filenode(self, path):
79 79 if '_manifest' in self.__dict__:
80 80 try:
81 81 return self._manifest[path]
82 82 except KeyError:
83 83 raise repo.LookupError(_("'%s' not found in manifest") % path)
84 84 if '_manifestdelta' in self.__dict__ or path in self.files():
85 85 if path in self._manifestdelta:
86 86 return self._manifestdelta[path]
87 87 node, flag = self._repo.manifest.find(self._changeset[0], path)
88 88 if not node:
89 89 raise repo.LookupError(_("'%s' not found in manifest") % path)
90 90
91 91 return node
92 92
93 93 def filectx(self, path, fileid=None):
94 94 """get a file context from this changeset"""
95 95 if fileid is None:
96 96 fileid = self.filenode(path)
97 97 return filectx(self._repo, path, fileid=fileid, changectx=self)
98 98
99 99 def filectxs(self):
100 100 """generate a file context for each file in this changeset's
101 101 manifest"""
102 102 mf = self.manifest()
103 103 m = mf.keys()
104 104 m.sort()
105 105 for f in m:
106 106 yield self.filectx(f, fileid=mf[f])
107 107
108 108 def ancestor(self, c2):
109 109 """
110 110 return the ancestor context of self and c2
111 111 """
112 112 n = self._repo.changelog.ancestor(self._node, c2._node)
113 113 return changectx(self._repo, n)
114 114
115 115 class filectx(object):
116 116 """A filecontext object makes access to data related to a particular
117 117 filerevision convenient."""
118 118 def __init__(self, repo, path, changeid=None, fileid=None,
119 119 filelog=None, changectx=None):
120 120 """changeid can be a changeset revision, node, or tag.
121 121 fileid can be a file revision or node."""
122 122 self._repo = repo
123 123 self._path = path
124 124
125 125 assert changeid is not None or fileid is not None
126 126
127 127 if filelog:
128 128 self._filelog = filelog
129 129 if changectx:
130 130 self._changectx = changectx
131 131 self._changeid = changectx.node()
132 132
133 133 if fileid is None:
134 134 self._changeid = changeid
135 135 else:
136 136 self._fileid = fileid
137 137
138 138 def __getattr__(self, name):
139 139 if name == '_changectx':
140 140 self._changectx = changectx(self._repo, self._changeid)
141 141 return self._changectx
142 142 elif name == '_filelog':
143 143 self._filelog = self._repo.file(self._path)
144 144 return self._filelog
145 145 elif name == '_changeid':
146 146 self._changeid = self._filelog.linkrev(self._filenode)
147 147 return self._changeid
148 148 elif name == '_filenode':
149 149 try:
150 150 if '_fileid' in self.__dict__:
151 151 self._filenode = self._filelog.lookup(self._fileid)
152 152 else:
153 153 self._filenode = self._changectx.filenode(self._path)
154 154 except revlog.RevlogError, inst:
155 155 raise repo.LookupError(str(inst))
156 156 return self._filenode
157 157 elif name == '_filerev':
158 158 self._filerev = self._filelog.rev(self._filenode)
159 159 return self._filerev
160 160 else:
161 161 raise AttributeError, name
162 162
163 163 def __nonzero__(self):
164 164 return self._filerev != nullid
165 165
166 166 def __str__(self):
167 167 return "%s@%s" % (self.path(), short(self.node()))
168 168
169 169 def __repr__(self):
170 170 return "<filectx %s>" % str(self)
171 171
172 172 def __eq__(self, other):
173 173 return self._path == other._path and self._changeid == other._changeid
174 174
175 175 def filectx(self, fileid):
176 176 '''opens an arbitrary revision of the file without
177 177 opening a new filelog'''
178 178 return filectx(self._repo, self._path, fileid=fileid,
179 179 filelog=self._filelog)
180 180
181 181 def filerev(self): return self._filerev
182 182 def filenode(self): return self._filenode
183 183 def filelog(self): return self._filelog
184 184
185 185 def rev(self):
186 186 if '_changectx' in self.__dict__:
187 187 return self._changectx.rev()
188 188 return self._filelog.linkrev(self._filenode)
189 189
190 190 def node(self): return self._changectx.node()
191 191 def user(self): return self._changectx.user()
192 192 def date(self): return self._changectx.date()
193 193 def files(self): return self._changectx.files()
194 194 def description(self): return self._changectx.description()
195 195 def manifest(self): return self._changectx.manifest()
196 196 def changectx(self): return self._changectx
197 197
198 198 def data(self): return self._filelog.read(self._filenode)
199 199 def renamed(self): return self._filelog.renamed(self._filenode)
200 200 def path(self): return self._path
201 201 def size(self): return self._filelog.size(self._filerev)
202 202
203 203 def cmp(self, text): return self._filelog.cmp(self._filenode, text)
204 204
205 205 def parents(self):
206 206 p = self._path
207 207 fl = self._filelog
208 208 pl = [ (p, n, fl) for n in self._filelog.parents(self._filenode) ]
209 209
210 210 r = self.renamed()
211 211 if r:
212 212 pl[0] = (r[0], r[1], None)
213 213
214 214 return [ filectx(self._repo, p, fileid=n, filelog=l)
215 215 for p,n,l in pl if n != nullid ]
216 216
217 217 def children(self):
218 218 # hard for renames
219 219 c = self._filelog.children(self._filenode)
220 220 return [ filectx(self._repo, self._path, fileid=x,
221 221 filelog=self._filelog) for x in c ]
222 222
223 223 def annotate(self, follow=False):
224 224 '''returns a list of tuples of (ctx, line) for each line
225 225 in the file, where ctx is the filectx of the node where
226 226 that line was last changed'''
227 227
228 228 def decorate(text, rev):
229 229 return ([rev] * len(text.splitlines()), text)
230 230
231 231 def pair(parent, child):
232 232 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
233 233 child[0][b1:b2] = parent[0][a1:a2]
234 234 return child
235 235
236 236 getlog = util.cachefunc(lambda x: self._repo.file(x))
237 237 def getctx(path, fileid):
238 238 log = path == self._path and self._filelog or getlog(path)
239 239 return filectx(self._repo, path, fileid=fileid, filelog=log)
240 240 getctx = util.cachefunc(getctx)
241 241
242 242 def parents(f):
243 243 # we want to reuse filectx objects as much as possible
244 244 p = f._path
245 245 if f._filerev is None: # working dir
246 246 pl = [ (n.path(), n.filerev()) for n in f.parents() ]
247 247 else:
248 248 pl = [ (p, n) for n in f._filelog.parentrevs(f._filerev) ]
249 249
250 250 if follow:
251 251 r = f.renamed()
252 252 if r:
253 253 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
254 254
255 255 return [ getctx(p, n) for p, n in pl if n != -1 ]
256 256
257 257 # find all ancestors
258 258 needed = {self: 1}
259 259 visit = [self]
260 260 files = [self._path]
261 261 while visit:
262 262 f = visit.pop(0)
263 263 for p in parents(f):
264 264 if p not in needed:
265 265 needed[p] = 1
266 266 visit.append(p)
267 267 if p._path not in files:
268 268 files.append(p._path)
269 269 else:
270 270 # count how many times we'll use this
271 271 needed[p] += 1
272 272
273 273 # sort by revision (per file) which is a topological order
274 274 visit = []
275 275 files.reverse()
276 276 for f in files:
277 277 fn = [(n._filerev, n) for n in needed.keys() if n._path == f]
278 278 fn.sort()
279 279 visit.extend(fn)
280 280 hist = {}
281 281
282 282 for r, f in visit:
283 283 curr = decorate(f.data(), f)
284 284 for p in parents(f):
285 285 if p != nullid:
286 286 curr = pair(hist[p], curr)
287 287 # trim the history of unneeded revs
288 288 needed[p] -= 1
289 289 if not needed[p]:
290 290 del hist[p]
291 291 hist[f] = curr
292 292
293 293 return zip(hist[f][0], hist[f][1].splitlines(1))
294 294
295 295 def ancestor(self, fc2):
296 296 """
297 297 find the common ancestor file context, if any, of self, and fc2
298 298 """
299 299
300 300 acache = {}
301 301
302 302 # prime the ancestor cache for the working directory
303 303 for c in (self, fc2):
304 304 if c._filerev == None:
305 305 pl = [ (n.path(), n.filenode()) for n in c.parents() ]
306 306 acache[(c._path, None)] = pl
307 307
308 308 flcache = {self._path:self._filelog, fc2._path:fc2._filelog}
309 309 def parents(vertex):
310 310 if vertex in acache:
311 311 return acache[vertex]
312 312 f, n = vertex
313 313 if f not in flcache:
314 314 flcache[f] = self._repo.file(f)
315 315 fl = flcache[f]
316 316 pl = [ (f,p) for p in fl.parents(n) if p != nullid ]
317 317 re = fl.renamed(n)
318 318 if re:
319 319 pl.append(re)
320 320 acache[vertex]=pl
321 321 return pl
322 322
323 323 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
324 324 v = ancestor.ancestor(a, b, parents)
325 325 if v:
326 326 f,n = v
327 327 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
328 328
329 329 return None
330 330
331 331 class workingctx(changectx):
332 332 """A workingctx object makes access to data related to
333 333 the current working directory convenient."""
334 334 def __init__(self, repo):
335 335 self._repo = repo
336 336 self._rev = None
337 337 self._node = None
338 338
339 339 def __str__(self):
340 340 return str(self._parents[0]) + "+"
341 341
342 342 def __nonzero__(self):
343 343 return True
344 344
345 345 def __getattr__(self, name):
346 346 if name == '_parents':
347 347 self._parents = self._repo.parents()
348 348 return self._parents
349 349 if name == '_status':
350 350 self._status = self._repo.status()
351 351 return self._status
352 352 if name == '_manifest':
353 353 self._buildmanifest()
354 354 return self._manifest
355 355 else:
356 356 raise AttributeError, name
357 357
358 358 def _buildmanifest(self):
359 359 """generate a manifest corresponding to the working directory"""
360 360
361 361 man = self._parents[0].manifest().copy()
362 362 copied = self._repo.dirstate.copies()
363 363 modified, added, removed, deleted, unknown = self._status[:5]
364 364 for i,l in (("a", added), ("m", modified), ("u", unknown)):
365 365 for f in l:
366 366 man[f] = man.get(copied.get(f, f), nullid) + i
367 367 man.set(f, util.is_exec(self._repo.wjoin(f), man.execf(f)))
368 368
369 369 for f in deleted + removed:
370 if f in man:
370 371 del man[f]
371 372
372 373 self._manifest = man
373 374
374 375 def manifest(self): return self._manifest
375 376
376 377 def user(self): return self._repo.ui.username()
377 378 def date(self): return util.makedate()
378 379 def description(self): return ""
379 380 def files(self):
380 381 f = self.modified() + self.added() + self.removed()
381 382 f.sort()
382 383 return f
383 384
384 385 def modified(self): return self._status[0]
385 386 def added(self): return self._status[1]
386 387 def removed(self): return self._status[2]
387 388 def deleted(self): return self._status[3]
388 389 def unknown(self): return self._status[4]
389 390 def clean(self): return self._status[5]
390 391
391 392 def parents(self):
392 393 """return contexts for each parent changeset"""
393 394 return self._parents
394 395
395 396 def children(self):
396 397 return []
397 398
398 399 def filectx(self, path):
399 400 """get a file context from the working directory"""
400 401 return workingfilectx(self._repo, path, workingctx=self)
401 402
402 403 def ancestor(self, c2):
403 404 """return the ancestor context of self and c2"""
404 405 return self._parents[0].ancestor(c2) # punt on two parents for now
405 406
406 407 class workingfilectx(filectx):
407 408 """A workingfilectx object makes access to data related to a particular
408 409 file in the working directory convenient."""
409 410 def __init__(self, repo, path, filelog=None, workingctx=None):
410 411 """changeid can be a changeset revision, node, or tag.
411 412 fileid can be a file revision or node."""
412 413 self._repo = repo
413 414 self._path = path
414 415 self._changeid = None
415 416 self._filerev = self._filenode = None
416 417
417 418 if filelog:
418 419 self._filelog = filelog
419 420 if workingctx:
420 421 self._changectx = workingctx
421 422
422 423 def __getattr__(self, name):
423 424 if name == '_changectx':
424 425 self._changectx = workingctx(repo)
425 426 return self._changectx
426 427 elif name == '_repopath':
427 428 self._repopath = (self._repo.dirstate.copied(self._path)
428 429 or self._path)
429 430 return self._repopath
430 431 elif name == '_filelog':
431 432 self._filelog = self._repo.file(self._repopath)
432 433 return self._filelog
433 434 else:
434 435 raise AttributeError, name
435 436
436 437 def __nonzero__(self):
437 438 return True
438 439
439 440 def __str__(self):
440 441 return "%s@%s" % (self.path(), self._changectx)
441 442
442 443 def filectx(self, fileid):
443 444 '''opens an arbitrary revision of the file without
444 445 opening a new filelog'''
445 446 return filectx(self._repo, self._repopath, fileid=fileid,
446 447 filelog=self._filelog)
447 448
448 449 def rev(self):
449 450 if '_changectx' in self.__dict__:
450 451 return self._changectx.rev()
451 452 return self._filelog.linkrev(self._filenode)
452 453
453 454 def data(self): return self._repo.wread(self._path)
454 455 def renamed(self):
455 456 rp = self._repopath
456 457 if rp == self._path:
457 458 return None
458 459 return rp, self._workingctx._parents._manifest.get(rp, nullid)
459 460
460 461 def parents(self):
461 462 '''return parent filectxs, following copies if necessary'''
462 463 p = self._path
463 464 rp = self._repopath
464 465 pcl = self._changectx._parents
465 466 fl = self._filelog
466 467 pl = [ (rp, pcl[0]._manifest.get(rp, nullid), fl) ]
467 468 if len(pcl) > 1:
468 469 if rp != p:
469 470 fl = None
470 471 pl.append((p, pcl[1]._manifest.get(p, nullid), fl))
471 472
472 473 return [ filectx(self._repo, p, fileid=n, filelog=l)
473 474 for p,n,l in pl if n != nullid ]
474 475
475 476 def children(self):
476 477 return []
477 478
478 479 def size(self): return os.stat(self._repo.wjoin(self._path)).st_size
479 480
480 481 def cmp(self, text): return self._repo.wread(self._path) == text
General Comments 0
You need to be logged in to leave comments. Login now