source: cl-darcs/tags/0.2.0/diff.lisp

Last change on this file was 101, checked in by Magnus Henoch, 17 years ago

Tag 0.2.0

File size: 6.3 KB
Line 
1;;; Copyright (C) 2006 Magnus Henoch
2;;;
3;;; This program is free software; you can redistribute it and/or
4;;; modify it under the terms of the GNU General Public License as
5;;; published by the Free Software Foundation; either version 2 of the
6;;; License, or (at your option) any later version.
7;;;
8;;; This program is distributed in the hope that it will be useful,
9;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
10;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11;;; General Public License for more details.
12;;;
13;;; You should have received a copy of the GNU General Public License
14;;; along with this program; if not, write to the Free Software
15;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
16
17(in-package :darcs)
18
19(defun diff-file (original modified &key filename)
20  "Find changes between ORIGINAL and MODIFIED.
21Return a list of HUNK-PATCHes.  Use FILENAME as their filename."
22  (setf original (make-upath original))
23  (setf modified (make-upath modified))
24  (let* ((original-lines
25          (if (fad:file-exists-p original)
26              (with-open-stream (in (open-upath original :binary t))
27                (loop for line = (read-binary-line in nil)
28                   while line collect line))
29              :nonexistent))
30         (modified-lines
31          (if (fad:file-exists-p modified)
32              (with-open-stream (in (open-upath modified :binary t))
33                (loop for line = (read-binary-line in nil)
34                   while line collect line))
35              :nonexistent))
36
37         ;; using equalp is safe (i.e. non-case-clobbering), as
38         ;; we use bytes instead of characters
39         (opcodes (when (and (listp original-lines)
40                             (listp modified-lines))
41                    (difflib:get-opcodes
42                     (make-instance 'difflib:sequence-matcher
43                                    :a original-lines
44                                    :b modified-lines
45                                    :test-function #'equalp))))
46
47         patches)
48    (cond
49      ((and (eql original-lines :nonexistent)
50            (eql modified-lines :nonexistent))
51       (error "Neither ~A nor ~A exist." original modified))
52      ((eql original-lines :nonexistent)
53       ;; Newly created file
54       ;; XXX: should we automatically add such files?
55       (list
56        (make-instance 'add-file-patch
57                       :filename filename)
58        (make-instance 'hunk-patch
59                       :filename filename
60                       :line-number 1
61                       :old ()
62                       :new modified-lines)))
63      ((eql modified-lines :nonexistent)
64       ;; Removed file
65       (list
66        (make-instance 'hunk-patch
67                       :filename filename
68                       :line-number 1
69                       :old original-lines
70                       :new ())
71        (make-instance 'rm-file-patch
72                       :filename filename)))
73      (t
74       ;; Possibly changed file
75       (dolist (opcode opcodes)
76         (unless (eql (difflib:opcode-tag opcode) :equal)
77           (push 
78            (make-instance 'hunk-patch
79                           :filename filename
80                           :line-number (1+ (difflib:opcode-j1 opcode))
81                           :old (subseq original-lines
82                                        (difflib:opcode-i1 opcode)
83                                        (difflib:opcode-i2 opcode))
84                           :new (subseq modified-lines
85                                        (difflib:opcode-j1 opcode)
86                                        (difflib:opcode-j2 opcode)))
87            patches)))
88
89       (nreverse patches)))))
90
91(defun diff-binary-file (original modified &key filename)
92  "Find changes between binary files ORIGINAL and MODIFIED.
93Use FILENAME as their filename.
94Return a list of one BINARY-PATCH, or an empty list if
95the files are equal."
96  (with-open-file (o original
97                      :direction :input :if-does-not-exist :error
98                      :element-type '(unsigned-byte 8))
99    (with-open-file (m modified
100                       :direction :input :if-does-not-exist :error
101                       :element-type '(unsigned-byte 8))
102      (let ((o-contents
103             (make-array (file-length o)
104                         :element-type '(unsigned-byte 8)))
105            (m-contents
106             (make-array (file-length m)
107                         :element-type '(unsigned-byte 8))))
108        (read-sequence o-contents o)
109        (read-sequence m-contents m)
110        (unless (equalp o-contents m-contents)
111          (list
112           (make-instance 'binary-patch
113                          :filename filename
114                          :oldhex o-contents
115                          :newhex m-contents)))))))
116
117(defun diff-repo (repo &optional original modified)
118  "Find changes in REPO from pristine tree.
119Return a list of patches.
120ORIGINAL and MODIFIED specify directories to start from."
121  (setf repo (fad:pathname-as-directory repo))
122  (unless (and original modified)
123    (setf modified repo)
124    (setf original (upath-subdir repo '("_darcs" "pristine"))))
125
126  (let* ((wild (make-pathname :directory '(:relative :wild-inferiors)
127                              :name :wild
128                              :type :wild
129                              :version :wild))
130         (repo-wild (merge-pathnames wild repo))
131         (pristine (upath-subdir repo '("_darcs" "pristine")))
132         (pristine-wild (merge-pathnames wild pristine))
133         (original-wild (merge-pathnames wild original))
134         (modified-wild (merge-pathnames wild modified))
135         patches)
136    ;; XXX: check if both directories exist
137
138    ;; With fad:list-directory, we get absolute pathnames.  We make
139    ;; them relative to the "root", so they can be compared.
140    (flet ((original-to-repo-relative (p)
141             (pathname (enough-namestring p pristine)))
142           (modified-to-repo-relative (p)
143             (pathname (enough-namestring p repo))))
144      ;; We list the files in the current directory, both in the
145      ;; original and the modified tree, and get the union.
146      (let* ((files-in-original 
147              (mapcar #'original-to-repo-relative
148                      (fad:list-directory original)))
149             (files-in-modified 
150              (mapcar #'modified-to-repo-relative
151                      (fad:list-directory modified)))
152             (files (nunion files-in-original files-in-modified
153                            :test #'equal)))
154        ;; Then we iterate through the union.
155        (dolist (file files)
156          (let ((original-pathname 
157                 (merge-pathnames file pristine))
158                (modified-pathname
159                 (merge-pathnames file repo))
160                (pathname-string
161                 (pathname-to-string file)))
162            (unless (file-boring-p repo pathname-string)
163              (cond
164                ((fad:directory-pathname-p file)
165                 (setf patches (nconc patches
166                                      (diff-repo repo original-pathname modified-pathname))))
167
168                ((file-binary-p repo pathname-string)
169                 (setf patches (nconc patches
170                                      (diff-binary-file original-pathname
171                                                        modified-pathname
172                                                        :filename pathname-string))))
173
174                (t
175                 (setf patches (nconc patches 
176                                      (diff-file original-pathname
177                                                 modified-pathname
178                                                 :filename pathname-string))))))))
179
180        patches))))
181
182(defun diff-repo-display (repo)
183  "Find changes in REPO and print them to *STANDARD-OUTPUT*."
184  (dolist (patch (diff-repo repo))
185    (display-patch patch *standard-output*)))
186
Note: See TracBrowser for help on using the repository browser.