Repository: conan-io/conan

Issue:
[SCM] [SVN] auto feature behavior depends on how user has checked out local working copy


- [x] I've read the [CONTRIBUTING guide](https://raw.githubusercontent.com/conan-io/conan/develop/.github/CONTRIBUTING.md).
- [x] I've specified the Conan version, operating system version and any tool that can be relevant.
- [x] I've explained the steps to reproduce the error or the motivation/use case of the question/suggestion.

Conan 1.8.2
Python 3.6.5
Windows 7
SVN 1.9.5

Our SVN repository structure is like this:
```
module1/<trunk|tags|branches>/conanfile.py
module2/<trunk|tags|branches>/conanfile.py
...
```

We have the following scm statement in our conanfile.py
```
scm = {
    "type": "svn",
    "url": "auto",
    "revision": "auto",
}
```

The current behavior when calling `conan create` depends on whether someone has checked out all modules from SVN or only one module.

Scenario A:
User A has checked out only module1 from SVN repository `https://svn_repo/module1/trunk` to a local folder `C:\svn\module1\trunk`.
Location of .svn hidden folder: `C:\svn\module1\trunk\.svn`
Location of conan file: `C:\svn\module1\trunk\conanfile.py`
Conan command: `conan create  C:\svn\module1\trunk  user/channel`
In this case the conan SVN scm auto feature behaves like expected.
The conan cache source folder afterwards contains exactly the same content as the local SVN working copy folder for which conan create was called.

Scenario B:
User B has checked out all modules from SVN repository `https://svn_repo` to a local folder `C:\svn`.
Location of .svn hidden folder: `C:\svn\.svn`
Location of conan file: `C:\svn\module1\trunk\conanfile.py`
Conan command: `conan create  C:\svn\module1\trunk  user/channel`
In this case the conan SVN scm auto feature behaves **not** like expected.
The conan cache source folder afterwards contains all modules (module1, module2, ...) instead of only module1 trunk, for which conan create was called.
So it contains the working copy of the SVN base folder 'https://svn_repo' instead of 'https://svn_repo/module1/trunk'

Expected behavior:
The source folder in the local conan cache should contain all subfolders of the local SVN folder where the conanfile.py is located for which conan create was called, not all root folders including other modules checked out locally by the user.
The behavior should not depend on the location of the hidden .svn directory which depends on how a user has checked out his/her local working copy.

Currently we cannot use the SVN scm auto feature, because a lot of users check out their SVN working copy like in scenario B.

Could you please give us a statement on this topic.

Thank You!

Discussion:

Patch:
diff --git a/conans/client/cmd/export.py b/conans/client/cmd/export.py
index 910842592..cc38d61eb 100644
--- a/conans/client/cmd/export.py
+++ b/conans/client/cmd/export.py
@@ -69,12 +69,17 @@ def _capture_export_scm_data(conanfile, conanfile_dir, destination_folder, outpu
         os.unlink(scm_src_file)
 
     scm_data = get_scm_data(conanfile)
-    captured_revision = scm_data.capture_revision if scm_data else False
+    if not scm_data:
+        return None, False
 
-    if not scm_data or not (scm_data.capture_origin or scm_data.capture_revision):
-        return None, captured_revision
+    # Resolve SCMData in the user workspace (someone may want to access CVS or import some py)
+    captured_revision = scm_data.capture_revision
 
     scm = SCM(scm_data, conanfile_dir, output)
+    if scm_data.capture_origin or scm_data.capture_revision:
+        # Generate the scm_folder.txt file pointing to the src_path
+        src_path = scm.get_repo_root()
+        save(scm_src_file, src_path.replace("\\", "/"))
 
     if scm_data.url == "auto":
         origin = scm.get_qualified_remote_url()
@@ -84,17 +89,14 @@ def _capture_export_scm_data(conanfile, conanfile_dir, destination_folder, outpu
             output.warn("Repo origin looks like a local path: %s" % origin)
         output.success("Repo origin deduced by 'auto': %s" % origin)
         scm_data.url = origin
+
     if scm_data.revision == "auto":
         if not scm.is_pristine():
             output.warn("Repo status is not pristine: there might be modified files")
         scm_data.revision = scm.get_revision()
         output.success("Revision deduced by 'auto': %s" % scm_data.revision)
 
-    # Generate the scm_folder.txt file pointing to the src_path
-    src_path = scm.get_repo_root()
-    save(scm_src_file, src_path.replace("\\", "/"))
-    _replace_scm_data_in_conanfile(os.path.join(destination_folder, "conanfile.py"),
-                                   scm_data)
+    _replace_scm_data_in_conanfile(os.path.join(destination_folder, "conanfile.py"), scm_data)
 
     return scm_data, captured_revision
 
@@ -146,7 +148,6 @@ def _replace_scm_data_in_conanfile(conanfile_path, scm_data):
     content = content if not headers else ''.join(headers) + content
     save(conanfile_path, content)
 
-
 def _export_conanfile(conanfile_path, output, client_cache, conanfile, conan_ref, keep_source):
 
     exports_folder = client_cache.export(conan_ref)


Test patch:
diff --git a/conans/test/functional/old/scm_test.py b/conans/test/functional/old/scm_test.py
index e32a0ef97..c4ced38f1 100644
--- a/conans/test/functional/old/scm_test.py
+++ b/conans/test/functional/old/scm_test.py
@@ -473,6 +473,81 @@ class ConanLib(ConanFile):
         data2 = json.loads(the_json)
         self.assertEquals(data, data2)
 
+    def test_git_delegated_function(self):
+        conanfile = """
+import os
+from conans import ConanFile
+from conans.client.tools.scm import Git
+
+def get_revision():
+    here = os.path.dirname(__file__)
+    git = Git(here)
+    return git.get_commit()
+
+def get_url():
+    def nested_url():
+        here = os.path.dirname(__file__)
+        git = Git(here)
+        return git.get_remote_url()
+    return nested_url()
+
+class MyLib(ConanFile):
+    name = "issue"
+    version = "3831"
+    scm = {'type': 'git', 'url': get_url(), 'revision': get_revision()}
+
+"""
+        self.client.save({"conanfile.py": conanfile})
+        path, commit = create_local_git_repo(folder=self.client.current_folder)
+        self.client.runner('git remote add origin "%s"' % path.replace("\\", "/"), cwd=path)
+
+        self.client.run("export . user/channel")
+        reference = ConanFileReference.loads("issue/3831@user/channel")
+        exported_conanfile = self.client.client_cache.conanfile(reference)
+        content = load(exported_conanfile)
+        self.assertIn(commit, content)
+
+    def test_delegated_python_code(self):
+        client = TestClient()
+        code_file = """
+import os
+from conans.tools import Git
+from conans import ConanFile
+
+def get_commit():
+    here = os.getcwd()
+    git = Git(here)
+    return git.get_commit()
+
+class MyLib(ConanFile):
+    pass
+"""
+        client.save({"conanfile.py": code_file})
+        client.run("export . tool/0.1@user/testing")
+
+        conanfile = """from conans import ConanFile, python_requires
+from conans.tools import load
+tool = python_requires("tool/0.1@user/testing")
+
+class MyLib(ConanFile):
+    scm = {'type': 'git', 'url': '%s', 'revision': tool.get_commit()}
+    def build(self):
+        self.output.info("File: {}".format(load("file.txt")))
+""" % client.current_folder.replace("\\", "/")
+
+        client.save({"conanfile.py": conanfile, "file.txt": "hello!"})
+        path, commit = create_local_git_repo(folder=client.current_folder)
+        client.runner('git remote add origin "%s"' % path.replace("\\", "/"), cwd=path)
+
+        client.run("export . pkg/0.1@user/channel")
+        reference = ConanFileReference.loads("pkg/0.1@user/channel")
+        exported_conanfile = client.client_cache.conanfile(reference)
+        content = load(exported_conanfile)
+        self.assertIn(commit, content)
+        # Check that everything works and is usable
+        client.run("install pkg/0.1@user/channel --build=missing")
+        self.assertIn("pkg/0.1@user/channel: File: hello!", client.out)
+
 
 @attr('svn')
 class SVNSCMTest(SVNLocalRepoTestCase):