Repository: conan-io/conan

Issue:
[feature] Add option to delete necessary files / directories when running conan config install
An issue that I've run into is that restructuring the structure of Conan profiles in a shared Conan configuration via Git can fail when switching a file to a directory of the same name or vice versa.
This pretty much fails without explanation, so the error message should probably be improved.
Perhaps it would be best to make this default behavior, which wouldn't require anything special to ensure scheduled runs of `conan config install` don't break. Otherwise, there could be an option like `--force` which would all overwriting any conflicting directories / files.

To reproduce this issue:

1. Create a git repository to share Conan configuration through `conan config install`.

```
mkdir conan-config
cd conan-config
mkdir profiles
mkdir -p profiles/debug
touch profiles/debug/address-sanitizer
git init
git add -A
git commit -m "Initial commit"
```

2.  Install the repository using `conan config install`.

```
conan config install .
```

3. Now re-organize such that a directory is now a file in the `conan-config` repository.

```
mv profiles/debug/address-sanitizer profiles/address-sanitizer
rmdir profiles/debug
touch profiles/debug
git add -A
git commit -m "Restructure profiles"
```

4. Re-run `conan config install` to update the profiles.

```
conan config install
Config install:  /var/home/jordan/Projects/test/conan-config
Copying file address-sanitizer to /var/home/jordan/.conan/profiles
Copying file debug to /var/home/jordan/.conan/profiles
ERROR: Failed conan config install:
```

Note the vague error message.

- [X] I've read the [CONTRIBUTING guide](https://github.com/conan-io/conan/blob/develop/.github/CONTRIBUTING.md).



Discussion:
Hi, I think I understand but do you have an error trace you can share? Sounds a bit like a corner case, right?
@lasote Sorry, I probably should have filed this using the bug template. I'll update the issue to describe the exact steps to make this happen. I don't think this is quite a _corner_ case, though, as I think this could reasonably happen under normal usage of the `conan config install` command, especially as things are better organized as they grow and evolve over time.
@lasote I've added steps to reproduce the issue including the error trace... not that it is much of an error trace.
Thanks, I see not that is not that weird and is totally unexpected. I'll check what can we improve.

Resolving patch:
diff --git a/conans/client/conf/config_installer.py b/conans/client/conf/config_installer.py
--- a/conans/client/conf/config_installer.py
+++ b/conans/client/conf/config_installer.py
@@ -82,8 +82,12 @@ def _filecopy(src, filename, dst):
     # copying with permissions that later cause bugs
     src = os.path.join(src, filename)
     dst = os.path.join(dst, filename)
+    # Clear the destination file
     if os.path.exists(dst):
-        remove(dst)
+        if os.path.isdir(dst):  # dst was a directory and now src is a file
+            rmdir(dst)
+        else:
+            remove(dst)
     shutil.copyfile(src, dst)
 
 
@@ -119,6 +123,11 @@ def _process_file(directory, filename, config, cache, output, folder):
                                              relpath)
             else:
                 target_folder = os.path.join(cache.cache_folder, relpath)
+
+            if os.path.exists(target_folder):
+                if os.path.isfile(target_folder):  # Existed as a file and now should be a folder
+                    remove(target_folder)
+
             mkdir(target_folder)
             output.info("Copying file %s to %s" % (filename, target_folder))
             _filecopy(directory, filename, target_folder)


Test patch:
diff --git a/conans/test/functional/command/config_install_test.py b/conans/test/functional/command/config_install_test.py
--- a/conans/test/functional/command/config_install_test.py
+++ b/conans/test/functional/command/config_install_test.py
@@ -804,3 +804,30 @@ def test_config_fails_git_folder(self):
         assert "config_install_interval = 5m" in conf
         dirs = os.listdir(client.cache.cache_folder)
         assert ".git" not in dirs
+
+    def test_config_install_reestructuring_source(self):
+        """  https://github.com/conan-io/conan/issues/9885 """
+
+        folder = temp_folder()
+        client = TestClient()
+        with client.chdir(folder):
+            client.save({"profiles/debug/address-sanitizer": ""})
+            client.run("config install .")
+
+        debug_cache_folder = os.path.join(client.cache_folder, "profiles", "debug")
+        assert os.path.isdir(debug_cache_folder)
+
+        # Now reestructure the files, what it was already a directory in the cache now we want
+        # it to be a file
+        folder = temp_folder()
+        with client.chdir(folder):
+            client.save({"profiles/debug": ""})
+            client.run("config install .")
+        assert os.path.isfile(debug_cache_folder)
+
+        # And now is a directory again
+        folder = temp_folder()
+        with client.chdir(folder):
+            client.save({"profiles/debug/address-sanitizer": ""})
+            client.run("config install .")
+        assert os.path.isdir(debug_cache_folder)