Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Jelmer Vernooij | ||||
Approved revision: | no longer in the source branch. | ||||
Merge reported by: | The Breezy Bot | ||||
Merged at revision: | not available | ||||
Proposed branch: | lp:~jelmer/brz/rebase | ||||
Merge into: | lp:brz | ||||
Diff against target: |
3072 lines (+3006/-0) 13 files modified
breezy/plugins/rewrite/README (+59/-0) breezy/plugins/rewrite/__init__.py (+76/-0) breezy/plugins/rewrite/commands.py (+529/-0) breezy/plugins/rewrite/maptree.py (+124/-0) breezy/plugins/rewrite/pseudonyms.py (+211/-0) breezy/plugins/rewrite/rebase.py (+604/-0) breezy/plugins/rewrite/tests/__init__.py (+29/-0) breezy/plugins/rewrite/tests/test_blackbox.py (+363/-0) breezy/plugins/rewrite/tests/test_maptree.py (+81/-0) breezy/plugins/rewrite/tests/test_pseudonyms.py (+39/-0) breezy/plugins/rewrite/tests/test_rebase.py (+669/-0) breezy/plugins/rewrite/tests/test_upgrade.py (+32/-0) breezy/plugins/rewrite/upgrade.py (+190/-0) |
||||
To merge this branch: | bzr merge lp:~jelmer/brz/rebase | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jelmer Vernooij | Approve | ||
Review via email:
|
Commit message
Port the rewrite plugin to breezy.
Description of the change
Port the rewrite plugin to breezy.
To post a comment you must log in.
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Jelmer Vernooij (jelmer) : | # |
review:
Approve
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
The Breezy Bot (the-breezy-bot) wrote : | # |
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
The Breezy Bot (the-breezy-bot) wrote : | # |
Running landing tests failed
https:/
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
The Breezy Bot (the-breezy-bot) wrote : | # |
Running landing tests failed
https:/
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added directory 'breezy/plugins/rewrite' |
2 | === added file 'breezy/plugins/rewrite/README' |
3 | --- breezy/plugins/rewrite/README 1970-01-01 00:00:00 +0000 |
4 | +++ breezy/plugins/rewrite/README 2020-01-11 23:46:53 +0000 |
5 | @@ -0,0 +1,59 @@ |
6 | +This plugin provides various commands for rewriting Bazaar revisions in |
7 | +an automated fashion. |
8 | + |
9 | +It includes a 'bzr rebase' command, in the same fashion of the |
10 | +famous ``git rebase`` command. |
11 | + |
12 | +========== |
13 | +How to use |
14 | +========== |
15 | +Simply run ``bzr rebase``, optionally specifying an upstream branch. If no |
16 | +branch is specified, it will use the current parent branch (which is usually |
17 | +what you want). |
18 | + |
19 | +If the rebase fails halfway through, perhaps because of conflicts |
20 | +replaying a revision, it will abort. When it's aborted, you can either resolve |
21 | +the conflicts (using "bzr resolve") and run ``bzr rebase-continue`` or |
22 | +abort the rebase and undo all changes it's done using ``bzr rebase-abort``. |
23 | +The current state of the rebase can be viewed with the ``bzr rebase-todo`` |
24 | +command. See "bzr help rebase" for more details. |
25 | + |
26 | +In the future, I hope it can also support rebasing on top of an unrelated |
27 | +branch. |
28 | + |
29 | +============ |
30 | +How it works |
31 | +============ |
32 | +The plugin will start off by writing a plan for the rebase, which it |
33 | +will write to .bzr/checkout/rebase-state. If the rebase is interrupted |
34 | +(conflicts that have to be resolved by the user) and |
35 | +needs to be continued or aborted, it will read this file to see what needs |
36 | +(still) needs to be done. |
37 | + |
38 | +The rebase-state file contains the following information: |
39 | + * last-revision-info when the rebase was started |
40 | + * map of revisions that need to be replaced. In simple situations, |
41 | + this would contain the revision on top of which the rebase is taking |
42 | + place and the revisions that are only on the branch that is being |
43 | + rebased. |
44 | + |
45 | + The map is from old revid to a tuple with new revid and new parents. |
46 | + |
47 | + For example, in the following scenario: |
48 | + |
49 | + A -> B -> C -> D main |
50 | + \ -> E -> F next |
51 | + |
52 | + Where next would be rebased on top of main, the replace map would look |
53 | + something like this: |
54 | + |
55 | + E -> (E', [D]) |
56 | + F -> (F', [E']) |
57 | + |
58 | +The rebase plan contains the newly generated revision ids so that it is |
59 | +not necessary to update it after each revision that has been written |
60 | +because the new generated revision ids would have to be known when rewriting |
61 | +their children. |
62 | + |
63 | +The 'rebase-of' revision property of newly created revisions |
64 | +will be set to the revision id of the revision they are a rewrite of. |
65 | |
66 | === added file 'breezy/plugins/rewrite/__init__.py' |
67 | --- breezy/plugins/rewrite/__init__.py 1970-01-01 00:00:00 +0000 |
68 | +++ breezy/plugins/rewrite/__init__.py 2020-01-11 23:46:53 +0000 |
69 | @@ -0,0 +1,76 @@ |
70 | +# Copyright (C) 2007 by Jelmer Vernooij <jelmer@samba.org> |
71 | +# |
72 | +# This program is free software; you can redistribute it and/or modify |
73 | +# it under the terms of the GNU General Public License as published by |
74 | +# the Free Software Foundation; either version 2 of the License, or |
75 | +# (at your option) any later version. |
76 | +# |
77 | +# This program is distributed in the hope that it will be useful, |
78 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
79 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
80 | +# GNU General Public License for more details. |
81 | +# |
82 | +# You should have received a copy of the GNU General Public License |
83 | +# along with this program; if not, write to the Free Software |
84 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
85 | +"""Rebase support. |
86 | + |
87 | +The Bazaar rebase plugin adds support for rebasing branches to Bazaar. |
88 | +It adds the command 'rebase' to Bazaar. When conflicts occur when replaying |
89 | +patches, the user can resolve the conflict and continue the rebase using the |
90 | +'rebase-continue' command or abort using the 'rebase-abort' command. |
91 | +""" |
92 | + |
93 | +from __future__ import absolute_import |
94 | + |
95 | +from ... import errors |
96 | +from ...commands import plugin_cmds |
97 | + |
98 | +from ...bzr.bzrdir import BzrFormat |
99 | + |
100 | +BzrFormat.register_feature(b"rebase-v1") |
101 | + |
102 | +from ...i18n import load_plugin_translations |
103 | +translation = load_plugin_translations("bzr-rewrite") |
104 | +gettext = translation.gettext |
105 | + |
106 | +for cmd in ['rebase', 'rebase_abort', 'rebase_continue', 'rebase_todo', |
107 | + 'replay', 'pseudonyms', 'rebase_foreign']: |
108 | + plugin_cmds.register_lazy("cmd_%s" % cmd, [], __name__ + ".commands") |
109 | + |
110 | + |
111 | +def show_rebase_summary(params): |
112 | + if getattr(params.new_tree, "_format", None) is None: |
113 | + return |
114 | + features = getattr(params.new_tree._format, "features", None) |
115 | + if features is None: |
116 | + return |
117 | + if "rebase-v1" not in features: |
118 | + return |
119 | + from .rebase import ( |
120 | + RebaseState1, |
121 | + rebase_todo, |
122 | + ) |
123 | + state = RebaseState1(params.new_tree) |
124 | + try: |
125 | + replace_map = state.read_plan()[1] |
126 | + except errors.NoSuchFile: |
127 | + return |
128 | + todo = list(rebase_todo(params.new_tree.branch.repository, replace_map)) |
129 | + params.to_file.write('Rebase in progress. (%d revisions left)\n' % len(todo)) |
130 | + |
131 | + |
132 | +from ...hooks import install_lazy_named_hook |
133 | + |
134 | +install_lazy_named_hook( |
135 | + 'breezy.status', 'hooks', 'post_status', |
136 | + show_rebase_summary, 'rewrite status') |
137 | + |
138 | + |
139 | +def load_tests(loader, basic_tests, pattern): |
140 | + testmod_names = [ |
141 | + 'tests', |
142 | + ] |
143 | + basic_tests.addTest(loader.loadTestsFromModuleNames( |
144 | + ["%s.%s" % (__name__, tmn) for tmn in testmod_names])) |
145 | + return basic_tests |
146 | |
147 | === added file 'breezy/plugins/rewrite/commands.py' |
148 | --- breezy/plugins/rewrite/commands.py 1970-01-01 00:00:00 +0000 |
149 | +++ breezy/plugins/rewrite/commands.py 2020-01-11 23:46:53 +0000 |
150 | @@ -0,0 +1,529 @@ |
151 | +# Copyright (C) 2007 by Jelmer Vernooij <jelmer@samba.org> |
152 | +# |
153 | +# This program is free software; you can redistribute it and/or modify |
154 | +# it under the terms of the GNU General Public License as published by |
155 | +# the Free Software Foundation; either version 2 of the License, or |
156 | +# (at your option) any later version. |
157 | +# |
158 | +# This program is distributed in the hope that it will be useful, |
159 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
160 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
161 | +# GNU General Public License for more details. |
162 | +# |
163 | +# You should have received a copy of the GNU General Public License |
164 | +# along with this program; if not, write to the Free Software |
165 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
166 | + |
167 | +"""Bazaar command-line subcommands.""" |
168 | + |
169 | +from __future__ import absolute_import |
170 | + |
171 | +from ...commands import ( |
172 | + Command, |
173 | + display_command, |
174 | + ) |
175 | +from ...errors import ( |
176 | + BzrCommandError, |
177 | + ConflictsInTree, |
178 | + NoSuchFile, |
179 | + NoWorkingTree, |
180 | + UncommittedChanges, |
181 | + ) |
182 | +from ...option import ( |
183 | + Option, |
184 | + ) |
185 | +from ...trace import ( |
186 | + note, |
187 | + ) |
188 | + |
189 | +from ...i18n import gettext |
190 | + |
191 | + |
192 | +def finish_rebase(state, wt, replace_map, replayer): |
193 | + from .rebase import ( |
194 | + rebase, |
195 | + ) |
196 | + try: |
197 | + # Start executing plan from current Branch.last_revision() |
198 | + rebase(wt.branch.repository, replace_map, replayer) |
199 | + except ConflictsInTree: |
200 | + raise BzrCommandError(gettext( |
201 | + "A conflict occurred replaying a commit." |
202 | + " Resolve the conflict and run 'brz rebase-continue' or " |
203 | + "run 'brz rebase-abort'.")) |
204 | + # Remove plan file |
205 | + state.remove_plan() |
206 | + |
207 | + |
208 | +class cmd_rebase(Command): |
209 | + """Re-base a branch. |
210 | + |
211 | + Rebasing is the process of taking a branch and modifying the history so |
212 | + that it appears to start from a different point. This can be useful |
213 | + to clean up the history before submitting your changes. The tree at the |
214 | + end of the process will be the same as if you had merged the other branch, |
215 | + but the history will be different. |
216 | + |
217 | + The command takes the location of another branch on to which the branch in |
218 | + the specified directory (by default, the current working directory) |
219 | + will be rebased. If a branch is not specified then the parent branch |
220 | + is used, and this is usually the desired result. |
221 | + |
222 | + The first step identifies the revisions that are in the current branch that |
223 | + are not in the parent branch. The current branch is then set to be at the |
224 | + same revision as the target branch, and each revision is replayed on top |
225 | + of the branch. At the end of the process it will appear as though your |
226 | + current branch was branched off the current last revision of the target. |
227 | + |
228 | + Each revision that is replayed may cause conflicts in the tree. If this |
229 | + happens the command will stop and allow you to fix them up. Resolve the |
230 | + commits as you would for a merge, and then run 'brz resolve' to marked |
231 | + them as resolved. Once you have resolved all the conflicts you should |
232 | + run 'brz rebase-continue' to continue the rebase operation. |
233 | + |
234 | + If conflicts are encountered and you decide that you do not wish to continue |
235 | + you can run 'brz rebase-abort'. |
236 | + |
237 | + The '--onto' option allows you to specify a different revision in the |
238 | + target branch to start at when replaying the revisions. This means that |
239 | + you can change the point at which the current branch will appear to be |
240 | + branched from when the operation completes. |
241 | + """ |
242 | + takes_args = ['upstream_location?'] |
243 | + takes_options = [ |
244 | + 'revision', 'merge-type', 'verbose', |
245 | + Option( |
246 | + 'dry-run', |
247 | + help="Show what would be done, but don't actually do anything."), |
248 | + Option( |
249 | + 'always-rebase-merges', |
250 | + help="Don't skip revisions that merge already present revisions."), |
251 | + Option( |
252 | + 'pending-merges', |
253 | + help="Rebase pending merges onto local branch."), |
254 | + Option( |
255 | + 'onto', help='Different revision to replay onto.', |
256 | + type=str), |
257 | + Option( |
258 | + 'directory', |
259 | + short_name='d', |
260 | + help="Branch to replay onto, rather than the one containing the working directory.", |
261 | + type=str) |
262 | + ] |
263 | + |
264 | + @display_command |
265 | + def run(self, upstream_location=None, onto=None, revision=None, |
266 | + merge_type=None, verbose=False, dry_run=False, |
267 | + always_rebase_merges=False, pending_merges=False, |
268 | + directory="."): |
269 | + from ...branch import Branch |
270 | + from ...revisionspec import RevisionSpec |
271 | + from ...workingtree import WorkingTree |
272 | + from .rebase import ( |
273 | + generate_simple_plan, |
274 | + rebase, |
275 | + RebaseState1, |
276 | + WorkingTreeRevisionRewriter, |
277 | + regenerate_default_revid, |
278 | + rebase_todo, |
279 | + ) |
280 | + if revision is not None and pending_merges: |
281 | + raise BzrCommandError(gettext( |
282 | + "--revision and --pending-merges are mutually exclusive")) |
283 | + |
284 | + wt = WorkingTree.open_containing(directory)[0] |
285 | + wt.lock_write() |
286 | + try: |
287 | + state = RebaseState1(wt) |
288 | + if upstream_location is None: |
289 | + if pending_merges: |
290 | + upstream_location = directory |
291 | + else: |
292 | + upstream_location = wt.branch.get_parent() |
293 | + if upstream_location is None: |
294 | + raise BzrCommandError(gettext("No upstream branch specified.")) |
295 | + note(gettext("Rebasing on %s"), upstream_location) |
296 | + upstream = Branch.open_containing(upstream_location)[0] |
297 | + upstream_repository = upstream.repository |
298 | + upstream_revision = upstream.last_revision() |
299 | + # Abort if there already is a plan file |
300 | + if state.has_plan(): |
301 | + raise BzrCommandError(gettext( |
302 | + "A rebase operation was interrupted. " |
303 | + "Continue using 'brz rebase-continue' or abort using 'brz " |
304 | + "rebase-abort'")) |
305 | + |
306 | + start_revid = None |
307 | + stop_revid = None |
308 | + if revision is not None: |
309 | + if len(revision) == 1: |
310 | + if revision[0] is not None: |
311 | + stop_revid = revision[0].as_revision_id(wt.branch) |
312 | + elif len(revision) == 2: |
313 | + if revision[0] is not None: |
314 | + start_revid = revision[0].as_revision_id(wt.branch) |
315 | + if revision[1] is not None: |
316 | + stop_revid = revision[1].as_revision_id(wt.branch) |
317 | + else: |
318 | + raise BzrCommandError(gettext( |
319 | + "--revision takes only one or two arguments")) |
320 | + |
321 | + if pending_merges: |
322 | + wt_parents = wt.get_parent_ids() |
323 | + if len(wt_parents) in (0, 1): |
324 | + raise BzrCommandError(gettext("No pending merges present.")) |
325 | + elif len(wt_parents) > 2: |
326 | + raise BzrCommandError(gettext( |
327 | + "Rebasing more than one pending merge not supported")) |
328 | + stop_revid = wt_parents[1] |
329 | + assert stop_revid is not None, "stop revid invalid" |
330 | + |
331 | + # Check for changes in the working tree. |
332 | + if (not pending_merges and |
333 | + wt.basis_tree().changes_from(wt).has_changed()): |
334 | + raise UncommittedChanges(wt) |
335 | + |
336 | + # Pull required revisions |
337 | + wt.branch.repository.fetch(upstream_repository, upstream_revision) |
338 | + if onto is None: |
339 | + onto = upstream.last_revision() |
340 | + else: |
341 | + rev_spec = RevisionSpec.from_string(onto) |
342 | + onto = rev_spec.as_revision_id(upstream) |
343 | + |
344 | + wt.branch.repository.fetch(upstream_repository, revision_id=onto) |
345 | + |
346 | + if stop_revid is None: |
347 | + stop_revid = wt.branch.last_revision() |
348 | + repo_graph = wt.branch.repository.get_graph() |
349 | + our_new, onto_unique = repo_graph.find_difference(stop_revid, onto) |
350 | + |
351 | + if start_revid is None: |
352 | + if not onto_unique: |
353 | + self.outf.write(gettext("No revisions to rebase.\n")) |
354 | + return |
355 | + if not our_new: |
356 | + self.outf.write(gettext( |
357 | + "Base branch is descendant of current " |
358 | + "branch. Pulling instead.\n")) |
359 | + if not dry_run: |
360 | + wt.pull(upstream, stop_revision=onto) |
361 | + return |
362 | + # else: include extra revisions needed to make start_revid mean |
363 | + # something. |
364 | + |
365 | + # Create plan |
366 | + replace_map = generate_simple_plan( |
367 | + our_new, start_revid, stop_revid, onto, repo_graph, |
368 | + lambda revid, ps: regenerate_default_revid( |
369 | + wt.branch.repository, revid), |
370 | + not always_rebase_merges) |
371 | + |
372 | + if verbose or dry_run: |
373 | + todo = list(rebase_todo(wt.branch.repository, replace_map)) |
374 | + note(gettext('%d revisions will be rebased:') % len(todo)) |
375 | + for revid in todo: |
376 | + note("%s" % revid) |
377 | + |
378 | + if not dry_run: |
379 | + # Write plan file |
380 | + state.write_plan(replace_map) |
381 | + |
382 | + replayer = WorkingTreeRevisionRewriter(wt, state, merge_type=merge_type) |
383 | + |
384 | + finish_rebase(state, wt, replace_map, replayer) |
385 | + finally: |
386 | + wt.unlock() |
387 | + |
388 | + |
389 | +class cmd_rebase_abort(Command): |
390 | + """Abort an interrupted rebase.""" |
391 | + |
392 | + takes_options = [ |
393 | + Option( |
394 | + 'directory', |
395 | + short_name='d', |
396 | + help="Branch to replay onto, rather than the one containing the working directory.", |
397 | + type=str)] |
398 | + |
399 | + @display_command |
400 | + def run(self, directory="."): |
401 | + from .rebase import ( |
402 | + RebaseState1, |
403 | + complete_revert, |
404 | + ) |
405 | + from ...workingtree import WorkingTree |
406 | + wt = WorkingTree.open_containing(directory)[0] |
407 | + wt.lock_write() |
408 | + try: |
409 | + state = RebaseState1(wt) |
410 | + # Read plan file and set last revision |
411 | + try: |
412 | + last_rev_info = state.read_plan()[0] |
413 | + except NoSuchFile: |
414 | + raise BzrCommandError("No rebase to abort") |
415 | + complete_revert(wt, [last_rev_info[1]]) |
416 | + state.remove_plan() |
417 | + finally: |
418 | + wt.unlock() |
419 | + |
420 | + |
421 | +class cmd_rebase_continue(Command): |
422 | + """Continue an interrupted rebase after resolving conflicts.""" |
423 | + takes_options = [ |
424 | + 'merge-type', Option( |
425 | + 'directory', |
426 | + short_name='d', |
427 | + help="Branch to replay onto, rather than the one containing the working directory.", |
428 | + type=str)] |
429 | + |
430 | + @display_command |
431 | + def run(self, merge_type=None, directory="."): |
432 | + from .rebase import ( |
433 | + RebaseState1, |
434 | + WorkingTreeRevisionRewriter, |
435 | + ) |
436 | + from ...workingtree import WorkingTree |
437 | + wt = WorkingTree.open_containing(directory)[0] |
438 | + wt.lock_write() |
439 | + try: |
440 | + state = RebaseState1(wt) |
441 | + replayer = WorkingTreeRevisionRewriter(wt, state, merge_type=merge_type) |
442 | + # Abort if there are any conflicts |
443 | + if len(wt.conflicts()) != 0: |
444 | + raise BzrCommandError(gettext( |
445 | + "There are still conflicts present. " |
446 | + "Resolve the conflicts and then run " |
447 | + "'brz resolve' and try again.")) |
448 | + # Read plan file |
449 | + try: |
450 | + replace_map = state.read_plan()[1] |
451 | + except NoSuchFile: |
452 | + raise BzrCommandError(gettext("No rebase to continue")) |
453 | + oldrevid = state.read_active_revid() |
454 | + if oldrevid is not None: |
455 | + oldrev = wt.branch.repository.get_revision(oldrevid) |
456 | + replayer.commit_rebase(oldrev, replace_map[oldrevid][0]) |
457 | + finish_rebase(state, wt, replace_map, replayer) |
458 | + finally: |
459 | + wt.unlock() |
460 | + |
461 | + |
462 | +class cmd_rebase_todo(Command): |
463 | + """Print list of revisions that still need to be replayed as part of the |
464 | + current rebase operation. |
465 | + |
466 | + """ |
467 | + |
468 | + takes_options = [ |
469 | + Option( |
470 | + 'directory', |
471 | + short_name='d', |
472 | + help="Branch to replay onto, rather than the one containing the working directory.", |
473 | + type=str)] |
474 | + |
475 | + def run(self, directory="."): |
476 | + from .rebase import ( |
477 | + RebaseState1, |
478 | + rebase_todo, |
479 | + ) |
480 | + from ...workingtree import WorkingTree |
481 | + wt = WorkingTree.open_containing(directory)[0] |
482 | + with wt.lock_read(): |
483 | + state = RebaseState1(wt) |
484 | + try: |
485 | + replace_map = state.read_plan()[1] |
486 | + except NoSuchFile: |
487 | + raise BzrCommandError(gettext("No rebase in progress")) |
488 | + currentrevid = state.read_active_revid() |
489 | + if currentrevid is not None: |
490 | + note(gettext("Currently replaying: %s") % currentrevid) |
491 | + for revid in rebase_todo(wt.branch.repository, replace_map): |
492 | + note(gettext("{0} -> {1}").format(revid, replace_map[revid][0])) |
493 | + |
494 | + |
495 | +class cmd_replay(Command): |
496 | + """Replay commits from another branch on top of this one. |
497 | + |
498 | + """ |
499 | + |
500 | + takes_options = [ |
501 | + 'revision', 'merge-type', |
502 | + Option( |
503 | + 'directory', |
504 | + short_name='d', |
505 | + help="Branch to replay onto, rather than the one containing the working directory.", |
506 | + type=str)] |
507 | + takes_args = ['location'] |
508 | + hidden = True |
509 | + |
510 | + def run(self, location, revision=None, merge_type=None, directory="."): |
511 | + from ...branch import Branch |
512 | + from ...workingtree import WorkingTree |
513 | + from ... import ui |
514 | + from .rebase import ( |
515 | + RebaseState1, |
516 | + regenerate_default_revid, |
517 | + WorkingTreeRevisionRewriter, |
518 | + ) |
519 | + |
520 | + from_branch = Branch.open_containing(location)[0] |
521 | + |
522 | + if revision is not None: |
523 | + if len(revision) == 1: |
524 | + if revision[0] is not None: |
525 | + todo = [revision[0].as_revision_id(from_branch)] |
526 | + elif len(revision) == 2: |
527 | + from_revno, from_revid = revision[0].in_history(from_branch) |
528 | + to_revno, to_revid = revision[1].in_history(from_branch) |
529 | + if to_revid is None: |
530 | + to_revno = from_branch.revno() |
531 | + todo = [] |
532 | + for revno in range(from_revno, to_revno + 1): |
533 | + todo.append(from_branch.get_rev_id(revno)) |
534 | + else: |
535 | + raise BzrCommandError(gettext( |
536 | + "--revision takes only one or two arguments")) |
537 | + else: |
538 | + raise BzrCommandError(gettext("--revision is mandatory")) |
539 | + |
540 | + wt = WorkingTree.open(directory) |
541 | + wt.lock_write() |
542 | + try: |
543 | + state = RebaseState1(wt) |
544 | + replayer = WorkingTreeRevisionRewriter(wt, state, merge_type=merge_type) |
545 | + pb = ui.ui_factory.nested_progress_bar() |
546 | + try: |
547 | + for revid in todo: |
548 | + pb.update(gettext("replaying commits"), todo.index(revid), len(todo)) |
549 | + wt.branch.repository.fetch(from_branch.repository, revid) |
550 | + newrevid = regenerate_default_revid(wt.branch.repository, revid) |
551 | + replayer(revid, newrevid, [wt.last_revision()]) |
552 | + finally: |
553 | + pb.finished() |
554 | + finally: |
555 | + wt.unlock() |
556 | + |
557 | + |
558 | +class cmd_pseudonyms(Command): |
559 | + """Show a list of 'pseudonym' revisions. |
560 | + |
561 | + Pseudonym revisions are revisions that are roughly the same revision, |
562 | + usually because they were converted from the same revision in a |
563 | + foreign version control system. |
564 | + """ |
565 | + |
566 | + takes_args = ['repository?'] |
567 | + hidden = True |
568 | + |
569 | + def run(self, repository=None): |
570 | + from ...controldir import ControlDir |
571 | + dir, _ = ControlDir.open_containing(repository) |
572 | + r = dir.find_repository() |
573 | + from .pseudonyms import find_pseudonyms |
574 | + for pseudonyms in find_pseudonyms(r, r.all_revision_ids()): |
575 | + self.outf.write(", ".join(pseudonyms) + "\n") |
576 | + |
577 | + |
578 | +class cmd_rebase_foreign(Command): |
579 | + """Rebase revisions based on a branch created with a different import tool. |
580 | + |
581 | + This will change the identity of revisions whose parents |
582 | + were mapped from revisions in the other version control system. |
583 | + |
584 | + You are recommended to run "brz check" in the local repository |
585 | + after running this command. |
586 | + """ |
587 | + takes_args = ['new_base?'] |
588 | + takes_options = [ |
589 | + 'verbose', |
590 | + Option("idmap-file", help="Write map with old and new revision ids.", |
591 | + type=str), |
592 | + Option( |
593 | + 'directory', |
594 | + short_name='d', |
595 | + help="Branch to replay onto, rather than the one containing the working directory.", |
596 | + type=str) |
597 | + ] |
598 | + |
599 | + def run(self, new_base=None, verbose=False, idmap_file=None, directory="."): |
600 | + from ... import ( |
601 | + urlutils, |
602 | + ) |
603 | + from ...branch import Branch |
604 | + from ...workingtree import WorkingTree |
605 | + from .pseudonyms import ( |
606 | + find_pseudonyms, |
607 | + generate_rebase_map_from_pseudonyms, |
608 | + pseudonyms_as_dict, |
609 | + ) |
610 | + from .upgrade import ( |
611 | + create_deterministic_revid, |
612 | + upgrade_branch, |
613 | + ) |
614 | + from ...foreign import ( |
615 | + update_workingtree_fileids, |
616 | + ) |
617 | + |
618 | + try: |
619 | + wt_to = WorkingTree.open(directory) |
620 | + branch_to = wt_to.branch |
621 | + except NoWorkingTree: |
622 | + wt_to = None |
623 | + branch_to = Branch.open(directory) |
624 | + |
625 | + stored_loc = branch_to.get_parent() |
626 | + if new_base is None: |
627 | + if stored_loc is None: |
628 | + raise BzrCommandError(gettext( |
629 | + "No pull location known or specified.")) |
630 | + else: |
631 | + display_url = urlutils.unescape_for_display( |
632 | + stored_loc, self.outf.encoding) |
633 | + self.outf.write(gettext("Using saved location: %s\n") % display_url) |
634 | + new_base = Branch.open(stored_loc) |
635 | + else: |
636 | + new_base = Branch.open(new_base) |
637 | + |
638 | + branch_to.repository.fetch( |
639 | + new_base.repository, revision_id=branch_to.last_revision()) |
640 | + |
641 | + pseudonyms = pseudonyms_as_dict(find_pseudonyms( |
642 | + branch_to.repository, branch_to.repository.all_revision_ids())) |
643 | + |
644 | + def generate_rebase_map(revision_id): |
645 | + return generate_rebase_map_from_pseudonyms( |
646 | + pseudonyms, branch_to.repository.get_ancestry(revision_id), |
647 | + branch_to.repository.get_ancestry(new_base.last_revision())) |
648 | + def determine_new_revid(old_revid, new_parents): |
649 | + return create_deterministic_revid(old_revid, new_parents) |
650 | + branch_to.lock_write() |
651 | + try: |
652 | + graph = branch_to.repository.get_graph() |
653 | + renames = upgrade_branch( |
654 | + branch_to, generate_rebase_map, |
655 | + determine_new_revid, allow_changes=True, |
656 | + verbose=verbose) |
657 | + if wt_to is not None: |
658 | + basis_tree = wt_to.basis_tree() |
659 | + basis_tree.lock_read() |
660 | + try: |
661 | + update_workingtree_fileids(wt_to, basis_tree) |
662 | + finally: |
663 | + basis_tree.unlock() |
664 | + finally: |
665 | + branch_to.unlock() |
666 | + |
667 | + if renames == {}: |
668 | + note(gettext("Nothing to do.")) |
669 | + |
670 | + if idmap_file is not None: |
671 | + f = open(idmap_file, 'w') |
672 | + try: |
673 | + for oldid, newid in renames.iteritems(): |
674 | + f.write("%s\t%s\n" % (oldid, newid)) |
675 | + finally: |
676 | + f.close() |
677 | + |
678 | + if wt_to is not None: |
679 | + wt_to.set_last_revision(branch_to.last_revision()) |
680 | |
681 | === added file 'breezy/plugins/rewrite/maptree.py' |
682 | --- breezy/plugins/rewrite/maptree.py 1970-01-01 00:00:00 +0000 |
683 | +++ breezy/plugins/rewrite/maptree.py 2020-01-11 23:46:53 +0000 |
684 | @@ -0,0 +1,124 @@ |
685 | +# Copyright (C) 2006-2007 by Jelmer Vernooij |
686 | +# |
687 | +# This program is free software; you can redistribute it and/or modify |
688 | +# it under the terms of the GNU General Public License as published by |
689 | +# the Free Software Foundation; either version 2 of the License, or |
690 | +# (at your option) any later version. |
691 | +# |
692 | +# This program is distributed in the hope that it will be useful, |
693 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
694 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
695 | +# GNU General Public License for more details. |
696 | +# |
697 | +# You should have received a copy of the GNU General Public License |
698 | +# along with this program; if not, write to the Free Software |
699 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
700 | +"""Map Tree.""" |
701 | + |
702 | +from __future__ import absolute_import |
703 | + |
704 | +def map_file_ids(repository, old_parents, new_parents): |
705 | + """Try to determine the equivalent file ids in two sets of parents. |
706 | + |
707 | + :param repository: Repository to use |
708 | + :param old_parents: List of revision ids of old parents |
709 | + :param new_parents: List of revision ids of new parents |
710 | + """ |
711 | + assert len(old_parents) == len(new_parents) |
712 | + ret = {} |
713 | + for (oldp, newp) in zip(old_parents, new_parents): |
714 | + oldtree = repository.revision_tree(oldp) |
715 | + newtree = repository.revision_tree(newp) |
716 | + for path, ie in oldtree.iter_entries_by_dir(): |
717 | + file_id = newtree.path2id(path) |
718 | + if file_id is not None: |
719 | + ret[ie.file_id] = file_id |
720 | + return ret |
721 | + |
722 | + |
723 | +class MapTree(object): |
724 | + """Wrapper around a tree that translates file ids. |
725 | + """ |
726 | + |
727 | + def __init__(self, oldtree, fileid_map): |
728 | + """Create a new MapTree. |
729 | + |
730 | + :param oldtree: Old tree to map to. |
731 | + :param fileid_map: Map with old -> new file ids. |
732 | + """ |
733 | + self.oldtree = oldtree |
734 | + self.map = fileid_map |
735 | + |
736 | + def old_id(self, file_id): |
737 | + """Look up the original file id of a file. |
738 | + |
739 | + :param file_id: New file id |
740 | + :return: Old file id if mapped, otherwise new file id |
741 | + """ |
742 | + for x in self.map: |
743 | + if self.map[x] == file_id: |
744 | + return x |
745 | + return file_id |
746 | + |
747 | + def new_id(self, file_id): |
748 | + """Look up the new file id of a file. |
749 | + |
750 | + :param file_id: Old file id |
751 | + :return: New file id |
752 | + """ |
753 | + try: |
754 | + return self.map[file_id] |
755 | + except KeyError: |
756 | + return file_id |
757 | + |
758 | + def get_file_sha1(self, path, file_id=None): |
759 | + "See Tree.get_file_sha1().""" |
760 | + return self.oldtree.get_file_sha1(path) |
761 | + |
762 | + def get_file_with_stat(self, path, file_id=None): |
763 | + "See Tree.get_file_with_stat().""" |
764 | + if getattr(self.oldtree, "get_file_with_stat", None) is not None: |
765 | + return self.oldtree.get_file_with_stat(path=path) |
766 | + else: |
767 | + return self.get_file(path), None |
768 | + |
769 | + def get_file(self, path, file_id=None): |
770 | + "See Tree.get_file().""" |
771 | + return self.oldtree.get_file(path) |
772 | + |
773 | + def is_executable(self, path, file_id=None): |
774 | + "See Tree.is_executable().""" |
775 | + return self.oldtree.is_executable(path) |
776 | + |
777 | + def has_filename(self, filename): |
778 | + "See Tree.has_filename().""" |
779 | + return self.oldtree.has_filename(filename) |
780 | + |
781 | + def path_content_summary(self, path): |
782 | + "See Tree.path_content_summary().""" |
783 | + return self.oldtree.path_content_summary(path) |
784 | + |
785 | + def map_ie(self, ie): |
786 | + """Fix the references to old file ids in an inventory entry. |
787 | + |
788 | + :param ie: Inventory entry to map |
789 | + :return: New inventory entry |
790 | + """ |
791 | + new_ie = ie.copy() |
792 | + new_ie.file_id = self.new_id(new_ie.file_id) |
793 | + new_ie.parent_id = self.new_id(new_ie.parent_id) |
794 | + return new_ie |
795 | + |
796 | + def iter_entries_by_dir(self): |
797 | + """See Tree.iter_entries_by_dir.""" |
798 | + for path, ie in self.oldtree.iter_entries_by_dir(): |
799 | + yield path, self.map_ie(ie) |
800 | + |
801 | + def path2id(self, path): |
802 | + file_id = self.oldtree.path2id(path) |
803 | + if file_id is None: |
804 | + return None |
805 | + return self.new_id(file_id) |
806 | + |
807 | + def id2path(self, file_id): |
808 | + return self.oldtree.id2path(self.old_id(file_id=file_id)) |
809 | |
810 | === added file 'breezy/plugins/rewrite/pseudonyms.py' |
811 | --- breezy/plugins/rewrite/pseudonyms.py 1970-01-01 00:00:00 +0000 |
812 | +++ breezy/plugins/rewrite/pseudonyms.py 2020-01-11 23:46:53 +0000 |
813 | @@ -0,0 +1,211 @@ |
814 | +# Copyright (C) 2009 by Jelmer Vernooij <jelmer@samba.org> |
815 | +# |
816 | +# This program is free software; you can redistribute it and/or modify |
817 | +# it under the terms of the GNU General Public License as published by |
818 | +# the Free Software Foundation; either version 2 of the License, or |
819 | +# (at your option) any later version. |
820 | +# |
821 | +# This program is distributed in the hope that it will be useful, |
822 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
823 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
824 | +# GNU General Public License for more details. |
825 | +# |
826 | +# You should have received a copy of the GNU General Public License |
827 | +# along with this program; if not, write to the Free Software |
828 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
829 | + |
830 | +"""Revision pseudonyms.""" |
831 | + |
832 | +from __future__ import absolute_import |
833 | + |
834 | +from collections import defaultdict |
835 | + |
836 | +from breezy import ( |
837 | + errors, |
838 | + foreign, |
839 | + ui, |
840 | + urlutils, |
841 | + ) |
842 | + |
843 | + |
844 | +def parse_git_svn_id(text): |
845 | + """Parse a git svn id string. |
846 | + |
847 | + :param text: git svn id |
848 | + :return: URL, revision number, uuid |
849 | + """ |
850 | + (head, uuid) = text.rsplit(" ", 1) |
851 | + (full_url, rev) = head.rsplit("@", 1) |
852 | + return (full_url.encode("utf-8"), int(rev), uuid.encode("utf-8")) |
853 | + |
854 | + |
855 | +class SubversionBranchUrlFinder(object): |
856 | + |
857 | + def __init__(self): |
858 | + self._roots = defaultdict(set) |
859 | + |
860 | + def find_root(self, uuid, url): |
861 | + for root in self._roots[uuid]: |
862 | + if url.startswith(root): |
863 | + return root |
864 | + try: |
865 | + from subvertpy.ra import RemoteAccess |
866 | + except ImportError: |
867 | + return None |
868 | + c = RemoteAccess(url) |
869 | + root = c.get_repos_root() |
870 | + self._roots[uuid].add(root) |
871 | + return root |
872 | + |
873 | + def find_branch_path(self, uuid, url): |
874 | + root = self.find_root(uuid, url) |
875 | + if root is None: |
876 | + return None |
877 | + assert url.startswith(root) |
878 | + return url[len(root):].strip("/") |
879 | + |
880 | + |
881 | +svn_branch_path_finder = SubversionBranchUrlFinder() |
882 | + |
883 | + |
884 | +def _extract_converted_from_revid(rev): |
885 | + if "converted-from" not in rev.properties: |
886 | + return |
887 | + |
888 | + for line in rev.properties.get("converted-from", "").splitlines(): |
889 | + (kind, serialized_foreign_revid) = line.split(" ", 1) |
890 | + yield (kind, serialized_foreign_revid) |
891 | + |
892 | + |
893 | +def _extract_cscvs(rev): |
894 | + """Older-style launchpad-cscvs import.""" |
895 | + if "cscvs-svn-branch-path" not in rev.properties: |
896 | + return |
897 | + yield ( |
898 | + "svn", "%s:%s:%s" % ( |
899 | + rev.properties["cscvs-svn-repository-uuid"], |
900 | + rev.properties["cscvs-svn-revision-number"], |
901 | + urlutils.quote(rev.properties["cscvs-svn-branch-path"].strip("/")))) |
902 | + |
903 | + |
904 | +def _extract_git_svn_id(rev): |
905 | + if "git-svn-id" not in rev.properties: |
906 | + return |
907 | + (full_url, revnum, uuid) = parse_git_svn_id(rev.properties['git-svn-id']) |
908 | + branch_path = svn_branch_path_finder.find_branch_path(uuid, full_url) |
909 | + if branch_path is not None: |
910 | + yield ("svn", "%s:%d:%s" % (uuid, revnum, urlutils.quote(branch_path))) |
911 | + |
912 | + |
913 | +def _extract_foreign_revision(rev): |
914 | + # Perhaps 'rev' is a foreign revision ? |
915 | + if getattr(rev, "foreign_revid", None) is not None: |
916 | + yield ("svn", rev.mapping.vcs.serialize_foreign_revid(rev.foreign_revid)) |
917 | + |
918 | + |
919 | +def _extract_foreign_revid(rev): |
920 | + # Try parsing the revision id |
921 | + try: |
922 | + foreign_revid, mapping = \ |
923 | + foreign.foreign_vcs_registry.parse_revision_id(rev.revision_id) |
924 | + except errors.InvalidRevisionId: |
925 | + pass |
926 | + else: |
927 | + yield ( |
928 | + mapping.vcs.abbreviation, |
929 | + mapping.vcs.serialize_foreign_revid(foreign_revid)) |
930 | + |
931 | + |
932 | +def _extract_debian_md5sum(rev): |
933 | + if 'deb-md5' in rev.properties: |
934 | + yield ("debian-md5sum", rev.properties["deb-md5"]) |
935 | + |
936 | + |
937 | +_foreign_revid_extractors = [ |
938 | + _extract_converted_from_revid, |
939 | + _extract_cscvs, |
940 | + _extract_git_svn_id, |
941 | + _extract_foreign_revision, |
942 | + _extract_foreign_revid, |
943 | + _extract_debian_md5sum, |
944 | + ] |
945 | + |
946 | + |
947 | +def extract_foreign_revids(rev): |
948 | + """Find ids of semi-equivalent revisions in foreign VCS'es. |
949 | + |
950 | + :param: Bazaar revision object |
951 | + :return: Set with semi-equivalent revisions. |
952 | + """ |
953 | + ret = set() |
954 | + for extractor in _foreign_revid_extractors: |
955 | + ret.update(extractor(rev)) |
956 | + return ret |
957 | + |
958 | + |
959 | +def find_pseudonyms(repository, revids): |
960 | + """Find revisions that are pseudonyms of each other. |
961 | + |
962 | + :param repository: Repository object |
963 | + :param revids: Sequence of revision ids to check |
964 | + :return: Iterable over sets of pseudonyms |
965 | + """ |
966 | + # Where have foreign revids ended up? |
967 | + conversions = defaultdict(set) |
968 | + # What are native revids conversions of? |
969 | + conversion_of = defaultdict(set) |
970 | + revs = repository.get_revisions(revids) |
971 | + pb = ui.ui_factory.nested_progress_bar() |
972 | + try: |
973 | + for i, rev in enumerate(revs): |
974 | + pb.update("finding pseudonyms", i, len(revs)) |
975 | + for foreign_revid in extract_foreign_revids(rev): |
976 | + conversion_of[rev.revision_id].add(foreign_revid) |
977 | + conversions[foreign_revid].add(rev.revision_id) |
978 | + finally: |
979 | + pb.finished() |
980 | + done = set() |
981 | + for foreign_revid in conversions.keys(): |
982 | + ret = set() |
983 | + check = set(conversions[foreign_revid]) |
984 | + while check: |
985 | + x = check.pop() |
986 | + extra = set() |
987 | + for frevid in conversion_of[x]: |
988 | + extra.update(conversions[frevid]) |
989 | + del conversions[frevid] |
990 | + del conversion_of[x] |
991 | + check.update(extra) |
992 | + ret.add(x) |
993 | + if len(ret) > 1: |
994 | + yield ret |
995 | + |
996 | + |
997 | +def pseudonyms_as_dict(l): |
998 | + """Convert an iterable over pseudonyms to a dictionary. |
999 | + |
1000 | + :param l: Iterable over sets of pseudonyms |
1001 | + :return: Dictionary with pseudonyms for each revid. |
1002 | + """ |
1003 | + ret = {} |
1004 | + for pns in l: |
1005 | + for pn in pns: |
1006 | + ret[pn] = pns - set([pn]) |
1007 | + return ret |
1008 | + |
1009 | + |
1010 | +def generate_rebase_map_from_pseudonyms(pseudonym_dict, existing, desired): |
1011 | + """Create a rebase map from pseudonyms and existing/desired ancestry. |
1012 | + |
1013 | + :param pseudonym_dict: Dictionary with pseudonym as returned by |
1014 | + pseudonyms_as_dict() |
1015 | + :param existing: Existing ancestry, might need to be rebased |
1016 | + :param desired: Desired ancestry |
1017 | + :return: rebase map, as dictionary |
1018 | + """ |
1019 | + rebase_map = {} |
1020 | + for revid in existing: |
1021 | + for pn in pseudonym_dict.get(revid, []): |
1022 | + if pn in desired: |
1023 | + rebase_map[revid] = pn |
1024 | + return rebase_map |
1025 | |
1026 | === added file 'breezy/plugins/rewrite/rebase.py' |
1027 | --- breezy/plugins/rewrite/rebase.py 1970-01-01 00:00:00 +0000 |
1028 | +++ breezy/plugins/rewrite/rebase.py 2020-01-11 23:46:53 +0000 |
1029 | @@ -0,0 +1,604 @@ |
1030 | +# Copyright (C) 2006-2007 by Jelmer Vernooij |
1031 | +# |
1032 | +# This program is free software; you can redistribute it and/or modify |
1033 | +# it under the terms of the GNU General Public License as published by |
1034 | +# the Free Software Foundation; either version 2 of the License, or |
1035 | +# (at your option) any later version. |
1036 | +# |
1037 | +# This program is distributed in the hope that it will be useful, |
1038 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1039 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1040 | +# GNU General Public License for more details. |
1041 | +# |
1042 | +# You should have received a copy of the GNU General Public License |
1043 | +# along with this program; if not, write to the Free Software |
1044 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
1045 | + |
1046 | +"""Rebase.""" |
1047 | + |
1048 | +from __future__ import absolute_import |
1049 | + |
1050 | +import os |
1051 | + |
1052 | +from ... import ( |
1053 | + config as _mod_config, |
1054 | + osutils, |
1055 | + ) |
1056 | +from ...errors import ( |
1057 | + BzrError, |
1058 | + NoSuchFile, |
1059 | + UnknownFormatError, |
1060 | + NoCommonAncestor, |
1061 | + UnrelatedBranches, |
1062 | + ) |
1063 | +from ...bzr.generate_ids import gen_revision_id |
1064 | +from ...graph import FrozenHeadsCache |
1065 | +from ...merge import Merger |
1066 | +from ...revision import NULL_REVISION |
1067 | +from ...trace import mutter |
1068 | +from ...tsort import topo_sort |
1069 | +from ...tree import TreeChange |
1070 | +from ... import ui |
1071 | + |
1072 | +from .maptree import ( |
1073 | + MapTree, |
1074 | + map_file_ids, |
1075 | + ) |
1076 | + |
1077 | +REBASE_PLAN_FILENAME = 'rebase-plan' |
1078 | +REBASE_CURRENT_REVID_FILENAME = 'rebase-current' |
1079 | +REBASE_PLAN_VERSION = 1 |
1080 | +REVPROP_REBASE_OF = 'rebase-of' |
1081 | + |
1082 | +class RebaseState(object): |
1083 | + |
1084 | + def has_plan(self): |
1085 | + """Check whether there is a rebase plan present. |
1086 | + |
1087 | + :return: boolean |
1088 | + """ |
1089 | + raise NotImplementedError(self.has_plan) |
1090 | + |
1091 | + def read_plan(self): |
1092 | + """Read a rebase plan file. |
1093 | + |
1094 | + :return: Tuple with last revision info and replace map. |
1095 | + """ |
1096 | + raise NotImplementedError(self.read_plan) |
1097 | + |
1098 | + def write_plan(self, replace_map): |
1099 | + """Write a rebase plan file. |
1100 | + |
1101 | + :param replace_map: Replace map (old revid -> (new revid, new parents)) |
1102 | + """ |
1103 | + raise NotImplementedError(self.write_plan) |
1104 | + |
1105 | + def remove_plan(self): |
1106 | + """Remove a rebase plan file. |
1107 | + """ |
1108 | + raise NotImplementedError(self.remove_plan) |
1109 | + |
1110 | + def write_active_revid(self, revid): |
1111 | + """Write the id of the revision that is currently being rebased. |
1112 | + |
1113 | + :param revid: Revision id to write |
1114 | + """ |
1115 | + raise NotImplementedError(self.write_active_revid) |
1116 | + |
1117 | + def read_active_revid(self): |
1118 | + """Read the id of the revision that is currently being rebased. |
1119 | + |
1120 | + :return: Id of the revision that is being rebased. |
1121 | + """ |
1122 | + raise NotImplementedError(self.read_active_revid) |
1123 | + |
1124 | + |
1125 | +class RebaseState1(RebaseState): |
1126 | + |
1127 | + def __init__(self, wt): |
1128 | + self.wt = wt |
1129 | + self.transport = wt._transport |
1130 | + |
1131 | + def has_plan(self): |
1132 | + """See `RebaseState`.""" |
1133 | + try: |
1134 | + return self.transport.get_bytes(REBASE_PLAN_FILENAME) != b'' |
1135 | + except NoSuchFile: |
1136 | + return False |
1137 | + |
1138 | + def read_plan(self): |
1139 | + """See `RebaseState`.""" |
1140 | + text = self.transport.get_bytes(REBASE_PLAN_FILENAME) |
1141 | + if text == b'': |
1142 | + raise NoSuchFile(REBASE_PLAN_FILENAME) |
1143 | + return unmarshall_rebase_plan(text) |
1144 | + |
1145 | + def write_plan(self, replace_map): |
1146 | + """See `RebaseState`.""" |
1147 | + self.wt.update_feature_flags({b"rebase-v1": b"write-required"}) |
1148 | + content = marshall_rebase_plan( |
1149 | + self.wt.branch.last_revision_info(), replace_map) |
1150 | + assert isinstance(content, bytes) |
1151 | + self.transport.put_bytes(REBASE_PLAN_FILENAME, content) |
1152 | + |
1153 | + def remove_plan(self): |
1154 | + """See `RebaseState`.""" |
1155 | + self.wt.update_feature_flags({b"rebase-v1": None}) |
1156 | + self.transport.put_bytes(REBASE_PLAN_FILENAME, b'') |
1157 | + |
1158 | + def write_active_revid(self, revid): |
1159 | + """See `RebaseState`.""" |
1160 | + if revid is None: |
1161 | + revid = NULL_REVISION |
1162 | + assert isinstance(revid, bytes) |
1163 | + self.transport.put_bytes(REBASE_CURRENT_REVID_FILENAME, revid) |
1164 | + |
1165 | + def read_active_revid(self): |
1166 | + """See `RebaseState`.""" |
1167 | + try: |
1168 | + text = self.transport.get_bytes(REBASE_CURRENT_REVID_FILENAME).rstrip(b"\n") |
1169 | + if text == NULL_REVISION: |
1170 | + return None |
1171 | + return text |
1172 | + except NoSuchFile: |
1173 | + return None |
1174 | + |
1175 | + |
1176 | +def marshall_rebase_plan(last_rev_info, replace_map): |
1177 | + """Marshall a rebase plan. |
1178 | + |
1179 | + :param last_rev_info: Last revision info tuple. |
1180 | + :param replace_map: Replace map (old revid -> (new revid, new parents)) |
1181 | + :return: string |
1182 | + """ |
1183 | + ret = b"# Bazaar rebase plan %d\n" % REBASE_PLAN_VERSION |
1184 | + ret += b"%d %s\n" % last_rev_info |
1185 | + for oldrev in replace_map: |
1186 | + (newrev, newparents) = replace_map[oldrev] |
1187 | + ret += b"%s %s" % (oldrev, newrev) + \ |
1188 | + b"".join([b" %s" % p for p in newparents]) + b"\n" |
1189 | + return ret |
1190 | + |
1191 | + |
1192 | +def unmarshall_rebase_plan(text): |
1193 | + """Unmarshall a rebase plan. |
1194 | + |
1195 | + :param text: Text to parse |
1196 | + :return: Tuple with last revision info, replace map. |
1197 | + """ |
1198 | + lines = text.split(b'\n') |
1199 | + # Make sure header is there |
1200 | + if lines[0] != b"# Bazaar rebase plan %d" % REBASE_PLAN_VERSION: |
1201 | + raise UnknownFormatError(lines[0]) |
1202 | + |
1203 | + pts = lines[1].split(b" ", 1) |
1204 | + last_revision_info = (int(pts[0]), pts[1]) |
1205 | + replace_map = {} |
1206 | + for l in lines[2:]: |
1207 | + if l == b"": |
1208 | + # Skip empty lines |
1209 | + continue |
1210 | + pts = l.split(b" ") |
1211 | + replace_map[pts[0]] = (pts[1], tuple(pts[2:])) |
1212 | + return (last_revision_info, replace_map) |
1213 | + |
1214 | + |
1215 | +def regenerate_default_revid(repository, revid): |
1216 | + """Generate a revision id for the rebase of an existing revision. |
1217 | + |
1218 | + :param repository: Repository in which the revision is present. |
1219 | + :param revid: Revision id of the revision that is being rebased. |
1220 | + :return: new revision id.""" |
1221 | + if revid == NULL_REVISION: |
1222 | + return NULL_REVISION |
1223 | + rev = repository.get_revision(revid) |
1224 | + return gen_revision_id(rev.committer, rev.timestamp) |
1225 | + |
1226 | + |
1227 | +def generate_simple_plan(todo_set, start_revid, stop_revid, onto_revid, graph, |
1228 | + generate_revid, skip_full_merged=False): |
1229 | + """Create a simple rebase plan that replays history based |
1230 | + on one revision being replayed on top of another. |
1231 | + |
1232 | + :param todo_set: A set of revisions to rebase. Only the revisions |
1233 | + topologically between stop_revid and start_revid (inclusive) are |
1234 | + rebased; other revisions are ignored (and references to them are |
1235 | + preserved). |
1236 | + :param start_revid: Id of revision at which to start replaying |
1237 | + :param stop_revid: Id of revision until which to stop replaying |
1238 | + :param onto_revid: Id of revision on top of which to replay |
1239 | + :param graph: Graph object |
1240 | + :param generate_revid: Function for generating new revision ids |
1241 | + :param skip_full_merged: Skip revisions that merge already merged |
1242 | + revisions. |
1243 | + |
1244 | + :return: replace map |
1245 | + """ |
1246 | + assert start_revid is None or start_revid in todo_set, \ |
1247 | + "invalid start revid(%r), todo_set(%r)" % (start_revid, todo_set) |
1248 | + assert stop_revid is None or stop_revid in todo_set, "invalid stop_revid" |
1249 | + replace_map = {} |
1250 | + parent_map = graph.get_parent_map(todo_set) |
1251 | + order = topo_sort(parent_map) |
1252 | + if stop_revid is None: |
1253 | + stop_revid = order[-1] |
1254 | + if start_revid is None: |
1255 | + # We need a common base. |
1256 | + lca = graph.find_lca(stop_revid, onto_revid) |
1257 | + if lca == set([NULL_REVISION]): |
1258 | + raise UnrelatedBranches() |
1259 | + start_revid = order[0] |
1260 | + todo = order[order.index(start_revid):order.index(stop_revid) + 1] |
1261 | + heads_cache = FrozenHeadsCache(graph) |
1262 | + # XXX: The output replacemap'd parents should get looked up in some manner |
1263 | + # by the heads cache? RBC 20080719 |
1264 | + for oldrevid in todo: |
1265 | + oldparents = parent_map[oldrevid] |
1266 | + assert isinstance(oldparents, tuple), "not tuple: %r" % oldparents |
1267 | + parents = [] |
1268 | + # Left parent: |
1269 | + if heads_cache.heads((oldparents[0], onto_revid)) == set((onto_revid,)): |
1270 | + parents.append(onto_revid) |
1271 | + elif oldparents[0] in replace_map: |
1272 | + parents.append(replace_map[oldparents[0]][0]) |
1273 | + else: |
1274 | + parents.append(onto_revid) |
1275 | + parents.append(oldparents[0]) |
1276 | + # Other parents: |
1277 | + if len(oldparents) > 1: |
1278 | + additional_parents = heads_cache.heads(oldparents[1:]) |
1279 | + for oldparent in oldparents[1:]: |
1280 | + if oldparent in additional_parents: |
1281 | + if heads_cache.heads((oldparent, onto_revid)) == set((onto_revid,)): |
1282 | + pass |
1283 | + elif oldparent in replace_map: |
1284 | + newparent = replace_map[oldparent][0] |
1285 | + if parents[0] == onto_revid: |
1286 | + parents[0] = newparent |
1287 | + else: |
1288 | + parents.append(newparent) |
1289 | + else: |
1290 | + parents.append(oldparent) |
1291 | + if len(parents) == 1 and skip_full_merged: |
1292 | + continue |
1293 | + parents = tuple(parents) |
1294 | + newrevid = generate_revid(oldrevid, parents) |
1295 | + assert newrevid != oldrevid, "old and newrevid equal (%r)" % newrevid |
1296 | + assert isinstance(parents, tuple), "parents not tuple: %r" % parents |
1297 | + replace_map[oldrevid] = (newrevid, parents) |
1298 | + return replace_map |
1299 | + |
1300 | + |
1301 | +def generate_transpose_plan(ancestry, renames, graph, generate_revid): |
1302 | + """Create a rebase plan that replaces a bunch of revisions |
1303 | + in a revision graph. |
1304 | + |
1305 | + :param ancestry: Ancestry to consider |
1306 | + :param renames: Renames of revision |
1307 | + :param graph: Graph object |
1308 | + :param generate_revid: Function for creating new revision ids |
1309 | + """ |
1310 | + replace_map = {} |
1311 | + todo = [] |
1312 | + children = {} |
1313 | + parent_map = {} |
1314 | + for r, ps in ancestry: |
1315 | + if r not in children: |
1316 | + children[r] = [] |
1317 | + if ps is None: # Ghost |
1318 | + continue |
1319 | + parent_map[r] = ps |
1320 | + if r not in children: |
1321 | + children[r] = [] |
1322 | + for p in ps: |
1323 | + if p not in children: |
1324 | + children[p] = [] |
1325 | + children[p].append(r) |
1326 | + |
1327 | + parent_map.update(graph.get_parent_map(filter(lambda x: x not in parent_map, renames.values()))) |
1328 | + |
1329 | + # todo contains a list of revisions that need to |
1330 | + # be rewritten |
1331 | + for r, v in renames.items(): |
1332 | + replace_map[r] = (v, parent_map[v]) |
1333 | + todo.append(r) |
1334 | + |
1335 | + total = len(todo) |
1336 | + processed = set() |
1337 | + i = 0 |
1338 | + pb = ui.ui_factory.nested_progress_bar() |
1339 | + try: |
1340 | + while len(todo) > 0: |
1341 | + r = todo.pop() |
1342 | + processed.add(r) |
1343 | + i += 1 |
1344 | + pb.update('determining dependencies', i, total) |
1345 | + # Add entry for them in replace_map |
1346 | + for c in children[r]: |
1347 | + if c in renames: |
1348 | + continue |
1349 | + if c in replace_map: |
1350 | + parents = replace_map[c][1] |
1351 | + else: |
1352 | + parents = parent_map[c] |
1353 | + assert isinstance(parents, tuple), \ |
1354 | + "Expected tuple of parents, got: %r" % parents |
1355 | + # replace r in parents with replace_map[r][0] |
1356 | + if not replace_map[r][0] in parents: |
1357 | + parents = list(parents) |
1358 | + parents[parents.index(r)] = replace_map[r][0] |
1359 | + parents = tuple(parents) |
1360 | + replace_map[c] = (generate_revid(c, tuple(parents)), |
1361 | + tuple(parents)) |
1362 | + if replace_map[c][0] == c: |
1363 | + del replace_map[c] |
1364 | + elif c not in processed: |
1365 | + todo.append(c) |
1366 | + finally: |
1367 | + pb.finished() |
1368 | + |
1369 | + # Remove items from the map that already exist |
1370 | + for revid in renames: |
1371 | + if revid in replace_map: |
1372 | + del replace_map[revid] |
1373 | + |
1374 | + return replace_map |
1375 | + |
1376 | + |
1377 | +def rebase_todo(repository, replace_map): |
1378 | + """Figure out what revisions still need to be rebased. |
1379 | + |
1380 | + :param repository: Repository that contains the revisions |
1381 | + :param replace_map: Replace map |
1382 | + """ |
1383 | + for revid, parent_ids in replace_map.items(): |
1384 | + assert isinstance(parent_ids, tuple), "replace map parents not tuple" |
1385 | + if not repository.has_revision(parent_ids[0]): |
1386 | + yield revid |
1387 | + |
1388 | + |
1389 | +def rebase(repository, replace_map, revision_rewriter): |
1390 | + """Rebase a working tree according to the specified map. |
1391 | + |
1392 | + :param repository: Repository that contains the revisions |
1393 | + :param replace_map: Dictionary with revisions to (optionally) rewrite |
1394 | + :param merge_fn: Function for replaying a revision |
1395 | + """ |
1396 | + # Figure out the dependencies |
1397 | + graph = repository.get_graph() |
1398 | + todo = list(graph.iter_topo_order(replace_map.keys())) |
1399 | + pb = ui.ui_factory.nested_progress_bar() |
1400 | + try: |
1401 | + for i, revid in enumerate(todo): |
1402 | + pb.update('rebase revisions', i, len(todo)) |
1403 | + (newrevid, newparents) = replace_map[revid] |
1404 | + assert isinstance(newparents, tuple), "Expected tuple for %r" % newparents |
1405 | + if repository.has_revision(newrevid): |
1406 | + # Was already converted, no need to worry about it again |
1407 | + continue |
1408 | + revision_rewriter(revid, newrevid, newparents) |
1409 | + finally: |
1410 | + pb.finished() |
1411 | + |
1412 | + |
1413 | +def wrap_iter_changes(old_iter_changes, map_tree): |
1414 | + for change in old_iter_changes: |
1415 | + if change.parent_id[0] is not None: |
1416 | + old_parent = map_tree.new_id(change.parent_id[0]) |
1417 | + else: |
1418 | + old_parent = change.parent_id[0] |
1419 | + if change.parent_id[1] is not None: |
1420 | + new_parent = map_tree.new_id(change.parent_id[1]) |
1421 | + else: |
1422 | + new_parent = change.parent_id[1] |
1423 | + yield TreeChange( |
1424 | + map_tree.new_id(change.file_id), change.path, |
1425 | + change.changed_content, change.versioned, |
1426 | + (old_parent, new_parent), change.name, change.kind, |
1427 | + change.executable) |
1428 | + |
1429 | + |
1430 | +class CommitBuilderRevisionRewriter(object): |
1431 | + """Revision rewriter that use commit builder. |
1432 | + |
1433 | + :ivar repository: Repository in which the revision is present. |
1434 | + """ |
1435 | + |
1436 | + def __init__(self, repository, map_ids=True): |
1437 | + self.repository = repository |
1438 | + self.map_ids = map_ids |
1439 | + |
1440 | + def _get_present_revisions(self, revids): |
1441 | + return tuple([p for p in revids if self.repository.has_revision(p)]) |
1442 | + |
1443 | + def __call__(self, oldrevid, newrevid, new_parents): |
1444 | + """Replay a commit by simply commiting the same snapshot with different |
1445 | + parents. |
1446 | + |
1447 | + :param oldrevid: Revision id of the revision to copy. |
1448 | + :param newrevid: Revision id of the revision to create. |
1449 | + :param new_parents: Revision ids of the new parent revisions. |
1450 | + """ |
1451 | + assert isinstance(new_parents, tuple), "CommitBuilderRevisionRewriter: Expected tuple for %r" % new_parents |
1452 | + mutter('creating copy %r of %r with new parents %r', |
1453 | + newrevid, oldrevid, new_parents) |
1454 | + oldrev = self.repository.get_revision(oldrevid) |
1455 | + |
1456 | + revprops = dict(oldrev.properties) |
1457 | + revprops[REVPROP_REBASE_OF] = oldrevid.decode('utf-8') |
1458 | + |
1459 | + # Check what new_ie.file_id should be |
1460 | + # use old and new parent trees to generate new_id map |
1461 | + nonghost_oldparents = self._get_present_revisions(oldrev.parent_ids) |
1462 | + nonghost_newparents = self._get_present_revisions(new_parents) |
1463 | + oldtree = self.repository.revision_tree(oldrevid) |
1464 | + if self.map_ids: |
1465 | + fileid_map = map_file_ids( |
1466 | + self.repository, nonghost_oldparents, |
1467 | + nonghost_newparents) |
1468 | + mappedtree = MapTree(oldtree, fileid_map) |
1469 | + else: |
1470 | + mappedtree = oldtree |
1471 | + |
1472 | + try: |
1473 | + old_base = nonghost_oldparents[0] |
1474 | + except IndexError: |
1475 | + old_base = NULL_REVISION |
1476 | + try: |
1477 | + new_base = new_parents[0] |
1478 | + except IndexError: |
1479 | + new_base = NULL_REVISION |
1480 | + old_base_tree = self.repository.revision_tree(old_base) |
1481 | + old_iter_changes = oldtree.iter_changes(old_base_tree) |
1482 | + iter_changes = wrap_iter_changes(old_iter_changes, mappedtree) |
1483 | + builder = self.repository.get_commit_builder( |
1484 | + branch=None, parents=new_parents, committer=oldrev.committer, |
1485 | + timestamp=oldrev.timestamp, timezone=oldrev.timezone, |
1486 | + revprops=revprops, revision_id=newrevid, |
1487 | + config_stack=_mod_config.GlobalStack()) |
1488 | + try: |
1489 | + for (relpath, fs_hash) in builder.record_iter_changes( |
1490 | + mappedtree, new_base, iter_changes): |
1491 | + pass |
1492 | + builder.finish_inventory() |
1493 | + return builder.commit(oldrev.message) |
1494 | + except: |
1495 | + builder.abort() |
1496 | + raise |
1497 | + |
1498 | + |
1499 | +class WorkingTreeRevisionRewriter(object): |
1500 | + |
1501 | + def __init__(self, wt, state, merge_type=None): |
1502 | + """ |
1503 | + :param wt: Working tree in which to do the replays. |
1504 | + """ |
1505 | + self.wt = wt |
1506 | + self.graph = self.wt.branch.repository.get_graph() |
1507 | + self.state = state |
1508 | + self.merge_type = merge_type |
1509 | + |
1510 | + def __call__(self, oldrevid, newrevid, newparents): |
1511 | + """Replay a commit in a working tree, with a different base. |
1512 | + |
1513 | + :param oldrevid: Old revision id |
1514 | + :param newrevid: New revision id |
1515 | + :param newparents: New parent revision ids |
1516 | + """ |
1517 | + repository = self.wt.branch.repository |
1518 | + if self.merge_type is None: |
1519 | + from ...merge import Merge3Merger |
1520 | + merge_type = Merge3Merger |
1521 | + else: |
1522 | + merge_type = self.merge_type |
1523 | + oldrev = self.wt.branch.repository.get_revision(oldrevid) |
1524 | + # Make sure there are no conflicts or pending merges/changes |
1525 | + # in the working tree |
1526 | + complete_revert(self.wt, [newparents[0]]) |
1527 | + assert not self.wt.changes_from(self.wt.basis_tree()).has_changed(), "Changes in rev" |
1528 | + |
1529 | + oldtree = repository.revision_tree(oldrevid) |
1530 | + self.state.write_active_revid(oldrevid) |
1531 | + merger = Merger(self.wt.branch, this_tree=self.wt) |
1532 | + merger.set_other_revision(oldrevid, self.wt.branch) |
1533 | + base_revid = self.determine_base( |
1534 | + oldrevid, oldrev.parent_ids, newrevid, newparents) |
1535 | + mutter('replaying %r as %r with base %r and new parents %r' % |
1536 | + (oldrevid, newrevid, base_revid, newparents)) |
1537 | + merger.set_base_revision(base_revid, self.wt.branch) |
1538 | + merger.merge_type = merge_type |
1539 | + merger.do_merge() |
1540 | + for newparent in newparents[1:]: |
1541 | + self.wt.add_pending_merge(newparent) |
1542 | + self.commit_rebase(oldrev, newrevid) |
1543 | + self.state.write_active_revid(None) |
1544 | + |
1545 | + def determine_base(self, oldrevid, oldparents, newrevid, newparents): |
1546 | + """Determine the base for replaying a revision using merge. |
1547 | + |
1548 | + :param oldrevid: Revid of old revision. |
1549 | + :param oldparents: List of old parents revids. |
1550 | + :param newrevid: Revid of new revision. |
1551 | + :param newparents: List of new parents revids. |
1552 | + :return: Revision id of the new new revision. |
1553 | + """ |
1554 | + # If this was the first commit, no base is needed |
1555 | + if len(oldparents) == 0: |
1556 | + return NULL_REVISION |
1557 | + |
1558 | + # In the case of a "simple" revision with just one parent, |
1559 | + # that parent should be the base |
1560 | + if len(oldparents) == 1: |
1561 | + return oldparents[0] |
1562 | + |
1563 | + # In case the rhs parent(s) of the origin revision has already been |
1564 | + # merged in the new branch, use diff between rhs parent and diff from |
1565 | + # original revision |
1566 | + if len(newparents) == 1: |
1567 | + # FIXME: Find oldparents entry that matches newparents[0] |
1568 | + # and return it |
1569 | + return oldparents[1] |
1570 | + |
1571 | + try: |
1572 | + return self.graph.find_unique_lca(*[oldparents[0], newparents[1]]) |
1573 | + except NoCommonAncestor: |
1574 | + return oldparents[0] |
1575 | + |
1576 | + def commit_rebase(self, oldrev, newrevid): |
1577 | + """Commit a rebase. |
1578 | + |
1579 | + :param oldrev: Revision info of new revision to commit. |
1580 | + :param newrevid: New revision id.""" |
1581 | + assert oldrev.revision_id != newrevid, "Invalid revid %r" % newrevid |
1582 | + revprops = dict(oldrev.properties) |
1583 | + revprops[REVPROP_REBASE_OF] = oldrev.revision_id.decode('utf-8') |
1584 | + committer = self.wt.branch.get_config().username() |
1585 | + authors = oldrev.get_apparent_authors() |
1586 | + if oldrev.committer == committer: |
1587 | + # No need to explicitly record the authors if the original |
1588 | + # committer is rebasing. |
1589 | + if [oldrev.committer] == authors: |
1590 | + authors = None |
1591 | + else: |
1592 | + if oldrev.committer not in authors: |
1593 | + authors.append(oldrev.committer) |
1594 | + if 'author' in revprops: |
1595 | + del revprops['author'] |
1596 | + if 'authors' in revprops: |
1597 | + del revprops['authors'] |
1598 | + self.wt.commit( |
1599 | + message=oldrev.message, timestamp=oldrev.timestamp, |
1600 | + timezone=oldrev.timezone, revprops=revprops, rev_id=newrevid, |
1601 | + committer=committer, authors=authors) |
1602 | + |
1603 | + |
1604 | +def complete_revert(wt, newparents): |
1605 | + """Simple helper that reverts to specified new parents and makes sure none |
1606 | + of the extra files are left around. |
1607 | + |
1608 | + :param wt: Working tree to use for rebase |
1609 | + :param newparents: New parents of the working tree |
1610 | + """ |
1611 | + newtree = wt.branch.repository.revision_tree(newparents[0]) |
1612 | + delta = wt.changes_from(newtree) |
1613 | + wt.branch.generate_revision_history(newparents[0]) |
1614 | + wt.set_parent_ids([r for r in newparents[:1] if r != NULL_REVISION]) |
1615 | + for change in delta.added: |
1616 | + abs_path = wt.abspath(change.path[1]) |
1617 | + if osutils.lexists(abs_path): |
1618 | + if osutils.isdir(abs_path): |
1619 | + osutils.rmtree(abs_path) |
1620 | + else: |
1621 | + os.unlink(abs_path) |
1622 | + wt.revert(None, old_tree=newtree, backups=False) |
1623 | + assert not wt.changes_from(wt.basis_tree()).has_changed(), "Rev changed" |
1624 | + wt.set_parent_ids([r for r in newparents if r != NULL_REVISION]) |
1625 | + |
1626 | + |
1627 | +class ReplaySnapshotError(BzrError): |
1628 | + """Raised when replaying a snapshot failed.""" |
1629 | + _fmt = """Replaying the snapshot failed: %(msg)s.""" |
1630 | + |
1631 | + def __init__(self, msg): |
1632 | + BzrError.__init__(self) |
1633 | + self.msg = msg |
1634 | |
1635 | === added directory 'breezy/plugins/rewrite/tests' |
1636 | === added file 'breezy/plugins/rewrite/tests/__init__.py' |
1637 | --- breezy/plugins/rewrite/tests/__init__.py 1970-01-01 00:00:00 +0000 |
1638 | +++ breezy/plugins/rewrite/tests/__init__.py 2020-01-11 23:46:53 +0000 |
1639 | @@ -0,0 +1,29 @@ |
1640 | +# Copyright (C) 2007-2010 by Jelmer Vernooij <jelmer@samba.org> |
1641 | +# |
1642 | +# This program is free software; you can redistribute it and/or modify |
1643 | +# it under the terms of the GNU General Public License as published by |
1644 | +# the Free Software Foundation; either version 2 of the License, or |
1645 | +# (at your option) any later version. |
1646 | +# |
1647 | +# This program is distributed in the hope that it will be useful, |
1648 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1649 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1650 | +# GNU General Public License for more details. |
1651 | +# |
1652 | +# You should have received a copy of the GNU General Public License |
1653 | +# along with this program; if not, write to the Free Software |
1654 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
1655 | + |
1656 | +"""Tests for brz-rewrite.""" |
1657 | + |
1658 | + |
1659 | +def load_tests(loader, basic_tests, pattern): |
1660 | + testmod_names = [ |
1661 | + 'test_blackbox', |
1662 | + 'test_maptree', |
1663 | + 'test_pseudonyms', |
1664 | + 'test_rebase', |
1665 | + 'test_upgrade'] |
1666 | + basic_tests.addTest(loader.loadTestsFromModuleNames( |
1667 | + ["%s.%s" % (__name__, tmn) for tmn in testmod_names])) |
1668 | + return basic_tests |
1669 | |
1670 | === added file 'breezy/plugins/rewrite/tests/test_blackbox.py' |
1671 | --- breezy/plugins/rewrite/tests/test_blackbox.py 1970-01-01 00:00:00 +0000 |
1672 | +++ breezy/plugins/rewrite/tests/test_blackbox.py 2020-01-11 23:46:53 +0000 |
1673 | @@ -0,0 +1,363 @@ |
1674 | +# Copyright (C) 2007 by Jelmer Vernooij <jelmer@samba.org> |
1675 | +# |
1676 | +# This program is free software; you can redistribute it and/or modify |
1677 | +# it under the terms of the GNU General Public License as published by |
1678 | +# the Free Software Foundation; either version 2 of the License, or |
1679 | +# (at your option) any later version. |
1680 | +# |
1681 | +# This program is distributed in the hope that it will be useful, |
1682 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1683 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1684 | +# GNU General Public License for more details. |
1685 | +# |
1686 | +# You should have received a copy of the GNU General Public License |
1687 | +# along with this program; if not, write to the Free Software |
1688 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
1689 | + |
1690 | +"""Couple of blackbox tests for the rewrite plugin.""" |
1691 | + |
1692 | +import os |
1693 | + |
1694 | +from ....branch import Branch |
1695 | +from ....tests.blackbox import ExternalBase |
1696 | + |
1697 | +class TestRebaseSimple(ExternalBase): |
1698 | + |
1699 | + def make_file(self, name, contents): |
1700 | + with open(name, 'w' + ('b' if isinstance(contents, bytes) else '')) as f: |
1701 | + f.write(contents) |
1702 | + |
1703 | + def setUp(self): |
1704 | + super(TestRebaseSimple, self).setUp() |
1705 | + os.mkdir('main') |
1706 | + os.chdir('main') |
1707 | + self.run_bzr('init') |
1708 | + self.make_file('hello', b"hi world") |
1709 | + self.run_bzr('add') |
1710 | + self.run_bzr('commit -m bla') |
1711 | + self.run_bzr('branch . ../feature') |
1712 | + |
1713 | + def test_no_upstream_branch(self): |
1714 | + self.run_bzr_error(['brz: ERROR: No upstream branch specified.\n'], |
1715 | + 'rebase') |
1716 | + |
1717 | + def test_notneeded(self): |
1718 | + os.chdir('../feature') |
1719 | + self.assertEquals( |
1720 | + 'No revisions to rebase.\n', |
1721 | + self.run_bzr('rebase ../main')[0]) |
1722 | + |
1723 | + def test_custom_merge_type(self): |
1724 | + self.make_file('hello', '42') |
1725 | + self.run_bzr('commit -m that') |
1726 | + os.chdir('../feature') |
1727 | + self.make_file('hoi', "my data") |
1728 | + self.run_bzr('add') |
1729 | + self.run_bzr('commit -m this') |
1730 | + self.assertEquals('', self.run_bzr('rebase --lca ../main')[0]) |
1731 | + self.assertEquals('3\n', self.run_bzr('revno')[0]) |
1732 | + |
1733 | + def test_notneeded_feature_ahead(self): |
1734 | + os.chdir('../feature') |
1735 | + self.make_file('barbla', "bloe") |
1736 | + self.run_bzr('add') |
1737 | + self.run_bzr('commit -m bloe') |
1738 | + self.assertEquals( |
1739 | + 'No revisions to rebase.\n', |
1740 | + self.run_bzr('rebase ../main')[0]) |
1741 | + |
1742 | + def test_notneeded_main_ahead(self): |
1743 | + self.make_file('barbla', "bloe") |
1744 | + self.run_bzr('add') |
1745 | + self.run_bzr('commit -m bloe') |
1746 | + os.chdir('../feature') |
1747 | + self.assertEquals( |
1748 | + "Base branch is descendant of current branch. Pulling instead.\n", |
1749 | + self.run_bzr('rebase ../main')[0]) |
1750 | + self.assertEquals(Branch.open("../feature").last_revision_info(), |
1751 | + Branch.open("../main").last_revision_info()) |
1752 | + |
1753 | + def test_no_pending_merges(self): |
1754 | + self.run_bzr_error(['brz: ERROR: No pending merges present.\n'], |
1755 | + ['rebase', '--pending-merges']) |
1756 | + |
1757 | + def test_pending_merges(self): |
1758 | + os.chdir('..') |
1759 | + self.build_tree_contents([('main/hello', '42')]) |
1760 | + self.run_bzr('add', working_dir='main') |
1761 | + self.run_bzr('commit -m that main') |
1762 | + self.build_tree_contents([('feature/hoi', 'my data')]) |
1763 | + self.run_bzr('add', working_dir='feature') |
1764 | + self.run_bzr('commit -m this feature') |
1765 | + self.assertEqual( |
1766 | + ('', ' M hello\nAll changes applied successfully.\n'), |
1767 | + self.run_bzr('merge ../main', working_dir='feature')) |
1768 | + out, err = self.run_bzr('rebase --pending-merges', working_dir='feature') |
1769 | + self.assertEqual('', out) |
1770 | + self.assertContainsRe(err, 'modified hello') |
1771 | + self.assertEqual( |
1772 | + ('3\n', ''), |
1773 | + self.run_bzr('revno', working_dir='feature')) |
1774 | + |
1775 | + def test_simple_success(self): |
1776 | + self.make_file('hello', '42') |
1777 | + self.run_bzr('commit -m that') |
1778 | + os.chdir('../feature') |
1779 | + self.make_file('hoi', "my data") |
1780 | + self.run_bzr('add') |
1781 | + self.run_bzr('commit -m this') |
1782 | + self.assertEquals('', self.run_bzr('rebase ../main')[0]) |
1783 | + self.assertEquals('3\n', self.run_bzr('revno')[0]) |
1784 | + |
1785 | + def test_range(self): |
1786 | + # commit mainline rev 2 |
1787 | + self.make_file('hello', '42') |
1788 | + self.run_bzr('commit -m that') |
1789 | + # commit feature rev 2 |
1790 | + os.chdir('../feature') |
1791 | + self.make_file('hoi', "my data") |
1792 | + self.run_bzr('add') |
1793 | + self.run_bzr('commit -m this') |
1794 | + # commit feature rev 3 |
1795 | + self.make_file('hooi', "your data") |
1796 | + self.run_bzr('add') |
1797 | + self.run_bzr('commit -m that') |
1798 | + # commit feature rev 4 |
1799 | + self.make_file('hoooi', "someone else's data") |
1800 | + self.run_bzr('add') |
1801 | + self.run_bzr('commit -m these') |
1802 | + # pick up just rev 2 and 3 and discard 4 from feature |
1803 | + self.assertEquals('', self.run_bzr('rebase -r2..3 ../main')[0]) |
1804 | + # our rev 2 is now rev3 and 3 is now rev4: |
1805 | + self.assertEquals('4\n', self.run_bzr('revno')[0]) |
1806 | + # content added from our old revisions 4 should be gone. |
1807 | + self.assertPathDoesNotExist('hoooi') |
1808 | + |
1809 | + def test_range_open_end(self): |
1810 | + # commit mainline rev 2 |
1811 | + self.make_file('hello', '42') |
1812 | + self.run_bzr('commit -m that') |
1813 | + # commit feature rev 2 |
1814 | + os.chdir('../feature') |
1815 | + self.make_file('hoi', "my data") |
1816 | + self.run_bzr('add') |
1817 | + self.run_bzr('commit -m this') |
1818 | + # commit feature rev 3 |
1819 | + self.make_file('hooi', "your data") |
1820 | + self.run_bzr('add') |
1821 | + self.run_bzr('commit -m that') |
1822 | + # commit feature rev 4 |
1823 | + self.make_file('hoooi', "someone else's data") |
1824 | + self.run_bzr('add') |
1825 | + self.run_bzr('commit -m these') |
1826 | + # rebase only rev 4 onto main |
1827 | + self.assertEquals('', self.run_bzr('rebase -r4.. ../main')[0]) |
1828 | + # should only get rev 3 (our old 2 and 3 are gone) |
1829 | + self.assertEquals('3\n', self.run_bzr('revno')[0]) |
1830 | + self.assertPathDoesNotExist('hoi') |
1831 | + self.assertPathDoesNotExist('hooi') |
1832 | + branch = Branch.open(".") |
1833 | + self.assertEquals( |
1834 | + "these", |
1835 | + branch.repository.get_revision(branch.last_revision()).message) |
1836 | + self.assertPathExists('hoooi') |
1837 | + |
1838 | + def test_conflicting(self): |
1839 | + # commit mainline rev 2 |
1840 | + self.make_file('hello', '42') |
1841 | + self.run_bzr('commit -m that') |
1842 | + # commit feature rev 2 changing hello differently |
1843 | + os.chdir('../feature') |
1844 | + self.make_file('hello', "other data") |
1845 | + self.run_bzr('commit -m this') |
1846 | + self.run_bzr_error([ |
1847 | + 'Text conflict in hello\n1 conflicts encountered.\nbrz: ERROR: A conflict occurred replaying a commit. Resolve the conflict and run \'brz rebase-continue\' or run \'brz rebase-abort\'.', |
1848 | + ], ['rebase', '../main']) |
1849 | + |
1850 | + def test_conflicting_abort(self): |
1851 | + self.make_file('hello', '42') |
1852 | + self.run_bzr('commit -m that') |
1853 | + os.chdir('../feature') |
1854 | + self.make_file('hello', "other data") |
1855 | + self.run_bzr('commit -m this') |
1856 | + old_log = self.run_bzr('log')[0] |
1857 | + self.run_bzr_error(['Text conflict in hello\n1 conflicts encountered.\nbrz: ERROR: A conflict occurred replaying a commit. Resolve the conflict and run \'brz rebase-continue\' or run \'brz rebase-abort\'.\n'], ['rebase', '../main']) |
1858 | + self.assertEquals('', self.run_bzr('rebase-abort')[0]) |
1859 | + self.assertEquals(old_log, self.run_bzr('log')[0]) |
1860 | + |
1861 | + def test_conflicting_continue(self): |
1862 | + self.make_file('hello', '42') |
1863 | + self.run_bzr('commit -m that') |
1864 | + os.chdir('../feature') |
1865 | + self.make_file('hello', "other data") |
1866 | + self.run_bzr('commit -m this') |
1867 | + self.run_bzr_error(['Text conflict in hello\n1 conflicts encountered.\nbrz: ERROR: A conflict occurred replaying a commit. Resolve the conflict and run \'brz rebase-continue\' or run \'brz rebase-abort\'.\n'], ['rebase', '../main']) |
1868 | + self.run_bzr('resolved hello') |
1869 | + self.assertEquals('', self.run_bzr('rebase-continue')[0]) |
1870 | + self.assertEquals('3\n', self.run_bzr('revno')[0]) |
1871 | + |
1872 | + def test_continue_nothing(self): |
1873 | + self.run_bzr_error(['brz: ERROR: No rebase to continue'], |
1874 | + ['rebase-continue']) |
1875 | + |
1876 | + def test_abort_nothing(self): |
1877 | + self.run_bzr_error(['brz: ERROR: No rebase to abort'], |
1878 | + ['rebase-abort']) |
1879 | + |
1880 | + def test_todo_nothing(self): |
1881 | + self.run_bzr_error(['brz: ERROR: No rebase in progress'], |
1882 | + ['rebase-todo']) |
1883 | + |
1884 | + def test_onto(self): |
1885 | + self.make_file('hello', '42') |
1886 | + self.run_bzr('add') |
1887 | + self.run_bzr('commit -m that') |
1888 | + self.make_file('other', '43') |
1889 | + self.run_bzr('add') |
1890 | + self.run_bzr('commit -m that_other') |
1891 | + os.chdir('../feature') |
1892 | + self.make_file('hoi', "my data") |
1893 | + self.run_bzr('add') |
1894 | + self.run_bzr('commit -m this') |
1895 | + self.assertEquals( |
1896 | + '', self.run_bzr('rebase --onto -2 ../main')[0]) |
1897 | + self.assertEquals( |
1898 | + '3\n', self.run_bzr('revno')[0]) |
1899 | + |
1900 | + def test_unrelated(self): |
1901 | + os.chdir('..') |
1902 | + os.mkdir('unrelated') |
1903 | + os.chdir('unrelated') |
1904 | + self.run_bzr('init') |
1905 | + self.make_file('hello', "hi world") |
1906 | + self.run_bzr('add') |
1907 | + self.run_bzr('commit -m x') |
1908 | + self.run_bzr_error(['brz: ERROR: Branches have no common ancestor, and no merge base.*'], |
1909 | + ['rebase', '../main']) |
1910 | + |
1911 | + def test_verbose(self): |
1912 | + self.make_file('hello', '42') |
1913 | + self.run_bzr('commit -m that') |
1914 | + os.chdir('../feature') |
1915 | + self.make_file('hoi', "my data") |
1916 | + self.run_bzr('add') |
1917 | + self.run_bzr('commit -m this') |
1918 | + out, err = self.run_bzr('rebase -v ../main') |
1919 | + self.assertContainsRe(err, '1 revisions will be rebased:') |
1920 | + self.assertEqual('', out) |
1921 | + self.assertEqual('3\n', self.run_bzr('revno')[0]) |
1922 | + |
1923 | + def test_useless_merge(self): |
1924 | + self.make_file('bar', '42') |
1925 | + self.run_bzr('add') |
1926 | + self.run_bzr('commit -m that') |
1927 | + os.chdir('../feature') |
1928 | + self.make_file('hello', "my data") |
1929 | + self.run_bzr('commit -m this') |
1930 | + self.run_bzr('merge') |
1931 | + self.run_bzr('commit -m merge') |
1932 | + self.run_bzr('rebase') |
1933 | + |
1934 | + def strip_last_revid_part(self, revid): |
1935 | + """Assume revid is a revid in the default form, and strip the part |
1936 | + which would be random. |
1937 | + """ |
1938 | + return revid[:revid.rindex(b'-')] |
1939 | + |
1940 | + def test_always_rebase_merges(self): |
1941 | + trunk = self.make_branch_and_tree('trunk') |
1942 | + trunk.commit('base') |
1943 | + feature2 = trunk.controldir.sprout('feature2').open_workingtree() |
1944 | + revid2 = feature2.commit('change') |
1945 | + feature = trunk.controldir.sprout('feature').open_workingtree() |
1946 | + feature.commit('change') |
1947 | + feature.merge_from_branch(feature2.branch) |
1948 | + feature.commit('merge') |
1949 | + feature.commit('change2') |
1950 | + trunk.commit('additional upstream change') |
1951 | + self.run_bzr('rebase --always-rebase-merges ../trunk', working_dir='feature') |
1952 | + # second revision back should be a merge of feature2 |
1953 | + repo = feature.branch.repository |
1954 | + repo.lock_read() |
1955 | + self.addCleanup(repo.unlock) |
1956 | + tip = feature.last_revision() |
1957 | + merge_id = repo.get_graph().get_parent_map([tip])[tip][0] |
1958 | + merge_parents = repo.get_graph().get_parent_map([merge_id])[merge_id] |
1959 | + self.assertEqual(self.strip_last_revid_part(revid2), |
1960 | + self.strip_last_revid_part(merge_parents[1])) |
1961 | + |
1962 | + def test_rebase_merge(self): |
1963 | + trunk = self.make_branch_and_tree('trunk') |
1964 | + trunk.commit('base') |
1965 | + feature2 = trunk.controldir.sprout('feature2').open_workingtree() |
1966 | + revid2 = feature2.commit('change') |
1967 | + feature = trunk.controldir.sprout('feature').open_workingtree() |
1968 | + feature.commit('change') |
1969 | + feature.merge_from_branch(feature2.branch) |
1970 | + feature.commit('merge') |
1971 | + feature.commit('change2') |
1972 | + trunk.commit('additional upstream change') |
1973 | + self.run_bzr('rebase ../trunk', working_dir='feature') |
1974 | + # second revision back should be a merge of feature2 |
1975 | + repo = feature.branch.repository |
1976 | + repo.lock_read() |
1977 | + self.addCleanup(repo.unlock) |
1978 | + tip = feature.last_revision() |
1979 | + merge_id = repo.get_graph().get_parent_map([tip])[tip][0] |
1980 | + merge_parents = repo.get_graph().get_parent_map([merge_id])[merge_id] |
1981 | + self.assertEqual(self.strip_last_revid_part(revid2), |
1982 | + self.strip_last_revid_part(merge_parents[1])) |
1983 | + |
1984 | + def test_directory(self): |
1985 | + self.make_file('test_directory1', "testing non-current directories") |
1986 | + self.run_bzr('add') |
1987 | + self.run_bzr('commit -m blah') |
1988 | + os.chdir('../feature') |
1989 | + self.make_file('test_directory2', "testing non-current directories") |
1990 | + self.run_bzr('add') |
1991 | + self.run_bzr('commit -m blah') |
1992 | + os.chdir('..') |
1993 | + self.assertEquals('', self.run_bzr('rebase -d feature main')[0]) |
1994 | + |
1995 | + |
1996 | +class ReplayTests(ExternalBase): |
1997 | + |
1998 | + def test_replay(self): |
1999 | + os.mkdir('main') |
2000 | + os.chdir('main') |
2001 | + self.run_bzr('init') |
2002 | + open('bar', 'w').write('42') |
2003 | + self.run_bzr('add') |
2004 | + self.run_bzr('commit -m that') |
2005 | + os.mkdir('../feature') |
2006 | + os.chdir('../feature') |
2007 | + self.run_bzr('init') |
2008 | + branch = Branch.open('.') |
2009 | + open('hello', 'w').write("my data") |
2010 | + self.run_bzr('add') |
2011 | + self.run_bzr('commit -m this') |
2012 | + self.assertEquals(1, branch.revno()) |
2013 | + self.run_bzr('replay -r1 ../main') |
2014 | + self.assertEquals(2, branch.revno()) |
2015 | + self.assertTrue(os.path.exists('bar')) |
2016 | + |
2017 | + def test_replay_open_range(self): |
2018 | + os.mkdir('main') |
2019 | + os.chdir('main') |
2020 | + self.run_bzr('init') |
2021 | + open('bar', 'w').write('42') |
2022 | + self.run_bzr('add') |
2023 | + self.run_bzr('commit -m that') |
2024 | + open('bar', 'w').write('84') |
2025 | + self.run_bzr('commit -m blathat') |
2026 | + os.mkdir('../feature') |
2027 | + os.chdir('../feature') |
2028 | + self.run_bzr('init') |
2029 | + branch = Branch.open('.') |
2030 | + open('hello', 'w').write("my data") |
2031 | + self.run_bzr('add') |
2032 | + self.run_bzr('commit -m this') |
2033 | + self.assertEquals(1, branch.revno()) |
2034 | + self.run_bzr('replay -r1.. ../main') |
2035 | + self.assertEquals(3, branch.revno()) |
2036 | + self.assertTrue(os.path.exists('bar')) |
2037 | |
2038 | === added file 'breezy/plugins/rewrite/tests/test_maptree.py' |
2039 | --- breezy/plugins/rewrite/tests/test_maptree.py 1970-01-01 00:00:00 +0000 |
2040 | +++ breezy/plugins/rewrite/tests/test_maptree.py 2020-01-11 23:46:53 +0000 |
2041 | @@ -0,0 +1,81 @@ |
2042 | +# Copyright (C) 2006-2007 by Jelmer Vernooij |
2043 | +# |
2044 | +# This program is free software; you can redistribute it and/or modify |
2045 | +# it under the terms of the GNU General Public License as published by |
2046 | +# the Free Software Foundation; either version 2 of the License, or |
2047 | +# (at your option) any later version. |
2048 | +# |
2049 | +# This program is distributed in the hope that it will be useful, |
2050 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2051 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2052 | +# GNU General Public License for more details. |
2053 | +# |
2054 | +# You should have received a copy of the GNU General Public License |
2055 | +# along with this program; if not, write to the Free Software |
2056 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
2057 | +"""Tests for the maptree code.""" |
2058 | + |
2059 | +from ....tests import ( |
2060 | + TestCase, |
2061 | + TestCaseWithTransport, |
2062 | + ) |
2063 | +from ....treebuilder import ( |
2064 | + TreeBuilder, |
2065 | + ) |
2066 | + |
2067 | +from ..maptree import ( |
2068 | + MapTree, |
2069 | + map_file_ids, |
2070 | + ) |
2071 | + |
2072 | + |
2073 | +class EmptyMapTreeTests(TestCaseWithTransport): |
2074 | + |
2075 | + def setUp(self): |
2076 | + super(EmptyMapTreeTests, self).setUp() |
2077 | + tree = self.make_branch_and_tree('branch') |
2078 | + self.oldtree = tree |
2079 | + |
2080 | + def test_has_filename(self): |
2081 | + self.oldtree.lock_write() |
2082 | + builder = TreeBuilder() |
2083 | + builder.start_tree(self.oldtree) |
2084 | + builder.build(['foo']) |
2085 | + builder.finish_tree() |
2086 | + self.maptree = MapTree(self.oldtree, {}) |
2087 | + self.oldtree.unlock() |
2088 | + self.assertTrue(self.maptree.has_filename('foo')) |
2089 | + self.assertTrue(self.oldtree.has_filename('foo')) |
2090 | + self.assertFalse(self.maptree.has_filename('bar')) |
2091 | + |
2092 | + def test_path2id(self): |
2093 | + self.oldtree.lock_write() |
2094 | + self.addCleanup(self.oldtree.unlock) |
2095 | + builder = TreeBuilder() |
2096 | + builder.start_tree(self.oldtree) |
2097 | + builder.build(['foo']) |
2098 | + builder.build(['bar']) |
2099 | + builder.build(['bla']) |
2100 | + builder.finish_tree() |
2101 | + self.maptree = MapTree(self.oldtree, {}) |
2102 | + self.assertEquals(self.oldtree.path2id("foo"), |
2103 | + self.maptree.path2id("foo")) |
2104 | + |
2105 | + def test_id2path(self): |
2106 | + self.oldtree.lock_write() |
2107 | + self.addCleanup(self.oldtree.unlock) |
2108 | + builder = TreeBuilder() |
2109 | + builder.start_tree(self.oldtree) |
2110 | + builder.build(['foo']) |
2111 | + builder.build(['bar']) |
2112 | + builder.build(['bla']) |
2113 | + builder.finish_tree() |
2114 | + self.maptree = MapTree(self.oldtree, {}) |
2115 | + self.assertEquals( |
2116 | + "foo", self.maptree.id2path(self.maptree.path2id("foo"))) |
2117 | + |
2118 | + |
2119 | +class MapFileIdTests(TestCase): |
2120 | + |
2121 | + def test_empty(self): |
2122 | + self.assertEquals({}, map_file_ids(None, [], [])) |
2123 | |
2124 | === added file 'breezy/plugins/rewrite/tests/test_pseudonyms.py' |
2125 | --- breezy/plugins/rewrite/tests/test_pseudonyms.py 1970-01-01 00:00:00 +0000 |
2126 | +++ breezy/plugins/rewrite/tests/test_pseudonyms.py 2020-01-11 23:46:53 +0000 |
2127 | @@ -0,0 +1,39 @@ |
2128 | +# Copyright (C) 2010 by Jelmer Vernooij <jelmer@samba.org> |
2129 | +# |
2130 | +# This program is free software; you can redistribute it and/or modify |
2131 | +# it under the terms of the GNU General Public License as published by |
2132 | +# the Free Software Foundation; either version 2 of the License, or |
2133 | +# (at your option) any later version. |
2134 | +# |
2135 | +# This program is distributed in the hope that it will be useful, |
2136 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2137 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2138 | +# GNU General Public License for more details. |
2139 | +# |
2140 | +# You should have received a copy of the GNU General Public License |
2141 | +# along with this program; if not, write to the Free Software |
2142 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
2143 | + |
2144 | +"""Tests for pseudonym handling.""" |
2145 | + |
2146 | +from ....revision import Revision |
2147 | +from ....tests import TestCase |
2148 | + |
2149 | +from ..pseudonyms import extract_foreign_revids |
2150 | + |
2151 | + |
2152 | +class ExtractForeignRevidTests(TestCase): |
2153 | + |
2154 | + def test_no_foreign_revid(self): |
2155 | + x = Revision(b"myrevid") |
2156 | + self.assertEquals(set(), extract_foreign_revids(x)) |
2157 | + |
2158 | + def test_cscvs(self): |
2159 | + x = Revision(b"myrevid") |
2160 | + x.properties = { |
2161 | + "cscvs-svn-repository-uuid": "someuuid", |
2162 | + "cscvs-svn-revision-number": "4", |
2163 | + "cscvs-svn-branch-path": "/trunk"} |
2164 | + self.assertEquals( |
2165 | + set([("svn", "someuuid:4:trunk")]), |
2166 | + extract_foreign_revids(x)) |
2167 | |
2168 | === added file 'breezy/plugins/rewrite/tests/test_rebase.py' |
2169 | --- breezy/plugins/rewrite/tests/test_rebase.py 1970-01-01 00:00:00 +0000 |
2170 | +++ breezy/plugins/rewrite/tests/test_rebase.py 2020-01-11 23:46:53 +0000 |
2171 | @@ -0,0 +1,669 @@ |
2172 | +# Copyright (C) 2006-2007 by Jelmer Vernooij |
2173 | +# |
2174 | +# This program is free software; you can redistribute it and/or modify |
2175 | +# it under the terms of the GNU General Public License as published by |
2176 | +# the Free Software Foundation; either version 2 of the License, or |
2177 | +# (at your option) any later version. |
2178 | +# |
2179 | +# This program is distributed in the hope that it will be useful, |
2180 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2181 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2182 | +# GNU General Public License for more details. |
2183 | +# |
2184 | +# You should have received a copy of the GNU General Public License |
2185 | +# along with this program; if not, write to the Free Software |
2186 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
2187 | + |
2188 | +"""Tests for the rebase code.""" |
2189 | + |
2190 | +from ....conflicts import ConflictList |
2191 | +from ....errors import ( |
2192 | + UnknownFormatError, |
2193 | + NoSuchFile, |
2194 | + ConflictsInTree, |
2195 | + ) |
2196 | +from ....graph import ( |
2197 | + Graph, |
2198 | + DictParentsProvider, |
2199 | + ) |
2200 | +from ....revision import NULL_REVISION |
2201 | +from ....tests import TestCase, TestCaseWithTransport |
2202 | +from ....tests.matchers import RevisionHistoryMatches |
2203 | + |
2204 | +from ..rebase import ( |
2205 | + marshall_rebase_plan, |
2206 | + unmarshall_rebase_plan, |
2207 | + CommitBuilderRevisionRewriter, |
2208 | + generate_simple_plan, |
2209 | + generate_transpose_plan, |
2210 | + rebase_todo, |
2211 | + REBASE_PLAN_FILENAME, |
2212 | + REBASE_CURRENT_REVID_FILENAME, |
2213 | + RebaseState1, |
2214 | + ReplaySnapshotError, |
2215 | + WorkingTreeRevisionRewriter, |
2216 | + ) |
2217 | + |
2218 | + |
2219 | +class RebasePlanReadWriterTests(TestCase): |
2220 | + |
2221 | + def test_simple_marshall_rebase_plan(self): |
2222 | + self.assertEqualDiff(b"""\ |
2223 | +# Bazaar rebase plan 1 |
2224 | +1 bla |
2225 | +oldrev newrev newparent1 newparent2 |
2226 | +""", marshall_rebase_plan((1, b"bla"), |
2227 | + {b"oldrev": (b"newrev", (b"newparent1", b"newparent2"))})) |
2228 | + |
2229 | + def test_simple_unmarshall_rebase_plan(self): |
2230 | + self.assertEquals(((1, b"bla"), |
2231 | + {b"oldrev": (b"newrev", (b"newparent1", b"newparent2"))}), |
2232 | + unmarshall_rebase_plan(b"""# Bazaar rebase plan 1 |
2233 | +1 bla |
2234 | +oldrev newrev newparent1 newparent2 |
2235 | +""")) |
2236 | + |
2237 | + def test_unmarshall_rebase_plan_formatunknown(self): |
2238 | + self.assertRaises(UnknownFormatError, |
2239 | + unmarshall_rebase_plan, b"""# Bazaar rebase plan x |
2240 | +1 bla |
2241 | +oldrev newrev newparent1 newparent2 |
2242 | +""") |
2243 | + |
2244 | + |
2245 | +class ConversionTests(TestCaseWithTransport): |
2246 | + |
2247 | + def test_simple(self): |
2248 | + wt = self.make_branch_and_tree('.') |
2249 | + b = wt.branch |
2250 | + with open('hello', 'w') as f: |
2251 | + f.write('hello world') |
2252 | + wt.add('hello') |
2253 | + wt.commit(message='add hello', rev_id=b"bla") |
2254 | + with open('hello', 'w') as f: |
2255 | + f.write('world') |
2256 | + wt.commit(message='change hello', rev_id=b"bloe") |
2257 | + wt.set_last_revision(b"bla") |
2258 | + b.generate_revision_history(b"bla") |
2259 | + with open('hello', 'w') as f: |
2260 | + f.write('world') |
2261 | + wt.commit(message='change hello', rev_id=b"bla2") |
2262 | + |
2263 | + wt.branch.repository.lock_write() |
2264 | + newrev = CommitBuilderRevisionRewriter(wt.branch.repository)( |
2265 | + b"bla2", b"bla4", (b"bloe",)) |
2266 | + self.assertEqual(b"bla4", newrev) |
2267 | + self.assertTrue(wt.branch.repository.has_revision(newrev)) |
2268 | + self.assertEqual( |
2269 | + (b"bloe",), wt.branch.repository.get_parent_map([newrev])[newrev]) |
2270 | + self.assertEqual( |
2271 | + "bla2", wt.branch.repository.get_revision(newrev).properties["rebase-of"]) |
2272 | + wt.branch.repository.unlock() |
2273 | + |
2274 | + |
2275 | +class PlanCreatorTests(TestCaseWithTransport): |
2276 | + |
2277 | + def test_simple_plan_creator(self): |
2278 | + wt = self.make_branch_and_tree('.') |
2279 | + b = wt.branch |
2280 | + with open('hello', 'w') as f: |
2281 | + f.write('hello world') |
2282 | + wt.add('hello') |
2283 | + wt.commit(message='add hello', rev_id=b"bla") |
2284 | + with open('hello', 'w') as f: |
2285 | + f.write('world') |
2286 | + wt.commit(message='change hello', rev_id=b"bloe") |
2287 | + wt.set_last_revision(b"bla") |
2288 | + b.generate_revision_history(b"bla") |
2289 | + with open('hello', 'w') as f: |
2290 | + f.write('world') |
2291 | + wt.commit(message='change hello', rev_id=b"bla2") |
2292 | + |
2293 | + b.repository.lock_read() |
2294 | + graph = b.repository.get_graph() |
2295 | + self.assertEquals( |
2296 | + {b'bla2': (b'newbla2', (b"bloe",))}, |
2297 | + generate_simple_plan( |
2298 | + graph.find_difference(b.last_revision(), b"bla")[0], |
2299 | + b"bla2", None, b"bloe", graph, lambda y, _: b"new" + y)) |
2300 | + b.repository.unlock() |
2301 | + |
2302 | + def test_simple_plan_creator_extra_history(self): |
2303 | + wt = self.make_branch_and_tree('.') |
2304 | + b = wt.branch |
2305 | + with open('hello', 'w') as f: |
2306 | + f.write('hello world') |
2307 | + wt.add('hello') |
2308 | + wt.commit(message='add hello', rev_id=b"bla") |
2309 | + with open('hello', 'w') as f: |
2310 | + f.write('world') |
2311 | + wt.commit(message='change hello', rev_id=b"bloe") |
2312 | + wt.set_last_revision(b"bla") |
2313 | + b.generate_revision_history(b"bla") |
2314 | + with open('hello', 'w') as f: |
2315 | + f.write('world') |
2316 | + wt.commit(message='change hello', rev_id=b"bla2") |
2317 | + with open('hello', 'w') as f: |
2318 | + f.write('universe') |
2319 | + wt.commit(message='change hello again', rev_id=b"bla3") |
2320 | + |
2321 | + with b.repository.lock_read(): |
2322 | + graph = b.repository.get_graph() |
2323 | + self.assertEquals( |
2324 | + {b'bla2': (b'newbla2', (b"bloe",)), b'bla3': (b'newbla3', (b'newbla2',))}, |
2325 | + generate_simple_plan( |
2326 | + graph.find_difference(b.last_revision(), b"bloe")[0], |
2327 | + b"bla2", None, b"bloe", |
2328 | + graph, lambda y, _: b"new" + y)) |
2329 | + |
2330 | + def test_generate_transpose_plan(self): |
2331 | + wt = self.make_branch_and_tree('.') |
2332 | + b = wt.branch |
2333 | + with open('hello', 'w') as f: |
2334 | + f.write('hello world') |
2335 | + wt.add('hello') |
2336 | + wt.commit(message='add hello', rev_id=b"bla") |
2337 | + with open('hello', 'w') as f: |
2338 | + f.write('world') |
2339 | + wt.commit(message='change hello', rev_id=b"bloe") |
2340 | + wt.set_last_revision(b"bla") |
2341 | + b.generate_revision_history(b"bla") |
2342 | + with open('hello', 'w') as f: |
2343 | + f.write('world') |
2344 | + wt.commit(message='change hello', rev_id=b"bla2") |
2345 | + with open('hello', 'w') as f: |
2346 | + f.write('universe') |
2347 | + wt.commit(message='change hello again', rev_id=b"bla3") |
2348 | + wt.set_last_revision(b"bla") |
2349 | + b.generate_revision_history(b"bla") |
2350 | + with open('hello', 'w') as f: |
2351 | + f.write('somebar') |
2352 | + wt.commit(message='change hello yet again', rev_id=b"blie") |
2353 | + wt.set_last_revision(NULL_REVISION) |
2354 | + b.generate_revision_history(NULL_REVISION) |
2355 | + wt.add('hello') |
2356 | + wt.commit(message='add hello', rev_id=b"lala") |
2357 | + |
2358 | + b.repository.lock_read() |
2359 | + graph = b.repository.get_graph() |
2360 | + self.assertEquals({ |
2361 | + b'blie': (b'newblie', (b'lala',))}, |
2362 | + generate_transpose_plan(graph.iter_ancestry( |
2363 | + [b"blie"]), |
2364 | + {b"bla": b"lala"}, graph, lambda y, _: b"new" + y)) |
2365 | + self.assertEquals({ |
2366 | + b'bla2': (b'newbla2', (b'lala',)), |
2367 | + b'bla3': (b'newbla3', (b'newbla2',)), |
2368 | + b'blie': (b'newblie', (b'lala',)), |
2369 | + b'bloe': (b'newbloe', (b'lala',))}, |
2370 | + generate_transpose_plan(graph.iter_ancestry( |
2371 | + b.repository._all_revision_ids()), |
2372 | + {b"bla": b"lala"}, graph, lambda y, _: b"new" + y)) |
2373 | + b.repository.unlock() |
2374 | + |
2375 | + def test_generate_transpose_plan_one(self): |
2376 | + graph = Graph(DictParentsProvider({"bla": ("bloe",), "bloe": (), "lala": ()})) |
2377 | + self.assertEquals( |
2378 | + {"bla": ("newbla", ("lala",))}, |
2379 | + generate_transpose_plan(graph.iter_ancestry( |
2380 | + ["bla", "bloe"]), {"bloe": "lala"}, graph, lambda y, _: "new" + y)) |
2381 | + |
2382 | + def test_plan_with_already_merged(self): |
2383 | + """We need to use a merge base that makes sense. |
2384 | + |
2385 | + A |
2386 | + | \ |
2387 | + B D |
2388 | + | \| |
2389 | + C E |
2390 | + |
2391 | + Rebasing E on C should result in: |
2392 | + |
2393 | + A -> B -> C -> D -> E |
2394 | + |
2395 | + with a plan of: |
2396 | + |
2397 | + D -> (D', [C]) |
2398 | + E -> (E', [D', C]) |
2399 | + """ |
2400 | + parents_map = { |
2401 | + "A": (), |
2402 | + "B": ("A",), |
2403 | + "C": ("B",), |
2404 | + "D": ("A",), |
2405 | + "E": ("D", "B") |
2406 | + } |
2407 | + graph = Graph(DictParentsProvider(parents_map)) |
2408 | + self.assertEquals( |
2409 | + {"D": ("D'", ("C",)), "E": ("E'", ("D'",))}, |
2410 | + generate_simple_plan( |
2411 | + ["D", "E"], "D", None, "C", graph, lambda y, _: y + "'")) |
2412 | + |
2413 | + def test_plan_with_already_merged_skip_merges(self): |
2414 | + """We need to use a merge base that makes sense. |
2415 | + |
2416 | + A |
2417 | + | \ |
2418 | + B D |
2419 | + | \| |
2420 | + C E |
2421 | + |
2422 | + Rebasing E on C should result in: |
2423 | + |
2424 | + A -> B -> C -> D' |
2425 | + |
2426 | + with a plan of: |
2427 | + |
2428 | + D -> (D', [C]) |
2429 | + """ |
2430 | + parents_map = { |
2431 | + "A": (), |
2432 | + "B": ("A",), |
2433 | + "C": ("B",), |
2434 | + "D": ("A",), |
2435 | + "E": ("D", "B") |
2436 | + } |
2437 | + graph = Graph(DictParentsProvider(parents_map)) |
2438 | + self.assertEquals( |
2439 | + {"D": ("D'", ("C",))}, |
2440 | + generate_simple_plan( |
2441 | + ["D", "E"], "D", None, "C", graph, lambda y, _: y + "'", True)) |
2442 | + |
2443 | + |
2444 | +class RebaseStateTests(TestCaseWithTransport): |
2445 | + |
2446 | + def setUp(self): |
2447 | + super(RebaseStateTests, self).setUp() |
2448 | + self.wt = self.make_branch_and_tree('.') |
2449 | + self.state = RebaseState1(self.wt) |
2450 | + |
2451 | + def test_rebase_plan_exists_false(self): |
2452 | + self.assertFalse(self.state.has_plan()) |
2453 | + |
2454 | + def test_rebase_plan_exists_empty(self): |
2455 | + self.wt._transport.put_bytes(REBASE_PLAN_FILENAME, b"") |
2456 | + self.assertFalse(self.state.has_plan()) |
2457 | + |
2458 | + def test_rebase_plan_exists(self): |
2459 | + self.wt._transport.put_bytes(REBASE_PLAN_FILENAME, b"foo") |
2460 | + self.assertTrue(self.state.has_plan()) |
2461 | + |
2462 | + def test_remove_rebase_plan(self): |
2463 | + self.wt._transport.put_bytes(REBASE_PLAN_FILENAME, b"foo") |
2464 | + self.state.remove_plan() |
2465 | + self.assertFalse(self.state.has_plan()) |
2466 | + |
2467 | + def test_remove_rebase_plan_twice(self): |
2468 | + self.state.remove_plan() |
2469 | + self.assertFalse(self.state.has_plan()) |
2470 | + |
2471 | + def test_write_rebase_plan(self): |
2472 | + with open('hello', 'w') as f: |
2473 | + f.write('hello world') |
2474 | + self.wt.add('hello') |
2475 | + self.wt.commit(message='add hello', rev_id=b"bla") |
2476 | + self.state.write_plan( |
2477 | + {b"oldrev": (b"newrev", [b"newparent1", b"newparent2"])}) |
2478 | + self.assertEqualDiff(b"""# Bazaar rebase plan 1 |
2479 | +1 bla |
2480 | +oldrev newrev newparent1 newparent2 |
2481 | +""", self.wt._transport.get_bytes(REBASE_PLAN_FILENAME)) |
2482 | + |
2483 | + def test_read_rebase_plan_nonexistant(self): |
2484 | + self.assertRaises(NoSuchFile, self.state.read_plan) |
2485 | + |
2486 | + def test_read_rebase_plan_empty(self): |
2487 | + self.wt._transport.put_bytes(REBASE_PLAN_FILENAME, b"") |
2488 | + self.assertRaises(NoSuchFile, self.state.read_plan) |
2489 | + |
2490 | + def test_read_rebase_plan(self): |
2491 | + self.wt._transport.put_bytes( |
2492 | + REBASE_PLAN_FILENAME, |
2493 | + b"""# Bazaar rebase plan 1 |
2494 | +1 bla |
2495 | +oldrev newrev newparent1 newparent2 |
2496 | +""") |
2497 | + self.assertEquals( |
2498 | + ((1, b"bla"), {b"oldrev": (b"newrev", (b"newparent1", b"newparent2"))}), |
2499 | + self.state.read_plan()) |
2500 | + |
2501 | + def test_read_nonexistant(self): |
2502 | + self.assertIs(None, self.state.read_active_revid()) |
2503 | + |
2504 | + def test_read_null(self): |
2505 | + self.wt._transport.put_bytes(REBASE_CURRENT_REVID_FILENAME, NULL_REVISION) |
2506 | + self.assertIs(None, self.state.read_active_revid()) |
2507 | + |
2508 | + def test_read(self): |
2509 | + self.wt._transport.put_bytes(REBASE_CURRENT_REVID_FILENAME, b"bla") |
2510 | + self.assertEquals(b"bla", self.state.read_active_revid()) |
2511 | + |
2512 | + def test_write(self): |
2513 | + self.state.write_active_revid(b"bloe") |
2514 | + self.assertEquals(b"bloe", self.state.read_active_revid()) |
2515 | + |
2516 | + def test_write_null(self): |
2517 | + self.state.write_active_revid(None) |
2518 | + self.assertIs(None, self.state.read_active_revid()) |
2519 | + |
2520 | + |
2521 | +class RebaseTodoTests(TestCase): |
2522 | + |
2523 | + def test_done(self): |
2524 | + class Repository: |
2525 | + def has_revision(self, revid): |
2526 | + return revid == "bloe" |
2527 | + self.assertEquals( |
2528 | + [], list(rebase_todo(Repository(), {"bla": ("bloe", [])}))) |
2529 | + |
2530 | + def test_notstarted(self): |
2531 | + class Repository: |
2532 | + def has_revision(self, revid): |
2533 | + return False |
2534 | + self.assertEquals(["bla"], list(rebase_todo(Repository(), {"bla": ("bloe", [])}))) |
2535 | + |
2536 | + def test_halfway(self): |
2537 | + class Repository: |
2538 | + def has_revision(self, revid): |
2539 | + return revid == "bloe" |
2540 | + self.assertEquals( |
2541 | + ["ha"], |
2542 | + list(rebase_todo(Repository(), {"bla": ("bloe", []), "ha": ("hee", [])}))) |
2543 | + |
2544 | + |
2545 | +class ReplaySnapshotTests(TestCaseWithTransport): |
2546 | + |
2547 | + def test_single_revision(self): |
2548 | + wt = self.make_branch_and_tree(".") |
2549 | + self.build_tree(['afile']) |
2550 | + wt.add(["afile"]) |
2551 | + wt.commit("bla", rev_id=b"oldcommit") |
2552 | + wt.branch.repository.lock_write() |
2553 | + CommitBuilderRevisionRewriter(wt.branch.repository)(b"oldcommit", b"newcommit", ()) |
2554 | + wt.branch.repository.unlock() |
2555 | + oldrev = wt.branch.repository.get_revision(b"oldcommit") |
2556 | + newrev = wt.branch.repository.get_revision(b"newcommit") |
2557 | + self.assertEquals([], newrev.parent_ids) |
2558 | + self.assertEquals(b"newcommit", newrev.revision_id) |
2559 | + self.assertEquals(oldrev.committer, newrev.committer) |
2560 | + self.assertEquals(oldrev.timestamp, newrev.timestamp) |
2561 | + self.assertEquals(oldrev.timezone, newrev.timezone) |
2562 | + tree = wt.branch.repository.revision_tree(b"newcommit") |
2563 | + self.assertEquals(b"newcommit", tree.get_file_revision("afile")) |
2564 | + |
2565 | + def test_two_revisions(self): |
2566 | + wt = self.make_branch_and_tree("old") |
2567 | + self.build_tree_contents([('old/afile', 'afilecontents'), ('old/notherfile', 'notherfilecontents')]) |
2568 | + wt.add(["afile"], [b"somefileid"]) |
2569 | + wt.commit("bla", rev_id=b"oldparent") |
2570 | + wt.add(["notherfile"]) |
2571 | + wt.commit("bla", rev_id=b"oldcommit") |
2572 | + oldrepos = wt.branch.repository |
2573 | + wt = self.make_branch_and_tree("new") |
2574 | + self.build_tree_contents([('new/afile', 'afilecontents'), ('new/notherfile', 'notherfilecontents')]) |
2575 | + wt.add(["afile"], [b"afileid"]) |
2576 | + wt.commit("bla", rev_id=b"newparent") |
2577 | + wt.branch.repository.fetch(oldrepos) |
2578 | + wt.branch.repository.lock_write() |
2579 | + CommitBuilderRevisionRewriter(wt.branch.repository)( |
2580 | + b"oldcommit", b"newcommit", (b"newparent",)) |
2581 | + wt.branch.repository.unlock() |
2582 | + oldrev = wt.branch.repository.get_revision(b"oldcommit") |
2583 | + newrev = wt.branch.repository.get_revision(b"newcommit") |
2584 | + self.assertEquals([b"newparent"], newrev.parent_ids) |
2585 | + self.assertEquals(b"newcommit", newrev.revision_id) |
2586 | + self.assertEquals(oldrev.committer, newrev.committer) |
2587 | + self.assertEquals(oldrev.timestamp, newrev.timestamp) |
2588 | + self.assertEquals(oldrev.timezone, newrev.timezone) |
2589 | + tree = wt.branch.repository.revision_tree(b"newcommit") |
2590 | + self.assertEquals(b"afileid", tree.path2id("afile")) |
2591 | + self.assertEquals(b"newcommit", tree.get_file_revision("notherfile")) |
2592 | + |
2593 | + def test_two_revisions_no_renames(self): |
2594 | + wt = self.make_branch_and_tree("old") |
2595 | + self.build_tree(['old/afile', 'old/notherfile']) |
2596 | + wt.add(["afile"], [b"somefileid"]) |
2597 | + wt.commit("bla", rev_id=b"oldparent") |
2598 | + wt.add(["notherfile"]) |
2599 | + wt.commit("bla", rev_id=b"oldcommit") |
2600 | + oldrepos = wt.branch.repository |
2601 | + wt = self.make_branch_and_tree("new") |
2602 | + self.build_tree(['new/afile', 'new/notherfile']) |
2603 | + wt.add(["afile"], [b"afileid"]) |
2604 | + wt.commit("bla", rev_id=b"newparent") |
2605 | + wt.branch.repository.fetch(oldrepos) |
2606 | + wt.branch.repository.lock_write() |
2607 | + CommitBuilderRevisionRewriter(wt.branch.repository)( |
2608 | + b"oldcommit", b"newcommit", (b"newparent",)) |
2609 | + wt.branch.repository.unlock() |
2610 | + |
2611 | + def test_multi_revisions(self): |
2612 | + wt = self.make_branch_and_tree("old") |
2613 | + self.build_tree_contents( |
2614 | + [('old/afile', 'afilecontent'), ('old/sfile', 'sfilecontent'), ('old/notherfile', 'notherfilecontent')]) |
2615 | + wt.add(['sfile']) |
2616 | + wt.add(["afile"], [b"somefileid"]) |
2617 | + wt.commit("bla", rev_id=b"oldgrandparent") |
2618 | + open("old/afile", "w").write("data") |
2619 | + wt.commit("bla", rev_id=b"oldparent") |
2620 | + wt.add(["notherfile"]) |
2621 | + wt.commit("bla", rev_id=b"oldcommit") |
2622 | + oldrepos = wt.branch.repository |
2623 | + wt = self.make_branch_and_tree("new") |
2624 | + self.build_tree_contents( |
2625 | + [('new/afile', 'afilecontent'), ('new/sfile', 'sfilecontent'), ('new/notherfile', 'notherfilecontent')]) |
2626 | + wt.add(['sfile']) |
2627 | + wt.add(["afile"], [b"afileid"]) |
2628 | + wt.commit("bla", rev_id=b"newgrandparent") |
2629 | + open("new/afile", "w").write("data") |
2630 | + wt.commit("bla", rev_id=b"newparent") |
2631 | + wt.branch.repository.fetch(oldrepos) |
2632 | + wt.branch.repository.lock_write() |
2633 | + CommitBuilderRevisionRewriter(wt.branch.repository)( |
2634 | + b"oldcommit", b"newcommit", (b"newparent",)) |
2635 | + wt.branch.repository.unlock() |
2636 | + oldrev = wt.branch.repository.get_revision(b"oldcommit") |
2637 | + newrev = wt.branch.repository.get_revision(b"newcommit") |
2638 | + self.assertEquals([b"newparent"], newrev.parent_ids) |
2639 | + self.assertEquals(b"newcommit", newrev.revision_id) |
2640 | + self.assertEquals(oldrev.committer, newrev.committer) |
2641 | + self.assertEquals(oldrev.timestamp, newrev.timestamp) |
2642 | + self.assertEquals(oldrev.timezone, newrev.timezone) |
2643 | + tree = wt.branch.repository.revision_tree(b"newcommit") |
2644 | + self.assertEquals(b"afileid", tree.path2id("afile")) |
2645 | + self.assertEquals(b"newcommit", tree.get_file_revision("notherfile")) |
2646 | + self.assertEquals(b"newgrandparent", tree.get_file_revision("sfile")) |
2647 | + |
2648 | + def test_maps_ids(self): |
2649 | + wt = self.make_branch_and_tree("old") |
2650 | + wt.commit("base", rev_id=b"base") |
2651 | + self.build_tree(['old/afile']) |
2652 | + wt.add(["afile"], ids=[b"originalid"]) |
2653 | + wt.commit("bla", rev_id=b"oldparent") |
2654 | + with open("old/afile", "w") as f: |
2655 | + f.write("bloe") |
2656 | + wt.commit("bla", rev_id=b"oldcommit") |
2657 | + oldrepos = wt.branch.repository |
2658 | + wt = self.make_branch_and_tree("new") |
2659 | + self.build_tree(['new/afile']) |
2660 | + wt.add(["afile"], ids=[b"newid"]) |
2661 | + wt.commit("bla", rev_id=b"newparent") |
2662 | + wt.branch.repository.fetch(oldrepos) |
2663 | + wt.branch.repository.lock_write() |
2664 | + CommitBuilderRevisionRewriter(wt.branch.repository)( |
2665 | + b"oldcommit", b"newcommit", (b"newparent",)) |
2666 | + wt.branch.repository.unlock() |
2667 | + oldrev = wt.branch.repository.get_revision(b"oldcommit") |
2668 | + newrev = wt.branch.repository.get_revision(b"newcommit") |
2669 | + self.assertEquals([b"newparent"], newrev.parent_ids) |
2670 | + self.assertEquals(b"newcommit", newrev.revision_id) |
2671 | + self.assertEquals(oldrev.committer, newrev.committer) |
2672 | + self.assertEquals(oldrev.timestamp, newrev.timestamp) |
2673 | + self.assertEquals(oldrev.timezone, newrev.timezone) |
2674 | + tree = wt.branch.repository.revision_tree(b"newcommit") |
2675 | + self.assertEquals(b"newid", tree.path2id("afile")) |
2676 | + self.assertEquals(b"newcommit", tree.get_file_revision("afile")) |
2677 | + |
2678 | + |
2679 | +class TestReplayWorkingtree(TestCaseWithTransport): |
2680 | + def test_conflicts(self): |
2681 | + wt = self.make_branch_and_tree("old") |
2682 | + wt.commit("base", rev_id=b"base") |
2683 | + self.build_tree(['old/afile']) |
2684 | + wt.add(["afile"], ids=[b"originalid"]) |
2685 | + wt.commit("bla", rev_id=b"oldparent") |
2686 | + with open("old/afile", "w") as f: |
2687 | + f.write("bloe") |
2688 | + wt.commit("bla", rev_id=b"oldcommit") |
2689 | + oldrepos = wt.branch.repository |
2690 | + wt = self.make_branch_and_tree("new") |
2691 | + self.build_tree(['new/afile']) |
2692 | + wt.add(["afile"], ids=[b"newid"]) |
2693 | + wt.commit("bla", rev_id=b"newparent") |
2694 | + wt.branch.repository.fetch(oldrepos) |
2695 | + with wt.lock_write(): |
2696 | + replayer = WorkingTreeRevisionRewriter(wt, RebaseState1(wt)) |
2697 | + self.assertRaises( |
2698 | + ConflictsInTree, |
2699 | + replayer, b"oldcommit", b"newcommit", |
2700 | + [b"newparent"], ) |
2701 | + |
2702 | + def test_simple(self): |
2703 | + wt = self.make_branch_and_tree("old") |
2704 | + wt.commit("base", rev_id=b"base") |
2705 | + self.build_tree(['old/afile']) |
2706 | + wt.add(["afile"], ids=[b"originalid"]) |
2707 | + wt.commit("bla", rev_id=b"oldparent") |
2708 | + with open("old/afile", "w") as f: |
2709 | + f.write("bloe") |
2710 | + wt.commit("bla", rev_id=b"oldcommit") |
2711 | + wt = wt.controldir.sprout("new").open_workingtree() |
2712 | + self.build_tree(['new/bfile']) |
2713 | + wt.add(["bfile"], ids=[b"newid"]) |
2714 | + wt.commit("bla", rev_id=b"newparent") |
2715 | + replayer = WorkingTreeRevisionRewriter(wt, RebaseState1(wt)) |
2716 | + replayer(b"oldcommit", b"newcommit", [b"newparent"]) |
2717 | + oldrev = wt.branch.repository.get_revision(b"oldcommit") |
2718 | + newrev = wt.branch.repository.get_revision(b"newcommit") |
2719 | + self.assertEquals([b"newparent"], newrev.parent_ids) |
2720 | + self.assertEquals(b"newcommit", newrev.revision_id) |
2721 | + self.assertEquals(oldrev.timestamp, newrev.timestamp) |
2722 | + self.assertEquals(oldrev.timezone, newrev.timezone) |
2723 | + |
2724 | + def test_multiple(self): |
2725 | + # rebase from |
2726 | + # base: [] |
2727 | + # oldparent: [base] |
2728 | + # newparent: [base] |
2729 | + # oldcommit: [oldparent, ghost] |
2730 | + # create newcommit by rebasing oldcommit from oldparent to newparent, |
2731 | + # keeping the merge of ghost. |
2732 | + # Common base: |
2733 | + wt = self.make_branch_and_tree("old") |
2734 | + wt.commit("base", rev_id=b"base") |
2735 | + # oldparent: |
2736 | + self.build_tree_contents([('old/afile', 'base content')]) |
2737 | + wt.add(["afile"], ids=[b"originalid"]) |
2738 | + wt.commit("bla", rev_id=b"oldparent") |
2739 | + # oldcommit (the delta getting rebased) |
2740 | + # - change the content of afile to be 'bloe' |
2741 | + with open("old/afile", "w") as f: |
2742 | + f.write("bloe") |
2743 | + wt.add_pending_merge(b"ghost") |
2744 | + wt.commit("bla", rev_id=b"oldcommit") |
2745 | + # newparent (the new base for the rebased commit) |
2746 | + new_tree = wt.controldir.sprout( |
2747 | + "new", revision_id=b'base').open_workingtree() |
2748 | + new_tree.branch.repository.fetch(wt.branch.repository) |
2749 | + wt = new_tree |
2750 | + self.build_tree_contents([('new/afile', 'base content')]) |
2751 | + wt.add(["afile"], ids=[b"originalid"]) |
2752 | + wt.commit("bla", rev_id=b"newparent") |
2753 | + # And do it! |
2754 | + wt.lock_write() |
2755 | + replayer = WorkingTreeRevisionRewriter(wt, RebaseState1(wt)) |
2756 | + replayer(b"oldcommit", b"newcommit", (b"newparent", b"ghost")) |
2757 | + wt.unlock() |
2758 | + oldrev = wt.branch.repository.get_revision(b"oldcommit") |
2759 | + newrev = wt.branch.repository.get_revision(b"newcommit") |
2760 | + self.assertEquals([b"oldparent", b"ghost"], oldrev.parent_ids) |
2761 | + self.assertEquals([b"newparent", b"ghost"], newrev.parent_ids) |
2762 | + self.assertEquals(b"newcommit", newrev.revision_id) |
2763 | + self.assertEquals(oldrev.timestamp, newrev.timestamp) |
2764 | + self.assertEquals(oldrev.timezone, newrev.timezone) |
2765 | + |
2766 | + def test_already_merged(self): |
2767 | + """We need to use a merge base that makes sense. |
2768 | + |
2769 | + A |
2770 | + | \ |
2771 | + B D |
2772 | + | \| |
2773 | + C E |
2774 | + |
2775 | + Rebasing E on C should result in: |
2776 | + |
2777 | + A -> B -> C -> D' -> E' |
2778 | + |
2779 | + Ancestry: |
2780 | + A: |
2781 | + B: A |
2782 | + C: A, B |
2783 | + D: A |
2784 | + E: A, B, D |
2785 | + D': A, B, C |
2786 | + E': A, B, C, D' |
2787 | + |
2788 | + """ |
2789 | + oldwt = self.make_branch_and_tree("old") |
2790 | + self.build_tree(['old/afile']) |
2791 | + with open("old/afile", "w") as f: |
2792 | + f.write("A\n" * 10) |
2793 | + oldwt.add(["afile"]) |
2794 | + oldwt.commit("base", rev_id=b"A") |
2795 | + newwt = oldwt.controldir.sprout("new").open_workingtree() |
2796 | + with open("old/afile", "w") as f: |
2797 | + f.write("A\n" * 10 + "B\n") |
2798 | + oldwt.commit("bla", rev_id=b"B") |
2799 | + with open("old/afile", "w") as f: |
2800 | + f.write("A\n" * 10 + "C\n") |
2801 | + oldwt.commit("bla", rev_id=b"C") |
2802 | + self.build_tree(['new/bfile']) |
2803 | + newwt.add(["bfile"]) |
2804 | + with open("new/bfile", "w") as f: |
2805 | + f.write("D\n") |
2806 | + newwt.commit("bla", rev_id=b"D") |
2807 | + with open("new/afile", "w") as f: |
2808 | + f.write("E\n" + "A\n" * 10 + "B\n") |
2809 | + with open("new/bfile", "w") as f: |
2810 | + f.write("D\nE\n") |
2811 | + newwt.add_pending_merge(b"B") |
2812 | + newwt.commit("bla", rev_id=b"E") |
2813 | + newwt.branch.repository.fetch(oldwt.branch.repository) |
2814 | + newwt.lock_write() |
2815 | + replayer = WorkingTreeRevisionRewriter(newwt, RebaseState1(newwt)) |
2816 | + replayer(b"D", b"D'", [b"C"]) |
2817 | + newwt.unlock() |
2818 | + oldrev = newwt.branch.repository.get_revision(b"D") |
2819 | + newrev = newwt.branch.repository.get_revision(b"D'") |
2820 | + self.assertEquals([b"C"], newrev.parent_ids) |
2821 | + newwt.lock_write() |
2822 | + replayer = WorkingTreeRevisionRewriter(newwt, RebaseState1(newwt)) |
2823 | + self.assertRaises(ConflictsInTree, replayer, b"E", b"E'", [b"D'"]) |
2824 | + newwt.unlock() |
2825 | + with open("new/afile", 'r') as f: |
2826 | + self.assertEquals("E\n" + "A\n" * 10 + "C\n", f.read()) |
2827 | + newwt.set_conflicts(ConflictList()) |
2828 | + oldrev = newwt.branch.repository.get_revision(b"E") |
2829 | + replayer.commit_rebase(oldrev, b"E'") |
2830 | + newrev = newwt.branch.repository.get_revision(b"E'") |
2831 | + self.assertEquals([b"D'"], newrev.parent_ids) |
2832 | + self.assertThat( |
2833 | + newwt.branch, |
2834 | + RevisionHistoryMatches([b"A", b"B", b"C", b"D'", b"E'"])) |
2835 | + |
2836 | + |
2837 | +class TestReplaySnapshotError(TestCase): |
2838 | + |
2839 | + def test_create(self): |
2840 | + ReplaySnapshotError("message") |
2841 | |
2842 | === added file 'breezy/plugins/rewrite/tests/test_upgrade.py' |
2843 | --- breezy/plugins/rewrite/tests/test_upgrade.py 1970-01-01 00:00:00 +0000 |
2844 | +++ breezy/plugins/rewrite/tests/test_upgrade.py 2020-01-11 23:46:53 +0000 |
2845 | @@ -0,0 +1,32 @@ |
2846 | +# Copyright (C) 2007-2009 Jelmer Vernooij <jelmer@samba.org> |
2847 | + |
2848 | +# This program is free software; you can redistribute it and/or modify |
2849 | +# it under the terms of the GNU General Public License as published by |
2850 | +# the Free Software Foundation; either version 2 of the License, or |
2851 | +# (at your option) any later version. |
2852 | + |
2853 | +# This program is distributed in the hope that it will be useful, |
2854 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2855 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2856 | +# GNU General Public License for more details. |
2857 | + |
2858 | +# You should have received a copy of the GNU General Public License |
2859 | +# along with this program; if not, write to the Free Software |
2860 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
2861 | + |
2862 | +"""Mapping upgrade tests.""" |
2863 | + |
2864 | +from ....tests import ( |
2865 | + TestCase, |
2866 | + ) |
2867 | + |
2868 | +from ..upgrade import ( |
2869 | + UpgradeChangesContent, |
2870 | + ) |
2871 | + |
2872 | + |
2873 | +class TestUpgradeChangesContent(TestCase): |
2874 | + |
2875 | + def test_init(self): |
2876 | + x = UpgradeChangesContent("revisionx") |
2877 | + self.assertEqual("revisionx", x.revid) |
2878 | |
2879 | === added file 'breezy/plugins/rewrite/upgrade.py' |
2880 | --- breezy/plugins/rewrite/upgrade.py 1970-01-01 00:00:00 +0000 |
2881 | +++ breezy/plugins/rewrite/upgrade.py 2020-01-11 23:46:53 +0000 |
2882 | @@ -0,0 +1,190 @@ |
2883 | +# Copyright (C) 2006-2009 by Jelmer Vernooij |
2884 | +# |
2885 | +# This program is free software; you can redistribute it and/or modify |
2886 | +# it under the terms of the GNU General Public License as published by |
2887 | +# the Free Software Foundation; either version 3 of the License, or |
2888 | +# (at your option) any later version. |
2889 | +# |
2890 | +# This program is distributed in the hope that it will be useful, |
2891 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2892 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2893 | +# GNU General Public License for more details. |
2894 | +# |
2895 | +# You should have received a copy of the GNU General Public License |
2896 | +# along with this program; if not, write to the Free Software |
2897 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
2898 | + |
2899 | + |
2900 | +"""Upgrading revisions made with older versions of the mapping.""" |
2901 | + |
2902 | +from __future__ import absolute_import |
2903 | + |
2904 | +from ... import ( |
2905 | + osutils, |
2906 | + trace, |
2907 | + ui, |
2908 | + ) |
2909 | +from ...errors import ( |
2910 | + BzrError, |
2911 | + ) |
2912 | +from .rebase import ( |
2913 | + generate_transpose_plan, |
2914 | + CommitBuilderRevisionRewriter, |
2915 | + rebase, |
2916 | + rebase_todo, |
2917 | + ) |
2918 | + |
2919 | + |
2920 | +class UpgradeChangesContent(BzrError): |
2921 | + """Inconsistency was found upgrading the mapping of a revision.""" |
2922 | + _fmt = """Upgrade will change contents in revision %(revid)s. Use --allow-changes to override.""" |
2923 | + |
2924 | + def __init__(self, revid): |
2925 | + self.revid = revid |
2926 | + |
2927 | + |
2928 | +def create_deterministic_revid(revid, new_parents): |
2929 | + """Create a new deterministic revision id with specified new parents. |
2930 | + |
2931 | + Prevents suffix to be appended needlessly. |
2932 | + |
2933 | + :param revid: Original revision id. |
2934 | + :return: New revision id |
2935 | + """ |
2936 | + if "-rebase-" in revid: |
2937 | + revid = revid[0:revid.rfind("-rebase-")] |
2938 | + return revid + "-rebase-" + osutils.sha_string(":".join(new_parents))[:8] |
2939 | + |
2940 | + |
2941 | +def upgrade_tags(tags, repository, generate_rebase_map, determine_new_revid, |
2942 | + allow_changes=False, verbose=False, branch_renames=None, |
2943 | + branch_ancestry=None): |
2944 | + """Upgrade a tags dictionary.""" |
2945 | + renames = {} |
2946 | + if branch_renames is not None: |
2947 | + renames.update(branch_renames) |
2948 | + pb = ui.ui_factory.nested_progress_bar() |
2949 | + try: |
2950 | + tags_dict = tags.get_tag_dict() |
2951 | + for i, (name, revid) in enumerate(tags_dict.iteritems()): |
2952 | + pb.update("upgrading tags", i, len(tags_dict)) |
2953 | + if revid not in renames: |
2954 | + try: |
2955 | + repository.lock_read() |
2956 | + revid_exists = repository.has_revision(revid) |
2957 | + finally: |
2958 | + repository.unlock() |
2959 | + if revid_exists: |
2960 | + renames.update(upgrade_repository( |
2961 | + repository, |
2962 | + generate_rebase_map, determine_new_revid, |
2963 | + revision_id=revid, allow_changes=allow_changes, |
2964 | + verbose=verbose)) |
2965 | + if (revid in renames and |
2966 | + (branch_ancestry is None or revid not in branch_ancestry)): |
2967 | + tags.set_tag(name, renames[revid]) |
2968 | + finally: |
2969 | + pb.finished() |
2970 | + |
2971 | + |
2972 | +def upgrade_branch(branch, generate_rebase_map, determine_new_revid, |
2973 | + allow_changes=False, verbose=False): |
2974 | + """Upgrade a branch to the current mapping version. |
2975 | + |
2976 | + :param branch: Branch to upgrade. |
2977 | + :param foreign_repository: Repository to fetch new revisions from |
2978 | + :param allow_changes: Allow changes in mappings. |
2979 | + :param verbose: Whether to print verbose list of rewrites |
2980 | + """ |
2981 | + revid = branch.last_revision() |
2982 | + renames = upgrade_repository( |
2983 | + branch.repository, generate_rebase_map, |
2984 | + determine_new_revid, revision_id=revid, |
2985 | + allow_changes=allow_changes, verbose=verbose) |
2986 | + if revid in renames: |
2987 | + branch.generate_revision_history(renames[revid]) |
2988 | + ancestry = branch.repository.get_ancestry( |
2989 | + branch.last_revision(), topo_sorted=False) |
2990 | + upgrade_tags( |
2991 | + branch.tags, branch.repository, generate_rebase_map, |
2992 | + determine_new_revid, allow_changes=allow_changes, verbose=verbose, |
2993 | + branch_renames=renames, branch_ancestry=ancestry) |
2994 | + return renames |
2995 | + |
2996 | + |
2997 | +def check_revision_changed(oldrev, newrev): |
2998 | + """Check if two revisions are different. This is exactly the same |
2999 | + as Revision.equals() except that it does not check the revision_id.""" |
3000 | + if (newrev.inventory_sha1 != oldrev.inventory_sha1 or |
3001 | + newrev.timestamp != oldrev.timestamp or |
3002 | + newrev.message != oldrev.message or |
3003 | + newrev.timezone != oldrev.timezone or |
3004 | + newrev.committer != oldrev.committer or |
3005 | + newrev.properties != oldrev.properties): |
3006 | + raise UpgradeChangesContent(oldrev.revision_id) |
3007 | + |
3008 | + |
3009 | +def create_upgrade_plan(repository, generate_rebase_map, determine_new_revid, |
3010 | + revision_id=None, allow_changes=False): |
3011 | + """Generate a rebase plan for upgrading revisions. |
3012 | + |
3013 | + :param repository: Repository to do upgrade in |
3014 | + :param foreign_repository: Subversion repository to fetch new revisions |
3015 | + from. |
3016 | + :param new_mapping: New mapping to use. |
3017 | + :param revision_id: Revision to upgrade (None for all revisions in |
3018 | + repository.) |
3019 | + :param allow_changes: Whether an upgrade is allowed to change the contents |
3020 | + of revisions. |
3021 | + :return: Tuple with a rebase plan and map of renamed revisions. |
3022 | + """ |
3023 | + |
3024 | + graph = repository.get_graph() |
3025 | + upgrade_map = generate_rebase_map(revision_id) |
3026 | + |
3027 | + if not allow_changes: |
3028 | + for oldrevid, newrevid in upgrade_map.iteritems(): |
3029 | + oldrev = repository.get_revision(oldrevid) |
3030 | + newrev = repository.get_revision(newrevid) |
3031 | + check_revision_changed(oldrev, newrev) |
3032 | + |
3033 | + if revision_id is None: |
3034 | + heads = repository.all_revision_ids() |
3035 | + else: |
3036 | + heads = [revision_id] |
3037 | + |
3038 | + plan = generate_transpose_plan( |
3039 | + graph.iter_ancestry(heads), upgrade_map, graph, determine_new_revid) |
3040 | + def remove_parents(entry): |
3041 | + (oldrevid, (newrevid, parents)) = entry |
3042 | + return (oldrevid, newrevid) |
3043 | + upgrade_map.update(dict(map(remove_parents, plan.iteritems()))) |
3044 | + |
3045 | + return (plan, upgrade_map) |
3046 | + |
3047 | + |
3048 | +def upgrade_repository(repository, generate_rebase_map, |
3049 | + determine_new_revid, revision_id=None, |
3050 | + allow_changes=False, verbose=False): |
3051 | + """Upgrade the revisions in repository until the specified stop revision. |
3052 | + |
3053 | + :param repository: Repository in which to upgrade. |
3054 | + :param foreign_repository: Repository to fetch new revisions from. |
3055 | + :param new_mapping: New mapping. |
3056 | + :param revision_id: Revision id up until which to upgrade, or None for |
3057 | + all revisions. |
3058 | + :param allow_changes: Allow changes to mappings. |
3059 | + :param verbose: Whether to print list of rewrites |
3060 | + :return: Dictionary of mapped revisions |
3061 | + """ |
3062 | + # Find revisions that need to be upgraded, create |
3063 | + # dictionary with revision ids in key, new parents in value |
3064 | + with repository.lock_write(): |
3065 | + (plan, revid_renames) = create_upgrade_plan( |
3066 | + repository, generate_rebase_map, determine_new_revid, |
3067 | + revision_id=revision_id, allow_changes=allow_changes) |
3068 | + if verbose: |
3069 | + for revid in rebase_todo(repository, plan): |
3070 | + trace.note("%s -> %s" % (revid, plan[revid][0])) |
3071 | + rebase(repository, plan, CommitBuilderRevisionRewriter(repository)) |
3072 | + return revid_renames |
Running landing tests failed /ci.breezy- vcs.org/ job/brz/ job/brz- land/591/
https:/