Show More
@@ -1,270 +1,266 b'' | |||||
1 | # color.py color output for the status and qseries commands |
|
1 | # color.py color output for the status and qseries commands | |
2 | # |
|
2 | # | |
3 | # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com> |
|
3 | # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com> | |
4 | # |
|
4 | # | |
5 | # This program is free software; you can redistribute it and/or modify it |
|
5 | # This program is free software; you can redistribute it and/or modify it | |
6 | # under the terms of the GNU General Public License as published by the |
|
6 | # under the terms of the GNU General Public License as published by the | |
7 | # Free Software Foundation; either version 2 of the License, or (at your |
|
7 | # Free Software Foundation; either version 2 of the License, or (at your | |
8 | # option) any later version. |
|
8 | # option) any later version. | |
9 | # |
|
9 | # | |
10 | # This program is distributed in the hope that it will be useful, but |
|
10 | # This program is distributed in the hope that it will be useful, but | |
11 | # WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | # WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General |
|
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General | |
13 | # Public License for more details. |
|
13 | # Public License for more details. | |
14 | # |
|
14 | # | |
15 | # You should have received a copy of the GNU General Public License along |
|
15 | # You should have received a copy of the GNU General Public License along | |
16 | # with this program; if not, write to the Free Software Foundation, Inc., |
|
16 | # with this program; if not, write to the Free Software Foundation, Inc., | |
17 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
|
17 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
18 |
|
18 | |||
19 | '''add color output to status, qseries, and diff-related commands |
|
19 | '''add color output to status, qseries, and diff-related commands | |
20 |
|
20 | |||
21 | This extension modifies the status command to add color to its output |
|
21 | This extension modifies the status command to add color to its output | |
22 | to reflect file status, the qseries command to add color to reflect |
|
22 | to reflect file status, the qseries command to add color to reflect | |
23 | patch status (applied, unapplied, missing), and to diff-related |
|
23 | patch status (applied, unapplied, missing), and to diff-related | |
24 | commands to highlight additions, removals, diff headers, and trailing |
|
24 | commands to highlight additions, removals, diff headers, and trailing | |
25 | whitespace. |
|
25 | whitespace. | |
26 |
|
26 | |||
27 | Other effects in addition to color, like bold and underlined text, are |
|
27 | Other effects in addition to color, like bold and underlined text, are | |
28 | also available. Effects are rendered with the ECMA-48 SGR control |
|
28 | also available. Effects are rendered with the ECMA-48 SGR control | |
29 | function (aka ANSI escape codes). This module also provides the |
|
29 | function (aka ANSI escape codes). This module also provides the | |
30 | render_text function, which can be used to add effects to any text. |
|
30 | render_text function, which can be used to add effects to any text. | |
31 |
|
31 | |||
32 | To enable this extension, add this to your .hgrc file: |
|
|||
33 | [extensions] |
|
|||
34 | color = |
|
|||
35 |
|
||||
36 | Default effects may be overridden from the .hgrc file: |
|
32 | Default effects may be overridden from the .hgrc file: | |
37 |
|
33 | |||
38 | [color] |
|
34 | [color] | |
39 | status.modified = blue bold underline red_background |
|
35 | status.modified = blue bold underline red_background | |
40 | status.added = green bold |
|
36 | status.added = green bold | |
41 | status.removed = red bold blue_background |
|
37 | status.removed = red bold blue_background | |
42 | status.deleted = cyan bold underline |
|
38 | status.deleted = cyan bold underline | |
43 | status.unknown = magenta bold underline |
|
39 | status.unknown = magenta bold underline | |
44 | status.ignored = black bold |
|
40 | status.ignored = black bold | |
45 |
|
41 | |||
46 | # 'none' turns off all effects |
|
42 | # 'none' turns off all effects | |
47 | status.clean = none |
|
43 | status.clean = none | |
48 | status.copied = none |
|
44 | status.copied = none | |
49 |
|
45 | |||
50 | qseries.applied = blue bold underline |
|
46 | qseries.applied = blue bold underline | |
51 | qseries.unapplied = black bold |
|
47 | qseries.unapplied = black bold | |
52 | qseries.missing = red bold |
|
48 | qseries.missing = red bold | |
53 |
|
49 | |||
54 | diff.diffline = bold |
|
50 | diff.diffline = bold | |
55 | diff.extended = cyan bold |
|
51 | diff.extended = cyan bold | |
56 | diff.file_a = red bold |
|
52 | diff.file_a = red bold | |
57 | diff.file_b = green bold |
|
53 | diff.file_b = green bold | |
58 | diff.hunk = magenta |
|
54 | diff.hunk = magenta | |
59 | diff.deleted = red |
|
55 | diff.deleted = red | |
60 | diff.inserted = green |
|
56 | diff.inserted = green | |
61 | diff.changed = white |
|
57 | diff.changed = white | |
62 | diff.trailingwhitespace = bold red_background |
|
58 | diff.trailingwhitespace = bold red_background | |
63 | ''' |
|
59 | ''' | |
64 |
|
60 | |||
65 | import os, sys |
|
61 | import os, sys | |
66 |
|
62 | |||
67 | from mercurial import cmdutil, commands, extensions |
|
63 | from mercurial import cmdutil, commands, extensions | |
68 | from mercurial.i18n import _ |
|
64 | from mercurial.i18n import _ | |
69 |
|
65 | |||
70 | # start and stop parameters for effects |
|
66 | # start and stop parameters for effects | |
71 | _effect_params = {'none': 0, |
|
67 | _effect_params = {'none': 0, | |
72 | 'black': 30, |
|
68 | 'black': 30, | |
73 | 'red': 31, |
|
69 | 'red': 31, | |
74 | 'green': 32, |
|
70 | 'green': 32, | |
75 | 'yellow': 33, |
|
71 | 'yellow': 33, | |
76 | 'blue': 34, |
|
72 | 'blue': 34, | |
77 | 'magenta': 35, |
|
73 | 'magenta': 35, | |
78 | 'cyan': 36, |
|
74 | 'cyan': 36, | |
79 | 'white': 37, |
|
75 | 'white': 37, | |
80 | 'bold': 1, |
|
76 | 'bold': 1, | |
81 | 'italic': 3, |
|
77 | 'italic': 3, | |
82 | 'underline': 4, |
|
78 | 'underline': 4, | |
83 | 'inverse': 7, |
|
79 | 'inverse': 7, | |
84 | 'black_background': 40, |
|
80 | 'black_background': 40, | |
85 | 'red_background': 41, |
|
81 | 'red_background': 41, | |
86 | 'green_background': 42, |
|
82 | 'green_background': 42, | |
87 | 'yellow_background': 43, |
|
83 | 'yellow_background': 43, | |
88 | 'blue_background': 44, |
|
84 | 'blue_background': 44, | |
89 | 'purple_background': 45, |
|
85 | 'purple_background': 45, | |
90 | 'cyan_background': 46, |
|
86 | 'cyan_background': 46, | |
91 | 'white_background': 47} |
|
87 | 'white_background': 47} | |
92 |
|
88 | |||
93 | def render_effects(text, effects): |
|
89 | def render_effects(text, effects): | |
94 | 'Wrap text in commands to turn on each effect.' |
|
90 | 'Wrap text in commands to turn on each effect.' | |
95 | start = [str(_effect_params[e]) for e in ['none'] + effects] |
|
91 | start = [str(_effect_params[e]) for e in ['none'] + effects] | |
96 | start = '\033[' + ';'.join(start) + 'm' |
|
92 | start = '\033[' + ';'.join(start) + 'm' | |
97 | stop = '\033[' + str(_effect_params['none']) + 'm' |
|
93 | stop = '\033[' + str(_effect_params['none']) + 'm' | |
98 | return ''.join([start, text, stop]) |
|
94 | return ''.join([start, text, stop]) | |
99 |
|
95 | |||
100 | def colorstatus(orig, ui, repo, *pats, **opts): |
|
96 | def colorstatus(orig, ui, repo, *pats, **opts): | |
101 | '''run the status command with colored output''' |
|
97 | '''run the status command with colored output''' | |
102 |
|
98 | |||
103 | delimiter = opts['print0'] and '\0' or '\n' |
|
99 | delimiter = opts['print0'] and '\0' or '\n' | |
104 |
|
100 | |||
105 | nostatus = opts.get('no_status') |
|
101 | nostatus = opts.get('no_status') | |
106 | opts['no_status'] = False |
|
102 | opts['no_status'] = False | |
107 | # run status and capture its output |
|
103 | # run status and capture its output | |
108 | ui.pushbuffer() |
|
104 | ui.pushbuffer() | |
109 | retval = orig(ui, repo, *pats, **opts) |
|
105 | retval = orig(ui, repo, *pats, **opts) | |
110 | # filter out empty strings |
|
106 | # filter out empty strings | |
111 | lines_with_status = [ line for line in ui.popbuffer().split(delimiter) if line ] |
|
107 | lines_with_status = [ line for line in ui.popbuffer().split(delimiter) if line ] | |
112 |
|
108 | |||
113 | if nostatus: |
|
109 | if nostatus: | |
114 | lines = [l[2:] for l in lines_with_status] |
|
110 | lines = [l[2:] for l in lines_with_status] | |
115 | else: |
|
111 | else: | |
116 | lines = lines_with_status |
|
112 | lines = lines_with_status | |
117 |
|
113 | |||
118 | # apply color to output and display it |
|
114 | # apply color to output and display it | |
119 | for i in xrange(len(lines)): |
|
115 | for i in xrange(len(lines)): | |
120 | status = _status_abbreviations[lines_with_status[i][0]] |
|
116 | status = _status_abbreviations[lines_with_status[i][0]] | |
121 | effects = _status_effects[status] |
|
117 | effects = _status_effects[status] | |
122 | if effects: |
|
118 | if effects: | |
123 | lines[i] = render_effects(lines[i], effects) |
|
119 | lines[i] = render_effects(lines[i], effects) | |
124 | ui.write(lines[i] + delimiter) |
|
120 | ui.write(lines[i] + delimiter) | |
125 | return retval |
|
121 | return retval | |
126 |
|
122 | |||
127 | _status_abbreviations = { 'M': 'modified', |
|
123 | _status_abbreviations = { 'M': 'modified', | |
128 | 'A': 'added', |
|
124 | 'A': 'added', | |
129 | 'R': 'removed', |
|
125 | 'R': 'removed', | |
130 | '!': 'deleted', |
|
126 | '!': 'deleted', | |
131 | '?': 'unknown', |
|
127 | '?': 'unknown', | |
132 | 'I': 'ignored', |
|
128 | 'I': 'ignored', | |
133 | 'C': 'clean', |
|
129 | 'C': 'clean', | |
134 | ' ': 'copied', } |
|
130 | ' ': 'copied', } | |
135 |
|
131 | |||
136 | _status_effects = { 'modified': ['blue', 'bold'], |
|
132 | _status_effects = { 'modified': ['blue', 'bold'], | |
137 | 'added': ['green', 'bold'], |
|
133 | 'added': ['green', 'bold'], | |
138 | 'removed': ['red', 'bold'], |
|
134 | 'removed': ['red', 'bold'], | |
139 | 'deleted': ['cyan', 'bold', 'underline'], |
|
135 | 'deleted': ['cyan', 'bold', 'underline'], | |
140 | 'unknown': ['magenta', 'bold', 'underline'], |
|
136 | 'unknown': ['magenta', 'bold', 'underline'], | |
141 | 'ignored': ['black', 'bold'], |
|
137 | 'ignored': ['black', 'bold'], | |
142 | 'clean': ['none'], |
|
138 | 'clean': ['none'], | |
143 | 'copied': ['none'], } |
|
139 | 'copied': ['none'], } | |
144 |
|
140 | |||
145 | def colorqseries(orig, ui, repo, *dummy, **opts): |
|
141 | def colorqseries(orig, ui, repo, *dummy, **opts): | |
146 | '''run the qseries command with colored output''' |
|
142 | '''run the qseries command with colored output''' | |
147 | ui.pushbuffer() |
|
143 | ui.pushbuffer() | |
148 | retval = orig(ui, repo, **opts) |
|
144 | retval = orig(ui, repo, **opts) | |
149 | patches = ui.popbuffer().splitlines() |
|
145 | patches = ui.popbuffer().splitlines() | |
150 | for patch in patches: |
|
146 | for patch in patches: | |
151 | patchname = patch |
|
147 | patchname = patch | |
152 | if opts['summary']: |
|
148 | if opts['summary']: | |
153 | patchname = patchname.split(': ')[0] |
|
149 | patchname = patchname.split(': ')[0] | |
154 | if ui.verbose: |
|
150 | if ui.verbose: | |
155 | patchname = patchname.split(' ', 2)[-1] |
|
151 | patchname = patchname.split(' ', 2)[-1] | |
156 |
|
152 | |||
157 | if opts['missing']: |
|
153 | if opts['missing']: | |
158 | effects = _patch_effects['missing'] |
|
154 | effects = _patch_effects['missing'] | |
159 | # Determine if patch is applied. |
|
155 | # Determine if patch is applied. | |
160 | elif [ applied for applied in repo.mq.applied |
|
156 | elif [ applied for applied in repo.mq.applied | |
161 | if patchname == applied.name ]: |
|
157 | if patchname == applied.name ]: | |
162 | effects = _patch_effects['applied'] |
|
158 | effects = _patch_effects['applied'] | |
163 | else: |
|
159 | else: | |
164 | effects = _patch_effects['unapplied'] |
|
160 | effects = _patch_effects['unapplied'] | |
165 | ui.write(render_effects(patch, effects) + '\n') |
|
161 | ui.write(render_effects(patch, effects) + '\n') | |
166 | return retval |
|
162 | return retval | |
167 |
|
163 | |||
168 | _patch_effects = { 'applied': ['blue', 'bold', 'underline'], |
|
164 | _patch_effects = { 'applied': ['blue', 'bold', 'underline'], | |
169 | 'missing': ['red', 'bold'], |
|
165 | 'missing': ['red', 'bold'], | |
170 | 'unapplied': ['black', 'bold'], } |
|
166 | 'unapplied': ['black', 'bold'], } | |
171 |
|
167 | |||
172 | def colorwrap(orig, s): |
|
168 | def colorwrap(orig, s): | |
173 | '''wrap ui.write for colored diff output''' |
|
169 | '''wrap ui.write for colored diff output''' | |
174 | lines = s.split('\n') |
|
170 | lines = s.split('\n') | |
175 | for i, line in enumerate(lines): |
|
171 | for i, line in enumerate(lines): | |
176 | stripline = line |
|
172 | stripline = line | |
177 | if line and line[0] in '+-': |
|
173 | if line and line[0] in '+-': | |
178 | # highlight trailing whitespace, but only in changed lines |
|
174 | # highlight trailing whitespace, but only in changed lines | |
179 | stripline = line.rstrip() |
|
175 | stripline = line.rstrip() | |
180 | for prefix, style in _diff_prefixes: |
|
176 | for prefix, style in _diff_prefixes: | |
181 | if stripline.startswith(prefix): |
|
177 | if stripline.startswith(prefix): | |
182 | lines[i] = render_effects(stripline, _diff_effects[style]) |
|
178 | lines[i] = render_effects(stripline, _diff_effects[style]) | |
183 | break |
|
179 | break | |
184 | if line != stripline: |
|
180 | if line != stripline: | |
185 | lines[i] += render_effects( |
|
181 | lines[i] += render_effects( | |
186 | line[len(stripline):], _diff_effects['trailingwhitespace']) |
|
182 | line[len(stripline):], _diff_effects['trailingwhitespace']) | |
187 | orig('\n'.join(lines)) |
|
183 | orig('\n'.join(lines)) | |
188 |
|
184 | |||
189 | def colorshowpatch(orig, self, node): |
|
185 | def colorshowpatch(orig, self, node): | |
190 | '''wrap cmdutil.changeset_printer.showpatch with colored output''' |
|
186 | '''wrap cmdutil.changeset_printer.showpatch with colored output''' | |
191 | oldwrite = extensions.wrapfunction(self.ui, 'write', colorwrap) |
|
187 | oldwrite = extensions.wrapfunction(self.ui, 'write', colorwrap) | |
192 | try: |
|
188 | try: | |
193 | orig(self, node) |
|
189 | orig(self, node) | |
194 | finally: |
|
190 | finally: | |
195 | self.ui.write = oldwrite |
|
191 | self.ui.write = oldwrite | |
196 |
|
192 | |||
197 | def colordiff(orig, ui, repo, *pats, **opts): |
|
193 | def colordiff(orig, ui, repo, *pats, **opts): | |
198 | '''run the diff command with colored output''' |
|
194 | '''run the diff command with colored output''' | |
199 | oldwrite = extensions.wrapfunction(ui, 'write', colorwrap) |
|
195 | oldwrite = extensions.wrapfunction(ui, 'write', colorwrap) | |
200 | try: |
|
196 | try: | |
201 | orig(ui, repo, *pats, **opts) |
|
197 | orig(ui, repo, *pats, **opts) | |
202 | finally: |
|
198 | finally: | |
203 | ui.write = oldwrite |
|
199 | ui.write = oldwrite | |
204 |
|
200 | |||
205 | _diff_prefixes = [('diff', 'diffline'), |
|
201 | _diff_prefixes = [('diff', 'diffline'), | |
206 | ('copy', 'extended'), |
|
202 | ('copy', 'extended'), | |
207 | ('rename', 'extended'), |
|
203 | ('rename', 'extended'), | |
208 | ('old', 'extended'), |
|
204 | ('old', 'extended'), | |
209 | ('new', 'extended'), |
|
205 | ('new', 'extended'), | |
210 | ('deleted', 'extended'), |
|
206 | ('deleted', 'extended'), | |
211 | ('---', 'file_a'), |
|
207 | ('---', 'file_a'), | |
212 | ('+++', 'file_b'), |
|
208 | ('+++', 'file_b'), | |
213 | ('@', 'hunk'), |
|
209 | ('@', 'hunk'), | |
214 | ('-', 'deleted'), |
|
210 | ('-', 'deleted'), | |
215 | ('+', 'inserted')] |
|
211 | ('+', 'inserted')] | |
216 |
|
212 | |||
217 | _diff_effects = {'diffline': ['bold'], |
|
213 | _diff_effects = {'diffline': ['bold'], | |
218 | 'extended': ['cyan', 'bold'], |
|
214 | 'extended': ['cyan', 'bold'], | |
219 | 'file_a': ['red', 'bold'], |
|
215 | 'file_a': ['red', 'bold'], | |
220 | 'file_b': ['green', 'bold'], |
|
216 | 'file_b': ['green', 'bold'], | |
221 | 'hunk': ['magenta'], |
|
217 | 'hunk': ['magenta'], | |
222 | 'deleted': ['red'], |
|
218 | 'deleted': ['red'], | |
223 | 'inserted': ['green'], |
|
219 | 'inserted': ['green'], | |
224 | 'changed': ['white'], |
|
220 | 'changed': ['white'], | |
225 | 'trailingwhitespace': ['bold', 'red_background']} |
|
221 | 'trailingwhitespace': ['bold', 'red_background']} | |
226 |
|
222 | |||
227 | def uisetup(ui): |
|
223 | def uisetup(ui): | |
228 | '''Initialize the extension.''' |
|
224 | '''Initialize the extension.''' | |
229 | _setupcmd(ui, 'diff', commands.table, colordiff, _diff_effects) |
|
225 | _setupcmd(ui, 'diff', commands.table, colordiff, _diff_effects) | |
230 | _setupcmd(ui, 'incoming', commands.table, None, _diff_effects) |
|
226 | _setupcmd(ui, 'incoming', commands.table, None, _diff_effects) | |
231 | _setupcmd(ui, 'log', commands.table, None, _diff_effects) |
|
227 | _setupcmd(ui, 'log', commands.table, None, _diff_effects) | |
232 | _setupcmd(ui, 'outgoing', commands.table, None, _diff_effects) |
|
228 | _setupcmd(ui, 'outgoing', commands.table, None, _diff_effects) | |
233 | _setupcmd(ui, 'tip', commands.table, None, _diff_effects) |
|
229 | _setupcmd(ui, 'tip', commands.table, None, _diff_effects) | |
234 | _setupcmd(ui, 'status', commands.table, colorstatus, _status_effects) |
|
230 | _setupcmd(ui, 'status', commands.table, colorstatus, _status_effects) | |
235 | try: |
|
231 | try: | |
236 | mq = extensions.find('mq') |
|
232 | mq = extensions.find('mq') | |
237 | _setupcmd(ui, 'qdiff', mq.cmdtable, colordiff, _diff_effects) |
|
233 | _setupcmd(ui, 'qdiff', mq.cmdtable, colordiff, _diff_effects) | |
238 | _setupcmd(ui, 'qseries', mq.cmdtable, colorqseries, _patch_effects) |
|
234 | _setupcmd(ui, 'qseries', mq.cmdtable, colorqseries, _patch_effects) | |
239 | except KeyError: |
|
235 | except KeyError: | |
240 | # The mq extension is not enabled |
|
236 | # The mq extension is not enabled | |
241 | pass |
|
237 | pass | |
242 |
|
238 | |||
243 | def _setupcmd(ui, cmd, table, func, effectsmap): |
|
239 | def _setupcmd(ui, cmd, table, func, effectsmap): | |
244 | '''patch in command to command table and load effect map''' |
|
240 | '''patch in command to command table and load effect map''' | |
245 | def nocolor(orig, *args, **opts): |
|
241 | def nocolor(orig, *args, **opts): | |
246 |
|
242 | |||
247 | if (opts['no_color'] or opts['color'] == 'never' or |
|
243 | if (opts['no_color'] or opts['color'] == 'never' or | |
248 | (opts['color'] == 'auto' and (os.environ.get('TERM') == 'dumb' |
|
244 | (opts['color'] == 'auto' and (os.environ.get('TERM') == 'dumb' | |
249 | or not sys.__stdout__.isatty()))): |
|
245 | or not sys.__stdout__.isatty()))): | |
250 | return orig(*args, **opts) |
|
246 | return orig(*args, **opts) | |
251 |
|
247 | |||
252 | oldshowpatch = extensions.wrapfunction(cmdutil.changeset_printer, |
|
248 | oldshowpatch = extensions.wrapfunction(cmdutil.changeset_printer, | |
253 | 'showpatch', colorshowpatch) |
|
249 | 'showpatch', colorshowpatch) | |
254 | try: |
|
250 | try: | |
255 | if func is not None: |
|
251 | if func is not None: | |
256 | return func(orig, *args, **opts) |
|
252 | return func(orig, *args, **opts) | |
257 | return orig(*args, **opts) |
|
253 | return orig(*args, **opts) | |
258 | finally: |
|
254 | finally: | |
259 | cmdutil.changeset_printer.showpatch = oldshowpatch |
|
255 | cmdutil.changeset_printer.showpatch = oldshowpatch | |
260 |
|
256 | |||
261 | entry = extensions.wrapcommand(table, cmd, nocolor) |
|
257 | entry = extensions.wrapcommand(table, cmd, nocolor) | |
262 | entry[1].extend([ |
|
258 | entry[1].extend([ | |
263 | ('', 'color', 'auto', _("when to colorize (always, auto, or never)")), |
|
259 | ('', 'color', 'auto', _("when to colorize (always, auto, or never)")), | |
264 | ('', 'no-color', None, _("don't colorize output")), |
|
260 | ('', 'no-color', None, _("don't colorize output")), | |
265 | ]) |
|
261 | ]) | |
266 |
|
262 | |||
267 | for status in effectsmap: |
|
263 | for status in effectsmap: | |
268 | effects = ui.configlist('color', cmd + '.' + status) |
|
264 | effects = ui.configlist('color', cmd + '.' + status) | |
269 | if effects: |
|
265 | if effects: | |
270 | effectsmap[status] = effects |
|
266 | effectsmap[status] = effects |
@@ -1,233 +1,228 b'' | |||||
1 | # extdiff.py - external diff program support for mercurial |
|
1 | # extdiff.py - external diff program support for mercurial | |
2 | # |
|
2 | # | |
3 | # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> |
|
3 | # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> | |
4 | # |
|
4 | # | |
5 | # This software may be used and distributed according to the terms of the |
|
5 | # This software may be used and distributed according to the terms of the | |
6 | # GNU General Public License version 2, incorporated herein by reference. |
|
6 | # GNU General Public License version 2, incorporated herein by reference. | |
7 |
|
7 | |||
8 | ''' |
|
8 | ''' | |
9 | The `extdiff' Mercurial extension allows you to use external programs |
|
9 | The `extdiff' Mercurial extension allows you to use external programs | |
10 | to compare revisions, or revision with working directory. The external diff |
|
10 | to compare revisions, or revision with working directory. The external diff | |
11 | programs are called with a configurable set of options and two |
|
11 | programs are called with a configurable set of options and two | |
12 | non-option arguments: paths to directories containing snapshots of |
|
12 | non-option arguments: paths to directories containing snapshots of | |
13 | files to compare. |
|
13 | files to compare. | |
14 |
|
14 | |||
15 | To enable this extension: |
|
|||
16 |
|
||||
17 | [extensions] |
|
|||
18 | hgext.extdiff = |
|
|||
19 |
|
||||
20 | The `extdiff' extension also allows to configure new diff commands, so |
|
15 | The `extdiff' extension also allows to configure new diff commands, so | |
21 | you do not need to type "hg extdiff -p kdiff3" always. |
|
16 | you do not need to type "hg extdiff -p kdiff3" always. | |
22 |
|
17 | |||
23 | [extdiff] |
|
18 | [extdiff] | |
24 | # add new command that runs GNU diff(1) in 'context diff' mode |
|
19 | # add new command that runs GNU diff(1) in 'context diff' mode | |
25 | cdiff = gdiff -Nprc5 |
|
20 | cdiff = gdiff -Nprc5 | |
26 | ## or the old way: |
|
21 | ## or the old way: | |
27 | #cmd.cdiff = gdiff |
|
22 | #cmd.cdiff = gdiff | |
28 | #opts.cdiff = -Nprc5 |
|
23 | #opts.cdiff = -Nprc5 | |
29 |
|
24 | |||
30 | # add new command called vdiff, runs kdiff3 |
|
25 | # add new command called vdiff, runs kdiff3 | |
31 | vdiff = kdiff3 |
|
26 | vdiff = kdiff3 | |
32 |
|
27 | |||
33 | # add new command called meld, runs meld (no need to name twice) |
|
28 | # add new command called meld, runs meld (no need to name twice) | |
34 | meld = |
|
29 | meld = | |
35 |
|
30 | |||
36 | # add new command called vimdiff, runs gvimdiff with DirDiff plugin |
|
31 | # add new command called vimdiff, runs gvimdiff with DirDiff plugin | |
37 | # (see http://www.vim.org/scripts/script.php?script_id=102) |
|
32 | # (see http://www.vim.org/scripts/script.php?script_id=102) | |
38 | # Non English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in |
|
33 | # Non English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in | |
39 | # your .vimrc |
|
34 | # your .vimrc | |
40 | vimdiff = gvim -f '+next' '+execute "DirDiff" argv(0) argv(1)' |
|
35 | vimdiff = gvim -f '+next' '+execute "DirDiff" argv(0) argv(1)' | |
41 |
|
36 | |||
42 | You can use -I/-X and list of file or directory names like normal "hg |
|
37 | You can use -I/-X and list of file or directory names like normal "hg | |
43 | diff" command. The `extdiff' extension makes snapshots of only needed |
|
38 | diff" command. The `extdiff' extension makes snapshots of only needed | |
44 | files, so running the external diff program will actually be pretty |
|
39 | files, so running the external diff program will actually be pretty | |
45 | fast (at least faster than having to compare the entire tree). |
|
40 | fast (at least faster than having to compare the entire tree). | |
46 | ''' |
|
41 | ''' | |
47 |
|
42 | |||
48 | from mercurial.i18n import _ |
|
43 | from mercurial.i18n import _ | |
49 | from mercurial.node import short |
|
44 | from mercurial.node import short | |
50 | from mercurial import cmdutil, util, commands |
|
45 | from mercurial import cmdutil, util, commands | |
51 | import os, shlex, shutil, tempfile |
|
46 | import os, shlex, shutil, tempfile | |
52 |
|
47 | |||
53 | def snapshot(ui, repo, files, node, tmproot): |
|
48 | def snapshot(ui, repo, files, node, tmproot): | |
54 | '''snapshot files as of some revision |
|
49 | '''snapshot files as of some revision | |
55 | if not using snapshot, -I/-X does not work and recursive diff |
|
50 | if not using snapshot, -I/-X does not work and recursive diff | |
56 | in tools like kdiff3 and meld displays too many files.''' |
|
51 | in tools like kdiff3 and meld displays too many files.''' | |
57 | dirname = os.path.basename(repo.root) |
|
52 | dirname = os.path.basename(repo.root) | |
58 | if dirname == "": |
|
53 | if dirname == "": | |
59 | dirname = "root" |
|
54 | dirname = "root" | |
60 | if node is not None: |
|
55 | if node is not None: | |
61 | dirname = '%s.%s' % (dirname, short(node)) |
|
56 | dirname = '%s.%s' % (dirname, short(node)) | |
62 | base = os.path.join(tmproot, dirname) |
|
57 | base = os.path.join(tmproot, dirname) | |
63 | os.mkdir(base) |
|
58 | os.mkdir(base) | |
64 | if node is not None: |
|
59 | if node is not None: | |
65 | ui.note(_('making snapshot of %d files from rev %s\n') % |
|
60 | ui.note(_('making snapshot of %d files from rev %s\n') % | |
66 | (len(files), short(node))) |
|
61 | (len(files), short(node))) | |
67 | else: |
|
62 | else: | |
68 | ui.note(_('making snapshot of %d files from working directory\n') % |
|
63 | ui.note(_('making snapshot of %d files from working directory\n') % | |
69 | (len(files))) |
|
64 | (len(files))) | |
70 | wopener = util.opener(base) |
|
65 | wopener = util.opener(base) | |
71 | fns_and_mtime = [] |
|
66 | fns_and_mtime = [] | |
72 | ctx = repo[node] |
|
67 | ctx = repo[node] | |
73 | for fn in files: |
|
68 | for fn in files: | |
74 | wfn = util.pconvert(fn) |
|
69 | wfn = util.pconvert(fn) | |
75 | if not wfn in ctx: |
|
70 | if not wfn in ctx: | |
76 | # skipping new file after a merge ? |
|
71 | # skipping new file after a merge ? | |
77 | continue |
|
72 | continue | |
78 | ui.note(' %s\n' % wfn) |
|
73 | ui.note(' %s\n' % wfn) | |
79 | dest = os.path.join(base, wfn) |
|
74 | dest = os.path.join(base, wfn) | |
80 | fctx = ctx[wfn] |
|
75 | fctx = ctx[wfn] | |
81 | data = repo.wwritedata(wfn, fctx.data()) |
|
76 | data = repo.wwritedata(wfn, fctx.data()) | |
82 | if 'l' in fctx.flags(): |
|
77 | if 'l' in fctx.flags(): | |
83 | wopener.symlink(data, wfn) |
|
78 | wopener.symlink(data, wfn) | |
84 | else: |
|
79 | else: | |
85 | wopener(wfn, 'w').write(data) |
|
80 | wopener(wfn, 'w').write(data) | |
86 | if 'x' in fctx.flags(): |
|
81 | if 'x' in fctx.flags(): | |
87 | util.set_flags(dest, False, True) |
|
82 | util.set_flags(dest, False, True) | |
88 | if node is None: |
|
83 | if node is None: | |
89 | fns_and_mtime.append((dest, repo.wjoin(fn), os.path.getmtime(dest))) |
|
84 | fns_and_mtime.append((dest, repo.wjoin(fn), os.path.getmtime(dest))) | |
90 | return dirname, fns_and_mtime |
|
85 | return dirname, fns_and_mtime | |
91 |
|
86 | |||
92 | def dodiff(ui, repo, diffcmd, diffopts, pats, opts): |
|
87 | def dodiff(ui, repo, diffcmd, diffopts, pats, opts): | |
93 | '''Do the actuall diff: |
|
88 | '''Do the actuall diff: | |
94 |
|
89 | |||
95 | - copy to a temp structure if diffing 2 internal revisions |
|
90 | - copy to a temp structure if diffing 2 internal revisions | |
96 | - copy to a temp structure if diffing working revision with |
|
91 | - copy to a temp structure if diffing working revision with | |
97 | another one and more than 1 file is changed |
|
92 | another one and more than 1 file is changed | |
98 | - just invoke the diff for a single file in the working dir |
|
93 | - just invoke the diff for a single file in the working dir | |
99 | ''' |
|
94 | ''' | |
100 |
|
95 | |||
101 | revs = opts.get('rev') |
|
96 | revs = opts.get('rev') | |
102 | change = opts.get('change') |
|
97 | change = opts.get('change') | |
103 |
|
98 | |||
104 | if revs and change: |
|
99 | if revs and change: | |
105 | msg = _('cannot specify --rev and --change at the same time') |
|
100 | msg = _('cannot specify --rev and --change at the same time') | |
106 | raise util.Abort(msg) |
|
101 | raise util.Abort(msg) | |
107 | elif change: |
|
102 | elif change: | |
108 | node2 = repo.lookup(change) |
|
103 | node2 = repo.lookup(change) | |
109 | node1 = repo[node2].parents()[0].node() |
|
104 | node1 = repo[node2].parents()[0].node() | |
110 | else: |
|
105 | else: | |
111 | node1, node2 = cmdutil.revpair(repo, revs) |
|
106 | node1, node2 = cmdutil.revpair(repo, revs) | |
112 |
|
107 | |||
113 | matcher = cmdutil.match(repo, pats, opts) |
|
108 | matcher = cmdutil.match(repo, pats, opts) | |
114 | modified, added, removed = repo.status(node1, node2, matcher)[:3] |
|
109 | modified, added, removed = repo.status(node1, node2, matcher)[:3] | |
115 | if not (modified or added or removed): |
|
110 | if not (modified or added or removed): | |
116 | return 0 |
|
111 | return 0 | |
117 |
|
112 | |||
118 | tmproot = tempfile.mkdtemp(prefix='extdiff.') |
|
113 | tmproot = tempfile.mkdtemp(prefix='extdiff.') | |
119 | dir2root = '' |
|
114 | dir2root = '' | |
120 | try: |
|
115 | try: | |
121 | # Always make a copy of node1 |
|
116 | # Always make a copy of node1 | |
122 | dir1 = snapshot(ui, repo, modified + removed, node1, tmproot)[0] |
|
117 | dir1 = snapshot(ui, repo, modified + removed, node1, tmproot)[0] | |
123 | changes = len(modified) + len(removed) + len(added) |
|
118 | changes = len(modified) + len(removed) + len(added) | |
124 |
|
119 | |||
125 | # If node2 in not the wc or there is >1 change, copy it |
|
120 | # If node2 in not the wc or there is >1 change, copy it | |
126 | if node2 or changes > 1: |
|
121 | if node2 or changes > 1: | |
127 | dir2, fns_and_mtime = snapshot(ui, repo, modified + added, node2, tmproot) |
|
122 | dir2, fns_and_mtime = snapshot(ui, repo, modified + added, node2, tmproot) | |
128 | else: |
|
123 | else: | |
129 | # This lets the diff tool open the changed file directly |
|
124 | # This lets the diff tool open the changed file directly | |
130 | dir2 = '' |
|
125 | dir2 = '' | |
131 | dir2root = repo.root |
|
126 | dir2root = repo.root | |
132 | fns_and_mtime = [] |
|
127 | fns_and_mtime = [] | |
133 |
|
128 | |||
134 | # If only one change, diff the files instead of the directories |
|
129 | # If only one change, diff the files instead of the directories | |
135 | if changes == 1 : |
|
130 | if changes == 1 : | |
136 | if len(modified): |
|
131 | if len(modified): | |
137 | dir1 = os.path.join(dir1, util.localpath(modified[0])) |
|
132 | dir1 = os.path.join(dir1, util.localpath(modified[0])) | |
138 | dir2 = os.path.join(dir2root, dir2, util.localpath(modified[0])) |
|
133 | dir2 = os.path.join(dir2root, dir2, util.localpath(modified[0])) | |
139 | elif len(removed) : |
|
134 | elif len(removed) : | |
140 | dir1 = os.path.join(dir1, util.localpath(removed[0])) |
|
135 | dir1 = os.path.join(dir1, util.localpath(removed[0])) | |
141 | dir2 = os.devnull |
|
136 | dir2 = os.devnull | |
142 | else: |
|
137 | else: | |
143 | dir1 = os.devnull |
|
138 | dir1 = os.devnull | |
144 | dir2 = os.path.join(dir2root, dir2, util.localpath(added[0])) |
|
139 | dir2 = os.path.join(dir2root, dir2, util.localpath(added[0])) | |
145 |
|
140 | |||
146 | cmdline = ('%s %s %s %s' % |
|
141 | cmdline = ('%s %s %s %s' % | |
147 | (util.shellquote(diffcmd), ' '.join(diffopts), |
|
142 | (util.shellquote(diffcmd), ' '.join(diffopts), | |
148 | util.shellquote(dir1), util.shellquote(dir2))) |
|
143 | util.shellquote(dir1), util.shellquote(dir2))) | |
149 | ui.debug(_('running %r in %s\n') % (cmdline, tmproot)) |
|
144 | ui.debug(_('running %r in %s\n') % (cmdline, tmproot)) | |
150 | util.system(cmdline, cwd=tmproot) |
|
145 | util.system(cmdline, cwd=tmproot) | |
151 |
|
146 | |||
152 | for copy_fn, working_fn, mtime in fns_and_mtime: |
|
147 | for copy_fn, working_fn, mtime in fns_and_mtime: | |
153 | if os.path.getmtime(copy_fn) != mtime: |
|
148 | if os.path.getmtime(copy_fn) != mtime: | |
154 | ui.debug(_('file changed while diffing. ' |
|
149 | ui.debug(_('file changed while diffing. ' | |
155 | 'Overwriting: %s (src: %s)\n') % (working_fn, copy_fn)) |
|
150 | 'Overwriting: %s (src: %s)\n') % (working_fn, copy_fn)) | |
156 | util.copyfile(copy_fn, working_fn) |
|
151 | util.copyfile(copy_fn, working_fn) | |
157 |
|
152 | |||
158 | return 1 |
|
153 | return 1 | |
159 | finally: |
|
154 | finally: | |
160 | ui.note(_('cleaning up temp directory\n')) |
|
155 | ui.note(_('cleaning up temp directory\n')) | |
161 | shutil.rmtree(tmproot) |
|
156 | shutil.rmtree(tmproot) | |
162 |
|
157 | |||
163 | def extdiff(ui, repo, *pats, **opts): |
|
158 | def extdiff(ui, repo, *pats, **opts): | |
164 | '''use external program to diff repository (or selected files) |
|
159 | '''use external program to diff repository (or selected files) | |
165 |
|
160 | |||
166 | Show differences between revisions for the specified files, using |
|
161 | Show differences between revisions for the specified files, using | |
167 | an external program. The default program used is diff, with |
|
162 | an external program. The default program used is diff, with | |
168 | default options "-Npru". |
|
163 | default options "-Npru". | |
169 |
|
164 | |||
170 | To select a different program, use the -p/--program option. The |
|
165 | To select a different program, use the -p/--program option. The | |
171 | program will be passed the names of two directories to compare. To |
|
166 | program will be passed the names of two directories to compare. To | |
172 | pass additional options to the program, use -o/--option. These |
|
167 | pass additional options to the program, use -o/--option. These | |
173 | will be passed before the names of the directories to compare. |
|
168 | will be passed before the names of the directories to compare. | |
174 |
|
169 | |||
175 | When two revision arguments are given, then changes are shown |
|
170 | When two revision arguments are given, then changes are shown | |
176 | between those revisions. If only one revision is specified then |
|
171 | between those revisions. If only one revision is specified then | |
177 | that revision is compared to the working directory, and, when no |
|
172 | that revision is compared to the working directory, and, when no | |
178 | revisions are specified, the working directory files are compared |
|
173 | revisions are specified, the working directory files are compared | |
179 | to its parent.''' |
|
174 | to its parent.''' | |
180 | program = opts['program'] or 'diff' |
|
175 | program = opts['program'] or 'diff' | |
181 | if opts['program']: |
|
176 | if opts['program']: | |
182 | option = opts['option'] |
|
177 | option = opts['option'] | |
183 | else: |
|
178 | else: | |
184 | option = opts['option'] or ['-Npru'] |
|
179 | option = opts['option'] or ['-Npru'] | |
185 | return dodiff(ui, repo, program, option, pats, opts) |
|
180 | return dodiff(ui, repo, program, option, pats, opts) | |
186 |
|
181 | |||
187 | cmdtable = { |
|
182 | cmdtable = { | |
188 | "extdiff": |
|
183 | "extdiff": | |
189 | (extdiff, |
|
184 | (extdiff, | |
190 | [('p', 'program', '', _('comparison program to run')), |
|
185 | [('p', 'program', '', _('comparison program to run')), | |
191 | ('o', 'option', [], _('pass option to comparison program')), |
|
186 | ('o', 'option', [], _('pass option to comparison program')), | |
192 | ('r', 'rev', [], _('revision')), |
|
187 | ('r', 'rev', [], _('revision')), | |
193 | ('c', 'change', '', _('change made by revision')), |
|
188 | ('c', 'change', '', _('change made by revision')), | |
194 | ] + commands.walkopts, |
|
189 | ] + commands.walkopts, | |
195 | _('hg extdiff [OPT]... [FILE]...')), |
|
190 | _('hg extdiff [OPT]... [FILE]...')), | |
196 | } |
|
191 | } | |
197 |
|
192 | |||
198 | def uisetup(ui): |
|
193 | def uisetup(ui): | |
199 | for cmd, path in ui.configitems('extdiff'): |
|
194 | for cmd, path in ui.configitems('extdiff'): | |
200 | if cmd.startswith('cmd.'): |
|
195 | if cmd.startswith('cmd.'): | |
201 | cmd = cmd[4:] |
|
196 | cmd = cmd[4:] | |
202 | if not path: path = cmd |
|
197 | if not path: path = cmd | |
203 | diffopts = ui.config('extdiff', 'opts.' + cmd, '') |
|
198 | diffopts = ui.config('extdiff', 'opts.' + cmd, '') | |
204 | diffopts = diffopts and [diffopts] or [] |
|
199 | diffopts = diffopts and [diffopts] or [] | |
205 | elif cmd.startswith('opts.'): |
|
200 | elif cmd.startswith('opts.'): | |
206 | continue |
|
201 | continue | |
207 | else: |
|
202 | else: | |
208 | # command = path opts |
|
203 | # command = path opts | |
209 | if path: |
|
204 | if path: | |
210 | diffopts = shlex.split(path) |
|
205 | diffopts = shlex.split(path) | |
211 | path = diffopts.pop(0) |
|
206 | path = diffopts.pop(0) | |
212 | else: |
|
207 | else: | |
213 | path, diffopts = cmd, [] |
|
208 | path, diffopts = cmd, [] | |
214 | def save(cmd, path, diffopts): |
|
209 | def save(cmd, path, diffopts): | |
215 | '''use closure to save diff command to use''' |
|
210 | '''use closure to save diff command to use''' | |
216 | def mydiff(ui, repo, *pats, **opts): |
|
211 | def mydiff(ui, repo, *pats, **opts): | |
217 | return dodiff(ui, repo, path, diffopts, pats, opts) |
|
212 | return dodiff(ui, repo, path, diffopts, pats, opts) | |
218 | mydiff.__doc__ = '''use %(path)s to diff repository (or selected files) |
|
213 | mydiff.__doc__ = '''use %(path)s to diff repository (or selected files) | |
219 |
|
214 | |||
220 | Show differences between revisions for the specified |
|
215 | Show differences between revisions for the specified | |
221 | files, using the %(path)s program. |
|
216 | files, using the %(path)s program. | |
222 |
|
217 | |||
223 | When two revision arguments are given, then changes are |
|
218 | When two revision arguments are given, then changes are | |
224 | shown between those revisions. If only one revision is |
|
219 | shown between those revisions. If only one revision is | |
225 | specified then that revision is compared to the working |
|
220 | specified then that revision is compared to the working | |
226 | directory, and, when no revisions are specified, the |
|
221 | directory, and, when no revisions are specified, the | |
227 | working directory files are compared to its parent.''' % { |
|
222 | working directory files are compared to its parent.''' % { | |
228 | 'path': util.uirepr(path), |
|
223 | 'path': util.uirepr(path), | |
229 | } |
|
224 | } | |
230 | return mydiff |
|
225 | return mydiff | |
231 | cmdtable[cmd] = (save(cmd, path, diffopts), |
|
226 | cmdtable[cmd] = (save(cmd, path, diffopts), | |
232 | cmdtable['extdiff'][1][1:], |
|
227 | cmdtable['extdiff'][1][1:], | |
233 | _('hg %s [OPTION]... [FILE]...') % cmd) |
|
228 | _('hg %s [OPTION]... [FILE]...') % cmd) |
@@ -1,358 +1,346 b'' | |||||
1 | # Minimal support for git commands on an hg repository |
|
1 | # Minimal support for git commands on an hg repository | |
2 | # |
|
2 | # | |
3 | # Copyright 2005, 2006 Chris Mason <mason@suse.com> |
|
3 | # Copyright 2005, 2006 Chris Mason <mason@suse.com> | |
4 | # |
|
4 | # | |
5 | # This software may be used and distributed according to the terms of the |
|
5 | # This software may be used and distributed according to the terms of the | |
6 | # GNU General Public License version 2, incorporated herein by reference. |
|
6 | # GNU General Public License version 2, incorporated herein by reference. | |
7 |
|
7 | |||
8 | '''browsing the repository in a graphical way |
|
8 | '''browsing the repository in a graphical way | |
9 |
|
9 | |||
10 | The hgk extension allows browsing the history of a repository in a |
|
10 | The hgk extension allows browsing the history of a repository in a | |
11 | graphical way. It requires Tcl/Tk version 8.4 or later. (Tcl/Tk is not |
|
11 | graphical way. It requires Tcl/Tk version 8.4 or later. (Tcl/Tk is not | |
12 | distributed with Mercurial.) |
|
12 | distributed with Mercurial.) | |
13 |
|
13 | |||
14 | hgk consists of two parts: a Tcl script that does the displaying and |
|
14 | hgk consists of two parts: a Tcl script that does the displaying and | |
15 | querying of information, and an extension to Mercurial named hgk.py, |
|
15 | querying of information, and an extension to Mercurial named hgk.py, | |
16 | which provides hooks for hgk to get information. hgk can be found in |
|
16 | which provides hooks for hgk to get information. hgk can be found in | |
17 |
the contrib directory, and |
|
17 | the contrib directory, and the extension is shipped in the hgext | |
18 |
|
18 | repository, and needs to be enabled. | ||
19 | To load the hgext.py extension, add it to your .hgrc file (you have to |
|
|||
20 | use your global $HOME/.hgrc file, not one in a repository). You can |
|
|||
21 | specify an absolute path: |
|
|||
22 |
|
||||
23 | [extensions] |
|
|||
24 | hgk=/usr/local/lib/hgk.py |
|
|||
25 |
|
||||
26 | Mercurial can also scan the default python library path for a file |
|
|||
27 | named 'hgk.py' if you set hgk empty: |
|
|||
28 |
|
||||
29 | [extensions] |
|
|||
30 | hgk= |
|
|||
31 |
|
19 | |||
32 | The hg view command will launch the hgk Tcl script. For this command |
|
20 | The hg view command will launch the hgk Tcl script. For this command | |
33 | to work, hgk must be in your search path. Alternately, you can specify |
|
21 | to work, hgk must be in your search path. Alternately, you can specify | |
34 | the path to hgk in your .hgrc file: |
|
22 | the path to hgk in your .hgrc file: | |
35 |
|
23 | |||
36 | [hgk] |
|
24 | [hgk] | |
37 | path=/location/of/hgk |
|
25 | path=/location/of/hgk | |
38 |
|
26 | |||
39 | hgk can make use of the extdiff extension to visualize revisions. |
|
27 | hgk can make use of the extdiff extension to visualize revisions. | |
40 | Assuming you had already configured extdiff vdiff command, just add: |
|
28 | Assuming you had already configured extdiff vdiff command, just add: | |
41 |
|
29 | |||
42 | [hgk] |
|
30 | [hgk] | |
43 | vdiff=vdiff |
|
31 | vdiff=vdiff | |
44 |
|
32 | |||
45 | Revisions context menu will now display additional entries to fire |
|
33 | Revisions context menu will now display additional entries to fire | |
46 | vdiff on hovered and selected revisions.''' |
|
34 | vdiff on hovered and selected revisions.''' | |
47 |
|
35 | |||
48 | import os |
|
36 | import os | |
49 | from mercurial import commands, util, patch, revlog, cmdutil |
|
37 | from mercurial import commands, util, patch, revlog, cmdutil | |
50 | from mercurial.node import nullid, nullrev, short |
|
38 | from mercurial.node import nullid, nullrev, short | |
51 | from mercurial.i18n import _ |
|
39 | from mercurial.i18n import _ | |
52 |
|
40 | |||
53 | def difftree(ui, repo, node1=None, node2=None, *files, **opts): |
|
41 | def difftree(ui, repo, node1=None, node2=None, *files, **opts): | |
54 | """diff trees from two commits""" |
|
42 | """diff trees from two commits""" | |
55 | def __difftree(repo, node1, node2, files=[]): |
|
43 | def __difftree(repo, node1, node2, files=[]): | |
56 | assert node2 is not None |
|
44 | assert node2 is not None | |
57 | mmap = repo[node1].manifest() |
|
45 | mmap = repo[node1].manifest() | |
58 | mmap2 = repo[node2].manifest() |
|
46 | mmap2 = repo[node2].manifest() | |
59 | m = cmdutil.match(repo, files) |
|
47 | m = cmdutil.match(repo, files) | |
60 | modified, added, removed = repo.status(node1, node2, m)[:3] |
|
48 | modified, added, removed = repo.status(node1, node2, m)[:3] | |
61 | empty = short(nullid) |
|
49 | empty = short(nullid) | |
62 |
|
50 | |||
63 | for f in modified: |
|
51 | for f in modified: | |
64 | # TODO get file permissions |
|
52 | # TODO get file permissions | |
65 | ui.write(":100664 100664 %s %s M\t%s\t%s\n" % |
|
53 | ui.write(":100664 100664 %s %s M\t%s\t%s\n" % | |
66 | (short(mmap[f]), short(mmap2[f]), f, f)) |
|
54 | (short(mmap[f]), short(mmap2[f]), f, f)) | |
67 | for f in added: |
|
55 | for f in added: | |
68 | ui.write(":000000 100664 %s %s N\t%s\t%s\n" % |
|
56 | ui.write(":000000 100664 %s %s N\t%s\t%s\n" % | |
69 | (empty, short(mmap2[f]), f, f)) |
|
57 | (empty, short(mmap2[f]), f, f)) | |
70 | for f in removed: |
|
58 | for f in removed: | |
71 | ui.write(":100664 000000 %s %s D\t%s\t%s\n" % |
|
59 | ui.write(":100664 000000 %s %s D\t%s\t%s\n" % | |
72 | (short(mmap[f]), empty, f, f)) |
|
60 | (short(mmap[f]), empty, f, f)) | |
73 | ## |
|
61 | ## | |
74 |
|
62 | |||
75 | while True: |
|
63 | while True: | |
76 | if opts['stdin']: |
|
64 | if opts['stdin']: | |
77 | try: |
|
65 | try: | |
78 | line = raw_input().split(' ') |
|
66 | line = raw_input().split(' ') | |
79 | node1 = line[0] |
|
67 | node1 = line[0] | |
80 | if len(line) > 1: |
|
68 | if len(line) > 1: | |
81 | node2 = line[1] |
|
69 | node2 = line[1] | |
82 | else: |
|
70 | else: | |
83 | node2 = None |
|
71 | node2 = None | |
84 | except EOFError: |
|
72 | except EOFError: | |
85 | break |
|
73 | break | |
86 | node1 = repo.lookup(node1) |
|
74 | node1 = repo.lookup(node1) | |
87 | if node2: |
|
75 | if node2: | |
88 | node2 = repo.lookup(node2) |
|
76 | node2 = repo.lookup(node2) | |
89 | else: |
|
77 | else: | |
90 | node2 = node1 |
|
78 | node2 = node1 | |
91 | node1 = repo.changelog.parents(node1)[0] |
|
79 | node1 = repo.changelog.parents(node1)[0] | |
92 | if opts['patch']: |
|
80 | if opts['patch']: | |
93 | if opts['pretty']: |
|
81 | if opts['pretty']: | |
94 | catcommit(ui, repo, node2, "") |
|
82 | catcommit(ui, repo, node2, "") | |
95 | m = cmdutil.match(repo, files) |
|
83 | m = cmdutil.match(repo, files) | |
96 | chunks = patch.diff(repo, node1, node2, match=m, |
|
84 | chunks = patch.diff(repo, node1, node2, match=m, | |
97 | opts=patch.diffopts(ui, {'git': True})) |
|
85 | opts=patch.diffopts(ui, {'git': True})) | |
98 | for chunk in chunks: |
|
86 | for chunk in chunks: | |
99 | ui.write(chunk) |
|
87 | ui.write(chunk) | |
100 | else: |
|
88 | else: | |
101 | __difftree(repo, node1, node2, files=files) |
|
89 | __difftree(repo, node1, node2, files=files) | |
102 | if not opts['stdin']: |
|
90 | if not opts['stdin']: | |
103 | break |
|
91 | break | |
104 |
|
92 | |||
105 | def catcommit(ui, repo, n, prefix, ctx=None): |
|
93 | def catcommit(ui, repo, n, prefix, ctx=None): | |
106 | nlprefix = '\n' + prefix; |
|
94 | nlprefix = '\n' + prefix; | |
107 | if ctx is None: |
|
95 | if ctx is None: | |
108 | ctx = repo[n] |
|
96 | ctx = repo[n] | |
109 | ui.write("tree %s\n" % short(ctx.changeset()[0])) # use ctx.node() instead ?? |
|
97 | ui.write("tree %s\n" % short(ctx.changeset()[0])) # use ctx.node() instead ?? | |
110 | for p in ctx.parents(): |
|
98 | for p in ctx.parents(): | |
111 | ui.write("parent %s\n" % p) |
|
99 | ui.write("parent %s\n" % p) | |
112 |
|
100 | |||
113 | date = ctx.date() |
|
101 | date = ctx.date() | |
114 | description = ctx.description().replace("\0", "") |
|
102 | description = ctx.description().replace("\0", "") | |
115 | lines = description.splitlines() |
|
103 | lines = description.splitlines() | |
116 | if lines and lines[-1].startswith('committer:'): |
|
104 | if lines and lines[-1].startswith('committer:'): | |
117 | committer = lines[-1].split(': ')[1].rstrip() |
|
105 | committer = lines[-1].split(': ')[1].rstrip() | |
118 | else: |
|
106 | else: | |
119 | committer = ctx.user() |
|
107 | committer = ctx.user() | |
120 |
|
108 | |||
121 | ui.write("author %s %s %s\n" % (ctx.user(), int(date[0]), date[1])) |
|
109 | ui.write("author %s %s %s\n" % (ctx.user(), int(date[0]), date[1])) | |
122 | ui.write("committer %s %s %s\n" % (committer, int(date[0]), date[1])) |
|
110 | ui.write("committer %s %s %s\n" % (committer, int(date[0]), date[1])) | |
123 | ui.write("revision %d\n" % ctx.rev()) |
|
111 | ui.write("revision %d\n" % ctx.rev()) | |
124 | ui.write("branch %s\n\n" % ctx.branch()) |
|
112 | ui.write("branch %s\n\n" % ctx.branch()) | |
125 |
|
113 | |||
126 | if prefix != "": |
|
114 | if prefix != "": | |
127 | ui.write("%s%s\n" % (prefix, description.replace('\n', nlprefix).strip())) |
|
115 | ui.write("%s%s\n" % (prefix, description.replace('\n', nlprefix).strip())) | |
128 | else: |
|
116 | else: | |
129 | ui.write(description + "\n") |
|
117 | ui.write(description + "\n") | |
130 | if prefix: |
|
118 | if prefix: | |
131 | ui.write('\0') |
|
119 | ui.write('\0') | |
132 |
|
120 | |||
133 | def base(ui, repo, node1, node2): |
|
121 | def base(ui, repo, node1, node2): | |
134 | """output common ancestor information""" |
|
122 | """output common ancestor information""" | |
135 | node1 = repo.lookup(node1) |
|
123 | node1 = repo.lookup(node1) | |
136 | node2 = repo.lookup(node2) |
|
124 | node2 = repo.lookup(node2) | |
137 | n = repo.changelog.ancestor(node1, node2) |
|
125 | n = repo.changelog.ancestor(node1, node2) | |
138 | ui.write(short(n) + "\n") |
|
126 | ui.write(short(n) + "\n") | |
139 |
|
127 | |||
140 | def catfile(ui, repo, type=None, r=None, **opts): |
|
128 | def catfile(ui, repo, type=None, r=None, **opts): | |
141 | """cat a specific revision""" |
|
129 | """cat a specific revision""" | |
142 | # in stdin mode, every line except the commit is prefixed with two |
|
130 | # in stdin mode, every line except the commit is prefixed with two | |
143 | # spaces. This way the our caller can find the commit without magic |
|
131 | # spaces. This way the our caller can find the commit without magic | |
144 | # strings |
|
132 | # strings | |
145 | # |
|
133 | # | |
146 | prefix = "" |
|
134 | prefix = "" | |
147 | if opts['stdin']: |
|
135 | if opts['stdin']: | |
148 | try: |
|
136 | try: | |
149 | (type, r) = raw_input().split(' '); |
|
137 | (type, r) = raw_input().split(' '); | |
150 | prefix = " " |
|
138 | prefix = " " | |
151 | except EOFError: |
|
139 | except EOFError: | |
152 | return |
|
140 | return | |
153 |
|
141 | |||
154 | else: |
|
142 | else: | |
155 | if not type or not r: |
|
143 | if not type or not r: | |
156 | ui.warn(_("cat-file: type or revision not supplied\n")) |
|
144 | ui.warn(_("cat-file: type or revision not supplied\n")) | |
157 | commands.help_(ui, 'cat-file') |
|
145 | commands.help_(ui, 'cat-file') | |
158 |
|
146 | |||
159 | while r: |
|
147 | while r: | |
160 | if type != "commit": |
|
148 | if type != "commit": | |
161 | ui.warn(_("aborting hg cat-file only understands commits\n")) |
|
149 | ui.warn(_("aborting hg cat-file only understands commits\n")) | |
162 | return 1; |
|
150 | return 1; | |
163 | n = repo.lookup(r) |
|
151 | n = repo.lookup(r) | |
164 | catcommit(ui, repo, n, prefix) |
|
152 | catcommit(ui, repo, n, prefix) | |
165 | if opts['stdin']: |
|
153 | if opts['stdin']: | |
166 | try: |
|
154 | try: | |
167 | (type, r) = raw_input().split(' '); |
|
155 | (type, r) = raw_input().split(' '); | |
168 | except EOFError: |
|
156 | except EOFError: | |
169 | break |
|
157 | break | |
170 | else: |
|
158 | else: | |
171 | break |
|
159 | break | |
172 |
|
160 | |||
173 | # git rev-tree is a confusing thing. You can supply a number of |
|
161 | # git rev-tree is a confusing thing. You can supply a number of | |
174 | # commit sha1s on the command line, and it walks the commit history |
|
162 | # commit sha1s on the command line, and it walks the commit history | |
175 | # telling you which commits are reachable from the supplied ones via |
|
163 | # telling you which commits are reachable from the supplied ones via | |
176 | # a bitmask based on arg position. |
|
164 | # a bitmask based on arg position. | |
177 | # you can specify a commit to stop at by starting the sha1 with ^ |
|
165 | # you can specify a commit to stop at by starting the sha1 with ^ | |
178 | def revtree(ui, args, repo, full="tree", maxnr=0, parents=False): |
|
166 | def revtree(ui, args, repo, full="tree", maxnr=0, parents=False): | |
179 | def chlogwalk(): |
|
167 | def chlogwalk(): | |
180 | count = len(repo) |
|
168 | count = len(repo) | |
181 | i = count |
|
169 | i = count | |
182 | l = [0] * 100 |
|
170 | l = [0] * 100 | |
183 | chunk = 100 |
|
171 | chunk = 100 | |
184 | while True: |
|
172 | while True: | |
185 | if chunk > i: |
|
173 | if chunk > i: | |
186 | chunk = i |
|
174 | chunk = i | |
187 | i = 0 |
|
175 | i = 0 | |
188 | else: |
|
176 | else: | |
189 | i -= chunk |
|
177 | i -= chunk | |
190 |
|
178 | |||
191 | for x in xrange(chunk): |
|
179 | for x in xrange(chunk): | |
192 | if i + x >= count: |
|
180 | if i + x >= count: | |
193 | l[chunk - x:] = [0] * (chunk - x) |
|
181 | l[chunk - x:] = [0] * (chunk - x) | |
194 | break |
|
182 | break | |
195 | if full != None: |
|
183 | if full != None: | |
196 | l[x] = repo[i + x] |
|
184 | l[x] = repo[i + x] | |
197 | l[x].changeset() # force reading |
|
185 | l[x].changeset() # force reading | |
198 | else: |
|
186 | else: | |
199 | l[x] = 1 |
|
187 | l[x] = 1 | |
200 | for x in xrange(chunk-1, -1, -1): |
|
188 | for x in xrange(chunk-1, -1, -1): | |
201 | if l[x] != 0: |
|
189 | if l[x] != 0: | |
202 | yield (i + x, full != None and l[x] or None) |
|
190 | yield (i + x, full != None and l[x] or None) | |
203 | if i == 0: |
|
191 | if i == 0: | |
204 | break |
|
192 | break | |
205 |
|
193 | |||
206 | # calculate and return the reachability bitmask for sha |
|
194 | # calculate and return the reachability bitmask for sha | |
207 | def is_reachable(ar, reachable, sha): |
|
195 | def is_reachable(ar, reachable, sha): | |
208 | if len(ar) == 0: |
|
196 | if len(ar) == 0: | |
209 | return 1 |
|
197 | return 1 | |
210 | mask = 0 |
|
198 | mask = 0 | |
211 | for i in xrange(len(ar)): |
|
199 | for i in xrange(len(ar)): | |
212 | if sha in reachable[i]: |
|
200 | if sha in reachable[i]: | |
213 | mask |= 1 << i |
|
201 | mask |= 1 << i | |
214 |
|
202 | |||
215 | return mask |
|
203 | return mask | |
216 |
|
204 | |||
217 | reachable = [] |
|
205 | reachable = [] | |
218 | stop_sha1 = [] |
|
206 | stop_sha1 = [] | |
219 | want_sha1 = [] |
|
207 | want_sha1 = [] | |
220 | count = 0 |
|
208 | count = 0 | |
221 |
|
209 | |||
222 | # figure out which commits they are asking for and which ones they |
|
210 | # figure out which commits they are asking for and which ones they | |
223 | # want us to stop on |
|
211 | # want us to stop on | |
224 | for i, arg in enumerate(args): |
|
212 | for i, arg in enumerate(args): | |
225 | if arg.startswith('^'): |
|
213 | if arg.startswith('^'): | |
226 | s = repo.lookup(arg[1:]) |
|
214 | s = repo.lookup(arg[1:]) | |
227 | stop_sha1.append(s) |
|
215 | stop_sha1.append(s) | |
228 | want_sha1.append(s) |
|
216 | want_sha1.append(s) | |
229 | elif arg != 'HEAD': |
|
217 | elif arg != 'HEAD': | |
230 | want_sha1.append(repo.lookup(arg)) |
|
218 | want_sha1.append(repo.lookup(arg)) | |
231 |
|
219 | |||
232 | # calculate the graph for the supplied commits |
|
220 | # calculate the graph for the supplied commits | |
233 | for i, n in enumerate(want_sha1): |
|
221 | for i, n in enumerate(want_sha1): | |
234 | reachable.append(set()); |
|
222 | reachable.append(set()); | |
235 | visit = [n]; |
|
223 | visit = [n]; | |
236 | reachable[i].add(n) |
|
224 | reachable[i].add(n) | |
237 | while visit: |
|
225 | while visit: | |
238 | n = visit.pop(0) |
|
226 | n = visit.pop(0) | |
239 | if n in stop_sha1: |
|
227 | if n in stop_sha1: | |
240 | continue |
|
228 | continue | |
241 | for p in repo.changelog.parents(n): |
|
229 | for p in repo.changelog.parents(n): | |
242 | if p not in reachable[i]: |
|
230 | if p not in reachable[i]: | |
243 | reachable[i].add(p) |
|
231 | reachable[i].add(p) | |
244 | visit.append(p) |
|
232 | visit.append(p) | |
245 | if p in stop_sha1: |
|
233 | if p in stop_sha1: | |
246 | continue |
|
234 | continue | |
247 |
|
235 | |||
248 | # walk the repository looking for commits that are in our |
|
236 | # walk the repository looking for commits that are in our | |
249 | # reachability graph |
|
237 | # reachability graph | |
250 | for i, ctx in chlogwalk(): |
|
238 | for i, ctx in chlogwalk(): | |
251 | n = repo.changelog.node(i) |
|
239 | n = repo.changelog.node(i) | |
252 | mask = is_reachable(want_sha1, reachable, n) |
|
240 | mask = is_reachable(want_sha1, reachable, n) | |
253 | if mask: |
|
241 | if mask: | |
254 | parentstr = "" |
|
242 | parentstr = "" | |
255 | if parents: |
|
243 | if parents: | |
256 | pp = repo.changelog.parents(n) |
|
244 | pp = repo.changelog.parents(n) | |
257 | if pp[0] != nullid: |
|
245 | if pp[0] != nullid: | |
258 | parentstr += " " + short(pp[0]) |
|
246 | parentstr += " " + short(pp[0]) | |
259 | if pp[1] != nullid: |
|
247 | if pp[1] != nullid: | |
260 | parentstr += " " + short(pp[1]) |
|
248 | parentstr += " " + short(pp[1]) | |
261 | if not full: |
|
249 | if not full: | |
262 | ui.write("%s%s\n" % (short(n), parentstr)) |
|
250 | ui.write("%s%s\n" % (short(n), parentstr)) | |
263 | elif full == "commit": |
|
251 | elif full == "commit": | |
264 | ui.write("%s%s\n" % (short(n), parentstr)) |
|
252 | ui.write("%s%s\n" % (short(n), parentstr)) | |
265 | catcommit(ui, repo, n, ' ', ctx) |
|
253 | catcommit(ui, repo, n, ' ', ctx) | |
266 | else: |
|
254 | else: | |
267 | (p1, p2) = repo.changelog.parents(n) |
|
255 | (p1, p2) = repo.changelog.parents(n) | |
268 | (h, h1, h2) = map(short, (n, p1, p2)) |
|
256 | (h, h1, h2) = map(short, (n, p1, p2)) | |
269 | (i1, i2) = map(repo.changelog.rev, (p1, p2)) |
|
257 | (i1, i2) = map(repo.changelog.rev, (p1, p2)) | |
270 |
|
258 | |||
271 | date = ctx.date()[0] |
|
259 | date = ctx.date()[0] | |
272 | ui.write("%s %s:%s" % (date, h, mask)) |
|
260 | ui.write("%s %s:%s" % (date, h, mask)) | |
273 | mask = is_reachable(want_sha1, reachable, p1) |
|
261 | mask = is_reachable(want_sha1, reachable, p1) | |
274 | if i1 != nullrev and mask > 0: |
|
262 | if i1 != nullrev and mask > 0: | |
275 | ui.write("%s:%s " % (h1, mask)), |
|
263 | ui.write("%s:%s " % (h1, mask)), | |
276 | mask = is_reachable(want_sha1, reachable, p2) |
|
264 | mask = is_reachable(want_sha1, reachable, p2) | |
277 | if i2 != nullrev and mask > 0: |
|
265 | if i2 != nullrev and mask > 0: | |
278 | ui.write("%s:%s " % (h2, mask)) |
|
266 | ui.write("%s:%s " % (h2, mask)) | |
279 | ui.write("\n") |
|
267 | ui.write("\n") | |
280 | if maxnr and count >= maxnr: |
|
268 | if maxnr and count >= maxnr: | |
281 | break |
|
269 | break | |
282 | count += 1 |
|
270 | count += 1 | |
283 |
|
271 | |||
284 | def revparse(ui, repo, *revs, **opts): |
|
272 | def revparse(ui, repo, *revs, **opts): | |
285 | """parse given revisions""" |
|
273 | """parse given revisions""" | |
286 | def revstr(rev): |
|
274 | def revstr(rev): | |
287 | if rev == 'HEAD': |
|
275 | if rev == 'HEAD': | |
288 | rev = 'tip' |
|
276 | rev = 'tip' | |
289 | return revlog.hex(repo.lookup(rev)) |
|
277 | return revlog.hex(repo.lookup(rev)) | |
290 |
|
278 | |||
291 | for r in revs: |
|
279 | for r in revs: | |
292 | revrange = r.split(':', 1) |
|
280 | revrange = r.split(':', 1) | |
293 | ui.write('%s\n' % revstr(revrange[0])) |
|
281 | ui.write('%s\n' % revstr(revrange[0])) | |
294 | if len(revrange) == 2: |
|
282 | if len(revrange) == 2: | |
295 | ui.write('^%s\n' % revstr(revrange[1])) |
|
283 | ui.write('^%s\n' % revstr(revrange[1])) | |
296 |
|
284 | |||
297 | # git rev-list tries to order things by date, and has the ability to stop |
|
285 | # git rev-list tries to order things by date, and has the ability to stop | |
298 | # at a given commit without walking the whole repo. TODO add the stop |
|
286 | # at a given commit without walking the whole repo. TODO add the stop | |
299 | # parameter |
|
287 | # parameter | |
300 | def revlist(ui, repo, *revs, **opts): |
|
288 | def revlist(ui, repo, *revs, **opts): | |
301 | """print revisions""" |
|
289 | """print revisions""" | |
302 | if opts['header']: |
|
290 | if opts['header']: | |
303 | full = "commit" |
|
291 | full = "commit" | |
304 | else: |
|
292 | else: | |
305 | full = None |
|
293 | full = None | |
306 | copy = [x for x in revs] |
|
294 | copy = [x for x in revs] | |
307 | revtree(ui, copy, repo, full, opts['max_count'], opts['parents']) |
|
295 | revtree(ui, copy, repo, full, opts['max_count'], opts['parents']) | |
308 |
|
296 | |||
309 | def config(ui, repo, **opts): |
|
297 | def config(ui, repo, **opts): | |
310 | """print extension options""" |
|
298 | """print extension options""" | |
311 | def writeopt(name, value): |
|
299 | def writeopt(name, value): | |
312 | ui.write('k=%s\nv=%s\n' % (name, value)) |
|
300 | ui.write('k=%s\nv=%s\n' % (name, value)) | |
313 |
|
301 | |||
314 | writeopt('vdiff', ui.config('hgk', 'vdiff', '')) |
|
302 | writeopt('vdiff', ui.config('hgk', 'vdiff', '')) | |
315 |
|
303 | |||
316 |
|
304 | |||
317 | def view(ui, repo, *etc, **opts): |
|
305 | def view(ui, repo, *etc, **opts): | |
318 | "start interactive history viewer" |
|
306 | "start interactive history viewer" | |
319 | os.chdir(repo.root) |
|
307 | os.chdir(repo.root) | |
320 | optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v]) |
|
308 | optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v]) | |
321 | cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc)) |
|
309 | cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc)) | |
322 | ui.debug(_("running %s\n") % cmd) |
|
310 | ui.debug(_("running %s\n") % cmd) | |
323 | util.system(cmd) |
|
311 | util.system(cmd) | |
324 |
|
312 | |||
325 | cmdtable = { |
|
313 | cmdtable = { | |
326 | "^view": |
|
314 | "^view": | |
327 | (view, |
|
315 | (view, | |
328 | [('l', 'limit', '', _('limit number of changes displayed'))], |
|
316 | [('l', 'limit', '', _('limit number of changes displayed'))], | |
329 | _('hg view [-l LIMIT] [REVRANGE]')), |
|
317 | _('hg view [-l LIMIT] [REVRANGE]')), | |
330 | "debug-diff-tree": |
|
318 | "debug-diff-tree": | |
331 | (difftree, |
|
319 | (difftree, | |
332 | [('p', 'patch', None, _('generate patch')), |
|
320 | [('p', 'patch', None, _('generate patch')), | |
333 | ('r', 'recursive', None, _('recursive')), |
|
321 | ('r', 'recursive', None, _('recursive')), | |
334 | ('P', 'pretty', None, _('pretty')), |
|
322 | ('P', 'pretty', None, _('pretty')), | |
335 | ('s', 'stdin', None, _('stdin')), |
|
323 | ('s', 'stdin', None, _('stdin')), | |
336 | ('C', 'copy', None, _('detect copies')), |
|
324 | ('C', 'copy', None, _('detect copies')), | |
337 | ('S', 'search', "", _('search'))], |
|
325 | ('S', 'search', "", _('search'))], | |
338 | _('hg git-diff-tree [OPTION]... NODE1 NODE2 [FILE]...')), |
|
326 | _('hg git-diff-tree [OPTION]... NODE1 NODE2 [FILE]...')), | |
339 | "debug-cat-file": |
|
327 | "debug-cat-file": | |
340 | (catfile, |
|
328 | (catfile, | |
341 | [('s', 'stdin', None, _('stdin'))], |
|
329 | [('s', 'stdin', None, _('stdin'))], | |
342 | _('hg debug-cat-file [OPTION]... TYPE FILE')), |
|
330 | _('hg debug-cat-file [OPTION]... TYPE FILE')), | |
343 | "debug-config": |
|
331 | "debug-config": | |
344 | (config, [], _('hg debug-config')), |
|
332 | (config, [], _('hg debug-config')), | |
345 | "debug-merge-base": |
|
333 | "debug-merge-base": | |
346 | (base, [], _('hg debug-merge-base node node')), |
|
334 | (base, [], _('hg debug-merge-base node node')), | |
347 | "debug-rev-parse": |
|
335 | "debug-rev-parse": | |
348 | (revparse, |
|
336 | (revparse, | |
349 | [('', 'default', '', _('ignored'))], |
|
337 | [('', 'default', '', _('ignored'))], | |
350 | _('hg debug-rev-parse REV')), |
|
338 | _('hg debug-rev-parse REV')), | |
351 | "debug-rev-list": |
|
339 | "debug-rev-list": | |
352 | (revlist, |
|
340 | (revlist, | |
353 | [('H', 'header', None, _('header')), |
|
341 | [('H', 'header', None, _('header')), | |
354 | ('t', 'topo-order', None, _('topo-order')), |
|
342 | ('t', 'topo-order', None, _('topo-order')), | |
355 | ('p', 'parents', None, _('parents')), |
|
343 | ('p', 'parents', None, _('parents')), | |
356 | ('n', 'max-count', 0, _('max-count'))], |
|
344 | ('n', 'max-count', 0, _('max-count'))], | |
357 | _('hg debug-rev-list [options] revs')), |
|
345 | _('hg debug-rev-list [options] revs')), | |
358 | } |
|
346 | } |
@@ -1,67 +1,62 b'' | |||||
1 | # highlight - syntax highlighting in hgweb, based on Pygments |
|
1 | # highlight - syntax highlighting in hgweb, based on Pygments | |
2 | # |
|
2 | # | |
3 | # Copyright 2008, 2009 Patrick Mezard <pmezard@gmail.com> and others |
|
3 | # Copyright 2008, 2009 Patrick Mezard <pmezard@gmail.com> and others | |
4 | # |
|
4 | # | |
5 | # This software may be used and distributed according to the terms of the |
|
5 | # This software may be used and distributed according to the terms of the | |
6 | # GNU General Public License version 2, incorporated herein by reference. |
|
6 | # GNU General Public License version 2, incorporated herein by reference. | |
7 | # |
|
7 | # | |
8 | # The original module was split in an interface and an implementation |
|
8 | # The original module was split in an interface and an implementation | |
9 | # file to defer pygments loading and speedup extension setup. |
|
9 | # file to defer pygments loading and speedup extension setup. | |
10 |
|
10 | |||
11 | """syntax highlighting in hgweb, based on Pygments |
|
11 | """syntax highlighting in hgweb, based on Pygments | |
12 |
|
12 | |||
13 | It depends on the Pygments syntax highlighting library: |
|
13 | It depends on the Pygments syntax highlighting library: | |
14 | http://pygments.org/ |
|
14 | http://pygments.org/ | |
15 |
|
15 | |||
16 | To enable the extension add this to hgrc: |
|
|||
17 |
|
||||
18 | [extensions] |
|
|||
19 | hgext.highlight = |
|
|||
20 |
|
||||
21 | There is a single configuration option: |
|
16 | There is a single configuration option: | |
22 |
|
17 | |||
23 | [web] |
|
18 | [web] | |
24 | pygments_style = <style> |
|
19 | pygments_style = <style> | |
25 |
|
20 | |||
26 | The default is 'colorful'. |
|
21 | The default is 'colorful'. | |
27 |
|
22 | |||
28 | -- Adam Hupp <adam@hupp.org> |
|
23 | -- Adam Hupp <adam@hupp.org> | |
29 | """ |
|
24 | """ | |
30 |
|
25 | |||
31 | import highlight |
|
26 | import highlight | |
32 | from mercurial.hgweb import webcommands, webutil, common |
|
27 | from mercurial.hgweb import webcommands, webutil, common | |
33 | from mercurial import extensions |
|
28 | from mercurial import extensions | |
34 |
|
29 | |||
35 | def filerevision_highlight(orig, web, tmpl, fctx): |
|
30 | def filerevision_highlight(orig, web, tmpl, fctx): | |
36 | mt = ''.join(tmpl('mimetype', encoding=web.encoding)) |
|
31 | mt = ''.join(tmpl('mimetype', encoding=web.encoding)) | |
37 | # only pygmentize for mimetype containing 'html' so we both match |
|
32 | # only pygmentize for mimetype containing 'html' so we both match | |
38 | # 'text/html' and possibly 'application/xhtml+xml' in the future |
|
33 | # 'text/html' and possibly 'application/xhtml+xml' in the future | |
39 | # so that we don't have to touch the extension when the mimetype |
|
34 | # so that we don't have to touch the extension when the mimetype | |
40 | # for a template changes; also hgweb optimizes the case that a |
|
35 | # for a template changes; also hgweb optimizes the case that a | |
41 | # raw file is sent using rawfile() and doesn't call us, so we |
|
36 | # raw file is sent using rawfile() and doesn't call us, so we | |
42 | # can't clash with the file's content-type here in case we |
|
37 | # can't clash with the file's content-type here in case we | |
43 | # pygmentize a html file |
|
38 | # pygmentize a html file | |
44 | if 'html' in mt: |
|
39 | if 'html' in mt: | |
45 | style = web.config('web', 'pygments_style', 'colorful') |
|
40 | style = web.config('web', 'pygments_style', 'colorful') | |
46 | highlight.pygmentize('fileline', fctx, style, tmpl) |
|
41 | highlight.pygmentize('fileline', fctx, style, tmpl) | |
47 | return orig(web, tmpl, fctx) |
|
42 | return orig(web, tmpl, fctx) | |
48 |
|
43 | |||
49 | def annotate_highlight(orig, web, req, tmpl): |
|
44 | def annotate_highlight(orig, web, req, tmpl): | |
50 | mt = ''.join(tmpl('mimetype', encoding=web.encoding)) |
|
45 | mt = ''.join(tmpl('mimetype', encoding=web.encoding)) | |
51 | if 'html' in mt: |
|
46 | if 'html' in mt: | |
52 | fctx = webutil.filectx(web.repo, req) |
|
47 | fctx = webutil.filectx(web.repo, req) | |
53 | style = web.config('web', 'pygments_style', 'colorful') |
|
48 | style = web.config('web', 'pygments_style', 'colorful') | |
54 | highlight.pygmentize('annotateline', fctx, style, tmpl) |
|
49 | highlight.pygmentize('annotateline', fctx, style, tmpl) | |
55 | return orig(web, req, tmpl) |
|
50 | return orig(web, req, tmpl) | |
56 |
|
51 | |||
57 | def generate_css(web, req, tmpl): |
|
52 | def generate_css(web, req, tmpl): | |
58 | pg_style = web.config('web', 'pygments_style', 'colorful') |
|
53 | pg_style = web.config('web', 'pygments_style', 'colorful') | |
59 | fmter = highlight.HtmlFormatter(style = pg_style) |
|
54 | fmter = highlight.HtmlFormatter(style = pg_style) | |
60 | req.respond(common.HTTP_OK, 'text/css') |
|
55 | req.respond(common.HTTP_OK, 'text/css') | |
61 | return ['/* pygments_style = %s */\n\n' % pg_style, fmter.get_style_defs('')] |
|
56 | return ['/* pygments_style = %s */\n\n' % pg_style, fmter.get_style_defs('')] | |
62 |
|
57 | |||
63 | # monkeypatch in the new version |
|
58 | # monkeypatch in the new version | |
64 | extensions.wrapfunction(webcommands, '_filerevision', filerevision_highlight) |
|
59 | extensions.wrapfunction(webcommands, '_filerevision', filerevision_highlight) | |
65 | extensions.wrapfunction(webcommands, 'annotate', annotate_highlight) |
|
60 | extensions.wrapfunction(webcommands, 'annotate', annotate_highlight) | |
66 | webcommands.highlightcss = generate_css |
|
61 | webcommands.highlightcss = generate_css | |
67 | webcommands.__all__.append('highlightcss') |
|
62 | webcommands.__all__.append('highlightcss') |
@@ -1,84 +1,80 b'' | |||||
1 | # interhg.py - interhg |
|
1 | # interhg.py - interhg | |
2 | # |
|
2 | # | |
3 | # Copyright 2007 OHASHI Hideya <ohachige@gmail.com> |
|
3 | # Copyright 2007 OHASHI Hideya <ohachige@gmail.com> | |
4 | # |
|
4 | # | |
5 | # Contributor(s): |
|
5 | # Contributor(s): | |
6 | # Edward Lee <edward.lee@engineering.uiuc.edu> |
|
6 | # Edward Lee <edward.lee@engineering.uiuc.edu> | |
7 | # |
|
7 | # | |
8 | # This software may be used and distributed according to the terms of the |
|
8 | # This software may be used and distributed according to the terms of the | |
9 | # GNU General Public License version 2, incorporated herein by reference. |
|
9 | # GNU General Public License version 2, incorporated herein by reference. | |
10 |
|
10 | |||
11 | '''expand expressions into changelog and summaries |
|
11 | '''expand expressions into changelog and summaries | |
12 |
|
12 | |||
13 | This extension allows the use of a special syntax in summaries, |
|
13 | This extension allows the use of a special syntax in summaries, | |
14 | which will be automatically expanded into links or any other |
|
14 | which will be automatically expanded into links or any other | |
15 | arbitrary expression, much like InterWiki does. |
|
15 | arbitrary expression, much like InterWiki does. | |
16 |
|
16 | |||
17 | To enable this extension, add the following lines to your hgrc: |
|
17 | A few example patterns (link to bug tracking, etc.) that may | |
18 |
|
18 | be used in your hgrc: | ||
19 | [extensions] |
|
|||
20 | interhg = |
|
|||
21 |
|
||||
22 | A few example patterns (link to bug tracking, etc.): |
|
|||
23 |
|
19 | |||
24 | [interhg] |
|
20 | [interhg] | |
25 | issues = s!issue(\d+)!<a href="http://bts/issue\1">issue\1<\/a>! |
|
21 | issues = s!issue(\d+)!<a href="http://bts/issue\1">issue\1<\/a>! | |
26 | bugzilla = s!((?:bug|b=|(?=#?\d{4,}))(?:\s*#?)(\d+))!<a..=\2">\1</a>!i |
|
22 | bugzilla = s!((?:bug|b=|(?=#?\d{4,}))(?:\s*#?)(\d+))!<a..=\2">\1</a>!i | |
27 | boldify = s/(^|\s)#(\d+)\b/ <b>#\2<\/b>/ |
|
23 | boldify = s/(^|\s)#(\d+)\b/ <b>#\2<\/b>/ | |
28 | ''' |
|
24 | ''' | |
29 |
|
25 | |||
30 | import re |
|
26 | import re | |
31 | from mercurial.hgweb import hgweb_mod |
|
27 | from mercurial.hgweb import hgweb_mod | |
32 | from mercurial import templatefilters, extensions |
|
28 | from mercurial import templatefilters, extensions | |
33 | from mercurial.i18n import _ |
|
29 | from mercurial.i18n import _ | |
34 |
|
30 | |||
35 | orig_escape = templatefilters.filters["escape"] |
|
31 | orig_escape = templatefilters.filters["escape"] | |
36 |
|
32 | |||
37 | interhg_table = [] |
|
33 | interhg_table = [] | |
38 |
|
34 | |||
39 | def interhg_escape(x): |
|
35 | def interhg_escape(x): | |
40 | escstr = orig_escape(x) |
|
36 | escstr = orig_escape(x) | |
41 | for regexp, format in interhg_table: |
|
37 | for regexp, format in interhg_table: | |
42 | escstr = regexp.sub(format, escstr) |
|
38 | escstr = regexp.sub(format, escstr) | |
43 | return escstr |
|
39 | return escstr | |
44 |
|
40 | |||
45 | templatefilters.filters["escape"] = interhg_escape |
|
41 | templatefilters.filters["escape"] = interhg_escape | |
46 |
|
42 | |||
47 | def interhg_refresh(orig, self): |
|
43 | def interhg_refresh(orig, self): | |
48 | interhg_table[:] = [] |
|
44 | interhg_table[:] = [] | |
49 | for key, pattern in self.repo.ui.configitems('interhg'): |
|
45 | for key, pattern in self.repo.ui.configitems('interhg'): | |
50 | # grab the delimiter from the character after the "s" |
|
46 | # grab the delimiter from the character after the "s" | |
51 | unesc = pattern[1] |
|
47 | unesc = pattern[1] | |
52 | delim = re.escape(unesc) |
|
48 | delim = re.escape(unesc) | |
53 |
|
49 | |||
54 | # identify portions of the pattern, taking care to avoid escaped |
|
50 | # identify portions of the pattern, taking care to avoid escaped | |
55 | # delimiters. the replace format and flags are optional, but delimiters |
|
51 | # delimiters. the replace format and flags are optional, but delimiters | |
56 | # are required. |
|
52 | # are required. | |
57 | match = re.match(r'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$' |
|
53 | match = re.match(r'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$' | |
58 | % (delim, delim, delim), pattern) |
|
54 | % (delim, delim, delim), pattern) | |
59 | if not match: |
|
55 | if not match: | |
60 | self.repo.ui.warn(_("interhg: invalid pattern for %s: %s\n") |
|
56 | self.repo.ui.warn(_("interhg: invalid pattern for %s: %s\n") | |
61 | % (key, pattern)) |
|
57 | % (key, pattern)) | |
62 | continue |
|
58 | continue | |
63 |
|
59 | |||
64 | # we need to unescape the delimiter for regexp and format |
|
60 | # we need to unescape the delimiter for regexp and format | |
65 | delim_re = re.compile(r'(?<!\\)\\%s' % delim) |
|
61 | delim_re = re.compile(r'(?<!\\)\\%s' % delim) | |
66 | regexp = delim_re.sub(unesc, match.group(1)) |
|
62 | regexp = delim_re.sub(unesc, match.group(1)) | |
67 | format = delim_re.sub(unesc, match.group(2)) |
|
63 | format = delim_re.sub(unesc, match.group(2)) | |
68 |
|
64 | |||
69 | # the pattern allows for 6 regexp flags, so set them if necessary |
|
65 | # the pattern allows for 6 regexp flags, so set them if necessary | |
70 | flagin = match.group(3) |
|
66 | flagin = match.group(3) | |
71 | flags = 0 |
|
67 | flags = 0 | |
72 | if flagin: |
|
68 | if flagin: | |
73 | for flag in flagin.upper(): |
|
69 | for flag in flagin.upper(): | |
74 | flags |= re.__dict__[flag] |
|
70 | flags |= re.__dict__[flag] | |
75 |
|
71 | |||
76 | try: |
|
72 | try: | |
77 | regexp = re.compile(regexp, flags) |
|
73 | regexp = re.compile(regexp, flags) | |
78 | interhg_table.append((regexp, format)) |
|
74 | interhg_table.append((regexp, format)) | |
79 | except re.error: |
|
75 | except re.error: | |
80 | self.repo.ui.warn(_("interhg: invalid regexp for %s: %s\n") |
|
76 | self.repo.ui.warn(_("interhg: invalid regexp for %s: %s\n") | |
81 | % (key, regexp)) |
|
77 | % (key, regexp)) | |
82 | return orig(self) |
|
78 | return orig(self) | |
83 |
|
79 | |||
84 | extensions.wrapfunction(hgweb_mod.hgweb, 'refresh', interhg_refresh) |
|
80 | extensions.wrapfunction(hgweb_mod.hgweb, 'refresh', interhg_refresh) |
@@ -1,534 +1,528 b'' | |||||
1 | # keyword.py - $Keyword$ expansion for Mercurial |
|
1 | # keyword.py - $Keyword$ expansion for Mercurial | |
2 | # |
|
2 | # | |
3 | # Copyright 2007, 2008 Christian Ebert <blacktrash@gmx.net> |
|
3 | # Copyright 2007, 2008 Christian Ebert <blacktrash@gmx.net> | |
4 | # |
|
4 | # | |
5 | # This software may be used and distributed according to the terms of the |
|
5 | # This software may be used and distributed according to the terms of the | |
6 | # GNU General Public License version 2, incorporated herein by reference. |
|
6 | # GNU General Public License version 2, incorporated herein by reference. | |
7 | # |
|
7 | # | |
8 | # $Id$ |
|
8 | # $Id$ | |
9 | # |
|
9 | # | |
10 | # Keyword expansion hack against the grain of a DSCM |
|
10 | # Keyword expansion hack against the grain of a DSCM | |
11 | # |
|
11 | # | |
12 | # There are many good reasons why this is not needed in a distributed |
|
12 | # There are many good reasons why this is not needed in a distributed | |
13 | # SCM, still it may be useful in very small projects based on single |
|
13 | # SCM, still it may be useful in very small projects based on single | |
14 | # files (like LaTeX packages), that are mostly addressed to an |
|
14 | # files (like LaTeX packages), that are mostly addressed to an | |
15 | # audience not running a version control system. |
|
15 | # audience not running a version control system. | |
16 | # |
|
16 | # | |
17 | # For in-depth discussion refer to |
|
17 | # For in-depth discussion refer to | |
18 | # <http://www.selenic.com/mercurial/wiki/index.cgi/KeywordPlan>. |
|
18 | # <http://www.selenic.com/mercurial/wiki/index.cgi/KeywordPlan>. | |
19 | # |
|
19 | # | |
20 | # Keyword expansion is based on Mercurial's changeset template mappings. |
|
20 | # Keyword expansion is based on Mercurial's changeset template mappings. | |
21 | # |
|
21 | # | |
22 | # Binary files are not touched. |
|
22 | # Binary files are not touched. | |
23 | # |
|
23 | # | |
24 | # Setup in hgrc: |
|
|||
25 | # |
|
|||
26 | # [extensions] |
|
|||
27 | # # enable extension |
|
|||
28 | # hgext.keyword = |
|
|||
29 | # |
|
|||
30 | # Files to act upon/ignore are specified in the [keyword] section. |
|
24 | # Files to act upon/ignore are specified in the [keyword] section. | |
31 | # Customized keyword template mappings in the [keywordmaps] section. |
|
25 | # Customized keyword template mappings in the [keywordmaps] section. | |
32 | # |
|
26 | # | |
33 | # Run "hg help keyword" and "hg kwdemo" to get info on configuration. |
|
27 | # Run "hg help keyword" and "hg kwdemo" to get info on configuration. | |
34 |
|
28 | |||
35 | '''keyword expansion in tracked files |
|
29 | '''keyword expansion in tracked files | |
36 |
|
30 | |||
37 | This extension expands RCS/CVS-like or self-customized $Keywords$ in |
|
31 | This extension expands RCS/CVS-like or self-customized $Keywords$ in | |
38 | tracked text files selected by your configuration. |
|
32 | tracked text files selected by your configuration. | |
39 |
|
33 | |||
40 | Keywords are only expanded in local repositories and not stored in the |
|
34 | Keywords are only expanded in local repositories and not stored in the | |
41 | change history. The mechanism can be regarded as a convenience for the |
|
35 | change history. The mechanism can be regarded as a convenience for the | |
42 | current user or for archive distribution. |
|
36 | current user or for archive distribution. | |
43 |
|
37 | |||
44 | Configuration is done in the [keyword] and [keywordmaps] sections of |
|
38 | Configuration is done in the [keyword] and [keywordmaps] sections of | |
45 | hgrc files. |
|
39 | hgrc files. | |
46 |
|
40 | |||
47 | Example: |
|
41 | Example: | |
48 |
|
42 | |||
49 | [keyword] |
|
43 | [keyword] | |
50 | # expand keywords in every python file except those matching "x*" |
|
44 | # expand keywords in every python file except those matching "x*" | |
51 | **.py = |
|
45 | **.py = | |
52 | x* = ignore |
|
46 | x* = ignore | |
53 |
|
47 | |||
54 | Note: the more specific you are in your filename patterns |
|
48 | Note: the more specific you are in your filename patterns | |
55 | the less you lose speed in huge repositories. |
|
49 | the less you lose speed in huge repositories. | |
56 |
|
50 | |||
57 | For [keywordmaps] template mapping and expansion demonstration and |
|
51 | For [keywordmaps] template mapping and expansion demonstration and | |
58 | control run "hg kwdemo". |
|
52 | control run "hg kwdemo". | |
59 |
|
53 | |||
60 | An additional date template filter {date|utcdate} is provided. |
|
54 | An additional date template filter {date|utcdate} is provided. | |
61 |
|
55 | |||
62 | The default template mappings (view with "hg kwdemo -d") can be |
|
56 | The default template mappings (view with "hg kwdemo -d") can be | |
63 | replaced with customized keywords and templates. Again, run "hg |
|
57 | replaced with customized keywords and templates. Again, run "hg | |
64 | kwdemo" to control the results of your config changes. |
|
58 | kwdemo" to control the results of your config changes. | |
65 |
|
59 | |||
66 | Before changing/disabling active keywords, run "hg kwshrink" to avoid |
|
60 | Before changing/disabling active keywords, run "hg kwshrink" to avoid | |
67 | the risk of inadvertently storing expanded keywords in the change |
|
61 | the risk of inadvertently storing expanded keywords in the change | |
68 | history. |
|
62 | history. | |
69 |
|
63 | |||
70 | To force expansion after enabling it, or a configuration change, run |
|
64 | To force expansion after enabling it, or a configuration change, run | |
71 | "hg kwexpand". |
|
65 | "hg kwexpand". | |
72 |
|
66 | |||
73 | Also, when committing with the record extension or using mq's qrecord, |
|
67 | Also, when committing with the record extension or using mq's qrecord, | |
74 | be aware that keywords cannot be updated. Again, run "hg kwexpand" on |
|
68 | be aware that keywords cannot be updated. Again, run "hg kwexpand" on | |
75 | the files in question to update keyword expansions after all changes |
|
69 | the files in question to update keyword expansions after all changes | |
76 | have been checked in. |
|
70 | have been checked in. | |
77 |
|
71 | |||
78 | Expansions spanning more than one line and incremental expansions, |
|
72 | Expansions spanning more than one line and incremental expansions, | |
79 | like CVS' $Log$, are not supported. A keyword template map |
|
73 | like CVS' $Log$, are not supported. A keyword template map | |
80 | "Log = {desc}" expands to the first line of the changeset description. |
|
74 | "Log = {desc}" expands to the first line of the changeset description. | |
81 | ''' |
|
75 | ''' | |
82 |
|
76 | |||
83 | from mercurial import commands, cmdutil, dispatch, filelog, revlog, extensions |
|
77 | from mercurial import commands, cmdutil, dispatch, filelog, revlog, extensions | |
84 | from mercurial import patch, localrepo, templater, templatefilters, util, match |
|
78 | from mercurial import patch, localrepo, templater, templatefilters, util, match | |
85 | from mercurial.hgweb import webcommands |
|
79 | from mercurial.hgweb import webcommands | |
86 | from mercurial.lock import release |
|
80 | from mercurial.lock import release | |
87 | from mercurial.node import nullid, hex |
|
81 | from mercurial.node import nullid, hex | |
88 | from mercurial.i18n import _ |
|
82 | from mercurial.i18n import _ | |
89 | import re, shutil, tempfile, time |
|
83 | import re, shutil, tempfile, time | |
90 |
|
84 | |||
91 | commands.optionalrepo += ' kwdemo' |
|
85 | commands.optionalrepo += ' kwdemo' | |
92 |
|
86 | |||
93 | # hg commands that do not act on keywords |
|
87 | # hg commands that do not act on keywords | |
94 | nokwcommands = ('add addremove annotate bundle copy export grep incoming init' |
|
88 | nokwcommands = ('add addremove annotate bundle copy export grep incoming init' | |
95 | ' log outgoing push rename rollback tip verify' |
|
89 | ' log outgoing push rename rollback tip verify' | |
96 | ' convert email glog') |
|
90 | ' convert email glog') | |
97 |
|
91 | |||
98 | # hg commands that trigger expansion only when writing to working dir, |
|
92 | # hg commands that trigger expansion only when writing to working dir, | |
99 | # not when reading filelog, and unexpand when reading from working dir |
|
93 | # not when reading filelog, and unexpand when reading from working dir | |
100 | restricted = 'merge record resolve qfold qimport qnew qpush qrefresh qrecord' |
|
94 | restricted = 'merge record resolve qfold qimport qnew qpush qrefresh qrecord' | |
101 |
|
95 | |||
102 | def utcdate(date): |
|
96 | def utcdate(date): | |
103 | '''Returns hgdate in cvs-like UTC format.''' |
|
97 | '''Returns hgdate in cvs-like UTC format.''' | |
104 | return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0])) |
|
98 | return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0])) | |
105 |
|
99 | |||
106 | # make keyword tools accessible |
|
100 | # make keyword tools accessible | |
107 | kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']} |
|
101 | kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']} | |
108 |
|
102 | |||
109 |
|
103 | |||
110 | class kwtemplater(object): |
|
104 | class kwtemplater(object): | |
111 | ''' |
|
105 | ''' | |
112 | Sets up keyword templates, corresponding keyword regex, and |
|
106 | Sets up keyword templates, corresponding keyword regex, and | |
113 | provides keyword substitution functions. |
|
107 | provides keyword substitution functions. | |
114 | ''' |
|
108 | ''' | |
115 | templates = { |
|
109 | templates = { | |
116 | 'Revision': '{node|short}', |
|
110 | 'Revision': '{node|short}', | |
117 | 'Author': '{author|user}', |
|
111 | 'Author': '{author|user}', | |
118 | 'Date': '{date|utcdate}', |
|
112 | 'Date': '{date|utcdate}', | |
119 | 'RCSFile': '{file|basename},v', |
|
113 | 'RCSFile': '{file|basename},v', | |
120 | 'Source': '{root}/{file},v', |
|
114 | 'Source': '{root}/{file},v', | |
121 | 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}', |
|
115 | 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}', | |
122 | 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}', |
|
116 | 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}', | |
123 | } |
|
117 | } | |
124 |
|
118 | |||
125 | def __init__(self, ui, repo): |
|
119 | def __init__(self, ui, repo): | |
126 | self.ui = ui |
|
120 | self.ui = ui | |
127 | self.repo = repo |
|
121 | self.repo = repo | |
128 | self.match = match.match(repo.root, '', [], |
|
122 | self.match = match.match(repo.root, '', [], | |
129 | kwtools['inc'], kwtools['exc']) |
|
123 | kwtools['inc'], kwtools['exc']) | |
130 | self.restrict = kwtools['hgcmd'] in restricted.split() |
|
124 | self.restrict = kwtools['hgcmd'] in restricted.split() | |
131 |
|
125 | |||
132 | kwmaps = self.ui.configitems('keywordmaps') |
|
126 | kwmaps = self.ui.configitems('keywordmaps') | |
133 | if kwmaps: # override default templates |
|
127 | if kwmaps: # override default templates | |
134 | kwmaps = [(k, templater.parsestring(v, False)) |
|
128 | kwmaps = [(k, templater.parsestring(v, False)) | |
135 | for (k, v) in kwmaps] |
|
129 | for (k, v) in kwmaps] | |
136 | self.templates = dict(kwmaps) |
|
130 | self.templates = dict(kwmaps) | |
137 | escaped = map(re.escape, self.templates.keys()) |
|
131 | escaped = map(re.escape, self.templates.keys()) | |
138 | kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped) |
|
132 | kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped) | |
139 | self.re_kw = re.compile(kwpat) |
|
133 | self.re_kw = re.compile(kwpat) | |
140 |
|
134 | |||
141 | templatefilters.filters['utcdate'] = utcdate |
|
135 | templatefilters.filters['utcdate'] = utcdate | |
142 | self.ct = cmdutil.changeset_templater(self.ui, self.repo, |
|
136 | self.ct = cmdutil.changeset_templater(self.ui, self.repo, | |
143 | False, None, '', False) |
|
137 | False, None, '', False) | |
144 |
|
138 | |||
145 | def substitute(self, data, path, ctx, subfunc): |
|
139 | def substitute(self, data, path, ctx, subfunc): | |
146 | '''Replaces keywords in data with expanded template.''' |
|
140 | '''Replaces keywords in data with expanded template.''' | |
147 | def kwsub(mobj): |
|
141 | def kwsub(mobj): | |
148 | kw = mobj.group(1) |
|
142 | kw = mobj.group(1) | |
149 | self.ct.use_template(self.templates[kw]) |
|
143 | self.ct.use_template(self.templates[kw]) | |
150 | self.ui.pushbuffer() |
|
144 | self.ui.pushbuffer() | |
151 | self.ct.show(ctx, root=self.repo.root, file=path) |
|
145 | self.ct.show(ctx, root=self.repo.root, file=path) | |
152 | ekw = templatefilters.firstline(self.ui.popbuffer()) |
|
146 | ekw = templatefilters.firstline(self.ui.popbuffer()) | |
153 | return '$%s: %s $' % (kw, ekw) |
|
147 | return '$%s: %s $' % (kw, ekw) | |
154 | return subfunc(kwsub, data) |
|
148 | return subfunc(kwsub, data) | |
155 |
|
149 | |||
156 | def expand(self, path, node, data): |
|
150 | def expand(self, path, node, data): | |
157 | '''Returns data with keywords expanded.''' |
|
151 | '''Returns data with keywords expanded.''' | |
158 | if not self.restrict and self.match(path) and not util.binary(data): |
|
152 | if not self.restrict and self.match(path) and not util.binary(data): | |
159 | ctx = self.repo.filectx(path, fileid=node).changectx() |
|
153 | ctx = self.repo.filectx(path, fileid=node).changectx() | |
160 | return self.substitute(data, path, ctx, self.re_kw.sub) |
|
154 | return self.substitute(data, path, ctx, self.re_kw.sub) | |
161 | return data |
|
155 | return data | |
162 |
|
156 | |||
163 | def iskwfile(self, path, flagfunc): |
|
157 | def iskwfile(self, path, flagfunc): | |
164 | '''Returns true if path matches [keyword] pattern |
|
158 | '''Returns true if path matches [keyword] pattern | |
165 | and is not a symbolic link. |
|
159 | and is not a symbolic link. | |
166 | Caveat: localrepository._link fails on Windows.''' |
|
160 | Caveat: localrepository._link fails on Windows.''' | |
167 | return self.match(path) and not 'l' in flagfunc(path) |
|
161 | return self.match(path) and not 'l' in flagfunc(path) | |
168 |
|
162 | |||
169 | def overwrite(self, node, expand, files): |
|
163 | def overwrite(self, node, expand, files): | |
170 | '''Overwrites selected files expanding/shrinking keywords.''' |
|
164 | '''Overwrites selected files expanding/shrinking keywords.''' | |
171 | ctx = self.repo[node] |
|
165 | ctx = self.repo[node] | |
172 | mf = ctx.manifest() |
|
166 | mf = ctx.manifest() | |
173 | if node is not None: # commit |
|
167 | if node is not None: # commit | |
174 | files = [f for f in ctx.files() if f in mf] |
|
168 | files = [f for f in ctx.files() if f in mf] | |
175 | notify = self.ui.debug |
|
169 | notify = self.ui.debug | |
176 | else: # kwexpand/kwshrink |
|
170 | else: # kwexpand/kwshrink | |
177 | notify = self.ui.note |
|
171 | notify = self.ui.note | |
178 | candidates = [f for f in files if self.iskwfile(f, ctx.flags)] |
|
172 | candidates = [f for f in files if self.iskwfile(f, ctx.flags)] | |
179 | if candidates: |
|
173 | if candidates: | |
180 | self.restrict = True # do not expand when reading |
|
174 | self.restrict = True # do not expand when reading | |
181 | msg = (expand and _('overwriting %s expanding keywords\n') |
|
175 | msg = (expand and _('overwriting %s expanding keywords\n') | |
182 | or _('overwriting %s shrinking keywords\n')) |
|
176 | or _('overwriting %s shrinking keywords\n')) | |
183 | for f in candidates: |
|
177 | for f in candidates: | |
184 | fp = self.repo.file(f) |
|
178 | fp = self.repo.file(f) | |
185 | data = fp.read(mf[f]) |
|
179 | data = fp.read(mf[f]) | |
186 | if util.binary(data): |
|
180 | if util.binary(data): | |
187 | continue |
|
181 | continue | |
188 | if expand: |
|
182 | if expand: | |
189 | if node is None: |
|
183 | if node is None: | |
190 | ctx = self.repo.filectx(f, fileid=mf[f]).changectx() |
|
184 | ctx = self.repo.filectx(f, fileid=mf[f]).changectx() | |
191 | data, found = self.substitute(data, f, ctx, |
|
185 | data, found = self.substitute(data, f, ctx, | |
192 | self.re_kw.subn) |
|
186 | self.re_kw.subn) | |
193 | else: |
|
187 | else: | |
194 | found = self.re_kw.search(data) |
|
188 | found = self.re_kw.search(data) | |
195 | if found: |
|
189 | if found: | |
196 | notify(msg % f) |
|
190 | notify(msg % f) | |
197 | self.repo.wwrite(f, data, mf.flags(f)) |
|
191 | self.repo.wwrite(f, data, mf.flags(f)) | |
198 | self.repo.dirstate.normal(f) |
|
192 | self.repo.dirstate.normal(f) | |
199 | self.restrict = False |
|
193 | self.restrict = False | |
200 |
|
194 | |||
201 | def shrinktext(self, text): |
|
195 | def shrinktext(self, text): | |
202 | '''Unconditionally removes all keyword substitutions from text.''' |
|
196 | '''Unconditionally removes all keyword substitutions from text.''' | |
203 | return self.re_kw.sub(r'$\1$', text) |
|
197 | return self.re_kw.sub(r'$\1$', text) | |
204 |
|
198 | |||
205 | def shrink(self, fname, text): |
|
199 | def shrink(self, fname, text): | |
206 | '''Returns text with all keyword substitutions removed.''' |
|
200 | '''Returns text with all keyword substitutions removed.''' | |
207 | if self.match(fname) and not util.binary(text): |
|
201 | if self.match(fname) and not util.binary(text): | |
208 | return self.shrinktext(text) |
|
202 | return self.shrinktext(text) | |
209 | return text |
|
203 | return text | |
210 |
|
204 | |||
211 | def shrinklines(self, fname, lines): |
|
205 | def shrinklines(self, fname, lines): | |
212 | '''Returns lines with keyword substitutions removed.''' |
|
206 | '''Returns lines with keyword substitutions removed.''' | |
213 | if self.match(fname): |
|
207 | if self.match(fname): | |
214 | text = ''.join(lines) |
|
208 | text = ''.join(lines) | |
215 | if not util.binary(text): |
|
209 | if not util.binary(text): | |
216 | return self.shrinktext(text).splitlines(True) |
|
210 | return self.shrinktext(text).splitlines(True) | |
217 | return lines |
|
211 | return lines | |
218 |
|
212 | |||
219 | def wread(self, fname, data): |
|
213 | def wread(self, fname, data): | |
220 | '''If in restricted mode returns data read from wdir with |
|
214 | '''If in restricted mode returns data read from wdir with | |
221 | keyword substitutions removed.''' |
|
215 | keyword substitutions removed.''' | |
222 | return self.restrict and self.shrink(fname, data) or data |
|
216 | return self.restrict and self.shrink(fname, data) or data | |
223 |
|
217 | |||
224 | class kwfilelog(filelog.filelog): |
|
218 | class kwfilelog(filelog.filelog): | |
225 | ''' |
|
219 | ''' | |
226 | Subclass of filelog to hook into its read, add, cmp methods. |
|
220 | Subclass of filelog to hook into its read, add, cmp methods. | |
227 | Keywords are "stored" unexpanded, and processed on reading. |
|
221 | Keywords are "stored" unexpanded, and processed on reading. | |
228 | ''' |
|
222 | ''' | |
229 | def __init__(self, opener, kwt, path): |
|
223 | def __init__(self, opener, kwt, path): | |
230 | super(kwfilelog, self).__init__(opener, path) |
|
224 | super(kwfilelog, self).__init__(opener, path) | |
231 | self.kwt = kwt |
|
225 | self.kwt = kwt | |
232 | self.path = path |
|
226 | self.path = path | |
233 |
|
227 | |||
234 | def read(self, node): |
|
228 | def read(self, node): | |
235 | '''Expands keywords when reading filelog.''' |
|
229 | '''Expands keywords when reading filelog.''' | |
236 | data = super(kwfilelog, self).read(node) |
|
230 | data = super(kwfilelog, self).read(node) | |
237 | return self.kwt.expand(self.path, node, data) |
|
231 | return self.kwt.expand(self.path, node, data) | |
238 |
|
232 | |||
239 | def add(self, text, meta, tr, link, p1=None, p2=None): |
|
233 | def add(self, text, meta, tr, link, p1=None, p2=None): | |
240 | '''Removes keyword substitutions when adding to filelog.''' |
|
234 | '''Removes keyword substitutions when adding to filelog.''' | |
241 | text = self.kwt.shrink(self.path, text) |
|
235 | text = self.kwt.shrink(self.path, text) | |
242 | return super(kwfilelog, self).add(text, meta, tr, link, p1, p2) |
|
236 | return super(kwfilelog, self).add(text, meta, tr, link, p1, p2) | |
243 |
|
237 | |||
244 | def cmp(self, node, text): |
|
238 | def cmp(self, node, text): | |
245 | '''Removes keyword substitutions for comparison.''' |
|
239 | '''Removes keyword substitutions for comparison.''' | |
246 | text = self.kwt.shrink(self.path, text) |
|
240 | text = self.kwt.shrink(self.path, text) | |
247 | if self.renamed(node): |
|
241 | if self.renamed(node): | |
248 | t2 = super(kwfilelog, self).read(node) |
|
242 | t2 = super(kwfilelog, self).read(node) | |
249 | return t2 != text |
|
243 | return t2 != text | |
250 | return revlog.revlog.cmp(self, node, text) |
|
244 | return revlog.revlog.cmp(self, node, text) | |
251 |
|
245 | |||
252 | def _status(ui, repo, kwt, unknown, *pats, **opts): |
|
246 | def _status(ui, repo, kwt, unknown, *pats, **opts): | |
253 | '''Bails out if [keyword] configuration is not active. |
|
247 | '''Bails out if [keyword] configuration is not active. | |
254 | Returns status of working directory.''' |
|
248 | Returns status of working directory.''' | |
255 | if kwt: |
|
249 | if kwt: | |
256 | match = cmdutil.match(repo, pats, opts) |
|
250 | match = cmdutil.match(repo, pats, opts) | |
257 | return repo.status(match=match, unknown=unknown, clean=True) |
|
251 | return repo.status(match=match, unknown=unknown, clean=True) | |
258 | if ui.configitems('keyword'): |
|
252 | if ui.configitems('keyword'): | |
259 | raise util.Abort(_('[keyword] patterns cannot match')) |
|
253 | raise util.Abort(_('[keyword] patterns cannot match')) | |
260 | raise util.Abort(_('no [keyword] patterns configured')) |
|
254 | raise util.Abort(_('no [keyword] patterns configured')) | |
261 |
|
255 | |||
262 | def _kwfwrite(ui, repo, expand, *pats, **opts): |
|
256 | def _kwfwrite(ui, repo, expand, *pats, **opts): | |
263 | '''Selects files and passes them to kwtemplater.overwrite.''' |
|
257 | '''Selects files and passes them to kwtemplater.overwrite.''' | |
264 | if repo.dirstate.parents()[1] != nullid: |
|
258 | if repo.dirstate.parents()[1] != nullid: | |
265 | raise util.Abort(_('outstanding uncommitted merge')) |
|
259 | raise util.Abort(_('outstanding uncommitted merge')) | |
266 | kwt = kwtools['templater'] |
|
260 | kwt = kwtools['templater'] | |
267 | status = _status(ui, repo, kwt, False, *pats, **opts) |
|
261 | status = _status(ui, repo, kwt, False, *pats, **opts) | |
268 | modified, added, removed, deleted = status[:4] |
|
262 | modified, added, removed, deleted = status[:4] | |
269 | if modified or added or removed or deleted: |
|
263 | if modified or added or removed or deleted: | |
270 | raise util.Abort(_('outstanding uncommitted changes')) |
|
264 | raise util.Abort(_('outstanding uncommitted changes')) | |
271 | wlock = lock = None |
|
265 | wlock = lock = None | |
272 | try: |
|
266 | try: | |
273 | wlock = repo.wlock() |
|
267 | wlock = repo.wlock() | |
274 | lock = repo.lock() |
|
268 | lock = repo.lock() | |
275 | kwt.overwrite(None, expand, status[6]) |
|
269 | kwt.overwrite(None, expand, status[6]) | |
276 | finally: |
|
270 | finally: | |
277 | release(lock, wlock) |
|
271 | release(lock, wlock) | |
278 |
|
272 | |||
279 | def demo(ui, repo, *args, **opts): |
|
273 | def demo(ui, repo, *args, **opts): | |
280 | '''print [keywordmaps] configuration and an expansion example |
|
274 | '''print [keywordmaps] configuration and an expansion example | |
281 |
|
275 | |||
282 | Show current, custom, or default keyword template maps and their |
|
276 | Show current, custom, or default keyword template maps and their | |
283 | expansions. |
|
277 | expansions. | |
284 |
|
278 | |||
285 | Extend current configuration by specifying maps as arguments and |
|
279 | Extend current configuration by specifying maps as arguments and | |
286 | optionally by reading from an additional hgrc file. |
|
280 | optionally by reading from an additional hgrc file. | |
287 |
|
281 | |||
288 | Override current keyword template maps with "default" option. |
|
282 | Override current keyword template maps with "default" option. | |
289 | ''' |
|
283 | ''' | |
290 | def demostatus(stat): |
|
284 | def demostatus(stat): | |
291 | ui.status(_('\n\t%s\n') % stat) |
|
285 | ui.status(_('\n\t%s\n') % stat) | |
292 |
|
286 | |||
293 | def demoitems(section, items): |
|
287 | def demoitems(section, items): | |
294 | ui.write('[%s]\n' % section) |
|
288 | ui.write('[%s]\n' % section) | |
295 | for k, v in items: |
|
289 | for k, v in items: | |
296 | ui.write('%s = %s\n' % (k, v)) |
|
290 | ui.write('%s = %s\n' % (k, v)) | |
297 |
|
291 | |||
298 | msg = 'hg keyword config and expansion example' |
|
292 | msg = 'hg keyword config and expansion example' | |
299 | kwstatus = 'current' |
|
293 | kwstatus = 'current' | |
300 | fn = 'demo.txt' |
|
294 | fn = 'demo.txt' | |
301 | branchname = 'demobranch' |
|
295 | branchname = 'demobranch' | |
302 | tmpdir = tempfile.mkdtemp('', 'kwdemo.') |
|
296 | tmpdir = tempfile.mkdtemp('', 'kwdemo.') | |
303 | ui.note(_('creating temporary repository at %s\n') % tmpdir) |
|
297 | ui.note(_('creating temporary repository at %s\n') % tmpdir) | |
304 | repo = localrepo.localrepository(ui, tmpdir, True) |
|
298 | repo = localrepo.localrepository(ui, tmpdir, True) | |
305 | ui.setconfig('keyword', fn, '') |
|
299 | ui.setconfig('keyword', fn, '') | |
306 | if args or opts.get('rcfile'): |
|
300 | if args or opts.get('rcfile'): | |
307 | kwstatus = 'custom' |
|
301 | kwstatus = 'custom' | |
308 | if opts.get('rcfile'): |
|
302 | if opts.get('rcfile'): | |
309 | ui.readconfig(opts.get('rcfile')) |
|
303 | ui.readconfig(opts.get('rcfile')) | |
310 | if opts.get('default'): |
|
304 | if opts.get('default'): | |
311 | kwstatus = 'default' |
|
305 | kwstatus = 'default' | |
312 | kwmaps = kwtemplater.templates |
|
306 | kwmaps = kwtemplater.templates | |
313 | if ui.configitems('keywordmaps'): |
|
307 | if ui.configitems('keywordmaps'): | |
314 | # override maps from optional rcfile |
|
308 | # override maps from optional rcfile | |
315 | for k, v in kwmaps.iteritems(): |
|
309 | for k, v in kwmaps.iteritems(): | |
316 | ui.setconfig('keywordmaps', k, v) |
|
310 | ui.setconfig('keywordmaps', k, v) | |
317 | elif args: |
|
311 | elif args: | |
318 | # simulate hgrc parsing |
|
312 | # simulate hgrc parsing | |
319 | rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args] |
|
313 | rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args] | |
320 | fp = repo.opener('hgrc', 'w') |
|
314 | fp = repo.opener('hgrc', 'w') | |
321 | fp.writelines(rcmaps) |
|
315 | fp.writelines(rcmaps) | |
322 | fp.close() |
|
316 | fp.close() | |
323 | ui.readconfig(repo.join('hgrc')) |
|
317 | ui.readconfig(repo.join('hgrc')) | |
324 | if not opts.get('default'): |
|
318 | if not opts.get('default'): | |
325 | kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates |
|
319 | kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates | |
326 | uisetup(ui) |
|
320 | uisetup(ui) | |
327 | reposetup(ui, repo) |
|
321 | reposetup(ui, repo) | |
328 | for k, v in ui.configitems('extensions'): |
|
322 | for k, v in ui.configitems('extensions'): | |
329 | if k.endswith('keyword'): |
|
323 | if k.endswith('keyword'): | |
330 | extension = '%s = %s' % (k, v) |
|
324 | extension = '%s = %s' % (k, v) | |
331 | break |
|
325 | break | |
332 | demostatus('config using %s keyword template maps' % kwstatus) |
|
326 | demostatus('config using %s keyword template maps' % kwstatus) | |
333 | ui.write('[extensions]\n%s\n' % extension) |
|
327 | ui.write('[extensions]\n%s\n' % extension) | |
334 | demoitems('keyword', ui.configitems('keyword')) |
|
328 | demoitems('keyword', ui.configitems('keyword')) | |
335 | demoitems('keywordmaps', kwmaps.iteritems()) |
|
329 | demoitems('keywordmaps', kwmaps.iteritems()) | |
336 | keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n' |
|
330 | keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n' | |
337 | repo.wopener(fn, 'w').write(keywords) |
|
331 | repo.wopener(fn, 'w').write(keywords) | |
338 | repo.add([fn]) |
|
332 | repo.add([fn]) | |
339 | path = repo.wjoin(fn) |
|
333 | path = repo.wjoin(fn) | |
340 | ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path)) |
|
334 | ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path)) | |
341 | ui.note(keywords) |
|
335 | ui.note(keywords) | |
342 | ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname)) |
|
336 | ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname)) | |
343 | # silence branch command if not verbose |
|
337 | # silence branch command if not verbose | |
344 | quiet = ui.quiet |
|
338 | quiet = ui.quiet | |
345 | ui.quiet = not ui.verbose |
|
339 | ui.quiet = not ui.verbose | |
346 | commands.branch(ui, repo, branchname) |
|
340 | commands.branch(ui, repo, branchname) | |
347 | ui.quiet = quiet |
|
341 | ui.quiet = quiet | |
348 | for name, cmd in ui.configitems('hooks'): |
|
342 | for name, cmd in ui.configitems('hooks'): | |
349 | if name.split('.', 1)[0].find('commit') > -1: |
|
343 | if name.split('.', 1)[0].find('commit') > -1: | |
350 | repo.ui.setconfig('hooks', name, '') |
|
344 | repo.ui.setconfig('hooks', name, '') | |
351 | ui.note(_('unhooked all commit hooks\n')) |
|
345 | ui.note(_('unhooked all commit hooks\n')) | |
352 | ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg)) |
|
346 | ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg)) | |
353 | repo.commit(text=msg) |
|
347 | repo.commit(text=msg) | |
354 | fmt = ui.verbose and ' in %s' % path or '' |
|
348 | fmt = ui.verbose and ' in %s' % path or '' | |
355 | demostatus('%s keywords expanded%s' % (kwstatus, fmt)) |
|
349 | demostatus('%s keywords expanded%s' % (kwstatus, fmt)) | |
356 | ui.write(repo.wread(fn)) |
|
350 | ui.write(repo.wread(fn)) | |
357 | ui.debug(_('\nremoving temporary repository %s\n') % tmpdir) |
|
351 | ui.debug(_('\nremoving temporary repository %s\n') % tmpdir) | |
358 | shutil.rmtree(tmpdir, ignore_errors=True) |
|
352 | shutil.rmtree(tmpdir, ignore_errors=True) | |
359 |
|
353 | |||
360 | def expand(ui, repo, *pats, **opts): |
|
354 | def expand(ui, repo, *pats, **opts): | |
361 | '''expand keywords in the working directory |
|
355 | '''expand keywords in the working directory | |
362 |
|
356 | |||
363 | Run after (re)enabling keyword expansion. |
|
357 | Run after (re)enabling keyword expansion. | |
364 |
|
358 | |||
365 | kwexpand refuses to run if given files contain local changes. |
|
359 | kwexpand refuses to run if given files contain local changes. | |
366 | ''' |
|
360 | ''' | |
367 | # 3rd argument sets expansion to True |
|
361 | # 3rd argument sets expansion to True | |
368 | _kwfwrite(ui, repo, True, *pats, **opts) |
|
362 | _kwfwrite(ui, repo, True, *pats, **opts) | |
369 |
|
363 | |||
370 | def files(ui, repo, *pats, **opts): |
|
364 | def files(ui, repo, *pats, **opts): | |
371 | '''print files currently configured for keyword expansion |
|
365 | '''print files currently configured for keyword expansion | |
372 |
|
366 | |||
373 | Crosscheck which files in working directory are potential targets |
|
367 | Crosscheck which files in working directory are potential targets | |
374 | for keyword expansion. That is, files matched by [keyword] config |
|
368 | for keyword expansion. That is, files matched by [keyword] config | |
375 | patterns but not symlinks. |
|
369 | patterns but not symlinks. | |
376 | ''' |
|
370 | ''' | |
377 | kwt = kwtools['templater'] |
|
371 | kwt = kwtools['templater'] | |
378 | status = _status(ui, repo, kwt, opts.get('untracked'), *pats, **opts) |
|
372 | status = _status(ui, repo, kwt, opts.get('untracked'), *pats, **opts) | |
379 | modified, added, removed, deleted, unknown, ignored, clean = status |
|
373 | modified, added, removed, deleted, unknown, ignored, clean = status | |
380 | files = sorted(modified + added + clean + unknown) |
|
374 | files = sorted(modified + added + clean + unknown) | |
381 | wctx = repo[None] |
|
375 | wctx = repo[None] | |
382 | kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)] |
|
376 | kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)] | |
383 | cwd = pats and repo.getcwd() or '' |
|
377 | cwd = pats and repo.getcwd() or '' | |
384 | kwfstats = not opts.get('ignore') and (('K', kwfiles),) or () |
|
378 | kwfstats = not opts.get('ignore') and (('K', kwfiles),) or () | |
385 | if opts.get('all') or opts.get('ignore'): |
|
379 | if opts.get('all') or opts.get('ignore'): | |
386 | kwfstats += (('I', [f for f in files if f not in kwfiles]),) |
|
380 | kwfstats += (('I', [f for f in files if f not in kwfiles]),) | |
387 | for char, filenames in kwfstats: |
|
381 | for char, filenames in kwfstats: | |
388 | fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n' |
|
382 | fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n' | |
389 | for f in filenames: |
|
383 | for f in filenames: | |
390 | ui.write(fmt % repo.pathto(f, cwd)) |
|
384 | ui.write(fmt % repo.pathto(f, cwd)) | |
391 |
|
385 | |||
392 | def shrink(ui, repo, *pats, **opts): |
|
386 | def shrink(ui, repo, *pats, **opts): | |
393 | '''revert expanded keywords in the working directory |
|
387 | '''revert expanded keywords in the working directory | |
394 |
|
388 | |||
395 | Run before changing/disabling active keywords or if you experience |
|
389 | Run before changing/disabling active keywords or if you experience | |
396 | problems with "hg import" or "hg merge". |
|
390 | problems with "hg import" or "hg merge". | |
397 |
|
391 | |||
398 | kwshrink refuses to run if given files contain local changes. |
|
392 | kwshrink refuses to run if given files contain local changes. | |
399 | ''' |
|
393 | ''' | |
400 | # 3rd argument sets expansion to False |
|
394 | # 3rd argument sets expansion to False | |
401 | _kwfwrite(ui, repo, False, *pats, **opts) |
|
395 | _kwfwrite(ui, repo, False, *pats, **opts) | |
402 |
|
396 | |||
403 |
|
397 | |||
404 | def uisetup(ui): |
|
398 | def uisetup(ui): | |
405 | '''Collects [keyword] config in kwtools. |
|
399 | '''Collects [keyword] config in kwtools. | |
406 | Monkeypatches dispatch._parse if needed.''' |
|
400 | Monkeypatches dispatch._parse if needed.''' | |
407 |
|
401 | |||
408 | for pat, opt in ui.configitems('keyword'): |
|
402 | for pat, opt in ui.configitems('keyword'): | |
409 | if opt != 'ignore': |
|
403 | if opt != 'ignore': | |
410 | kwtools['inc'].append(pat) |
|
404 | kwtools['inc'].append(pat) | |
411 | else: |
|
405 | else: | |
412 | kwtools['exc'].append(pat) |
|
406 | kwtools['exc'].append(pat) | |
413 |
|
407 | |||
414 | if kwtools['inc']: |
|
408 | if kwtools['inc']: | |
415 | def kwdispatch_parse(orig, ui, args): |
|
409 | def kwdispatch_parse(orig, ui, args): | |
416 | '''Monkeypatch dispatch._parse to obtain running hg command.''' |
|
410 | '''Monkeypatch dispatch._parse to obtain running hg command.''' | |
417 | cmd, func, args, options, cmdoptions = orig(ui, args) |
|
411 | cmd, func, args, options, cmdoptions = orig(ui, args) | |
418 | kwtools['hgcmd'] = cmd |
|
412 | kwtools['hgcmd'] = cmd | |
419 | return cmd, func, args, options, cmdoptions |
|
413 | return cmd, func, args, options, cmdoptions | |
420 |
|
414 | |||
421 | extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse) |
|
415 | extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse) | |
422 |
|
416 | |||
423 | def reposetup(ui, repo): |
|
417 | def reposetup(ui, repo): | |
424 | '''Sets up repo as kwrepo for keyword substitution. |
|
418 | '''Sets up repo as kwrepo for keyword substitution. | |
425 | Overrides file method to return kwfilelog instead of filelog |
|
419 | Overrides file method to return kwfilelog instead of filelog | |
426 | if file matches user configuration. |
|
420 | if file matches user configuration. | |
427 | Wraps commit to overwrite configured files with updated |
|
421 | Wraps commit to overwrite configured files with updated | |
428 | keyword substitutions. |
|
422 | keyword substitutions. | |
429 | Monkeypatches patch and webcommands.''' |
|
423 | Monkeypatches patch and webcommands.''' | |
430 |
|
424 | |||
431 | try: |
|
425 | try: | |
432 | if (not repo.local() or not kwtools['inc'] |
|
426 | if (not repo.local() or not kwtools['inc'] | |
433 | or kwtools['hgcmd'] in nokwcommands.split() |
|
427 | or kwtools['hgcmd'] in nokwcommands.split() | |
434 | or '.hg' in util.splitpath(repo.root) |
|
428 | or '.hg' in util.splitpath(repo.root) | |
435 | or repo._url.startswith('bundle:')): |
|
429 | or repo._url.startswith('bundle:')): | |
436 | return |
|
430 | return | |
437 | except AttributeError: |
|
431 | except AttributeError: | |
438 | pass |
|
432 | pass | |
439 |
|
433 | |||
440 | kwtools['templater'] = kwt = kwtemplater(ui, repo) |
|
434 | kwtools['templater'] = kwt = kwtemplater(ui, repo) | |
441 |
|
435 | |||
442 | class kwrepo(repo.__class__): |
|
436 | class kwrepo(repo.__class__): | |
443 | def file(self, f): |
|
437 | def file(self, f): | |
444 | if f[0] == '/': |
|
438 | if f[0] == '/': | |
445 | f = f[1:] |
|
439 | f = f[1:] | |
446 | return kwfilelog(self.sopener, kwt, f) |
|
440 | return kwfilelog(self.sopener, kwt, f) | |
447 |
|
441 | |||
448 | def wread(self, filename): |
|
442 | def wread(self, filename): | |
449 | data = super(kwrepo, self).wread(filename) |
|
443 | data = super(kwrepo, self).wread(filename) | |
450 | return kwt.wread(filename, data) |
|
444 | return kwt.wread(filename, data) | |
451 |
|
445 | |||
452 | def commit(self, text='', user=None, date=None, match=None, |
|
446 | def commit(self, text='', user=None, date=None, match=None, | |
453 | force=False, editor=None, extra={}): |
|
447 | force=False, editor=None, extra={}): | |
454 | wlock = lock = None |
|
448 | wlock = lock = None | |
455 | _p1 = _p2 = None |
|
449 | _p1 = _p2 = None | |
456 | try: |
|
450 | try: | |
457 | wlock = self.wlock() |
|
451 | wlock = self.wlock() | |
458 | lock = self.lock() |
|
452 | lock = self.lock() | |
459 | # store and postpone commit hooks |
|
453 | # store and postpone commit hooks | |
460 | commithooks = {} |
|
454 | commithooks = {} | |
461 | for name, cmd in ui.configitems('hooks'): |
|
455 | for name, cmd in ui.configitems('hooks'): | |
462 | if name.split('.', 1)[0] == 'commit': |
|
456 | if name.split('.', 1)[0] == 'commit': | |
463 | commithooks[name] = cmd |
|
457 | commithooks[name] = cmd | |
464 | ui.setconfig('hooks', name, None) |
|
458 | ui.setconfig('hooks', name, None) | |
465 | if commithooks: |
|
459 | if commithooks: | |
466 | # store parents for commit hook environment |
|
460 | # store parents for commit hook environment | |
467 | _p1, _p2 = repo.dirstate.parents() |
|
461 | _p1, _p2 = repo.dirstate.parents() | |
468 | _p1 = hex(_p1) |
|
462 | _p1 = hex(_p1) | |
469 | if _p2 == nullid: |
|
463 | if _p2 == nullid: | |
470 | _p2 = '' |
|
464 | _p2 = '' | |
471 | else: |
|
465 | else: | |
472 | _p2 = hex(_p2) |
|
466 | _p2 = hex(_p2) | |
473 |
|
467 | |||
474 | n = super(kwrepo, self).commit(text, user, date, match, force, |
|
468 | n = super(kwrepo, self).commit(text, user, date, match, force, | |
475 | editor, extra) |
|
469 | editor, extra) | |
476 |
|
470 | |||
477 | # restore commit hooks |
|
471 | # restore commit hooks | |
478 | for name, cmd in commithooks.iteritems(): |
|
472 | for name, cmd in commithooks.iteritems(): | |
479 | ui.setconfig('hooks', name, cmd) |
|
473 | ui.setconfig('hooks', name, cmd) | |
480 | if n is not None: |
|
474 | if n is not None: | |
481 | kwt.overwrite(n, True, None) |
|
475 | kwt.overwrite(n, True, None) | |
482 | repo.hook('commit', node=n, parent1=_p1, parent2=_p2) |
|
476 | repo.hook('commit', node=n, parent1=_p1, parent2=_p2) | |
483 | return n |
|
477 | return n | |
484 | finally: |
|
478 | finally: | |
485 | release(lock, wlock) |
|
479 | release(lock, wlock) | |
486 |
|
480 | |||
487 | # monkeypatches |
|
481 | # monkeypatches | |
488 | def kwpatchfile_init(orig, self, ui, fname, opener, missing=False, eol=None): |
|
482 | def kwpatchfile_init(orig, self, ui, fname, opener, missing=False, eol=None): | |
489 | '''Monkeypatch/wrap patch.patchfile.__init__ to avoid |
|
483 | '''Monkeypatch/wrap patch.patchfile.__init__ to avoid | |
490 | rejects or conflicts due to expanded keywords in working dir.''' |
|
484 | rejects or conflicts due to expanded keywords in working dir.''' | |
491 | orig(self, ui, fname, opener, missing, eol) |
|
485 | orig(self, ui, fname, opener, missing, eol) | |
492 | # shrink keywords read from working dir |
|
486 | # shrink keywords read from working dir | |
493 | self.lines = kwt.shrinklines(self.fname, self.lines) |
|
487 | self.lines = kwt.shrinklines(self.fname, self.lines) | |
494 |
|
488 | |||
495 | def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None, |
|
489 | def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None, | |
496 | opts=None): |
|
490 | opts=None): | |
497 | '''Monkeypatch patch.diff to avoid expansion except when |
|
491 | '''Monkeypatch patch.diff to avoid expansion except when | |
498 | comparing against working dir.''' |
|
492 | comparing against working dir.''' | |
499 | if node2 is not None: |
|
493 | if node2 is not None: | |
500 | kwt.match = util.never |
|
494 | kwt.match = util.never | |
501 | elif node1 is not None and node1 != repo['.'].node(): |
|
495 | elif node1 is not None and node1 != repo['.'].node(): | |
502 | kwt.restrict = True |
|
496 | kwt.restrict = True | |
503 | return orig(repo, node1, node2, match, changes, opts) |
|
497 | return orig(repo, node1, node2, match, changes, opts) | |
504 |
|
498 | |||
505 | def kwweb_skip(orig, web, req, tmpl): |
|
499 | def kwweb_skip(orig, web, req, tmpl): | |
506 | '''Wraps webcommands.x turning off keyword expansion.''' |
|
500 | '''Wraps webcommands.x turning off keyword expansion.''' | |
507 | kwt.match = util.never |
|
501 | kwt.match = util.never | |
508 | return orig(web, req, tmpl) |
|
502 | return orig(web, req, tmpl) | |
509 |
|
503 | |||
510 | repo.__class__ = kwrepo |
|
504 | repo.__class__ = kwrepo | |
511 |
|
505 | |||
512 | extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init) |
|
506 | extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init) | |
513 | extensions.wrapfunction(patch, 'diff', kw_diff) |
|
507 | extensions.wrapfunction(patch, 'diff', kw_diff) | |
514 | for c in 'annotate changeset rev filediff diff'.split(): |
|
508 | for c in 'annotate changeset rev filediff diff'.split(): | |
515 | extensions.wrapfunction(webcommands, c, kwweb_skip) |
|
509 | extensions.wrapfunction(webcommands, c, kwweb_skip) | |
516 |
|
510 | |||
517 | cmdtable = { |
|
511 | cmdtable = { | |
518 | 'kwdemo': |
|
512 | 'kwdemo': | |
519 | (demo, |
|
513 | (demo, | |
520 | [('d', 'default', None, _('show default keyword template maps')), |
|
514 | [('d', 'default', None, _('show default keyword template maps')), | |
521 | ('f', 'rcfile', [], _('read maps from rcfile'))], |
|
515 | ('f', 'rcfile', [], _('read maps from rcfile'))], | |
522 | _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')), |
|
516 | _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')), | |
523 | 'kwexpand': (expand, commands.walkopts, |
|
517 | 'kwexpand': (expand, commands.walkopts, | |
524 | _('hg kwexpand [OPTION]... [FILE]...')), |
|
518 | _('hg kwexpand [OPTION]... [FILE]...')), | |
525 | 'kwfiles': |
|
519 | 'kwfiles': | |
526 | (files, |
|
520 | (files, | |
527 | [('a', 'all', None, _('show keyword status flags of all files')), |
|
521 | [('a', 'all', None, _('show keyword status flags of all files')), | |
528 | ('i', 'ignore', None, _('show files excluded from expansion')), |
|
522 | ('i', 'ignore', None, _('show files excluded from expansion')), | |
529 | ('u', 'untracked', None, _('additionally show untracked files')), |
|
523 | ('u', 'untracked', None, _('additionally show untracked files')), | |
530 | ] + commands.walkopts, |
|
524 | ] + commands.walkopts, | |
531 | _('hg kwfiles [OPTION]... [FILE]...')), |
|
525 | _('hg kwfiles [OPTION]... [FILE]...')), | |
532 | 'kwshrink': (shrink, commands.walkopts, |
|
526 | 'kwshrink': (shrink, commands.walkopts, | |
533 | _('hg kwshrink [OPTION]... [FILE]...')), |
|
527 | _('hg kwshrink [OPTION]... [FILE]...')), | |
534 | } |
|
528 | } |
@@ -1,512 +1,507 b'' | |||||
1 | # patchbomb.py - sending Mercurial changesets as patch emails |
|
1 | # patchbomb.py - sending Mercurial changesets as patch emails | |
2 | # |
|
2 | # | |
3 | # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others |
|
3 | # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others | |
4 | # |
|
4 | # | |
5 | # This software may be used and distributed according to the terms of the |
|
5 | # This software may be used and distributed according to the terms of the | |
6 | # GNU General Public License version 2, incorporated herein by reference. |
|
6 | # GNU General Public License version 2, incorporated herein by reference. | |
7 |
|
7 | |||
8 | '''sending Mercurial changesets as a series of patch emails |
|
8 | '''sending Mercurial changesets as a series of patch emails | |
9 |
|
9 | |||
10 | The series is started off with a "[PATCH 0 of N]" introduction, which |
|
10 | The series is started off with a "[PATCH 0 of N]" introduction, which | |
11 | describes the series as a whole. |
|
11 | describes the series as a whole. | |
12 |
|
12 | |||
13 | Each patch email has a Subject line of "[PATCH M of N] ...", using the |
|
13 | Each patch email has a Subject line of "[PATCH M of N] ...", using the | |
14 | first line of the changeset description as the subject text. The |
|
14 | first line of the changeset description as the subject text. The | |
15 | message contains two or three body parts: |
|
15 | message contains two or three body parts: | |
16 |
|
16 | |||
17 | The changeset description. |
|
17 | The changeset description. | |
18 |
|
18 | |||
19 | [Optional] The result of running diffstat on the patch. |
|
19 | [Optional] The result of running diffstat on the patch. | |
20 |
|
20 | |||
21 | The patch itself, as generated by "hg export". |
|
21 | The patch itself, as generated by "hg export". | |
22 |
|
22 | |||
23 | Each message refers to the first in the series using the In-Reply-To |
|
23 | Each message refers to the first in the series using the In-Reply-To | |
24 | and References headers, so they will show up as a sequence in threaded |
|
24 | and References headers, so they will show up as a sequence in threaded | |
25 | mail and news readers, and in mail archives. |
|
25 | mail and news readers, and in mail archives. | |
26 |
|
26 | |||
27 | With the -d/--diffstat option, you will be prompted for each changeset |
|
27 | With the -d/--diffstat option, you will be prompted for each changeset | |
28 | with a diffstat summary and the changeset summary, so you can be sure |
|
28 | with a diffstat summary and the changeset summary, so you can be sure | |
29 | you are sending the right changes. |
|
29 | you are sending the right changes. | |
30 |
|
30 | |||
31 | To enable this extension: |
|
|||
32 |
|
||||
33 | [extensions] |
|
|||
34 | hgext.patchbomb = |
|
|||
35 |
|
||||
36 | To configure other defaults, add a section like this to your hgrc |
|
31 | To configure other defaults, add a section like this to your hgrc | |
37 | file: |
|
32 | file: | |
38 |
|
33 | |||
39 | [email] |
|
34 | [email] | |
40 | from = My Name <my@email> |
|
35 | from = My Name <my@email> | |
41 | to = recipient1, recipient2, ... |
|
36 | to = recipient1, recipient2, ... | |
42 | cc = cc1, cc2, ... |
|
37 | cc = cc1, cc2, ... | |
43 | bcc = bcc1, bcc2, ... |
|
38 | bcc = bcc1, bcc2, ... | |
44 |
|
39 | |||
45 | Then you can use the "hg email" command to mail a series of changesets |
|
40 | Then you can use the "hg email" command to mail a series of changesets | |
46 | as a patchbomb. |
|
41 | as a patchbomb. | |
47 |
|
42 | |||
48 | To avoid sending patches prematurely, it is a good idea to first run |
|
43 | To avoid sending patches prematurely, it is a good idea to first run | |
49 | the "email" command with the "-n" option (test only). You will be |
|
44 | the "email" command with the "-n" option (test only). You will be | |
50 | prompted for an email recipient address, a subject and an introductory |
|
45 | prompted for an email recipient address, a subject and an introductory | |
51 | message describing the patches of your patchbomb. Then when all is |
|
46 | message describing the patches of your patchbomb. Then when all is | |
52 | done, patchbomb messages are displayed. If the PAGER environment |
|
47 | done, patchbomb messages are displayed. If the PAGER environment | |
53 | variable is set, your pager will be fired up once for each patchbomb |
|
48 | variable is set, your pager will be fired up once for each patchbomb | |
54 | message, so you can verify everything is alright. |
|
49 | message, so you can verify everything is alright. | |
55 |
|
50 | |||
56 | The -m/--mbox option is also very useful. Instead of previewing each |
|
51 | The -m/--mbox option is also very useful. Instead of previewing each | |
57 | patchbomb message in a pager or sending the messages directly, it will |
|
52 | patchbomb message in a pager or sending the messages directly, it will | |
58 | create a UNIX mailbox file with the patch emails. This mailbox file |
|
53 | create a UNIX mailbox file with the patch emails. This mailbox file | |
59 | can be previewed with any mail user agent which supports UNIX mbox |
|
54 | can be previewed with any mail user agent which supports UNIX mbox | |
60 | files, e.g. with mutt: |
|
55 | files, e.g. with mutt: | |
61 |
|
56 | |||
62 | % mutt -R -f mbox |
|
57 | % mutt -R -f mbox | |
63 |
|
58 | |||
64 | When you are previewing the patchbomb messages, you can use `formail' |
|
59 | When you are previewing the patchbomb messages, you can use `formail' | |
65 | (a utility that is commonly installed as part of the procmail |
|
60 | (a utility that is commonly installed as part of the procmail | |
66 | package), to send each message out: |
|
61 | package), to send each message out: | |
67 |
|
62 | |||
68 | % formail -s sendmail -bm -t < mbox |
|
63 | % formail -s sendmail -bm -t < mbox | |
69 |
|
64 | |||
70 | That should be all. Now your patchbomb is on its way out. |
|
65 | That should be all. Now your patchbomb is on its way out. | |
71 |
|
66 | |||
72 | You can also either configure the method option in the email section |
|
67 | You can also either configure the method option in the email section | |
73 | to be a sendmail compatible mailer or fill out the [smtp] section so |
|
68 | to be a sendmail compatible mailer or fill out the [smtp] section so | |
74 | that the patchbomb extension can automatically send patchbombs |
|
69 | that the patchbomb extension can automatically send patchbombs | |
75 | directly from the commandline. See the [email] and [smtp] sections in |
|
70 | directly from the commandline. See the [email] and [smtp] sections in | |
76 | hgrc(5) for details.''' |
|
71 | hgrc(5) for details.''' | |
77 |
|
72 | |||
78 | import os, errno, socket, tempfile, cStringIO |
|
73 | import os, errno, socket, tempfile, cStringIO | |
79 | import email.MIMEMultipart, email.MIMEBase |
|
74 | import email.MIMEMultipart, email.MIMEBase | |
80 | import email.Utils, email.Encoders, email.Generator |
|
75 | import email.Utils, email.Encoders, email.Generator | |
81 | from mercurial import cmdutil, commands, hg, mail, patch, util |
|
76 | from mercurial import cmdutil, commands, hg, mail, patch, util | |
82 | from mercurial.i18n import _ |
|
77 | from mercurial.i18n import _ | |
83 | from mercurial.node import bin |
|
78 | from mercurial.node import bin | |
84 |
|
79 | |||
85 | def prompt(ui, prompt, default=None, rest=': ', empty_ok=False): |
|
80 | def prompt(ui, prompt, default=None, rest=': ', empty_ok=False): | |
86 | if not ui.interactive(): |
|
81 | if not ui.interactive(): | |
87 | return default |
|
82 | return default | |
88 | if default: |
|
83 | if default: | |
89 | prompt += ' [%s]' % default |
|
84 | prompt += ' [%s]' % default | |
90 | prompt += rest |
|
85 | prompt += rest | |
91 | while True: |
|
86 | while True: | |
92 | r = ui.prompt(prompt, default=default) |
|
87 | r = ui.prompt(prompt, default=default) | |
93 | if r: |
|
88 | if r: | |
94 | return r |
|
89 | return r | |
95 | if default is not None: |
|
90 | if default is not None: | |
96 | return default |
|
91 | return default | |
97 | if empty_ok: |
|
92 | if empty_ok: | |
98 | return r |
|
93 | return r | |
99 | ui.warn(_('Please enter a valid value.\n')) |
|
94 | ui.warn(_('Please enter a valid value.\n')) | |
100 |
|
95 | |||
101 | def cdiffstat(ui, summary, patchlines): |
|
96 | def cdiffstat(ui, summary, patchlines): | |
102 | s = patch.diffstat(patchlines) |
|
97 | s = patch.diffstat(patchlines) | |
103 | if summary: |
|
98 | if summary: | |
104 | ui.write(summary, '\n') |
|
99 | ui.write(summary, '\n') | |
105 | ui.write(s, '\n') |
|
100 | ui.write(s, '\n') | |
106 | ans = prompt(ui, _('does the diffstat above look okay? '), 'y') |
|
101 | ans = prompt(ui, _('does the diffstat above look okay? '), 'y') | |
107 | if not ans.lower().startswith('y'): |
|
102 | if not ans.lower().startswith('y'): | |
108 | raise util.Abort(_('diffstat rejected')) |
|
103 | raise util.Abort(_('diffstat rejected')) | |
109 | return s |
|
104 | return s | |
110 |
|
105 | |||
111 | def makepatch(ui, repo, patch, opts, _charsets, idx, total, patchname=None): |
|
106 | def makepatch(ui, repo, patch, opts, _charsets, idx, total, patchname=None): | |
112 |
|
107 | |||
113 | desc = [] |
|
108 | desc = [] | |
114 | node = None |
|
109 | node = None | |
115 | body = '' |
|
110 | body = '' | |
116 |
|
111 | |||
117 | for line in patch: |
|
112 | for line in patch: | |
118 | if line.startswith('#'): |
|
113 | if line.startswith('#'): | |
119 | if line.startswith('# Node ID'): |
|
114 | if line.startswith('# Node ID'): | |
120 | node = line.split()[-1] |
|
115 | node = line.split()[-1] | |
121 | continue |
|
116 | continue | |
122 | if line.startswith('diff -r') or line.startswith('diff --git'): |
|
117 | if line.startswith('diff -r') or line.startswith('diff --git'): | |
123 | break |
|
118 | break | |
124 | desc.append(line) |
|
119 | desc.append(line) | |
125 |
|
120 | |||
126 | if not patchname and not node: |
|
121 | if not patchname and not node: | |
127 | raise ValueError |
|
122 | raise ValueError | |
128 |
|
123 | |||
129 | if opts.get('attach'): |
|
124 | if opts.get('attach'): | |
130 | body = ('\n'.join(desc[1:]).strip() or |
|
125 | body = ('\n'.join(desc[1:]).strip() or | |
131 | 'Patch subject is complete summary.') |
|
126 | 'Patch subject is complete summary.') | |
132 | body += '\n\n\n' |
|
127 | body += '\n\n\n' | |
133 |
|
128 | |||
134 | if opts.get('plain'): |
|
129 | if opts.get('plain'): | |
135 | while patch and patch[0].startswith('# '): |
|
130 | while patch and patch[0].startswith('# '): | |
136 | patch.pop(0) |
|
131 | patch.pop(0) | |
137 | if patch: |
|
132 | if patch: | |
138 | patch.pop(0) |
|
133 | patch.pop(0) | |
139 | while patch and not patch[0].strip(): |
|
134 | while patch and not patch[0].strip(): | |
140 | patch.pop(0) |
|
135 | patch.pop(0) | |
141 |
|
136 | |||
142 | if opts.get('diffstat'): |
|
137 | if opts.get('diffstat'): | |
143 | body += cdiffstat(ui, '\n'.join(desc), patch) + '\n\n' |
|
138 | body += cdiffstat(ui, '\n'.join(desc), patch) + '\n\n' | |
144 |
|
139 | |||
145 | if opts.get('attach') or opts.get('inline'): |
|
140 | if opts.get('attach') or opts.get('inline'): | |
146 | msg = email.MIMEMultipart.MIMEMultipart() |
|
141 | msg = email.MIMEMultipart.MIMEMultipart() | |
147 | if body: |
|
142 | if body: | |
148 | msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test'))) |
|
143 | msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test'))) | |
149 | p = mail.mimetextpatch('\n'.join(patch), 'x-patch', opts.get('test')) |
|
144 | p = mail.mimetextpatch('\n'.join(patch), 'x-patch', opts.get('test')) | |
150 | binnode = bin(node) |
|
145 | binnode = bin(node) | |
151 | # if node is mq patch, it will have the patch file's name as a tag |
|
146 | # if node is mq patch, it will have the patch file's name as a tag | |
152 | if not patchname: |
|
147 | if not patchname: | |
153 | patchtags = [t for t in repo.nodetags(binnode) |
|
148 | patchtags = [t for t in repo.nodetags(binnode) | |
154 | if t.endswith('.patch') or t.endswith('.diff')] |
|
149 | if t.endswith('.patch') or t.endswith('.diff')] | |
155 | if patchtags: |
|
150 | if patchtags: | |
156 | patchname = patchtags[0] |
|
151 | patchname = patchtags[0] | |
157 | elif total > 1: |
|
152 | elif total > 1: | |
158 | patchname = cmdutil.make_filename(repo, '%b-%n.patch', |
|
153 | patchname = cmdutil.make_filename(repo, '%b-%n.patch', | |
159 | binnode, seqno=idx, total=total) |
|
154 | binnode, seqno=idx, total=total) | |
160 | else: |
|
155 | else: | |
161 | patchname = cmdutil.make_filename(repo, '%b.patch', binnode) |
|
156 | patchname = cmdutil.make_filename(repo, '%b.patch', binnode) | |
162 | disposition = 'inline' |
|
157 | disposition = 'inline' | |
163 | if opts.get('attach'): |
|
158 | if opts.get('attach'): | |
164 | disposition = 'attachment' |
|
159 | disposition = 'attachment' | |
165 | p['Content-Disposition'] = disposition + '; filename=' + patchname |
|
160 | p['Content-Disposition'] = disposition + '; filename=' + patchname | |
166 | msg.attach(p) |
|
161 | msg.attach(p) | |
167 | else: |
|
162 | else: | |
168 | body += '\n'.join(patch) |
|
163 | body += '\n'.join(patch) | |
169 | msg = mail.mimetextpatch(body, display=opts.get('test')) |
|
164 | msg = mail.mimetextpatch(body, display=opts.get('test')) | |
170 |
|
165 | |||
171 | subj = desc[0].strip().rstrip('. ') |
|
166 | subj = desc[0].strip().rstrip('. ') | |
172 | if total == 1 and not opts.get('intro'): |
|
167 | if total == 1 and not opts.get('intro'): | |
173 | subj = '[PATCH] ' + (opts.get('subject') or subj) |
|
168 | subj = '[PATCH] ' + (opts.get('subject') or subj) | |
174 | else: |
|
169 | else: | |
175 | tlen = len(str(total)) |
|
170 | tlen = len(str(total)) | |
176 | subj = '[PATCH %0*d of %d] %s' % (tlen, idx, total, subj) |
|
171 | subj = '[PATCH %0*d of %d] %s' % (tlen, idx, total, subj) | |
177 | msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test')) |
|
172 | msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test')) | |
178 | msg['X-Mercurial-Node'] = node |
|
173 | msg['X-Mercurial-Node'] = node | |
179 | return msg, subj |
|
174 | return msg, subj | |
180 |
|
175 | |||
181 | def patchbomb(ui, repo, *revs, **opts): |
|
176 | def patchbomb(ui, repo, *revs, **opts): | |
182 | '''send changesets by email |
|
177 | '''send changesets by email | |
183 |
|
178 | |||
184 | By default, diffs are sent in the format generated by hg export, |
|
179 | By default, diffs are sent in the format generated by hg export, | |
185 | one per message. The series starts with a "[PATCH 0 of N]" |
|
180 | one per message. The series starts with a "[PATCH 0 of N]" | |
186 | introduction, which describes the series as a whole. |
|
181 | introduction, which describes the series as a whole. | |
187 |
|
182 | |||
188 | Each patch email has a Subject line of "[PATCH M of N] ...", using |
|
183 | Each patch email has a Subject line of "[PATCH M of N] ...", using | |
189 | the first line of the changeset description as the subject text. |
|
184 | the first line of the changeset description as the subject text. | |
190 | The message contains two or three parts. First, the changeset |
|
185 | The message contains two or three parts. First, the changeset | |
191 | description. Next, (optionally) if the diffstat program is |
|
186 | description. Next, (optionally) if the diffstat program is | |
192 | installed and -d/--diffstat is used, the result of running |
|
187 | installed and -d/--diffstat is used, the result of running | |
193 | diffstat on the patch. Finally, the patch itself, as generated by |
|
188 | diffstat on the patch. Finally, the patch itself, as generated by | |
194 | "hg export". |
|
189 | "hg export". | |
195 |
|
190 | |||
196 | By default the patch is included as text in the email body for |
|
191 | By default the patch is included as text in the email body for | |
197 | easy reviewing. Using the -a/--attach option will instead create |
|
192 | easy reviewing. Using the -a/--attach option will instead create | |
198 | an attachment for the patch. With -i/--inline an inline attachment |
|
193 | an attachment for the patch. With -i/--inline an inline attachment | |
199 | will be created. |
|
194 | will be created. | |
200 |
|
195 | |||
201 | With -o/--outgoing, emails will be generated for patches not found |
|
196 | With -o/--outgoing, emails will be generated for patches not found | |
202 | in the destination repository (or only those which are ancestors |
|
197 | in the destination repository (or only those which are ancestors | |
203 | of the specified revisions if any are provided) |
|
198 | of the specified revisions if any are provided) | |
204 |
|
199 | |||
205 | With -b/--bundle, changesets are selected as for --outgoing, but a |
|
200 | With -b/--bundle, changesets are selected as for --outgoing, but a | |
206 | single email containing a binary Mercurial bundle as an attachment |
|
201 | single email containing a binary Mercurial bundle as an attachment | |
207 | will be sent. |
|
202 | will be sent. | |
208 |
|
203 | |||
209 | Examples: |
|
204 | Examples: | |
210 |
|
205 | |||
211 | hg email -r 3000 # send patch 3000 only |
|
206 | hg email -r 3000 # send patch 3000 only | |
212 | hg email -r 3000 -r 3001 # send patches 3000 and 3001 |
|
207 | hg email -r 3000 -r 3001 # send patches 3000 and 3001 | |
213 | hg email -r 3000:3005 # send patches 3000 through 3005 |
|
208 | hg email -r 3000:3005 # send patches 3000 through 3005 | |
214 | hg email 3000 # send patch 3000 (deprecated) |
|
209 | hg email 3000 # send patch 3000 (deprecated) | |
215 |
|
210 | |||
216 | hg email -o # send all patches not in default |
|
211 | hg email -o # send all patches not in default | |
217 | hg email -o DEST # send all patches not in DEST |
|
212 | hg email -o DEST # send all patches not in DEST | |
218 | hg email -o -r 3000 # send all ancestors of 3000 not in default |
|
213 | hg email -o -r 3000 # send all ancestors of 3000 not in default | |
219 | hg email -o -r 3000 DEST # send all ancestors of 3000 not in DEST |
|
214 | hg email -o -r 3000 DEST # send all ancestors of 3000 not in DEST | |
220 |
|
215 | |||
221 | hg email -b # send bundle of all patches not in default |
|
216 | hg email -b # send bundle of all patches not in default | |
222 | hg email -b DEST # send bundle of all patches not in DEST |
|
217 | hg email -b DEST # send bundle of all patches not in DEST | |
223 | hg email -b -r 3000 # bundle of all ancestors of 3000 not in default |
|
218 | hg email -b -r 3000 # bundle of all ancestors of 3000 not in default | |
224 | hg email -b -r 3000 DEST # bundle of all ancestors of 3000 not in DEST |
|
219 | hg email -b -r 3000 DEST # bundle of all ancestors of 3000 not in DEST | |
225 |
|
220 | |||
226 | Before using this command, you will need to enable email in your |
|
221 | Before using this command, you will need to enable email in your | |
227 | hgrc. See the [email] section in hgrc(5) for details. |
|
222 | hgrc. See the [email] section in hgrc(5) for details. | |
228 | ''' |
|
223 | ''' | |
229 |
|
224 | |||
230 | _charsets = mail._charsets(ui) |
|
225 | _charsets = mail._charsets(ui) | |
231 |
|
226 | |||
232 | def outgoing(dest, revs): |
|
227 | def outgoing(dest, revs): | |
233 | '''Return the revisions present locally but not in dest''' |
|
228 | '''Return the revisions present locally but not in dest''' | |
234 | dest = ui.expandpath(dest or 'default-push', dest or 'default') |
|
229 | dest = ui.expandpath(dest or 'default-push', dest or 'default') | |
235 | revs = [repo.lookup(rev) for rev in revs] |
|
230 | revs = [repo.lookup(rev) for rev in revs] | |
236 | other = hg.repository(cmdutil.remoteui(repo, opts), dest) |
|
231 | other = hg.repository(cmdutil.remoteui(repo, opts), dest) | |
237 | ui.status(_('comparing with %s\n') % dest) |
|
232 | ui.status(_('comparing with %s\n') % dest) | |
238 | o = repo.findoutgoing(other) |
|
233 | o = repo.findoutgoing(other) | |
239 | if not o: |
|
234 | if not o: | |
240 | ui.status(_("no changes found\n")) |
|
235 | ui.status(_("no changes found\n")) | |
241 | return [] |
|
236 | return [] | |
242 | o = repo.changelog.nodesbetween(o, revs or None)[0] |
|
237 | o = repo.changelog.nodesbetween(o, revs or None)[0] | |
243 | return [str(repo.changelog.rev(r)) for r in o] |
|
238 | return [str(repo.changelog.rev(r)) for r in o] | |
244 |
|
239 | |||
245 | def getpatches(revs): |
|
240 | def getpatches(revs): | |
246 | for r in cmdutil.revrange(repo, revs): |
|
241 | for r in cmdutil.revrange(repo, revs): | |
247 | output = cStringIO.StringIO() |
|
242 | output = cStringIO.StringIO() | |
248 | patch.export(repo, [r], fp=output, |
|
243 | patch.export(repo, [r], fp=output, | |
249 | opts=patch.diffopts(ui, opts)) |
|
244 | opts=patch.diffopts(ui, opts)) | |
250 | yield output.getvalue().split('\n') |
|
245 | yield output.getvalue().split('\n') | |
251 |
|
246 | |||
252 | def getbundle(dest): |
|
247 | def getbundle(dest): | |
253 | tmpdir = tempfile.mkdtemp(prefix='hg-email-bundle-') |
|
248 | tmpdir = tempfile.mkdtemp(prefix='hg-email-bundle-') | |
254 | tmpfn = os.path.join(tmpdir, 'bundle') |
|
249 | tmpfn = os.path.join(tmpdir, 'bundle') | |
255 | try: |
|
250 | try: | |
256 | commands.bundle(ui, repo, tmpfn, dest, **opts) |
|
251 | commands.bundle(ui, repo, tmpfn, dest, **opts) | |
257 | return open(tmpfn, 'rb').read() |
|
252 | return open(tmpfn, 'rb').read() | |
258 | finally: |
|
253 | finally: | |
259 | try: |
|
254 | try: | |
260 | os.unlink(tmpfn) |
|
255 | os.unlink(tmpfn) | |
261 | except: |
|
256 | except: | |
262 | pass |
|
257 | pass | |
263 | os.rmdir(tmpdir) |
|
258 | os.rmdir(tmpdir) | |
264 |
|
259 | |||
265 | if not (opts.get('test') or opts.get('mbox')): |
|
260 | if not (opts.get('test') or opts.get('mbox')): | |
266 | # really sending |
|
261 | # really sending | |
267 | mail.validateconfig(ui) |
|
262 | mail.validateconfig(ui) | |
268 |
|
263 | |||
269 | if not (revs or opts.get('rev') |
|
264 | if not (revs or opts.get('rev') | |
270 | or opts.get('outgoing') or opts.get('bundle') |
|
265 | or opts.get('outgoing') or opts.get('bundle') | |
271 | or opts.get('patches')): |
|
266 | or opts.get('patches')): | |
272 | raise util.Abort(_('specify at least one changeset with -r or -o')) |
|
267 | raise util.Abort(_('specify at least one changeset with -r or -o')) | |
273 |
|
268 | |||
274 | if opts.get('outgoing') and opts.get('bundle'): |
|
269 | if opts.get('outgoing') and opts.get('bundle'): | |
275 | raise util.Abort(_("--outgoing mode always on with --bundle;" |
|
270 | raise util.Abort(_("--outgoing mode always on with --bundle;" | |
276 | " do not re-specify --outgoing")) |
|
271 | " do not re-specify --outgoing")) | |
277 |
|
272 | |||
278 | if opts.get('outgoing') or opts.get('bundle'): |
|
273 | if opts.get('outgoing') or opts.get('bundle'): | |
279 | if len(revs) > 1: |
|
274 | if len(revs) > 1: | |
280 | raise util.Abort(_("too many destinations")) |
|
275 | raise util.Abort(_("too many destinations")) | |
281 | dest = revs and revs[0] or None |
|
276 | dest = revs and revs[0] or None | |
282 | revs = [] |
|
277 | revs = [] | |
283 |
|
278 | |||
284 | if opts.get('rev'): |
|
279 | if opts.get('rev'): | |
285 | if revs: |
|
280 | if revs: | |
286 | raise util.Abort(_('use only one form to specify the revision')) |
|
281 | raise util.Abort(_('use only one form to specify the revision')) | |
287 | revs = opts.get('rev') |
|
282 | revs = opts.get('rev') | |
288 |
|
283 | |||
289 | if opts.get('outgoing'): |
|
284 | if opts.get('outgoing'): | |
290 | revs = outgoing(dest, opts.get('rev')) |
|
285 | revs = outgoing(dest, opts.get('rev')) | |
291 | if opts.get('bundle'): |
|
286 | if opts.get('bundle'): | |
292 | opts['revs'] = revs |
|
287 | opts['revs'] = revs | |
293 |
|
288 | |||
294 | # start |
|
289 | # start | |
295 | if opts.get('date'): |
|
290 | if opts.get('date'): | |
296 | start_time = util.parsedate(opts.get('date')) |
|
291 | start_time = util.parsedate(opts.get('date')) | |
297 | else: |
|
292 | else: | |
298 | start_time = util.makedate() |
|
293 | start_time = util.makedate() | |
299 |
|
294 | |||
300 | def genmsgid(id): |
|
295 | def genmsgid(id): | |
301 | return '<%s.%s@%s>' % (id[:20], int(start_time[0]), socket.getfqdn()) |
|
296 | return '<%s.%s@%s>' % (id[:20], int(start_time[0]), socket.getfqdn()) | |
302 |
|
297 | |||
303 | def getdescription(body, sender): |
|
298 | def getdescription(body, sender): | |
304 | if opts.get('desc'): |
|
299 | if opts.get('desc'): | |
305 | body = open(opts.get('desc')).read() |
|
300 | body = open(opts.get('desc')).read() | |
306 | else: |
|
301 | else: | |
307 | ui.write(_('\nWrite the introductory message for the ' |
|
302 | ui.write(_('\nWrite the introductory message for the ' | |
308 | 'patch series.\n\n')) |
|
303 | 'patch series.\n\n')) | |
309 | body = ui.edit(body, sender) |
|
304 | body = ui.edit(body, sender) | |
310 | return body |
|
305 | return body | |
311 |
|
306 | |||
312 | def getpatchmsgs(patches, patchnames=None): |
|
307 | def getpatchmsgs(patches, patchnames=None): | |
313 | jumbo = [] |
|
308 | jumbo = [] | |
314 | msgs = [] |
|
309 | msgs = [] | |
315 |
|
310 | |||
316 | ui.write(_('This patch series consists of %d patches.\n\n') |
|
311 | ui.write(_('This patch series consists of %d patches.\n\n') | |
317 | % len(patches)) |
|
312 | % len(patches)) | |
318 |
|
313 | |||
319 | name = None |
|
314 | name = None | |
320 | for i, p in enumerate(patches): |
|
315 | for i, p in enumerate(patches): | |
321 | jumbo.extend(p) |
|
316 | jumbo.extend(p) | |
322 | if patchnames: |
|
317 | if patchnames: | |
323 | name = patchnames[i] |
|
318 | name = patchnames[i] | |
324 | msg = makepatch(ui, repo, p, opts, _charsets, i + 1, |
|
319 | msg = makepatch(ui, repo, p, opts, _charsets, i + 1, | |
325 | len(patches), name) |
|
320 | len(patches), name) | |
326 | msgs.append(msg) |
|
321 | msgs.append(msg) | |
327 |
|
322 | |||
328 | if len(patches) > 1 or opts.get('intro'): |
|
323 | if len(patches) > 1 or opts.get('intro'): | |
329 | tlen = len(str(len(patches))) |
|
324 | tlen = len(str(len(patches))) | |
330 |
|
325 | |||
331 | subj = '[PATCH %0*d of %d] %s' % ( |
|
326 | subj = '[PATCH %0*d of %d] %s' % ( | |
332 | tlen, 0, len(patches), |
|
327 | tlen, 0, len(patches), | |
333 | opts.get('subject') or |
|
328 | opts.get('subject') or | |
334 | prompt(ui, 'Subject:', |
|
329 | prompt(ui, 'Subject:', | |
335 | rest=' [PATCH %0*d of %d] ' % (tlen, 0, len(patches)))) |
|
330 | rest=' [PATCH %0*d of %d] ' % (tlen, 0, len(patches)))) | |
336 |
|
331 | |||
337 | body = '' |
|
332 | body = '' | |
338 | if opts.get('diffstat'): |
|
333 | if opts.get('diffstat'): | |
339 | d = cdiffstat(ui, _('Final summary:\n'), jumbo) |
|
334 | d = cdiffstat(ui, _('Final summary:\n'), jumbo) | |
340 | if d: |
|
335 | if d: | |
341 | body = '\n' + d |
|
336 | body = '\n' + d | |
342 |
|
337 | |||
343 | body = getdescription(body, sender) |
|
338 | body = getdescription(body, sender) | |
344 | msg = mail.mimeencode(ui, body, _charsets, opts.get('test')) |
|
339 | msg = mail.mimeencode(ui, body, _charsets, opts.get('test')) | |
345 | msg['Subject'] = mail.headencode(ui, subj, _charsets, |
|
340 | msg['Subject'] = mail.headencode(ui, subj, _charsets, | |
346 | opts.get('test')) |
|
341 | opts.get('test')) | |
347 |
|
342 | |||
348 | msgs.insert(0, (msg, subj)) |
|
343 | msgs.insert(0, (msg, subj)) | |
349 | return msgs |
|
344 | return msgs | |
350 |
|
345 | |||
351 | def getbundlemsgs(bundle): |
|
346 | def getbundlemsgs(bundle): | |
352 | subj = (opts.get('subject') |
|
347 | subj = (opts.get('subject') | |
353 | or prompt(ui, 'Subject:', 'A bundle for your repository')) |
|
348 | or prompt(ui, 'Subject:', 'A bundle for your repository')) | |
354 |
|
349 | |||
355 | body = getdescription('', sender) |
|
350 | body = getdescription('', sender) | |
356 | msg = email.MIMEMultipart.MIMEMultipart() |
|
351 | msg = email.MIMEMultipart.MIMEMultipart() | |
357 | if body: |
|
352 | if body: | |
358 | msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test'))) |
|
353 | msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test'))) | |
359 | datapart = email.MIMEBase.MIMEBase('application', 'x-mercurial-bundle') |
|
354 | datapart = email.MIMEBase.MIMEBase('application', 'x-mercurial-bundle') | |
360 | datapart.set_payload(bundle) |
|
355 | datapart.set_payload(bundle) | |
361 | bundlename = '%s.hg' % opts.get('bundlename', 'bundle') |
|
356 | bundlename = '%s.hg' % opts.get('bundlename', 'bundle') | |
362 | datapart.add_header('Content-Disposition', 'attachment', |
|
357 | datapart.add_header('Content-Disposition', 'attachment', | |
363 | filename=bundlename) |
|
358 | filename=bundlename) | |
364 | email.Encoders.encode_base64(datapart) |
|
359 | email.Encoders.encode_base64(datapart) | |
365 | msg.attach(datapart) |
|
360 | msg.attach(datapart) | |
366 | msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test')) |
|
361 | msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test')) | |
367 | return [(msg, subj)] |
|
362 | return [(msg, subj)] | |
368 |
|
363 | |||
369 | sender = (opts.get('from') or ui.config('email', 'from') or |
|
364 | sender = (opts.get('from') or ui.config('email', 'from') or | |
370 | ui.config('patchbomb', 'from') or |
|
365 | ui.config('patchbomb', 'from') or | |
371 | prompt(ui, 'From', ui.username())) |
|
366 | prompt(ui, 'From', ui.username())) | |
372 |
|
367 | |||
373 | # internal option used by pbranches |
|
368 | # internal option used by pbranches | |
374 | patches = opts.get('patches') |
|
369 | patches = opts.get('patches') | |
375 | if patches: |
|
370 | if patches: | |
376 | msgs = getpatchmsgs(patches, opts.get('patchnames')) |
|
371 | msgs = getpatchmsgs(patches, opts.get('patchnames')) | |
377 | elif opts.get('bundle'): |
|
372 | elif opts.get('bundle'): | |
378 | msgs = getbundlemsgs(getbundle(dest)) |
|
373 | msgs = getbundlemsgs(getbundle(dest)) | |
379 | else: |
|
374 | else: | |
380 | msgs = getpatchmsgs(list(getpatches(revs))) |
|
375 | msgs = getpatchmsgs(list(getpatches(revs))) | |
381 |
|
376 | |||
382 | def getaddrs(opt, prpt, default = None): |
|
377 | def getaddrs(opt, prpt, default = None): | |
383 | addrs = opts.get(opt) or (ui.config('email', opt) or |
|
378 | addrs = opts.get(opt) or (ui.config('email', opt) or | |
384 | ui.config('patchbomb', opt) or |
|
379 | ui.config('patchbomb', opt) or | |
385 | prompt(ui, prpt, default)).split(',') |
|
380 | prompt(ui, prpt, default)).split(',') | |
386 | return [mail.addressencode(ui, a.strip(), _charsets, opts.get('test')) |
|
381 | return [mail.addressencode(ui, a.strip(), _charsets, opts.get('test')) | |
387 | for a in addrs if a.strip()] |
|
382 | for a in addrs if a.strip()] | |
388 |
|
383 | |||
389 | to = getaddrs('to', 'To') |
|
384 | to = getaddrs('to', 'To') | |
390 | cc = getaddrs('cc', 'Cc', '') |
|
385 | cc = getaddrs('cc', 'Cc', '') | |
391 |
|
386 | |||
392 | bcc = opts.get('bcc') or (ui.config('email', 'bcc') or |
|
387 | bcc = opts.get('bcc') or (ui.config('email', 'bcc') or | |
393 | ui.config('patchbomb', 'bcc') or '').split(',') |
|
388 | ui.config('patchbomb', 'bcc') or '').split(',') | |
394 | bcc = [mail.addressencode(ui, a.strip(), _charsets, opts.get('test')) |
|
389 | bcc = [mail.addressencode(ui, a.strip(), _charsets, opts.get('test')) | |
395 | for a in bcc if a.strip()] |
|
390 | for a in bcc if a.strip()] | |
396 |
|
391 | |||
397 | ui.write('\n') |
|
392 | ui.write('\n') | |
398 |
|
393 | |||
399 | parent = opts.get('in_reply_to') or None |
|
394 | parent = opts.get('in_reply_to') or None | |
400 | # angle brackets may be omitted, they're not semantically part of the msg-id |
|
395 | # angle brackets may be omitted, they're not semantically part of the msg-id | |
401 | if parent is not None: |
|
396 | if parent is not None: | |
402 | if not parent.startswith('<'): |
|
397 | if not parent.startswith('<'): | |
403 | parent = '<' + parent |
|
398 | parent = '<' + parent | |
404 | if not parent.endswith('>'): |
|
399 | if not parent.endswith('>'): | |
405 | parent += '>' |
|
400 | parent += '>' | |
406 |
|
401 | |||
407 | first = True |
|
402 | first = True | |
408 |
|
403 | |||
409 | sender_addr = email.Utils.parseaddr(sender)[1] |
|
404 | sender_addr = email.Utils.parseaddr(sender)[1] | |
410 | sender = mail.addressencode(ui, sender, _charsets, opts.get('test')) |
|
405 | sender = mail.addressencode(ui, sender, _charsets, opts.get('test')) | |
411 | sendmail = None |
|
406 | sendmail = None | |
412 | for m, subj in msgs: |
|
407 | for m, subj in msgs: | |
413 | try: |
|
408 | try: | |
414 | m['Message-Id'] = genmsgid(m['X-Mercurial-Node']) |
|
409 | m['Message-Id'] = genmsgid(m['X-Mercurial-Node']) | |
415 | except TypeError: |
|
410 | except TypeError: | |
416 | m['Message-Id'] = genmsgid('patchbomb') |
|
411 | m['Message-Id'] = genmsgid('patchbomb') | |
417 | if parent: |
|
412 | if parent: | |
418 | m['In-Reply-To'] = parent |
|
413 | m['In-Reply-To'] = parent | |
419 | m['References'] = parent |
|
414 | m['References'] = parent | |
420 | if first: |
|
415 | if first: | |
421 | parent = m['Message-Id'] |
|
416 | parent = m['Message-Id'] | |
422 | first = False |
|
417 | first = False | |
423 |
|
418 | |||
424 | m['User-Agent'] = 'Mercurial-patchbomb/%s' % util.version() |
|
419 | m['User-Agent'] = 'Mercurial-patchbomb/%s' % util.version() | |
425 | m['Date'] = email.Utils.formatdate(start_time[0]) |
|
420 | m['Date'] = email.Utils.formatdate(start_time[0]) | |
426 |
|
421 | |||
427 | start_time = (start_time[0] + 1, start_time[1]) |
|
422 | start_time = (start_time[0] + 1, start_time[1]) | |
428 | m['From'] = sender |
|
423 | m['From'] = sender | |
429 | m['To'] = ', '.join(to) |
|
424 | m['To'] = ', '.join(to) | |
430 | if cc: |
|
425 | if cc: | |
431 | m['Cc'] = ', '.join(cc) |
|
426 | m['Cc'] = ', '.join(cc) | |
432 | if bcc: |
|
427 | if bcc: | |
433 | m['Bcc'] = ', '.join(bcc) |
|
428 | m['Bcc'] = ', '.join(bcc) | |
434 | if opts.get('test'): |
|
429 | if opts.get('test'): | |
435 | ui.status(_('Displaying '), subj, ' ...\n') |
|
430 | ui.status(_('Displaying '), subj, ' ...\n') | |
436 | ui.flush() |
|
431 | ui.flush() | |
437 | if 'PAGER' in os.environ: |
|
432 | if 'PAGER' in os.environ: | |
438 | fp = util.popen(os.environ['PAGER'], 'w') |
|
433 | fp = util.popen(os.environ['PAGER'], 'w') | |
439 | else: |
|
434 | else: | |
440 | fp = ui |
|
435 | fp = ui | |
441 | generator = email.Generator.Generator(fp, mangle_from_=False) |
|
436 | generator = email.Generator.Generator(fp, mangle_from_=False) | |
442 | try: |
|
437 | try: | |
443 | generator.flatten(m, 0) |
|
438 | generator.flatten(m, 0) | |
444 | fp.write('\n') |
|
439 | fp.write('\n') | |
445 | except IOError, inst: |
|
440 | except IOError, inst: | |
446 | if inst.errno != errno.EPIPE: |
|
441 | if inst.errno != errno.EPIPE: | |
447 | raise |
|
442 | raise | |
448 | if fp is not ui: |
|
443 | if fp is not ui: | |
449 | fp.close() |
|
444 | fp.close() | |
450 | elif opts.get('mbox'): |
|
445 | elif opts.get('mbox'): | |
451 | ui.status(_('Writing '), subj, ' ...\n') |
|
446 | ui.status(_('Writing '), subj, ' ...\n') | |
452 | fp = open(opts.get('mbox'), 'In-Reply-To' in m and 'ab+' or 'wb+') |
|
447 | fp = open(opts.get('mbox'), 'In-Reply-To' in m and 'ab+' or 'wb+') | |
453 | generator = email.Generator.Generator(fp, mangle_from_=True) |
|
448 | generator = email.Generator.Generator(fp, mangle_from_=True) | |
454 | date = util.datestr(start_time, '%a %b %d %H:%M:%S %Y') |
|
449 | date = util.datestr(start_time, '%a %b %d %H:%M:%S %Y') | |
455 | fp.write('From %s %s\n' % (sender_addr, date)) |
|
450 | fp.write('From %s %s\n' % (sender_addr, date)) | |
456 | generator.flatten(m, 0) |
|
451 | generator.flatten(m, 0) | |
457 | fp.write('\n\n') |
|
452 | fp.write('\n\n') | |
458 | fp.close() |
|
453 | fp.close() | |
459 | else: |
|
454 | else: | |
460 | if not sendmail: |
|
455 | if not sendmail: | |
461 | sendmail = mail.connect(ui) |
|
456 | sendmail = mail.connect(ui) | |
462 | ui.status(_('Sending '), subj, ' ...\n') |
|
457 | ui.status(_('Sending '), subj, ' ...\n') | |
463 | # Exim does not remove the Bcc field |
|
458 | # Exim does not remove the Bcc field | |
464 | del m['Bcc'] |
|
459 | del m['Bcc'] | |
465 | fp = cStringIO.StringIO() |
|
460 | fp = cStringIO.StringIO() | |
466 | generator = email.Generator.Generator(fp, mangle_from_=False) |
|
461 | generator = email.Generator.Generator(fp, mangle_from_=False) | |
467 | generator.flatten(m, 0) |
|
462 | generator.flatten(m, 0) | |
468 | sendmail(sender, to + bcc + cc, fp.getvalue()) |
|
463 | sendmail(sender, to + bcc + cc, fp.getvalue()) | |
469 |
|
464 | |||
470 | emailopts = [ |
|
465 | emailopts = [ | |
471 | ('a', 'attach', None, _('send patches as attachments')), |
|
466 | ('a', 'attach', None, _('send patches as attachments')), | |
472 | ('i', 'inline', None, _('send patches as inline attachments')), |
|
467 | ('i', 'inline', None, _('send patches as inline attachments')), | |
473 | ('', 'bcc', [], _('email addresses of blind carbon copy recipients')), |
|
468 | ('', 'bcc', [], _('email addresses of blind carbon copy recipients')), | |
474 | ('c', 'cc', [], _('email addresses of copy recipients')), |
|
469 | ('c', 'cc', [], _('email addresses of copy recipients')), | |
475 | ('d', 'diffstat', None, _('add diffstat output to messages')), |
|
470 | ('d', 'diffstat', None, _('add diffstat output to messages')), | |
476 | ('', 'date', '', _('use the given date as the sending date')), |
|
471 | ('', 'date', '', _('use the given date as the sending date')), | |
477 | ('', 'desc', '', _('use the given file as the series description')), |
|
472 | ('', 'desc', '', _('use the given file as the series description')), | |
478 | ('f', 'from', '', _('email address of sender')), |
|
473 | ('f', 'from', '', _('email address of sender')), | |
479 | ('n', 'test', None, _('print messages that would be sent')), |
|
474 | ('n', 'test', None, _('print messages that would be sent')), | |
480 | ('m', 'mbox', '', |
|
475 | ('m', 'mbox', '', | |
481 | _('write messages to mbox file instead of sending them')), |
|
476 | _('write messages to mbox file instead of sending them')), | |
482 | ('s', 'subject', '', |
|
477 | ('s', 'subject', '', | |
483 | _('subject of first message (intro or single patch)')), |
|
478 | _('subject of first message (intro or single patch)')), | |
484 | ('', 'in-reply-to', '', |
|
479 | ('', 'in-reply-to', '', | |
485 | _('message identifier to reply to')), |
|
480 | _('message identifier to reply to')), | |
486 | ('t', 'to', [], _('email addresses of recipients')), |
|
481 | ('t', 'to', [], _('email addresses of recipients')), | |
487 | ] |
|
482 | ] | |
488 |
|
483 | |||
489 |
|
484 | |||
490 | cmdtable = { |
|
485 | cmdtable = { | |
491 | "email": |
|
486 | "email": | |
492 | (patchbomb, |
|
487 | (patchbomb, | |
493 | [('g', 'git', None, _('use git extended diff format')), |
|
488 | [('g', 'git', None, _('use git extended diff format')), | |
494 | ('', 'plain', None, _('omit hg patch header')), |
|
489 | ('', 'plain', None, _('omit hg patch header')), | |
495 | ('o', 'outgoing', None, |
|
490 | ('o', 'outgoing', None, | |
496 | _('send changes not found in the target repository')), |
|
491 | _('send changes not found in the target repository')), | |
497 | ('b', 'bundle', None, |
|
492 | ('b', 'bundle', None, | |
498 | _('send changes not in target as a binary bundle')), |
|
493 | _('send changes not in target as a binary bundle')), | |
499 | ('', 'bundlename', 'bundle', |
|
494 | ('', 'bundlename', 'bundle', | |
500 | _('name of the bundle attachment file')), |
|
495 | _('name of the bundle attachment file')), | |
501 | ('r', 'rev', [], _('a revision to send')), |
|
496 | ('r', 'rev', [], _('a revision to send')), | |
502 | ('', 'force', None, |
|
497 | ('', 'force', None, | |
503 | _('run even when remote repository is unrelated ' |
|
498 | _('run even when remote repository is unrelated ' | |
504 | '(with -b/--bundle)')), |
|
499 | '(with -b/--bundle)')), | |
505 | ('', 'base', [], |
|
500 | ('', 'base', [], | |
506 | _('a base changeset to specify instead of a destination ' |
|
501 | _('a base changeset to specify instead of a destination ' | |
507 | '(with -b/--bundle)')), |
|
502 | '(with -b/--bundle)')), | |
508 | ('', 'intro', None, |
|
503 | ('', 'intro', None, | |
509 | _('send an introduction email for a single patch')), |
|
504 | _('send an introduction email for a single patch')), | |
510 | ] + emailopts + commands.remoteopts, |
|
505 | ] + emailopts + commands.remoteopts, | |
511 | _('hg email [OPTION]... [DEST]...')) |
|
506 | _('hg email [OPTION]... [DEST]...')) | |
512 | } |
|
507 | } |
@@ -1,110 +1,106 b'' | |||||
1 | # Copyright (C) 2006 - Marco Barisione <marco@barisione.org> |
|
1 | # Copyright (C) 2006 - Marco Barisione <marco@barisione.org> | |
2 | # |
|
2 | # | |
3 | # This is a small extension for Mercurial (http://www.selenic.com/mercurial) |
|
3 | # This is a small extension for Mercurial (http://www.selenic.com/mercurial) | |
4 | # that removes files not known to mercurial |
|
4 | # that removes files not known to mercurial | |
5 | # |
|
5 | # | |
6 | # This program was inspired by the "cvspurge" script contained in CVS utilities |
|
6 | # This program was inspired by the "cvspurge" script contained in CVS utilities | |
7 | # (http://www.red-bean.com/cvsutils/). |
|
7 | # (http://www.red-bean.com/cvsutils/). | |
8 | # |
|
8 | # | |
9 | # To enable the "purge" extension put these lines in your ~/.hgrc: |
|
|||
10 | # [extensions] |
|
|||
11 | # hgext.purge = |
|
|||
12 | # |
|
|||
13 | # For help on the usage of "hg purge" use: |
|
9 | # For help on the usage of "hg purge" use: | |
14 | # hg help purge |
|
10 | # hg help purge | |
15 | # |
|
11 | # | |
16 | # This program is free software; you can redistribute it and/or modify |
|
12 | # This program is free software; you can redistribute it and/or modify | |
17 | # it under the terms of the GNU General Public License as published by |
|
13 | # it under the terms of the GNU General Public License as published by | |
18 | # the Free Software Foundation; either version 2 of the License, or |
|
14 | # the Free Software Foundation; either version 2 of the License, or | |
19 | # (at your option) any later version. |
|
15 | # (at your option) any later version. | |
20 | # |
|
16 | # | |
21 | # This program is distributed in the hope that it will be useful, |
|
17 | # This program is distributed in the hope that it will be useful, | |
22 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
23 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
24 | # GNU General Public License for more details. |
|
20 | # GNU General Public License for more details. | |
25 | # |
|
21 | # | |
26 | # You should have received a copy of the GNU General Public License |
|
22 | # You should have received a copy of the GNU General Public License | |
27 | # along with this program; if not, write to the Free Software |
|
23 | # along with this program; if not, write to the Free Software | |
28 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
|
24 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
29 |
|
25 | |||
30 | from mercurial import util, commands, cmdutil |
|
26 | from mercurial import util, commands, cmdutil | |
31 | from mercurial.i18n import _ |
|
27 | from mercurial.i18n import _ | |
32 | import os, stat |
|
28 | import os, stat | |
33 |
|
29 | |||
34 | def purge(ui, repo, *dirs, **opts): |
|
30 | def purge(ui, repo, *dirs, **opts): | |
35 | '''removes files not tracked by Mercurial |
|
31 | '''removes files not tracked by Mercurial | |
36 |
|
32 | |||
37 | Delete files not known to Mercurial. This is useful to test local |
|
33 | Delete files not known to Mercurial. This is useful to test local | |
38 | and uncommitted changes in an otherwise-clean source tree. |
|
34 | and uncommitted changes in an otherwise-clean source tree. | |
39 |
|
35 | |||
40 | This means that purge will delete: |
|
36 | This means that purge will delete: | |
41 | - Unknown files: files marked with "?" by "hg status" |
|
37 | - Unknown files: files marked with "?" by "hg status" | |
42 | - Empty directories: in fact Mercurial ignores directories unless |
|
38 | - Empty directories: in fact Mercurial ignores directories unless | |
43 | they contain files under source control management |
|
39 | they contain files under source control management | |
44 | But it will leave untouched: |
|
40 | But it will leave untouched: | |
45 | - Modified and unmodified tracked files |
|
41 | - Modified and unmodified tracked files | |
46 | - Ignored files (unless --all is specified) |
|
42 | - Ignored files (unless --all is specified) | |
47 | - New files added to the repository (with "hg add") |
|
43 | - New files added to the repository (with "hg add") | |
48 |
|
44 | |||
49 | If directories are given on the command line, only files in these |
|
45 | If directories are given on the command line, only files in these | |
50 | directories are considered. |
|
46 | directories are considered. | |
51 |
|
47 | |||
52 | Be careful with purge, as you could irreversibly delete some files |
|
48 | Be careful with purge, as you could irreversibly delete some files | |
53 | you forgot to add to the repository. If you only want to print the |
|
49 | you forgot to add to the repository. If you only want to print the | |
54 | list of files that this program would delete, use the --print |
|
50 | list of files that this program would delete, use the --print | |
55 | option. |
|
51 | option. | |
56 | ''' |
|
52 | ''' | |
57 | act = not opts['print'] |
|
53 | act = not opts['print'] | |
58 | eol = '\n' |
|
54 | eol = '\n' | |
59 | if opts['print0']: |
|
55 | if opts['print0']: | |
60 | eol = '\0' |
|
56 | eol = '\0' | |
61 | act = False # --print0 implies --print |
|
57 | act = False # --print0 implies --print | |
62 |
|
58 | |||
63 | def remove(remove_func, name): |
|
59 | def remove(remove_func, name): | |
64 | if act: |
|
60 | if act: | |
65 | try: |
|
61 | try: | |
66 | remove_func(repo.wjoin(name)) |
|
62 | remove_func(repo.wjoin(name)) | |
67 | except OSError: |
|
63 | except OSError: | |
68 | m = _('%s cannot be removed') % name |
|
64 | m = _('%s cannot be removed') % name | |
69 | if opts['abort_on_err']: |
|
65 | if opts['abort_on_err']: | |
70 | raise util.Abort(m) |
|
66 | raise util.Abort(m) | |
71 | ui.warn(_('warning: %s\n') % m) |
|
67 | ui.warn(_('warning: %s\n') % m) | |
72 | else: |
|
68 | else: | |
73 | ui.write('%s%s' % (name, eol)) |
|
69 | ui.write('%s%s' % (name, eol)) | |
74 |
|
70 | |||
75 | def removefile(path): |
|
71 | def removefile(path): | |
76 | try: |
|
72 | try: | |
77 | os.remove(path) |
|
73 | os.remove(path) | |
78 | except OSError: |
|
74 | except OSError: | |
79 | # read-only files cannot be unlinked under Windows |
|
75 | # read-only files cannot be unlinked under Windows | |
80 | s = os.stat(path) |
|
76 | s = os.stat(path) | |
81 | if (s.st_mode & stat.S_IWRITE) != 0: |
|
77 | if (s.st_mode & stat.S_IWRITE) != 0: | |
82 | raise |
|
78 | raise | |
83 | os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE) |
|
79 | os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE) | |
84 | os.remove(path) |
|
80 | os.remove(path) | |
85 |
|
81 | |||
86 | directories = [] |
|
82 | directories = [] | |
87 | match = cmdutil.match(repo, dirs, opts) |
|
83 | match = cmdutil.match(repo, dirs, opts) | |
88 | match.dir = directories.append |
|
84 | match.dir = directories.append | |
89 | status = repo.status(match=match, ignored=opts['all'], unknown=True) |
|
85 | status = repo.status(match=match, ignored=opts['all'], unknown=True) | |
90 |
|
86 | |||
91 | for f in sorted(status[4] + status[5]): |
|
87 | for f in sorted(status[4] + status[5]): | |
92 | ui.note(_('Removing file %s\n') % f) |
|
88 | ui.note(_('Removing file %s\n') % f) | |
93 | remove(removefile, f) |
|
89 | remove(removefile, f) | |
94 |
|
90 | |||
95 | for f in sorted(directories, reverse=True): |
|
91 | for f in sorted(directories, reverse=True): | |
96 | if match(f) and not os.listdir(repo.wjoin(f)): |
|
92 | if match(f) and not os.listdir(repo.wjoin(f)): | |
97 | ui.note(_('Removing directory %s\n') % f) |
|
93 | ui.note(_('Removing directory %s\n') % f) | |
98 | remove(os.rmdir, f) |
|
94 | remove(os.rmdir, f) | |
99 |
|
95 | |||
100 | cmdtable = { |
|
96 | cmdtable = { | |
101 | 'purge|clean': |
|
97 | 'purge|clean': | |
102 | (purge, |
|
98 | (purge, | |
103 | [('a', 'abort-on-err', None, _('abort if an error occurs')), |
|
99 | [('a', 'abort-on-err', None, _('abort if an error occurs')), | |
104 | ('', 'all', None, _('purge ignored files too')), |
|
100 | ('', 'all', None, _('purge ignored files too')), | |
105 | ('p', 'print', None, _('print filenames instead of deleting them')), |
|
101 | ('p', 'print', None, _('print filenames instead of deleting them')), | |
106 | ('0', 'print0', None, _('end filenames with NUL, for use with xargs' |
|
102 | ('0', 'print0', None, _('end filenames with NUL, for use with xargs' | |
107 | ' (implies -p/--print)')), |
|
103 | ' (implies -p/--print)')), | |
108 | ] + commands.walkopts, |
|
104 | ] + commands.walkopts, | |
109 | _('hg purge [OPTION]... [DIR]...')) |
|
105 | _('hg purge [OPTION]... [DIR]...')) | |
110 | } |
|
106 | } |
@@ -1,126 +1,121 b'' | |||||
1 | # win32mbcs.py -- MBCS filename support for Mercurial |
|
1 | # win32mbcs.py -- MBCS filename support for Mercurial | |
2 | # |
|
2 | # | |
3 | # Copyright (c) 2008 Shun-ichi Goto <shunichi.goto@gmail.com> |
|
3 | # Copyright (c) 2008 Shun-ichi Goto <shunichi.goto@gmail.com> | |
4 | # |
|
4 | # | |
5 | # Version: 0.2 |
|
5 | # Version: 0.2 | |
6 | # Author: Shun-ichi Goto <shunichi.goto@gmail.com> |
|
6 | # Author: Shun-ichi Goto <shunichi.goto@gmail.com> | |
7 | # |
|
7 | # | |
8 | # This software may be used and distributed according to the terms of the |
|
8 | # This software may be used and distributed according to the terms of the | |
9 | # GNU General Public License version 2, incorporated herein by reference. |
|
9 | # GNU General Public License version 2, incorporated herein by reference. | |
10 | # |
|
10 | # | |
11 |
|
11 | |||
12 | """allow to use MBCS path with problematic encoding. |
|
12 | """allow to use MBCS path with problematic encoding. | |
13 |
|
13 | |||
14 | Some MBCS encodings are not good for some path operations (i.e. |
|
14 | Some MBCS encodings are not good for some path operations (i.e. | |
15 | splitting path, case conversion, etc.) with its encoded bytes. We call |
|
15 | splitting path, case conversion, etc.) with its encoded bytes. We call | |
16 | such a encoding (i.e. shift_jis and big5) as "problematic encoding". |
|
16 | such a encoding (i.e. shift_jis and big5) as "problematic encoding". | |
17 | This extension can be used to fix the issue with those encodings by |
|
17 | This extension can be used to fix the issue with those encodings by | |
18 | wrapping some functions to convert to Unicode string before path |
|
18 | wrapping some functions to convert to Unicode string before path | |
19 | operation. |
|
19 | operation. | |
20 |
|
20 | |||
21 | This extension is useful for: |
|
21 | This extension is useful for: | |
22 | * Japanese Windows users using shift_jis encoding. |
|
22 | * Japanese Windows users using shift_jis encoding. | |
23 | * Chinese Windows users using big5 encoding. |
|
23 | * Chinese Windows users using big5 encoding. | |
24 | * All users who use a repository with one of problematic encodings on |
|
24 | * All users who use a repository with one of problematic encodings on | |
25 | case-insensitive file system. |
|
25 | case-insensitive file system. | |
26 |
|
26 | |||
27 | This extension is not needed for: |
|
27 | This extension is not needed for: | |
28 | * Any user who use only ASCII chars in path. |
|
28 | * Any user who use only ASCII chars in path. | |
29 | * Any user who do not use any of problematic encodings. |
|
29 | * Any user who do not use any of problematic encodings. | |
30 |
|
30 | |||
31 | Note that there are some limitations on using this extension: |
|
31 | Note that there are some limitations on using this extension: | |
32 | * You should use single encoding in one repository. |
|
32 | * You should use single encoding in one repository. | |
33 | * You should set same encoding for the repository by locale or |
|
33 | * You should set same encoding for the repository by locale or | |
34 | HGENCODING. |
|
34 | HGENCODING. | |
35 |
|
35 | |||
36 | To use this extension, enable the extension in .hg/hgrc or ~/.hgrc: |
|
|||
37 |
|
||||
38 | [extensions] |
|
|||
39 | hgext.win32mbcs = |
|
|||
40 |
|
||||
41 | Path encoding conversion are done between Unicode and |
|
36 | Path encoding conversion are done between Unicode and | |
42 | encoding.encoding which is decided by Mercurial from current locale |
|
37 | encoding.encoding which is decided by Mercurial from current locale | |
43 | setting or HGENCODING. |
|
38 | setting or HGENCODING. | |
44 |
|
39 | |||
45 | """ |
|
40 | """ | |
46 |
|
41 | |||
47 | import os |
|
42 | import os | |
48 | from mercurial.i18n import _ |
|
43 | from mercurial.i18n import _ | |
49 | from mercurial import util, encoding |
|
44 | from mercurial import util, encoding | |
50 |
|
45 | |||
51 | def decode(arg): |
|
46 | def decode(arg): | |
52 | if isinstance(arg, str): |
|
47 | if isinstance(arg, str): | |
53 | uarg = arg.decode(encoding.encoding) |
|
48 | uarg = arg.decode(encoding.encoding) | |
54 | if arg == uarg.encode(encoding.encoding): |
|
49 | if arg == uarg.encode(encoding.encoding): | |
55 | return uarg |
|
50 | return uarg | |
56 | raise UnicodeError("Not local encoding") |
|
51 | raise UnicodeError("Not local encoding") | |
57 | elif isinstance(arg, tuple): |
|
52 | elif isinstance(arg, tuple): | |
58 | return tuple(map(decode, arg)) |
|
53 | return tuple(map(decode, arg)) | |
59 | elif isinstance(arg, list): |
|
54 | elif isinstance(arg, list): | |
60 | return map(decode, arg) |
|
55 | return map(decode, arg) | |
61 | return arg |
|
56 | return arg | |
62 |
|
57 | |||
63 | def encode(arg): |
|
58 | def encode(arg): | |
64 | if isinstance(arg, unicode): |
|
59 | if isinstance(arg, unicode): | |
65 | return arg.encode(encoding.encoding) |
|
60 | return arg.encode(encoding.encoding) | |
66 | elif isinstance(arg, tuple): |
|
61 | elif isinstance(arg, tuple): | |
67 | return tuple(map(encode, arg)) |
|
62 | return tuple(map(encode, arg)) | |
68 | elif isinstance(arg, list): |
|
63 | elif isinstance(arg, list): | |
69 | return map(encode, arg) |
|
64 | return map(encode, arg) | |
70 | return arg |
|
65 | return arg | |
71 |
|
66 | |||
72 | def wrapper(func, args): |
|
67 | def wrapper(func, args): | |
73 | # check argument is unicode, then call original |
|
68 | # check argument is unicode, then call original | |
74 | for arg in args: |
|
69 | for arg in args: | |
75 | if isinstance(arg, unicode): |
|
70 | if isinstance(arg, unicode): | |
76 | return func(*args) |
|
71 | return func(*args) | |
77 |
|
72 | |||
78 | try: |
|
73 | try: | |
79 | # convert arguments to unicode, call func, then convert back |
|
74 | # convert arguments to unicode, call func, then convert back | |
80 | return encode(func(*decode(args))) |
|
75 | return encode(func(*decode(args))) | |
81 | except UnicodeError: |
|
76 | except UnicodeError: | |
82 | # If not encoded with encoding.encoding, report it then |
|
77 | # If not encoded with encoding.encoding, report it then | |
83 | # continue with calling original function. |
|
78 | # continue with calling original function. | |
84 | raise util.Abort(_("[win32mbcs] filename conversion fail with" |
|
79 | raise util.Abort(_("[win32mbcs] filename conversion fail with" | |
85 | " %s encoding\n") % (encoding.encoding)) |
|
80 | " %s encoding\n") % (encoding.encoding)) | |
86 |
|
81 | |||
87 | def wrapname(name): |
|
82 | def wrapname(name): | |
88 | idx = name.rfind('.') |
|
83 | idx = name.rfind('.') | |
89 | module = name[:idx] |
|
84 | module = name[:idx] | |
90 | name = name[idx+1:] |
|
85 | name = name[idx+1:] | |
91 | module = globals()[module] |
|
86 | module = globals()[module] | |
92 | func = getattr(module, name) |
|
87 | func = getattr(module, name) | |
93 | def f(*args): |
|
88 | def f(*args): | |
94 | return wrapper(func, args) |
|
89 | return wrapper(func, args) | |
95 | try: |
|
90 | try: | |
96 | f.__name__ = func.__name__ # fail with python23 |
|
91 | f.__name__ = func.__name__ # fail with python23 | |
97 | except Exception: |
|
92 | except Exception: | |
98 | pass |
|
93 | pass | |
99 | setattr(module, name, f) |
|
94 | setattr(module, name, f) | |
100 |
|
95 | |||
101 | # List of functions to be wrapped. |
|
96 | # List of functions to be wrapped. | |
102 | # NOTE: os.path.dirname() and os.path.basename() are safe because |
|
97 | # NOTE: os.path.dirname() and os.path.basename() are safe because | |
103 | # they use result of os.path.split() |
|
98 | # they use result of os.path.split() | |
104 | funcs = '''os.path.join os.path.split os.path.splitext |
|
99 | funcs = '''os.path.join os.path.split os.path.splitext | |
105 | os.path.splitunc os.path.normpath os.path.normcase os.makedirs |
|
100 | os.path.splitunc os.path.normpath os.path.normcase os.makedirs | |
106 | util.endswithsep util.splitpath util.checkcase util.fspath''' |
|
101 | util.endswithsep util.splitpath util.checkcase util.fspath''' | |
107 |
|
102 | |||
108 | # codec and alias names of sjis and big5 to be faked. |
|
103 | # codec and alias names of sjis and big5 to be faked. | |
109 | problematic_encodings = '''big5 big5-tw csbig5 big5hkscs big5-hkscs |
|
104 | problematic_encodings = '''big5 big5-tw csbig5 big5hkscs big5-hkscs | |
110 | hkscs cp932 932 ms932 mskanji ms-kanji shift_jis csshiftjis shiftjis |
|
105 | hkscs cp932 932 ms932 mskanji ms-kanji shift_jis csshiftjis shiftjis | |
111 | sjis s_jis shift_jis_2004 shiftjis2004 sjis_2004 sjis2004 |
|
106 | sjis s_jis shift_jis_2004 shiftjis2004 sjis_2004 sjis2004 | |
112 | shift_jisx0213 shiftjisx0213 sjisx0213 s_jisx0213 950 cp950 ms950 ''' |
|
107 | shift_jisx0213 shiftjisx0213 sjisx0213 s_jisx0213 950 cp950 ms950 ''' | |
113 |
|
108 | |||
114 | def reposetup(ui, repo): |
|
109 | def reposetup(ui, repo): | |
115 | # TODO: decide use of config section for this extension |
|
110 | # TODO: decide use of config section for this extension | |
116 | if not os.path.supports_unicode_filenames: |
|
111 | if not os.path.supports_unicode_filenames: | |
117 | ui.warn(_("[win32mbcs] cannot activate on this platform.\n")) |
|
112 | ui.warn(_("[win32mbcs] cannot activate on this platform.\n")) | |
118 | return |
|
113 | return | |
119 |
|
114 | |||
120 | # fake is only for relevant environment. |
|
115 | # fake is only for relevant environment. | |
121 | if encoding.encoding.lower() in problematic_encodings.split(): |
|
116 | if encoding.encoding.lower() in problematic_encodings.split(): | |
122 | for f in funcs.split(): |
|
117 | for f in funcs.split(): | |
123 | wrapname(f) |
|
118 | wrapname(f) | |
124 | ui.debug(_("[win32mbcs] activated with encoding: %s\n") |
|
119 | ui.debug(_("[win32mbcs] activated with encoding: %s\n") | |
125 | % encoding.encoding) |
|
120 | % encoding.encoding) | |
126 |
|
121 |
@@ -1,165 +1,159 b'' | |||||
1 | # zeroconf.py - zeroconf support for Mercurial |
|
1 | # zeroconf.py - zeroconf support for Mercurial | |
2 | # |
|
2 | # | |
3 | # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> |
|
3 | # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> | |
4 | # |
|
4 | # | |
5 | # This software may be used and distributed according to the terms of the |
|
5 | # This software may be used and distributed according to the terms of the | |
6 | # GNU General Public License version 2, incorporated herein by reference. |
|
6 | # GNU General Public License version 2, incorporated herein by reference. | |
7 |
|
7 | |||
8 | '''zeroconf support for Mercurial repositories |
|
8 | '''zeroconf support for Mercurial repositories | |
9 |
|
9 | |||
10 | Zeroconf enabled repositories will be announced in a network without |
|
10 | Zeroconf enabled repositories will be announced in a network without | |
11 | the need to configure a server or a service. They can be discovered |
|
11 | the need to configure a server or a service. They can be discovered | |
12 | without knowing their actual IP address. |
|
12 | without knowing their actual IP address. | |
13 |
|
13 | |||
14 | To use the zeroconf extension add the following entry to your hgrc |
|
|||
15 | file: |
|
|||
16 |
|
||||
17 | [extensions] |
|
|||
18 | hgext.zeroconf = |
|
|||
19 |
|
||||
20 | To allow other people to discover your repository using run "hg serve" |
|
14 | To allow other people to discover your repository using run "hg serve" | |
21 | in your repository. |
|
15 | in your repository. | |
22 |
|
16 | |||
23 | $ cd test |
|
17 | $ cd test | |
24 | $ hg serve |
|
18 | $ hg serve | |
25 |
|
19 | |||
26 | You can discover zeroconf enabled repositories by running "hg paths". |
|
20 | You can discover zeroconf enabled repositories by running "hg paths". | |
27 |
|
21 | |||
28 | $ hg paths |
|
22 | $ hg paths | |
29 | zc-test = http://example.com:8000/test |
|
23 | zc-test = http://example.com:8000/test | |
30 | ''' |
|
24 | ''' | |
31 |
|
25 | |||
32 | import Zeroconf, socket, time, os |
|
26 | import Zeroconf, socket, time, os | |
33 | from mercurial import ui |
|
27 | from mercurial import ui | |
34 | from mercurial import extensions |
|
28 | from mercurial import extensions | |
35 | from mercurial.hgweb import hgweb_mod |
|
29 | from mercurial.hgweb import hgweb_mod | |
36 | from mercurial.hgweb import hgwebdir_mod |
|
30 | from mercurial.hgweb import hgwebdir_mod | |
37 |
|
31 | |||
38 | # publish |
|
32 | # publish | |
39 |
|
33 | |||
40 | server = None |
|
34 | server = None | |
41 | localip = None |
|
35 | localip = None | |
42 |
|
36 | |||
43 | def getip(): |
|
37 | def getip(): | |
44 | # finds external-facing interface without sending any packets (Linux) |
|
38 | # finds external-facing interface without sending any packets (Linux) | |
45 | try: |
|
39 | try: | |
46 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
|
40 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | |
47 | s.connect(('1.0.0.1', 0)) |
|
41 | s.connect(('1.0.0.1', 0)) | |
48 | ip = s.getsockname()[0] |
|
42 | ip = s.getsockname()[0] | |
49 | return ip |
|
43 | return ip | |
50 | except: |
|
44 | except: | |
51 | pass |
|
45 | pass | |
52 |
|
46 | |||
53 | # Generic method, sometimes gives useless results |
|
47 | # Generic method, sometimes gives useless results | |
54 | try: |
|
48 | try: | |
55 | dumbip = socket.gethostbyaddr(socket.gethostname())[2][0] |
|
49 | dumbip = socket.gethostbyaddr(socket.gethostname())[2][0] | |
56 | if not dumbip.startswith('127.') and ':' not in dumbip: |
|
50 | if not dumbip.startswith('127.') and ':' not in dumbip: | |
57 | return dumbip |
|
51 | return dumbip | |
58 | except socket.gaierror: |
|
52 | except socket.gaierror: | |
59 | dumbip = '127.0.0.1' |
|
53 | dumbip = '127.0.0.1' | |
60 |
|
54 | |||
61 | # works elsewhere, but actually sends a packet |
|
55 | # works elsewhere, but actually sends a packet | |
62 | try: |
|
56 | try: | |
63 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
|
57 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | |
64 | s.connect(('1.0.0.1', 1)) |
|
58 | s.connect(('1.0.0.1', 1)) | |
65 | ip = s.getsockname()[0] |
|
59 | ip = s.getsockname()[0] | |
66 | return ip |
|
60 | return ip | |
67 | except: |
|
61 | except: | |
68 | pass |
|
62 | pass | |
69 |
|
63 | |||
70 | return dumbip |
|
64 | return dumbip | |
71 |
|
65 | |||
72 | def publish(name, desc, path, port): |
|
66 | def publish(name, desc, path, port): | |
73 | global server, localip |
|
67 | global server, localip | |
74 | if not server: |
|
68 | if not server: | |
75 | ip = getip() |
|
69 | ip = getip() | |
76 | if ip.startswith('127.'): |
|
70 | if ip.startswith('127.'): | |
77 | # if we have no internet connection, this can happen. |
|
71 | # if we have no internet connection, this can happen. | |
78 | return |
|
72 | return | |
79 | localip = socket.inet_aton(ip) |
|
73 | localip = socket.inet_aton(ip) | |
80 | server = Zeroconf.Zeroconf(ip) |
|
74 | server = Zeroconf.Zeroconf(ip) | |
81 |
|
75 | |||
82 | hostname = socket.gethostname().split('.')[0] |
|
76 | hostname = socket.gethostname().split('.')[0] | |
83 | host = hostname + ".local" |
|
77 | host = hostname + ".local" | |
84 | name = "%s-%s" % (hostname, name) |
|
78 | name = "%s-%s" % (hostname, name) | |
85 |
|
79 | |||
86 | # advertise to browsers |
|
80 | # advertise to browsers | |
87 | svc = Zeroconf.ServiceInfo('_http._tcp.local.', |
|
81 | svc = Zeroconf.ServiceInfo('_http._tcp.local.', | |
88 | name + '._http._tcp.local.', |
|
82 | name + '._http._tcp.local.', | |
89 | server = host, |
|
83 | server = host, | |
90 | port = port, |
|
84 | port = port, | |
91 | properties = {'description': desc, |
|
85 | properties = {'description': desc, | |
92 | 'path': "/" + path}, |
|
86 | 'path': "/" + path}, | |
93 | address = localip, weight = 0, priority = 0) |
|
87 | address = localip, weight = 0, priority = 0) | |
94 | server.registerService(svc) |
|
88 | server.registerService(svc) | |
95 |
|
89 | |||
96 | # advertise to Mercurial clients |
|
90 | # advertise to Mercurial clients | |
97 | svc = Zeroconf.ServiceInfo('_hg._tcp.local.', |
|
91 | svc = Zeroconf.ServiceInfo('_hg._tcp.local.', | |
98 | name + '._hg._tcp.local.', |
|
92 | name + '._hg._tcp.local.', | |
99 | server = host, |
|
93 | server = host, | |
100 | port = port, |
|
94 | port = port, | |
101 | properties = {'description': desc, |
|
95 | properties = {'description': desc, | |
102 | 'path': "/" + path}, |
|
96 | 'path': "/" + path}, | |
103 | address = localip, weight = 0, priority = 0) |
|
97 | address = localip, weight = 0, priority = 0) | |
104 | server.registerService(svc) |
|
98 | server.registerService(svc) | |
105 |
|
99 | |||
106 | class hgwebzc(hgweb_mod.hgweb): |
|
100 | class hgwebzc(hgweb_mod.hgweb): | |
107 | def __init__(self, repo, name=None): |
|
101 | def __init__(self, repo, name=None): | |
108 | super(hgwebzc, self).__init__(repo, name) |
|
102 | super(hgwebzc, self).__init__(repo, name) | |
109 | name = self.reponame or os.path.basename(repo.root) |
|
103 | name = self.reponame or os.path.basename(repo.root) | |
110 | desc = self.repo.ui.config("web", "description", name) |
|
104 | desc = self.repo.ui.config("web", "description", name) | |
111 | publish(name, desc, name, int(repo.ui.config("web", "port", 8000))) |
|
105 | publish(name, desc, name, int(repo.ui.config("web", "port", 8000))) | |
112 |
|
106 | |||
113 | class hgwebdirzc(hgwebdir_mod.hgwebdir): |
|
107 | class hgwebdirzc(hgwebdir_mod.hgwebdir): | |
114 | def run(self): |
|
108 | def run(self): | |
115 | for r, p in self.repos: |
|
109 | for r, p in self.repos: | |
116 | u = self.ui.copy() |
|
110 | u = self.ui.copy() | |
117 | u.readconfig(os.path.join(p, '.hg', 'hgrc')) |
|
111 | u.readconfig(os.path.join(p, '.hg', 'hgrc')) | |
118 | n = os.path.basename(r) |
|
112 | n = os.path.basename(r) | |
119 | publish(n, "hgweb", p, int(u.config("web", "port", 8000))) |
|
113 | publish(n, "hgweb", p, int(u.config("web", "port", 8000))) | |
120 | return super(hgwebdirzc, self).run() |
|
114 | return super(hgwebdirzc, self).run() | |
121 |
|
115 | |||
122 | # listen |
|
116 | # listen | |
123 |
|
117 | |||
124 | class listener(object): |
|
118 | class listener(object): | |
125 | def __init__(self): |
|
119 | def __init__(self): | |
126 | self.found = {} |
|
120 | self.found = {} | |
127 | def removeService(self, server, type, name): |
|
121 | def removeService(self, server, type, name): | |
128 | if repr(name) in self.found: |
|
122 | if repr(name) in self.found: | |
129 | del self.found[repr(name)] |
|
123 | del self.found[repr(name)] | |
130 | def addService(self, server, type, name): |
|
124 | def addService(self, server, type, name): | |
131 | self.found[repr(name)] = server.getServiceInfo(type, name) |
|
125 | self.found[repr(name)] = server.getServiceInfo(type, name) | |
132 |
|
126 | |||
133 | def getzcpaths(): |
|
127 | def getzcpaths(): | |
134 | ip = getip() |
|
128 | ip = getip() | |
135 | if ip.startswith('127.'): |
|
129 | if ip.startswith('127.'): | |
136 | return |
|
130 | return | |
137 | server = Zeroconf.Zeroconf(ip) |
|
131 | server = Zeroconf.Zeroconf(ip) | |
138 | l = listener() |
|
132 | l = listener() | |
139 | Zeroconf.ServiceBrowser(server, "_hg._tcp.local.", l) |
|
133 | Zeroconf.ServiceBrowser(server, "_hg._tcp.local.", l) | |
140 | time.sleep(1) |
|
134 | time.sleep(1) | |
141 | server.close() |
|
135 | server.close() | |
142 | for v in l.found.values(): |
|
136 | for v in l.found.values(): | |
143 | n = v.name[:v.name.index('.')] |
|
137 | n = v.name[:v.name.index('.')] | |
144 | n.replace(" ", "-") |
|
138 | n.replace(" ", "-") | |
145 | u = "http://%s:%s%s" % (socket.inet_ntoa(v.address), v.port, |
|
139 | u = "http://%s:%s%s" % (socket.inet_ntoa(v.address), v.port, | |
146 | v.properties.get("path", "/")) |
|
140 | v.properties.get("path", "/")) | |
147 | yield "zc-" + n, u |
|
141 | yield "zc-" + n, u | |
148 |
|
142 | |||
149 | def config(orig, self, section, key, default=None, untrusted=False): |
|
143 | def config(orig, self, section, key, default=None, untrusted=False): | |
150 | if section == "paths" and key.startswith("zc-"): |
|
144 | if section == "paths" and key.startswith("zc-"): | |
151 | for n, p in getzcpaths(): |
|
145 | for n, p in getzcpaths(): | |
152 | if n == key: |
|
146 | if n == key: | |
153 | return p |
|
147 | return p | |
154 | return orig(self, section, key, default, untrusted) |
|
148 | return orig(self, section, key, default, untrusted) | |
155 |
|
149 | |||
156 | def configitems(orig, self, section, untrusted=False): |
|
150 | def configitems(orig, self, section, untrusted=False): | |
157 | r = orig(self, section, untrusted) |
|
151 | r = orig(self, section, untrusted) | |
158 | if section == "paths": |
|
152 | if section == "paths": | |
159 | r += getzcpaths() |
|
153 | r += getzcpaths() | |
160 | return r |
|
154 | return r | |
161 |
|
155 | |||
162 | extensions.wrapfunction(ui.ui, 'config', config) |
|
156 | extensions.wrapfunction(ui.ui, 'config', config) | |
163 | extensions.wrapfunction(ui.ui, 'configitems', configitems) |
|
157 | extensions.wrapfunction(ui.ui, 'configitems', configitems) | |
164 | hgweb_mod.hgweb = hgwebzc |
|
158 | hgweb_mod.hgweb = hgwebzc | |
165 | hgwebdir_mod.hgwebdir = hgwebdirzc |
|
159 | hgwebdir_mod.hgwebdir = hgwebdirzc |
General Comments 0
You need to be logged in to leave comments.
Login now