2014-02-16 21:39:54 +00:00
|
|
|
|
{- metadata based branch views
|
|
|
|
|
-
|
2015-01-21 16:50:09 +00:00
|
|
|
|
- Copyright 2014 Joey Hess <id@joeyh.name>
|
2014-02-16 21:39:54 +00:00
|
|
|
|
-
|
2019-03-13 19:48:14 +00:00
|
|
|
|
- Licensed under the GNU AGPL version 3 or higher.
|
2014-02-16 21:39:54 +00:00
|
|
|
|
-}
|
|
|
|
|
|
2019-12-09 17:49:05 +00:00
|
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
|
|
2014-02-16 21:39:54 +00:00
|
|
|
|
module Annex.View where
|
|
|
|
|
|
2016-01-20 20:36:33 +00:00
|
|
|
|
import Annex.Common
|
2014-02-22 17:35:50 +00:00
|
|
|
|
import Annex.View.ViewedFile
|
2014-02-17 04:18:57 +00:00
|
|
|
|
import Types.View
|
2014-02-16 21:39:54 +00:00
|
|
|
|
import Types.MetaData
|
2014-03-02 18:53:19 +00:00
|
|
|
|
import Annex.MetaData
|
2019-11-11 22:20:35 +00:00
|
|
|
|
import qualified Annex
|
2014-02-18 21:38:23 +00:00
|
|
|
|
import qualified Git
|
2014-02-19 18:14:44 +00:00
|
|
|
|
import qualified Git.DiffTree as DiffTree
|
2014-02-16 21:39:54 +00:00
|
|
|
|
import qualified Git.Branch
|
2014-02-18 21:38:23 +00:00
|
|
|
|
import qualified Git.LsFiles
|
2014-02-19 18:14:44 +00:00
|
|
|
|
import qualified Git.Ref
|
2014-02-18 21:38:23 +00:00
|
|
|
|
import Git.UpdateIndex
|
|
|
|
|
import Git.Sha
|
2014-02-19 00:32:00 +00:00
|
|
|
|
import Git.Types
|
2014-02-19 18:14:44 +00:00
|
|
|
|
import Git.FilePath
|
2015-12-15 19:34:28 +00:00
|
|
|
|
import Annex.WorkTree
|
2016-04-06 19:33:29 +00:00
|
|
|
|
import Annex.GitOverlay
|
2014-02-18 21:38:23 +00:00
|
|
|
|
import Annex.Link
|
2014-02-19 18:14:44 +00:00
|
|
|
|
import Annex.CatFile
|
2014-02-18 21:38:23 +00:00
|
|
|
|
import Logs.MetaData
|
|
|
|
|
import Logs.View
|
2014-02-21 22:34:34 +00:00
|
|
|
|
import Utility.Glob
|
2014-02-19 18:14:44 +00:00
|
|
|
|
import Types.Command
|
|
|
|
|
import CmdLine.Action
|
2014-02-16 21:39:54 +00:00
|
|
|
|
|
2019-01-07 19:51:05 +00:00
|
|
|
|
import qualified Data.Text as T
|
|
|
|
|
import qualified Data.ByteString as B
|
2014-02-16 21:39:54 +00:00
|
|
|
|
import qualified Data.Set as S
|
2014-02-22 20:09:00 +00:00
|
|
|
|
import qualified Data.Map as M
|
2014-02-17 02:44:28 +00:00
|
|
|
|
import "mtl" Control.Monad.Writer
|
2014-02-16 21:39:54 +00:00
|
|
|
|
|
add tip about metadata driven views (and more flexible view filtering)
While writing this documentation, I realized that there needed to be a way
to stay in a view like tag=* while adding a filter like tag=work that
applies to the same field.
So, there are really two ways a view can be refined. It can have a new
"field=explicitvalue" filter added to it, which does not change the
"shape" of the view, but narrows the files it shows.
Or, it can have a new view added, which adds another level of
subdirectories.
So, added a vfilter command, which takes explicit values to add to the
filter, and rejects changes that would change the shape of the view.
And, made vadd only accept changes that change the shape of the view.
And, changed the View data type slightly; now components that can match
multiple metadata values can be visible, or not visible.
This commit was sponsored by Stelian Iancu.
2014-02-19 19:10:18 +00:00
|
|
|
|
{- Each visible ViewFilter in a view results in another level of
|
2014-02-19 05:28:48 +00:00
|
|
|
|
- subdirectory nesting. When a file matches multiple ways, it will appear
|
|
|
|
|
- in multiple subdirectories. This means there is a bit of an exponential
|
|
|
|
|
- blowup with a single file appearing in a crazy number of places!
|
|
|
|
|
-
|
|
|
|
|
- Capping the view size to 5 is reasonable; why wants to dig
|
|
|
|
|
- through 5+ levels of subdirectories to find anything?
|
|
|
|
|
-}
|
|
|
|
|
viewTooLarge :: View -> Bool
|
|
|
|
|
viewTooLarge view = visibleViewSize view > 5
|
|
|
|
|
|
|
|
|
|
visibleViewSize :: View -> Int
|
add tip about metadata driven views (and more flexible view filtering)
While writing this documentation, I realized that there needed to be a way
to stay in a view like tag=* while adding a filter like tag=work that
applies to the same field.
So, there are really two ways a view can be refined. It can have a new
"field=explicitvalue" filter added to it, which does not change the
"shape" of the view, but narrows the files it shows.
Or, it can have a new view added, which adds another level of
subdirectories.
So, added a vfilter command, which takes explicit values to add to the
filter, and rejects changes that would change the shape of the view.
And, made vadd only accept changes that change the shape of the view.
And, changed the View data type slightly; now components that can match
multiple metadata values can be visible, or not visible.
This commit was sponsored by Stelian Iancu.
2014-02-19 19:10:18 +00:00
|
|
|
|
visibleViewSize = length . filter viewVisible . viewComponents
|
2014-02-19 05:28:48 +00:00
|
|
|
|
|
2014-03-02 18:53:19 +00:00
|
|
|
|
{- Parses field=value, field!=value, tag, and !tag
|
|
|
|
|
-
|
|
|
|
|
- Note that the field may not be a legal metadata field name,
|
|
|
|
|
- but it's let through anyway.
|
|
|
|
|
- This is useful when matching on directory names with spaces,
|
|
|
|
|
- which are not legal MetaFields.
|
|
|
|
|
-}
|
|
|
|
|
parseViewParam :: String -> (MetaField, ViewFilter)
|
|
|
|
|
parseViewParam s = case separate (== '=') s of
|
|
|
|
|
('!':tag, []) | not (null tag) ->
|
|
|
|
|
( tagMetaField
|
|
|
|
|
, mkExcludeValues tag
|
|
|
|
|
)
|
|
|
|
|
(tag, []) ->
|
|
|
|
|
( tagMetaField
|
|
|
|
|
, mkFilterValues tag
|
|
|
|
|
)
|
|
|
|
|
(field, wanted)
|
|
|
|
|
| end field == "!" ->
|
2019-01-07 19:51:05 +00:00
|
|
|
|
( mkMetaFieldUnchecked (T.pack (beginning field))
|
2014-03-02 18:53:19 +00:00
|
|
|
|
, mkExcludeValues wanted
|
|
|
|
|
)
|
|
|
|
|
| otherwise ->
|
2019-01-07 19:51:05 +00:00
|
|
|
|
( mkMetaFieldUnchecked (T.pack field)
|
2014-03-02 18:53:19 +00:00
|
|
|
|
, mkFilterValues wanted
|
|
|
|
|
)
|
|
|
|
|
where
|
|
|
|
|
mkFilterValues v
|
2019-12-09 17:49:05 +00:00
|
|
|
|
| any (`elem` v) ['*', '?'] = FilterGlob v
|
2019-01-07 19:51:05 +00:00
|
|
|
|
| otherwise = FilterValues $ S.singleton $ toMetaValue $ encodeBS v
|
|
|
|
|
mkExcludeValues = ExcludeValues . S.singleton . toMetaValue . encodeBS
|
2014-03-02 18:53:19 +00:00
|
|
|
|
|
add tip about metadata driven views (and more flexible view filtering)
While writing this documentation, I realized that there needed to be a way
to stay in a view like tag=* while adding a filter like tag=work that
applies to the same field.
So, there are really two ways a view can be refined. It can have a new
"field=explicitvalue" filter added to it, which does not change the
"shape" of the view, but narrows the files it shows.
Or, it can have a new view added, which adds another level of
subdirectories.
So, added a vfilter command, which takes explicit values to add to the
filter, and rejects changes that would change the shape of the view.
And, made vadd only accept changes that change the shape of the view.
And, changed the View data type slightly; now components that can match
multiple metadata values can be visible, or not visible.
This commit was sponsored by Stelian Iancu.
2014-02-19 19:10:18 +00:00
|
|
|
|
data ViewChange = Unchanged | Narrowing | Widening
|
|
|
|
|
deriving (Ord, Eq, Show)
|
|
|
|
|
|
|
|
|
|
{- Updates a view, adding new fields to filter on (Narrowing),
|
|
|
|
|
- or allowing new values in an existing field (Widening). -}
|
2014-03-02 18:53:19 +00:00
|
|
|
|
refineView :: View -> [(MetaField, ViewFilter)] -> (View, ViewChange)
|
2014-03-02 19:36:45 +00:00
|
|
|
|
refineView origview = checksize . calc Unchanged origview
|
add tip about metadata driven views (and more flexible view filtering)
While writing this documentation, I realized that there needed to be a way
to stay in a view like tag=* while adding a filter like tag=work that
applies to the same field.
So, there are really two ways a view can be refined. It can have a new
"field=explicitvalue" filter added to it, which does not change the
"shape" of the view, but narrows the files it shows.
Or, it can have a new view added, which adds another level of
subdirectories.
So, added a vfilter command, which takes explicit values to add to the
filter, and rejects changes that would change the shape of the view.
And, made vadd only accept changes that change the shape of the view.
And, changed the View data type slightly; now components that can match
multiple metadata values can be visible, or not visible.
This commit was sponsored by Stelian Iancu.
2014-02-19 19:10:18 +00:00
|
|
|
|
where
|
2014-03-02 19:36:45 +00:00
|
|
|
|
calc c v [] = (v, c)
|
|
|
|
|
calc c v ((f, vf):rest) =
|
|
|
|
|
let (v', c') = refine v f vf
|
|
|
|
|
in calc (max c c') v' rest
|
|
|
|
|
|
|
|
|
|
refine view field vf
|
|
|
|
|
| field `elem` map viewField (viewComponents view) =
|
|
|
|
|
let (components', viewchanges) = runWriter $
|
|
|
|
|
mapM (\c -> updateViewComponent c field vf) (viewComponents view)
|
|
|
|
|
viewchange = if field `elem` map viewField (viewComponents origview)
|
2014-10-09 18:53:13 +00:00
|
|
|
|
then maximum viewchanges
|
2014-03-02 19:36:45 +00:00
|
|
|
|
else Narrowing
|
|
|
|
|
in (view { viewComponents = components' }, viewchange)
|
|
|
|
|
| otherwise =
|
|
|
|
|
let component = mkViewComponent field vf
|
|
|
|
|
view' = view { viewComponents = component : viewComponents view }
|
|
|
|
|
in (view', Narrowing)
|
|
|
|
|
|
|
|
|
|
checksize r@(v, _)
|
2016-11-16 01:29:54 +00:00
|
|
|
|
| viewTooLarge v = giveup $ "View is too large (" ++ show (visibleViewSize v) ++ " levels of subdirectories)"
|
2014-03-02 19:36:45 +00:00
|
|
|
|
| otherwise = r
|
|
|
|
|
|
|
|
|
|
updateViewComponent :: ViewComponent -> MetaField -> ViewFilter -> Writer [ViewChange] ViewComponent
|
|
|
|
|
updateViewComponent c field vf
|
|
|
|
|
| viewField c == field = do
|
|
|
|
|
let (newvf, viewchange) = combineViewFilter (viewFilter c) vf
|
|
|
|
|
tell [viewchange]
|
|
|
|
|
return $ mkViewComponent field newvf
|
|
|
|
|
| otherwise = return c
|
add tip about metadata driven views (and more flexible view filtering)
While writing this documentation, I realized that there needed to be a way
to stay in a view like tag=* while adding a filter like tag=work that
applies to the same field.
So, there are really two ways a view can be refined. It can have a new
"field=explicitvalue" filter added to it, which does not change the
"shape" of the view, but narrows the files it shows.
Or, it can have a new view added, which adds another level of
subdirectories.
So, added a vfilter command, which takes explicit values to add to the
filter, and rejects changes that would change the shape of the view.
And, made vadd only accept changes that change the shape of the view.
And, changed the View data type slightly; now components that can match
multiple metadata values can be visible, or not visible.
This commit was sponsored by Stelian Iancu.
2014-02-19 19:10:18 +00:00
|
|
|
|
|
|
|
|
|
{- Adds an additional filter to a view. This can only result in narrowing
|
|
|
|
|
- the view. Multivalued filters are added in non-visible form. -}
|
2014-03-02 18:53:19 +00:00
|
|
|
|
filterView :: View -> [(MetaField, ViewFilter)] -> View
|
add tip about metadata driven views (and more flexible view filtering)
While writing this documentation, I realized that there needed to be a way
to stay in a view like tag=* while adding a filter like tag=work that
applies to the same field.
So, there are really two ways a view can be refined. It can have a new
"field=explicitvalue" filter added to it, which does not change the
"shape" of the view, but narrows the files it shows.
Or, it can have a new view added, which adds another level of
subdirectories.
So, added a vfilter command, which takes explicit values to add to the
filter, and rejects changes that would change the shape of the view.
And, made vadd only accept changes that change the shape of the view.
And, changed the View data type slightly; now components that can match
multiple metadata values can be visible, or not visible.
This commit was sponsored by Stelian Iancu.
2014-02-19 19:10:18 +00:00
|
|
|
|
filterView v vs = v { viewComponents = viewComponents f' ++ viewComponents v}
|
|
|
|
|
where
|
|
|
|
|
f = fst $ refineView (v {viewComponents = []}) vs
|
|
|
|
|
f' = f { viewComponents = map toinvisible (viewComponents f) }
|
|
|
|
|
toinvisible c = c { viewVisible = False }
|
|
|
|
|
|
2014-02-19 06:27:58 +00:00
|
|
|
|
{- Combine old and new ViewFilters, yielding a result that matches
|
2014-02-17 02:44:28 +00:00
|
|
|
|
- either old+new, or only new.
|
|
|
|
|
-
|
|
|
|
|
- If we have FilterValues and change to a FilterGlob,
|
|
|
|
|
- it's always a widening change, because the glob could match other
|
|
|
|
|
- values. OTOH, going the other way, it's a Narrowing change if the old
|
|
|
|
|
- glob matches all the new FilterValues.
|
|
|
|
|
-
|
|
|
|
|
- With two globs, the old one is discarded, and the new one is used.
|
|
|
|
|
- We can tell if that's a narrowing change by checking if the old
|
|
|
|
|
- glob matches the new glob. For example, "*" matches "foo*",
|
|
|
|
|
- so that's narrowing. While "f?o" does not match "f??", so that's
|
|
|
|
|
- widening.
|
|
|
|
|
-}
|
|
|
|
|
combineViewFilter :: ViewFilter -> ViewFilter -> (ViewFilter, ViewChange)
|
|
|
|
|
combineViewFilter old@(FilterValues olds) (FilterValues news)
|
|
|
|
|
| combined == old = (combined, Unchanged)
|
|
|
|
|
| otherwise = (combined, Widening)
|
|
|
|
|
where
|
|
|
|
|
combined = FilterValues (S.union olds news)
|
2014-03-02 18:53:19 +00:00
|
|
|
|
combineViewFilter old@(ExcludeValues olds) (ExcludeValues news)
|
|
|
|
|
| combined == old = (combined, Unchanged)
|
|
|
|
|
| otherwise = (combined, Narrowing)
|
|
|
|
|
where
|
2014-03-02 19:44:14 +00:00
|
|
|
|
combined = ExcludeValues (S.union olds news)
|
2014-02-17 04:38:33 +00:00
|
|
|
|
combineViewFilter (FilterValues _) newglob@(FilterGlob _) =
|
2014-02-17 02:44:28 +00:00
|
|
|
|
(newglob, Widening)
|
|
|
|
|
combineViewFilter (FilterGlob oldglob) new@(FilterValues s)
|
2019-01-07 19:51:05 +00:00
|
|
|
|
| all (matchGlob (compileGlob oldglob CaseInsensative) . decodeBS . fromMetaValue) (S.toList s) = (new, Narrowing)
|
2014-02-17 02:44:28 +00:00
|
|
|
|
| otherwise = (new, Widening)
|
|
|
|
|
combineViewFilter (FilterGlob old) newglob@(FilterGlob new)
|
|
|
|
|
| old == new = (newglob, Unchanged)
|
2014-02-21 22:34:34 +00:00
|
|
|
|
| matchGlob (compileGlob old CaseInsensative) new = (newglob, Narrowing)
|
2014-02-17 02:44:28 +00:00
|
|
|
|
| otherwise = (newglob, Widening)
|
2014-03-02 18:53:19 +00:00
|
|
|
|
combineViewFilter (FilterGlob _) new@(ExcludeValues _) = (new, Narrowing)
|
|
|
|
|
combineViewFilter (ExcludeValues _) new@(FilterGlob _) = (new, Widening)
|
|
|
|
|
combineViewFilter (FilterValues _) new@(ExcludeValues _) = (new, Narrowing)
|
|
|
|
|
combineViewFilter (ExcludeValues _) new@(FilterValues _) = (new, Widening)
|
2014-02-17 02:44:28 +00:00
|
|
|
|
|
2014-02-16 21:39:54 +00:00
|
|
|
|
{- Generates views for a file from a branch, based on its metadata
|
|
|
|
|
- and the filename used in the branch.
|
|
|
|
|
-
|
|
|
|
|
- Note that a file may appear multiple times in a view, when it
|
|
|
|
|
- has multiple matching values for a MetaField used in the View.
|
2014-02-16 21:46:52 +00:00
|
|
|
|
-
|
|
|
|
|
- Of course if its MetaData does not match the View, it won't appear at
|
|
|
|
|
- all.
|
2014-02-19 06:27:58 +00:00
|
|
|
|
-
|
|
|
|
|
- Note that for efficiency, it's useful to partially
|
|
|
|
|
- evaluate this function with the view parameter and reuse
|
|
|
|
|
- the result. The globs in the view will then be compiled and memoized.
|
2014-02-16 21:39:54 +00:00
|
|
|
|
-}
|
2014-02-22 17:35:50 +00:00
|
|
|
|
viewedFiles :: View -> MkViewedFile -> FilePath -> MetaData -> [ViewedFile]
|
|
|
|
|
viewedFiles view =
|
2014-02-19 06:27:58 +00:00
|
|
|
|
let matchers = map viewComponentMatcher (viewComponents view)
|
2014-02-22 17:35:50 +00:00
|
|
|
|
in \mkviewedfile file metadata ->
|
2014-02-19 06:27:58 +00:00
|
|
|
|
let matches = map (\m -> m metadata) matchers
|
|
|
|
|
in if any isNothing matches
|
|
|
|
|
then []
|
|
|
|
|
else
|
|
|
|
|
let paths = pathProduct $
|
|
|
|
|
map (map toViewPath) (visible matches)
|
|
|
|
|
in if null paths
|
2014-02-22 17:35:50 +00:00
|
|
|
|
then [mkviewedfile file]
|
|
|
|
|
else map (</> mkviewedfile file) paths
|
2014-02-16 21:39:54 +00:00
|
|
|
|
where
|
|
|
|
|
visible = map (fromJust . snd) .
|
add tip about metadata driven views (and more flexible view filtering)
While writing this documentation, I realized that there needed to be a way
to stay in a view like tag=* while adding a filter like tag=work that
applies to the same field.
So, there are really two ways a view can be refined. It can have a new
"field=explicitvalue" filter added to it, which does not change the
"shape" of the view, but narrows the files it shows.
Or, it can have a new view added, which adds another level of
subdirectories.
So, added a vfilter command, which takes explicit values to add to the
filter, and rejects changes that would change the shape of the view.
And, made vadd only accept changes that change the shape of the view.
And, changed the View data type slightly; now components that can match
multiple metadata values can be visible, or not visible.
This commit was sponsored by Stelian Iancu.
2014-02-19 19:10:18 +00:00
|
|
|
|
filter (viewVisible . fst) .
|
|
|
|
|
zip (viewComponents view)
|
2014-02-16 21:39:54 +00:00
|
|
|
|
|
2014-02-19 06:27:58 +00:00
|
|
|
|
{- Checks if metadata matches a ViewComponent filter, and if so
|
|
|
|
|
- returns the value, or values that match. Self-memoizing on ViewComponent. -}
|
|
|
|
|
viewComponentMatcher :: ViewComponent -> (MetaData -> Maybe [MetaValue])
|
|
|
|
|
viewComponentMatcher viewcomponent = \metadata ->
|
2014-03-02 18:53:19 +00:00
|
|
|
|
matcher (currentMetaDataValues metafield metadata)
|
2014-02-19 06:27:58 +00:00
|
|
|
|
where
|
2014-10-09 18:53:13 +00:00
|
|
|
|
metafield = viewField viewcomponent
|
2014-02-19 06:27:58 +00:00
|
|
|
|
matcher = case viewFilter viewcomponent of
|
2014-03-02 18:53:19 +00:00
|
|
|
|
FilterValues s -> \values -> setmatches $
|
|
|
|
|
S.intersection s values
|
2014-02-19 06:27:58 +00:00
|
|
|
|
FilterGlob glob ->
|
2014-02-21 22:34:34 +00:00
|
|
|
|
let cglob = compileGlob glob CaseInsensative
|
2014-03-02 18:53:19 +00:00
|
|
|
|
in \values -> setmatches $
|
2019-01-07 19:51:05 +00:00
|
|
|
|
S.filter (matchGlob cglob . decodeBS . fromMetaValue) values
|
2014-03-02 18:53:19 +00:00
|
|
|
|
ExcludeValues excludes -> \values ->
|
|
|
|
|
if S.null (S.intersection values excludes)
|
|
|
|
|
then Just []
|
|
|
|
|
else Nothing
|
|
|
|
|
setmatches s
|
|
|
|
|
| S.null s = Nothing
|
|
|
|
|
| otherwise = Just (S.toList s)
|
2014-02-19 06:27:58 +00:00
|
|
|
|
|
2016-01-08 17:55:35 +00:00
|
|
|
|
-- This is '∕', a unicode character that displays the same as '/' but is
|
|
|
|
|
-- not it. It is encoded using the filesystem encoding, which allows it
|
|
|
|
|
-- to be used even when not in a unicode capable locale.
|
|
|
|
|
pseudoSlash :: String
|
|
|
|
|
pseudoSlash = "\56546\56456\56469"
|
|
|
|
|
|
|
|
|
|
-- And this is '╲' similarly.
|
|
|
|
|
pseudoBackslash :: String
|
|
|
|
|
pseudoBackslash = "\56546\56469\56498"
|
|
|
|
|
|
2014-02-17 01:00:12 +00:00
|
|
|
|
toViewPath :: MetaValue -> FilePath
|
2019-01-07 19:51:05 +00:00
|
|
|
|
toViewPath = escapeslash [] . decodeBS . fromMetaValue
|
2014-02-17 01:00:12 +00:00
|
|
|
|
where
|
2016-01-08 17:55:35 +00:00
|
|
|
|
escapeslash s ('/':cs) = escapeslash (pseudoSlash:s) cs
|
|
|
|
|
escapeslash s ('\\':cs) = escapeslash (pseudoBackslash:s) cs
|
|
|
|
|
escapeslash s ('%':cs) = escapeslash ("%%":s) cs
|
|
|
|
|
escapeslash s (c1:c2:c3:cs)
|
|
|
|
|
| [c1,c2,c3] == pseudoSlash = escapeslash ("%":pseudoSlash:s) cs
|
|
|
|
|
| [c1,c2,c3] == pseudoBackslash = escapeslash ("%":pseudoBackslash:s) cs
|
|
|
|
|
| otherwise = escapeslash ([c1]:s) (c2:c3:cs)
|
|
|
|
|
escapeslash s cs = concat (reverse (cs:s))
|
2014-02-17 01:00:12 +00:00
|
|
|
|
|
|
|
|
|
fromViewPath :: FilePath -> MetaValue
|
2019-01-07 19:51:05 +00:00
|
|
|
|
fromViewPath = toMetaValue . encodeBS . deescapeslash []
|
2014-02-17 01:00:12 +00:00
|
|
|
|
where
|
2016-01-08 17:55:35 +00:00
|
|
|
|
deescapeslash s ('%':escapedc:cs) = deescapeslash ([escapedc]:s) cs
|
|
|
|
|
deescapeslash s (c1:c2:c3:cs)
|
|
|
|
|
| [c1,c2,c3] == pseudoSlash = deescapeslash ("/":s) cs
|
|
|
|
|
| [c1,c2,c3] == pseudoBackslash = deescapeslash ("\\":s) cs
|
|
|
|
|
| otherwise = deescapeslash ([c1]:s) (c2:c3:cs)
|
|
|
|
|
deescapeslash s cs = concat (reverse (cs:s))
|
2014-02-17 01:00:12 +00:00
|
|
|
|
|
2016-01-08 16:45:32 +00:00
|
|
|
|
prop_viewPath_roundtrips :: MetaValue -> Bool
|
|
|
|
|
prop_viewPath_roundtrips v = fromViewPath (toViewPath v) == v
|
2014-02-17 01:00:12 +00:00
|
|
|
|
|
2014-02-16 21:39:54 +00:00
|
|
|
|
pathProduct :: [[FilePath]] -> [FilePath]
|
|
|
|
|
pathProduct [] = []
|
|
|
|
|
pathProduct (l:ls) = foldl combinel l ls
|
|
|
|
|
where
|
|
|
|
|
combinel xs ys = [combine x y | x <- xs, y <- ys]
|
|
|
|
|
|
2014-02-22 17:35:50 +00:00
|
|
|
|
{- Extracts the metadata from a ViewedFile, based on the view that was used
|
2014-02-22 22:16:28 +00:00
|
|
|
|
- to construct it.
|
|
|
|
|
-
|
|
|
|
|
- Derived metadata is excluded.
|
|
|
|
|
-}
|
2014-02-22 17:35:50 +00:00
|
|
|
|
fromView :: View -> ViewedFile -> MetaData
|
2014-02-22 22:16:28 +00:00
|
|
|
|
fromView view f = MetaData $
|
|
|
|
|
M.fromList (zip fields values) `M.difference` derived
|
2014-02-16 21:39:54 +00:00
|
|
|
|
where
|
add tip about metadata driven views (and more flexible view filtering)
While writing this documentation, I realized that there needed to be a way
to stay in a view like tag=* while adding a filter like tag=work that
applies to the same field.
So, there are really two ways a view can be refined. It can have a new
"field=explicitvalue" filter added to it, which does not change the
"shape" of the view, but narrows the files it shows.
Or, it can have a new view added, which adds another level of
subdirectories.
So, added a vfilter command, which takes explicit values to add to the
filter, and rejects changes that would change the shape of the view.
And, made vadd only accept changes that change the shape of the view.
And, changed the View data type slightly; now components that can match
multiple metadata values can be visible, or not visible.
This commit was sponsored by Stelian Iancu.
2014-02-19 19:10:18 +00:00
|
|
|
|
visible = filter viewVisible (viewComponents view)
|
2014-02-17 04:38:33 +00:00
|
|
|
|
fields = map viewField visible
|
2014-02-22 22:16:28 +00:00
|
|
|
|
paths = splitDirectories (dropFileName f)
|
|
|
|
|
values = map (S.singleton . fromViewPath) paths
|
|
|
|
|
MetaData derived = getViewedFileMetaData f
|
2014-02-17 01:00:12 +00:00
|
|
|
|
|
|
|
|
|
{- Constructing a view that will match arbitrary metadata, and applying
|
2014-02-22 17:35:50 +00:00
|
|
|
|
- it to a file yields a set of ViewedFile which all contain the same
|
2014-02-17 01:00:12 +00:00
|
|
|
|
- MetaFields that were present in the input metadata
|
add tip about metadata driven views (and more flexible view filtering)
While writing this documentation, I realized that there needed to be a way
to stay in a view like tag=* while adding a filter like tag=work that
applies to the same field.
So, there are really two ways a view can be refined. It can have a new
"field=explicitvalue" filter added to it, which does not change the
"shape" of the view, but narrows the files it shows.
Or, it can have a new view added, which adds another level of
subdirectories.
So, added a vfilter command, which takes explicit values to add to the
filter, and rejects changes that would change the shape of the view.
And, made vadd only accept changes that change the shape of the view.
And, changed the View data type slightly; now components that can match
multiple metadata values can be visible, or not visible.
This commit was sponsored by Stelian Iancu.
2014-02-19 19:10:18 +00:00
|
|
|
|
- (excluding fields that are not visible). -}
|
|
|
|
|
prop_view_roundtrips :: FilePath -> MetaData -> Bool -> Bool
|
2019-08-16 15:34:01 +00:00
|
|
|
|
prop_view_roundtrips f metadata visible = or
|
|
|
|
|
[ null f
|
|
|
|
|
, null (takeFileName f) && null (takeDirectory f)
|
|
|
|
|
, viewTooLarge view
|
|
|
|
|
, all hasfields (viewedFiles view viewedFileFromReference f metadata)
|
|
|
|
|
]
|
2014-02-17 01:00:12 +00:00
|
|
|
|
where
|
2014-02-18 21:38:23 +00:00
|
|
|
|
view = View (Git.Ref "master") $
|
2019-01-07 19:51:05 +00:00
|
|
|
|
map (\(mf, mv) -> ViewComponent mf (FilterValues $ S.filter (not . B.null . fromMetaValue) mv) visible)
|
2014-02-18 21:38:23 +00:00
|
|
|
|
(fromMetaData metadata)
|
add tip about metadata driven views (and more flexible view filtering)
While writing this documentation, I realized that there needed to be a way
to stay in a view like tag=* while adding a filter like tag=work that
applies to the same field.
So, there are really two ways a view can be refined. It can have a new
"field=explicitvalue" filter added to it, which does not change the
"shape" of the view, but narrows the files it shows.
Or, it can have a new view added, which adds another level of
subdirectories.
So, added a vfilter command, which takes explicit values to add to the
filter, and rejects changes that would change the shape of the view.
And, made vadd only accept changes that change the shape of the view.
And, changed the View data type slightly; now components that can match
multiple metadata values can be visible, or not visible.
This commit was sponsored by Stelian Iancu.
2014-02-19 19:10:18 +00:00
|
|
|
|
visiblefields = sort (map viewField $ filter viewVisible (viewComponents view))
|
2014-02-17 01:00:12 +00:00
|
|
|
|
hasfields fv = sort (map fst (fromMetaData (fromView view fv))) == visiblefields
|
2014-02-16 21:39:54 +00:00
|
|
|
|
|
2014-02-22 20:09:00 +00:00
|
|
|
|
{- A directory foo/bar/baz/ is turned into metadata fields
|
|
|
|
|
- /=foo, foo/=bar, foo/bar/=baz.
|
|
|
|
|
-
|
|
|
|
|
- Note that this may generate MetaFields that legalField rejects.
|
|
|
|
|
- This is necessary to have a 1:1 mapping between directory names and
|
|
|
|
|
- fields. So this MetaData cannot safely be serialized. -}
|
|
|
|
|
getDirMetaData :: FilePath -> MetaData
|
|
|
|
|
getDirMetaData d = MetaData $ M.fromList $ zip fields values
|
|
|
|
|
where
|
|
|
|
|
dirs = splitDirectories d
|
2019-01-07 19:51:05 +00:00
|
|
|
|
fields = map (mkMetaFieldUnchecked . T.pack . addTrailingPathSeparator . joinPath)
|
2014-02-22 20:09:00 +00:00
|
|
|
|
(inits dirs)
|
2019-01-07 19:51:05 +00:00
|
|
|
|
values = map (S.singleton . toMetaValue . encodeBS . fromMaybe "" . headMaybe)
|
2014-02-22 20:09:00 +00:00
|
|
|
|
(tails dirs)
|
|
|
|
|
|
|
|
|
|
getWorkTreeMetaData :: FilePath -> MetaData
|
|
|
|
|
getWorkTreeMetaData = getDirMetaData . dropFileName
|
|
|
|
|
|
|
|
|
|
getViewedFileMetaData :: FilePath -> MetaData
|
|
|
|
|
getViewedFileMetaData = getDirMetaData . dirFromViewedFile . takeFileName
|
|
|
|
|
|
2014-02-16 21:39:54 +00:00
|
|
|
|
{- Applies a view to the currently checked out branch, generating a new
|
|
|
|
|
- branch for the view.
|
|
|
|
|
-}
|
|
|
|
|
applyView :: View -> Annex Git.Branch
|
2015-04-11 04:10:34 +00:00
|
|
|
|
applyView = applyView' viewedFileFromReference getWorkTreeMetaData
|
2014-02-16 21:39:54 +00:00
|
|
|
|
|
2014-02-17 02:44:28 +00:00
|
|
|
|
{- Generates a new branch for a View, which must be a more narrow
|
2014-02-16 21:39:54 +00:00
|
|
|
|
- version of the View originally used to generate the currently
|
add tip about metadata driven views (and more flexible view filtering)
While writing this documentation, I realized that there needed to be a way
to stay in a view like tag=* while adding a filter like tag=work that
applies to the same field.
So, there are really two ways a view can be refined. It can have a new
"field=explicitvalue" filter added to it, which does not change the
"shape" of the view, but narrows the files it shows.
Or, it can have a new view added, which adds another level of
subdirectories.
So, added a vfilter command, which takes explicit values to add to the
filter, and rejects changes that would change the shape of the view.
And, made vadd only accept changes that change the shape of the view.
And, changed the View data type slightly; now components that can match
multiple metadata values can be visible, or not visible.
This commit was sponsored by Stelian Iancu.
2014-02-19 19:10:18 +00:00
|
|
|
|
- checked out branch. That is, it must match a subset of the files
|
|
|
|
|
- in view, not any others.
|
2014-02-16 21:39:54 +00:00
|
|
|
|
-}
|
2014-02-17 02:44:28 +00:00
|
|
|
|
narrowView :: View -> Annex Git.Branch
|
2014-02-22 20:09:00 +00:00
|
|
|
|
narrowView = applyView' viewedFileReuse getViewedFileMetaData
|
2014-02-16 21:39:54 +00:00
|
|
|
|
|
2018-05-14 18:58:13 +00:00
|
|
|
|
{- Go through each staged file.
|
|
|
|
|
- If the file is not annexed, skip it, unless it's a dotfile in the top,
|
|
|
|
|
- or a file in a dotdir in the top.
|
2014-02-22 17:35:50 +00:00
|
|
|
|
- Look up the metadata of annexed files, and generate any ViewedFiles,
|
2014-02-18 21:38:23 +00:00
|
|
|
|
- and stage them.
|
|
|
|
|
-
|
2015-12-26 18:45:07 +00:00
|
|
|
|
- Must be run from top of repository.
|
2014-02-16 21:39:54 +00:00
|
|
|
|
-}
|
2014-02-22 20:09:00 +00:00
|
|
|
|
applyView' :: MkViewedFile -> (FilePath -> MetaData) -> View -> Annex Git.Branch
|
|
|
|
|
applyView' mkviewedfile getfilemetadata view = do
|
2014-02-18 21:38:23 +00:00
|
|
|
|
top <- fromRepo Git.repoPath
|
2019-12-09 17:49:05 +00:00
|
|
|
|
(l, clean) <- inRepo $ Git.LsFiles.stagedDetails [top]
|
2014-02-19 00:01:44 +00:00
|
|
|
|
liftIO . nukeFile =<< fromRepo gitAnnexViewIndex
|
2020-06-04 16:05:25 +00:00
|
|
|
|
viewg <- withViewIndex gitRepo
|
|
|
|
|
withUpdateIndex viewg $ \uh -> do
|
|
|
|
|
forM_ l $ \(f, sha, mode) -> do
|
|
|
|
|
topf <- inRepo (toTopFilePath f)
|
|
|
|
|
go uh topf sha (toTreeItemType =<< mode) =<< lookupFile f
|
|
|
|
|
liftIO $ void clean
|
|
|
|
|
genViewBranch view
|
2014-02-18 21:38:23 +00:00
|
|
|
|
where
|
2014-02-22 17:35:50 +00:00
|
|
|
|
genviewedfiles = viewedFiles view mkviewedfile -- enables memoization
|
2018-05-14 18:58:13 +00:00
|
|
|
|
|
|
|
|
|
go uh topf _sha _mode (Just k) = do
|
2014-02-18 21:38:23 +00:00
|
|
|
|
metadata <- getCurrentMetaData k
|
2019-12-09 17:49:05 +00:00
|
|
|
|
let f = fromRawFilePath $ getTopFilePath topf
|
2014-02-23 04:08:29 +00:00
|
|
|
|
let metadata' = getfilemetadata f `unionMetaData` metadata
|
2014-02-22 20:09:00 +00:00
|
|
|
|
forM_ (genviewedfiles f metadata') $ \fv -> do
|
2019-12-09 17:49:05 +00:00
|
|
|
|
f' <- fromRawFilePath <$>
|
|
|
|
|
fromRepo (fromTopFilePath $ asTopFilePath $ toRawFilePath fv)
|
2016-03-14 19:58:46 +00:00
|
|
|
|
stagesymlink uh f' =<< calcRepo (gitAnnexLink f' k)
|
2018-05-14 18:58:13 +00:00
|
|
|
|
go uh topf (Just sha) (Just treeitemtype) Nothing
|
2019-12-09 17:49:05 +00:00
|
|
|
|
| "." `B.isPrefixOf` getTopFilePath topf =
|
2018-05-14 18:58:13 +00:00
|
|
|
|
liftIO $ Git.UpdateIndex.streamUpdateIndex' uh $
|
|
|
|
|
pureStreamer $ updateIndexLine sha treeitemtype topf
|
|
|
|
|
go _ _ _ _ _ = noop
|
|
|
|
|
|
2016-03-14 19:58:46 +00:00
|
|
|
|
stagesymlink uh f linktarget = do
|
|
|
|
|
sha <- hashSymlink linktarget
|
2014-02-19 00:32:00 +00:00
|
|
|
|
liftIO . Git.UpdateIndex.streamUpdateIndex' uh
|
|
|
|
|
=<< inRepo (Git.UpdateIndex.stageSymlink f sha)
|
2014-02-16 21:39:54 +00:00
|
|
|
|
|
2014-02-19 18:14:44 +00:00
|
|
|
|
{- 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.
|
|
|
|
|
-}
|
2014-02-22 17:35:50 +00:00
|
|
|
|
withViewChanges :: (ViewedFile -> Key -> CommandStart) -> (ViewedFile -> Key -> CommandStart) -> Annex ()
|
2014-02-19 18:14:44 +00:00
|
|
|
|
withViewChanges addmeta removemeta = do
|
|
|
|
|
(diffs, cleanup) <- inRepo $ DiffTree.diffIndex Git.Ref.headRef
|
|
|
|
|
forM_ diffs handleremovals
|
2015-12-26 19:09:47 +00:00
|
|
|
|
forM_ diffs handleadds
|
2014-02-19 18:14:44 +00:00
|
|
|
|
void $ liftIO cleanup
|
|
|
|
|
where
|
|
|
|
|
handleremovals item
|
2020-01-07 15:35:17 +00:00
|
|
|
|
| DiffTree.srcsha item `notElem` nullShas =
|
unify exception handling into Utility.Exception
Removed old extensible-exceptions, only needed for very old ghc.
Made webdav use Utility.Exception, to work after some changes in DAV's
exception handling.
Removed Annex.Exception. Mostly this was trivial, but note that
tryAnnex is replaced with tryNonAsync and catchAnnex replaced with
catchNonAsync. In theory that could be a behavior change, since the former
caught all exceptions, and the latter don't catch async exceptions.
However, in practice, nothing in the Annex monad uses async exceptions.
Grepping for throwTo and killThread only find stuff in the assistant,
which does not seem related.
Command.Add.undo is changed to accept a SomeException, and things
that use it for rollback now catch non-async exceptions, rather than
only IOExceptions.
2014-08-08 01:55:44 +00:00
|
|
|
|
handlechange item removemeta
|
2015-12-07 19:22:01 +00:00
|
|
|
|
=<< catKey (DiffTree.srcsha item)
|
2014-02-19 18:14:44 +00:00
|
|
|
|
| otherwise = noop
|
2015-12-26 19:09:47 +00:00
|
|
|
|
handleadds item
|
2020-01-07 15:35:17 +00:00
|
|
|
|
| DiffTree.dstsha item `notElem` nullShas =
|
unify exception handling into Utility.Exception
Removed old extensible-exceptions, only needed for very old ghc.
Made webdav use Utility.Exception, to work after some changes in DAV's
exception handling.
Removed Annex.Exception. Mostly this was trivial, but note that
tryAnnex is replaced with tryNonAsync and catchAnnex replaced with
catchNonAsync. In theory that could be a behavior change, since the former
caught all exceptions, and the latter don't catch async exceptions.
However, in practice, nothing in the Annex monad uses async exceptions.
Grepping for throwTo and killThread only find stuff in the assistant,
which does not seem related.
Command.Add.undo is changed to accept a SomeException, and things
that use it for rollback now catch non-async exceptions, rather than
only IOExceptions.
2014-08-08 01:55:44 +00:00
|
|
|
|
handlechange item addmeta
|
2015-12-26 19:04:26 +00:00
|
|
|
|
=<< catKey (DiffTree.dstsha item)
|
2014-02-19 18:14:44 +00:00
|
|
|
|
| otherwise = noop
|
unify exception handling into Utility.Exception
Removed old extensible-exceptions, only needed for very old ghc.
Made webdav use Utility.Exception, to work after some changes in DAV's
exception handling.
Removed Annex.Exception. Mostly this was trivial, but note that
tryAnnex is replaced with tryNonAsync and catchAnnex replaced with
catchNonAsync. In theory that could be a behavior change, since the former
caught all exceptions, and the latter don't catch async exceptions.
However, in practice, nothing in the Annex monad uses async exceptions.
Grepping for throwTo and killThread only find stuff in the assistant,
which does not seem related.
Command.Add.undo is changed to accept a SomeException, and things
that use it for rollback now catch non-async exceptions, rather than
only IOExceptions.
2014-08-08 01:55:44 +00:00
|
|
|
|
handlechange item a = maybe noop
|
2019-12-09 17:49:05 +00:00
|
|
|
|
(void . commandAction . a (fromRawFilePath $ getTopFilePath $ DiffTree.file item))
|
2014-02-19 18:14:44 +00:00
|
|
|
|
|
2014-02-18 21:38:23 +00:00
|
|
|
|
{- Runs an action using the view index file.
|
|
|
|
|
- Note that the file does not necessarily exist, or can contain
|
|
|
|
|
- info staged for an old view. -}
|
2015-12-26 18:52:03 +00:00
|
|
|
|
withViewIndex :: Annex a -> Annex a
|
2020-04-10 17:37:04 +00:00
|
|
|
|
withViewIndex = withIndexFile ViewIndexFile . const
|
add tip about metadata driven views (and more flexible view filtering)
While writing this documentation, I realized that there needed to be a way
to stay in a view like tag=* while adding a filter like tag=work that
applies to the same field.
So, there are really two ways a view can be refined. It can have a new
"field=explicitvalue" filter added to it, which does not change the
"shape" of the view, but narrows the files it shows.
Or, it can have a new view added, which adds another level of
subdirectories.
So, added a vfilter command, which takes explicit values to add to the
filter, and rejects changes that would change the shape of the view.
And, made vadd only accept changes that change the shape of the view.
And, changed the View data type slightly; now components that can match
multiple metadata values can be visible, or not visible.
This commit was sponsored by Stelian Iancu.
2014-02-19 19:10:18 +00:00
|
|
|
|
|
2015-12-26 18:52:03 +00:00
|
|
|
|
{- Generates a branch for a view, using the view index file
|
|
|
|
|
- to make a commit to the view branch. The view branch is not
|
|
|
|
|
- checked out, but entering it will display the view. -}
|
|
|
|
|
genViewBranch :: View -> Annex Git.Branch
|
|
|
|
|
genViewBranch view = withViewIndex $ do
|
|
|
|
|
let branch = branchView view
|
2019-11-11 22:20:35 +00:00
|
|
|
|
cmode <- annexCommitMode <$> Annex.getGitConfig
|
2019-11-11 20:15:05 +00:00
|
|
|
|
void $ inRepo $ Git.Branch.commit cmode True (fromRef branch) branch []
|
2015-12-26 18:52:03 +00:00
|
|
|
|
return branch
|
|
|
|
|
|
add tip about metadata driven views (and more flexible view filtering)
While writing this documentation, I realized that there needed to be a way
to stay in a view like tag=* while adding a filter like tag=work that
applies to the same field.
So, there are really two ways a view can be refined. It can have a new
"field=explicitvalue" filter added to it, which does not change the
"shape" of the view, but narrows the files it shows.
Or, it can have a new view added, which adds another level of
subdirectories.
So, added a vfilter command, which takes explicit values to add to the
filter, and rejects changes that would change the shape of the view.
And, made vadd only accept changes that change the shape of the view.
And, changed the View data type slightly; now components that can match
multiple metadata values can be visible, or not visible.
This commit was sponsored by Stelian Iancu.
2014-02-19 19:10:18 +00:00
|
|
|
|
withCurrentView :: (View -> Annex a) -> Annex a
|
2016-11-16 01:29:54 +00:00
|
|
|
|
withCurrentView a = maybe (giveup "Not in a view.") a =<< currentView
|