##// END OF EJS Templates
summary: indicate if a rebase is underway
Bryan O'Sullivan -
r19214:0250047a default
parent child Browse files
Show More
@@ -1,545 +1,550 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 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 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''colorize output from some commands
8 '''colorize output from some commands
9
9
10 This extension modifies the status and resolve commands to add color
10 This extension modifies the status and resolve commands to add color
11 to their output to reflect file status, the qseries command to add
11 to their output to reflect file status, the qseries command to add
12 color to reflect patch status (applied, unapplied, missing), and to
12 color to reflect patch status (applied, unapplied, missing), and to
13 diff-related commands to highlight additions, removals, diff headers,
13 diff-related commands to highlight additions, removals, diff headers,
14 and trailing whitespace.
14 and trailing whitespace.
15
15
16 Other effects in addition to color, like bold and underlined text, are
16 Other effects in addition to color, like bold and underlined text, are
17 also available. By default, the terminfo database is used to find the
17 also available. By default, the terminfo database is used to find the
18 terminal codes used to change color and effect. If terminfo is not
18 terminal codes used to change color and effect. If terminfo is not
19 available, then effects are rendered with the ECMA-48 SGR control
19 available, then effects are rendered with the ECMA-48 SGR control
20 function (aka ANSI escape codes).
20 function (aka ANSI escape codes).
21
21
22 Default effects may be overridden from your configuration file::
22 Default effects may be overridden from your configuration file::
23
23
24 [color]
24 [color]
25 status.modified = blue bold underline red_background
25 status.modified = blue bold underline red_background
26 status.added = green bold
26 status.added = green bold
27 status.removed = red bold blue_background
27 status.removed = red bold blue_background
28 status.deleted = cyan bold underline
28 status.deleted = cyan bold underline
29 status.unknown = magenta bold underline
29 status.unknown = magenta bold underline
30 status.ignored = black bold
30 status.ignored = black bold
31
31
32 # 'none' turns off all effects
32 # 'none' turns off all effects
33 status.clean = none
33 status.clean = none
34 status.copied = none
34 status.copied = none
35
35
36 qseries.applied = blue bold underline
36 qseries.applied = blue bold underline
37 qseries.unapplied = black bold
37 qseries.unapplied = black bold
38 qseries.missing = red bold
38 qseries.missing = red bold
39
39
40 diff.diffline = bold
40 diff.diffline = bold
41 diff.extended = cyan bold
41 diff.extended = cyan bold
42 diff.file_a = red bold
42 diff.file_a = red bold
43 diff.file_b = green bold
43 diff.file_b = green bold
44 diff.hunk = magenta
44 diff.hunk = magenta
45 diff.deleted = red
45 diff.deleted = red
46 diff.inserted = green
46 diff.inserted = green
47 diff.changed = white
47 diff.changed = white
48 diff.trailingwhitespace = bold red_background
48 diff.trailingwhitespace = bold red_background
49
49
50 resolve.unresolved = red bold
50 resolve.unresolved = red bold
51 resolve.resolved = green bold
51 resolve.resolved = green bold
52
52
53 bookmarks.current = green
53 bookmarks.current = green
54
54
55 branches.active = none
55 branches.active = none
56 branches.closed = black bold
56 branches.closed = black bold
57 branches.current = green
57 branches.current = green
58 branches.inactive = none
58 branches.inactive = none
59
59
60 tags.normal = green
60 tags.normal = green
61 tags.local = black bold
61 tags.local = black bold
62
62
63 rebase.rebased = blue
64 rebase.remaining = red bold
65
63 The available effects in terminfo mode are 'blink', 'bold', 'dim',
66 The available effects in terminfo mode are 'blink', 'bold', 'dim',
64 'inverse', 'invisible', 'italic', 'standout', and 'underline'; in
67 'inverse', 'invisible', 'italic', 'standout', and 'underline'; in
65 ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and
68 ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and
66 'underline'. How each is rendered depends on the terminal emulator.
69 'underline'. How each is rendered depends on the terminal emulator.
67 Some may not be available for a given terminal type, and will be
70 Some may not be available for a given terminal type, and will be
68 silently ignored.
71 silently ignored.
69
72
70 Note that on some systems, terminfo mode may cause problems when using
73 Note that on some systems, terminfo mode may cause problems when using
71 color with the pager extension and less -R. less with the -R option
74 color with the pager extension and less -R. less with the -R option
72 will only display ECMA-48 color codes, and terminfo mode may sometimes
75 will only display ECMA-48 color codes, and terminfo mode may sometimes
73 emit codes that less doesn't understand. You can work around this by
76 emit codes that less doesn't understand. You can work around this by
74 either using ansi mode (or auto mode), or by using less -r (which will
77 either using ansi mode (or auto mode), or by using less -r (which will
75 pass through all terminal control codes, not just color control
78 pass through all terminal control codes, not just color control
76 codes).
79 codes).
77
80
78 Because there are only eight standard colors, this module allows you
81 Because there are only eight standard colors, this module allows you
79 to define color names for other color slots which might be available
82 to define color names for other color slots which might be available
80 for your terminal type, assuming terminfo mode. For instance::
83 for your terminal type, assuming terminfo mode. For instance::
81
84
82 color.brightblue = 12
85 color.brightblue = 12
83 color.pink = 207
86 color.pink = 207
84 color.orange = 202
87 color.orange = 202
85
88
86 to set 'brightblue' to color slot 12 (useful for 16 color terminals
89 to set 'brightblue' to color slot 12 (useful for 16 color terminals
87 that have brighter colors defined in the upper eight) and, 'pink' and
90 that have brighter colors defined in the upper eight) and, 'pink' and
88 'orange' to colors in 256-color xterm's default color cube. These
91 'orange' to colors in 256-color xterm's default color cube. These
89 defined colors may then be used as any of the pre-defined eight,
92 defined colors may then be used as any of the pre-defined eight,
90 including appending '_background' to set the background to that color.
93 including appending '_background' to set the background to that color.
91
94
92 By default, the color extension will use ANSI mode (or win32 mode on
95 By default, the color extension will use ANSI mode (or win32 mode on
93 Windows) if it detects a terminal. To override auto mode (to enable
96 Windows) if it detects a terminal. To override auto mode (to enable
94 terminfo mode, for example), set the following configuration option::
97 terminfo mode, for example), set the following configuration option::
95
98
96 [color]
99 [color]
97 mode = terminfo
100 mode = terminfo
98
101
99 Any value other than 'ansi', 'win32', 'terminfo', or 'auto' will
102 Any value other than 'ansi', 'win32', 'terminfo', or 'auto' will
100 disable color.
103 disable color.
101 '''
104 '''
102
105
103 import os
106 import os
104
107
105 from mercurial import commands, dispatch, extensions, ui as uimod, util
108 from mercurial import commands, dispatch, extensions, ui as uimod, util
106 from mercurial import templater, error
109 from mercurial import templater, error
107 from mercurial.i18n import _
110 from mercurial.i18n import _
108
111
109 testedwith = 'internal'
112 testedwith = 'internal'
110
113
111 # start and stop parameters for effects
114 # start and stop parameters for effects
112 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
115 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
113 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
116 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
114 'italic': 3, 'underline': 4, 'inverse': 7,
117 'italic': 3, 'underline': 4, 'inverse': 7,
115 'black_background': 40, 'red_background': 41,
118 'black_background': 40, 'red_background': 41,
116 'green_background': 42, 'yellow_background': 43,
119 'green_background': 42, 'yellow_background': 43,
117 'blue_background': 44, 'purple_background': 45,
120 'blue_background': 44, 'purple_background': 45,
118 'cyan_background': 46, 'white_background': 47}
121 'cyan_background': 46, 'white_background': 47}
119
122
120 def _terminfosetup(ui, mode):
123 def _terminfosetup(ui, mode):
121 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
124 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
122
125
123 global _terminfo_params
126 global _terminfo_params
124 # If we failed to load curses, we go ahead and return.
127 # If we failed to load curses, we go ahead and return.
125 if not _terminfo_params:
128 if not _terminfo_params:
126 return
129 return
127 # Otherwise, see what the config file says.
130 # Otherwise, see what the config file says.
128 if mode not in ('auto', 'terminfo'):
131 if mode not in ('auto', 'terminfo'):
129 return
132 return
130
133
131 _terminfo_params.update((key[6:], (False, int(val)))
134 _terminfo_params.update((key[6:], (False, int(val)))
132 for key, val in ui.configitems('color')
135 for key, val in ui.configitems('color')
133 if key.startswith('color.'))
136 if key.startswith('color.'))
134
137
135 try:
138 try:
136 curses.setupterm()
139 curses.setupterm()
137 except curses.error, e:
140 except curses.error, e:
138 _terminfo_params = {}
141 _terminfo_params = {}
139 return
142 return
140
143
141 for key, (b, e) in _terminfo_params.items():
144 for key, (b, e) in _terminfo_params.items():
142 if not b:
145 if not b:
143 continue
146 continue
144 if not curses.tigetstr(e):
147 if not curses.tigetstr(e):
145 # Most terminals don't support dim, invis, etc, so don't be
148 # Most terminals don't support dim, invis, etc, so don't be
146 # noisy and use ui.debug().
149 # noisy and use ui.debug().
147 ui.debug("no terminfo entry for %s\n" % e)
150 ui.debug("no terminfo entry for %s\n" % e)
148 del _terminfo_params[key]
151 del _terminfo_params[key]
149 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
152 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
150 # Only warn about missing terminfo entries if we explicitly asked for
153 # Only warn about missing terminfo entries if we explicitly asked for
151 # terminfo mode.
154 # terminfo mode.
152 if mode == "terminfo":
155 if mode == "terminfo":
153 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
156 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
154 "ECMA-48 color\n"))
157 "ECMA-48 color\n"))
155 _terminfo_params = {}
158 _terminfo_params = {}
156
159
157 def _modesetup(ui, opts):
160 def _modesetup(ui, opts):
158 global _terminfo_params
161 global _terminfo_params
159
162
160 coloropt = opts['color']
163 coloropt = opts['color']
161 auto = coloropt == 'auto'
164 auto = coloropt == 'auto'
162 always = not auto and util.parsebool(coloropt)
165 always = not auto and util.parsebool(coloropt)
163 if not always and not auto:
166 if not always and not auto:
164 return None
167 return None
165
168
166 formatted = always or (os.environ.get('TERM') != 'dumb' and ui.formatted())
169 formatted = always or (os.environ.get('TERM') != 'dumb' and ui.formatted())
167
170
168 mode = ui.config('color', 'mode', 'auto')
171 mode = ui.config('color', 'mode', 'auto')
169 realmode = mode
172 realmode = mode
170 if mode == 'auto':
173 if mode == 'auto':
171 if os.name == 'nt' and 'TERM' not in os.environ:
174 if os.name == 'nt' and 'TERM' not in os.environ:
172 # looks line a cmd.exe console, use win32 API or nothing
175 # looks line a cmd.exe console, use win32 API or nothing
173 realmode = 'win32'
176 realmode = 'win32'
174 else:
177 else:
175 realmode = 'ansi'
178 realmode = 'ansi'
176
179
177 if realmode == 'win32':
180 if realmode == 'win32':
178 _terminfo_params = {}
181 _terminfo_params = {}
179 if not w32effects:
182 if not w32effects:
180 if mode == 'win32':
183 if mode == 'win32':
181 # only warn if color.mode is explicitly set to win32
184 # only warn if color.mode is explicitly set to win32
182 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
185 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
183 return None
186 return None
184 _effects.update(w32effects)
187 _effects.update(w32effects)
185 elif realmode == 'ansi':
188 elif realmode == 'ansi':
186 _terminfo_params = {}
189 _terminfo_params = {}
187 elif realmode == 'terminfo':
190 elif realmode == 'terminfo':
188 _terminfosetup(ui, mode)
191 _terminfosetup(ui, mode)
189 if not _terminfo_params:
192 if not _terminfo_params:
190 if mode == 'terminfo':
193 if mode == 'terminfo':
191 ## FIXME Shouldn't we return None in this case too?
194 ## FIXME Shouldn't we return None in this case too?
192 # only warn if color.mode is explicitly set to win32
195 # only warn if color.mode is explicitly set to win32
193 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
196 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
194 realmode = 'ansi'
197 realmode = 'ansi'
195 else:
198 else:
196 return None
199 return None
197
200
198 if always or (auto and formatted):
201 if always or (auto and formatted):
199 return realmode
202 return realmode
200 return None
203 return None
201
204
202 try:
205 try:
203 import curses
206 import curses
204 # Mapping from effect name to terminfo attribute name or color number.
207 # Mapping from effect name to terminfo attribute name or color number.
205 # This will also force-load the curses module.
208 # This will also force-load the curses module.
206 _terminfo_params = {'none': (True, 'sgr0'),
209 _terminfo_params = {'none': (True, 'sgr0'),
207 'standout': (True, 'smso'),
210 'standout': (True, 'smso'),
208 'underline': (True, 'smul'),
211 'underline': (True, 'smul'),
209 'reverse': (True, 'rev'),
212 'reverse': (True, 'rev'),
210 'inverse': (True, 'rev'),
213 'inverse': (True, 'rev'),
211 'blink': (True, 'blink'),
214 'blink': (True, 'blink'),
212 'dim': (True, 'dim'),
215 'dim': (True, 'dim'),
213 'bold': (True, 'bold'),
216 'bold': (True, 'bold'),
214 'invisible': (True, 'invis'),
217 'invisible': (True, 'invis'),
215 'italic': (True, 'sitm'),
218 'italic': (True, 'sitm'),
216 'black': (False, curses.COLOR_BLACK),
219 'black': (False, curses.COLOR_BLACK),
217 'red': (False, curses.COLOR_RED),
220 'red': (False, curses.COLOR_RED),
218 'green': (False, curses.COLOR_GREEN),
221 'green': (False, curses.COLOR_GREEN),
219 'yellow': (False, curses.COLOR_YELLOW),
222 'yellow': (False, curses.COLOR_YELLOW),
220 'blue': (False, curses.COLOR_BLUE),
223 'blue': (False, curses.COLOR_BLUE),
221 'magenta': (False, curses.COLOR_MAGENTA),
224 'magenta': (False, curses.COLOR_MAGENTA),
222 'cyan': (False, curses.COLOR_CYAN),
225 'cyan': (False, curses.COLOR_CYAN),
223 'white': (False, curses.COLOR_WHITE)}
226 'white': (False, curses.COLOR_WHITE)}
224 except ImportError:
227 except ImportError:
225 _terminfo_params = False
228 _terminfo_params = False
226
229
227 _styles = {'grep.match': 'red bold',
230 _styles = {'grep.match': 'red bold',
228 'grep.linenumber': 'green',
231 'grep.linenumber': 'green',
229 'grep.rev': 'green',
232 'grep.rev': 'green',
230 'grep.change': 'green',
233 'grep.change': 'green',
231 'grep.sep': 'cyan',
234 'grep.sep': 'cyan',
232 'grep.filename': 'magenta',
235 'grep.filename': 'magenta',
233 'grep.user': 'magenta',
236 'grep.user': 'magenta',
234 'grep.date': 'magenta',
237 'grep.date': 'magenta',
235 'bookmarks.current': 'green',
238 'bookmarks.current': 'green',
236 'branches.active': 'none',
239 'branches.active': 'none',
237 'branches.closed': 'black bold',
240 'branches.closed': 'black bold',
238 'branches.current': 'green',
241 'branches.current': 'green',
239 'branches.inactive': 'none',
242 'branches.inactive': 'none',
240 'diff.changed': 'white',
243 'diff.changed': 'white',
241 'diff.deleted': 'red',
244 'diff.deleted': 'red',
242 'diff.diffline': 'bold',
245 'diff.diffline': 'bold',
243 'diff.extended': 'cyan bold',
246 'diff.extended': 'cyan bold',
244 'diff.file_a': 'red bold',
247 'diff.file_a': 'red bold',
245 'diff.file_b': 'green bold',
248 'diff.file_b': 'green bold',
246 'diff.hunk': 'magenta',
249 'diff.hunk': 'magenta',
247 'diff.inserted': 'green',
250 'diff.inserted': 'green',
248 'diff.trailingwhitespace': 'bold red_background',
251 'diff.trailingwhitespace': 'bold red_background',
249 'diffstat.deleted': 'red',
252 'diffstat.deleted': 'red',
250 'diffstat.inserted': 'green',
253 'diffstat.inserted': 'green',
251 'ui.prompt': 'yellow',
254 'ui.prompt': 'yellow',
252 'log.changeset': 'yellow',
255 'log.changeset': 'yellow',
256 'rebase.rebased': 'blue',
257 'rebase.remaining': 'red bold',
253 'resolve.resolved': 'green bold',
258 'resolve.resolved': 'green bold',
254 'resolve.unresolved': 'red bold',
259 'resolve.unresolved': 'red bold',
255 'status.added': 'green bold',
260 'status.added': 'green bold',
256 'status.clean': 'none',
261 'status.clean': 'none',
257 'status.copied': 'none',
262 'status.copied': 'none',
258 'status.deleted': 'cyan bold underline',
263 'status.deleted': 'cyan bold underline',
259 'status.ignored': 'black bold',
264 'status.ignored': 'black bold',
260 'status.modified': 'blue bold',
265 'status.modified': 'blue bold',
261 'status.removed': 'red bold',
266 'status.removed': 'red bold',
262 'status.unknown': 'magenta bold underline',
267 'status.unknown': 'magenta bold underline',
263 'tags.normal': 'green',
268 'tags.normal': 'green',
264 'tags.local': 'black bold'}
269 'tags.local': 'black bold'}
265
270
266
271
267 def _effect_str(effect):
272 def _effect_str(effect):
268 '''Helper function for render_effects().'''
273 '''Helper function for render_effects().'''
269
274
270 bg = False
275 bg = False
271 if effect.endswith('_background'):
276 if effect.endswith('_background'):
272 bg = True
277 bg = True
273 effect = effect[:-11]
278 effect = effect[:-11]
274 attr, val = _terminfo_params[effect]
279 attr, val = _terminfo_params[effect]
275 if attr:
280 if attr:
276 return curses.tigetstr(val)
281 return curses.tigetstr(val)
277 elif bg:
282 elif bg:
278 return curses.tparm(curses.tigetstr('setab'), val)
283 return curses.tparm(curses.tigetstr('setab'), val)
279 else:
284 else:
280 return curses.tparm(curses.tigetstr('setaf'), val)
285 return curses.tparm(curses.tigetstr('setaf'), val)
281
286
282 def render_effects(text, effects):
287 def render_effects(text, effects):
283 'Wrap text in commands to turn on each effect.'
288 'Wrap text in commands to turn on each effect.'
284 if not text:
289 if not text:
285 return text
290 return text
286 if not _terminfo_params:
291 if not _terminfo_params:
287 start = [str(_effects[e]) for e in ['none'] + effects.split()]
292 start = [str(_effects[e]) for e in ['none'] + effects.split()]
288 start = '\033[' + ';'.join(start) + 'm'
293 start = '\033[' + ';'.join(start) + 'm'
289 stop = '\033[' + str(_effects['none']) + 'm'
294 stop = '\033[' + str(_effects['none']) + 'm'
290 else:
295 else:
291 start = ''.join(_effect_str(effect)
296 start = ''.join(_effect_str(effect)
292 for effect in ['none'] + effects.split())
297 for effect in ['none'] + effects.split())
293 stop = _effect_str('none')
298 stop = _effect_str('none')
294 return ''.join([start, text, stop])
299 return ''.join([start, text, stop])
295
300
296 def extstyles():
301 def extstyles():
297 for name, ext in extensions.extensions():
302 for name, ext in extensions.extensions():
298 _styles.update(getattr(ext, 'colortable', {}))
303 _styles.update(getattr(ext, 'colortable', {}))
299
304
300 def configstyles(ui):
305 def configstyles(ui):
301 for status, cfgeffects in ui.configitems('color'):
306 for status, cfgeffects in ui.configitems('color'):
302 if '.' not in status or status.startswith('color.'):
307 if '.' not in status or status.startswith('color.'):
303 continue
308 continue
304 cfgeffects = ui.configlist('color', status)
309 cfgeffects = ui.configlist('color', status)
305 if cfgeffects:
310 if cfgeffects:
306 good = []
311 good = []
307 for e in cfgeffects:
312 for e in cfgeffects:
308 if not _terminfo_params and e in _effects:
313 if not _terminfo_params and e in _effects:
309 good.append(e)
314 good.append(e)
310 elif e in _terminfo_params or e[:-11] in _terminfo_params:
315 elif e in _terminfo_params or e[:-11] in _terminfo_params:
311 good.append(e)
316 good.append(e)
312 else:
317 else:
313 ui.warn(_("ignoring unknown color/effect %r "
318 ui.warn(_("ignoring unknown color/effect %r "
314 "(configured in color.%s)\n")
319 "(configured in color.%s)\n")
315 % (e, status))
320 % (e, status))
316 _styles[status] = ' '.join(good)
321 _styles[status] = ' '.join(good)
317
322
318 class colorui(uimod.ui):
323 class colorui(uimod.ui):
319 def popbuffer(self, labeled=False):
324 def popbuffer(self, labeled=False):
320 if self._colormode is None:
325 if self._colormode is None:
321 return super(colorui, self).popbuffer(labeled)
326 return super(colorui, self).popbuffer(labeled)
322
327
323 if labeled:
328 if labeled:
324 return ''.join(self.label(a, label) for a, label
329 return ''.join(self.label(a, label) for a, label
325 in self._buffers.pop())
330 in self._buffers.pop())
326 return ''.join(a for a, label in self._buffers.pop())
331 return ''.join(a for a, label in self._buffers.pop())
327
332
328 _colormode = 'ansi'
333 _colormode = 'ansi'
329 def write(self, *args, **opts):
334 def write(self, *args, **opts):
330 if self._colormode is None:
335 if self._colormode is None:
331 return super(colorui, self).write(*args, **opts)
336 return super(colorui, self).write(*args, **opts)
332
337
333 label = opts.get('label', '')
338 label = opts.get('label', '')
334 if self._buffers:
339 if self._buffers:
335 self._buffers[-1].extend([(str(a), label) for a in args])
340 self._buffers[-1].extend([(str(a), label) for a in args])
336 elif self._colormode == 'win32':
341 elif self._colormode == 'win32':
337 for a in args:
342 for a in args:
338 win32print(a, super(colorui, self).write, **opts)
343 win32print(a, super(colorui, self).write, **opts)
339 else:
344 else:
340 return super(colorui, self).write(
345 return super(colorui, self).write(
341 *[self.label(str(a), label) for a in args], **opts)
346 *[self.label(str(a), label) for a in args], **opts)
342
347
343 def write_err(self, *args, **opts):
348 def write_err(self, *args, **opts):
344 if self._colormode is None:
349 if self._colormode is None:
345 return super(colorui, self).write_err(*args, **opts)
350 return super(colorui, self).write_err(*args, **opts)
346
351
347 label = opts.get('label', '')
352 label = opts.get('label', '')
348 if self._colormode == 'win32':
353 if self._colormode == 'win32':
349 for a in args:
354 for a in args:
350 win32print(a, super(colorui, self).write_err, **opts)
355 win32print(a, super(colorui, self).write_err, **opts)
351 else:
356 else:
352 return super(colorui, self).write_err(
357 return super(colorui, self).write_err(
353 *[self.label(str(a), label) for a in args], **opts)
358 *[self.label(str(a), label) for a in args], **opts)
354
359
355 def label(self, msg, label):
360 def label(self, msg, label):
356 if self._colormode is None:
361 if self._colormode is None:
357 return super(colorui, self).label(msg, label)
362 return super(colorui, self).label(msg, label)
358
363
359 effects = []
364 effects = []
360 for l in label.split():
365 for l in label.split():
361 s = _styles.get(l, '')
366 s = _styles.get(l, '')
362 if s:
367 if s:
363 effects.append(s)
368 effects.append(s)
364 effects = ' '.join(effects)
369 effects = ' '.join(effects)
365 if effects:
370 if effects:
366 return '\n'.join([render_effects(s, effects)
371 return '\n'.join([render_effects(s, effects)
367 for s in msg.split('\n')])
372 for s in msg.split('\n')])
368 return msg
373 return msg
369
374
370 def templatelabel(context, mapping, args):
375 def templatelabel(context, mapping, args):
371 if len(args) != 2:
376 if len(args) != 2:
372 # i18n: "label" is a keyword
377 # i18n: "label" is a keyword
373 raise error.ParseError(_("label expects two arguments"))
378 raise error.ParseError(_("label expects two arguments"))
374
379
375 thing = templater.stringify(args[1][0](context, mapping, args[1][1]))
380 thing = templater.stringify(args[1][0](context, mapping, args[1][1]))
376 thing = templater.runtemplate(context, mapping,
381 thing = templater.runtemplate(context, mapping,
377 templater.compiletemplate(thing, context))
382 templater.compiletemplate(thing, context))
378
383
379 # apparently, repo could be a string that is the favicon?
384 # apparently, repo could be a string that is the favicon?
380 repo = mapping.get('repo', '')
385 repo = mapping.get('repo', '')
381 if isinstance(repo, str):
386 if isinstance(repo, str):
382 return thing
387 return thing
383
388
384 label = templater.stringify(args[0][0](context, mapping, args[0][1]))
389 label = templater.stringify(args[0][0](context, mapping, args[0][1]))
385 label = templater.runtemplate(context, mapping,
390 label = templater.runtemplate(context, mapping,
386 templater.compiletemplate(label, context))
391 templater.compiletemplate(label, context))
387
392
388 thing = templater.stringify(thing)
393 thing = templater.stringify(thing)
389 label = templater.stringify(label)
394 label = templater.stringify(label)
390
395
391 return repo.ui.label(thing, label)
396 return repo.ui.label(thing, label)
392
397
393 def uisetup(ui):
398 def uisetup(ui):
394 if ui.plain():
399 if ui.plain():
395 return
400 return
396 if not issubclass(ui.__class__, colorui):
401 if not issubclass(ui.__class__, colorui):
397 colorui.__bases__ = (ui.__class__,)
402 colorui.__bases__ = (ui.__class__,)
398 ui.__class__ = colorui
403 ui.__class__ = colorui
399 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
404 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
400 mode = _modesetup(ui_, opts)
405 mode = _modesetup(ui_, opts)
401 colorui._colormode = mode
406 colorui._colormode = mode
402 if mode:
407 if mode:
403 extstyles()
408 extstyles()
404 configstyles(ui_)
409 configstyles(ui_)
405 return orig(ui_, opts, cmd, cmdfunc)
410 return orig(ui_, opts, cmd, cmdfunc)
406 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
411 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
407 templater.funcs['label'] = templatelabel
412 templater.funcs['label'] = templatelabel
408
413
409 def extsetup(ui):
414 def extsetup(ui):
410 commands.globalopts.append(
415 commands.globalopts.append(
411 ('', 'color', 'auto',
416 ('', 'color', 'auto',
412 # i18n: 'always', 'auto', and 'never' are keywords and should
417 # i18n: 'always', 'auto', and 'never' are keywords and should
413 # not be translated
418 # not be translated
414 _("when to colorize (boolean, always, auto, or never)"),
419 _("when to colorize (boolean, always, auto, or never)"),
415 _('TYPE')))
420 _('TYPE')))
416
421
417 if os.name != 'nt':
422 if os.name != 'nt':
418 w32effects = None
423 w32effects = None
419 else:
424 else:
420 import re, ctypes
425 import re, ctypes
421
426
422 _kernel32 = ctypes.windll.kernel32
427 _kernel32 = ctypes.windll.kernel32
423
428
424 _WORD = ctypes.c_ushort
429 _WORD = ctypes.c_ushort
425
430
426 _INVALID_HANDLE_VALUE = -1
431 _INVALID_HANDLE_VALUE = -1
427
432
428 class _COORD(ctypes.Structure):
433 class _COORD(ctypes.Structure):
429 _fields_ = [('X', ctypes.c_short),
434 _fields_ = [('X', ctypes.c_short),
430 ('Y', ctypes.c_short)]
435 ('Y', ctypes.c_short)]
431
436
432 class _SMALL_RECT(ctypes.Structure):
437 class _SMALL_RECT(ctypes.Structure):
433 _fields_ = [('Left', ctypes.c_short),
438 _fields_ = [('Left', ctypes.c_short),
434 ('Top', ctypes.c_short),
439 ('Top', ctypes.c_short),
435 ('Right', ctypes.c_short),
440 ('Right', ctypes.c_short),
436 ('Bottom', ctypes.c_short)]
441 ('Bottom', ctypes.c_short)]
437
442
438 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
443 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
439 _fields_ = [('dwSize', _COORD),
444 _fields_ = [('dwSize', _COORD),
440 ('dwCursorPosition', _COORD),
445 ('dwCursorPosition', _COORD),
441 ('wAttributes', _WORD),
446 ('wAttributes', _WORD),
442 ('srWindow', _SMALL_RECT),
447 ('srWindow', _SMALL_RECT),
443 ('dwMaximumWindowSize', _COORD)]
448 ('dwMaximumWindowSize', _COORD)]
444
449
445 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
450 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
446 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
451 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
447
452
448 _FOREGROUND_BLUE = 0x0001
453 _FOREGROUND_BLUE = 0x0001
449 _FOREGROUND_GREEN = 0x0002
454 _FOREGROUND_GREEN = 0x0002
450 _FOREGROUND_RED = 0x0004
455 _FOREGROUND_RED = 0x0004
451 _FOREGROUND_INTENSITY = 0x0008
456 _FOREGROUND_INTENSITY = 0x0008
452
457
453 _BACKGROUND_BLUE = 0x0010
458 _BACKGROUND_BLUE = 0x0010
454 _BACKGROUND_GREEN = 0x0020
459 _BACKGROUND_GREEN = 0x0020
455 _BACKGROUND_RED = 0x0040
460 _BACKGROUND_RED = 0x0040
456 _BACKGROUND_INTENSITY = 0x0080
461 _BACKGROUND_INTENSITY = 0x0080
457
462
458 _COMMON_LVB_REVERSE_VIDEO = 0x4000
463 _COMMON_LVB_REVERSE_VIDEO = 0x4000
459 _COMMON_LVB_UNDERSCORE = 0x8000
464 _COMMON_LVB_UNDERSCORE = 0x8000
460
465
461 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
466 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
462 w32effects = {
467 w32effects = {
463 'none': -1,
468 'none': -1,
464 'black': 0,
469 'black': 0,
465 'red': _FOREGROUND_RED,
470 'red': _FOREGROUND_RED,
466 'green': _FOREGROUND_GREEN,
471 'green': _FOREGROUND_GREEN,
467 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
472 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
468 'blue': _FOREGROUND_BLUE,
473 'blue': _FOREGROUND_BLUE,
469 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
474 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
470 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
475 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
471 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
476 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
472 'bold': _FOREGROUND_INTENSITY,
477 'bold': _FOREGROUND_INTENSITY,
473 'black_background': 0x100, # unused value > 0x0f
478 'black_background': 0x100, # unused value > 0x0f
474 'red_background': _BACKGROUND_RED,
479 'red_background': _BACKGROUND_RED,
475 'green_background': _BACKGROUND_GREEN,
480 'green_background': _BACKGROUND_GREEN,
476 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
481 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
477 'blue_background': _BACKGROUND_BLUE,
482 'blue_background': _BACKGROUND_BLUE,
478 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
483 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
479 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
484 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
480 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
485 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
481 _BACKGROUND_BLUE),
486 _BACKGROUND_BLUE),
482 'bold_background': _BACKGROUND_INTENSITY,
487 'bold_background': _BACKGROUND_INTENSITY,
483 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
488 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
484 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
489 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
485 }
490 }
486
491
487 passthrough = set([_FOREGROUND_INTENSITY,
492 passthrough = set([_FOREGROUND_INTENSITY,
488 _BACKGROUND_INTENSITY,
493 _BACKGROUND_INTENSITY,
489 _COMMON_LVB_UNDERSCORE,
494 _COMMON_LVB_UNDERSCORE,
490 _COMMON_LVB_REVERSE_VIDEO])
495 _COMMON_LVB_REVERSE_VIDEO])
491
496
492 stdout = _kernel32.GetStdHandle(
497 stdout = _kernel32.GetStdHandle(
493 _STD_OUTPUT_HANDLE) # don't close the handle returned
498 _STD_OUTPUT_HANDLE) # don't close the handle returned
494 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
499 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
495 w32effects = None
500 w32effects = None
496 else:
501 else:
497 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
502 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
498 if not _kernel32.GetConsoleScreenBufferInfo(
503 if not _kernel32.GetConsoleScreenBufferInfo(
499 stdout, ctypes.byref(csbi)):
504 stdout, ctypes.byref(csbi)):
500 # stdout may not support GetConsoleScreenBufferInfo()
505 # stdout may not support GetConsoleScreenBufferInfo()
501 # when called from subprocess or redirected
506 # when called from subprocess or redirected
502 w32effects = None
507 w32effects = None
503 else:
508 else:
504 origattr = csbi.wAttributes
509 origattr = csbi.wAttributes
505 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
510 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
506 re.MULTILINE | re.DOTALL)
511 re.MULTILINE | re.DOTALL)
507
512
508 def win32print(text, orig, **opts):
513 def win32print(text, orig, **opts):
509 label = opts.get('label', '')
514 label = opts.get('label', '')
510 attr = origattr
515 attr = origattr
511
516
512 def mapcolor(val, attr):
517 def mapcolor(val, attr):
513 if val == -1:
518 if val == -1:
514 return origattr
519 return origattr
515 elif val in passthrough:
520 elif val in passthrough:
516 return attr | val
521 return attr | val
517 elif val > 0x0f:
522 elif val > 0x0f:
518 return (val & 0x70) | (attr & 0x8f)
523 return (val & 0x70) | (attr & 0x8f)
519 else:
524 else:
520 return (val & 0x07) | (attr & 0xf8)
525 return (val & 0x07) | (attr & 0xf8)
521
526
522 # determine console attributes based on labels
527 # determine console attributes based on labels
523 for l in label.split():
528 for l in label.split():
524 style = _styles.get(l, '')
529 style = _styles.get(l, '')
525 for effect in style.split():
530 for effect in style.split():
526 attr = mapcolor(w32effects[effect], attr)
531 attr = mapcolor(w32effects[effect], attr)
527
532
528 # hack to ensure regexp finds data
533 # hack to ensure regexp finds data
529 if not text.startswith('\033['):
534 if not text.startswith('\033['):
530 text = '\033[m' + text
535 text = '\033[m' + text
531
536
532 # Look for ANSI-like codes embedded in text
537 # Look for ANSI-like codes embedded in text
533 m = re.match(ansire, text)
538 m = re.match(ansire, text)
534
539
535 try:
540 try:
536 while m:
541 while m:
537 for sattr in m.group(1).split(';'):
542 for sattr in m.group(1).split(';'):
538 if sattr:
543 if sattr:
539 attr = mapcolor(int(sattr), attr)
544 attr = mapcolor(int(sattr), attr)
540 _kernel32.SetConsoleTextAttribute(stdout, attr)
545 _kernel32.SetConsoleTextAttribute(stdout, attr)
541 orig(m.group(2), **opts)
546 orig(m.group(2), **opts)
542 m = re.match(ansire, m.group(3))
547 m = re.match(ansire, m.group(3))
543 finally:
548 finally:
544 # Explicitly reset original attributes
549 # Explicitly reset original attributes
545 _kernel32.SetConsoleTextAttribute(stdout, origattr)
550 _kernel32.SetConsoleTextAttribute(stdout, origattr)
@@ -1,788 +1,800 b''
1 # rebase.py - rebasing feature for mercurial
1 # rebase.py - rebasing feature for mercurial
2 #
2 #
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot 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 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''command to move sets of revisions to a different ancestor
8 '''command to move sets of revisions to a different ancestor
9
9
10 This extension lets you rebase changesets in an existing Mercurial
10 This extension lets you rebase changesets in an existing Mercurial
11 repository.
11 repository.
12
12
13 For more information:
13 For more information:
14 http://mercurial.selenic.com/wiki/RebaseExtension
14 http://mercurial.selenic.com/wiki/RebaseExtension
15 '''
15 '''
16
16
17 from mercurial import hg, util, repair, merge, cmdutil, commands, bookmarks
17 from mercurial import hg, util, repair, merge, cmdutil, commands, bookmarks
18 from mercurial import extensions, patch, scmutil, phases, obsolete, error
18 from mercurial import extensions, patch, scmutil, phases, obsolete, error
19 from mercurial.commands import templateopts
19 from mercurial.commands import templateopts
20 from mercurial.node import nullrev
20 from mercurial.node import nullrev
21 from mercurial.lock import release
21 from mercurial.lock import release
22 from mercurial.i18n import _
22 from mercurial.i18n import _
23 import os, errno
23 import os, errno
24
24
25 nullmerge = -2
25 nullmerge = -2
26 revignored = -3
26 revignored = -3
27
27
28 cmdtable = {}
28 cmdtable = {}
29 command = cmdutil.command(cmdtable)
29 command = cmdutil.command(cmdtable)
30 testedwith = 'internal'
30 testedwith = 'internal'
31
31
32 @command('rebase',
32 @command('rebase',
33 [('s', 'source', '',
33 [('s', 'source', '',
34 _('rebase from the specified changeset'), _('REV')),
34 _('rebase from the specified changeset'), _('REV')),
35 ('b', 'base', '',
35 ('b', 'base', '',
36 _('rebase from the base of the specified changeset '
36 _('rebase from the base of the specified changeset '
37 '(up to greatest common ancestor of base and dest)'),
37 '(up to greatest common ancestor of base and dest)'),
38 _('REV')),
38 _('REV')),
39 ('r', 'rev', [],
39 ('r', 'rev', [],
40 _('rebase these revisions'),
40 _('rebase these revisions'),
41 _('REV')),
41 _('REV')),
42 ('d', 'dest', '',
42 ('d', 'dest', '',
43 _('rebase onto the specified changeset'), _('REV')),
43 _('rebase onto the specified changeset'), _('REV')),
44 ('', 'collapse', False, _('collapse the rebased changesets')),
44 ('', 'collapse', False, _('collapse the rebased changesets')),
45 ('m', 'message', '',
45 ('m', 'message', '',
46 _('use text as collapse commit message'), _('TEXT')),
46 _('use text as collapse commit message'), _('TEXT')),
47 ('e', 'edit', False, _('invoke editor on commit messages')),
47 ('e', 'edit', False, _('invoke editor on commit messages')),
48 ('l', 'logfile', '',
48 ('l', 'logfile', '',
49 _('read collapse commit message from file'), _('FILE')),
49 _('read collapse commit message from file'), _('FILE')),
50 ('', 'keep', False, _('keep original changesets')),
50 ('', 'keep', False, _('keep original changesets')),
51 ('', 'keepbranches', False, _('keep original branch names')),
51 ('', 'keepbranches', False, _('keep original branch names')),
52 ('D', 'detach', False, _('(DEPRECATED)')),
52 ('D', 'detach', False, _('(DEPRECATED)')),
53 ('t', 'tool', '', _('specify merge tool')),
53 ('t', 'tool', '', _('specify merge tool')),
54 ('c', 'continue', False, _('continue an interrupted rebase')),
54 ('c', 'continue', False, _('continue an interrupted rebase')),
55 ('a', 'abort', False, _('abort an interrupted rebase'))] +
55 ('a', 'abort', False, _('abort an interrupted rebase'))] +
56 templateopts,
56 templateopts,
57 _('[-s REV | -b REV] [-d REV] [OPTION]'))
57 _('[-s REV | -b REV] [-d REV] [OPTION]'))
58 def rebase(ui, repo, **opts):
58 def rebase(ui, repo, **opts):
59 """move changeset (and descendants) to a different branch
59 """move changeset (and descendants) to a different branch
60
60
61 Rebase uses repeated merging to graft changesets from one part of
61 Rebase uses repeated merging to graft changesets from one part of
62 history (the source) onto another (the destination). This can be
62 history (the source) onto another (the destination). This can be
63 useful for linearizing *local* changes relative to a master
63 useful for linearizing *local* changes relative to a master
64 development tree.
64 development tree.
65
65
66 You should not rebase changesets that have already been shared
66 You should not rebase changesets that have already been shared
67 with others. Doing so will force everybody else to perform the
67 with others. Doing so will force everybody else to perform the
68 same rebase or they will end up with duplicated changesets after
68 same rebase or they will end up with duplicated changesets after
69 pulling in your rebased changesets.
69 pulling in your rebased changesets.
70
70
71 In its default configuration, Mercurial will prevent you from
71 In its default configuration, Mercurial will prevent you from
72 rebasing published changes. See :hg:`help phases` for details.
72 rebasing published changes. See :hg:`help phases` for details.
73
73
74 If you don't specify a destination changeset (``-d/--dest``),
74 If you don't specify a destination changeset (``-d/--dest``),
75 rebase uses the tipmost head of the current named branch as the
75 rebase uses the tipmost head of the current named branch as the
76 destination. (The destination changeset is not modified by
76 destination. (The destination changeset is not modified by
77 rebasing, but new changesets are added as its descendants.)
77 rebasing, but new changesets are added as its descendants.)
78
78
79 You can specify which changesets to rebase in two ways: as a
79 You can specify which changesets to rebase in two ways: as a
80 "source" changeset or as a "base" changeset. Both are shorthand
80 "source" changeset or as a "base" changeset. Both are shorthand
81 for a topologically related set of changesets (the "source
81 for a topologically related set of changesets (the "source
82 branch"). If you specify source (``-s/--source``), rebase will
82 branch"). If you specify source (``-s/--source``), rebase will
83 rebase that changeset and all of its descendants onto dest. If you
83 rebase that changeset and all of its descendants onto dest. If you
84 specify base (``-b/--base``), rebase will select ancestors of base
84 specify base (``-b/--base``), rebase will select ancestors of base
85 back to but not including the common ancestor with dest. Thus,
85 back to but not including the common ancestor with dest. Thus,
86 ``-b`` is less precise but more convenient than ``-s``: you can
86 ``-b`` is less precise but more convenient than ``-s``: you can
87 specify any changeset in the source branch, and rebase will select
87 specify any changeset in the source branch, and rebase will select
88 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
88 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
89 uses the parent of the working directory as the base.
89 uses the parent of the working directory as the base.
90
90
91 For advanced usage, a third way is available through the ``--rev``
91 For advanced usage, a third way is available through the ``--rev``
92 option. It allows you to specify an arbitrary set of changesets to
92 option. It allows you to specify an arbitrary set of changesets to
93 rebase. Descendants of revs you specify with this option are not
93 rebase. Descendants of revs you specify with this option are not
94 automatically included in the rebase.
94 automatically included in the rebase.
95
95
96 By default, rebase recreates the changesets in the source branch
96 By default, rebase recreates the changesets in the source branch
97 as descendants of dest and then destroys the originals. Use
97 as descendants of dest and then destroys the originals. Use
98 ``--keep`` to preserve the original source changesets. Some
98 ``--keep`` to preserve the original source changesets. Some
99 changesets in the source branch (e.g. merges from the destination
99 changesets in the source branch (e.g. merges from the destination
100 branch) may be dropped if they no longer contribute any change.
100 branch) may be dropped if they no longer contribute any change.
101
101
102 One result of the rules for selecting the destination changeset
102 One result of the rules for selecting the destination changeset
103 and source branch is that, unlike ``merge``, rebase will do
103 and source branch is that, unlike ``merge``, rebase will do
104 nothing if you are at the latest (tipmost) head of a named branch
104 nothing if you are at the latest (tipmost) head of a named branch
105 with two heads. You need to explicitly specify source and/or
105 with two heads. You need to explicitly specify source and/or
106 destination (or ``update`` to the other head, if it's the head of
106 destination (or ``update`` to the other head, if it's the head of
107 the intended source branch).
107 the intended source branch).
108
108
109 If a rebase is interrupted to manually resolve a merge, it can be
109 If a rebase is interrupted to manually resolve a merge, it can be
110 continued with --continue/-c or aborted with --abort/-a.
110 continued with --continue/-c or aborted with --abort/-a.
111
111
112 Returns 0 on success, 1 if nothing to rebase.
112 Returns 0 on success, 1 if nothing to rebase.
113 """
113 """
114 originalwd = target = None
114 originalwd = target = None
115 activebookmark = None
115 activebookmark = None
116 external = nullrev
116 external = nullrev
117 state = {}
117 state = {}
118 skipped = set()
118 skipped = set()
119 targetancestors = set()
119 targetancestors = set()
120
120
121 editor = None
121 editor = None
122 if opts.get('edit'):
122 if opts.get('edit'):
123 editor = cmdutil.commitforceeditor
123 editor = cmdutil.commitforceeditor
124
124
125 lock = wlock = None
125 lock = wlock = None
126 try:
126 try:
127 wlock = repo.wlock()
127 wlock = repo.wlock()
128 lock = repo.lock()
128 lock = repo.lock()
129
129
130 # Validate input and define rebasing points
130 # Validate input and define rebasing points
131 destf = opts.get('dest', None)
131 destf = opts.get('dest', None)
132 srcf = opts.get('source', None)
132 srcf = opts.get('source', None)
133 basef = opts.get('base', None)
133 basef = opts.get('base', None)
134 revf = opts.get('rev', [])
134 revf = opts.get('rev', [])
135 contf = opts.get('continue')
135 contf = opts.get('continue')
136 abortf = opts.get('abort')
136 abortf = opts.get('abort')
137 collapsef = opts.get('collapse', False)
137 collapsef = opts.get('collapse', False)
138 collapsemsg = cmdutil.logmessage(ui, opts)
138 collapsemsg = cmdutil.logmessage(ui, opts)
139 extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
139 extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
140 keepf = opts.get('keep', False)
140 keepf = opts.get('keep', False)
141 keepbranchesf = opts.get('keepbranches', False)
141 keepbranchesf = opts.get('keepbranches', False)
142 # keepopen is not meant for use on the command line, but by
142 # keepopen is not meant for use on the command line, but by
143 # other extensions
143 # other extensions
144 keepopen = opts.get('keepopen', False)
144 keepopen = opts.get('keepopen', False)
145
145
146 if collapsemsg and not collapsef:
146 if collapsemsg and not collapsef:
147 raise util.Abort(
147 raise util.Abort(
148 _('message can only be specified with collapse'))
148 _('message can only be specified with collapse'))
149
149
150 if contf or abortf:
150 if contf or abortf:
151 if contf and abortf:
151 if contf and abortf:
152 raise util.Abort(_('cannot use both abort and continue'))
152 raise util.Abort(_('cannot use both abort and continue'))
153 if collapsef:
153 if collapsef:
154 raise util.Abort(
154 raise util.Abort(
155 _('cannot use collapse with continue or abort'))
155 _('cannot use collapse with continue or abort'))
156 if srcf or basef or destf:
156 if srcf or basef or destf:
157 raise util.Abort(
157 raise util.Abort(
158 _('abort and continue do not allow specifying revisions'))
158 _('abort and continue do not allow specifying revisions'))
159 if opts.get('tool', False):
159 if opts.get('tool', False):
160 ui.warn(_('tool option will be ignored\n'))
160 ui.warn(_('tool option will be ignored\n'))
161
161
162 (originalwd, target, state, skipped, collapsef, keepf,
162 (originalwd, target, state, skipped, collapsef, keepf,
163 keepbranchesf, external, activebookmark) = restorestatus(repo)
163 keepbranchesf, external, activebookmark) = restorestatus(repo)
164 if abortf:
164 if abortf:
165 return abort(repo, originalwd, target, state)
165 return abort(repo, originalwd, target, state)
166 else:
166 else:
167 if srcf and basef:
167 if srcf and basef:
168 raise util.Abort(_('cannot specify both a '
168 raise util.Abort(_('cannot specify both a '
169 'source and a base'))
169 'source and a base'))
170 if revf and basef:
170 if revf and basef:
171 raise util.Abort(_('cannot specify both a '
171 raise util.Abort(_('cannot specify both a '
172 'revision and a base'))
172 'revision and a base'))
173 if revf and srcf:
173 if revf and srcf:
174 raise util.Abort(_('cannot specify both a '
174 raise util.Abort(_('cannot specify both a '
175 'revision and a source'))
175 'revision and a source'))
176
176
177 cmdutil.bailifchanged(repo)
177 cmdutil.bailifchanged(repo)
178
178
179 if not destf:
179 if not destf:
180 # Destination defaults to the latest revision in the
180 # Destination defaults to the latest revision in the
181 # current branch
181 # current branch
182 branch = repo[None].branch()
182 branch = repo[None].branch()
183 dest = repo[branch]
183 dest = repo[branch]
184 else:
184 else:
185 dest = scmutil.revsingle(repo, destf)
185 dest = scmutil.revsingle(repo, destf)
186
186
187 if revf:
187 if revf:
188 rebaseset = repo.revs('%lr', revf)
188 rebaseset = repo.revs('%lr', revf)
189 elif srcf:
189 elif srcf:
190 src = scmutil.revrange(repo, [srcf])
190 src = scmutil.revrange(repo, [srcf])
191 rebaseset = repo.revs('(%ld)::', src)
191 rebaseset = repo.revs('(%ld)::', src)
192 else:
192 else:
193 base = scmutil.revrange(repo, [basef or '.'])
193 base = scmutil.revrange(repo, [basef or '.'])
194 rebaseset = repo.revs(
194 rebaseset = repo.revs(
195 '(children(ancestor(%ld, %d)) and ::(%ld))::',
195 '(children(ancestor(%ld, %d)) and ::(%ld))::',
196 base, dest, base)
196 base, dest, base)
197 if rebaseset:
197 if rebaseset:
198 root = min(rebaseset)
198 root = min(rebaseset)
199 else:
199 else:
200 root = None
200 root = None
201
201
202 if not rebaseset:
202 if not rebaseset:
203 repo.ui.debug('base is ancestor of destination\n')
203 repo.ui.debug('base is ancestor of destination\n')
204 result = None
204 result = None
205 elif (not (keepf or obsolete._enabled)
205 elif (not (keepf or obsolete._enabled)
206 and repo.revs('first(children(%ld) - %ld)',
206 and repo.revs('first(children(%ld) - %ld)',
207 rebaseset, rebaseset)):
207 rebaseset, rebaseset)):
208 raise util.Abort(
208 raise util.Abort(
209 _("can't remove original changesets with"
209 _("can't remove original changesets with"
210 " unrebased descendants"),
210 " unrebased descendants"),
211 hint=_('use --keep to keep original changesets'))
211 hint=_('use --keep to keep original changesets'))
212 else:
212 else:
213 result = buildstate(repo, dest, rebaseset, collapsef)
213 result = buildstate(repo, dest, rebaseset, collapsef)
214
214
215 if not result:
215 if not result:
216 # Empty state built, nothing to rebase
216 # Empty state built, nothing to rebase
217 ui.status(_('nothing to rebase\n'))
217 ui.status(_('nothing to rebase\n'))
218 return 1
218 return 1
219 elif not keepf and not repo[root].mutable():
219 elif not keepf and not repo[root].mutable():
220 raise util.Abort(_("can't rebase immutable changeset %s")
220 raise util.Abort(_("can't rebase immutable changeset %s")
221 % repo[root],
221 % repo[root],
222 hint=_('see hg help phases for details'))
222 hint=_('see hg help phases for details'))
223 else:
223 else:
224 originalwd, target, state = result
224 originalwd, target, state = result
225 if collapsef:
225 if collapsef:
226 targetancestors = repo.changelog.ancestors([target],
226 targetancestors = repo.changelog.ancestors([target],
227 inclusive=True)
227 inclusive=True)
228 external = checkexternal(repo, state, targetancestors)
228 external = checkexternal(repo, state, targetancestors)
229
229
230 if keepbranchesf:
230 if keepbranchesf:
231 assert not extrafn, 'cannot use both keepbranches and extrafn'
231 assert not extrafn, 'cannot use both keepbranches and extrafn'
232 def extrafn(ctx, extra):
232 def extrafn(ctx, extra):
233 extra['branch'] = ctx.branch()
233 extra['branch'] = ctx.branch()
234 if collapsef:
234 if collapsef:
235 branches = set()
235 branches = set()
236 for rev in state:
236 for rev in state:
237 branches.add(repo[rev].branch())
237 branches.add(repo[rev].branch())
238 if len(branches) > 1:
238 if len(branches) > 1:
239 raise util.Abort(_('cannot collapse multiple named '
239 raise util.Abort(_('cannot collapse multiple named '
240 'branches'))
240 'branches'))
241
241
242
242
243 # Rebase
243 # Rebase
244 if not targetancestors:
244 if not targetancestors:
245 targetancestors = repo.changelog.ancestors([target], inclusive=True)
245 targetancestors = repo.changelog.ancestors([target], inclusive=True)
246
246
247 # Keep track of the current bookmarks in order to reset them later
247 # Keep track of the current bookmarks in order to reset them later
248 currentbookmarks = repo._bookmarks.copy()
248 currentbookmarks = repo._bookmarks.copy()
249 activebookmark = activebookmark or repo._bookmarkcurrent
249 activebookmark = activebookmark or repo._bookmarkcurrent
250 if activebookmark:
250 if activebookmark:
251 bookmarks.unsetcurrent(repo)
251 bookmarks.unsetcurrent(repo)
252
252
253 sortedstate = sorted(state)
253 sortedstate = sorted(state)
254 total = len(sortedstate)
254 total = len(sortedstate)
255 pos = 0
255 pos = 0
256 for rev in sortedstate:
256 for rev in sortedstate:
257 pos += 1
257 pos += 1
258 if state[rev] == -1:
258 if state[rev] == -1:
259 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
259 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
260 _('changesets'), total)
260 _('changesets'), total)
261 storestatus(repo, originalwd, target, state, collapsef, keepf,
261 storestatus(repo, originalwd, target, state, collapsef, keepf,
262 keepbranchesf, external, activebookmark)
262 keepbranchesf, external, activebookmark)
263 p1, p2 = defineparents(repo, rev, target, state,
263 p1, p2 = defineparents(repo, rev, target, state,
264 targetancestors)
264 targetancestors)
265 if len(repo.parents()) == 2:
265 if len(repo.parents()) == 2:
266 repo.ui.debug('resuming interrupted rebase\n')
266 repo.ui.debug('resuming interrupted rebase\n')
267 else:
267 else:
268 try:
268 try:
269 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
269 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
270 stats = rebasenode(repo, rev, p1, state, collapsef)
270 stats = rebasenode(repo, rev, p1, state, collapsef)
271 if stats and stats[3] > 0:
271 if stats and stats[3] > 0:
272 raise error.InterventionRequired(
272 raise error.InterventionRequired(
273 _('unresolved conflicts (see hg '
273 _('unresolved conflicts (see hg '
274 'resolve, then hg rebase --continue)'))
274 'resolve, then hg rebase --continue)'))
275 finally:
275 finally:
276 ui.setconfig('ui', 'forcemerge', '')
276 ui.setconfig('ui', 'forcemerge', '')
277 cmdutil.duplicatecopies(repo, rev, target)
277 cmdutil.duplicatecopies(repo, rev, target)
278 if not collapsef:
278 if not collapsef:
279 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
279 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
280 editor=editor)
280 editor=editor)
281 else:
281 else:
282 # Skip commit if we are collapsing
282 # Skip commit if we are collapsing
283 repo.setparents(repo[p1].node())
283 repo.setparents(repo[p1].node())
284 newrev = None
284 newrev = None
285 # Update the state
285 # Update the state
286 if newrev is not None:
286 if newrev is not None:
287 state[rev] = repo[newrev].rev()
287 state[rev] = repo[newrev].rev()
288 else:
288 else:
289 if not collapsef:
289 if not collapsef:
290 ui.note(_('no changes, revision %d skipped\n') % rev)
290 ui.note(_('no changes, revision %d skipped\n') % rev)
291 ui.debug('next revision set to %s\n' % p1)
291 ui.debug('next revision set to %s\n' % p1)
292 skipped.add(rev)
292 skipped.add(rev)
293 state[rev] = p1
293 state[rev] = p1
294
294
295 ui.progress(_('rebasing'), None)
295 ui.progress(_('rebasing'), None)
296 ui.note(_('rebase merging completed\n'))
296 ui.note(_('rebase merging completed\n'))
297
297
298 if collapsef and not keepopen:
298 if collapsef and not keepopen:
299 p1, p2 = defineparents(repo, min(state), target,
299 p1, p2 = defineparents(repo, min(state), target,
300 state, targetancestors)
300 state, targetancestors)
301 if collapsemsg:
301 if collapsemsg:
302 commitmsg = collapsemsg
302 commitmsg = collapsemsg
303 else:
303 else:
304 commitmsg = 'Collapsed revision'
304 commitmsg = 'Collapsed revision'
305 for rebased in state:
305 for rebased in state:
306 if rebased not in skipped and state[rebased] > nullmerge:
306 if rebased not in skipped and state[rebased] > nullmerge:
307 commitmsg += '\n* %s' % repo[rebased].description()
307 commitmsg += '\n* %s' % repo[rebased].description()
308 commitmsg = ui.edit(commitmsg, repo.ui.username())
308 commitmsg = ui.edit(commitmsg, repo.ui.username())
309 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
309 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
310 extrafn=extrafn, editor=editor)
310 extrafn=extrafn, editor=editor)
311
311
312 if 'qtip' in repo.tags():
312 if 'qtip' in repo.tags():
313 updatemq(repo, state, skipped, **opts)
313 updatemq(repo, state, skipped, **opts)
314
314
315 if currentbookmarks:
315 if currentbookmarks:
316 # Nodeids are needed to reset bookmarks
316 # Nodeids are needed to reset bookmarks
317 nstate = {}
317 nstate = {}
318 for k, v in state.iteritems():
318 for k, v in state.iteritems():
319 if v > nullmerge:
319 if v > nullmerge:
320 nstate[repo[k].node()] = repo[v].node()
320 nstate[repo[k].node()] = repo[v].node()
321 # XXX this is the same as dest.node() for the non-continue path --
321 # XXX this is the same as dest.node() for the non-continue path --
322 # this should probably be cleaned up
322 # this should probably be cleaned up
323 targetnode = repo[target].node()
323 targetnode = repo[target].node()
324
324
325 if not keepf:
325 if not keepf:
326 collapsedas = None
326 collapsedas = None
327 if collapsef:
327 if collapsef:
328 collapsedas = newrev
328 collapsedas = newrev
329 clearrebased(ui, repo, state, skipped, collapsedas)
329 clearrebased(ui, repo, state, skipped, collapsedas)
330
330
331 if currentbookmarks:
331 if currentbookmarks:
332 updatebookmarks(repo, targetnode, nstate, currentbookmarks)
332 updatebookmarks(repo, targetnode, nstate, currentbookmarks)
333
333
334 clearstatus(repo)
334 clearstatus(repo)
335 ui.note(_("rebase completed\n"))
335 ui.note(_("rebase completed\n"))
336 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
336 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
337 if skipped:
337 if skipped:
338 ui.note(_("%d revisions have been skipped\n") % len(skipped))
338 ui.note(_("%d revisions have been skipped\n") % len(skipped))
339
339
340 if (activebookmark and
340 if (activebookmark and
341 repo['tip'].node() == repo._bookmarks[activebookmark]):
341 repo['tip'].node() == repo._bookmarks[activebookmark]):
342 bookmarks.setcurrent(repo, activebookmark)
342 bookmarks.setcurrent(repo, activebookmark)
343
343
344 finally:
344 finally:
345 release(lock, wlock)
345 release(lock, wlock)
346
346
347 def checkexternal(repo, state, targetancestors):
347 def checkexternal(repo, state, targetancestors):
348 """Check whether one or more external revisions need to be taken in
348 """Check whether one or more external revisions need to be taken in
349 consideration. In the latter case, abort.
349 consideration. In the latter case, abort.
350 """
350 """
351 external = nullrev
351 external = nullrev
352 source = min(state)
352 source = min(state)
353 for rev in state:
353 for rev in state:
354 if rev == source:
354 if rev == source:
355 continue
355 continue
356 # Check externals and fail if there are more than one
356 # Check externals and fail if there are more than one
357 for p in repo[rev].parents():
357 for p in repo[rev].parents():
358 if (p.rev() not in state
358 if (p.rev() not in state
359 and p.rev() not in targetancestors):
359 and p.rev() not in targetancestors):
360 if external != nullrev:
360 if external != nullrev:
361 raise util.Abort(_('unable to collapse, there is more '
361 raise util.Abort(_('unable to collapse, there is more '
362 'than one external parent'))
362 'than one external parent'))
363 external = p.rev()
363 external = p.rev()
364 return external
364 return external
365
365
366 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
366 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
367 'Commit the changes and store useful information in extra'
367 'Commit the changes and store useful information in extra'
368 try:
368 try:
369 repo.setparents(repo[p1].node(), repo[p2].node())
369 repo.setparents(repo[p1].node(), repo[p2].node())
370 ctx = repo[rev]
370 ctx = repo[rev]
371 if commitmsg is None:
371 if commitmsg is None:
372 commitmsg = ctx.description()
372 commitmsg = ctx.description()
373 extra = {'rebase_source': ctx.hex()}
373 extra = {'rebase_source': ctx.hex()}
374 if extrafn:
374 if extrafn:
375 extrafn(ctx, extra)
375 extrafn(ctx, extra)
376 # Commit might fail if unresolved files exist
376 # Commit might fail if unresolved files exist
377 newrev = repo.commit(text=commitmsg, user=ctx.user(),
377 newrev = repo.commit(text=commitmsg, user=ctx.user(),
378 date=ctx.date(), extra=extra, editor=editor)
378 date=ctx.date(), extra=extra, editor=editor)
379 repo.dirstate.setbranch(repo[newrev].branch())
379 repo.dirstate.setbranch(repo[newrev].branch())
380 targetphase = max(ctx.phase(), phases.draft)
380 targetphase = max(ctx.phase(), phases.draft)
381 # retractboundary doesn't overwrite upper phase inherited from parent
381 # retractboundary doesn't overwrite upper phase inherited from parent
382 newnode = repo[newrev].node()
382 newnode = repo[newrev].node()
383 if newnode:
383 if newnode:
384 phases.retractboundary(repo, targetphase, [newnode])
384 phases.retractboundary(repo, targetphase, [newnode])
385 return newrev
385 return newrev
386 except util.Abort:
386 except util.Abort:
387 # Invalidate the previous setparents
387 # Invalidate the previous setparents
388 repo.dirstate.invalidate()
388 repo.dirstate.invalidate()
389 raise
389 raise
390
390
391 def rebasenode(repo, rev, p1, state, collapse):
391 def rebasenode(repo, rev, p1, state, collapse):
392 'Rebase a single revision'
392 'Rebase a single revision'
393 # Merge phase
393 # Merge phase
394 # Update to target and merge it with local
394 # Update to target and merge it with local
395 if repo['.'].rev() != repo[p1].rev():
395 if repo['.'].rev() != repo[p1].rev():
396 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
396 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
397 merge.update(repo, p1, False, True, False)
397 merge.update(repo, p1, False, True, False)
398 else:
398 else:
399 repo.ui.debug(" already in target\n")
399 repo.ui.debug(" already in target\n")
400 repo.dirstate.write()
400 repo.dirstate.write()
401 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
401 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
402 base = None
402 base = None
403 if repo[rev].rev() != repo[min(state)].rev():
403 if repo[rev].rev() != repo[min(state)].rev():
404 base = repo[rev].p1().node()
404 base = repo[rev].p1().node()
405 # When collapsing in-place, the parent is the common ancestor, we
405 # When collapsing in-place, the parent is the common ancestor, we
406 # have to allow merging with it.
406 # have to allow merging with it.
407 return merge.update(repo, rev, True, True, False, base, collapse)
407 return merge.update(repo, rev, True, True, False, base, collapse)
408
408
409 def nearestrebased(repo, rev, state):
409 def nearestrebased(repo, rev, state):
410 """return the nearest ancestors of rev in the rebase result"""
410 """return the nearest ancestors of rev in the rebase result"""
411 rebased = [r for r in state if state[r] > nullmerge]
411 rebased = [r for r in state if state[r] > nullmerge]
412 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
412 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
413 if candidates:
413 if candidates:
414 return state[candidates[0]]
414 return state[candidates[0]]
415 else:
415 else:
416 return None
416 return None
417
417
418 def defineparents(repo, rev, target, state, targetancestors):
418 def defineparents(repo, rev, target, state, targetancestors):
419 'Return the new parent relationship of the revision that will be rebased'
419 'Return the new parent relationship of the revision that will be rebased'
420 parents = repo[rev].parents()
420 parents = repo[rev].parents()
421 p1 = p2 = nullrev
421 p1 = p2 = nullrev
422
422
423 P1n = parents[0].rev()
423 P1n = parents[0].rev()
424 if P1n in targetancestors:
424 if P1n in targetancestors:
425 p1 = target
425 p1 = target
426 elif P1n in state:
426 elif P1n in state:
427 if state[P1n] == nullmerge:
427 if state[P1n] == nullmerge:
428 p1 = target
428 p1 = target
429 elif state[P1n] == revignored:
429 elif state[P1n] == revignored:
430 p1 = nearestrebased(repo, P1n, state)
430 p1 = nearestrebased(repo, P1n, state)
431 if p1 is None:
431 if p1 is None:
432 p1 = target
432 p1 = target
433 else:
433 else:
434 p1 = state[P1n]
434 p1 = state[P1n]
435 else: # P1n external
435 else: # P1n external
436 p1 = target
436 p1 = target
437 p2 = P1n
437 p2 = P1n
438
438
439 if len(parents) == 2 and parents[1].rev() not in targetancestors:
439 if len(parents) == 2 and parents[1].rev() not in targetancestors:
440 P2n = parents[1].rev()
440 P2n = parents[1].rev()
441 # interesting second parent
441 # interesting second parent
442 if P2n in state:
442 if P2n in state:
443 if p1 == target: # P1n in targetancestors or external
443 if p1 == target: # P1n in targetancestors or external
444 p1 = state[P2n]
444 p1 = state[P2n]
445 elif state[P2n] == revignored:
445 elif state[P2n] == revignored:
446 p2 = nearestrebased(repo, P2n, state)
446 p2 = nearestrebased(repo, P2n, state)
447 if p2 is None:
447 if p2 is None:
448 # no ancestors rebased yet, detach
448 # no ancestors rebased yet, detach
449 p2 = target
449 p2 = target
450 else:
450 else:
451 p2 = state[P2n]
451 p2 = state[P2n]
452 else: # P2n external
452 else: # P2n external
453 if p2 != nullrev: # P1n external too => rev is a merged revision
453 if p2 != nullrev: # P1n external too => rev is a merged revision
454 raise util.Abort(_('cannot use revision %d as base, result '
454 raise util.Abort(_('cannot use revision %d as base, result '
455 'would have 3 parents') % rev)
455 'would have 3 parents') % rev)
456 p2 = P2n
456 p2 = P2n
457 repo.ui.debug(" future parents are %d and %d\n" %
457 repo.ui.debug(" future parents are %d and %d\n" %
458 (repo[p1].rev(), repo[p2].rev()))
458 (repo[p1].rev(), repo[p2].rev()))
459 return p1, p2
459 return p1, p2
460
460
461 def isagitpatch(repo, patchname):
461 def isagitpatch(repo, patchname):
462 'Return true if the given patch is in git format'
462 'Return true if the given patch is in git format'
463 mqpatch = os.path.join(repo.mq.path, patchname)
463 mqpatch = os.path.join(repo.mq.path, patchname)
464 for line in patch.linereader(file(mqpatch, 'rb')):
464 for line in patch.linereader(file(mqpatch, 'rb')):
465 if line.startswith('diff --git'):
465 if line.startswith('diff --git'):
466 return True
466 return True
467 return False
467 return False
468
468
469 def updatemq(repo, state, skipped, **opts):
469 def updatemq(repo, state, skipped, **opts):
470 'Update rebased mq patches - finalize and then import them'
470 'Update rebased mq patches - finalize and then import them'
471 mqrebase = {}
471 mqrebase = {}
472 mq = repo.mq
472 mq = repo.mq
473 original_series = mq.fullseries[:]
473 original_series = mq.fullseries[:]
474 skippedpatches = set()
474 skippedpatches = set()
475
475
476 for p in mq.applied:
476 for p in mq.applied:
477 rev = repo[p.node].rev()
477 rev = repo[p.node].rev()
478 if rev in state:
478 if rev in state:
479 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
479 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
480 (rev, p.name))
480 (rev, p.name))
481 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
481 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
482 else:
482 else:
483 # Applied but not rebased, not sure this should happen
483 # Applied but not rebased, not sure this should happen
484 skippedpatches.add(p.name)
484 skippedpatches.add(p.name)
485
485
486 if mqrebase:
486 if mqrebase:
487 mq.finish(repo, mqrebase.keys())
487 mq.finish(repo, mqrebase.keys())
488
488
489 # We must start import from the newest revision
489 # We must start import from the newest revision
490 for rev in sorted(mqrebase, reverse=True):
490 for rev in sorted(mqrebase, reverse=True):
491 if rev not in skipped:
491 if rev not in skipped:
492 name, isgit = mqrebase[rev]
492 name, isgit = mqrebase[rev]
493 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
493 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
494 mq.qimport(repo, (), patchname=name, git=isgit,
494 mq.qimport(repo, (), patchname=name, git=isgit,
495 rev=[str(state[rev])])
495 rev=[str(state[rev])])
496 else:
496 else:
497 # Rebased and skipped
497 # Rebased and skipped
498 skippedpatches.add(mqrebase[rev][0])
498 skippedpatches.add(mqrebase[rev][0])
499
499
500 # Patches were either applied and rebased and imported in
500 # Patches were either applied and rebased and imported in
501 # order, applied and removed or unapplied. Discard the removed
501 # order, applied and removed or unapplied. Discard the removed
502 # ones while preserving the original series order and guards.
502 # ones while preserving the original series order and guards.
503 newseries = [s for s in original_series
503 newseries = [s for s in original_series
504 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
504 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
505 mq.fullseries[:] = newseries
505 mq.fullseries[:] = newseries
506 mq.seriesdirty = True
506 mq.seriesdirty = True
507 mq.savedirty()
507 mq.savedirty()
508
508
509 def updatebookmarks(repo, targetnode, nstate, originalbookmarks):
509 def updatebookmarks(repo, targetnode, nstate, originalbookmarks):
510 'Move bookmarks to their correct changesets, and delete divergent ones'
510 'Move bookmarks to their correct changesets, and delete divergent ones'
511 marks = repo._bookmarks
511 marks = repo._bookmarks
512 for k, v in originalbookmarks.iteritems():
512 for k, v in originalbookmarks.iteritems():
513 if v in nstate:
513 if v in nstate:
514 # update the bookmarks for revs that have moved
514 # update the bookmarks for revs that have moved
515 marks[k] = nstate[v]
515 marks[k] = nstate[v]
516 bookmarks.deletedivergent(repo, [targetnode], k)
516 bookmarks.deletedivergent(repo, [targetnode], k)
517
517
518 marks.write()
518 marks.write()
519
519
520 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
520 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
521 external, activebookmark):
521 external, activebookmark):
522 'Store the current status to allow recovery'
522 'Store the current status to allow recovery'
523 f = repo.opener("rebasestate", "w")
523 f = repo.opener("rebasestate", "w")
524 f.write(repo[originalwd].hex() + '\n')
524 f.write(repo[originalwd].hex() + '\n')
525 f.write(repo[target].hex() + '\n')
525 f.write(repo[target].hex() + '\n')
526 f.write(repo[external].hex() + '\n')
526 f.write(repo[external].hex() + '\n')
527 f.write('%d\n' % int(collapse))
527 f.write('%d\n' % int(collapse))
528 f.write('%d\n' % int(keep))
528 f.write('%d\n' % int(keep))
529 f.write('%d\n' % int(keepbranches))
529 f.write('%d\n' % int(keepbranches))
530 f.write('%s\n' % (activebookmark or ''))
530 f.write('%s\n' % (activebookmark or ''))
531 for d, v in state.iteritems():
531 for d, v in state.iteritems():
532 oldrev = repo[d].hex()
532 oldrev = repo[d].hex()
533 if v > nullmerge:
533 if v > nullmerge:
534 newrev = repo[v].hex()
534 newrev = repo[v].hex()
535 else:
535 else:
536 newrev = v
536 newrev = v
537 f.write("%s:%s\n" % (oldrev, newrev))
537 f.write("%s:%s\n" % (oldrev, newrev))
538 f.close()
538 f.close()
539 repo.ui.debug('rebase status stored\n')
539 repo.ui.debug('rebase status stored\n')
540
540
541 def clearstatus(repo):
541 def clearstatus(repo):
542 'Remove the status files'
542 'Remove the status files'
543 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
543 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
544
544
545 def restorestatus(repo):
545 def restorestatus(repo):
546 'Restore a previously stored status'
546 'Restore a previously stored status'
547 try:
547 try:
548 target = None
548 target = None
549 collapse = False
549 collapse = False
550 external = nullrev
550 external = nullrev
551 activebookmark = None
551 activebookmark = None
552 state = {}
552 state = {}
553 f = repo.opener("rebasestate")
553 f = repo.opener("rebasestate")
554 for i, l in enumerate(f.read().splitlines()):
554 for i, l in enumerate(f.read().splitlines()):
555 if i == 0:
555 if i == 0:
556 originalwd = repo[l].rev()
556 originalwd = repo[l].rev()
557 elif i == 1:
557 elif i == 1:
558 target = repo[l].rev()
558 target = repo[l].rev()
559 elif i == 2:
559 elif i == 2:
560 external = repo[l].rev()
560 external = repo[l].rev()
561 elif i == 3:
561 elif i == 3:
562 collapse = bool(int(l))
562 collapse = bool(int(l))
563 elif i == 4:
563 elif i == 4:
564 keep = bool(int(l))
564 keep = bool(int(l))
565 elif i == 5:
565 elif i == 5:
566 keepbranches = bool(int(l))
566 keepbranches = bool(int(l))
567 elif i == 6 and not (len(l) == 81 and ':' in l):
567 elif i == 6 and not (len(l) == 81 and ':' in l):
568 # line 6 is a recent addition, so for backwards compatibility
568 # line 6 is a recent addition, so for backwards compatibility
569 # check that the line doesn't look like the oldrev:newrev lines
569 # check that the line doesn't look like the oldrev:newrev lines
570 activebookmark = l
570 activebookmark = l
571 else:
571 else:
572 oldrev, newrev = l.split(':')
572 oldrev, newrev = l.split(':')
573 if newrev in (str(nullmerge), str(revignored)):
573 if newrev in (str(nullmerge), str(revignored)):
574 state[repo[oldrev].rev()] = int(newrev)
574 state[repo[oldrev].rev()] = int(newrev)
575 else:
575 else:
576 state[repo[oldrev].rev()] = repo[newrev].rev()
576 state[repo[oldrev].rev()] = repo[newrev].rev()
577 skipped = set()
577 skipped = set()
578 # recompute the set of skipped revs
578 # recompute the set of skipped revs
579 if not collapse:
579 if not collapse:
580 seen = set([target])
580 seen = set([target])
581 for old, new in sorted(state.items()):
581 for old, new in sorted(state.items()):
582 if new != nullrev and new in seen:
582 if new != nullrev and new in seen:
583 skipped.add(old)
583 skipped.add(old)
584 seen.add(new)
584 seen.add(new)
585 repo.ui.debug('computed skipped revs: %s\n' % skipped)
585 repo.ui.debug('computed skipped revs: %s\n' % skipped)
586 repo.ui.debug('rebase status resumed\n')
586 repo.ui.debug('rebase status resumed\n')
587 return (originalwd, target, state, skipped,
587 return (originalwd, target, state, skipped,
588 collapse, keep, keepbranches, external, activebookmark)
588 collapse, keep, keepbranches, external, activebookmark)
589 except IOError, err:
589 except IOError, err:
590 if err.errno != errno.ENOENT:
590 if err.errno != errno.ENOENT:
591 raise
591 raise
592 raise util.Abort(_('no rebase in progress'))
592 raise util.Abort(_('no rebase in progress'))
593
593
594 def abort(repo, originalwd, target, state):
594 def abort(repo, originalwd, target, state):
595 'Restore the repository to its original state'
595 'Restore the repository to its original state'
596 dstates = [s for s in state.values() if s != nullrev]
596 dstates = [s for s in state.values() if s != nullrev]
597 immutable = [d for d in dstates if not repo[d].mutable()]
597 immutable = [d for d in dstates if not repo[d].mutable()]
598 if immutable:
598 if immutable:
599 raise util.Abort(_("can't abort rebase due to immutable changesets %s")
599 raise util.Abort(_("can't abort rebase due to immutable changesets %s")
600 % ', '.join(str(repo[r]) for r in immutable),
600 % ', '.join(str(repo[r]) for r in immutable),
601 hint=_('see hg help phases for details'))
601 hint=_('see hg help phases for details'))
602
602
603 descendants = set()
603 descendants = set()
604 if dstates:
604 if dstates:
605 descendants = set(repo.changelog.descendants(dstates))
605 descendants = set(repo.changelog.descendants(dstates))
606 if descendants - set(dstates):
606 if descendants - set(dstates):
607 repo.ui.warn(_("warning: new changesets detected on target branch, "
607 repo.ui.warn(_("warning: new changesets detected on target branch, "
608 "can't abort\n"))
608 "can't abort\n"))
609 return -1
609 return -1
610 else:
610 else:
611 # Strip from the first rebased revision
611 # Strip from the first rebased revision
612 merge.update(repo, repo[originalwd].rev(), False, True, False)
612 merge.update(repo, repo[originalwd].rev(), False, True, False)
613 rebased = filter(lambda x: x > -1 and x != target, state.values())
613 rebased = filter(lambda x: x > -1 and x != target, state.values())
614 if rebased:
614 if rebased:
615 strippoints = [c.node() for c in repo.set('roots(%ld)', rebased)]
615 strippoints = [c.node() for c in repo.set('roots(%ld)', rebased)]
616 # no backup of rebased cset versions needed
616 # no backup of rebased cset versions needed
617 repair.strip(repo.ui, repo, strippoints)
617 repair.strip(repo.ui, repo, strippoints)
618 clearstatus(repo)
618 clearstatus(repo)
619 repo.ui.warn(_('rebase aborted\n'))
619 repo.ui.warn(_('rebase aborted\n'))
620 return 0
620 return 0
621
621
622 def buildstate(repo, dest, rebaseset, collapse):
622 def buildstate(repo, dest, rebaseset, collapse):
623 '''Define which revisions are going to be rebased and where
623 '''Define which revisions are going to be rebased and where
624
624
625 repo: repo
625 repo: repo
626 dest: context
626 dest: context
627 rebaseset: set of rev
627 rebaseset: set of rev
628 '''
628 '''
629
629
630 # This check isn't strictly necessary, since mq detects commits over an
630 # This check isn't strictly necessary, since mq detects commits over an
631 # applied patch. But it prevents messing up the working directory when
631 # applied patch. But it prevents messing up the working directory when
632 # a partially completed rebase is blocked by mq.
632 # a partially completed rebase is blocked by mq.
633 if 'qtip' in repo.tags() and (dest.node() in
633 if 'qtip' in repo.tags() and (dest.node() in
634 [s.node for s in repo.mq.applied]):
634 [s.node for s in repo.mq.applied]):
635 raise util.Abort(_('cannot rebase onto an applied mq patch'))
635 raise util.Abort(_('cannot rebase onto an applied mq patch'))
636
636
637 roots = list(repo.set('roots(%ld)', rebaseset))
637 roots = list(repo.set('roots(%ld)', rebaseset))
638 if not roots:
638 if not roots:
639 raise util.Abort(_('no matching revisions'))
639 raise util.Abort(_('no matching revisions'))
640 roots.sort()
640 roots.sort()
641 state = {}
641 state = {}
642 detachset = set()
642 detachset = set()
643 for root in roots:
643 for root in roots:
644 commonbase = root.ancestor(dest)
644 commonbase = root.ancestor(dest)
645 if commonbase == root:
645 if commonbase == root:
646 raise util.Abort(_('source is ancestor of destination'))
646 raise util.Abort(_('source is ancestor of destination'))
647 if commonbase == dest:
647 if commonbase == dest:
648 samebranch = root.branch() == dest.branch()
648 samebranch = root.branch() == dest.branch()
649 if not collapse and samebranch and root in dest.children():
649 if not collapse and samebranch and root in dest.children():
650 repo.ui.debug('source is a child of destination\n')
650 repo.ui.debug('source is a child of destination\n')
651 return None
651 return None
652
652
653 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, roots))
653 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, roots))
654 state.update(dict.fromkeys(rebaseset, nullrev))
654 state.update(dict.fromkeys(rebaseset, nullrev))
655 # Rebase tries to turn <dest> into a parent of <root> while
655 # Rebase tries to turn <dest> into a parent of <root> while
656 # preserving the number of parents of rebased changesets:
656 # preserving the number of parents of rebased changesets:
657 #
657 #
658 # - A changeset with a single parent will always be rebased as a
658 # - A changeset with a single parent will always be rebased as a
659 # changeset with a single parent.
659 # changeset with a single parent.
660 #
660 #
661 # - A merge will be rebased as merge unless its parents are both
661 # - A merge will be rebased as merge unless its parents are both
662 # ancestors of <dest> or are themselves in the rebased set and
662 # ancestors of <dest> or are themselves in the rebased set and
663 # pruned while rebased.
663 # pruned while rebased.
664 #
664 #
665 # If one parent of <root> is an ancestor of <dest>, the rebased
665 # If one parent of <root> is an ancestor of <dest>, the rebased
666 # version of this parent will be <dest>. This is always true with
666 # version of this parent will be <dest>. This is always true with
667 # --base option.
667 # --base option.
668 #
668 #
669 # Otherwise, we need to *replace* the original parents with
669 # Otherwise, we need to *replace* the original parents with
670 # <dest>. This "detaches" the rebased set from its former location
670 # <dest>. This "detaches" the rebased set from its former location
671 # and rebases it onto <dest>. Changes introduced by ancestors of
671 # and rebases it onto <dest>. Changes introduced by ancestors of
672 # <root> not common with <dest> (the detachset, marked as
672 # <root> not common with <dest> (the detachset, marked as
673 # nullmerge) are "removed" from the rebased changesets.
673 # nullmerge) are "removed" from the rebased changesets.
674 #
674 #
675 # - If <root> has a single parent, set it to <dest>.
675 # - If <root> has a single parent, set it to <dest>.
676 #
676 #
677 # - If <root> is a merge, we cannot decide which parent to
677 # - If <root> is a merge, we cannot decide which parent to
678 # replace, the rebase operation is not clearly defined.
678 # replace, the rebase operation is not clearly defined.
679 #
679 #
680 # The table below sums up this behavior:
680 # The table below sums up this behavior:
681 #
681 #
682 # +------------------+----------------------+-------------------------+
682 # +------------------+----------------------+-------------------------+
683 # | | one parent | merge |
683 # | | one parent | merge |
684 # +------------------+----------------------+-------------------------+
684 # +------------------+----------------------+-------------------------+
685 # | parent in | new parent is <dest> | parents in ::<dest> are |
685 # | parent in | new parent is <dest> | parents in ::<dest> are |
686 # | ::<dest> | | remapped to <dest> |
686 # | ::<dest> | | remapped to <dest> |
687 # +------------------+----------------------+-------------------------+
687 # +------------------+----------------------+-------------------------+
688 # | unrelated source | new parent is <dest> | ambiguous, abort |
688 # | unrelated source | new parent is <dest> | ambiguous, abort |
689 # +------------------+----------------------+-------------------------+
689 # +------------------+----------------------+-------------------------+
690 #
690 #
691 # The actual abort is handled by `defineparents`
691 # The actual abort is handled by `defineparents`
692 if len(root.parents()) <= 1:
692 if len(root.parents()) <= 1:
693 # ancestors of <root> not ancestors of <dest>
693 # ancestors of <root> not ancestors of <dest>
694 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
694 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
695 [root.rev()]))
695 [root.rev()]))
696 for r in detachset:
696 for r in detachset:
697 if r not in state:
697 if r not in state:
698 state[r] = nullmerge
698 state[r] = nullmerge
699 if len(roots) > 1:
699 if len(roots) > 1:
700 # If we have multiple roots, we may have "hole" in the rebase set.
700 # If we have multiple roots, we may have "hole" in the rebase set.
701 # Rebase roots that descend from those "hole" should not be detached as
701 # Rebase roots that descend from those "hole" should not be detached as
702 # other root are. We use the special `revignored` to inform rebase that
702 # other root are. We use the special `revignored` to inform rebase that
703 # the revision should be ignored but that `defineparents` should search
703 # the revision should be ignored but that `defineparents` should search
704 # a rebase destination that make sense regarding rebased topology.
704 # a rebase destination that make sense regarding rebased topology.
705 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
705 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
706 for ignored in set(rebasedomain) - set(rebaseset):
706 for ignored in set(rebasedomain) - set(rebaseset):
707 state[ignored] = revignored
707 state[ignored] = revignored
708 return repo['.'].rev(), dest.rev(), state
708 return repo['.'].rev(), dest.rev(), state
709
709
710 def clearrebased(ui, repo, state, skipped, collapsedas=None):
710 def clearrebased(ui, repo, state, skipped, collapsedas=None):
711 """dispose of rebased revision at the end of the rebase
711 """dispose of rebased revision at the end of the rebase
712
712
713 If `collapsedas` is not None, the rebase was a collapse whose result if the
713 If `collapsedas` is not None, the rebase was a collapse whose result if the
714 `collapsedas` node."""
714 `collapsedas` node."""
715 if obsolete._enabled:
715 if obsolete._enabled:
716 markers = []
716 markers = []
717 for rev, newrev in sorted(state.items()):
717 for rev, newrev in sorted(state.items()):
718 if newrev >= 0:
718 if newrev >= 0:
719 if rev in skipped:
719 if rev in skipped:
720 succs = ()
720 succs = ()
721 elif collapsedas is not None:
721 elif collapsedas is not None:
722 succs = (repo[collapsedas],)
722 succs = (repo[collapsedas],)
723 else:
723 else:
724 succs = (repo[newrev],)
724 succs = (repo[newrev],)
725 markers.append((repo[rev], succs))
725 markers.append((repo[rev], succs))
726 if markers:
726 if markers:
727 obsolete.createmarkers(repo, markers)
727 obsolete.createmarkers(repo, markers)
728 else:
728 else:
729 rebased = [rev for rev in state if state[rev] > nullmerge]
729 rebased = [rev for rev in state if state[rev] > nullmerge]
730 if rebased:
730 if rebased:
731 stripped = []
731 stripped = []
732 for root in repo.set('roots(%ld)', rebased):
732 for root in repo.set('roots(%ld)', rebased):
733 if set(repo.changelog.descendants([root.rev()])) - set(state):
733 if set(repo.changelog.descendants([root.rev()])) - set(state):
734 ui.warn(_("warning: new changesets detected "
734 ui.warn(_("warning: new changesets detected "
735 "on source branch, not stripping\n"))
735 "on source branch, not stripping\n"))
736 else:
736 else:
737 stripped.append(root.node())
737 stripped.append(root.node())
738 if stripped:
738 if stripped:
739 # backup the old csets by default
739 # backup the old csets by default
740 repair.strip(ui, repo, stripped, "all")
740 repair.strip(ui, repo, stripped, "all")
741
741
742
742
743 def pullrebase(orig, ui, repo, *args, **opts):
743 def pullrebase(orig, ui, repo, *args, **opts):
744 'Call rebase after pull if the latter has been invoked with --rebase'
744 'Call rebase after pull if the latter has been invoked with --rebase'
745 if opts.get('rebase'):
745 if opts.get('rebase'):
746 if opts.get('update'):
746 if opts.get('update'):
747 del opts['update']
747 del opts['update']
748 ui.debug('--update and --rebase are not compatible, ignoring '
748 ui.debug('--update and --rebase are not compatible, ignoring '
749 'the update flag\n')
749 'the update flag\n')
750
750
751 movemarkfrom = repo['.'].node()
751 movemarkfrom = repo['.'].node()
752 cmdutil.bailifchanged(repo)
752 cmdutil.bailifchanged(repo)
753 revsprepull = len(repo)
753 revsprepull = len(repo)
754 origpostincoming = commands.postincoming
754 origpostincoming = commands.postincoming
755 def _dummy(*args, **kwargs):
755 def _dummy(*args, **kwargs):
756 pass
756 pass
757 commands.postincoming = _dummy
757 commands.postincoming = _dummy
758 try:
758 try:
759 orig(ui, repo, *args, **opts)
759 orig(ui, repo, *args, **opts)
760 finally:
760 finally:
761 commands.postincoming = origpostincoming
761 commands.postincoming = origpostincoming
762 revspostpull = len(repo)
762 revspostpull = len(repo)
763 if revspostpull > revsprepull:
763 if revspostpull > revsprepull:
764 # --rev option from pull conflict with rebase own --rev
764 # --rev option from pull conflict with rebase own --rev
765 # dropping it
765 # dropping it
766 if 'rev' in opts:
766 if 'rev' in opts:
767 del opts['rev']
767 del opts['rev']
768 rebase(ui, repo, **opts)
768 rebase(ui, repo, **opts)
769 branch = repo[None].branch()
769 branch = repo[None].branch()
770 dest = repo[branch].rev()
770 dest = repo[branch].rev()
771 if dest != repo['.'].rev():
771 if dest != repo['.'].rev():
772 # there was nothing to rebase we force an update
772 # there was nothing to rebase we force an update
773 hg.update(repo, dest)
773 hg.update(repo, dest)
774 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
774 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
775 ui.status(_("updating bookmark %s\n")
775 ui.status(_("updating bookmark %s\n")
776 % repo._bookmarkcurrent)
776 % repo._bookmarkcurrent)
777 else:
777 else:
778 if opts.get('tool'):
778 if opts.get('tool'):
779 raise util.Abort(_('--tool can only be used with --rebase'))
779 raise util.Abort(_('--tool can only be used with --rebase'))
780 orig(ui, repo, *args, **opts)
780 orig(ui, repo, *args, **opts)
781
781
782 def summaryhook(ui, repo):
783 if not os.path.exists(repo.join('rebasestate')):
784 return
785 state = restorestatus(repo)[2]
786 numrebased = len([i for i in state.itervalues() if i != -1])
787 # i18n: column positioning for "hg summary"
788 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
789 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
790 ui.label(_('%d remaining'), 'rebase.remaining') %
791 (len(state) - numrebased)))
792
782 def uisetup(ui):
793 def uisetup(ui):
783 'Replace pull with a decorator to provide --rebase option'
794 'Replace pull with a decorator to provide --rebase option'
784 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
795 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
785 entry[1].append(('', 'rebase', None,
796 entry[1].append(('', 'rebase', None,
786 _("rebase working directory to branch head")))
797 _("rebase working directory to branch head")))
787 entry[1].append(('t', 'tool', '',
798 entry[1].append(('t', 'tool', '',
788 _("specify merge tool for rebase")))
799 _("specify merge tool for rebase")))
800 cmdutil.summaryhooks.add('rebase', summaryhook)
@@ -1,439 +1,440 b''
1 $ cat >> $HGRCPATH <<EOF
1 $ cat >> $HGRCPATH <<EOF
2 > [extensions]
2 > [extensions]
3 > graphlog=
3 > graphlog=
4 > rebase=
4 > rebase=
5 >
5 >
6 > [phases]
6 > [phases]
7 > publish=False
7 > publish=False
8 >
8 >
9 > [alias]
9 > [alias]
10 > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
10 > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
11 > EOF
11 > EOF
12
12
13
13
14 $ hg init a
14 $ hg init a
15 $ cd a
15 $ cd a
16 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
16 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
17 adding changesets
17 adding changesets
18 adding manifests
18 adding manifests
19 adding file changes
19 adding file changes
20 added 8 changesets with 7 changes to 7 files (+2 heads)
20 added 8 changesets with 7 changes to 7 files (+2 heads)
21 (run 'hg heads' to see heads, 'hg merge' to merge)
21 (run 'hg heads' to see heads, 'hg merge' to merge)
22 $ hg up tip
22 $ hg up tip
23 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
23 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
24
24
25 $ echo I > I
25 $ echo I > I
26 $ hg ci -AmI
26 $ hg ci -AmI
27 adding I
27 adding I
28
28
29 $ hg tglog
29 $ hg tglog
30 @ 8: 'I'
30 @ 8: 'I'
31 |
31 |
32 o 7: 'H'
32 o 7: 'H'
33 |
33 |
34 | o 6: 'G'
34 | o 6: 'G'
35 |/|
35 |/|
36 o | 5: 'F'
36 o | 5: 'F'
37 | |
37 | |
38 | o 4: 'E'
38 | o 4: 'E'
39 |/
39 |/
40 | o 3: 'D'
40 | o 3: 'D'
41 | |
41 | |
42 | o 2: 'C'
42 | o 2: 'C'
43 | |
43 | |
44 | o 1: 'B'
44 | o 1: 'B'
45 |/
45 |/
46 o 0: 'A'
46 o 0: 'A'
47
47
48 $ cd ..
48 $ cd ..
49
49
50
50
51 These fail:
51 These fail:
52
52
53 $ hg clone -q -u . a a1
53 $ hg clone -q -u . a a1
54 $ cd a1
54 $ cd a1
55
55
56 $ hg rebase -s 8 -d 7
56 $ hg rebase -s 8 -d 7
57 nothing to rebase
57 nothing to rebase
58 [1]
58 [1]
59
59
60 $ hg rebase --continue --abort
60 $ hg rebase --continue --abort
61 abort: cannot use both abort and continue
61 abort: cannot use both abort and continue
62 [255]
62 [255]
63
63
64 $ hg rebase --continue --collapse
64 $ hg rebase --continue --collapse
65 abort: cannot use collapse with continue or abort
65 abort: cannot use collapse with continue or abort
66 [255]
66 [255]
67
67
68 $ hg rebase --continue --dest 4
68 $ hg rebase --continue --dest 4
69 abort: abort and continue do not allow specifying revisions
69 abort: abort and continue do not allow specifying revisions
70 [255]
70 [255]
71
71
72 $ hg rebase --base 5 --source 4
72 $ hg rebase --base 5 --source 4
73 abort: cannot specify both a source and a base
73 abort: cannot specify both a source and a base
74 [255]
74 [255]
75
75
76 $ hg rebase --rev 5 --source 4
76 $ hg rebase --rev 5 --source 4
77 abort: cannot specify both a revision and a source
77 abort: cannot specify both a revision and a source
78 [255]
78 [255]
79 $ hg rebase --base 5 --rev 4
79 $ hg rebase --base 5 --rev 4
80 abort: cannot specify both a revision and a base
80 abort: cannot specify both a revision and a base
81 [255]
81 [255]
82
82
83 $ hg rebase
83 $ hg rebase
84 nothing to rebase
84 nothing to rebase
85 [1]
85 [1]
86
86
87 $ hg up -q 7
87 $ hg up -q 7
88
88
89 $ hg rebase --traceback
89 $ hg rebase --traceback
90 nothing to rebase
90 nothing to rebase
91 [1]
91 [1]
92
92
93
93
94 These work:
94 These work:
95
95
96 Rebase with no arguments (from 3 onto 8):
96 Rebase with no arguments (from 3 onto 8):
97
97
98 $ hg up -q -C 3
98 $ hg up -q -C 3
99
99
100 $ hg rebase
100 $ hg rebase
101 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob)
101 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob)
102
102
103 $ hg tglog
103 $ hg tglog
104 @ 8: 'D'
104 @ 8: 'D'
105 |
105 |
106 o 7: 'C'
106 o 7: 'C'
107 |
107 |
108 o 6: 'B'
108 o 6: 'B'
109 |
109 |
110 o 5: 'I'
110 o 5: 'I'
111 |
111 |
112 o 4: 'H'
112 o 4: 'H'
113 |
113 |
114 | o 3: 'G'
114 | o 3: 'G'
115 |/|
115 |/|
116 o | 2: 'F'
116 o | 2: 'F'
117 | |
117 | |
118 | o 1: 'E'
118 | o 1: 'E'
119 |/
119 |/
120 o 0: 'A'
120 o 0: 'A'
121
121
122 Try to rollback after a rebase (fail):
122 Try to rollback after a rebase (fail):
123
123
124 $ hg rollback
124 $ hg rollback
125 no rollback information available
125 no rollback information available
126 [1]
126 [1]
127
127
128 $ cd ..
128 $ cd ..
129
129
130
130
131 Rebase with base == '.' => same as no arguments (from 3 onto 8):
131 Rebase with base == '.' => same as no arguments (from 3 onto 8):
132
132
133 $ hg clone -q -u 3 a a2
133 $ hg clone -q -u 3 a a2
134 $ cd a2
134 $ cd a2
135
135
136 $ hg rebase --base .
136 $ hg rebase --base .
137 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob)
137 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob)
138
138
139 $ hg tglog
139 $ hg tglog
140 @ 8: 'D'
140 @ 8: 'D'
141 |
141 |
142 o 7: 'C'
142 o 7: 'C'
143 |
143 |
144 o 6: 'B'
144 o 6: 'B'
145 |
145 |
146 o 5: 'I'
146 o 5: 'I'
147 |
147 |
148 o 4: 'H'
148 o 4: 'H'
149 |
149 |
150 | o 3: 'G'
150 | o 3: 'G'
151 |/|
151 |/|
152 o | 2: 'F'
152 o | 2: 'F'
153 | |
153 | |
154 | o 1: 'E'
154 | o 1: 'E'
155 |/
155 |/
156 o 0: 'A'
156 o 0: 'A'
157
157
158 $ cd ..
158 $ cd ..
159
159
160
160
161 Rebase with dest == branch(.) => same as no arguments (from 3 onto 8):
161 Rebase with dest == branch(.) => same as no arguments (from 3 onto 8):
162
162
163 $ hg clone -q -u 3 a a3
163 $ hg clone -q -u 3 a a3
164 $ cd a3
164 $ cd a3
165
165
166 $ hg rebase --dest 'branch(.)'
166 $ hg rebase --dest 'branch(.)'
167 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob)
167 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob)
168
168
169 $ hg tglog
169 $ hg tglog
170 @ 8: 'D'
170 @ 8: 'D'
171 |
171 |
172 o 7: 'C'
172 o 7: 'C'
173 |
173 |
174 o 6: 'B'
174 o 6: 'B'
175 |
175 |
176 o 5: 'I'
176 o 5: 'I'
177 |
177 |
178 o 4: 'H'
178 o 4: 'H'
179 |
179 |
180 | o 3: 'G'
180 | o 3: 'G'
181 |/|
181 |/|
182 o | 2: 'F'
182 o | 2: 'F'
183 | |
183 | |
184 | o 1: 'E'
184 | o 1: 'E'
185 |/
185 |/
186 o 0: 'A'
186 o 0: 'A'
187
187
188 $ cd ..
188 $ cd ..
189
189
190
190
191 Specify only source (from 2 onto 8):
191 Specify only source (from 2 onto 8):
192
192
193 $ hg clone -q -u . a a4
193 $ hg clone -q -u . a a4
194 $ cd a4
194 $ cd a4
195
195
196 $ hg rebase --source 'desc("C")'
196 $ hg rebase --source 'desc("C")'
197 saved backup bundle to $TESTTMP/a4/.hg/strip-backup/*-backup.hg (glob)
197 saved backup bundle to $TESTTMP/a4/.hg/strip-backup/*-backup.hg (glob)
198
198
199 $ hg tglog
199 $ hg tglog
200 @ 8: 'D'
200 @ 8: 'D'
201 |
201 |
202 o 7: 'C'
202 o 7: 'C'
203 |
203 |
204 o 6: 'I'
204 o 6: 'I'
205 |
205 |
206 o 5: 'H'
206 o 5: 'H'
207 |
207 |
208 | o 4: 'G'
208 | o 4: 'G'
209 |/|
209 |/|
210 o | 3: 'F'
210 o | 3: 'F'
211 | |
211 | |
212 | o 2: 'E'
212 | o 2: 'E'
213 |/
213 |/
214 | o 1: 'B'
214 | o 1: 'B'
215 |/
215 |/
216 o 0: 'A'
216 o 0: 'A'
217
217
218 $ cd ..
218 $ cd ..
219
219
220
220
221 Specify only dest (from 3 onto 6):
221 Specify only dest (from 3 onto 6):
222
222
223 $ hg clone -q -u 3 a a5
223 $ hg clone -q -u 3 a a5
224 $ cd a5
224 $ cd a5
225
225
226 $ hg rebase --dest 6
226 $ hg rebase --dest 6
227 saved backup bundle to $TESTTMP/a5/.hg/strip-backup/*-backup.hg (glob)
227 saved backup bundle to $TESTTMP/a5/.hg/strip-backup/*-backup.hg (glob)
228
228
229 $ hg tglog
229 $ hg tglog
230 @ 8: 'D'
230 @ 8: 'D'
231 |
231 |
232 o 7: 'C'
232 o 7: 'C'
233 |
233 |
234 o 6: 'B'
234 o 6: 'B'
235 |
235 |
236 | o 5: 'I'
236 | o 5: 'I'
237 | |
237 | |
238 | o 4: 'H'
238 | o 4: 'H'
239 | |
239 | |
240 o | 3: 'G'
240 o | 3: 'G'
241 |\|
241 |\|
242 | o 2: 'F'
242 | o 2: 'F'
243 | |
243 | |
244 o | 1: 'E'
244 o | 1: 'E'
245 |/
245 |/
246 o 0: 'A'
246 o 0: 'A'
247
247
248 $ cd ..
248 $ cd ..
249
249
250
250
251 Specify only base (from 1 onto 8):
251 Specify only base (from 1 onto 8):
252
252
253 $ hg clone -q -u . a a6
253 $ hg clone -q -u . a a6
254 $ cd a6
254 $ cd a6
255
255
256 $ hg rebase --base 'desc("D")'
256 $ hg rebase --base 'desc("D")'
257 saved backup bundle to $TESTTMP/a6/.hg/strip-backup/*-backup.hg (glob)
257 saved backup bundle to $TESTTMP/a6/.hg/strip-backup/*-backup.hg (glob)
258
258
259 $ hg tglog
259 $ hg tglog
260 @ 8: 'D'
260 @ 8: 'D'
261 |
261 |
262 o 7: 'C'
262 o 7: 'C'
263 |
263 |
264 o 6: 'B'
264 o 6: 'B'
265 |
265 |
266 o 5: 'I'
266 o 5: 'I'
267 |
267 |
268 o 4: 'H'
268 o 4: 'H'
269 |
269 |
270 | o 3: 'G'
270 | o 3: 'G'
271 |/|
271 |/|
272 o | 2: 'F'
272 o | 2: 'F'
273 | |
273 | |
274 | o 1: 'E'
274 | o 1: 'E'
275 |/
275 |/
276 o 0: 'A'
276 o 0: 'A'
277
277
278 $ cd ..
278 $ cd ..
279
279
280
280
281 Specify source and dest (from 2 onto 7):
281 Specify source and dest (from 2 onto 7):
282
282
283 $ hg clone -q -u . a a7
283 $ hg clone -q -u . a a7
284 $ cd a7
284 $ cd a7
285
285
286 $ hg rebase --source 2 --dest 7
286 $ hg rebase --source 2 --dest 7
287 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/*-backup.hg (glob)
287 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/*-backup.hg (glob)
288
288
289 $ hg tglog
289 $ hg tglog
290 @ 8: 'D'
290 @ 8: 'D'
291 |
291 |
292 o 7: 'C'
292 o 7: 'C'
293 |
293 |
294 | o 6: 'I'
294 | o 6: 'I'
295 |/
295 |/
296 o 5: 'H'
296 o 5: 'H'
297 |
297 |
298 | o 4: 'G'
298 | o 4: 'G'
299 |/|
299 |/|
300 o | 3: 'F'
300 o | 3: 'F'
301 | |
301 | |
302 | o 2: 'E'
302 | o 2: 'E'
303 |/
303 |/
304 | o 1: 'B'
304 | o 1: 'B'
305 |/
305 |/
306 o 0: 'A'
306 o 0: 'A'
307
307
308 $ cd ..
308 $ cd ..
309
309
310
310
311 Specify base and dest (from 1 onto 7):
311 Specify base and dest (from 1 onto 7):
312
312
313 $ hg clone -q -u . a a8
313 $ hg clone -q -u . a a8
314 $ cd a8
314 $ cd a8
315
315
316 $ hg rebase --base 3 --dest 7
316 $ hg rebase --base 3 --dest 7
317 saved backup bundle to $TESTTMP/a8/.hg/strip-backup/*-backup.hg (glob)
317 saved backup bundle to $TESTTMP/a8/.hg/strip-backup/*-backup.hg (glob)
318
318
319 $ hg tglog
319 $ hg tglog
320 @ 8: 'D'
320 @ 8: 'D'
321 |
321 |
322 o 7: 'C'
322 o 7: 'C'
323 |
323 |
324 o 6: 'B'
324 o 6: 'B'
325 |
325 |
326 | o 5: 'I'
326 | o 5: 'I'
327 |/
327 |/
328 o 4: 'H'
328 o 4: 'H'
329 |
329 |
330 | o 3: 'G'
330 | o 3: 'G'
331 |/|
331 |/|
332 o | 2: 'F'
332 o | 2: 'F'
333 | |
333 | |
334 | o 1: 'E'
334 | o 1: 'E'
335 |/
335 |/
336 o 0: 'A'
336 o 0: 'A'
337
337
338 $ cd ..
338 $ cd ..
339
339
340
340
341 Specify only revs (from 2 onto 8)
341 Specify only revs (from 2 onto 8)
342
342
343 $ hg clone -q -u . a a9
343 $ hg clone -q -u . a a9
344 $ cd a9
344 $ cd a9
345
345
346 $ hg rebase --rev 'desc("C")::'
346 $ hg rebase --rev 'desc("C")::'
347 saved backup bundle to $TESTTMP/a9/.hg/strip-backup/*-backup.hg (glob)
347 saved backup bundle to $TESTTMP/a9/.hg/strip-backup/*-backup.hg (glob)
348
348
349 $ hg tglog
349 $ hg tglog
350 @ 8: 'D'
350 @ 8: 'D'
351 |
351 |
352 o 7: 'C'
352 o 7: 'C'
353 |
353 |
354 o 6: 'I'
354 o 6: 'I'
355 |
355 |
356 o 5: 'H'
356 o 5: 'H'
357 |
357 |
358 | o 4: 'G'
358 | o 4: 'G'
359 |/|
359 |/|
360 o | 3: 'F'
360 o | 3: 'F'
361 | |
361 | |
362 | o 2: 'E'
362 | o 2: 'E'
363 |/
363 |/
364 | o 1: 'B'
364 | o 1: 'B'
365 |/
365 |/
366 o 0: 'A'
366 o 0: 'A'
367
367
368 $ cd ..
368 $ cd ..
369
369
370 Test --tool parameter:
370 Test --tool parameter:
371
371
372 $ hg init b
372 $ hg init b
373 $ cd b
373 $ cd b
374
374
375 $ echo c1 > c1
375 $ echo c1 > c1
376 $ hg ci -Am c1
376 $ hg ci -Am c1
377 adding c1
377 adding c1
378
378
379 $ echo c2 > c2
379 $ echo c2 > c2
380 $ hg ci -Am c2
380 $ hg ci -Am c2
381 adding c2
381 adding c2
382
382
383 $ hg up -q 0
383 $ hg up -q 0
384 $ echo c2b > c2
384 $ echo c2b > c2
385 $ hg ci -Am c2b
385 $ hg ci -Am c2b
386 adding c2
386 adding c2
387 created new head
387 created new head
388
388
389 $ cd ..
389 $ cd ..
390
390
391 $ hg clone -q -u . b b1
391 $ hg clone -q -u . b b1
392 $ cd b1
392 $ cd b1
393
393
394 $ hg rebase -s 2 -d 1 --tool internal:local
394 $ hg rebase -s 2 -d 1 --tool internal:local
395 saved backup bundle to $TESTTMP/b1/.hg/strip-backup/*-backup.hg (glob)
395 saved backup bundle to $TESTTMP/b1/.hg/strip-backup/*-backup.hg (glob)
396
396
397 $ hg cat c2
397 $ hg cat c2
398 c2
398 c2
399
399
400 $ cd ..
400 $ cd ..
401
401
402
402
403 $ hg clone -q -u . b b2
403 $ hg clone -q -u . b b2
404 $ cd b2
404 $ cd b2
405
405
406 $ hg rebase -s 2 -d 1 --tool internal:other
406 $ hg rebase -s 2 -d 1 --tool internal:other
407 saved backup bundle to $TESTTMP/b2/.hg/strip-backup/*-backup.hg (glob)
407 saved backup bundle to $TESTTMP/b2/.hg/strip-backup/*-backup.hg (glob)
408
408
409 $ hg cat c2
409 $ hg cat c2
410 c2b
410 c2b
411
411
412 $ cd ..
412 $ cd ..
413
413
414
414
415 $ hg clone -q -u . b b3
415 $ hg clone -q -u . b b3
416 $ cd b3
416 $ cd b3
417
417
418 $ hg rebase -s 2 -d 1 --tool internal:fail
418 $ hg rebase -s 2 -d 1 --tool internal:fail
419 unresolved conflicts (see hg resolve, then hg rebase --continue)
419 unresolved conflicts (see hg resolve, then hg rebase --continue)
420 [1]
420 [1]
421
421
422 $ hg summary
422 $ hg summary
423 parent: 1:56daeba07f4b
423 parent: 1:56daeba07f4b
424 c2
424 c2
425 parent: 2:e4e3f3546619 tip
425 parent: 2:e4e3f3546619 tip
426 c2b
426 c2b
427 branch: default
427 branch: default
428 commit: 1 modified, 1 unresolved (merge)
428 commit: 1 modified, 1 unresolved (merge)
429 update: (current)
429 update: (current)
430 rebase: 0 rebased, 1 remaining (rebase --continue)
430
431
431 $ hg resolve -l
432 $ hg resolve -l
432 U c2
433 U c2
433
434
434 $ hg resolve -m c2
435 $ hg resolve -m c2
435 $ hg rebase -c --tool internal:fail
436 $ hg rebase -c --tool internal:fail
436 tool option will be ignored
437 tool option will be ignored
437 saved backup bundle to $TESTTMP/b3/.hg/strip-backup/*-backup.hg (glob)
438 saved backup bundle to $TESTTMP/b3/.hg/strip-backup/*-backup.hg (glob)
438
439
439 $ cd ..
440 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now