7from typing
import List, Optional, Tuple
18 find_path_to_project_root_from_repo_root,
42 # Optional user, e.g. 'git@'
44 # Server, e.g. 'github.com'.
46 # The server-side path. e.g. 'user/project.git'. Must start with an
47 # alphanumeric character so as not to be confusable with a Windows paths
48 # like 'C:/foo/bar' or 'C:\foo\bar'.
72 unset_environ = (
"GIT_DIR",
"GIT_WORK_TREE")
73 default_arg_rev =
"HEAD"
76 def get_base_rev_args(rev: str) -> List[str]:
79 def is_immutable_rev_checkout(self, url: str, dest: str) -> bool:
80 _, rev_options = self.get_url_rev_options(hide_url(url))
90 is_tag_or_branch = bool(self.get_revision_sha(dest,
rev_options.rev)[0])
91 return not is_tag_or_branch
94 version = self.run_command(
96 command_desc=
"git version",
109 Return the current branch, or None if HEAD isn't at a branch
110 (e.g. detached HEAD).
116 args = [
"symbolic-ref",
"-q",
"HEAD"]
117 output = cls.run_command(
119 extra_ok_returncodes=(1,),
127 return ref[
len(
"refs/heads/") :]
132 def get_revision_sha(cls, dest: str, rev: str) -> Tuple[Optional[str], bool]:
134 Return (sha_or_none, is_branch), where sha_or_none is a commit hash
135 if the revision names a remote branch or tag, otherwise None.
138 dest: the repository directory.
139 rev: the revision name.
142 output = cls.run_command(
147 on_returncode=
"ignore",
158 ref_sha, ref_name =
line.split(
" ", maxsplit=2)
162 raise ValueError(f
"unexpected show-ref line: {line!r}")
164 refs[ref_name] = ref_sha
166 branch_ref = f
"refs/remotes/origin/{rev}"
167 tag_ref = f
"refs/tags/{rev}"
180 Return true if rev is a ref or is a commit that we don't have locally.
182 Branches and tags are not considered in this method because they are
183 assumed to be always available locally (which is a normal outcome of
184 ``git clone`` and ``git fetch --tags``).
194 if cls.has_commit(dest, rev):
202 cls, dest: str, url: HiddenText, rev_options: RevOptions
205 Resolve a revision to a new RevOptions object with the SHA1 of the
206 branch, tag, or ref if found.
209 rev_options: a RevOptions object.
214 assert rev
is not None
216 sha, is_branch = cls.get_revision_sha(dest, rev)
228 "Did not find branch or tag '%s', assuming revision or ref.",
232 if not cls._should_fetch(dest, rev):
241 sha = cls.get_revision(dest, rev=
"FETCH_HEAD")
247 def is_commit_id_equal(cls, dest: str, name: Optional[str]) -> bool:
249 Return whether the current commit hash equals the given name.
252 dest: the repository directory.
259 return cls.get_revision(dest) == name
262 self, dest: str, url: HiddenText, rev_options: RevOptions, verbosity: int
265 logger.info(
"Cloning %s%s to %s", url, rev_display, display_path(dest))
267 flags: Tuple[str, ...] = (
"--quiet",)
271 flags = (
"--verbose",
"--progress")
272 if self.get_git_version() >= (2, 17):
279 "--filter=blob:none",
286 self.run_command(make_command(
"clone", *flags, url, dest))
290 rev_options = self.resolve_revision(dest, url, rev_options)
291 branch_name =
getattr(rev_options,
"branch_name",
None)
292 logger.debug(
"Rev options %s, branch_name %s", rev_options, branch_name)
293 if branch_name
is None:
297 cmd_args = make_command(
302 self.run_command(cmd_args, cwd=dest)
303 elif self.get_current_branch(dest) != branch_name:
306 track_branch = f
"origin/{branch_name}"
314 self.run_command(cmd_args, cwd=dest)
316 sha = self.get_revision(dest)
322 self.update_submodules(dest)
324 def switch(self, dest: str, url: HiddenText, rev_options: RevOptions) ->
None:
326 make_command(
"config",
"remote.origin.url", url),
330 self.run_command(cmd_args, cwd=dest)
332 self.update_submodules(dest)
334 def update(self, dest: str, url: HiddenText, rev_options: RevOptions) ->
None:
336 if self.get_git_version() >= (1, 9):
338 self.run_command([
"fetch",
"-q",
"--tags"], cwd=dest)
340 self.run_command([
"fetch",
"-q"], cwd=dest)
342 rev_options = self.resolve_revision(dest, url, rev_options)
344 self.run_command(cmd_args, cwd=dest)
346 self.update_submodules(dest)
349 def get_remote_url(cls, location: str) -> str:
351 Return URL of the first remote encountered.
353 Raises RemoteNotFoundError if the repository does not have a remote
358 stdout = cls.run_command(
359 [
"config",
"--get-regexp",
r"remote\..*\.url"],
360 extra_ok_returncodes=(1,),
367 found_remote = remotes[0]
369 raise RemoteNotFoundError
371 for remote
in remotes:
373 found_remote = remote
376 return cls._git_remote_to_pip_url(
url.strip())
381 Convert a remote url from what git uses to what pip accepts.
383 There are 3 legal forms **url** may take:
385 1. A fully qualified url: ssh://git@example.com/foo/bar.git
386 2. A local project.git folder: /path/to/bare/repository.git
387 3. SCP shorthand for form 1: git@example.com:foo/bar.git
389 Form 1 is output as-is. Form 2 must be converted to URI and form 3 must
390 be converted to form 1.
392 See the corresponding test test_git_remote_url_to_pip() for examples of
393 sample inputs/outputs.
410 def has_commit(cls, location: str, rev: str) -> bool:
412 Check if rev is a commit that is available in the local repository.
416 [
"rev-parse",
"-q",
"--verify",
"sha^" + rev],
418 log_failed_cmd=
False,
420 except InstallationError:
426 def get_revision(cls, location: str, rev: Optional[str] =
None) -> str:
429 current_rev = cls.run_command(
438 def get_subdirectory(cls, location: str) -> Optional[str]:
440 Return the path to Python project root, relative to the repo root.
441 Return None if the project root is in the repo root.
444 git_dir = cls.run_command(
445 [
"rev-parse",
"--git-dir"],
453 return find_path_to_project_root_from_repo_root(location, repo_root)
456 def get_url_rev_and_auth(cls, url: str) -> Tuple[str, Optional[str], AuthInfo]:
458 Prefixes stub URLs like 'user@hostname:user/repo.git' with 'ssh://'.
459 That's required because although they use SSH they sometimes don't
460 work with a ssh:// scheme (e.g. GitHub). But we need a scheme for
461 parsing. Hence we remove it again afterwards and return it as a stub.
465 scheme, netloc, path, query, fragment =
urlsplit(url)
473 (scheme[after_plus:], netloc, newpath, query, fragment),
477 assert "file:" not in url
479 url, rev, user_pass =
super().get_url_rev_and_auth(url)
482 url, rev, user_pass =
super().get_url_rev_and_auth(url)
484 return url, rev, user_pass
491 [
"submodule",
"update",
"--init",
"--recursive",
"-q"],
496 def get_repository_root(cls, location: str) -> Optional[str]:
497 loc =
super().get_repository_root(location)
502 [
"rev-parse",
"--show-toplevel"],
506 on_returncode=
"raise",
507 log_failed_cmd=
False,
511 "could not determine if %s is under git control "
512 "because git is not available",
516 except InstallationError:
521 def should_add_vcs_url_prefix(repo_url: str) -> bool:
522 """In either https or ssh form, requirements must be prefixed with git+."""