{"thread":{"id":"6593","subject":"git-blame.el","startedAt":"2007-01-31T13:04:06Z","lastAt":"2007-02-04T20:04:49Z","messageCount":9,"participants":["David Kågedal","Randal L. Schwartz","Junio C Hamano","Karl Hasselström","Jakub Narebski"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"33127","messageId":"87iren2vqx.fsf@morpheus.local","threadId":"6593","inReplyTo":null,"subject":"git-blame.el","fromName":"David Kågedal","fromEmail":"davidk@lysator.liu.se","sentAt":"2007-01-31T13:04:06Z","receivedAt":"2007-01-31T13:04:06Z","isPatch":false,"sender":{"key":"davidk@lysator.liu.se","avatar":"https://avatars.githubusercontent.com/u/60530?v=4"},"body":"Here's another version of git-blame.el that automatically tries to\ncreate a sensible list of colors to use for both light and dark\nbackgrounds.  Plus a few minor fixes.\n\nTo use:\n\n  1) Load into emacs: M-x load-file RET git-blame.el RET\n  2) Open a git-controlled file\n  3) Blame: M-x git-blame-mode\n\n;;; git-blame.el\n;; David Kågedal\n\n(defun color-scale (l)\n  (let* ((colors ())\n         r g b)\n    (setq r l)\n    (while r\n      (setq g l)\n      (while g\n        (setq b l)\n        (while b\n          (push (concat \"#\" (car r) (car g) (car b)) colors)\n          (pop b))\n        (pop g))\n      (pop r))\n    colors))\n\n(defvar git-blame-dark-colors\n  (color-scale '(\"00\" \"04\" \"08\" \"0c\"\n                 \"10\" \"14\" \"18\" \"1c\"\n                 \"20\" \"24\" \"28\" \"2c\"\n                 \"30\" \"34\" \"38\" \"3c\")))\n\n(defvar git-blame-light-colors\n  (color-scale '(\"c0\" \"c4\" \"c8\" \"cc\"\n                 \"d0\" \"d4\" \"d8\" \"dc\"\n                 \"e0\" \"e4\" \"e8\" \"ec\"\n                 \"f0\" \"f4\" \"f8\" \"fc\")))\n\n(defvar git-blame-ancient-color \"dark green\")\n\n(defvar git-blame-overlays nil)\n(defvar git-blame-cache nil)\n\n(defvar git-blame-mode nil)\n(make-variable-buffer-local 'git-blame-mode)\n(push (list 'git-blame-mode \" blame\") minor-mode-alist)\n\n(defun git-blame-mode (&optional arg)\n  (interactive \"P\")\n  (if arg\n      (setq git-blame-mode (eq arg 1))\n    (setq git-blame-mode (not git-blame-mode)))\n  (make-local-variable 'git-blame-overlays)\n  (make-local-variable 'git-blame-colors)\n  (make-local-variable 'git-blame-cache)\n  (let ((bgmode (cdr (assoc 'background-mode (frame-parameters)))))\n    (if (eq bgmode 'dark)\n        (setq git-blame-colors git-blame-dark-colors)\n      (setq git-blame-colors git-blame-light-colors)))\n  (if git-blame-mode\n      (git-blame-run)\n    (git-blame-cleanup)))\n\n(defun git-blame-run ()\n  (let* ((display-buf (current-buffer))\n         (blame-buf (get-buffer-create\n                     (concat \" git blame for \" (buffer-name))))\n         (proc (start-process \"git-blame\" blame-buf\n                             \"git\" \"blame\" \"--incremental\"\n                             (file-name-nondirectory buffer-file-name))))\n    (mapcar 'delete-overlay git-blame-overlays)\n    (setq git-blame-overlays nil)\n    (setq git-blame-cache (make-hash-table :test 'equal))\n    (with-current-buffer blame-buf\n      (erase-buffer)\n      (make-local-variable 'git-blame-file)\n      (make-local-variable 'git-blame-current)\n      (setq git-blame-file display-buf)\n      (setq git-blame-current nil))\n    (set-process-filter proc 'git-blame-filter)\n    (set-process-sentinel proc 'git-blame-sentinel)))\n\n(defun git-blame-cleanup ()\n  \"Remove all blame properties\"\n    (mapcar 'delete-overlay git-blame-overlays)\n    (setq git-blame-overlays nil)\n    (let ((modified (buffer-modified-p)))\n      (remove-text-properties (point-min) (point-max) '(point-entered nil))\n      (set-buffer-modified-p modified)))\n    \n\n(defun git-blame-sentinel (proc status)\n  ;;(kill-buffer (process-buffer proc))\n  (message \"git blame finished\"))\n\n(defvar in-blame-filter nil)\n\n(defun git-blame-filter (proc str)\n  (save-excursion\n    (set-buffer (process-buffer proc))\n    (goto-char (process-mark proc))\n    (insert-before-markers str)\n    (goto-char 0)\n    (unless in-blame-filter\n      (let ((more t)\n            (in-blame-filter t))\n        (while more\n          (setq more (git-blame-parse)))))))\n\n(defun git-blame-parse ()\n  (cond ((looking-at \"\\\\([0-9a-f]\\\\{40\\\\}\\\\) \\\\([0-9]+\\\\) \\\\([0-9]+\\\\) \\\\([0-9]+\\\\)\\n\")\n         (let ((hash (match-string 1))\n               (src-line (string-to-number (match-string 2)))\n               (res-line (string-to-number (match-string 3)))\n               (num-lines (string-to-number (match-string 4))))\n           (setq git-blame-current\n                 (git-blame-new-commit\n                  hash src-line res-line num-lines)))\n         (delete-region (point) (match-end 0))\n         t)\n        ((looking-at \"filename \\\\(.+\\\\)\\n\")\n         (let ((filename (match-string 1)))\n           (git-blame-add-info \"filename\" filename))\n         (delete-region (point) (match-end 0))\n         t)\n        ((looking-at \"\\\\([a-z-]+\\\\) \\\\(.+\\\\)\\n\")\n         (let ((key (match-string 1))\n               (value (match-string 2)))\n           (git-blame-add-info key value))\n         (delete-region (point) (match-end 0))\n         t)\n        ((looking-at \"boundary\\n\")\n         (setq git-blame-current nil)\n         (delete-region (point) (match-end 0))\n         t)\n        (t\n         nil)))\n\n\n(defun git-blame-new-commit (hash src-line res-line num-lines)\n  (save-excursion\n    (set-buffer git-blame-file)\n    (let ((info (gethash hash git-blame-cache))\n          (inhibit-point-motion-hooks t))\n      (when (not info)\n        (let ((color (pop git-blame-colors)))\n          (unless color\n            (setq color git-blame-ancient-color))\n          (setq info (list hash src-line res-line num-lines\n                           (cons 'color color))))\n        (puthash hash info git-blame-cache))\n      (goto-line res-line)\n      (while (> num-lines 0)\n        (if (get-text-property (point) 'git-blame)\n            (forward-line)\n          (let* ((start (point))\n                 (end (progn (forward-line 1) (point)))\n                 (ovl (make-overlay start end)))\n            (push ovl git-blame-overlays)\n            (overlay-put ovl 'git-blame info)\n            (overlay-put ovl 'help-echo hash)\n            (overlay-put ovl 'face (list :background\n                                         (cdr (assq 'color (cddddr info)))))\n            ;;(overlay-put ovl 'point-entered\n            ;;             `(lambda (x y) (git-blame-identify ,hash)))\n            (let ((modified (buffer-modified-p)))\n              (put-text-property (if (= start 1) start (1- start)) (1- end)\n                                 'point-entered\n                                 `(lambda (x y) (git-blame-identify ,hash)))\n              (set-buffer-modified-p modified))))\n        (setq num-lines (1- num-lines))))))\n\n(defun git-blame-add-info (key value)\n  (if git-blame-current\n      (nconc git-blame-current (list (cons (intern key) value)))))\n\n(defun git-blame-current-commit ()\n  (let ((info (get-char-property (point) 'git-blame)))\n    (if info\n        (car info)\n      (error \"No commit info\"))))\n\n(defun git-blame-identify (&optional hash)\n  (interactive)\n  (shell-command\n   (format \"git log -1 --pretty=oneline %s\" (or hash\n                                                (git-blame-current-commit)))))\n\n-- \nDavid Kågedal\n"},{"id":"33163","messageId":"864pq7839g.fsf@blue.stonehenge.com","threadId":"6593","inReplyTo":"87iren2vqx.fsf@morpheus.local","subject":"Re: git-blame.el","fromName":"Randal L. Schwartz","fromEmail":"merlyn@stonehenge.com","sentAt":"2007-01-31T18:22:51Z","receivedAt":"2007-01-31T18:22:51Z","isPatch":false,"sender":{"key":"merlyn@stonehenge.com","avatar":"https://gravatar.com/avatar/dc528d210743ff0333e6213f9ee7b33b23f1b7bc1f3c5a8c2d819074ecd7ab19?d=mp&s=160"},"body":">>>>> \"David\" == David Kågedal <davidk@lysator.liu.se> writes:\n\nDavid> Here's another version of git-blame.el that automatically tries to\nDavid> create a sensible list of colors to use for both light and dark\nDavid> backgrounds.  Plus a few minor fixes.\n\nIf this gets traction, this should be what vc-annotate uses on a git file.\n\n-- \nRandal L. Schwartz - Stonehenge Consulting Services, Inc. - +1 503 777 0095\n<merlyn@stonehenge.com> <URL:http://www.stonehenge.com/merlyn/>\nPerl/Unix/security consulting, Technical writing, Comedy, etc. etc.\nSee PerlTraining.Stonehenge.com for onsite and open-enrollment Perl training!\n"},{"id":"33171","messageId":"7v64anvu2u.fsf@assigned-by-dhcp.cox.net","threadId":"6593","inReplyTo":"87iren2vqx.fsf@morpheus.local","subject":"Re: git-blame.el","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2007-01-31T20:07:21Z","receivedAt":"2007-01-31T20:07:21Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"I seem to be getting tons of these; wouldn't you want some (require)?\n\nerror in process filter: assq: Symbol's function definition is void: cddddr\nerror in process filter: Symbol's function definition is void: cddddr\nerror in process filter: assq: Symbol's function definition is void: cddddr\nerror in process filter: Symbol's function definition is void: cddddr\nerror in process filter: assq: Symbol's function definition is void: cddddr\nerror in process filter: Symbol's function definition is void: cddddr\nerror in process filter: assq: Symbol's function definition is void: cddddr\nerror in process filter: Symbol's function definition is void: cddddr\nerror in process filter: assq: Symbol's function definition is void: cddddr\nerror in process filter: Symbol's function definition is void: cddddr\nerror in process filter: assq: Symbol's function definition is void: cddddr\nerror in process filter: Symbol's function definition is void: cddddr\nerror in process filter: assq: Symbol's function definition is void: cddddr\nerror in process filter: Symbol's function definition is void: cddddr\nerror in process filter: assq: Symbol's function definition is void: cddddr\nerror in process filter: Symbol's function definition is void: cddddr\nerror in process filter: assq: Symbol's function definition is void: cddddr\nerror in process filter: Symbol's function definition is void: cddddr\nerror in process filter: assq: Symbol's function definition is void: cddddr\nerror in process filter: Symbol's function definition is void: cddddr\nerror in process filter: assq: Symbol's function definition is void: cddddr\nerror in process filter: Symbol's function definition is void: cddddr\nerror in process filter: assq: Symbol's function definition is void: cddddr\nerror in process filter: Symbol's function definition is void: cddddr\nerror in process filter: assq: Symbol's function definition is void: cddddr\nerror in process filter: Symbol's function definition is void: cddddr\nerror in process filter: assq: Symbol's function definition is void: cddddr\nerror in process filter: Symbol's function definition is void: cddddr\nerror in process filter: assq: Symbol's function definition is void: cddddr\nerror in process filter: Symbol's function definition is void: cddddr\nerror in process filter: assq: Symbol's function definition is void: cddddr\nerror in process filter: Symbol's function definition is void: cddddr\nerror in process filter: assq: Symbol's function definition is void: cddddr\nerror in process filter: Symbol's function definition is void: cddddr\nerror in process filter: assq: Symbol's function definition is void: cddddr\nerror in process filter: Symbol's function definition is void: cddddr\n"},{"id":"33175","messageId":"87irenym9o.fsf@morpheus.local","threadId":"6593","inReplyTo":"7v64anvu2u.fsf@assigned-by-dhcp.cox.net","subject":"Re: git-blame.el","fromName":"David Kågedal","fromEmail":"davidk@lysator.liu.se","sentAt":"2007-01-31T20:27:47Z","receivedAt":"2007-01-31T20:27:47Z","isPatch":false,"sender":{"key":"davidk@lysator.liu.se","avatar":"https://avatars.githubusercontent.com/u/60530?v=4"},"body":"Junio C Hamano <junkio@cox.net> writes:\n\n> I seem to be getting tons of these; wouldn't you want some (require)?\n\nYeah, I apparently need a (require 'cl)\n\nI try to stay away from cl by habit, but since I usually have it\nrequired by something I use, I might use it without noticing.\n\nSo, to try it, add (require 'cl) to the top of the file.\n\n-- \nDavid Kågedal\n"},{"id":"33177","messageId":"87ejpazzn8.fsf@morpheus.local","threadId":"6593","inReplyTo":"864pq7839g.fsf@blue.stonehenge.com","subject":"Re: git-blame.el","fromName":"David Kågedal","fromEmail":"davidk@lysator.liu.se","sentAt":"2007-01-31T20:53:31Z","receivedAt":"2007-01-31T20:53:31Z","isPatch":false,"sender":{"key":"davidk@lysator.liu.se","avatar":"https://avatars.githubusercontent.com/u/60530?v=4"},"body":"merlyn@stonehenge.com (Randal L. Schwartz) writes:\n\n>>>>>> \"David\" == David Kågedal <davidk@lysator.liu.se> writes:\n>\n> David> Here's another version of git-blame.el that automatically tries to\n> David> create a sensible list of colors to use for both light and dark\n> David> backgrounds.  Plus a few minor fixes.\n>\n> If this gets traction, this should be what vc-annotate uses on a git file.\n\nI only had a very quick glance, but one \"problem\" is that vc-annotate\nassumes that what you want to do is to run an annotate/blame command\nand show the output in another buffer.  My solution is a minor-mode\nthat shows the blame directly in the edit buffer, and I'm not sure how\neasy it would be to convince vc that this is what I want.  And I need\na way to turn it off again, and maybe some local key bindings when it\nis active.  This all points to the fact that a minor mode is really\nthe thing to use.  So maybe I should simply rebind C-x v g to\ngit-blame-mode instead of vc-annotate...\n\n-- \nDavid Kågedal\n"},{"id":"33230","messageId":"20070201131213.GB11611@diana.vm.bytemark.co.uk","threadId":"6593","inReplyTo":"87iren2vqx.fsf@morpheus.local","subject":"Re: git-blame.el","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-02-01T13:12:13Z","receivedAt":"2007-02-01T13:12:13Z","isPatch":false,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"On 2007-01-31 14:04:06 +0100, David Kågedal wrote:\n\n> Here's another version of git-blame.el that automatically tries to\n> create a sensible list of colors to use for both light and dark\n> backgrounds. Plus a few minor fixes.\n\nI've tried it, like the concept of in-buffer blame and the pretty\ncolors, and have a few comments and suggestions:\n\n  1. For some files, but not all, emacs is unresponsive (and consumes\n     lots of CPU) right after git-blame-mode is activated. Once\n     git-blame has finished, it becomes responsive again, but this\n     kind of defeats the whole \"incremental\" idea.\n\n     This is most easily seen by holdnig down Ctrl+N or similar, to\n     make the cursor move constantly.\n\n  2. Getting to see the sha1 of the commit is not so useful when it\n     can't be selected for copy-pasting. Maybe a keyboard shortcut for\n     \"copy-sha1-to-kill-ring\"?\n\n  3. Even after I've edited a line, or added a new line, they continue\n     to be attributed to the same existing commits. They should either\n     have no attribution, or possibly just \"local edit\" or something.\n     I seem to recall this kind of functionality for git-blame being\n     discussed very recently?\n\n  4. It would be nice with a keyboard shortcut that (in a new buffer)\n     printed more details about the commit under the cursor, kind of\n     like the output from git-log. (Having this available would\n     obviate the need for (2).)\n\n  5. It would be nice with a keyboard shortcut for displaying the\n     commit under the cursor in gitk. (For extra points: successive\n     uses of this command should reuse the same gitk window if it's\n     still open.)\n\n  6. It would be nice with a keyboard shortcut for displaying (in a\n     separate buffer) the diff to that file introduced by the commit\n     under the cursor. This could be combined with (3) by having\n     commit details followed by diff.\n\nOh, and try to have it done by Monday. ;-)\n\n-- \nKarl Hasselström, kha@treskal.com\n      www.treskal.com/kalle\n"},{"id":"33231","messageId":"87veimxbc6.fsf@morpheus.local","threadId":"6593","inReplyTo":"20070201131213.GB11611@diana.vm.bytemark.co.uk","subject":"Re: git-blame.el","fromName":"David Kågedal","fromEmail":"davidk@lysator.liu.se","sentAt":"2007-02-01T13:21:29Z","receivedAt":"2007-02-01T13:21:29Z","isPatch":false,"sender":{"key":"davidk@lysator.liu.se","avatar":"https://avatars.githubusercontent.com/u/60530?v=4"},"body":"Karl Hasselström <kha@treskal.com> writes:\n\n> On 2007-01-31 14:04:06 +0100, David Kågedal wrote:\n>\n>> Here's another version of git-blame.el that automatically tries to\n>> create a sensible list of colors to use for both light and dark\n>> backgrounds. Plus a few minor fixes.\n>\n> I've tried it, like the concept of in-buffer blame and the pretty\n> colors, and have a few comments and suggestions:\n>\n>   1. For some files, but not all, emacs is unresponsive (and consumes\n>      lots of CPU) right after git-blame-mode is activated. Once\n>      git-blame has finished, it becomes responsive again, but this\n>      kind of defeats the whole \"incremental\" idea.\n>\n>      This is most easily seen by holdnig down Ctrl+N or similar, to\n>      make the cursor move constantly.\n\nDon't know why this happens.  I haven't seen it myself.\n\n>   2. Getting to see the sha1 of the commit is not so useful when it\n>      can't be selected for copy-pasting. Maybe a keyboard shortcut for\n>      \"copy-sha1-to-kill-ring\"?\n\nSure.  It's a trivial addition.\n\n>   3. Even after I've edited a line, or added a new line, they continue\n>      to be attributed to the same existing commits. They should either\n>      have no attribution, or possibly just \"local edit\" or something.\n>      I seem to recall this kind of functionality for git-blame being\n>      discussed very recently?\n\nI saw it was discussed, but I don't think it was added.  Currently, it\nprobably makes most sense to verify that the file hasn't been\nmodified, and then switch to read-only mode while viewing the blame.\n\n>   4. It would be nice with a keyboard shortcut that (in a new buffer)\n>      printed more details about the commit under the cursor, kind of\n>      like the output from git-log. (Having this available would\n>      obviate the need for (2).)\n>\n>   5. It would be nice with a keyboard shortcut for displaying the\n>      commit under the cursor in gitk. (For extra points: successive\n>      uses of this command should reuse the same gitk window if it's\n>      still open.)\n\nThis is almost as trivial as 2, since the hash is available, you can\nsimply run shell-command.\n\n>   6. It would be nice with a keyboard shortcut for displaying (in a\n>      separate buffer) the diff to that file introduced by the commit\n>      under the cursor. This could be combined with (3) by having\n>      commit details followed by diff.\n\nAs in \"git log -p\", you mean?\n\n> Oh, and try to have it done by Monday. ;-)\n\nSorry Kalle.  I won't have any more spare cycles until next week.\n\n-- \nDavid Kågedal\n"},{"id":"33238","messageId":"20070201142638.GA12980@diana.vm.bytemark.co.uk","threadId":"6593","inReplyTo":"87veimxbc6.fsf@morpheus.local","subject":"Re: git-blame.el","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-02-01T14:26:38Z","receivedAt":"2007-02-01T14:26:38Z","isPatch":false,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"On 2007-02-01 14:21:29 +0100, David Kågedal wrote:\n\n> Karl Hasselström <kha@treskal.com> writes:\n>\n> >   3. Even after I've edited a line, or added a new line, they\n> >      continue to be attributed to the same existing commits. They\n> >      should either have no attribution, or possibly just \"local\n> >      edit\" or something. I seem to recall this kind of\n> >      functionality for git-blame being discussed very recently?\n>\n> I saw it was discussed, but I don't think it was added. Currently,\n> it probably makes most sense to verify that the file hasn't been\n> modified, and then switch to read-only mode while viewing the blame.\n\nHmm, probably, yes. But it'll be kind of limiting to not be able to\nrun blame on a file that has local modifications. I think I understand\nwhy vc-annotate opens a new buffer ...\n\n> >   6. It would be nice with a keyboard shortcut for displaying (in\n> >      a separate buffer) the diff to that file introduced by the\n> >      commit under the cursor. This could be combined with (3) by\n> >      having commit details followed by diff.\n>\n> As in \"git log -p\", you mean?\n\nYes. I was thinking of exactly what \"git log -p $hash^..$hash\"\nproduces. The Emacs windows should be split in two frames, with the\ncommit details + diff in the lower frame, just like vc-diff.\n\n-- \nKarl Hasselström, kha@treskal.com\n      www.treskal.com/kalle\n"},{"id":"33585","messageId":"11706194892444-git-send-email-jnareb@gmail.com","threadId":"6593","inReplyTo":"87iren2vqx.fsf@morpheus.local","subject":"[PATCH] git-blame.el --- Minor mode for incremental blame for Git","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2007-02-04T20:04:49Z","receivedAt":"2007-02-04T20:04:49Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Here is an Emacs implementation of incremental git-blame.  When you\nturn it on while viewing a file, the editor buffer will be updated by\nsetting the background of individual lines to a color that reflects\nwhich commit it comes from.  And when you move around the buffer, a\none-line summary will be shown in the echo area.\n\nCreated by David KÃ¥gedal, header and comments added by Jakub NarÄbski.\n\nSigned-off-by: Jakub Narebski <jnareb@gmail.com>\nSigned-off-by: David KÃ¥gedal <davidk@lysator.liu.se>\n---\nHere it is as a proper git patch, and with some headers and comments\nadded. As compared to latest version sent by David KÃ¥gedal,\n  Message-ID: <87iren2vqx.fsf@morpheus.local>\n  http://permalink.gmane.org/gmane.comp.version-control.git/38246\nit has elisp header and description by David KÃ¥gedal, has (require 'cl)\nwith explanation added, and (provide 'git-blame) at the end; no other\nchanges (as of now).\n\nDavid, is it released as GPL? If it is, GPL boilerplate (as in git.el)\ncould be added to the header.\n\n contrib/emacs/git-blame.el |  222 ++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 222 insertions(+), 0 deletions(-)\n\ndiff --git a/contrib/emacs/git-blame.el b/contrib/emacs/git-blame.el\nnew file mode 100644\nindex 0000000..fa89889\n--- /dev/null\n+++ b/contrib/emacs/git-blame.el\n@@ -0,0 +1,222 @@\n+;;; git-blame.el --- Minor mode for incremental blame for Git  -*- coding: utf-8 -*-\n+;;\n+;; Copyright (C) 2007 by David KÃ¥gedal\n+;;\n+;; Authors:    David KÃ¥gedal <davidk@lysator.liu.se>\n+;; Created:    31 Jan 2007\n+;; Keywords:   git, version control, release management\n+;;\n+;; Compatibility: Emacs21\n+\n+;;; Commentary:\n+;;\n+;; Here is an Emacs implementation of incremental git-blame.  When you\n+;; turn it on while viewing a file, the editor buffer will be updated by\n+;; setting the background of individual lines to a color that reflects\n+;; which commit it comes from.  And when you move around the buffer, a\n+;; one-line summary will be shown in the echo area.\n+\n+;;; Installation:\n+;;\n+;;  1) Load into emacs: M-x load-file RET git-blame.el RET\n+;;  2) Open a git-controlled file\n+;;  3) Blame: M-x git-blame-mode\n+\n+;;; Compatibility:\n+;;\n+;; It requires GNU Emacs 21.  If you'are using Emacs 20, try\n+;; changing this:\n+;;\n+;;            (overlay-put ovl 'face (list :background\n+;;                                         (cdr (assq 'color (cddddr info)))))\n+;;\n+;; to\n+;;\n+;;            (overlay-put ovl 'face (cons 'background-color\n+;;                                         (cdr (assq 'color (cddddr info)))))\n+\n+\n+;;; Code:\n+\n+(require 'cl)\t\t\t      ; to use `cddddr', `push', `pop'\n+\n+(defun color-scale (l)\n+  (let* ((colors ())\n+         r g b)\n+    (setq r l)\n+    (while r\n+      (setq g l)\n+      (while g\n+        (setq b l)\n+        (while b\n+          (push (concat \"#\" (car r) (car g) (car b)) colors)\n+          (pop b))\n+        (pop g))\n+      (pop r))\n+    colors))\n+\n+(defvar git-blame-dark-colors\n+  (color-scale '(\"00\" \"04\" \"08\" \"0c\"\n+                 \"10\" \"14\" \"18\" \"1c\"\n+                 \"20\" \"24\" \"28\" \"2c\"\n+                 \"30\" \"34\" \"38\" \"3c\")))\n+\n+(defvar git-blame-light-colors\n+  (color-scale '(\"c0\" \"c4\" \"c8\" \"cc\"\n+                 \"d0\" \"d4\" \"d8\" \"dc\"\n+                 \"e0\" \"e4\" \"e8\" \"ec\"\n+                 \"f0\" \"f4\" \"f8\" \"fc\")))\n+\n+(defvar git-blame-ancient-color \"dark green\")\n+\n+(defvar git-blame-overlays nil)\n+(defvar git-blame-cache nil)\n+\n+(defvar git-blame-mode nil)\n+(make-variable-buffer-local 'git-blame-mode)\n+(push (list 'git-blame-mode \" blame\") minor-mode-alist)\n+\n+(defun git-blame-mode (&optional arg)\n+  (interactive \"P\")\n+  (if arg\n+      (setq git-blame-mode (eq arg 1))\n+    (setq git-blame-mode (not git-blame-mode)))\n+  (make-local-variable 'git-blame-overlays)\n+  (make-local-variable 'git-blame-colors)\n+  (make-local-variable 'git-blame-cache)\n+   (let ((bgmode (cdr (assoc 'background-mode (frame-parameters)))))\n+    (if (eq bgmode 'dark)\n+        (setq git-blame-colors git-blame-dark-colors)\n+      (setq git-blame-colors git-blame-light-colors)))\n+  (if git-blame-mode\n+      (git-blame-run)\n+    (git-blame-cleanup)))\n+\n+(defun git-blame-run ()\n+  (let* ((display-buf (current-buffer))\n+         (blame-buf (get-buffer-create\n+                     (concat \" git blame for \" (buffer-name))))\n+         (proc (start-process \"git-blame\" blame-buf\n+                             \"git\" \"blame\" \"--incremental\"\n+                             (file-name-nondirectory buffer-file-name))))\n+    (mapcar 'delete-overlay git-blame-overlays)\n+    (setq git-blame-overlays nil)\n+    (setq git-blame-cache (make-hash-table :test 'equal))\n+    (with-current-buffer blame-buf\n+      (erase-buffer)\n+      (make-local-variable 'git-blame-file)\n+      (make-local-variable 'git-blame-current)\n+      (setq git-blame-file display-buf)\n+      (setq git-blame-current nil))\n+    (set-process-filter proc 'git-blame-filter)\n+    (set-process-sentinel proc 'git-blame-sentinel)))\n+\n+(defun git-blame-cleanup ()\n+  \"Remove all blame properties\"\n+    (mapcar 'delete-overlay git-blame-overlays)\n+    (setq git-blame-overlays nil)\n+    (let ((modified (buffer-modified-p)))\n+      (remove-text-properties (point-min) (point-max) '(point-entered nil))\n+      (set-buffer-modified-p modified)))\n+\n+\n+(defun git-blame-sentinel (proc status)\n+  ;;(kill-buffer (process-buffer proc))\n+  (message \"git blame finished\"))\n+\n+(defvar in-blame-filter nil)\n+\n+(defun git-blame-filter (proc str)\n+  (save-excursion\n+    (set-buffer (process-buffer proc))\n+    (goto-char (process-mark proc))\n+    (insert-before-markers str)\n+    (goto-char 0)\n+    (unless in-blame-filter\n+      (let ((more t)\n+            (in-blame-filter t))\n+        (while more\n+          (setq more (git-blame-parse)))))))\n+\n+(defun git-blame-parse ()\n+  (cond ((looking-at \"\\\\([0-9a-f]\\\\{40\\\\}\\\\) \\\\([0-9]+\\\\) \\\\([0-9]+\\\\) \\\\([0-9]+\\\\)\\n\")\n+         (let ((hash (match-string 1))\n+               (src-line (string-to-number (match-string 2)))\n+               (res-line (string-to-number (match-string 3)))\n+               (num-lines (string-to-number (match-string 4))))\n+           (setq git-blame-current\n+                 (git-blame-new-commit\n+                  hash src-line res-line num-lines)))\n+         (delete-region (point) (match-end 0))\n+         t)\n+        ((looking-at \"filename \\\\(.+\\\\)\\n\")\n+         (let ((filename (match-string 1)))\n+           (git-blame-add-info \"filename\" filename))\n+         (delete-region (point) (match-end 0))\n+         t)\n+        ((looking-at \"\\\\([a-z-]+\\\\) \\\\(.+\\\\)\\n\")\n+         (let ((key (match-string 1))\n+               (value (match-string 2)))\n+           (git-blame-add-info key value))\n+         (delete-region (point) (match-end 0))\n+         t)\n+        ((looking-at \"boundary\\n\")\n+         (setq git-blame-current nil)\n+         (delete-region (point) (match-end 0))\n+         t)\n+        (t\n+         nil)))\n+\n+\n+(defun git-blame-new-commit (hash src-line res-line num-lines)\n+  (save-excursion\n+    (set-buffer git-blame-file)\n+    (let ((info (gethash hash git-blame-cache))\n+          (inhibit-point-motion-hooks t))\n+      (when (not info)\n+        (let ((color (pop git-blame-colors)))\n+          (unless color\n+            (setq color git-blame-ancient-color))\n+          (setq info (list hash src-line res-line num-lines\n+                           (cons 'color color))))\n+        (puthash hash info git-blame-cache))\n+      (goto-line res-line)\n+      (while (> num-lines 0)\n+        (if (get-text-property (point) 'git-blame)\n+            (forward-line)\n+          (let* ((start (point))\n+                 (end (progn (forward-line 1) (point)))\n+                 (ovl (make-overlay start end)))\n+            (push ovl git-blame-overlays)\n+            (overlay-put ovl 'git-blame info)\n+            (overlay-put ovl 'help-echo hash)\n+            (overlay-put ovl 'face (list :background\n+                                         (cdr (assq 'color (cddddr info)))))\n+            ;;(overlay-put ovl 'point-entered\n+            ;;             `(lambda (x y) (git-blame-identify ,hash)))\n+            (let ((modified (buffer-modified-p)))\n+              (put-text-property (if (= start 1) start (1- start)) (1- end)\n+                                 'point-entered\n+                                 `(lambda (x y) (git-blame-identify ,hash)))\n+              (set-buffer-modified-p modified))))\n+        (setq num-lines (1- num-lines))))))\n+\n+(defun git-blame-add-info (key value)\n+  (if git-blame-current\n+      (nconc git-blame-current (list (cons (intern key) value)))))\n+\n+(defun git-blame-current-commit ()\n+  (let ((info (get-char-property (point) 'git-blame)))\n+    (if info\n+        (car info)\n+      (error \"No commit info\"))))\n+\n+(defun git-blame-identify (&optional hash)\n+  (interactive)\n+  (shell-command\n+   (format \"git log -1 --pretty=oneline %s\" (or hash\n+                                                (git-blame-current-commit)))))\n+\n+(provide 'git-blame)\n+\n+;;; git-blame.el ends here\n-- \n1.4.4.4\n"}]}