1 % Copyright (C) 2002-2004 David Roundy
3 % This program is free software; you can redistribute it and/or modify
4 % it under the terms of the GNU General Public License as published by
5 % the Free Software Foundation; either version 2, or (at your option)
8 % This program is distributed in the hope that it will be useful,
9 % but WITHOUT ANY WARRANTY; without even the implied warranty of
10 % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 % GNU General Public License for more details.
13 % You should have received a copy of the GNU General Public License
14 % along with this program; see the file COPYING. If not, write to
15 % the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 % Boston, MA 02110-1301, USA.
18 \subsection{darcs add}
20 module Darcs.Commands.Add ( add ) where
22 import Data.List ( (\\), nub)
25 import Darcs.Arguments (noskip_boring, allow_problematic_filenames,
27 recursive, working_repo_dir, dry_run_noxml, umask_option,
28 list_files, list_unregistered_files,
29 DarcsFlag (AllowCaseOnly, AllowWindowsReserved, Boring, Recursive,
30 Verbose, Quiet, FancyMoveAdd, DryRun),
33 import Darcs.Utils ( withCurrentDirectory, nubsort )
34 import IsoDate ( getIsoDateTime )
35 import Darcs.Repository ( amInRepository, withRepoLock, ($-),
36 slurp_pending, add_to_pending )
37 import Darcs.Patch ( Prim, apply_to_slurpy, addfile, adddir, move )
38 import Darcs.Ordered ( FL(..), unsafeFL, concatFL, nullFL )
39 import Darcs.SlurpDirectory ( Slurpy, slurp_has_anycase, slurp_has,
40 isFileReallySymlink, doesDirectoryReallyExist,
41 doesFileReallyExist, slurp_hasdir,
43 import Darcs.Patch.FileName ( fp2fn )
44 import Darcs.FilePathUtils ( (///) )
45 import Darcs.RepoPath ( toFilePath )
46 import Control.Monad ( when )
47 import Darcs.Repository.Prefs ( darcsdir_filter, boring_file_filter )
48 import Data.Maybe ( maybeToList )
49 import System.IO ( hPutStrLn, stderr )
50 import qualified System.FilePath.Windows as WindowsFilePath
54 add_description :: String
56 "Add one or more new files or directories."
66 "Add needs to be called whenever you add a new file or directory to your\n"++
67 "project. Of course, it also needs to be called when you first create the\n"++
68 "project, to let darcs know which files should be kept track of.\n"
73 add = DarcsCommand {command_name = "add",
74 command_help = add_help,
75 command_description = add_description,
76 command_extra_args = -1,
77 command_extra_arg_help = ["<FILE or DIRECTORY> ..."],
78 command_command = add_cmd,
79 command_prereq = amInRepository,
80 command_get_arg_possibilities = list_unregistered_files,
81 command_argdefaults = nodefaults,
82 command_advanced_options = [umask_option],
83 command_basic_options =
84 [noskip_boring, allow_problematic_filenames,
85 recursive "add contents of subdirectories",
87 working_repo_dir, dry_run_noxml]}
90 Darcs will refuse to add a file or directory that differs from an existing
91 one only in case. This is because the HFS+ file system used on MacOS
92 treats such files as being one and the same.
94 You can not add symbolic links to darcs.
95 If you try to do that, darcs will refuse and print an error message.
96 Perhaps you want to make symbolic links \emph{to} the files in darcs instead?
102 By default darcs will ignore all files that match any of the boring patterns.
103 If you want to add such a file anyway you must use the \verb!--boring! option.
106 add_cmd :: [DarcsFlag] -> [String] -> IO ()
107 add_cmd opts args = withRepoLock opts $- \repository ->
108 do cur <- slurp_pending repository
109 origfiles <- map toFilePath `fmap` fixSubPaths opts args
110 parlist <- get_parents cur origfiles
111 flist' <- if Recursive `elem` opts
112 then expand_dirs origfiles
113 else return origfiles
114 let flist = nubsort (parlist ++ flist')
115 -- refuse to add boring files recursively:
116 nboring <- if Boring `elem` opts
117 then return $ darcsdir_filter
118 else boring_file_filter
119 let putInfoLn = if Quiet `elem` opts then \_ -> return () else putStrLn
120 sequence_ $ map (putInfoLn . ((msg_skipping msgs ++ " boring file ")++)) $
121 flist \\ nboring flist
122 date <- getIsoDateTime
123 ps <- addp msgs opts date cur $ nboring flist
124 when (nullFL ps && not (null args)) $ do
125 fail "No files were added"
126 when (not gotDryRun) $ add_to_pending repository ps
128 gotDryRun = DryRun `elem` opts
129 msgs | gotDryRun = dryRunMessages
130 | otherwise = normalMessages
132 addp :: AddMessages -> [DarcsFlag] -> String -> Slurpy -> [FilePath] -> IO (FL Prim)
133 addp msgs opts date cur0 files = do
136 (\f rest cur accPS accDups -> do
137 (cur', mp, mdup) <- addp' cur f
138 rest cur' (maybeToList mp ++ accPS) (maybeToList mdup ++ accDups))
139 (\_ ps dups -> return (reverse ps, dups))
142 let uniq_dups = nub dups
144 if gotAllowCaseOnly then ":"
145 else ";\nnote that to ensure portability we don't allow\n" ++
146 "files that differ only in case. Use --case-ok to override this:"
147 when (not (null dups)) $ do
152 isDir <- doesDirectoryReallyExist f
155 "The following directory "++msg_is msgs++" already in the repository"
157 "The following file "++msg_is msgs++" already in the repository"
160 areDirs <- mapM doesDirectoryReallyExist fs
163 "The following directories "++msg_are msgs++" already in the repository"
167 "The following files and directories " ++
168 msg_are msgs ++ " already in the repository"
170 "The following files " ++ msg_are msgs ++ " already in the repository")
171 putInfo $ dupMsg ++ caseMsg
172 mapM_ putInfo uniq_dups
173 return $ concatFL $ unsafeFL ps
175 addp' :: Slurpy -> FilePath -> IO (Slurpy, Maybe (FL Prim), Maybe FilePath)
178 then do return (cur, Nothing, Just f)
181 then do putInfo $ "The filename " ++ f ++ " is invalid under Windows.\nUse --reserved-ok to allow it."
184 isdir <- doesDirectoryReallyExist f
186 then trypatch $ myadddir f
187 else do isfile <- doesFileReallyExist f
189 then trypatch $ myaddfile f
190 else do islink <- isFileReallySymlink f
192 putInfo $ "Sorry, file " ++ f ++ " is a symbolic link, which is unsupported by darcs."
193 else putInfo $ "File "++ f ++" does not exist!"
195 where already_has = if gotAllowCaseOnly
197 else slurp_has_anycase f cur
198 is_badfilename = not (gotAllowWindowsReserved || WindowsFilePath.isValid f)
199 add_failure = (cur, Nothing, Nothing)
201 case apply_to_slurpy p cur of
202 Nothing -> do putInfo $ msg_skipping msgs ++ " '" ++ f ++ "' ... " ++ parent_error
203 return (cur, Nothing, Nothing)
204 Just s' -> do putVerbose $ msg_adding msgs++" '"++f++"'"
205 return (s', Just p, Nothing)
206 parentdir = get_parentdir f
207 have_parentdir = slurp_hasdir (fp2fn parentdir) cur
208 parent_error = if have_parentdir
210 else "couldn't add parent directory '"++parentdir++
212 myadddir d = if gotFancyMoveAdd
213 then adddir (d++"-"++date) :>:
214 move (d++"-"++date) d :>: NilFL
215 else adddir d :>: NilFL
216 myaddfile d = if gotFancyMoveAdd
217 then addfile (d++"-"++date) :>:
218 move (d++"-"++date) d :>: NilFL
219 else addfile d :>: NilFL
220 putVerbose = if Verbose `elem` opts || DryRun `elem` opts
223 putInfo = if Quiet `elem` opts then \_ -> return () else hPutStrLn stderr
224 gotFancyMoveAdd = FancyMoveAdd `elem` opts
225 gotAllowCaseOnly = AllowCaseOnly `elem` opts
226 gotAllowWindowsReserved = AllowWindowsReserved `elem` opts
230 { msg_skipping :: String
231 , msg_adding :: String
236 normalMessages, dryRunMessages :: AddMessages
239 { msg_skipping = "Skipping"
240 , msg_adding = "Adding"
246 { msg_skipping = "Would skip"
247 , msg_adding = "Would add"
248 , msg_is = "would be"
249 , msg_are = "would be"
257 The \verb!--date-trick! option allows you to enable an experimental trick
258 to make add conflicts, in which two users each add a file or directory with
259 the same name, less problematic. While this trick is completely safe, it
260 is not clear to what extent it is beneficial.
263 expand_dirs :: [FilePath] -> IO [FilePath]
264 expand_dirs fs = concat `fmap` mapM expand_one fs
265 expand_one :: FilePath -> IO [FilePath]
266 expand_one "" = list_files
268 isdir <- doesDirectoryReallyExist f
269 if not isdir then return [f]
270 else do fs <- withCurrentDirectory f list_files
271 return $ f: map ((///) f) fs
273 get_parents :: Slurpy -> [FilePath] -> IO [FilePath]
275 concat `fmap` mapM (get_parent cur) fs
276 get_parent :: Slurpy -> FilePath -> IO [FilePath]
278 if slurp_hasdir (fp2fn parentdir) cur
280 else do grandparents <- get_parent cur parentdir
281 return (grandparents ++ [parentdir])
282 where parentdir = get_parentdir f
284 get_parentdir :: FilePath -> FilePath
285 get_parentdir f = reverse $ drop 1 $ dropWhile (/='/') $ reverse f