_git_credential.py 4.5 KB

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