##// END OF EJS Templates
mq: new commands qselect, qguard...
Vadim Gelfer -
r2821:2e4ace00 default
parent child Browse files
Show More
@@ -0,0 +1,62
1 #!/bin/sh
2
3 HGRCPATH=$HGTMP/.hgrc; export HGRCPATH
4 echo "[extensions]" >> $HGTMP/.hgrc
5 echo "mq=" >> $HGTMP/.hgrc
6
7 hg init
8 hg qinit
9
10 echo x > x
11 hg ci -Ama
12
13 hg qnew a.patch
14 echo a > a
15 hg add a
16 hg qrefresh
17
18 hg qnew b.patch
19 echo b > b
20 hg add b
21 hg qrefresh
22
23 hg qnew c.patch
24 echo c > c
25 hg add c
26 hg qrefresh
27
28 hg qpop -a
29
30 echo % should fail
31 hg qguard +fail
32
33 hg qpush
34 echo % should guard a.patch
35 hg qguard +a
36 echo % should print +a
37 hg qguard
38 hg qpop
39
40 hg qguard a.patch
41 echo % should push b.patch
42 hg qpush
43
44 hg qpop
45 hg qselect a
46 echo % should push a.patch
47 hg qpush
48
49 hg qguard c.patch -a
50 echo % should print -a
51 hg qguard c.patch
52
53 echo % should skip c.patch
54 hg qpush -a
55
56 hg qguard -n c.patch
57 echo % should push c.patch
58 hg qpush -a
59
60 hg qpop -a
61 hg qselect -n
62 hg qpush -a
@@ -65,14 +65,17 class queue:
65 65 self.series_dirty = 0
66 66 self.series_path = "series"
67 67 self.status_path = "status"
68 self.guards_path = "guards"
69 self.active_guards = None
70 self.guards_dirty = False
68 71
69 72 if os.path.exists(self.join(self.series_path)):
70 73 self.full_series = self.opener(self.series_path).read().splitlines()
71 74 self.parse_series()
72 75
73 76 if os.path.exists(self.join(self.status_path)):
74 self.applied = [statusentry(l)
75 for l in self.opener(self.status_path).read().splitlines()]
77 lines = self.opener(self.status_path).read().splitlines()
78 self.applied = [statusentry(l) for l in lines]
76 79
77 80 def join(self, *p):
78 81 return os.path.join(self.path, *p)
@@ -90,12 +93,122 class queue:
90 93 index += 1
91 94 return None
92 95
96 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
97
93 98 def parse_series(self):
94 99 self.series = []
100 self.series_guards = []
95 101 for l in self.full_series:
96 s = l.split('#', 1)[0].strip()
97 if s:
98 self.series.append(s)
102 h = l.find('#')
103 if h == -1:
104 patch = l
105 comment = ''
106 elif h == 0:
107 continue
108 else:
109 patch = l[:h]
110 comment = l[h:]
111 patch = patch.strip()
112 if patch:
113 self.series.append(patch)
114 self.series_guards.append(self.guard_re.findall(comment))
115
116 def check_guard(self, guard):
117 bad_chars = '# \t\r\n\f'
118 first = guard[0]
119 for c in '-+':
120 if first == c:
121 return (_('guard %r starts with invalid character: %r') %
122 (guard, c))
123 for c in bad_chars:
124 if c in guard:
125 return _('invalid character in guard %r: %r') % (guard, c)
126
127 def set_active(self, guards):
128 for guard in guards:
129 bad = self.check_guard(guard)
130 if bad:
131 raise util.Abort(bad)
132 guards = dict.fromkeys(guards).keys()
133 guards.sort()
134 self.ui.debug('active guards: %s\n' % ' '.join(guards))
135 self.active_guards = guards
136 self.guards_dirty = True
137
138 def active(self):
139 if self.active_guards is None:
140 self.active_guards = []
141 try:
142 guards = self.opener(self.guards_path).read().split()
143 except IOError, err:
144 if err.errno != errno.ENOENT: raise
145 guards = []
146 for i, guard in enumerate(guards):
147 bad = self.check_guard(guard)
148 if bad:
149 self.ui.warn('%s:%d: %s\n' %
150 (self.join(self.guards_path), i + 1, bad))
151 else:
152 self.active_guards.append(guard)
153 return self.active_guards
154
155 def set_guards(self, idx, guards):
156 for g in guards:
157 if len(g) < 2:
158 raise util.Abort(_('guard %r too short') % g)
159 if g[0] not in '-+':
160 raise util.Abort(_('guard %r starts with invalid char') % g)
161 bad = self.check_guard(g[1:])
162 if bad:
163 raise util.Abort(bad)
164 drop = self.guard_re.sub('', self.full_series[idx])
165 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
166 self.parse_series()
167 self.series_dirty = True
168
169 def pushable(self, idx):
170 if isinstance(idx, str):
171 idx = self.series.index(idx)
172 patchguards = self.series_guards[idx]
173 if not patchguards:
174 return True, None
175 default = False
176 guards = self.active()
177 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
178 if exactneg:
179 return False, exactneg[0]
180 pos = [g for g in patchguards if g[0] == '+']
181 exactpos = [g for g in pos if g[1:] in guards]
182 if pos:
183 if exactpos:
184 return True, exactpos[0]
185 return False, ''
186 return True, ''
187
188 def explain_pushable(self, idx, all_patches=False):
189 write = all_patches and self.ui.write or self.ui.warn
190 if all_patches or self.ui.verbose:
191 if isinstance(idx, str):
192 idx = self.series.index(idx)
193 pushable, why = self.pushable(idx)
194 if all_patches and pushable:
195 if why is None:
196 write(_('allowing %s - no guards in effect\n') %
197 self.series[idx])
198 else:
199 if not why:
200 write(_('allowing %s - no matching negative guards\n') %
201 self.series[idx])
202 else:
203 write(_('allowing %s - guarded by %r\n') %
204 (self.series[idx], why))
205 if not pushable:
206 if why and why[0] in '-+':
207 write(_('skipping %s - guarded by %r\n') %
208 (self.series[idx], why))
209 else:
210 write(_('skipping %s - no matching guards\n') %
211 self.series[idx])
99 212
100 213 def save_dirty(self):
101 214 def write_list(items, path):
@@ -105,6 +218,7 class queue:
105 218 fp.close()
106 219 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
107 220 if self.series_dirty: write_list(self.full_series, self.series_path)
221 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
108 222
109 223 def readheaders(self, patch):
110 224 def eatdiff(lines):
@@ -257,7 +371,10 class queue:
257 371 if not patch:
258 372 self.ui.warn("patch %s does not exist\n" % patch)
259 373 return (1, None)
260
374 pushable, reason = self.pushable(patch)
375 if not pushable:
376 self.explain_pushable(patch, all_patches=True)
377 continue
261 378 info = mergeq.isapplied(patch)
262 379 if not info:
263 380 self.ui.warn("patch %s is not applied\n" % patch)
@@ -321,6 +438,10 class queue:
321 438 tr = repo.transaction()
322 439 n = None
323 440 for patch in series:
441 pushable, reason = self.pushable(patch)
442 if not pushable:
443 self.explain_pushable(patch, all_patches=True)
444 continue
324 445 self.ui.warn("applying %s\n" % patch)
325 446 pf = os.path.join(patchdir, patch)
326 447
@@ -639,8 +760,7 class queue:
639 760 pass
640 761 else:
641 762 if sno < len(self.series):
642 patch = self.series[sno]
643 return patch
763 return self.series[sno]
644 764 if not strict:
645 765 # return any partial match made above
646 766 if res:
@@ -926,18 +1046,26 class queue:
926 1046 start = self.series_end()
927 1047 else:
928 1048 start = self.series.index(patch) + 1
929 return [(i, self.series[i]) for i in xrange(start, len(self.series))]
1049 unapplied = []
1050 for i in xrange(start, len(self.series)):
1051 pushable, reason = self.pushable(i)
1052 if pushable:
1053 unapplied.append((i, self.series[i]))
1054 self.explain_pushable(i)
1055 return unapplied
930 1056
931 1057 def qseries(self, repo, missing=None, summary=False):
932 start = self.series_end()
1058 start = self.series_end(all_patches=True)
933 1059 if not missing:
934 1060 for i in range(len(self.series)):
935 1061 patch = self.series[i]
936 1062 if self.ui.verbose:
937 1063 if i < start:
938 1064 status = 'A'
1065 elif self.pushable(i)[0]:
1066 status = 'U'
939 1067 else:
940 status = 'U'
1068 status = 'G'
941 1069 self.ui.write('%d %s ' % (i, status))
942 1070 if summary:
943 1071 msg = self.readheaders(patch)[0]
@@ -1060,16 +1188,27 class queue:
1060 1188 return end + 1
1061 1189 return 0
1062 1190
1063 def series_end(self):
1191 def series_end(self, all_patches=False):
1064 1192 end = 0
1193 def next(start):
1194 if all_patches:
1195 return start
1196 i = start
1197 while i < len(self.series):
1198 p, reason = self.pushable(i)
1199 if p:
1200 break
1201 self.explain_pushable(i)
1202 i += 1
1203 return i
1065 1204 if len(self.applied) > 0:
1066 1205 p = self.applied[-1].name
1067 1206 try:
1068 1207 end = self.series.index(p)
1069 1208 except ValueError:
1070 1209 return 0
1071 return end + 1
1072 return end
1210 return next(end + 1)
1211 return next(end)
1073 1212
1074 1213 def qapplied(self, repo, patch=None):
1075 1214 if patch and patch not in self.series:
@@ -1372,6 +1511,51 def fold(ui, repo, *files, **opts):
1372 1511
1373 1512 q.save_dirty()
1374 1513
1514 def guard(ui, repo, *args, **opts):
1515 '''set or print guards for a patch
1516
1517 guards control whether a patch can be pushed. a patch with no
1518 guards is aways pushed. a patch with posative guard ("+foo") is
1519 pushed only if qselect command enables guard "foo". a patch with
1520 nagative guard ("-foo") is never pushed if qselect command enables
1521 guard "foo".
1522
1523 with no arguments, default is to print current active guards.
1524 with arguments, set active guards for patch.
1525
1526 to set nagative guard "-foo" on topmost patch ("--" is needed so
1527 hg will not interpret "-foo" as argument):
1528 hg qguard -- -foo
1529
1530 to set guards on other patch:
1531 hg qguard other.patch +2.6.17 -stable
1532 '''
1533 def status(idx):
1534 guards = q.series_guards[idx] or ['unguarded']
1535 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1536 q = repo.mq
1537 patch = None
1538 args = list(args)
1539 if opts['list']:
1540 if args or opts['none']:
1541 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1542 for i in xrange(len(q.series)):
1543 status(i)
1544 return
1545 if not args or args[0][0:1] in '-+':
1546 if not q.applied:
1547 raise util.Abort(_('no patches applied'))
1548 patch = q.applied[-1].name
1549 if patch is None and args[0][0:1] not in '-+':
1550 patch = args.pop(0)
1551 if patch is None:
1552 raise util.Abort(_('no patch to work with'))
1553 if args or opts['none']:
1554 q.set_guards(q.find_series(patch), args)
1555 q.save_dirty()
1556 else:
1557 status(q.series.index(q.lookup(patch)))
1558
1375 1559 def header(ui, repo, patch=None):
1376 1560 """Print the header of the topmost or specified patch"""
1377 1561 q = repo.mq
@@ -1546,6 +1730,69 def strip(ui, repo, rev, **opts):
1546 1730 repo.mq.strip(repo, rev, backup=backup)
1547 1731 return 0
1548 1732
1733 def select(ui, repo, *args, **opts):
1734 '''set or print guarded patches to push
1735
1736 use qguard command to set or print guards on patch. then use
1737 qselect to tell mq which guards to use. example:
1738
1739 qguard foo.patch -stable (nagative guard)
1740 qguard bar.patch +stable (posative guard)
1741 qselect stable
1742
1743 this sets "stable" guard. mq will skip foo.patch (because it has
1744 nagative match) but push bar.patch (because it has posative
1745 match).
1746
1747 with no arguments, default is to print current active guards.
1748 with arguments, set active guards as given.
1749
1750 use -n/--none to deactivate guards (no other arguments needed).
1751 when no guards active, patches with posative guards are skipped,
1752 patches with nagative guards are pushed.
1753
1754 use -s/--series to print list of all guards in series file (no
1755 other arguments needed). use -v for more information.'''
1756
1757 q = repo.mq
1758 guards = q.active()
1759 if args or opts['none']:
1760 q.set_active(args)
1761 q.save_dirty()
1762 if not args:
1763 ui.status(_('guards deactivated\n'))
1764 if q.series:
1765 pushable = [p for p in q.unapplied(repo) if q.pushable(p[0])[0]]
1766 ui.status(_('%d of %d unapplied patches active\n') %
1767 (len(pushable), len(q.series)))
1768 elif opts['series']:
1769 guards = {}
1770 noguards = 0
1771 for gs in q.series_guards:
1772 if not gs:
1773 noguards += 1
1774 for g in gs:
1775 guards.setdefault(g, 0)
1776 guards[g] += 1
1777 if ui.verbose:
1778 guards['NONE'] = noguards
1779 guards = guards.items()
1780 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
1781 if guards:
1782 ui.note(_('guards in series file:\n'))
1783 for guard, count in guards:
1784 ui.note('%2d ' % count)
1785 ui.write(guard, '\n')
1786 else:
1787 ui.note(_('no guards in series file\n'))
1788 else:
1789 if guards:
1790 ui.note(_('active guards:\n'))
1791 for g in guards:
1792 ui.write(g, '\n')
1793 else:
1794 ui.write(_('no active guards\n'))
1795
1549 1796 def version(ui, q=None):
1550 1797 """print the version number of the mq extension"""
1551 1798 ui.write("mq version %s\n" % versionstr)
@@ -1605,6 +1852,9 cmdtable = {
1605 1852 ('m', 'message', '', _('set patch header to <text>')),
1606 1853 ('l', 'logfile', '', _('set patch header to contents of <file>'))],
1607 1854 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
1855 'qguard': (guard, [('l', 'list', None, _('list all patches and guards')),
1856 ('n', 'none', None, _('drop all guards'))],
1857 'hg qguard [PATCH] [+GUARD...] [-GUARD...]'),
1608 1858 'qheader': (header, [],
1609 1859 _('hg qheader [PATCH]')),
1610 1860 "^qimport":
@@ -1662,6 +1912,10 cmdtable = {
1662 1912 ('e', 'empty', None, 'clear queue status file'),
1663 1913 ('f', 'force', None, 'force copy')],
1664 1914 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
1915 "qselect": (select,
1916 [('n', 'none', None, _('disable all guards')),
1917 ('s', 'series', None, _('list all guards in series file'))],
1918 'hg qselect [GUARDS]'),
1665 1919 "qseries":
1666 1920 (series,
1667 1921 [('m', 'missing', None, 'print patches not in series'),
@@ -30,6 +30,7 list of commands (use "hg help -v mq" to
30 30 qdelete remove a patch from the series file
31 31 qdiff diff of the current patch
32 32 qfold fold the named patches into the current patch
33 qguard set or print guards for a patch
33 34 qheader Print the header of the topmost or specified patch
34 35 qimport import a patch
35 36 qinit init a new queue repository
@@ -42,6 +43,7 list of commands (use "hg help -v mq" to
42 43 qrename rename a patch
43 44 qrestore restore the queue state saved by a rev
44 45 qsave save current queue state
46 qselect set or print guarded patches to push
45 47 qseries print the entire series file
46 48 qtop print the name of the current patch
47 49 qunapplied print the patches not yet applied
General Comments 0
You need to be logged in to leave comments. Login now