Move to Parent

This post is inspired by a discussion regarding a coding snack done by lanux128 on DonationCoder. A user requested a tool to add a context menu entry that would copy selected files and folders to the parent folder.

Moving folders is one of those tasks that appear trivial. But as always, the devil is in the details.

To keep it simple, we will assume regular files and folders in a filesystem where paths are unique. The problem we will look at is: Given absolute paths Src and Dst (where Dst is not equal to, or a subfolder of Src), move the contents of Src to Dst.

The first solution that comes to mind is to move files and folders recursively. In pseudocode it would be something along the lines of:

This works — well, almost.

Consider the following folder structure (please forgive the tree output, it was an easy way to get a consistent representation):

We are in \parent\foo and want to move the contents (the subfolder foo and the file log.txt) to \parent. The correct result would be:

The problem is that if we call MoveSimple(\parent\foo, \parent), and we process the subfolder foo first, then we will overwrite \parent\foo\log.txt before it gets moved out to \parent.

You might suggest that we handle files first, and then recurse on subfolders. But that won’t work either:

If we recurse into foo before zeb, we will overwrite \parent\foo\zeb\log.txt before it gets moved out.

By now it should be clear that the culprit is the foo subfolder. With that, we end up writing to parts of the folder structure we are in the process of moving.

More generally, we may run into problems if any destination path touches the source tree.

The sledge hammer fix is to make sure there is no way we can overwrite anything in the source folder, by first moving it to someplace safe, and then to where we want it:

This works, but now we are moving every file and folder twice. Besides the performance impact, it would also make it hard to interrupt the operation and handle errors — the user could end up with files lost in a temporary directory.

Let’s go back to MoveSimple and try to figure out what exactly went wrong. It worked fine as long as we did not write to parts of the source tree that we had not moved yet.

The only way we can end up writing into the source tree, is if Src contains a special subfolder with a relative path that exactly matches the path from Dst to Src. Such a path would be unique.

Moving that special folder is only a problem if there is still something else left in Src that has not yet been moved. If we moved everything else, there is nothing it can overwrite.

So if we check if we run into such a special folder, and make sure we move everything else before it, it should work:

I wrote a simple perl script that implements MoveSafe, and it works for all the test cases in this post, but of course standard disclaimers apply.

There are other ways to implement safely moving a folder, depending on what features you desire. For instance you can make it safe to move a folder to a child folder, which this function does not handle. The aim with this particular method was to get (roughly) the same speed as the simple recursive method.

At this point I started wondering if I was overcomplicating things — if this was in fact all rather trivial. So I set out to check how some popular file managers would fare.

The five I tried were: Directory Opus 9.5.5.0, Total Commander 7.55a, xplorer² 1.8.0.13, XYplorer 9.50, and Windows Explorer (WinXP SP3).

I made two test cases, which are similar to the examples above, but contain a few more files to make sure sorting order does not help any application get it right by chance.

I used a script to generate the test cases to make sure they were identical, and used any internal move command where possible. I answered yes to any dialogs that asked if I wanted to overwrite anything.

The first test case is this (as usual we are in \parent\foo and want to move the contents to \parent):

And the second test case is:

None of the five file managers tested handled these two test correctly. I realize this special condition is not very common, but I still found it interesting that none of them, not even Windows Explorer, appear to be able to move any folder to a parent folder.

13 thoughts on “Move to Parent”

  1. Thanks mouser, glad you like it :-).

    I fixed a bug in MoveSafeHelper — when processing a folder, we can only remove it if we did not find a special path inside it.

    There is one more issue I can see. If the special path is deeper than one, MoveSafe may not remove all of that path it should.

  2. It’s a very special case and something it would be hard for a program to automatically get “right” 100% of the time. What I _think_ you’re really saying is, you want a function to eliminate one level in the directory tree. So in your test case #1 above, given the path \parent\foo\foo\foo, the first “foo” would be removed.

    But what would happen (in that example) if the user also had a.txt and z.txt under foo? The a.txt and z.txt from the subfolder would clash when moved to the parent. Or, what if the user didn’t select ALL files in the sub-folder to move, but only selected for example, ‘foo’ and ‘z.txt’. You can’t eliminate a folder from the path without moving (or deleting) all its contents, but if the user didn’t select ‘a.txt’ as well, it would be left behind.

    Obviously all these situations can be dealt with but I’m just trying to point out it’s not as simple a problem as you have stated, and as it’s a) quite complex and b) very rare, it seems to me that this is something best dealt with by the user, who presumably knows exactly what it is they’re trying to accomplish.

  3. (mistake in previous comment, I meant to say “But what would happen (in that example) if the user also had a.txt and z.txt under parent”, not “… under foo”)

  4. You can do this in Directory Opus by creating a button which runs the following command:

    @nofilenamequoting @runmode hide@set TempFolder={sourcepath|..}SafeMove_{date|yyyyMMdd}{time|HHmmss}Rename FROM . TO "{$TempFolder}"Copy MOVE FILE "{$TempFolder}\*" TO "{sourcepath|..}"cmd.exe /C rmdir "{$TempFolder}"

    It works by moving the current folder out of the way, so it’s a sibling of the parent, then moving everything inside of it into the original parent.

    That will also prompt you if any files in the current directory clash with ones in the parent. If those files are skipped then they’ll be left in the SafeMove_* temporary directory (which is why the last line uses the DOS rmdir command; it avoids deleting the temporary directory if it is not empty). (That’s also why the @runmode hide line is there; it stops a DOS prompt flashing on the screen.)

  5. First, thanks for taking the time to reply here Jonathan.

    What I _think_ you’re really saying is, you want a function to eliminate one level in the directory tree. So in your test case #1 above, given the path \parent\foo\foo\foo, the first “foo” would be removed.

    I am not looking for a special move function, I am trying to point to what I think is a defect in the way move is implemented in most file managers.

    What I am trying to derive is a function that can move the contents of one folder into another folder. In the examples used here, I happen to be moving to the immediate parent, which gives the same result as eliminating the current folder. But that would be an oversimplification — the problem is the same if you move to a folder further up the same path.

    I chose to look at the problem of moving all the contents of a folder to keep the example code as simple as possible. Of course moving a number of selected files and folder is the same as moving each file and then moving the contents of each folder.

    Let me use one of the test cases I looked at while working on the post. It is a case that I think shows the issue well in regards to Directory Opus. We have this folder structure:

    As usual we are in \parent\foo. We want to move the contents (everything below \parent\foo). We want to move it to the folder \parent.

    Now, what I, as a user, would expect this operation to do, is the equivalent of moving those items to a temporary folder on another drive, and then move them into the destination folder.

    That is, figuratively speaking, grabbing the files and folders, lifting them up, and dropping them down onto the destination folder, preserving their tree structure, so any files in subfolders will end up in the corresponding place in the new location.

    This is the desired result:

    If I open Directory Opus and set the left pane to \parent, and the right pane to \parent\foo, then click the select all button on the toolbar, and then the move button, I get this result:

    As you can see, one of the zeb folders has disappeared, and both the files ended up in the same folder. So the move operation did not preserve the structure of the source tree.

    The reason is that DO is doing a simple recursive move. It will recurse into \parent\foo\foo first, then into \parent\foo\foo\zeb, move the file info.txt into \parent\foo\zeb, then recurse out again, deleting the folder. Then when it comes back to \parent\foo it will recurse into \parent\foo\zeb and move that entire folder out.

    Since DO is processing the folders in the order they are listed in the source pane, you can change the sorting order and you will get a different result. Clearly you do not want the result of a move to depend on how the files are sorted.

    I agree it is not a situation that arises often, and that is probably why the file managers I tested are not able to handle it. But I think it is a valid operation on a valid folder structure, and as such it should be possible.

    Or, if the developer chooses not to support it, there should be a warning when encountering such a situation, so the user knows he may get random results based on sorting order.

    I hope this (overly long) comment helped explain the exact details of the issue :).

  6. I’m not sure there is a simple solution to this which would work in all cases.

    You’re advocating a breadth-first recursion to avoid problems when moving things up a folder tree, but that would result in the same sorts of problems when moving things deeper into a tree (i.e. you *want* depth-first recursion then).

    Sure, file managers could try to work out what’s going on and change recursion types accordingly but I’m not sure that added complexity (including testing etc.) would be worthwhile just to address this one, fairly rare/contrived case which IMO is best avoided in the first place and can be solved better in other ways. Anything which copies files already has a billion other cases and issues to deal with — file copying is ridiculously complex :) — so adding more complexity really has to be worth it, especially when doing so may just move the problem into other cases rather than completely solve it.

    Also, when moving things up with parent\one\two\three, you’re still left with the “one” directory afterwards when presumably you would want that to be deleted? (This situation is hidden in the parent\foo\foo\foo example because the first two foo folders get folded together. That isn’t true in the general case, though.)

    I think any time the source and destination folders overlap the user has to think about what they are doing and should aim to avoid operations that depend on the order of recursion. The command in my previous post does that by moving the source folder out of the way — no more overlap — and then moving its contents (and deleting the source folder, if non-empty, so it would work in the parent\one\two\three case).

  7. Thank you for the insightful comments (and DO script) Leo :).

    I’m not sure there is a simple solution to this which would work in all cases.

    I tend to agree there. At least the problem is a lot harder than one would imagine at first.

    You’re advocating a breadth-first recursion to avoid problems when moving things up a folder tree, but that would result in the same sorts of problems when moving things deeper into a tree (i.e. you *want* depth-first recursion then).

    My suggestion is not a breath-first recursion, it merely postpones moving the problematic part of the tree if such exists. And you are right, it will not handle moving a folder deeper into a tree.

    In fact, I am not entirely convinced moving a folder to one of its children makes sense, at least as a single operation. I mean, if you think of “move” as the equivalent of moving to a temporary location and then to the destination, then you would be trying to move to a folder that is no longer present.

    All five file managers I tested will give an error message if you attempt to move a folder to one of its children, while none of them warn about moving to a parent with overlap, as shown in this post.

    In my opinion, if the developer makes a choice to not try to handle this case due to complexity, it would be a good idea to add a warning, similar to when attempting to move to a child. Checking if the situation described occurs is not (much) harder.

  8. Is the “Replace?” prompt already a warning?

    If you move a directory up a level, expecting it to be an atomic operation, then get asked if you want to replace one file with another, at that point you know it’s not doing what you think it is and it’s time to stop, undo and re-think.

  9. If you get a replace prompt, maybe.

    I guess you probably only tried the first example in my post. If you try the second test case at the end of the original post, or the one I posted in my comment above about Directory Opus, you will see there are no warnings at all — just misplaced files and disappeared folders.

    I know this is a rare problem, which is probably why it hasn’t been fixed already, but it is a valid problem in my humble opinion :).

  10. And just for the record, I am not trying to bash any specific file manager here — I am a registered user of several of the ones I tested, including Directory Opus :).

    I just picked the top five most popular from the poll linked.

Leave a Reply