##// END OF EJS Templates
Merge with crew
Matt Mackall -
r2803:987c31e2 merge default
parent child Browse files
Show More
@@ -0,0 +1,93 b''
1 # fetch.py - pull and merge remote changes
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 #
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
7
8 from mercurial.demandload import *
9 from mercurial.i18n import gettext as _
10 from mercurial.node import *
11 demandload(globals(), 'mercurial:commands,hg,node,util')
12
13 def fetch(ui, repo, source='default', **opts):
14 '''Pull changes from a remote repository, merge new changes if needed.
15
16 This finds all changes from the repository at the specified path
17 or URL and adds them to the local repository.
18
19 If the pulled changes add a new head, the head is automatically
20 merged, and the result of the merge is committed. Otherwise, the
21 working directory is updated.'''
22
23 def postincoming(other, modheads):
24 if modheads == 0:
25 return 0
26 if modheads == 1:
27 return commands.doupdate(ui, repo)
28 newheads = repo.heads(parent)
29 newchildren = [n for n in repo.heads(parent) if n != parent]
30 newparent = parent
31 if newchildren:
32 commands.doupdate(ui, repo, node=hex(newchildren[0]))
33 newparent = newchildren[0]
34 newheads = [n for n in repo.heads() if n != newparent]
35 err = False
36 if newheads:
37 ui.status(_('merging with new head %d:%s\n') %
38 (repo.changelog.rev(newheads[0]), short(newheads[0])))
39 err = repo.update(newheads[0], allow=True, remind=False)
40 if not err and len(newheads) > 1:
41 ui.status(_('not merging with %d other new heads '
42 '(use "hg heads" and "hg merge" to merge them)') %
43 (len(newheads) - 1))
44 if not err:
45 mod, add, rem = repo.status()[:3]
46 message = (commands.logmessage(opts) or
47 (_('Automated merge with %s') % other.url()))
48 n = repo.commit(mod + add + rem, message,
49 opts['user'], opts['date'],
50 force_editor=opts.get('force_editor'))
51 ui.status(_('new changeset %d:%s merges remote changes '
52 'with local\n') % (repo.changelog.rev(n),
53 short(n)))
54 def pull():
55 commands.setremoteconfig(ui, opts)
56
57 other = hg.repository(ui, ui.expandpath(source))
58 ui.status(_('pulling from %s\n') % source)
59 revs = None
60 if opts['rev'] and not other.local():
61 raise util.Abort(_("fetch -r doesn't work for remote repositories yet"))
62 elif opts['rev']:
63 revs = [other.lookup(rev) for rev in opts['rev']]
64 modheads = repo.pull(other, heads=revs)
65 return postincoming(other, modheads)
66
67 parent, p2 = repo.dirstate.parents()
68 if parent != repo.changelog.tip():
69 raise util.Abort(_('working dir not at tip '
70 '(use "hg update" to check out tip)'))
71 if p2 != nullid:
72 raise util.Abort(_('outstanding uncommitted merge'))
73 mod, add, rem = repo.status()[:3]
74 if mod or add or rem:
75 raise util.Abort(_('outstanding uncommitted changes'))
76 if len(repo.heads()) > 1:
77 raise util.Abort(_('multiple heads in this repository '
78 '(use "hg heads" and "hg merge" to merge them)'))
79 return pull()
80
81 cmdtable = {
82 'fetch':
83 (fetch,
84 [('e', 'ssh', '', _('specify ssh command to use')),
85 ('m', 'message', '', _('use <text> as commit message')),
86 ('l', 'logfile', '', _('read the commit message from <file>')),
87 ('d', 'date', '', _('record datecode as commit date')),
88 ('u', 'user', '', _('record user as commiter')),
89 ('r', 'rev', [], _('a specific revision you would like to pull')),
90 ('f', 'force-editor', None, _('edit commit message')),
91 ('', 'remotecmd', '', _('hg command to run on the remote side'))],
92 'hg fetch [SOURCE]'),
93 }
@@ -39,6 +39,16 b' versionstr = "0.45"'
39 39
40 40 commands.norepo += " qclone qversion"
41 41
42 class StatusEntry:
43 def __init__(self, rev, name=None):
44 if not name:
45 self.rev, self.name = rev.split(':')
46 else:
47 self.rev, self.name = rev, name
48
49 def __str__(self):
50 return self.rev + ':' + self.name
51
42 52 class queue:
43 53 def __init__(self, ui, path, patchdir=None):
44 54 self.basepath = path
@@ -60,7 +70,8 b' class queue:'
60 70 self.parse_series()
61 71
62 72 if os.path.exists(os.path.join(self.path, self.status_path)):
63 self.applied = self.opener(self.status_path).read().splitlines()
73 self.applied = [StatusEntry(l)
74 for l in self.opener(self.status_path).read().splitlines()]
64 75
65 76 def find_series(self, patch):
66 77 pre = re.compile("(\s*)([^#]+)")
@@ -88,7 +99,7 b' class queue:'
88 99 for i in items:
89 100 print >> fp, i
90 101 fp.close()
91 if self.applied_dirty: write_list(self.applied, self.status_path)
102 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
92 103 if self.series_dirty: write_list(self.full_series, self.series_path)
93 104
94 105 def readheaders(self, patch):
@@ -209,12 +220,10 b' class queue:'
209 220 return p1
210 221 if len(self.applied) == 0:
211 222 return None
212 (top, patch) = self.applied[-1].split(':')
213 top = revlog.bin(top)
214 return top
223 return revlog.bin(self.applied[-1].rev)
215 224 pp = repo.changelog.parents(rev)
216 225 if pp[1] != revlog.nullid:
217 arevs = [ x.split(':')[0] for x in self.applied ]
226 arevs = [ x.rev for x in self.applied ]
218 227 p0 = revlog.hex(pp[0])
219 228 p1 = revlog.hex(pp[1])
220 229 if p0 in arevs:
@@ -234,7 +243,7 b' class queue:'
234 243 pname = ".hg.patches.merge.marker"
235 244 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
236 245 wlock=wlock)
237 self.applied.append(revlog.hex(n) + ":" + pname)
246 self.applied.append(StatusEntry(revlog.hex(n), pname))
238 247 self.applied_dirty = 1
239 248
240 249 head = self.qparents(repo)
@@ -252,7 +261,7 b' class queue:'
252 261 rev = revlog.bin(info[1])
253 262 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
254 263 if head:
255 self.applied.append(revlog.hex(head) + ":" + patch)
264 self.applied.append(StatusEntry(revlog.hex(head), patch))
256 265 self.applied_dirty = 1
257 266 if err:
258 267 return (err, head)
@@ -263,8 +272,8 b' class queue:'
263 272 patchfile: file name of patch'''
264 273 try:
265 274 pp = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
266 f = os.popen("%s -d '%s' -p1 --no-backup-if-mismatch < '%s'" %
267 (pp, repo.root, patchfile))
275 f = os.popen("%s -d %s -p1 --no-backup-if-mismatch < %s" %
276 (pp, util.shellquote(repo.root), util.shellquote(patchfile)))
268 277 except:
269 278 self.ui.warn("patch failed, unable to continue (try -v)\n")
270 279 return (None, [], False)
@@ -275,11 +284,7 b' class queue:'
275 284 if self.ui.verbose:
276 285 self.ui.warn(l + "\n")
277 286 if l[:14] == 'patching file ':
278 pf = os.path.normpath(l[14:])
279 # when patch finds a space in the file name, it puts
280 # single quotes around the filename. strip them off
281 if pf[0] == "'" and pf[-1] == "'":
282 pf = pf[1:-1]
287 pf = os.path.normpath(util.parse_patch_output(l))
283 288 if pf not in files:
284 289 files.append(pf)
285 290 printed_file = False
@@ -351,7 +356,7 b' class queue:'
351 356 raise util.Abort(_("repo commit failed"))
352 357
353 358 if update_status:
354 self.applied.append(revlog.hex(n) + ":" + patch)
359 self.applied.append(StatusEntry(revlog.hex(n), patch))
355 360
356 361 if patcherr:
357 362 if not patchfound:
@@ -389,8 +394,7 b' class queue:'
389 394
390 395 def check_toppatch(self, repo):
391 396 if len(self.applied) > 0:
392 (top, patch) = self.applied[-1].split(':')
393 top = revlog.bin(top)
397 top = revlog.bin(self.applied[-1].rev)
394 398 pp = repo.dirstate.parents()
395 399 if top not in pp:
396 400 raise util.Abort(_("queue top not at same revision as working directory"))
@@ -421,7 +425,7 b' class queue:'
421 425 if n == None:
422 426 raise util.Abort(_("repo commit failed"))
423 427 self.full_series[insert:insert] = [patch]
424 self.applied.append(revlog.hex(n) + ":" + patch)
428 self.applied.append(StatusEntry(revlog.hex(n), patch))
425 429 self.parse_series()
426 430 self.series_dirty = 1
427 431 self.applied_dirty = 1
@@ -501,9 +505,9 b' class queue:'
501 505 # we go in two steps here so the strip loop happens in a
502 506 # sensible order. When stripping many files, this helps keep
503 507 # our disk access patterns under control.
504 list = seen.keys()
505 list.sort()
506 for f in list:
508 seen_list = seen.keys()
509 seen_list.sort()
510 for f in seen_list:
507 511 ff = repo.file(f)
508 512 filerev = seen[f]
509 513 if filerev != 0:
@@ -535,7 +539,6 b' class queue:'
535 539 saveheads = []
536 540 savebases = {}
537 541
538 tip = chlog.tip()
539 542 heads = limitheads(chlog, rev)
540 543 seen = {}
541 544
@@ -566,7 +569,7 b' class queue:'
566 569 savebases[x] = 1
567 570
568 571 # create a changegroup for all the branches we need to keep
569 if backup is "all":
572 if backup == "all":
570 573 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
571 574 bundle(backupch)
572 575 if saveheads:
@@ -581,16 +584,15 b' class queue:'
581 584 if saveheads:
582 585 self.ui.status("adding branch\n")
583 586 commands.unbundle(self.ui, repo, chgrpfile, update=False)
584 if backup is not "strip":
587 if backup != "strip":
585 588 os.unlink(chgrpfile)
586 589
587 590 def isapplied(self, patch):
588 591 """returns (index, rev, patch)"""
589 592 for i in xrange(len(self.applied)):
590 p = self.applied[i]
591 a = p.split(':')
592 if a[1] == patch:
593 return (i, a[0], a[1])
593 a = self.applied[i]
594 if a.name == patch:
595 return (i, a.rev, a.name)
594 596 return None
595 597
596 598 # if the exact patch name does not exist, we try a few
@@ -693,7 +695,7 b' class queue:'
693 695 ret = self.mergepatch(repo, mergeq, s, wlock)
694 696 else:
695 697 ret = self.apply(repo, s, list, wlock=wlock)
696 top = self.applied[-1].split(':')[1]
698 top = self.applied[-1].name
697 699 if ret[0]:
698 700 self.ui.write("Errors during apply, please fix and refresh %s\n" %
699 701 top)
@@ -730,7 +732,7 b' class queue:'
730 732
731 733 if not update:
732 734 parents = repo.dirstate.parents()
733 rr = [ revlog.bin(x.split(':')[0]) for x in self.applied ]
735 rr = [ revlog.bin(x.rev) for x in self.applied ]
734 736 for p in parents:
735 737 if p in rr:
736 738 self.ui.warn("qpop: forcing dirstate update\n")
@@ -751,7 +753,7 b' class queue:'
751 753 if popi >= end:
752 754 self.ui.warn("qpop: %s is already at the top\n" % patch)
753 755 return
754 info = [ popi ] + self.applied[popi].split(':')
756 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
755 757
756 758 start = info[0]
757 759 rev = revlog.bin(info[1])
@@ -784,7 +786,7 b' class queue:'
784 786 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
785 787 del self.applied[start:end]
786 788 if len(self.applied):
787 self.ui.write("Now at: %s\n" % self.applied[-1].split(':')[1])
789 self.ui.write("Now at: %s\n" % self.applied[-1].name)
788 790 else:
789 791 self.ui.write("Patch queue now empty\n")
790 792
@@ -802,8 +804,7 b' class queue:'
802 804 return
803 805 wlock = repo.wlock()
804 806 self.check_toppatch(repo)
805 qp = self.qparents(repo)
806 (top, patch) = self.applied[-1].split(':')
807 (top, patch) = (self.applied[-1].rev, self.applied[-1].name)
807 808 top = revlog.bin(top)
808 809 cparents = repo.changelog.parents(top)
809 810 patchparent = self.qparents(repo, top)
@@ -899,7 +900,7 b' class queue:'
899 900
900 901 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
901 902 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
902 self.applied[-1] = revlog.hex(n) + ':' + patch
903 self.applied[-1] = StatusEntry(revlog.hex(n), patch)
903 904 self.applied_dirty = 1
904 905 else:
905 906 commands.dodiff(patchf, self.ui, repo, patchparent, None)
@@ -921,10 +922,7 b' class queue:'
921 922 start = self.series_end()
922 923 else:
923 924 start = self.series.index(patch) + 1
924 for p in self.series[start:]:
925 if self.ui.verbose:
926 self.ui.write("%d " % self.series.index(p))
927 self.ui.write("%s\n" % p)
925 return [(i, self.series[i]) for i in xrange(start, len(self.series))]
928 926
929 927 def qseries(self, repo, missing=None, summary=False):
930 928 start = self.series_end()
@@ -944,7 +942,7 b' class queue:'
944 942 msg = ''
945 943 self.ui.write('%s%s\n' % (patch, msg))
946 944 else:
947 list = []
945 msng_list = []
948 946 for root, dirs, files in os.walk(self.path):
949 947 d = root[len(self.path) + 1:]
950 948 for f in files:
@@ -952,13 +950,12 b' class queue:'
952 950 if (fl not in self.series and
953 951 fl not in (self.status_path, self.series_path)
954 952 and not fl.startswith('.')):
955 list.append(fl)
956 list.sort()
957 if list:
958 for x in list:
959 if self.ui.verbose:
960 self.ui.write("D ")
961 self.ui.write("%s\n" % x)
953 msng_list.append(fl)
954 msng_list.sort()
955 for x in msng_list:
956 if self.ui.verbose:
957 self.ui.write("D ")
958 self.ui.write("%s\n" % x)
962 959
963 960 def issaveline(self, l):
964 961 name = l.split(':')[1]
@@ -987,12 +984,11 b' class queue:'
987 984 qpp = [ hg.bin(x) for x in l ]
988 985 elif datastart != None:
989 986 l = lines[i].rstrip()
990 index = l.index(':')
991 id = l[:index]
992 file = l[index + 1:]
993 if id:
994 applied.append(l)
995 series.append(file)
987 se = StatusEntry(l)
988 file_ = se.name
989 if se.rev:
990 applied.append(se)
991 series.append(file_)
996 992 if datastart == None:
997 993 self.ui.warn("No saved patch data found\n")
998 994 return 1
@@ -1043,18 +1039,18 b' class queue:'
1043 1039 pp = r.dirstate.parents()
1044 1040 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1045 1041 msg += "\n\nPatch Data:\n"
1046 text = msg + "\n".join(self.applied) + '\n' + (ar and "\n".join(ar)
1042 text = msg + "\n".join(str(self.applied)) + '\n' + (ar and "\n".join(ar)
1047 1043 + '\n' or "")
1048 1044 n = repo.commit(None, text, user=None, force=1)
1049 1045 if not n:
1050 1046 self.ui.warn("repo commit failed\n")
1051 1047 return 1
1052 self.applied.append(revlog.hex(n) + ":" + '.hg.patches.save.line')
1048 self.applied.append(StatusEntry(revlog.hex(n),'.hg.patches.save.line'))
1053 1049 self.applied_dirty = 1
1054 1050
1055 1051 def full_series_end(self):
1056 1052 if len(self.applied) > 0:
1057 (top, p) = self.applied[-1].split(':')
1053 p = self.applied[-1].name
1058 1054 end = self.find_series(p)
1059 1055 if end == None:
1060 1056 return len(self.full_series)
@@ -1064,7 +1060,7 b' class queue:'
1064 1060 def series_end(self):
1065 1061 end = 0
1066 1062 if len(self.applied) > 0:
1067 (top, p) = self.applied[-1].split(':')
1063 p = self.applied[-1].name
1068 1064 try:
1069 1065 end = self.series.index(p)
1070 1066 except ValueError:
@@ -1084,8 +1080,7 b' class queue:'
1084 1080 self.ui.write("%s\n" % p)
1085 1081
1086 1082 def appliedname(self, index):
1087 p = self.applied[index]
1088 pname = p.split(':')[1]
1083 pname = self.applied[index].name
1089 1084 if not self.ui.verbose:
1090 1085 p = pname
1091 1086 else:
@@ -1173,8 +1168,10 b' def applied(ui, repo, patch=None, **opts'
1173 1168
1174 1169 def unapplied(ui, repo, patch=None, **opts):
1175 1170 """print the patches not yet applied"""
1176 repo.mq.unapplied(repo, patch)
1177 return 0
1171 for i, p in repo.mq.unapplied(repo, patch):
1172 if ui.verbose:
1173 ui.write("%d " % i)
1174 ui.write("%s\n" % p)
1178 1175
1179 1176 def qimport(ui, repo, *filename, **opts):
1180 1177 """import a patch"""
@@ -1223,7 +1220,7 b' def clone(ui, source, dest=None, **opts)'
1223 1220 if sr.local():
1224 1221 reposetup(ui, sr)
1225 1222 if sr.mq.applied:
1226 qbase = revlog.bin(sr.mq.applied[0].split(':')[0])
1223 qbase = revlog.bin(sr.mq.applied[0].rev)
1227 1224 if not hg.islocal(dest):
1228 1225 destrev = sr.parents(qbase)[0]
1229 1226 ui.note(_('cloning main repo\n'))
@@ -1286,7 +1283,7 b' def new(ui, repo, patch, **opts):'
1286 1283 If neither is specified, the patch header is empty and the
1287 1284 commit message is 'New patch: PATCH'"""
1288 1285 q = repo.mq
1289 message=commands.logmessage(**opts)
1286 message = commands.logmessage(**opts)
1290 1287 q.new(repo, patch, msg=message, force=opts['force'])
1291 1288 q.save_dirty()
1292 1289 return 0
@@ -1294,11 +1291,11 b' def new(ui, repo, patch, **opts):'
1294 1291 def refresh(ui, repo, **opts):
1295 1292 """update the current patch"""
1296 1293 q = repo.mq
1297 message=commands.logmessage(**opts)
1294 message = commands.logmessage(**opts)
1298 1295 if opts['edit']:
1299 1296 if message:
1300 1297 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1301 patch = q.applied[-1].split(':')[1]
1298 patch = q.applied[-1].name
1302 1299 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1303 1300 message = ui.edit('\n'.join(message), user or ui.username())
1304 1301 q.refresh(repo, msg=message, short=opts['short'])
@@ -1331,7 +1328,7 b' def fold(ui, repo, *files, **opts):'
1331 1328 if not q.check_toppatch(repo):
1332 1329 raise util.Abort(_('No patches applied\n'))
1333 1330
1334 message=commands.logmessage(**opts)
1331 message = commands.logmessage(**opts)
1335 1332 if opts['edit']:
1336 1333 if message:
1337 1334 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
@@ -1342,7 +1339,7 b' def fold(ui, repo, *files, **opts):'
1342 1339 for f in files:
1343 1340 patch = q.lookup(f)
1344 1341 if patch in patches or patch == parent:
1345 self.ui.warn(_('Skipping already folded patch %s') % patch)
1342 ui.warn(_('Skipping already folded patch %s') % patch)
1346 1343 if q.isapplied(patch):
1347 1344 raise util.Abort(_('qfold cannot fold already applied patch %s') % patch)
1348 1345 patches.append(patch)
@@ -1388,20 +1385,20 b' def header(ui, repo, patch=None):'
1388 1385 ui.write('\n'.join(message) + '\n')
1389 1386
1390 1387 def lastsavename(path):
1391 (dir, base) = os.path.split(path)
1392 names = os.listdir(dir)
1388 (directory, base) = os.path.split(path)
1389 names = os.listdir(directory)
1393 1390 namere = re.compile("%s.([0-9]+)" % base)
1394 max = None
1391 maxindex = None
1395 1392 maxname = None
1396 1393 for f in names:
1397 1394 m = namere.match(f)
1398 1395 if m:
1399 1396 index = int(m.group(1))
1400 if max == None or index > max:
1401 max = index
1397 if maxindex == None or index > maxindex:
1398 maxindex = index
1402 1399 maxname = f
1403 1400 if maxname:
1404 return (os.path.join(dir, maxname), max)
1401 return (os.path.join(directory, maxname), maxindex)
1405 1402 return (None, None)
1406 1403
1407 1404 def savename(path):
@@ -1482,7 +1479,7 b' def rename(ui, repo, patch, name=None, *'
1482 1479
1483 1480 info = q.isapplied(patch)
1484 1481 if info:
1485 q.applied[info[0]] = info[1] + ':' + name
1482 q.applied[info[0]] = StatusEntry(info[1], name)
1486 1483 q.applied_dirty = 1
1487 1484
1488 1485 util.rename(os.path.join(q.path, patch), absdest)
@@ -1508,7 +1505,7 b' def restore(ui, repo, rev, **opts):'
1508 1505 def save(ui, repo, **opts):
1509 1506 """save current queue state"""
1510 1507 q = repo.mq
1511 message=commands.logmessage(**opts)
1508 message = commands.logmessage(**opts)
1512 1509 ret = q.save(repo, msg=message)
1513 1510 if ret:
1514 1511 return ret
@@ -1563,7 +1560,7 b' def reposetup(ui, repo):'
1563 1560 if not q.applied:
1564 1561 return tagscache
1565 1562
1566 mqtags = [patch.split(':') for patch in q.applied]
1563 mqtags = [(patch.rev, patch.name) for patch in q.applied]
1567 1564 mqtags.append((mqtags[-1][0], 'qtip'))
1568 1565 mqtags.append((mqtags[0][0], 'qbase'))
1569 1566 for patch in mqtags:
@@ -255,7 +255,7 b' def hook(ui, repo, hooktype, node=None, '
255 255 changegroup. else send one email per changeset.'''
256 256 n = notifier(ui, repo, hooktype)
257 257 if not n.subs:
258 ui.debug(_('notify: no subscribers to this repo\n'))
258 ui.debug(_('notify: no subscribers to repo %s\n' % n.root))
259 259 return
260 260 if n.skipsource(source):
261 261 ui.debug(_('notify: changes have source "%s" - skipping\n') %
@@ -288,7 +288,8 b' def patchbomb(ui, repo, *revs, **opts):'
288 288 fp.close()
289 289 else:
290 290 ui.status('Sending ', m['Subject'], ' ...\n')
291 m.__delitem__('bcc')
291 # Exim does not remove the Bcc field
292 del m['Bcc']
292 293 mail.sendmail(sender, to + bcc + cc, m.as_string(0))
293 294
294 295 cmdtable = {
@@ -40,7 +40,7 b' def relpath(repo, args):'
40 40 return [util.normpath(os.path.join(cwd, x)) for x in args]
41 41 return args
42 42
43 def logmessage(**opts):
43 def logmessage(opts):
44 44 """ get the log message according to -m and -l option """
45 45 message = opts['message']
46 46 logfile = opts['logfile']
@@ -125,12 +125,22 b' def walkchangerevs(ui, repo, pats, opts)'
125 125
126 126
127 127 files, matchfn, anypats = matchpats(repo, pats, opts)
128 follow = opts.get('follow')
128 follow = opts.get('follow') or opts.get('follow_first')
129 129
130 130 if repo.changelog.count() == 0:
131 131 return [], False, matchfn
132 132
133 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
133 if follow:
134 p = repo.dirstate.parents()[0]
135 if p == nullid:
136 ui.warn(_('No working directory revision; defaulting to tip\n'))
137 start = 'tip'
138 else:
139 start = repo.changelog.rev(p)
140 defrange = '%s:0' % start
141 else:
142 defrange = 'tip:0'
143 revs = map(int, revrange(ui, repo, opts['rev'] or [defrange]))
134 144 wanted = {}
135 145 slowpath = anypats
136 146 fncache = {}
@@ -206,10 +216,55 b' def walkchangerevs(ui, repo, pats, opts)'
206 216 wanted[rev] = 1
207 217
208 218 def iterate():
219 class followfilter:
220 def __init__(self, onlyfirst=False):
221 self.startrev = -1
222 self.roots = []
223 self.onlyfirst = onlyfirst
224
225 def match(self, rev):
226 def realparents(rev):
227 if self.onlyfirst:
228 return repo.changelog.parentrevs(rev)[0:1]
229 else:
230 return filter(lambda x: x != -1, repo.changelog.parentrevs(rev))
231
232 if self.startrev == -1:
233 self.startrev = rev
234 return True
235
236 if rev > self.startrev:
237 # forward: all descendants
238 if not self.roots:
239 self.roots.append(self.startrev)
240 for parent in realparents(rev):
241 if parent in self.roots:
242 self.roots.append(rev)
243 return True
244 else:
245 # backwards: all parents
246 if not self.roots:
247 self.roots.extend(realparents(self.startrev))
248 if rev in self.roots:
249 self.roots.remove(rev)
250 self.roots.extend(realparents(rev))
251 return True
252
253 return False
254
255 if follow and not files:
256 ff = followfilter(onlyfirst=opts.get('follow_first'))
257 def want(rev):
258 if rev not in wanted:
259 return False
260 return ff.match(rev)
261 else:
262 def want(rev):
263 return rev in wanted
264
209 265 for i, window in increasing_windows(0, len(revs)):
210 266 yield 'window', revs[0] < revs[-1], revs[-1]
211 nrevs = [rev for rev in revs[i:i+window]
212 if rev in wanted]
267 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
213 268 srevs = list(nrevs)
214 269 srevs.sort()
215 270 for rev in srevs:
@@ -1041,7 +1096,7 b' def commit(ui, repo, *pats, **opts):'
1041 1096 If no commit message is specified, the editor configured in your hgrc
1042 1097 or in the EDITOR environment variable is started to enter a message.
1043 1098 """
1044 message = logmessage(**opts)
1099 message = logmessage(opts)
1045 1100
1046 1101 if opts['addremove']:
1047 1102 addremove_lock(ui, repo, pats, opts)
@@ -1972,8 +2027,14 b' def log(ui, repo, *pats, **opts):'
1972 2027 project.
1973 2028
1974 2029 File history is shown without following rename or copy history of
1975 files. Use -f/--follow to follow history across renames and
1976 copies.
2030 files. Use -f/--follow with a file name to follow history across
2031 renames and copies. --follow without a file name will only show
2032 ancestors or descendants of the starting revision. --follow-first
2033 only follows the first parent of merge revisions.
2034
2035 If no revision range is specified, the default is tip:0 unless
2036 --follow is set, in which case the working directory parent is
2037 used as the starting revision.
1977 2038
1978 2039 By default this command outputs: changeset id and hash, tags,
1979 2040 non-trivial parents, user, date and time, and a summary for each
@@ -2728,8 +2789,8 b' def tag(ui, repo, name, rev_=None, **opt'
2728 2789 necessary. The file '.hg/localtags' is used for local tags (not
2729 2790 shared among repositories).
2730 2791 """
2731 if name == "tip":
2732 raise util.Abort(_("the name 'tip' is reserved"))
2792 if name in ['tip', '.']:
2793 raise util.Abort(_("the name '%s' is reserved") % name)
2733 2794 if rev_ is not None:
2734 2795 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2735 2796 "please use 'hg tag [-r REV] NAME' instead\n"))
@@ -3087,7 +3148,9 b' table = {'
3087 3148 (log,
3088 3149 [('b', 'branches', None, _('show branches')),
3089 3150 ('f', 'follow', None,
3090 _('follow file history across copies and renames')),
3151 _('follow changeset history, or file history across copies and renames')),
3152 ('', 'follow-first', None,
3153 _('only follow the first parent of merge changesets')),
3091 3154 ('k', 'keyword', [], _('search for a keyword')),
3092 3155 ('l', 'limit', '', _('limit number of changes displayed')),
3093 3156 ('r', 'rev', [], _('show the specified revision or range')),
@@ -292,6 +292,10 b' class localrepository(repo.repository):'
292 292 try:
293 293 return self.tags()[key]
294 294 except KeyError:
295 if key == '.':
296 key = self.dirstate.parents()[0]
297 if key == nullid:
298 raise repo.RepoError(_("no revision checked out"))
295 299 try:
296 300 return self.changelog.lookup(key)
297 301 except:
@@ -1693,6 +1697,7 b' class localrepository(repo.repository):'
1693 1697
1694 1698 return newheads - oldheads + 1
1695 1699
1700
1696 1701 def stream_in(self, remote):
1697 1702 fp = remote.stream_out()
1698 1703 resp = int(fp.readline())
@@ -48,7 +48,8 b' def merge3(repo, fn, my, other, p1, p2):'
48 48 return r
49 49
50 50 def update(repo, node, allow=False, force=False, choose=None,
51 moddirstate=True, forcemerge=False, wlock=None, show_stats=True):
51 moddirstate=True, forcemerge=False, wlock=None, show_stats=True,
52 remind=True):
52 53 pl = repo.dirstate.parents()
53 54 if not force and pl[1] != nullid:
54 55 raise util.Abort(_("outstanding uncommitted merges"))
@@ -337,7 +338,7 b' def update(repo, node, allow=False, forc'
337 338 " hg merge %s\n"
338 339 % (repo.changelog.rev(p1),
339 340 repo.changelog.rev(p2))))
340 else:
341 elif remind:
341 342 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
342 343 elif failedmerge:
343 344 repo.ui.status(_("There are unresolved merges with"
@@ -99,9 +99,9 b' def patch(strip, patchname, ui, cwd=None'
99 99 patcher = find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
100 100 args = []
101 101 if cwd:
102 args.append('-d "%s"' % cwd)
103 fp = os.popen('%s %s -p%d < "%s"' % (patcher, ' '.join(args), strip,
104 patchname))
102 args.append('-d %s' % shellquote(cwd))
103 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
104 shellquote(patchname)))
105 105 files = {}
106 106 for line in fp:
107 107 line = line.rstrip()
@@ -611,6 +611,9 b" if os.name == 'nt':"
611 611 def samestat(s1, s2):
612 612 return False
613 613
614 def shellquote(s):
615 return '"%s"' % s.replace('"', '\\"')
616
614 617 def explain_exit(code):
615 618 return _("exited with status %d") % code, code
616 619
@@ -700,6 +703,9 b' else:'
700 703 else:
701 704 raise
702 705
706 def shellquote(s):
707 return "'%s'" % s.replace("'", "'\\''")
708
703 709 def testpid(pid):
704 710 '''return False if pid dead, True if running or not sure'''
705 711 try:
@@ -28,3 +28,38 b' echo % one rename'
28 28 hg log -vf a
29 29 echo % many renames
30 30 hg log -vf e
31
32 # log --follow tests
33 hg init ../follow
34 cd ../follow
35 echo base > base
36 hg ci -Ambase -d '1 0'
37
38 echo r1 >> base
39 hg ci -Amr1 -d '1 0'
40 echo r2 >> base
41 hg ci -Amr2 -d '1 0'
42
43 hg up -C 1
44 echo b1 > b1
45 hg ci -Amb1 -d '1 0'
46
47 echo % log -f
48 hg log -f
49
50 hg up -C 0
51 echo b2 > b2
52 hg ci -Amb2 -d '1 0'
53
54 echo % log -f -r 1:tip
55 hg log -f -r 1:tip
56
57 hg up -C 3
58 hg merge tip
59 hg ci -mm12 -d '1 0'
60
61 echo postm >> b1
62 hg ci -Amb1.1 -d'1 0'
63
64 echo % log --follow-first
65 hg log --follow-first
@@ -76,3 +76,76 b' description:'
76 76 a
77 77
78 78
79 adding base
80 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
81 adding b1
82 % log -f
83 changeset: 3:e62f78d544b4
84 tag: tip
85 parent: 1:3d5bf5654eda
86 user: test
87 date: Thu Jan 01 00:00:01 1970 +0000
88 summary: b1
89
90 changeset: 1:3d5bf5654eda
91 user: test
92 date: Thu Jan 01 00:00:01 1970 +0000
93 summary: r1
94
95 changeset: 0:67e992f2c4f3
96 user: test
97 date: Thu Jan 01 00:00:01 1970 +0000
98 summary: base
99
100 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
101 adding b2
102 % log -f -r 1:tip
103 changeset: 1:3d5bf5654eda
104 user: test
105 date: Thu Jan 01 00:00:01 1970 +0000
106 summary: r1
107
108 changeset: 2:60c670bf5b30
109 user: test
110 date: Thu Jan 01 00:00:01 1970 +0000
111 summary: r2
112
113 changeset: 3:e62f78d544b4
114 parent: 1:3d5bf5654eda
115 user: test
116 date: Thu Jan 01 00:00:01 1970 +0000
117 summary: b1
118
119 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
120 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
121 (branch merge, don't forget to commit)
122 % log --follow-first
123 changeset: 6:2404bbcab562
124 tag: tip
125 user: test
126 date: Thu Jan 01 00:00:01 1970 +0000
127 summary: b1.1
128
129 changeset: 5:302e9dd6890d
130 parent: 3:e62f78d544b4
131 parent: 4:ddb82e70d1a1
132 user: test
133 date: Thu Jan 01 00:00:01 1970 +0000
134 summary: m12
135
136 changeset: 3:e62f78d544b4
137 parent: 1:3d5bf5654eda
138 user: test
139 date: Thu Jan 01 00:00:01 1970 +0000
140 summary: b1
141
142 changeset: 1:3d5bf5654eda
143 user: test
144 date: Thu Jan 01 00:00:01 1970 +0000
145 summary: r1
146
147 changeset: 0:67e992f2c4f3
148 user: test
149 date: Thu Jan 01 00:00:01 1970 +0000
150 summary: base
151
General Comments 0
You need to be logged in to leave comments. Login now