Merge lp:~vila/bzr/646961-attr-error into lp:bzr/2.2

Proposed by Vincent Ladeuil
Status: Merged
Approved by: Vincent Ladeuil
Approved revision: no longer in the source branch.
Merged at revision: 5108
Proposed branch: lp:~vila/bzr/646961-attr-error
Merge into: lp:bzr/2.2
Diff against target: 171 lines (+67/-14)
5 files modified
NEWS (+4/-0)
bzrlib/conflicts.py (+3/-4)
bzrlib/tests/blackbox/test_conflicts.py (+45/-0)
bzrlib/tests/test_workingtree.py (+7/-7)
bzrlib/transform.py (+8/-3)
To merge this branch: bzr merge lp:~vila/bzr/646961-attr-error
Reviewer Review Type Date Requested Status
Andrew Bennetts Needs Fixing
Review via email: mp+39852@code.launchpad.net

Commit message

Properly refuse to obey --take-this and --take-other for text conflicts.

Description of the change

The root cause of bug #646961 is that --take-this and
--take-other are not implemented for text conflicts (and the
reason for this is that users may be misunderstanding the
feature, see
http://wiki.bazaar.canonical.com/VincentLadeuil/ConflictResolution
for details).

This patch fixes the most urgent issue targeted to 2.2 without
adding a new feature (since 2.2 is stable) by fixing several
issues (whack-a-mole entertainment ahead):

- the TextConflict class was inheriting from PathConflict instead
  of Conflict. There was a FIXME about that but I didn't realize
  at the time that this was inheriting action_take_this and
  action_take_other and doing so, pretending to implement
  --take-this and --take-other leading to the bug,

- the _duplicate_entries conflict detection was trying to take
  deleted files into account which makes no sense since they
  don't have a name anymore (while the code path shouldn't have
  been triggered, this is worth fixing anyway).

The TextConflict doesn't inherit from PathConflict anymore which
makes the test complete without resolving the conflict at
all. It's unfortunate that we succeed silently here but fixing
that is out of scope as it's related to several other bugs.
The added test will fail when --take-other is implemented.

To post a comment you must log in.
Revision history for this message
Andrew Bennetts (spiv) wrote :

I have some tweak requests, so:

 review needs-fixing

But basically this looks like a good incremental improvement, thanks!

Details follow.

Vincent Ladeuil wrote:
[...]
> The TextConflict doesn't inherit from PathConflict anymore which
> makes the test complete without resolving the conflict at
> all. It's unfortunate that we succeed silently here but fixing
> that is out of scope as it's related to several other bugs.
> The added test will fail when --take-other is implemented.

That's a shame, but at least it's an improvement over crashing.

How hard is it to emit a warning that not all conflicts were resolved?

> --
> https://code.launchpad.net/~vila/bzr/646961-attr-error/+merge/39852
> Your team bzr-core is requested to review the proposed merge of lp:~vila/bzr/646961-attr-error into lp:bzr/2.2.

> === modified file 'NEWS'
> --- NEWS 2010-10-30 06:59:55 +0000
> +++ NEWS 2010-11-02 14:35:59 +0000
> @@ -19,6 +19,9 @@
> Bug Fixes
> *********
>
> +* ``bzr resolve --take--other <file>`` will not crash anymore if ``<file>`` is
> + involved in a text conflict. (Vincent Ladeuil, #646961)

Typo: should be “--take-other” (three hyphens, not four).

If I understand the cover letter correctly, in this scenario
--take-other still won't resolve the conflict. It might be worth saying
so in the NEWS entry.

[...]
> +++ bzrlib/conflicts.py 2010-11-02 14:35:59 +0000
> @@ -600,12 +600,9 @@
> self._resolve_with_cleanups(tree, 'THIS')
>
>
> -# FIXME: TextConflict is about a single file-id, there never is a conflict_path
> -# attribute so we shouldn't inherit from PathConflict but simply from Conflict
> -
> # TODO: There should be a base revid attribute to better inform the user about
> # how the conflicts were generated.
> -class TextConflict(PathConflict):
> +class TextConflict(Conflict):

Sounds good. Does this mean that PathConflict.as_stanza no longer needs this
logic:

        if self.conflict_path is not None:
            s.add('conflict_path', self.conflict_path)

?

> === modified file 'bzrlib/tests/blackbox/test_conflicts.py'
[...]
> +class TestResolveSilentlyIgnore(script.TestCaseWithTransportAndScript):
> +
> + def test_bug_646961(self):
[...]
> + # The following wil fail when --take-other is implemented
> + # for text conflicts

Shouldn't this test be a KnownFailure, then?

-Andrew.

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

>>>>> Andrew Bennetts <email address hidden> writes:

<snip/>

    > How hard is it to emit a warning that not all conflicts were
    > resolved?

Tedious more than hard, there is a code path that already mentions how
many conflicts were resolved :-/

This is certainly a feature worth generalizing, but it's not trivial
(yet).

Something like 'n conflicts resolved, m remaining conflicts' can be
added to the other code path. I'll look into adding it to a further
submission against trunk.

<snip/>

    > Typo: should be “--take-other” (three hyphens, not four).

Fixed.

    > If I understand the cover letter correctly, in this scenario
    > --take-other still won't resolve the conflict. It might be worth saying
    > so in the NEWS entry.

You're right, this is *still* not resolving the conflict but at least
two users missed this fact, so it's time to implement it but worth
reminding the lack of the feature in the NEWS.

<snip/>

    > Sounds good. Does this mean that PathConflict.as_stanza no longer needs this
    > logic:

    > if self.conflict_path is not None:
    > s.add('conflict_path', self.conflict_path)

    > ?

Probably, I have a note about it, I'll add tests to ensure this is true
in my next proposal.

    >> === modified file 'bzrlib/tests/blackbox/test_conflicts.py'
    > [...]
    >> +class TestResolveSilentlyIgnore(script.TestCaseWithTransportAndScript):
    >> +
    >> + def test_bug_646961(self):
    > [...]
    >> + # The following wil fail when --take-other is implemented
    >> + # for text conflicts

    > Shouldn't this test be a KnownFailure, then?

Well, I used a script test because 1) the bug reporter had written in a
way that I could reuse with only little tweaks 2) to help track the bug by
reproducing it and ensure my patch was fixing it.

But I plan to get rid of it in the next submission so all I wanted was a
guarantee that it will fail once the feature is implemented and the
conflict resolved. So trying to inject a KnownFailure there sounded less
interesting than documenting that the conflict wasn't resolved.

Thanks for the prompt review !

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

So, I will consider that some tweaks being addressed I can land this for 2.2 and will follow-up with a submission against *trunk* for the others (report conflicts resolved and as_stanza).

I'll make yet another submission to *implement* --take-this/--take-other for text conflicts (but I'd like to also add --prefer-this/--prefer-other/--take-both for that and it's a bit more work).

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

sent to pqm by email

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

sent to pqm by email

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

sent to pqm by email

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS'
2--- NEWS 2010-10-30 06:59:55 +0000
3+++ NEWS 2010-11-07 10:22:42 +0000
4@@ -19,6 +19,10 @@
5 Bug Fixes
6 *********
7
8+* ``bzr resolve --take-other <file>`` will not crash anymore if ``<file>``
9+ is involved in a text conflict (but the conflict is still not
10+ resolved). (Vincent Ladeuil, #646961)
11+
12 * Correctly set the Content-Type header when http POSTing to comply
13 with stricter web frameworks. (Vincent Ladeuil, #655100)
14
15
16=== modified file 'bzrlib/conflicts.py'
17--- bzrlib/conflicts.py 2010-05-28 14:15:28 +0000
18+++ bzrlib/conflicts.py 2010-11-07 10:22:42 +0000
19@@ -600,12 +600,9 @@
20 self._resolve_with_cleanups(tree, 'THIS')
21
22
23-# FIXME: TextConflict is about a single file-id, there never is a conflict_path
24-# attribute so we shouldn't inherit from PathConflict but simply from Conflict
25-
26 # TODO: There should be a base revid attribute to better inform the user about
27 # how the conflicts were generated.
28-class TextConflict(PathConflict):
29+class TextConflict(Conflict):
30 """The merge algorithm could not resolve all differences encountered."""
31
32 has_files = True
33@@ -614,6 +611,8 @@
34
35 format = 'Text conflict in %(path)s'
36
37+ rformat = '%(class)s(%(path)r, %(file_id)r)'
38+
39 def associated_filenames(self):
40 return [self.path + suffix for suffix in CONFLICT_SUFFIXES]
41
42
43=== modified file 'bzrlib/tests/blackbox/test_conflicts.py'
44--- bzrlib/tests/blackbox/test_conflicts.py 2010-05-28 14:15:28 +0000
45+++ bzrlib/tests/blackbox/test_conflicts.py 2010-11-07 10:22:42 +0000
46@@ -19,6 +19,8 @@
47 tests,
48 workingtree,
49 )
50+from bzrlib.tests import script
51+
52
53 # FIXME: These don't really look at the output of the conflict commands, just
54 # the number of lines - there should be more examination.
55@@ -126,3 +128,46 @@
56 self.assertEqual('', err)
57 out, err = self.run_bzr('conflicts')
58 self.assertEqual(0, len(out.splitlines()))
59+
60+class TestResolveSilentlyIgnore(script.TestCaseWithTransportAndScript):
61+
62+ def test_bug_646961(self):
63+ self.run_script("""\
64+ $ bzr init base
65+ Created a standalone tree (format: 2a)
66+ $ cd base
67+ $ echo >file1
68+ $ bzr add
69+ adding file1
70+ $ bzr ci -m "stuff"
71+ 2>Committing to: .../base/
72+ 2>added file1
73+ 2>Committed revision 1.
74+ $ cd ..
75+ $ bzr branch base branch
76+ 2>Branched 1 revision(s).
77+ $ cd base
78+ $ echo "1" >> file1
79+ $ bzr ci -m "branch 1"
80+ 2>Committing to: .../base/
81+ 2>modified file1
82+ 2>Committed revision 2.
83+ $ cd ../branch
84+ $ echo "2" >> file1
85+ $ bzr ci -m "branch 2"
86+ 2>Committing to: .../branch/
87+ 2>modified file1
88+ 2>Committed revision 2.
89+ $ cd ../base
90+ $ bzr merge ../branch
91+ 2> M file1
92+ 2>Text conflict in file1
93+ 2>1 conflicts encountered.
94+ # The following succeeds silently without resolving the conflict
95+ $ bzr resolve file1 --take-other
96+ # The following wil fail when --take-other is implemented
97+ # for text conflicts
98+ $ bzr conflicts
99+ Text conflict in file1
100+ """)
101+
102
103=== modified file 'bzrlib/tests/test_workingtree.py'
104--- bzrlib/tests/test_workingtree.py 2010-06-20 11:18:38 +0000
105+++ bzrlib/tests/test_workingtree.py 2010-11-07 10:22:42 +0000
106@@ -349,28 +349,28 @@
107 self.build_tree_contents([('this/hello', 'Hello World')])
108 this.commit('Add World')
109 this.merge_from_branch(other.branch)
110- self.assertEqual([conflicts.TextConflict('hello', None, 'hello_id')],
111+ self.assertEqual([conflicts.TextConflict('hello', 'hello_id')],
112 this.conflicts())
113 this.auto_resolve()
114- self.assertEqual([conflicts.TextConflict('hello', None, 'hello_id')],
115+ self.assertEqual([conflicts.TextConflict('hello', 'hello_id')],
116 this.conflicts())
117 self.build_tree_contents([('this/hello', '<<<<<<<')])
118 this.auto_resolve()
119- self.assertEqual([conflicts.TextConflict('hello', None, 'hello_id')],
120+ self.assertEqual([conflicts.TextConflict('hello', 'hello_id')],
121 this.conflicts())
122 self.build_tree_contents([('this/hello', '=======')])
123 this.auto_resolve()
124- self.assertEqual([conflicts.TextConflict('hello', None, 'hello_id')],
125+ self.assertEqual([conflicts.TextConflict('hello', 'hello_id')],
126 this.conflicts())
127 self.build_tree_contents([('this/hello', '\n>>>>>>>')])
128 remaining, resolved = this.auto_resolve()
129- self.assertEqual([conflicts.TextConflict('hello', None, 'hello_id')],
130+ self.assertEqual([conflicts.TextConflict('hello', 'hello_id')],
131 this.conflicts())
132 self.assertEqual([], resolved)
133 self.build_tree_contents([('this/hello', 'hELLO wORLD')])
134 remaining, resolved = this.auto_resolve()
135 self.assertEqual([], this.conflicts())
136- self.assertEqual([conflicts.TextConflict('hello', None, 'hello_id')],
137+ self.assertEqual([conflicts.TextConflict('hello', 'hello_id')],
138 resolved)
139 self.failIfExists('this/hello.BASE')
140
141@@ -378,7 +378,7 @@
142 tree = self.make_branch_and_tree('tree')
143 self.build_tree(['tree/hello/'])
144 tree.add('hello', 'hello-id')
145- file_conflict = conflicts.TextConflict('file', None, 'hello-id')
146+ file_conflict = conflicts.TextConflict('file', 'hello-id')
147 tree.set_conflicts(conflicts.ConflictList([file_conflict]))
148 tree.auto_resolve()
149
150
151=== modified file 'bzrlib/transform.py'
152--- bzrlib/transform.py 2010-07-21 09:15:47 +0000
153+++ bzrlib/transform.py 2010-11-07 10:22:42 +0000
154@@ -645,9 +645,14 @@
155 if (self._new_name, self._new_parent) == ({}, {}):
156 return conflicts
157 for children in by_parent.itervalues():
158- name_ids = [(self.final_name(t), t) for t in children]
159- if not self._case_sensitive_target:
160- name_ids = [(n.lower(), t) for n, t in name_ids]
161+ name_ids = []
162+ for child_tid in children:
163+ name = self.final_name(child_tid)
164+ if name is not None:
165+ # Keep children only if they still exist in the end
166+ if not self._case_sensitive_target:
167+ name = name.lower()
168+ name_ids.append((name, child_tid))
169 name_ids.sort()
170 last_name = None
171 last_trans_id = None

Subscribers

People subscribed via source and target branches