/dvc/mode/lisp/dvc-core.el
Emacs Lisp | 1205 lines | 952 code | 149 blank | 104 comment | 41 complexity | 2121e17d30cf9c07615a6c314efca73a MD5 | raw file
Possible License(s): GPL-2.0, MPL-2.0-no-copyleft-exception, JSON
- ;;; dvc-core.el --- Core functions for distributed version control
- ;; Copyright (C) 2005-2009 by all contributors
- ;; Author: Stefan Reichoer, <stefan@xsteve.at>
- ;; Contributions From:
- ;; Matthieu Moy <Matthieu.Moy@imag.fr>
- ;; This file 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, or (at your option)
- ;; any later version.
- ;; This file 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 GNU Emacs; see the file COPYING. If not, write to
- ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- ;; Boston, MA 02110-1301, USA.
- ;;; Commentary:
- ;; This file provides the low-level functions used by the DVC interfaces
- ;; to distributed revison control systems.
- ;;; History:
- ;; This file holds general useful functions, previously only used for tla.
- ;;; Code:
- (require 'dvc-defs)
- (require 'dvc-register)
- (eval-and-compile (require 'dvc-utils))
- (require 'dvc-buffers)
- (eval-when-compile (require 'cl))
- (eval-when-compile (require 'dired))
- (eval-and-compile (require 'dvc-lisp))
- (defvar dvc-sh-executable "sh" "The shell that is used for dvc interaction.")
- ;; --------------------------------------------------------------------------------
- ;; Various constants
- ;; --------------------------------------------------------------------------------
- (defconst dvc-mark (dvc-face-add "*" 'dvc-mark) "Fontified string used for marking.")
- (defconst dvc-exclude (dvc-face-add "E" 'dvc-mark) "Fontified string used for excluded files.")
- ;; --------------------------------------------------------------------------------
- ;; Internal variables
- ;; --------------------------------------------------------------------------------
- (defvar dvc-memorized-log-header nil)
- (defvar dvc-memorized-log-message nil)
- (defvar dvc-memorized-version nil)
- (defvar dvc-memorized-patch-sender nil)
- ;; --------------------------------------------------------------------------------
- ;; Various helper functions
- ;; --------------------------------------------------------------------------------
- ;; list-buffers-directory is used by uniquify to get the directory for
- ;; the buffer when buffer-file-name is nil, as it is for many dvc
- ;; buffers (dvc-diff-mode, etc). It needs to survive
- ;; kill-all-local-variables, so we declare it permanent local.
- (make-variable-buffer-local 'list-buffers-directory)
- (put 'list-buffers-directory 'permanent-local t)
- (defun dvc-find-tree-root-file-first (file-or-dir &optional location)
- "Find FILE-OR-DIR upward in the file system from LOCATION.
- Finding is continued upward to \"/\" until FILE-OR-DIR can be found.
- Once FILE-OR-DIR is found, the finding is broken off.
- A directory which holds FILE-OR-DIR is returned. If no such directory
- `nil' is returned. `default-directory' is used instead if LOCATION is not
- given,
- The resulting directory is guaranteed to end in a \"/\" character.
- This function may be useful to find \{arch\} and/or _darcs directories."
- (let ((pwd (or location default-directory))
- (pwd-stack nil)
- new-pwd)
- (while (not (or (string= pwd "/")
- (member pwd pwd-stack)
- (file-exists-p (concat (file-name-as-directory pwd)
- file-or-dir))))
- (setq pwd-stack (cons pwd pwd-stack))
- (setq new-pwd
- (dvc-expand-file-name (concat (file-name-as-directory pwd) "..")))
- ;; detect MS-Windows roots (c:/, d:/, ...)
- (setq pwd (if (string= new-pwd pwd) "/" new-pwd)))
- (unless (string= pwd "/")
- (setq pwd (replace-regexp-in-string "\\([^:]\\)/*$" "\\1" pwd))
- (setq pwd (file-name-as-directory pwd))
- (if (memq system-type '(ms-dos windows-nt))
- (expand-file-name pwd)
- pwd))))
- (defun dvc-tree-root-helper (file-or-dir interactivep msg
- &optional location no-error)
- "Find FILE-OR-DIR upward in the file system from LOCATION.
- Calls `dvc-find-tree-root-file-first', shows a message when
- called interactively, and manages no-error.
- If LOCATION is nil, the tree root is returned, and it is
- guaranteed to end in a \"/\" character.
- MSG must be of the form \"%S is not a ...-managed tree\"."
- (let ((location (dvc-uniquify-file-name location)))
- (let ((pwd (dvc-find-tree-root-file-first
- file-or-dir location)))
- (when (and interactivep pwd)
- (dvc-trace "%s" pwd))
- (or pwd
- (if no-error
- nil
- (error msg
- (or location default-directory)))))))
- (defun dvc-find-tree-root-file-last (file-or-dir &optional location)
- "Like `dvc-find-tree-root-file-upward' but recursively if FILE-OR-DIR is found.
- Finding is started from LOCATION but is stoped when FILE-OR-DIR cannot be found.
- Fiddled is continued upward while FILE-OR-DIR can be found.
- The last found directory which holds FILE-OR-DIR is returned. `nil' is returned
- if finding failed.
- `default-directory' is used instead if LOCATION is not given,
- This function may be useful to find CVS or .svn directories"
- (let ((pwd (or location default-directory))
- old-pwd)
- (while (and pwd (not (string= pwd "/")))
- (if (file-exists-p (concat (file-name-as-directory pwd)
- file-or-dir))
- (setq old-pwd pwd
- pwd (expand-file-name (concat (file-name-as-directory pwd)
- "..")))
- (setq pwd nil)))
- (when old-pwd
- (expand-file-name
- (replace-regexp-in-string "/+$" "/" old-pwd)))))
- (defmacro dvc-make-bymouse-function (function)
- "Create a new function by adding mouse interface to FUNCTION.
- The new function is named FUNCTION-by-mouse; and takes one argument,
- a mouse click event.
- Thew new function moves the point to the place where mouse is clicked
- then invoke FUNCTION."
- (declare (debug (&define name :name -by-mouse)))
- `(defun ,(intern (concat (symbol-name function) "-by-mouse")) (event)
- ,(concat "`" (symbol-name function) "'" " with mouse interface.")
- (interactive "e")
- (mouse-set-point event)
- (,function)))
- ;; Adapted from `dired-delete-file' in Emacs 22
- (defun dvc-delete-recursively (file)
- "Delete FILE or directory recursively."
- (let (files)
- (if (not (eq t (car (file-attributes file))))
- (delete-file file)
- (when (setq files
- (directory-files
- file t "^\\([^.]\\|\\.\\([^.]\\|\\..\\)\\).*"))
- (while files
- (dvc-delete-recursively (car files))
- (setq files (cdr files))))
- (delete-directory file))))
- ;; --------------------------------------------------------------------------------
- ;; File selection helpers
- ;; --------------------------------------------------------------------------------
- (defvar dvc-get-file-info-at-point-function nil
- "Function used to get the file at point, anywhere.")
- (defun dvc-get-file-info-at-point ()
- "Gets the filename at point, according to mode.
- Calls the function `dvc-get-file-info-at-point-function' if defined.
- When in dired mode, return the file where point is.
- Otherwise return the buffer file name."
- (cond (dvc-get-file-info-at-point-function
- (funcall dvc-get-file-info-at-point-function))
- ((eq major-mode 'dired-mode)
- (dired-get-filename))
- (t (buffer-file-name))))
- ;;;###autoload
- (defun dvc-current-file-list (&optional selection-mode)
- "Return a list of currently active files.
- When in dired mode, return the marked files or the file under point.
- In a legacy DVC mode, return `dvc-buffer-marked-file-list' if non-nil.
- In a fileinfo DVC mode, return `dvc-fileinfo-marked-files'.
- otherwise the result depends on SELECTION-MODE:
- * When 'nil-if-none-marked, return nil.
- * When 'all-if-none-marked, return all files.
- * Otherwise return result of calling `dvc-get-file-info-at-point'."
- (cond
- ((eq major-mode 'dired-mode)
- (dired-get-marked-files))
- ((dvc-derived-mode-p 'dvc-diff-mode 'dvc-status-mode)
- (or (remove nil dvc-buffer-marked-file-list)
- (dvc-fileinfo-marked-files)
- (cond
- ((eq selection-mode 'nil-if-none-marked)
- nil)
- ((eq selection-mode 'all-if-none-marked)
- (dvc-fileinfo-all-files))
- (t (list (dvc-get-file-info-at-point))))))
- ((eq major-mode 'dvc-bookmark-mode)
- (cond
- ((eq selection-mode 'nil-if-none-marked)
- nil)
- (t
- (error "selection-mode %s not implemented for dvc bookmark buffer" selection-mode))))
- ;; If other modes are added here, dvc-log-edit must be updated to
- ;; support them as well.
- (t
- ;; Some other mode. We assume it has no notion of "marked files",
- ;; so there are none marked. The only file name available is
- ;; buffer-file-name, so we could just return that. But some DVC
- ;; mode might set dvc-get-file-info-at-point-function without
- ;; updating this function, so support that.
- (if (eq selection-mode 'nil-if-none-marked)
- nil
- (list (dvc-get-file-info-at-point))))))
- (defun dvc-confirm-read-file-name (prompt &optional mustmatch file-name default-filename)
- "A wrapper around `read-file-name' that provides some useful defaults."
- (unless file-name
- (setq file-name (dvc-get-file-info-at-point)))
- (read-file-name prompt
- (file-name-directory (or file-name ""))
- default-filename
- mustmatch
- (file-name-nondirectory (or file-name ""))))
- (defun dvc-confirm-read-file-name-list (prompt &optional files single-prompt mustmatch)
- (or
- (if dvc-test-mode files)
- (let ((num-files (length files)))
- (if (= num-files 1)
- (let ((confirmed-file-name
- (dvc-confirm-read-file-name single-prompt mustmatch (car files))))
- ;; I don't think `dvc-confirm-read-file-name' can return nil.
- (assert confirmed-file-name)
- (list confirmed-file-name))
- (and (y-or-n-p (format prompt num-files))
- files)))))
- (defcustom dvc-confirm-file-op-method 'y-or-n-p
- "Function to use for confirming file-based DVC operations.
- Some valid options are:
- y-or-n-p: Prompt for 'y' or 'n' keystroke.
- yes-or-no-p: Prompt for \"yes\" or \"no\" string.
- dvc-always-true: Do not display a prompt."
- :type 'function
- :group 'dvc)
- (defun dvc-always-true (&rest ignore)
- "Do nothing and return t.
- This function accepts any number of arguments, but ignores them."
- (interactive)
- t)
- (defun dvc-confirm-file-op (operation files confirm)
- "Confirm OPERATION (a string, used in prompt) on FILE (list of strings).
- If CONFIRM is nil, just return FILES (no prompt).
- Returns FILES, or nil if not confirmed.
- If you want to adjust the function called to confirm the
- operation, then customize the `dvc-confirm-file-op-method' function."
- (or
- ;; Allow bypassing confirmation with `dvc-test-mode'. See
- ;; tests/xmtn-tests.el dvc-status-add.
- (if dvc-test-mode files)
- ;; Abstracted from pcvs.el cvs-do-removal
- (if (not confirm)
- files
- (let ((nfiles (length files)))
- (if (funcall (or (and (functionp dvc-confirm-file-op-method)
- dvc-confirm-file-op-method)
- 'y-or-n-p)
- (if (= 1 nfiles)
- (format "%s file: \"%s\" ? "
- operation
- (car files))
- (format "%s %d files? "
- operation
- nfiles)))
- files
- nil)))))
- (defun dvc-dvc-files-to-commit ()
- ;;todo: set the correct modifier, one of dvc-modified, dvc-added, dvc-move, now use only nil
- ;; FIXME: this is only used by dvc-log-insert-commit-file-list; should just merge this code there.
- (let ((files
- (with-current-buffer dvc-partner-buffer (dvc-current-file-list 'all-if-none-marked))))
- (mapcar (lambda (arg) (cons nil arg)) files)))
- (defun dvc-find-file-at-point ()
- "Opens the file at point.
- The filename is obtained with `dvc-get-file-info-at-point'."
- (interactive)
- (let* ((file (dvc-get-file-info-at-point)))
- (cond
- ((not file)
- (error "No file at point"))
- (t
- (find-file file)))))
- (dvc-make-bymouse-function dvc-find-file-at-point)
- (defun dvc-find-file-other-window ()
- "Visit the current file in the other window.
- The filename is obtained with `dvc-get-file-info-at-point'."
- (interactive)
- (let ((file (dvc-get-file-info-at-point)))
- (if file
- (progn
- (find-file-other-window file))
- (error "No file at point"))))
- (defun dvc-view-file ()
- "Visit the current file in `view-mode'.
- The filename is obtained with `dvc-get-file-info-at-point'."
- (interactive)
- (let ((file (dvc-get-file-info-at-point)))
- (if file
- (view-file-other-window file)
- (error "No file at point"))))
- (defun dvc-dired-jump ()
- "Jump to a dired buffer, containing the file at point."
- (interactive)
- (let ((file-full-path (expand-file-name (or (dvc-get-file-info-at-point) ""))))
- (let ((default-directory (file-name-directory file-full-path)))
- (dvc-funcall-if-exists dired-jump))
- (dired-goto-file file-full-path)))
- (defun dvc-purge-files (&rest files)
- "Delete FILES from the harddisk. No backup is created for these FILES.
- These function bypasses the used revision control system."
- (interactive (dvc-current-file-list))
- (let ((multiprompt (format "Are you sure to purge %%d files? "))
- (singleprompt (format "Purge file: ")))
- (when (dvc-confirm-read-file-name-list multiprompt files singleprompt nil)
- (mapcar #'delete-file files)
- (message "Purged %S" files))))
- (defun dvc-current-executable ()
- "Return the name of the binary associated with the current dvc backend.
- This uses `dvc-current-active-dvc'.
- \"DVC\" is returned if `dvc-current-active-dvc' returns nil."
- (let ((dvc (dvc-current-active-dvc)))
- (if (not dvc)
- "DVC"
- (dvc-variable dvc "executable"))))
- ;; partner buffer stuff
- (defvar dvc-partner-buffer nil
- "DVC Partner buffer; stores diff buffer for log-edit, etc.
- Local to each buffer, not killed by kill-all-local-variables.")
- (make-variable-buffer-local 'dvc-partner-buffer)
- (put 'dvc-partner-buffer 'permanent-local t)
- (defun dvc-buffer-pop-to-partner-buffer ()
- "Pop to dvc-partner-buffer, if available."
- (interactive)
- (if (and (boundp 'dvc-partner-buffer) dvc-partner-buffer)
- (if (buffer-live-p dvc-partner-buffer)
- (pop-to-buffer dvc-partner-buffer)
- (message "Partner buffer has been killed"))
- (message "No partner buffer set for this buffer.")))
- (defmacro dvc-with-keywords (keywords plist &rest body)
- "Execute a body of code with keywords bound.
- Each keyword listed in KEYWORDS is bound to its value from PLIST, then
- BODY is evaluated."
- (declare (indent 1) (debug (sexp form body)))
- (flet ((keyword-to-symbol (keyword)
- (intern (substring (symbol-name keyword) 1))))
- (let ((keyword (make-symbol "keyword"))
- (default (make-symbol "default")))
- `(let ,(mapcar (lambda (keyword-entry)
- (keyword-to-symbol (if (consp keyword-entry)
- (car keyword-entry)
- keyword-entry)))
- keywords)
- (dolist (keyword-entry ',keywords)
- (let ((,keyword (if (consp keyword-entry)
- (car keyword-entry)
- keyword-entry))
- (,default (if (consp keyword-entry)
- (cadr keyword-entry)
- nil)))
- (set (intern (substring (symbol-name ,keyword) 1))
- (or (cadr (member ,keyword ,plist))
- ,default))))
- ,@body))))
- ;; ----------------------------------------------------------------------------
- ;; Process management
- ;; ----------------------------------------------------------------------------
- ;; Candidates for process handlers
- (defun dvc-default-error-function (output error status arguments)
- "Default function called when a DVC process ends with a non-zero status.
- OUTPUT is the buffer containing process standard output.
- ERROR is the buffer containing process error output.
- STATUS indicates the return status of the program.
- ARGUMENTS is a list of the arguments that the process was called with."
- (if (> (with-current-buffer error (point-max)) 1)
- (dvc-show-error-buffer error)
- (if (> (with-current-buffer output (point-max)) 1)
- (dvc-show-error-buffer output)
- (error "`%s %s' failed with code %d and no output!"
- (dvc-current-executable)
- (mapconcat 'identity arguments " ")
- status)))
- (error "`%s %s' failed with code %d"
- (dvc-current-executable)
- (mapconcat 'identity arguments " ")
- status))
- (defvar dvc-default-killed-function-noerror 0
- "The number of killed processes we will ignore until throwing an error.
- If the value is 0, `dvc-default-killed-function' will throw an error.
- See `dvc-default-killed-function'.")
- (defun dvc-default-killed-function (output error status arguments)
- "Default function called when a DVC process is killed.
- OUTPUT is the buffer containing process standard output.
- ERROR is the buffer containing process error output.
- STATUS indicates the return status of the program.
- ARGUMENTS is a list of the arguments that the process was called with."
- (if (> dvc-default-killed-function-noerror 0)
- (setq dvc-default-killed-function-noerror
- (- dvc-default-killed-function-noerror 1))
- (dvc-switch-to-buffer error)
- (error "`%s %s' process killed !"
- (dvc-current-executable)
- (mapconcat 'identity arguments " "))))
- (defun dvc-null-handler (output error status arguments)
- "Handle a finished process without doing anything.
- Candidate as an argument for one of the keywords :finished, :error or :killed
- in `dvc-run-dvc-sync' or `dvc-run-dvc-async'.
- OUTPUT is the buffer containing process standard output.
- ERROR is the buffer containing process error output.
- STATUS indicates the return status of the program.
- ARGUMENTS is a list of the arguments that the process was called with."
- nil)
- (defun dvc-status-handler (output error status arguments)
- "Return an integer value that reflects the process status.
- Candidate as an argument for one of the keywords :finished, :error or :killed
- in `dvc-run-dvc-sync' or `dvc-run-dvc-async'.
- OUTPUT is the buffer containing process standard output.
- ERROR is the buffer containing process error output.
- STATUS indicates the return status of the program.
- ARGUMENTS is a list of the arguments that the process was called with."
- (cond ((numberp status) status)
- ((string-match "^exited abnormally with code \\(.*\\)" status)
- (string-to-number (match-string 1)))
- (t (error status))))
- (defun dvc-output-buffer-handler (output error status arguments)
- "Return the output of a finished process, stripping any trailing newline.
- OUTPUT is the buffer containing process standard output.
- ERROR is the buffer containing process error output.
- STATUS indicates the return status of the program.
- ARGUMENTS is a list of the arguments that the process was called with."
- (dvc-buffer-content output))
- (defun dvc-output-buffer-handler-withnewline (output error status arguments)
- "Same as dvc-output-buffer-handler, but keep potential final newline."
- (with-current-buffer output (buffer-string)))
- (defun dvc-output-and-error-buffer-handler (output error status arguments)
- "Return the output of a finished process, stripping any trailing newline.
- OUTPUT is the buffer containing process standard output.
- ERROR is the buffer containing process error output.
- STATUS indicates the return status of the program.
- ARGUMENTS is a list of the arguments that the process was called with."
- (concat (dvc-buffer-content output)
- (dvc-buffer-content error)))
- (defun dvc-output-buffer-split-handler (output error status arguments)
- "Return the output of a finished process as a list of lines.
- OUTPUT is the buffer containing process standard output.
- ERROR is the buffer containing process error output.
- STATUS indicates the return status of the program.
- ARGUMENTS is a list of the arguments that the process was called with."
- (split-string (dvc-buffer-content output) "\n"))
- (defun dvc-default-finish-function (output error status arguments)
- "Default function called when a DVC process terminates.
- OUTPUT is the buffer containing process standard output.
- ERROR is the buffer containing process error output.
- STATUS indicates the return status of the program.
- ARGUMENTS is a list of the arguments that the process was called with."
- (let ((has-output))
- (with-current-buffer output
- (dvc-process-buffer-mode)
- (setq has-output (> (point-max) 1)))
- (when has-output
- (dvc-switch-to-buffer output))
- (when (or dvc-debug has-output)
- (message "Process `%s %s' finished"
- (dvc-current-executable)
- (mapconcat 'identity arguments " ")))
- status))
- (defun dvc-finish-function-without-buffer-switch (output error status arguments)
- "Similar to `dvc-default-finish-function' but no buffer switch.
- OUTPUT is the buffer containing process standard output.
- ERROR is the buffer containing process error output.
- STATUS indicates the return status of the program.
- ARGUMENTS is a list of the arguments that the process was called
- with."
- (with-current-buffer output
- (dvc-trace "Process `%s %s' finished"
- (dvc-current-executable)
- (mapconcat 'identity arguments " "))
- status))
- (defvar dvc-process-running nil
- "List of DVC processes running.
- A value of nil indicates no processes are running.
- The list is a list of pairs (process event) where EVENT is the event
- corresponding to the beginning of the execution of process. It can be
- used to get more info about the process.")
- (defun dvc-build-dvc-command (dvc list-args)
- "Build a shell command to run DVC with args LIST-ARGS.
- DVC can be one of 'baz, 'xhg, ..."
- (let ((executable (executable-find (dvc-variable dvc "executable"))))
- ;; 'executable-find' allows leading ~
- (if (not executable)
- (error "executable for %s not found" (symbol-name dvc)))
- (mapconcat 'shell-quote-argument
- (cons executable
- (remq nil list-args))
- " ")))
- (defcustom dvc-password-prompt-regexp
- "[Pp]ass\\(word\\|phrase\\).*:\\s *\\'"
- "*Regexp matching prompts for passwords in the inferior process."
- :type 'regexp
- :group 'dvc)
- (defun dvc-process-filter (proc string &optional no-insert)
- "Filter PROC's STRING.
- Prompt for password with `read-passwd' if the output of PROC matches
- `dvc-password-prompt-regexp'.
- If NO-INSERT is non-nil, do not insert the string.
- In all cases, a new string is returned after normalizing newlines."
- (with-current-buffer (process-buffer proc)
- (setq string (replace-regexp-in-string "\015" "\n" string))
- (unless no-insert
- (goto-char (process-mark proc))
- (insert string)
- (set-marker (process-mark proc) (point)))
- (when (string-match dvc-password-prompt-regexp string)
- (string-match "^\\([^\n]+\\)\n*\\'" string)
- (let ((passwd (read-passwd (match-string 1 string))))
- (process-send-string proc (concat passwd "\n"))))
- string))
- (defun dvc-prepare-environment (env)
- "By default, do not touch the environment"
- env)
- (defun dvc-default-global-argument ()
- "By default, no global argument."
- nil)
- (defun dvc-run-dvc-async (dvc arguments &rest keys)
- "Run a process asynchronously.
- Current directory for the process is the current `default-directory'.
- ARGUMENTS is a list of arguments. nil values in this list are removed.
- KEYS is a list of keywords and values. Possible keywords are:
- :finished ....... Function run when the process finishes. If none
- specified, `dvc-default-finish-function' is run.
- :killed ......... Function run when the process is killed. If none
- specified, `dvc-default-killed-function' is run.
- :error .......... Function run when the process exits with a non 0
- status. If none specified,
- `dvc-default-error-function' is run.
- All these functions take 4 arguments : output, error, status, and
- arguments.
- - \"output\" is the output buffer
- - \"error\" is the buffer where standard error is redirected
- - \"status\" is the numeric exit-status or the signal number
- - \"arguments\" is the list of arguments, as a list of strings,
- like '(\"changes\" \"--diffs\")
- `dvc-null-handler' can be used here if there's nothing to do.
- :filter Function to call every time we receive output from
- the process. It should take arguments proc and string.
- The string will have been run through
- `dvc-process-filter' to deal with password prompts and
- newlines.
- :output-buffer .. Buffer where the output of the process should be
- redirected. If none specified, a new one is
- created, and will be entered in
- `dvc-dead-process-buffer-queue' to be killed
- later.
- :error-buffer ... Buffer where the standard error of the process
- should be redirected.
- :related-buffer . Defaults to `current-buffer'. This is the buffer
- where the result of the process will be used. If
- this buffer is killed before the end of the
- execution, the user is prompted if he wants to kill
- the process."
- (dvc-with-keywords
- (:finished :killed :error :filter
- :output-buffer :error-buffer :related-buffer)
- keys
- (let* ((output-buf (or (and output-buffer
- (get-buffer-create output-buffer))
- (dvc-new-process-buffer nil dvc)))
- (error-buf (or (and error-buffer (get-buffer-create error-buffer))
- (dvc-new-error-buffer nil dvc)))
- (error-file (dvc-make-temp-name "dvc-errors"))
- (global-arg (funcall (dvc-function dvc "default-global-argument")))
- (command (dvc-build-dvc-command
- dvc (append global-arg arguments)))
- ;; Make the `default-directory' unique. The trailing slash
- ;; may be necessary in some cases.
- (default-directory (dvc-uniquify-file-name default-directory))
- (process
- (let ((process-environment
- (funcall (dvc-function dvc "prepare-environment")
- process-environment)))
- (with-current-buffer output-buf
- ;; process filter will need to know which dvc to run
- ;; if there is a choice
- (setq dvc-buffer-current-active-dvc dvc))
- ;; `start-process' sends both stderr and stdout to
- ;; `output-buf'. But we want to keep stderr separate. So
- ;; we use a shell to redirect stderr before Emacs sees
- ;; it. Note that this means we require "sh" even on
- ;; MS Windows.
- (start-process
- (dvc-variable dvc "executable") output-buf
- dvc-sh-executable "-c"
- (format "%s 2> %s"
- command error-file))))
- (process-event
- (list process
- (dvc-log-event output-buf
- error-buf
- command
- default-directory "started"))))
- (with-current-buffer (or related-buffer (current-buffer))
- (dvc-trace "Running process `%s' in `%s'" command default-directory)
- (add-to-list 'dvc-process-running process-event)
- (set-process-filter
- process
- (if (not filter)
- 'dvc-process-filter
- (dvc-capturing-lambda (proc string)
- (funcall (capture filter)
- proc
- (dvc-process-filter proc string t)))))
- (set-process-sentinel
- process
- (dvc-capturing-lambda (process event)
- (let ((default-directory (capture default-directory)))
- (dvc-log-event (capture output-buf) (capture error-buf)
- (capture command)
- (capture default-directory)
- (dvc-strip-final-newline event))
- (setq dvc-process-running
- (delq (capture process-event) dvc-process-running))
- (when (file-exists-p (capture error-file))
- (with-current-buffer (capture error-buf)
- (insert-file-contents (capture error-file)))
- (delete-file (capture error-file)))
- (let ((state (process-status process))
- (status (process-exit-status process))
- (dvc-temp-current-active-dvc (capture dvc)))
- (unwind-protect
- (cond ((and (eq state 'exit) (= status 0))
- (funcall (or (capture finished)
- 'dvc-default-finish-function)
- (capture output-buf) (capture error-buf)
- status (capture arguments)))
- ((eq state 'signal)
- (funcall (or (capture killed)
- 'dvc-default-killed-function)
- (capture output-buf) (capture error-buf)
- status (capture arguments)))
- ((eq state 'exit) ;; status != 0
- (funcall (or (capture error)
- 'dvc-default-error-function)
- (capture output-buf) (capture error-buf)
- status (capture arguments)))))
- ;; Schedule any buffers we created for killing
- (unless (capture output-buffer)
- (dvc-kill-process-buffer (capture output-buf)))
- (unless (capture error-buffer)
- (dvc-kill-process-buffer (capture error-buf)))))))
- process))))
- (defun dvc-run-dvc-sync (dvc arguments &rest keys)
- "Run DVC synchronously.
- See `dvc-run-dvc-async' for details on possible ARGUMENTS and KEYS."
- (dvc-with-keywords
- (:finished :killed :error :output-buffer :error-buffer :related-buffer)
- keys
- (let* ((output-buf (or (and output-buffer
- (get-buffer-create output-buffer))
- (dvc-new-process-buffer t dvc)))
- (error-buf (or (and error-buffer (get-buffer-create error-buffer))
- (dvc-new-error-buffer t dvc)))
- (global-arg (funcall (dvc-function dvc "default-global-argument")))
- (command (dvc-build-dvc-command
- dvc (append global-arg arguments)))
- (arguments (remq nil arguments))
- (error-file (dvc-make-temp-name "arch-errors"))
- ;; Make the `default-directory' unique. The trailing slash
- ;; may be necessary in some cases.
- (default-directory (dvc-uniquify-file-name default-directory)))
- (with-current-buffer (or related-buffer (current-buffer))
- (dvc-log-event output-buf error-buf command default-directory
- "started")
- (let ((status (let ((process-environment
- (funcall (dvc-function dvc "prepare-environment")
- process-environment)))
- (call-process dvc-sh-executable nil output-buf nil "-c"
- (format "%s 2> %s"
- command
- error-file)))))
- (when (file-exists-p error-file)
- (with-current-buffer error-buf
- (insert-file-contents error-file))
- (delete-file error-file))
- (unwind-protect
- (let ((dvc-temp-current-active-dvc dvc))
- (cond ((stringp status)
- (when (string= status "Terminated")
- (funcall (or killed 'dvc-default-killed-function)
- output-buf error-buf status arguments)))
- ((numberp status)
- (if (zerop status)
- (funcall (or finished 'dvc-default-finish-function)
- output-buf error-buf status arguments)
- (funcall (or error 'dvc-default-error-function)
- output-buf error-buf status arguments)))
- (t (message "Unknown status - %s" status))))
- ;; Schedule any buffers we created for killing
- (unless output-buffer (dvc-kill-process-buffer output-buf))
- (unless error-buffer (dvc-kill-process-buffer error-buf))))))))
- (defun dvc-processes-related-to-buffer (buffer)
- "Returns a list of DVC process whose related buffer is BUFFER."
- (let ((accu nil))
- (dolist (entry dvc-process-running)
- (when (eq (dvc-event-related-buffer (cadr entry)) buffer)
- (push (car entry) accu)))
- (setq accu (nreverse accu))
- accu))
- (defun dvc-kill-process-maybe (buffer)
- "Prompts and possibly kill process whose related buffer is BUFFER."
- ;; FIXME: It would be reasonable to run this here, to give any
- ;; process one last chance to run. But somehow this screws up
- ;; package-maint-clean-some-elc. (accept-process-output)
- (let* ((processes (dvc-processes-related-to-buffer buffer))
- (l (length processes)))
- (when (and processes
- (y-or-n-p (format "%s process%s running in buffer %s. Kill %s? "
- l (if (= l 1) "" "es")
- (buffer-name buffer)
- (if (= l 1) "it" "them"))))
- (dolist (process processes)
- (when (eq (process-status process) 'run)
- (incf dvc-default-killed-function-noerror)
- (kill-process process)))))
- ;; make sure it worked
- (let ((processes (dvc-processes-related-to-buffer buffer)))
- (when processes
- (error "Process still running in buffer %s" buffer))))
- (add-hook 'kill-buffer-hook 'dvc-kill-buffer-function)
- (defun dvc-kill-buffer-function ()
- "Function run when a buffer is killed."
- (dvc-buffers-tree-remove (current-buffer))
- (dvc-kill-process-maybe (current-buffer)))
- (defun dvc-run-dvc-display-as-info (dvc arg-list &optional show-error-buffer info-string asynchron)
- "Call either `dvc-run-dvc-async' or `dvc-run-dvc-sync' and display the result in an info buffer.
- When INFO-STRING is given, insert it at the buffer beginning."
- (let ((buffer (dvc-get-buffer-create dvc 'info)))
- (funcall (if asynchron 'dvc-run-dvc-async 'dvc-run-dvc-sync) dvc arg-list
- :finished
- (dvc-capturing-lambda (output error status arguments)
- (progn
- (with-current-buffer (capture buffer)
- (let ((inhibit-read-only t))
- (erase-buffer)
- (dvc-info-buffer-mode)
- (when (capture info-string)
- (insert (capture info-string)))
- (insert-buffer-substring output)
- (when (capture show-error-buffer)
- (insert-buffer-substring error))
- (toggle-read-only 1)))
- (dvc-switch-to-buffer (capture buffer)))))))
- (defvar dvc-info-buffer-mode-map
- (let ((map (make-sparse-keymap)))
- (define-key map (dvc-prefix-buffer ?L) 'dvc-open-internal-log-buffer)
- (define-key map dvc-keyvec-quit 'dvc-buffer-quit)
- map)
- "Keymap used in a dvc info buffer.")
- (define-derived-mode dvc-info-buffer-mode fundamental-mode
- "DVC info mode"
- "Major mode for dvc info buffers"
- (dvc-install-buffer-menu)
- (toggle-read-only 1))
- (defvar dvc-log-cookie nil)
- (defstruct (dvc-event) output-buffer error-buffer related-buffer
- command tree event time)
- (defsubst dvc-log-printer-print-buffer (buffer function)
- "Helper function for `dvc-log-printer'.
- Print a buffer filed for BUFFER during printing a log event.
- The printed name of BUFFER is mouse sensitive. If the user
- clicks it, FUNCTION is invoked."
- (let ((alive-p (buffer-live-p buffer))
- map)
- (dvc-face-add
- (or
- ;; pp-to-string is very costly.
- ;; Handle the typical case with hard-coding.
- (unless alive-p "#<killed buffer>")
- ;; Normal case.
- (buffer-name buffer)
- ;; Extra case.
- (pp-to-string buffer))
- 'dvc-buffer
- (when alive-p
- (setq map (make-sparse-keymap))
- (define-key map [mouse-2] function)
- map)
- nil
- "Show the buffer")))
- (defun dvc-log-recently-p (elem limit-minute)
- "Check ELEM recorded a recent event or not.
- Return nil If ELEM recorded an event older than LIMIT-MINUTE.
- Else return t."
- (let* ((recorded (dvc-event-time elem))
- (cur (current-time))
- (diff-minute (/ (+ (* 65536 (- (nth 0 cur)
- (nth 0 recorded)))
- (- (nth 1 cur)
- (nth 1 recorded)))
- 60)))
- (if (> limit-minute diff-minute)
- t
- nil)))
- (defun dvc-log-printer (elem)
- "Arch event printer which prints ELEM."
- (let ((event (dvc-event-event elem))
- (p (point)))
- (insert
- "Command: " (dvc-event-command elem)
- "\nDirectory: " (dvc-face-add (or (dvc-event-tree elem) "(nil)")
- 'dvc-local-directory)
- "\nDate: " (format-time-string "%c" (dvc-event-time elem))
- "\nRelated Buffer: " (dvc-log-printer-print-buffer
- (dvc-event-related-buffer elem)
- 'dvc-switch-to-related-buffer-by-mouse)
- "\nOutput Buffer: " (dvc-log-printer-print-buffer
- (dvc-event-output-buffer elem)
- 'dvc-switch-to-output-buffer-by-mouse)
- "\nError Buffer: " (dvc-log-printer-print-buffer
- (dvc-event-error-buffer elem)
- 'dvc-switch-to-error-buffer-by-mouse)
- (if (not (string= event "started"))
- (concat "\nEvent: " event)
- "")
- "\n")
- ;; Reflect the point to `default-directory'.
- ;; NOTE: XEmacs doesn't have `point-entered' special text property.
- (put-text-property
- p (point)
- 'point-entered (lambda (old new)
- (setq default-directory
- (dvc-event-tree
- (ewoc-data
- (ewoc-locate dvc-log-cookie))))))))
- (defmacro dvc-switch-to-buffer-macro (function accessor)
- "Define a FUNCTION for switching to the buffer associated with some event.
- ACCESSOR is a function for retrieving the appropriate buffer from a
- `dvc-event' structure."
- (declare (debug (&define name symbolp)))
- `(defun ,function ()
- "In a log buffer, pops to the output or error buffer corresponding to the
- process at point"
- (interactive)
- (let ((buffer (,accessor
- (ewoc-data (ewoc-locate dvc-log-cookie)))))
- (cond ((buffer-live-p buffer)
- (dvc-switch-to-buffer buffer)
- (unless (member buffer
- (mapcar (lambda (p)
- (process-buffer (car p)))
- dvc-process-running))
- (dvc-process-buffer-mode)))
- (t (error "Buffer has been killed"))))))
- (dvc-switch-to-buffer-macro dvc-switch-to-output-buffer
- dvc-event-output-buffer)
- (dvc-switch-to-buffer-macro dvc-switch-to-error-buffer
- dvc-event-error-buffer)
- (dvc-switch-to-buffer-macro dvc-switch-to-related-buffer
- dvc-event-related-buffer)
- (dvc-make-bymouse-function dvc-switch-to-output-buffer)
- (dvc-make-bymouse-function dvc-switch-to-error-buffer)
- (dvc-make-bymouse-function dvc-switch-to-related-buffer)
- (defun dvc-log-event (output error command tree event)
- "Log an event in the `dvc-log-buffer' buffer.
- OUTPUT is the buffer containing process standard output.
- ERROR is the buffer containing process error output.
- COMMAND is the command that was executed.
- TREE is the process's working directory.
- EVENT is the event that occurred.
- Returns that event."
- (unless (and dvc-log-cookie
- (buffer-live-p (ewoc-buffer dvc-log-cookie)))
- (with-current-buffer (get-buffer-create dvc-log-buffer)
- (setq dvc-log-cookie
- (ewoc-create (dvc-ewoc-create-api-select
- #'dvc-log-printer)))
- (dvc-log-buffer-mode)))
- (let ((related-buffer (current-buffer)))
- (with-current-buffer (ewoc-buffer dvc-log-cookie)
- (let ((elem (make-dvc-event :output-buffer output
- :error-buffer error
- :related-buffer related-buffer
- :command command
- :tree tree
- :event event
- :time (current-time)))
- buffer-read-only)
- (ewoc-enter-last dvc-log-cookie elem)
- ;; If an event is too old (30 minutes after it has been
- ;; recorded), throw it away.
- (ewoc-filter dvc-log-cookie 'dvc-log-recently-p 30)
- (ewoc-refresh dvc-log-cookie)
- elem))))
- (defun dvc-log-next ()
- "Move to the next log entry."
- (interactive)
- (let ((next (ewoc-next dvc-log-cookie
- (ewoc-locate dvc-log-cookie))))
- (when next (goto-char (ewoc-location next)))))
- (defun dvc-log-prev ()
- "Move to the previous log entry."
- (interactive)
- (let ((prev (ewoc-prev dvc-log-cookie
- (ewoc-locate dvc-log-cookie))))
- (when prev (goto-char (ewoc-location prev)))))
- ;;
- ;; Log buffer mode section
- ;;
- (defvar dvc-log-buffer-mode-map
- (let ((map (make-sparse-keymap)))
- (define-key map dvc-keyvec-help 'describe-mode)
- (define-key map [?o] 'dvc-switch-to-output-buffer)
- (define-key map "\C-m" 'dvc-switch-to-output-buffer)
- (define-key map [?e] 'dvc-switch-to-error-buffer)
- (define-key map [?r] 'dvc-switch-to-related-buffer)
- (define-key map [?n] 'dvc-log-next)
- (define-key map [?p] 'dvc-log-prev)
- (define-key map dvc-keyvec-quit 'dvc-buffer-quit)
- map)
- "Keymap used in DVC's log buffer.")
- (define-derived-mode dvc-log-buffer-mode fundamental-mode "DVC Log"
- "Major mode for DVC's internal log buffer. You can open this buffer
- with `dvc-open-internal-log-buffer'."
- (toggle-read-only 1))
- (defun dvc-open-internal-log-buffer ()
- "Switch to the DVC's internal log buffer.
- This buffer contains a list of all the DVC commands previously executed.
- The buffer uses the mode `dvc-log-buffer-mode'"
- (interactive)
- (let ((buffer-name (buffer-name)))
- (dvc-switch-to-buffer dvc-log-buffer)
- (goto-char (point-max))
- (when (re-search-backward (concat " Buffer: "
- (regexp-quote buffer-name)
- "$")
- nil t)
- (dvc-flash-line))))
- (defun dvc-clear-log-buffer ()
- "Kill the log buffer."
- (when (bufferp (get-buffer dvc-log-buffer))
- (kill-buffer dvc-log-buffer)))
- (defun dvc-get-process-output ()
- "Return the content of the last process buffer.
- Strips the final newline if there is one."
- (dvc-buffer-content dvc-last-process-buffer))
- (defun dvc-get-error-output ()
- "Return the content of the last error buffer.
- Strips the final newline if there is one."
- (dvc-buffer-content dvc-last-error-buffer))
- ;; TODO: per backend cound.
- (add-to-list 'minor-mode-alist
- '(dvc-process-running
- (:eval (if (equal (length dvc-process-running) 1)
- " DVC running"
- (concat " DVC running("
- (int-to-string (length dvc-process-running))
- ")")))))
- (defun dvc-log-edit-file-name ()
- "Return a suitable file name to edit the commit message"
- ;; FIXME: replace this with define-dvc-unified-command
- (dvc-call "dvc-log-edit-file-name-func"))
- (defun dvc-dvc-log-edit-file-name-func ()
- (concat (file-name-as-directory (dvc-tree-root))
- (dvc-variable (dvc-current-active-dvc)
- "log-edit-file-name")))
- ;;
- ;; Revision manipulation
- ;;
- ;; revision grammar is specified in ../docs/DVC-API
- ;; accessors
- (defun dvc-revision-get-dvc (revision-id)
- (car revision-id))
- (defun dvc-revision-get-type (revision-id)
- (car (nth 1 revision-id)))
- (defun dvc-revision-get-data (revision-id)
- (cdr (nth 1 revision-id)))
- (defun dvc-revision-to-string (revision-id &optional prev-format orig-str)
- "Return a string representation for REVISION-ID.
- If PREV-FORMAT is specified, it is the format string to use for
- entries that are before the given revision ID. The format string
- should take two parameters. The first is the revision ID, and
- the second is a number which indicates how many generations back
- to travel.
- If ORIG-STR is specified, it is the string that indicates the
- current revision of the working tree."
- (let* ((type (dvc-revision-get-type revision-id))
- (data (dvc-revision-get-data revision-id)))
- ;;(dvc-trace "dvc-revision-to-string: type: %s, data: %s, orig-str: %s" type data orig-str)
- (case type
- (revision (dvc-name-construct (nth 0 data)))
- (local-tree (car data))
- (last-revision (or orig-str "original"))
- (previous-revision
- (format (or prev-format "%s:-%s")
- (dvc-revision-to-string
- (list (dvc-revision-get-dvc revision-id) (nth 0 data)))
- (int-to-string (nth 1 data))))
- (t "UNKNOWN"))))
- (defun dvc-revision-get-buffer (file revision-id)
- "Return an empty buffer suitable for viewing FILE in REVISION-ID.
- The name of the buffer is chosen according to FILE and REVISION-ID.
- REVISION-ID may have the values described in docs/DVC-API."
- (let* ((type (dvc-revision-get-type revision-id))
- (name (concat
- (file-name-nondirectory file)
- "(" (dvc-revision-to-string revision-id) ")")))
- ;; replace / by | to work around uniquify
- (setq name (replace-regexp-in-string "\\/" "|" name))
- (let ((buffer (generate-new-buffer name)))
- (with-current-buffer buffer
- (let ((buffer-file-name file))
- (set-auto-mode t)))
- (dvc-buffers-tree-add (dvc-revision-get-dvc revision-id) type file buffer)
- buffer)))
- (defun dvc-revision-get-file-in-buffer (file revision-id)
- "Return a buffer with the content of FILE at REVISION-ID.
- REVISION-ID is as specified in docs/DVC-API."
- (dvc-trace "dvc-revision-get-file-in-buffer. revision-id=%S" revision-id)
- (let* ((type (dvc-revision-get-type revision-id))
- (inhibit-read-only t)
- ;; find-file-noselect will call dvc-current-active-dvc in a
- ;; hook; specify dvc for dvc-call
- (dvc-temp-current-active-dvc (dvc-revision-get-dvc revision-id))
- (buffer (unless (eq type 'local-tree) (dvc-revision-get-buffer file revision-id))))
- (case type
- (local-tree (find-file-noselect file))
- (revision
- (with-current-buffer buffer
- (dvc-call "revision-get-file-revision"
- file (dvc-revision-get-data revision-id))
- (set-buffer-modified-p nil)
- (toggle-read-only 1)
- buffer))
- (previous-revision
- (with-current-buffer buffer
- (let* ((dvc (dvc-revision-get-dvc revision-id))
- (data (nth 0 (dvc-revision-get-data revision-id)))
- (rev-id (list dvc data)))
- (dvc-call "revision-get-previous-revision" file rev-id))
- (set-buffer-modified-p nil)
- (toggle-read-only 1)
- buffer))
- (last-revision
- (with-current-buffer buffer
- (dvc-call "revision-get-last-revision"
- file (dvc-revision-get-data revision-id))
- (set-buffer-modified-p nil)
- (toggle-read-only 1)
- buffer))
- (t (error "TODO: dvc-revision-get-file-in-buffer type %S" type)))))
- (defun dvc-dvc-revision-nth-ancestor (revision n)
- "Default function to get the n-th ancestor of REVISION."
- (let ((count n)
- (res revision))
- (while (> count 0)
- (setq res (dvc-revision-direct-ancestor res)
- count (- count 1)))
- res))
- ;;
- ;; DVC command version
- ;;
- (defun dvc-dvc-command-version ()
- "Fallback for `dvc-command-vesion'. Returns just `nil'.
- This function is called only if the current backend doesn't
- implement `command-version' function."
- nil)
- (provide 'dvc-core)
- ;;; dvc-core.el ends here