Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#200] Warnings about files that weren't added to git yet #215

Merged
merged 2 commits into from
Nov 17, 2022
Merged
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
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ Unreleased
`<!-- xrefcheck: ignore all -->` instead of `<!-- xrefcheck: ignore file -->`
should be used to disable checking for links in file, so it's clearer that
file itself is not ignored (and links can target it).
* [#215](https://github.com/serokell/xrefcheck/pull/215)
+ Now we notify user when there are scannable files that were not added to Git
yet. Also added CLI option `--include-untracked` to scan such files and treat
as existing.

0.2.2
==========
Expand Down
7 changes: 6 additions & 1 deletion src/Xrefcheck/CLI.hs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ data Options = Options
, oColorMode :: ColorMode
, oExclusionOptions :: ExclusionOptions
, oNetworkingOptions :: NetworkingOptions
, oScanPolicy :: ScanPolicy
}

data ExclusionOptions = ExclusionOptions
Expand Down Expand Up @@ -181,9 +182,13 @@ optionsParser = do
]
oColorMode <- flag WithColors WithoutColors $
long "no-color" <>
help "Disable ANSI coloring of output"
help "Disable ANSI coloring of output."
Martoon-00 marked this conversation as resolved.
Show resolved Hide resolved
oExclusionOptions <- exclusionOptionsParser
oNetworkingOptions <- networkingOptionsParser
oScanPolicy <- flag OnlyTracked IncludeUntracked $
long "include-untracked" <>
help "Scan and treat as existing files that were not added to Git.\
\ Files explicitly ignored by Git are always ignored by xrefcheck."
return Options{..}

exclusionOptionsParser :: Parser ExclusionOptions
Expand Down
2 changes: 1 addition & 1 deletion src/Xrefcheck/Command.hs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ defaultAction Options{..} = do

(ScanResult scanErrs repoInfo) <- allowRewrite showProgressBar $ \rw -> do
let fullConfig = addExclusionOptions (cExclusions config) oExclusionOptions
scanRepo rw (formats $ cScanners config) fullConfig oRoot
scanRepo oScanPolicy rw (formats $ cScanners config) fullConfig oRoot

when oVerbose $
fmt [int||
Expand Down
37 changes: 31 additions & 6 deletions src/Xrefcheck/Core.hs
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,36 @@ makeLenses ''FileInfo
instance Default FileInfo where
def = diffToFileInfo mempty

data ScanPolicy
= OnlyTracked
-- ^ Scan and treat as existing only files tracked by Git.
-- Warn when there are scannable files not added to Git yet.
| IncludeUntracked
-- ^ Also scan and treat as existing
-- files that were neither tracked nor ignored by Git.
deriving stock (Show, Eq)

data FileStatus
= Scanned FileInfo
| NotScannable
-- ^ Files that are not supported by our scanners
| NotAddedToGit
-- ^ We are not scanning files that are not added to git
-- unless --include-untracked CLI option was enabled, but we're
-- gathering information about them to improve reports.
deriving stock (Show)

data DirectoryStatus
= TrackedDirectory
| UntrackedDirectory
deriving stock (Show)

-- | All tracked files and directories.
data RepoInfo = RepoInfo
{ riFiles :: Map FilePath (Maybe FileInfo)
-- ^ Files from the repo with `FileInfo` attached to files that we can scan.
, riDirectories :: Set FilePath
-- ^ Tracked directories.
{ riFiles :: Map FilePath FileStatus
-- ^ Files from the repo with `FileInfo` attached to files that we've scanned.
, riDirectories :: Map FilePath DirectoryStatus
-- ^ Directories containing those files.
} deriving stock (Show)

-----------------------------------------------------------
Expand Down Expand Up @@ -180,8 +204,9 @@ instance Given ColorMode => Buildable FileInfo where
|]

instance Given ColorMode => Buildable RepoInfo where
build (RepoInfo (nonEmpty . mapMaybe sequence . toPairs -> Just m) _) =
interpolateBlockListF' "⮚" buildFileReport m
build (RepoInfo m _)
| Just scanned <- nonEmpty [(name, info) | (name, Scanned info) <- toPairs m]
= interpolateBlockListF' "⮚" buildFileReport scanned
where
buildFileReport (name, info) =
[int||
Expand Down
100 changes: 75 additions & 25 deletions src/Xrefcheck/Scan.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module Xrefcheck.Scan
, ScanAction
, FormatsSupport
, RepoInfo (..)
, ReadDirectoryMode(..)
, ScanError (..)
, ScanErrorDescription (..)
, ScanResult (..)
Expand Down Expand Up @@ -138,18 +139,41 @@ specificFormatsSupport formats = \ext -> M.lookup ext formatsMap
, extension <- extensions
]

-- | Process files that are tracked by git and not ignored by the config.
data ReadDirectoryMode
= RdmTracked
-- ^ Consider files tracked by Git, obtained from "git ls-files"
| RdmUntracked
-- ^ Consider files that are not tracked nor ignored by Git, obtained from
-- "git ls-files --others --exclude-standard"
| RdmBothTrackedAndUtracked
-- ^ Combine output from commands listed above, so we consider all files
-- except ones that are explicitly ignored by Git

-- | Process files that match given @ReadDirectoryMode@ and aren't ignored by the config.
readDirectoryWith
:: forall a. ExclusionConfig
:: forall a. ReadDirectoryMode
-> ExclusionConfig
-> (FilePath -> IO a)
-> FilePath
-> IO [(FilePath, a)]
readDirectoryWith config scanner root =
readDirectoryWith mode config scanner root =
traverse scanFile
. filter (not . isIgnored)
. fmap (location </>)
. L.lines =<< readCreateProcess (shell "git ls-files"){cwd = Just root} ""
. L.lines =<< getFiles

where

getFiles = case mode of
RdmTracked -> getTrackedFiles
RdmUntracked -> getUntrackedFiles
RdmBothTrackedAndUtracked -> liftA2 (<>) getTrackedFiles getUntrackedFiles

getTrackedFiles = readCreateProcess
(shell "git ls-files"){cwd = Just root} ""
getUntrackedFiles = readCreateProcess
(shell "git ls-files --others --exclude-standard"){cwd = Just root} ""

scanFile :: FilePath -> IO (FilePath, a)
scanFile = sequence . (normaliseWithNoTrailing &&& scanner)

Expand All @@ -165,21 +189,48 @@ readDirectoryWith config scanner root =

scanRepo
:: MonadIO m
=> Rewrite -> FormatsSupport -> ExclusionConfig -> FilePath -> m ScanResult
scanRepo rw formatsSupport config root = do
=> ScanPolicy -> Rewrite -> FormatsSupport -> ExclusionConfig -> FilePath -> m ScanResult
scanRepo scanMode rw formatsSupport config root = do
putTextRewrite rw "Scanning repository..."

when (not $ isDirectory root) $
die $ "Repository's root does not seem to be a directory: " <> root

(errs, fileInfos) <- liftIO
$ (gatherScanErrs &&& gatherFileInfos)
<$> readDirectoryWith config processFile root

let dirs = fromList $ foldMap (getDirs . fst) fileInfos
(errs, processedFiles) <-
let mode = case scanMode of
OnlyTracked -> RdmTracked
IncludeUntracked -> RdmBothTrackedAndUtracked
in liftIO
$ (gatherScanErrs &&& gatherFileStatuses)
<$> readDirectoryWith mode config processFile root

notProcessedFiles <- case scanMode of
OnlyTracked -> liftIO $
readDirectoryWith RdmUntracked config (const $ pure NotAddedToGit) root
IncludeUntracked -> pure []

let scannableNotProcessedFiles = filter (isJust . mscanner . fst) notProcessedFiles

whenJust (nonEmpty $ map fst scannableNotProcessedFiles) $ \files -> hPutStrLn @Text stderr
[int|A|
Those files are not added by Git, so we're not scanning them:
#{interpolateBlockListF files}
Please run "git add" before running xrefcheck or enable \
--include-untracked CLI option to check these files.
|]

return . ScanResult errs $ RepoInfo (M.fromList fileInfos) dirs
let trackedDirs = foldMap (getDirs . fst) processedFiles
untrackedDirs = foldMap (getDirs . fst) notProcessedFiles
return . ScanResult errs $ RepoInfo
{ riFiles = M.fromList $ processedFiles <> notProcessedFiles
, riDirectories = M.fromList
$ map (, TrackedDirectory) trackedDirs
<> map (, UntrackedDirectory) untrackedDirs
}
where
mscanner :: FilePath -> Maybe ScanAction
mscanner = formatsSupport . takeExtension

isDirectory :: FilePath -> Bool
isDirectory = readingSystem . doesDirectoryExist

Expand All @@ -188,20 +239,19 @@ scanRepo rw formatsSupport config root = do
getDirs = scanl (</>) "" . splitDirectories . takeDirectory

gatherScanErrs
:: [(FilePath, Maybe (FileInfo, [ScanError]))]
:: [(FilePath, (FileStatus, [ScanError]))]
-> [ScanError]
gatherScanErrs = fold . mapMaybe (fmap snd . snd)

gatherFileInfos
:: [(FilePath, Maybe (FileInfo, [ScanError]))]
-> [(FilePath, Maybe FileInfo)]
gatherFileInfos = map (second (fmap fst))

processFile :: FilePath -> IO $ Maybe (FileInfo, [ScanError])
processFile file = do
let ext = takeExtension file
let mscanner = formatsSupport ext
forM mscanner ($ file)
gatherScanErrs = foldMap (snd . snd)

gatherFileStatuses
:: [(FilePath, (FileStatus, [ScanError]))]
-> [(FilePath, FileStatus)]
gatherFileStatuses = map (second fst)

processFile :: FilePath -> IO (FileStatus, [ScanError])
processFile file = case mscanner file of
Nothing -> pure (NotScannable, [])
Just scanner -> scanner file <&> _1 %~ Scanned

-----------------------------------------------------------
-- Yaml instances
Expand Down
48 changes: 32 additions & 16 deletions src/Xrefcheck/Verify.hs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ instance (Given ColorMode, Buildable a) => Buildable (WithReferenceLoc a) where
data VerifyError
= LocalFileDoesNotExist FilePath
| LocalFileOutsideRepo FilePath
| LinkTargetNotAddedToGit FilePath
| AnchorDoesNotExist Text [Anchor]
| AmbiguousAnchorRef FilePath Text (NonEmpty Anchor)
| ExternalResourceInvalidUri URIBS.URIParseError
Expand Down Expand Up @@ -148,6 +149,14 @@ instance Given ColorMode => Buildable VerifyError where
#{file}
|]


LinkTargetNotAddedToGit file ->
[int||
⛀ Link target is not tracked by Git:
#{file}
Please run "git add" before running xrefcheck or enable --include-untracked CLI option.
|]

AnchorDoesNotExist anchor similar -> case nonEmpty similar of
Nothing ->
[int||
Expand Down Expand Up @@ -339,10 +348,13 @@ verifyRepo
(file, fileInfo) <- M.toList files
guard . not $ matchesGlobPatterns root (ecIgnoreRefsFrom cExclusions) file
case fileInfo of
Just fi -> do
Scanned fi -> do
ref <- _fiReferences fi
return (file, ref)
Nothing -> empty -- no support for such file, can do nothing
NotScannable -> empty -- No support for such file, can do nothing.
NotAddedToGit -> empty -- If this file is scannable, we've notified
-- user that we are scanning only files
-- added to Git while gathering RepoInfo.

progressRef <- newIORef $ initVerifyProgress (map snd toScan)

Expand Down Expand Up @@ -504,14 +516,15 @@ verifyReference
checkRef mAnchor referredFile = verifying $
unless (isVirtual referredFile) do
checkReferredFileIsInsideRepo referredFile
checkReferredFileExists referredFile
case lookupFilePath referredFile $ M.toList files of
Nothing -> pass -- no support for such file, can do nothing
Just referredFileInfo -> whenJust mAnchor $
mFileStatus <- tryGetFileStatus referredFile
case mFileStatus of
Right (Scanned referredFileInfo) -> whenJust mAnchor $
checkAnchor referredFile (_fiAnchors referredFileInfo)

lookupFilePath :: FilePath -> [(FilePath, Maybe FileInfo)] -> Maybe FileInfo
lookupFilePath fp = snd <=< find (equalFilePath (expandIndirections fp) . fst)
Right NotScannable -> pass -- no support for such file, can do nothing
Right NotAddedToGit -> throwError (LinkTargetNotAddedToGit referredFile)
Left UntrackedDirectory -> throwError (LinkTargetNotAddedToGit referredFile)
Left TrackedDirectory -> pass -- path leads to directory, currently
-- if such link contain anchor, we ignore it

-- expands ".." and "."
-- expandIndirections "a/b/../c" = "a/c"
Expand Down Expand Up @@ -545,18 +558,21 @@ verifyReference
nestingChange "." = 0
nestingChange _ = 1

checkReferredFileExists file = do
unless (fileExists || dirExists) $
throwError (LocalFileDoesNotExist file)
-- Returns `Nothing` when path corresponds to an existing (and tracked) directory
tryGetFileStatus :: FilePath -> ExceptT VerifyError IO (Either DirectoryStatus FileStatus)
tryGetFileStatus file
| Just f <- mFile = return $ Right f
| Just d <- mDir = return $ Left d
| otherwise = throwError (LocalFileDoesNotExist file)
where
matchesFilePath :: FilePath -> Bool
matchesFilePath = equalFilePath $ expandIndirections file

fileExists :: Bool
fileExists = any matchesFilePath $ M.keys files
mFile :: Maybe FileStatus
mFile = (files M.!) <$> find matchesFilePath (M.keys files)

dirExists :: Bool
dirExists = any matchesFilePath dirs
mDir :: Maybe DirectoryStatus
mDir = (dirs M.!) <$> find matchesFilePath (M.keys dirs)

checkAnchor file fileAnchors anchor = do
checkAnchorReferenceAmbiguity file fileAnchors anchor
Expand Down
2 changes: 1 addition & 1 deletion tests/Test/Xrefcheck/IgnoreRegexSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ test_ignoreRegex = give WithoutColors $
in testGroup "Regular expressions performance"
[ testCase "Check that only not matched links are verified" $ do
scanResult <- allowRewrite showProgressBar $ \rw ->
scanRepo rw formats (config ^. cExclusionsL) root
scanRepo OnlyTracked rw formats (config ^. cExclusionsL) root

verifyRes <- allowRewrite showProgressBar $ \rw ->
verifyRepo rw config verifyMode root $ srRepoInfo scanResult
Expand Down
2 changes: 1 addition & 1 deletion tests/Test/Xrefcheck/TrailingSlashSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ test_slash = testGroup "Trailing forward slash detection" $
root <>
"\" should exist") $ do
(ScanResult _ (RepoInfo repoInfo _)) <- allowRewrite False $ \rw ->
scanRepo rw format (cExclusions config & ecIgnoreL .~ []) root
scanRepo OnlyTracked rw format (cExclusions config & ecIgnoreL .~ []) root
nonExistentFiles <- lefts <$> forM (keys repoInfo) (\filePath -> do
predicate <- doesFileExist filePath
return $ if predicate
Expand Down
Loading