assistant: Deal with upcoming git's refusal to merge unrelated histories by default
git 2.8.1 (or perhaps 2.9.0) is going to prevent git merge from merging in unrelated branches. Since the webapp's pairing etc features often combine together repositories with unrelated histories, work around this behavior change by setting GIT_MERGE_ALLOW_UNRELATED_HISTORIES when the assistant merges. Note though that this is not done for git annex sync's merges, so it will follow git's default or configured behavior.
This commit is contained in:
parent
8ab27235ea
commit
46e3319995
9 changed files with 90 additions and 46 deletions
|
@ -260,8 +260,8 @@ adjustedBranchCommitMessage = "git-annex adjusted branch"
|
|||
{- Update the currently checked out adjusted branch, merging the provided
|
||||
- branch into it. Note that the provided branch should be a non-adjusted
|
||||
- branch. -}
|
||||
updateAdjustedBranch :: Branch -> (OrigBranch, Adjustment) -> Git.Branch.CommitMode -> Annex Bool
|
||||
updateAdjustedBranch tomerge (origbranch, adj) commitmode = catchBoolIO $
|
||||
updateAdjustedBranch :: Branch -> (OrigBranch, Adjustment) -> [Git.Merge.MergeConfig] -> Git.Branch.CommitMode -> Annex Bool
|
||||
updateAdjustedBranch tomerge (origbranch, adj) mergeconfig commitmode = catchBoolIO $
|
||||
join $ preventCommits go
|
||||
where
|
||||
adjbranch@(AdjBranch currbranch) = originalToAdjusted origbranch adj
|
||||
|
@ -304,7 +304,7 @@ updateAdjustedBranch tomerge (origbranch, adj) commitmode = catchBoolIO $
|
|||
showAction $ "Merging into " ++ fromRef (Git.Ref.base origbranch)
|
||||
-- The --no-ff is important; it makes git
|
||||
-- merge not care that the work tree is empty.
|
||||
merged <- inRepo (Git.Merge.mergeNonInteractive' [Param "--no-ff"] tomerge commitmode)
|
||||
merged <- inRepo (Git.Merge.merge' [Param "--no-ff"] tomerge mergeconfig commitmode)
|
||||
<||> (resolveMerge (Just updatedorig) tomerge True <&&> commitResolvedMerge commitmode)
|
||||
if merged
|
||||
then do
|
||||
|
@ -340,7 +340,7 @@ updateAdjustedBranch tomerge (origbranch, adj) commitmode = catchBoolIO $
|
|||
-- this commit will be a fast-forward.
|
||||
adjmergecommitff <- commitAdjustedTree' adjtree (BasisBranch mergecommit) [currbranch]
|
||||
showAction "Merging into adjusted branch"
|
||||
ifM (autoMergeFrom adjmergecommitff (Just currbranch) commitmode)
|
||||
ifM (autoMergeFrom adjmergecommitff (Just currbranch) mergeconfig commitmode)
|
||||
( reparent adjtree adjmergecommit =<< getcurrentcommit
|
||||
, return False
|
||||
)
|
||||
|
|
|
@ -43,16 +43,16 @@ import qualified Data.ByteString.Lazy as L
|
|||
- Callers should use Git.Branch.changed first, to make sure that
|
||||
- there are changes from the current branch to the branch being merged in.
|
||||
-}
|
||||
autoMergeFrom :: Git.Ref -> Maybe Git.Ref -> Git.Branch.CommitMode -> Annex Bool
|
||||
autoMergeFrom branch currbranch commitmode = do
|
||||
autoMergeFrom :: Git.Ref -> Maybe Git.Ref -> [Git.Merge.MergeConfig] -> Git.Branch.CommitMode -> Annex Bool
|
||||
autoMergeFrom branch currbranch mergeconfig commitmode = do
|
||||
showOutput
|
||||
case currbranch of
|
||||
Nothing -> go Nothing
|
||||
Just b -> go =<< inRepo (Git.Ref.sha b)
|
||||
where
|
||||
go old = ifM isDirect
|
||||
( mergeDirect currbranch old branch (resolveMerge old branch False) commitmode
|
||||
, inRepo (Git.Merge.mergeNonInteractive branch commitmode)
|
||||
( mergeDirect currbranch old branch (resolveMerge old branch False) mergeconfig commitmode
|
||||
, inRepo (Git.Merge.merge branch mergeconfig commitmode)
|
||||
<||> (resolveMerge old branch False <&&> commitResolvedMerge commitmode)
|
||||
)
|
||||
|
||||
|
|
|
@ -162,8 +162,8 @@ addDirect file cache = do
|
|||
- file. This is the same as what git does when updating the index
|
||||
- normally.
|
||||
-}
|
||||
mergeDirect :: Maybe Git.Ref -> Maybe Git.Ref -> Git.Branch -> Annex Bool -> Git.Branch.CommitMode -> Annex Bool
|
||||
mergeDirect startbranch oldref branch resolvemerge commitmode = exclusively $ do
|
||||
mergeDirect :: Maybe Git.Ref -> Maybe Git.Ref -> Git.Branch -> Annex Bool -> [Git.Merge.MergeConfig] -> Git.Branch.CommitMode -> Annex Bool
|
||||
mergeDirect startbranch oldref branch resolvemerge mergeconfig commitmode = exclusively $ do
|
||||
reali <- liftIO . absPath =<< fromRepo indexFile
|
||||
tmpi <- liftIO . absPath =<< fromRepo indexFileLock
|
||||
liftIO $ whenM (doesFileExist reali) $
|
||||
|
@ -176,7 +176,7 @@ mergeDirect startbranch oldref branch resolvemerge commitmode = exclusively $ do
|
|||
createDirectoryIfMissing True d
|
||||
|
||||
withIndexFile tmpi $ do
|
||||
merged <- stageMerge d branch commitmode
|
||||
merged <- stageMerge d branch mergeconfig commitmode
|
||||
ok <- if merged
|
||||
then return True
|
||||
else resolvemerge
|
||||
|
@ -195,19 +195,18 @@ mergeDirect startbranch oldref branch resolvemerge commitmode = exclusively $ do
|
|||
|
||||
{- Stage a merge into the index, avoiding changing HEAD or the current
|
||||
- branch. -}
|
||||
stageMerge :: FilePath -> Git.Branch -> Git.Branch.CommitMode -> Annex Bool
|
||||
stageMerge d branch commitmode = do
|
||||
stageMerge :: FilePath -> Git.Branch -> [Git.Merge.MergeConfig] -> Git.Branch.CommitMode -> Annex Bool
|
||||
stageMerge d branch mergeconfig commitmode = do
|
||||
-- XXX A bug in git makes stageMerge unsafe to use if the git repo
|
||||
-- is configured with core.symlinks=false
|
||||
-- Using mergeNonInteractive is not ideal though, since it will
|
||||
-- Using merge is not ideal though, since it will
|
||||
-- update the current branch immediately, before the work tree
|
||||
-- has been updated, which would leave things in an inconsistent
|
||||
-- state if mergeDirectCleanup is interrupted.
|
||||
-- <http://marc.info/?l=git&m=140262402204212&w=2>
|
||||
liftIO $ print ("stagemerge in", d)
|
||||
merger <- ifM (coreSymlinks <$> Annex.getGitConfig)
|
||||
( return Git.Merge.stageMerge
|
||||
, return $ \ref -> Git.Merge.mergeNonInteractive ref commitmode
|
||||
( return $ \ref -> Git.Merge.stageMerge ref mergeconfig
|
||||
, return $ \ref -> Git.Merge.merge ref mergeconfig commitmode
|
||||
)
|
||||
inRepo $ \g -> do
|
||||
wd <- liftIO $ absPath d
|
||||
|
|
|
@ -21,6 +21,7 @@ import Utility.Parallel
|
|||
import qualified Git
|
||||
import qualified Git.Command
|
||||
import qualified Git.Ref
|
||||
import qualified Git.Merge
|
||||
import qualified Remote
|
||||
import qualified Types.Remote as Remote
|
||||
import qualified Remote.List as Remote
|
||||
|
@ -238,12 +239,19 @@ manualPull currentbranch remotes = do
|
|||
)
|
||||
haddiverged <- liftAnnex Annex.Branch.forceUpdate
|
||||
forM_ normalremotes $ \r ->
|
||||
liftAnnex $ Command.Sync.mergeRemote r currentbranch
|
||||
liftAnnex $ Command.Sync.mergeRemote r currentbranch mergeConfig
|
||||
u <- liftAnnex getUUID
|
||||
forM_ xmppremotes $ \r ->
|
||||
sendNetMessage $ Pushing (getXMPPClientID r) (PushRequest u)
|
||||
return (catMaybes failed, haddiverged)
|
||||
|
||||
mergeConfig :: [Git.Merge.MergeConfig]
|
||||
mergeConfig =
|
||||
[ Git.Merge.MergeNonInteractive
|
||||
-- Pairing involves merging unrelated histories
|
||||
, Git.Merge.MergeUnrelatedHistories
|
||||
]
|
||||
|
||||
{- Start syncing a remote, using a background thread. -}
|
||||
syncRemote :: Remote -> Assistant ()
|
||||
syncRemote remote = do
|
||||
|
|
|
@ -12,6 +12,7 @@ import Assistant.TransferQueue
|
|||
import Assistant.BranchChange
|
||||
import Assistant.DaemonStatus
|
||||
import Assistant.ScanRemotes
|
||||
import Assistant.Sync
|
||||
import Utility.DirWatcher
|
||||
import Utility.DirWatcher.Types
|
||||
import qualified Annex.Branch
|
||||
|
@ -85,7 +86,8 @@ onChange file
|
|||
, "into", Git.fromRef b
|
||||
]
|
||||
void $ liftAnnex $ Command.Sync.merge
|
||||
currbranch Git.Branch.AutomaticCommit
|
||||
currbranch mergeConfig
|
||||
Git.Branch.AutomaticCommit
|
||||
changedbranch
|
||||
mergecurrent _ = noop
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ import Annex.Hook
|
|||
import qualified Git.Command
|
||||
import qualified Git.LsFiles as LsFiles
|
||||
import qualified Git.Branch
|
||||
import qualified Git.Merge
|
||||
import qualified Git.Types as Git
|
||||
import qualified Git.Ref
|
||||
import qualified Git
|
||||
|
@ -112,7 +113,7 @@ seek o = allowConcurrentOutput $ do
|
|||
mapM_ includeCommandAction $ concat
|
||||
[ [ commit o ]
|
||||
, [ withbranch mergeLocal ]
|
||||
, map (withbranch . pullRemote o) gitremotes
|
||||
, map (withbranch . pullRemote o mergeconfig) gitremotes
|
||||
, [ mergeAnnex ]
|
||||
]
|
||||
when (contentOption o) $
|
||||
|
@ -123,13 +124,15 @@ seek o = allowConcurrentOutput $ do
|
|||
-- and merge again to avoid our push overwriting
|
||||
-- those changes.
|
||||
mapM_ includeCommandAction $ concat
|
||||
[ map (withbranch . pullRemote o) gitremotes
|
||||
[ map (withbranch . pullRemote o mergeconfig) gitremotes
|
||||
, [ commitAnnex, mergeAnnex ]
|
||||
]
|
||||
|
||||
void $ includeCommandAction $ withbranch pushLocal
|
||||
-- Pushes to remotes can run concurrently.
|
||||
mapM_ (commandAction . withbranch . pushRemote o) gitremotes
|
||||
where
|
||||
mergeconfig = [Git.Merge.MergeNonInteractive]
|
||||
|
||||
type CurrBranch = (Maybe Git.Branch, Maybe Adjustment)
|
||||
|
||||
|
@ -166,11 +169,11 @@ getCurrBranch = do
|
|||
prepMerge :: Annex ()
|
||||
prepMerge = Annex.changeDirectory =<< fromRepo Git.repoPath
|
||||
|
||||
merge :: CurrBranch -> Git.Branch.CommitMode -> Git.Branch -> Annex Bool
|
||||
merge (Just b, Just adj) commitmode tomerge =
|
||||
updateAdjustedBranch tomerge (b, adj) commitmode
|
||||
merge (b, _) commitmode tomerge =
|
||||
autoMergeFrom tomerge b commitmode
|
||||
merge :: CurrBranch -> [Git.Merge.MergeConfig] -> Git.Branch.CommitMode -> Git.Branch -> Annex Bool
|
||||
merge (Just b, Just adj) mergeconfig commitmode tomerge =
|
||||
updateAdjustedBranch tomerge (b, adj) mergeconfig commitmode
|
||||
merge (b, _) mergeconfig commitmode tomerge =
|
||||
autoMergeFrom tomerge b mergeconfig commitmode
|
||||
|
||||
syncBranch :: Git.Branch -> Git.Branch
|
||||
syncBranch = Git.Ref.under "refs/heads/synced" . fromDirectBranch . fromAdjustedBranch
|
||||
|
@ -257,7 +260,7 @@ mergeLocal currbranch@(Just branch, madj) = go =<< needmerge
|
|||
go False = stop
|
||||
go True = do
|
||||
showStart "merge" $ Git.Ref.describe syncbranch
|
||||
next $ next $ merge currbranch Git.Branch.ManualCommit syncbranch
|
||||
next $ next $ merge currbranch [Git.Merge.MergeNonInteractive] Git.Branch.ManualCommit syncbranch
|
||||
branch' = maybe branch (adjBranch . originalToAdjusted branch) madj
|
||||
mergeLocal (Nothing, _) = stop
|
||||
|
||||
|
@ -291,13 +294,13 @@ updateBranch syncbranch updateto g =
|
|||
, Param $ Git.fromRef $ updateto
|
||||
] g
|
||||
|
||||
pullRemote :: SyncOptions -> Remote -> CurrBranch -> CommandStart
|
||||
pullRemote o remote branch = stopUnless (pure $ pullOption o) $ do
|
||||
pullRemote :: SyncOptions -> [Git.Merge.MergeConfig] -> Remote -> CurrBranch -> CommandStart
|
||||
pullRemote o mergeconfig remote branch = stopUnless (pure $ pullOption o) $ do
|
||||
showStart "pull" (Remote.name remote)
|
||||
next $ do
|
||||
showOutput
|
||||
stopUnless fetch $
|
||||
next $ mergeRemote remote branch
|
||||
next $ mergeRemote remote branch mergeconfig
|
||||
where
|
||||
fetch = inRepoWithSshOptionsTo (Remote.repo remote) (Remote.gitconfig remote) $
|
||||
Git.Command.runBool
|
||||
|
@ -308,8 +311,8 @@ pullRemote o remote branch = stopUnless (pure $ pullOption o) $ do
|
|||
- were committed (or pushed changes, if this is a bare remote),
|
||||
- while the synced/master may have changes that some
|
||||
- other remote synced to this remote. So, merge them both. -}
|
||||
mergeRemote :: Remote -> CurrBranch -> CommandCleanup
|
||||
mergeRemote remote currbranch = ifM isBareRepo
|
||||
mergeRemote :: Remote -> CurrBranch -> [Git.Merge.MergeConfig] -> CommandCleanup
|
||||
mergeRemote remote currbranch mergeconfig = ifM isBareRepo
|
||||
( return True
|
||||
, case currbranch of
|
||||
(Nothing, _) -> do
|
||||
|
@ -321,7 +324,7 @@ mergeRemote remote currbranch = ifM isBareRepo
|
|||
)
|
||||
where
|
||||
mergelisted getlist = and <$>
|
||||
(mapM (merge currbranch Git.Branch.ManualCommit . remoteBranch remote) =<< getlist)
|
||||
(mapM (merge currbranch mergeconfig Git.Branch.ManualCommit . remoteBranch remote) =<< getlist)
|
||||
tomerge = filterM (changed remote)
|
||||
branchlist Nothing = []
|
||||
branchlist (Just branch) = [branch, syncBranch branch]
|
||||
|
|
47
Git/Merge.hs
47
Git/Merge.hs
|
@ -1,36 +1,51 @@
|
|||
{- git merging
|
||||
-
|
||||
- Copyright 2012, 2014 Joey Hess <id@joeyh.name>
|
||||
- Copyright 2012-2016 Joey Hess <id@joeyh.name>
|
||||
-
|
||||
- Licensed under the GNU GPL version 3 or higher.
|
||||
-}
|
||||
|
||||
module Git.Merge where
|
||||
module Git.Merge (
|
||||
MergeConfig(..),
|
||||
CommitMode(..),
|
||||
merge,
|
||||
merge',
|
||||
stageMerge,
|
||||
) where
|
||||
|
||||
import Common
|
||||
import Git
|
||||
import Git.Command
|
||||
import Git.BuildVersion
|
||||
import Git.Branch (CommitMode(..))
|
||||
import Git.Env
|
||||
|
||||
{- Avoids recent git's interactive merge. -}
|
||||
mergeNonInteractive :: Ref -> CommitMode -> Repo -> IO Bool
|
||||
mergeNonInteractive = mergeNonInteractive' []
|
||||
data MergeConfig
|
||||
= MergeNonInteractive
|
||||
-- ^ avoids recent git's interactive merge
|
||||
| MergeUnrelatedHistories
|
||||
-- ^ avoids recent git's prevention of merging unrelated histories
|
||||
deriving (Eq)
|
||||
|
||||
mergeNonInteractive' :: [CommandParam] -> Ref -> CommitMode -> Repo -> IO Bool
|
||||
mergeNonInteractive' extraparams branch commitmode
|
||||
| older "1.7.7.6" = merge [Param $ fromRef branch]
|
||||
| otherwise = merge $ [Param "--no-edit", Param $ fromRef branch]
|
||||
merge :: Ref -> [MergeConfig] -> CommitMode -> Repo -> IO Bool
|
||||
merge = merge' []
|
||||
|
||||
merge' :: [CommandParam] -> Ref -> [MergeConfig] -> CommitMode -> Repo -> IO Bool
|
||||
merge' extraparams branch mergeconfig commitmode r
|
||||
| MergeNonInteractive `notElem` mergeconfig || older "1.7.7.6" =
|
||||
go [Param $ fromRef branch]
|
||||
| otherwise = go [Param "--no-edit", Param $ fromRef branch]
|
||||
where
|
||||
merge ps = runBool $ sp ++ [Param "merge"] ++ ps ++ extraparams
|
||||
go ps = runBool (sp ++ [Param "merge"] ++ ps ++ extraparams)
|
||||
=<< cfgRepo mergeconfig r
|
||||
sp
|
||||
| commitmode == AutomaticCommit =
|
||||
[Param "-c", Param "commit.gpgsign=false"]
|
||||
| otherwise = []
|
||||
|
||||
{- Stage the merge into the index, but do not commit it.-}
|
||||
stageMerge :: Ref -> Repo -> IO Bool
|
||||
stageMerge branch = runBool
|
||||
stageMerge :: Ref -> [MergeConfig] -> Repo -> IO Bool
|
||||
stageMerge branch mergeconfig r = runBool
|
||||
[ Param "merge"
|
||||
, Param "--quiet"
|
||||
, Param "--no-commit"
|
||||
|
@ -38,4 +53,10 @@ stageMerge branch = runBool
|
|||
-- commit.
|
||||
, Param "--no-ff"
|
||||
, Param $ fromRef branch
|
||||
]
|
||||
] =<< cfgRepo mergeconfig r
|
||||
|
||||
cfgRepo :: [MergeConfig] -> Repo -> IO Repo
|
||||
cfgRepo mergeconfig r
|
||||
| MergeUnrelatedHistories `elem` mergeconfig =
|
||||
addGitEnv r "GIT_MERGE_ALLOW_UNRELATED_HISTORIES" "1"
|
||||
| otherwise = return r
|
||||
|
|
7
debian/changelog
vendored
7
debian/changelog
vendored
|
@ -16,6 +16,13 @@ git-annex (6.20160419) UNRELEASED; urgency=medium
|
|||
* When git-annex is used with a git version older than 2.2.0, disable
|
||||
support for adjusted branches, since GIT_COMMON_DIR is needed to update
|
||||
them and was first added in that version of git.
|
||||
* git 2.8.1 (or perhaps 2.9.0) is going to prevent git merge from
|
||||
merging in unrelated branches. Since the webapp's pairing etc features
|
||||
often combine together repositories with unrelated histories, work around
|
||||
this behavior change by setting GIT_MERGE_ALLOW_UNRELATED_HISTORIES
|
||||
when the assistant merges. Note though that this is not done for
|
||||
git annex sync's merges, so it will follow git's default or configured
|
||||
behavior.
|
||||
|
||||
-- Joey Hess <id@joeyh.name> Tue, 19 Apr 2016 12:57:15 -0400
|
||||
|
||||
|
|
|
@ -13,3 +13,7 @@ be split into a fetch and a merge in order to pass the option to the merge;
|
|||
but AFAICS, git-annex never uses `git pull`)
|
||||
|
||||
--[[Joey]]
|
||||
|
||||
> [[done]]; used the environment variable
|
||||
> `GIT_MERGE_ALLOW_UNRELATED_HISTORIES` which will hopefully land in git
|
||||
> `next` (currently in `pu`) --[[Joey]]
|
||||
|
|
Loading…
Reference in a new issue