File: DiffDriver.hs

package info (click to toggle)
git-annex 10.20251029-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 75,300 kB
  • sloc: haskell: 91,492; javascript: 9,103; sh: 1,593; makefile: 216; perl: 137; ansic: 44
file content (149 lines) | stat: -rw-r--r-- 3,962 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
{- git-annex command
 -
 - Copyright 2014-2023 Joey Hess <id@joeyh.name>
 -
 - Licensed under the GNU AGPL version 3 or higher.
 -}

module Command.DiffDriver where

import Command
import Annex.Content
import Annex.Link
import Git.Types
import qualified Command.Get

cmd :: Command
cmd = command "diffdriver" SectionPlumbing 
	"git diff driver"
	("-- cmd --") (seek <$$> optParser)

data Options = Options
	{ textDiff :: Bool
	, getOption :: Bool
	, restOptions :: CmdParams
	}

optParser :: CmdParamsDesc -> Parser Options
optParser desc = Options
	<$> switch
		( long "text"
		<> help "diff text files with diff(1)"
		)
	<*> switch
		( long "get"
		<> help "get file contents from remotes"
		)
	<*> cmdParams desc

seek :: Options -> CommandSeek
seek opts = do
	let (req, differ) = parseReq opts
	void $ liftIO . exitBool =<< liftIO . differ =<< fixupReq req opts

data Req 
	= Req
		{ rPath :: FilePath
		, rOldFile :: FilePath
		, rOldHex :: String
		, rOldMode :: String
		, rNewFile :: FilePath
		, rNewHex :: String
		, rNewMode ::String
		}
	| UnmergedReq
		{ rPath :: FilePath
		}

type Differ = Req -> IO Bool

serializeReq :: Req -> [CommandParam]
serializeReq req@(UnmergedReq {}) = [Param $ rPath req]
serializeReq req@(Req {}) = map Param
	[ rPath req
	, rOldFile req
	, rOldHex req
	, rOldMode req
	, rNewFile req
	, rNewHex req
	, rNewMode req
	]

parseReq :: Options -> (Req, Differ)
parseReq opts
	| textDiff opts = case separate (== "--") (restOptions opts) of
		(_,[]) -> (mk (restOptions opts), textDiffer [])
		(ps,rest) -> (mk rest, textDiffer ps)
	| otherwise = case separate (== "--") (restOptions opts) of
		(c:ps, l) -> (mk l, externalDiffer c ps)
		([],_) -> badopts
  where
	mk (path:old_file:old_hex:old_mode:new_file:new_hex:new_mode:[]) =
		Req
			{ rPath = path
			, rOldFile = old_file
			, rOldHex = old_hex
			, rOldMode = old_mode
			, rNewFile = new_file
			, rNewHex = new_hex
			, rNewMode = new_mode
			}
	mk (unmergedpath:[]) = UnmergedReq { rPath = unmergedpath }
	mk _ = badopts

	badopts = giveup $ "Unexpected input: " ++ unwords (restOptions opts)

{- Check if either file is a symlink to a git-annex object,
 - which git-diff will leave as a normal file containing the link text.
 -
 - Also check if either file is a pointer file, as used for unlocked files.
 -
 - In either case, adjust the Req to instead point to the actual
 - location of the annexed object (which may or may not be present).
 -
 - This also gets objects from remotes when the getOption is set.
 -}
fixupReq :: Req -> Options -> Annex Req
fixupReq req@(UnmergedReq {}) _ = return req
fixupReq req@(Req {}) opts = 
	check rOldFile rOldMode setoldfile req
		>>= check rNewFile rNewMode setnewfile
  where
	setoldfile r@(Req {}) f = r { rOldFile = f }
	setoldfile r@(UnmergedReq {}) _ = r
	setnewfile r@(Req {}) f = r { rNewFile = f }
	setnewfile r@(UnmergedReq {}) _ = r
	check getfile getmode setfile r = case readTreeItemType (encodeBS (getmode r)) of
		Just TreeSymlink -> do
			v <- getAnnexLinkTarget' f False
			maybe (return r) go (parseLinkTargetOrPointer =<< v)
		_ -> maybe (return r) go =<< liftIO (isPointerFile f)
	  where
		f = toOsPath (getfile r)
	  	go k = do
			when (getOption opts) $
				unlessM (inAnnex k) $
					commandAction $
						starting "get" ai si $
							Command.Get.perform NoLiveUpdate k af
			repoint k
		  where
			ai = OnlyActionOn k (ActionItemKey k)
			si = SeekInput []
			af = AssociatedFile (Just f)
		repoint k = withObjectLoc k $
			pure . setfile r . fromOsPath

externalDiffer :: String -> [String] -> Differ
externalDiffer c ps = \req -> boolSystem c (map Param ps ++ serializeReq req )

textDiffer :: [String] -> Differ
textDiffer diffopts req = do
	putStrLn ("diff a/" ++ rPath req ++ " b/" ++ rPath req)
	-- diff exits nonzero on difference, so ignore exit status
	void $ boolSystem "diff" $
		[ Param "-u"
		, Param (rOldFile req)
		, Param (rNewFile req)
		] ++ map Param diffopts
	return True