File: Diff.lhs

package info (click to toggle)
darcs 1.0.2-1
  • links: PTS
  • area: main
  • in suites: sarge
  • size: 3,796 kB
  • ctags: 321
  • sloc: haskell: 14,370; sh: 941; ansic: 893; perl: 810; makefile: 49; xml: 14
file content (246 lines) | stat: -rw-r--r-- 9,693 bytes parent folder | download
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
%  Copyright (C) 2002-2003 David Roundy
%
%  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; either version 2, or (at your option)
%  any later version.
%
%  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, write to the Free Software Foundation,
%  Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
\chapter{Diff}

\begin{code}
module Diff ( smart_diff, sync, cmp ) where

import System.Posix
     ( setFileTimes, epochTime )
import IO ( IOMode(ReadMode), hFileSize, hClose )
import Directory ( doesDirectoryExist, doesFileExist,
                   getDirectoryContents,
                 )
import Monad ( liftM, when )
import List ( sort )
import Maybe ( catMaybes )

import AntiMemo ( readAntiMemo )
import FastPackedString ( PackedString, hGetPS, lengthPS, is_funky, nilPS,
                          unlinesPS, nullPS, lastPS,
                        )
import SlurpDirectory ( Slurpy, FileContents, slurp_name, is_dir, is_file,
                        get_dircontents, get_filecontents,
                        get_mtime, get_length,
                        undefined_time, undefined_size,
                      )
import Patch ( Patch, hunk, canonize, join_patches, reorder_and_coalesce,
               submerge_in_dir, flatten, rmfile, rmdir,
               addfile, adddir,
               binary, invert,
             )
import System.IO ( openBinaryFile )
import RepoPrefs ( FileType(..) )
import DarcsFlags ( DarcsFlag(IgnoreTimes,LookForAdds,All) )
import DarcsUtils ( catchall )
#include "impossible.h"
\end{code}

The diff function takes a recursive diff of two slurped-up directory trees.
The code involved is actually pretty trivial.  \verb!paranoid_diff! runs a
diff in which we don't make the assumption that files with the same
modification time are identical.

\begin{code}
smart_diff :: [DarcsFlag]
           -> (FilePath -> FileType) -> Slurpy -> Slurpy -> Maybe Patch
smart_diff opts = gendiff (IgnoreTimes `elem` opts,
                           LookForAdds `elem` opts,
                           All `elem` opts)

gendiff :: (Bool,Bool,Bool)
        -> (FilePath -> FileType) -> Slurpy -> Slurpy -> Maybe Patch
gendiff opts@(isparanoid,_,nosort) wt s1 s2
    | is_file s1 && is_file s2 && maybe_differ =
          case wt n2 of
          TextFile -> diff_files n2 fc1 fc2
          BinaryFile -> if b1 /= b2 then Just $ binary n2 b1 b2
                                    else Nothing
    | is_dir s1 && is_dir s2 =
        case recur_diff opts wt (get_dircontents s1) (get_dircontents s2) of
        [] -> Nothing
        ps -> let sortf = if nosort then id else reorder_and_coalesce
                  in Just $ sortf $ join_patches $ map (submerge_in_dir n2) ps
    | otherwise = Nothing
    where n2 = slurp_name s2
          fc1 = get_filecontents s1
          fc2 = get_filecontents s2
          b1 = getbin fc1
          b2 = getbin fc2
          maybe_differ = isparanoid
                       || get_mtime s1 == undefined_time || get_mtime s1 /= get_mtime s2
                       || get_length s1 == undefined_size || get_length s1 /= get_length s2

recur_diff :: (Bool,Bool,Bool)
               -> (FilePath -> FileType) -> [Slurpy] -> [Slurpy] -> [Patch]
recur_diff _ _ [] [] = []
recur_diff opts@(_,doadd,_) wt (s:ss) (s':ss')
    | s < s' = diff_removed wt s ++ recur_diff opts wt ss (s':ss')
    | s > s' = if not doadd then recur_diff opts wt (s:ss) ss'
               else diff_added wt s' ++ recur_diff opts wt (s:ss) ss'
    | s == s' = case gendiff opts wt s s' of
                Nothing -> rest
                Just p -> flatten p ++ rest
    where rest = recur_diff opts wt ss ss'
recur_diff opts wt (s:ss) [] =
    diff_removed wt s ++ recur_diff opts wt ss []
recur_diff opts@(_,True,_) wt [] (s':ss') =
    diff_added wt s' ++ recur_diff opts wt [] ss'
recur_diff (_,False,_) _ [] _ = []
recur_diff _ _ _ _ = impossible

getbin :: FileContents -> PackedString
getbin (_,Just b) = b
getbin (c,Nothing) = unlinesPS $ readAntiMemo c

get_text :: FileContents -> [PackedString]
get_text (x,_) = readAntiMemo x

empt :: FileContents
empt = (return [nilPS],Just nilPS)

diff_files :: FilePath -> FileContents -> FileContents -> Maybe Patch
diff_files f o n | get_text o == [nilPS] && get_text n == [nilPS] = Nothing
                 | get_text o == [nilPS] = diff_from_empty f n
                 | get_text n == [nilPS] = invert `liftM` diff_from_empty f o
diff_files f o n = if getbin o == getbin n
                   then Nothing
                   else if has_bin o || has_bin n
                        then Just $ binary f (getbin o) (getbin n)
                        else canonize $ hunk f 1 (fst o) (fst n)

diff_from_empty :: FilePath -> FileContents -> Maybe Patch
diff_from_empty f (pls, Nothing) =
    let ls = readAntiMemo pls in
    if null ls then Nothing
               else if nullPS $ last ls
                    then Just $ hunk f 1 (return []) $ init `fmap` pls
                    else Just $ hunk f 1 (return [nilPS]) pls
diff_from_empty f (pls, Just b) =
    if b == nilPS then Nothing
                  else if has_bin (pls, Just b)
                       then Just $ binary f nilPS b
                       else if lastPS b == '\n'
                            then Just $ hunk f 1 (return []) $ init `fmap` pls
                            else Just $ hunk f 1 (return [nilPS]) pls

has_bin :: FileContents -> Bool
has_bin (_,Nothing) = False
has_bin (_,Just b) = is_funky b
\end{code}

\begin{code}
diff_added :: (FilePath -> FileType) -> Slurpy -> [Patch]
diff_added wt s
    | is_file s = case wt n of
                  TextFile -> catMaybes
                              [Just $ addfile n,
                               diff_from_empty n $ get_filecontents s]
                  BinaryFile -> [addfile n,
                                 binary n nilPS (getbin $ get_filecontents s)]
    | otherwise {- is_dir s -} =
        adddir n :(map (submerge_in_dir n) $
                   concatMap (diff_added wt) $ get_dircontents s)
    where n = slurp_name s
\end{code}

\begin{code}
diff_removed :: (FilePath -> FileType) -> Slurpy -> [Patch]
diff_removed wt s
    | is_file s = case wt n of
                  TextFile -> catMaybes
                              [diff_files n (get_filecontents s) empt,
                               Just $ rmfile n]
                  BinaryFile -> [binary n (getbin $ get_filecontents s) nilPS,
                                 rmfile n]
    | otherwise {- is_dir s -}
        = (map (submerge_in_dir n) $
           concatMap (diff_removed wt) $ get_dircontents s) ++ [rmdir n]
    where n = slurp_name s
\end{code}

\begin{code}
sync :: String -> Slurpy -> Slurpy -> IO ()
sync path s1 s2
    | is_file s1 && is_file s2 &&
      (get_mtime s1 == undefined_time || get_mtime s1 /= get_mtime s2) &&
      get_length s1 == get_length s2 &&
      getbin (get_filecontents s1) == getbin (get_filecontents s2) =
        set_mtime n (get_mtime s2)
    | is_dir s1 && is_dir s2 = recur_sync n (get_dircontents s1) (get_dircontents s2)
    | otherwise = return ()
    where n = path++"/"++slurp_name s2
          set_mtime fname ctime = do now <- epochTime
                                     setFileTimes fname now ctime
                                                      `catchall` return ()
          recur_sync _ [] _ = return ()
          recur_sync _ _ [] = return ()
          recur_sync p (s:ss) (s':ss')
              | s < s' = recur_sync p ss (s':ss')
              | s > s' = recur_sync p (s:ss) ss'
              | otherwise = do sync p s s'
                               recur_sync p ss ss'
\end{code}


\begin{code}
cmp :: FilePath -> FilePath -> IO Bool
cmp p1 p2 = do
  dir1 <- doesDirectoryExist p1
  dir2 <- doesDirectoryExist p2
  file1 <- doesFileExist p1
  file2 <- doesFileExist p2
  if dir1 && dir2
     then cmpdir p1 p2
     else if file1 && file2
          then cmpfile p1 p2
          else return False
cmpdir :: FilePath -> FilePath -> IO Bool
cmpdir d1 d2 = do
  fn1 <- liftM (filter (\f->f/="." && f /="..")) $ getDirectoryContents d1
  fn2 <- liftM (filter (\f->f/="." && f /="..")) $ getDirectoryContents d2
  if sort fn1 /= sort fn2
     then return False
     else andIO $ map (\fn-> cmp (d1++"/"++fn) (d2++"/"++fn)) fn1
andIO :: [IO Bool] -> IO Bool
andIO (iob:iobs) = do b <- iob
                      if b then andIO iobs else return False
andIO [] = return True
cmpfile :: FilePath -> FilePath -> IO Bool
cmpfile f1 f2 = do
  h1 <- openBinaryFile f1 ReadMode
  h2 <- openBinaryFile f2 ReadMode
  l1 <- hFileSize h1
  l2 <- hFileSize h2
  if l1 /= l2
     then do hClose h1
             hClose h2
             putStrLn $ "different file lengths for "++f1++" and "++f2
             return False
     else do b <- hcmp h1 h2
             when (not b) $ putStrLn $ "files "++f1++" and "++f2++" differ"
             hClose h1
             hClose h2
             return b
    where hcmp h1 h2 = do c1 <- hGetPS h1 1024
                          c2 <- hGetPS h2 1024
                          if c1 /= c2
                             then return False
                             else if lengthPS c1 == 1024
                                  then hcmp h1 h2
                                  else return True
\end{code}