diff --git a/Annex/Branch.hs b/Annex/Branch.hs index 717cbc0400..2fb9e030de 100644 --- a/Annex/Branch.hs +++ b/Annex/Branch.hs @@ -818,12 +818,18 @@ performTransitionsLocked jl ts neednewlocalbranch transitionedrefs = do if neednewlocalbranch then do cmode <- annexCommitMode <$> Annex.getGitConfig - committedref <- inRepo $ Git.Branch.commitAlways cmode message fullname transitionedrefs - setIndexSha committedref + -- Creating a new empty branch must happen + -- atomically, so if this is interrupted, + -- it will not leave the new branch created + -- but without exports grafted in. + c <- inRepo $ Git.Branch.commitShaAlways + cmode message transitionedrefs + void $ regraftexports c else do ref <- getBranch - commitIndex jl ref message (nub $ fullname:transitionedrefs) - regraftexports + ref' <- regraftexports ref + commitIndex jl ref' message + (nub $ fullname:transitionedrefs) where message | neednewlocalbranch && null transitionedrefs = "new branch for transition " ++ tdesc @@ -872,13 +878,21 @@ performTransitionsLocked jl ts neednewlocalbranch transitionedrefs = do apply rest file content' -- Trees mentioned in export.log were grafted into the old - -- git-annex branch to make sure they remain available. Re-graft - -- the trees into the new branch. - regraftexports = do + -- git-annex branch to make sure they remain available. + -- Re-graft the trees. + regraftexports parent = do l <- exportedTreeishes . M.elems . parseExportLogMap <$> getStaged exportLog - forM_ l $ \t -> - rememberTreeishLocked t (asTopFilePath exportTreeGraftPoint) jl + c <- regraft l parent + inRepo $ Git.Branch.update' fullname c + setIndexSha c + return c + where + regraft [] c = pure c + regraft (et:ets) c = + prepRememberTreeish et graftpoint c + >>= regraft ets + graftpoint = asTopFilePath exportTreeGraftPoint checkBranchDifferences :: Git.Ref -> Annex () checkBranchDifferences ref = do @@ -935,26 +949,29 @@ getMergedRefs' = do - Returns the sha of the git commit made to the git-annex branch. -} rememberTreeish :: Git.Ref -> TopFilePath -> Annex Git.Sha -rememberTreeish treeish graftpoint = lockJournal $ - rememberTreeishLocked treeish graftpoint -rememberTreeishLocked :: Git.Ref -> TopFilePath -> JournalLocked -> Annex Git.Sha -rememberTreeishLocked treeish graftpoint jl = do +rememberTreeish treeish graftpoint = lockJournal $ \jl -> do branchref <- getBranch updateIndex jl branchref + c <- prepRememberTreeish treeish graftpoint branchref + inRepo $ Git.Branch.update' fullname c + -- The tree in c is the same as the tree in branchref, + -- and the index was updated to that above, so it's safe to + -- say that the index contains c. + setIndexSha c + return c + +{- Create a series of commits that graft a tree onto the parent commit, + - and then remove it. -} +prepRememberTreeish :: Git.Ref -> TopFilePath -> Git.Ref -> Annex Git.Sha +prepRememberTreeish treeish graftpoint parent = do origtree <- fromMaybe (giveup "unable to determine git-annex branch tree") <$> - inRepo (Git.Ref.tree branchref) + inRepo (Git.Ref.tree parent) addedt <- inRepo $ Git.Tree.graftTree treeish graftpoint origtree cmode <- annexCommitMode <$> Annex.getGitConfig c <- inRepo $ Git.Branch.commitTree cmode - ["graft"] [branchref] addedt - c' <- inRepo $ Git.Branch.commitTree cmode + ["graft"] [parent] addedt + inRepo $ Git.Branch.commitTree cmode ["graft cleanup"] [c] origtree - inRepo $ Git.Branch.update' fullname c' - -- The tree in c' is the same as the tree in branchref, - -- and the index was updated to that above, so it's safe to - -- say that the index contains c'. - setIndexSha c' - return c' {- Runs an action on the content of selected files from the branch. - This is much faster than reading the content of each file in turn, diff --git a/CHANGELOG b/CHANGELOG index 693f55a8ab..e42f967b5b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ git-annex (10.20240532) UNRELEASED; urgency=medium + * Fix a bug where interrupting git-annex while it is updating the + git-annex branch for an export could later lead to git fsck + complaining about missing tree objects. * Fix Windows build with Win32 2.13.4+ Thanks, Oleg Tolmatcev diff --git a/Git/Branch.hs b/Git/Branch.hs index 8569f5d249..9d0ba56384 100644 --- a/Git/Branch.hs +++ b/Git/Branch.hs @@ -178,13 +178,25 @@ commitCommand' runner commitmode commitquiet ps = - in any way, or output a summary. -} commit :: CommitMode -> Bool -> String -> Branch -> [Ref] -> Repo -> IO (Maybe Sha) -commit commitmode allowempty message branch parentrefs repo = do - tree <- writeTree repo - ifM (cancommit tree) - ( do - sha <- commitTree commitmode [message] parentrefs tree repo +commit commitmode allowempty message branch parentrefs repo = + commitSha commitmode allowempty message parentrefs repo >>= \case + Just sha -> do update' branch sha repo return $ Just sha + Nothing -> return Nothing + where + cancommit tree + | allowempty = return True + | otherwise = case parentrefs of + [p] -> maybe False (tree /=) <$> Git.Ref.tree p repo + _ -> return True + +{- Same as commit but without updating any branch. -} +commitSha :: CommitMode -> Bool -> String -> [Ref] -> Repo -> IO (Maybe Sha) +commitSha commitmode allowempty message parentrefs repo = do + tree <- writeTree repo + ifM (cancommit tree) + ( Just <$> commitTree commitmode [message] parentrefs tree repo , return Nothing ) where @@ -198,6 +210,10 @@ commitAlways :: CommitMode -> String -> Branch -> [Ref] -> Repo -> IO Sha commitAlways commitmode message branch parentrefs repo = fromJust <$> commit commitmode True message branch parentrefs repo +commitShaAlways :: CommitMode -> String -> [Ref] -> Repo -> IO Sha +commitShaAlways commitmode message parentrefs repo = fromJust + <$> commitSha commitmode True message parentrefs repo + -- Throws exception if the index is locked, with an error message output by -- git on stderr. writeTree :: Repo -> IO Sha diff --git a/doc/bugs/annex_merge__breaks_git_repository__33__/comment_8_678151d78d145da6d249184ac212f935._comment b/doc/bugs/annex_merge__breaks_git_repository__33__/comment_8_678151d78d145da6d249184ac212f935._comment new file mode 100644 index 0000000000..d815f62ca9 --- /dev/null +++ b/doc/bugs/annex_merge__breaks_git_repository__33__/comment_8_678151d78d145da6d249184ac212f935._comment @@ -0,0 +1,15 @@ +[[!comment format=mdwn + username="joey" + subject="""comment 8""" + date="2024-06-07T17:59:43Z" + content=""" +Fixed performTransitionsLocked to create the new git-annex branch +atomically. + +Found another way this could happen, interrupting `git-annex export` after +it writes export.log but before it grafts the tree into the git-annex +branch. Fixed that one too. + +So hopefully this won't happen to any more repositories with these fixes. +Still leaves the question of how to recover from the problem. +"""]]