@@ -17,6 +17,7 @@ import qualified Data.Text as T
17import GHC.Generics
17import GHC.Generics
18import Data.Binary
18import Data.Binary
19import Debug.Trace
19import Debug.Trace
20+import Data.Either (lefts, rights)
21--operators which manipulate a transaction graph
22--operators which manipulate a transaction graph
22data TransactionGraphOperator = JumpToHead HeadName |
23data TransactionGraphOperator = JumpToHead HeadName |
@@ -52,7 +53,9 @@ headNameForTransaction transaction (TransactionGraph heads _) = if M.null matchi
52 matchingTrans = M.filter (transaction ==) heads
53 matchingTrans = M.filter (transaction ==) heads
54transactionForUUID :: U.UUID -> TransactionGraph -> Either RelationalError Transaction
55transactionForUUID :: U.UUID -> TransactionGraph -> Either RelationalError Transaction
55−transactionForUUID uuid graph = if S.null matchingTrans then
56+transactionForUUID uuid graph = if uuid == U.nil then
57+ Left RootTransactionTraversalError
58+ else if S.null matchingTrans then
56 Left $ NoSuchTransactionError uuid
59 Left $ NoSuchTransactionError uuid
58 Right $ head (S.toList matchingTrans)
61 Right $ head (S.toList matchingTrans)
@@ -83,23 +86,24 @@ childTransactions (Transaction _ (MergeTransactionInfo _ _ children) _) = transa
84-- create a new commit and add it to the heads
87-- create a new commit and add it to the heads
85-- technically, the new head could be added to an existing commit, but by adding a new commit, the new head is unambiguously linked to a new commit (with a context indentical to its parent)
88-- technically, the new head could be added to an existing commit, but by adding a new commit, the new head is unambiguously linked to a new commit (with a context indentical to its parent)
86−addBranch :: U.UUID -> HeadName -> Transaction -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
89+addBranch :: U.UUID -> HeadName -> U.UUID -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
90+addBranch newUUID newBranchName branchPointId graph = do
91+ parentTrans <- transactionForUUID branchPointId graph
87−addBranch newUUID newBranchName branchPoint graph = addTransactionToGraph newBranchName branchPoint newUUID (transactionContext branchPoint) graph
92+ addTransactionToGraph newBranchName branchPointId newUUID (transactionContext parentTrans) graph
89--adds a disconnected transaction to a transaction graph at some head
94--adds a disconnected transaction to a transaction graph at some head
90addDisconnectedTransaction :: U.UUID -> HeadName -> DisconnectedTransaction -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
95addDisconnectedTransaction :: U.UUID -> HeadName -> DisconnectedTransaction -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
91−addDisconnectedTransaction newUUID headName (DisconnectedTransaction parentUUID context) graph = do
96+addDisconnectedTransaction newUUID headName (DisconnectedTransaction parentUUID context) graph = addTransactionToGraph headName parentUUID newUUID context graph
92− parentTrans <- transactionForUUID parentUUID graph
93− addTransactionToGraph headName parentTrans newUUID context graph
95-- create a new transaction on "newHeadName" with the branchPointTrans
98-- create a new transaction on "newHeadName" with the branchPointTrans
96−addTransactionToGraph :: HeadName -> Transaction -> U.UUID -> DatabaseContext -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
99+addTransactionToGraph :: HeadName -> U.UUID -> U.UUID -> DatabaseContext -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
97−addTransactionToGraph newHeadName branchPointTrans newUUID newContext (TransactionGraph heads transSet) = do
100+addTransactionToGraph newHeadName branchPointTransId newUUID newContext graph@(TransactionGraph heads transSet) = do
101+ parentTransaction <- transactionForUUID branchPointTransId graph
98− let freshTransaction = Transaction newUUID (TransactionInfo (transactionUUID branchPointTrans) S.empty) newContext
102+ let freshTransaction = Transaction newUUID (TransactionInfo branchPointTransId S.empty) newContext
99 -- there are two parents for MergeTransactions! not implemented
103 -- there are two parents for MergeTransactions! not implemented
100 --update parentTransaction to add child
104 --update parentTransaction to add child
101− let parentTransaction = branchPointTrans
105+ let updatedParentTransaction = transactionSetChildren parentTransaction (S.insert (transactionUUID freshTransaction) (transactionChildren parentTransaction))
102− let updatedTransSet = (S.insert freshTransaction <$> S.insert parentTransaction <$> S.delete parentTransaction) transSet
106+ updatedTransSet = (S.insert freshTransaction <$> S.insert updatedParentTransaction) transSet
103 let updatedGraph = TransactionGraph (M.insert newHeadName freshTransaction heads) updatedTransSet
107 let updatedGraph = TransactionGraph (M.insert newHeadName freshTransaction heads) updatedTransSet
104 return (freshTransaction, updatedGraph)
108 return (freshTransaction, updatedGraph)
@@ -182,12 +186,10 @@ evalGraphOp newUUID discon@(DisconnectedTransaction parentUUID disconContext) gr
183-- create a new commit and add it to the heads
187-- create a new commit and add it to the heads
184-- technically, the new head could be added to an existing commit, but by adding a new commit, the new head is unambiguously linked to a new commit (with a context indentical to its parent)
188-- technically, the new head could be added to an existing commit, but by adding a new commit, the new head is unambiguously linked to a new commit (with a context indentical to its parent)
185−evalGraphOp newUUID (DisconnectedTransaction parentUUID disconContext) graph (Branch newBranchName) = case transactionForUUID parentUUID graph of
189+evalGraphOp newUUID (DisconnectedTransaction parentUUID disconContext) graph (Branch newBranchName) = case addBranch newUUID newBranchName parentUUID graph of
186 Left err -> Left err
190 Left err -> Left err
187− Right parentTrans -> case addBranch newUUID newBranchName parentTrans graph of
188− Left err -> Left err
189− Right (_, newGraph) -> Right (newDiscon, newGraph)
191+ Right (_, newGraph) -> Right (newDiscon, newGraph)
191 newDiscon = DisconnectedTransaction newUUID disconContext
193 newDiscon = DisconnectedTransaction newUUID disconContext
193-- add the disconnected transaction to the graph
195-- add the disconnected transaction to the graph
@@ -273,29 +275,55 @@ validateHeadName headName graph (t1, t2) = do
274 Right trans
276 Right trans
276−-- | Return the common ancestor of two transactions. When looking for the first common ancestor, we don't need to search both parents of a merge transaction because the merge must have diverged from a common ancestor anyway, so we arbitrarily just search one of the parents of a merge transaction. Technically, due to merge transactions, this may not actually return the first common ancestor, but it should be pretty close.
278+-- Algorithm: start at one transaction and work backwards up the parents. If there is a node we have not yet visited as a child, then walk that up to its head. If that branch contains the goal transaction, then we have completed a valid subgraph traversal.
277−subGraphToFirstCommonAncestor :: TransactionGraph -> TransactionHeads -> TransactionGraph -> Transaction -> Transaction -> Either RelationalError TransactionGraph
279+subGraphOfFirstCommonAncestor :: TransactionGraph -> TransactionHeads -> Transaction -> Transaction -> S.Set Transaction -> Either RelationalError TransactionGraph
278−subGraphToFirstCommonAncestor origGraph resultHeads subGraph trans1 trans2 = do
280+subGraphOfFirstCommonAncestor origGraph resultHeads currentTrans goalTrans traverseSet = do
279− let t1id = transactionUUID trans1
281+ let currentid = transactionUUID currentTrans
280− t2id = transactionUUID trans2
282+ goalid = transactionUUID goalTrans
281− validChildIds = traceShowId $ S.map transactionUUID (transactionsForGraph subGraph)
283+ if currentTrans == goalTrans then
282− filterTransSet transSet = S.map (\t -> filterChildTransactions t validChildIds) transSet
283− if t1id == t2id then
284− --also filter child transactions
285− Right (TransactionGraph resultHeads (S.union (transactionsForGraph subGraph) (filterTransSet (S.singleton trans1))))
284+ Right (TransactionGraph resultHeads traverseSet) -- add filter
286− else if U.null t1id || U.null t2id then --no common ancestor
285+ --catch root transaction to improve error?
287− Left (NoCommonTransactionAncestorError t1id t2id)
289− parentTrans1 <- oneParent trans1
287+ currentTransChildren <- liftM S.fromList $ mapM (flip transactionForUUID origGraph) (S.toList (transactionChildren currentTrans))
290− parentTrans2 <- oneParent trans2
288+ let searchChildren = S.difference (S.insert currentTrans traverseSet) currentTransChildren
291− --add transactions to graph
289+ searchChild start = pathToTransaction origGraph start goalTrans (S.insert currentTrans traverseSet)
290+ childSearches = map searchChild (S.toList searchChildren)
291+ errors = lefts childSearches
292+ pathsFound = rights childSearches
293+ if null childSearches || all (== (Left (FailedToFindTransactionError goalid))) childSearches then --no path found, so search the parent
294+ case oneParent currentTrans of
295+ Left RootTransactionTraversalError -> Left (NoCommonTransactionAncestorError currentid goalid)
296+ Left err -> Left err
297+ Right currentTransParent -> do
298+ subGraphOfFirstCommonAncestor origGraph resultHeads currentTransParent goalTrans (S.insert currentTrans traverseSet)
299+ else if length errors > 0 then -- traversal error
301+ else -- we found a path
292− let subGraph' = TransactionGraph resultHeads newTrans
302+ Right (TransactionGraph resultHeads (S.unions (traverseSet : pathsFound)))
293− newTrans = S.union (transactionsForGraph subGraph) (filterTransSet (S.fromList [trans1, trans2]))
294− subGraphToFirstCommonAncestor origGraph resultHeads subGraph' parentTrans1 parentTrans2
296 oneParent (Transaction _ (TransactionInfo parentId _) _) = transactionForUUID parentId origGraph
304 oneParent (Transaction _ (TransactionInfo parentId _) _) = transactionForUUID parentId origGraph
297 oneParent (Transaction _ (MergeTransactionInfo parentId _ _) _) = transactionForUUID parentId origGraph
305 oneParent (Transaction _ (MergeTransactionInfo parentId _ _) _) = transactionForUUID parentId origGraph
307+-- | Search from a past graph point to all following heads for a specific transaction. If found, return the transaction path, otherwise a RelationalError.
308+pathToTransaction :: TransactionGraph -> Transaction -> Transaction -> S.Set Transaction -> Either RelationalError (S.Set Transaction)
309+pathToTransaction graph currentTransaction targetTransaction accumTransSet = do
310+ let targetId = transactionUUID targetTransaction
311+ if targetTransaction == currentTransaction then
314+ currentTransChildren <- mapM (flip transactionForUUID graph) (S.toList (transactionChildren currentTransaction))
315+ if length currentTransChildren == 0 then
316+ Left (FailedToFindTransactionError targetId)
318+ let searches = map (\t -> pathToTransaction graph targetTransaction t (S.insert t accumTransSet)) currentTransChildren
319+ let realErrors = filter (/= FailedToFindTransactionError targetId) (lefts searches)
320+ paths = rights searches
321+ if length realErrors > 0 then -- found some real errors
322+ Left (head realErrors)
323+ else if length paths == 0 then -- failed to find transaction in all children
324+ Left (FailedToFindTransactionError targetId)
325+ else --we have some paths!
326+ Right (S.unions paths)
300mergeTransactions :: U.UUID -> U.UUID -> MergeStrategy -> (HeadName, HeadName) -> TransactionGraph -> Either RelationalError (DisconnectedTransaction, TransactionGraph)
328mergeTransactions :: U.UUID -> U.UUID -> MergeStrategy -> (HeadName, HeadName) -> TransactionGraph -> Either RelationalError (DisconnectedTransaction, TransactionGraph)
301mergeTransactions newUUID parentUUID mergeStrategy (headNameA, headNameB) graph = do
329mergeTransactions newUUID parentUUID mergeStrategy (headNameA, headNameB) graph = do
@@ -315,14 +343,15 @@ mergeTransactions newUUID parentUUID mergeStrategy (headNameA, headNameB) graph
316 disconParent <- transactionForUUID parentUUID graph
344 disconParent <- transactionForUUID parentUUID graph
317 let subHeads = M.filterWithKey (\k _ -> elem k [headNameA, headNameB]) (transactionHeadsForGraph graph)
345 let subHeads = M.filterWithKey (\k _ -> elem k [headNameA, headNameB]) (transactionHeadsForGraph graph)
318− subGraph <- subGraphToFirstCommonAncestor graph subHeads emptyTransactionGraph transA transB
346+ subGraph <- subGraphOfFirstCommonAncestor graph subHeads transA transB S.empty
319 case createMergeTransaction newUUID mergeStrategy subGraph (transA, transB) of
348 case createMergeTransaction newUUID mergeStrategy subGraph (transA, transB) of
320 Left err -> Left (MergeTransactionError err)
349 Left err -> Left (MergeTransactionError err)
321 Right mergedTrans -> case headNameForTransaction disconParent graph of
350 Right mergedTrans -> case headNameForTransaction disconParent graph of
322 Nothing -> Left (TransactionIsNotAHeadError parentUUID)
351 Nothing -> Left (TransactionIsNotAHeadError parentUUID)
323 Just headName -> do
352 Just headName -> do
324 -- why create a merge transaction and then only use the context?
353 -- why create a merge transaction and then only use the context?
325− (newTrans, newGraph) <- addTransactionToGraph headName disconParent newUUID (transactionContext mergedTrans) graph
354+ (newTrans, newGraph) <- addTransactionToGraph headName parentUUID newUUID (transactionContext mergedTrans) graph
326 let newGraph' = TransactionGraph newHeads (transactionsForGraph newGraph)
355 let newGraph' = TransactionGraph newHeads (transactionsForGraph newGraph)
327 newHeads = M.delete headNameToDelete (transactionHeadsForGraph newGraph)
356 newHeads = M.delete headNameToDelete (transactionHeadsForGraph newGraph)
328 newDiscon = newDisconnectedTransaction newUUID (transactionContext newTrans)
357 newDiscon = newDisconnectedTransaction newUUID (transactionContext newTrans)