% Copyright (C) 2002-2005 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; see the file COPYING. If not, write to % the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, % Boston, MA 02110-1301, USA. \subsection{darcs pull} \begin{code} module Pull ( pull, merge_with_us_and_pending, save_patches, check_unrecorded_conflicts, ) where import System ( ExitCode(..), exitWith ) import Monad ( when, liftM ) import Maybe ( catMaybes ) import SignalHandler ( withSignalsBlocked ) import DarcsCommands ( DarcsCommand(..), loggers ) import DarcsCommandsAux ( check_paths ) import DarcsArguments ( DarcsFlag( AnyOrder, Test, Verbose, Quiet, Intersection ), want_external_merge, nocompress, ignoretimes, no_deps, use_external_merge, match_several, fix_filepaths, all_gui_interactive, intersection_or_union, print_dry_run_message_and_exit, any_verbosity, test, dry_run, set_default, summary, working_repo_dir, set_scripts_executable, ssh_cm, umask_option, ) import Repository ( Repository, PatchToken, PatchSet, identifyRepository, amInRepository, withRepoLock, slurp_recorded, slurp_recorded_and_unrecorded, read_repo, absolute_dir, get_unrecorded, with_new_pending, read_pending, sync_repo, applyToWorking, applyToPristine, writePatch, updateInventory, ) import Patch ( Patch, join_patches, merge, patch2patchinfo, unjoin_patches, list_touched_files, invert, list_conflicted_files, null_patch, ) import SelectChanges ( promptChar ) import PatchInfo ( PatchInfo, human_friendly, showPatchInfo ) import SlurpDirectory ( wait_a_moment ) import RepoPrefs ( defaultrepo, set_defaultrepo, get_preflist ) import Motd (show_motd ) import Depends ( get_common_and_uncommon, patchset_intersection, patchset_union ) import Resolution ( standard_resolution, external_resolution ) import SelectChanges ( with_selected_changes ) import DarcsUtils ( clarify_errors, nubsort, formatPath ) import Test ( test_patch ) import Printer ( Doc, putDocLn, errorDoc, vcat, ($$), text ) #include "impossible.h" \end{code} \begin{code} pull_description :: String pull_description = "Copy and apply patches from another repository to this one." \end{code} \options{pull} \haskell{pull_help} \begin{code} pull_help :: String pull_help = "Pull is used to bring changes made in another repository into the current\n"++ "repository (that is, either the one in the current directory, or the one\n"++ "specified with the --repodir option). Pull allows you to bring over all or\n"++ "some of the patches that are in that repository but not in this one. Pull\n"++ "accepts arguments, which are URLs from which to pull, and when called\n"++ "without an argument, pull will use the repository from which you have most\n"++ "recently either pushed or pulled.\n" \end{code} \begin{code} pull :: DarcsCommand pull = DarcsCommand {command_name = "pull", command_help = pull_help, command_description = pull_description, command_extra_args = -1, command_extra_arg_help = ["[REPOSITORY]..."], command_command = pull_cmd, command_prereq = amInRepository, command_get_arg_possibilities = get_preflist "repos", command_argdefaults = defaultrepo, command_darcsoptions = [match_several, all_gui_interactive, intersection_or_union, use_external_merge,nocompress, test, dry_run, summary, any_verbosity, ignoretimes,no_deps, set_default, working_repo_dir, set_scripts_executable, ssh_cm, umask_option]} \end{code} \begin{code} pull_cmd :: [DarcsFlag] -> [String] -> IO () pull_cmd opts unfixedrepodirs@(_:_) = let (logMessage, logError, logDocLn) = loggers opts putInfo = if Quiet `elem` opts then \_ -> return () else logDocLn putVerbose = if Verbose `elem` opts then putDocLn else \_ -> return () in withRepoLock opts $ \repository -> do -- Test to make sure we aren't trying to pull from the current repo let repodirs = fix_filepaths "" opts unfixedrepodirs cur_absolute_repo_dir <- absolute_dir "." req_absolute_repo_dirs <- mapM absolute_dir repodirs when (cur_absolute_repo_dir `elem` req_absolute_repo_dirs) $ fail "Can't pull from current repository!" them <- read_repos opts repodirs old_default <- defaultrepo "" [] set_defaultrepo (head repodirs) opts when (old_default == repodirs) $ putInfo $ text $ "Pulling from "++concatMap formatPath repodirs++"..." mapM (show_motd opts) repodirs us <- read_repo repository case get_common_and_uncommon (us, them) of (_, us', them') -> do putVerbose $ text "We have the following new (to them) patches:" $$ (vcat $ map (human_friendly.fst) $ head us') putVerbose $ text "They have the following patches to pull:" $$ (vcat $ map (human_friendly.fst) $ head them') case them' of [[]] -> do putInfo $ text "No remote changes to pull in!" exitWith ExitSuccess _ -> return () s <- slurp_recorded repository let ps = map fromJustPatch $ reverse $ head them' with_selected_changes "pull" opts s ps (Just $ length ps) $ \ (_,to_be_pulled) -> do print_dry_run_message_and_exit "pull" opts to_be_pulled when (null to_be_pulled) $ do logMessage "You don't want to pull any patches, and that's fine with me!" exitWith ExitSuccess check_paths opts to_be_pulled putVerbose $ text "Getting and merging the following patches:" putVerbose $ format_patches_inventory to_be_pulled (pc,pw) <- merge_with_us_and_pending opts (map fromJustPatch $ reverse $ head us', to_be_pulled) standard_resolved_pw <- standard_resolution pw have_conflicts <- announce_merge_conflicts logMessage standard_resolved_pw have_unrecorded_conflicts <- check_unrecorded_conflicts pc (_, working) <- slurp_recorded_and_unrecorded repository pw_resolved <- case (want_external_merge opts, have_conflicts || have_unrecorded_conflicts) of (Nothing,_) -> return $ join_patches standard_resolved_pw (_,False) -> return $ join_patches standard_resolved_pw (Just c, True) -> do pend <- get_unrecorded repository (AnyOrder:opts) join_patches `liftM` external_resolution c working (join_patches $ (++catMaybes [pend]) $ map fromJustPatch $ reverse $ head us') (join_patches to_be_pulled) pw putVerbose $ text "Applying patches to the local directories..." when (Test `elem` opts) $ do testproblem <- test_patch opts pc when (testproblem /= ExitSuccess) $ do logError "Error in test..." exitWith $ ExitFailure 1 patchTokens <- save_patches repository opts $ unjoin_patches pc mp <- get_unrecorded repository (AnyOrder:opts) let newpend = join_patches [invert pc, fromMaybePatch mp, pw_resolved] withSignalsBlocked $ with_new_pending repository newpend $ do repairable $ applyToPristine repository pc `catch` \e -> fail ("Error applying patch to recorded.\nRunning 'darcs repair' on the target repository may help.\n" ++ show e) updateInventory repository patchTokens -- so work will be more recent than rec: revertable wait_a_moment revertable $ applyToWorking repository opts pw_resolved `catch` \e -> fail ("Error applying patch to working dir:\n" ++ show e) sync_repo repository putInfo $ text "Finished pulling and applying." where fromMaybePatch Nothing = null_patch fromMaybePatch (Just p) = p repairable x = x `clarify_errors` unlines ["Your repository is now in an inconsistent state.", "This must be fixed by running darcs repair."] revertable x = x `clarify_errors` unlines ["This may have left your working directory an inconsistent", "but recoverable state. If you had no un-recorded changes", "by using 'darcs revert' you should be able to make your", "working directory consistent again."] pull_cmd _ [] = fail "No default repository to pull from, please specify one" format_patches_inventory :: [Patch] -> Doc format_patches_inventory ps = vcat $ map (showPatchInfo.fromJust.patch2patchinfo) ps fromJustPatch :: (PatchInfo, Maybe Patch) -> Patch fromJustPatch (pinfo, Nothing) = errorDoc $ text "Error reading patch:" $$ human_friendly pinfo fromJustPatch (_, Just p) = p \end{code} \begin{code} read_repos :: [DarcsFlag] -> [String] -> IO PatchSet read_repos _ [] = impossible read_repos opts us = do rs <- mapM (\u -> identifyRepository u >>= read_repo) us return $ if Intersection `elem` opts then patchset_intersection rs else patchset_union rs \end{code} \begin{options} --intersection, --union [default] \end{options} If you provide more than one repository as an argument to pull, darcs' behavior is determined by the presence of the \verb!--intersection! flag. The default (\verb!--union!) behavior is to pull any patches that are in any of the specified repositories. If you instead specify the \verb!--intersection! flag, darcs will only pull those patches which are present in all source repositories. \begin{options} --external-merge \end{options} You can use an external interactive merge tool to resolve conflicts with the flag \verb!--external-merge!. For more details see subsection~\ref{resolution}. \begin{options} --matches, --no-deps, --patches, --tags \end{options} The \verb!--patches!, \verb!--matches!, and \verb!--tags! options can be used to select which patches to pull, as described in subsection~\ref{selecting}. darcs will silently pull along any other patches upon which the selected patches depend. So \verb!--patches bugfix! means ``pull all the patches with `bugfix' in their name, along with any patches they require.'' If you really only want the patches with `bugfix' in their name, you should use the \verb!--no-deps! option, which makes darcs pull in only the selected patches which have no dependencies (apart from other selected patches). \begin{options} --no-test, --test \end{options} If you specify the \verb!--test! option, pull will run the test (if a test exists) on a scratch copy of the repository contents prior to actually performing the pull. If the test fails, the pull will be aborted. \begin{options} --verbose \end{options} Adding the \verb!--verbose! option causes another section to appear in the output which also displays a summary of patches that you have and the remote repository lacks. Thus, the following syntax can be used to show you all the patch differences between two repositories: \begin{verbatim} darcs pull --dry-run --verbose \end{verbatim} \begin{code} save_patches :: Repository -> [DarcsFlag] -> Maybe [Patch] -> IO [(PatchInfo, PatchToken)] save_patches _ _ (Just []) = return [] save_patches _ _ Nothing = return [] save_patches repo opts (Just (p:ps)) = do p' <- (liftM ppt2pipt) (writePatch repo opts p) ps' <- save_patches repo opts $ Just ps return (p':ps') where ppt2pipt :: (Patch, PatchToken) -> (PatchInfo, PatchToken) ppt2pipt (patch, pt) = (fromJust (patch2patchinfo patch), pt) \end{code} \begin{code} merge_with_us_and_pending :: [DarcsFlag] -> ([Patch],[Patch]) -> IO (Patch, Patch) merge_with_us_and_pending opts (us,them) = case (join_patches us, join_patches them) of (usp, themp) -> case merge (themp, usp) of Nothing -> fail "There was a bug in merging... giving up!" Just (themp',_) -> do past_pending <- merge_with_pending opts themp' return (themp', past_pending) merge_with_pending :: [DarcsFlag] -> Patch -> IO Patch merge_with_pending opts p = do repository <- identifyRepository "." -- we don't care if it looks pretty... pend <- get_unrecorded repository (AnyOrder:opts) case pend of Nothing -> return p Just pendp -> case merge (p,pendp) of Nothing -> fail "Bug in merging with pending..." Just (p',_) -> return p' \end{code} \begin{code} announce_merge_conflicts :: (String -> IO ()) -> [Patch] -> IO Bool announce_merge_conflicts logMessage resolved_pw = case nubsort $ list_touched_files $ join_patches $ tail resolved_pw of [] -> return False cfs -> do logMessage "We have conflicts in the following files:" logMessage $ unwords cfs return True check_unrecorded_conflicts :: Patch -> IO Bool check_unrecorded_conflicts pc = do repository <- identifyRepository "." mpend <- read_pending repository case mpend of Nothing -> return False Just pend -> case merge (pend, pc) of Nothing -> impossible Just (pend',_) -> case list_conflicted_files pend' of [] -> return False fs -> do yorn <- promptChar ("You have conflicting local changes to:\n" ++ unwords fs++"\nProceed?") "yn" when (yorn /= 'y') $ do putStr "Cancelled." exitWith ExitSuccess return True \end{code}