1 ;;; bzrmerge.el --- help merge one Emacs bzr branch to another
3 ;; Copyright (C) 2010-2012 Free Software Foundation, Inc.
5 ;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
8 ;; GNU Emacs is free software: you can redistribute it and/or modify
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.
13 ;; GNU Emacs is distributed in the hope that it will be useful,
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.
18 ;; You should have received a copy of the GNU General Public License
19 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
23 ;; Some usage notes are in admin/notes/bzr.
27 (eval-when-compile (require 'cl-lib
))
29 (defvar bzrmerge-skip-regexp
30 "back[- ]?port\\|merge\\|sync\\|re-?generate\\|bump version\\|from trunk\\|\
32 "Regexp matching logs of revisions that might be skipped.
33 `bzrmerge-missing' will ask you if it should skip any matches.")
35 (defconst bzrmerge-buffer
"*bzrmerge*"
36 "Working buffer for bzrmerge.")
38 (defconst bzrmerge-warning-buffer
"*bzrmerge warnings*"
39 "Buffer where bzrmerge will display any warnings.")
41 (defun bzrmerge-merges ()
42 "Return the list of already merged (not yet committed) revisions.
43 The list returned is sorted by oldest-first."
44 (with-current-buffer (get-buffer-create bzrmerge-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"))
54 (if (not (re-search-forward "^pending merges:\n" nil t
))
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.
64 (skip-chars-forward " ")
65 (push (buffer-substring (point) (line-end-position)) merges
)
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.
77 (if (re-search-forward "^author: *\\([^<]*[^ ]\\) +<.*"
81 (if (re-search-forward
82 "^committer: *\\([^<]*[^< ]\\) +<" nil t
)
86 (if (re-search-forward
87 "^timestamp:[^0-9]*\\([-0-9]+\\)" nil t
)
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))))
98 merge
(concat author
" " timestamp
" " line1
))
100 merge
(concat author
" " timestamp
" [merge] " line1
)))))
102 (defun bzrmerge-missing (from merges
)
103 "Return the list of revisions that need to be merged.
104 MERGES is the revisions already merged but not yet committed.
105 Asks about skipping revisions with logs matching `bzrmerge-skip-regexp'.
106 The result is of the form (TOMERGE . TOSKIP) where TOMERGE and TOSKIP
107 are both lists of revnos, in oldest-first order."
108 (with-current-buffer (get-buffer-create bzrmerge-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
)
118 (while (not (bzrmerge-check-match (pop merges
)))
120 (error "Unmatched tip of merged revisions")))
121 (let ((case-fold-search t
)
122 (revno (match-string 1))
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
)
129 (re-search-forward bzrmerge-skip-regexp nil t
))
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))))
137 Type `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 (pcase (save-excursion
143 (format "%s: Skip (y/n/N/q/%s)? " str
144 (key-description (vector help-char
)))
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
))))))
154 (push revno revnos
)))))
155 (delete-region (point) (point-max)))
156 (and (or revnos skipped
)
157 (cons (nreverse revnos
) (nreverse skipped
))))))
159 (defun bzrmerge-resolve (file)
160 (unless (file-exists-p file
) (error "Bzrmerge-resolve: Can't find %s" file
))
162 (let ((exists (find-buffer-visiting file
)))
163 (with-current-buffer (let ((enable-local-variables :safe
)
164 (enable-local-eval nil
))
165 (find-file-noselect file
))
166 (if (buffer-modified-p)
167 (error "Unsaved changes in %s" (current-buffer)))
170 ((derived-mode-p 'change-log-mode
)
171 ;; Fix up dates before resolving the conflicts.
172 (goto-char (point-min))
173 (let ((diff-auto-refine-mode nil
))
174 (while (re-search-forward smerge-begin-re nil t
)
175 (smerge-match-conflict)
176 (smerge-ensure-match 3)
177 (let ((start1 (match-beginning 1))
179 (start3 (match-beginning 3))
180 (end3 (copy-marker (match-end 3) t
)))
182 (while (re-search-forward change-log-start-entry-re end3 t
)
183 (let* ((str (match-string 0))
184 (newstr (save-match-data
185 (concat (add-log-iso8601-time-string)
186 (when (string-match " *\\'" str
)
187 (match-string 0 str
))))))
188 (replace-match newstr t t
)))
189 ;; change-log-resolve-conflict prefers to put match-1's
190 ;; elements first (for equal dates), whereas we want to put
192 (let ((match3 (buffer-substring start3 end3
))
193 (match1 (buffer-substring start1 end1
)))
194 (delete-region start3 end3
)
197 (delete-region start1 end1
)
200 ;; (pop-to-buffer (current-buffer)) (debug 'before-resolve)
202 ;; Try to resolve the conflicts.
204 ((member file
'("configure" "lisp/ldefs-boot.el"
205 "lisp/emacs-lisp/cl-loaddefs.el"))
206 ;; We are in the file's buffer, so names are relative.
207 (call-process "bzr" nil t nil
"revert"
208 (file-name-nondirectory file
))
209 (revert-buffer nil
'noconfirm
))
211 (goto-char (point-max))
212 (while (re-search-backward smerge-begin-re nil t
)
215 (smerge-match-conflict)
217 ;; (when (derived-mode-p 'change-log-mode)
218 ;; (pop-to-buffer (current-buffer)) (debug 'after-resolve))
220 (goto-char (point-min))
221 (prog1 (re-search-forward smerge-begin-re nil t
)
222 (unless exists
(kill-buffer))))))))
224 (defun bzrmerge-add-metadata (from endrevno
)
225 "Add the metadata for a merge of FROM upto ENDREVNO.
226 Does not make other difference."
227 (if (with-temp-buffer
228 (call-process "bzr" nil t nil
"status")
229 (goto-char (point-min))
230 (re-search-forward "^conflicts:\n" nil t
))
231 (error "Don't know how to add metadata in the presence of conflicts")
232 (call-process "bzr" nil t nil
"shelve" "--all"
233 "-m" "Bzrmerge shelved merge during skipping")
234 (call-process "bzr" nil t nil
"revert")
235 (call-process "bzr" nil t nil
236 "merge" "-r" (format "%s" endrevno
) from
)
237 (call-process "bzr" nil t nil
"revert" ".")
238 (call-process "bzr" nil t nil
"unshelve")))
240 (defvar bzrmerge-already-done nil
)
242 (defun bzrmerge-apply (missing from
)
243 (setq from
(expand-file-name from
))
244 (with-current-buffer (get-buffer-create bzrmerge-buffer
)
246 (when (equal (cdr bzrmerge-already-done
) (list from missing
))
247 (setq missing
(car bzrmerge-already-done
)))
248 (setq bzrmerge-already-done nil
)
249 (let ((merge (car missing
))
253 (when (or merge skip
)
255 ((and skip
(or (null merge
) (< (car skip
) (car merge
))))
256 ;; Do a "skip" (i.e. merge the meta-data only).
257 (setq beg
(1- (car skip
)))
258 (while (and skip
(or (null merge
) (< (car skip
) (car merge
))))
259 (cl-assert (> (car skip
) (or end beg
)))
260 (setq end
(pop skip
)))
261 (message "Skipping %s..%s" beg end
)
262 (bzrmerge-add-metadata from end
))
265 ;; Do a "normal" merge.
266 (cl-assert (or (null skip
) (< (car merge
) (car skip
))))
267 (setq beg
(1- (car merge
)))
268 (while (and merge
(or (null skip
) (< (car merge
) (car skip
))))
269 (cl-assert (> (car merge
) (or end beg
)))
270 (setq end
(pop merge
)))
271 (message "Merging %s..%s" beg end
)
272 (if (with-temp-buffer
273 (call-process "bzr" nil t nil
"status")
274 (zerop (buffer-size)))
275 (call-process "bzr" nil t nil
276 "merge" "-r" (format "%s" end
) from
)
277 ;; Stupidly, "bzr merge --force -r A..B" dos not maintain the
278 ;; metadata properly except when the checkout is clean.
279 (call-process "bzr" nil t nil
"merge"
280 "--force" "-r" (format "%s..%s" beg end
) from
)
281 ;; The merge did not update the metadata, so force the next time
282 ;; around to update it (as a "skip").
285 (pop-to-buffer (current-buffer))
287 ;; (debug 'after-merge)
288 ;; Check the conflicts.
289 ;; FIXME if using the helpful bzr changelog_merge plugin,
290 ;; there are normally no conflicts in ChangeLogs.
291 ;; But we still want the dates fixing, like bzrmerge-resolve does.
292 (let ((conflicted nil
)
294 (goto-char (point-min))
295 (when (re-search-forward "bzr: ERROR:" nil t
)
296 (error "Internal Bazaar error!!"))
297 (while (re-search-forward "^Text conflict in " nil t
)
298 (push (buffer-substring (point) (line-end-position)) files
))
299 (if (re-search-forward "^\\([0-9]+\\) conflicts encountered" nil t
)
300 (if (/= (length files
) (string-to-number (match-string 1)))
302 (if files
(setq conflicted t
)))
304 (if (bzrmerge-resolve file
)
305 (setq conflicted t
)))
307 (setq bzrmerge-already-done
308 (list (cons merge skip
) from missing
))
310 ;; FIXME: Obviously, we'd rather make it right rather
311 ;; than output such a warning. But I don't know how to add
312 ;; the metadata to bzr's since the technique used in
313 ;; bzrmerge-add-metadata does not work when there
315 (display-warning 'bzrmerge
"Resolve conflicts manually.
316 ¡BEWARE! Important metadata is kept in this Emacs session!
317 Do not commit without re-running `M-x bzrmerge' first!"
318 :warning bzrmerge-warning-buffer
))
319 (error "Resolve conflicts manually")))))
320 (cons merge skip
)))))
322 (defun bzrmerge (from)
323 "Merge from branch FROM into `default-directory'."
328 (call-process "bzr" nil t nil
"info")
329 (goto-char (point-min))
330 (when (re-search-forward "submit branch: *" nil t
)
331 (buffer-substring (point) (line-end-position))))))
332 (read-file-name "From branch: " nil nil nil def
))))
333 ;; Eg we ran bzrmerge once, it stopped with conflicts, we fixed them
334 ;; and are running it again.
335 (if (get-buffer bzrmerge-warning-buffer
)
336 (kill-buffer bzrmerge-warning-buffer
))
337 (message "Merging from %s..." from
)
339 (let ((default-directory (or (vc-bzr-root default-directory
)
340 (error "Not in a Bzr tree"))))
341 ;; First, check the status.
342 (let* ((merges (bzrmerge-merges))
343 ;; OK, we have the status, now check the missing data.
344 (missing (bzrmerge-missing from merges
)))
346 (message "Merging from %s...nothing to merge" from
)
348 (setq missing
(bzrmerge-apply missing from
)))
349 (message "Merging from %s...done" from
)))))
352 ;;; bzrmerge.el ends here