;;; org-attach.el --- Manage file attachments to org-mode tasks
-;; Copyright (C) 2008-2011 Free Software Foundation, Inc.
+;; Copyright (C) 2008-2015 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@newartisans.com>
;; Keywords: org data task
-;; Version: 7.4
;; This file is part of GNU Emacs.
;;
(require 'cl))
(require 'org-id)
(require 'org)
+(require 'vc-git)
(defgroup org-attach nil
"Options concerning entry attachments in Org-mode."
:group 'org-attach
:type 'directory)
+(defcustom org-attach-git-annex-cutoff (* 32 1024)
+ "If non-nil, files larger than this will be annexed instead of stored."
+ :group 'org-attach
+ :version "24.4"
+ :package-version '(Org . "8.0")
+ :type '(choice
+ (const :tag "None" nil)
+ (integer :tag "Bytes")))
+
(defcustom org-attach-auto-tag "ATTACH"
"Tag that will be triggered automatically when an entry has an attachment."
:group 'org-attach
mv rename the file to move it into the attachment directory
cp copy the file
ln create a hard link. Note that this is not supported
+ on all systems, and then the result is not defined.
+lns create a symbol link. Note that this is not supported
on all systems, and then the result is not defined."
:group 'org-attach
:type '(choice
(const :tag "Copy" cp)
(const :tag "Move/Rename" mv)
- (const :tag "Link" ln)))
+ (const :tag "Hard Link" ln)
+ (const :tag "Symbol Link" lns)))
(defcustom org-attach-expert nil
"Non-nil means do not show the splash buffer with the attach dispatcher."
:group 'org-attach
:type 'boolean)
-
(defvar org-attach-inherited nil
"Indicates if the last access to the attachment directory was inherited.")
+(defcustom org-attach-store-link-p nil
+ "Non-nil means store a link to a file when attaching it."
+ :group 'org-attach
+ :version "24.1"
+ :type '(choice
+ (const :tag "Don't store link" nil)
+ (const :tag "Link to origin location" t)
+ (const :tag "Link to the attach-dir location" attached)))
+
;;;###autoload
(defun org-attach ()
"The dispatcher for attachment commands.
(princ "Select an Attachment Command:
a Select a file and attach it to the task, using `org-attach-method'.
-c/m/l Attach a file using copy/move/link method.
+c/m/l/y Attach a file using copy/move/link/symbolic-link method.
n Create a new attachment, as an Emacs buffer.
z Synchronize the current task with its attachment
directory, in case you added attachments yourself.
(let ((org-attach-method 'mv)) (call-interactively 'org-attach-attach)))
((memq c '(?l ?\C-l))
(let ((org-attach-method 'ln)) (call-interactively 'org-attach-attach)))
+ ((memq c '(?y ?\C-y))
+ (let ((org-attach-method 'lns)) (call-interactively 'org-attach-attach)))
((memq c '(?n ?\C-n)) (call-interactively 'org-attach-new))
((memq c '(?z ?\C-z)) (call-interactively 'org-attach-sync))
((memq c '(?o ?\C-o)) (call-interactively 'org-attach-open))
(save-excursion
(save-restriction
(widen)
- (goto-char org-entry-property-inherited-from)
+ (if (marker-position org-entry-property-inherited-from)
+ (goto-char org-entry-property-inherited-from)
+ (org-back-to-heading t))
(let (org-attach-allow-inheritance)
(org-attach-dir create-if-not-exists-p)))))
(org-attach-check-absolute-path attach-dir)
(defun org-attach-commit ()
"Commit changes to git if `org-attach-directory' is properly initialized.
This checks for the existence of a \".git\" directory in that directory."
- (let ((dir (expand-file-name org-attach-directory)))
- (when (file-exists-p (expand-file-name ".git" dir))
+ (let* ((dir (expand-file-name org-attach-directory))
+ (git-dir (vc-git-root dir))
+ (changes 0))
+ (when (and git-dir (executable-find "git"))
(with-temp-buffer
(cd dir)
- (shell-command "git add .")
- (shell-command "git ls-files --deleted" t)
- (mapc (lambda (file)
- (unless (string= file "")
- (shell-command
- (concat "git rm \"" file "\""))))
- (split-string (buffer-string) "\n"))
- (shell-command "git commit -m 'Synchronized attachments'")))))
+ (let ((have-annex
+ (and org-attach-git-annex-cutoff
+ (file-exists-p (expand-file-name "annex" git-dir)))))
+ (dolist (new-or-modified
+ (split-string
+ (shell-command-to-string
+ "git ls-files -zmo --exclude-standard") "\0" t))
+ (if (and have-annex
+ (>= (nth 7 (file-attributes new-or-modified))
+ org-attach-git-annex-cutoff))
+ (call-process "git" nil nil nil "annex" "add" new-or-modified)
+ (call-process "git" nil nil nil "add" new-or-modified))
+ (incf changes)))
+ (dolist (deleted
+ (split-string
+ (shell-command-to-string "git ls-files -z --deleted") "\0" t))
+ (call-process "git" nil nil nil "rm" deleted)
+ (incf changes))
+ (when (> changes 0)
+ (shell-command "git commit -m 'Synchronized attachments'"))))))
(defun org-attach-tag (&optional off)
"Turn the autotag on or (if OFF is set) off."
"Turn the autotag off."
(org-attach-tag 'off))
+(defun org-attach-store-link (file)
+ "Add a link to `org-stored-link' when attaching a file.
+Only do this when `org-attach-store-link-p' is non-nil."
+ (setq org-stored-links
+ (cons (list (org-attach-expand-link file)
+ (file-name-nondirectory file))
+ org-stored-links)))
+
(defun org-attach-attach (file &optional visit-dir method)
"Move/copy/link FILE into the attachment directory of the current task.
If VISIT-DIR is non-nil, visit the directory with dired.
-METHOD may be `cp', `mv', or `ln', default taken from `org-attach-method'."
+METHOD may be `cp', `mv', `ln', or `lns' default taken from
+`org-attach-method'."
(interactive "fFile to keep as an attachment: \nP")
(setq method (or method org-attach-method))
(let ((basename (file-name-nondirectory file)))
(cond
((eq method 'mv) (rename-file file fname))
((eq method 'cp) (copy-file file fname))
- ((eq method 'ln) (add-name-to-file file fname)))
+ ((eq method 'ln) (add-name-to-file file fname))
+ ((eq method 'lns) (make-symbolic-link file fname)))
(org-attach-commit)
(org-attach-tag)
+ (cond ((eq org-attach-store-link-p 'attached)
+ (org-attach-store-link fname))
+ ((eq org-attach-store-link-p t)
+ (org-attach-store-link file)))
(if visit-dir
(dired attach-dir)
(message "File \"%s\" is now a task attachment." basename)))))
On some systems, this apparently does copy the file instead."
(interactive)
(let ((org-attach-method 'ln)) (call-interactively 'org-attach-attach)))
+(defun org-attach-attach-lns ()
+ "Attach a file by creating a symbolic link to it.
+
+Beware that this does not work on systems that do not support symbolic links.
+On some systems, this apparently does copy the file instead."
+ (interactive)
+ (let ((org-attach-method 'lns)) (call-interactively 'org-attach-attach)))
(defun org-attach-new (file)
"Create a new attachment FILE for the current task.
(directory-files dir nil "[^~]\\'"))))
(defun org-attach-reveal (&optional if-exists)
- "Show the attachment directory of the current task in dired."
+ "Show the attachment directory of the current task.
+This will attempt to use an external program to show the directory."
(interactive "P")
(let ((attach-dir (org-attach-dir (not if-exists))))
(and attach-dir (org-open-file attach-dir))))
(defun org-attach-reveal-in-emacs ()
- "Show the attachment directory of the current task.
-This will attempt to use an external program to show the directory."
+ "Show the attachment directory of the current task in dired."
(interactive)
(let ((attach-dir (org-attach-dir t)))
(dired attach-dir)))
(file (if (= (length files) 1)
(car files)
(org-icompleting-read "Open attachment: "
- (mapcar 'list files) nil t))))
+ (mapcar 'list files) nil t))))
(org-open-file (expand-file-name file attach-dir) in-emacs)))
(defun org-attach-open-in-emacs ()
(provide 'org-attach)
+;; Local variables:
+;; generated-autoload-file: "org-loaddefs.el"
+;; End:
+
;;; org-attach.el ends here