% Copyright (C) 2002-2004 David Roundy % % This program is free software; you can redistribute it and/or modify % it under the terms of the GNU General Public License as published by % the Free Software Foundation; either version 2, or (at your option) % any later version. % % This program is distributed in the hope that it will be useful, % but WITHOUT ANY WARRANTY; without even the implied warranty of % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the % GNU General Public License for more details. % % You should have received a copy of the GNU General Public License % along with this program; if not, write to the Free Software Foundation, % Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. \section{darcs get} \begin{code} module Get ( get ) where import IO ( hFlush, stdout ) import Directory ( setCurrentDirectory, doesDirectoryExist, doesFileExist, createDirectory ) import Workaround ( getCurrentDirectory ) import System ( ExitCode(..), exitWith ) import Monad ( when, unless ) import DarcsCommands ( DarcsCommand(..), nodefaults ) import DarcsArguments ( DarcsFlag( RepoName, Partial, Verbose, Quiet, Context ), any_verbosity, partial, reponame, match_one_context, set_default, ) import Repository ( read_repo, write_inventory, write_checkpoint_patch, mmap_slurp_all_but_darcs, slurp_recorded, is_repo, absolute_dir, get_checkpoint, copy_repo_patches, apply_patches, sync_repo, ) import Patch ( apply_to_slurpy, patch2patchinfo, invert ) import SlurpDirectory ( slurp_write, slurp_write_dirty, empty_slurpy ) import External ( copyFileOrUrl, Cachable(..) ) import Depends ( get_common_and_uncommon ) import RepoPrefs ( set_defaultrepo, write_default_prefs, show_motd ) import Match ( have_patchset_match, get_one_patchset ) import Lock ( rm_recursive ) import DarcsUtils ( catchall ) #include "impossible.h" \end{code} \begin{code} get_description :: String get_description = "Get a repository.\n" \end{code} \options{get} \haskell{get_help} I recommend using the \verb!--verbose! flag to get, as this command can take a while, and with no feedback, that can be rather boring. If the remote repo and the current directory are in the same filesystem and that filesystem supports hard links, get will create hard links for the patch files, which means that the additional storage space needed will be minimal. This is \emph{very} good for your disk usage (and for the speed of running get), so if you want multiple copies of a repository, I strongly recommend first running \verb!darcs get! to get yourself one copy, and then running \verb!darcs get! on that copy to make any more you like. The only catch is that the first time you run \verb!darcs push! or \verb!darcs pull! from any of these second copies, by default they will access your first copy---which may not be what you want. You may specify the name of the repository created by providing a second argument to get, which is a directory name. \begin{code} get_help :: String get_help = "Get is used to get a local copy of a repository.\n" \end{code} \begin{code} get :: DarcsCommand get = DarcsCommand {command_name = "get", command_help = get_help, command_description = get_description, command_extra_args = -1, command_extra_arg_help = [""], command_command = get_cmd, command_prereq = \_ -> return $ (Just "", ""), command_get_arg_possibilities = return [], command_argdefaults = nodefaults, command_darcsoptions = [reponame, partial, match_one_context, any_verbosity, set_default]} \end{code} \begin{code} get_cmd :: [DarcsFlag] -> [String] -> IO () get_cmd opts [inrepodir, outname] = get_cmd (RepoName outname:opts) [inrepodir] get_cmd orig_opts [inrepodir] = do former_dir <- getCurrentDirectory let opts = fix_context orig_opts fix_context o@(Context ('/':_):_) = o fix_context (Context f:os) = Context (former_dir++"/"++f):os fix_context (o:os) = o : fix_context os fix_context [] = [] repodir <- absolute_dir inrepodir repovalid <- is_repo repodir unless repovalid $ do putStr $ "Bad repo directory: "++repodir++"\n" exitWith $ ExitFailure 1 show_motd opts repodir patches <- read_repo repodir when (Partial `elem` opts) $ putVerbose "Reading checkpoint...\n" mch <- get_checkpoint opts repodir myname <- make_repo_name opts repodir createDirectory myname setCurrentDirectory myname createDirectory "_darcs" createDirectory "_darcs/patches" createDirectory "_darcs/current" createDirectory "_darcs/checkpoints" createDirectory "_darcs/prefs" write_default_prefs when (inrepodir == repodir) $ set_defaultrepo repodir opts putVerbose $ "Getting the inventory...\n" write_inventory "." patches copy_repo_patches opts repodir "." local_patches <- read_repo "." repo_is_local <- doesDirectoryExist repodir if repo_is_local && not (Partial `elem` opts) then do s <- slurp_recorded repodir copyFileOrUrl (repodir++"/_darcs/prefs/prefs") "_darcs/prefs/prefs" (MaxAge 600) `catchall` return () slurp_write s s' <- slurp_recorded repodir setCurrentDirectory "_darcs/current" slurp_write s' else do setCurrentDirectory myname putInfo $ "Applying patches to the \"working\" directory...\n" if Partial `elem` opts && mch /= Nothing then let p_ch = fromJust mch pi_ch = fromJust $ patch2patchinfo p_ch needed_patches = dropWhile ((/= pi_ch).fst) $ reverse $ concat local_patches in do write_checkpoint_patch p_ch case apply_to_slurpy p_ch empty_slurpy of Just s -> slurp_write_dirty s Nothing -> fail "Bad checkpoint!" apply_patches putVorDot putInfo needed_patches else apply_patches putVorDot putInfo $ reverse $ concat local_patches finishDots s <- mmap_slurp_all_but_darcs "." setCurrentDirectory "_darcs/current" slurp_write s putVerbose "Syncing the repository...\n" setCurrentDirectory myname sync_repo go_to_chosen_version putVerbose putInfo opts putStr $ "Finished getting.\n" where am_verbose = Verbose `elem` orig_opts am_informative = not $ Quiet `elem` orig_opts putVerbose s = when am_verbose $ putStr s putInfo s = when am_informative $ putStr s putVorDot s = if am_verbose then putStr s else when am_informative $ do putStr "." hFlush stdout finishDots = when (am_informative && not am_verbose) $ putStr "\n" get_cmd _ _ = fail "You must provide 'get' with either one or two arguments." \end{code} \begin{code} make_repo_name :: [DarcsFlag] -> FilePath -> IO String make_repo_name (RepoName n:_) _ = modify_repo_name n make_repo_name (_:as) d = make_repo_name as d make_repo_name [] d = case dropWhile (=='.') $ reverse $ takeWhile (\c -> c /= '/' && c /= ':') $ dropWhile (=='/') $ reverse d of "" -> modify_repo_name "anonymous_repo" base -> modify_repo_name base modify_repo_name :: String -> IO String modify_repo_name name = if head name == '/' then mrn name (-1) else do cwd <- getCurrentDirectory mrn (cwd ++ "/" ++ name) (-1) where mrn :: String -> Int -> IO String mrn n i = do exists <- doesDirectoryExist thename file_exists <- doesFileExist thename if not exists && not file_exists then do when (i /= -1) $ putStr $ "Directory '"++ n ++ "' already exists, creating repository as '"++ thename ++"'\n" return thename else mrn n $ i+1 where thename = if i == -1 then n else n++"_"++show i \end{code} If you want to get a specific version of a repository, you have a few options. You can either use the \verb!--tag!, \verb!--to-patch! or \verb!--to-match! options, or you can use the \verb!--context=FILENAME! option, which specifies a file containing a context generated with \verb!darcs changes --context!. This allows you (for example) to include in your compiled program an option to output the precise version of the repository from which it was generated, and then perhaps ask users to include this information in bug reports. Note that when specifying \verb!--to-patch! or \verb!--to-match!, you may get a version of your code that has never before been seen, if the patches have gotten themselves reordered. If you ever want to be able to precisely reproduce a given version, you need either to tag it or create a context file. \begin{code} go_to_chosen_version :: (String -> IO ()) -> (String -> IO ()) -> [DarcsFlag] -> IO () go_to_chosen_version putVerbose putInfo opts = when (have_patchset_match opts) $ do putVerbose "Going to specified version...\n" patches <- read_repo "." context <- get_one_patchset opts let (_,us',them') = get_common_and_uncommon (patches, context) when (them' /= [[]]) $ fail $ "Missing patches from context!" write_inventory "." context apply_patches putVerbose putInfo $ map invert_it $ head us' rm_recursive "_darcs/current" createDirectory "_darcs/current" s <- mmap_slurp_all_but_darcs "." setCurrentDirectory "_darcs/current" slurp_write s where invert_it (pinf, Nothing) = (pinf, Nothing) invert_it (pinf, Just p) = (pinf, Just $ invert p) \end{code}