##// END OF EJS Templates
mq: new commands qselect, qguard...
Vadim Gelfer -
r2821:2e4ace00 default
parent child Browse files
Show More
@@ -0,0 +1,62 b''
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 b' class queue:'
65 self.series_dirty = 0
65 self.series_dirty = 0
66 self.series_path = "series"
66 self.series_path = "series"
67 self.status_path = "status"
67 self.status_path = "status"
68 self.guards_path = "guards"
69 self.active_guards = None
70 self.guards_dirty = False
68
71
69 if os.path.exists(self.join(self.series_path)):
72 if os.path.exists(self.join(self.series_path)):
70 self.full_series = self.opener(self.series_path).read().splitlines()
73 self.full_series = self.opener(self.series_path).read().splitlines()
71 self.parse_series()
74 self.parse_series()
72
75
73 if os.path.exists(self.join(self.status_path)):
76 if os.path.exists(self.join(self.status_path)):
74 self.applied = [statusentry(l)
77 lines = self.opener(self.status_path).read().splitlines()
75 for l in self.opener(self.status_path).read().splitlines()]
78 self.applied = [statusentry(l) for l in lines]
76
79
77 def join(self, *p):
80 def join(self, *p):
78 return os.path.join(self.path, *p)
81 return os.path.join(self.path, *p)
@@ -90,12 +93,122 b' class queue:'
90 index += 1
93 index += 1
91 return None
94 return None
92
95
96 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
97
93 def parse_series(self):
98 def parse_series(self):
94 self.series = []
99 self.series = []
100 self.series_guards = []
95 for l in self.full_series:
101 for l in self.full_series:
96 s = l.split('#', 1)[0].strip()
102 h = l.find('#')
97 if s:
103 if h == -1:
98 self.series.append(s)
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 def save_dirty(self):
213 def save_dirty(self):
101 def write_list(items, path):
214 def write_list(items, path):
@@ -105,6 +218,7 b' class queue:'
105 fp.close()
218 fp.close()
106 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
219 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
107 if self.series_dirty: write_list(self.full_series, self.series_path)
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 def readheaders(self, patch):
223 def readheaders(self, patch):
110 def eatdiff(lines):
224 def eatdiff(lines):
@@ -257,7 +371,10 b' class queue:'
257 if not patch:
371 if not patch:
258 self.ui.warn("patch %s does not exist\n" % patch)
372 self.ui.warn("patch %s does not exist\n" % patch)
259 return (1, None)
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 info = mergeq.isapplied(patch)
378 info = mergeq.isapplied(patch)
262 if not info:
379 if not info:
263 self.ui.warn("patch %s is not applied\n" % patch)
380 self.ui.warn("patch %s is not applied\n" % patch)
@@ -321,6 +438,10 b' class queue:'
321 tr = repo.transaction()
438 tr = repo.transaction()
322 n = None
439 n = None
323 for patch in series:
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 self.ui.warn("applying %s\n" % patch)
445 self.ui.warn("applying %s\n" % patch)
325 pf = os.path.join(patchdir, patch)
446 pf = os.path.join(patchdir, patch)
326
447
@@ -639,8 +760,7 b' class queue:'
639 pass
760 pass
640 else:
761 else:
641 if sno < len(self.series):
762 if sno < len(self.series):
642 patch = self.series[sno]
763 return self.series[sno]
643 return patch
644 if not strict:
764 if not strict:
645 # return any partial match made above
765 # return any partial match made above
646 if res:
766 if res:
@@ -926,18 +1046,26 b' class queue:'
926 start = self.series_end()
1046 start = self.series_end()
927 else:
1047 else:
928 start = self.series.index(patch) + 1
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 def qseries(self, repo, missing=None, summary=False):
1057 def qseries(self, repo, missing=None, summary=False):
932 start = self.series_end()
1058 start = self.series_end(all_patches=True)
933 if not missing:
1059 if not missing:
934 for i in range(len(self.series)):
1060 for i in range(len(self.series)):
935 patch = self.series[i]
1061 patch = self.series[i]
936 if self.ui.verbose:
1062 if self.ui.verbose:
937 if i < start:
1063 if i < start:
938 status = 'A'
1064 status = 'A'
1065 elif self.pushable(i)[0]:
1066 status = 'U'
939 else:
1067 else:
940 status = 'U'
1068 status = 'G'
941 self.ui.write('%d %s ' % (i, status))
1069 self.ui.write('%d %s ' % (i, status))
942 if summary:
1070 if summary:
943 msg = self.readheaders(patch)[0]
1071 msg = self.readheaders(patch)[0]
@@ -1060,16 +1188,27 b' class queue:'
1060 return end + 1
1188 return end + 1
1061 return 0
1189 return 0
1062
1190
1063 def series_end(self):
1191 def series_end(self, all_patches=False):
1064 end = 0
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 if len(self.applied) > 0:
1204 if len(self.applied) > 0:
1066 p = self.applied[-1].name
1205 p = self.applied[-1].name
1067 try:
1206 try:
1068 end = self.series.index(p)
1207 end = self.series.index(p)
1069 except ValueError:
1208 except ValueError:
1070 return 0
1209 return 0
1071 return end + 1
1210 return next(end + 1)
1072 return end
1211 return next(end)
1073
1212
1074 def qapplied(self, repo, patch=None):
1213 def qapplied(self, repo, patch=None):
1075 if patch and patch not in self.series:
1214 if patch and patch not in self.series:
@@ -1372,6 +1511,51 b' def fold(ui, repo, *files, **opts):'
1372
1511
1373 q.save_dirty()
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 def header(ui, repo, patch=None):
1559 def header(ui, repo, patch=None):
1376 """Print the header of the topmost or specified patch"""
1560 """Print the header of the topmost or specified patch"""
1377 q = repo.mq
1561 q = repo.mq
@@ -1546,6 +1730,69 b' def strip(ui, repo, rev, **opts):'
1546 repo.mq.strip(repo, rev, backup=backup)
1730 repo.mq.strip(repo, rev, backup=backup)
1547 return 0
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 def version(ui, q=None):
1796 def version(ui, q=None):
1550 """print the version number of the mq extension"""
1797 """print the version number of the mq extension"""
1551 ui.write("mq version %s\n" % versionstr)
1798 ui.write("mq version %s\n" % versionstr)
@@ -1605,6 +1852,9 b' cmdtable = {'
1605 ('m', 'message', '', _('set patch header to <text>')),
1852 ('m', 'message', '', _('set patch header to <text>')),
1606 ('l', 'logfile', '', _('set patch header to contents of <file>'))],
1853 ('l', 'logfile', '', _('set patch header to contents of <file>'))],
1607 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
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 'qheader': (header, [],
1858 'qheader': (header, [],
1609 _('hg qheader [PATCH]')),
1859 _('hg qheader [PATCH]')),
1610 "^qimport":
1860 "^qimport":
@@ -1662,6 +1912,10 b' cmdtable = {'
1662 ('e', 'empty', None, 'clear queue status file'),
1912 ('e', 'empty', None, 'clear queue status file'),
1663 ('f', 'force', None, 'force copy')],
1913 ('f', 'force', None, 'force copy')],
1664 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
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 "qseries":
1919 "qseries":
1666 (series,
1920 (series,
1667 [('m', 'missing', None, 'print patches not in series'),
1921 [('m', 'missing', None, 'print patches not in series'),
@@ -30,6 +30,7 b' list of commands (use "hg help -v mq" to'
30 qdelete remove a patch from the series file
30 qdelete remove a patch from the series file
31 qdiff diff of the current patch
31 qdiff diff of the current patch
32 qfold fold the named patches into the current patch
32 qfold fold the named patches into the current patch
33 qguard set or print guards for a patch
33 qheader Print the header of the topmost or specified patch
34 qheader Print the header of the topmost or specified patch
34 qimport import a patch
35 qimport import a patch
35 qinit init a new queue repository
36 qinit init a new queue repository
@@ -42,6 +43,7 b' list of commands (use "hg help -v mq" to'
42 qrename rename a patch
43 qrename rename a patch
43 qrestore restore the queue state saved by a rev
44 qrestore restore the queue state saved by a rev
44 qsave save current queue state
45 qsave save current queue state
46 qselect set or print guarded patches to push
45 qseries print the entire series file
47 qseries print the entire series file
46 qtop print the name of the current patch
48 qtop print the name of the current patch
47 qunapplied print the patches not yet applied
49 qunapplied print the patches not yet applied
General Comments 0
You need to be logged in to leave comments. Login now