Repository: conan-io/conan
Pull request #18110 (2025-04-11 07:34:35)

Also do 'git fetch commit' in checkout_from_conandata_coordinates
Changelog: (Fix): Can not checkout commit in PR in Azure DevOps
Docs: Omit

This is necessary when using [git scm](https://docs.conan.io/2/examples/tools/scm/git/capture_scm/git_capture_scm.html) integration with Azure DevOps.
When doing a PR, a new commit is done (target branch + PR-branch), but this commit is not automatically cloned. Probably because it is not referenced by any branch or tag.

Fetching the commit before checking out the commit fixes that.

I do not know how this works in other DevOps platform. However, if the commit is already there, this is basically a no-op. It queries the remote but, if the commit is already there, it does not download anything. So this should be fine for all DevOps platform.

Workaround:
- Do not use `checkout_from_conandata_coordinates()` but:
```
    def source(self):
        sources = self.conan_data["scm"]
        git = Git(self)
        git.clone(url=sources["url"], target=".")
        git.run(f"fetch origin {sources['commit']}")
        git.checkout(commit=sources["commit"])
```
Or use `fetch_commit()` (experimental, but covers this issue):
```
    def source(self):
        sources = self.conan_data["scm"]
        git = Git(self)
        git.fetch_commit(sources["url"], sources["commit"])
```

- [ ] Refer to the issue that supports this Pull Request.
- [x] If the issue has missing info, explain the purpose/use case/pain/need that covers this Pull Request.
- [x] I've read the [Contributing guide](https://github.com/conan-io/conan/blob/develop2/.github/CONTRIBUTING.md).
- [ ] I've followed the PEP8 style guides for Python code.
- [ ] I've opened another PR in the Conan docs repo to the ``develop`` branch, documenting this one.



Patch:
diff --git a/conan/tools/scm/git.py b/conan/tools/scm/git.py
index c7bcb6ffe..a8a7dd0d3 100644
--- a/conan/tools/scm/git.py
+++ b/conan/tools/scm/git.py
@@ -277,8 +277,9 @@ class Git:
     def checkout_from_conandata_coordinates(self):
         """
         Reads the "scm" field from the ``conandata.yml``, that must contain at least "url" and
-        "commit" and then do a ``clone(url, target=".")`` followed by a ``checkout(commit)``.
+        "commit" and then do a ``clone(url, target=".")``, ``fetch <commit>``, followed by a ``checkout(commit)``.
         """
         sources = self._conanfile.conan_data["scm"]
-        self.clone(url=sources["url"], target=".")
+        self.clone(url=sources["url"], target=".", args=["--origin=origin"])
+        self.run(f"fetch origin {sources['commit']}")
         self.checkout(commit=sources["commit"])


Test patch:
diff --git a/test/functional/tools/scm/test_git.py b/test/functional/tools/scm/test_git.py
index e33194333..d3ebd1404 100644
--- a/test/functional/tools/scm/test_git.py
+++ b/test/functional/tools/scm/test_git.py
@@ -727,6 +727,32 @@ class TestGitBasicSCMFlow:
         assert "pkg/0.1: MYCMAKE: mycmake" in c2.out
         assert "pkg/0.1: MYFILE: myheader!" in c2.out
 
+    def test_fetch_commit(self):
+        """ Testing fetch commit
+        """
+        url = git_create_bare_repo()
+        c = TestClient(default_server_user=True)
+        c.run_command('git clone "file://{}" .'.format(url))
+        c.save({"conanfile.py": self.conanfile_scm,
+                "src/myfile.h": "myheader!",
+                "CMakeLists.txt": "mycmake"})
+        c.run_command("git checkout -b mybranch")
+        git_add_changes_commit(folder=c.current_folder)
+        c.run_command("git push --set-upstream origin mybranch")
+        c.run("create .")
+        assert "pkg/0.1: MYCMAKE: mycmake" in c.out
+        assert "pkg/0.1: MYFILE: myheader!" in c.out
+        c.run("upload * -c -r=default")
+        # Create an orphan commit by removing the branch
+        c.run_command("git push origin --delete mybranch")
+        rmdir(c.current_folder)  # Remove current folder to make sure things are not used from here
+
+        # use another fresh client
+        c2 = TestClient(servers=c.servers)
+        c2.run("install --requires=pkg/0.1@ --build=pkg*")
+        assert "pkg/0.1: MYCMAKE: mycmake" in c2.out
+        assert "pkg/0.1: MYFILE: myheader!" in c2.out
+
 
 @pytest.mark.tool("git")
 class TestGitBasicSCMFlowSubfolder: