clean up
This commit is contained in:
parent
ecdaa9511d
commit
49fcd0ca44
152
Main.hs
152
Main.hs
|
|
@ -8,125 +8,17 @@ import Data.Bool
|
|||
import Data.Foldable
|
||||
import Data.Function
|
||||
import Data.List
|
||||
import Data.Maybe
|
||||
import Data.Traversable
|
||||
import Opts
|
||||
import System.Environment
|
||||
import System.Exit
|
||||
import System.FilePath
|
||||
import System.IO
|
||||
import System.IO.Temp
|
||||
import System.Process
|
||||
|
||||
import Opts
|
||||
import Progs
|
||||
import qualified Toks
|
||||
import Toks (Tok)
|
||||
|
||||
import Debug.Trace
|
||||
|
||||
{-
|
||||
- interface to other programs
|
||||
-}
|
||||
diffProg = fromMaybe "diff" <$> lookupEnv "WERGE_DIFF"
|
||||
|
||||
gitProg = fromMaybe "git" <$> lookupEnv "WERGE_GIT"
|
||||
|
||||
bracketFile path mode = bracket (openFile path mode) hClose
|
||||
|
||||
rundiff f1 f2 out = do
|
||||
diff <- diffProg
|
||||
st <-
|
||||
bracketFile out WriteMode $ \oh ->
|
||||
withCreateProcess
|
||||
(proc
|
||||
diff
|
||||
[ "--text"
|
||||
, "--new-line-format=+%L"
|
||||
, "--old-line-format=-%L"
|
||||
, "--unchanged-line-format= %L"
|
||||
, f1
|
||||
, f2
|
||||
])
|
||||
{std_in = NoStream, std_out = UseHandle oh} $ \_ _ _ -> waitForProcess
|
||||
when (st == ExitFailure 2) $ error "diff failed"
|
||||
unless (st `elem` [ExitSuccess, ExitFailure 1])
|
||||
$ error "diff failed for unknown reason (is GNU diffutils installed?)"
|
||||
|
||||
gitRepoRelRoot = do
|
||||
git <- gitProg
|
||||
(path, st) <-
|
||||
withCreateProcess
|
||||
(proc git ["rev-parse", "--show-cdup"])
|
||||
{std_in = NoStream, std_out = CreatePipe} $ \_ (Just oh) _ p ->
|
||||
(,) <$> hGetContents' oh <*> waitForProcess p
|
||||
unless (st == ExitSuccess) $ error "git failed"
|
||||
let [p] = lines path
|
||||
pure p
|
||||
|
||||
gitUnmerged = do
|
||||
git <- gitProg
|
||||
(paths, st) <-
|
||||
withCreateProcess
|
||||
(proc git ["status", "--porcelain=v1"])
|
||||
{std_in = NoStream, std_out = CreatePipe} $ \_ (Just oh) _ p ->
|
||||
(,)
|
||||
<$> (map (drop 3) . filter ("UU " `isPrefixOf`) . lines
|
||||
<$> hGetContents' oh)
|
||||
<*> waitForProcess p
|
||||
unless (st == ExitSuccess) $ error "git failed"
|
||||
pure paths
|
||||
|
||||
gitCheckoutMOY cfg u my old your = do
|
||||
git <- gitProg
|
||||
(paths, st) <-
|
||||
withCreateProcess
|
||||
(proc git ["ls-files", "--unmerged", "--", u])
|
||||
{std_in = NoStream, std_out = CreatePipe} $ \_ (Just oh) _ p ->
|
||||
(,)
|
||||
<$> (sortOn snd
|
||||
. map ((\[a, b] -> (a, b)) . take 2 . drop 1 . words)
|
||||
. lines
|
||||
<$> hGetContents' oh)
|
||||
<*> waitForProcess p
|
||||
unless (st == ExitSuccess) $ error "git failed"
|
||||
let co (hash, _) path = do
|
||||
st <-
|
||||
withCreateProcess
|
||||
(proc "git" ["cat-file", "blob", hash])
|
||||
{std_in = NoStream, std_out = CreatePipe} $ \_ (Just ho) _ p -> do
|
||||
hSplitToFile cfg ho path
|
||||
waitForProcess p
|
||||
unless (st == ExitSuccess) . error
|
||||
$ "failed checking out " ++ u ++ " from blob " ++ hash
|
||||
case paths of
|
||||
[(_, "1"), (_, "2"), (_, "3")] ->
|
||||
zipWithM co paths [old, my, your] >> pure ()
|
||||
_ -> error $ "bad data from ls-files for unmerged " ++ u
|
||||
|
||||
gitAdd path = do
|
||||
git <- gitProg
|
||||
st <- rawSystem git ["add", "--", path]
|
||||
unless (st == ExitSuccess) $ error "git-add failed"
|
||||
|
||||
{-
|
||||
- configurable splitting
|
||||
-
|
||||
- TODO this should probably enforce joinSpaces?
|
||||
- or have joinSpaces as configurable? (probably best, default true)
|
||||
-}
|
||||
hSplitToFile cfg h path =
|
||||
case cfgTokenizer cfg of
|
||||
TokenizeCharCategory -> internal Toks.splitCategory
|
||||
TokenizeCharCategorySimple -> internal Toks.splitSimple
|
||||
TokenizeFilter cmd -> do
|
||||
st <-
|
||||
bracketFile path WriteMode $ \ho ->
|
||||
withCreateProcess
|
||||
(shell cmd) {std_in = UseHandle h, std_out = UseHandle ho} $ \_ _ _ ->
|
||||
waitForProcess
|
||||
unless (st == ExitSuccess) $ error "tokenize filter failed"
|
||||
where
|
||||
internal s = hGetContents h >>= writeFile path . Toks.toFile . s
|
||||
|
||||
{-
|
||||
- merge algorithms
|
||||
-}
|
||||
|
|
@ -142,7 +34,7 @@ pdiff path = map go . lines <$> readFile path
|
|||
go ('-':s) = (Del, s)
|
||||
go (' ':s) = (Keep, s)
|
||||
go ('+':s) = (Add, s)
|
||||
go [] = error "unexpected output from diff"
|
||||
go _ = error "unexpected output from diff"
|
||||
|
||||
data Merged
|
||||
= Ok [String]
|
||||
|
|
@ -150,9 +42,11 @@ data Merged
|
|||
| Conflict [String] [String] [String]
|
||||
deriving (Show)
|
||||
|
||||
isKeepTok :: (Op, String) -> Bool
|
||||
isKeepTok (Keep, _) = True
|
||||
isKeepTok _ = False
|
||||
|
||||
isDelTok :: (Op, String) -> Bool
|
||||
isDelTok (Del, _) = True
|
||||
isDelTok _ = False
|
||||
|
||||
|
|
@ -170,6 +64,7 @@ chunks xs =
|
|||
let (reps, ys) = break isKeepTok xs
|
||||
in uncurry (Replace `on` map snd) (partition isDelTok reps) : chunks ys
|
||||
|
||||
align1 :: Eq a => [a] -> [a] -> ([a], [a], [a])
|
||||
align1 as [] = ([], as, [])
|
||||
align1 [] bs = ([], [], bs)
|
||||
align1 (a:as) (b:bs)
|
||||
|
|
@ -178,7 +73,7 @@ align1 (a:as) (b:bs)
|
|||
align1 _ _ = error "chunks do not align"
|
||||
|
||||
align :: [Merged] -> [Merged] -> [Merged]
|
||||
align m y = connect $ slice m y
|
||||
align m0 y0 = connect $ slice m0 y0
|
||||
where
|
||||
erase x = Replace x []
|
||||
nemap _ [] = []
|
||||
|
|
@ -204,13 +99,14 @@ align m y = connect $ slice m y
|
|||
slice _ _ = error "unacceptable chunks"
|
||||
coFlag (Ok _) = False
|
||||
coFlag (Replace _ _) = True
|
||||
coFlag _ = error "flagging unacceptable chunks"
|
||||
coSig (a, b) = (coFlag a, coFlag b)
|
||||
coConn' (a, b) (a', b') = (a && a') || (b && b')
|
||||
coConn = coConn' `on` coSig
|
||||
coGroup [] = []
|
||||
coGroup (x:xs) =
|
||||
case coGroup xs of
|
||||
xs'@(ys@(y:_):yss)
|
||||
(ys@(y:_):yss)
|
||||
| coConn x y -> (x : ys) : yss
|
||||
xs' -> [x] : xs'
|
||||
connect = map confl . coGroup
|
||||
|
|
@ -218,12 +114,14 @@ align m y = connect $ slice m y
|
|||
toCon (Ok o, Replace _ y) = Conflict o o y
|
||||
toCon (Replace o m, Ok _) = Conflict m o o
|
||||
toCon (Replace o m, Replace _ y) = Conflict m o y
|
||||
confl = foldr cappend (Ok []) . map toCon
|
||||
cappend (Ok x) (Ok o) = Ok (x ++ o)
|
||||
cappend (Ok x) (Conflict m o y) = error "align consistency check fail" -- Conflict (x++m) (x++o) (x++y)
|
||||
cappend (Conflict m o y) (Ok x) = Conflict (m ++ x) (o ++ x) (y ++ x)
|
||||
cappend (Conflict m o y) (Conflict m' o' y') =
|
||||
toCon _ = error "converting unacceptable chunks"
|
||||
confl = foldr coAppend (Ok []) . map toCon
|
||||
coAppend (Ok x) (Ok o) = Ok (x ++ o)
|
||||
coAppend (Ok _) (Conflict _ _ _) = error "align consistency check fail" -- Conflict (x++m) (x++o) (x++y)
|
||||
coAppend (Conflict m o y) (Ok x) = Conflict (m ++ x) (o ++ x) (y ++ x)
|
||||
coAppend (Conflict m o y) (Conflict m' o' y') =
|
||||
Conflict (m ++ m') (o ++ o') (y ++ y')
|
||||
coAppend _ _ = error "appending unacceptable chunks"
|
||||
|
||||
regroup :: [Merged] -> [Merged]
|
||||
regroup [] = []
|
||||
|
|
@ -234,10 +132,11 @@ regroup (x@(Ok a):xs) =
|
|||
xs' -> x : xs'
|
||||
regroup (x:xs) = x : regroup xs
|
||||
|
||||
zeal Config {..} (Conflict m o y) =
|
||||
before' ++ (Conflict (reverse m'') o (reverse y'') : after')
|
||||
zeal :: Config -> Merged -> [Merged]
|
||||
zeal Config {..} (Conflict m0 o0 y0) =
|
||||
before' ++ (Conflict (reverse m'') o0 (reverse y'') : after')
|
||||
where
|
||||
((m', y'), before) = pops m y
|
||||
((m', y'), before) = pops m0 y0
|
||||
((m'', y''), rafter) = pops (reverse m') (reverse y')
|
||||
before' =
|
||||
case before of
|
||||
|
|
@ -258,6 +157,7 @@ zeal Config {..} (Conflict m o y) =
|
|||
pops ms ys = ((ms, ys), [])
|
||||
zeal _ x = [x]
|
||||
|
||||
resolveSpace :: Config -> Merged -> Merged
|
||||
resolveSpace Config {..} c@(Conflict m o y)
|
||||
| not (all Toks.space $ concat [m, o, y]) = c
|
||||
| m == o && o == y = Ok o
|
||||
|
|
@ -290,6 +190,7 @@ expand n = go
|
|||
xs' -> x : xs'
|
||||
go (x:xs) = x : go xs
|
||||
|
||||
resolve :: Config -> Merged -> Merged
|
||||
resolve cfg@Config {..} c@(Conflict m o y)
|
||||
| cfgSpaceResolution /= SpaceNormal
|
||||
, all Toks.space (concat [m, o, y]) = resolveSpace cfg c
|
||||
|
|
@ -305,6 +206,7 @@ resolve cfg@Config {..} c@(Conflict m o y)
|
|||
ResolveKeep -> c
|
||||
resolve _ x = x
|
||||
|
||||
merge :: Config -> [(Op, String)] -> [(Op, String)] -> [Merged]
|
||||
merge cfg@Config {..} ms ys =
|
||||
regroup
|
||||
. map (resolve cfg)
|
||||
|
|
@ -325,7 +227,7 @@ format Config {..} h = go False
|
|||
go c (Ok x:xs) = do
|
||||
hPutStr h (Toks.glue x)
|
||||
go c xs
|
||||
go c (Conflict m o y:xs) = do
|
||||
go _ (Conflict m o y:xs) = do
|
||||
hPutStr h
|
||||
$ mconcat
|
||||
[ cfgLabelStart
|
||||
|
|
@ -337,7 +239,9 @@ format Config {..} h = go False
|
|||
, cfgLabelEnd
|
||||
]
|
||||
go True xs
|
||||
go _ _ = error "bad format (replace)"
|
||||
|
||||
runCmd :: Command -> Config -> IO ()
|
||||
runCmd CmdDiff3 {..} cfg =
|
||||
withSystemTempDirectory "werge-diff3" $ \workdir -> do
|
||||
let [fMy, fOld, fYour, fdMy, fdYour] =
|
||||
|
|
@ -378,9 +282,7 @@ runCmd CmdGitMerge {..} cfg = do
|
|||
main :: IO ()
|
||||
main = catch go bad
|
||||
where
|
||||
go = do
|
||||
(cfg, cmd) <- parseOpts
|
||||
runCmd cmd cfg
|
||||
go = parseOpts >>= uncurry (flip runCmd)
|
||||
bad e = do
|
||||
hPutStrLn stderr $ "fatal: " ++ displayException (e :: IOException)
|
||||
exitWith (ExitFailure 2)
|
||||
|
|
|
|||
11
Opts.hs
11
Opts.hs
|
|
@ -16,6 +16,7 @@ data Tokenizer
|
|||
| TokenizeCharCategorySimple
|
||||
deriving (Show)
|
||||
|
||||
tokenizer :: Parser Tokenizer
|
||||
tokenizer =
|
||||
asum
|
||||
[ TokenizeFilter
|
||||
|
|
@ -44,6 +45,7 @@ data ConflictMask = ConflictMask
|
|||
, cmResolveSeparate :: Bool
|
||||
} deriving (Show)
|
||||
|
||||
conflictMask :: String -> String -> Parser ConflictMask
|
||||
conflictMask label objs = do
|
||||
cmResolveOverlaps' <-
|
||||
fmap not . switch
|
||||
|
|
@ -70,6 +72,7 @@ data Resolution
|
|||
| ResolveYour
|
||||
deriving (Show, Eq)
|
||||
|
||||
resolutionMode :: String -> Either String Resolution
|
||||
resolutionMode x
|
||||
| x `isPrefixOf` "keep" = Right ResolveKeep
|
||||
| x `isPrefixOf` "my" = Right ResolveMy
|
||||
|
|
@ -86,6 +89,7 @@ data SpaceResolution
|
|||
| SpaceSpecial Resolution
|
||||
deriving (Show, Eq)
|
||||
|
||||
spaceMode :: String -> Either String SpaceResolution
|
||||
spaceMode x
|
||||
| x `isPrefixOf` "normal" = Right SpaceNormal
|
||||
| Right y <- resolutionMode x = Right (SpaceSpecial y)
|
||||
|
|
@ -110,6 +114,7 @@ data Config = Config
|
|||
, cfgLabelEnd :: String
|
||||
} deriving (Show)
|
||||
|
||||
config :: Parser Config
|
||||
config = do
|
||||
cfgTokenizer <- tokenizer
|
||||
cfgZealous <-
|
||||
|
|
@ -212,6 +217,7 @@ data Command
|
|||
}
|
||||
deriving (Show)
|
||||
|
||||
cmdDiff3 :: Parser Command
|
||||
cmdDiff3 = do
|
||||
d3my <- strArgument $ metavar "MYFILE" <> help "Version with local edits"
|
||||
d3old <- strArgument $ metavar "OLDFILE" <> help "Original file version"
|
||||
|
|
@ -219,13 +225,15 @@ cmdDiff3 = do
|
|||
strArgument $ metavar "YOURFILE" <> help "Version with other people's edits"
|
||||
pure CmdDiff3 {..}
|
||||
|
||||
cmdGitMerge :: Parser Command
|
||||
cmdGitMerge = do
|
||||
gmFiles <-
|
||||
asum
|
||||
[ fmap Just . some
|
||||
$ strArgument
|
||||
$ metavar "UNMERGED"
|
||||
<> help "Unmerged file tracked by git (can be specified repeatedly)"
|
||||
<> help
|
||||
"Unmerged file tracked by git (can be specified repeatedly)"
|
||||
, flag'
|
||||
Nothing
|
||||
(long "unmerged"
|
||||
|
|
@ -246,6 +254,7 @@ cmdGitMerge = do
|
|||
|
||||
-- TODO have some option to output the (partially merged) my/old/your files so
|
||||
-- that folks can continue with external program or so (such as meld)
|
||||
cmd :: Parser Command
|
||||
cmd =
|
||||
hsubparser
|
||||
$ mconcat
|
||||
|
|
|
|||
131
Progs.hs
Normal file
131
Progs.hs
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
module Progs where
|
||||
|
||||
import Control.Exception
|
||||
import Control.Monad
|
||||
import Data.List
|
||||
import Data.Maybe
|
||||
import System.Environment
|
||||
import System.Exit
|
||||
import System.IO
|
||||
import System.Process
|
||||
|
||||
import Opts
|
||||
import qualified Toks
|
||||
|
||||
bracketFile :: FilePath -> IOMode -> (Handle -> IO c) -> IO c
|
||||
bracketFile path mode = bracket (openFile path mode) hClose
|
||||
|
||||
{-
|
||||
- interface to gnu diff
|
||||
-}
|
||||
diffProg :: IO String
|
||||
diffProg = fromMaybe "diff" <$> lookupEnv "WERGE_DIFF"
|
||||
|
||||
rundiff :: FilePath -> FilePath -> FilePath -> IO ()
|
||||
rundiff f1 f2 out = do
|
||||
diff <- diffProg
|
||||
st <-
|
||||
bracketFile out WriteMode $ \oh ->
|
||||
withCreateProcess
|
||||
(proc
|
||||
diff
|
||||
[ "--text"
|
||||
, "--new-line-format=+%L"
|
||||
, "--old-line-format=-%L"
|
||||
, "--unchanged-line-format= %L"
|
||||
, f1
|
||||
, f2
|
||||
])
|
||||
{std_in = NoStream, std_out = UseHandle oh} $ \_ _ _ -> waitForProcess
|
||||
when (st == ExitFailure 2) $ error "diff failed"
|
||||
unless (st `elem` [ExitSuccess, ExitFailure 1])
|
||||
$ error "diff failed for unknown reason (is GNU diffutils installed?)"
|
||||
|
||||
{-
|
||||
- interface to git
|
||||
-}
|
||||
gitProg :: IO String
|
||||
gitProg = fromMaybe "git" <$> lookupEnv "WERGE_GIT"
|
||||
|
||||
gitRepoRelRoot :: IO FilePath
|
||||
gitRepoRelRoot = do
|
||||
git <- gitProg
|
||||
(path, st) <-
|
||||
withCreateProcess
|
||||
(proc git ["rev-parse", "--show-cdup"])
|
||||
{std_in = NoStream, std_out = CreatePipe} $ \_ (Just oh) _ p ->
|
||||
(,) <$> hGetContents' oh <*> waitForProcess p
|
||||
unless (st == ExitSuccess) $ error "git failed"
|
||||
case lines path of
|
||||
[p] -> pure p
|
||||
_ -> fail "bad git-rev-parse output"
|
||||
|
||||
gitUnmerged :: IO [FilePath]
|
||||
gitUnmerged = do
|
||||
git <- gitProg
|
||||
(paths, st) <-
|
||||
withCreateProcess
|
||||
(proc git ["status", "--porcelain=v1"])
|
||||
{std_in = NoStream, std_out = CreatePipe} $ \_ (Just oh) _ p ->
|
||||
(,)
|
||||
<$> (map (drop 3) . filter ("UU " `isPrefixOf`) . lines
|
||||
<$> hGetContents' oh)
|
||||
<*> waitForProcess p
|
||||
unless (st == ExitSuccess) $ error "git failed"
|
||||
pure paths
|
||||
|
||||
gitCheckoutMOY ::
|
||||
Config -> FilePath -> FilePath -> FilePath -> FilePath -> IO ()
|
||||
gitCheckoutMOY cfg u my old your = do
|
||||
git <- gitProg
|
||||
(paths, st) <-
|
||||
withCreateProcess
|
||||
(proc git ["ls-files", "--unmerged", "--", u])
|
||||
{std_in = NoStream, std_out = CreatePipe} $ \_ (Just oh) _ p ->
|
||||
(,)
|
||||
<$> (sortOn snd
|
||||
. map ((\[a, b] -> (a, b)) . take 2 . drop 1 . words)
|
||||
. lines
|
||||
<$> hGetContents' oh)
|
||||
<*> waitForProcess p
|
||||
unless (st == ExitSuccess) $ error "git failed"
|
||||
let co (hash, _) path = do
|
||||
st' <-
|
||||
withCreateProcess
|
||||
(proc "git" ["cat-file", "blob", hash])
|
||||
{std_in = NoStream, std_out = CreatePipe} $ \_ (Just ho) _ p -> do
|
||||
hSplitToFile cfg ho path
|
||||
waitForProcess p
|
||||
unless (st' == ExitSuccess) . error
|
||||
$ "failed checking out " ++ u ++ " from blob " ++ hash
|
||||
case paths of
|
||||
[(_, "1"), (_, "2"), (_, "3")] ->
|
||||
zipWithM co paths [old, my, your] >> pure ()
|
||||
_ -> error $ "bad data from ls-files for unmerged " ++ u
|
||||
|
||||
gitAdd :: FilePath -> IO ()
|
||||
gitAdd path = do
|
||||
git <- gitProg
|
||||
st <- rawSystem git ["add", "--", path]
|
||||
unless (st == ExitSuccess) $ error "git-add failed"
|
||||
|
||||
{-
|
||||
- interface to external tokenizers
|
||||
-
|
||||
- TODO this might probably enforce joinSpaces?
|
||||
- or have joinSpaces as configurable? (probably best, default true)
|
||||
-}
|
||||
hSplitToFile :: Config -> Handle -> FilePath -> IO ()
|
||||
hSplitToFile cfg h path =
|
||||
case cfgTokenizer cfg of
|
||||
TokenizeCharCategory -> internal Toks.splitCategory
|
||||
TokenizeCharCategorySimple -> internal Toks.splitSimple
|
||||
TokenizeFilter fltr -> do
|
||||
st <-
|
||||
bracketFile path WriteMode $ \ho ->
|
||||
withCreateProcess
|
||||
(shell fltr) {std_in = UseHandle h, std_out = UseHandle ho} $ \_ _ _ ->
|
||||
waitForProcess
|
||||
unless (st == ExitSuccess) $ error "tokenize filter failed"
|
||||
where
|
||||
internal s = hGetContents h >>= writeFile path . Toks.toFile . s
|
||||
6
Toks.hs
6
Toks.hs
|
|
@ -15,11 +15,11 @@ unescape :: String -> String
|
|||
unescape [] = []
|
||||
unescape ('\\':'\\':xs) = '\\' : unescape xs
|
||||
unescape ('\\':'n':xs) = '\n' : unescape xs
|
||||
unescape ('\\':_) = error "bad escape?"
|
||||
unescape ('\\':_) = error "bad escape on input"
|
||||
unescape (x:xs) = x : unescape xs
|
||||
|
||||
markSpace :: String -> Tok
|
||||
markSpace [] = error "wat"
|
||||
markSpace [] = error "empty token"
|
||||
markSpace s@(c:_)
|
||||
| isSpace c = '.' : s
|
||||
| otherwise = '|' : s
|
||||
|
|
@ -27,7 +27,7 @@ markSpace s@(c:_)
|
|||
unmarkSpace :: Tok -> String
|
||||
unmarkSpace ('.':s) = s
|
||||
unmarkSpace ('|':s) = s
|
||||
unmarkSpace x = error "unwat"
|
||||
unmarkSpace _ = error "bad space marking on input"
|
||||
|
||||
space :: Tok -> Bool
|
||||
space ('.':_) = True
|
||||
|
|
|
|||
|
|
@ -22,8 +22,9 @@ executable werge
|
|||
main-is: Main.hs
|
||||
other-modules:
|
||||
Opts
|
||||
Toks
|
||||
Paths_werge
|
||||
Progs
|
||||
Toks
|
||||
|
||||
autogen-modules: Paths_werge
|
||||
build-depends:
|
||||
|
|
|
|||
Loading…
Reference in a new issue