source: cl-darcs/tags/0.2.0/commute.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(defgeneric commute (p2 p1)
20  (:documentation "Attempt to commute the patches P2 and P1.
21Return a list, (P1-NEW P2-NEW), such that applying P2-NEW and then
22P1-NEW has the same effect as applying P1 and then P2.
23If commutations fails, return nil."))
24
25(defmethod commute ((p2 patch) (p1 patch))
26  "Default method prints a warning and returns nil."
27  (warn "No method defined for commuting ~A and ~A." p2 p1)
28  nil)
29
30(defmethod commute ((p2 named-patch) (p1 patch))
31  "Commute a named patch and another patch."
32  (destructuring-bind (&optional p1-new p2-new)
33      (commute (named-patch-patch p2) p1)
34    (if p1-new
35        (list p1-new
36              (make-instance 'named-patch
37                             :patchinfo (named-patch-patchinfo p2)
38                             :dependencies (named-patch-dependencies p2)
39                             :patch p2-new))
40        (call-next-method))))
41
42(defmethod commute ((p2 patch) (p1 named-patch))
43  "Commute a patch with a named patch."
44  (destructuring-bind (&optional p1-new p2-new)
45      (commute p2 (named-patch-patch p1))
46    (if p1-new
47        (list (make-instance 'named-patch
48                             :patchinfo (named-patch-patchinfo p1)
49                             :dependencies (named-patch-dependencies p1)
50                             :patch p1-new)
51              p2-new)
52        (call-next-method))))
53
54(defmethod commute ((p2 move-patch) (p1 file-patch))
55  "Commute a move patch with a file patch."
56  (let ((patched-file (patch-filename p1))
57        (moved-from (patch-move-from p2))
58        (moved-to (patch-move-to p2)))
59    (cond
60      ;; File was patched and then moved
61      ((equal patched-file moved-from)
62       (let ((p1-new (copy-patch p1)))
63         (setf (patch-filename p1-new) moved-to)
64         (list p1-new p2)))
65      ;; Another file moved on top of original file
66      ((equal patched-file moved-to)
67       (warn "Collision when commuting ~A and ~A." p2 p1)
68       nil)
69      ;; Patches touch different files
70      (t
71       (list p1 p2)))))
72
73(defmethod commute ((p2 file-patch) (p1 move-patch))
74  "Commute a file patch with a move patch."
75  (let ((moved-from (patch-move-from p1))
76        (moved-to (patch-move-to p1))
77        (patched-file (patch-filename p2)))
78    (cond
79      ;; File was moved and then patched
80      ((equal moved-to patched-file)
81       (let ((p2-new (copy-patch p2)))
82         (setf (patch-filename p2-new) moved-from)
83         (list p1 p2-new)))
84      ;; File was moved before being patched
85      ((equal moved-from patched-file)
86       (warn "Collision when commuting ~A and ~A." p2 p1)
87       nil)
88      ;; Patches touch different files
89      (t
90       (list p1 p2)))))
91
92(defmethod commute :around ((p2 file-patch) (p1 file-patch))
93  "If P1 and P2 change different files, commutation is trivial."
94  (let ((p1-file (patch-filename p1))
95        (p2-file (patch-filename p2)))
96    (if (not (equal p1-file p2-file))
97        (list p1 p2)
98        (call-next-method))))
99
100(defmethod commute ((p2 hunk-patch) (p1 hunk-patch))
101  "Attempt to commute the two hunk patches P1 and P2."
102  (assert (equal (patch-filename p1) (patch-filename p2)))
103  (with-accessors ((line1 hunk-line-number)
104                   (old1 hunk-old-lines)
105                   (new1 hunk-new-lines)) p1
106    (with-accessors ((line2 hunk-line-number)
107                      (old2 hunk-old-lines)
108                      (new2 hunk-new-lines)) p2
109      (cond
110        ((< (+ line1 (length new1)) line2)
111         ;; The first patch changes text before the second patch.
112         (list p1
113               (make-instance 'hunk-patch :filename (patch-filename p2)
114                              :line-number (+ line2 (- (length new1)) (length old1))
115                              :old old2 :new new2)))
116        ((< (+ line2 (length old2)) line1)
117         ;; The second patch changes text before the first patch.
118         (list (make-instance 'hunk-patch :filename (patch-filename p1)
119                              :line-number (+ line1 (length new2) (- (length old2)))
120                              :old old1 :new new1)
121               p2))
122        ((and (= (+ line1 (length new1)) line2)
123              (notany #'zerop
124                      (mapcar #'length (list old1 old2 new1 new2))))
125         ;; The first patch goes exactly until the beginning of the second patch.
126         (list p1
127               (make-instance 'hunk-patch :filename (patch-filename p2)
128                              :line-number (+ line2 (- (length new1)) (length old1))
129                              :old old2 :new new2)))
130        ((and (= (+ line2 (length old2)) line1)
131              (notany #'zerop
132                      (mapcar #'length (list old1 old2 new1 new2))))
133         ;; The second patch goes exactly until the beginning of the first patch.
134         (list (make-instance 'hunk-patch :filename (patch-filename p1)
135                              :line-number (+ line1 (length new2) (- (length old2)))
136                              :old old1 :new new1)
137               p2))
138        (t
139         ;; In other cases, there is no failsafe way to commute the
140         ;; patches, so we give up.
141         nil)))))
142
143(defmethod commute ((p2 composite-patch) (p1 patch))
144  (cond
145    ;; Simple case first...
146    ((null (patches p2))
147     (list p1 p2))
148    (t
149     ;; Now, p1 was committed before all the patches in p2, and we
150     ;; want it to come after.
151     (let ((p2s (patches p2))
152           p2s-new)
153       (loop for p in p2s
154          do (destructuring-bind (&optional p1-new p-new)
155                 (commute p p1)
156               (cond
157                 ((null p1-new)
158                  (return-from commute (call-next-method)))
159                 (t
160                  (setf p1 p1-new)
161                  (push p-new p2s-new)))))
162       (list p1 (make-instance 'composite-patch :patches (nreverse p2s-new)))))))
163(defmethod commute ((p2 patch) (p1 composite-patch))
164  (cond
165    ((null (patches p1))
166     (list p1 p2))
167    (t
168     ;; p2 was committed after all the patches in p1.  Thus we start
169     ;; backwards in p1, commuting p2 with each of the patches.
170     (let ((p1s (reverse (patches p1)))
171           p1s-new)
172       (loop for p in p1s
173          do (destructuring-bind (&optional p-new p2-new)
174                 (commute p2 p)
175               (cond
176                 ((null p-new)
177                  (return-from commute (call-next-method)))
178                 (t
179                  (setf p2 p2-new)
180                  (push p-new p1s-new)))))
181       (list (make-instance 'composite-patch :patches p1s-new)
182             p2)))))
Note: See TracBrowser for help on using the repository browser.