Repository: conan-io/conan

Issue:
Optimization on SCM ('scm_folder.txt' pointing to local folder) could lead to unexpected package
When the SCM feature is used, there is an optimization to avoid the git commands `git commit`,  `git push` in order to test new sources for `conan create` command: Conan creates a file 'scm_folder.txt' inside the cache that stores the path to the local sources and copies them from it instead of cloning the repository.

This optimization also applies for `conan install <...> --build=ref/v1@user/channel`, so this command will copy the sources from the local folder in order to build `ref` (but those sources could correspond to a different version, package,... to the one supposed to be at the moment when the recipe was created in the cache).




Discussion:
Fix: command `conan create` will use the `scm_folder.txt`, but command `conan install ...` won't. There is a first implementation of a test in https://github.com/conan-io/conan/pull/4193, but it may require more testing

Resolving patch:
diff --git a/conans/client/graph/build_mode.py b/conans/client/graph/build_mode.py
--- a/conans/client/graph/build_mode.py
+++ b/conans/client/graph/build_mode.py
@@ -39,7 +39,7 @@ def __init__(self, params, output):
                 raise ConanException("--build=never not compatible with other options")
         self._unused_patterns = list(self.patterns)
 
-    def forced(self, conan_file, reference):
+    def forced(self, conan_file, ref):
         if self.never:
             return False
         if self.all:
@@ -50,7 +50,7 @@ def forced(self, conan_file, reference):
                                    "build_policy='always'")
             return True
 
-        ref = reference.name
+        ref = ref.name
         # Patterns to match, if package matches pattern, build is forced
         for pattern in self.patterns:
             if fnmatch.fnmatch(ref, pattern):
diff --git a/conans/client/source.py b/conans/client/source.py
--- a/conans/client/source.py
+++ b/conans/client/source.py
@@ -196,7 +196,7 @@ def _run_scm(conanfile, src_folder, local_sources_path, output, cache):
     else:
         # In user space, if revision="auto", then copy
         captured = scm_data.capture_origin or scm_data.capture_revision
-    local_sources_path = local_sources_path if captured else None
+    local_sources_path = local_sources_path if captured and conanfile.develop else None
 
     if local_sources_path:
         excluded = SCM(scm_data, local_sources_path, output).excluded_files


Test patch:
diff --git a/conans/test/functional/scm/test_local_modified.py b/conans/test/functional/scm/test_local_modified.py
new file mode 100644
--- /dev/null
+++ b/conans/test/functional/scm/test_local_modified.py
@@ -0,0 +1,57 @@
+# coding=utf-8
+
+import textwrap
+import unittest
+
+from conans.model.ref import ConanFileReference
+from conans.test.utils.tools import TestClient, create_local_git_repo
+
+
+class SCMFolderObsoleteTest(unittest.TestCase):
+    conanfile = textwrap.dedent("""\
+        from conans import ConanFile, tools
+
+        class Pkg(ConanFile):
+            scm = {"type": "git",
+                   "url": "auto",
+                   "revision": "auto"}
+
+            def build(self):
+                content = tools.load("file.txt")
+                self.output.info(">>>> I'm {}/{}@{}/{}".format(self.name, self.version,
+                                                               self.user, self.channel))
+                self.output.info(">>>> content: {} ".format(content))
+        """)
+
+    def setUp(self):
+        self.reference = "pkg/v1@user/channel"
+        self.t = TestClient(path_with_spaces=False)
+
+        # Create pkg/v1
+        url, _ = create_local_git_repo(files={'conanfile.py': self.conanfile,
+                                              'file.txt': self.reference},
+                                       folder=self.t.current_folder)
+        self.t.runner('git remote add origin {}'.format(url), cwd=self.t.current_folder)
+        self.t.run("create . {}".format(self.reference))
+        self.assertIn(">>>> I'm {}".format(self.reference), self.t.out)
+        self.assertIn(">>>> content: {}".format(self.reference), self.t.out)
+
+        # Change something in the local folder
+        self.new_content = "Updated in the local folder!"
+        self.t.save({'file.txt': self.new_content})
+
+    def test_create_workflow(self):
+        """ Use the 'create' command, local changes are reflected in the cache """
+        self.t.run("create . {}".format(self.reference))
+        self.assertIn(">>>> I'm {}".format(self.reference), self.t.out)
+        self.assertIn(">>>> content: {}".format(self.new_content), self.t.out)
+
+    def test_install_workflow(self):
+        """ Using the install command, it won't be taken into account """
+        t2 = TestClient(base_folder=self.t.base_folder)
+        t2.save({'conanfile.txt': "[requires]\n{}".format(self.reference)})
+        ref = ConanFileReference.loads(self.reference)
+        t2.run("install . --build={}".format(ref.name))
+        self.assertNotIn(self.new_content, t2.out)
+        self.assertIn(">>>> I'm {}".format(self.reference), self.t.out)
+        self.assertIn(">>>> content: {}".format(self.reference), self.t.out)