Skip to content

Commit 4f0e352

Browse files
authored
Merge pull request #12283 from DeterminateSystems/type-safe-git-url
Git fetcher: Replace RepoInfo::url by a std::variant
2 parents 43a170a + f5548c1 commit 4f0e352

File tree

7 files changed

+80
-59
lines changed

7 files changed

+80
-59
lines changed

src/libfetchers/fetchers.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ void Input::clone(const Path & destDir) const
358358
scheme->clone(*this, destDir);
359359
}
360360

361-
std::optional<Path> Input::getSourcePath() const
361+
std::optional<std::filesystem::path> Input::getSourcePath() const
362362
{
363363
assert(scheme);
364364
return scheme->getSourcePath(*this);
@@ -461,7 +461,7 @@ Input InputScheme::applyOverrides(
461461
return input;
462462
}
463463

464-
std::optional<Path> InputScheme::getSourcePath(const Input & input) const
464+
std::optional<std::filesystem::path> InputScheme::getSourcePath(const Input & input) const
465465
{
466466
return {};
467467
}

src/libfetchers/fetchers.hh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ public:
164164

165165
void clone(const Path & destDir) const;
166166

167-
std::optional<Path> getSourcePath() const;
167+
std::optional<std::filesystem::path> getSourcePath() const;
168168

169169
/**
170170
* Write a file to this input, for input types that support
@@ -247,7 +247,7 @@ struct InputScheme
247247

248248
virtual void clone(const Input & input, const Path & destDir) const;
249249

250-
virtual std::optional<Path> getSourcePath(const Input & input) const;
250+
virtual std::optional<std::filesystem::path> getSourcePath(const Input & input) const;
251251

252252
virtual void putFile(
253253
const Input & input,

src/libfetchers/git.cc

Lines changed: 71 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ struct GitInputScheme : InputScheme
297297

298298
Strings args = {"clone"};
299299

300-
args.push_back(repoInfo.url);
300+
args.push_back(repoInfo.locationToArg());
301301

302302
if (auto ref = input.getRef()) {
303303
args.push_back("--branch");
@@ -311,11 +311,9 @@ struct GitInputScheme : InputScheme
311311
runProgram("git", true, args, {}, true);
312312
}
313313

314-
std::optional<Path> getSourcePath(const Input & input) const override
314+
std::optional<std::filesystem::path> getSourcePath(const Input & input) const override
315315
{
316-
auto repoInfo = getRepoInfo(input);
317-
if (repoInfo.isLocal) return repoInfo.url;
318-
return std::nullopt;
316+
return getRepoInfo(input).getPath();
319317
}
320318

321319
void putFile(
@@ -325,14 +323,15 @@ struct GitInputScheme : InputScheme
325323
std::optional<std::string> commitMsg) const override
326324
{
327325
auto repoInfo = getRepoInfo(input);
328-
if (!repoInfo.isLocal)
326+
auto repoPath = repoInfo.getPath();
327+
if (!repoPath)
329328
throw Error("cannot commit '%s' to Git repository '%s' because it's not a working tree", path, input.to_string());
330329

331-
writeFile((CanonPath(repoInfo.url) / path).abs(), contents);
330+
writeFile(*repoPath / path.rel(), contents);
332331

333332
auto result = runProgram(RunOptions {
334333
.program = "git",
335-
.args = {"-C", repoInfo.url, "--git-dir", repoInfo.gitDir, "check-ignore", "--quiet", std::string(path.rel())},
334+
.args = {"-C", *repoPath, "--git-dir", repoInfo.gitDir, "check-ignore", "--quiet", std::string(path.rel())},
336335
});
337336
auto exitCode =
338337
#ifndef WIN32 // TODO abstract over exit status handling on Windows
@@ -345,40 +344,57 @@ struct GitInputScheme : InputScheme
345344
if (exitCode != 0) {
346345
// The path is not `.gitignore`d, we can add the file.
347346
runProgram("git", true,
348-
{ "-C", repoInfo.url, "--git-dir", repoInfo.gitDir, "add", "--intent-to-add", "--", std::string(path.rel()) });
347+
{ "-C", *repoPath, "--git-dir", repoInfo.gitDir, "add", "--intent-to-add", "--", std::string(path.rel()) });
349348

350349

351350
if (commitMsg) {
352351
// Pause the logger to allow for user input (such as a gpg passphrase) in `git commit`
353352
logger->pause();
354353
Finally restoreLogger([]() { logger->resume(); });
355354
runProgram("git", true,
356-
{ "-C", repoInfo.url, "--git-dir", repoInfo.gitDir, "commit", std::string(path.rel()), "-F", "-" },
355+
{ "-C", *repoPath, "--git-dir", repoInfo.gitDir, "commit", std::string(path.rel()), "-F", "-" },
357356
*commitMsg);
358357
}
359358
}
360359
}
361360

362361
struct RepoInfo
363362
{
364-
/* Whether this is a local, non-bare repository. */
365-
bool isLocal = false;
363+
/* Either the path of the repo (for local, non-bare repos), or
364+
the URL (which is never a `file` URL). */
365+
std::variant<std::filesystem::path, ParsedURL> location;
366366

367367
/* Working directory info: the complete list of files, and
368368
whether the working directory is dirty compared to HEAD. */
369369
GitRepo::WorkdirInfo workdirInfo;
370370

371-
/* URL of the repo, or its path if isLocal. Never a `file` URL. */
372-
std::string url;
371+
std::string locationToArg() const
372+
{
373+
return std::visit(
374+
overloaded {
375+
[&](const std::filesystem::path & path)
376+
{ return path.string(); },
377+
[&](const ParsedURL & url)
378+
{ return url.to_string(); }
379+
}, location);
380+
}
381+
382+
std::optional<std::filesystem::path> getPath() const
383+
{
384+
if (auto path = std::get_if<std::filesystem::path>(&location))
385+
return *path;
386+
else
387+
return std::nullopt;
388+
}
373389

374390
void warnDirty(const Settings & settings) const
375391
{
376392
if (workdirInfo.isDirty) {
377393
if (!settings.allowDirty)
378-
throw Error("Git tree '%s' is dirty", url);
394+
throw Error("Git tree '%s' is dirty", locationToArg());
379395

380396
if (settings.warnDirty)
381-
warn("Git tree '%s' is dirty", url);
397+
warn("Git tree '%s' is dirty", locationToArg());
382398
}
383399
}
384400

@@ -425,7 +441,6 @@ struct GitInputScheme : InputScheme
425441
static bool forceHttp = getEnv("_NIX_FORCE_HTTP") == "1"; // for testing
426442
auto url = parseURL(getStrAttr(input.attrs, "url"));
427443
bool isBareRepository = url.scheme == "file" && !pathExists(url.path + "/.git");
428-
repoInfo.isLocal = url.scheme == "file" && !forceHttp && !isBareRepository;
429444
//
430445
// FIXME: here we turn a possibly relative path into an absolute path.
431446
// This allows relative git flake inputs to be resolved against the
@@ -435,22 +450,22 @@ struct GitInputScheme : InputScheme
435450
//
436451
// See: https://discourse.nixos.org/t/57783 and #9708
437452
//
438-
if (repoInfo.isLocal) {
453+
if (url.scheme == "file" && !forceHttp && !isBareRepository) {
439454
if (!isAbsolute(url.path)) {
440455
warn(
441456
"Fetching Git repository '%s', which uses a path relative to the current directory. "
442457
"This is not supported and will stop working in a future release. "
443458
"See https://github.com/NixOS/nix/issues/12281 for details.",
444459
url);
445460
}
446-
repoInfo.url = std::filesystem::absolute(url.path).string();
461+
repoInfo.location = std::filesystem::absolute(url.path);
447462
} else
448-
repoInfo.url = url.to_string();
463+
repoInfo.location = url;
449464

450465
// If this is a local directory and no ref or revision is
451466
// given, then allow the use of an unclean working tree.
452-
if (!input.getRef() && !input.getRev() && repoInfo.isLocal)
453-
repoInfo.workdirInfo = GitRepo::getCachedWorkdirInfo(repoInfo.url);
467+
if (auto repoPath = repoInfo.getPath(); !input.getRef() && !input.getRev() && repoPath)
468+
repoInfo.workdirInfo = GitRepo::getCachedWorkdirInfo(*repoPath);
454469

455470
return repoInfo;
456471
}
@@ -480,7 +495,7 @@ struct GitInputScheme : InputScheme
480495
if (auto revCountAttrs = cache->lookup(key))
481496
return getIntAttr(*revCountAttrs, "revCount");
482497

483-
Activity act(*logger, lvlChatty, actUnknown, fmt("getting Git revision count of '%s'", repoInfo.url));
498+
Activity act(*logger, lvlChatty, actUnknown, fmt("getting Git revision count of '%s'", repoInfo.locationToArg()));
484499

485500
auto revCount = GitRepo::openRepo(repoDir)->getRevCount(rev);
486501

@@ -491,11 +506,15 @@ struct GitInputScheme : InputScheme
491506

492507
std::string getDefaultRef(const RepoInfo & repoInfo) const
493508
{
494-
auto head = repoInfo.isLocal
495-
? GitRepo::openRepo(repoInfo.url)->getWorkdirRef()
496-
: readHeadCached(repoInfo.url);
509+
auto head = std::visit(
510+
overloaded {
511+
[&](const std::filesystem::path & path)
512+
{ return GitRepo::openRepo(path)->getWorkdirRef(); },
513+
[&](const ParsedURL & url)
514+
{ return readHeadCached(url.to_string()); }
515+
}, repoInfo.location);
497516
if (!head) {
498-
warn("could not read HEAD ref from repo at '%s', using 'master'", repoInfo.url);
517+
warn("could not read HEAD ref from repo at '%s', using 'master'", repoInfo.locationToArg());
499518
return "master";
500519
}
501520
return *head;
@@ -540,12 +559,13 @@ struct GitInputScheme : InputScheme
540559

541560
Path repoDir;
542561

543-
if (repoInfo.isLocal) {
544-
repoDir = repoInfo.url;
562+
if (auto repoPath = repoInfo.getPath()) {
563+
repoDir = *repoPath;
545564
if (!input.getRev())
546565
input.attrs.insert_or_assign("rev", GitRepo::openRepo(repoDir)->resolveRef(ref).gitRev());
547566
} else {
548-
Path cacheDir = getCachePath(repoInfo.url, getShallowAttr(input));
567+
auto repoUrl = std::get<ParsedURL>(repoInfo.location);
568+
Path cacheDir = getCachePath(repoUrl.to_string(), getShallowAttr(input));
549569
repoDir = cacheDir;
550570
repoInfo.gitDir = ".";
551571

@@ -555,7 +575,7 @@ struct GitInputScheme : InputScheme
555575
auto repo = GitRepo::openRepo(cacheDir, true, true);
556576

557577
// We need to set the origin so resolving submodule URLs works
558-
repo->setRemote("origin", repoInfo.url);
578+
repo->setRemote("origin", repoUrl.to_string());
559579

560580
Path localRefFile =
561581
ref.compare(0, 5, "refs/") == 0
@@ -594,11 +614,11 @@ struct GitInputScheme : InputScheme
594614
? ref
595615
: "refs/heads/" + ref;
596616

597-
repo->fetch(repoInfo.url, fmt("%s:%s", fetchRef, fetchRef), getShallowAttr(input));
617+
repo->fetch(repoUrl.to_string(), fmt("%s:%s", fetchRef, fetchRef), getShallowAttr(input));
598618
} catch (Error & e) {
599619
if (!pathExists(localRefFile)) throw;
600620
logError(e.info());
601-
warn("could not update local clone of Git repository '%s'; continuing with the most recent version", repoInfo.url);
621+
warn("could not update local clone of Git repository '%s'; continuing with the most recent version", repoInfo.locationToArg());
602622
}
603623

604624
try {
@@ -607,8 +627,8 @@ struct GitInputScheme : InputScheme
607627
} catch (Error & e) {
608628
warn("could not update mtime for file '%s': %s", localRefFile, e.info().msg);
609629
}
610-
if (!originalRef && !storeCachedHead(repoInfo.url, ref))
611-
warn("could not update cached head '%s' for '%s'", ref, repoInfo.url);
630+
if (!originalRef && !storeCachedHead(repoUrl.to_string(), ref))
631+
warn("could not update cached head '%s' for '%s'", ref, repoInfo.locationToArg());
612632
}
613633

614634
if (auto rev = input.getRev()) {
@@ -620,8 +640,7 @@ struct GitInputScheme : InputScheme
620640
"allRefs = true;" ANSI_NORMAL " to " ANSI_BOLD "fetchGit" ANSI_NORMAL ".",
621641
rev->gitRev(),
622642
ref,
623-
repoInfo.url
624-
);
643+
repoInfo.locationToArg());
625644
} else
626645
input.attrs.insert_or_assign("rev", repo->resolveRef(ref).gitRev());
627646

@@ -633,7 +652,7 @@ struct GitInputScheme : InputScheme
633652
auto isShallow = repo->isShallow();
634653

635654
if (isShallow && !getShallowAttr(input))
636-
throw Error("'%s' is a shallow Git repository, but shallow repositories are only allowed when `shallow = true;` is specified", repoInfo.url);
655+
throw Error("'%s' is a shallow Git repository, but shallow repositories are only allowed when `shallow = true;` is specified", repoInfo.locationToArg());
637656

638657
// FIXME: check whether rev is an ancestor of ref?
639658

@@ -648,7 +667,7 @@ struct GitInputScheme : InputScheme
648667
infoAttrs.insert_or_assign("revCount",
649668
getRevCount(repoInfo, repoDir, rev));
650669

651-
printTalkative("using revision %s of repo '%s'", rev.gitRev(), repoInfo.url);
670+
printTalkative("using revision %s of repo '%s'", rev.gitRev(), repoInfo.locationToArg());
652671

653672
verifyCommit(input, repo);
654673

@@ -702,21 +721,23 @@ struct GitInputScheme : InputScheme
702721
RepoInfo & repoInfo,
703722
Input && input) const
704723
{
724+
auto repoPath = repoInfo.getPath().value();
725+
705726
if (getSubmodulesAttr(input))
706727
/* Create mountpoints for the submodules. */
707728
for (auto & submodule : repoInfo.workdirInfo.submodules)
708729
repoInfo.workdirInfo.files.insert(submodule.path);
709730

710-
auto repo = GitRepo::openRepo(repoInfo.url, false, false);
731+
auto repo = GitRepo::openRepo(repoPath, false, false);
711732

712733
auto exportIgnore = getExportIgnoreAttr(input);
713734

714735
ref<SourceAccessor> accessor =
715736
repo->getAccessor(repoInfo.workdirInfo,
716737
exportIgnore,
717-
makeNotAllowedError(repoInfo.url));
738+
makeNotAllowedError(repoInfo.locationToArg()));
718739

719-
accessor->setPathDisplay(repoInfo.url);
740+
accessor->setPathDisplay(repoInfo.locationToArg());
720741

721742
/* If the repo has submodules, return a mounted input accessor
722743
consisting of the accessor for the top-level repo and the
@@ -725,10 +746,10 @@ struct GitInputScheme : InputScheme
725746
std::map<CanonPath, nix::ref<SourceAccessor>> mounts;
726747

727748
for (auto & submodule : repoInfo.workdirInfo.submodules) {
728-
auto submodulePath = CanonPath(repoInfo.url) / submodule.path;
749+
auto submodulePath = repoPath / submodule.path.rel();
729750
fetchers::Attrs attrs;
730751
attrs.insert_or_assign("type", "git");
731-
attrs.insert_or_assign("url", submodulePath.abs());
752+
attrs.insert_or_assign("url", submodulePath.string());
732753
attrs.insert_or_assign("exportIgnore", Explicit<bool>{ exportIgnore });
733754
attrs.insert_or_assign("submodules", Explicit<bool>{ true });
734755
// TODO: fall back to getAccessorFromCommit-like fetch when submodules aren't checked out
@@ -752,7 +773,7 @@ struct GitInputScheme : InputScheme
752773
}
753774

754775
if (!repoInfo.workdirInfo.isDirty) {
755-
auto repo = GitRepo::openRepo(repoInfo.url);
776+
auto repo = GitRepo::openRepo(repoPath);
756777

757778
if (auto ref = repo->getWorkdirRef())
758779
input.attrs.insert_or_assign("ref", *ref);
@@ -762,7 +783,7 @@ struct GitInputScheme : InputScheme
762783

763784
input.attrs.insert_or_assign("rev", rev.gitRev());
764785
input.attrs.insert_or_assign("revCount",
765-
rev == nullRev ? 0 : getRevCount(repoInfo, repoInfo.url, rev));
786+
rev == nullRev ? 0 : getRevCount(repoInfo, repoPath, rev));
766787

767788
verifyCommit(input, repo);
768789
} else {
@@ -781,7 +802,7 @@ struct GitInputScheme : InputScheme
781802
input.attrs.insert_or_assign(
782803
"lastModified",
783804
repoInfo.workdirInfo.headRev
784-
? getLastModified(repoInfo, repoInfo.url, *repoInfo.workdirInfo.headRev)
805+
? getLastModified(repoInfo, repoPath, *repoInfo.workdirInfo.headRev)
785806
: 0);
786807

787808
return {accessor, std::move(input)};
@@ -804,7 +825,7 @@ struct GitInputScheme : InputScheme
804825
}
805826

806827
auto [accessor, final] =
807-
input.getRef() || input.getRev() || !repoInfo.isLocal
828+
input.getRef() || input.getRev() || !repoInfo.getPath()
808829
? getAccessorFromCommit(store, repoInfo, std::move(input))
809830
: getAccessorFromWorkdir(store, repoInfo, std::move(input));
810831

@@ -822,14 +843,14 @@ struct GitInputScheme : InputScheme
822843
return makeFingerprint(*rev);
823844
else {
824845
auto repoInfo = getRepoInfo(input);
825-
if (repoInfo.isLocal && repoInfo.workdirInfo.headRev && repoInfo.workdirInfo.submodules.empty()) {
846+
if (auto repoPath = repoInfo.getPath(); repoPath && repoInfo.workdirInfo.headRev && repoInfo.workdirInfo.submodules.empty()) {
826847
/* Calculate a fingerprint that takes into account the
827848
deleted and modified/added files. */
828849
HashSink hashSink{HashAlgorithm::SHA512};
829850
for (auto & file : repoInfo.workdirInfo.dirtyFiles) {
830851
writeString("modified:", hashSink);
831852
writeString(file.abs(), hashSink);
832-
dumpPath(repoInfo.url + "/" + file.abs(), hashSink);
853+
dumpPath(*repoPath / file.rel(), hashSink);
833854
}
834855
for (auto & file : repoInfo.workdirInfo.deletedFiles) {
835856
writeString("deleted:", hashSink);

src/libfetchers/mercurial.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ struct MercurialInputScheme : InputScheme
126126
return res;
127127
}
128128

129-
std::optional<Path> getSourcePath(const Input & input) const override
129+
std::optional<std::filesystem::path> getSourcePath(const Input & input) const override
130130
{
131131
auto url = parseURL(getStrAttr(input.attrs, "url"));
132132
if (url.scheme == "file" && !input.getRef() && !input.getRev())

0 commit comments

Comments
 (0)