packages feed

git-annex 3.20111211 → 3.20111231

raw patch · 167 files changed

+3795/−1711 lines, 167 filesdep +lifted-basedep +transformers-basedep ~monad-control

Dependencies added: lifted-base, transformers-base

Dependency ranges changed: monad-control

Files

Annex.hs view
@@ -5,7 +5,7 @@  - Licensed under the GNU GPL version 3 or higher.  -} -{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE GeneralizedNewtypeDeriving, TypeFamilies, MultiParamTypeClasses #-}  module Annex ( 	Annex,@@ -22,13 +22,15 @@ 	fromRepo, ) where -import Control.Monad.IO.Control import Control.Monad.State+import Control.Monad.Trans.Control (StM, MonadBaseControl, liftBaseWith, restoreM)+import Control.Monad.Base (liftBase, MonadBase)  import Common import qualified Git+import qualified Git.Config import Git.CatFile-import Git.Queue+import qualified Git.Queue import Types.Backend import qualified Types.Remote import Types.Crypto@@ -36,6 +38,7 @@ import Types.TrustLevel import Types.UUID import qualified Utility.Matcher+import qualified Utility.Format import qualified Data.Map as M  -- git-annex's monad@@ -43,32 +46,44 @@ 	deriving ( 		Monad, 		MonadIO,-		MonadControlIO, 		MonadState AnnexState, 		Functor, 		Applicative 	) +instance MonadBase IO Annex where+	liftBase = Annex . liftBase++instance MonadBaseControl IO Annex where+	newtype StM Annex a = StAnnex (StM (StateT AnnexState IO) a)+	liftBaseWith f = Annex $ liftBaseWith $ \runInIO ->+		f $ liftM StAnnex . runInIO . runAnnex+	restoreM = Annex . restoreM . unStAnnex+		where+			unStAnnex (StAnnex st) = st+ data OutputType = NormalOutput | QuietOutput | JSONOutput +type Matcher a = Either [Utility.Matcher.Token a] (Utility.Matcher.Matcher a)+ -- internal state storage data AnnexState = AnnexState 	{ repo :: Git.Repo-	, backends :: [Backend Annex]-	, remotes :: [Types.Remote.Remote Annex]-	, repoqueue :: Queue+	, backends :: [BackendA Annex]+	, remotes :: [Types.Remote.RemoteA Annex]+	, repoqueue :: Git.Queue.Queue 	, output :: OutputType 	, force :: Bool 	, fast :: Bool 	, auto :: Bool-	, print0 :: Bool+	, format :: Maybe Utility.Format.Format 	, branchstate :: BranchState 	, catfilehandle :: Maybe CatFileHandle 	, forcebackend :: Maybe String 	, forcenumcopies :: Maybe Int 	, toremote :: Maybe String 	, fromremote :: Maybe String-	, limit :: Either [Utility.Matcher.Token (FilePath -> Annex Bool)] (Utility.Matcher.Matcher (FilePath -> Annex Bool))+	, limit :: Matcher (FilePath -> Annex Bool) 	, forcetrust :: [(UUID, TrustLevel)] 	, trustmap :: Maybe TrustMap 	, ciphers :: M.Map EncryptedCipher Cipher@@ -79,12 +94,12 @@ 	{ repo = gitrepo 	, backends = [] 	, remotes = []-	, repoqueue = Git.Queue.empty+	, repoqueue = Git.Queue.new 	, output = NormalOutput 	, force = False 	, fast = False 	, auto = False-	, print0 = False+	, format = Nothing 	, branchstate = startBranchState 	, catfilehandle = Nothing 	, forcebackend = Nothing@@ -99,7 +114,7 @@  {- Create and returns an Annex state object for the specified git repo. -} new :: Git.Repo -> IO AnnexState-new gitrepo = newState <$> Git.configRead gitrepo+new gitrepo = newState <$> Git.Config.read gitrepo  {- performs an action in the Annex monad -} run :: AnnexState -> Annex a -> IO (a, AnnexState)@@ -114,7 +129,7 @@  {- Applies a state mutation function to change the internal state.   -- - Example: changeState $ \s -> s { quiet = True }+ - Example: changeState $ \s -> s { output = QuietOutput }  -} changeState :: (AnnexState -> AnnexState) -> Annex () changeState = modify
Annex/Branch.hs view
@@ -6,29 +6,33 @@  -}  module Annex.Branch (+	name,+	hasOrigin,+	hasSibling,+	siblingBranches, 	create, 	update,-	disableUpdate,+	forceUpdate,+	updateTo, 	get, 	change, 	commit, 	files,-	refExists,-	hasOrigin,-	hasSomeBranch,-	name	 ) where -import System.IO.Binary-import System.Exit import qualified Data.ByteString.Lazy.Char8 as L  import Common.Annex import Annex.Exception-import Types.BranchState+import Annex.BranchState+import Annex.Journal import qualified Git+import qualified Git.Command+import qualified Git.Ref+import qualified Git.Branch import qualified Git.UnionMerge-import qualified Annex+import qualified Git.HashObject+import qualified Git.Index import Annex.CatFile  {- Name of the branch that is used to store git-annex's information. -}@@ -43,162 +47,60 @@ originname :: Git.Ref originname = Git.Ref $ "origin/" ++ show name -{- Populates the branch's index file with the current branch contents.- - - - This is only done when the index doesn't yet exist, and the index - - is used to build up changes to be commited to the branch, and merge- - in changes from other branches.- -}-genIndex :: Git.Repo -> IO ()-genIndex g = Git.UnionMerge.stream_update_index g-	[Git.UnionMerge.ls_tree fullname g]--{- Merges the specified branches into the index.- - Any changes staged in the index will be preserved. -}-mergeIndex :: [Git.Ref] -> Annex ()-mergeIndex branches = do-	h <- catFileHandle-	inRepo $ \g -> Git.UnionMerge.merge_index h g branches--{- Runs an action using the branch's index file. -}-withIndex :: Annex a -> Annex a-withIndex = withIndex' False-withIndex' :: Bool -> Annex a -> Annex a-withIndex' bootstrapping a = do-	f <- fromRepo gitAnnexIndex-	bracketIO (Git.useIndex f) id $ do-		unlessM (liftIO $ doesFileExist f) $ do-			unless bootstrapping create-			liftIO $ createDirectoryIfMissing True $ takeDirectory f-			unless bootstrapping $ inRepo genIndex-		a--{- Updates the branch's index to reflect the current contents of the branch.- - Any changes staged in the index will be preserved.- -- - Compares the ref stored in the lock file with the current- - ref of the branch to see if an update is needed.- -}-updateIndex :: Annex (Maybe Git.Ref)-updateIndex = do-	branchref <- getRef fullname-	go branchref-	return branchref-	where-		go Nothing = return ()-		go (Just branchref) = do-			lock <- fromRepo gitAnnexIndexLock-			lockref <- firstRef <$> liftIO (catchDefaultIO (readFileStrict lock) "")-			when (lockref /= branchref) $ do-				withIndex $ mergeIndex [fullname]-				setIndexRef branchref--{- Record that the branch's index has been updated to correspond to a- - given ref of the branch. -}-setIndexRef :: Git.Ref -> Annex ()-setIndexRef ref = do-        lock <- fromRepo gitAnnexIndexLock-	liftIO $ writeFile lock $ show ref ++ "\n"--{- Commits the staged changes in the index to the branch.- - - - Ensures that the branch's index file is first updated to include the- - current state of the branch, before running the commit action. This- - is needed because the branch may have had changes pushed to it, that- - are not yet reflected in the index.- -- - Also safely handles a race that can occur if a change is being pushed- - into the branch at the same time. When the race happens, the commit will- - be made on top of the newly pushed change, but without the index file- - being updated to include it. The result is that the newly pushed- - change is reverted. This race is detected and another commit made- - to fix it.- -}-commitBranch :: String -> [Git.Ref] -> Annex ()-commitBranch message parents = do-	expected <- updateIndex-	committedref <- inRepo $ Git.commit message fullname parents-	setIndexRef committedref-	parentrefs <- commitparents <$> catObject committedref-	when (racedetected expected parentrefs) $-		fixrace committedref parentrefs-	where-		-- look for "parent ref" lines and return the refs-		commitparents = map (Git.Ref . snd) . filter isparent .-			map (toassoc . L.unpack) . L.lines-		toassoc = separate (== ' ')-		isparent (k,_) = k == "parent"-		- 		{- The race can be detected by checking the commit's-		 - parent, which will be the newly pushed branch,-		 - instead of the expected ref that the index was updated to. -}-		racedetected Nothing parentrefs-			| null parentrefs = False -- first commit, no parents-			| otherwise = True -- race on first commit-		racedetected (Just expectedref) parentrefs-			| expectedref `elem` parentrefs = False -- good parent-			| otherwise = True -- race!-		-		{- To recover from the race, union merge the lost refs-		 - into the index, and recommit on top of the bad commit. -}-		fixrace committedref lostrefs = do-			mergeIndex lostrefs-			commitBranch racemessage [committedref]-		-		racemessage = message ++ " (recovery from race)"--{- Runs an action using the branch's index file, first making sure that- - the branch and index are up-to-date. -}-withIndexUpdate :: Annex a -> Annex a-withIndexUpdate a = update >> withIndex a--getState :: Annex BranchState-getState = Annex.getState Annex.branchstate--setState :: BranchState -> Annex ()-setState state = Annex.changeState $ \s -> s { Annex.branchstate = state }--setCache :: FilePath -> String -> Annex ()-setCache file content = do-	state <- getState-	setState state { cachedFile = Just file, cachedContent = content }+{- Does origin/git-annex exist? -}+hasOrigin :: Annex Bool+hasOrigin = inRepo $ Git.Ref.exists originname -invalidateCache :: Annex ()-invalidateCache = do-	state <- getState-	setState state { cachedFile = Nothing, cachedContent = "" }+{- Does the git-annex branch or a sibling foo/git-annex branch exist? -}+hasSibling :: Annex Bool+hasSibling = not . null <$> siblingBranches -getCache :: FilePath -> Annex (Maybe String)-getCache file = getState >>= go-	where-		go state-			| cachedFile state == Just file =-				return $ Just $ cachedContent state-			| otherwise = return Nothing+{- List of git-annex (refs, branches), including the main one and any+ - from remotes. Duplicate refs are filtered out. -}+siblingBranches :: Annex [(Git.Ref, Git.Branch)]+siblingBranches = inRepo $ Git.Ref.matchingUniq name  {- Creates the branch, if it does not already exist. -} create :: Annex ()-create = unlessM hasBranch $ hasOrigin >>= go+create = do+	_ <- getBranch+	return ()++{- Returns the ref of the branch, creating it first if necessary. -}+getBranch :: Annex (Git.Ref)+getBranch = maybe (hasOrigin >>= go >>= use) (return) =<< branchsha 	where 		go True = do-			inRepo $ Git.run "branch"+			inRepo $ Git.Command.run "branch" 				[Param $ show name, Param $ show originname]-			maybe (return ()) setIndexRef =<< getRef fullname-		go False = withIndex' True $ -			setIndexRef =<< (inRepo $ Git.commit "branch created" fullname [])--{- Stages the journal, and commits staged changes to the branch. -}-commit :: String -> Annex ()-commit message = whenM journalDirty $ lockJournal $ do-	stageJournalFiles-	withIndex $ commitBranch message [fullname]+			fromMaybe (error $ "failed to create " ++ show name)+				<$> branchsha+		go False = withIndex' True $ do+			inRepo $ Git.Branch.commit "branch created" fullname []+		use sha = do+			setIndexSha sha+			return sha+		branchsha = inRepo $ Git.Ref.sha fullname  {- Ensures that the branch and index are is up-to-date; should be  - called before data is read from it. Runs only once per git-annex run.+ -}+update :: Annex ()+update = runUpdateOnce $ updateTo =<< siblingBranches++{- Forces an update even if one has already been run. -}+forceUpdate :: Annex ()+forceUpdate = updateTo =<< siblingBranches++{- Merges the specified Refs into the index, if they have any changes not+ - already in it. The Branch names are only used in the commit message;+ - it's even possible that the provided Branches have not been updated to+ - point to the Refs yet.  -  - Before refs are merged into the index, it's important to first stage the  - journal into the index. Otherwise, any changes in the journal would  - later get staged, and might overwrite changes made during the merge.+ - If no Refs are provided, the journal is still staged and committed.  -  - (It would be cleaner to handle the merge by updating the journal, not the  - index, with changes from the branches.)@@ -206,152 +108,35 @@  - The branch is fast-forwarded if possible, otherwise a merge commit is  - made.  -}-update :: Annex ()-update = onceonly $ do-	-- ensure branch exists-	create+updateTo :: [(Git.Ref, Git.Branch)] -> Annex ()+updateTo pairs = do+	-- ensure branch exists, and get its current ref+	branchref <- getBranch 	-- check what needs updating before taking the lock 	dirty <- journalDirty-	c <- filterM (changedBranch name . snd) =<< siblingBranches-	let (refs, branches) = unzip c+	(refs, branches) <- unzip <$> filterM isnewer pairs 	if (not dirty && null refs)-		then simpleupdate+		then updateIndex branchref 		else withIndex $ lockJournal $ do-			when dirty stageJournalFiles+			when dirty stageJournal 			let merge_desc = if null branches 				then "update"  				else "merging " ++-					unwords (map Git.refDescribe branches) ++ +					unwords (map Git.Ref.describe branches) ++  					" into " ++ show name 			unless (null branches) $ do 				showSideAction merge_desc-				mergeIndex branches-			ff <- if dirty then return False else tryFastForwardTo refs+				mergeIndex refs+			ff <- if dirty+				then return False+				else inRepo $ Git.Branch.fastForward fullname refs 			if ff-				then simpleupdate-				else commitBranch merge_desc (nub $ fullname:refs)+				then updateIndex branchref+				else commitBranch branchref merge_desc+					(nub $ fullname:refs) 			invalidateCache 	where-		onceonly a = unlessM (branchUpdated <$> getState) $ do-			r <- a-			disableUpdate-			return r-		simpleupdate = do-			_ <- updateIndex-			return ()--{- Checks if the second branch has any commits not present on the first- - branch. -}-changedBranch :: Git.Branch -> Git.Branch -> Annex Bool-changedBranch origbranch newbranch = not . L.null <$> diffs-	where-		diffs = inRepo $ Git.pipeRead-			[ Param "log"-			, Param (show origbranch ++ ".." ++ show newbranch)-			, Params "--oneline -n1"-			]--{- Given a set of refs that are all known to have commits not- - on the git-annex branch, tries to update the branch by a- - fast-forward.- -- - In order for that to be possible, one of the refs must contain- - every commit present in all the other refs, as well as in the- - git-annex branch.- -}-tryFastForwardTo :: [Git.Ref] -> Annex Bool-tryFastForwardTo [] = return True-tryFastForwardTo (first:rest) = do-	-- First, check that the git-annex branch does not contain any-	-- new commits that are not in the first other branch. If it does,-	-- cannot fast-forward.-	diverged <- changedBranch first fullname-	if diverged-		then no_ff-		else maybe no_ff do_ff =<< findbest first rest-	where-		no_ff = return False-		do_ff branch = do-			inRepo $ Git.run "update-ref"-				[Param $ show fullname, Param $ show branch]-			return True-		findbest c [] = return $ Just c-		findbest c (r:rs)-			| c == r = findbest c rs-			| otherwise = do-			better <- changedBranch c r-			worse <- changedBranch r c-			case (better, worse) of-				(True, True) -> return Nothing -- divergent fail-				(True, False) -> findbest r rs -- better-				(False, True) -> findbest c rs -- worse-				(False, False) -> findbest c rs -- same-			-{- Avoids updating the branch. A useful optimisation when the branch- - is known to have not changed, or git-annex won't be relying on info- - from it. -}-disableUpdate :: Annex ()-disableUpdate = Annex.changeState setupdated-	where-		setupdated s = s { Annex.branchstate = new }-			where-				new = old { branchUpdated = True }-				old = Annex.branchstate s--{- Checks if a git ref exists. -}-refExists :: Git.Ref -> Annex Bool-refExists ref = inRepo $ Git.runBool "show-ref"-	[Param "--verify", Param "-q", Param $ show ref]--{- Does the main git-annex branch exist? -}-hasBranch :: Annex Bool-hasBranch = refExists fullname--{- Does origin/git-annex exist? -}-hasOrigin :: Annex Bool-hasOrigin = refExists originname--{- Does the git-annex branch or a foo/git-annex branch exist? -}-hasSomeBranch :: Annex Bool-hasSomeBranch = not . null <$> siblingBranches--{- List of git-annex (refs, branches), including the main one and any- - from remotes. Duplicate refs are filtered out. -}-siblingBranches :: Annex [(Git.Ref, Git.Branch)]-siblingBranches = do-	r <- inRepo $ Git.pipeRead [Param "show-ref", Param $ show name]-	return $ nubBy uref $ map (gen . words . L.unpack) (L.lines r)-	where-		gen l = (Git.Ref $ head l, Git.Ref $ last l)-		uref (a, _) (b, _) = a == b--{- Get the ref of a branch. -}-getRef :: Git.Ref -> Annex (Maybe Git.Ref)-getRef branch = process . L.unpack <$> showref-	where-		showref = inRepo $ Git.pipeRead [Param "show-ref",-			Param "--hash", -- get the hash-			Params "--verify", -- only exact match-			Param $ show branch]-		process [] = Nothing-		process s = Just $ firstRef s--firstRef :: String-> Git.Ref-firstRef = Git.Ref . takeWhile (/= '\n')--{- Applies a function to modifiy the content of a file.- -- - Note that this does not cause the branch to be merged, it only- - modifes the current content of the file on the branch.- -}-change :: FilePath -> (String -> String) -> Annex ()-change file a = lockJournal $ getStale file >>= return . a >>= set file--{- Records new content of a file into the journal. -}-set :: FilePath -> String -> Annex ()-set file content = do-	setJournalFile file content-	setCache file content+		isnewer (r, _) = inRepo $ Git.Branch.changed fullname r  {- Gets the content of a file on the branch, or content from the journal, or  - staged in the index.@@ -380,116 +165,154 @@ 			setCache file content 			return content +{- Applies a function to modifiy the content of a file.+ -+ - Note that this does not cause the branch to be merged, it only+ - modifes the current content of the file on the branch.+ -}+change :: FilePath -> (String -> String) -> Annex ()+change file a = lockJournal $ getStale file >>= return . a >>= set file++{- Records new content of a file into the journal and cache. -}+set :: FilePath -> String -> Annex ()+set file content = do+	setJournalFile file content+	setCache file content++{- Stages the journal, and commits staged changes to the branch. -}+commit :: String -> Annex ()+commit message = whenM journalDirty $ lockJournal $ do+	stageJournal+	ref <- getBranch+	withIndex $ commitBranch ref message [fullname]++{- Commits the staged changes in the index to the branch.+ - + - Ensures that the branch's index file is first updated to the state+ - of the brannch at branchref, before running the commit action. This+ - is needed because the branch may have had changes pushed to it, that+ - are not yet reflected in the index.+ -+ - Also safely handles a race that can occur if a change is being pushed+ - into the branch at the same time. When the race happens, the commit will+ - be made on top of the newly pushed change, but without the index file+ - being updated to include it. The result is that the newly pushed+ - change is reverted. This race is detected and another commit made+ - to fix it.+ - + - The branchref value can have been obtained using getBranch at any+ - previous point, though getting it a long time ago makes the race+ - more likely to occur.+ -}+commitBranch :: Git.Ref -> String -> [Git.Ref] -> Annex ()+commitBranch branchref message parents = do+	updateIndex branchref+	committedref <- inRepo $ Git.Branch.commit message fullname parents+	setIndexSha committedref+	parentrefs <- commitparents <$> catObject committedref+	when (racedetected branchref parentrefs) $+		fixrace committedref parentrefs+	where+		-- look for "parent ref" lines and return the refs+		commitparents = map (Git.Ref . snd) . filter isparent .+			map (toassoc . L.unpack) . L.lines+		toassoc = separate (== ' ')+		isparent (k,_) = k == "parent"+		+ 		{- The race can be detected by checking the commit's+		 - parent, which will be the newly pushed branch,+		 - instead of the expected ref that the index was updated to. -}+		racedetected expectedref parentrefs+			| expectedref `elem` parentrefs = False -- good parent+			| otherwise = True -- race!+		+		{- To recover from the race, union merge the lost refs+		 - into the index, and recommit on top of the bad commit. -}+		fixrace committedref lostrefs = do+			mergeIndex lostrefs+			commitBranch committedref racemessage [committedref]+		+		racemessage = message ++ " (recovery from race)"+ {- Lists all files on the branch. There may be duplicates in the list. -} files :: Annex [FilePath] files = withIndexUpdate $ do-	bfiles <- inRepo $ Git.pipeNullSplit+	bfiles <- inRepo $ Git.Command.pipeNullSplit 		[Params "ls-tree --name-only -r -z", Param $ show fullname] 	jfiles <- getJournalledFiles 	return $ jfiles ++ bfiles -{- Records content for a file in the branch to the journal.- -- - Using the journal, rather than immediatly staging content to the index- - avoids git needing to rewrite the index after every change. -}-setJournalFile :: FilePath -> String -> Annex ()-setJournalFile file content = do-	g <- gitRepo-	liftIO $ doRedo (write g) $ do-		createDirectoryIfMissing True $ gitAnnexJournalDir g-		createDirectoryIfMissing True $ gitAnnexTmpDir g-	where-		-- journal file is written atomically-		write g = do-			let jfile = journalFile g file-			let tmpfile = gitAnnexTmpDir g </> takeFileName jfile-			writeBinaryFile tmpfile content-			moveFile tmpfile jfile -{- Gets any journalled content for a file in the branch. -}-getJournalFile :: FilePath -> Annex (Maybe String)-getJournalFile file = inRepo $ \g -> catchMaybeIO $-	readFileStrict $ journalFile g file+{- Populates the branch's index file with the current branch contents.+ - + - This is only done when the index doesn't yet exist, and the index + - is used to build up changes to be commited to the branch, and merge+ - in changes from other branches.+ -}+genIndex :: Git.Repo -> IO ()+genIndex g = Git.UnionMerge.stream_update_index g+	[Git.UnionMerge.ls_tree fullname g] -{- List of files that have updated content in the journal. -}-getJournalledFiles :: Annex [FilePath]-getJournalledFiles = map fileJournal <$> getJournalFiles+{- Merges the specified refs into the index.+ - Any changes staged in the index will be preserved. -}+mergeIndex :: [Git.Ref] -> Annex ()+mergeIndex branches = do+	h <- catFileHandle+	inRepo $ \g -> Git.UnionMerge.merge_index h g branches -{- List of existing journal files. -}-getJournalFiles :: Annex [FilePath]-getJournalFiles = do-	g <- gitRepo-	fs <- liftIO $-		catchDefaultIO (getDirectoryContents $ gitAnnexJournalDir g) []-	return $ filter (`notElem` [".", ".."]) fs+{- Runs an action using the branch's index file. -}+withIndex :: Annex a -> Annex a+withIndex = withIndex' False+withIndex' :: Bool -> Annex a -> Annex a+withIndex' bootstrapping a = do+	f <- fromRepo gitAnnexIndex+	bracketIO (Git.Index.override f) id $ do+		unlessM (liftIO $ doesFileExist f) $ do+			unless bootstrapping create+			liftIO $ createDirectoryIfMissing True $ takeDirectory f+			unless bootstrapping $ inRepo genIndex+		a -{- Stages the specified journalfiles. -}-stageJournalFiles :: Annex ()-stageJournalFiles = do+{- Runs an action using the branch's index file, first making sure that+ - the branch and index are up-to-date. -}+withIndexUpdate :: Annex a -> Annex a+withIndexUpdate a = update >> withIndex a++{- Updates the branch's index to reflect the current contents of the branch.+ - Any changes staged in the index will be preserved.+ -+ - Compares the ref stored in the lock file with the current+ - ref of the branch to see if an update is needed.+ -}+updateIndex :: Git.Ref -> Annex ()+updateIndex branchref = do+	lock <- fromRepo gitAnnexIndexLock+	lockref <- Git.Ref . firstLine <$>+		liftIO (catchDefaultIO (readFileStrict lock) "")+	when (lockref /= branchref) $ do+		withIndex $ mergeIndex [fullname]+		setIndexSha branchref++{- Record that the branch's index has been updated to correspond to a+ - given ref of the branch. -}+setIndexSha :: Git.Ref -> Annex ()+setIndexSha ref = do+        lock <- fromRepo gitAnnexIndexLock+	liftIO $ writeFile lock $ show ref ++ "\n"++{- Stages the journal into the index. -}+stageJournal :: Annex ()+stageJournal = do 	fs <- getJournalFiles 	g <- gitRepo 	withIndex $ liftIO $ do 		let dir = gitAnnexJournalDir g 		let paths = map (dir </>) fs-		-- inject all the journal files directly into git-		-- in one quick command-		(pid, fromh, toh) <- hPipeBoth "git" $ toCommand $ git_hash_object g-		_ <- forkProcess $ do-			hPutStr toh $ unlines paths-			hClose toh-			exitSuccess-		hClose toh-		shas <- map Git.Ref . lines <$> hGetContents fromh-		-- update the index, also in just one command+		(shas, cleanup) <- Git.HashObject.hashFiles paths g 		Git.UnionMerge.update_index g $ 			index_lines shas (map fileJournal fs)-		hClose fromh-		forceSuccess pid+		cleanup 		mapM_ removeFile paths 	where 		index_lines shas = map genline . zip shas 		genline (sha, file) = Git.UnionMerge.update_index_line sha file-		git_hash_object = Git.gitCommandLine-			[Param "hash-object", Param "-w", Param "--stdin-paths"]---{- Checks if there are changes in the journal. -}-journalDirty :: Annex Bool-journalDirty = not . null <$> getJournalFiles--{- Produces a filename to use in the journal for a file on the branch.- -- - The journal typically won't have a lot of files in it, so the hashing- - used in the branch is not necessary, and all the files are put directly- - in the journal directory.- -}-journalFile :: Git.Repo -> FilePath -> FilePath-journalFile repo file = gitAnnexJournalDir repo </> concatMap mangle file-	where-		mangle '/' = "_"-		mangle '_' = "__"-		mangle c = [c]--{- Converts a journal file (relative to the journal dir) back to the- - filename on the branch. -}-fileJournal :: FilePath -> FilePath-fileJournal = replace "//" "_" . replace "_" "/"--{- Runs an action that modifies the journal, using locking to avoid- - contention with other git-annex processes. -}-lockJournal :: Annex a -> Annex a-lockJournal a = do-	file <- fromRepo gitAnnexJournalLock-	bracketIO (lock file) unlock a-	where-		lock file = do-			l <- doRedo (createFile file stdFileMode) $-				createDirectoryIfMissing True $ takeDirectory file-			waitToSetLock l (WriteLock, AbsoluteSeek, 0, 0)-			return l-		unlock = closeFd--{- Runs an action, catching failure and running something to fix it up, and- - retrying if necessary. -}-doRedo :: IO a -> IO b -> IO a-doRedo a b = catch a $ const $ b >> a
+ Annex/BranchState.hs view
@@ -0,0 +1,56 @@+{- git-annex branch state management+ -+ - Runtime state about the git-annex branch, including a small read cache.+ -+ - Copyright 2011 Joey Hess <joey@kitenet.net>+ -+ - Licensed under the GNU GPL version 3 or higher.+ -}++module Annex.BranchState where++import Common.Annex+import Types.BranchState+import qualified Annex++getState :: Annex BranchState+getState = Annex.getState Annex.branchstate++setState :: BranchState -> Annex ()+setState state = Annex.changeState $ \s -> s { Annex.branchstate = state }++setCache :: FilePath -> String -> Annex ()+setCache file content = do+	state <- getState+	setState state { cachedFile = Just file, cachedContent = content }++getCache :: FilePath -> Annex (Maybe String)+getCache file = getState >>= go+	where+		go state+			| cachedFile state == Just file =+				return $ Just $ cachedContent state+			| otherwise = return Nothing++invalidateCache :: Annex ()+invalidateCache = do+	state <- getState+	setState state { cachedFile = Nothing, cachedContent = "" }++{- Runs an action to update the branch, if it's not been updated before+ - in this run of git-annex. -}+runUpdateOnce :: Annex () -> Annex ()+runUpdateOnce a = unlessM (branchUpdated <$> getState) $ do+	a+	disableUpdate++{- Avoids updating the branch. A useful optimisation when the branch+ - is known to have not changed, or git-annex won't be relying on info+ - from it. -}+disableUpdate :: Annex ()+disableUpdate = Annex.changeState setupdated+	where+		setupdated s = s { Annex.branchstate = new }+			where+				new = old { branchUpdated = True }+				old = Annex.branchstate s
Annex/Exception.hs view
@@ -11,8 +11,8 @@ 	throw, ) where -import Control.Exception.Control (handle)-import Control.Monad.IO.Control (liftIOOp)+import Control.Exception.Lifted (handle)+import Control.Monad.Trans.Control (liftBaseOp) import Control.Exception hiding (handle, throw)  import Common.Annex@@ -20,7 +20,7 @@ {- Runs an Annex action, with setup and cleanup both in the IO monad. -} bracketIO :: IO c -> (c -> IO b) -> Annex a -> Annex a bracketIO setup cleanup go =-	liftIOOp (Control.Exception.bracket setup cleanup) (const go)+	liftBaseOp (Control.Exception.bracket setup cleanup) (const go)  {- Throws an exception in the Annex monad. -} throw :: Control.Exception.Exception e => e -> Annex a
+ Annex/Journal.hs view
@@ -0,0 +1,94 @@+{- management of the git-annex journal and cache+ -+ - The journal is used to queue up changes before they are committed to the+ - git-annex branch. Amoung other things, it ensures that if git-annex is+ - interrupted, its recorded data is not lost.+ -+ - Copyright 2011 Joey Hess <joey@kitenet.net>+ -+ - Licensed under the GNU GPL version 3 or higher.+ -}++module Annex.Journal where++import System.IO.Binary++import Common.Annex+import Annex.Exception+import qualified Git++{- Records content for a file in the branch to the journal.+ -+ - Using the journal, rather than immediatly staging content to the index+ - avoids git needing to rewrite the index after every change. -}+setJournalFile :: FilePath -> String -> Annex ()+setJournalFile file content = do+	g <- gitRepo+	liftIO $ doRedo (write g) $ do+		createDirectoryIfMissing True $ gitAnnexJournalDir g+		createDirectoryIfMissing True $ gitAnnexTmpDir g+	where+		-- journal file is written atomically+		write g = do+			let jfile = journalFile g file+			let tmpfile = gitAnnexTmpDir g </> takeFileName jfile+			writeBinaryFile tmpfile content+			moveFile tmpfile jfile++{- Gets any journalled content for a file in the branch. -}+getJournalFile :: FilePath -> Annex (Maybe String)+getJournalFile file = inRepo $ \g -> catchMaybeIO $+	readFileStrict $ journalFile g file++{- List of files that have updated content in the journal. -}+getJournalledFiles :: Annex [FilePath]+getJournalledFiles = map fileJournal <$> getJournalFiles++{- List of existing journal files. -}+getJournalFiles :: Annex [FilePath]+getJournalFiles = do+	g <- gitRepo+	fs <- liftIO $+		catchDefaultIO (getDirectoryContents $ gitAnnexJournalDir g) []+	return $ filter (`notElem` [".", ".."]) fs++{- Checks if there are changes in the journal. -}+journalDirty :: Annex Bool+journalDirty = not . null <$> getJournalFiles++{- Produces a filename to use in the journal for a file on the branch.+ -+ - The journal typically won't have a lot of files in it, so the hashing+ - used in the branch is not necessary, and all the files are put directly+ - in the journal directory.+ -}+journalFile :: Git.Repo -> FilePath -> FilePath+journalFile repo file = gitAnnexJournalDir repo </> concatMap mangle file+	where+		mangle '/' = "_"+		mangle '_' = "__"+		mangle c = [c]++{- Converts a journal file (relative to the journal dir) back to the+ - filename on the branch. -}+fileJournal :: FilePath -> FilePath+fileJournal = replace "//" "_" . replace "_" "/"++{- Runs an action that modifies the journal, using locking to avoid+ - contention with other git-annex processes. -}+lockJournal :: Annex a -> Annex a+lockJournal a = do+	file <- fromRepo gitAnnexJournalLock+	bracketIO (lock file) unlock a+	where+		lock file = do+			l <- doRedo (createFile file stdFileMode) $+				createDirectoryIfMissing True $ takeDirectory file+			waitToSetLock l (WriteLock, AbsoluteSeek, 0, 0)+			return l+		unlock = closeFd++{- Runs an action, catching failure and running something to fix it up, and+ - retrying if necessary. -}+doRedo :: IO a -> IO b -> IO a+doRedo a b = catch a $ const $ b >> a
Annex/Ssh.hs view
@@ -7,10 +7,9 @@  module Annex.Ssh where -import Control.Monad.State (liftIO)-+import Common import qualified Git-import Utility.SafeCommand+import qualified Git.Url import Types import Config import Annex.UUID@@ -22,10 +21,10 @@ sshToRepo repo sshcmd = do 	s <- getConfig repo "ssh-options" "" 	let sshoptions = map Param (words s)-	let sshport = case Git.urlPort repo of+	let sshport = case Git.Url.port repo of 		Nothing -> [] 		Just p -> [Param "-p", Param (show p)]-	let sshhost = Param $ Git.urlHostUser repo+	let sshhost = Param $ Git.Url.hostuser repo 	return $ sshoptions ++ sshport ++ [sshhost] ++ sshcmd  {- Generates parameters to run a git-annex-shell command on a remote
Annex/UUID.hs view
@@ -21,6 +21,7 @@  import Common.Annex import qualified Git+import qualified Git.Config import qualified Build.SysConfig as SysConfig import Config @@ -55,14 +56,14 @@ 			return u 		else return c 	where-		cached = toUUID . Git.configGet cachekey ""+		cached = toUUID . Git.Config.get cachekey "" 		updatecache u = do 			g <- gitRepo 			when (g /= r) $ storeUUID cachekey u 		cachekey = remoteConfig r "uuid"  getUncachedUUID :: Git.Repo -> UUID-getUncachedUUID = toUUID . Git.configGet configkey ""+getUncachedUUID = toUUID . Git.Config.get configkey ""  {- Make sure that the repo has an annex.uuid setting. -} prepUUID :: Annex ()
Annex/Version.hs view
@@ -8,7 +8,7 @@ module Annex.Version where  import Common.Annex-import qualified Git+import qualified Git.Config import Config  type Version = String@@ -26,7 +26,7 @@ versionField = "annex.version"  getVersion :: Annex (Maybe Version)-getVersion = handle <$> fromRepo (Git.configGet versionField "")+getVersion = handle <$> fromRepo (Git.Config.get versionField "") 	where 		handle [] = Nothing 		handle v = Just v
Backend.hs view
@@ -20,7 +20,8 @@ import System.Posix.Files  import Common.Annex-import qualified Git+import qualified Git.Config+import qualified Git.CheckAttr import qualified Annex import Types.Key import qualified Types.Backend as B@@ -30,11 +31,11 @@ import qualified Backend.WORM import qualified Backend.URL -list :: [Backend Annex]+list :: [Backend] list = Backend.SHA.backends ++ Backend.WORM.backends ++ Backend.URL.backends  {- List of backends in the order to try them when storing a new key. -}-orderedList :: Annex [Backend Annex]+orderedList :: Annex [Backend] orderedList = do 	l <- Annex.getState Annex.backends -- list is cached here 	if not $ null l@@ -47,18 +48,18 @@ 			l' <- (lookupBackendName name :) <$> standard 			Annex.changeState $ \s -> s { Annex.backends = l' } 			return l'-		standard = fromRepo $ parseBackendList . Git.configGet "annex.backends" ""+		standard = fromRepo $ parseBackendList . Git.Config.get "annex.backends" "" 		parseBackendList [] = list 		parseBackendList s = map lookupBackendName $ words s  {- Generates a key for a file, trying each backend in turn until one  - accepts it. -}-genKey :: FilePath -> Maybe (Backend Annex) -> Annex (Maybe (Key, Backend Annex))+genKey :: FilePath -> Maybe Backend -> Annex (Maybe (Key, Backend)) genKey file trybackend = do 	bs <- orderedList 	let bs' = maybe bs (: bs) trybackend 	genKey' bs' file-genKey' :: [Backend Annex] -> FilePath -> Annex (Maybe (Key, Backend Annex))+genKey' :: [Backend] -> FilePath -> Annex (Maybe (Key, Backend)) genKey' [] _ = return Nothing genKey' (b:bs) file = do 	r <- (B.getKey b) file@@ -74,7 +75,7 @@  {- Looks up the key and backend corresponding to an annexed file,  - by examining what the file symlinks to. -}-lookupFile :: FilePath -> Annex (Maybe (Key, Backend Annex))+lookupFile :: FilePath -> Annex (Maybe (Key, Backend)) lookupFile file = do 	tl <- liftIO $ try getsymlink 	case tl of@@ -93,7 +94,7 @@ 						bname ++ ")" 					return Nothing -type BackendFile = (Maybe (Backend Annex), FilePath)+type BackendFile = (Maybe Backend, FilePath)  {- Looks up the backends that should be used for each file in a list.  - That can be configured on a per-file basis in the gitattributes file.@@ -102,20 +103,18 @@ chooseBackends fs = Annex.getState Annex.forcebackend >>= go 	where 		go Nothing = do-			pairs <- inRepo $ Git.checkAttr "annex.backend" fs+			pairs <- inRepo $ Git.CheckAttr.lookup "annex.backend" fs 			return $ map (\(f,b) -> (maybeLookupBackendName b, f)) pairs 		go (Just _) = do 			l <- orderedList-			return $ map (\f -> (Just $ head l, f)) fs+			return $ map (\f -> (Just $ Prelude.head l, f)) fs  {- Looks up a backend by name. May fail if unknown. -}-lookupBackendName :: String -> Backend Annex+lookupBackendName :: String -> Backend lookupBackendName s = fromMaybe unknown $ maybeLookupBackendName s 	where 		unknown = error $ "unknown backend " ++ s-maybeLookupBackendName :: String -> Maybe (Backend Annex)-maybeLookupBackendName s-	| length matches == 1 = Just $ head matches-	| otherwise = Nothing+maybeLookupBackendName :: String -> Maybe Backend+maybeLookupBackendName s = headMaybe matches 	where 		matches = filter (\b -> s == B.name b) list
Backend/SHA.hs view
@@ -21,21 +21,21 @@ sizes :: [Int] sizes = [256, 1, 512, 224, 384] -backends :: [Backend Annex]+backends :: [Backend] backends = catMaybes $ map genBackend sizes ++ map genBackendE sizes -genBackend :: SHASize -> Maybe (Backend Annex)+genBackend :: SHASize -> Maybe Backend genBackend size 	| isNothing (shaCommand size) = Nothing 	| otherwise = Just b 	where-		b = Types.Backend.Backend+		b = Backend 			{ name = shaName size 			, getKey = keyValue size 			, fsckKey = checkKeyChecksum size 			} -genBackendE :: SHASize -> Maybe (Backend Annex)+genBackendE :: SHASize -> Maybe Backend genBackendE size = 	case genBackend size of 		Nothing -> Nothing@@ -62,11 +62,10 @@ shaN size file = do 	showAction "checksum" 	liftIO $ pOpen ReadFromPipe command (toCommand [File file]) $ \h -> do-		line <- hGetLine h-		let bits = split " " line-		if null bits+		sha <- fst . separate (== ' ') <$> hGetLine h+		if null sha 			then error $ command ++ " parse error"-			else return $ head bits+			else return sha 	where 		command = fromJust $ shaCommand size 
Backend/URL.hs view
@@ -14,11 +14,11 @@ import Types.Backend import Types.Key -backends :: [Backend Annex]+backends :: [Backend] backends = [backend] -backend :: Backend Annex-backend = Types.Backend.Backend {+backend :: Backend+backend = Backend { 	name = "URL", 	getKey = const (return Nothing), 	fsckKey = const (return True)
Backend/WORM.hs view
@@ -11,11 +11,11 @@ import Types.Backend import Types.Key -backends :: [Backend Annex]+backends :: [Backend] backends = [backend] -backend :: Backend Annex-backend = Types.Backend.Backend {+backend :: Backend+backend = Backend { 	name = "WORM", 	getKey = keyValue, 	fsckKey = const (return True)
CHANGELOG view
@@ -1,3 +1,30 @@+git-annex (3.20111231) unstable; urgency=low++  * sync: Improved to work well without a central bare repository.+    Thanks to Joachim Breitner.+  * Rather than manually committing, pushing, pulling, merging, and git annex+    merging, we encourage you to give "git annex sync" a try.+  * sync --fast: Selects some of the remotes with the lowest annex.cost+    and syncs those, in addition to any specified at the command line.+  * Union merge now finds the least expensive way to represent the merge.+  * reinject: Add a sanity check for using an annexed file as the source file.+  * Properly handle multiline git config values.+  * Fix the hook special remote, which bitrotted a while ago.+  * map: --fast disables use of dot to display map+  * Test suite improvements. Current top-level test coverage: 75%+  * Improve deletion of files from rsync special remotes. Closes: #652849+  * Add --include, which is the same as --not --exclude.+  * Format strings can be specified using the new --format option, to control+    what is output by git annex find.+  * Support git annex find --json+  * Fixed behavior when multiple insteadOf configs are provided for the+    same url base.+  * Can now be built with older git versions (before 1.7.7); the resulting+    binary should only be used with old git.+  * Updated to build with monad-control 0.3.++ -- Joey Hess <joeyh@debian.org>  Sat, 31 Dec 2011 14:55:29 -0400+ git-annex (3.20111211) unstable; urgency=medium    * Fix bug in last version in getting contents from bare repositories.
CmdLine.hs view
@@ -20,6 +20,7 @@ import qualified Annex import qualified Annex.Queue import qualified Git+import qualified Git.Command import Annex.Content import Command @@ -50,7 +51,7 @@ 		check (_, [], []) = err "missing command" 		check (flags, name:rest, []) 			| null matches = err $ "unknown command " ++ name-			| otherwise = (flags, head matches, rest)+			| otherwise = (flags, Prelude.head matches, rest) 			where 				matches = filter (\c -> name == cmdname c) cmds 		check (_, _, errs) = err $ concat errs@@ -101,5 +102,5 @@ shutdown :: Annex Bool shutdown = do 	saveState-	liftIO Git.reap -- zombies from long-running git processes+	liftIO Git.Command.reap -- zombies from long-running git processes 	return True
Command.hs view
@@ -77,10 +77,10 @@  {- Modifies an action to only act on files that are already annexed,  - and passes the key and backend on to it. -}-whenAnnexed :: (FilePath -> (Key, Backend Annex) -> Annex (Maybe a)) -> FilePath -> Annex (Maybe a)+whenAnnexed :: (FilePath -> (Key, Backend) -> Annex (Maybe a)) -> FilePath -> Annex (Maybe a) whenAnnexed a file = ifAnnexed file (a file) (return Nothing) -ifAnnexed :: FilePath -> ((Key, Backend Annex) -> Annex a) -> Annex a -> Annex a+ifAnnexed :: FilePath -> ((Key, Backend) -> Annex a) -> Annex a -> Annex a ifAnnexed file yes no = maybe no yes =<< Backend.lookupFile file  notBareRepo :: Annex a -> Annex a
Command/AddUrl.hs view
@@ -65,7 +65,6 @@ url2file url = do 	whenM (doesFileExist file) $ 		error $ "already have this url in " ++ file-	liftIO $ print file 	return file 	where 		file = escape $ uriRegName auth ++ uriPath url ++ uriQuery url
Command/Copy.hs view
@@ -21,6 +21,6 @@  -- A copy is just a move that does not delete the source file. -- However, --auto mode avoids unnecessary copies.-start :: Maybe Int -> FilePath -> (Key, Backend Annex) -> CommandStart+start :: Maybe Int -> FilePath -> (Key, Backend) -> CommandStart start numcopies file (key, backend) = autoCopies key (<) numcopies $ 	Command.Move.start False file (key, backend)
Command/Drop.hs view
@@ -24,7 +24,7 @@ seek :: [CommandSeek] seek = [withNumCopies $ \n -> whenAnnexed $ start n] -start :: Maybe Int -> FilePath -> (Key, Backend Annex) -> CommandStart+start :: Maybe Int -> FilePath -> (Key, Backend) -> CommandStart start numcopies file (key, _) = autoCopies key (>) numcopies $ do 	from <- Annex.getState Annex.fromremote 	case from of@@ -41,7 +41,7 @@ 	showStart "drop" file 	next $ performLocal key numcopies -startRemote :: FilePath -> Maybe Int -> Key -> Remote.Remote Annex -> CommandStart+startRemote :: FilePath -> Maybe Int -> Key -> Remote -> CommandStart startRemote file numcopies key remote = do 	showStart "drop" file 	next $ performRemote key numcopies remote@@ -55,7 +55,7 @@ 		whenM (inAnnex key) $ removeAnnex key 		next $ cleanupLocal key -performRemote :: Key -> Maybe Int -> Remote.Remote Annex -> CommandPerform+performRemote :: Key -> Maybe Int -> Remote -> CommandPerform performRemote key numcopies remote = lockContent key $ do 	-- Filter the remote it's being dropped from out of the lists of 	-- places assumed to have the key, and places to check.@@ -79,7 +79,7 @@ 	logStatus key InfoMissing 	return True -cleanupRemote :: Key -> Remote.Remote Annex -> Bool -> CommandCleanup+cleanupRemote :: Key -> Remote -> Bool -> CommandCleanup cleanupRemote key remote ok = do 	-- better safe than sorry: assume the remote dropped the key 	-- even if it seemed to fail; the failure could have occurred@@ -90,7 +90,7 @@ {- Checks specified remotes to verify that enough copies of a key exist to  - allow it to be safely removed (with no data loss). Can be provided with  - some locations where the key is known/assumed to be present. -}-canDropKey :: Key -> Maybe Int -> [UUID] -> [Remote.Remote Annex] -> [UUID] -> Annex Bool+canDropKey :: Key -> Maybe Int -> [UUID] -> [Remote] -> [UUID] -> Annex Bool canDropKey key numcopiesM have check skip = do 	force <- Annex.getState Annex.force 	if force || numcopiesM == Just 0@@ -99,7 +99,7 @@ 			need <- getNumCopies numcopiesM 			findCopies key need skip have check -findCopies :: Key -> Int -> [UUID] -> [UUID] -> [Remote.Remote Annex] -> Annex Bool+findCopies :: Key -> Int -> [UUID] -> [UUID] -> [Remote] -> Annex Bool findCopies key need skip = helper [] 	where 		helper bad have []@@ -116,7 +116,7 @@ 					(False, Left _)     -> helper (r:bad) have rs 					_                   -> helper bad have rs -notEnoughCopies :: Key -> Int -> [UUID] -> [UUID] -> [Remote.Remote Annex] -> Annex Bool+notEnoughCopies :: Key -> Int -> [UUID] -> [UUID] -> [Remote] -> Annex Bool notEnoughCopies key need have skip bad = do 	unsafe 	showLongNote $
Command/Find.hs view
@@ -7,11 +7,16 @@  module Command.Find where +import qualified Data.Map as M+ import Common.Annex import Command import Annex.Content import Limit import qualified Annex+import qualified Utility.Format+import Utility.DataUnits+import Types.Key  def :: [Command] def = [command "find" paramPaths seek "lists available files"]@@ -19,13 +24,25 @@ seek :: [CommandSeek] seek = [withFilesInGit $ whenAnnexed start] -start :: FilePath -> (Key, Backend Annex) -> CommandStart+start :: FilePath -> (Key, Backend) -> CommandStart start file (key, _) = do 	-- only files inAnnex are shown, unless the user has requested 	-- others via a limit-	whenM (liftM2 (||) (inAnnex key) limited) $ do-		print0 <- Annex.getState Annex.print0-		if print0-			then liftIO $ putStr (file ++ "\0")-			else liftIO $ putStrLn file+	whenM (liftM2 (||) limited (inAnnex key)) $+		unlessM (showFullJSON vars) $ do+			f <- Annex.getState Annex.format+			case f of+				Nothing -> liftIO $ putStrLn file+				Just formatter -> liftIO $ putStr $+					Utility.Format.format formatter $+						M.fromList vars 	stop+	where+		vars =+			[ ("file", file)+			, ("key", show key)+			, ("backend", keyBackendName key)+			, ("bytesize", size show)+			, ("humansize", size $ roughSize storageUnits True)+			]+		size c = maybe "unknown" c $ keySize key
Command/Fix.hs view
@@ -20,7 +20,7 @@ seek = [withFilesInGit $ whenAnnexed start]  {- Fixes the symlink to an annexed file. -}-start :: FilePath -> (Key, Backend Annex) -> CommandStart+start :: FilePath -> (Key, Backend) -> CommandStart start file (key, _) = do 	link <- calcGitLink file key 	stopUnless ((/=) link <$> liftIO (readSymbolicLink file)) $ do
Command/Fsck.hs view
@@ -30,12 +30,12 @@ 	, withBarePresentKeys startBare 	] -start :: Maybe Int -> FilePath -> (Key, Backend Annex) -> CommandStart+start :: Maybe Int -> FilePath -> (Key, Backend) -> CommandStart start numcopies file (key, backend) = do 	showStart "fsck" file 	next $ perform key file backend numcopies -perform :: Key -> FilePath -> Backend Annex -> Maybe Int -> CommandPerform+perform :: Key -> FilePath -> Backend -> Maybe Int -> CommandPerform perform key file backend numcopies = check 	-- order matters 	[ verifyLocationLog key file@@ -64,7 +64,7 @@ {- Note that numcopies cannot be checked in a bare repository, because  - getting the numcopies value requires a working copy with .gitattributes  - files. -}-performBare :: Key -> Backend Annex -> CommandPerform+performBare :: Key -> Backend -> CommandPerform performBare key backend = check 	[ verifyLocationLog key (show key) 	, checkKeySize key@@ -136,7 +136,7 @@ 					return False  -checkBackend :: Backend Annex -> Key -> Annex Bool+checkBackend :: Backend -> Key -> Annex Bool checkBackend = Types.Backend.fsckKey  checkKeyNumCopies :: Key -> FilePath -> Maybe Int -> Annex Bool
Command/Get.hs view
@@ -21,7 +21,7 @@ seek :: [CommandSeek] seek = [withNumCopies $ \n -> whenAnnexed $ start n] -start :: Maybe Int -> FilePath -> (Key, Backend Annex) -> CommandStart+start :: Maybe Int -> FilePath -> (Key, Backend) -> CommandStart start numcopies file (key, _) = stopUnless (not <$> inAnnex key) $ 	autoCopies key (<) numcopies $ do 		from <- Annex.getState Annex.fromremote
Command/InitRemote.hs view
@@ -25,9 +25,13 @@ seek = [withWords start]  start :: [String] -> CommandStart-start ws = do-	when (null ws) needname-+start [] = do+	names <- remoteNames+	error $ "Specify a name for the remote. " +++		if null names+			then ""+			else "Either a new name, or one of these existing special remotes: " ++ join " " names+start (name:ws) = do 	(u, c) <- findByName name 	let fullconfig = config `M.union` c	 	t <- findType fullconfig@@ -36,17 +40,9 @@ 	next $ perform t u $ M.union config c  	where-		name = head ws-		config = Logs.Remote.keyValToConfig $ tail ws-		needname = do-			let err s = error $ "Specify a name for the remote. " ++ s-			names <- remoteNames-			if null names-				then err ""-				else err $ "Either a new name, or one of these existing special remotes: " ++ join " " names-			+		config = Logs.Remote.keyValToConfig ws -perform :: R.RemoteType Annex -> UUID -> R.RemoteConfig -> CommandPerform+perform :: RemoteType -> UUID -> R.RemoteConfig -> CommandPerform perform t u c = do 	c' <- R.setup t u c 	next $ cleanup u c'@@ -67,11 +63,8 @@ 			return (uuid, M.insert nameKey name M.empty)  findByName' :: String ->  M.Map UUID R.RemoteConfig -> Maybe (UUID, R.RemoteConfig)-findByName' n m-	| null matches = Nothing-	| otherwise = Just $ head matches+findByName' n = headMaybe . filter (matching . snd) . M.toList 	where-		matches = filter (matching . snd) $ M.toList m 		matching c = case M.lookup nameKey c of 			Nothing -> False 			Just n'@@ -84,7 +77,7 @@ 	return $ mapMaybe (M.lookup nameKey . snd) $ M.toList m  {- find the specified remote type -}-findType :: R.RemoteConfig -> Annex (R.RemoteType Annex)+findType :: R.RemoteConfig -> Annex RemoteType findType config = maybe unspecified specified $ M.lookup typeKey config 	where 		unspecified = error "Specify the type of remote with type="
Command/Map.hs view
@@ -13,6 +13,10 @@ import Common.Annex import Command import qualified Git+import qualified Git.Url+import qualified Git.Config+import qualified Git.Construct+import qualified Annex import Annex.UUID import Logs.UUID import Logs.Trust@@ -37,10 +41,14 @@ 	trusted <- trustGet Trusted  	liftIO $ writeFile file (drawMap rs umap trusted)-	showLongNote $ "running: dot -Tx11 " ++ file-	showOutput-	r <- liftIO $ boolSystem "dot" [Param "-Tx11", File file]-	next $ next $ return r+	next $ next $ do+		fast <- Annex.getState Annex.fast+		if fast+			then return True+			else do+				showLongNote $ "running: dot -Tx11 " ++ file+				showOutput+				liftIO $ boolSystem "dot" [Param "-Tx11", File file] 	where 		file = "map.dot" @@ -66,11 +74,11 @@  hostname :: Git.Repo -> String hostname r-	| Git.repoIsUrl r = Git.urlHost r+	| Git.repoIsUrl r = Git.Url.host r 	| otherwise = "localhost"  basehostname :: Git.Repo -> String-basehostname r = head $ split "." $ hostname r+basehostname r = Prelude.head $ split "." $ hostname r  {- A name to display for a repo. Uses the name from uuid.log if available,  - or the remote name if not. -}@@ -80,7 +88,7 @@ 	| otherwise = M.findWithDefault fallback repouuid umap 	where 		repouuid = getUncachedUUID r-		fallback = fromMaybe "unknown" $ Git.repoRemoteName r+		fallback = fromMaybe "unknown" $ Git.remoteName r  {- A unique id for the node for a repo. Uses the annex.uuid if available. -} nodeId :: Git.Repo -> String@@ -97,7 +105,7 @@ 			decorate $ Dot.graphNode (nodeId r) (repoName umap r) 		edges = map (edge umap fullinfo r) (Git.remotes r) 		decorate-			| Git.configMap r == M.empty = unreachable+			| Git.config r == M.empty = unreachable 			| otherwise = reachable  {- An edge between two repos. The second repo is a remote of the first. -}@@ -114,7 +122,7 @@ 		{- Only name an edge if the name is different than the name 		 - that will be used for the destination node, and is 		 - different from its hostname. (This reduces visual clutter.) -}-		edgename = maybe Nothing calcname $ Git.repoRemoteName to+		edgename = maybe Nothing calcname $ Git.remoteName to 		calcname n 			| n `elem` [repoName umap fullto, hostname fullto] = Nothing 			| otherwise = Just n@@ -139,20 +147,20 @@ 		-- The remotes will be relative to r', and need to be 		-- made absolute for later use. 		remotes <- mapM (absRepo r') (Git.remotes r')-		let r'' = Git.remotesAdd r' remotes+		let r'' = r' { Git.remotes = remotes }  		spider' (rs ++ remotes) (r'':known)  {- Converts repos to a common absolute form. -} absRepo :: Git.Repo -> Git.Repo -> Annex Git.Repo absRepo reference r-	| Git.repoIsUrl reference = return $ Git.localToUrl reference r-	| otherwise = liftIO $ Git.repoFromAbsPath =<< absPath (Git.workTree r)+	| Git.repoIsUrl reference = return $ Git.Construct.localToUrl reference r+	| otherwise = liftIO $ Git.Construct.fromAbsPath =<< absPath (Git.workTree r)  {- Checks if two repos are the same. -} same :: Git.Repo -> Git.Repo -> Bool same a b-	| both Git.repoIsSsh = matching Git.urlAuthority && matching Git.workTree+	| both Git.repoIsSsh = matching Git.Url.authority && matching Git.workTree 	| both Git.repoIsUrl && neither Git.repoIsSsh = matching show 	| neither Git.repoIsSsh = matching Git.workTree 	| otherwise = False@@ -182,7 +190,7 @@ tryScan r 	| Git.repoIsSsh r = sshscan 	| Git.repoIsUrl r = return Nothing-	| otherwise = safely $ Git.configRead r+	| otherwise = safely $ Git.Config.read r 	where 		safely a = do 			result <- liftIO (try a :: IO (Either SomeException Git.Repo))@@ -191,7 +199,7 @@ 				Right r' -> return $ Just r' 		pipedconfig cmd params = safely $ 			pOpen ReadFromPipe cmd (toCommand params) $-				Git.hConfigRead r+				Git.Config.hRead r  		configlist = 			onRemote r (pipedconfig, Nothing) "configlist" []@@ -200,7 +208,7 @@ 			liftIO $ pipedconfig "ssh" sshparams 			where 				sshcmd = cddir ++ " && " ++-					"git config --list"+					"git config --null --list" 				dir = Git.workTree r 				cddir 					| "/~" `isPrefixOf` dir =
Command/Migrate.hs view
@@ -21,7 +21,7 @@ seek :: [CommandSeek] seek = [withBackendFilesInGit $ \(b, f) -> whenAnnexed (start b) f] -start :: Maybe (Backend Annex) -> FilePath -> (Key, Backend Annex) -> CommandStart+start :: Maybe Backend -> FilePath -> (Key, Backend) -> CommandStart start b file (key, oldbackend) = do 	exists <- inAnnex key 	newbackend <- choosebackend b@@ -31,7 +31,7 @@ 			next $ perform file key newbackend 		else stop 	where-		choosebackend Nothing = head <$> Backend.orderedList+		choosebackend Nothing = Prelude.head <$> Backend.orderedList 		choosebackend (Just backend) = return backend  {- Checks if a key is upgradable to a newer representation. -}@@ -47,7 +47,7 @@  - backends that allow the filename to influence the keys they  - generate.  -}-perform :: FilePath -> Key -> Backend Annex -> CommandPerform+perform :: FilePath -> Key -> Backend -> CommandPerform perform file oldkey newbackend = do 	src <- inRepo $ gitAnnexLocation oldkey 	tmp <- fromRepo gitAnnexTmpDir
Command/Move.hs view
@@ -23,7 +23,7 @@ seek :: [CommandSeek] seek = [withFilesInGit $ whenAnnexed $ start True] -start :: Bool -> FilePath -> (Key, Backend Annex) -> CommandStart+start :: Bool -> FilePath -> (Key, Backend) -> CommandStart start move file (key, _) = do 	noAuto 	to <- Annex.getState Annex.toremote@@ -54,7 +54,7 @@  - A file's content can be moved even if there are insufficient copies to  - allow it to be dropped.  -}-toStart :: Remote.Remote Annex -> Bool -> FilePath -> Key -> CommandStart+toStart :: Remote -> Bool -> FilePath -> Key -> CommandStart toStart dest move file key = do 	u <- getUUID 	ishere <- inAnnex key@@ -63,7 +63,7 @@ 		else do 			showMoveAction move file 			next $ toPerform dest move key-toPerform :: Remote.Remote Annex -> Bool -> Key -> CommandPerform+toPerform :: Remote -> Bool -> Key -> CommandPerform toPerform dest move key = moveLock move key $ do 	-- Checking the remote is expensive, so not done in the start step. 	-- In fast mode, location tracking is assumed to be correct,@@ -105,7 +105,7 @@  - If the current repository already has the content, it is still removed  - from the remote.  -}-fromStart :: Remote.Remote Annex -> Bool -> FilePath -> Key -> CommandStart+fromStart :: Remote -> Bool -> FilePath -> Key -> CommandStart fromStart src move file key 	| move = go 	| otherwise = stopUnless (not <$> inAnnex key) go@@ -113,12 +113,12 @@ 		go = stopUnless (fromOk src key) $ do 			showMoveAction move file 			next $ fromPerform src move key-fromOk :: Remote.Remote Annex -> Key -> Annex Bool+fromOk :: Remote -> Key -> Annex Bool fromOk src key = do 	u <- getUUID 	remotes <- Remote.keyPossibilities key 	return $ u /= Remote.uuid src && any (== src) remotes-fromPerform :: Remote.Remote Annex -> Bool -> Key -> CommandPerform+fromPerform :: Remote -> Bool -> Key -> CommandPerform fromPerform src move key = moveLock move key $ do 	ishere <- inAnnex key 	if ishere
Command/Reinject.hs view
@@ -24,11 +24,16 @@ start (src:dest:[]) 	| src == dest = stop 	| otherwise = do-		showStart "reinject" dest-		next $ whenAnnexed (perform src) dest+		ifAnnexed src+			(error $ "cannot used annexed file as src: " ++ src)+			go+	where+		go = do+			showStart "reinject" dest+			next $ whenAnnexed (perform src) dest start _ = error "specify a src file and a dest file" -perform :: FilePath -> FilePath -> (Key, Backend Annex) -> CommandPerform+perform :: FilePath -> FilePath -> (Key, Backend) -> CommandPerform perform src _dest (key, backend) = do 	unlessM move $ error "mv failed!" 	next $ cleanup key backend@@ -40,7 +45,7 @@ 		move = getViaTmp key $ \tmp -> 			liftIO $ boolSystem "mv" [File src, File tmp] -cleanup :: Key -> Backend Annex -> CommandCleanup+cleanup :: Key -> Backend -> CommandCleanup cleanup key backend = do 	logStatus key InfoPresent 
Command/Status.hs view
@@ -116,7 +116,7 @@ 	us <- M.keys <$> (M.union <$> uuidMap <*> remoteMap) 	rs <- fst <$> trustPartition level us 	s <- prettyPrintUUIDs n rs-	return $ if null s then "0" else show (length rs) ++ "\n" ++ init s+	return $ if null s then "0" else show (length rs) ++ "\n" ++ beginning s 	where 		n = desc ++ " repositories" 
Command/Sync.hs view
@@ -1,70 +1,172 @@ {- git-annex command  -  - Copyright 2011 Joey Hess <joey@kitenet.net>+ - Copyright 2011 Joachim Breitner <mail@joachim-breitner.de>  -  - Licensed under the GNU GPL version 3 or higher.  -} +{-# LANGUAGE BangPatterns #-}+ module Command.Sync where  import Common.Annex import Command+import qualified Remote+import qualified Annex import qualified Annex.Branch+import qualified Git.Command+import qualified Git.Branch+import qualified Git.Ref import qualified Git+import qualified Types.Remote+import qualified Remote.Git -import qualified Data.ByteString.Lazy.Char8 as L+import qualified Data.Map as M  def :: [Command]-def = [command "sync" paramPaths seek "synchronize local repository with remote"]+def = [command "sync" (paramOptional (paramRepeating paramRemote))+	[seek] "synchronize local repository with remotes"] --- syncing involves several operations, any of which can independantly fail-seek :: [CommandSeek]-seek = map withNothing [commit, pull, push]+-- syncing involves several operations, any of which can independently fail+seek :: CommandSeek+seek rs = do+	!branch <- fromMaybe nobranch <$> inRepo Git.Branch.current+	remotes <- syncRemotes rs+	return $ concat $+		[ [ commit ]+		, [ mergeLocal branch ]+		, [ pullRemote remote branch | remote <- remotes ]+		, [ mergeAnnex ]+		, [ pushLocal branch ]+		, [ pushRemote remote branch | remote <- remotes ]+		]+	where+		nobranch = error "no branch is checked out" +syncBranch :: Git.Ref -> Git.Ref+syncBranch = Git.Ref.under "refs/heads/synced/"++remoteBranch :: Remote -> Git.Ref -> Git.Ref+remoteBranch remote = Git.Ref.under $ "refs/remotes/" ++ Remote.name remote++syncRemotes :: [String] -> Annex [Remote]+syncRemotes rs = do+	fast <- Annex.getState Annex.fast+	if fast+		then nub <$> pickfast+		else wanted+	where+		pickfast = (++) <$> listed <*> (good =<< fastest <$> available)+		wanted+			| null rs = good =<< available+			| otherwise = listed+		listed = mapM Remote.byName rs+		available = filter nonspecial <$> Remote.enabledRemoteList+		good = filterM $ Remote.Git.repoAvail . Types.Remote.repo+		nonspecial r = Types.Remote.remotetype r == Remote.Git.remote+		fastest = fromMaybe [] . headMaybe .+			map snd . sort . M.toList . costmap+		costmap = M.fromListWith (++) . map costpair+		costpair r = (Types.Remote.cost r, [r])+ commit :: CommandStart commit = do 	showStart "commit" "" 	next $ next $ do 		showOutput 		-- Commit will fail when the tree is clean, so ignore failure.-		_ <- inRepo $ Git.runBool "commit" [Param "-a", Param "-m", Param "sync"]+		_ <- inRepo $ Git.Command.runBool "commit"+			[Param "-a", Param "-m", Param "git-annex automatic sync"] 		return True -pull :: CommandStart-pull = do-	remote <- defaultRemote-	showStart "pull" remote-	next $ next $ do-		showOutput-		checkRemote remote-		inRepo $ Git.runBool "pull" [Param remote]+mergeLocal :: Git.Ref -> CommandStart+mergeLocal branch = go =<< needmerge+	where+		syncbranch = syncBranch branch+		needmerge = do+			unlessM (inRepo $ Git.Ref.exists syncbranch) $+				updateBranch syncbranch+			inRepo $ Git.Branch.changed branch syncbranch+		go False = stop+		go True = do+			showStart "merge" $ Git.Ref.describe syncbranch+			next $ next $ mergeFrom syncbranch -push :: CommandStart-push = do-	remote <- defaultRemote-	showStart "push" remote-	next $ next $ do-		Annex.Branch.update+pushLocal :: Git.Ref -> CommandStart+pushLocal branch = do+	updateBranch $ syncBranch branch+	stop++updateBranch :: Git.Ref -> Annex ()+updateBranch syncbranch = +	unlessM go $ error $ "failed to update " ++ show syncbranch+	where+		go = inRepo $ Git.Command.runBool "branch"+			[ Param "-f"+			, Param $ show $ Git.Ref.base syncbranch+			]++pullRemote :: Remote -> Git.Ref -> CommandStart+pullRemote remote branch = do+	showStart "pull" (Remote.name remote)+	next $ do 		showOutput-		inRepo $ Git.runBool "push" [Param remote, matchingbranches]+		fetched <- inRepo $ Git.Command.runBool "fetch"+			[Param $ Remote.name remote]+		if fetched+			then next $ mergeRemote remote branch+			else stop++{- The remote probably has both a master and a synced/master branch.+ - Which to merge from? Well, the master has whatever latest changes+ - were committed, while the synced/master may have changes that some+ - other remote synced to this remote. So, merge them both. -}+mergeRemote :: Remote -> Git.Ref -> CommandCleanup+mergeRemote remote branch = all id <$> (mapM merge =<< tomerge) 	where-		-- git push may be configured to not push matching-		-- branches; this should ensure it always does.-		matchingbranches = Param ":"+		merge = mergeFrom . remoteBranch remote+		tomerge = filterM (changed remote) [branch, syncBranch branch] --- the remote defaults to origin when not configured-defaultRemote :: Annex String-defaultRemote = do-	branch <- currentBranch-	fromRepo $ Git.configGet ("branch." ++ branch ++ ".remote") "origin"+pushRemote :: Remote -> Git.Ref -> CommandStart+pushRemote remote branch = go =<< needpush+	where+		needpush = anyM (newer remote) [syncbranch, Annex.Branch.name]+		go False = stop+		go True = do+			showStart "push" (Remote.name remote)+			next $ next $ do+				showOutput+				inRepo $ Git.Command.runBool "push" $+					[ Param (Remote.name remote)+					, Param (show $ Annex.Branch.name)+					, Param refspec+					]+		refspec = show (Git.Ref.base branch) ++ ":" ++ show (Git.Ref.base syncbranch)+		syncbranch = syncBranch branch -currentBranch :: Annex String-currentBranch = last . split "/" . L.unpack . head . L.lines <$>-	inRepo (Git.pipeRead [Param "symbolic-ref", Param "HEAD"])+mergeAnnex :: CommandStart+mergeAnnex = do+	Annex.Branch.forceUpdate+	stop -checkRemote :: String -> Annex ()-checkRemote remote = do-	remoteurl <- fromRepo $-		Git.configGet ("remote." ++ remote ++ ".url") ""-	when (null remoteurl) $ do-		error $ "No url is configured for the remote: " ++ remote+mergeFrom :: Git.Ref -> CommandCleanup+mergeFrom branch = do+	showOutput+	inRepo $ Git.Command.runBool "merge" [Param $ show branch]++changed :: Remote -> Git.Ref -> Annex Bool+changed remote b = do+	let r = remoteBranch remote b+	e <- inRepo $ Git.Ref.exists r+	if e+		then inRepo $ Git.Branch.changed b r+		else return False++newer :: Remote -> Git.Ref -> Annex Bool+newer remote b = do+	let r = remoteBranch remote b+	e <- inRepo $ Git.Ref.exists r+	if e+		then inRepo $ Git.Branch.changed r b+		else return True
Command/Unannex.hs view
@@ -13,7 +13,7 @@ import Utility.FileMode import Logs.Location import Annex.Content-import qualified Git+import qualified Git.Command import qualified Git.LsFiles as LsFiles  def :: [Command]@@ -22,7 +22,7 @@ seek :: [CommandSeek] seek = [withFilesInGit $ whenAnnexed start] -start :: FilePath -> (Key, Backend Annex) -> CommandStart+start :: FilePath -> (Key, Backend) -> CommandStart start file (key, _) = stopUnless (inAnnex key) $ do 	showStart "unannex" file 	next $ perform file key@@ -34,15 +34,17 @@ cleanup file key = do 	liftIO $ removeFile file 	-- git rm deletes empty directory without --cached-	inRepo $ Git.run "rm" [Params "--cached --quiet --", File file]+	inRepo $ Git.Command.run "rm" [Params "--cached --quiet --", File file] 	 	-- If the file was already committed, it is now staged for removal. 	-- Commit that removal now, to avoid later confusing the 	-- pre-commit hook if this file is later added back to 	-- git as a normal, non-annexed file. 	whenM (not . null <$> inRepo (LsFiles.staged [file])) $ do-		inRepo $ Git.run "commit" [-			Param "-m", Param "content removed from git annex",+		showOutput+		inRepo $ Git.Command.run "commit" [+			Param "-q",+			Params "-m", Param "content removed from git annex", 			Param "--", File file]  	fast <- Annex.getState Annex.fast
Command/Uninit.hs view
@@ -12,6 +12,7 @@ import Common.Annex import Command import qualified Git+import qualified Git.Command import qualified Annex import qualified Command.Unannex import Init@@ -28,14 +29,14 @@ 	when (b == Annex.Branch.name) $ error $ 		"cannot uninit when the " ++ show b ++ " branch is checked out" 	where-		current_branch = Git.Ref . head . lines . B.unpack <$> revhead-		revhead = inRepo $ Git.pipeRead +		current_branch = Git.Ref . Prelude.head . lines . B.unpack <$> revhead+		revhead = inRepo $ Git.Command.pipeRead  			[Params "rev-parse --abbrev-ref HEAD"]  seek :: [CommandSeek] seek = [withFilesInGit $ whenAnnexed startUnannex, withNothing start] -startUnannex :: FilePath -> (Key, Backend Annex) -> CommandStart+startUnannex :: FilePath -> (Key, Backend) -> CommandStart startUnannex file info = do 	-- Force fast mode before running unannex. This way, if multiple 	-- files link to a key, it will be left in the annex and hardlinked@@ -57,5 +58,6 @@ 	liftIO $ removeDirectoryRecursive annexdir 	-- avoid normal shutdown 	saveState-	inRepo $ Git.run "branch" [Param "-D", Param $ show Annex.Branch.name]+	inRepo $ Git.Command.run "branch"+		[Param "-D", Param $ show Annex.Branch.name] 	liftIO exitSuccess
Command/Unlock.hs view
@@ -26,7 +26,7 @@  {- The unlock subcommand replaces the symlink with a copy of the file's  - content. -}-start :: FilePath -> (Key, Backend Annex) -> CommandStart+start :: FilePath -> (Key, Backend) -> CommandStart start file (key, _) = do 	showStart "unlock" file 	next $ perform file key
Command/Unused.hs view
@@ -20,6 +20,8 @@ import Logs.Location import qualified Annex import qualified Git+import qualified Git.Command+import qualified Git.Ref import qualified Git.LsFiles as LsFiles import qualified Git.LsTree as LsTree import qualified Backend@@ -64,7 +66,7 @@ 	checkRemoteUnused' =<< Remote.byName name 	next $ return True -checkRemoteUnused' :: Remote.Remote Annex -> Annex ()+checkRemoteUnused' :: Remote -> Annex () checkRemoteUnused' r = do 	showAction "checking for unused data" 	remotehas <- loggedKeysFor (Remote.uuid r)@@ -110,14 +112,14 @@ 	["(To see where data was previously used, try: git log --stat -S'KEY')"] ++ 	trailer -remoteUnusedMsg :: Remote.Remote Annex -> [(Int, Key)] -> String+remoteUnusedMsg :: Remote -> [(Int, Key)] -> String remoteUnusedMsg r u = unusedMsg' u 	["Some annexed data on " ++ name ++ " is not used by any files:"] 	[dropMsg $ Just r] 	where 		name = Remote.name r  -dropMsg :: Maybe (Remote.Remote Annex) -> String+dropMsg :: Maybe Remote -> String dropMsg Nothing = dropMsg' "" dropMsg (Just r) = dropMsg' $ " --from " ++ Remote.name r dropMsg' :: String -> String@@ -147,18 +149,18 @@ excludeReferenced :: [Key] -> Annex [Key] excludeReferenced [] = return [] -- optimisation excludeReferenced l = do-	c <- inRepo $ Git.pipeRead [Param "show-ref"]+	c <- inRepo $ Git.Command.pipeRead [Param "show-ref"] 	removewith (getKeysReferenced : map getKeysReferencedInGit (refs c)) 		(S.fromList l) 	where 		-- Skip the git-annex branches, and get all other unique refs.-		refs = map (Git.Ref .  last) .-			nubBy cmpheads .+		refs = map (Git.Ref .  snd) .+			nubBy uniqref . 			filter ourbranches .-			map words . lines . L.unpack-		cmpheads a b = head a == head b+			map (separate (== ' ')) . lines . L.unpack+		uniqref (a, _) (b, _) = a == b 		ourbranchend = '/' : show Annex.Branch.name-		ourbranches ws = not $ ourbranchend `isSuffixOf` last ws+		ourbranches (_, b) = not $ ourbranchend `isSuffixOf` b 		removewith [] s = return $ S.toList s 		removewith (a:as) s 			| s == S.empty = return [] -- optimisation@@ -190,7 +192,7 @@ {- List of keys referenced by symlinks in a git ref. -} getKeysReferencedInGit :: Git.Ref -> Annex [Key] getKeysReferencedInGit ref = do-	showAction $ "checking " ++ Git.refDescribe ref+	showAction $ "checking " ++ Git.Ref.describe ref 	findkeys [] =<< inRepo (LsTree.lsTree ref) 	where 		findkeys c [] = return c
Command/Whereis.hs view
@@ -20,7 +20,7 @@ seek :: [CommandSeek] seek = [withFilesInGit $ whenAnnexed start] -start :: FilePath -> (Key, Backend Annex) -> CommandStart+start :: FilePath -> (Key, Backend) -> CommandStart start file (key, _) = do 	showStart "whereis" file 	next $ perform key
Common.hs view
@@ -6,7 +6,7 @@ import Control.Exception.Extensible as X (IOException)  import Data.Maybe as X-import Data.List as X+import Data.List as X hiding (head, tail, init, last) import Data.String.Utils as X  import System.Path as X@@ -25,3 +25,5 @@ import Utility.Path as X import Utility.Directory as X import Utility.Monad as X++import Utility.PartialPrelude as X
Config.hs view
@@ -9,6 +9,8 @@  import Common.Annex import qualified Git+import qualified Git.Config+import qualified Git.Command import qualified Annex  type ConfigKey = String@@ -16,21 +18,21 @@ {- Changes a git config setting in both internal state and .git/config -} setConfig :: ConfigKey -> String -> Annex () setConfig k value = do-	inRepo $ Git.run "config" [Param k, Param value]+	inRepo $ Git.Command.run "config" [Param k, Param value] 	-- re-read git config and update the repo's state-	newg <- inRepo Git.configRead+	newg <- inRepo Git.Config.read 	Annex.changeState $ \s -> s { Annex.repo = newg }  {- Looks up a per-remote config setting in git config.  - Failing that, tries looking for a global config option. -} getConfig :: Git.Repo -> ConfigKey -> String -> Annex String getConfig r key def = do-	def' <- fromRepo $ Git.configGet ("annex." ++ key) def-	fromRepo $ Git.configGet (remoteConfig r key) def'+	def' <- fromRepo $ Git.Config.get ("annex." ++ key) def+	fromRepo $ Git.Config.get (remoteConfig r key) def'  {- Looks up a per-remote config setting in git config. -} remoteConfig :: Git.Repo -> ConfigKey -> String-remoteConfig r key = "remote." ++ fromMaybe "" (Git.repoRemoteName r) ++ ".annex-" ++ key+remoteConfig r key = "remote." ++ fromMaybe "" (Git.remoteName r) ++ ".annex-" ++ key  {- Calculates cost for a remote. Either the default, or as configured   - by remote.<name>.annex-cost, or if remote.<name>.annex-cost-command@@ -38,15 +40,10 @@ remoteCost :: Git.Repo -> Int -> Annex Int remoteCost r def = do 	cmd <- getConfig r "cost-command" ""-	safeparse <$> if not $ null cmd+	(fromMaybe def . readMaybe) <$>+		if not $ null cmd 			then liftIO $ snd <$> pipeFrom "sh" ["-c", cmd] 			else getConfig r "cost" ""-	where-		safeparse v-			| null ws = def-			| otherwise = fromMaybe def $ readMaybe $ head ws-			where-				ws = words v  cheapRemoteCost :: Int cheapRemoteCost = 100@@ -83,6 +80,6 @@ 	where 		use (Just n) = return n 		use Nothing = perhaps (return 1) =<< -			readMaybe <$> fromRepo (Git.configGet config "1")+			readMaybe <$> fromRepo (Git.Config.get config "1") 		perhaps fallback = maybe fallback (return . id) 		config = "annex.numcopies"
Crypto.hs view
@@ -30,14 +30,10 @@ import qualified Data.Map as M import Data.ByteString.Lazy.UTF8 (fromString) import Data.Digest.Pure.SHA-import System.Posix.Types import Control.Applicative-import Control.Concurrent-import Control.Exception (finally)-import System.Exit-import System.Environment  import Common.Annex+import qualified Utility.Gpg as Gpg import Types.Key import Types.Remote import Utility.Base64@@ -71,7 +67,7 @@ 	random <- genrandom 	encryptCipher (Cipher random) ks 	where-		genrandom = gpgRead+		genrandom = Gpg.readStrict 			-- Armor the random data, to avoid newlines, 			-- since gpg only reads ciphers up to the first 			-- newline.@@ -119,7 +115,7 @@ encryptCipher :: Cipher -> KeyIds -> IO EncryptedCipher encryptCipher (Cipher c) (KeyIds ks) = do 	let ks' = nub $ sort ks -- gpg complains about duplicate recipient keyids-	encipher <- gpgPipeStrict (encrypt++recipients ks') c+	encipher <- Gpg.pipeStrict (encrypt++recipients ks') c 	return $ EncryptedCipher encipher (KeyIds ks') 	where 		encrypt = [ Params "--encrypt" ]@@ -132,7 +128,7 @@ {- Decrypting an EncryptedCipher is expensive; the Cipher should be cached. -} decryptCipher :: RemoteConfig -> EncryptedCipher -> IO Cipher decryptCipher _ (EncryptedCipher encipher _) = -	Cipher <$> gpgPipeStrict decrypt encipher+	Cipher <$> Gpg.pipeStrict decrypt encipher 	where 		decrypt = [ Param "--decrypt" ] @@ -150,12 +146,12 @@ {- Runs an action, passing it a handle from which it can   - stream encrypted content. -} withEncryptedHandle :: Cipher -> IO L.ByteString -> (Handle -> IO a) -> IO a-withEncryptedHandle = gpgCipherHandle [Params "--symmetric --force-mdc"]+withEncryptedHandle = Gpg.passphraseHandle [Params "--symmetric --force-mdc"] . cipherPassphrase  {- Runs an action, passing it a handle from which it can  - stream decrypted content. -} withDecryptedHandle :: Cipher -> IO L.ByteString -> (Handle -> IO a) -> IO a-withDecryptedHandle = gpgCipherHandle [Param "--decrypt"]+withDecryptedHandle = Gpg.passphraseHandle [Param "--decrypt"] . cipherPassphrase  {- Streams encrypted content to an action. -} withEncryptedContent :: Cipher -> IO L.ByteString -> (L.ByteString -> IO a) -> IO a@@ -169,70 +165,8 @@       -> Cipher -> IO L.ByteString -> (L.ByteString -> IO a) -> IO a pass to c i a = to c i $ \h -> a =<< L.hGetContents h -gpgParams :: [CommandParam] -> IO [String]-gpgParams params = do-	-- Enable batch mode if GPG_AGENT_INFO is set, to avoid extraneous-	-- gpg output about password prompts.-	e <- catchDefaultIO (getEnv "GPG_AGENT_INFO") ""-	let batch = if null e then [] else ["--batch"]-	return $ batch ++ defaults ++ toCommand params-	where-		-- be quiet, even about checking the trustdb-		defaults = ["--quiet", "--trust-model", "always"]--gpgRead :: [CommandParam] -> IO String-gpgRead params = do-	params' <- gpgParams params-	pOpen ReadFromPipe "gpg" params' hGetContentsStrict--gpgPipeStrict :: [CommandParam] -> String -> IO String-gpgPipeStrict params input = do-	params' <- gpgParams params-	(pid, fromh, toh) <- hPipeBoth "gpg" params'-	_ <- forkIO $ finally (hPutStr toh input) (hClose toh)-	output <- hGetContentsStrict fromh-	forceSuccess pid-	return output--{- Runs gpg with a cipher and some parameters, feeding it an input,- - and passing a handle to its output to an action.- -- - Note that to avoid deadlock with the cleanup stage,- - the action must fully consume gpg's input before returning. -}-gpgCipherHandle :: [CommandParam] -> Cipher -> IO L.ByteString -> (Handle -> IO a) -> IO a-gpgCipherHandle params c a b = do-	-- pipe the passphrase into gpg on a fd-	(frompipe, topipe) <- createPipe-	_ <- forkIO $ do-		toh <- fdToHandle topipe-		hPutStrLn toh $ cipherPassphrase c-		hClose toh-	let Fd passphrasefd = frompipe-	let passphrase = [Param "--passphrase-fd", Param $ show passphrasefd]--	params' <- gpgParams $ passphrase ++ params-	(pid, fromh, toh) <- hPipeBoth "gpg" params'-	pid2 <- forkProcess $ do-		L.hPut toh =<< a-		hClose toh-		exitSuccess-	hClose toh-	ret <- b fromh--	-- cleanup-	forceSuccess pid-	_ <- getProcessStatus True False pid2-	closeFd frompipe-	return ret- configKeyIds :: RemoteConfig -> IO KeyIds-configKeyIds c = parse <$> gpgRead params-	where-		params = [Params "--with-colons --list-public-keys",-			Param $ configGet c "encryption"]-		parse = KeyIds . map keyIdField . filter pubKey . lines-		pubKey = isPrefixOf "pub:"-		keyIdField s = split ":" s !! 4+configKeyIds c = Gpg.findPubKeys $ configGet c "encryption"  configGet :: RemoteConfig -> String -> String configGet c key = fromMaybe missing $ M.lookup key c
Git.hs view
@@ -3,173 +3,36 @@  - This is written to be completely independant of git-annex and should be  - suitable for other uses.  -- - Copyright 2010,2011 Joey Hess <joey@kitenet.net>+ - Copyright 2010, 2011 Joey Hess <joey@kitenet.net>  -  - Licensed under the GNU GPL version 3 or higher.  -}  module Git (-	Repo,+	Repo(..), 	Ref(..), 	Branch, 	Sha, 	Tag,-	repoFromCwd,-	repoFromAbsPath,-	repoFromUnknown,-	repoFromUrl,-	localToUrl, 	repoIsUrl, 	repoIsSsh, 	repoIsHttp, 	repoIsLocalBare, 	repoDescribe,-	refDescribe, 	repoLocation, 	workTree,-	workTreeFile, 	gitDir,-	urlPath,-	urlHost,-	urlPort,-	urlHostUser,-	urlAuthority,-	urlScheme,-	configGet,-	configMap,-	configRead,-	hConfigRead,-	configStore, 	configTrue,-	gitCommandLine,-	run,-	runBool,-	pipeRead,-	pipeWrite,-	pipeWriteRead,-	pipeNullSplit,-	pipeNullSplitB, 	attributes,-	remotes,-	remotesAdd,-	genRemote,-	repoRemoteName,-	repoRemoteNameSet,-	repoRemoteNameFromKey,-	checkAttr,-	decodeGitFile,-	encodeGitFile,-	repoAbsPath,-	reap,-	useIndex,-	getSha,-	shaSize,-	commit, 	assertLocal,--	prop_idempotent_deencode ) where -import System.Posix.Directory-import System.Posix.User-import Control.Exception (bracket_)-import qualified Data.Map as M hiding (map, split)-import Network.URI+import qualified Data.Map as M import Data.Char-import Data.Word (Word8)-import Codec.Binary.UTF8.String (encode)-import Text.Printf-import System.Exit-import System.Posix.Env (setEnv, unsetEnv, getEnv)-import qualified Data.ByteString.Lazy.Char8 as L+import Network.URI (uriPath, uriScheme)  import Common--{- There are two types of repositories; those on local disk and those- - accessed via an URL. -}-data RepoLocation = Dir FilePath | Url URI | Unknown-	deriving (Show, Eq)--data Repo = Repo {-	location :: RepoLocation,-	config :: M.Map String String,-	remotes :: [Repo],-	-- remoteName holds the name used for this repo in remotes-	remoteName :: Maybe String -} deriving (Show, Eq)--{- A git ref. Can be a sha1, or a branch or tag name. -}-newtype Ref = Ref String-	deriving (Eq)--instance Show Ref where-	show (Ref v) = v--{- Aliases for Ref. -}-type Branch = Ref-type Sha = Ref-type Tag = Ref--newFrom :: RepoLocation -> Repo-newFrom l = -	Repo {-		location = l,-		config = M.empty,-		remotes = [],-		remoteName = Nothing-	}--{- Local Repo constructor, requires an absolute path to the repo be- - specified. -}-repoFromAbsPath :: FilePath -> IO Repo-repoFromAbsPath dir-	| "/" `isPrefixOf` dir = do- 		-- Git always looks for "dir.git" in preference to-		-- to "dir", even if dir ends in a "/".-		let canondir = dropTrailingPathSeparator dir-		let dir' = canondir ++ ".git"-		e <- doesDirectoryExist dir'-		if e-			then ret dir'-			else if "/.git" `isSuffixOf` canondir-				then do-					-- When dir == "foo/.git", git looks-					-- for "foo/.git/.git", and failing-					-- that, uses "foo" as the repository.-					e' <- doesDirectoryExist $ dir </> ".git"-					if e'-						then ret dir-						else ret $ takeDirectory canondir-				else ret dir-	| otherwise = error $ "internal error, " ++ dir ++ " is not absolute"-	where-		ret = return . newFrom . Dir--{- Remote Repo constructor. Throws exception on invalid url. -}-repoFromUrl :: String -> IO Repo-repoFromUrl url-	| startswith "file://" url = repoFromAbsPath $ uriPath u-	| otherwise = return $ newFrom $ Url u-		where-			u = fromMaybe bad $ parseURI url-			bad = error $ "bad url " ++ url--{- Creates a repo that has an unknown location. -}-repoFromUnknown :: Repo-repoFromUnknown = newFrom Unknown--{- Converts a Local Repo into a remote repo, using the reference repo- - which is assumed to be on the same host. -}-localToUrl :: Repo -> Repo -> Repo-localToUrl reference r-	| not $ repoIsUrl reference = error "internal error; reference repo not url"-	| repoIsUrl r = r-	| otherwise = r { location = Url $ fromJust $ parseURI absurl }-	where-		absurl =-			urlScheme reference ++ "//" ++-			urlAuthority reference ++-			workTree r+import Git.Types  {- User-visible description of a git repo. -} repoDescribe :: Repo -> String@@ -178,43 +41,12 @@ repoDescribe Repo { location = Dir dir } = dir repoDescribe Repo { location = Unknown } = "UNKNOWN" -{- Converts a fully qualified git ref into a user-visible version. -}-refDescribe :: Ref -> String-refDescribe = remove "refs/heads/" . remove "refs/remotes/" . show-	where-		remove prefix s-			| prefix `isPrefixOf` s = drop (length prefix) s-			| otherwise = s- {- Location of the repo, either as a path or url. -} repoLocation :: Repo -> String repoLocation Repo { location = Url url } = show url repoLocation Repo { location = Dir dir } = dir repoLocation Repo { location = Unknown } = undefined -{- Constructs and returns an updated version of a repo with- - different remotes list. -}-remotesAdd :: Repo -> [Repo] -> Repo-remotesAdd repo rs = repo { remotes = rs }--{- Returns the name of the remote that corresponds to the repo, if- - it is a remote. -}-repoRemoteName :: Repo -> Maybe String-repoRemoteName Repo { remoteName = Just name } = Just name-repoRemoteName _ = Nothing--{- Sets the name of a remote. -}-repoRemoteNameSet :: String -> Repo -> Repo-repoRemoteNameSet n r = r { remoteName = Just n }--{- Sets the name of a remote based on the git config key, such as-   "remote.foo.url". -}-repoRemoteNameFromKey :: String -> Repo -> Repo-repoRemoteNameFromKey k = repoRemoteNameSet basename-	where-		basename = join "." $ reverse $ drop 1 $-				reverse $ drop 1 $ split "." k- {- Some code needs to vary between URL and normal repos,  - or bare and non-bare, these functions help with that. -} repoIsUrl :: Repo -> Bool@@ -223,11 +55,13 @@  repoIsSsh :: Repo -> Bool repoIsSsh Repo { location = Url url } -	| uriScheme url == "ssh:" = True+	| scheme == "ssh:" = True 	-- git treats these the same as ssh-	| uriScheme url == "git+ssh:" = True-	| uriScheme url == "ssh+git:" = True+	| scheme == "git+ssh:" = True+	| scheme == "ssh+git:" = True 	| otherwise = False+	where+		scheme = uriScheme url repoIsSsh _ = False  repoIsHttp :: Repo -> Bool@@ -248,15 +82,8 @@ assertLocal repo action =  	if not $ repoIsUrl repo 		then action-		else error $ "acting on URL git repo " ++  repoDescribe repo ++ -				" not supported"-assertUrl :: Repo -> a -> a-assertUrl repo action = -	if repoIsUrl repo-		then action-		else error $ "acting on local git repo " ++  repoDescribe repo ++ +		else error $ "acting on non-local git repo " ++  repoDescribe repo ++  				" not supported"- configBare :: Repo -> Bool configBare repo = maybe unknown configTrue $ M.lookup "core.bare" $ config repo 	where@@ -280,458 +107,10 @@  -  - Note that for URL repositories, this is the path on the remote host. -} workTree :: Repo -> FilePath-workTree r@(Repo { location = Url _ }) = urlPath r-workTree (Repo { location = Dir d }) = d+workTree Repo { location = Url u } = uriPath u+workTree Repo { location = Dir d } = d workTree Repo { location = Unknown } = undefined -{- Given a relative or absolute filename inside a git repository's- - workTree, calculates the name to use to refer to that file to git.- -- - This is complicated because the best choice can vary depending on- - whether the cwd is in a subdirectory of the git repository, or not.- -- - For example, when adding a file "/tmp/repo/foo", it's best to refer- - to it as "foo" if the cwd is outside the repository entirely- - (this avoids a gotcha with using the full path name when /tmp/repo- - is itself a symlink). But, if the cwd is "/tmp/repo/subdir",- - it's best to refer to "../foo".- -}-workTreeFile :: FilePath -> Repo -> IO FilePath-workTreeFile file repo@(Repo { location = Dir d }) = do-	cwd <- getCurrentDirectory-	let file' = absfile cwd-	unless (inrepo file') $-		error $ file ++ " is not located inside git repository " ++ absrepo-	if inrepo $ addTrailingPathSeparator cwd-		then return $ relPathDirToFile cwd file'-		else return $ drop (length absrepo) file'-	where-		-- normalize both repo and file, so that repo-		-- will be substring of file-		absrepo = maybe bad addTrailingPathSeparator $ absNormPath "/" d-		absfile c = fromMaybe file $ secureAbsNormPath c file-		inrepo f = absrepo `isPrefixOf` f-		bad = error $ "bad repo" ++ repoDescribe repo-workTreeFile _ repo = assertLocal repo $ error "internal"--{- Path of an URL repo. -}-urlPath :: Repo -> String-urlPath Repo { location = Url u } = uriPath u-urlPath repo = assertUrl repo $ error "internal"--{- Scheme of an URL repo. -}-urlScheme :: Repo -> String-urlScheme Repo { location = Url u } = uriScheme u-urlScheme repo = assertUrl repo $ error "internal"--{- Work around a bug in the real uriRegName- - <http://trac.haskell.org/network/ticket/40> -}-uriRegName' :: URIAuth -> String-uriRegName' a = fixup $ uriRegName a-	where-		fixup x@('[':rest)-			| rest !! len == ']' = take len rest-			| otherwise = x-			where-				len  = length rest - 1-		fixup x = x--{- Hostname of an URL repo. -}-urlHost :: Repo -> String-urlHost = urlAuthPart uriRegName'--{- Port of an URL repo, if it has a nonstandard one. -}-urlPort :: Repo -> Maybe Integer-urlPort r = -	case urlAuthPart uriPort r of-		":" -> Nothing-		(':':p) -> readMaybe p-		_ -> Nothing--{- Hostname of an URL repo, including any username (ie, "user@host") -}-urlHostUser :: Repo -> String-urlHostUser r = urlAuthPart uriUserInfo r ++ urlAuthPart uriRegName' r--{- The full authority portion an URL repo. (ie, "user@host:port") -}-urlAuthority :: Repo -> String-urlAuthority = urlAuthPart assemble-	where-		assemble a = uriUserInfo a ++ uriRegName' a ++ uriPort a--{- Applies a function to extract part of the uriAuthority of an URL repo. -}-urlAuthPart :: (URIAuth -> a) -> Repo -> a-urlAuthPart a Repo { location = Url u } = a auth-	where-		auth = fromMaybe (error $ "bad url " ++ show u) (uriAuthority u)-urlAuthPart _ repo = assertUrl repo $ error "internal"--{- Constructs a git command line operating on the specified repo. -}-gitCommandLine :: [CommandParam] -> Repo -> [CommandParam]-gitCommandLine params repo@(Repo { location = Dir _ } ) =-	-- force use of specified repo via --git-dir and --work-tree-	[ Param ("--git-dir=" ++ gitDir repo)-	, Param ("--work-tree=" ++ workTree repo)-	] ++ params-gitCommandLine _ repo = assertLocal repo $ error "internal"--{- Runs git in the specified repo. -}-runBool :: String -> [CommandParam] -> Repo -> IO Bool-runBool subcommand params repo = assertLocal repo $-	boolSystem "git" $ gitCommandLine (Param subcommand : params) repo--{- Runs git in the specified repo, throwing an error if it fails. -}-run :: String -> [CommandParam] -> Repo -> IO ()-run subcommand params repo = assertLocal repo $-	runBool subcommand params repo-		>>! error $ "git " ++ show params ++ " failed"--{- Runs a git subcommand and returns its output, lazily. - -- - Note that this leaves the git process running, and so zombies will- - result unless reap is called.- -}-pipeRead :: [CommandParam] -> Repo -> IO L.ByteString-pipeRead params repo = assertLocal repo $ do-	(_, h) <- hPipeFrom "git" $ toCommand $ gitCommandLine params repo-	hSetBinaryMode h True-	L.hGetContents h--{- Runs a git subcommand, feeding it input.- - You should call either getProcessStatus or forceSuccess on the PipeHandle. -}-pipeWrite :: [CommandParam] -> L.ByteString -> Repo -> IO PipeHandle-pipeWrite params s repo = assertLocal repo $ do-	(p, h) <- hPipeTo "git" (toCommand $ gitCommandLine params repo)-	L.hPut h s-	hClose h-	return p--{- Runs a git subcommand, feeding it input, and returning its output.- - You should call either getProcessStatus or forceSuccess on the PipeHandle. -}-pipeWriteRead :: [CommandParam] -> L.ByteString -> Repo -> IO (PipeHandle, L.ByteString)-pipeWriteRead params s repo = assertLocal repo $ do-	(p, from, to) <- hPipeBoth "git" (toCommand $ gitCommandLine params repo)-	hSetBinaryMode from True-	L.hPut to s-	hClose to-	c <- L.hGetContents from-	return (p, c)--{- Reads null terminated output of a git command (as enabled by the -z - - parameter), and splits it. -}-pipeNullSplit :: [CommandParam] -> Repo -> IO [String]-pipeNullSplit params repo = map L.unpack <$> pipeNullSplitB params repo--{- For when Strings are not needed. -}-pipeNullSplitB ::[CommandParam] -> Repo -> IO [L.ByteString]-pipeNullSplitB params repo = filter (not . L.null) . L.split '\0' <$>-	pipeRead params repo--{- Reaps any zombie git processes. -}-reap :: IO ()-reap = do-	-- throws an exception when there are no child processes-	r <- catchDefaultIO (getAnyProcessStatus False True) Nothing-	maybe (return ()) (const reap) r--{- Forces git to use the specified index file.- - Returns an action that will reset back to the default- - index file. -}-useIndex :: FilePath -> IO (IO ())-useIndex index = do-	res <- getEnv var-	setEnv var index True-	return $ reset res-	where-		var = "GIT_INDEX_FILE"-		reset (Just v) = setEnv var v True-		reset _ = unsetEnv var--{- Runs an action that causes a git subcommand to emit a sha, and strips-   any trailing newline, returning the sha. -}-getSha :: String -> IO String -> IO Sha-getSha subcommand a = do-	t <- a-	let t' = if last t == '\n'-		then init t-		else t-	when (length t' /= shaSize) $-		error $ "failed to read sha from git " ++ subcommand ++ " (" ++ t' ++ ")"-	return $ Ref t'--{- Size of a git sha. -}-shaSize :: Int-shaSize = 40--{- Commits the index into the specified branch (or other ref), - - with the specified parent refs, and returns the new ref -}-commit :: String -> Ref -> [Ref] -> Repo -> IO Ref-commit message newref parentrefs repo = do-	tree <- getSha "write-tree" $ asString $-		pipeRead [Param "write-tree"] repo-	sha <- getSha "commit-tree" $ asString $-		ignorehandle $ pipeWriteRead-			(map Param $ ["commit-tree", show tree] ++ ps)-			(L.pack message) repo-	run "update-ref" [Param $ show newref, Param $ show sha] repo-	return sha-	where-		ignorehandle a = snd <$> a-		asString a = L.unpack <$> a-		ps = concatMap (\r -> ["-p", show r]) parentrefs--{- Runs git config and populates a repo with its config. -}-configRead :: Repo -> IO Repo-configRead repo@(Repo { location = Dir d }) = do-	{- Cannot use pipeRead because it relies on the config having-	   been already read. Instead, chdir to the repo. -}-	cwd <- getCurrentDirectory-	bracket_ (changeWorkingDirectory d) (changeWorkingDirectory cwd) $-		pOpen ReadFromPipe "git" ["config", "--list"] $ hConfigRead repo-configRead r = assertLocal r $ error "internal"--{- Reads git config from a handle and populates a repo with it. -}-hConfigRead :: Repo -> Handle -> IO Repo-hConfigRead repo h = do-	val <- hGetContentsStrict h-	configStore val repo--{- Stores a git config into a repo, returning the new version of the repo.- - The git config may be multiple lines, or a single line. Config settings- - can be updated inrementally. -}-configStore :: String -> Repo -> IO Repo-configStore s repo = do-	let repo' = repo { config = configParse s `M.union` config repo }-	rs <- configRemotes repo'-	return $ repo' { remotes = rs }--{- Parses git config --list output into a config map. -}-configParse :: String -> M.Map String String-configParse s = M.fromList $ map pair $ lines s-	where-		pair = separate (== '=')--{- Calculates a list of a repo's configured remotes, by parsing its config. -}-configRemotes :: Repo -> IO [Repo]-configRemotes repo = mapM construct remotepairs-	where-		filterconfig f = filter f $ M.toList $ config repo-		filterkeys f = filterconfig (\(k,_) -> f k)-		remotepairs = filterkeys isremote-		isremote k = startswith "remote." k && endswith ".url" k-		construct (k,v) = repoRemoteNameFromKey k <$> genRemote v repo--{- Generates one of a repo's remotes using a given location (ie, an url). -}-genRemote :: String -> Repo -> IO Repo-genRemote s repo = gen $ calcloc s-	where-		filterconfig f = filter f $ M.toList $ config repo-		gen v	-			| scpstyle v = repoFromUrl $ scptourl v-			| isURI v = repoFromUrl v-			| otherwise = repoFromRemotePath v repo-		-- insteadof config can rewrite remote location-		calcloc l-			| null insteadofs = l-			| otherwise = replacement ++ drop (length bestvalue) l-			where-				replacement = drop (length prefix) $-					take (length bestkey - length suffix) bestkey-				(bestkey, bestvalue) = maximumBy longestvalue insteadofs-				longestvalue (_, a) (_, b) = compare b a-				insteadofs = filterconfig $ \(k, v) -> -					startswith prefix k &&-					endswith suffix k &&-					startswith v l-				(prefix, suffix) = ("url." , ".insteadof")-		-- git remotes can be written scp style -- [user@]host:dir-		scpstyle v = ":" `isInfixOf` v && not ("//" `isInfixOf` v)-		scptourl v = "ssh://" ++ host ++ slash dir-			where-				(host, dir) = separate (== ':') v-				slash d	| d == "" = "/~/" ++ d-					| "/" `isPrefixOf` d = d-					| "~" `isPrefixOf` d = '/':d-					| otherwise = "/~/" ++ d- {- Checks if a string from git config is a true value. -} configTrue :: String -> Bool configTrue s = map toLower s == "true"--{- Returns a single git config setting, or a default value if not set. -}-configGet :: String -> String -> Repo -> String-configGet key defaultValue repo = -	M.findWithDefault defaultValue key (config repo)--{- Access to raw config Map -}-configMap :: Repo -> M.Map String String-configMap = config--{- Efficiently looks up a gitattributes value for each file in a list. -}-checkAttr :: String -> [FilePath] -> Repo -> IO [(FilePath, String)]-checkAttr attr files repo = do-	-- git check-attr needs relative filenames input; it will choke-	-- on some absolute filenames. This also means it will output-	-- all relative filenames.-	cwd <- getCurrentDirectory-	let relfiles = map (relPathDirToFile cwd . absPathFrom cwd) files-	(_, fromh, toh) <- hPipeBoth "git" (toCommand params)-        _ <- forkProcess $ do-		hClose fromh-                hPutStr toh $ join "\0" relfiles-                hClose toh-                exitSuccess-        hClose toh-	(map topair . lines) <$> hGetContents fromh-	where-		params = gitCommandLine -				[ Param "check-attr"-				, Param attr-				, Params "-z --stdin"-				] repo-		topair l = (file, value)-			where -				file = decodeGitFile $ join sep $ take end bits-				value = bits !! end-				end = length bits - 1-				bits = split sep l-				sep = ": " ++ attr ++ ": "--{- Some git commands output encoded filenames. Decode that (annoyingly- - complex) encoding. -}-decodeGitFile :: String -> FilePath-decodeGitFile [] = []-decodeGitFile f@(c:s)-	-- encoded strings will be inside double quotes-	| c == '"' = unescape ("", middle)-	| otherwise = f-	where-		e = '\\'-		middle = init s-		unescape (b, []) = b-		-- look for escapes starting with '\'-		unescape (b, v) = b ++ beginning ++ unescape (decode rest)-			where-				pair = span (/= e) v-				beginning = fst pair-				rest = snd pair-		isescape x = x == e-		-- \NNN is an octal encoded character-		decode (x:n1:n2:n3:rest)-			| isescape x && alloctal = (fromoctal, rest)-				where-					alloctal = isOctDigit n1 &&-						isOctDigit n2 &&-						isOctDigit n3-					fromoctal = [chr $ readoctal [n1, n2, n3]]-					readoctal o = read $ "0o" ++ o :: Int-		-- \C is used for a few special characters-		decode (x:nc:rest)-			| isescape x = ([echar nc], rest)-			where-				echar 'a' = '\a'-				echar 'b' = '\b'-				echar 'f' = '\f'-				echar 'n' = '\n'-				echar 'r' = '\r'-				echar 't' = '\t'-				echar 'v' = '\v'-				echar a = a-		decode n = ("", n)--{- Should not need to use this, except for testing decodeGitFile. -}-encodeGitFile :: FilePath -> String-encodeGitFile s = foldl (++) "\"" (map echar s) ++ "\""-	where-		e c = '\\' : [c]-		echar '\a' = e 'a'-		echar '\b' = e 'b'-		echar '\f' = e 'f'-		echar '\n' = e 'n'-		echar '\r' = e 'r'-		echar '\t' = e 't'-		echar '\v' = e 'v'-		echar '\\' = e '\\'-		echar '"'  = e '"'-		echar x-			| ord x < 0x20 = e_num x -- low ascii-			| ord x >= 256 = e_utf x-			| ord x > 0x7E = e_num x -- high ascii-			| otherwise = [x]        -- printable ascii-			where -				showoctal i = '\\' : printf "%03o" i-				e_num c = showoctal $ ord c-				-- unicode character is decomposed to-				-- Word8s and each is shown in octal-				e_utf c = showoctal =<< (encode [c] :: [Word8])--{- for quickcheck -}-prop_idempotent_deencode :: String -> Bool-prop_idempotent_deencode s = s == decodeGitFile (encodeGitFile s)--{- Constructs a Repo from the path specified in the git remotes of- - another Repo. -}-repoFromRemotePath :: FilePath -> Repo -> IO Repo-repoFromRemotePath dir repo = do-	dir' <- expandTilde dir-	repoFromAbsPath $ workTree repo </> dir'--{- Git remotes can have a directory that is specified relative- - to the user's home directory, or that contains tilde expansions.- - This converts such a directory to an absolute path.- - Note that it has to run on the system where the remote is.- -}-repoAbsPath :: FilePath -> IO FilePath-repoAbsPath d = do-	d' <- expandTilde d-	h <- myHomeDir-	return $ h </> d'--expandTilde :: FilePath -> IO FilePath-expandTilde = expandt True-	where-		expandt _ [] = return ""-		expandt _ ('/':cs) = do-			v <- expandt True cs-			return ('/':v)-		expandt True ('~':'/':cs) = do-			h <- myHomeDir-			return $ h </> cs-		expandt True ('~':cs) = do-			let (name, rest) = findname "" cs-			u <- getUserEntryForName name-			return $ homeDirectory u </> rest-		expandt _ (c:cs) = do-			v <- expandt False cs-			return (c:v)-		findname n [] = (n, "")-		findname n (c:cs)-			| c == '/' = (n, cs)-			| otherwise = findname (n++[c]) cs--{- Finds the current git repository, which may be in a parent directory. -}-repoFromCwd :: IO Repo-repoFromCwd = getCurrentDirectory >>= seekUp isRepoTop >>= maybe norepo makerepo-	where-		makerepo = return . newFrom . Dir-		norepo = error "Not in a git repository."--seekUp :: (FilePath -> IO Bool) -> FilePath -> IO (Maybe FilePath)-seekUp want dir = do-	ok <- want dir-	if ok-		then return $ Just dir-		else case parentDir dir of-			"" -> return Nothing-			d -> seekUp want d--isRepoTop :: FilePath -> IO Bool-isRepoTop dir = do-	r <- isRepo-	b <- isBareRepo-	return (r || b)-	where-		isRepo = gitSignature ".git" ".git/config"-		isBareRepo = gitSignature "objects" "config"-		gitSignature subdir file = liftM2 (&&)-			(doesDirectoryExist (dir ++ "/" ++ subdir))-			(doesFileExist (dir ++ "/" ++ file))
+ Git/Branch.hs view
@@ -0,0 +1,87 @@+{- git branch stuff+ -+ - Copyright 2011 Joey Hess <joey@kitenet.net>+ -+ - Licensed under the GNU GPL version 3 or higher.+ -}++module Git.Branch where++import qualified Data.ByteString.Lazy.Char8 as L++import Common+import Git+import Git.Sha+import Git.Command++{- The currently checked out branch. -}+current :: Repo -> IO (Maybe Git.Ref)+current r = parse <$> pipeRead [Param "symbolic-ref", Param "HEAD"] r+	where+		parse v+			| L.null v = Nothing+			| otherwise = Just $ Git.Ref $ firstLine $ L.unpack v++{- Checks if the second branch has any commits not present on the first+ - branch. -}+changed :: Branch -> Branch -> Repo -> IO Bool+changed origbranch newbranch repo+	| origbranch == newbranch = return False+	| otherwise = not . L.null <$> diffs+	where+		diffs = pipeRead+			[ Param "log"+			, Param (show origbranch ++ ".." ++ show newbranch)+			, Params "--oneline -n1"+			] repo++{- Given a set of refs that are all known to have commits not+ - on the branch, tries to update the branch by a fast-forward.+ -+ - In order for that to be possible, one of the refs must contain+ - every commit present in all the other refs.+ -}+fastForward :: Branch -> [Ref] -> Repo -> IO Bool+fastForward _ [] _ = return True+fastForward branch (first:rest) repo = do+	-- First, check that the branch does not contain any+	-- new commits that are not in the first ref. If it does,+	-- cannot fast-forward.+	diverged <- changed first branch repo+	if diverged+		then no_ff+		else maybe no_ff do_ff =<< findbest first rest+	where+		no_ff = return False+		do_ff to = do+			run "update-ref"+				[Param $ show branch, Param $ show to] repo+			return True+		findbest c [] = return $ Just c+		findbest c (r:rs)+			| c == r = findbest c rs+			| otherwise = do+			better <- changed c r repo+			worse <- changed r c repo+			case (better, worse) of+				(True, True) -> return Nothing -- divergent fail+				(True, False) -> findbest r rs -- better+				(False, True) -> findbest c rs -- worse+				(False, False) -> findbest c rs -- same++{- Commits the index into the specified branch (or other ref), + - with the specified parent refs, and returns the committed sha -}+commit :: String -> Branch -> [Ref] -> Repo -> IO Sha+commit message branch parentrefs repo = do+	tree <- getSha "write-tree" $ asString $+		pipeRead [Param "write-tree"] repo+	sha <- getSha "commit-tree" $ asString $+		ignorehandle $ pipeWriteRead+			(map Param $ ["commit-tree", show tree] ++ ps)+			(L.pack message) repo+	run "update-ref" [Param $ show branch, Param $ show sha] repo+	return sha+	where+		ignorehandle a = snd <$> a+		asString a = L.unpack <$> a+		ps = concatMap (\r -> ["-p", show r]) parentrefs
Git/CatFile.hs view
@@ -19,15 +19,17 @@ import qualified Data.ByteString.Char8 as S import qualified Data.ByteString.Lazy.Char8 as L +import Common import Git-import Utility.SafeCommand+import Git.Sha+import Git.Command  type CatFileHandle = (PipeHandle, Handle, Handle)  {- Starts git cat-file running in batch mode in a repo and returns a handle. -} catFileStart :: Repo -> IO CatFileHandle catFileStart repo = hPipeBoth "git" $ toCommand $-	Git.gitCommandLine [Param "cat-file", Param "--batch"] repo+	gitCommandLine [Param "cat-file", Param "--batch"] repo  {- Stops git cat-file. -} catFileStop :: CatFileHandle -> IO ()@@ -49,23 +51,23 @@ 	header <- hGetLine from 	case words header of 		[sha, objtype, size]-			| length sha == Git.shaSize &&+			| length sha == shaSize && 			  validobjtype objtype -> handle size-			| otherwise -> empty+			| otherwise -> dne 		_-			| header == show object ++ " missing" -> empty+			| header == show object ++ " missing" -> dne 			| otherwise -> error $ "unknown response from git cat-file " ++ header 	where 		handle size = case reads size of 			[(bytes, "")] -> readcontent bytes-			_ -> empty+			_ -> dne 		readcontent bytes = do 			content <- S.hGet from bytes 			c <- hGetChar from 			when (c /= '\n') $ 				error "missing newline from git cat-file" 			return $ L.fromChunks [content]-		empty = return L.empty+		dne = return L.empty 		validobjtype t 			| t == "blob" = True 			| t == "commit" = True
+ Git/CheckAttr.hs view
@@ -0,0 +1,66 @@+{- git check-attr interface+ -+ - Copyright 2010, 2011 Joey Hess <joey@kitenet.net>+ -+ - Licensed under the GNU GPL version 3 or higher.+ -}++module Git.CheckAttr where++import System.Exit++import Common+import Git+import Git.Command+import qualified Git.Filename+import qualified Git.Version++{- Efficiently looks up a gitattributes value for each file in a list. -}+lookup :: String -> [FilePath] -> Repo -> IO [(FilePath, String)]+lookup attr files repo = do+	cwd <- getCurrentDirectory+	(_, fromh, toh) <- hPipeBoth "git" (toCommand params)+        _ <- forkProcess $ do+		hClose fromh+                hPutStr toh $ join "\0" $ input cwd+                hClose toh+                exitSuccess+        hClose toh+	output cwd . lines <$> hGetContents fromh+	where+		params = gitCommandLine +				[ Param "check-attr"+				, Param attr+				, Params "-z --stdin"+				] repo++		{- Before git 1.7.7, git check-attr worked best with+		 - absolute filenames; using them worked around some bugs+		 - with relative filenames.+		 - +		 - With newer git, git check-attr chokes on some absolute+		 - filenames, and the bugs that necessitated them were fixed,+		 - so use relative filenames. -}+		oldgit = Git.Version.older "1.7.7"+		input cwd+			| oldgit = map (absPathFrom cwd) files+			| otherwise = map (relPathDirToFile cwd . absPathFrom cwd) files+		output cwd+			| oldgit = map (torel cwd . topair)+			| otherwise = map topair++		topair l = (Git.Filename.decode file, value)+			where +				file = join sep $ beginning bits+				value = end bits !! 0+				bits = split sep l+				sep = ": " ++ attr ++ ": "++		torel cwd (file, value) = (relfile, value)+			where+				relfile+					| startswith cwd' file = drop (length cwd') file+					| otherwise = relPathDirToFile top' file+				top = workTree repo+				cwd' = cwd ++ "/"+				top' = top ++ "/"
+ Git/Command.hs view
@@ -0,0 +1,82 @@+{- running git commands+ -+ - Copyright 2010, 2011 Joey Hess <joey@kitenet.net>+ -+ - Licensed under the GNU GPL version 3 or higher.+ -}++module Git.Command where++import qualified Data.ByteString.Lazy.Char8 as L++import Common+import Git+import Git.Types++{- Constructs a git command line operating on the specified repo. -}+gitCommandLine :: [CommandParam] -> Repo -> [CommandParam]+gitCommandLine params repo@(Repo { location = Dir _ } ) =+	-- force use of specified repo via --git-dir and --work-tree+	[ Param ("--git-dir=" ++ gitDir repo)+	, Param ("--work-tree=" ++ workTree repo)+	] ++ params+gitCommandLine _ repo = assertLocal repo $ error "internal"++{- Runs git in the specified repo. -}+runBool :: String -> [CommandParam] -> Repo -> IO Bool+runBool subcommand params repo = assertLocal repo $+	boolSystem "git" $ gitCommandLine (Param subcommand : params) repo++{- Runs git in the specified repo, throwing an error if it fails. -}+run :: String -> [CommandParam] -> Repo -> IO ()+run subcommand params repo = assertLocal repo $+	runBool subcommand params repo+		>>! error $ "git " ++ show params ++ " failed"++{- Runs a git subcommand and returns its output, lazily. + -+ - Note that this leaves the git process running, and so zombies will+ - result unless reap is called.+ -}+pipeRead :: [CommandParam] -> Repo -> IO L.ByteString+pipeRead params repo = assertLocal repo $ do+	(_, h) <- hPipeFrom "git" $ toCommand $ gitCommandLine params repo+	hSetBinaryMode h True+	L.hGetContents h++{- Runs a git subcommand, feeding it input.+ - You should call either getProcessStatus or forceSuccess on the PipeHandle. -}+pipeWrite :: [CommandParam] -> L.ByteString -> Repo -> IO PipeHandle+pipeWrite params s repo = assertLocal repo $ do+	(p, h) <- hPipeTo "git" (toCommand $ gitCommandLine params repo)+	L.hPut h s+	hClose h+	return p++{- Runs a git subcommand, feeding it input, and returning its output.+ - You should call either getProcessStatus or forceSuccess on the PipeHandle. -}+pipeWriteRead :: [CommandParam] -> L.ByteString -> Repo -> IO (PipeHandle, L.ByteString)+pipeWriteRead params s repo = assertLocal repo $ do+	(p, from, to) <- hPipeBoth "git" (toCommand $ gitCommandLine params repo)+	hSetBinaryMode from True+	L.hPut to s+	hClose to+	c <- L.hGetContents from+	return (p, c)++{- Reads null terminated output of a git command (as enabled by the -z + - parameter), and splits it. -}+pipeNullSplit :: [CommandParam] -> Repo -> IO [String]+pipeNullSplit params repo = map L.unpack <$> pipeNullSplitB params repo++{- For when Strings are not needed. -}+pipeNullSplitB ::[CommandParam] -> Repo -> IO [L.ByteString]+pipeNullSplitB params repo = filter (not . L.null) . L.split '\0' <$>+	pipeRead params repo++{- Reaps any zombie git processes. -}+reap :: IO ()+reap = do+	-- throws an exception when there are no child processes+	r <- catchDefaultIO (getAnyProcessStatus False True) Nothing+	maybe (return ()) (const reap) r
+ Git/Config.hs view
@@ -0,0 +1,66 @@+{- git repository configuration handling+ -+ - Copyright 2010,2011 Joey Hess <joey@kitenet.net>+ -+ - Licensed under the GNU GPL version 3 or higher.+ -}++module Git.Config where++import System.Posix.Directory+import Control.Exception (bracket_)+import qualified Data.Map as M++import Common+import Git+import Git.Types+import qualified Git.Construct++{- Returns a single git config setting, or a default value if not set. -}+get :: String -> String -> Repo -> String+get key defaultValue repo = M.findWithDefault defaultValue key (config repo)++{- Runs git config and populates a repo with its config. -}+read :: Repo -> IO Repo+read repo@(Repo { location = Dir d }) = do+	{- Cannot use pipeRead because it relies on the config having+	   been already read. Instead, chdir to the repo. -}+	cwd <- getCurrentDirectory+	bracket_ (changeWorkingDirectory d) (changeWorkingDirectory cwd) $+		pOpen ReadFromPipe "git" ["config", "--null", "--list"] $+			hRead repo+read r = assertLocal r $+	error $ "internal error; trying to read config of " ++ show r++{- Reads git config from a handle and populates a repo with it. -}+hRead :: Repo -> Handle -> IO Repo+hRead repo h = do+	val <- hGetContentsStrict h+	store val repo++{- Stores a git config into a repo, returning the new version of the repo.+ - The git config may be multiple lines, or a single line. Config settings+ - can be updated inrementally. -}+store :: String -> Repo -> IO Repo+store s repo = do+	let c = parse s+	let repo' = repo+		{ config = (M.map Prelude.head c) `M.union` config repo+		, fullconfig = M.unionWith (++) c (fullconfig repo)+		}+	rs <- Git.Construct.fromRemotes repo'+	return $ repo' { remotes = rs }++{- Parses git config --list or git config --null --list output into a+ - config map. -}+parse :: String -> M.Map String [String]+parse [] = M.empty+parse s+	-- --list output will have an = in the first line+	| all ('=' `elem`) (take 1 ls) = sep '=' ls+	-- --null --list output separates keys from values with newlines+	| otherwise = sep '\n' $ split "\0" s+	where+		ls = lines s+		sep c = M.fromListWith (++) . map (\(k,v) -> (k, [v])) .+			map (separate (== c))
+ Git/Construct.hs view
@@ -0,0 +1,219 @@+{- Construction of Git Repo objects+ -+ - Copyright 2010,2011 Joey Hess <joey@kitenet.net>+ -+ - Licensed under the GNU GPL version 3 or higher.+ -}++module Git.Construct (+	fromCwd,+	fromAbsPath,+	fromUrl,+	fromUnknown,+	localToUrl,+	remoteNamed,+	remoteNamedFromKey,+	fromRemotes,+	fromRemoteLocation,+	repoAbsPath,+) where++import System.Posix.User+import qualified Data.Map as M hiding (map, split)+import Network.URI++import Common+import Git.Types+import Git+import qualified Git.Url as Url++{- Finds the current git repository, which may be in a parent directory. -}+fromCwd :: IO Repo+fromCwd = getCurrentDirectory >>= seekUp isRepoTop >>= maybe norepo makerepo+	where+		makerepo = return . newFrom . Dir+		norepo = error "Not in a git repository."++{- Local Repo constructor, requires an absolute path to the repo be+ - specified. -}+fromAbsPath :: FilePath -> IO Repo+fromAbsPath dir+	| "/" `isPrefixOf` dir = do+ 		-- Git always looks for "dir.git" in preference to+		-- to "dir", even if dir ends in a "/".+		let canondir = dropTrailingPathSeparator dir+		let dir' = canondir ++ ".git"+		e <- doesDirectoryExist dir'+		if e+			then ret dir'+			else if "/.git" `isSuffixOf` canondir+				then do+					-- When dir == "foo/.git", git looks+					-- for "foo/.git/.git", and failing+					-- that, uses "foo" as the repository.+					e' <- doesDirectoryExist $ dir </> ".git"+					if e'+						then ret dir+						else ret $ takeDirectory canondir+				else ret dir+	| otherwise = error $ "internal error, " ++ dir ++ " is not absolute"+	where+		ret = return . newFrom . Dir++{- Remote Repo constructor. Throws exception on invalid url. -}+fromUrl :: String -> IO Repo+fromUrl url+	| startswith "file://" url = fromAbsPath $ uriPath u+	| otherwise = return $ newFrom $ Url u+		where+			u = fromMaybe bad $ parseURI url+			bad = error $ "bad url " ++ url++{- Creates a repo that has an unknown location. -}+fromUnknown :: IO Repo+fromUnknown = return $ newFrom Unknown++{- Converts a local Repo into a remote repo, using the reference repo+ - which is assumed to be on the same host. -}+localToUrl :: Repo -> Repo -> Repo+localToUrl reference r+	| not $ repoIsUrl reference = error "internal error; reference repo not url"+	| repoIsUrl r = r+	| otherwise = r { location = Url $ fromJust $ parseURI absurl }+	where+		absurl =+			Url.scheme reference ++ "//" +++			Url.authority reference +++			workTree r++{- Calculates a list of a repo's configured remotes, by parsing its config. -}+fromRemotes :: Repo -> IO [Repo]+fromRemotes repo = mapM construct remotepairs+	where+		filterconfig f = filter f $ M.toList $ config repo+		filterkeys f = filterconfig (\(k,_) -> f k)+		remotepairs = filterkeys isremote+		isremote k = startswith "remote." k && endswith ".url" k+		construct (k,v) = remoteNamedFromKey k $ fromRemoteLocation v repo++{- Sets the name of a remote when constructing the Repo to represent it. -}+remoteNamed :: String -> IO Repo -> IO Repo+remoteNamed n constructor = do+	r <- constructor+	return $ r { remoteName = Just n }++{- Sets the name of a remote based on the git config key, such as+   "remote.foo.url". -}+remoteNamedFromKey :: String -> IO Repo -> IO Repo+remoteNamedFromKey k = remoteNamed basename+	where+		basename = join "." $ reverse $ drop 1 $+				reverse $ drop 1 $ split "." k++{- Constructs a new Repo for one of a Repo's remotes using a given+ - location (ie, an url). -}+fromRemoteLocation :: String -> Repo -> IO Repo+fromRemoteLocation s repo = gen $ calcloc s+	where+		gen v	+			| scpstyle v = fromUrl $ scptourl v+			| isURI v = fromUrl v+			| otherwise = fromRemotePath v repo+		-- insteadof config can rewrite remote location+		calcloc l+			| null insteadofs = l+			| otherwise = replacement ++ drop (length bestvalue) l+			where+				replacement = drop (length prefix) $+					take (length bestkey - length suffix) bestkey+				(bestkey, bestvalue) = maximumBy longestvalue insteadofs+				longestvalue (_, a) (_, b) = compare b a+				insteadofs = filterconfig $ \(k, v) -> +					startswith prefix k &&+					endswith suffix k &&+					startswith v l+				filterconfig f = filter f $+					concatMap splitconfigs $+						M.toList $ fullconfig repo+				splitconfigs (k, vs) = map (\v -> (k, v)) vs+				(prefix, suffix) = ("url." , ".insteadof")+		-- git remotes can be written scp style -- [user@]host:dir+		scpstyle v = ":" `isInfixOf` v && not ("//" `isInfixOf` v)+		scptourl v = "ssh://" ++ host ++ slash dir+			where+				(host, dir) = separate (== ':') v+				slash d	| d == "" = "/~/" ++ d+					| "/" `isPrefixOf` d = d+					| "~" `isPrefixOf` d = '/':d+					| otherwise = "/~/" ++ d++{- Constructs a Repo from the path specified in the git remotes of+ - another Repo. -}+fromRemotePath :: FilePath -> Repo -> IO Repo+fromRemotePath dir repo = do+	dir' <- expandTilde dir+	fromAbsPath $ workTree repo </> dir'++{- Git remotes can have a directory that is specified relative+ - to the user's home directory, or that contains tilde expansions.+ - This converts such a directory to an absolute path.+ - Note that it has to run on the system where the remote is.+ -}+repoAbsPath :: FilePath -> IO FilePath+repoAbsPath d = do+	d' <- expandTilde d+	h <- myHomeDir+	return $ h </> d'++expandTilde :: FilePath -> IO FilePath+expandTilde = expandt True+	where+		expandt _ [] = return ""+		expandt _ ('/':cs) = do+			v <- expandt True cs+			return ('/':v)+		expandt True ('~':'/':cs) = do+			h <- myHomeDir+			return $ h </> cs+		expandt True ('~':cs) = do+			let (name, rest) = findname "" cs+			u <- getUserEntryForName name+			return $ homeDirectory u </> rest+		expandt _ (c:cs) = do+			v <- expandt False cs+			return (c:v)+		findname n [] = (n, "")+		findname n (c:cs)+			| c == '/' = (n, cs)+			| otherwise = findname (n++[c]) cs++seekUp :: (FilePath -> IO Bool) -> FilePath -> IO (Maybe FilePath)+seekUp want dir = do+	ok <- want dir+	if ok+		then return $ Just dir+		else case parentDir dir of+			"" -> return Nothing+			d -> seekUp want d++isRepoTop :: FilePath -> IO Bool+isRepoTop dir = do+	r <- isRepo+	b <- isBareRepo+	return (r || b)+	where+		isRepo = gitSignature ".git" ".git/config"+		isBareRepo = gitSignature "objects" "config"+		gitSignature subdir file = liftM2 (&&)+			(doesDirectoryExist (dir ++ "/" ++ subdir))+			(doesFileExist (dir ++ "/" ++ file))++newFrom :: RepoLocation -> Repo+newFrom l = +	Repo {+		location = l,+		config = M.empty,+		fullconfig = M.empty,+		remotes = [],+		remoteName = Nothing+	}
+ Git/Filename.hs view
@@ -0,0 +1,28 @@+{- Some git commands output encoded filenames, in a rather annoyingly complex+ - C-style encoding.+ -+ - Copyright 2010, 2011 Joey Hess <joey@kitenet.net>+ -+ - Licensed under the GNU GPL version 3 or higher.+ -}++module Git.Filename where++import Utility.Format (decode_c, encode_c)++import Common++decode :: String -> FilePath+decode [] = []+decode f@(c:s)+	-- encoded strings will be inside double quotes+	| c == '"' && end s == ['"'] = decode_c $ beginning s+	| otherwise = f++{- Should not need to use this, except for testing decode. -}+encode :: FilePath -> String+encode s = "\"" ++ encode_c s ++ "\""++{- for quickcheck -}+prop_idempotent_deencode :: String -> Bool+prop_idempotent_deencode s = s == decode (encode s)
+ Git/HashObject.hs view
@@ -0,0 +1,32 @@+{- git hash-object interface+ -+ - Copyright 2011 Joey Hess <joey@kitenet.net>+ -+ - Licensed under the GNU GPL version 3 or higher.+ -}++module Git.HashObject where++import Common+import Git+import Git.Command++{- Injects a set of files into git, returning the shas of the objects+ - and an IO action to call ones the the shas have been used. -}+hashFiles :: [FilePath] -> Repo -> IO ([Sha], IO ())+hashFiles paths repo = do+	(pid, fromh, toh) <- hPipeBoth "git" $ toCommand $ git_hash_object repo+	_ <- forkProcess (feeder toh)+	hClose toh+	shas <- map Ref . lines <$> hGetContentsStrict fromh+	return (shas, ender fromh pid)+	where+		git_hash_object = gitCommandLine+			[Param "hash-object", Param "-w", Param "--stdin-paths"]+		feeder toh = do+			hPutStr toh $ unlines paths+			hClose toh+			exitSuccess+		ender fromh pid = do+			hClose fromh+			forceSuccess pid
+ Git/Index.hs view
@@ -0,0 +1,24 @@+{- git index file stuff+ -+ - Copyright 2011 Joey Hess <joey@kitenet.net>+ -+ - Licensed under the GNU GPL version 3 or higher.+ -}++module Git.Index where++import System.Posix.Env (setEnv, unsetEnv, getEnv)++{- Forces git to use the specified index file.+ -+ - Returns an action that will reset back to the default+ - index file. -}+override :: FilePath -> IO (IO ())+override index = do+	res <- getEnv var+	setEnv var index True+	return $ reset res+	where+		var = "GIT_INDEX_FILE"+		reset (Just v) = setEnv var v True+		reset _ = unsetEnv var
Git/LsFiles.hs view
@@ -15,8 +15,9 @@ 	typeChangedStaged, ) where +import Common import Git-import Utility.SafeCommand+import Git.Command  {- Scans for files that are checked into git at the specified locations. -} inRepo :: [FilePath] -> Repo -> IO [FilePath]@@ -42,10 +43,10 @@ stagedNotDeleted = staged' [Param "--diff-filter=ACMRT"]  staged' :: [CommandParam] -> [FilePath] -> Repo -> IO [FilePath]-staged' middle l = pipeNullSplit $ start ++ middle ++ end+staged' ps l = pipeNullSplit $ prefix ++ ps ++ suffix 	where-		start = [Params "diff --cached --name-only -z"]-		end = Param "--" : map File l+		prefix = [Params "diff --cached --name-only -z"]+		suffix = Param "--" : map File l  {- Returns a list of files that have unstaged changes. -} changedUnstaged :: [FilePath] -> Repo -> IO [FilePath]@@ -64,7 +65,7 @@ typeChanged = typeChanged' []  typeChanged' :: [CommandParam] -> [FilePath] -> Repo -> IO [FilePath]-typeChanged' middle l = pipeNullSplit $ start ++ middle ++ end+typeChanged' ps l = pipeNullSplit $ prefix ++ ps ++ suffix 	where-		start = [Params "diff --name-only --diff-filter=T -z"]-		end = Param "--" : map File l+		prefix = [Params "diff --name-only --diff-filter=T -z"]+		suffix = Param "--" : map File l
Git/LsTree.hs view
@@ -16,8 +16,10 @@ import System.Posix.Types import qualified Data.ByteString.Lazy.Char8 as L +import Common import Git-import Utility.SafeCommand+import Git.Command+import qualified Git.Filename  data TreeItem = TreeItem 	{ mode :: FileMode@@ -35,10 +37,10 @@  - (The --long format is not currently supported.) -} parseLsTree :: L.ByteString -> TreeItem parseLsTree l = TreeItem -	{ mode = fst $ head $ readOct $ L.unpack m+	{ mode = fst $ Prelude.head $ readOct $ L.unpack m 	, typeobj = L.unpack t 	, sha = L.unpack s-	, file = decodeGitFile $ L.unpack f+	, file = Git.Filename.decode $ L.unpack f 	} 	where 		-- l = <mode> SP <type> SP <sha> TAB <file>
Git/Queue.hs view
@@ -7,7 +7,7 @@  module Git.Queue ( 	Queue,-	empty,+	new, 	add, 	size, 	full,@@ -18,10 +18,11 @@ import System.IO import System.Cmd.Utils import Data.String.Utils-import Control.Monad (forM_) import Utility.SafeCommand +import Common import Git+import Git.Command  {- An action to perform in a git repository. The file to act on  - is not included, and must be able to be appended after the params. -}@@ -49,8 +50,8 @@ maxSize = 10240  {- Constructor for empty queue. -}-empty :: Queue-empty = Queue 0 M.empty+new :: Queue+new = Queue 0 M.empty  {- Adds an action to a queue. -} add :: Queue -> String -> [CommandParam] -> [FilePath] -> Queue@@ -75,7 +76,7 @@ flush :: Queue -> Repo -> IO Queue flush (Queue _ m) repo = do 	forM_ (M.toList m) $ uncurry $ runAction repo-	return empty+	return new  {- Runs an Action on a list of files in a git repository.  -
+ Git/Ref.hs view
@@ -0,0 +1,65 @@+{- git ref stuff+ -+ - Copyright 2011 Joey Hess <joey@kitenet.net>+ -+ - Licensed under the GNU GPL version 3 or higher.+ -}++module Git.Ref where++import qualified Data.ByteString.Lazy.Char8 as L++import Common+import Git+import Git.Command++{- Converts a fully qualified git ref into a user-visible string. -}+describe :: Ref -> String+describe = show . base++{- Often git refs are fully qualified (eg: refs/heads/master).+ - Converts such a fully qualified ref into a base ref (eg: master). -}+base :: Ref -> Ref+base = Ref . remove "refs/heads/" . remove "refs/remotes/" . show+	where+		remove prefix s+			| prefix `isPrefixOf` s = drop (length prefix) s+			| otherwise = s+++{- Given a directory such as "refs/remotes/origin", and a ref such as+ - refs/heads/master, yields a version of that ref under the directory,+ - such as refs/remotes/origin/master. -}+under :: String -> Ref -> Ref+under dir r = Ref $ dir </> show (base r)++{- Checks if a ref exists. -}+exists :: Ref -> Repo -> IO Bool+exists ref = runBool "show-ref" +	[Param "--verify", Param "-q", Param $ show ref]++{- Get the sha of a fully qualified git ref, if it exists. -}+sha :: Branch -> Repo -> IO (Maybe Sha)+sha branch repo = process . L.unpack <$> showref repo+	where+		showref = pipeRead [Param "show-ref",+			Param "--hash", -- get the hash+			Param $ show branch]+		process [] = Nothing+		process s = Just $ Ref $ firstLine s++{- List of (refs, branches) matching a given ref spec. -}+matching :: Ref -> Repo -> IO [(Ref, Branch)]+matching ref repo = do+	r <- pipeRead [Param "show-ref", Param $ show ref] repo+	return $ map (gen . L.unpack) (L.lines r)+	where+		gen l = let (r, b) = separate (== ' ') l in+			(Ref r, Ref b)++{- List of (refs, branches) matching a given ref spec.+ - Duplicate refs are filtered out. -}+matchingUniq :: Ref -> Repo -> IO [(Ref, Branch)]+matchingUniq ref repo = nubBy uniqref <$> matching ref repo+	where+		uniqref (a, _) (b, _) = a == b
+ Git/Sha.hs view
@@ -0,0 +1,36 @@+{- git SHA stuff+ -+ - Copyright 2011 Joey Hess <joey@kitenet.net>+ -+ - Licensed under the GNU GPL version 3 or higher.+ -}++module Git.Sha where++import Common+import Git.Types++{- Runs an action that causes a git subcommand to emit a Sha, and strips+   any trailing newline, returning the sha. -}+getSha :: String -> IO String -> IO Sha+getSha subcommand a = maybe bad return =<< extractSha <$> a+	where+		bad = error $ "failed to read sha from git " ++ subcommand++{- Extracts the Sha from a string. There can be a trailing newline after+ - it, but nothing else. -}+extractSha :: String -> Maybe Sha+extractSha s+	| len == shaSize = val s+	| len == shaSize + 1 && length s' == shaSize = val s'+	| otherwise = Nothing+	where+		len = length s+		s' = firstLine s+		val v+			| all (`elem` "1234567890ABCDEFabcdef") v = Just $ Ref v+			| otherwise = Nothing++{- Size of a git sha. -}+shaSize :: Int+shaSize = 40
+ Git/Types.hs view
@@ -0,0 +1,38 @@+{- git data types+ -+ - Copyright 2010,2011 Joey Hess <joey@kitenet.net>+ -+ - Licensed under the GNU GPL version 3 or higher.+ -}++module Git.Types where++import Network.URI+import qualified Data.Map as M++{- There are two types of repositories; those on local disk and those+ - accessed via an URL. -}+data RepoLocation = Dir FilePath | Url URI | Unknown+	deriving (Show, Eq)++data Repo = Repo {+	location :: RepoLocation,+	config :: M.Map String String,+	-- a given git config key can actually have multiple values+	fullconfig :: M.Map String [String],+	remotes :: [Repo],+	-- remoteName holds the name used for this repo in remotes+	remoteName :: Maybe String +} deriving (Show, Eq)++{- A git ref. Can be a sha1, or a branch or tag name. -}+newtype Ref = Ref String+	deriving (Eq)++instance Show Ref where+	show (Ref v) = v++{- Aliases for Ref. -}+type Branch = Ref+type Sha = Ref+type Tag = Ref
Git/UnionMerge.hs view
@@ -15,19 +15,21 @@ ) where  import System.Cmd.Utils-import Data.List import qualified Data.ByteString.Lazy.Char8 as L+import qualified Data.Set as S  import Common import Git+import Git.Sha import Git.CatFile+import Git.Command  type Streamer = (String -> IO ()) -> IO ()  {- Performs a union merge between two branches, staging it in the index.  - Any previously staged changes in the index will be lost.  -- - Should be run with a temporary index file configured by Git.useIndex.+ - Should be run with a temporary index file configured by useIndex.  -} merge :: Ref -> Ref -> Repo -> IO () merge x y repo = do@@ -53,7 +55,7 @@ {- Streams content into update-index. -} stream_update_index :: Repo -> [Streamer] -> IO () stream_update_index repo as = do-	(p, h) <- hPipeTo "git" (toCommand $ Git.gitCommandLine params repo)+	(p, h) <- hPipeTo "git" (toCommand $ gitCommandLine params repo) 	forM_ as (stream h) 	hClose h 	forceSuccess p@@ -103,19 +105,18 @@ mergeFile :: String -> FilePath -> CatFileHandle -> Repo -> IO (Maybe String) mergeFile info file h repo = case filter (/= nullsha) [Ref asha, Ref bsha] of 	[] -> return Nothing-	(sha:[]) -> return $ Just $ update_index_line sha file-	shas -> do-		content <- L.concat <$> mapM (catObject h) shas-		sha <- hashObject (unionmerge content) repo-		return $ Just $ update_index_line sha file+	(sha:[]) -> use sha+	shas -> use =<< either return (hashObject repo . L.unlines) =<<+		calcMerge . zip shas <$> mapM getcontents shas 	where-		[_colonamode, _bmode, asha, bsha, _status] = words info+		[_colonmode, _bmode, asha, bsha, _status] = words info 		nullsha = Ref $ replicate shaSize '0'-		unionmerge = L.unlines . nub . L.lines+		getcontents s = L.lines <$> catObject h s+		use sha = return $ Just $ update_index_line sha file  {- Injects some content into git, returning its Sha. -}-hashObject :: L.ByteString -> Repo -> IO Sha-hashObject content repo = getSha subcmd $ do+hashObject :: Repo -> L.ByteString -> IO Sha+hashObject repo content = getSha subcmd $ do 	(h, s) <- pipeWriteRead (map Param params) content repo 	L.length s `seq` do 		forceSuccess h@@ -124,3 +125,17 @@ 	where 		subcmd = "hash-object" 		params = [subcmd, "-w", "--stdin"]++{- Calculates a union merge between a list of refs, with contents.+ -+ - When possible, reuses the content of an existing ref, rather than+ - generating new content.+ -}+calcMerge :: [(Ref, [L.ByteString])] -> Either Ref [L.ByteString]+calcMerge shacontents+	| null reuseable = Right $ new+	| otherwise = Left $ fst $ Prelude.head reuseable+	where+		reuseable = filter (\c -> sorteduniq (snd c) == new) shacontents+		new = sorteduniq $ concat $ map snd shacontents+		sorteduniq = S.toList . S.fromList
+ Git/Url.hs view
@@ -0,0 +1,70 @@+{- git repository urls+ -+ - Copyright 2010, 2011 Joey Hess <joey@kitenet.net>+ -+ - Licensed under the GNU GPL version 3 or higher.+ -}++module Git.Url (+	scheme,+	host,+	port,+	hostuser,+	authority,+) where++import Network.URI hiding (scheme, authority)++import Common+import Git.Types+import Git++{- Scheme of an URL repo. -}+scheme :: Repo -> String+scheme Repo { location = Url u } = uriScheme u+scheme repo = notUrl repo++{- Work around a bug in the real uriRegName+ - <http://trac.haskell.org/network/ticket/40> -}+uriRegName' :: URIAuth -> String+uriRegName' a = fixup $ uriRegName a+	where+		fixup x@('[':rest)+			| rest !! len == ']' = take len rest+			| otherwise = x+			where+				len  = length rest - 1+		fixup x = x++{- Hostname of an URL repo. -}+host :: Repo -> String+host = authpart uriRegName'++{- Port of an URL repo, if it has a nonstandard one. -}+port :: Repo -> Maybe Integer+port r = +	case authpart uriPort r of+		":" -> Nothing+		(':':p) -> readMaybe p+		_ -> Nothing++{- Hostname of an URL repo, including any username (ie, "user@host") -}+hostuser :: Repo -> String+hostuser r = authpart uriUserInfo r ++ authpart uriRegName' r++{- The full authority portion an URL repo. (ie, "user@host:port") -}+authority :: Repo -> String+authority = authpart assemble+	where+		assemble a = uriUserInfo a ++ uriRegName' a ++ uriPort a++{- Applies a function to extract part of the uriAuthority of an URL repo. -}+authpart :: (URIAuth -> a) -> Repo -> a+authpart a Repo { location = Url u } = a auth+	where+		auth = fromMaybe (error $ "bad url " ++ show u) (uriAuthority u)+authpart _ repo = notUrl repo++notUrl :: Repo -> a+notUrl repo = error $+	"acting on local git repo " ++ repoDescribe repo ++ " not supported"
+ Git/Version.hs view
@@ -0,0 +1,38 @@+{- git version checking+ -+ - Copyright 2011 Joey Hess <joey@kitenet.net>+ -+ - Licensed under the GNU GPL version 3 or higher.+ -}++module Git.Version where++import Common+import qualified Build.SysConfig++{- Using the version it was configured for avoids running git to check its+ - version, at the cost that upgrading git won't be noticed.+ - This is only acceptable because it's rare that git's version influences+ - code's behavior. -}+version :: String+version = Build.SysConfig.gitversion++older :: String -> Bool+older v = normalize version < normalize v++{- To compare dotted versions like 1.7.7 and 1.8, they are normalized to+ - a somewhat arbitrary integer representation. -}+normalize :: String -> Integer+normalize = sum . mult 1 . reverse .+		extend precision . take precision .+		map readi . split "."+	where+		extend n l = l ++ replicate (n - length l) 0+		mult _ [] = []+		mult n (x:xs) = (n*x) : mult (n*10^width) xs+		readi :: String -> Integer+		readi s = case reads s of+			((x,_):_) -> x+			_ -> 0+		precision = 10 -- number of segments of the version to compare+		width = length "yyyymmddhhmmss" -- maximum width of a segment
GitAnnex.hs view
@@ -10,13 +10,15 @@ import System.Console.GetOpt  import Common.Annex-import qualified Git+import qualified Git.Config+import qualified Git.Construct import CmdLine import Command import Types.TrustLevel import qualified Annex import qualified Remote import qualified Limit+import qualified Utility.Format  import qualified Command.Add import qualified Command.Unannex@@ -107,10 +109,14 @@ 		"override trust setting to untrusted" 	, Option ['c'] ["config"] (ReqArg setgitconfig "NAME=VALUE") 		"override git configuration setting"-	, Option [] ["print0"] (NoArg (setprint0 True))-		"terminate filename with null"+	, Option [] ["print0"] (NoArg setprint0)+		"terminate output with null"+	, Option [] ["format"] (ReqArg setformat paramFormat)+		"control format of output" 	, Option ['x'] ["exclude"] (ReqArg Limit.addExclude paramGlob) 		"skip files matching the glob pattern"+	, Option ['I'] ["include"] (ReqArg Limit.addInclude paramGlob)+		"don't skip files matching the glob pattern" 	, Option ['i'] ["in"] (ReqArg Limit.addIn paramRemote) 		"skip files not present in a remote" 	, Option ['C'] ["copies"] (ReqArg Limit.addCopies paramNumber)@@ -122,14 +128,15 @@ 		setto v = Annex.changeState $ \s -> s { Annex.toremote = Just v } 		setfrom v = Annex.changeState $ \s -> s { Annex.fromremote = Just v } 		setnumcopies v = Annex.changeState $ \s -> s {Annex.forcenumcopies = readMaybe v }-		setprint0 v = Annex.changeState $ \s -> s { Annex.print0 = v }+		setformat v = Annex.changeState $ \s -> s { Annex.format = Just $ Utility.Format.gen v }+		setprint0 = setformat "${file}\0" 		setgitconfig :: String -> Annex () 		setgitconfig v = do-			newg <- inRepo $ Git.configStore v+			newg <- inRepo $ Git.Config.store v 			Annex.changeState $ \s -> s { Annex.repo = newg }  header :: String header = "Usage: git-annex command [option ..]"  run :: [String] -> IO ()-run args = dispatch args cmds options header Git.repoFromCwd+run args = dispatch args cmds options header Git.Construct.fromCwd
INSTALL view
@@ -5,6 +5,7 @@ * [[Ubuntu]] * [[Fedora]] * [[FreeBSD]]+* [[openSUSE]]  ## Using cabal @@ -24,6 +25,7 @@   * [SHA](http://hackage.haskell.org/package/SHA)   * [dataenc](http://hackage.haskell.org/package/dataenc)   * [monad-control](http://hackage.haskell.org/package/monad-control)+  * [lifted-base](http://hackage.haskell.org/package/lifted-base)   * [TestPack](http://hackage.haskell.org/cgi-bin/hackage-scripts/package/testpack)   * [QuickCheck 2](http://hackage.haskell.org/package/QuickCheck)   * [HTTP](http://hackage.haskell.org/package/HTTP)
Init.hs view
@@ -39,7 +39,7 @@ ensureInitialized = getVersion >>= maybe needsinit checkVersion 	where 		needsinit = do-			annexed <- Annex.Branch.hasSomeBranch+			annexed <- Annex.Branch.hasSibling 			if annexed 				then initialize Nothing 				else error "First run: git-annex init"
Limit.hs view
@@ -55,10 +55,16 @@ addLimit = add . Utility.Matcher.Operation  {- Add a limit to skip files that do not match the glob. -}+addInclude :: String -> Annex ()+addInclude glob = addLimit $ return . matchglob glob++{- Add a limit to skip files that match the glob. -} addExclude :: String -> Annex ()-addExclude glob = addLimit $ return . notExcluded+addExclude glob = addLimit $ return . not . matchglob glob++matchglob :: String -> FilePath -> Bool+matchglob glob f = isJust $ match cregex f [] 	where-		notExcluded f = isNothing $ match cregex f [] 		cregex = compile regex [] 		regex = '^':wildToRegex glob 
Locations.hs view
@@ -90,7 +90,8 @@ 		return $ inrepo ".git" </> annexLocation key hashDirMixed 	where 		inrepo d = Git.workTree r </> d-		check locs = fromMaybe (head locs) <$> firstM doesFileExist locs+		check locs@(l:_) = fromMaybe l <$> firstM doesFileExist locs+		check [] = error "internal"  {- The annex directory of a repository. -} gitAnnexDir :: Git.Repo -> FilePath
Logs/Location.hs view
@@ -68,7 +68,7 @@ {- Converts a log filename into a key. -} logFileKey :: FilePath -> Maybe Key logFileKey file-	| end == ".log" = fileKey beginning+	| ext == ".log" = fileKey base 	| otherwise = Nothing 	where-		(beginning, end) = splitAt (length file - 4) file+		(base, ext) = splitAt (length file - 4) file
Logs/Remote.hs view
@@ -73,14 +73,13 @@ 			| c == '&' = entity rest 			| otherwise = c : unescape rest 		entity s = if ok-				then chr (read num) : unescape rest+				then chr (Prelude.read num) : unescape rest 				else '&' : unescape s 			where 				num = takeWhile isNumber s 				r = drop (length num) s 				rest = drop 1 r-				ok = not (null num) && -					not (null r) && head r == ';'+				ok = not (null num) && take 1 r == ";"  {- for quickcheck -} prop_idempotent_configEscape :: String -> Bool
Logs/Trust.hs view
@@ -54,18 +54,16 @@ 		Just m -> return m 		Nothing -> do 			overrides <- M.fromList <$> Annex.getState Annex.forcetrust-			m <- (M.union overrides . simpleMap . parseLog parseTrust) <$>+			m <- (M.union overrides . simpleMap . parseLog (Just . parseTrust)) <$> 				Annex.Branch.get trustLog 			Annex.changeState $ \s -> s { Annex.trustmap = Just m } 			return m -parseTrust :: String -> Maybe TrustLevel-parseTrust s-	| length w > 0 = Just $ parse $ head w-	-- back-compat; the trust.log used to only list trusted repos-	| otherwise = Just Trusted+{- The trust.log used to only list trusted repos, without a field for the+ - trust status, which is why this defaults to Trusted. -}+parseTrust :: String -> TrustLevel+parseTrust s = maybe Trusted parse $ headMaybe $ words s 	where-		w = words s 		parse "1" = Trusted 		parse "0" = UnTrusted 		parse "X" = DeadTrusted@@ -82,6 +80,6 @@ trustSet uuid@(UUID _) level = do 	ts <- liftIO getPOSIXTime 	Annex.Branch.change trustLog $-		showLog showTrust . changeLog ts uuid level . parseLog parseTrust+		showLog showTrust . changeLog ts uuid level . parseLog (Just . parseTrust) 	Annex.changeState $ \s -> s { Annex.trustmap = Nothing } trustSet NoUUID _ = error "unknown UUID; cannot modify trust level"
Logs/UUID.hs view
@@ -57,9 +57,9 @@ 				kuuid = fromUUID k 				isbad = not (isuuid kuuid) && isuuid lastword 				ws = words $ value v-				lastword = last ws+				lastword = Prelude.last ws 				fixeduuid = toUUID lastword-				fixedvalue = unwords $ kuuid: init ws+				fixedvalue = unwords $ kuuid: Prelude.init ws 		-- For the fixed line to take precidence, it should be 		-- slightly newer, but only slightly. 		newertime (LogEntry (Date d) _) = d + minimumPOSIXTimeSlice
Logs/UUIDBased.hs view
@@ -64,15 +64,15 @@ 			where 				makepair v = Just (toUUID u, LogEntry ts v) 				ws = words line-				u = head ws-				end = last ws+				u = Prelude.head ws+				t = Prelude.last ws 				ts-					| tskey `isPrefixOf` end =-						pdate $ tail $ dropWhile (/= '=') end+					| tskey `isPrefixOf` t =+						pdate $ drop 1 $ dropWhile (/= '=') t 					| otherwise = Unknown 				info 					| ts == Unknown = drop 1 ws-					| otherwise = drop 1 $ init ws+					| otherwise = drop 1 $ beginning ws 				pdate s = case parseTime defaultTimeLocale "%s%Qs" s of 					Nothing -> Unknown 					Just d -> Date $ utcTimeToPOSIXSeconds d
Makefile view
@@ -1,6 +1,6 @@ PREFIX=/usr IGNORE=-ignore-package monads-fd-GHCFLAGS=-O2 -Wall $(IGNORE) -fspec-constr-count=5+GHCFLAGS=-O2 -Wall $(IGNORE) -fspec-constr-count=8  ifdef PROFILE GHCFLAGS=-prof -auto-all -rtsopts -caf-all -fforce-recomp $(IGNORE)@@ -64,7 +64,7 @@ 		rsync -a --delete html/ $(DESTDIR)$(PREFIX)/share/doc/git-annex/html/; \ 	fi -test: $(bins)+test: 	@if ! $(GHCMAKE) -O0 test; then \ 		echo "** not running test suite" >&2; \ 	else \@@ -74,13 +74,14 @@ 		fi; \ 	fi -testcoverage: $(bins)+testcoverage: 	rm -f test.tix test 	ghc -odir build/test -hidir build/test $(GHCFLAGS) --make -fhpc test 	./test 	@echo "" 	@hpc report test --exclude=Main --exclude=QC 	@hpc markup test --exclude=Main --exclude=QC --destdir=.hpc >/dev/null+	@echo "(See .hpc/ for test coverage details.)"  # If ikiwiki is available, build static html docs suitable for being # shipped in the software package.
Messages.hs view
@@ -20,6 +20,7 @@ 	warning, 	indent, 	maybeShowJSON,+	showFullJSON, 	showCustom, 	showHeader, 	showRaw,@@ -90,9 +91,16 @@ indent :: String -> String indent = join "\n" . map (\l -> "  " ++ l) . lines -{- Shows a JSON value only when in json mode. -}+{- Shows a JSON fragment only when in json mode. -} maybeShowJSON :: JSON a => [(String, a)] -> Annex () maybeShowJSON v = handle (JSON.add v) q++{- Shows a complete JSON value, only when in json mode. -}+showFullJSON :: JSON a => [(String, a)] -> Annex Bool+showFullJSON v = Annex.getState Annex.output >>= liftIO . go+	where+		go Annex.JSONOutput = JSON.complete v >> return True+		go _ = return False  {- Performs an action that outputs nonstandard/customized output, and  - in JSON mode wraps its output in JSON.start and JSON.end, so it's
Messages/JSON.hs view
@@ -9,7 +9,8 @@ 	start, 	end, 	note,-	add+	add,+	complete ) where  import Text.JSON@@ -31,3 +32,9 @@  add :: JSON a => [(String, a)] -> IO () add v = putStr $ Stream.add v++complete :: JSON a => [(String, a)] -> IO ()+complete v = putStr $ concat+	[ Stream.start v+	, Stream.end+	]
Options.hs view
@@ -82,6 +82,8 @@ paramUUID = "UUID" paramType :: String paramType = "TYPE"+paramFormat :: String+paramFormat = "FORMAT" paramKeyValue :: String paramKeyValue = "K=V" paramNothing :: String
Remote.hs view
@@ -16,6 +16,8 @@ 	hasKeyCheap,  	remoteTypes,+	remoteList,+	enabledRemoteList, 	remoteMap, 	byName, 	prettyPrintUUIDs,@@ -52,7 +54,7 @@ import qualified Remote.Web import qualified Remote.Hook -remoteTypes :: [RemoteType Annex]+remoteTypes :: [RemoteType] remoteTypes = 	[ Remote.Git.remote 	, Remote.S3.remote@@ -65,8 +67,8 @@  {- Builds a list of all available Remotes.  - Since doing so can be expensive, the list is cached. -}-genList :: Annex [Remote Annex]-genList = do+remoteList :: Annex [Remote]+remoteList = do 	rs <- Annex.getState Annex.remotes 	if null rs 		then do@@ -84,26 +86,29 @@ 			u <- getRepoUUID r 			generate t r u (M.lookup u m) +{- All remotes that are not ignored. -}+enabledRemoteList :: Annex [Remote]+enabledRemoteList = filterM (repoNotIgnored . repo) =<< remoteList+ {- Map of UUIDs of Remotes and their names. -} remoteMap :: Annex (M.Map UUID String)-remoteMap = M.fromList . map (\r -> (uuid r, name r)) <$> genList+remoteMap = M.fromList . map (\r -> (uuid r, name r)) <$> remoteList  {- Looks up a remote by name. (Or by UUID.) Only finds currently configured  - git remotes. -}-byName :: String -> Annex (Remote Annex)+byName :: String -> Annex (Remote) byName n = do 	res <- byName' n 	case res of 		Left e -> error e 		Right r -> return r-byName' :: String -> Annex (Either String (Remote Annex))+byName' :: String -> Annex (Either String Remote) byName' "" = return $ Left "no remote specified" byName' n = do-	allremotes <- genList-	let match = filter matching allremotes+	match <- filter matching <$> remoteList 	if null match 		then return $ Left $ "there is no git remote named \"" ++ n ++ "\""-		else return $ Right $ head match+		else return $ Right $ Prelude.head match 	where 		matching r = n == name r || toUUID n == uuid r @@ -112,6 +117,7 @@  - .git/config. -} nameToUUID :: String -> Annex UUID nameToUUID "." = getUUID -- special case for current repo+nameToUUID "" = error "no remote specified" nameToUUID n = byName' n >>= go 	where 		go (Right r) = return $ uuid r@@ -162,16 +168,16 @@ 			]  {- Filters a list of remotes to ones that have the listed uuids. -}-remotesWithUUID :: [Remote Annex] -> [UUID] -> [Remote Annex]+remotesWithUUID :: [Remote] -> [UUID] -> [Remote] remotesWithUUID rs us = filter (\r -> uuid r `elem` us) rs  {- Filters a list of remotes to ones that do not have the listed uuids. -}-remotesWithoutUUID :: [Remote Annex] -> [UUID] -> [Remote Annex]+remotesWithoutUUID :: [Remote] -> [UUID] -> [Remote] remotesWithoutUUID rs us = filter (\r -> uuid r `notElem` us) rs  {- Cost ordered lists of remotes that the Logs.Location indicate may have a key.  -}-keyPossibilities :: Key -> Annex [Remote Annex]+keyPossibilities :: Key -> Annex [Remote] keyPossibilities key = fst <$> keyPossibilities' False key  {- Cost ordered lists of remotes that the Logs.Location indicate may have a key.@@ -179,10 +185,10 @@  - Also returns a list of UUIDs that are trusted to have the key  - (some may not have configured remotes).  -}-keyPossibilitiesTrusted :: Key -> Annex ([Remote Annex], [UUID])+keyPossibilitiesTrusted :: Key -> Annex ([Remote], [UUID]) keyPossibilitiesTrusted = keyPossibilities' True -keyPossibilities' :: Bool -> Key -> Annex ([Remote Annex], [UUID])+keyPossibilities' :: Bool -> Key -> Annex ([Remote], [UUID]) keyPossibilities' withtrusted key = do 	u <- getUUID 	trusted <- if withtrusted then trustGet Trusted else return []@@ -195,7 +201,7 @@ 	let validtrusteduuids = validuuids `intersect` trusted  	-- remotes that match uuids that have the key-	allremotes <- filterM (repoNotIgnored . repo) =<< genList+	allremotes <- enabledRemoteList 	let validremotes = remotesWithUUID allremotes validuuids  	return (sort validremotes, validtrusteduuids)@@ -218,7 +224,7 @@ 		message [] us = "Also these untrusted repositories may contain the file:\n" ++ us 		message rs us = message rs [] ++ message [] us -showTriedRemotes :: [Remote Annex] -> Annex ()+showTriedRemotes :: [Remote] -> Annex () showTriedRemotes [] = return ()	 showTriedRemotes remotes = 	showLongNote $ "Unable to access these remotes: " ++@@ -234,7 +240,7 @@  - in the local repo, not on the remote. The process of transferring the  - key to the remote, or removing the key from it *may* log the change  - on the remote, but this cannot always be relied on. -}-logStatus :: Remote Annex -> Key -> Bool -> Annex ()+logStatus :: Remote -> Key -> Bool -> Annex () logStatus remote key present = logChange key (uuid remote) status 	where 		status = if present then InfoPresent else InfoMissing
Remote/Bup.hs view
@@ -15,6 +15,9 @@ import Common.Annex import Types.Remote import qualified Git+import qualified Git.Command+import qualified Git.Config+import qualified Git.Construct import Config import Annex.Ssh import Remote.Helper.Special@@ -23,7 +26,7 @@  type BupRepo = String -remote :: RemoteType Annex+remote :: RemoteType remote = RemoteType { 	typename = "bup", 	enumerate = findSpecialRemotes "buprepo",@@ -31,7 +34,7 @@ 	setup = bupSetup } -gen :: Git.Repo -> UUID -> Maybe RemoteConfig -> Annex (Remote Annex)+gen :: Git.Repo -> UUID -> Maybe RemoteConfig -> Annex Remote gen r u c = do 	buprepo <- getConfig r "buprepo" (error "missing buprepo") 	cst <- remoteCost r (if bupLocal buprepo then semiCheapRemoteCost else expensiveRemoteCost)@@ -51,7 +54,8 @@ 			hasKey = checkPresent r bupr', 			hasKeyCheap = bupLocal buprepo, 			config = c,-			repo = r+			repo = r,+			remotetype = remote 		}  bupSetup :: UUID -> RemoteConfig -> Annex RemoteConfig@@ -146,7 +150,7 @@ 		ok <- onBupRemote bupr boolSystem "git" params 		return $ Right ok 	| otherwise = liftIO $ catchMsgIO $-		boolSystem "git" $ Git.gitCommandLine params bupr+		boolSystem "git" $ Git.Command.gitCommandLine params bupr 	where 		params =  			[ Params "show-ref --quiet --verify"@@ -163,10 +167,10 @@ 				[Params $ "config annex.uuid " ++ v] 					>>! error "ssh failed" 		else liftIO $ do-			r' <- Git.configRead r-			let olduuid = Git.configGet "annex.uuid" "" r'+			r' <- Git.Config.read r+			let olduuid = Git.Config.get "annex.uuid" "" r' 			when (olduuid == "") $-				Git.run "config"+				Git.Command.run "config" 					[Param "annex.uuid", Param v] r' 	where 		v = fromUUID u@@ -192,9 +196,9 @@ getBupUUID r u 	| Git.repoIsUrl r = return (u, r) 	| otherwise = liftIO $ do-		ret <- try $ Git.configRead r+		ret <- try $ Git.Config.read r 		case ret of-			Right r' -> return (toUUID $ Git.configGet "annex.uuid" "" r', r')+			Right r' -> return (toUUID $ Git.Config.get "annex.uuid" "" r', r') 			Left _ -> return (NoUUID, r)  {- Converts a bup remote path spec into a Git.Repo. There are some@@ -203,23 +207,23 @@ bup2GitRemote "" = do 	-- bup -r "" operates on ~/.bup 	h <- myHomeDir-	Git.repoFromAbsPath $ h </> ".bup"+	Git.Construct.fromAbsPath $ h </> ".bup" bup2GitRemote r 	| bupLocal r = -		if head r == '/'-			then Git.repoFromAbsPath r+		if "/" `isPrefixOf` r+			then Git.Construct.fromAbsPath r 			else error "please specify an absolute path"-	| otherwise = Git.repoFromUrl $ "ssh://" ++ host ++ slash dir+	| otherwise = Git.Construct.fromUrl $ "ssh://" ++ host ++ slash dir 		where 			bits = split ":" r-			host = head bits+			host = Prelude.head bits 			dir = join ":" $ drop 1 bits 			-- "host:~user/dir" is not supported specially by bup; 			-- "host:dir" is relative to the home directory; 			-- "host:" goes in ~/.bup 			slash d-				| d == "" = "/~/.bup"-				| head d == '/' = d+				| null d = "/~/.bup"+				| "/" `isPrefixOf` d = d 				| otherwise = "/~/" ++ d  bupLocal :: BupRepo -> Bool
Remote/Directory.hs view
@@ -20,7 +20,7 @@ import Remote.Helper.Encryptable import Crypto -remote :: RemoteType Annex+remote :: RemoteType remote = RemoteType { 	typename = "directory", 	enumerate = findSpecialRemotes "directory",@@ -28,7 +28,7 @@ 	setup = directorySetup } -gen :: Git.Repo -> UUID -> Maybe RemoteConfig -> Annex (Remote Annex)+gen :: Git.Repo -> UUID -> Maybe RemoteConfig -> Annex Remote gen r u c = do 	dir <- getConfig r "directory" (error "missing directory") 	cst <- remoteCost r cheapRemoteCost@@ -45,7 +45,8 @@ 			hasKey = checkPresent dir, 			hasKeyCheap = True, 			config = Nothing,-			repo = r+			repo = r,+			remotetype = remote 		}  directorySetup :: UUID -> RemoteConfig -> Annex RemoteConfig@@ -96,7 +97,7 @@  storeHelper :: FilePath -> Key -> (FilePath -> IO Bool) -> IO Bool storeHelper d key a = do-	let dest = head $ locations d key+	let dest = Prelude.head $ locations d key 	let dir = parentDir dest 	createDirectoryIfMissing True dir 	allowWrite dir
Remote/Git.hs view
@@ -5,7 +5,7 @@  - Licensed under the GNU GPL version 3 or higher.  -} -module Remote.Git (remote) where+module Remote.Git (remote, repoAvail) where  import Control.Exception.Extensible import qualified Data.Map as M@@ -16,16 +16,19 @@ import Annex.Ssh import Types.Remote import qualified Git+import qualified Git.Command+import qualified Git.Config+import qualified Git.Construct import qualified Annex import Annex.UUID import qualified Annex.Content-import qualified Annex.Branch+import qualified Annex.BranchState import qualified Utility.Url as Url import Utility.TempFile import Config import Init -remote :: RemoteType Annex+remote :: RemoteType remote = RemoteType { 	typename = "git", 	enumerate = list,@@ -35,18 +38,19 @@  list :: Annex [Git.Repo] list = do-	c <- fromRepo Git.configMap+	c <- fromRepo Git.config 	mapM (tweakurl c) =<< fromRepo Git.remotes 	where 		annexurl n = "remote." ++ n ++ ".annexurl" 		tweakurl c r = do-			let n = fromJust $ Git.repoRemoteName r+			let n = fromJust $ Git.remoteName r 			case M.lookup (annexurl n) c of 				Nothing -> return r-				Just url -> Git.repoRemoteNameSet n <$>-					inRepo (Git.genRemote url)+				Just url -> inRepo $ \g ->+					Git.Construct.remoteNamed n $+						Git.Construct.fromRemoteLocation url g -gen :: Git.Repo -> UUID -> Maybe RemoteConfig -> Annex (Remote Annex)+gen :: Git.Repo -> UUID -> Maybe RemoteConfig -> Annex Remote gen r u _ = do  	{- It's assumed to be cheap to read the config of non-URL remotes, 	 - so this is done each time git-annex is run. Conversely,@@ -75,20 +79,21 @@ 		hasKey = inAnnex r', 		hasKeyCheap = cheap, 		config = Nothing,-		repo = r'+		repo = r',+		remotetype = remote 	}  {- Tries to read the config for a specified remote, updates state, and  - returns the updated repo. -} tryGitConfigRead :: Git.Repo -> Annex Git.Repo tryGitConfigRead r -	| not $ M.null $ Git.configMap r = return r -- already read+	| not $ M.null $ Git.config r = return r -- already read 	| Git.repoIsSsh r = store $ onRemote r (pipedconfig, r) "configlist" [] 	| Git.repoIsHttp r = store $ safely geturlconfig 	| Git.repoIsUrl r = return r-	| otherwise = store $ safely $ do-		onLocal r ensureInitialized-		Git.configRead r+	| otherwise = store $ safely $ onLocal r $ do +		ensureInitialized+		Annex.getState Annex.repo 	where 		-- Reading config can fail due to IO error or 		-- for other reasons; catch all possible exceptions.@@ -100,27 +105,27 @@  		pipedconfig cmd params = safely $ 			pOpen ReadFromPipe cmd (toCommand params) $-				Git.hConfigRead r+				Git.Config.hRead r  		geturlconfig = do 			s <- Url.get (Git.repoLocation r ++ "/config") 			withTempFile "git-annex.tmp" $ \tmpfile h -> do 				hPutStr h s 				hClose h-				pOpen ReadFromPipe "git" ["config", "--list", "--file", tmpfile] $-					Git.hConfigRead r+				pOpen ReadFromPipe "git" ["config", "--null", "--list", "--file", tmpfile] $+					Git.Config.hRead r  		store a = do 			r' <- a 			g <- gitRepo 			let l = Git.remotes g-			let g' = Git.remotesAdd g $ exchange l r'+			let g' = g { Git.remotes = exchange l r' } 			Annex.changeState $ \s -> s { Annex.repo = g' } 			return r'  		exchange [] _ = [] 		exchange (old:ls) new =-			if Git.repoRemoteName old == Git.repoRemoteName new+			if Git.remoteName old == Git.remoteName new 				then new : exchange ls new 				else old : exchange ls new @@ -159,21 +164,28 @@ 				dispatch (Right Nothing) = unknown 		unknown = Left $ "unable to check " ++ Git.repoDescribe r +{- Checks inexpensively if a repository is available for use. -}+repoAvail :: Git.Repo -> Annex Bool+repoAvail r +	| Git.repoIsHttp r = return True+	| Git.repoIsUrl r = return True+	| otherwise = liftIO $ catchBoolIO $ onLocal r $ return True+ {- Runs an action on a local repository inexpensively, by making an annex  - monad using that repository. -} onLocal :: Git.Repo -> Annex a -> IO a onLocal r a = do 	-- Avoid re-reading the repository's configuration if it was 	-- already read.-	state <- if M.null $ Git.configMap r+	state <- if M.null $ Git.config r 		then Annex.new r 		else return $ Annex.newState r 	Annex.eval state $ do 		-- No need to update the branch; its data is not used 		-- for anything onLocal is used to do.-		Annex.Branch.disableUpdate+		Annex.BranchState.disableUpdate 		ret <- a-		liftIO Git.reap+		liftIO Git.Command.reap 		return ret  keyUrls :: Git.Repo -> Key -> [String]
Remote/Helper/Encryptable.hs view
@@ -41,8 +41,8 @@ 	:: Maybe RemoteConfig 	-> ((Cipher, Key) -> Key -> Annex Bool) 	-> ((Cipher, Key) -> FilePath -> Annex Bool)-	-> Remote Annex -	-> Remote Annex+	-> Remote+	-> Remote encryptableRemote c storeKeyEncrypted retrieveKeyFileEncrypted r =  	r { 		storeKey = store,
Remote/Helper/Special.hs view
@@ -12,6 +12,8 @@ import Common.Annex import Types.Remote import qualified Git+import qualified Git.Command+import qualified Git.Construct  {- Special remotes don't have a configured url, so Git.Repo does not  - automatically generate remotes for them. This looks for a different@@ -19,11 +21,11 @@  -} findSpecialRemotes :: String -> Annex [Git.Repo] findSpecialRemotes s = do-	m <- fromRepo Git.configMap-	return $ map construct $ remotepairs m+	m <- fromRepo Git.config+	liftIO $ mapM construct $ remotepairs m 	where 		remotepairs = M.toList . M.filterWithKey match-		construct (k,_) = Git.repoRemoteNameFromKey k Git.repoFromUnknown+		construct (k,_) = Git.Construct.remoteNamedFromKey k Git.Construct.fromUnknown 		match k _ = startswith "remote." k && endswith (".annex-"++s) k  {- Sets up configuration for a special remote in .git/config. -}@@ -32,7 +34,7 @@ 	set ("annex-"++k) v 	set ("annex-uuid") (fromUUID u) 	where-		set a b = inRepo $ Git.run "config"+		set a b = inRepo $ Git.Command.run "config" 			[Param (configsetting a), Param b] 		remotename = fromJust (M.lookup "name" c) 		configsetting s = "remote." ++ remotename ++ "." ++ s
Remote/Hook.hs view
@@ -20,7 +20,7 @@ import Remote.Helper.Encryptable import Crypto -remote :: RemoteType Annex+remote :: RemoteType remote = RemoteType { 	typename = "hook", 	enumerate = findSpecialRemotes "hooktype",@@ -28,7 +28,7 @@ 	setup = hookSetup } -gen :: Git.Repo -> UUID -> Maybe RemoteConfig -> Annex (Remote Annex)+gen :: Git.Repo -> UUID -> Maybe RemoteConfig -> Annex Remote gen r u c = do 	hooktype <- getConfig r "hooktype" (error "missing hooktype") 	cst <- remoteCost r expensiveRemoteCost@@ -45,7 +45,8 @@ 			hasKey = checkPresent r hooktype, 			hasKeyCheap = False, 			config = Nothing,-			repo = r+			repo = r,+			remotetype = remote 		}  hookSetup :: UUID -> RemoteConfig -> Annex RemoteConfig@@ -62,13 +63,12 @@ 		env s v = ("ANNEX_" ++ s, v) 		keyenv = 			[ env "KEY" (show k)-			, env "HASH_1" hash_1-			, env "HASH_2" hash_2+			, env "HASH_1" (hashbits !! 0)+			, env "HASH_2" (hashbits !! 1) 			] 		fileenv Nothing = [] 		fileenv (Just file) =  [env "FILE" file]-		[hash_1, hash_2, _rest] =-			map takeDirectory $ splitPath $ hashDirMixed k+		hashbits = map takeDirectory $ splitPath $ hashDirMixed k  lookupHook :: String -> String -> Annex (Maybe String) lookupHook hooktype hook =do
Remote/Rsync.hs view
@@ -27,7 +27,7 @@ 	rsyncOptions :: [CommandParam] } -remote :: RemoteType Annex+remote :: RemoteType remote = RemoteType { 	typename = "rsync", 	enumerate = findSpecialRemotes "rsyncurl",@@ -35,7 +35,7 @@ 	setup = rsyncSetup } -gen :: Git.Repo -> UUID -> Maybe RemoteConfig -> Annex (Remote Annex)+gen :: Git.Repo -> UUID -> Maybe RemoteConfig -> Annex Remote gen r u c = do 	o <- genRsyncOpts r 	cst <- remoteCost r expensiveRemoteCost@@ -52,7 +52,8 @@ 			hasKey = checkPresent r o, 			hasKeyCheap = False, 			config = Nothing,-			repo = r+			repo = r,+			remotetype = remote 		}  genRsyncOpts :: Git.Repo -> Annex RsyncOpts@@ -92,11 +93,6 @@ 		use h = rsyncUrl o </> h k </> rsyncEscape o (f </> f)                 f = keyFile k -rsyncUrlDirs :: RsyncOpts -> Key -> [String]-rsyncUrlDirs o k = map use annexHashes-	where-		use h = rsyncUrl o </> h k </> rsyncEscape o (keyFile k)- store :: RsyncOpts -> Key -> Annex Bool store o k = rsyncSend o k =<< inRepo (gitAnnexLocation k) @@ -125,17 +121,29 @@ 		else return res  remove :: RsyncOpts -> Key -> Annex Bool-remove o k = untilTrue (rsyncUrlDirs o k) $ \d ->-	withRsyncScratchDir $ \tmp -> liftIO $ do-		{- Send an empty directory to rysnc as the-		 - parent directory of the file to remove. -}-		let dummy = tmp </> keyFile k-		createDirectoryIfMissing True dummy-		rsync $ rsyncOptions o ++-			[ Params "--quiet --delete --recursive"-			, partialParams-			, Param $ addTrailingPathSeparator dummy-			, Param d+remove o k = withRsyncScratchDir $ \tmp -> liftIO $ do+	{- Send an empty directory to rysnc to make it delete. -}+	let dummy = tmp </> keyFile k+	createDirectoryIfMissing True dummy+	rsync $ rsyncOptions o +++		map (\s -> Param $ "--include=" ++ s) includes +++		[ Param "--exclude=*" -- exclude everything else+		, Params "--quiet --delete --recursive"+		, partialParams+		, Param $ addTrailingPathSeparator dummy+		, Param $ rsyncUrl o+		]+	where+		{- Specify include rules to match the directories where the+		 - content could be. Note that the parent directories have+		 - to also be explicitly included, due to how rsync+		 - traverses directories. -}+		includes = concatMap use annexHashes+		use h = let dir = h k in+			[ parentDir dir+			, dir+			-- match content directory and anything in it+			, dir </> keyFile k </> "***" 			]  checkPresent :: Git.Repo -> RsyncOpts -> Key -> Annex (Either String Bool)@@ -188,7 +196,7 @@    directories. -} rsyncSend :: RsyncOpts -> Key -> FilePath -> Annex Bool rsyncSend o k src = withRsyncScratchDir $ \tmp -> do-	let dest = tmp </> head (keyPaths k)+	let dest = tmp </> Prelude.head (keyPaths k) 	liftIO $ createDirectoryIfMissing True $ parentDir dest 	liftIO $ createLink src dest 	rsyncRemote o
Remote/S3real.hs view
@@ -28,7 +28,7 @@ import Annex.Content import Utility.Base64 -remote :: RemoteType Annex+remote :: RemoteType remote = RemoteType { 	typename = "S3", 	enumerate = findSpecialRemotes "s3",@@ -36,11 +36,11 @@ 	setup = s3Setup } -gen :: Git.Repo -> UUID -> Maybe RemoteConfig -> Annex (Remote Annex)+gen :: Git.Repo -> UUID -> Maybe RemoteConfig -> Annex Remote gen r u c = do 	cst <- remoteCost r expensiveRemoteCost 	return $ gen' r u c cst-gen' :: Git.Repo -> UUID -> Maybe RemoteConfig -> Int -> Remote Annex+gen' :: Git.Repo -> UUID -> Maybe RemoteConfig -> Int -> Remote gen' r u c cst = 	encryptableRemote c 		(storeEncrypted this)@@ -57,7 +57,8 @@ 			hasKey = checkPresent this, 			hasKeyCheap = False, 			config = c,-			repo = r+			repo = r,+			remotetype = remote 		}  s3Setup :: UUID -> RemoteConfig -> Annex RemoteConfig@@ -110,13 +111,13 @@ 					-- be human-readable 					M.delete "bucket" defaults -store :: Remote Annex -> Key -> Annex Bool+store :: Remote -> Key -> Annex Bool store r k = s3Action r False $ \(conn, bucket) -> do 	dest <- inRepo $ gitAnnexLocation k 	res <- liftIO $ storeHelper (conn, bucket) r k dest 	s3Bool res -storeEncrypted :: Remote Annex -> (Cipher, Key) -> Key -> Annex Bool+storeEncrypted :: Remote -> (Cipher, Key) -> Key -> Annex Bool storeEncrypted r (cipher, enck) k = s3Action r False $ \(conn, bucket) ->  	-- To get file size of the encrypted content, have to use a temp file. 	-- (An alternative would be chunking to to a constant size.)@@ -126,7 +127,7 @@ 		res <- liftIO $ storeHelper (conn, bucket) r enck tmp 		s3Bool res -storeHelper :: (AWSConnection, String) -> Remote Annex -> Key -> FilePath -> IO (AWSResult ())+storeHelper :: (AWSConnection, String) -> Remote -> Key -> FilePath -> IO (AWSResult ()) storeHelper (conn, bucket) r k file = do 	content <- liftIO $ L.readFile file 	-- size is provided to S3 so the whole content does not need to be@@ -148,7 +149,7 @@ 		xheaders = filter isxheader $ M.assocs $ fromJust $ config r 		isxheader (h, _) = "x-amz-" `isPrefixOf` h -retrieve :: Remote Annex -> Key -> FilePath -> Annex Bool+retrieve :: Remote -> Key -> FilePath -> Annex Bool retrieve r k f = s3Action r False $ \(conn, bucket) -> do 	res <- liftIO $ getObject conn $ bucketKey r bucket k 	case res of@@ -157,7 +158,7 @@ 			return True 		Left e -> s3Warning e -retrieveEncrypted :: Remote Annex -> (Cipher, Key) -> FilePath -> Annex Bool+retrieveEncrypted :: Remote -> (Cipher, Key) -> FilePath -> Annex Bool retrieveEncrypted r (cipher, enck) f = s3Action r False $ \(conn, bucket) -> do 	res <- liftIO $ getObject conn $ bucketKey r bucket enck 	case res of@@ -167,12 +168,12 @@ 				return True 		Left e -> s3Warning e -remove :: Remote Annex -> Key -> Annex Bool+remove :: Remote -> Key -> Annex Bool remove r k = s3Action r False $ \(conn, bucket) -> do 	res <- liftIO $ deleteObject conn $ bucketKey r bucket k 	s3Bool res -checkPresent :: Remote Annex -> Key -> Annex (Either String Bool)+checkPresent :: Remote -> Key -> Annex (Either String Bool) checkPresent r k = s3Action r noconn $ \(conn, bucket) -> do 	showAction $ "checking " ++ name r 	res <- liftIO $ getObjectInfo conn $ bucketKey r bucket k@@ -195,7 +196,7 @@ s3Bool (Right _) = return True s3Bool (Left e) = s3Warning e -s3Action :: Remote Annex -> a -> ((AWSConnection, String) -> Annex a) -> Annex a+s3Action :: Remote -> a -> ((AWSConnection, String) -> Annex a) -> Annex a s3Action r noconn action = do 	when (isNothing $ config r) $ 		error $ "Missing configuration for special remote " ++ name r@@ -205,14 +206,14 @@ 		(Just b, Just c) -> action (c, b) 		_ -> return noconn -bucketFile :: Remote Annex -> Key -> FilePath+bucketFile :: Remote -> Key -> FilePath bucketFile r = munge . show 	where 		munge s = case M.lookup "mungekeys" $ fromJust $ config r of 			Just "ia" -> iaMunge s 			_ -> s -bucketKey :: Remote Annex -> String -> Key -> S3Object+bucketKey :: Remote -> String -> Key -> S3Object bucketKey r bucket k = S3Object bucket (bucketFile r k) "" [] L.empty  {- Internet Archive limits filenames to a subset of ascii,
Remote/S3stub.hs view
@@ -4,7 +4,7 @@ import Types.Remote import Types -remote :: RemoteType Annex+remote :: RemoteType remote = RemoteType { 	typename = "S3", 	enumerate = return [],
Remote/Web.hs view
@@ -10,11 +10,12 @@ import Common.Annex import Types.Remote import qualified Git+import qualified Git.Construct import Config import Logs.Web import qualified Utility.Url as Url -remote :: RemoteType Annex+remote :: RemoteType remote = RemoteType { 	typename = "web", 	enumerate = list,@@ -26,9 +27,11 @@ -- (If the web should cease to exist, remove this module and redistribute -- a new release to the survivors by carrier pigeon.) list :: Annex [Git.Repo]-list = return [Git.repoRemoteNameSet "web" Git.repoFromUnknown]+list = do+	r <- liftIO $ Git.Construct.remoteNamed "web" Git.Construct.fromUnknown+	return [r] -gen :: Git.Repo -> UUID -> Maybe RemoteConfig -> Annex (Remote Annex)+gen :: Git.Repo -> UUID -> Maybe RemoteConfig -> Annex Remote gen r _ _ =  	return Remote { 		uuid = webUUID,@@ -40,7 +43,8 @@ 		hasKey = checkKey, 		hasKeyCheap = False, 		config = Nothing,-		repo = r+		repo = r,+		remotetype = remote 	}  downloadKey :: Key -> FilePath -> Annex Bool
Seek.hs view
@@ -18,6 +18,7 @@ import qualified Annex import qualified Git import qualified Git.LsFiles as LsFiles+import qualified Git.CheckAttr import qualified Limit  seekHelper :: ([FilePath] -> Git.Repo -> IO [FilePath]) -> [FilePath] -> Annex [FilePath]@@ -31,7 +32,7 @@ withAttrFilesInGit :: String -> ((FilePath, String) -> CommandStart) -> CommandSeek withAttrFilesInGit attr a params = do 	files <- seekHelper LsFiles.inRepo params-	prepFilteredGen a fst $ inRepo $ Git.checkAttr attr files+	prepFilteredGen a fst $ inRepo $ Git.CheckAttr.lookup attr files  withNumCopies :: (Maybe Int -> FilePath -> CommandStart) -> CommandSeek withNumCopies a params = withAttrFilesInGit "annex.numcopies" go params
Types.hs view
@@ -9,10 +9,17 @@ 	Annex, 	Backend, 	Key,-	UUID(..)+	UUID(..),+	Remote,+	RemoteType ) where  import Annex import Types.Backend import Types.Key import Types.UUID+import Types.Remote++type Backend = BackendA Annex+type Remote = RemoteA Annex+type RemoteType = RemoteTypeA Annex
Types/Backend.hs view
@@ -1,6 +1,6 @@ {- git-annex key/value backend data type  -- - Most things should not need this, using Remotes instead+ - Most things should not need this, using Types instead  -  - Copyright 2010 Joey Hess <joey@kitenet.net>  -@@ -11,7 +11,7 @@  import Types.Key -data Backend a = Backend {+data BackendA a = Backend { 	-- name of this backend 	name :: String, 	-- converts a filename to a key@@ -20,8 +20,8 @@ 	fsckKey :: Key -> a Bool } -instance Show (Backend a) where+instance Show (BackendA a) where 	show backend = "Backend { name =\"" ++ name backend ++ "\" }" -instance Eq (Backend a) where+instance Eq (BackendA a) where 	a == b = name a == name b
Types/Crypto.hs view
@@ -5,13 +5,16 @@  - Licensed under the GNU GPL version 3 or higher.  -} -module Types.Crypto where+module Types.Crypto (+	Cipher(..),+	EncryptedCipher(..),+	KeyIds(..),+) where +import Utility.Gpg (KeyIds(..))+ -- XXX ideally, this would be a locked memory region newtype Cipher = Cipher String  data EncryptedCipher = EncryptedCipher String KeyIds-	deriving (Ord, Eq)--newtype KeyIds = KeyIds [String] 	deriving (Ord, Eq)
Types/Remote.hs view
@@ -1,6 +1,6 @@ {- git-annex remotes types  -- - Most things should not need this, using Remote instead+ - Most things should not need this, using Types instead  -  - Copyright 2011 Joey Hess <joey@kitenet.net>  -@@ -19,19 +19,22 @@ type RemoteConfig = M.Map String String  {- There are different types of remotes. -}-data RemoteType a = RemoteType {+data RemoteTypeA a = RemoteType { 	-- human visible type name 	typename :: String, 	-- enumerates remotes of this type 	enumerate :: a [Git.Repo], 	-- generates a remote of this type-	generate :: Git.Repo -> UUID -> Maybe RemoteConfig -> a (Remote a),+	generate :: Git.Repo -> UUID -> Maybe RemoteConfig -> a (RemoteA a), 	-- initializes or changes a remote 	setup :: UUID -> RemoteConfig -> a RemoteConfig } +instance Eq (RemoteTypeA a) where+	x == y = typename x == typename y+ {- An individual remote. -}-data Remote a = Remote {+data RemoteA a = Remote { 	-- each Remote has a unique uuid 	uuid :: UUID, 	-- each Remote has a human visible name@@ -53,16 +56,18 @@ 	-- a Remote can have a persistent configuration store 	config :: Maybe RemoteConfig, 	-- git configuration for the remote-	repo :: Git.Repo+	repo :: Git.Repo,+	-- the type of the remote+	remotetype :: RemoteTypeA a } -instance Show (Remote a) where+instance Show (RemoteA a) where 	show remote = "Remote { name =\"" ++ name remote ++ "\" }"  -- two remotes are the same if they have the same uuid-instance Eq (Remote a) where+instance Eq (RemoteA a) where 	x == y = uuid x == uuid y  -- order remotes by cost-instance Ord (Remote a) where+instance Ord (RemoteA a) where 	compare = comparing cost
Upgrade/V0.hs view
@@ -33,7 +33,7 @@ keyFile0 = Upgrade.V1.keyFile1 fileKey0 :: FilePath -> Key fileKey0 = Upgrade.V1.fileKey1-lookupFile0 :: FilePath -> Annex (Maybe (Key, Backend Annex))+lookupFile0 :: FilePath -> Annex (Maybe (Key, Backend)) lookupFile0 = Upgrade.V1.lookupFile1  getKeysPresent0 :: FilePath -> Annex [Key]
Upgrade/V1.hs view
@@ -146,20 +146,20 @@ readKey1 :: String -> Key readKey1 v =  	if mixup-		then fromJust $ readKey $ join ":" $ tail bits+		then fromJust $ readKey $ join ":" $ Prelude.tail bits 		else Key { keyName = n , keyBackendName = b, keySize = s, keyMtime = t } 	where 		bits = split ":" v-		b = head bits+		b = Prelude.head bits 		n = join ":" $ drop (if wormy then 3 else 1) bits 		t = if wormy-			then Just (read (bits !! 1) :: EpochTime)+			then Just (Prelude.read (bits !! 1) :: EpochTime) 			else Nothing 		s = if wormy-			then Just (read (bits !! 2) :: Integer)+			then Just (Prelude.read (bits !! 2) :: Integer) 			else Nothing-		wormy = head bits == "WORM"-		mixup = wormy && isUpper (head $ bits !! 1)+		wormy = Prelude.head bits == "WORM"+		mixup = wormy && isUpper (Prelude.head $ bits !! 1)  showKey1 :: Key -> String showKey1 Key { keyName = n , keyBackendName = b, keySize = s, keyMtime = t } =@@ -181,7 +181,7 @@ readLog1 :: FilePath -> IO [LogLine] readLog1 file = catchDefaultIO (parseLog <$> readFileStrict file) [] -lookupFile1 :: FilePath -> Annex (Maybe (Key, Backend Annex))+lookupFile1 :: FilePath -> Annex (Maybe (Key, Backend)) lookupFile1 file = do 	tl <- liftIO $ try getsymlink 	case tl of
Upgrade/V2.hs view
@@ -9,6 +9,8 @@  import Common.Annex import qualified Git+import qualified Git.Command+import qualified Git.Ref import qualified Annex.Branch import Logs.Location import Annex.Content@@ -52,7 +54,7 @@ 	showProgress  	when e $ do-		inRepo $ Git.run "rm" [Param "-r", Param "-f", Param "-q", File old]+		inRepo $ Git.Command.run "rm" [Param "-r", Param "-f", Param "-q", File old] 		unless bare $ inRepo gitAttributesUnWrite 	showProgress @@ -86,7 +88,7 @@  push :: Annex () push = do-	origin_master <- Annex.Branch.refExists $ Git.Ref "origin/master"+	origin_master <- inRepo $ Git.Ref.exists $ Git.Ref "origin/master" 	origin_gitannex <- Annex.Branch.hasOrigin 	case (origin_master, origin_gitannex) of 		(_, True) -> do@@ -103,7 +105,8 @@ 			Annex.Branch.update -- just in case 			showAction "pushing new git-annex branch to origin" 			showOutput-			inRepo $ Git.run "push" [Param "origin", Param $ show Annex.Branch.name]+			inRepo $ Git.Command.run "push"+				[Param "origin", Param $ show Annex.Branch.name] 		_ -> do 			-- no origin exists, so just let the user 			-- know about the new branch@@ -126,7 +129,7 @@ 		c <- readFileStrict attributes 		liftIO $ viaTmp writeFile attributes $ unlines $ 			filter (`notElem` attrLines) $ lines c-		Git.run "add" [File attributes] repo+		Git.Command.run "add" [File attributes] repo  stateDir :: FilePath stateDir = addTrailingPathSeparator ".git-annex"
− Utility/BadPrelude.hs
@@ -1,28 +0,0 @@-{- Some stuff from Prelude should not be used, as it tends to be a source- - of bugs.- -- - This exports functions that conflict with the prelude, which avoids- - them being accidentially used.- -}--module Utility.BadPrelude where--{- head is a partial function; head [] is an error -}-head :: [a] -> a-head = Prelude.head--{- tail is also partial -}-tail :: [a] -> a-tail = Prelude.tail--{- init too -}-init :: [a] -> a-init = Prelude.init--{- last too -}-last :: [a] -> a-last = Prelude.last--{- read should be avoided, as it throws an error -}-read :: Read a => String -> a-read = Prelude.read
+ Utility/Format.hs view
@@ -0,0 +1,173 @@+{- Formatted string handling.+ -+ - Copyright 2010, 2011 Joey Hess <joey@kitenet.net>+ -+ - Licensed under the GNU GPL version 3 or higher.+ -}++module Utility.Format (+	Format,+	gen,+	format,+	decode_c,+	encode_c,+	prop_idempotent_deencode+) where++import Text.Printf (printf)+import Data.Char (isAlphaNum, isOctDigit, isSpace, chr, ord)+import Data.Maybe (fromMaybe)+import Data.Word (Word8)+import Data.List (isPrefixOf)+import qualified Codec.Binary.UTF8.String+import qualified Data.Map as M++import Utility.PartialPrelude++type FormatString = String++{- A format consists of a list of fragments. -}+type Format = [Frag]++{- A fragment is either a constant string,+ - or a variable, with a justification. -}+data Frag = Const String | Var String Justify+	deriving (Show)++data Justify = LeftJustified Int | RightJustified Int | UnJustified+	deriving (Show)++{- Expands a Format using some variables, generating a formatted string.+ - This can be repeatedly called, efficiently. -}+format :: Format -> M.Map String String -> String+format f vars = concatMap expand f+	where+		expand (Const s) = s+		expand (Var name j)+			| "escaped_" `isPrefixOf` name =+				justify j $ encode_c_strict $+					getvar $ drop (length "escaped_") name+			| otherwise = justify j $ getvar name+		getvar name = fromMaybe "" $ M.lookup name vars+		justify UnJustified s        = s+		justify (LeftJustified i) s  = s ++ pad i s+		justify (RightJustified i) s = pad i s ++ s+		pad i s = take (i - length s) spaces+		spaces = repeat ' '++{- Generates a Format that can be used to expand variables in a+ - format string, such as "${foo} ${bar;10} ${baz;-10}\n"+ -+ - (This is the same type of format string used by dpkg-query.)+ -}+gen :: FormatString -> Format+gen = filter (not . empty) . fuse [] . scan [] . decode_c+	where+		-- The Format is built up in reverse, for efficiency,+		-- and can have many adjacent Consts. Fusing it fixes both+		-- problems.+		fuse f [] = f+		fuse f (Const c1:Const c2:vs) = fuse f $ Const (c2++c1) : vs+		fuse f (v:vs) = fuse (v:f) vs++		scan f (a:b:cs)+			| a == '$' && b == '{' = invar f [] cs+			| otherwise = scan (Const [a] : f ) (b:cs)+		scan f v = Const v : f++		invar f var [] = Const (novar var) : f+		invar f var (c:cs)+			| c == '}' = foundvar f var UnJustified cs+			| isAlphaNum c || c == '_' = invar f (c:var) cs+			| c == ';' = inpad "" f var cs+			| otherwise = scan ((Const $ novar $ c:var):f) cs++		inpad p f var (c:cs)+			| c == '}' = foundvar f var (readjustify $ reverse p) cs+			| otherwise = inpad (c:p) f var cs+		inpad p f var [] = Const (novar $ p++";"++var) : f+		readjustify = getjustify . fromMaybe 0 . readMaybe+		getjustify i+			| i == 0 = UnJustified+			| i < 0 = LeftJustified (-1 * i)+			| otherwise = RightJustified i+		novar v = "${" ++ reverse v+		foundvar f v p cs = scan (Var (reverse v) p : f) cs++empty :: Frag -> Bool+empty (Const "") = True+empty _ = False++{- Decodes a C-style encoding, where \n is a newline, \NNN is an octal+ - encoded character, etc.+ -}+decode_c :: FormatString -> FormatString+decode_c [] = []+decode_c s = unescape ("", s)+	where+		e = '\\'+		unescape (b, []) = b+		-- look for escapes starting with '\'+		unescape (b, v) = b ++ fst pair ++ unescape (handle $ snd pair)+			where+				pair = span (/= e) v+		isescape x = x == e+		-- \NNN is an octal encoded character+		handle (x:n1:n2:n3:rest)+			| isescape x && alloctal = (fromoctal, rest)+				where+					alloctal = isOctDigit n1 &&+						isOctDigit n2 &&+						isOctDigit n3+					fromoctal = [chr $ readoctal [n1, n2, n3]]+					readoctal o = Prelude.read $ "0o" ++ o :: Int+		-- \C is used for a few special characters+		handle (x:nc:rest)+			| isescape x = ([echar nc], rest)+			where+				echar 'a' = '\a'+				echar 'b' = '\b'+				echar 'f' = '\f'+				echar 'n' = '\n'+				echar 'r' = '\r'+				echar 't' = '\t'+				echar 'v' = '\v'+				echar a = a+		handle n = ("", n)++{- Inverse of decode_c. -}+encode_c :: FormatString -> FormatString+encode_c = encode_c' (const False)++{- Encodes more strictly, including whitespace. -}+encode_c_strict :: FormatString -> FormatString+encode_c_strict = encode_c' isSpace++encode_c' :: (Char -> Bool) -> FormatString -> FormatString+encode_c' p = concatMap echar+	where+		e c = '\\' : [c]+		echar '\a' = e 'a'+		echar '\b' = e 'b'+		echar '\f' = e 'f'+		echar '\n' = e 'n'+		echar '\r' = e 'r'+		echar '\t' = e 't'+		echar '\v' = e 'v'+		echar '\\' = e '\\'+		echar '"'  = e '"'+		echar c+			| ord c < 0x20 = e_asc c -- low ascii+			| ord c >= 256 = e_utf c -- unicode+			| ord c > 0x7E = e_asc c -- high ascii+			| p c          = e_asc c -- unprintable ascii+			| otherwise    = [c]     -- printable ascii+		-- unicode character is decomposed to individual Word8s,+		-- and each is shown in octal+		e_utf c = showoctal =<< (Codec.Binary.UTF8.String.encode [c] :: [Word8])+		e_asc c = showoctal $ ord c+		showoctal i = '\\' : printf "%03o" i++{- for quickcheck -}+prop_idempotent_deencode :: String -> Bool+prop_idempotent_deencode s = s == decode_c (encode_c s)
+ Utility/Gpg.hs view
@@ -0,0 +1,194 @@+{- gpg interface+ -+ - Copyright 2011 Joey Hess <joey@kitenet.net>+ -+ - Licensed under the GNU GPL version 3 or higher.+ -}++module Utility.Gpg where++import qualified Data.ByteString.Lazy.Char8 as L+import System.Posix.Types+import Control.Applicative+import Control.Concurrent+import Control.Exception (finally, bracket)+import System.Exit+import System.Posix.Env (setEnv, unsetEnv, getEnv)	++import Common++newtype KeyIds = KeyIds [String]+        deriving (Ord, Eq)++stdParams :: [CommandParam] -> IO [String]+stdParams params = do+	-- Enable batch mode if GPG_AGENT_INFO is set, to avoid extraneous+	-- gpg output about password prompts.+	e <- getEnv "GPG_AGENT_INFO"+	let batch = if isNothing e then [] else ["--batch"]+	return $ batch ++ defaults ++ toCommand params+	where+		-- be quiet, even about checking the trustdb+		defaults = ["--quiet", "--trust-model", "always"]++{- Runs gpg with some params and returns its stdout, strictly. -}+readStrict :: [CommandParam] -> IO String+readStrict params = do+	params' <- stdParams params+	pOpen ReadFromPipe "gpg" params' hGetContentsStrict++{- Runs gpg, piping an input value to it, and returning its stdout,+ - strictly. -}+pipeStrict :: [CommandParam] -> String -> IO String+pipeStrict params input = do+	params' <- stdParams params+	(pid, fromh, toh) <- hPipeBoth "gpg" params'+	_ <- forkIO $ finally (hPutStr toh input) (hClose toh)+	output <- hGetContentsStrict fromh+	forceSuccess pid+	return output++{- Runs gpg with some parameters, first feeding it a passphrase via+ - --passphrase-fd, then feeding it an input, and passing a handle+ - to its output to an action.+ -+ - Note that to avoid deadlock with the cleanup stage,+ - the action must fully consume gpg's input before returning. -}+passphraseHandle :: [CommandParam] -> String -> IO L.ByteString -> (Handle -> IO a) -> IO a+passphraseHandle params passphrase a b = do+	-- pipe the passphrase into gpg on a fd+	(frompipe, topipe) <- createPipe+	_ <- forkIO $ do+		toh <- fdToHandle topipe+		hPutStrLn toh passphrase+		hClose toh+	let Fd pfd = frompipe+	let passphrasefd = [Param "--passphrase-fd", Param $ show pfd]++	params' <- stdParams $ passphrasefd ++ params+	(pid, fromh, toh) <- hPipeBoth "gpg" params'+	pid2 <- forkProcess $ do+		L.hPut toh =<< a+		hClose toh+		exitSuccess+	hClose toh+	ret <- b fromh++	-- cleanup+	forceSuccess pid+	_ <- getProcessStatus True False pid2+	closeFd frompipe+	return ret++{- Finds gpg public keys matching some string. (Could be an email address,+ - a key id, or a name. -}+findPubKeys :: String -> IO KeyIds+findPubKeys for = KeyIds . parse <$> readStrict params+	where+		params = [Params "--with-colons --list-public-keys", Param for]+		parse = map keyIdField . filter pubKey . lines+		pubKey = isPrefixOf "pub:"+		keyIdField s = split ":" s !! 4++++{- A test key. This is provided pre-generated since generating a new gpg+ - key is too much work (requires too much entropy) for a test suite to+ - do.+ -+ - This key was generated with no exipiration date, and a small keysize. + - It has an empty passphrase. -}+testKeyId :: String+testKeyId = "129D6E0AC537B9C7"+testKey :: String+testKey = keyBlock True+	[ "mI0ETvFAZgEEAKnqwWgZqznMhi1RQExem2H8t3OyKDxaNN3rBN8T6LWGGqAYV4wT"+	, "r8In5tfsnz64bKpE1Qi68JURFwYmthgUL9N48tbODU8t3xzijdjLOSaTyqkH1ik6"+	, "EyulfKN63xLne9i4F9XqNwpiZzukXYbNfHkDA2yb0M6g4UFKLY/fNzGXABEBAAG0"+	, "W2luc2VjdXJlIHRlc3Qga2V5ICh0aGlzIGlzIGEgdGVzdCBrZXksIGRvIG5vdCB1"+	, "c2UgZm9yIGFjdHVhbCBlbmNyeXB0aW9uKSA8dGVzdEBleGFtcGxlLmNvbT6IuAQT"+	, "AQgAIgUCTvFAZgIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQEp1uCsU3"+	, "uceQ9wP/YMd1f0+/eLLcwGXNBvGqyVhUOfAKknO1bMzGbqTsq9g60qegy/cldqee"+	, "xVxNfy0VN//JeMfgdcb8+RgJYLoaMrTy9CcsUcFPxtwN9tcLmsM0V2/fNmmFBO9t"+	, "v75iH+zeFbNg0/FbPkHiN6Mjw7P2gXYKQXgTvQZBWaphk8oQlBm4jQRO8UBmAQQA"+	, "vdi50M/WRCkOLt2RsUve8V8brMWYTJBJTTWoHUeRr82v4NCdX7OE1BsoVK8cy/1Q"+	, "Y+gLOH9PqinuGGNWRmPV2Ju/RYn5H7sdewXA8E80xWhc4phHRMJ8Jjhg/GVPamkJ"+	, "8B5zeKF0jcLFl7cuVdOyQakhoeDWJd0CyfW837nmPtMAEQEAAYifBBgBCAAJBQJO"+	, "8UBmAhsMAAoJEBKdbgrFN7nHclAEAKBShuP/toH03atDUQTbGE34CA4yEC9BVghi"+	, "7kviOZlOz2s8xAfp/8AYsrECx1kgbXcA7JD902eNyp7NzXsdJX0zJwHqiuZW0XlD"+	, "T8ZJu4qrYRYgl/790WPESZ+ValvHD/fqkR38RF4tfxvyoMhhp0roGmJY33GASIG/"+	, "+gQkDF9/"+	, "=1k11"+	]+testSecretKey :: String+testSecretKey = keyBlock False+	[ "lQHYBE7xQGYBBACp6sFoGas5zIYtUUBMXpth/Ldzsig8WjTd6wTfE+i1hhqgGFeM"+	, "E6/CJ+bX7J8+uGyqRNUIuvCVERcGJrYYFC/TePLWzg1PLd8c4o3Yyzkmk8qpB9Yp"+	, "OhMrpXyjet8S53vYuBfV6jcKYmc7pF2GzXx5AwNsm9DOoOFBSi2P3zcxlwARAQAB"+	, "AAP+PlRboxy7Z0XjuG70N6+CrzSddQbW5KCwgPFrxYsPk7sAPFcBkmRMVlv9vZpS"+	, "phbP4bvDK+MrSntM51g+9uE802yhPhSWdmEbImiWfV2ucEhlLjD8gw7JDex9XZ0a"+	, "EbTOV56wOsILuedX/jF/6i6IQzy5YmuMeo+ip1XQIsIN+80CAMyXepOBJgHw/gBD"+	, "VdXh/l//vUkQQlhInQYwgkKbr0POCTdr8DM1qdKLcUD9Q1khgNRp0vZGGz+5xsrc"+	, "KaODUlMCANSczLJcYWa8yPqB3S14yTe7qmtDiOS362+SeVUwQA7eQ06PcHLPsN+p"+	, "NtWoHRfYazxrs+g0JvmoQOYdj4xSQy0CAMq7H/l6aeG1n8tpyMxqE7OvBOsvzdu5"+	, "XS7I1AnwllVFgvTadVvqgf7b+hdYd91doeHDUGqSYO78UG1GgaBHJdylqrRbaW5z"+	, "ZWN1cmUgdGVzdCBrZXkgKHRoaXMgaXMgYSB0ZXN0IGtleSwgZG8gbm90IHVzZSBm"+	, "b3IgYWN0dWFsIGVuY3J5cHRpb24pIDx0ZXN0QGV4YW1wbGUuY29tPoi4BBMBCAAi"+	, "BQJO8UBmAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRASnW4KxTe5x5D3"+	, "A/9gx3V/T794stzAZc0G8arJWFQ58AqSc7VszMZupOyr2DrSp6DL9yV2p57FXE1/"+	, "LRU3/8l4x+B1xvz5GAlguhoytPL0JyxRwU/G3A321wuawzRXb982aYUE722/vmIf"+	, "7N4Vs2DT8Vs+QeI3oyPDs/aBdgpBeBO9BkFZqmGTyhCUGZ0B2ARO8UBmAQQAvdi5"+	, "0M/WRCkOLt2RsUve8V8brMWYTJBJTTWoHUeRr82v4NCdX7OE1BsoVK8cy/1QY+gL"+	, "OH9PqinuGGNWRmPV2Ju/RYn5H7sdewXA8E80xWhc4phHRMJ8Jjhg/GVPamkJ8B5z"+	, "eKF0jcLFl7cuVdOyQakhoeDWJd0CyfW837nmPtMAEQEAAQAD/RaVtFFTkF1udun7"+	, "YOwzJvQXCO9OWHZvSdEeG4BUNdAwy4YWu0oZzKkBDBS6+lWILqqb/c28U4leUJ1l"+	, "H+viz5svN9BWWyj/UpI00uwUo9JaIqalemwfLx6vsh69b54L1B4exLZHYGLvy/B3"+	, "5T6bT0gpOE+53BRtKcJaOh/McQeJAgDTOCBU5weWOf6Bhqnw3Vr/gRfxntAz2okN"+	, "gqz/h79mWbCc/lHKoYQSsrCdMiwziHSjXwvehUrdWE/AcomtW0vbAgDmGJqJ2fNr"+	, "HvdsGx4Ld/BxyiZbCURJLUQ5CwzfHGIvBu9PMT8zM26NOSncaXRjxDna2Ggh8Uum"+	, "ANEwbnhxFwZpAf9L9RLYIMTtAqwBjfXJg/lHcc2R+VP0hL5c8zFz+S+w7bRqINwL"+	, "ff1JstKuHT2nJnu0ustK66by8YI3T0hDFFahnNCInwQYAQgACQUCTvFAZgIbDAAK"+	, "CRASnW4KxTe5x3JQBACgUobj/7aB9N2rQ1EE2xhN+AgOMhAvQVYIYu5L4jmZTs9r"+	, "PMQH6f/AGLKxAsdZIG13AOyQ/dNnjcqezc17HSV9MycB6ormVtF5Q0/GSbuKq2EW"+	, "IJf+/dFjxEmflWpbxw/36pEd/EReLX8b8qDIYadK6BpiWN9xgEiBv/oEJAxffw=="+	, "=LDsg"+	]+keyBlock :: Bool -> [String] -> String+keyBlock public ls = unlines+	[ "-----BEGIN PGP "++t++" KEY BLOCK-----"+	, "Version: GnuPG v1.4.11 (GNU/Linux)"+	, ""+	, unlines ls+	, "-----END PGP "++t++" KEY BLOCK-----"+	]+	where+		t+			| public = "PUBLIC"+			| otherwise = "PRIVATE"++{- Runs an action using gpg in a test harness, in which gpg does+ - not use ~/.gpg/, but a directory with the test key set up to be used. -}+testHarness :: IO a -> IO a+testHarness a = do+	orig <- getEnv var+	bracket setup (cleanup orig) (const a)+	where+		var = "GNUPGHOME"		++		setup = do+			base <- getTemporaryDirectory+			dir <- mktmpdir $ base </> "gpgtmpXXXXXX"+			setEnv var dir True+			_ <- pipeStrict [Params "--import -q"] $ unlines+				[testSecretKey, testKey]+			return dir+		+		cleanup orig tmpdir = removeDirectoryRecursive tmpdir >> reset orig+                reset (Just v) = setEnv var v True+                reset _ = unsetEnv var++{- Tests the test harness. -}+testTestHarness :: IO Bool+testTestHarness = do+	keys <- testHarness $ findPubKeys testKeyId+	return $ KeyIds [testKeyId] == keys
Utility/Misc.hs view
@@ -21,12 +21,6 @@ readFileStrict :: FilePath -> IO String readFileStrict = readFile >=> \s -> length s `seq` return s -{- Attempts to read a value from a String. -}-readMaybe :: (Read a) => String -> Maybe a-readMaybe s = case reads s of-	((x,_):_) -> Just x-	_ -> Nothing- {- Like break, but the character matching the condition is not included  - in the second result list.  -@@ -39,6 +33,10 @@ 		unbreak r@(a, b) 			| null b = r 			| otherwise = (a, tail b)++{- Breaks out the first line. -}+firstLine :: String-> String+firstLine = takeWhile (/= '\n')  {- Catches IO errors and returns a Bool -} catchBoolIO :: IO Bool -> IO Bool
Utility/Monad.hs view
@@ -28,3 +28,11 @@ {- Runs an action on values from a list until it succeeds. -} untilTrue :: (Monad m) => [a] -> (a -> m Bool) -> m Bool untilTrue = flip anyM++{- Runs a monadic action, passing its value to an observer+ - before returning it. -}+observe :: (Monad m) => (a -> m b) -> m a -> m a+observe observer a = do+	r <- a+	_ <- observer r+	return r
+ Utility/PartialPrelude.hs view
@@ -0,0 +1,64 @@+{- Parts of the Prelude are partial functions, which are a common source of+ - bugs.+ -+ - This exports functions that conflict with the prelude, which avoids+ - them being accidentially used.+ -}++module Utility.PartialPrelude where++{- read should be avoided, as it throws an error+ - Instead, use: readMaybe -}+read :: Read a => String -> a+read = Prelude.read++{- head is a partial function; head [] is an error+ - Instead, use: take 1 or headMaybe -}+head :: [a] -> a+head = Prelude.head++{- tail is also partial+ - Instead, use: drop 1 -}+tail :: [a] -> [a]+tail = Prelude.tail++{- init too+ - Instead, use: beginning -}+init :: [a] -> [a]+init = Prelude.init++{- last too+ - Instead, use: end or lastMaybe -}+last :: [a] -> a+last = Prelude.last++{- Attempts to read a value from a String.+ -+ - Ignores leading/trailing whitespace, and throws away any trailing+ - text after the part that can be read.+ -}+readMaybe :: (Read a) => String -> Maybe a+readMaybe s = case reads s of+	((x,_):_) -> Just x+	_ -> Nothing++{- Like head but Nothing on empty list. -}+headMaybe :: [a] -> Maybe a+headMaybe [] = Nothing+headMaybe v = Just $ Prelude.head v++{- Like last but Nothing on empty list. -}+lastMaybe :: [a] -> Maybe a+lastMaybe [] = Nothing+lastMaybe v = Just $ Prelude.last v++{- All but the last element of a list.+ - (Like init, but no error on an empty list.) -}+beginning :: [a] -> [a]+beginning [] = []+beginning l = Prelude.init l++{- Like last, but no error on an empty list. -}+end :: [a] -> [a]+end [] = []+end l = [Prelude.last l]
Utility/Url.hs view
@@ -7,6 +7,7 @@  module Utility.Url ( 	exists,+	canDownload, 	download, 	get ) where@@ -32,9 +33,16 @@ 				(2,_,_) -> return True 				_ -> return False +canDownload :: IO Bool+canDownload = (||) <$> inPath "wget" <*> inPath "curl"+ {- Used to download large files, such as the contents of keys.+ -  - Uses wget or curl program for its progress bar. (Wget has a better one,- - so is preferred.) -}+ - so is preferred.) Which program to use is determined at run time; it+ - would not be appropriate to test at configure time and build support+ - for only one in.+ -} download :: URLString -> FilePath -> IO Bool download url file = do 	e <- inPath "wget"
configure.hs view
@@ -2,7 +2,6 @@  import System.Directory import Data.List-import Data.String.Utils import System.Cmd.Utils  import Build.TestConfig@@ -11,7 +10,7 @@ tests = 	[ TestCase "version" getVersion 	, TestCase "git" $ requireCmd "git" "git --version >/dev/null"-	, TestCase "git version" checkGitVersion+	, TestCase "git version" getGitVersion 	, testCp "cp_a" "-a" 	, testCp "cp_p" "-p" 	, testCp "cp_reflink_auto" "--reflink=auto"@@ -19,6 +18,7 @@ 	, TestCase "xargs -0" $ requireCmd "xargs_0" "xargs -0 </dev/null" 	, TestCase "rsync" $ requireCmd "rsync" "rsync --version >/dev/null" 	, TestCase "curl" $ testCmd "curl" "curl --version >/dev/null"+	, TestCase "wget" $ testCmd "wget" "wget --version >/dev/null" 	, TestCase "bup" $ testCmd "bup" "bup --version >/dev/null" 	, TestCase "gpg" $ testCmd "gpg" "gpg --version >/dev/null" 	] ++ shaTestCases [1, 256, 512, 224, 384]@@ -57,25 +57,11 @@ 	where 		middle = drop 1 . init -{- Checks for a new enough version of git. -}-checkGitVersion :: Test-checkGitVersion = do+getGitVersion :: Test+getGitVersion = do 	(_, s) <- pipeFrom "git" ["--version"] 	let version = last $ words $ head $ lines s-	if dotted version < dotted need-		then error $ "git version " ++ version ++ " too old; need " ++ need-		else return $ Config "gitversion" (StringConfig version)-	where-		-- for git-check-attr behavior change-		need = "1.7.7"-		dotted = sum . mult 1 . reverse . extend 10 . map readi . split "." -		extend n l = l ++ replicate (n - length l) 0-		mult _ [] = []-		mult n (x:xs) = (n*x) : mult (n*100) xs-		readi :: String -> Integer-		readi s = case reads s of-			((x,_):_) -> x-			_ -> 0+	return $ Config "gitversion" (StringConfig version)  {- Set up cabal file with version. -} cabalSetup :: IO ()
debian/changelog view
@@ -1,3 +1,30 @@+git-annex (3.20111231) unstable; urgency=low++  * sync: Improved to work well without a central bare repository.+    Thanks to Joachim Breitner.+  * Rather than manually committing, pushing, pulling, merging, and git annex+    merging, we encourage you to give "git annex sync" a try.+  * sync --fast: Selects some of the remotes with the lowest annex.cost+    and syncs those, in addition to any specified at the command line.+  * Union merge now finds the least expensive way to represent the merge.+  * reinject: Add a sanity check for using an annexed file as the source file.+  * Properly handle multiline git config values.+  * Fix the hook special remote, which bitrotted a while ago.+  * map: --fast disables use of dot to display map+  * Test suite improvements. Current top-level test coverage: 75%+  * Improve deletion of files from rsync special remotes. Closes: #652849+  * Add --include, which is the same as --not --exclude.+  * Format strings can be specified using the new --format option, to control+    what is output by git annex find.+  * Support git annex find --json+  * Fixed behavior when multiple insteadOf configs are provided for the+    same url base.+  * Can now be built with older git versions (before 1.7.7); the resulting+    binary should only be used with old git.+  * Updated to build with monad-control 0.3.++ -- Joey Hess <joeyh@debian.org>  Sat, 31 Dec 2011 14:55:29 -0400+ git-annex (3.20111211) unstable; urgency=medium    * Fix bug in last version in getting contents from bare repositories.
debian/control view
@@ -13,7 +13,8 @@ 	libghc-utf8-string-dev, 	libghc-hs3-dev (>= 0.5.6), 	libghc-testpack-dev [any-i386 any-amd64],-	libghc-monad-control-dev,+	libghc-monad-control-dev (>= 0.3),+	libghc-lifted-base-dev, 	libghc-json-dev, 	ikiwiki, 	perlmagick,
+ doc/bugs/Remote_repo_and_set_operation_with_find.mdwn view
@@ -0,0 +1,6 @@+Currently, git annex find lists files that are present in the current repository, possibly restricted to a subdirectory. But it does not easily seem possible to get this information about a remote repository.++I would find it useful if this command understood flags that makes it tell me what is present somewhere else (maybe "--on remote") and combinations of the flags ("--on remote1 --and --not-on remote2" or "--on disk1 --or --on disk2").++> Almost. You're looking for `--in remote`, which was added 2 months ago.+> [[done]] --[[Joey]] 
doc/bugs/git-annex_losing_rsync_remotes_with_encryption_enabled.mdwn view
@@ -99,3 +99,5 @@ > here's an already decrypted cipher -- it must be the right one! >  > Problem reproduced here, and fixed. [[done]] --[[Joey]]++THX Joey! -- [[gebi]]
+ doc/bugs/git_rename_detection_on_file_move/comment_6_f63de6fe2f7189c8c2908cc41c4bc963._comment view
@@ -0,0 +1,19 @@+[[!comment format=mdwn+ username="http://adamspiers.myopenid.com/"+ nickname="Adam"+ subject="extra level of indirection"+ date="2011-12-19T12:45:18Z"+ content="""+Surely this could be handled with an extra layer of indirection?++git-annex would ensure that every directory containing annexed data contains a new symlink `.git-annex` which points to `$git_root/.git/annex`.  Then every symlink to an annexed object uses a relative symlink via this: `.git_annex/objects/xx/yy/ZZZZZZZZZZ`.  Even though this symlink is relative, moving it to a different directory would not break anything: if the move destination directory already contained other annexed data, it would also already contain `.git-annex` so git-annex wouldn't need to do anything.  And if it didn't, git-annex would simply create a new `.git-annex` symlink there.++These `.git-annex` symlinks could either be added to `.gitignore`, or manually/automatically checked in to the current branch - I'm not sure which would be best.  There's also the option of using multiple levels of indirection:++    foo/bar/baz/.git-annex -> ../.git-annex+    foo/bar/.git-annex -> ../.git-annex+    foo/.git-annex -> ../.git-annex+    .git-annex -> .git/annex++I'm not sure whether this would bring any advantages.  It might bring a performance hit due to the kernel having to traverse more symlinks, but without benchmarking it's difficult to say how much.  I'd expect it only to be an issue with a large number of deep directory trees.+"""]]
+ doc/bugs/git_rename_detection_on_file_move/comment_7_7f20d0b2f6ed1c34021a135438037306._comment view
@@ -0,0 +1,10 @@+[[!comment format=mdwn+ username="http://joey.kitenet.net/"+ nickname="joey"+ subject="comment 7"+ date="2011-12-19T18:22:25Z"+ content="""+That seems an excellent idea, also eliminating the need for git annex fix after moving. ++However, I think CVS and svn have taught us the pain associated with a version control system putting something in every subdirectory. Would this pain be worth avoiding the minor pain of needing git annex fix and sometimes being unable to follow renames?+"""]]
+ doc/bugs/git_rename_detection_on_file_move/comment_8_6a00500b24ba53248c78e1ffc8d1a591._comment view
@@ -0,0 +1,21 @@+[[!comment format=mdwn+ username="http://adamspiers.myopenid.com/"+ nickname="Adam"+ subject="comment 8"+ date="2011-12-20T12:00:11Z"+ content="""+Personally I'd rather have working rename detection but I agree it's not 100% ideal to be littering multiple directories like this, so perhaps you could make it optional, e.g. based on a git config setting?++Here are a few more considerations, some in defence of the approach, some against it:++* `.git-annex` is hidden; `CVS/` is not.+* Unlike `CVS/` and `.svn/`, it's only a symlink, not a directory containing other files.+* It doesn't contain any data specific to that directory and could easily be regenerated if deleted accidentally or otherwise.+* If a whole directory containing `.git-annex` was moved within the repository:+    * git-annex would need to fix up these symlinks if and only if it's moved to a different depth within the tree.+    * However, if the multi-level indirection approach is used, `.git-annex` in any subdirectory is *always* a symlink to `../.git-annex` so instead you would need to check that all of the new ancestors contain this symlink too, and optionally remove any no longer needed symlinks.+    * In either case, git-annex already goes to the trouble of fixing symlinks, and if anything, I *think* this approach would reduce the number of symlinks which need checking (right?)+* find `$git_root/foo -follow`, `diff -r` etc. would traverse into `$git_root/.git/annex`++This last point is the only downside to this approach I can think of which gives me any noticeable cause for concern.  However, people are already use to working around this from CVS and svn days, e.g. `diff -r -x .svn` so I don't think it's anywhere near bad enough to rule it out.+"""]]
+ doc/bugs/git_rename_detection_on_file_move/comment_9_75e0973f6d573df615e01005ebcea87d._comment view
@@ -0,0 +1,9 @@+[[!comment format=mdwn+ username="http://joey.kitenet.net/"+ nickname="joey"+ subject="comment 9"+ date="2011-12-20T14:56:12Z"+ content="""+Git can follow the rename fine if the file is committed before `git annex fix` (you can git commit -n to see this), so +making git-annex pre-commit generate a fixup commit before the staged commit would be one way. Or the other two ways I originally mentioned when writing down this minor issue. I like all those approaches better than .git-annex clutter.+"""]]
+ doc/bugs/problems_with_utf8_names/comment_1_3c7e3f021c2c94277eecf9c8af6cec5f._comment view
@@ -0,0 +1,17 @@+[[!comment format=mdwn+ username="http://adamspiers.myopenid.com/"+ nickname="Adam"+ subject="Any update on this?"+ date="2011-12-24T01:05:07Z"+ content="""+I just noticed this issue, and was wondering what the current status is.++    % ls -l 04\ -\ Orixás.mp3+    -rw-r--r-- 1 adam users 8377816 Jul 12  2007 04 - Orixás.mp3+    % echo 04\ -\ Orixás.mp3 | od -c+    0000000   0   4       -       O   r   i   x 303 241   s   .   m   p   3+    0000020  \n+    0000021+    % git annex add 04\ -\ Orixás.mp3+    git-annex: /home/adam/music/RotC/transcribe/04 - Orixás.mp3: getSymbolicLinkStatus: does not exist (No such file or directory)+"""]]
+ doc/bugs/problems_with_utf8_names/comment_2_bad4c4c5f54358d1bc0ab2adc713782a._comment view
@@ -0,0 +1,10 @@+[[!comment format=mdwn+ username="http://www.joachim-breitner.de/"+ nickname="nomeata"+ subject="comment 2"+ date="2011-12-24T12:49:40Z"+ content="""+This (rather longish) thread discusses the current situation, the planned changes for 7.2 and the various issues: http://haskell.org/pipermail/glasgow-haskell-users/2011-November/021115.html++The summary seems to be: From 7.2 on, getDirectoryContents _will_ return proper Strings, i.e. where a Char represents a Unicode code point, and not a Word8, which will fix the problem of outputting them.+"""]]
+ doc/bugs/problems_with_utf8_names/comment_3_4f936a5d3f9c7df64c8a87e62b7fbfdc._comment view
@@ -0,0 +1,8 @@+[[!comment format=mdwn+ username="http://www.joachim-breitner.de/"+ nickname="nomeata"+ subject="comment 3"+ date="2011-12-24T12:51:43Z"+ content="""+An alternative that is available from ghc 7.4 on is a pure ByteString based unix API: http://thread.gmane.org/gmane.comp.lang.haskell.libraries/16556+"""]]
+ doc/bugs/problems_with_utf8_names/comment_4_93bee35f5fa7744834994bc7a253a6f9._comment view
@@ -0,0 +1,10 @@+[[!comment format=mdwn+ username="http://joey.kitenet.net/"+ nickname="joey"+ subject="comment 4"+ date="2011-12-24T16:49:13Z"+ content="""+Adam, this bug was fixed a long time ago, first using option #2 above, but later switching to option #3 -- git-annex treats filenames as opaque binary blobs and never decodes them in any encoding; haskell's normal encoding support for stdio is disabled. ++And it never resulted in a failure like you show. I cannot reproduce your problem, but it is a different bug, please open a new bug report.+"""]]
doc/distributed_version_control.mdwn view
@@ -12,7 +12,7 @@ information is committed to git, to let repositories inform other repositories what file contents they have available. ---+---  The [[walkthrough]] shows how to create a distributed set of git-annex repositories with no central repository.
+ doc/forum/A_really_stupid_question.mdwn view
@@ -0,0 +1,3 @@+Sorry, but all this wiki and the manpage seem to gloss over the most obvious question:++What happens when you commit conflicting edits in different repositories?
+ doc/forum/A_really_stupid_question/comment_1_40e02556de0b00b94f245a0196b5a89f._comment view
@@ -0,0 +1,31 @@+[[!comment format=mdwn+ username="http://joey.kitenet.net/"+ nickname="joey"+ subject="Good question!"+ date="2011-12-20T23:07:25Z"+ content="""+You get a regular git merge conflict, which can be resolved in any of the regular ways, except that conflicting files are just symlinks.++Example:++<pre>+$ git pull+...+Auto-merging myfile+CONFLICT (add/add): Merge conflict in myfile+Automatic merge failed; fix conflicts and then commit the result.+$ git status+# On branch master+# Your branch and 'origin/master' have diverged,+# and have 1 and 1 different commit(s) each, respectively.+#+# Unmerged paths:+#   (use \"git add/rm <file>...\" as appropriate to mark resolution)+#+#	both added:         myfile+#+no changes added to commit (use \"git add\" and/or \"git commit -a\")+$ git add myfile+$ git commit -m \"took local version of the conflicting file\"+</pre>+"""]]
+ doc/forum/git_pull_remote_git-annex/comment_3_1aa89725b5196e40a16edeeb5ccfa371._comment view
@@ -0,0 +1,14 @@+[[!comment format=mdwn+ username="https://www.google.com/accounts/o8/id?id=AItOawnXybLxkPMYpP3yw4b_I6IdC3cKTD-xEdU"+ nickname="Matt"+ subject="comment 3"+ date="2011-12-21T16:06:25Z"+ content="""+hmmmm - I'm still not sure I get this.++If I'm using a whole bunch of distributed annexs with no central repo, then I can not do a `git pull remote` without either specifying the branch to use or changing default tracked remote via `git branch --set-upstream`.  The former like you note doesn't pull the git-annex branch down the latter only works one-at-a-time.++The docs read to me as though I ought to be able to do a `git pull remote ; git annex get .` using anyone of my distributed annexs.++Am I doing something wrong?  Or is the above correct?+"""]]
+ doc/forum/git_pull_remote_git-annex/comment_4_646f2077edcabc000a7d9cb75a93cf55._comment view
@@ -0,0 +1,37 @@+[[!comment format=mdwn+ username="http://adamspiers.myopenid.com/"+ nickname="Adam"+ subject="I think Matt is right."+ date="2011-12-23T14:04:44Z"+ content="""+I got bitten by this too.  It seems that the user is expected to fetch+remote git-annex branches themselves, but this is not documented+anywhere.++The man page says of \"git annex merge\":++    Automatically merges any changes from remotes into the git-annex+    branch.++I am not a git newbie, but even so I had incorrectly assumed that git+annex merge would take care of pulling the git-annex branch from the+remote prior to merging, thereby ensuring all versions of the+git-annex branch would be merged, and that the location tracking data+would be synced across all peer repositories.++My master branches do not track any specific upstream branch, because+I am operating in a decentralized fashion.  Therefore the error+message caused by `git pull $remote` succeeded in encouraging me to+instead use `git pull $remote master`, and this excludes the git-annex+branch from the fetch.  Even worse, a git newbie might realise this+and be tempted to do `git pull $remote git-annex`.++Therefore I think it needs to be explicitly documented that++    git fetch $remote+    git merge $remote/master++is required when the local branch doesn't track an upstream branch.+Or maybe a `--fetch` option could be added to `git annex merge` to+perform the fetch from all remotes before running the merge(s).+"""]]
+ doc/forum/git_pull_remote_git-annex/comment_5_4f2a05ef6551806dd0ec65372f183ca4._comment view
@@ -0,0 +1,10 @@+[[!comment format=mdwn+ username="http://joey.kitenet.net/"+ nickname="joey"+ subject="comment 5"+ date="2011-12-23T16:50:26Z"+ content="""+My goal for `git-annex merge` is that users should not need to know about it, so it should not be doing expensive pulls.++I hope that `git annex sync` will grow some useful features to support fully distributed git usage, as being discussed in [[pure_git-annex_only_workflow]]. I still use centralized git to avoid these problems myself.+"""]]
+ doc/forum/git_pull_remote_git-annex/comment_6_3925d1aa56bce9380f712e238d63080f._comment view
@@ -0,0 +1,8 @@+[[!comment format=mdwn+ username="http://adamspiers.myopenid.com/"+ nickname="Adam"+ subject="comment 6"+ date="2011-12-23T17:14:03Z"+ content="""+Extending `git annex sync` would be nice, although auto-commit does not suit every use case, so it would be better not to couple one to the other.+"""]]
+ doc/forum/git_pull_remote_git-annex/comment_7_24c45ee981b18bc78325c768242e635d._comment view
@@ -0,0 +1,8 @@+[[!comment format=mdwn+ username="http://adamspiers.myopenid.com/"+ nickname="Adam"+ subject="comment 7"+ date="2011-12-23T17:24:58Z"+ content="""+P.S. I see you already [fixed the docs](http://source.git-annex.branchable.com/?p=source.git;a=commitdiff;h=a0227e81f9c82afc12ac1bd1cecd63cc0894d751) - thanks! :)+"""]]
+ doc/forum/git_pull_remote_git-annex/comment_8_7e76ee9b6520cbffaf484c9299a63ad3._comment view
@@ -0,0 +1,12 @@+[[!comment format=mdwn+ username="http://joey.kitenet.net/"+ nickname="joey"+ subject="git tweak-fetch"+ date="2011-12-26T18:50:35Z"+ content="""+The git tweak-fetch hook that I have been developing, and hope will be accepted into git soon, provides some abilities that could be used to make \"git pull remote\" always merge remote/master. Normall, git can only be configured to do that merge automatically for one remote (ie, origin). But the tweak-fetch hook can flag arbitrary branches as needing merge. ++So, it could always flag tracking branches of the currently checked out branch for merge. This would be enabled by some setting, probably, since it's not necessarily the case that everyone wants to auto-merge when they pull like this. (Which is why git doesn't do it by default after all.)++(The tweak-fetch hook will also entirely eliminate the need to run git annex merge manually, since it can always take care of merging the git-annex branch.)+"""]]
+ doc/forum/pure_git-annex_only_workflow/comment_10_683768c9826b0bf0f267e8734b9eb872._comment view
@@ -0,0 +1,8 @@+[[!comment format=mdwn+ username="http://www.joachim-breitner.de/"+ nickname="nomeata"+ subject="Finally some code"+ date="2011-12-29T19:58:31Z"+ content="""+The repository at http://git.nomeata.de/?p=git-annex.git;a=summary contains changes to Commands/Sync.hs (and to the manpage) that implements this behavior. The functionality should be fine; the progress output is not very nice yet, but I’m not sure if I really understood the various Command types. It also should be more easily discoverable how to activate the behavior (by running \"git branch synced/master\") by providing a helpful message, at least unless git annex init creates the branch by default.+"""]]
+ doc/forum/pure_git-annex_only_workflow/comment_11_6b541ed834ef45606f3b98779a25a148._comment view
@@ -0,0 +1,30 @@+[[!comment format=mdwn+ username="http://joey.kitenet.net/"+ nickname="joey"+ subject="comment 11"+ date="2011-12-30T21:49:06Z"+ content="""+OMG, my first sizable haskell patch!++So trying this out..++In each repo I want to sync, I first `git branch synced/master`++Then in each repo, I found I had to pull from each of its remotes, to get the tracking branches that `defaultSyncRemotes` looks for to know those remotes are syncable. This was the surprising thing for me, I had expected sync to somehow work out which remotes were syncable without my explicit pull. And it was not very obvious that sync was not doing its thing before I did that, since it still does a lot of \"stuff\".++Once set up properly, `git annex sync` fetches from each remote, merges, and then pushes to each remote that has a synced branch. Changes propigate around even when some links are one-directional. Cool!++So it works fine, but I think more needs to be done to make setting up syncing easier. Ideally, all a user would need to do is run \"git annex sync\" and it syncs from all remotes, without needing to manually set up the synced/master branch.++While this would lose the ability to control which remotes are synced, I think that being able to `git annex sync origin` and only sync from/to origin is sufficient, for the centralized use case.++---++Code review:++Why did you make `branch` strict?++There is a bit of a bug in your use of Command.Merge.start. The git-annex branch merge code only runs once per git-annex run, and often this comes before sync fetches from the remotes, leading to a push conflict. I've fixed this in my \"sync\" branch, along with a few other minor things.++`mergeRemote` merges from `refs/remotes/foo/synced/master`. But that will only be up-to-date if `git annex sync` has recently been run there. Is there any reason it couldn't merge from `refs/remotes/foo/master`?+"""]]
+ doc/forum/pure_git-annex_only_workflow/comment_12_ca8ca35d6cd4a9f94568536736c12adc._comment view
@@ -0,0 +1,10 @@+[[!comment format=mdwn+ username="http://joey.kitenet.net/"+ nickname="joey"+ subject="comment 12"+ date="2011-12-30T23:45:57Z"+ content="""+I have made a new `autosync` branch, where all that the user needs to do is run `git annex sync` and it automatically sets up the synced/master branch. I find this very easy to use, what do you think?++Note that `autosync` is also pretty smart about not running commands like \"git merge\" and \"git push\" when they would not do anything. So you may find `git annex sync` not showing all the steps you'd expect. The only step a sync always performs now is pulling from the remotes.+"""]]
+ doc/forum/pure_git-annex_only_workflow/comment_13_00c82d320c7b4bb51078beba17e14dc8._comment view
@@ -0,0 +1,8 @@+[[!comment format=mdwn+ username="http://joey.kitenet.net/"+ nickname="joey"+ subject="comment 13"+ date="2011-12-31T18:34:31Z"+ content="""+I have merged my autosync branch, the improved sync command will be in this year's last git-annex release!+"""]]
+ doc/forum/pure_git-annex_only_workflow/comment_14_b63568b327215ef8f646a39d760fdfc0._comment view
@@ -0,0 +1,32 @@+[[!comment format=mdwn+ username="http://www.joachim-breitner.de/"+ nickname="nomeata"+ subject="comment 14"+ date="2012-01-02T14:02:04Z"+ content="""+Sorry for not replying earlier, but my non-mailinglist-communications-workflows are suboptimal :-)++> Then in each repo, I found I had to pull from each of its remotes, to get the tracking branches that defaultSyncRemotes looks for to know those remotes are syncable.  This was the surprising thing for me, I had expected sync to somehow work out which remotes were syncable without my explicit pull. And it was not very obvious that sync was not doing its thing before I did that, since it still does a lot of \"stuff\".++Right. But \"git fetch\" ought to be enough.++Personally, I’d just pull and push everywhere, but you pointed out that it ought to be manageable. The existence of the synced/master branch is the flag that indicates this, so you need to propagate this once. Note that if the branch were already created by \"git annex init\", then this would not be a problem.++It is not required to use \"git fetch\" once, you can also call \"git annex sync <remote>\" once with the remote explicitly mentioned; this would involve a fetch.++> While this would lose the ability to control which remotes are synced, I think that being able to git annex sync origin and only sync from/to origin is sufficient, for the centralized use case.++I’d leave this decision to you. But I see that you took the decision already, as your code now creates the synced/master branch when it does not exist (e290f4a8).++> Why did you make branch strict?++Because it did not work otherwise :-). It uses pipeRead, which is lazy, and for some reason git and/or your utility functions did not like that the output of the command was not consumed before the next git command was called. I did not investigate further. For better code, I’d suggest to add a function like pipeRead that completely reads the git output before returning, thus avoiding any issues with lazyIO.++> mergeRemote merges from refs/remotes/foo/synced/master. But that will only be up-to-date if git annex sync has recently been run there. Is there any reason it couldn't merge from refs/remotes/foo/master?++Hmm, good question. It is probably save to merge from both, and push only to synced/master. But which one first? synced/master can be ahead if the repo was synced to from somewhere else, master can be ahead if there are local changes. Maybe git merge should be called on all remote heads simultaniously, thus generating only one commit for the merge. I don’t know how well that works in practice.++Thanks for including my code,+Joachim++"""]]
+ doc/forum/pure_git-annex_only_workflow/comment_15_cb7c856d8141b2de3cc95874753f1ee5._comment view
@@ -0,0 +1,12 @@+[[!comment format=mdwn+ username="http://joey.kitenet.net/"+ nickname="joey"+ subject="comment 15"+ date="2012-01-02T16:01:49Z"+ content="""+With a lazy branch, I get \"git-annex: no branch is checked out\". Weird.. my best guess is that it's because this is running at the seek stage, which is unusual, and the value is not used until a later stage and so perhaps the git command gets reaped by some cleanup code before its output is read.++(pipeRead is lazy because often it's used to read large quantities of data from git that are processed progressively.)++I did make it merge both branches, separately. It would be possible to do one single merge, but it's probably harder for the user to recover if there are conflicts in an octopus merge. The order of the merges does not seem to me to matter much, barring conflicts it will work either way. Dealing with conflicts during sync is probably a weakness of all this; after the first conflict the rest of the sync will continue failing.+"""]]
+ doc/forum/pure_git-annex_only_workflow/comment_4_dc8a3f75533906ad3756fcc47f7e96bb._comment view
@@ -0,0 +1,20 @@+[[!comment format=mdwn+ username="http://www.joachim-breitner.de/"+ nickname="nomeata"+ subject="comment 4"+ date="2011-12-13T18:16:08Z"+ content="""+I thought about this some more, and I think I have a pretty decent solution that avoids a central bare repository. Instead of pushing to master (which git does not like) or trying to guess the remote branch name on the other side, there is a well-known branch name, say git-annex-master. Then a sync command would do something like this (untested):++    git commit -a -m 'git annex sync' # ideally with a description derived from the diff+    git merge git-annex-master+    git pull someremote git-annex-master # for all reachable remotes. Or better to use fetch and then merge everything in one command?+    git branch -f git-annex-master # (or checkout git-annex-master, merge master, checkout master, but since we merged before this should have the same effect+    git annex merge+    git push someremote git-annex-master # for all reachable remotes++The nice things are: One can push to any remote repository, and thus avoid the issue of pushing to a portable device; the merging happens on the master branch, so if it fails to merge automatically, regular git foo can resolve it, and all changes eventually reach every repository.++What do you think?+    +"""]]
+ doc/forum/pure_git-annex_only_workflow/comment_5_afe5035a6b35ed2c7e193fb69cc182e2._comment view
@@ -0,0 +1,24 @@+[[!comment format=mdwn+ username="http://www.joachim-breitner.de/"+ nickname="nomeata"+ subject="comment 5"+ date="2011-12-13T18:47:18Z"+ content="""+After some experimentation, this seems to work better:++    	git commit -a -m 'git annex sync'+	git merge git-annex-master+	for remote in $(git remote)+	do+		git fetch $remote+		git merge $remote git-annex-master+	done+	git branch -f git-annex-master+	git annex merge+	for remote in $(git remote)+	do+		git push $remote git-annex git-annex-master+	done++Maybe this approach can be enhance to skip stuff gracefully if there is no git-annex-master branch and then be added to what \"git annex sync\" does, this way those who want to use the feature can do so by running \"git branch git-annex-master\" once. Or, if you like this and want to make it default, just make git-annex-init create the git-annex-master branch :-)+"""]]
+ doc/forum/pure_git-annex_only_workflow/comment_6_3660d45c5656f68924acbd23790024ee._comment view
@@ -0,0 +1,12 @@+[[!comment format=mdwn+ username="http://joey.kitenet.net/"+ nickname="joey"+ subject="comment 6"+ date="2011-12-13T20:53:23Z"+ content="""+It would be clearer to call \"git-annex-master\" \"synced/master\" (or really \"synced/$current_branch\"). That does highlight that this method of syncing is not particularly specific to git-annex.++I think this would be annoying to those who do use a central bare repository, because of the unnecessary pushing and pulling to other repos, which could be expensive to do, especially if you have a lot of interconnected repos. So having a way to enable/disable it seems best.++Maybe you should work up a patch to Command/Sync.hs, since I know you know haskell :)+"""]]
+ doc/forum/pure_git-annex_only_workflow/comment_7_33db51096f568c65b22b4be0b5538c0d._comment view
@@ -0,0 +1,15 @@+[[!comment format=mdwn+ username="http://www.joachim-breitner.de/"+ nickname="nomeata"+ subject="comment 7"+ date="2011-12-18T12:08:51Z"+ content="""+I agree on the naming suggestions, and that it does not suit everybody. Maybe I’ll think some more about it. The point is: I’m trying to make live easy for those who do not want to manually create some complicated setup, so if it needs configuration, it is already off that track. But turning the current behavior into something people have to configure is also not well received by the users.++Given that \"git annex sync\" is a new command, maybe it is fine to have this as a default behavior, and offer an easy way out. The easy way out could be one of two flags that can be set for a repo (or a remote):++* \"central\", which makes git annex sync only push and pull to and that repo (unless a different remote is given on the command line)+* \"unsynced\", which makes git annex sync skip the repo.++Maybe central is enough.+"""]]
+ doc/forum/pure_git-annex_only_workflow/comment_8_6e5b42fdb7801daadc0b3046cbc3d51e._comment view
@@ -0,0 +1,12 @@+[[!comment format=mdwn+ username="http://joey.kitenet.net/"+ nickname="joey"+ subject="comment 8"+ date="2011-12-19T18:29:01Z"+ content="""+I don't mind changing the behavior of git-annex sync, certianly..++Looking thru git's documentation, I found some existing configuration that could be reused following your idea.+There is a remote.name.skipDefaultUpdate and a remote.name.skipFetchAll. Though both have to do with fetches, not pushes.+Another approach might be to use git's remote group stuff.+"""]]
+ doc/forum/pure_git-annex_only_workflow/comment_9_ace319652f9c7546883b5152ddc82591._comment view
@@ -0,0 +1,14 @@+[[!comment format=mdwn+ username="http://www.joachim-breitner.de/"+ nickname="nomeata"+ subject="comment 9"+ date="2011-12-19T22:56:26Z"+ content="""+Another option that would please the naive user without hindering the more advanced user: \"git annex init\", by default, creates a synced/master branch. \"git annex sync\" will pull from every <remote>/sync/master branch it finds, and also push to any <remote>/sync/master branch it finds, but will not create any. So by default (at least for new users), this provides simple one-step syncing.++Advanced users can disable this per-repo by just deleting the synced/master branch. Presumably the logic will be: Every repo that should not be pushed to, because it has access to some central repo, should not have a synced/master branch. Every other repo, including the (or one of the few) central repos, will have the branch.++This is not the most expressive solution, as it does not allow configuring syncing between arbitrary pairs of repos, but it feels like a good compromise between that and simplicity and transparency.++I think it's about time that I provide less talk and more code. I’ll see when I find the time :-)+"""]]
+ doc/forum/syncing_non-git_trees_with_git-annex.mdwn view
@@ -0,0 +1,46 @@+I have a bunch of directory trees with large data files scattered over various computers and disk drives - they contain photos, videos, music, and so on.  In many cases I initially copied one of these trees from one machine to another just as a cheap and dirty backup, and then made small modifications to both trees in ways I no longer remember. For example, I returned from a trip with a bunch of new photos, and then might have rotated some of them 90 degrees on one machine, and edited or renamed them on another.++What I want to do now is use git-annex as a way of initially synchronising the trees, and then fully managing them on an ongoing basis.  Note that the trees are *not* yet git repositories.  In order to be able to detect straight-forward file renames, I believe that [[the SHA1 backend|tips/using_the_SHA1_backend]] probably makes the most sense.++I've been playing around and arrived at the following setup procedure.  For the sake of discussion, I assume that we have two trees `a` and `b` which live in the same directory referred to by `$td`, and that all large files end with the `.avi` suffix.++     # Setup git in 'a'.+     cd $td/a+     git init++     # Setup git-annex in 'a'.+     echo '* annex.backend=SHA1' > .gitattributes+     git add .gitattributes+     git commit -m'use SHA1 backend'+     git annex init++     # Annex all large files.+     find -name \*.avi | xargs git annex add+     git add .+     git commit -m'Initial import'++     # Setup git in 'b'.+     cd $td/b+     git clone -n $td/a new+     mv new/.git .+     rmdir new+     git reset # reset git index to b's wd - hangover from cloning from 'a'++     # Setup git-annex in 'b'.+     # This merges a's (origin's) git-annex branch into the local git-annex branch.+     git annex init++     # Annex all large files - because we're using SHA1 backend, some+     # should hash to the same keys as in 'a'.+     find -name \*.avi | xargs git annex add+     git add .+     git commit -m'Changes in b tree'++     git remote add a $td/a++     # Now pull changes in 'b' back to 'a'.+     cd $td/a+     git remote add b $td/b+     git pull b master++This seems to work, but have I missed anything?
+ doc/forum/using_git_annex_to_merge_and_synchronize_2_directories___40__like_unison__41__.mdwn view
@@ -0,0 +1,7 @@+I would like to use git-annex to synchronize 2 directories in the same manner as unison.++I'm starting with 2 directories. There is an overlap of the same set of files in each directory, but each directory also has additional files as well.++I create a git annex in each directory but when I do a git pull it merges and produces conflicts on those files that are the same.++What is the correct workflow for this type of scenario?
+ doc/forum/vlc_and_git-annex.mdwn view
@@ -0,0 +1,11 @@+I used to save movies with the srt subtitle files next to them. ++Usually vlc finds it because it's on the same directory than the movie file, however with git annex the link is located on another folder.+So after adding movies to git, the subtitles doesn't load anymore.++couldn't find a quick fix. I'm thinking a bash script, but wanted to discuss it here with all annex users.++I know It's out of annex scope, but I think a movie archive is a great scenario for git-annex.+most of my HD is filled up with movies from the camcorder, screencast, etc... +And we usually don't modify those files+
+ doc/forum/vlc_and_git-annex/comment_1_9c9ab8ce463cf74418aa2f385955f165._comment view
@@ -0,0 +1,10 @@+[[!comment format=mdwn+ username="http://joey.kitenet.net/"+ nickname="joey"+ subject="comment 1"+ date="2011-12-23T16:16:19Z"+ content="""+From what you say, it seems that vlc is following the symlink to the movie content, and then looking for subtitles next to the file the symlink points to. It would have to explicitly realpath the symlink to have this behavior, and this sounds like a misfeature.. perhaps you could point out to the vlc people the mistake in doing so?++There's a simple use-case where this behavior is obviously wrong, without involving git-annex. Suppose I have a movie, and one version of subtitles for it, in directory `foo`. I want to modify the subtitles, so I make a new directory `bar`, symlink the large movie file from `foo` to save space, and copy over and edit the subtitles from `foo`. Now I run vlc in `bar` to test my new subtitles. If it ignores the locally present subtitles and goes off looking for the ones in `bar`, I say this is broken behavior.+"""]]
+ doc/forum/vlc_and_git-annex/comment_2_037f94c1deeac873dbdb36cd4c927e45._comment view
@@ -0,0 +1,8 @@+[[!comment format=mdwn+ username="http://joey.kitenet.net/"+ nickname="joey"+ subject="comment 2"+ date="2011-12-23T18:43:05Z"+ content="""+Since subtitle files are typically pretty small, a workaround is to simply check them into git directly, and only use git-annex for the movies. (Or `git annex unannex` the ones you've already annexed.)+"""]]
doc/git-annex.mdwn view
@@ -120,17 +120,28 @@   Use this to undo an unlock command if you don't want to modify   the files, or have made modifications you want to discard. -* sync+* sync [remote ...] -  Use this command when you want to synchronize the local repository-  with its default remote (typically "origin"). The sync process involves-  first committing all local changes, then pulling and merging any changes-  from the remote, and finally pushing the repository's state to the remote.-  You can use standard git commands to do each of those steps by hand,-  or if you don't want to worry about the details, you can use sync.+  Use this command when you want to synchronize the local repository with+  one or more of its remotes. You can specifiy the remotes to sync with;+  the default is to sync with all remotes. Or specify --fast to sync with+  the remotes with the lowest annex-cost value. -  Note that sync does not transfer any file contents from or to the remote.+  The sync process involves first committing all local changes, then+  fetching and merging the `synced/master` and the `git-annex` branch+  from the remote repositories and finally pushing the changes back to+  those branches on the remote repositories. You can use standard git+  commands to do each of those steps by hand, or if you don't want to+  worry about the details, you can use sync. +  Note that syncing with a remote will not update the remote's working+  tree with changes made to the local repository. However, those changes+  are pushed to the remote, so can be merged into its working tree+  by running "git annex sync" on the remote.++  Note that sync does not transfer any file contents from or to the remote+  repositories.+ * addurl [url ...]    Downloads each url to a file, which is added to the annex.@@ -218,10 +229,10 @@  * merge -  Automatically merges any changes from remotes into the git-annex branch.-  While git-annex mostly handles keeping the git-annex branch merged-  automatically, if you find you are unable to push the git-annex branch-  due non-fast-forward, this will fix it.+  Automatically merges remote tracking branches */git-annex into+  the git-annex branch. While git-annex mostly handles keeping the+  git-annex branch merged automatically, if you find you are unable+  to push the git-annex branch due non-fast-forward, this will fix it.  * fix [path ...] @@ -241,16 +252,21 @@  * find [path ...] -  Outputs a list of annexed files whose content is currently present.-  Or, if a file matching option is specified, outputs a list of all-  matching files, whether or not their content is currently present.+  Outputs a list of annexed files in the specified path. With no path,+  finds files in the current directory and its subdirectories. -  With no parameters, defaults to finding all files in the current directory-  and its subdirectories.+  By default, only lists annexed files whose content is currently present.+  This can be changed by specifying file matching options. To list all+  annexed files, present or not, specify --include "*". To list all+  annexed files whose content is not present, specify --not --in="."    To output filenames terminated with nulls, for use with xargs -0,-  specify --print0.+  specify --print0. Or, a custom output formatting can be specified using+  --format. The default output format is the same as --format='${file}\\n' +  These variables are available for use in formats: file, key, backend,+  bytesize, humansize+ * whereis [path ...]    Displays a list of repositories known to contain the content of the@@ -268,9 +284,10 @@   Helps you keep track of your repositories, and the connections between them,   by going out and looking at all the ones it can get to, and generating a   Graphviz file displaying it all. If the `dot` command is available, it is-  used to display the file to your screen (using x11 backend).+  used to display the file to your screen (using x11 backend). (To disable+  this display, specify --fast) -  Note that this only connects to hosts that the host it's run on can+  This command only connects to hosts that the host it's run on can   directly connect to. It does not try to tunnel through intermediate hosts.   So it might not show all connections between the repositories in the network. @@ -426,6 +443,16 @@   are in the annex, their backend is known and this option is not   necessary. +* --format=value++  Specifies a custom output format. The value is a format string, +  in which '${var}' is expanded to the value of a variable. To right-justify+  a variable with whitespace, use '${var;width}' ; to left-justify+  a variable, use '${var;-width}'; to escape unusual characters in a variable,+  use '${escaped_var}'++  Also, '\\n' is a newline, '\\000' is a NULL, etc.+ * -c name=value    Used to override git configuration settings. May be specified multiple times.@@ -438,7 +465,7 @@ Arbitrarily complicated expressions can be built using these options. For example: -	--exclude '*.mp3' --and --not -( --in usbdrive --or --in archive -)+	--exclude '*.mp3' --and --not -( --in=usbdrive --or --in=archive -)  The above example prevents git-annex from working on mp3 files whose file contents are present at either of two repositories.@@ -446,7 +473,16 @@ * --exclude=glob    Skips files matching the glob pattern. The glob is matched relative to-  the current directory. For example: --exclude='*.mp3' --exclude='subdir/*'+  the current directory. For example:++	--exclude='*.mp3' --exclude='subdir/*'++* --include=glob++  Skips files not matching the glob pattern.  (Same as --not --exclude.)+  For example, to include only mp3 and ogg files:++	--include='*.mp3' --or --include='*.ogg'  * --in=repository 
doc/index.mdwn view
@@ -45,6 +45,7 @@ * [[git-annex man page|git-annex]] * [[key-value backends|backends]] for data storage * [[special_remotes]] (including [[special_remotes/S3]] and [[special_remotes/bup]])+* [[sync]] * [[encryption]] * [[bare_repositories]] * [[internals]]
doc/install.mdwn view
@@ -5,6 +5,7 @@ * [[Ubuntu]] * [[Fedora]] * [[FreeBSD]]+* [[openSUSE]]  ## Using cabal @@ -24,6 +25,7 @@   * [SHA](http://hackage.haskell.org/package/SHA)   * [dataenc](http://hackage.haskell.org/package/dataenc)   * [monad-control](http://hackage.haskell.org/package/monad-control)+  * [lifted-base](http://hackage.haskell.org/package/lifted-base)   * [TestPack](http://hackage.haskell.org/cgi-bin/hackage-scripts/package/testpack)   * [QuickCheck 2](http://hackage.haskell.org/package/QuickCheck)   * [HTTP](http://hackage.haskell.org/package/HTTP)
+ doc/install/openSUSE.mdwn view
@@ -0,0 +1,4 @@+Unfortunately there is currently no git-annex rpm available for openSUSE; however it is possible to build it via cabal or from source as described on the [[install]] page.  Fulfilling the dependencies listed on that page should not be a problem, except for obtaining a suitable version of the Haskell library.++The last [official release of Haskell for openSUSE](https://build.opensuse.org/project/show?project=devel:languages:haskell) is quite old, and may not satisfy the dependencies needed by git-annex.  Fortunately [searching the openSUSE build service](http://software.opensuse.org/search?q=cabal&baseproject=openSUSE%3A11.4&lang=en&include_home=true&exclude_debug=true) reveals that Peter Trommler has built a [newer Haskell suite](https://build.opensuse.org/project/show?project=home%3Aptrommler%3Adevel%3Alanguages%3Ahaskell) based on ghc 7.2.+To install this, simply click on the relevant "1-Click Install" link in the openSUSE build service search results.
doc/internals.mdwn view
@@ -77,7 +77,7 @@ 	1287290776.765152s 1 e605dca6-446a-11e0-8b2a-002170d25c55 	1287290767.478634s 0 26339d22-446b-11e0-9101-002170d25c55 -These files are designed to be auto-merged using git's union merge driver.+These files are designed to be auto-merged using git's [[union merge driver|git-union-merge]]. The timestamps allow the most recent information to be identified.  ## `remote/web/aaa/bbb/*.log`
+ doc/news/version_3.20111122~bpo60+2.mdwn view
@@ -0,0 +1,22 @@+git-annex 3.20111122~bpo60+2 released with [[!toggle text="these changes"]]+[[!toggleable text="""+   * merge: Improve commit messages to mention what was merged.+   * Avoid doing auto-merging in commands that don't need fully current+     information from the git-annex branch. In particular, git annex add+     no longer needs to auto-merge.+   * init: When run in an already initalized repository, and without+     a description specified, don't delete the old description.+   * Optimised union merging; now only runs git cat-file once, and runs+     in constant space.+   * status: Now displays trusted, untrusted, and semitrusted repositories+     separately.+   * status: Include all special remotes in the list of repositories.+   * status: Fix --json mode.+   * status: --fast is back+   * Fix support for insteadOf url remapping. Closes: #[644278](http://bugs.debian.org/644278)+   * When not run in a git repository, git-annex can still display a usage+     message, and "git annex version" even works.+   * migrate: Don't fall over a stale temp file.+   * Avoid excessive escaping for rsync special remotes that are not accessed+     over ssh.+   * find: Support --print0"""]]
+ doc/news/version_3.20111231.mdwn view
@@ -0,0 +1,24 @@+git-annex 3.20111231 released with [[!toggle text="these changes"]]+[[!toggleable text="""+   * sync: Improved to work well without a central bare repository.+     Thanks to Joachim Breitner.+   * Rather than manually committing, pushing, pulling, merging, and git annex+     merging, we encourage you to give "git annex sync" a try.+   * sync --fast: Selects some of the remotes with the lowest annex.cost+     and syncs those, in addition to any specified at the command line.+   * Union merge now finds the least expensive way to represent the merge.+   * reinject: Add a sanity check for using an annexed file as the source file.+   * Properly handle multiline git config values.+   * Fix the hook special remote, which bitrotted a while ago.+   * map: --fast disables use of dot to display map+   * Test suite improvements. Current top-level test coverage: 75%+   * Improve deletion of files from rsync special remotes. Closes: #[652849](http://bugs.debian.org/652849)+   * Add --include, which is the same as --not --exclude.+   * Format strings can be specified using the new --format option, to control+     what is output by git annex find.+   * Support git annex find --json+   * Fixed behavior when multiple insteadOf configs are provided for the+     same url base.+   * Can now be built with older git versions (before 1.7.7); the resulting+     binary should only be used with old git.+   * Updated to build with monad-control 0.3."""]]
+ doc/news/version_backport.mdwn view
@@ -0,0 +1,6 @@+git-annex backport released with [[!toggle text="these changes"]]+[[!toggleable text="""+   * Removed conflict on newer version of git, this backport can now be used+     with either stable's git, or the git backport.+   * This backport continues to be modified to not build without monad-control,+     which is not available in stable."""]]
+ doc/sync.mdwn view
@@ -0,0 +1,37 @@+The `git annex sync` command provides an easy way to keep several+repositories in sync. ++Often git is used in a centralized fashion with a central bare repositry+which changes are pulled and pushed to using normal git commands.+That works fine, if you don't mind having a central repository.++But it can be harder to use git in a fully decentralized fashion, with no+central repository and still keep repositories in sync with one another.+You have to remember to pull from each remote, and merge the appopriate+branch after pulling. It's difficult to *push* to a remote, since git does+not allow pushes into the currently checked out branch.++`git annex sync` makes it easier using a scheme devised by Joachim+Breitner. The idea is to have a branch `synced/master` (actually,+`synced/$currentbranch`), that is never directly checked out, and serves+as a drop-point for other repositories to use to push changes.++When you run `git annex sync`, it merges the `synced/master` branch+into `master`, receiving anything that's been pushed to it. Then it+fetches from each remote, and merges in any changes that have been made+to the remotes too. Finally, it updates `synced/master` to reflect the new+state of `master`, and pushes it out to each of the remotes.++This way, changes propigate around between repositories as `git annex sync`+is run on each of them. Every repository does not need to be able to talk+to every other repository; as long as the graph of repositories is+connected, and `git annex sync` is run from time to time on each, a given+change, made anywhere, will eventually reach every other repository.++The workflow for using `git annex sync` is simple:++* Make some changes to files in the repository, using `git-annex`,+  or anything else.+* Run `git annex sync` to save the changes.+* Next time you're working on a different clone of that repository,+  run `git annex sync` to update it.
+ doc/tips/centralised_repository:_starting_from_nothing.mdwn view
@@ -0,0 +1,75 @@+If you are starting from nothing (no existing `git` or `git-annex` repository) and want to use a server as a centralised repository, try the following steps.++On the server where you'll hold the "master" repository:++	server$ cd /one/git+	server$ mkdir m+	server$ cd m+	server$ git init --bare+	Initialized empty Git repository in /one/git/m/+	server$ git annex init origin+	init origin ok+	server$ ++Clone that to the laptop:++	laptop$ cd /other+	laptop$ git clone ssh://server//one/git/m+	Cloning into 'm'...+	Warning: No xauth data; using fake authentication data for X11 forwarding.+	remote: Counting objects: 5, done.        +	remote: Compressing objects: 100% (3/3), done.        +	remote: Total 5 (delta 0), reused 0 (delta 0)        +	Receiving objects: 100% (5/5), done.+	warning: remote HEAD refers to nonexistent ref, unable to checkout.++	laptop$ cd m+	laptop$ git annex init laptop+	init laptop ok+        laptop$ ++Merge the `git-annex` repository (this is the bit that is often+overlooked!):++	laptop$ git annex merge	+	merge . (merging "origin/git-annex" into git-annex...)+	ok+	laptop$ ++Add some content:++	laptop$ git annex addurl http://kitenet.net/~joey/screencasts/git-annex_coding_in_haskell.ogg+	"kitenet.net_~joey_screencasts_git-annex_coding_in_haskell.ogg"+	addurl kitenet.net_~joey_screencasts_git-annex_coding_in_haskell.ogg (downloading http://kitenet.net/~joey/screencasts/git-annex_coding_in_haskell.ogg ...) --2011-12-15 08:13:10--  http://kitenet.net/~joey/screencasts/git-annex_coding_in_haskell.ogg+	Resolving kitenet.net (kitenet.net)... 2001:41c8:125:49::10, 80.68.85.49+	Connecting to kitenet.net (kitenet.net)|2001:41c8:125:49::10|:80... connected.+	HTTP request sent, awaiting response... 200 OK+	Length: 39362757 (38M) [audio/ogg]+	Saving to: `/other/m/.git/annex/tmp/URL--http&c%%kitenet.net%~joey%screencasts%git-annex_coding_in_haskell.ogg'++	100%[======================================>] 39,362,757  2.31M/s   in 17s     ++	2011-12-15 08:13:27 (2.21 MB/s) - `/other/m/.git/annex/tmp/URL--http&c%%kitenet.net%~joey%screencasts%git-annex_coding_in_haskell.ogg' saved [39362757/39362757]++	(checksum...) ok+	(Recording state in git...)+	laptop$ git commit -m 'See Joey play.'+	[master (root-commit) 106e923] See Joey play.+	 1 files changed, 1 insertions(+), 0 deletions(-)+	  create mode 120000 kitenet.net_~joey_screencasts_git-annex_coding_in_haskell.ogg+	laptop$ ++All fine, now push it back to the centralised master:++	laptop$ git push+	Counting objects: 20, done.+	Delta compression using up to 4 threads.+	Compressing objects: 100% (11/11), done.+	Writing objects: 100% (18/18), 1.50 KiB, done.+	Total 18 (delta 1), reused 1 (delta 0)+	To ssh://server//one/git/m+	   3ba1386..ad3bc9e  git-annex -> git-annex+	laptop$ ++You can add more "client" repositories by following the `laptop`+sequence of operations.
+ doc/tips/finding_duplicate_files.mdwn view
@@ -0,0 +1,21 @@+Maybe you had a lot of files scattered around on different drives, and you+added them all into a single git-annex repository. Some of the files are+surely duplicates of others.++While git-annex stores the file contents efficiently, it would still+help in cleaning up this mess if you could find, and perhaps remove+the duplicate files.++Here's a command line that will show duplicate sets of files grouped together:++	git annex find --include '*' --format='${file} ${escaped_key}\n' | \+		sort -k2 | uniq --all-repeated=separate -f1 | \+		sed 's/ [^ ]*$//'++Here's a command line that will remove one of each duplicate set of files:++	git annex find --include '*' --format='${file} ${escaped_key}\n' | \+		sort -k2 | uniq --repeated -f1 | sed 's/ [^ ]*$//' | \+		xargs -d '\n' git rm++--[[Joey]] 
+ doc/tips/finding_duplicate_files/comment_1_ddb477ca242ffeb21e0df394d8fdf5d2._comment view
@@ -0,0 +1,8 @@+[[!comment format=mdwn+ username="http://adamspiers.myopenid.com/"+ nickname="Adam"+ subject="Cool"+ date="2011-12-23T19:16:50Z"+ content="""+Very nice :)  Just for reference, here's [my Perl implementation](https://github.com/aspiers/git-config/blob/master/bin/git-annex-finddups).  As per [this discussion](http://git-annex.branchable.com/todo/wishlist:_Provide_a___34__git_annex__34___command_that_will_skip_duplicates/#comment-fb15d5829a52cd05bcbd5dc53edaffb2) it would be interesting to benchmark these two approaches and see if one is substantially more efficient than the other w.r.t. CPU and memory usage.+"""]]
doc/tips/using_gitolite_with_git-annex.mdwn view
@@ -21,7 +21,7 @@ Set `$GL_ADC_PATH` in `.gitolite.rc`, if you have not already done so.  <pre>-echo '$GL_ADC_PATH = "/usr/local/lib/gitolite/adc/;' >>~gitolite/.gitolite.rc+echo '$GL_ADC_PATH = "/usr/local/lib/gitolite/adc/;"' >>~gitolite/.gitolite.rc </pre>  Make the ADC directory, and a "ua" subdirectory.@@ -75,3 +75,15 @@ total size is 2502  speedup is 0.95 ok </pre>+++### Troubleshooting++I got an error like this when setting up gitolite *after* setting up a local git repo and git annex:++<pre>+git-annex-shell: First run: git-annex init+Command ssh ["git@git.example.com","git-annex-shell 'configlist' '/~/myrepo.git'"] failed; exit code 1+</pre>++because I forgot to "git push --all" after adding the new gitolite remote.
+ doc/tips/using_gitolite_with_git-annex/comment_1_9a2a2a8eac9af97e0c984ad105763a73._comment view
@@ -0,0 +1,15 @@+[[!comment format=mdwn+ username="http://www.openid.albertlash.com/openid/"+ ip="71.178.29.218"+ subject="comment 1"+ date="2011-12-24T06:08:45Z"+ content="""+Looks like you are missing a closing double quote on the line:+++echo '$GL_ADC_PATH = \"/usr/local/lib/gitolite/adc/;' >>~gitolite/.gitolite.rc++right after /;++I got this working by the way - great stuff.+"""]]
+ doc/tips/using_gitolite_with_git-annex/comment_2_d8efea4ab9576555fadbb47666ecefa9._comment view
@@ -0,0 +1,8 @@+[[!comment format=mdwn+ username="http://joey.kitenet.net/"+ nickname="joey"+ subject="comment 2"+ date="2011-12-24T16:54:31Z"+ content="""+I've fixed the typo (anyone can edit pages in this wiki FWIW.)+"""]]
+ doc/tips/using_gitolite_with_git-annex/comment_3_807035f38509ccb9f93f1929ecd37417._comment view
@@ -0,0 +1,8 @@+[[!comment format=mdwn+ username="bremner"+ ip="156.34.79.193"+ subject="repo name conventions?"+ date="2011-12-30T21:41:13Z"+ content="""+I'm confused by the fact that the git-annex-shell adc rejects any repo names that don't start with /~/ since none of my repos start that way. It seems work ok if I just delete /\~ from the front of the regex, but I feel like I must be missing something. +"""]]
+ doc/tips/using_gitolite_with_git-annex/comment_4_eb81f824aadc97f098379c5f7e4fba4c._comment view
@@ -0,0 +1,33 @@+[[!comment format=mdwn+ username="http://joey.kitenet.net/"+ nickname="joey"+ subject="comment 4"+ date="2011-12-31T00:29:45Z"+ content="""+Well a repo url like `gitolite@localhost:testing` puts it in the gitolite user's /~/testing++This worked when I added the gitolite stuff, anyway.. Let's see if it still does:++<pre>+joey@gnu:~/tmp>mkdir g+joey@gnu:~/tmp>cd g+joey@gnu:~/tmp/g>git init+Initialized empty Git repository in /home/joey/tmp/g/.git/+joey@gnu:~/tmp/g>git annex init+init  ok+joey@gnu:~/tmp/g>git remote add test 'gitolite@localhost:testing'+joey@gnu:~/tmp/g>touch foo+joey@gnu:~/tmp/g>git annex add foo+add foo (checksum...) ok+(Recording state in git...)+joey@gnu:~/tmp/g>git annex copy foo --to test --debug+git [\"--git-dir=/home/joey/tmp/g/.git\",\"--work-tree=/home/joey/tmp/g\",\"ls-files\",\"--cached\",\"-z\",\"--\",\"foo\"]+git [\"--git-dir=/home/joey/tmp/g/.git\",\"--work-tree=/home/joey/tmp/g\",\"check-attr\",\"annex.numcopies\",\"-z\",\"--stdin\"]+git [\"--git-dir=/home/joey/tmp/g/.git\",\"--work-tree=/home/joey/tmp/g\",\"show-ref\",\"--hash\",\"refs/heads/git-annex\"]+git [\"--git-dir=/home/joey/tmp/g/.git\",\"--work-tree=/home/joey/tmp/g\",\"show-ref\",\"git-annex\"]+git [\"--git-dir=/home/joey/tmp/g/.git\",\"--work-tree=/home/joey/tmp/g\",\"cat-file\",\"--batch\"]+Running: ssh [\"-4\",\"gitolite@localhost\",\"git-annex-shell 'configlist' '/~/testing'\"]+</pre>++Still seems right, the ADC's regexp will match this the git-annex shell command.+"""]]
+ doc/tips/using_gitolite_with_git-annex/comment_5_f688309532d2993630e9e72e87fb9c46._comment view
@@ -0,0 +1,20 @@+[[!comment format=mdwn+ username="bremner"+ ip="156.34.79.193"+ subject="gitolite gets different paths for different urls"+ date="2011-12-31T01:50:49Z"+ content="""+I guess there is some path rewriting going in in gitolite proper because if try a url of the form +ssh://git@localhost/testing, then it still works with gitolite, but fails with the ADC because +the repo is passed as /testing:+<pre>+Running: ssh [\"git@host\",\"git-annex-shell 'configlist' '/recommend'\"]+Running: ssh [\"git@host\",\"git-annex-shell 'configlist' '/recommend'\"]+</pre>++What I have to ask Sitaram and or find in the docs is if this is a bug or a feature in gitolite.  I can see how the leading slash would get swallowed up by this line +<pre>+$repo = \"'$REPO_BASE/$repo.git'\"+</pre>+in gl-auth-command, but I guess that isn't the whole story.+"""]]
+ doc/tips/using_gitolite_with_git-annex/comment_6_3e203e010a4df5bf03899f867718adc5._comment view
@@ -0,0 +1,25 @@+[[!comment format=mdwn+ username="bremner"+ ip="156.34.79.193"+ subject="ssh://gitolite-host/repo-name is supposed to work"+ date="2011-12-31T03:34:17Z"+ content="""+I confirmed with Sitaram that this is intentional, if probably under-documented.+Since the ADC strips the leading /~/ in assigning $start anyway, I guess something like the following will  work +<pre>++diff --git a/contrib/adc/git-annex-shell b/contrib/adc/git-annex-shell+index 7f9f5b8..523dfed 100755+--- a/contrib/adc/git-annex-shell++++ b/contrib/adc/git-annex-shell+@@ -28,7 +28,7 @@ my $cmd=$ENV{SSH_ORIGINAL_COMMAND};+ # the second parameter.+ # Further parameters are not validated here (see below).+ die \"bad git-annex-shell command: $cmd\"+-    unless $cmd =~ m#^(git-annex-shell '\w+' ')/\~/([0-9a-zA-Z][0-9a-zA-Z._\@/+-++    unless $cmd =~ m#^(git-annex-shell '\w+' ')/(?:\~\/)?([0-9a-zA-Z][0-9a-zA-Z.+ my $start = $1;+ my $repo = $2;+ my $end = $3;+</pre>+"""]]
+ doc/tips/using_gitolite_with_git-annex/comment_7_f8fd08b6ab47378ad88c87348057220d._comment view
@@ -0,0 +1,10 @@+[[!comment format=mdwn+ username="http://joey.kitenet.net/"+ nickname="joey"+ subject="comment 7"+ date="2011-12-31T18:32:28Z"+ content="""+That patch seems ok, it doesn't seem to allow through any repo locations that were blocked before.++So, it has my blessing.. but the ADC is in gitolite and will need to be patched there.+"""]]
+ doc/tips/using_gitolite_with_git-annex/comment_8_8249772c142117f88e37975d058aa936._comment view
@@ -0,0 +1,29 @@+[[!comment format=mdwn+ username="bremner"+ ip="156.34.79.193"+ subject="afaict git annex normalizes urls on the client side."+ date="2011-12-31T22:29:38Z"+ content="""+After some debugging printing, here is my current understanding.++- urls of the form git@host:~repo or ssh://git@host++  - git sends commands like  \"git-receive-pack '~/repo'+  - gitolite converts these to $REPO_BASE/~/repo which fails.  ~/repo would also fail fwiw.+  - git-annex sends seems /~/repo, which works++- urls of the form git@host:/repo or ssh://git@host/repo++  - git sends \"git-receive-pack '/db/cs3383'\"+  - gitolite converts this to $REPO_BASE/repo which works+  - git annex sends \"git-annex-shell 'inannex' '/repo' ...\" which works, but only with the patch above.++- urls of the form git@host:repo++  - git sends \"git-receive-pack 'repo'+  - gitolite converts this to $REPO_BASE/repo, which works+  - git-annex sends \"git-annex-shell 'inannex' '/~/db/cs3383'...\", which also works for git-annex-shell.++So the weird case is the last one where git and git-annex are sending different things over the wire.+I don't know if you have other motivations for doing the url normalization on the client side, but it isn't needed for gitolite, and in some sense complicates things a little.  On the other hand, now that I see what is going on, it isn't a big deal to just strip the leading /~ off in the adc. It does lead to the odd situation of some URLs working for git-annex but not git.+"""]]
doc/todo/Please_add_support_for_monad-control_0.3.x.mdwn view
@@ -5,3 +5,5 @@ >  > There is now a branch in git called `new-monad-control` that will build > with the new monad-control. --[[Joey]]++>> Now merged to master. [[done]] --[[Joey]]
doc/todo/add_-all_option.mdwn view
@@ -5,7 +5,8 @@ This would be useful when a repository has a history with deleted files whose content you want to keep (so you're not using `dropunused`). Or when you have a lot of branches and just want to be able to fsck-every file referenced in any branch.+every file referenced in any branch. It could also be useful (or even a+good default) in a bare repository.  A problem with the idea is that `.gitattributes` values for keys not currently in the tree would not be available (without horrific anounts of
@@ -0,0 +1,52 @@+I have two repos, using SHA1 backend and both using git.+The first one is a laptop, the second one is a usb drive.++When I drop a file on the laptop repo, the file is not available on that repo until I run *git annex get*+But when the usb drive is plugged in the file is actually available.++How about adding a feature to link some/all files to the remote repo?++e.g. +We have *railscasts/196-nested-model-form-part-1.mp4* file added to git, and only available on the usb drive:++      $ git annex whereis 196-nested-model-form-part-1.mp4 +         whereis 196-nested-model-form-part-1.mp4 (1 copy) +  	   a7b7d7a4-2a8a-11e1-aebc-d3c589296e81 -- origin (Portable usb drive)++I can see the link with:++    $ cd railscasts+    $ ls -ls 196*+    8 lrwxr-xr-x  1 framallo  staff  193 Dec 20 05:49 196-nested-model-form-part-1.mp4 -> ../.git/annex/objects/Wz/6P/SHA256-s16898930--43679c67cd968243f58f8f7fb30690b5f3f067574e318d609a01613a2a14351e/SHA256-s16898930--43679c67cd968243f58f8f7fb30690b5f3f067574e318d609a01613a2a14351e++I save this in a variable just to make the example more clear:++    ID=".git/annex/objects/Wz/6P/SHA256-s16898930--43679c67cd968243f58f8f7fb30690b5f3f067574e318d609a01613a2a14351e/SHA256-s16898930--43679c67cd968243f58f8f7fb30690b5f3f067574e318d609a01613a2a14351e"++The file doesn't exist on the local repo:++    $ ls ../$ID+    ls: ../$ID: No such file or directory++however I can create a link to access that file on the remote repo.+First I create a needed dir:++    $ mkdir ../.git/annex/objects/Wz/6P/SHA256-s16898930--43679c67cd968243f58f8f7fb30690b5f3f067574e318d609a01613a2a14351e/++Then I link to the remote file:++    $ ln -s /mnt/usb_drive/repo_folder/$ID ../$ID++now I can open the file in the laptop repo.+++I think it could be easy to implement. Maybe It's a naive approach, but looks apealing.+Checking if it's a real file or a link shouldn't impact on performance.+The limitation is that it would work only with remote repos on local dirs++Also allows you to have one directory structure like AFS or other distributed FS. If the file is not local I go to the remote server. +Which is great for apps like Picasa, Itunes, and friends that depends on the file location.++> This is a duplicate of [[union_mounting]]. So closing it: [[done]]. +> +> It's a good idea, but making sure git-annex correctly handles these links in all cases is a subtle problem that has not yet been tackled. --[[Joey]]
doc/todo/wishlist:_Provide_a___34__git_annex__34___command_that_will_skip_duplicates.mdwn view
@@ -24,3 +24,5 @@ (Another way to do this would be to "git annex add" them all, and then use a "git annex remove-duplicates" that could prompt me about which files are duplicates of each other, and then I could pipe that command's output into xargs git rm.)  (As I write this, I realize it's possible to parse the destination of the symlink in a way that does this..)++> [[done]]; see [[tips/finding_duplicate_files]] --[[Joey]]
doc/walkthrough.mdwn view
@@ -8,6 +8,7 @@ 	adding_files 	renaming_files 	getting_file_content+	syncing 	transferring_files:_When_things_go_wrong 	removing_files 	removing_files:_When_things_go_wrong
doc/walkthrough/getting_file_content.mdwn view
@@ -6,14 +6,7 @@ USB drive.  	# cd /media/usb/annex-	# git pull laptop+	# git fetch laptop; git merge laptop/master 	# git annex get . 	get my_cool_big_file (from laptop...) ok 	get iso/debian.iso (from laptop...) ok--Notice that you had to git pull from laptop first, this lets git-annex know-what has changed in laptop, and so it knows about the files present there and-can get them. The alternate approach is to set up a central bare repository,-and always push changes to it after committing them, then in the above,-you can just pull from the central repository to get synced up to all-repositories.
+ doc/walkthrough/syncing.mdwn view
@@ -0,0 +1,25 @@+Notice that in the [[previous example|getting_file_content]], you had to+git fetch and merge from laptop first. This lets git-annex know what has+changed in laptop, and so it knows about the files present there and can+get them.++If you have a lot of repositories to keep in sync, manually fetching and +merging from them can become tedious. To automate it there is a handy+sync command, which also even commits your changes for you.++	# cd /media/usb/annex+	# git annex sync+	commit+	nothing to commit (working directory clean)+	ok+	pull laptop+	ok+	push laptop+	ok++After you run sync, the repository will be updated with all changes made to+its remotes, and any changes in the repository will be pushed out to its+remotes, where a sync will get them. This is especially useful when using+git in a distributed fashion, without a +[[central bare repository|tips/centralized_git_repository_tutorial]]. See+[[sync]] for details.
git-annex-shell.hs view
@@ -9,7 +9,7 @@ import System.Console.GetOpt  import Common.Annex-import qualified Git+import qualified Git.Construct import CmdLine import Command import Annex.UUID@@ -80,7 +80,7 @@ builtin cmd dir params = do 	checkNotReadOnly cmd 	dispatch (cmd : filterparams params) cmds options header $-		Git.repoAbsPath dir >>= Git.repoFromAbsPath+		Git.Construct.repoAbsPath dir >>= Git.Construct.fromAbsPath  external :: [String] -> IO () external params = do
git-annex.cabal view
@@ -1,5 +1,5 @@ Name: git-annex-Version: 3.20111211+Version: 3.20111231 Cabal-Version: >= 1.6 License: GPL Maintainer: Joey Hess <joey@kitenet.net>@@ -7,7 +7,7 @@ Stability: Stable Copyright: 2010-2011 Joey Hess License-File: GPL-Extra-Source-Files: ./debian/NEWS ./debian/control ./debian/rules ./debian/manpages ./debian/changelog ./debian/doc-base ./debian/copyright ./debian/compat ./Messages/JSON.hs ./configure.hs ./Annex.hs ./Seek.hs ./Crypto.hs ./Common.hs ./README ./Command/Merge.hs ./Command/AddUrl.hs ./Command/Whereis.hs ./Command/Unlock.hs ./Command/Version.hs ./Command/FromKey.hs ./Command/PreCommit.hs ./Command/Sync.hs ./Command/Map.hs ./Command/Unused.hs ./Command/Uninit.hs ./Command/Migrate.hs ./Command/Init.hs ./Command/ConfigList.hs ./Command/Trust.hs ./Command/Lock.hs ./Command/SendKey.hs ./Command/RecvKey.hs ./Command/Reinject.hs ./Command/Add.hs ./Command/Fix.hs ./Command/Untrust.hs ./Command/Dead.hs ./Command/InitRemote.hs ./Command/Semitrust.hs ./Command/Fsck.hs ./Command/Move.hs ./Command/DropUnused.hs ./Command/Get.hs ./Command/Upgrade.hs ./Command/Describe.hs ./Command/Drop.hs ./Command/DropKey.hs ./Command/InAnnex.hs ./Command/Find.hs ./Command/Unannex.hs ./Command/Status.hs ./Command/Copy.hs ./Init.hs ./Types.hs ./Common/Annex.hs ./Makefile ./git-annex.cabal ./Upgrade/V1.hs ./Upgrade/V2.hs ./Upgrade/V0.hs ./CHANGELOG ./Git/LsFiles.hs ./Git/UnionMerge.hs ./Git/CatFile.hs ./Git/Queue.hs ./Git/LsTree.hs ./doc/upgrades.mdwn ./doc/forum/Recommended_number_of_repositories.mdwn ./doc/forum/wishlist:_define_remotes_that_must_have_all_files.mdwn ./doc/forum/Will_git_annex_work_on_a_FAT32_formatted_key__63__.mdwn ./doc/forum/wishlist:_command_options_changes/comment_2_f6a637c78c989382e3c22d41b7fb4cc2._comment ./doc/forum/wishlist:_command_options_changes/comment_3_bf1114533d2895804e531e76eb6b8095._comment ./doc/forum/wishlist:_command_options_changes/comment_1_bfba72a696789bf21b2435dea15f967a._comment ./doc/forum/pure_git-annex_only_workflow/comment_1_a32f7efd18d174845099a4ed59e6feae._comment ./doc/forum/pure_git-annex_only_workflow/comment_3_9b7d89da52f7ebb7801f9ec8545c3aba._comment ./doc/forum/pure_git-annex_only_workflow/comment_2_66dc9b65523a9912411db03c039ba848._comment ./doc/forum/wishlist:_git-annex_replicate/comment_3_c13f4f9c3d5884fc6255fd04feadc2b1._comment ./doc/forum/wishlist:_git-annex_replicate/comment_1_9926132ec6052760cdf28518a24e2358._comment ./doc/forum/wishlist:_git-annex_replicate/comment_2_c43932f4194aba8fb2470b18e0817599._comment ./doc/forum/wishlist:_git-annex_replicate/comment_4_63f24abf086d644dced8b01e1a9948c9._comment ./doc/forum/Podcast_syncing_use-case/comment_1_ace6f9d3a950348a3ac0ff592b62e786._comment ./doc/forum/Podcast_syncing_use-case/comment_2_930a6620b4d516e69ed952f9da5371bb._comment ./doc/forum/Behaviour_of_fsck/comment_4_e4911dc6793f98fb81151daacbe49968._comment ./doc/forum/Behaviour_of_fsck/comment_3_97848f9a3db89c0427cfb671ba13300e._comment ./doc/forum/Behaviour_of_fsck/comment_2_ead36a23c3e6efa1c41e4555f93e014e._comment ./doc/forum/Behaviour_of_fsck/comment_1_0e40f158b3f4ccdcaab1408d858b68b8._comment ./doc/forum/incompatible_versions__63__/comment_1_629f28258746d413e452cbd42a1a43f4._comment ./doc/forum/Recommended_number_of_repositories/comment_1_3ef256230756be8a9679b107cdbfd018._comment ./doc/forum/migration_to_git-annex_and_rsync.mdwn ./doc/forum/hashing_objects_directories.mdwn ./doc/forum/git_annex_ls___47___metadata_in_git_annex_whereis.mdwn ./doc/forum/Podcast_syncing_use-case.mdwn ./doc/forum/hashing_objects_directories/comment_5_ef6cfd49d24c180c2d0a062e5bd3a0be._comment ./doc/forum/hashing_objects_directories/comment_1_c55c56076be4f54251b0b7f79f28a607._comment ./doc/forum/hashing_objects_directories/comment_2_504c96959c779176f991f4125ea22009._comment ./doc/forum/hashing_objects_directories/comment_3_9134bde0a13aac0b6a4e5ebabd7f22e8._comment ./doc/forum/hashing_objects_directories/comment_4_0de9170e429cbfea66f5afa8980d45ac._comment ./doc/forum/location_tracking_cleanup.mdwn ./doc/forum/Wishlist:_Ways_of_selecting_files_based_on_meta-information.mdwn ./doc/forum/confusion_with_remotes__44___map.mdwn ./doc/forum/git-annex_on_OSX.mdwn ./doc/forum/git_tag_missing_for_3.20111011/comment_1_7a53bf273f3078ab3351369ef2b5f2a6._comment ./doc/forum/version_3_upgrade/comment_1_05fc9c9cad26c520bebb98c852c71e35._comment ./doc/forum/example_of_massively_disconnected_operation.mdwn ./doc/forum/git-annex_communication_channels/comment_2_c7aeefa6ef9a2e75d8667b479ade1b7f._comment ./doc/forum/git-annex_communication_channels/comment_5_404b723a681eb93fee015cea8024b6bc._comment ./doc/forum/git-annex_communication_channels/comment_1_198325d2e9337c90f026396de89eec0e._comment ./doc/forum/git-annex_communication_channels/comment_4_1ba6ddf54843c17c7d19a9996f2ab712._comment ./doc/forum/git-annex_communication_channels/comment_6_0d87d0e26461494b1d7f8a701a924729._comment ./doc/forum/git-annex_communication_channels/comment_3_1ff08a3e0e63fa0e560cbc9602245caa._comment ./doc/forum/git-annex_communication_channels/comment_7_2c87c7a0648fe87c2bf6b4391f1cc468._comment ./doc/forum/wishlist:_git-annex_replicate.mdwn ./doc/forum/advantages_of_SHA__42___over_WORM/comment_1_96c354cac4b5ce5cf6664943bc84db1d._comment ./doc/forum/performance_improvement:_git_on_ssd__44___annex_on_spindle_disk.mdwn ./doc/forum/Behaviour_of_fsck.mdwn ./doc/forum/git_annex_add_crash_and_subsequent_recovery.mdwn ./doc/forum/working_without_git-annex_commits.mdwn ./doc/forum/migrate_existing_git_repository_to_git-annex.mdwn ./doc/forum/__34__git_annex_lock__34___very_slow_for_big_repo.mdwn ./doc/forum/wishlist:_traffic_accounting_for_git-annex.mdwn ./doc/forum/git_pull_remote_git-annex.mdwn ./doc/forum/rsync_over_ssh__63__.mdwn ./doc/forum/unannex_alternatives/comment_3_b1687fc8f9e7744327bbeb6f0635d1cd._comment ./doc/forum/unannex_alternatives/comment_1_dcd4cd41280b41512bbdffafaf307993._comment ./doc/forum/unannex_alternatives/comment_2_58a72a9fe0f58c7af0b4d7927a2dd21d._comment ./doc/forum/wishlist:_git_annex_status/comment_4_9aeeb83d202dc8fb33ff364b0705ad94._comment ./doc/forum/wishlist:_git_annex_status/comment_3_d1fd70c67243971c96d59e1ffb7ef6e7._comment ./doc/forum/wishlist:_git_annex_status/comment_2_c2b0ce025805b774dc77ce264a222824._comment ./doc/forum/wishlist:_git_annex_status/comment_1_994bfd12c5d82e08040d6116915c5090._comment ./doc/forum/git_pull_remote_git-annex/comment_2_0f7f4a311b0ec1d89613e80847e69b42._comment ./doc/forum/git_pull_remote_git-annex/comment_1_9c245db3518d8b889ecdf5115ad9e053._comment ./doc/forum/wishlist:_command_options_changes.mdwn ./doc/forum/location_tracking_cleanup/comment_3_c15428cec90e969284a5e690fb4b2fde._comment ./doc/forum/location_tracking_cleanup/comment_2_e7395cb6e01f42da72adf71ea3ebcde4._comment ./doc/forum/location_tracking_cleanup/comment_1_7d6319e8c94dfe998af9cfcbf170efb2._comment ./doc/forum/new_microfeatures/comment_2_41ad904c68e89c85e1fc49c9e9106969._comment ./doc/forum/new_microfeatures/comment_1_058bd517c6fffaf3446b1f5d5be63623._comment ./doc/forum/new_microfeatures/comment_3_a1a9347b5bc517f2a89a8b292c3f8517._comment ./doc/forum/new_microfeatures/comment_7_94045b9078b1fff877933b012d1b49e2._comment ./doc/forum/new_microfeatures/comment_5_3c627d275586ff499d928a8f8136babf._comment ./doc/forum/new_microfeatures/comment_4_5a6786dc52382fff5cc42fdb05770196._comment ./doc/forum/new_microfeatures/comment_6_31ea08c008500560c0b96c6601bc6362._comment ./doc/forum/Error_while_adding_a_file___34__createSymbolicLink:_already_exists__34__.mdwn ./doc/forum/advantages_of_SHA__42___over_WORM.mdwn ./doc/forum/wishlist:_git_annex_put_--_same_as_get__44___but_for_defaults.mdwn ./doc/forum/OSX__39__s_default_sshd_behaviour_has_limited_paths_set.mdwn ./doc/forum/bainstorming:_git_annex_push___38___pull.mdwn ./doc/forum/sparse_git_checkouts_with_annex.mdwn ./doc/forum/Need_new_build_instructions_for_Debian_stable.mdwn ./doc/forum/batch_check_on_remote_when_using_copy.mdwn ./doc/forum/tips:_special__95__remotes__47__hook_with_tahoe-lafs.mdwn ./doc/forum/wishlist:alias_system.mdwn ./doc/forum/incompatible_versions__63__.mdwn ./doc/forum/wishlist:_git_backend_for_git-annex.mdwn ./doc/forum/seems_to_build_fine_on_haskell_platform_2011.mdwn ./doc/forum/rsync_over_ssh__63__/comment_1_ee21f32e90303e20339e0a568321bbbe._comment ./doc/forum/rsync_over_ssh__63__/comment_2_aa690da6ecfb2b30fc5080ad76dc77b1._comment ./doc/forum/wishlist:_special_remote_for_sftp_or_rsync.mdwn ./doc/forum/OSX__39__s_haskell-platform_statically_links_things.mdwn ./doc/forum/git_tag_missing_for_3.20111011.mdwn ./doc/forum/unannex_alternatives.mdwn ./doc/forum/can_git-annex_replace_ddm__63__.mdwn ./doc/forum/sparse_git_checkouts_with_annex/comment_2_e357db3ccc4079f07a291843975535eb._comment ./doc/forum/sparse_git_checkouts_with_annex/comment_1_c7dc199c5740a0e7ba606dfb5e3e579a._comment ./doc/forum/sparse_git_checkouts_with_annex/comment_3_fcfafca994194d57dccf5319c7c9e646._comment ./doc/forum/sparse_git_checkouts_with_annex/comment_4_04dc14880f31eee2b6d767d4d4258c5a._comment ./doc/forum/git-annex_communication_channels.mdwn ./doc/forum/can_git-annex_replace_ddm__63__/comment_3_4c69097fe2ee81359655e59a03a9bb8d._comment ./doc/forum/can_git-annex_replace_ddm__63__/comment_2_008554306dd082d7f543baf283510e92._comment ./doc/forum/can_git-annex_replace_ddm__63__/comment_1_aa05008dfe800474ff76678a400099e1._comment ./doc/forum/version_3_upgrade.mdwn ./doc/forum/relying_on_git_for_numcopies/comment_3_43d8e1513eb9947f8a503f094c03f307._comment ./doc/forum/relying_on_git_for_numcopies/comment_2_be6acbc26008a9cb54e7b8f498f2c2a2._comment ./doc/forum/relying_on_git_for_numcopies/comment_1_8ad3cccd7f66f6423341d71241ba89fc._comment ./doc/forum/confusion_with_remotes__44___map/comment_1_a38ded23b7f288292a843abcb1a56f38._comment ./doc/forum/confusion_with_remotes__44___map/comment_2_cd1c98b1276444e859a22c3dbd6f2a79._comment ./doc/forum/confusion_with_remotes__44___map/comment_6_496b0d9b86869bbac3a1356d53a3dda4._comment ./doc/forum/confusion_with_remotes__44___map/comment_5_27801584325d259fa490f67273f2ff71._comment ./doc/forum/confusion_with_remotes__44___map/comment_4_3b89b6d1518267fcbc050c9de038b9ca._comment ./doc/forum/confusion_with_remotes__44___map/comment_3_18531754089c991b6caefc57a5c17fe9._comment ./doc/forum/confusion_with_remotes__44___map/comment_7_9a456f61f956a3d5e81e723d5a90794c._comment ./doc/forum/--print0_option_as_in___34__find__34__.mdwn ./doc/forum/relying_on_git_for_numcopies.mdwn ./doc/forum/wishlist:_do_round_robin_downloading_of_data.mdwn ./doc/forum/pure_git-annex_only_workflow.mdwn ./doc/forum/wishlist:_git_annex_status.mdwn ./doc/forum/getting_git_annex_to_do_a_force_copy_to_a_remote.mdwn ./doc/forum/Problems_with_large_numbers_of_files.mdwn ./doc/forum/Can_I_store_normal_files_in_the_git-annex_git_repository__63__.mdwn ./doc/forum/Is_an_automagic_upgrade_of_the_object_directory_safe__63__.mdwn ./doc/forum/wishlist:_push_to_cia.vc_from_the_website__39__s_repo__44___not_your_personal_one.mdwn ./doc/forum/new_microfeatures.mdwn ./doc/backends.mdwn ./doc/bugs/__39__annex_add__39___fails_to___39__git_add__39___for_parent_relative_path.mdwn ./doc/bugs/uuid.log_trust.log_and_remote.log_merge_wackiness.mdwn ./doc/bugs/softlink_mtime.mdwn ./doc/bugs/uninit_should_not_run_when_branch_git-annex_is_checked_out.mdwn ./doc/bugs/ordering.mdwn ./doc/bugs/dropping_files_with_a_URL_backend_fails.mdwn ./doc/bugs/fsck__47__fix_should_check__47__fix_the_permissions_of_.git__47__annex.mdwn ./doc/bugs/dropunused_doesn__39__t_handle_double_spaces_in_filename.mdwn ./doc/bugs/add_script-friendly_output_options.mdwn ./doc/bugs/uninit_does_not_work_in_old_repos.mdwn ./doc/bugs/git_annex_get_choke_when_remote_is_an_ssh_url_with_a_port.mdwn ./doc/bugs/unannex_and_uninit_do_not_work_when_git_index_is_broken.mdwn ./doc/bugs/git_annex_migrate_leaves_old_backend_versions_around.mdwn ./doc/bugs/git_annex_unused_failes_on_empty_repository.mdwn ./doc/bugs/git_annex_add_eats_files_when_filename_is_too_long.mdwn ./doc/bugs/S3_bucket_uses_the_same_key_for_encryption_and_hashing.mdwn ./doc/bugs/problem_with_upgrade_v2_-__62___v3.mdwn ./doc/bugs/git_annex_copy_--fast_does_not_copy_files.mdwn ./doc/bugs/Cabal_dependency_monadIO_missing.mdwn ./doc/bugs/dotdot_problem.mdwn ./doc/bugs/Problems_running_make_on_osx.mdwn ./doc/bugs/free_space_checking/comment_2_8a65f6d3dcf5baa3f7f2dbe1346e2615._comment ./doc/bugs/free_space_checking/comment_3_0fc6ff79a357b1619d13018ccacc7c10._comment ./doc/bugs/free_space_checking/comment_1_a868e805be43c5a7c19c41f1af8e41e6._comment ./doc/bugs/Error_when_moving_annexed_file_to_a_.gitignored_location.mdwn ./doc/bugs/done.mdwn ./doc/bugs/git_annex_copy_-f_REMOTE_._doesn__39__t_work_as_expected.mdwn ./doc/bugs/Displayed_copy_speed_is_wrong/comment_2_8b240de1d5ae9229fa2d77d1cc15a552._comment ./doc/bugs/Displayed_copy_speed_is_wrong/comment_1_74de3091e8bfd7acd6795e61f39f07c6._comment ./doc/bugs/Build_error_on_Mac_OSX_10.6.mdwn ./doc/bugs/bare_git_repos.mdwn ./doc/bugs/fat_support.mdwn ./doc/bugs/problems_with_utf8_names.mdwn ./doc/bugs/error_with_file_names_starting_with_dash.mdwn ./doc/bugs/not_possible_to_have_annex_on_a_separate_filesystem.mdwn ./doc/bugs/unhappy_without_UTF8_locale.mdwn ./doc/bugs/S3_memory_leaks.mdwn ./doc/bugs/git_annex_map_has_problems_with_urls_containing___126__.mdwn ./doc/bugs/interrupting_migration_causes_problems.mdwn ./doc/bugs/upgrade_left_untracked_.git-annex__47____42___directories.mdwn ./doc/bugs/cyclic_drop.mdwn ./doc/bugs/Problems_running_make_on_osx/comment_9_cc283b485b3c95ba7eebc8f0c96969b3._comment ./doc/bugs/Problems_running_make_on_osx/comment_4_c52be386f79f14c8570a8f1397c68581._comment ./doc/bugs/Problems_running_make_on_osx/comment_18_64fab50d95de619eb2e8f08f90237de1._comment ./doc/bugs/Problems_running_make_on_osx/comment_16_5c2dd6002aadaab30841b77a5f5aed34._comment ./doc/bugs/Problems_running_make_on_osx/comment_10_94e4ac430140042a2d0fb5a16d86b4e5._comment ./doc/bugs/Problems_running_make_on_osx/comment_15_6b8867b8e48bf807c955779c9f8f0909._comment ./doc/bugs/Problems_running_make_on_osx/comment_11_56f1143fa191361d63b441741699e17f._comment ./doc/bugs/Problems_running_make_on_osx/comment_20_7db27d1a22666c831848bc6c06d66a84._comment ./doc/bugs/Problems_running_make_on_osx/comment_12_ec5131624d0d2285d3b6880e47033f97._comment ./doc/bugs/Problems_running_make_on_osx/comment_3_68f0f8ae953589ae26d57310b40c878d._comment ./doc/bugs/Problems_running_make_on_osx/comment_14_89a960b6706ed703b390a81a8bc4e311._comment ./doc/bugs/Problems_running_make_on_osx/comment_17_62fccb04b0e4b695312f7a3f32fb96ee._comment ./doc/bugs/Problems_running_make_on_osx/comment_19_4253988ed178054c8b6400beeed68a29._comment ./doc/bugs/Problems_running_make_on_osx/comment_6_0c46f5165ceb5a7b9ea9689c33b3a4f8._comment ./doc/bugs/Problems_running_make_on_osx/comment_1_34120e82331ace01a6a4960862d38f2d._comment ./doc/bugs/Problems_running_make_on_osx/comment_7_237a137cce58a28abcc736cbf2c420b0._comment ./doc/bugs/Problems_running_make_on_osx/comment_5_7f1330a1e541b0f3e2192e596d7f7bee._comment ./doc/bugs/Problems_running_make_on_osx/comment_2_cc53d1681d576186dbc868dd9801d551._comment ./doc/bugs/Problems_running_make_on_osx/comment_13_88ed095a448096bf8a69015a04e64df1._comment ./doc/bugs/Problems_running_make_on_osx/comment_8_efafa203addf8fa79e33e21a87fb5a2b._comment ./doc/bugs/git-annex_directory_hashing_problems_on_osx.mdwn ./doc/bugs/No_easy_way_to_re-inject_a_file_into_an_annex.mdwn ./doc/bugs/git_annex_unlock_is_not_atomic.mdwn ./doc/bugs/free_space_checking.mdwn ./doc/bugs/wishlist:_more_descriptive_commit_messages_in_git-annex_branch.mdwn ./doc/bugs/Displayed_copy_speed_is_wrong.mdwn ./doc/bugs/git_annex_should_use___39__git_add_-f__39___internally.mdwn ./doc/bugs/minor_bug:_errors_are_not_verbose_enough.mdwn ./doc/bugs/No_version_information_from_cli.mdwn ./doc/bugs/Error_while_adding_a_file___34__createSymbolicLink:_already_exists__34__.mdwn ./doc/bugs/old_data_isn__39__t_unused_after_migration.mdwn ./doc/bugs/fails_to_handle_lot_of_files.mdwn ./doc/bugs/tests_fail_when_there_is_no_global_.gitconfig_for_the_user.mdwn ./doc/bugs/git_annex_initremote_walks_.git-annex.mdwn ./doc/bugs/git-annex_incorrectly_parses_bare_IPv6_addresses.mdwn ./doc/bugs/git_annex_version_should_without_being_in_a_repo_.mdwn ./doc/bugs/Can__39__t___34__git-annex_get__34___with_3.20111203.mdwn ./doc/bugs/Prevent_accidental_merges/comment_1_4c46a193915eab8f308a04175cb2e40a._comment ./doc/bugs/conflicting_haskell_packages/comment_1_e552a6cc6d7d1882e14130edfc2d6b3b._comment ./doc/bugs/tmp_file_handling.mdwn ./doc/bugs/annex_add_in_annex.mdwn ./doc/bugs/making_annex-merge_try_a_fast-forward.mdwn ./doc/bugs/bad_behaviour_with_file_names_with_newline_in_them.mdwn ./doc/bugs/on--git-dir_and_--work-tree_options.mdwn ./doc/bugs/Makefile_is_missing_dependancies.mdwn ./doc/bugs/test_suite_shouldn__39__t_fail_silently.mdwn ./doc/bugs/git_annex_upgrade_output_is_inconsistent_and_spammy.mdwn ./doc/bugs/problem_with_upgrade_v2_-__62___v3/comment_4_91439d4dbbf1461e281b276eb0003691._comment ./doc/bugs/problem_with_upgrade_v2_-__62___v3/comment_6_f360f0006bc9115bc5a3e2eb9fe58abd._comment ./doc/bugs/problem_with_upgrade_v2_-__62___v3/comment_3_86d9e7244ae492bcbe62720b8c4fc4a9._comment ./doc/bugs/problem_with_upgrade_v2_-__62___v3/comment_2_cd0123392b16d89db41b45464165c247._comment ./doc/bugs/problem_with_upgrade_v2_-__62___v3/comment_1_5f60006c9bb095167d817f234a14d20b._comment ./doc/bugs/problem_with_upgrade_v2_-__62___v3/comment_5_ca33a9ca0df33f7c1b58353d7ffb943d._comment ./doc/bugs/wishlist:_query_things_like_description__44___trust_level.mdwn ./doc/bugs/git_rename_detection_on_file_move/comment_3_57010bcaca42089b451ad8659a1e018e._comment ./doc/bugs/git_rename_detection_on_file_move/comment_4_79d96599f757757f34d7b784e6c0e81c._comment ./doc/bugs/git_rename_detection_on_file_move/comment_5_d61f5693d947b9736b29fca1dbc7ad76._comment ./doc/bugs/git_rename_detection_on_file_move/comment_2_7101d07400ad5935f880dc00d89bf90e._comment ./doc/bugs/git_rename_detection_on_file_move/comment_1_0531dcfa833b0321a7009526efe3df33._comment ./doc/bugs/fsck_claims_failed_checksum_when_less_copies_than_required_are_found.mdwn ./doc/bugs/add_range_argument_to___34__git_annex_dropunused__34___.mdwn ./doc/bugs/git_rename_detection_on_file_move.mdwn ./doc/bugs/problem_commit_normal_links.mdwn ./doc/bugs/extraneous_shell_escaping_for_rsync_remotes.mdwn ./doc/bugs/git_annex_unused_seems_to_check_for_current_path.mdwn ./doc/bugs/Makefile_is_missing_dependancies/comment_6_24119fc5d5963ce9dd669f7dcf006859._comment ./doc/bugs/Makefile_is_missing_dependancies/comment_3_c38b6f4abc9b9ad413c3b83ca04386c3._comment ./doc/bugs/Makefile_is_missing_dependancies/comment_8_a3555e3286cdc2bfeb9cde0ff727ba74._comment ./doc/bugs/Makefile_is_missing_dependancies/comment_2_416f12dbd0c2b841fac8164645b81df5._comment ./doc/bugs/Makefile_is_missing_dependancies/comment_5_0a1c52e2c96d19b9c3eb7e99b8c2434f._comment ./doc/bugs/Makefile_is_missing_dependancies/comment_7_96fd4725df4b54e670077a18d3ac4943._comment ./doc/bugs/Makefile_is_missing_dependancies/comment_1_5a3da5f79c8563c7a450aa29728abe7c._comment ./doc/bugs/Makefile_is_missing_dependancies/comment_4_cc13873175edf191047282700315beee._comment ./doc/bugs/Trouble_initializing_git_annex_on_NFS.mdwn ./doc/bugs/touch.hsc_has_problems_on_non-linux_based_systems.mdwn ./doc/bugs/build_issue_with_latest_release_0.20110522-1-gde817ba.mdwn ./doc/bugs/weird_local_clone_confuses.mdwn ./doc/bugs/fsck_output.mdwn ./doc/bugs/git-annex_branch_push_race.mdwn ./doc/bugs/wishlist:_allow_users_to_provide_UUID_when_running___96__git_annex_init__96__.mdwn ./doc/bugs/git-annex_has_issues_with_git_when_staging__47__commiting_logs.mdwn ./doc/bugs/git_annex_gets_confused_about_remotes_with_dots_in_their_names.mdwn ./doc/bugs/unannex_vs_unlock_hook_confusion.mdwn ./doc/bugs/Cabal_dependency_monadIO_missing/comment_2_4f4d8e1e00a2a4f7e8a8ab082e16adac._comment ./doc/bugs/Cabal_dependency_monadIO_missing/comment_1_14be660aa57fadec0d81b32a8b52c66f._comment ./doc/bugs/conflicting_haskell_packages.mdwn ./doc/bugs/git_command_line_constructed_by_unannex_command_has_tons_of_redundant_-a_paramters.mdwn ./doc/bugs/backend_version_upgrade_leaves_repo_unusable.mdwn ./doc/bugs/fat_support/comment_3_df3b943bc1081a8f3f7434ae0c8e061e._comment ./doc/bugs/fat_support/comment_4_90a8a15bedd94480945a374f9d706b86._comment ./doc/bugs/fat_support/comment_5_64bbf89de0836673224b83fdefa0407b._comment ./doc/bugs/fat_support/comment_1_04bcc4795d431e8cb32293aab29bbfe2._comment ./doc/bugs/fat_support/comment_2_bb4a97ebadb5c53809fc78431eabd7c8._comment ./doc/bugs/unannex_command_doesn__39__t_all_files.mdwn ./doc/bugs/annex_unannex__47__uninit_should_handle_copies.mdwn ./doc/bugs/git-annex_branch_corruption.mdwn ./doc/bugs/git_annex_fsck_is_a_no-op_in_bare_repos.mdwn ./doc/bugs/Unfortunate_interaction_with_Calibre.mdwn ./doc/bugs/error_propigation.mdwn ./doc/bugs/wishlist:_support_drop__44___find_on_special_remotes.mdwn ./doc/bugs/scp_interrupt_to_background.mdwn ./doc/bugs/copy_fast_confusing_with_broken_locationlog.mdwn ./doc/bugs/uninit_does_not_work_in_old_repos/comment_1_bc0619c6e17139df74639448aa6a0f72._comment ./doc/bugs/git-annex_losing_rsync_remotes_with_encryption_enabled.mdwn ./doc/bugs/configure_script_should_detect_uuidgen_instead_of_just_uuid.mdwn ./doc/bugs/check_for_curl_in_configure.hs.mdwn ./doc/bugs/fails_to_handle_lot_of_files/comment_1_09d8e4e66d8273fab611bd29e82dc7fc._comment ./doc/bugs/fails_to_handle_lot_of_files/comment_2_fd2ec05f4b5a7a6ae6bd9f5dbc3156de._comment ./doc/bugs/Name_scheme_does_not_follow_git__39__s_rules.mdwn ./doc/bugs/concurrent_git-annex_processes_can_lead_to_locking_issues.mdwn ./doc/bugs/Prevent_accidental_merges.mdwn ./doc/bugs/building_on_lenny.mdwn ./doc/bugs/case_sensitivity_on_FAT.mdwn ./doc/bugs/encrypted_S3_stalls.mdwn ./doc/bugs/support_bare_git_repo__44___with_the_annex_directory_exposed_to_http.mdwn ./doc/bugs/WORM:_Handle_long_filenames_correctly.mdwn ./doc/logo_small.png ./doc/tips.mdwn ./doc/use_case/Bob.mdwn ./doc/use_case/Alice.mdwn ./doc/upgrades/SHA_size.mdwn ./doc/news/sharebox_a_FUSE_filesystem_for_git-annex.mdwn ./doc/news/version_3.20111122.mdwn ./doc/news/version_3.20111203.mdwn ./doc/news/version_3.20111107.mdwn ./doc/news/LWN_article.mdwn ./doc/news/version_3.20111211.mdwn ./doc/news/version_3.20111111.mdwn ./doc/git-union-merge.mdwn ./doc/special_remotes/S3.mdwn ./doc/special_remotes/web.mdwn ./doc/special_remotes/bup.mdwn ./doc/special_remotes/directory.mdwn ./doc/special_remotes/hook.mdwn ./doc/special_remotes/rsync.mdwn ./doc/users/joey.mdwn ./doc/users/fmarier.mdwn ./doc/users/chrysn.mdwn ./doc/users/gebi.mdwn ./doc/templates/walkthrough.tmpl ./doc/templates/bare.tmpl ./doc/index.mdwn ./doc/summary.mdwn ./doc/forum.mdwn ./doc/encryption/comment_1_1afca8d7182075d46db41f6ad3dd5911._comment ./doc/transferring_data.mdwn ./doc/download.mdwn ./doc/future_proofing.mdwn ./doc/todo/auto_remotes/discussion.mdwn ./doc/todo/file_copy_progress_bar.mdwn ./doc/todo/use_cp_reflink.mdwn ./doc/todo/gitrm.mdwn ./doc/todo/exclude_files_on_a_given_remote.mdwn ./doc/todo/git-annex_unused_eats_memory.mdwn ./doc/todo/Please_abort_build_if___34__make_test__34___fails.mdwn ./doc/todo/add_--exclude_option_to_git_annex_find.mdwn ./doc/todo/tahoe_lfs_for_reals/comment_2_80b9e848edfdc7be21baab7d0cef0e3a._comment ./doc/todo/tahoe_lfs_for_reals/comment_1_0a4793ce6a867638f6e510e71dd4bb44._comment ./doc/todo/object_dir_reorg_v2.mdwn ./doc/todo/S3.mdwn ./doc/todo/auto_remotes.mdwn ./doc/todo/wishlist:_support_for_more_ssh_urls_.mdwn ./doc/todo/support-non-utf8-locales.mdwn ./doc/todo/done.mdwn ./doc/todo/fsck.mdwn ./doc/todo/support_S3_multipart_uploads.mdwn ./doc/todo/wishlist:_Provide_a___34__git_annex__34___command_that_will_skip_duplicates.mdwn ./doc/todo/pushpull.mdwn ./doc/todo/smudge.mdwn ./doc/todo/cache_key_info/comment_1_578df1b3b2cbfdc4aa1805378f35dc48._comment ./doc/todo/optimise_git-annex_merge.mdwn ./doc/todo/object_dir_reorg_v2/comment_3_79bdf9c51dec9f52372ce95b53233bb2._comment ./doc/todo/object_dir_reorg_v2/comment_7_42501404c82ca07147e2cce0cff59474._comment ./doc/todo/object_dir_reorg_v2/comment_5_821c382987f105da72a50e0a5ce61fdc._comment ./doc/todo/object_dir_reorg_v2/comment_1_ba03333dc76ff49eccaba375e68cb525._comment ./doc/todo/object_dir_reorg_v2/comment_2_81276ac309959dc741bc90101c213ab7._comment ./doc/todo/object_dir_reorg_v2/comment_6_8834c3a3f1258c4349d23aff8549bf35._comment ./doc/todo/object_dir_reorg_v2/comment_4_93aada9b1680fed56cc6f0f7c3aca5e5._comment ./doc/todo/union_mounting.mdwn ./doc/todo/wishlist:_swift_backend.mdwn ./doc/todo/Please_add_support_for_monad-control_0.3.x.mdwn ./doc/todo/wishlist:_swift_backend/comment_1_e6efbb35f61ee521b473a92674036788._comment ./doc/todo/wishlist:_swift_backend/comment_2_5d8c83b0485112e98367b7abaab3f4e3._comment ./doc/todo/git_annex_init_:_include_repo_description_and__47__or_UUID_in_commit_message.mdwn ./doc/todo/gitolite_and_gitosis_support.mdwn ./doc/todo/wishlist:___34__git_annex_add__34___multiple_processes.mdwn ./doc/todo/hidden_files.mdwn ./doc/todo/smudge/comment_1_4ea616bcdbc9e9a6fae9f2e2795c31c9._comment ./doc/todo/smudge/comment_2_e04b32caa0d2b4c577cdaf382a3ff7f6._comment ./doc/todo/network_remotes.mdwn ./doc/todo/add_a_git_backend.mdwn ./doc/todo/parallel_possibilities/comment_1_d8e34fc2bc4e5cf761574608f970d496._comment ./doc/todo/parallel_possibilities/comment_2_adb76f06a7997abe4559d3169a3181c3._comment ./doc/todo/checkout.mdwn ./doc/todo/immutable_annexed_files.mdwn ./doc/todo/wishlist:_Prevent_repeated_password_prompts_for_one_command.mdwn ./doc/todo/git-annex-shell.mdwn ./doc/todo/speed_up_fsck.mdwn ./doc/todo/tahoe_lfs_for_reals.mdwn ./doc/todo/avoid_unnecessary_union_merges.mdwn ./doc/todo/support_fsck_in_bare_repos.mdwn ./doc/todo/add_-all_option.mdwn ./doc/todo/using_url_backend.mdwn ./doc/todo/branching.mdwn ./doc/todo/rsync.mdwn ./doc/todo/backendSHA1.mdwn ./doc/todo/cache_key_info.mdwn ./doc/todo/parallel_possibilities.mdwn ./doc/todo/symlink_farming_commit_hook.mdwn ./doc/feeds.mdwn ./doc/copies.mdwn ./doc/repomap.png ./doc/special_remotes.mdwn ./doc/todo.mdwn ./doc/walkthrough/using_ssh_remotes.mdwn ./doc/walkthrough/automatically_managing_content.mdwn ./doc/walkthrough/removing_files/comment_1_cb65e7c510b75be1c51f655b058667c6._comment ./doc/walkthrough/removing_files/comment_2_64709ea4558915edd5c8ca4486965b07._comment ./doc/walkthrough/moving_file_content_between_repositories.mdwn ./doc/walkthrough/modifying_annexed_files.mdwn ./doc/walkthrough/more.mdwn ./doc/walkthrough/removing_files.mdwn ./doc/walkthrough/creating_a_repository.mdwn ./doc/walkthrough/adding_a_remote.mdwn ./doc/walkthrough/adding_a_remote/comment_1_0a59355bd33a796aec97173607e6adc9._comment ./doc/walkthrough/adding_a_remote/comment_2_f8cd79ef1593a8181a7f1086a87713e8._comment ./doc/walkthrough/adding_a_remote/comment_3_60691af4400521b5a8c8d75efe3b44cb._comment ./doc/walkthrough/adding_a_remote/comment_4_6f7cf5c330272c96b3abeb6612075c9d._comment ./doc/walkthrough/removing_files:_When_things_go_wrong.mdwn ./doc/walkthrough/renaming_files.mdwn ./doc/walkthrough/using_bup.mdwn ./doc/walkthrough/fsck:_when_things_go_wrong.mdwn ./doc/walkthrough/adding_files.mdwn ./doc/walkthrough/unused_data.mdwn ./doc/walkthrough/fsck:_verifying_your_data.mdwn ./doc/walkthrough/getting_file_content.mdwn ./doc/walkthrough/backups.mdwn ./doc/walkthrough/transferring_files:_When_things_go_wrong.mdwn ./doc/GPL ./doc/logo.png ./doc/location_tracking.mdwn ./doc/design.mdwn ./doc/news.mdwn ./doc/tips/untrusted_repositories.mdwn ./doc/tips/automatically_getting_files_on_checkout.mdwn ./doc/tips/what_to_do_when_a_repository_is_corrupted.mdwn ./doc/tips/Internet_Archive_via_S3.mdwn ./doc/tips/what_to_do_when_you_lose_a_repository.mdwn ./doc/tips/using_Amazon_S3.mdwn ./doc/tips/using_the_web_as_a_special_remote.mdwn ./doc/tips/powerful_file_matching.mdwn ./doc/tips/using_gitolite_with_git-annex.mdwn ./doc/tips/recover_data_from_lost+found.mdwn ./doc/tips/centralized_git_repository_tutorial.mdwn ./doc/tips/using_git_annex_with_no_fixed_hostname_and_optimising_ssh.mdwn ./doc/tips/using_the_SHA1_backend.mdwn ./doc/tips/migrating_data_to_a_new_backend.mdwn ./doc/encryption.mdwn ./doc/not.mdwn ./doc/download/comment_7_a5eebd214b135f34b18274a682211943._comment ./doc/download/comment_3_cf6044ebe99f71158034e21197228abd._comment ./doc/download/comment_5_c6b1bc40226fc2c8ba3e558150856992._comment ./doc/download/comment_6_3a52993d3553deb9a413debec9a5f92d._comment ./doc/download/comment_2_f85f72b33aedc3425f0c0c47867d02f3._comment ./doc/download/comment_8_59a976de6c7d333709b92f7cd5830850._comment ./doc/download/comment_4_10fc013865c7542c2ed9d6c0963bb391._comment ./doc/download/comment_1_fbd8b6d39e9d3c71791551358c863966._comment ./doc/contact.mdwn ./doc/trust.mdwn ./doc/bare_repositories.mdwn ./doc/distributed_version_control.mdwn ./doc/install.mdwn ./doc/git-annex-shell.mdwn ./doc/bugs.mdwn ./doc/meta.mdwn ./doc/design/encryption/comment_2_a610b3d056a059899178859a3a821ea5._comment ./doc/design/encryption/comment_3_cca186a9536cd3f6e86994631b14231c._comment ./doc/design/encryption/comment_1_4715ffafb3c4a9915bc33f2b26aaa9c1._comment ./doc/design/encryption/comment_4_8f3ba3e504b058791fc6e6f9c38154cf._comment ./doc/design/encryption.mdwn ./doc/comments.mdwn ./doc/git-annex.mdwn ./doc/internals.mdwn ./doc/users.mdwn ./doc/install/Ubuntu.mdwn ./doc/install/Debian.mdwn ./doc/install/OSX.mdwn ./doc/install/FreeBSD.mdwn ./doc/install/OSX/comment_1_0a1760bf0db1f1ba89bdb4c62032f631._comment ./doc/install/comment_3_cff163ea3e7cad926f4ed9e78b896598._comment ./doc/install/Debian/comment_2_648e3467e260cdf233acdb0b53313ce0._comment ./doc/install/Debian/comment_1_029486088d098c2d4f1099f2f0e701a9._comment ./doc/install/Fedora.mdwn ./doc/install/comment_4_82a17eee4a076c6c79fddeda347e0c9a._comment ./doc/walkthrough.mdwn ./CmdLine.hs ./Messages.hs ./Setup.hs ./GPL ./mdwn2man ./Remote.hs ./git-annex.hs ./Locations.hs ./Backend/WORM.hs ./Backend/URL.hs ./Backend/SHA.hs ./Options.hs ./git-annex-shell.hs ./test.hs ./INSTALL ./Remote/Web.hs ./Remote/S3stub.hs ./Remote/Helper/Encryptable.hs ./Remote/Helper/Special.hs ./Remote/Directory.hs ./Remote/S3real.hs ./Remote/Bup.hs ./Remote/Hook.hs ./Remote/Rsync.hs ./Remote/Git.hs ./Build/TestConfig.hs ./Upgrade.hs ./Annex/Version.hs ./Annex/Exception.hs ./Annex/CatFile.hs ./Annex/Ssh.hs ./Annex/Queue.hs ./Annex/Branch.hs ./Annex/UUID.hs ./Annex/Content.hs ./git-union-merge.hs ./Utility/Path.hs ./Utility/BadPrelude.hs ./Utility/Matcher.hs ./Utility/TempFile.hs ./Utility/Url.hs ./Utility/DataUnits.hs ./Utility/StatFS.hsc ./Utility/Conditional.hs ./Utility/Monad.hs ./Utility/RsyncFile.hs ./Utility/CopyFile.hs ./Utility/Misc.hs ./Utility/SafeCommand.hs ./Utility/Directory.hs ./Utility/FileMode.hs ./Utility/JSONStream.hs ./Utility/Dot.hs ./Utility/Touch.hsc ./Utility/Base64.hs ./Types/Crypto.hs ./Types/UUID.hs ./Types/TrustLevel.hs ./Types/Remote.hs ./Types/BranchState.hs ./Types/Command.hs ./Types/Backend.hs ./Types/Key.hs ./Config.hs ./Checks.hs ./Command.hs ./.gitignore ./Backend.hs ./Logs/Web.hs ./Logs/UUIDBased.hs ./Logs/Trust.hs ./Logs/Location.hs ./Logs/UUID.hs ./Logs/Remote.hs ./Logs/Presence.hs ./.gitattributes ./Git.hs ./Limit.hs ./GitAnnex.hs+Extra-Source-Files: ./debian/NEWS ./debian/control ./debian/rules ./debian/manpages ./debian/changelog ./debian/doc-base ./debian/copyright ./debian/compat ./Messages/JSON.hs ./configure.hs ./Annex.hs ./Seek.hs ./Crypto.hs ./Common.hs ./README ./Command/Merge.hs ./Command/AddUrl.hs ./Command/Whereis.hs ./Command/Unlock.hs ./Command/Version.hs ./Command/FromKey.hs ./Command/PreCommit.hs ./Command/Sync.hs ./Command/Map.hs ./Command/Unused.hs ./Command/Uninit.hs ./Command/Migrate.hs ./Command/Init.hs ./Command/ConfigList.hs ./Command/Trust.hs ./Command/Lock.hs ./Command/SendKey.hs ./Command/RecvKey.hs ./Command/Reinject.hs ./Command/Add.hs ./Command/Fix.hs ./Command/Untrust.hs ./Command/Dead.hs ./Command/InitRemote.hs ./Command/Semitrust.hs ./Command/Fsck.hs ./Command/Move.hs ./Command/DropUnused.hs ./Command/Get.hs ./Command/Upgrade.hs ./Command/Describe.hs ./Command/Drop.hs ./Command/DropKey.hs ./Command/InAnnex.hs ./Command/Find.hs ./Command/Unannex.hs ./Command/Status.hs ./Command/Copy.hs ./Init.hs ./Types.hs ./Common/Annex.hs ./Makefile ./git-annex.cabal ./Upgrade/V1.hs ./Upgrade/V2.hs ./Upgrade/V0.hs ./CHANGELOG ./Git/LsFiles.hs ./Git/Version.hs ./Git/UnionMerge.hs ./Git/Url.hs ./Git/HashObject.hs ./Git/Types.hs ./Git/CatFile.hs ./Git/Queue.hs ./Git/Ref.hs ./Git/Filename.hs ./Git/Branch.hs ./Git/Sha.hs ./Git/LsTree.hs ./Git/Config.hs ./Git/CheckAttr.hs ./Git/Command.hs ./Git/Construct.hs ./Git/Index.hs ./doc/upgrades.mdwn ./doc/forum/Recommended_number_of_repositories.mdwn ./doc/forum/wishlist:_define_remotes_that_must_have_all_files.mdwn ./doc/forum/Will_git_annex_work_on_a_FAT32_formatted_key__63__.mdwn ./doc/forum/wishlist:_command_options_changes/comment_2_f6a637c78c989382e3c22d41b7fb4cc2._comment ./doc/forum/wishlist:_command_options_changes/comment_3_bf1114533d2895804e531e76eb6b8095._comment ./doc/forum/wishlist:_command_options_changes/comment_1_bfba72a696789bf21b2435dea15f967a._comment ./doc/forum/pure_git-annex_only_workflow/comment_4_dc8a3f75533906ad3756fcc47f7e96bb._comment ./doc/forum/pure_git-annex_only_workflow/comment_15_cb7c856d8141b2de3cc95874753f1ee5._comment ./doc/forum/pure_git-annex_only_workflow/comment_7_33db51096f568c65b22b4be0b5538c0d._comment ./doc/forum/pure_git-annex_only_workflow/comment_9_ace319652f9c7546883b5152ddc82591._comment ./doc/forum/pure_git-annex_only_workflow/comment_12_ca8ca35d6cd4a9f94568536736c12adc._comment ./doc/forum/pure_git-annex_only_workflow/comment_10_683768c9826b0bf0f267e8734b9eb872._comment ./doc/forum/pure_git-annex_only_workflow/comment_11_6b541ed834ef45606f3b98779a25a148._comment ./doc/forum/pure_git-annex_only_workflow/comment_14_b63568b327215ef8f646a39d760fdfc0._comment ./doc/forum/pure_git-annex_only_workflow/comment_8_6e5b42fdb7801daadc0b3046cbc3d51e._comment ./doc/forum/pure_git-annex_only_workflow/comment_1_a32f7efd18d174845099a4ed59e6feae._comment ./doc/forum/pure_git-annex_only_workflow/comment_13_00c82d320c7b4bb51078beba17e14dc8._comment ./doc/forum/pure_git-annex_only_workflow/comment_3_9b7d89da52f7ebb7801f9ec8545c3aba._comment ./doc/forum/pure_git-annex_only_workflow/comment_6_3660d45c5656f68924acbd23790024ee._comment ./doc/forum/pure_git-annex_only_workflow/comment_2_66dc9b65523a9912411db03c039ba848._comment ./doc/forum/pure_git-annex_only_workflow/comment_5_afe5035a6b35ed2c7e193fb69cc182e2._comment ./doc/forum/wishlist:_git-annex_replicate/comment_3_c13f4f9c3d5884fc6255fd04feadc2b1._comment ./doc/forum/wishlist:_git-annex_replicate/comment_1_9926132ec6052760cdf28518a24e2358._comment ./doc/forum/wishlist:_git-annex_replicate/comment_2_c43932f4194aba8fb2470b18e0817599._comment ./doc/forum/wishlist:_git-annex_replicate/comment_4_63f24abf086d644dced8b01e1a9948c9._comment ./doc/forum/Podcast_syncing_use-case/comment_1_ace6f9d3a950348a3ac0ff592b62e786._comment ./doc/forum/Podcast_syncing_use-case/comment_2_930a6620b4d516e69ed952f9da5371bb._comment ./doc/forum/Behaviour_of_fsck/comment_4_e4911dc6793f98fb81151daacbe49968._comment ./doc/forum/Behaviour_of_fsck/comment_3_97848f9a3db89c0427cfb671ba13300e._comment ./doc/forum/Behaviour_of_fsck/comment_2_ead36a23c3e6efa1c41e4555f93e014e._comment ./doc/forum/Behaviour_of_fsck/comment_1_0e40f158b3f4ccdcaab1408d858b68b8._comment ./doc/forum/incompatible_versions__63__/comment_1_629f28258746d413e452cbd42a1a43f4._comment ./doc/forum/vlc_and_git-annex/comment_1_9c9ab8ce463cf74418aa2f385955f165._comment ./doc/forum/vlc_and_git-annex/comment_2_037f94c1deeac873dbdb36cd4c927e45._comment ./doc/forum/Recommended_number_of_repositories/comment_1_3ef256230756be8a9679b107cdbfd018._comment ./doc/forum/migration_to_git-annex_and_rsync.mdwn ./doc/forum/hashing_objects_directories.mdwn ./doc/forum/git_annex_ls___47___metadata_in_git_annex_whereis.mdwn ./doc/forum/Podcast_syncing_use-case.mdwn ./doc/forum/hashing_objects_directories/comment_5_ef6cfd49d24c180c2d0a062e5bd3a0be._comment ./doc/forum/hashing_objects_directories/comment_1_c55c56076be4f54251b0b7f79f28a607._comment ./doc/forum/hashing_objects_directories/comment_2_504c96959c779176f991f4125ea22009._comment ./doc/forum/hashing_objects_directories/comment_3_9134bde0a13aac0b6a4e5ebabd7f22e8._comment ./doc/forum/hashing_objects_directories/comment_4_0de9170e429cbfea66f5afa8980d45ac._comment ./doc/forum/location_tracking_cleanup.mdwn ./doc/forum/Wishlist:_Ways_of_selecting_files_based_on_meta-information.mdwn ./doc/forum/confusion_with_remotes__44___map.mdwn ./doc/forum/git-annex_on_OSX.mdwn ./doc/forum/A_really_stupid_question.mdwn ./doc/forum/git_tag_missing_for_3.20111011/comment_1_7a53bf273f3078ab3351369ef2b5f2a6._comment ./doc/forum/version_3_upgrade/comment_1_05fc9c9cad26c520bebb98c852c71e35._comment ./doc/forum/example_of_massively_disconnected_operation.mdwn ./doc/forum/git-annex_communication_channels/comment_2_c7aeefa6ef9a2e75d8667b479ade1b7f._comment ./doc/forum/git-annex_communication_channels/comment_5_404b723a681eb93fee015cea8024b6bc._comment ./doc/forum/git-annex_communication_channels/comment_1_198325d2e9337c90f026396de89eec0e._comment ./doc/forum/git-annex_communication_channels/comment_4_1ba6ddf54843c17c7d19a9996f2ab712._comment ./doc/forum/git-annex_communication_channels/comment_6_0d87d0e26461494b1d7f8a701a924729._comment ./doc/forum/git-annex_communication_channels/comment_3_1ff08a3e0e63fa0e560cbc9602245caa._comment ./doc/forum/git-annex_communication_channels/comment_7_2c87c7a0648fe87c2bf6b4391f1cc468._comment ./doc/forum/wishlist:_git-annex_replicate.mdwn ./doc/forum/advantages_of_SHA__42___over_WORM/comment_1_96c354cac4b5ce5cf6664943bc84db1d._comment ./doc/forum/performance_improvement:_git_on_ssd__44___annex_on_spindle_disk.mdwn ./doc/forum/Behaviour_of_fsck.mdwn ./doc/forum/git_annex_add_crash_and_subsequent_recovery.mdwn ./doc/forum/working_without_git-annex_commits.mdwn ./doc/forum/migrate_existing_git_repository_to_git-annex.mdwn ./doc/forum/__34__git_annex_lock__34___very_slow_for_big_repo.mdwn ./doc/forum/wishlist:_traffic_accounting_for_git-annex.mdwn ./doc/forum/git_pull_remote_git-annex.mdwn ./doc/forum/rsync_over_ssh__63__.mdwn ./doc/forum/unannex_alternatives/comment_3_b1687fc8f9e7744327bbeb6f0635d1cd._comment ./doc/forum/unannex_alternatives/comment_1_dcd4cd41280b41512bbdffafaf307993._comment ./doc/forum/unannex_alternatives/comment_2_58a72a9fe0f58c7af0b4d7927a2dd21d._comment ./doc/forum/wishlist:_git_annex_status/comment_4_9aeeb83d202dc8fb33ff364b0705ad94._comment ./doc/forum/wishlist:_git_annex_status/comment_3_d1fd70c67243971c96d59e1ffb7ef6e7._comment ./doc/forum/wishlist:_git_annex_status/comment_2_c2b0ce025805b774dc77ce264a222824._comment ./doc/forum/wishlist:_git_annex_status/comment_1_994bfd12c5d82e08040d6116915c5090._comment ./doc/forum/git_pull_remote_git-annex/comment_7_24c45ee981b18bc78325c768242e635d._comment ./doc/forum/git_pull_remote_git-annex/comment_2_0f7f4a311b0ec1d89613e80847e69b42._comment ./doc/forum/git_pull_remote_git-annex/comment_1_9c245db3518d8b889ecdf5115ad9e053._comment ./doc/forum/git_pull_remote_git-annex/comment_5_4f2a05ef6551806dd0ec65372f183ca4._comment ./doc/forum/git_pull_remote_git-annex/comment_4_646f2077edcabc000a7d9cb75a93cf55._comment ./doc/forum/git_pull_remote_git-annex/comment_8_7e76ee9b6520cbffaf484c9299a63ad3._comment ./doc/forum/git_pull_remote_git-annex/comment_3_1aa89725b5196e40a16edeeb5ccfa371._comment ./doc/forum/git_pull_remote_git-annex/comment_6_3925d1aa56bce9380f712e238d63080f._comment ./doc/forum/wishlist:_command_options_changes.mdwn ./doc/forum/location_tracking_cleanup/comment_3_c15428cec90e969284a5e690fb4b2fde._comment ./doc/forum/location_tracking_cleanup/comment_2_e7395cb6e01f42da72adf71ea3ebcde4._comment ./doc/forum/location_tracking_cleanup/comment_1_7d6319e8c94dfe998af9cfcbf170efb2._comment ./doc/forum/new_microfeatures/comment_2_41ad904c68e89c85e1fc49c9e9106969._comment ./doc/forum/new_microfeatures/comment_1_058bd517c6fffaf3446b1f5d5be63623._comment ./doc/forum/new_microfeatures/comment_3_a1a9347b5bc517f2a89a8b292c3f8517._comment ./doc/forum/new_microfeatures/comment_7_94045b9078b1fff877933b012d1b49e2._comment ./doc/forum/new_microfeatures/comment_5_3c627d275586ff499d928a8f8136babf._comment ./doc/forum/new_microfeatures/comment_4_5a6786dc52382fff5cc42fdb05770196._comment ./doc/forum/new_microfeatures/comment_6_31ea08c008500560c0b96c6601bc6362._comment ./doc/forum/Error_while_adding_a_file___34__createSymbolicLink:_already_exists__34__.mdwn ./doc/forum/advantages_of_SHA__42___over_WORM.mdwn ./doc/forum/wishlist:_git_annex_put_--_same_as_get__44___but_for_defaults.mdwn ./doc/forum/OSX__39__s_default_sshd_behaviour_has_limited_paths_set.mdwn ./doc/forum/bainstorming:_git_annex_push___38___pull.mdwn ./doc/forum/sparse_git_checkouts_with_annex.mdwn ./doc/forum/Need_new_build_instructions_for_Debian_stable.mdwn ./doc/forum/batch_check_on_remote_when_using_copy.mdwn ./doc/forum/tips:_special__95__remotes__47__hook_with_tahoe-lafs.mdwn ./doc/forum/wishlist:alias_system.mdwn ./doc/forum/incompatible_versions__63__.mdwn ./doc/forum/A_really_stupid_question/comment_1_40e02556de0b00b94f245a0196b5a89f._comment ./doc/forum/wishlist:_git_backend_for_git-annex.mdwn ./doc/forum/vlc_and_git-annex.mdwn ./doc/forum/seems_to_build_fine_on_haskell_platform_2011.mdwn ./doc/forum/rsync_over_ssh__63__/comment_1_ee21f32e90303e20339e0a568321bbbe._comment ./doc/forum/rsync_over_ssh__63__/comment_2_aa690da6ecfb2b30fc5080ad76dc77b1._comment ./doc/forum/wishlist:_special_remote_for_sftp_or_rsync.mdwn ./doc/forum/OSX__39__s_haskell-platform_statically_links_things.mdwn ./doc/forum/using_git_annex_to_merge_and_synchronize_2_directories___40__like_unison__41__.mdwn ./doc/forum/git_tag_missing_for_3.20111011.mdwn ./doc/forum/unannex_alternatives.mdwn ./doc/forum/can_git-annex_replace_ddm__63__.mdwn ./doc/forum/sparse_git_checkouts_with_annex/comment_2_e357db3ccc4079f07a291843975535eb._comment ./doc/forum/sparse_git_checkouts_with_annex/comment_1_c7dc199c5740a0e7ba606dfb5e3e579a._comment ./doc/forum/sparse_git_checkouts_with_annex/comment_3_fcfafca994194d57dccf5319c7c9e646._comment ./doc/forum/sparse_git_checkouts_with_annex/comment_4_04dc14880f31eee2b6d767d4d4258c5a._comment ./doc/forum/syncing_non-git_trees_with_git-annex.mdwn ./doc/forum/git-annex_communication_channels.mdwn ./doc/forum/can_git-annex_replace_ddm__63__/comment_3_4c69097fe2ee81359655e59a03a9bb8d._comment ./doc/forum/can_git-annex_replace_ddm__63__/comment_2_008554306dd082d7f543baf283510e92._comment ./doc/forum/can_git-annex_replace_ddm__63__/comment_1_aa05008dfe800474ff76678a400099e1._comment ./doc/forum/version_3_upgrade.mdwn ./doc/forum/relying_on_git_for_numcopies/comment_3_43d8e1513eb9947f8a503f094c03f307._comment ./doc/forum/relying_on_git_for_numcopies/comment_2_be6acbc26008a9cb54e7b8f498f2c2a2._comment ./doc/forum/relying_on_git_for_numcopies/comment_1_8ad3cccd7f66f6423341d71241ba89fc._comment ./doc/forum/confusion_with_remotes__44___map/comment_1_a38ded23b7f288292a843abcb1a56f38._comment ./doc/forum/confusion_with_remotes__44___map/comment_2_cd1c98b1276444e859a22c3dbd6f2a79._comment ./doc/forum/confusion_with_remotes__44___map/comment_6_496b0d9b86869bbac3a1356d53a3dda4._comment ./doc/forum/confusion_with_remotes__44___map/comment_5_27801584325d259fa490f67273f2ff71._comment ./doc/forum/confusion_with_remotes__44___map/comment_4_3b89b6d1518267fcbc050c9de038b9ca._comment ./doc/forum/confusion_with_remotes__44___map/comment_3_18531754089c991b6caefc57a5c17fe9._comment ./doc/forum/confusion_with_remotes__44___map/comment_7_9a456f61f956a3d5e81e723d5a90794c._comment ./doc/forum/--print0_option_as_in___34__find__34__.mdwn ./doc/forum/relying_on_git_for_numcopies.mdwn ./doc/forum/wishlist:_do_round_robin_downloading_of_data.mdwn ./doc/forum/pure_git-annex_only_workflow.mdwn ./doc/forum/wishlist:_git_annex_status.mdwn ./doc/forum/getting_git_annex_to_do_a_force_copy_to_a_remote.mdwn ./doc/forum/Problems_with_large_numbers_of_files.mdwn ./doc/forum/Can_I_store_normal_files_in_the_git-annex_git_repository__63__.mdwn ./doc/forum/Is_an_automagic_upgrade_of_the_object_directory_safe__63__.mdwn ./doc/forum/wishlist:_push_to_cia.vc_from_the_website__39__s_repo__44___not_your_personal_one.mdwn ./doc/forum/new_microfeatures.mdwn ./doc/backends.mdwn ./doc/bugs/__39__annex_add__39___fails_to___39__git_add__39___for_parent_relative_path.mdwn ./doc/bugs/uuid.log_trust.log_and_remote.log_merge_wackiness.mdwn ./doc/bugs/Remote_repo_and_set_operation_with_find.mdwn ./doc/bugs/softlink_mtime.mdwn ./doc/bugs/uninit_should_not_run_when_branch_git-annex_is_checked_out.mdwn ./doc/bugs/ordering.mdwn ./doc/bugs/dropping_files_with_a_URL_backend_fails.mdwn ./doc/bugs/fsck__47__fix_should_check__47__fix_the_permissions_of_.git__47__annex.mdwn ./doc/bugs/dropunused_doesn__39__t_handle_double_spaces_in_filename.mdwn ./doc/bugs/add_script-friendly_output_options.mdwn ./doc/bugs/uninit_does_not_work_in_old_repos.mdwn ./doc/bugs/git_annex_get_choke_when_remote_is_an_ssh_url_with_a_port.mdwn ./doc/bugs/unannex_and_uninit_do_not_work_when_git_index_is_broken.mdwn ./doc/bugs/git_annex_migrate_leaves_old_backend_versions_around.mdwn ./doc/bugs/git_annex_unused_failes_on_empty_repository.mdwn ./doc/bugs/git_annex_add_eats_files_when_filename_is_too_long.mdwn ./doc/bugs/S3_bucket_uses_the_same_key_for_encryption_and_hashing.mdwn ./doc/bugs/problem_with_upgrade_v2_-__62___v3.mdwn ./doc/bugs/git_annex_copy_--fast_does_not_copy_files.mdwn ./doc/bugs/Cabal_dependency_monadIO_missing.mdwn ./doc/bugs/dotdot_problem.mdwn ./doc/bugs/Problems_running_make_on_osx.mdwn ./doc/bugs/free_space_checking/comment_2_8a65f6d3dcf5baa3f7f2dbe1346e2615._comment ./doc/bugs/free_space_checking/comment_3_0fc6ff79a357b1619d13018ccacc7c10._comment ./doc/bugs/free_space_checking/comment_1_a868e805be43c5a7c19c41f1af8e41e6._comment ./doc/bugs/Error_when_moving_annexed_file_to_a_.gitignored_location.mdwn ./doc/bugs/done.mdwn ./doc/bugs/git_annex_copy_-f_REMOTE_._doesn__39__t_work_as_expected.mdwn ./doc/bugs/Displayed_copy_speed_is_wrong/comment_2_8b240de1d5ae9229fa2d77d1cc15a552._comment ./doc/bugs/Displayed_copy_speed_is_wrong/comment_1_74de3091e8bfd7acd6795e61f39f07c6._comment ./doc/bugs/Build_error_on_Mac_OSX_10.6.mdwn ./doc/bugs/bare_git_repos.mdwn ./doc/bugs/fat_support.mdwn ./doc/bugs/problems_with_utf8_names.mdwn ./doc/bugs/error_with_file_names_starting_with_dash.mdwn ./doc/bugs/not_possible_to_have_annex_on_a_separate_filesystem.mdwn ./doc/bugs/unhappy_without_UTF8_locale.mdwn ./doc/bugs/S3_memory_leaks.mdwn ./doc/bugs/git_annex_map_has_problems_with_urls_containing___126__.mdwn ./doc/bugs/interrupting_migration_causes_problems.mdwn ./doc/bugs/upgrade_left_untracked_.git-annex__47____42___directories.mdwn ./doc/bugs/cyclic_drop.mdwn ./doc/bugs/Problems_running_make_on_osx/comment_9_cc283b485b3c95ba7eebc8f0c96969b3._comment ./doc/bugs/Problems_running_make_on_osx/comment_4_c52be386f79f14c8570a8f1397c68581._comment ./doc/bugs/Problems_running_make_on_osx/comment_18_64fab50d95de619eb2e8f08f90237de1._comment ./doc/bugs/Problems_running_make_on_osx/comment_16_5c2dd6002aadaab30841b77a5f5aed34._comment ./doc/bugs/Problems_running_make_on_osx/comment_10_94e4ac430140042a2d0fb5a16d86b4e5._comment ./doc/bugs/Problems_running_make_on_osx/comment_15_6b8867b8e48bf807c955779c9f8f0909._comment ./doc/bugs/Problems_running_make_on_osx/comment_11_56f1143fa191361d63b441741699e17f._comment ./doc/bugs/Problems_running_make_on_osx/comment_20_7db27d1a22666c831848bc6c06d66a84._comment ./doc/bugs/Problems_running_make_on_osx/comment_12_ec5131624d0d2285d3b6880e47033f97._comment ./doc/bugs/Problems_running_make_on_osx/comment_3_68f0f8ae953589ae26d57310b40c878d._comment ./doc/bugs/Problems_running_make_on_osx/comment_14_89a960b6706ed703b390a81a8bc4e311._comment ./doc/bugs/Problems_running_make_on_osx/comment_17_62fccb04b0e4b695312f7a3f32fb96ee._comment ./doc/bugs/Problems_running_make_on_osx/comment_19_4253988ed178054c8b6400beeed68a29._comment ./doc/bugs/Problems_running_make_on_osx/comment_6_0c46f5165ceb5a7b9ea9689c33b3a4f8._comment ./doc/bugs/Problems_running_make_on_osx/comment_1_34120e82331ace01a6a4960862d38f2d._comment ./doc/bugs/Problems_running_make_on_osx/comment_7_237a137cce58a28abcc736cbf2c420b0._comment ./doc/bugs/Problems_running_make_on_osx/comment_5_7f1330a1e541b0f3e2192e596d7f7bee._comment ./doc/bugs/Problems_running_make_on_osx/comment_2_cc53d1681d576186dbc868dd9801d551._comment ./doc/bugs/Problems_running_make_on_osx/comment_13_88ed095a448096bf8a69015a04e64df1._comment ./doc/bugs/Problems_running_make_on_osx/comment_8_efafa203addf8fa79e33e21a87fb5a2b._comment ./doc/bugs/git-annex_directory_hashing_problems_on_osx.mdwn ./doc/bugs/No_easy_way_to_re-inject_a_file_into_an_annex.mdwn ./doc/bugs/git_annex_unlock_is_not_atomic.mdwn ./doc/bugs/free_space_checking.mdwn ./doc/bugs/wishlist:_more_descriptive_commit_messages_in_git-annex_branch.mdwn ./doc/bugs/Displayed_copy_speed_is_wrong.mdwn ./doc/bugs/git_annex_should_use___39__git_add_-f__39___internally.mdwn ./doc/bugs/minor_bug:_errors_are_not_verbose_enough.mdwn ./doc/bugs/No_version_information_from_cli.mdwn ./doc/bugs/Error_while_adding_a_file___34__createSymbolicLink:_already_exists__34__.mdwn ./doc/bugs/old_data_isn__39__t_unused_after_migration.mdwn ./doc/bugs/fails_to_handle_lot_of_files.mdwn ./doc/bugs/tests_fail_when_there_is_no_global_.gitconfig_for_the_user.mdwn ./doc/bugs/git_annex_initremote_walks_.git-annex.mdwn ./doc/bugs/git-annex_incorrectly_parses_bare_IPv6_addresses.mdwn ./doc/bugs/git_annex_version_should_without_being_in_a_repo_.mdwn ./doc/bugs/Can__39__t___34__git-annex_get__34___with_3.20111203.mdwn ./doc/bugs/Prevent_accidental_merges/comment_1_4c46a193915eab8f308a04175cb2e40a._comment ./doc/bugs/conflicting_haskell_packages/comment_1_e552a6cc6d7d1882e14130edfc2d6b3b._comment ./doc/bugs/tmp_file_handling.mdwn ./doc/bugs/annex_add_in_annex.mdwn ./doc/bugs/making_annex-merge_try_a_fast-forward.mdwn ./doc/bugs/bad_behaviour_with_file_names_with_newline_in_them.mdwn ./doc/bugs/on--git-dir_and_--work-tree_options.mdwn ./doc/bugs/Makefile_is_missing_dependancies.mdwn ./doc/bugs/test_suite_shouldn__39__t_fail_silently.mdwn ./doc/bugs/git_annex_upgrade_output_is_inconsistent_and_spammy.mdwn ./doc/bugs/problem_with_upgrade_v2_-__62___v3/comment_4_91439d4dbbf1461e281b276eb0003691._comment ./doc/bugs/problem_with_upgrade_v2_-__62___v3/comment_6_f360f0006bc9115bc5a3e2eb9fe58abd._comment ./doc/bugs/problem_with_upgrade_v2_-__62___v3/comment_3_86d9e7244ae492bcbe62720b8c4fc4a9._comment ./doc/bugs/problem_with_upgrade_v2_-__62___v3/comment_2_cd0123392b16d89db41b45464165c247._comment ./doc/bugs/problem_with_upgrade_v2_-__62___v3/comment_1_5f60006c9bb095167d817f234a14d20b._comment ./doc/bugs/problem_with_upgrade_v2_-__62___v3/comment_5_ca33a9ca0df33f7c1b58353d7ffb943d._comment ./doc/bugs/wishlist:_query_things_like_description__44___trust_level.mdwn ./doc/bugs/git_rename_detection_on_file_move/comment_9_75e0973f6d573df615e01005ebcea87d._comment ./doc/bugs/git_rename_detection_on_file_move/comment_7_7f20d0b2f6ed1c34021a135438037306._comment ./doc/bugs/git_rename_detection_on_file_move/comment_3_57010bcaca42089b451ad8659a1e018e._comment ./doc/bugs/git_rename_detection_on_file_move/comment_4_79d96599f757757f34d7b784e6c0e81c._comment ./doc/bugs/git_rename_detection_on_file_move/comment_5_d61f5693d947b9736b29fca1dbc7ad76._comment ./doc/bugs/git_rename_detection_on_file_move/comment_8_6a00500b24ba53248c78e1ffc8d1a591._comment ./doc/bugs/git_rename_detection_on_file_move/comment_2_7101d07400ad5935f880dc00d89bf90e._comment ./doc/bugs/git_rename_detection_on_file_move/comment_6_f63de6fe2f7189c8c2908cc41c4bc963._comment ./doc/bugs/git_rename_detection_on_file_move/comment_1_0531dcfa833b0321a7009526efe3df33._comment ./doc/bugs/fsck_claims_failed_checksum_when_less_copies_than_required_are_found.mdwn ./doc/bugs/add_range_argument_to___34__git_annex_dropunused__34___.mdwn ./doc/bugs/git_rename_detection_on_file_move.mdwn ./doc/bugs/problem_commit_normal_links.mdwn ./doc/bugs/extraneous_shell_escaping_for_rsync_remotes.mdwn ./doc/bugs/problems_with_utf8_names/comment_1_3c7e3f021c2c94277eecf9c8af6cec5f._comment ./doc/bugs/problems_with_utf8_names/comment_4_93bee35f5fa7744834994bc7a253a6f9._comment ./doc/bugs/problems_with_utf8_names/comment_3_4f936a5d3f9c7df64c8a87e62b7fbfdc._comment ./doc/bugs/problems_with_utf8_names/comment_2_bad4c4c5f54358d1bc0ab2adc713782a._comment ./doc/bugs/git_annex_unused_seems_to_check_for_current_path.mdwn ./doc/bugs/Makefile_is_missing_dependancies/comment_6_24119fc5d5963ce9dd669f7dcf006859._comment ./doc/bugs/Makefile_is_missing_dependancies/comment_3_c38b6f4abc9b9ad413c3b83ca04386c3._comment ./doc/bugs/Makefile_is_missing_dependancies/comment_8_a3555e3286cdc2bfeb9cde0ff727ba74._comment ./doc/bugs/Makefile_is_missing_dependancies/comment_2_416f12dbd0c2b841fac8164645b81df5._comment ./doc/bugs/Makefile_is_missing_dependancies/comment_5_0a1c52e2c96d19b9c3eb7e99b8c2434f._comment ./doc/bugs/Makefile_is_missing_dependancies/comment_7_96fd4725df4b54e670077a18d3ac4943._comment ./doc/bugs/Makefile_is_missing_dependancies/comment_1_5a3da5f79c8563c7a450aa29728abe7c._comment ./doc/bugs/Makefile_is_missing_dependancies/comment_4_cc13873175edf191047282700315beee._comment ./doc/bugs/Trouble_initializing_git_annex_on_NFS.mdwn ./doc/bugs/touch.hsc_has_problems_on_non-linux_based_systems.mdwn ./doc/bugs/build_issue_with_latest_release_0.20110522-1-gde817ba.mdwn ./doc/bugs/weird_local_clone_confuses.mdwn ./doc/bugs/fsck_output.mdwn ./doc/bugs/git-annex_branch_push_race.mdwn ./doc/bugs/wishlist:_allow_users_to_provide_UUID_when_running___96__git_annex_init__96__.mdwn ./doc/bugs/git-annex_has_issues_with_git_when_staging__47__commiting_logs.mdwn ./doc/bugs/git_annex_gets_confused_about_remotes_with_dots_in_their_names.mdwn ./doc/bugs/unannex_vs_unlock_hook_confusion.mdwn ./doc/bugs/Cabal_dependency_monadIO_missing/comment_2_4f4d8e1e00a2a4f7e8a8ab082e16adac._comment ./doc/bugs/Cabal_dependency_monadIO_missing/comment_1_14be660aa57fadec0d81b32a8b52c66f._comment ./doc/bugs/conflicting_haskell_packages.mdwn ./doc/bugs/git_command_line_constructed_by_unannex_command_has_tons_of_redundant_-a_paramters.mdwn ./doc/bugs/backend_version_upgrade_leaves_repo_unusable.mdwn ./doc/bugs/fat_support/comment_3_df3b943bc1081a8f3f7434ae0c8e061e._comment ./doc/bugs/fat_support/comment_4_90a8a15bedd94480945a374f9d706b86._comment ./doc/bugs/fat_support/comment_5_64bbf89de0836673224b83fdefa0407b._comment ./doc/bugs/fat_support/comment_1_04bcc4795d431e8cb32293aab29bbfe2._comment ./doc/bugs/fat_support/comment_2_bb4a97ebadb5c53809fc78431eabd7c8._comment ./doc/bugs/unannex_command_doesn__39__t_all_files.mdwn ./doc/bugs/annex_unannex__47__uninit_should_handle_copies.mdwn ./doc/bugs/git-annex_branch_corruption.mdwn ./doc/bugs/git_annex_fsck_is_a_no-op_in_bare_repos.mdwn ./doc/bugs/Unfortunate_interaction_with_Calibre.mdwn ./doc/bugs/error_propigation.mdwn ./doc/bugs/wishlist:_support_drop__44___find_on_special_remotes.mdwn ./doc/bugs/scp_interrupt_to_background.mdwn ./doc/bugs/copy_fast_confusing_with_broken_locationlog.mdwn ./doc/bugs/uninit_does_not_work_in_old_repos/comment_1_bc0619c6e17139df74639448aa6a0f72._comment ./doc/bugs/git-annex_losing_rsync_remotes_with_encryption_enabled.mdwn ./doc/bugs/configure_script_should_detect_uuidgen_instead_of_just_uuid.mdwn ./doc/bugs/check_for_curl_in_configure.hs.mdwn ./doc/bugs/fails_to_handle_lot_of_files/comment_1_09d8e4e66d8273fab611bd29e82dc7fc._comment ./doc/bugs/fails_to_handle_lot_of_files/comment_2_fd2ec05f4b5a7a6ae6bd9f5dbc3156de._comment ./doc/bugs/Name_scheme_does_not_follow_git__39__s_rules.mdwn ./doc/bugs/concurrent_git-annex_processes_can_lead_to_locking_issues.mdwn ./doc/bugs/Prevent_accidental_merges.mdwn ./doc/bugs/building_on_lenny.mdwn ./doc/bugs/case_sensitivity_on_FAT.mdwn ./doc/bugs/encrypted_S3_stalls.mdwn ./doc/bugs/support_bare_git_repo__44___with_the_annex_directory_exposed_to_http.mdwn ./doc/bugs/WORM:_Handle_long_filenames_correctly.mdwn ./doc/logo_small.png ./doc/tips.mdwn ./doc/use_case/Bob.mdwn ./doc/use_case/Alice.mdwn ./doc/upgrades/SHA_size.mdwn ./doc/news/sharebox_a_FUSE_filesystem_for_git-annex.mdwn ./doc/news/version_3.20111122.mdwn ./doc/news/version_3.20111203.mdwn ./doc/news/version_3.20111107.mdwn ./doc/news/version_backport.mdwn ./doc/news/version_3.20111122~bpo60+2.mdwn ./doc/news/version_3.20111231.mdwn ./doc/news/LWN_article.mdwn ./doc/news/version_3.20111211.mdwn ./doc/news/version_3.20111111.mdwn ./doc/git-union-merge.mdwn ./doc/special_remotes/S3.mdwn ./doc/special_remotes/web.mdwn ./doc/special_remotes/bup.mdwn ./doc/special_remotes/directory.mdwn ./doc/special_remotes/hook.mdwn ./doc/special_remotes/rsync.mdwn ./doc/users/joey.mdwn ./doc/users/fmarier.mdwn ./doc/users/chrysn.mdwn ./doc/users/gebi.mdwn ./doc/templates/walkthrough.tmpl ./doc/templates/bare.tmpl ./doc/index.mdwn ./doc/summary.mdwn ./doc/forum.mdwn ./doc/encryption/comment_1_1afca8d7182075d46db41f6ad3dd5911._comment ./doc/transferring_data.mdwn ./doc/download.mdwn ./doc/future_proofing.mdwn ./doc/todo/auto_remotes/discussion.mdwn ./doc/todo/link_file_to_remote_repo_feature.mdwn ./doc/todo/file_copy_progress_bar.mdwn ./doc/todo/use_cp_reflink.mdwn ./doc/todo/gitrm.mdwn ./doc/todo/exclude_files_on_a_given_remote.mdwn ./doc/todo/git-annex_unused_eats_memory.mdwn ./doc/todo/Please_abort_build_if___34__make_test__34___fails.mdwn ./doc/todo/add_--exclude_option_to_git_annex_find.mdwn ./doc/todo/tahoe_lfs_for_reals/comment_2_80b9e848edfdc7be21baab7d0cef0e3a._comment ./doc/todo/tahoe_lfs_for_reals/comment_1_0a4793ce6a867638f6e510e71dd4bb44._comment ./doc/todo/object_dir_reorg_v2.mdwn ./doc/todo/S3.mdwn ./doc/todo/auto_remotes.mdwn ./doc/todo/wishlist:_support_for_more_ssh_urls_.mdwn ./doc/todo/support-non-utf8-locales.mdwn ./doc/todo/done.mdwn ./doc/todo/fsck.mdwn ./doc/todo/support_S3_multipart_uploads.mdwn ./doc/todo/wishlist:_Provide_a___34__git_annex__34___command_that_will_skip_duplicates.mdwn ./doc/todo/pushpull.mdwn ./doc/todo/smudge.mdwn ./doc/todo/cache_key_info/comment_1_578df1b3b2cbfdc4aa1805378f35dc48._comment ./doc/todo/optimise_git-annex_merge.mdwn ./doc/todo/object_dir_reorg_v2/comment_3_79bdf9c51dec9f52372ce95b53233bb2._comment ./doc/todo/object_dir_reorg_v2/comment_7_42501404c82ca07147e2cce0cff59474._comment ./doc/todo/object_dir_reorg_v2/comment_5_821c382987f105da72a50e0a5ce61fdc._comment ./doc/todo/object_dir_reorg_v2/comment_1_ba03333dc76ff49eccaba375e68cb525._comment ./doc/todo/object_dir_reorg_v2/comment_2_81276ac309959dc741bc90101c213ab7._comment ./doc/todo/object_dir_reorg_v2/comment_6_8834c3a3f1258c4349d23aff8549bf35._comment ./doc/todo/object_dir_reorg_v2/comment_4_93aada9b1680fed56cc6f0f7c3aca5e5._comment ./doc/todo/union_mounting.mdwn ./doc/todo/wishlist:_swift_backend.mdwn ./doc/todo/Please_add_support_for_monad-control_0.3.x.mdwn ./doc/todo/wishlist:_swift_backend/comment_1_e6efbb35f61ee521b473a92674036788._comment ./doc/todo/wishlist:_swift_backend/comment_2_5d8c83b0485112e98367b7abaab3f4e3._comment ./doc/todo/git_annex_init_:_include_repo_description_and__47__or_UUID_in_commit_message.mdwn ./doc/todo/gitolite_and_gitosis_support.mdwn ./doc/todo/wishlist:___34__git_annex_add__34___multiple_processes.mdwn ./doc/todo/hidden_files.mdwn ./doc/todo/smudge/comment_1_4ea616bcdbc9e9a6fae9f2e2795c31c9._comment ./doc/todo/smudge/comment_2_e04b32caa0d2b4c577cdaf382a3ff7f6._comment ./doc/todo/network_remotes.mdwn ./doc/todo/add_a_git_backend.mdwn ./doc/todo/parallel_possibilities/comment_1_d8e34fc2bc4e5cf761574608f970d496._comment ./doc/todo/parallel_possibilities/comment_2_adb76f06a7997abe4559d3169a3181c3._comment ./doc/todo/checkout.mdwn ./doc/todo/immutable_annexed_files.mdwn ./doc/todo/wishlist:_Prevent_repeated_password_prompts_for_one_command.mdwn ./doc/todo/git-annex-shell.mdwn ./doc/todo/speed_up_fsck.mdwn ./doc/todo/tahoe_lfs_for_reals.mdwn ./doc/todo/avoid_unnecessary_union_merges.mdwn ./doc/todo/support_fsck_in_bare_repos.mdwn ./doc/todo/add_-all_option.mdwn ./doc/todo/using_url_backend.mdwn ./doc/todo/branching.mdwn ./doc/todo/rsync.mdwn ./doc/todo/backendSHA1.mdwn ./doc/todo/cache_key_info.mdwn ./doc/todo/parallel_possibilities.mdwn ./doc/todo/symlink_farming_commit_hook.mdwn ./doc/feeds.mdwn ./doc/sync.mdwn ./doc/copies.mdwn ./doc/repomap.png ./doc/special_remotes.mdwn ./doc/todo.mdwn ./doc/walkthrough/using_ssh_remotes.mdwn ./doc/walkthrough/automatically_managing_content.mdwn ./doc/walkthrough/removing_files/comment_1_cb65e7c510b75be1c51f655b058667c6._comment ./doc/walkthrough/removing_files/comment_2_64709ea4558915edd5c8ca4486965b07._comment ./doc/walkthrough/moving_file_content_between_repositories.mdwn ./doc/walkthrough/modifying_annexed_files.mdwn ./doc/walkthrough/more.mdwn ./doc/walkthrough/removing_files.mdwn ./doc/walkthrough/creating_a_repository.mdwn ./doc/walkthrough/adding_a_remote.mdwn ./doc/walkthrough/adding_a_remote/comment_1_0a59355bd33a796aec97173607e6adc9._comment ./doc/walkthrough/adding_a_remote/comment_2_f8cd79ef1593a8181a7f1086a87713e8._comment ./doc/walkthrough/adding_a_remote/comment_3_60691af4400521b5a8c8d75efe3b44cb._comment ./doc/walkthrough/adding_a_remote/comment_4_6f7cf5c330272c96b3abeb6612075c9d._comment ./doc/walkthrough/removing_files:_When_things_go_wrong.mdwn ./doc/walkthrough/renaming_files.mdwn ./doc/walkthrough/using_bup.mdwn ./doc/walkthrough/fsck:_when_things_go_wrong.mdwn ./doc/walkthrough/syncing.mdwn ./doc/walkthrough/adding_files.mdwn ./doc/walkthrough/unused_data.mdwn ./doc/walkthrough/fsck:_verifying_your_data.mdwn ./doc/walkthrough/getting_file_content.mdwn ./doc/walkthrough/backups.mdwn ./doc/walkthrough/transferring_files:_When_things_go_wrong.mdwn ./doc/GPL ./doc/logo.png ./doc/location_tracking.mdwn ./doc/design.mdwn ./doc/news.mdwn ./doc/tips/untrusted_repositories.mdwn ./doc/tips/automatically_getting_files_on_checkout.mdwn ./doc/tips/what_to_do_when_a_repository_is_corrupted.mdwn ./doc/tips/Internet_Archive_via_S3.mdwn ./doc/tips/what_to_do_when_you_lose_a_repository.mdwn ./doc/tips/using_Amazon_S3.mdwn ./doc/tips/using_the_web_as_a_special_remote.mdwn ./doc/tips/powerful_file_matching.mdwn ./doc/tips/using_gitolite_with_git-annex.mdwn ./doc/tips/recover_data_from_lost+found.mdwn ./doc/tips/centralized_git_repository_tutorial.mdwn ./doc/tips/finding_duplicate_files/comment_1_ddb477ca242ffeb21e0df394d8fdf5d2._comment ./doc/tips/using_git_annex_with_no_fixed_hostname_and_optimising_ssh.mdwn ./doc/tips/using_gitolite_with_git-annex/comment_4_eb81f824aadc97f098379c5f7e4fba4c._comment ./doc/tips/using_gitolite_with_git-annex/comment_6_3e203e010a4df5bf03899f867718adc5._comment ./doc/tips/using_gitolite_with_git-annex/comment_1_9a2a2a8eac9af97e0c984ad105763a73._comment ./doc/tips/using_gitolite_with_git-annex/comment_8_8249772c142117f88e37975d058aa936._comment ./doc/tips/using_gitolite_with_git-annex/comment_5_f688309532d2993630e9e72e87fb9c46._comment ./doc/tips/using_gitolite_with_git-annex/comment_7_f8fd08b6ab47378ad88c87348057220d._comment ./doc/tips/using_gitolite_with_git-annex/comment_3_807035f38509ccb9f93f1929ecd37417._comment ./doc/tips/using_gitolite_with_git-annex/comment_2_d8efea4ab9576555fadbb47666ecefa9._comment ./doc/tips/finding_duplicate_files.mdwn ./doc/tips/centralised_repository:_starting_from_nothing.mdwn ./doc/tips/using_the_SHA1_backend.mdwn ./doc/tips/migrating_data_to_a_new_backend.mdwn ./doc/encryption.mdwn ./doc/not.mdwn ./doc/download/comment_7_a5eebd214b135f34b18274a682211943._comment ./doc/download/comment_3_cf6044ebe99f71158034e21197228abd._comment ./doc/download/comment_5_c6b1bc40226fc2c8ba3e558150856992._comment ./doc/download/comment_6_3a52993d3553deb9a413debec9a5f92d._comment ./doc/download/comment_2_f85f72b33aedc3425f0c0c47867d02f3._comment ./doc/download/comment_8_59a976de6c7d333709b92f7cd5830850._comment ./doc/download/comment_4_10fc013865c7542c2ed9d6c0963bb391._comment ./doc/download/comment_1_fbd8b6d39e9d3c71791551358c863966._comment ./doc/contact.mdwn ./doc/trust.mdwn ./doc/bare_repositories.mdwn ./doc/distributed_version_control.mdwn ./doc/install.mdwn ./doc/git-annex-shell.mdwn ./doc/bugs.mdwn ./doc/meta.mdwn ./doc/design/encryption/comment_2_a610b3d056a059899178859a3a821ea5._comment ./doc/design/encryption/comment_3_cca186a9536cd3f6e86994631b14231c._comment ./doc/design/encryption/comment_1_4715ffafb3c4a9915bc33f2b26aaa9c1._comment ./doc/design/encryption/comment_4_8f3ba3e504b058791fc6e6f9c38154cf._comment ./doc/design/encryption.mdwn ./doc/comments.mdwn ./doc/git-annex.mdwn ./doc/internals.mdwn ./doc/users.mdwn ./doc/install/Ubuntu.mdwn ./doc/install/Debian.mdwn ./doc/install/openSUSE.mdwn ./doc/install/OSX.mdwn ./doc/install/FreeBSD.mdwn ./doc/install/OSX/comment_1_0a1760bf0db1f1ba89bdb4c62032f631._comment ./doc/install/comment_3_cff163ea3e7cad926f4ed9e78b896598._comment ./doc/install/Debian/comment_2_648e3467e260cdf233acdb0b53313ce0._comment ./doc/install/Debian/comment_1_029486088d098c2d4f1099f2f0e701a9._comment ./doc/install/Fedora.mdwn ./doc/install/comment_4_82a17eee4a076c6c79fddeda347e0c9a._comment ./doc/walkthrough.mdwn ./CmdLine.hs ./Messages.hs ./Setup.hs ./GPL ./mdwn2man ./Remote.hs ./git-annex.hs ./Locations.hs ./Backend/WORM.hs ./Backend/URL.hs ./Backend/SHA.hs ./Options.hs ./git-annex-shell.hs ./test.hs ./INSTALL ./Remote/Web.hs ./Remote/S3stub.hs ./Remote/Helper/Encryptable.hs ./Remote/Helper/Special.hs ./Remote/Directory.hs ./Remote/S3real.hs ./Remote/Bup.hs ./Remote/Hook.hs ./Remote/Rsync.hs ./Remote/Git.hs ./Build/TestConfig.hs ./Upgrade.hs ./Annex/Version.hs ./Annex/Exception.hs ./Annex/Journal.hs ./Annex/CatFile.hs ./Annex/Ssh.hs ./Annex/Queue.hs ./Annex/Branch.hs ./Annex/UUID.hs ./Annex/BranchState.hs ./Annex/Content.hs ./git-union-merge.hs ./Utility/Path.hs ./Utility/Matcher.hs ./Utility/TempFile.hs ./Utility/Url.hs ./Utility/DataUnits.hs ./Utility/StatFS.hsc ./Utility/Conditional.hs ./Utility/Monad.hs ./Utility/RsyncFile.hs ./Utility/CopyFile.hs ./Utility/Misc.hs ./Utility/SafeCommand.hs ./Utility/Gpg.hs ./Utility/Directory.hs ./Utility/FileMode.hs ./Utility/PartialPrelude.hs ./Utility/JSONStream.hs ./Utility/Dot.hs ./Utility/Touch.hsc ./Utility/Base64.hs ./Utility/Format.hs ./Types/Crypto.hs ./Types/UUID.hs ./Types/TrustLevel.hs ./Types/Remote.hs ./Types/BranchState.hs ./Types/Command.hs ./Types/Backend.hs ./Types/Key.hs ./Config.hs ./Checks.hs ./Command.hs ./.gitignore ./Backend.hs ./Logs/Web.hs ./Logs/UUIDBased.hs ./Logs/Trust.hs ./Logs/Location.hs ./Logs/UUID.hs ./Logs/Remote.hs ./Logs/Presence.hs ./.gitattributes ./Git.hs ./Limit.hs ./GitAnnex.hs Homepage: http://git-annex.branchable.com/ Build-type: Custom Category: Utility@@ -30,8 +30,8 @@   Main-Is: git-annex.hs   Build-Depends: MissingH, hslogger, directory, filepath,    unix, containers, utf8-string, network, mtl, bytestring, old-locale, time,-   pcre-light, extensible-exceptions, dataenc, SHA, process, hS3, HTTP,-   base < 5, monad-control < 0.3, json+   pcre-light, extensible-exceptions, dataenc, SHA, process, hS3, json, HTTP,+   base < 5, monad-control, transformers-base, lifted-base  Executable git-annex-shell   Main-Is: git-annex-shell.hs
git-union-merge.hs view
@@ -9,6 +9,10 @@  import Common import qualified Git.UnionMerge+import qualified Git.Config+import qualified Git.Construct+import qualified Git.Branch+import qualified Git.Index import qualified Git  header :: String@@ -38,9 +42,9 @@ main :: IO () main = do 	[aref, bref, newref] <- map Git.Ref <$> parseArgs-	g <- Git.configRead =<< Git.repoFromCwd-	_ <- Git.useIndex (tmpIndex g)+	g <- Git.Config.read =<< Git.Construct.fromCwd+	_ <- Git.Index.override (tmpIndex g) 	setup g 	Git.UnionMerge.merge aref bref g-	_ <- Git.commit "union merge" newref [aref, bref] g+	_ <- Git.Branch.commit "union merge" newref [aref, bref] g 	cleanup g
test.hs view
@@ -17,6 +17,7 @@ import qualified Control.Exception.Extensible as E import qualified Data.Map as M import System.IO.HVFS (SystemFS(..))+import Text.JSON  import Common @@ -24,7 +25,9 @@ import qualified Annex import qualified Annex.UUID import qualified Backend-import qualified Git+import qualified Git.Config+import qualified Git.Construct+import qualified Git.Filename import qualified Locations import qualified Types.Backend import qualified Types@@ -40,6 +43,9 @@ import qualified Crypto import qualified Utility.Path import qualified Utility.FileMode+import qualified Utility.Gpg+import qualified Build.SysConfig+import qualified Utility.Format  -- for quickcheck instance Arbitrary Types.Key.Key where@@ -67,7 +73,8 @@  quickcheck :: Test quickcheck = TestLabel "quickcheck" $ TestList-	[ qctest "prop_idempotent_deencode" Git.prop_idempotent_deencode+	[ qctest "prop_idempotent_deencode_git" Git.Filename.prop_idempotent_deencode+	, qctest "prop_idempotent_deencode" Utility.Format.prop_idempotent_deencode 	, qctest "prop_idempotent_fileKey" Locations.prop_idempotent_fileKey 	, qctest "prop_idempotent_key_read_show" Types.Key.prop_idempotent_key_read_show 	, qctest "prop_idempotent_shellEscape" Utility.SafeCommand.prop_idempotent_shellEscape@@ -100,11 +107,27 @@ 	, test_fsck 	, test_migrate 	, test_unused+	, test_addurl+	, test_describe+	, test_find+	, test_merge+	, test_status+	, test_version+	, test_sync+	, test_map+	, test_uninit+	, test_upgrade+	, test_whereis+	, test_hook_remote+	, test_directory_remote+	, test_rsync_remote+	, test_bup_remote+	, test_crypto 	]  test_init :: Test test_init = "git-annex init" ~: TestCase $ innewrepo $ do-	git_annex "init" ["-q", reponame] @? "init failed"+	git_annex "init" [reponame] @? "init failed" 	where 		reponame = "test repo" @@ -115,38 +138,44 @@ 		-- annexed file that later tests will use 		basic = TestCase $ inmainrepo $ do 			writeFile annexedfile $ content annexedfile-			git_annex "add" ["-q", annexedfile] @? "add failed"+			git_annex "add" [annexedfile] @? "add failed" 			annexed_present annexedfile 			writeFile sha1annexedfile $ content sha1annexedfile-			git_annex "add" ["-q", sha1annexedfile, "--backend=SHA1"] @? "add with SHA1 failed"+			git_annex "add" [sha1annexedfile, "--backend=SHA1"] @? "add with SHA1 failed" 			annexed_present sha1annexedfile+			checkbackend sha1annexedfile backendSHA1+			writeFile wormannexedfile $ content wormannexedfile+			git_annex "add" [wormannexedfile, "--backend=WORM"] @? "add with WORM failed"+			annexed_present wormannexedfile+			checkbackend wormannexedfile backendWORM+			boolSystem "git" [Params "rm --force -q", File wormannexedfile] @? "git rm failed" 			writeFile ingitfile $ content ingitfile 			boolSystem "git" [Param "add", File ingitfile] @? "git add failed" 			boolSystem "git" [Params "commit -q -a -m commit"] @? "git commit failed"-			git_annex "add" ["-q", ingitfile] @? "add ingitfile should be no-op"+			git_annex "add" [ingitfile] @? "add ingitfile should be no-op" 			unannexed ingitfile 		sha1dup = TestCase $ intmpclonerepo $ do 			writeFile sha1annexedfiledup $ content sha1annexedfiledup-			git_annex "add" ["-q", sha1annexedfiledup, "--backend=SHA1"] @? "add of second file with same SHA1 failed"+			git_annex "add" [sha1annexedfiledup, "--backend=SHA1"] @? "add of second file with same SHA1 failed" 			annexed_present sha1annexedfiledup 			annexed_present sha1annexedfile 		subdirs = TestCase $ intmpclonerepo $ do 			createDirectory "dir" 			writeFile "dir/foo" $ content annexedfile-			git_annex "add" ["-q", "dir"] @? "add of subdir failed"+			git_annex "add" ["dir"] @? "add of subdir failed" 			createDirectory "dir2" 			writeFile "dir2/foo" $ content annexedfile 			changeWorkingDirectory "dir"-			git_annex "add" ["-q", "../dir2"] @? "add of ../subdir failed"+			git_annex "add" ["../dir2"] @? "add of ../subdir failed"  test_reinject :: Test test_reinject = "git-annex reinject/fromkey" ~: TestCase $ intmpclonerepo $ do-	git_annex "drop" ["-q", "--force", sha1annexedfile] @? "drop failed"+	git_annex "drop" ["--force", sha1annexedfile] @? "drop failed" 	writeFile tmp $ content sha1annexedfile 	r <- annexeval $ Types.Backend.getKey backendSHA1 tmp 	let key = show $ fromJust r-	git_annex "reinject" ["-q", tmp, sha1annexedfile] @? "reinject failed"-	git_annex "fromkey" ["-q", key, sha1annexedfiledup] @? "fromkey failed"+	git_annex "reinject" [tmp, sha1annexedfile] @? "reinject failed"+	git_annex "fromkey" [key, sha1annexedfiledup] @? "fromkey failed" 	annexed_present sha1annexedfiledup 	where 		tmp = "tmpfile"@@ -156,45 +185,43 @@ 	where 		nocopy = "no content" ~: intmpclonerepo $ do 			annexed_notpresent annexedfile-			git_annex "unannex" ["-q", annexedfile] @? "unannex failed with no copy"+			git_annex "unannex" [annexedfile] @? "unannex failed with no copy" 			annexed_notpresent annexedfile 		withcopy = "with content" ~: intmpclonerepo $ do-			git_annex "get" ["-q", annexedfile] @? "get failed"+			git_annex "get" [annexedfile] @? "get failed" 			annexed_present annexedfile-			git_annex "unannex" ["-q", annexedfile, sha1annexedfile] @? "unannex failed"+			git_annex "unannex" [annexedfile, sha1annexedfile] @? "unannex failed" 			unannexed annexedfile-			git_annex "unannex" ["-q", annexedfile] @? "unannex failed on non-annexed file"+			git_annex "unannex" [annexedfile] @? "unannex failed on non-annexed file" 			unannexed annexedfile-			git_annex "unannex" ["-q", ingitfile] @? "unannex ingitfile should be no-op"+			git_annex "unannex" [ingitfile] @? "unannex ingitfile should be no-op" 			unannexed ingitfile  test_drop :: Test test_drop = "git-annex drop" ~: TestList [noremote, withremote, untrustedremote] 	where 		noremote = "no remotes" ~: TestCase $ intmpclonerepo $ do-			git_annex "get" ["-q", annexedfile] @? "get failed"+			git_annex "get" [annexedfile] @? "get failed" 			boolSystem "git" [Params "remote rm origin"] 				@? "git remote rm origin failed"-			r <- git_annex "drop" ["-q", annexedfile]-			not r @? "drop wrongly succeeded with no known copy of file"+			not <$> git_annex "drop" [annexedfile] @? "drop wrongly succeeded with no known copy of file" 			annexed_present annexedfile-			git_annex "drop" ["-q", "--force", annexedfile] @? "drop --force failed"+			git_annex "drop" ["--force", annexedfile] @? "drop --force failed" 			annexed_notpresent annexedfile-			git_annex "drop" ["-q", annexedfile] @? "drop of dropped file failed"-			git_annex "drop" ["-q", ingitfile] @? "drop ingitfile should be no-op"+			git_annex "drop" [annexedfile] @? "drop of dropped file failed"+			git_annex "drop" [ingitfile] @? "drop ingitfile should be no-op" 			unannexed ingitfile 		withremote = "with remote" ~: TestCase $ intmpclonerepo $ do-			git_annex "get" ["-q", annexedfile] @? "get failed"+			git_annex "get" [annexedfile] @? "get failed" 			annexed_present annexedfile-			git_annex "drop" ["-q", annexedfile] @? "drop failed though origin has copy"+			git_annex "drop" [annexedfile] @? "drop failed though origin has copy" 			annexed_notpresent annexedfile 			inmainrepo $ annexed_present annexedfile 		untrustedremote = "untrusted remote" ~: TestCase $ intmpclonerepo $ do-			git_annex "untrust" ["-q", "origin"] @? "untrust of origin failed"-			git_annex "get" ["-q", annexedfile] @? "get failed"+			git_annex "untrust" ["origin"] @? "untrust of origin failed"+			git_annex "get" [annexedfile] @? "get failed" 			annexed_present annexedfile-			r <- git_annex "drop" ["-q", annexedfile]-			not r @? "drop wrongly suceeded with only an untrusted copy of the file"+			not <$> git_annex "drop" [annexedfile] @? "drop wrongly suceeded with only an untrusted copy of the file" 			annexed_present annexedfile 			inmainrepo $ annexed_present annexedfile @@ -202,15 +229,15 @@ test_get = "git-annex get" ~: TestCase $ intmpclonerepo $ do 	inmainrepo $ annexed_present annexedfile 	annexed_notpresent annexedfile-	git_annex "get" ["-q", annexedfile] @? "get of file failed"+	git_annex "get" [annexedfile] @? "get of file failed" 	inmainrepo $ annexed_present annexedfile 	annexed_present annexedfile-	git_annex "get" ["-q", annexedfile] @? "get of file already here failed"+	git_annex "get" [annexedfile] @? "get of file already here failed" 	inmainrepo $ annexed_present annexedfile 	annexed_present annexedfile 	inmainrepo $ unannexed ingitfile 	unannexed ingitfile-	git_annex "get" ["-q", ingitfile] @? "get ingitfile should be no-op"+	git_annex "get" [ingitfile] @? "get ingitfile should be no-op" 	inmainrepo $ unannexed ingitfile 	unannexed ingitfile @@ -218,24 +245,24 @@ test_move = "git-annex move" ~: TestCase $ intmpclonerepo $ do 	annexed_notpresent annexedfile 	inmainrepo $ annexed_present annexedfile-	git_annex "move" ["-q", "--from", "origin", annexedfile] @? "move --from of file failed"+	git_annex "move" ["--from", "origin", annexedfile] @? "move --from of file failed" 	annexed_present annexedfile 	inmainrepo $ annexed_notpresent annexedfile-	git_annex "move" ["-q", "--from", "origin", annexedfile] @? "move --from of file already here failed"+	git_annex "move" ["--from", "origin", annexedfile] @? "move --from of file already here failed" 	annexed_present annexedfile 	inmainrepo $ annexed_notpresent annexedfile-	git_annex "move" ["-q", "--to", "origin", annexedfile] @? "move --to of file failed"+	git_annex "move" ["--to", "origin", annexedfile] @? "move --to of file failed" 	inmainrepo $ annexed_present annexedfile 	annexed_notpresent annexedfile-	git_annex "move" ["-q", "--to", "origin", annexedfile] @? "move --to of file already there failed"+	git_annex "move" ["--to", "origin", annexedfile] @? "move --to of file already there failed" 	inmainrepo $ annexed_present annexedfile 	annexed_notpresent annexedfile 	unannexed ingitfile 	inmainrepo $ unannexed ingitfile-	git_annex "move" ["-q", "--to", "origin", ingitfile] @? "move of ingitfile should be no-op"+	git_annex "move" ["--to", "origin", ingitfile] @? "move of ingitfile should be no-op" 	unannexed ingitfile 	inmainrepo $ unannexed ingitfile-	git_annex "move" ["-q", "--from", "origin", ingitfile] @? "move of ingitfile should be no-op"+	git_annex "move" ["--from", "origin", ingitfile] @? "move of ingitfile should be no-op" 	unannexed ingitfile 	inmainrepo $ unannexed ingitfile @@ -243,24 +270,24 @@ test_copy = "git-annex copy" ~: TestCase $ intmpclonerepo $ do 	annexed_notpresent annexedfile 	inmainrepo $ annexed_present annexedfile-	git_annex "copy" ["-q", "--from", "origin", annexedfile] @? "copy --from of file failed"+	git_annex "copy" ["--from", "origin", annexedfile] @? "copy --from of file failed" 	annexed_present annexedfile 	inmainrepo $ annexed_present annexedfile-	git_annex "copy" ["-q", "--from", "origin", annexedfile] @? "copy --from of file already here failed"+	git_annex "copy" ["--from", "origin", annexedfile] @? "copy --from of file already here failed" 	annexed_present annexedfile 	inmainrepo $ annexed_present annexedfile-	git_annex "copy" ["-q", "--to", "origin", annexedfile] @? "copy --to of file already there failed"+	git_annex "copy" ["--to", "origin", annexedfile] @? "copy --to of file already there failed" 	annexed_present annexedfile 	inmainrepo $ annexed_present annexedfile-	git_annex "move" ["-q", "--to", "origin", annexedfile] @? "move --to of file already there failed"+	git_annex "move" ["--to", "origin", annexedfile] @? "move --to of file already there failed" 	annexed_notpresent annexedfile 	inmainrepo $ annexed_present annexedfile 	unannexed ingitfile 	inmainrepo $ unannexed ingitfile-	git_annex "copy" ["-q", "--to", "origin", ingitfile] @? "copy of ingitfile should be no-op"+	git_annex "copy" ["--to", "origin", ingitfile] @? "copy of ingitfile should be no-op" 	unannexed ingitfile 	inmainrepo $ unannexed ingitfile-	git_annex "copy" ["-q", "--from", "origin", ingitfile] @? "copy of ingitfile should be no-op"+	git_annex "copy" ["--from", "origin", ingitfile] @? "copy of ingitfile should be no-op" 	checkregularfile ingitfile 	checkcontent ingitfile @@ -268,36 +295,35 @@ test_lock = "git-annex unlock/lock" ~: intmpclonerepo $ do 	-- regression test: unlock of not present file should skip it 	annexed_notpresent annexedfile-	r <- git_annex "unlock" ["-q", annexedfile]-	not r @? "unlock failed to fail with not present file"+	not <$> git_annex "unlock" [annexedfile] @? "unlock failed to fail with not present file" 	annexed_notpresent annexedfile -	git_annex "get" ["-q", annexedfile] @? "get of file failed"+	git_annex "get" [annexedfile] @? "get of file failed" 	annexed_present annexedfile-	git_annex "unlock" ["-q", annexedfile] @? "unlock failed"		+	git_annex "unlock" [annexedfile] @? "unlock failed"		 	unannexed annexedfile 	-- write different content, to verify that lock 	-- throws it away 	changecontent annexedfile 	writeFile annexedfile $ content annexedfile ++ "foo"-	git_annex "lock" ["-q", annexedfile] @? "lock failed"+	git_annex "lock" [annexedfile] @? "lock failed" 	annexed_present annexedfile-	git_annex "unlock" ["-q", annexedfile] @? "unlock failed"		+	git_annex "unlock" [annexedfile] @? "unlock failed"		 	unannexed annexedfile 	changecontent annexedfile-	git_annex "add" ["-q", annexedfile] @? "add of modified file failed"+	git_annex "add" [annexedfile] @? "add of modified file failed" 	runchecks [checklink, checkunwritable] annexedfile 	c <- readFile annexedfile 	assertEqual "content of modified file" c (changedcontent annexedfile)-	r' <- git_annex "drop" ["-q", annexedfile]+	r' <- git_annex "drop" [annexedfile] 	not r' @? "drop wrongly succeeded with no known copy of modified file"  test_edit :: Test test_edit = "git-annex edit/commit" ~: TestList [t False, t True] 	where t precommit = TestCase $ intmpclonerepo $ do-		git_annex "get" ["-q", annexedfile] @? "get of file failed"+		git_annex "get" [annexedfile] @? "get of file failed" 		annexed_present annexedfile-		git_annex "edit" ["-q", annexedfile] @? "edit failed"+		git_annex "edit" [annexedfile] @? "edit failed" 		unannexed annexedfile 		changecontent annexedfile 		if precommit@@ -306,7 +332,7 @@ 				-- staged, normally git commit does this 				boolSystem "git" [Param "add", File annexedfile] 					@? "git add of edited file failed"-				git_annex "pre-commit" ["-q"]+				git_annex "pre-commit" [] 					@? "pre-commit failed" 			else do 				boolSystem "git" [Params "commit -q -a -m contentchanged"]@@ -314,22 +340,21 @@ 		runchecks [checklink, checkunwritable] annexedfile 		c <- readFile annexedfile 		assertEqual "content of modified file" c (changedcontent annexedfile)-		r <- git_annex "drop" ["-q", annexedfile]-		not r @? "drop wrongly succeeded with no known copy of modified file"+		not <$> git_annex "drop" [annexedfile] @? "drop wrongly succeeded with no known copy of modified file"  test_fix :: Test test_fix = "git-annex fix" ~: intmpclonerepo $ do 	annexed_notpresent annexedfile-	git_annex "fix" ["-q", annexedfile] @? "fix of not present failed"+	git_annex "fix" [annexedfile] @? "fix of not present failed" 	annexed_notpresent annexedfile-	git_annex "get" ["-q", annexedfile] @? "get of file failed"+	git_annex "get" [annexedfile] @? "get of file failed" 	annexed_present annexedfile-	git_annex "fix" ["-q", annexedfile] @? "fix of present file failed"+	git_annex "fix" [annexedfile] @? "fix of present file failed" 	annexed_present annexedfile 	createDirectory subdir 	boolSystem "git" [Param "mv", File annexedfile, File subdir] 		@? "git mv failed"-	git_annex "fix" ["-q", newfile] @? "fix of moved file failed"+	git_annex "fix" [newfile] @? "fix of moved file failed" 	runchecks [checklink, checkunwritable] newfile 	c <- readFile newfile 	assertEqual "content of moved file" c (content annexedfile)@@ -338,18 +363,22 @@ 		newfile = subdir ++ "/" ++ annexedfile  test_trust :: Test-test_trust = "git-annex trust/untrust/semitrust" ~: intmpclonerepo $ do-	git_annex "trust" ["-q", repo] @? "trust failed"+test_trust = "git-annex trust/untrust/semitrust/dead" ~: intmpclonerepo $ do+	git_annex "trust" [repo] @? "trust failed" 	trustcheck Logs.Trust.Trusted "trusted 1"-	git_annex "trust" ["-q", repo] @? "trust of trusted failed"+	git_annex "trust" [repo] @? "trust of trusted failed" 	trustcheck Logs.Trust.Trusted "trusted 2"-	git_annex "untrust" ["-q", repo] @? "untrust failed"+	git_annex "untrust" [repo] @? "untrust failed" 	trustcheck Logs.Trust.UnTrusted "untrusted 1"-	git_annex "untrust" ["-q", repo] @? "untrust of untrusted failed"+	git_annex "untrust" [repo] @? "untrust of untrusted failed" 	trustcheck Logs.Trust.UnTrusted "untrusted 2"-	git_annex "semitrust" ["-q", repo] @? "semitrust failed"+	git_annex "dead" [repo] @? "dead failed"+	trustcheck Logs.Trust.DeadTrusted "deadtrusted 1"+	git_annex "dead" [repo] @? "dead of dead failed"+	trustcheck Logs.Trust.DeadTrusted "deadtrusted 2"+	git_annex "semitrust" [repo] @? "semitrust failed" 	trustcheck Logs.Trust.SemiTrusted "semitrusted 1"-	git_annex "semitrust" ["-q", repo] @? "semitrust of semitrusted failed"+	git_annex "semitrust" [repo] @? "semitrust of semitrusted failed" 	trustcheck Logs.Trust.SemiTrusted "semitrusted 2" 	where 		repo = "origin"@@ -361,63 +390,63 @@ 			assertBool msg present  test_fsck :: Test-test_fsck = "git-annex fsck" ~: TestList [basicfsck, withlocaluntrusted, withremoteuntrusted]+test_fsck = "git-annex fsck" ~: TestList [basicfsck, barefsck, withlocaluntrusted, withremoteuntrusted] 	where 		basicfsck = TestCase $ intmpclonerepo $ do-			git_annex "fsck" ["-q"] @? "fsck failed"+			git_annex "fsck" [] @? "fsck failed" 			boolSystem "git" [Params "config annex.numcopies 2"] @? "git config failed" 			fsck_should_fail "numcopies unsatisfied" 			boolSystem "git" [Params "config annex.numcopies 1"] @? "git config failed" 			corrupt annexedfile 			corrupt sha1annexedfile+		barefsck = TestCase $ intmpbareclonerepo $ do+			git_annex "fsck" [] @? "fsck failed" 		withlocaluntrusted = TestCase $ intmpclonerepo $ do-			git_annex "get" ["-q", annexedfile] @? "get failed"-			git_annex "untrust" ["-q", "origin"] @? "untrust of origin repo failed"-			git_annex "untrust" ["-q", "."] @? "untrust of current repo failed"+			git_annex "get" [annexedfile] @? "get failed"+			git_annex "untrust" ["origin"] @? "untrust of origin repo failed"+			git_annex "untrust" ["."] @? "untrust of current repo failed" 			fsck_should_fail "content only available in untrusted (current) repository"-			git_annex "trust" ["-q", "."] @? "trust of current repo failed"-			git_annex "fsck" ["-q", annexedfile] @? "fsck failed on file present in trusted repo"+			git_annex "trust" ["."] @? "trust of current repo failed"+			git_annex "fsck" [annexedfile] @? "fsck failed on file present in trusted repo" 		withremoteuntrusted = TestCase $ intmpclonerepo $ do 			boolSystem "git" [Params "config annex.numcopies 2"] @? "git config failed"-			git_annex "get" ["-q", annexedfile] @? "get failed"-			git_annex "get" ["-q", sha1annexedfile] @? "get failed"-			git_annex "fsck" ["-q"] @? "fsck failed with numcopies=2 and 2 copies"-			git_annex "untrust" ["-q", "origin"] @? "untrust of origin failed"+			git_annex "get" [annexedfile] @? "get failed"+			git_annex "get" [sha1annexedfile] @? "get failed"+			git_annex "fsck" [] @? "fsck failed with numcopies=2 and 2 copies"+			git_annex "untrust" ["origin"] @? "untrust of origin failed" 			fsck_should_fail "content not replicated to enough non-untrusted repositories"  		corrupt f = do-			git_annex "get" ["-q", f] @? "get of file failed"+			git_annex "get" [f] @? "get of file failed" 			Utility.FileMode.allowWrite f 			writeFile f (changedcontent f)-			r <- git_annex "fsck" ["-q"]-			not r @? "fsck failed to fail with corrupted file content"-			git_annex "fsck" ["-q"] @? "fsck unexpectedly failed again; previous one did not fix problem with " ++ f+			not <$> git_annex "fsck" [] @? "fsck failed to fail with corrupted file content"+			git_annex "fsck" [] @? "fsck unexpectedly failed again; previous one did not fix problem with " ++ f 		fsck_should_fail m = do-			r <- git_annex "fsck" ["-q"]-			not r @? "fsck failed to fail with " ++ m+			not <$> git_annex "fsck" [] @? "fsck failed to fail with " ++ m  test_migrate :: Test test_migrate = "git-annex migrate" ~: TestList [t False, t True] 	where t usegitattributes = TestCase $ intmpclonerepo $ do 		annexed_notpresent annexedfile 		annexed_notpresent sha1annexedfile-		git_annex "migrate" ["-q", annexedfile] @? "migrate of not present failed"-		git_annex "migrate" ["-q", sha1annexedfile] @? "migrate of not present failed"-		git_annex "get" ["-q", annexedfile] @? "get of file failed"-		git_annex "get" ["-q", sha1annexedfile] @? "get of file failed"+		git_annex "migrate" [annexedfile] @? "migrate of not present failed"+		git_annex "migrate" [sha1annexedfile] @? "migrate of not present failed"+		git_annex "get" [annexedfile] @? "get of file failed"+		git_annex "get" [sha1annexedfile] @? "get of file failed" 		annexed_present annexedfile 		annexed_present sha1annexedfile 		if usegitattributes 			then do 				writeFile ".gitattributes" $ "* annex.backend=SHA1"-				git_annex "migrate" ["-q", sha1annexedfile]+				git_annex "migrate" [sha1annexedfile] 					@? "migrate sha1annexedfile failed"-				git_annex "migrate" ["-q", annexedfile]+				git_annex "migrate" [annexedfile] 					@? "migrate annexedfile failed" 			else do-				git_annex "migrate" ["-q", sha1annexedfile, "--backend", "SHA1"]+				git_annex "migrate" [sha1annexedfile, "--backend", "SHA1"] 					@? "migrate sha1annexedfile failed"-				git_annex "migrate" ["-q", annexedfile, "--backend", "SHA1"]+				git_annex "migrate" [annexedfile, "--backend", "SHA1"] 					@? "migrate annexedfile failed" 		annexed_present annexedfile 		annexed_present sha1annexedfile@@ -425,29 +454,23 @@ 		checkbackend sha1annexedfile backendSHA1  		-- check that reversing a migration works-		writeFile ".gitattributes" $ "* annex.backend=WORM"-		git_annex "migrate" ["-q", sha1annexedfile]+		writeFile ".gitattributes" $ "* annex.backend=SHA256"+		git_annex "migrate" [sha1annexedfile] 			@? "migrate sha1annexedfile failed"-		git_annex "migrate" ["-q", annexedfile]+		git_annex "migrate" [annexedfile] 			@? "migrate annexedfile failed" 		annexed_present annexedfile 		annexed_present sha1annexedfile-		checkbackend annexedfile backendWORM-		checkbackend sha1annexedfile backendWORM-		-		where-			checkbackend file expected = do-				r <- annexeval $ Backend.lookupFile file-				let b = snd $ fromJust r-				assertEqual ("backend for " ++ file) expected b+		checkbackend annexedfile backendSHA256+		checkbackend sha1annexedfile backendSHA256  test_unused :: Test test_unused = "git-annex unused/dropunused" ~: intmpclonerepo $ do 	-- keys have to be looked up before files are removed 	annexedfilekey <- annexeval $ findkey annexedfile 	sha1annexedfilekey <- annexeval $ findkey sha1annexedfile-	git_annex "get" ["-q", annexedfile] @? "get of file failed"-	git_annex "get" ["-q", sha1annexedfile] @? "get of file failed"+	git_annex "get" [annexedfile] @? "get of file failed"+	git_annex "get" [sha1annexedfile] @? "get of file failed" 	checkunused [] 	boolSystem "git" [Params "rm -q", File annexedfile] @? "git rm failed" 	checkunused []@@ -461,17 +484,17 @@ 	checkunused [annexedfilekey, sha1annexedfilekey]  	-- good opportunity to test dropkey also-	git_annex "dropkey" ["-q", "--force", show annexedfilekey]+	git_annex "dropkey" ["--force", show annexedfilekey] 		@? "dropkey failed" 	checkunused [sha1annexedfilekey] -	git_annex "dropunused" ["-q", "1", "2"] @? "dropunused failed"+	git_annex "dropunused" ["1", "2"] @? "dropunused failed" 	checkunused []-	git_annex "dropunused" ["-q", "10", "501"] @? "dropunused failed on bogus numbers"+	git_annex "dropunused" ["10", "501"] @? "dropunused failed on bogus numbers"  	where 		checkunused expectedkeys = do-			git_annex "unused" ["-q"] @? "unused failed"+			git_annex "unused" [] @? "unused failed" 			unusedmap <- annexeval $ Command.DropUnused.readUnusedLog "" 			let unusedkeys = M.elems unusedmap 			assertEqual "unused keys differ"@@ -480,6 +503,188 @@ 			r <- Backend.lookupFile f 			return $ fst $ fromJust r +test_addurl :: Test+test_addurl = "git-annex addurl" ~: intmpclonerepo $ do+	annexed_notpresent annexedfile+	-- can't check download; test suite should not access network,+	-- and starting up a web server seems excessive+	git_annex "addurl" ["--fast", "http://example.com/nosuchfile"] @? "addurl failed"++test_describe :: Test+test_describe = "git-annex describe" ~: intmpclonerepo $ do+	git_annex "describe" [".", "this repo"] @? "describe 1 failed"+	git_annex "describe" ["origin", "origin repo"] @? "describe 2 failed"++test_find :: Test+test_find = "git-annex find" ~: intmpclonerepo $ do+	annexed_notpresent annexedfile+	git_annex_expectoutput "find" [] []+	git_annex "get" [annexedfile] @? "get failed"+	annexed_present annexedfile+	annexed_notpresent sha1annexedfile+	git_annex_expectoutput "find" [] [annexedfile]+	git_annex_expectoutput "find" ["--exclude", annexedfile, "--and", "--exclude", sha1annexedfile] []+	git_annex_expectoutput "find" ["--include", annexedfile] [annexedfile]+	git_annex_expectoutput "find" ["--not", "--in", "origin"] []+	git_annex_expectoutput "find" ["--copies", "1", "--and", "--not", "--copies", "2"] [sha1annexedfile]+	git_annex_expectoutput "find" ["--inbackend", "SHA1"] [sha1annexedfile]+	git_annex_expectoutput "find" ["--inbackend", "WORM"] []++test_merge :: Test+test_merge = "git-annex merge" ~: intmpclonerepo $ do+	git_annex "merge" [] @? "merge failed"++test_status :: Test+test_status = "git-annex status" ~: intmpclonerepo $ do+	json <- git_annex_output "status" ["--json"]+	case Text.JSON.decodeStrict json :: Text.JSON.Result (JSObject JSValue) of+		Ok _ -> return ()+		Error e -> assertFailure e++test_version :: Test+test_version = "git-annex version" ~: intmpclonerepo $ do+	git_annex "version" [] @? "version failed"++test_sync :: Test+test_sync = "git-annex sync" ~: intmpclonerepo $ do+	git_annex "sync" [] @? "sync failed"++test_map :: Test+test_map = "git-annex map" ~: intmpclonerepo $ do+	-- set descriptions, that will be looked for in the map+	git_annex "describe" [".", "this repo"] @? "describe 1 failed"+	git_annex "describe" ["origin", "origin repo"] @? "describe 2 failed"+	-- --fast avoids it running graphviz, not a build dependency+	git_annex "map" ["--fast"] @? "map failed"+	doesFileExist "map.dot" @? "map.dot not generated"+	c <- readFile "map.dot"+	("this repo" `isInfixOf` c && "origin repo" `isInfixOf` c) @? ("map.dot bad content: " ++ c)++test_uninit :: Test+test_uninit = "git-annex uninit" ~: intmpclonerepo $ do+	git_annex "get" [] @? "get failed"+	annexed_present annexedfile+	boolSystem "git" [Params "checkout git-annex"] @? "git checkout git-annex"+	not <$> git_annex "uninit" [] @? "uninit failed to fail when git-annex branch was checked out"+	boolSystem "git" [Params "checkout master"] @? "git checkout master"+	_ <- git_annex "uninit" [] -- exit status not checked; does abnormal exit+	checkregularfile annexedfile+	doesDirectoryExist ".git" @? ".git vanished in uninit"+	not <$> doesDirectoryExist ".git/annex" @? ".git/annex still present after uninit"++test_upgrade :: Test+test_upgrade = "git-annex upgrade" ~: intmpclonerepo $ do+	git_annex "upgrade" [] @? "upgrade from same version failed"++test_whereis :: Test+test_whereis = "git-annex whereis" ~: intmpclonerepo $ do+	annexed_notpresent annexedfile+	git_annex "whereis" [annexedfile] @? "whereis on non-present file failed"+	git_annex "untrust" ["origin"] @? "untrust failed"+	not <$> git_annex "whereis" [annexedfile] @? "whereis on non-present file only present in untrusted repo failed to fail"+	git_annex "get" [annexedfile] @? "get failed"+	annexed_present annexedfile+	git_annex "whereis" [annexedfile] @? "whereis on present file failed"++test_hook_remote :: Test+test_hook_remote = "git-annex hook remote" ~: intmpclonerepo $ do+	git_annex "initremote" (words "foo type=hook encryption=none hooktype=foo") @? "initremote failed"+	createDirectory dir+	git_config "annex.foo-store-hook" $+		"cp $ANNEX_FILE " ++ loc+	git_config "annex.foo-retrieve-hook" $+		"cp " ++ loc ++ " $ANNEX_FILE"+	git_config "annex.foo-remove-hook" $+		"rm -f " ++ loc+	git_config "annex.foo-checkpresent-hook" $+		"if [ -e " ++ loc ++ " ]; then echo $ANNEX_KEY; fi"+	git_annex "get" [annexedfile] @? "get of file failed"+	annexed_present annexedfile+	git_annex "copy" [annexedfile, "--to", "foo"] @? "copy --to hook remote failed"+	annexed_present annexedfile+	git_annex "drop" [annexedfile, "--numcopies=2"] @? "drop failed"+	annexed_notpresent annexedfile+	git_annex "move" [annexedfile, "--from", "foo"] @? "move --from hook remote failed"+	annexed_present annexedfile+	not <$> git_annex "drop" [annexedfile, "--numcopies=2"] @? "drop failed to fail"+	annexed_present annexedfile+	where+		dir = "dir"+		loc = dir ++ "/$ANNEX_KEY"+		git_config k v = boolSystem "git" [Param "config", Param k, Param v]+			@? "git config failed"++test_directory_remote :: Test+test_directory_remote = "git-annex directory remote" ~: intmpclonerepo $ do+	createDirectory "dir"+	git_annex "initremote" (words $ "foo type=directory encryption=none directory=dir") @? "initremote failed"+	git_annex "get" [annexedfile] @? "get of file failed"+	annexed_present annexedfile+	git_annex "copy" [annexedfile, "--to", "foo"] @? "copy --to directory remote failed"+	annexed_present annexedfile+	git_annex "drop" [annexedfile, "--numcopies=2"] @? "drop failed"+	annexed_notpresent annexedfile+	git_annex "move" [annexedfile, "--from", "foo"] @? "move --from directory remote failed"+	annexed_present annexedfile+	not <$> git_annex "drop" [annexedfile, "--numcopies=2"] @? "drop failed to fail"+	annexed_present annexedfile++test_rsync_remote :: Test+test_rsync_remote = "git-annex rsync remote" ~: intmpclonerepo $ do+	createDirectory "dir"+	git_annex "initremote" (words $ "foo type=rsync encryption=none rsyncurl=dir") @? "initremote failed"+	git_annex "get" [annexedfile] @? "get of file failed"+	annexed_present annexedfile+	git_annex "copy" [annexedfile, "--to", "foo"] @? "copy --to rsync remote failed"+	annexed_present annexedfile+	git_annex "drop" [annexedfile, "--numcopies=2"] @? "drop failed"+	annexed_notpresent annexedfile+	git_annex "move" [annexedfile, "--from", "foo"] @? "move --from rsync remote failed"+	annexed_present annexedfile+	not <$> git_annex "drop" [annexedfile, "--numcopies=2"] @? "drop failed to fail"+	annexed_present annexedfile++test_bup_remote :: Test+test_bup_remote = "git-annex bup remote" ~: intmpclonerepo $ when Build.SysConfig.bup $ do+	dir <- absPath "dir" -- bup special remote needs an absolute path+	createDirectory dir+	git_annex "initremote" (words $ "foo type=bup encryption=none buprepo="++dir) @? "initremote failed"+	git_annex "get" [annexedfile] @? "get of file failed"+	annexed_present annexedfile+	git_annex "copy" [annexedfile, "--to", "foo"] @? "copy --to bup remote failed"+	annexed_present annexedfile+	git_annex "drop" [annexedfile, "--numcopies=2"] @? "drop failed"+	annexed_notpresent annexedfile+	git_annex "copy" [annexedfile, "--from", "foo"] @? "copy --from bup remote failed"+	annexed_present annexedfile+	not <$> git_annex "move" [annexedfile, "--from", "foo"] @? "move --from bup remote failed to fail"+	annexed_present annexedfile++-- gpg is not a build dependency, so only test when it's available+test_crypto :: Test+test_crypto = "git-annex crypto" ~: intmpclonerepo $ when Build.SysConfig.gpg $ do+	Utility.Gpg.testTestHarness @? "test harness self-test failed"+	Utility.Gpg.testHarness $ do+		createDirectory "dir"+		let initremote = git_annex "initremote"+			[ "foo"+			, "type=directory"+			, "encryption=" ++ Utility.Gpg.testKeyId+			, "directory=dir"+			]+		initremote @? "initremote failed"+		initremote @? "initremote failed when run twice in a row"+		git_annex "get" [annexedfile] @? "get of file failed"+		annexed_present annexedfile+		git_annex "copy" [annexedfile, "--to", "foo"] @? "copy --to encrypted remote failed"+		annexed_present annexedfile+		git_annex "drop" [annexedfile, "--numcopies=2"] @? "drop failed"+		annexed_notpresent annexedfile+		git_annex "move" [annexedfile, "--from", "foo"] @? "move --from encrypted remote failed"+		annexed_present annexedfile+		not <$> git_annex "drop" [annexedfile, "--numcopies=2"] @? "drop failed to fail"+		annexed_present annexedfile	+ -- This is equivilant to running git-annex, but it's all run in-process -- so test coverage collection works. git_annex :: String -> [String] -> IO Bool@@ -490,16 +695,41 @@ 		Right _ -> return True 		Left _ -> return False 	where-		run = GitAnnex.run (command:params)+		run = GitAnnex.run (command:"-q":params) +{- Runs git-annex and returns its output. -}+git_annex_output :: String -> [String] -> IO String+git_annex_output command params = do+	(frompipe, topipe) <- createPipe+	pid <- forkProcess $ do+		_ <- dupTo topipe stdOutput+		closeFd frompipe+		_ <- git_annex command params+		exitSuccess+	-- XXX since the above is a separate process, code coverage stats are+	-- not gathered for things run in it.+	closeFd topipe+	fromh <- fdToHandle frompipe+	got <- hGetContentsStrict fromh+	hClose fromh+	_ <- getProcessStatus True False pid+	-- XXX hack Run same command again, to get code coverage.+	_ <- git_annex command params+	return got++git_annex_expectoutput :: String -> [String] -> [String] -> IO ()+git_annex_expectoutput command params expected = do+	got <- lines <$> git_annex_output command params+	got == expected @? ("unexpected value running " ++ command ++ " " ++ show params ++ " -- got: " ++ show got ++ " expected: " ++ show expected)+ -- Runs an action in the current annex. Note that shutdown actions -- are not run; this should only be used for actions that query state. annexeval :: Types.Annex a -> IO a annexeval a = do-	g <- Git.repoFromCwd-	g' <- Git.configRead g+	g <- Git.Construct.fromCwd+	g' <- Git.Config.read g 	s <- Annex.new g'-	Annex.eval s a+	Annex.eval s { Annex.output = Annex.QuietOutput } a  innewrepo :: Assertion -> Assertion innewrepo a = withgitrepo $ \r -> indir r a@@ -508,11 +738,14 @@ inmainrepo a = indir mainrepodir a  intmpclonerepo :: Assertion -> Assertion-intmpclonerepo a = withtmpclonerepo $ \r -> indir r a+intmpclonerepo a = withtmpclonerepo False $ \r -> indir r a -withtmpclonerepo :: (FilePath -> Assertion) -> Assertion-withtmpclonerepo = bracket (clonerepo mainrepodir tmprepodir) cleanup+intmpbareclonerepo :: Assertion -> Assertion+intmpbareclonerepo a = withtmpclonerepo True $ \r -> indir r a +withtmpclonerepo :: Bool -> (FilePath -> Assertion) -> Assertion+withtmpclonerepo bare = bracket (clonerepo mainrepodir tmprepodir bare) cleanup+ withgitrepo :: (FilePath -> Assertion) -> Assertion withgitrepo = bracket (setuprepo mainrepodir) return @@ -539,11 +772,12 @@ 	return dir  -- clones are always done as local clones; we cannot test ssh clones-clonerepo :: FilePath -> FilePath -> IO FilePath-clonerepo old new = do+clonerepo :: FilePath -> FilePath -> Bool -> IO FilePath+clonerepo old new bare = do 	cleanup new 	ensuretmpdir-	boolSystem "git" [Params "clone -q", File old, File new] @? "git clone failed"+	let b = if bare then " --bare" else ""+	boolSystem "git" [Params ("clone -q" ++ b), File old, File new] @? "git clone failed" 	indir new $ git_annex "init" ["-q", new] @? "git annex init failed" 	return new 	@@ -616,6 +850,12 @@ 				expected (thisuuid `elem` uuids) 		_ -> assertFailure $ f ++ " failed to look up key" +checkbackend :: FilePath -> Types.Backend -> Assertion+checkbackend file expected = do+	r <- annexeval $ Backend.lookupFile file+	let b = snd $ fromJust r+	assertEqual ("backend for " ++ file) expected b+ inlocationlog :: FilePath -> Assertion inlocationlog f = checklocationlog f True @@ -669,6 +909,9 @@ annexedfile :: String annexedfile = "foo" +wormannexedfile :: String+wormannexedfile = "apple"+ sha1annexedfile :: String sha1annexedfile = "sha1foo" @@ -684,6 +927,7 @@ 	| f == ingitfile = "normal file content" 	| f == sha1annexedfile ="sha1 annexed file content" 	| f == sha1annexedfiledup = content sha1annexedfile+	| f == wormannexedfile = "worm annexed file content" 	| otherwise = "unknown file " ++ f  changecontent :: FilePath -> IO ()@@ -692,11 +936,14 @@ changedcontent :: FilePath -> String changedcontent f = (content f) ++ " (modified)" -backendSHA1 :: Types.Backend Types.Annex+backendSHA1 :: Types.Backend backendSHA1 = backend_ "SHA1" -backendWORM :: Types.Backend Types.Annex+backendSHA256 :: Types.Backend+backendSHA256 = backend_ "SHA256"++backendWORM :: Types.Backend backendWORM = backend_ "WORM" -backend_ :: String -> Types.Backend Types.Annex+backend_ :: String -> Types.Backend backend_ name = Backend.lookupBackendName name