Support working trees set up by git-worktree.

Support working trees set up by git-worktree, by setting up some symlinks
such that git-annex links work right.

Also improved support for repositories created with --separate-git-dir.
At least recent git makes a .git file for those (older may have used a
symlink?), so that also needs to be converted to a symlink.

This commit was sponsored by Nick Piper on Patreon.
This commit is contained in:
Joey Hess 2018-07-18 14:25:03 -04:00
parent ac5680f6f5
commit 081f8e57c6
No known key found for this signature in database
GPG key ID: DB12DB0FF05F8F38
4 changed files with 81 additions and 18 deletions

View file

@ -1,6 +1,6 @@
{- git-annex repository fixups
-
- Copyright 2013, 2015 Joey Hess <id@joeyh.name>
- Copyright 2013-2018 Joey Hess <id@joeyh.name>
-
- Licensed under the GNU GPL version 3 or higher.
-}
@ -15,6 +15,8 @@ import Utility.Path
import Utility.SafeCommand
import Utility.Directory
import Utility.Exception
import Utility.Monad
import Utility.PartialPrelude
import System.IO
import System.FilePath
@ -27,7 +29,7 @@ import qualified Data.Map as M
fixupRepo :: Repo -> GitConfig -> IO Repo
fixupRepo r c = do
let r' = disableWildcardExpansion r
r'' <- fixupSubmodule r' c
r'' <- fixupUnusualRepos r' c
if annexDirect c
then return (fixupDirect r'')
else return r''
@ -56,43 +58,89 @@ fixupDirect r = r
{- Submodules have their gitdir containing ".git/modules/", and
- have core.worktree set, and also have a .git file in the top
- of the repo.
-
- We need to unset core.worktree, and change the .git file into a
- symlink to the git directory. This way, annex symlinks will be
- of the repo. We need to unset core.worktree, and change the .git
- file into a symlink to the git directory. This way, annex symlinks will be
- of the usual .git/annex/object form, and will consistently work
- whether a repo is used as a submodule or not, and wheverever the
- submodule is mounted.
-
- git-worktree directories have a .git file.
- That needs to be converted to a symlink, and .git/annex made a symlink
- to the main repository's git-annex directory.
- The worktree shares git config with the main repository, so the same
- annex uuid and other configuration will be used in the worktree as in
- the main repository.
-
- git clone or init with --separate-git-dir similarly makes a .git file,
- which in that case points to a different git directory. It's
- also converted to a symlink so links to .git/annex will work.
-
- When the filesystem doesn't support symlinks, we cannot make .git
- into a symlink. But we don't need too, since the repo will use direct
- mode, In this case, we merely adjust the Repo so that
- symlinks to objects that get checked in will be in the right form.
- mode.
-}
fixupSubmodule :: Repo -> GitConfig -> IO Repo
fixupSubmodule r@(Repo { location = l@(Local { worktree = Just w, gitdir = d }) }) c
fixupUnusualRepos :: Repo -> GitConfig -> IO Repo
fixupUnusualRepos r@(Repo { location = l@(Local { worktree = Just w, gitdir = d }) }) c
| needsSubmoduleFixup r = do
when (coreSymlinks c) $
replacedotgit
(replacedotgit >> unsetcoreworktree)
`catchNonAsync` \_e -> hPutStrLn stderr
"warning: unable to convert submodule to form that will work with git-annex"
return $ r
{ location = if coreSymlinks c
then l { gitdir = dotgit }
else l
, config = M.delete "core.worktree" (config r)
return $ r'
{ config = M.delete "core.worktree" (config r)
}
| otherwise = ifM (needsGitLinkFixup r)
( do
when (coreSymlinks c) $
(replacedotgit >> worktreefixup)
`catchNonAsync` \_e -> hPutStrLn stderr
"warning: unable to convert .git file to symlink that will work with git-annex"
return r'
, return r
)
where
dotgit = w </> ".git"
replacedotgit = whenM (doesFileExist dotgit) $ do
linktarget <- relPathDirToFile w d
nukeFile dotgit
createSymbolicLink linktarget dotgit
unsetcoreworktree =
maybe (error "unset core.worktree failed") (\_ -> return ())
=<< Git.Config.unset "core.worktree" r
fixupSubmodule r _ = return r
worktreefixup =
-- git-worktree sets up a "commondir" file that contains
-- the path to the main git directory.
-- Using --separate-git-dir does not.
catchDefaultIO Nothing (headMaybe . lines <$> readFile (d </> "commondir")) >>= \case
Just gd -> do
-- Make the worktree's git directory
-- contain an annex symlink to the main
-- repository's annex directory.
let linktarget = gd </> "annex"
createSymbolicLink linktarget (dotgit </> "annex")
Nothing -> return ()
-- Repo adjusted, so that symlinks to objects that get checked
-- in will have the usual path, rather than pointing off to the
-- real .git directory.
r'
| coreSymlinks c = r { location = l { gitdir = dotgit } }
| otherwise = r
fixupUnusualRepos r _ = return r
needsSubmoduleFixup :: Repo -> Bool
needsSubmoduleFixup (Repo { location = (Local { worktree = Just _, gitdir = d }) }) =
(".git" </> "modules") `isInfixOf` d
needsSubmoduleFixup _ = False
needsGitLinkFixup :: Repo -> IO Bool
needsGitLinkFixup (Repo { location = (Local { worktree = Just wt, gitdir = d }) })
-- Optimization: Avoid statting .git in the common case; only
-- when the gitdir is not in the usual place inside the worktree
-- might .git be a file.
| wt </> ".git" == d = return False
| otherwise = doesFileExist (wt </> ".git")
needsGitLinkFixup _ = return False

View file

@ -1,5 +1,7 @@
git-annex (6.20180627) UNRELEASED; urgency=medium
* Support working trees set up by git-worktree.
* Improve support for repositories created with --separate-git-dir.
* Include uname in standalone builds.
* Support configuring remote.web.annex-cost and remote.bittorrent.annex-cost
* info: Display uuid and description when a repository is identified by

View file

@ -207,10 +207,19 @@ checkForRepo dir =
( return $ Just $ LocalUnknown dir
, return Nothing
)
isRepo = checkdir $ gitSignature $ ".git" </> "config"
isRepo = checkdir $
gitSignature (".git" </> "config")
<||>
-- A git-worktree lacks .git/config, but has .git/commondir.
-- (Normally the .git is a file, not a symlink, but it can
-- be converted to a symlink and git will still work;
-- this handles that case.)
gitSignature (".git" </> "gitdir")
isBareRepo = checkdir $ gitSignature "config"
<&&> doesDirectoryExist (dir </> "objects")
gitDirFile = do
-- git-submodule, git-worktree, and --separate-git-dir
-- make .git be a file pointing to the real git directory.
c <- firstLine <$>
catchDefaultIO "" (readFile $ dir </> ".git")
return $ if gitdirprefix `isPrefixOf` c

View file

@ -39,6 +39,10 @@ to copy files between the two repos, using space only once. This
works, of course, only on filesystems that support hardlinks, but
that's usually the case for filesystems that support symlinks.
Alternatively, `git worktree` can be used to add another worktree to a git
repository. This way, multiple worktrees can share the same git-annex
object store.
Real world cases
----------------