diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba2f643 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +SOURCES/subversion-1.10.2.tar.bz2 diff --git a/.subversion.metadata b/.subversion.metadata new file mode 100644 index 0000000..13cc2a5 --- /dev/null +++ b/.subversion.metadata @@ -0,0 +1 @@ +bc52ef2e671f821998ac9a5f7ebecbbcaaef83b8 SOURCES/subversion-1.10.2.tar.bz2 diff --git a/SOURCES/filter-requires.sh b/SOURCES/filter-requires.sh new file mode 100755 index 0000000..26131c9 --- /dev/null +++ b/SOURCES/filter-requires.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# Munge Perl requirements: +# - remove dependency on Config::Inifiles +# - only require File::Path >= 1.04, not >= 1.404 +# (since rpmvercmp thinks 04 < 1.404, not unreasonably) +# - filter out requirements for SVN:: modules; otherwise +# subversion requires subversion-perl +/usr/lib/rpm/perl.req $* | +sed -e '/perl(Config::IniFiles)/d' \ + -e '/perl(SVN::/d' \ + -e 's/perl(File::Path) >= 1.0404/perl(File::Path) >= 1.04/' + + diff --git a/SOURCES/psvn-init.el b/SOURCES/psvn-init.el new file mode 100644 index 0000000..cbe7cc3 --- /dev/null +++ b/SOURCES/psvn-init.el @@ -0,0 +1,3 @@ +(defalias 'svn-examine 'svn-status) +(autoload 'svn-status "psvn" "Examine the status of Subversion working copy in +directory DIR.") diff --git a/SOURCES/psvn.el b/SOURCES/psvn.el new file mode 100644 index 0000000..f775b67 --- /dev/null +++ b/SOURCES/psvn.el @@ -0,0 +1,6562 @@ +;;; psvn.el --- Subversion interface for emacs +;; Copyright (C) 2002-2015 by Stefan Reichoer + +;; Author: Stefan Reichoer +;; Note: This version is currently not under svn control +;; For the revision date see svn-psvn-revision below + +;; psvn.el 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 2, or (at your option) +;; any later version. + +;; psvn.el 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., 59 Temple Place - Suite 330, +;; Boston, MA 02111-1307, USA. + +;;; Commentary + +;; psvn.el is tested with GNU Emacs 21.3 on windows, debian linux, +;; freebsd5, red hat el4, ubuntu 11.10 with svn 1.6.12 + +;; psvn.el needs at least svn 1.1.0 +;; if you upgrade to a higher version, you need to do a fresh checkout + +;; psvn.el is an interface for the revision control tool subversion +;; (see http://subversion.tigris.org) +;; psvn.el provides a similar interface for subversion as pcl-cvs for cvs. +;; At the moment the following commands are implemented: +;; +;; M-x svn-status: run 'svn -status -v' +;; M-x svn-examine (like pcl-cvs cvs-examine) is alias for svn-status +;; +;; and show the result in the svn-status-buffer-name buffer (normally: *svn-status*). +;; If svn-status-verbose is set to nil, only "svn status" without "-v" +;; is run. Currently you have to toggle this variable manually. +;; This buffer uses svn-status mode in which the following keys are defined: +;; g - svn-status-update: run 'svn status -v' +;; M-s - svn-status-update: run 'svn status -v' +;; C-u g - svn-status-update: run 'svn status -vu' +;; = - svn-status-show-svn-diff run 'svn diff' +;; l - svn-status-show-svn-log run 'svn log' +;; i - svn-status-info run 'svn info' +;; r - svn-status-revert run 'svn revert' +;; X v - svn-status-resolved run 'svn resolved' +;; U - svn-status-update-cmd run 'svn update' +;; M-u - svn-status-update-cmd run 'svn update' +;; c - svn-status-commit run 'svn commit' +;; a - svn-status-add-file run 'svn add --non-recursive' +;; A - svn-status-add-file-recursively run 'svn add' +;; + - svn-status-make-directory run 'svn mkdir' +;; R - svn-status-mv run 'svn mv' +;; C - svn-status-cp run 'svn cp' +;; D - svn-status-rm run 'svn rm' +;; M-c - svn-status-cleanup run 'svn cleanup' +;; k - svn-status-lock run 'svn lock' +;; K - svn-status-unlock run 'svn unlock' +;; b - svn-status-blame run 'svn blame' +;; X e - svn-status-export run 'svn export' +;; RET - svn-status-find-file-or-examine-directory +;; ^ - svn-status-examine-parent +;; ~ - svn-status-get-specific-revision +;; E - svn-status-ediff-with-revision +;; X X - svn-status-resolve-conflicts +;; S g - svn-status-grep-files +;; S s - svn-status-search-files +;; s - svn-status-show-process-buffer +;; h - svn-status-pop-to-partner-buffer +;; e - svn-status-toggle-edit-cmd-flag +;; ? - svn-status-toggle-hide-unknown +;; _ - svn-status-toggle-hide-unmodified +;; z - svn-status-toggle-hide-externals +;; m - svn-status-set-user-mark +;; u - svn-status-unset-user-mark +;; $ - svn-status-toggle-elide +;; w - svn-status-copy-current-line-info +;; DEL - svn-status-unset-user-mark-backwards +;; * ! - svn-status-unset-all-usermarks +;; * ? - svn-status-mark-unknown +;; * A - svn-status-mark-added +;; * M - svn-status-mark-modified +;; * P - svn-status-mark-modified-properties +;; * D - svn-status-mark-deleted +;; * * - svn-status-mark-changed +;; * . - svn-status-mark-by-file-ext +;; * % - svn-status-mark-filename-regexp +;; * s - svn-status-store-usermarks +;; * l - svn-status-load-usermarks +;; . - svn-status-goto-root-or-return +;; f - svn-status-find-file +;; o - svn-status-find-file-other-window +;; C-o - svn-status-find-file-other-window-noselect +;; v - svn-status-view-file-other-window +;; I - svn-status-parse-info +;; V - svn-status-svnversion +;; P l - svn-status-property-list +;; P s - svn-status-property-set +;; P d - svn-status-property-delete +;; P e - svn-status-property-edit-one-entry +;; P i - svn-status-property-ignore-file +;; P I - svn-status-property-ignore-file-extension +;; P C-i - svn-status-property-edit-svn-ignore +;; P X e - svn-status-property-edit-svn-externals +;; P k - svn-status-property-set-keyword-list +;; P K i - svn-status-property-set-keyword-id +;; P K d - svn-status-property-set-keyword-date +;; P y - svn-status-property-set-eol-style +;; P x - svn-status-property-set-executable +;; P m - svn-status-property-set-mime-type +;; H - svn-status-use-history +;; x - svn-status-update-buffer +;; q - svn-status-bury-buffer + +;; C-x C-j - svn-status-dired-jump + +;; The output in the buffer contains this header to ease reading +;; of svn output: +;; FPH BASE CMTD Author em File +;; F = Filemark +;; P = Property mark +;; H = History mark +;; BASE = local base revision +;; CMTD = last committed revision +;; Author = author of change +;; em = "**" or "(Update Available)" [see `svn-status-short-mod-flag-p'] +;; if file can be updated +;; File = path/filename +;; + +;; To use psvn.el put the following line in your .emacs: +;; (require 'psvn) +;; Start the svn interface with M-x svn-status + +;; The latest version of psvn.el can be found at: +;; http://www.xsteve.at/prg/emacs/psvn.el + +;; TODO: +;; * shortcut for svn propset svn:keywords "Date" psvn.el +;; * docstrings for the functions +;; * perhaps shortcuts for ranges, dates +;; * when editing the command line - offer help from the svn client +;; * finish svn-status-property-set +;; * Add repository browser +;; * Get rid of all byte-compiler warnings +;; * SVK working copy support +;; * multiple independent buffers in svn-status-mode +;; There are "TODO" comments in other parts of this file as well. + +;; Overview over the implemented/not (yet) implemented svn sub-commands: +;; * add implemented +;; * blame implemented +;; * cat implemented +;; * checkout (co) implemented +;; * cleanup implemented +;; * commit (ci) implemented +;; * copy (cp) implemented +;; * delete (del, remove, rm) implemented +;; * diff (di) implemented +;; * export implemented +;; * help (?, h) +;; * import used (in svn-admin-create-trunk-directory) +;; * info implemented +;; * list (ls) implemented +;; * lock implemented +;; * log implemented +;; * merge +;; * mkdir implemented +;; * move (mv, rename, ren) implemented +;; * propdel (pdel) implemented +;; * propedit (pedit, pe) not needed +;; * propget (pget, pg) used (in svn-status-property-edit) +;; * proplist (plist, pl) implemented +;; * propset (pset, ps) used (in svn-prop-edit-do-it) +;; * resolved implemented +;; * revert implemented +;; * status (stat, st) implemented +;; * switch (sw) +;; * unlock implemented +;; * update (up) implemented + +;; For the not yet implemented commands you should use the command line +;; svn client. If there are user requests for any missing commands I will +;; probably implement them. + +;; There is also limited support for the web-based software project management and bug/issue tracking system trac +;; Trac ticket links can be enabled in the *svn-log* buffers when using the following: +;; (setq svn-log-link-handlers '(trac-ticket-short)) + +;; --------------------------- +;; Frequently asked questions: +;; --------------------------- + +;; Q1: I need support for user names with blanks/spaces +;; A1: Add the user names to svn-user-names-including-blanks and set the +;; svn-pre-parse-status-hook. +;; The problem is, that the user names and the file names from the svn status +;; output can both contain blanks. Blanks in file names are supported. +;; the svn-user-names-including-blanks list is used to replace the spaces +;; in the user names with - to overcome this problem + +;; Q2: My svn-update command it taking a really long time. How can I +;; see what's going on? +;; A2: In the *svn-status* buffer press "s". + +;; Q3: How do I enter a username and password? +;; A3: In the *svn-status* buffer press "s", switch to the +;; *svn-process* buffer and press enter. You will be prompted for +;; username and password. + +;; Q4: What does "?", "M", and "C" in the first column of the +;; *svn-status* buffer mean? +;; A4: "?" means the file(s) is not under Subversion control +;; "M" means you have a locally modified file +;; "C" means there is a conflict +;; "@$&#!" means someone is saying nasty things to you + + +;; Comments / suggestions and bug reports are welcome! + +;; Development notes +;; ----------------- + +;; "svn-" is the package prefix used in psvn.el. There are also longer +;; prefixes which clarify the code and help symbol completion, but they +;; are not intended to prevent name clashes with other packages. All +;; interactive commands meant to be used only in a specific mode should +;; have names beginning with the name of that mode: for example, +;; "svn-status-add-file" in "svn-status-mode". "psvn" should be used +;; only in names of files, customization groups, and features. If SVK +;; support is ever added, it should use "svn-svk-" when no existing +;; prefix is applicable. + +;; Many of the variables marked as `risky-local-variable' are probably +;; impossible to abuse, as the commands that read them are used only in +;; buffers that are not visiting any files. Better safe than sorry. + +;;; Code: + +(defconst svn-psvn-revision "2015-07-20, 21:42:00" "The revision date of psvn.") + + +(require 'easymenu) + +(eval-when-compile (require 'dired)) +(eval-when-compile (require 'ediff-util)) +(eval-when-compile (require 'ediff-wind)) +(eval-when-compile (require 'vc-hooks)) +(eval-when-compile (require 'elp)) +(eval-when-compile (require 'pp)) + +(condition-case nil + (progn + (require 'diff-mode)) + (error nil)) + + +;;; user setable variables +(defcustom svn-status-verbose t + "*Add '-v' to svn status call. +This can be toggled with \\[svn-status-toggle-svn-verbose-flag]." + :type 'boolean + :group 'psvn) +(defcustom svn-log-edit-file-name "++svn-log++" + "*Name of a saved log file. +This can be either absolute, or relative to the default directory +of the `svn-log-edit-buffer-name' buffer." + :type 'file + :group 'psvn) +(put 'svn-log-edit-file-name 'risky-local-variable t) +(defcustom svn-log-edit-insert-files-to-commit t + "*Insert the filelist to commit in the *svn-log* buffer" + :type 'boolean + :group 'psvn) +(defcustom svn-log-edit-show-diff-for-commit nil + "*Show the diff being committed when you run `svn-status-commit.'." + :type 'boolean + :group 'psvn) +(defcustom svn-log-edit-use-log-edit-mode + (and (condition-case nil (require 'log-edit) (error nil)) t) + "*Use log-edit-mode as base for svn-log-edit-mode +This variable takes effect only when psvn.el is being loaded." + :type 'boolean + :group 'psvn) +(defcustom svn-log-edit-paragraph-start + "$\\|[ \t]*$\\|##.*$\\|\\*.*:.*$\\|[ \t]+(.+):.*$" + "*Value used for `paragraph-start' in `svn-log-edit-buffer-name' buffer." + :type 'regexp + :group 'psvn) +(defcustom svn-log-edit-paragraph-separate "$\\|##.*$" + "*Value used for `paragraph-separate' in `svn-log-edit-buffer-name' buffer." + :type 'regexp + :group 'psvn) +(defcustom svn-status-hide-unknown nil + "*Hide unknown files in `svn-status-buffer-name' buffer. +This can be toggled with \\[svn-status-toggle-hide-unknown]." + :type 'boolean + :group 'psvn) +(defcustom svn-status-hide-unmodified nil + "*Hide unmodified files in `svn-status-buffer-name' buffer. +This can be toggled with \\[svn-status-toggle-hide-unmodified]." + :type 'boolean + :group 'psvn) +(defcustom svn-status-hide-externals nil + "*Hide external files in `svn-status-buffer-name' buffer. +This can be toggled with \\[svn-status-toggle-hide-externals]." + :type 'boolean + :group 'psvn) +(defcustom svn-status-sort-status-buffer t + "*Whether to sort the `svn-status-buffer-name' buffer. + +Setting this variable to nil speeds up \\[M-x svn-status], however the +listing may then become incorrect. + +This can be toggled with \\[svn-status-toggle-sort-status-buffer]." + :type 'boolean + :group 'psvn) + +(defcustom svn-status-ediff-delete-temporary-files nil + "*Whether to delete temporary ediff files. If set to ask, ask the user" + :type '(choice (const t) + (const nil) + (const ask)) + :group 'psvn) + +(defcustom svn-status-changelog-style 'changelog + "*The changelog style that is used for `svn-file-add-to-changelog'. +Possible values are: + 'changelog: use `add-change-log-entry-other-window' + 'svn-dev: use commit messages that are used by the svn developers + a function: This function is called to add a new entry to the changelog file. +" + :type '(set (const changelog) + (const svn-dev)) + :group 'psvn) + +(defcustom svn-status-unmark-files-after-list '(commit revert) + "*List of operations after which all user marks will be removed. +Possible values are: commit, revert." + :type '(set (const commit) + (const revert)) + :group 'psvn) + +(defcustom svn-status-preserve-window-configuration t + "*Try to preserve the window configuration." + :type 'boolean + :group 'psvn) + +(defcustom svn-status-auto-revert-buffers t + "*Auto revert buffers that have changed on disk." + :type 'boolean + :group 'psvn) + +(defcustom svn-status-fancy-file-state-in-modeline t + "*Show a color dot in the modeline that describes the state of the current file." + :type 'boolean + :group 'psvn) + +(defcustom svn-status-indentation 2 + "*Indenation per directory level in the `svn-status-buffer-name' buffer." + :type 'integer + :group 'psvn) + +(defcustom svn-status-negate-meaning-of-arg-commands '() + "*List of operations that should use a negated meaning of the prefix argument. +The supported functions are `svn-status' and `svn-status-set-user-mark'." + :type '(set (function-item svn-status) + (function-item svn-status-set-user-mark)) + :group 'psvn) + +(defcustom svn-status-svn-executable "svn" + "*The name of the svn executable. +This can be either absolute or looked up on `exec-path'." + ;; Don't use (file :must-match t). It doesn't know about `exec-path'. + :type 'file + :group 'psvn) +(put 'svn-status-svn-executable 'risky-local-variable t) + +(defcustom svn-status-default-export-directory "~/" "*The default directory that is suggested svn export." + :type 'file + :group 'psvn) + +(defcustom svn-status-svn-environment-var-list '("LC_MESSAGES=C" "LC_ALL=") + "*A list of environment variables that should be set for that svn process. +Each element is either a string \"VARIABLE=VALUE\" which will be added to +the environment when svn is run, or just \"VARIABLE\" which causes that +variable to be entirely removed from the environment. + +The default setting is '(\"LC_MESSAGES=C\" \"LC_ALL=\"). This ensures that the svn command +line client does not output localized strings. psvn.el relies on the english +messages." + :type '(repeat string) + :group 'psvn) +(put 'svn-status-svn-environment-var-list 'risky-local-variable t) + +(defcustom svn-browse-url-function nil + ;; If the user hasn't changed `svn-browse-url-function', then changing + ;; `browse-url-browser-function' should affect psvn even after it has + ;; been loaded. + "Function to display a Subversion related WWW page in a browser. +So far, this is used only for \"trac\" issue tracker integration. +By default, this is nil, which means use `browse-url-browser-function'. +Any non-nil value overrides that variable, with the same syntax." + ;; It would be nice to show the full list of browsers supported by + ;; browse-url, but (custom-variable-type 'browse-url-browser-function) + ;; returns just `function' if browse-url has not yet been loaded, + ;; and there seems to be no easy way to autoload browse-url when + ;; the custom-type of svn-browse-url-function is actually needed. + ;; So I'll only offer enough choices to cover all supported types. + :type `(choice (const :tag "Specified by `browse-url-browser-function'" nil) + (function :value browse-url-default-browser + ;; In XEmacs 21.4.17, the `function' widget matches + ;; all objects. Constrain it here so that alists + ;; fall through to the next choice. Accept either + ;; a symbol (fbound or not) or a lambda expression. + :match ,(lambda (widget value) + (or (symbolp value) (functionp value)))) + (svn-alist :tag "Regexp/function association list" + :key-type regexp :value-type function + :value (("." . browse-url-default-browser)))) + :link '(emacs-commentary-link "browse-url") + :group 'psvn) +;; (put 'svn-browse-url-function 'risky-local-variable t) +;; already implied by "-function" suffix + +(defcustom svn-log-edit-header + "## Lines starting with '## ' will be removed from the log message.\n" + "*Header content of the *svn-log* buffer" + :type 'string + :group 'psvn) + +(defcustom svn-status-window-alist + '((diff "*svn-diff*") (log "*svn-log*") (info t) (blame t) (proplist t) (update t)) + "An alist to specify which windows should be used for svn command outputs. +The following keys are supported: diff, log, info, blame, proplist, update. +The following values can be given: +nil ... show in `svn-process-buffer-name' buffer +t ... show in dedicated *svn-info* buffer +invisible ... don't show the buffer (eventually useful for update) +a string ... show in a buffer named string" + :type '(svn-alist + :key-type symbol + :value-type (group + (choice + (const :tag "Show in *svn-process* buffer" nil) + (const :tag "Show in dedicated *svn-info* buffer" t) + (const :tag "Don't show the output" invisible) + (string :tag "Show in a buffer named")))) + :options '(diff log info blame proplist update) + :group 'psvn) + +(defcustom svn-status-short-mod-flag-p t + "*Whether the mark for out of date files is short or long. + +If this variable is is t, and a file is out of date (i.e., there is a newer +version in the repository than the working copy), then the file will +be marked by \"**\" + +If this variable is nil, and the file is out of date then the longer phrase +\"(Update Available)\" is used. + +In either case the mark gets the face +`svn-status-update-available-face', and will only be visible if +`\\[svn-status-update]' is run with a prefix argument" + :type '(choice (const :tag "Short \"**\"" t) + (const :tag "Long \"(Update Available)\"" nil)) + :group 'psvn) + +(defvar svn-status-debug-level 0 "The psvn.el debugging verbosity level. +The higher the number, the more debug messages are shown. + +See `svn-status-message' for the meaning of values for that variable.") + +(defvar svn-bookmark-list nil "A list of locations for a quick access via `svn-status-via-bookmark'") +;;(setq svn-bookmark-list '(("proj1" . "~/work/proj1") +;; ("doc1" . "~/docs/doc1"))) + +(defvar svn-status-buffer-name "*svn-status*" "Name for the svn status buffer") +(defvar svn-process-buffer-name " *svn-process*" "Name for the svn process buffer") +(defvar svn-log-edit-buffer-name "*svn-log-edit*" "Name for the svn log-edit buffer") + +(defcustom svn-status-use-header-line + (if (boundp 'header-line-format) t 'inline) + "*Whether a header line should be used. +When t: Use the emacs header line +When 'inline: Insert the header line in the `svn-status-buffer-name' buffer +Otherwise: Don't display a header line" + :type '(choice (const :tag "Show column titles as a header line" t) + (const :tag "Insert column titles as text in the buffer" inline) + (other :tag "No column titles" nil)) + :group 'psvn) + +;;; default arguments to pass to svn commands +;; TODO: When customizing, an option menu or completion might be nice.... +(defcustom svn-status-default-log-arguments '("-v") + "*List of arguments to pass to svn log. +\(used in `svn-status-show-svn-log'; override these by giving prefixes\)." + :type '(repeat string) + :group 'psvn) +(put 'svn-status-default-log-arguments 'risky-local-variable t) + +(defcustom svn-status-default-commit-arguments '() + "*List of arguments to pass to svn commit. +If you don't like recursive commits, set this value to (\"-N\") +or mark the directory before committing it. +Do not put an empty string here, except as an argument of an option: +Subversion and the operating system may treat that as a file name +equivalent to \".\", so you would commit more than you intended." + :type '(repeat string) + :group 'psvn) +(put 'svn-status-default-commit-arguments 'risky-local-variable t) + +(defcustom svn-status-default-diff-arguments '("-x" "--ignore-eol-style") + "*A list of arguments that is passed to the svn diff command. +When the built in diff command is used, +the following options are available: --ignore-eol-style, --ignore-space-change, +--ignore-all-space, --ignore-eol-style. +The following setting ignores eol style changes and all white space changes: +'(\"-x\" \"--ignore-eol-style --ignore-all-space\") + +If you'd like to suppress whitespace changes using the external diff command +use the following value: +'(\"--diff-cmd\" \"diff\" \"-x\" \"-wbBu\") + +" + :type '(repeat string) + :group 'psvn) +(put 'svn-status-default-diff-arguments 'risky-local-variable t) + +(defcustom svn-status-default-status-arguments '() + "*A list of arguments that is passed to the svn status command. +The following options are available: --ignore-externals + +" + :type '(repeat string) + :group 'psvn) +(put 'svn-status-default-status-arguments 'risky-local-variable t) + +(defcustom svn-status-default-blame-arguments '("-x" "--ignore-eol-style") + "*A list of arguments that is passed to the svn blame command. +See `svn-status-default-diff-arguments' for some examples." + :type '(repeat string) + :group 'psvn) + +(put 'svn-status-default-blame-arguments 'risky-local-variable t) + +(defvar svn-trac-project-root nil + "Path for an eventual existing trac issue tracker. +This can be set with \\[svn-status-set-trac-project-root].") + +(defvar svn-status-module-name nil + "*A short name for the actual project. +This can be set with \\[svn-status-set-module-name].") + +(defvar svn-status-branch-list nil + "*A list of known branches for the actual project +This can be set with \\[svn-status-set-branch-list]. + +The list contains full repository paths or shortcuts starting with \# +\# at the beginning is replaced by the repository url. +\#1\# has the special meaning that all paths below the given directory +will be considered for interactive selections. + +A useful setting might be: '\(\"\#trunk\" \"\#1\#tags\" \"\#1\#branches\")") + +(defvar svn-status-load-state-before-svn-status t + "*Whether to automatically restore state from ++psvn.state file before running svn-status.") + +(defvar svn-log-link-handlers nil "A list of link handlers in *svn-log* buffers. +These link handlers must be registered via `svn-log-register-link-handler'") + +;;; hooks +(defvar svn-status-mode-hook nil "Hook run when entering `svn-status-mode'.") +(defvar svn-log-edit-mode-hook nil "Hook run when entering `svn-log-edit-mode'.") +(defvar svn-log-edit-done-hook nil "Hook run after commiting files via svn.") +;; (put 'svn-log-edit-mode-hook 'risky-local-variable t) +;; (put 'svn-log-edit-done-hook 'risky-local-variable t) +;; already implied by "-hook" suffix + +(defvar svn-post-process-svn-output-hook 'svn-fixup-tramp-output-maybe "Hook that can be used to preprocess the output from svn. +The function `svn-status-remove-control-M' can be useful for that hook") + +(when (eq system-type 'windows-nt) + (add-hook 'svn-post-process-svn-output-hook 'svn-status-remove-control-M)) + +(defvar svn-status-svn-process-coding-system (when (boundp 'locale-coding-system) locale-coding-system) + "The coding system that is used for the svn command line client. +It is used in svn-run, if it is not nil.") + +(defvar svn-status-svn-file-coding-system 'undecided-unix + "The coding system that is used to save files that are loaded as +parameter or data files via the svn command line client. +It is used in the following functions: `svn-prop-edit-do-it', `svn-log-edit-done'. +You could set it to 'utf-8") + +(defcustom svn-status-use-ido-completion + (fboundp 'ido-completing-read) + "*Use ido completion functionality." + :type 'boolean + :group 'psvn) + +(defvar svn-status-completing-read-function + (if svn-status-use-ido-completion 'ido-completing-read 'completing-read)) + +;;; experimental features +(defvar svn-status-track-user-input nil "Track user/password queries. +This feature is implemented via a process filter. +It is an experimental feature.") + +(defvar svn-status-refresh-info nil "Whether `svn-status-update-buffer' should call `svn-status-parse-info'.") + +;;; Customize group +(defgroup psvn nil + "Subversion interface for Emacs." + :group 'tools) + +(defgroup psvn-faces nil + "psvn faces." + :group 'psvn) + + +(eval-and-compile + (require 'cl) + (defconst svn-xemacsp (featurep 'xemacs)) + (if svn-xemacsp + (require 'overlay) + (require 'overlay nil t))) + +(defcustom svn-status-display-full-path nil + "Specifies how the filenames look like in the listing. +If t, their full path name will be displayed, else only the filename." + :type 'boolean + :group 'psvn) + +(defcustom svn-status-prefix-key [(control x) (meta s)] + "Prefix key for the psvn commands in the global keymap." + :type '(choice (const [(control x) ?v ?S]) + (const [(super s)]) + (const [(hyper s)]) + (const [(control x) ?v]) + (const [(control x) ?V]) + (sexp)) + :group 'psvn + :set (lambda (var value) + (if (boundp var) + (global-unset-key (symbol-value var))) + (set var value) + (global-set-key (symbol-value var) 'svn-global-keymap))) + +(defcustom svn-admin-default-create-directory "~/" + "*The default directory that is suggested for `svn-admin-create'." + :type 'string + :group 'psvn) + +(defvar svn-status-custom-hide-function nil + "A function that receives a line-info and decides whether to hide that line. +See psvn.el for an example function.") +;; (put 'svn-status-custom-hide-function 'risky-local-variable t) +;; already implied by "-function" suffix + + +;; Use the normally used mode for files ending in .~HEAD~, .~BASE~, ... +(add-to-list 'auto-mode-alist '("\\.~?\\(HEAD\\|BASE\\|PREV\\)~?\\'" ignore t)) + +;;; internal variables +(defvar svn-status-directory-history nil "List of visited svn working directories.") +(defvar svn-process-cmd nil) +(defvar svn-status-info nil) +(defvar svn-status-filename-to-buffer-position-cache (make-hash-table :test 'equal :weakness t)) +(defvar svn-status-base-info nil "The parsed result from the svn info command as a plist.") +(defvar svn-status-initial-window-configuration nil) +(defvar svn-status-default-column 23) +(defvar svn-status-default-revision-width 4) +(defvar svn-status-default-author-width 9) +(defvar svn-status-line-format " %c%c%c %4s %4s %-9s") +(defvar svn-start-of-file-list-line-number 0) +(defvar svn-status-files-to-commit nil + "List of files to commit at `svn-log-edit-done'. +This is always set together with `svn-status-recursive-commit'.") +(defvar svn-status-recursive-commit nil + "Non-nil if the next commit should be recursive. +This is always set together with `svn-status-files-to-commit'.") +(defvar svn-log-edit-update-log-entry nil + "Revision number whose log entry is being edited. +This is nil if the log entry is for a new commit.") +(defvar svn-status-pre-commit-window-configuration nil) +(defvar svn-status-pre-propedit-window-configuration nil) +(defvar svn-status-head-revision nil) +(defvar svn-status-root-return-info nil) +(defvar svn-status-property-edit-must-match-flag nil) +(defvar svn-status-propedit-property-name nil "The property name for the actual svn propset command") +(defvar svn-status-propedit-file-list nil) +(defvar svn-status-mode-line-process "") +(defvar svn-status-mode-line-process-status "") +(defvar svn-status-mode-line-process-edit-flag "") +(defvar svn-status-edit-svn-command nil) +(defvar svn-status-update-previous-process-output nil) +(defvar svn-pre-run-asynch-recent-keys nil) +(defvar svn-pre-run-mode-line-process nil) +(defvar svn-arg-file-content nil) +(defvar svn-status-temp-dir + (file-name-as-directory + (expand-file-name + (or + (when (boundp 'temporary-file-directory) temporary-file-directory) ;emacs + ;; XEmacs 21.4.17 can return "/tmp/kalle" from (temp-directory). + ;; `file-name-as-directory' adds a slash so we can append a file name. + (when (fboundp 'temp-directory) (temp-directory)) + "/tmp/"))) "The directory that is used to store temporary files for psvn.") +;; Because `temporary-file-directory' is not a risky local variable in +;; GNU Emacs 22.0.51, we don't mark `svn-status-temp-dir' as such either. +(defvar svn-temp-suffix (make-temp-name ".")) +(put 'svn-temp-suffix 'risky-local-variable t) +(defvar svn-status-temp-file-to-remove nil) +(put 'svn-status-temp-file-to-remove 'risky-local-variable t) +(defvar svn-status-temp-arg-file (concat svn-status-temp-dir "svn.arg" svn-temp-suffix)) +(put 'svn-status-temp-arg-file 'risky-local-variable t) +(defvar svn-status-options nil) +(defvar svn-status-remote) +(defvar svn-status-commit-rev-number nil) +(defvar svn-status-update-rev-number nil) +(defvar svn-status-operated-on-dot nil) +(defvar svn-status-last-commit-author nil) +(defvar svn-status-elided-list nil) +(defvar svn-status-last-output-buffer-name nil "The buffer name for the buffer that holds the output from the last executed svn command") +(defvar svn-status-pre-run-svn-buffer nil) +(defvar svn-status-update-list nil) +(defvar svn-transient-buffers) +(defvar svn-ediff-windows) +(defvar svn-ediff-result) +(defvar svn-status-last-diff-options nil) +(defvar svn-status-blame-file-name nil) +(defvar svn-status-blame-revision nil) +(defvar svn-admin-last-repository-dir nil "The last repository url for various operations.") +(defvar svn-last-cmd-ring (make-ring 30) "Ring that holds the last executed svn commands (for debugging purposes)") +(defvar svn-status-cached-version-string nil) +(defvar svn-client-version nil "The version number of the used svn client") +(defvar svn-status-get-line-information-for-file nil) +(defvar svn-status-base-dir-cache (make-hash-table :test 'equal :weakness nil)) +(defvar svn-status-usermark-storage (make-hash-table :test 'equal :weakness nil)) +(defvar svn-log-registered-link-handlers (make-hash-table :test 'eql :weakness nil)) + +(defvar svn-status-partner-buffer nil "The partner buffer for this svn related buffer") +(make-variable-buffer-local 'svn-status-partner-buffer) + +;; Emacs 21 defines these in ediff-init.el but it seems more robust +;; to just declare the variables here than try to load that file. +;; It is Ediff's job to declare these as risky-local-variable if needed. +(defvar ediff-buffer-A) +(defvar ediff-buffer-B) +(defvar ediff-buffer-C) +(defvar ediff-quit-hook) + +;; Ditto for log-edit.el. +(defvar log-edit-initial-files) +(defvar log-edit-callback) +(defvar log-edit-listfun) + +;; Ediff does not use this variable in GNU Emacs 20.7, GNU Emacs 21.4, +;; nor XEmacs 21.4.17. However, pcl-cvs (a.k.a. pcvs) does. +;; TODO: Check if this should be moved into the "svn-" namespace. +(defvar ediff-after-quit-destination-buffer) + +;; That is an example for the svn-status-custom-hide-function: +;; Note: For many cases it is a better solution to ignore files or +;; file extensions via the svn-ignore properties (on P i, P I) +;; (setq svn-status-custom-hide-function 'svn-status-hide-pyc-files) +;; (defun svn-status-hide-pyc-files (info) +;; "Hide all pyc files in the `svn-status-buffer-name' buffer." +;; (let* ((fname (svn-status-line-info->filename-nondirectory info)) +;; (fname-len (length fname))) +;; (and (> fname-len 4) (string= (substring fname (- fname-len 4)) ".pyc")))) + +;;; faces +(defface svn-status-marked-face + '((((type tty) (class color)) (:foreground "green" :weight light)) + (((class color) (background light)) (:foreground "green3")) + (((class color) (background dark)) (:foreground "palegreen2")) + (t (:weight bold))) + "Face to highlight the mark for user marked files in svn status buffers." + :group 'psvn-faces) + +(defface svn-status-marked-popup-face + '((((type tty) (class color)) (:foreground "green" :weight light)) + (((class color) (background light)) (:foreground "green3")) + (((class color) (background dark)) (:foreground "palegreen2")) + (t (:weight bold))) + "Face to highlight the actual file, if a popup menu is activated." + :group 'psvn-faces) + +(defface svn-status-update-available-face + '((((type tty) (class color)) (:foreground "magenta" :weight light)) + (((class color) (background light)) (:foreground "magenta")) + (((class color) (background dark)) (:foreground "yellow")) + (t (:weight bold))) + "Face used to highlight the 'out of date' mark. +\(i.e., the mark used when there is a newer version in the repository +than the working copy.\) + +See also `svn-status-short-mod-flag-p'." + :group 'psvn-faces) + +;based on cvs-filename-face +(defface svn-status-directory-face + '((((type tty) (class color)) (:foreground "lightblue" :weight light)) + (((class color) (background light)) (:foreground "blue4")) + (((class color) (background dark)) (:foreground "lightskyblue1")) + (t (:weight bold))) + "Face for directories in *svn-status* buffers. +See `svn-status--line-info->directory-p' for what counts as a directory." + :group 'psvn-faces) + +;based on font-lock-comment-face +(defface svn-status-filename-face + '((((class color) (background light)) (:foreground "chocolate")) + (((class color) (background dark)) (:foreground "beige"))) + "Face for non-directories in *svn-status* buffers. +See `svn-status--line-info->directory-p' for what counts as a directory." + :group 'psvn-faces) + +;not based on anything, may be horribly ugly! +(defface svn-status-symlink-face + '((((class color) (background light)) (:foreground "cornflower blue")) + (((class color) (background dark)) (:foreground "cyan"))) + "Face for symlinks in *svn-status* buffers. + +This is the face given to the actual link (i.e., the versioned item), +the target of the link gets either `svn-status-filename-face' or +`svn-status-directory-face'." + :group 'psvn-faces) + +;based on font-lock-warning-face +(defface svn-status-locked-face + '((t + (:weight bold :foreground "Red"))) + "Face for the phrase \"[ LOCKED ]\" `svn-status-buffer-name' buffers." + :group 'psvn-faces) + +;based on vhdl-font-lock-directive-face +(defface svn-status-switched-face + '((((class color) + (background light)) + (:foreground "CadetBlue")) + (((class color) + (background dark)) + (:foreground "Aquamarine")) + (t + (:bold t :italic t))) + "Face for the phrase \"(switched)\" non-directories in svn status buffers." + :group 'psvn-faces) + +(if svn-xemacsp + (defface svn-status-blame-highlight-face + '((((type tty) (class color)) (:foreground "green" :weight light)) + (((class color) (background light)) (:foreground "green3")) + (((class color) (background dark)) (:foreground "palegreen2")) + (t (:weight bold))) + "Default face for highlighting a line in svn status blame mode." + :group 'psvn-faces) + (defface svn-status-blame-highlight-face + '((t :inherit highlight)) + "Default face for highlighting a line in svn status blame mode." + :group 'psvn-faces)) + +(if svn-xemacsp + (defface svn-log-partner-highlight-face + '((((type tty) (class color)) (:foreground "yellow" :weight light)) + (((class color) (background light)) (:foreground "gold")) + (((class color) (background dark)) (:foreground "gold")) + (t (:weight bold))) + "Default face for highlighting the partner in svn log mode." + :group 'psvn-faces) + (defface svn-log-partner-highlight-face + '((((class color) (background light)) + (:background "light goldenrod" :weight bold)) + (t (:weight bold))) + "Default face for highlighting the partner in svn log mode." + :group 'psvn-faces)) + +(defface svn-status-blame-rev-number-face + '((((class color) (background light)) (:foreground "DarkGoldenrod")) + (((class color) (background dark)) (:foreground "LightGoldenrod")) + (t (:weight bold :slant italic))) + "Face to highlight revision numbers in the svn-blame mode." + :group 'psvn-faces) + +(defvar svn-highlight t) +;; stolen from PCL-CVS +(defun svn-add-face (str face &optional keymap) + "Return string STR decorated with the specified FACE. +If `svn-highlight' is nil then just return STR." + (when svn-highlight + ;; Do not use `list*'; cl.el might not have been loaded. We could + ;; put (require 'cl) at the top but let's try to manage without. + (add-text-properties 0 (length str) + `(face ,face + mouse-face highlight) +;; 18.10.2004: the keymap parameter is not used (yet) in psvn.el +;; ,@(when keymap +;; `(mouse-face highlight +;; local-map ,keymap))) + str)) + str) + +(defun svn-status-maybe-add-face (condition text face) + "If CONDITION then add FACE to TEXT. +Else return TEXT unchanged." + (if condition + (svn-add-face text face) + text)) + +(defun svn-status-choose-face-to-add (condition text face1 face2) + "If CONDITION then add FACE1 to TEXT, else add FACE2 to TEXT." + (if condition + (svn-add-face text face1) + (svn-add-face text face2))) + +(defun svn-status-maybe-add-string (condition string face) + "If CONDITION then return STRING decorated with FACE. +Otherwise, return \"\"." + (if condition + (svn-add-face string face) + "")) + +;; compatibility +;; emacs 20 +(defalias 'svn-point-at-eol + (if (fboundp 'point-at-eol) 'point-at-eol 'line-end-position)) +(defalias 'svn-point-at-bol + (if (fboundp 'point-at-bol) 'point-at-bol 'line-beginning-position)) +(defalias 'svn-read-directory-name + (if (fboundp 'read-directory-name) 'read-directory-name 'read-file-name)) + +(eval-when-compile + (if (not (fboundp 'gethash)) + (require 'cl-macs))) +(defalias 'svn-puthash (if (fboundp 'puthash) 'puthash 'cl-puthash)) + +;; emacs 21 +(if (fboundp 'line-number-at-pos) + (defalias 'svn-line-number-at-pos 'line-number-at-pos) + (defun svn-line-number-at-pos (&optional pos) + "Return (narrowed) buffer line number at position POS. +If POS is nil, use current buffer location." + (let ((opoint (or pos (point))) start) + (save-excursion + (goto-char (point-min)) + (setq start (point)) + (goto-char opoint) + (forward-line 0) + (1+ (count-lines start (point))))))) + +(defun svn-substring-no-properties (string &optional from to) + (if (fboundp 'substring-no-properties) + (substring-no-properties string from to) + (substring string (or from 0) to))) + +; xemacs +;; Evaluate the defsubst at compile time, so that the byte compiler +;; knows the definition and can inline calls. It cannot detect the +;; defsubst automatically from within the if form. +(eval-and-compile + (if (fboundp 'match-string-no-properties) + (defalias 'svn-match-string-no-properties 'match-string-no-properties) + (defsubst svn-match-string-no-properties (match) + (buffer-substring-no-properties (match-beginning match) (match-end match))))) + +; XEmacs doesn't have a function `help-buffer' +(eval-and-compile + (if (fboundp 'help-buffer) + (defalias 'svn-help-buffer 'help-buffer) ; FSF Emacs + (defun svn-help-buffer () + (buffer-name (get-buffer-create (help-buffer-name "SVN")))))) ; XEmacs + + +;; XEmacs 21.4.17 does not have an `alist' widget. Define a replacement. +;; To find out whether the `alist' widget exists, we cannot check just +;; (get 'alist 'widget-type), because GNU Emacs 21.4 defines it in +;; "wid-edit.el", which is not preloaded; it will be autoloaded when +;; `widget-create' is called. Instead, we call `widgetp', which is +;; also autoloaded from "wid-edit.el". XEmacs 21.4.17 does not have +;; `widgetp' either, so we check that first. +(if (and (fboundp 'widgetp) (widgetp 'alist)) + (define-widget 'svn-alist 'alist + "An association list. +Use this instead of `alist', for XEmacs 21.4 compatibility.") + (define-widget 'svn-alist 'list + "An association list. +Use this instead of `alist', for XEmacs 21.4 compatibility." + :convert-widget 'svn-alist-convert-widget + :tag "Association List" + :key-type 'sexp + :value-type 'sexp) + (defun svn-alist-convert-widget (widget) + (let* ((value-type (widget-get widget :value-type)) + (option-widgets (loop for option in (widget-get widget :options) + collect `(cons :format "%v" + (const :format "%t: %v\n" + :tag "Key" + ,option) + ,value-type)))) + (widget-put widget :args + `(,@(when option-widgets + `((set :inline t :format "%v" + ,@option-widgets))) + (editable-list :inline t + (cons :format "%v" + ,(widget-get widget :key-type) + ,value-type))))) + widget)) + +;; process launch functions +(defvar svn-call-process-function (if (fboundp 'process-file) 'process-file 'call-process)) +(defvar svn-start-process-function (if (fboundp 'start-file-process) 'start-file-process 'start-process)) + + +;;; keymaps + +(defvar svn-global-keymap nil "Global keymap for psvn.el. +To bind this to a different key, customize `svn-status-prefix-key'.") +(put 'svn-global-keymap 'risky-local-variable t) +(when (not svn-global-keymap) + (setq svn-global-keymap (make-sparse-keymap)) + (define-key svn-global-keymap (kbd "v") 'svn-status-version) + (define-key svn-global-keymap (kbd "s") 'svn-status-this-directory) + (define-key svn-global-keymap (kbd "b") 'svn-status-via-bookmark) + (define-key svn-global-keymap (kbd "h") 'svn-status-use-history) + (define-key svn-global-keymap (kbd "u") 'svn-status-update-cmd) + (define-key svn-global-keymap (kbd "=") 'svn-status-show-svn-diff) + (define-key svn-global-keymap (kbd "f =") 'svn-file-show-svn-diff) + (define-key svn-global-keymap (kbd "f e") 'svn-file-show-svn-ediff) + (define-key svn-global-keymap (kbd "f l") 'svn-status-show-svn-log) + (define-key svn-global-keymap (kbd "f b") 'svn-status-blame) + (define-key svn-global-keymap (kbd "f a") 'svn-file-add-to-changelog) + (define-key svn-global-keymap (kbd "f r") 'svn-file-revert) + (define-key svn-global-keymap (kbd "c") 'svn-status-commit) + (define-key svn-global-keymap (kbd "S") 'svn-status-switch-to-status-buffer) + (define-key svn-global-keymap (kbd "o") 'svn-status-pop-to-status-buffer) + (define-key svn-global-keymap (kbd "C-k") 'svn-process-kill)) + +(defvar svn-status-diff-mode-map () + "Keymap used in `svn-status-diff-mode' for additional commands that are not defined in diff-mode.") +(put 'svn-status-diff-mode-map 'risky-local-variable t) ;for Emacs 20.7 + +(when (not svn-status-diff-mode-map) + (setq svn-status-diff-mode-map (copy-keymap diff-mode-shared-map)) + (define-key svn-status-diff-mode-map [?g] 'revert-buffer) + (define-key svn-status-diff-mode-map [?s] 'svn-status-pop-to-status-buffer) + (define-key svn-status-diff-mode-map [?c] 'svn-status-diff-pop-to-commit-buffer) + (define-key svn-status-diff-mode-map [?w] 'svn-status-diff-save-current-defun-as-kill)) + +(defvar svn-global-trac-map () + "Subkeymap used in `svn-global-keymap' for trac issue tracker commands.") +(put 'svn-global-trac-map 'risky-local-variable t) ;for Emacs 20.7 +(when (not svn-global-trac-map) + (setq svn-global-trac-map (make-sparse-keymap)) + (define-key svn-global-trac-map (kbd "w") 'svn-trac-browse-wiki) + (define-key svn-global-trac-map (kbd "t") 'svn-trac-browse-timeline) + (define-key svn-global-trac-map (kbd "m") 'svn-trac-browse-roadmap) + (define-key svn-global-trac-map (kbd "s") 'svn-trac-browse-source) + (define-key svn-global-trac-map (kbd "r") 'svn-trac-browse-report) + (define-key svn-global-trac-map (kbd "i") 'svn-trac-browse-ticket) + (define-key svn-global-trac-map (kbd "c") 'svn-trac-browse-changeset) + (define-key svn-global-keymap (kbd "t") svn-global-trac-map)) + +;; The setter of `svn-status-prefix-key' makes a binding in the global +;; map refer to the `svn-global-keymap' symbol, rather than directly +;; to the keymap. Emacs then implicitly uses the symbol-function. +;; This has the advantage that `describe-bindings' (C-h b) can show +;; the name of the keymap and link to its documentation. +(defalias 'svn-global-keymap svn-global-keymap) +;; `defalias' of GNU Emacs 21.4 doesn't allow a docstring argument. +(put 'svn-global-keymap 'function-documentation + '(documentation-property 'svn-global-keymap 'variable-documentation t)) + + +;; named after SVN_WC_ADM_DIR_NAME in svn_wc.h +(defun svn-wc-adm-dir-name () + "Return the name of the \".svn\" subdirectory or equivalent." + (if (and (eq system-type 'windows-nt) + (getenv "SVN_ASP_DOT_NET_HACK")) + "_svn" + ".svn")) + +(defun svn-log-edit-file-name (&optional curdir) + "Get the name of the saved log edit file +If curdir, return `svn-log-edit-file-name' +Otherwise position svn-log-edit-file-name in the root directory of this working copy" + (if curdir + svn-log-edit-file-name + (concat (svn-status-base-dir) svn-log-edit-file-name))) + +(defun svn-status-message (level &rest args) + "If LEVEL is lower than `svn-status-debug-level' print ARGS using `message'. + +Guideline for numbers: +1 - error messages, 3 - non-serious error messages, 5 - messages for things +that take a long time, 7 - not very important messages on stuff, 9 - messages +inside loops." + (if (<= level svn-status-debug-level) + (apply 'message args))) + +(defun svn-status-flatten-list (list) + "Flatten any lists within ARGS, so that there are no sublists." + (loop for item in list + if (listp item) nconc (svn-status-flatten-list item) + else collect item)) + +(defun svn-status-window-line-position (w) + "Return the window line at point for window W, or nil if W is nil." + (svn-status-message 3 "About to count lines; selected window is %s" (selected-window)) + (and w (count-lines (window-start w) (point)))) + +;;;###autoload +(defun svn-checkout (repos-url path) + "Run svn checkout REPOS-URL PATH." + (interactive (list (read-string "Checkout from repository Url: ") + (expand-file-name + (svn-read-directory-name "Checkout to directory: ")))) + (svn-run t t 'checkout "checkout" repos-url (expand-file-name path))) + +;;;###autoload (defalias 'svn-examine 'svn-status) +(defalias 'svn-examine 'svn-status) + +;;;###autoload +(defun svn-version-controlled-dir-p (dir) + "Return t if DIR is part of a Subversion workarea." + (= 0 (call-process svn-status-svn-executable nil nil nil "info" dir))) + +;;;###autoload +(defun svn-status (dir &optional arg) + "Examine the status of Subversion working copy in directory DIR. +If ARG is -, allow editing of the parameters. One could add -N to +run svn status non recursively to make it faster. +For every other non nil ARG pass the -u argument to `svn status', which +asks svn to connect to the repository and check to see if there are updates +there. + +If DIR is not an SVN working copy, examine if there is CVS and run +`cvs-examine'. Otherwise ask if to run `dired'." + (interactive (list (expand-file-name + (svn-read-directory-name "SVN status directory: " + nil default-directory nil)) + current-prefix-arg)) + (cond + ((svn-version-controlled-dir-p (expand-file-name dir)) + (setq arg (svn-status-possibly-negate-meaning-of-arg arg 'svn-status)) + (svn-status-1 dir arg)) + ((and (file-directory-p (concat (file-name-as-directory dir) "CVS")) + (fboundp 'cvs-examine)) + (cvs-examine dir nil)) + (t + (when (y-or-n-p + (format + (concat + "%s " + "is not Subversion controlled (missing %s " + "directory). " + "Run dired instead? ") + dir + (svn-wc-adm-dir-name))) + (dired dir))))) + +(defvar svn-status-display-new-status-buffer nil) +(defun svn-status-1 (dir &optional arg) + "Examine DIR. See `svn-status' for more information." + (unless (file-directory-p dir) + (error "%s is not a directory" dir)) + (setq dir (file-name-as-directory dir)) + (when svn-status-load-state-before-svn-status + (unless (string= dir (car svn-status-directory-history)) + (let ((default-directory dir)) ;otherwise svn-status-base-dir looks in the wrong place + (svn-status-load-state t)))) + (setq svn-status-directory-history (delete dir svn-status-directory-history)) + (add-to-list 'svn-status-directory-history dir) + (if (string= (buffer-name) svn-status-buffer-name) + (setq svn-status-display-new-status-buffer nil) + (setq svn-status-display-new-status-buffer t) + ;;(message "psvn: Saving initial window configuration") + (setq svn-status-initial-window-configuration + (current-window-configuration))) + (let* ((cur-buf (current-buffer)) + (status-buf (get-buffer-create svn-status-buffer-name)) + (proc-buf (get-buffer-create svn-process-buffer-name)) + (want-edit (eq arg '-)) + (status-option (if want-edit + (if svn-status-verbose "-v" "") + (if svn-status-verbose + (if arg "-uv" "-v") + (if arg "-u" ""))))) + (save-excursion + (set-buffer status-buf) + (buffer-disable-undo) + (setq default-directory dir) + (set-buffer proc-buf) + (setq default-directory dir + svn-status-remote (when arg t)) + (if want-edit + (let ((svn-status-edit-svn-command t)) + (svn-run t t 'status "status" svn-status-default-status-arguments status-option)) + (svn-run t t 'status "status" svn-status-default-status-arguments status-option))))) + +(defun svn-status-this-directory (arg) + "Run `svn-status' for the `default-directory'" + (interactive "P") + (svn-status default-directory arg)) + +(defun svn-status-use-history () + "Interactively select a different directory from `svn-status-directory-history'." + (interactive) + (let* ((in-status-buffer (eq major-mode 'svn-status-mode)) + (hist (if in-status-buffer (cdr svn-status-directory-history) svn-status-directory-history)) + (dir (funcall svn-status-completing-read-function "svn-status on directory: " hist)) + (svn-status-buffer (get-buffer svn-status-buffer-name)) + (svn-buffer-available (and svn-status-buffer + (with-current-buffer svn-status-buffer-name (string= default-directory dir))))) + (if (file-directory-p dir) + (if svn-buffer-available + (svn-status-switch-to-status-buffer) + (unless svn-status-refresh-info + (setq svn-status-refresh-info 'once)) + (svn-status dir)) + (error "%s is not a directory" dir)))) + +(defun svn-had-user-input-since-asynch-run () + (not (equal (recent-keys) svn-pre-run-asynch-recent-keys))) + +(defun svn-expand-filename-for-remote-access (file-name) + "Convert the given local part of a filename to a full file name to allow accessing remote files" + ;; when running svn on a remote host: expand local file names to get full names to access the file on the remote host via emacs + (if (and (fboundp 'file-remote-p) (file-remote-p default-directory)) + (concat (file-remote-p default-directory) file-name) + file-name)) + +(defun svn-local-filename-for-remote-access (file-name) + "Convert a full file name to a local file name that can be used for a local svn invocation." + (if (and (fboundp 'file-remote-p) (file-remote-p file-name)) + (tramp-file-name-localname (tramp-dissect-file-name file-name)) + file-name)) + +(defun svn-process-environment () + "Construct the environment for the svn process. +It is a combination of `svn-status-svn-environment-var-list' and +the usual `process-environment'." + ;; If there are duplicate elements in `process-environment', then GNU + ;; Emacs 21.4 guarantees that the first one wins; but GNU Emacs 20.7 + ;; and XEmacs 21.4.17 don't document what happens. We'll just remove + ;; any duplicates ourselves, then. This also gives us an opportunity + ;; to handle the "VARIABLE" syntax that none of them supports. + (loop with found = '() + for elt in (append svn-status-svn-environment-var-list + process-environment) + for has-value = (string-match "=" elt) + for name = (substring elt 0 has-value) + unless (member name found) + do (push name found) + and when has-value + collect elt)) + +(defun svn-run (run-asynchron clear-process-buffer cmdtype &rest arglist) + "Run svn with arguments ARGLIST. + +If RUN-ASYNCHRON is t then run svn asynchronously. + +If CLEAR-PROCESS-BUFFER is t then erase the contents of the +`svn-process-buffer-name' buffer before commencing. + +CMDTYPE is a symbol such as 'mv, 'revert, or 'add, representing the +command to run. + +ARGLIST is a list of arguments \(which must include the command name, +for example: '(\"revert\" \"file1\"\) +ARGLIST is flattened and any every nil value is discarded. + +If the variable `svn-status-edit-svn-command' is non-nil then the user +can edit ARGLIST before running svn. + +The hook svn-pre-run-hook allows to monitor/modify the ARGLIST." + (setq arglist (svn-status-flatten-list arglist)) + (if (eq (process-status "svn") nil) + (progn + (when svn-status-edit-svn-command + (setq arglist (append + (list (car arglist)) + (split-string + (read-from-minibuffer + (format "svn %s flags: " (car arglist)) + (mapconcat 'identity (cdr arglist) " "))))) + (when (eq svn-status-edit-svn-command t) + (svn-status-toggle-edit-cmd-flag t)) + (message "svn-run %s: %S" cmdtype arglist)) + (run-hooks 'svn-pre-run-hook) + (unless (eq mode-line-process 'svn-status-mode-line-process) + (setq svn-pre-run-mode-line-process mode-line-process) + (setq mode-line-process 'svn-status-mode-line-process)) + (setq svn-status-pre-run-svn-buffer (current-buffer)) + (let* ((pre-run-buffer-default-directory default-directory) + (proc-buf (get-buffer-create svn-process-buffer-name)) + (svn-exe svn-status-svn-executable) + (svn-proc)) + (when (listp (car arglist)) + (setq arglist (car arglist))) + (save-excursion + (set-buffer proc-buf) + (setq default-directory pre-run-buffer-default-directory) + (setq buffer-read-only nil) + (buffer-disable-undo) + (fundamental-mode) + (if clear-process-buffer + (delete-region (point-min) (point-max)) + (goto-char (point-max))) + (setq svn-process-cmd cmdtype) + (setq svn-status-last-commit-author nil) + (setq svn-status-mode-line-process-status (format " running %s" cmdtype)) + (svn-status-update-mode-line) + (save-excursion (sit-for 0.1)) + (ring-insert svn-last-cmd-ring (list (current-time-string) arglist default-directory svn-arg-file-content)) + (setq svn-arg-file-content nil) + (setq svn-process-handle-error-msg nil) + (if run-asynchron + (progn + ;;(message "running asynchron: %s %S" svn-exe arglist) + (setq svn-pre-run-asynch-recent-keys (recent-keys)) + (let ((process-environment (svn-process-environment)) + (process-connection-type nil)) + ;; Communicate with the subprocess via pipes rather + ;; than via a pseudoterminal, so that if the svn+ssh + ;; scheme is being used, SSH will not ask for a + ;; passphrase via stdio; psvn.el is currently unable + ;; to answer such prompts. Instead, SSH will run + ;; x11-ssh-askpass if possible. If Emacs is being + ;; run on a TTY without $DISPLAY, this will fail; in + ;; such cases, the user should start ssh-agent and + ;; then run ssh-add explicitly. + (setq svn-proc (apply svn-start-process-function "svn" proc-buf svn-exe arglist))) + (when svn-status-svn-process-coding-system + (set-process-coding-system svn-proc svn-status-svn-process-coding-system + svn-status-svn-process-coding-system)) + (set-process-sentinel svn-proc 'svn-process-sentinel) + (when svn-status-track-user-input + (set-process-filter svn-proc 'svn-process-filter))) + ;;(message "running synchron: %s %S" svn-exe arglist) + (let ((process-environment (svn-process-environment))) + ;; `call-process' ignores `process-connection-type' and + ;; never opens a pseudoterminal. + (apply svn-call-process-function svn-exe nil proc-buf nil arglist)) + (setq svn-status-last-output-buffer-name svn-process-buffer-name) + (run-hooks 'svn-post-process-svn-output-hook) + (setq svn-status-mode-line-process-status "") + (svn-status-update-mode-line) + (when svn-pre-run-mode-line-process + (setq mode-line-process svn-pre-run-mode-line-process) + (setq svn-pre-run-mode-line-process nil)))))) + (error "You can only run one svn process at once!"))) + +(defun svn-process-sentinel-fixup-path-seperators () + "Convert all path separators to UNIX style. +\(This is a no-op unless `system-type' is windows-nt\)" + (when (eq system-type 'windows-nt) + (save-excursion + (goto-char (point-min)) + (while (search-forward "\\" nil t) + (replace-match "/"))))) + +(defun svn-process-sentinel (process event) + "Called after a svn process has finished." + ;;(princ (format "Process: %s had the event `%s'" process event))) + (let ((act-buf (current-buffer))) + (when svn-pre-run-mode-line-process + (with-current-buffer svn-status-pre-run-svn-buffer + (setq mode-line-process svn-pre-run-mode-line-process)) + (setq svn-pre-run-mode-line-process nil)) + (set-buffer (process-buffer process)) + (setq svn-status-mode-line-process-status "") + (svn-status-update-mode-line) + (cond ((string= event "finished\n") + (run-hooks 'svn-post-process-svn-output-hook) + (cond ((eq svn-process-cmd 'status) + ;;(message "svn status finished") + (svn-process-sentinel-fixup-path-seperators) + (svn-parse-status-result) + (svn-status-apply-elide-list) + (when svn-status-update-previous-process-output + (set-buffer (process-buffer process)) + (delete-region (point-min) (point-max)) + (insert "Output from svn command:\n") + (insert svn-status-update-previous-process-output) + (goto-char (point-min)) + (setq svn-status-update-previous-process-output nil)) + (when svn-status-update-list + ;; (message "Using svn-status-update-list: %S" svn-status-update-list) + (save-excursion + (svn-status-update-with-command-list svn-status-update-list)) + (setq svn-status-update-list nil)) + (when svn-status-display-new-status-buffer + (set-window-configuration svn-status-initial-window-configuration) + (if (svn-had-user-input-since-asynch-run) + (message "svn status finished") + (switch-to-buffer svn-status-buffer-name)))) + ((eq svn-process-cmd 'log) + (svn-status-show-process-output 'log t) + (pop-to-buffer svn-status-last-output-buffer-name) + (svn-log-view-mode) + (forward-line 2) + (unless (looking-at "Changed paths:") + (forward-line 1)) + (font-lock-fontify-buffer) + (message "svn log finished")) + ((eq svn-process-cmd 'info) + (svn-status-show-process-output 'info t) + (message "svn info finished")) + ((eq svn-process-cmd 'ls) + (svn-status-show-process-output 'info t) + (message "svn ls finished")) + ((eq svn-process-cmd 'diff) + (svn-status-activate-diff-mode) + (message "svn diff finished")) + ((eq svn-process-cmd 'parse-info) + (svn-status-parse-info-result)) + ((eq svn-process-cmd 'blame) + (svn-status-show-process-output 'blame t) + (when svn-status-pre-run-svn-buffer + (with-current-buffer svn-status-pre-run-svn-buffer + (unless (eq major-mode 'svn-status-mode) + (let ((src-line-number (svn-line-number-at-pos))) + (pop-to-buffer (get-buffer svn-status-last-output-buffer-name)) + (goto-line src-line-number))))) + (with-current-buffer (get-buffer svn-status-last-output-buffer-name) + (svn-status-activate-blame-mode)) + (message "svn blame finished")) + ((eq svn-process-cmd 'commit) + (svn-process-sentinel-fixup-path-seperators) + (svn-status-remove-temp-file-maybe) + (when (member 'commit svn-status-unmark-files-after-list) + (svn-status-unset-all-usermarks)) + (svn-status-update-with-command-list (svn-status-parse-commit-output)) + (svn-revert-some-buffers) + (run-hooks 'svn-log-edit-done-hook) + (setq svn-status-files-to-commit nil + svn-status-recursive-commit nil) + (if (null svn-status-commit-rev-number) + (message "No revision to commit.") + (message "svn: Committed revision %s." svn-status-commit-rev-number))) + ((eq svn-process-cmd 'update) + (svn-status-show-process-output 'update t) + (setq svn-status-update-list (svn-status-parse-update-output)) + (svn-revert-some-buffers) + (svn-status-update) + (if (car svn-status-update-rev-number) + (message "svn: Updated to revision %s." (cadr svn-status-update-rev-number)) + (message "svn: At revision %s." (cadr svn-status-update-rev-number)))) + ((eq svn-process-cmd 'add) + (svn-status-update-with-command-list (svn-status-parse-ar-output)) + (message "svn add finished")) + ((eq svn-process-cmd 'lock) + (svn-status-update) + (message "svn lock finished")) + ((eq svn-process-cmd 'unlock) + (svn-status-update) + (message "svn unlock finished")) + ((eq svn-process-cmd 'mkdir) + (svn-status-update) + (message "svn mkdir finished")) + ((eq svn-process-cmd 'revert) + (when (member 'revert svn-status-unmark-files-after-list) + (svn-status-unset-all-usermarks)) + (svn-revert-some-buffers) + (svn-status-update) + (message "svn revert finished")) + ((eq svn-process-cmd 'resolved) + (svn-status-update) + (message "svn resolved finished")) + ((eq svn-process-cmd 'rm) + (svn-status-update-with-command-list (svn-status-parse-ar-output)) + (message "svn rm finished")) + ((eq svn-process-cmd 'cleanup) + (message "svn cleanup finished")) + ((eq svn-process-cmd 'proplist) + (svn-status-show-process-output 'proplist t) + (message "svn proplist finished")) + ((eq svn-process-cmd 'checkout) + (svn-status default-directory)) + ((eq svn-process-cmd 'proplist-parse) + (svn-status-property-parse-property-names)) + ((eq svn-process-cmd 'propset) + (svn-status-remove-temp-file-maybe) + (if (member svn-status-propedit-property-name '("svn:keywords")) + (svn-status-update-with-command-list (svn-status-parse-property-output)) + (svn-status-update))) + ((eq svn-process-cmd 'propdel) + (svn-status-update)))) + ((string= event "killed\n") + (message "svn process killed")) + ((string-match "exited abnormally" event) + (while (accept-process-output process 0 100)) + ;; find last error message and show it. + (goto-char (point-max)) + (if (re-search-backward "^svn: " nil t) + (let ((error-strings) + (beginning-of-buffer)) + (while (and (looking-at "^svn: ") (not beginning-of-buffer)) + (setq error-strings (append error-strings (list (buffer-substring-no-properties (+ 5 (svn-point-at-bol)) (svn-point-at-eol))))) + (setq beginning-of-buffer (bobp)) + (forward-line -1)) + (svn-process-handle-error (mapconcat 'identity (reverse error-strings) "\n"))) + (message "svn failed: %s" event))) + (t + (message "svn process had unknown event: %s" event)) + (svn-status-show-process-output nil t)))) + +(defvar svn-process-handle-error-msg nil) +(defvar svn-handle-error-function nil + "A function that will be called with an error string received from the svn client. +When this function resets `svn-process-handle-error-msg' to nil, the default error handling +(just show the error message) is not executed.") +(defun svn-process-handle-error (error-msg) + (setq svn-process-handle-error-msg error-msg) + (when (functionp svn-handle-error-function) + (funcall svn-handle-error-function error-msg)) + (when svn-process-handle-error-msg + (electric-helpify 'svn-process-help-with-error-msg))) + +(defun svn-process-help-with-error-msg () + (interactive) + (let ((help-msg (cadr (assoc svn-process-handle-error-msg + '(("Cannot non-recursively commit a directory deletion" + "Please unmark all files and position point at the directory you would like to remove.\nThen run commit again.")))))) + (if help-msg + (save-excursion + (with-output-to-temp-buffer (svn-help-buffer) + (princ (format "svn failed: %s\n\n%s" svn-process-handle-error-msg help-msg)))) + (message "svn failed:\n%s" svn-process-handle-error-msg)))) + + +(defun svn-process-filter (process str) + "Track the svn process output and ask user questions in the minibuffer when appropriate." + (save-window-excursion + (set-buffer svn-process-buffer-name) + ;;(message "svn-process-filter: %s" str) + (goto-char (point-max)) + (insert str) + (save-excursion + (goto-char (svn-point-at-bol)) + (when (looking-at "Password for '\\(.*\\)': ") + ;(svn-status-show-process-buffer) + (let ((passwd (read-passwd + (format "Enter svn password for %s: " (match-string 1))))) + (svn-process-send-string-and-newline passwd t))) + (when (looking-at "Username: ") + (let ((user-name (with-local-quit (read-string "Username for svn operation: ")))) + (svn-process-send-string-and-newline user-name))) + (when (looking-at "(R)eject, accept (t)emporarily or accept (p)ermanently") + (svn-status-show-process-buffer) + (let ((answer (with-local-quit (read-string "(R)eject, accept (t)emporarily or accept (p)ermanently? ")))) + (svn-process-send-string (substring answer 0 1))))))) + +(defun svn-revert-some-buffers (&optional tree) + "Reverts all buffers visiting a file in TREE that aren't modified. +To be run after a commit, an update or a merge." + (interactive) + (let ((tree (or (svn-status-base-dir) tree))) + (dolist (buffer (buffer-list)) + (with-current-buffer buffer + (when (not (buffer-modified-p)) + (let ((file (buffer-file-name))) + (when file + (let ((root (svn-status-base-dir (file-name-directory file))) + (point-pos (point))) + (when (and root + (string= root tree) + ;; buffer is modified and in the tree TREE. + svn-status-auto-revert-buffers) + (when svn-status-fancy-file-state-in-modeline + (svn-status-update-modeline)) + ;; (message "svn-revert-some-buffers: %s %s" (buffer-file-name) (verify-visited-file-modtime (current-buffer))) + ;; Keep the buffer if the file doesn't exist + (when (and (file-exists-p file) (not (verify-visited-file-modtime (current-buffer)))) + (revert-buffer t t) + (goto-char point-pos))))))))))) + +(defun svn-parse-rev-num (str) + (if (and str (stringp str) + (save-match-data (string-match "^[0-9]+" str))) + (string-to-number str) + -1)) + +(defsubst svn-status-make-ui-status () + "Make a ui-status structure for a file in a svn working copy. +The initial values in the structure returned by this function +are good for a file or directory that the user hasn't seen before. + +The ui-status structure keeps track of how the file or directory +should be displayed in svn-status mode. Updating the svn-status +buffer from the working copy preserves the ui-status if possible. +User commands modify this structure; each file or directory must +thus have its own copy. + +Currently, the ui-status is a list (USER-MARK USER-ELIDE). +USER-MARK is non-nil iff the user has marked the file or directory, + typically with `svn-status-set-user-mark'. To read USER-MARK, + call `svn-status-line-info->has-usermark'. +USER-ELIDE is non-nil iff the user has elided the file or directory + from the svn-status buffer, typically with `svn-status-toggle-elide'. + To read USER-ELIDE, call `svn-status-line-info->user-elide'. + +Call `svn-status-line-info->ui-status' to access the whole ui-status +structure." + (list nil nil)) + +(defun svn-status-make-dummy-dirs (dir-list old-ui-information) + "Calculate additionally necessary directories that were not shown in the output +of 'svn status'" + ;; (message "svn-status-make-dummy-dirs %S" dir-list) + (let ((candidate) + (base-dir)) + (dolist (dir dir-list) + (setq base-dir (file-name-directory dir)) + (while base-dir + ;;(message "dir: %S dir-list: %S, base-dir: %S" dir dir-list base-dir) + (setq candidate (replace-regexp-in-string "/+$" "" base-dir)) + (setq base-dir (file-name-directory candidate)) + ;; (message "dir: %S, candidate: %S" dir candidate) + (add-to-list 'dir-list candidate)))) + ;; (message "svn-status-make-dummy-dirs %S" dir-list) + (append (mapcar (lambda (dir) + (svn-status-make-line-info + dir + (gethash dir old-ui-information))) + dir-list) + svn-status-info)) + +(defun svn-status-make-line-info (&optional + path + ui + file-mark prop-mark + local-rev last-change-rev + author + update-mark + locked-mark + with-history-mark + switched-mark + locked-repo-mark + psvn-extra-info) + "Create a new line-info from the given arguments +Anything left nil gets a sensible default. +nb: LOCKED-MARK refers to the kind of locks you get after an error, + LOCKED-REPO-MARK is the kind managed with `svn lock'" + (list (or ui (svn-status-make-ui-status)) + (or file-mark ? ) + (or prop-mark ? ) + (or path "") + (or local-rev ? ) + (or last-change-rev ? ) + (or author "") + update-mark + locked-mark + with-history-mark + switched-mark + locked-repo-mark + psvn-extra-info)) + +(defvar svn-user-names-including-blanks nil "A list of svn user names that include blanks. +To add support for the names \"feng shui\" and \"mister blank\", place the following in your .emacs: + (setq svn-user-names-including-blanks '(\"feng shui\" \"mister blank\")) + (add-hook 'svn-pre-parse-status-hook 'svn-status-parse-fixup-user-names-including-blanks) +") +;;(setq svn-user-names-including-blanks '("feng shui" "mister blank")) +;;(add-hook 'svn-pre-parse-status-hook 'svn-status-parse-fixup-user-names-including-blanks) + +(defun svn-status-parse-fixup-user-names-including-blanks () + "Helper function to allow user names that include blanks. +Add this function to the `svn-pre-parse-status-hook'. The variable +`svn-user-names-including-blanks' must be configured to hold all user names that contain +blanks. This function replaces the blanks with '-' to allow further processing with +the usual parsing functionality in `svn-parse-status-result'." + (when svn-user-names-including-blanks + (goto-char (point-min)) + (let ((search-string (concat " \\(" (mapconcat 'concat svn-user-names-including-blanks "\\|") "\\) "))) + (save-match-data + (save-excursion + (while (re-search-forward search-string (point-max) t) + (replace-match (replace-regexp-in-string " " "-" (match-string 1)) nil nil nil 1))))))) + +(defun svn-parse-status-result () + "Parse the `svn-process-buffer-name' buffer. +The results are used to build the `svn-status-info' variable." + (setq svn-status-head-revision nil) + (save-excursion + (let ((old-ui-information (svn-status-ui-information-hash-table)) + (svn-marks) + (svn-file-mark) + (svn-property-mark) + (svn-wc-locked-mark) + (svn-repo-locked-mark) + (svn-with-history-mark) + (svn-switched-mark) + (svn-update-mark) + (local-rev) + (last-change-rev) + (author) + (path) + (dir) + (revision-width svn-status-default-revision-width) + (author-width svn-status-default-author-width) + (svn-marks-length (if svn-status-verbose + (if svn-status-remote + 8 6) + (if svn-status-remote + ;; not verbose + 8 7))) + (dir-set '(".")) + (externals-map (make-hash-table :test 'equal)) + (skip-double-external-dir-entry-name nil)) + (set-buffer svn-process-buffer-name) + (setq svn-status-info nil) + (run-hooks 'svn-pre-parse-status-hook) + (goto-char (point-min)) + (while (< (point) (point-max)) + (cond + ((= (svn-point-at-eol) (svn-point-at-bol)) ;skip blank lines + nil) + ((looking-at "Status against revision:[ ]+\\([0-9]+\\)") + ;; the above message appears for the main listing plus once for each svn:externals entry + (unless svn-status-head-revision + (setq svn-status-head-revision (match-string 1)))) + ((looking-at "Performing status on external item at '\\(.*\\)'") + ;; The *next* line has info about the directory named in svn:externals + ;; [ie the directory in (match-string 1)] + ;; we should parse it, and merge the info with what we have already know + ;; but for now just ignore the line completely + ; (forward-line) + ;; Actually, this seems to not always be the case + ;; I have an example where we are in an svn:external which + ;; is itself inside a svn:external, this need not be true: + ;; the next line is not 'X dir' but just 'dir', so we + ;; actually need to parse that line, or the results will + ;; not contain dir! + ;; so we should merge lines 'X dir' with ' dir', but for now + ;; we just leave both in the results + + ;; My attempt to merge the lines uses skip-double-external-dir-entry-name + ;; and externals-map + (setq skip-double-external-dir-entry-name (svn-match-string-no-properties 1)) + ;; (message "Going to skip %s" skip-double-external-dir-entry-name) + nil) + ((looking-at "--- Changelist") ; skip svn changelist header lines + ;; See: http://svn.collab.net/repos/svn/trunk/notes/changelist-design.txt + nil) + (t + (setq svn-marks (buffer-substring (point) (+ (point) svn-marks-length)) + svn-file-mark (elt svn-marks 0) ; 1st column - M,A,C,D,G,? etc + svn-property-mark (elt svn-marks 1) ; 2nd column - M,C (properties) + svn-wc-locked-mark (elt svn-marks 2) ; 3rd column - L or blank + svn-with-history-mark (elt svn-marks 3) ; 4th column - + or blank + svn-switched-mark (elt svn-marks 4) ; 5th column - S,X or blank + svn-repo-locked-mark (elt svn-marks 5)) ; 6th column - K,O,T,B or blank + (when svn-status-remote + (setq svn-update-mark (elt svn-marks 7))) ; 8th column - * or blank + (when (eq svn-property-mark ?\ ) (setq svn-property-mark nil)) + (when (eq svn-wc-locked-mark ?\ ) (setq svn-wc-locked-mark nil)) + (when (eq svn-with-history-mark ?\ ) (setq svn-with-history-mark nil)) + (when (eq svn-switched-mark ?\ ) (setq svn-switched-mark nil)) + (when (eq svn-update-mark ?\ ) (setq svn-update-mark nil)) + (when (eq svn-repo-locked-mark ?\ ) (setq svn-repo-locked-mark nil)) + (forward-char svn-marks-length) + (skip-chars-forward " ") + ;; (message "after marks: '%s'" (buffer-substring (point) (line-end-position))) + (cond + ((looking-at "\\([-?]\\|[0-9]+\\) +\\([-?]\\|[0-9]+\\) +\\([^ ]+\\) *\\(.+\\)$") + (setq local-rev (svn-parse-rev-num (match-string 1)) + last-change-rev (svn-parse-rev-num (match-string 2)) + author (match-string 3) + path (match-string 4))) + ((looking-at "\\([-?]\\|[0-9]+\\) +\\([^ ]+\\)$") + (setq local-rev (svn-parse-rev-num (match-string 1)) + last-change-rev -1 + author "?" + path (match-string 2))) + ((looking-at "\\(.*\\)") + (setq path (match-string 1) + local-rev -1 + last-change-rev -1 + author (if (eq svn-file-mark ?X) "" "?"))) ;clear author of svn:externals dirs + (t + (error "Unknown status line format"))) + (unless path (setq path ".")) + (setq dir (file-name-directory path)) + (if (and (not svn-status-verbose) dir) + (let ((dirname (directory-file-name dir))) + (if (not (member dirname dir-set)) + (setq dir-set (cons dirname dir-set))))) + (if (and skip-double-external-dir-entry-name (string= skip-double-external-dir-entry-name path)) + ;; merge this entry to a previous saved one + (let ((info (gethash path externals-map))) + ;; (message "skip-double-external-dir-entry-name: %s - path: %s" skip-double-external-dir-entry-name path) + (if info + (progn + (svn-status-line-info->set-localrev info local-rev) + (svn-status-line-info->set-lastchangerev info last-change-rev) + (svn-status-line-info->set-author info author) + (svn-status-message 3 "merging entry for %s to %s" path info) + (setq skip-double-external-dir-entry-name nil)) + (message "psvn: %s not handled correct, please report this case." path))) + (setq svn-status-info + (cons (svn-status-make-line-info path + (gethash path old-ui-information) + svn-file-mark + svn-property-mark + local-rev + last-change-rev + author + svn-update-mark + svn-wc-locked-mark + svn-with-history-mark + svn-switched-mark + svn-repo-locked-mark + nil) ;;psvn-extra-info + svn-status-info))) + (when (eq svn-file-mark ?X) + (svn-puthash (match-string 1) (car svn-status-info) externals-map) + (svn-status-message 3 "found external: %s %S" (match-string 1) (car svn-status-info))) + (setq revision-width (max revision-width + (length (number-to-string local-rev)) + (length (number-to-string last-change-rev)))) + (setq author-width (max author-width (length author))))) + (forward-line 1)) + (unless svn-status-verbose + (setq svn-status-info (svn-status-make-dummy-dirs dir-set + old-ui-information))) + (setq svn-status-default-column + (+ 6 revision-width revision-width author-width + (if svn-status-short-mod-flag-p 3 0))) + (setq svn-status-line-format (format " %%c%%c%%c %%%ds %%%ds %%-%ds" + revision-width + revision-width + author-width)) + (setq svn-status-info (nreverse svn-status-info)) + (when svn-status-sort-status-buffer + (setq svn-status-info (sort svn-status-info 'svn-status-sort-predicate)))))) + +;;(string-lessp "." "%") => nil +;;(svn-status-sort-predicate '(t t t ".") '(t t t "%")) => t +(defun svn-status-sort-predicate (a b) + "Return t if A should appear before B in the `svn-status-buffer-name' buffer. +A and B must be line-info's." + (string-lessp (concat (svn-status-line-info->full-path a) "/") + (concat (svn-status-line-info->full-path b) "/"))) + +(defun svn-status-remove-temp-file-maybe () + "Remove any (no longer required) temporary files created by psvn.el." + (when svn-status-temp-file-to-remove + (when (file-exists-p svn-status-temp-file-to-remove) + (delete-file svn-status-temp-file-to-remove)) + (when (file-exists-p svn-status-temp-arg-file) + (delete-file svn-status-temp-arg-file)) + (setq svn-status-temp-file-to-remove nil))) + +(defun svn-status-remove-control-M () + "Remove ^M at end of line in the whole buffer." + (interactive) + (let ((buffer-read-only nil)) + (save-match-data + (save-excursion + (goto-char (point-min)) + (while (re-search-forward "\r$" (point-max) t) + (replace-match "" nil nil)))))) + +(defun svn-fixup-tramp-exit () + "Helper function to handle tramp connections stopping with an exit output." + (goto-char (point-max)) + (when (eq (svn-point-at-bol) (svn-point-at-eol)) + (forward-line -1)) + (beginning-of-line) + (when (looking-at "exit") + (delete-region (point) (svn-point-at-eol)))) + +(defun svn-fixup-tramp-output-maybe () + "Fixup leftover output when running via tramp" + (when (fboundp 'file-remote-p) + (when (file-remote-p default-directory) + (svn-fixup-tramp-exit)))) + +(condition-case nil + ;;(easy-menu-add-item nil '("tools") ["SVN Status" svn-status t] "PCL-CVS") + (easy-menu-add-item nil '("tools") ["SVN Status" svn-status t]) + (error (message "psvn: could not install menu"))) + +(defvar svn-status-mode-map () "Keymap used in `svn-status-mode' buffers.") +(put 'svn-status-mode-map 'risky-local-variable t) ;for Emacs 20.7 +(defvar svn-status-mode-mark-map () + "Subkeymap used in `svn-status-mode' for mark commands.") +(put 'svn-status-mode-mark-map 'risky-local-variable t) ;for Emacs 20.7 +(defvar svn-status-mode-property-map () + "Subkeymap used in `svn-status-mode' for property commands.") +(put 'svn-status-mode-property-map 'risky-local-variable t) ;for Emacs 20.7 +(defvar svn-status-mode-options-map () + "Subkeymap used in `svn-status-mode' for option commands.") +(put 'svn-status-mode-options-map 'risky-local-variable t) ;for Emacs 20.7 +(defvar svn-status-mode-trac-map () + "Subkeymap used in `svn-status-mode' for trac issue tracker commands.") +(put 'svn-status-mode-trac-map 'risky-local-variable t) ;for Emacs 20.7 +(defvar svn-status-mode-extension-map () + "Subkeymap used in `svn-status-mode' for some seldom used commands.") +(put 'svn-status-mode-extension-map 'risky-local-variable t) ;for Emacs 20.7 +(defvar svn-status-mode-branch-map () + "Subkeymap used in `svn-status-mode' for branching commands.") +(put 'svn-status-mode-extension-map 'risky-local-variable t) ;for Emacs 20.7 +(defvar svn-status-mode-search-map () + "Subkeymap used in `svn-status-mode' for search commands.") +(put 'svn-status-mode-search-map 'risky-local-variable t) ;for Emacs 20.7 + +(when (not svn-status-mode-map) + (setq svn-status-mode-map (make-sparse-keymap)) + (suppress-keymap svn-status-mode-map) + ;; Don't use (kbd ""); it's unreachable with GNU Emacs 21.3 on a TTY. + (define-key svn-status-mode-map (kbd "RET") 'svn-status-find-file-or-examine-directory) + (define-key svn-status-mode-map (kbd "") 'svn-status-mouse-find-file-or-examine-directory) + (define-key svn-status-mode-map (kbd "^") 'svn-status-examine-parent) + (define-key svn-status-mode-map (kbd "s") 'svn-status-show-process-buffer) + (define-key svn-status-mode-map (kbd "h") 'svn-status-pop-to-partner-buffer) + (define-key svn-status-mode-map (kbd "f") 'svn-status-find-files) + (define-key svn-status-mode-map (kbd "o") 'svn-status-find-file-other-window) + (define-key svn-status-mode-map (kbd "C-o") 'svn-status-find-file-other-window-noselect) + (define-key svn-status-mode-map (kbd "v") 'svn-status-view-file-other-window) + (define-key svn-status-mode-map (kbd "e") 'svn-status-toggle-edit-cmd-flag) + (define-key svn-status-mode-map (kbd "g") 'svn-status-update) + (define-key svn-status-mode-map (kbd "M-s") 'svn-status-update) ;; PCL-CVS compatibility + (define-key svn-status-mode-map (kbd "q") 'svn-status-bury-buffer) + (define-key svn-status-mode-map (kbd "x") 'svn-status-redraw-status-buffer) + (define-key svn-status-mode-map (kbd "H") 'svn-status-use-history) + (define-key svn-status-mode-map (kbd "m") 'svn-status-set-user-mark) + (define-key svn-status-mode-map (kbd "u") 'svn-status-unset-user-mark) + ;; This matches a binding of `dired-unmark-all-files' in `dired-mode-map' + ;; of both GNU Emacs and XEmacs. It seems unreachable with XEmacs on + ;; TTY, but if that's a problem then its Dired needs fixing too. + ;; Or you could just use "*!". + (define-key svn-status-mode-map "\M-\C-?" 'svn-status-unset-all-usermarks) + ;; The key that normally deletes characters backwards should here + ;; instead unmark files backwards. In GNU Emacs, that would be (kbd + ;; "DEL") aka [?\177], but XEmacs treats those as [(delete)] and + ;; would bind a key that normally deletes forwards. [(backspace)] + ;; is unreachable with GNU Emacs on a tty. Try to recognize the + ;; dialect and act accordingly. + ;; + ;; XEmacs has a `delete-forward-p' function that checks the + ;; `delete-key-deletes-forward' option. We don't use those, for two + ;; reasons: psvn.el may be loaded before user customizations, and + ;; XEmacs allows simultaneous connections to multiple devices with + ;; different keyboards. + (define-key svn-status-mode-map + (if (member (kbd "DEL") '([(delete)] [delete])) + [(backspace)] ; XEmacs + (kbd "DEL")) ; GNU Emacs + 'svn-status-unset-user-mark-backwards) + (define-key svn-status-mode-map (kbd "$") 'svn-status-toggle-elide) + (define-key svn-status-mode-map (kbd "w") 'svn-status-copy-current-line-info) + (define-key svn-status-mode-map (kbd ".") 'svn-status-goto-root-or-return) + (define-key svn-status-mode-map (kbd "I") 'svn-status-parse-info) + (define-key svn-status-mode-map (kbd "V") 'svn-status-svnversion) + (define-key svn-status-mode-map (kbd "?") 'svn-status-toggle-hide-unknown) + (define-key svn-status-mode-map (kbd "_") 'svn-status-toggle-hide-unmodified) + (define-key svn-status-mode-map (kbd "z") 'svn-status-toggle-hide-externals) + (define-key svn-status-mode-map (kbd "a") 'svn-status-add-file) + (define-key svn-status-mode-map (kbd "A") 'svn-status-add-file-recursively) + (define-key svn-status-mode-map (kbd "+") 'svn-status-make-directory) + (define-key svn-status-mode-map (kbd "R") 'svn-status-mv) + (define-key svn-status-mode-map (kbd "C") 'svn-status-cp) + (define-key svn-status-mode-map (kbd "D") 'svn-status-rm) + (define-key svn-status-mode-map (kbd "c") 'svn-status-commit) + (define-key svn-status-mode-map (kbd "M-c") 'svn-status-cleanup) + (define-key svn-status-mode-map (kbd "k") 'svn-status-lock) + (define-key svn-status-mode-map (kbd "K") 'svn-status-unlock) + (define-key svn-status-mode-map (kbd "U") 'svn-status-update-cmd) + (define-key svn-status-mode-map (kbd "M-u") 'svn-status-update-cmd) + (define-key svn-status-mode-map (kbd "r") 'svn-status-revert) + (define-key svn-status-mode-map (kbd "l") 'svn-status-show-svn-log) + (define-key svn-status-mode-map (kbd "i") 'svn-status-info) + (define-key svn-status-mode-map (kbd "b") 'svn-status-blame) + (define-key svn-status-mode-map (kbd "=") 'svn-status-show-svn-diff) + ;; [(control ?=)] is unreachable on TTY, but you can use "*u" instead. + ;; (Is the "u" mnemonic for something?) + (define-key svn-status-mode-map (kbd "C-=") 'svn-status-show-svn-diff-for-marked-files) + (define-key svn-status-mode-map (kbd "~") 'svn-status-get-specific-revision) + (define-key svn-status-mode-map (kbd "E") 'svn-status-ediff-with-revision) + + (define-key svn-status-mode-map (kbd "n") 'svn-status-next-line) + (define-key svn-status-mode-map (kbd "p") 'svn-status-previous-line) + (define-key svn-status-mode-map (kbd "") 'svn-status-next-line) + (define-key svn-status-mode-map (kbd "") 'svn-status-previous-line) + (define-key svn-status-mode-map (kbd "C-x C-j") 'svn-status-dired-jump) + (define-key svn-status-mode-map [down-mouse-3] 'svn-status-popup-menu)) + +(when (not svn-status-mode-mark-map) + (setq svn-status-mode-mark-map (make-sparse-keymap)) + (define-key svn-status-mode-map (kbd "*") svn-status-mode-mark-map) + (define-key svn-status-mode-mark-map (kbd "!") 'svn-status-unset-all-usermarks) + (define-key svn-status-mode-mark-map (kbd "?") 'svn-status-mark-unknown) + (define-key svn-status-mode-mark-map (kbd "A") 'svn-status-mark-added) + (define-key svn-status-mode-mark-map (kbd "M") 'svn-status-mark-modified) + (define-key svn-status-mode-mark-map (kbd "P") 'svn-status-mark-modified-properties) + (define-key svn-status-mode-mark-map (kbd "D") 'svn-status-mark-deleted) + (define-key svn-status-mode-mark-map (kbd "*") 'svn-status-mark-changed) + (define-key svn-status-mode-mark-map (kbd ".") 'svn-status-mark-by-file-ext) + (define-key svn-status-mode-mark-map (kbd "%") 'svn-status-mark-filename-regexp) + (define-key svn-status-mode-mark-map (kbd "s") 'svn-status-store-usermarks) + (define-key svn-status-mode-mark-map (kbd "l") 'svn-status-load-usermarks) + (define-key svn-status-mode-mark-map (kbd "u") 'svn-status-show-svn-diff-for-marked-files)) + +(when (not svn-status-mode-search-map) + (setq svn-status-mode-search-map (make-sparse-keymap)) + (define-key svn-status-mode-search-map (kbd "g") 'svn-status-grep-files) + (define-key svn-status-mode-search-map (kbd "s") 'svn-status-search-files) + (define-key svn-status-mode-map (kbd "S") svn-status-mode-search-map)) + +(when (not svn-status-mode-property-map) + (setq svn-status-mode-property-map (make-sparse-keymap)) + (define-key svn-status-mode-property-map (kbd "l") 'svn-status-property-list) + (define-key svn-status-mode-property-map (kbd "s") 'svn-status-property-set) + (define-key svn-status-mode-property-map (kbd "d") 'svn-status-property-delete) + (define-key svn-status-mode-property-map (kbd "e") 'svn-status-property-edit-one-entry) + (define-key svn-status-mode-property-map (kbd "i") 'svn-status-property-ignore-file) + (define-key svn-status-mode-property-map (kbd "I") 'svn-status-property-ignore-file-extension) + ;; XEmacs 21.4.15 on TTY (vt420) converts `C-i' to `TAB', + ;; which [(control ?i)] won't match. Handle it separately. + ;; On GNU Emacs, the following two forms bind the same key, + ;; reducing clutter in `where-is'. + (define-key svn-status-mode-property-map [(control ?i)] 'svn-status-property-edit-svn-ignore) + (define-key svn-status-mode-property-map (kbd "TAB") 'svn-status-property-edit-svn-ignore) + (define-key svn-status-mode-property-map (kbd "Xe") 'svn-status-property-edit-svn-externals) + (define-key svn-status-mode-property-map (kbd "k") 'svn-status-property-set-keyword-list) + (define-key svn-status-mode-property-map (kbd "Ki") 'svn-status-property-set-keyword-id) + (define-key svn-status-mode-property-map (kbd "Kd") 'svn-status-property-set-keyword-date) + (define-key svn-status-mode-property-map (kbd "y") 'svn-status-property-set-eol-style) + (define-key svn-status-mode-property-map (kbd "x") 'svn-status-property-set-executable) + (define-key svn-status-mode-property-map (kbd "m") 'svn-status-property-set-mime-type) + ;; TODO: Why is `svn-status-select-line' in `svn-status-mode-property-map'? + (define-key svn-status-mode-property-map (kbd "RET") 'svn-status-select-line) + (define-key svn-status-mode-map (kbd "P") svn-status-mode-property-map)) +(when (not svn-status-mode-extension-map) + (setq svn-status-mode-extension-map (make-sparse-keymap)) + (define-key svn-status-mode-extension-map (kbd "v") 'svn-status-resolved) + (define-key svn-status-mode-extension-map (kbd "X") 'svn-status-resolve-conflicts) + (define-key svn-status-mode-extension-map (kbd "e") 'svn-status-export) + (define-key svn-status-mode-map (kbd "X") svn-status-mode-extension-map)) +(when (not svn-status-mode-options-map) + (setq svn-status-mode-options-map (make-sparse-keymap)) + (define-key svn-status-mode-options-map (kbd "s") 'svn-status-save-state) + (define-key svn-status-mode-options-map (kbd "l") 'svn-status-load-state) + (define-key svn-status-mode-options-map (kbd "x") 'svn-status-toggle-sort-status-buffer) + (define-key svn-status-mode-options-map (kbd "v") 'svn-status-toggle-svn-verbose-flag) + (define-key svn-status-mode-options-map (kbd "f") 'svn-status-toggle-display-full-path) + (define-key svn-status-mode-options-map (kbd "t") 'svn-status-set-trac-project-root) + (define-key svn-status-mode-options-map (kbd "n") 'svn-status-set-module-name) + (define-key svn-status-mode-options-map (kbd "c") 'svn-status-set-changelog-style) + (define-key svn-status-mode-options-map (kbd "b") 'svn-status-set-branch-list) + (define-key svn-status-mode-map (kbd "O") svn-status-mode-options-map)) +(when (not svn-status-mode-trac-map) + (setq svn-status-mode-trac-map (make-sparse-keymap)) + (define-key svn-status-mode-trac-map (kbd "w") 'svn-trac-browse-wiki) + (define-key svn-status-mode-trac-map (kbd "t") 'svn-trac-browse-timeline) + (define-key svn-status-mode-trac-map (kbd "m") 'svn-trac-browse-roadmap) + (define-key svn-status-mode-trac-map (kbd "r") 'svn-trac-browse-report) + (define-key svn-status-mode-trac-map (kbd "s") 'svn-trac-browse-source) + (define-key svn-status-mode-trac-map (kbd "i") 'svn-trac-browse-ticket) + (define-key svn-status-mode-trac-map (kbd "c") 'svn-trac-browse-changeset) + (define-key svn-status-mode-map (kbd "T") svn-status-mode-trac-map)) +(when (not svn-status-mode-branch-map) + (setq svn-status-mode-branch-map (make-sparse-keymap)) + (define-key svn-status-mode-branch-map (kbd "d") 'svn-branch-diff) + (define-key svn-status-mode-map (kbd "B") svn-status-mode-branch-map)) + +(easy-menu-define svn-status-mode-menu svn-status-mode-map + "'svn-status-mode' menu" + '("SVN" + ["svn status" svn-status-update t] + ["svn update" svn-status-update-cmd t] + ["svn commit" svn-status-commit t] + ["svn log" svn-status-show-svn-log t] + ["svn info" svn-status-info t] + ["svn blame" svn-status-blame t] + ("Diff" + ["svn diff current file" svn-status-show-svn-diff t] + ["svn diff marked files" svn-status-show-svn-diff-for-marked-files t] + ["svn ediff current file" svn-status-ediff-with-revision t] + ["svn resolve conflicts" svn-status-resolve-conflicts] + ) + ("Search" + ["Grep marked files" svn-status-grep-files t] + ["Search marked files" svn-status-search-files t] + ) + ["svn cat ..." svn-status-get-specific-revision t] + ["svn add" svn-status-add-file t] + ["svn add recursively" svn-status-add-file-recursively t] + ["svn mkdir..." svn-status-make-directory t] + ["svn mv..." svn-status-mv t] + ["svn cp..." svn-status-cp t] + ["svn rm..." svn-status-rm t] + ["svn export..." svn-status-export t] + ["Up Directory" svn-status-examine-parent t] + ["Elide Directory" svn-status-toggle-elide t] + ["svn revert" svn-status-revert t] + ["svn resolved" svn-status-resolved t] + ["svn cleanup" svn-status-cleanup t] + ["svn lock" svn-status-lock t] + ["svn unlock" svn-status-unlock t] + ["Show Process Buffer" svn-status-show-process-buffer t] + ("Branch" + ["diff" svn-branch-diff t] + ["Set Branch list" svn-status-set-branch-list t] + ) + ("Property" + ["svn proplist" svn-status-property-list t] + ["Set Multiple Properties..." svn-status-property-set t] + ["Edit One Property..." svn-status-property-edit-one-entry t] + ["svn propdel..." svn-status-property-delete t] + "---" + ["svn:ignore File..." svn-status-property-ignore-file t] + ["svn:ignore File Extension..." svn-status-property-ignore-file-extension t] + ["Edit svn:ignore Property" svn-status-property-edit-svn-ignore t] + "---" + ["Edit svn:externals Property" svn-status-property-edit-svn-externals t] + "---" + ["Edit svn:keywords List" svn-status-property-set-keyword-list t] + ["Add/Remove Id to/from svn:keywords" svn-status-property-set-keyword-id t] + ["Add/Remove Date to/from svn:keywords" svn-status-property-set-keyword-date t] + "---" + ["Select svn:eol-style" svn-status-property-set-eol-style t] + ["Set svn:executable" svn-status-property-set-executable t] + ["Set svn:mime-type" svn-status-property-set-mime-type t] + ) + ("Options" + ["Save Options" svn-status-save-state t] + ["Load Options" svn-status-load-state t] + ["Set Trac project root" svn-status-set-trac-project-root t] + ["Set Short module name" svn-status-set-module-name t] + ["Set Changelog style" svn-status-set-changelog-style t] + ["Set Branch list" svn-status-set-branch-list t] + ["Sort the *svn-status* buffer" svn-status-toggle-sort-status-buffer + :style toggle :selected svn-status-sort-status-buffer] + ["Use -v for svn status calls" svn-status-toggle-svn-verbose-flag + :style toggle :selected svn-status-verbose] + ["Display full path names" svn-status-toggle-display-full-path + :style toggle :selected svn-status-display-full-path] + ) + ("Trac" + ["Browse wiki" svn-trac-browse-wiki t] + ["Browse timeline" svn-trac-browse-timeline t] + ["Browse roadmap" svn-trac-browse-roadmap t] + ["Browse source" svn-trac-browse-source t] + ["Browse report" svn-trac-browse-report t] + ["Browse ticket" svn-trac-browse-ticket t] + ["Browse changeset" svn-trac-browse-changeset t] + ["Set Trac project root" svn-status-set-trac-project-root t] + ) + "---" + ["Edit Next SVN Cmd Line" svn-status-toggle-edit-cmd-flag t] + ["Work Directory History..." svn-status-use-history t] + ("Mark / Unmark" + ["Mark" svn-status-set-user-mark t] + ["Unmark" svn-status-unset-user-mark t] + ["Unmark all" svn-status-unset-all-usermarks t] + "---" + ["Mark/Unmark unknown" svn-status-mark-unknown t] + ["Mark/Unmark modified" svn-status-mark-modified t] + ["Mark/Unmark modified properties" svn-status-mark-modified-properties t] + ["Mark/Unmark added" svn-status-mark-added t] + ["Mark/Unmark deleted" svn-status-mark-deleted t] + ["Mark/Unmark modified/added/deleted" svn-status-mark-changed t] + ["Mark/Unmark filename by extension" svn-status-mark-by-file-ext t] + ["Mark/Unmark filename by regexp" svn-status-mark-filename-regexp t] + ["Store Usermarks" svn-status-store-usermarks t] + ["Load Usermarks" svn-status-load-usermarks t] + ) + ["Hide Unknown" svn-status-toggle-hide-unknown + :style toggle :selected svn-status-hide-unknown] + ["Hide Unmodified" svn-status-toggle-hide-unmodified + :style toggle :selected svn-status-hide-unmodified] + ["Hide Externals" svn-status-toggle-hide-externals + :style toggle :selected svn-status-hide-externals] + ["Show Client versions" svn-status-version t] + ["Prepare bug report" svn-prepare-bug-report t] + )) + +(defvar svn-status-file-popup-menu-list + '(["open" svn-status-find-file-other-window t] + ["svn diff" svn-status-show-svn-diff t] + ["svn commit" svn-status-commit t] + ["svn log" svn-status-show-svn-log t] + ["svn blame" svn-status-blame t] + ["mark" svn-status-set-user-mark t] + ["unmark" svn-status-unset-user-mark t] + ["svn add" svn-status-add-file t] + ["svn add recursively" svn-status-add-file-recursively t] + ["svn mv..." svn-status-mv t] + ["svn rm..." svn-status-rm t] + ["svn lock" svn-status-lock t] + ["svn unlock" svn-status-unlock t] + ["svn info" svn-status-info t] + ) "A list of menu entries for `svn-status-popup-menu'") + +;; extend svn-status-file-popup-menu-list via: +;; (add-to-list 'svn-status-file-popup-menu-list ["commit" svn-status-commit t]) + +(defun svn-status-popup-menu (event) + "Display a file specific popup menu" + (interactive "e") + (mouse-set-point event) + (let* ((line-info (svn-status-get-line-information)) + (name (svn-status-line-info->filename line-info))) + (when line-info + (easy-menu-define svn-status-actual-popup-menu nil nil + (append (list name) svn-status-file-popup-menu-list)) + (svn-status-face-set-temporary-during-popup + 'svn-status-marked-popup-face (svn-point-at-bol) (svn-point-at-eol) + svn-status-actual-popup-menu)))) + +(defun svn-status-face-set-temporary-during-popup (face begin end menu &optional prefix) + "Put FACE on BEGIN and END in the buffer during Popup MENU. +PREFIX is passed to `popup-menu'." + (let (o) + (unwind-protect + (progn + (setq o (make-overlay begin end)) + (overlay-put o 'face face) + (save-excursion (sit-for 0)) + (popup-menu menu prefix)) + (delete-overlay o)))) + +(defun svn-status-mode () + "Major mode used by psvn.el to display the output of \"svn status\". + +The Output has the following format: + FPH BASE CMTD Author em File +F = Filemark +P = Property mark +H = History mark +BASE = local base revision +CMTD = last committed revision +Author = author of change +em = \"**\" or \"(Update Available)\" [see `svn-status-short-mod-flag-p'] + if file can be updated +File = path/filename + +The following keys are defined: +\\{svn-status-mode-map}" + (interactive) + (kill-all-local-variables) + + (use-local-map svn-status-mode-map) + (easy-menu-add svn-status-mode-menu) + + (setq major-mode 'svn-status-mode) + (setq mode-name "svn-status") + (setq mode-line-process 'svn-status-mode-line-process) + (run-hooks 'svn-status-mode-hook) + (let ((view-read-only nil)) + (toggle-read-only 1))) + +(defun svn-status-update-mode-line () + (setq svn-status-mode-line-process + (concat svn-status-mode-line-process-edit-flag svn-status-mode-line-process-status)) + (force-mode-line-update)) + +(defun svn-status-bury-buffer (arg) + "Bury the buffers used by psvn.el +Currently this is: + `svn-status-buffer-name' + `svn-process-buffer-name' + `svn-log-edit-buffer-name' + *svn-property-edit* + *svn-log* + *svn-info* +When called with a prefix argument, ARG, switch back to the window configuration that was +in use before `svn-status' was called." + (interactive "P") + (cond (arg + (when svn-status-initial-window-configuration + (set-window-configuration svn-status-initial-window-configuration))) + (t + (let ((bl `(,svn-log-edit-buffer-name "*svn-property-edit*" "*svn-log*" "*svn-info*" ,svn-process-buffer-name))) + (while bl + (when (get-buffer (car bl)) + (bury-buffer (car bl))) + (setq bl (cdr bl))) + (when (string= (buffer-name) svn-status-buffer-name) + (bury-buffer)))))) + +(defun svn-status-save-some-buffers (&optional tree) + "Save all buffers visiting a file in TREE. +If TREE is not given, try `svn-status-base-dir' as TREE." + (interactive) + ;; (message "svn-status-save-some-buffers: tree1: %s" tree) + (let ((ok t) + (tree (or (svn-status-base-dir) + tree))) + ;; (message "svn-status-save-some-buffers: tree2: %s" tree) + (unless tree + (error "Not in a svn project tree")) + (dolist (buffer (buffer-list)) + (with-current-buffer buffer + (when (buffer-modified-p) + (let ((file (buffer-file-name))) + (when file + (let ((root (svn-status-base-dir (file-name-directory file)))) + ;; (message "svn-status-save-some-buffers: file: %s, root: %s" file root) + (when (and root + (string= root tree) + ;; buffer is modified and in the tree TREE. + (or (y-or-n-p (concat "Save buffer " (buffer-name) "? ")) + (setq ok nil))) + (save-buffer)))))))) + ok)) + +(defun svn-status-find-files () + "Open selected file(s) for editing. +See `svn-status-marked-files' for what counts as selected." + (interactive) + (let ((fnames (mapcar 'svn-status-line-info->full-path (svn-status-marked-files)))) + (mapc 'find-file fnames))) + + +(defun svn-status-find-file-other-window () + "Open the file in the other window for editing." + (interactive) + (svn-status-ensure-cursor-on-file) + (find-file-other-window (svn-status-line-info->filename + (svn-status-get-line-information)))) + +(defun svn-status-find-file-other-window-noselect () + "Open the file in the other window for editing, but don't select it." + (interactive) + (svn-status-ensure-cursor-on-file) + (display-buffer + (find-file-noselect (svn-status-line-info->filename + (svn-status-get-line-information))))) + +(defun svn-status-view-file-other-window () + "Open the file in the other window for viewing." + (interactive) + (svn-status-ensure-cursor-on-file) + (view-file-other-window (svn-status-line-info->filename + (svn-status-get-line-information)))) + +(defun svn-status-find-file-or-examine-directory () + "If point is on a directory, run `svn-status' on that directory. +Otherwise run `find-file'." + (interactive) + (svn-status-ensure-cursor-on-file) + (let ((line-info (svn-status-get-line-information))) + (if (svn-status-line-info->directory-p line-info) + (svn-status (svn-status-line-info->full-path line-info)) + (find-file (svn-status-line-info->filename line-info))))) + +(defun svn-status-examine-parent () + "Run `svn-status' on the parent of the current directory." + (interactive) + (svn-status (expand-file-name "../"))) + +(defun svn-status-mouse-find-file-or-examine-directory (event) + "Move point to where EVENT occurred, and do `svn-status-find-file-or-examine-directory' +EVENT could be \"mouse clicked\" or similar." + (interactive "e") + (mouse-set-point event) + (svn-status-find-file-or-examine-directory)) + +(defun svn-status-line-info->ui-status (line-info) + "Return the ui-status structure of LINE-INFO. +See `svn-status-make-ui-status' for information about the ui-status." + (nth 0 line-info)) + +(defun svn-status-line-info->has-usermark (line-info) (nth 0 (nth 0 line-info))) +(defun svn-status-line-info->user-elide (line-info) (nth 1 (nth 0 line-info))) + +(defun svn-status-line-info->filemark (line-info) (nth 1 line-info)) +(defun svn-status-line-info->propmark (line-info) (nth 2 line-info)) +(defun svn-status-line-info->filename (line-info) (nth 3 line-info)) +(defun svn-status-line-info->filename-nondirectory (line-info) + (file-name-nondirectory (svn-status-line-info->filename line-info))) +(defun svn-status-line-info->localrev (line-info) + (if (>= (nth 4 line-info) 0) + (nth 4 line-info) + nil)) +(defun svn-status-line-info->lastchangerev (line-info) + "Return the last revision in which LINE-INFO was modified." + (let ((l (nth 5 line-info))) + (if (and l (>= l 0)) + l + nil))) +(defun svn-status-line-info->author (line-info) + "Return the last author that changed the item that is represented in LINE-INFO." + (nth 6 line-info)) +(defun svn-status-line-info->update-available (line-info) + "Return whether LINE-INFO is out of date. +In other words, whether there is a newer version available in the +repository than the working copy." + (nth 7 line-info)) +(defun svn-status-line-info->locked (line-info) + "Return whether LINE-INFO represents a locked file. +This is column three of the `svn status' output. +The result will be nil or \"L\". +\(A file becomes locked when an operation is interrupted; run \\[svn-status-cleanup]' +to unlock it.\)" + (nth 8 line-info)) +(defun svn-status-line-info->historymark (line-info) + "Mark from column four of output from `svn status'. +This will be nil unless the file is scheduled for addition with +history, when it will be \"+\"." + (nth 9 line-info)) +(defun svn-status-line-info->switched (line-info) + "Return whether LINE-INFO is switched relative to its parent. +This is column five of the output from `svn status'. +The result will be \"S\", \"X\" or nil." + (nth 10 line-info)) +(defun svn-status-line-info->repo-locked (line-info) + "Return whether LINE-INFO contains some locking information. +This is column six of the output from `svn status'. +The result will be \"K\", \"O\", \"T\", \"B\" or nil." + (nth 11 line-info)) +(defun svn-status-line-info->psvn-extra-info (line-info) + "Return a list of extra information for psvn associated with LINE-INFO. +This list holds currently only one element: +* The action after a commit or update." + (nth 12 line-info)) + +(defun svn-status-line-info->is-visiblep (line-info) + "Return whether the line is visible or not" + (or (not (or (svn-status-line-info->hide-because-unknown line-info) + (svn-status-line-info->hide-because-unmodified line-info) + (svn-status-line-info->hide-because-externals line-info) + (svn-status-line-info->hide-because-custom-hide-function line-info) + (svn-status-line-info->hide-because-user-elide line-info))) + (svn-status-line-info->update-available line-info) ;; show the line, if an update is available + (svn-status-line-info->psvn-extra-info line-info) ;; show the line, if there is some extra info displayed on this line + )) + +(defun svn-status-line-info->hide-because-unknown (line-info) + (and svn-status-hide-unknown + (eq (svn-status-line-info->filemark line-info) ??))) + +(defun svn-status-line-info->hide-because-externals (line-info) + (and svn-status-hide-externals + (eq (svn-status-line-info->filemark line-info) ?X))) + +(defun svn-status-line-info->hide-because-custom-hide-function (line-info) + (and svn-status-custom-hide-function + (apply svn-status-custom-hide-function (list line-info)))) + +(defun svn-status-line-info->hide-because-unmodified (line-info) + ;;(message " %S %S %S %S - %s" svn-status-hide-unmodified (svn-status-line-info->propmark line-info) ?_ + ;; (svn-status-line-info->filemark line-info) (svn-status-line-info->filename line-info)) + (and svn-status-hide-unmodified + (and (or (eq (svn-status-line-info->filemark line-info) ?_) + (eq (svn-status-line-info->filemark line-info) ? )) + (or (eq (svn-status-line-info->propmark line-info) ?_) + (eq (svn-status-line-info->propmark line-info) ? ) + (eq (svn-status-line-info->propmark line-info) nil))))) + +(defun svn-status-line-info->hide-because-user-elide (line-info) + (eq (svn-status-line-info->user-elide line-info) t)) + +(defun svn-status-line-info->show-user-elide-continuation (line-info) + (eq (svn-status-line-info->user-elide line-info) 'directory)) + +;; modify the line-info +(defun svn-status-line-info->set-filemark (line-info value) + (setcar (nthcdr 1 line-info) value)) + +(defun svn-status-line-info->set-propmark (line-info value) + (setcar (nthcdr 2 line-info) value)) + +(defun svn-status-line-info->set-localrev (line-info value) + (setcar (nthcdr 4 line-info) value)) + +(defun svn-status-line-info->set-author (line-info value) + (setcar (nthcdr 6 line-info) value)) + +(defun svn-status-line-info->set-lastchangerev (line-info value) + (setcar (nthcdr 5 line-info) value)) + +(defun svn-status-line-info->set-repo-locked (line-info value) + (setcar (nthcdr 11 line-info) value)) + +(defun svn-status-line-info->set-psvn-extra-info (line-info value) + (setcar (nthcdr 12 line-info) value)) + +(defun svn-status-copy-current-line-info (arg) + "Copy the current file name at point, using `svn-status-copy-filename-as-kill'. +If no file is at point, copy everything starting from ':' to the end of line." + (interactive "P") + (if (svn-status-get-line-information) + (svn-status-copy-filename-as-kill arg) + (save-excursion + (goto-char (svn-point-at-bol)) + (when (looking-at ".+?: *\\(.+\\)$") + (kill-new (svn-match-string-no-properties 1)) + (message "Copied: %s" (svn-match-string-no-properties 1)))))) + +(defun svn-status-copy-filename-as-kill (arg) + "Copy the actual file name to the kill-ring. +When called with the prefix argument 0, use the full path name." + (interactive "P") + (let ((str (if (eq arg 0) + (svn-status-line-info->full-path (svn-status-get-line-information)) + (svn-status-line-info->filename (svn-status-get-line-information))))) + (kill-new str) + (message "Copied %s" str))) + +(defun svn-status-get-child-directories (&optional dir) + "Return a list of subdirectories for DIR" + (interactive) + (let ((this-dir (concat (expand-file-name (or dir (svn-status-line-info->filename (svn-status-get-line-information)))) "/")) + (test-dir) + (sub-dir-list)) + ;;(message "this-dir %S" this-dir) + (dolist (line-info svn-status-info) + (when (svn-status-line-info->directory-p line-info) + (setq test-dir (svn-status-line-info->full-path line-info)) + (when (string= (file-name-directory test-dir) this-dir) + (add-to-list 'sub-dir-list (file-relative-name (svn-status-line-info->full-path line-info)) t)))) + sub-dir-list)) + +(defun svn-status-toggle-elide (arg) + "Toggle eliding of the current file or directory. +When called with a prefix argument, toggle the hiding of all subdirectories for the current directory." + (interactive "P") + (if arg + (let ((cur-line (svn-status-line-info->filename (svn-status-get-line-information)))) + (when (svn-status-line-info->user-elide (svn-status-get-line-information)) + (svn-status-toggle-elide nil)) + (dolist (dir-name (svn-status-get-child-directories)) + (svn-status-goto-file-name dir-name) + (svn-status-toggle-elide nil)) + (svn-status-goto-file-name cur-line)) + (let ((st-info svn-status-info) + (fname) + (test (svn-status-line-info->filename (svn-status-get-line-information))) + (len-test) + (len-fname) + (new-elide-mark t) + (elide-mark)) + (if (member test svn-status-elided-list) + (setq svn-status-elided-list (delete test svn-status-elided-list)) + (add-to-list 'svn-status-elided-list test)) + (when (string= test ".") + (setq test "")) + (setq len-test (length test)) + (while st-info + (setq fname (svn-status-line-info->filename (car st-info))) + (setq len-fname (length fname)) + (when (and (>= len-fname len-test) + (string= (substring fname 0 len-test) test)) + (setq elide-mark new-elide-mark) + (when (or (string= fname ".") + (and (= len-fname len-test) (svn-status-line-info->directory-p (car st-info)))) + (message "Elided directory %s and all its files." fname) + (setq new-elide-mark (not (svn-status-line-info->user-elide (car st-info)))) + (setq elide-mark (if new-elide-mark 'directory nil))) + ;;(message "elide-mark: %S member: %S" elide-mark (member fname svn-status-elided-list)) + (when (and (member fname svn-status-elided-list) (not elide-mark)) + (setq svn-status-elided-list (delete fname svn-status-elided-list))) + (setcar (nthcdr 1 (svn-status-line-info->ui-status (car st-info))) elide-mark)) + (setq st-info (cdr st-info)))) + ;;(message "svn-status-elided-list: %S" svn-status-elided-list) + (svn-status-update-buffer))) + +(defun svn-status-apply-elide-list () + "Elide files/directories according to `svn-status-elided-list'." + (interactive) + (let ((st-info svn-status-info) + (fname) + (len-fname) + (test) + (len-test) + (elided-list) + (elide-mark)) + (when svn-status-elided-list + (while st-info + (setq fname (svn-status-line-info->filename (car st-info))) + (setq len-fname (length fname)) + (setq elided-list svn-status-elided-list) + (setq elide-mark nil) + (while elided-list + (setq test (car elided-list)) + (when (string= test ".") + (setq test "")) + (setq len-test (length test)) + (when (and (>= len-fname len-test) + (string= (substring fname 0 len-test) test)) + (setq elide-mark t) + (when (or (string= fname ".") + (and (= len-fname len-test) (svn-status-line-info->directory-p (car st-info)))) + (setq elide-mark 'directory))) + (setq elided-list (cdr elided-list))) + ;;(message "fname: %s elide-mark: %S" fname elide-mark) + (setcar (nthcdr 1 (svn-status-line-info->ui-status (car st-info))) elide-mark) + (setq st-info (cdr st-info))))) + (svn-status-update-buffer)) + +(defun svn-status-update-with-command-list (cmd-list) + (save-excursion + (set-buffer svn-status-buffer-name) + (let ((st-info) + (found) + (action) + (fname (svn-status-line-info->filename (svn-status-get-line-information))) + (fname-pos (point)) + (column (current-column))) + (setq cmd-list (sort cmd-list '(lambda (item1 item2) (string-lessp (car item1) (car item2))))) + (while cmd-list + (unless st-info (setq st-info svn-status-info)) + ;;(message "%S" (caar cmd-list)) + (setq found nil) + (while (and (not found) st-info) + (setq found (string= (caar cmd-list) (svn-status-line-info->filename (car st-info)))) + ;;(message "found: %S" found) + (unless found (setq st-info (cdr st-info)))) + (unless found + (svn-status-message 3 "psvn: continue to search for %s" (caar cmd-list)) + (setq st-info svn-status-info) + (while (and (not found) st-info) + (setq found (string= (caar cmd-list) (svn-status-line-info->filename (car st-info)))) + (unless found (setq st-info (cdr st-info))))) + (if found + ;;update the info line + (progn + (setq action (cadar cmd-list)) + ;;(message "found %s, action: %S" (caar cmd-list) action) + (svn-status-annotate-status-buffer-entry action (car st-info))) + (svn-status-message 3 "psvn: did not find %s" (caar cmd-list))) + (setq cmd-list (cdr cmd-list))) + (if fname + (progn + (goto-char fname-pos) + (svn-status-goto-file-name fname) + (goto-char (+ column (svn-point-at-bol)))) + (goto-char (+ (next-overlay-change (point-min)) svn-status-default-column)))))) + +(defun svn-status-annotate-status-buffer-entry (action line-info) + (let ((tag-string)) + (svn-status-goto-file-name (svn-status-line-info->filename line-info)) + (when (and (member action '(committed added)) + svn-status-commit-rev-number) + (svn-status-line-info->set-localrev line-info svn-status-commit-rev-number) + (svn-status-line-info->set-lastchangerev line-info svn-status-commit-rev-number)) + (when svn-status-last-commit-author + (svn-status-line-info->set-author line-info svn-status-last-commit-author)) + (svn-status-line-info->set-psvn-extra-info line-info (list action)) + (cond ((equal action 'committed) + (setq tag-string " ") + (when (member (svn-status-line-info->repo-locked line-info) '(?K)) + (svn-status-line-info->set-repo-locked line-info nil))) + ((equal action 'added) + (setq tag-string " ")) + ((equal action 'deleted) + (setq tag-string " ")) + ((equal action 'replaced) + (setq tag-string " ")) + ((equal action 'updated) + (setq tag-string " ")) + ((equal action 'updated-props) + (setq tag-string " ")) + ((equal action 'conflicted) + (setq tag-string " ") + (svn-status-line-info->set-filemark line-info ?C)) + ((equal action 'merged) + (setq tag-string " ")) + ((equal action 'propset) + ;;(setq tag-string " ") + (svn-status-line-info->set-propmark line-info svn-status-file-modified-after-save-flag)) + ((equal action 'added-wc) + (svn-status-line-info->set-filemark line-info ?A) + (svn-status-line-info->set-localrev line-info 0)) + ((equal action 'deleted-wc) + (svn-status-line-info->set-filemark line-info ?D)) + (t + (error "Unknown action '%s for %s" action (svn-status-line-info->filename line-info)))) + (when (and tag-string (not (member action '(conflicted merged)))) + (svn-status-line-info->set-filemark line-info ? ) + (svn-status-line-info->set-propmark line-info ? )) + (let ((buffer-read-only nil)) + (delete-region (svn-point-at-bol) (svn-point-at-eol)) + (svn-insert-line-in-status-buffer line-info) + (backward-char 1) + (when tag-string + (insert tag-string)) + (delete-char 1)))) + + + +;; (svn-status-update-with-command-list '(("++ideas" committed) ("a.txt" committed) ("alf"))) +;; (svn-status-update-with-command-list (svn-status-parse-commit-output)) + +(defun svn-status-parse-commit-output () + "Parse the output of svn commit. +Return a list that is suitable for `svn-status-update-with-command-list'" + (save-excursion + (set-buffer svn-process-buffer-name) + (let ((action) + (file-name) + (skip) + (result)) + (goto-char (point-min)) + (setq svn-status-commit-rev-number nil) + (setq skip nil) ; set to t whenever we find a line not about a committed file + (while (< (point) (point-max)) + (cond ((= (svn-point-at-eol) (svn-point-at-bol)) ;skip blank lines + (setq skip t)) + ((looking-at "Sending") + (setq action 'committed)) + ((looking-at "Adding") + (setq action 'added)) + ((looking-at "Deleting") + (setq action 'deleted)) + ((looking-at "Replacing") + (setq action 'replaced)) + ((looking-at "Transmitting file data") + (setq skip t)) + ((looking-at "Committed revision \\([0-9]+\\)") + (setq svn-status-commit-rev-number + (string-to-number (svn-match-string-no-properties 1))) + (setq skip t)) + (t ;; this should never be needed(?) + (setq action 'unknown))) + (unless skip ;found an interesting line + (forward-char 15) + (when svn-status-operated-on-dot + ;; when the commit used . as argument, delete the trailing directory + ;; from the svn output + (search-forward "/" nil t)) + (setq file-name (buffer-substring-no-properties (point) (svn-point-at-eol))) + (unless svn-status-last-commit-author + (setq svn-status-last-commit-author (car (svn-status-info-for-path (expand-file-name (concat default-directory file-name)))))) + (setq result (cons (list file-name action) + result)) + (setq skip nil)) + (forward-line 1)) + result))) +;;(svn-status-parse-commit-output) +;;(svn-status-annotate-status-buffer-entry) + +(defun svn-status-parse-ar-output () + "Parse the output of svn add|remove. +Return a list that is suitable for `svn-status-update-with-command-list'" + (save-excursion + (set-buffer svn-process-buffer-name) + (let ((action) + (name) + (skip) + (result)) + (goto-char (point-min)) + (while (< (point) (point-max)) + (cond ((= (svn-point-at-eol) (svn-point-at-bol)) ;skip blank lines + (setq skip t)) + ((looking-at "A") + (setq action 'added-wc)) + ((looking-at "D") + (setq action 'deleted-wc)) + (t ;; this should never be needed(?) + (setq action 'unknown))) + (unless skip ;found an interesting line + (forward-char 10) + (setq name (buffer-substring-no-properties (point) (svn-point-at-eol))) + (setq result (cons (list name action) + result)) + (setq skip nil)) + (forward-line 1)) + result))) +;; (svn-status-parse-ar-output) +;; (svn-status-update-with-command-list (svn-status-parse-ar-output)) + +(defun svn-status-parse-update-output () + "Parse the output of svn update. +Return a list that is suitable for `svn-status-update-with-command-list'" + (save-excursion + (set-buffer svn-process-buffer-name) + (setq svn-status-update-rev-number nil) + (let ((action) + (name) + (skip) + (result)) + (goto-char (point-min)) + (while (< (point) (point-max)) + (cond ((= (svn-point-at-eol) (svn-point-at-bol)) ;skip blank lines + (setq skip t)) + ((looking-at "Updated to revision \\([0-9]+\\)") + (setq svn-status-update-rev-number + (list t (string-to-number (svn-match-string-no-properties 1)))) + (setq skip t)) + ((looking-at "At revision \\([0-9]+\\)") + (setq svn-status-update-rev-number + (list nil (string-to-number (svn-match-string-no-properties 1)))) + (setq skip t)) + ((looking-at "U") + (setq action 'updated)) + ((looking-at "A") + (setq action 'added)) + ((looking-at "D") + (setq skip t)) + ;;(setq action 'deleted)) ;;deleted files are not displayed in the svn status output. + ((looking-at "C") + (setq action 'conflicted)) + ((looking-at "G") + (setq action 'merged)) + + ((looking-at " U") + (setq action 'updated-props)) + + (t ;; this should never be needed(?) + (setq action (concat "parse-update: '" + (buffer-substring-no-properties (point) (+ 2 (point))) "'")))) + (unless skip ;found an interesting line + (forward-char 3) + (setq name (buffer-substring-no-properties (point) (svn-point-at-eol))) + (setq result (cons (list name action) + result)) + (setq skip nil)) + (forward-line 1)) + result))) +;; (svn-status-parse-update-output) +;; (svn-status-update-with-command-list (svn-status-parse-update-output)) + +(defun svn-status-parse-property-output () + "Parse the output of svn propset. +Return a list that is suitable for `svn-status-update-with-command-list'" + (save-excursion + (set-buffer svn-process-buffer-name) + (let ((result)) + (dolist (line (split-string (buffer-substring-no-properties (point-min) (point-max)) "\n")) + (message "%s" line) + (when (string-match "property '\\(.+\\)' set on '\\(.+\\)'" line) + ;;(message "property %s - file %s" (match-string 1 line) (match-string 2 line)) + (setq result (cons (list (match-string 2 line) 'propset) result)))) + result))) + +;; (svn-status-parse-property-output) +;; (svn-status-update-with-command-list (svn-status-parse-property-output)) + + +(defun svn-status-line-info->symlink-p (line-info) + "Return non-nil if LINE-INFO refers to a symlink, nil otherwise. +The value is the name of the file to which it is linked. \(See +`file-symlink-p'.\) + +On win32 systems this won't work, even though symlinks are supported +by subversion on such systems." + ;; on win32 would need to see how svn does symlinks + (file-symlink-p (svn-status-line-info->filename line-info))) + +(defun svn-status-line-info->directory-p (line-info) + "Return t if LINE-INFO refers to a directory, nil otherwise. +Symbolic links to directories count as directories (see `file-directory-p')." + (file-directory-p (svn-status-line-info->filename line-info))) + +(defun svn-status-line-info->full-path (line-info) + "Return the full path of the file represented by LINE-INFO." + (expand-file-name + (svn-status-line-info->filename line-info))) + +;;Not convinced that this is the fastest way, but... +(defun svn-status-count-/ (string) + "Return number of \"/\"'s in STRING." + (let ((n 0) + (last 0)) + (while (setq last (string-match "/" string (1+ last))) + (setq n (1+ n))) + n)) + +(defun svn-insert-line-in-status-buffer (line-info) + "Format LINE-INFO and insert the result in the current buffer." + (let ((usermark (if (svn-status-line-info->has-usermark line-info) "*" " ")) + (update-available (if (svn-status-line-info->update-available line-info) + (svn-add-face (if svn-status-short-mod-flag-p + "** " + " (Update Available)") + 'svn-status-update-available-face) + (if svn-status-short-mod-flag-p " " ""))) + (filename ;; file or /path/to/file + (concat + (if (or svn-status-display-full-path + svn-status-hide-unmodified + svn-status-hide-externals) + (svn-add-face + (let ((dir-name (file-name-as-directory + (svn-status-line-info->directory-containing-line-info + line-info nil)))) + (if (and (<= 2 (length dir-name)) + (= ?. (aref dir-name 0)) + (= ?/ (aref dir-name 1))) + (substring dir-name 2) + dir-name)) + 'svn-status-directory-face) + ;; showing all files, so add indentation + (make-string (* svn-status-indentation (svn-status-count-/ + (svn-status-line-info->filename line-info))) + 32)) + ;;symlinks get a different face + (let ((target (svn-status-line-info->symlink-p line-info))) + (if target + ;; name -> trget + ;; name gets symlink-face, target gets file/directory face + (concat + (svn-add-face (svn-status-line-info->filename-nondirectory line-info) + 'svn-status-symlink-face) + " -> " + (svn-status-choose-face-to-add + ;; TODO: could use different faces for + ;; unversioned targets and broken symlinks? + (svn-status-line-info->directory-p line-info) + target + 'svn-status-directory-face + 'svn-status-filename-face)) + ;; else target is not a link + (svn-status-choose-face-to-add + (svn-status-line-info->directory-p line-info) + (svn-status-line-info->filename-nondirectory line-info) + 'svn-status-directory-face + 'svn-status-filename-face))) + )) + (elide-hint (if (svn-status-line-info->show-user-elide-continuation line-info) " ..." ""))) + (svn-puthash (svn-status-line-info->filename line-info) + (point) + svn-status-filename-to-buffer-position-cache) + (insert (svn-status-maybe-add-face + (svn-status-line-info->has-usermark line-info) + (concat usermark + (format svn-status-line-format + (svn-status-line-info->filemark line-info) + (or (svn-status-line-info->propmark line-info) ? ) + (or (svn-status-line-info->historymark line-info) ? ) + (or (svn-status-line-info->localrev line-info) "") + (or (svn-status-line-info->lastchangerev line-info) "") + (svn-status-line-info->author line-info)) + (when svn-status-short-mod-flag-p update-available) + filename + (unless svn-status-short-mod-flag-p update-available) + (svn-status-maybe-add-string (svn-status-line-info->locked line-info) + " [ LOCKED ]" 'svn-status-locked-face) + (svn-status-maybe-add-string (svn-status-line-info->repo-locked line-info) + (let ((flag (svn-status-line-info->repo-locked line-info))) + (cond ((eq flag ?K) " [ REPO-LOCK-HERE ]") + ((eq flag ?O) " [ REPO-LOCK-OTHER ]") + ((eq flag ?T) " [ REPO-LOCK-STOLEN ]") + ((eq flag ?B) " [ REPO-LOCK-BROKEN ]") + (t " [ REPO-LOCK-UNKNOWN ]"))) + 'svn-status-locked-face) + (svn-status-maybe-add-string (eq (svn-status-line-info->switched line-info) ?S) + " (switched)" 'svn-status-switched-face) + elide-hint) + 'svn-status-marked-face) + "\n"))) + +(defun svn-status-redraw-status-buffer () + "Redraw the `svn-status-buffer-name' buffer. +Additionally clear the psvn-extra-info field in all line-info lists." + (interactive) + (dolist (line-info svn-status-info) + (svn-status-line-info->set-psvn-extra-info line-info nil)) + (svn-status-update-buffer)) + +(defun svn-status-update-buffer () + "Update the `svn-status-buffer-name' buffer, using `svn-status-info'. + This function does not access the repository." + (interactive) + ;(message "buffer-name: %s" (buffer-name)) + (unless (string= (buffer-name) svn-status-buffer-name) + (set-buffer svn-status-buffer-name)) + (svn-status-mode) + (when svn-status-refresh-info + (when (eq svn-status-refresh-info 'once) + (setq svn-status-refresh-info nil)) + (svn-status-parse-info t)) + (let ((st-info svn-status-info) + (buffer-read-only nil) + (start-pos) + (overlay) + (unmodified-count 0) ;how many unmodified files are hidden + (unknown-count 0) ;how many unknown files are hidden + (externals-count 0) ;how many svn:externals files are hidden + (custom-hide-count 0) ;how many files are hidden via svn-status-custom-hide-function + (marked-count 0) ;how many files are elided + (user-elide-count 0) + (first-line t) + (fname (svn-status-line-info->filename (svn-status-get-line-information))) + (fname-pos (point)) + (window-line-pos (svn-status-window-line-position (get-buffer-window (current-buffer)))) + (header-line-string) + (column (current-column))) + (delete-region (point-min) (point-max)) + (insert "\n") + ;; Insert all files and directories + (while st-info + (setq start-pos (point)) + (cond ((or (svn-status-line-info->has-usermark (car st-info)) first-line) + ;; Show a marked file and the "." always + (svn-insert-line-in-status-buffer (car st-info)) + (setq first-line nil)) + ((svn-status-line-info->update-available (car st-info)) + (svn-insert-line-in-status-buffer (car st-info))) + ((and svn-status-custom-hide-function + (apply svn-status-custom-hide-function (list (car st-info)))) + (setq custom-hide-count (1+ custom-hide-count))) + ((svn-status-line-info->hide-because-user-elide (car st-info)) + (setq user-elide-count (1+ user-elide-count))) + ((svn-status-line-info->hide-because-unknown (car st-info)) + (setq unknown-count (1+ unknown-count))) + ((svn-status-line-info->hide-because-unmodified (car st-info)) + (setq unmodified-count (1+ unmodified-count))) + ((svn-status-line-info->hide-because-externals (car st-info)) + (setq externals-count (1+ externals-count))) + (t + (svn-insert-line-in-status-buffer (car st-info)))) + (when (svn-status-line-info->has-usermark (car st-info)) + (setq marked-count (+ marked-count 1))) + (setq overlay (make-overlay start-pos (point))) + (overlay-put overlay 'svn-info (car st-info)) + (overlay-put overlay 'evaporate t) + (setq st-info (cdr st-info))) + ;; Insert status information at the buffer beginning + (goto-char (point-min)) + (insert (format "svn status for directory %s%s\n" + default-directory + (if svn-status-head-revision (format " (status against revision: %s)" + svn-status-head-revision) + ""))) + (when svn-status-module-name + (insert (format "Project name: %s\n" svn-status-module-name))) + (when svn-status-branch-list + (insert (format "Branches: %s\n" svn-status-branch-list))) + (when svn-status-base-info + (insert (concat "Repository Root: " (svn-status-base-info->repository-root) "\n")) + (insert (concat "Repository Url: " (svn-status-base-info->url) "\n"))) + (when svn-status-hide-unknown + (insert + (format "%d Unknown file(s) are hidden - press `?' to toggle hiding\n" + unknown-count))) + (when svn-status-hide-unmodified + (insert + (format "%d Unmodified file(s) are hidden - press `_' to toggle hiding\n" + unmodified-count))) + (when svn-status-hide-externals + (insert + (format "%d Externals file(s) are hidden - press `z' to toggle hiding\n" + externals-count))) + (when (> custom-hide-count 0) + (insert + (format "%d file(s) are hidden via the svn-status-custom-hide-function\n" + custom-hide-count))) + (when (> user-elide-count 0) + (insert (format "%d file(s) elided\n" user-elide-count))) + (insert (format "%d file(s) marked\n" marked-count)) + (setq header-line-string (concat (format svn-status-line-format + 70 80 72 "BASE" "CMTD" "Author") + (if svn-status-short-mod-flag-p "em " "") + "File")) + (cond ((eq svn-status-use-header-line t) + (setq header-line-format (concat " " header-line-string))) + ((eq svn-status-use-header-line 'inline) + (insert "\n " header-line-string "\n"))) + (setq svn-start-of-file-list-line-number (+ (count-lines (point-min) (point)) 1)) + (if fname + (progn + (goto-char fname-pos) + (svn-status-goto-file-name fname) + (goto-char (+ column (svn-point-at-bol))) + (when window-line-pos + (recenter window-line-pos))) + (goto-char (+ (next-overlay-change (point-min)) svn-status-default-column))))) + +(defun svn-status-parse-info (arg) + "Parse the svn info output for the base directory. +Show the repository url after this call in the `svn-status-buffer-name' buffer. +When called with the prefix argument 0, reset the information to nil. +This hides the repository information again. + +When ARG is t, don't update the svn status buffer. This is useful for +non-interactive use." + (interactive "P") + (if (eq arg 0) + (setq svn-status-base-info nil) + (let ((svn-process-buffer-name "*svn-info-output*")) + (when (get-buffer svn-process-buffer-name) + (kill-buffer svn-process-buffer-name)) + (svn-run nil t 'parse-info "info" ".") + (svn-status-parse-info-result))) + (unless (eq arg t) + (svn-status-update-buffer))) + +(defun svn-status-parse-info-result () + "Parse the result from the svn info command. +Put the found values in `svn-status-base-info'." + (save-excursion + (setq svn-status-base-info ()) + (set-buffer svn-process-buffer-name) + (goto-char (point-min)) + (let ((case-fold-search t) + (key) + (val)) + (loop while (looking-at "\\(.*?\\)\\s-*:\\s-*\\(.*\\)$") + do (setq key (intern (concat ":" (downcase (subst-char-in-string ?\ ?- (match-string 1)))))) + (setq val (match-string 2)) + (setq svn-status-base-info (plist-put svn-status-base-info + key val)) + until (< 0 (forward-line)))))) + +(defun svn-status-base-info->url () + "Extract the url part from `svn-status-base-info'." + (plist-get svn-status-base-info :url)) + +(defun svn-status-base-info->repository-root () + "Extract the repository-root part from `svn-status-base-info'." + (plist-get svn-status-base-info :repository-root)) + +(defun svn-status-checkout-prefix-path () + "When only a part of the svn repository is checked out, return the file path for this checkout." + (interactive) + (svn-status-parse-info t) + (let ((root (svn-status-base-info->repository-root)) + (url (svn-status-base-info->url)) + (p) + (base-dir (svn-status-base-dir)) + (wc-checkout-prefix)) + (setq p (substring url (length root))) + (setq wc-checkout-prefix (file-relative-name default-directory base-dir)) + (when (string= wc-checkout-prefix "./") + (setq wc-checkout-prefix "")) + ;; (message "svn-status-checkout-prefix-path: wc-checkout-prefix: '%s' p: '%s' base-dir: %s" wc-checkout-prefix p base-dir) + (setq p (substring p 0 (- (length p) (length wc-checkout-prefix)))) + (when (interactive-p) + (message "svn-status-checkout-prefix-path: '%s'" p)) + p)) + +(defun svn-status-ls (path &optional synchron) + "Run svn ls PATH." + (interactive "sPath for svn ls: ") + (svn-run (not synchron) t 'ls "ls" path) + (when synchron + (split-string (with-current-buffer svn-process-buffer-name + (buffer-substring-no-properties (point-min) (point-max)))))) + +(defun svn-status-ls-branches () + "Show, which branches exist for the actual working copy. +Note: this command assumes the proposed standard svn repository layout." + (interactive) + (svn-status-parse-info t) + (svn-status-ls (concat (svn-status-base-info->repository-root) "/branches"))) + +(defun svn-status-ls-tags () + "Show, which tags exist for the actual working copy. +Note: this command assumes the proposed standard svn repository layout." + (interactive) + (svn-status-parse-info t) + (svn-status-ls (concat (svn-status-base-info->repository-root) "/tags"))) + +(defun svn-status-toggle-edit-cmd-flag (&optional reset) + "Allow the user to edit the parameters for the next svn command. +This command toggles between +* editing the next command parameters (EditCmd) +* editing all all command parameters (EditCmd#) +* don't edit the command parameters () +The string in parentheses is shown in the status line to show the state." + (interactive) + (cond ((or reset (eq svn-status-edit-svn-command 'sticky)) + (setq svn-status-edit-svn-command nil)) + ((eq svn-status-edit-svn-command nil) + (setq svn-status-edit-svn-command t)) + ((eq svn-status-edit-svn-command t) + (setq svn-status-edit-svn-command 'sticky))) + (cond ((eq svn-status-edit-svn-command t) + (setq svn-status-mode-line-process-edit-flag " EditCmd")) + ((eq svn-status-edit-svn-command 'sticky) + (setq svn-status-mode-line-process-edit-flag " EditCmd#")) + (t + (setq svn-status-mode-line-process-edit-flag ""))) + (svn-status-update-mode-line)) + +(defun svn-status-goto-root-or-return () + "Bounce point between the root (\".\") and the current line." + (interactive) + (if (string= (svn-status-line-info->filename (svn-status-get-line-information)) ".") + (when svn-status-root-return-info + (svn-status-goto-file-name + (svn-status-line-info->filename svn-status-root-return-info))) + (setq svn-status-root-return-info (svn-status-get-line-information)) + (svn-status-goto-file-name "."))) + +(defun svn-status-next-line (nr-of-lines) + "Go to the next line that holds a file information. +When called with a prefix argument advance the given number of lines." + (interactive "p") + (while (progn + (forward-line nr-of-lines) + (and (not (eobp)) + (not (svn-status-get-line-information))))) + (when (svn-status-get-line-information) + (goto-char (+ (svn-point-at-bol) svn-status-default-column)))) + +(defun svn-status-previous-line (nr-of-lines) + "Go to the previous line that holds a file information. +When called with a prefix argument go back the given number of lines." + (interactive "p") + (while (progn + (forward-line (- nr-of-lines)) + (and (not (bobp)) + (not (svn-status-get-line-information))))) + (when (svn-status-get-line-information) + (goto-char (+ (svn-point-at-bol) svn-status-default-column)))) + +(defun svn-status-dired-jump () + "Jump to a dired buffer, containing the file at point." + (interactive) + (let* ((line-info (svn-status-get-line-information)) + (file-full-path (if line-info + (svn-status-line-info->full-path line-info) + default-directory))) + (let ((default-directory + (file-name-as-directory + (expand-file-name (if line-info + (svn-status-line-info->directory-containing-line-info line-info t) + default-directory))))) + (if (fboundp 'dired-jump-back) (dired-jump-back) (dired-jump))) ;; Xemacs uses dired-jump-back + (dired-goto-file file-full-path))) + +(defun svn-status-possibly-negate-meaning-of-arg (arg &optional command) + "Negate arg, if this-command is a member of svn-status-possibly-negate-meaning-of-arg." + (unless command + (setq command this-command)) + (if (member command svn-status-negate-meaning-of-arg-commands) + (not arg) + arg)) + +(defun svn-status-update (&optional arg) + "Run 'svn status -v'. +When called with a prefix argument run 'svn status -vu'." + (interactive "P") + (unless (interactive-p) + (save-excursion + (set-buffer svn-process-buffer-name) + (setq svn-status-update-previous-process-output + (buffer-substring (point-min) (point-max))))) + (svn-status default-directory arg)) + +(defun svn-status-get-line-information () + "Find out about the file under point. +The result may be parsed with the various `svn-status-line-info->...' functions." + (if (eq major-mode 'svn-status-mode) + (let ((svn-info nil)) + (dolist (overlay (overlays-at (point))) + (setq svn-info (or svn-info + (overlay-get overlay 'svn-info)))) + svn-info) + ;; different mode, means called not from the *svn-status* buffer + (if svn-status-get-line-information-for-file + (svn-status-make-line-info (if (eq svn-status-get-line-information-for-file 'relative) + (file-relative-name (buffer-file-name) (svn-status-base-dir)) + (buffer-file-name))) + (svn-status-make-line-info ".")))) + + +(defun svn-status-get-file-list (use-marked-files) + "Get either the selected files or the file under point. +USE-MARKED-FILES decides which we do. +See `svn-status-marked-files' for what counts as selected." + (if use-marked-files + (svn-status-marked-files) + (list (svn-status-get-line-information)))) + +(defun svn-status-get-file-list-names (use-marked-files) + (mapcar 'svn-status-line-info->filename (svn-status-get-file-list use-marked-files))) + +(defun svn-status-get-file-information () + "Find out about the file under point. +The result may be parsed with the various `svn-status-line-info->...' functions. +When called from a *svn-status* buffer, do the same as `svn-status-get-line-information'. +When called from a file buffer provide a structure that contains the filename." + (cond ((eq major-mode 'svn-status-mode) + (svn-status-get-line-information)) + (t + ;; a fake structure that contains the buffername for the current buffer + (svn-status-make-line-info (buffer-file-name (current-buffer)))))) + +(defun svn-status-select-line () + "Return information about the file under point. +\(Only used for debugging\)" + (interactive) + (let ((info (svn-status-get-line-information))) + (if info + (message "%S hide-because-unknown: %S hide-because-unmodified: %S hide-because-externals: %S" info + (svn-status-line-info->hide-because-unknown info) + (svn-status-line-info->hide-because-unmodified info) + (svn-status-line-info->hide-because-externals info)) + (message "No file on this line")))) + (defun svn-status-ensure-cursor-on-file () + "Raise an error unless point is on a valid file." + (unless (svn-status-get-line-information) + (error "No file on the current line"))) + +(defun svn-status-directory-containing-point (allow-self) + "Find the (full path of) directory containing the file under point. + +If ALLOW-SELF and the file is a directory, return that directory, +otherwise return the directory containing the file under point." + ;;the first `or' below is because s-s-g-l-i returns `nil' if + ;;point was outside the file list, but we need + ;;s-s-l-i->f to return a string to add to `default-directory'. + (let ((line-info (or (svn-status-get-line-information) + (svn-status-make-line-info)))) + (file-name-as-directory + (expand-file-name + (svn-status-line-info->directory-containing-line-info line-info allow-self))))) + +(defun svn-status-line-info->directory-containing-line-info (line-info allow-self) + "Find the directory containing for LINE-INFO. + +If ALLOW-SELF is t and LINE-INFO refers to a directory then return the +directory itself, in all other cases find the parent directory" + (if (and allow-self (svn-status-line-info->directory-p line-info)) + (svn-status-line-info->filename line-info) + ;;The next `or' is because (file-name-directory "file") returns nil + (or (file-name-directory (svn-status-line-info->filename line-info)) + "."))) + +(defun svn-status-set-user-mark (arg) + "Set a user mark on the current file or directory. +If the cursor is on a file this file is marked and the cursor advances to the next line. +If the cursor is on a directory all files in this directory are marked. + +If this function is called with a prefix argument, only the current line is +marked, even if it is a directory." + (interactive "P") + (setq arg (svn-status-possibly-negate-meaning-of-arg arg 'svn-status-set-user-mark)) + (let ((info (svn-status-get-line-information))) + (if info + (progn + (svn-status-apply-usermark t arg) + (svn-status-next-line 1)) + (message "No file on this line - cannot set a mark")))) + +(defun svn-status-unset-user-mark (arg) + "Remove a user mark on the current file or directory. +If the cursor is on a file, this file is unmarked and the cursor advances to the next line. +If the cursor is on a directory, all files in this directory are unmarked. + +If this function is called with a prefix argument, only the current line is +unmarked, even if is a directory." + (interactive "P") + (setq arg (svn-status-possibly-negate-meaning-of-arg arg 'svn-status-set-user-mark)) + (let ((info (svn-status-get-line-information))) + (if info + (progn + (svn-status-apply-usermark nil arg) + (svn-status-next-line 1)) + (message "No file on this line - cannot unset a mark")))) + +(defun svn-status-unset-user-mark-backwards () + "Remove a user mark from the previous file. +Then move to that line." + ;; This is consistent with `dired-unmark-backward' and + ;; `cvs-mode-unmark-up'. + (interactive) + (let ((info (save-excursion + (svn-status-next-line -1) + (svn-status-get-line-information)))) + (if info + (progn + (svn-status-next-line -1) + (svn-status-apply-usermark nil t)) + (message "No file on previous line - cannot unset a mark")))) + +(defun svn-status-apply-usermark (set-mark only-this-line) + "Do the work for the various marking/unmarking functions." + (let* ((st-info svn-status-info) + (mark-count 0) + (line-info (svn-status-get-line-information)) + (file-name (svn-status-line-info->filename line-info)) + (sub-file-regexp (if (file-directory-p file-name) + (concat "^" (regexp-quote + (file-name-as-directory file-name))) + nil)) + (newcursorpos-fname) + (i-fname) + (first-line t) + (current-line svn-start-of-file-list-line-number)) + (while st-info + (when (or (svn-status-line-info->is-visiblep (car st-info)) first-line) + (setq current-line (1+ current-line)) + (setq first-line nil)) + (setq i-fname (svn-status-line-info->filename (car st-info))) + (when (or (string= file-name i-fname) + (when sub-file-regexp + (string-match sub-file-regexp i-fname))) + (when (svn-status-line-info->is-visiblep (car st-info)) + (when (or (not only-this-line) (string= file-name i-fname)) + (setq newcursorpos-fname i-fname) + (unless (eq (car (svn-status-line-info->ui-status (car st-info))) set-mark) + (setcar (svn-status-line-info->ui-status (car st-info)) set-mark) + (setq mark-count (+ 1 mark-count)) + (save-excursion + (let ((buffer-read-only nil)) + (goto-line current-line) + (delete-region (svn-point-at-bol) (svn-point-at-eol)) + (svn-insert-line-in-status-buffer (car st-info)) + (delete-char 1))) + (message "%s %s" (if set-mark "Marked" "Unmarked") i-fname))))) + (setq st-info (cdr st-info))) + ;;(svn-status-update-buffer) + (svn-status-goto-file-name newcursorpos-fname) + (when (> mark-count 1) + (message "%s %d files" (if set-mark "Marked" "Unmarked") mark-count)))) + +(defun svn-status-apply-usermark-checked (check-function set-mark) + "Mark or unmark files, whether a given function returns t. +The function is called with the line information. Therefore the +svn-status-line-info->* functions can be used in the check." + (let ((st-info svn-status-info) + (mark-count 0)) + (while st-info + (when (apply check-function (list (car st-info))) + (unless (eq (svn-status-line-info->has-usermark (car st-info)) set-mark) + (setq mark-count (+ 1 mark-count)) + (message "%s %s" + (if set-mark "Marked" "Unmarked") + (svn-status-line-info->filename (car st-info)))) + (setcar (svn-status-line-info->ui-status (car st-info)) set-mark)) + (setq st-info (cdr st-info))) + (svn-status-update-buffer) + (when (> mark-count 1) + (message "%s %d files" (if set-mark "Marked" "Unmarked") mark-count)))) + +(defun svn-status-mark-unknown (arg) + "Mark all unknown files. +These are the files marked with '?' in the `svn-status-buffer-name' buffer. +If the function is called with a prefix arg, unmark all these files." + (interactive "P") + (svn-status-apply-usermark-checked + '(lambda (info) (eq (svn-status-line-info->filemark info) ??)) (not arg))) + +(defun svn-status-mark-added (arg) + "Mark all added files. +These are the files marked with 'A' in the `svn-status-buffer-name' buffer. +If the function is called with a prefix ARG, unmark all these files." + (interactive "P") + (svn-status-apply-usermark-checked + '(lambda (info) (eq (svn-status-line-info->filemark info) ?A)) (not arg))) + +(defun svn-status-mark-modified (arg) + "Mark all modified files. +These are the files marked with 'M' in the `svn-status-buffer-name' buffer. +Changed properties are considered. +If the function is called with a prefix ARG, unmark all these files." + (interactive "P") + (svn-status-apply-usermark-checked + '(lambda (info) (or (eq (svn-status-line-info->filemark info) ?M) + (eq (svn-status-line-info->filemark info) + svn-status-file-modified-after-save-flag) + (eq (svn-status-line-info->propmark info) ?M))) + (not arg))) + +(defun svn-status-mark-modified-properties (arg) + "Mark all files and directories with modified properties. +If the function is called with a prefix ARG, unmark all these entries." + (interactive "P") + (svn-status-apply-usermark-checked + '(lambda (info) (or (eq (svn-status-line-info->propmark info) ?M))) + (not arg))) + +(defun svn-status-mark-deleted (arg) + "Mark all files scheduled for deletion. +These are the files marked with 'D' in the `svn-status-buffer-name' buffer. +If the function is called with a prefix ARG, unmark all these files." + (interactive "P") + (svn-status-apply-usermark-checked + '(lambda (info) (eq (svn-status-line-info->filemark info) ?D)) (not arg))) + +(defun svn-status-mark-changed (arg) + "Mark all files that could be committed. +This means we mark +* all modified files +* all files scheduled for addition +* all files scheduled for deletion +* all files with modified properties + +The last two categories include all copied and moved files. +If called with a prefix ARG, unmark all such files." + (interactive "P") + (svn-status-mark-added arg) + (svn-status-mark-modified arg) + (svn-status-mark-deleted arg) + (svn-status-mark-modified-properties arg)) + +(defun svn-status-unset-all-usermarks () + (interactive) + (svn-status-apply-usermark-checked '(lambda (info) t) nil)) + +(defun svn-status-store-usermarks (arg) + "Store the current usermarks in `svn-status-usermark-storage'. +When called with a prefix argument it is possible to store different sets of marks." + (interactive "P") + (let ((file-list (svn-status-get-file-list-names t))) + (svn-puthash arg file-list svn-status-usermark-storage) + (message "psvn stored %d user marks" (length file-list)))) + +(defun svn-status-load-usermarks (arg) + "Load previously stored user marks from `svn-status-usermark-storage'. +When called with a prefix argument it is possible to store/load different sets of marks." + (interactive "P") + (let ((file-list (gethash arg svn-status-usermark-storage))) + (svn-status-apply-usermark-checked + '(lambda (info) (member (svn-status-line-info->filename info) file-list)) t))) + +(defvar svn-status-regexp-history nil + "History list of regular expressions used in svn status commands.") + +(defun svn-status-read-regexp (prompt) + (read-from-minibuffer prompt nil nil nil 'svn-status-regexp-history)) + +(defun svn-status-mark-filename-regexp (regexp &optional unmark) + "Mark all files matching REGEXP. +If the function is called with a prefix arg, unmark all these files." + (interactive + (list (svn-status-read-regexp (concat (if current-prefix-arg "Unmark" "Mark") + " files (regexp): ")) + (if current-prefix-arg t nil))) + (svn-status-apply-usermark-checked + '(lambda (info) (string-match regexp (svn-status-line-info->filename-nondirectory info))) (not unmark))) + +(defun svn-status-mark-by-file-ext (ext &optional unmark) + "Mark all files matching the given file extension EXT. +If the function is called with a prefix arg, unmark all these files." + (interactive + (list (read-string (concat (if current-prefix-arg "Unmark" "Mark") + " files with extensions: ")) + (if current-prefix-arg t nil))) + (svn-status-apply-usermark-checked + '(lambda (info) (let ((case-fold-search nil)) + (string-match (concat "\\." ext "$") (svn-status-line-info->filename-nondirectory info)))) (not unmark))) + +(defun svn-status-toggle-hide-unknown () + (interactive) + (setq svn-status-hide-unknown (not svn-status-hide-unknown)) + (svn-status-update-buffer)) + +(defun svn-status-toggle-hide-unmodified () + (interactive) + (setq svn-status-hide-unmodified (not svn-status-hide-unmodified)) + (svn-status-update-buffer)) + +(defun svn-status-toggle-hide-externals () + (interactive) + (setq svn-status-hide-externals (not svn-status-hide-externals)) + (svn-status-update-buffer)) + +(defun svn-status-get-file-name-buffer-position (name) + "Find the buffer position for a file. +If the file is not found, return nil." + (let ((start-pos (let ((cached-pos (gethash name + svn-status-filename-to-buffer-position-cache))) + (when cached-pos + (goto-char (previous-overlay-change cached-pos))) + (point))) + (found)) + ;; performance optimization: search from point to end of buffer + (while (and (not found) (< (point) (point-max))) + (goto-char (next-overlay-change (point))) + (when (string= name (svn-status-line-info->filename + (svn-status-get-line-information))) + (setq start-pos (+ (point) svn-status-default-column)) + (setq found t))) + ;; search from buffer start to point + (goto-char (point-min)) + (while (and (not found) (< (point) start-pos)) + (goto-char (next-overlay-change (point))) + (when (string= name (svn-status-line-info->filename + (svn-status-get-line-information))) + (setq start-pos (+ (point) svn-status-default-column)) + (setq found t))) + (and found start-pos))) + +(defun svn-status-goto-file-name (name) + "Move the cursor the the line that displays NAME." + (let ((pos (svn-status-get-file-name-buffer-position name))) + (if pos + (goto-char pos) + (svn-status-message 7 "Note: svn-status-goto-file-name: %s not found" name)))) + +(defun svn-status-find-info-for-file-name (name) + (let* ((st-info svn-status-info) + (info)) + (while st-info + (when (string= name (svn-status-line-info->filename (car st-info))) + (setq info (car st-info)) + (setq st-info nil)) ; terminate loop + (setq st-info (cdr st-info))) + info)) + +(defun svn-status-marked-files () + "Return all files marked by `svn-status-set-user-mark', +or (if no files were marked) the file under point." + (if (eq major-mode 'svn-status-mode) + (let* ((st-info svn-status-info) + (file-list)) + (while st-info + (when (svn-status-line-info->has-usermark (car st-info)) + (setq file-list (append file-list (list (car st-info))))) + (setq st-info (cdr st-info))) + (or file-list + (if (svn-status-get-line-information) + (list (svn-status-get-line-information)) + nil))) + ;; different mode, means called not from the *svn-status* buffer + (if svn-status-get-line-information-for-file + (list (svn-status-make-line-info (if (eq svn-status-get-line-information-for-file 'relative) + (file-relative-name (buffer-file-name) (svn-status-base-dir)) + (buffer-file-name)))) + (list (svn-status-make-line-info "."))))) + +(defun svn-status-marked-file-names () + (mapcar 'svn-status-line-info->filename (svn-status-marked-files))) + +(defun svn-status-some-files-marked-p () + "Return non-nil iff a file has been marked by `svn-status-set-user-mark'. +Unlike `svn-status-marked-files', this does not select the file under point +if no files have been marked." + ;; `some' would be shorter but requires cl-seq at runtime. + ;; (Because it accepts both lists and vectors, it is difficult to inline.) + (loop for line-info in svn-status-info + thereis (svn-status-line-info->has-usermark line-info))) + +(defun svn-status-only-dirs-or-nothing-marked-p () + "Return non-nil iff only dirs has been marked by `svn-status-set-user-mark'." + ;; `some' would be shorter but requires cl-seq at runtime. + ;; (Because it accepts both lists and vectors, it is difficult to inline.) + (loop for line-info in svn-status-info + thereis (and (not (svn-status-line-info->directory-p line-info)) + (svn-status-line-info->has-usermark line-info)))) + +(defun svn-status-ui-information-hash-table () + (let ((st-info svn-status-info) + (svn-status-ui-information (make-hash-table :test 'equal))) + (while st-info + (svn-puthash (svn-status-line-info->filename (car st-info)) + (svn-status-line-info->ui-status (car st-info)) + svn-status-ui-information) + (setq st-info (cdr st-info))) + svn-status-ui-information)) + + +(defun svn-status-create-arg-file (file-info-list) + "Create an svn client argument file" + ;; create the arg file on the remote host when we will run svn on this host! + (let ((file-name (svn-expand-filename-for-remote-access svn-status-temp-arg-file))) + ;; (message "svn-status-create-arg-file %s: %s" default-directory file-name) + (with-temp-file file-name + (let ((st-info file-info-list)) + (while st-info + (insert (svn-status-line-info->filename (car st-info))) + (insert "\n") + (setq st-info (cdr st-info))) + (setq svn-arg-file-content (buffer-substring-no-properties (point-min) (point-max))))))) + +(defun svn-status-show-process-buffer-internal (&optional scroll-to-top) + (let ((cur-buff (current-buffer))) + (unless svn-status-preserve-window-configuration + (when (string= (buffer-name) svn-status-buffer-name) + (delete-other-windows))) + (pop-to-buffer svn-process-buffer-name) + (svn-process-mode) + (when scroll-to-top + (goto-char (point-min))) + (pop-to-buffer cur-buff))) + +(defun svn-status-show-process-output (cmd &optional scroll-to-top) + "Display the result of a svn command. +Consider svn-status-window-alist to choose the buffer name." + (let ((window-mode (cadr (assoc cmd svn-status-window-alist))) + (process-default-directory)) + (cond ((eq window-mode nil) ;; use *svn-process* buffer + (setq svn-status-last-output-buffer-name svn-process-buffer-name)) + ((eq window-mode t) ;; use *svn-info* buffer + (setq svn-status-last-output-buffer-name "*svn-info*")) + ((eq window-mode 'invisible) ;; don't display the buffer + (setq svn-status-last-output-buffer-name nil)) + (t + (setq svn-status-last-output-buffer-name window-mode))) + (when svn-status-last-output-buffer-name + (if window-mode + (progn + (unless svn-status-preserve-window-configuration + (when (string= (buffer-name) svn-status-buffer-name) + (delete-other-windows))) + (pop-to-buffer svn-process-buffer-name) + (setq process-default-directory default-directory) + (switch-to-buffer (get-buffer-create svn-status-last-output-buffer-name)) + (setq default-directory process-default-directory) + (let ((buffer-read-only nil)) + (delete-region (point-min) (point-max)) + (insert-buffer-substring svn-process-buffer-name) + (when scroll-to-top + (goto-char (point-min)))) + (when (eq window-mode t) ;; *svn-info* buffer + (svn-info-mode)) + (other-window 1)) + (svn-status-show-process-buffer-internal scroll-to-top))))) + +(defun svn-status-svn-log-switches (arg) + (cond ((eq arg 0) '()) + ((or (eq arg -1) (eq arg '-)) '("-q")) + (arg '("-v")) + (t svn-status-default-log-arguments))) + +(defun svn-status-show-svn-log (arg) + "Run `svn log' on selected files. +The output is put into the *svn-log* buffer +The optional prefix argument ARG determines which switches are passed to `svn log': + no prefix --- use whatever is in the list `svn-status-default-log-arguments' + prefix argument of -1: --- use the -q switch (quiet) + prefix argument of 0 --- use no arguments + other prefix arguments: --- use the -v switch (verbose) + +See `svn-status-marked-files' for what counts as selected." + (interactive "P") + (let ((switches (svn-status-svn-log-switches arg)) + (svn-status-get-line-information-for-file t)) + ;; (message "svn-status-show-svn-log %S" arg) + (svn-status-create-arg-file (svn-status-marked-files)) + (svn-run t t 'log "log" "--targets" svn-status-temp-arg-file switches))) + +(defun svn-status-version () + "Show the version numbers for psvn.el and the svn command line client. +The version number of the client is cached in `svn-client-version'." + (interactive) + (let ((window-conf (current-window-configuration)) + (version-string)) + (if (or (interactive-p) (not svn-status-cached-version-string)) + (progn + (svn-run nil t 'version "--version") + (when (interactive-p) + (svn-status-show-process-output 'info t)) + (with-current-buffer svn-status-last-output-buffer-name + (goto-char (point-min)) + (setq svn-client-version + (when (re-search-forward "svn, version \\([0-9\.]+\\)" nil t) + (mapcar 'string-to-number (split-string (match-string 1) "\\.")))) + (let ((buffer-read-only nil)) + (goto-char (point-min)) + (insert (format "psvn.el revision: %s\n\n" svn-psvn-revision))) + (setq version-string (buffer-substring-no-properties (point-min) (point-max)))) + (setq svn-status-cached-version-string version-string)) + (setq version-string svn-status-cached-version-string) + (unless (interactive-p) + (set-window-configuration window-conf) + version-string)))) + +(defun svn-compute-svn-client-version () + "Ensure that svn-client-version is available." + (unless svn-client-version + (svn-status-version))) + +(defun svn-status-info () + "Run `svn info' on all selected files. +See `svn-status-marked-files' for what counts as selected." + (interactive) + (svn-status-create-arg-file (svn-status-marked-files)) + (svn-run t t 'info "info" "--targets" svn-status-temp-arg-file)) + +(defun svn-status-info-for-path (path) + "Run svn info on the given PATH. +Return some interesting parts of the resulting output. +At the moment a list containing the last changed author is returned." + (let ((svn-process-buffer-name "*svn-info-output*") + (last-changed-author)) + (svn-run nil t 'info "info" path) + (with-current-buffer svn-process-buffer-name + (goto-char (point-min)) + (when (search-forward "last changed author: " nil t) + (setq last-changed-author (buffer-substring-no-properties (point) (svn-point-at-eol))))) + (svn-status-message 7 "last-changed-author for '%s': %s" path last-changed-author) + (list last-changed-author))) + +(defun svn-status-blame (revision &optional file-name) + "Run `svn blame' on the current file. +When called with a prefix argument, ask the user for the REVISION to use. +When called from a file buffer, go to the current line in the resulting blame output." + (interactive "P") + (when current-prefix-arg + (setq revision (svn-status-read-revision-string "Blame for version: " "BASE"))) + (unless revision (setq revision "BASE")) + (setq svn-status-blame-revision revision) + (setq svn-status-blame-file-name (if file-name + file-name + (svn-status-line-info->filename (svn-status-get-file-information)))) + (svn-run t t 'blame "blame" svn-status-default-blame-arguments "-r" revision svn-status-blame-file-name)) + +(defun svn-blame-blame-again (arg) + "Run svn blame again, using the revision before the change at point. +When point is at revision 3472, run it with 3471." + (interactive "P") + (let ((rev (svn-blame-rev-at-point))) + (setq rev (number-to-string (- (string-to-number rev) 1))) + (when current-prefix-arg + (setq rev (svn-status-read-revision-string (format "Svn blame for rev#? ") rev))) + (svn-status-blame rev svn-status-blame-file-name))) + +(defun svn-status-show-svn-diff (arg) + "Run `svn diff' on the current file. +If the current file is a directory, compare it recursively. +If there is a newer revision in the repository, the diff is done against HEAD, +otherwise compare the working copy with BASE. +If ARG then prompt for revision to diff against (unless arg is '-) +When called with a negative prefix argument, do a non recursive diff." + (interactive "P") + (let ((non-recursive (or (and (numberp arg) (< arg 0)) (eq arg '-))) + (revision (if (and (not (eq arg '-)) arg) :ask :auto))) + (svn-status-ensure-cursor-on-file) + (svn-status-show-svn-diff-internal (list (svn-status-get-line-information)) (not non-recursive) + revision))) + +(defun svn-file-show-svn-diff (arg) + "Run `svn diff' on the current file. +If there is a newer revision in the repository, the diff is done against HEAD, +otherwise compare the working copy with BASE. +If ARG then prompt for revision to diff against." + (interactive "P") + (svn-status-show-svn-diff-internal (list (svn-status-make-line-info buffer-file-name)) nil + (if arg :ask :auto))) + +(defun svn-status-show-svn-diff-for-marked-files (arg) + "Run `svn diff' on all selected files. +If some files have been marked, compare those non-recursively; +this is because marking a directory with \\[svn-status-set-user-mark] +normally marks all of its files as well. +If no files have been marked, compare recursively the file at point. +If ARG then prompt for revision to diff against, else compare working copy with BASE." + (interactive "P") + (svn-status-show-svn-diff-internal (svn-status-marked-files) + (not (svn-status-some-files-marked-p)) + (if arg :ask "BASE"))) + +(defun svn-status-diff-show-changeset (rev &optional user-confirmation rev-against) + "Show the changeset for a given log entry. +When called with a prefix argument, ask the user for the revision." + (let* ((upper-rev (if rev-against rev-against rev)) + (lower-rev (if rev-against rev (number-to-string (- (string-to-number upper-rev) 1)))) + (rev-arg (concat lower-rev ":" upper-rev))) + (when user-confirmation + (setq rev-arg (read-string "Revision for changeset: " rev-arg))) + (svn-run nil t 'diff "diff" svn-status-default-diff-arguments (concat "-r" rev-arg)) + (svn-status-activate-diff-mode))) + +(defun svn-status-show-svn-diff-internal (line-infos recursive revision) + ;; REVISION must be one of: + ;; - a string: whatever the -r option allows. + ;; - `:ask': asks the user to specify the revision, which then becomes + ;; saved in `minibuffer-history' rather than in `command-history'. + ;; - `:auto': use "HEAD" if an update is known to exist, "BASE" otherwise. + ;; In the future, `nil' might mean omit the -r option entirely; + ;; but that currently seems to imply "BASE", so we just use that. + (when (eq revision :ask) + (setq revision (svn-status-read-revision-string + "Diff with files for version: " "PREV"))) + + (setq svn-status-last-diff-options (list line-infos recursive revision)) + + (let ((clear-buf t) + (beginning nil)) + (dolist (line-info line-infos) + (svn-run nil clear-buf 'diff "diff" svn-status-default-diff-arguments + "-r" (if (eq revision :auto) + (if (svn-status-line-info->update-available line-info) + "HEAD" "BASE") + revision) + (unless recursive "--non-recursive") + (svn-status-line-info->filename line-info)) + (setq clear-buf nil) + + ;; "svn diff --non-recursive" skips only subdirectories, not files. + ;; But a non-recursive diff via psvn should skip files too, because + ;; the user would have marked them if he wanted them to be compared. + ;; So we'll look for the "Index: foo" line that marks the first file + ;; in the diff output, and delete it and everything that follows. + ;; This is made more complicated by the fact that `svn-status-activate-diff-mode' + ;; expects the output to be left in the *svn-process* buffer. + (unless recursive + ;; Check `directory-p' relative to the `default-directory' of the + ;; "*svn-status*" buffer, not that of the svn-process-buffer-name buffer. + (let ((directory-p (svn-status-line-info->directory-p line-info))) + (with-current-buffer svn-process-buffer-name + (when directory-p + (goto-char (or beginning (point-min))) + (when (re-search-forward "^Index: " nil t) + (delete-region (match-beginning 0) (point-max)))) + (goto-char (setq beginning (point-max)))))))) + (svn-status-activate-diff-mode)) + +(defun svn-status-diff-save-current-defun-as-kill () + "Copy the function name for the change at point to the kill-ring. +That function uses `add-log-current-defun'" + (interactive) + (let ((func-name (add-log-current-defun))) + (if func-name + (progn + (kill-new func-name) + (message "Copied %S" func-name)) + (message "No current defun detected.")))) + +(defun svn-status-diff-pop-to-commit-buffer () + "Temporary switch to the `svn-status-buffer-name' buffer and start a commit from there." + (interactive) + (let ((window-conf (current-window-configuration))) + (svn-status-switch-to-status-buffer) + (svn-status-commit) + (set-window-configuration window-conf) + (setq svn-status-pre-commit-window-configuration window-conf) + (pop-to-buffer svn-log-edit-buffer-name))) + +(defun svn-status-activate-diff-mode () + "Show the `svn-process-buffer-name' buffer, using the diff-mode." + (svn-status-show-process-output 'diff t) + (let ((working-directory default-directory)) + (save-excursion + (set-buffer svn-status-last-output-buffer-name) + (setq default-directory working-directory) + (svn-status-diff-mode) + (setq buffer-read-only t)))) + +(define-derived-mode svn-status-diff-mode fundamental-mode "svn-diff" + "Major mode to display svn diffs. Derives from `diff-mode'. + +Commands: +\\{svn-status-diff-mode-map} +" + (let ((diff-mode-shared-map (copy-keymap svn-status-diff-mode-map)) + major-mode mode-name) + (diff-mode) + (set (make-local-variable 'revert-buffer-function) 'svn-status-diff-update))) + +(defun svn-status-diff-update (arg noconfirm) + "Rerun the last svn diff command and update the *svn-diff* buffer." + (interactive) + (svn-status-save-some-buffers) + (save-window-excursion + (apply 'svn-status-show-svn-diff-internal svn-status-last-diff-options))) + +(defun svn-status-show-process-buffer () + "Show the content of the `svn-process-buffer-name' buffer" + (interactive) + (svn-status-show-process-output nil)) + +(defun svn-status-pop-to-partner-buffer () + "Pop to the `svn-status-partner-buffer' if that variable is set." + (interactive) + (when svn-status-partner-buffer + (let ((cur-buf (current-buffer))) + (pop-to-buffer svn-status-partner-buffer) + (setq svn-status-partner-buffer cur-buf)))) + +(defun svn-status-pop-to-new-partner-buffer (buffer) + "Call `pop-to-buffer' and register the current buffer as partner buffer for BUFFER." + (let ((cur-buf (current-buffer))) + (pop-to-buffer buffer) + (setq svn-status-partner-buffer cur-buf))) + +(defun svn-status-add-file-recursively (arg) + "Run `svn add' on all selected files. +When a directory is added, add files recursively. +See `svn-status-marked-files' for what counts as selected. +When this function is called with a prefix argument, use the actual file instead." + (interactive "P") + (message "adding: %S" (svn-status-get-file-list-names (not arg))) + (svn-status-create-arg-file (svn-status-get-file-list (not arg))) + (svn-run t t 'add "add" "--targets" svn-status-temp-arg-file)) + +(defun svn-status-add-file (arg) + "Run `svn add' on all selected files. +When a directory is added, don't add the files of the directory + (svn add --non-recursive is called). +See `svn-status-marked-files' for what counts as selected. +When this function is called with a prefix argument, use the actual file instead." + (interactive "P") + (message "adding: %S" (svn-status-get-file-list-names (not arg))) + (svn-status-create-arg-file (svn-status-get-file-list (not arg))) + (svn-run t t 'add "add" "--non-recursive" "--targets" svn-status-temp-arg-file)) + +(defun svn-status-lock (arg) + "Run `svn lock' on all selected files. +See `svn-status-marked-files' for what counts as selected." + (interactive "P") + (message "locking: %S" (svn-status-get-file-list-names t)) + (svn-status-create-arg-file (svn-status-get-file-list t)) + (svn-run t t 'lock "lock" "--targets" svn-status-temp-arg-file)) + +(defun svn-status-unlock (arg) + "Run `svn unlock' on all selected files. +See `svn-status-marked-files' for what counts as selected." + (interactive "P") + (message "unlocking: %S" (svn-status-get-file-list-names t)) + (svn-status-create-arg-file (svn-status-get-file-list t)) + (svn-run t t 'unlock "unlock" "--targets" svn-status-temp-arg-file)) + +(defun svn-status-make-directory (dir) + "Run `svn mkdir DIR'." + ;; TODO: Allow entering a URI interactively. + ;; Currently, `read-file-name' corrupts it. + (interactive (list (read-file-name "Make directory: " + (svn-status-directory-containing-point t)))) + (unless (string-match "^[^:/]+://" dir) ; Is it a URI? + (setq dir (file-relative-name dir))) + (svn-run t t 'mkdir "mkdir" "--" dir)) + +(defun svn-status-mv () + "Prompt for a destination, and `svn mv' selected files there. +See `svn-status-marked-files' for what counts as `selected'. + +If one file was selected then the destination DEST should be a +filename to rename the selected file to, or a directory to move the +file into; if multiple files were selected then DEST should be a +directory to move the selected files into. + +The default DEST is the directory containing point. + +BUG: If we've marked some directory containging a file as well as the +file itself, then we should just mv the directory, but this implementation +doesn't check for that. +SOLUTION: for each dir, umark all its contents (but not the dir +itself) before running mv." + (interactive) + (svn-status-mv-cp "mv" "Rename" "Move" "mv")) + +(defun svn-status-cp () + "See `svn-status-mv'" + (interactive) + (svn-status-mv-cp "cp" "Copy" "Copy" "cp")) + +(defun svn-status-mv-cp (command singleprompt manyprompt fallback) + "Run svn COMMAND on marked files, prompting for destination + +This function acts on `svn-status-marked-files': at the prompt the +user can enter a new file name, or an existing directory: this is used as the argument for svn COMMAND. + COMMAND --- string saying what to do: \"mv\" or \"cp\" + SINGLEPROMPT --- string at start of prompt when one file marked + MANYPROMPT --- string at start of prompt when multiple files marked + FALLBACK --- If any marked file is unversioned, use this instead of 'svn COMMAND'" + (let* ((marked-files (svn-status-marked-files)) + (num-of-files (length marked-files)) + dest) + (if (= 1 num-of-files) + ;; one file to act on: new name, or directory to hold results + (setq dest (read-file-name + (format "%s %s to: " singleprompt + (svn-status-line-info->filename (car marked-files))) + (svn-status-directory-containing-point t) + (svn-status-line-info->full-path (car marked-files)))) + ;;TODO: (when file-exists-p but-no-dir-p dest (error "%s already exists" dest)) + ;;multiple files selected, so prompt for existing directory to mv them into. + (setq dest (expand-file-name + (svn-read-directory-name + (format "%s %d files to directory: " manyprompt num-of-files) + (svn-status-directory-containing-point t) nil t))) + (unless (file-directory-p dest) + (error "%s is not a directory" dest))) + (when (string= dest "") + (error "No destination entered")) + (unless (string-match "^[^:/]+://" dest) ; Is it a URI? + (setq dest (file-relative-name dest))) + + ;;do the move: svn mv only lets us move things once at a time, so + ;;we need to run svn mv once for each file (hence second arg to + ;;svn-run is nil.) + + ;;TODO: before doing any moving, For every marked directory, + ;;ensure none of its contents are also marked, since we dont want + ;;to move both file *and* its parent... + ;; what about elided files? what if user marks a dir+contents, then presses `_' ? +;; ;one solution: +;; (dolist (original marked-files) +;; (when (svn-status-line-info->directory-p original) +;; ;; run svn-status-goto-file-name to move point to line of file +;; ;; run svn-status-unset-user-mark to unmark dir+all contents +;; ;; run svn-status-set-user-mark to remark dir +;; ;; maybe check for local mods here, and unmark if user does't say --force? +;; )) + (dolist (original marked-files) + (let ((original-name (svn-status-line-info->filename original)) + (original-filemarks (svn-status-line-info->filemark original)) + (original-propmarks (svn-status-line-info->propmark original)) + (moved nil)) + (cond + ((or (eq original-filemarks ?M) ;local mods: maybe do `svn mv --force' + (eq original-propmarks ?M)) ;local prop mods: maybe do `svn mv --force' + (if (yes-or-no-p + (format "%s has local modifications; use `--force' to really move it? " original-name)) + (progn + (svn-status-run-mv-cp command original-name dest t) + (setq moved t)) + (message "Not acting on %s" original-name))) + ((eq original-filemarks ??) ;original is unversioned: use fallback + (if (yes-or-no-p (format "%s is unversioned. Use `%s -i -- %s %s'? " + original-name fallback original-name dest)) + ;; TODO: consider svn-call-process-function here also... + (progn (call-process fallback nil (get-buffer-create svn-process-buffer-name) nil + "-i" "--" original-name dest) + (setq moved t)) + ;;new files created by fallback are not in *svn-status* now, + ;;TODO: so call (svn-status-update) here? + (message "Not acting on %s" original-name))) + + ((eq original-filemarks ?A) ;;`A' (`svn add'ed, but not committed) + (message "Not acting on %s (commit it first)" original-name)) + + ((eq original-filemarks ? ) ;original is unmodified: can proceed + (svn-status-run-mv-cp command original-name dest) + (setq moved t)) + + ;;file has some other mark (eg conflicted) + (t + (if (yes-or-no-p + (format "The status of %s looks scary. Risk moving it anyway? " + original-name)) + (progn + (svn-status-run-mv-cp command original-name dest) + (setq moved t)) + (message "Not acting on %s" original-name)))) + (when moved + (message "psvn: did '%s' from %s to %s" command original-name dest) + ;; Silently rename the visited file of any buffer visiting this file. + (when (get-file-buffer original-name) + (with-current-buffer (get-file-buffer original-name) + (set-visited-file-name dest nil t)))))) + (svn-status-update))) + +(defun svn-status-run-mv-cp (command original destination &optional force) + "Actually run svn mv or svn cp. +This is just to prevent duplication in `svn-status-prompt-and-act-on-files'" + (if force + (svn-run nil t (intern command) command "--force" "--" original destination) + (svn-run nil t (intern command) command "--" original destination)) +;;;TODO: use something like the following instead of calling svn-status-update +;;; at the end of svn-status-mv-cp. +;; (let ((output (svn-status-parse-ar-output)) +;; newfile +;; buffer-read-only) ; otherwise insert-line-in-status-buffer fails +;; (dolist (new-file output) +;; (when (eq (cadr new-file) 'added-wc) +;; ;; files with 'wc-added action do not exist in *svn-status* +;; ;; buffer yet, so give each of them their own line-info +;; ;; TODO: need to insert the new line-info in a sensible place, ie in the correct directory! [svn-status-filename-to-buffer-position-cache might help?] + +;; (svn-insert-line-in-status-buffer +;; (svn-status-make-line-info (car new-file))))) +;; (svn-status-update-with-command-list output)) + ) + +(defun svn-status-revert () + "Run `svn revert' on all selected files. +See `svn-status-marked-files' for what counts as selected." + (interactive) + (let* ((marked-files (svn-status-marked-files)) + (num-of-files (length marked-files))) + (when (yes-or-no-p + (if (= 1 num-of-files) + (format "Revert %s? " (svn-status-line-info->filename (car marked-files))) + (format "Revert %d files? " num-of-files))) + (message "reverting: %S" (svn-status-marked-file-names)) + (svn-status-create-arg-file (svn-status-marked-files)) + (svn-run t t 'revert "revert" "--targets" svn-status-temp-arg-file)))) + +(defun svn-file-revert () + "Run `svn revert' on the current file." + (interactive) + (when (y-or-n-p (format "Revert %s? " buffer-file-name)) + (svn-run t t 'revert "revert" buffer-file-name))) + +(defun svn-status-rm (force) + "Run `svn rm' on all selected files. +See `svn-status-marked-files' for what counts as selected. +When called with a prefix argument add the command line switch --force. + +Forcing the deletion can also be used to delete files not under svn control." + (interactive "P") + (let* ((marked-files (svn-status-marked-files)) + (num-of-files (length marked-files))) + (when (yes-or-no-p + (if (= 1 num-of-files) + (format "%sRemove %s? " (if force "Force " "") (svn-status-line-info->filename (car marked-files))) + (format "%sRemove %d files? " (if force "Force " "") num-of-files))) + (message "removing: %S" (svn-status-marked-file-names)) + (svn-status-create-arg-file (svn-status-marked-files)) + (if force + (save-excursion + (svn-run t t 'rm "rm" "--force" "--targets" svn-status-temp-arg-file) + (dolist (to-delete (svn-status-marked-files)) + (when (eq (svn-status-line-info->filemark to-delete) ??) + (svn-status-goto-file-name (svn-status-line-info->filename to-delete)) + (let ((buffer-read-only nil)) + (delete-region (svn-point-at-bol) (+ 1 (svn-point-at-eol))) + (delete to-delete svn-status-info))))) + (svn-run t t 'rm "rm" "--targets" svn-status-temp-arg-file))))) + +(defun svn-status-update-cmd (arg) + "Run svn update. +When called with a prefix argument, ask the user for the revision to update to. +When called with a negative prefix argument, only update the selected files." + (interactive "P") + (let* ((selective-update (or (and (numberp arg) (< arg 0)) (eq arg '-))) + (update-extra-arg) + (rev (when arg (svn-status-read-revision-string + (if selective-update + (format "Selected entries: Run svn update -r ") + (format "Directory: %s: Run svn update -r " default-directory)) + (if selective-update "HEAD" nil))))) + (svn-compute-svn-client-version) + (if (and (<= (car svn-client-version) 1) (< (cadr svn-client-version) 5)) + (setq update-extra-arg (list "--non-interactive")) ;; svn version < 1.5 + (setq update-extra-arg (list "--accept" "postpone"))) ;; svn version >= 1.5 + (if selective-update + (progn + (message "Running svn-update for %s" (svn-status-marked-file-names)) + (svn-run t t 'update "update" + (when rev (list "-r" rev)) + update-extra-arg + (svn-status-marked-file-names))) + (message "Running svn-update for %s" default-directory) + (svn-run t t 'update "update" + (when rev (list "-r" rev)) + update-extra-arg + (svn-local-filename-for-remote-access (expand-file-name default-directory)))))) + +(defun svn-status-commit () + "Commit selected files. +If some files have been marked, commit those non-recursively; +this is because marking a directory with \\[svn-status-set-user-mark] +normally marks all of its files as well. +If no files have been marked, commit recursively the file at point." + (interactive) + (svn-status-save-some-buffers) + (let* ((selected-files (svn-status-marked-files))) + (setq svn-status-files-to-commit selected-files + svn-status-recursive-commit (not (svn-status-only-dirs-or-nothing-marked-p))) + (svn-log-edit-show-files-to-commit) + (svn-status-pop-to-commit-buffer) + (when svn-log-edit-insert-files-to-commit + (svn-log-edit-insert-files-to-commit)) + (when svn-log-edit-show-diff-for-commit + (svn-log-edit-svn-diff nil)))) + +(defun svn-status-pop-to-commit-buffer () + "Pop to the svn commit buffer. +If a saved log message exists in `svn-log-edit-file-name' insert it in the buffer." + (interactive) + (setq svn-status-pre-commit-window-configuration (current-window-configuration)) + (let* ((use-existing-buffer (get-buffer svn-log-edit-buffer-name)) + (commit-buffer (get-buffer-create svn-log-edit-buffer-name)) + (dir default-directory) + (log-edit-file-name)) + (pop-to-buffer commit-buffer) + (setq default-directory dir) + (setq log-edit-file-name (svn-log-edit-file-name)) + (unless use-existing-buffer + (when (and log-edit-file-name (file-readable-p log-edit-file-name)) + (insert-file-contents log-edit-file-name))) + (svn-log-edit-mode))) + +(defun svn-status-switch-to-status-buffer () + "Switch to the `svn-status-buffer-name' buffer." + (interactive) + (switch-to-buffer svn-status-buffer-name)) + +(defun svn-status-pop-to-status-buffer () + "Pop to the `svn-status-buffer-name' buffer." + (interactive) + (pop-to-buffer svn-status-buffer-name)) + +(defun svn-status-via-bookmark (bookmark) + "Allows a quick selection of a bookmark in `svn-bookmark-list'. +Run `svn-status' on the selected bookmark." + (interactive + (list + (let ((completion-ignore-case t)) + (funcall svn-status-completing-read-function "SVN status bookmark: " svn-bookmark-list)))) + (unless bookmark + (error "No bookmark specified")) + (let ((directory (cdr (assoc bookmark svn-bookmark-list)))) + (if (file-directory-p directory) + (svn-status directory) + (error "%s is not a directory" directory)))) + +(defun svn-status-export () + "Run `svn export' for the current working copy. +Ask the user for the destination path. +`svn-status-default-export-directory' is suggested as export directory." + (interactive) + (let* ((src default-directory) + (dir1-name (nth 1 (nreverse (split-string src "/")))) + (dest (read-file-name (format "Export %s to " src) (concat svn-status-default-export-directory dir1-name)))) + (svn-run t t 'export "export" (expand-file-name src) (expand-file-name dest)) + (message "svn-status-export %s %s" src dest))) + +(defun svn-status-cleanup (arg) + "Run `svn cleanup' on all selected files. +See `svn-status-marked-files' for what counts as selected. +When this function is called with a prefix argument, use the actual file instead." + (interactive "P") + (let ((file-names (svn-status-get-file-list-names (not arg)))) + (if file-names + (progn + (message "svn-status-cleanup %S" file-names) + (svn-run t t 'cleanup (append (list "cleanup") file-names))) + (message "No valid file selected - No status cleanup possible")))) + +(defun svn-status-resolved () + "Run `svn resolved' on all selected files. +See `svn-status-marked-files' for what counts as selected." + (interactive) + (let* ((marked-files (svn-status-marked-files)) + (num-of-files (length marked-files))) + (when (yes-or-no-p + (if (= 1 num-of-files) + (format "Resolve %s? " (svn-status-line-info->filename (car marked-files))) + (format "Resolve %d files? " num-of-files))) + (message "resolving: %S" (svn-status-marked-file-names)) + (svn-status-create-arg-file (svn-status-marked-files)) + (svn-run t t 'resolved "resolved" "--targets" svn-status-temp-arg-file)))) + + +(defun svn-status-svnversion () + "Run svnversion on the directory that contains the file at point." + (interactive) + (svn-status-ensure-cursor-on-file) + (let ((simple-path (svn-status-line-info->filename (svn-status-get-line-information))) + (full-path (svn-status-line-info->full-path (svn-status-get-line-information))) + (version)) + (unless (file-directory-p simple-path) + (setq simple-path (or (file-name-directory simple-path) ".")) + (setq full-path (file-name-directory full-path))) + (setq version (shell-command-to-string (concat "svnversion -n " full-path))) + (message "svnversion for '%s': %s" simple-path version) + version)) + +;; -------------------------------------------------------------------------------- +;; Update the `svn-status-buffer-name' buffer, when a file is saved +;; -------------------------------------------------------------------------------- + +(defvar svn-status-file-modified-after-save-flag ?m + "Flag shown whenever a file is modified and saved in Emacs. +The flag is shown in the `svn-status-buffer-name' buffer. +Recommended values are ?m or ?M.") +(defun svn-status-after-save-hook () + "Set a modified indication, when a file is saved from a svn working copy." + (let* ((svn-dir (car-safe svn-status-directory-history)) + (svn-dir (when svn-dir (expand-file-name svn-dir))) + (file-dir (file-name-directory (buffer-file-name))) + (svn-dir-len (length (or svn-dir ""))) + (file-dir-len (length file-dir)) + (file-name)) + (when (and (get-buffer svn-status-buffer-name) + svn-dir + (>= file-dir-len svn-dir-len) + (string= (substring file-dir 0 svn-dir-len) svn-dir)) + (setq file-name (substring (buffer-file-name) svn-dir-len)) + ;;(message "In svn-status directory %S" file-name) + (let ((st-info svn-status-info) + (i-fname)) + (while st-info + (setq i-fname (svn-status-line-info->filename (car st-info))) + ;;(message "i-fname=%S" i-fname) + (when (and (string= file-name i-fname) + (not (eq (svn-status-line-info->filemark (car st-info)) ??))) + (svn-status-line-info->set-filemark (car st-info) + svn-status-file-modified-after-save-flag) + (save-window-excursion + (set-buffer svn-status-buffer-name) + (save-excursion + (let ((buffer-read-only nil) + (pos (svn-status-get-file-name-buffer-position i-fname))) + (if pos + (progn + (goto-char pos) + (delete-region (svn-point-at-bol) (svn-point-at-eol)) + (svn-insert-line-in-status-buffer (car st-info)) + (delete-char 1)) + (svn-status-message 3 "psvn: file %s not found, updating %s buffer content..." + i-fname svn-status-buffer-name) + (svn-status-update-buffer)))))) + (setq st-info (cdr st-info)))))) + nil) + +(add-hook 'after-save-hook 'svn-status-after-save-hook) + +;; -------------------------------------------------------------------------------- +;; vc-svn integration +;; -------------------------------------------------------------------------------- +(defvar svn-status-state-mark-modeline t) ; modeline mark display or not +(defvar svn-status-state-mark-tooltip nil) ; modeline tooltip display + +(defun svn-status-state-mark-modeline-dot (color) + (propertize " " + 'help-echo 'svn-status-state-mark-tooltip + 'display + `(image :type xpm + :data ,(format "/* XPM */ +static char * data[] = { +\"18 13 3 1\", +\" c None\", +\"+ c #000000\", +\". c %s\", +\" \", +\" +++++ \", +\" +.....+ \", +\" +.......+ \", +\" +.........+ \", +\" +.........+ \", +\" +.........+ \", +\" +.........+ \", +\" +.........+ \", +\" +.......+ \", +\" +.....+ \", +\" +++++ \", +\" \"};" + color) + :ascent center))) + +(defun svn-status-install-state-mark-modeline (color) + (push `(svn-status-state-mark-modeline + ,(svn-status-state-mark-modeline-dot color)) + mode-line-format) + (force-mode-line-update t)) + +(defun svn-status-uninstall-state-mark-modeline () + (setq mode-line-format + (remove-if #'(lambda (mode) (eq (car-safe mode) + 'svn-status-state-mark-modeline)) + mode-line-format)) + (force-mode-line-update t)) + +(defun svn-status-update-state-mark-tooltip (tooltip) + (setq svn-status-state-mark-tooltip tooltip)) + +(defun svn-status-update-state-mark (color) + (svn-status-uninstall-state-mark-modeline) + (svn-status-install-state-mark-modeline color)) + +(defsubst svn-status-in-vc-mode? () + "Is vc-svn active?" + (cond + ((fboundp 'vc-backend) + (eq 'SVN (vc-backend buffer-file-name))) + ((and (boundp 'vc-mode) vc-mode) + (string-match "^ SVN" (svn-substring-no-properties vc-mode))))) + +(when svn-status-fancy-file-state-in-modeline + (defadvice vc-find-file-hook (after svn-status-vc-svn-find-file-hook activate) + "vc-find-file-hook advice for synchronizing psvn with vc-svn interface" + (when (svn-status-in-vc-mode?) (svn-status-update-modeline))) + + (defadvice vc-after-save (after svn-status-vc-svn-after-save activate) + "vc-after-save advice for synchronizing psvn when saving buffer" + (when (svn-status-in-vc-mode?) (svn-status-update-modeline))) + + (defadvice ediff-refresh-mode-lines + (around svn-modeline-ediff-fixup activate compile) + "Fixup svn file status in the modeline when using ediff" + (ediff-with-current-buffer ediff-buffer-A + (svn-status-uninstall-state-mark-modeline)) + (ediff-with-current-buffer ediff-buffer-B + (svn-status-uninstall-state-mark-modeline)) + ad-do-it + (ediff-with-current-buffer ediff-buffer-A + (svn-status-update-modeline)) + (ediff-with-current-buffer ediff-buffer-B + (svn-status-update-modeline)))) + +(defun svn-status-update-modeline () + "Update modeline state dot mark properly" + (when (and buffer-file-name (svn-status-in-vc-mode?)) + (svn-status-update-state-mark + (svn-status-interprete-state-mode-color + (vc-svn-state buffer-file-name))))) + +(defsubst svn-status-interprete-state-mode-color (stat) + "Interpret vc-svn-state symbol to mode line color" + (case stat + ('edited "tomato" ) + ('up-to-date "GreenYellow" ) + ;; what is missing here?? + ;; ('unknown "gray" ) + ;; ('added "blue" ) + ;; ('deleted "red" ) + ;; ('unmerged "purple" ) + (t "red"))) + +;; -------------------------------------------------------------------------------- +;; Getting older revisions +;; -------------------------------------------------------------------------------- + +(defun svn-status-get-specific-revision (arg) + "Retrieve older revisions. +The older revisions are stored in backup files named F.~REVISION~. + +When the function is called without a prefix argument: get all marked files. +With a prefix argument: get only the actual file." + (interactive "P") + (svn-status-get-specific-revision-internal + (svn-status-get-file-list (not arg)) :ask t)) + +(defun svn-status-get-specific-revision-internal (line-infos revision handle-relative-svn-status-dir) + "Retrieve older revisions of files. +LINE-INFOS is a list of line-info structures (see +`svn-status-get-line-information'). +REVISION is one of: +- a string: whatever the -r option allows. +- `:ask': asks the user to specify the revision, which then becomes + saved in `minibuffer-history' rather than in `command-history'. +- `:auto': Use \"HEAD\" if an update is known to exist, \"BASE\" otherwise. + +After the call, `svn-status-get-revision-file-info' will be an alist +\((WORKING-FILE-NAME . RETRIEVED-REVISION-FILE-NAME) ...). These file +names are relative to the directory where `svn-status' was run." + ;; In `svn-status-show-svn-diff-internal', there is a comment + ;; that REVISION `nil' might mean omitting the -r option entirely. + ;; That doesn't seem like a good idea with svn cat. + + ;; (message "svn-status-get-specific-revision-internal: %S %S" line-infos revision) + + (when (eq revision :ask) + (setq revision (svn-status-read-revision-string + "Get files for version: " "PREV"))) + + (let ((count (length line-infos))) + (if (= count 1) + (let ((line-info (car line-infos))) + (message "Getting revision %s of %s" + (if (eq revision :auto) + (if (svn-status-line-info->update-available line-info) + "HEAD" "BASE") + revision) + (svn-status-line-info->filename line-info))) + ;; We could compute "Getting HEAD of 8 files and BASE of 11 files" + ;; but that'd be more bloat than it's worth. + (message "Getting revision %s of %d files" + (if (eq revision :auto) "HEAD or BASE" revision) + count))) + + (let ((svn-status-get-specific-revision-file-info '())) + (dolist (line-info line-infos) + (let* ((revision (if (eq revision :auto) + (if (svn-status-line-info->update-available line-info) + "HEAD" "BASE") + revision)) ;must be a string by this point + (file-name (svn-status-line-info->filename line-info)) + ;; If REVISION is e.g. "HEAD", should we find out the actual + ;; revision number and save "foo.~123~" rather than "foo.~HEAD~"? + ;; OTOH, `auto-mode-alist' already ignores ".~HEAD~" suffixes, + ;; and if users often want to know the revision numbers of such + ;; files, they can use svn:keywords. + (file-name-with-revision (concat (file-name-nondirectory file-name) ".~" revision "~")) + (default-directory (concat (svn-status-base-dir) + (if handle-relative-svn-status-dir + (file-relative-name default-directory (svn-status-base-dir)) + "") + (file-name-directory file-name)))) + ;; `add-to-list' would unnecessarily check for duplicates. + (push (cons file-name (concat (file-name-directory file-name) file-name-with-revision)) + svn-status-get-specific-revision-file-info) + (svn-status-message 3 "svn-status-get-specific-revision-internal: file: %s, default-directory: %s" + file-name default-directory) + (svn-status-message 3 "svn-status-get-specific-revision-internal: file-name-with-revision: %s %S" + file-name-with-revision (file-exists-p file-name-with-revision)) + (save-excursion + (if (or (not (file-exists-p file-name-with-revision)) ;; file does not exist + (not (string= (number-to-string (string-to-number revision)) revision))) ;; revision is not a number + (progn + (message "Getting revision %s of %s, target: %s" revision file-name + (expand-file-name(concat default-directory file-name-with-revision))) + (svn-compute-svn-client-version) + (let ((content + (with-temp-buffer + (if (and (and (<= (car svn-client-version) 1) (< (cadr svn-client-version) 7)) + (string= revision "BASE")) + ;; Shortcut: Take the file from the file system when using svn client < v1.7 + (insert-file-contents (concat (svn-wc-adm-dir-name) + "/text-base/" + (file-name-nondirectory file-name) + ".svn-base")) + (progn + (svn-run nil t 'cat "cat" "-r" revision + (concat default-directory (file-name-nondirectory file-name))) + ;;todo: error processing + ;;svn: Filesystem has no item + ;;svn: file not found: revision `15', path `/trunk/file.txt' + (insert-buffer-substring svn-process-buffer-name))) + (buffer-string)))) + (find-file file-name-with-revision) + (setq buffer-read-only nil) + (erase-buffer) ;Widen, because we'll save the whole buffer. + (insert content) + (goto-char (point-min)) + (let ((write-file-functions nil) + (require-final-newline nil)) + (save-buffer)))) + (find-file file-name-with-revision))))) + ;;(message "default-directory: %s revision-file-info: %S" default-directory svn-status-get-specific-revision-file-info) + (nreverse svn-status-get-specific-revision-file-info))) + +(defun svn-status-ediff-with-revision (arg) + "Run ediff on the current file with a different revision. +If there is a newer revision in the repository, the diff is done against HEAD, +otherwise compare the working copy with BASE. +If ARG then prompt for revision to diff against." + (interactive "P") + (let* ((svn-status-get-specific-revision-file-info + (svn-status-get-specific-revision-internal + (list (svn-status-make-line-info + (file-relative-name + (svn-status-line-info->full-path (svn-status-get-line-information)) + (svn-status-base-dir)) + nil nil nil nil nil nil + (svn-status-line-info->update-available (svn-status-get-line-information)))) + (if arg :ask :auto) + nil)) + (ediff-after-quit-destination-buffer (current-buffer)) + (default-directory (svn-status-base-dir)) + (my-buffer (find-file-noselect (caar svn-status-get-specific-revision-file-info))) + (base-buff (find-file-noselect (cdar svn-status-get-specific-revision-file-info))) + (svn-transient-buffers (list my-buffer base-buff)) + (startup-hook '(svn-ediff-startup-hook))) + (ediff-buffers base-buff my-buffer startup-hook))) + +(defun svn-ediff-startup-hook () + ;; (message "svn-ediff-startup-hook: ediff-after-quit-hook-internal: %S" ediff-after-quit-hook-internal) + (add-hook 'ediff-after-quit-hook-internal + `(lambda () + (svn-ediff-exit-hook + ',ediff-after-quit-destination-buffer ',svn-transient-buffers)) + nil 'local)) + +(defun svn-ediff-exit-hook (svn-buf tmp-bufs) + ;; (message "svn-ediff-exit-hook: svn-buf: %s, tmp-bufs: %s" svn-buf tmp-bufs) + ;; kill the temp buffers (and their associated windows) + (dolist (tb tmp-bufs) + (when (and tb (buffer-live-p tb) (not (buffer-modified-p tb))) + (let* ((win (get-buffer-window tb t)) + (file-name (buffer-file-name tb)) + (is-temp-file (numberp (string-match "~\\([0-9]+\\|BASE\\)~" file-name)))) + ;; (message "svn-ediff-exit-hook - is-temp-file: %s, temp-buf:: %s - %s " is-temp-file (current-buffer) file-name) + (when (and win (> (count-windows) 1) + (delete-window win))) + (kill-buffer tb) + (when (and is-temp-file svn-status-ediff-delete-temporary-files) + (when (or (eq svn-status-ediff-delete-temporary-files t) + (y-or-n-p (format "Delete File '%s' ? " file-name))) + (delete-file file-name)))))) + ;; switch back to the *svn* buffer + (when (and svn-buf (buffer-live-p svn-buf) + (not (get-buffer-window svn-buf t))) + (ignore-errors (switch-to-buffer svn-buf)))) + + +(defun svn-status-read-revision-string (prompt &optional default-value) + "Prompt the user for a svn revision number." + (interactive) + (read-string prompt default-value)) + +(defun svn-file-show-svn-ediff (arg) + "Run ediff on the current file with a previous revision. +If ARG then prompt for revision to diff against." + (interactive "P") + (let ((svn-status-get-line-information-for-file 'relative) + (default-directory (svn-status-base-dir))) + (svn-status-ediff-with-revision arg))) + +;; -------------------------------------------------------------------------------- +;; SVN process handling +;; -------------------------------------------------------------------------------- + +(defun svn-process-kill () + "Kill the current running svn process." + (interactive) + (let ((process (get-process "svn"))) + (if process + (delete-process process) + (message "No running svn process")))) + +(defun svn-process-send-string (string &optional send-passwd) + "Send a string to the running svn process. +This is useful, if the running svn process asks the user a question. +Note: use C-q C-j to send a line termination character." + (interactive "sSend string to svn process: ") + (save-excursion + (set-buffer svn-process-buffer-name) + (goto-char (point-max)) + (let ((buffer-read-only nil)) + (insert (if send-passwd (make-string (length string) ?.) string))) + (set-marker (process-mark (get-process "svn")) (point))) + (process-send-string "svn" string)) + +(defun svn-process-send-string-and-newline (string &optional send-passwd) + "Send a string to the running svn process. +Just call `svn-process-send-string' with STRING and an end of line termination. +When called with a prefix argument, read the data from user as password." + (interactive (let* ((use-passwd current-prefix-arg) + (s (if use-passwd + (read-passwd "Send secret line to svn process: ") + (read-string "Send line to svn process: ")))) + (list s use-passwd))) + (svn-process-send-string (concat string "\n") send-passwd)) + +;; -------------------------------------------------------------------------------- +;; Search interface +;; -------------------------------------------------------------------------------- + +(defun svn-status-grep-files (regexp) + "Run grep on selected file(s). +See `svn-status-marked-files' for what counts as selected." + (interactive "sGrep files for: ") + (unless grep-command + (grep-compute-defaults)) + (grep (format "%s %s %s" grep-command (shell-quote-argument regexp) + (mapconcat 'identity (svn-status-marked-file-names) " ")))) + +(defun svn-status-search-files (search-string) + "Search selected file(s) for a fixed SEARCH-STRING. +See `svn-status-marked-files' for what counts as selected." + (interactive "sSearch files for: ") + (svn-status-grep-files (regexp-quote search-string))) + +;; -------------------------------------------------------------------------------- +;; Property List stuff +;; -------------------------------------------------------------------------------- + +(defun svn-status-property-list () + (interactive) + (let ((file-names (svn-status-marked-file-names))) + (if file-names + (progn + (svn-run t t 'proplist (append (list "proplist" "-v") file-names))) + (message "No valid file selected - No property listing possible")))) + +(defun svn-status-proplist-start () + (svn-status-ensure-cursor-on-file) + (svn-run t t 'proplist-parse "proplist" (svn-status-line-info->filename + (svn-status-get-line-information)))) +(defun svn-status-property-edit-one-entry (arg) + "Edit a property. +When called with a prefix argument, it is possible to enter a new property." + (interactive "P") + (setq svn-status-property-edit-must-match-flag (not arg)) + (svn-status-proplist-start)) + +(defun svn-status-property-set () + (interactive) + (setq svn-status-property-edit-must-match-flag nil) + (svn-status-proplist-start)) + +(defun svn-status-property-delete () + (interactive) + (setq svn-status-property-edit-must-match-flag t) + (svn-status-proplist-start)) + +(defun svn-status-property-parse-property-names () + ;(svn-status-show-process-buffer-internal t) + (message "svn-status-property-parse-property-names") + (let ((pl) + (prop-name) + (prop-value)) + (save-excursion + (set-buffer svn-process-buffer-name) + (goto-char (point-min)) + (forward-line 1) + (while (looking-at " \\(.+\\)") + (setq pl (append pl (list (match-string 1)))) + (forward-line 1))) + ;(cond last-command: svn-status-property-set, svn-status-property-edit-one-entry + (cond ((eq last-command 'svn-status-property-edit-one-entry) + ;;(message "svn-status-property-edit-one-entry") + (setq prop-name + (completing-read "Set Property - Name: " (mapcar 'list pl) + nil svn-status-property-edit-must-match-flag)) + (unless (string= prop-name "") + (save-excursion + (set-buffer svn-status-buffer-name) + (svn-status-property-edit (list (svn-status-get-line-information)) + prop-name)))) + ((eq last-command 'svn-status-property-set) + (message "svn-status-property-set") + (setq prop-name + (completing-read "Set Property - Name: " (mapcar 'list pl) nil nil)) + (setq prop-value (read-from-minibuffer "Property value: ")) + (unless (string= prop-name "") + (save-excursion + (set-buffer svn-status-buffer-name) + (message "Setting property %s := %s for %S" prop-name prop-value + (svn-status-marked-file-names)) + (let ((file-names (svn-status-marked-file-names))) + (when file-names + (svn-run nil t 'propset + (append (list "propset" prop-name prop-value) file-names)) + ) + ) + (message "propset finished.") + ))) + ((eq last-command 'svn-status-property-delete) + (setq prop-name + (completing-read "Delete Property - Name: " (mapcar 'list pl) nil t)) + (unless (string= prop-name "") + (save-excursion + (set-buffer svn-status-buffer-name) + (let ((file-names (svn-status-marked-file-names))) + (when file-names + (message "Going to delete prop %s for %s" prop-name file-names) + (svn-run t t 'propdel + (append (list "propdel" prop-name) file-names)))))))))) + +(defun svn-status-property-edit (file-info-list prop-name &optional new-prop-value remove-values) + (let* ((commit-buffer (get-buffer-create "*svn-property-edit*")) + (dir default-directory) + ;; now only one file is implemented ... + (file-name (svn-status-line-info->filename (car file-info-list))) + (prop-value)) + (message "Edit property %s for file %s" prop-name file-name) + (svn-run nil t 'propget-parse "propget" prop-name file-name) + (save-excursion + (set-buffer svn-process-buffer-name) + (setq prop-value (if (> (point-max) 1) + (buffer-substring (point-min) (- (point-max) 1)) + ""))) + (setq svn-status-propedit-property-name prop-name) + (setq svn-status-propedit-file-list file-info-list) + (setq svn-status-pre-propedit-window-configuration (current-window-configuration)) + (pop-to-buffer commit-buffer) + ;; If the buffer has been narrowed, `svn-prop-edit-done' will use + ;; only the accessible part. So we need not erase the rest here. + (delete-region (point-min) (point-max)) + (setq default-directory dir) + (insert prop-value) + (svn-status-remove-control-M) + (when new-prop-value + (when (listp new-prop-value) + (if remove-values + (message "Remove prop values %S " new-prop-value) + (message "Adding new prop values %S " new-prop-value)) + (while new-prop-value + (goto-char (point-min)) + (if (re-search-forward (concat "^" (regexp-quote (car new-prop-value)) "$") nil t) + (when remove-values + (kill-whole-line 1)) + (unless remove-values + (goto-char (point-max)) + (when (> (current-column) 0) (insert "\n")) + (insert (car new-prop-value)))) + (setq new-prop-value (cdr new-prop-value))))) + (svn-prop-edit-mode))) + +(defun svn-status-property-set-property (file-info-list prop-name prop-value) + "Set a property on a given file list." + (save-excursion + (set-buffer (get-buffer-create "*svn-property-edit*")) + ;; If the buffer has been narrowed, `svn-prop-edit-do-it' will use + ;; only the accessible part. So we need not erase the rest here. + (delete-region (point-min) (point-max)) + (insert prop-value)) + (setq svn-status-propedit-file-list (svn-status-marked-files)) + (setq svn-status-propedit-property-name prop-name) + (svn-prop-edit-do-it nil) + (svn-status-update)) + + +(defun svn-status-get-directory (line-info) + (let* ((file-name (svn-status-line-info->filename line-info)) + (file-dir (file-name-directory file-name))) + ;;(message "file-dir: %S" file-dir) + (if file-dir + (substring file-dir 0 (- (length file-dir) 1)) + "."))) + +(defun svn-status-get-file-list-per-directory (files) + ;;(message "%S" files) + (let ((dir-list nil) + (i files) + (j) + (dir)) + (while i + (setq dir (svn-status-get-directory (car i))) + (setq j (assoc dir dir-list)) + (if j + (progn + ;;(message "dir already present %S %s" j dir) + (setcdr j (append (cdr j) (list (car i))))) + (setq dir-list (append dir-list (list (list dir (car i)))))) + (setq i (cdr i))) + ;;(message "svn-status-get-file-list-per-directory: %S" dir-list) + dir-list)) + +(defun svn-status-property-ignore-file () + (interactive) + (let ((d-list (svn-status-get-file-list-per-directory (svn-status-marked-files))) + (dir) + (f-info) + (ext-list)) + (while d-list + (setq dir (caar d-list)) + (setq f-info (cdar d-list)) + (setq ext-list (mapcar '(lambda (i) + (svn-status-line-info->filename-nondirectory i)) f-info)) + ;;(message "ignore in dir %s: %S" dir f-info) + (save-window-excursion + (when (y-or-n-p (format "Ignore %S for %s? " ext-list dir)) + (svn-status-property-edit + (list (svn-status-find-info-for-file-name dir)) "svn:ignore" ext-list) + (svn-prop-edit-do-it nil))) ; synchronous + (setq d-list (cdr d-list))) + (svn-status-update))) + +(defun svn-status-property-ignore-file-extension () + (interactive) + (let ((d-list (svn-status-get-file-list-per-directory (svn-status-marked-files))) + (dir) + (f-info) + (ext-list)) + (while d-list + (setq dir (caar d-list)) + (setq f-info (cdar d-list)) + ;;(message "ignore in dir %s: %S" dir f-info) + (setq ext-list nil) + (while f-info + (add-to-list 'ext-list (concat "*." + (file-name-extension + (svn-status-line-info->filename (car f-info))))) + (setq f-info (cdr f-info))) + ;;(message "%S" ext-list) + (save-window-excursion + (when (y-or-n-p (format "Ignore %S for %s? " ext-list dir)) + (svn-status-property-edit + (list (svn-status-find-info-for-file-name dir)) "svn:ignore" + ext-list) + (svn-prop-edit-do-it nil))) + (setq d-list (cdr d-list))) + (svn-status-update))) + +(defun svn-status-property-edit-svn-ignore () + (interactive) + (let* ((line-info (svn-status-get-line-information)) + (dir (if (svn-status-line-info->directory-p line-info) + (svn-status-line-info->filename line-info) + (svn-status-get-directory line-info)))) + (svn-status-property-edit + (list (svn-status-find-info-for-file-name dir)) "svn:ignore") + (message "Edit svn:ignore on %s" dir))) + + +(defun svn-status-property-edit-svn-externals () + (interactive) + (let* ((line-info (svn-status-get-line-information)) + (dir (if (svn-status-line-info->directory-p line-info) + (svn-status-line-info->filename line-info) + (svn-status-get-directory line-info)))) + (svn-status-property-edit + (list (svn-status-find-info-for-file-name dir)) "svn:externals") + (message "Edit svn:externals on %s" dir))) + + +(defun svn-status-property-set-keyword-list () + "Edit the svn:keywords property on the marked files." + (interactive) + ;;(message "Set svn:keywords for %S" (svn-status-marked-file-names)) + (svn-status-property-edit (svn-status-marked-files) "svn:keywords")) + +(defun svn-status-property-set-keyword-id (arg) + "Set/Remove Id from the svn:keywords property. +Normally Id is added to the svn:keywords property. + +When called with the prefix arg -, remove Id from the svn:keywords property." + (interactive "P") + (svn-status-property-edit (svn-status-marked-files) "svn:keywords" '("Id") (eq arg '-)) + (svn-prop-edit-do-it nil)) + +(defun svn-status-property-set-keyword-date (arg) + "Set/Remove Date from the svn:keywords property. +Normally Date is added to the svn:keywords property. + +When called with the prefix arg -, remove Date from the svn:keywords property." + (interactive "P") + (svn-status-property-edit (svn-status-marked-files) "svn:keywords" '("Date") (eq arg '-)) + (svn-prop-edit-do-it nil)) + + +(defun svn-status-property-set-eol-style () + "Edit the svn:eol-style property on the marked files." + (interactive) + (svn-status-property-set-property + (svn-status-marked-files) "svn:eol-style" + (completing-read "Set svn:eol-style for the marked files: " + (mapcar 'list '("native" "CRLF" "LF" "CR")) + nil t))) + +(defun svn-status-property-set-executable (&optional unset) + "Set the svn:executable property on the marked files. +When called with a prefix argument: unset the svn:executable property." + (interactive "P") + (if unset + (progn + (svn-run nil t 'propdel (append (list "propdel" "svn:executable") (svn-status-marked-file-names))) + (message "Unset the svn:executable property for %s" (svn-status-marked-file-names)) + (svn-status-update)) + (svn-status-property-set-property (svn-status-marked-files) "svn:executable" "*"))) + +(defun svn-status-property-set-mime-type () + "Set the svn:mime-type property on the marked files." + (interactive) + (require 'mailcap nil t) + (let ((completion-ignore-case t) + (mime-types (when (fboundp 'mailcap-mime-types) + (mailcap-mime-types)))) + (svn-status-property-set-property + (svn-status-marked-files) "svn:mime-type" + (funcall svn-status-completing-read-function "Set svn:mime-type for the marked files: " + (mapcar (lambda (x) (cons x x)) ; for Emacs 21 + (sort mime-types 'string<)))))) + +;; -------------------------------------------------------------------------------- +;; svn-prop-edit-mode: +;; -------------------------------------------------------------------------------- + +(defvar svn-prop-edit-mode-map () "Keymap used in `svn-prop-edit-mode' buffers.") +(put 'svn-prop-edit-mode-map 'risky-local-variable t) ;for Emacs 20.7 + +(when (not svn-prop-edit-mode-map) + (setq svn-prop-edit-mode-map (make-sparse-keymap)) + (define-key svn-prop-edit-mode-map [(control ?c) (control ?c)] 'svn-prop-edit-done) + (define-key svn-prop-edit-mode-map [(control ?c) (control ?d)] 'svn-prop-edit-svn-diff) + (define-key svn-prop-edit-mode-map [(control ?c) (control ?s)] 'svn-prop-edit-svn-status) + (define-key svn-prop-edit-mode-map [(control ?c) (control ?l)] 'svn-prop-edit-svn-log) + (define-key svn-prop-edit-mode-map [(control ?c) (control ?q)] 'svn-prop-edit-abort)) + +(easy-menu-define svn-prop-edit-mode-menu svn-prop-edit-mode-map +"'svn-prop-edit-mode' menu" + '("SVN-PropEdit" + ["Commit" svn-prop-edit-done t] + ["Show Diff" svn-prop-edit-svn-diff t] + ["Show Status" svn-prop-edit-svn-status t] + ["Show Log" svn-prop-edit-svn-log t] + ["Abort" svn-prop-edit-abort t])) + +(defun svn-prop-edit-mode () + "Major Mode to edit file properties of files under svn control. +Commands: +\\{svn-prop-edit-mode-map}" + (interactive) + (kill-all-local-variables) + (use-local-map svn-prop-edit-mode-map) + (easy-menu-add svn-prop-edit-mode-menu) + (setq major-mode 'svn-prop-edit-mode) + (setq mode-name "svn-prop-edit")) + +(defun svn-prop-edit-abort () + (interactive) + (bury-buffer) + (set-window-configuration svn-status-pre-propedit-window-configuration)) + +(defun svn-prop-edit-done () + (interactive) + (svn-prop-edit-do-it t)) + +(defun svn-prop-edit-do-it (async) + "Run svn propset `svn-status-propedit-property-name' with the content of the +*svn-property-edit* buffer." + (message "svn propset %s on %s" + svn-status-propedit-property-name + (mapcar 'svn-status-line-info->filename svn-status-propedit-file-list)) + (save-excursion + (set-buffer (get-buffer "*svn-property-edit*")) + (when (fboundp 'set-buffer-file-coding-system) + (set-buffer-file-coding-system svn-status-svn-file-coding-system nil)) + (let ((svn-propedit-file-name (concat svn-status-temp-dir "svn-prop-edit.txt" svn-temp-suffix))) + (setq svn-status-temp-file-to-remove (svn-expand-filename-for-remote-access svn-propedit-file-name)) + (write-region (point-min) (point-max) svn-status-temp-file-to-remove nil 1) + (when svn-status-propedit-file-list ; there are files to change properties + (svn-status-create-arg-file svn-status-propedit-file-list) + (setq svn-status-propedit-file-list nil) + (svn-run async t 'propset "propset" + svn-status-propedit-property-name + "--targets" svn-status-temp-arg-file + (when (eq svn-status-svn-file-coding-system 'utf-8) + '("--encoding" "UTF-8")) + "-F" svn-propedit-file-name) + (unless async (svn-status-remove-temp-file-maybe))) + (when svn-status-pre-propedit-window-configuration + (set-window-configuration svn-status-pre-propedit-window-configuration))))) + +(defun svn-prop-edit-svn-diff (arg) + (interactive "P") + (set-buffer svn-status-buffer-name) + ;; Because propedit is not recursive in our use, neither is this diff. + (svn-status-show-svn-diff-internal svn-status-propedit-file-list nil + (if arg :ask "BASE"))) + +(defun svn-prop-edit-svn-log (arg) + (interactive "P") + (set-buffer svn-status-buffer-name) + (svn-status-show-svn-log arg)) + +(defun svn-prop-edit-svn-status () + (interactive) + (pop-to-buffer svn-status-buffer-name) + (other-window 1)) + +;; -------------------------------------------------------------------------------- +;; svn-log-edit-mode: +;; -------------------------------------------------------------------------------- + +(defvar svn-log-edit-mode-map () "Keymap used in `svn-log-edit-mode' buffers.") +(put 'svn-log-edit-mode-map 'risky-local-variable t) ;for Emacs 20.7 + +(defvar svn-log-edit-mode-menu) ;really defined with `easy-menu-define' below. + +(defun svn-log-edit-common-setup () + (set (make-local-variable 'paragraph-start) svn-log-edit-paragraph-start) + (set (make-local-variable 'paragraph-separate) svn-log-edit-paragraph-separate)) + +(if svn-log-edit-use-log-edit-mode + (define-derived-mode svn-log-edit-mode log-edit-mode "svn-log-edit" + "Wrapper around `log-edit-mode' for psvn.el" + (easy-menu-add svn-log-edit-mode-menu) + (setq svn-log-edit-update-log-entry nil) + (set (make-local-variable 'log-edit-callback) 'svn-log-edit-done) + (set (make-local-variable 'log-edit-listfun) 'svn-log-edit-files-to-commit) + (set (make-local-variable 'log-edit-initial-files) (log-edit-files)) + (svn-log-edit-common-setup) + (message "Press %s when you are done editing." + (substitute-command-keys "\\[log-edit-done]")) + ) + (defun svn-log-edit-mode () + "Major Mode to edit svn log messages. +Commands: +\\{svn-log-edit-mode-map}" + (interactive) + (kill-all-local-variables) + (use-local-map svn-log-edit-mode-map) + (easy-menu-add svn-log-edit-mode-menu) + (setq major-mode 'svn-log-edit-mode) + (setq mode-name "svn-log-edit") + (setq svn-log-edit-update-log-entry nil) + (svn-log-edit-common-setup) + (run-hooks 'svn-log-edit-mode-hook))) + +(when (not svn-log-edit-mode-map) + (setq svn-log-edit-mode-map (make-sparse-keymap)) + (unless svn-log-edit-use-log-edit-mode + (define-key svn-log-edit-mode-map (kbd "C-c C-c") 'svn-log-edit-done)) + (define-key svn-log-edit-mode-map (kbd "C-c C-d") 'svn-log-edit-svn-diff) + (define-key svn-log-edit-mode-map (kbd "C-c C-s") 'svn-log-edit-save-message) + (define-key svn-log-edit-mode-map (kbd "C-c C-i") 'svn-log-edit-svn-status) + (define-key svn-log-edit-mode-map (kbd "C-c C-l") 'svn-log-edit-svn-log) + (define-key svn-log-edit-mode-map (kbd "C-c C-?") 'svn-log-edit-show-files-to-commit) + (define-key svn-log-edit-mode-map (kbd "C-c C-z") 'svn-log-edit-erase-edit-buffer) + (define-key svn-log-edit-mode-map (kbd "C-c C-q") 'svn-log-edit-abort)) + +(easy-menu-define svn-log-edit-mode-menu svn-log-edit-mode-map +"'svn-log-edit-mode' menu" + '("SVN-Log" + ["Save to disk" svn-log-edit-save-message t] + ["Commit" svn-log-edit-done t] + ["Show Diff" svn-log-edit-svn-diff t] + ["Show Status" svn-log-edit-svn-status t] + ["Show Log" svn-log-edit-svn-log t] + ["Show files to commit" svn-log-edit-show-files-to-commit t] + ["Erase buffer" svn-log-edit-erase-edit-buffer] + ["Abort" svn-log-edit-abort t])) +(put 'svn-log-edit-mode-menu 'risky-local-variable t) + +(defun svn-log-edit-abort () + (interactive) + (bury-buffer) + (set-window-configuration svn-status-pre-commit-window-configuration)) + +(defun svn-log-edit-done () + "Finish editing the log message and run svn commit." + (interactive) + (svn-status-save-some-buffers) + (let ((svn-logedit-file-name)) + (save-excursion + (set-buffer (get-buffer svn-log-edit-buffer-name)) + (when svn-log-edit-insert-files-to-commit + (svn-log-edit-remove-comment-lines)) + (when (fboundp 'set-buffer-file-coding-system) + (set-buffer-file-coding-system svn-status-svn-file-coding-system nil)) + (when (or svn-log-edit-update-log-entry svn-status-files-to-commit) + (setq svn-log-edit-file-name (concat svn-status-temp-dir "svn-log-edit.txt" svn-temp-suffix)) + (setq svn-status-temp-file-to-remove (svn-expand-filename-for-remote-access svn-log-edit-file-name)) + (write-region (point-min) (point-max) svn-status-temp-file-to-remove nil 1)) + (bury-buffer)) + (if svn-log-edit-update-log-entry + (when (y-or-n-p "Update the log entry? ") + ;; svn propset svn:log --revprop -r11672 -F file + (svn-run nil t 'propset "propset" "svn:log" "--revprop" + (concat "-r" svn-log-edit-update-log-entry) + "-F" svn-log-edit-file-name) + (save-excursion + (set-buffer svn-process-buffer-name) + (message "%s" (buffer-substring (point-min) (- (point-max) 1))))) + (when svn-status-files-to-commit ; there are files to commit + (setq svn-status-operated-on-dot + (and (= 1 (length svn-status-files-to-commit)) + (string= "." (svn-status-line-info->filename (car svn-status-files-to-commit))))) + (svn-status-create-arg-file svn-status-files-to-commit) + (svn-run t t 'commit "commit" + (unless svn-status-recursive-commit "--non-recursive") + "--targets" svn-status-temp-arg-file + "-F" svn-log-edit-file-name + (when (eq svn-status-svn-file-coding-system 'utf-8) + '("--encoding" "UTF-8")) + svn-status-default-commit-arguments)) + (set-window-configuration svn-status-pre-commit-window-configuration) + (message "svn-log editing done")))) + +(defun svn-log-edit-svn-diff (arg) + "Show the diff we are about to commit. +If ARG then show diff between some other version of the selected files." + (interactive "P") + (set-buffer svn-status-buffer-name) ; TODO: is this necessary? + ;; This call is very much like `svn-status-show-svn-diff-for-marked-files' + ;; but uses commit-specific variables instead of the current marks. + (svn-status-show-svn-diff-internal svn-status-files-to-commit + svn-status-recursive-commit + (if arg :ask "BASE"))) + +(defun svn-log-edit-svn-log (arg) + (interactive "P") + (set-buffer svn-status-buffer-name) + (svn-status-show-svn-log arg)) + +(defun svn-log-edit-svn-status () + (interactive) + (pop-to-buffer svn-status-buffer-name) + (other-window 1)) + +(defun svn-log-edit-files-to-commit () + (mapcar 'svn-status-line-info->filename svn-status-files-to-commit)) + +(defun svn-log-edit-show-files-to-commit () + (interactive) + (message "Files to commit%s: %S" + (if svn-status-recursive-commit " recursively" "") + (svn-log-edit-files-to-commit))) + +(defun svn-log-edit-save-message () + "Save the current log message to the file `svn-log-edit-file-name'." + (interactive) + (let ((log-edit-file-name (svn-log-edit-file-name))) + (if (string= buffer-file-name log-edit-file-name) + (save-buffer) + (write-region (point-min) (point-max) log-edit-file-name)))) + +(defun svn-log-edit-erase-edit-buffer () + "Delete everything in the `svn-log-edit-buffer-name' buffer." + (interactive) + (set-buffer svn-log-edit-buffer-name) + (erase-buffer)) + +(defun svn-log-edit-insert-files-to-commit () + (interactive) + (svn-log-edit-remove-comment-lines) + (let ((buf-size (- (point-max) (point-min)))) + (save-excursion + (goto-char (point-min)) + (insert svn-log-edit-header) + (insert "## File(s) to commit" + (if svn-status-recursive-commit " recursively" "") ":\n") + (let ((file-list svn-status-files-to-commit)) + (while file-list + (insert (concat "## " (svn-status-line-info->filename (car file-list)) "\n")) + (setq file-list (cdr file-list))))) + (when (= 0 buf-size) + (goto-char (point-max))))) + +(defun svn-log-edit-remove-comment-lines () + (interactive) + (save-excursion + (goto-char (point-min)) + (flush-lines "^## .*"))) + +(defun svn-file-add-to-changelog (prefix-arg) + "Create a changelog entry for the function at point. +The variable `svn-status-changelog-style' allows to select the used changlog style" + (interactive "P") + (cond ((eq svn-status-changelog-style 'changelog) + (svn-file-add-to-log-changelog-style prefix-arg)) + ((eq svn-status-changelog-style 'svn-dev) + (svn-file-add-to-log-svn-dev-style prefix-arg)) + ((fboundp svn-status-changelog-style) + (funcall svn-status-changelog-style prefix-arg)) + (t + (error "Invalid setting for `svn-status-changelog-style'")))) + +(defun svn-file-add-to-log-changelog-style (curdir) + "Create a changelog entry for the function at point. +`add-change-log-entry-other-window' creates the header information. +If CURDIR, save the log file in the current directory, otherwise in the base directory of this working copy." + (interactive "P") + (add-change-log-entry-other-window nil (svn-log-edit-file-name curdir)) + (svn-log-edit-mode)) + +;; taken from svn-dev.el: svn-log-path-derive +(defun svn-dev-log-path-derive (path) + "Derive a relative directory path for absolute PATH, for a log entry." + (save-match-data + (let ((base (file-name-nondirectory path)) + (chop-spot (string-match + "\\(code/\\)\\|\\(src/\\)\\|\\(projects/\\)" + path))) + (if chop-spot + (progn + (setq path (substring path (match-end 0))) + ;; Kluge for Subversion developers. + (if (string-match "subversion/" path) + (substring path (+ (match-beginning 0) 11)) + path)) + (string-match (expand-file-name "~/") path) + (substring path (match-end 0)))))) + +;; taken from svn-dev.el: svn-log-message +(defun svn-file-add-to-log-svn-dev-style (prefix-arg) + "Add to an in-progress log message, based on context around point. +If PREFIX-ARG is negative, then use basenames only in +log messages, otherwise use full paths. The current defun name is +always used. + +If PREFIX-ARG is a list (e.g. by using C-u), save the log file in +the current directory, otherwise in the base directory of this +working copy. + +If the log message already contains material about this defun, then put +point there, so adding to that material is easy. + +Else if the log message already contains material about this file, put +point there, and push onto the kill ring the defun name with log +message dressing around it, plus the raw defun name, so yank and +yank-next are both useful. + +Else if there is no material about this defun nor file anywhere in the +log message, then put point at the end of the message and insert a new +entry for file with defun. +" + (interactive "P") + (let* ((short-file-names (and (numberp prefix-arg) (< prefix-arg 0))) + (curdir (listp prefix-arg)) + (this-file (if short-file-names + (file-name-nondirectory buffer-file-name) + (svn-dev-log-path-derive buffer-file-name))) + (this-defun (or (add-log-current-defun) + (save-excursion + (save-match-data + (if (eq major-mode 'c-mode) + (progn + (if (fboundp 'c-beginning-of-statement-1) + (c-beginning-of-statement-1) + (c-beginning-of-statement)) + (search-forward "(" nil t) + (forward-char -1) + (forward-sexp -1) + (buffer-substring + (point) + (progn (forward-sexp 1) (point))))))))) + (log-file (svn-log-edit-file-name curdir))) + (find-file log-file) + (goto-char (point-min)) + ;; Strip text properties from strings + (set-text-properties 0 (length this-file) nil this-file) + (set-text-properties 0 (length this-defun) nil this-defun) + ;; If log message for defun already in progress, add to it + (if (and + this-defun ;; we have a defun to work with + (search-forward this-defun nil t) ;; it's in the log msg already + (save-excursion ;; and it's about the same file + (save-match-data + (if (re-search-backward ; Ick, I want a real filename regexp! + "^\\*\\s-+\\([a-zA-Z0-9-_.@=+^$/%!?(){}<>]+\\)" nil t) + (string-equal (match-string 1) this-file) + t)))) + (if (re-search-forward ":" nil t) + (if (looking-at " ") (forward-char 1))) + ;; Else no log message for this defun in progress... + (goto-char (point-min)) + ;; But if log message for file already in progress, add to it. + (if (search-forward this-file nil t) + (progn + (if this-defun (progn + (kill-new (format "(%s): " this-defun)) + (kill-new this-defun))) + (search-forward ")" nil t) + (if (looking-at " ") (forward-char 1))) + ;; Found neither defun nor its file, so create new entry. + (goto-char (point-max)) + (if (not (bolp)) (insert "\n")) + (insert (format "\n* %s (%s): " this-file (or this-defun ""))) + ;; Finally, if no derived defun, put point where the user can + ;; type it themselves. + (if (not this-defun) (forward-char -3)))))) + +;; -------------------------------------------------------------------------------- +;; svn-log-view-mode: +;; -------------------------------------------------------------------------------- + +(defvar svn-log-view-mode-map () "Keymap used in `svn-log-view-mode' buffers.") +(put 'svn-log-view-mode-map 'risky-local-variable t) ;for Emacs 20.7 + +(when (not svn-log-view-mode-map) + (setq svn-log-view-mode-map (make-sparse-keymap)) + (suppress-keymap svn-log-view-mode-map) + (define-key svn-log-view-mode-map (kbd "p") 'svn-log-view-prev) + (define-key svn-log-view-mode-map (kbd "n") 'svn-log-view-next) + (define-key svn-log-view-mode-map (kbd "~") 'svn-log-get-specific-revision) + (define-key svn-log-view-mode-map (kbd "f") 'svn-log-get-specific-revision) + (define-key svn-log-view-mode-map (kbd "E") 'svn-log-ediff-specific-revision) + (define-key svn-log-view-mode-map (kbd "=") 'svn-log-view-diff) + (define-key svn-log-view-mode-map (kbd "#") 'svn-log-mark-partner-revision) + (define-key svn-log-view-mode-map (kbd "x") 'svn-log-exchange-partner-mark-with-point) + (define-key svn-log-view-mode-map (kbd "TAB") 'svn-log-next-link) + (define-key svn-log-view-mode-map [backtab] 'svn-log-prev-link) + (define-key svn-log-view-mode-map (kbd "RET") 'svn-log-find-file-at-point) + (define-key svn-log-view-mode-map (kbd "e") 'svn-log-edit-log-entry) + (define-key svn-log-view-mode-map (kbd "q") 'bury-buffer)) + +(defvar svn-log-view-popup-menu-map () + "Keymap used to show popup menu in `svn-log-view-mode' buffers.") +(put 'svn-log-view-popup-menu-map 'risky-local-variable t) ;for Emacs 20.7 +(when (not svn-log-view-popup-menu-map) + (setq svn-log-view-popup-menu-map (make-sparse-keymap)) + (suppress-keymap svn-log-view-popup-menu-map) + (define-key svn-log-view-popup-menu-map [down-mouse-3] 'svn-log-view-popup-menu)) + +(easy-menu-define svn-log-view-mode-menu svn-log-view-mode-map +"'svn-log-view-mode' menu" + '("SVN-LogView" + ["Show Changeset" svn-log-view-diff t] + ["Ediff file at point" svn-log-ediff-specific-revision t] + ["Find file at point" svn-log-find-file-at-point t] + ["Mark as diff against revision" svn-log-mark-partner-revision t] + ["Get older revision for file at point" svn-log-get-specific-revision t] + ["Edit log message" svn-log-edit-log-entry t])) + +(defun svn-log-view-popup-menu (event) + (interactive "e") + (mouse-set-point event) + (let* ((rev (svn-log-revision-at-point))) + (when rev + (svn-status-face-set-temporary-during-popup + 'svn-status-marked-popup-face (svn-point-at-bol) (svn-point-at-eol) + svn-log-view-mode-menu)))) + +(defvar svn-log-view-font-lock-basic-keywords + '(("^r[0-9]+ .+" (0 `(face font-lock-keyword-face + mouse-face highlight + keymap ,svn-log-view-popup-menu-map)))) + "Basic keywords in `svn-log-view-mode'.") +(put 'svn-log-view-font-basic-lock-keywords 'risky-local-variable t) ;for Emacs 20.7 + +(defvar svn-log-view-font-lock-keywords) +(define-derived-mode svn-log-view-mode fundamental-mode "svn-log-view" + "Major Mode to show the output from svn log. +Commands: +\\{svn-log-view-mode-map} +" + (use-local-map svn-log-view-mode-map) + (easy-menu-add svn-log-view-mode-menu) + (set (make-local-variable 'svn-log-view-font-lock-keywords) svn-log-view-font-lock-basic-keywords) + (dolist (lh svn-log-link-handlers) + (add-to-list 'svn-log-view-font-lock-keywords (gethash lh svn-log-registered-link-handlers))) + (set (make-local-variable 'font-lock-defaults) '(svn-log-view-font-lock-keywords t))) + +(defun svn-log-view-next () + (interactive) + (when (re-search-forward "^r[0-9]+" nil t) + (beginning-of-line 2) + (unless (looking-at "Changed paths:") + (beginning-of-line 1)))) + +(defun svn-log-view-prev () + (interactive) + (when (re-search-backward "^r[0-9]+" nil t 2) + (beginning-of-line 2) + (unless (looking-at "Changed paths:") + (beginning-of-line 1)))) + +(defun svn-log-mark-partner-revision () + "Mark the revision at point to be used as diff against revision." + (interactive) + (let ((start-pos) + (point-at-partner-rev) + (overlay)) + (dolist (ov (overlays-in (point-min) (point-max))) + (when (overlay-get ov 'svn-log-partner-revision) + (setq point-at-partner-rev (and (>= (point) (overlay-start ov)) + (<= (point) (overlay-end ov)))) + (delete-overlay ov))) + (unless point-at-partner-rev + (save-excursion + (when (re-search-backward "^r[0-9]+" nil t 1) + (setq start-pos (point)) + (re-search-forward "^---------------") + (setq overlay (make-overlay start-pos (line-beginning-position 0))) + (overlay-put overlay 'face 'svn-log-partner-highlight-face) + (overlay-put overlay 'svn-log-partner-revision t)))))) + +(defun svn-log-exchange-partner-mark-with-point () + (interactive) + (let ((cur-pos (point)) + (dest-pos)) + (dolist (ov (overlays-in (point-min) (point-max))) + (when (overlay-get ov 'svn-log-partner-revision) + (setq dest-pos (overlay-start ov)))) + (when dest-pos + (svn-log-mark-partner-revision) + (goto-char dest-pos) + (forward-line 3) + (svn-log-view-prev) + (svn-log-view-next)))) + +(defun svn-log-revision-for-diff () + (let ((rev)) + (dolist (ov (overlays-in (point-min) (point-max))) + (when (overlay-get ov 'svn-log-partner-revision) + (save-excursion + (unless (and (>= (point) (overlay-start ov)) + (<= (point) (overlay-end ov))) + (goto-char (overlay-start ov)) + (setq rev (svn-log-revision-at-point)))))) + rev)) + +(defun svn-log-revision-at-point () + (save-excursion + (end-of-line) + (re-search-backward "^r\\([0-9]+\\)") + (svn-match-string-no-properties 1))) + +(defun svn-log-file-name-at-point (respect-checkout-prefix-path) + (let ((full-file-name) + (file-name) + (checkout-prefix-path (if respect-checkout-prefix-path + (url-unhex-string + (svn-status-checkout-prefix-path)) + ""))) + (save-excursion + (beginning-of-line) + (when (looking-at " [MA] /\\(.+\\)$") + (setq full-file-name (svn-match-string-no-properties 1)))) + (when (string= checkout-prefix-path "") + (setq checkout-prefix-path "/")) + (if (null full-file-name) + (progn + (message "No file at point") + nil) + (setq file-name + (if (eq (string-match (regexp-quote (substring checkout-prefix-path 1)) full-file-name) 0) + (substring full-file-name (- (length checkout-prefix-path) (if (string= checkout-prefix-path "/") 1 0))) + full-file-name)) + ;; (message "svn-log-file-name-at-point %s prefix: '%s', full-file-name: %s" file-name checkout-prefix-path full-file-name) + file-name))) + +(defun svn-log-find-file-at-point () + (interactive) + (let ((file-name (svn-log-file-name-at-point t))) + (when file-name + (let ((default-directory (svn-status-base-dir))) + ;;(message "svn-log-file-name-at-point: %s, default-directory: %s" file-name default-directory) + (find-file file-name))))) + +(defun svn-log-next-link () + "Jump to the next external link in this buffer" + (interactive) + (let ((start-pos (if (get-text-property (point) 'link-handler) + (next-single-property-change (point) 'link-handler) + (point)))) + (goto-char (or (next-single-property-change start-pos 'link-handler) (point))))) + +(defun svn-log-prev-link () + "Jump to the previous external link in this buffer" + (interactive) + (let ((start-pos (if (get-text-property (point) 'link-handler) + (previous-single-property-change (point) 'link-handler) + (point)))) + (goto-char (or (previous-single-property-change (or start-pos (point)) 'link-handler) (point))))) + +(defun svn-log-view-diff (arg) + "Show the changeset for a given log entry. +When called with a prefix argument, ask the user for the revision." + (interactive "P") + (svn-status-diff-show-changeset (svn-log-revision-at-point) arg (svn-log-revision-for-diff))) + +(defun svn-log-get-specific-revision () + "Get an older revision of the file at point via svn cat." + (interactive) + ;; (message "%S" (svn-status-make-line-info (svn-log-file-name-at-point t))) + (let ((default-directory (svn-status-base-dir)) + (file-name (svn-log-file-name-at-point t))) + (if file-name + (svn-status-get-specific-revision-internal + (list (svn-status-make-line-info file-name)) + (svn-log-revision-at-point) + nil) + (message "No file at point")))) + +(defun svn-log-ediff-specific-revision (&optional user-confirmation) + "Call ediff for the file at point to view a changeset. +When called with a prefix argument, ask the user for the revision." + (interactive "P") + ;; (message "svn-log-ediff-specific-revision: %s" (svn-log-file-name-at-point t)) + (let* ((cur-buf (current-buffer)) + (diff-rev (svn-log-revision-for-diff)) + (upper-rev (if diff-rev + diff-rev + (svn-log-revision-at-point))) + (lower-rev (if diff-rev + (svn-log-revision-at-point) + (number-to-string (- (string-to-number upper-rev) 1)))) + (file-name (svn-log-file-name-at-point t)) + (default-directory (svn-status-base-dir)) + (upper-rev-file-name) + (lower-rev-file-name) + (rev-arg)) + (when user-confirmation + (setq rev-arg (read-string "Revision for changeset: " (concat lower-rev ":" upper-rev))) + (setq lower-rev (car (split-string rev-arg ":"))) + (setq upper-rev (cadr (split-string rev-arg ":")))) + ;;(message "lower-rev: %s, upper-rev: %s" lower-rev upper-rev) + (setq upper-rev-file-name (when file-name + (cdar (svn-status-get-specific-revision-internal + (list (svn-status-make-line-info file-name)) upper-rev nil)))) + (setq lower-rev-file-name (when file-name + (cdar (svn-status-get-specific-revision-internal + (list (svn-status-make-line-info file-name)) lower-rev nil)))) + ;;(message "%S %S" upper-rev-file-name lower-rev-file-name) + (if file-name + (let* ((ediff-after-quit-destination-buffer cur-buf) + (newer-buffer (find-file-noselect upper-rev-file-name)) + (base-buff (find-file-noselect lower-rev-file-name)) + (svn-transient-buffers (list base-buff newer-buffer)) + (startup-hook '(svn-ediff-startup-hook))) + (ediff-buffers base-buff newer-buffer startup-hook)) + (message "No file at point")))) + +(defun svn-log-edit-log-entry () + "Edit the given log entry." + (interactive) + (let ((rev (svn-log-revision-at-point)) + (log-message)) + (svn-run nil t 'propget-parse "propget" "--revprop" (concat "-r" rev) "svn:log") + (save-excursion + (set-buffer svn-process-buffer-name) + (setq log-message (if (> (point-max) 1) + (buffer-substring (point-min) (- (point-max) 1)) + ""))) + (svn-status-pop-to-commit-buffer) + ;; If the buffer has been narrowed, `svn-log-edit-done' will use + ;; only the accessible part. So we need not erase the rest here. + (delete-region (point-min) (point-max)) + (insert log-message) + (goto-char (point-min)) + (setq svn-log-edit-update-log-entry rev))) + + +;; allow additional hyperlinks in log view buffers +(defvar svn-log-link-keymap () + "Keymap used to resolve links `svn-log-view-mode' buffers.") +(put 'svn-log-link-keymap 'risky-local-variable t) ;for Emacs 20.7 +(when (not svn-log-link-keymap) + (setq svn-log-link-keymap (make-sparse-keymap)) + (suppress-keymap svn-log-link-keymap) + (define-key svn-log-link-keymap [mouse-2] 'svn-log-resolve-mouse-link) + (define-key svn-log-link-keymap (kbd "RET") 'svn-log-resolve-link)) + +(defun svn-log-resolve-mouse-link (event) + (interactive "e") + (mouse-set-point event) + (svn-log-resolve-link)) + +(defun svn-log-resolve-link () + (interactive) + (let* ((point-adjustment (if (not (get-text-property (- (point) 1) 'link-handler)) 1 + (if (not (get-text-property (+ (point) 1) 'link-handler)) -1 0))) + (link-name (buffer-substring-no-properties (previous-single-property-change (+ (point) point-adjustment) 'link-handler) + (next-single-property-change (+ (point) point-adjustment) 'link-handler)))) + ;; (message "svn-log-resolve-link '%s'" link-name) + (funcall (get-text-property (point) 'link-handler) link-name))) + +(defun svn-log-register-link-handler (handler-id link-regexp handler-function) + "Register a link handler for external links in *svn-log* buffers +HANDLER-ID is a symbolic name for this handler. The link handler is active when HANDLER-ID +is registered in `svn-log-link-handlers'. +LINK-REGEXP specifies a regular expression that matches the external link. +HANDLER-FUNCTION is called with the match of LINK-REGEXP when the user clicks at the external link." + (let ((font-lock-desc (list link-regexp '(0 `(face font-lock-function-name-face + mouse-face highlight + link-handler invalid-handler-function + keymap ,svn-log-link-keymap))))) + ;; no idea, how to use handler-function in invalid-handler-function above, so set it here + (setcar (nthcdr 5 (nth 1 (nth 1 (nth 1 font-lock-desc)))) handler-function) + (svn-puthash handler-id font-lock-desc svn-log-registered-link-handlers))) + +;; example: add support for ditrack links and handle them via svn-log-resolve-ditrack +;;(svn-log-register-link-handler 'ditrack-issue "i#[0-9]+" 'svn-log-resolve-ditrack) +;;(defun svn-log-resolve-ditrack (link-name) +;; (interactive) +;; (message "svn-log-resolve-ditrack %s" link-name)) + + +(defun svn-log-resolve-trac-ticket-short (link-name) + "Show the trac ticket specified by LINK-NAME via `svn-trac-browse-ticket'." + (interactive) + (let ((ticket-nr (string-to-number (svn-substring-no-properties link-name 1)))) + (svn-trac-browse-ticket ticket-nr))) + +;; register the out of the box provided link handlers +(svn-log-register-link-handler 'trac-ticket-short "#[0-9]+" 'svn-log-resolve-trac-ticket-short) + +;; the actually used link handlers are specified in svn-log-link-handlers + +;; -------------------------------------------------------------------------------- +;; svn-info-mode +;; -------------------------------------------------------------------------------- +(defvar svn-info-mode-map () "Keymap used in `svn-info-mode' buffers.") +(put 'svn-info-mode-map 'risky-local-variable t) ;for Emacs 20.7 + +(when (not svn-info-mode-map) + (setq svn-info-mode-map (make-sparse-keymap)) + (define-key svn-info-mode-map [?s] 'svn-status-pop-to-status-buffer) + (define-key svn-info-mode-map (kbd "h") 'svn-status-pop-to-partner-buffer) + (define-key svn-info-mode-map (kbd "n") 'next-line) + (define-key svn-info-mode-map (kbd "p") 'previous-line) + (define-key svn-info-mode-map (kbd "RET") 'svn-info-show-context) + (define-key svn-info-mode-map [?q] 'bury-buffer)) + +(defun svn-info-mode () + "Major Mode to view informative output from svn." + (interactive) + (kill-all-local-variables) + (use-local-map svn-info-mode-map) + (setq major-mode 'svn-info-mode) + (setq mode-name "svn-info") + (toggle-read-only 1)) + +(defun svn-info-show-context () + "Show the context for a line in the info buffer. +Currently is the output from the svn update command known." + (interactive) + (cond ((save-excursion + (goto-char (point-max)) + (forward-line -1) + (beginning-of-line) + (looking-at "Updated to revision")) + ;; svn-info contains info from an svn update + (let ((cur-pos (point)) + (file-name (buffer-substring-no-properties + (progn (beginning-of-line) (re-search-forward ".. +") (point)) + (line-end-position))) + (pos)) + (when (eq system-type 'windows-nt) + (setq file-name (replace-regexp-in-string "\\\\" "/" file-name))) + (goto-char cur-pos) + (with-current-buffer svn-status-buffer-name + (setq pos (svn-status-get-file-name-buffer-position file-name))) + (when pos + (svn-status-pop-to-new-partner-buffer svn-status-buffer-name) + (goto-char pos)))))) + +;; -------------------------------------------------------------------------------- +;; svn blame minor mode +;; -------------------------------------------------------------------------------- + +(unless (assq 'svn-blame-mode minor-mode-alist) + (setq minor-mode-alist + (cons (list 'svn-blame-mode " SvnBlame") + minor-mode-alist))) + +(defvar svn-blame-mode-map () "Keymap used in `svn-blame-mode' buffers.") +(put 'svn-blame-mode-map 'risky-local-variable t) ;for Emacs 20.7 + +(when (not svn-blame-mode-map) + (setq svn-blame-mode-map (make-sparse-keymap)) + (define-key svn-blame-mode-map [?s] 'svn-status-pop-to-status-buffer) + (define-key svn-blame-mode-map (kbd "n") 'next-line) + (define-key svn-blame-mode-map (kbd "p") 'previous-line) + (define-key svn-blame-mode-map (kbd "RET") 'svn-blame-open-source-file) + (define-key svn-blame-mode-map (kbd "a") 'svn-blame-highlight-author) + (define-key svn-blame-mode-map (kbd "r") 'svn-blame-highlight-revision) + (define-key svn-blame-mode-map (kbd "=") 'svn-blame-show-changeset) + (define-key svn-blame-mode-map (kbd "l") 'svn-blame-show-log) + (define-key svn-blame-mode-map (kbd "b") 'svn-blame-blame-again) + (define-key svn-blame-mode-map (kbd "s") 'svn-blame-show-statistics) + (define-key svn-blame-mode-map [?q] 'bury-buffer)) + +(easy-menu-define svn-blame-mode-menu svn-blame-mode-map +"svn blame minor mode menu" + '("SvnBlame" + ["Jump to source location" svn-blame-open-source-file t] + ["Show changeset" svn-blame-show-changeset t] + ["Show log" svn-blame-show-log t] + ["Show blame again" svn-blame-blame-again t] + ["Show statistics" svn-blame-show-statistics t] + ["Highlight by author" svn-blame-highlight-author t] + ["Highlight by revision" svn-blame-highlight-revision t])) + +(or (assq 'svn-blame-mode minor-mode-map-alist) + (setq minor-mode-map-alist + (cons (cons 'svn-blame-mode svn-blame-mode-map) minor-mode-map-alist))) + +(make-variable-buffer-local 'svn-blame-mode) + +(defun svn-blame-mode (&optional arg) + "Toggle svn blame minor mode. +With ARG, turn svn blame minor mode on if ARG is positive, off otherwise. + +Key bindings: +\\{svn-blame-mode-map}" + (interactive "P") + (setq svn-blame-mode (if (null arg) + (not svn-blame-mode) + (> (prefix-numeric-value arg) 0))) + (if svn-blame-mode + (progn + (easy-menu-add svn-blame-mode-menu) + (toggle-read-only 1)) + (easy-menu-remove svn-blame-mode-menu)) + (force-mode-line-update)) + +(defun svn-status-activate-blame-mode () + "Activate the svn blame minor in the current buffer. +The current buffer must contain a valid output from svn blame" + (save-excursion + (goto-char (point-min)) + (let ((buffer-read-only nil) + (line (svn-line-number-at-pos)) + (limit (point-max)) + (info-end-col (save-excursion (forward-word 2) (+ (current-column) 1))) + (s) + ov) + ;; remove the old overlays (only for testing) + ;; (dolist (ov (overlays-in (point) limit)) + ;; (when (overlay-get ov 'svn-blame-line-info) + ;; (delete-overlay ov))) + (while (and (not (eobp)) (< (point) limit)) + (setq s (buffer-substring-no-properties (svn-point-at-bol) (+ (svn-point-at-bol) info-end-col))) + (delete-region (svn-point-at-bol) (+ (svn-point-at-bol) info-end-col)) + (setq ov (make-overlay (point) (point))) + (overlay-put ov 'svn-blame-line-info t) + (overlay-put ov 'before-string (propertize s 'face 'svn-status-blame-rev-number-face)) + (overlay-put ov 'rev-info (delete "" (split-string s " "))) + (forward-line) + (setq line (1+ line))))) + (let* ((buf-name (format "*svn-blame: %s <%s>*" + (file-relative-name svn-status-blame-file-name) + svn-status-blame-revision)) + (buffer (get-buffer buf-name))) + (when buffer + (kill-buffer buffer)) + (rename-buffer buf-name)) + ;; use the correct mode for the displayed blame output + (let ((buffer-file-name svn-status-blame-file-name)) + (normal-mode) + (set (make-local-variable 'svn-status-blame-file-name) svn-status-blame-file-name)) + (font-lock-fontify-buffer) + (svn-blame-mode 1)) + +(defun svn-blame-open-source-file () + "Jump to the source file location for the current position in the svn blame buffer" + (interactive) + (let ((src-line-number (svn-line-number-at-pos)) + (src-line-col (current-column))) + (find-file-other-window svn-status-blame-file-name) + (goto-line src-line-number) + (forward-char src-line-col))) + +(defun svn-blame-rev-at-point () + (let ((rev)) + (dolist (ov (overlays-in (svn-point-at-bol) (line-end-position))) + (when (overlay-get ov 'svn-blame-line-info) + (setq rev (car (overlay-get ov 'rev-info))))) + rev)) + +(defun svn-blame-show-changeset (arg) + "Show a diff for the revision at point. +When called with a prefix argument, allow the user to edit the revision." + (interactive "P") + (svn-status-diff-show-changeset (svn-blame-rev-at-point) arg)) + +(defun svn-blame-show-log (arg) + "Show the log for the revision at point. +The output is put into the *svn-log* buffer +The optional prefix argument ARG determines which switches are passed to `svn log': + no prefix --- use whatever is in the list `svn-status-default-log-arguments' + prefix argument of -1: --- use the -q switch (quiet) + prefix argument of 0 --- use no arguments + other prefix arguments: --- use the -v switch (verbose)" + (interactive "P") + (let ((switches (svn-status-svn-log-switches arg)) + (rev (svn-blame-rev-at-point))) + (svn-run t t 'log "log" "--revision" rev switches))) + +(defun svn-blame-highlight-line-maybe (compare-func) + (let ((reference-value) + (is-highlighted) + (consider-this-line) + (hl-ov)) + (dolist (ov (overlays-in (svn-point-at-bol) (line-end-position))) + (when (overlay-get ov 'svn-blame-line-info) + (setq reference-value (funcall compare-func ov))) + (when (overlay-get ov 'svn-blame-highlighted) + (setq is-highlighted t))) + (save-excursion + (goto-char (point-min)) + (while (not (eobp)) + (setq consider-this-line nil) + (dolist (ov (overlays-in (svn-point-at-bol) (line-end-position))) + (when (overlay-get ov 'svn-blame-line-info) + (when (string= reference-value (funcall compare-func ov)) + (setq consider-this-line t)))) + (when consider-this-line + (dolist (ov (overlays-in (svn-point-at-bol) (line-end-position))) + (when (and (overlay-get ov 'svn-blame-highlighted) is-highlighted) + (delete-overlay ov)) + (unless is-highlighted + (setq hl-ov (make-overlay (svn-point-at-bol) (line-end-position))) + (overlay-put hl-ov 'svn-blame-highlighted t) + (overlay-put hl-ov 'face 'svn-status-blame-highlight-face)))) + (forward-line))))) + +(defun svn-blame-show-statistics () + "Show statistics for the current blame buffer." + (interactive) + (let ((author-map (make-hash-table :test 'equal)) + (revision-map (make-hash-table :test 'equal)) + (rev-info) + (author-list) + (author) + (revision-list) + (revision)) + (save-excursion + (goto-char (point-min)) + (while (not (eobp)) + (dolist (ov (overlays-in (svn-point-at-bol) (line-end-position))) + (when (overlay-get ov 'svn-blame-line-info) + (setq rev-info (overlay-get ov 'rev-info)) + (setq author (cadr rev-info)) + (setq revision (string-to-number (car rev-info))) + (svn-puthash author (+ (gethash author author-map 0) 1) author-map) + (svn-puthash revision (+ (gethash revision revision-map 0) 1) revision-map))) + (forward-line)) + (maphash '(lambda (key value) (add-to-list 'author-list (list key value))) author-map) + (maphash '(lambda (key value) (add-to-list 'revision-list (list key value))) revision-map) + (pop-to-buffer (get-buffer-create (replace-regexp-in-string "svn-blame:" "svn-blame-statistics:" (buffer-name)))) + (erase-buffer) + (insert (propertize "Authors:\n" 'face 'font-lock-function-name-face)) + (dolist (line (sort author-list '(lambda (v1 v2) (> (cadr v1) (cadr v2))))) + (insert (format "%s: %s line%s\n" (car line) (cadr line) (if (eq (cadr line) 1) "" "s")))) + (insert (propertize "\nRevisions:\n" 'face 'font-lock-function-name-face)) + (dolist (line (sort revision-list '(lambda (v1 v2) (< (car v1) (car v2))))) + (insert (format "%s: %s line%s\n" (car line) (cadr line) (if (eq (cadr line) 1) "" "s")))) + (goto-char (point-min))))) + +(defun svn-blame-highlight-author-field (ov) + (cadr (overlay-get ov 'rev-info))) + +(defun svn-blame-highlight-author () + "(Un)Highlight all lines with the same author." + (interactive) + (svn-blame-highlight-line-maybe 'svn-blame-highlight-author-field)) + +(defun svn-blame-highlight-revision-field (ov) + (car (overlay-get ov 'rev-info))) + +(defun svn-blame-highlight-revision () + "(Un)Highlight all lines with the same revision." + (interactive) + (svn-blame-highlight-line-maybe 'svn-blame-highlight-revision-field)) + +;; -------------------------------------------------------------------------------- +;; svn-process-mode +;; -------------------------------------------------------------------------------- +(defvar svn-process-mode-map () "Keymap used in `svn-process-mode' buffers.") +(put 'svn-process-mode-map 'risky-local-variable t) ;for Emacs 20.7 + +(when (not svn-process-mode-map) + (setq svn-process-mode-map (make-sparse-keymap)) + (define-key svn-process-mode-map (kbd "RET") 'svn-process-send-string-and-newline) + (define-key svn-process-mode-map [?s] 'svn-process-send-string) + (define-key svn-process-mode-map [?q] 'bury-buffer)) + +(easy-menu-define svn-process-mode-menu svn-process-mode-map +"'svn-process-mode' menu" + '("SvnProcess" + ["Send line to process" svn-process-send-string-and-newline t] + ["Send raw string to process" svn-process-send-string t] + ["Bury process buffer" bury-buffer t])) + +(defun svn-process-mode () + "Major Mode to view process output from svn. + +You can send a new line terminated string to the process via \\[svn-process-send-string-and-newline] +You can send raw data to the process via \\[svn-process-send-string]." + (interactive) + (kill-all-local-variables) + (use-local-map svn-process-mode-map) + (easy-menu-add svn-log-view-mode-menu) + (setq major-mode 'svn-process-mode) + (setq mode-name "svn-process")) + +;; -------------------------------------------------------------------------------- +;; svn status persistent options +;; -------------------------------------------------------------------------------- + +(defun svn-status-repo-for-path (directory) + "Find the repository root for DIRECTORY." + (let ((old-process-default-dir)) + (with-current-buffer (get-buffer-create svn-process-buffer-name) + (setq old-process-default-dir default-directory) + (setq default-directory directory)) ;; update the default-directory for the *svn-process* buffer + (svn-status-parse-info t) + (or (plist-get svn-status-base-info :repository-root) + (if (plist-get svn-status-base-info :repository-uuid) + (concat "Svn Repo UUID: " (plist-get svn-status-base-info :repository-uuid)) + (message "psvn.el: Detected an old svn working copy in '%s'. Please check it out again to get a 'Repository Root' entry in the svn info output." + default-directory))))) + +(defun svn-status-base-dir (&optional start-directory) + "Find the svn root directory for the current working copy. +Return nil, if not in a svn working copy." + (let* ((start-dir (expand-file-name (or start-directory default-directory))) + (base-dir (gethash start-dir svn-status-base-dir-cache 'not-found))) + ;;(message "svn-status-base-dir: %S %S" start-dir base-dir) + (if (not (eq base-dir 'not-found)) + base-dir + ;; (message "calculating base-dir for %s" start-dir) + (svn-compute-svn-client-version) + ;; (message "repository-root: %s start-dir: %s" repository-root start-dir) + (cond + ((and (<= (car svn-client-version) 1) (< (cadr svn-client-version) 3)) + (setq base-dir (svn-status-base-dir-for-ancient-svn-client start-dir))) ;; svn version < 1.3 + ((and (<= (car svn-client-version) 1) (< (cadr svn-client-version) 7)) + (setq base-dir (svn-status-base-dir-for-old-svn-client start-dir))) ;; svn version < 1.7 + (t + (setq base-dir (svn-status-base-dir-1 start-dir)))) + (when base-dir + (svn-puthash start-dir base-dir svn-status-base-dir-cache)) + (svn-status-message 7 "svn-status-base-dir %s => %s" start-dir base-dir) + base-dir))) + +(defun svn-status-base-dir-1 (&optional start-directory) + "Find the svn root directory for the current working copy. +Return nil, if not in a svn working copy. +This function is used for svn clients version 1.7 and up." + (let ((default-directory (if start-directory + (expand-file-name start-directory) + (symbol-value 'default-directory))) + parent + wc-root) + (when (svn-version-controlled-dir-p default-directory) + (svn-status-parse-info t) + (setq wc-root (file-name-as-directory (plist-get svn-status-base-info :working-copy-root-path))) + (when wc-root + ;; traversing up the hierarchy shortens the path name. Stop if + ;; it doesn't, e.g we reached / already. + (setq parent (expand-file-name (concat wc-root ".."))) + (or (and (< (length parent) (length wc-root)) + (svn-status-base-dir-1 (expand-file-name (concat wc-root "..")))) + wc-root))))) + +(defun svn-status-base-dir-for-old-svn-client (&optional start-directory) + "Find the svn root directory for the current working copy. +Return nil, if not in a svn working copy. +This function is used for svn clients version 1.6 and below." + (let* ((base-dir (expand-file-name (or start-directory default-directory))) + (repository-root (svn-status-repo-for-path base-dir)) + (dot-svn-dir (concat base-dir (svn-wc-adm-dir-name))) + (in-tree (and repository-root (file-exists-p dot-svn-dir))) + (dir-below (expand-file-name base-dir))) + (while (when (and dir-below (file-exists-p dot-svn-dir)) + (setq base-dir (file-name-directory dot-svn-dir)) + (string-match "\\(.+/\\).+/" dir-below) + (setq dir-below + (and (string-match "\\(.*/\\)[^/]+/" dir-below) + (match-string 1 dir-below))) + ;; (message "base-dir: %s, dir-below: %s, dot-svn-dir: %s in-tree: %s" base-dir dir-below dot-svn-dir in-tree) + (when dir-below + (if (string= (svn-status-repo-for-path dir-below) repository-root) + (setq dot-svn-dir (concat dir-below (svn-wc-adm-dir-name))) + (setq dir-below nil))))) + (and in-tree base-dir))) + +(defun svn-status-base-dir-for-ancient-svn-client (&optional start-directory) + "Find the svn root directory for the current working copy. +Return nil, if not in a svn working copy. +This function is used for svn clients version 1.2 and below." + (let* ((base-dir (expand-file-name (or start-directory default-directory))) + (dot-svn-dir (concat base-dir (svn-wc-adm-dir-name))) + (in-tree (file-exists-p dot-svn-dir)) + (dir-below (expand-file-name default-directory))) + (while (when (and dir-below (file-exists-p dot-svn-dir)) + (setq base-dir (file-name-directory dot-svn-dir)) + (string-match "\\(.+/\\).+/" dir-below) + (setq dir-below + (and (string-match "\\(.*/\\)[^/]+/" dir-below) + (match-string 1 dir-below))) + (setq dot-svn-dir (concat dir-below (svn-wc-adm-dir-name))))) + (and in-tree base-dir))) + +(defun svn-status-save-state () + "Save psvn persistent options for this working copy to a file." + (interactive) + (let ((buf (find-file (concat (svn-status-base-dir) "++psvn.state")))) + (erase-buffer) ;Widen, because we'll save the whole buffer. + ;; TO CHECK: why is svn-status-options a global variable?? + (setq svn-status-options + (list + (list "svn-trac-project-root" svn-trac-project-root) + (list "sort-status-buffer" svn-status-sort-status-buffer) + (list "elide-list" svn-status-elided-list) + (list "module-name" svn-status-module-name) + (list "branch-list" svn-status-branch-list) + (list "changelog-style" svn-status-changelog-style) + )) + (insert (pp-to-string svn-status-options)) + (save-buffer) + (kill-buffer buf))) + +(defun svn-status-load-state (&optional no-error) + "Load psvn persistent options for this working copy from a file." + (interactive) + (let ((file (concat (svn-status-base-dir) "++psvn.state"))) + (if (file-readable-p file) + (with-temp-buffer + (insert-file-contents file) + (setq svn-status-options (read (current-buffer))) + (setq svn-status-sort-status-buffer + (nth 1 (assoc "sort-status-buffer" svn-status-options))) + (setq svn-trac-project-root + (nth 1 (assoc "svn-trac-project-root" svn-status-options))) + (setq svn-status-elided-list + (nth 1 (assoc "elide-list" svn-status-options))) + (setq svn-status-module-name + (nth 1 (assoc "module-name" svn-status-options))) + (setq svn-status-branch-list + (nth 1 (assoc "branch-list" svn-status-options))) + (setq svn-status-changelog-style + (nth 1 (assoc "changelog-style" svn-status-options))) + (when (and (interactive-p) svn-status-elided-list (svn-status-apply-elide-list))) + (message "psvn.el: loaded %s" file)) + (if no-error + (setq svn-trac-project-root nil + svn-status-elided-list nil + svn-status-module-name nil + svn-status-branch-list nil + svn-status-changelog-style 'changelog) + (error "psvn.el: %s is not readable." file))))) + +(defun svn-status-toggle-sort-status-buffer () + "Toggle sorting of the *svn-status* buffer. + +If you turn off sorting, you can speed up \\[svn-status]. However, +the buffer is not correctly sorted then. This function will be +removed again, when a faster parsing and display routine for +`svn-status' is available." + (interactive) + (setq svn-status-sort-status-buffer (not svn-status-sort-status-buffer)) + (message "The %s buffer will %sbe sorted." svn-status-buffer-name + (if svn-status-sort-status-buffer "" "not "))) + +(defun svn-status-toggle-svn-verbose-flag () + "Toggle `svn-status-verbose'. " + (interactive) + (setq svn-status-verbose (not svn-status-verbose)) + (message "svn status calls will %suse the -v flag." (if svn-status-verbose "" "not "))) + +(defun svn-status-toggle-display-full-path () + "Toggle displaying the full path in the `svn-status-buffer-name' buffer" + (interactive) + (setq svn-status-display-full-path (not svn-status-display-full-path)) + (message "The %s buffer will%s use full path names." svn-status-buffer-name + (if svn-status-display-full-path "" " not")) + (svn-status-update-buffer)) + +(defun svn-status-set-trac-project-root () + (interactive) + (setq svn-trac-project-root + (read-string "Trac project root (e.g.: http://projects.edgewall.com/trac/): " + svn-trac-project-root)) + (when (yes-or-no-p "Save the new setting for svn-trac-project-root to disk? ") + (svn-status-save-state))) + +(defun svn-status-set-module-name () + "Interactively set `svn-status-module-name'." + (interactive) + (setq svn-status-module-name + (read-string "Short Unit Name (e.g.: MyProject): " + svn-status-module-name)) + (when (yes-or-no-p "Save the new setting for svn-status-module-name to disk? ") + (svn-status-save-state))) + +(defun svn-status-set-changelog-style () + "Interactively set `svn-status-changelog-style'." + (interactive) + (setq svn-status-changelog-style + (intern (funcall svn-status-completing-read-function "svn-status on directory: " '("changelog" "svn-dev" "other")))) + (when (string= svn-status-changelog-style 'other) + (setq svn-status-changelog-style (car (find-function-read)))) + (when (yes-or-no-p "Save the new setting for svn-status-changelog-style to disk? ") + (svn-status-save-state))) + +(defun svn-status-set-branch-list () + "Interactively set `svn-status-branch-list'." + (interactive) + (setq svn-status-branch-list + (split-string (read-string "Branch list: " + (mapconcat 'identity svn-status-branch-list " ")))) + (when (yes-or-no-p "Save the new setting for svn-status-branch-list to disk? ") + (svn-status-save-state))) + +(defun svn-browse-url (url) + "Call `browse-url', using `svn-browse-url-function'." + (let ((browse-url-browser-function (or svn-browse-url-function + browse-url-browser-function))) + (browse-url url))) + +;; -------------------------------------------------------------------------------- +;; svn status trac integration +;; -------------------------------------------------------------------------------- +(defun svn-trac-browse-wiki () + "Open the trac wiki view for the current svn repository." + (interactive) + (unless svn-trac-project-root + (svn-status-set-trac-project-root)) + (svn-browse-url (concat svn-trac-project-root "wiki"))) + +(defun svn-trac-browse-timeline () + "Open the trac timeline view for the current svn repository." + (interactive) + (unless svn-trac-project-root + (svn-status-set-trac-project-root)) + (svn-browse-url (concat svn-trac-project-root "timeline"))) + +(defun svn-trac-browse-roadmap () + "Open the trac roadmap view for the current svn repository." + (interactive) + (unless svn-trac-project-root + (svn-status-set-trac-project-root)) + (svn-browse-url (concat svn-trac-project-root "roadmap"))) + +(defun svn-trac-browse-source () + "Open the trac source browser for the current svn repository." + (interactive) + (unless svn-trac-project-root + (svn-status-set-trac-project-root)) + (svn-browse-url (concat svn-trac-project-root "browser"))) + +(defun svn-trac-browse-report (arg) + "Open the trac report view for the current svn repository. +When called with a prefix argument, display the given report number." + (interactive "P") + (unless svn-trac-project-root + (svn-status-set-trac-project-root)) + (svn-browse-url (concat svn-trac-project-root "report" (if (numberp arg) (format "/%s" arg) "")))) + +(defun svn-trac-browse-changeset (changeset-nr) + "Show a changeset in the trac issue tracker." + (interactive (list (read-number "Browse changeset number: " (number-at-point)))) + (unless svn-trac-project-root + (svn-status-set-trac-project-root)) + (svn-browse-url (concat svn-trac-project-root "changeset/" (number-to-string changeset-nr)))) + +(defun svn-trac-browse-ticket (ticket-nr) + "Show a ticket in the trac issue tracker." + (interactive (list (read-number "Browse ticket number: " (number-at-point)))) + (unless svn-trac-project-root + (svn-status-set-trac-project-root)) + (svn-browse-url (concat svn-trac-project-root "ticket/" (number-to-string ticket-nr)))) + +;;;------------------------------------------------------------ +;;; resolve conflicts using ediff +;;;------------------------------------------------------------ +(defun svn-resolve-conflicts-ediff (&optional name-A name-B) + "Invoke ediff to resolve conflicts in the current buffer. +The conflicts must be marked with rcsmerge conflict markers." + (interactive) + (let* ((found nil) + (file-name (file-name-nondirectory buffer-file-name)) + (your-buffer (generate-new-buffer + (concat "*" file-name + " " (or name-A "WORKFILE") "*"))) + (other-buffer (generate-new-buffer + (concat "*" file-name + " " (or name-B "CHECKED-IN") "*"))) + (result-buffer (current-buffer))) + (save-excursion + (set-buffer your-buffer) + (erase-buffer) + (insert-buffer-substring result-buffer) + (goto-char (point-min)) + (while (re-search-forward "^<<<<<<< .\\(mine\\|working\\)\n" nil t) + (setq found t) + (replace-match "") + (if (not (re-search-forward "^=======\n" nil t)) + (error "Malformed conflict marker")) + (replace-match "") + (let ((start (point))) + (if (not (re-search-forward "^>>>>>>> .\\(r[0-9]+\\|merge.*\\)\n" nil t)) + (error "Malformed conflict marker")) + (delete-region start (point)))) + (if (not found) + (progn + (kill-buffer your-buffer) + (kill-buffer other-buffer) + (error "No conflict markers found"))) + (set-buffer other-buffer) + (erase-buffer) + (insert-buffer-substring result-buffer) + (goto-char (point-min)) + (while (re-search-forward "^<<<<<<< .\\(mine\\|working\\)\n" nil t) + (let ((start (match-beginning 0))) + (if (not (re-search-forward "^=======\n" nil t)) + (error "Malformed conflict marker")) + (delete-region start (point)) + (if (not (re-search-forward "^>>>>>>> .\\(r[0-9]+\\|merge.*\\)\n" nil t)) + (error "Malformed conflict marker")) + (replace-match ""))) + (let ((config (current-window-configuration)) + (ediff-default-variant 'default-B)) + + ;; Fire up ediff. + + (set-buffer (ediff-merge-buffers your-buffer other-buffer)) + + ;; Ediff is now set up, and we are in the control buffer. + ;; Do a few further adjustments and take precautions for exit. + + (make-local-variable 'svn-ediff-windows) + (setq svn-ediff-windows config) + (make-local-variable 'svn-ediff-result) + (setq svn-ediff-result result-buffer) + (make-local-variable 'ediff-quit-hook) + (setq ediff-quit-hook + (lambda () + (let ((buffer-A ediff-buffer-A) + (buffer-B ediff-buffer-B) + (buffer-C ediff-buffer-C) + (result svn-ediff-result) + (windows svn-ediff-windows)) + (ediff-cleanup-mess) + (set-buffer result) + (erase-buffer) + (insert-buffer-substring buffer-C) + (kill-buffer buffer-A) + (kill-buffer buffer-B) + (kill-buffer buffer-C) + (set-window-configuration windows) + (message "Conflict resolution finished; you may save the buffer")))) + (message "Please resolve conflicts now; exit ediff when done") + nil)))) + +(defun svn-resolve-conflicts (filename) + (let ((buff (find-file-noselect filename))) + (if buff + (progn (switch-to-buffer buff) + (svn-resolve-conflicts-ediff)) + (error "can not open file %s" filename)))) + +(defun svn-status-resolve-conflicts () + "Resolve conflict in the selected file" + (interactive) + (let ((file-info (svn-status-get-line-information))) + (or (and file-info + (= ?C (svn-status-line-info->filemark file-info)) + (svn-resolve-conflicts + (svn-status-line-info->full-path file-info))) + (error "can not resolve conflicts at this point")))) + + +;; -------------------------------------------------------------------------------- +;; Working with branches +;; -------------------------------------------------------------------------------- + +(defun svn-branch-select (&optional prompt) + "Select a branch interactively from `svn-status-branch-list'" + (interactive) + (unless prompt + (setq prompt "Select branch: ")) + (let* ((branch (funcall svn-status-completing-read-function prompt svn-status-branch-list)) + (directory) + (base-url)) + (when (string-match "#\\(1#\\)?\\(.+\\)" branch) + (setq directory (match-string 2 branch)) + (setq base-url (concat (svn-status-base-info->repository-root) "/" directory)) + (save-match-data + (svn-status-parse-info t)) + (if (eq (length (match-string 1 branch)) 0) + (setq branch base-url) + (let ((svn-status-branch-list (svn-status-ls base-url t))) + (setq branch (concat (svn-status-base-info->repository-root) "/" + directory "/" + (svn-branch-select (format "Select branch from '%s': " directory))))))) + branch)) + +(defun svn-branch-diff (branch1 branch2) + "Show the diff between two svn repository urls. +When called interactively, use `svn-branch-select' to choose two branches from `svn-status-branch-list'." + (interactive + (let* ((branch1 (svn-branch-select "svn diff branch1: ")) + (branch2 (svn-branch-select (format "svn diff %s against: " branch1)))) + (list branch1 branch2))) + (svn-run t t 'diff "diff" svn-status-default-diff-arguments branch1 branch2)) + +;; -------------------------------------------------------------------------------- +;; svnadmin interface +;; -------------------------------------------------------------------------------- +(defun svn-admin-create (dir) + "Run svnadmin create DIR." + (interactive (list (expand-file-name + (svn-read-directory-name "Create a svn repository at: " + svn-admin-default-create-directory nil nil)))) + (shell-command-to-string (concat "svnadmin create " dir)) + (setq svn-admin-last-repository-dir (concat "file://" dir)) + (message "Svn repository created at %s" dir) + (run-hooks 'svn-admin-create-hook)) + +;; - Import an empty directory +;; cd to an empty directory +;; svn import -m "Initial import" . file:///home/stefan/svn_repos/WaldiConfig/trunk +(defun svn-admin-create-trunk-directory () + "Import an empty trunk directory to `svn-admin-last-repository-dir'. +Set `svn-admin-last-repository-dir' to the new created trunk url." + (interactive) + (let ((empty-temp-dir-name (make-temp-name svn-status-temp-dir))) + (make-directory empty-temp-dir-name t) + (setq svn-admin-last-repository-dir (concat svn-admin-last-repository-dir "/trunk")) + (svn-run nil t 'import "import" "-m" "Created trunk directory" + empty-temp-dir-name svn-admin-last-repository-dir) + (delete-directory empty-temp-dir-name))) + +(defun svn-admin-start-import () + "Start to import the current working directory in a subversion repository. +The user is asked to perform the following two steps: +1. Create a local repository +2. Add a trunk directory to that repository + +After that step the empty base directory (either the root directory or +the trunk directory of the selected repository) is checked out in the current +working directory." + (interactive) + (if (y-or-n-p "Create local repository? ") + (progn + (call-interactively 'svn-admin-create) + (when (y-or-n-p "Add a trunk directory? ") + (svn-admin-create-trunk-directory))) + (setq svn-admin-last-repository-dir (read-string "Repository Url: "))) + (svn-checkout svn-admin-last-repository-dir ".")) + +;; -------------------------------------------------------------------------------- +;; svn status profiling +;; -------------------------------------------------------------------------------- +;;; Note about profiling psvn: +;; (load-library "elp") +;; M-x elp-reset-all +;; (elp-instrument-package "svn-") +;; M-x svn-status +;; M-x elp-results + +(defun svn-status-elp-init () + (interactive) + (require 'elp) + (elp-reset-all) + (elp-instrument-package "svn-") + (message "Run the desired svn command (e.g. M-x svn-status), then use M-x elp-results.")) + +(defun svn-status-last-commands (&optional string-prefix) + "Return a string with the last executed svn commands" + (interactive) + (unless string-prefix + (setq string-prefix "")) + (with-output-to-string + (dolist (e (ring-elements svn-last-cmd-ring)) + (princ (format "%s%s: svn %s <%s>\n" string-prefix (nth 0 e) (mapconcat 'concat (nth 1 e) " ") (nth 2 e))) + (when (nth 3 e) + (princ (format "%s\n" string-prefix)) + (princ (nth 3 e)) + (princ (format "%s\n" string-prefix)))))) + +;; -------------------------------------------------------------------------------- +;; reporting bugs +;; -------------------------------------------------------------------------------- +(defun svn-insert-indented-lines (text) + "Helper function to insert TEXT, indented by two characters." + (dolist (line (split-string text "\n")) + (insert (format " %s\n" line)))) + +(defun svn-prepare-bug-report () + "Create the buffer *psvn-bug-report*. This buffer can be useful to debug problems with psvn.el" + (interactive) + (let* ((last-output-buffer-name (or svn-status-last-output-buffer-name svn-process-buffer-name)) + (last-svn-cmd-output (with-current-buffer last-output-buffer-name + (buffer-substring-no-properties (point-min) (point-max))))) + (switch-to-buffer "*psvn-bug-report*") + (delete-region (point-min) (point-max)) + (insert "This buffer holds some debug informations for psvn.el\n") + (insert "Please enter a description of the observed and the wanted behaviour\n") + (insert "and send it to the author (stefan@xsteve.at) to allow easier debugging\n\n") + (insert "Revisions:\n") + (svn-insert-indented-lines (svn-status-version)) + (insert "Language environment:\n") + (dolist (elem (svn-process-environment)) + (when (member (car (split-string elem "=")) '("LC_MESSAGES" "LC_ALL" "LANG")) + (insert (format " %s\n" elem)))) + (when svn-process-handle-error-msg + (insert "\nsvn client error message:\n") + (svn-insert-indented-lines svn-process-handle-error-msg)) + (insert "\nLast svn commands:\n") + (svn-insert-indented-lines (svn-status-last-commands)) + (insert (format "\nContent of the <%s> buffer:\n" last-output-buffer-name)) + (svn-insert-indented-lines last-svn-cmd-output) + (goto-char (point-min)))) + +;; -------------------------------------------------------------------------------- +;; Make it easier to reload psvn, if a distribution has an older version +;; Just add the following to your .emacs: +;; (svn-prepare-for-reload) +;; (load "/path/to/psvn.el") + +;; Note the above will only work, if the loaded psvn.el has already the +;; function svn-prepare-for-reload +;; If this is not the case, do the following: +;; (load "/path/to/psvn.el");;make svn-prepare-for-reload available +;; (svn-prepare-for-reload) +;; (load "/path/to/psvn.el");; update the keybindings +;; -------------------------------------------------------------------------------- + +(defvar svn-prepare-for-reload-dont-touch-list '() "A list of variables that should not be touched by `svn-prepare-for-reload'") +(defvar svn-prepare-for-reload-variables-list '(svn-global-keymap svn-status-diff-mode-map svn-global-trac-map svn-status-mode-map + svn-status-mode-property-map svn-status-mode-extension-map + svn-status-mode-options-map svn-status-mode-trac-map svn-status-mode-branch-map + svn-log-edit-mode-map svn-log-view-mode-map + svn-log-view-popup-menu-map svn-info-mode-map svn-blame-mode-map svn-process-mode-map) + "A list of variables that should be set to nil via M-x `svn-prepare-for-reload'") +(defun svn-prepare-for-reload () + "This function resets some psvn.el variables to nil. +It makes reloading a newer version of psvn.el easier, if for example the used +GNU/Linux distribution uses an older version. + +The variables specified in `svn-prepare-for-reload-variables-list' will be reseted by this function. + +A variable will keep its value, if it is specified in `svn-prepare-for-reload-dont-touch-list'." + (interactive) + (dolist (var svn-prepare-for-reload-variables-list) + (unless (member var svn-prepare-for-reload-dont-touch-list) + (message (format "Resetting value of %s to nil" var))) + (set var nil))) + +(provide 'psvn) + +;; Local Variables: +;; indent-tabs-mode: nil +;; time-stamp-pattern: "300/(defconst svn-psvn-revision \"%:y-%02m-%02d, %02H:%02M:%02S\" \"The revision date of psvn.\")$" +;; End: +;;; psvn.el ends here diff --git a/SOURCES/subversion-1.10.0-pie.patch b/SOURCES/subversion-1.10.0-pie.patch new file mode 100644 index 0000000..47901a1 --- /dev/null +++ b/SOURCES/subversion-1.10.0-pie.patch @@ -0,0 +1,72 @@ + +Link executables using -pie, link test executables using -no-install. + +diff -uap subversion-1.10.0/build.conf.pie subversion-1.10.0/build.conf +--- subversion-1.10.0/build.conf.pie ++++ subversion-1.10.0/build.conf +@@ -783,6 +783,7 @@ + libs = libsvn_repos libsvn_fs libsvn_delta libsvn_subr aprutil apriconv apr + msvc-static = yes + undefined-lib-symbols = yes ++link-cmd = $(LINK_TEST_LIB) + + # ---------------------------------------------------------------------------- + # Tests for libsvn_fs_base +diff -uap subversion-1.10.0/build/generator/gen_base.py.pie subversion-1.10.0/build/generator/gen_base.py +--- subversion-1.10.0/build/generator/gen_base.py.pie ++++ subversion-1.10.0/build/generator/gen_base.py +@@ -599,7 +599,7 @@ + self.install = options.get('install') + self.compile_cmd = options.get('compile-cmd') + self.sources = options.get('sources', '*.c *.cpp') +- self.link_cmd = options.get('link-cmd', '$(LINK)') ++ self.link_cmd = options.get('link-cmd', '$(LINK_LIB)') + + self.external_lib = options.get('external-lib') + self.external_project = options.get('external-project') +@@ -659,6 +659,17 @@ + + self.msvc_force_static = options.get('msvc-force-static') == 'yes' + ++ if self.install in ['test', 'bdb-test', 'sub-test', ]: ++ self.link_cmd = '$(LINK_TEST)' ++ elif self.install in ['cxxhl-tests', ]: ++ self.link_cmd = '$(LINK_TEST_CXX)' ++ elif self.link_cmd == '$(LINK_LIB)': ++ # Over-ride the default for TargetLinked. ++ self.link_cmd = '$(LINK_EXE)' ++ else: ++ raise GenError('ERROR: Unknown executable link type for ' + self.name + \ ++ ': ' + self.link_cmd + ' (' + self.install + ')') ++ + def add_dependencies(self): + TargetLinked.add_dependencies(self) + +diff -uap subversion-1.10.0/Makefile.in.pie subversion-1.10.0/Makefile.in +--- subversion-1.10.0/Makefile.in.pie ++++ subversion-1.10.0/Makefile.in +@@ -268,6 +268,11 @@ + LINK_LIB = $(LINK) $(LT_SO_VERSION) -rpath $(libdir) + LINK_CXX = $(LIBTOOL) $(LTCXXFLAGS) --mode=link $(CXX) $(LT_LDFLAGS) $(CXXFLAGS) $(LDFLAGS) + LINK_CXX_LIB = $(LINK_CXX) $(LT_SO_VERSION) -rpath $(libdir) ++LINK_TEST = $(LINK) -no-install ++LINK_TEST_LIB = $(LINK) -avoid-version ++LINK_TEST_CXX_LIB = $(LINK_CXX) -avoid-version ++LINK_EXE = $(LINK) -pie ++LINK_CXX_EXE = $(LINK) -pie + + # special link rule for mod_dav_svn + LINK_APACHE_MOD = $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) $(LT_LDFLAGS) $(CFLAGS) $(LDFLAGS) -rpath $(APACHE_LIBEXECDIR) -avoid-version -module $(APACHE_LDFLAGS) -shared +@@ -780,10 +785,10 @@ + $(PYTHON) $(top_srcdir)/build/transform_sql.py $< $(top_srcdir)/$@ + + .c.o: +- $(COMPILE) -o $@ -c $< ++ $(COMPILE) -fPIE -o $@ -c $< + + .cpp.o: +- $(COMPILE_CXX) -o $@ -c $< ++ $(COMPILE_CXX) -fPIE -o $@ -c $< + + .c.lo: + $(LT_COMPILE) -o $@ -c $< diff --git a/SOURCES/subversion-1.10.0-rpath.patch b/SOURCES/subversion-1.10.0-rpath.patch new file mode 100644 index 0000000..4d20107 --- /dev/null +++ b/SOURCES/subversion-1.10.0-rpath.patch @@ -0,0 +1,42 @@ + +Only link libraries using -rpath, to avoid unnecessary RPATH tags in executables. + +diff -uap subversion-1.10.0/build.conf.rpath subversion-1.10.0/build.conf +--- subversion-1.10.0/build.conf.rpath ++++ subversion-1.10.0/build.conf +@@ -568,7 +568,7 @@ + path = subversion/bindings/swig/python/libsvn_swig_py + libs = libsvn_client libsvn_wc libsvn_ra libsvn_delta libsvn_subr + apriconv apr python swig +-link-cmd = $(LINK) ++link-cmd = $(LINK_LIB) + install = swig-py-lib + # need special build rule to include -DSWIGPYTHON + compile-cmd = $(COMPILE_SWIG_PY) +@@ -594,7 +594,7 @@ + lang = ruby + path = subversion/bindings/swig/ruby/libsvn_swig_ruby + libs = libsvn_client libsvn_wc libsvn_delta libsvn_subr apriconv apr ruby swig +-link-cmd = $(LINK) $(SWIG_RB_LIBS) ++link-cmd = $(LINK_LIB) $(SWIG_RB_LIBS) + install = swig-rb-lib + # need special build rule to include + compile-cmd = $(COMPILE_SWIG_RB) +diff -uap subversion-1.10.0/Makefile.in.rpath subversion-1.10.0/Makefile.in +--- subversion-1.10.0/Makefile.in.rpath ++++ subversion-1.10.0/Makefile.in +@@ -264,10 +264,10 @@ + COMPILE_GOOGLEMOCK_CXX = $(LT_COMPILE_CXX_NOWARN) $(GOOGLEMOCK_LIB_INCLUDES) -o $@ -c + COMPILE_CXXHL_GOOGLEMOCK_CXX = $(LT_COMPILE_CXX) $(CXXHL_INCLUDES) $(GOOGLEMOCK_INCLUDES) -o $@ -c + +-LINK = $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) $(LT_LDFLAGS) $(CFLAGS) $(LDFLAGS) -rpath $(libdir) +-LINK_LIB = $(LINK) $(LT_SO_VERSION) +-LINK_CXX = $(LIBTOOL) $(LTCXXFLAGS) --mode=link $(CXX) $(LT_LDFLAGS) $(CXXFLAGS) $(LDFLAGS) -rpath $(libdir) +-LINK_CXX_LIB = $(LINK_CXX) $(LT_SO_VERSION) ++LINK = $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) $(LT_LDFLAGS) $(CFLAGS) $(LDFLAGS) ++LINK_LIB = $(LINK) $(LT_SO_VERSION) -rpath $(libdir) ++LINK_CXX = $(LIBTOOL) $(LTCXXFLAGS) --mode=link $(CXX) $(LT_LDFLAGS) $(CXXFLAGS) $(LDFLAGS) ++LINK_CXX_LIB = $(LINK_CXX) $(LT_SO_VERSION) -rpath $(libdir) + + # special link rule for mod_dav_svn + LINK_APACHE_MOD = $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) $(LT_LDFLAGS) $(CFLAGS) $(LDFLAGS) -rpath $(APACHE_LIBEXECDIR) -avoid-version -module $(APACHE_LDFLAGS) -shared diff --git a/SOURCES/subversion-1.10.2-CVE-2018-11782.patch b/SOURCES/subversion-1.10.2-CVE-2018-11782.patch new file mode 100644 index 0000000..a298173 --- /dev/null +++ b/SOURCES/subversion-1.10.2-CVE-2018-11782.patch @@ -0,0 +1,221 @@ + +https://bugzilla.redhat.com/show_bug.cgi?id=17330884 + +https://subversion.apache.org/security/CVE-2018-11782-advisory.txt + +Fixes for CVE-2018-11782, svnserve get-deleted-rev assertion failure. + +The svn protocol prototype for get-deleted-rev does not allow for a reply of +SVN_INVALID_REVNUM directly. A query having such an answer previously caused +the server to raise an assertion failure which could crash the whole process +or a thread or child process of it, depending on the build configuration of +the server. + +To work around the problem without changing the protocol, we re-purpose the +obsolete error code 'SVN_ERR_ENTRY_MISSING_REVISION' to communicate this +'not deleted' reply to the client. + + - With a new client against a new server, such queries are now handled + correctly. + + - With an old client against a new server, the client will report a more + informative error message, and the server will not crash. + + - With a new client against an old server, the behaviour is the same as + with an old client against an old server. + +In addition, this fixes a similar problem whereby any regular error response +to a 'get-deleted-rev' query resulted in the server closing the connection, +process and/or thread (again depending on the build configuration). Now such +errors are correctly passed back to the client. + +* subversion/libsvn_ra_svn/client.c + (ra_svn_get_deleted_rev): Detect error SVN_ERR_ENTRY_MISSING_REVISION + and convert it to a response of SVN_INVALID_REVNUM. + +* subversion/svnserve/serve.c + (get_deleted_rev): Respond with error SVN_ERR_ENTRY_MISSING_REVISION + instead of an assertion failure if the answer is SVN_INVALID_REVNUM. + If svn_repos_deleted_rev() returns an error, pass that error back to + the client. + +* subversion/tests/libsvn_ra/ra-test.c + (commit_two_changes): New. + (test_get_deleted_rev_no_delete, + test_get_deleted_rev_errors): New tests. + (test_funcs): Run them. +--This line, and those below, will be ignored-- + +Index: subversion/libsvn_ra_svn/client.c +=================================================================== +--- subversion-1.10.2/subversion/libsvn_ra_svn/client.c.cve11782 ++++ subversion-1.10.2/subversion/libsvn_ra_svn/client.c +@@ -3105,6 +3105,7 @@ + { + svn_ra_svn__session_baton_t *sess_baton = session->priv; + svn_ra_svn_conn_t *conn = sess_baton->conn; ++ svn_error_t *err; + + path = reparent_path(session, path, pool); + +@@ -3116,8 +3117,20 @@ + SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), + N_("'get-deleted-rev' not implemented"))); + +- return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, "r", +- revision_deleted)); ++ err = svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, "r", ++ revision_deleted)); ++ /* The protocol does not allow for a reply of SVN_INVALID_REVNUM directly. ++ Instead, a new enough server returns SVN_ERR_ENTRY_MISSING_REVISION to ++ indicate the answer to the query is SVN_INVALID_REVNUM. (An older server ++ closes the connection and returns SVN_ERR_RA_SVN_CONNECTION_CLOSED.) */ ++ if (err && err->apr_err == SVN_ERR_ENTRY_MISSING_REVISION) ++ { ++ *revision_deleted = SVN_INVALID_REVNUM; ++ svn_error_clear(err); ++ } ++ else ++ SVN_ERR(err); ++ return SVN_NO_ERROR; + } + + static svn_error_t * +--- subversion-1.10.2/subversion/svnserve/serve.c.cve11782 ++++ subversion-1.10.2/subversion/svnserve/serve.c +@@ -3505,8 +3505,21 @@ + svn_relpath_canonicalize(path, pool), pool); + SVN_ERR(log_command(b, conn, pool, "get-deleted-rev")); + SVN_ERR(trivial_auth_request(conn, pool, b)); +- SVN_ERR(svn_repos_deleted_rev(b->repository->fs, full_path, peg_revision, +- end_revision, &revision_deleted, pool)); ++ SVN_CMD_ERR(svn_repos_deleted_rev(b->repository->fs, full_path, peg_revision, ++ end_revision, &revision_deleted, pool)); ++ ++ /* The protocol does not allow for a reply of SVN_INVALID_REVNUM directly. ++ Instead, return SVN_ERR_ENTRY_MISSING_REVISION. A new enough client ++ knows that this means the answer to the query is SVN_INVALID_REVNUM. ++ (An older client reports this as an error.) */ ++ if (revision_deleted == SVN_INVALID_REVNUM) ++ SVN_CMD_ERR(svn_error_createf(SVN_ERR_ENTRY_MISSING_REVISION, NULL, ++ "svn protocol command 'get-deleted-rev': " ++ "path '%s' was not deleted in r%ld-%ld; " ++ "NOTE: newer clients handle this case " ++ "and do not report it as an error", ++ full_path, peg_revision, end_revision)); ++ + SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", revision_deleted)); + return SVN_NO_ERROR; + } +--- subversion-1.10.2/subversion/tests/libsvn_ra/ra-test.c.cve11782 ++++ subversion-1.10.2/subversion/tests/libsvn_ra/ra-test.c +@@ -94,6 +94,41 @@ + return SVN_NO_ERROR; + } + ++/* Commit two revisions: add 'B', then delete 'A' */ ++static svn_error_t * ++commit_two_changes(svn_ra_session_t *session, ++ apr_pool_t *pool) ++{ ++ apr_hash_t *revprop_table = apr_hash_make(pool); ++ const svn_delta_editor_t *editor; ++ void *edit_baton; ++ void *root_baton, *dir_baton; ++ ++ /* mkdir B */ ++ SVN_ERR(svn_ra_get_commit_editor3(session, &editor, &edit_baton, ++ revprop_table, ++ NULL, NULL, NULL, TRUE, pool)); ++ SVN_ERR(editor->open_root(edit_baton, SVN_INVALID_REVNUM, ++ pool, &root_baton)); ++ SVN_ERR(editor->add_directory("B", root_baton, NULL, SVN_INVALID_REVNUM, ++ pool, &dir_baton)); ++ SVN_ERR(editor->close_directory(dir_baton, pool)); ++ SVN_ERR(editor->close_directory(root_baton, pool)); ++ SVN_ERR(editor->close_edit(edit_baton, pool)); ++ ++ /* delete A */ ++ SVN_ERR(svn_ra_get_commit_editor3(session, &editor, &edit_baton, ++ revprop_table, ++ NULL, NULL, NULL, TRUE, pool)); ++ SVN_ERR(editor->open_root(edit_baton, SVN_INVALID_REVNUM, ++ pool, &root_baton)); ++ SVN_ERR(editor->delete_entry("A", SVN_INVALID_REVNUM, root_baton, pool)); ++ SVN_ERR(editor->close_directory(root_baton, pool)); ++ SVN_ERR(editor->close_edit(edit_baton, pool)); ++ ++ return SVN_NO_ERROR; ++} ++ + static svn_error_t * + commit_tree(svn_ra_session_t *session, + apr_pool_t *pool) +@@ -1784,6 +1819,56 @@ + return SVN_NO_ERROR; + } + ++/* Cases of 'get-deleted-rev' that should return SVN_INVALID_REVNUM. */ ++static svn_error_t * ++test_get_deleted_rev_no_delete(const svn_test_opts_t *opts, ++ apr_pool_t *pool) ++{ ++ svn_ra_session_t *ra_session; ++ svn_revnum_t revision_deleted; ++ ++ SVN_ERR(make_and_open_repos(&ra_session, ++ "test-repo-get-deleted-rev-no-delete", opts, ++ pool)); ++ SVN_ERR(commit_changes(ra_session, pool)); ++ SVN_ERR(commit_two_changes(ra_session, pool)); ++ ++ /* expect 'no deletion' in the range up to r2, when it is deleted in r3 */ ++ /* This was failing over RA-SVN where the 'get-deleted-rev' wire command's ++ prototype cannot directly represent that result. A new enough client and ++ server collaborate on a work-around implemented using an error code. */ ++ SVN_ERR(svn_ra_get_deleted_rev(ra_session, "A", 1, 2, ++ &revision_deleted, pool)); ++ SVN_TEST_INT_ASSERT(revision_deleted, SVN_INVALID_REVNUM); ++ ++ /* this connection should still be open: a simple case should still work */ ++ SVN_ERR(svn_ra_get_deleted_rev(ra_session, "A", 1, 3, ++ &revision_deleted, pool)); ++ SVN_TEST_INT_ASSERT(revision_deleted, 3); ++ ++ return SVN_NO_ERROR; ++} ++ ++/* Cases of 'get-deleted-rev' that should return an error. */ ++static svn_error_t * ++test_get_deleted_rev_errors(const svn_test_opts_t *opts, ++ apr_pool_t *pool) ++{ ++ svn_ra_session_t *ra_session; ++ svn_revnum_t revision_deleted; ++ ++ SVN_ERR(make_and_open_repos(&ra_session, ++ "test-repo-get-deleted-rev-errors", opts, pool)); ++ SVN_ERR(commit_changes(ra_session, pool)); ++ ++ /* expect an error when searching up to r3, when repository head is r1 */ ++ SVN_TEST_ASSERT_ERROR(svn_ra_get_deleted_rev(ra_session, "A", 1, 3, ++ &revision_deleted, pool), ++ SVN_ERR_FS_NO_SUCH_REVISION); ++ ++ return SVN_NO_ERROR; ++} ++ + + /* The test table. */ + +@@ -1820,6 +1905,10 @@ + "check how last change applies to empty commit"), + SVN_TEST_OPTS_PASS(commit_locked_file, + "check commit editor for a locked file"), ++ SVN_TEST_OPTS_PASS(test_get_deleted_rev_no_delete, ++ "test get-deleted-rev no delete"), ++ SVN_TEST_OPTS_PASS(test_get_deleted_rev_errors, ++ "test get-deleted-rev errors"), + SVN_TEST_NULL + }; + diff --git a/SOURCES/subversion-1.10.2-CVE-2019-0203.patch b/SOURCES/subversion-1.10.2-CVE-2019-0203.patch new file mode 100644 index 0000000..8a42a26 --- /dev/null +++ b/SOURCES/subversion-1.10.2-CVE-2019-0203.patch @@ -0,0 +1,31 @@ +diff --git a/subversion/svnserve/serve.c b/subversion/svnserve/serve.c +index 5192e7c..6159e22 100644 +--- a/subversion/svnserve/serve.c ++++ b/subversion/svnserve/serve.c +@@ -4101,7 +4101,7 @@ construct_server_baton(server_baton_t **baton, + serve_params_t *params, + apr_pool_t *scratch_pool) + { +- svn_error_t *err, *io_err; ++ svn_error_t *err; + apr_uint64_t ver; + const char *client_url, *ra_client_string, *client_string; + svn_ra_svn__list_t *caplist; +@@ -4239,11 +4239,12 @@ construct_server_baton(server_baton_t **baton, + } + if (err) + { +- log_error(err, b); +- io_err = svn_ra_svn__write_cmd_failure(conn, scratch_pool, err); +- svn_error_clear(err); +- SVN_ERR(io_err); +- return svn_ra_svn__flush(conn, scratch_pool); ++ /* Report these errors to the client before closing the connection. */ ++ err = svn_error_compose_create(err, ++ svn_ra_svn__write_cmd_failure(conn, scratch_pool, err)); ++ err = svn_error_compose_create(err, ++ svn_ra_svn__flush(conn, scratch_pool)); ++ return err; + } + + SVN_ERR(svn_fs_get_uuid(b->repository->fs, &b->repository->uuid, diff --git a/SOURCES/subversion-1.8.0-rubybind.patch b/SOURCES/subversion-1.8.0-rubybind.patch new file mode 100644 index 0000000..de9288a --- /dev/null +++ b/SOURCES/subversion-1.8.0-rubybind.patch @@ -0,0 +1,24 @@ + +Try a little harder to avoid svnserve() bind failures. + +--- subversion-1.8.0/subversion/bindings/swig/ruby/test/util.rb.rubybind ++++ subversion-1.8.0/subversion/bindings/swig/ruby/test/util.rb +@@ -39,7 +39,8 @@ module SvnTestUtil + @realm = "sample realm" + + @svnserve_host = "127.0.0.1" +- @svnserve_ports = (64152..64282).collect{|x| x.to_s} ++ sport = (50000 + rand(100) * 100) ++ @svnserve_ports = (sport..sport + 99).collect{|x| x.to_s} + + @tmp_path = Dir.mktmpdir + @wc_path = File.join(@tmp_path, "wc") +@@ -252,6 +253,8 @@ realm = #{@realm} + "--listen-port", port, + "-d", "--foreground") + } ++ # wait a while for svnserve to attempt a bind() and possibly fail ++ sleep(1) + pid, status = Process.waitpid2(@svnserve_pid, Process::WNOHANG) + if status and status.exited? + if $DEBUG diff --git a/SOURCES/subversion-1.8.5-swigplWall.patch b/SOURCES/subversion-1.8.5-swigplWall.patch new file mode 100644 index 0000000..af66806 --- /dev/null +++ b/SOURCES/subversion-1.8.5-swigplWall.patch @@ -0,0 +1,16 @@ + +Don't drop -Wall in the swig Perl bindings, otherwise building with +e.g. -Wformat-security might break. + +https://bugzilla.redhat.com/show_bug.cgi?id=1037341 + +--- subversion-1.8.5/subversion/bindings/swig/perl/native/Makefile.PL.in.swigplWall ++++ subversion-1.8.5/subversion/bindings/swig/perl/native/Makefile.PL.in +@@ -54,7 +54,6 @@ my $includes = ' -I/usr/include/apr-1 + # SWIG is using C++ style comments in an extern "C" code. + $cflags =~ s/-ansi\s+//g; + $cflags =~ s/-std=c89\s+//g; +-$cflags =~ s/-Wall//g; + $cflags =~ s/-Wunused//g; + $cflags =~ s/-Wshadow//g; + $cflags =~ s/-Wstrict-prototypes//g; diff --git a/SOURCES/subversion.conf b/SOURCES/subversion.conf new file mode 100644 index 0000000..1c0f8c7 --- /dev/null +++ b/SOURCES/subversion.conf @@ -0,0 +1,41 @@ + +LoadModule dav_svn_module modules/mod_dav_svn.so +LoadModule authz_svn_module modules/mod_authz_svn.so +LoadModule dontdothat_module modules/mod_dontdothat.so + +# +# Example configuration to enable HTTP access for a directory +# containing Subversion repositories, "/var/www/svn". Each repository +# must be both: +# +# a) readable and writable by the 'apache' user, and +# +# b) labelled with the 'httpd_sys_content_t' context if using +# SELinux +# + +# +# To create a new repository "http://localhost/repos/stuff" using +# this configuration, run as root: +# +# # cd /var/www/svn +# # svnadmin create stuff +# # chown -R apache:apache stuff +# # chcon -R -t httpd_sys_content_t stuff +# + +# +# DAV svn +# SVNParentPath /var/www/svn +# +# # Limit write permission to list of valid users. +# +# # Require SSL connection for password protection. +# # SSLRequireSSL +# +# AuthType Basic +# AuthName "Authorization Realm" +# AuthUserFile /path/to/passwdfile +# Require valid-user +# +# diff --git a/SOURCES/svnserve.service b/SOURCES/svnserve.service new file mode 100644 index 0000000..6fbbe1e --- /dev/null +++ b/SOURCES/svnserve.service @@ -0,0 +1,13 @@ +[Unit] +Description=Subversion protocol daemon +After=syslog.target network.target +Documentation=man:svnserve(8) + +[Service] +Type=forking +EnvironmentFile=/etc/sysconfig/svnserve +ExecStart=/usr/bin/svnserve --daemon --pid-file=/run/svnserve/svnserve.pid $OPTIONS +PrivateTmp=yes + +[Install] +WantedBy=multi-user.target diff --git a/SOURCES/svnserve.sysconf b/SOURCES/svnserve.sysconf new file mode 100644 index 0000000..647e09d --- /dev/null +++ b/SOURCES/svnserve.sysconf @@ -0,0 +1,4 @@ +# OPTIONS is used to pass command-line arguments to svnserve. +# +# Specify the repository location in -r parameter: +OPTIONS="-r /var/svn" diff --git a/SOURCES/svnserve.tmpfiles b/SOURCES/svnserve.tmpfiles new file mode 100644 index 0000000..e8487d3 --- /dev/null +++ b/SOURCES/svnserve.tmpfiles @@ -0,0 +1 @@ +D /run/svnserve 0700 root root - diff --git a/SPECS/subversion.spec b/SPECS/subversion.spec new file mode 100644 index 0000000..1c2e656 --- /dev/null +++ b/SPECS/subversion.spec @@ -0,0 +1,911 @@ +# set to zero to avoid running test suite + +%bcond_without kwallet +%bcond_without python2 +%bcond_with python3 +%bcond_without bdb +%bcond_without tests +%bcond_without pyswig + +%ifarch %{power64} s390x +%global with_java 0 +%else +%global with_java 1 +%endif + +%if %{with python2} == %{with python3} +%error Pick exactly one Python version +%endif + +# set JDK path to build javahl; default for JPackage +%define jdk_path /usr/lib/jvm/java + +%{!?_httpd_mmn: %{expand: %%global _httpd_mmn %%(cat %{_includedir}/httpd/.mmn 2>/dev/null || echo 0-0)}} + +%define perl_vendorarch %(eval "`%{__perl} -V:installvendorarch`"; echo $installvendorarch) + +%if %{with python2} +%global svn_python_sitearch %{python2_sitearch} +%global svn_python %{__python2} +%global svn_python_br python2-devel +%else +%global svn_python_sitearch %{python3_sitearch} +%global svn_python %{__python3} +%global svn_python_br python3-devel +%endif + +Summary: A Modern Concurrent Version Control System +Name: subversion +Version: 1.10.2 +Release: 3%{?dist} +License: ASL 2.0 +Group: Development/Tools +URL: https://subversion.apache.org/ + +Source0: https://www.apache.org/dist/subversion/subversion-%{version}.tar.bz2 +Source1: subversion.conf +Source3: filter-requires.sh +Source4: http://www.xsteve.at/prg/emacs/psvn.el +Source5: psvn-init.el +Source6: svnserve.service +Source7: svnserve.tmpfiles +Source8: svnserve.sysconf +Patch1: subversion-1.10.0-rpath.patch +Patch2: subversion-1.10.0-pie.patch +Patch4: subversion-1.8.0-rubybind.patch +Patch5: subversion-1.8.5-swigplWall.patch +Patch6: subversion-1.10.2-CVE-2019-0203.patch +Patch7: subversion-1.10.2-CVE-2018-11782.patch +BuildRequires: autoconf, libtool, texinfo, which +BuildRequires: swig >= 1.3.24, gettext +%if %{with bdb} +BuildRequires: libdb-devel >= 4.1.25 +%endif +BuildRequires: %{svn_python_br} +BuildRequires: apr-devel >= 1.3.0, apr-util-devel >= 1.3.0 +BuildRequires: libserf-devel >= 1.3.0, cyrus-sasl-devel +BuildRequires: sqlite-devel >= 3.4.0, file-devel, systemd-units +BuildRequires: utf8proc-devel, lz4-devel +# Any apr-util crypto backend needed +BuildRequires: apr-util-openssl +# For systemctl scriptlets +Requires(post): systemd +Requires(preun): systemd +Requires(postun): systemd +Provides: svn = %{version}-%{release} +Requires: subversion-libs%{?_isa} = %{version}-%{release} + +%define __perl_requires %{SOURCE3} + +# Put Python bindings in site-packages +%define swigdirs swig_pydir=%{svn_python_sitearch}/libsvn swig_pydir_extra=%{svn_python_sitearch}/svn + +%description +Subversion is a concurrent version control system which enables one +or more users to collaborate in developing and maintaining a +hierarchy of files and directories while keeping a history of all +changes. Subversion only stores the differences between versions, +instead of every complete file. Subversion is intended to be a +compelling replacement for CVS. + +%package libs +Group: Development/Tools +Summary: Libraries for Subversion Version Control system +# APR 1.3.x interfaces are required +Conflicts: apr%{?_isa} < 1.3.0 +# Enforced at run-time by ra_serf +Conflicts: libserf%{?_isa} < 1.3.0 + +%description libs +The subversion-libs package includes the essential shared libraries +used by the Subversion version control tools. + +%if %{with python2} && %{with pyswig} +%package -n python2-subversion +%{?python_provide:%python_provide python2-subversion} +# Remove before F30 +Provides: %{name}-python = %{version}-%{release} +Provides: %{name}-python%{?_isa} = %{version}-%{release} +Obsoletes: %{name}-python < %{version}-%{release} +BuildRequires: python2-devel +Group: Development/Libraries +Summary: Python bindings for Subversion Version Control system + +%description -n python2-subversion +The python2-subversion package includes the Python 2.x bindings to the +Subversion libraries. +%endif +%if %{with python3} && %{with pyswig} +%package -n python3-subversion +%{?python_provide:%python_provide python3-subversion} +Group: Development/Libraries +Summary: Python bindings for Subversion Version Control system +BuildRequires: python3-devel + +%description -n python3-subversion +The python3-subversion package includes the Python 3.x bindings to the +Subversion libraries. +%endif + +%package devel +Group: Development/Tools +Summary: Development package for the Subversion libraries +Requires: subversion%{?_isa} = %{version}-%{release} +Requires: apr-devel%{?_isa}, apr-util-devel%{?_isa} + +%description devel +The subversion-devel package includes the libraries and include files +for developers interacting with the subversion package. + +%package gnome +Group: Development/Tools +Summary: GNOME Keyring support for Subversion +Requires: subversion%{?_isa} = %{version}-%{release} +BuildRequires: dbus-devel, libsecret-devel + +%description gnome +The subversion-gnome package adds support for storing Subversion +passwords in the GNOME Keyring. + +%if %{with kwallet} +%package kde +Group: Development/Tools +Summary: KDE Wallet support for Subversion +Requires: subversion%{?_isa} = %{version}-%{release} +BuildRequires: kdelibs-devel >= 4.0.0 + +%description kde +The subversion-kde package adds support for storing Subversion +passwords in the KDE Wallet. +%endif + +%package -n mod_dav_svn +Group: System Environment/Daemons +Summary: Apache httpd module for Subversion server +Requires: httpd-mmn = %{_httpd_mmn} +Requires: subversion-libs%{?_isa} = %{version}-%{release} +BuildRequires: httpd-devel >= 2.0.45 + +%description -n mod_dav_svn +The mod_dav_svn package allows access to a Subversion repository +using HTTP, via the Apache httpd server. + +%package perl +Group: Development/Libraries +Summary: Perl bindings to the Subversion libraries +BuildRequires: perl-devel >= 2:5.8.0, perl-generators, perl(ExtUtils::MakeMaker) +BuildRequires: perl(Test::More), perl(ExtUtils::Embed) +Requires: %(eval `perl -V:version`; echo "perl(:MODULE_COMPAT_$version)") +Requires: subversion%{?_isa} = %{version}-%{release} + +%description perl +This package includes the Perl bindings to the Subversion libraries. + +%if %{with_java} +%package javahl +Group: Development/Libraries +Summary: JNI bindings to the Subversion libraries +Requires: subversion = %{version}-%{release} +BuildRequires: java-devel-openjdk +# JAR repacking requires both zip and unzip in the buildroot +BuildRequires: zip, unzip +# For the tests +BuildRequires: junit +BuildArch: noarch + +%description javahl +This package includes the JNI bindings to the Subversion libraries. +%endif + +%package ruby +Group: Development/Libraries +Summary: Ruby bindings to the Subversion libraries +BuildRequires: ruby-devel >= 1.9.1, ruby >= 1.9.1 +BuildRequires: rubygem(test-unit) +Requires: subversion%{?_isa} = %{version}-%{release} +Conflicts: ruby-libs%{?_isa} < 1.8.2 + +%description ruby +This package includes the Ruby bindings to the Subversion libraries. + +%package tools +Group: Development/Tools +Summary: Supplementary tools for Subversion +Requires: subversion%{?_isa} = %{version}-%{release} + +%description tools +This package includes supplementary tools for use with Subversion. + +%prep +%setup -q +%patch1 -p1 -b .rpath +%patch2 -p1 -b .pie +%patch4 -p1 -b .rubybind +%patch5 -p1 -b .swigplWall +%patch6 -p1 -b .cve0203 +%patch7 -p1 -b .cve11782 + +%build +# Regenerate the buildsystem, so that: +# 1) patches applied to configure.in take effect +# 2) the swig bindings are regenerated using the system swig +# (2) is not ideal since typically upstream test with a different +# swig version +# This PATH order makes the fugly test for libtoolize work... +mv build-outputs.mk build-outputs.mk.old +export PYTHON=%{svn_python} + +PATH=/usr/bin:$PATH ./autogen.sh --release + +# fix shebang lines, #111498 +perl -pi -e 's|/usr/bin/env perl -w|/usr/bin/perl -w|' tools/hook-scripts/*.pl.in +# fix python executable +perl -pi -e 's|/usr/bin/env python.*|%{svn_python}|' subversion/tests/cmdline/svneditor.py + +# override weird -shrext from ruby +export svn_cv_ruby_link="%{__cc} -shared" +export svn_cv_ruby_sitedir_libsuffix="" +export svn_cv_ruby_sitedir_archsuffix="" + +#export EXTRA_CFLAGS="$RPM_OPT_FLAGS -DSVN_SQLITE_MIN_VERSION_NUMBER=3007012 \ +# -DSVN_SQLITE_MIN_VERSION=\\\"3.7.12\\\"" +export APACHE_LDFLAGS="-Wl,-z,relro,-z,now" +export CC=gcc CXX=g++ JAVA_HOME=%{jdk_path} + +%configure --with-apr=%{_prefix} --with-apr-util=%{_prefix} \ + --disable-debug \ + --with-swig --with-serf=%{_prefix} \ + --with-ruby-sitedir=%{ruby_vendorarchdir} \ + --with-ruby-test-verbose=verbose \ + --with-apxs=%{_httpd_apxs} --disable-mod-activation \ + --with-apache-libexecdir=%{_httpd_moddir} \ + --disable-static --with-sasl=%{_prefix} \ + --with-libmagic=%{_prefix} \ + --with-gnome-keyring \ +%if %{with_java} + --enable-javahl \ + --with-junit=%{_prefix}/share/java/junit.jar \ +%endif +%if %{with kwallet} + --with-kwallet=%{_includedir}/kde4:%{_libdir}/kde4/devel \ +%endif +%if %{with bdb} + --with-berkeley-db \ +%else + --without-berkeley-db \ +%endif + || (cat config.log; exit 1) +make %{?_smp_mflags} all tools +%if %{with pyswig} +make swig-py swig-py-lib %{swigdirs} +%endif +make swig-pl swig-pl-lib swig-rb swig-rb-lib +%if %{with_java} +# javahl-javah does not parallel-make with javahl +#make javahl-java javahl-javah +make javahl +%endif + +%install +make install DESTDIR=$RPM_BUILD_ROOT +%if %{with pyswig} +make install-swig-py %{swigdirs} DESTDIR=$RPM_BUILD_ROOT +%endif +make install-swig-pl-lib install-swig-rb DESTDIR=$RPM_BUILD_ROOT +make pure_vendor_install -C subversion/bindings/swig/perl/native \ + PERL_INSTALL_ROOT=$RPM_BUILD_ROOT +%if %{with_java} +make install-javahl-java install-javahl-lib javahl_javadir=%{_javadir} DESTDIR=$RPM_BUILD_ROOT +%endif + +install -m 755 -d ${RPM_BUILD_ROOT}%{_sysconfdir}/subversion + +mkdir -p ${RPM_BUILD_ROOT}{%{_httpd_modconfdir},%{_httpd_confdir}} + +%if "%{_httpd_modconfdir}" == "%{_httpd_confdir}" +# httpd <= 2.2.x +install -p -m 644 %{SOURCE1} ${RPM_BUILD_ROOT}%{_httpd_confdir} +%else +sed -n /^LoadModule/p %{SOURCE1} > 10-subversion.conf +sed /^LoadModule/d %{SOURCE1} > example.conf +touch -r %{SOURCE1} 10-subversion.conf example.conf +install -p -m 644 10-subversion.conf ${RPM_BUILD_ROOT}%{_httpd_modconfdir} +%endif + +# Remove unpackaged files +rm -rf ${RPM_BUILD_ROOT}%{_includedir}/subversion-*/*.txt \ + ${RPM_BUILD_ROOT}%{svn_python_sitearch}/*/*.{a,la} + +# The SVN build system is broken w.r.t. DSO support; it treats +# normal libraries as DSOs and puts them in $libdir, whereas they +# should go in some subdir somewhere, and be linked using -module, +# etc. So, forcibly nuke the .so's for libsvn_auth_{gnome,kde}, +# since nothing should ever link against them directly. +rm -f ${RPM_BUILD_ROOT}%{_libdir}/libsvn_auth_*.so + +# remove stuff produced with Perl modules +find $RPM_BUILD_ROOT -type f \ + -a \( -name .packlist -o \( -name '*.bs' -a -empty \) \) \ + -print0 | xargs -0 rm -f + +# make Perl modules writable so they get stripped +find $RPM_BUILD_ROOT%{_libdir}/perl5 -type f -perm 555 -print0 | + xargs -0 chmod 755 + +# unnecessary libraries for swig bindings +rm -f ${RPM_BUILD_ROOT}%{_libdir}/libsvn_swig_*.{so,la,a} + +# Remove unnecessary ruby libraries +rm -f ${RPM_BUILD_ROOT}%{ruby_vendorarchdir}/svn/ext/*.*a + +# Trim what goes in docdir +rm -rvf tools/*/*.in tools/hook-scripts/mailer/tests + +# Install psvn for emacs and xemacs +for f in emacs/site-lisp xemacs/site-packages/lisp; do + install -m 755 -d ${RPM_BUILD_ROOT}%{_datadir}/$f + install -m 644 $RPM_SOURCE_DIR/psvn.el ${RPM_BUILD_ROOT}%{_datadir}/$f +done + +install -m 644 $RPM_SOURCE_DIR/psvn-init.el \ + ${RPM_BUILD_ROOT}%{_datadir}/emacs/site-lisp + +# Rename authz_svn INSTALL doc for docdir +ln -f subversion/mod_authz_svn/INSTALL mod_authz_svn-INSTALL + +# Trim exported dependencies to APR libraries only: +sed -i "/^dependency_libs/{ + s, -l[^ ']*, ,g; + s, -L[^ ']*, ,g; + s,%{_libdir}/lib[^a][^p][^r][^ ']*.la, ,g; + }" $RPM_BUILD_ROOT%{_libdir}/*.la + +# Install bash completion +install -Dpm 644 tools/client-side/bash_completion \ + $RPM_BUILD_ROOT%{_datadir}/bash-completion/completions/svn +for comp in svnadmin svndumpfilter svnlook svnsync svnversion; do + ln -s svn \ + $RPM_BUILD_ROOT%{_datadir}/bash-completion/completions/${comp} +done + +# Install svnserve bits +mkdir -p %{buildroot}%{_unitdir} \ + %{buildroot}/run/svnserve \ + %{buildroot}%{_prefix}/lib/tmpfiles.d \ + %{buildroot}%{_sysconfdir}/sysconfig + +install -p -m 644 $RPM_SOURCE_DIR/svnserve.service \ + %{buildroot}%{_unitdir}/svnserve.service +install -p -m 644 $RPM_SOURCE_DIR/svnserve.tmpfiles \ + %{buildroot}%{_prefix}/lib/tmpfiles.d/svnserve.conf +install -p -m 644 $RPM_SOURCE_DIR/svnserve.sysconf \ + %{buildroot}%{_sysconfdir}/sysconfig/svnserve + +# Install tools ex diff*, x509-parser +make install-tools DESTDIR=$RPM_BUILD_ROOT toolsdir=%{_bindir} +rm -f $RPM_BUILD_ROOT%{_bindir}/diff* $RPM_BUILD_ROOT%{_bindir}/x509-parser + +# Don't add spurious dependency in libserf-devel +sed -i "/^Requires.private/s, serf-1, ," \ + $RPM_BUILD_ROOT%{_datadir}/pkgconfig/libsvn_ra_serf.pc + +# Make svnauthz-validate a symlink +rm $RPM_BUILD_ROOT%{_bindir}/svnauthz-validate +ln -s svnauthz $RPM_BUILD_ROOT%{_bindir}/svnauthz-validate + +for f in svn-populate-node-origins-index fsfs-access-map \ + svnauthz svnauthz-validate svnmucc svnraisetreeconflict svnbench \ + svn-mergeinfo-normalizer fsfs-stats svnmover svnconflict; do + echo %{_bindir}/$f + if test -f $RPM_BUILD_ROOT%{_mandir}/man?/${f}.*; then + echo %{_mandir}/man?/${f}.* + fi +done | tee tools.files | sed 's/^/%%exclude /' > exclude.tools.files + +%find_lang %{name} + +cat %{name}.lang exclude.tools.files >> %{name}.files + +%if %{with tests} +%check +export LANG=C LC_ALL=C +export LD_LIBRARY_PATH=$RPM_BUILD_ROOT%{_libdir} +export MALLOC_PERTURB_=171 MALLOC_CHECK_=3 +export LIBC_FATAL_STDERR_=1 +export PYTHON=%{svn_python} +if ! make check CLEANUP=yes; then + : Test suite failure. + cat fails.log + exit 1 +fi +if ! make check-swig-pl check-swig-rb; then + : Swig test failure. + exit 1 +fi +%if %{with pyswig} +if ! make check-swig-py; then + : Python swig test failure. + exit 1 +fi +%endif +# check-swig-rb omitted: it runs svnserve +%if %{with_java} +make check-javahl +%endif +%endif + +%post +%systemd_post svnserve.service + +%preun +%systemd_preun svnserve.service + +%postun +%systemd_postun_with_restart svnserve.service + +%post libs -p /sbin/ldconfig + +%postun libs -p /sbin/ldconfig + +%post perl -p /sbin/ldconfig + +%postun perl -p /sbin/ldconfig + +%post ruby -p /sbin/ldconfig + +%postun ruby -p /sbin/ldconfig + +%if %{with_java} +%post javahl -p /sbin/ldconfig + +%postun javahl -p /sbin/ldconfig +%endif + +%files -f %{name}.files +%{!?_licensedir:%global license %%doc} +%license LICENSE NOTICE +%doc BUGS COMMITTERS INSTALL README CHANGES +%doc mod_authz_svn-INSTALL +%{_bindir}/* +%{_mandir}/man*/* +%{_datadir}/emacs/site-lisp/*.el +%{_datadir}/xemacs/site-packages/lisp/*.el +%{_datadir}/bash-completion/ +%config(noreplace) %{_sysconfdir}/sysconfig/svnserve +%dir %{_sysconfdir}/subversion +%exclude %{_mandir}/man*/*::* +%{_unitdir}/*.service +%attr(0700,root,root) %dir /run/svnserve +%{_prefix}/lib/tmpfiles.d/svnserve.conf + +%files tools -f tools.files +%doc tools/hook-scripts tools/backup tools/bdb tools/examples tools/xslt + +%files libs +%{!?_licensedir:%global license %%doc} +%license LICENSE NOTICE +%{_libdir}/libsvn*.so.* +%exclude %{_libdir}/libsvn_swig_perl* +%exclude %{_libdir}/libsvn_swig_ruby* +%if %{with_java} +%{_libdir}/libsvnjavahl-*.so +%endif +%if %{with kwallet} +%exclude %{_libdir}/libsvn_auth_kwallet* +%endif +%exclude %{_libdir}/libsvn_auth_gnome* + +%if %{with python2} && %{with pyswig} +%files -n python2-subversion +%{python2_sitearch}/svn +%{python2_sitearch}/libsvn +%endif + +%if %{with python3} && %{with pyswig} +%files -n python3-subversion +%{python3_sitearch}/svn +%{python3_sitearch}/libsvn +%endif + +%files gnome +%{_libdir}/libsvn_auth_gnome_keyring-*.so.* + +%if %{with kwallet} +%files kde +%{_libdir}/libsvn_auth_kwallet-*.so.* +%endif + +%files devel +%{_includedir}/subversion-1 +%{_libdir}/libsvn*.*a +%{_libdir}/libsvn*.so +%{_datadir}/pkgconfig/*.pc +%exclude %{_libdir}/libsvn_swig_perl* +%exclude %{_libdir}/libsvnjavahl-*.so + +%files -n mod_dav_svn +%config(noreplace) %{_httpd_modconfdir}/*.conf +%{_libdir}/httpd/modules/mod_*.so +%if "%{_httpd_modconfdir}" != "%{_httpd_confdir}" +%doc example.conf +%endif + +%files perl +%{perl_vendorarch}/auto/SVN +%{perl_vendorarch}/SVN +%{_libdir}/libsvn_swig_perl* +%{_mandir}/man*/*::* + +%files ruby +%{_libdir}/libsvn_swig_ruby* +%{ruby_vendorarchdir}/svn + +%if %{with_java} +%files javahl +%{_javadir}/svn-javahl.jar +%endif + +%changelog +* Mon May 18 2020 Joe Orton - 1.10.2-3 +- add security fix for CVE-2018-11782 + +* Thu Aug 01 2019 Lubos Uhliarik - 1.10.2-2 +- Resolves: #1733443 - CVE-2019-0203 subversion:1.10/subversion: remote + unauthenticated denial-of-service in subversion svnserve + +* Fri Jul 20 2018 Joe Orton - 1.10.2-1 +- update to 1.10.2 (#1603197) + +* Sat Jul 14 2018 Fedora Release Engineering - 1.10.0-10 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild + +* Fri Jun 29 2018 Jitka Plesnikova - 1.10.0-9 +- Perl 5.28 rebuild + +* Thu Jun 28 2018 Joe Orton - 1.10.0-8 +- fix test suite invocation + +* Thu Jun 28 2018 Joe Orton - 1.10.0-7 +- switch build conditional to disable only python bindings + +* Thu May 3 2018 Joe Orton - 1.10.0-6 +- really disable Berkeley DB support if required by bcond +- add build conditional to disable swig binding subpackages + +* Tue May 1 2018 Joe Orton - 1.10.0-5 +- remove build and -devel deps on libgnome-keyring-devel + +* Tue May 1 2018 Joe Orton - 1.10.0-4 +- drop -devel dep on libserf-devel + +* Tue Apr 24 2018 Joe Orton - 1.10.0-3 +- add bdb, tests as build conditional + +* Tue Apr 17 2018 Joe Orton - 1.10.0-2 +- move new tools to -tools + +* Mon Apr 16 2018 Joe Orton - 1.10.0-1 +- update to 1.10.0 (#1566493) + +* Tue Mar 27 2018 Joe Orton - 1.9.7-7 +- add build conditionals for python2, python3 and kwallet + +* Thu Feb 8 2018 Joe Orton - 1.9.7-6 +- force use of Python2 in test suite + +* Thu Feb 01 2018 Iryna Shcherbina - 1.9.7-5 +- Update Python 2 dependency declarations to new packaging standards + (See https://fedoraproject.org/wiki/FinalizingFedoraSwitchtoPython3) + +* Sat Jan 20 2018 Björn Esser - 1.9.7-4 +- Rebuilt for switch to libxcrypt + +* Fri Jan 05 2018 Mamoru TASAKA - 1.9.7-3 +- F-28: rebuild for ruby25 + +* Sun Dec 17 2017 Zbigniew Jędrzejewski-Szmek - 1.9.7-2 +- Python 2 binary package renamed to python2-subversion + See https://fedoraproject.org/wiki/FinalizingFedoraSwitchtoPython3 + +* Fri Aug 11 2017 Joe Orton - 1.9.7-1 +- update to 1.9.7 (CVE-2017-9800, #1480402) +- add Documentation= to svnserve.service + +* Thu Aug 03 2017 Fedora Release Engineering - 1.9.6-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Binutils_Mass_Rebuild + +* Thu Jul 27 2017 Fedora Release Engineering - 1.9.6-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild + +* Mon Jul 17 2017 Joe Orton - 1.9.6-2 +- move javahl .so to -libs (#1469158) + +* Thu Jul 6 2017 Joe Orton - 1.9.6-1 +- update to 1.9.6 (#1467890) +- update to latest upstream psvn.el +- move libsvnjavahl to -libs, build -javahl noarch +- fix javahl Requires + +* Sun Jun 04 2017 Jitka Plesnikova - 1.9.5-4 +- Perl 5.26 rebuild + +* Sat Feb 11 2017 Fedora Release Engineering - 1.9.5-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild + +* Fri Jan 13 2017 Mamoru TASAKA - 1.9.5-2 +- F-26: rebuild for ruby24 + +* Mon Jan 2 2017 Joe Orton - 1.9.5-1 +- update to 1.9.5 (#1400040, CVE-2016-8734) + +* Tue Jul 19 2016 Fedora Release Engineering - 1.9.4-4 +- https://fedoraproject.org/wiki/Changes/Automatic_Provides_for_Python_RPM_Packages + +* Wed May 25 2016 Jitka Plesnikova - 1.9.4-3 +- Enable tests +- Revert one of Ruby 2.2 fixes + +* Tue May 17 2016 Jitka Plesnikova - 1.9.4-2 +- Perl 5.24 rebuild + +* Sun May 8 2016 Peter Robinson 1.9.4-1 +- Update to 1.9.4 (#1331222) CVE-2016-2167 CVE-2016-2168 +- Move tools in docs to tools subpackage (rhbz 1171757 1199761) +- Disable make check to work around FTBFS + +* Fri Feb 05 2016 Fedora Release Engineering - 1.9.3-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild + +* Thu Jan 21 2016 Joe Orton - 1.9.3-2 +- rebuild for Ruby 2.3 + +* Tue Dec 15 2015 Joe Orton - 1.9.3-1 +- update to 1.9.3 (#1291683) +- use private /tmp in svnserve.service + +* Thu Sep 24 2015 Joe Orton - 1.9.2-1 +- update to 1.9.2 (#1265447) + +* Mon Sep 14 2015 Joe Orton - 1.9.1-1 +- update to 1.9.1 (#1259099) + +* Mon Aug 24 2015 Joe Orton - 1.9.0-1 +- update to 1.9.0 (#1207835) +- package pkgconfig files + +* Tue Jul 14 2015 Joe Orton - 1.8.13-7 +- move svnauthz to -tools; make svnauthz-validate a symlink +- move svnmucc man page to -tools +- restore dep on systemd (#1183873) + +* Fri Jul 10 2015 Joe Orton - 1.8.13-6 +- rebuild with tests enabled + +* Fri Jun 19 2015 Fedora Release Engineering - 1.8.13-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild + +* Mon Jun 15 2015 Ville Skyttä - 1.8.13-4 +- Own bash-completion dirs not owned by anything in dep chain + +* Sat Jun 06 2015 Jitka Plesnikova - 1.8.13-3 +- Perl 5.22 rebuild + +* Tue Apr 21 2015 Peter Robinson 1.8.13-2 +- Disable tests to fix swig test issues + +* Wed Apr 08 2015 - 1.8.13-1 +- Fix Ruby's test suite. + +* Tue Apr 7 2015 Joe Orton - 1.8.13-1 +- update to 1.8.13 (#1207835) +- attempt to patch around SWIG issues + +* Tue Dec 16 2014 Joe Orton - 1.8.11-1 +- update to 1.8.11 (#1174521) +- require newer libserf (#1155670) + +* Tue Sep 23 2014 Joe Orton - 1.8.10-6 +- prevents assert()ions in library code (#1058693) + +* Tue Sep 23 2014 Joe Orton - 1.8.10-5 +- drop sysv conversion trigger (#1133786) + +* Tue Sep 23 2014 Joe Orton - 1.8.10-4 +- move svn-bench, fsfs-* to -tools + +* Tue Aug 26 2014 Jitka Plesnikova - 1.8.10-3 +- Perl 5.20 rebuild + +* Thu Aug 21 2014 Kevin Fenzi - 1.8.10-2 +- Rebuild for rpm bug 1131960 + +* Mon Aug 18 2014 Joe Orton - 1.8.10-1 +- update to 1.8.10 (#1129100, #1128884, #1125800) + +* Mon Aug 18 2014 Fedora Release Engineering - 1.8.9-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_22_Mass_Rebuild + +* Sun Jun 08 2014 Fedora Release Engineering - 1.8.9-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild + +* Wed May 28 2014 Joe Orton - 1.8.9-1 +- update to 1.8.9 (#1100779) + +* Tue Apr 29 2014 Vít Ondruch - 1.8.8-3 +- Rebuilt for https://fedoraproject.org/wiki/Changes/Ruby_2.1 + +* Tue Apr 22 2014 Joe Orton - 1.8.8-2 +- require minitest 4 to fix tests for Ruby bindings (#1089252) + +* Fri Feb 28 2014 Joe Orton - 1.8.8-1 +- update to 1.8.8 + +* Thu Jan 23 2014 Joe Orton - 1.8.5-4 +- fix _httpd_mmn expansion in absence of httpd-devel + +* Mon Jan 6 2014 Joe Orton - 1.8.5-3 +- fix permissions of /run/svnserve (#1048422) + +* Tue Dec 10 2013 Joe Orton - 1.8.5-2 +- don't drop -Wall when building swig Perl bindings (#1037341) + +* Tue Nov 26 2013 Joe Orton - 1.8.5-1 +- update to 1.8.5 (#1034130) +- add fix for wc-queries-test breakage (h/t Andreas Stieger, r1542774) + +* Mon Nov 18 2013 Joe Orton - 1.8.4-2 +- add fix for ppc breakage (Andreas Stieger, #985582) + +* Tue Oct 29 2013 Joe Orton - 1.8.4-1 +- update to 1.8.4 + +* Tue Sep 3 2013 Joe Orton - 1.8.3-1 +- update to 1.8.3 +- move bash completions out of /etc (#922993) + +* Tue Aug 06 2013 Adam Williamson - 1.8.1-2 +- rebuild for perl 5.18 (again; 1.8.1-1 beat out 1.8.0-2) + +* Thu Jul 25 2013 Joe Orton - 1.8.1-1 +- update to 1.8.1 + +* Fri Jul 19 2013 Joe Orton - 1.8.0-3 +- temporarily ignore test suite failures on ppc* (#985582) + +* Wed Jul 17 2013 Petr Pisar - 1.8.0-2 +- Perl 5.18 rebuild + +* Tue Jun 18 2013 Joe Orton - 1.8.0-1 +- update to 1.8.0; switch to serf +- use full relro in mod_dav_svn build (#973694) + +* Mon Jun 3 2013 Joe Orton - 1.7.10-1 +- update to 1.7.10 (#970014) +- fix aarch64 build issues (Dennis Gilmore, #926578) + +* Thu May 9 2013 Joe Orton - 1.7.9-3 +- fix spurious failures in ruby test suite (upstream r1327373) + +* Thu May 9 2013 Joe Orton - 1.7.9-2 +- try harder to avoid svnserve bind failures in ruby binding tests +- enable verbose output for ruby binding tests + +* Tue Apr 9 2013 Joe Orton - 1.7.9-1 +- update to 1.7.9 + +* Wed Mar 27 2013 Vít Ondruch - 1.7.8-6 +- Rebuild for https://fedoraproject.org/wiki/Features/Ruby_2.0.0 +- Drop Ruby version checks from configuration script. +- Fix and enable Ruby test suite. + +* Thu Mar 14 2013 Joe Orton - 1.7.8-5 +- drop specific dep on ruby(abi) + +* Fri Feb 15 2013 Fedora Release Engineering - 1.7.8-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild + +* Tue Jan 8 2013 Joe Orton - 1.7.8-3 +- update to latest psvn.el + +* Tue Jan 8 2013 Lukáš Nykrýn - 1.7.8-2 +- Scriptlets replaced with new systemd macros (#850410) + +* Fri Jan 4 2013 Joe Orton - 1.7.8-1 +- update to 1.7.8 + +* Thu Oct 11 2012 Joe Orton - 1.7.7-1 +- update to 1.7.7 + +* Fri Aug 17 2012 Joe Orton - 1.7.6-1 +- update to 1.7.6 + +* Sat Jul 21 2012 Fedora Release Engineering - 1.7.5-6 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_18_Mass_Rebuild + +* Mon Jul 16 2012 Joe Orton - 1.7.5-5 +- switch svnserve pidfile to use /run, use /usr/lib/tmpfiles.d (#840195) + +* Thu Jun 28 2012 Petr Pisar - 1.7.5-4 +- Perl 5.16 rebuild + +* Mon Jun 18 2012 Dan Horák - 1.7.5-2 +- Perl 5.16 rebuild + +* Tue May 22 2012 Joe Orton - 1.7.5-1 +- update to 1.7.5 + +* Tue Apr 24 2012 Joe Orton - 1.7.4-6 +- drop strict sqlite version requirement (#815396) + +* Mon Apr 23 2012 Joe Orton - 1.7.4-5 +- switch to libdb-devel (#814090) + +* Thu Apr 19 2012 Joe Orton - 1.7.4-4 +- adapt for conf.modules.d with httpd 2.4 +- add possible workaround for kwallet crasher (#810861) + +* Fri Mar 30 2012 Joe Orton - 1.7.4-3 +- re-enable test suite + +* Fri Mar 30 2012 Joe Orton - 1.7.4-2 +- fix build with httpd 2.4 + +* Mon Mar 12 2012 Joe Orton - 1.7.4-1 +- update to 1.7.4 +- fix build with httpd 2.4 + +* Thu Mar 1 2012 Joe Orton - 1.7.3-7 +- re-enable kwallet (#791031) + +* Wed Feb 29 2012 Joe Orton - 1.7.3-6 +- update psvn + +* Wed Feb 29 2012 Joe Orton - 1.7.3-5 +- add tools subpackage (#648015) + +* Tue Feb 28 2012 Joe Orton - 1.7.3-4 +- trim contents of doc dic (#746433) + +* Tue Feb 28 2012 Joe Orton - 1.7.3-3 +- re-enable test suite + +* Tue Feb 28 2012 Joe Orton - 1.7.3-2 +- add upstream test suite fixes for APR hash change (r1293602, r1293811) +- use ruby vendorlib directory (#798203) +- convert svnserve to systemd (#754074) + +* Mon Feb 13 2012 Joe Orton - 1.7.3-1 +- update to 1.7.3 +- ship, enable mod_dontdothat + +* Mon Feb 13 2012 Joe Orton - 1.7.2-2 +- require ruby 1.9.1 abi + +* Thu Feb 9 2012 Joe Orton - 1.7.2-1 +- update to 1.7.2 +- add Vincent Batts' Ruby 1.9 fixes from dev@ + +* Sun Feb 5 2012 Peter Robinson - 1.7.1-3 +- fix gnome-keyring build deps + +* Sat Jan 14 2012 Fedora Release Engineering - 1.7.1-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_17_Mass_Rebuild + +* Mon Nov 28 2011 Joe Orton - 1.7.1-1 +- update to 1.7.1 +- (temporarily) disable failing kwallet support + +* Sun Nov 27 2011 Ville Skyttä - 1.7.0-3 +- Build with libmagic support. + +* Sat Oct 15 2011 Ville Skyttä - 1.7.0-2 +- Fix apr Conflicts syntax in -libs. +- Fix obsolete chown syntax in subversion.conf. +- Fix use of spaces vs tabs in specfile. + +* Wed Oct 12 2011 Joe Orton - 1.7.0-1 +- update to 1.7.0 +- drop svn2cl (no longer shipped in upstream tarball)