63 self.full_series = [] |
63 self.full_series = [] |
64 self.applied_dirty = 0 |
64 self.applied_dirty = 0 |
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) |
79 |
82 |
80 def find_series(self, patch): |
83 def find_series(self, patch): |
88 if s == patch: |
91 if s == patch: |
89 return index |
92 return index |
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): |
102 fp = self.opener(path, 'w') |
215 fp = self.opener(path, 'w') |
103 for i in items: |
216 for i in items: |
104 print >> fp, i |
217 print >> fp, i |
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): |
111 while lines: |
225 while lines: |
112 l = lines[-1] |
226 l = lines[-1] |
924 raise util.Abort(_("patch %s is not in series file") % patch) |
1044 raise util.Abort(_("patch %s is not in series file") % patch) |
925 if not patch: |
1045 if not patch: |
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] |
944 msg = msg and ': ' + msg[0] or ': ' |
1072 msg = msg and ': ' + msg[0] or ': ' |
945 else: |
1073 else: |
1058 if end == None: |
1186 if end == None: |
1059 return len(self.full_series) |
1187 return len(self.full_series) |
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: |
1076 raise util.Abort(_("patch %s is not in series file") % patch) |
1215 raise util.Abort(_("patch %s is not in series file") % patch) |
1077 if not patch: |
1216 if not patch: |
1370 for patch in patches: |
1509 for patch in patches: |
1371 q.delete(repo, patch, force=opts['force']) |
1510 q.delete(repo, patch, force=opts['force']) |
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 |
1378 |
1562 |
1379 if patch: |
1563 if patch: |
1544 elif opts['nobackup']: |
1728 elif opts['nobackup']: |
1545 backup = 'none' |
1729 backup = 'none' |
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) |
1552 return 0 |
1799 return 0 |
1553 |
1800 |
1603 [('e', 'edit', None, _('edit patch header')), |
1850 [('e', 'edit', None, _('edit patch header')), |
1604 ('f', 'force', None, _('delete folded patch files')), |
1851 ('f', 'force', None, _('delete folded patch files')), |
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": |
1611 (qimport, |
1861 (qimport, |
1612 [('e', 'existing', None, 'import file in patch dir'), |
1862 [('e', 'existing', None, 'import file in patch dir'), |
1660 ('c', 'copy', None, 'copy patch directory'), |
1910 ('c', 'copy', None, 'copy patch directory'), |
1661 ('n', 'name', '', 'copy directory name'), |
1911 ('n', 'name', '', 'copy directory name'), |
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'), |
1668 ('s', 'summary', None, _('print first line of patch header'))], |
1922 ('s', 'summary', None, _('print first line of patch header'))], |
1669 'hg qseries [-m]'), |
1923 'hg qseries [-m]'), |