Skip to content

Commit 12787b5

Browse files
committed
git.credentials: support UseHttpPath
1 parent 29226c4 commit 12787b5

File tree

2 files changed

+79
-9
lines changed

2 files changed

+79
-9
lines changed

src/scmrepo/git/credentials.py

+28-8
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,15 @@ class GitCredentialHelper(CredentialHelper):
107107
>>> password = credentials.password
108108
"""
109109

110-
def __init__(self, command: str):
110+
def __init__(self, command: str, use_http_path: bool = False):
111111
super().__init__()
112112
self._command = command
113113
self._run_kwargs: Dict[str, Any] = {}
114114
if self._command[0] == "!":
115115
# On Windows this will only work in git-bash and/or WSL2
116116
self._run_kwargs["shell"] = True
117117
self._encoding = locale.getpreferredencoding()
118+
self.use_http_path = use_http_path
118119

119120
def _prepare_command(self, action: Optional[str] = None) -> Union[str, List[str]]:
120121
if self._command[0] == "!":
@@ -150,7 +151,12 @@ def get(self, credential: "Credential", **kwargs) -> "Credential":
150151
if not (credential.protocol or credential.host):
151152
raise ValueError("One of protocol, hostname must be provided")
152153
cmd = self._prepare_command("get")
153-
helper_input = [f"{key}={value}" for key, value in credential.items()]
154+
use_path = credential.protocol in ("http", "https") and self.use_http_path
155+
helper_input = [
156+
f"{key}={value}"
157+
for key, value in credential.items()
158+
if key != "path" or use_path
159+
]
154160
helper_input.append("")
155161

156162
try:
@@ -186,7 +192,12 @@ def get(self, credential: "Credential", **kwargs) -> "Credential":
186192
def store(self, credential: "Credential", **kwargs):
187193
"""Store the credential, if applicable to the helper"""
188194
cmd = self._prepare_command("store")
189-
helper_input = [f"{key}={value}" for key, value in credential.items()]
195+
use_path = credential.protocol in ("http", "https") and self.use_http_path
196+
helper_input = [
197+
f"{key}={value}"
198+
for key, value in credential.items()
199+
if key != "path" or use_path
200+
]
190201
helper_input.append("")
191202

192203
try:
@@ -205,7 +216,12 @@ def store(self, credential: "Credential", **kwargs):
205216
def erase(self, credential: "Credential", **kwargs):
206217
"""Remove a matching credential, if any, from the helper’s storage"""
207218
cmd = self._prepare_command("erase")
208-
helper_input = [f"{key}={value}" for key, value in credential.items()]
219+
use_path = credential.protocol in ("http", "https") and self.use_http_path
220+
helper_input = [
221+
f"{key}={value}"
222+
for key, value in credential.items()
223+
if key != "path" or use_path
224+
]
209225
helper_input.append("")
210226

211227
try:
@@ -224,7 +240,7 @@ def erase(self, credential: "Credential", **kwargs):
224240
@staticmethod
225241
def get_matching_commands(
226242
base_url: str, config: Optional[Union["ConfigDict", "StackedConfig"]] = None
227-
):
243+
) -> Iterator[Tuple[str, bool]]:
228244
config = config or StackedConfig.default()
229245
if isinstance(config, StackedConfig):
230246
backends: Iterable["ConfigDict"] = config.backends
@@ -240,7 +256,11 @@ def get_matching_commands(
240256
except KeyError:
241257
# no helper configured
242258
continue
243-
yield command.decode(conf.encoding or sys.getdefaultencoding())
259+
use_http_path = conf.get_boolean(section, "usehttppath", False)
260+
yield (
261+
command.decode(conf.encoding or sys.getdefaultencoding()),
262+
use_http_path,
263+
)
244264

245265

246266
class _CredentialKey(NamedTuple):
@@ -542,8 +562,8 @@ def describe(self, use_path: bool = False, sanitize: bool = True) -> str:
542562
def helpers(self) -> List["CredentialHelper"]:
543563
url = self.url
544564
return [
545-
GitCredentialHelper(command)
546-
for command in GitCredentialHelper.get_matching_commands(url)
565+
GitCredentialHelper(command, use_http_path=use_http_path)
566+
for command, use_http_path in GitCredentialHelper.get_matching_commands(url)
547567
]
548568

549569
def fill(self, interactive: bool = True) -> "Credential":

tests/test_credentials.py

+51-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import io
12
import os
23

34
import pytest
@@ -25,14 +26,30 @@ def test_subprocess_get(git_helper, mocker):
2526
)
2627
),
2728
)
28-
creds = git_helper.get(Credential(protocol="https", host="foo.com"))
29+
creds = git_helper.get(Credential(protocol="https", host="foo.com", path="foo.git"))
2930
assert run.call_args.args[0] == ["git-credential-foo", "get"]
3031
assert run.call_args.kwargs.get("input") == os.linesep.join(
3132
["protocol=https", "host=foo.com", ""]
3233
)
3334
assert creds == Credential(url="https://foo:bar@foo.com")
3435

3536

37+
def test_subprocess_get_use_http_path(git_helper, mocker):
38+
git_helper.use_http_path = True
39+
run = mocker.patch(
40+
"subprocess.run",
41+
return_value=mocker.Mock(
42+
stdout=os.linesep.join(["username=foo", "password=bar", ""])
43+
),
44+
)
45+
creds = git_helper.get(Credential(protocol="https", host="foo.com", path="foo.git"))
46+
assert run.call_args.args[0] == ["git-credential-foo", "get"]
47+
assert run.call_args.kwargs.get("input") == os.linesep.join(
48+
["protocol=https", "host=foo.com", "path=foo.git", ""]
49+
)
50+
assert creds == Credential(username="foo", password="bar")
51+
52+
3653
def test_subprocess_get_failed(git_helper, mocker):
3754
from subprocess import CalledProcessError
3855

@@ -151,3 +168,36 @@ def test_memory_helper_prompt_askpass(mocker):
151168
"/usr/local/bin/my-askpass",
152169
"Password for 'https://foo@foo.com': ",
153170
]
171+
172+
173+
def test_get_matching_commands():
174+
from dulwich.config import ConfigFile
175+
176+
config_file = io.BytesIO(
177+
"""
178+
[credential]
179+
helper = /usr/local/bin/my-helper
180+
UseHttpPath = true
181+
""".encode(
182+
"ascii"
183+
)
184+
)
185+
config_file.seek(0)
186+
config = ConfigFile.from_file(config_file)
187+
assert list(
188+
GitCredentialHelper.get_matching_commands("https://foo.com/foo.git", config)
189+
) == [("/usr/local/bin/my-helper", True)]
190+
191+
config_file = io.BytesIO(
192+
"""
193+
[credential]
194+
helper = /usr/local/bin/my-helper
195+
""".encode(
196+
"ascii"
197+
)
198+
)
199+
config_file.seek(0)
200+
config = ConfigFile.from_file(config_file)
201+
assert list(
202+
GitCredentialHelper.get_matching_commands("https://foo.com/foo.git", config)
203+
) == [("/usr/local/bin/my-helper", False)]

0 commit comments

Comments
 (0)