Mercurial > emacs
changeset 112018:b0a317b18658
* admin/bzrmerge.el: New file to help merge branches while skipping
some revisions (e.g. from emacs-23 to trunk).
author | Stefan Monnier <monnier@iro.umontreal.ca> |
---|---|
date | Sun, 26 Dec 2010 20:27:08 -0500 |
parents | db006527425b |
children | 359425b45529 |
files | admin/ChangeLog admin/bzrmerge.el |
diffstat | 2 files changed, 301 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- a/admin/ChangeLog Sun Dec 26 18:17:09 2010 -0500 +++ b/admin/ChangeLog Sun Dec 26 20:27:08 2010 -0500 @@ -1,3 +1,8 @@ +2010-12-27 Stefan Monnier <monnier@iro.umontreal.ca> + + * bzrmerge.el: New file to help merge branches while skipping + some revisions (e.g. from emacs-23 to trunk). + 2010-12-03 Andreas Schwab <schwab@linux-m68k.org> * CPP-DEFINES (EXPLICIT_SIGN_EXTEND): Remove.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/admin/bzrmerge.el Sun Dec 26 20:27:08 2010 -0500 @@ -0,0 +1,296 @@ +;;; bzrmerge.el --- + +;; Copyright (C) 2010 Stefan Monnier + +;; Author: Stefan Monnier <monnier@iro.umontreal.ca> +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; + +;;; Code: + +(defun bzrmerge-merges () + "Return the list of already merged (not not committed) revisions. +The list returned is sorted by oldest-first." + (with-current-buffer (get-buffer-create "*bzrmerge*") + (erase-buffer) + ;; We generally want to make sure we start with a clean tree, but we also + ;; want to allow restarts (i.e. with some part of FROM already merged but + ;; not yet committed). + (call-process "bzr" nil t nil "status" "-v") + (goto-char (point-min)) + (when (re-search-forward "^conflicts:\n" nil t) + (error "You still have unresolved conflicts")) + (let ((merges ())) + (if (not (re-search-forward "^pending merges:\n" nil t)) + (when (save-excursion + (goto-char (point-min)) + (re-search-forward "^[a-z ]*:\n" nil t)) + (error "You still have uncommitted changes")) + ;; This is really stupid, but it seems there's no easy way to figure + ;; out which revisions have been merged already. The only info I can + ;; find is the "pending merges" from "bzr status -v", which is not + ;; very machine-friendly. + (while (not (eobp)) + (skip-chars-forward " ") + (push (buffer-substring (point) (line-end-position)) merges) + (forward-line 1))) + merges))) + +(defun bzrmerge-check-match (merge) + ;; Make sure the MERGES match the revisions on the FROM branch. + ;; Stupidly the best form of MERGES I can find is the one from + ;; "bzr status -v" which is very machine non-friendly, so I have + ;; to do some fuzzy matching. + (let ((author + (or + (save-excursion + (if (re-search-forward "^author: *\\([^<]*[^ ]\\) +<.*" + nil t) + (match-string 1))) + (save-excursion + (if (re-search-forward + "^committer: *\\([^<]*[^< ]\\) +<" nil t) + (match-string 1))))) + (timestamp + (save-excursion + (if (re-search-forward + "^timestamp:[^0-9]*\\([-0-9]+\\)" nil t) + (match-string 1)))) + (line1 + (save-excursion + (if (re-search-forward "^message:[ \n]*" nil t) + (buffer-substring (point) (line-end-position)))))) + ;; The `merge' may have a truncated line1 with "...", so get + ;; rid of any "..." and then look for a prefix match. + (when (string-match "\\.+\\'" merge) + (setq merge (substring merge 0 (match-beginning 0)))) + (or (string-prefix-p + merge (concat author " " timestamp " " line1)) + (string-prefix-p + merge (concat author " " timestamp " [merge] " line1))))) + +(defun bzrmerge-missing (from merges) + "Return the list of revisions that need to be merged. +MERGES is the revisions already merged but not yet committed. +The result is of the form (TOMERGE . TOSKIP) where TOMERGE and TOSKIP +are both lists of revnos, in oldest-first order." + (with-current-buffer (get-buffer-create "*bzrmerge*") + (erase-buffer) + (call-process "bzr" nil t nil "missing" "--theirs-only" + (expand-file-name from)) + (let ((revnos ()) (skipped ())) + (pop-to-buffer (current-buffer)) + (goto-char (point-max)) + (while (re-search-backward "^------------------------------------------------------------\nrevno: \\([0-9.]+\\).*" nil t) + (save-excursion + (if merges + (while (not (bzrmerge-check-match (pop merges))) + (unless merges + (error "Unmatched tip of merged revisions"))) + (let ((case-fold-search t) + (revno (match-string 1)) + (skip nil)) + (if (string-match "\\." revno) + (error "Unexpected dotted revno!") + (setq revno (string-to-number revno))) + (re-search-forward "^message:\n") + (while (and (not skip) + (re-search-forward + "back[- ]?port\\|merge\\|re-?generate\\|bump version" nil t)) + (let ((str (buffer-substring (line-beginning-position) + (line-end-position)))) + (when (string-match "\\` *" str) + (setq str (substring str (match-end 0)))) + (when (string-match "[.!;, ]+\\'" str) + (setq str (substring str 0 (match-beginning 0)))) + (if (save-excursion (y-or-n-p (concat str ": Skip? "))) + (setq skip t)))) + (if skip + (push revno skipped) + (push revno revnos))))) + (delete-region (point) (point-max))) + (cons (nreverse revnos) (nreverse skipped))))) + +(defun bzrmerge-resolve (file) + (unless (file-exists-p file) (error "Bzrmerge-resolve: Can't find %s" file)) + (with-demoted-errors + (let ((exists (find-buffer-visiting file))) + (with-current-buffer (find-file-noselect file) + (if (buffer-modified-p) + (error "Unsaved changes in %s" (current-buffer))) + (save-excursion + (cond + ((derived-mode-p 'change-log-mode) + ;; Fix up dates before resolving the conflicts. + (goto-char (point-min)) + (let ((diff-auto-refine-mode nil)) + (while (re-search-forward smerge-begin-re nil t) + (smerge-match-conflict) + (smerge-ensure-match 3) + (let ((start1 (match-beginning 1)) + (end1 (match-end 1)) + (start3 (match-beginning 3)) + (end3 (copy-marker (match-end 3) t))) + (goto-char start3) + (while (re-search-forward change-log-start-entry-re end3 t) + (let* ((str (match-string 0)) + (newstr (save-match-data + (concat (add-log-iso8601-time-string) + (when (string-match " *\\'" str) + (match-string 0 str)))))) + (replace-match newstr t t))) + ;; change-log-resolve-conflict prefers to put match-1's + ;; elements first (for equal dates), whereas we want to put + ;; match-3's first. + (let ((match3 (buffer-substring start3 end3)) + (match1 (buffer-substring start1 end1))) + (delete-region start3 end3) + (goto-char start3) + (insert match1) + (delete-region start1 end1) + (goto-char start1) + (insert match3))))) + ;; (pop-to-buffer (current-buffer)) (debug 'before-resolve) + )) + ;; Try to resolve the conflicts. + (cond + ((member file '("configure" "lisp/ldefs-boot.el")) + (call-process "bzr" nil t nil "revert" file) + (revert-buffer nil 'noconfirm)) + (t + (goto-char (point-max)) + (while (re-search-backward smerge-begin-re nil t) + (save-excursion + (ignore-errors + (smerge-match-conflict) + (smerge-resolve)))) + ;; (when (derived-mode-p 'change-log-mode) + ;; (pop-to-buffer (current-buffer)) (debug 'after-resolve)) + (save-buffer))) + (goto-char (point-min)) + (prog1 (re-search-forward smerge-begin-re nil t) + (unless exists (kill-buffer)))))))) + +(defun bzrmerge-add-metadata (from endrevno) + "Add the metadata for a merge of FROM upto ENDREVNO. +Does not make other difference." + (if (with-temp-buffer + (call-process "bzr" nil t nil "status") + (goto-char (point-min)) + (re-search-forward "^conflicts:\n" nil t)) + (error "Don't know how to add metadata in the presence of conflicts") + (call-process "bzr" nil t nil "shelve" "--all" + "-m" "Bzrmerge shelved merge during skipping") + (call-process "bzr" nil t nil "revert") + (call-process "bzr" nil t nil + "merge" "-r" (format "%s" endrevno) from) + (call-process "bzr" nil t nil "revert" ".") + (call-process "bzr" nil t nil "unshelve"))) + +(defvar bzrmerge-already-done nil) + +(defun bzrmerge-apply (missing from) + (setq from (expand-file-name from)) + (with-current-buffer (get-buffer-create "*bzrmerge*") + (erase-buffer) + (when (equal (cdr bzrmerge-already-done) (list from missing)) + (setq missing (car bzrmerge-already-done))) + (setq bzrmerge-already-done nil) + (let ((merge (car missing)) + (skip (cdr missing)) + beg end) + (when (or merge skip) + (cond + ((and skip (or (null merge) (< (car skip) (car merge)))) + ;; Do a "skip" (i.e. merge the meta-data only). + (setq beg (1- (car skip))) + (while (and skip (or (null merge) (< (car skip) (car merge)))) + (assert (> (car skip) (or end beg))) + (setq end (pop skip))) + (message "Skipping %s..%s" beg end) + (bzrmerge-add-metadata from end)) + + (t + ;; Do a "normal" merge. + (assert (or (null skip) (< (car merge) (car skip)))) + (setq beg (1- (car merge))) + (while (and merge (or (null skip) (< (car merge) (car skip)))) + (assert (> (car merge) (or end beg))) + (setq end (pop merge))) + (message "Merging %s..%s" beg end) + (if (with-temp-buffer + (call-process "bzr" nil t nil "status") + (zerop (buffer-size))) + (call-process "bzr" nil t nil + "merge" "-r" (format "%s" end) from) + ;; Stupidly, "bzr merge --force -r A..B" dos not maintain the + ;; metadata properly except when the checkout is clean. + (call-process "bzr" nil t nil "merge" + "--force" "-r" (format "%s..%s" beg end) from) + ;; The merge did not update the metadata, so force the next time + ;; around to update it (as a "skip"). + (push end skip)) + (pop-to-buffer (current-buffer)) + (sit-for 1) + ;; (debug 'after-merge) + ;; Check the conflicts. + (let ((conflicted nil) + (files ())) + (goto-char (point-min)) + (when (re-search-forward "bzr: ERROR:" nil t) + (error "Internal Bazaar error!!")) + (while (re-search-forward "^Text conflict in " nil t) + (push (buffer-substring (point) (line-end-position)) files)) + (if (re-search-forward "^\\([0-9]+\\) conflicts encountered" nil t) + (if (/= (length files) (string-to-number (match-string 1))) + (setq conflicted t)) + (if files (setq conflicted t))) + (dolist (file files) + (if (bzrmerge-resolve file) + (setq conflicted t))) + (when conflicted + (setq bzrmerge-already-done + (list (cons merge skip) from missing)) + (error "Resolve conflicts manually"))))) + (cons merge skip))))) + +(defun bzrmerge (from) + "Merge from branch FROM into `default-directory'." + (interactive + (list + (let ((def + (with-temp-buffer + (call-process "bzr" nil t nil "info") + (goto-char (point-min)) + (when (re-search-forward "submit branch: *" nil t) + (buffer-substring (point) (line-end-position)))))) + (read-file-name "From branch: " nil nil nil def)))) + (message "Merging from %s..." from) + (require 'vc-bzr) + (let ((default-directory (or (vc-bzr-root default-directory) + (error "Not in a Bzr tree")))) + ;; First, check the status. + (let* ((merges (bzrmerge-merges)) + ;; OK, we have the status, now check the missing data. + (missing (bzrmerge-missing from merges))) + (while missing + (setq missing (bzrmerge-apply missing from)))))) + +(provide 'bzrmerge) +;;; bzrmerge.el ends here