Merge lp:~vila/bzr/conflict-manager into lp:bzr

Proposed by Vincent Ladeuil
Status: Merged
Approved by: John A Meinel
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~vila/bzr/conflict-manager
Merge into: lp:bzr
Prerequisite: lp:~vila/bzr/cleanup-conflicts
Diff against target: 1502 lines (+954/-125) (has conflicts)
7 files modified
NEWS (+8/-0)
bzrlib/conflicts.py (+228/-36)
bzrlib/help_topics/en/conflict-types.txt (+172/-52)
bzrlib/option.py (+1/-1)
bzrlib/tests/blackbox/test_conflicts.py (+39/-36)
bzrlib/tests/test_conflicts.py (+503/-0)
bzrlib/workingtree.py (+3/-0)
Text conflict in NEWS
To merge this branch: bzr merge lp:~vila/bzr/conflict-manager
Reviewer Review Type Date Requested Status
John A Meinel Needs Fixing
Review via email: mp+16785@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Vincent Ladeuil (vila) wrote :

This patch implements some simple actions to resolve tree shape conflicts (that is, all conflicts that are not text conflicts) by providing --keep-mine and --take-their
to the resolve command.
Just marking the conflict as resolved is still accessible via the --done default action.

It also documents (mostly via hidden comments in documentation, TODOs and FIXMEs in the code)
the next steps I plan to work on.

But since this is already a significant patch, I'd like feedback before going further.

Revision history for this message
Aaron Bentley (abentley) wrote :
Download full text (5.1 KiB)

This looks a bit weird to me. Haven't done a full review, but for two-way conflicts, bzr takes the changes from OTHER, so keep_mine should never be a no-op, and take_their should often be a no-op.

take_their should actually be take_theirs. ("my" is to "their" as "mine" is to "theirs".)

Also, the safest way to rename multiple files at once is using a TreeTransform.

There are some grammar issues in the proposed new text:
+may conflict with the current state of your working tree.
+
+When conflicts are present in your working tree (as shown by ``bzr
+conflicts``), you should resolve them and then inform bzr that the conflicts
+has been resolved.

"have been resolved"

+
+Resolving conflicts is sometimes not obvious. Either because the user that
+should resolve them is not the one responsible for their occurrence, as is the
+case when merging other people work or because some conflicts are presented in

"other people's work"

+a way that is not easy to understand.
+
+Bazaar try to avoid conflicts, its aim is to ask you to resolve the conflict

"Bazaar tries to avoid conflicts; its aim is to ask you to resolve the conflict"

+if and only if there's an actual conceptual conflict in the source tree.
+Because Bazaar doesn't understand the real meaning of the files being
+versioned it can, when faced with ambiguities, fall short in either direction

"versioned,"

(But it may be better to restructure the sentence instead of adding commas.)

+trying to resolve the conflict itself. Many kinds of changes can be combined
+programmatically, but sometimes only a human can determine the right thing to
+do.
+
+When Bazaar generates a conflict, it adds information into the working tree to
+present the conflicting versions and it's up to you to find the correct

"versions,"

+resolution.
+
+Whatever the conflict is, resolving it is roughly done in two steps:
+
+- modify the working tree content so that the conflicted item is now in the
+ state you want to keep,
+
+- inform Bazaar that the conflict is now solved and ask to cleanup any
+ remaining generated information (``bzr resolve <item>``).
+
+For most conflict types, there are some obvious ways to modify the working
+tree and put it into the desired state. For some types of conflicts, Bazaar
+itself already made a choice when possible.

I think "for some types...when possible" is too cautious, but at any rate, "Bazaar itself already made a choice when possible" is ungrammatical. It should be "Bazaar itself will have already made a choice, when possible"

+Yet, whether Bazaar made a choice or not, there are some other simple but
+alternative ways to resolve the conflict.

"Bazaar makes a choice"

("alternative" tastes funny here. Maybe "different"?)

+Various actions are available depending on the kind of conflict, for some of
+these actions, Bazaar can provide some help. In the end you should at least
+inform Bazaar that you're done with the conflict with::
+
+ ``bzr resolve FILE --action=done'
+
+Note that this is the default action when a single file is involved so you can
+simply use::
+
+ ``bzr resolve FILE``

Why tell them to use --action=done and then immediately contradict yourself? And w...

Read more...

Revision history for this message
John A Meinel (jameinel) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Vincent Ladeuil wrote:
> Vincent Ladeuil has proposed merging lp:~vila/bzr/conflict-manager into lp:bzr with lp:~vila/bzr/cleanup-conflicts as a prerequisite.
>
> Requested reviews:
> bzr-core (bzr-core)
>
>
> This patch implements some simple actions to resolve tree shape conflicts (that is, all conflicts that are not text conflicts) by providing --keep-mine and --take-their
> to the resolve command.
> Just marking the conflict as resolved is still accessible via the --done default action.

Meta-comment

We seem a bit confused as to whether 'bzr resolve' is a command that has
actions, or whether it is a command that records actions you've already
done. I'm worried that this takes it a step further. (resolve
- --interactive is quite different from what 'resolve' does today.)

I'm wondering if we should be considering how to split out the current
functionality into separate commands...

John
=:->

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAktCTvwACgkQJdeBCYSNAAN5rgCggVpL7OvYmZSUwPFJ0/UHfjLq
N14An0NCsQrKF6+Q6+7xR++1F2y2CB/2
=1KQw
-----END PGP SIGNATURE-----

Revision history for this message
Vincent Ladeuil (vila) wrote :
Download full text (3.4 KiB)

>>>>> "Aaron" == Aaron Bentley <email address hidden> writes:

    Aaron> This looks a bit weird to me. Haven't done a full
    Aaron> review, but for two-way conflicts, bzr takes the
    Aaron> changes from OTHER, so keep_mine should never be a
    Aaron> no-op, and take_their should often be a no-op.

I didn't touch the code that generate the conflicts, so any
incoherency there was there. The overall code is already hard to
modify so I don't intend to change that sort of thing, yet.

But, yes, I was surprised to find that we don't always act the
same way when faced with conflicts and I'd like to provide a more
"regular" behavior (like using .THIS .OTHER instead of .new or
.diverted for example).

Also, if there is a conflict, it sounds acceptable that *both*
keep_mine and take_theirs should not be no-ops... with the
down-side that this will left the working tree in a worse state
than choosing between THIS or OTHER.

I'd welcome any feedback about why we do things the way they are
done today !

    Aaron> take_their should actually be take_theirs. ("my" is
    Aaron> to "their" as "mine" is to "theirs".)

Damn, I wrote it --take-theirs first :)

    Aaron> Also, the safest way to rename multiple files at once
    Aaron> is using a TreeTransform.

Thanks. I think there is a single place that deserves that, I'll check.

    Aaron> There are some grammar issues in the proposed new
    Aaron> text:

Thanks, I addressed them in my branch and only answer other
points below.

<snip/>

    Aaron> +Various actions are available depending on the kind of conflict, for some of
    Aaron> +these actions, Bazaar can provide some help. In the end you should at least
    Aaron> +inform Bazaar that you're done with the conflict with::
    Aaron> +
    Aaron> + ``bzr resolve FILE --action=done'
    Aaron> +
    Aaron> +Note that this is the default action when a single file is involved so you can
    Aaron> +simply use::
    Aaron> +
    Aaron> + ``bzr resolve FILE``

    Aaron> Why tell them to use --action=done and then
    Aaron> immediately contradict yourself?

Because the former is explicit why the later is implicit (and
don't contradict each other). I try to always use the explicit
form in docs.

    Aaron> And why --action=done instead of --done ?

Same.

    Aaron> +When you have resolved text conflicts, just run ``bzr
    Aaron> +resolve --auto``, and Bazaar will auto-detect which
    Aaron> +conflicts you have resolved.

    Aaron> Requiring --auto is a regression IMO.

Hehe, this text doesn't imply that --auto is *required* it just
mention it explicitly (same rule as above).

Well, I didn't change the behavior of --auto (finally), but I
intend to. The code that still implements the magic behavior of
--auto. But as said, I find that behavior pretty surprising and
I think we should get rid of it.

141 + # FIXME: There is a special case here related to the option
142 + # handling that could be clearer and easier to discover by
143 + # providing an --auto action (bug #344013 and #383396) and
144 + # make it mandatory instead of implicit and active only
145 + # when no file_list is provided -- vila 091229

If this patch is accepted then --aut...

Read more...

Revision history for this message
Vincent Ladeuil (vila) wrote :

>>>>> "jam" == John A Meinel <email address hidden> writes:

<snip/>

    jam> We seem a bit confused as to whether 'bzr resolve' is a
    jam> command that has actions, or whether it is a command
    jam> that records actions you've already done.

Yes. It's surprising. But 'resolved', which reflects more closely
the actual behavior is already declared as an alias.

    jam> I'm worried that this takes it a step further. (resolve
    jam> --interactive is quite different from what 'resolve'
    jam> does today.)

Well, it literally resolves the conflict instead of just deleting
it (and I abandon the --interactive idea in favor of handling a
list of files (as resolve was already), GUIs may choose
differently but at least the command line UI provides the basis).

So it makes sense to use 'resolve' for the actions and 'resolved'
for just marking the conflicts as resolved.

But then, in 99% of the actions you also want to mark the
conflict as resolved !

In the end, I considered that I was providing more ways to
resolve the conflicts than the actual --done action.

    jam> I'm wondering if we should be considering how to split
    jam> out the current functionality into separate commands...

Well, we currently have: bzr resolve [--auto] {--all|FILE+}.

There is not much that will be lost except that the magic
behavior associated with --auto (disabled when you use --all)
will become explicit.

Do you have better names to propose ?

   Vincent

Revision history for this message
Ian Clatworthy (ian-clatworthy) wrote :

I started reviewing this the other day and didn't get a long way. I'm not sure I'm best qualified to comment on the bigger picture here: I'd like Aaron to sign off on that before this lands.

I'm more than happy to review the code for logic bugs and the doc for readability if that helps.

Revision history for this message
Vincent Ladeuil (vila) wrote :

Revno 4668 takes review comments into account:
- settle on using --take-this/--take-other instead of --keep-mine/--take-theirs,
- stop using rest in conflict-types.txt since it's displayed by
  ``bzr help conflict-types`` without processing.

Overall, it doesn't modify any existing behaviour but adds new features
on ``bzr resolve``.

The next steps are described at
 http://wiki.bazaar.canonical.com/VincentLadeuil/ConflictResolution

Discussions may still be needed before the next submissions but this patch
itself shouldn't be controversial.

Revision history for this message
John A Meinel (jameinel) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Vincent Ladeuil wrote:
> Revno 4668 takes review comments into account:
> - settle on using --take-this/--take-other instead of --keep-mine/--take-theirs,
> - stop using rest in conflict-types.txt since it's displayed by
> ``bzr help conflict-types`` without processing.

I would probably have left in the ReST syntax, since it is how other
bits are formatted, and leave to a separate action creating a plain-text
formatter for the rest documentation. (help strings, etc are all already
ReST or meant to be ReST, so we need to handle it anyway.)

John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAktplFsACgkQJdeBCYSNAAO8BgCfeC/fFAMUm0XGyrDKJRwdnlLy
sUwAnjX6ZYNufZNRIVZ9veuWZSXwHmt5
=h1WN
-----END PGP SIGNATURE-----

Revision history for this message
Vincent Ladeuil (vila) wrote :

>>>>> "jam" == John A Meinel <email address hidden> writes:

    jam> Vincent Ladeuil wrote:
    >> Revno 4668 takes review comments into account:
    >> - settle on using --take-this/--take-other instead of --keep-mine/--take-theirs,
    >> - stop using rest in conflict-types.txt since it's displayed by
    >> ``bzr help conflict-types`` without processing.

    jam> I would probably have left in the ReST syntax, since it is how other
    jam> bits are formatted, and leave to a separate action creating a plain-text
    jam> formatter for the rest documentation. (help strings, etc are all already
    jam> ReST or meant to be ReST, so we need to handle it anyway.)

And spit rest bits to the user when doing `bzr help conflict-types` ???

Revision history for this message
John A Meinel (jameinel) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Vincent Ladeuil wrote:
>>>>>> "jam" == John A Meinel <email address hidden> writes:
>
> jam> Vincent Ladeuil wrote:
> >> Revno 4668 takes review comments into account:
> >> - settle on using --take-this/--take-other instead of --keep-mine/--take-theirs,
> >> - stop using rest in conflict-types.txt since it's displayed by
> >> ``bzr help conflict-types`` without processing.
>
> jam> I would probably have left in the ReST syntax, since it is how other
> jam> bits are formatted, and leave to a separate action creating a plain-text
> jam> formatter for the rest documentation. (help strings, etc are all already
> jam> ReST or meant to be ReST, so we need to handle it anyway.)
>
> And spit rest bits to the user when doing `bzr help conflict-types` ???

Until we fix 'bzr help' we already do that for lots of other ones.

'bzr help init' shows a '::'
'bzr help init-repo' as well
'bzr help diff'
'bzr help log'

etc, etc, etc.

There is an open bug that we should use a plain-text formatter when
writing out ReST code, I'd rather fix that, then not have use use ReST
for help which gets translated into our HTML documentation.

John
=:->

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAktpxSkACgkQJdeBCYSNAAMfCQCeJOtTHmoD7iy+nmhXH80AXD7I
8mgAoNV98mCOlfe2/62SdBnXFvGN47vF
=WUOX
-----END PGP SIGNATURE-----

Revision history for this message
Ian Clatworthy (ian-clatworthy) wrote :

John A Meinel wrote:
> Vincent Ladeuil wrote:

>> And spit rest bits to the user when doing `bzr help conflict-types` ???
>
> Until we fix 'bzr help' we already do that for lots of other ones.
>
> 'bzr help init' shows a '::'
> 'bzr help init-repo' as well
> 'bzr help diff'
> 'bzr help log'
>
> etc, etc, etc.
>
> There is an open bug that we should use a plain-text formatter when
> writing out ReST code, I'd rather fix that, then not have use use ReST
> for help which gets translated into our HTML documentation.

Some limited ReST translations gets done when outputting text help
currently, e.g.

:: at the ends of a line gets mapped to :
:doc:`xxx-help` gets mapped to `bzr help xxx`

We don't bundle DocTools or Sphinx but it is possible to write text
that is reasonable in both text output and html output given the rules
already in place. I'm sure we could tweak them further though.

Ian C.

Revision history for this message
Vincent Ladeuil (vila) wrote :

>>>>> "Ian" == Ian Clatworthy <email address hidden> writes:

    Ian> John A Meinel wrote:
    >> Vincent Ladeuil wrote:

    >>> And spit rest bits to the user when doing `bzr help conflict-types` ???
    >>
    >> Until we fix 'bzr help' we already do that for lots of other ones.
    >>
    >> 'bzr help init' shows a '::'
    >> 'bzr help init-repo' as well
    >> 'bzr help diff'
    >> 'bzr help log'
    >>
    >> etc, etc, etc.
    >>
    >> There is an open bug that we should use a plain-text formatter when
    >> writing out ReST code, I'd rather fix that, then not have use use ReST
    >> for help which gets translated into our HTML documentation.

    Ian> Some limited ReST translations gets done when outputting text help
    Ian> currently, e.g.

    Ian> :: at the ends of a line gets mapped to :
    Ian> :doc:`xxx-help` gets mapped to `bzr help xxx`

    Ian> We don't bundle DocTools or Sphinx but it is possible to write text
    Ian> that is reasonable in both text output and html output given the rules
    Ian> already in place. I'm sure we could tweak them further though.

Ok, so I've tweaked back these changes, I originally revert the
rest changes because, thinking it *was* a rest document, I
embedded some TODO and FIXMEs in the comment which are *not* to
be displayed in a help output.

Now, can I have some *review* on the core of this patch or,
failing that, an agreement that since nobody cares enough and the
changes are unlikely to break anything I can just land it ?

Thanks in advance.

Revision history for this message
John A Meinel (jameinel) wrote :

101 +class ResolveActionOption(option.RegistryOption):
102 +
103 + def __init__(self):
104 + super(ResolveActionOption, self).__init__(
105 + 'action', 'How to resolve the conflict.',
106 + value_switches=True,
107 + registry=resolve_action_registry)
108 +
109 +

^- This only seems useful if you were actually going to re-use the
ResolveActionOption.
I realize you have some tests for it, but it doesn't do anything beyond a
standard RegistryOption.

Were you thinking to have the '--auto' logic as part of the option object?

168 +def resolve(tree, paths=None, ignore_misses=False, recursive=False,
169 + action='done'):
^- Having a top-level function in builtins.py to do command specific actions
isn't particularly nice, though I realize this wasn't from you. Perhaps just a
comment that it should be moved into a bzrlib.resolve* module?

236 + def _do(self, action, tree):
237 + """Apply the specified action to the conflict.
238 +
239 + :param action: The method name to call.
240 +
241 + :param tree: The tree passed as a parameter to the method.
242 + """
243 + meth = getattr(self, action, None)
244 + if meth is None:
245 + raise NotImplementedError(self.__class__.__name__ + '.' + action)
246 + meth(tree)

^- To avoid namespace collisions, this would probably be better as:

meth = getattr(self, 'action_' + action, None)
...

=== modified file 'bzrlib/tests/test_conflicts.py'
971 --- bzrlib/tests/test_conflicts.py 2010-01-04 14:56:59 +0000
972 +++ bzrlib/tests/test_conflicts.py 2010-02-04 13:06:32 +0000
973 @@ -21,8 +21,10 @@
974 bzrdir,
975 conflicts,
976 errors,
977 + option,
978 tests,
979 )
980 +from bzrlib.tests import script

^- script tests don't seem to be a great match for whitebox testing. And
looking at the scripts themselves they certainly look like blackbox tests.
(using commit to assert that things are ok.) Versus even an "assert*" function.

The tests seem fine, but they look like they should belong in a
blackbox/test_resolve.py module.

review: Needs Fixing
Revision history for this message
Vincent Ladeuil (vila) wrote :

> 101 +class ResolveActionOption(option.RegistryOption):
> 102 +
> 103 + def __init__(self):
> 104 + super(ResolveActionOption, self).__init__(
> 105 + 'action', 'How to resolve the conflict.',
> 106 + value_switches=True,
> 107 + registry=resolve_action_registry)
> 108 +
> 109 +
>
>
> ^- This only seems useful if you were actually going to re-use the
> ResolveActionOption.
> I realize you have some tests for it, but it doesn't do anything beyond a
> standard RegistryOption.

You're right, I introduced it for tests only.
The alternative was to define it in tests only too which was more likely to break in the future.

I can reconsider later if you feel strongly about it but overall I think it makes things simpler.

>
> Were you thinking to have the '--auto' logic as part of the option object?

Hopefully not :)

>
>
> 168 +def resolve(tree, paths=None, ignore_misses=False, recursive=False,
> 169 + action='done'):
> ^- Having a top-level function in builtins.py to do command specific actions
> isn't particularly nice, though I realize this wasn't from you. Perhaps just a
> comment that it should be moved into a bzrlib.resolve* module?

Meh, that's in conflicts.py and I don't think creating a resolve.py module is right. resolve is an action on conflict objects.

It *could* arguably become part of the MutableTree API and get a @needs_tree_write_lock
decorator or become a method of ConflictList.

>
>
> 236 + def _do(self, action, tree):
> 237 + """Apply the specified action to the conflict.
> 238 +
> 239 + :param action: The method name to call.
> 240 +
> 241 + :param tree: The tree passed as a parameter to the method.
> 242 + """
> 243 + meth = getattr(self, action, None)
> 244 + if meth is None:
> 245 + raise NotImplementedError(self.__class__.__name__ + '.' + action)
> 246 + meth(tree)
>
> ^- To avoid namespace collisions, this would probably be better as:
>
> meth = getattr(self, 'action_' + action, None)
> ...

Good point. Fixed.

>
>
> === modified file 'bzrlib/tests/test_conflicts.py'
> 971 --- bzrlib/tests/test_conflicts.py 2010-01-04 14:56:59 +0000
> 972 +++ bzrlib/tests/test_conflicts.py 2010-02-04 13:06:32 +0000
> 973 @@ -21,8 +21,10 @@
> 974 bzrdir,
> 975 conflicts,
> 976 errors,
> 977 + option,
> 978 tests,
> 979 )
> 980 +from bzrlib.tests import script
>
> ^- script tests don't seem to be a great match for whitebox testing. And
> looking at the scripts themselves they certainly look like blackbox tests.
> (using commit to assert that things are ok.) Versus even an "assert*"
> function.

>
> The tests seem fine, but they look like they should belong in a
> blackbox/test_resolve.py module.

Right, they served as a test bed for shell-like tests and like most shell-like tests they should be rewritten :) I'll add a TODO for that and will address it shortly.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS'
2--- NEWS 2010-02-04 11:02:01 +0000
3+++ NEWS 2010-02-04 13:06:32 +0000
4@@ -19,6 +19,7 @@
5 New Features
6 ************
7
8+<<<<<<< TREE
9 * If the Apport crash-reporting tool is available, bzr crashes are now
10 stored into the ``/var/crash`` apport spool directory, and the user is
11 invited to report them to the developers from there, either
12@@ -31,6 +32,13 @@
13 * Stop sending apport crash files to ``.cache`` in the directory from
14 which ``bzr selftest`` was run. (Martin Pool, #422350)
15
16+=======
17+* Tree-shape conflicts can be resolved by providing ``--take-this`` and
18+ ``--take-other`` to the ``bzr resolve`` command. Just marking the conflict
19+ as resolved is still accessible via the ``--done`` default action.
20+ (Vincent Ladeuil)
21+
22+>>>>>>> MERGE-SOURCE
23 bzr 2.1.0 (not released yet)
24 ############################
25
26
27=== modified file 'bzrlib/conflicts.py'
28--- bzrlib/conflicts.py 2010-01-08 07:37:25 +0000
29+++ bzrlib/conflicts.py 2010-02-04 13:06:32 +0000
30@@ -1,4 +1,4 @@
31-# Copyright (C) 2005, 2007 Canonical Ltd
32+# Copyright (C) 2005, 2007, 2009, 2010 Canonical Ltd
33 #
34 # This program is free software; you can redistribute it and/or modify
35 # it under the terms of the GNU General Public License as published by
36@@ -14,12 +14,11 @@
37 # along with this program; if not, write to the Free Software
38 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
39
40-# TODO: Move this into builtins
41-
42 # TODO: 'bzr resolve' should accept a directory name and work from that
43 # point down
44
45 import os
46+import re
47
48 from bzrlib.lazy_import import lazy_import
49 lazy_import(globals(), """
50@@ -32,9 +31,14 @@
51 osutils,
52 rio,
53 trace,
54+ transform,
55+ workingtree,
56 )
57 """)
58-from bzrlib.option import Option
59+from bzrlib import (
60+ option,
61+ registry,
62+ )
63
64
65 CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
66@@ -55,14 +59,13 @@
67 Use bzr resolve when you have fixed a problem.
68 """
69 takes_options = [
70- Option('text',
71- help='List paths of files with text conflicts.'),
72+ option.Option('text',
73+ help='List paths of files with text conflicts.'),
74 ]
75 _see_also = ['resolve', 'conflict-types']
76
77 def run(self, text=False):
78- from bzrlib.workingtree import WorkingTree
79- wt = WorkingTree.open_containing(u'.')[0]
80+ wt = workingtree.WorkingTree.open_containing(u'.')[0]
81 for conflict in wt.conflicts():
82 if text:
83 if conflict.typestring != 'text conflict':
84@@ -72,6 +75,28 @@
85 self.outf.write(str(conflict) + '\n')
86
87
88+resolve_action_registry = registry.Registry()
89+
90+
91+resolve_action_registry.register(
92+ 'done', 'done', 'Marks the conflict as resolved' )
93+resolve_action_registry.register(
94+ 'take-this', 'take_this',
95+ 'Resolve the conflict preserving the version in the working tree' )
96+resolve_action_registry.register(
97+ 'take-other', 'take_other',
98+ 'Resolve the conflict taking the merged version into account' )
99+resolve_action_registry.default_key = 'done'
100+
101+class ResolveActionOption(option.RegistryOption):
102+
103+ def __init__(self):
104+ super(ResolveActionOption, self).__init__(
105+ 'action', 'How to resolve the conflict.',
106+ value_switches=True,
107+ registry=resolve_action_registry)
108+
109+
110 class cmd_resolve(commands.Command):
111 """Mark a conflict as resolved.
112
113@@ -87,20 +112,33 @@
114 aliases = ['resolved']
115 takes_args = ['file*']
116 takes_options = [
117- Option('all', help='Resolve all conflicts in this tree.'),
118+ option.Option('all', help='Resolve all conflicts in this tree.'),
119+ ResolveActionOption(),
120 ]
121 _see_also = ['conflicts']
122- def run(self, file_list=None, all=False):
123- from bzrlib.workingtree import WorkingTree
124+ def run(self, file_list=None, all=False, action=None):
125 if all:
126 if file_list:
127 raise errors.BzrCommandError("If --all is specified,"
128 " no FILE may be provided")
129- tree = WorkingTree.open_containing('.')[0]
130- resolve(tree)
131+ tree = workingtree.WorkingTree.open_containing('.')[0]
132+ if action is None:
133+ action = 'done'
134 else:
135 tree, file_list = builtins.tree_files(file_list)
136 if file_list is None:
137+ if action is None:
138+ # FIXME: There is a special case here related to the option
139+ # handling that could be clearer and easier to discover by
140+ # providing an --auto action (bug #344013 and #383396) and
141+ # make it mandatory instead of implicit and active only
142+ # when no file_list is provided -- vila 091229
143+ action = 'auto'
144+ else:
145+ if action is None:
146+ action = 'done'
147+ if action == 'auto':
148+ if file_list is None:
149 un_resolved, resolved = tree.auto_resolve()
150 if len(un_resolved) > 0:
151 trace.note('%d conflict(s) auto-resolved.', len(resolved))
152@@ -112,10 +150,16 @@
153 trace.note('All conflicts resolved.')
154 return 0
155 else:
156- resolve(tree, file_list)
157-
158-
159-def resolve(tree, paths=None, ignore_misses=False, recursive=False):
160+ # FIXME: This can never occur but the block above needs some
161+ # refactoring to transfer tree.auto_resolve() to
162+ # conflict.auto(tree) --vila 091242
163+ pass
164+ else:
165+ resolve(tree, file_list, action=action)
166+
167+
168+def resolve(tree, paths=None, ignore_misses=False, recursive=False,
169+ action='done'):
170 """Resolve some or all of the conflicts in a working tree.
171
172 :param paths: If None, resolve all conflicts. Otherwise, select only
173@@ -125,24 +169,29 @@
174 recursive commands like revert, this should be True. For commands
175 or applications wishing finer-grained control, like the resolve
176 command, this should be False.
177- :ignore_misses: If False, warnings will be printed if the supplied paths
178- do not have conflicts.
179+ :param ignore_misses: If False, warnings will be printed if the supplied
180+ paths do not have conflicts.
181+ :param action: How the conflict should be resolved,
182 """
183 tree.lock_tree_write()
184 try:
185 tree_conflicts = tree.conflicts()
186 if paths is None:
187 new_conflicts = ConflictList()
188- selected_conflicts = tree_conflicts
189+ to_process = tree_conflicts
190 else:
191- new_conflicts, selected_conflicts = \
192- tree_conflicts.select_conflicts(tree, paths, ignore_misses,
193- recursive)
194+ new_conflicts, to_process = tree_conflicts.select_conflicts(
195+ tree, paths, ignore_misses, recursive)
196+ for conflict in to_process:
197+ try:
198+ conflict._do(action, tree)
199+ conflict.cleanup(tree)
200+ except NotImplementedError:
201+ new_conflicts.append(conflict)
202 try:
203 tree.set_conflicts(new_conflicts)
204 except errors.UnsupportedOperation:
205 pass
206- selected_conflicts.remove_files(tree)
207 finally:
208 tree.unlock()
209
210@@ -237,12 +286,7 @@
211 for conflict in self:
212 if not conflict.has_files:
213 continue
214- for suffix in CONFLICT_SUFFIXES:
215- try:
216- osutils.delete_any(tree.abspath(conflict.path+suffix))
217- except OSError, e:
218- if e.errno != errno.ENOENT:
219- raise
220+ conflict.cleanup(tree)
221
222 def select_conflicts(self, tree, paths, ignore_misses=False,
223 recurse=False):
224@@ -301,6 +345,7 @@
225 class Conflict(object):
226 """Base class for all types of conflict"""
227
228+ # FIXME: cleanup should take care of that ? -- vila 091229
229 has_files = False
230
231 def __init__(self, path, file_id=None):
232@@ -355,6 +400,32 @@
233 else:
234 return None, conflict.typestring
235
236+ def _do(self, action, tree):
237+ """Apply the specified action to the conflict.
238+
239+ :param action: The method name to call.
240+
241+ :param tree: The tree passed as a parameter to the method.
242+ """
243+ meth = getattr(self, action, None)
244+ if meth is None:
245+ raise NotImplementedError(self.__class__.__name__ + '.' + action)
246+ meth(tree)
247+
248+ def cleanup(self, tree):
249+ raise NotImplementedError(self.cleanup)
250+
251+ def done(self, tree):
252+ """Mark the conflict as solved once it has been handled."""
253+ # This method does nothing but simplifies the design of upper levels.
254+ pass
255+
256+ def take_this(self, tree):
257+ raise NotImplementedError(self.take_this)
258+
259+ def take_other(self, tree):
260+ raise NotImplementedError(self.take_other)
261+
262
263 class PathConflict(Conflict):
264 """A conflict was encountered merging file paths"""
265@@ -364,6 +435,7 @@
266 format = 'Path conflict: %(path)s / %(conflict_path)s'
267
268 rformat = '%(class)s(%(path)r, %(conflict_path)r, %(file_id)r)'
269+
270 def __init__(self, path, conflict_path=None, file_id=None):
271 Conflict.__init__(self, path, file_id)
272 self.conflict_path = conflict_path
273@@ -374,6 +446,17 @@
274 s.add('conflict_path', self.conflict_path)
275 return s
276
277+ def cleanup(self, tree):
278+ # No additional files have been generated here
279+ pass
280+
281+ def take_this(self, tree):
282+ tree.rename_one(self.conflict_path, self.path)
283+
284+ def take_other(self, tree):
285+ # just acccept bzr proposal
286+ pass
287+
288
289 class ContentsConflict(PathConflict):
290 """The files are of different types, or not present"""
291@@ -384,7 +467,30 @@
292
293 format = 'Contents conflict in %(path)s'
294
295-
296+ def cleanup(self, tree):
297+ for suffix in ('.BASE', '.OTHER'):
298+ try:
299+ osutils.delete_any(tree.abspath(self.path + suffix))
300+ except OSError, e:
301+ if e.errno != errno.ENOENT:
302+ raise
303+
304+ # FIXME: I smell something weird here and it seems we should be able to be
305+ # more coherent with some other conflict ? bzr *did* a choice there but
306+ # neither take_this nor take_other reflect that... -- vila 091224
307+ def take_this(self, tree):
308+ tree.remove([self.path + '.OTHER'], force=True, keep_files=False)
309+
310+ def take_other(self, tree):
311+ tree.remove([self.path], force=True, keep_files=False)
312+
313+
314+
315+# FIXME: TextConflict is about a single file-id, there never is a conflict_path
316+# attribute so we shouldn't inherit from PathConflict but simply from Conflict
317+
318+# TODO: There should be a base revid attribute to better inform the user about
319+# how the conflicts were generated.
320 class TextConflict(PathConflict):
321 """The merge algorithm could not resolve all differences encountered."""
322
323@@ -394,6 +500,14 @@
324
325 format = 'Text conflict in %(path)s'
326
327+ def cleanup(self, tree):
328+ for suffix in CONFLICT_SUFFIXES:
329+ try:
330+ osutils.delete_any(tree.abspath(self.path+suffix))
331+ except OSError, e:
332+ if e.errno != errno.ENOENT:
333+ raise
334+
335
336 class HandledConflict(Conflict):
337 """A path problem that has been provisionally resolved.
338@@ -414,6 +528,10 @@
339 s.add('action', self.action)
340 return s
341
342+ def cleanup(self, tree):
343+ """Nothing to cleanup."""
344+ pass
345+
346
347 class HandledPathConflict(HandledConflict):
348 """A provisionally-resolved path problem involving two paths.
349@@ -460,15 +578,22 @@
350
351 format = 'Conflict adding file %(conflict_path)s. %(action)s %(path)s.'
352
353+ def take_this(self, tree):
354+ tree.remove([self.conflict_path], force=True, keep_files=False)
355+ tree.rename_one(self.path, self.conflict_path)
356+
357+ def take_other(self, tree):
358+ tree.remove([self.path], force=True, keep_files=False)
359+
360
361 class ParentLoop(HandledPathConflict):
362 """An attempt to create an infinitely-looping directory structure.
363 This is rare, but can be produced like so:
364
365 tree A:
366- mv foo/bar
367+ mv foo bar
368 tree B:
369- mv bar/foo
370+ mv bar foo
371 merge A and B
372 """
373
374@@ -476,6 +601,27 @@
375
376 format = 'Conflict moving %(conflict_path)s into %(path)s. %(action)s.'
377
378+ def take_this(self, tree):
379+ # just acccept bzr proposal
380+ pass
381+
382+ def take_other(self, tree):
383+ # FIXME: We shouldn't have to manipulate so many paths here (and there
384+ # is probably a bug or two...)
385+ base_path = osutils.basename(self.path)
386+ conflict_base_path = osutils.basename(self.conflict_path)
387+ tt = transform.TreeTransform(tree)
388+ try:
389+ p_tid = tt.trans_id_file_id(self.file_id)
390+ parent_tid = tt.get_tree_parent(p_tid)
391+ cp_tid = tt.trans_id_file_id(self.conflict_file_id)
392+ cparent_tid = tt.get_tree_parent(cp_tid)
393+ tt.adjust_path(base_path, cparent_tid, cp_tid)
394+ tt.adjust_path(conflict_base_path, parent_tid, p_tid)
395+ tt.apply()
396+ finally:
397+ tt.finalize()
398+
399
400 class UnversionedParent(HandledConflict):
401 """An attempt to version a file whose parent directory is not versioned.
402@@ -488,18 +634,34 @@
403 format = 'Conflict because %(path)s is not versioned, but has versioned'\
404 ' children. %(action)s.'
405
406+ # FIXME: We silently do nothing to make tests pass, but most probably the
407+ # conflict shouldn't exist (the long story is that the conflict is
408+ # generated with another one that can be resolved properly) -- vila 091224
409+ def take_this(self, tree):
410+ pass
411+
412+ def take_other(self, tree):
413+ pass
414+
415
416 class MissingParent(HandledConflict):
417 """An attempt to add files to a directory that is not present.
418 Typically, the result of a merge where THIS deleted the directory and
419 the OTHER added a file to it.
420- See also: DeletingParent (same situation, reversed THIS and OTHER)
421+ See also: DeletingParent (same situation, THIS and OTHER reversed)
422 """
423
424 typestring = 'missing parent'
425
426 format = 'Conflict adding files to %(path)s. %(action)s.'
427
428+ def take_this(self, tree):
429+ tree.remove([self.path], force=True, keep_files=False)
430+
431+ def take_other(self, tree):
432+ # just acccept bzr proposal
433+ pass
434+
435
436 class DeletingParent(HandledConflict):
437 """An attempt to add files to a directory that is not present.
438@@ -512,9 +674,19 @@
439 format = "Conflict: can't delete %(path)s because it is not empty. "\
440 "%(action)s."
441
442+ # FIXME: It's a bit strange that the default action is not coherent with
443+ # MissingParent from the *user* pov.
444+
445+ def take_this(self, tree):
446+ # just acccept bzr proposal
447+ pass
448+
449+ def take_other(self, tree):
450+ tree.remove([self.path], force=True, keep_files=False)
451+
452
453 class NonDirectoryParent(HandledConflict):
454- """An attempt to add files to a directory that is not a director or
455+ """An attempt to add files to a directory that is not a directory or
456 an attempt to change the kind of a directory with files.
457 """
458
459@@ -523,6 +695,27 @@
460 format = "Conflict: %(path)s is not a directory, but has files in it."\
461 " %(action)s."
462
463+ # FIXME: .OTHER should be used instead of .new when the conflict is created
464+
465+ def take_this(self, tree):
466+ # FIXME: we should preserve that path when the conflict is generated !
467+ if self.path.endswith('.new'):
468+ conflict_path = self.path[:-(len('.new'))]
469+ tree.remove([self.path], force=True, keep_files=False)
470+ tree.add(conflict_path)
471+ else:
472+ raise NotImplementedError(self.take_this)
473+
474+ def take_other(self, tree):
475+ # FIXME: we should preserve that path when the conflict is generated !
476+ if self.path.endswith('.new'):
477+ conflict_path = self.path[:-(len('.new'))]
478+ tree.remove([conflict_path], force=True, keep_files=False)
479+ tree.rename_one(self.path, conflict_path)
480+ else:
481+ raise NotImplementedError(self.take_other)
482+
483+
484 ctype = {}
485
486
487@@ -532,7 +725,6 @@
488 for conflict_type in conflict_types:
489 ctype[conflict_type.typestring] = conflict_type
490
491-
492 register_types(ContentsConflict, TextConflict, PathConflict, DuplicateID,
493 DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,
494 DeletingParent, NonDirectoryParent)
495
496=== modified file 'bzrlib/help_topics/en/conflict-types.txt'
497--- bzrlib/help_topics/en/conflict-types.txt 2010-01-08 07:37:25 +0000
498+++ bzrlib/help_topics/en/conflict-types.txt 2010-02-04 13:06:32 +0000
499@@ -3,16 +3,59 @@
500
501 Some operations, like merge, revert and pull, modify the contents of your
502 working tree. These modifications are programmatically generated, and so they
503-may conflict with the current state of your working tree. Many kinds of changes
504-can be combined programmatically, but sometimes only a human can determine the
505-right thing to do. When this happens Bazaar will inform you that there is a
506-conflict and then ask you to resolve it. The command to tell Bazaar a conflict
507-is resolved is ``resolve``, but you must perform some action before you can do
508-this.
509+may conflict with the current state of your working tree.
510+
511+When conflicts are present in your working tree (as shown by ``bzr
512+conflicts``), you should resolve them and then inform bzr that the conflicts
513+have been resolved.
514+
515+Resolving conflicts is sometimes not obvious. Either because the user that
516+should resolve them is not the one responsible for their occurrence, as is the
517+case when merging other people's work or because some conflicts are presented
518+in a way that is not easy to understand.
519+
520+Bazaar tries to avoid conflicts ; its aim is to ask you to resolve the
521+conflict if and only if there's an actual conceptual conflict in the source
522+tree. Because Bazaar doesn't understand the real meaning of the files being
523+versioned, it can, when faced with ambiguities, fall short in either direction
524+trying to resolve the conflict itself. Many kinds of changes can be combined
525+programmatically, but sometimes only a human can determine the right thing to
526+do.
527+
528+When Bazaar generates a conflict, it adds information into the working tree to
529+present the conflicting versions, and it's up to you to find the correct
530+resolution.
531+
532+Whatever the conflict is, resolving it is roughly done in two steps::
533+
534+ * modify the working tree content so that the conflicted item is now in the
535+ state you want to keep,
536+
537+ * inform Bazaar that the conflict is now solved and ask to cleanup any
538+ remaining generated information (``bzr resolve <item>``).
539+
540+For most conflict types, there are some obvious ways to modify the working
541+tree and put it into the desired state. For some types of conflicts, Bazaar
542+itself already made a choice, when possible.
543+
544+Yet, whether Bazaar makes a choice or not, there are some other simple but
545+different ways to resolve the conflict.
546
547 Each type of conflict is explained below, and the action which must be done to
548 resolve the conflict is outlined.
549
550+Various actions are available depending on the kind of conflict, for some of
551+these actions, Bazaar can provide some help. In the end you should at least
552+inform Bazaar that you're done with the conflict with::
553+
554+ ``bzr resolve FILE --action=done'
555+
556+Note that this is the default action when a single file is involved so you can
557+simply use::
558+
559+ ``bzr resolve FILE``
560+
561+See ``bzr help resolve`` for more details.
562
563 Text conflicts
564 --------------
565@@ -54,8 +97,12 @@
566 you are done editing, the file should look like it never had a conflict, and be
567 ready to commit.
568
569-When you have resolved text conflicts, just run "bzr resolve", and Bazaar will
570-auto-detect which conflicts you have resolved.
571+When you have resolved text conflicts, just run ``bzr resolve --auto``, and
572+Bazaar will auto-detect which conflicts you have resolved.
573+
574+When the conflict is resolved, Bazaar deletes the previously generated
575+``.BASE``, ``.THIS`` and ``.OTHER`` files if they are still present in the
576+working tree.
577
578 Content conflicts
579 -----------------
580@@ -64,20 +111,29 @@
581
582 Contents conflict in FILE
583
584-This conflict happens when there are conflicting changes in the target tree and
585-the merge source, but the conflicted items are not text files. They may be
586-binary files, or symlinks, or directories. It can even happen with files that
587-are deleted on one side, and modified on the other.
588+This conflict happens when there are conflicting changes in the working tree
589+and the merge source, but the conflicted items are not text files. They may
590+be binary files, or symlinks, or directories. It can even happen with files
591+that are deleted on one side, and modified on the other.
592
593 Like text conflicts, Bazaar will emit THIS, OTHER and BASE files. (They may be
594 regular files, symlinks or directories). But it will not include a "main copy"
595 of the file with herringbone conflict markers. It will appear that the "main
596 copy" has been renamed to THIS or OTHER.
597
598-To resolve this, use "bzr mv" to rename the file back to its normal name, and
599-combine the changes manually. When you are satisfied, run "bzr resolve
600-FILE". Bazaar cannot auto-detect when conflicts of this kind have been
601-resolved.
602+To resolve that kind of conflict, you should rebuild FILE from either version
603+or a combination of both.
604+
605+``bzr resolve`` recognizes the following actions:
606+
607+ * ``--action=take-this`` will issue ``bzr mv FILE.THIS FILE``,
608+ * ``--action=take-other`` will issue ``bzr mv FILE.OTHER FILE``,
609+ * ``--action=done`` will just mark the conflict as resolved.
610+
611+Any action will also delete the previously generated ``.BASE``, ``.THIS`` and
612+``.OTHER`` files if they are still present in the working tree.
613+
614+Bazaar cannot auto-detect when conflicts of this kind have been resolved.
615
616 Tag conflicts
617 -------------
618@@ -95,12 +151,13 @@
619 To resolve the conflict, you must apply the correct tags to either the target
620 branch or the source branch as appropriate. Use "bzr tags --show-ids -d
621 SOURCE_URL" to see the tags in the source branch. If you want to make the
622-target branch's tags match the source branch, then in the target branch do
623+target branch's tags match the source branch, then in the target branch do
624 ``bzr tag --force -r revid:REVISION_ID CONFLICTING_TAG`` for each of the
625 CONFLICTING_TAGs, where REVISION_ID comes from the list of tags in the source
626-branch. You need not call "bzr resolve" after doing this. To resolve in favor of the target branch, you need to similarly use ``tag --force`` in the source
627-branch. (Note that pulling or pushing using --overwrite will overwrite all
628-tags as well.)
629+branch. You need not call "bzr resolve" after doing this. To resolve in
630+favor of the target branch, you need to similarly use ``tag --force`` in the
631+source branch. (Note that pulling or pushing using --overwrite will overwrite
632+all tags as well.)
633
634 Duplicate paths
635 ---------------
636@@ -110,10 +167,20 @@
637 Conflict adding file FILE. Moved existing file to FILE.moved.
638
639 Sometimes Bazaar will attempt to create a file using a pathname that has
640-already been used. The existing file will be renamed to "FILE.moved". If
641-you wish, you can rename either one of these files, or combine their contents.
642-When you are satisfied, you can run "bzr resolve FILE" to mark the conflict as
643-resolved.
644+already been used. The existing file will be renamed to "FILE.moved".
645+
646+To resolve that kind of conflict, you should rebuild FILE from either version
647+or a combination of both.
648+
649+``bzr resolve`` recognizes the following actions::
650+
651+ * ``--action=take-this`` will issue ``bzr rm FILE ; bzr mv FILE.moved FILE``,
652+ * ``--action=take-other`` will issue ``bzr rm FILE.moved``,
653+ * ``--action=done`` will just mark the conflict as resolved.
654+
655+Note that you must get rid of FILE.moved before using ``--action=done``.
656+
657+Bazaar cannot auto-detect when conflicts of this kind have been resolved.
658
659 Unversioned parent
660 ------------------
661@@ -137,26 +204,47 @@
662
663 Conflict adding files to FILE. Created directory.
664
665-This happens when a file has been deleted in the target, but has new children
666-in the source. This is similar to the "unversioned parent" conflict, except
667-that the parent directory does not *exist*, instead of just being unversioned.
668-In this situation, Bazaar will create the missing parent. Resolving this issue
669-depends very much on the particular scenario. You may wish to rename or delete
670-either the file or the directory. When you are satisfied, you can run "bzr
671-resolve FILE" to mark the conflict as resolved.
672+This happens when a directory has been deleted in the target, but has new
673+children in the source. This is similar to the "unversioned parent" conflict,
674+except that the parent directory does not *exist*, instead of just being
675+unversioned. In this situation, Bazaar will create the missing parent.
676+Resolving this issue depends very much on the particular scenario.
677+
678+To resolve that kind of conflict, you should either remove or rename the
679+children or the directory or a combination of both.
680+
681+``bzr resolve`` recognizes the following actions::
682+
683+ * ``--action=take-this`` will issue ``bzr rm directory``
684+ including the children,
685+ * ``--action=take-other`` will acknowledge Bazaar choice to keep
686+ the children and restoring the directory,
687+ * ``--action=done`` will just mark the conflict as resolved.
688+
689+Bazaar cannot auto-detect when conflicts of this kind have been resolved.
690
691 Deleting parent
692 ---------------
693
694 Typical message::
695
696- Conflict: can't delete FILE because it is not empty. Not deleting.
697+ Conflict: can't delete DIR because it is not empty. Not deleting.
698
699 This is the opposite of "missing parent". A directory is deleted in the
700 source, but has new children in the target. Bazaar will retain the directory.
701-Resolving this issue depends very much on the particular scenario. You may
702-wish to rename or delete either the file or the directory. When you are
703-satisfied, you can run "bzr resolve FILE" to mark the conflict as resolved.
704+Resolving this issue depends very much on the particular scenario.
705+
706+To resolve that kind of conflict, you should either remove or rename the
707+children or the directory or a combination of both.
708+
709+``bzr resolve`` recognizes the following actions::
710+
711+ * ``--action=take-this`` will acknowledge Bazaar choice to keep the directory,
712+ * ``--action=take-other`` will issue ``bzr rm directory`` including the
713+ children,
714+ * ``--action=done`` will just mark the conflict as resolved.
715+
716+Bazaar cannot auto-detect when conflicts of this kind have been resolved.
717
718 Path conflict
719 -------------
720@@ -166,9 +254,19 @@
721 Path conflict: PATH1 / PATH2
722
723 This happens when the source and target have each modified the name or parent
724-directory of a file. Bazaar will use the path elements from the source. You
725-can rename the file, and once you have, run "bzr resolve FILE" to mark the
726-conflict as resolved.
727+directory of a file. Bazaar will use the path elements from the source.
728+
729+To resolve that kind of conflict, you just have to decide what name should be
730+retained for the file involved.
731+
732+``bzr resolve`` recognizes the following actions::
733+
734+ * ``--action=take-this`` will revert Bazaar choice and keep ``PATH1`` by
735+ issuing ``bzr mv PATH2 PATH1``,
736+ * ``--action=take-other`` will acknowledge Bazaar choice of keeping ``PATH2``,
737+ * ``--action=done`` will just mark the conflict as resolved.
738+
739+Bazaar cannot auto-detect when conflicts of this kind have been resolved.
740
741 Parent loop
742 -----------
743@@ -179,45 +277,67 @@
744
745 This happens when the source and the target have each moved directories, so
746 that, if the change could be applied, a directory would be contained by itself.
747-For example::
748+For example:
749
750 $ bzr init
751- $ bzr mkdir a
752- $ bzr mkdir b
753+ $ bzr mkdir white
754+ $ bzr mkdir black
755 $ bzr commit -m "BASE"
756 $ bzr branch . ../other
757- $ bzr mv a b
758+ $ bzr mv white black
759 $ bzr commit -m "THIS"
760- $ bzr mv ../other/b ../other/a
761+ $ bzr mv ../other/black ../other/white
762 $ bzr commit ../other -m "OTHER"
763 $ bzr merge ../other
764
765-In this situation, Bazaar will cancel the move, and leave "a" in "b".
766-You can rename the directories if you like, and once you have, run "bzr resolve
767-FILE" to mark the conflict as resolved.
768+In this situation, Bazaar will cancel the move, and leave ``white`` in
769+``black``. To resolve that kind of conflict, you just have to decide what
770+name should be retained for the directories involved.
771+
772+``bzr resolve`` recognizes the following actions::
773+
774+ * ``--action=take-this`` will acknowledge Bazaar choice of leaving ``white``
775+ in ``black``,
776+ * ``--action=take-other`` will revert Bazaar choice and move ``black`` in
777+ ``white`` by issuing ``bzr mv black/white white ; bzr mv black white``,
778+ * ``--action=done`` will just mark the conflict as resolved.
779+
780+Bazaar cannot auto-detect when conflicts of this kind have been resolved.
781
782 Non-directory parent
783 --------------------
784
785 Typical message::
786
787- Conflict: FILE.new is not a directory, but has files in it.
788+ Conflict: foo.new is not a directory, but has files in it.
789 Created directory.
790
791 This happens when one side has added files to a directory, and the other side
792-has changed the directory into a file or symlink. For example::
793+has changed the directory into a file or symlink. For example:
794
795 $ bzr init
796- $ bzr mkdir a
797+ $ bzr mkdir foo
798 $ bzr commit -m "BASE"
799 $ bzr branch . ../other
800- $ rmdir a
801- $ touch a
802+ $ rmdir foo
803+ $ touch foo
804 $ bzr commit -m "THIS"
805- $ bzr mkdir ../other/a/b
806+ $ bzr mkdir ../other/foo/bar
807 $ bzr commit ../other -m "OTHER"
808 $ bzr merge ../other
809
810+To resolve that kind of conflict, you have to decide what name should be
811+retained for the file, directory or symlink involved.
812+
813+``bzr resolve`` recognizes the following actions::
814+
815+ * ``--action=take-this`` will issue ``bzr rm --force foo.new`` and
816+ ``bzr add foo``,
817+ * ``--action=take-other`` will issue ``bzr rm --force foo`` and
818+ ``bzr mv foo.new foo``,
819+ * ``--action=done`` will just mark the conflict as resolved.
820+
821+Bazaar cannot auto-detect when conflicts of this kind have been resolved.
822
823 MalformedTransform
824 ------------------
825
826=== added directory 'bzrlib/help_topics/es'
827=== removed directory 'bzrlib/help_topics/es'
828=== renamed file 'bzrlib/help_topics/es/conflicts.txt' => 'bzrlib/help_topics/es/conflict-types.txt'
829=== modified file 'bzrlib/option.py'
830--- bzrlib/option.py 2009-08-18 08:10:44 +0000
831+++ bzrlib/option.py 2010-02-04 13:06:31 +0000
832@@ -361,7 +361,7 @@
833
834 name, help, value_switches and enum_switch are passed to the
835 RegistryOption constructor. Any other keyword arguments are treated
836- as values for the option, and they value is treated as the help.
837+ as values for the option, and their value is treated as the help.
838 """
839 reg = _mod_registry.Registry()
840 for name, switch_help in kwargs.iteritems():
841
842=== modified file 'bzrlib/tests/blackbox/test_conflicts.py'
843--- bzrlib/tests/blackbox/test_conflicts.py 2009-03-23 14:59:43 +0000
844+++ bzrlib/tests/blackbox/test_conflicts.py 2010-02-04 13:06:32 +0000
845@@ -1,4 +1,4 @@
846-# Copyright (C) 2006 Canonical Ltd
847+# Copyright (C) 2006, 2009, 2010 Canonical Ltd
848 #
849 # This program is free software; you can redistribute it and/or modify
850 # it under the terms of the GNU General Public License as published by
851@@ -14,21 +14,22 @@
852 # along with this program; if not, write to the Free Software
853 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
854
855-import os
856-
857 from bzrlib import (
858- conflicts
859+ conflicts,
860+ tests,
861+ workingtree,
862 )
863-from bzrlib.workingtree import WorkingTree
864-from bzrlib.tests.blackbox import ExternalBase
865
866 # FIXME: These don't really look at the output of the conflict commands, just
867 # the number of lines - there should be more examination.
868
869-class TestConflicts(ExternalBase):
870+class TestConflictsBase(tests.TestCaseWithTransport):
871
872 def setUp(self):
873- super(ExternalBase, self).setUp()
874+ super(TestConflictsBase, self).setUp()
875+ self.make_tree_with_conflicts()
876+
877+ def make_tree_with_conflicts(self):
878 a_tree = self.make_branch_and_tree('a')
879 self.build_tree_contents([
880 ('a/myfile', 'contentsa\n'),
881@@ -53,45 +54,48 @@
882 a_tree.rename_one('mydir', 'mydir3')
883 a_tree.commit(message='change')
884 a_tree.merge_from_branch(b_tree.branch)
885- os.chdir('a')
886+
887+ def run_bzr(self, cmd, working_dir='a', **kwargs):
888+ return super(TestConflictsBase, self).run_bzr(
889+ cmd, working_dir=working_dir, **kwargs)
890+
891+
892+class TestConflicts(TestConflictsBase):
893
894 def test_conflicts(self):
895- conflicts, errs = self.run_bzr('conflicts')
896- self.assertEqual(3, len(conflicts.splitlines()))
897+ out, err = self.run_bzr('conflicts')
898+ self.assertEqual(3, len(out.splitlines()))
899
900 def test_conflicts_text(self):
901- conflicts = self.run_bzr('conflicts --text')[0].splitlines()
902- self.assertEqual(['my_other_file', 'myfile'], conflicts)
903+ out, err = self.run_bzr('conflicts --text')
904+ self.assertEqual(['my_other_file', 'myfile'], out.splitlines())
905+
906+
907+class TestResolve(TestConflictsBase):
908
909 def test_resolve(self):
910 self.run_bzr('resolve myfile')
911- conflicts, errs = self.run_bzr('conflicts')
912- self.assertEqual(2, len(conflicts.splitlines()))
913+ out, err = self.run_bzr('conflicts')
914+ self.assertEqual(2, len(out.splitlines()))
915 self.run_bzr('resolve my_other_file')
916 self.run_bzr('resolve mydir2')
917- conflicts, errs = self.run_bzr('conflicts')
918- self.assertEqual(len(conflicts.splitlines()), 0)
919+ out, err = self.run_bzr('conflicts')
920+ self.assertEqual(0, len(out.splitlines()))
921
922 def test_resolve_all(self):
923 self.run_bzr('resolve --all')
924- conflicts, errs = self.run_bzr('conflicts')
925- self.assertEqual(len(conflicts.splitlines()), 0)
926+ out, err = self.run_bzr('conflicts')
927+ self.assertEqual(0, len(out.splitlines()))
928
929 def test_resolve_in_subdir(self):
930 """resolve when run from subdirectory should handle relative paths"""
931- orig_dir = os.getcwdu()
932- try:
933- os.mkdir("subdir")
934- os.chdir("subdir")
935- self.run_bzr("resolve ../myfile")
936- os.chdir("../../b")
937- self.run_bzr("resolve ../a/myfile")
938- wt = WorkingTree.open_containing('.')[0]
939- conflicts = wt.conflicts()
940- if not conflicts.is_empty():
941- self.fail("tree still contains conflicts: %r" % conflicts)
942- finally:
943- os.chdir(orig_dir)
944+ self.build_tree(["a/subdir/"])
945+ self.run_bzr("resolve ../myfile", working_dir='a/subdir')
946+ self.run_bzr("resolve ../a/myfile", working_dir='b')
947+ wt = workingtree.WorkingTree.open_containing('b')[0]
948+ conflicts = wt.conflicts()
949+ self.assertEqual(True, conflicts.is_empty(),
950+ "tree still contains conflicts: %r" % conflicts)
951
952 def test_auto_resolve(self):
953 """Text conflicts can be resolved automatically"""
954@@ -102,11 +106,10 @@
955 self.assertEqual(tree.kind('file_id'), 'file')
956 file_conflict = conflicts.TextConflict('file', file_id='file_id')
957 tree.set_conflicts(conflicts.ConflictList([file_conflict]))
958- os.chdir('tree')
959- note = self.run_bzr('resolve', retcode=1)[1]
960+ note = self.run_bzr('resolve', retcode=1, working_dir='tree')[1]
961 self.assertContainsRe(note, '0 conflict\\(s\\) auto-resolved.')
962 self.assertContainsRe(note,
963 'Remaining conflicts:\nText conflict in file')
964- self.build_tree_contents([('file', 'a\n')])
965- note = self.run_bzr('resolve')[1]
966+ self.build_tree_contents([('tree/file', 'a\n')])
967+ note = self.run_bzr('resolve', working_dir='tree')[1]
968 self.assertContainsRe(note, 'All conflicts resolved.')
969
970=== modified file 'bzrlib/tests/test_conflicts.py'
971--- bzrlib/tests/test_conflicts.py 2010-01-04 14:56:59 +0000
972+++ bzrlib/tests/test_conflicts.py 2010-02-04 13:06:32 +0000
973@@ -21,8 +21,10 @@
974 bzrdir,
975 conflicts,
976 errors,
977+ option,
978 tests,
979 )
980+from bzrlib.tests import script
981
982
983 # TODO: Test commit with some added, and added-but-missing files
984@@ -171,3 +173,504 @@
985 if 'conflict_file_id' in stanza:
986 self.assertStartsWith(stanza['conflict_file_id'], u'\xeed')
987
988+
989+# FIXME: Tests missing for DuplicateID conflict type
990+class TestResolveConflicts(script.TestCaseWithTransportAndScript):
991+
992+ preamble = None # The setup script set by daughter classes
993+
994+ def setUp(self):
995+ super(TestResolveConflicts, self).setUp()
996+ self.run_script(self.preamble)
997+
998+
999+class TestResolveTextConflicts(TestResolveConflicts):
1000+ # TBC
1001+ pass
1002+
1003+
1004+class TestResolveContentConflicts(TestResolveConflicts):
1005+
1006+ # FIXME: We need to add the reverse case (delete in trunk, modify in
1007+ # branch) but that could wait until the resolution mechanism is implemented.
1008+
1009+ preamble = """
1010+$ bzr init trunk
1011+$ cd trunk
1012+$ echo 'trunk content' >file
1013+$ bzr add file
1014+$ bzr commit -m 'Create trunk'
1015+
1016+$ bzr branch . ../branch
1017+$ cd ../branch
1018+$ bzr rm file
1019+$ bzr commit -m 'Delete file'
1020+
1021+$ cd ../trunk
1022+$ echo 'more content' >>file
1023+$ bzr commit -m 'Modify file'
1024+
1025+$ cd ../branch
1026+$ bzr merge ../trunk
1027+2>+N file.OTHER
1028+2>Contents conflict in file
1029+2>1 conflicts encountered.
1030+"""
1031+
1032+ def test_take_this(self):
1033+ self.run_script("""
1034+$ bzr rm file.OTHER --force # a simple rm file.OTHER is valid too
1035+$ bzr resolve file
1036+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1037+""")
1038+
1039+ def test_take_other(self):
1040+ self.run_script("""
1041+$ bzr mv file.OTHER file
1042+$ bzr resolve file
1043+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1044+""")
1045+
1046+ def test_resolve_taking_this(self):
1047+ self.run_script("""
1048+$ bzr resolve --take-this file
1049+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1050+""")
1051+
1052+ def test_resolve_taking_other(self):
1053+ self.run_script("""
1054+$ bzr resolve --take-other file
1055+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1056+""")
1057+
1058+
1059+class TestResolveDuplicateEntry(TestResolveConflicts):
1060+
1061+ preamble = """
1062+$ bzr init trunk
1063+$ cd trunk
1064+$ echo 'trunk content' >file
1065+$ bzr add file
1066+$ bzr commit -m 'Create trunk'
1067+$ echo 'trunk content too' >file2
1068+$ bzr add file2
1069+$ bzr commit -m 'Add file2 in trunk'
1070+
1071+$ bzr branch . -r 1 ../branch
1072+$ cd ../branch
1073+$ echo 'branch content' >file2
1074+$ bzr add file2
1075+$ bzr commit -m 'Add file2 in branch'
1076+
1077+$ bzr merge ../trunk
1078+2>+N file2
1079+2>R file2 => file2.moved
1080+2>Conflict adding file file2. Moved existing file to file2.moved.
1081+2>1 conflicts encountered.
1082+"""
1083+
1084+ def test_keep_this(self):
1085+ self.run_script("""
1086+$ bzr rm file2 --force
1087+$ bzr mv file2.moved file2
1088+$ bzr resolve file2
1089+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1090+""")
1091+
1092+ def test_keep_other(self):
1093+ self.failIfExists('branch/file2.moved')
1094+ self.run_script("""
1095+$ bzr rm file2.moved --force
1096+$ bzr resolve file2
1097+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1098+""")
1099+ self.failIfExists('branch/file2.moved')
1100+
1101+ def test_resolve_taking_this(self):
1102+ self.run_script("""
1103+$ bzr resolve --take-this file2
1104+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1105+""")
1106+
1107+ def test_resolve_taking_other(self):
1108+ self.run_script("""
1109+$ bzr resolve --take-other file2
1110+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1111+""")
1112+
1113+
1114+class TestResolveUnversionedParent(TestResolveConflicts):
1115+
1116+ # FIXME: Add the reverse tests: dir deleted in trunk, file added in branch
1117+
1118+ # FIXME: While this *creates* UnversionedParent conflicts, this really only
1119+ # tests MissingParent resolution :-/
1120+ preamble = """
1121+$ bzr init trunk
1122+$ cd trunk
1123+$ mkdir dir
1124+$ bzr add dir
1125+$ bzr commit -m 'Create trunk'
1126+$ echo 'trunk content' >dir/file
1127+$ bzr add dir/file
1128+$ bzr commit -m 'Add dir/file in trunk'
1129+
1130+$ bzr branch . -r 1 ../branch
1131+$ cd ../branch
1132+$ bzr rm dir
1133+$ bzr commit -m 'Remove dir in branch'
1134+
1135+$ bzr merge ../trunk
1136+2>+N dir/
1137+2>+N dir/file
1138+2>Conflict adding files to dir. Created directory.
1139+2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
1140+2>2 conflicts encountered.
1141+"""
1142+
1143+ def test_take_this(self):
1144+ self.run_script("""
1145+$ bzr rm dir --force
1146+$ bzr resolve dir
1147+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1148+""")
1149+
1150+ def test_take_other(self):
1151+ self.run_script("""
1152+$ bzr resolve dir
1153+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1154+""")
1155+
1156+
1157+class TestResolveMissingParent(TestResolveConflicts):
1158+
1159+ preamble = """
1160+$ bzr init trunk
1161+$ cd trunk
1162+$ mkdir dir
1163+$ echo 'trunk content' >dir/file
1164+$ bzr add
1165+$ bzr commit -m 'Create trunk'
1166+$ echo 'trunk content' >dir/file2
1167+$ bzr add dir/file2
1168+$ bzr commit -m 'Add dir/file2 in branch'
1169+
1170+$ bzr branch . -r 1 ../branch
1171+$ cd ../branch
1172+$ bzr rm dir/file --force
1173+$ bzr rm dir
1174+$ bzr commit -m 'Remove dir/file'
1175+
1176+$ bzr merge ../trunk
1177+2>+N dir/
1178+2>+N dir/file2
1179+2>Conflict adding files to dir. Created directory.
1180+2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
1181+2>2 conflicts encountered.
1182+"""
1183+
1184+ def test_keep_them_all(self):
1185+ self.run_script("""
1186+$ bzr resolve dir
1187+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1188+""")
1189+
1190+ def test_adopt_child(self):
1191+ self.run_script("""
1192+$ bzr mv dir/file2 file2
1193+$ bzr rm dir --force
1194+$ bzr resolve dir
1195+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1196+""")
1197+
1198+ def test_kill_them_all(self):
1199+ self.run_script("""
1200+$ bzr rm dir --force
1201+$ bzr resolve dir
1202+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1203+""")
1204+
1205+ def test_resolve_taking_this(self):
1206+ self.run_script("""
1207+$ bzr resolve --take-this dir
1208+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1209+""")
1210+
1211+ def test_resolve_taking_other(self):
1212+ self.run_script("""
1213+$ bzr resolve --take-other dir
1214+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1215+""")
1216+
1217+
1218+class TestResolveDeletingParent(TestResolveConflicts):
1219+
1220+ preamble = """
1221+$ bzr init trunk
1222+$ cd trunk
1223+$ mkdir dir
1224+$ echo 'trunk content' >dir/file
1225+$ bzr add
1226+$ bzr commit -m 'Create trunk'
1227+$ bzr rm dir/file --force
1228+$ bzr rm dir --force
1229+$ bzr commit -m 'Remove dir/file'
1230+
1231+$ bzr branch . -r 1 ../branch
1232+$ cd ../branch
1233+$ echo 'branch content' >dir/file2
1234+$ bzr add dir/file2
1235+$ bzr commit -m 'Add dir/file2 in branch'
1236+
1237+$ bzr merge ../trunk
1238+2>-D dir/file
1239+2>Conflict: can't delete dir because it is not empty. Not deleting.
1240+2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
1241+2>2 conflicts encountered.
1242+"""
1243+
1244+ def test_keep_them_all(self):
1245+ self.run_script("""
1246+$ bzr resolve dir
1247+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1248+""")
1249+
1250+ def test_adopt_child(self):
1251+ self.run_script("""
1252+$ bzr mv dir/file2 file2
1253+$ bzr rm dir --force
1254+$ bzr resolve dir
1255+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1256+""")
1257+
1258+ def test_kill_them_all(self):
1259+ self.run_script("""
1260+$ bzr rm dir --force
1261+$ bzr resolve dir
1262+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1263+""")
1264+
1265+ def test_resolve_taking_this(self):
1266+ self.run_script("""
1267+$ bzr resolve --take-this dir
1268+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1269+""")
1270+
1271+ def test_resolve_taking_other(self):
1272+ self.run_script("""
1273+$ bzr resolve --take-other dir
1274+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1275+""")
1276+
1277+
1278+class TestResolvePathConflict(TestResolveConflicts):
1279+
1280+ preamble = """
1281+$ bzr init trunk
1282+$ cd trunk
1283+$ echo 'Boo!' >file
1284+$ bzr add
1285+$ bzr commit -m 'Create trunk'
1286+$ bzr mv file file-in-trunk
1287+$ bzr commit -m 'Renamed to file-in-trunk'
1288+
1289+$ bzr branch . -r 1 ../branch
1290+$ cd ../branch
1291+$ bzr mv file file-in-branch
1292+$ bzr commit -m 'Renamed to file-in-branch'
1293+
1294+$ bzr merge ../trunk
1295+2>R file-in-branch => file-in-trunk
1296+2>Path conflict: file-in-branch / file-in-trunk
1297+2>1 conflicts encountered.
1298+"""
1299+
1300+ def test_keep_source(self):
1301+ self.run_script("""
1302+$ bzr resolve file-in-trunk
1303+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1304+""")
1305+
1306+ def test_keep_target(self):
1307+ self.run_script("""
1308+$ bzr mv file-in-trunk file-in-branch
1309+$ bzr resolve file-in-branch
1310+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1311+""")
1312+
1313+ def test_resolve_taking_this(self):
1314+ self.run_script("""
1315+$ bzr resolve --take-this file-in-branch
1316+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1317+""")
1318+
1319+ def test_resolve_taking_other(self):
1320+ self.run_script("""
1321+$ bzr resolve --take-other file-in-branch
1322+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1323+""")
1324+
1325+
1326+class TestResolveParentLoop(TestResolveConflicts):
1327+
1328+ preamble = """
1329+$ bzr init trunk
1330+$ cd trunk
1331+$ bzr mkdir dir1
1332+$ bzr mkdir dir2
1333+$ bzr commit -m 'Create trunk'
1334+$ bzr mv dir2 dir1
1335+$ bzr commit -m 'Moved dir2 into dir1'
1336+
1337+$ bzr branch . -r 1 ../branch
1338+$ cd ../branch
1339+$ bzr mv dir1 dir2
1340+$ bzr commit -m 'Moved dir1 into dir2'
1341+
1342+$ bzr merge ../trunk
1343+2>Conflict moving dir2/dir1 into dir2. Cancelled move.
1344+2>1 conflicts encountered.
1345+"""
1346+
1347+ def test_take_this(self):
1348+ self.run_script("""
1349+$ bzr resolve dir2
1350+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1351+""")
1352+
1353+ def test_take_other(self):
1354+ self.run_script("""
1355+$ bzr mv dir2/dir1 dir1
1356+$ bzr mv dir2 dir1
1357+$ bzr resolve dir2
1358+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1359+""")
1360+
1361+ def test_resolve_taking_this(self):
1362+ self.run_script("""
1363+$ bzr resolve --take-this dir2
1364+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1365+""")
1366+ self.failUnlessExists('dir2')
1367+
1368+ def test_resolve_taking_other(self):
1369+ self.run_script("""
1370+$ bzr resolve --take-other dir2
1371+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1372+""")
1373+ self.failUnlessExists('dir1')
1374+
1375+
1376+class TestResolveNonDirectoryParent(TestResolveConflicts):
1377+
1378+ preamble = """
1379+$ bzr init trunk
1380+$ cd trunk
1381+$ bzr mkdir foo
1382+$ bzr commit -m 'Create trunk'
1383+$ echo "Boing" >foo/bar
1384+$ bzr add foo/bar
1385+$ bzr commit -m 'Add foo/bar'
1386+
1387+$ bzr branch . -r 1 ../branch
1388+$ cd ../branch
1389+$ rm -r foo
1390+$ echo "Boo!" >foo
1391+$ bzr commit -m 'foo is now a file'
1392+
1393+$ bzr merge ../trunk
1394+2>+N foo.new/bar
1395+2>RK foo => foo.new/
1396+# FIXME: The message is misleading, foo.new *is* a directory when the message
1397+# is displayed -- vila 090916
1398+2>Conflict: foo.new is not a directory, but has files in it. Created directory.
1399+2>1 conflicts encountered.
1400+"""
1401+
1402+ def test_take_this(self):
1403+ self.run_script("""
1404+$ bzr rm foo.new --force
1405+# FIXME: Isn't it weird that foo is now unkown even if foo.new has been put
1406+# aside ? -- vila 090916
1407+$ bzr add foo
1408+$ bzr resolve foo.new
1409+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1410+""")
1411+
1412+ def test_take_other(self):
1413+ self.run_script("""
1414+$ bzr rm foo --force
1415+$ bzr mv foo.new foo
1416+$ bzr resolve foo
1417+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1418+""")
1419+
1420+ def test_resolve_taking_this(self):
1421+ self.run_script("""
1422+$ bzr resolve --take-this foo.new
1423+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1424+""")
1425+
1426+ def test_resolve_taking_other(self):
1427+ self.run_script("""
1428+$ bzr resolve --take-other foo.new
1429+$ bzr commit --strict -m 'No more conflicts nor unknown files'
1430+""")
1431+
1432+
1433+class TestMalformedTransform(script.TestCaseWithTransportAndScript):
1434+
1435+ def test_bug_430129(self):
1436+ # This is nearly like TestResolveNonDirectoryParent but with branch and
1437+ # trunk switched. As such it should certainly produce the same
1438+ # conflict.
1439+ self.run_script("""
1440+$ bzr init trunk
1441+$ cd trunk
1442+$ bzr mkdir foo
1443+$ bzr commit -m 'Create trunk'
1444+$ rm -r foo
1445+$ echo "Boo!" >foo
1446+$ bzr commit -m 'foo is now a file'
1447+
1448+$ bzr branch . -r 1 ../branch
1449+$ cd ../branch
1450+$ echo "Boing" >foo/bar
1451+$ bzr add foo/bar
1452+$ bzr commit -m 'Add foo/bar'
1453+
1454+$ bzr merge ../trunk
1455+2>bzr: ERROR: Tree transform is malformed [('unversioned executability', 'new-1')]
1456+""")
1457+
1458+
1459+class TestResolveActionOption(tests.TestCase):
1460+
1461+ def setUp(self):
1462+ super(TestResolveActionOption, self).setUp()
1463+ self.options = [conflicts.ResolveActionOption()]
1464+ self.parser = option.get_optparser(dict((o.name, o)
1465+ for o in self.options))
1466+
1467+ def parse(self, args):
1468+ return self.parser.parse_args(args)
1469+
1470+ def test_unknown_action(self):
1471+ self.assertRaises(errors.BadOptionValue,
1472+ self.parse, ['--action', 'take-me-to-the-moon'])
1473+
1474+ def test_done(self):
1475+ opts, args = self.parse(['--action', 'done'])
1476+ self.assertEqual({'action':'done'}, opts)
1477+
1478+ def test_take_this(self):
1479+ opts, args = self.parse(['--action', 'take-this'])
1480+ self.assertEqual({'action': 'take_this'}, opts)
1481+ opts, args = self.parse(['--take-this'])
1482+ self.assertEqual({'action': 'take_this'}, opts)
1483+
1484+ def test_take_other(self):
1485+ opts, args = self.parse(['--action', 'take-other'])
1486+ self.assertEqual({'action': 'take_other'}, opts)
1487+ opts, args = self.parse(['--take-other'])
1488+ self.assertEqual({'action': 'take_other'}, opts)
1489
1490=== modified file 'bzrlib/workingtree.py'
1491--- bzrlib/workingtree.py 2010-01-31 12:05:38 +0000
1492+++ bzrlib/workingtree.py 2010-02-04 13:06:32 +0000
1493@@ -111,6 +111,9 @@
1494
1495
1496 MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"
1497+# TODO: Modifying the conflict objects or their type is currently nearly
1498+# impossible as there is no clear relationship between the working tree format
1499+# and the conflict list file format.
1500 CONFLICT_HEADER_1 = "BZR conflict list format 1"
1501
1502 ERROR_PATH_NOT_FOUND = 3 # WindowsError errno code, equivalent to ENOENT