diff --git a/CHANGELOG b/CHANGELOG index 0d2953328a..b62c1d81b5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ git-annex (10.20220725) UNRELEASED; urgency=medium * Added new matching options --want-get-by and --want-drop-by. * Allow find --branch to be used in a bare repository, the same as the deprecated findref can be. + * add --dry-run: New option. -- Joey Hess Mon, 25 Jul 2022 15:35:45 -0400 diff --git a/CmdLine/GitAnnex/Options.hs b/CmdLine/GitAnnex/Options.hs index 48d7a3daf4..df24b7f1f9 100644 --- a/CmdLine/GitAnnex/Options.hs +++ b/CmdLine/GitAnnex/Options.hs @@ -128,6 +128,12 @@ parseUUIDOption :: String -> DeferredParse UUID parseUUIDOption = DeferredParse . (Remote.nameToUUID) +parseDryRunOption :: Parser DryRun +parseDryRunOption = DryRun <$> switch + ( long "dry-run" + <> help "don't make changes, but show what would be done" + ) + -- | From or To a remote. data FromToOptions = FromRemote (DeferredParse Remote) diff --git a/Command.hs b/Command.hs index bc418fdc3d..21607e4962 100644 --- a/Command.hs +++ b/Command.hs @@ -106,6 +106,12 @@ stop = return Nothing stopUnless :: Annex Bool -> Annex (Maybe a) -> Annex (Maybe a) stopUnless c a = ifM c ( a , stop ) +{- When doing a dry run, avoid actually performing the action, but pretend + - that it succeeded. -} +skipWhenDryRun :: DryRun -> CommandPerform -> CommandPerform +skipWhenDryRun (DryRun False) a = a +skipWhenDryRun (DryRun True) _ = next $ return True + {- When acting on a failed transfer, stops unless it was in the specified - direction. -} checkFailedTransferDirection :: ActionItem -> Direction -> Annex (Maybe a) -> Annex (Maybe a) diff --git a/Command/Add.hs b/Command/Add.hs index a777178afb..cf3e5f5534 100644 --- a/Command/Add.hs +++ b/Command/Add.hs @@ -52,6 +52,7 @@ data AddOptions = AddOptions , updateOnly :: Bool , largeFilesOverride :: Maybe Bool , checkGitIgnoreOption :: CheckGitIgnore + , dryRunOption :: DryRun } optParser :: CmdParamsDesc -> Parser AddOptions @@ -65,6 +66,7 @@ optParser desc = AddOptions ) <*> (parseforcelarge <|> parseforcesmall) <*> checkGitIgnoreSwitch + <*> parseDryRunOption where parseforcelarge = flag Nothing (Just True) ( long "force-large" @@ -91,16 +93,16 @@ seek o = startConcurrency commandStages $ do ifM (pure (annexdotfiles || not (dotfile file)) <&&> (checkFileMatcher largematcher file <||> Annex.getRead Annex.force)) - ( start si file addunlockedmatcher + ( start dr si file addunlockedmatcher , if includingsmall then ifM (annexAddSmallFiles <$> Annex.getGitConfig) - ( startSmall si file s + ( startSmall dr si file s , stop ) else stop ) - Just True -> start si file addunlockedmatcher - Just False -> startSmallOverridden si file + Just True -> start dr si file addunlockedmatcher + Just False -> startSmallOverridden dr si file case batchOption o of Batch fmt | updateOnly o -> @@ -126,25 +128,26 @@ seek o = startConcurrency commandStages $ do -- same as a modified unlocked file would get -- locked when added. go False withUnmodifiedUnlockedPointers + where + dr = dryRunOption o {- Pass file off to git-add. -} -startSmall :: SeekInput -> RawFilePath -> FileStatus -> CommandStart -startSmall si file s = +startSmall :: DryRun -> SeekInput -> RawFilePath -> FileStatus -> CommandStart +startSmall dr si file s = starting "add" (ActionItemTreeFile file) si $ - next $ addSmall file s + addSmall dr file s -addSmall :: RawFilePath -> FileStatus -> Annex Bool -addSmall file s = do +addSmall :: DryRun -> RawFilePath -> FileStatus -> CommandPerform +addSmall dr file s = do showNote "non-large file; adding content to git repository" - addFile Small file s + skipWhenDryRun dr $ next $ addFile Small file s -startSmallOverridden :: SeekInput -> RawFilePath -> CommandStart -startSmallOverridden si file = +startSmallOverridden :: DryRun -> SeekInput -> RawFilePath -> CommandStart +startSmallOverridden dr si file = liftIO (catchMaybeIO $ R.getSymbolicLinkStatus file) >>= \case - Just s -> starting "add" (ActionItemTreeFile file) si $ next $ do - + Just s -> starting "add" (ActionItemTreeFile file) si $ do showNote "adding content to git repository" - addFile Small file s + skipWhenDryRun dr $ next $ addFile Small file s Nothing -> stop data SmallOrLarge = Small | Large @@ -188,8 +191,8 @@ addFile smallorlarge file s = do isRegularFile a /= isRegularFile b || isSymbolicLink a /= isSymbolicLink b -start :: SeekInput -> RawFilePath -> AddUnlockedMatcher -> CommandStart -start si file addunlockedmatcher = +start :: DryRun -> SeekInput -> RawFilePath -> AddUnlockedMatcher -> CommandStart +start dr si file addunlockedmatcher = liftIO (catchMaybeIO $ R.getSymbolicLinkStatus file) >>= \case Nothing -> stop Just s @@ -200,16 +203,17 @@ start si file addunlockedmatcher = where go s = ifAnnexed file (addpresent s) (add s) add s = starting "add" (ActionItemTreeFile file) si $ - if isSymbolicLink s - then next $ addFile Small file s - else perform file addunlockedmatcher + skipWhenDryRun dr $ + if isSymbolicLink s + then next $ addFile Small file s + else perform file addunlockedmatcher addpresent s key | isSymbolicLink s = fixuplink key | otherwise = add s fixuplink key = starting "add" (ActionItemTreeFile file) si $ addingExistingLink file key $ - withOtherTmp $ \tmp -> do + skipWhenDryRun dr $ withOtherTmp $ \tmp -> do let tmpf = tmp P. P.takeFileName file liftIO $ moveFile file tmpf ifM (isSymbolicLink <$> liftIO (R.getSymbolicLinkStatus tmpf)) @@ -223,9 +227,10 @@ start si file addunlockedmatcher = ) fixuppointer s key = starting "add" (ActionItemTreeFile file) si $ - addingExistingLink file key $ do - Database.Keys.addAssociatedFile key =<< inRepo (toTopFilePath file) - next $ addFile Large file s + addingExistingLink file key $ + skipWhenDryRun dr $ do + Database.Keys.addAssociatedFile key =<< inRepo (toTopFilePath file) + next $ addFile Large file s perform :: RawFilePath -> AddUnlockedMatcher -> CommandPerform perform file addunlockedmatcher = withOtherTmp $ \tmpdir -> do diff --git a/Command/AddUrl.hs b/Command/AddUrl.hs index 4bfa5767a4..805e72a0e7 100644 --- a/Command/AddUrl.hs +++ b/Command/AddUrl.hs @@ -476,7 +476,7 @@ addWorkTree _ addunlockedmatcher u url file key mtmp = case mtmp of (fromRawFilePath file) (fromRawFilePath tmp) go - else void $ Command.Add.addSmall file s + else void $ Command.Add.addSmall (DryRun False) file s where go = do maybeShowJSON $ JSONChunk [("key", serializeKey key)] diff --git a/Command/Import.hs b/Command/Import.hs index 692b621c5f..b24bbcfd5f 100644 --- a/Command/Import.hs +++ b/Command/Import.hs @@ -248,7 +248,7 @@ startLocal o addunlockedmatcher largematcher mode (srcfile, destfile) = >>= maybe stop (\addedk -> next $ Command.Add.cleanup addedk True) - , next $ Command.Add.addSmall destfile s + , Command.Add.addSmall (DryRun False) destfile s ) notoverwriting why = do warning $ "not overwriting existing " ++ fromRawFilePath destfile ++ " " ++ why diff --git a/Types/Command.hs b/Types/Command.hs index 94e10dfb16..4f3a2b8632 100644 --- a/Types/Command.hs +++ b/Types/Command.hs @@ -133,3 +133,6 @@ descSection SectionUtility = "Utility commands" descSection SectionPlumbing = "Plumbing commands" descSection SectionTesting = "Testing commands" descSection SectionAddOn = "Addon commands" + +newtype DryRun = DryRun Bool + deriving (Show) diff --git a/doc/git-annex-add.mdwn b/doc/git-annex-add.mdwn index c3e15e12fe..253afd8cc4 100644 --- a/doc/git-annex-add.mdwn +++ b/doc/git-annex-add.mdwn @@ -77,6 +77,10 @@ annexed content, and other symlinks. Like `git add --update`, this does not add new files, but any updates to tracked files will be added to the index. +* `--dry-run` + + Output what would be done for each file, but avoid making any changes. + * `--json` Enable JSON output. This is intended to be parsed by programs that use diff --git a/doc/todo/add_--dry-run.mdwn b/doc/todo/add_--dry-run.mdwn index 7eab779ccc..dd21381eab 100644 --- a/doc/todo/add_--dry-run.mdwn +++ b/doc/todo/add_--dry-run.mdwn @@ -21,4 +21,5 @@ well -- unless there was a version staged already you don't want to loose etc. [[!meta author=yoh]] [[!tag projects/datalad]] - + +> [[done]] joey diff --git a/doc/todo/add_--dry-run/comment_2_312127fc2780fd894332d7d8b7f7a209._comment b/doc/todo/add_--dry-run/comment_2_312127fc2780fd894332d7d8b7f7a209._comment new file mode 100644 index 0000000000..9f9a72f144 --- /dev/null +++ b/doc/todo/add_--dry-run/comment_2_312127fc2780fd894332d7d8b7f7a209._comment @@ -0,0 +1,25 @@ +[[!comment format=mdwn + username="joey" + subject="""comment 2""" + date="2022-08-03T14:53:35Z" + content=""" +Implemented `git-annex add --dry-run`. + +As noted it does not let you tell if a file would be added locked or unlocked +since `git-annex add` output is the same either way. + +Also when JSON output is enabled, `git-annex add` usually outputs the key, +but with `--dry-run`, it does not. Generating the key would involve locking +down the file, and by that point the command is making changes to the +filesystem. Use `git-annex calckey` instead. + +Implementation was not as simple as making CommandPerform a no-op. Some parts +of CommandPerform actions needed to be run when doing a dry-run, in order for +addingExistingLink to display its warning, and for showNote to display the +very information that the user is interested in seeing from --dry-run. + +A cleaner implementation would probably involve moving those actions out of +CommandPerform, perhaps by splitting CommandPerform into two parts. +Which would be a lot of work and still failure prone. That is why I don't +think it's a good idea to add --dry-run to very many commands. +"""]]