Merge lp:~vila/bzr/conflict-manager into lp:bzr
- conflict-manager
- Merge into bzr.dev
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
John A Meinel | Needs Fixing | ||
Review via email: mp+16785@code.launchpad.net |
Commit message
Description of the change
Vincent Ladeuil (vila) wrote : | # |
Aaron Bentley (abentley) wrote : | # |
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...
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://
iEYEARECAAYFAkt
N14An0NCsQrKF6+
=1KQw
-----END PGP SIGNATURE-----
Vincent Ladeuil (vila) wrote : | # |
>>>>> "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...
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
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.
Vincent Ladeuil (vila) wrote : | # |
Revno 4668 takes review comments into account:
- settle on using --take-
- 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://
Discussions may still be needed before the next submissions but this patch
itself shouldn't be controversial.
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-
> - 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://
iEYEARECAAYFAkt
sUwAnjX6ZYNufZN
=h1WN
-----END PGP SIGNATURE-----
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-
>> - 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` ???
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-
> >> - 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://
iEYEARECAAYFAkt
8mgAoNV98mCOlfe
=WUOX
-----END PGP SIGNATURE-----
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.
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.
John A Meinel (jameinel) wrote : | # |
101 +class ResolveActionOp
102 +
103 + def __init__(self):
104 + super(ResolveAc
105 + 'action', 'How to resolve the conflict.',
106 + value_switches=
107 + registry=
108 +
109 +
^- This only seems useful if you were actually going to re-use the
ResolveActionOp
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_
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 NotImplementedE
246 + meth(tree)
^- To avoid namespace collisions, this would probably be better as:
meth = getattr(self, 'action_' + action, None)
...
=== modified file 'bzrlib/
971 --- bzrlib/
972 +++ bzrlib/
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/
Vincent Ladeuil (vila) wrote : | # |
> 101 +class ResolveActionOp
> 102 +
> 103 + def __init__(self):
> 104 + super(ResolveAc
> 105 + 'action', 'How to resolve the conflict.',
> 106 + value_switches=
> 107 + registry=
> 108 +
> 109 +
>
>
> ^- This only seems useful if you were actually going to re-use the
> ResolveActionOp
> 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_
> 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_
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 NotImplementedE
> 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/
> 971 --- bzrlib/
> 972 +++ bzrlib/
> 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/
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
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 |
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.