_git_credential.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. # Copyright 2022-present, the HuggingFace Inc. team.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """Contains utilities to manage Git credentials."""
  15. import re
  16. import subprocess
  17. from ..constants import ENDPOINT
  18. from ._subprocess import run_interactive_subprocess, run_subprocess
  19. GIT_CREDENTIAL_REGEX = re.compile(
  20. r"""
  21. ^\s* # start of line
  22. credential\.helper # credential.helper value
  23. \s*=\s* # separator
  24. ([\w\-\/]+) # the helper name or absolute path (group 1)
  25. (\s|$) # whitespace or end of line
  26. """,
  27. flags=re.MULTILINE | re.IGNORECASE | re.VERBOSE,
  28. )
  29. def list_credential_helpers(folder: str | None = None) -> list[str]:
  30. """Return the list of git credential helpers configured.
  31. See https://git-scm.com/docs/gitcredentials.
  32. Credentials are saved in all configured helpers (store, cache, macOS keychain,...).
  33. Calls "`git credential approve`" internally. See https://git-scm.com/docs/git-credential.
  34. Args:
  35. folder (`str`, *optional*):
  36. The folder in which to check the configured helpers.
  37. """
  38. try:
  39. output = run_subprocess("git config --list", folder=folder).stdout
  40. parsed = _parse_credential_output(output)
  41. return parsed
  42. except subprocess.CalledProcessError as exc:
  43. raise OSError(exc.stderr)
  44. def set_git_credential(token: str, username: str = "hf_user", folder: str | None = None) -> None:
  45. """Save a username/token pair in git credential for HF Hub registry.
  46. Credentials are saved in all configured helpers (store, cache, macOS keychain,...).
  47. Calls "`git credential approve`" internally. See https://git-scm.com/docs/git-credential.
  48. Args:
  49. username (`str`, defaults to `"hf_user"`):
  50. A git username. Defaults to `"hf_user"`, the default user used in the Hub.
  51. token (`str`, defaults to `"hf_user"`):
  52. A git password. In practice, the User Access Token for the Hub.
  53. See https://huggingface.co/settings/tokens.
  54. folder (`str`, *optional*):
  55. The folder in which to check the configured helpers.
  56. """
  57. with run_interactive_subprocess("git credential approve", folder=folder) as (
  58. stdin,
  59. _,
  60. ):
  61. stdin.write(f"url={ENDPOINT}\nusername={username.lower()}\npassword={token}\n\n")
  62. stdin.flush()
  63. def unset_git_credential(username: str = "hf_user", folder: str | None = None) -> None:
  64. """Erase credentials from git credential for HF Hub registry.
  65. Credentials are erased from the configured helpers (store, cache, macOS
  66. keychain,...), if any. If `username` is not provided, any credential configured for
  67. HF Hub endpoint is erased.
  68. Calls "`git credential erase`" internally. See https://git-scm.com/docs/git-credential.
  69. Args:
  70. username (`str`, defaults to `"hf_user"`):
  71. A git username. Defaults to `"hf_user"`, the default user used in the Hub.
  72. folder (`str`, *optional*):
  73. The folder in which to check the configured helpers.
  74. """
  75. with run_interactive_subprocess("git credential reject", folder=folder) as (
  76. stdin,
  77. _,
  78. ):
  79. standard_input = f"url={ENDPOINT}\n"
  80. if username is not None:
  81. standard_input += f"username={username.lower()}\n"
  82. standard_input += "\n"
  83. stdin.write(standard_input)
  84. stdin.flush()
  85. def _parse_credential_output(output: str) -> list[str]:
  86. """Parse the output of `git credential fill` to extract the password.
  87. Args:
  88. output (`str`):
  89. The output of `git credential fill`.
  90. """
  91. # NOTE: If user has set a helper for a custom URL, it will not be caught here.
  92. # Example: `credential.https://huggingface.co.helper=store`
  93. # See: https://github.com/huggingface/huggingface_hub/pull/1138#discussion_r1013324508
  94. return sorted( # Sort for nice printing
  95. { # Might have some duplicates
  96. match[0] for match in GIT_CREDENTIAL_REGEX.findall(output)
  97. }
  98. )