aboutsummaryrefslogtreecommitdiff
path: root/Logs/Export.hs
blob: 2327d70d1f7d2e5950d6b8a0c16076fbb268921b (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
{- git-annex export log
 -
 - Copyright 2017 Joey Hess <id@joeyh.name>
 -
 - Licensed under the GNU GPL version 3 or higher.
 -}

module Logs.Export where

import qualified Data.Map as M

import Annex.Common
import qualified Annex.Branch
import qualified Git
import qualified Git.Branch
import Git.Tree
import Git.Sha
import Git.FilePath
import Logs
import Logs.UUIDBased
import Annex.UUID

data Exported = Exported
	{ exportedTreeish :: Git.Ref
	, incompleteExportedTreeish :: [Git.Ref]
	}
	deriving (Eq)

-- | Get what's been exported to a special remote.
--
-- If the list contains multiple items, there was an export conflict,
-- and different trees were exported to the same special remote.
getExport :: UUID -> Annex [Exported]
getExport remoteuuid = nub . mapMaybe get . M.elems . simpleMap 
	. parseLogNew parseExportLog
	<$> Annex.Branch.get exportLog
  where
	get (ExportLog exported u)
		| u == remoteuuid = Just exported
		| otherwise = Nothing

data ExportChange = ExportChange
	{ oldTreeish :: [Git.Ref]
	, newTreeish :: Git.Ref
	}

-- | Record a change in what's exported to a special remote.
--
-- This is called before an export begins uploading new files to the
-- remote, but after it's cleaned up any files that need to be deleted
-- from the old treeish.
--
-- Any entries in the log for the oldTreeish will be updated to the
-- newTreeish. This way, when multiple repositories are exporting to
-- the same special remote, there's no conflict as long as they move
-- forward in lock-step.
--
-- Also, the newTreeish is grafted into the git-annex branch. This is done
-- to ensure that it's available later.
recordExport :: UUID -> ExportChange -> Annex ()
recordExport remoteuuid ec = do
	c <- liftIO currentVectorClock
	u <- getUUID
	let val = ExportLog (Exported (newTreeish ec) []) remoteuuid
	Annex.Branch.change exportLog $
		showLogNew formatExportLog 
			. changeLog c u val 
			. M.mapWithKey (updateothers c u)
			. parseLogNew parseExportLog
  where
	updateothers c u theiru le@(LogEntry _ (ExportLog exported@(Exported { exportedTreeish = t }) remoteuuid'))
		| u == theiru || remoteuuid' /= remoteuuid || t `notElem` oldTreeish ec = le
		| otherwise = LogEntry c (ExportLog (exported { exportedTreeish = newTreeish ec }) theiru)

-- | Record the beginning of an export, to allow cleaning up from
-- interrupted exports.
--
-- This is called before any changes are made to the remote.
recordExportBeginning :: UUID -> Git.Ref -> Annex ()
recordExportBeginning remoteuuid newtree = do
	c <- liftIO currentVectorClock
	u <- getUUID
	ExportLog old _ <- fromMaybe (ExportLog (Exported emptyTree []) remoteuuid)
		. M.lookup u . simpleMap 
		. parseLogNew parseExportLog
		<$> Annex.Branch.get exportLog
	let new = old { incompleteExportedTreeish = nub (newtree:incompleteExportedTreeish old) }
	Annex.Branch.change exportLog $
		showLogNew formatExportLog 
			. changeLog c u (ExportLog new remoteuuid)
			. parseLogNew parseExportLog
	graftTreeish newtree

data ExportLog = ExportLog Exported UUID

formatExportLog :: ExportLog -> String
formatExportLog (ExportLog exported remoteuuid) = unwords $
	[ Git.fromRef (exportedTreeish exported)
	, fromUUID remoteuuid
	] ++ map Git.fromRef (incompleteExportedTreeish exported)

parseExportLog :: String -> Maybe ExportLog
parseExportLog s = case words s of
	(et:u:it) -> Just $
		ExportLog (Exported (Git.Ref et) (map Git.Ref it)) (toUUID u)
	_ -> Nothing

-- To prevent git-annex branch merge conflicts, the treeish is
-- first grafted in and then removed in a subsequent commit.
graftTreeish :: Git.Ref -> Annex ()
graftTreeish treeish = do
	branchref <- Annex.Branch.getBranch
	Tree t <- inRepo $ getTree branchref
	t' <- inRepo $ recordTree $ Tree $
		RecordedSubTree (asTopFilePath graftpoint) treeish [] : t
	commit <- inRepo $ Git.Branch.commitTree Git.Branch.AutomaticCommit
		"export tree" [branchref] t'
	origtree <- inRepo $ recordTree (Tree t)
	commit' <- inRepo $ Git.Branch.commitTree Git.Branch.AutomaticCommit
		"export tree cleanup" [commit] origtree
	inRepo $ Git.Branch.update' Annex.Branch.fullname commit'
  where
	graftpoint = "export.tree"