packages feed

git-annex-10.20261020: Annex/AdjustTreeItem.hs

{- TreeItem adjustment implementation
 -
 - Copyright 2016-2026 Joey Hess <id@joeyh.name>
 -
 - Licensed under the GNU AGPL version 3 or higher.
 -}

{-# LANGUAGE BangPatterns, OverloadedStrings #-}

module Annex.AdjustTreeItem (
	AdjustTreeItem,
	getAdjustTreeItem
) where

import Annex.Common
import Types.AdjustedBranch
import Types.AdjustTreeItem
import Git
import Git.Types
import Git.Tree (TreeItem(..))
import Git.FilePath
import Annex.CatFile
import Annex.Link
import Annex.Content.Presence
import {-# SOURCE #-} Annex.Wanted
import qualified Database.Keys
import Utility.FileMode
import qualified Utility.RawFilePath as R

import System.PosixCompat.Files (fileMode)

getAdjustTreeItem :: AdjustTreeItem
getAdjustTreeItem = AdjustTreeItem
	{ adjustTreeItem = adjustTreeItemC
	, adjustmentIsStable = adjustmentIsStableC
	}

class AdjustTreeItemClass t where
	-- How to perform various adjustments to a TreeItem.
	adjustTreeItemC :: t -> TreeItem -> Annex (Maybe TreeItem)
	-- Will adjusting a given tree always yield the same adjusted tree?
	adjustmentIsStableC :: t -> Bool

instance AdjustTreeItemClass Adjustment where
	adjustTreeItemC (LinkAdjustment l) t = adjustTreeItemC l t
	adjustTreeItemC (PresenceAdjustment p Nothing) t = adjustTreeItemC p t
	adjustTreeItemC (PresenceAdjustment p (Just l)) t =
		adjustTreeItemC p t >>= \case
			Nothing -> return Nothing
			Just t' -> adjustTreeItemC l t'
	adjustTreeItemC (LockUnlockPresentAdjustment l) t = adjustTreeItemC l t
	adjustTreeItemC (WantedAdjustment p Nothing) t = adjustTreeItemC p t
	adjustTreeItemC (WantedAdjustment p (Just l)) t =
		adjustTreeItemC p t >>= \case
			Nothing -> return Nothing
			Just t' -> adjustTreeItemC l t'

	adjustmentIsStableC (LinkAdjustment l) = adjustmentIsStableC l
	adjustmentIsStableC (PresenceAdjustment p _) = adjustmentIsStableC p
	adjustmentIsStableC (LockUnlockPresentAdjustment l) = adjustmentIsStableC l
	adjustmentIsStableC (WantedAdjustment p _) = adjustmentIsStableC p

instance AdjustTreeItemClass LinkAdjustment where
	adjustTreeItemC UnlockAdjustment =
		ifSymlink adjustToPointer noAdjust
	adjustTreeItemC LockAdjustment =
		ifSymlink noAdjust adjustToSymlink
	adjustTreeItemC FixAdjustment =
		ifSymlink adjustToSymlink noAdjust
	adjustTreeItemC UnFixAdjustment =
		ifSymlink (adjustToSymlink' gitAnnexLinkCanonical) noAdjust
	
	adjustmentIsStableC _ = True

instance AdjustTreeItemClass PresenceAdjustment where
	adjustTreeItemC HideMissingAdjustment = 
		ifPresent noAdjust hideAdjust
	adjustTreeItemC ShowMissingAdjustment =
		noAdjust

	adjustmentIsStableC HideMissingAdjustment = False
	adjustmentIsStableC ShowMissingAdjustment = True

instance AdjustTreeItemClass WantedAdjustment where
	adjustTreeItemC HideUnwantedAdjustment = 
		ifWanted noAdjust hideAdjust
	adjustTreeItemC ShowUnwantedAdjustment =
		noAdjust

	adjustmentIsStableC HideUnwantedAdjustment = False
	adjustmentIsStableC ShowUnwantedAdjustment = True

instance AdjustTreeItemClass LockUnlockPresentAdjustment where
	adjustTreeItemC UnlockPresentAdjustment = 
		ifPresent adjustToPointer adjustToSymlink
	adjustTreeItemC LockPresentAdjustment =
		-- Turn all pointers back to symlinks, whether the content
		-- is present or not. This is done because the content
		-- availability may have changed and the branch not been
		-- re-adjusted to keep up, so there may be pointers whose
		-- content is not present.
		ifSymlink noAdjust adjustToSymlink

	adjustmentIsStableC UnlockPresentAdjustment = False
	adjustmentIsStableC LockPresentAdjustment = True

ifSymlink
	:: (TreeItem -> Annex a)
	-> (TreeItem -> Annex a)
	-> TreeItem
	-> Annex a
ifSymlink issymlink notsymlink ti@(TreeItem _f m _s)
	| toTreeItemType m == Just TreeSymlink = issymlink ti
	| otherwise = notsymlink ti

ifPresent
	:: (TreeItem -> Annex (Maybe TreeItem))
	-> (TreeItem -> Annex (Maybe TreeItem))
	-> TreeItem
	-> Annex (Maybe TreeItem)
ifPresent ispresent notpresent ti@(TreeItem _ _ s) =
	catKey s >>= \case
		Just k -> ifM (inAnnex k)
			( ispresent ti
			, notpresent ti
			)
		Nothing -> return (Just ti)

ifWanted
	:: (TreeItem -> Annex (Maybe TreeItem))
	-> (TreeItem -> Annex (Maybe TreeItem))
	-> TreeItem
	-> Annex (Maybe TreeItem)
ifWanted iswanted notwanted ti@(TreeItem topf _ s) =
	catKey s >>= \case
		Just k -> do
			af <- AssociatedFile . Just
				<$> fromRepo (fromTopFilePath topf)
			ifM (wantGet NoLiveUpdate True (Just k) af)
				( iswanted ti
				, notwanted ti
				)
		Nothing -> return (Just ti)

noAdjust :: TreeItem -> Annex (Maybe TreeItem)
noAdjust = return . Just

hideAdjust :: TreeItem -> Annex (Maybe TreeItem)
hideAdjust _ = return Nothing

adjustToPointer :: TreeItem -> Annex (Maybe TreeItem)
adjustToPointer ti@(TreeItem f _m s) = catKey s >>= \case
	Just k -> do
		Database.Keys.addAssociatedFile k f
		exe <- catchDefaultIO False $
			(isExecutable . fileMode) <$> 
				(liftIO . R.getFileStatus . fromOsPath
					=<< calcRepo (gitAnnexLocation k))
		let mode = fromTreeItemType $ 
			if exe then TreeExecutable else TreeFile
		Just . TreeItem f mode <$> hashPointerFile k
	Nothing -> return (Just ti)

adjustToSymlink :: TreeItem -> Annex (Maybe TreeItem)
adjustToSymlink = adjustToSymlink' gitAnnexLink

adjustToSymlink' :: (OsPath -> Key -> Git.Repo -> GitConfig -> IO OsPath) -> TreeItem -> Annex (Maybe TreeItem)
adjustToSymlink' gitannexlink ti@(TreeItem f _m s) = catKey s >>= \case
	Just k -> do
		absf <- inRepo $ \r -> absPath $ fromTopFilePath f r
		linktarget <- calcRepo $ gitannexlink absf k
		Just . TreeItem f (fromTreeItemType TreeSymlink)
			<$> hashSymlink (fromOsPath linktarget)
	Nothing -> return (Just ti)