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
1=== added file '.testr.conf'
2--- .testr.conf 1970-01-01 00:00:00 +0000
3+++ .testr.conf 2010-08-05 18:57:44 +0000
4@@ -0,0 +1,4 @@
5+[DEFAULT]
6+# not quite idea, because 'all tests' is too many, and 'just loom' is too few.
7+test_command=bzr selftest --subunit $IDOPTION
8+test_id_option=--load-list $IDFILE
9
10=== modified file 'HOWTO'
11--- HOWTO 2008-09-12 01:55:17 +0000
12+++ HOWTO 2010-08-05 18:57:44 +0000
13@@ -47,6 +47,11 @@
14
15 This will convert your branch to a loom - at this point you will require the
16 loom plugin to use bzr on it. It will also create a thread called 'upstream'.
17+
18+The bzr ``nick`` is used to index into the threads in the loom. The current
19+thread is the bzr nick for the branch and is recorded in commits as such. You
20+can use ``bzr nick newname`` to change the name of a thread.
21+
22 If you do 'bzr push' to a new branch now, it will make the remote branch be a
23 loom as well. If you push to an existing normal bzr branch, then the current
24 thread of your loom is what will be pushed. You can use this to publish
25@@ -177,7 +182,7 @@
26 When you are doing an update to upstream and they have merged a patch, your
27 thread will suddenly lose all its changes. Lets say in the example above that
28 upstream have merged the autoconf update. When you are updating that thread,
29-add in a call to ``diff -r thread:`` and you will see no changes.
30+add in a call to ``diff -r below:`` and you will see no changes.
31
32 % bzr up-thread
33 All changes applied successfully.
34@@ -201,3 +206,16 @@
35 debian
36 =>fix 64-bit compilation
37 upstream
38+
39+
40+Showing a single patch
41+----------------------
42+
43+You can show a single patch without knowing the names of other threads by using
44+the below: and thread: revision specifiers::
45+
46+ % bzr show-loom
47+ debian
48+ =>update-configure
49+ upstream
50+ % bzr diff -r below:update-configure..thread:update-configure
51
52=== modified file 'NEWS'
53--- NEWS 2008-11-19 07:18:17 +0000
54+++ NEWS 2010-08-05 18:57:44 +0000
55@@ -5,7 +5,95 @@
56 .. contents::
57
58 IN DEVELOPMENT
59---------------
60+==============
61+
62+NOTES WHEN UPGRADING
63+--------------------
64+
65+* bzr-loom requires bzr 2.2.0 (or very recent 2.2b releases) due to an API
66+ change in bzr needed to fix branching and pulling of looms. On older versions
67+ of bzr bzr-loom will still work for most operations but will fail when making
68+ new branches as part of a push or branch operation. (Robert Collins, #201613)
69+
70+CHANGES
71+-------
72+
73+* --auto is now the default on up-thread. You can supply a thread name to stop
74+ at a given thread, or --manual to go up a single thread. (Aaron Bentley)
75+
76+* ``bzr combine-thread`` now accepts a ``--force`` option.
77+
78+FEATURES
79+--------
80+
81+* A new revision specifier ``below:`` has been added. (Robert Collins, #195282)
82+
83+IMPROVEMENTS
84+------------
85+
86+* Loom now takes advantage of lazy loading of bzr objects (though not to a
87+ complete degree), reducing the overhead of having it installed.
88+ (Robert Collins)
89+
90+BUGFIXES
91+--------
92+
93+* ``bzr combine-thread`` will no longer combine threads without ``--force``
94+ when the thread being removed has work not merged into either the thread
95+ above or below. (Robert Collins, #506235)
96+
97+* ``bzr loomify`` explicitly checks that branches being converted are not Looms
98+ already. This should not have been needed, but apparently it was.
99+ (Robert Collins, #600452)
100+
101+* ``bzr nick`` will now rename a thread rather than setting the current thread
102+ pointer to an invalid value. (Robert Collins, #203203, #260947, #304608)
103+
104+* ``bzr nick`` will now rename the branch too. (Vincent Ladeuil, #606174)
105+
106+* ``switch`` now accepts the ``--directory`` option. (Vincent Ladeuil, #595563)
107+
108+* The ``thread:`` revision specifier will no longer throw an attribute error
109+ when used on a normal branch. (Robert Collins, #231283)
110+
111+API BREAKS
112+----------
113+
114+TESTING
115+-------
116+
117+INTERNALS
118+---------
119+
120+2.1
121+===
122+
123+ NOTES WHEN UPGRADING:
124+
125+ CHANGES:
126+
127+ FEATURES:
128+
129+ IMPROVEMENTS:
130+
131+ BUGFIXES:
132+
133+ * Stop using APIs deprecated for 2.1.0 (child progress bars for
134+ merge and trace.info). (Vincent Ladeuil, #528472)
135+
136+ * Work with changes to bzr trunk - colocated branches and switch -r.
137+
138+ API BREAKS:
139+
140+ TESTING:
141+
142+ INTERNALS:
143+
144+ * .testr.conf added to help use with testr - still need to specify what tests
145+ to run. (Robert Collins)
146+
147+2.0
148+===
149
150 NOTES WHEN UPGRADING:
151
152@@ -21,14 +109,26 @@
153 * ``bzr switch`` now accepts ``top:`` and ``bottom:`` to jump to the top
154 and bottom thread respectively. (Jonathan Lange)
155
156+ * ``bzr switch -b newthread`` now works. (Robert Collins, #433811)
157+
158 * ``bzr push`` now pushes the last-loom rather than creating an empty loom.
159 (Robert Collins, #201613)
160
161 * ``up`` and ``down`` are now aliases for ``up-thread`` and
162 ``down-thread`` respectively.
163
164+ * ``up-thread`` now notifies when a thread becomes empty. This is a step
165+ towards removing it automatically/prompting to do so.
166+ (James Westby, #195133)
167+
168 BUGFIXES:
169
170+ * ``pull`` expects the keywork local. (Mark Lee, #379347)
171+
172+ * ``setup.py`` doesn't actually install. (Mark Lee, #379069)
173+
174+ * module has no attribute ``PushResult``. (Robert Collins)
175+
176 API BREAKS:
177
178 TESTING:
179@@ -37,7 +137,7 @@
180
181
182 1.3
183----
184+===
185
186 IMPROVEMENTS:
187
188
189=== modified file 'README'
190--- README 2008-09-12 01:55:17 +0000
191+++ README 2010-08-05 18:57:44 +0000
192@@ -77,9 +77,11 @@
193 branch will pull into the current thread of a loom. This assymetry makes it easy
194 to work with developers who are not using looms.
195
196-Loom also adds a new revision specifier 'thread:'. You can use this to diff
197-against threads in the current loom. For example: 'bzr diff -r thread:' will
198-show you the difference between the current thread and the thread below it.
199+Loom also adds new revision specifiers 'thread:' and 'below:'. You can use these
200+to diff against threads in the current Loom. For instance, 'bzr diff -r
201+thread:' will show you the different between the thread below yours, and your
202+thread. See ``bzr help revisionspec`` for the detailed help on these two
203+revision specifiers.
204
205
206 Documentation
207
208=== modified file 'TODO'
209--- TODO 2008-01-20 07:34:29 +0000
210+++ TODO 2010-08-05 18:57:44 +0000
211@@ -95,7 +95,7 @@
212 TODO either means changing 'diff's defaults, adding a flag to diff, or using
213 a different revspec prefix; or something like that.)
214 - 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 / ?)
215-- during up-thread, if we could pull or if there is no diff, then the thread has been merged, offer to remove it.
216+- 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).
217 - loom to have the same 'tree root id' as its branches, to allow nested looms by reference. EEK!.
218 - show-loom to allow -r -1.
219 - 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.
220@@ -110,5 +110,4 @@
221 therein as well, to support uncommit between record calls.
222 - 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).
223 - support tags on push/pull in looms
224-- 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.
225 - perhaps bzr send should send the whole loom ? (e.g. as a patch bomb - a series of patches?)
226
227=== modified file '__init__.py'
228--- __init__.py 2008-09-12 01:55:17 +0000
229+++ __init__.py 2010-08-05 18:57:44 +0000
230@@ -48,19 +48,21 @@
231 to remove a thread which has been merged into upstream.
232
233
234-Loom also adds a new revision specifier 'thread:'. You can use this to diff
235-against threads in the current Loom. For instance, 'bzr diff -r thread:' will
236-show you the different between the thread below yours, and your thread.
237+Loom also adds new revision specifiers 'thread:' and 'below:'. You can use these
238+to diff against threads in the current Loom. For instance, 'bzr diff -r
239+thread:' will show you the different between the thread below yours, and your
240+thread. See ``bzr help revisionspec`` for the detailed help on these two
241+revision specifiers.
242 """
243
244-version_info = (1, 4, 0, 'dev', 0)
245+from version import bzr_plugin_version as version_info
246
247 import bzrlib.builtins
248 import bzrlib.commands
249+import bzrlib.revisionspec
250
251-import branch
252 import commands
253-import revspec
254+import formats
255
256
257 for command in [
258@@ -74,8 +76,12 @@
259 'show_loom',
260 'up_thread',
261 ]:
262- bzrlib.commands.register_command(getattr(commands, 'cmd_' + command))
263+ bzrlib.commands.plugin_cmds.register_lazy('cmd_' + command, [],
264+ 'bzrlib.plugins.loom.commands')
265
266+# XXX: bzr fix needed: for status and switch, we have to register directly, not
267+# lazily, because register_lazy does not stack in the same way register_command
268+# does.
269 if not hasattr(bzrlib.builtins, "cmd_switch"):
270 # provide a switch command (allows
271 bzrlib.commands.register_command(getattr(commands, 'cmd_switch'))
272@@ -86,6 +92,19 @@
273 commands.cmd_status._original_command = bzrlib.commands.register_command(
274 commands.cmd_status, True)
275
276+revspec_registry = getattr(bzrlib.revisionspec, 'revspec_registry', None)
277+if revspec_registry is not None:
278+ revspec_registry.register_lazy('thread:', 'bzrlib.plugins.loom.revspec',
279+ 'RevisionSpecThread')
280+ revspec_registry.register_lazy('below:', 'bzrlib.plugins.loom.revspec',
281+ 'RevisionSpecBelow')
282+else:
283+ import revspec
284+ bzrlib.revisionspec.SPEC_TYPES.append(revspec.RevisionSpecThread)
285+ bzrlib.revisionspec.SPEC_TYPES.append(revspec.RevisionSpecBelow)
286+
287+#register loom formats
288+formats.register_formats()
289
290 def test_suite():
291 import bzrlib.plugins.loom.tests
292
293=== modified file 'branch.py'
294--- branch.py 2009-04-23 09:09:12 +0000
295+++ branch.py 2010-08-05 18:57:44 +0000
296@@ -31,13 +31,14 @@
297 from bzrlib.decorators import needs_read_lock, needs_write_lock
298 import bzrlib.errors
299 import bzrlib.osutils
300-from bzrlib import symbol_versioning
301+from bzrlib import remote, symbol_versioning
302 import bzrlib.trace
303 import bzrlib.ui
304 from bzrlib.revision import is_null, NULL_REVISION
305 import bzrlib.tree
306 import bzrlib.urlutils
307
308+import formats
309 import loom_io
310 import loom_state
311
312@@ -45,6 +46,26 @@
313 EMPTY_REVISION = 'empty:'
314
315
316+def create_thread(loom, thread_name):
317+ """Create a thread in the branch loom called thread."""
318+ require_loom_branch(loom)
319+ loom.lock_write()
320+ try:
321+ loom.new_thread(thread_name, loom.nick)
322+ loom._set_nick(thread_name)
323+ finally:
324+ loom.unlock()
325+
326+
327+class AlreadyLoom(bzrlib.errors.BzrError):
328+
329+ _fmt = """Loom %(loom)s is already a loom."""
330+
331+ def __init__(self, loom):
332+ bzrlib.errors.BzrError.__init__(self)
333+ self.loom = loom
334+
335+
336 def loomify(branch):
337 """Convert branch to a loom.
338
339@@ -53,6 +74,12 @@
340 try:
341 branch.lock_write()
342 try:
343+ require_loom_branch(branch)
344+ except NotALoom:
345+ pass
346+ else:
347+ raise AlreadyLoom(branch)
348+ try:
349 format = {
350 bzrlib.branch.BzrBranchFormat5: BzrBranchLoomFormat1,
351 bzrlib.branch.BzrBranchFormat6: BzrBranchLoomFormat6,
352@@ -65,25 +92,12 @@
353 branch.unlock()
354
355
356-def require_loom_branch(branch):
357- """Return None if branch is already loomified, or raise NotALoom."""
358- if not branch._format.__class__ in LOOM_FORMATS:
359- raise NotALoom(branch)
360-
361-
362-class NotALoom(bzrlib.errors.BzrError):
363-
364- _fmt = ("The branch %(branch)s is not a loom. "
365- "You can use 'bzr loomify' to make it into a loom.")
366-
367- def __init__(self, branch):
368- bzrlib.errors.BzrError.__init__(self)
369- self.branch = branch
370+require_loom_branch = formats.require_loom_branch
371+NotALoom = formats.NotALoom
372
373
374 class LoomThreadError(bzrlib.errors.BzrError):
375-
376- _fmt = """Base class for Loom-Thread errors."""
377+ """Base class for Loom-Thread errors."""
378
379 def __init__(self, branch, thread):
380 bzrlib.errors.BzrError.__init__(self)
381@@ -198,14 +212,14 @@
382 if len(threads) <= current_index:
383 # removed the end
384 # take the new end thread
385- self.nick = threads[-1][0]
386+ self._set_nick(threads[-1][0])
387 new_rev = threads[-1][1]
388 if new_rev == EMPTY_REVISION:
389 new_rev = bzrlib.revision.NULL_REVISION
390 self.generate_revision_history(new_rev)
391 return
392 # non-end thread removed.
393- self.nick = threads[current_index][0]
394+ self._set_nick(threads[current_index][0])
395 new_rev = threads[current_index][1]
396 if new_rev == EMPTY_REVISION:
397 new_rev = bzrlib.revision.NULL_REVISION
398@@ -229,85 +243,25 @@
399 def clone(self, to_bzrdir, revision_id=None, repository_policy=None):
400 """Clone the branch into to_bzrdir.
401
402- This differs from the base clone by cloning the loom and
403- setting the current nick to the top of the loom.
404+ This differs from the base clone by cloning the loom, setting the
405+ current nick to the top of the loom, not honouring any branch format
406+ selection on the target bzrdir, and ensuring that the format of
407+ the created branch is stacking compatible.
408 """
409- result = self._format.initialize(to_bzrdir)
410+ # If the target is a stackable repository, force-upgrade the
411+ # output loom format
412+ if (isinstance(to_bzrdir, bzrdir.BzrDirMeta1) and
413+ to_bzrdir._format.repository_format.supports_external_lookups):
414+ format = BzrBranchLoomFormat7()
415+ else:
416+ format = self._format
417+ result = format.initialize(to_bzrdir)
418 if repository_policy is not None:
419 repository_policy.configure_branch(result)
420- self.copy_content_into(result, revision_id=revision_id)
421+ bzrlib.branch.InterBranch.get(self, result).copy_content_into(
422+ revision_id=revision_id)
423 return result
424
425- @needs_read_lock
426- def copy_content_into(self, destination, revision_id=None):
427- # XXX: hint for bzrlib - break this into two routines, one for
428- # copying the last-rev pointer, one for copying parent etc.
429- destination.lock_write()
430- try:
431- source_nick = self.nick
432- state = self.get_loom_state()
433- parents = state.get_parents()
434- if parents:
435- loom_tip = parents[0]
436- else:
437- loom_tip = None
438- threads = self.get_threads(state.get_basis_revision_id())
439- if revision_id not in (None, NULL_REVISION):
440- if threads:
441- # revision_id should be in the loom, or its an error
442- found_threads = [thread for thread, rev in threads
443- if rev == revision_id]
444- if not found_threads:
445- # the thread we have been asked to set in the remote
446- # side has not been recorded yet, so its data is not
447- # present at this point.
448- raise UnrecordedRevision(self, revision_id)
449-
450- # pull in the warp, which was skipped during the initial pull
451- # because the front end does not know what to pull.
452- # nb: this is mega huge hacky. THINK. RBC 2006062
453- nested = bzrlib.ui.ui_factory.nested_progress_bar()
454- try:
455- if parents:
456- destination.repository.fetch(self.repository,
457- revision_id=parents[0])
458- if threads:
459- for thread, rev_id in reversed(threads):
460- # fetch the loom content for this revision
461- destination.repository.fetch(self.repository,
462- revision_id=rev_id)
463- finally:
464- nested.finished()
465- state = loom_state.LoomState()
466- try:
467- require_loom_branch(destination)
468- if threads:
469- last_rev = threads[-1][1]
470- if last_rev == EMPTY_REVISION:
471- last_rev = bzrlib.revision.NULL_REVISION
472- destination.generate_revision_history(last_rev)
473- state.set_parents([loom_tip])
474- state.set_threads(
475- (thread + ([thread[1]],) for thread in threads)
476- )
477- else:
478- # no threads yet, be a normal branch.
479- self._synchronize_history(destination, revision_id)
480- destination._set_last_loom(state)
481- except NotALoom:
482- self._synchronize_history(destination, revision_id)
483- try:
484- parent = self.get_parent()
485- except bzrlib.errors.InaccessibleParent, e:
486- bzrlib.trace.mutter('parent was not accessible to copy: %s', e)
487- else:
488- if parent:
489- destination.set_parent(parent)
490- if threads:
491- destination.nick = threads[-1][0]
492- finally:
493- destination.unlock()
494-
495 def _get_checkout_format(self):
496 """Checking out a Loom gets a regular branch for now.
497
498@@ -435,22 +389,25 @@
499 result.append((name, rev_id))
500 return result
501
502- @needs_write_lock
503- def pull(self, source, overwrite=False, stop_revision=None,
504- run_hooks=True, possible_transports=None, _override_hook_target=None):
505- """Pull from a branch into this loom.
506-
507- If the remote branch is a non-loom branch, the pull is done against the
508- current warp. If it is a loom branch, then the pull is done against the
509- entire loom and the current thread set to the top thread.
510- """
511- if not isinstance(source, LoomSupport):
512- return super(LoomSupport, self).pull(source,
513- overwrite=overwrite, stop_revision=stop_revision,
514- possible_transports=possible_transports,
515- _override_hook_target=_override_hook_target)
516- return _Puller(source, self).transfer(overwrite, stop_revision,
517- run_hooks, possible_transports, _override_hook_target)
518+ def _loom_get_nick(self):
519+ return self._get_nick(local=True)
520+
521+ def _rename_thread(self, nick):
522+ """Rename the current thread to nick."""
523+ state = self.get_loom_state()
524+ threads = state.get_threads()
525+ if not len(threads):
526+ # No threads at all - probably a default initialised loom in the
527+ # test suite.
528+ return self._set_nick(nick)
529+ current_index = state.thread_index(self.nick)
530+ threads[current_index] = (nick,) + threads[current_index][1:]
531+ state.set_threads(threads)
532+ self._set_last_loom(state)
533+ # Preserve default behavior: set the branch nick
534+ self._set_nick(nick)
535+
536+ nick = property(_loom_get_nick, _rename_thread)
537
538 @needs_read_lock
539 def push(self, target, overwrite=False, stop_revision=None,
540@@ -606,10 +563,20 @@
541
542
543 class _Puller(object):
544+ # XXX: Move into InterLoomBranch.
545
546 def __init__(self, source, target):
547 self.target = target
548 self.source = source
549+ # If _Puller has been created, we need real branch objects.
550+ self.real_target = self.unwrap_branch(target)
551+ self.real_source = self.unwrap_branch(source)
552+
553+ def unwrap_branch(self, branch):
554+ if isinstance(branch, remote.RemoteBranch):
555+ branch._ensure_real()
556+ return branch._real_branch
557+ return branch
558
559 def prepare_result(self, _override_hook_target):
560 result = self.make_result()
561@@ -669,8 +636,10 @@
562 return result
563
564 def transfer(self, overwrite, stop_revision, run_hooks=True,
565- possible_transports=None, _override_hook_target=None):
566+ possible_transports=None, _override_hook_target=None, local=False):
567 """Implementation of push and pull"""
568+ if local:
569+ raise bzrlib.errors.LocalRequiresBoundBranch()
570 # pull the loom, and position our
571 pb = bzrlib.ui.ui_factory.nested_progress_bar()
572 try:
573@@ -678,7 +647,7 @@
574 self.target.lock_write()
575 self.source.lock_read()
576 try:
577- source_state = self.source.get_loom_state()
578+ source_state = self.real_source.get_loom_state()
579 source_parents = source_state.get_parents()
580 if not source_parents:
581 return self.plain_transfer(result, run_hooks,
582@@ -724,7 +693,7 @@
583 # and save the state.
584 self.target._set_last_loom(my_state)
585 # set the branch nick.
586- self.target.nick = threads[-1][0]
587+ self.target._set_nick(threads[-1][0])
588 # and position the branch on the top loom
589 new_rev = threads[-1][1]
590 if new_rev == EMPTY_REVISION:
591@@ -743,7 +712,7 @@
592
593 @staticmethod
594 def make_result():
595- return bzrlib.branch.PushResult()
596+ return bzrlib.branch.BranchPushResult()
597
598 @staticmethod
599 def post_hooks():
600@@ -780,9 +749,11 @@
601 # A mixin is not ideal because it is tricky to test, but it seems to be the
602 # best solution for now.
603
604- def initialize(self, a_bzrdir):
605+ def initialize(self, a_bzrdir, name=None):
606 """Create a branch of this format in a_bzrdir."""
607- super(LoomFormatMixin, self).initialize(a_bzrdir)
608+ if name is not None:
609+ raise bzrlib.errors.NoColocatedBranchSupport(self)
610+ super(LoomFormatMixin, self).initialize(a_bzrdir, name=None)
611 branch_transport = a_bzrdir.get_branch_transport(self)
612 files = []
613 state = loom_state.LoomState()
614@@ -801,15 +772,21 @@
615 control_files.unlock()
616 return self.open(a_bzrdir, _found=True, )
617
618- def open(self, a_bzrdir, _found=False, ignore_fallbacks=False):
619+ def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False):
620 """Return the branch object for a_bzrdir
621
622 _found is a private parameter, do not use it. It is used to indicate
623 if format probing has already be done.
624+
625+ :param name: The 'colocated branches' name for the branch to open.
626+ in future, Loom may use that to return a Thread, but for now
627+ it is unused.
628 """
629 if not _found:
630 format = BranchFormat.find_format(a_bzrdir)
631 assert format.__class__ == self.__class__
632+ if name is not None:
633+ raise bzrlib.errors.NoColocatedBranchSupport(self)
634 transport = a_bzrdir.get_branch_transport(None)
635 control_files = bzrlib.lockable_files.LockableFiles(
636 transport, 'lock', bzrlib.lockdir.LockDir)
637@@ -923,13 +900,136 @@
638 return "bzr loom format 7 (based on bzr branch format 7)\n"
639
640
641-bzrlib.branch.BranchFormat.register_format(BzrBranchLoomFormat1())
642-bzrlib.branch.BranchFormat.register_format(BzrBranchLoomFormat6())
643-bzrlib.branch.BranchFormat.register_format(BzrBranchLoomFormat7())
644-
645-
646-LOOM_FORMATS = [
647- BzrBranchLoomFormat1,
648- BzrBranchLoomFormat6,
649- BzrBranchLoomFormat7,
650-]
651+# Handle the smart server:
652+
653+class InterLoomBranch(bzrlib.branch.GenericInterBranch):
654+
655+ @classmethod
656+ def _get_branch_formats_to_test(klass):
657+ return [
658+ (bzrlib.branch.BranchFormat._default_format,
659+ BzrBranchLoomFormat7()),
660+ (BzrBranchLoomFormat7(),
661+ bzrlib.branch.BranchFormat._default_format),
662+ (BzrBranchLoomFormat7(), BzrBranchLoomFormat7()),
663+ ]
664+
665+ def unwrap_branch(self, branch):
666+ if isinstance(branch, remote.RemoteBranch):
667+ branch._ensure_real()
668+ return branch._real_branch
669+ return branch
670+
671+ @classmethod
672+ def is_compatible(klass, source, target):
673+ # 1st cut: special case and handle all *->Loom and Loom->*
674+ return klass.branch_is_loom(source) or klass.branch_is_loom(target)
675+
676+ def get_loom_state(self, branch):
677+ branch = self.unwrap_branch(branch)
678+ return branch.get_loom_state()
679+
680+ def get_threads(self, branch, revision_id):
681+ branch = self.unwrap_branch(branch)
682+ return branch.get_threads(revision_id)
683+
684+ @classmethod
685+ def branch_is_loom(klass, branch):
686+ format = klass.unwrap_format(branch._format)
687+ return isinstance(format, LoomFormatMixin)
688+
689+ @needs_write_lock
690+ def copy_content_into(self, revision_id=None):
691+ if not self.__class__.branch_is_loom(self.source):
692+ # target is loom, but the generic code path works Just Fine for
693+ # regular to loom copy_content_into.
694+ return super(InterLoomBranch, self).copy_content_into(
695+ revision_id=revision_id)
696+ # XXX: hint for bzrlib - break this into two routines, one for
697+ # copying the last-rev pointer, one for copying parent etc.
698+ source_nick = self.source.nick
699+ state = self.get_loom_state(self.source)
700+ parents = state.get_parents()
701+ if parents:
702+ loom_tip = parents[0]
703+ else:
704+ loom_tip = None
705+ threads = self.get_threads(self.source, state.get_basis_revision_id())
706+ if revision_id not in (None, NULL_REVISION):
707+ if threads:
708+ # revision_id should be in the loom, or its an error
709+ found_threads = [thread for thread, rev in threads
710+ if rev == revision_id]
711+ if not found_threads:
712+ # the thread we have been asked to set in the remote
713+ # side has not been recorded yet, so its data is not
714+ # present at this point.
715+ raise UnrecordedRevision(self.source, revision_id)
716+
717+ # pull in the warp, which was skipped during the initial pull
718+ # because the front end does not know what to pull.
719+ # nb: this is mega huge hacky. THINK. RBC 2006062
720+ nested = bzrlib.ui.ui_factory.nested_progress_bar()
721+ try:
722+ if parents:
723+ self.target.repository.fetch(self.source.repository,
724+ revision_id=parents[0])
725+ if threads:
726+ for thread, rev_id in reversed(threads):
727+ # fetch the loom content for this revision
728+ self.target.repository.fetch(self.source.repository,
729+ revision_id=rev_id)
730+ finally:
731+ nested.finished()
732+ state = loom_state.LoomState()
733+ try:
734+ require_loom_branch(self.target)
735+ if threads:
736+ last_rev = threads[-1][1]
737+ if last_rev == EMPTY_REVISION:
738+ last_rev = bzrlib.revision.NULL_REVISION
739+ self.target.generate_revision_history(last_rev)
740+ state.set_parents([loom_tip])
741+ state.set_threads(
742+ (thread + ([thread[1]],) for thread in threads)
743+ )
744+ else:
745+ # no threads yet, be a normal branch.
746+ self.source._synchronize_history(self.target, revision_id)
747+ target_loom = self.unwrap_branch(self.target)
748+ target_loom._set_last_loom(state)
749+ except NotALoom:
750+ self.source._synchronize_history(self.target, revision_id)
751+ try:
752+ parent = self.source.get_parent()
753+ except bzrlib.errors.InaccessibleParent, e:
754+ bzrlib.trace.mutter('parent was not accessible to copy: %s', e)
755+ else:
756+ if parent:
757+ self.target.set_parent(parent)
758+ if threads:
759+ self.target._set_nick(threads[-1][0])
760+
761+ @needs_write_lock
762+ def pull(self, overwrite=False, stop_revision=None,
763+ run_hooks=True, possible_transports=None, _override_hook_target=None,
764+ local=False):
765+ """Perform a pull, reading from self.source and writing to self.target.
766+
767+ If the source branch is a non-loom branch, the pull is done against the
768+ current warp. If it is a loom branch, then the pull is done against the
769+ entire loom and the current thread set to the top thread.
770+ """
771+ # Special code only needed when both source and targets are looms:
772+ if (self.__class__.branch_is_loom(self.target) and
773+ self.__class__.branch_is_loom(self.source)):
774+ return _Puller(self.source, self.target).transfer(overwrite, stop_revision,
775+ run_hooks, possible_transports, _override_hook_target, local)
776+ return super(InterLoomBranch, self).pull(
777+ overwrite=overwrite, stop_revision=stop_revision,
778+ possible_transports=possible_transports,
779+ _override_hook_target=_override_hook_target, local=local,
780+ run_hooks=run_hooks)
781+
782+
783+bzrlib.branch.InterBranch.register_optimiser(InterLoomBranch)
784
785=== modified file 'commands.py'
786--- commands.py 2009-02-13 02:30:24 +0000
787+++ commands.py 2010-08-05 18:57:44 +0000
788@@ -17,18 +17,23 @@
789
790 """Loom commands."""
791
792-from bzrlib import workingtree
793+from bzrlib import bzrdir, directory_service, workingtree
794 import bzrlib.commands
795 import bzrlib.branch
796 from bzrlib import errors
797+from bzrlib.lazy_import import lazy_import
798 import bzrlib.merge
799 from bzrlib.option import Option
800 import bzrlib.revision
801 import bzrlib.trace
802 import bzrlib.transport
803
804+import formats
805+
806+lazy_import(globals(), """
807 import branch
808 from tree import LoomTreeDecorator
809+""")
810
811
812 class cmd_loomify(bzrlib.commands.Command):
813@@ -67,7 +72,7 @@
814
815
816 class cmd_combine_thread(bzrlib.commands.Command):
817- """Combine the current thread with the thread below it.
818+ __doc__ = """Combine the current thread with the thread below it.
819
820 This will currently refuse to operate on the last thread, but in the future
821 will just turn the loom into a normal branch again.
822@@ -79,23 +84,53 @@
823 * Change threads to the thread below.
824 """
825
826- def run(self):
827+ takes_options = [
828+ Option('force', help='Combine even if work in the thread is not '
829+ 'integrated up or down the loom.'),
830+ ]
831+
832+ def run(self, force=False):
833 (tree, path) = workingtree.WorkingTree.open_containing('.')
834 branch.require_loom_branch(tree.branch)
835- tree.lock_write()
836- try:
837- current_thread = tree.branch.nick
838- state = tree.branch.get_loom_state()
839+ self.add_cleanup(tree.lock_write().unlock)
840+ current_thread = tree.branch.nick
841+ state = tree.branch.get_loom_state()
842+ if not force:
843+ # Check for unmerged work.
844+ # XXX: Layering issue whom should be caring for the check, not the
845+ # command thats for sure.
846 threads = state.get_threads()
847- new_thread = state.get_new_thread_after_deleting(current_thread)
848- if new_thread is None:
849- raise branch.CannotCombineOnLastThread
850- bzrlib.trace.note("Combining thread '%s' into '%s'",
851- current_thread, new_thread)
852- LoomTreeDecorator(tree).down_thread(new_thread)
853- tree.branch.remove_thread(current_thread)
854- finally:
855- tree.unlock()
856+ current_index = state.thread_index(current_thread)
857+ rev_below = None
858+ rev_current = threads[current_index][1]
859+ rev_above = None
860+ if current_index:
861+ # There is a thread below
862+ rev_below = threads[current_index - 1][1]
863+ if current_index < len(threads) - 1:
864+ rev_above = threads[current_index + 1][1]
865+ graph = tree.branch.repository.get_graph()
866+ candidates = [rev for rev in
867+ (rev_below, rev_current, rev_above) if rev]
868+ heads = graph.heads(candidates)
869+ # If current is not a head, its trivially merged, or
870+ # if current is == rev_below, its also merged, or
871+ # if there is only one thread its merged (well its not unmerged).
872+ if (rev_current == rev_below or rev_current not in heads or
873+ (rev_below is None and rev_above is None)):
874+ merged = True
875+ else:
876+ merged = False
877+ if not merged:
878+ raise errors.BzrCommandError("Thread '%s' has unmerged work"
879+ ". Use --force to combine anyway." % current_thread)
880+ new_thread = state.get_new_thread_after_deleting(current_thread)
881+ if new_thread is None:
882+ raise branch.CannotCombineOnLastThread
883+ bzrlib.trace.note("Combining thread '%s' into '%s'",
884+ current_thread, new_thread)
885+ LoomTreeDecorator(tree).down_thread(new_thread)
886+ tree.branch.remove_thread(current_thread)
887
888
889 class cmd_create_thread(bzrlib.commands.Command):
890@@ -106,19 +141,15 @@
891
892 The thread-name must be a valid branch 'nickname', and must not be the name
893 of an existing thread in your loom.
894+
895+ The new thread is created immediately after the current thread.
896 """
897
898 takes_args = ['thread']
899
900 def run(self, thread):
901 (loom, path) = bzrlib.branch.Branch.open_containing('.')
902- branch.require_loom_branch(loom)
903- loom.lock_write()
904- try:
905- loom.new_thread(thread, loom.nick)
906- loom.nick = thread
907- finally:
908- loom.unlock()
909+ branch.create_thread(loom, thread)
910
911
912 class cmd_show_loom(bzrlib.commands.Command):
913@@ -160,7 +191,7 @@
914 else:
915 path = file_list[0]
916 (loom, _) = bzrlib.branch.Branch.open_containing(path)
917- branch.require_loom_branch(loom)
918+ formats.require_loom_branch(loom)
919 loom.lock_read()
920 try:
921 print 'Current thread: %s' % loom.nick
922@@ -172,7 +203,7 @@
923 self._original_command().run_argv_aliases(argv, alias_argv)
924 try:
925 super(cmd_status, self).run_argv_aliases(list(argv), alias_argv)
926- except branch.NotALoom:
927+ except formats.NotALoom:
928 pass
929
930
931@@ -208,15 +239,43 @@
932 return thread[0]
933 return to_location
934
935- def run(self, to_location, force=False):
936- (tree, path) = workingtree.WorkingTree.open_containing('.')
937- tree = LoomTreeDecorator(tree)
938+ def run(self, to_location=None, force=False, create_branch=False,
939+ revision=None, directory=None):
940+ # The top of this is cribbed from bzr; because bzr isn't factored out
941+ # enough.
942+ if directory is None:
943+ directory = u'.'
944+ control_dir, path = bzrdir.BzrDir.open_containing(directory)
945+ if to_location is None:
946+ if revision is None:
947+ raise errors.BzrCommandError(
948+ 'You must supply either a revision or a location')
949+ to_location = '.'
950 try:
951- thread_name = self._get_thread_name(tree.branch, to_location)
952- return tree.down_thread(thread_name)
953- except (AttributeError, branch.NoSuchThread):
954- # When there is no thread its probably an external branch
955- # that we have been given.
956+ from_branch = control_dir.open_branch()
957+ except errors.NotBranchError:
958+ from_branch = None
959+ if create_branch:
960+ if from_branch is None:
961+ raise errors.BzrCommandError(
962+ 'cannot create branch without source branch')
963+ to_location = directory_service.directories.dereference(
964+ to_location)
965+ if from_branch is not None:
966+ # Note: reopens.
967+ (tree, path) = workingtree.WorkingTree.open_containing(directory)
968+ tree = LoomTreeDecorator(tree)
969+ try:
970+ if create_branch:
971+ return branch.create_thread(tree.branch, to_location)
972+ thread_name = self._get_thread_name(tree.branch, to_location)
973+ return tree.down_thread(thread_name)
974+ except (AttributeError, branch.NoSuchThread, branch.NotALoom):
975+ # When there is no thread its probably an external branch
976+ # that we have been given.
977+ raise errors.MustUseDecorated
978+ else:
979+ # switching to a relocated branch
980 raise errors.MustUseDecorated
981
982 def run_argv_aliases(self, argv, alias_argv=None):
983@@ -307,26 +366,34 @@
984
985
986 class cmd_up_thread(bzrlib.commands.Command):
987- """Move the branch up a thread in the loom.
988-
989+ """Move the branch up to the top thread in the loom.
990+
991 This merges the changes done in this thread but not incorporated into
992 the next thread up into the next thread up and switches your tree to be
993- that thread.
994+ that thread. Unless there are conflicts, or --manual is specified, it
995+ will then commit and repeat the process.
996 """
997
998+ takes_args = ['thread?']
999+
1000 takes_options = ['merge-type', Option('auto',
1001- help='Automatically commit and merge repeatedly.')]
1002+ help='Deprecated - now the default.'),
1003+ Option('manual', help='Perform commit manually.'),
1004+ ]
1005
1006 _see_also = ['down-thread', 'switch']
1007
1008- def run(self, merge_type=None, auto=False):
1009+ def run(self, merge_type=None, manual=False, thread=None, auto=None):
1010 (tree, path) = workingtree.WorkingTree.open_containing('.')
1011 branch.require_loom_branch(tree.branch)
1012 tree = LoomTreeDecorator(tree)
1013- if not auto:
1014+ if manual:
1015+ if thread is not None:
1016+ raise errors.BzrCommandError('Specifying a thread does not'
1017+ ' work with --manual.')
1018 return tree.up_thread(merge_type)
1019 else:
1020- return tree.up_many(merge_type)
1021+ return tree.up_many(merge_type, thread)
1022
1023
1024 class cmd_export_loom(bzrlib.commands.Command):
1025
1026=== added file 'formats.py'
1027--- formats.py 1970-01-01 00:00:00 +0000
1028+++ formats.py 2010-08-05 18:57:44 +0000
1029@@ -0,0 +1,74 @@
1030+# Loom, a plugin for bzr to assist in developing focused patches.
1031+# Copyright (C) 2010 Canonical Limited.
1032+#
1033+# This program is free software; you can redistribute it and/or modify
1034+# it under the terms of the GNU General Public License version 2 as published
1035+# by the Free Software Foundation.
1036+#
1037+# This program is distributed in the hope that it will be useful,
1038+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1039+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1040+# GNU General Public License for more details.
1041+#
1042+# You should have received a copy of the GNU General Public License
1043+# along with this program; if not, write to the Free Software
1044+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
1045+#
1046+
1047+"""Format information about formats for Loom.
1048+
1049+This is split out from the implementation of the formats to permit lazy
1050+loading without requiring the implementation code to be cryptic.
1051+"""
1052+
1053+__all__ = [
1054+ 'NotALoom',
1055+ 'register_formats',
1056+ 'require_loom_branch',
1057+ ]
1058+
1059+from bzrlib.lazy_import import lazy_import
1060+import bzrlib.errors
1061+
1062+lazy_import(globals(), """
1063+from bzrlib import branch as _mod_branch
1064+""")
1065+
1066+
1067+_LOOM_FORMATS = {
1068+ "Bazaar-NG Loom branch format 1\n": "BzrBranchLoomFormat1",
1069+ "Bazaar-NG Loom branch format 6\n": "BzrBranchLoomFormat6",
1070+ "Bazaar-NG Loom branch format 7\n": "BzrBranchLoomFormat7",
1071+ }
1072+
1073+def register_formats():
1074+ if getattr(_mod_branch, 'MetaDirBranchFormatFactory', None):
1075+ branch_formats = [_mod_branch.MetaDirBranchFormatFactory(format_string,
1076+ "bzrlib.plugins.loom.branch", format_class) for
1077+ (format_string, format_class) in _LOOM_FORMATS.iteritems()]
1078+ else:
1079+ # Compat for folk not running bleeding edge. Like me as I commit this.
1080+ import branch
1081+ branch_formats = [
1082+ branch.BzrBranchLoomFormat1(),
1083+ branch.BzrBranchLoomFormat6(),
1084+ branch.BzrBranchLoomFormat7(),
1085+ ]
1086+ map(_mod_branch.BranchFormat.register_format, branch_formats)
1087+
1088+
1089+def require_loom_branch(branch):
1090+ """Return None if branch is already loomified, or raise NotALoom."""
1091+ if branch._format.network_name() not in _LOOM_FORMATS:
1092+ raise NotALoom(branch)
1093+
1094+
1095+# TODO: define errors without importing all errors.
1096+class NotALoom(bzrlib.errors.BzrError):
1097+
1098+ _fmt = ("The branch %(branch)s is not a loom. "
1099+ "You can use 'bzr loomify' to make it into a loom.")
1100+
1101+ def __init__(self, branch):
1102+ bzrlib.errors.BzrError.__init__(self)
1103+ self.branch = branch
1104
1105=== modified file 'revspec.py'
1106--- revspec.py 2009-02-13 02:30:24 +0000
1107+++ revspec.py 2010-08-05 18:57:44 +0000
1108@@ -18,52 +18,87 @@
1109 """Loom specific revision-specifiers."""
1110
1111
1112-from bzrlib import revisionspec
1113 from bzrlib.plugins.loom.branch import NoLowerThread
1114+from bzrlib.plugins.loom.formats import require_loom_branch
1115 from bzrlib.revisionspec import RevisionSpec, RevisionInfo
1116
1117
1118-class RevisionSpecThread(RevisionSpec):
1119- """The thread: revision specifier."""
1120-
1121- help_txt = """Selects the tip of a revision from a loom.
1122-
1123- Selects the tip of a thread in a loom.
1124-
1125- Examples::
1126-
1127- thread: -> return the tip of the next lower thread.
1128- thread:foo -> return the tip of the thread named 'foo'
1129-
1130- see also: loom
1131- """
1132-
1133- prefix = 'thread:'
1134+class LoomRevisionSpec(RevisionSpec):
1135+ """A revision spec that needs a loom."""
1136
1137 def _match_on(self, branch, revs):
1138 return RevisionInfo(branch, None, self._as_revision_id(branch))
1139
1140 def _as_revision_id(self, branch):
1141- # '' -> next lower
1142- # foo -> named
1143+ require_loom_branch(branch)
1144 branch.lock_read()
1145 try:
1146 state = branch.get_loom_state()
1147 threads = state.get_threads()
1148- if len(self.spec):
1149- index = state.thread_index(self.spec)
1150- else:
1151- current_thread = branch.nick
1152- index = state.thread_index(current_thread) - 1
1153- if index < 0:
1154- raise NoLowerThread()
1155- return threads[index][1]
1156+ return self._as_thread_revision_id(branch, state, threads)
1157 finally:
1158 branch.unlock()
1159
1160
1161-revspec_registry = getattr(revisionspec, 'revspec_registry', None)
1162-if revspec_registry is not None:
1163- revspec_registry.register('thread:', RevisionSpecThread)
1164-else:
1165- revisionspec.SPEC_TYPES.append(RevisionSpecThread)
1166+class RevisionSpecBelow(LoomRevisionSpec):
1167+ """The below: revision specifier."""
1168+
1169+ help_txt = """Selects the tip of the thread below a thread from a loom.
1170+
1171+ Selects the tip of the thread below a thread in a loom.
1172+
1173+ Examples::
1174+
1175+ below: -> return the tip of the next lower thread.
1176+ below:foo -> return the tip of the thread under the one
1177+ named 'foo'
1178+
1179+ see also: loom, the thread: revision specifier
1180+ """
1181+
1182+ prefix = 'below:'
1183+
1184+ def _as_thread_revision_id(self, branch, state, threads):
1185+ # '' -> next lower
1186+ # foo -> thread under foo
1187+ if len(self.spec):
1188+ index = state.thread_index(self.spec)
1189+ else:
1190+ current_thread = branch.nick
1191+ index = state.thread_index(current_thread)
1192+ if index < 1:
1193+ raise NoLowerThread()
1194+ return threads[index - 1][1]
1195+
1196+
1197+class RevisionSpecThread(LoomRevisionSpec):
1198+ """The thread: revision specifier."""
1199+
1200+ help_txt = """Selects the tip of a thread from a loom.
1201+
1202+ Selects the tip of a thread in a loom.
1203+
1204+ Examples::
1205+
1206+ thread: -> return the tip of the next lower thread.
1207+ thread:foo -> return the tip of the thread named 'foo'
1208+
1209+ see also: loom, the below: revision specifier
1210+ """
1211+
1212+ prefix = 'thread:'
1213+
1214+ def _as_thread_revision_id(self, branch, state, threads):
1215+ # '' -> next lower
1216+ # foo -> named
1217+ if len(self.spec):
1218+ index = state.thread_index(self.spec)
1219+ else:
1220+ current_thread = branch.nick
1221+ index = state.thread_index(current_thread) - 1
1222+ if index < 0:
1223+ raise NoLowerThread()
1224+ return threads[index][1]
1225+
1226+
1227+
1228
1229=== modified file 'setup.py'
1230--- setup.py 2008-05-30 02:39:07 +0000
1231+++ setup.py 2010-08-05 18:57:44 +0000
1232@@ -20,13 +20,11 @@
1233 "Bazaar-NG Loom branch format 6\n":"Loom branch format 6",
1234 }
1235
1236-bzr_plugin_version = (1, 4, 0, 'dev', 0)
1237-bzr_minimum_version = (1, 0, 0)
1238-bzr_maximum_version = None
1239+from version import *
1240
1241-if __name__ == 'main':
1242+if __name__ == '__main__':
1243 setup(name="Loom",
1244- version="1.4.0dev0",
1245+ version="2.2.1dev0",
1246 description="Loom plugin for bzr.",
1247 author="Canonical Ltd",
1248 author_email="bazaar@lists.canonical.com",
1249
1250=== modified file 'tests/__init__.py'
1251--- tests/__init__.py 2008-09-12 01:55:17 +0000
1252+++ tests/__init__.py 2010-08-05 18:57:44 +0000
1253@@ -22,6 +22,7 @@
1254 import bzrlib.plugins.loom.branch
1255 from bzrlib.tests import TestCaseWithTransport
1256 from bzrlib.tests.TestUtil import TestLoader, TestSuite
1257+from bzrlib.workingtree import WorkingTree
1258
1259
1260 def test_suite():
1261@@ -41,7 +42,9 @@
1262
1263 def get_tree_with_loom(self, path="."):
1264 """Get a tree with no commits in loom format."""
1265- tree = self.make_branch_and_tree(path)
1266+ # May open on Remote - we want the vfs backed version for loom tests.
1267+ self.make_branch_and_tree(path)
1268+ tree = WorkingTree.open(path)
1269 bzrlib.plugins.loom.branch.loomify(tree.branch)
1270 return tree.bzrdir.open_workingtree()
1271
1272
1273=== modified file 'tests/blackbox.py'
1274--- tests/blackbox.py 2008-11-24 16:38:16 +0000
1275+++ tests/blackbox.py 2010-08-05 18:57:44 +0000
1276@@ -35,7 +35,7 @@
1277 def _add_patch(self, tree, name):
1278 """Add a patch to a new thread, returning the revid of te commit."""
1279 tree.branch.new_thread(name)
1280- tree.branch.nick = name
1281+ tree.branch._set_nick(name)
1282 self.build_tree([name])
1283 tree.add(name)
1284 return tree.commit(name)
1285@@ -147,11 +147,11 @@
1286 self.assertShowLoom(['vendor'], 'vendor')
1287 tree.branch.new_thread('debian')
1288 self.assertShowLoom(['vendor', 'debian'], 'vendor')
1289- tree.branch.nick = 'debian'
1290+ tree.branch._set_nick('debian')
1291 self.assertShowLoom(['vendor', 'debian'], 'debian')
1292 tree.branch.new_thread('patch A', 'vendor')
1293 self.assertShowLoom(['vendor', 'patch A', 'debian'], 'debian')
1294- tree.branch.nick = 'patch A'
1295+ tree.branch._set_nick('patch A')
1296 self.assertShowLoom(['vendor', 'patch A', 'debian'], 'patch A')
1297
1298 def test_show_loom_with_location(self):
1299@@ -266,6 +266,17 @@
1300 "All changes applied successfully.\nMoved to thread 'thread2'.\n",
1301 err)
1302
1303+ def test_switch_dash_b(self):
1304+ # 'bzr switch -b new-thread' makes and switches to a new thread.
1305+ tree = self.get_vendor_loom()
1306+ self._add_patch(tree, 'thread2')
1307+ LoomTreeDecorator(tree).down_thread('vendor')
1308+ self.assertEqual(tree.branch.nick, 'vendor')
1309+ out, err = self.run_bzr(['switch', '-b', 'thread1'], retcode=0)
1310+ self.assertEqual(tree.branch.nick, 'thread1')
1311+ self.assertEqual('', out)
1312+ self.assertEqual('', err)
1313+
1314
1315 class TestRecord(TestsWithLooms):
1316
1317@@ -281,7 +292,7 @@
1318 """Adding a new thread is enough to allow recording."""
1319 tree = self.get_vendor_loom()
1320 tree.branch.new_thread('feature')
1321- tree.branch.nick = 'feature'
1322+ tree.branch._set_nick('feature')
1323 out, err = self.run_bzr(['record', 'add feature branch.'])
1324 self.assertEqual('Loom recorded.\n', out)
1325 self.assertEqual('', err)
1326@@ -303,7 +314,7 @@
1327 """moving down when the revision is unchanged should work."""
1328 tree = self.get_vendor_loom()
1329 tree.branch.new_thread('patch')
1330- tree.branch.nick = 'patch'
1331+ tree.branch._set_nick('patch')
1332 rev = tree.last_revision()
1333 out, err = self.run_bzr(['down-thread'])
1334 self.assertEqual('', out)
1335@@ -314,7 +325,7 @@
1336 def test_down_thread_removes_changes_between_threads(self):
1337 tree = self.get_vendor_loom()
1338 tree.branch.new_thread('patch')
1339- tree.branch.nick = 'patch'
1340+ tree.branch._set_nick('patch')
1341 rev = tree.last_revision()
1342 self.build_tree(['afile'])
1343 tree.add('afile')
1344@@ -336,7 +347,7 @@
1345 """Do a down thread when the lower patch is not in the r-h of the old."""
1346 tree = self.get_vendor_loom()
1347 tree.branch.new_thread('patch')
1348- tree.branch.nick = 'vendor'
1349+ tree.branch._set_nick('vendor')
1350 # do a null change in vendor - a new release.
1351 vendor_release = tree.commit('new vendor release.', allow_pointless=True)
1352 # pop up, then down
1353@@ -389,7 +400,7 @@
1354 """Trying to down-thread with changes causes an error."""
1355 tree = self.get_vendor_loom()
1356 tree.branch.new_thread('upper-thread')
1357- tree.branch.nick = 'upper-thread'
1358+ tree.branch._set_nick('upper-thread')
1359 self.build_tree(['new-file'])
1360 tree.add('new-file')
1361 out, err = self.run_bzr('down-thread', retcode=3)
1362@@ -405,33 +416,35 @@
1363 self.assertEqual('', out)
1364 self.assertEqual(
1365 'bzr: ERROR: Cannot move up from the highest thread.\n', err)
1366-
1367+
1368 def test_up_thread_same_revision(self):
1369 """moving up when the revision is unchanged should work."""
1370 tree = self.get_vendor_loom()
1371 tree.branch.new_thread('patch')
1372- tree.branch.nick = 'vendor'
1373+ tree.branch._set_nick('vendor')
1374 rev = tree.last_revision()
1375 out, err = self.run_bzr(['up-thread'])
1376 self.assertEqual('', out)
1377 self.assertEqual('', err)
1378 self.assertEqual('patch', tree.branch.nick)
1379 self.assertEqual(rev, tree.last_revision())
1380-
1381- def test_up_thread_preserves_changes(self):
1382+
1383+ def test_up_thread_manual_preserves_changes(self):
1384 tree = self.get_vendor_loom()
1385 tree.branch.new_thread('patch')
1386- tree.branch.nick = 'vendor'
1387+ tree.branch._set_nick('vendor')
1388 patch_rev = tree.last_revision()
1389 # add a change in vendor - a new release.
1390 self.build_tree(['afile'])
1391 tree.add('afile')
1392 vendor_release = tree.commit('new vendor release adds a file.')
1393- out, err = self.run_bzr(['up-thread'])
1394+ out, err = self.run_bzr(['up-thread', '--manual'])
1395 self.assertEqual('', out)
1396 self.assertEqual(
1397 "All changes applied successfully.\n"
1398- "Moved to thread 'patch'.\n", err)
1399+ "Moved to thread 'patch'.\n"
1400+ 'This thread is now empty, you may wish to run "bzr '
1401+ 'combine-thread" to remove it.\n', err)
1402 self.assertEqual('patch', tree.branch.nick)
1403 # the tree needs to be updated.
1404 self.assertEqual(patch_rev, tree.last_revision())
1405@@ -442,11 +455,18 @@
1406 self.run_bzr(['diff'], retcode=1)
1407 self.assertEqual([patch_rev, vendor_release], tree.get_parent_ids())
1408
1409+ def test_up_thread_manual_rejects_specified_thread(self):
1410+ tree = self.get_vendor_loom()
1411+ tree.branch.new_thread('patch')
1412+ out, err = self.run_bzr('up-thread --manual patch', retcode=3)
1413+ self.assertContainsRe(err, 'Specifying a thread does not work with'
1414+ ' --manual.')
1415+
1416 def test_up_thread_gets_conflicts(self):
1417 """Do a change in both the baseline and the next patch up."""
1418 tree = self.get_vendor_loom()
1419 tree.branch.new_thread('patch')
1420- tree.branch.nick = 'patch'
1421+ tree.branch._set_nick('patch')
1422 # add a change in patch - a new release.
1423 self.build_tree(['afile'])
1424 tree.add('afile')
1425@@ -483,14 +503,72 @@
1426 self.run_bzr(['down-thread'])
1427 self.run_bzr(['up-thread', '--lca'])
1428
1429- def test_up_thread_auto(self):
1430+ def test_up_thread_no_manual(self):
1431 tree = self.get_vendor_loom()
1432 tree.branch.new_thread('middle')
1433 tree.branch.new_thread('top')
1434- self.run_bzr('up-thread --auto')
1435+ self.run_bzr('up-thread')
1436 branch = _mod_branch.Branch.open('.')
1437 self.assertEqual('top', branch.nick)
1438
1439+ def test_up_with_clean_merge_leaving_thread_empty(self):
1440+ """This tests what happens when a thread becomes empty.
1441+
1442+ A thread becomes empty when all its changes are included in
1443+ a lower thread, and so its diff to the thread below contains
1444+ nothing.
1445+
1446+ The user should be warned when this happens.
1447+ """
1448+ tree = self.get_vendor_loom()
1449+ self.build_tree(['afile'])
1450+ tree.add('afile')
1451+ patch_rev = tree.commit('add afile in base')
1452+ tree.branch.new_thread('patch')
1453+ tree.branch._set_nick('patch')
1454+ # make a change to afile in patch.
1455+ f = open('afile', 'wb')
1456+ try:
1457+ f.write('new contents of afile\n')
1458+ finally:
1459+ f.close()
1460+ patch_rev = tree.commit('make a change to afile')
1461+ # make the same change in vendor.
1462+ self.run_bzr(['down-thread'])
1463+ f = open('afile', 'wb')
1464+ try:
1465+ f.write('new contents of afile\n')
1466+ finally:
1467+ f.close()
1468+ vendor_release = tree.commit('make the same change to afile')
1469+ # check that the trees no longer differ after the up merge,
1470+ # and that we are
1471+ out, err = self.run_bzr(['up-thread', '--manual'])
1472+ self.assertEqual('', out)
1473+ self.assertStartsWith(err,
1474+ "All changes applied successfully.\n"
1475+ "Moved to thread 'patch'.\n"
1476+ 'This thread is now empty, you may wish to run "bzr '
1477+ 'combine-thread" to remove it.\n')
1478+ self.assertEqual('patch', tree.branch.nick)
1479+ # the tree needs to be updated.
1480+ self.assertEqual(patch_rev, tree.last_revision())
1481+ # the branch needs to be updated.
1482+ self.assertEqual(patch_rev, tree.branch.last_revision())
1483+ self.assertTrue(tree.has_filename('afile'))
1484+ # diff should return 0 now as we have no uncommitted changes.
1485+ self.run_bzr(['diff'])
1486+ self.assertEqual([patch_rev, vendor_release], tree.get_parent_ids())
1487+
1488+ def test_up_thread_accepts_thread(self):
1489+ tree = self.get_vendor_loom()
1490+ tree.branch.new_thread('lower-middle')
1491+ tree.branch.new_thread('upper-middle')
1492+ tree.branch.new_thread('top')
1493+ self.run_bzr('up-thread upper-middle')
1494+ branch = _mod_branch.Branch.open('.')
1495+ self.assertEqual('upper-middle', branch.nick)
1496+
1497
1498 class TestPush(TestsWithLooms):
1499
1500@@ -592,7 +670,7 @@
1501 # not in, so we can revert that by name,
1502 tree = self.get_vendor_loom()
1503 tree.branch.new_thread('after-vendor')
1504- tree.branch.nick = 'after-vendor'
1505+ tree.branch._set_nick('after-vendor')
1506 tree.commit('after-vendor commit', allow_pointless=True)
1507 tree.branch.record_loom('save loom with vendor and after-vendor')
1508 old_threads = tree.branch.get_loom_state().get_threads()
1509@@ -632,6 +710,56 @@
1510 loom_tree.down_thread()
1511 return tree, loom_tree
1512
1513+ def get_loom_with_unique_thread(self):
1514+ """Return a loom with a unique thread.
1515+
1516+ That is:
1517+ vendor:[]
1518+ unique-thread:[vendor]
1519+ above-vendor:[vendor]
1520+
1521+ - unique-thread has work not in vendor and not in above-vendor.
1522+
1523+ The returned loom is on the vendor thread.
1524+ """
1525+ tree, _ = self.get_two_thread_loom()
1526+ tree.branch.new_thread('unique-thread', 'vendor')
1527+ loom_tree = LoomTreeDecorator(tree)
1528+ loom_tree.up_thread()
1529+ self.build_tree(['file-b'])
1530+ tree.add('file-b')
1531+ tree.commit('a unique change', rev_id='uniquely-yrs-1')
1532+ loom_tree.down_thread()
1533+ return tree, loom_tree
1534+
1535+ def test_combine_unmerged_thread_force(self):
1536+ """Combining a thread with unique work works with --force."""
1537+ tree, loom_tree = self.get_loom_with_unique_thread()
1538+ vendor_revid = tree.last_revision()
1539+ loom_tree.up_thread()
1540+ out, err = self.run_bzr(['combine-thread', '--force'])
1541+ self.assertEqual('', out)
1542+ self.assertEqual(
1543+ "Combining thread 'unique-thread' into 'vendor'\n"
1544+ 'All changes applied successfully.\n'
1545+ "Moved to thread 'vendor'.\n",
1546+ err)
1547+ self.assertEqual(vendor_revid, tree.last_revision())
1548+ self.assertEqual('vendor', tree.branch.nick)
1549+
1550+ def test_combine_unmerged_thread_errors(self):
1551+ """Combining a thread with unique work errors without --force."""
1552+ tree, loom_tree = self.get_loom_with_unique_thread()
1553+ loom_tree.up_thread()
1554+ unique_revid = tree.last_revision()
1555+ out, err = self.run_bzr(['combine-thread'], retcode=3)
1556+ self.assertEqual('', out)
1557+ self.assertEqual("bzr: ERROR: "
1558+"Thread 'unique-thread' has unmerged work. Use --force to combine anyway.\n",
1559+ err)
1560+ self.assertEqual(unique_revid, tree.last_revision())
1561+ self.assertEqual('unique-thread', tree.branch.nick)
1562+
1563 def test_combine_last_two_threads(self):
1564 """Doing a combine on two threads gives you just the bottom one."""
1565 tree, loom_tree = self.get_two_thread_loom()
1566
1567=== modified file 'tests/test_branch.py'
1568--- tests/test_branch.py 2008-11-24 16:38:16 +0000
1569+++ tests/test_branch.py 2010-08-05 18:57:44 +0000
1570@@ -23,6 +23,7 @@
1571 from bzrlib.branch import Branch
1572 import bzrlib.errors as errors
1573 from bzrlib.plugins.loom.branch import (
1574+ AlreadyLoom,
1575 EMPTY_REVISION,
1576 loomify,
1577 require_loom_branch,
1578@@ -33,7 +34,10 @@
1579 from bzrlib.plugins.loom.tree import LoomTreeDecorator
1580 import bzrlib.revision
1581 from bzrlib.revision import NULL_REVISION
1582-from bzrlib.tests import TestCaseWithTransport
1583+from bzrlib.tests import (
1584+ TestCaseWithTransport,
1585+ test_server,
1586+ )
1587 from bzrlib.transport import get_transport
1588 from bzrlib.workingtree import WorkingTree
1589
1590@@ -48,11 +52,18 @@
1591 self.assertFileEqual('Loom current 1\n\n', '.bzr/branch/last-loom')
1592
1593
1594+
1595+class StubFormat(object):
1596+
1597+ def network_name(self):
1598+ return "Nothing to see."
1599+
1600+
1601 class LockableStub(object):
1602
1603 def __init__(self):
1604 self._calls = []
1605- self._format = "Nothing to see."
1606+ self._format = StubFormat()
1607
1608 def lock_write(self):
1609 self._calls.append(("write",))
1610@@ -67,8 +78,7 @@
1611 branch = self.make_branch('.')
1612 self.assertRaises(NotALoom, require_loom_branch, branch)
1613
1614-
1615- def works_on_loom(self, format):
1616+ def works_on_format(self, format):
1617 branch = self.make_branch('.', format)
1618 loomify(branch)
1619 # reopen it
1620@@ -76,13 +86,19 @@
1621 self.assertEqual(None, require_loom_branch(branch))
1622
1623 def test_works_on_loom1(self):
1624- self.works_on_loom('knit')
1625+ self.works_on_format('knit')
1626
1627 def test_works_on_loom6(self):
1628- self.works_on_loom('pack-0.92')
1629+ self.works_on_format('pack-0.92')
1630
1631 def test_works_on_loom7(self):
1632- self.works_on_loom('1.6')
1633+ self.works_on_format('1.6')
1634+
1635+ def test_no_harm_to_looms(self):
1636+ branch = self.make_branch('.')
1637+ loomify(branch)
1638+ branch = branch.bzrdir.open_branch()
1639+ self.assertRaises(AlreadyLoom, loomify, branch)
1640
1641
1642 class TestLoomify(TestCaseWithTransport):
1643@@ -132,6 +148,7 @@
1644 bzrlib.plugins.loom.branch.LoomBranch7,
1645 bzrlib.plugins.loom.branch.BzrBranchLoomFormat7)
1646
1647+
1648 class TestLoom(TestCaseWithLoom):
1649
1650 def make_loom(self, path):
1651@@ -184,9 +201,9 @@
1652 tree.branch.new_thread('baseline')
1653 tree.branch.new_thread('middlepoint')
1654 tree.branch.new_thread('endpoint')
1655- tree.branch.nick = 'middlepoint'
1656+ tree.branch._set_nick('middlepoint')
1657 rev_id2 = tree.commit('middle', allow_pointless=True)
1658- tree.branch.nick = 'endpoint'
1659+ tree.branch._set_nick('endpoint')
1660 rev_id3 = tree.commit('end', allow_pointless=True)
1661 tree.branch.new_thread('afterbase', 'baseline')
1662 tree.branch.new_thread('aftermiddle', 'middlepoint')
1663@@ -209,7 +226,7 @@
1664 tree = self.get_tree_with_one_commit()
1665 tree.branch.new_thread('baseline')
1666 tree.branch.new_thread('tail')
1667- tree.branch.nick = 'baseline'
1668+ tree.branch._set_nick('baseline')
1669 first_rev = tree.last_revision()
1670 # lock the tree to prevent unlock triggering implicit record
1671 tree.lock_write()
1672@@ -230,7 +247,7 @@
1673
1674 def test_clone_empty_loom(self):
1675 source_tree = self.get_tree_with_loom('source')
1676- source_tree.branch.nick = 'source'
1677+ source_tree.branch._set_nick('source')
1678 target_tree = source_tree.bzrdir.clone('target').open_workingtree()
1679 self.assertLoomSproutedOk(source_tree, target_tree)
1680
1681@@ -244,7 +261,7 @@
1682 source_tree = self.get_tree_with_one_commit('source')
1683 source_tree.branch.new_thread('bottom')
1684 source_tree.branch.new_thread('top')
1685- source_tree.branch.nick = 'top'
1686+ source_tree.branch._set_nick('top')
1687 source_tree.commit('phwoar', allow_pointless=True)
1688 source_tree.branch.record_loom('commit to loom')
1689 target_tree = source_tree.bzrdir.clone('target').open_workingtree()
1690@@ -252,23 +269,33 @@
1691
1692 def test_clone_nonempty_loom_bottom(self):
1693 """Cloning loom should reset the current loom pointer."""
1694+ self.make_and_clone_simple_loom()
1695+
1696+ def make_and_clone_simple_loom(self):
1697 source_tree = self.get_tree_with_one_commit('source')
1698 source_tree.branch.new_thread('bottom')
1699 source_tree.branch.new_thread('top')
1700- source_tree.branch.nick = 'top'
1701+ source_tree.branch._set_nick('top')
1702 source_tree.commit('phwoar', allow_pointless=True)
1703 source_tree.branch.record_loom('commit to loom')
1704 LoomTreeDecorator(source_tree).down_thread()
1705- # now clone
1706- target_tree = source_tree.bzrdir.clone('target').open_workingtree()
1707+ # now clone from the 'default url' - transport_server rather than
1708+ # vfs_server.
1709+ source_branch = Branch.open(self.get_url('source'))
1710+ target_tree = source_branch.bzrdir.sprout('target').open_workingtree()
1711 self.assertLoomSproutedOk(source_tree, target_tree)
1712
1713+ def test_sprout_remote_loom(self):
1714+ # RemoteBranch should permit sprouting properly.
1715+ self.transport_server = test_server.SmartTCPServer_for_testing
1716+ self.make_and_clone_simple_loom()
1717+
1718 def test_sprout_nonempty_loom_bottom(self):
1719 """Sprouting always resets the loom to the top."""
1720 source_tree = self.get_tree_with_one_commit('source')
1721 source_tree.branch.new_thread('bottom')
1722 source_tree.branch.new_thread('top')
1723- source_tree.branch.nick = 'top'
1724+ source_tree.branch._set_nick('top')
1725 source_tree.commit('phwoar', allow_pointless=True)
1726 source_tree.branch.record_loom('commit to loom')
1727 LoomTreeDecorator(source_tree).down_thread()
1728@@ -322,10 +349,10 @@
1729 source = self.get_tree_with_loom('source')
1730 source.branch.new_thread('bottom')
1731 source.branch.new_thread('top')
1732- source.branch.nick = 'bottom'
1733+ source.branch._set_nick('bottom')
1734 source.branch.record_loom('commit to loom')
1735 target = source.bzrdir.sprout('target').open_branch()
1736- target.nick = 'top'
1737+ target._set_nick('top')
1738 # put a commit in the bottom and top of this loom
1739 bottom_rev1 = source.commit('commit my arse')
1740 source_loom_tree = LoomTreeDecorator(source)
1741@@ -355,14 +382,20 @@
1742
1743 def test_pull_into_empty_loom(self):
1744 """Doing a pull into a loom with no loom revisions works."""
1745+ self.pull_into_empty_loom()
1746+
1747+ def pull_into_empty_loom(self):
1748 source = self.get_tree_with_loom('source')
1749 target = source.bzrdir.sprout('target').open_branch()
1750 source.branch.new_thread('a thread')
1751- source.branch.nick = 'a thread'
1752+ source.branch._set_nick('a thread')
1753 # put a commit in the thread for source.
1754 bottom_rev1 = source.commit('commit a thread')
1755 source.branch.record_loom('commit to loom')
1756- target.pull(source.branch)
1757+ # now pull from the 'default url' - transport_server rather than
1758+ # vfs_server - this may be a RemoteBranch.
1759+ source_branch = Branch.open(self.get_url('source'))
1760+ target.pull(source_branch)
1761 # check loom threads
1762 threads = target.get_loom_state().get_threads()
1763 self.assertEqual(
1764@@ -374,12 +407,17 @@
1765 self.assertTrue(target.repository.has_revision(rev_id))
1766 self.assertEqual(source.branch.loom_parents(), target.loom_parents())
1767
1768+ def test_pull_remote_loom(self):
1769+ # RemoteBranch should permit sprouting properly.
1770+ self.transport_server = test_server.SmartTCPServer_for_testing
1771+ self.pull_into_empty_loom()
1772+
1773 def test_pull_thread_at_null(self):
1774 """Doing a pull when the source loom has a thread with no history."""
1775 source = self.get_tree_with_loom('source')
1776 target = source.bzrdir.sprout('target').open_branch()
1777 source.branch.new_thread('a thread')
1778- source.branch.nick = 'a thread'
1779+ source.branch._set_nick('a thread')
1780 source.branch.record_loom('commit to loom')
1781 target.pull(source.branch)
1782 # check loom threads
1783@@ -398,10 +436,10 @@
1784 source = self.get_tree_with_loom('source')
1785 source.branch.new_thread('bottom')
1786 source.branch.new_thread('top')
1787- source.branch.nick = 'bottom'
1788+ source.branch._set_nick('bottom')
1789 source.branch.record_loom('commit to loom')
1790 target = source.bzrdir.sprout('target').open_branch()
1791- target.nick = 'top'
1792+ target._set_nick('top')
1793 # put a commit in the bottom and top of this loom
1794 bottom_rev1 = source.commit('commit bottom')
1795 source_loom_tree = LoomTreeDecorator(source)
1796@@ -432,7 +470,7 @@
1797 def test_implicit_record(self):
1798 tree = self.get_tree_with_loom('source')
1799 tree.branch.new_thread('bottom')
1800- tree.branch.nick = 'bottom'
1801+ tree.branch._set_nick('bottom')
1802 tree.lock_write()
1803 try:
1804 bottom_rev1 = tree.commit('commit my arse')
1805@@ -453,7 +491,7 @@
1806 self.assertEqual([], tree.branch.loom_parents())
1807 # add a thread and record it.
1808 tree.branch.new_thread('bottom')
1809- tree.branch.nick = 'bottom'
1810+ tree.branch._set_nick('bottom')
1811 rev_id = tree.branch.record_loom('Setup test loom.')
1812 # after recording, the parents list should have changed.
1813 self.assertEqual([rev_id], tree.branch.loom_parents())
1814@@ -464,7 +502,7 @@
1815 # new threads
1816 tree.branch.new_thread('foo')
1817 tree.branch.new_thread('bar')
1818- tree.branch.nick = 'bar'
1819+ tree.branch._set_nick('bar')
1820 last_rev = tree.branch.last_revision()
1821 # and a change to the revision history of this thread
1822 tree.commit('change bar', allow_pointless=True)
1823@@ -478,7 +516,7 @@
1824 # new threads
1825 tree.branch.new_thread('foo')
1826 tree.branch.new_thread('bar')
1827- tree.branch.nick = 'bar'
1828+ tree.branch._set_nick('bar')
1829 # and a change to the revision history of this thread
1830 tree.commit('change bar', allow_pointless=True)
1831 # now record
1832@@ -503,7 +541,7 @@
1833 # new threads
1834 tree.branch.new_thread('base')
1835 tree.branch.new_thread('top')
1836- tree.branch.nick = 'top'
1837+ tree.branch._set_nick('top')
1838 # and a change to the revision history of this thread
1839 tree.tree.commit('change top', allow_pointless=True)
1840 last_rev = tree.branch.last_revision()
1841@@ -527,7 +565,7 @@
1842 tree.branch.new_thread('foo')
1843 tree.branch.new_thread('bar')
1844 # do a commit, so the last_revision should change.
1845- tree.branch.nick = 'bar'
1846+ tree.branch._set_nick('bar')
1847 tree.commit('bar-ness', allow_pointless=True)
1848 tree.branch.revert_thread('bar')
1849 self.assertEqual(
1850@@ -540,11 +578,11 @@
1851 # ensure we have some stuff to revert
1852 tree.branch.new_thread('foo')
1853 tree.branch.new_thread('bar')
1854- tree.branch.nick = 'foo'
1855+ tree.branch._set_nick('foo')
1856 # record the loom to put the threads in the basis
1857 tree.branch.record_loom('record it!')
1858 # do a commit, so the last_revision should change.
1859- tree.branch.nick = 'bar'
1860+ tree.branch._set_nick('bar')
1861 tree.commit('bar-ness', allow_pointless=True)
1862 tree.branch.revert_thread('bar')
1863 self.assertEqual(
1864@@ -558,7 +596,7 @@
1865 tree = self.get_tree_with_loom()
1866 tree.branch.new_thread('bar')
1867 tree.branch.new_thread('foo')
1868- tree.branch.nick = 'bar'
1869+ tree.branch._set_nick('bar')
1870 tree.branch.remove_thread('foo')
1871 state = tree.branch.get_loom_state()
1872 self.assertEqual([('bar', 'empty:', [])], state.get_threads())
1873@@ -569,17 +607,17 @@
1874 self.assertEqual([], tree.branch.get_threads(NULL_REVISION))
1875 # and loom history should make no difference:
1876 tree.branch.new_thread('foo')
1877- tree.branch.nick = 'foo'
1878+ tree.branch._set_nick('foo')
1879 tree.branch.record_loom('foo')
1880 self.assertEqual([], tree.branch.get_threads(NULL_REVISION))
1881
1882 def get_multi_threaded(self):
1883 tree = self.get_tree_with_loom()
1884 tree.branch.new_thread('thread1')
1885- tree.branch.nick = 'thread1'
1886+ tree.branch._set_nick('thread1')
1887 tree.commit('thread1', rev_id='thread1-id')
1888 tree.branch.new_thread('thread2', 'thread1')
1889- tree.branch.nick = 'thread2'
1890+ tree.branch._set_nick('thread2')
1891 tree.commit('thread2', rev_id='thread2-id')
1892 return tree
1893
1894@@ -635,3 +673,14 @@
1895 export_branch = Branch.open_from_transport(
1896 root_transport.clone('thread1'))
1897 self.assertEqual('thread1-id', export_branch.last_revision())
1898+
1899+ def test_set_nick_renames_thread(self):
1900+ tree = self.get_tree_with_loom()
1901+ tree.branch.new_thread(tree.branch.nick)
1902+ orig_threads = tree.branch.get_loom_state().get_threads()
1903+ new_thread_name = 'new thread name'
1904+ tree.branch.nick = new_thread_name
1905+ new_threads = tree.branch.get_loom_state().get_threads()
1906+ self.assertNotEqual(orig_threads, new_threads)
1907+ self.assertEqual(new_thread_name, new_threads[0][0])
1908+ self.assertEqual(new_thread_name, tree.branch.nick)
1909
1910=== modified file 'tests/test_revspec.py'
1911--- tests/test_revspec.py 2008-09-12 01:55:17 +0000
1912+++ tests/test_revspec.py 2010-08-05 18:57:44 +0000
1913@@ -19,26 +19,29 @@
1914 """Tests of the loom revision-specifiers."""
1915
1916
1917-import bzrlib
1918+import bzrlib.errors
1919 from bzrlib.plugins.loom.branch import NoLowerThread, NoSuchThread
1920 from bzrlib.plugins.loom.tests import TestCaseWithLoom
1921 import bzrlib.plugins.loom.tree
1922 from bzrlib.revisionspec import RevisionSpec
1923
1924
1925-class TestThreadRevSpec(TestCaseWithLoom):
1926- """Tests of the ThreadRevisionSpecifier."""
1927-
1928+class TestRevSpec(TestCaseWithLoom):
1929+
1930 def get_two_thread_loom(self):
1931 tree = self.get_tree_with_loom('source')
1932 tree.branch.new_thread('bottom')
1933 tree.branch.new_thread('top')
1934- tree.branch.nick = 'bottom'
1935+ tree.branch._set_nick('bottom')
1936 rev_id_bottom = tree.commit('change bottom')
1937 loom_tree = bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)
1938 loom_tree.up_thread()
1939 rev_id_top = tree.commit('change top')
1940 return tree, loom_tree, rev_id_bottom, rev_id_top
1941+
1942+
1943+class TestThreadRevSpec(TestRevSpec):
1944+ """Tests of the ThreadRevisionSpecifier."""
1945
1946 def test_thread_colon_at_bottom_errors(self):
1947 tree, loom_tree, rev_id, _ = self.get_two_thread_loom()
1948@@ -69,3 +72,44 @@
1949 loom_tree.down_thread()
1950 spec = RevisionSpec.from_string('thread:top')
1951 self.assertEqual(rev_id, spec.as_revision_id(tree.branch))
1952+
1953+ def test_thread_on_non_loom_gives_BzrError(self):
1954+ tree = self.make_branch_and_tree('.')
1955+ spec = RevisionSpec.from_string('thread:')
1956+ err = self.assertRaises(bzrlib.errors.BzrError, spec.as_revision_id,
1957+ tree.branch)
1958+ self.assertFalse(err.internal_error)
1959+
1960+
1961+class TestBelowRevSpec(TestRevSpec):
1962+ """Tests of the below: revision specifier."""
1963+
1964+ def test_below_gets_tip_of_thread_below(self):
1965+ tree, loom_tree, _, rev_id = self.get_two_thread_loom()
1966+ loom_tree.down_thread()
1967+ expected_id = tree.branch.last_revision()
1968+ loom_tree.up_thread()
1969+ spec = RevisionSpec.from_string('below:')
1970+ self.assertEqual(expected_id, spec.as_revision_id(tree.branch))
1971+
1972+ def test_below_on_bottom_thread_gives_BzrError(self):
1973+ tree, loom_tree, _, rev_id = self.get_two_thread_loom()
1974+ loom_tree.down_thread()
1975+ spec = RevisionSpec.from_string('below:')
1976+ err = self.assertRaises(bzrlib.errors.BzrError, spec.as_revision_id,
1977+ tree.branch)
1978+ self.assertFalse(err.internal_error)
1979+
1980+ def test_below_named_thread(self):
1981+ tree, loom_tree, _, rev_id = self.get_two_thread_loom()
1982+ loom_tree.down_thread()
1983+ expected_id = tree.branch.last_revision()
1984+ spec = RevisionSpec.from_string('below:top')
1985+ self.assertEqual(expected_id, spec.as_revision_id(tree.branch))
1986+
1987+ def test_below_on_non_loom_gives_BzrError(self):
1988+ tree = self.make_branch_and_tree('.')
1989+ spec = RevisionSpec.from_string('below:')
1990+ err = self.assertRaises(bzrlib.errors.BzrError, spec.as_revision_id,
1991+ tree.branch)
1992+ self.assertFalse(err.internal_error)
1993
1994=== modified file 'tests/test_tree.py'
1995--- tests/test_tree.py 2008-11-24 16:38:16 +0000
1996+++ tests/test_tree.py 2010-08-05 18:57:44 +0000
1997@@ -38,14 +38,14 @@
1998 tree = self.get_tree_with_loom('source')
1999 tree.branch.new_thread('bottom')
2000 tree.branch.new_thread('top')
2001- tree.branch.nick = 'bottom'
2002+ tree.branch._set_nick('bottom')
2003 return bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)
2004
2005 def test_down_thread(self):
2006 tree = self.get_tree_with_loom('source')
2007 tree.branch.new_thread('bottom')
2008 tree.branch.new_thread('top')
2009- tree.branch.nick = 'top'
2010+ tree.branch._set_nick('top')
2011 loom_tree = bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)
2012 loom_tree.down_thread()
2013 self.assertEqual('bottom', tree.branch.nick)
2014@@ -53,7 +53,7 @@
2015 def _add_thread(self, tree, name):
2016 """Create a new thread with a commit and return the commit id."""
2017 tree.branch.new_thread(name)
2018- tree.branch.nick = name
2019+ tree.branch._set_nick(name)
2020 return tree.commit(name)
2021
2022 def test_down_named_thread(self):
2023@@ -78,7 +78,7 @@
2024 tree = self.get_tree_with_loom('tree')
2025 tree.branch.new_thread('bottom')
2026 tree.branch.new_thread('top')
2027- tree.branch.nick = 'bottom'
2028+ tree.branch._set_nick('bottom')
2029 bottom_rev1 = tree.commit('bottom_commit')
2030 tree_loom_tree = bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)
2031 tree_loom_tree.up_thread()
2032@@ -89,10 +89,10 @@
2033 """up-thread into a thread that already has this thread is a no-op."""
2034 tree = self.get_tree_with_loom('tree')
2035 tree.branch.new_thread('bottom')
2036- tree.branch.nick = 'bottom'
2037+ tree.branch._set_nick('bottom')
2038 bottom_rev1 = tree.commit('bottom_commit')
2039 tree.branch.new_thread('top', 'bottom')
2040- tree.branch.nick = 'top'
2041+ tree.branch._set_nick('top')
2042 top_rev1 = tree.commit('top_commit', allow_pointless=True)
2043 tree_loom_tree = bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)
2044 tree_loom_tree.down_thread()
2045@@ -108,10 +108,10 @@
2046 """up-thread from a thread with new work."""
2047 tree = self.get_tree_with_loom('tree')
2048 tree.branch.new_thread('bottom')
2049- tree.branch.nick = 'bottom'
2050+ tree.branch._set_nick('bottom')
2051 bottom_rev1 = tree.commit('bottom_commit')
2052 tree.branch.new_thread('top', 'bottom')
2053- tree.branch.nick = 'top'
2054+ tree.branch._set_nick('top')
2055 top_rev1 = tree.commit('top_commit', allow_pointless=True)
2056 tree_loom_tree = bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)
2057 tree_loom_tree.down_thread()
2058@@ -144,14 +144,16 @@
2059 self.build_tree_contents([('source/a', 'c')])
2060 loom_tree.tree.commit('content to c')
2061 loom_tree.up_thread(_mod_merge.WeaveMerger)
2062- self.failIfExists('source/a.BASE')
2063+ # Disabled because WeaveMerger writes BASE files now. XXX: Figure out
2064+ # how to test this actually worked, again.
2065+ # self.failIfExists('source/a.BASE')
2066
2067 def get_loom_with_three_threads(self):
2068 tree = self.get_tree_with_loom('source')
2069 tree.branch.new_thread('bottom')
2070 tree.branch.new_thread('middle')
2071 tree.branch.new_thread('top')
2072- tree.branch.nick = 'bottom'
2073+ tree.branch._set_nick('bottom')
2074 return bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)
2075
2076 def test_up_many(self):
2077@@ -191,12 +193,26 @@
2078 self.assertEqual(['middle-1', 'bottom-2'], tree.get_parent_ids())
2079 self.assertEqual(1, len(tree.conflicts()))
2080
2081+ def test_up_many_target_thread(self):
2082+ loom_tree = self.get_loom_with_three_threads()
2083+ tree = loom_tree.tree
2084+ loom_tree.up_many(target_thread='middle')
2085+ self.assertEqual('middle', tree.branch.nick)
2086+
2087+ def test_up_many_target_thread_lower(self):
2088+ loom_tree = self.get_loom_with_three_threads()
2089+ tree = loom_tree.tree
2090+ loom_tree.up_many(target_thread='top')
2091+ e = self.assertRaises(errors.BzrCommandError,
2092+ loom_tree.up_many, target_thread='middle')
2093+ self.assertEqual('Cannot up-thread to lower thread.', str(e))
2094+
2095 def test_revert_loom(self):
2096 tree = self.get_tree_with_loom(',')
2097 # ensure we have some stuff to revert
2098 tree.branch.new_thread('foo')
2099 tree.branch.new_thread('bar')
2100- tree.branch.nick = 'bar'
2101+ tree.branch._set_nick('bar')
2102 tree.commit('change something', allow_pointless=True)
2103 loom_tree = bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)
2104 loom_tree.revert_loom()
2105@@ -211,7 +227,7 @@
2106 # ensure we have some stuff to revert
2107 tree.branch.new_thread('foo')
2108 tree.branch.new_thread('bar')
2109- tree.branch.nick = 'bar'
2110+ tree.branch._set_nick('bar')
2111 tree.commit('change something', allow_pointless=True)
2112 loom_tree = bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)
2113 loom_tree.revert_loom(thread='bar')
2114@@ -228,7 +244,7 @@
2115 # ensure we have some stuff to revert
2116 tree.branch.new_thread('foo')
2117 tree.branch.new_thread('bar')
2118- tree.branch.nick = 'bar'
2119+ tree.branch._set_nick('bar')
2120 tree.commit('change something', allow_pointless=True)
2121 loom_tree = bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)
2122 loom_tree.revert_loom(thread='foo')
2123
2124=== modified file 'tree.py'
2125--- tree.py 2008-11-24 16:38:16 +0000
2126+++ tree.py 2010-08-05 18:57:44 +0000
2127@@ -24,7 +24,10 @@
2128 __all__ = ['LoomTreeDecorator']
2129
2130
2131-from bzrlib import ui
2132+from bzrlib import (
2133+ trace,
2134+ ui,
2135+ )
2136 from bzrlib.decorators import needs_write_lock
2137 import bzrlib.errors
2138 import bzrlib.revision
2139@@ -78,7 +81,7 @@
2140 graph = self.tree.branch.repository.get_graph()
2141 # special case no-change condition.
2142 if new_thread_rev == old_thread_rev:
2143- self.tree.branch.nick = new_thread_name
2144+ self.tree.branch._set_nick(new_thread_name)
2145 return 0
2146 if new_thread_rev == EMPTY_REVISION:
2147 new_thread_rev = bzrlib.revision.NULL_REVISION
2148@@ -87,19 +90,15 @@
2149 # merge the tree up into the new patch:
2150 if merge_type is None:
2151 merge_type = bzrlib.merge.Merge3Merger
2152- pb = ui.ui_factory.nested_progress_bar()
2153 try:
2154- try:
2155- merge_controller = bzrlib.merge.Merger.from_revision_ids(
2156- pb, self.tree, new_thread_rev, revision_graph=graph)
2157- except bzrlib.errors.UnrelatedBranches:
2158- raise bzrlib.errors.BzrCommandError('corrupt loom: thread %s'
2159- ' has no common ancestor with thread %s'
2160- % (new_thread_name, threadname))
2161- merge_controller.merge_type = merge_type
2162- result = merge_controller.do_merge()
2163- finally:
2164- pb.finished()
2165+ merge_controller = bzrlib.merge.Merger.from_revision_ids(
2166+ None, self.tree, new_thread_rev, revision_graph=graph)
2167+ except bzrlib.errors.UnrelatedBranches:
2168+ raise bzrlib.errors.BzrCommandError('corrupt loom: thread %s'
2169+ ' has no common ancestor with thread %s'
2170+ % (new_thread_name, threadname))
2171+ merge_controller.merge_type = merge_type
2172+ result = merge_controller.do_merge()
2173 # change the tree to the revision of the new thread.
2174 parent_trees = []
2175 if new_thread_rev != bzrlib.revision.NULL_REVISION:
2176@@ -126,20 +125,37 @@
2177 # change the branch
2178 self.tree.branch.generate_revision_history(new_thread_rev)
2179 # update the branch nick.
2180- self.tree.branch.nick = new_thread_name
2181- bzrlib.trace.note("Moved to thread '%s'." % new_thread_name)
2182+ self.tree.branch._set_nick(new_thread_name)
2183+ trace.note("Moved to thread '%s'." % new_thread_name)
2184+ if (basis_tree is not None and
2185+ not result and not
2186+ self.tree.changes_from(basis_tree).has_changed()):
2187+ trace.note("This thread is now empty, you may wish to "
2188+ 'run "bzr combine-thread" to remove it.')
2189 if result != 0:
2190 return 1
2191 else:
2192 return 0
2193
2194- def up_many(self, merge_type=None):
2195- threads = self.branch.get_loom_state().get_threads()
2196- top_thread_name = threads[-1][0]
2197- while self.branch.nick != top_thread_name:
2198+ def up_many(self, merge_type=None, target_thread=None):
2199+ loom_state = self.branch.get_loom_state()
2200+ threads = loom_state.get_threads()
2201+ if target_thread is None:
2202+ target_thread = threads[-1][0]
2203+ if self.branch.nick == target_thread:
2204+ raise bzrlib.errors.BzrCommandError(
2205+ 'Cannot move up from the highest thread.')
2206+ else:
2207+ upper_thread_i = loom_state.thread_index(target_thread)
2208+ lower_thread_i = loom_state.thread_index(self.branch.nick)
2209+ if lower_thread_i > upper_thread_i:
2210+ raise bzrlib.errors.BzrCommandError(
2211+ "Cannot up-thread to lower thread.")
2212+ while self.branch.nick != target_thread:
2213 old_nick = self.branch.nick
2214- if self.up_thread(merge_type) != 0:
2215- break
2216+ result = self.up_thread(merge_type)
2217+ if result != 0:
2218+ return result
2219 if len(self.tree.get_parent_ids()) > 1:
2220 self.tree.commit('Merge %s into %s' % (old_nick,
2221 self.branch.nick))
2222@@ -147,12 +163,11 @@
2223 @needs_write_lock
2224 def down_thread(self, name=None):
2225 """Move to a thread down in the loom.
2226-
2227+
2228 :param name: If None, use the next lower thread; otherwise the nae of
2229 the thread to move to.
2230 """
2231 self._check_switch()
2232- current_revision = self.tree.last_revision()
2233 threadname = self.tree.branch.nick
2234 state = self.tree.branch.get_loom_state()
2235 threads = state.get_threads()
2236@@ -168,7 +183,7 @@
2237 index = state.thread_index(name)
2238 new_thread_rev = threads[index][1]
2239 assert new_thread_rev is not None
2240- self.tree.branch.nick = new_thread_name
2241+ self.tree.branch._set_nick(new_thread_name)
2242 if new_thread_rev == old_thread_rev:
2243 # fast path no-op changes
2244 bzrlib.trace.note("Moved to thread '%s'." % new_thread_name)
2245@@ -177,17 +192,30 @@
2246 new_thread_rev = bzrlib.revision.NULL_REVISION
2247 if old_thread_rev == EMPTY_REVISION:
2248 old_thread_rev = bzrlib.revision.NULL_REVISION
2249- basis_tree = self.tree.branch.repository.revision_tree(old_thread_rev)
2250- to_tree = self.tree.branch.repository.revision_tree(new_thread_rev)
2251+ repository = self.tree.branch.repository
2252+ try:
2253+ basis_tree = self.tree.revision_tree(old_thread_rev)
2254+ except bzrlib.errors.NoSuchRevisionInTree:
2255+ basis_tree = repository.revision_tree(old_thread_rev)
2256+ to_tree = repository.revision_tree(new_thread_rev)
2257 result = bzrlib.merge.merge_inner(self.tree.branch,
2258 to_tree,
2259 basis_tree,
2260 this_tree=self.tree)
2261- self.tree.branch.generate_revision_history(new_thread_rev)
2262- self.tree.set_last_revision(new_thread_rev)
2263+ branch_revno, branch_revision = self.tree.branch.last_revision_info()
2264+ graph = repository.get_graph()
2265+ new_thread_revno = graph.find_distance_to_null(new_thread_rev,
2266+ [(branch_revision, branch_revno)])
2267+ self.tree.branch.set_last_revision_info(new_thread_revno,
2268+ new_thread_rev)
2269+ if new_thread_rev == bzrlib.revision.NULL_REVISION:
2270+ parent_list = []
2271+ else:
2272+ parent_list = [(new_thread_rev, to_tree)]
2273+ self.tree.set_parent_trees(parent_list)
2274 bzrlib.trace.note("Moved to thread '%s'." % new_thread_name)
2275 return result
2276-
2277+
2278 def lock_write(self):
2279 self.tree.lock_write()
2280
2281
2282=== added file 'version.py'
2283--- version.py 1970-01-01 00:00:00 +0000
2284+++ version.py 2010-08-05 18:57:44 +0000
2285@@ -0,0 +1,28 @@
2286+# Loom, a plugin for bzr to assist in developing focused patches.
2287+# Copyright (C) 2010 Canonical Limited.
2288+#
2289+# This program is free software; you can redistribute it and/or modify
2290+# it under the terms of the GNU General Public License version 2 as published
2291+# by the Free Software Foundation.
2292+#
2293+# This program is distributed in the hope that it will be useful,
2294+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2295+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2296+# GNU General Public License for more details.
2297+#
2298+# You should have received a copy of the GNU General Public License
2299+# along with this program; if not, write to the Free Software
2300+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
2301+#
2302+
2303+"""Versioning information for bzr-loom."""
2304+
2305+__all__ = [
2306+ 'bzr_plugin_version',
2307+ 'bzr_minimum_version',
2308+ 'bzr_maximum_version',
2309+ ]
2310+
2311+bzr_plugin_version = (2, 2, 1, 'dev', 0)
2312+bzr_minimum_version = (2, 2, 0)
2313+bzr_maximum_version = None

Subscribers

People subscribed via source and target branches