aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Annex.hs7
-rw-r--r--Annex/Direct.hs20
-rw-r--r--Annex/Direct/Fixup.hs31
-rw-r--r--Annex/Fixup.hs86
-rw-r--r--Config.hs8
-rw-r--r--Git/Config.hs15
-rw-r--r--debian/changelog1
-rw-r--r--doc/bugs/Git_annexed_files_symlink_are_wrong_when_submodule_is_not_in_the_same_path.mdwn6
-rw-r--r--doc/links/the_details.mdwn1
-rw-r--r--doc/submodules.mdwn21
10 files changed, 155 insertions, 41 deletions
diff --git a/Annex.hs b/Annex.hs
index f85c7e0f2..e1a32faca 100644
--- a/Annex.hs
+++ b/Annex.hs
@@ -37,7 +37,7 @@ module Annex (
import Common
import qualified Git
import qualified Git.Config
-import Annex.Direct.Fixup
+import Annex.Fixup
import Git.CatFile
import Git.CheckAttr
import Git.CheckIgnore
@@ -183,12 +183,13 @@ newState c r = AnnexState
}
{- Makes an Annex state object for the specified git repo.
- - Ensures the config is read, if it was not already. -}
+ - Ensures the config is read, if it was not already, and performs
+ - any necessary git repo fixups. -}
new :: Git.Repo -> IO AnnexState
new r = do
r' <- Git.Config.read =<< Git.relPath r
let c = extractGitConfig r'
- newState c <$> if annexDirect c then fixupDirect r' else return r'
+ newState c <$> fixupRepo r' c
{- Performs an action in the Annex monad from a starting state,
- returning a new state. -}
diff --git a/Annex/Direct.hs b/Annex/Direct.hs
index 1c733cb55..bb470e04c 100644
--- a/Annex/Direct.hs
+++ b/Annex/Direct.hs
@@ -406,7 +406,25 @@ setDirect wantdirect = do
Annex.changeGitConfig $ \c -> c { annexDirect = wantdirect }
where
val = Git.Config.boolConfig wantdirect
- setbare = setConfig (ConfigKey Git.Config.coreBare) val
+ coreworktree = ConfigKey "core.worktree"
+ indirectworktree = ConfigKey "core.indirect-worktree"
+ setbare = do
+ -- core.worktree is not compatable with
+ -- core.bare; git does not allow both to be set, so
+ -- unset it when enabling direct mode, caching in
+ -- core.indirect-worktree
+ if wantdirect
+ then moveconfig coreworktree indirectworktree
+ else moveconfig indirectworktree coreworktree
+ setConfig (ConfigKey Git.Config.coreBare) val
+ moveconfig src dest = do
+ v <- getConfigMaybe src
+ case v of
+ Nothing -> noop
+ Just wt -> do
+ unsetConfig src
+ setConfig dest wt
+ reloadConfig
{- Since direct mode sets core.bare=true, incoming pushes could change
- the currently checked out branch. To avoid this problem, HEAD
diff --git a/Annex/Direct/Fixup.hs b/Annex/Direct/Fixup.hs
deleted file mode 100644
index 793f92eaf..000000000
--- a/Annex/Direct/Fixup.hs
+++ /dev/null
@@ -1,31 +0,0 @@
-{- git-annex direct mode guard fixup
- -
- - Copyright 2013 Joey Hess <id@joeyh.name>
- -
- - Licensed under the GNU GPL version 3 or higher.
- -}
-
-module Annex.Direct.Fixup where
-
-import Git.Types
-import Git.Config
-import qualified Git.Construct as Construct
-import Utility.Path
-import Utility.SafeCommand
-
-{- Direct mode repos have core.bare=true, but are not really bare.
- - Fix up the Repo to be a non-bare repo, and arrange for git commands
- - run by git-annex to be passed parameters that override this setting. -}
-fixupDirect :: Repo -> IO Repo
-fixupDirect r@(Repo { location = l@(Local { gitdir = d, worktree = Nothing }) }) = do
- let r' = r
- { location = l { worktree = Just (parentDir d) }
- , gitGlobalOpts = gitGlobalOpts r ++
- [ Param "-c"
- , Param $ coreBare ++ "=" ++ boolConfig False
- ]
- }
- -- Recalc now that the worktree is correct.
- rs' <- Construct.fromRemotes r'
- return $ r' { remotes = rs' }
-fixupDirect r = return r
diff --git a/Annex/Fixup.hs b/Annex/Fixup.hs
new file mode 100644
index 000000000..cda36461c
--- /dev/null
+++ b/Annex/Fixup.hs
@@ -0,0 +1,86 @@
+{- git-annex repository fixups
+ -
+ - Copyright 2013, 2015 Joey Hess <id@joeyh.name>
+ -
+ - Licensed under the GNU GPL version 3 or higher.
+ -}
+
+module Annex.Fixup where
+
+import Git.Types
+import Git.Config
+import Types.GitConfig
+import qualified Git.Construct as Construct
+import Utility.Path
+import Utility.SafeCommand
+import Utility.Directory
+import Utility.PosixFiles
+import Utility.Exception
+
+import System.IO
+import System.FilePath
+import System.Directory
+import Data.List
+import Control.Monad
+import Control.Monad.IfElse
+import qualified Data.Map as M
+
+fixupRepo :: Repo -> GitConfig -> IO Repo
+fixupRepo r c = do
+ r' <- fixupSubmodule r c
+ if annexDirect c
+ then fixupDirect r'
+ else return r'
+
+{- Direct mode repos have core.bare=true, but are not really bare.
+ - Fix up the Repo to be a non-bare repo, and arrange for git commands
+ - run by git-annex to be passed parameters that override this setting. -}
+fixupDirect :: Repo -> IO Repo
+fixupDirect r@(Repo { location = l@(Local { gitdir = d, worktree = Nothing }) }) = do
+ let r' = r
+ { location = l { worktree = Just (parentDir d) }
+ , gitGlobalOpts = gitGlobalOpts r ++
+ [ Param "-c"
+ , Param $ coreBare ++ "=" ++ boolConfig False
+ ]
+ }
+ -- Recalc now that the worktree is correct.
+ rs' <- Construct.fromRemotes r'
+ return $ r' { remotes = rs' }
+fixupDirect r = return 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 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.
+ -
+ - When the filesystem doesn't support symlinks, we cannot make .git
+ - into a symlink. In this case, we merely adjust the Repo so that
+ - symlinks to objects that get checked in will be in the right form.
+ -}
+fixupSubmodule :: Repo -> GitConfig -> IO Repo
+fixupSubmodule r@(Repo { location = l@(Local { worktree = Just w, gitdir = d }) }) c
+ | (".git" </> "modules") `isInfixOf` d = do
+ when (coreSymlinks c) $
+ replacedotgit
+ `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)
+ }
+ where
+ dotgit = w </> ".git"
+ replacedotgit = whenM (doesFileExist dotgit) $ do
+ nukeFile dotgit
+ createSymbolicLink (w </> d) dotgit
+ maybe (error "unset core.worktree failed") (\_ -> return ())
+ =<< Git.Config.unset "core.worktree" r
+fixupSubmodule r _ = return r
diff --git a/Config.hs b/Config.hs
index 29135ed96..d9ad80eed 100644
--- a/Config.hs
+++ b/Config.hs
@@ -37,13 +37,9 @@ setConfig (ConfigKey key) value = do
reloadConfig :: Annex ()
reloadConfig = Annex.changeGitRepo =<< inRepo Git.Config.reRead
-{- Unsets a git config setting. (Leaves it in state currently.) -}
+{- Unsets a git config setting. (Leaves it in state.) -}
unsetConfig :: ConfigKey -> Annex ()
-unsetConfig ck@(ConfigKey key) = ifM (isJust <$> getConfigMaybe ck)
- ( inRepo $ Git.Command.run
- [Param "config", Param "--unset", Param key]
- , noop -- avoid unsetting something not set; that would fail
- )
+unsetConfig (ConfigKey key) = void $ inRepo $ Git.Config.unset key
{- A per-remote config setting in git config. -}
remoteConfig :: Git.Repo -> UnqualifiedConfigKey -> ConfigKey
diff --git a/Git/Config.hs b/Git/Config.hs
index 44e0ad9a9..3d6239560 100644
--- a/Git/Config.hs
+++ b/Git/Config.hs
@@ -14,6 +14,7 @@ import Common
import Git
import Git.Types
import qualified Git.Construct
+import qualified Git.Command
import Utility.UserInfo
{- Returns a single git config setting, or a default value if not set. -}
@@ -193,3 +194,17 @@ changeFile f k v = boolSystem "git"
, Param k
, Param v
]
+
+{- Unsets a git config setting, in both the git repo,
+ - and the cached config in the Repo.
+ -
+ - If unsetting the config fails, including in a read-only repo, or
+ - when the config is not set, returns Nothing.
+ -}
+unset :: String -> Repo -> IO (Maybe Repo)
+unset k r = ifM (Git.Command.runBool ps r)
+ ( return $ Just $ r { config = M.delete k (config r) }
+ , return Nothing
+ )
+ where
+ ps = [Param "config", Param "--unset-all", Param k]
diff --git a/debian/changelog b/debian/changelog
index b5b545ca2..9fd6440f6 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -21,6 +21,7 @@ git-annex (5.2015022) UNRELEASED; urgency=medium
(This used to be done, but it forgot to do it since version 4.20130909.)
* When re-execing git-annex, use current program location, rather than
~/.config/git-annex/program, when possible.
+ * Submodules are now supported by git-annex!
-- Joey Hess <id@joeyh.name> Thu, 19 Feb 2015 14:16:03 -0400
diff --git a/doc/bugs/Git_annexed_files_symlink_are_wrong_when_submodule_is_not_in_the_same_path.mdwn b/doc/bugs/Git_annexed_files_symlink_are_wrong_when_submodule_is_not_in_the_same_path.mdwn
index f7bae3b4b..11040e301 100644
--- a/doc/bugs/Git_annexed_files_symlink_are_wrong_when_submodule_is_not_in_the_same_path.mdwn
+++ b/doc/bugs/Git_annexed_files_symlink_are_wrong_when_submodule_is_not_in_the_same_path.mdwn
@@ -63,3 +63,9 @@ I tried playing with making the repository direct and then indirect, hoping that
### Please provide any additional information below.
[[!tag confirmed]]
+
+> [[fixed|done]] -- with a current version of git, git-annex now supports
+> [[/submodules]]. NB: Filesystem must support symlinks, or this won't
+> work.
+>
+> -- [[Joey]]
diff --git a/doc/links/the_details.mdwn b/doc/links/the_details.mdwn
index a7f8633a8..cb18d5b2b 100644
--- a/doc/links/the_details.mdwn
+++ b/doc/links/the_details.mdwn
@@ -3,6 +3,7 @@
* [[encryption]]
* [[key-value backends|backends]]
* [[bare_repositories]]
+* [[submodules]]
* [[internals]]
* [[scalability]]
* [[design]]
diff --git a/doc/submodules.mdwn b/doc/submodules.mdwn
new file mode 100644
index 000000000..fe5fc9a9e
--- /dev/null
+++ b/doc/submodules.mdwn
@@ -0,0 +1,21 @@
+[Git submodules](http://git-scm.com/book/en/v2/Git-Tools-Submodules) are
+supported by git-annex since version 5.20150303.
+
+Git normally makes a `.git` **file** in a
+submodule, that points to the real git repository under `.git/modules/`.
+This presents problems for git-annex. So, when used in a submodule,
+git-annex will automatically replace the `.git` file with a symlink
+pointing at the git repository.
+
+With that taken care of, git-annex should work ok in submodules. Although
+this is a new and somewhat experimental feature.
+
+The conversion of .git file to .git symlink mostly won't bother git.
+
+Known problems:
+
+* If you want to delete a whole submodule, `git rm submodule`
+ will refuse to delete it, complaining that the
+ submodule "uses a .git directory". Workaround: Use `rm -rf`
+ to delete the tree, and then `git commit`.
+* This won't work on filesystems not supporting symlinks.