From 16048667c98adedbf2177f9032c72ef58e0c6acb Mon Sep 17 00:00:00 2001 From: Seb M'Caw Date: Mon, 29 Jul 2024 10:59:05 +0100 Subject: [PATCH] Fix changing branch of pins with 'git+ssh://' and 'xyz+https://' urls (#1722) --- src/alire/alire-user_pins.adb | 20 ++++ .../tests/pin/branch-remote-protocols/test.py | 98 +++++++++++++++++++ .../pin/branch-remote-protocols/test.yaml | 4 + 3 files changed, 122 insertions(+) create mode 100644 testsuite/tests/pin/branch-remote-protocols/test.py create mode 100644 testsuite/tests/pin/branch-remote-protocols/test.yaml diff --git a/src/alire/alire-user_pins.adb b/src/alire/alire-user_pins.adb index adf22e83d..cc9a4f725 100644 --- a/src/alire/alire-user_pins.adb +++ b/src/alire/alire-user_pins.adb @@ -7,6 +7,9 @@ with Alire.Utils.User_Input; with Alire.Utils.TTY; with Alire.VFS; +with Ada.Strings.Unbounded; +with AAA.Strings; + with GNAT.OS_Lib; package body Alire.User_Pins is @@ -465,6 +468,8 @@ package body Alire.User_Pins is ----------------- function Load_Remote return Pin is + use Ada.Strings.Unbounded; + use AAA.Strings; Result : Pin := (Kind => To_Git, URL => +This.Checked_Pop (Keys.URL, @@ -473,6 +478,21 @@ package body Alire.User_Pins is Commit => <>, Local_Path => <>); begin + -- "git+ssh://"" and "ssh+git://" are deprecated, so treat them as + -- identical to "ssh://" + if Has_Prefix (To_String (Result.URL), "git+ssh://") + or else Has_Prefix (To_String (Result.URL), "ssh+git://") + then + Replace_Slice (Result.URL, 1, 7, "ssh"); + end if; + + -- Likewise, anything of the form "xyz+https://" should be treated + -- as just "https://" + if Contains (To_String (Result.URL), "+http") then + Result.URL := To_Unbounded_String + (Tail (To_String (Result.URL), '+')); + end if; + if This.Contains (Keys.Branch) and then This.Contains (Keys.Commit) then diff --git a/testsuite/tests/pin/branch-remote-protocols/test.py b/testsuite/tests/pin/branch-remote-protocols/test.py new file mode 100644 index 000000000..047e582c1 --- /dev/null +++ b/testsuite/tests/pin/branch-remote-protocols/test.py @@ -0,0 +1,98 @@ +""" +Check pinning to branches with "git+ssh://" and "xyz+https://" urls +""" + +import os +import subprocess + +from drivers.alr import alr_pin, alr_unpin, init_local_crate +from drivers.helpers import init_git_repo, git_branch +from drivers.asserts import assert_eq + + +# Create a crate with differing branches. +init_local_crate(name="remote", enter=False) +LOCAL_REPO_PATH = os.path.join(os.getcwd(), "remote") +# On the default branch, test_file contains "This is the main branch.\n". +test_file_path = os.path.join(LOCAL_REPO_PATH, "test_file") +with open(test_file_path, "w") as f: + f.write("This is the main branch.\n") +init_git_repo("remote") +os.chdir("remote") +default_branch = git_branch() +# On the "other" branch, test_file contains "This is the other branch.\n". +subprocess.run(["git", "checkout", "-b", "other"]).check_returncode() +with open(test_file_path, "w") as f: + f.write("This is the other branch.\n") +subprocess.run(["git", "add", "test_file"]).check_returncode() +subprocess.run(["git", "commit", "-m", "Change test_file"]).check_returncode() +# Return to the default branch +subprocess.run(["git", "checkout", default_branch]).check_returncode() +os.chdir("..") + + +# Prepare a directory on PATH at which to mock git. +ACTUAL_GIT_PATH = ( + subprocess.run(["bash", "-c", "type -p git"], capture_output=True) + .stdout.decode() + .strip() +) +MOCK_PATH = os.path.join(os.getcwd(), "mock_path") +os.mkdir(MOCK_PATH) +os.environ["PATH"] = f'{MOCK_PATH}:{os.environ["PATH"]}' + + +# Perform the actual tests +URLs = [ + "git+ssh://ssh.gitlab.company-name.com/path/to/repo.git", + "xyz+https://github.com/path/to/repo.git", +] +SANITISED_URLS = [ + "ssh://ssh.gitlab.company-name.com/path/to/repo.git", + "https://github.com/path/to/repo.git", +] +CACHE_TEST_FILE_PATH = "alire/cache/pins/remote/test_file" +for URL, S_URL in zip(URLs, SANITISED_URLS): + # Mock git with a wrapper that naively converts the url into the local path + # to the "remote" crate. + wrapper_script = "\n".join( + [ + "#! /usr/bin/env python", + "import subprocess, sys", + 'if sys.argv[1:] == ["config", "--list"]:', + f' print("remote.origin.url={S_URL}\\n")', + "else:", + " args = [", + f' ("{LOCAL_REPO_PATH}" if a == "{S_URL}" else a)', + " for a in sys.argv[1:]", + " ]", + f' subprocess.run(["{ACTUAL_GIT_PATH}"] + args).check_returncode()', + ] + ) + wrapper_descriptor = os.open( + os.path.join(MOCK_PATH, "git"), + flags=(os.O_WRONLY | os.O_CREAT | os.O_TRUNC), + mode=0o764, + ) + with open(wrapper_descriptor, "w") as f: + f.write(wrapper_script) + + # Create an empty crate, and pin the default branch of the test repo + init_local_crate() + alr_pin("remote", url=URL, branch=default_branch) + with open(CACHE_TEST_FILE_PATH) as f: + assert_eq("This is the main branch.\n", f.read()) + + # Edit pin to point to the other branch, and verify the cached copy changes + # as it should + alr_unpin("remote", update=False) + alr_pin("remote", url=URL, branch="other") + with open(CACHE_TEST_FILE_PATH) as f: + assert_eq("This is the other branch.\n", f.read()) + + +# Restore PATH +os.environ["PATH"] = os.environ["PATH"][len(MOCK_PATH) + 1 :] + + +print("SUCCESS") diff --git a/testsuite/tests/pin/branch-remote-protocols/test.yaml b/testsuite/tests/pin/branch-remote-protocols/test.yaml new file mode 100644 index 000000000..ee8ead706 --- /dev/null +++ b/testsuite/tests/pin/branch-remote-protocols/test.yaml @@ -0,0 +1,4 @@ +driver: python-script +control: + - [SKIP, "skip_linux", "Test is Linux-only"] +indexes: {}