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
=== modified file 'NEWS'
--- NEWS 2010-02-04 11:02:01 +0000
+++ NEWS 2010-02-04 13:06:32 +0000
@@ -19,6 +19,7 @@
19New Features19New Features
20************20************
2121
22<<<<<<< TREE
22* If the Apport crash-reporting tool is available, bzr crashes are now23* If the Apport crash-reporting tool is available, bzr crashes are now
23 stored into the ``/var/crash`` apport spool directory, and the user is24 stored into the ``/var/crash`` apport spool directory, and the user is
24 invited to report them to the developers from there, either25 invited to report them to the developers from there, either
@@ -31,6 +32,13 @@
31* Stop sending apport crash files to ``.cache`` in the directory from32* Stop sending apport crash files to ``.cache`` in the directory from
32 which ``bzr selftest`` was run. (Martin Pool, #422350)33 which ``bzr selftest`` was run. (Martin Pool, #422350)
3334
35=======
36* Tree-shape conflicts can be resolved by providing ``--take-this`` and
37 ``--take-other`` to the ``bzr resolve`` command. Just marking the conflict
38 as resolved is still accessible via the ``--done`` default action.
39 (Vincent Ladeuil)
40
41>>>>>>> MERGE-SOURCE
34bzr 2.1.0 (not released yet)42bzr 2.1.0 (not released yet)
35############################43############################
3644
3745
=== modified file 'bzrlib/conflicts.py'
--- bzrlib/conflicts.py 2010-01-08 07:37:25 +0000
+++ bzrlib/conflicts.py 2010-02-04 13:06:32 +0000
@@ -1,4 +1,4 @@
1# Copyright (C) 2005, 2007 Canonical Ltd1# Copyright (C) 2005, 2007, 2009, 2010 Canonical Ltd
2#2#
3# This program is free software; you can redistribute it and/or modify3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by4# it under the terms of the GNU General Public License as published by
@@ -14,12 +14,11 @@
14# along with this program; if not, write to the Free Software14# along with this program; if not, write to the Free Software
15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1616
17# TODO: Move this into builtins
18
19# TODO: 'bzr resolve' should accept a directory name and work from that17# TODO: 'bzr resolve' should accept a directory name and work from that
20# point down18# point down
2119
22import os20import os
21import re
2322
24from bzrlib.lazy_import import lazy_import23from bzrlib.lazy_import import lazy_import
25lazy_import(globals(), """24lazy_import(globals(), """
@@ -32,9 +31,14 @@
32 osutils,31 osutils,
33 rio,32 rio,
34 trace,33 trace,
34 transform,
35 workingtree,
35 )36 )
36""")37""")
37from bzrlib.option import Option38from bzrlib import (
39 option,
40 registry,
41 )
3842
3943
40CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')44CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
@@ -55,14 +59,13 @@
55 Use bzr resolve when you have fixed a problem.59 Use bzr resolve when you have fixed a problem.
56 """60 """
57 takes_options = [61 takes_options = [
58 Option('text',62 option.Option('text',
59 help='List paths of files with text conflicts.'),63 help='List paths of files with text conflicts.'),
60 ]64 ]
61 _see_also = ['resolve', 'conflict-types']65 _see_also = ['resolve', 'conflict-types']
6266
63 def run(self, text=False):67 def run(self, text=False):
64 from bzrlib.workingtree import WorkingTree68 wt = workingtree.WorkingTree.open_containing(u'.')[0]
65 wt = WorkingTree.open_containing(u'.')[0]
66 for conflict in wt.conflicts():69 for conflict in wt.conflicts():
67 if text:70 if text:
68 if conflict.typestring != 'text conflict':71 if conflict.typestring != 'text conflict':
@@ -72,6 +75,28 @@
72 self.outf.write(str(conflict) + '\n')75 self.outf.write(str(conflict) + '\n')
7376
7477
78resolve_action_registry = registry.Registry()
79
80
81resolve_action_registry.register(
82 'done', 'done', 'Marks the conflict as resolved' )
83resolve_action_registry.register(
84 'take-this', 'take_this',
85 'Resolve the conflict preserving the version in the working tree' )
86resolve_action_registry.register(
87 'take-other', 'take_other',
88 'Resolve the conflict taking the merged version into account' )
89resolve_action_registry.default_key = 'done'
90
91class ResolveActionOption(option.RegistryOption):
92
93 def __init__(self):
94 super(ResolveActionOption, self).__init__(
95 'action', 'How to resolve the conflict.',
96 value_switches=True,
97 registry=resolve_action_registry)
98
99
75class cmd_resolve(commands.Command):100class cmd_resolve(commands.Command):
76 """Mark a conflict as resolved.101 """Mark a conflict as resolved.
77102
@@ -87,20 +112,33 @@
87 aliases = ['resolved']112 aliases = ['resolved']
88 takes_args = ['file*']113 takes_args = ['file*']
89 takes_options = [114 takes_options = [
90 Option('all', help='Resolve all conflicts in this tree.'),115 option.Option('all', help='Resolve all conflicts in this tree.'),
116 ResolveActionOption(),
91 ]117 ]
92 _see_also = ['conflicts']118 _see_also = ['conflicts']
93 def run(self, file_list=None, all=False):119 def run(self, file_list=None, all=False, action=None):
94 from bzrlib.workingtree import WorkingTree
95 if all:120 if all:
96 if file_list:121 if file_list:
97 raise errors.BzrCommandError("If --all is specified,"122 raise errors.BzrCommandError("If --all is specified,"
98 " no FILE may be provided")123 " no FILE may be provided")
99 tree = WorkingTree.open_containing('.')[0]124 tree = workingtree.WorkingTree.open_containing('.')[0]
100 resolve(tree)125 if action is None:
126 action = 'done'
101 else:127 else:
102 tree, file_list = builtins.tree_files(file_list)128 tree, file_list = builtins.tree_files(file_list)
103 if file_list is None:129 if file_list is None:
130 if action is None:
131 # FIXME: There is a special case here related to the option
132 # handling that could be clearer and easier to discover by
133 # providing an --auto action (bug #344013 and #383396) and
134 # make it mandatory instead of implicit and active only
135 # when no file_list is provided -- vila 091229
136 action = 'auto'
137 else:
138 if action is None:
139 action = 'done'
140 if action == 'auto':
141 if file_list is None:
104 un_resolved, resolved = tree.auto_resolve()142 un_resolved, resolved = tree.auto_resolve()
105 if len(un_resolved) > 0:143 if len(un_resolved) > 0:
106 trace.note('%d conflict(s) auto-resolved.', len(resolved))144 trace.note('%d conflict(s) auto-resolved.', len(resolved))
@@ -112,10 +150,16 @@
112 trace.note('All conflicts resolved.')150 trace.note('All conflicts resolved.')
113 return 0151 return 0
114 else:152 else:
115 resolve(tree, file_list)153 # FIXME: This can never occur but the block above needs some
116154 # refactoring to transfer tree.auto_resolve() to
117155 # conflict.auto(tree) --vila 091242
118def resolve(tree, paths=None, ignore_misses=False, recursive=False):156 pass
157 else:
158 resolve(tree, file_list, action=action)
159
160
161def resolve(tree, paths=None, ignore_misses=False, recursive=False,
162 action='done'):
119 """Resolve some or all of the conflicts in a working tree.163 """Resolve some or all of the conflicts in a working tree.
120164
121 :param paths: If None, resolve all conflicts. Otherwise, select only165 :param paths: If None, resolve all conflicts. Otherwise, select only
@@ -125,24 +169,29 @@
125 recursive commands like revert, this should be True. For commands169 recursive commands like revert, this should be True. For commands
126 or applications wishing finer-grained control, like the resolve170 or applications wishing finer-grained control, like the resolve
127 command, this should be False.171 command, this should be False.
128 :ignore_misses: If False, warnings will be printed if the supplied paths172 :param ignore_misses: If False, warnings will be printed if the supplied
129 do not have conflicts.173 paths do not have conflicts.
174 :param action: How the conflict should be resolved,
130 """175 """
131 tree.lock_tree_write()176 tree.lock_tree_write()
132 try:177 try:
133 tree_conflicts = tree.conflicts()178 tree_conflicts = tree.conflicts()
134 if paths is None:179 if paths is None:
135 new_conflicts = ConflictList()180 new_conflicts = ConflictList()
136 selected_conflicts = tree_conflicts181 to_process = tree_conflicts
137 else:182 else:
138 new_conflicts, selected_conflicts = \183 new_conflicts, to_process = tree_conflicts.select_conflicts(
139 tree_conflicts.select_conflicts(tree, paths, ignore_misses,184 tree, paths, ignore_misses, recursive)
140 recursive)185 for conflict in to_process:
186 try:
187 conflict._do(action, tree)
188 conflict.cleanup(tree)
189 except NotImplementedError:
190 new_conflicts.append(conflict)
141 try:191 try:
142 tree.set_conflicts(new_conflicts)192 tree.set_conflicts(new_conflicts)
143 except errors.UnsupportedOperation:193 except errors.UnsupportedOperation:
144 pass194 pass
145 selected_conflicts.remove_files(tree)
146 finally:195 finally:
147 tree.unlock()196 tree.unlock()
148197
@@ -237,12 +286,7 @@
237 for conflict in self:286 for conflict in self:
238 if not conflict.has_files:287 if not conflict.has_files:
239 continue288 continue
240 for suffix in CONFLICT_SUFFIXES:289 conflict.cleanup(tree)
241 try:
242 osutils.delete_any(tree.abspath(conflict.path+suffix))
243 except OSError, e:
244 if e.errno != errno.ENOENT:
245 raise
246290
247 def select_conflicts(self, tree, paths, ignore_misses=False,291 def select_conflicts(self, tree, paths, ignore_misses=False,
248 recurse=False):292 recurse=False):
@@ -301,6 +345,7 @@
301class Conflict(object):345class Conflict(object):
302 """Base class for all types of conflict"""346 """Base class for all types of conflict"""
303347
348 # FIXME: cleanup should take care of that ? -- vila 091229
304 has_files = False349 has_files = False
305350
306 def __init__(self, path, file_id=None):351 def __init__(self, path, file_id=None):
@@ -355,6 +400,32 @@
355 else:400 else:
356 return None, conflict.typestring401 return None, conflict.typestring
357402
403 def _do(self, action, tree):
404 """Apply the specified action to the conflict.
405
406 :param action: The method name to call.
407
408 :param tree: The tree passed as a parameter to the method.
409 """
410 meth = getattr(self, action, None)
411 if meth is None:
412 raise NotImplementedError(self.__class__.__name__ + '.' + action)
413 meth(tree)
414
415 def cleanup(self, tree):
416 raise NotImplementedError(self.cleanup)
417
418 def done(self, tree):
419 """Mark the conflict as solved once it has been handled."""
420 # This method does nothing but simplifies the design of upper levels.
421 pass
422
423 def take_this(self, tree):
424 raise NotImplementedError(self.take_this)
425
426 def take_other(self, tree):
427 raise NotImplementedError(self.take_other)
428
358429
359class PathConflict(Conflict):430class PathConflict(Conflict):
360 """A conflict was encountered merging file paths"""431 """A conflict was encountered merging file paths"""
@@ -364,6 +435,7 @@
364 format = 'Path conflict: %(path)s / %(conflict_path)s'435 format = 'Path conflict: %(path)s / %(conflict_path)s'
365436
366 rformat = '%(class)s(%(path)r, %(conflict_path)r, %(file_id)r)'437 rformat = '%(class)s(%(path)r, %(conflict_path)r, %(file_id)r)'
438
367 def __init__(self, path, conflict_path=None, file_id=None):439 def __init__(self, path, conflict_path=None, file_id=None):
368 Conflict.__init__(self, path, file_id)440 Conflict.__init__(self, path, file_id)
369 self.conflict_path = conflict_path441 self.conflict_path = conflict_path
@@ -374,6 +446,17 @@
374 s.add('conflict_path', self.conflict_path)446 s.add('conflict_path', self.conflict_path)
375 return s447 return s
376448
449 def cleanup(self, tree):
450 # No additional files have been generated here
451 pass
452
453 def take_this(self, tree):
454 tree.rename_one(self.conflict_path, self.path)
455
456 def take_other(self, tree):
457 # just acccept bzr proposal
458 pass
459
377460
378class ContentsConflict(PathConflict):461class ContentsConflict(PathConflict):
379 """The files are of different types, or not present"""462 """The files are of different types, or not present"""
@@ -384,7 +467,30 @@
384467
385 format = 'Contents conflict in %(path)s'468 format = 'Contents conflict in %(path)s'
386469
387470 def cleanup(self, tree):
471 for suffix in ('.BASE', '.OTHER'):
472 try:
473 osutils.delete_any(tree.abspath(self.path + suffix))
474 except OSError, e:
475 if e.errno != errno.ENOENT:
476 raise
477
478 # FIXME: I smell something weird here and it seems we should be able to be
479 # more coherent with some other conflict ? bzr *did* a choice there but
480 # neither take_this nor take_other reflect that... -- vila 091224
481 def take_this(self, tree):
482 tree.remove([self.path + '.OTHER'], force=True, keep_files=False)
483
484 def take_other(self, tree):
485 tree.remove([self.path], force=True, keep_files=False)
486
487
488
489# FIXME: TextConflict is about a single file-id, there never is a conflict_path
490# attribute so we shouldn't inherit from PathConflict but simply from Conflict
491
492# TODO: There should be a base revid attribute to better inform the user about
493# how the conflicts were generated.
388class TextConflict(PathConflict):494class TextConflict(PathConflict):
389 """The merge algorithm could not resolve all differences encountered."""495 """The merge algorithm could not resolve all differences encountered."""
390496
@@ -394,6 +500,14 @@
394500
395 format = 'Text conflict in %(path)s'501 format = 'Text conflict in %(path)s'
396502
503 def cleanup(self, tree):
504 for suffix in CONFLICT_SUFFIXES:
505 try:
506 osutils.delete_any(tree.abspath(self.path+suffix))
507 except OSError, e:
508 if e.errno != errno.ENOENT:
509 raise
510
397511
398class HandledConflict(Conflict):512class HandledConflict(Conflict):
399 """A path problem that has been provisionally resolved.513 """A path problem that has been provisionally resolved.
@@ -414,6 +528,10 @@
414 s.add('action', self.action)528 s.add('action', self.action)
415 return s529 return s
416530
531 def cleanup(self, tree):
532 """Nothing to cleanup."""
533 pass
534
417535
418class HandledPathConflict(HandledConflict):536class HandledPathConflict(HandledConflict):
419 """A provisionally-resolved path problem involving two paths.537 """A provisionally-resolved path problem involving two paths.
@@ -460,15 +578,22 @@
460578
461 format = 'Conflict adding file %(conflict_path)s. %(action)s %(path)s.'579 format = 'Conflict adding file %(conflict_path)s. %(action)s %(path)s.'
462580
581 def take_this(self, tree):
582 tree.remove([self.conflict_path], force=True, keep_files=False)
583 tree.rename_one(self.path, self.conflict_path)
584
585 def take_other(self, tree):
586 tree.remove([self.path], force=True, keep_files=False)
587
463588
464class ParentLoop(HandledPathConflict):589class ParentLoop(HandledPathConflict):
465 """An attempt to create an infinitely-looping directory structure.590 """An attempt to create an infinitely-looping directory structure.
466 This is rare, but can be produced like so:591 This is rare, but can be produced like so:
467592
468 tree A:593 tree A:
469 mv foo/bar594 mv foo bar
470 tree B:595 tree B:
471 mv bar/foo596 mv bar foo
472 merge A and B597 merge A and B
473 """598 """
474599
@@ -476,6 +601,27 @@
476601
477 format = 'Conflict moving %(conflict_path)s into %(path)s. %(action)s.'602 format = 'Conflict moving %(conflict_path)s into %(path)s. %(action)s.'
478603
604 def take_this(self, tree):
605 # just acccept bzr proposal
606 pass
607
608 def take_other(self, tree):
609 # FIXME: We shouldn't have to manipulate so many paths here (and there
610 # is probably a bug or two...)
611 base_path = osutils.basename(self.path)
612 conflict_base_path = osutils.basename(self.conflict_path)
613 tt = transform.TreeTransform(tree)
614 try:
615 p_tid = tt.trans_id_file_id(self.file_id)
616 parent_tid = tt.get_tree_parent(p_tid)
617 cp_tid = tt.trans_id_file_id(self.conflict_file_id)
618 cparent_tid = tt.get_tree_parent(cp_tid)
619 tt.adjust_path(base_path, cparent_tid, cp_tid)
620 tt.adjust_path(conflict_base_path, parent_tid, p_tid)
621 tt.apply()
622 finally:
623 tt.finalize()
624
479625
480class UnversionedParent(HandledConflict):626class UnversionedParent(HandledConflict):
481 """An attempt to version a file whose parent directory is not versioned.627 """An attempt to version a file whose parent directory is not versioned.
@@ -488,18 +634,34 @@
488 format = 'Conflict because %(path)s is not versioned, but has versioned'\634 format = 'Conflict because %(path)s is not versioned, but has versioned'\
489 ' children. %(action)s.'635 ' children. %(action)s.'
490636
637 # FIXME: We silently do nothing to make tests pass, but most probably the
638 # conflict shouldn't exist (the long story is that the conflict is
639 # generated with another one that can be resolved properly) -- vila 091224
640 def take_this(self, tree):
641 pass
642
643 def take_other(self, tree):
644 pass
645
491646
492class MissingParent(HandledConflict):647class MissingParent(HandledConflict):
493 """An attempt to add files to a directory that is not present.648 """An attempt to add files to a directory that is not present.
494 Typically, the result of a merge where THIS deleted the directory and649 Typically, the result of a merge where THIS deleted the directory and
495 the OTHER added a file to it.650 the OTHER added a file to it.
496 See also: DeletingParent (same situation, reversed THIS and OTHER)651 See also: DeletingParent (same situation, THIS and OTHER reversed)
497 """652 """
498653
499 typestring = 'missing parent'654 typestring = 'missing parent'
500655
501 format = 'Conflict adding files to %(path)s. %(action)s.'656 format = 'Conflict adding files to %(path)s. %(action)s.'
502657
658 def take_this(self, tree):
659 tree.remove([self.path], force=True, keep_files=False)
660
661 def take_other(self, tree):
662 # just acccept bzr proposal
663 pass
664
503665
504class DeletingParent(HandledConflict):666class DeletingParent(HandledConflict):
505 """An attempt to add files to a directory that is not present.667 """An attempt to add files to a directory that is not present.
@@ -512,9 +674,19 @@
512 format = "Conflict: can't delete %(path)s because it is not empty. "\674 format = "Conflict: can't delete %(path)s because it is not empty. "\
513 "%(action)s."675 "%(action)s."
514676
677 # FIXME: It's a bit strange that the default action is not coherent with
678 # MissingParent from the *user* pov.
679
680 def take_this(self, tree):
681 # just acccept bzr proposal
682 pass
683
684 def take_other(self, tree):
685 tree.remove([self.path], force=True, keep_files=False)
686
515687
516class NonDirectoryParent(HandledConflict):688class NonDirectoryParent(HandledConflict):
517 """An attempt to add files to a directory that is not a director or689 """An attempt to add files to a directory that is not a directory or
518 an attempt to change the kind of a directory with files.690 an attempt to change the kind of a directory with files.
519 """691 """
520692
@@ -523,6 +695,27 @@
523 format = "Conflict: %(path)s is not a directory, but has files in it."\695 format = "Conflict: %(path)s is not a directory, but has files in it."\
524 " %(action)s."696 " %(action)s."
525697
698 # FIXME: .OTHER should be used instead of .new when the conflict is created
699
700 def take_this(self, tree):
701 # FIXME: we should preserve that path when the conflict is generated !
702 if self.path.endswith('.new'):
703 conflict_path = self.path[:-(len('.new'))]
704 tree.remove([self.path], force=True, keep_files=False)
705 tree.add(conflict_path)
706 else:
707 raise NotImplementedError(self.take_this)
708
709 def take_other(self, tree):
710 # FIXME: we should preserve that path when the conflict is generated !
711 if self.path.endswith('.new'):
712 conflict_path = self.path[:-(len('.new'))]
713 tree.remove([conflict_path], force=True, keep_files=False)
714 tree.rename_one(self.path, conflict_path)
715 else:
716 raise NotImplementedError(self.take_other)
717
718
526ctype = {}719ctype = {}
527720
528721
@@ -532,7 +725,6 @@
532 for conflict_type in conflict_types:725 for conflict_type in conflict_types:
533 ctype[conflict_type.typestring] = conflict_type726 ctype[conflict_type.typestring] = conflict_type
534727
535
536register_types(ContentsConflict, TextConflict, PathConflict, DuplicateID,728register_types(ContentsConflict, TextConflict, PathConflict, DuplicateID,
537 DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,729 DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,
538 DeletingParent, NonDirectoryParent)730 DeletingParent, NonDirectoryParent)
539731
=== modified file 'bzrlib/help_topics/en/conflict-types.txt'
--- bzrlib/help_topics/en/conflict-types.txt 2010-01-08 07:37:25 +0000
+++ bzrlib/help_topics/en/conflict-types.txt 2010-02-04 13:06:32 +0000
@@ -3,16 +3,59 @@
33
4Some operations, like merge, revert and pull, modify the contents of your4Some operations, like merge, revert and pull, modify the contents of your
5working tree. These modifications are programmatically generated, and so they5working tree. These modifications are programmatically generated, and so they
6may conflict with the current state of your working tree. Many kinds of changes6may conflict with the current state of your working tree.
7can be combined programmatically, but sometimes only a human can determine the7
8right thing to do. When this happens Bazaar will inform you that there is a8When conflicts are present in your working tree (as shown by ``bzr
9conflict and then ask you to resolve it. The command to tell Bazaar a conflict9conflicts``), you should resolve them and then inform bzr that the conflicts
10is resolved is ``resolve``, but you must perform some action before you can do10have been resolved.
11this.11
12Resolving conflicts is sometimes not obvious. Either because the user that
13should resolve them is not the one responsible for their occurrence, as is the
14case when merging other people's work or because some conflicts are presented
15in a way that is not easy to understand.
16
17Bazaar tries to avoid conflicts ; its aim is to ask you to resolve the
18conflict if and only if there's an actual conceptual conflict in the source
19tree. Because Bazaar doesn't understand the real meaning of the files being
20versioned, it can, when faced with ambiguities, fall short in either direction
21trying to resolve the conflict itself. Many kinds of changes can be combined
22programmatically, but sometimes only a human can determine the right thing to
23do.
24
25When Bazaar generates a conflict, it adds information into the working tree to
26present the conflicting versions, and it's up to you to find the correct
27resolution.
28
29Whatever the conflict is, resolving it is roughly done in two steps::
30
31 * modify the working tree content so that the conflicted item is now in the
32 state you want to keep,
33
34 * inform Bazaar that the conflict is now solved and ask to cleanup any
35 remaining generated information (``bzr resolve <item>``).
36
37For most conflict types, there are some obvious ways to modify the working
38tree and put it into the desired state. For some types of conflicts, Bazaar
39itself already made a choice, when possible.
40
41Yet, whether Bazaar makes a choice or not, there are some other simple but
42different ways to resolve the conflict.
1243
13Each type of conflict is explained below, and the action which must be done to44Each type of conflict is explained below, and the action which must be done to
14resolve the conflict is outlined.45resolve the conflict is outlined.
1546
47Various actions are available depending on the kind of conflict, for some of
48these actions, Bazaar can provide some help. In the end you should at least
49inform Bazaar that you're done with the conflict with::
50
51 ``bzr resolve FILE --action=done'
52
53Note that this is the default action when a single file is involved so you can
54simply use::
55
56 ``bzr resolve FILE``
57
58See ``bzr help resolve`` for more details.
1659
17Text conflicts60Text conflicts
18--------------61--------------
@@ -54,8 +97,12 @@
54you are done editing, the file should look like it never had a conflict, and be97you are done editing, the file should look like it never had a conflict, and be
55ready to commit.98ready to commit.
5699
57When you have resolved text conflicts, just run "bzr resolve", and Bazaar will100When you have resolved text conflicts, just run ``bzr resolve --auto``, and
58auto-detect which conflicts you have resolved.101Bazaar will auto-detect which conflicts you have resolved.
102
103When the conflict is resolved, Bazaar deletes the previously generated
104``.BASE``, ``.THIS`` and ``.OTHER`` files if they are still present in the
105working tree.
59106
60Content conflicts107Content conflicts
61-----------------108-----------------
@@ -64,20 +111,29 @@
64111
65 Contents conflict in FILE112 Contents conflict in FILE
66113
67This conflict happens when there are conflicting changes in the target tree and114This conflict happens when there are conflicting changes in the working tree
68the merge source, but the conflicted items are not text files. They may be115and the merge source, but the conflicted items are not text files. They may
69binary files, or symlinks, or directories. It can even happen with files that116be binary files, or symlinks, or directories. It can even happen with files
70are deleted on one side, and modified on the other.117that are deleted on one side, and modified on the other.
71118
72Like text conflicts, Bazaar will emit THIS, OTHER and BASE files. (They may be119Like text conflicts, Bazaar will emit THIS, OTHER and BASE files. (They may be
73regular files, symlinks or directories). But it will not include a "main copy"120regular files, symlinks or directories). But it will not include a "main copy"
74of the file with herringbone conflict markers. It will appear that the "main121of the file with herringbone conflict markers. It will appear that the "main
75copy" has been renamed to THIS or OTHER.122copy" has been renamed to THIS or OTHER.
76123
77To resolve this, use "bzr mv" to rename the file back to its normal name, and124To resolve that kind of conflict, you should rebuild FILE from either version
78combine the changes manually. When you are satisfied, run "bzr resolve125or a combination of both.
79FILE". Bazaar cannot auto-detect when conflicts of this kind have been126
80resolved.127``bzr resolve`` recognizes the following actions:
128
129 * ``--action=take-this`` will issue ``bzr mv FILE.THIS FILE``,
130 * ``--action=take-other`` will issue ``bzr mv FILE.OTHER FILE``,
131 * ``--action=done`` will just mark the conflict as resolved.
132
133Any action will also delete the previously generated ``.BASE``, ``.THIS`` and
134``.OTHER`` files if they are still present in the working tree.
135
136Bazaar cannot auto-detect when conflicts of this kind have been resolved.
81137
82Tag conflicts138Tag conflicts
83-------------139-------------
@@ -95,12 +151,13 @@
95To resolve the conflict, you must apply the correct tags to either the target151To resolve the conflict, you must apply the correct tags to either the target
96branch or the source branch as appropriate. Use "bzr tags --show-ids -d152branch or the source branch as appropriate. Use "bzr tags --show-ids -d
97SOURCE_URL" to see the tags in the source branch. If you want to make the153SOURCE_URL" to see the tags in the source branch. If you want to make the
98target branch's tags match the source branch, then in the target branch do 154target branch's tags match the source branch, then in the target branch do
99``bzr tag --force -r revid:REVISION_ID CONFLICTING_TAG`` for each of the155``bzr tag --force -r revid:REVISION_ID CONFLICTING_TAG`` for each of the
100CONFLICTING_TAGs, where REVISION_ID comes from the list of tags in the source156CONFLICTING_TAGs, where REVISION_ID comes from the list of tags in the source
101branch. 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 source157branch. You need not call "bzr resolve" after doing this. To resolve in
102branch. (Note that pulling or pushing using --overwrite will overwrite all158favor of the target branch, you need to similarly use ``tag --force`` in the
103tags as well.)159source branch. (Note that pulling or pushing using --overwrite will overwrite
160all tags as well.)
104161
105Duplicate paths162Duplicate paths
106---------------163---------------
@@ -110,10 +167,20 @@
110 Conflict adding file FILE. Moved existing file to FILE.moved.167 Conflict adding file FILE. Moved existing file to FILE.moved.
111168
112Sometimes Bazaar will attempt to create a file using a pathname that has169Sometimes Bazaar will attempt to create a file using a pathname that has
113already been used. The existing file will be renamed to "FILE.moved". If170already been used. The existing file will be renamed to "FILE.moved".
114you wish, you can rename either one of these files, or combine their contents.171
115When you are satisfied, you can run "bzr resolve FILE" to mark the conflict as172To resolve that kind of conflict, you should rebuild FILE from either version
116resolved.173or a combination of both.
174
175``bzr resolve`` recognizes the following actions::
176
177 * ``--action=take-this`` will issue ``bzr rm FILE ; bzr mv FILE.moved FILE``,
178 * ``--action=take-other`` will issue ``bzr rm FILE.moved``,
179 * ``--action=done`` will just mark the conflict as resolved.
180
181Note that you must get rid of FILE.moved before using ``--action=done``.
182
183Bazaar cannot auto-detect when conflicts of this kind have been resolved.
117184
118Unversioned parent185Unversioned parent
119------------------186------------------
@@ -137,26 +204,47 @@
137204
138 Conflict adding files to FILE. Created directory.205 Conflict adding files to FILE. Created directory.
139206
140This happens when a file has been deleted in the target, but has new children207This happens when a directory has been deleted in the target, but has new
141in the source. This is similar to the "unversioned parent" conflict, except208children in the source. This is similar to the "unversioned parent" conflict,
142that the parent directory does not *exist*, instead of just being unversioned.209except that the parent directory does not *exist*, instead of just being
143In this situation, Bazaar will create the missing parent. Resolving this issue210unversioned. In this situation, Bazaar will create the missing parent.
144depends very much on the particular scenario. You may wish to rename or delete211Resolving this issue depends very much on the particular scenario.
145either the file or the directory. When you are satisfied, you can run "bzr212
146resolve FILE" to mark the conflict as resolved.213To resolve that kind of conflict, you should either remove or rename the
214children or the directory or a combination of both.
215
216``bzr resolve`` recognizes the following actions::
217
218 * ``--action=take-this`` will issue ``bzr rm directory``
219 including the children,
220 * ``--action=take-other`` will acknowledge Bazaar choice to keep
221 the children and restoring the directory,
222 * ``--action=done`` will just mark the conflict as resolved.
223
224Bazaar cannot auto-detect when conflicts of this kind have been resolved.
147225
148Deleting parent226Deleting parent
149---------------227---------------
150228
151Typical message::229Typical message::
152230
153 Conflict: can't delete FILE because it is not empty. Not deleting.231 Conflict: can't delete DIR because it is not empty. Not deleting.
154232
155This is the opposite of "missing parent". A directory is deleted in the233This is the opposite of "missing parent". A directory is deleted in the
156source, but has new children in the target. Bazaar will retain the directory.234source, but has new children in the target. Bazaar will retain the directory.
157Resolving this issue depends very much on the particular scenario. You may235Resolving this issue depends very much on the particular scenario.
158wish to rename or delete either the file or the directory. When you are236
159satisfied, you can run "bzr resolve FILE" to mark the conflict as resolved.237To resolve that kind of conflict, you should either remove or rename the
238children or the directory or a combination of both.
239
240``bzr resolve`` recognizes the following actions::
241
242 * ``--action=take-this`` will acknowledge Bazaar choice to keep the directory,
243 * ``--action=take-other`` will issue ``bzr rm directory`` including the
244 children,
245 * ``--action=done`` will just mark the conflict as resolved.
246
247Bazaar cannot auto-detect when conflicts of this kind have been resolved.
160248
161Path conflict249Path conflict
162-------------250-------------
@@ -166,9 +254,19 @@
166 Path conflict: PATH1 / PATH2254 Path conflict: PATH1 / PATH2
167255
168This happens when the source and target have each modified the name or parent256This happens when the source and target have each modified the name or parent
169directory of a file. Bazaar will use the path elements from the source. You257directory of a file. Bazaar will use the path elements from the source.
170can rename the file, and once you have, run "bzr resolve FILE" to mark the258
171conflict as resolved.259To resolve that kind of conflict, you just have to decide what name should be
260retained for the file involved.
261
262``bzr resolve`` recognizes the following actions::
263
264 * ``--action=take-this`` will revert Bazaar choice and keep ``PATH1`` by
265 issuing ``bzr mv PATH2 PATH1``,
266 * ``--action=take-other`` will acknowledge Bazaar choice of keeping ``PATH2``,
267 * ``--action=done`` will just mark the conflict as resolved.
268
269Bazaar cannot auto-detect when conflicts of this kind have been resolved.
172270
173Parent loop271Parent loop
174-----------272-----------
@@ -179,45 +277,67 @@
179277
180This happens when the source and the target have each moved directories, so278This happens when the source and the target have each moved directories, so
181that, if the change could be applied, a directory would be contained by itself.279that, if the change could be applied, a directory would be contained by itself.
182For example::280For example:
183281
184 $ bzr init282 $ bzr init
185 $ bzr mkdir a283 $ bzr mkdir white
186 $ bzr mkdir b284 $ bzr mkdir black
187 $ bzr commit -m "BASE"285 $ bzr commit -m "BASE"
188 $ bzr branch . ../other286 $ bzr branch . ../other
189 $ bzr mv a b287 $ bzr mv white black
190 $ bzr commit -m "THIS"288 $ bzr commit -m "THIS"
191 $ bzr mv ../other/b ../other/a289 $ bzr mv ../other/black ../other/white
192 $ bzr commit ../other -m "OTHER"290 $ bzr commit ../other -m "OTHER"
193 $ bzr merge ../other291 $ bzr merge ../other
194292
195In this situation, Bazaar will cancel the move, and leave "a" in "b".293In this situation, Bazaar will cancel the move, and leave ``white`` in
196You can rename the directories if you like, and once you have, run "bzr resolve294``black``. To resolve that kind of conflict, you just have to decide what
197FILE" to mark the conflict as resolved.295name should be retained for the directories involved.
296
297``bzr resolve`` recognizes the following actions::
298
299 * ``--action=take-this`` will acknowledge Bazaar choice of leaving ``white``
300 in ``black``,
301 * ``--action=take-other`` will revert Bazaar choice and move ``black`` in
302 ``white`` by issuing ``bzr mv black/white white ; bzr mv black white``,
303 * ``--action=done`` will just mark the conflict as resolved.
304
305Bazaar cannot auto-detect when conflicts of this kind have been resolved.
198306
199Non-directory parent307Non-directory parent
200--------------------308--------------------
201309
202Typical message::310Typical message::
203311
204 Conflict: FILE.new is not a directory, but has files in it.312 Conflict: foo.new is not a directory, but has files in it.
205 Created directory.313 Created directory.
206314
207This happens when one side has added files to a directory, and the other side315This happens when one side has added files to a directory, and the other side
208has changed the directory into a file or symlink. For example::316has changed the directory into a file or symlink. For example:
209317
210 $ bzr init318 $ bzr init
211 $ bzr mkdir a319 $ bzr mkdir foo
212 $ bzr commit -m "BASE"320 $ bzr commit -m "BASE"
213 $ bzr branch . ../other321 $ bzr branch . ../other
214 $ rmdir a322 $ rmdir foo
215 $ touch a323 $ touch foo
216 $ bzr commit -m "THIS"324 $ bzr commit -m "THIS"
217 $ bzr mkdir ../other/a/b325 $ bzr mkdir ../other/foo/bar
218 $ bzr commit ../other -m "OTHER"326 $ bzr commit ../other -m "OTHER"
219 $ bzr merge ../other327 $ bzr merge ../other
220328
329To resolve that kind of conflict, you have to decide what name should be
330retained for the file, directory or symlink involved.
331
332``bzr resolve`` recognizes the following actions::
333
334 * ``--action=take-this`` will issue ``bzr rm --force foo.new`` and
335 ``bzr add foo``,
336 * ``--action=take-other`` will issue ``bzr rm --force foo`` and
337 ``bzr mv foo.new foo``,
338 * ``--action=done`` will just mark the conflict as resolved.
339
340Bazaar cannot auto-detect when conflicts of this kind have been resolved.
221341
222MalformedTransform342MalformedTransform
223------------------343------------------
224344
=== added directory 'bzrlib/help_topics/es'
=== removed directory 'bzrlib/help_topics/es'
=== renamed file 'bzrlib/help_topics/es/conflicts.txt' => 'bzrlib/help_topics/es/conflict-types.txt'
=== modified file 'bzrlib/option.py'
--- bzrlib/option.py 2009-08-18 08:10:44 +0000
+++ bzrlib/option.py 2010-02-04 13:06:31 +0000
@@ -361,7 +361,7 @@
361361
362 name, help, value_switches and enum_switch are passed to the362 name, help, value_switches and enum_switch are passed to the
363 RegistryOption constructor. Any other keyword arguments are treated363 RegistryOption constructor. Any other keyword arguments are treated
364 as values for the option, and they value is treated as the help.364 as values for the option, and their value is treated as the help.
365 """365 """
366 reg = _mod_registry.Registry()366 reg = _mod_registry.Registry()
367 for name, switch_help in kwargs.iteritems():367 for name, switch_help in kwargs.iteritems():
368368
=== modified file 'bzrlib/tests/blackbox/test_conflicts.py'
--- bzrlib/tests/blackbox/test_conflicts.py 2009-03-23 14:59:43 +0000
+++ bzrlib/tests/blackbox/test_conflicts.py 2010-02-04 13:06:32 +0000
@@ -1,4 +1,4 @@
1# Copyright (C) 2006 Canonical Ltd1# Copyright (C) 2006, 2009, 2010 Canonical Ltd
2#2#
3# This program is free software; you can redistribute it and/or modify3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by4# it under the terms of the GNU General Public License as published by
@@ -14,21 +14,22 @@
14# along with this program; if not, write to the Free Software14# along with this program; if not, write to the Free Software
15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1616
17import os
18
19from bzrlib import (17from bzrlib import (
20 conflicts18 conflicts,
19 tests,
20 workingtree,
21 )21 )
22from bzrlib.workingtree import WorkingTree
23from bzrlib.tests.blackbox import ExternalBase
2422
25# FIXME: These don't really look at the output of the conflict commands, just23# FIXME: These don't really look at the output of the conflict commands, just
26# the number of lines - there should be more examination.24# the number of lines - there should be more examination.
2725
28class TestConflicts(ExternalBase):26class TestConflictsBase(tests.TestCaseWithTransport):
2927
30 def setUp(self):28 def setUp(self):
31 super(ExternalBase, self).setUp()29 super(TestConflictsBase, self).setUp()
30 self.make_tree_with_conflicts()
31
32 def make_tree_with_conflicts(self):
32 a_tree = self.make_branch_and_tree('a')33 a_tree = self.make_branch_and_tree('a')
33 self.build_tree_contents([34 self.build_tree_contents([
34 ('a/myfile', 'contentsa\n'),35 ('a/myfile', 'contentsa\n'),
@@ -53,45 +54,48 @@
53 a_tree.rename_one('mydir', 'mydir3')54 a_tree.rename_one('mydir', 'mydir3')
54 a_tree.commit(message='change')55 a_tree.commit(message='change')
55 a_tree.merge_from_branch(b_tree.branch)56 a_tree.merge_from_branch(b_tree.branch)
56 os.chdir('a')57
58 def run_bzr(self, cmd, working_dir='a', **kwargs):
59 return super(TestConflictsBase, self).run_bzr(
60 cmd, working_dir=working_dir, **kwargs)
61
62
63class TestConflicts(TestConflictsBase):
5764
58 def test_conflicts(self):65 def test_conflicts(self):
59 conflicts, errs = self.run_bzr('conflicts')66 out, err = self.run_bzr('conflicts')
60 self.assertEqual(3, len(conflicts.splitlines()))67 self.assertEqual(3, len(out.splitlines()))
6168
62 def test_conflicts_text(self):69 def test_conflicts_text(self):
63 conflicts = self.run_bzr('conflicts --text')[0].splitlines()70 out, err = self.run_bzr('conflicts --text')
64 self.assertEqual(['my_other_file', 'myfile'], conflicts)71 self.assertEqual(['my_other_file', 'myfile'], out.splitlines())
72
73
74class TestResolve(TestConflictsBase):
6575
66 def test_resolve(self):76 def test_resolve(self):
67 self.run_bzr('resolve myfile')77 self.run_bzr('resolve myfile')
68 conflicts, errs = self.run_bzr('conflicts')78 out, err = self.run_bzr('conflicts')
69 self.assertEqual(2, len(conflicts.splitlines()))79 self.assertEqual(2, len(out.splitlines()))
70 self.run_bzr('resolve my_other_file')80 self.run_bzr('resolve my_other_file')
71 self.run_bzr('resolve mydir2')81 self.run_bzr('resolve mydir2')
72 conflicts, errs = self.run_bzr('conflicts')82 out, err = self.run_bzr('conflicts')
73 self.assertEqual(len(conflicts.splitlines()), 0)83 self.assertEqual(0, len(out.splitlines()))
7484
75 def test_resolve_all(self):85 def test_resolve_all(self):
76 self.run_bzr('resolve --all')86 self.run_bzr('resolve --all')
77 conflicts, errs = self.run_bzr('conflicts')87 out, err = self.run_bzr('conflicts')
78 self.assertEqual(len(conflicts.splitlines()), 0)88 self.assertEqual(0, len(out.splitlines()))
7989
80 def test_resolve_in_subdir(self):90 def test_resolve_in_subdir(self):
81 """resolve when run from subdirectory should handle relative paths"""91 """resolve when run from subdirectory should handle relative paths"""
82 orig_dir = os.getcwdu()92 self.build_tree(["a/subdir/"])
83 try:93 self.run_bzr("resolve ../myfile", working_dir='a/subdir')
84 os.mkdir("subdir")94 self.run_bzr("resolve ../a/myfile", working_dir='b')
85 os.chdir("subdir")95 wt = workingtree.WorkingTree.open_containing('b')[0]
86 self.run_bzr("resolve ../myfile")96 conflicts = wt.conflicts()
87 os.chdir("../../b")97 self.assertEqual(True, conflicts.is_empty(),
88 self.run_bzr("resolve ../a/myfile")98 "tree still contains conflicts: %r" % conflicts)
89 wt = WorkingTree.open_containing('.')[0]
90 conflicts = wt.conflicts()
91 if not conflicts.is_empty():
92 self.fail("tree still contains conflicts: %r" % conflicts)
93 finally:
94 os.chdir(orig_dir)
9599
96 def test_auto_resolve(self):100 def test_auto_resolve(self):
97 """Text conflicts can be resolved automatically"""101 """Text conflicts can be resolved automatically"""
@@ -102,11 +106,10 @@
102 self.assertEqual(tree.kind('file_id'), 'file')106 self.assertEqual(tree.kind('file_id'), 'file')
103 file_conflict = conflicts.TextConflict('file', file_id='file_id')107 file_conflict = conflicts.TextConflict('file', file_id='file_id')
104 tree.set_conflicts(conflicts.ConflictList([file_conflict]))108 tree.set_conflicts(conflicts.ConflictList([file_conflict]))
105 os.chdir('tree')109 note = self.run_bzr('resolve', retcode=1, working_dir='tree')[1]
106 note = self.run_bzr('resolve', retcode=1)[1]
107 self.assertContainsRe(note, '0 conflict\\(s\\) auto-resolved.')110 self.assertContainsRe(note, '0 conflict\\(s\\) auto-resolved.')
108 self.assertContainsRe(note,111 self.assertContainsRe(note,
109 'Remaining conflicts:\nText conflict in file')112 'Remaining conflicts:\nText conflict in file')
110 self.build_tree_contents([('file', 'a\n')])113 self.build_tree_contents([('tree/file', 'a\n')])
111 note = self.run_bzr('resolve')[1]114 note = self.run_bzr('resolve', working_dir='tree')[1]
112 self.assertContainsRe(note, 'All conflicts resolved.')115 self.assertContainsRe(note, 'All conflicts resolved.')
113116
=== modified file 'bzrlib/tests/test_conflicts.py'
--- bzrlib/tests/test_conflicts.py 2010-01-04 14:56:59 +0000
+++ bzrlib/tests/test_conflicts.py 2010-02-04 13:06:32 +0000
@@ -21,8 +21,10 @@
21 bzrdir,21 bzrdir,
22 conflicts,22 conflicts,
23 errors,23 errors,
24 option,
24 tests,25 tests,
25 )26 )
27from bzrlib.tests import script
2628
2729
28# TODO: Test commit with some added, and added-but-missing files30# TODO: Test commit with some added, and added-but-missing files
@@ -171,3 +173,504 @@
171 if 'conflict_file_id' in stanza:173 if 'conflict_file_id' in stanza:
172 self.assertStartsWith(stanza['conflict_file_id'], u'\xeed')174 self.assertStartsWith(stanza['conflict_file_id'], u'\xeed')
173175
176
177# FIXME: Tests missing for DuplicateID conflict type
178class TestResolveConflicts(script.TestCaseWithTransportAndScript):
179
180 preamble = None # The setup script set by daughter classes
181
182 def setUp(self):
183 super(TestResolveConflicts, self).setUp()
184 self.run_script(self.preamble)
185
186
187class TestResolveTextConflicts(TestResolveConflicts):
188 # TBC
189 pass
190
191
192class TestResolveContentConflicts(TestResolveConflicts):
193
194 # FIXME: We need to add the reverse case (delete in trunk, modify in
195 # branch) but that could wait until the resolution mechanism is implemented.
196
197 preamble = """
198$ bzr init trunk
199$ cd trunk
200$ echo 'trunk content' >file
201$ bzr add file
202$ bzr commit -m 'Create trunk'
203
204$ bzr branch . ../branch
205$ cd ../branch
206$ bzr rm file
207$ bzr commit -m 'Delete file'
208
209$ cd ../trunk
210$ echo 'more content' >>file
211$ bzr commit -m 'Modify file'
212
213$ cd ../branch
214$ bzr merge ../trunk
2152>+N file.OTHER
2162>Contents conflict in file
2172>1 conflicts encountered.
218"""
219
220 def test_take_this(self):
221 self.run_script("""
222$ bzr rm file.OTHER --force # a simple rm file.OTHER is valid too
223$ bzr resolve file
224$ bzr commit --strict -m 'No more conflicts nor unknown files'
225""")
226
227 def test_take_other(self):
228 self.run_script("""
229$ bzr mv file.OTHER file
230$ bzr resolve file
231$ bzr commit --strict -m 'No more conflicts nor unknown files'
232""")
233
234 def test_resolve_taking_this(self):
235 self.run_script("""
236$ bzr resolve --take-this file
237$ bzr commit --strict -m 'No more conflicts nor unknown files'
238""")
239
240 def test_resolve_taking_other(self):
241 self.run_script("""
242$ bzr resolve --take-other file
243$ bzr commit --strict -m 'No more conflicts nor unknown files'
244""")
245
246
247class TestResolveDuplicateEntry(TestResolveConflicts):
248
249 preamble = """
250$ bzr init trunk
251$ cd trunk
252$ echo 'trunk content' >file
253$ bzr add file
254$ bzr commit -m 'Create trunk'
255$ echo 'trunk content too' >file2
256$ bzr add file2
257$ bzr commit -m 'Add file2 in trunk'
258
259$ bzr branch . -r 1 ../branch
260$ cd ../branch
261$ echo 'branch content' >file2
262$ bzr add file2
263$ bzr commit -m 'Add file2 in branch'
264
265$ bzr merge ../trunk
2662>+N file2
2672>R file2 => file2.moved
2682>Conflict adding file file2. Moved existing file to file2.moved.
2692>1 conflicts encountered.
270"""
271
272 def test_keep_this(self):
273 self.run_script("""
274$ bzr rm file2 --force
275$ bzr mv file2.moved file2
276$ bzr resolve file2
277$ bzr commit --strict -m 'No more conflicts nor unknown files'
278""")
279
280 def test_keep_other(self):
281 self.failIfExists('branch/file2.moved')
282 self.run_script("""
283$ bzr rm file2.moved --force
284$ bzr resolve file2
285$ bzr commit --strict -m 'No more conflicts nor unknown files'
286""")
287 self.failIfExists('branch/file2.moved')
288
289 def test_resolve_taking_this(self):
290 self.run_script("""
291$ bzr resolve --take-this file2
292$ bzr commit --strict -m 'No more conflicts nor unknown files'
293""")
294
295 def test_resolve_taking_other(self):
296 self.run_script("""
297$ bzr resolve --take-other file2
298$ bzr commit --strict -m 'No more conflicts nor unknown files'
299""")
300
301
302class TestResolveUnversionedParent(TestResolveConflicts):
303
304 # FIXME: Add the reverse tests: dir deleted in trunk, file added in branch
305
306 # FIXME: While this *creates* UnversionedParent conflicts, this really only
307 # tests MissingParent resolution :-/
308 preamble = """
309$ bzr init trunk
310$ cd trunk
311$ mkdir dir
312$ bzr add dir
313$ bzr commit -m 'Create trunk'
314$ echo 'trunk content' >dir/file
315$ bzr add dir/file
316$ bzr commit -m 'Add dir/file in trunk'
317
318$ bzr branch . -r 1 ../branch
319$ cd ../branch
320$ bzr rm dir
321$ bzr commit -m 'Remove dir in branch'
322
323$ bzr merge ../trunk
3242>+N dir/
3252>+N dir/file
3262>Conflict adding files to dir. Created directory.
3272>Conflict because dir is not versioned, but has versioned children. Versioned directory.
3282>2 conflicts encountered.
329"""
330
331 def test_take_this(self):
332 self.run_script("""
333$ bzr rm dir --force
334$ bzr resolve dir
335$ bzr commit --strict -m 'No more conflicts nor unknown files'
336""")
337
338 def test_take_other(self):
339 self.run_script("""
340$ bzr resolve dir
341$ bzr commit --strict -m 'No more conflicts nor unknown files'
342""")
343
344
345class TestResolveMissingParent(TestResolveConflicts):
346
347 preamble = """
348$ bzr init trunk
349$ cd trunk
350$ mkdir dir
351$ echo 'trunk content' >dir/file
352$ bzr add
353$ bzr commit -m 'Create trunk'
354$ echo 'trunk content' >dir/file2
355$ bzr add dir/file2
356$ bzr commit -m 'Add dir/file2 in branch'
357
358$ bzr branch . -r 1 ../branch
359$ cd ../branch
360$ bzr rm dir/file --force
361$ bzr rm dir
362$ bzr commit -m 'Remove dir/file'
363
364$ bzr merge ../trunk
3652>+N dir/
3662>+N dir/file2
3672>Conflict adding files to dir. Created directory.
3682>Conflict because dir is not versioned, but has versioned children. Versioned directory.
3692>2 conflicts encountered.
370"""
371
372 def test_keep_them_all(self):
373 self.run_script("""
374$ bzr resolve dir
375$ bzr commit --strict -m 'No more conflicts nor unknown files'
376""")
377
378 def test_adopt_child(self):
379 self.run_script("""
380$ bzr mv dir/file2 file2
381$ bzr rm dir --force
382$ bzr resolve dir
383$ bzr commit --strict -m 'No more conflicts nor unknown files'
384""")
385
386 def test_kill_them_all(self):
387 self.run_script("""
388$ bzr rm dir --force
389$ bzr resolve dir
390$ bzr commit --strict -m 'No more conflicts nor unknown files'
391""")
392
393 def test_resolve_taking_this(self):
394 self.run_script("""
395$ bzr resolve --take-this dir
396$ bzr commit --strict -m 'No more conflicts nor unknown files'
397""")
398
399 def test_resolve_taking_other(self):
400 self.run_script("""
401$ bzr resolve --take-other dir
402$ bzr commit --strict -m 'No more conflicts nor unknown files'
403""")
404
405
406class TestResolveDeletingParent(TestResolveConflicts):
407
408 preamble = """
409$ bzr init trunk
410$ cd trunk
411$ mkdir dir
412$ echo 'trunk content' >dir/file
413$ bzr add
414$ bzr commit -m 'Create trunk'
415$ bzr rm dir/file --force
416$ bzr rm dir --force
417$ bzr commit -m 'Remove dir/file'
418
419$ bzr branch . -r 1 ../branch
420$ cd ../branch
421$ echo 'branch content' >dir/file2
422$ bzr add dir/file2
423$ bzr commit -m 'Add dir/file2 in branch'
424
425$ bzr merge ../trunk
4262>-D dir/file
4272>Conflict: can't delete dir because it is not empty. Not deleting.
4282>Conflict because dir is not versioned, but has versioned children. Versioned directory.
4292>2 conflicts encountered.
430"""
431
432 def test_keep_them_all(self):
433 self.run_script("""
434$ bzr resolve dir
435$ bzr commit --strict -m 'No more conflicts nor unknown files'
436""")
437
438 def test_adopt_child(self):
439 self.run_script("""
440$ bzr mv dir/file2 file2
441$ bzr rm dir --force
442$ bzr resolve dir
443$ bzr commit --strict -m 'No more conflicts nor unknown files'
444""")
445
446 def test_kill_them_all(self):
447 self.run_script("""
448$ bzr rm dir --force
449$ bzr resolve dir
450$ bzr commit --strict -m 'No more conflicts nor unknown files'
451""")
452
453 def test_resolve_taking_this(self):
454 self.run_script("""
455$ bzr resolve --take-this dir
456$ bzr commit --strict -m 'No more conflicts nor unknown files'
457""")
458
459 def test_resolve_taking_other(self):
460 self.run_script("""
461$ bzr resolve --take-other dir
462$ bzr commit --strict -m 'No more conflicts nor unknown files'
463""")
464
465
466class TestResolvePathConflict(TestResolveConflicts):
467
468 preamble = """
469$ bzr init trunk
470$ cd trunk
471$ echo 'Boo!' >file
472$ bzr add
473$ bzr commit -m 'Create trunk'
474$ bzr mv file file-in-trunk
475$ bzr commit -m 'Renamed to file-in-trunk'
476
477$ bzr branch . -r 1 ../branch
478$ cd ../branch
479$ bzr mv file file-in-branch
480$ bzr commit -m 'Renamed to file-in-branch'
481
482$ bzr merge ../trunk
4832>R file-in-branch => file-in-trunk
4842>Path conflict: file-in-branch / file-in-trunk
4852>1 conflicts encountered.
486"""
487
488 def test_keep_source(self):
489 self.run_script("""
490$ bzr resolve file-in-trunk
491$ bzr commit --strict -m 'No more conflicts nor unknown files'
492""")
493
494 def test_keep_target(self):
495 self.run_script("""
496$ bzr mv file-in-trunk file-in-branch
497$ bzr resolve file-in-branch
498$ bzr commit --strict -m 'No more conflicts nor unknown files'
499""")
500
501 def test_resolve_taking_this(self):
502 self.run_script("""
503$ bzr resolve --take-this file-in-branch
504$ bzr commit --strict -m 'No more conflicts nor unknown files'
505""")
506
507 def test_resolve_taking_other(self):
508 self.run_script("""
509$ bzr resolve --take-other file-in-branch
510$ bzr commit --strict -m 'No more conflicts nor unknown files'
511""")
512
513
514class TestResolveParentLoop(TestResolveConflicts):
515
516 preamble = """
517$ bzr init trunk
518$ cd trunk
519$ bzr mkdir dir1
520$ bzr mkdir dir2
521$ bzr commit -m 'Create trunk'
522$ bzr mv dir2 dir1
523$ bzr commit -m 'Moved dir2 into dir1'
524
525$ bzr branch . -r 1 ../branch
526$ cd ../branch
527$ bzr mv dir1 dir2
528$ bzr commit -m 'Moved dir1 into dir2'
529
530$ bzr merge ../trunk
5312>Conflict moving dir2/dir1 into dir2. Cancelled move.
5322>1 conflicts encountered.
533"""
534
535 def test_take_this(self):
536 self.run_script("""
537$ bzr resolve dir2
538$ bzr commit --strict -m 'No more conflicts nor unknown files'
539""")
540
541 def test_take_other(self):
542 self.run_script("""
543$ bzr mv dir2/dir1 dir1
544$ bzr mv dir2 dir1
545$ bzr resolve dir2
546$ bzr commit --strict -m 'No more conflicts nor unknown files'
547""")
548
549 def test_resolve_taking_this(self):
550 self.run_script("""
551$ bzr resolve --take-this dir2
552$ bzr commit --strict -m 'No more conflicts nor unknown files'
553""")
554 self.failUnlessExists('dir2')
555
556 def test_resolve_taking_other(self):
557 self.run_script("""
558$ bzr resolve --take-other dir2
559$ bzr commit --strict -m 'No more conflicts nor unknown files'
560""")
561 self.failUnlessExists('dir1')
562
563
564class TestResolveNonDirectoryParent(TestResolveConflicts):
565
566 preamble = """
567$ bzr init trunk
568$ cd trunk
569$ bzr mkdir foo
570$ bzr commit -m 'Create trunk'
571$ echo "Boing" >foo/bar
572$ bzr add foo/bar
573$ bzr commit -m 'Add foo/bar'
574
575$ bzr branch . -r 1 ../branch
576$ cd ../branch
577$ rm -r foo
578$ echo "Boo!" >foo
579$ bzr commit -m 'foo is now a file'
580
581$ bzr merge ../trunk
5822>+N foo.new/bar
5832>RK foo => foo.new/
584# FIXME: The message is misleading, foo.new *is* a directory when the message
585# is displayed -- vila 090916
5862>Conflict: foo.new is not a directory, but has files in it. Created directory.
5872>1 conflicts encountered.
588"""
589
590 def test_take_this(self):
591 self.run_script("""
592$ bzr rm foo.new --force
593# FIXME: Isn't it weird that foo is now unkown even if foo.new has been put
594# aside ? -- vila 090916
595$ bzr add foo
596$ bzr resolve foo.new
597$ bzr commit --strict -m 'No more conflicts nor unknown files'
598""")
599
600 def test_take_other(self):
601 self.run_script("""
602$ bzr rm foo --force
603$ bzr mv foo.new foo
604$ bzr resolve foo
605$ bzr commit --strict -m 'No more conflicts nor unknown files'
606""")
607
608 def test_resolve_taking_this(self):
609 self.run_script("""
610$ bzr resolve --take-this foo.new
611$ bzr commit --strict -m 'No more conflicts nor unknown files'
612""")
613
614 def test_resolve_taking_other(self):
615 self.run_script("""
616$ bzr resolve --take-other foo.new
617$ bzr commit --strict -m 'No more conflicts nor unknown files'
618""")
619
620
621class TestMalformedTransform(script.TestCaseWithTransportAndScript):
622
623 def test_bug_430129(self):
624 # This is nearly like TestResolveNonDirectoryParent but with branch and
625 # trunk switched. As such it should certainly produce the same
626 # conflict.
627 self.run_script("""
628$ bzr init trunk
629$ cd trunk
630$ bzr mkdir foo
631$ bzr commit -m 'Create trunk'
632$ rm -r foo
633$ echo "Boo!" >foo
634$ bzr commit -m 'foo is now a file'
635
636$ bzr branch . -r 1 ../branch
637$ cd ../branch
638$ echo "Boing" >foo/bar
639$ bzr add foo/bar
640$ bzr commit -m 'Add foo/bar'
641
642$ bzr merge ../trunk
6432>bzr: ERROR: Tree transform is malformed [('unversioned executability', 'new-1')]
644""")
645
646
647class TestResolveActionOption(tests.TestCase):
648
649 def setUp(self):
650 super(TestResolveActionOption, self).setUp()
651 self.options = [conflicts.ResolveActionOption()]
652 self.parser = option.get_optparser(dict((o.name, o)
653 for o in self.options))
654
655 def parse(self, args):
656 return self.parser.parse_args(args)
657
658 def test_unknown_action(self):
659 self.assertRaises(errors.BadOptionValue,
660 self.parse, ['--action', 'take-me-to-the-moon'])
661
662 def test_done(self):
663 opts, args = self.parse(['--action', 'done'])
664 self.assertEqual({'action':'done'}, opts)
665
666 def test_take_this(self):
667 opts, args = self.parse(['--action', 'take-this'])
668 self.assertEqual({'action': 'take_this'}, opts)
669 opts, args = self.parse(['--take-this'])
670 self.assertEqual({'action': 'take_this'}, opts)
671
672 def test_take_other(self):
673 opts, args = self.parse(['--action', 'take-other'])
674 self.assertEqual({'action': 'take_other'}, opts)
675 opts, args = self.parse(['--take-other'])
676 self.assertEqual({'action': 'take_other'}, opts)
174677
=== modified file 'bzrlib/workingtree.py'
--- bzrlib/workingtree.py 2010-01-31 12:05:38 +0000
+++ bzrlib/workingtree.py 2010-02-04 13:06:32 +0000
@@ -111,6 +111,9 @@
111111
112112
113MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"113MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"
114# TODO: Modifying the conflict objects or their type is currently nearly
115# impossible as there is no clear relationship between the working tree format
116# and the conflict list file format.
114CONFLICT_HEADER_1 = "BZR conflict list format 1"117CONFLICT_HEADER_1 = "BZR conflict list format 1"
115118
116ERROR_PATH_NOT_FOUND = 3 # WindowsError errno code, equivalent to ENOENT119ERROR_PATH_NOT_FOUND = 3 # WindowsError errno code, equivalent to ENOENT