##// END OF EJS Templates
commit: move editor outside transaction...
Matt Mackall -
r8496:a21605de default
parent child Browse files
Show More
@@ -1,2092 +1,2087 b''
1 1 # localrepo.py - read/write repository class for mercurial
2 2 #
3 3 # Copyright 2005-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, incorporated herein by reference.
7 7
8 8 from node import bin, hex, nullid, nullrev, short
9 9 from i18n import _
10 10 import repo, changegroup
11 11 import changelog, dirstate, filelog, manifest, context
12 12 import lock, transaction, store, encoding
13 13 import util, extensions, hook, error
14 14 import match as match_
15 15 import merge as merge_
16 16 from lock import release
17 17 import weakref, stat, errno, os, time, inspect
18 18 propertycache = util.propertycache
19 19
20 20 class localrepository(repo.repository):
21 21 capabilities = set(('lookup', 'changegroupsubset'))
22 22 supported = set('revlogv1 store fncache'.split())
23 23
24 24 def __init__(self, baseui, path=None, create=0):
25 25 repo.repository.__init__(self)
26 26 self.root = os.path.realpath(path)
27 27 self.path = os.path.join(self.root, ".hg")
28 28 self.origroot = path
29 29 self.opener = util.opener(self.path)
30 30 self.wopener = util.opener(self.root)
31 31
32 32 if not os.path.isdir(self.path):
33 33 if create:
34 34 if not os.path.exists(path):
35 35 os.mkdir(path)
36 36 os.mkdir(self.path)
37 37 requirements = ["revlogv1"]
38 38 if baseui.configbool('format', 'usestore', True):
39 39 os.mkdir(os.path.join(self.path, "store"))
40 40 requirements.append("store")
41 41 if baseui.configbool('format', 'usefncache', True):
42 42 requirements.append("fncache")
43 43 # create an invalid changelog
44 44 self.opener("00changelog.i", "a").write(
45 45 '\0\0\0\2' # represents revlogv2
46 46 ' dummy changelog to prevent using the old repo layout'
47 47 )
48 48 reqfile = self.opener("requires", "w")
49 49 for r in requirements:
50 50 reqfile.write("%s\n" % r)
51 51 reqfile.close()
52 52 else:
53 53 raise error.RepoError(_("repository %s not found") % path)
54 54 elif create:
55 55 raise error.RepoError(_("repository %s already exists") % path)
56 56 else:
57 57 # find requirements
58 58 requirements = set()
59 59 try:
60 60 requirements = set(self.opener("requires").read().splitlines())
61 61 except IOError, inst:
62 62 if inst.errno != errno.ENOENT:
63 63 raise
64 64 for r in requirements - self.supported:
65 65 raise error.RepoError(_("requirement '%s' not supported") % r)
66 66
67 67 self.store = store.store(requirements, self.path, util.opener)
68 68 self.spath = self.store.path
69 69 self.sopener = self.store.opener
70 70 self.sjoin = self.store.join
71 71 self.opener.createmode = self.store.createmode
72 72
73 73 self.baseui = baseui
74 74 self.ui = baseui.copy()
75 75 try:
76 76 self.ui.readconfig(self.join("hgrc"), self.root)
77 77 extensions.loadall(self.ui)
78 78 except IOError:
79 79 pass
80 80
81 81 self.tagscache = None
82 82 self._tagstypecache = None
83 83 self.branchcache = None
84 84 self._ubranchcache = None # UTF-8 version of branchcache
85 85 self._branchcachetip = None
86 86 self.nodetagscache = None
87 87 self.filterpats = {}
88 88 self._datafilters = {}
89 89 self._transref = self._lockref = self._wlockref = None
90 90
91 91 @propertycache
92 92 def changelog(self):
93 93 c = changelog.changelog(self.sopener)
94 94 if 'HG_PENDING' in os.environ:
95 95 p = os.environ['HG_PENDING']
96 96 if p.startswith(self.root):
97 97 c.readpending('00changelog.i.a')
98 98 self.sopener.defversion = c.version
99 99 return c
100 100
101 101 @propertycache
102 102 def manifest(self):
103 103 return manifest.manifest(self.sopener)
104 104
105 105 @propertycache
106 106 def dirstate(self):
107 107 return dirstate.dirstate(self.opener, self.ui, self.root)
108 108
109 109 def __getitem__(self, changeid):
110 110 if changeid == None:
111 111 return context.workingctx(self)
112 112 return context.changectx(self, changeid)
113 113
114 114 def __nonzero__(self):
115 115 return True
116 116
117 117 def __len__(self):
118 118 return len(self.changelog)
119 119
120 120 def __iter__(self):
121 121 for i in xrange(len(self)):
122 122 yield i
123 123
124 124 def url(self):
125 125 return 'file:' + self.root
126 126
127 127 def hook(self, name, throw=False, **args):
128 128 return hook.hook(self.ui, self, name, throw, **args)
129 129
130 130 tag_disallowed = ':\r\n'
131 131
132 132 def _tag(self, names, node, message, local, user, date, extra={}):
133 133 if isinstance(names, str):
134 134 allchars = names
135 135 names = (names,)
136 136 else:
137 137 allchars = ''.join(names)
138 138 for c in self.tag_disallowed:
139 139 if c in allchars:
140 140 raise util.Abort(_('%r cannot be used in a tag name') % c)
141 141
142 142 for name in names:
143 143 self.hook('pretag', throw=True, node=hex(node), tag=name,
144 144 local=local)
145 145
146 146 def writetags(fp, names, munge, prevtags):
147 147 fp.seek(0, 2)
148 148 if prevtags and prevtags[-1] != '\n':
149 149 fp.write('\n')
150 150 for name in names:
151 151 m = munge and munge(name) or name
152 152 if self._tagstypecache and name in self._tagstypecache:
153 153 old = self.tagscache.get(name, nullid)
154 154 fp.write('%s %s\n' % (hex(old), m))
155 155 fp.write('%s %s\n' % (hex(node), m))
156 156 fp.close()
157 157
158 158 prevtags = ''
159 159 if local:
160 160 try:
161 161 fp = self.opener('localtags', 'r+')
162 162 except IOError:
163 163 fp = self.opener('localtags', 'a')
164 164 else:
165 165 prevtags = fp.read()
166 166
167 167 # local tags are stored in the current charset
168 168 writetags(fp, names, None, prevtags)
169 169 for name in names:
170 170 self.hook('tag', node=hex(node), tag=name, local=local)
171 171 return
172 172
173 173 try:
174 174 fp = self.wfile('.hgtags', 'rb+')
175 175 except IOError:
176 176 fp = self.wfile('.hgtags', 'ab')
177 177 else:
178 178 prevtags = fp.read()
179 179
180 180 # committed tags are stored in UTF-8
181 181 writetags(fp, names, encoding.fromlocal, prevtags)
182 182
183 183 if '.hgtags' not in self.dirstate:
184 184 self.add(['.hgtags'])
185 185
186 186 tagnode = self.commit(['.hgtags'], message, user, date, extra=extra)
187 187
188 188 for name in names:
189 189 self.hook('tag', node=hex(node), tag=name, local=local)
190 190
191 191 return tagnode
192 192
193 193 def tag(self, names, node, message, local, user, date):
194 194 '''tag a revision with one or more symbolic names.
195 195
196 196 names is a list of strings or, when adding a single tag, names may be a
197 197 string.
198 198
199 199 if local is True, the tags are stored in a per-repository file.
200 200 otherwise, they are stored in the .hgtags file, and a new
201 201 changeset is committed with the change.
202 202
203 203 keyword arguments:
204 204
205 205 local: whether to store tags in non-version-controlled file
206 206 (default False)
207 207
208 208 message: commit message to use if committing
209 209
210 210 user: name of user to use if committing
211 211
212 212 date: date tuple to use if committing'''
213 213
214 214 for x in self.status()[:5]:
215 215 if '.hgtags' in x:
216 216 raise util.Abort(_('working copy of .hgtags is changed '
217 217 '(please commit .hgtags manually)'))
218 218
219 219 self.tags() # instantiate the cache
220 220 self._tag(names, node, message, local, user, date)
221 221
222 222 def tags(self):
223 223 '''return a mapping of tag to node'''
224 224 if self.tagscache:
225 225 return self.tagscache
226 226
227 227 globaltags = {}
228 228 tagtypes = {}
229 229
230 230 def readtags(lines, fn, tagtype):
231 231 filetags = {}
232 232 count = 0
233 233
234 234 def warn(msg):
235 235 self.ui.warn(_("%s, line %s: %s\n") % (fn, count, msg))
236 236
237 237 for l in lines:
238 238 count += 1
239 239 if not l:
240 240 continue
241 241 s = l.split(" ", 1)
242 242 if len(s) != 2:
243 243 warn(_("cannot parse entry"))
244 244 continue
245 245 node, key = s
246 246 key = encoding.tolocal(key.strip()) # stored in UTF-8
247 247 try:
248 248 bin_n = bin(node)
249 249 except TypeError:
250 250 warn(_("node '%s' is not well formed") % node)
251 251 continue
252 252 if bin_n not in self.changelog.nodemap:
253 253 warn(_("tag '%s' refers to unknown node") % key)
254 254 continue
255 255
256 256 h = []
257 257 if key in filetags:
258 258 n, h = filetags[key]
259 259 h.append(n)
260 260 filetags[key] = (bin_n, h)
261 261
262 262 for k, nh in filetags.iteritems():
263 263 if k not in globaltags:
264 264 globaltags[k] = nh
265 265 tagtypes[k] = tagtype
266 266 continue
267 267
268 268 # we prefer the global tag if:
269 269 # it supercedes us OR
270 270 # mutual supercedes and it has a higher rank
271 271 # otherwise we win because we're tip-most
272 272 an, ah = nh
273 273 bn, bh = globaltags[k]
274 274 if (bn != an and an in bh and
275 275 (bn not in ah or len(bh) > len(ah))):
276 276 an = bn
277 277 ah.extend([n for n in bh if n not in ah])
278 278 globaltags[k] = an, ah
279 279 tagtypes[k] = tagtype
280 280
281 281 # read the tags file from each head, ending with the tip
282 282 f = None
283 283 for rev, node, fnode in self._hgtagsnodes():
284 284 f = (f and f.filectx(fnode) or
285 285 self.filectx('.hgtags', fileid=fnode))
286 286 readtags(f.data().splitlines(), f, "global")
287 287
288 288 try:
289 289 data = encoding.fromlocal(self.opener("localtags").read())
290 290 # localtags are stored in the local character set
291 291 # while the internal tag table is stored in UTF-8
292 292 readtags(data.splitlines(), "localtags", "local")
293 293 except IOError:
294 294 pass
295 295
296 296 self.tagscache = {}
297 297 self._tagstypecache = {}
298 298 for k, nh in globaltags.iteritems():
299 299 n = nh[0]
300 300 if n != nullid:
301 301 self.tagscache[k] = n
302 302 self._tagstypecache[k] = tagtypes[k]
303 303 self.tagscache['tip'] = self.changelog.tip()
304 304 return self.tagscache
305 305
306 306 def tagtype(self, tagname):
307 307 '''
308 308 return the type of the given tag. result can be:
309 309
310 310 'local' : a local tag
311 311 'global' : a global tag
312 312 None : tag does not exist
313 313 '''
314 314
315 315 self.tags()
316 316
317 317 return self._tagstypecache.get(tagname)
318 318
319 319 def _hgtagsnodes(self):
320 320 last = {}
321 321 ret = []
322 322 for node in reversed(self.heads()):
323 323 c = self[node]
324 324 rev = c.rev()
325 325 try:
326 326 fnode = c.filenode('.hgtags')
327 327 except error.LookupError:
328 328 continue
329 329 ret.append((rev, node, fnode))
330 330 if fnode in last:
331 331 ret[last[fnode]] = None
332 332 last[fnode] = len(ret) - 1
333 333 return [item for item in ret if item]
334 334
335 335 def tagslist(self):
336 336 '''return a list of tags ordered by revision'''
337 337 l = []
338 338 for t, n in self.tags().iteritems():
339 339 try:
340 340 r = self.changelog.rev(n)
341 341 except:
342 342 r = -2 # sort to the beginning of the list if unknown
343 343 l.append((r, t, n))
344 344 return [(t, n) for r, t, n in sorted(l)]
345 345
346 346 def nodetags(self, node):
347 347 '''return the tags associated with a node'''
348 348 if not self.nodetagscache:
349 349 self.nodetagscache = {}
350 350 for t, n in self.tags().iteritems():
351 351 self.nodetagscache.setdefault(n, []).append(t)
352 352 return self.nodetagscache.get(node, [])
353 353
354 354 def _branchtags(self, partial, lrev):
355 355 # TODO: rename this function?
356 356 tiprev = len(self) - 1
357 357 if lrev != tiprev:
358 358 self._updatebranchcache(partial, lrev+1, tiprev+1)
359 359 self._writebranchcache(partial, self.changelog.tip(), tiprev)
360 360
361 361 return partial
362 362
363 363 def _branchheads(self):
364 364 tip = self.changelog.tip()
365 365 if self.branchcache is not None and self._branchcachetip == tip:
366 366 return self.branchcache
367 367
368 368 oldtip = self._branchcachetip
369 369 self._branchcachetip = tip
370 370 if self.branchcache is None:
371 371 self.branchcache = {} # avoid recursion in changectx
372 372 else:
373 373 self.branchcache.clear() # keep using the same dict
374 374 if oldtip is None or oldtip not in self.changelog.nodemap:
375 375 partial, last, lrev = self._readbranchcache()
376 376 else:
377 377 lrev = self.changelog.rev(oldtip)
378 378 partial = self._ubranchcache
379 379
380 380 self._branchtags(partial, lrev)
381 381 # this private cache holds all heads (not just tips)
382 382 self._ubranchcache = partial
383 383
384 384 # the branch cache is stored on disk as UTF-8, but in the local
385 385 # charset internally
386 386 for k, v in partial.iteritems():
387 387 self.branchcache[encoding.tolocal(k)] = v
388 388 return self.branchcache
389 389
390 390
391 391 def branchtags(self):
392 392 '''return a dict where branch names map to the tipmost head of
393 393 the branch, open heads come before closed'''
394 394 bt = {}
395 395 for bn, heads in self._branchheads().iteritems():
396 396 head = None
397 397 for i in range(len(heads)-1, -1, -1):
398 398 h = heads[i]
399 399 if 'close' not in self.changelog.read(h)[5]:
400 400 head = h
401 401 break
402 402 # no open heads were found
403 403 if head is None:
404 404 head = heads[-1]
405 405 bt[bn] = head
406 406 return bt
407 407
408 408
409 409 def _readbranchcache(self):
410 410 partial = {}
411 411 try:
412 412 f = self.opener("branchheads.cache")
413 413 lines = f.read().split('\n')
414 414 f.close()
415 415 except (IOError, OSError):
416 416 return {}, nullid, nullrev
417 417
418 418 try:
419 419 last, lrev = lines.pop(0).split(" ", 1)
420 420 last, lrev = bin(last), int(lrev)
421 421 if lrev >= len(self) or self[lrev].node() != last:
422 422 # invalidate the cache
423 423 raise ValueError('invalidating branch cache (tip differs)')
424 424 for l in lines:
425 425 if not l: continue
426 426 node, label = l.split(" ", 1)
427 427 partial.setdefault(label.strip(), []).append(bin(node))
428 428 except KeyboardInterrupt:
429 429 raise
430 430 except Exception, inst:
431 431 if self.ui.debugflag:
432 432 self.ui.warn(str(inst), '\n')
433 433 partial, last, lrev = {}, nullid, nullrev
434 434 return partial, last, lrev
435 435
436 436 def _writebranchcache(self, branches, tip, tiprev):
437 437 try:
438 438 f = self.opener("branchheads.cache", "w", atomictemp=True)
439 439 f.write("%s %s\n" % (hex(tip), tiprev))
440 440 for label, nodes in branches.iteritems():
441 441 for node in nodes:
442 442 f.write("%s %s\n" % (hex(node), label))
443 443 f.rename()
444 444 except (IOError, OSError):
445 445 pass
446 446
447 447 def _updatebranchcache(self, partial, start, end):
448 448 for r in xrange(start, end):
449 449 c = self[r]
450 450 b = c.branch()
451 451 bheads = partial.setdefault(b, [])
452 452 bheads.append(c.node())
453 453 for p in c.parents():
454 454 pn = p.node()
455 455 if pn in bheads:
456 456 bheads.remove(pn)
457 457
458 458 def lookup(self, key):
459 459 if isinstance(key, int):
460 460 return self.changelog.node(key)
461 461 elif key == '.':
462 462 return self.dirstate.parents()[0]
463 463 elif key == 'null':
464 464 return nullid
465 465 elif key == 'tip':
466 466 return self.changelog.tip()
467 467 n = self.changelog._match(key)
468 468 if n:
469 469 return n
470 470 if key in self.tags():
471 471 return self.tags()[key]
472 472 if key in self.branchtags():
473 473 return self.branchtags()[key]
474 474 n = self.changelog._partialmatch(key)
475 475 if n:
476 476 return n
477 477 try:
478 478 if len(key) == 20:
479 479 key = hex(key)
480 480 except:
481 481 pass
482 482 raise error.RepoError(_("unknown revision '%s'") % key)
483 483
484 484 def local(self):
485 485 return True
486 486
487 487 def join(self, f):
488 488 return os.path.join(self.path, f)
489 489
490 490 def wjoin(self, f):
491 491 return os.path.join(self.root, f)
492 492
493 493 def rjoin(self, f):
494 494 return os.path.join(self.root, util.pconvert(f))
495 495
496 496 def file(self, f):
497 497 if f[0] == '/':
498 498 f = f[1:]
499 499 return filelog.filelog(self.sopener, f)
500 500
501 501 def changectx(self, changeid):
502 502 return self[changeid]
503 503
504 504 def parents(self, changeid=None):
505 505 '''get list of changectxs for parents of changeid'''
506 506 return self[changeid].parents()
507 507
508 508 def filectx(self, path, changeid=None, fileid=None):
509 509 """changeid can be a changeset revision, node, or tag.
510 510 fileid can be a file revision or node."""
511 511 return context.filectx(self, path, changeid, fileid)
512 512
513 513 def getcwd(self):
514 514 return self.dirstate.getcwd()
515 515
516 516 def pathto(self, f, cwd=None):
517 517 return self.dirstate.pathto(f, cwd)
518 518
519 519 def wfile(self, f, mode='r'):
520 520 return self.wopener(f, mode)
521 521
522 522 def _link(self, f):
523 523 return os.path.islink(self.wjoin(f))
524 524
525 525 def _filter(self, filter, filename, data):
526 526 if filter not in self.filterpats:
527 527 l = []
528 528 for pat, cmd in self.ui.configitems(filter):
529 529 if cmd == '!':
530 530 continue
531 531 mf = util.matcher(self.root, "", [pat], [], [])[1]
532 532 fn = None
533 533 params = cmd
534 534 for name, filterfn in self._datafilters.iteritems():
535 535 if cmd.startswith(name):
536 536 fn = filterfn
537 537 params = cmd[len(name):].lstrip()
538 538 break
539 539 if not fn:
540 540 fn = lambda s, c, **kwargs: util.filter(s, c)
541 541 # Wrap old filters not supporting keyword arguments
542 542 if not inspect.getargspec(fn)[2]:
543 543 oldfn = fn
544 544 fn = lambda s, c, **kwargs: oldfn(s, c)
545 545 l.append((mf, fn, params))
546 546 self.filterpats[filter] = l
547 547
548 548 for mf, fn, cmd in self.filterpats[filter]:
549 549 if mf(filename):
550 550 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
551 551 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
552 552 break
553 553
554 554 return data
555 555
556 556 def adddatafilter(self, name, filter):
557 557 self._datafilters[name] = filter
558 558
559 559 def wread(self, filename):
560 560 if self._link(filename):
561 561 data = os.readlink(self.wjoin(filename))
562 562 else:
563 563 data = self.wopener(filename, 'r').read()
564 564 return self._filter("encode", filename, data)
565 565
566 566 def wwrite(self, filename, data, flags):
567 567 data = self._filter("decode", filename, data)
568 568 try:
569 569 os.unlink(self.wjoin(filename))
570 570 except OSError:
571 571 pass
572 572 if 'l' in flags:
573 573 self.wopener.symlink(data, filename)
574 574 else:
575 575 self.wopener(filename, 'w').write(data)
576 576 if 'x' in flags:
577 577 util.set_flags(self.wjoin(filename), False, True)
578 578
579 579 def wwritedata(self, filename, data):
580 580 return self._filter("decode", filename, data)
581 581
582 582 def transaction(self):
583 583 tr = self._transref and self._transref() or None
584 584 if tr and tr.running():
585 585 return tr.nest()
586 586
587 587 # abort here if the journal already exists
588 588 if os.path.exists(self.sjoin("journal")):
589 589 raise error.RepoError(_("journal already exists - run hg recover"))
590 590
591 591 # save dirstate for rollback
592 592 try:
593 593 ds = self.opener("dirstate").read()
594 594 except IOError:
595 595 ds = ""
596 596 self.opener("journal.dirstate", "w").write(ds)
597 597 self.opener("journal.branch", "w").write(self.dirstate.branch())
598 598
599 599 renames = [(self.sjoin("journal"), self.sjoin("undo")),
600 600 (self.join("journal.dirstate"), self.join("undo.dirstate")),
601 601 (self.join("journal.branch"), self.join("undo.branch"))]
602 602 tr = transaction.transaction(self.ui.warn, self.sopener,
603 603 self.sjoin("journal"),
604 604 aftertrans(renames),
605 605 self.store.createmode)
606 606 self._transref = weakref.ref(tr)
607 607 return tr
608 608
609 609 def recover(self):
610 610 lock = self.lock()
611 611 try:
612 612 if os.path.exists(self.sjoin("journal")):
613 613 self.ui.status(_("rolling back interrupted transaction\n"))
614 614 transaction.rollback(self.sopener, self.sjoin("journal"), self.ui.warn)
615 615 self.invalidate()
616 616 return True
617 617 else:
618 618 self.ui.warn(_("no interrupted transaction available\n"))
619 619 return False
620 620 finally:
621 621 lock.release()
622 622
623 623 def rollback(self):
624 624 wlock = lock = None
625 625 try:
626 626 wlock = self.wlock()
627 627 lock = self.lock()
628 628 if os.path.exists(self.sjoin("undo")):
629 629 self.ui.status(_("rolling back last transaction\n"))
630 630 transaction.rollback(self.sopener, self.sjoin("undo"), self.ui.warn)
631 631 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
632 632 try:
633 633 branch = self.opener("undo.branch").read()
634 634 self.dirstate.setbranch(branch)
635 635 except IOError:
636 636 self.ui.warn(_("Named branch could not be reset, "
637 637 "current branch still is: %s\n")
638 638 % encoding.tolocal(self.dirstate.branch()))
639 639 self.invalidate()
640 640 self.dirstate.invalidate()
641 641 else:
642 642 self.ui.warn(_("no rollback information available\n"))
643 643 finally:
644 644 release(lock, wlock)
645 645
646 646 def invalidate(self):
647 647 for a in "changelog manifest".split():
648 648 if a in self.__dict__:
649 649 delattr(self, a)
650 650 self.tagscache = None
651 651 self._tagstypecache = None
652 652 self.nodetagscache = None
653 653 self.branchcache = None
654 654 self._ubranchcache = None
655 655 self._branchcachetip = None
656 656
657 657 def _lock(self, lockname, wait, releasefn, acquirefn, desc):
658 658 try:
659 659 l = lock.lock(lockname, 0, releasefn, desc=desc)
660 660 except error.LockHeld, inst:
661 661 if not wait:
662 662 raise
663 663 self.ui.warn(_("waiting for lock on %s held by %r\n") %
664 664 (desc, inst.locker))
665 665 # default to 600 seconds timeout
666 666 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
667 667 releasefn, desc=desc)
668 668 if acquirefn:
669 669 acquirefn()
670 670 return l
671 671
672 672 def lock(self, wait=True):
673 673 l = self._lockref and self._lockref()
674 674 if l is not None and l.held:
675 675 l.lock()
676 676 return l
677 677
678 678 l = self._lock(self.sjoin("lock"), wait, None, self.invalidate,
679 679 _('repository %s') % self.origroot)
680 680 self._lockref = weakref.ref(l)
681 681 return l
682 682
683 683 def wlock(self, wait=True):
684 684 l = self._wlockref and self._wlockref()
685 685 if l is not None and l.held:
686 686 l.lock()
687 687 return l
688 688
689 689 l = self._lock(self.join("wlock"), wait, self.dirstate.write,
690 690 self.dirstate.invalidate, _('working directory of %s') %
691 691 self.origroot)
692 692 self._wlockref = weakref.ref(l)
693 693 return l
694 694
695 695 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
696 696 """
697 697 commit an individual file as part of a larger transaction
698 698 """
699 699
700 700 fname = fctx.path()
701 701 text = fctx.data()
702 702 flog = self.file(fname)
703 703 fparent1 = manifest1.get(fname, nullid)
704 704 fparent2 = fparent2o = manifest2.get(fname, nullid)
705 705
706 706 meta = {}
707 707 copy = fctx.renamed()
708 708 if copy and copy[0] != fname:
709 709 # Mark the new revision of this file as a copy of another
710 710 # file. This copy data will effectively act as a parent
711 711 # of this new revision. If this is a merge, the first
712 712 # parent will be the nullid (meaning "look up the copy data")
713 713 # and the second one will be the other parent. For example:
714 714 #
715 715 # 0 --- 1 --- 3 rev1 changes file foo
716 716 # \ / rev2 renames foo to bar and changes it
717 717 # \- 2 -/ rev3 should have bar with all changes and
718 718 # should record that bar descends from
719 719 # bar in rev2 and foo in rev1
720 720 #
721 721 # this allows this merge to succeed:
722 722 #
723 723 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
724 724 # \ / merging rev3 and rev4 should use bar@rev2
725 725 # \- 2 --- 4 as the merge base
726 726 #
727 727
728 728 cfname = copy[0]
729 729 crev = manifest1.get(cfname)
730 730 newfparent = fparent2
731 731
732 732 if manifest2: # branch merge
733 733 if fparent2 == nullid or crev is None: # copied on remote side
734 734 if cfname in manifest2:
735 735 crev = manifest2[cfname]
736 736 newfparent = fparent1
737 737
738 738 # find source in nearest ancestor if we've lost track
739 739 if not crev:
740 740 self.ui.debug(_(" %s: searching for copy revision for %s\n") %
741 741 (fname, cfname))
742 742 for ancestor in self['.'].ancestors():
743 743 if cfname in ancestor:
744 744 crev = ancestor[cfname].filenode()
745 745 break
746 746
747 747 self.ui.debug(_(" %s: copy %s:%s\n") % (fname, cfname, hex(crev)))
748 748 meta["copy"] = cfname
749 749 meta["copyrev"] = hex(crev)
750 750 fparent1, fparent2 = nullid, newfparent
751 751 elif fparent2 != nullid:
752 752 # is one parent an ancestor of the other?
753 753 fparentancestor = flog.ancestor(fparent1, fparent2)
754 754 if fparentancestor == fparent1:
755 755 fparent1, fparent2 = fparent2, nullid
756 756 elif fparentancestor == fparent2:
757 757 fparent2 = nullid
758 758
759 759 # is the file changed?
760 760 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
761 761 changelist.append(fname)
762 762 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
763 763
764 764 # are just the flags changed during merge?
765 765 if fparent1 != fparent2o and manifest1.flags(fname) != fctx.flags():
766 766 changelist.append(fname)
767 767
768 768 return fparent1
769 769
770 770 def commit(self, files=None, text="", user=None, date=None, match=None,
771 771 force=False, editor=False, extra={}):
772 772 wlock = lock = None
773 773 if extra.get("close"):
774 774 force = True
775 775 if files:
776 776 files = list(set(files))
777 777
778 778 ret = None
779 779 wlock = self.wlock()
780 780 try:
781 781 p1, p2 = self.dirstate.parents()
782 782
783 783 if (not force and p2 != nullid and
784 784 (match and (match.files() or match.anypats()))):
785 785 raise util.Abort(_('cannot partially commit a merge '
786 786 '(do not specify files or patterns)'))
787 787
788 788 if files:
789 789 modified, removed = [], []
790 790 for f in files:
791 791 s = self.dirstate[f]
792 792 if s in 'nma':
793 793 modified.append(f)
794 794 elif s == 'r':
795 795 removed.append(f)
796 796 else:
797 797 self.ui.warn(_("%s not tracked!\n") % f)
798 798 changes = [modified, [], removed, [], []]
799 799 else:
800 800 changes = self.status(match=match)
801 801
802 802 if (not (changes[0] or changes[1] or changes[2])
803 803 and not force and p2 == nullid and
804 804 self[None].branch() == self['.'].branch()):
805 805 self.ui.status(_("nothing changed\n"))
806 806 return None
807 807
808 808 ms = merge_.mergestate(self)
809 809 for f in changes[0]:
810 810 if f in ms and ms[f] == 'u':
811 811 raise util.Abort(_("unresolved merge conflicts "
812 812 "(see hg resolve)"))
813
813 814 wctx = context.workingctx(self, (p1, p2), text, user, date,
814 815 extra, changes)
815 ret = self.commitctx(wctx, editor, True)
816 if editor:
817 wctx._text = editor(self, wctx,
818 changes[1], changes[0], changes[2])
819
820 ret = self.commitctx(wctx, True)
816 821 ms.reset()
817 822
818 823 # update dirstate
819 824 for f in changes[0] + changes[1]:
820 825 self.dirstate.normal(f)
821 826 for f in changes[2]:
822 827 self.dirstate.forget(f)
823 828 self.dirstate.setparents(ret)
824 829
825 830 return ret
826 831
827 832 finally:
828 833 if ret == None:
829 834 self.dirstate.invalidate() # didn't successfully commit
830 835 wlock.release()
831 836
832 def commitctx(self, ctx, editor=None, error=False):
837 def commitctx(self, ctx, error=False):
833 838 """Add a new revision to current repository.
834 839
835 840 Revision information is passed via the context argument.
836 841 If editor is supplied, it is called to get a commit message.
837 842 If working is set, the working directory is affected.
838 843 """
839 844
840 845 tr = lock = None
841 846 remove = ctx.removed()
842 847 p1, p2 = ctx.p1(), ctx.p2()
843 848 m1 = p1.manifest().copy()
844 849 m2 = p2.manifest()
845 850 user = ctx.user()
846 851
847 852 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
848 853 self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
849 854
850 855 lock = self.lock()
851 856 try:
852 857 tr = self.transaction()
853 858 trp = weakref.proxy(tr)
854 859
855 860 # check in files
856 861 new = {}
857 862 changed = []
858 863 linkrev = len(self)
859 864 for f in sorted(ctx.modified() + ctx.added()):
860 865 self.ui.note(f + "\n")
861 866 try:
862 867 fctx = ctx[f]
863 868 new[f] = self._filecommit(fctx, m1, m2, linkrev, trp,
864 869 changed)
865 870 m1.set(f, fctx.flags())
866 871 except (OSError, IOError):
867 872 if error:
868 873 self.ui.warn(_("trouble committing %s!\n") % f)
869 874 raise
870 875 else:
871 876 remove.append(f)
872 877
873 updated, added = [], []
874 for f in sorted(changed):
875 if f in m1 or f in m2:
876 updated.append(f)
877 else:
878 added.append(f)
879
880 878 # update manifest
881 879 m1.update(new)
882 880 removed = [f for f in sorted(remove) if f in m1 or f in m2]
883 881 removed1 = []
884 882
885 883 for f in removed:
886 884 if f in m1:
887 885 del m1[f]
888 886 removed1.append(f)
889 887 mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(),
890 888 p2.manifestnode(), (new, removed1))
891 889
892 890 text = ctx.description()
893 if editor:
894 text = editor(self, ctx, added, updated, removed)
895
896 891 lines = [line.rstrip() for line in text.rstrip().splitlines()]
897 892 while lines and not lines[0]:
898 893 del lines[0]
899 894 text = '\n'.join(lines)
900 895
901 896 self.changelog.delayupdate()
902 897 n = self.changelog.add(mn, changed + removed, text, trp,
903 898 p1.node(), p2.node(),
904 899 user, ctx.date(), ctx.extra().copy())
905 900 p = lambda: self.changelog.writepending() and self.root or ""
906 901 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
907 902 parent2=xp2, pending=p)
908 903 self.changelog.finalize(trp)
909 904 tr.close()
910 905
911 906 if self.branchcache:
912 907 self.branchtags()
913 908
914 909 self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
915 910 return n
916 911 finally:
917 912 del tr
918 913 lock.release()
919 914
920 915 def walk(self, match, node=None):
921 916 '''
922 917 walk recursively through the directory tree or a given
923 918 changeset, finding all files matched by the match
924 919 function
925 920 '''
926 921 return self[node].walk(match)
927 922
928 923 def status(self, node1='.', node2=None, match=None,
929 924 ignored=False, clean=False, unknown=False):
930 925 """return status of files between two nodes or node and working directory
931 926
932 927 If node1 is None, use the first dirstate parent instead.
933 928 If node2 is None, compare node1 with working directory.
934 929 """
935 930
936 931 def mfmatches(ctx):
937 932 mf = ctx.manifest().copy()
938 933 for fn in mf.keys():
939 934 if not match(fn):
940 935 del mf[fn]
941 936 return mf
942 937
943 938 if isinstance(node1, context.changectx):
944 939 ctx1 = node1
945 940 else:
946 941 ctx1 = self[node1]
947 942 if isinstance(node2, context.changectx):
948 943 ctx2 = node2
949 944 else:
950 945 ctx2 = self[node2]
951 946
952 947 working = ctx2.rev() is None
953 948 parentworking = working and ctx1 == self['.']
954 949 match = match or match_.always(self.root, self.getcwd())
955 950 listignored, listclean, listunknown = ignored, clean, unknown
956 951
957 952 # load earliest manifest first for caching reasons
958 953 if not working and ctx2.rev() < ctx1.rev():
959 954 ctx2.manifest()
960 955
961 956 if not parentworking:
962 957 def bad(f, msg):
963 958 if f not in ctx1:
964 959 self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
965 960 return False
966 961 match.bad = bad
967 962
968 963 if working: # we need to scan the working dir
969 964 s = self.dirstate.status(match, listignored, listclean, listunknown)
970 965 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
971 966
972 967 # check for any possibly clean files
973 968 if parentworking and cmp:
974 969 fixup = []
975 970 # do a full compare of any files that might have changed
976 971 for f in sorted(cmp):
977 972 if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f)
978 973 or ctx1[f].cmp(ctx2[f].data())):
979 974 modified.append(f)
980 975 else:
981 976 fixup.append(f)
982 977
983 978 if listclean:
984 979 clean += fixup
985 980
986 981 # update dirstate for files that are actually clean
987 982 if fixup:
988 983 wlock = None
989 984 try:
990 985 try:
991 986 # updating the dirstate is optional
992 987 # so we don't wait on the lock
993 988 wlock = self.wlock(False)
994 989 for f in fixup:
995 990 self.dirstate.normal(f)
996 991 except error.LockError:
997 992 pass
998 993 finally:
999 994 release(wlock)
1000 995
1001 996 if not parentworking:
1002 997 mf1 = mfmatches(ctx1)
1003 998 if working:
1004 999 # we are comparing working dir against non-parent
1005 1000 # generate a pseudo-manifest for the working dir
1006 1001 mf2 = mfmatches(self['.'])
1007 1002 for f in cmp + modified + added:
1008 1003 mf2[f] = None
1009 1004 mf2.set(f, ctx2.flags(f))
1010 1005 for f in removed:
1011 1006 if f in mf2:
1012 1007 del mf2[f]
1013 1008 else:
1014 1009 # we are comparing two revisions
1015 1010 deleted, unknown, ignored = [], [], []
1016 1011 mf2 = mfmatches(ctx2)
1017 1012
1018 1013 modified, added, clean = [], [], []
1019 1014 for fn in mf2:
1020 1015 if fn in mf1:
1021 1016 if (mf1.flags(fn) != mf2.flags(fn) or
1022 1017 (mf1[fn] != mf2[fn] and
1023 1018 (mf2[fn] or ctx1[fn].cmp(ctx2[fn].data())))):
1024 1019 modified.append(fn)
1025 1020 elif listclean:
1026 1021 clean.append(fn)
1027 1022 del mf1[fn]
1028 1023 else:
1029 1024 added.append(fn)
1030 1025 removed = mf1.keys()
1031 1026
1032 1027 r = modified, added, removed, deleted, unknown, ignored, clean
1033 1028 [l.sort() for l in r]
1034 1029 return r
1035 1030
1036 1031 def add(self, list):
1037 1032 wlock = self.wlock()
1038 1033 try:
1039 1034 rejected = []
1040 1035 for f in list:
1041 1036 p = self.wjoin(f)
1042 1037 try:
1043 1038 st = os.lstat(p)
1044 1039 except:
1045 1040 self.ui.warn(_("%s does not exist!\n") % f)
1046 1041 rejected.append(f)
1047 1042 continue
1048 1043 if st.st_size > 10000000:
1049 1044 self.ui.warn(_("%s: files over 10MB may cause memory and"
1050 1045 " performance problems\n"
1051 1046 "(use 'hg revert %s' to unadd the file)\n")
1052 1047 % (f, f))
1053 1048 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1054 1049 self.ui.warn(_("%s not added: only files and symlinks "
1055 1050 "supported currently\n") % f)
1056 1051 rejected.append(p)
1057 1052 elif self.dirstate[f] in 'amn':
1058 1053 self.ui.warn(_("%s already tracked!\n") % f)
1059 1054 elif self.dirstate[f] == 'r':
1060 1055 self.dirstate.normallookup(f)
1061 1056 else:
1062 1057 self.dirstate.add(f)
1063 1058 return rejected
1064 1059 finally:
1065 1060 wlock.release()
1066 1061
1067 1062 def forget(self, list):
1068 1063 wlock = self.wlock()
1069 1064 try:
1070 1065 for f in list:
1071 1066 if self.dirstate[f] != 'a':
1072 1067 self.ui.warn(_("%s not added!\n") % f)
1073 1068 else:
1074 1069 self.dirstate.forget(f)
1075 1070 finally:
1076 1071 wlock.release()
1077 1072
1078 1073 def remove(self, list, unlink=False):
1079 1074 wlock = None
1080 1075 try:
1081 1076 if unlink:
1082 1077 for f in list:
1083 1078 try:
1084 1079 util.unlink(self.wjoin(f))
1085 1080 except OSError, inst:
1086 1081 if inst.errno != errno.ENOENT:
1087 1082 raise
1088 1083 wlock = self.wlock()
1089 1084 for f in list:
1090 1085 if unlink and os.path.exists(self.wjoin(f)):
1091 1086 self.ui.warn(_("%s still exists!\n") % f)
1092 1087 elif self.dirstate[f] == 'a':
1093 1088 self.dirstate.forget(f)
1094 1089 elif f not in self.dirstate:
1095 1090 self.ui.warn(_("%s not tracked!\n") % f)
1096 1091 else:
1097 1092 self.dirstate.remove(f)
1098 1093 finally:
1099 1094 release(wlock)
1100 1095
1101 1096 def undelete(self, list):
1102 1097 manifests = [self.manifest.read(self.changelog.read(p)[0])
1103 1098 for p in self.dirstate.parents() if p != nullid]
1104 1099 wlock = self.wlock()
1105 1100 try:
1106 1101 for f in list:
1107 1102 if self.dirstate[f] != 'r':
1108 1103 self.ui.warn(_("%s not removed!\n") % f)
1109 1104 else:
1110 1105 m = f in manifests[0] and manifests[0] or manifests[1]
1111 1106 t = self.file(f).read(m[f])
1112 1107 self.wwrite(f, t, m.flags(f))
1113 1108 self.dirstate.normal(f)
1114 1109 finally:
1115 1110 wlock.release()
1116 1111
1117 1112 def copy(self, source, dest):
1118 1113 p = self.wjoin(dest)
1119 1114 if not (os.path.exists(p) or os.path.islink(p)):
1120 1115 self.ui.warn(_("%s does not exist!\n") % dest)
1121 1116 elif not (os.path.isfile(p) or os.path.islink(p)):
1122 1117 self.ui.warn(_("copy failed: %s is not a file or a "
1123 1118 "symbolic link\n") % dest)
1124 1119 else:
1125 1120 wlock = self.wlock()
1126 1121 try:
1127 1122 if self.dirstate[dest] in '?r':
1128 1123 self.dirstate.add(dest)
1129 1124 self.dirstate.copy(source, dest)
1130 1125 finally:
1131 1126 wlock.release()
1132 1127
1133 1128 def heads(self, start=None, closed=True):
1134 1129 heads = self.changelog.heads(start)
1135 1130 def display(head):
1136 1131 if closed:
1137 1132 return True
1138 1133 extras = self.changelog.read(head)[5]
1139 1134 return ('close' not in extras)
1140 1135 # sort the output in rev descending order
1141 1136 heads = [(-self.changelog.rev(h), h) for h in heads if display(h)]
1142 1137 return [n for (r, n) in sorted(heads)]
1143 1138
1144 1139 def branchheads(self, branch=None, start=None, closed=True):
1145 1140 if branch is None:
1146 1141 branch = self[None].branch()
1147 1142 branches = self._branchheads()
1148 1143 if branch not in branches:
1149 1144 return []
1150 1145 bheads = branches[branch]
1151 1146 # the cache returns heads ordered lowest to highest
1152 1147 bheads.reverse()
1153 1148 if start is not None:
1154 1149 # filter out the heads that cannot be reached from startrev
1155 1150 bheads = self.changelog.nodesbetween([start], bheads)[2]
1156 1151 if not closed:
1157 1152 bheads = [h for h in bheads if
1158 1153 ('close' not in self.changelog.read(h)[5])]
1159 1154 return bheads
1160 1155
1161 1156 def branches(self, nodes):
1162 1157 if not nodes:
1163 1158 nodes = [self.changelog.tip()]
1164 1159 b = []
1165 1160 for n in nodes:
1166 1161 t = n
1167 1162 while 1:
1168 1163 p = self.changelog.parents(n)
1169 1164 if p[1] != nullid or p[0] == nullid:
1170 1165 b.append((t, n, p[0], p[1]))
1171 1166 break
1172 1167 n = p[0]
1173 1168 return b
1174 1169
1175 1170 def between(self, pairs):
1176 1171 r = []
1177 1172
1178 1173 for top, bottom in pairs:
1179 1174 n, l, i = top, [], 0
1180 1175 f = 1
1181 1176
1182 1177 while n != bottom and n != nullid:
1183 1178 p = self.changelog.parents(n)[0]
1184 1179 if i == f:
1185 1180 l.append(n)
1186 1181 f = f * 2
1187 1182 n = p
1188 1183 i += 1
1189 1184
1190 1185 r.append(l)
1191 1186
1192 1187 return r
1193 1188
1194 1189 def findincoming(self, remote, base=None, heads=None, force=False):
1195 1190 """Return list of roots of the subsets of missing nodes from remote
1196 1191
1197 1192 If base dict is specified, assume that these nodes and their parents
1198 1193 exist on the remote side and that no child of a node of base exists
1199 1194 in both remote and self.
1200 1195 Furthermore base will be updated to include the nodes that exists
1201 1196 in self and remote but no children exists in self and remote.
1202 1197 If a list of heads is specified, return only nodes which are heads
1203 1198 or ancestors of these heads.
1204 1199
1205 1200 All the ancestors of base are in self and in remote.
1206 1201 All the descendants of the list returned are missing in self.
1207 1202 (and so we know that the rest of the nodes are missing in remote, see
1208 1203 outgoing)
1209 1204 """
1210 1205 return self.findcommonincoming(remote, base, heads, force)[1]
1211 1206
1212 1207 def findcommonincoming(self, remote, base=None, heads=None, force=False):
1213 1208 """Return a tuple (common, missing roots, heads) used to identify
1214 1209 missing nodes from remote.
1215 1210
1216 1211 If base dict is specified, assume that these nodes and their parents
1217 1212 exist on the remote side and that no child of a node of base exists
1218 1213 in both remote and self.
1219 1214 Furthermore base will be updated to include the nodes that exists
1220 1215 in self and remote but no children exists in self and remote.
1221 1216 If a list of heads is specified, return only nodes which are heads
1222 1217 or ancestors of these heads.
1223 1218
1224 1219 All the ancestors of base are in self and in remote.
1225 1220 """
1226 1221 m = self.changelog.nodemap
1227 1222 search = []
1228 1223 fetch = set()
1229 1224 seen = set()
1230 1225 seenbranch = set()
1231 1226 if base == None:
1232 1227 base = {}
1233 1228
1234 1229 if not heads:
1235 1230 heads = remote.heads()
1236 1231
1237 1232 if self.changelog.tip() == nullid:
1238 1233 base[nullid] = 1
1239 1234 if heads != [nullid]:
1240 1235 return [nullid], [nullid], list(heads)
1241 1236 return [nullid], [], []
1242 1237
1243 1238 # assume we're closer to the tip than the root
1244 1239 # and start by examining the heads
1245 1240 self.ui.status(_("searching for changes\n"))
1246 1241
1247 1242 unknown = []
1248 1243 for h in heads:
1249 1244 if h not in m:
1250 1245 unknown.append(h)
1251 1246 else:
1252 1247 base[h] = 1
1253 1248
1254 1249 heads = unknown
1255 1250 if not unknown:
1256 1251 return base.keys(), [], []
1257 1252
1258 1253 req = set(unknown)
1259 1254 reqcnt = 0
1260 1255
1261 1256 # search through remote branches
1262 1257 # a 'branch' here is a linear segment of history, with four parts:
1263 1258 # head, root, first parent, second parent
1264 1259 # (a branch always has two parents (or none) by definition)
1265 1260 unknown = remote.branches(unknown)
1266 1261 while unknown:
1267 1262 r = []
1268 1263 while unknown:
1269 1264 n = unknown.pop(0)
1270 1265 if n[0] in seen:
1271 1266 continue
1272 1267
1273 1268 self.ui.debug(_("examining %s:%s\n")
1274 1269 % (short(n[0]), short(n[1])))
1275 1270 if n[0] == nullid: # found the end of the branch
1276 1271 pass
1277 1272 elif n in seenbranch:
1278 1273 self.ui.debug(_("branch already found\n"))
1279 1274 continue
1280 1275 elif n[1] and n[1] in m: # do we know the base?
1281 1276 self.ui.debug(_("found incomplete branch %s:%s\n")
1282 1277 % (short(n[0]), short(n[1])))
1283 1278 search.append(n[0:2]) # schedule branch range for scanning
1284 1279 seenbranch.add(n)
1285 1280 else:
1286 1281 if n[1] not in seen and n[1] not in fetch:
1287 1282 if n[2] in m and n[3] in m:
1288 1283 self.ui.debug(_("found new changeset %s\n") %
1289 1284 short(n[1]))
1290 1285 fetch.add(n[1]) # earliest unknown
1291 1286 for p in n[2:4]:
1292 1287 if p in m:
1293 1288 base[p] = 1 # latest known
1294 1289
1295 1290 for p in n[2:4]:
1296 1291 if p not in req and p not in m:
1297 1292 r.append(p)
1298 1293 req.add(p)
1299 1294 seen.add(n[0])
1300 1295
1301 1296 if r:
1302 1297 reqcnt += 1
1303 1298 self.ui.debug(_("request %d: %s\n") %
1304 1299 (reqcnt, " ".join(map(short, r))))
1305 1300 for p in xrange(0, len(r), 10):
1306 1301 for b in remote.branches(r[p:p+10]):
1307 1302 self.ui.debug(_("received %s:%s\n") %
1308 1303 (short(b[0]), short(b[1])))
1309 1304 unknown.append(b)
1310 1305
1311 1306 # do binary search on the branches we found
1312 1307 while search:
1313 1308 newsearch = []
1314 1309 reqcnt += 1
1315 1310 for n, l in zip(search, remote.between(search)):
1316 1311 l.append(n[1])
1317 1312 p = n[0]
1318 1313 f = 1
1319 1314 for i in l:
1320 1315 self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
1321 1316 if i in m:
1322 1317 if f <= 2:
1323 1318 self.ui.debug(_("found new branch changeset %s\n") %
1324 1319 short(p))
1325 1320 fetch.add(p)
1326 1321 base[i] = 1
1327 1322 else:
1328 1323 self.ui.debug(_("narrowed branch search to %s:%s\n")
1329 1324 % (short(p), short(i)))
1330 1325 newsearch.append((p, i))
1331 1326 break
1332 1327 p, f = i, f * 2
1333 1328 search = newsearch
1334 1329
1335 1330 # sanity check our fetch list
1336 1331 for f in fetch:
1337 1332 if f in m:
1338 1333 raise error.RepoError(_("already have changeset ")
1339 1334 + short(f[:4]))
1340 1335
1341 1336 if base.keys() == [nullid]:
1342 1337 if force:
1343 1338 self.ui.warn(_("warning: repository is unrelated\n"))
1344 1339 else:
1345 1340 raise util.Abort(_("repository is unrelated"))
1346 1341
1347 1342 self.ui.debug(_("found new changesets starting at ") +
1348 1343 " ".join([short(f) for f in fetch]) + "\n")
1349 1344
1350 1345 self.ui.debug(_("%d total queries\n") % reqcnt)
1351 1346
1352 1347 return base.keys(), list(fetch), heads
1353 1348
1354 1349 def findoutgoing(self, remote, base=None, heads=None, force=False):
1355 1350 """Return list of nodes that are roots of subsets not in remote
1356 1351
1357 1352 If base dict is specified, assume that these nodes and their parents
1358 1353 exist on the remote side.
1359 1354 If a list of heads is specified, return only nodes which are heads
1360 1355 or ancestors of these heads, and return a second element which
1361 1356 contains all remote heads which get new children.
1362 1357 """
1363 1358 if base == None:
1364 1359 base = {}
1365 1360 self.findincoming(remote, base, heads, force=force)
1366 1361
1367 1362 self.ui.debug(_("common changesets up to ")
1368 1363 + " ".join(map(short, base.keys())) + "\n")
1369 1364
1370 1365 remain = set(self.changelog.nodemap)
1371 1366
1372 1367 # prune everything remote has from the tree
1373 1368 remain.remove(nullid)
1374 1369 remove = base.keys()
1375 1370 while remove:
1376 1371 n = remove.pop(0)
1377 1372 if n in remain:
1378 1373 remain.remove(n)
1379 1374 for p in self.changelog.parents(n):
1380 1375 remove.append(p)
1381 1376
1382 1377 # find every node whose parents have been pruned
1383 1378 subset = []
1384 1379 # find every remote head that will get new children
1385 1380 updated_heads = set()
1386 1381 for n in remain:
1387 1382 p1, p2 = self.changelog.parents(n)
1388 1383 if p1 not in remain and p2 not in remain:
1389 1384 subset.append(n)
1390 1385 if heads:
1391 1386 if p1 in heads:
1392 1387 updated_heads.add(p1)
1393 1388 if p2 in heads:
1394 1389 updated_heads.add(p2)
1395 1390
1396 1391 # this is the set of all roots we have to push
1397 1392 if heads:
1398 1393 return subset, list(updated_heads)
1399 1394 else:
1400 1395 return subset
1401 1396
1402 1397 def pull(self, remote, heads=None, force=False):
1403 1398 lock = self.lock()
1404 1399 try:
1405 1400 common, fetch, rheads = self.findcommonincoming(remote, heads=heads,
1406 1401 force=force)
1407 1402 if fetch == [nullid]:
1408 1403 self.ui.status(_("requesting all changes\n"))
1409 1404
1410 1405 if not fetch:
1411 1406 self.ui.status(_("no changes found\n"))
1412 1407 return 0
1413 1408
1414 1409 if heads is None and remote.capable('changegroupsubset'):
1415 1410 heads = rheads
1416 1411
1417 1412 if heads is None:
1418 1413 cg = remote.changegroup(fetch, 'pull')
1419 1414 else:
1420 1415 if not remote.capable('changegroupsubset'):
1421 1416 raise util.Abort(_("Partial pull cannot be done because other repository doesn't support changegroupsubset."))
1422 1417 cg = remote.changegroupsubset(fetch, heads, 'pull')
1423 1418 return self.addchangegroup(cg, 'pull', remote.url())
1424 1419 finally:
1425 1420 lock.release()
1426 1421
1427 1422 def push(self, remote, force=False, revs=None):
1428 1423 # there are two ways to push to remote repo:
1429 1424 #
1430 1425 # addchangegroup assumes local user can lock remote
1431 1426 # repo (local filesystem, old ssh servers).
1432 1427 #
1433 1428 # unbundle assumes local user cannot lock remote repo (new ssh
1434 1429 # servers, http servers).
1435 1430
1436 1431 if remote.capable('unbundle'):
1437 1432 return self.push_unbundle(remote, force, revs)
1438 1433 return self.push_addchangegroup(remote, force, revs)
1439 1434
1440 1435 def prepush(self, remote, force, revs):
1441 1436 common = {}
1442 1437 remote_heads = remote.heads()
1443 1438 inc = self.findincoming(remote, common, remote_heads, force=force)
1444 1439
1445 1440 update, updated_heads = self.findoutgoing(remote, common, remote_heads)
1446 1441 if revs is not None:
1447 1442 msng_cl, bases, heads = self.changelog.nodesbetween(update, revs)
1448 1443 else:
1449 1444 bases, heads = update, self.changelog.heads()
1450 1445
1451 1446 if not bases:
1452 1447 self.ui.status(_("no changes found\n"))
1453 1448 return None, 1
1454 1449 elif not force:
1455 1450 # check if we're creating new remote heads
1456 1451 # to be a remote head after push, node must be either
1457 1452 # - unknown locally
1458 1453 # - a local outgoing head descended from update
1459 1454 # - a remote head that's known locally and not
1460 1455 # ancestral to an outgoing head
1461 1456
1462 1457 warn = 0
1463 1458
1464 1459 if remote_heads == [nullid]:
1465 1460 warn = 0
1466 1461 elif not revs and len(heads) > len(remote_heads):
1467 1462 warn = 1
1468 1463 else:
1469 1464 newheads = list(heads)
1470 1465 for r in remote_heads:
1471 1466 if r in self.changelog.nodemap:
1472 1467 desc = self.changelog.heads(r, heads)
1473 1468 l = [h for h in heads if h in desc]
1474 1469 if not l:
1475 1470 newheads.append(r)
1476 1471 else:
1477 1472 newheads.append(r)
1478 1473 if len(newheads) > len(remote_heads):
1479 1474 warn = 1
1480 1475
1481 1476 if warn:
1482 1477 self.ui.warn(_("abort: push creates new remote heads!\n"))
1483 1478 self.ui.status(_("(did you forget to merge?"
1484 1479 " use push -f to force)\n"))
1485 1480 return None, 0
1486 1481 elif inc:
1487 1482 self.ui.warn(_("note: unsynced remote changes!\n"))
1488 1483
1489 1484
1490 1485 if revs is None:
1491 1486 # use the fast path, no race possible on push
1492 1487 cg = self._changegroup(common.keys(), 'push')
1493 1488 else:
1494 1489 cg = self.changegroupsubset(update, revs, 'push')
1495 1490 return cg, remote_heads
1496 1491
1497 1492 def push_addchangegroup(self, remote, force, revs):
1498 1493 lock = remote.lock()
1499 1494 try:
1500 1495 ret = self.prepush(remote, force, revs)
1501 1496 if ret[0] is not None:
1502 1497 cg, remote_heads = ret
1503 1498 return remote.addchangegroup(cg, 'push', self.url())
1504 1499 return ret[1]
1505 1500 finally:
1506 1501 lock.release()
1507 1502
1508 1503 def push_unbundle(self, remote, force, revs):
1509 1504 # local repo finds heads on server, finds out what revs it
1510 1505 # must push. once revs transferred, if server finds it has
1511 1506 # different heads (someone else won commit/push race), server
1512 1507 # aborts.
1513 1508
1514 1509 ret = self.prepush(remote, force, revs)
1515 1510 if ret[0] is not None:
1516 1511 cg, remote_heads = ret
1517 1512 if force: remote_heads = ['force']
1518 1513 return remote.unbundle(cg, remote_heads, 'push')
1519 1514 return ret[1]
1520 1515
1521 1516 def changegroupinfo(self, nodes, source):
1522 1517 if self.ui.verbose or source == 'bundle':
1523 1518 self.ui.status(_("%d changesets found\n") % len(nodes))
1524 1519 if self.ui.debugflag:
1525 1520 self.ui.debug(_("list of changesets:\n"))
1526 1521 for node in nodes:
1527 1522 self.ui.debug("%s\n" % hex(node))
1528 1523
1529 1524 def changegroupsubset(self, bases, heads, source, extranodes=None):
1530 1525 """This function generates a changegroup consisting of all the nodes
1531 1526 that are descendents of any of the bases, and ancestors of any of
1532 1527 the heads.
1533 1528
1534 1529 It is fairly complex as determining which filenodes and which
1535 1530 manifest nodes need to be included for the changeset to be complete
1536 1531 is non-trivial.
1537 1532
1538 1533 Another wrinkle is doing the reverse, figuring out which changeset in
1539 1534 the changegroup a particular filenode or manifestnode belongs to.
1540 1535
1541 1536 The caller can specify some nodes that must be included in the
1542 1537 changegroup using the extranodes argument. It should be a dict
1543 1538 where the keys are the filenames (or 1 for the manifest), and the
1544 1539 values are lists of (node, linknode) tuples, where node is a wanted
1545 1540 node and linknode is the changelog node that should be transmitted as
1546 1541 the linkrev.
1547 1542 """
1548 1543
1549 1544 if extranodes is None:
1550 1545 # can we go through the fast path ?
1551 1546 heads.sort()
1552 1547 allheads = self.heads()
1553 1548 allheads.sort()
1554 1549 if heads == allheads:
1555 1550 common = []
1556 1551 # parents of bases are known from both sides
1557 1552 for n in bases:
1558 1553 for p in self.changelog.parents(n):
1559 1554 if p != nullid:
1560 1555 common.append(p)
1561 1556 return self._changegroup(common, source)
1562 1557
1563 1558 self.hook('preoutgoing', throw=True, source=source)
1564 1559
1565 1560 # Set up some initial variables
1566 1561 # Make it easy to refer to self.changelog
1567 1562 cl = self.changelog
1568 1563 # msng is short for missing - compute the list of changesets in this
1569 1564 # changegroup.
1570 1565 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
1571 1566 self.changegroupinfo(msng_cl_lst, source)
1572 1567 # Some bases may turn out to be superfluous, and some heads may be
1573 1568 # too. nodesbetween will return the minimal set of bases and heads
1574 1569 # necessary to re-create the changegroup.
1575 1570
1576 1571 # Known heads are the list of heads that it is assumed the recipient
1577 1572 # of this changegroup will know about.
1578 1573 knownheads = set()
1579 1574 # We assume that all parents of bases are known heads.
1580 1575 for n in bases:
1581 1576 knownheads.update(cl.parents(n))
1582 1577 knownheads.discard(nullid)
1583 1578 knownheads = list(knownheads)
1584 1579 if knownheads:
1585 1580 # Now that we know what heads are known, we can compute which
1586 1581 # changesets are known. The recipient must know about all
1587 1582 # changesets required to reach the known heads from the null
1588 1583 # changeset.
1589 1584 has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
1590 1585 junk = None
1591 1586 # Transform the list into a set.
1592 1587 has_cl_set = set(has_cl_set)
1593 1588 else:
1594 1589 # If there were no known heads, the recipient cannot be assumed to
1595 1590 # know about any changesets.
1596 1591 has_cl_set = set()
1597 1592
1598 1593 # Make it easy to refer to self.manifest
1599 1594 mnfst = self.manifest
1600 1595 # We don't know which manifests are missing yet
1601 1596 msng_mnfst_set = {}
1602 1597 # Nor do we know which filenodes are missing.
1603 1598 msng_filenode_set = {}
1604 1599
1605 1600 junk = mnfst.index[len(mnfst) - 1] # Get around a bug in lazyindex
1606 1601 junk = None
1607 1602
1608 1603 # A changeset always belongs to itself, so the changenode lookup
1609 1604 # function for a changenode is identity.
1610 1605 def identity(x):
1611 1606 return x
1612 1607
1613 1608 # A function generating function. Sets up an environment for the
1614 1609 # inner function.
1615 1610 def cmp_by_rev_func(revlog):
1616 1611 # Compare two nodes by their revision number in the environment's
1617 1612 # revision history. Since the revision number both represents the
1618 1613 # most efficient order to read the nodes in, and represents a
1619 1614 # topological sorting of the nodes, this function is often useful.
1620 1615 def cmp_by_rev(a, b):
1621 1616 return cmp(revlog.rev(a), revlog.rev(b))
1622 1617 return cmp_by_rev
1623 1618
1624 1619 # If we determine that a particular file or manifest node must be a
1625 1620 # node that the recipient of the changegroup will already have, we can
1626 1621 # also assume the recipient will have all the parents. This function
1627 1622 # prunes them from the set of missing nodes.
1628 1623 def prune_parents(revlog, hasset, msngset):
1629 1624 haslst = list(hasset)
1630 1625 haslst.sort(cmp_by_rev_func(revlog))
1631 1626 for node in haslst:
1632 1627 parentlst = [p for p in revlog.parents(node) if p != nullid]
1633 1628 while parentlst:
1634 1629 n = parentlst.pop()
1635 1630 if n not in hasset:
1636 1631 hasset.add(n)
1637 1632 p = [p for p in revlog.parents(n) if p != nullid]
1638 1633 parentlst.extend(p)
1639 1634 for n in hasset:
1640 1635 msngset.pop(n, None)
1641 1636
1642 1637 # This is a function generating function used to set up an environment
1643 1638 # for the inner function to execute in.
1644 1639 def manifest_and_file_collector(changedfileset):
1645 1640 # This is an information gathering function that gathers
1646 1641 # information from each changeset node that goes out as part of
1647 1642 # the changegroup. The information gathered is a list of which
1648 1643 # manifest nodes are potentially required (the recipient may
1649 1644 # already have them) and total list of all files which were
1650 1645 # changed in any changeset in the changegroup.
1651 1646 #
1652 1647 # We also remember the first changenode we saw any manifest
1653 1648 # referenced by so we can later determine which changenode 'owns'
1654 1649 # the manifest.
1655 1650 def collect_manifests_and_files(clnode):
1656 1651 c = cl.read(clnode)
1657 1652 for f in c[3]:
1658 1653 # This is to make sure we only have one instance of each
1659 1654 # filename string for each filename.
1660 1655 changedfileset.setdefault(f, f)
1661 1656 msng_mnfst_set.setdefault(c[0], clnode)
1662 1657 return collect_manifests_and_files
1663 1658
1664 1659 # Figure out which manifest nodes (of the ones we think might be part
1665 1660 # of the changegroup) the recipient must know about and remove them
1666 1661 # from the changegroup.
1667 1662 def prune_manifests():
1668 1663 has_mnfst_set = set()
1669 1664 for n in msng_mnfst_set:
1670 1665 # If a 'missing' manifest thinks it belongs to a changenode
1671 1666 # the recipient is assumed to have, obviously the recipient
1672 1667 # must have that manifest.
1673 1668 linknode = cl.node(mnfst.linkrev(mnfst.rev(n)))
1674 1669 if linknode in has_cl_set:
1675 1670 has_mnfst_set.add(n)
1676 1671 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
1677 1672
1678 1673 # Use the information collected in collect_manifests_and_files to say
1679 1674 # which changenode any manifestnode belongs to.
1680 1675 def lookup_manifest_link(mnfstnode):
1681 1676 return msng_mnfst_set[mnfstnode]
1682 1677
1683 1678 # A function generating function that sets up the initial environment
1684 1679 # the inner function.
1685 1680 def filenode_collector(changedfiles):
1686 1681 next_rev = [0]
1687 1682 # This gathers information from each manifestnode included in the
1688 1683 # changegroup about which filenodes the manifest node references
1689 1684 # so we can include those in the changegroup too.
1690 1685 #
1691 1686 # It also remembers which changenode each filenode belongs to. It
1692 1687 # does this by assuming the a filenode belongs to the changenode
1693 1688 # the first manifest that references it belongs to.
1694 1689 def collect_msng_filenodes(mnfstnode):
1695 1690 r = mnfst.rev(mnfstnode)
1696 1691 if r == next_rev[0]:
1697 1692 # If the last rev we looked at was the one just previous,
1698 1693 # we only need to see a diff.
1699 1694 deltamf = mnfst.readdelta(mnfstnode)
1700 1695 # For each line in the delta
1701 1696 for f, fnode in deltamf.iteritems():
1702 1697 f = changedfiles.get(f, None)
1703 1698 # And if the file is in the list of files we care
1704 1699 # about.
1705 1700 if f is not None:
1706 1701 # Get the changenode this manifest belongs to
1707 1702 clnode = msng_mnfst_set[mnfstnode]
1708 1703 # Create the set of filenodes for the file if
1709 1704 # there isn't one already.
1710 1705 ndset = msng_filenode_set.setdefault(f, {})
1711 1706 # And set the filenode's changelog node to the
1712 1707 # manifest's if it hasn't been set already.
1713 1708 ndset.setdefault(fnode, clnode)
1714 1709 else:
1715 1710 # Otherwise we need a full manifest.
1716 1711 m = mnfst.read(mnfstnode)
1717 1712 # For every file in we care about.
1718 1713 for f in changedfiles:
1719 1714 fnode = m.get(f, None)
1720 1715 # If it's in the manifest
1721 1716 if fnode is not None:
1722 1717 # See comments above.
1723 1718 clnode = msng_mnfst_set[mnfstnode]
1724 1719 ndset = msng_filenode_set.setdefault(f, {})
1725 1720 ndset.setdefault(fnode, clnode)
1726 1721 # Remember the revision we hope to see next.
1727 1722 next_rev[0] = r + 1
1728 1723 return collect_msng_filenodes
1729 1724
1730 1725 # We have a list of filenodes we think we need for a file, lets remove
1731 1726 # all those we know the recipient must have.
1732 1727 def prune_filenodes(f, filerevlog):
1733 1728 msngset = msng_filenode_set[f]
1734 1729 hasset = set()
1735 1730 # If a 'missing' filenode thinks it belongs to a changenode we
1736 1731 # assume the recipient must have, then the recipient must have
1737 1732 # that filenode.
1738 1733 for n in msngset:
1739 1734 clnode = cl.node(filerevlog.linkrev(filerevlog.rev(n)))
1740 1735 if clnode in has_cl_set:
1741 1736 hasset.add(n)
1742 1737 prune_parents(filerevlog, hasset, msngset)
1743 1738
1744 1739 # A function generator function that sets up the a context for the
1745 1740 # inner function.
1746 1741 def lookup_filenode_link_func(fname):
1747 1742 msngset = msng_filenode_set[fname]
1748 1743 # Lookup the changenode the filenode belongs to.
1749 1744 def lookup_filenode_link(fnode):
1750 1745 return msngset[fnode]
1751 1746 return lookup_filenode_link
1752 1747
1753 1748 # Add the nodes that were explicitly requested.
1754 1749 def add_extra_nodes(name, nodes):
1755 1750 if not extranodes or name not in extranodes:
1756 1751 return
1757 1752
1758 1753 for node, linknode in extranodes[name]:
1759 1754 if node not in nodes:
1760 1755 nodes[node] = linknode
1761 1756
1762 1757 # Now that we have all theses utility functions to help out and
1763 1758 # logically divide up the task, generate the group.
1764 1759 def gengroup():
1765 1760 # The set of changed files starts empty.
1766 1761 changedfiles = {}
1767 1762 # Create a changenode group generator that will call our functions
1768 1763 # back to lookup the owning changenode and collect information.
1769 1764 group = cl.group(msng_cl_lst, identity,
1770 1765 manifest_and_file_collector(changedfiles))
1771 1766 for chnk in group:
1772 1767 yield chnk
1773 1768
1774 1769 # The list of manifests has been collected by the generator
1775 1770 # calling our functions back.
1776 1771 prune_manifests()
1777 1772 add_extra_nodes(1, msng_mnfst_set)
1778 1773 msng_mnfst_lst = msng_mnfst_set.keys()
1779 1774 # Sort the manifestnodes by revision number.
1780 1775 msng_mnfst_lst.sort(cmp_by_rev_func(mnfst))
1781 1776 # Create a generator for the manifestnodes that calls our lookup
1782 1777 # and data collection functions back.
1783 1778 group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
1784 1779 filenode_collector(changedfiles))
1785 1780 for chnk in group:
1786 1781 yield chnk
1787 1782
1788 1783 # These are no longer needed, dereference and toss the memory for
1789 1784 # them.
1790 1785 msng_mnfst_lst = None
1791 1786 msng_mnfst_set.clear()
1792 1787
1793 1788 if extranodes:
1794 1789 for fname in extranodes:
1795 1790 if isinstance(fname, int):
1796 1791 continue
1797 1792 msng_filenode_set.setdefault(fname, {})
1798 1793 changedfiles[fname] = 1
1799 1794 # Go through all our files in order sorted by name.
1800 1795 for fname in sorted(changedfiles):
1801 1796 filerevlog = self.file(fname)
1802 1797 if not len(filerevlog):
1803 1798 raise util.Abort(_("empty or missing revlog for %s") % fname)
1804 1799 # Toss out the filenodes that the recipient isn't really
1805 1800 # missing.
1806 1801 if fname in msng_filenode_set:
1807 1802 prune_filenodes(fname, filerevlog)
1808 1803 add_extra_nodes(fname, msng_filenode_set[fname])
1809 1804 msng_filenode_lst = msng_filenode_set[fname].keys()
1810 1805 else:
1811 1806 msng_filenode_lst = []
1812 1807 # If any filenodes are left, generate the group for them,
1813 1808 # otherwise don't bother.
1814 1809 if len(msng_filenode_lst) > 0:
1815 1810 yield changegroup.chunkheader(len(fname))
1816 1811 yield fname
1817 1812 # Sort the filenodes by their revision #
1818 1813 msng_filenode_lst.sort(cmp_by_rev_func(filerevlog))
1819 1814 # Create a group generator and only pass in a changenode
1820 1815 # lookup function as we need to collect no information
1821 1816 # from filenodes.
1822 1817 group = filerevlog.group(msng_filenode_lst,
1823 1818 lookup_filenode_link_func(fname))
1824 1819 for chnk in group:
1825 1820 yield chnk
1826 1821 if fname in msng_filenode_set:
1827 1822 # Don't need this anymore, toss it to free memory.
1828 1823 del msng_filenode_set[fname]
1829 1824 # Signal that no more groups are left.
1830 1825 yield changegroup.closechunk()
1831 1826
1832 1827 if msng_cl_lst:
1833 1828 self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
1834 1829
1835 1830 return util.chunkbuffer(gengroup())
1836 1831
1837 1832 def changegroup(self, basenodes, source):
1838 1833 # to avoid a race we use changegroupsubset() (issue1320)
1839 1834 return self.changegroupsubset(basenodes, self.heads(), source)
1840 1835
1841 1836 def _changegroup(self, common, source):
1842 1837 """Generate a changegroup of all nodes that we have that a recipient
1843 1838 doesn't.
1844 1839
1845 1840 This is much easier than the previous function as we can assume that
1846 1841 the recipient has any changenode we aren't sending them.
1847 1842
1848 1843 common is the set of common nodes between remote and self"""
1849 1844
1850 1845 self.hook('preoutgoing', throw=True, source=source)
1851 1846
1852 1847 cl = self.changelog
1853 1848 nodes = cl.findmissing(common)
1854 1849 revset = set([cl.rev(n) for n in nodes])
1855 1850 self.changegroupinfo(nodes, source)
1856 1851
1857 1852 def identity(x):
1858 1853 return x
1859 1854
1860 1855 def gennodelst(log):
1861 1856 for r in log:
1862 1857 if log.linkrev(r) in revset:
1863 1858 yield log.node(r)
1864 1859
1865 1860 def changed_file_collector(changedfileset):
1866 1861 def collect_changed_files(clnode):
1867 1862 c = cl.read(clnode)
1868 1863 changedfileset.update(c[3])
1869 1864 return collect_changed_files
1870 1865
1871 1866 def lookuprevlink_func(revlog):
1872 1867 def lookuprevlink(n):
1873 1868 return cl.node(revlog.linkrev(revlog.rev(n)))
1874 1869 return lookuprevlink
1875 1870
1876 1871 def gengroup():
1877 1872 # construct a list of all changed files
1878 1873 changedfiles = set()
1879 1874
1880 1875 for chnk in cl.group(nodes, identity,
1881 1876 changed_file_collector(changedfiles)):
1882 1877 yield chnk
1883 1878
1884 1879 mnfst = self.manifest
1885 1880 nodeiter = gennodelst(mnfst)
1886 1881 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1887 1882 yield chnk
1888 1883
1889 1884 for fname in sorted(changedfiles):
1890 1885 filerevlog = self.file(fname)
1891 1886 if not len(filerevlog):
1892 1887 raise util.Abort(_("empty or missing revlog for %s") % fname)
1893 1888 nodeiter = gennodelst(filerevlog)
1894 1889 nodeiter = list(nodeiter)
1895 1890 if nodeiter:
1896 1891 yield changegroup.chunkheader(len(fname))
1897 1892 yield fname
1898 1893 lookup = lookuprevlink_func(filerevlog)
1899 1894 for chnk in filerevlog.group(nodeiter, lookup):
1900 1895 yield chnk
1901 1896
1902 1897 yield changegroup.closechunk()
1903 1898
1904 1899 if nodes:
1905 1900 self.hook('outgoing', node=hex(nodes[0]), source=source)
1906 1901
1907 1902 return util.chunkbuffer(gengroup())
1908 1903
1909 1904 def addchangegroup(self, source, srctype, url, emptyok=False):
1910 1905 """add changegroup to repo.
1911 1906
1912 1907 return values:
1913 1908 - nothing changed or no source: 0
1914 1909 - more heads than before: 1+added heads (2..n)
1915 1910 - less heads than before: -1-removed heads (-2..-n)
1916 1911 - number of heads stays the same: 1
1917 1912 """
1918 1913 def csmap(x):
1919 1914 self.ui.debug(_("add changeset %s\n") % short(x))
1920 1915 return len(cl)
1921 1916
1922 1917 def revmap(x):
1923 1918 return cl.rev(x)
1924 1919
1925 1920 if not source:
1926 1921 return 0
1927 1922
1928 1923 self.hook('prechangegroup', throw=True, source=srctype, url=url)
1929 1924
1930 1925 changesets = files = revisions = 0
1931 1926
1932 1927 # write changelog data to temp files so concurrent readers will not see
1933 1928 # inconsistent view
1934 1929 cl = self.changelog
1935 1930 cl.delayupdate()
1936 1931 oldheads = len(cl.heads())
1937 1932
1938 1933 tr = self.transaction()
1939 1934 try:
1940 1935 trp = weakref.proxy(tr)
1941 1936 # pull off the changeset group
1942 1937 self.ui.status(_("adding changesets\n"))
1943 1938 clstart = len(cl)
1944 1939 chunkiter = changegroup.chunkiter(source)
1945 1940 if cl.addgroup(chunkiter, csmap, trp) is None and not emptyok:
1946 1941 raise util.Abort(_("received changelog group is empty"))
1947 1942 clend = len(cl)
1948 1943 changesets = clend - clstart
1949 1944
1950 1945 # pull off the manifest group
1951 1946 self.ui.status(_("adding manifests\n"))
1952 1947 chunkiter = changegroup.chunkiter(source)
1953 1948 # no need to check for empty manifest group here:
1954 1949 # if the result of the merge of 1 and 2 is the same in 3 and 4,
1955 1950 # no new manifest will be created and the manifest group will
1956 1951 # be empty during the pull
1957 1952 self.manifest.addgroup(chunkiter, revmap, trp)
1958 1953
1959 1954 # process the files
1960 1955 self.ui.status(_("adding file changes\n"))
1961 1956 while 1:
1962 1957 f = changegroup.getchunk(source)
1963 1958 if not f:
1964 1959 break
1965 1960 self.ui.debug(_("adding %s revisions\n") % f)
1966 1961 fl = self.file(f)
1967 1962 o = len(fl)
1968 1963 chunkiter = changegroup.chunkiter(source)
1969 1964 if fl.addgroup(chunkiter, revmap, trp) is None:
1970 1965 raise util.Abort(_("received file revlog group is empty"))
1971 1966 revisions += len(fl) - o
1972 1967 files += 1
1973 1968
1974 1969 newheads = len(cl.heads())
1975 1970 heads = ""
1976 1971 if oldheads and newheads != oldheads:
1977 1972 heads = _(" (%+d heads)") % (newheads - oldheads)
1978 1973
1979 1974 self.ui.status(_("added %d changesets"
1980 1975 " with %d changes to %d files%s\n")
1981 1976 % (changesets, revisions, files, heads))
1982 1977
1983 1978 if changesets > 0:
1984 1979 p = lambda: cl.writepending() and self.root or ""
1985 1980 self.hook('pretxnchangegroup', throw=True,
1986 1981 node=hex(cl.node(clstart)), source=srctype,
1987 1982 url=url, pending=p)
1988 1983
1989 1984 # make changelog see real files again
1990 1985 cl.finalize(trp)
1991 1986
1992 1987 tr.close()
1993 1988 finally:
1994 1989 del tr
1995 1990
1996 1991 if changesets > 0:
1997 1992 # forcefully update the on-disk branch cache
1998 1993 self.ui.debug(_("updating the branch cache\n"))
1999 1994 self.branchtags()
2000 1995 self.hook("changegroup", node=hex(cl.node(clstart)),
2001 1996 source=srctype, url=url)
2002 1997
2003 1998 for i in xrange(clstart, clend):
2004 1999 self.hook("incoming", node=hex(cl.node(i)),
2005 2000 source=srctype, url=url)
2006 2001
2007 2002 # never return 0 here:
2008 2003 if newheads < oldheads:
2009 2004 return newheads - oldheads - 1
2010 2005 else:
2011 2006 return newheads - oldheads + 1
2012 2007
2013 2008
2014 2009 def stream_in(self, remote):
2015 2010 fp = remote.stream_out()
2016 2011 l = fp.readline()
2017 2012 try:
2018 2013 resp = int(l)
2019 2014 except ValueError:
2020 2015 raise error.ResponseError(
2021 2016 _('Unexpected response from remote server:'), l)
2022 2017 if resp == 1:
2023 2018 raise util.Abort(_('operation forbidden by server'))
2024 2019 elif resp == 2:
2025 2020 raise util.Abort(_('locking the remote repository failed'))
2026 2021 elif resp != 0:
2027 2022 raise util.Abort(_('the server sent an unknown error code'))
2028 2023 self.ui.status(_('streaming all changes\n'))
2029 2024 l = fp.readline()
2030 2025 try:
2031 2026 total_files, total_bytes = map(int, l.split(' ', 1))
2032 2027 except (ValueError, TypeError):
2033 2028 raise error.ResponseError(
2034 2029 _('Unexpected response from remote server:'), l)
2035 2030 self.ui.status(_('%d files to transfer, %s of data\n') %
2036 2031 (total_files, util.bytecount(total_bytes)))
2037 2032 start = time.time()
2038 2033 for i in xrange(total_files):
2039 2034 # XXX doesn't support '\n' or '\r' in filenames
2040 2035 l = fp.readline()
2041 2036 try:
2042 2037 name, size = l.split('\0', 1)
2043 2038 size = int(size)
2044 2039 except (ValueError, TypeError):
2045 2040 raise error.ResponseError(
2046 2041 _('Unexpected response from remote server:'), l)
2047 2042 self.ui.debug(_('adding %s (%s)\n') % (name, util.bytecount(size)))
2048 2043 ofp = self.sopener(name, 'w')
2049 2044 for chunk in util.filechunkiter(fp, limit=size):
2050 2045 ofp.write(chunk)
2051 2046 ofp.close()
2052 2047 elapsed = time.time() - start
2053 2048 if elapsed <= 0:
2054 2049 elapsed = 0.001
2055 2050 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
2056 2051 (util.bytecount(total_bytes), elapsed,
2057 2052 util.bytecount(total_bytes / elapsed)))
2058 2053 self.invalidate()
2059 2054 return len(self.heads()) + 1
2060 2055
2061 2056 def clone(self, remote, heads=[], stream=False):
2062 2057 '''clone remote repository.
2063 2058
2064 2059 keyword arguments:
2065 2060 heads: list of revs to clone (forces use of pull)
2066 2061 stream: use streaming clone if possible'''
2067 2062
2068 2063 # now, all clients that can request uncompressed clones can
2069 2064 # read repo formats supported by all servers that can serve
2070 2065 # them.
2071 2066
2072 2067 # if revlog format changes, client will have to check version
2073 2068 # and format flags on "stream" capability, and use
2074 2069 # uncompressed only if compatible.
2075 2070
2076 2071 if stream and not heads and remote.capable('stream'):
2077 2072 return self.stream_in(remote)
2078 2073 return self.pull(remote, heads)
2079 2074
2080 2075 # used to avoid circular references so destructors work
2081 2076 def aftertrans(files):
2082 2077 renamefiles = [tuple(t) for t in files]
2083 2078 def a():
2084 2079 for src, dest in renamefiles:
2085 2080 util.rename(src, dest)
2086 2081 return a
2087 2082
2088 2083 def instance(ui, path, create):
2089 2084 return localrepository(ui, util.drop_scheme('file', path), create)
2090 2085
2091 2086 def islocal(path):
2092 2087 return True
@@ -1,121 +1,117 b''
1 1 % commit date test
2 transaction abort!
3 rollback completed
4 2 abort: empty commit message
5 3 abort: impossible time zone offset: 4444444
6 4 abort: invalid date: '1\t15.1'
7 5 abort: invalid date: 'foo bar'
8 6 abort: date exceeds 32 bits: 111111111111
9 7 % commit added file that has been deleted
10 8 nothing changed
11 9 abort: file bar not found!
12 10 adding dir/file
13 11 dir/file
14 12 committed changeset 2:d2a76177cb42
15 13 adding dir.file
16 14 abort: no match under directory dir!
17 15 abort: no match under directory .!
18 16 abort: no match under directory ../dir2!
19 17 dir/file
20 18 committed changeset 3:1cd62a2d8db5
21 19 does-not-exist: No such file or directory
22 20 abort: file does-not-exist not found!
23 21 abort: file baz not tracked!
24 22 abort: file quux not tracked!
25 23 dir/file
26 24 committed changeset 4:49176991390e
27 25 % partial subdir commit test
28 26 adding bar/bar
29 27 adding foo/foo
30 28 % subdir log 1
31 29 changeset: 0:6ef3cb06bb80
32 30 user: test
33 31 date: Mon Jan 12 13:46:40 1970 +0000
34 32 files: foo/foo
35 33 description:
36 34 commit-subdir-1
37 35
38 36
39 37 % subdir log 2
40 38 changeset: 1:f2e51572cf5a
41 39 tag: tip
42 40 user: test
43 41 date: Mon Jan 12 13:46:41 1970 +0000
44 42 files: bar/bar
45 43 description:
46 44 commit-subdir-2
47 45
48 46
49 47 % full log
50 48 changeset: 1:f2e51572cf5a
51 49 tag: tip
52 50 user: test
53 51 date: Mon Jan 12 13:46:41 1970 +0000
54 52 files: bar/bar
55 53 description:
56 54 commit-subdir-2
57 55
58 56
59 57 changeset: 0:6ef3cb06bb80
60 58 user: test
61 59 date: Mon Jan 12 13:46:40 1970 +0000
62 60 files: foo/foo
63 61 description:
64 62 commit-subdir-1
65 63
66 64
67 65 % dot and subdir commit test
68 66 % full log
69 67 changeset: 1:d9180e04fa8a
70 68 tag: tip
71 69 user: test
72 70 date: Sat Jan 24 03:33:20 1970 +0000
73 71 files: foo/plain-file
74 72 description:
75 73 commit-foo-dot
76 74
77 75
78 76 changeset: 0:80b572aaf098
79 77 user: test
80 78 date: Mon Jan 12 13:46:40 1970 +0000
81 79 files: foo/plain-file
82 80 description:
83 81 commit-foo-subdir
84 82
85 83
86 84 % subdir log
87 85 changeset: 1:d9180e04fa8a
88 86 tag: tip
89 87 user: test
90 88 date: Sat Jan 24 03:33:20 1970 +0000
91 89 summary: commit-foo-dot
92 90
93 91 changeset: 0:80b572aaf098
94 92 user: test
95 93 date: Mon Jan 12 13:46:40 1970 +0000
96 94 summary: commit-foo-subdir
97 95
98 96 adding a
99 97 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
100 98 created new head
101 99 merging a
102 100 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
103 101 (branch merge, don't forget to commit)
104 102 % should fail because we are specifying a file name
105 103 abort: cannot partially commit a merge (do not specify files or patterns)
106 104 % should fail because we are specifying a pattern
107 105 abort: cannot partially commit a merge (do not specify files or patterns)
108 106 % should succeed
109 107 % test commit message content
110 108
111 109
112 110 HG: Enter commit message. Lines beginning with 'HG:' are removed.
113 111 HG: --
114 112 HG: user: test
115 113 HG: branch 'default'
116 114 HG: added added
117 115 HG: changed changed
118 116 HG: removed removed
119 transaction abort!
120 rollback completed
121 117 abort: empty commit message
@@ -1,298 +1,294 b''
1 1 adding a
2 2 adding d1/d2/a
3 3 % import exported patch
4 4 requesting all changes
5 5 adding changesets
6 6 adding manifests
7 7 adding file changes
8 8 added 1 changesets with 2 changes to 2 files
9 9 updating working directory
10 10 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
11 11 applying ../tip.patch
12 12 % message should be same
13 13 summary: second change
14 14 % committer should be same
15 15 user: someone
16 16 % import exported patch with external patcher
17 17 requesting all changes
18 18 adding changesets
19 19 adding manifests
20 20 adding file changes
21 21 added 1 changesets with 2 changes to 2 files
22 22 updating working directory
23 23 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
24 24 applying ../tip.patch
25 25 line2
26 26 % import of plain diff should fail without message
27 27 requesting all changes
28 28 adding changesets
29 29 adding manifests
30 30 adding file changes
31 31 added 1 changesets with 2 changes to 2 files
32 32 updating working directory
33 33 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
34 34 applying ../tip.patch
35 transaction abort!
36 rollback completed
37 35 abort: empty commit message
38 36 % import of plain diff should be ok with message
39 37 requesting all changes
40 38 adding changesets
41 39 adding manifests
42 40 adding file changes
43 41 added 1 changesets with 2 changes to 2 files
44 42 updating working directory
45 43 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
46 44 applying ../tip.patch
47 45 % import of plain diff with specific date and user
48 46 requesting all changes
49 47 adding changesets
50 48 adding manifests
51 49 adding file changes
52 50 added 1 changesets with 2 changes to 2 files
53 51 updating working directory
54 52 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
55 53 applying ../tip.patch
56 54 changeset: 1:ca68f19f3a40
57 55 tag: tip
58 56 user: user@nowhere.net
59 57 date: Thu Jan 01 00:00:01 1970 +0000
60 58 files: a
61 59 description:
62 60 patch
63 61
64 62
65 63 diff -r 80971e65b431 -r ca68f19f3a40 a
66 64 --- a/a Thu Jan 01 00:00:00 1970 +0000
67 65 +++ b/a Thu Jan 01 00:00:01 1970 +0000
68 66 @@ -1,1 +1,2 @@
69 67 line 1
70 68 +line 2
71 69
72 70 % import of plain diff should be ok with --no-commit
73 71 requesting all changes
74 72 adding changesets
75 73 adding manifests
76 74 adding file changes
77 75 added 1 changesets with 2 changes to 2 files
78 76 updating working directory
79 77 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
80 78 applying ../tip.patch
81 79 diff -r 80971e65b431 a
82 80 --- a/a
83 81 +++ b/a
84 82 @@ -1,1 +1,2 @@
85 83 line 1
86 84 +line 2
87 85 % hg -R repo import
88 86 requesting all changes
89 87 adding changesets
90 88 adding manifests
91 89 adding file changes
92 90 added 1 changesets with 2 changes to 2 files
93 91 updating working directory
94 92 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
95 93 applying tip.patch
96 94 % import from stdin
97 95 requesting all changes
98 96 adding changesets
99 97 adding manifests
100 98 adding file changes
101 99 added 1 changesets with 2 changes to 2 files
102 100 updating working directory
103 101 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
104 102 applying patch from stdin
105 103 % override commit message
106 104 requesting all changes
107 105 adding changesets
108 106 adding manifests
109 107 adding file changes
110 108 added 1 changesets with 2 changes to 2 files
111 109 updating working directory
112 110 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
113 111 applying patch from stdin
114 112 summary: override
115 113 % plain diff in email, subject, message body
116 114 requesting all changes
117 115 adding changesets
118 116 adding manifests
119 117 adding file changes
120 118 added 1 changesets with 2 changes to 2 files
121 119 updating working directory
122 120 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
123 121 applying ../msg.patch
124 122 user: email patcher
125 123 summary: email patch
126 124 % plain diff in email, no subject, message body
127 125 requesting all changes
128 126 adding changesets
129 127 adding manifests
130 128 adding file changes
131 129 added 1 changesets with 2 changes to 2 files
132 130 updating working directory
133 131 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
134 132 applying patch from stdin
135 133 % plain diff in email, subject, no message body
136 134 requesting all changes
137 135 adding changesets
138 136 adding manifests
139 137 adding file changes
140 138 added 1 changesets with 2 changes to 2 files
141 139 updating working directory
142 140 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
143 141 applying patch from stdin
144 142 % plain diff in email, no subject, no message body, should fail
145 143 requesting all changes
146 144 adding changesets
147 145 adding manifests
148 146 adding file changes
149 147 added 1 changesets with 2 changes to 2 files
150 148 updating working directory
151 149 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
152 150 applying patch from stdin
153 transaction abort!
154 rollback completed
155 151 abort: empty commit message
156 152 % hg export in email, should use patch header
157 153 requesting all changes
158 154 adding changesets
159 155 adding manifests
160 156 adding file changes
161 157 added 1 changesets with 2 changes to 2 files
162 158 updating working directory
163 159 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
164 160 applying patch from stdin
165 161 summary: second change
166 162 % plain diff in email, [PATCH] subject, message body with subject
167 163 requesting all changes
168 164 adding changesets
169 165 adding manifests
170 166 adding file changes
171 167 added 1 changesets with 2 changes to 2 files
172 168 updating working directory
173 169 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
174 170 applying patch from stdin
175 171 email patch
176 172
177 173 next line
178 174 ---
179 175 % import patch1 patch2; rollback
180 176 parent: 0
181 177 applying ../patch1
182 178 applying ../patch2
183 179 rolling back last transaction
184 180 parent: 1
185 181 % hg import in a subdirectory
186 182 requesting all changes
187 183 adding changesets
188 184 adding manifests
189 185 adding file changes
190 186 added 1 changesets with 2 changes to 2 files
191 187 updating working directory
192 188 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
193 189 applying ../../../tip.patch
194 190 % message should be 'subdir change'
195 191 summary: subdir change
196 192 % committer should be 'someoneelse'
197 193 user: someoneelse
198 194 % should be empty
199 195 % test fuzziness
200 196 adding a
201 197 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
202 198 created new head
203 199 applying tip.patch
204 200 patching file a
205 201 Hunk #1 succeeded at 1 with fuzz 2 (offset -2 lines).
206 202 a
207 203 adding a
208 204 adding b1
209 205 adding c1
210 206 adding d
211 207 diff --git a/a b/a
212 208 --- a/a
213 209 +++ b/a
214 210 @@ -0,0 +1,1 @@
215 211 +a
216 212 diff --git a/b1 b/b2
217 213 rename from b1
218 214 rename to b2
219 215 --- a/b1
220 216 +++ b/b2
221 217 @@ -0,0 +1,1 @@
222 218 +b
223 219 diff --git a/c1 b/c1
224 220 --- a/c1
225 221 +++ b/c1
226 222 @@ -0,0 +1,1 @@
227 223 +c
228 224 diff --git a/c1 b/c2
229 225 copy from c1
230 226 copy to c2
231 227 --- a/c1
232 228 +++ b/c2
233 229 @@ -0,0 +1,1 @@
234 230 +c
235 231 diff --git a/d b/d
236 232 --- a/d
237 233 +++ b/d
238 234 @@ -1,1 +0,0 @@
239 235 -d
240 236 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
241 237 applying empty.diff
242 238 % a file
243 239 a
244 240 % b1 file
245 241 % b2 file
246 242 b
247 243 % c1 file
248 244 c
249 245 % c2 file
250 246 c
251 247 % d file
252 248 % test trailing binary removal
253 249 adding a
254 250 adding b
255 251 R a
256 252 R b
257 253 diff --git a/a b/a
258 254 diff --git a/b b/b
259 255 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
260 256 applying remove.diff
261 257 % test update+rename with common name (issue 927)
262 258 adding a
263 259 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
264 260 applying copy.diff
265 261 % view a
266 262 a
267 263 % view a2
268 264 a
269 265 % test -p0
270 266 adding a
271 267 applying patch from stdin
272 268 bb
273 269 % test paths outside repo root
274 270 applying patch from stdin
275 271 abort: ../outside/foo not under root
276 272 % test import with similarity (issue295)
277 273 adding a
278 274 applying ../rename.diff
279 275 patching file a
280 276 patching file b
281 277 removing a
282 278 adding b
283 279 recording removal of a as rename to b (88% similar)
284 280 A b
285 281 a
286 282 R a
287 283 undeleting a
288 284 forgetting b
289 285 applying ../rename.diff
290 286 patching file a
291 287 patching file b
292 288 removing a
293 289 adding b
294 290 A b
295 291 R a
296 292 % add empty file from the end of patch (issue 1495)
297 293 adding a
298 294 applying a.patch
@@ -1,504 +1,498 b''
1 1 % help
2 2 keyword extension - keyword expansion in local repositories
3 3
4 4 This extension expands RCS/CVS-like or self-customized $Keywords$ in
5 5 tracked text files selected by your configuration.
6 6
7 7 Keywords are only expanded in local repositories and not stored in the
8 8 change history. The mechanism can be regarded as a convenience for the
9 9 current user or for archive distribution.
10 10
11 11 Configuration is done in the [keyword] and [keywordmaps] sections of
12 12 hgrc files.
13 13
14 14 Example:
15 15
16 16 [keyword]
17 17 # expand keywords in every python file except those matching "x*"
18 18 **.py =
19 19 x* = ignore
20 20
21 21 Note: the more specific you are in your filename patterns
22 22 the less you lose speed in huge repositories.
23 23
24 24 For [keywordmaps] template mapping and expansion demonstration and
25 25 control run "hg kwdemo".
26 26
27 27 An additional date template filter {date|utcdate} is provided.
28 28
29 29 The default template mappings (view with "hg kwdemo -d") can be
30 30 replaced with customized keywords and templates. Again, run "hg
31 31 kwdemo" to control the results of your config changes.
32 32
33 33 Before changing/disabling active keywords, run "hg kwshrink" to avoid
34 34 the risk of inadvertedly storing expanded keywords in the change
35 35 history.
36 36
37 37 To force expansion after enabling it, or a configuration change, run
38 38 "hg kwexpand".
39 39
40 40 Also, when committing with the record extension or using mq's qrecord,
41 41 be aware that keywords cannot be updated. Again, run "hg kwexpand" on
42 42 the files in question to update keyword expansions after all changes
43 43 have been checked in.
44 44
45 45 Expansions spanning more than one line and incremental expansions,
46 46 like CVS' $Log$, are not supported. A keyword template map
47 47 "Log = {desc}" expands to the first line of the changeset description.
48 48
49 49 list of commands:
50 50
51 51 kwdemo print [keywordmaps] configuration and an expansion example
52 52 kwexpand expand keywords in working directory
53 53 kwfiles print files currently configured for keyword expansion
54 54 kwshrink revert expanded keywords in working directory
55 55
56 56 enabled extensions:
57 57
58 58 keyword keyword expansion in local repositories
59 59 mq patch management and development
60 60 notify hook extension to email notifications on commits/pushes
61 61
62 62 use "hg -v help keyword" to show aliases and global options
63 63 % hg kwdemo
64 64 [extensions]
65 65 hgext.keyword =
66 66 [keyword]
67 67 * =
68 68 b = ignore
69 69 demo.txt =
70 70 [keywordmaps]
71 71 RCSFile = {file|basename},v
72 72 Author = {author|user}
73 73 Header = {root}/{file},v {node|short} {date|utcdate} {author|user}
74 74 Source = {root}/{file},v
75 75 Date = {date|utcdate}
76 76 Id = {file|basename},v {node|short} {date|utcdate} {author|user}
77 77 Revision = {node|short}
78 78 $RCSFile: demo.txt,v $
79 79 $Author: test $
80 80 $Header: /TMP/demo.txt,v xxxxxxxxxxxx 2000/00/00 00:00:00 test $
81 81 $Source: /TMP/demo.txt,v $
82 82 $Date: 2000/00/00 00:00:00 $
83 83 $Id: demo.txt,v xxxxxxxxxxxx 2000/00/00 00:00:00 test $
84 84 $Revision: xxxxxxxxxxxx $
85 85 [extensions]
86 86 hgext.keyword =
87 87 [keyword]
88 88 * =
89 89 b = ignore
90 90 demo.txt =
91 91 [keywordmaps]
92 92 Branch = {branches}
93 93 $Branch: demobranch $
94 94 % kwshrink should exit silently in empty/invalid repo
95 95 pulling from test-keyword.hg
96 96 requesting all changes
97 97 adding changesets
98 98 adding manifests
99 99 adding file changes
100 100 added 1 changesets with 1 changes to 1 files
101 101 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
102 102 % cat
103 103 expand $Id$
104 104 do not process $Id:
105 105 xxx $
106 106 ignore $Id$
107 107 % addremove
108 108 adding a
109 109 adding b
110 110 % status
111 111 A a
112 112 A b
113 113 % default keyword expansion including commit hook
114 114 % interrupted commit should not change state or run commit hook
115 a
116 b
117 transaction abort!
118 rollback completed
119 115 abort: empty commit message
120 116 % status
121 117 A a
122 118 A b
123 119 % commit
124 120 a
125 121 b
126 122 overwriting a expanding keywords
127 123 running hook commit.test: cp a hooktest
128 124 committed changeset 1:ef63ca68695bc9495032c6fda1350c71e6d256e9
129 125 % status
130 126 ? hooktest
131 127 % identify
132 128 ef63ca68695b
133 129 % cat
134 130 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
135 131 do not process $Id:
136 132 xxx $
137 133 ignore $Id$
138 134 % hg cat
139 135 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
140 136 do not process $Id:
141 137 xxx $
142 138 ignore $Id$
143 139 a
144 140 % diff a hooktest
145 141 % removing commit hook from config
146 142 % bundle
147 143 2 changesets found
148 144 % notify on pull to check whether keywords stay as is in email
149 145 % ie. if patch.diff wrapper acts as it should
150 146 % pull from bundle
151 147 pulling from ../kw.hg
152 148 requesting all changes
153 149 adding changesets
154 150 adding manifests
155 151 adding file changes
156 152 added 2 changesets with 3 changes to 3 files
157 153
158 154 diff -r 000000000000 -r a2392c293916 sym
159 155 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
160 156 +++ b/sym Sat Feb 09 20:25:47 2008 +0100
161 157 @@ -0,0 +1,1 @@
162 158 +a
163 159 \ No newline at end of file
164 160
165 161 diff -r a2392c293916 -r ef63ca68695b a
166 162 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
167 163 +++ b/a Thu Jan 01 00:00:00 1970 +0000
168 164 @@ -0,0 +1,3 @@
169 165 +expand $Id$
170 166 +do not process $Id:
171 167 +xxx $
172 168 diff -r a2392c293916 -r ef63ca68695b b
173 169 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
174 170 +++ b/b Thu Jan 01 00:00:00 1970 +0000
175 171 @@ -0,0 +1,1 @@
176 172 +ignore $Id$
177 173 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
178 174 % remove notify config
179 175 % touch
180 176 % status
181 177 % update
182 178 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
183 179 % cat
184 180 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
185 181 do not process $Id:
186 182 xxx $
187 183 ignore $Id$
188 184 % check whether expansion is filewise
189 185 % commit c
190 186 adding c
191 187 % force expansion
192 188 overwriting a expanding keywords
193 189 overwriting c expanding keywords
194 190 % compare changenodes in a c
195 191 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
196 192 do not process $Id:
197 193 xxx $
198 194 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
199 195 tests for different changenodes
200 196 % qinit -c
201 197 % qimport
202 198 % qcommit
203 199 % keywords should not be expanded in patch
204 200 # HG changeset patch
205 201 # User User Name <user@example.com>
206 202 # Date 1 0
207 203 # Node ID 40a904bbbe4cd4ab0a1f28411e35db26341a40ad
208 204 # Parent ef63ca68695bc9495032c6fda1350c71e6d256e9
209 205 cndiff
210 206
211 207 diff -r ef63ca68695b -r 40a904bbbe4c c
212 208 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
213 209 +++ b/c Thu Jan 01 00:00:01 1970 +0000
214 210 @@ -0,0 +1,2 @@
215 211 +$Id$
216 212 +tests for different changenodes
217 213 % qpop
218 214 patch queue now empty
219 215 % qgoto - should imply qpush
220 216 applying mqtest.diff
221 217 now at: mqtest.diff
222 218 % cat
223 219 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
224 220 tests for different changenodes
225 221 % qpop and move on
226 222 patch queue now empty
227 223 % copy
228 224 % kwfiles added
229 225 a
230 226 c
231 227 % commit
232 228 c
233 229 c: copy a:0045e12f6c5791aac80ca6cbfd97709a88307292
234 230 overwriting c expanding keywords
235 231 committed changeset 2:e22d299ac0c2bd8897b3df5114374b9e4d4ca62f
236 232 % cat a c
237 233 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
238 234 do not process $Id:
239 235 xxx $
240 236 expand $Id: c,v e22d299ac0c2 1970/01/01 00:00:01 user $
241 237 do not process $Id:
242 238 xxx $
243 239 % touch copied c
244 240 % status
245 241 % kwfiles
246 242 a
247 243 c
248 244 % diff --rev
249 245 diff -r ef63ca68695b c
250 246 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
251 247 @@ -0,0 +1,3 @@
252 248 +expand $Id$
253 249 +do not process $Id:
254 250 +xxx $
255 251 % rollback
256 252 rolling back last transaction
257 253 % status
258 254 A c
259 255 % update -C
260 256 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
261 257 % custom keyword expansion
262 258 % try with kwdemo
263 259 [extensions]
264 260 hgext.keyword =
265 261 [keyword]
266 262 * =
267 263 b = ignore
268 264 demo.txt =
269 265 [keywordmaps]
270 266 Xinfo = {author}: {desc}
271 267 $Xinfo: test: hg keyword config and expansion example $
272 268 % cat
273 269 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
274 270 do not process $Id:
275 271 xxx $
276 272 ignore $Id$
277 273 % hg cat
278 274 expand $Id: a ef63ca68695b Thu, 01 Jan 1970 00:00:00 +0000 user $
279 275 do not process $Id:
280 276 xxx $
281 277 ignore $Id$
282 278 a
283 279 % interrupted commit should not change state
284 transaction abort!
285 rollback completed
286 280 abort: empty commit message
287 281 % status
288 282 M a
289 283 ? log
290 284 % commit
291 285 a
292 286 overwriting a expanding keywords
293 287 committed changeset 2:bb948857c743469b22bbf51f7ec8112279ca5d83
294 288 % status
295 289 % verify
296 290 checking changesets
297 291 checking manifests
298 292 crosschecking files in changesets and manifests
299 293 checking files
300 294 3 files, 3 changesets, 4 total revisions
301 295 % cat
302 296 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
303 297 do not process $Id:
304 298 xxx $
305 299 $Xinfo: User Name <user@example.com>: firstline $
306 300 ignore $Id$
307 301 % hg cat
308 302 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
309 303 do not process $Id:
310 304 xxx $
311 305 $Xinfo: User Name <user@example.com>: firstline $
312 306 ignore $Id$
313 307 a
314 308 % annotate
315 309 1: expand $Id$
316 310 1: do not process $Id:
317 311 1: xxx $
318 312 2: $Xinfo$
319 313 % remove
320 314 committed changeset 3:d14c712653769de926994cf7fbb06c8fbd68f012
321 315 % status
322 316 % rollback
323 317 rolling back last transaction
324 318 % status
325 319 R a
326 320 % revert a
327 321 % cat a
328 322 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
329 323 do not process $Id:
330 324 xxx $
331 325 $Xinfo: User Name <user@example.com>: firstline $
332 326 % clone to test incoming
333 327 requesting all changes
334 328 adding changesets
335 329 adding manifests
336 330 adding file changes
337 331 added 2 changesets with 3 changes to 3 files
338 332 updating working directory
339 333 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
340 334 % incoming
341 335 comparing with test-keyword/Test
342 336 searching for changes
343 337 changeset: 2:bb948857c743
344 338 tag: tip
345 339 user: User Name <user@example.com>
346 340 date: Thu Jan 01 00:00:02 1970 +0000
347 341 summary: firstline
348 342
349 343 % commit rejecttest
350 344 a
351 345 overwriting a expanding keywords
352 346 committed changeset 2:85e279d709ffc28c9fdd1b868570985fc3d87082
353 347 % export
354 348 % import
355 349 applying ../rejecttest.diff
356 350 % cat
357 351 expand $Id: a 4e0994474d25 Thu, 01 Jan 1970 00:00:03 +0000 user $ rejecttest
358 352 do not process $Id: rejecttest
359 353 xxx $
360 354 $Xinfo: User Name <user@example.com>: rejects? $
361 355 ignore $Id$
362 356
363 357 % rollback
364 358 rolling back last transaction
365 359 % clean update
366 360 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
367 361 % kwexpand/kwshrink on selected files
368 362 % copy a x/a
369 363 % kwexpand a
370 364 overwriting a expanding keywords
371 365 % kwexpand x/a should abort
372 366 abort: outstanding uncommitted changes
373 367 x/a
374 368 x/a: copy a:779c764182ce5d43e2b1eb66ce06d7b47bfe342e
375 369 overwriting x/a expanding keywords
376 370 committed changeset 3:cfa68229c1167443337266ebac453c73b1d5d16e
377 371 % cat a
378 372 expand $Id: x/a cfa68229c116 Thu, 01 Jan 1970 00:00:03 +0000 user $
379 373 do not process $Id:
380 374 xxx $
381 375 $Xinfo: User Name <user@example.com>: xa $
382 376 % kwshrink a inside directory x
383 377 overwriting x/a shrinking keywords
384 378 % cat a
385 379 expand $Id$
386 380 do not process $Id:
387 381 xxx $
388 382 $Xinfo$
389 383 % kwexpand nonexistent
390 384 nonexistent:
391 385 % hg serve
392 386 % expansion
393 387 % hgweb file
394 388 200 Script output follows
395 389
396 390 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
397 391 do not process $Id:
398 392 xxx $
399 393 $Xinfo: User Name <user@example.com>: firstline $
400 394 % no expansion
401 395 % hgweb annotate
402 396 200 Script output follows
403 397
404 398
405 399 user@1: expand $Id$
406 400 user@1: do not process $Id:
407 401 user@1: xxx $
408 402 user@2: $Xinfo$
409 403
410 404
411 405
412 406
413 407 % hgweb changeset
414 408 200 Script output follows
415 409
416 410
417 411 # HG changeset patch
418 412 # User User Name <user@example.com>
419 413 # Date 3 0
420 414 # Node ID cfa68229c1167443337266ebac453c73b1d5d16e
421 415 # Parent bb948857c743469b22bbf51f7ec8112279ca5d83
422 416 xa
423 417
424 418 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
425 419 +++ b/x/a Thu Jan 01 00:00:03 1970 +0000
426 420 @@ -0,0 +1,4 @@
427 421 +expand $Id$
428 422 +do not process $Id:
429 423 +xxx $
430 424 +$Xinfo$
431 425
432 426 % hgweb filediff
433 427 200 Script output follows
434 428
435 429
436 430 --- a/a Thu Jan 01 00:00:00 1970 +0000
437 431 +++ b/a Thu Jan 01 00:00:02 1970 +0000
438 432 @@ -1,3 +1,4 @@
439 433 expand $Id$
440 434 do not process $Id:
441 435 xxx $
442 436 +$Xinfo$
443 437
444 438
445 439
446 440
447 441 % errors encountered
448 442 % merge/resolve
449 443 % simplemerge
450 444 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
451 445 created new head
452 446 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
453 447 (branch merge, don't forget to commit)
454 448 $Id: m 8731e1dadc99 Thu, 01 Jan 1970 00:00:00 +0000 test $
455 449 foo
456 450 % conflict
457 451 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
458 452 created new head
459 453 merging m
460 454 warning: conflicts during merge.
461 455 merging m failed!
462 456 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
463 457 use 'hg resolve' to retry unresolved file merges or 'hg up --clean' to abandon
464 458 % keyword stays outside conflict zone
465 459 $Id$
466 460 <<<<<<< local
467 461 bar
468 462 =======
469 463 foo
470 464 >>>>>>> other
471 465 % resolve to local
472 466 $Id: m 43dfd2854b5b Thu, 01 Jan 1970 00:00:00 +0000 test $
473 467 bar
474 468 % switch off expansion
475 469 % kwshrink with unknown file u
476 470 overwriting a shrinking keywords
477 471 overwriting m shrinking keywords
478 472 overwriting x/a shrinking keywords
479 473 % cat
480 474 expand $Id$
481 475 do not process $Id:
482 476 xxx $
483 477 $Xinfo$
484 478 ignore $Id$
485 479 % hg cat
486 480 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
487 481 do not process $Id:
488 482 xxx $
489 483 $Xinfo: User Name <user@example.com>: firstline $
490 484 ignore $Id$
491 485 a
492 486 % cat
493 487 expand $Id$
494 488 do not process $Id:
495 489 xxx $
496 490 $Xinfo$
497 491 ignore $Id$
498 492 % hg cat
499 493 expand $Id$
500 494 do not process $Id:
501 495 xxx $
502 496 $Xinfo$
503 497 ignore $Id$
504 498 a
@@ -1,576 +1,574 b''
1 1 % help
2 2 hg record [OPTION]... [FILE]...
3 3
4 4 interactively select changes to commit
5 5
6 6 If a list of files is omitted, all changes reported by "hg status"
7 7 will be candidates for recording.
8 8
9 9 See 'hg help dates' for a list of formats valid for -d/--date.
10 10
11 11 You will be prompted for whether to record changes to each
12 12 modified file, and for files with multiple changes, for each
13 13 change to use. For each query, the following responses are
14 14 possible:
15 15
16 16 y - record this change
17 17 n - skip this change
18 18
19 19 s - skip remaining changes to this file
20 20 f - record remaining changes to this file
21 21
22 22 d - done, skip remaining changes and files
23 23 a - record all changes to all remaining files
24 24 q - quit, recording no changes
25 25
26 26 ? - display help
27 27
28 28 options:
29 29
30 30 -A --addremove mark new/missing files as added/removed before
31 31 committing
32 32 --close-branch mark a branch as closed, hiding it from the branch
33 33 list
34 34 -I --include include names matching the given patterns
35 35 -X --exclude exclude names matching the given patterns
36 36 -m --message use <text> as commit message
37 37 -l --logfile read commit message from <file>
38 38 -d --date record datecode as commit date
39 39 -u --user record the specified user as committer
40 40
41 41 use "hg -v help record" to show global options
42 42 % select no files
43 43 diff --git a/empty-rw b/empty-rw
44 44 new file mode 100644
45 45 examine changes to 'empty-rw'? [Ynsfdaq?] no changes to record
46 46
47 47 changeset: -1:000000000000
48 48 tag: tip
49 49 user:
50 50 date: Thu Jan 01 00:00:00 1970 +0000
51 51
52 52
53 53 % select files but no hunks
54 54 diff --git a/empty-rw b/empty-rw
55 55 new file mode 100644
56 examine changes to 'empty-rw'? [Ynsfdaq?] transaction abort!
57 rollback completed
58 abort: empty commit message
56 examine changes to 'empty-rw'? [Ynsfdaq?] abort: empty commit message
59 57
60 58 changeset: -1:000000000000
61 59 tag: tip
62 60 user:
63 61 date: Thu Jan 01 00:00:00 1970 +0000
64 62
65 63
66 64 % record empty file
67 65 diff --git a/empty-rw b/empty-rw
68 66 new file mode 100644
69 67 examine changes to 'empty-rw'? [Ynsfdaq?]
70 68 changeset: 0:c0708cf4e46e
71 69 tag: tip
72 70 user: test
73 71 date: Thu Jan 01 00:00:00 1970 +0000
74 72 summary: empty
75 73
76 74
77 75 % rename empty file
78 76 diff --git a/empty-rw b/empty-rename
79 77 rename from empty-rw
80 78 rename to empty-rename
81 79 examine changes to 'empty-rw' and 'empty-rename'? [Ynsfdaq?]
82 80 changeset: 1:df251d174da3
83 81 tag: tip
84 82 user: test
85 83 date: Thu Jan 01 00:00:01 1970 +0000
86 84 summary: rename
87 85
88 86
89 87 % copy empty file
90 88 diff --git a/empty-rename b/empty-copy
91 89 copy from empty-rename
92 90 copy to empty-copy
93 91 examine changes to 'empty-rename' and 'empty-copy'? [Ynsfdaq?]
94 92 changeset: 2:b63ea3939f8d
95 93 tag: tip
96 94 user: test
97 95 date: Thu Jan 01 00:00:02 1970 +0000
98 96 summary: copy
99 97
100 98
101 99 % delete empty file
102 100 diff --git a/empty-copy b/empty-copy
103 101 deleted file mode 100644
104 102 examine changes to 'empty-copy'? [Ynsfdaq?]
105 103 changeset: 3:a2546574bce9
106 104 tag: tip
107 105 user: test
108 106 date: Thu Jan 01 00:00:03 1970 +0000
109 107 summary: delete
110 108
111 109
112 110 % add binary file
113 111 1 changesets found
114 112 diff --git a/tip.bundle b/tip.bundle
115 113 new file mode 100644
116 114 this is a binary file
117 115 examine changes to 'tip.bundle'? [Ynsfdaq?]
118 116 changeset: 4:9e998a545a8b
119 117 tag: tip
120 118 user: test
121 119 date: Thu Jan 01 00:00:04 1970 +0000
122 120 summary: binary
123 121
124 122 diff -r a2546574bce9 -r 9e998a545a8b tip.bundle
125 123 Binary file tip.bundle has changed
126 124
127 125 % change binary file
128 126 1 changesets found
129 127 diff --git a/tip.bundle b/tip.bundle
130 128 this modifies a binary file (all or nothing)
131 129 examine changes to 'tip.bundle'? [Ynsfdaq?]
132 130 changeset: 5:93d05561507d
133 131 tag: tip
134 132 user: test
135 133 date: Thu Jan 01 00:00:05 1970 +0000
136 134 summary: binary-change
137 135
138 136 diff -r 9e998a545a8b -r 93d05561507d tip.bundle
139 137 Binary file tip.bundle has changed
140 138
141 139 % rename and change binary file
142 140 1 changesets found
143 141 diff --git a/tip.bundle b/top.bundle
144 142 rename from tip.bundle
145 143 rename to top.bundle
146 144 this modifies a binary file (all or nothing)
147 145 examine changes to 'tip.bundle' and 'top.bundle'? [Ynsfdaq?]
148 146 changeset: 6:699cc1bea9aa
149 147 tag: tip
150 148 user: test
151 149 date: Thu Jan 01 00:00:06 1970 +0000
152 150 summary: binary-change-rename
153 151
154 152 diff -r 93d05561507d -r 699cc1bea9aa tip.bundle
155 153 Binary file tip.bundle has changed
156 154 diff -r 93d05561507d -r 699cc1bea9aa top.bundle
157 155 Binary file top.bundle has changed
158 156
159 157 % add plain file
160 158 diff --git a/plain b/plain
161 159 new file mode 100644
162 160 examine changes to 'plain'? [Ynsfdaq?]
163 161 changeset: 7:118ed744216b
164 162 tag: tip
165 163 user: test
166 164 date: Thu Jan 01 00:00:07 1970 +0000
167 165 summary: plain
168 166
169 167 diff -r 699cc1bea9aa -r 118ed744216b plain
170 168 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
171 169 +++ b/plain Thu Jan 01 00:00:07 1970 +0000
172 170 @@ -0,0 +1,10 @@
173 171 +1
174 172 +2
175 173 +3
176 174 +4
177 175 +5
178 176 +6
179 177 +7
180 178 +8
181 179 +9
182 180 +10
183 181
184 182 % modify end of plain file
185 183 diff --git a/plain b/plain
186 184 1 hunks, 1 lines changed
187 185 examine changes to 'plain'? [Ynsfdaq?] @@ -8,3 +8,4 @@
188 186 8
189 187 9
190 188 10
191 189 +11
192 190 record this change to 'plain'? [Ynsfdaq?] % modify end of plain file, no EOL
193 191 diff --git a/plain b/plain
194 192 1 hunks, 1 lines changed
195 193 examine changes to 'plain'? [Ynsfdaq?] @@ -9,3 +9,4 @@
196 194 9
197 195 10
198 196 11
199 197 +cf81a2760718a74d44c0c2eecb72f659e63a69c5
200 198 \ No newline at end of file
201 199 record this change to 'plain'? [Ynsfdaq?] % modify end of plain file, add EOL
202 200 diff --git a/plain b/plain
203 201 1 hunks, 2 lines changed
204 202 examine changes to 'plain'? [Ynsfdaq?] @@ -9,4 +9,4 @@
205 203 9
206 204 10
207 205 11
208 206 -cf81a2760718a74d44c0c2eecb72f659e63a69c5
209 207 \ No newline at end of file
210 208 +cf81a2760718a74d44c0c2eecb72f659e63a69c5
211 209 record this change to 'plain'? [Ynsfdaq?] % modify beginning, trim end, record both
212 210 diff --git a/plain b/plain
213 211 2 hunks, 4 lines changed
214 212 examine changes to 'plain'? [Ynsfdaq?] @@ -1,4 +1,4 @@
215 213 -1
216 214 +2
217 215 2
218 216 3
219 217 4
220 218 record change 1/2 to 'plain'? [Ynsfdaq?] @@ -8,5 +8,3 @@
221 219 8
222 220 9
223 221 10
224 222 -11
225 223 -cf81a2760718a74d44c0c2eecb72f659e63a69c5
226 224 record change 2/2 to 'plain'? [Ynsfdaq?]
227 225 changeset: 11:d09ab1967dab
228 226 tag: tip
229 227 user: test
230 228 date: Thu Jan 01 00:00:10 1970 +0000
231 229 summary: begin-and-end
232 230
233 231 diff -r e2ecd9b0b78d -r d09ab1967dab plain
234 232 --- a/plain Thu Jan 01 00:00:10 1970 +0000
235 233 +++ b/plain Thu Jan 01 00:00:10 1970 +0000
236 234 @@ -1,4 +1,4 @@
237 235 -1
238 236 +2
239 237 2
240 238 3
241 239 4
242 240 @@ -8,5 +8,3 @@
243 241 8
244 242 9
245 243 10
246 244 -11
247 245 -cf81a2760718a74d44c0c2eecb72f659e63a69c5
248 246
249 247 % trim beginning, modify end
250 248 % record end
251 249 diff --git a/plain b/plain
252 250 2 hunks, 5 lines changed
253 251 examine changes to 'plain'? [Ynsfdaq?] @@ -1,9 +1,6 @@
254 252 -2
255 253 -2
256 254 -3
257 255 4
258 256 5
259 257 6
260 258 7
261 259 8
262 260 9
263 261 record change 1/2 to 'plain'? [Ynsfdaq?] @@ -4,7 +1,7 @@
264 262 4
265 263 5
266 264 6
267 265 7
268 266 8
269 267 9
270 268 -10
271 269 +10.new
272 270 record change 2/2 to 'plain'? [Ynsfdaq?]
273 271 changeset: 12:44516c9708ae
274 272 tag: tip
275 273 user: test
276 274 date: Thu Jan 01 00:00:11 1970 +0000
277 275 summary: end-only
278 276
279 277 diff -r d09ab1967dab -r 44516c9708ae plain
280 278 --- a/plain Thu Jan 01 00:00:10 1970 +0000
281 279 +++ b/plain Thu Jan 01 00:00:11 1970 +0000
282 280 @@ -7,4 +7,4 @@
283 281 7
284 282 8
285 283 9
286 284 -10
287 285 +10.new
288 286
289 287 % record beginning
290 288 diff --git a/plain b/plain
291 289 1 hunks, 3 lines changed
292 290 examine changes to 'plain'? [Ynsfdaq?] @@ -1,6 +1,3 @@
293 291 -2
294 292 -2
295 293 -3
296 294 4
297 295 5
298 296 6
299 297 record this change to 'plain'? [Ynsfdaq?]
300 298 changeset: 13:3ebbace64a8d
301 299 tag: tip
302 300 user: test
303 301 date: Thu Jan 01 00:00:12 1970 +0000
304 302 summary: begin-only
305 303
306 304 diff -r 44516c9708ae -r 3ebbace64a8d plain
307 305 --- a/plain Thu Jan 01 00:00:11 1970 +0000
308 306 +++ b/plain Thu Jan 01 00:00:12 1970 +0000
309 307 @@ -1,6 +1,3 @@
310 308 -2
311 309 -2
312 310 -3
313 311 4
314 312 5
315 313 6
316 314
317 315 % add to beginning, trim from end
318 316 % record end
319 317 diff --git a/plain b/plain
320 318 2 hunks, 4 lines changed
321 319 examine changes to 'plain'? [Ynsfdaq?] @@ -1,6 +1,9 @@
322 320 +1
323 321 +2
324 322 +3
325 323 4
326 324 5
327 325 6
328 326 7
329 327 8
330 328 9
331 329 record change 1/2 to 'plain'? [Ynsfdaq?] @@ -1,7 +4,6 @@
332 330 4
333 331 5
334 332 6
335 333 7
336 334 8
337 335 9
338 336 -10.new
339 337 record change 2/2 to 'plain'? [Ynsfdaq?] % add to beginning, middle, end
340 338 % record beginning, middle
341 339 diff --git a/plain b/plain
342 340 3 hunks, 7 lines changed
343 341 examine changes to 'plain'? [Ynsfdaq?] @@ -1,2 +1,5 @@
344 342 +1
345 343 +2
346 344 +3
347 345 4
348 346 5
349 347 record change 1/3 to 'plain'? [Ynsfdaq?] @@ -1,6 +4,8 @@
350 348 4
351 349 5
352 350 +5.new
353 351 +5.reallynew
354 352 6
355 353 7
356 354 8
357 355 9
358 356 record change 2/3 to 'plain'? [Ynsfdaq?] @@ -3,4 +8,6 @@
359 357 6
360 358 7
361 359 8
362 360 9
363 361 +10
364 362 +11
365 363 record change 3/3 to 'plain'? [Ynsfdaq?]
366 364 changeset: 15:c1c639d8b268
367 365 tag: tip
368 366 user: test
369 367 date: Thu Jan 01 00:00:14 1970 +0000
370 368 summary: middle-only
371 369
372 370 diff -r efc0dad7bd9f -r c1c639d8b268 plain
373 371 --- a/plain Thu Jan 01 00:00:13 1970 +0000
374 372 +++ b/plain Thu Jan 01 00:00:14 1970 +0000
375 373 @@ -1,5 +1,10 @@
376 374 +1
377 375 +2
378 376 +3
379 377 4
380 378 5
381 379 +5.new
382 380 +5.reallynew
383 381 6
384 382 7
385 383 8
386 384
387 385 % record end
388 386 diff --git a/plain b/plain
389 387 1 hunks, 2 lines changed
390 388 examine changes to 'plain'? [Ynsfdaq?] @@ -9,3 +9,5 @@
391 389 7
392 390 8
393 391 9
394 392 +10
395 393 +11
396 394 record this change to 'plain'? [Ynsfdaq?]
397 395 changeset: 16:80b74bbc7808
398 396 tag: tip
399 397 user: test
400 398 date: Thu Jan 01 00:00:15 1970 +0000
401 399 summary: end-only
402 400
403 401 diff -r c1c639d8b268 -r 80b74bbc7808 plain
404 402 --- a/plain Thu Jan 01 00:00:14 1970 +0000
405 403 +++ b/plain Thu Jan 01 00:00:15 1970 +0000
406 404 @@ -9,3 +9,5 @@
407 405 7
408 406 8
409 407 9
410 408 +10
411 409 +11
412 410
413 411 adding subdir/a
414 412 diff --git a/subdir/a b/subdir/a
415 413 1 hunks, 1 lines changed
416 414 examine changes to 'subdir/a'? [Ynsfdaq?] @@ -1,1 +1,2 @@
417 415 a
418 416 +a
419 417 record this change to 'subdir/a'? [Ynsfdaq?]
420 418 changeset: 18:33ff5c4fb017
421 419 tag: tip
422 420 user: test
423 421 date: Thu Jan 01 00:00:16 1970 +0000
424 422 summary: subdir-change
425 423
426 424 diff -r aecf2b2ea83c -r 33ff5c4fb017 subdir/a
427 425 --- a/subdir/a Thu Jan 01 00:00:16 1970 +0000
428 426 +++ b/subdir/a Thu Jan 01 00:00:16 1970 +0000
429 427 @@ -1,1 +1,2 @@
430 428 a
431 429 +a
432 430
433 431 % help, quit
434 432 diff --git a/subdir/f1 b/subdir/f1
435 433 1 hunks, 1 lines changed
436 434 examine changes to 'subdir/f1'? [Ynsfdaq?] y - record this change
437 435 n - skip this change
438 436 s - skip remaining changes to this file
439 437 f - record remaining changes to this file
440 438 d - done, skip remaining changes and files
441 439 a - record all changes to all remaining files
442 440 q - quit, recording no changes
443 441 ? - display help
444 442 examine changes to 'subdir/f1'? [Ynsfdaq?] abort: user quit
445 443 % skip
446 444 diff --git a/subdir/f1 b/subdir/f1
447 445 1 hunks, 1 lines changed
448 446 examine changes to 'subdir/f1'? [Ynsfdaq?] diff --git a/subdir/f2 b/subdir/f2
449 447 1 hunks, 1 lines changed
450 448 examine changes to 'subdir/f2'? [Ynsfdaq?] abort: response expected
451 449 % no
452 450 diff --git a/subdir/f1 b/subdir/f1
453 451 1 hunks, 1 lines changed
454 452 examine changes to 'subdir/f1'? [Ynsfdaq?] diff --git a/subdir/f2 b/subdir/f2
455 453 1 hunks, 1 lines changed
456 454 examine changes to 'subdir/f2'? [Ynsfdaq?] abort: response expected
457 455 % f, quit
458 456 diff --git a/subdir/f1 b/subdir/f1
459 457 1 hunks, 1 lines changed
460 458 examine changes to 'subdir/f1'? [Ynsfdaq?] diff --git a/subdir/f2 b/subdir/f2
461 459 1 hunks, 1 lines changed
462 460 examine changes to 'subdir/f2'? [Ynsfdaq?] abort: user quit
463 461 % s, all
464 462 diff --git a/subdir/f1 b/subdir/f1
465 463 1 hunks, 1 lines changed
466 464 examine changes to 'subdir/f1'? [Ynsfdaq?] diff --git a/subdir/f2 b/subdir/f2
467 465 1 hunks, 1 lines changed
468 466 examine changes to 'subdir/f2'? [Ynsfdaq?]
469 467 changeset: 20:094183e04b7c
470 468 tag: tip
471 469 user: test
472 470 date: Thu Jan 01 00:00:18 1970 +0000
473 471 summary: x
474 472
475 473 diff -r f9e855cd9374 -r 094183e04b7c subdir/f2
476 474 --- a/subdir/f2 Thu Jan 01 00:00:17 1970 +0000
477 475 +++ b/subdir/f2 Thu Jan 01 00:00:18 1970 +0000
478 476 @@ -1,1 +1,2 @@
479 477 b
480 478 +b
481 479
482 480 % f
483 481 diff --git a/subdir/f1 b/subdir/f1
484 482 1 hunks, 1 lines changed
485 483 examine changes to 'subdir/f1'? [Ynsfdaq?]
486 484 changeset: 21:38164785b0ef
487 485 tag: tip
488 486 user: test
489 487 date: Thu Jan 01 00:00:19 1970 +0000
490 488 summary: y
491 489
492 490 diff -r 094183e04b7c -r 38164785b0ef subdir/f1
493 491 --- a/subdir/f1 Thu Jan 01 00:00:18 1970 +0000
494 492 +++ b/subdir/f1 Thu Jan 01 00:00:19 1970 +0000
495 493 @@ -1,1 +1,2 @@
496 494 a
497 495 +a
498 496
499 497 % preserve chmod +x
500 498 diff --git a/subdir/f1 b/subdir/f1
501 499 old mode 100644
502 500 new mode 100755
503 501 1 hunks, 1 lines changed
504 502 examine changes to 'subdir/f1'? [Ynsfdaq?] @@ -1,2 +1,3 @@
505 503 a
506 504 a
507 505 +a
508 506 record this change to 'subdir/f1'? [Ynsfdaq?]
509 507 changeset: 22:a891589cb933
510 508 tag: tip
511 509 user: test
512 510 date: Thu Jan 01 00:00:20 1970 +0000
513 511 summary: z
514 512
515 513 diff --git a/subdir/f1 b/subdir/f1
516 514 old mode 100644
517 515 new mode 100755
518 516 --- a/subdir/f1
519 517 +++ b/subdir/f1
520 518 @@ -1,2 +1,3 @@
521 519 a
522 520 a
523 521 +a
524 522
525 523 % preserve execute permission on original
526 524 diff --git a/subdir/f1 b/subdir/f1
527 525 1 hunks, 1 lines changed
528 526 examine changes to 'subdir/f1'? [Ynsfdaq?] @@ -1,3 +1,4 @@
529 527 a
530 528 a
531 529 a
532 530 +b
533 531 record this change to 'subdir/f1'? [Ynsfdaq?]
534 532 changeset: 23:befa0dae6201
535 533 tag: tip
536 534 user: test
537 535 date: Thu Jan 01 00:00:21 1970 +0000
538 536 summary: aa
539 537
540 538 diff --git a/subdir/f1 b/subdir/f1
541 539 --- a/subdir/f1
542 540 +++ b/subdir/f1
543 541 @@ -1,3 +1,4 @@
544 542 a
545 543 a
546 544 a
547 545 +b
548 546
549 547 % preserve chmod -x
550 548 diff --git a/subdir/f1 b/subdir/f1
551 549 old mode 100755
552 550 new mode 100644
553 551 1 hunks, 1 lines changed
554 552 examine changes to 'subdir/f1'? [Ynsfdaq?] @@ -2,3 +2,4 @@
555 553 a
556 554 a
557 555 b
558 556 +c
559 557 record this change to 'subdir/f1'? [Ynsfdaq?]
560 558 changeset: 24:8fd83ff53ce6
561 559 tag: tip
562 560 user: test
563 561 date: Thu Jan 01 00:00:22 1970 +0000
564 562 summary: ab
565 563
566 564 diff --git a/subdir/f1 b/subdir/f1
567 565 old mode 100755
568 566 new mode 100644
569 567 --- a/subdir/f1
570 568 +++ b/subdir/f1
571 569 @@ -2,3 +2,4 @@
572 570 a
573 571 a
574 572 b
575 573 +c
576 574
General Comments 0
You need to be logged in to leave comments. Login now