##// END OF EJS Templates
merge: run update hook after the last wlock release...
Matt Harbison -
r24881:51844b8b stable
parent child Browse files
Show More
@@ -1,1205 +1,1207 b''
1 1 # merge.py - directory-level update/merge handling for Mercurial
2 2 #
3 3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import struct
9 9
10 10 from node import nullid, nullrev, hex, bin
11 11 from i18n import _
12 12 from mercurial import obsolete
13 13 import error as errormod, util, filemerge, copies, subrepo, worker
14 14 import errno, os, shutil
15 15
16 16 _pack = struct.pack
17 17 _unpack = struct.unpack
18 18
19 19 def _droponode(data):
20 20 # used for compatibility for v1
21 21 bits = data.split('\0')
22 22 bits = bits[:-2] + bits[-1:]
23 23 return '\0'.join(bits)
24 24
25 25 class mergestate(object):
26 26 '''track 3-way merge state of individual files
27 27
28 28 it is stored on disk when needed. Two file are used, one with an old
29 29 format, one with a new format. Both contains similar data, but the new
30 30 format can store new kind of field.
31 31
32 32 Current new format is a list of arbitrary record of the form:
33 33
34 34 [type][length][content]
35 35
36 36 Type is a single character, length is a 4 bytes integer, content is an
37 37 arbitrary suites of bytes of length `length`.
38 38
39 39 Type should be a letter. Capital letter are mandatory record, Mercurial
40 40 should abort if they are unknown. lower case record can be safely ignored.
41 41
42 42 Currently known record:
43 43
44 44 L: the node of the "local" part of the merge (hexified version)
45 45 O: the node of the "other" part of the merge (hexified version)
46 46 F: a file to be merged entry
47 47 '''
48 48 statepathv1 = 'merge/state'
49 49 statepathv2 = 'merge/state2'
50 50
51 51 def __init__(self, repo):
52 52 self._repo = repo
53 53 self._dirty = False
54 54 self._read()
55 55
56 56 def reset(self, node=None, other=None):
57 57 self._state = {}
58 58 self._local = None
59 59 self._other = None
60 60 if node:
61 61 self._local = node
62 62 self._other = other
63 63 shutil.rmtree(self._repo.join('merge'), True)
64 64 self._dirty = False
65 65
66 66 def _read(self):
67 67 """Analyse each record content to restore a serialized state from disk
68 68
69 69 This function process "record" entry produced by the de-serialization
70 70 of on disk file.
71 71 """
72 72 self._state = {}
73 73 self._local = None
74 74 self._other = None
75 75 records = self._readrecords()
76 76 for rtype, record in records:
77 77 if rtype == 'L':
78 78 self._local = bin(record)
79 79 elif rtype == 'O':
80 80 self._other = bin(record)
81 81 elif rtype == 'F':
82 82 bits = record.split('\0')
83 83 self._state[bits[0]] = bits[1:]
84 84 elif not rtype.islower():
85 85 raise util.Abort(_('unsupported merge state record: %s')
86 86 % rtype)
87 87 self._dirty = False
88 88
89 89 def _readrecords(self):
90 90 """Read merge state from disk and return a list of record (TYPE, data)
91 91
92 92 We read data from both v1 and v2 files and decide which one to use.
93 93
94 94 V1 has been used by version prior to 2.9.1 and contains less data than
95 95 v2. We read both versions and check if no data in v2 contradicts
96 96 v1. If there is not contradiction we can safely assume that both v1
97 97 and v2 were written at the same time and use the extract data in v2. If
98 98 there is contradiction we ignore v2 content as we assume an old version
99 99 of Mercurial has overwritten the mergestate file and left an old v2
100 100 file around.
101 101
102 102 returns list of record [(TYPE, data), ...]"""
103 103 v1records = self._readrecordsv1()
104 104 v2records = self._readrecordsv2()
105 105 oldv2 = set() # old format version of v2 record
106 106 for rec in v2records:
107 107 if rec[0] == 'L':
108 108 oldv2.add(rec)
109 109 elif rec[0] == 'F':
110 110 # drop the onode data (not contained in v1)
111 111 oldv2.add(('F', _droponode(rec[1])))
112 112 for rec in v1records:
113 113 if rec not in oldv2:
114 114 # v1 file is newer than v2 file, use it
115 115 # we have to infer the "other" changeset of the merge
116 116 # we cannot do better than that with v1 of the format
117 117 mctx = self._repo[None].parents()[-1]
118 118 v1records.append(('O', mctx.hex()))
119 119 # add place holder "other" file node information
120 120 # nobody is using it yet so we do no need to fetch the data
121 121 # if mctx was wrong `mctx[bits[-2]]` may fails.
122 122 for idx, r in enumerate(v1records):
123 123 if r[0] == 'F':
124 124 bits = r[1].split('\0')
125 125 bits.insert(-2, '')
126 126 v1records[idx] = (r[0], '\0'.join(bits))
127 127 return v1records
128 128 else:
129 129 return v2records
130 130
131 131 def _readrecordsv1(self):
132 132 """read on disk merge state for version 1 file
133 133
134 134 returns list of record [(TYPE, data), ...]
135 135
136 136 Note: the "F" data from this file are one entry short
137 137 (no "other file node" entry)
138 138 """
139 139 records = []
140 140 try:
141 141 f = self._repo.vfs(self.statepathv1)
142 142 for i, l in enumerate(f):
143 143 if i == 0:
144 144 records.append(('L', l[:-1]))
145 145 else:
146 146 records.append(('F', l[:-1]))
147 147 f.close()
148 148 except IOError, err:
149 149 if err.errno != errno.ENOENT:
150 150 raise
151 151 return records
152 152
153 153 def _readrecordsv2(self):
154 154 """read on disk merge state for version 2 file
155 155
156 156 returns list of record [(TYPE, data), ...]
157 157 """
158 158 records = []
159 159 try:
160 160 f = self._repo.vfs(self.statepathv2)
161 161 data = f.read()
162 162 off = 0
163 163 end = len(data)
164 164 while off < end:
165 165 rtype = data[off]
166 166 off += 1
167 167 length = _unpack('>I', data[off:(off + 4)])[0]
168 168 off += 4
169 169 record = data[off:(off + length)]
170 170 off += length
171 171 records.append((rtype, record))
172 172 f.close()
173 173 except IOError, err:
174 174 if err.errno != errno.ENOENT:
175 175 raise
176 176 return records
177 177
178 178 def active(self):
179 179 """Whether mergestate is active.
180 180
181 181 Returns True if there appears to be mergestate. This is a rough proxy
182 182 for "is a merge in progress."
183 183 """
184 184 # Check local variables before looking at filesystem for performance
185 185 # reasons.
186 186 return bool(self._local) or bool(self._state) or \
187 187 self._repo.vfs.exists(self.statepathv1) or \
188 188 self._repo.vfs.exists(self.statepathv2)
189 189
190 190 def commit(self):
191 191 """Write current state on disk (if necessary)"""
192 192 if self._dirty:
193 193 records = []
194 194 records.append(('L', hex(self._local)))
195 195 records.append(('O', hex(self._other)))
196 196 for d, v in self._state.iteritems():
197 197 records.append(('F', '\0'.join([d] + v)))
198 198 self._writerecords(records)
199 199 self._dirty = False
200 200
201 201 def _writerecords(self, records):
202 202 """Write current state on disk (both v1 and v2)"""
203 203 self._writerecordsv1(records)
204 204 self._writerecordsv2(records)
205 205
206 206 def _writerecordsv1(self, records):
207 207 """Write current state on disk in a version 1 file"""
208 208 f = self._repo.vfs(self.statepathv1, 'w')
209 209 irecords = iter(records)
210 210 lrecords = irecords.next()
211 211 assert lrecords[0] == 'L'
212 212 f.write(hex(self._local) + '\n')
213 213 for rtype, data in irecords:
214 214 if rtype == 'F':
215 215 f.write('%s\n' % _droponode(data))
216 216 f.close()
217 217
218 218 def _writerecordsv2(self, records):
219 219 """Write current state on disk in a version 2 file"""
220 220 f = self._repo.vfs(self.statepathv2, 'w')
221 221 for key, data in records:
222 222 assert len(key) == 1
223 223 format = '>sI%is' % len(data)
224 224 f.write(_pack(format, key, len(data), data))
225 225 f.close()
226 226
227 227 def add(self, fcl, fco, fca, fd):
228 228 """add a new (potentially?) conflicting file the merge state
229 229 fcl: file context for local,
230 230 fco: file context for remote,
231 231 fca: file context for ancestors,
232 232 fd: file path of the resulting merge.
233 233
234 234 note: also write the local version to the `.hg/merge` directory.
235 235 """
236 236 hash = util.sha1(fcl.path()).hexdigest()
237 237 self._repo.vfs.write('merge/' + hash, fcl.data())
238 238 self._state[fd] = ['u', hash, fcl.path(),
239 239 fca.path(), hex(fca.filenode()),
240 240 fco.path(), hex(fco.filenode()),
241 241 fcl.flags()]
242 242 self._dirty = True
243 243
244 244 def __contains__(self, dfile):
245 245 return dfile in self._state
246 246
247 247 def __getitem__(self, dfile):
248 248 return self._state[dfile][0]
249 249
250 250 def __iter__(self):
251 251 return iter(sorted(self._state))
252 252
253 253 def files(self):
254 254 return self._state.keys()
255 255
256 256 def mark(self, dfile, state):
257 257 self._state[dfile][0] = state
258 258 self._dirty = True
259 259
260 260 def unresolved(self):
261 261 """Obtain the paths of unresolved files."""
262 262
263 263 for f, entry in self._state.items():
264 264 if entry[0] == 'u':
265 265 yield f
266 266
267 267 def resolve(self, dfile, wctx, labels=None):
268 268 """rerun merge process for file path `dfile`"""
269 269 if self[dfile] == 'r':
270 270 return 0
271 271 stateentry = self._state[dfile]
272 272 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
273 273 octx = self._repo[self._other]
274 274 fcd = wctx[dfile]
275 275 fco = octx[ofile]
276 276 fca = self._repo.filectx(afile, fileid=anode)
277 277 # "premerge" x flags
278 278 flo = fco.flags()
279 279 fla = fca.flags()
280 280 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
281 281 if fca.node() == nullid:
282 282 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
283 283 afile)
284 284 elif flags == fla:
285 285 flags = flo
286 286 # restore local
287 287 f = self._repo.vfs('merge/' + hash)
288 288 self._repo.wwrite(dfile, f.read(), flags)
289 289 f.close()
290 290 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca,
291 291 labels=labels)
292 292 if r is None:
293 293 # no real conflict
294 294 del self._state[dfile]
295 295 self._dirty = True
296 296 elif not r:
297 297 self.mark(dfile, 'r')
298 298 return r
299 299
300 300 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
301 301 if f2 is None:
302 302 f2 = f
303 303 return (os.path.isfile(repo.wjoin(f))
304 304 and repo.wvfs.audit.check(f)
305 305 and repo.dirstate.normalize(f) not in repo.dirstate
306 306 and mctx[f2].cmp(wctx[f]))
307 307
308 308 def _checkunknownfiles(repo, wctx, mctx, force, actions):
309 309 """
310 310 Considers any actions that care about the presence of conflicting unknown
311 311 files. For some actions, the result is to abort; for others, it is to
312 312 choose a different action.
313 313 """
314 314 aborts = []
315 315 if not force:
316 316 for f, (m, args, msg) in actions.iteritems():
317 317 if m in ('c', 'dc'):
318 318 if _checkunknownfile(repo, wctx, mctx, f):
319 319 aborts.append(f)
320 320 elif m == 'dg':
321 321 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
322 322 aborts.append(f)
323 323
324 324 for f in sorted(aborts):
325 325 repo.ui.warn(_("%s: untracked file differs\n") % f)
326 326 if aborts:
327 327 raise util.Abort(_("untracked files in working directory differ "
328 328 "from files in requested revision"))
329 329
330 330 for f, (m, args, msg) in actions.iteritems():
331 331 if m == 'c':
332 332 actions[f] = ('g', args, msg)
333 333 elif m == 'cm':
334 334 fl2, anc = args
335 335 different = _checkunknownfile(repo, wctx, mctx, f)
336 336 if different:
337 337 actions[f] = ('m', (f, f, None, False, anc),
338 338 "remote differs from untracked local")
339 339 else:
340 340 actions[f] = ('g', (fl2,), "remote created")
341 341
342 342 def _forgetremoved(wctx, mctx, branchmerge):
343 343 """
344 344 Forget removed files
345 345
346 346 If we're jumping between revisions (as opposed to merging), and if
347 347 neither the working directory nor the target rev has the file,
348 348 then we need to remove it from the dirstate, to prevent the
349 349 dirstate from listing the file when it is no longer in the
350 350 manifest.
351 351
352 352 If we're merging, and the other revision has removed a file
353 353 that is not present in the working directory, we need to mark it
354 354 as removed.
355 355 """
356 356
357 357 actions = {}
358 358 m = 'f'
359 359 if branchmerge:
360 360 m = 'r'
361 361 for f in wctx.deleted():
362 362 if f not in mctx:
363 363 actions[f] = m, None, "forget deleted"
364 364
365 365 if not branchmerge:
366 366 for f in wctx.removed():
367 367 if f not in mctx:
368 368 actions[f] = 'f', None, "forget removed"
369 369
370 370 return actions
371 371
372 372 def _checkcollision(repo, wmf, actions):
373 373 # build provisional merged manifest up
374 374 pmmf = set(wmf)
375 375
376 376 if actions:
377 377 # k, dr, e and rd are no-op
378 378 for m in 'a', 'f', 'g', 'cd', 'dc':
379 379 for f, args, msg in actions[m]:
380 380 pmmf.add(f)
381 381 for f, args, msg in actions['r']:
382 382 pmmf.discard(f)
383 383 for f, args, msg in actions['dm']:
384 384 f2, flags = args
385 385 pmmf.discard(f2)
386 386 pmmf.add(f)
387 387 for f, args, msg in actions['dg']:
388 388 pmmf.add(f)
389 389 for f, args, msg in actions['m']:
390 390 f1, f2, fa, move, anc = args
391 391 if move:
392 392 pmmf.discard(f1)
393 393 pmmf.add(f)
394 394
395 395 # check case-folding collision in provisional merged manifest
396 396 foldmap = {}
397 397 for f in sorted(pmmf):
398 398 fold = util.normcase(f)
399 399 if fold in foldmap:
400 400 raise util.Abort(_("case-folding collision between %s and %s")
401 401 % (f, foldmap[fold]))
402 402 foldmap[fold] = f
403 403
404 404 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial,
405 405 acceptremote, followcopies):
406 406 """
407 407 Merge p1 and p2 with ancestor pa and generate merge action list
408 408
409 409 branchmerge and force are as passed in to update
410 410 partial = function to filter file lists
411 411 acceptremote = accept the incoming changes without prompting
412 412 """
413 413
414 414 copy, movewithdir, diverge, renamedelete = {}, {}, {}, {}
415 415
416 416 # manifests fetched in order are going to be faster, so prime the caches
417 417 [x.manifest() for x in
418 418 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
419 419
420 420 if followcopies:
421 421 ret = copies.mergecopies(repo, wctx, p2, pa)
422 422 copy, movewithdir, diverge, renamedelete = ret
423 423
424 424 repo.ui.note(_("resolving manifests\n"))
425 425 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
426 426 % (bool(branchmerge), bool(force), bool(partial)))
427 427 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
428 428
429 429 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
430 430 copied = set(copy.values())
431 431 copied.update(movewithdir.values())
432 432
433 433 if '.hgsubstate' in m1:
434 434 # check whether sub state is modified
435 435 for s in sorted(wctx.substate):
436 436 if wctx.sub(s).dirty():
437 437 m1['.hgsubstate'] += '+'
438 438 break
439 439
440 440 # Compare manifests
441 441 diff = m1.diff(m2)
442 442
443 443 actions = {}
444 444 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
445 445 if partial and not partial(f):
446 446 continue
447 447 if n1 and n2: # file exists on both local and remote side
448 448 if f not in ma:
449 449 fa = copy.get(f, None)
450 450 if fa is not None:
451 451 actions[f] = ('m', (f, f, fa, False, pa.node()),
452 452 "both renamed from " + fa)
453 453 else:
454 454 actions[f] = ('m', (f, f, None, False, pa.node()),
455 455 "both created")
456 456 else:
457 457 a = ma[f]
458 458 fla = ma.flags(f)
459 459 nol = 'l' not in fl1 + fl2 + fla
460 460 if n2 == a and fl2 == fla:
461 461 actions[f] = ('k' , (), "remote unchanged")
462 462 elif n1 == a and fl1 == fla: # local unchanged - use remote
463 463 if n1 == n2: # optimization: keep local content
464 464 actions[f] = ('e', (fl2,), "update permissions")
465 465 else:
466 466 actions[f] = ('g', (fl2,), "remote is newer")
467 467 elif nol and n2 == a: # remote only changed 'x'
468 468 actions[f] = ('e', (fl2,), "update permissions")
469 469 elif nol and n1 == a: # local only changed 'x'
470 470 actions[f] = ('g', (fl1,), "remote is newer")
471 471 else: # both changed something
472 472 actions[f] = ('m', (f, f, f, False, pa.node()),
473 473 "versions differ")
474 474 elif n1: # file exists only on local side
475 475 if f in copied:
476 476 pass # we'll deal with it on m2 side
477 477 elif f in movewithdir: # directory rename, move local
478 478 f2 = movewithdir[f]
479 479 if f2 in m2:
480 480 actions[f2] = ('m', (f, f2, None, True, pa.node()),
481 481 "remote directory rename, both created")
482 482 else:
483 483 actions[f2] = ('dm', (f, fl1),
484 484 "remote directory rename - move from " + f)
485 485 elif f in copy:
486 486 f2 = copy[f]
487 487 actions[f] = ('m', (f, f2, f2, False, pa.node()),
488 488 "local copied/moved from " + f2)
489 489 elif f in ma: # clean, a different, no remote
490 490 if n1 != ma[f]:
491 491 if acceptremote:
492 492 actions[f] = ('r', None, "remote delete")
493 493 else:
494 494 actions[f] = ('cd', None, "prompt changed/deleted")
495 495 elif n1[20:] == 'a':
496 496 # This extra 'a' is added by working copy manifest to mark
497 497 # the file as locally added. We should forget it instead of
498 498 # deleting it.
499 499 actions[f] = ('f', None, "remote deleted")
500 500 else:
501 501 actions[f] = ('r', None, "other deleted")
502 502 elif n2: # file exists only on remote side
503 503 if f in copied:
504 504 pass # we'll deal with it on m1 side
505 505 elif f in movewithdir:
506 506 f2 = movewithdir[f]
507 507 if f2 in m1:
508 508 actions[f2] = ('m', (f2, f, None, False, pa.node()),
509 509 "local directory rename, both created")
510 510 else:
511 511 actions[f2] = ('dg', (f, fl2),
512 512 "local directory rename - get from " + f)
513 513 elif f in copy:
514 514 f2 = copy[f]
515 515 if f2 in m2:
516 516 actions[f] = ('m', (f2, f, f2, False, pa.node()),
517 517 "remote copied from " + f2)
518 518 else:
519 519 actions[f] = ('m', (f2, f, f2, True, pa.node()),
520 520 "remote moved from " + f2)
521 521 elif f not in ma:
522 522 # local unknown, remote created: the logic is described by the
523 523 # following table:
524 524 #
525 525 # force branchmerge different | action
526 526 # n * * | create
527 527 # y n * | create
528 528 # y y n | create
529 529 # y y y | merge
530 530 #
531 531 # Checking whether the files are different is expensive, so we
532 532 # don't do that when we can avoid it.
533 533 if not force:
534 534 actions[f] = ('c', (fl2,), "remote created")
535 535 elif not branchmerge:
536 536 actions[f] = ('c', (fl2,), "remote created")
537 537 else:
538 538 actions[f] = ('cm', (fl2, pa.node()),
539 539 "remote created, get or merge")
540 540 elif n2 != ma[f]:
541 541 if acceptremote:
542 542 actions[f] = ('c', (fl2,), "remote recreating")
543 543 else:
544 544 actions[f] = ('dc', (fl2,), "prompt deleted/changed")
545 545
546 546 return actions, diverge, renamedelete
547 547
548 548 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
549 549 """Resolves false conflicts where the nodeid changed but the content
550 550 remained the same."""
551 551
552 552 for f, (m, args, msg) in actions.items():
553 553 if m == 'cd' and f in ancestor and not wctx[f].cmp(ancestor[f]):
554 554 # local did change but ended up with same content
555 555 actions[f] = 'r', None, "prompt same"
556 556 elif m == 'dc' and f in ancestor and not mctx[f].cmp(ancestor[f]):
557 557 # remote did change but ended up with same content
558 558 del actions[f] # don't get = keep local deleted
559 559
560 560 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force, partial,
561 561 acceptremote, followcopies):
562 562 "Calculate the actions needed to merge mctx into wctx using ancestors"
563 563
564 564 if len(ancestors) == 1: # default
565 565 actions, diverge, renamedelete = manifestmerge(
566 566 repo, wctx, mctx, ancestors[0], branchmerge, force, partial,
567 567 acceptremote, followcopies)
568 568 _checkunknownfiles(repo, wctx, mctx, force, actions)
569 569
570 570 else: # only when merge.preferancestor=* - the default
571 571 repo.ui.note(
572 572 _("note: merging %s and %s using bids from ancestors %s\n") %
573 573 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
574 574
575 575 # Call for bids
576 576 fbids = {} # mapping filename to bids (action method to list af actions)
577 577 diverge, renamedelete = None, None
578 578 for ancestor in ancestors:
579 579 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
580 580 actions, diverge1, renamedelete1 = manifestmerge(
581 581 repo, wctx, mctx, ancestor, branchmerge, force, partial,
582 582 acceptremote, followcopies)
583 583 _checkunknownfiles(repo, wctx, mctx, force, actions)
584 584 if diverge is None: # and renamedelete is None.
585 585 # Arbitrarily pick warnings from first iteration
586 586 diverge = diverge1
587 587 renamedelete = renamedelete1
588 588 for f, a in sorted(actions.iteritems()):
589 589 m, args, msg = a
590 590 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
591 591 if f in fbids:
592 592 d = fbids[f]
593 593 if m in d:
594 594 d[m].append(a)
595 595 else:
596 596 d[m] = [a]
597 597 else:
598 598 fbids[f] = {m: [a]}
599 599
600 600 # Pick the best bid for each file
601 601 repo.ui.note(_('\nauction for merging merge bids\n'))
602 602 actions = {}
603 603 for f, bids in sorted(fbids.items()):
604 604 # bids is a mapping from action method to list af actions
605 605 # Consensus?
606 606 if len(bids) == 1: # all bids are the same kind of method
607 607 m, l = bids.items()[0]
608 608 if util.all(a == l[0] for a in l[1:]): # len(bids) is > 1
609 609 repo.ui.note(" %s: consensus for %s\n" % (f, m))
610 610 actions[f] = l[0]
611 611 continue
612 612 # If keep is an option, just do it.
613 613 if 'k' in bids:
614 614 repo.ui.note(" %s: picking 'keep' action\n" % f)
615 615 actions[f] = bids['k'][0]
616 616 continue
617 617 # If there are gets and they all agree [how could they not?], do it.
618 618 if 'g' in bids:
619 619 ga0 = bids['g'][0]
620 620 if util.all(a == ga0 for a in bids['g'][1:]):
621 621 repo.ui.note(" %s: picking 'get' action\n" % f)
622 622 actions[f] = ga0
623 623 continue
624 624 # TODO: Consider other simple actions such as mode changes
625 625 # Handle inefficient democrazy.
626 626 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
627 627 for m, l in sorted(bids.items()):
628 628 for _f, args, msg in l:
629 629 repo.ui.note(' %s -> %s\n' % (msg, m))
630 630 # Pick random action. TODO: Instead, prompt user when resolving
631 631 m, l = bids.items()[0]
632 632 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
633 633 (f, m))
634 634 actions[f] = l[0]
635 635 continue
636 636 repo.ui.note(_('end of auction\n\n'))
637 637
638 638 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
639 639
640 640 if wctx.rev() is None:
641 641 fractions = _forgetremoved(wctx, mctx, branchmerge)
642 642 actions.update(fractions)
643 643
644 644 return actions, diverge, renamedelete
645 645
646 646 def batchremove(repo, actions):
647 647 """apply removes to the working directory
648 648
649 649 yields tuples for progress updates
650 650 """
651 651 verbose = repo.ui.verbose
652 652 unlink = util.unlinkpath
653 653 wjoin = repo.wjoin
654 654 audit = repo.wvfs.audit
655 655 i = 0
656 656 for f, args, msg in actions:
657 657 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
658 658 if verbose:
659 659 repo.ui.note(_("removing %s\n") % f)
660 660 audit(f)
661 661 try:
662 662 unlink(wjoin(f), ignoremissing=True)
663 663 except OSError, inst:
664 664 repo.ui.warn(_("update failed to remove %s: %s!\n") %
665 665 (f, inst.strerror))
666 666 if i == 100:
667 667 yield i, f
668 668 i = 0
669 669 i += 1
670 670 if i > 0:
671 671 yield i, f
672 672
673 673 def batchget(repo, mctx, actions):
674 674 """apply gets to the working directory
675 675
676 676 mctx is the context to get from
677 677
678 678 yields tuples for progress updates
679 679 """
680 680 verbose = repo.ui.verbose
681 681 fctx = mctx.filectx
682 682 wwrite = repo.wwrite
683 683 i = 0
684 684 for f, args, msg in actions:
685 685 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
686 686 if verbose:
687 687 repo.ui.note(_("getting %s\n") % f)
688 688 wwrite(f, fctx(f).data(), args[0])
689 689 if i == 100:
690 690 yield i, f
691 691 i = 0
692 692 i += 1
693 693 if i > 0:
694 694 yield i, f
695 695
696 696 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
697 697 """apply the merge action list to the working directory
698 698
699 699 wctx is the working copy context
700 700 mctx is the context to be merged into the working copy
701 701
702 702 Return a tuple of counts (updated, merged, removed, unresolved) that
703 703 describes how many files were affected by the update.
704 704 """
705 705
706 706 updated, merged, removed, unresolved = 0, 0, 0, 0
707 707 ms = mergestate(repo)
708 708 ms.reset(wctx.p1().node(), mctx.node())
709 709 moves = []
710 710 for m, l in actions.items():
711 711 l.sort()
712 712
713 713 # prescan for merges
714 714 for f, args, msg in actions['m']:
715 715 f1, f2, fa, move, anc = args
716 716 if f == '.hgsubstate': # merged internally
717 717 continue
718 718 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
719 719 fcl = wctx[f1]
720 720 fco = mctx[f2]
721 721 actx = repo[anc]
722 722 if fa in actx:
723 723 fca = actx[fa]
724 724 else:
725 725 fca = repo.filectx(f1, fileid=nullrev)
726 726 ms.add(fcl, fco, fca, f)
727 727 if f1 != f and move:
728 728 moves.append(f1)
729 729
730 730 audit = repo.wvfs.audit
731 731 _updating = _('updating')
732 732 _files = _('files')
733 733 progress = repo.ui.progress
734 734
735 735 # remove renamed files after safely stored
736 736 for f in moves:
737 737 if os.path.lexists(repo.wjoin(f)):
738 738 repo.ui.debug("removing %s\n" % f)
739 739 audit(f)
740 740 util.unlinkpath(repo.wjoin(f))
741 741
742 742 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
743 743
744 744 def dirtysubstate():
745 745 # mark '.hgsubstate' as possibly dirty forcibly, because
746 746 # modified '.hgsubstate' is misunderstood as clean,
747 747 # when both st_size/st_mtime of '.hgsubstate' aren't changed,
748 748 # even if "submerge" fails and '.hgsubstate' is inconsistent
749 749 repo.dirstate.normallookup('.hgsubstate')
750 750
751 751 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
752 752 dirtysubstate()
753 753 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
754 754
755 755 # remove in parallel (must come first)
756 756 z = 0
757 757 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
758 758 for i, item in prog:
759 759 z += i
760 760 progress(_updating, z, item=item, total=numupdates, unit=_files)
761 761 removed = len(actions['r'])
762 762
763 763 # get in parallel
764 764 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
765 765 for i, item in prog:
766 766 z += i
767 767 progress(_updating, z, item=item, total=numupdates, unit=_files)
768 768 updated = len(actions['g'])
769 769
770 770 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
771 771 dirtysubstate()
772 772 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
773 773
774 774 # forget (manifest only, just log it) (must come first)
775 775 for f, args, msg in actions['f']:
776 776 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
777 777 z += 1
778 778 progress(_updating, z, item=f, total=numupdates, unit=_files)
779 779
780 780 # re-add (manifest only, just log it)
781 781 for f, args, msg in actions['a']:
782 782 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
783 783 z += 1
784 784 progress(_updating, z, item=f, total=numupdates, unit=_files)
785 785
786 786 # keep (noop, just log it)
787 787 for f, args, msg in actions['k']:
788 788 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
789 789 # no progress
790 790
791 791 # merge
792 792 for f, args, msg in actions['m']:
793 793 repo.ui.debug(" %s: %s -> m\n" % (f, msg))
794 794 z += 1
795 795 progress(_updating, z, item=f, total=numupdates, unit=_files)
796 796 if f == '.hgsubstate': # subrepo states need updating
797 797 dirtysubstate()
798 798 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
799 799 overwrite)
800 800 continue
801 801 audit(f)
802 802 r = ms.resolve(f, wctx, labels=labels)
803 803 if r is not None and r > 0:
804 804 unresolved += 1
805 805 else:
806 806 if r is None:
807 807 updated += 1
808 808 else:
809 809 merged += 1
810 810
811 811 # directory rename, move local
812 812 for f, args, msg in actions['dm']:
813 813 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
814 814 z += 1
815 815 progress(_updating, z, item=f, total=numupdates, unit=_files)
816 816 f0, flags = args
817 817 repo.ui.note(_("moving %s to %s\n") % (f0, f))
818 818 audit(f)
819 819 repo.wwrite(f, wctx.filectx(f0).data(), flags)
820 820 util.unlinkpath(repo.wjoin(f0))
821 821 updated += 1
822 822
823 823 # local directory rename, get
824 824 for f, args, msg in actions['dg']:
825 825 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
826 826 z += 1
827 827 progress(_updating, z, item=f, total=numupdates, unit=_files)
828 828 f0, flags = args
829 829 repo.ui.note(_("getting %s to %s\n") % (f0, f))
830 830 repo.wwrite(f, mctx.filectx(f0).data(), flags)
831 831 updated += 1
832 832
833 833 # exec
834 834 for f, args, msg in actions['e']:
835 835 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
836 836 z += 1
837 837 progress(_updating, z, item=f, total=numupdates, unit=_files)
838 838 flags, = args
839 839 audit(f)
840 840 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
841 841 updated += 1
842 842
843 843 ms.commit()
844 844 progress(_updating, None, total=numupdates, unit=_files)
845 845
846 846 return updated, merged, removed, unresolved
847 847
848 848 def recordupdates(repo, actions, branchmerge):
849 849 "record merge actions to the dirstate"
850 850 # remove (must come first)
851 851 for f, args, msg in actions['r']:
852 852 if branchmerge:
853 853 repo.dirstate.remove(f)
854 854 else:
855 855 repo.dirstate.drop(f)
856 856
857 857 # forget (must come first)
858 858 for f, args, msg in actions['f']:
859 859 repo.dirstate.drop(f)
860 860
861 861 # re-add
862 862 for f, args, msg in actions['a']:
863 863 if not branchmerge:
864 864 repo.dirstate.add(f)
865 865
866 866 # exec change
867 867 for f, args, msg in actions['e']:
868 868 repo.dirstate.normallookup(f)
869 869
870 870 # keep
871 871 for f, args, msg in actions['k']:
872 872 pass
873 873
874 874 # get
875 875 for f, args, msg in actions['g']:
876 876 if branchmerge:
877 877 repo.dirstate.otherparent(f)
878 878 else:
879 879 repo.dirstate.normal(f)
880 880
881 881 # merge
882 882 for f, args, msg in actions['m']:
883 883 f1, f2, fa, move, anc = args
884 884 if branchmerge:
885 885 # We've done a branch merge, mark this file as merged
886 886 # so that we properly record the merger later
887 887 repo.dirstate.merge(f)
888 888 if f1 != f2: # copy/rename
889 889 if move:
890 890 repo.dirstate.remove(f1)
891 891 if f1 != f:
892 892 repo.dirstate.copy(f1, f)
893 893 else:
894 894 repo.dirstate.copy(f2, f)
895 895 else:
896 896 # We've update-merged a locally modified file, so
897 897 # we set the dirstate to emulate a normal checkout
898 898 # of that file some time in the past. Thus our
899 899 # merge will appear as a normal local file
900 900 # modification.
901 901 if f2 == f: # file not locally copied/moved
902 902 repo.dirstate.normallookup(f)
903 903 if move:
904 904 repo.dirstate.drop(f1)
905 905
906 906 # directory rename, move local
907 907 for f, args, msg in actions['dm']:
908 908 f0, flag = args
909 909 if branchmerge:
910 910 repo.dirstate.add(f)
911 911 repo.dirstate.remove(f0)
912 912 repo.dirstate.copy(f0, f)
913 913 else:
914 914 repo.dirstate.normal(f)
915 915 repo.dirstate.drop(f0)
916 916
917 917 # directory rename, get
918 918 for f, args, msg in actions['dg']:
919 919 f0, flag = args
920 920 if branchmerge:
921 921 repo.dirstate.add(f)
922 922 repo.dirstate.copy(f0, f)
923 923 else:
924 924 repo.dirstate.normal(f)
925 925
926 926 def update(repo, node, branchmerge, force, partial, ancestor=None,
927 927 mergeancestor=False, labels=None):
928 928 """
929 929 Perform a merge between the working directory and the given node
930 930
931 931 node = the node to update to, or None if unspecified
932 932 branchmerge = whether to merge between branches
933 933 force = whether to force branch merging or file overwriting
934 934 partial = a function to filter file lists (dirstate not updated)
935 935 mergeancestor = whether it is merging with an ancestor. If true,
936 936 we should accept the incoming changes for any prompts that occur.
937 937 If false, merging with an ancestor (fast-forward) is only allowed
938 938 between different named branches. This flag is used by rebase extension
939 939 as a temporary fix and should be avoided in general.
940 940
941 941 The table below shows all the behaviors of the update command
942 942 given the -c and -C or no options, whether the working directory
943 943 is dirty, whether a revision is specified, and the relationship of
944 944 the parent rev to the target rev (linear, on the same named
945 945 branch, or on another named branch).
946 946
947 947 This logic is tested by test-update-branches.t.
948 948
949 949 -c -C dirty rev | linear same cross
950 950 n n n n | ok (1) x
951 951 n n n y | ok ok ok
952 952 n n y n | merge (2) (2)
953 953 n n y y | merge (3) (3)
954 954 n y * * | --- discard ---
955 955 y n y * | --- (4) ---
956 956 y n n * | --- ok ---
957 957 y y * * | --- (5) ---
958 958
959 959 x = can't happen
960 960 * = don't-care
961 961 1 = abort: not a linear update (merge or update --check to force update)
962 962 2 = abort: uncommitted changes (commit and merge, or update --clean to
963 963 discard changes)
964 964 3 = abort: uncommitted changes (commit or update --clean to discard changes)
965 965 4 = abort: uncommitted changes (checked in commands.py)
966 966 5 = incompatible options (checked in commands.py)
967 967
968 968 Return the same tuple as applyupdates().
969 969 """
970 970
971 971 onode = node
972 972 wlock = repo.wlock()
973 973 try:
974 974 wc = repo[None]
975 975 pl = wc.parents()
976 976 p1 = pl[0]
977 977 pas = [None]
978 978 if ancestor is not None:
979 979 pas = [repo[ancestor]]
980 980
981 981 if node is None:
982 982 # Here is where we should consider bookmarks, divergent bookmarks,
983 983 # foreground changesets (successors), and tip of current branch;
984 984 # but currently we are only checking the branch tips.
985 985 try:
986 986 node = repo.branchtip(wc.branch())
987 987 except errormod.RepoLookupError:
988 988 if wc.branch() == 'default': # no default branch!
989 989 node = repo.lookup('tip') # update to tip
990 990 else:
991 991 raise util.Abort(_("branch %s not found") % wc.branch())
992 992
993 993 if p1.obsolete() and not p1.children():
994 994 # allow updating to successors
995 995 successors = obsolete.successorssets(repo, p1.node())
996 996
997 997 # behavior of certain cases is as follows,
998 998 #
999 999 # divergent changesets: update to highest rev, similar to what
1000 1000 # is currently done when there are more than one head
1001 1001 # (i.e. 'tip')
1002 1002 #
1003 1003 # replaced changesets: same as divergent except we know there
1004 1004 # is no conflict
1005 1005 #
1006 1006 # pruned changeset: no update is done; though, we could
1007 1007 # consider updating to the first non-obsolete parent,
1008 1008 # similar to what is current done for 'hg prune'
1009 1009
1010 1010 if successors:
1011 1011 # flatten the list here handles both divergent (len > 1)
1012 1012 # and the usual case (len = 1)
1013 1013 successors = [n for sub in successors for n in sub]
1014 1014
1015 1015 # get the max revision for the given successors set,
1016 1016 # i.e. the 'tip' of a set
1017 1017 node = repo.revs('max(%ln)', successors).first()
1018 1018 pas = [p1]
1019 1019
1020 1020 overwrite = force and not branchmerge
1021 1021
1022 1022 p2 = repo[node]
1023 1023 if pas[0] is None:
1024 1024 if repo.ui.config('merge', 'preferancestor', '*') == '*':
1025 1025 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1026 1026 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1027 1027 else:
1028 1028 pas = [p1.ancestor(p2, warn=branchmerge)]
1029 1029
1030 1030 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1031 1031
1032 1032 ### check phase
1033 1033 if not overwrite and len(pl) > 1:
1034 1034 raise util.Abort(_("outstanding uncommitted merge"))
1035 1035 if branchmerge:
1036 1036 if pas == [p2]:
1037 1037 raise util.Abort(_("merging with a working directory ancestor"
1038 1038 " has no effect"))
1039 1039 elif pas == [p1]:
1040 1040 if not mergeancestor and p1.branch() == p2.branch():
1041 1041 raise util.Abort(_("nothing to merge"),
1042 1042 hint=_("use 'hg update' "
1043 1043 "or check 'hg heads'"))
1044 1044 if not force and (wc.files() or wc.deleted()):
1045 1045 raise util.Abort(_("uncommitted changes"),
1046 1046 hint=_("use 'hg status' to list changes"))
1047 1047 for s in sorted(wc.substate):
1048 1048 wc.sub(s).bailifchanged()
1049 1049
1050 1050 elif not overwrite:
1051 1051 if p1 == p2: # no-op update
1052 1052 # call the hooks and exit early
1053 1053 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1054 1054 repo.hook('update', parent1=xp2, parent2='', error=0)
1055 1055 return 0, 0, 0, 0
1056 1056
1057 1057 if pas not in ([p1], [p2]): # nonlinear
1058 1058 dirty = wc.dirty(missing=True)
1059 1059 if dirty or onode is None:
1060 1060 # Branching is a bit strange to ensure we do the minimal
1061 1061 # amount of call to obsolete.background.
1062 1062 foreground = obsolete.foreground(repo, [p1.node()])
1063 1063 # note: the <node> variable contains a random identifier
1064 1064 if repo[node].node() in foreground:
1065 1065 pas = [p1] # allow updating to successors
1066 1066 elif dirty:
1067 1067 msg = _("uncommitted changes")
1068 1068 if onode is None:
1069 1069 hint = _("commit and merge, or update --clean to"
1070 1070 " discard changes")
1071 1071 else:
1072 1072 hint = _("commit or update --clean to discard"
1073 1073 " changes")
1074 1074 raise util.Abort(msg, hint=hint)
1075 1075 else: # node is none
1076 1076 msg = _("not a linear update")
1077 1077 hint = _("merge or update --check to force update")
1078 1078 raise util.Abort(msg, hint=hint)
1079 1079 else:
1080 1080 # Allow jumping branches if clean and specific rev given
1081 1081 pas = [p1]
1082 1082
1083 1083 followcopies = False
1084 1084 if overwrite:
1085 1085 pas = [wc]
1086 1086 elif pas == [p2]: # backwards
1087 1087 pas = [wc.p1()]
1088 1088 elif not branchmerge and not wc.dirty(missing=True):
1089 1089 pass
1090 1090 elif pas[0] and repo.ui.configbool('merge', 'followcopies', True):
1091 1091 followcopies = True
1092 1092
1093 1093 ### calculate phase
1094 1094 actionbyfile, diverge, renamedelete = calculateupdates(
1095 1095 repo, wc, p2, pas, branchmerge, force, partial, mergeancestor,
1096 1096 followcopies)
1097 1097 # Convert to dictionary-of-lists format
1098 1098 actions = dict((m, []) for m in 'a f g cd dc r dm dg m e k'.split())
1099 1099 for f, (m, args, msg) in actionbyfile.iteritems():
1100 1100 if m not in actions:
1101 1101 actions[m] = []
1102 1102 actions[m].append((f, args, msg))
1103 1103
1104 1104 if not util.checkcase(repo.path):
1105 1105 # check collision between files only in p2 for clean update
1106 1106 if (not branchmerge and
1107 1107 (force or not wc.dirty(missing=True, branch=False))):
1108 1108 _checkcollision(repo, p2.manifest(), None)
1109 1109 else:
1110 1110 _checkcollision(repo, wc.manifest(), actions)
1111 1111
1112 1112 # Prompt and create actions. TODO: Move this towards resolve phase.
1113 1113 for f, args, msg in sorted(actions['cd']):
1114 1114 if repo.ui.promptchoice(
1115 1115 _("local changed %s which remote deleted\n"
1116 1116 "use (c)hanged version or (d)elete?"
1117 1117 "$$ &Changed $$ &Delete") % f, 0):
1118 1118 actions['r'].append((f, None, "prompt delete"))
1119 1119 else:
1120 1120 actions['a'].append((f, None, "prompt keep"))
1121 1121 del actions['cd'][:]
1122 1122
1123 1123 for f, args, msg in sorted(actions['dc']):
1124 1124 flags, = args
1125 1125 if repo.ui.promptchoice(
1126 1126 _("remote changed %s which local deleted\n"
1127 1127 "use (c)hanged version or leave (d)eleted?"
1128 1128 "$$ &Changed $$ &Deleted") % f, 0) == 0:
1129 1129 actions['g'].append((f, (flags,), "prompt recreating"))
1130 1130 del actions['dc'][:]
1131 1131
1132 1132 ### apply phase
1133 1133 if not branchmerge: # just jump to the new rev
1134 1134 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1135 1135 if not partial:
1136 1136 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1137 1137 # note that we're in the middle of an update
1138 1138 repo.vfs.write('updatestate', p2.hex())
1139 1139
1140 1140 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1141 1141
1142 1142 # divergent renames
1143 1143 for f, fl in sorted(diverge.iteritems()):
1144 1144 repo.ui.warn(_("note: possible conflict - %s was renamed "
1145 1145 "multiple times to:\n") % f)
1146 1146 for nf in fl:
1147 1147 repo.ui.warn(" %s\n" % nf)
1148 1148
1149 1149 # rename and delete
1150 1150 for f, fl in sorted(renamedelete.iteritems()):
1151 1151 repo.ui.warn(_("note: possible conflict - %s was deleted "
1152 1152 "and renamed to:\n") % f)
1153 1153 for nf in fl:
1154 1154 repo.ui.warn(" %s\n" % nf)
1155 1155
1156 1156 if not partial:
1157 1157 repo.dirstate.beginparentchange()
1158 1158 repo.setparents(fp1, fp2)
1159 1159 recordupdates(repo, actions, branchmerge)
1160 1160 # update completed, clear state
1161 1161 util.unlink(repo.join('updatestate'))
1162 1162
1163 1163 if not branchmerge:
1164 1164 repo.dirstate.setbranch(p2.branch())
1165 1165 repo.dirstate.endparentchange()
1166 1166 finally:
1167 1167 wlock.release()
1168 1168
1169 1169 if not partial:
1170 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1170 def updatehook(parent1=xp1, parent2=xp2, error=stats[3]):
1171 repo.hook('update', parent1=parent1, parent2=parent2, error=error)
1172 repo._afterlock(updatehook)
1171 1173 return stats
1172 1174
1173 1175 def graft(repo, ctx, pctx, labels):
1174 1176 """Do a graft-like merge.
1175 1177
1176 1178 This is a merge where the merge ancestor is chosen such that one
1177 1179 or more changesets are grafted onto the current changeset. In
1178 1180 addition to the merge, this fixes up the dirstate to include only
1179 1181 a single parent and tries to duplicate any renames/copies
1180 1182 appropriately.
1181 1183
1182 1184 ctx - changeset to rebase
1183 1185 pctx - merge base, usually ctx.p1()
1184 1186 labels - merge labels eg ['local', 'graft']
1185 1187
1186 1188 """
1187 1189 # If we're grafting a descendant onto an ancestor, be sure to pass
1188 1190 # mergeancestor=True to update. This does two things: 1) allows the merge if
1189 1191 # the destination is the same as the parent of the ctx (so we can use graft
1190 1192 # to copy commits), and 2) informs update that the incoming changes are
1191 1193 # newer than the destination so it doesn't prompt about "remote changed foo
1192 1194 # which local deleted".
1193 1195 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
1194 1196
1195 1197 stats = update(repo, ctx.node(), True, True, False, pctx.node(),
1196 1198 mergeancestor=mergeancestor, labels=labels)
1197 1199
1198 1200 # drop the second merge parent
1199 1201 repo.dirstate.beginparentchange()
1200 1202 repo.setparents(repo['.'].node(), nullid)
1201 1203 repo.dirstate.write()
1202 1204 # fix up dirstate for copies and renames
1203 1205 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1204 1206 repo.dirstate.endparentchange()
1205 1207 return stats
@@ -1,688 +1,690 b''
1 1 commit hooks can see env vars
2 2 (and post-transaction one are run unlocked)
3 3
4 4 $ hg init a
5 5 $ cd a
6 6 $ cat > .hg/hgrc <<EOF
7 7 > [hooks]
8 8 > commit = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" commit"
9 9 > commit.b = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" commit.b"
10 10 > precommit = sh -c "HG_LOCAL= HG_NODE= HG_TAG= python \"$TESTDIR/printenv.py\" precommit"
11 11 > pretxncommit = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" pretxncommit"
12 12 > pretxncommit.tip = hg -q tip
13 13 > pre-identify = python "$TESTDIR/printenv.py" pre-identify 1
14 14 > pre-cat = python "$TESTDIR/printenv.py" pre-cat
15 15 > post-cat = python "$TESTDIR/printenv.py" post-cat
16 16 > pretxnopen = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" pretxnopen"
17 17 > pretxnclose = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" pretxnclose"
18 18 > txnclose = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" txnclose"
19 19 > txnabort = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" txnabort"
20 20 > txnclose.checklock = sh -c "hg debuglock > /dev/null"
21 21 > EOF
22 22 $ echo a > a
23 23 $ hg add a
24 24 $ hg commit -m a
25 25 precommit hook: HG_PARENT1=0000000000000000000000000000000000000000
26 26 pretxnopen hook: HG_TXNNAME=commit
27 27 pretxncommit hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000 HG_PENDING=$TESTTMP/a
28 28 0:cb9a9f314b8b
29 29 pretxnclose hook: HG_PENDING=$TESTTMP/a HG_PHASES_MOVED=1 HG_TXNID=TXN:* HG_XNNAME=commit (glob)
30 30 txnclose hook: HG_PHASES_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
31 31 commit hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
32 32 commit.b hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
33 33
34 34 $ hg clone . ../b
35 35 updating to branch default
36 36 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
37 37 $ cd ../b
38 38
39 39 changegroup hooks can see env vars
40 40
41 41 $ cat > .hg/hgrc <<EOF
42 42 > [hooks]
43 43 > prechangegroup = python "$TESTDIR/printenv.py" prechangegroup
44 44 > changegroup = python "$TESTDIR/printenv.py" changegroup
45 45 > incoming = python "$TESTDIR/printenv.py" incoming
46 46 > EOF
47 47
48 48 pretxncommit and commit hooks can see both parents of merge
49 49
50 50 $ cd ../a
51 51 $ echo b >> a
52 52 $ hg commit -m a1 -d "1 0"
53 53 precommit hook: HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
54 54 pretxnopen hook: HG_TXNNAME=commit
55 55 pretxncommit hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
56 56 1:ab228980c14d
57 57 pretxnclose hook: HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_XNNAME=commit (glob)
58 58 txnclose hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
59 59 commit hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
60 60 commit.b hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
61 61 $ hg update -C 0
62 62 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
63 63 $ echo b > b
64 64 $ hg add b
65 65 $ hg commit -m b -d '1 0'
66 66 precommit hook: HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
67 67 pretxnopen hook: HG_TXNNAME=commit
68 68 pretxncommit hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
69 69 2:ee9deb46ab31
70 70 pretxnclose hook: HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_XNNAME=commit (glob)
71 71 txnclose hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
72 72 commit hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
73 73 commit.b hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
74 74 created new head
75 75 $ hg merge 1
76 76 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
77 77 (branch merge, don't forget to commit)
78 78 $ hg commit -m merge -d '2 0'
79 79 precommit hook: HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
80 80 pretxnopen hook: HG_TXNNAME=commit
81 81 pretxncommit hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd HG_PENDING=$TESTTMP/a
82 82 3:07f3376c1e65
83 83 pretxnclose hook: HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_XNNAME=commit (glob)
84 84 txnclose hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
85 85 commit hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
86 86 commit.b hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
87 87
88 88 test generic hooks
89 89
90 90 $ hg id
91 91 pre-identify hook: HG_ARGS=id HG_OPTS={'bookmarks': None, 'branch': None, 'id': None, 'insecure': None, 'num': None, 'remotecmd': '', 'rev': '', 'ssh': '', 'tags': None} HG_PATS=[]
92 92 abort: pre-identify hook exited with status 1
93 93 [255]
94 94 $ hg cat b
95 95 pre-cat hook: HG_ARGS=cat b HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': ''} HG_PATS=['b']
96 96 b
97 97 post-cat hook: HG_ARGS=cat b HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': ''} HG_PATS=['b'] HG_RESULT=0
98 98
99 99 $ cd ../b
100 100 $ hg pull ../a
101 101 pulling from ../a
102 102 searching for changes
103 103 prechangegroup hook: HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
104 104 adding changesets
105 105 adding manifests
106 106 adding file changes
107 107 added 3 changesets with 2 changes to 2 files
108 108 changegroup hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
109 109 incoming hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
110 110 incoming hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
111 111 incoming hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
112 112 (run 'hg update' to get a working copy)
113 113
114 114 tag hooks can see env vars
115 115
116 116 $ cd ../a
117 117 $ cat >> .hg/hgrc <<EOF
118 118 > pretag = python "$TESTDIR/printenv.py" pretag
119 119 > tag = sh -c "HG_PARENT1= HG_PARENT2= python \"$TESTDIR/printenv.py\" tag"
120 120 > EOF
121 121 $ hg tag -d '3 0' a
122 122 pretag hook: HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
123 123 precommit hook: HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
124 124 pretxnopen hook: HG_TXNNAME=commit
125 125 pretxncommit hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PENDING=$TESTTMP/a
126 126 4:539e4b31b6dc
127 127 pretxnclose hook: HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_XNNAME=commit (glob)
128 128 tag hook: HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
129 129 txnclose hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
130 130 commit hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
131 131 commit.b hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
132 132 $ hg tag -l la
133 133 pretag hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=la
134 134 tag hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=la
135 135
136 136 pretag hook can forbid tagging
137 137
138 138 $ echo "pretag.forbid = python \"$TESTDIR/printenv.py\" pretag.forbid 1" >> .hg/hgrc
139 139 $ hg tag -d '4 0' fa
140 140 pretag hook: HG_LOCAL=0 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fa
141 141 pretag.forbid hook: HG_LOCAL=0 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fa
142 142 abort: pretag.forbid hook exited with status 1
143 143 [255]
144 144 $ hg tag -l fla
145 145 pretag hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fla
146 146 pretag.forbid hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fla
147 147 abort: pretag.forbid hook exited with status 1
148 148 [255]
149 149
150 150 pretxncommit hook can see changeset, can roll back txn, changeset no
151 151 more there after
152 152
153 153 $ echo "pretxncommit.forbid0 = hg tip -q" >> .hg/hgrc
154 154 $ echo "pretxncommit.forbid1 = python \"$TESTDIR/printenv.py\" pretxncommit.forbid 1" >> .hg/hgrc
155 155 $ echo z > z
156 156 $ hg add z
157 157 $ hg -q tip
158 158 4:539e4b31b6dc
159 159 $ hg commit -m 'fail' -d '4 0'
160 160 precommit hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
161 161 pretxnopen hook: HG_TXNNAME=commit
162 162 pretxncommit hook: HG_NODE=6f611f8018c10e827fee6bd2bc807f937e761567 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/a
163 163 5:6f611f8018c1
164 164 5:6f611f8018c1
165 165 pretxncommit.forbid hook: HG_NODE=6f611f8018c10e827fee6bd2bc807f937e761567 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/a
166 166 transaction abort!
167 167 txnabort hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
168 168 rollback completed
169 169 abort: pretxncommit.forbid1 hook exited with status 1
170 170 [255]
171 171 $ hg -q tip
172 172 4:539e4b31b6dc
173 173
174 174 (Check that no 'changelog.i.a' file were left behind)
175 175
176 176 $ ls -1 .hg/store/
177 177 00changelog.i
178 178 00manifest.i
179 179 data
180 180 fncache
181 181 journal.phaseroots
182 182 phaseroots
183 183 undo
184 184 undo.backup.fncache
185 185 undo.backupfiles
186 186 undo.phaseroots
187 187
188 188
189 189 precommit hook can prevent commit
190 190
191 191 $ echo "precommit.forbid = python \"$TESTDIR/printenv.py\" precommit.forbid 1" >> .hg/hgrc
192 192 $ hg commit -m 'fail' -d '4 0'
193 193 precommit hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
194 194 precommit.forbid hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
195 195 abort: precommit.forbid hook exited with status 1
196 196 [255]
197 197 $ hg -q tip
198 198 4:539e4b31b6dc
199 199
200 200 preupdate hook can prevent update
201 201
202 202 $ echo "preupdate = python \"$TESTDIR/printenv.py\" preupdate" >> .hg/hgrc
203 203 $ hg update 1
204 204 preupdate hook: HG_PARENT1=ab228980c14d
205 205 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
206 206
207 207 update hook
208 208
209 209 $ echo "update = python \"$TESTDIR/printenv.py\" update" >> .hg/hgrc
210 210 $ hg update
211 211 preupdate hook: HG_PARENT1=539e4b31b6dc
212 212 update hook: HG_ERROR=0 HG_PARENT1=539e4b31b6dc
213 213 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
214 214
215 215 pushkey hook
216 216
217 217 $ echo "pushkey = python \"$TESTDIR/printenv.py\" pushkey" >> .hg/hgrc
218 218 $ cd ../b
219 219 $ hg bookmark -r null foo
220 220 $ hg push -B foo ../a
221 221 pushing to ../a
222 222 searching for changes
223 223 no changes found
224 224 pretxnopen hook: HG_TXNNAME=bookmarks
225 225 pretxnclose hook: HG_BOOKMARK_MOVED=1 HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_XNNAME=bookmarks (glob)
226 226 txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmarks (glob)
227 227 pushkey hook: HG_KEY=foo HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000 HG_RET=1
228 228 exporting bookmark foo
229 229 [1]
230 230 $ cd ../a
231 231
232 232 listkeys hook
233 233
234 234 $ echo "listkeys = python \"$TESTDIR/printenv.py\" listkeys" >> .hg/hgrc
235 235 $ hg bookmark -r null bar
236 236 $ cd ../b
237 237 $ hg pull -B bar ../a
238 238 pulling from ../a
239 239 listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
240 240 listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
241 241 no changes found
242 242 listkeys hook: HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
243 243 adding remote bookmark bar
244 244 $ cd ../a
245 245
246 246 test that prepushkey can prevent incoming keys
247 247
248 248 $ echo "prepushkey = python \"$TESTDIR/printenv.py\" prepushkey.forbid 1" >> .hg/hgrc
249 249 $ cd ../b
250 250 $ hg bookmark -r null baz
251 251 $ hg push -B baz ../a
252 252 pushing to ../a
253 253 searching for changes
254 254 listkeys hook: HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
255 255 listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
256 256 no changes found
257 257 listkeys hook: HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
258 258 prepushkey.forbid hook: HG_KEY=baz HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000
259 259 pushkey-abort: prepushkey hook exited with status 1
260 260 exporting bookmark baz failed!
261 261 [1]
262 262 $ cd ../a
263 263
264 264 test that prelistkeys can prevent listing keys
265 265
266 266 $ echo "prelistkeys = python \"$TESTDIR/printenv.py\" prelistkeys.forbid 1" >> .hg/hgrc
267 267 $ hg bookmark -r null quux
268 268 $ cd ../b
269 269 $ hg pull -B quux ../a
270 270 pulling from ../a
271 271 prelistkeys.forbid hook: HG_NAMESPACE=bookmarks
272 272 abort: prelistkeys hook exited with status 1
273 273 [255]
274 274 $ cd ../a
275 275 $ rm .hg/hgrc
276 276
277 277 prechangegroup hook can prevent incoming changes
278 278
279 279 $ cd ../b
280 280 $ hg -q tip
281 281 3:07f3376c1e65
282 282 $ cat > .hg/hgrc <<EOF
283 283 > [hooks]
284 284 > prechangegroup.forbid = python "$TESTDIR/printenv.py" prechangegroup.forbid 1
285 285 > EOF
286 286 $ hg pull ../a
287 287 pulling from ../a
288 288 searching for changes
289 289 prechangegroup.forbid hook: HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
290 290 abort: prechangegroup.forbid hook exited with status 1
291 291 [255]
292 292
293 293 pretxnchangegroup hook can see incoming changes, can roll back txn,
294 294 incoming changes no longer there after
295 295
296 296 $ cat > .hg/hgrc <<EOF
297 297 > [hooks]
298 298 > pretxnchangegroup.forbid0 = hg tip -q
299 299 > pretxnchangegroup.forbid1 = python "$TESTDIR/printenv.py" pretxnchangegroup.forbid 1
300 300 > EOF
301 301 $ hg pull ../a
302 302 pulling from ../a
303 303 searching for changes
304 304 adding changesets
305 305 adding manifests
306 306 adding file changes
307 307 added 1 changesets with 1 changes to 1 files
308 308 4:539e4b31b6dc
309 309 pretxnchangegroup.forbid hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/b HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
310 310 transaction abort!
311 311 rollback completed
312 312 abort: pretxnchangegroup.forbid1 hook exited with status 1
313 313 [255]
314 314 $ hg -q tip
315 315 3:07f3376c1e65
316 316
317 317 outgoing hooks can see env vars
318 318
319 319 $ rm .hg/hgrc
320 320 $ cat > ../a/.hg/hgrc <<EOF
321 321 > [hooks]
322 322 > preoutgoing = python "$TESTDIR/printenv.py" preoutgoing
323 323 > outgoing = python "$TESTDIR/printenv.py" outgoing
324 324 > EOF
325 325 $ hg pull ../a
326 326 pulling from ../a
327 327 searching for changes
328 328 preoutgoing hook: HG_SOURCE=pull
329 329 adding changesets
330 330 outgoing hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_SOURCE=pull
331 331 adding manifests
332 332 adding file changes
333 333 added 1 changesets with 1 changes to 1 files
334 334 adding remote bookmark quux
335 335 (run 'hg update' to get a working copy)
336 336 $ hg rollback
337 337 repository tip rolled back to revision 3 (undo pull)
338 338
339 339 preoutgoing hook can prevent outgoing changes
340 340
341 341 $ echo "preoutgoing.forbid = python \"$TESTDIR/printenv.py\" preoutgoing.forbid 1" >> ../a/.hg/hgrc
342 342 $ hg pull ../a
343 343 pulling from ../a
344 344 searching for changes
345 345 preoutgoing hook: HG_SOURCE=pull
346 346 preoutgoing.forbid hook: HG_SOURCE=pull
347 347 abort: preoutgoing.forbid hook exited with status 1
348 348 [255]
349 349
350 350 outgoing hooks work for local clones
351 351
352 352 $ cd ..
353 353 $ cat > a/.hg/hgrc <<EOF
354 354 > [hooks]
355 355 > preoutgoing = python "$TESTDIR/printenv.py" preoutgoing
356 356 > outgoing = python "$TESTDIR/printenv.py" outgoing
357 357 > EOF
358 358 $ hg clone a c
359 359 preoutgoing hook: HG_SOURCE=clone
360 360 outgoing hook: HG_NODE=0000000000000000000000000000000000000000 HG_SOURCE=clone
361 361 updating to branch default
362 362 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
363 363 $ rm -rf c
364 364
365 365 preoutgoing hook can prevent outgoing changes for local clones
366 366
367 367 $ echo "preoutgoing.forbid = python \"$TESTDIR/printenv.py\" preoutgoing.forbid 1" >> a/.hg/hgrc
368 368 $ hg clone a zzz
369 369 preoutgoing hook: HG_SOURCE=clone
370 370 preoutgoing.forbid hook: HG_SOURCE=clone
371 371 abort: preoutgoing.forbid hook exited with status 1
372 372 [255]
373 373
374 374 $ cd "$TESTTMP/b"
375 375
376 376 $ cat > hooktests.py <<EOF
377 377 > from mercurial import util
378 378 >
379 379 > uncallable = 0
380 380 >
381 381 > def printargs(args):
382 382 > args.pop('ui', None)
383 383 > args.pop('repo', None)
384 384 > a = list(args.items())
385 385 > a.sort()
386 386 > print 'hook args:'
387 387 > for k, v in a:
388 388 > print ' ', k, v
389 389 >
390 390 > def passhook(**args):
391 391 > printargs(args)
392 392 >
393 393 > def failhook(**args):
394 394 > printargs(args)
395 395 > return True
396 396 >
397 397 > class LocalException(Exception):
398 398 > pass
399 399 >
400 400 > def raisehook(**args):
401 401 > raise LocalException('exception from hook')
402 402 >
403 403 > def aborthook(**args):
404 404 > raise util.Abort('raise abort from hook')
405 405 >
406 406 > def brokenhook(**args):
407 407 > return 1 + {}
408 408 >
409 409 > def verbosehook(ui, **args):
410 410 > ui.note('verbose output from hook\n')
411 411 >
412 412 > def printtags(ui, repo, **args):
413 413 > print sorted(repo.tags())
414 414 >
415 415 > class container:
416 416 > unreachable = 1
417 417 > EOF
418 418
419 419 test python hooks
420 420
421 421 #if windows
422 422 $ PYTHONPATH="$TESTTMP/b;$PYTHONPATH"
423 423 #else
424 424 $ PYTHONPATH="$TESTTMP/b:$PYTHONPATH"
425 425 #endif
426 426 $ export PYTHONPATH
427 427
428 428 $ echo '[hooks]' > ../a/.hg/hgrc
429 429 $ echo 'preoutgoing.broken = python:hooktests.brokenhook' >> ../a/.hg/hgrc
430 430 $ hg pull ../a 2>&1 | grep 'raised an exception'
431 431 error: preoutgoing.broken hook raised an exception: unsupported operand type(s) for +: 'int' and 'dict'
432 432
433 433 $ echo '[hooks]' > ../a/.hg/hgrc
434 434 $ echo 'preoutgoing.raise = python:hooktests.raisehook' >> ../a/.hg/hgrc
435 435 $ hg pull ../a 2>&1 | grep 'raised an exception'
436 436 error: preoutgoing.raise hook raised an exception: exception from hook
437 437
438 438 $ echo '[hooks]' > ../a/.hg/hgrc
439 439 $ echo 'preoutgoing.abort = python:hooktests.aborthook' >> ../a/.hg/hgrc
440 440 $ hg pull ../a
441 441 pulling from ../a
442 442 searching for changes
443 443 error: preoutgoing.abort hook failed: raise abort from hook
444 444 abort: raise abort from hook
445 445 [255]
446 446
447 447 $ echo '[hooks]' > ../a/.hg/hgrc
448 448 $ echo 'preoutgoing.fail = python:hooktests.failhook' >> ../a/.hg/hgrc
449 449 $ hg pull ../a
450 450 pulling from ../a
451 451 searching for changes
452 452 hook args:
453 453 hooktype preoutgoing
454 454 source pull
455 455 abort: preoutgoing.fail hook failed
456 456 [255]
457 457
458 458 $ echo '[hooks]' > ../a/.hg/hgrc
459 459 $ echo 'preoutgoing.uncallable = python:hooktests.uncallable' >> ../a/.hg/hgrc
460 460 $ hg pull ../a
461 461 pulling from ../a
462 462 searching for changes
463 463 abort: preoutgoing.uncallable hook is invalid ("hooktests.uncallable" is not callable)
464 464 [255]
465 465
466 466 $ echo '[hooks]' > ../a/.hg/hgrc
467 467 $ echo 'preoutgoing.nohook = python:hooktests.nohook' >> ../a/.hg/hgrc
468 468 $ hg pull ../a
469 469 pulling from ../a
470 470 searching for changes
471 471 abort: preoutgoing.nohook hook is invalid ("hooktests.nohook" is not defined)
472 472 [255]
473 473
474 474 $ echo '[hooks]' > ../a/.hg/hgrc
475 475 $ echo 'preoutgoing.nomodule = python:nomodule' >> ../a/.hg/hgrc
476 476 $ hg pull ../a
477 477 pulling from ../a
478 478 searching for changes
479 479 abort: preoutgoing.nomodule hook is invalid ("nomodule" not in a module)
480 480 [255]
481 481
482 482 $ echo '[hooks]' > ../a/.hg/hgrc
483 483 $ echo 'preoutgoing.badmodule = python:nomodule.nowhere' >> ../a/.hg/hgrc
484 484 $ hg pull ../a
485 485 pulling from ../a
486 486 searching for changes
487 487 abort: preoutgoing.badmodule hook is invalid (import of "nomodule" failed)
488 488 [255]
489 489
490 490 $ echo '[hooks]' > ../a/.hg/hgrc
491 491 $ echo 'preoutgoing.unreachable = python:hooktests.container.unreachable' >> ../a/.hg/hgrc
492 492 $ hg pull ../a
493 493 pulling from ../a
494 494 searching for changes
495 495 abort: preoutgoing.unreachable hook is invalid (import of "hooktests.container" failed)
496 496 [255]
497 497
498 498 $ echo '[hooks]' > ../a/.hg/hgrc
499 499 $ echo 'preoutgoing.pass = python:hooktests.passhook' >> ../a/.hg/hgrc
500 500 $ hg pull ../a
501 501 pulling from ../a
502 502 searching for changes
503 503 hook args:
504 504 hooktype preoutgoing
505 505 source pull
506 506 adding changesets
507 507 adding manifests
508 508 adding file changes
509 509 added 1 changesets with 1 changes to 1 files
510 510 adding remote bookmark quux
511 511 (run 'hg update' to get a working copy)
512 512
513 513 make sure --traceback works
514 514
515 515 $ echo '[hooks]' > .hg/hgrc
516 516 $ echo 'commit.abort = python:hooktests.aborthook' >> .hg/hgrc
517 517
518 518 $ echo aa > a
519 519 $ hg --traceback commit -d '0 0' -ma 2>&1 | grep '^Traceback'
520 520 Traceback (most recent call last):
521 521
522 522 $ cd ..
523 523 $ hg init c
524 524 $ cd c
525 525
526 526 $ cat > hookext.py <<EOF
527 527 > def autohook(**args):
528 528 > print "Automatically installed hook"
529 529 >
530 530 > def reposetup(ui, repo):
531 531 > repo.ui.setconfig("hooks", "commit.auto", autohook)
532 532 > EOF
533 533 $ echo '[extensions]' >> .hg/hgrc
534 534 $ echo 'hookext = hookext.py' >> .hg/hgrc
535 535
536 536 $ touch foo
537 537 $ hg add foo
538 538 $ hg ci -d '0 0' -m 'add foo'
539 539 Automatically installed hook
540 540 $ echo >> foo
541 541 $ hg ci --debug -d '0 0' -m 'change foo'
542 542 committing files:
543 543 foo
544 544 committing manifest
545 545 committing changelog
546 546 calling hook commit.auto: hgext_hookext.autohook
547 547 Automatically installed hook
548 548 committed changeset 1:52998019f6252a2b893452765fcb0a47351a5708
549 549
550 550 $ hg showconfig hooks
551 551 hooks.commit.auto=<function autohook at *> (glob)
552 552
553 553 test python hook configured with python:[file]:[hook] syntax
554 554
555 555 $ cd ..
556 556 $ mkdir d
557 557 $ cd d
558 558 $ hg init repo
559 559 $ mkdir hooks
560 560
561 561 $ cd hooks
562 562 $ cat > testhooks.py <<EOF
563 563 > def testhook(**args):
564 564 > print 'hook works'
565 565 > EOF
566 566 $ echo '[hooks]' > ../repo/.hg/hgrc
567 567 $ echo "pre-commit.test = python:`pwd`/testhooks.py:testhook" >> ../repo/.hg/hgrc
568 568
569 569 $ cd ../repo
570 570 $ hg commit -d '0 0'
571 571 hook works
572 572 nothing changed
573 573 [1]
574 574
575 575 $ echo '[hooks]' > .hg/hgrc
576 576 $ echo "update.ne = python:`pwd`/nonexistent.py:testhook" >> .hg/hgrc
577 577 $ echo "pre-identify.npmd = python:`pwd`/:no_python_module_dir" >> .hg/hgrc
578 578
579 579 $ hg up null
580 580 loading update.ne hook failed:
581 581 abort: No such file or directory: $TESTTMP/d/repo/nonexistent.py
582 582 [255]
583 583
584 584 $ hg id
585 585 loading pre-identify.npmd hook failed:
586 586 abort: No module named repo!
587 587 [255]
588 588
589 589 $ cd ../../b
590 590
591 591 make sure --traceback works on hook import failure
592 592
593 593 $ cat > importfail.py <<EOF
594 594 > import somebogusmodule
595 595 > # dereference something in the module to force demandimport to load it
596 596 > somebogusmodule.whatever
597 597 > EOF
598 598
599 599 $ echo '[hooks]' > .hg/hgrc
600 600 $ echo 'precommit.importfail = python:importfail.whatever' >> .hg/hgrc
601 601
602 602 $ echo a >> a
603 603 $ hg --traceback commit -ma 2>&1 | egrep -v '^( +File| [a-zA-Z(])'
604 604 exception from first failed import attempt:
605 605 Traceback (most recent call last):
606 606 ImportError: No module named somebogusmodule
607 607 exception from second failed import attempt:
608 608 Traceback (most recent call last):
609 609 ImportError: No module named hgext_importfail
610 610 Traceback (most recent call last):
611 611 Abort: precommit.importfail hook is invalid (import of "importfail" failed)
612 612 abort: precommit.importfail hook is invalid (import of "importfail" failed)
613 613
614 614 Issue1827: Hooks Update & Commit not completely post operation
615 615
616 commit and update hooks should run after command completion
616 commit and update hooks should run after command completion. The largefiles
617 use demonstrates a recursive wlock, showing the hook doesn't run until the
618 final release (and dirstate flush).
617 619
618 620 $ echo '[hooks]' > .hg/hgrc
619 621 $ echo 'commit = hg id' >> .hg/hgrc
620 622 $ echo 'update = hg id' >> .hg/hgrc
621 623 $ echo bb > a
622 624 $ hg ci -ma
623 625 223eafe2750c tip
624 $ hg up 0
626 $ hg up 0 --config extensions.largefiles=
625 627 cb9a9f314b8b
626 628 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
627 629
628 630 make sure --verbose (and --quiet/--debug etc.) are propagated to the local ui
629 631 that is passed to pre/post hooks
630 632
631 633 $ echo '[hooks]' > .hg/hgrc
632 634 $ echo 'pre-identify = python:hooktests.verbosehook' >> .hg/hgrc
633 635 $ hg id
634 636 cb9a9f314b8b
635 637 $ hg id --verbose
636 638 calling hook pre-identify: hooktests.verbosehook
637 639 verbose output from hook
638 640 cb9a9f314b8b
639 641
640 642 Ensure hooks can be prioritized
641 643
642 644 $ echo '[hooks]' > .hg/hgrc
643 645 $ echo 'pre-identify.a = python:hooktests.verbosehook' >> .hg/hgrc
644 646 $ echo 'pre-identify.b = python:hooktests.verbosehook' >> .hg/hgrc
645 647 $ echo 'priority.pre-identify.b = 1' >> .hg/hgrc
646 648 $ echo 'pre-identify.c = python:hooktests.verbosehook' >> .hg/hgrc
647 649 $ hg id --verbose
648 650 calling hook pre-identify.b: hooktests.verbosehook
649 651 verbose output from hook
650 652 calling hook pre-identify.a: hooktests.verbosehook
651 653 verbose output from hook
652 654 calling hook pre-identify.c: hooktests.verbosehook
653 655 verbose output from hook
654 656 cb9a9f314b8b
655 657
656 658 new tags must be visible in pretxncommit (issue3210)
657 659
658 660 $ echo 'pretxncommit.printtags = python:hooktests.printtags' >> .hg/hgrc
659 661 $ hg tag -f foo
660 662 ['a', 'foo', 'tip']
661 663
662 664 new commits must be visible in pretxnchangegroup (issue3428)
663 665
664 666 $ cd ..
665 667 $ hg init to
666 668 $ echo '[hooks]' >> to/.hg/hgrc
667 669 $ echo 'pretxnchangegroup = hg --traceback tip' >> to/.hg/hgrc
668 670 $ echo a >> to/a
669 671 $ hg --cwd to ci -Ama
670 672 adding a
671 673 $ hg clone to from
672 674 updating to branch default
673 675 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
674 676 $ echo aa >> from/a
675 677 $ hg --cwd from ci -mb
676 678 $ hg --cwd from push
677 679 pushing to $TESTTMP/to (glob)
678 680 searching for changes
679 681 adding changesets
680 682 adding manifests
681 683 adding file changes
682 684 added 1 changesets with 1 changes to 1 files
683 685 changeset: 1:9836a07b9b9d
684 686 tag: tip
685 687 user: test
686 688 date: Thu Jan 01 00:00:00 1970 +0000
687 689 summary: b
688 690
General Comments 0
You need to be logged in to leave comments. Login now