]> code.delx.au - gnu-emacs/commitdiff
(smerge-apply-resolution-patch): New fun.
authorStefan Monnier <monnier@iro.umontreal.ca>
Mon, 31 Mar 2008 02:57:39 +0000 (02:57 +0000)
committerStefan Monnier <monnier@iro.umontreal.ca>
Mon, 31 Mar 2008 02:57:39 +0000 (02:57 +0000)
(smerge-resolve): Add various resolution heuristics.

lisp/ChangeLog
lisp/smerge-mode.el

index 4131847e17ffebf1671f4d679f3b39dad5cf6f7f..d8d5d73a1b797edeec61df43769434f10e08ccbb 100644 (file)
@@ -1,5 +1,8 @@
 2008-03-31  Stefan Monnier  <monnier@iro.umontreal.ca>
 
+       * smerge-mode.el (smerge-apply-resolution-patch): New fun.
+       (smerge-resolve): Add various resolution heuristics.
+
        * smerge-mode.el (smerge-refine): Allow highlighting other subparts
        in 3-way conflicts.
 
index ab8196c80c5db15dda20079eae5cd761be01267d..c80ae06a04fcbf16c5e80856639db671db802985 100644 (file)
@@ -407,6 +407,62 @@ according to `smerge-match-conflict'.")
        (smerge-remove-props (or beg (point-min)) (or end (point-max)))
        (push event unread-command-events)))))
 
+(defun smerge-apply-resolution-patch (buf m0b m0e m3b m3e &optional m2b)
+  "Replace the conflict with a bunch of subconflicts.
+BUF contains a plain diff between match-1 and match-3."
+  (let ((line 1)
+        (textbuf (current-buffer))
+        (name1 (progn (goto-char m0b)
+                      (buffer-substring (+ (point) 8) (line-end-position))))
+        (name2 (when m2b (goto-char m2b) (forward-line -1)
+                     (buffer-substring (+ (point) 8) (line-end-position))))
+        (name3 (progn (goto-char m0e) (forward-line -1)
+                      (buffer-substring (+ (point) 8) (line-end-position)))))
+    (smerge-remove-props m0b m0e)
+    (delete-region m3e m0e)
+    (delete-region m0b m3b)
+    (setq m3b m0b)
+    (setq m3e (- m3e (- m3b m0b)))
+    (goto-char m3b)
+    (with-current-buffer buf
+      (goto-char (point-min))
+      (while (not (eobp))
+        (if (not (looking-at "\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)?\\([acd]\\)\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)?$"))
+            (error "Unexpected patch hunk header: %s"
+                   (buffer-substring (point) (line-end-position)))
+          (let* ((op (char-after (match-beginning 3)))
+                 (startline (+ (string-to-number (match-string 1))
+                               ;; No clue why this is the way it is, but line
+                               ;; numbers seem to be off-by-one for `a' ops.
+                               (if (eq op ?a) 1 0)))
+                 (endline (if (eq op ?a) startline
+                            (1+ (if (match-end 2)
+                                    (string-to-number (match-string 2))
+                                  startline))))
+                 (lines (- endline startline))
+                 (otherlines (cond
+                              ((eq op ?d) nil)
+                              ((null (match-end 5)) 1)
+                              (t (- (string-to-number (match-string 5))
+                                    (string-to-number (match-string 4)) -1))))
+                 othertext)
+            (forward-line 1)                             ;Skip header.
+            (forward-line lines)                         ;Skip deleted text.
+            (if (eq op ?c) (forward-line 1))             ;Skip separator.
+            (setq othertext
+                  (if (null otherlines) ""
+                    (let ((pos (point)))
+                      (dotimes (i otherlines) (delete-char 2) (forward-line 1))
+                      (buffer-substring pos (point)))))
+            (with-current-buffer textbuf
+              (forward-line (- startline line))
+              (insert "<<<<<<< " name1 "\n" othertext
+                      (if name2 (concat "||||||| " name2))
+                      "=======\n")
+              (forward-line lines)
+              (insert ">>>>>>> " name3 "\n")
+              (setq line endline))))))))
+
 (defun smerge-resolve (&optional safe)
   "Resolve the conflict at point intelligently.
 This relies on mode-specific knowledge and thus only works in
@@ -414,33 +470,101 @@ some major modes.  Uses `smerge-resolve-function' to do the actual work."
   (interactive)
   (smerge-match-conflict)
   (smerge-remove-props (match-beginning 0) (match-end 0))
-  (cond
-   ;; Trivial diff3 -A non-conflicts.
-   ((and (eq (match-end 1) (match-end 3))
-        (eq (match-beginning 1) (match-beginning 3)))
-    (smerge-keep-n 3))
-   ;; Mode-specific conflict resolution.
-   ((condition-case nil
-        (atomic-change-group
-          (if safe
-              (funcall smerge-resolve-function safe)
-            (funcall smerge-resolve-function))
-          t)
-      (error nil))
-    ;; Nothing to do: the resolution function has done it already.
-    nil)
-   ;; FIXME: Add "if [ diff -b MINE OTHER ]; then select OTHER; fi"
-   ((and (match-end 2)
-        ;; FIXME: Add "diff -b BASE MINE | patch OTHER".
-        ;; FIXME: Add "diff -b BASE OTHER | patch MINE".
-        nil)
-    )
-   ((and (not (match-end 2))
-        ;; FIXME: Add "diff -b"-based refinement.
-        nil)
-    )
-   (t
-    (error "Don't know how to resolve")))
+  (let ((md (match-data))
+       (m0b (match-beginning 0))
+       (m1b (match-beginning 1))
+       (m2b (match-beginning 2))
+       (m3b (match-beginning 3))
+       (m0e (match-end 0))
+       (m1e (match-end 1))
+       (m2e (match-end 2))
+       (m3e (match-end 3))
+       (buf (generate-new-buffer " *smerge*"))
+        m b o)
+    (unwind-protect
+       (progn
+          (cond
+           ;; Trivial diff3 -A non-conflicts.
+           ((and (eq (match-end 1) (match-end 3))
+                 (eq (match-beginning 1) (match-beginning 3)))
+            (smerge-keep-n 3))
+           ;; Mode-specific conflict resolution.
+           ((condition-case nil
+                (atomic-change-group
+                  (if safe
+                      (funcall smerge-resolve-function safe)
+                    (funcall smerge-resolve-function))
+                  t)
+              (error nil))
+            ;; Nothing to do: the resolution function has done it already.
+            nil)
+          ;; "Mere whitespace" conflicts.
+          ((or (and (eq m1e m3e) (eq m1b m3b)) ;Non-conflict.
+               (progn
+                 (setq m (make-temp-file "smm"))
+                 (write-region m1b m1e m nil 'silent)
+                 (setq o (make-temp-file "smo"))
+                 (write-region m3b m3e o nil 'silent)
+                  ;; Same patch applied on both sides, with whitespace changes.
+                 (zerop (call-process diff-command nil nil nil "-b" m o)))
+               (when m2e
+                 (setq b (make-temp-file "smb"))
+                 (write-region m2b m2e b nil 'silent)
+                  ;; Only minor whitespace changes made locally.
+                 (zerop (call-process diff-command nil buf nil "-bc" b m))))
+           (set-match-data md)
+           (smerge-keep-n 3))
+           ;; Refine a 2-way conflict using "diff -b".
+           ;; In case of a 3-way conflict with an empty base
+           ;; (i.e. 2 conflicting additions), we do the same, presuming
+           ;; that the 2 additions should be somehow merged rather
+           ;; than concatenated.
+          ((not (or (and m2b (not (eq m2b m2e)))
+                     (eq m1b m1e) (eq m3b m3e)
+                     (let ((lines (count-lines m3b m3e)))
+                       (call-process diff-command nil buf nil "-b" o m)
+                       (with-current-buffer buf
+                         (goto-char (point-min))
+                         ;; Make sure there's some refinement.
+                         (looking-at
+                          (concat "1," (number-to-string lines) "c"))))))
+            (smerge-apply-resolution-patch buf m0b m0e m3b m3e m2b))
+          ;; Try "diff -b BASE MINE | patch OTHER".
+          ((when (and (not safe) m2e b
+                       ;; If the BASE is empty, this would just concatenate
+                       ;; the two, which is rarely right.
+                       (not (eq m2b m2e)))
+             (with-current-buffer buf
+               (zerop (call-process-region
+                       (point-min) (point-max) "patch" t nil nil
+                       "-r" "/dev/null" "--no-backup-if-mismatch"
+                       "-fl" o))))
+           (save-restriction
+             (narrow-to-region m0b m0e)
+              (smerge-remove-props m0b m0e)
+             (insert-file-contents o nil nil nil t)))
+          ;; Try "diff -b BASE OTHER | patch MINE".
+          ((when (and (not safe) m2e b
+                       ;; If the BASE is empty, this would just concatenate
+                       ;; the two, which is rarely right.
+                       (not (eq m2b m2e)))
+             (write-region m3b m3e o nil 'silent)
+             (call-process diff-command nil buf nil "-bc" b o)
+             (with-current-buffer buf
+               (zerop (call-process-region
+                       (point-min) (point-max) "patch" t nil nil
+                       "-r" "/dev/null" "--no-backup-if-mismatch"
+                       "-fl" m))))
+           (save-restriction
+             (narrow-to-region m0b m0e)
+              (smerge-remove-props m0b m0e)
+             (insert-file-contents m nil nil nil t)))
+           (t
+            (error "Don't know how to resolve"))))
+      (if (buffer-name buf) (kill-buffer buf))
+      (if m (delete-file m))
+      (if b (delete-file b))
+      (if o (delete-file o))))
   (smerge-auto-leave))
 
 (defun smerge-resolve-all ()