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