packages feed

git-mediate 1.0.8.1 → 1.0.9

raw patch · 11 files changed

+457/−192 lines, 11 filesdep +generic-data

Dependencies added: generic-data

Files

ChangeLog.md view
@@ -1,15 +1,83 @@-# Since 1.0.7--* Fix stack.yaml to use Diff 0.4 as well+## 1.0.9 / To be released -# Since 1.0.6+* Do not warn when git is set to use `zdiff3` conflict style+* Resolve line ending conventions changes (i.e changes from Unix/Windows line endings)+* Preserve file modes when resolving conflicts (i.e executable scripts remain executable)+* Handle changes in `git status` formatting for files in spaces in their names+* `--context` option for `-d` mode to control size of context shown around diff+* Fixed parsing of conflicts with nested recursive conflicts+* `--editor` goes to the location of the first conflict in the file -* Upgrade Diff dependency to 0.4+## 1.0.8.1 / 2020.10.13 -# Since 1.0.5+* First release on Debian (entered Debian unstable at 2023.07.20)+* Build maintenance (anti-bitrot) -* Allow merging a specific file+## 1.0.6 / 2020.01.07 +* `--merge-file` option to merge specific file, even if file is not marked as conflicted * Reduce add/add conflicts with matching prefix/suffix lines- * Add support for `--untabify`++## 1.0.5 / 2018.07.24++* Windows compatibility fixes++## 1.0.1 / 2018.07.24++* Conflict headers for `-d` option (i.e "### Conflict 3 of 7")+* Improved error message when not running inside a git repository++## 1.0 / 2016.12.20++* Renamed to `git-mediate`+* First release on Hackage+* Add `--version` flag++## 0.3.2.4 / 2016.12.20++* Reduce conflicts when first or last lines match in all parts++## 0.3.2.1 / 2015.12.25++* `--diff2` option to dump diffs in `diff2` format+* Fix bug in modify/delete conflicts when not running from the repo's root++## 0.3.1.2 / 2015.09.20++* Better error reporting for conflict parsing errors++## 0.3.1.1 / 2015.09.01++* Support modify/delete conflicts++## 0.3.0.3 / 2015.08.16++* `-d` option also prints the diffs markers+* Support add/add conflicts+* Fixed bug with submodule conflicts++## 0.3.0.1 / 2015.06.06++* Detect git using `diff2` conflict style and add `-s` option to switch to `diff3` style+* Fixed bug with filenames containing spaces++## 0.2.0.2 / 2015.02.03++* Support for terminals without color++## 0.2.0.1 / 2015.01.12++* Don't keep `.bk` backups files for content before resolution++## 0.2 / 2014.12.18++* `-d` option for displaying the remaining conflicts++## 0.1.0.1 / 2014.12.10++* Conflict also resolves if both sides match (regardless of base also matching)++## Development started / 2014.12.1++* Tool developed started originally as "resolve-trivial-conflicts"
LICENSE view
@@ -1,7 +1,7 @@-GNU GENERAL PUBLIC LICENSE+                    GNU GENERAL PUBLIC LICENSE                        Version 2, June 1991 - Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA  Everyone is permitted to copy and distribute verbatim copies  of this license document, but changing it is not allowed.@@ -290,8 +290,8 @@ convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. -    {description}-    Copyright (C) {year}  {fullname}+    <one line to give the program's name and a brief idea of what it does.>+    Copyright (C) <year>  <name of author>      This program is free software; you can redistribute it and/or modify     it under the terms of the GNU General Public License as published by@@ -329,7 +329,7 @@   Yoyodyne, Inc., hereby disclaims all copyright interest in the program   `Gnomovision' (which makes passes at compilers) written by James Hacker. -  {signature of Ty Coon}, 1 April 1989+  <signature of Ty Coon>, 1 April 1989   Ty Coon, President of Vice  This General Public License does not permit incorporating your program into@@ -337,4 +337,3 @@ consider it more useful to permit linking proprietary applications with the library.  If this is what you want to do, use the GNU Lesser General Public License instead of this License.-
+ README.md view
@@ -0,0 +1,124 @@+# Introduction++Handling conflicts is difficult!++One useful way to handle them, is to use git's diff3 conflict style:++```shell+git config --global merge.conflictstyle diff3+```++And then when you get a conflict, it looks like:++    Unconflicted stuff++    <<<<<<< HEAD+    Version A changes+    |||||||+    Base version+    ======= Version B+    Version B changes+    >>>>>>>++    More unconflicted stuff here++Then you are supposed to manually merge the useful changes in the top and bottom parts, relative to the base version.++A useful way to do this is to figure out which of the changes (Version A or Version B) is a simpler change.++Perhaps one of the versions just added a small comment above the code section:++    Unconflicted stuff++    <<<<<<< HEAD+    Added a comment here+    BASE+    |||||||+    BASE+    ======= Version B+    BASE and complex changes here+    >>>>>>>++    More unconflicted stuff here++One easy thing to do, mechanically, is to apply the simple change to+the other 2 versions. Thus, it becomes:++    Unconflicted stuff++    <<<<<<< HEAD+    Added a comment here+    BASE+    |||||||+    Added a comment here+    BASE+    ======= Version B+    Added a comment here+    BASE and complex changes here+    >>>>>>>++    More unconflicted stuff here++Now, you can run this little utility: git-mediate, which will see+the conflict has become trivial (only one side changed anything) and+select that side appropriately.++When all conflicts have been resolved in a file, "git add" will be+used on it automatically.++## Simpler case++You might just resolve the conflicts manually and remove the merge markers from all of the conflicts.++In such a case, just run git-mediate, and it will "git add" the+file for you.++# Installation++## Recommended: Using haskell-stack++1. Install [haskell stack](https://docs.haskellstack.org/en/stable/)+2. Run: `stack install git-mediate`++## Alternative install: from sources++Clone it:++    git clone https://github.com/Peaker/git-mediate+    cd git-mediate++Option #1: Build & install using stack: `stack install` (make sure you installed [haskell stack](https://docs.haskellstack.org/en/stable/))++Option #2: Build & install using cabal: `cabal install` (make sure `~/.cabal/bin` is in your `$PATH`)++# Use++Call the git-mediate from a git repository with conflicts.++# Additional features++## Open editor++You can use the `-e` flag to invoke your `$EDITOR` on every conflicted file that could not be automatically resolved.++## Show conflict diffs++Sometimes, the conflict is just a giant block of incomprehensible text next to another giant block of incomprehensible text.++You can use the `-d` flag to show the conflict in diff-from-base form. Then, you can manually apply the changes you see in both the base and wherever needed, and use git-mediate again to make sure you've updated everything appropriately.++# License++Copyright (C) 2014-2023  Eyal Lotem++This program is free software; you can redistribute it and/or modify+it under the terms of the GNU General Public License as published by+the Free Software Foundation; version 2 of the License only.++This program is distributed in the hope that it will be useful,+but WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the+GNU General Public License for more details.++You should have received a copy of the GNU General Public License+along with this program.  If not, see <https://www.gnu.org/licenses/>.
git-mediate.cabal view
@@ -1,8 +1,8 @@ -- Initial git-mediate.cabal generated by cabal init.  For--- further documentation, see http://haskell.org/cabal/users-guide/+-- further documentation, see https://cabal.readthedocs.io/  name:                git-mediate-version:             1.0.8.1+version:             1.0.9 synopsis:            Tool to help resolving git conflicts description:         Git conflict resolution has never been easier                      .@@ -52,6 +52,7 @@ build-type:          Simple extra-source-files:  stack.yaml                      ChangeLog.md+                     README.md cabal-version:       >=1.10  source-repository head@@ -81,5 +82,6 @@                      , Diff >=0.4                      , ansi-terminal >=0.6.2                      , optparse-applicative >=0.11+                     , generic-data >=0.8.2   hs-source-dirs:      src   default-language:    Haskell2010
src/Conflict.hs view
@@ -1,68 +1,70 @@-{-# LANGUAGE FlexibleContexts, NoImplicitPrelude, RecordWildCards #-}+{-# LANGUAGE FlexibleContexts, NoImplicitPrelude, DeriveTraversable, NamedFieldPuns, DerivingVia, DeriveGeneric, OverloadedRecordDot #-}  module Conflict-    ( Conflict(..), LineNo-    , bodies, setBodies, bodyStrings, setBodyStrings+    ( Conflict(..), Sides(..), SrcContent(..)+    , setEachBody, setStrings     , pretty, prettyLines     , parse-    , markerPrefix     ) where  import Control.Monad.State (MonadState, state, evalStateT) import Control.Monad.Writer (runWriter, tell)-import Data.Functor.Identity (Identity(..))-import Data.List (isPrefixOf)+import Data.Maybe (fromMaybe)+import Generic.Data (Generically1(..))+import GHC.Generics (Generic1)  import Prelude.Compat -type LineNo = Int+data Sides a = Sides+    { sideA :: a+    , sideBase :: a+    , sideB :: a+    } deriving (Functor, Foldable, Traversable, Show, Eq, Ord, Generic1)+    deriving Applicative via Generically1 Sides -data Conflict = Conflict-    { cMarkerA    :: (LineNo, String) -- <<<<<<<....-    , cMarkerBase :: (LineNo, String) -- |||||||....-    , cMarkerB    :: (LineNo, String) -- =======....-    , cMarkerEnd  :: (LineNo, String) -- >>>>>>>....-    , cBodyA      :: [String]-    , cBodyBase   :: [String]-    , cBodyB      :: [String]-    } deriving (Show)+data SrcContent = SrcContent+    { lineNo :: Int+    , content :: String+    } deriving Show --- traversal-bodies :: Applicative f => ([String] -> f [String]) -> Conflict -> f Conflict-bodies f c@Conflict{..} =-    mk <$> f cBodyA <*> f cBodyBase <*> f cBodyB-    where-        mk bodyA bodyBase bodyB =-            c{cBodyA=bodyA, cBodyBase=bodyBase, cBodyB=bodyB}+data Conflict = Conflict+    { markers   :: Sides SrcContent -- The markers at the beginning of sections+    , markerEnd :: SrcContent       -- The ">>>>>>>...." marker at the end of the conflict+    , bodies    :: Sides [String]+    } deriving Show -bodyStrings :: Applicative f => (String -> f String) -> Conflict -> f Conflict-bodyStrings = bodies . traverse+setBodies :: (Sides [String] -> Sides [String]) -> Conflict -> Conflict+setBodies f c = c{bodies = f c.bodies} --- setter:-setBodies :: ([String] -> [String]) -> Conflict -> Conflict-setBodies f = runIdentity . bodies (Identity . f)+setEachBody :: ([String] -> [String]) -> Conflict -> Conflict+setEachBody = setBodies . fmap -setBodyStrings :: (String -> String) -> Conflict -> Conflict-setBodyStrings f = runIdentity . bodyStrings (Identity . f)+setStrings :: (String -> String) -> Conflict -> Conflict+setStrings = setEachBody . map  prettyLines :: Conflict -> [String]-prettyLines Conflict {..} =-    concat-    [ snd cMarkerA    : cBodyA-    , snd cMarkerBase : cBodyBase-    , snd cMarkerB    : cBodyB-    , [snd cMarkerEnd]-    ]+prettyLines c =+    concat ((:) <$> ((.content) <$> c.markers) <*> c.bodies) <> [c.markerEnd.content]  pretty :: Conflict -> String pretty = unlines . prettyLines --- '>' -> ">>>>>>>"-markerPrefix :: Char -> String-markerPrefix = replicate 7--breakUpToMarker :: MonadState [(LineNo, String)] m => Char -> m [(LineNo, String)]-breakUpToMarker c = state (break ((markerPrefix c `isPrefixOf`) . snd))+breakUpToMarker ::+    MonadState [SrcContent] m =>+    Char -> Maybe Int -> m [SrcContent]+breakUpToMarker c mCount =+    state (break cond)+    where+        count = fromMaybe 7 mCount+        prefix = replicate count c+        cond l =+            pre == prefix && rightCount+            where+                (pre, post) = splitAt count l.content+                rightCount =+                    case (mCount, post) of+                    (Just{}, x:_) -> c /= x+                    _ -> True  readHead :: MonadState [a] m => m (Maybe a) readHead = state f@@ -70,53 +72,51 @@         f [] = (Nothing, [])         f (l:ls) = (Just l, ls) -tryReadUpToMarker :: MonadState [(LineNo, String)] m => Char -> m ([(LineNo, String)], Maybe (LineNo, String))-tryReadUpToMarker c =-    do-        ls <- breakUpToMarker c-        mHead <- readHead-        return (ls, mHead)+tryReadUpToMarker ::+    MonadState [SrcContent] m =>+    Char -> Maybe Int -> m ([SrcContent], Maybe SrcContent)+tryReadUpToMarker c mCount =+    (,) <$> breakUpToMarker c mCount <*> readHead -readUpToMarker :: MonadState [(LineNo, String)] m => Char -> m ([(LineNo, String)], (LineNo, String))-readUpToMarker c = do-    res <- tryReadUpToMarker c+readUpToMarker ::+    MonadState [SrcContent] m =>+    Char -> Maybe Int -> m ([SrcContent], SrcContent)+readUpToMarker c mCount = do+    res <- tryReadUpToMarker c mCount     case res of-        (ls, Just h)  -> return (ls, h)+        (ls, Just h)  -> pure (ls, h)         (ls, Nothing) ->             error $ concat             [ "Parse error: failed reading up to marker: "             , show c, ", got:"-            , concatMap (\(l,s) -> "\n" ++ show l ++ "\t" ++ s) $ take 5 ls+            , concatMap (\l -> "\n" ++ show l.lineNo ++ "\t" ++ l.content) $ take 5 ls             ] -parseConflict :: MonadState [(LineNo, String)] m => (LineNo, String) -> m Conflict+parseConflict :: MonadState [SrcContent] m => SrcContent -> m Conflict parseConflict markerA =-    do  (linesA   , markerBase) <- readUpToMarker '|'-        (linesBase, markerB)    <- readUpToMarker '='-        (linesB   , markerEnd)  <- readUpToMarker '>'-        return Conflict-            { cMarkerA    = markerA-            , cMarkerBase = markerBase-            , cMarkerB    = markerB-            , cMarkerEnd  = markerEnd-            , cBodyA      = map snd linesA-            , cBodyB      = map snd linesB-            , cBodyBase   = map snd linesBase+    do  (linesA   , markerBase) <- readUpToMarker '|' markerCount+        (linesBase, markerB)    <- readUpToMarker '=' markerCount+        (linesB   , markerEnd)  <- readUpToMarker '>' markerCount+        pure Conflict+            { markers    = Sides markerA markerBase markerB+            , markerEnd+            , bodies     = fmap (.content) <$> Sides linesA linesBase linesB             }+    where+        markerCount = Just (length (takeWhile (== '<') markerA.content)) -parseFromNumberedLines :: [(LineNo, String)] -> [Either String Conflict]+parseFromNumberedLines :: [SrcContent] -> [Either String Conflict] parseFromNumberedLines =     snd . runWriter . evalStateT loop     where         loop =-            do  (ls, mMarkerA) <- tryReadUpToMarker '<'-                tell $ map (Left . snd) ls+            do  (ls, mMarkerA) <- tryReadUpToMarker '<' Nothing+                tell $ map (Left . (.content)) ls                 case mMarkerA of-                    Nothing -> return ()+                    Nothing -> pure ()                     Just markerA ->-                        do  tell . return . Right =<< parseConflict markerA+                        do  tell . pure . Right =<< parseConflict markerA                             loop  parse :: String -> [Either String Conflict]-parse input =-    parseFromNumberedLines (zip [1..] (lines input))+parse = parseFromNumberedLines . zipWith SrcContent [1..] . lines
src/Environment.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE NoImplicitPrelude, OverloadedRecordDot #-}  module Environment     ( checkConflictStyle, shouldUseColorByTerminal, openEditor@@ -6,6 +6,7 @@  import qualified Control.Exception as E import           Control.Monad (when, unless)+import           Data.Functor ((<&>)) import           Opts (Options(..)) import           PPDiff (ColorEnable(..)) import           StrUtils (stripNewline)@@ -19,15 +20,15 @@  shouldUseColorByTerminal :: IO ColorEnable shouldUseColorByTerminal =-    do  istty <- hSupportsANSI stdout-        return $ if istty then EnableColor else DisableColor+    hSupportsANSI stdout+    <&> \istty -> if istty then EnableColor else DisableColor  getConflictStyle :: IO String getConflictStyle =     do  (exitCode, output, _) <- readProcessWithExitCode "git" ["config", "merge.conflictstyle"] stdin         case exitCode of-            ExitSuccess -> return $ stripNewline output-            ExitFailure 1 -> return "unset"+            ExitSuccess -> pure $ stripNewline output+            ExitFailure 1 -> pure "unset"             ExitFailure _ -> E.throwIO exitCode     where         stdin = ""@@ -39,8 +40,8 @@ checkConflictStyle :: Options -> IO () checkConflictStyle opts =     do  conflictStyle <- getConflictStyle-        when (conflictStyle /= "diff3") $-            do  unless (shouldSetConflictStyle opts) $+        unless (conflictStyle `elem` ["diff3", "zdiff3"]) $+            do  unless opts.shouldSetConflictStyle $                     fail $ concat                     [ "merge.conflictstyle must be diff3 but is "                     , show conflictStyle@@ -56,9 +57,13 @@                     , "specified in your per-project .git/config?"                     ] -openEditor :: Options -> FilePath -> IO ()-openEditor opts path-    | shouldUseEditor opts =+openEditor :: Options -> FilePath -> Int -> IO ()+openEditor opts path lineNo+    | opts.shouldUseEditor =         do  editor <- getEnv "EDITOR"-            callProcess editor [path]-    | otherwise = return ()+            let cmdOpts =+                    case editor of+                    "code" -> ["--goto", path <> ":" <> show lineNo]+                    _ -> ["+" <> show lineNo, path]+            callProcess editor cmdOpts+    | otherwise = pure ()
src/Main.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE NoImplicitPrelude, OverloadedRecordDot, FlexibleContexts #-}  module Main (main) where @@ -6,6 +6,8 @@ import qualified Conflict import qualified Control.Exception as E import           Control.Monad (when, unless, filterM)+import           Data.Algorithm.Diff (Diff, PolyDiff(..))+import           Data.Either (rights) import           Data.Foldable (asum, traverse_) import           Data.List (isPrefixOf) import           Data.Maybe (mapMaybe)@@ -15,9 +17,9 @@ import           PPDiff (ppDiff, ColorEnable(..)) import           Resolution (Result(..), NewContent(..), Untabify(..)) import qualified Resolution-import           SideDiff (getConflictDiffs, getConflictDiff2s)+import           SideDiff (SideDiff(..), getConflictDiffs, getConflictDiff2s) import           StrUtils (ensureNewline, stripNewline, unprefix)-import           System.Directory (renameFile, removeFile, getCurrentDirectory)+import           System.Directory (renameFile, removeFile, getCurrentDirectory, getPermissions, setPermissions) import           System.Exit (ExitCode(..), exitWith) import           System.FilePath ((<.>), makeRelative, joinPath, splitPath) import qualified System.FilePath as FilePath@@ -27,45 +29,59 @@  import           Prelude.Compat +-- '>' -> ">>>>>>>"+markerPrefix :: Char -> String+markerPrefix = replicate 7+ markerLine :: Char -> String -> String-markerLine c str = Conflict.markerPrefix c ++ " " ++ str ++ "\n"+markerLine c str = markerPrefix c ++ " " ++ str ++ "\n"  gitAdd :: FilePath -> IO () gitAdd fileName =     callProcess "git" ["add", "--", fileName] +trimDiff :: Int -> [Diff a] -> [Diff a]+trimDiff contextLen =+    reverse . f . reverse . f+    where+        f l = drop (length (takeWhile both l) - contextLen) l+        both Both{} = True+        both _ = False+ dumpDiffs :: ColorEnable -> Options -> FilePath -> Int -> (Int, Conflict) -> IO () dumpDiffs colorEnable opts filePath count (idx, conflict) =     do         putStrLn $ unwords ["### Conflict", show idx, "of", show count]-        when (shouldDumpDiffs opts) $ traverse_ dumpDiff $ getConflictDiffs conflict-        when (shouldDumpDiff2 opts) $ dumpDiff2 $ getConflictDiff2s conflict+        when opts.shouldDumpDiffs $ traverse_ dumpDiff $ getConflictDiffs conflict+        when opts.shouldDumpDiff2 $ dumpDiff2 $ getConflictDiff2s conflict     where-        dumpDiff (side, (lineNo, marker), diff) =+        dumpDiff d =             do  putStrLn $ concat-                    [filePath, ":", show lineNo, ":Diff", show side, ": ", marker]-                putStr $ unlines $ map (ppDiff colorEnable) diff-        dumpDiff2 ((lineNoA, markerA), (lineNoB, markerB), diff) =-            do  putStrLn $ concat [filePath, ":", show lineNoA, " <->", markerA]-                putStrLn $ concat [filePath, ":", show lineNoB, ": ", markerB]-                putStr $ unlines $ map (ppDiff colorEnable) diff+                    [filePath, ":", show d.marker.lineNo, ":Diff", show d.side, ": ", d.marker.content]+                putStr $ unlines $ map (ppDiff colorEnable) (trimDiff opts.diffsContext d.diff)+        dumpDiff2 (markerA, markerB, d) =+            do  putStrLn $ concat [filePath, ":", show markerA.lineNo, " <->", markerA.content]+                putStrLn $ concat [filePath, ":", show markerB.lineNo, ": ", markerB.content]+                putStr $ unlines $ map (ppDiff colorEnable) d  dumpAndOpenEditor :: ColorEnable -> Options -> FilePath -> [Conflict] -> IO () dumpAndOpenEditor colorEnable opts path conflicts =-    do  when (shouldDumpDiffs opts || shouldDumpDiff2 opts) $+    do  when (opts.shouldDumpDiffs || opts.shouldDumpDiff2) $             traverse_ (dumpDiffs colorEnable opts path (length conflicts)) (zip [1..] conflicts)-        openEditor opts path+        openEditor opts path ((Conflict.lineNo . Conflict.sideA . markers . head) conflicts)  overwrite :: FilePath -> String -> IO ()-overwrite fileName newContent =-    do  renameFile fileName bkup-        writeFile fileName newContent+overwrite fileName content =+    do  oldPermissions <- getPermissions fileName+        renameFile fileName bkup+        writeFile fileName content+        setPermissions fileName oldPermissions         removeFile bkup     where         bkup = fileName <.> "bk"  handleFileResult :: ColorEnable -> Options -> FilePath -> NewContent -> IO ()-handleFileResult colorEnable opts fileName (NewContent result newContent)+handleFileResult colorEnable opts fileName res     | successes == 0 && allGood =       do  putStrLn $ fileName ++ ": No conflicts, git-adding"           gitAdd fileName@@ -77,7 +93,7 @@     | successes == 0 =       do  putStrLn $ concat               [ fileName, ": Reduced ", show reductions, " conflicts"]-          overwrite fileName newContent+          overwrite fileName res.newContent           doDump     | otherwise =       do  putStrLn $ concat@@ -85,29 +101,29 @@               , " conflicts (failed to resolve " ++ show (reductions + failures) ++ " conflicts)"               , if allGood then ", git adding" else ""               ]-          overwrite fileName newContent+          overwrite fileName res.newContent           if allGood               then gitAdd fileName               else doDump     where-        allGood = Resolution.fullySuccessful result+        allGood = Resolution.fullySuccessful res.result         doDump =             dumpAndOpenEditor colorEnable opts fileName-            [ conflict | Right conflict <- Conflict.parse newContent ]+            (rights (Conflict.parse res.newContent))         Result-            { _resolvedSuccessfully = successes-            , _reducedConflicts = reductions-            , _failedToResolve = failures-            } = result+            { resolvedSuccessfully = successes+            , reducedConflicts = reductions+            , failedToResolve = failures+            } = res.result  resolve :: ColorEnable -> Options -> FilePath -> IO Result resolve colorEnable opts fileName =     do         resolutions <--            Resolution.resolveContent (Untabify (Opts.untabify opts))+            Resolution.resolveContent (Untabify opts.untabify)             . Conflict.parse             <$> readFile fileName-        _result resolutions <$ handleFileResult colorEnable opts fileName resolutions+        resolutions.result <$ handleFileResult colorEnable opts fileName resolutions  relativePath :: FilePath -> FilePath -> FilePath relativePath base path@@ -151,8 +167,8 @@ deleteModifyConflictAddMarkers path =     withAllStageFiles path $ \baseTmp mLocalTmp mRemoteTmp ->     do  baseContent <- readFile baseTmp-        localContent <- maybe (return "") readFile mLocalTmp-        remoteContent <- maybe (return "") readFile mRemoteTmp+        localContent <- foldMap readFile mLocalTmp+        remoteContent <- foldMap readFile mRemoteTmp         overwrite path $             concat             [ markerLine '<' "LOCAL"@@ -167,7 +183,7 @@ deleteModifyConflictHandle :: FilePath -> IO () deleteModifyConflictHandle path =     do  marked <--            any (Conflict.markerPrefix '<' `isPrefixOf`) . lines <$> readFile path+            any (markerPrefix '<' `isPrefixOf`) . lines <$> readFile path         unless marked $             do  putStrLn $ show path ++ " has a delete/modify conflict. Adding conflict markers"                 deleteModifyConflictAddMarkers path@@ -188,7 +204,7 @@             -- "fatal: Not a git repository (or any of the parent directories): .git"             hPutStr stderr statusStderr             exitWith statusCode-        return statusPorcelain+        pure statusPorcelain  getGitRootDir :: IO FilePath getGitRootDir =@@ -202,9 +218,13 @@       rootDir <- getGitRootDir       let rootRelativeFiles =               filterM (fmap not . isDirectory) . map (rootDir </>)+      let decode x =+              case reads x of+              [(r, "")] -> r+              _ -> x       let firstMatchingPrefix :: [String] -> String -> Maybe String           firstMatchingPrefix prefixes =-              asum . traverse unprefix prefixes+              fmap decode . asum . traverse unprefix prefixes       let filesMatchingPrefixes :: [String] -> IO [FilePath]           filesMatchingPrefixes prefixes =               rootRelativeFiles . mapMaybe (firstMatchingPrefix prefixes)@@ -224,17 +244,17 @@        traverse_ deleteModifyConflictHandle deleteModifyConflicts -      result <- filesMatchingPrefixes ["UU ", "AA ", "DA ", "AD ", "DU ", "UD "]+      res <- filesMatchingPrefixes ["UU ", "AA ", "DA ", "AD ", "DU ", "UD "]           >>= foldMap (resolve colorEnable opts)        -- Heuristically delete files that were remove/modify conflict       -- and ended up with empty content       traverse_ removeFileIfEmpty deleteModifyConflicts-      pure result+      pure res  exitCodeOf :: Result -> ExitCode-exitCodeOf result-    | Resolution.fullySuccessful result = ExitSuccess+exitCodeOf res+    | Resolution.fullySuccessful res = ExitSuccess     | otherwise = ExitFailure 111  exitProcess :: Result -> IO ()@@ -243,9 +263,9 @@ main :: IO () main =   do  opts <- Opts.getOpts-      colorEnable <- maybe shouldUseColorByTerminal return (shouldUseColor opts)+      colorEnable <- maybe shouldUseColorByTerminal pure opts.shouldUseColor       checkConflictStyle opts-      case mergeSpecificFile opts of+      case opts.mergeSpecificFile of           Nothing -> mediateAll colorEnable opts           Just path -> resolve colorEnable opts path           >>= exitProcess
src/Opts.hs view
@@ -20,6 +20,7 @@     , shouldSetConflictStyle :: Bool     , untabify :: Maybe Int     , mergeSpecificFile :: Maybe FilePath+    , diffsContext :: Int     }  data CmdArgs = CmdVersion | CmdOptions Options@@ -57,12 +58,14 @@                         <> O.help "Convert tabs to the spaces at the tab stops for the given tab size"                     )                 )-            <*> ( ( Just <$>-                    O.strOption-                    (O.long "merge-file" <> O.short 'f' <> O.help "Merge a specific file")-                  )-                  <|> pure Nothing+            <*> O.optional+                ( O.strOption+                    ( O.long "merge-file" <> O.short 'f' <> O.help "Merge a specific file")                 )+            <*> O.option O.auto+                (O.long "context" <> O.short 'U' <> O.metavar "LINECOUNT" <> O.showDefault <> O.value 3+                    <> O.help "Number of context lines around dumped diffs"+                )             )  opts :: O.ParserInfo CmdArgs@@ -71,7 +74,7 @@     O.fullDesc     <> O.progDesc        "Resolve any git conflicts that have become trivial by editing operations.\n\-       \Go to http://github.com/Peaker/git-mediate for example use."+       \Go to https://github.com/Peaker/git-mediate for example use."     <> O.header "git-mediate - Become a conflicts hero"  getOpts :: IO Options@@ -82,4 +85,4 @@         do             putStrLn $ "git-mediate version " ++ versionString             exitSuccess-    CmdOptions o -> return o+    CmdOptions o -> pure o
src/Resolution.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE NoImplicitPrelude, RecordWildCards, BangPatterns #-}+{-# LANGUAGE NoImplicitPrelude, BangPatterns, DerivingVia, DeriveGeneric, OverloadedRecordDot #-}  module Resolution     ( Result(..)@@ -8,8 +8,10 @@     , fullySuccessful     ) where -import           Conflict (Conflict(..))+import           Conflict (Conflict(..), Sides(..)) import qualified Conflict+import           Data.Foldable (Foldable(..))+import           Generic.Data (Generic, Generically(..))  import           Prelude.Compat @@ -18,28 +20,31 @@     | Resolution String     | PartialResolution String +resolveGen :: Eq a => Sides a -> Maybe a+resolveGen (Sides a base b)+    | a == base = Just b+    | b == base = Just a+    | a == b = Just a+    | otherwise = Nothing+ resolveConflict :: Conflict -> Resolution-resolveConflict conflict@Conflict{..}-    | cBodyA == cBodyBase = Resolution $ unlines cBodyB-    | cBodyB == cBodyBase = Resolution $ unlines cBodyA-    | cBodyA == cBodyB = Resolution $ unlines cBodyA-    | matchTop > 0 || matchBottom > 0 =-        PartialResolution $ unlines $-        take matchTop cBodyA ++-        Conflict.prettyLines conflict-        { cBodyA = unmatched cBodyA-        , cBodyBase = unmatched cBodyBase-        , cBodyB = unmatched cBodyB-        } ++-        takeEnd matchBottom cBodyA-    | otherwise = NoResolution+resolveConflict c =+    case resolveGen c.bodies of+    Just r -> Resolution $ unlines r+    Nothing+        | matchTop > 0 || matchBottom > 0 ->+            PartialResolution $ unlines $+            take matchTop c.bodies.sideA +++            Conflict.prettyLines (Conflict.setEachBody unmatched c) +++            takeEnd matchBottom c.bodies.sideA+        | otherwise -> NoResolution     where         match base a b             | null base = lengthOfCommonPrefix a b             | otherwise = minimum $ map (lengthOfCommonPrefix base) [a, b]-        matchTop = match cBodyBase cBodyA cBodyB+        matchTop = match c.bodies.sideBase c.bodies.sideA c.bodies.sideB         revBottom = reverse . drop matchTop-        matchBottom = match (revBottom cBodyBase) (revBottom cBodyA) (revBottom cBodyB)+        matchBottom = match (revBottom c.bodies.sideBase) (revBottom c.bodies.sideA) (revBottom c.bodies.sideB)         dropEnd count xs = take (length xs - count) xs         takeEnd count xs = drop (length xs - count) xs         unmatched xs = drop matchTop $ dropEnd matchBottom xs@@ -48,9 +53,9 @@ lengthOfCommonPrefix x y = length $ takeWhile id $ zipWith (==) x y  data Result = Result-    { _resolvedSuccessfully :: !Int-    , _reducedConflicts :: !Int-    , _failedToResolve :: !Int+    { resolvedSuccessfully :: !Int+    , reducedConflicts :: !Int+    , failedToResolve :: !Int     }  fullySuccessful :: Result -> Bool@@ -62,14 +67,11 @@ instance Monoid Result where mempty = Result 0 0 0  data NewContent = NewContent-    { _result :: !Result-    , _newContent :: !String+    { result :: !Result+    , newContent :: !String     }--instance Semigroup NewContent where-    NewContent x0 y0 <> NewContent x1 y1 = NewContent (x0<>x1) (y0<>y1)--instance Monoid NewContent where mempty = NewContent mempty mempty+    deriving Generic+    deriving (Semigroup, Monoid) via Generically NewContent  newtype Untabify = Untabify { mUntabifySize :: Maybe Int } @@ -85,16 +87,53 @@         go _ [] = []  untabify :: Int -> Conflict -> Conflict-untabify = Conflict.setBodyStrings . untabifyStr+untabify = Conflict.setStrings . untabifyStr +data LineEnding = LF | CRLF | Mixed+    deriving (Eq, Ord)++lineEnding :: String -> LineEnding+lineEnding x@(_:_) | last x == '\r' = CRLF+lineEnding _ = LF++lineEndings :: [String] -> LineEnding+lineEndings [] = Mixed+lineEndings xs =+    foldl1 f (map lineEnding xs)+    where+        f Mixed _ = Mixed+        f x c+            | x == c = x+            | otherwise = Mixed++allSame :: Eq a => [a] -> Bool+allSame (x:y:rest) = x == y && allSame (y:rest)+allSame _ = True++lineBreakFix :: Conflict -> Conflict+lineBreakFix c+    | any null (toList c.bodies)+    || allSame (toList endings) = c+    | otherwise =+        case resolveGen endings of+        Just LF -> Conflict.setStrings removeCr c+        Just CRLF -> Conflict.setStrings makeCr c+        _ -> c+    where+        endings = fmap lineEndings c.bodies+        removeCr x@(_:_) | last x == '\r' = init x+        removeCr x = x+        makeCr x@(_:_) | last x == '\r' = x+        makeCr x = x <> "\r"+ resolveContent :: Untabify -> [Either String Conflict] -> NewContent-resolveContent (Untabify mUntabifySize) =+resolveContent (Untabify mTabSize) =     foldMap go     where-        untabified = maybe id untabify mUntabifySize+        untabified = maybe id untabify mTabSize         go (Left line) = NewContent mempty (unlines [line])         go (Right conflict) =-            case resolveConflict (untabified conflict) of+            case (resolveConflict . lineBreakFix . untabified) conflict of             NoResolution               -> NewContent (Result 0 0 1)                                           (Conflict.pretty (untabified conflict))             Resolution trivialLines    -> NewContent (Result 1 0 0) trivialLines
src/SideDiff.hs view
@@ -1,11 +1,11 @@-{-# LANGUAGE NoImplicitPrelude, RecordWildCards #-}+{-# LANGUAGE NoImplicitPrelude, OverloadedRecordDot #-}  module SideDiff-    ( SideDiff, Side(..)+    ( SideDiff(..), Side(..)     , getConflictDiffs, getConflictDiff2s     ) where -import           Conflict (Conflict(..), LineNo)+import           Conflict (Conflict(..), Sides(..), SrcContent(..)) import           Data.Algorithm.Diff (Diff, getDiff)  import           Prelude.Compat@@ -13,14 +13,19 @@ data Side = A | B     deriving (Eq, Ord, Show) -type SideDiff = (Side, (LineNo, String), [Diff String])+data SideDiff = SideDiff+    { side :: Side+    , marker :: SrcContent+    , diff :: [Diff String]+    }  getConflictDiffs :: Conflict -> [SideDiff]-getConflictDiffs Conflict{..} =-    [ (A, cMarkerA, getDiff cBodyBase cBodyA)-    | not (null cBodyA) ] ++-    [ (B, (fst cMarkerB, snd cMarkerEnd), getDiff cBodyBase cBodyB)-    | not (null cBodyB) ]+getConflictDiffs c =+    [ SideDiff A c.markers.sideA (getDiff c.bodies.sideBase c.bodies.sideA)+    | not (null c.bodies.sideA) ] +++    [ SideDiff B (SrcContent c.markers.sideB.lineNo c.markerEnd.content) (getDiff c.bodies.sideBase c.bodies.sideB)+    | not (null c.bodies.sideB) ] -getConflictDiff2s :: Conflict -> ((LineNo, String), (LineNo, String), [Diff String])-getConflictDiff2s Conflict{..} = (cMarkerA, cMarkerB, getDiff cBodyA cBodyB)+getConflictDiff2s :: Conflict -> (SrcContent, SrcContent, [Diff String])+getConflictDiff2s c =+    (c.markers.sideA, c.markers.sideB, getDiff c.bodies.sideA c.bodies.sideB)
stack.yaml view
@@ -1,4 +1,4 @@ flags: {} packages: - '.'-resolver: lts-16.0+resolver: lts-21.3