@@ -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);
0 commit comments