* obsolete/mouse-sel.el: Add an Obsolete-since header.
[bpt/emacs.git] / admin / bzrmerge.el
CommitLineData
f6041baa 1;;; bzrmerge.el --- help merge one Emacs bzr branch to another
9ebea0e7 2
acaf905b 3;; Copyright (C) 2010-2012 Free Software Foundation, Inc.
9ebea0e7
SM
4
5;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
6;; Keywords:
7
529ee9ed 8;; GNU Emacs is free software: you can redistribute it and/or modify
9ebea0e7
SM
9;; it under the terms of the GNU General Public License as published by
10;; the Free Software Foundation, either version 3 of the License, or
11;; (at your option) any later version.
12
529ee9ed 13;; GNU Emacs is distributed in the hope that it will be useful,
9ebea0e7
SM
14;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16;; GNU General Public License for more details.
17
18;; You should have received a copy of the GNU General Public License
529ee9ed 19;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
9ebea0e7
SM
20
21;;; Commentary:
22
f6041baa 23;; Some usage notes are in admin/notes/bzr.
9ebea0e7
SM
24
25;;; Code:
26
1851cac3
GM
27(eval-when-compile
28 (require 'cl)) ; assert
29
d0cb8662
GM
30(defvar bzrmerge-skip-regexp
31 "back[- ]?port\\|merge\\|sync\\|re-?generate\\|bump version"
32 "Regexp matching logs of revisions that might be skipped.
33`bzrmerge-missing' will ask you if it should skip any matches.")
34
f6041baa
GM
35(defconst bzrmerge-buffer "*bzrmerge*"
36 "Working buffer for bzrmerge.")
37
4d881c83
GM
38(defconst bzrmerge-warning-buffer "*bzrmerge warnings*"
39 "Buffer where bzrmerge will display any warnings.")
40
9ebea0e7 41(defun bzrmerge-merges ()
1851cac3 42 "Return the list of already merged (not yet committed) revisions.
9ebea0e7 43The list returned is sorted by oldest-first."
f6041baa 44 (with-current-buffer (get-buffer-create bzrmerge-buffer)
9ebea0e7
SM
45 (erase-buffer)
46 ;; We generally want to make sure we start with a clean tree, but we also
47 ;; want to allow restarts (i.e. with some part of FROM already merged but
48 ;; not yet committed).
49 (call-process "bzr" nil t nil "status" "-v")
50 (goto-char (point-min))
51 (when (re-search-forward "^conflicts:\n" nil t)
52 (error "You still have unresolved conflicts"))
53 (let ((merges ()))
54 (if (not (re-search-forward "^pending merges:\n" nil t))
55 (when (save-excursion
56 (goto-char (point-min))
57 (re-search-forward "^[a-z ]*:\n" nil t))
58 (error "You still have uncommitted changes"))
59 ;; This is really stupid, but it seems there's no easy way to figure
60 ;; out which revisions have been merged already. The only info I can
61 ;; find is the "pending merges" from "bzr status -v", which is not
62 ;; very machine-friendly.
63 (while (not (eobp))
64 (skip-chars-forward " ")
65 (push (buffer-substring (point) (line-end-position)) merges)
66 (forward-line 1)))
67 merges)))
68
69(defun bzrmerge-check-match (merge)
70 ;; Make sure the MERGES match the revisions on the FROM branch.
71 ;; Stupidly the best form of MERGES I can find is the one from
72 ;; "bzr status -v" which is very machine non-friendly, so I have
73 ;; to do some fuzzy matching.
74 (let ((author
75 (or
76 (save-excursion
77 (if (re-search-forward "^author: *\\([^<]*[^ ]\\) +<.*"
78 nil t)
79 (match-string 1)))
80 (save-excursion
81 (if (re-search-forward
82 "^committer: *\\([^<]*[^< ]\\) +<" nil t)
83 (match-string 1)))))
84 (timestamp
85 (save-excursion
86 (if (re-search-forward
87 "^timestamp:[^0-9]*\\([-0-9]+\\)" nil t)
88 (match-string 1))))
89 (line1
90 (save-excursion
91 (if (re-search-forward "^message:[ \n]*" nil t)
92 (buffer-substring (point) (line-end-position))))))
93 ;; The `merge' may have a truncated line1 with "...", so get
94 ;; rid of any "..." and then look for a prefix match.
95 (when (string-match "\\.+\\'" merge)
96 (setq merge (substring merge 0 (match-beginning 0))))
97 (or (string-prefix-p
98 merge (concat author " " timestamp " " line1))
99 (string-prefix-p
100 merge (concat author " " timestamp " [merge] " line1)))))
101
102(defun bzrmerge-missing (from merges)
103 "Return the list of revisions that need to be merged.
104MERGES is the revisions already merged but not yet committed.
d0cb8662 105Asks about skipping revisions with logs matching `bzrmerge-skip-regexp'.
9ebea0e7
SM
106The result is of the form (TOMERGE . TOSKIP) where TOMERGE and TOSKIP
107are both lists of revnos, in oldest-first order."
f6041baa 108 (with-current-buffer (get-buffer-create bzrmerge-buffer)
9ebea0e7
SM
109 (erase-buffer)
110 (call-process "bzr" nil t nil "missing" "--theirs-only"
111 (expand-file-name from))
112 (let ((revnos ()) (skipped ()))
113 (pop-to-buffer (current-buffer))
114 (goto-char (point-max))
115 (while (re-search-backward "^------------------------------------------------------------\nrevno: \\([0-9.]+\\).*" nil t)
116 (save-excursion
117 (if merges
118 (while (not (bzrmerge-check-match (pop merges)))
119 (unless merges
120 (error "Unmatched tip of merged revisions")))
121 (let ((case-fold-search t)
122 (revno (match-string 1))
123 (skip nil))
124 (if (string-match "\\." revno)
125 (error "Unexpected dotted revno!")
126 (setq revno (string-to-number revno)))
127 (re-search-forward "^message:\n")
128 (while (and (not skip)
d0cb8662 129 (re-search-forward bzrmerge-skip-regexp nil t))
9ebea0e7
SM
130 (let ((str (buffer-substring (line-beginning-position)
131 (line-end-position))))
132 (when (string-match "\\` *" str)
133 (setq str (substring str (match-end 0))))
134 (when (string-match "[.!;, ]+\\'" str)
135 (setq str (substring str 0 (match-beginning 0))))
54de86ac
GM
136 (let ((help-form "\
137Type `y' to skip this revision,
138`N' to include it and go on to the next revision,
139`n' to not skip, but continue to search this log entry for skip regexps,
140`q' to quit merging."))
141 (case (save-excursion
142 (read-char-choice
143 (format "%s: Skip (y/n/N/q/%s)? " str
144 (key-description (vector help-char)))
145 '(?y ?n ?N ?q)))
146 (?y (setq skip t))
147 (?q (keyboard-quit))
148 ;; A single log entry can match skip-regexp multiple
149 ;; times. If you are sure you don't want to skip it,
150 ;; you don't want to be asked multiple times.
151 (?N (setq skip 'no))))))
152 (if (eq skip t)
9ebea0e7
SM
153 (push revno skipped)
154 (push revno revnos)))))
155 (delete-region (point) (point-max)))
f6041baa
GM
156 (and (or revnos skipped)
157 (cons (nreverse revnos) (nreverse skipped))))))
9ebea0e7
SM
158
159(defun bzrmerge-resolve (file)
160 (unless (file-exists-p file) (error "Bzrmerge-resolve: Can't find %s" file))
161 (with-demoted-errors
162 (let ((exists (find-buffer-visiting file)))
2a35386d
GM
163 (with-current-buffer (let ((enable-local-variables :safe))
164 (find-file-noselect file))
9ebea0e7
SM
165 (if (buffer-modified-p)
166 (error "Unsaved changes in %s" (current-buffer)))
167 (save-excursion
168 (cond
169 ((derived-mode-p 'change-log-mode)
170 ;; Fix up dates before resolving the conflicts.
171 (goto-char (point-min))
172 (let ((diff-auto-refine-mode nil))
173 (while (re-search-forward smerge-begin-re nil t)
174 (smerge-match-conflict)
175 (smerge-ensure-match 3)
176 (let ((start1 (match-beginning 1))
177 (end1 (match-end 1))
178 (start3 (match-beginning 3))
179 (end3 (copy-marker (match-end 3) t)))
180 (goto-char start3)
181 (while (re-search-forward change-log-start-entry-re end3 t)
182 (let* ((str (match-string 0))
183 (newstr (save-match-data
184 (concat (add-log-iso8601-time-string)
185 (when (string-match " *\\'" str)
186 (match-string 0 str))))))
187 (replace-match newstr t t)))
188 ;; change-log-resolve-conflict prefers to put match-1's
189 ;; elements first (for equal dates), whereas we want to put
190 ;; match-3's first.
191 (let ((match3 (buffer-substring start3 end3))
192 (match1 (buffer-substring start1 end1)))
193 (delete-region start3 end3)
194 (goto-char start3)
195 (insert match1)
196 (delete-region start1 end1)
197 (goto-char start1)
198 (insert match3)))))
199 ;; (pop-to-buffer (current-buffer)) (debug 'before-resolve)
200 ))
201 ;; Try to resolve the conflicts.
202 (cond
6fffc900
GM
203 ((member file '("configure" "lisp/ldefs-boot.el"
204 "lisp/emacs-lisp/cl-loaddefs.el"))
7b24e97a
GM
205 ;; We are in the file's buffer, so names are relative.
206 (call-process "bzr" nil t nil "revert"
207 (file-name-nondirectory file))
9ebea0e7
SM
208 (revert-buffer nil 'noconfirm))
209 (t
210 (goto-char (point-max))
211 (while (re-search-backward smerge-begin-re nil t)
212 (save-excursion
213 (ignore-errors
214 (smerge-match-conflict)
215 (smerge-resolve))))
216 ;; (when (derived-mode-p 'change-log-mode)
217 ;; (pop-to-buffer (current-buffer)) (debug 'after-resolve))
218 (save-buffer)))
219 (goto-char (point-min))
220 (prog1 (re-search-forward smerge-begin-re nil t)
221 (unless exists (kill-buffer))))))))
222
223(defun bzrmerge-add-metadata (from endrevno)
224 "Add the metadata for a merge of FROM upto ENDREVNO.
225Does not make other difference."
226 (if (with-temp-buffer
227 (call-process "bzr" nil t nil "status")
228 (goto-char (point-min))
229 (re-search-forward "^conflicts:\n" nil t))
230 (error "Don't know how to add metadata in the presence of conflicts")
231 (call-process "bzr" nil t nil "shelve" "--all"
232 "-m" "Bzrmerge shelved merge during skipping")
233 (call-process "bzr" nil t nil "revert")
234 (call-process "bzr" nil t nil
235 "merge" "-r" (format "%s" endrevno) from)
236 (call-process "bzr" nil t nil "revert" ".")
237 (call-process "bzr" nil t nil "unshelve")))
529ee9ed 238
9ebea0e7
SM
239(defvar bzrmerge-already-done nil)
240
241(defun bzrmerge-apply (missing from)
242 (setq from (expand-file-name from))
f6041baa 243 (with-current-buffer (get-buffer-create bzrmerge-buffer)
9ebea0e7
SM
244 (erase-buffer)
245 (when (equal (cdr bzrmerge-already-done) (list from missing))
246 (setq missing (car bzrmerge-already-done)))
247 (setq bzrmerge-already-done nil)
248 (let ((merge (car missing))
249 (skip (cdr missing))
41f44310 250 (unsafe nil)
9ebea0e7
SM
251 beg end)
252 (when (or merge skip)
253 (cond
254 ((and skip (or (null merge) (< (car skip) (car merge))))
255 ;; Do a "skip" (i.e. merge the meta-data only).
256 (setq beg (1- (car skip)))
257 (while (and skip (or (null merge) (< (car skip) (car merge))))
258 (assert (> (car skip) (or end beg)))
259 (setq end (pop skip)))
260 (message "Skipping %s..%s" beg end)
261 (bzrmerge-add-metadata from end))
262
263 (t
264 ;; Do a "normal" merge.
265 (assert (or (null skip) (< (car merge) (car skip))))
266 (setq beg (1- (car merge)))
267 (while (and merge (or (null skip) (< (car merge) (car skip))))
268 (assert (> (car merge) (or end beg)))
269 (setq end (pop merge)))
270 (message "Merging %s..%s" beg end)
271 (if (with-temp-buffer
272 (call-process "bzr" nil t nil "status")
273 (zerop (buffer-size)))
274 (call-process "bzr" nil t nil
275 "merge" "-r" (format "%s" end) from)
276 ;; Stupidly, "bzr merge --force -r A..B" dos not maintain the
277 ;; metadata properly except when the checkout is clean.
278 (call-process "bzr" nil t nil "merge"
279 "--force" "-r" (format "%s..%s" beg end) from)
280 ;; The merge did not update the metadata, so force the next time
281 ;; around to update it (as a "skip").
41f44310 282 (setq unsafe t)
9ebea0e7
SM
283 (push end skip))
284 (pop-to-buffer (current-buffer))
285 (sit-for 1)
286 ;; (debug 'after-merge)
287 ;; Check the conflicts.
59af988b
GM
288 ;; FIXME if using the helpful bzr changelog_merge plugin,
289 ;; there are normally no conflicts in ChangeLogs.
290 ;; But we still want the dates fixing, like bzrmerge-resolve does.
9ebea0e7
SM
291 (let ((conflicted nil)
292 (files ()))
293 (goto-char (point-min))
294 (when (re-search-forward "bzr: ERROR:" nil t)
295 (error "Internal Bazaar error!!"))
296 (while (re-search-forward "^Text conflict in " nil t)
297 (push (buffer-substring (point) (line-end-position)) files))
298 (if (re-search-forward "^\\([0-9]+\\) conflicts encountered" nil t)
299 (if (/= (length files) (string-to-number (match-string 1)))
300 (setq conflicted t))
301 (if files (setq conflicted t)))
302 (dolist (file files)
303 (if (bzrmerge-resolve file)
304 (setq conflicted t)))
305 (when conflicted
306 (setq bzrmerge-already-done
307 (list (cons merge skip) from missing))
41f44310
SM
308 (if unsafe
309 ;; FIXME: Obviously, we'd rather make it right rather
310 ;; than output such a warning. But I don't know how to add
311 ;; the metadata to bzr's since the technique used in
312 ;; bzrmerge-add-metadata does not work when there
313 ;; are conflicts.
314 (display-warning 'bzrmerge "Resolve conflicts manually.
f6041baa 315