aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Annex/Direct.hs19
-rw-r--r--CmdLine/GitAnnex.hs2
-rw-r--r--Command/Proxy.hs48
-rw-r--r--Git/Branch.hs6
-rw-r--r--Utility/Tmp.hs16
-rw-r--r--debian/changelog3
-rw-r--r--doc/bugs/main_repo_not_available_on_downloads.kitenet.net.mdwn2
-rw-r--r--doc/direct_mode.mdwn25
-rw-r--r--doc/git-annex.mdwn25
-rw-r--r--doc/todo/direct_mode_undo.mdwn42
10 files changed, 151 insertions, 37 deletions
diff --git a/Annex/Direct.hs b/Annex/Direct.hs
index 9489b74f2..e4015dd16 100644
--- a/Annex/Direct.hs
+++ b/Annex/Direct.hs
@@ -225,9 +225,17 @@ mergeDirectCommit allowff old branch commitmode = do
where
canff = maybe (return False) (\o -> inRepo $ Git.Branch.fastForwardable o branch) old
-{- Cleans up after a direct mode merge. The merge must have been staged
- - in the index. Uses diff-index to compare the staged changes with
- - the tree before the merge, and applies those changes to the work tree.
+mergeDirectCleanup :: FilePath -> Git.Ref -> Annex ()
+mergeDirectCleanup d oldref = do
+ updateWorkTree d oldref
+ liftIO $ removeDirectoryRecursive d
+
+{- Updates the direct mode work tree to reflect the changes staged in the
+ - index by a git command, that was run in a temporary work tree.
+ -
+ - Uses diff-index to compare the staged changes with provided ref
+ - which should be the tree before the merge, and applies those
+ - changes to the work tree.
-
- There are really only two types of changes: An old item can be deleted,
- or a new item added. Two passes are made, first deleting and then
@@ -236,8 +244,8 @@ mergeDirectCommit allowff old branch commitmode = do
- order, but we cannot add the directory until the file with the
- same name is removed.)
-}
-mergeDirectCleanup :: FilePath -> Git.Ref -> Annex ()
-mergeDirectCleanup d oldref = do
+updateWorkTree :: FilePath -> Git.Ref -> Annex ()
+updateWorkTree d oldref = do
(items, cleanup) <- inRepo $ DiffTree.diffIndex oldref
makeabs <- flip fromTopFilePath <$> gitRepo
let fsitems = zip (map (makeabs . DiffTree.file) items) items
@@ -246,7 +254,6 @@ mergeDirectCleanup d oldref = do
forM_ fsitems $
go makeabs DiffTree.dstsha DiffTree.dstmode movein movein_raw
void $ liftIO cleanup
- liftIO $ removeDirectoryRecursive d
where
go makeabs getsha getmode a araw (f, item)
| getsha item == nullSha = noop
diff --git a/CmdLine/GitAnnex.hs b/CmdLine/GitAnnex.hs
index 5f5d4a151..2fca855e0 100644
--- a/CmdLine/GitAnnex.hs
+++ b/CmdLine/GitAnnex.hs
@@ -83,6 +83,7 @@ import qualified Command.Direct
import qualified Command.Indirect
import qualified Command.Upgrade
import qualified Command.Forget
+import qualified Command.Proxy
import qualified Command.Version
import qualified Command.Help
#ifdef WITH_ASSISTANT
@@ -175,6 +176,7 @@ cmds = concat
, Command.Indirect.cmd
, Command.Upgrade.cmd
, Command.Forget.cmd
+ , Command.Proxy.cmd
, Command.Version.cmd
, Command.Help.cmd
#ifdef WITH_ASSISTANT
diff --git a/Command/Proxy.hs b/Command/Proxy.hs
new file mode 100644
index 000000000..135a76504
--- /dev/null
+++ b/Command/Proxy.hs
@@ -0,0 +1,48 @@
+{- git-annex command
+ -
+ - Copyright 2014 Joey Hess <joey@kitenet.net>
+ -
+ - Licensed under the GNU GPL version 3 or higher.
+ -}
+
+module Command.Proxy where
+
+import Common.Annex
+import Command
+import Config
+import Utility.Tmp
+import Utility.Env
+import Annex.Direct
+import qualified Git.Branch
+import qualified Git.Sha
+
+cmd :: [Command]
+cmd = [notBareRepo $
+ command "proxy" ("-- git command") seek
+ SectionCommon "safely bypass direct mode guard"]
+
+seek :: CommandSeek
+seek ("--":ps) = withWords start ps
+seek ps = withWords start ps
+
+start :: [String] -> CommandStart
+start [] = error "Did not specify command to run."
+start (c:ps) = liftIO . exitWith =<< ifM isDirect
+ ( do
+ g <- gitRepo
+ withTmpDirIn (gitAnnexTmpMiscDir g) "proxy" go
+ , liftIO $ safeSystem c (map Param ps)
+ )
+ where
+ go tmp = do
+ oldref <- fromMaybe Git.Sha.emptyTree
+ <$> inRepo Git.Branch.currentSha
+ liftIO $ print oldref
+ exitcode <- liftIO $ proxy tmp
+ mergeDirectCleanup tmp oldref
+ return exitcode
+ proxy tmp = do
+ usetmp <- Just . addEntry "GIT_WORK_TREE" tmp <$> getEnvironment
+ unlessM (boolSystemEnv "git" [Param "checkout", Param "--", Param "."] usetmp) $
+ error "Failed to set up proxy work tree."
+ safeSystemEnv c (map Param ps) usetmp
diff --git a/Git/Branch.hs b/Git/Branch.hs
index 0b7d888b8..1d5ffd39d 100644
--- a/Git/Branch.hs
+++ b/Git/Branch.hs
@@ -43,6 +43,12 @@ currentUnsafe r = parse . firstLine
| null l = Nothing
| otherwise = Just $ Git.Ref l
+currentSha :: Repo -> IO (Maybe Git.Sha)
+currentSha r = go =<< current r
+ where
+ go Nothing = return Nothing
+ go (Just ref) = Git.Ref.sha ref r
+
{- Checks if the second branch has any commits not present on the first
- branch. -}
changed :: Branch -> Branch -> Repo -> IO Bool
diff --git a/Utility/Tmp.hs b/Utility/Tmp.hs
index edd82f5ac..7599cdd00 100644
--- a/Utility/Tmp.hs
+++ b/Utility/Tmp.hs
@@ -24,8 +24,8 @@ type Template = String
{- Runs an action like writeFile, writing to a temp file first and
- then moving it into place. The temp file is stored in the same
- directory as the final file to avoid cross-device renames. -}
-viaTmp :: (FilePath -> String -> IO ()) -> FilePath -> String -> IO ()
-viaTmp a file content = bracket setup cleanup use
+viaTmp :: (MonadMask m, MonadIO m) => (FilePath -> String -> m ()) -> FilePath -> String -> m ()
+viaTmp a file content = bracketIO setup cleanup use
where
(dir, base) = splitFileName file
template = base ++ ".tmp"
@@ -36,9 +36,9 @@ viaTmp a file content = bracket setup cleanup use
_ <- tryIO $ hClose h
tryIO $ removeFile tmpfile
use (tmpfile, h) = do
- hClose h
+ liftIO $ hClose h
a tmpfile content
- rename tmpfile file
+ liftIO $ rename tmpfile file
{- Runs an action with a tmp file located in the system's tmp directory
- (or in "." if there is none) then removes the file. -}
@@ -61,15 +61,15 @@ withTmpFileIn tmpdir template a = bracket create remove use
{- Runs an action with a tmp directory located within the system's tmp
- directory (or within "." if there is none), then removes the tmp
- directory and all its contents. -}
-withTmpDir :: Template -> (FilePath -> IO a) -> IO a
+withTmpDir :: (MonadMask m, MonadIO m) => Template -> (FilePath -> m a) -> m a
withTmpDir template a = do
- tmpdir <- catchDefaultIO "." getTemporaryDirectory
+ tmpdir <- liftIO $ catchDefaultIO "." getTemporaryDirectory
withTmpDirIn tmpdir template a
{- Runs an action with a tmp directory located within a specified directory,
- then removes the tmp directory and all its contents. -}
-withTmpDirIn :: FilePath -> Template -> (FilePath -> IO a) -> IO a
-withTmpDirIn tmpdir template = bracket create remove
+withTmpDirIn :: (MonadMask m, MonadIO m) => FilePath -> Template -> (FilePath -> m a) -> m a
+withTmpDirIn tmpdir template = bracketIO create remove
where
remove d = whenM (doesDirectoryExist d) $ do
#if mingw32_HOST_OS
diff --git a/debian/changelog b/debian/changelog
index e6cbbce97..bcf47e3dd 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -3,6 +3,9 @@ git-annex (5.20141126) UNRELEASED; urgency=medium
* pre-commit: Block partial commit of unlocked annexed file, since
that left a typechange staged in index due to some infelicity of git's
handling of partial commits.
+ * proxy: New command for direct mode repositories, allows bypassing
+ the direct mode guard in a safe way to do all sorts of things
+ including git revert, git mv, git checkout ...
* Debian package is now maintained by Gergely Nagy.
-- Joey Hess <joeyh@debian.org> Mon, 10 Nov 2014 15:31:55 -0400
diff --git a/doc/bugs/main_repo_not_available_on_downloads.kitenet.net.mdwn b/doc/bugs/main_repo_not_available_on_downloads.kitenet.net.mdwn
index f327d7e2a..65671a2e9 100644
--- a/doc/bugs/main_repo_not_available_on_downloads.kitenet.net.mdwn
+++ b/doc/bugs/main_repo_not_available_on_downloads.kitenet.net.mdwn
@@ -21,3 +21,5 @@ Shouldn't this be done automatically?
### Please provide any additional information below.
Thanks! -- [[anarcat]]
+
+> [[done]]; apparently some pebak. --[[Joey]]
diff --git a/doc/direct_mode.mdwn b/doc/direct_mode.mdwn
index 749d3a6e2..93e6b3538 100644
--- a/doc/direct_mode.mdwn
+++ b/doc/direct_mode.mdwn
@@ -3,8 +3,9 @@ git, and in turn point at the content of large files that is stored in
`.git/annex/objects/`. Direct mode gets rid of the symlinks.
The advantage of direct mode is that you can access files directly,
-including modifying them. The disadvantage is that most regular git
-commands cannot be used in a direct mode repository.
+including modifying them. The disadvantage is that mant regular git
+commands cannot be used in a direct mode repository, since they don't
+understand how to update its working tree.
Normally, git-annex repositories start off in indirect mode. With some
exceptions:
@@ -82,11 +83,29 @@ There are still lots of git commands you can use in direct mode. For
example, you can run `git log` on files, run `git push`, `git fetch`,
`git config`, `git remote add` etc.
+## proxing git commands in direct mode
+
+For those times when you really need to run a command like `git revert
+HEAD` in a direct mode repository, git-annex has the ability to proxy
+the command to work in direct mode.
+
+For example:
+
+ git annex proxy -- git revert HEAD
+
+ git annex proxy -- git checkout HEAD^^
+
+ git annex proxy -- git mv mydir newname
+
+This works by setting up a temporary work tree, letting the git
+command run on that work tree, and then updating the real work
+tree to reflect any changes staged or committed by the git command,
+with appropriate handling of the direct mode files.
+
## forcing git to use the work tree in direct mode
This is for experts only. You can lose data doing this, or check enormous
files directly into your git repository, and it's your fault if you do!
-Also, there should be no good reason to need to do this, ever.
Ok, with the warnings out of the way, all you need to do to make any
git command access the work tree in direct mode is pass it
diff --git a/doc/git-annex.mdwn b/doc/git-annex.mdwn
index b22ff3881..0e302b4b7 100644
--- a/doc/git-annex.mdwn
+++ b/doc/git-annex.mdwn
@@ -282,6 +282,25 @@ subdirectories).
are on a video hosting site, and the video is downloaded. This allows
importing e.g., youtube playlists.
+* `proxy -- git cmd [options]`
+
+ Only useful in a direct mode repository, this runs the specified git
+ command with a temporary work tree, and updates the working tree to
+ reflect any changes staged or committed by the git command.
+
+ For example, to revert the most recent change that was committed
+ to the repository:
+
+ git annex proxy -- git revert HEAD
+
+ To check out a past version of the repository:
+
+ git annex proxy -- git checkout HEAD^^
+
+ To rename a directory:
+
+ git annex proxy -- git mv mydir newname
+
* `watch`
Watches for changes to files in the current directory and its subdirectories,
@@ -499,9 +518,9 @@ subdirectories).
As part of the switch to direct mode, any changed files will be committed.
- Note that git commands that operate on the work tree are often unsafe to
- use in direct mode repositories, and can result in data loss or other
- bad behavior.
+ Note that git commands that operate on the work tree will refuse to
+ run in direct mode repositories. Use `git annex proxy` to safely run such
+ commands.
* `indirect`
diff --git a/doc/todo/direct_mode_undo.mdwn b/doc/todo/direct_mode_undo.mdwn
index 969678d23..7d719c74b 100644
--- a/doc/todo/direct_mode_undo.mdwn
+++ b/doc/todo/direct_mode_undo.mdwn
@@ -6,26 +6,40 @@ mode first, which can range from annoying to really annoying to impossible
## general approach
-`git annex foo $gitcmd` could:
+`git annex proxy $gitcmd` could:
1. check out a local clone of the repo
-2. run "git $gitcmd" inside the cline
-3. merge any changes from the clone back into the direct mode repo
+2. run "git $gitcmd" inside the clone
+3. Merge any changes from the clone back into the direct mode repo
and update the work tree the same as is done by `git annex merge`.
+4. If a different branch was checked out in the clone, update the repo
+ to have that same branch checked out.
This is a general bypass for the direct mode guard. It should work anywhere
(even on FAT). It avoids problems like `git commit -a` being unsafe in
direct mode, since running such a command in a local clone, which does not
use direct mode is always safe.
+Beyond handling undo, #4 means that this can be used to check
+out past versions of the repo (eg, `git annex proxy checkout HEAD^^`)
+
One problem with it is that it can only operate on changes that have been
committed. If you've just accidentially deleted a file and want to undo
that, and haven't run `git annex sync` to commit it, you can't revert it.
-Also, while this is general, I don't know if the generality is useful.
-What other changes, than revert, would it make sense to do with such a
-command? It could be used to check out some other branch, but step 3 above
-would merge that branch back into the direct mode repo.
+The need to make a local clone will make it a bit slow, since the whole
+work tree will need to be set up. It might be possible to reuse the clone
+next time (after resetting it to reflect the current HEAD).
+
+Some things like the reflog and local branches don't get cloned, so
+git commands that try to act on those wouldn't work. Maybe it would be
+better to make it use a separate work tree, but the same .git directory?
+Then step #3 would instead update the direct mode work tree to refect
+the new HEAD, and step #4 would not be needed.
+
+> This is done.. But, I think an undo command would also be good
+> to do, as a nicer user interface that can integrate well with a file
+> manager. --[[Joey]]
## git annex undo
@@ -61,16 +75,10 @@ it gets committed, and then that commit is reverted, resulting in another
commit. Which a later run of undo can in turn revert. If it didn't commit,
the history about the staged change that was reverted would be lost.
-What about undoing changes to a whole directory? It first would
-need to look through the git history to find every file under that
-directory. And then it would behave as if it were passed all those
-files. This would be useful for reverting `rm -rf`. But it could be very
-expensive. And it could lead to surprising results, like undoing a lot
-of unrelated changes when running on a bunch of files in a directory
-that were changed at different times.
-
-Maybe instead of letting a directory be passed, make undo with no
-parameters revert all changes made in the most recent commit.
+What about undoing changes to a whole directory? Recursively undoing
+the last change to each file would be expensive, and likely confusing.
+Instead, when a directory is passed, it could find the most recent commit
+that touched files in that directory, and undo the changes to those files.
Also, --depth could make undo look for an older commit than the most
recent one to affect the specified file.