summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Annex/Branch.hs114
-rw-r--r--Annex/CatFile.hs6
-rw-r--r--doc/bugs/git-annex_branch_push_race.mdwn2
3 files changed, 90 insertions, 32 deletions
diff --git a/Annex/Branch.hs b/Annex/Branch.hs
index 52e82e25c..0ac419994 100644
--- a/Annex/Branch.hs
+++ b/Annex/Branch.hs
@@ -60,20 +60,38 @@ 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 ()
+updateIndex :: Annex (Maybe Git.Ref)
updateIndex = do
- lock <- fromRepo gitAnnexIndexLock
- lockref <- firstRef <$> liftIO (catchDefaultIO (readFileStrict lock) "")
branchref <- getRef fullname
- when (lockref /= branchref) $ do
- withIndex $ mergeIndex [fullname]
- setIndexRef branchref
+ 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. -}
@@ -82,18 +100,52 @@ setIndexRef ref = do
lock <- fromRepo gitAnnexIndexLock
liftIO $ writeFile lock $ show ref ++ "\n"
-{- 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
+{- 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. -}
@@ -126,22 +178,20 @@ getCache file = getState >>= go
{- Creates the branch, if it does not already exist. -}
create :: Annex ()
-create = unlessM hasBranch $ hasOrigin >>= go >>= setIndexRef
+create = unlessM hasBranch $ hasOrigin >>= go
where
go True = do
inRepo $ Git.run "branch"
[Param $ show name, Param $ show originname]
- getRef fullname
- go False = withIndex' True $
- inRepo $ Git.commit "branch created" fullname []
+ 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
- updateIndex
stageJournalFiles
- withIndex $
- setIndexRef =<< inRepo (Git.commit message fullname [fullname])
+ withIndex $ commitBranch message [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.
@@ -162,7 +212,7 @@ update :: Annex ()
update = onceonly $ do
-- ensure branch exists, and index is up-to-date
create
- updateIndex
+ _ <- updateIndex
-- check what needs updating before taking the lock
dirty <- journalDirty
c <- filterM (changedBranch name . snd) =<< siblingBranches
@@ -178,9 +228,7 @@ update = onceonly $ do
showSideAction merge_desc
mergeIndex branches
ff <- if dirty then return False else tryFastForwardTo refs
- unless ff $
- setIndexRef =<<
- inRepo (Git.commit merge_desc fullname (nub $ fullname:refs))
+ unless ff $ commitBranch merge_desc (nub $ fullname:refs)
invalidateCache
where
onceonly a = unlessM (branchUpdated <$> getState) $ do
@@ -274,13 +322,15 @@ siblingBranches = do
uref (a, _) (b, _) = a == b
{- Get the ref of a branch. -}
-getRef :: Git.Ref -> Annex Git.Ref
-getRef branch = firstRef . L.unpack <$> showref
+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
- Param "--verify", -- only exact match
+ Params "--verify", -- only exact match
Param $ show branch]
+ process [] = Nothing
+ process s = Just $ firstRef s
firstRef :: String-> Git.Ref
firstRef = Git.Ref . takeWhile (/= '\n')
diff --git a/Annex/CatFile.hs b/Annex/CatFile.hs
index 1d996edfd..bcf44551e 100644
--- a/Annex/CatFile.hs
+++ b/Annex/CatFile.hs
@@ -7,6 +7,7 @@
module Annex.CatFile (
catFile,
+ catObject,
catFileHandle
) where
@@ -22,6 +23,11 @@ catFile branch file = do
h <- catFileHandle
liftIO $ Git.CatFile.catFile h branch file
+catObject :: Git.Ref -> Annex L.ByteString
+catObject ref = do
+ h <- catFileHandle
+ liftIO $ Git.CatFile.catObject h ref
+
catFileHandle :: Annex Git.CatFile.CatFileHandle
catFileHandle = maybe startup return =<< Annex.getState Annex.catfilehandle
where
diff --git a/doc/bugs/git-annex_branch_push_race.mdwn b/doc/bugs/git-annex_branch_push_race.mdwn
index 257c477bf..013ff70dd 100644
--- a/doc/bugs/git-annex_branch_push_race.mdwn
+++ b/doc/bugs/git-annex_branch_push_race.mdwn
@@ -40,4 +40,6 @@ redo them.
will need the same check for the race as the other commits. It won't loop
forever, I hope.)
+> [[done]] and tested.
+
--[[Joey]]