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.
This commit is contained in:
Joey Hess 2014-02-19 14:14:44 -04:00
parent 02259d2a55
commit 39ebfa1a2e
6 changed files with 103 additions and 23 deletions

View file

@ -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

View file

@ -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 = "-"

View file

@ -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 ()

View file

@ -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

2
debian/changelog vendored
View file

@ -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.

View file

@ -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 ...]`