;; TODO
;; - portability to XEmacs
;; - better handling of subprocess errors
-;; - hook into file save (after-save-hook)
;; - diff against other branch
;; - renaming files from the status buffer
;; - creating tags
(eval-when-compile (require 'cl))
(require 'ewoc)
(require 'log-edit)
+(require 'easymenu)
;;;; Customizations
"Remove everything from the status list."
(ewoc-filter status (lambda (info) nil)))
-(defun git-set-files-state (files state)
- "Set the state of a list of files."
- (dolist (info files)
- (unless (eq (git-fileinfo->state info) state)
- (setf (git-fileinfo->state info) state)
- (setf (git-fileinfo->rename-state info) nil)
- (setf (git-fileinfo->orig-name info) nil)
- (setf (git-fileinfo->needs-refresh info) t))))
+(defun git-set-fileinfo-state (info state)
+ "Set the state of a file info."
+ (unless (eq (git-fileinfo->state info) state)
+ (setf (git-fileinfo->state info) state
+ (git-fileinfo->old-perm info) 0
+ (git-fileinfo->new-perm info) 0
+ (git-fileinfo->rename-state info) nil
+ (git-fileinfo->orig-name info) nil
+ (git-fileinfo->needs-refresh info) t)))
(defun git-status-filenames-map (status func files &rest args)
"Apply FUNC to the status files names in the FILES list."
(defun git-set-filenames-state (status files state)
"Set the state of a list of named files."
(when files
- (git-status-filenames-map status
- (lambda (info state)
- (unless (eq (git-fileinfo->state info) state)
- (setf (git-fileinfo->state info) state)
- (setf (git-fileinfo->rename-state info) nil)
- (setf (git-fileinfo->orig-name info) nil)
- (setf (git-fileinfo->needs-refresh info) t)))
- files state)
+ (git-status-filenames-map status #'git-set-fileinfo-state files state)
(unless state ;; delete files whose state has been set to nil
(ewoc-filter status (lambda (info) (git-fileinfo->state info))))))
(condition-case nil (delete-file ".git/MERGE_HEAD") (error nil))
(condition-case nil (delete-file ".git/MERGE_MSG") (error nil))
(with-current-buffer buffer (erase-buffer))
- (git-set-files-state files 'uptodate)
+ (dolist (info files) (git-set-fileinfo-state info 'uptodate))
(git-call-process-env nil nil "rerere")
+ (git-call-process-env nil nil "gc" "--auto")
(git-refresh-files)
(git-refresh-ewoc-hf git-status)
(message "Committed %s." commit)
"Mark all files."
(interactive)
(unless git-status (error "Not in git-status buffer."))
- (ewoc-map (lambda (info) (setf (git-fileinfo->marked info) t) t) git-status)
+ (ewoc-map (lambda (info) (unless (git-fileinfo->marked info)
+ (setf (git-fileinfo->marked info) t))) git-status)
; move back to goal column after invalidate
(when goal-column (move-to-column goal-column)))
"Unmark all files."
(interactive)
(unless git-status (error "Not in git-status buffer."))
- (ewoc-map (lambda (info) (setf (git-fileinfo->marked info) nil) t) git-status)
+ (ewoc-map (lambda (info) (when (git-fileinfo->marked info)
+ (setf (git-fileinfo->marked info) nil)
+ t)) git-status)
; move back to goal column after invalidate
(when goal-column (move-to-column goal-column)))
(when modified
(apply #'git-call-process-env nil nil "checkout" "HEAD" modified))
(git-update-status-files (append added modified) 'uptodate)
- (git-success-message "Reverted" files))))
+ (git-success-message "Reverted" (git-get-filenames files)))))
(defun git-resolve-file ()
"Resolve conflicts in marked file(s)."
(define-key toggle-map "i" 'git-toggle-show-ignored)
(define-key toggle-map "k" 'git-toggle-show-unknown)
(define-key toggle-map "m" 'git-toggle-all-marks)
- (setq git-status-mode-map map)))
+ (setq git-status-mode-map map))
+ (easy-menu-define git-menu git-status-mode-map
+ "Git Menu"
+ `("Git"
+ ["Refresh" git-refresh-status t]
+ ["Commit" git-commit-file t]
+ ("Merge"
+ ["Next Unmerged File" git-next-unmerged-file t]
+ ["Prev Unmerged File" git-prev-unmerged-file t]
+ ["Mark as Resolved" git-resolve-file t]
+ ["Interactive Merge File" git-find-file-imerge t]
+ ["Diff Against Common Base File" git-diff-file-base t]
+ ["Diff Combined" git-diff-file-combined t]
+ ["Diff Against Merge Head" git-diff-file-merge-head t]
+ ["Diff Against Mine" git-diff-file-mine t]
+ ["Diff Against Other" git-diff-file-other t])
+ "--------"
+ ["Add File" git-add-file t]
+ ["Revert File" git-revert-file t]
+ ["Ignore File" git-ignore-file t]
+ ["Remove File" git-remove-file t]
+ "--------"
+ ["Find File" git-find-file t]
+ ["View File" git-view-file t]
+ ["Diff File" git-diff-file t]
+ ["Interactive Diff File" git-diff-file-idiff t]
+ ["Log" git-log-file t]
+ "--------"
+ ["Mark" git-mark-file t]
+ ["Mark All" git-mark-all t]
+ ["Unmark" git-unmark-file t]
+ ["Unmark All" git-unmark-all t]
+ ["Toggle All Marks" git-toggle-all-marks t]
+ ["Hide Handled Files" git-remove-handled t]
+ "--------"
+ ["Show Uptodate Files" git-toggle-show-uptodate :style toggle :selected git-show-uptodate]
+ ["Show Ignored Files" git-toggle-show-ignored :style toggle :selected git-show-ignored]
+ ["Show Unknown Files" git-toggle-show-unknown :style toggle :selected git-show-unknown]
+ "--------"
+ ["Quit" git-status-quit t])))
+
;; git mode should only run in the *git status* buffer
(put 'git-status-mode 'mode-class 'special)
(cd dir)
(git-status-mode)
(git-refresh-status)
- (goto-char (point-min)))
+ (goto-char (point-min))
+ (add-hook 'after-save-hook 'git-update-saved-file))
(message "%s is not a git working tree." dir)))
+(defun git-update-saved-file ()
+ "Update the corresponding git-status buffer when a file is saved.
+Meant to be used in `after-save-hook'."
+ (let* ((file (expand-file-name buffer-file-name))
+ (dir (condition-case nil (git-get-top-dir (file-name-directory file)) (error nil)))
+ (buffer (and dir (git-find-status-buffer dir))))
+ (when buffer
+ (with-current-buffer buffer
+ (let ((filename (file-relative-name file dir)))
+ ; skip files located inside the .git directory
+ (unless (string-match "^\\.git/" filename)
+ (git-call-process-env nil nil "add" "--refresh" "--" filename)
+ (git-update-status-files (list filename) 'uptodate)))))))
+
(defun git-help ()
"Display help for Git mode."
(interactive)