undo command

This commit was sponsored by Andrew Cant.
This commit is contained in:
Joey Hess 2014-11-13 16:41:21 -04:00
parent d22d650f59
commit 13260ccc3a
7 changed files with 139 additions and 6 deletions

View file

@ -84,6 +84,7 @@ import qualified Command.Indirect
import qualified Command.Upgrade import qualified Command.Upgrade
import qualified Command.Forget import qualified Command.Forget
import qualified Command.Proxy import qualified Command.Proxy
import qualified Command.Undo
import qualified Command.Version import qualified Command.Version
import qualified Command.Help import qualified Command.Help
#ifdef WITH_ASSISTANT #ifdef WITH_ASSISTANT
@ -177,6 +178,7 @@ cmds = concat
, Command.Upgrade.cmd , Command.Upgrade.cmd
, Command.Forget.cmd , Command.Forget.cmd
, Command.Proxy.cmd , Command.Proxy.cmd
, Command.Undo.cmd
, Command.Version.cmd , Command.Version.cmd
, Command.Help.cmd , Command.Help.cmd
#ifdef WITH_ASSISTANT #ifdef WITH_ASSISTANT

84
Command/Undo.hs Normal file
View file

@ -0,0 +1,84 @@
{- git-annex command
-
- Copyright 2014 Joey Hess <joey@kitenet.net>
-
- Licensed under the GNU GPL version 3 or higher.
-}
module Command.Undo where
import Common.Annex
import Command
import Config
import Annex.Direct
import Annex.CatFile
import Git.DiffTree
import Git.FilePath
import Git.UpdateIndex
import Git.Sha
import qualified Git.LsFiles as LsFiles
import qualified Git.Command as Git
import qualified Git.Branch
import qualified Command.Sync
cmd :: [Command]
cmd = [notBareRepo $
command "undo" paramPaths seek
SectionCommon "undo last change to a file or directory"]
seek :: CommandSeek
seek ps = do
-- Safety first; avoid any undo that would touch files that are not
-- in the index.
(fs, cleanup) <- inRepo $ LsFiles.notInRepo False ps
unless (null fs) $
error $ "Cannot undo changes to files that are not checked into git: " ++ unwords fs
void $ liftIO $ cleanup
-- Committing staged changes before undo allows later
-- undoing the undo. It would be nicer to only commit staged
-- changes to the specified files, rather than all staged changes,
-- but that is difficult to do; a partial git-commit can't be done
-- in direct mode.
void $ Command.Sync.commitStaged Git.Branch.ManualCommit
"commit before undo"
withStrings start ps
start :: FilePath -> CommandStart
start p = do
showStart "undo" p
next $ perform p
perform :: FilePath -> CommandPerform
perform p = do
g <- gitRepo
-- Get the reversed diff that needs to be applied to undo.
(diff, cleanup) <- inRepo $
diffLog [Param "-R", Param "--", Param p]
top <- inRepo $ toTopFilePath p
let diff' = filter (`isDiffOf` top) diff
liftIO $ streamUpdateIndex g (map stageDiffTreeItem diff')
-- Take two passes through the diff, first doing any removals,
-- and then any adds. This order is necessary to handle eg, removing
-- a directory and replacing it with a file.
let (removals, adds) = partition (\di -> dstsha di == nullSha) diff'
let mkrel di = liftIO $ relPathCwdToFile $ fromTopFilePath (file di) g
forM_ removals $ \di -> do
f <- mkrel di
whenM isDirect $
maybe noop (`removeDirect` f)
=<< catKey (srcsha di) (srcmode di)
liftIO $ nukeFile f
forM_ adds $ \di -> do
f <- mkrel di
inRepo $ Git.run [Param "checkout", Param "--", File f]
whenM isDirect $
maybe noop (`toDirect` f)
=<< catKey (dstsha di) (dstmode di)
next $ liftIO cleanup

View file

@ -7,10 +7,12 @@
module Git.DiffTree ( module Git.DiffTree (
DiffTreeItem(..), DiffTreeItem(..),
isDiffOf,
diffTree, diffTree,
diffTreeRecursive, diffTreeRecursive,
diffIndex, diffIndex,
diffWorkTree, diffWorkTree,
diffLog,
) where ) where
import Numeric import Numeric
@ -33,6 +35,13 @@ data DiffTreeItem = DiffTreeItem
, file :: TopFilePath , file :: TopFilePath
} deriving Show } deriving Show
{- Checks if the DiffTreeItem modifies a file with a given name
- or under a directory by that name. -}
isDiffOf :: DiffTreeItem -> TopFilePath -> Bool
isDiffOf diff f = case getTopFilePath f of
"" -> True -- top of repo contains all
d -> d `dirContains` getTopFilePath (file diff)
{- Diffs two tree Refs. -} {- Diffs two tree Refs. -}
diffTree :: Ref -> Ref -> Repo -> IO ([DiffTreeItem], IO Bool) diffTree :: Ref -> Ref -> Repo -> IO ([DiffTreeItem], IO Bool)
diffTree src dst = getdiff (Param "diff-tree") diffTree src dst = getdiff (Param "diff-tree")
@ -66,16 +75,23 @@ diffIndex' ref params repo =
, return ([], return True) , return ([], return True)
) )
{- Runs git log in --raw mode to get the changes that were made in
- a particular commit. The output format is adjusted to be the same
- as diff-tree --raw._-}
diffLog :: [CommandParam] -> Repo -> IO ([DiffTreeItem], IO Bool)
diffLog params = getdiff (Param "log")
(Param "-n1" : Param "--abbrev=40" : Param "--pretty=format:" : params)
getdiff :: CommandParam -> [CommandParam] -> Repo -> IO ([DiffTreeItem], IO Bool) getdiff :: CommandParam -> [CommandParam] -> Repo -> IO ([DiffTreeItem], IO Bool)
getdiff command params repo = do getdiff command params repo = do
(diff, cleanup) <- pipeNullSplit ps repo (diff, cleanup) <- pipeNullSplit ps repo
return (parseDiffTree diff, cleanup) return (parseDiffRaw diff, cleanup)
where where
ps = command : Params "-z --raw --no-renames -l0" : params ps = command : Params "-z --raw --no-renames -l0" : params
{- Parses diff-tree output. -} {- Parses --raw output used by diff-tree and git-log. -}
parseDiffTree :: [String] -> [DiffTreeItem] parseDiffRaw :: [String] -> [DiffTreeItem]
parseDiffTree l = go l [] parseDiffRaw l = go l []
where where
go [] c = c go [] c = c
go (info:f:rest) c = go rest (mk info f : c) go (info:f:rest) c = go rest (mk info f : c)

View file

@ -19,7 +19,8 @@ module Git.UpdateIndex (
updateIndexLine, updateIndexLine,
stageFile, stageFile,
unstageFile, unstageFile,
stageSymlink stageSymlink,
stageDiffTreeItem,
) where ) where
import Common import Common
@ -28,6 +29,7 @@ import Git.Types
import Git.Command import Git.Command
import Git.FilePath import Git.FilePath
import Git.Sha import Git.Sha
import qualified Git.DiffTree as Diff
{- Streamers are passed a callback and should feed it lines in the form {- Streamers are passed a callback and should feed it lines in the form
- read by update-index, and generated by ls-tree. -} - read by update-index, and generated by ls-tree. -}
@ -95,7 +97,10 @@ stageFile sha filetype file repo = do
unstageFile :: FilePath -> Repo -> IO Streamer unstageFile :: FilePath -> Repo -> IO Streamer
unstageFile file repo = do unstageFile file repo = do
p <- toTopFilePath file repo p <- toTopFilePath file repo
return $ pureStreamer $ "0 " ++ fromRef nullSha ++ "\t" ++ indexPath p return $ unstageFile' p
unstageFile' :: TopFilePath -> Streamer
unstageFile' p = pureStreamer $ "0 " ++ fromRef nullSha ++ "\t" ++ indexPath p
{- A streamer that adds a symlink to the index. -} {- A streamer that adds a symlink to the index. -}
stageSymlink :: FilePath -> Sha -> Repo -> IO Streamer stageSymlink :: FilePath -> Sha -> Repo -> IO Streamer
@ -106,5 +111,11 @@ stageSymlink file sha repo = do
<*> toTopFilePath file repo <*> toTopFilePath file repo
return $ pureStreamer line return $ pureStreamer line
{- A streamer that applies a DiffTreeItem to the index. -}
stageDiffTreeItem :: Diff.DiffTreeItem -> Streamer
stageDiffTreeItem d = case toBlobType (Diff.dstmode d) of
Nothing -> unstageFile' (Diff.file d)
Just t -> pureStreamer $ updateIndexLine (Diff.dstsha d) t (Diff.file d)
indexPath :: TopFilePath -> InternalGitPath indexPath :: TopFilePath -> InternalGitPath
indexPath = toInternalGitPath . getTopFilePath indexPath = toInternalGitPath . getTopFilePath

2
debian/changelog vendored
View file

@ -6,6 +6,8 @@ git-annex (5.20141126) UNRELEASED; urgency=medium
* proxy: New command for direct mode repositories, allows bypassing * proxy: New command for direct mode repositories, allows bypassing
the direct mode guard in a safe way to do all sorts of things the direct mode guard in a safe way to do all sorts of things
including git revert, git mv, git checkout ... including git revert, git mv, git checkout ...
* undo: New command to undo the most recent change to a file
or to the contents of a directory.
* Work around behavior change in lsof 4.88's -F output format. * Work around behavior change in lsof 4.88's -F output format.
* Debian package is now maintained by Gergely Nagy. * Debian package is now maintained by Gergely Nagy.

View file

@ -282,6 +282,22 @@ subdirectories).
are on a video hosting site, and the video is downloaded. This allows are on a video hosting site, and the video is downloaded. This allows
importing e.g., youtube playlists. importing e.g., youtube playlists.
* `undo [filename|directory] ...`
When passed a filename, undoes the last change that was made to that
file.
When passed a directory, undoes the last change that was made to the
contents of that directory.
Running undo a second time will undo the undo, returning the working
tree to the same state it had before. In order for undoing an undo of
staged changes, any staged changes are first committed by the
undo command.
Note that this does not undo get/drop of a file's content; it only
operates on the file tree committed to git.
* `watch` * `watch`
Watches for changes to files in the current directory and its subdirectories, Watches for changes to files in the current directory and its subdirectories,

View file

@ -80,5 +80,7 @@ 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 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. that touched files in that directory, and undo the changes to those files.
> [[done]] --[[Joey]]
Also, --depth could make undo look for an older commit than the most Also, --depth could make undo look for an older commit than the most
recent one to affect the specified file. recent one to affect the specified file.