Merge lp:~vila/bzr/323111-orphan-config-option into lp:bzr

Proposed by Vincent Ladeuil
Status: Merged
Approved by: Vincent Ladeuil
Approved revision: no longer in the source branch.
Merged at revision: 5504
Proposed branch: lp:~vila/bzr/323111-orphan-config-option
Merge into: lp:bzr
Prerequisite: lp:~vila/bzr/323111-deprecate-get-backup-name
Diff against target: 588 lines (+275/-90) (has conflicts)
8 files modified
bzrlib/help_topics/en/conflict-types.txt (+63/-44)
bzrlib/ignores.py (+1/-0)
bzrlib/tests/per_workingtree/test_pull.py (+2/-0)
bzrlib/tests/test_bzrdir.py (+3/-0)
bzrlib/tests/test_transform.py (+59/-12)
bzrlib/transform.py (+125/-25)
doc/en/release-notes/bzr-2.3.txt (+13/-5)
doc/en/whats-new/whats-new-in-2.3.txt (+9/-4)
Text conflict in doc/en/whats-new/whats-new-in-2.3.txt
To merge this branch: bzr merge lp:~vila/bzr/323111-orphan-config-option
Reviewer Review Type Date Requested Status
Martin Pool Needs Fixing
Review via email: mp+35690@code.launchpad.net

Commit message

Provides a ``bzr.transform.orphan_policy`` option to control orphan handling when merging directory deletion.

Description of the change

This introduces a ``bzrlib.transform.orphan_policy`` configuration
variable that control whether orphans are created or not during a
tree transform and how.

The accepted values are:

- never: The previous behaviour, no orphans are ever created
  leaving the directory in a conflicted state that the user has
  to resolve.

- always: The new behaviour, unversioned files are moved into a
  ``bzr-orphans`` directory at the root of the working tree. The
  user is expected to look at the content and remove the
  bzr-orphans directory (the orphans will accumulate otherwise).

There is now an ``orphaning_registry`` in bzrlib.transform that
is used to centralise the various ways to manage orphans and as
the source of valid values for the config variable.

The default value (``always``) is also the fallback if a bogus
value is provided (and a warning emitted in this case).

I made the factory accept a simple callable as requiring a
TreeTransform method would require using a getattr (since we will
have to use the name in the registry to get the bound method
later) for no good reasons. This also leave more room for exotic
implementations (as in alien to TreeTransform, requiring more
house-keeping, etc).

To post a comment you must log in.
Revision history for this message
Martin Pool (mbp) wrote :

You should add something to the user guide: aside from being needed, reading how we describe it to users will help us know if we've got it right. Starting from the text for whatsnew will be ok.

I think the always/never distinction is not the right dimension though, because it seems to assume there is one thing that we want to have either on or off. But I think what we want is, well, actually different policies, which might include:

 * conflict: mark the directory conflicted and don't remove it
 * unversion: mark the containing directory unversioned, but leave it in the tree
 * move: move them to a bzr-orphans directory in the root of the tree
 * delete: just immediately delete them

> I made the factory accept a simple callable as requiring a TreeTransform method

I think, generally, I'm against any interface taking a pure callable. (I was just regretting one I added myself in Launchpad feature flags.) So often it turns out that you also want to ask the thing passed in for a description of itself, or something like that. Passing an object with a well-known method name gives a bit more room to move.

I agree it shouldn't be bound to treetransform details.

188 + # FIXME: There is no tree config, so we use the branch one (it's weird
189 + # to define it this way as orphaning can only occur in a working tree,
190 + # but that's all we have (for now). It will find the option in
191 + # locations,conf or bazaar.conf though) -- vila 20100916
192 + conf = self._tree.branch.get_config()

Perhaps there should be a bug for that.

review: Needs Fixing
Revision history for this message
Andrew Bennetts (spiv) wrote :

Martin Pool wrote:
[...]
> I think the always/never distinction is not the right dimension though,
> because it seems to assume there is one thing that we want to have either on
> or off. But I think what we want is, well, actually different policies, which
> might include:
>
> * conflict: mark the directory conflicted and don't remove it
> * unversion: mark the containing directory unversioned, but leave it in the tree
> * move: move them to a bzr-orphans directory in the root of the tree
> * delete: just immediately delete them

This is a great way to put it, that's what I was thinking too.

For example, I'm imagining plugins adding support for:

orphans = vary_by_file_ext
orphan_policies = pyc:delete,*:move

(as a not very carefully designed strawman...)

Similarly, it would be lovely if support for 'precious' files could be done
initially by a plugin...

> > I made the factory accept a simple callable as requiring a TreeTransform method
>
> I think, generally, I'm against any interface taking a pure callable. (I was
> just regretting one I added myself in Launchpad feature flags.) So often it
> turns out that you also want to ask the thing passed in for a description of
> itself, or something like that. Passing an object with a well-known method
> name gives a bit more room to move.

See also our hook registration API, which asks for a description for its
callables :)

Revision history for this message
Marius Kruger (amanica) wrote :

On 17 September 2010 10:01, Andrew Bennetts For example, I'm imagining
plugins adding support for:
>
>
> orphans = vary_by_file_ext
> orphan_policies = pyc:delete,*:move
>

wouldn't it be more suitable to go in ~/.bazaar/rules then?

Revision history for this message
Martin Pool (mbp) wrote :

On 17 September 2010 18:01, Andrew Bennetts
<email address hidden> wrote:
> Martin Pool wrote:
> [...]
>> I think the always/never distinction is not the right dimension though,
>> because it seems to assume there is one thing that we want to have either on
>> or off.  But I think what we want is, well, actually different policies, which
>> might include:
>>
>>  * conflict: mark the directory conflicted and don't remove it
>>  * unversion: mark the containing directory unversioned, but leave it in the tree
>>  * move: move them to a bzr-orphans directory in the root of the tree
>>  * delete: just immediately delete them
>
> This is a great way to put it, that's what I was thinking too.
>
> For example, I'm imagining plugins adding support for:
>
> orphans = vary_by_file_ext
> orphan_policies = pyc:delete,*:move
>
> (as a not very carefully designed strawman...)

Mm, or better I'd like to see the rules file group them in to classes
of 'precious', 'junk', etc, then perhaps different orphan policies
depending on class.
--
Martin

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

>>>>> Martin Pool <email address hidden> writes:

<snip/>

    > Mm, or better I'd like to see the rules file group them in to classes
    > of 'precious', 'junk', etc, then perhaps different orphan policies
    > depending on class.

That exactly what I feared, you're trying to fit precious/junk handling
into this bug fix which, while related, will not allow addressing the
whole problem.

I am *not* trying to handle precious files here not delete junk files,
I'm just putting unversioned file away when they block deleting a
directory.

That's only *one* aspect and people are suffering from this *today*.

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

>>>>> Marius Kruger <email address hidden> writes:

    > On 17 September 2010 10:01, Andrew Bennetts For example, I'm imagining
    > plugins adding support for:
    >>
    >>
    >> orphans = vary_by_file_ext
    >> orphan_policies = pyc:delete,*:move
    >>

    > wouldn't it be more suitable to go in ~/.bazaar/rules then?

It would be even more suitable to mirror what is done for ignored files
(junk file, .bzrjunk files and so on) and modify all the commands that
should take care of that.

Then we can have:
- versioned files
- unversioned files
  - junk
  - the rest, which include precious files and maybe nothing else

But again, that vastly out-of-scope with this bugfix.

Revision history for this message
Martin Pool (mbp) wrote :

On 17 September 2010 22:37, Vincent Ladeuil <email address hidden> wrote:
>>>>>> Martin Pool <email address hidden> writes:
>
> <snip/>
>
>    > Mm, or better I'd like to see the rules file group them in to classes
>    > of 'precious', 'junk', etc, then perhaps different orphan policies
>    > depending on class.
>
> That exactly what I feared, you're trying to fit precious/junk handling
> into this bug fix which, while related, will not allow addressing the
> whole problem.
>
> I am *not* trying to handle precious files here not delete junk files,
> I'm just putting unversioned file away when they block deleting a
> directory.
>
> That's only *one* aspect and people are suffering from this *today*.

Sorry, I didn't mean to imply this is part of this bug, it was a
discussion of follow-on changes. I think you are quite right to
change just this before bringing in a junk category. However, I do
think you should change the option values.

--
Martin

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

Forwarding to <email address hidden> to get a larger feedback.
>>>>> Martin Pool <email address hidden> writes:

    > On 17 September 2010 22:37, Vincent Ladeuil <email address hidden> wrote:
    >>>>>>> Martin Pool <email address hidden> writes:
    >>
    >> <snip/>
    >>
    >>    > Mm, or better I'd like to see the rules file group them in to classes
    >>    > of 'precious', 'junk', etc, then perhaps different orphan policies
    >>    > depending on class.
    >>
    >> That exactly what I feared, you're trying to fit precious/junk handling
    >> into this bug fix which, while related, will not allow addressing the
    >> whole problem.
    >>
    >> I am *not* trying to handle precious files here not delete junk files,
    >> I'm just putting unversioned file away when they block deleting a
    >> directory.
    >>
    >> That's only *one* aspect and people are suffering from this *today*.

    > Sorry, I didn't mean to imply this is part of this bug, it was a
    > discussion of follow-on changes.

Ha ok, right, I completely misunderstood then.

    > I think you are quite right to change just this before bringing in
    > a junk category. However, I do think you should change the option
    > values.

So, for this submission, and trying to take all applicable remarks into
account, I will:

- rename 'always' to 'move'
- rename 'never' to 'conflict'
- add 'bzr-orphans' to the default ignore list

- delay turning callables into full-fledged ones until the impact of the
  junk/repcious files handling make it clearer what this fix is really
  suppposed to provide (I kind of suspect it won't have to make a
  decision anymore if the unversioned files are handled higher in the
  stack).

and:

- make 'conflict' the default, so people impacted by this bug can turn
  it to 'move' and all the other will remained un-impacted until the
  follow-on changes are implemented properly.

For the follow-on changes, we need a bit more analysis to:

- check which commands should now take the junk files into account (at
  least pull/merge/switch should delete them even before the orphaning
  triggered, clean-tree should handle them appropriately, but they may
  be other. Thoughts ?)

- discuss a bit more what a 'precious' file is.

I'm not clear about what a precious but *unversioned* file is... either
it's really precious and it's versioned or it can be generated but I
dont quite see why it should be called precious then or... what ?

What kind of file should bzr never delete, yet, don't handle (or even
ignore) ? A project settings file ? But in this case such a file
shouldn't be in a directory that don't exist anymore for a given tree
revision no ?

         Vincent

Revision history for this message
Martin Pool (mbp) wrote :

On 22 September 2010 18:57, Vincent Ladeuil <email address hidden> wrote:

> What kind of file should bzr never delete, yet, don't handle (or even
> ignore) ? A project settings file ? But in this case such a file
> shouldn't be in a directory that don't exist anymore for a given tree
> revision no ?

Let's talk about it on the list?

I could guess:
 * project settings
 * build products that are super expensive to produce
 * per-tree configuration (similar things seem to come up a lot from
people running apps within a source tree)
 * for that matter the database produced by a running app

--
Martin

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

> On 22 September 2010 18:57, Vincent Ladeuil <email address hidden> wrote:
>
> > What kind of file should bzr never delete, yet, don't handle (or even
> > ignore) ? A project settings file ? But in this case such a file
> > shouldn't be in a directory that don't exist anymore for a given tree
> > revision no ?
>
> Let's talk about it on the list?
>
> I could guess:
> * project settings
> * build products that are super expensive to produce
> * per-tree configuration (similar things seem to come up a lot from
> people running apps within a source tree)
> * for that matter the database produced by a running app

Clear enough for me, thanks. This hints at 'precious' being clearly: unversioned but not junk.

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

Hmm, someone mentioned an interesting twist: when switching a tree from one branch to another, you want *some* precious files to follow and *some* other to stay:

<zyga_> vila, I just noticed bzr switch-pipe does not store local changes
<zyga_> vila, perhaps the version I'm using is not compatible with bzr (on 10.10)
<vila> zyga_: by store local changes you mean shelve ?
<vila> zyga_: keeping uncommitted changes in the working tree while switching is a feature, I often need to commit code I just wrote while not being in the "right" thread (or pipe)
<zyga> vila, no I mean bzr switch-pipeline
<zyga> vila, I found the issue
<zyga> vila, bzr-pipeline is _fantastic_
<zyga> vila, the issue was, untracked files are _not_ stored/restored by the pipeline
<zyga> vila, so if you add a new file and start hacking on it (but not bzr add it) it will not be hidden by bzr switch-pipeline
<vila> zyga: that's a bzr issue then, there are still unversioned files that you want to keep with you (even if there are unversioned files that you want to hide)
<vila> zyga: examples at https://code.edge.launchpad.net/~vila/bzr/323111-orphan-config-option/+merge/35690
<vila> zyga: comments or bugs with your use case welcome

Revision history for this message
Martin Pool (mbp) wrote :

On 4 October 2010 21:53, Vincent Ladeuil <email address hidden> wrote:
> Hmm, someone mentioned an interesting twist: when switching a tree from one branch to another, you want *some* precious files to follow and *some* other to stay:

Well, the obvious answer there is that he should add but not commit
them, and then they'll dtrt.

Perhaps user feedback comments would be better kept on a bug than
here... (but here's better than nowhere.)

--
Martin

Revision history for this message
Martin Pool (mbp) wrote :

> orhaning
typo

Perhaps there should be user docs for this?

The NEWS entry ought to explain what an orphan is

+orphaning_registry = registry.Registry()
+orphaning_registry.register('conflict', refuse_orphan,
+ 'Never create orphans.')
+orphaning_registry.register('move', move_orphan,
+ 'Move orphans into the bzr-orphans directory.')
+orphaning_registry._set_default_key('conflict')

I think in explaining 'conflict' it's not so much never create them - the transform situtation forces the existence of orphans. Rather, we'll leave them in place and mark the directory conflicted.

         delete_any(self._limbo_name(trans_id))

     def new_orphan(self, trans_id, parent_id):
- """See TreeTransformBase.new_orphan."""
- # Add the orphan dir if it doesn't exist
- orphan_dir = 'bzr-orphans'
- od_id = self.trans_id_tree_path(orphan_dir)
- if self.final_kind(od_id) is None:
- self.create_directory(od_id)
- parent_path = self._tree_id_paths[parent_id]
- # Find a name that doesn't exist yet in the orphan dir
- actual_name = self.final_name(trans_id)
- new_name = self._available_backup_name(actual_name, od_id)
- self.adjust_path(new_name, od_id, trans_id)
- trace.warning('%s has been orphaned in %s'
- % (joinpath(parent_path, actual_name), orphan_dir))
+ # FIXME: There is no tree config, so we use the branch one (it's weird
+ # to define it this way as orphaning can only occur in a working tree,
+ # but that's all we have (for now). It will find the option in
+ # locations,conf or bazaar.conf though) -- vila 20100916
+ conf = self._tree.branch.get_config()
+ conf_var_name = 'bzrlib.transform.orphan_policy'
+ orphan_policy = conf.get_user_option(conf_var_name)
+ default_policy = orphaning_registry.default_key
+ if orphan_policy is None:
+ orphan_policy = default_policy
+ if orphan_policy not in orphaning_registry:
+ trace.warning('%s (from %s) is not a known policy, defaulting to %s'
+ % (orphan_policy, conf_var_name, default_policy))
+ orphan_policy = default_policy
+ handle_orphan = orphaning_registry.get(orphan_policy)
+ handle_orphan(self, trans_id, parent_id)
+

You have a comma in 'locations,conf'.

Won't checking this here mean that you get one warning per orphaned file? Perhaps not necessarily a big deal but it suggests you should check this somewhere earlier, and then hold onto the handler?

otherwise I think this is ok. Perhaps we should file some followon bugs.

I might like a 'just kill them' policy.

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

> Perhaps there should be user docs for this?

Added in bzrlib/help_topics/en/conflict-types.txt

>
> The NEWS entry ought to explain what an orphan is

Done

>
> +orphaning_registry = registry.Registry()
> +orphaning_registry.register('conflict', refuse_orphan,
> + 'Never create orphans.')
> +orphaning_registry.register('move', move_orphan,
> + 'Move orphans into the bzr-orphans directory.')
> +orphaning_registry._set_default_key('conflict')
>
> I think in explaining 'conflict' it's not so much never create them - the
> transform situtation forces the existence of orphans. Rather, we'll leave
> them in place and mark the directory conflicted.

Fixed.

> You have a comma in 'locations,conf'.

Fixed.

>
> Won't checking this here mean that you get one warning per orphaned file?

Yes, but only if the user set a wrong value so I think it's ok.

> Perhaps not necessarily a big deal but it suggests you should check this
> somewhere earlier, and then hold onto the handler?

That would be overkill just avoid multiple valid warnings that the user should fixed.

>
> otherwise I think this is ok. Perhaps we should file some followon bugs.
>

I think we have some for junk/precious handling no ?

> I might like a 'just kill them' policy.

That's what junk file handling should render useless.

I'll land with the fixes mentioned above.

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
=== modified file 'bzrlib/help_topics/en/conflict-types.txt'
--- bzrlib/help_topics/en/conflict-types.txt 2010-04-28 13:15:14 +0000
+++ bzrlib/help_topics/en/conflict-types.txt 2010-10-15 14:58:56 +0000
@@ -26,13 +26,13 @@
26present the conflicting versions, and it's up to you to find the correct26present the conflicting versions, and it's up to you to find the correct
27resolution.27resolution.
2828
29Whatever the conflict is, resolving it is roughly done in two steps::29Whatever the conflict is, resolving it is roughly done in two steps:
3030
31 - modify the working tree content so that the conflicted item is now in the31- modify the working tree content so that the conflicted item is now in the
32 state you want to keep,32 state you want to keep,
3333
34 - inform Bazaar that the conflict is now solved and ask to cleanup any34- inform Bazaar that the conflict is now solved and ask to cleanup any
35 remaining generated information (``bzr resolve <item>``).35 remaining generated information (``bzr resolve <item>``).
3636
37For most conflict types, there are some obvious ways to modify the working37For most conflict types, there are some obvious ways to modify the working
38tree and put it into the desired state. For some types of conflicts, Bazaar38tree and put it into the desired state. For some types of conflicts, Bazaar
@@ -126,9 +126,9 @@
126126
127``bzr resolve`` recognizes the following actions:127``bzr resolve`` recognizes the following actions:
128128
129 - ``--action=take-this`` will issue ``bzr mv FILE.THIS FILE``,129* ``--action=take-this`` will issue ``bzr mv FILE.THIS FILE``,
130 - ``--action=take-other`` will issue ``bzr mv FILE.OTHER FILE``,130* ``--action=take-other`` will issue ``bzr mv FILE.OTHER FILE``,
131 - ``--action=done`` will just mark the conflict as resolved.131* ``--action=done`` will just mark the conflict as resolved.
132132
133Any action will also delete the previously generated ``.BASE``, ``.THIS`` and133Any action will also delete the previously generated ``.BASE``, ``.THIS`` and
134``.OTHER`` files if they are still present in the working tree.134``.OTHER`` files if they are still present in the working tree.
@@ -172,11 +172,11 @@
172To resolve that kind of conflict, you should rebuild FILE from either version172To resolve that kind of conflict, you should rebuild FILE from either version
173or a combination of both.173or a combination of both.
174174
175``bzr resolve`` recognizes the following actions::175``bzr resolve`` recognizes the following actions:
176176
177 - ``--action=take-this`` will issue ``bzr rm FILE ; bzr mv FILE.moved FILE``,177* ``--action=take-this`` will issue ``bzr rm FILE ; bzr mv FILE.moved FILE``,
178 - ``--action=take-other`` will issue ``bzr rm FILE.moved``,178* ``--action=take-other`` will issue ``bzr rm FILE.moved``,
179 - ``--action=done`` will just mark the conflict as resolved.179* ``--action=done`` will just mark the conflict as resolved.
180180
181Note that you must get rid of FILE.moved before using ``--action=done``.181Note that you must get rid of FILE.moved before using ``--action=done``.
182182
@@ -213,13 +213,13 @@
213To resolve that kind of conflict, you should either remove or rename the213To resolve that kind of conflict, you should either remove or rename the
214children or the directory or a combination of both.214children or the directory or a combination of both.
215215
216``bzr resolve`` recognizes the following actions::216``bzr resolve`` recognizes the following actions:
217217
218 - ``--action=take-this`` will issue ``bzr rm directory`` including the218* ``--action=take-this`` will issue ``bzr rm directory`` including the
219 children,219 children,
220 - ``--action=take-other`` will acknowledge Bazaar choice to keep the children220* ``--action=take-other`` will acknowledge Bazaar choice to keep the children
221 and restoring the directory,221 and restoring the directory,
222 - ``--action=done`` will just mark the conflict as resolved.222* ``--action=done`` will just mark the conflict as resolved.
223223
224Bazaar cannot auto-detect when conflicts of this kind have been resolved.224Bazaar cannot auto-detect when conflicts of this kind have been resolved.
225225
@@ -231,18 +231,37 @@
231 Conflict: can't delete DIR because it is not empty. Not deleting.231 Conflict: can't delete DIR because it is not empty. Not deleting.
232232
233This is the opposite of "missing parent". A directory is deleted in the233This is the opposite of "missing parent". A directory is deleted in the
234source, but has new children in the target. Bazaar will retain the directory.234source, but has new children in the target (either because a directory
235Resolving this issue depends very much on the particular scenario.235deletion is merged or because the merge introduce new children). Bazaar
236will retain the directory. Resolving this issue depends very much on the
237particular scenario.
236238
237To resolve that kind of conflict, you should either remove or rename the239To resolve that kind of conflict, you should either remove or rename the
238children or the directory or a combination of both.240children or the directory or a combination of both.
239241
240``bzr resolve`` recognizes the following actions::242``bzr resolve`` recognizes the following actions:
241243
242 - ``--action=take-this`` will acknowledge Bazaar choice to keep the directory,244* ``--action=take-this`` will acknowledge Bazaar choice to keep the directory,
243 - ``--action=take-other`` will issue ``bzr rm directory`` including the 245
244 children,246* ``--action=take-other`` will issue ``bzr rm directory`` including the
245 - ``--action=done`` will just mark the conflict as resolved.247 children,
248
249* ``--action=done`` will just mark the conflict as resolved.
250
251Note that when merging a directory deletion, if unversioned files are
252present, they become potential orphans has they don't have a directory
253parent anymore.
254
255Handling such orphans, *before* the conflict is created, is controlled by
256setting the ``bzr.transform.orphan_policy`` configuration option.
257
258There are two possible values for this option:
259
260* ``conflict`` (the default): will leave the orphans in place and
261 generate a conflicts,
262
263* ``move``: will move the orphans to a ``bzr-orphans`` directory at the root
264 of the working tree with names like ``<file>.~#~``.
246265
247Bazaar cannot auto-detect when conflicts of this kind have been resolved.266Bazaar cannot auto-detect when conflicts of this kind have been resolved.
248267
@@ -259,12 +278,12 @@
259To resolve that kind of conflict, you just have to decide what name should be278To resolve that kind of conflict, you just have to decide what name should be
260retained for the file involved.279retained for the file involved.
261280
262``bzr resolve`` recognizes the following actions::281``bzr resolve`` recognizes the following actions:
263282
264 - ``--action=take-this`` will revert Bazaar choice and keep ``PATH1`` by283* ``--action=take-this`` will revert Bazaar choice and keep ``PATH1`` by
265 issuing ``bzr mv PATH2 PATH1``,284 issuing ``bzr mv PATH2 PATH1``,
266 - ``--action=take-other`` will acknowledge Bazaar choice of keeping ``PATH2``,285* ``--action=take-other`` will acknowledge Bazaar choice of keeping ``PATH2``,
267 - ``--action=done`` will just mark the conflict as resolved.286* ``--action=done`` will just mark the conflict as resolved.
268287
269Bazaar cannot auto-detect when conflicts of this kind have been resolved.288Bazaar cannot auto-detect when conflicts of this kind have been resolved.
270289
@@ -294,13 +313,13 @@
294``black``. To resolve that kind of conflict, you just have to decide what313``black``. To resolve that kind of conflict, you just have to decide what
295name should be retained for the directories involved.314name should be retained for the directories involved.
296315
297``bzr resolve`` recognizes the following actions::316``bzr resolve`` recognizes the following actions:
298317
299 - ``--action=take-this`` will acknowledge Bazaar choice of leaving ``white`` 318* ``--action=take-this`` will acknowledge Bazaar choice of leaving ``white``
300 in ``black``,319 in ``black``,
301 - ``--action=take-other`` will revert Bazaar choice and move ``black`` in320* ``--action=take-other`` will revert Bazaar choice and move ``black`` in
302 ``white`` by issuing ``bzr mv black/white white ; bzr mv black white``,321 ``white`` by issuing ``bzr mv black/white white ; bzr mv black white``,
303 - ``--action=done`` will just mark the conflict as resolved.322* ``--action=done`` will just mark the conflict as resolved.
304323
305Bazaar cannot auto-detect when conflicts of this kind have been resolved.324Bazaar cannot auto-detect when conflicts of this kind have been resolved.
306325
@@ -329,13 +348,13 @@
329To resolve that kind of conflict, you have to decide what name should be348To resolve that kind of conflict, you have to decide what name should be
330retained for the file, directory or symlink involved.349retained for the file, directory or symlink involved.
331350
332``bzr resolve`` recognizes the following actions::351``bzr resolve`` recognizes the following actions:
333352
334 - ``--action=take-this`` will issue ``bzr rm --force foo.new`` and 353* ``--action=take-this`` will issue ``bzr rm --force foo.new`` and
335 ``bzr add foo``,354 ``bzr add foo``,
336 - ``--action=take-other`` will issue ``bzr rm --force foo`` and 355* ``--action=take-other`` will issue ``bzr rm --force foo`` and
337 ``bzr mv foo.new foo``,356 ``bzr mv foo.new foo``,
338 - ``--action=done`` will just mark the conflict as resolved.357* ``--action=done`` will just mark the conflict as resolved.
339358
340Bazaar cannot auto-detect when conflicts of this kind have been resolved.359Bazaar cannot auto-detect when conflicts of this kind have been resolved.
341360
342361
=== modified file 'bzrlib/ignores.py'
--- bzrlib/ignores.py 2010-09-04 15:34:10 +0000
+++ bzrlib/ignores.py 2010-10-15 14:58:56 +0000
@@ -42,6 +42,7 @@
42 '.#*',42 '.#*',
43 '[#]*#',43 '[#]*#',
44 '__pycache__',44 '__pycache__',
45 'bzr-orphans',
45]46]
4647
4748
4849
=== modified file 'bzrlib/tests/per_workingtree/test_pull.py'
--- bzrlib/tests/per_workingtree/test_pull.py 2010-10-15 14:58:56 +0000
+++ bzrlib/tests/per_workingtree/test_pull.py 2010-10-15 14:58:56 +0000
@@ -91,6 +91,8 @@
91 'WorkingTreeFormat2 does not support missing parent conflicts')91 'WorkingTreeFormat2 does not support missing parent conflicts')
92 trunk = self.make_branch_deleting_dir('trunk')92 trunk = self.make_branch_deleting_dir('trunk')
93 work = trunk.bzrdir.sprout('work', revision_id='2').open_workingtree()93 work = trunk.bzrdir.sprout('work', revision_id='2').open_workingtree()
94 work.branch.get_config().set_user_option(
95 'bzr.transform.orphan_policy', 'move')
94 # Add some unversioned files in dir96 # Add some unversioned files in dir
95 self.build_tree(['work/dir/foo',97 self.build_tree(['work/dir/foo',
96 'work/dir/subdir/',98 'work/dir/subdir/',
9799
=== modified file 'bzrlib/tests/test_bzrdir.py'
--- bzrlib/tests/test_bzrdir.py 2010-10-15 14:58:56 +0000
+++ bzrlib/tests/test_bzrdir.py 2010-10-15 14:58:56 +0000
@@ -791,6 +791,9 @@
791 self.build_tree(['tree1/subtree/file'])791 self.build_tree(['tree1/subtree/file'])
792 sub_tree.add('file')792 sub_tree.add('file')
793 tree.commit('Initial commit')793 tree.commit('Initial commit')
794 # The following line force the orhaning to reveal bug #634470
795 tree.branch.get_config().set_user_option(
796 'bzr.transform.orphan_policy', 'move')
794 tree.bzrdir.destroy_workingtree()797 tree.bzrdir.destroy_workingtree()
795 # FIXME: subtree/.bzr is left here which allows the test to pass (or798 # FIXME: subtree/.bzr is left here which allows the test to pass (or
796 # fail :-( ) -- vila 20100909799 # fail :-( ) -- vila 20100909
797800
=== modified file 'bzrlib/tests/test_transform.py'
--- bzrlib/tests/test_transform.py 2010-10-15 14:58:56 +0000
+++ bzrlib/tests/test_transform.py 2010-10-15 14:58:56 +0000
@@ -29,6 +29,7 @@
29 rules,29 rules,
30 symbol_versioning,30 symbol_versioning,
31 tests,31 tests,
32 trace,
32 transform,33 transform,
33 urlutils,34 urlutils,
34 )35 )
@@ -2363,7 +2364,7 @@
2363 # parent2364 # parent
2364 self.assertLength(2, conflicts)2365 self.assertLength(2, conflicts)
23652366
2366 def test_resolve_orphan_non_versioned_file(self):2367 def test_non_versioned_file_create_conflict(self):
2367 wt, tt = self.make_tt_with_versioned_dir()2368 wt, tt = self.make_tt_with_versioned_dir()
2368 dir_tid = tt.trans_id_tree_file_id('dir-id')2369 dir_tid = tt.trans_id_tree_file_id('dir-id')
2369 tt.new_file('file', dir_tid, 'Contents')2370 tt.new_file('file', dir_tid, 'Contents')
@@ -2371,7 +2372,9 @@
2371 tt.unversion_file(dir_tid)2372 tt.unversion_file(dir_tid)
2372 conflicts = resolve_conflicts(tt)2373 conflicts = resolve_conflicts(tt)
2373 # no conflicts or rather: orphaning 'file' resolve the 'dir' conflict2374 # no conflicts or rather: orphaning 'file' resolve the 'dir' conflict
2374 self.assertLength(0, conflicts)2375 self.assertLength(1, conflicts)
2376 self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
2377 conflicts.pop())
23752378
23762379
2377A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),2380A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
@@ -3258,35 +3261,79 @@
32583261
3259class TestOrphan(tests.TestCaseWithTransport):3262class TestOrphan(tests.TestCaseWithTransport):
32603263
3261 # Alternative implementations may want to test:
3262 # - can't create orphan dir
3263 # - orphaning forbidden
3264 # - can't create orphan
3265
3266 def test_no_orphan_for_transform_preview(self):3264 def test_no_orphan_for_transform_preview(self):
3267 tree = self.make_branch_and_tree('tree')3265 tree = self.make_branch_and_tree('tree')
3268 tt = transform.TransformPreview(tree)3266 tt = transform.TransformPreview(tree)
3269 self.addCleanup(tt.finalize)3267 self.addCleanup(tt.finalize)
3270 self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')3268 self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')
32713269
3272 def test_new_orphan(self):3270 def _set_orphan_policy(self, wt, policy):
3273 wt = self.make_branch_and_tree('.')3271 wt.branch.get_config().set_user_option('bzr.transform.orphan_policy',
3272 policy)
3273
3274 def _prepare_orphan(self, wt):
3274 self.build_tree(['dir/', 'dir/foo'])3275 self.build_tree(['dir/', 'dir/foo'])
3275 wt.add(['dir'], ['dir-id'])3276 wt.add(['dir'], ['dir-id'])
3276 wt.commit('add dir')3277 wt.commit('add dir')
3277 tt = transform.TreeTransform(wt)3278 tt = transform.TreeTransform(wt)
3278 self.addCleanup(tt.finalize)3279 self.addCleanup(tt.finalize)
3279 dir_tid = tt.trans_id_tree_path('dir')3280 dir_tid = tt.trans_id_tree_path('dir')
3280 foo_tid = tt.trans_id_tree_path('dir/foo')3281 orphan_tid = tt.trans_id_tree_path('dir/foo')
3281 tt.delete_contents(dir_tid)3282 tt.delete_contents(dir_tid)
3282 tt.unversion_file(dir_tid)3283 tt.unversion_file(dir_tid)
3283 raw_conflicts = tt.find_conflicts()3284 raw_conflicts = tt.find_conflicts()
3284 self.assertLength(1, raw_conflicts)3285 self.assertLength(1, raw_conflicts)
3285 self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])3286 self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])
3287 return tt, orphan_tid
3288
3289 def test_new_orphan_created(self):
3290 wt = self.make_branch_and_tree('.')
3291 self._set_orphan_policy(wt, 'move')
3292 tt, orphan_tid = self._prepare_orphan(wt)
3286 remaining_conflicts = resolve_conflicts(tt)3293 remaining_conflicts = resolve_conflicts(tt)
3287 # Yeah for resolved conflicts !3294 # Yeah for resolved conflicts !
3288 self.assertLength(0, remaining_conflicts)3295 self.assertLength(0, remaining_conflicts)
3289 # We have a new orphan3296 # We have a new orphan
3290 self.assertEquals('foo.~1~', tt.final_name(foo_tid))3297 self.assertEquals('foo.~1~', tt.final_name(orphan_tid))
3291 self.assertEquals('bzr-orphans',3298 self.assertEquals('bzr-orphans',
3292 tt.final_name(tt.final_parent(foo_tid)))3299 tt.final_name(tt.final_parent(orphan_tid)))
3300
3301 def test_never_orphan(self):
3302 wt = self.make_branch_and_tree('.')
3303 self._set_orphan_policy(wt, 'conflict')
3304 tt, orphan_tid = self._prepare_orphan(wt)
3305 remaining_conflicts = resolve_conflicts(tt)
3306 self.assertLength(1, remaining_conflicts)
3307 self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3308 remaining_conflicts.pop())
3309
3310 def test_orphan_error(self):
3311 def bogus_orphan(tt, orphan_id, parent_id):
3312 raise transform.OrphaningError(tt.final_name(orphan_id),
3313 tt.final_name(parent_id))
3314 transform.orphaning_registry.register('bogus', bogus_orphan,
3315 'Raise an error when orphaning')
3316 wt = self.make_branch_and_tree('.')
3317 self._set_orphan_policy(wt, 'bogus')
3318 tt, orphan_tid = self._prepare_orphan(wt)
3319 remaining_conflicts = resolve_conflicts(tt)
3320 self.assertLength(1, remaining_conflicts)
3321 self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3322 remaining_conflicts.pop())
3323
3324 def test_unknown_orphan_policy(self):
3325 wt = self.make_branch_and_tree('.')
3326 # Set a fictional policy nobody ever implemented
3327 self._set_orphan_policy(wt, 'donttouchmypreciouuus')
3328 tt, orphan_tid = self._prepare_orphan(wt)
3329 warnings = []
3330 def warning(*args):
3331 warnings.append(args[0] % args[1:])
3332 self.overrideAttr(trace, 'warning', warning)
3333 remaining_conflicts = resolve_conflicts(tt)
3334 # We fallback to the default policy which create a conflict
3335 self.assertLength(1, remaining_conflicts)
3336 self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3337 remaining_conflicts.pop())
3338 self.assertLength(1, warnings)
3339 self.assertStartsWith(warnings[0], 'donttouchmypreciouuus')
32933340
=== modified file 'bzrlib/transform.py'
--- bzrlib/transform.py 2010-10-15 14:58:56 +0000
+++ bzrlib/transform.py 2010-10-15 14:58:56 +0000
@@ -22,6 +22,7 @@
22from bzrlib import (22from bzrlib import (
23 errors,23 errors,
24 lazy_import,24 lazy_import,
25 registry,
25 )26 )
26lazy_import.lazy_import(globals(), """27lazy_import.lazy_import(globals(), """
27from bzrlib import (28from bzrlib import (
@@ -75,6 +76,7 @@
75 map[key] = value76 map[key] = value
7677
7778
79
78class _TransformResults(object):80class _TransformResults(object):
79 def __init__(self, modified_paths, rename_count):81 def __init__(self, modified_paths, rename_count):
80 object.__init__(self)82 object.__init__(self)
@@ -797,6 +799,32 @@
797 """799 """
798 raise NotImplementedError(self.new_orphan)800 raise NotImplementedError(self.new_orphan)
799801
802 def _get_potential_orphans(self, dir_id):
803 """Find the potential orphans in a directory.
804
805 A directory can't be safely deleted if there are versioned files in it.
806 If all the contained files are unversioned then they can be orphaned.
807
808 The 'None' return value means that the directory contains at least one
809 versioned file and should not be deleted.
810
811 :param dir_id: The directory trans id.
812
813 :return: A list of the orphan trans ids or None if at least one
814 versioned file is present.
815 """
816 orphans = []
817 # Find the potential orphans, stop if one item should be kept
818 for c in self.by_parent()[dir_id]:
819 if self.final_file_id(c) is None:
820 orphans.append(c)
821 else:
822 # We have a versioned file here, searching for orphans is
823 # meaningless.
824 orphans = None
825 break
826 return orphans
827
800 def _affected_ids(self):828 def _affected_ids(self):
801 """Return the set of transform ids affected by the transform"""829 """Return the set of transform ids affected by the transform"""
802 trans_ids = set(self._removed_id)830 trans_ids = set(self._removed_id)
@@ -1310,19 +1338,87 @@
1310 delete_any(self._limbo_name(trans_id))1338 delete_any(self._limbo_name(trans_id))
13111339
1312 def new_orphan(self, trans_id, parent_id):1340 def new_orphan(self, trans_id, parent_id):
1313 """See TreeTransformBase.new_orphan."""1341 # FIXME: There is no tree config, so we use the branch one (it's weird
1314 # Add the orphan dir if it doesn't exist1342 # to define it this way as orphaning can only occur in a working tree,
1315 orphan_dir = 'bzr-orphans'1343 # but that's all we have (for now). It will find the option in
1316 od_id = self.trans_id_tree_path(orphan_dir)1344 # locations.conf or bazaar.conf though) -- vila 20100916
1317 if self.final_kind(od_id) is None:1345 conf = self._tree.branch.get_config()
1318 self.create_directory(od_id)1346 conf_var_name = 'bzr.transform.orphan_policy'
1319 parent_path = self._tree_id_paths[parent_id]1347 orphan_policy = conf.get_user_option(conf_var_name)
1320 # Find a name that doesn't exist yet in the orphan dir1348 default_policy = orphaning_registry.default_key
1321 actual_name = self.final_name(trans_id)1349 if orphan_policy is None:
1322 new_name = self._available_backup_name(actual_name, od_id)1350 orphan_policy = default_policy
1323 self.adjust_path(new_name, od_id, trans_id)1351 if orphan_policy not in orphaning_registry:
1324 trace.warning('%s has been orphaned in %s'1352 trace.warning('%s (from %s) is not a known policy, defaulting to %s'
1325 % (joinpath(parent_path, actual_name), orphan_dir))1353 % (orphan_policy, conf_var_name, default_policy))
1354 orphan_policy = default_policy
1355 handle_orphan = orphaning_registry.get(orphan_policy)
1356 handle_orphan(self, trans_id, parent_id)
1357
1358
1359class OrphaningError(errors.BzrError):
1360
1361 # Only bugs could lead to such exception being seen by the user
1362 internal_error = True
1363 _fmt = "Error while orphaning %s in %s directory"
1364
1365 def __init__(self, orphan, parent):
1366 errors.BzrError.__init__(self)
1367 self.orphan = orphan
1368 self.parent = parent
1369
1370
1371class OrphaningForbidden(OrphaningError):
1372
1373 _fmt = "Policy: %s doesn't allow creating orphans."
1374
1375 def __init__(self, policy):
1376 errors.BzrError.__init__(self)
1377 self.policy = policy
1378
1379
1380def move_orphan(tt, orphan_id, parent_id):
1381 """See TreeTransformBase.new_orphan.
1382
1383 This creates a new orphan in the `bzr-orphans` dir at the root of the
1384 `TreeTransform`.
1385
1386 :param tt: The TreeTransform orphaning `trans_id`.
1387
1388 :param orphan_id: The trans id that should be orphaned.
1389
1390 :param parent_id: The orphan parent trans id.
1391 """
1392 # Add the orphan dir if it doesn't exist
1393 orphan_dir_basename = 'bzr-orphans'
1394 od_id = tt.trans_id_tree_path(orphan_dir_basename)
1395 if tt.final_kind(od_id) is None:
1396 tt.create_directory(od_id)
1397 parent_path = tt._tree_id_paths[parent_id]
1398 # Find a name that doesn't exist yet in the orphan dir
1399 actual_name = tt.final_name(orphan_id)
1400 new_name = tt._available_backup_name(actual_name, od_id)
1401 tt.adjust_path(new_name, od_id, orphan_id)
1402 trace.warning('%s has been orphaned in %s'
1403 % (joinpath(parent_path, actual_name), orphan_dir_basename))
1404
1405
1406def refuse_orphan(tt, orphan_id, parent_id):
1407 """See TreeTransformBase.new_orphan.
1408
1409 This refuses to create orphan, letting the caller handle the conflict.
1410 """
1411 raise OrphaningForbidden('never')
1412
1413
1414orphaning_registry = registry.Registry()
1415orphaning_registry.register(
1416 'conflict', refuse_orphan,
1417 'Leave orphans in place and create a conflict on the directory.')
1418orphaning_registry.register(
1419 'move', move_orphan,
1420 'Move orphans into the bzr-orphans directory.')
1421orphaning_registry._set_default_key('conflict')
13261422
13271423
1328class TreeTransform(DiskTreeTransform):1424class TreeTransform(DiskTreeTransform):
@@ -2859,20 +2955,24 @@
2859 elif c_type == 'missing parent':2955 elif c_type == 'missing parent':
2860 trans_id = conflict[1]2956 trans_id = conflict[1]
2861 if trans_id in tt._removed_contents:2957 if trans_id in tt._removed_contents:
2862 orphans = []2958 cancel_deletion = True
2863 empty = True2959 orphans = tt._get_potential_orphans(trans_id)
2864 # Find the potential orphans, stop if one item should be kept2960 if orphans:
2865 for c in tt.by_parent()[trans_id]:2961 cancel_deletion = False
2866 if tt.final_file_id(c) is None:
2867 orphans.append(c)
2868 else:
2869 empty = False
2870 break
2871 if empty:
2872 # All children are orphans2962 # All children are orphans
2873 for o in orphans:2963 for o in orphans:
2874 tt.new_orphan(o, trans_id)2964 try:
2875 else:2965 tt.new_orphan(o, trans_id)
2966 except OrphaningError:
2967 # Something bad happened so we cancel the directory
2968 # deletion which will leave it in place with a
2969 # conflict. The user can deal with it from there.
2970 # Note that this also catch the case where we don't
2971 # want to create orphans and leave the directory in
2972 # place.
2973 cancel_deletion = True
2974 break
2975 if cancel_deletion:
2876 # Cancel the directory deletion2976 # Cancel the directory deletion
2877 tt.cancel_deletion(trans_id)2977 tt.cancel_deletion(trans_id)
2878 new_conflicts.add(('deleting parent', 'Not deleting',2978 new_conflicts.add(('deleting parent', 'Not deleting',
28792979
=== modified file 'doc/en/release-notes/bzr-2.3.txt'
--- doc/en/release-notes/bzr-2.3.txt 2010-10-15 14:58:56 +0000
+++ doc/en/release-notes/bzr-2.3.txt 2010-10-15 14:58:56 +0000
@@ -175,11 +175,16 @@
175* ``unshelve --preview`` now can show diff in a non-ascii encoding.175* ``unshelve --preview`` now can show diff in a non-ascii encoding.
176 (Andrej A Antonov, #518916)176 (Andrej A Antonov, #518916)
177177
178* When a bzr command remove a previously versioned directory, all178* Deleting a versioned directory can leave orphans: unversioned files that
179 unversioned files are moved to a 'bzr-orphans' directory at the working179 were present don't have a parent anymore. The
180 tree root with backup names (<file>.~#~). This was previously creating180 ``bzr.transform.orphan_policy`` configuration option controls the ``bzr``
181 spurious conflicts during merge, pull or switch operations.181 behaviour: ``conflict`` (the default) leave the orphans in place and
182 (Vincent Ladeuil, #323111)182 create a conflict for the directory, ``move`` create orphans named
183 ``<file>.~#~`` in a ``bzr-orphans`` directory at the root of the working
184 tree. (Vincent Ladeuil, #323111)
185
186Improvements
187************
183188
184Documentation189Documentation
185*************190*************
@@ -187,6 +192,9 @@
187* Correct the documentation for setting up the smart server with Apache.192* Correct the documentation for setting up the smart server with Apache.
188 (Neil Martinsen-Burrell, #311518)193 (Neil Martinsen-Burrell, #311518)
189194
195* Fix rst typos in bzrlib/help_topics/en/conflict-types.txt.
196 (Vincent Ladeuil, #660943)
197
190* Provide more detailed help on the Launchpad plugin at "bzr help198* Provide more detailed help on the Launchpad plugin at "bzr help
191 plugins/launchpad". (Neil Martinsen-Burrell, #589379)199 plugins/launchpad". (Neil Martinsen-Burrell, #589379)
192200
193201
=== modified file 'doc/en/whats-new/whats-new-in-2.3.txt'
--- doc/en/whats-new/whats-new-in-2.3.txt 2010-10-15 14:58:56 +0000
+++ doc/en/whats-new/whats-new-in-2.3.txt 2010-10-15 14:58:56 +0000
@@ -104,10 +104,15 @@
104Improved conflict handling104Improved conflict handling
105**************************105**************************
106106
107* Deleting a versioned directory containing unversioned files will no107* ``pull``, ``merge`` or ``switch`` can lead to conflicts when deleting a
108 longer create a conflict. Instead, the unversioned files will be moved108 versioned directory contains unversioned files. The cause of the conflict
109 into a 'bzr-orphans' directory at the root of the working tree.109 is that deleting the directory will orphan the unversioned files so the
110 (Vincent Ladeuil, #323111)110 user needs to instruct ``bzr`` what do to do about these orpahns. This is
111 controlled by setting the ``bzr.transform.orphan_policy`` configuration
112 variable with a value of ``move``. In this case the unversioned files are
113 moved to a ``bzr-orphans`` directory at the root of the working tree. The
114 default behaviour is specified (if needed) by setting the variable to
115 ``conflict``. (Vincent Ladeuil, #323111)
111116
112>>>>>>> MERGE-SOURCE117>>>>>>> MERGE-SOURCE
113Documentation118Documentation