##// END OF EJS Templates
transaction: write pending generated files...
Pierre-Yves David -
r23358:1b51d1b0 default
parent child Browse files
Show More
@@ -1,508 +1,509
1 1 # transaction.py - simple journaling scheme for mercurial
2 2 #
3 3 # This transaction scheme is intended to gracefully handle program
4 4 # errors and interruptions. More serious failures like system crashes
5 5 # can be recovered with an fsck-like tool. As the whole repository is
6 6 # effectively log-structured, this should amount to simply truncating
7 7 # anything that isn't referenced in the changelog.
8 8 #
9 9 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
10 10 #
11 11 # This software may be used and distributed according to the terms of the
12 12 # GNU General Public License version 2 or any later version.
13 13
14 14 from i18n import _
15 15 import os
16 16 import errno
17 17 import error, util
18 18
19 19 version = 2
20 20
21 21 def active(func):
22 22 def _active(self, *args, **kwds):
23 23 if self.count == 0:
24 24 raise error.Abort(_(
25 25 'cannot use transaction when it is already committed/aborted'))
26 26 return func(self, *args, **kwds)
27 27 return _active
28 28
29 29 def _playback(journal, report, opener, vfsmap, entries, backupentries,
30 30 unlink=True):
31 31 for f, o, _ignore in entries:
32 32 if o or not unlink:
33 33 try:
34 34 fp = opener(f, 'a')
35 35 fp.truncate(o)
36 36 fp.close()
37 37 except IOError:
38 38 report(_("failed to truncate %s\n") % f)
39 39 raise
40 40 else:
41 41 try:
42 42 opener.unlink(f)
43 43 except (IOError, OSError), inst:
44 44 if inst.errno != errno.ENOENT:
45 45 raise
46 46
47 47 backupfiles = []
48 48 for l, f, b, c in backupentries:
49 49 if l not in vfsmap and c:
50 50 report("couldn't handle %s: unknown cache location %s\n"
51 51 % (b, l))
52 52 vfs = vfsmap[l]
53 53 try:
54 54 if f and b:
55 55 filepath = vfs.join(f)
56 56 backuppath = vfs.join(b)
57 57 try:
58 58 util.copyfile(backuppath, filepath)
59 59 backupfiles.append(b)
60 60 except IOError:
61 61 report(_("failed to recover %s\n") % f)
62 62 else:
63 63 target = f or b
64 64 try:
65 65 vfs.unlink(target)
66 66 except (IOError, OSError), inst:
67 67 if inst.errno != errno.ENOENT:
68 68 raise
69 69 except (IOError, OSError, util.Abort), inst:
70 70 if not c:
71 71 raise
72 72
73 73 opener.unlink(journal)
74 74 backuppath = "%s.backupfiles" % journal
75 75 if opener.exists(backuppath):
76 76 opener.unlink(backuppath)
77 77 try:
78 78 for f in backupfiles:
79 79 if opener.exists(f):
80 80 opener.unlink(f)
81 81 except (IOError, OSError, util.Abort), inst:
82 82 # only pure backup file remains, it is sage to ignore any error
83 83 pass
84 84
85 85 class transaction(object):
86 86 def __init__(self, report, opener, vfsmap, journal, after=None,
87 87 createmode=None, onclose=None, onabort=None):
88 88 """Begin a new transaction
89 89
90 90 Begins a new transaction that allows rolling back writes in the event of
91 91 an exception.
92 92
93 93 * `after`: called after the transaction has been committed
94 94 * `createmode`: the mode of the journal file that will be created
95 95 * `onclose`: called as the transaction is closing, but before it is
96 96 closed
97 97 * `onabort`: called as the transaction is aborting, but before any files
98 98 have been truncated
99 99 """
100 100 self.count = 1
101 101 self.usages = 1
102 102 self.report = report
103 103 # a vfs to the store content
104 104 self.opener = opener
105 105 # a map to access file in various {location -> vfs}
106 106 vfsmap = vfsmap.copy()
107 107 vfsmap[''] = opener # set default value
108 108 self._vfsmap = vfsmap
109 109 self.after = after
110 110 self.onclose = onclose
111 111 self.onabort = onabort
112 112 self.entries = []
113 113 self.map = {}
114 114 self.journal = journal
115 115 self._queue = []
116 116 # a dict of arguments to be passed to hooks
117 117 self.hookargs = {}
118 118 self.file = opener.open(self.journal, "w")
119 119
120 120 # a list of ('location', 'path', 'backuppath', cache) entries.
121 121 # - if 'backuppath' is empty, no file existed at backup time
122 122 # - if 'path' is empty, this is a temporary transaction file
123 123 # - if 'location' is not empty, the path is outside main opener reach.
124 124 # use 'location' value as a key in a vfsmap to find the right 'vfs'
125 125 # (cache is currently unused)
126 126 self._backupentries = []
127 127 self._backupmap = {}
128 128 self._backupjournal = "%s.backupfiles" % journal
129 129 self._backupsfile = opener.open(self._backupjournal, 'w')
130 130 self._backupsfile.write('%d\n' % version)
131 131
132 132 if createmode is not None:
133 133 opener.chmod(self.journal, createmode & 0666)
134 134 opener.chmod(self._backupjournal, createmode & 0666)
135 135
136 136 # hold file generations to be performed on commit
137 137 self._filegenerators = {}
138 138 # hold callbalk to write pending data for hooks
139 139 self._pendingcallback = {}
140 140 # True is any pending data have been written ever
141 141 self._anypending = False
142 142 # holds callback to call when writing the transaction
143 143 self._finalizecallback = {}
144 144 # hold callbalk for post transaction close
145 145 self._postclosecallback = {}
146 146
147 147 def __del__(self):
148 148 if self.journal:
149 149 self._abort()
150 150
151 151 @active
152 152 def startgroup(self):
153 153 """delay registration of file entry
154 154
155 155 This is used by strip to delay vision of strip offset. The transaction
156 156 sees either none or all of the strip actions to be done."""
157 157 self._queue.append([])
158 158
159 159 @active
160 160 def endgroup(self):
161 161 """apply delayed registration of file entry.
162 162
163 163 This is used by strip to delay vision of strip offset. The transaction
164 164 sees either none or all of the strip actions to be done."""
165 165 q = self._queue.pop()
166 166 for f, o, data in q:
167 167 self._addentry(f, o, data)
168 168
169 169 @active
170 170 def add(self, file, offset, data=None):
171 171 """record the state of an append-only file before update"""
172 172 if file in self.map or file in self._backupmap:
173 173 return
174 174 if self._queue:
175 175 self._queue[-1].append((file, offset, data))
176 176 return
177 177
178 178 self._addentry(file, offset, data)
179 179
180 180 def _addentry(self, file, offset, data):
181 181 """add a append-only entry to memory and on-disk state"""
182 182 if file in self.map or file in self._backupmap:
183 183 return
184 184 self.entries.append((file, offset, data))
185 185 self.map[file] = len(self.entries) - 1
186 186 # add enough data to the journal to do the truncate
187 187 self.file.write("%s\0%d\n" % (file, offset))
188 188 self.file.flush()
189 189
190 190 @active
191 191 def addbackup(self, file, hardlink=True, location=''):
192 192 """Adds a backup of the file to the transaction
193 193
194 194 Calling addbackup() creates a hardlink backup of the specified file
195 195 that is used to recover the file in the event of the transaction
196 196 aborting.
197 197
198 198 * `file`: the file path, relative to .hg/store
199 199 * `hardlink`: use a hardlink to quickly create the backup
200 200 """
201 201 if self._queue:
202 202 msg = 'cannot use transaction.addbackup inside "group"'
203 203 raise RuntimeError(msg)
204 204
205 205 if file in self.map or file in self._backupmap:
206 206 return
207 207 dirname, filename = os.path.split(file)
208 208 backupfilename = "%s.backup.%s" % (self.journal, filename)
209 209 backupfile = os.path.join(dirname, backupfilename)
210 210 vfs = self._vfsmap[location]
211 211 if vfs.exists(file):
212 212 filepath = vfs.join(file)
213 213 backuppath = vfs.join(backupfile)
214 214 util.copyfiles(filepath, backuppath, hardlink=hardlink)
215 215 else:
216 216 backupfile = ''
217 217
218 218 self._addbackupentry((location, file, backupfile, False))
219 219
220 220 def _addbackupentry(self, entry):
221 221 """register a new backup entry and write it to disk"""
222 222 self._backupentries.append(entry)
223 223 self._backupmap[file] = len(self._backupentries) - 1
224 224 self._backupsfile.write("%s\0%s\0%s\0%d\n" % entry)
225 225 self._backupsfile.flush()
226 226
227 227 @active
228 228 def registertmp(self, tmpfile, location=''):
229 229 """register a temporary transaction file
230 230
231 231 Such files will be deleted when the transaction exits (on both
232 232 failure and success).
233 233 """
234 234 self._addbackupentry((location, '', tmpfile, False))
235 235
236 236 @active
237 237 def addfilegenerator(self, genid, filenames, genfunc, order=0,
238 238 location=''):
239 239 """add a function to generates some files at transaction commit
240 240
241 241 The `genfunc` argument is a function capable of generating proper
242 242 content of each entry in the `filename` tuple.
243 243
244 244 At transaction close time, `genfunc` will be called with one file
245 245 object argument per entries in `filenames`.
246 246
247 247 The transaction itself is responsible for the backup, creation and
248 248 final write of such file.
249 249
250 250 The `genid` argument is used to ensure the same set of file is only
251 251 generated once. Call to `addfilegenerator` for a `genid` already
252 252 present will overwrite the old entry.
253 253
254 254 The `order` argument may be used to control the order in which multiple
255 255 generator will be executed.
256 256
257 257 The `location` arguments may be used to indicate the files are located
258 258 outside of the the standard directory for transaction. It should match
259 259 one of the key of the `transaction.vfsmap` dictionnary.
260 260 """
261 261 # For now, we are unable to do proper backup and restore of custom vfs
262 262 # but for bookmarks that are handled outside this mechanism.
263 263 self._filegenerators[genid] = (order, filenames, genfunc, location)
264 264
265 265 def _generatefiles(self, suffix=''):
266 266 # write files registered for generation
267 267 any = False
268 268 for entry in sorted(self._filegenerators.values()):
269 269 any = True
270 270 order, filenames, genfunc, location = entry
271 271 vfs = self._vfsmap[location]
272 272 files = []
273 273 try:
274 274 for name in filenames:
275 275 name += suffix
276 276 if suffix:
277 277 self.registertmp(name, location=location)
278 278 else:
279 279 self.addbackup(name, location=location)
280 280 files.append(vfs(name, 'w', atomictemp=True))
281 281 genfunc(*files)
282 282 finally:
283 283 for f in files:
284 284 f.close()
285 285 return any
286 286
287 287 @active
288 288 def find(self, file):
289 289 if file in self.map:
290 290 return self.entries[self.map[file]]
291 291 if file in self._backupmap:
292 292 return self._backupentries[self._backupmap[file]]
293 293 return None
294 294
295 295 @active
296 296 def replace(self, file, offset, data=None):
297 297 '''
298 298 replace can only replace already committed entries
299 299 that are not pending in the queue
300 300 '''
301 301
302 302 if file not in self.map:
303 303 raise KeyError(file)
304 304 index = self.map[file]
305 305 self.entries[index] = (file, offset, data)
306 306 self.file.write("%s\0%d\n" % (file, offset))
307 307 self.file.flush()
308 308
309 309 @active
310 310 def nest(self):
311 311 self.count += 1
312 312 self.usages += 1
313 313 return self
314 314
315 315 def release(self):
316 316 if self.count > 0:
317 317 self.usages -= 1
318 318 # if the transaction scopes are left without being closed, fail
319 319 if self.count > 0 and self.usages == 0:
320 320 self._abort()
321 321
322 322 def running(self):
323 323 return self.count > 0
324 324
325 325 def addpending(self, category, callback):
326 326 """add a callback to be called when the transaction is pending
327 327
328 328 The transaction will be given as callback's first argument.
329 329
330 330 Category is a unique identifier to allow overwriting an old callback
331 331 with a newer callback.
332 332 """
333 333 self._pendingcallback[category] = callback
334 334
335 335 @active
336 336 def writepending(self):
337 337 '''write pending file to temporary version
338 338
339 339 This is used to allow hooks to view a transaction before commit'''
340 340 categories = sorted(self._pendingcallback)
341 341 for cat in categories:
342 342 # remove callback since the data will have been flushed
343 343 any = self._pendingcallback.pop(cat)(self)
344 344 self._anypending = self._anypending or any
345 self._anypending |= self._generatefiles(suffix='.pending')
345 346 return self._anypending
346 347
347 348 @active
348 349 def addfinalize(self, category, callback):
349 350 """add a callback to be called when the transaction is closed
350 351
351 352 The transaction will be given as callback's first argument.
352 353
353 354 Category is a unique identifier to allow overwriting old callbacks with
354 355 newer callbacks.
355 356 """
356 357 self._finalizecallback[category] = callback
357 358
358 359 @active
359 360 def addpostclose(self, category, callback):
360 361 """add a callback to be called after the transaction is closed
361 362
362 363 The transaction will be given as callback's first argument.
363 364
364 365 Category is a unique identifier to allow overwriting an old callback
365 366 with a newer callback.
366 367 """
367 368 self._postclosecallback[category] = callback
368 369
369 370 @active
370 371 def close(self):
371 372 '''commit the transaction'''
372 373 if self.count == 1:
373 374 self._generatefiles()
374 375 categories = sorted(self._finalizecallback)
375 376 for cat in categories:
376 377 self._finalizecallback[cat](self)
377 378 if self.onclose is not None:
378 379 self.onclose()
379 380
380 381 self.count -= 1
381 382 if self.count != 0:
382 383 return
383 384 self.file.close()
384 385 self._backupsfile.close()
385 386 # cleanup temporary files
386 387 for l, f, b, c in self._backupentries:
387 388 if l not in self._vfsmap and c:
388 389 self.report("couldn't remote %s: unknown cache location %s\n"
389 390 % (b, l))
390 391 continue
391 392 vfs = self._vfsmap[l]
392 393 if not f and b and vfs.exists(b):
393 394 try:
394 395 vfs.unlink(b)
395 396 except (IOError, OSError, util.Abort), inst:
396 397 if not c:
397 398 raise
398 399 # Abort may be raise by read only opener
399 400 self.report("couldn't remote %s: %s\n"
400 401 % (vfs.join(b), inst))
401 402 self.entries = []
402 403 if self.after:
403 404 self.after()
404 405 if self.opener.isfile(self.journal):
405 406 self.opener.unlink(self.journal)
406 407 if self.opener.isfile(self._backupjournal):
407 408 self.opener.unlink(self._backupjournal)
408 409 for _l, _f, b, c in self._backupentries:
409 410 if l not in self._vfsmap and c:
410 411 self.report("couldn't remote %s: unknown cache location"
411 412 "%s\n" % (b, l))
412 413 continue
413 414 vfs = self._vfsmap[l]
414 415 if b and vfs.exists(b):
415 416 try:
416 417 vfs.unlink(b)
417 418 except (IOError, OSError, util.Abort), inst:
418 419 if not c:
419 420 raise
420 421 # Abort may be raise by read only opener
421 422 self.report("couldn't remote %s: %s\n"
422 423 % (vfs.join(b), inst))
423 424 self._backupentries = []
424 425 self.journal = None
425 426 # run post close action
426 427 categories = sorted(self._postclosecallback)
427 428 for cat in categories:
428 429 self._postclosecallback[cat](self)
429 430
430 431 @active
431 432 def abort(self):
432 433 '''abort the transaction (generally called on error, or when the
433 434 transaction is not explicitly committed before going out of
434 435 scope)'''
435 436 self._abort()
436 437
437 438 def _abort(self):
438 439 self.count = 0
439 440 self.usages = 0
440 441 self.file.close()
441 442 self._backupsfile.close()
442 443
443 444 if self.onabort is not None:
444 445 self.onabort()
445 446
446 447 try:
447 448 if not self.entries and not self._backupentries:
448 449 if self.journal:
449 450 self.opener.unlink(self.journal)
450 451 if self._backupjournal:
451 452 self.opener.unlink(self._backupjournal)
452 453 return
453 454
454 455 self.report(_("transaction abort!\n"))
455 456
456 457 try:
457 458 _playback(self.journal, self.report, self.opener, self._vfsmap,
458 459 self.entries, self._backupentries, False)
459 460 self.report(_("rollback completed\n"))
460 461 except Exception:
461 462 self.report(_("rollback failed - please run hg recover\n"))
462 463 finally:
463 464 self.journal = None
464 465
465 466
466 467 def rollback(opener, vfsmap, file, report):
467 468 """Rolls back the transaction contained in the given file
468 469
469 470 Reads the entries in the specified file, and the corresponding
470 471 '*.backupfiles' file, to recover from an incomplete transaction.
471 472
472 473 * `file`: a file containing a list of entries, specifying where
473 474 to truncate each file. The file should contain a list of
474 475 file\0offset pairs, delimited by newlines. The corresponding
475 476 '*.backupfiles' file should contain a list of file\0backupfile
476 477 pairs, delimited by \0.
477 478 """
478 479 entries = []
479 480 backupentries = []
480 481
481 482 fp = opener.open(file)
482 483 lines = fp.readlines()
483 484 fp.close()
484 485 for l in lines:
485 486 try:
486 487 f, o = l.split('\0')
487 488 entries.append((f, int(o), None))
488 489 except ValueError:
489 490 report(_("couldn't read journal entry %r!\n") % l)
490 491
491 492 backupjournal = "%s.backupfiles" % file
492 493 if opener.exists(backupjournal):
493 494 fp = opener.open(backupjournal)
494 495 lines = fp.readlines()
495 496 if lines:
496 497 ver = lines[0][:-1]
497 498 if ver == str(version):
498 499 for line in lines[1:]:
499 500 if line:
500 501 # Shave off the trailing newline
501 502 line = line[:-1]
502 503 l, f, b, c = line.split('\0')
503 504 backupentries.append((l, f, b, bool(c)))
504 505 else:
505 506 report(_("journal was created by a different version of "
506 507 "Mercurial"))
507 508
508 509 _playback(file, report, opener, vfsmap, entries, backupentries)
@@ -1,482 +1,499
1 1 Test exchange of common information using bundle2
2 2
3 3
4 4 $ getmainid() {
5 5 > hg -R main log --template '{node}\n' --rev "$1"
6 6 > }
7 7
8 8 enable obsolescence
9 9
10 10 $ cat >> $HGRCPATH << EOF
11 11 > [experimental]
12 12 > evolution=createmarkers,exchange
13 13 > bundle2-exp=True
14 14 > [ui]
15 15 > ssh=python "$TESTDIR/dummyssh"
16 16 > logtemplate={rev}:{node|short} {phase} {author} {bookmarks} {desc|firstline}
17 17 > [web]
18 18 > push_ssl = false
19 19 > allow_push = *
20 20 > [phases]
21 21 > publish=False
22 22 > [hooks]
23 23 > changegroup = sh -c "HG_LOCAL= python \"$TESTDIR/printenv.py\" changegroup"
24 24 > b2x-transactionclose = sh -c "HG_LOCAL= python \"$TESTDIR/printenv.py\" b2x-transactionclose"
25 25 > EOF
26 26
27 27 The extension requires a repo (currently unused)
28 28
29 29 $ hg init main
30 30 $ cd main
31 31 $ touch a
32 32 $ hg add a
33 33 $ hg commit -m 'a'
34 34
35 35 $ hg unbundle $TESTDIR/bundles/rebase.hg
36 36 adding changesets
37 37 adding manifests
38 38 adding file changes
39 39 added 8 changesets with 7 changes to 7 files (+3 heads)
40 40 changegroup hook: HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_SOURCE=unbundle HG_URL=bundle:*/rebase.hg (glob)
41 41 (run 'hg heads' to see heads, 'hg merge' to merge)
42 42
43 43 $ cd ..
44 44
45 45 Real world exchange
46 46 =====================
47 47
48 48 Add more obsolescence information
49 49
50 50 $ hg -R main debugobsolete -d '0 0' 1111111111111111111111111111111111111111 `getmainid 9520eea781bc`
51 51 $ hg -R main debugobsolete -d '0 0' 2222222222222222222222222222222222222222 `getmainid 24b6387c8c8c`
52 52
53 53 clone --pull
54 54
55 55 $ hg -R main phase --public cd010b8cd998
56 56 $ hg clone main other --pull --rev 9520eea781bc
57 57 adding changesets
58 58 adding manifests
59 59 adding file changes
60 60 added 2 changesets with 2 changes to 2 files
61 61 1 new obsolescence markers
62 62 b2x-transactionclose hook: HG_NEW_OBSMARKERS=1 HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=file:$TESTTMP/main
63 63 changegroup hook: HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_SOURCE=pull HG_URL=file:$TESTTMP/main
64 64 updating to branch default
65 65 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
66 66 $ hg -R other log -G
67 67 @ 1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
68 68 |
69 69 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
70 70
71 71 $ hg -R other debugobsolete
72 72 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
73 73
74 74 pull
75 75
76 76 $ hg -R main phase --public 9520eea781bc
77 77 $ hg -R other pull -r 24b6387c8c8c
78 78 pulling from $TESTTMP/main (glob)
79 79 searching for changes
80 80 adding changesets
81 81 adding manifests
82 82 adding file changes
83 83 added 1 changesets with 1 changes to 1 files (+1 heads)
84 84 1 new obsolescence markers
85 85 b2x-transactionclose hook: HG_NEW_OBSMARKERS=1 HG_NODE=24b6387c8c8cae37178880f3fa95ded3cb1cf785 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=file:$TESTTMP/main
86 86 changegroup hook: HG_NODE=24b6387c8c8cae37178880f3fa95ded3cb1cf785 HG_SOURCE=pull HG_URL=file:$TESTTMP/main
87 87 (run 'hg heads' to see heads, 'hg merge' to merge)
88 88 $ hg -R other log -G
89 89 o 2:24b6387c8c8c draft Nicolas Dumazet <nicdumz.commits@gmail.com> F
90 90 |
91 91 | @ 1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
92 92 |/
93 93 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
94 94
95 95 $ hg -R other debugobsolete
96 96 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
97 97 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
98 98
99 99 pull empty (with phase movement)
100 100
101 101 $ hg -R main phase --public 24b6387c8c8c
102 102 $ hg -R other pull -r 24b6387c8c8c
103 103 pulling from $TESTTMP/main (glob)
104 104 no changes found
105 105 b2x-transactionclose hook: HG_NEW_OBSMARKERS=0 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=file:$TESTTMP/main
106 106 $ hg -R other log -G
107 107 o 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
108 108 |
109 109 | @ 1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
110 110 |/
111 111 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
112 112
113 113 $ hg -R other debugobsolete
114 114 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
115 115 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
116 116
117 117 pull empty
118 118
119 119 $ hg -R other pull -r 24b6387c8c8c
120 120 pulling from $TESTTMP/main (glob)
121 121 no changes found
122 122 b2x-transactionclose hook: HG_NEW_OBSMARKERS=0 HG_SOURCE=pull HG_URL=file:$TESTTMP/main
123 123 $ hg -R other log -G
124 124 o 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
125 125 |
126 126 | @ 1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
127 127 |/
128 128 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
129 129
130 130 $ hg -R other debugobsolete
131 131 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
132 132 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
133 133
134 134 add extra data to test their exchange during push
135 135
136 136 $ hg -R main bookmark --rev eea13746799a book_eea1
137 137 $ hg -R main debugobsolete -d '0 0' 3333333333333333333333333333333333333333 `getmainid eea13746799a`
138 138 $ hg -R main bookmark --rev 02de42196ebe book_02de
139 139 $ hg -R main debugobsolete -d '0 0' 4444444444444444444444444444444444444444 `getmainid 02de42196ebe`
140 140 $ hg -R main bookmark --rev 42ccdea3bb16 book_42cc
141 141 $ hg -R main debugobsolete -d '0 0' 5555555555555555555555555555555555555555 `getmainid 42ccdea3bb16`
142 142 $ hg -R main bookmark --rev 5fddd98957c8 book_5fdd
143 143 $ hg -R main debugobsolete -d '0 0' 6666666666666666666666666666666666666666 `getmainid 5fddd98957c8`
144 144 $ hg -R main bookmark --rev 32af7686d403 book_32af
145 145 $ hg -R main debugobsolete -d '0 0' 7777777777777777777777777777777777777777 `getmainid 32af7686d403`
146 146
147 147 $ hg -R other bookmark --rev cd010b8cd998 book_eea1
148 148 $ hg -R other bookmark --rev cd010b8cd998 book_02de
149 149 $ hg -R other bookmark --rev cd010b8cd998 book_42cc
150 150 $ hg -R other bookmark --rev cd010b8cd998 book_5fdd
151 151 $ hg -R other bookmark --rev cd010b8cd998 book_32af
152 152
153 153 $ hg -R main phase --public eea13746799a
154 154
155 155 push
156 156 $ hg -R main push other --rev eea13746799a --bookmark book_eea1
157 157 pushing to other
158 158 searching for changes
159 159 b2x-transactionclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2-EXP=1 HG_NEW_OBSMARKERS=1 HG_NODE=eea13746799a9e0bfd88f29d3c2e9dc9389f524f HG_PHASES_MOVED=1 HG_SOURCE=push HG_URL=push
160 160 changegroup hook: HG_BUNDLE2-EXP=1 HG_NODE=eea13746799a9e0bfd88f29d3c2e9dc9389f524f HG_SOURCE=push HG_URL=push
161 161 remote: adding changesets
162 162 remote: adding manifests
163 163 remote: adding file changes
164 164 remote: added 1 changesets with 0 changes to 0 files (-1 heads)
165 165 remote: 1 new obsolescence markers
166 166 updating bookmark book_eea1
167 167 $ hg -R other log -G
168 168 o 3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> book_eea1 G
169 169 |\
170 170 | o 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
171 171 | |
172 172 @ | 1:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com> E
173 173 |/
174 174 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_02de book_32af book_42cc book_5fdd A
175 175
176 176 $ hg -R other debugobsolete
177 177 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
178 178 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
179 179 3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
180 180
181 181 pull over ssh
182 182
183 183 $ hg -R other pull ssh://user@dummy/main -r 02de42196ebe --bookmark book_02de
184 184 pulling from ssh://user@dummy/main
185 185 searching for changes
186 186 adding changesets
187 187 adding manifests
188 188 adding file changes
189 189 added 1 changesets with 1 changes to 1 files (+1 heads)
190 190 1 new obsolescence markers
191 191 updating bookmark book_02de
192 192 b2x-transactionclose hook: HG_BOOKMARK_MOVED=1 HG_NEW_OBSMARKERS=1 HG_NODE=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=ssh://user@dummy/main
193 193 changegroup hook: HG_NODE=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_SOURCE=pull HG_URL=ssh://user@dummy/main
194 194 (run 'hg heads' to see heads, 'hg merge' to merge)
195 195 $ hg -R other debugobsolete
196 196 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
197 197 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
198 198 3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
199 199 4444444444444444444444444444444444444444 02de42196ebee42ef284b6780a87cdc96e8eaab6 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
200 200
201 201 pull over http
202 202
203 203 $ hg -R main serve -p $HGPORT -d --pid-file=main.pid -E main-error.log
204 204 $ cat main.pid >> $DAEMON_PIDS
205 205
206 206 $ hg -R other pull http://localhost:$HGPORT/ -r 42ccdea3bb16 --bookmark book_42cc
207 207 pulling from http://localhost:$HGPORT/
208 208 searching for changes
209 209 adding changesets
210 210 adding manifests
211 211 adding file changes
212 212 added 1 changesets with 1 changes to 1 files (+1 heads)
213 213 1 new obsolescence markers
214 214 updating bookmark book_42cc
215 215 b2x-transactionclose hook: HG_BOOKMARK_MOVED=1 HG_NEW_OBSMARKERS=1 HG_NODE=42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=http://localhost:$HGPORT/
216 216 changegroup hook: HG_NODE=42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 HG_SOURCE=pull HG_URL=http://localhost:$HGPORT/
217 217 (run 'hg heads .' to see heads, 'hg merge' to merge)
218 218 $ cat main-error.log
219 219 $ hg -R other debugobsolete
220 220 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
221 221 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
222 222 3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
223 223 4444444444444444444444444444444444444444 02de42196ebee42ef284b6780a87cdc96e8eaab6 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
224 224 5555555555555555555555555555555555555555 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
225 225
226 226 push over ssh
227 227
228 228 $ hg -R main push ssh://user@dummy/other -r 5fddd98957c8 --bookmark book_5fdd
229 229 pushing to ssh://user@dummy/other
230 230 searching for changes
231 231 remote: adding changesets
232 232 remote: adding manifests
233 233 remote: adding file changes
234 234 remote: added 1 changesets with 1 changes to 1 files
235 235 remote: 1 new obsolescence markers
236 236 updating bookmark book_5fdd
237 237 remote: b2x-transactionclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2-EXP=1 HG_NEW_OBSMARKERS=1 HG_NODE=5fddd98957c8a54a4d436dfe1da9d87f21a1b97b HG_SOURCE=serve HG_URL=remote:ssh:127.0.0.1
238 238 remote: changegroup hook: HG_BUNDLE2-EXP=1 HG_NODE=5fddd98957c8a54a4d436dfe1da9d87f21a1b97b HG_SOURCE=serve HG_URL=remote:ssh:127.0.0.1
239 239 $ hg -R other log -G
240 240 o 6:5fddd98957c8 draft Nicolas Dumazet <nicdumz.commits@gmail.com> book_5fdd C
241 241 |
242 242 o 5:42ccdea3bb16 draft Nicolas Dumazet <nicdumz.commits@gmail.com> book_42cc B
243 243 |
244 244 | o 4:02de42196ebe draft Nicolas Dumazet <nicdumz.commits@gmail.com> book_02de H
245 245 | |
246 246 | | o 3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> book_eea1 G
247 247 | |/|
248 248 | o | 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
249 249 |/ /
250 250 | @ 1:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com> E
251 251 |/
252 252 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_32af A
253 253
254 254 $ hg -R other debugobsolete
255 255 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
256 256 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
257 257 3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
258 258 4444444444444444444444444444444444444444 02de42196ebee42ef284b6780a87cdc96e8eaab6 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
259 259 5555555555555555555555555555555555555555 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
260 260 6666666666666666666666666666666666666666 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
261 261
262 262 push over http
263 263
264 264 $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
265 265 $ cat other.pid >> $DAEMON_PIDS
266 266
267 267 $ hg -R main phase --public 32af7686d403
268 268 $ hg -R main push http://localhost:$HGPORT2/ -r 32af7686d403 --bookmark book_32af
269 269 pushing to http://localhost:$HGPORT2/
270 270 searching for changes
271 271 remote: adding changesets
272 272 remote: adding manifests
273 273 remote: adding file changes
274 274 remote: added 1 changesets with 1 changes to 1 files
275 275 remote: 1 new obsolescence markers
276 276 updating bookmark book_32af
277 277 $ cat other-error.log
278 278
279 279 Check final content.
280 280
281 281 $ hg -R other log -G
282 282 o 7:32af7686d403 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_32af D
283 283 |
284 284 o 6:5fddd98957c8 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_5fdd C
285 285 |
286 286 o 5:42ccdea3bb16 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_42cc B
287 287 |
288 288 | o 4:02de42196ebe draft Nicolas Dumazet <nicdumz.commits@gmail.com> book_02de H
289 289 | |
290 290 | | o 3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> book_eea1 G
291 291 | |/|
292 292 | o | 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
293 293 |/ /
294 294 | @ 1:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com> E
295 295 |/
296 296 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
297 297
298 298 $ hg -R other debugobsolete
299 299 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
300 300 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
301 301 3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
302 302 4444444444444444444444444444444444444444 02de42196ebee42ef284b6780a87cdc96e8eaab6 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
303 303 5555555555555555555555555555555555555555 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
304 304 6666666666666666666666666666666666666666 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
305 305 7777777777777777777777777777777777777777 32af7686d403cf45b5d95f2d70cebea587ac806a 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
306 306
307 (check that no 'pending' files remain)
308
309 $ ls -1 other/.hg/bookmarks*
310 other/.hg/bookmarks
311 $ ls -1 other/.hg/store/phaseroots*
312 other/.hg/store/phaseroots
313 $ ls -1 other/.hg/store/00changelog.i*
314 other/.hg/store/00changelog.i
315
307 316 Error Handling
308 317 ==============
309 318
310 319 Check that errors are properly returned to the client during push.
311 320
312 321 Setting up
313 322
314 323 $ cat > failpush.py << EOF
315 324 > """A small extension that makes push fails when using bundle2
316 325 >
317 326 > used to test error handling in bundle2
318 327 > """
319 328 >
320 329 > from mercurial import util
321 330 > from mercurial import bundle2
322 331 > from mercurial import exchange
323 332 > from mercurial import extensions
324 333 >
325 334 > def _pushbundle2failpart(pushop, bundler):
326 335 > reason = pushop.ui.config('failpush', 'reason', None)
327 336 > part = None
328 337 > if reason == 'abort':
329 338 > bundler.newpart('test:abort')
330 339 > if reason == 'unknown':
331 340 > bundler.newpart('TEST:UNKNOWN')
332 341 > if reason == 'race':
333 342 > # 20 Bytes of crap
334 343 > bundler.newpart('b2x:check:heads', data='01234567890123456789')
335 344 >
336 345 > @bundle2.parthandler("test:abort")
337 346 > def handleabort(op, part):
338 347 > raise util.Abort('Abandon ship!', hint="don't panic")
339 348 >
340 349 > def uisetup(ui):
341 350 > exchange.b2partsgenmapping['failpart'] = _pushbundle2failpart
342 351 > exchange.b2partsgenorder.insert(0, 'failpart')
343 352 >
344 353 > EOF
345 354
346 355 $ cd main
347 356 $ hg up tip
348 357 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
349 358 $ echo 'I' > I
350 359 $ hg add I
351 360 $ hg ci -m 'I'
352 361 $ hg id
353 362 e7ec4e813ba6 tip
354 363 $ cd ..
355 364
356 365 $ cat << EOF >> $HGRCPATH
357 366 > [extensions]
358 367 > failpush=$TESTTMP/failpush.py
359 368 > EOF
360 369
361 370 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
362 371 $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
363 372 $ cat other.pid >> $DAEMON_PIDS
364 373
365 374 Doing the actual push: Abort error
366 375
367 376 $ cat << EOF >> $HGRCPATH
368 377 > [failpush]
369 378 > reason = abort
370 379 > EOF
371 380
372 381 $ hg -R main push other -r e7ec4e813ba6
373 382 pushing to other
374 383 searching for changes
375 384 abort: Abandon ship!
376 385 (don't panic)
377 386 [255]
378 387
379 388 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
380 389 pushing to ssh://user@dummy/other
381 390 searching for changes
382 391 abort: Abandon ship!
383 392 (don't panic)
384 393 [255]
385 394
386 395 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
387 396 pushing to http://localhost:$HGPORT2/
388 397 searching for changes
389 398 abort: Abandon ship!
390 399 (don't panic)
391 400 [255]
392 401
393 402
394 403 Doing the actual push: unknown mandatory parts
395 404
396 405 $ cat << EOF >> $HGRCPATH
397 406 > [failpush]
398 407 > reason = unknown
399 408 > EOF
400 409
401 410 $ hg -R main push other -r e7ec4e813ba6
402 411 pushing to other
403 412 searching for changes
404 413 abort: missing support for test:unknown
405 414 [255]
406 415
407 416 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
408 417 pushing to ssh://user@dummy/other
409 418 searching for changes
410 419 abort: missing support for test:unknown
411 420 [255]
412 421
413 422 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
414 423 pushing to http://localhost:$HGPORT2/
415 424 searching for changes
416 425 abort: missing support for test:unknown
417 426 [255]
418 427
419 428 Doing the actual push: race
420 429
421 430 $ cat << EOF >> $HGRCPATH
422 431 > [failpush]
423 432 > reason = race
424 433 > EOF
425 434
426 435 $ hg -R main push other -r e7ec4e813ba6
427 436 pushing to other
428 437 searching for changes
429 438 abort: push failed:
430 439 'repository changed while pushing - please try again'
431 440 [255]
432 441
433 442 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
434 443 pushing to ssh://user@dummy/other
435 444 searching for changes
436 445 abort: push failed:
437 446 'repository changed while pushing - please try again'
438 447 [255]
439 448
440 449 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
441 450 pushing to http://localhost:$HGPORT2/
442 451 searching for changes
443 452 abort: push failed:
444 453 'repository changed while pushing - please try again'
445 454 [255]
446 455
447 456 Doing the actual push: hook abort
448 457
449 458 $ cat << EOF >> $HGRCPATH
450 459 > [failpush]
451 460 > reason =
452 461 > [hooks]
453 462 > b2x-pretransactionclose.failpush = false
454 463 > EOF
455 464
456 465 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
457 466 $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
458 467 $ cat other.pid >> $DAEMON_PIDS
459 468
460 469 $ hg -R main push other -r e7ec4e813ba6
461 470 pushing to other
462 471 searching for changes
463 472 transaction abort!
464 473 rollback completed
465 474 abort: b2x-pretransactionclose.failpush hook exited with status 1
466 475 [255]
467 476
468 477 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
469 478 pushing to ssh://user@dummy/other
470 479 searching for changes
471 480 abort: b2x-pretransactionclose.failpush hook exited with status 1
472 481 remote: transaction abort!
473 482 remote: rollback completed
474 483 [255]
475 484
476 485 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
477 486 pushing to http://localhost:$HGPORT2/
478 487 searching for changes
479 488 abort: b2x-pretransactionclose.failpush hook exited with status 1
480 489 [255]
481 490
491 (check that no 'pending' files remain)
482 492
493 $ ls -1 other/.hg/bookmarks*
494 other/.hg/bookmarks
495 $ ls -1 other/.hg/store/phaseroots*
496 other/.hg/store/phaseroots
497 $ ls -1 other/.hg/store/00changelog.i*
498 other/.hg/store/00changelog.i
499
General Comments 0
You need to be logged in to leave comments. Login now