bzrmerge.el 15 KB

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