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. |
---|
21 | Return 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. |
---|
93 | Use FILENAME as their filename. |
---|
94 | Return a list of one BINARY-PATCH, or an empty list if |
---|
95 | the 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. |
---|
119 | Return a list of patches. |
---|
120 | ORIGINAL 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 | |
---|