Merge lp:bzr-loom into lp:~launchpad-pqm/bzr-loom/trunk

Proposed by Aaron Bentley
Status: Merged
Approved by: Paul Hummer
Approved revision: 126
Merged at revision: 48
Proposed branch: lp:bzr-loom
Merge into: lp:~launchpad-pqm/bzr-loom/trunk
Diff against target: 2313 lines (+1033/-321)
18 files modified
.testr.conf (+4/-0)
HOWTO (+19/-1)
NEWS (+102/-2)
README (+5/-3)
TODO (+1/-2)
__init__.py (+26/-7)
branch.py (+226/-126)
commands.py (+107/-40)
formats.py (+74/-0)
revspec.py (+67/-32)
setup.py (+3/-5)
tests/__init__.py (+4/-1)
tests/blackbox.py (+147/-19)
tests/test_branch.py (+84/-35)
tests/test_revspec.py (+49/-5)
tests/test_tree.py (+29/-13)
tree.py (+58/-30)
version.py (+28/-0)
To merge this branch: bzr merge lp:bzr-loom
Reviewer Review Type Date Requested Status
Paul Hummer (community) Approve
Review via email: mp+31873@code.launchpad.net

Commit message

Update to tip for bzr 2.2 support.

Description of the change

Update Launchpad copy of bzr-loom to trunk tip for bzr 2.2 support.
(Otherwise, you get API breakage.)

To post a comment you must log in.
Revision history for this message
Paul Hummer (rockstar) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file '.testr.conf'
--- .testr.conf 1970-01-01 00:00:00 +0000
+++ .testr.conf 2010-08-05 18:57:44 +0000
@@ -0,0 +1,4 @@
1[DEFAULT]
2# not quite idea, because 'all tests' is too many, and 'just loom' is too few.
3test_command=bzr selftest --subunit $IDOPTION
4test_id_option=--load-list $IDFILE
05
=== modified file 'HOWTO'
--- HOWTO 2008-09-12 01:55:17 +0000
+++ HOWTO 2010-08-05 18:57:44 +0000
@@ -47,6 +47,11 @@
4747
48This will convert your branch to a loom - at this point you will require the48This will convert your branch to a loom - at this point you will require the
49loom plugin to use bzr on it. It will also create a thread called 'upstream'.49loom plugin to use bzr on it. It will also create a thread called 'upstream'.
50
51The bzr ``nick`` is used to index into the threads in the loom. The current
52thread is the bzr nick for the branch and is recorded in commits as such. You
53can use ``bzr nick newname`` to change the name of a thread.
54
50If you do 'bzr push' to a new branch now, it will make the remote branch be a55If you do 'bzr push' to a new branch now, it will make the remote branch be a
51loom as well. If you push to an existing normal bzr branch, then the current56loom as well. If you push to an existing normal bzr branch, then the current
52thread of your loom is what will be pushed. You can use this to publish57thread of your loom is what will be pushed. You can use this to publish
@@ -177,7 +182,7 @@
177When you are doing an update to upstream and they have merged a patch, your182When you are doing an update to upstream and they have merged a patch, your
178thread will suddenly lose all its changes. Lets say in the example above that183thread will suddenly lose all its changes. Lets say in the example above that
179upstream have merged the autoconf update. When you are updating that thread,184upstream have merged the autoconf update. When you are updating that thread,
180add in a call to ``diff -r thread:`` and you will see no changes.185add in a call to ``diff -r below:`` and you will see no changes.
181186
182 % bzr up-thread187 % bzr up-thread
183 All changes applied successfully.188 All changes applied successfully.
@@ -201,3 +206,16 @@
201 debian206 debian
202 =>fix 64-bit compilation207 =>fix 64-bit compilation
203 upstream208 upstream
209
210
211Showing a single patch
212----------------------
213
214You can show a single patch without knowing the names of other threads by using
215the below: and thread: revision specifiers::
216
217 % bzr show-loom
218 debian
219 =>update-configure
220 upstream
221 % bzr diff -r below:update-configure..thread:update-configure
204222
=== modified file 'NEWS'
--- NEWS 2008-11-19 07:18:17 +0000
+++ NEWS 2010-08-05 18:57:44 +0000
@@ -5,7 +5,95 @@
5.. contents::5.. contents::
66
7IN DEVELOPMENT7IN DEVELOPMENT
8--------------8==============
9
10NOTES WHEN UPGRADING
11--------------------
12
13* bzr-loom requires bzr 2.2.0 (or very recent 2.2b releases) due to an API
14 change in bzr needed to fix branching and pulling of looms. On older versions
15 of bzr bzr-loom will still work for most operations but will fail when making
16 new branches as part of a push or branch operation. (Robert Collins, #201613)
17
18CHANGES
19-------
20
21* --auto is now the default on up-thread. You can supply a thread name to stop
22 at a given thread, or --manual to go up a single thread. (Aaron Bentley)
23
24* ``bzr combine-thread`` now accepts a ``--force`` option.
25
26FEATURES
27--------
28
29* A new revision specifier ``below:`` has been added. (Robert Collins, #195282)
30
31IMPROVEMENTS
32------------
33
34* Loom now takes advantage of lazy loading of bzr objects (though not to a
35 complete degree), reducing the overhead of having it installed.
36 (Robert Collins)
37
38BUGFIXES
39--------
40
41* ``bzr combine-thread`` will no longer combine threads without ``--force``
42 when the thread being removed has work not merged into either the thread
43 above or below. (Robert Collins, #506235)
44
45* ``bzr loomify`` explicitly checks that branches being converted are not Looms
46 already. This should not have been needed, but apparently it was.
47 (Robert Collins, #600452)
48
49* ``bzr nick`` will now rename a thread rather than setting the current thread
50 pointer to an invalid value. (Robert Collins, #203203, #260947, #304608)
51
52* ``bzr nick`` will now rename the branch too. (Vincent Ladeuil, #606174)
53
54* ``switch`` now accepts the ``--directory`` option. (Vincent Ladeuil, #595563)
55
56* The ``thread:`` revision specifier will no longer throw an attribute error
57 when used on a normal branch. (Robert Collins, #231283)
58
59API BREAKS
60----------
61
62TESTING
63-------
64
65INTERNALS
66---------
67
682.1
69===
70
71 NOTES WHEN UPGRADING:
72
73 CHANGES:
74
75 FEATURES:
76
77 IMPROVEMENTS:
78
79 BUGFIXES:
80
81 * Stop using APIs deprecated for 2.1.0 (child progress bars for
82 merge and trace.info). (Vincent Ladeuil, #528472)
83
84 * Work with changes to bzr trunk - colocated branches and switch -r.
85
86 API BREAKS:
87
88 TESTING:
89
90 INTERNALS:
91
92 * .testr.conf added to help use with testr - still need to specify what tests
93 to run. (Robert Collins)
94
952.0
96===
997
10 NOTES WHEN UPGRADING:98 NOTES WHEN UPGRADING:
11 99
@@ -21,14 +109,26 @@
21 * ``bzr switch`` now accepts ``top:`` and ``bottom:`` to jump to the top109 * ``bzr switch`` now accepts ``top:`` and ``bottom:`` to jump to the top
22 and bottom thread respectively. (Jonathan Lange)110 and bottom thread respectively. (Jonathan Lange)
23111
112 * ``bzr switch -b newthread`` now works. (Robert Collins, #433811)
113
24 * ``bzr push`` now pushes the last-loom rather than creating an empty loom.114 * ``bzr push`` now pushes the last-loom rather than creating an empty loom.
25 (Robert Collins, #201613)115 (Robert Collins, #201613)
26116
27 * ``up`` and ``down`` are now aliases for ``up-thread`` and117 * ``up`` and ``down`` are now aliases for ``up-thread`` and
28 ``down-thread`` respectively.118 ``down-thread`` respectively.
29119
120 * ``up-thread`` now notifies when a thread becomes empty. This is a step
121 towards removing it automatically/prompting to do so.
122 (James Westby, #195133)
123
30 BUGFIXES:124 BUGFIXES:
31125
126 * ``pull`` expects the keywork local. (Mark Lee, #379347)
127
128 * ``setup.py`` doesn't actually install. (Mark Lee, #379069)
129
130 * module has no attribute ``PushResult``. (Robert Collins)
131
32 API BREAKS:132 API BREAKS:
33133
34 TESTING:134 TESTING:
@@ -37,7 +137,7 @@
37137
38138
391.31391.3
40---140===
41141
42 IMPROVEMENTS:142 IMPROVEMENTS:
43143
44144
=== modified file 'README'
--- README 2008-09-12 01:55:17 +0000
+++ README 2010-08-05 18:57:44 +0000
@@ -77,9 +77,11 @@
77branch will pull into the current thread of a loom. This assymetry makes it easy77branch will pull into the current thread of a loom. This assymetry makes it easy
78to work with developers who are not using looms.78to work with developers who are not using looms.
7979
80Loom also adds a new revision specifier 'thread:'. You can use this to diff80Loom also adds new revision specifiers 'thread:' and 'below:'. You can use these
81against threads in the current loom. For example: 'bzr diff -r thread:' will81to diff against threads in the current Loom. For instance, 'bzr diff -r
82show you the difference between the current thread and the thread below it.82thread:' will show you the different between the thread below yours, and your
83thread. See ``bzr help revisionspec`` for the detailed help on these two
84revision specifiers.
8385
8486
85Documentation87Documentation
8688
=== modified file 'TODO'
--- TODO 2008-01-20 07:34:29 +0000
+++ TODO 2010-08-05 18:57:44 +0000
@@ -95,7 +95,7 @@
95 TODO either means changing 'diff's defaults, adding a flag to diff, or using95 TODO either means changing 'diff's defaults, adding a flag to diff, or using
96 a different revspec prefix; or something like that.)96 a different revspec prefix; or something like that.)
97- export-patch to export the diff from this thread to the lower thread (using -ancestry logic) to a file named as the warp is named. (what about / ?)97- export-patch to export the diff from this thread to the lower thread (using -ancestry logic) to a file named as the warp is named. (what about / ?)
98- during up-thread, if we could pull or if there is no diff, then the thread has been merged, offer to remove it.98- during up-thread, if we could pull or if there is no diff, then the thread has been merged, offer to remove it. (Currently suggests to remove it).
99- loom to have the same 'tree root id' as its branches, to allow nested looms by reference. EEK!.99- loom to have the same 'tree root id' as its branches, to allow nested looms by reference. EEK!.
100- show-loom to allow -r -1.100- show-loom to allow -r -1.
101- combine-thread to warn if the thread being combined has changes not present in the one below it. I.e. by ancestry, or by doing a merge and recording differences. For bonus points, do the merge, but record the lower thread as the last-revision in the tree still, and set no pending-merges. This preserves the difference whilst still combining the threads.101- combine-thread to warn if the thread being combined has changes not present in the one below it. I.e. by ancestry, or by doing a merge and recording differences. For bonus points, do the merge, but record the lower thread as the last-revision in the tree still, and set no pending-merges. This preserves the difference whilst still combining the threads.
@@ -110,5 +110,4 @@
110 therein as well, to support uncommit between record calls.110 therein as well, to support uncommit between record calls.
111- combine-thread should de-dup penmding merges (use case: up-thread finds a fully merged thread so there are pending merges but no diff between threads; this is when combine-thread is often called).111- combine-thread should de-dup penmding merges (use case: up-thread finds a fully merged thread so there are pending merges but no diff between threads; this is when combine-thread is often called).
112- support tags on push/pull in looms112- support tags on push/pull in looms
113- bzr nick <name> should rename the current thread (by adding one with the new name and in current-thread deleted the active location - same as merging a delete.
114- perhaps bzr send should send the whole loom ? (e.g. as a patch bomb - a series of patches?)113- perhaps bzr send should send the whole loom ? (e.g. as a patch bomb - a series of patches?)
115114
=== modified file '__init__.py'
--- __init__.py 2008-09-12 01:55:17 +0000
+++ __init__.py 2010-08-05 18:57:44 +0000
@@ -48,19 +48,21 @@
48 to remove a thread which has been merged into upstream. 48 to remove a thread which has been merged into upstream.
4949
5050
51Loom also adds a new revision specifier 'thread:'. You can use this to diff51Loom also adds new revision specifiers 'thread:' and 'below:'. You can use these
52against threads in the current Loom. For instance, 'bzr diff -r thread:' will52to diff against threads in the current Loom. For instance, 'bzr diff -r
53show you the different between the thread below yours, and your thread.53thread:' will show you the different between the thread below yours, and your
54thread. See ``bzr help revisionspec`` for the detailed help on these two
55revision specifiers.
54"""56"""
5557
56version_info = (1, 4, 0, 'dev', 0)58from version import bzr_plugin_version as version_info
5759
58import bzrlib.builtins60import bzrlib.builtins
59import bzrlib.commands61import bzrlib.commands
62import bzrlib.revisionspec
6063
61import branch
62import commands64import commands
63import revspec65import formats
6466
6567
66for command in [68for command in [
@@ -74,8 +76,12 @@
74 'show_loom',76 'show_loom',
75 'up_thread',77 'up_thread',
76 ]:78 ]:
77 bzrlib.commands.register_command(getattr(commands, 'cmd_' + command))79 bzrlib.commands.plugin_cmds.register_lazy('cmd_' + command, [],
80 'bzrlib.plugins.loom.commands')
7881
82# XXX: bzr fix needed: for status and switch, we have to register directly, not
83# lazily, because register_lazy does not stack in the same way register_command
84# does.
79if not hasattr(bzrlib.builtins, "cmd_switch"):85if not hasattr(bzrlib.builtins, "cmd_switch"):
80 # provide a switch command (allows 86 # provide a switch command (allows
81 bzrlib.commands.register_command(getattr(commands, 'cmd_switch'))87 bzrlib.commands.register_command(getattr(commands, 'cmd_switch'))
@@ -86,6 +92,19 @@
86commands.cmd_status._original_command = bzrlib.commands.register_command(92commands.cmd_status._original_command = bzrlib.commands.register_command(
87 commands.cmd_status, True)93 commands.cmd_status, True)
8894
95revspec_registry = getattr(bzrlib.revisionspec, 'revspec_registry', None)
96if revspec_registry is not None:
97 revspec_registry.register_lazy('thread:', 'bzrlib.plugins.loom.revspec',
98 'RevisionSpecThread')
99 revspec_registry.register_lazy('below:', 'bzrlib.plugins.loom.revspec',
100 'RevisionSpecBelow')
101else:
102 import revspec
103 bzrlib.revisionspec.SPEC_TYPES.append(revspec.RevisionSpecThread)
104 bzrlib.revisionspec.SPEC_TYPES.append(revspec.RevisionSpecBelow)
105
106#register loom formats
107formats.register_formats()
89108
90def test_suite():109def test_suite():
91 import bzrlib.plugins.loom.tests110 import bzrlib.plugins.loom.tests
92111
=== modified file 'branch.py'
--- branch.py 2009-04-23 09:09:12 +0000
+++ branch.py 2010-08-05 18:57:44 +0000
@@ -31,13 +31,14 @@
31from bzrlib.decorators import needs_read_lock, needs_write_lock31from bzrlib.decorators import needs_read_lock, needs_write_lock
32import bzrlib.errors32import bzrlib.errors
33import bzrlib.osutils33import bzrlib.osutils
34from bzrlib import symbol_versioning34from bzrlib import remote, symbol_versioning
35import bzrlib.trace35import bzrlib.trace
36import bzrlib.ui36import bzrlib.ui
37from bzrlib.revision import is_null, NULL_REVISION37from bzrlib.revision import is_null, NULL_REVISION
38import bzrlib.tree38import bzrlib.tree
39import bzrlib.urlutils39import bzrlib.urlutils
4040
41import formats
41import loom_io42import loom_io
42import loom_state43import loom_state
4344
@@ -45,6 +46,26 @@
45EMPTY_REVISION = 'empty:'46EMPTY_REVISION = 'empty:'
4647
4748
49def create_thread(loom, thread_name):
50 """Create a thread in the branch loom called thread."""
51 require_loom_branch(loom)
52 loom.lock_write()
53 try:
54 loom.new_thread(thread_name, loom.nick)
55 loom._set_nick(thread_name)
56 finally:
57 loom.unlock()
58
59
60class AlreadyLoom(bzrlib.errors.BzrError):
61
62 _fmt = """Loom %(loom)s is already a loom."""
63
64 def __init__(self, loom):
65 bzrlib.errors.BzrError.__init__(self)
66 self.loom = loom
67
68
48def loomify(branch):69def loomify(branch):
49 """Convert branch to a loom.70 """Convert branch to a loom.
5071
@@ -53,6 +74,12 @@
53 try:74 try:
54 branch.lock_write()75 branch.lock_write()
55 try:76 try:
77 require_loom_branch(branch)
78 except NotALoom:
79 pass
80 else:
81 raise AlreadyLoom(branch)
82 try:
56 format = {83 format = {
57 bzrlib.branch.BzrBranchFormat5: BzrBranchLoomFormat1,84 bzrlib.branch.BzrBranchFormat5: BzrBranchLoomFormat1,
58 bzrlib.branch.BzrBranchFormat6: BzrBranchLoomFormat6,85 bzrlib.branch.BzrBranchFormat6: BzrBranchLoomFormat6,
@@ -65,25 +92,12 @@
65 branch.unlock()92 branch.unlock()
6693
6794
68def require_loom_branch(branch):95require_loom_branch = formats.require_loom_branch
69 """Return None if branch is already loomified, or raise NotALoom."""96NotALoom = formats.NotALoom
70 if not branch._format.__class__ in LOOM_FORMATS:
71 raise NotALoom(branch)
72
73
74class NotALoom(bzrlib.errors.BzrError):
75
76 _fmt = ("The branch %(branch)s is not a loom. "
77 "You can use 'bzr loomify' to make it into a loom.")
78
79 def __init__(self, branch):
80 bzrlib.errors.BzrError.__init__(self)
81 self.branch = branch
8297
8398
84class LoomThreadError(bzrlib.errors.BzrError):99class LoomThreadError(bzrlib.errors.BzrError):
85100 """Base class for Loom-Thread errors."""
86 _fmt = """Base class for Loom-Thread errors."""
87101
88 def __init__(self, branch, thread):102 def __init__(self, branch, thread):
89 bzrlib.errors.BzrError.__init__(self)103 bzrlib.errors.BzrError.__init__(self)
@@ -198,14 +212,14 @@
198 if len(threads) <= current_index:212 if len(threads) <= current_index:
199 # removed the end213 # removed the end
200 # take the new end thread214 # take the new end thread
201 self.nick = threads[-1][0]215 self._set_nick(threads[-1][0])
202 new_rev = threads[-1][1]216 new_rev = threads[-1][1]
203 if new_rev == EMPTY_REVISION:217 if new_rev == EMPTY_REVISION:
204 new_rev = bzrlib.revision.NULL_REVISION218 new_rev = bzrlib.revision.NULL_REVISION
205 self.generate_revision_history(new_rev)219 self.generate_revision_history(new_rev)
206 return220 return
207 # non-end thread removed.221 # non-end thread removed.
208 self.nick = threads[current_index][0]222 self._set_nick(threads[current_index][0])
209 new_rev = threads[current_index][1]223 new_rev = threads[current_index][1]
210 if new_rev == EMPTY_REVISION:224 if new_rev == EMPTY_REVISION:
211 new_rev = bzrlib.revision.NULL_REVISION225 new_rev = bzrlib.revision.NULL_REVISION
@@ -229,85 +243,25 @@
229 def clone(self, to_bzrdir, revision_id=None, repository_policy=None):243 def clone(self, to_bzrdir, revision_id=None, repository_policy=None):
230 """Clone the branch into to_bzrdir.244 """Clone the branch into to_bzrdir.
231 245
232 This differs from the base clone by cloning the loom and 246 This differs from the base clone by cloning the loom, setting the
233 setting the current nick to the top of the loom.247 current nick to the top of the loom, not honouring any branch format
248 selection on the target bzrdir, and ensuring that the format of
249 the created branch is stacking compatible.
234 """250 """
235 result = self._format.initialize(to_bzrdir)251 # If the target is a stackable repository, force-upgrade the
252 # output loom format
253 if (isinstance(to_bzrdir, bzrdir.BzrDirMeta1) and
254 to_bzrdir._format.repository_format.supports_external_lookups):
255 format = BzrBranchLoomFormat7()
256 else:
257 format = self._format
258 result = format.initialize(to_bzrdir)
236 if repository_policy is not None:259 if repository_policy is not None:
237 repository_policy.configure_branch(result)260 repository_policy.configure_branch(result)
238 self.copy_content_into(result, revision_id=revision_id)261 bzrlib.branch.InterBranch.get(self, result).copy_content_into(
262 revision_id=revision_id)
239 return result263 return result
240264
241 @needs_read_lock
242 def copy_content_into(self, destination, revision_id=None):
243 # XXX: hint for bzrlib - break this into two routines, one for
244 # copying the last-rev pointer, one for copying parent etc.
245 destination.lock_write()
246 try:
247 source_nick = self.nick
248 state = self.get_loom_state()
249 parents = state.get_parents()
250 if parents:
251 loom_tip = parents[0]
252 else:
253 loom_tip = None
254 threads = self.get_threads(state.get_basis_revision_id())
255 if revision_id not in (None, NULL_REVISION):
256 if threads:
257 # revision_id should be in the loom, or its an error
258 found_threads = [thread for thread, rev in threads
259 if rev == revision_id]
260 if not found_threads:
261 # the thread we have been asked to set in the remote
262 # side has not been recorded yet, so its data is not
263 # present at this point.
264 raise UnrecordedRevision(self, revision_id)
265
266 # pull in the warp, which was skipped during the initial pull
267 # because the front end does not know what to pull.
268 # nb: this is mega huge hacky. THINK. RBC 2006062
269 nested = bzrlib.ui.ui_factory.nested_progress_bar()
270 try:
271 if parents:
272 destination.repository.fetch(self.repository,
273 revision_id=parents[0])
274 if threads:
275 for thread, rev_id in reversed(threads):
276 # fetch the loom content for this revision
277 destination.repository.fetch(self.repository,
278 revision_id=rev_id)
279 finally:
280 nested.finished()
281 state = loom_state.LoomState()
282 try:
283 require_loom_branch(destination)
284 if threads:
285 last_rev = threads[-1][1]
286 if last_rev == EMPTY_REVISION:
287 last_rev = bzrlib.revision.NULL_REVISION
288 destination.generate_revision_history(last_rev)
289 state.set_parents([loom_tip])
290 state.set_threads(
291 (thread + ([thread[1]],) for thread in threads)
292 )
293 else:
294 # no threads yet, be a normal branch.
295 self._synchronize_history(destination, revision_id)
296 destination._set_last_loom(state)
297 except NotALoom:
298 self._synchronize_history(destination, revision_id)
299 try:
300 parent = self.get_parent()
301 except bzrlib.errors.InaccessibleParent, e:
302 bzrlib.trace.mutter('parent was not accessible to copy: %s', e)
303 else:
304 if parent:
305 destination.set_parent(parent)
306 if threads:
307 destination.nick = threads[-1][0]
308 finally:
309 destination.unlock()
310
311 def _get_checkout_format(self):265 def _get_checkout_format(self):
312 """Checking out a Loom gets a regular branch for now.266 """Checking out a Loom gets a regular branch for now.
313 267
@@ -435,22 +389,25 @@
435 result.append((name, rev_id))389 result.append((name, rev_id))
436 return result390 return result
437391
438 @needs_write_lock392 def _loom_get_nick(self):
439 def pull(self, source, overwrite=False, stop_revision=None,393 return self._get_nick(local=True)
440 run_hooks=True, possible_transports=None, _override_hook_target=None):394
441 """Pull from a branch into this loom.395 def _rename_thread(self, nick):
442396 """Rename the current thread to nick."""
443 If the remote branch is a non-loom branch, the pull is done against the397 state = self.get_loom_state()
444 current warp. If it is a loom branch, then the pull is done against the398 threads = state.get_threads()
445 entire loom and the current thread set to the top thread.399 if not len(threads):
446 """400 # No threads at all - probably a default initialised loom in the
447 if not isinstance(source, LoomSupport):401 # test suite.
448 return super(LoomSupport, self).pull(source,402 return self._set_nick(nick)
449 overwrite=overwrite, stop_revision=stop_revision,403 current_index = state.thread_index(self.nick)
450 possible_transports=possible_transports,404 threads[current_index] = (nick,) + threads[current_index][1:]
451 _override_hook_target=_override_hook_target)405 state.set_threads(threads)
452 return _Puller(source, self).transfer(overwrite, stop_revision,406 self._set_last_loom(state)
453 run_hooks, possible_transports, _override_hook_target)407 # Preserve default behavior: set the branch nick
408 self._set_nick(nick)
409
410 nick = property(_loom_get_nick, _rename_thread)
454411
455 @needs_read_lock412 @needs_read_lock
456 def push(self, target, overwrite=False, stop_revision=None,413 def push(self, target, overwrite=False, stop_revision=None,
@@ -606,10 +563,20 @@
606563
607564
608class _Puller(object):565class _Puller(object):
566 # XXX: Move into InterLoomBranch.
609567
610 def __init__(self, source, target):568 def __init__(self, source, target):
611 self.target = target569 self.target = target
612 self.source = source570 self.source = source
571 # If _Puller has been created, we need real branch objects.
572 self.real_target = self.unwrap_branch(target)
573 self.real_source = self.unwrap_branch(source)
574
575 def unwrap_branch(self, branch):
576 if isinstance(branch, remote.RemoteBranch):
577 branch._ensure_real()
578 return branch._real_branch
579 return branch
613580
614 def prepare_result(self, _override_hook_target):581 def prepare_result(self, _override_hook_target):
615 result = self.make_result()582 result = self.make_result()
@@ -669,8 +636,10 @@
669 return result636 return result
670637
671 def transfer(self, overwrite, stop_revision, run_hooks=True,638 def transfer(self, overwrite, stop_revision, run_hooks=True,
672 possible_transports=None, _override_hook_target=None):639 possible_transports=None, _override_hook_target=None, local=False):
673 """Implementation of push and pull"""640 """Implementation of push and pull"""
641 if local:
642 raise bzrlib.errors.LocalRequiresBoundBranch()
674 # pull the loom, and position our643 # pull the loom, and position our
675 pb = bzrlib.ui.ui_factory.nested_progress_bar()644 pb = bzrlib.ui.ui_factory.nested_progress_bar()
676 try:645 try:
@@ -678,7 +647,7 @@
678 self.target.lock_write()647 self.target.lock_write()
679 self.source.lock_read()648 self.source.lock_read()
680 try:649 try:
681 source_state = self.source.get_loom_state()650 source_state = self.real_source.get_loom_state()
682 source_parents = source_state.get_parents()651 source_parents = source_state.get_parents()
683 if not source_parents:652 if not source_parents:
684 return self.plain_transfer(result, run_hooks,653 return self.plain_transfer(result, run_hooks,
@@ -724,7 +693,7 @@
724 # and save the state.693 # and save the state.
725 self.target._set_last_loom(my_state)694 self.target._set_last_loom(my_state)
726 # set the branch nick.695 # set the branch nick.
727 self.target.nick = threads[-1][0]696 self.target._set_nick(threads[-1][0])
728 # and position the branch on the top loom697 # and position the branch on the top loom
729 new_rev = threads[-1][1]698 new_rev = threads[-1][1]
730 if new_rev == EMPTY_REVISION:699 if new_rev == EMPTY_REVISION:
@@ -743,7 +712,7 @@
743712
744 @staticmethod713 @staticmethod
745 def make_result():714 def make_result():
746 return bzrlib.branch.PushResult()715 return bzrlib.branch.BranchPushResult()
747716
748 @staticmethod717 @staticmethod
749 def post_hooks():718 def post_hooks():
@@ -780,9 +749,11 @@
780 # A mixin is not ideal because it is tricky to test, but it seems to be the749 # A mixin is not ideal because it is tricky to test, but it seems to be the
781 # best solution for now.750 # best solution for now.
782751
783 def initialize(self, a_bzrdir):752 def initialize(self, a_bzrdir, name=None):
784 """Create a branch of this format in a_bzrdir."""753 """Create a branch of this format in a_bzrdir."""
785 super(LoomFormatMixin, self).initialize(a_bzrdir)754 if name is not None:
755 raise bzrlib.errors.NoColocatedBranchSupport(self)
756 super(LoomFormatMixin, self).initialize(a_bzrdir, name=None)
786 branch_transport = a_bzrdir.get_branch_transport(self)757 branch_transport = a_bzrdir.get_branch_transport(self)
787 files = []758 files = []
788 state = loom_state.LoomState()759 state = loom_state.LoomState()
@@ -801,15 +772,21 @@
801 control_files.unlock()772 control_files.unlock()
802 return self.open(a_bzrdir, _found=True, )773 return self.open(a_bzrdir, _found=True, )
803774
804 def open(self, a_bzrdir, _found=False, ignore_fallbacks=False):775 def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False):
805 """Return the branch object for a_bzrdir776 """Return the branch object for a_bzrdir
806777
807 _found is a private parameter, do not use it. It is used to indicate778 _found is a private parameter, do not use it. It is used to indicate
808 if format probing has already be done.779 if format probing has already be done.
780
781 :param name: The 'colocated branches' name for the branch to open.
782 in future, Loom may use that to return a Thread, but for now
783 it is unused.
809 """784 """
810 if not _found:785 if not _found:
811 format = BranchFormat.find_format(a_bzrdir)786 format = BranchFormat.find_format(a_bzrdir)
812 assert format.__class__ == self.__class__787 assert format.__class__ == self.__class__
788 if name is not None:
789 raise bzrlib.errors.NoColocatedBranchSupport(self)
813 transport = a_bzrdir.get_branch_transport(None)790 transport = a_bzrdir.get_branch_transport(None)
814 control_files = bzrlib.lockable_files.LockableFiles(791 control_files = bzrlib.lockable_files.LockableFiles(
815 transport, 'lock', bzrlib.lockdir.LockDir)792 transport, 'lock', bzrlib.lockdir.LockDir)
@@ -923,13 +900,136 @@
923 return "bzr loom format 7 (based on bzr branch format 7)\n"900 return "bzr loom format 7 (based on bzr branch format 7)\n"
924901
925902
926bzrlib.branch.BranchFormat.register_format(BzrBranchLoomFormat1())903# Handle the smart server:
927bzrlib.branch.BranchFormat.register_format(BzrBranchLoomFormat6())904
928bzrlib.branch.BranchFormat.register_format(BzrBranchLoomFormat7())905class InterLoomBranch(bzrlib.branch.GenericInterBranch):
929906
930907 @classmethod
931LOOM_FORMATS = [908 def _get_branch_formats_to_test(klass):
932 BzrBranchLoomFormat1,909 return [
933 BzrBranchLoomFormat6,910 (bzrlib.branch.BranchFormat._default_format,
934 BzrBranchLoomFormat7,911 BzrBranchLoomFormat7()),
935]912 (BzrBranchLoomFormat7(),
913 bzrlib.branch.BranchFormat._default_format),
914 (BzrBranchLoomFormat7(), BzrBranchLoomFormat7()),
915 ]
916
917 def unwrap_branch(self, branch):
918 if isinstance(branch, remote.RemoteBranch):
919 branch._ensure_real()
920 return branch._real_branch
921 return branch
922
923 @classmethod
924 def is_compatible(klass, source, target):
925 # 1st cut: special case and handle all *->Loom and Loom->*
926 return klass.branch_is_loom(source) or klass.branch_is_loom(target)
927
928 def get_loom_state(self, branch):
929 branch = self.unwrap_branch(branch)
930 return branch.get_loom_state()
931
932 def get_threads(self, branch, revision_id):
933 branch = self.unwrap_branch(branch)
934 return branch.get_threads(revision_id)
935
936 @classmethod
937 def branch_is_loom(klass, branch):
938 format = klass.unwrap_format(branch._format)
939 return isinstance(format, LoomFormatMixin)
940
941 @needs_write_lock
942 def copy_content_into(self, revision_id=None):
943 if not self.__class__.branch_is_loom(self.source):
944 # target is loom, but the generic code path works Just Fine for
945 # regular to loom copy_content_into.
946 return super(InterLoomBranch, self).copy_content_into(
947 revision_id=revision_id)
948 # XXX: hint for bzrlib - break this into two routines, one for
949 # copying the last-rev pointer, one for copying parent etc.
950 source_nick = self.source.nick
951 state = self.get_loom_state(self.source)
952 parents = state.get_parents()
953 if parents:
954 loom_tip = parents[0]
955 else:
956 loom_tip = None
957 threads = self.get_threads(self.source, state.get_basis_revision_id())
958 if revision_id not in (None, NULL_REVISION):
959 if threads:
960 # revision_id should be in the loom, or its an error
961 found_threads = [thread for thread, rev in threads
962 if rev == revision_id]
963 if not found_threads:
964 # the thread we have been asked to set in the remote
965 # side has not been recorded yet, so its data is not
966 # present at this point.
967 raise UnrecordedRevision(self.source, revision_id)
968
969 # pull in the warp, which was skipped during the initial pull
970 # because the front end does not know what to pull.
971 # nb: this is mega huge hacky. THINK. RBC 2006062
972 nested = bzrlib.ui.ui_factory.nested_progress_bar()
973 try:
974 if parents:
975 self.target.repository.fetch(self.source.repository,
976 revision_id=parents[0])
977 if threads:
978 for thread, rev_id in reversed(threads):
979 # fetch the loom content for this revision
980 self.target.repository.fetch(self.source.repository,
981 revision_id=rev_id)
982 finally:
983 nested.finished()
984 state = loom_state.LoomState()
985 try:
986 require_loom_branch(self.target)
987 if threads:
988 last_rev = threads[-1][1]
989 if last_rev == EMPTY_REVISION:
990 last_rev = bzrlib.revision.NULL_REVISION
991 self.target.generate_revision_history(last_rev)
992 state.set_parents([loom_tip])
993 state.set_threads(
994 (thread + ([thread[1]],) for thread in threads)
995 )
996 else:
997 # no threads yet, be a normal branch.
998 self.source._synchronize_history(self.target, revision_id)
999 target_loom = self.unwrap_branch(self.target)
1000 target_loom._set_last_loom(state)
1001 except NotALoom:
1002 self.source._synchronize_history(self.target, revision_id)
1003 try:
1004 parent = self.source.get_parent()
1005 except bzrlib.errors.InaccessibleParent, e:
1006 bzrlib.trace.mutter('parent was not accessible to copy: %s', e)
1007 else:
1008 if parent:
1009 self.target.set_parent(parent)
1010 if threads:
1011 self.target._set_nick(threads[-1][0])
1012
1013 @needs_write_lock
1014 def pull(self, overwrite=False, stop_revision=None,
1015 run_hooks=True, possible_transports=None, _override_hook_target=None,
1016 local=False):
1017 """Perform a pull, reading from self.source and writing to self.target.
1018
1019 If the source branch is a non-loom branch, the pull is done against the
1020 current warp. If it is a loom branch, then the pull is done against the
1021 entire loom and the current thread set to the top thread.
1022 """
1023 # Special code only needed when both source and targets are looms:
1024 if (self.__class__.branch_is_loom(self.target) and
1025 self.__class__.branch_is_loom(self.source)):
1026 return _Puller(self.source, self.target).transfer(overwrite, stop_revision,
1027 run_hooks, possible_transports, _override_hook_target, local)
1028 return super(InterLoomBranch, self).pull(
1029 overwrite=overwrite, stop_revision=stop_revision,
1030 possible_transports=possible_transports,
1031 _override_hook_target=_override_hook_target, local=local,
1032 run_hooks=run_hooks)
1033
1034
1035bzrlib.branch.InterBranch.register_optimiser(InterLoomBranch)
9361036
=== modified file 'commands.py'
--- commands.py 2009-02-13 02:30:24 +0000
+++ commands.py 2010-08-05 18:57:44 +0000
@@ -17,18 +17,23 @@
1717
18"""Loom commands."""18"""Loom commands."""
1919
20from bzrlib import workingtree20from bzrlib import bzrdir, directory_service, workingtree
21import bzrlib.commands21import bzrlib.commands
22import bzrlib.branch22import bzrlib.branch
23from bzrlib import errors23from bzrlib import errors
24from bzrlib.lazy_import import lazy_import
24import bzrlib.merge25import bzrlib.merge
25from bzrlib.option import Option26from bzrlib.option import Option
26import bzrlib.revision27import bzrlib.revision
27import bzrlib.trace28import bzrlib.trace
28import bzrlib.transport29import bzrlib.transport
2930
31import formats
32
33lazy_import(globals(), """
30import branch34import branch
31from tree import LoomTreeDecorator35from tree import LoomTreeDecorator
36""")
3237
3338
34class cmd_loomify(bzrlib.commands.Command):39class cmd_loomify(bzrlib.commands.Command):
@@ -67,7 +72,7 @@
6772
6873
69class cmd_combine_thread(bzrlib.commands.Command):74class cmd_combine_thread(bzrlib.commands.Command):
70 """Combine the current thread with the thread below it.75 __doc__ = """Combine the current thread with the thread below it.
71 76
72 This will currently refuse to operate on the last thread, but in the future77 This will currently refuse to operate on the last thread, but in the future
73 will just turn the loom into a normal branch again.78 will just turn the loom into a normal branch again.
@@ -79,23 +84,53 @@
79 * Change threads to the thread below.84 * Change threads to the thread below.
80 """85 """
8186
82 def run(self):87 takes_options = [
88 Option('force', help='Combine even if work in the thread is not '
89 'integrated up or down the loom.'),
90 ]
91
92 def run(self, force=False):
83 (tree, path) = workingtree.WorkingTree.open_containing('.')93 (tree, path) = workingtree.WorkingTree.open_containing('.')
84 branch.require_loom_branch(tree.branch)94 branch.require_loom_branch(tree.branch)
85 tree.lock_write()95 self.add_cleanup(tree.lock_write().unlock)
86 try:96 current_thread = tree.branch.nick
87 current_thread = tree.branch.nick97 state = tree.branch.get_loom_state()
88 state = tree.branch.get_loom_state()98 if not force:
99 # Check for unmerged work.
100 # XXX: Layering issue whom should be caring for the check, not the
101 # command thats for sure.
89 threads = state.get_threads()102 threads = state.get_threads()
90 new_thread = state.get_new_thread_after_deleting(current_thread)103 current_index = state.thread_index(current_thread)
91 if new_thread is None:104 rev_below = None
92 raise branch.CannotCombineOnLastThread105 rev_current = threads[current_index][1]
93 bzrlib.trace.note("Combining thread '%s' into '%s'",106 rev_above = None
94 current_thread, new_thread)107 if current_index:
95 LoomTreeDecorator(tree).down_thread(new_thread)108 # There is a thread below
96 tree.branch.remove_thread(current_thread)109 rev_below = threads[current_index - 1][1]
97 finally:110 if current_index < len(threads) - 1:
98 tree.unlock()111 rev_above = threads[current_index + 1][1]
112 graph = tree.branch.repository.get_graph()
113 candidates = [rev for rev in
114 (rev_below, rev_current, rev_above) if rev]
115 heads = graph.heads(candidates)
116 # If current is not a head, its trivially merged, or
117 # if current is == rev_below, its also merged, or
118 # if there is only one thread its merged (well its not unmerged).
119 if (rev_current == rev_below or rev_current not in heads or
120 (rev_below is None and rev_above is None)):
121 merged = True
122 else:
123 merged = False
124 if not merged:
125 raise errors.BzrCommandError("Thread '%s' has unmerged work"
126 ". Use --force to combine anyway." % current_thread)
127 new_thread = state.get_new_thread_after_deleting(current_thread)
128 if new_thread is None:
129 raise branch.CannotCombineOnLastThread
130 bzrlib.trace.note("Combining thread '%s' into '%s'",
131 current_thread, new_thread)
132 LoomTreeDecorator(tree).down_thread(new_thread)
133 tree.branch.remove_thread(current_thread)
99134
100135
101class cmd_create_thread(bzrlib.commands.Command):136class cmd_create_thread(bzrlib.commands.Command):
@@ -106,19 +141,15 @@
106141
107 The thread-name must be a valid branch 'nickname', and must not be the name142 The thread-name must be a valid branch 'nickname', and must not be the name
108 of an existing thread in your loom.143 of an existing thread in your loom.
144
145 The new thread is created immediately after the current thread.
109 """146 """
110147
111 takes_args = ['thread']148 takes_args = ['thread']
112149
113 def run(self, thread):150 def run(self, thread):
114 (loom, path) = bzrlib.branch.Branch.open_containing('.')151 (loom, path) = bzrlib.branch.Branch.open_containing('.')
115 branch.require_loom_branch(loom)152 branch.create_thread(loom, thread)
116 loom.lock_write()
117 try:
118 loom.new_thread(thread, loom.nick)
119 loom.nick = thread
120 finally:
121 loom.unlock()
122153
123154
124class cmd_show_loom(bzrlib.commands.Command):155class cmd_show_loom(bzrlib.commands.Command):
@@ -160,7 +191,7 @@
160 else:191 else:
161 path = file_list[0]192 path = file_list[0]
162 (loom, _) = bzrlib.branch.Branch.open_containing(path)193 (loom, _) = bzrlib.branch.Branch.open_containing(path)
163 branch.require_loom_branch(loom)194 formats.require_loom_branch(loom)
164 loom.lock_read()195 loom.lock_read()
165 try:196 try:
166 print 'Current thread: %s' % loom.nick197 print 'Current thread: %s' % loom.nick
@@ -172,7 +203,7 @@
172 self._original_command().run_argv_aliases(argv, alias_argv)203 self._original_command().run_argv_aliases(argv, alias_argv)
173 try:204 try:
174 super(cmd_status, self).run_argv_aliases(list(argv), alias_argv)205 super(cmd_status, self).run_argv_aliases(list(argv), alias_argv)
175 except branch.NotALoom:206 except formats.NotALoom:
176 pass207 pass
177208
178209
@@ -208,15 +239,43 @@
208 return thread[0]239 return thread[0]
209 return to_location240 return to_location
210241
211 def run(self, to_location, force=False):242 def run(self, to_location=None, force=False, create_branch=False,
212 (tree, path) = workingtree.WorkingTree.open_containing('.')243 revision=None, directory=None):
213 tree = LoomTreeDecorator(tree)244 # The top of this is cribbed from bzr; because bzr isn't factored out
245 # enough.
246 if directory is None:
247 directory = u'.'
248 control_dir, path = bzrdir.BzrDir.open_containing(directory)
249 if to_location is None:
250 if revision is None:
251 raise errors.BzrCommandError(
252 'You must supply either a revision or a location')
253 to_location = '.'
214 try:254 try:
215 thread_name = self._get_thread_name(tree.branch, to_location)255 from_branch = control_dir.open_branch()
216 return tree.down_thread(thread_name)256 except errors.NotBranchError:
217 except (AttributeError, branch.NoSuchThread):257 from_branch = None
218 # When there is no thread its probably an external branch258 if create_branch:
219 # that we have been given.259 if from_branch is None:
260 raise errors.BzrCommandError(
261 'cannot create branch without source branch')
262 to_location = directory_service.directories.dereference(
263 to_location)
264 if from_branch is not None:
265 # Note: reopens.
266 (tree, path) = workingtree.WorkingTree.open_containing(directory)
267 tree = LoomTreeDecorator(tree)
268 try:
269 if create_branch:
270 return branch.create_thread(tree.branch, to_location)
271 thread_name = self._get_thread_name(tree.branch, to_location)
272 return tree.down_thread(thread_name)
273 except (AttributeError, branch.NoSuchThread, branch.NotALoom):
274 # When there is no thread its probably an external branch
275 # that we have been given.
276 raise errors.MustUseDecorated
277 else:
278 # switching to a relocated branch
220 raise errors.MustUseDecorated279 raise errors.MustUseDecorated
221280
222 def run_argv_aliases(self, argv, alias_argv=None):281 def run_argv_aliases(self, argv, alias_argv=None):
@@ -307,26 +366,34 @@
307366
308367
309class cmd_up_thread(bzrlib.commands.Command):368class cmd_up_thread(bzrlib.commands.Command):
310 """Move the branch up a thread in the loom.369 """Move the branch up to the top thread in the loom.
311 370
312 This merges the changes done in this thread but not incorporated into371 This merges the changes done in this thread but not incorporated into
313 the next thread up into the next thread up and switches your tree to be372 the next thread up into the next thread up and switches your tree to be
314 that thread.373 that thread. Unless there are conflicts, or --manual is specified, it
374 will then commit and repeat the process.
315 """375 """
316376
377 takes_args = ['thread?']
378
317 takes_options = ['merge-type', Option('auto',379 takes_options = ['merge-type', Option('auto',
318 help='Automatically commit and merge repeatedly.')]380 help='Deprecated - now the default.'),
381 Option('manual', help='Perform commit manually.'),
382 ]
319383
320 _see_also = ['down-thread', 'switch']384 _see_also = ['down-thread', 'switch']
321385
322 def run(self, merge_type=None, auto=False):386 def run(self, merge_type=None, manual=False, thread=None, auto=None):
323 (tree, path) = workingtree.WorkingTree.open_containing('.')387 (tree, path) = workingtree.WorkingTree.open_containing('.')
324 branch.require_loom_branch(tree.branch)388 branch.require_loom_branch(tree.branch)
325 tree = LoomTreeDecorator(tree)389 tree = LoomTreeDecorator(tree)
326 if not auto:390 if manual:
391 if thread is not None:
392 raise errors.BzrCommandError('Specifying a thread does not'
393 ' work with --manual.')
327 return tree.up_thread(merge_type)394 return tree.up_thread(merge_type)
328 else:395 else:
329 return tree.up_many(merge_type)396 return tree.up_many(merge_type, thread)
330397
331398
332class cmd_export_loom(bzrlib.commands.Command):399class cmd_export_loom(bzrlib.commands.Command):
333400
=== added file 'formats.py'
--- formats.py 1970-01-01 00:00:00 +0000
+++ formats.py 2010-08-05 18:57:44 +0000
@@ -0,0 +1,74 @@
1# Loom, a plugin for bzr to assist in developing focused patches.
2# Copyright (C) 2010 Canonical Limited.
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License version 2 as published
6# by the Free Software Foundation.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program; if not, write to the Free Software
15# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
16#
17
18"""Format information about formats for Loom.
19
20This is split out from the implementation of the formats to permit lazy
21loading without requiring the implementation code to be cryptic.
22"""
23
24__all__ = [
25 'NotALoom',
26 'register_formats',
27 'require_loom_branch',
28 ]
29
30from bzrlib.lazy_import import lazy_import
31import bzrlib.errors
32
33lazy_import(globals(), """
34from bzrlib import branch as _mod_branch
35""")
36
37
38_LOOM_FORMATS = {
39 "Bazaar-NG Loom branch format 1\n": "BzrBranchLoomFormat1",
40 "Bazaar-NG Loom branch format 6\n": "BzrBranchLoomFormat6",
41 "Bazaar-NG Loom branch format 7\n": "BzrBranchLoomFormat7",
42 }
43
44def register_formats():
45 if getattr(_mod_branch, 'MetaDirBranchFormatFactory', None):
46 branch_formats = [_mod_branch.MetaDirBranchFormatFactory(format_string,
47 "bzrlib.plugins.loom.branch", format_class) for
48 (format_string, format_class) in _LOOM_FORMATS.iteritems()]
49 else:
50 # Compat for folk not running bleeding edge. Like me as I commit this.
51 import branch
52 branch_formats = [
53 branch.BzrBranchLoomFormat1(),
54 branch.BzrBranchLoomFormat6(),
55 branch.BzrBranchLoomFormat7(),
56 ]
57 map(_mod_branch.BranchFormat.register_format, branch_formats)
58
59
60def require_loom_branch(branch):
61 """Return None if branch is already loomified, or raise NotALoom."""
62 if branch._format.network_name() not in _LOOM_FORMATS:
63 raise NotALoom(branch)
64
65
66# TODO: define errors without importing all errors.
67class NotALoom(bzrlib.errors.BzrError):
68
69 _fmt = ("The branch %(branch)s is not a loom. "
70 "You can use 'bzr loomify' to make it into a loom.")
71
72 def __init__(self, branch):
73 bzrlib.errors.BzrError.__init__(self)
74 self.branch = branch
075
=== modified file 'revspec.py'
--- revspec.py 2009-02-13 02:30:24 +0000
+++ revspec.py 2010-08-05 18:57:44 +0000
@@ -18,52 +18,87 @@
18"""Loom specific revision-specifiers."""18"""Loom specific revision-specifiers."""
1919
2020
21from bzrlib import revisionspec
22from bzrlib.plugins.loom.branch import NoLowerThread21from bzrlib.plugins.loom.branch import NoLowerThread
22from bzrlib.plugins.loom.formats import require_loom_branch
23from bzrlib.revisionspec import RevisionSpec, RevisionInfo23from bzrlib.revisionspec import RevisionSpec, RevisionInfo
2424
2525
26class RevisionSpecThread(RevisionSpec):26class LoomRevisionSpec(RevisionSpec):
27 """The thread: revision specifier."""27 """A revision spec that needs a loom."""
28
29 help_txt = """Selects the tip of a revision from a loom.
30
31 Selects the tip of a thread in a loom.
32
33 Examples::
34
35 thread: -> return the tip of the next lower thread.
36 thread:foo -> return the tip of the thread named 'foo'
37
38 see also: loom
39 """
40
41 prefix = 'thread:'
4228
43 def _match_on(self, branch, revs):29 def _match_on(self, branch, revs):
44 return RevisionInfo(branch, None, self._as_revision_id(branch))30 return RevisionInfo(branch, None, self._as_revision_id(branch))
4531
46 def _as_revision_id(self, branch):32 def _as_revision_id(self, branch):
47 # '' -> next lower33 require_loom_branch(branch)
48 # foo -> named
49 branch.lock_read()34 branch.lock_read()
50 try:35 try:
51 state = branch.get_loom_state()36 state = branch.get_loom_state()
52 threads = state.get_threads()37 threads = state.get_threads()
53 if len(self.spec):38 return self._as_thread_revision_id(branch, state, threads)
54 index = state.thread_index(self.spec)
55 else:
56 current_thread = branch.nick
57 index = state.thread_index(current_thread) - 1
58 if index < 0:
59 raise NoLowerThread()
60 return threads[index][1]
61 finally:39 finally:
62 branch.unlock()40 branch.unlock()
6341
6442
65revspec_registry = getattr(revisionspec, 'revspec_registry', None)43class RevisionSpecBelow(LoomRevisionSpec):
66if revspec_registry is not None:44 """The below: revision specifier."""
67 revspec_registry.register('thread:', RevisionSpecThread)45
68else:46 help_txt = """Selects the tip of the thread below a thread from a loom.
69 revisionspec.SPEC_TYPES.append(RevisionSpecThread)47
48 Selects the tip of the thread below a thread in a loom.
49
50 Examples::
51
52 below: -> return the tip of the next lower thread.
53 below:foo -> return the tip of the thread under the one
54 named 'foo'
55
56 see also: loom, the thread: revision specifier
57 """
58
59 prefix = 'below:'
60
61 def _as_thread_revision_id(self, branch, state, threads):
62 # '' -> next lower
63 # foo -> thread under foo
64 if len(self.spec):
65 index = state.thread_index(self.spec)
66 else:
67 current_thread = branch.nick
68 index = state.thread_index(current_thread)
69 if index < 1:
70 raise NoLowerThread()
71 return threads[index - 1][1]
72
73
74class RevisionSpecThread(LoomRevisionSpec):
75 """The thread: revision specifier."""
76
77 help_txt = """Selects the tip of a thread from a loom.
78
79 Selects the tip of a thread in a loom.
80
81 Examples::
82
83 thread: -> return the tip of the next lower thread.
84 thread:foo -> return the tip of the thread named 'foo'
85
86 see also: loom, the below: revision specifier
87 """
88
89 prefix = 'thread:'
90
91 def _as_thread_revision_id(self, branch, state, threads):
92 # '' -> next lower
93 # foo -> named
94 if len(self.spec):
95 index = state.thread_index(self.spec)
96 else:
97 current_thread = branch.nick
98 index = state.thread_index(current_thread) - 1
99 if index < 0:
100 raise NoLowerThread()
101 return threads[index][1]
102
103
104
70105
=== modified file 'setup.py'
--- setup.py 2008-05-30 02:39:07 +0000
+++ setup.py 2010-08-05 18:57:44 +0000
@@ -20,13 +20,11 @@
20 "Bazaar-NG Loom branch format 6\n":"Loom branch format 6",20 "Bazaar-NG Loom branch format 6\n":"Loom branch format 6",
21 }21 }
2222
23bzr_plugin_version = (1, 4, 0, 'dev', 0)23from version import *
24bzr_minimum_version = (1, 0, 0)
25bzr_maximum_version = None
2624
27if __name__ == 'main':25if __name__ == '__main__':
28 setup(name="Loom",26 setup(name="Loom",
29 version="1.4.0dev0",27 version="2.2.1dev0",
30 description="Loom plugin for bzr.",28 description="Loom plugin for bzr.",
31 author="Canonical Ltd",29 author="Canonical Ltd",
32 author_email="bazaar@lists.canonical.com",30 author_email="bazaar@lists.canonical.com",
3331
=== modified file 'tests/__init__.py'
--- tests/__init__.py 2008-09-12 01:55:17 +0000
+++ tests/__init__.py 2010-08-05 18:57:44 +0000
@@ -22,6 +22,7 @@
22import bzrlib.plugins.loom.branch22import bzrlib.plugins.loom.branch
23from bzrlib.tests import TestCaseWithTransport23from bzrlib.tests import TestCaseWithTransport
24from bzrlib.tests.TestUtil import TestLoader, TestSuite24from bzrlib.tests.TestUtil import TestLoader, TestSuite
25from bzrlib.workingtree import WorkingTree
2526
2627
27def test_suite():28def test_suite():
@@ -41,7 +42,9 @@
4142
42 def get_tree_with_loom(self, path="."):43 def get_tree_with_loom(self, path="."):
43 """Get a tree with no commits in loom format."""44 """Get a tree with no commits in loom format."""
44 tree = self.make_branch_and_tree(path)45 # May open on Remote - we want the vfs backed version for loom tests.
46 self.make_branch_and_tree(path)
47 tree = WorkingTree.open(path)
45 bzrlib.plugins.loom.branch.loomify(tree.branch)48 bzrlib.plugins.loom.branch.loomify(tree.branch)
46 return tree.bzrdir.open_workingtree()49 return tree.bzrdir.open_workingtree()
4750
4851
=== modified file 'tests/blackbox.py'
--- tests/blackbox.py 2008-11-24 16:38:16 +0000
+++ tests/blackbox.py 2010-08-05 18:57:44 +0000
@@ -35,7 +35,7 @@
35 def _add_patch(self, tree, name):35 def _add_patch(self, tree, name):
36 """Add a patch to a new thread, returning the revid of te commit."""36 """Add a patch to a new thread, returning the revid of te commit."""
37 tree.branch.new_thread(name)37 tree.branch.new_thread(name)
38 tree.branch.nick = name38 tree.branch._set_nick(name)
39 self.build_tree([name])39 self.build_tree([name])
40 tree.add(name)40 tree.add(name)
41 return tree.commit(name)41 return tree.commit(name)
@@ -147,11 +147,11 @@
147 self.assertShowLoom(['vendor'], 'vendor')147 self.assertShowLoom(['vendor'], 'vendor')
148 tree.branch.new_thread('debian')148 tree.branch.new_thread('debian')
149 self.assertShowLoom(['vendor', 'debian'], 'vendor')149 self.assertShowLoom(['vendor', 'debian'], 'vendor')
150 tree.branch.nick = 'debian'150 tree.branch._set_nick('debian')
151 self.assertShowLoom(['vendor', 'debian'], 'debian')151 self.assertShowLoom(['vendor', 'debian'], 'debian')
152 tree.branch.new_thread('patch A', 'vendor')152 tree.branch.new_thread('patch A', 'vendor')
153 self.assertShowLoom(['vendor', 'patch A', 'debian'], 'debian')153 self.assertShowLoom(['vendor', 'patch A', 'debian'], 'debian')
154 tree.branch.nick = 'patch A'154 tree.branch._set_nick('patch A')
155 self.assertShowLoom(['vendor', 'patch A', 'debian'], 'patch A')155 self.assertShowLoom(['vendor', 'patch A', 'debian'], 'patch A')
156156
157 def test_show_loom_with_location(self):157 def test_show_loom_with_location(self):
@@ -266,6 +266,17 @@
266 "All changes applied successfully.\nMoved to thread 'thread2'.\n",266 "All changes applied successfully.\nMoved to thread 'thread2'.\n",
267 err)267 err)
268268
269 def test_switch_dash_b(self):
270 # 'bzr switch -b new-thread' makes and switches to a new thread.
271 tree = self.get_vendor_loom()
272 self._add_patch(tree, 'thread2')
273 LoomTreeDecorator(tree).down_thread('vendor')
274 self.assertEqual(tree.branch.nick, 'vendor')
275 out, err = self.run_bzr(['switch', '-b', 'thread1'], retcode=0)
276 self.assertEqual(tree.branch.nick, 'thread1')
277 self.assertEqual('', out)
278 self.assertEqual('', err)
279
269280
270class TestRecord(TestsWithLooms):281class TestRecord(TestsWithLooms):
271282
@@ -281,7 +292,7 @@
281 """Adding a new thread is enough to allow recording."""292 """Adding a new thread is enough to allow recording."""
282 tree = self.get_vendor_loom()293 tree = self.get_vendor_loom()
283 tree.branch.new_thread('feature')294 tree.branch.new_thread('feature')
284 tree.branch.nick = 'feature'295 tree.branch._set_nick('feature')
285 out, err = self.run_bzr(['record', 'add feature branch.'])296 out, err = self.run_bzr(['record', 'add feature branch.'])
286 self.assertEqual('Loom recorded.\n', out)297 self.assertEqual('Loom recorded.\n', out)
287 self.assertEqual('', err)298 self.assertEqual('', err)
@@ -303,7 +314,7 @@
303 """moving down when the revision is unchanged should work."""314 """moving down when the revision is unchanged should work."""
304 tree = self.get_vendor_loom()315 tree = self.get_vendor_loom()
305 tree.branch.new_thread('patch')316 tree.branch.new_thread('patch')
306 tree.branch.nick = 'patch'317 tree.branch._set_nick('patch')
307 rev = tree.last_revision()318 rev = tree.last_revision()
308 out, err = self.run_bzr(['down-thread'])319 out, err = self.run_bzr(['down-thread'])
309 self.assertEqual('', out)320 self.assertEqual('', out)
@@ -314,7 +325,7 @@
314 def test_down_thread_removes_changes_between_threads(self):325 def test_down_thread_removes_changes_between_threads(self):
315 tree = self.get_vendor_loom()326 tree = self.get_vendor_loom()
316 tree.branch.new_thread('patch')327 tree.branch.new_thread('patch')
317 tree.branch.nick = 'patch'328 tree.branch._set_nick('patch')
318 rev = tree.last_revision()329 rev = tree.last_revision()
319 self.build_tree(['afile'])330 self.build_tree(['afile'])
320 tree.add('afile')331 tree.add('afile')
@@ -336,7 +347,7 @@
336 """Do a down thread when the lower patch is not in the r-h of the old."""347 """Do a down thread when the lower patch is not in the r-h of the old."""
337 tree = self.get_vendor_loom()348 tree = self.get_vendor_loom()
338 tree.branch.new_thread('patch')349 tree.branch.new_thread('patch')
339 tree.branch.nick = 'vendor'350 tree.branch._set_nick('vendor')
340 # do a null change in vendor - a new release.351 # do a null change in vendor - a new release.
341 vendor_release = tree.commit('new vendor release.', allow_pointless=True)352 vendor_release = tree.commit('new vendor release.', allow_pointless=True)
342 # pop up, then down353 # pop up, then down
@@ -389,7 +400,7 @@
389 """Trying to down-thread with changes causes an error."""400 """Trying to down-thread with changes causes an error."""
390 tree = self.get_vendor_loom()401 tree = self.get_vendor_loom()
391 tree.branch.new_thread('upper-thread')402 tree.branch.new_thread('upper-thread')
392 tree.branch.nick = 'upper-thread'403 tree.branch._set_nick('upper-thread')
393 self.build_tree(['new-file'])404 self.build_tree(['new-file'])
394 tree.add('new-file')405 tree.add('new-file')
395 out, err = self.run_bzr('down-thread', retcode=3)406 out, err = self.run_bzr('down-thread', retcode=3)
@@ -405,33 +416,35 @@
405 self.assertEqual('', out)416 self.assertEqual('', out)
406 self.assertEqual(417 self.assertEqual(
407 'bzr: ERROR: Cannot move up from the highest thread.\n', err)418 'bzr: ERROR: Cannot move up from the highest thread.\n', err)
408 419
409 def test_up_thread_same_revision(self):420 def test_up_thread_same_revision(self):
410 """moving up when the revision is unchanged should work."""421 """moving up when the revision is unchanged should work."""
411 tree = self.get_vendor_loom()422 tree = self.get_vendor_loom()
412 tree.branch.new_thread('patch')423 tree.branch.new_thread('patch')
413 tree.branch.nick = 'vendor'424 tree.branch._set_nick('vendor')
414 rev = tree.last_revision()425 rev = tree.last_revision()
415 out, err = self.run_bzr(['up-thread'])426 out, err = self.run_bzr(['up-thread'])
416 self.assertEqual('', out)427 self.assertEqual('', out)
417 self.assertEqual('', err)428 self.assertEqual('', err)
418 self.assertEqual('patch', tree.branch.nick)429 self.assertEqual('patch', tree.branch.nick)
419 self.assertEqual(rev, tree.last_revision())430 self.assertEqual(rev, tree.last_revision())
420 431
421 def test_up_thread_preserves_changes(self):432 def test_up_thread_manual_preserves_changes(self):
422 tree = self.get_vendor_loom()433 tree = self.get_vendor_loom()
423 tree.branch.new_thread('patch')434 tree.branch.new_thread('patch')
424 tree.branch.nick = 'vendor'435 tree.branch._set_nick('vendor')
425 patch_rev = tree.last_revision()436 patch_rev = tree.last_revision()
426 # add a change in vendor - a new release.437 # add a change in vendor - a new release.
427 self.build_tree(['afile'])438 self.build_tree(['afile'])
428 tree.add('afile')439 tree.add('afile')
429 vendor_release = tree.commit('new vendor release adds a file.')440 vendor_release = tree.commit('new vendor release adds a file.')
430 out, err = self.run_bzr(['up-thread'])441 out, err = self.run_bzr(['up-thread', '--manual'])
431 self.assertEqual('', out)442 self.assertEqual('', out)
432 self.assertEqual(443 self.assertEqual(
433 "All changes applied successfully.\n"444 "All changes applied successfully.\n"
434 "Moved to thread 'patch'.\n", err)445 "Moved to thread 'patch'.\n"
446 'This thread is now empty, you may wish to run "bzr '
447 'combine-thread" to remove it.\n', err)
435 self.assertEqual('patch', tree.branch.nick)448 self.assertEqual('patch', tree.branch.nick)
436 # the tree needs to be updated.449 # the tree needs to be updated.
437 self.assertEqual(patch_rev, tree.last_revision())450 self.assertEqual(patch_rev, tree.last_revision())
@@ -442,11 +455,18 @@
442 self.run_bzr(['diff'], retcode=1)455 self.run_bzr(['diff'], retcode=1)
443 self.assertEqual([patch_rev, vendor_release], tree.get_parent_ids())456 self.assertEqual([patch_rev, vendor_release], tree.get_parent_ids())
444457
458 def test_up_thread_manual_rejects_specified_thread(self):
459 tree = self.get_vendor_loom()
460 tree.branch.new_thread('patch')
461 out, err = self.run_bzr('up-thread --manual patch', retcode=3)
462 self.assertContainsRe(err, 'Specifying a thread does not work with'
463 ' --manual.')
464
445 def test_up_thread_gets_conflicts(self):465 def test_up_thread_gets_conflicts(self):
446 """Do a change in both the baseline and the next patch up."""466 """Do a change in both the baseline and the next patch up."""
447 tree = self.get_vendor_loom()467 tree = self.get_vendor_loom()
448 tree.branch.new_thread('patch')468 tree.branch.new_thread('patch')
449 tree.branch.nick = 'patch'469 tree.branch._set_nick('patch')
450 # add a change in patch - a new release.470 # add a change in patch - a new release.
451 self.build_tree(['afile'])471 self.build_tree(['afile'])
452 tree.add('afile')472 tree.add('afile')
@@ -483,14 +503,72 @@
483 self.run_bzr(['down-thread'])503 self.run_bzr(['down-thread'])
484 self.run_bzr(['up-thread', '--lca'])504 self.run_bzr(['up-thread', '--lca'])
485505
486 def test_up_thread_auto(self):506 def test_up_thread_no_manual(self):
487 tree = self.get_vendor_loom()507 tree = self.get_vendor_loom()
488 tree.branch.new_thread('middle')508 tree.branch.new_thread('middle')
489 tree.branch.new_thread('top')509 tree.branch.new_thread('top')
490 self.run_bzr('up-thread --auto')510 self.run_bzr('up-thread')
491 branch = _mod_branch.Branch.open('.')511 branch = _mod_branch.Branch.open('.')
492 self.assertEqual('top', branch.nick)512 self.assertEqual('top', branch.nick)
493513
514 def test_up_with_clean_merge_leaving_thread_empty(self):
515 """This tests what happens when a thread becomes empty.
516
517 A thread becomes empty when all its changes are included in
518 a lower thread, and so its diff to the thread below contains
519 nothing.
520
521 The user should be warned when this happens.
522 """
523 tree = self.get_vendor_loom()
524 self.build_tree(['afile'])
525 tree.add('afile')
526 patch_rev = tree.commit('add afile in base')
527 tree.branch.new_thread('patch')
528 tree.branch._set_nick('patch')
529 # make a change to afile in patch.
530 f = open('afile', 'wb')
531 try:
532 f.write('new contents of afile\n')
533 finally:
534 f.close()
535 patch_rev = tree.commit('make a change to afile')
536 # make the same change in vendor.
537 self.run_bzr(['down-thread'])
538 f = open('afile', 'wb')
539 try:
540 f.write('new contents of afile\n')
541 finally:
542 f.close()
543 vendor_release = tree.commit('make the same change to afile')
544 # check that the trees no longer differ after the up merge,
545 # and that we are
546 out, err = self.run_bzr(['up-thread', '--manual'])
547 self.assertEqual('', out)
548 self.assertStartsWith(err,
549 "All changes applied successfully.\n"
550 "Moved to thread 'patch'.\n"
551 'This thread is now empty, you may wish to run "bzr '
552 'combine-thread" to remove it.\n')
553 self.assertEqual('patch', tree.branch.nick)
554 # the tree needs to be updated.
555 self.assertEqual(patch_rev, tree.last_revision())
556 # the branch needs to be updated.
557 self.assertEqual(patch_rev, tree.branch.last_revision())
558 self.assertTrue(tree.has_filename('afile'))
559 # diff should return 0 now as we have no uncommitted changes.
560 self.run_bzr(['diff'])
561 self.assertEqual([patch_rev, vendor_release], tree.get_parent_ids())
562
563 def test_up_thread_accepts_thread(self):
564 tree = self.get_vendor_loom()
565 tree.branch.new_thread('lower-middle')
566 tree.branch.new_thread('upper-middle')
567 tree.branch.new_thread('top')
568 self.run_bzr('up-thread upper-middle')
569 branch = _mod_branch.Branch.open('.')
570 self.assertEqual('upper-middle', branch.nick)
571
494572
495class TestPush(TestsWithLooms):573class TestPush(TestsWithLooms):
496574
@@ -592,7 +670,7 @@
592 # not in, so we can revert that by name,670 # not in, so we can revert that by name,
593 tree = self.get_vendor_loom()671 tree = self.get_vendor_loom()
594 tree.branch.new_thread('after-vendor')672 tree.branch.new_thread('after-vendor')
595 tree.branch.nick = 'after-vendor'673 tree.branch._set_nick('after-vendor')
596 tree.commit('after-vendor commit', allow_pointless=True)674 tree.commit('after-vendor commit', allow_pointless=True)
597 tree.branch.record_loom('save loom with vendor and after-vendor')675 tree.branch.record_loom('save loom with vendor and after-vendor')
598 old_threads = tree.branch.get_loom_state().get_threads()676 old_threads = tree.branch.get_loom_state().get_threads()
@@ -632,6 +710,56 @@
632 loom_tree.down_thread()710 loom_tree.down_thread()
633 return tree, loom_tree711 return tree, loom_tree
634712
713 def get_loom_with_unique_thread(self):
714 """Return a loom with a unique thread.
715
716 That is:
717 vendor:[]
718 unique-thread:[vendor]
719 above-vendor:[vendor]
720
721 - unique-thread has work not in vendor and not in above-vendor.
722
723 The returned loom is on the vendor thread.
724 """
725 tree, _ = self.get_two_thread_loom()
726 tree.branch.new_thread('unique-thread', 'vendor')
727 loom_tree = LoomTreeDecorator(tree)
728 loom_tree.up_thread()
729 self.build_tree(['file-b'])
730 tree.add('file-b')
731 tree.commit('a unique change', rev_id='uniquely-yrs-1')
732 loom_tree.down_thread()
733 return tree, loom_tree
734
735 def test_combine_unmerged_thread_force(self):
736 """Combining a thread with unique work works with --force."""
737 tree, loom_tree = self.get_loom_with_unique_thread()
738 vendor_revid = tree.last_revision()
739 loom_tree.up_thread()
740 out, err = self.run_bzr(['combine-thread', '--force'])
741 self.assertEqual('', out)
742 self.assertEqual(
743 "Combining thread 'unique-thread' into 'vendor'\n"
744 'All changes applied successfully.\n'
745 "Moved to thread 'vendor'.\n",
746 err)
747 self.assertEqual(vendor_revid, tree.last_revision())
748 self.assertEqual('vendor', tree.branch.nick)
749
750 def test_combine_unmerged_thread_errors(self):
751 """Combining a thread with unique work errors without --force."""
752 tree, loom_tree = self.get_loom_with_unique_thread()
753 loom_tree.up_thread()
754 unique_revid = tree.last_revision()
755 out, err = self.run_bzr(['combine-thread'], retcode=3)
756 self.assertEqual('', out)
757 self.assertEqual("bzr: ERROR: "
758"Thread 'unique-thread' has unmerged work. Use --force to combine anyway.\n",
759 err)
760 self.assertEqual(unique_revid, tree.last_revision())
761 self.assertEqual('unique-thread', tree.branch.nick)
762
635 def test_combine_last_two_threads(self):763 def test_combine_last_two_threads(self):
636 """Doing a combine on two threads gives you just the bottom one."""764 """Doing a combine on two threads gives you just the bottom one."""
637 tree, loom_tree = self.get_two_thread_loom()765 tree, loom_tree = self.get_two_thread_loom()
638766
=== modified file 'tests/test_branch.py'
--- tests/test_branch.py 2008-11-24 16:38:16 +0000
+++ tests/test_branch.py 2010-08-05 18:57:44 +0000
@@ -23,6 +23,7 @@
23from bzrlib.branch import Branch23from bzrlib.branch import Branch
24import bzrlib.errors as errors24import bzrlib.errors as errors
25from bzrlib.plugins.loom.branch import (25from bzrlib.plugins.loom.branch import (
26 AlreadyLoom,
26 EMPTY_REVISION,27 EMPTY_REVISION,
27 loomify,28 loomify,
28 require_loom_branch,29 require_loom_branch,
@@ -33,7 +34,10 @@
33from bzrlib.plugins.loom.tree import LoomTreeDecorator34from bzrlib.plugins.loom.tree import LoomTreeDecorator
34import bzrlib.revision35import bzrlib.revision
35from bzrlib.revision import NULL_REVISION36from bzrlib.revision import NULL_REVISION
36from bzrlib.tests import TestCaseWithTransport37from bzrlib.tests import (
38 TestCaseWithTransport,
39 test_server,
40 )
37from bzrlib.transport import get_transport41from bzrlib.transport import get_transport
38from bzrlib.workingtree import WorkingTree42from bzrlib.workingtree import WorkingTree
3943
@@ -48,11 +52,18 @@
48 self.assertFileEqual('Loom current 1\n\n', '.bzr/branch/last-loom')52 self.assertFileEqual('Loom current 1\n\n', '.bzr/branch/last-loom')
4953
5054
55
56class StubFormat(object):
57
58 def network_name(self):
59 return "Nothing to see."
60
61
51class LockableStub(object):62class LockableStub(object):
5263
53 def __init__(self):64 def __init__(self):
54 self._calls = []65 self._calls = []
55 self._format = "Nothing to see."66 self._format = StubFormat()
5667
57 def lock_write(self):68 def lock_write(self):
58 self._calls.append(("write",))69 self._calls.append(("write",))
@@ -67,8 +78,7 @@
67 branch = self.make_branch('.')78 branch = self.make_branch('.')
68 self.assertRaises(NotALoom, require_loom_branch, branch)79 self.assertRaises(NotALoom, require_loom_branch, branch)
6980
7081 def works_on_format(self, format):
71 def works_on_loom(self, format):
72 branch = self.make_branch('.', format)82 branch = self.make_branch('.', format)
73 loomify(branch)83 loomify(branch)
74 # reopen it84 # reopen it
@@ -76,13 +86,19 @@
76 self.assertEqual(None, require_loom_branch(branch))86 self.assertEqual(None, require_loom_branch(branch))
7787
78 def test_works_on_loom1(self):88 def test_works_on_loom1(self):
79 self.works_on_loom('knit')89 self.works_on_format('knit')
8090
81 def test_works_on_loom6(self):91 def test_works_on_loom6(self):
82 self.works_on_loom('pack-0.92')92 self.works_on_format('pack-0.92')
8393
84 def test_works_on_loom7(self):94 def test_works_on_loom7(self):
85 self.works_on_loom('1.6')95 self.works_on_format('1.6')
96
97 def test_no_harm_to_looms(self):
98 branch = self.make_branch('.')
99 loomify(branch)
100 branch = branch.bzrdir.open_branch()
101 self.assertRaises(AlreadyLoom, loomify, branch)
86102
87103
88class TestLoomify(TestCaseWithTransport):104class TestLoomify(TestCaseWithTransport):
@@ -132,6 +148,7 @@
132 bzrlib.plugins.loom.branch.LoomBranch7,148 bzrlib.plugins.loom.branch.LoomBranch7,
133 bzrlib.plugins.loom.branch.BzrBranchLoomFormat7)149 bzrlib.plugins.loom.branch.BzrBranchLoomFormat7)
134150
151
135class TestLoom(TestCaseWithLoom):152class TestLoom(TestCaseWithLoom):
136153
137 def make_loom(self, path):154 def make_loom(self, path):
@@ -184,9 +201,9 @@
184 tree.branch.new_thread('baseline')201 tree.branch.new_thread('baseline')
185 tree.branch.new_thread('middlepoint')202 tree.branch.new_thread('middlepoint')
186 tree.branch.new_thread('endpoint')203 tree.branch.new_thread('endpoint')
187 tree.branch.nick = 'middlepoint'204 tree.branch._set_nick('middlepoint')
188 rev_id2 = tree.commit('middle', allow_pointless=True)205 rev_id2 = tree.commit('middle', allow_pointless=True)
189 tree.branch.nick = 'endpoint'206 tree.branch._set_nick('endpoint')
190 rev_id3 = tree.commit('end', allow_pointless=True)207 rev_id3 = tree.commit('end', allow_pointless=True)
191 tree.branch.new_thread('afterbase', 'baseline')208 tree.branch.new_thread('afterbase', 'baseline')
192 tree.branch.new_thread('aftermiddle', 'middlepoint')209 tree.branch.new_thread('aftermiddle', 'middlepoint')
@@ -209,7 +226,7 @@
209 tree = self.get_tree_with_one_commit()226 tree = self.get_tree_with_one_commit()
210 tree.branch.new_thread('baseline')227 tree.branch.new_thread('baseline')
211 tree.branch.new_thread('tail')228 tree.branch.new_thread('tail')
212 tree.branch.nick = 'baseline'229 tree.branch._set_nick('baseline')
213 first_rev = tree.last_revision()230 first_rev = tree.last_revision()
214 # lock the tree to prevent unlock triggering implicit record231 # lock the tree to prevent unlock triggering implicit record
215 tree.lock_write()232 tree.lock_write()
@@ -230,7 +247,7 @@
230247
231 def test_clone_empty_loom(self):248 def test_clone_empty_loom(self):
232 source_tree = self.get_tree_with_loom('source')249 source_tree = self.get_tree_with_loom('source')
233 source_tree.branch.nick = 'source'250 source_tree.branch._set_nick('source')
234 target_tree = source_tree.bzrdir.clone('target').open_workingtree()251 target_tree = source_tree.bzrdir.clone('target').open_workingtree()
235 self.assertLoomSproutedOk(source_tree, target_tree)252 self.assertLoomSproutedOk(source_tree, target_tree)
236253
@@ -244,7 +261,7 @@
244 source_tree = self.get_tree_with_one_commit('source')261 source_tree = self.get_tree_with_one_commit('source')
245 source_tree.branch.new_thread('bottom')262 source_tree.branch.new_thread('bottom')
246 source_tree.branch.new_thread('top')263 source_tree.branch.new_thread('top')
247 source_tree.branch.nick = 'top'264 source_tree.branch._set_nick('top')
248 source_tree.commit('phwoar', allow_pointless=True)265 source_tree.commit('phwoar', allow_pointless=True)
249 source_tree.branch.record_loom('commit to loom')266 source_tree.branch.record_loom('commit to loom')
250 target_tree = source_tree.bzrdir.clone('target').open_workingtree()267 target_tree = source_tree.bzrdir.clone('target').open_workingtree()
@@ -252,23 +269,33 @@
252269
253 def test_clone_nonempty_loom_bottom(self):270 def test_clone_nonempty_loom_bottom(self):
254 """Cloning loom should reset the current loom pointer."""271 """Cloning loom should reset the current loom pointer."""
272 self.make_and_clone_simple_loom()
273
274 def make_and_clone_simple_loom(self):
255 source_tree = self.get_tree_with_one_commit('source')275 source_tree = self.get_tree_with_one_commit('source')
256 source_tree.branch.new_thread('bottom')276 source_tree.branch.new_thread('bottom')
257 source_tree.branch.new_thread('top')277 source_tree.branch.new_thread('top')
258 source_tree.branch.nick = 'top'278 source_tree.branch._set_nick('top')
259 source_tree.commit('phwoar', allow_pointless=True)279 source_tree.commit('phwoar', allow_pointless=True)
260 source_tree.branch.record_loom('commit to loom')280 source_tree.branch.record_loom('commit to loom')
261 LoomTreeDecorator(source_tree).down_thread()281 LoomTreeDecorator(source_tree).down_thread()
262 # now clone282 # now clone from the 'default url' - transport_server rather than
263 target_tree = source_tree.bzrdir.clone('target').open_workingtree()283 # vfs_server.
284 source_branch = Branch.open(self.get_url('source'))
285 target_tree = source_branch.bzrdir.sprout('target').open_workingtree()
264 self.assertLoomSproutedOk(source_tree, target_tree)286 self.assertLoomSproutedOk(source_tree, target_tree)
265287
288 def test_sprout_remote_loom(self):
289 # RemoteBranch should permit sprouting properly.
290 self.transport_server = test_server.SmartTCPServer_for_testing
291 self.make_and_clone_simple_loom()
292
266 def test_sprout_nonempty_loom_bottom(self):293 def test_sprout_nonempty_loom_bottom(self):
267 """Sprouting always resets the loom to the top."""294 """Sprouting always resets the loom to the top."""
268 source_tree = self.get_tree_with_one_commit('source')295 source_tree = self.get_tree_with_one_commit('source')
269 source_tree.branch.new_thread('bottom')296 source_tree.branch.new_thread('bottom')
270 source_tree.branch.new_thread('top')297 source_tree.branch.new_thread('top')
271 source_tree.branch.nick = 'top'298 source_tree.branch._set_nick('top')
272 source_tree.commit('phwoar', allow_pointless=True)299 source_tree.commit('phwoar', allow_pointless=True)
273 source_tree.branch.record_loom('commit to loom')300 source_tree.branch.record_loom('commit to loom')
274 LoomTreeDecorator(source_tree).down_thread()301 LoomTreeDecorator(source_tree).down_thread()
@@ -322,10 +349,10 @@
322 source = self.get_tree_with_loom('source')349 source = self.get_tree_with_loom('source')
323 source.branch.new_thread('bottom')350 source.branch.new_thread('bottom')
324 source.branch.new_thread('top')351 source.branch.new_thread('top')
325 source.branch.nick = 'bottom'352 source.branch._set_nick('bottom')
326 source.branch.record_loom('commit to loom')353 source.branch.record_loom('commit to loom')
327 target = source.bzrdir.sprout('target').open_branch()354 target = source.bzrdir.sprout('target').open_branch()
328 target.nick = 'top'355 target._set_nick('top')
329 # put a commit in the bottom and top of this loom356 # put a commit in the bottom and top of this loom
330 bottom_rev1 = source.commit('commit my arse')357 bottom_rev1 = source.commit('commit my arse')
331 source_loom_tree = LoomTreeDecorator(source)358 source_loom_tree = LoomTreeDecorator(source)
@@ -355,14 +382,20 @@
355 382
356 def test_pull_into_empty_loom(self):383 def test_pull_into_empty_loom(self):
357 """Doing a pull into a loom with no loom revisions works."""384 """Doing a pull into a loom with no loom revisions works."""
385 self.pull_into_empty_loom()
386
387 def pull_into_empty_loom(self):
358 source = self.get_tree_with_loom('source')388 source = self.get_tree_with_loom('source')
359 target = source.bzrdir.sprout('target').open_branch()389 target = source.bzrdir.sprout('target').open_branch()
360 source.branch.new_thread('a thread')390 source.branch.new_thread('a thread')
361 source.branch.nick = 'a thread'391 source.branch._set_nick('a thread')
362 # put a commit in the thread for source.392 # put a commit in the thread for source.
363 bottom_rev1 = source.commit('commit a thread')393 bottom_rev1 = source.commit('commit a thread')
364 source.branch.record_loom('commit to loom')394 source.branch.record_loom('commit to loom')
365 target.pull(source.branch)395 # now pull from the 'default url' - transport_server rather than
396 # vfs_server - this may be a RemoteBranch.
397 source_branch = Branch.open(self.get_url('source'))
398 target.pull(source_branch)
366 # check loom threads399 # check loom threads
367 threads = target.get_loom_state().get_threads()400 threads = target.get_loom_state().get_threads()
368 self.assertEqual(401 self.assertEqual(
@@ -374,12 +407,17 @@
374 self.assertTrue(target.repository.has_revision(rev_id))407 self.assertTrue(target.repository.has_revision(rev_id))
375 self.assertEqual(source.branch.loom_parents(), target.loom_parents())408 self.assertEqual(source.branch.loom_parents(), target.loom_parents())
376409
410 def test_pull_remote_loom(self):
411 # RemoteBranch should permit sprouting properly.
412 self.transport_server = test_server.SmartTCPServer_for_testing
413 self.pull_into_empty_loom()
414
377 def test_pull_thread_at_null(self):415 def test_pull_thread_at_null(self):
378 """Doing a pull when the source loom has a thread with no history."""416 """Doing a pull when the source loom has a thread with no history."""
379 source = self.get_tree_with_loom('source')417 source = self.get_tree_with_loom('source')
380 target = source.bzrdir.sprout('target').open_branch()418 target = source.bzrdir.sprout('target').open_branch()
381 source.branch.new_thread('a thread')419 source.branch.new_thread('a thread')
382 source.branch.nick = 'a thread'420 source.branch._set_nick('a thread')
383 source.branch.record_loom('commit to loom')421 source.branch.record_loom('commit to loom')
384 target.pull(source.branch)422 target.pull(source.branch)
385 # check loom threads423 # check loom threads
@@ -398,10 +436,10 @@
398 source = self.get_tree_with_loom('source')436 source = self.get_tree_with_loom('source')
399 source.branch.new_thread('bottom')437 source.branch.new_thread('bottom')
400 source.branch.new_thread('top')438 source.branch.new_thread('top')
401 source.branch.nick = 'bottom'439 source.branch._set_nick('bottom')
402 source.branch.record_loom('commit to loom')440 source.branch.record_loom('commit to loom')
403 target = source.bzrdir.sprout('target').open_branch()441 target = source.bzrdir.sprout('target').open_branch()
404 target.nick = 'top'442 target._set_nick('top')
405 # put a commit in the bottom and top of this loom443 # put a commit in the bottom and top of this loom
406 bottom_rev1 = source.commit('commit bottom')444 bottom_rev1 = source.commit('commit bottom')
407 source_loom_tree = LoomTreeDecorator(source)445 source_loom_tree = LoomTreeDecorator(source)
@@ -432,7 +470,7 @@
432 def test_implicit_record(self):470 def test_implicit_record(self):
433 tree = self.get_tree_with_loom('source')471 tree = self.get_tree_with_loom('source')
434 tree.branch.new_thread('bottom')472 tree.branch.new_thread('bottom')
435 tree.branch.nick = 'bottom'473 tree.branch._set_nick('bottom')
436 tree.lock_write()474 tree.lock_write()
437 try:475 try:
438 bottom_rev1 = tree.commit('commit my arse')476 bottom_rev1 = tree.commit('commit my arse')
@@ -453,7 +491,7 @@
453 self.assertEqual([], tree.branch.loom_parents())491 self.assertEqual([], tree.branch.loom_parents())
454 # add a thread and record it.492 # add a thread and record it.
455 tree.branch.new_thread('bottom')493 tree.branch.new_thread('bottom')
456 tree.branch.nick = 'bottom'494 tree.branch._set_nick('bottom')
457 rev_id = tree.branch.record_loom('Setup test loom.')495 rev_id = tree.branch.record_loom('Setup test loom.')
458 # after recording, the parents list should have changed.496 # after recording, the parents list should have changed.
459 self.assertEqual([rev_id], tree.branch.loom_parents())497 self.assertEqual([rev_id], tree.branch.loom_parents())
@@ -464,7 +502,7 @@
464 # new threads502 # new threads
465 tree.branch.new_thread('foo')503 tree.branch.new_thread('foo')
466 tree.branch.new_thread('bar')504 tree.branch.new_thread('bar')
467 tree.branch.nick = 'bar'505 tree.branch._set_nick('bar')
468 last_rev = tree.branch.last_revision()506 last_rev = tree.branch.last_revision()
469 # and a change to the revision history of this thread507 # and a change to the revision history of this thread
470 tree.commit('change bar', allow_pointless=True)508 tree.commit('change bar', allow_pointless=True)
@@ -478,7 +516,7 @@
478 # new threads516 # new threads
479 tree.branch.new_thread('foo')517 tree.branch.new_thread('foo')
480 tree.branch.new_thread('bar')518 tree.branch.new_thread('bar')
481 tree.branch.nick = 'bar'519 tree.branch._set_nick('bar')
482 # and a change to the revision history of this thread520 # and a change to the revision history of this thread
483 tree.commit('change bar', allow_pointless=True)521 tree.commit('change bar', allow_pointless=True)
484 # now record522 # now record
@@ -503,7 +541,7 @@
503 # new threads541 # new threads
504 tree.branch.new_thread('base')542 tree.branch.new_thread('base')
505 tree.branch.new_thread('top')543 tree.branch.new_thread('top')
506 tree.branch.nick = 'top'544 tree.branch._set_nick('top')
507 # and a change to the revision history of this thread545 # and a change to the revision history of this thread
508 tree.tree.commit('change top', allow_pointless=True)546 tree.tree.commit('change top', allow_pointless=True)
509 last_rev = tree.branch.last_revision()547 last_rev = tree.branch.last_revision()
@@ -527,7 +565,7 @@
527 tree.branch.new_thread('foo')565 tree.branch.new_thread('foo')
528 tree.branch.new_thread('bar')566 tree.branch.new_thread('bar')
529 # do a commit, so the last_revision should change.567 # do a commit, so the last_revision should change.
530 tree.branch.nick = 'bar'568 tree.branch._set_nick('bar')
531 tree.commit('bar-ness', allow_pointless=True)569 tree.commit('bar-ness', allow_pointless=True)
532 tree.branch.revert_thread('bar')570 tree.branch.revert_thread('bar')
533 self.assertEqual(571 self.assertEqual(
@@ -540,11 +578,11 @@
540 # ensure we have some stuff to revert578 # ensure we have some stuff to revert
541 tree.branch.new_thread('foo')579 tree.branch.new_thread('foo')
542 tree.branch.new_thread('bar')580 tree.branch.new_thread('bar')
543 tree.branch.nick = 'foo'581 tree.branch._set_nick('foo')
544 # record the loom to put the threads in the basis582 # record the loom to put the threads in the basis
545 tree.branch.record_loom('record it!')583 tree.branch.record_loom('record it!')
546 # do a commit, so the last_revision should change.584 # do a commit, so the last_revision should change.
547 tree.branch.nick = 'bar'585 tree.branch._set_nick('bar')
548 tree.commit('bar-ness', allow_pointless=True)586 tree.commit('bar-ness', allow_pointless=True)
549 tree.branch.revert_thread('bar')587 tree.branch.revert_thread('bar')
550 self.assertEqual(588 self.assertEqual(
@@ -558,7 +596,7 @@
558 tree = self.get_tree_with_loom()596 tree = self.get_tree_with_loom()
559 tree.branch.new_thread('bar')597 tree.branch.new_thread('bar')
560 tree.branch.new_thread('foo')598 tree.branch.new_thread('foo')
561 tree.branch.nick = 'bar'599 tree.branch._set_nick('bar')
562 tree.branch.remove_thread('foo')600 tree.branch.remove_thread('foo')
563 state = tree.branch.get_loom_state()601 state = tree.branch.get_loom_state()
564 self.assertEqual([('bar', 'empty:', [])], state.get_threads())602 self.assertEqual([('bar', 'empty:', [])], state.get_threads())
@@ -569,17 +607,17 @@
569 self.assertEqual([], tree.branch.get_threads(NULL_REVISION))607 self.assertEqual([], tree.branch.get_threads(NULL_REVISION))
570 # and loom history should make no difference:608 # and loom history should make no difference:
571 tree.branch.new_thread('foo')609 tree.branch.new_thread('foo')
572 tree.branch.nick = 'foo'610 tree.branch._set_nick('foo')
573 tree.branch.record_loom('foo')611 tree.branch.record_loom('foo')
574 self.assertEqual([], tree.branch.get_threads(NULL_REVISION))612 self.assertEqual([], tree.branch.get_threads(NULL_REVISION))
575613
576 def get_multi_threaded(self):614 def get_multi_threaded(self):
577 tree = self.get_tree_with_loom()615 tree = self.get_tree_with_loom()
578 tree.branch.new_thread('thread1')616 tree.branch.new_thread('thread1')
579 tree.branch.nick = 'thread1'617 tree.branch._set_nick('thread1')
580 tree.commit('thread1', rev_id='thread1-id')618 tree.commit('thread1', rev_id='thread1-id')
581 tree.branch.new_thread('thread2', 'thread1')619 tree.branch.new_thread('thread2', 'thread1')
582 tree.branch.nick = 'thread2'620 tree.branch._set_nick('thread2')
583 tree.commit('thread2', rev_id='thread2-id')621 tree.commit('thread2', rev_id='thread2-id')
584 return tree622 return tree
585623
@@ -635,3 +673,14 @@
635 export_branch = Branch.open_from_transport(673 export_branch = Branch.open_from_transport(
636 root_transport.clone('thread1'))674 root_transport.clone('thread1'))
637 self.assertEqual('thread1-id', export_branch.last_revision())675 self.assertEqual('thread1-id', export_branch.last_revision())
676
677 def test_set_nick_renames_thread(self):
678 tree = self.get_tree_with_loom()
679 tree.branch.new_thread(tree.branch.nick)
680 orig_threads = tree.branch.get_loom_state().get_threads()
681 new_thread_name = 'new thread name'
682 tree.branch.nick = new_thread_name
683 new_threads = tree.branch.get_loom_state().get_threads()
684 self.assertNotEqual(orig_threads, new_threads)
685 self.assertEqual(new_thread_name, new_threads[0][0])
686 self.assertEqual(new_thread_name, tree.branch.nick)
638687
=== modified file 'tests/test_revspec.py'
--- tests/test_revspec.py 2008-09-12 01:55:17 +0000
+++ tests/test_revspec.py 2010-08-05 18:57:44 +0000
@@ -19,26 +19,29 @@
19"""Tests of the loom revision-specifiers."""19"""Tests of the loom revision-specifiers."""
2020
2121
22import bzrlib22import bzrlib.errors
23from bzrlib.plugins.loom.branch import NoLowerThread, NoSuchThread23from bzrlib.plugins.loom.branch import NoLowerThread, NoSuchThread
24from bzrlib.plugins.loom.tests import TestCaseWithLoom24from bzrlib.plugins.loom.tests import TestCaseWithLoom
25import bzrlib.plugins.loom.tree25import bzrlib.plugins.loom.tree
26from bzrlib.revisionspec import RevisionSpec26from bzrlib.revisionspec import RevisionSpec
2727
2828
29class TestThreadRevSpec(TestCaseWithLoom):29class TestRevSpec(TestCaseWithLoom):
30 """Tests of the ThreadRevisionSpecifier."""30
31
32 def get_two_thread_loom(self):31 def get_two_thread_loom(self):
33 tree = self.get_tree_with_loom('source')32 tree = self.get_tree_with_loom('source')
34 tree.branch.new_thread('bottom')33 tree.branch.new_thread('bottom')
35 tree.branch.new_thread('top')34 tree.branch.new_thread('top')
36 tree.branch.nick = 'bottom'35 tree.branch._set_nick('bottom')
37 rev_id_bottom = tree.commit('change bottom')36 rev_id_bottom = tree.commit('change bottom')
38 loom_tree = bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)37 loom_tree = bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)
39 loom_tree.up_thread()38 loom_tree.up_thread()
40 rev_id_top = tree.commit('change top')39 rev_id_top = tree.commit('change top')
41 return tree, loom_tree, rev_id_bottom, rev_id_top40 return tree, loom_tree, rev_id_bottom, rev_id_top
41
42
43class TestThreadRevSpec(TestRevSpec):
44 """Tests of the ThreadRevisionSpecifier."""
42 45
43 def test_thread_colon_at_bottom_errors(self):46 def test_thread_colon_at_bottom_errors(self):
44 tree, loom_tree, rev_id, _ = self.get_two_thread_loom()47 tree, loom_tree, rev_id, _ = self.get_two_thread_loom()
@@ -69,3 +72,44 @@
69 loom_tree.down_thread()72 loom_tree.down_thread()
70 spec = RevisionSpec.from_string('thread:top')73 spec = RevisionSpec.from_string('thread:top')
71 self.assertEqual(rev_id, spec.as_revision_id(tree.branch))74 self.assertEqual(rev_id, spec.as_revision_id(tree.branch))
75
76 def test_thread_on_non_loom_gives_BzrError(self):
77 tree = self.make_branch_and_tree('.')
78 spec = RevisionSpec.from_string('thread:')
79 err = self.assertRaises(bzrlib.errors.BzrError, spec.as_revision_id,
80 tree.branch)
81 self.assertFalse(err.internal_error)
82
83
84class TestBelowRevSpec(TestRevSpec):
85 """Tests of the below: revision specifier."""
86
87 def test_below_gets_tip_of_thread_below(self):
88 tree, loom_tree, _, rev_id = self.get_two_thread_loom()
89 loom_tree.down_thread()
90 expected_id = tree.branch.last_revision()
91 loom_tree.up_thread()
92 spec = RevisionSpec.from_string('below:')
93 self.assertEqual(expected_id, spec.as_revision_id(tree.branch))
94
95 def test_below_on_bottom_thread_gives_BzrError(self):
96 tree, loom_tree, _, rev_id = self.get_two_thread_loom()
97 loom_tree.down_thread()
98 spec = RevisionSpec.from_string('below:')
99 err = self.assertRaises(bzrlib.errors.BzrError, spec.as_revision_id,
100 tree.branch)
101 self.assertFalse(err.internal_error)
102
103 def test_below_named_thread(self):
104 tree, loom_tree, _, rev_id = self.get_two_thread_loom()
105 loom_tree.down_thread()
106 expected_id = tree.branch.last_revision()
107 spec = RevisionSpec.from_string('below:top')
108 self.assertEqual(expected_id, spec.as_revision_id(tree.branch))
109
110 def test_below_on_non_loom_gives_BzrError(self):
111 tree = self.make_branch_and_tree('.')
112 spec = RevisionSpec.from_string('below:')
113 err = self.assertRaises(bzrlib.errors.BzrError, spec.as_revision_id,
114 tree.branch)
115 self.assertFalse(err.internal_error)
72116
=== modified file 'tests/test_tree.py'
--- tests/test_tree.py 2008-11-24 16:38:16 +0000
+++ tests/test_tree.py 2010-08-05 18:57:44 +0000
@@ -38,14 +38,14 @@
38 tree = self.get_tree_with_loom('source')38 tree = self.get_tree_with_loom('source')
39 tree.branch.new_thread('bottom')39 tree.branch.new_thread('bottom')
40 tree.branch.new_thread('top')40 tree.branch.new_thread('top')
41 tree.branch.nick = 'bottom'41 tree.branch._set_nick('bottom')
42 return bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)42 return bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)
4343
44 def test_down_thread(self):44 def test_down_thread(self):
45 tree = self.get_tree_with_loom('source')45 tree = self.get_tree_with_loom('source')
46 tree.branch.new_thread('bottom')46 tree.branch.new_thread('bottom')
47 tree.branch.new_thread('top')47 tree.branch.new_thread('top')
48 tree.branch.nick = 'top'48 tree.branch._set_nick('top')
49 loom_tree = bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)49 loom_tree = bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)
50 loom_tree.down_thread()50 loom_tree.down_thread()
51 self.assertEqual('bottom', tree.branch.nick)51 self.assertEqual('bottom', tree.branch.nick)
@@ -53,7 +53,7 @@
53 def _add_thread(self, tree, name):53 def _add_thread(self, tree, name):
54 """Create a new thread with a commit and return the commit id."""54 """Create a new thread with a commit and return the commit id."""
55 tree.branch.new_thread(name)55 tree.branch.new_thread(name)
56 tree.branch.nick = name56 tree.branch._set_nick(name)
57 return tree.commit(name)57 return tree.commit(name)
5858
59 def test_down_named_thread(self):59 def test_down_named_thread(self):
@@ -78,7 +78,7 @@
78 tree = self.get_tree_with_loom('tree')78 tree = self.get_tree_with_loom('tree')
79 tree.branch.new_thread('bottom')79 tree.branch.new_thread('bottom')
80 tree.branch.new_thread('top')80 tree.branch.new_thread('top')
81 tree.branch.nick = 'bottom'81 tree.branch._set_nick('bottom')
82 bottom_rev1 = tree.commit('bottom_commit')82 bottom_rev1 = tree.commit('bottom_commit')
83 tree_loom_tree = bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)83 tree_loom_tree = bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)
84 tree_loom_tree.up_thread()84 tree_loom_tree.up_thread()
@@ -89,10 +89,10 @@
89 """up-thread into a thread that already has this thread is a no-op."""89 """up-thread into a thread that already has this thread is a no-op."""
90 tree = self.get_tree_with_loom('tree')90 tree = self.get_tree_with_loom('tree')
91 tree.branch.new_thread('bottom')91 tree.branch.new_thread('bottom')
92 tree.branch.nick = 'bottom'92 tree.branch._set_nick('bottom')
93 bottom_rev1 = tree.commit('bottom_commit')93 bottom_rev1 = tree.commit('bottom_commit')
94 tree.branch.new_thread('top', 'bottom')94 tree.branch.new_thread('top', 'bottom')
95 tree.branch.nick = 'top'95 tree.branch._set_nick('top')
96 top_rev1 = tree.commit('top_commit', allow_pointless=True)96 top_rev1 = tree.commit('top_commit', allow_pointless=True)
97 tree_loom_tree = bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)97 tree_loom_tree = bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)
98 tree_loom_tree.down_thread()98 tree_loom_tree.down_thread()
@@ -108,10 +108,10 @@
108 """up-thread from a thread with new work."""108 """up-thread from a thread with new work."""
109 tree = self.get_tree_with_loom('tree')109 tree = self.get_tree_with_loom('tree')
110 tree.branch.new_thread('bottom')110 tree.branch.new_thread('bottom')
111 tree.branch.nick = 'bottom'111 tree.branch._set_nick('bottom')
112 bottom_rev1 = tree.commit('bottom_commit')112 bottom_rev1 = tree.commit('bottom_commit')
113 tree.branch.new_thread('top', 'bottom')113 tree.branch.new_thread('top', 'bottom')
114 tree.branch.nick = 'top'114 tree.branch._set_nick('top')
115 top_rev1 = tree.commit('top_commit', allow_pointless=True)115 top_rev1 = tree.commit('top_commit', allow_pointless=True)
116 tree_loom_tree = bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)116 tree_loom_tree = bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)
117 tree_loom_tree.down_thread()117 tree_loom_tree.down_thread()
@@ -144,14 +144,16 @@
144 self.build_tree_contents([('source/a', 'c')])144 self.build_tree_contents([('source/a', 'c')])
145 loom_tree.tree.commit('content to c')145 loom_tree.tree.commit('content to c')
146 loom_tree.up_thread(_mod_merge.WeaveMerger)146 loom_tree.up_thread(_mod_merge.WeaveMerger)
147 self.failIfExists('source/a.BASE')147 # Disabled because WeaveMerger writes BASE files now. XXX: Figure out
148 # how to test this actually worked, again.
149 # self.failIfExists('source/a.BASE')
148150
149 def get_loom_with_three_threads(self):151 def get_loom_with_three_threads(self):
150 tree = self.get_tree_with_loom('source')152 tree = self.get_tree_with_loom('source')
151 tree.branch.new_thread('bottom')153 tree.branch.new_thread('bottom')
152 tree.branch.new_thread('middle')154 tree.branch.new_thread('middle')
153 tree.branch.new_thread('top')155 tree.branch.new_thread('top')
154 tree.branch.nick = 'bottom'156 tree.branch._set_nick('bottom')
155 return bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)157 return bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)
156158
157 def test_up_many(self):159 def test_up_many(self):
@@ -191,12 +193,26 @@
191 self.assertEqual(['middle-1', 'bottom-2'], tree.get_parent_ids())193 self.assertEqual(['middle-1', 'bottom-2'], tree.get_parent_ids())
192 self.assertEqual(1, len(tree.conflicts()))194 self.assertEqual(1, len(tree.conflicts()))
193195
196 def test_up_many_target_thread(self):
197 loom_tree = self.get_loom_with_three_threads()
198 tree = loom_tree.tree
199 loom_tree.up_many(target_thread='middle')
200 self.assertEqual('middle', tree.branch.nick)
201
202 def test_up_many_target_thread_lower(self):
203 loom_tree = self.get_loom_with_three_threads()
204 tree = loom_tree.tree
205 loom_tree.up_many(target_thread='top')
206 e = self.assertRaises(errors.BzrCommandError,
207 loom_tree.up_many, target_thread='middle')
208 self.assertEqual('Cannot up-thread to lower thread.', str(e))
209
194 def test_revert_loom(self):210 def test_revert_loom(self):
195 tree = self.get_tree_with_loom(',')211 tree = self.get_tree_with_loom(',')
196 # ensure we have some stuff to revert212 # ensure we have some stuff to revert
197 tree.branch.new_thread('foo')213 tree.branch.new_thread('foo')
198 tree.branch.new_thread('bar')214 tree.branch.new_thread('bar')
199 tree.branch.nick = 'bar'215 tree.branch._set_nick('bar')
200 tree.commit('change something', allow_pointless=True)216 tree.commit('change something', allow_pointless=True)
201 loom_tree = bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)217 loom_tree = bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)
202 loom_tree.revert_loom()218 loom_tree.revert_loom()
@@ -211,7 +227,7 @@
211 # ensure we have some stuff to revert227 # ensure we have some stuff to revert
212 tree.branch.new_thread('foo')228 tree.branch.new_thread('foo')
213 tree.branch.new_thread('bar')229 tree.branch.new_thread('bar')
214 tree.branch.nick = 'bar'230 tree.branch._set_nick('bar')
215 tree.commit('change something', allow_pointless=True)231 tree.commit('change something', allow_pointless=True)
216 loom_tree = bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)232 loom_tree = bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)
217 loom_tree.revert_loom(thread='bar')233 loom_tree.revert_loom(thread='bar')
@@ -228,7 +244,7 @@
228 # ensure we have some stuff to revert244 # ensure we have some stuff to revert
229 tree.branch.new_thread('foo')245 tree.branch.new_thread('foo')
230 tree.branch.new_thread('bar')246 tree.branch.new_thread('bar')
231 tree.branch.nick = 'bar'247 tree.branch._set_nick('bar')
232 tree.commit('change something', allow_pointless=True)248 tree.commit('change something', allow_pointless=True)
233 loom_tree = bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)249 loom_tree = bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)
234 loom_tree.revert_loom(thread='foo')250 loom_tree.revert_loom(thread='foo')
235251
=== modified file 'tree.py'
--- tree.py 2008-11-24 16:38:16 +0000
+++ tree.py 2010-08-05 18:57:44 +0000
@@ -24,7 +24,10 @@
24__all__ = ['LoomTreeDecorator']24__all__ = ['LoomTreeDecorator']
2525
2626
27from bzrlib import ui27from bzrlib import (
28 trace,
29 ui,
30 )
28from bzrlib.decorators import needs_write_lock31from bzrlib.decorators import needs_write_lock
29import bzrlib.errors32import bzrlib.errors
30import bzrlib.revision33import bzrlib.revision
@@ -78,7 +81,7 @@
78 graph = self.tree.branch.repository.get_graph()81 graph = self.tree.branch.repository.get_graph()
79 # special case no-change condition.82 # special case no-change condition.
80 if new_thread_rev == old_thread_rev:83 if new_thread_rev == old_thread_rev:
81 self.tree.branch.nick = new_thread_name84 self.tree.branch._set_nick(new_thread_name)
82 return 085 return 0
83 if new_thread_rev == EMPTY_REVISION:86 if new_thread_rev == EMPTY_REVISION:
84 new_thread_rev = bzrlib.revision.NULL_REVISION87 new_thread_rev = bzrlib.revision.NULL_REVISION
@@ -87,19 +90,15 @@
87 # merge the tree up into the new patch:90 # merge the tree up into the new patch:
88 if merge_type is None:91 if merge_type is None:
89 merge_type = bzrlib.merge.Merge3Merger92 merge_type = bzrlib.merge.Merge3Merger
90 pb = ui.ui_factory.nested_progress_bar()
91 try:93 try:
92 try:94 merge_controller = bzrlib.merge.Merger.from_revision_ids(
93 merge_controller = bzrlib.merge.Merger.from_revision_ids(95 None, self.tree, new_thread_rev, revision_graph=graph)
94 pb, self.tree, new_thread_rev, revision_graph=graph)96 except bzrlib.errors.UnrelatedBranches:
95 except bzrlib.errors.UnrelatedBranches:97 raise bzrlib.errors.BzrCommandError('corrupt loom: thread %s'
96 raise bzrlib.errors.BzrCommandError('corrupt loom: thread %s'98 ' has no common ancestor with thread %s'
97 ' has no common ancestor with thread %s'99 % (new_thread_name, threadname))
98 % (new_thread_name, threadname))100 merge_controller.merge_type = merge_type
99 merge_controller.merge_type = merge_type101 result = merge_controller.do_merge()
100 result = merge_controller.do_merge()
101 finally:
102 pb.finished()
103 # change the tree to the revision of the new thread.102 # change the tree to the revision of the new thread.
104 parent_trees = []103 parent_trees = []
105 if new_thread_rev != bzrlib.revision.NULL_REVISION:104 if new_thread_rev != bzrlib.revision.NULL_REVISION:
@@ -126,20 +125,37 @@
126 # change the branch125 # change the branch
127 self.tree.branch.generate_revision_history(new_thread_rev)126 self.tree.branch.generate_revision_history(new_thread_rev)
128 # update the branch nick.127 # update the branch nick.
129 self.tree.branch.nick = new_thread_name128 self.tree.branch._set_nick(new_thread_name)
130 bzrlib.trace.note("Moved to thread '%s'." % new_thread_name)129 trace.note("Moved to thread '%s'." % new_thread_name)
130 if (basis_tree is not None and
131 not result and not
132 self.tree.changes_from(basis_tree).has_changed()):
133 trace.note("This thread is now empty, you may wish to "
134 'run "bzr combine-thread" to remove it.')
131 if result != 0:135 if result != 0:
132 return 1136 return 1
133 else:137 else:
134 return 0138 return 0
135139
136 def up_many(self, merge_type=None):140 def up_many(self, merge_type=None, target_thread=None):
137 threads = self.branch.get_loom_state().get_threads()141 loom_state = self.branch.get_loom_state()
138 top_thread_name = threads[-1][0]142 threads = loom_state.get_threads()
139 while self.branch.nick != top_thread_name:143 if target_thread is None:
144 target_thread = threads[-1][0]
145 if self.branch.nick == target_thread:
146 raise bzrlib.errors.BzrCommandError(
147 'Cannot move up from the highest thread.')
148 else:
149 upper_thread_i = loom_state.thread_index(target_thread)
150 lower_thread_i = loom_state.thread_index(self.branch.nick)
151 if lower_thread_i > upper_thread_i:
152 raise bzrlib.errors.BzrCommandError(
153 "Cannot up-thread to lower thread.")
154 while self.branch.nick != target_thread:
140 old_nick = self.branch.nick155 old_nick = self.branch.nick
141 if self.up_thread(merge_type) != 0:156 result = self.up_thread(merge_type)
142 break157 if result != 0:
158 return result
143 if len(self.tree.get_parent_ids()) > 1:159 if len(self.tree.get_parent_ids()) > 1:
144 self.tree.commit('Merge %s into %s' % (old_nick,160 self.tree.commit('Merge %s into %s' % (old_nick,
145 self.branch.nick))161 self.branch.nick))
@@ -147,12 +163,11 @@
147 @needs_write_lock163 @needs_write_lock
148 def down_thread(self, name=None):164 def down_thread(self, name=None):
149 """Move to a thread down in the loom.165 """Move to a thread down in the loom.
150 166
151 :param name: If None, use the next lower thread; otherwise the nae of167 :param name: If None, use the next lower thread; otherwise the nae of
152 the thread to move to.168 the thread to move to.
153 """169 """
154 self._check_switch()170 self._check_switch()
155 current_revision = self.tree.last_revision()
156 threadname = self.tree.branch.nick171 threadname = self.tree.branch.nick
157 state = self.tree.branch.get_loom_state()172 state = self.tree.branch.get_loom_state()
158 threads = state.get_threads()173 threads = state.get_threads()
@@ -168,7 +183,7 @@
168 index = state.thread_index(name)183 index = state.thread_index(name)
169 new_thread_rev = threads[index][1]184 new_thread_rev = threads[index][1]
170 assert new_thread_rev is not None185 assert new_thread_rev is not None
171 self.tree.branch.nick = new_thread_name186 self.tree.branch._set_nick(new_thread_name)
172 if new_thread_rev == old_thread_rev:187 if new_thread_rev == old_thread_rev:
173 # fast path no-op changes188 # fast path no-op changes
174 bzrlib.trace.note("Moved to thread '%s'." % new_thread_name)189 bzrlib.trace.note("Moved to thread '%s'." % new_thread_name)
@@ -177,17 +192,30 @@
177 new_thread_rev = bzrlib.revision.NULL_REVISION192 new_thread_rev = bzrlib.revision.NULL_REVISION
178 if old_thread_rev == EMPTY_REVISION:193 if old_thread_rev == EMPTY_REVISION:
179 old_thread_rev = bzrlib.revision.NULL_REVISION194 old_thread_rev = bzrlib.revision.NULL_REVISION
180 basis_tree = self.tree.branch.repository.revision_tree(old_thread_rev)195 repository = self.tree.branch.repository
181 to_tree = self.tree.branch.repository.revision_tree(new_thread_rev)196 try:
197 basis_tree = self.tree.revision_tree(old_thread_rev)
198 except bzrlib.errors.NoSuchRevisionInTree:
199 basis_tree = repository.revision_tree(old_thread_rev)
200 to_tree = repository.revision_tree(new_thread_rev)
182 result = bzrlib.merge.merge_inner(self.tree.branch,201 result = bzrlib.merge.merge_inner(self.tree.branch,
183 to_tree,202 to_tree,
184 basis_tree,203 basis_tree,
185 this_tree=self.tree)204 this_tree=self.tree)
186 self.tree.branch.generate_revision_history(new_thread_rev)205 branch_revno, branch_revision = self.tree.branch.last_revision_info()
187 self.tree.set_last_revision(new_thread_rev)206 graph = repository.get_graph()
207 new_thread_revno = graph.find_distance_to_null(new_thread_rev,
208 [(branch_revision, branch_revno)])
209 self.tree.branch.set_last_revision_info(new_thread_revno,
210 new_thread_rev)
211 if new_thread_rev == bzrlib.revision.NULL_REVISION:
212 parent_list = []
213 else:
214 parent_list = [(new_thread_rev, to_tree)]
215 self.tree.set_parent_trees(parent_list)
188 bzrlib.trace.note("Moved to thread '%s'." % new_thread_name)216 bzrlib.trace.note("Moved to thread '%s'." % new_thread_name)
189 return result217 return result
190 218
191 def lock_write(self):219 def lock_write(self):
192 self.tree.lock_write()220 self.tree.lock_write()
193221
194222
=== added file 'version.py'
--- version.py 1970-01-01 00:00:00 +0000
+++ version.py 2010-08-05 18:57:44 +0000
@@ -0,0 +1,28 @@
1# Loom, a plugin for bzr to assist in developing focused patches.
2# Copyright (C) 2010 Canonical Limited.
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License version 2 as published
6# by the Free Software Foundation.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program; if not, write to the Free Software
15# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
16#
17
18"""Versioning information for bzr-loom."""
19
20__all__ = [
21 'bzr_plugin_version',
22 'bzr_minimum_version',
23 'bzr_maximum_version',
24 ]
25
26bzr_plugin_version = (2, 2, 1, 'dev', 0)
27bzr_minimum_version = (2, 2, 0)
28bzr_maximum_version = None

Subscribers

People subscribed via source and target branches