Switch to using relative paths to the git repository.
This allows the git repository to be moved while git-annex is running in it, with fewer problems. On Windows, this avoids some of the problems with the absurdly small MAX_PATH of 260 bytes. In particular, git-annex repositories should work in deeper/longer directory structures than before. See http://git-annex.branchable.com/bugs/__34__git-annex:_direct:_1_failed__34___on_Windows/ There are several possible ways this change could break git-annex: 1. If it changes its working directory while it's running, that would be Bad News. Good news everyone! git-annex never does so. It would also break thread safety, so all such things were stomped out long ago. 2. parentDir "." -> "" which is not a valid path. I had to fix one instace of this, and I should probably wipe all calls to parentDir out of the git-annex code base; it was never a good idea. 3. Things like relPathDirToFile require absolute input paths, and code assumes that the git repo path is absolute and passes it to it as-is. In the case of relPathDirToFile, I converted it to not make this assumption. Currently, the test suite has 16 failures.
This commit is contained in:
parent
550f269828
commit
cd865c3b8f
14 changed files with 70 additions and 50 deletions
2
Annex.hs
2
Annex.hs
|
@ -187,7 +187,7 @@ newState c r = AnnexState
|
||||||
- Ensures the config is read, if it was not already. -}
|
- Ensures the config is read, if it was not already. -}
|
||||||
new :: Git.Repo -> IO AnnexState
|
new :: Git.Repo -> IO AnnexState
|
||||||
new r = do
|
new r = do
|
||||||
r' <- Git.adjustPath <$> Git.Config.read r
|
r' <- Git.Config.read =<< Git.relPath r
|
||||||
let c = extractGitConfig r'
|
let c = extractGitConfig r'
|
||||||
newState c <$> if annexDirect c then fixupDirect r' else return r'
|
newState c <$> if annexDirect c then fixupDirect r' else return r'
|
||||||
|
|
||||||
|
|
|
@ -114,7 +114,7 @@ addAssociatedFile key file = do
|
||||||
normaliseAssociatedFile :: FilePath -> Annex FilePath
|
normaliseAssociatedFile :: FilePath -> Annex FilePath
|
||||||
normaliseAssociatedFile file = do
|
normaliseAssociatedFile file = do
|
||||||
top <- fromRepo Git.repoPath
|
top <- fromRepo Git.repoPath
|
||||||
liftIO $ relPathDirToFile top <$> absPath file
|
liftIO $ relPathDirToFile top file
|
||||||
|
|
||||||
{- Checks if a file in the tree, associated with a key, has not been modified.
|
{- Checks if a file in the tree, associated with a key, has not been modified.
|
||||||
-
|
-
|
||||||
|
|
|
@ -10,16 +10,17 @@ module Annex.Direct.Fixup where
|
||||||
import Git.Types
|
import Git.Types
|
||||||
import Git.Config
|
import Git.Config
|
||||||
import qualified Git.Construct as Construct
|
import qualified Git.Construct as Construct
|
||||||
import Utility.Path
|
|
||||||
import Utility.SafeCommand
|
import Utility.SafeCommand
|
||||||
|
|
||||||
|
import System.FilePath
|
||||||
|
|
||||||
{- Direct mode repos have core.bare=true, but are not really bare.
|
{- 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
|
- 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. -}
|
- run by git-annex to be passed parameters that override this setting. -}
|
||||||
fixupDirect :: Repo -> IO Repo
|
fixupDirect :: Repo -> IO Repo
|
||||||
fixupDirect r@(Repo { location = l@(Local { gitdir = d, worktree = Nothing }) }) = do
|
fixupDirect r@(Repo { location = l@(Local { gitdir = d, worktree = Nothing }) }) = do
|
||||||
let r' = r
|
let r' = r
|
||||||
{ location = l { worktree = Just (parentDir d) }
|
{ location = l { worktree = Just (takeDirectory d) }
|
||||||
, gitGlobalOpts = gitGlobalOpts r ++
|
, gitGlobalOpts = gitGlobalOpts r ++
|
||||||
[ Param "-c"
|
[ Param "-c"
|
||||||
, Param $ coreBare ++ "=" ++ boolConfig False
|
, Param $ coreBare ++ "=" ++ boolConfig False
|
||||||
|
|
|
@ -28,9 +28,9 @@ start :: [FilePath] -> CommandStart
|
||||||
start [] = do
|
start [] = do
|
||||||
-- Like git status, when run without a directory, behave as if
|
-- Like git status, when run without a directory, behave as if
|
||||||
-- given the path to the top of the repository.
|
-- given the path to the top of the repository.
|
||||||
currdir <- liftIO getCurrentDirectory
|
|
||||||
top <- fromRepo Git.repoPath
|
top <- fromRepo Git.repoPath
|
||||||
start' [relPathDirToFile currdir top]
|
d <- liftIO $ relPathCwdToFile top
|
||||||
|
start' [d]
|
||||||
start locs = start' locs
|
start locs = start' locs
|
||||||
|
|
||||||
start' :: [FilePath] -> CommandStart
|
start' :: [FilePath] -> CommandStart
|
||||||
|
|
46
Git.hs
46
Git.hs
|
@ -31,6 +31,7 @@ module Git (
|
||||||
hookPath,
|
hookPath,
|
||||||
assertLocal,
|
assertLocal,
|
||||||
adjustPath,
|
adjustPath,
|
||||||
|
relPath,
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Network.URI (uriPath, uriScheme, unEscapeString)
|
import Network.URI (uriPath, uriScheme, unEscapeString)
|
||||||
|
@ -141,25 +142,28 @@ hookPath script repo = do
|
||||||
isexecutable f = isExecutable . fileMode <$> getFileStatus f
|
isexecutable f = isExecutable . fileMode <$> getFileStatus f
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
{- Adusts the path to a local Repo.
|
{- Makes the path to a local Repo be relative to the cwd. -}
|
||||||
-
|
relPath :: Repo -> IO Repo
|
||||||
- On windows, prefixing a path with \\?\ makes it be processed as a raw
|
relPath = adjustPath torel
|
||||||
- path (/ is not converted to \). The benefit is that such a path does
|
where
|
||||||
- avoids Windows's 260 byte limitation on the entire path. -}
|
torel p = do
|
||||||
adjustPath :: Repo -> Repo
|
p' <- relPathCwdToFile p
|
||||||
adjustPath r@(Repo { location = l@(Local { gitdir = d, worktree = w }) }) = r
|
if null p'
|
||||||
{ location = l
|
then return "."
|
||||||
{ gitdir = adjustPath' d
|
else return p'
|
||||||
, worktree = fmap adjustPath' w
|
|
||||||
}
|
|
||||||
}
|
|
||||||
adjustPath r@(Repo { location = LocalUnknown d }) =
|
|
||||||
r { location = LocalUnknown (adjustPath' d) }
|
|
||||||
adjustPath r = r
|
|
||||||
|
|
||||||
adjustPath' :: FilePath -> FilePath
|
{- Adusts the path to a local Repo using the provided function. -}
|
||||||
#if mingw32_HOST_OS
|
adjustPath :: (FilePath -> IO FilePath) -> Repo -> IO Repo
|
||||||
adjustPath' d = "\\\\?\\" ++ d
|
adjustPath f r@(Repo { location = l@(Local { gitdir = d, worktree = w }) }) = do
|
||||||
#else
|
d' <- f d
|
||||||
adjustPath' = id
|
w' <- maybe (pure Nothing) (Just <$$> f) w
|
||||||
#endif
|
return $ r
|
||||||
|
{ location = l
|
||||||
|
{ gitdir = d'
|
||||||
|
, worktree = w'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
adjustPath f r@(Repo { location = LocalUnknown d }) = do
|
||||||
|
d' <- f d
|
||||||
|
return $ r { location = LocalUnknown d' }
|
||||||
|
adjustPath _ r = pure r
|
||||||
|
|
|
@ -84,7 +84,7 @@ checkAttr (h, attrs, oldgit, currdir) want file = do
|
||||||
- so use relative filenames. -}
|
- so use relative filenames. -}
|
||||||
file'
|
file'
|
||||||
| oldgit = absPathFrom currdir file
|
| oldgit = absPathFrom currdir file
|
||||||
| otherwise = relPathDirToFile currdir $ absPathFrom currdir file
|
| otherwise = relPathDirToFileAbs currdir $ absPathFrom currdir file
|
||||||
oldattrvalue attr l = end bits !! 0
|
oldattrvalue attr l = end bits !! 0
|
||||||
where
|
where
|
||||||
bits = split sep l
|
bits = split sep l
|
||||||
|
|
|
@ -16,7 +16,7 @@ import qualified Utility.CoProcess as CoProcess
|
||||||
|
|
||||||
{- Constructs a git command line operating on the specified repo. -}
|
{- Constructs a git command line operating on the specified repo. -}
|
||||||
gitCommandLine :: [CommandParam] -> Repo -> [CommandParam]
|
gitCommandLine :: [CommandParam] -> Repo -> [CommandParam]
|
||||||
gitCommandLine params r@(Repo { location = l@(Local _ _ ) }) =
|
gitCommandLine params r@(Repo { location = l@(Local { } ) }) =
|
||||||
setdir : settree ++ gitGlobalOpts r ++ params
|
setdir : settree ++ gitGlobalOpts r ++ params
|
||||||
where
|
where
|
||||||
setdir = Param $ "--git-dir=" ++ gitdir l
|
setdir = Param $ "--git-dir=" ++ gitdir l
|
||||||
|
|
|
@ -39,8 +39,7 @@ fromTopFilePath p repo = absPathFrom (repoPath repo) (getTopFilePath p)
|
||||||
|
|
||||||
{- The input FilePath can be absolute, or relative to the CWD. -}
|
{- The input FilePath can be absolute, or relative to the CWD. -}
|
||||||
toTopFilePath :: FilePath -> Git.Repo -> IO TopFilePath
|
toTopFilePath :: FilePath -> Git.Repo -> IO TopFilePath
|
||||||
toTopFilePath file repo = TopFilePath <$>
|
toTopFilePath file repo = TopFilePath <$> relPathDirToFile (repoPath repo) file
|
||||||
relPathDirToFile (repoPath repo) <$> absPath file
|
|
||||||
|
|
||||||
{- The input FilePath must already be relative to the top of the git
|
{- The input FilePath must already be relative to the top of the git
|
||||||
- repository -}
|
- repository -}
|
||||||
|
|
|
@ -131,9 +131,9 @@ typeChanged' ps l repo = do
|
||||||
(fs, cleanup) <- pipeNullSplit (prefix ++ ps ++ suffix) repo
|
(fs, cleanup) <- pipeNullSplit (prefix ++ ps ++ suffix) repo
|
||||||
-- git diff returns filenames relative to the top of the git repo;
|
-- git diff returns filenames relative to the top of the git repo;
|
||||||
-- convert to filenames relative to the cwd, like git ls-files.
|
-- convert to filenames relative to the cwd, like git ls-files.
|
||||||
let top = repoPath repo
|
top <- absPath (repoPath repo)
|
||||||
currdir <- getCurrentDirectory
|
currdir <- getCurrentDirectory
|
||||||
return (map (\f -> relPathDirToFile currdir $ top </> f) fs, cleanup)
|
return (map (\f -> relPathDirToFileAbs currdir $ top </> f) fs, cleanup)
|
||||||
where
|
where
|
||||||
prefix = [Params "diff --name-only --diff-filter=T -z"]
|
prefix = [Params "diff --name-only --diff-filter=T -z"]
|
||||||
suffix = Param "--" : (if null l then [File "."] else map File l)
|
suffix = Param "--" : (if null l then [File "."] else map File l)
|
||||||
|
|
|
@ -225,10 +225,13 @@ badBranches missing r = filterM isbad =<< getAllRefs r
|
||||||
- Relies on packed refs being exploded before it's called.
|
- Relies on packed refs being exploded before it's called.
|
||||||
-}
|
-}
|
||||||
getAllRefs :: Repo -> IO [Ref]
|
getAllRefs :: Repo -> IO [Ref]
|
||||||
getAllRefs r = map toref <$> dirContentsRecursive refdir
|
getAllRefs r = getAllRefs' (localGitDir r </> "refs")
|
||||||
where
|
|
||||||
refdir = localGitDir r </> "refs"
|
getAllRefs' :: FilePath -> IO [Ref]
|
||||||
toref = Ref . relPathDirToFile (localGitDir r)
|
getAllRefs' refdir = do
|
||||||
|
let topsegs = length (splitPath refdir) - 1
|
||||||
|
let toref = Ref . joinPath . drop topsegs . splitPath
|
||||||
|
map toref <$> dirContentsRecursive refdir
|
||||||
|
|
||||||
explodePackedRefsFile :: Repo -> IO ()
|
explodePackedRefsFile :: Repo -> IO ()
|
||||||
explodePackedRefsFile r = do
|
explodePackedRefsFile r = do
|
||||||
|
|
|
@ -87,8 +87,8 @@ import qualified Git
|
||||||
- Everything else should not end in a trailing path sepatator.
|
- Everything else should not end in a trailing path sepatator.
|
||||||
-
|
-
|
||||||
- Only functions (with names starting with "git") that build a path
|
- Only functions (with names starting with "git") that build a path
|
||||||
- based on a git repository should return an absolute path.
|
- based on a git repository should return full path relative to the git
|
||||||
- Everything else should use relative paths.
|
- repository. Everything else returns path segments.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
{- The directory git annex uses for local state, relative to the .git
|
{- The directory git annex uses for local state, relative to the .git
|
||||||
|
@ -108,7 +108,7 @@ annexLocations key = map (annexLocation key) annexHashes
|
||||||
annexLocation :: Key -> Hasher -> FilePath
|
annexLocation :: Key -> Hasher -> FilePath
|
||||||
annexLocation key hasher = objectDir </> keyPath key hasher
|
annexLocation key hasher = objectDir </> keyPath key hasher
|
||||||
|
|
||||||
{- Annexed object's absolute location in a repository.
|
{- Annexed object's location in a repository.
|
||||||
-
|
-
|
||||||
- When there are multiple possible locations, returns the one where the
|
- When there are multiple possible locations, returns the one where the
|
||||||
- file is actually present.
|
- file is actually present.
|
||||||
|
@ -146,7 +146,7 @@ gitAnnexLink file key r = do
|
||||||
currdir <- getCurrentDirectory
|
currdir <- getCurrentDirectory
|
||||||
let absfile = fromMaybe whoops $ absNormPathUnix currdir file
|
let absfile = fromMaybe whoops $ absNormPathUnix currdir file
|
||||||
loc <- gitAnnexLocation' key r False
|
loc <- gitAnnexLocation' key r False
|
||||||
return $ relPathDirToFile (parentDir absfile) loc
|
relPathDirToFile (parentDir absfile) loc
|
||||||
where
|
where
|
||||||
whoops = error $ "unable to normalize " ++ file
|
whoops = error $ "unable to normalize " ++ file
|
||||||
|
|
||||||
|
|
2
Test.hs
2
Test.hs
|
@ -79,9 +79,11 @@ import qualified Remote.Helper.Encryptable
|
||||||
import qualified Types.Crypto
|
import qualified Types.Crypto
|
||||||
import qualified Utility.Gpg
|
import qualified Utility.Gpg
|
||||||
#endif
|
#endif
|
||||||
|
import qualified Messages
|
||||||
|
|
||||||
main :: [String] -> IO ()
|
main :: [String] -> IO ()
|
||||||
main ps = do
|
main ps = do
|
||||||
|
Messages.enableDebugOutput
|
||||||
let tests = testGroup "Tests"
|
let tests = testGroup "Tests"
|
||||||
-- Test both direct and indirect mode.
|
-- Test both direct and indirect mode.
|
||||||
-- Windows is only going to use direct mode,
|
-- Windows is only going to use direct mode,
|
||||||
|
|
|
@ -126,14 +126,19 @@ absPath file = do
|
||||||
- relPathCwdToFile "/tmp/foo/bar" == ""
|
- relPathCwdToFile "/tmp/foo/bar" == ""
|
||||||
-}
|
-}
|
||||||
relPathCwdToFile :: FilePath -> IO FilePath
|
relPathCwdToFile :: FilePath -> IO FilePath
|
||||||
relPathCwdToFile f = relPathDirToFile <$> getCurrentDirectory <*> absPath f
|
relPathCwdToFile f = do
|
||||||
|
c <- getCurrentDirectory
|
||||||
|
relPathDirToFile c f
|
||||||
|
|
||||||
{- Constructs a relative path from a directory to a file.
|
{- Constructs a relative path from a directory to a file. -}
|
||||||
-
|
relPathDirToFile :: FilePath -> FilePath -> IO FilePath
|
||||||
- Both must be absolute, and cannot contain .. etc. (eg use absPath first).
|
relPathDirToFile from to = relPathDirToFileAbs <$> absPath from <*> absPath to
|
||||||
|
|
||||||
|
{- This requires the first path to be absolute, and the
|
||||||
|
- second path cannot contain ../ or ./
|
||||||
-}
|
-}
|
||||||
relPathDirToFile :: FilePath -> FilePath -> FilePath
|
relPathDirToFileAbs :: FilePath -> FilePath -> FilePath
|
||||||
relPathDirToFile from to = join s $ dotdots ++ uncommon
|
relPathDirToFileAbs from to = join s $ dotdots ++ uncommon
|
||||||
where
|
where
|
||||||
s = [pathSeparator]
|
s = [pathSeparator]
|
||||||
pfrom = split s from
|
pfrom = split s from
|
||||||
|
@ -149,7 +154,7 @@ prop_relPathDirToFile_basics from to
|
||||||
| from == to = null r
|
| from == to = null r
|
||||||
| otherwise = not (null r)
|
| otherwise = not (null r)
|
||||||
where
|
where
|
||||||
r = relPathDirToFile from to
|
r = relPathDirToFileAbs from to
|
||||||
|
|
||||||
prop_relPathDirToFile_regressionTest :: Bool
|
prop_relPathDirToFile_regressionTest :: Bool
|
||||||
prop_relPathDirToFile_regressionTest = same_dir_shortcurcuits_at_difference
|
prop_relPathDirToFile_regressionTest = same_dir_shortcurcuits_at_difference
|
||||||
|
@ -158,7 +163,7 @@ prop_relPathDirToFile_regressionTest = same_dir_shortcurcuits_at_difference
|
||||||
- location, but it's not really the same directory.
|
- location, but it's not really the same directory.
|
||||||
- Code used to get this wrong. -}
|
- Code used to get this wrong. -}
|
||||||
same_dir_shortcurcuits_at_difference =
|
same_dir_shortcurcuits_at_difference =
|
||||||
relPathDirToFile (joinPath [pathSeparator : "tmp", "r", "lll", "xxx", "yyy", "18"])
|
relPathDirToFileAbs (joinPath [pathSeparator : "tmp", "r", "lll", "xxx", "yyy", "18"])
|
||||||
(joinPath [pathSeparator : "tmp", "r", ".git", "annex", "objects", "18", "gk", "SHA256-foo", "SHA256-foo"])
|
(joinPath [pathSeparator : "tmp", "r", ".git", "annex", "objects", "18", "gk", "SHA256-foo", "SHA256-foo"])
|
||||||
== joinPath ["..", "..", "..", "..", ".git", "annex", "objects", "18", "gk", "SHA256-foo", "SHA256-foo"]
|
== joinPath ["..", "..", "..", "..", ".git", "annex", "objects", "18", "gk", "SHA256-foo", "SHA256-foo"]
|
||||||
|
|
||||||
|
@ -187,7 +192,7 @@ relHome :: FilePath -> IO String
|
||||||
relHome path = do
|
relHome path = do
|
||||||
home <- myHomeDir
|
home <- myHomeDir
|
||||||
return $ if dirContains home path
|
return $ if dirContains home path
|
||||||
then "~/" ++ relPathDirToFile home path
|
then "~/" ++ relPathDirToFileAbs home path
|
||||||
else path
|
else path
|
||||||
|
|
||||||
{- Checks if a command is available in PATH.
|
{- Checks if a command is available in PATH.
|
||||||
|
|
6
debian/changelog
vendored
6
debian/changelog
vendored
|
@ -12,6 +12,12 @@ git-annex (5.20141232) UNRELEASED; urgency=medium
|
||||||
* Check git version at runtime, rather than assuming it will be the same
|
* Check git version at runtime, rather than assuming it will be the same
|
||||||
as the git version used at build time when running git-checkattr and
|
as the git version used at build time when running git-checkattr and
|
||||||
git-branch remove.
|
git-branch remove.
|
||||||
|
* Switch to using relative paths to the git repository.
|
||||||
|
- This allows the git repository to be moved while git-annex is running in
|
||||||
|
it, with fewer problems.
|
||||||
|
- On Windows, this avoids some of the problems with the absurdly small
|
||||||
|
MAX_PATH of 260 bytes. In particular, git-annex repositories should
|
||||||
|
work in deeper/longer directory structures than before.
|
||||||
|
|
||||||
-- Joey Hess <id@joeyh.name> Fri, 02 Jan 2015 13:35:13 -0400
|
-- Joey Hess <id@joeyh.name> Fri, 02 Jan 2015 13:35:13 -0400
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue