R/type-git.R

Defines functions type_git_make_resolution type_git_get_data git_rx installedok_remote_git satisfy_remote_git download_remote_git resolve_remote_git parse_remote_git

parse_remote_git <- function(specs, config, ...) {
  pds <- re_match(specs, git_rx())
  pds$ref <- pds$.text
  cn <- setdiff(colnames(pds), c(".match", ".text"))
  pds <- pds[, cn]
  pds$type <- "git"
  pds$dotgit <- ifelse(grepl("[.]git$", pds$repo), ".git",  "")
  pds$repo <- sub("[.]git$", "", pds$repo)
  pds$package <- ifelse(nzchar(pds$package), pds$package, pds$repo)
  pds$url <- paste0(pds$protocol, "://", pds$host, pds$path, pds$repo, pds$dotgit)
  pds$commitish[pds$commitish == ""] <- "HEAD"
  lapply(
    seq_len(nrow(pds)),
    function(i) as.list(pds[i, ])
  )
}

resolve_remote_git <- function(remote, direct, config, cache,
                               dependencies, ...) {
  type_git_get_data(remote)$
    then(function(resp) {
      data <- list(
        desc = resp$description,
        sha = resp$sha,
        remote = remote,
        direct = direct,
        dependencies = dependencies[[2 - direct]]
      )
      type_git_make_resolution(data)
    })
}

download_remote_git <- function(resolution, target, target_tree,
                                config, cache, which, on_progress) {

  # This is similar to github

  package <- resolution$package
  sha <- resolution$extra[[1]][["remotesha"]] %||% NA_character_
  need_vignettes <- which == "resolution"
  nocache <- is_true_param(resolution$params[[1]], "nocache")
  source <- is_true_param(resolution$params[[1]], "source")

  # in case there is a leftover tree here
  unlink(target_tree, recursive = TRUE)

  ## 1. Check for a binary package

  if (!nocache && !source) {
    ptfm <- current_r_platform()
    hit <- cache$package$copy_to(
      target, package = package, sha256 = sha, built = TRUE,
      platform = ptfm, rversion = current_r_version(),
      .list = c(if (need_vignettes) c(vignettes = TRUE)))

    if (nrow(hit)) {
      return(paste("Had", ptfm)) # TODO: untested currently
    }
  }

  ## 2. Check if we have a built package in the cache. We do not check the
  ## ref or the type, so the package could have been built from a local
  ## ref or from another repo. As long as the sha is the same, we are
  ## fine. If we don't require vignetted, then a package with or without
  ## vignettes is fine.

  if (!nocache) {
    hit <- cache$package$copy_to(
      target, package = package, sha256 = sha, built = TRUE,
      .list = c(if (need_vignettes) c(vignettes = TRUE)))
    if (nrow(hit)) {
      return("Had")
    }
  }

  ## 3. Check if we have a repo snapshot in the cache.

  rel_target <- resolution$target
  subdir <- resolution$remote[[1]]$subdir
  if (!nocache) {
    hit <- cache$package$copy_to(
      target_tree, package = package, sha256 = sha, built = FALSE)
    if (nrow(hit)) {
      return("Had")
    }
  }

  ## 4. Need to download the repo

  url <- resolution$remote[[1]]$url
  sha <- resolution$metadata[[1]][["RemoteSha"]]
  pkgdir <- file.path(target_tree, resolution$package)
  mkdirp(pkgdir)
  p <- async_git_download_repo(url, ref = sha, output = pkgdir)

  # submodules?
  submodules <- config$get("git-submodules")
  if (submodules) {
    p <- p$then(function(x) async_update_git_submodules_r(pkgdir, subdir))
  }

  p$then(function() {
    "Got"
  })
}

satisfy_remote_git <- function(resolution, candidate,
                               config, ...) {
  ## 1. package name must match
  if (resolution$package != candidate$package) {
    return(structure(FALSE, reason = "Package names differ"))
  }

  ## 2. installed ref is good, if it has the same sha
  if (candidate$type == "installed") {
    want_reinst <- is_true_param(resolution$params[[1]], "reinstall")
    if (want_reinst) {
      return(structure(FALSE, reason = "Re-install requested"))
    }
    sha1 <- candidate$extra[[1]][["remotesha"]] %||% NA_character_
    sha2 <- resolution$extra[[1]][["remotesha"]] %||% NA_character_
    ok <- is_string(sha1) && is_string(sha2) && same_sha(sha1, sha2)
    if (!ok) {
      return(structure(FALSE, reason = "Installed package sha mismatch"))
    } else {
      return(TRUE)
    }
  }

  ## 3. local packages satisfy a git remote
  ## See https://github.com/r-lib/pkgdepends/issues/229
  if (candidate$type == "local") {
    return (TRUE)
  }

  ## 3. other refs are also good, as long as they have the same sha
  sha1 <- (if (is.list(candidate$extra[[1]]))candidate$extra[[1]][["remotesha"]]) %||% NA_character_
  sha2 <- resolution$extra[[1]][["remotesha"]] %||% NA_character_
  ok <- is_string(sha1) && is_string(sha2) && same_sha(sha1, sha2)
  if (!ok) {
    return(structure(FALSE, reason = "Candidate package sha mismatch"))
  } else {
    return(TRUE)
  }
}

installedok_remote_git <- function(installed, solution, config, ...) {
  identical(installed$package, solution$package) &&
    identical(installed$version, solution$version) &&
    identical(installed[["remotesha"]], solution$metadata[[1]][["RemoteSha"]])
}

# -------------------------------------------------------------------------

git_rx <- function() {
  paste0(
    "^",
    ## Optional package name
    "(?:(?<package>", package_name_rx(), ")=)?",
    ## Remote type
    "(?:git::)",
    "(?:(?<protocol>[^/]*)://)?",
    "(?<host>[^/]+)",
    "(?<path>[^@]*/)",
    "(?<repo>[^/@]*)",
    "(?:@(?<commitish>.*))?"
  )
}

type_git_get_data <- function(remote) {
  remote
  url <- remote$url
  sha <- NULL
  dsc <- NULL
  desc_path <- if (is.null(remote$subdir) || remote$subdir == "") {
    "DESCRIPTION"
  } else {
    paste0(remote$subdir, "/", "DESCRIPTION")
  }

  async_git_list_files(url, remote$commitish)$
    catch(error = function(err) {
      throw(pkg_error(
        "Failed to download {.path {desc_path}} from git repo at {.url {remote$url}}."
      ), parent = err)
    })$
    then(function(files) {
      sha <<- files$sha
      desc_idx <- which(files$files$path == desc_path)
      if (length(desc_idx) == 0) {
        throw(pkg_error(
          "Could not find {.path {desc_path}} in git repo at {.url {remote$url}}."
        ))
      }
      if (files$files$type[desc_idx] != "blob") {
        throw(pkg_error(
          "{.path {desc_path}} is a directory in git repo at {.url {remote$url}}."
        ))
      }
      files$files$hash[desc_idx]
    })$
    then(function(desc_hash) {
      async_git_download_file(url, desc_hash, output = NULL)$
      catch(error = function(err) {
        throw(pkg_error(
          "Failed to download {.path {desc_path}} from git repo at {.url {remote$url}}."
        ), parent = err)
      })$
      then(function(desc_dl) {
        dsc <<- desc::desc(text = rawToChar(desc_dl$raw))
      })$
      catch(error = function(err) {
        throw(pkg_error(
          "Failed to parse {.path {desc_path}} from git repo at {.url {remote$url}}."
        ), parent = err)
      })
    })$
    then(function() {
      list(sha = sha, description = dsc)
    })
}

type_git_make_resolution <- function(data) {
  deps <- resolve_ref_deps(
    data$desc$get_deps(),
    data$desc$get("Remotes"),
    data$desc$get(extra_config_fields(data$desc$fields()))
  )

  sha <- data$sha
  sha7 <- substr(sha, 1, 7)
  commitish <- data$remote$commitish %|z|% NULL
  package <- data$desc$get_field("Package")
  version <- data$desc$get_field("Version")
  dependencies <- data$dependencies
  unknown <- deps$ref[deps$type %in% dependencies]
  unknown <- setdiff(unknown, c(base_packages(), "R"))

  meta <- c(
    RemoteType = "git",
    RemoteUrl = data$remote$url,
    RemotePkgRef = data$remote$ref,
    RemoteRef = commitish %||% "HEAD",
    RemoteSha = sha
  )

  list(
    ref = data$remote$ref,
    type = data$remote$type,
    direct = data$direct,
    status = "OK",
    package = package,
    version = version,
    license = data$desc$get_field("License", NA_character_),
    sources = data$remote$url,
    target = unclass(sprintf("src/contrib/%s_%s_git_%s", package, version, sha7)),
    remote = list(data$remote),
    deps = list(deps),
    unknown_deps = unknown,
    extra = list(list(remotesha = sha)),
    metadata = meta,
    params = data$remote$params,
    sysreqs = data$desc$get_field("SystemRequirements", "")
  )
}
r-lib/depends documentation built on April 26, 2024, 1:44 p.m.