summaryrefslogtreecommitdiff
path: root/Branch.hs
blob: 9d7b1b0941f8c86499b6e768aecd2e41b21f041e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
{- management of the git-annex branch
 -
 - Copyright 2011 Joey Hess <joey@kitenet.net>
 -
 - Licensed under the GNU GPL version 3 or higher.
 -}

module Branch (
	update,
	get,
	change,
	commit
) where

import Control.Monad (unless, liftM)
import Control.Monad.State (liftIO)
import System.FilePath
import System.Directory
import Data.String.Utils
import System.Cmd.Utils
import Data.Maybe

import qualified GitRepo as Git
import qualified GitUnionMerge
import qualified Annex
import Utility
import Types
import Messages

{- Name of the branch that is used to store git-annex's information. -}
name :: String
name = "git-annex"

{- Fully qualified name of the branch. -}
fullname :: String
fullname = "refs/heads/" ++ name

{- A separate index file for the branch. -}
index :: Git.Repo -> FilePath
index g = Git.workTree g </> Git.gitDir g </> "index." ++ name

{- Populates the branch's index file with the current branch contents.
 - 
 - Usually, 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.
 -}
genIndex :: FilePath -> Git.Repo -> IO ()
genIndex f g = do
	ls <- Git.pipeNullSplit g $
		map Param ["ls-tree", "-z", "-r", "--full-tree", fullname]
	forceSuccess =<< Git.pipeWrite g
		(map Param ["update-index", "-z", "--index-info"])
		(join "\0" ls)

{- Runs an action using the branch's index file. -}
withIndex :: Annex a -> Annex a
withIndex a = do
	g <- Annex.gitRepo
	let f = index g
	liftIO $ Git.useIndex f

	e <- liftIO $ doesFileExist f
	unless e $ liftIO $ genIndex f g

	r <- a
	liftIO $ Git.useDefaultIndex
	return r

{- Ensures that the branch is up-to-date; should be called before
 - data is read from it. Runs only once per git-annex run. -}
update :: Annex ()
update = do
	updated <- Annex.getState Annex.updated
	unless updated $ withIndex $ do
		g <- Annex.gitRepo
		r <- liftIO $ Git.pipeRead g [Param "show-ref", Param name]
		let refs = map (last . words) (lines r)
		updated <- catMaybes `liftM` mapM updateRef refs
		unless (null updated) $ liftIO $
			GitUnionMerge.commit g "update" fullname
				(fullname:updated)
		Annex.changeState $ \s -> s { Annex.updated = True }

{- Ensures that a given ref has been merged into the index. -}
updateRef :: String -> Annex (Maybe String)
updateRef ref
	| ref == fullname = return Nothing
	| otherwise = do
		g <- Annex.gitRepo
		diffs <- liftIO $ Git.pipeRead g [
			Param "log",
			Param (name++".."++ref),
			Params "--oneline -n1"
			]
		if (null diffs)
			then return Nothing
			else do
				showSideAction $ "merging " ++ ref ++ " into " ++ name ++ "..."
				-- By passing only one ref, it is actually
				-- merged into the index, preserving any
				-- changes that may already be staged.
				liftIO $ GitUnionMerge.merge g [ref]
				return $ Just ref

{- Stages the content of a file into the branch's index. -}
change :: FilePath -> String -> Annex ()
change file content = do
	g <- Annex.gitRepo
	sha <- liftIO $ Git.hashObject g content
	withIndex $ liftIO $ Git.run g "update-index"
		[ Params "--add --cacheinfo 100644 ",
		  Param sha, File file]

{- Commits staged changes to the branch. -}
commit :: String -> Annex ()
commit message = withIndex $ do
	g <- Annex.gitRepo
	liftIO $ GitUnionMerge.commit g message fullname []

{- Gets the content of a file on the branch, or content staged in the index
 - if it's newer. Returns an empty string if the file didn't exist yet. -}
get :: FilePath -> Annex String
get file = update >> do
	withIndex $ do
		g <- Annex.gitRepo
		liftIO $ catch (cat g) (const $ return "")
	where
		-- To avoid stderr from cat-file when file does not exist,
		-- first run it with -e to check that it exists.
		cat g = do
			Git.run g "cat-file" [Param "-e", catfile]
			Git.pipeRead g [Param "cat-file", Param "blob", catfile]
		catfile = Param $ ':':file