diff options
author | Joey Hess <joey@kitenet.net> | 2014-02-19 14:14:44 -0400 |
---|---|---|
committer | Joey Hess <joey@kitenet.net> | 2014-02-19 14:17:58 -0400 |
commit | ade0216bd08c2f9c1ed10c1c2274fd5b071c9c93 (patch) | |
tree | 6097def98c91ec5a0b6b9a694ab5b4b4137063c0 | |
parent | 8d0b3f09a9effc71f729d5b820076c642b605eb4 (diff) |
pre-commit: Update metadata when committing changes to annexed files within a view.
So the user can now switch to a view and then move files around within it
to manage metadata. For example, moving a file into a new directory
when in the tags=* view adds a tag to it.
Implementation is fairly efficient. One diff-index, which is no more
expensive than the first stage of a git commit, followed by possibly
some cat-file --batch traffic to find the key (when deleting a file).
Very similar to what's done in direct mode when committing. And like
direct mode when updating the WC after a merge, it has to buffer the
diff-tree values in order to make 2 passes over them.
When not in a view, pre-commit now does one extra git symbolic-ref,
which is tiny overhead.
This commit was sponsored by Andrew Eskridge.
-rw-r--r-- | Annex/View.hs | 48 | ||||
-rw-r--r-- | Command/PreCommit.hs | 47 | ||||
-rw-r--r-- | Logs/MetaData.hs | 12 | ||||
-rw-r--r-- | Types/MetaData.hs | 6 | ||||
-rw-r--r-- | debian/changelog | 2 | ||||
-rw-r--r-- | doc/git-annex.mdwn | 11 |
6 files changed, 103 insertions, 23 deletions
diff --git a/Annex/View.hs b/Annex/View.hs index 6db31ce92..abf8f073e 100644 --- a/Annex/View.hs +++ b/Annex/View.hs @@ -13,19 +13,25 @@ import Common.Annex import Types.View import Types.MetaData import qualified Git -import qualified Git.DiffTree +import qualified Git.DiffTree as DiffTree import qualified Git.Branch import qualified Git.LsFiles +import qualified Git.Ref import Git.UpdateIndex import Git.Sha import Git.HashObject import Git.Types +import Git.FilePath import qualified Backend import Annex.Index import Annex.Link +import Annex.CatFile import Logs.MetaData import Logs.View import Utility.FileMode +import Types.Command +import Config +import CmdLine.Action import qualified Data.Set as S import System.Path.WildMatch @@ -337,14 +343,50 @@ applyView' mkfileview view = do -} updateView :: View -> Git.Ref -> Git.Ref -> Annex Git.Branch updateView view ref oldref = genViewBranch view $ do - (diffs, cleanup) <- inRepo $ Git.DiffTree.diffTree oldref ref + (diffs, cleanup) <- inRepo $ DiffTree.diffTree oldref ref forM_ diffs go void $ liftIO cleanup where go diff - | Git.DiffTree.dstsha diff == nullSha = error "TODO delete file" + | DiffTree.dstsha diff == nullSha = error "TODO delete file" | otherwise = error "TODO add file" +{- Diff between currently checked out branch and staged changes, and + - update metadata to reflect the changes that are being committed to the + - view. + - + - Adding a file to a directory adds the metadata represented by + - that directory to the file, and removing a file from a directory + - removes the metadata. + - + - Note that removes must be handled before adds. This is so + - that moving a file from x/foo/ to x/bar/ adds back the metadata for x. + -} +withViewChanges :: (FileView -> Key -> CommandStart) -> (FileView -> Key -> CommandStart) -> Annex () +withViewChanges addmeta removemeta = do + makeabs <- flip fromTopFilePath <$> gitRepo + (diffs, cleanup) <- inRepo $ DiffTree.diffIndex Git.Ref.headRef + forM_ diffs handleremovals + forM_ diffs (handleadds makeabs) + void $ liftIO cleanup + where + handleremovals item + | DiffTree.srcsha item /= nullSha = + handle item removemeta + =<< catKey (DiffTree.srcsha item) (DiffTree.srcmode item) + | otherwise = noop + handleadds makeabs item + | DiffTree.dstsha item /= nullSha = + handle item addmeta + =<< ifM isDirect + ( catKey (DiffTree.dstsha item) (DiffTree.dstmode item) + -- optimisation + , isAnnexLink $ makeabs $ DiffTree.file item + ) + | otherwise = noop + handle item a = maybe noop + (void . commandAction . a (getTopFilePath $ DiffTree.file item)) + {- Generates a branch for a view. This is done using a different index - file. An action is run to stage the files that will be in the branch. - Then a commit is made, to the view branch. The view branch is not diff --git a/Command/PreCommit.hs b/Command/PreCommit.hs index 388d065c0..4b90b5c2e 100644 --- a/Command/PreCommit.hs +++ b/Command/PreCommit.hs @@ -1,6 +1,6 @@ {- git-annex command - - - Copyright 2010, 2013 Joey Hess <joey@kitenet.net> + - Copyright 2010-2014 Joey Hess <joey@kitenet.net> - - Licensed under the GNU GPL version 3 or higher. -} @@ -13,6 +13,13 @@ import Config import qualified Command.Add import qualified Command.Fix import Annex.Direct +import Annex.View +import Logs.View +import Logs.MetaData +import Types.View +import Types.MetaData + +import qualified Data.Set as S def :: [Command] def = [command "pre-commit" paramPaths seek SectionPlumbing @@ -27,13 +34,45 @@ seek ps = ifM isDirect withFilesToBeCommitted (whenAnnexed Command.Fix.start) ps -- inject unlocked files into the annex withFilesUnlockedToBeCommitted startIndirect ps + -- committing changes to a view updates metadata + mv <- currentView + case mv of + Nothing -> noop + Just v -> withViewChanges + (addViewMetaData v) + (removeViewMetaData v) ) startIndirect :: FilePath -> CommandStart -startIndirect file = next $ do - unlessM (callCommandAction $ Command.Add.start file) $ - error $ "failed to add " ++ file ++ "; canceling commit" +startIndirect f = next $ do + unlessM (callCommandAction $ Command.Add.start f) $ + error $ "failed to add " ++ f ++ "; canceling commit" next $ return True startDirect :: [String] -> CommandStart startDirect _ = next $ next $ preCommitDirect + +addViewMetaData :: View -> FileView -> Key -> CommandStart +addViewMetaData v f k = do + showStart "metadata" f + next $ next $ changeMetaData k $ fromView v f + +removeViewMetaData :: View -> FileView -> Key -> CommandStart +removeViewMetaData v f k = do + showStart "metadata" f + next $ next $ changeMetaData k $ unsetMetaData $ fromView v f + +changeMetaData :: Key -> MetaData -> CommandCleanup +changeMetaData k metadata = do + showMetaDataChange metadata + addMetaData k metadata + return True + +showMetaDataChange :: MetaData -> Annex () +showMetaDataChange = showLongNote . unlines . concatMap showmeta . fromMetaData + where + showmeta (f, vs) = map (showmetavalue f) $ S.toList vs + showmetavalue f v = fromMetaField f ++ showset v ++ "=" ++ fromMetaValue v + showset v + | isSet v = "+" + | otherwise = "-" diff --git a/Logs/MetaData.hs b/Logs/MetaData.hs index a959743df..807b50afa 100644 --- a/Logs/MetaData.hs +++ b/Logs/MetaData.hs @@ -28,8 +28,6 @@ module Logs.MetaData ( getCurrentMetaData, getMetaData, - setMetaData, - unsetMetaData, addMetaData, addMetaData', currentMetaData, @@ -58,16 +56,6 @@ getCurrentMetaData = currentMetaData . collect <$$> getMetaData where collect = foldl' unionMetaData newMetaData . map value . S.toAscList -setMetaData :: Key -> MetaField -> String -> Annex () -setMetaData = setMetaData' True - -unsetMetaData :: Key -> MetaField -> String -> Annex () -unsetMetaData = setMetaData' False - -setMetaData' :: Bool -> Key -> MetaField -> String -> Annex () -setMetaData' isset k field s = addMetaData k $ - updateMetaData field (mkMetaValue (CurrentlySet isset) s) newMetaData - {- Adds in some metadata, which can override existing values, or unset - them, but otherwise leaves any existing metadata as-is. -} addMetaData :: Key -> MetaData -> Annex () diff --git a/Types/MetaData.hs b/Types/MetaData.hs index 601757315..617c122a6 100644 --- a/Types/MetaData.hs +++ b/Types/MetaData.hs @@ -22,12 +22,14 @@ module Types.MetaData ( toMetaValue, mkMetaValue, unsetMetaValue, + unsetMetaData, fromMetaValue, fromMetaData, newMetaData, updateMetaData, unionMetaData, differenceMetaData, + isSet, currentMetaData, currentMetaDataValues, metaDataValues, @@ -137,6 +139,10 @@ mkMetaValue = MetaValue unsetMetaValue :: MetaValue -> MetaValue unsetMetaValue (MetaValue _ s) = MetaValue (CurrentlySet False) s +{- Marks all MetaValues as no longer currently set. -} +unsetMetaData :: MetaData -> MetaData +unsetMetaData (MetaData m) = MetaData $ M.map (S.map unsetMetaValue) m + fromMetaField :: MetaField -> String fromMetaField (MetaField f) = f diff --git a/debian/changelog b/debian/changelog index f1afdb821..36ed0b4d5 100644 --- a/debian/changelog +++ b/debian/changelog @@ -8,6 +8,8 @@ git-annex (5.20140211) UNRELEASED; urgency=medium * view: New command that creates and checks out a branch that provides a structured view of selected metadata. * vadd, vpop, vcycle: New commands for operating within views. + * pre-commit: Update metadata when committing changes to annexed files + within a view. * Add progress display for transfers to/from external special remotes. * Windows webapp: Can set up box.com, Amazon S3, and rsync.net remotes * Windows webapp: Can create repos on removable drives. diff --git a/doc/git-annex.mdwn b/doc/git-annex.mdwn index a5f73ac8e..cdd59ef9c 100644 --- a/doc/git-annex.mdwn +++ b/doc/git-annex.mdwn @@ -321,10 +321,12 @@ subdirectories). shown in the view. Multiple values for a metadata field can be specified, either by using - a glob (field="\*") or by listing each wanted value. + a glob (field="\*") or by listing each wanted value. The resulting view + will put files in subdirectories according to the value of their fields. - When multiple field values match, the view branch will have a - subdirectory for each value. + Once within a view, you can make additional subdirectories, and + copy or move files into them. When you commit, the metadata will + be updated to correspond to your changes. * `vpop [N]` @@ -801,7 +803,8 @@ subdirectories). Fixes up symlinks that are staged as part of a commit, to ensure they point to annexed content. Also handles injecting changes to unlocked - files into the annex. + files into the annex. When in a view, updates metadata to reflect changes + made to files in the view. * `lookupkey [file ...]` |