diff --git a/.gitignore b/.gitignore index 34f767e..1afc7c3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +*~ *.elc .#* TAGS diff --git a/git--test.el b/git--test.el index e582083..e71422a 100644 --- a/git--test.el +++ b/git--test.el @@ -91,7 +91,7 @@ (git--commit-buffer) (assert (not (buffer-live-p (get-buffer git--commit-log-buffer)))) (assert (eq 'uptodate (git--status-file "f1"))) - (assert (string-match "^[0-9a-f]* *another test commit" + (assert (string-match "^[0-9a-f.]* *another test commit" (git--last-log-short))) ;; Should be one above last commit (setq second-commit-id (git--rev-parse "HEAD")) diff --git a/git-emacs.el b/git-emacs.el index 5c4caa2..be64ae8 100644 --- a/git-emacs.el +++ b/git-emacs.el @@ -38,7 +38,7 @@ ;; ;;; Commentary: ;; -;; I referenced a lot of codes such as under +;; Some related packages were used directly or as inspiration: ;; - psvn.el (Stefan Reichoer) ;; - vc-git.el (Alexandre Julliard) ;; - git.el (Alexandre Julliard) @@ -47,6 +47,10 @@ ;; ;;; Installation ;; +;; First, make sure that vc-git.el is in your load path. Emacs 23 ships it by +;; default, for older versions you can get it from git distributions prior +;; to 1.6.x. +;; ;; 1) Load at startup (simplest) ;; ;; (add-to-list 'load-path "~/.emacs.d/git-emacs") ; or your installation path @@ -61,11 +65,6 @@ ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; -;;; BUG FIXES -;; 2008.03.28 : git-diff just work on git root -;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; ;; TODO : check git environment ;; TODO : status -> index ;; TODO : pull/patch @@ -96,7 +95,7 @@ (require 'git-global-keys) ; global keyboard mappings (require 'git-emacs-autoloads) ; the minimal autoloads -;; Autoloaded submodules, those not declared in git-emacs autoloads +;; Autoloaded submodules, those not declared in git-emacs-autoloads (autoload 'git-blame-mode "git-blame" "Minor mode for incremental blame for Git" t) @@ -123,7 +122,7 @@ :group 'tools) (defcustom git--use-ido t - "Use ido for Git prompts. Affects the default of `git--completing-read'." + "Whether to use ido completion for git-emacs prompts." :type '(boolean) :group 'git-emacs) @@ -136,7 +135,7 @@ (unless ido-mode (ido-mode t) (ido-mode -1)) #'ido-completing-read) #'completing-read) - "Function to use for git minibuffer prompts with choices. It should have + "Function to use for git-emacs minibuffer prompts with choices. It should have the signature of `completing-read'.") (defgroup git-emacs-faces nil @@ -159,9 +158,11 @@ the signature of `completing-read'.") (defconst git--msg-failed (propertize "Failed" 'face 'git--bold-face)) ;;----------------------------------------------------------------------------- -;; internal variable +;; Internal variables. ;;----------------------------------------------------------------------------- + +(defvar git--executable "git" "Main git executable") (defvar git--commit-log-buffer "*git commit*") (defvar git--log-flyspell-mode t "enable flyspell-mode when editing log") (defvar git--repository-bookmarks @@ -195,23 +196,18 @@ the signature of `completing-read'.") ;;----------------------------------------------------------------------------- -;; fork git process +;; Low-level execution functions. ;;----------------------------------------------------------------------------- (defsubst git--exec (cmd outbuf infile &rest args) - "Execute 'git' clumsily" - - (apply #'call-process - "git" ; cmd - infile ; in file - outbuf ; out buffer - nil ; display - (cons cmd args))) ; args + "Low level function for calling git. CMD is the main git subcommand, args +are the remaining args. See `call-process' for the meaning of OUTBUF and +INFILE. Reeturns git's exit code." + (apply #'call-process git--executable infile outbuf nil (cons cmd args))) (defun git--exec-pipe (cmd input &rest args) - "Execute 'echo input | git cmd args' and return result -string. INPUT can also be a buffer." - + "Execute 'git cmd args', piping INPUT (which can be a buffer or string). +Return result string." (with-output-to-string (with-current-buffer standard-output (let ((tmp (make-temp-file "git-emacs-tmp"))) @@ -223,37 +219,32 @@ string. INPUT can also be a buffer." (with-temp-buffer (insert input) (write-file tmp))) - ;; tricky hide write to file message - (message "") + (message "") ;hide write to file message (apply #'git--exec cmd t tmp args)) (delete-file tmp)))))) (defsubst git--exec-buffer (cmd &rest args) "Execute 'git' within the buffer. Return the exit code." - (apply #'git--exec cmd t nil args)) (defsubst git--exec-string-no-error (cmd &rest args) - "Execute 'git' and return result string (which may be a failure message)." + "Execute 'git CMD ARGS' and return result string, which may be +a failure message." (with-output-to-string (with-current-buffer standard-output (apply #'git--exec-buffer cmd args)))) -(defsubst git--trim-string (str) - "Trim the front and rear part of the string" - +(defun git--trim-string (str) + "Trim the spaces / newlines from the beginning and end of STR." (let ((begin 0) (end (- (length str) 1))) - ;; trim front (while (and (< begin end) (memq (aref str begin) '(? ?\n))) (incf begin)) - ;; trim rear (while (and (<= begin end) (memq (aref str end) '(? ?\n))) (decf end)) - (substring str begin (+ end 1)))) (defun git--exec-string (cmd &rest args) @@ -271,7 +262,7 @@ if it fails. If the command succeeds, returns the git output." ;; guys -- I get to learn to learn how to do horrible hacks like this. ;; Hopefully the messages aren't translated or something. (defun git--commit-dryrun-compat(outbuf &rest args) - "Executes commit --dryrun with the specified args, falls back to the + "Executes commit --dry-run with the specified args, falls back to the older git status if that command is not present. If OUTBUF is not nil, puts the standard output there. Returns the git return code." ;; Going forward, this will simply succeed. @@ -292,25 +283,20 @@ the standard output there. Returns the git return code." ;; utilities ;;----------------------------------------------------------------------------- -(defsubst git--trim-tail (str) - "Trim only the tail of the string" - +(defun git--trim-tail (str) + "Trim spaces / newlines from the end of STR." (let ((end (- (length str) 1))) - (while (and (< 0 end) (memq (aref str end) '(? ?\n))) (decf end)) - (substring str 0 (+ end 1)))) (defsubst git--join (seq &optional sep) - "' '.join( seq ) in python" - + "Joins the strings in SEQ with the SEP (defaults to blank)." (mapconcat #'identity seq (if sep sep " "))) (defsubst git--concat-path-only (path added) "Concatenate the path with proper separator" - (concat (file-name-as-directory path) added)) (defsubst git--concat-path (path added) @@ -320,8 +306,7 @@ the standard output there. Returns the git return code." (git--concat-path dir git--repository-dir)) (defun git--quit-buffer () - "Delete the window and kill the current buffer" - + "Delete the current window, kill the current buffer." (interactive) (let ((buffer (current-buffer))) ;; Emacs refuses to delete a "maximized" window (i.e. just 1 in frame) @@ -329,8 +314,7 @@ the standard output there. Returns the git return code." (kill-buffer buffer))) (defsubst git--rev-parse (&rest args) - "Execute 'git-rev-parse' with args and return as string" - + "Execute 'git rev-parse ARGS', return result string." (apply #'git--exec-string "rev-parse" args)) (defmacro git-in-lowest-existing-dir (dir &rest BODY) @@ -369,8 +353,7 @@ autoloading which we know has already happened." (git--concat-path default-directory (car (split-string cdup "\n")))))) (defsubst git--interpret-to-state-symbol (stat) - "Interpret git state string to state symbol" - + "Interpret a one-letter git state string to our state symbols." (case (string-to-char stat) (?H 'uptodate ) (?M 'modified ) @@ -438,9 +421,10 @@ repository), a list (filelist) or nil (current git repository)." (t (git--find-buffers-from-file-list repo-or-filelist predicate)))) (defun git--maybe-ask-save (&optional repo-or-filelist) - "If there are modified buffers which visit files in the given REPO-OR-FILELIST, -ask to save them. If REPO-OR-FILELIST is nil, look for buffers in -the current git repo. Returns the number of buffers saved." + "If there are modified buffers which visit files in the given +REPO-OR-FILELIST,ask to save them. If REPO-OR-FILELIST is nil, +look for buffers in the current git repo. Returns the number of +buffers saved." (let ((buffers (git--find-buffers repo-or-filelist #'buffer-modified-p))) (map-y-or-n-p (lambda(buffer) (format "Save %s? " (buffer-name buffer))) @@ -547,7 +531,8 @@ runs git commit --amend -a, alowing an update of the previous commit." (defstruct (git--fileinfo (:copier nil) (:constructor git--create-fileinfo - (name type &optional sha1 perm marked stat size refresh)) + (name type &optional sha1 perm marked + stat size refresh)) (:conc-name git--fileinfo->)) marked ;; t/nil expanded ;; t/nil @@ -599,24 +584,21 @@ properly expanded tree." ;;----------------------------------------------------------------------------- -;; git execute command +;; git commands ;;----------------------------------------------------------------------------- (defun git--init (dir) - "Execute 'git-init' at 'dir' directory" - + "Execute 'git init' in DIR (current dir, if unspecified)." (with-temp-buffer (when dir (cd dir)) (git--exec-string "init"))) (defun git--checkout (&rest args) - "git checkout 'git-checkout' with 'args'" - + "Execute 'git checkout ARGS' and return resulting string." (apply #'git--exec-string "checkout" args)) -(defun git--clone-sentinal (proc stat) - "git clone process sentinal" - +(defun git--clone-sentinel (proc stat) + "Process sentinel for 'git clone' processes." (let ((cmd (git--join (process-command proc)))) (cond ((string= stat "finished\n") (message "%s : %s" (git--bold-face "Cloned") cmd)) @@ -627,44 +609,40 @@ properly expanded tree." (message "%s : %s" git--msg-critical cmd))))) (defun git--clone (&rest args) - "Execute 'git-clone' with 'args' and set sentinal -and finally 'git--clone-sentinal' is called" - - (let ((proc (apply #'start-process "git-clone" nil "git-clone" args))) - (set-process-sentinel proc 'git--clone-sentinal) + "Execute 'git clone ARGS', with a process sentinel showing status." + (let ((proc (apply #'start-process "git-clone" nil "git" "clone" args))) + (set-process-sentinel proc 'git--clone-sentinel) (message "%s : %s" (git--bold-face "Run") (git--join (process-command proc))))) (defun git--commit (msg &rest args) - "Execute 'git-commit' with 'args' and pipe the 'msg' string" - + "Execute 'git commit ARGS', pipe the MSG string" (git--trim-string (apply #'git--exec-pipe "commit" msg "-F" "-" args))) (defun git--reset (&rest args) - "Execute 'git-rest' with 'args' and return the result as string" - + "Execute 'git reset ARGS', return the result string." (apply #'git--exec-string "reset" args)) -(defsubst git--config (&rest args) - "Execute git-config with args" - - (git--trim-string (apply #'git--exec-string "config" args))) +(defun git--config (&rest args) + "Execute 'git config ARGS', return the result string. Return empty +if git config fails (behaviour if unconfigured as of version 1.7.1)." + (condition-case nil + (git--trim-string (apply #'git--exec-string "config" args)) + (error ""))) (defun git--add (files) - "Execute git-add for each files" - + "Execute 'git add' with the sequence FILES." (when (stringp files) (setq files (list files))) (apply #'git--exec-string "add" files)) (defun git--mv (src dst) - "Execute git-mv for src and dst" + "Execute git mv for SRC and DST files." (git--exec-string "mv" src dst)) (defun git--tag (&rest args) - "Execute 'git-tag' with 'args' and return the result as string" - + "Execute 'git tag ARGS', return the result as string." (apply #'git--exec-string "tag" args)) (defvar git--tag-history nil "History variable for tags entered by user.") @@ -674,7 +652,6 @@ and finally 'git--clone-sentinal' is called" "Create a new tag for the current commit, or a specified one. git-snapshot is an alias to this. Returns the previous target of the tag, nil if none." - (interactive) (let* ((friendly-commit (if commit (git--bold-face commit) "current tree")) @@ -696,11 +673,10 @@ nil if none." (defun git--tag-list () "Get the list of known git tags, which may not always refer to commit objects" - (split-string (git--tag "-l") "\n" t)) (defsubst git--diff-raw (args &rest files) - "Execute 'git-diff --raw' with 'args' and 'files' at current buffer. This + "Execute 'git diff --raw' with 'args' and 'files' at current buffer. This gives, essentially, file status." ;; git-diff abbreviates by default, and also produces a diff. (apply #'git--exec-buffer "diff" "-z" "--full-index" "--raw" "--abbrev=40" @@ -756,14 +732,14 @@ gives, essentially, file status." fileinfo)) (defun git--symbolic-ref (arg) - "Execute git-symbolic-ref with 'arg' and return sha1 string, or nil if the + "Execute 'git symbolic-ref ARG' and return the sha1 string, or nil if the arg is not a symbolic ref." (let ((commit (git--exec-string-no-error "symbolic-ref" "-q" arg))) (when (> (length commit) 0) (car (split-string commit"\n"))))) (defun git--current-branch () - "Execute git-symbolic-ref of 'HEAD' and return branch name string. Returns + "Execute 'git symbolic-ref'HEAD' and return branch name string. Returns nil if there is no current branch." (let ((branch (git-in-lowest-existing-dir nil (git--symbolic-ref "HEAD")))) (when branch @@ -771,25 +747,14 @@ nil if there is no current branch." (substring branch (match-end 0)) branch)))) -(defsubst git--rev-list (&rest args) - "Execute git-rev-list with 'arg' and print the result to the current buffer" - - (apply #'git--exec-buffer "rev-list" args)) - (defsubst git--log (&rest args) - "Execute git-log with 'arg' and return result string" - + "Execute 'git log ARGS' and return result string" (apply #'git--exec-string "log" "-z" args)) -(defsubst git--last-log () - "Get the last log" - - (git--log "--max-count=1" "--pretty=full")) - (defsubst git--last-log-short () - "Get the last log as short form" - - (git--trim-string (git--log "--max-count=1" "--pretty=oneline"))) + "Get the last log, as one line: " + (git--trim-string (git--log "--max-count=1" "--pretty=oneline" + "--abbrev-commit"))) (defsubst git--last-log-message () "Return the last commit message, as a possibly multiline string, with an " @@ -865,15 +830,14 @@ only checks the specified files. The list is sorted by filename." (sort fileinfo 'git--fileinfo-lessp))) (defsubst git--to-type-sym (type) - "Change string symbol type to 'blob, 'tree or 'commit (i.e. submodule)" - + "Convert a string type from git to 'blob, 'tree or 'commit (i.e. submodule)" (cond ((string= type "blob") 'blob) ((string= type "tree") 'tree) ((string= type "commit") 'commit) (t (error "strange type : %s" type)))) (defun git--ls-tree (&rest args) - "Execute git-ls-tree with args and return the result as the list of 'git--fileinfo'" + "Execute 'git ls-tree ARGS', return a list of git--fileinfo structs." (let (fileinfo) (with-temp-buffer @@ -904,6 +868,7 @@ only checks the specified files. The list is sorted by filename." (sort fileinfo 'git--fileinfo-lessp))) (defun git--merge (&rest args) + "Run 'git merge ARGS', output the return message to the user." (message "%s" (git--trim-string (apply #'git--exec-string "merge" args)))) (defsubst git--branch (&rest args) @@ -919,34 +884,18 @@ SIZE is 5, but it will be longer if needed (due to conflicts)." (defsubst git--today () (time-stamp-string "%:y-%02m-%02d %02H:%02M:%02S")) -(defsubst git--interpret-state-mode-color (stat) - "Interpret git state symbol to mode line color" - - (case stat - ('modified "tomato" ) - ('unknown "gray" ) - ('added "blue" ) - ('deleted "red" ) - ('unmerged "purple" ) - ('uptodate "GreenYellow" ) - ('staged "yellow" ) - (t "red"))) - - ;;----------------------------------------------------------------------------- ;; git application ;;----------------------------------------------------------------------------- (defsubst git--managed-on-git? () - "Check see if vc-git mode is on the vc-git" - + "Returns true if we're in a git repository." (not (string-match "fatal: Not a git repository" (git--rev-parse "HEAD")))) (defun git--status-file (file) - "Return the status of the file" - + "Return the git status of FILE, as a symbol." (let ((fileinfo (git--status-index file))) (unless fileinfo (setq fileinfo (git--ls-files file))) (when (= 1 (length fileinfo)) @@ -975,25 +924,21 @@ SIZE is 5, but it will be longer if needed (due to conflicts)." (cons (nreverse branches) current-branch))) (defun git--cat-file (buffer-name &rest args) - "Execute git-cat-file and return the buffer with the file content" - + "Execute 'git cat-file ARGS' and return a new buffer named BUFFER-NAME +with the file content" (let ((buffer (get-buffer-create buffer-name))) (with-current-buffer buffer - - ;; set buffer writable (setq buffer-read-only nil) (erase-buffer) - ;; set tricky auto mode for highlighting + ;; auto mode, for highlighting (let ((buffer-file-name buffer-name)) (set-auto-mode)) - - ;; ok cat file to buffer (apply #'git--exec-buffer "cat-file" args) ;; set buffer readonly & quit (setq buffer-read-only t) - ;; check see if failed + ;; Failed? (goto-char (point-min)) (when (looking-at "^\\([Ff]atal\\|[Ff]ailed\\|[Ee]rror\\):") (let ((msg (buffer-string))) @@ -1002,89 +947,104 @@ SIZE is 5, but it will be longer if needed (due to conflicts)." (error "%s" (git--trim-tail msg))))) buffer)) -(defsubst git--select-branch (&rest excepts) - "Select the branch" +(defun git--select-branch (&rest excepts) + "Prompts the user for a branch name. Offers all the branches for completion, +except EXCEPTS. Returns the user's selection." (let ((branches (car (git--branch-list)))) (git--select-from-user - "Select Branch : " + "Select branch: " (delq nil (mapcar (lambda (b) (unless (member b excepts) b)) branches))))) +;; ================================================================================ +;; I will revise this code laster this week +;; ================================================================================ (defun git-pull-ff-only () "Interactive git pull. Prompts user for a remote branch, and pulls from it. - This command will fail if we can not do a ff-only pull from the remote branch. - " + This command will fail if we can not do a ff-only pull from the remote branch." (interactive) - (let ((remote (git--select-remote (concat - "Select remote for pull (local branch is " (git--current-branch) "): " - )))) - (message (git--pull-ff-only remote)) - ) - ) + (let ((remote (git--select-remote + (concat "Select remote for pull (local branch:" + (git--current-branch) + "): ")))) + (message (git--pull-ff-only remote)))) +;; XXX. should this be implemented list this way? umm.. (defsubst git--select-remote (prompt &rest excepts) "Select remote branch interactively." (let ((remotes (git--symbolic-commits '("remotes")))) - (git--select-from-user - prompt - (delq nil (mapcar (lambda (b) (unless (member b excepts) b)) - remotes)) - ))) + (git--select-from-user prompt + (delq nil (mapcar (lambda (b) (unless (member b excepts) b)) + remotes))))) (defun git--pull-ff-only (remote) "Pull from remote into current branch, but only on a fast-forward pull." (let ((split-remote (split-string remote "/")) - (parse-success-string (lambda (resultstring) ;; Parses success string - (let ((lines (split-string resultstring "\n"))) - (if (string-equal (nth 2 lines) "Already up-to-date.") - "Already up-to-date." - (let ((revision-change (split-string (cadr (split-string (nth 2 lines) )) "\\.\\."))) - (concat "Pulled revisions from " (car revision-change) " to " (cadr revision-change) "." (nth (- (length lines) 2) lines))))))) - ) + (parse-success-string (lambda (resultstring) ;; Parses success string + (let ((lines (split-string resultstring "\n"))) + (if (string-equal (nth 2 lines) "Already up-to-date.") + "Already up-to-date." + (let ((revision-change + (split-string (cadr (split-string (nth 2 lines) )) + "\\.\\."))) + (concat "Pulled revisions from " + (car revision-change) + " to " + (cadr revision-change) + "." (nth (- (length lines) 2) lines)))))))) (let ((remote-name (car split-remote)) - (remote-branch (car (cdr split-remote))) - ) + (remote-branch (car (cdr split-remote)))) (condition-case err - (progn - (funcall parse-success-string (git--exec-string "pull" "--ff-only" remote-name (concat remote-branch))) - ) - (error (error-message-string err)) - )))) + (progn + (funcall parse-success-string + (git--exec-string "pull" "--ff-only" remote-name (concat remote-branch)))) + (error (error-message-string err)))))) (defun git--split-porcelain (resultstring) - (mapcar (lambda (s) (split-string s "\t")) (split-string resultstring "\n")) - ) + (mapcar (lambda (s) (split-string s "\t")) (split-string resultstring "\n"))) (defun git--n-n-th (i j arr) "Given a 2-d list, access the i,j 'th element" - (nth j (nth i arr)) - ) + (nth j (nth i arr))) + +(defun git-push-ff-only () + "Interactive git-push command." + (interactive) + (let ((remote (git--select-remote (concat + "Select remote for push (local branch is " (git--current-branch) "): " + )))) + (condition-case err + (git--push-ff-only-handle-success remote) + (error (error-message-string err)) + ))) (defun git--actual-push (remote-name remote-branch) (let (( actual-run-output (git--split-porcelain (git--exec-string "push" "--porcelain" remote-name (concat (git--current-branch) ":" remote-branch))))) (message (concat "Pushed changes " (git--n-n-th 1 2 dry-run-output) " to remote " remote-name "/" remote-branch))) ) -(defun git--push-ff-only (remote) +(defun git--push-ff-only-handle-success (remote) "Pushes from current branch into remote, fast-forward only." (let ((split-remote (split-string remote "/"))) - (let ((dry-run-output (git--split-porcelain (git--exec-string "push" "--dry-run" "--porcelain" (car split-remote) (concat (git--current-branch) ":" (cadr split-remote)))))) + (let ((dry-run-output (git--split-porcelain + (git--exec-string "push" "--dry-run" "--porcelain" + (car split-remote) + (concat (git--current-branch) ":" (cadr split-remote)))))) (let ((newbranch (string-equal (git--n-n-th 1 2 dry-run-output) "[new branch]")) - (change-diff (git--n-n-th 1 2 dry-run-output)) - (up-to-date (string-equal (git--n-n-th 1 0 dry-run-output) "Everything up-to-date")) - ) - (cond (newbranch (if (y-or-n-p (concat "Pushing will create branch " (cadr split-remote) " in remote. Continue? ")) - (git--actual-push (car split-remote) (cadr split-remote)) - (message "Did not push."))) - (up-to-date (message "Remote branch is up to date.")) - ((not newbranch) (git--actual-push (car split-remote) (cadr split-remote))) - (t (message "Did not push.")) - ) - ) - ))) + (change-diff (git--n-n-th 1 2 dry-run-output)) + (up-to-date (string-equal (git--n-n-th 1 0 dry-run-output) "Everything up-to-date"))) + (cond (newbranch (if (y-or-n-p (concat "Pushing will create branch " + (cadr split-remote) + " in remote. Continue? ")) + (git--actual-push (car split-remote) (cadr split-remote)) + (message "Did not push."))) + (up-to-date (message "Remote branch is up to date.")) + ((not newbranch) (git--actual-push (car split-remote) (cadr split-remote))) + (t (message "Did not push."))))))) +;; -------------------------------------------------------------------------------- (defun git--symbolic-commits (&optional reftypes) - "Find symbolic names referring to commits, using git-for-each-ref. + "Find symbolic names referring to commits, using 'git for-each-ref'. REFTYPES is a list of types of refs under .git/refs ; by default, (\"heads\" \"tags\" \"remotes\") , which gives branches, tags and remote branches. Returns a list of the refs found (as strings), in the order @@ -1149,8 +1109,7 @@ pending commit buffer or nil if the buffer wasn't needed." ;;----------------------------------------------------------------------------- (defun git--update-modeline () - "Update modeline state dot mark properly" - + "Update the current's buffer modeline state display." ;; mark depending on the fileinfo state (when (and buffer-file-name (git--in-vc-mode?)) (git--update-state-mark @@ -1159,7 +1118,7 @@ pending commit buffer or nil if the buffer wasn't needed." (defalias 'git-history 'git-log) (defadvice vc-after-save (after git--vc-git-after-save activate) - "vc-after-save advice for synchronizing when saving buffer" + "vc-after-save advice for updating status" (when (git--in-vc-mode?) (git--update-modeline))) @@ -1187,11 +1146,11 @@ pending commit buffer or nil if the buffer wasn't needed." ad-do-it))) ;;----------------------------------------------------------------------------- -;; public functions +;; Low-level commit support. ;;----------------------------------------------------------------------------- (defun git--config-get-author () - "Find appropriate user.name" + "Returns the user's name, either from git or from emacs's config." (let ((config-user-name (git--config "user.name"))) (or (and (not (string= config-user-name "")) config-user-name) @@ -1199,8 +1158,7 @@ pending commit buffer or nil if the buffer wasn't needed." (and (boundp 'user-full-name) user-full-name)))) (defun git--config-get-email () - "Find appropriate user.email" - + "Returns the user's email, either from git or from emacs's config." (let ((config-user-email (git--config "user.email"))) (or (and (not (string= config-user-email "")) config-user-email) (and (fboundp 'user-mail-address) (user-mail-address)) @@ -1210,6 +1168,8 @@ pending commit buffer or nil if the buffer wasn't needed." "Insert the log header to the buffer. If AMEND, grab the info from the last commit, like git commit --amend will do once we commit." + ;; TODO: we should make git always use this info, even if its config + ;; has changed. Otherwise this is misleading. (insert git--log-header-line "\n" "# Branch : " (or (git--current-branch) "") "\n") (if amend @@ -1241,9 +1201,8 @@ commit, like git commit --amend will do once we commit." (make-variable-buffer-local 'git--commit-amend) (defun git--commit-buffer () - "When you press C-cC-c after editing log, this function is called -Trim the buffer log and commit" - + "Called when the user commits, in the commit buffer (C-cC-c). +Trim the buffer log, commit runs any after-commit functions." (interactive) ;; check buffer @@ -1279,9 +1238,13 @@ Trim the buffer log and commit" ;; hooks (e.g. switch branch) (run-hooks 'local-git--commit-after-hook 'git--commit-after-hook))) +;;----------------------------------------------------------------------------- +;; Merge support. +;;----------------------------------------------------------------------------- + (defun git--resolve-fill-buffer (template side) - "Make the new buffer based on the conflicted template on each -side ('local or 'remote). TEMPLATE is the original buffer. Returns the buffer." + "Make a buffer showing a SIDE ('local or 'remote) of the conflict +buffer TEMPLATE. Returns the buffer." (let* ((filename (file-relative-name (buffer-file-name template))) (buffer-name (format "*%s*: %s" @@ -1369,7 +1332,6 @@ and the user accepts the result." (filename (file-relative-name buffer-file-name)) (our-buffer (git--resolve-fill-buffer result-buffer 'local)) (their-buffer (git--resolve-fill-buffer result-buffer 'remote)) - ;; there seems to be a bug with ancestor handling in emacs-snapshot (base-buffer (git--resolve-fill-base)) (config (current-window-configuration)) (ediff-default-variant 'combined) @@ -1490,11 +1452,15 @@ the user quits or the merge is successfully committed." (defun git-resolve-merge () - "Resolve merge for the current buffer" - + "Resolve git merge conflicts in the current buffer with ediff." (interactive) (git--resolve-merge-buffer)) + +;;----------------------------------------------------------------------------- +;; High-level commit functions. +;;----------------------------------------------------------------------------- + (defconst git--commit-status-font-lock-keywords '(("^#\t\\([^:]+\\): +[^ ]+$" (1 'git--bold-face)) @@ -1695,10 +1661,16 @@ a prefix argument, is specified, does a commit --amend." (git--add ".") (git-commit-all)))) +;;----------------------------------------------------------------------------- +;; Miscellaneous high-level functions. +;;----------------------------------------------------------------------------- + (defun git-clone (dir) - "Clone from repository" + "Clone a repository (prompts for the URL) into the local directory DIR ( +prompts if unspecified)." + + (interactive "DLocal destination directory: ") - (interactive "DLocal Directory : ") (let ((repository (git--select-from-user "Clone repository: " git--repository-bookmarks @@ -1769,8 +1741,7 @@ revert operation, instead popping up a commit buffer." :group 'git-emacs) (defun gitk () - "Launch gitk in emacs" - + "Launch gitk in the current directory." (interactive) (start-process "gitk" nil gitk-program)) @@ -1834,10 +1805,8 @@ once the checkout is complete." (git-after-working-dir-change repo-dir)))))) (defun git-delete-branch (&optional branch) - "Delete branch after selecting branch" - + "Delete BRANCH, prompt the user if unspecified." (interactive) - ;; select branch if not assigned (unless branch (setq branch (git--select-branch "master"))) (let ((saved-head (git--abbrev-commit branch 10))) ; safer abbrev @@ -1846,8 +1815,7 @@ once the checkout is complete." (git--bold-face branch) saved-head))) (defun git-delete-tag (tag) - "Delete tag after selecting tag" - + "Delete TAG, prompt the user if unspecified." (interactive (list (git--select-from-user "Delete tag: " (git--tag-list)))) (let ((saved-tag-target @@ -1873,6 +1841,99 @@ the result as a message." (sit-for 2) (git-after-working-dir-change)) + + +(defun git-config-init () + "Set initial configuration, it query the logined user information" + + (interactive) + + (let ((name (git--trim-string (git--config "user.name"))) + (email (git--trim-string (git--config "user.email")))) + + (when (or (null name) (string= "" name)) + (setq name (read-from-minibuffer "User Name : " + (git--config-get-author))) + + (git--config "--global" "user.name" name)) + + (when (or (null email) (string= "" email)) + (setq email (read-from-minibuffer "User Email : " + (git--config-get-email))) + + (git--config "--global" "user.email" email)) + + (message "Set user.name(%s) and user.email(%s)" name email))) + +(defun git-ignore (file-or-glob) + "Add FILE-OR-GLOB to .gitignore. Prompts the user if interactive." + (interactive "sIgnore file or glob: ") + (with-temp-buffer + (insert file-or-glob "\n") + (append-to-file (point-min) (point-max) + (expand-file-name ".gitignore" (git--get-top-dir))))) + +(defun git-switch-branch (&optional branch) + "Git switch branch, selecting from a list of branches." + (interactive) + (git--maybe-ask-save) + (git--maybe-ask-and-commit + (lexical-let ((branch branch) + (repo-dir default-directory)) + (lambda() + (let ((branch (or branch (git--select-branch (git--current-branch))))) + (git--checkout branch) + (git-after-working-dir-change repo-dir)))))) + +(defun git-add () + "Add files to index. If executed in a buffer currently under git control, +adds the current contents of that file to the index. Otherwise, prompts +for new files to add to git." + (interactive) + (if (not (and buffer-file-name (git--in-vc-mode?))) + (git-add-new) + (let ((rel-filename (file-relative-name buffer-file-name))) + (if (not (git--ls-files "--modified" "--" rel-filename)) + (error "%s is already current in the index" rel-filename) + (when (y-or-n-p (format "Add the current contents of %s to the index? " + rel-filename)) + (git--add rel-filename) + (git--update-modeline)))))) + +(defun git-add-new () + "Add new files to the index, prompting the user for filenames or globs" + (interactive) + ;; TODO: ido doesn't give good feedback on globs + (let* ((choices (mapcar #'(lambda (fi) + (git--fileinfo->name fi)) + (git--ls-files "--others" "--exclude-standard"))) + (default-choice ;; the current file, if it's a choice + (when buffer-file-name + (let ((current-file (file-relative-name buffer-file-name))) + (when (member current-file choices) + (message "default: %S" current-file) current-file)))) + (files (git--select-from-user "Add new files (glob) >> " + choices nil default-choice)) + (matched-files (mapcar #'(lambda (fi) (git--fileinfo->name fi)) + (git--ls-files "--others" "--exclude-standard" + "--" files)))) + (if (not matched-files) (error "No files matched \"%s\"" files) + (let ((output (replace-regexp-in-string "[\s\n]+$" "" + (git--add matched-files)))) + (if (not (equal output "")) + (error "git: %s" output) + (message "Added %s to git" (git--join matched-files ", ")) + ;; refresh vc-git. + (dolist (filename matched-files) + (let ((added-file-buffer (get-file-buffer filename))) + (when added-file-buffer + (with-current-buffer added-file-buffer (vc-find-file-hook)))))) + )))) + +;;----------------------------------------------------------------------------- +;; Low-level diff functions +;;----------------------------------------------------------------------------- + (defun git--diff (file rev &optional before-ediff-hook after-ediff-hook) "Starts an ediff session between the FILE and its specified revision. REVISION should not include the filename, e.g. \"HEAD:\". If @@ -1974,96 +2035,6 @@ buffer instead of a new one." (vc-exec-after `(goto-char (point-min)))) (pop-to-buffer buffer))) - -(defun git-config-init () - "Set initial configuration, it query the logined user information" - - (interactive) - - (let ((name (git--trim-string (git--config "user.name"))) - (email (git--trim-string (git--config "user.email")))) - - (when (or (null name) (string= "" name)) - (setq name (read-from-minibuffer "User Name : " - (git--config-get-author))) - - (git--config "--global" "user.name" name)) - - (when (or (null email) (string= "" email)) - (setq email (read-from-minibuffer "User Email : " - (git--config-get-email))) - - (git--config "--global" "user.email" email)) - - (message "Set user.name(%s) and user.email(%s)" name email))) - -(defun git-ignore (ignored-opt) - "Add ignore file" - - (interactive "sIgnore Option : ") - - (with-temp-buffer - (insert ignored-opt "\n") - (append-to-file (point-min) (point-max) ".gitignore"))) - -(defun git-switch-branch (&optional branch) - "Git switch branch, selecting from a list of branches." - (interactive) - (git--maybe-ask-save) - (git--maybe-ask-and-commit - (lexical-let ((branch branch) - (repo-dir default-directory)) - (lambda() - (let ((branch (or branch (git--select-branch (git--current-branch))))) - (git--checkout branch) - (git-after-working-dir-change repo-dir)))))) - -(defun git-add () - "Add files to index. If executed in a buffer currently under git control, -adds the current contents of that file to the index. Otherwise, prompts -for new files to add to git." - (interactive) - (if (not (and buffer-file-name (git--in-vc-mode?))) - (git-add-new) - (let ((rel-filename (file-relative-name buffer-file-name))) - (if (not (git--ls-files "--modified" "--" rel-filename)) - (error "%s is already current in the index" rel-filename) - (when (y-or-n-p (format "Add the current contents of %s to the index? " - rel-filename)) - (git--add rel-filename) - (git--update-modeline)))))) - -(defun git-add-new () - "Add new files to the index, prompting the user for filenames or globs" - (interactive) - ;; TODO: ido doesn't give good feedback on globs - (let* ((choices (mapcar #'(lambda (fi) - (git--fileinfo->name fi)) - (git--ls-files "--others" "--exclude-standard"))) - (default-choice ;; the current file, if it's a choice - (when buffer-file-name - (let ((current-file (file-relative-name buffer-file-name))) - (when (member current-file choices) - (message "default: %S" current-file) current-file)))) - (files (git--select-from-user "Add new files (glob) >> " - choices nil default-choice)) - (matched-files (mapcar #'(lambda (fi) (git--fileinfo->name fi)) - (git--ls-files "--others" "--exclude-standard" - "--" files)))) - (if (not matched-files) (error "No files matched \"%s\"" files) - (let ((output (replace-regexp-in-string "[\s\n]+$" "" - (git--add matched-files)))) - (if (not (equal output "")) - (error "git: %s" output) - (message "Added %s to git" (git--join matched-files ", ")) - ;; refresh vc-git. - (dolist (filename matched-files) - (let ((added-file-buffer (get-file-buffer filename))) - (when added-file-buffer - (with-current-buffer added-file-buffer (vc-find-file-hook)))))) - )))) - - ;;----------------------------------------------------------------------------- ;; branch mode ;;----------------------------------------------------------------------------- @@ -2102,7 +2073,7 @@ been displayed.") (defun git--branch-mode () - "Set current buffer as branch-mode" + "Initialize branch mode buffer." (kill-all-local-variables) (buffer-disable-undo) @@ -2220,7 +2191,7 @@ preserve the cursor position." (defun git-branch () - "Launch git-branch mode and return the resulting buffer." + "Pop up a git branch mode buffer and return it." (interactive) @@ -2478,7 +2449,7 @@ that variable in .emacs. ;; Add interactively ;;----------------------------------------------------------------------------- (defun git-add-interactively() - "A friendly replacement git add -i, using ediff" + "A friendly replacement for git add -i, using ediff" (interactive) ; haha (git--require-buffer-in-git) (git--diff @@ -2542,9 +2513,9 @@ that variable in .emacs. (remove-hook 'grep-setup-hook git-grep-setup-hook)))) -;; ----------------------------------------------------------------------------- -;; stash support -;; ----------------------------------------------------------------------------- +;;----------------------------------------------------------------------------- +;; Minimalistic stash support. +;;----------------------------------------------------------------------------- (defconst git--stash-list-font-lock-keywords '(("^\\(stash@{\\([^}]*\\)}\\):" (1 font-lock-function-name-face prepend) @@ -2619,4 +2590,5 @@ usual pre / post work: ask for save, ask for refresh." (sleep-for 1.5) ; let the user digest message (git-after-working-dir-change)) -(provide 'git-emacs) + +(provide 'git-emacs) \ No newline at end of file diff --git a/git-global-keys.el b/git-global-keys.el index c00a294..a84e8ad 100644 --- a/git-global-keys.el +++ b/git-global-keys.el @@ -1,4 +1,7 @@ -;; See git-emacs.el for License information +;; Global keys for git-emacs. +;; +;; See git-emacs.el for license and versioning. + (require 'easymenu) (defcustom git-keyboard-prefix "\C-xg" diff --git a/git-load-example.eh b/git-load-example.eh new file mode 100644 index 0000000..7c65704 --- /dev/null +++ b/git-load-example.eh @@ -0,0 +1,8 @@ +;;============================================================ +;; git-emacs mode +;;------------------------------------------------------------ +(eval-when-compile (require 'soo-load)) +(setq git-state-modeline-decoration 'git-state-decoration-large-dot) +(require 'git-emacs-autoloads) +;;------------------------------------------------------------ + diff --git a/git-log.el b/git-log.el index 013f906..6b15e5b 100644 --- a/git-log.el +++ b/git-log.el @@ -1,3 +1,5 @@ +;; Git log mode support, part of git-emacs +;; ;; See git-emacs.el for license information (require 'log-view) diff --git a/git-modeline.el b/git-modeline.el index e0b7270..0d607ec 100644 --- a/git-modeline.el +++ b/git-modeline.el @@ -1,7 +1,9 @@ +;; Mode line decoration support, part of git-emacs. +;; +;; See git-emacs.el for license and versioning. ;; ;; ref. "test-runner-image.el" posted at ;; "http://nschum.de/src/emacs/test-runner/" -;; (require 'git-emacs) @@ -25,6 +27,19 @@ of mode-line-format." :group 'git-emacs ) +(defun git--interpret-state-mode-color (stat) + "Return a mode line status color appropriate for STAT (a state symbol)." + (case stat + ('modified "tomato" ) + ('unknown "gray" ) + ('added "blue" ) + ('deleted "red" ) + ('unmerged "purple" ) + ('uptodate "GreenYellow" ) + ('staged "yellow" ) + (t "red"))) + + ;; Modeline decoration options (defun git-state-decoration-small-dot(stat) (git--state-mark-modeline-dot @@ -66,7 +81,7 @@ static char * data[] = { \" +++++ \", \" \"};")) -(defsubst git--interpret-state-mode-letter(stat) +(defun git--interpret-state-mode-letter(stat) (case stat ('modified "M") ('unknown "?") diff --git a/git-status.el b/git-status.el index 3556760..8a14621 100644 --- a/git-status.el +++ b/git-status.el @@ -1,4 +1,6 @@ -;; See git-emacs.el for license and versioning +;; Git status buffer support, part of git-emacs. +;; +;; See git-emacs.el for license and versioning. (require 'git-emacs) ; main module (require 'ewoc) ; view @@ -28,7 +30,6 @@ (defun git--refresh-desc () "Refresh the git-status-mode header description" - (ewoc-set-hf git--status-view (concat (git--bold-face "Directory") " : " default-directory "\n" @@ -90,14 +91,12 @@ (defsubst git--status-node-mark (info) "Render status view node mark" - (propertize (if (git--fileinfo->marked info) "*" " ") 'face 'git--mark-face)) (defsubst git--status-node-stat (info) "Render status view node state" - (let ((stat (git--fileinfo->stat info))) (propertize (capitalize (symbol-name stat)) 'face @@ -113,7 +112,6 @@ (defsubst git--status-node-perm (info) "Render status view node permission" - (or (git--fileinfo->perm info) "------")) (defun git--status-human-readable-size (size) @@ -131,14 +129,12 @@ to ls -sh; e.g. 29152 -> 28K." (defsubst git--status-node-size (info) "Render status view node size" - (let ((size (git--fileinfo->size info))) (if (not size) "" (git--status-human-readable-size size)))) (defsubst git--status-node-name (info) "Render status view node name" - (let ((name (git--fileinfo->name info)) (type (git--fileinfo->type info))) @@ -220,7 +216,6 @@ to ls -sh; e.g. 29152 -> 28K." (defun git-status (dir) "Launch git-status-mode on the specified directory. With a prefix argument (C-u), always prompts." - (interactive (list (git--get-top-dir-or-prompt "Select directory: " (when current-prefix-arg t)))) @@ -242,7 +237,6 @@ argument (C-u), always prompts." (defsubst git--clear-status () "Clear the git-status-view" - (ewoc-filter git--status-view #'(lambda (info) nil)) (ewoc-refresh git--status-view) (let ((buffer-read-only nil)) (erase-buffer))) @@ -250,11 +244,11 @@ argument (C-u), always prompts." (defsubst git--status-tree () (git--ls-tree "HEAD")) (defsubst git--status-map (node pred) - "Iterating 'git--status-view' by using 'ewoc-next and return the next node. -The predicate function should get 'node and 'data arguments and -it should return t or nil. If predicate returned nil we continue to scan, -otherwise stop and return the node." - + "Iterate over git--status-view nodes by using 'ewoc-next. Stops +when PRED returns t and returns that node. The predicate function +should get 'node and 'data arguments and it should return t or +nil. If predicate returned nil we continue to scan, otherwise +stop and return the node." (let ((data nil) (cont t)) @@ -265,9 +259,8 @@ otherwise stop and return the node." node)) -;; TODO -> binary search (defun git--status-view-dumb-update-element (fi) - "Add update 'fi' to 'git--status-view' thoughtlessly!" + "Add updated fileinfo FI to `git--status-view'. Slow, right now." (unless (git--status-map (ewoc-nth git--status-view 0) #'(lambda (node data) @@ -275,11 +268,11 @@ otherwise stop and return the node." (ewoc-enter-before git--status-view node fi)))) (ewoc-enter-last git--status-view fi))) -(defun git--status-view-update-state (fileinfo) - "Update the state-view elements in fileinfo" +(defun git--status-view-update-state (fileinfos) + "Update the state of the status view nodes corresponding to FILEINFOS." - (let ((hashed-info (make-hash-table :test 'equal :size (length fileinfo)))) - (dolist (fi fileinfo) + (let ((hashed-info (make-hash-table :test 'equal :size (length fileinfos)))) + (dolist (fi fileinfos) (puthash (git--fileinfo->name fi) fi hashed-info)) (ewoc-collect git--status-view @@ -363,7 +356,7 @@ are very deep (used when repositioning mark on refresh)." (defun git--status-view-update () - "Friendly update view function" + "Update the state of all changed files." (let ((fileinfos (sort (git--status-index) #'git--fileinfo-lessp))) (git--status-view-update-expand-tree fileinfos) @@ -458,10 +451,10 @@ are very deep (used when repositioning mark on refresh)." ;; Use the sub-maps from git-global-keys for diffs. (define-key map "d" (copy-keymap git--diff-buffer-map)) (define-key map "D" (copy-keymap git--diff-all-map)) - (define-key map "b" 'git--status-view-switch-branch) + (define-key map "b" 'git-switch-branch) (define-key map "!" 'git--status-view-resolve-merge) - (define-key map "." 'git--status-view-git-cmd) - (define-key map "k" 'git--status-view-gitk) + (define-key map "." 'git-cmd) + (define-key map "k" 'gitk) (define-key map "L" 'git-log-files) (define-key map "g" 'git--status-view-refresh) (define-key map "a" 'git--status-view-add) @@ -476,7 +469,7 @@ are very deep (used when repositioning mark on refresh)." (define-key map "c" (copy-keymap git--commit-map)) (define-key map "R" 'git-reset) - (define-key map "\C-m" 'git--status-view-do-propriate) + (define-key map "\C-m" 'git--status-view-open-or-expand) (setq git-status-mode-map map)) @@ -515,7 +508,7 @@ are very deep (used when repositioning mark on refresh)." ["Unmark" git--status-view-unmark-and-next t] "----" ["Branch Mode" git-branch t] - ["Switch to Branch..." git--status-view-switch-branch t] + ["Switch to Branch..." git-switch-branch t] ("Commit" ["All Changes" git-commit-all :keys "c RET" :active t] ["Index" git-commit :keys "c i" :active t] @@ -526,8 +519,8 @@ are very deep (used when repositioning mark on refresh)." ["Revert" git-revert t] ["Log for Project" git-log t] "----" - ["Git Command" git--status-view-git-cmd t] - ["GitK" git--status-view-gitk t] + ["Git Command" git-cmd t] + ["GitK" gitk t] "----" ["Quit" git--status-view-quit t])) @@ -599,13 +592,10 @@ them)." (setf (git--fileinfo->expanded data) t)))) (defun git--shrink-tree (node) - "Shrink 'node' in 'git--status-view', but node->type should be 'tree" - + "Shrink 'node' in 'git--status-view'. node->type should be 'tree" (let* ((data (ewoc-data node)) (name (git--fileinfo->name data))) - (unless (git--fileinfo-is-dir data) (error "type should be 'tree")) - (when (git--fileinfo->expanded data) ;; make regexp "node->name/" (git--status-delete-after-regex @@ -616,9 +606,7 @@ them)." (defun git--status-view-expand-tree-toggle () "Expand if tree is not expanded otherwise close the tree" - (interactive) - (let* ((node (ewoc-locate git--status-view)) (node-info (ewoc-data node))) (when (and node node-info @@ -632,7 +620,7 @@ them)." ;;----------------------------------------------------------------------------- (defun git--status-view-forward-line (n) - "Move to forward on the status view item" + "Move forward by N lines in the status view." (interactive "p") @@ -646,21 +634,21 @@ them)." (move-to-column git--status-line-column)) (defun git--status-view-first-line () - "Move to the first item" + "Move to the first item in the status view." (interactive) (goto-char (point-min)) (git--status-view-forward-line 1)) (defun git--status-view-last-line () - "Move to the last item" + "Move to the last item in the status view." (interactive) (goto-char (point-max)) (git--status-view-forward-line -1)) (defun git--forward-meaningful-line (move) - "Implementation of forward meaningful line" + "Call MOVE until we end up on a meaningful line (i.e. one with updates)." (let ((start-node (ewoc-locate git--status-view))) (funcall move 1) @@ -671,65 +659,56 @@ them)." (funcall move 1)))) (defun git--status-view-next-line (&optional n) - "Move to the next line" - + "Move to the next line in the status view." (interactive "p") - (if (eql (ewoc-locate git--status-view) (ewoc-nth git--status-view -1)) (git--status-view-first-line) (git--status-view-forward-line 1))) (defun git--status-view-next-meaningful-line () - "Move to the meaningful next line" - + "Move to the next meaningful line in the status view." (interactive) (git--forward-meaningful-line 'git--status-view-next-line)) (defun git--status-view-prev-line (&optional n) - "Move to the previous line" - + "Move to the previous line in the status view." (interactive "p") - (if (eql (ewoc-locate git--status-view) (ewoc-nth git--status-view 0)) (git--status-view-last-line) (git--status-view-forward-line -1))) (defun git--status-view-prev-meaningful-line () - "Move the the meaningful previous line" - + "Move to the previous meaningful line in the status view." (interactive) (git--forward-meaningful-line 'git--status-view-prev-line)) ;;----------------------------------------------------------------------------- -;; status view marking +;; Marking. ;;----------------------------------------------------------------------------- (defun git--mark-line (marked) - "Implementation of marking" + "Sets the mark flag of the current line to MARK. Updates the view." (let ((node (ewoc-locate git--status-view))) (setf (git--fileinfo->marked (ewoc-data node)) marked) (ewoc-invalidate git--status-view node))) (defun git--status-view-mark-and-next () - "Mark and go to the next line" - + "Mark and go to the next line." (interactive) (git--mark-line t) (git--status-view-next-line)) (defun git--status-view-unmark-and-next () - "Unmark and go to the next line" - + "Unmark and go to the next line." (interactive) (git--mark-line nil) (git--status-view-next-line)) (defun git--toggle-line () - "Implementation of toggle line" - + "Toggles the marked state of the current line." (let* ((node (ewoc-locate git--status-view)) (data (ewoc-data node)) (mark (git--fileinfo->marked data))) @@ -737,48 +716,27 @@ them)." (ewoc-invalidate git--status-view node))) (defun git--status-view-toggle-and-next () - "Toggle the mark and go to next line" - + "Toggle the marked state of the current line and move to the next." (interactive) (git--toggle-line) (git--status-view-next-line)) ;;----------------------------------------------------------------------------- -;; status view independent command +;; Commands ;;----------------------------------------------------------------------------- (defun git--status-view-quit () - "Quit" - + "Quit the git status buffer." (interactive) (kill-buffer (current-buffer))) -(defun git--status-view-switch-branch () - "Switch branch" - - (interactive) - (call-interactively 'git-switch-branch)) - -(defun git--status-view-git-cmd () - "Direct git command" - - (interactive) - (call-interactively 'git-cmd)) - -(defun git--status-view-gitk () - "Launch gitk" - - (interactive) - (call-interactively 'gitk)) - (defun git--status-view-refresh () - "Refresh view" - + "Refresh git status buffer." (interactive) (git--please-wait "Reading git status" (revert-buffer))) (defun git--status-view-mark-reg (reg) - "Mark with regular expression" + "Prompt for a regular expression, mark the files that match." (interactive "sRegexp >> ") (ewoc-collect git--status-view @@ -791,44 +749,38 @@ them)." (git--status-view-next-meaningful-line)) (defun git--status-view-summary () - "To the summary mode with occur" - + "Pops up an 'occur' summary of the changed files." (interactive) (occur "[\t* ]+\\(Deleted\\|Modified\\|Unknown\\|Added\\|Staged\\)") - (message "Move with 'next-error and 'previous-error")) ;;----------------------------------------------------------------------------- -;; status view for one selected file +;; Operations on single files. ;;----------------------------------------------------------------------------- (defsubst git--status-view-select-filename () - "Return current filename of view item" - + "Return the filename of the current status view item." (let ((filename (git--fileinfo->name (ewoc-data (ewoc-locate git--status-view))))) (when (file-directory-p filename) - (error "Execute on file")) + (error "Not a file")) filename)) (defsubst git--status-view-select-type () - "Return current type of view item" - + "Return the type of the current view item." (git--fileinfo->type (ewoc-data (ewoc-locate git--status-view)))) (defun git--status-view-view-file () - "View the selected file" - + "View the selected file." (interactive) (view-file (git--status-view-select-filename))) (defun git--status-view-open-file () - "Open the selected file" - + "Open the selected file." (interactive) (find-file (git--status-view-select-filename))) (defun git--status-view-descend-submodule () - "Opens a status view on the selected submodule" + "Opens a status view on the selected submodule." (let ((submodule-dir (git--fileinfo->name (ewoc-data (ewoc-locate git--status-view))))) (git-status submodule-dir) @@ -836,22 +788,18 @@ them)." submodule-dir))) (defun git--status-view-resolve-merge () - "Resolve the conflict if necessary" - + "Resolve merge conflicts in the currently selected file (must be unmerged)." (interactive) - (let ((file (git--status-view-select-filename))) (if (eq 'unmerged (git--status-file file)) (progn (find-file (git--status-view-select-filename)) (git--resolve-merge-buffer)) - (error "Selected file is not unmerged state")))) - -(defun git--status-view-do-propriate () - "If 'tree selected -> expand or un-expand otherwise open it" + (error "File is not unmerged")))) +(defun git--status-view-open-or-expand () + "Open or expands the current file / directory / submodule." (interactive) - (case (git--status-view-select-type) ('tree (git--status-view-expand-tree-toggle)) ('blob (git--status-view-open-file)) @@ -859,10 +807,8 @@ them)." (t (error "Not supported type")))) (defun git--status-view-blame () - "Open the file as blame-mode" - + "Open the current file and enable blame mode." (interactive) - (when (eq (git--status-view-select-type) 'blob) (find-file (git--status-view-select-filename)) (git-blame-mode t))) @@ -874,7 +820,6 @@ them)." (defsubst git--status-view-marked-files () "Return a list of the marked files. Usually, `git--status-view-marked-or-file' is what you want instead." - (let (files) (ewoc-collect git--status-view #'(lambda (node) @@ -885,7 +830,6 @@ them)." (defsubst git--status-view-marked-or-file () "Return a list of the marked files, or if none, the file on the current line. You can think of this as the \"selected files\"." - (let ((files (git--status-view-marked-files))) (when (null files) (setq files (list (git--status-view-select-filename)))) @@ -893,9 +837,7 @@ current line. You can think of this as the \"selected files\"." (defun git--status-view-rm () "Delete the selected files." - (interactive) - (let* ((files (git--status-view-marked-or-file)) ;; We can't afford to use stale fileinfos here, the warnings ;; are crucial. @@ -953,35 +895,26 @@ current line. You can think of this as the \"selected files\"." (revert-buffer)) (defun git--status-view-rename () - "Rename the selected files." - + "Rename the selected file(s)." (interactive) - (let ((files (git--status-view-marked-or-file))) (dolist (src files) (let ((msg (format "%s '%s' to >> " (git--bold-face "Rename") src))) (git--mv src (file-relative-name (read-from-minibuffer msg src)))))) - (revert-buffer)) (defun git--status-view-add () - "Add the selected files." - + "Add the selected file(s) to the index." (interactive) (git--add (git--status-view-marked-or-file)) (revert-buffer)) (defun git--status-view-add-ignore () - "Add the selected file to .gitignore" - + "Add the selected file(s) to .gitignore" (interactive) - (let ((files (git--status-view-marked-or-file))) - (unless files (list (read-from-minibuffer "Add Ignore >> "))) - (dolist (file files) (git-ignore file))) - (revert-buffer)) ;;-----------------------------------------------------------------------------