Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 30 additions & 15 deletions src/nimblepkg/versiondiscovery.nim
Original file line number Diff line number Diff line change
Expand Up @@ -637,18 +637,33 @@ proc collectAllVersions*(package: PackageMinimalInfo, options: Options, getMinim
## Processes top-level dependencies in parallel (default) or sequentially (with --sync).
## Each branch gets its own visited set to avoid race conditions on shared state.

result = newTable[string, PackageVersions]()
if not options.parallelDiscovery:
for pv in package.requires:
var visitedCopy = initHashSet[PkgTuple]()
let resultTable = await processRequirements(pv, visitedCopy, getMinimalPackage, preferredPackages, options, nimBin)
mergeVersionTables(result[], resultTable[])
else:
var futures: seq[Future[TableRef[string, PackageVersions]]] = @[]
for pv in package.requires:
var visitedCopy = initHashSet[PkgTuple]()
futures.add processRequirements(pv, visitedCopy, getMinimalPackage, preferredPackages, options, nimBin)
await allFutures(futures)
for fut in futures:
if not fut.failed:
mergeVersionTables(result[], fut.read()[])
# During discovery we must never block on an interactive git credential
# prompt: a candidate version may require a repo that was deleted or renamed,
# and git would ask for a username/password for it. Dead URLs should fail
# fast so the version gets excluded. An explicit user-set value is respected;
# only the default (unset) case is forced to non-interactive. The previous
# value is restored afterwards, so actual installs keep interactive prompts
# for private repositories.
let hadTerminalPrompt = existsEnv("GIT_TERMINAL_PROMPT")
let prevTerminalPrompt = getEnv("GIT_TERMINAL_PROMPT")
if not hadTerminalPrompt:
putEnv("GIT_TERMINAL_PROMPT", "0")
try:
result = newTable[string, PackageVersions]()
if not options.parallelDiscovery:
for pv in package.requires:
var visitedCopy = initHashSet[PkgTuple]()
let resultTable = await processRequirements(pv, visitedCopy, getMinimalPackage, preferredPackages, options, nimBin)
mergeVersionTables(result[], resultTable[])
else:
var futures: seq[Future[TableRef[string, PackageVersions]]] = @[]
for pv in package.requires:
var visitedCopy = initHashSet[PkgTuple]()
futures.add processRequirements(pv, visitedCopy, getMinimalPackage, preferredPackages, options, nimBin)
await allFutures(futures)
for fut in futures:
if not fut.failed:
mergeVersionTables(result[], fut.read()[])
finally:
if hadTerminalPrompt: putEnv("GIT_TERMINAL_PROMPT", prevTerminalPrompt)
else: delEnv("GIT_TERMINAL_PROMPT")
1 change: 1 addition & 0 deletions tests/tester.nim
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import tuninstall
import tsat
import tver
import tversiondiscovery
import tgitprompt
import tniminstall
import trequireflag
import tdeclarativeparser
Expand Down
59 changes: 59 additions & 0 deletions tests/tgitprompt.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{.used.}
# Tests for: git credential prompts are suppressed during version discovery.
#
# Version discovery probes many candidate URLs, including historical ones whose
# repos may be deleted or renamed. For those, git would otherwise block on an
# interactive `Username for 'https://github.com':` prompt. Discovery must never
# prompt: dead URLs should fail fast so the version gets excluded. Actual
# installs run outside discovery and keep interactive prompts for private
# repositories.

import unittest, os
import std/[tables, options]
import chronos
import nimblepkg/[version, options, packageinfotypes, versiondiscovery]

let nimBin = some("nim")

proc collect(root: PackageMinimalInfo, mock: GetPackageMinimal): TableRef[string, PackageVersions] =
var options = initOptions()
options.parallelDiscovery = false
result = waitFor collectAllVersions(root, options, mock, nimBin = nimBin)

suite "git credential prompts are suppressed during discovery":
test "discovery sets GIT_TERMINAL_PROMPT=0 only when the user didn't set it":
# An explicit user-set value is respected: discovery must not override it,
# and it must be restored afterwards so actual installs keep interactive
# prompts for private repositories.
var promptTotal = 0
var promptSeenZero = 0 # discovery forced non-interactive
var promptSeenUser = 0 # discovery kept the user's value
proc mock(pv: PkgTuple, options: Options, nimBin: Option[string]): Future[seq[PackageMinimalInfo]] {.async.} =
inc promptTotal
case getEnv("GIT_TERMINAL_PROMPT")
of "0": inc promptSeenZero
of "1": inc promptSeenUser
else: discard
return @[PackageMinimalInfo(name: "dep", version: newVersion("1.0.0"))]

let root = PackageMinimalInfo(
name: "root", version: newVersion("1.0.0"), isRoot: true,
requires: @[(name: "dep", ver: VersionRange(kind: verAny))])

# User set GIT_TERMINAL_PROMPT=1: it is respected, not overridden.
putEnv("GIT_TERMINAL_PROMPT", "1")
discard collect(root, mock)
check promptTotal > 0
check promptSeenUser == promptTotal
check promptSeenZero == 0
check getEnv("GIT_TERMINAL_PROMPT") == "1"

# When it wasn't set, discovery forces 0 and removes it afterwards.
delEnv("GIT_TERMINAL_PROMPT")
promptTotal = 0
promptSeenZero = 0
promptSeenUser = 0
discard collect(root, mock)
check promptSeenZero == promptTotal
check promptSeenUser == 0
check not existsEnv("GIT_TERMINAL_PROMPT")
Loading