##// END OF EJS Templates
Merge with stable
Martin Geisler -
r10660:24555e21 merge default
parent child Browse files
Show More
@@ -1,167 +1,168 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # check-code - a style and portability checker for Mercurial
3 # check-code - a style and portability checker for Mercurial
4 #
4 #
5 # Copyright 2010 Matt Mackall <mpm@selenic.com>
5 # Copyright 2010 Matt Mackall <mpm@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 import sys, re, glob
10 import sys, re, glob
11
11
12 def repquote(m):
12 def repquote(m):
13 t = re.sub(r"\w", "x", m.group(2))
13 t = re.sub(r"\w", "x", m.group(2))
14 t = re.sub(r"[^\sx]", "o", t)
14 t = re.sub(r"[^\sx]", "o", t)
15 return m.group(1) + t + m.group(1)
15 return m.group(1) + t + m.group(1)
16
16
17 def repcomment(m):
17 def repcomment(m):
18 return m.group(1) + "#" * len(m.group(2))
18 return m.group(1) + "#" * len(m.group(2))
19
19
20 def repccomment(m):
20 def repccomment(m):
21 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
21 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
22 return m.group(1) + t + "*/"
22 return m.group(1) + t + "*/"
23
23
24 def repcallspaces(m):
24 def repcallspaces(m):
25 t = re.sub(r"\n\s+", "\n", m.group(2))
25 t = re.sub(r"\n\s+", "\n", m.group(2))
26 return m.group(1) + t
26 return m.group(1) + t
27
27
28 def repinclude(m):
28 def repinclude(m):
29 return m.group(1) + "<foo>"
29 return m.group(1) + "<foo>"
30
30
31 def rephere(m):
31 def rephere(m):
32 t = re.sub(r"\S", "x", m.group(2))
32 t = re.sub(r"\S", "x", m.group(2))
33 return m.group(1) + t
33 return m.group(1) + t
34
34
35
35
36 testpats = [
36 testpats = [
37 (r'(pushd|popd)', "don't use 'pushd' or 'popd', use 'cd'"),
37 (r'(pushd|popd)', "don't use 'pushd' or 'popd', use 'cd'"),
38 (r'\W\$?\(\([^\)]*\)\)', "don't use (()) or $(()), use 'expr'"),
38 (r'\W\$?\(\([^\)]*\)\)', "don't use (()) or $(()), use 'expr'"),
39 (r'^function', "don't use 'function', use old style"),
39 (r'^function', "don't use 'function', use old style"),
40 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
40 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
41 (r'echo.*\\n', "don't use 'echo \\n', use printf"),
41 (r'echo.*\\n', "don't use 'echo \\n', use printf"),
42 (r'^diff.*-\w*N', "don't use 'diff -N'"),
42 (r'^diff.*-\w*N', "don't use 'diff -N'"),
43 (r'(^| )wc[^|]*$', "filter wc output"),
43 (r'(^| )wc[^|]*$', "filter wc output"),
44 (r'head -c', "don't use 'head -c', use 'dd'"),
44 (r'head -c', "don't use 'head -c', use 'dd'"),
45 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
45 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
46 (r'printf.*\\\d\d\d', "don't use 'printf \NNN', use Python"),
46 (r'printf.*\\\d\d\d', "don't use 'printf \NNN', use Python"),
47 (r'printf.*\\x', "don't use printf \\x, use Python"),
47 (r'printf.*\\x', "don't use printf \\x, use Python"),
48 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
48 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
49 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
49 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
50 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
50 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
51 "use egrep for extended grep syntax"),
51 "use egrep for extended grep syntax"),
52 (r'/bin/', "don't use explicit paths for tools"),
52 (r'/bin/', "don't use explicit paths for tools"),
53 (r'\$PWD', "don't use $PWD, use `pwd`"),
53 (r'\$PWD', "don't use $PWD, use `pwd`"),
54 (r'[^\n]\Z', "no trailing newline"),
54 (r'[^\n]\Z', "no trailing newline"),
55 (r'export.*=', "don't export and assign at once"),
55 ]
56 ]
56
57
57 testfilters = [
58 testfilters = [
58 (r"( *)(#([^\n]*\S)?)", repcomment),
59 (r"( *)(#([^\n]*\S)?)", repcomment),
59 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
60 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
60 ]
61 ]
61
62
62 pypats = [
63 pypats = [
63 (r'^\s*\t', "don't use tabs"),
64 (r'^\s*\t', "don't use tabs"),
64 (r'\S;\s*\n', "semicolon"),
65 (r'\S;\s*\n', "semicolon"),
65 (r'\w,\w', "missing whitespace after ,"),
66 (r'\w,\w', "missing whitespace after ,"),
66 (r'\w[+/*\-<>]\w', "missing whitespace in expression"),
67 (r'\w[+/*\-<>]\w', "missing whitespace in expression"),
67 (r'^\s+\w+=\w+[^,)]$', "missing whitespace in assignment"),
68 (r'^\s+\w+=\w+[^,)]$', "missing whitespace in assignment"),
68 (r'.{85}', "line too long"),
69 (r'.{85}', "line too long"),
69 (r'[^\n]\Z', "no trailing newline"),
70 (r'[^\n]\Z', "no trailing newline"),
70 # (r'^\s+[^_ ][^_. ]+_[^_]+\s*=', "don't use underbars in identifiers"),
71 # (r'^\s+[^_ ][^_. ]+_[^_]+\s*=', "don't use underbars in identifiers"),
71 # (r'\w*[a-z][A-Z]\w*\s*=', "don't use camelcase in identifiers"),
72 # (r'\w*[a-z][A-Z]\w*\s*=', "don't use camelcase in identifiers"),
72 (r'^\s*(if|while|def|class|except|try)\s[^[]*:\s*[^\]#\s]+',
73 (r'^\s*(if|while|def|class|except|try)\s[^[]*:\s*[^\]#\s]+',
73 "linebreak after :"),
74 "linebreak after :"),
74 (r'class\s[^(]:', "old-style class, use class foo(object)"),
75 (r'class\s[^(]:', "old-style class, use class foo(object)"),
75 (r'^\s+del\(', "del isn't a function"),
76 (r'^\s+del\(', "del isn't a function"),
76 (r'^\s+except\(', "except isn't a function"),
77 (r'^\s+except\(', "except isn't a function"),
77 (r',]', "unneeded trailing ',' in list"),
78 (r',]', "unneeded trailing ',' in list"),
78 # (r'class\s[A-Z][^\(]*\((?!Exception)',
79 # (r'class\s[A-Z][^\(]*\((?!Exception)',
79 # "don't capitalize non-exception classes"),
80 # "don't capitalize non-exception classes"),
80 # (r'in range\(', "use xrange"),
81 # (r'in range\(', "use xrange"),
81 # (r'^\s*print\s+', "avoid using print in core and extensions"),
82 # (r'^\s*print\s+', "avoid using print in core and extensions"),
82 (r'[\x80-\xff]', "non-ASCII character literal"),
83 (r'[\x80-\xff]', "non-ASCII character literal"),
83 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
84 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
84 (r'^\s*with\s+', "with not available in Python 2.4"),
85 (r'^\s*with\s+', "with not available in Python 2.4"),
85 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
86 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
86 (r'([\(\[]\s\S)|(\S\s[\)\]])', "gratuitous whitespace in () or []"),
87 (r'([\(\[]\s\S)|(\S\s[\)\]])', "gratuitous whitespace in () or []"),
87 # (r'\s\s=', "gratuitous whitespace before ="),
88 # (r'\s\s=', "gratuitous whitespace before ="),
88 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\S', "missing whitespace around operator"),
89 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\S', "missing whitespace around operator"),
89 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\s', "missing whitespace around operator"),
90 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\s', "missing whitespace around operator"),
90 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=)\S', "missing whitespace around operator"),
91 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=)\S', "missing whitespace around operator"),
91 (r'[^+=*!<>&| -](\s=|=\s)[^= ]', "wrong whitespace around ="),
92 (r'[^+=*!<>&| -](\s=|=\s)[^= ]', "wrong whitespace around ="),
92 (r'raise Exception', "don't raise generic exceptions"),
93 (r'raise Exception', "don't raise generic exceptions"),
93 (r'ui\.(status|progress|write|note)\([\'\"]x', "unwrapped ui message"),
94 (r'ui\.(status|progress|write|note)\([\'\"]x', "unwrapped ui message"),
94 ]
95 ]
95
96
96 pyfilters = [
97 pyfilters = [
97 (r"""(''')(([^']|\\'|'{1,2}(?!'))*)'''""", repquote),
98 (r"""(''')(([^']|\\'|'{1,2}(?!'))*)'''""", repquote),
98 (r'''(""")(([^"]|\\"|"{1,2}(?!"))*)"""''', repquote),
99 (r'''(""")(([^"]|\\"|"{1,2}(?!"))*)"""''', repquote),
99 (r'''(?<!")(")(([^"\n]|\\")+)"(?!")''', repquote),
100 (r'''(?<!")(")(([^"\n]|\\")+)"(?!")''', repquote),
100 (r"""(?<!')(')(([^'\n]|\\')+)'(?!')""", repquote),
101 (r"""(?<!')(')(([^'\n]|\\')+)'(?!')""", repquote),
101 (r"( *)(#([^\n]*\S)?)", repcomment),
102 (r"( *)(#([^\n]*\S)?)", repcomment),
102 ]
103 ]
103
104
104 cpats = [
105 cpats = [
105 (r'//', "don't use //-style comments"),
106 (r'//', "don't use //-style comments"),
106 (r'^ ', "don't use spaces to indent"),
107 (r'^ ', "don't use spaces to indent"),
107 (r'\S\t', "don't use tabs except for indent"),
108 (r'\S\t', "don't use tabs except for indent"),
108 (r'(\S\s+|^\s+)\n', "trailing whitespace"),
109 (r'(\S\s+|^\s+)\n', "trailing whitespace"),
109 (r'.{85}', "line too long"),
110 (r'.{85}', "line too long"),
110 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
111 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
111 (r'return\(', "return is not a function"),
112 (r'return\(', "return is not a function"),
112 (r' ;', "no space before ;"),
113 (r' ;', "no space before ;"),
113 (r'\w+\* \w+', "use int *foo, not int* foo"),
114 (r'\w+\* \w+', "use int *foo, not int* foo"),
114 (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
115 (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
115 (r'\S+ (\+\+|--)', "use foo++, not foo ++"),
116 (r'\S+ (\+\+|--)', "use foo++, not foo ++"),
116 (r'\w,\w', "missing whitespace after ,"),
117 (r'\w,\w', "missing whitespace after ,"),
117 (r'\w[+/*]\w', "missing whitespace in expression"),
118 (r'\w[+/*]\w', "missing whitespace in expression"),
118 (r'^#\s+\w', "use #foo, not # foo"),
119 (r'^#\s+\w', "use #foo, not # foo"),
119 (r'[^\n]\Z', "no trailing newline"),
120 (r'[^\n]\Z', "no trailing newline"),
120 ]
121 ]
121
122
122 cfilters = [
123 cfilters = [
123 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
124 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
124 (r'''(?<!")(")(([^"]|\\")+"(?!"))''', repquote),
125 (r'''(?<!")(")(([^"]|\\")+"(?!"))''', repquote),
125 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
126 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
126 (r'(\()([^)]+\))', repcallspaces),
127 (r'(\()([^)]+\))', repcallspaces),
127 ]
128 ]
128
129
129 checks = [
130 checks = [
130 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
131 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
131 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
132 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
132 ('c', r'.*\.c$', cfilters, cpats),
133 ('c', r'.*\.c$', cfilters, cpats),
133 ]
134 ]
134
135
135 if len(sys.argv) == 1:
136 if len(sys.argv) == 1:
136 check = glob.glob("*")
137 check = glob.glob("*")
137 else:
138 else:
138 check = sys.argv[1:]
139 check = sys.argv[1:]
139
140
140 for f in check:
141 for f in check:
141 for name, match, filters, pats in checks:
142 for name, match, filters, pats in checks:
142 fc = 0
143 fc = 0
143 if not re.match(match, f):
144 if not re.match(match, f):
144 continue
145 continue
145 pre = post = open(f).read()
146 pre = post = open(f).read()
146 if "no-" + "check-code" in pre:
147 if "no-" + "check-code" in pre:
147 break
148 break
148 for p, r in filters:
149 for p, r in filters:
149 post = re.sub(p, r, post)
150 post = re.sub(p, r, post)
150 # print post # uncomment to show filtered version
151 # print post # uncomment to show filtered version
151 z = enumerate(zip(pre.splitlines(), post.splitlines(True)))
152 z = enumerate(zip(pre.splitlines(), post.splitlines(True)))
152 for n, l in z:
153 for n, l in z:
153 if "check-code" + "-ignore" in l[0]:
154 if "check-code" + "-ignore" in l[0]:
154 continue
155 continue
155 lc = 0
156 lc = 0
156 for p, msg in pats:
157 for p, msg in pats:
157 if re.search(p, l[1]):
158 if re.search(p, l[1]):
158 if not lc:
159 if not lc:
159 print "%s:%d:" % (f, n + 1)
160 print "%s:%d:" % (f, n + 1)
160 print " > %s" % l[0]
161 print " > %s" % l[0]
161 print " %s" % msg
162 print " %s" % msg
162 lc += 1
163 lc += 1
163 fc += 1
164 fc += 1
164 if fc == 15:
165 if fc == 15:
165 print " (too many errors, giving up)"
166 print " (too many errors, giving up)"
166 break
167 break
167 break
168 break
@@ -1,546 +1,545 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, error
17 from mercurial import hg, util, repair, merge, cmdutil, commands, error
18 from mercurial import extensions, ancestor, copies, patch
18 from mercurial import extensions, ancestor, copies, patch
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
26
27 def rebase(ui, repo, **opts):
27 def rebase(ui, repo, **opts):
28 """move changeset (and descendants) to a different branch
28 """move changeset (and descendants) to a different branch
29
29
30 Rebase uses repeated merging to graft changesets from one part of
30 Rebase uses repeated merging to graft changesets from one part of
31 history (the source) onto another (the destination). This can be
31 history (the source) onto another (the destination). This can be
32 useful for linearizing local changes relative to a master
32 useful for linearizing local changes relative to a master
33 development tree.
33 development tree.
34
34
35 If you don't specify a destination changeset (``-d/--dest``),
35 If you don't specify a destination changeset (``-d/--dest``),
36 rebase uses the tipmost head of the current named branch as the
36 rebase uses the tipmost head of the current named branch as the
37 destination. (The destination changeset is not modified by
37 destination. (The destination changeset is not modified by
38 rebasing, but new changesets are added as its descendants.)
38 rebasing, but new changesets are added as its descendants.)
39
39
40 You can specify which changesets to rebase in two ways: as a
40 You can specify which changesets to rebase in two ways: as a
41 \"source\" changeset or as a \"base\" changeset. Both are
41 "source" changeset or as a "base" changeset. Both are shorthand
42 shorthand for a topologically related set of changesets (the
42 for a topologically related set of changesets (the "source
43 \"source branch\"). If you specify source (``-s/--source``),
43 branch"). If you specify source (``-s/--source``), rebase will
44 rebase will rebase that changeset and all of its descendants onto
44 rebase that changeset and all of its descendants onto dest. If you
45 dest. If you specify base (``-b/--base``), rebase will select
45 specify base (``-b/--base``), rebase will select ancestors of base
46 ancestors of base back to but not including the common ancestor
46 back to but not including the common ancestor with dest. Thus,
47 with dest. Thus, ``-b`` is less precise but more convenient than
47 ``-b`` is less precise but more convenient than ``-s``: you can
48 ``-s``: you can specify any changeset in the source branch, and
48 specify any changeset in the source branch, and rebase will select
49 rebase will select the whole branch. If you specify neither ``-s``
49 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
50 nor ``-b``, rebase uses the parent of the working directory as the
50 uses the parent of the working directory as the base.
51 base.
52
51
53 By default, rebase recreates the changesets in the source branch
52 By default, rebase recreates the changesets in the source branch
54 as descendants of dest and then destroys the originals. Use
53 as descendants of dest and then destroys the originals. Use
55 ``--keep`` to preserve the original source changesets. Some
54 ``--keep`` to preserve the original source changesets. Some
56 changesets in the source branch (e.g. merges from the destination
55 changesets in the source branch (e.g. merges from the destination
57 branch) may be dropped if they no longer contribute any change.
56 branch) may be dropped if they no longer contribute any change.
58
57
59 One result of the rules for selecting the destination changeset
58 One result of the rules for selecting the destination changeset
60 and source branch is that, unlike ``merge``, rebase will do
59 and source branch is that, unlike ``merge``, rebase will do
61 nothing if you are at the latest (tipmost) head of a named branch
60 nothing if you are at the latest (tipmost) head of a named branch
62 with two heads. You need to explicitly specify source and/or
61 with two heads. You need to explicitly specify source and/or
63 destination (or ``update`` to the other head, if it's the head of
62 destination (or ``update`` to the other head, if it's the head of
64 the intended source branch).
63 the intended source branch).
65
64
66 If a rebase is interrupted to manually resolve a merge, it can be
65 If a rebase is interrupted to manually resolve a merge, it can be
67 continued with --continue/-c or aborted with --abort/-a.
66 continued with --continue/-c or aborted with --abort/-a.
68 """
67 """
69 originalwd = target = None
68 originalwd = target = None
70 external = nullrev
69 external = nullrev
71 state = {}
70 state = {}
72 skipped = set()
71 skipped = set()
73 targetancestors = set()
72 targetancestors = set()
74
73
75 lock = wlock = None
74 lock = wlock = None
76 try:
75 try:
77 lock = repo.lock()
76 lock = repo.lock()
78 wlock = repo.wlock()
77 wlock = repo.wlock()
79
78
80 # Validate input and define rebasing points
79 # Validate input and define rebasing points
81 destf = opts.get('dest', None)
80 destf = opts.get('dest', None)
82 srcf = opts.get('source', None)
81 srcf = opts.get('source', None)
83 basef = opts.get('base', None)
82 basef = opts.get('base', None)
84 contf = opts.get('continue')
83 contf = opts.get('continue')
85 abortf = opts.get('abort')
84 abortf = opts.get('abort')
86 collapsef = opts.get('collapse', False)
85 collapsef = opts.get('collapse', False)
87 extrafn = opts.get('extrafn')
86 extrafn = opts.get('extrafn')
88 keepf = opts.get('keep', False)
87 keepf = opts.get('keep', False)
89 keepbranchesf = opts.get('keepbranches', False)
88 keepbranchesf = opts.get('keepbranches', False)
90 detachf = opts.get('detach', False)
89 detachf = opts.get('detach', False)
91
90
92 if contf or abortf:
91 if contf or abortf:
93 if contf and abortf:
92 if contf and abortf:
94 raise error.ParseError('rebase',
93 raise error.ParseError('rebase',
95 _('cannot use both abort and continue'))
94 _('cannot use both abort and continue'))
96 if collapsef:
95 if collapsef:
97 raise error.ParseError(
96 raise error.ParseError(
98 'rebase', _('cannot use collapse with continue or abort'))
97 'rebase', _('cannot use collapse with continue or abort'))
99
98
100 if detachf:
99 if detachf:
101 raise error.ParseError(
100 raise error.ParseError(
102 'rebase', _('cannot use detach with continue or abort'))
101 'rebase', _('cannot use detach with continue or abort'))
103
102
104 if srcf or basef or destf:
103 if srcf or basef or destf:
105 raise error.ParseError('rebase',
104 raise error.ParseError('rebase',
106 _('abort and continue do not allow specifying revisions'))
105 _('abort and continue do not allow specifying revisions'))
107
106
108 (originalwd, target, state, collapsef, keepf,
107 (originalwd, target, state, collapsef, keepf,
109 keepbranchesf, external) = restorestatus(repo)
108 keepbranchesf, external) = restorestatus(repo)
110 if abortf:
109 if abortf:
111 abort(repo, originalwd, target, state)
110 abort(repo, originalwd, target, state)
112 return
111 return
113 else:
112 else:
114 if srcf and basef:
113 if srcf and basef:
115 raise error.ParseError('rebase', _('cannot specify both a '
114 raise error.ParseError('rebase', _('cannot specify both a '
116 'revision and a base'))
115 'revision and a base'))
117 if detachf:
116 if detachf:
118 if not srcf:
117 if not srcf:
119 raise error.ParseError(
118 raise error.ParseError(
120 'rebase', _('detach requires a revision to be specified'))
119 'rebase', _('detach requires a revision to be specified'))
121 if basef:
120 if basef:
122 raise error.ParseError(
121 raise error.ParseError(
123 'rebase', _('cannot specify a base with detach'))
122 'rebase', _('cannot specify a base with detach'))
124
123
125 cmdutil.bail_if_changed(repo)
124 cmdutil.bail_if_changed(repo)
126 result = buildstate(repo, destf, srcf, basef, detachf)
125 result = buildstate(repo, destf, srcf, basef, detachf)
127 if not result:
126 if not result:
128 # Empty state built, nothing to rebase
127 # Empty state built, nothing to rebase
129 ui.status(_('nothing to rebase\n'))
128 ui.status(_('nothing to rebase\n'))
130 return
129 return
131 else:
130 else:
132 originalwd, target, state = result
131 originalwd, target, state = result
133 if collapsef:
132 if collapsef:
134 targetancestors = set(repo.changelog.ancestors(target))
133 targetancestors = set(repo.changelog.ancestors(target))
135 external = checkexternal(repo, state, targetancestors)
134 external = checkexternal(repo, state, targetancestors)
136
135
137 if keepbranchesf:
136 if keepbranchesf:
138 if extrafn:
137 if extrafn:
139 raise error.ParseError(
138 raise error.ParseError(
140 'rebase', _('cannot use both keepbranches and extrafn'))
139 'rebase', _('cannot use both keepbranches and extrafn'))
141 def extrafn(ctx, extra):
140 def extrafn(ctx, extra):
142 extra['branch'] = ctx.branch()
141 extra['branch'] = ctx.branch()
143
142
144 # Rebase
143 # Rebase
145 if not targetancestors:
144 if not targetancestors:
146 targetancestors = set(repo.changelog.ancestors(target))
145 targetancestors = set(repo.changelog.ancestors(target))
147 targetancestors.add(target)
146 targetancestors.add(target)
148
147
149 for rev in sorted(state):
148 for rev in sorted(state):
150 if state[rev] == -1:
149 if state[rev] == -1:
151 ui.debug("rebasing %d:%s\n" % (rev, repo[rev]))
150 ui.debug("rebasing %d:%s\n" % (rev, repo[rev]))
152 storestatus(repo, originalwd, target, state, collapsef, keepf,
151 storestatus(repo, originalwd, target, state, collapsef, keepf,
153 keepbranchesf, external)
152 keepbranchesf, external)
154 p1, p2 = defineparents(repo, rev, target, state,
153 p1, p2 = defineparents(repo, rev, target, state,
155 targetancestors)
154 targetancestors)
156 if len(repo.parents()) == 2:
155 if len(repo.parents()) == 2:
157 repo.ui.debug('resuming interrupted rebase\n')
156 repo.ui.debug('resuming interrupted rebase\n')
158 else:
157 else:
159 stats = rebasenode(repo, rev, p1, p2, state)
158 stats = rebasenode(repo, rev, p1, p2, state)
160 if stats and stats[3] > 0:
159 if stats and stats[3] > 0:
161 raise util.Abort(_('fix unresolved conflicts with hg '
160 raise util.Abort(_('fix unresolved conflicts with hg '
162 'resolve then run hg rebase --continue'))
161 'resolve then run hg rebase --continue'))
163 updatedirstate(repo, rev, target, p2)
162 updatedirstate(repo, rev, target, p2)
164 if not collapsef:
163 if not collapsef:
165 extra = {'rebase_source': repo[rev].hex()}
164 extra = {'rebase_source': repo[rev].hex()}
166 if extrafn:
165 if extrafn:
167 extrafn(repo[rev], extra)
166 extrafn(repo[rev], extra)
168 newrev = concludenode(repo, rev, p1, p2, extra=extra)
167 newrev = concludenode(repo, rev, p1, p2, extra=extra)
169 else:
168 else:
170 # Skip commit if we are collapsing
169 # Skip commit if we are collapsing
171 repo.dirstate.setparents(repo[p1].node())
170 repo.dirstate.setparents(repo[p1].node())
172 newrev = None
171 newrev = None
173 # Update the state
172 # Update the state
174 if newrev is not None:
173 if newrev is not None:
175 state[rev] = repo[newrev].rev()
174 state[rev] = repo[newrev].rev()
176 else:
175 else:
177 if not collapsef:
176 if not collapsef:
178 ui.note(_('no changes, revision %d skipped\n') % rev)
177 ui.note(_('no changes, revision %d skipped\n') % rev)
179 ui.debug('next revision set to %s\n' % p1)
178 ui.debug('next revision set to %s\n' % p1)
180 skipped.add(rev)
179 skipped.add(rev)
181 state[rev] = p1
180 state[rev] = p1
182
181
183 ui.note(_('rebase merging completed\n'))
182 ui.note(_('rebase merging completed\n'))
184
183
185 if collapsef:
184 if collapsef:
186 p1, p2 = defineparents(repo, min(state), target,
185 p1, p2 = defineparents(repo, min(state), target,
187 state, targetancestors)
186 state, targetancestors)
188 commitmsg = 'Collapsed revision'
187 commitmsg = 'Collapsed revision'
189 for rebased in state:
188 for rebased in state:
190 if rebased not in skipped and state[rebased] != nullmerge:
189 if rebased not in skipped and state[rebased] != nullmerge:
191 commitmsg += '\n* %s' % repo[rebased].description()
190 commitmsg += '\n* %s' % repo[rebased].description()
192 commitmsg = ui.edit(commitmsg, repo.ui.username())
191 commitmsg = ui.edit(commitmsg, repo.ui.username())
193 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
192 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
194 extra=extrafn)
193 extra=extrafn)
195
194
196 if 'qtip' in repo.tags():
195 if 'qtip' in repo.tags():
197 updatemq(repo, state, skipped, **opts)
196 updatemq(repo, state, skipped, **opts)
198
197
199 if not keepf:
198 if not keepf:
200 # Remove no more useful revisions
199 # Remove no more useful revisions
201 rebased = [rev for rev in state if state[rev] != nullmerge]
200 rebased = [rev for rev in state if state[rev] != nullmerge]
202 if rebased:
201 if rebased:
203 if set(repo.changelog.descendants(min(rebased))) - set(state):
202 if set(repo.changelog.descendants(min(rebased))) - set(state):
204 ui.warn(_("warning: new changesets detected "
203 ui.warn(_("warning: new changesets detected "
205 "on source branch, not stripping\n"))
204 "on source branch, not stripping\n"))
206 else:
205 else:
207 repair.strip(ui, repo, repo[min(rebased)].node(), "strip")
206 repair.strip(ui, repo, repo[min(rebased)].node(), "strip")
208
207
209 clearstatus(repo)
208 clearstatus(repo)
210 ui.status(_("rebase completed\n"))
209 ui.status(_("rebase completed\n"))
211 if os.path.exists(repo.sjoin('undo')):
210 if os.path.exists(repo.sjoin('undo')):
212 util.unlink(repo.sjoin('undo'))
211 util.unlink(repo.sjoin('undo'))
213 if skipped:
212 if skipped:
214 ui.note(_("%d revisions have been skipped\n") % len(skipped))
213 ui.note(_("%d revisions have been skipped\n") % len(skipped))
215 finally:
214 finally:
216 release(lock, wlock)
215 release(lock, wlock)
217
216
218 def rebasemerge(repo, rev, first=False):
217 def rebasemerge(repo, rev, first=False):
219 'return the correct ancestor'
218 'return the correct ancestor'
220 oldancestor = ancestor.ancestor
219 oldancestor = ancestor.ancestor
221
220
222 def newancestor(a, b, pfunc):
221 def newancestor(a, b, pfunc):
223 if b == rev:
222 if b == rev:
224 return repo[rev].parents()[0].rev()
223 return repo[rev].parents()[0].rev()
225 return oldancestor(a, b, pfunc)
224 return oldancestor(a, b, pfunc)
226
225
227 if not first:
226 if not first:
228 ancestor.ancestor = newancestor
227 ancestor.ancestor = newancestor
229 else:
228 else:
230 repo.ui.debug("first revision, do not change ancestor\n")
229 repo.ui.debug("first revision, do not change ancestor\n")
231 try:
230 try:
232 stats = merge.update(repo, rev, True, True, False)
231 stats = merge.update(repo, rev, True, True, False)
233 return stats
232 return stats
234 finally:
233 finally:
235 ancestor.ancestor = oldancestor
234 ancestor.ancestor = oldancestor
236
235
237 def checkexternal(repo, state, targetancestors):
236 def checkexternal(repo, state, targetancestors):
238 """Check whether one or more external revisions need to be taken in
237 """Check whether one or more external revisions need to be taken in
239 consideration. In the latter case, abort.
238 consideration. In the latter case, abort.
240 """
239 """
241 external = nullrev
240 external = nullrev
242 source = min(state)
241 source = min(state)
243 for rev in state:
242 for rev in state:
244 if rev == source:
243 if rev == source:
245 continue
244 continue
246 # Check externals and fail if there are more than one
245 # Check externals and fail if there are more than one
247 for p in repo[rev].parents():
246 for p in repo[rev].parents():
248 if (p.rev() not in state
247 if (p.rev() not in state
249 and p.rev() not in targetancestors):
248 and p.rev() not in targetancestors):
250 if external != nullrev:
249 if external != nullrev:
251 raise util.Abort(_('unable to collapse, there is more '
250 raise util.Abort(_('unable to collapse, there is more '
252 'than one external parent'))
251 'than one external parent'))
253 external = p.rev()
252 external = p.rev()
254 return external
253 return external
255
254
256 def updatedirstate(repo, rev, p1, p2):
255 def updatedirstate(repo, rev, p1, p2):
257 """Keep track of renamed files in the revision that is going to be rebased
256 """Keep track of renamed files in the revision that is going to be rebased
258 """
257 """
259 # Here we simulate the copies and renames in the source changeset
258 # Here we simulate the copies and renames in the source changeset
260 cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True)
259 cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True)
261 m1 = repo[rev].manifest()
260 m1 = repo[rev].manifest()
262 m2 = repo[p1].manifest()
261 m2 = repo[p1].manifest()
263 for k, v in cop.iteritems():
262 for k, v in cop.iteritems():
264 if k in m1:
263 if k in m1:
265 if v in m1 or v in m2:
264 if v in m1 or v in m2:
266 repo.dirstate.copy(v, k)
265 repo.dirstate.copy(v, k)
267 if v in m2 and v not in m1:
266 if v in m2 and v not in m1:
268 repo.dirstate.remove(v)
267 repo.dirstate.remove(v)
269
268
270 def concludenode(repo, rev, p1, p2, commitmsg=None, extra=None):
269 def concludenode(repo, rev, p1, p2, commitmsg=None, extra=None):
271 'Commit the changes and store useful information in extra'
270 'Commit the changes and store useful information in extra'
272 try:
271 try:
273 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
272 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
274 if commitmsg is None:
273 if commitmsg is None:
275 commitmsg = repo[rev].description()
274 commitmsg = repo[rev].description()
276 if extra is None:
275 if extra is None:
277 extra = {}
276 extra = {}
278 # Commit might fail if unresolved files exist
277 # Commit might fail if unresolved files exist
279 newrev = repo.commit(text=commitmsg, user=repo[rev].user(),
278 newrev = repo.commit(text=commitmsg, user=repo[rev].user(),
280 date=repo[rev].date(), extra=extra)
279 date=repo[rev].date(), extra=extra)
281 repo.dirstate.setbranch(repo[newrev].branch())
280 repo.dirstate.setbranch(repo[newrev].branch())
282 return newrev
281 return newrev
283 except util.Abort:
282 except util.Abort:
284 # Invalidate the previous setparents
283 # Invalidate the previous setparents
285 repo.dirstate.invalidate()
284 repo.dirstate.invalidate()
286 raise
285 raise
287
286
288 def rebasenode(repo, rev, p1, p2, state):
287 def rebasenode(repo, rev, p1, p2, state):
289 'Rebase a single revision'
288 'Rebase a single revision'
290 # Merge phase
289 # Merge phase
291 # Update to target and merge it with local
290 # Update to target and merge it with local
292 if repo['.'].rev() != repo[p1].rev():
291 if repo['.'].rev() != repo[p1].rev():
293 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
292 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
294 merge.update(repo, p1, False, True, False)
293 merge.update(repo, p1, False, True, False)
295 else:
294 else:
296 repo.ui.debug(" already in target\n")
295 repo.ui.debug(" already in target\n")
297 repo.dirstate.write()
296 repo.dirstate.write()
298 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
297 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
299 first = repo[rev].rev() == repo[min(state)].rev()
298 first = repo[rev].rev() == repo[min(state)].rev()
300 stats = rebasemerge(repo, rev, first)
299 stats = rebasemerge(repo, rev, first)
301 return stats
300 return stats
302
301
303 def defineparents(repo, rev, target, state, targetancestors):
302 def defineparents(repo, rev, target, state, targetancestors):
304 'Return the new parent relationship of the revision that will be rebased'
303 'Return the new parent relationship of the revision that will be rebased'
305 parents = repo[rev].parents()
304 parents = repo[rev].parents()
306 p1 = p2 = nullrev
305 p1 = p2 = nullrev
307
306
308 P1n = parents[0].rev()
307 P1n = parents[0].rev()
309 if P1n in targetancestors:
308 if P1n in targetancestors:
310 p1 = target
309 p1 = target
311 elif P1n in state:
310 elif P1n in state:
312 if state[P1n] == nullmerge:
311 if state[P1n] == nullmerge:
313 p1 = target
312 p1 = target
314 else:
313 else:
315 p1 = state[P1n]
314 p1 = state[P1n]
316 else: # P1n external
315 else: # P1n external
317 p1 = target
316 p1 = target
318 p2 = P1n
317 p2 = P1n
319
318
320 if len(parents) == 2 and parents[1].rev() not in targetancestors:
319 if len(parents) == 2 and parents[1].rev() not in targetancestors:
321 P2n = parents[1].rev()
320 P2n = parents[1].rev()
322 # interesting second parent
321 # interesting second parent
323 if P2n in state:
322 if P2n in state:
324 if p1 == target: # P1n in targetancestors or external
323 if p1 == target: # P1n in targetancestors or external
325 p1 = state[P2n]
324 p1 = state[P2n]
326 else:
325 else:
327 p2 = state[P2n]
326 p2 = state[P2n]
328 else: # P2n external
327 else: # P2n external
329 if p2 != nullrev: # P1n external too => rev is a merged revision
328 if p2 != nullrev: # P1n external too => rev is a merged revision
330 raise util.Abort(_('cannot use revision %d as base, result '
329 raise util.Abort(_('cannot use revision %d as base, result '
331 'would have 3 parents') % rev)
330 'would have 3 parents') % rev)
332 p2 = P2n
331 p2 = P2n
333 repo.ui.debug(" future parents are %d and %d\n" %
332 repo.ui.debug(" future parents are %d and %d\n" %
334 (repo[p1].rev(), repo[p2].rev()))
333 (repo[p1].rev(), repo[p2].rev()))
335 return p1, p2
334 return p1, p2
336
335
337 def isagitpatch(repo, patchname):
336 def isagitpatch(repo, patchname):
338 'Return true if the given patch is in git format'
337 'Return true if the given patch is in git format'
339 mqpatch = os.path.join(repo.mq.path, patchname)
338 mqpatch = os.path.join(repo.mq.path, patchname)
340 for line in patch.linereader(file(mqpatch, 'rb')):
339 for line in patch.linereader(file(mqpatch, 'rb')):
341 if line.startswith('diff --git'):
340 if line.startswith('diff --git'):
342 return True
341 return True
343 return False
342 return False
344
343
345 def updatemq(repo, state, skipped, **opts):
344 def updatemq(repo, state, skipped, **opts):
346 'Update rebased mq patches - finalize and then import them'
345 'Update rebased mq patches - finalize and then import them'
347 mqrebase = {}
346 mqrebase = {}
348 for p in repo.mq.applied:
347 for p in repo.mq.applied:
349 if repo[p.rev].rev() in state:
348 if repo[p.rev].rev() in state:
350 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
349 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
351 (repo[p.rev].rev(), p.name))
350 (repo[p.rev].rev(), p.name))
352 mqrebase[repo[p.rev].rev()] = (p.name, isagitpatch(repo, p.name))
351 mqrebase[repo[p.rev].rev()] = (p.name, isagitpatch(repo, p.name))
353
352
354 if mqrebase:
353 if mqrebase:
355 repo.mq.finish(repo, mqrebase.keys())
354 repo.mq.finish(repo, mqrebase.keys())
356
355
357 # We must start import from the newest revision
356 # We must start import from the newest revision
358 for rev in sorted(mqrebase, reverse=True):
357 for rev in sorted(mqrebase, reverse=True):
359 if rev not in skipped:
358 if rev not in skipped:
360 repo.ui.debug('import mq patch %d (%s)\n'
359 repo.ui.debug('import mq patch %d (%s)\n'
361 % (state[rev], mqrebase[rev][0]))
360 % (state[rev], mqrebase[rev][0]))
362 repo.mq.qimport(repo, (), patchname=mqrebase[rev][0],
361 repo.mq.qimport(repo, (), patchname=mqrebase[rev][0],
363 git=mqrebase[rev][1],rev=[str(state[rev])])
362 git=mqrebase[rev][1],rev=[str(state[rev])])
364 repo.mq.save_dirty()
363 repo.mq.save_dirty()
365
364
366 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
365 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
367 external):
366 external):
368 'Store the current status to allow recovery'
367 'Store the current status to allow recovery'
369 f = repo.opener("rebasestate", "w")
368 f = repo.opener("rebasestate", "w")
370 f.write(repo[originalwd].hex() + '\n')
369 f.write(repo[originalwd].hex() + '\n')
371 f.write(repo[target].hex() + '\n')
370 f.write(repo[target].hex() + '\n')
372 f.write(repo[external].hex() + '\n')
371 f.write(repo[external].hex() + '\n')
373 f.write('%d\n' % int(collapse))
372 f.write('%d\n' % int(collapse))
374 f.write('%d\n' % int(keep))
373 f.write('%d\n' % int(keep))
375 f.write('%d\n' % int(keepbranches))
374 f.write('%d\n' % int(keepbranches))
376 for d, v in state.iteritems():
375 for d, v in state.iteritems():
377 oldrev = repo[d].hex()
376 oldrev = repo[d].hex()
378 newrev = repo[v].hex()
377 newrev = repo[v].hex()
379 f.write("%s:%s\n" % (oldrev, newrev))
378 f.write("%s:%s\n" % (oldrev, newrev))
380 f.close()
379 f.close()
381 repo.ui.debug('rebase status stored\n')
380 repo.ui.debug('rebase status stored\n')
382
381
383 def clearstatus(repo):
382 def clearstatus(repo):
384 'Remove the status files'
383 'Remove the status files'
385 if os.path.exists(repo.join("rebasestate")):
384 if os.path.exists(repo.join("rebasestate")):
386 util.unlink(repo.join("rebasestate"))
385 util.unlink(repo.join("rebasestate"))
387
386
388 def restorestatus(repo):
387 def restorestatus(repo):
389 'Restore a previously stored status'
388 'Restore a previously stored status'
390 try:
389 try:
391 target = None
390 target = None
392 collapse = False
391 collapse = False
393 external = nullrev
392 external = nullrev
394 state = {}
393 state = {}
395 f = repo.opener("rebasestate")
394 f = repo.opener("rebasestate")
396 for i, l in enumerate(f.read().splitlines()):
395 for i, l in enumerate(f.read().splitlines()):
397 if i == 0:
396 if i == 0:
398 originalwd = repo[l].rev()
397 originalwd = repo[l].rev()
399 elif i == 1:
398 elif i == 1:
400 target = repo[l].rev()
399 target = repo[l].rev()
401 elif i == 2:
400 elif i == 2:
402 external = repo[l].rev()
401 external = repo[l].rev()
403 elif i == 3:
402 elif i == 3:
404 collapse = bool(int(l))
403 collapse = bool(int(l))
405 elif i == 4:
404 elif i == 4:
406 keep = bool(int(l))
405 keep = bool(int(l))
407 elif i == 5:
406 elif i == 5:
408 keepbranches = bool(int(l))
407 keepbranches = bool(int(l))
409 else:
408 else:
410 oldrev, newrev = l.split(':')
409 oldrev, newrev = l.split(':')
411 state[repo[oldrev].rev()] = repo[newrev].rev()
410 state[repo[oldrev].rev()] = repo[newrev].rev()
412 repo.ui.debug('rebase status resumed\n')
411 repo.ui.debug('rebase status resumed\n')
413 return originalwd, target, state, collapse, keep, keepbranches, external
412 return originalwd, target, state, collapse, keep, keepbranches, external
414 except IOError, err:
413 except IOError, err:
415 if err.errno != errno.ENOENT:
414 if err.errno != errno.ENOENT:
416 raise
415 raise
417 raise util.Abort(_('no rebase in progress'))
416 raise util.Abort(_('no rebase in progress'))
418
417
419 def abort(repo, originalwd, target, state):
418 def abort(repo, originalwd, target, state):
420 'Restore the repository to its original state'
419 'Restore the repository to its original state'
421 if set(repo.changelog.descendants(target)) - set(state.values()):
420 if set(repo.changelog.descendants(target)) - set(state.values()):
422 repo.ui.warn(_("warning: new changesets detected on target branch, "
421 repo.ui.warn(_("warning: new changesets detected on target branch, "
423 "not stripping\n"))
422 "not stripping\n"))
424 else:
423 else:
425 # Strip from the first rebased revision
424 # Strip from the first rebased revision
426 merge.update(repo, repo[originalwd].rev(), False, True, False)
425 merge.update(repo, repo[originalwd].rev(), False, True, False)
427 rebased = filter(lambda x: x > -1, state.values())
426 rebased = filter(lambda x: x > -1, state.values())
428 if rebased:
427 if rebased:
429 strippoint = min(rebased)
428 strippoint = min(rebased)
430 repair.strip(repo.ui, repo, repo[strippoint].node(), "strip")
429 repair.strip(repo.ui, repo, repo[strippoint].node(), "strip")
431 clearstatus(repo)
430 clearstatus(repo)
432 repo.ui.status(_('rebase aborted\n'))
431 repo.ui.status(_('rebase aborted\n'))
433
432
434 def buildstate(repo, dest, src, base, detach):
433 def buildstate(repo, dest, src, base, detach):
435 'Define which revisions are going to be rebased and where'
434 'Define which revisions are going to be rebased and where'
436 targetancestors = set()
435 targetancestors = set()
437 detachset = set()
436 detachset = set()
438
437
439 if not dest:
438 if not dest:
440 # Destination defaults to the latest revision in the current branch
439 # Destination defaults to the latest revision in the current branch
441 branch = repo[None].branch()
440 branch = repo[None].branch()
442 dest = repo[branch].rev()
441 dest = repo[branch].rev()
443 else:
442 else:
444 if 'qtip' in repo.tags() and (repo[dest].hex() in
443 if 'qtip' in repo.tags() and (repo[dest].hex() in
445 [s.rev for s in repo.mq.applied]):
444 [s.rev for s in repo.mq.applied]):
446 raise util.Abort(_('cannot rebase onto an applied mq patch'))
445 raise util.Abort(_('cannot rebase onto an applied mq patch'))
447 dest = repo[dest].rev()
446 dest = repo[dest].rev()
448
447
449 if src:
448 if src:
450 commonbase = repo[src].ancestor(repo[dest])
449 commonbase = repo[src].ancestor(repo[dest])
451 if commonbase == repo[src]:
450 if commonbase == repo[src]:
452 raise util.Abort(_('source is ancestor of destination'))
451 raise util.Abort(_('source is ancestor of destination'))
453 if commonbase == repo[dest]:
452 if commonbase == repo[dest]:
454 raise util.Abort(_('source is descendant of destination'))
453 raise util.Abort(_('source is descendant of destination'))
455 source = repo[src].rev()
454 source = repo[src].rev()
456 if detach:
455 if detach:
457 # We need to keep track of source's ancestors up to the common base
456 # We need to keep track of source's ancestors up to the common base
458 srcancestors = set(repo.changelog.ancestors(source))
457 srcancestors = set(repo.changelog.ancestors(source))
459 baseancestors = set(repo.changelog.ancestors(commonbase.rev()))
458 baseancestors = set(repo.changelog.ancestors(commonbase.rev()))
460 detachset = srcancestors - baseancestors
459 detachset = srcancestors - baseancestors
461 detachset.remove(commonbase.rev())
460 detachset.remove(commonbase.rev())
462 else:
461 else:
463 if base:
462 if base:
464 cwd = repo[base].rev()
463 cwd = repo[base].rev()
465 else:
464 else:
466 cwd = repo['.'].rev()
465 cwd = repo['.'].rev()
467
466
468 if cwd == dest:
467 if cwd == dest:
469 repo.ui.debug('source and destination are the same\n')
468 repo.ui.debug('source and destination are the same\n')
470 return None
469 return None
471
470
472 targetancestors = set(repo.changelog.ancestors(dest))
471 targetancestors = set(repo.changelog.ancestors(dest))
473 if cwd in targetancestors:
472 if cwd in targetancestors:
474 repo.ui.debug('source is ancestor of destination\n')
473 repo.ui.debug('source is ancestor of destination\n')
475 return None
474 return None
476
475
477 cwdancestors = set(repo.changelog.ancestors(cwd))
476 cwdancestors = set(repo.changelog.ancestors(cwd))
478 if dest in cwdancestors:
477 if dest in cwdancestors:
479 repo.ui.debug('source is descendant of destination\n')
478 repo.ui.debug('source is descendant of destination\n')
480 return None
479 return None
481
480
482 cwdancestors.add(cwd)
481 cwdancestors.add(cwd)
483 rebasingbranch = cwdancestors - targetancestors
482 rebasingbranch = cwdancestors - targetancestors
484 source = min(rebasingbranch)
483 source = min(rebasingbranch)
485
484
486 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
485 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
487 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
486 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
488 state.update(dict.fromkeys(detachset, nullmerge))
487 state.update(dict.fromkeys(detachset, nullmerge))
489 state[source] = nullrev
488 state[source] = nullrev
490 return repo['.'].rev(), repo[dest].rev(), state
489 return repo['.'].rev(), repo[dest].rev(), state
491
490
492 def pullrebase(orig, ui, repo, *args, **opts):
491 def pullrebase(orig, ui, repo, *args, **opts):
493 'Call rebase after pull if the latter has been invoked with --rebase'
492 'Call rebase after pull if the latter has been invoked with --rebase'
494 if opts.get('rebase'):
493 if opts.get('rebase'):
495 if opts.get('update'):
494 if opts.get('update'):
496 del opts['update']
495 del opts['update']
497 ui.debug('--update and --rebase are not compatible, ignoring '
496 ui.debug('--update and --rebase are not compatible, ignoring '
498 'the update flag\n')
497 'the update flag\n')
499
498
500 cmdutil.bail_if_changed(repo)
499 cmdutil.bail_if_changed(repo)
501 revsprepull = len(repo)
500 revsprepull = len(repo)
502 origpostincoming = commands.postincoming
501 origpostincoming = commands.postincoming
503 def _dummy(*args, **kwargs):
502 def _dummy(*args, **kwargs):
504 pass
503 pass
505 commands.postincoming = _dummy
504 commands.postincoming = _dummy
506 try:
505 try:
507 orig(ui, repo, *args, **opts)
506 orig(ui, repo, *args, **opts)
508 finally:
507 finally:
509 commands.postincoming = origpostincoming
508 commands.postincoming = origpostincoming
510 revspostpull = len(repo)
509 revspostpull = len(repo)
511 if revspostpull > revsprepull:
510 if revspostpull > revsprepull:
512 rebase(ui, repo, **opts)
511 rebase(ui, repo, **opts)
513 branch = repo[None].branch()
512 branch = repo[None].branch()
514 dest = repo[branch].rev()
513 dest = repo[branch].rev()
515 if dest != repo['.'].rev():
514 if dest != repo['.'].rev():
516 # there was nothing to rebase we force an update
515 # there was nothing to rebase we force an update
517 hg.update(repo, dest)
516 hg.update(repo, dest)
518 else:
517 else:
519 orig(ui, repo, *args, **opts)
518 orig(ui, repo, *args, **opts)
520
519
521 def uisetup(ui):
520 def uisetup(ui):
522 'Replace pull with a decorator to provide --rebase option'
521 'Replace pull with a decorator to provide --rebase option'
523 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
522 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
524 entry[1].append(('', 'rebase', None,
523 entry[1].append(('', 'rebase', None,
525 _("rebase working directory to branch head"))
524 _("rebase working directory to branch head"))
526 )
525 )
527
526
528 cmdtable = {
527 cmdtable = {
529 "rebase":
528 "rebase":
530 (rebase,
529 (rebase,
531 [
530 [
532 ('s', 'source', '', _('rebase from the specified changeset')),
531 ('s', 'source', '', _('rebase from the specified changeset')),
533 ('b', 'base', '', _('rebase from the base of the specified changeset '
532 ('b', 'base', '', _('rebase from the base of the specified changeset '
534 '(up to greatest common ancestor of base and dest)')),
533 '(up to greatest common ancestor of base and dest)')),
535 ('d', 'dest', '', _('rebase onto the specified changeset')),
534 ('d', 'dest', '', _('rebase onto the specified changeset')),
536 ('', 'collapse', False, _('collapse the rebased changesets')),
535 ('', 'collapse', False, _('collapse the rebased changesets')),
537 ('', 'keep', False, _('keep original changesets')),
536 ('', 'keep', False, _('keep original changesets')),
538 ('', 'keepbranches', False, _('keep original branch names')),
537 ('', 'keepbranches', False, _('keep original branch names')),
539 ('', 'detach', False, _('force detaching of source from its original '
538 ('', 'detach', False, _('force detaching of source from its original '
540 'branch')),
539 'branch')),
541 ('c', 'continue', False, _('continue an interrupted rebase')),
540 ('c', 'continue', False, _('continue an interrupted rebase')),
542 ('a', 'abort', False, _('abort an interrupted rebase'))] +
541 ('a', 'abort', False, _('abort an interrupted rebase'))] +
543 templateopts,
542 templateopts,
544 _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
543 _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
545 'hg rebase {-a|-c}'))
544 'hg rebase {-a|-c}'))
546 }
545 }
@@ -1,60 +1,60 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 hg init
3 hg init
4 echo a > a
4 echo a > a
5 hg commit -A -ma
5 hg commit -A -ma
6
6
7 echo b >> a
7 echo b >> a
8 hg commit -mb
8 hg commit -mb
9
9
10 echo c >> a
10 echo c >> a
11 hg commit -mc
11 hg commit -mc
12
12
13 hg up 1
13 hg up 1
14 echo d >> a
14 echo d >> a
15 hg commit -md
15 hg commit -md
16
16
17 hg up 1
17 hg up 1
18 echo e >> a
18 echo e >> a
19 hg commit -me
19 hg commit -me
20
20
21 hg up 1
21 hg up 1
22 echo % should fail because not at a head
22 echo % should fail because not at a head
23 hg merge
23 hg merge
24
24
25 hg up
25 hg up
26 echo % should fail because \> 2 heads
26 echo % should fail because \> 2 heads
27 export HGMERGE=internal:other
27 HGMERGE=internal:other; export HGMERGE
28 hg merge
28 hg merge
29
29
30 echo % should succeed
30 echo % should succeed
31 hg merge 2
31 hg merge 2
32 hg commit -mm1
32 hg commit -mm1
33
33
34 echo % should succeed - 2 heads
34 echo % should succeed - 2 heads
35 hg merge -P
35 hg merge -P
36 hg merge
36 hg merge
37 hg commit -mm2
37 hg commit -mm2
38
38
39 echo % should fail because at tip
39 echo % should fail because at tip
40 hg merge
40 hg merge
41
41
42 hg up 0
42 hg up 0
43 echo % should fail because 1 head
43 echo % should fail because 1 head
44 hg merge
44 hg merge
45
45
46 hg up 3
46 hg up 3
47 echo f >> a
47 echo f >> a
48 hg branch foobranch
48 hg branch foobranch
49 hg commit -mf
49 hg commit -mf
50 echo % should fail because merge with other branch
50 echo % should fail because merge with other branch
51 hg merge
51 hg merge
52
52
53 # Test for issue2043: ensure that 'merge -P' shows ancestors of 6 that
53 # Test for issue2043: ensure that 'merge -P' shows ancestors of 6 that
54 # are not ancestors of 7, regardless of where their least common
54 # are not ancestors of 7, regardless of where their least common
55 # ancestor is.
55 # ancestor is.
56 echo % merge preview not affected by common ancestor
56 echo % merge preview not affected by common ancestor
57 hg up -q 7
57 hg up -q 7
58 hg merge -q -P 6 # expect: 2, 4, 5, 6
58 hg merge -q -P 6 # expect: 2, 4, 5, 6
59
59
60 true
60 true
General Comments 0
You need to be logged in to leave comments. Login now