+++ /dev/null
-;;; git-commit-autoloads.el --- automatically extracted autoloads -*- lexical-binding: t -*-
-;;
-;;; Code:
-
-(add-to-list 'load-path (directory-file-name
- (or (file-name-directory #$) (car load-path))))
-
-\f
-;;;### (autoloads nil "git-commit" "git-commit.el" (0 0 0 0))
-;;; Generated autoloads from git-commit.el
-(put 'git-commit-major-mode 'safe-local-variable
- (lambda (val)
- (memq val '(text-mode
- markdown-mode
- org-mode
- fundamental-mode
- git-commit-elisp-text-mode))))
-
-(register-definition-prefixes "git-commit" '("git-commit-" "global-git-commit-mode"))
-
-;;;***
-\f
-;;;### (autoloads nil nil ("git-commit-pkg.el") (0 0 0 0))
-
-;;;***
-\f
-;; Local Variables:
-;; version-control: never
-;; no-byte-compile: t
-;; no-update-autoloads: t
-;; coding: utf-8
-;; End:
-;;; git-commit-autoloads.el ends here
+++ /dev/null
-(define-package "git-commit" "20230103.2224" "Edit Git commit messages."
- '((emacs "25.1")
- (compat "28.1.1.2")
- (transient "20210920")
- (with-editor "20211001"))
- :commit "f47b68929f01a1b8299f3160b0ab02d69d59fcb7" :authors
- '(("Jonas Bernoulli" . "jonasNOSPAMbernoul.li")
- ("Sebastian Wiesner" . "lunaryornNOSPAMgmail.com")
- ("Florian Ragwitz" . "raflNOSPAMdebian.org")
- ("Marius Vollmer" . "marius.vollmerNOSPAMgmail.com"))
- :maintainer
- '("Jonas Bernoulli" . "jonasNOSPAMbernoul.li")
- :keywords
- '("git" "tools" "vc")
- :url "https://github.com/magit/magit")
-;; Local Variables:
-;; no-byte-compile: t
-;; End:
+++ /dev/null
-;;; git-commit.el --- Edit Git commit messages -*- lexical-binding:t; coding:utf-8 -*-
-
-;; Copyright (C) 2008-2023 The Magit Project Contributors
-
-;; Author: Jonas Bernoulli <jonasNOSPAMbernoul.li>
-;; Sebastian Wiesner <lunaryornNOSPAMgmail.com>
-;; Florian Ragwitz <raflNOSPAMdebian.org>
-;; Marius Vollmer <marius.vollmerNOSPAMgmail.com>
-;; Maintainer: Jonas Bernoulli <jonasNOSPAMbernoul.li>
-
-;; Homepage: https://github.com/magit/magit
-;; Keywords: git tools vc
-
-;; Package-Version: 3.3.0.50-git
-;; Package-Requires: (
-;; (emacs "25.1")
-;; (compat "28.1.1.2")
-;; (transient "0.3.6")
-;; (with-editor "3.0.5"))
-
-;; SPDX-License-Identifier: GPL-3.0-or-later
-
-;; Magit is free software: you can redistribute it and/or modify
-;; it under the terms of the GNU General Public License as published
-;; by the Free Software Foundation, either version 3 of the License,
-;; or (at your option) any later version.
-;;
-;; Magit is distributed in the hope that it will be useful,
-;; but WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-;; GNU General Public License for more details.
-;;
-;; You should have received a copy of the GNU General Public License
-;; along with Magit. If not, see <https://www.gnu.org/licenses/>.
-
-;; You should have received a copy of the AUTHORS.md file, which
-;; lists all contributors. If not, see https://magit.vc/authors.
-
-;;; Commentary:
-
-;; This package assists the user in writing good Git commit messages.
-
-;; While Git allows for the message to be provided on the command
-;; line, it is preferable to tell Git to create the commit without
-;; actually passing it a message. Git then invokes the `$GIT_EDITOR'
-;; (or if that is undefined `$EDITOR') asking the user to provide the
-;; message by editing the file ".git/COMMIT_EDITMSG" (or another file
-;; in that directory, e.g. ".git/MERGE_MSG" for merge commits).
-
-;; When `global-git-commit-mode' is enabled, which it is by default,
-;; then opening such a file causes the features described below, to
-;; be enabled in that buffer. Normally this would be done using a
-;; major-mode but to allow the use of any major-mode, as the user sees
-;; fit, it is done here by running a setup function, which among other
-;; things turns on the preferred major-mode, by default `text-mode'.
-
-;; Git waits for the `$EDITOR' to finish and then either creates the
-;; commit using the contents of the file as commit message, or, if the
-;; editor process exited with a non-zero exit status, aborts without
-;; creating a commit. Unfortunately Emacsclient (which is what Emacs
-;; users should be using as `$EDITOR' or at least as `$GIT_EDITOR')
-;; does not differentiate between "successfully" editing a file and
-;; aborting; not out of the box that is.
-
-;; By making use of the `with-editor' package this package provides
-;; both ways of finish an editing session. In either case the file
-;; is saved, but Emacseditor's exit code differs.
-;;
-;; C-c C-c Finish the editing session successfully by returning
-;; with exit code 0. Git then creates the commit using
-;; the message it finds in the file.
-;;
-;; C-c C-k Aborts the edit editing session by returning with exit
-;; code 1. Git then aborts the commit.
-
-;; Aborting the commit does not cause the message to be lost, but
-;; relying solely on the file not being tampered with is risky. This
-;; package additionally stores all aborted messages for the duration
-;; of the current session (i.e. until you close Emacs). To get back
-;; an aborted message use M-p and M-n while editing a message.
-;;
-;; M-p Replace the buffer contents with the previous message
-;; from the message ring. Of course only after storing
-;; the current content there too.
-;;
-;; M-n Replace the buffer contents with the next message from
-;; the message ring, after storing the current content.
-
-;; Some support for pseudo headers as used in some projects is
-;; provided by these commands:
-;;
-;; C-c C-s Insert a Signed-off-by header.
-;; C-c C-a Insert a Acked-by header.
-;; C-c C-m Insert a Modified-by header.
-;; C-c C-t Insert a Tested-by header.
-;; C-c C-r Insert a Reviewed-by header.
-;; C-c C-o Insert a Cc header.
-;; C-c C-p Insert a Reported-by header.
-;; C-c C-i Insert a Suggested-by header.
-
-;; When Git requests a commit message from the user, it does so by
-;; having her edit a file which initially contains some comments,
-;; instructing her what to do, and providing useful information, such
-;; as which files were modified. These comments, even when left
-;; intact by the user, do not become part of the commit message. This
-;; package ensures these comments are propertizes as such and further
-;; prettifies them by using different faces for various parts, such as
-;; files.
-
-;; Finally this package highlights style errors, like lines that are
-;; too long, or when the second line is not empty. It may even nag
-;; you when you attempt to finish the commit without having fixed
-;; these issues. The style checks and many other settings can easily
-;; be configured:
-;;
-;; M-x customize-group RET git-commit RET
-
-;;; Code:
-
-(require 'seq)
-(require 'subr-x)
-
-(require 'log-edit)
-(require 'ring)
-(require 'rx)
-(require 'server)
-(require 'transient)
-(require 'with-editor)
-
-;; For historic reasons Magit isn't a hard dependency.
-(unless (and (require 'magit-base nil t)
- (require 'magit-git nil t))
- (declare-function magit-completing-read "magit-base"
- ( prompt collection &optional predicate require-match
- initial-input hist def fallback))
- (declare-function magit-expand-git-file-name "magit-git" (filename))
- (declare-function magit-git-lines "magit-git" (&rest args))
- (declare-function magit-hook-custom-get "magit-base" (symbol))
- (declare-function magit-list-local-branch-names "magit-git" ()))
-
-(defvar diff-default-read-only)
-(defvar flyspell-generic-check-word-predicate)
-(defvar font-lock-beg)
-(defvar font-lock-end)
-(defvar recentf-exclude)
-
-;;; Options
-;;;; Variables
-
-(defgroup git-commit nil
- "Edit Git commit messages."
- :prefix "git-commit-"
- :link '(info-link "(magit)Editing Commit Messages")
- :group 'tools)
-
-(define-minor-mode global-git-commit-mode
- "Edit Git commit messages.
-
-This global mode arranges for `git-commit-setup' to be called
-when a Git commit message file is opened. That usually happens
-when Git uses the Emacsclient as $GIT_EDITOR to have the user
-provide such a commit message.
-
-Loading the library `git-commit' by default enables this mode,
-but the library is not automatically loaded because doing that
-would pull in many dependencies and increase startup time too
-much. You can either rely on `magit' loading this library or
-you can load it explicitly. Autoloading is not an alternative
-because in this case autoloading would immediately trigger
-full loading."
- :group 'git-commit
- :type 'boolean
- :global t
- :init-value t
- :initialize (lambda (symbol exp)
- (custom-initialize-default symbol exp)
- (when global-git-commit-mode
- (add-hook 'find-file-hook #'git-commit-setup-check-buffer)))
- (if global-git-commit-mode
- (add-hook 'find-file-hook #'git-commit-setup-check-buffer)
- (remove-hook 'find-file-hook #'git-commit-setup-check-buffer)))
-
-(defcustom git-commit-major-mode #'text-mode
- "Major mode used to edit Git commit messages.
-The major mode configured here is turned on by the minor mode
-`git-commit-mode'."
- :group 'git-commit
- :type '(choice (function-item text-mode)
- (function-item markdown-mode)
- (function-item org-mode)
- (function-item fundamental-mode)
- (function-item git-commit-elisp-text-mode)
- (function :tag "Another mode")
- (const :tag "No major mode")))
-;;;###autoload(put 'git-commit-major-mode 'safe-local-variable
-;;;###autoload (lambda (val)
-;;;###autoload (memq val '(text-mode
-;;;###autoload markdown-mode
-;;;###autoload org-mode
-;;;###autoload fundamental-mode
-;;;###autoload git-commit-elisp-text-mode))))
-
-(defcustom git-commit-setup-hook
- '(git-commit-save-message
- git-commit-setup-changelog-support
- git-commit-turn-on-auto-fill
- git-commit-propertize-diff
- bug-reference-mode
- with-editor-usage-message)
- "Hook run at the end of `git-commit-setup'."
- :group 'git-commit
- :type 'hook
- :get (and (featurep 'magit-base) #'magit-hook-custom-get)
- :options '(git-commit-save-message
- git-commit-setup-changelog-support
- magit-generate-changelog
- git-commit-turn-on-auto-fill
- git-commit-turn-on-orglink
- git-commit-turn-on-flyspell
- git-commit-propertize-diff
- bug-reference-mode
- with-editor-usage-message))
-
-(defcustom git-commit-post-finish-hook nil
- "Hook run after the user finished writing a commit message.
-
-\\<with-editor-mode-map>\
-This hook is only run after pressing \\[with-editor-finish] in a buffer used
-to edit a commit message. If a commit is created without the
-user typing a message into a buffer, then this hook is not run.
-
-This hook is not run until the new commit has been created. If
-that takes Git longer than `git-commit-post-finish-hook-timeout'
-seconds, then this hook isn't run at all. For certain commands
-such as `magit-rebase-continue' this hook is never run because
-doing so would lead to a race condition.
-
-This hook is only run if `magit' is available.
-
-Also see `magit-post-commit-hook'."
- :group 'git-commit
- :type 'hook
- :get (and (featurep 'magit-base) #'magit-hook-custom-get))
-
-(defcustom git-commit-post-finish-hook-timeout 1
- "Time in seconds to wait for git to create a commit.
-
-The hook `git-commit-post-finish-hook' (which see) is run only
-after git is done creating a commit. If it takes longer than
-`git-commit-post-finish-hook-timeout' seconds to create the
-commit, then the hook is not run at all."
- :group 'git-commit
- :safe 'numberp
- :type 'number)
-
-(defcustom git-commit-finish-query-functions
- '(git-commit-check-style-conventions)
- "List of functions called to query before performing commit.
-
-The commit message buffer is current while the functions are
-called. If any of them returns nil, then the commit is not
-performed and the buffer is not killed. The user should then
-fix the issue and try again.
-
-The functions are called with one argument. If it is non-nil,
-then that indicates that the user used a prefix argument to
-force finishing the session despite issues. Functions should
-usually honor this wish and return non-nil."
- :options '(git-commit-check-style-conventions)
- :type 'hook
- :group 'git-commit)
-
-(defcustom git-commit-style-convention-checks '(non-empty-second-line)
- "List of checks performed by `git-commit-check-style-conventions'.
-Valid members are `non-empty-second-line' and `overlong-summary-line'.
-That function is a member of `git-commit-finish-query-functions'."
- :options '(non-empty-second-line overlong-summary-line)
- :type '(list :convert-widget custom-hook-convert-widget)
- :group 'git-commit)
-
-(defcustom git-commit-summary-max-length 68
- "Column beyond which characters in the summary lines are highlighted.
-
-The highlighting indicates that the summary is getting too long
-by some standards. It does in no way imply that going over the
-limit a few characters or in some cases even many characters is
-anything that deserves shaming. It's just a friendly reminder
-that if you can make the summary shorter, then you might want
-to consider doing so."
- :group 'git-commit
- :safe 'numberp
- :type 'number)
-
-(defcustom git-commit-fill-column nil
- "Override `fill-column' in commit message buffers.
-
-If this is non-nil, then it should be an integer. If that is the
-case and the buffer-local value of `fill-column' is not already
-set by the time `git-commit-turn-on-auto-fill' is called as a
-member of `git-commit-setup-hook', then that function sets the
-buffer-local value of `fill-column' to the value of this option.
-
-This option exists mostly for historic reasons. If you are not
-already using it, then you probably shouldn't start doing so."
- :group 'git-commit
- :safe 'numberp
- :type '(choice (const :tag "use regular fill-column")
- number))
-
-(make-obsolete-variable 'git-commit-fill-column 'fill-column
- "Magit 2.11.0" 'set)
-
-(defcustom git-commit-known-pseudo-headers
- '("Signed-off-by" "Acked-by" "Modified-by" "Cc"
- "Suggested-by" "Reported-by" "Tested-by" "Reviewed-by"
- "Co-authored-by" "Co-developed-by")
- "A list of Git pseudo headers to be highlighted."
- :group 'git-commit
- :safe (lambda (val) (and (listp val) (seq-every-p #'stringp val)))
- :type '(repeat string))
-
-(defcustom git-commit-use-local-message-ring nil
- "Whether to use a local message ring instead of the global one.
-This can be set globally, in which case every repository gets its
-own commit message ring, or locally for a single repository. If
-Magit isn't available, then setting this to a non-nil value has
-no effect."
- :group 'git-commit
- :safe 'booleanp
- :type 'boolean)
-
-;;;; Faces
-
-(defgroup git-commit-faces nil
- "Faces used for highlighting Git commit messages."
- :prefix "git-commit-"
- :group 'git-commit
- :group 'faces)
-
-(defface git-commit-summary
- '((t :inherit font-lock-type-face))
- "Face used for the summary in commit messages."
- :group 'git-commit-faces)
-
-(defface git-commit-overlong-summary
- '((t :inherit font-lock-warning-face))
- "Face used for the tail of overlong commit message summaries."
- :group 'git-commit-faces)
-
-(defface git-commit-nonempty-second-line
- '((t :inherit font-lock-warning-face))
- "Face used for non-whitespace on the second line of commit messages."
- :group 'git-commit-faces)
-
-(defface git-commit-keyword
- '((t :inherit font-lock-string-face))
- "Face used for keywords in commit messages.
-In this context a \"keyword\" is text surrounded by brackets."
- :group 'git-commit-faces)
-
-(define-obsolete-face-alias 'git-commit-note
- 'git-commit-keyword "Git-Commit 3.0.0")
-
-(defface git-commit-pseudo-header
- '((t :inherit font-lock-string-face))
- "Face used for pseudo headers in commit messages."
- :group 'git-commit-faces)
-
-(defface git-commit-known-pseudo-header
- '((t :inherit font-lock-keyword-face))
- "Face used for the keywords of known pseudo headers in commit messages."
- :group 'git-commit-faces)
-
-(defface git-commit-comment-branch-local
- (if (featurep 'magit)
- '((t :inherit magit-branch-local))
- '((t :inherit font-lock-variable-name-face)))
- "Face used for names of local branches in commit message comments."
- :group 'git-commit-faces)
-
-(define-obsolete-face-alias 'git-commit-comment-branch
- 'git-commit-comment-branch-local "Git-Commit 2.12.0")
-
-(defface git-commit-comment-branch-remote
- (if (featurep 'magit)
- '((t :inherit magit-branch-remote))
- '((t :inherit font-lock-variable-name-face)))
- "Face used for names of remote branches in commit message comments.
-This is only used if Magit is available."
- :group 'git-commit-faces)
-
-(defface git-commit-comment-detached
- '((t :inherit git-commit-comment-branch-local))
- "Face used for detached `HEAD' in commit message comments."
- :group 'git-commit-faces)
-
-(defface git-commit-comment-heading
- '((t :inherit git-commit-known-pseudo-header))
- "Face used for headings in commit message comments."
- :group 'git-commit-faces)
-
-(defface git-commit-comment-file
- '((t :inherit git-commit-pseudo-header))
- "Face used for file names in commit message comments."
- :group 'git-commit-faces)
-
-(defface git-commit-comment-action
- '((t :inherit bold))
- "Face used for actions in commit message comments."
- :group 'git-commit-faces)
-
-;;; Keymap
-
-(defvar git-commit-mode-map
- (let ((map (make-sparse-keymap)))
- (define-key map (kbd "M-p") #'git-commit-prev-message)
- (define-key map (kbd "M-n") #'git-commit-next-message)
- (define-key map (kbd "C-c M-p") #'git-commit-search-message-backward)
- (define-key map (kbd "C-c M-n") #'git-commit-search-message-forward)
- (define-key map (kbd "C-c C-i") #'git-commit-insert-pseudo-header)
- (define-key map (kbd "C-c C-a") #'git-commit-ack)
- (define-key map (kbd "C-c M-i") #'git-commit-suggested)
- (define-key map (kbd "C-c C-m") #'git-commit-modified)
- (define-key map (kbd "C-c C-o") #'git-commit-cc)
- (define-key map (kbd "C-c C-p") #'git-commit-reported)
- (define-key map (kbd "C-c C-r") #'git-commit-review)
- (define-key map (kbd "C-c C-s") #'git-commit-signoff)
- (define-key map (kbd "C-c C-t") #'git-commit-test)
- (define-key map (kbd "C-c M-s") #'git-commit-save-message)
- map)
- "Key map used by `git-commit-mode'.")
-
-;;; Menu
-
-(require 'easymenu)
-(easy-menu-define git-commit-mode-menu git-commit-mode-map
- "Git Commit Mode Menu"
- '("Commit"
- ["Previous" git-commit-prev-message t]
- ["Next" git-commit-next-message t]
- "-"
- ["Ack" git-commit-ack :active t
- :help "Insert an 'Acked-by' header"]
- ["Sign-Off" git-commit-signoff :active t
- :help "Insert a 'Signed-off-by' header"]
- ["Modified-by" git-commit-modified :active t
- :help "Insert a 'Modified-by' header"]
- ["Tested-by" git-commit-test :active t
- :help "Insert a 'Tested-by' header"]
- ["Reviewed-by" git-commit-review :active t
- :help "Insert a 'Reviewed-by' header"]
- ["CC" git-commit-cc t
- :help "Insert a 'Cc' header"]
- ["Reported" git-commit-reported :active t
- :help "Insert a 'Reported-by' header"]
- ["Suggested" git-commit-suggested t
- :help "Insert a 'Suggested-by' header"]
- ["Co-authored-by" git-commit-co-authored t
- :help "Insert a 'Co-authored-by' header"]
- ["Co-developed-by" git-commit-co-developed t
- :help "Insert a 'Co-developed-by' header"]
- "-"
- ["Save" git-commit-save-message t]
- ["Cancel" with-editor-cancel t]
- ["Commit" with-editor-finish t]))
-
-;;; Hooks
-
-(defconst git-commit-filename-regexp "/\\(\
-\\(\\(COMMIT\\|NOTES\\|PULLREQ\\|MERGEREQ\\|TAG\\)_EDIT\\|MERGE_\\|\\)MSG\
-\\|\\(BRANCH\\|EDIT\\)_DESCRIPTION\\)\\'")
-
-(with-eval-after-load 'recentf
- (add-to-list 'recentf-exclude git-commit-filename-regexp))
-
-(add-to-list 'with-editor-file-name-history-exclude git-commit-filename-regexp)
-
-(defun git-commit-setup-font-lock-in-buffer ()
- (and buffer-file-name
- (string-match-p git-commit-filename-regexp buffer-file-name)
- (git-commit-setup-font-lock)))
-
-(add-hook 'after-change-major-mode-hook #'git-commit-setup-font-lock-in-buffer)
-
-(defun git-commit-setup-check-buffer ()
- (and buffer-file-name
- (string-match-p git-commit-filename-regexp buffer-file-name)
- (git-commit-setup)))
-
-(defvar git-commit-mode)
-
-(defun git-commit-file-not-found ()
- ;; cygwin git will pass a cygwin path (/cygdrive/c/foo/.git/...),
- ;; try to handle this in window-nt Emacs.
- (when-let
- ((file (and (or (string-match-p git-commit-filename-regexp
- buffer-file-name)
- (and (boundp 'git-rebase-filename-regexp)
- (string-match-p git-rebase-filename-regexp
- buffer-file-name)))
- (not (file-accessible-directory-p
- (file-name-directory buffer-file-name)))
- (if (require 'magit-git nil t)
- ;; Emacs prepends a "c:".
- (magit-expand-git-file-name
- (substring buffer-file-name 2))
- ;; Fallback if we can't load `magit-git'.
- (and (string-match
- "\\`[a-z]:/\\(cygdrive/\\)?\\([a-z]\\)/\\(.*\\)"
- buffer-file-name)
- (concat (match-string 2 buffer-file-name) ":/"
- (match-string 3 buffer-file-name)))))))
- (when (file-accessible-directory-p (file-name-directory file))
- (let ((inhibit-read-only t))
- (insert-file-contents file t)
- t))))
-
-(when (eq system-type 'windows-nt)
- (add-hook 'find-file-not-found-functions #'git-commit-file-not-found))
-
-(defconst git-commit-usage-message "\
-Type \\[with-editor-finish] to finish, \
-\\[with-editor-cancel] to cancel, and \
-\\[git-commit-prev-message] and \\[git-commit-next-message] \
-to recover older messages")
-
-(defun git-commit-setup ()
- (when (fboundp 'magit-toplevel)
- ;; `magit-toplevel' is autoloaded and defined in magit-git.el,
- ;; That library declares this functions without loading
- ;; magit-process.el, which defines it.
- (require 'magit-process nil t))
- (when git-commit-major-mode
- (let ((auto-mode-alist (list (cons (concat "\\`"
- (regexp-quote buffer-file-name)
- "\\'")
- git-commit-major-mode)))
- ;; The major-mode hook might want to consult these minor
- ;; modes, while the minor-mode hooks might want to consider
- ;; the major mode.
- (git-commit-mode t)
- (with-editor-mode t))
- (normal-mode t)))
- ;; Pretend that git-commit-mode is a major-mode,
- ;; so that directory-local settings can be used.
- (let ((default-directory
- (or (and (not (file-exists-p ".dir-locals.el"))
- ;; When $GIT_DIR/.dir-locals.el doesn't exist,
- ;; fallback to $GIT_WORK_TREE/.dir-locals.el,
- ;; because the maintainer can use the latter
- ;; to enforce conventions, while s/he has no
- ;; control over the former.
- (fboundp 'magit-toplevel) ; silence byte-compiler
- (magit-toplevel))
- default-directory)))
- (let ((buffer-file-name nil) ; trick hack-dir-local-variables
- (major-mode 'git-commit-mode)) ; trick dir-locals-collect-variables
- (hack-dir-local-variables)
- (hack-local-variables-apply)))
- ;; Show our own message using our hook.
- (setq with-editor-show-usage nil)
- (setq with-editor-usage-message git-commit-usage-message)
- (unless with-editor-mode
- ;; Maybe already enabled when using `shell-command' or an Emacs shell.
- (with-editor-mode 1))
- (add-hook 'with-editor-finish-query-functions
- #'git-commit-finish-query-functions nil t)
- (add-hook 'with-editor-pre-finish-hook
- #'git-commit-save-message nil t)
- (add-hook 'with-editor-pre-cancel-hook
- #'git-commit-save-message nil t)
- (when (fboundp 'magit-commit--reset-command)
- (add-hook 'with-editor-post-finish-hook #'magit-commit--reset-command)
- (add-hook 'with-editor-post-cancel-hook #'magit-commit--reset-command))
- (when (and (fboundp 'magit-rev-parse)
- (not (memq last-command
- '(magit-sequencer-continue
- magit-sequencer-skip
- magit-am-continue
- magit-am-skip
- magit-rebase-continue
- magit-rebase-skip))))
- (add-hook 'with-editor-post-finish-hook
- (apply-partially #'git-commit-run-post-finish-hook
- (magit-rev-parse "HEAD"))
- nil t)
- (when (fboundp 'magit-wip-maybe-add-commit-hook)
- (magit-wip-maybe-add-commit-hook)))
- (setq with-editor-cancel-message
- #'git-commit-cancel-message)
- (git-commit-mode 1)
- (git-commit-setup-font-lock)
- (git-commit-prepare-message-ring)
- (when (boundp 'save-place)
- (setq save-place nil))
- (save-excursion
- (goto-char (point-min))
- (when (looking-at "\\`\\(\\'\\|\n[^\n]\\)")
- (open-line 1)))
- (with-demoted-errors "Error running git-commit-setup-hook: %S"
- (run-hooks 'git-commit-setup-hook))
- (set-buffer-modified-p nil))
-
-(defun git-commit-run-post-finish-hook (previous)
- (when (and git-commit-post-finish-hook
- (require 'magit nil t)
- (fboundp 'magit-rev-parse))
- (cl-block nil
- (let ((break (time-add (current-time)
- (seconds-to-time
- git-commit-post-finish-hook-timeout))))
- (while (equal (magit-rev-parse "HEAD") previous)
- (if (time-less-p (current-time) break)
- (sit-for 0.01)
- (message "No commit created after 1 second. Not running %s."
- 'git-commit-post-finish-hook)
- (cl-return))))
- (run-hooks 'git-commit-post-finish-hook))))
-
-(define-minor-mode git-commit-mode
- "Auxiliary minor mode used when editing Git commit messages.
-This mode is only responsible for setting up some key bindings.
-Don't use it directly, instead enable `global-git-commit-mode'."
- :lighter "")
-
-(put 'git-commit-mode 'permanent-local t)
-
-(defun git-commit-setup-changelog-support ()
- "Treat ChangeLog entries as unindented paragraphs."
- (when (fboundp 'log-indent-fill-entry) ; New in Emacs 27.
- (setq-local fill-paragraph-function #'log-indent-fill-entry))
- (setq-local fill-indent-according-to-mode t)
- (setq-local paragraph-start (concat paragraph-start "\\|\\*\\|(")))
-
-(defun git-commit-turn-on-auto-fill ()
- "Unconditionally turn on Auto Fill mode.
-If `git-commit-fill-column' is non-nil, and `fill-column'
-doesn't already have a buffer-local value, then set that
-to `git-commit-fill-column'."
- (when (and (numberp git-commit-fill-column)
- (not (local-variable-p 'fill-column)))
- (setq fill-column git-commit-fill-column))
- (setq-local comment-auto-fill-only-comments nil)
- (turn-on-auto-fill))
-
-(defun git-commit-turn-on-orglink ()
- "Turn on Orglink mode if it is available.
-If `git-commit-major-mode' is `org-mode', then silently forgo
-turning on `orglink-mode'."
- (when (and (not (derived-mode-p 'org-mode))
- (boundp 'orglink-match-anywhere)
- (fboundp 'orglink-mode))
- (setq-local orglink-match-anywhere t)
- (orglink-mode 1)))
-
-(defun git-commit-turn-on-flyspell ()
- "Unconditionally turn on Flyspell mode.
-Also prevent comments from being checked and
-finally check current non-comment text."
- (require 'flyspell)
- (turn-on-flyspell)
- (setq flyspell-generic-check-word-predicate
- #'git-commit-flyspell-verify)
- (let ((end)
- (comment-start-regex (format "^\\(%s\\|$\\)" comment-start)))
- (save-excursion
- (goto-char (point-max))
- (while (and (not (bobp)) (looking-at comment-start-regex))
- (forward-line -1))
- (unless (looking-at comment-start-regex)
- (forward-line))
- (setq end (point)))
- (flyspell-region (point-min) end)))
-
-(defun git-commit-flyspell-verify ()
- (not (= (char-after (line-beginning-position))
- (aref comment-start 0))))
-
-(defun git-commit-finish-query-functions (force)
- (run-hook-with-args-until-failure
- 'git-commit-finish-query-functions force))
-
-(defun git-commit-check-style-conventions (force)
- "Check for violations of certain basic style conventions.
-
-For each violation ask the user if she wants to proceed anyway.
-Option `git-commit-style-convention-checks' controls which
-conventions are checked."
- (or force
- (save-excursion
- (goto-char (point-min))
- (re-search-forward (git-commit-summary-regexp) nil t)
- (if (equal (match-string 1) "")
- t ; Just try; we don't know whether --allow-empty-message was used.
- (and (or (not (memq 'overlong-summary-line
- git-commit-style-convention-checks))
- (equal (match-string 2) "")
- (y-or-n-p "Summary line is too long. Commit anyway? "))
- (or (not (memq 'non-empty-second-line
- git-commit-style-convention-checks))
- (not (match-string 3))
- (y-or-n-p "Second line is not empty. Commit anyway? ")))))))
-
-(defun git-commit-cancel-message ()
- (message
- (concat "Commit canceled"
- (and (memq 'git-commit-save-message with-editor-pre-cancel-hook)
- ". Message saved to `log-edit-comment-ring'"))))
-
-;;; History
-
-(defun git-commit-prev-message (arg)
- "Cycle backward through message history, after saving current message.
-With a numeric prefix ARG, go back ARG comments."
- (interactive "*p")
- (let ((len (ring-length log-edit-comment-ring)))
- (if (<= len 0)
- (progn (message "Empty comment ring") (ding))
- ;; Unlike `log-edit-previous-comment' we save the current
- ;; non-empty and newly written comment, because otherwise
- ;; it would be irreversibly lost.
- (when-let ((message (git-commit-buffer-message)))
- (unless (ring-member log-edit-comment-ring message)
- (ring-insert log-edit-comment-ring message)
- (cl-incf arg)
- (setq len (ring-length log-edit-comment-ring))))
- ;; Delete the message but not the instructions at the end.
- (save-restriction
- (goto-char (point-min))
- (narrow-to-region
- (point)
- (if (re-search-forward (concat "^" comment-start) nil t)
- (max 1 (- (point) 2))
- (point-max)))
- (delete-region (point-min) (point)))
- (setq log-edit-comment-ring-index (log-edit-new-comment-index arg len))
- (message "Comment %d" (1+ log-edit-comment-ring-index))
- (insert (ring-ref log-edit-comment-ring log-edit-comment-ring-index)))))
-
-(defun git-commit-next-message (arg)
- "Cycle forward through message history, after saving current message.
-With a numeric prefix ARG, go forward ARG comments."
- (interactive "*p")
- (git-commit-prev-message (- arg)))
-
-(defun git-commit-search-message-backward (string)
- "Search backward through message history for a match for STRING.
-Save current message first."
- (interactive
- ;; Avoid `format-prompt' because it isn't available until Emacs 28.
- (list (read-string (format "Comment substring (default %s): "
- log-edit-last-comment-match)
- nil nil log-edit-last-comment-match)))
- (cl-letf (((symbol-function #'log-edit-previous-comment)
- (symbol-function #'git-commit-prev-message)))
- (log-edit-comment-search-backward string)))
-
-(defun git-commit-search-message-forward (string)
- "Search forward through message history for a match for STRING.
-Save current message first."
- (interactive
- ;; Avoid `format-prompt' because it isn't available until Emacs 28.
- (list (read-string (format "Comment substring (default %s): "
- log-edit-last-comment-match)
- nil nil log-edit-last-comment-match)))
- (cl-letf (((symbol-function #'log-edit-previous-comment)
- (symbol-function #'git-commit-prev-message)))
- (log-edit-comment-search-forward string)))
-
-(defun git-commit-save-message ()
- "Save current message to `log-edit-comment-ring'."
- (interactive)
- (if-let ((message (git-commit-buffer-message)))
- (progn
- (when-let ((index (ring-member log-edit-comment-ring message)))
- (ring-remove log-edit-comment-ring index))
- (ring-insert log-edit-comment-ring message)
- (when (and git-commit-use-local-message-ring
- (fboundp 'magit-repository-local-set))
- (magit-repository-local-set 'log-edit-comment-ring
- log-edit-comment-ring))
- (message "Message saved"))
- (message "Only whitespace and/or comments; message not saved")))
-
-(defun git-commit-prepare-message-ring ()
- (make-local-variable 'log-edit-comment-ring-index)
- (when (and git-commit-use-local-message-ring
- (fboundp 'magit-repository-local-get))
- (setq-local log-edit-comment-ring
- (magit-repository-local-get
- 'log-edit-comment-ring
- (make-ring log-edit-maximum-comment-ring-size)))))
-
-(defun git-commit-buffer-message ()
- (let ((flush (concat "^" comment-start))
- (str (buffer-substring-no-properties (point-min) (point-max))))
- (with-temp-buffer
- (insert str)
- (goto-char (point-min))
- (when (re-search-forward (concat flush " -+ >8 -+$") nil t)
- (delete-region (line-beginning-position) (point-max)))
- (goto-char (point-min))
- (flush-lines flush)
- (goto-char (point-max))
- (unless (eq (char-before) ?\n)
- (insert ?\n))
- (setq str (buffer-string)))
- (unless (string-match "\\`[ \t\n\r]*\\'" str)
- (when (string-match "\\`\n\\{2,\\}" str)
- (setq str (replace-match "\n" t t str)))
- (when (string-match "\n\\{2,\\}\\'" str)
- (setq str (replace-match "\n" t t str)))
- str)))
-
-;;; Utilities
-
-(defsubst git-commit-executable ()
- (if (fboundp 'magit-git-executable)
- (magit-git-executable)
- "git"))
-
-;;; Headers
-
-(transient-define-prefix git-commit-insert-pseudo-header ()
- "Insert a commit message pseudo header."
- [["Insert ... by yourself"
- ("a" "Ack" git-commit-ack)
- ("m" "Modified" git-commit-modified)
- ("r" "Reviewed" git-commit-review)
- ("s" "Signed-off" git-commit-signoff)
- ("t" "Tested" git-commit-test)]
- ["Insert ... by someone"
- ("C-c" "Cc" git-commit-cc)
- ("C-r" "Reported" git-commit-reported)
- ("C-i" "Suggested" git-commit-suggested)
- ("C-a" "Co-authored" git-commit-co-authored)
- ("C-d" "Co-developed" git-commit-co-developed)]])
-
-(defun git-commit-ack (name mail)
- "Insert a header acknowledging that you have looked at the commit."
- (interactive (git-commit-self-ident))
- (git-commit-insert-header "Acked-by" name mail))
-
-(defun git-commit-modified (name mail)
- "Insert a header to signal that you have modified the commit."
- (interactive (git-commit-self-ident))
- (git-commit-insert-header "Modified-by" name mail))
-
-(defun git-commit-review (name mail)
- "Insert a header acknowledging that you have reviewed the commit."
- (interactive (git-commit-self-ident))
- (git-commit-insert-header "Reviewed-by" name mail))
-
-(defun git-commit-signoff (name mail)
- "Insert a header to sign off the commit."
- (interactive (git-commit-self-ident))
- (git-commit-insert-header "Signed-off-by" name mail))
-
-(defun git-commit-test (name mail)
- "Insert a header acknowledging that you have tested the commit."
- (interactive (git-commit-self-ident))
- (git-commit-insert-header "Tested-by" name mail))
-
-(defun git-commit-cc (name mail)
- "Insert a header mentioning someone who might be interested."
- (interactive (git-commit-read-ident "Cc"))
- (git-commit-insert-header "Cc" name mail))
-
-(defun git-commit-reported (name mail)
- "Insert a header mentioning the person who reported the issue."
- (interactive (git-commit-read-ident "Reported-by"))
- (git-commit-insert-header "Reported-by" name mail))
-
-(defun git-commit-suggested (name mail)
- "Insert a header mentioning the person who suggested the change."
- (interactive (git-commit-read-ident "Suggested-by"))
- (git-commit-insert-header "Suggested-by" name mail))
-
-(defun git-commit-co-authored (name mail)
- "Insert a header mentioning the person who co-authored the commit."
- (interactive (git-commit-read-ident "Co-authored-by"))
- (git-commit-insert-header "Co-authored-by" name mail))
-
-(defun git-commit-co-developed (name mail)
- "Insert a header mentioning the person who co-developed the commit."
- (interactive (git-commit-read-ident "Co-developed-by"))
- (git-commit-insert-header "Co-developed-by" name mail))
-
-(defun git-commit-self-ident ()
- (list (or (getenv "GIT_AUTHOR_NAME")
- (getenv "GIT_COMMITTER_NAME")
- (with-demoted-errors "Error running 'git config user.name': %S"
- (car (process-lines
- (git-commit-executable) "config" "user.name")))
- user-full-name
- (read-string "Name: "))
- (or (getenv "GIT_AUTHOR_EMAIL")
- (getenv "GIT_COMMITTER_EMAIL")
- (getenv "EMAIL")
- (with-demoted-errors "Error running 'git config user.email': %S"
- (car (process-lines
- (git-commit-executable) "config" "user.email")))
- (read-string "Email: "))))
-
-(defvar git-commit-read-ident-history nil)
-
-(defun git-commit-read-ident (prompt)
- (if (require 'magit-git nil t)
- (let ((str (magit-completing-read
- prompt
- (sort (delete-dups
- (magit-git-lines "log" "-n9999" "--format=%aN <%ae>"))
- #'string<)
- nil nil nil 'git-commit-read-ident-history)))
- (save-match-data
- (if (string-match "\\`\\([^<]+\\) *<\\([^>]+\\)>\\'" str)
- (list (save-match-data (string-trim (match-string 1 str)))
- (string-trim (match-string 2 str)))
- (user-error "Invalid input"))))
- (list (read-string "Name: ")
- (read-string "Email: "))))
-
-(defun git-commit-insert-header (header name email)
- (setq header (format "%s: %s <%s>" header name email))
- (save-excursion
- (goto-char (point-max))
- (cond ((re-search-backward "^[-a-zA-Z]+: [^<]+? <[^>]+>" nil t)
- (end-of-line)
- (insert ?\n header)
- (unless (= (char-after) ?\n)
- (insert ?\n)))
- (t
- (while (re-search-backward (concat "^" comment-start) nil t))
- (unless (looking-back "\n\n" nil)
- (insert ?\n))
- (insert header ?\n)))
- (unless (or (eobp) (= (char-after) ?\n))
- (insert ?\n))))
-
-;;; Font-Lock
-
-(defvar-local git-commit-need-summary-line t
- "Whether the text should have a heading that is separated from the body.
-
-For commit messages that is a convention that should not
-be violated. For notes it is up to the user. If you do
-not want to insist on an empty second line here, then use
-something like:
-
- (add-hook \\='git-commit-setup-hook
- (lambda ()
- (when (equal (file-name-nondirectory (buffer-file-name))
- \"NOTES_EDITMSG\")
- (setq git-commit-need-summary-line nil))))")
-
-(defun git-commit-summary-regexp ()
- (if git-commit-need-summary-line
- (concat
- ;; Leading empty lines and comments
- (format "\\`\\(?:^\\(?:\\s-*\\|%s.*\\)\n\\)*" comment-start)
- ;; Summary line
- (format "\\(.\\{0,%d\\}\\)\\(.*\\)" git-commit-summary-max-length)
- ;; Non-empty non-comment second line
- (format "\\(?:\n%s\\|\n\\(.+\\)\\)?" comment-start))
- "\\(EASTER\\) \\(EGG\\)"))
-
-(defun git-commit-extend-region-summary-line ()
- "Identify the multiline summary-regexp construct.
-Added to `font-lock-extend-region-functions'."
- (save-excursion
- (save-match-data
- (goto-char (point-min))
- (when (looking-at (git-commit-summary-regexp))
- (let ((summary-beg (match-beginning 0))
- (summary-end (match-end 0)))
- (when (or (< summary-beg font-lock-beg summary-end)
- (< summary-beg font-lock-end summary-end))
- (setq font-lock-beg (min font-lock-beg summary-beg))
- (setq font-lock-end (max font-lock-end summary-end))))))))
-
-(defvar-local git-commit--branch-name-regexp nil)
-
-(defconst git-commit-comment-headings
- '("Changes to be committed:"
- "Untracked files:"
- "Changed but not updated:"
- "Changes not staged for commit:"
- "Unmerged paths:"
- "Author:"
- "Date:")
- "Also fontified outside of comments in `git-commit-font-lock-keywords-2'.")
-
-(defconst git-commit-font-lock-keywords-1
- '(;; Pseudo headers
- (eval . `(,(format "^\\(%s:\\)\\( .*\\)"
- (regexp-opt git-commit-known-pseudo-headers))
- (1 'git-commit-known-pseudo-header)
- (2 'git-commit-pseudo-header)))
- ;; Summary
- (eval . `(,(git-commit-summary-regexp)
- (1 'git-commit-summary)))
- ;; - Keyword [aka "text in brackets"] (overrides summary)
- ("\\[[^][]+?\\]"
- (0 'git-commit-keyword t))
- ;; - Non-empty second line (overrides summary and note)
- (eval . `(,(git-commit-summary-regexp)
- (2 'git-commit-overlong-summary t t)
- (3 'git-commit-nonempty-second-line t t)))))
-
-(defconst git-commit-font-lock-keywords-2
- `(,@git-commit-font-lock-keywords-1
- ;; Comments
- (eval . `(,(format "^%s.*" comment-start)
- (0 'font-lock-comment-face append)))
- (eval . `(,(format "^%s On branch \\(.*\\)" comment-start)
- (1 'git-commit-comment-branch-local t)))
- (eval . `(,(format "^%s \\(HEAD\\) detached at" comment-start)
- (1 'git-commit-comment-detached t)))
- (eval . `(,(format "^%s %s" comment-start
- (regexp-opt git-commit-comment-headings t))
- (1 'git-commit-comment-heading t)))
- (eval . `(,(format "^%s\t\\(?:\\([^:\n]+\\):\\s-+\\)?\\(.*\\)" comment-start)
- (1 'git-commit-comment-action t t)
- (2 'git-commit-comment-file t)))
- ;; "commit HASH"
- (eval . `(,(rx bol "commit " (1+ alnum) eol)
- (0 'git-commit-pseudo-header)))
- ;; `git-commit-comment-headings' (but not in commented lines)
- (eval . `(,(rx-to-string `(seq bol (or ,@git-commit-comment-headings) (1+ blank) (1+ nonl) eol))
- (0 'git-commit-pseudo-header)))))
-
-(defconst git-commit-font-lock-keywords-3
- `(,@git-commit-font-lock-keywords-2
- ;; More comments
- (eval
- ;; Your branch is ahead of 'master' by 3 commits.
- ;; Your branch is behind 'master' by 2 commits, and can be fast-forwarded.
- . `(,(format
- "^%s Your branch is \\(?:ahead\\|behind\\) of '%s' by \\([0-9]*\\)"
- comment-start git-commit--branch-name-regexp)
- (1 'git-commit-comment-branch-local t)
- (2 'git-commit-comment-branch-remote t)
- (3 'bold t)))
- (eval
- ;; Your branch is up to date with 'master'.
- ;; Your branch and 'master' have diverged,
- . `(,(format
- "^%s Your branch \\(?:is up[- ]to[- ]date with\\|and\\) '%s'"
- comment-start git-commit--branch-name-regexp)
- (1 'git-commit-comment-branch-local t)
- (2 'git-commit-comment-branch-remote t)))
- (eval
- ;; and have 1 and 2 different commits each, respectively.
- . `(,(format
- "^%s and have \\([0-9]*\\) and \\([0-9]*\\) commits each"
- comment-start)
- (1 'bold t)
- (2 'bold t)))))
-
-(defvar git-commit-font-lock-keywords git-commit-font-lock-keywords-3
- "Font-Lock keywords for Git-Commit mode.")
-
-(defun git-commit-setup-font-lock ()
- (with-demoted-errors "Error running git-commit-setup-font-lock: %S"
- (let ((table (make-syntax-table (syntax-table))))
- (when comment-start
- (modify-syntax-entry (string-to-char comment-start) "." table))
- (modify-syntax-entry ?# "." table)
- (modify-syntax-entry ?\" "." table)
- (modify-syntax-entry ?\' "." table)
- (modify-syntax-entry ?` "." table)
- (set-syntax-table table))
- (setq-local comment-start
- (or (with-temp-buffer
- (and (zerop
- (call-process
- (git-commit-executable) nil (list t nil) nil
- "config" "core.commentchar"))
- (not (bobp))
- (progn
- (goto-char (point-min))
- (buffer-substring (point) (line-end-position)))))
- "#"))
- (setq-local comment-start-skip (format "^%s+[\s\t]*" comment-start))
- (setq-local comment-end-skip "\n")
- (setq-local comment-use-syntax nil)
- (setq-local git-commit--branch-name-regexp
- (if (and (featurep 'magit-git)
- ;; When using cygwin git, we may end up in a
- ;; non-existing directory, which would cause
- ;; any git calls to signal an error.
- (file-accessible-directory-p default-directory))
- (progn
- ;; Make sure the below functions are available.
- (require 'magit)
- ;; Font-Lock wants every submatch to succeed, so
- ;; also match the empty string. Avoid listing
- ;; remote branches and using `regexp-quote',
- ;; because in repositories have thousands of
- ;; branches that would be very slow. See #4353.
- (format "\\(\\(?:%s\\)\\|\\)\\([^']+\\)"
- (mapconcat #'identity
- (magit-list-local-branch-names)
- "\\|")))
- "\\([^']*\\)"))
- (setq-local font-lock-multiline t)
- (add-hook 'font-lock-extend-region-functions
- #'git-commit-extend-region-summary-line
- t t)
- (font-lock-add-keywords nil git-commit-font-lock-keywords)))
-
-(defun git-commit-propertize-diff ()
- (require 'diff-mode)
- (save-excursion
- (goto-char (point-min))
- (when (re-search-forward "^diff --git" nil t)
- (beginning-of-line)
- (let ((buffer (current-buffer)))
- (insert
- (with-temp-buffer
- (insert
- (with-current-buffer buffer
- (prog1 (buffer-substring-no-properties (point) (point-max))
- (delete-region (point) (point-max)))))
- (let ((diff-default-read-only nil))
- (diff-mode))
- (let (font-lock-verbose font-lock-support-mode)
- (if (fboundp 'font-lock-ensure)
- (font-lock-ensure)
- (with-no-warnings
- (font-lock-fontify-buffer))))
- (let (next (pos (point-min)))
- (while (setq next (next-single-property-change pos 'face))
- (put-text-property pos next 'font-lock-face
- (get-text-property pos 'face))
- (setq pos next))
- (put-text-property pos (point-max) 'font-lock-face
- (get-text-property pos 'face)))
- (buffer-string)))))))
-
-;;; Elisp Text Mode
-
-(define-derived-mode git-commit-elisp-text-mode text-mode "ElText"
- "Major mode for editing commit messages of elisp projects.
-This is intended for use as `git-commit-major-mode' for projects
-that expect `symbols' to look like this. I.e. like they look in
-Elisp doc-strings, including this one. Unlike in doc-strings,
-\"strings\" also look different than the other text."
- (setq font-lock-defaults '(git-commit-elisp-text-mode-keywords)))
-
-(defvar git-commit-elisp-text-mode-keywords
- `((,(concat "[`‘]\\(" lisp-mode-symbol-regexp "\\)['’]")
- (1 font-lock-constant-face prepend))
- ("\"[^\"]*\"" (0 font-lock-string-face prepend))))
-
-;;; _
-(provide 'git-commit)
-;;; git-commit.el ends here
--- /dev/null
+;;; git-commit-autoloads.el --- automatically extracted autoloads -*- lexical-binding: t -*-
+;;
+;;; Code:
+
+(add-to-list 'load-path (directory-file-name
+ (or (file-name-directory #$) (car load-path))))
+
+\f
+;;;### (autoloads nil "git-commit" "git-commit.el" (0 0 0 0))
+;;; Generated autoloads from git-commit.el
+(put 'git-commit-major-mode 'safe-local-variable
+ (lambda (val)
+ (memq val '(text-mode
+ markdown-mode
+ org-mode
+ fundamental-mode
+ git-commit-elisp-text-mode))))
+
+(register-definition-prefixes "git-commit" '("git-commit-" "global-git-commit-mode"))
+
+;;;***
+\f
+;;;### (autoloads nil nil ("git-commit-pkg.el") (0 0 0 0))
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; coding: utf-8
+;; End:
+;;; git-commit-autoloads.el ends here
--- /dev/null
+(define-package "git-commit" "20230521.2344" "Edit Git commit messages."
+ '((emacs "25.1")
+ (compat "29.1.3.4")
+ (transient "20230201")
+ (with-editor "20230118"))
+ :commit "d81db78a0611d21048c1e2ff069a83f964092f10" :authors
+ '(("Jonas Bernoulli" . "jonasNOSPAMbernoul.li")
+ ("Sebastian Wiesner" . "lunaryornNOSPAMgmail.com")
+ ("Florian Ragwitz" . "raflNOSPAMdebian.org")
+ ("Marius Vollmer" . "marius.vollmerNOSPAMgmail.com"))
+ :maintainer
+ '("Jonas Bernoulli" . "jonasNOSPAMbernoul.li")
+ :keywords
+ '("git" "tools" "vc")
+ :url "https://github.com/magit/magit")
+;; Local Variables:
+;; no-byte-compile: t
+;; End:
--- /dev/null
+;;; git-commit.el --- Edit Git commit messages -*- lexical-binding:t; coding:utf-8 -*-
+
+;; Copyright (C) 2008-2023 The Magit Project Contributors
+
+;; Author: Jonas Bernoulli <jonasNOSPAMbernoul.li>
+;; Sebastian Wiesner <lunaryornNOSPAMgmail.com>
+;; Florian Ragwitz <raflNOSPAMdebian.org>
+;; Marius Vollmer <marius.vollmerNOSPAMgmail.com>
+;; Maintainer: Jonas Bernoulli <jonasNOSPAMbernoul.li>
+
+;; Homepage: https://github.com/magit/magit
+;; Keywords: git tools vc
+
+;; Package-Version: 3.3.0.50-git
+;; Package-Requires: (
+;; (emacs "25.1")
+;; (compat "29.1.3.4")
+;; (transient "0.3.6")
+;; (with-editor "3.0.5"))
+
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;; Magit is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published
+;; by the Free Software Foundation, either version 3 of the License,
+;; or (at your option) any later version.
+;;
+;; Magit is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with Magit. If not, see <https://www.gnu.org/licenses/>.
+
+;; You should have received a copy of the AUTHORS.md file, which
+;; lists all contributors. If not, see https://magit.vc/authors.
+
+;;; Commentary:
+
+;; This package assists the user in writing good Git commit messages.
+
+;; While Git allows for the message to be provided on the command
+;; line, it is preferable to tell Git to create the commit without
+;; actually passing it a message. Git then invokes the `$GIT_EDITOR'
+;; (or if that is undefined `$EDITOR') asking the user to provide the
+;; message by editing the file ".git/COMMIT_EDITMSG" (or another file
+;; in that directory, e.g., ".git/MERGE_MSG" for merge commits).
+
+;; When `global-git-commit-mode' is enabled, which it is by default,
+;; then opening such a file causes the features described below, to
+;; be enabled in that buffer. Normally this would be done using a
+;; major-mode but to allow the use of any major-mode, as the user sees
+;; fit, it is done here by running a setup function, which among other
+;; things turns on the preferred major-mode, by default `text-mode'.
+
+;; Git waits for the `$EDITOR' to finish and then either creates the
+;; commit using the contents of the file as commit message, or, if the
+;; editor process exited with a non-zero exit status, aborts without
+;; creating a commit. Unfortunately Emacsclient (which is what Emacs
+;; users should be using as `$EDITOR' or at least as `$GIT_EDITOR')
+;; does not differentiate between "successfully" editing a file and
+;; aborting; not out of the box that is.
+
+;; By making use of the `with-editor' package this package provides
+;; both ways of finish an editing session. In either case the file
+;; is saved, but Emacseditor's exit code differs.
+;;
+;; C-c C-c Finish the editing session successfully by returning
+;; with exit code 0. Git then creates the commit using
+;; the message it finds in the file.
+;;
+;; C-c C-k Aborts the edit editing session by returning with exit
+;; code 1. Git then aborts the commit.
+
+;; Aborting the commit does not cause the message to be lost, but
+;; relying solely on the file not being tampered with is risky. This
+;; package additionally stores all aborted messages for the duration
+;; of the current session (i.e., until you close Emacs). To get back
+;; an aborted message use M-p and M-n while editing a message.
+;;
+;; M-p Replace the buffer contents with the previous message
+;; from the message ring. Of course only after storing
+;; the current content there too.
+;;
+;; M-n Replace the buffer contents with the next message from
+;; the message ring, after storing the current content.
+
+;; Some support for pseudo headers as used in some projects is
+;; provided by these commands:
+;;
+;; C-c C-s Insert a Signed-off-by header.
+;; C-c C-a Insert a Acked-by header.
+;; C-c C-m Insert a Modified-by header.
+;; C-c C-t Insert a Tested-by header.
+;; C-c C-r Insert a Reviewed-by header.
+;; C-c C-o Insert a Cc header.
+;; C-c C-p Insert a Reported-by header.
+;; C-c C-i Insert a Suggested-by header.
+
+;; When Git requests a commit message from the user, it does so by
+;; having her edit a file which initially contains some comments,
+;; instructing her what to do, and providing useful information, such
+;; as which files were modified. These comments, even when left
+;; intact by the user, do not become part of the commit message. This
+;; package ensures these comments are propertizes as such and further
+;; prettifies them by using different faces for various parts, such as
+;; files.
+
+;; Finally this package highlights style errors, like lines that are
+;; too long, or when the second line is not empty. It may even nag
+;; you when you attempt to finish the commit without having fixed
+;; these issues. The style checks and many other settings can easily
+;; be configured:
+;;
+;; M-x customize-group RET git-commit RET
+
+;;; Code:
+
+(require 'compat)
+(require 'subr-x)
+(require 'log-edit)
+(require 'ring)
+(require 'rx)
+(require 'server)
+(require 'transient)
+(require 'with-editor)
+
+;; For historic reasons Magit isn't a hard dependency.
+(require 'magit-base nil t)
+(require 'magit-git nil t)
+(declare-function magit-completing-read "magit-base"
+ ( prompt collection &optional predicate require-match
+ initial-input hist def fallback))
+(declare-function magit-expand-git-file-name "magit-git" (filename))
+(declare-function magit-git-lines "magit-git" (&rest args))
+(declare-function magit-hook-custom-get "magit-base" (symbol))
+(declare-function magit-list-local-branch-names "magit-git" ())
+
+(defvar diff-default-read-only)
+(defvar flyspell-generic-check-word-predicate)
+(defvar font-lock-beg)
+(defvar font-lock-end)
+(defvar recentf-exclude)
+
+;;; Options
+;;;; Variables
+
+(defgroup git-commit nil
+ "Edit Git commit messages."
+ :prefix "git-commit-"
+ :link '(info-link "(magit)Editing Commit Messages")
+ :group 'tools)
+
+(define-minor-mode global-git-commit-mode
+ "Edit Git commit messages.
+
+This global mode arranges for `git-commit-setup' to be called
+when a Git commit message file is opened. That usually happens
+when Git uses the Emacsclient as $GIT_EDITOR to have the user
+provide such a commit message.
+
+Loading the library `git-commit' by default enables this mode,
+but the library is not automatically loaded because doing that
+would pull in many dependencies and increase startup time too
+much. You can either rely on `magit' loading this library or
+you can load it explicitly. Autoloading is not an alternative
+because in this case autoloading would immediately trigger
+full loading."
+ :group 'git-commit
+ :type 'boolean
+ :global t
+ :init-value t
+ :initialize (lambda (symbol exp)
+ (custom-initialize-default symbol exp)
+ (when global-git-commit-mode
+ (add-hook 'find-file-hook #'git-commit-setup-check-buffer)))
+ (if global-git-commit-mode
+ (add-hook 'find-file-hook #'git-commit-setup-check-buffer)
+ (remove-hook 'find-file-hook #'git-commit-setup-check-buffer)))
+
+(defcustom git-commit-major-mode #'text-mode
+ "Major mode used to edit Git commit messages.
+The major mode configured here is turned on by the minor mode
+`git-commit-mode'."
+ :group 'git-commit
+ :type '(choice (function-item text-mode)
+ (function-item markdown-mode)
+ (function-item org-mode)
+ (function-item fundamental-mode)
+ (function-item git-commit-elisp-text-mode)
+ (function :tag "Another mode")
+ (const :tag "No major mode")))
+;;;###autoload(put 'git-commit-major-mode 'safe-local-variable
+;;;###autoload (lambda (val)
+;;;###autoload (memq val '(text-mode
+;;;###autoload markdown-mode
+;;;###autoload org-mode
+;;;###autoload fundamental-mode
+;;;###autoload git-commit-elisp-text-mode))))
+
+(defcustom git-commit-setup-hook
+ '(git-commit-save-message
+ git-commit-setup-changelog-support
+ git-commit-turn-on-auto-fill
+ git-commit-propertize-diff
+ bug-reference-mode)
+ "Hook run at the end of `git-commit-setup'."
+ :group 'git-commit
+ :type 'hook
+ :get (and (featurep 'magit-base) #'magit-hook-custom-get)
+ :options '(git-commit-save-message
+ git-commit-setup-changelog-support
+ magit-generate-changelog
+ git-commit-turn-on-auto-fill
+ git-commit-turn-on-orglink
+ git-commit-turn-on-flyspell
+ git-commit-propertize-diff
+ bug-reference-mode))
+
+(defcustom git-commit-post-finish-hook nil
+ "Hook run after the user finished writing a commit message.
+
+\\<with-editor-mode-map>\
+This hook is only run after pressing \\[with-editor-finish] in a buffer used
+to edit a commit message. If a commit is created without the
+user typing a message into a buffer, then this hook is not run.
+
+This hook is not run until the new commit has been created. If
+that takes Git longer than `git-commit-post-finish-hook-timeout'
+seconds, then this hook isn't run at all. For certain commands
+such as `magit-rebase-continue' this hook is never run because
+doing so would lead to a race condition.
+
+This hook is only run if `magit' is available.
+
+Also see `magit-post-commit-hook'."
+ :group 'git-commit
+ :type 'hook
+ :get (and (featurep 'magit-base) #'magit-hook-custom-get))
+
+(defcustom git-commit-post-finish-hook-timeout 1
+ "Time in seconds to wait for git to create a commit.
+
+The hook `git-commit-post-finish-hook' (which see) is run only
+after git is done creating a commit. If it takes longer than
+`git-commit-post-finish-hook-timeout' seconds to create the
+commit, then the hook is not run at all."
+ :group 'git-commit
+ :safe 'numberp
+ :type 'number)
+
+(defcustom git-commit-finish-query-functions
+ '(git-commit-check-style-conventions)
+ "List of functions called to query before performing commit.
+
+The commit message buffer is current while the functions are
+called. If any of them returns nil, then the commit is not
+performed and the buffer is not killed. The user should then
+fix the issue and try again.
+
+The functions are called with one argument. If it is non-nil,
+then that indicates that the user used a prefix argument to
+force finishing the session despite issues. Functions should
+usually honor this wish and return non-nil."
+ :options '(git-commit-check-style-conventions)
+ :type 'hook
+ :group 'git-commit)
+
+(defcustom git-commit-style-convention-checks '(non-empty-second-line)
+ "List of checks performed by `git-commit-check-style-conventions'.
+Valid members are `non-empty-second-line' and `overlong-summary-line'.
+That function is a member of `git-commit-finish-query-functions'."
+ :options '(non-empty-second-line overlong-summary-line)
+ :type '(list :convert-widget custom-hook-convert-widget)
+ :group 'git-commit)
+
+(defcustom git-commit-summary-max-length 68
+ "Column beyond which characters in the summary lines are highlighted.
+
+The highlighting indicates that the summary is getting too long
+by some standards. It does in no way imply that going over the
+limit a few characters or in some cases even many characters is
+anything that deserves shaming. It's just a friendly reminder
+that if you can make the summary shorter, then you might want
+to consider doing so."
+ :group 'git-commit
+ :safe 'numberp
+ :type 'number)
+
+(defcustom git-commit-known-pseudo-headers
+ '("Signed-off-by" "Acked-by" "Modified-by" "Cc"
+ "Suggested-by" "Reported-by" "Tested-by" "Reviewed-by"
+ "Co-authored-by" "Co-developed-by")
+ "A list of Git pseudo headers to be highlighted."
+ :group 'git-commit
+ :safe (lambda (val) (and (listp val) (seq-every-p #'stringp val)))
+ :type '(repeat string))
+
+(defcustom git-commit-use-local-message-ring nil
+ "Whether to use a local message ring instead of the global one.
+This can be set globally, in which case every repository gets its
+own commit message ring, or locally for a single repository. If
+Magit isn't available, then setting this to a non-nil value has
+no effect."
+ :group 'git-commit
+ :safe 'booleanp
+ :type 'boolean)
+
+;;;; Faces
+
+(defgroup git-commit-faces nil
+ "Faces used for highlighting Git commit messages."
+ :prefix "git-commit-"
+ :group 'git-commit
+ :group 'faces)
+
+(defface git-commit-summary
+ '((t :inherit font-lock-type-face))
+ "Face used for the summary in commit messages."
+ :group 'git-commit-faces)
+
+(defface git-commit-overlong-summary
+ '((t :inherit font-lock-warning-face))
+ "Face used for the tail of overlong commit message summaries."
+ :group 'git-commit-faces)
+
+(defface git-commit-nonempty-second-line
+ '((t :inherit font-lock-warning-face))
+ "Face used for non-whitespace on the second line of commit messages."
+ :group 'git-commit-faces)
+
+(defface git-commit-keyword
+ '((t :inherit font-lock-string-face))
+ "Face used for keywords in commit messages.
+In this context a \"keyword\" is text surrounded by brackets."
+ :group 'git-commit-faces)
+
+(defface git-commit-pseudo-header
+ '((t :inherit font-lock-string-face))
+ "Face used for pseudo headers in commit messages."
+ :group 'git-commit-faces)
+
+(defface git-commit-known-pseudo-header
+ '((t :inherit font-lock-keyword-face))
+ "Face used for the keywords of known pseudo headers in commit messages."
+ :group 'git-commit-faces)
+
+(defface git-commit-comment-branch-local
+ (if (featurep 'magit)
+ '((t :inherit magit-branch-local))
+ '((t :inherit font-lock-variable-name-face)))
+ "Face used for names of local branches in commit message comments."
+ :group 'git-commit-faces)
+
+(defface git-commit-comment-branch-remote
+ (if (featurep 'magit)
+ '((t :inherit magit-branch-remote))
+ '((t :inherit font-lock-variable-name-face)))
+ "Face used for names of remote branches in commit message comments.
+This is only used if Magit is available."
+ :group 'git-commit-faces)
+
+(defface git-commit-comment-detached
+ '((t :inherit git-commit-comment-branch-local))
+ "Face used for detached `HEAD' in commit message comments."
+ :group 'git-commit-faces)
+
+(defface git-commit-comment-heading
+ '((t :inherit git-commit-known-pseudo-header))
+ "Face used for headings in commit message comments."
+ :group 'git-commit-faces)
+
+(defface git-commit-comment-file
+ '((t :inherit git-commit-pseudo-header))
+ "Face used for file names in commit message comments."
+ :group 'git-commit-faces)
+
+(defface git-commit-comment-action
+ '((t :inherit bold))
+ "Face used for actions in commit message comments."
+ :group 'git-commit-faces)
+
+;;; Keymap
+
+(defvar-keymap git-commit-mode-map
+ :doc "Key map used by `git-commit-mode'."
+ "M-p" #'git-commit-prev-message
+ "M-n" #'git-commit-next-message
+ "C-c M-p" #'git-commit-search-message-backward
+ "C-c M-n" #'git-commit-search-message-forward
+ "C-c C-i" #'git-commit-insert-pseudo-header
+ "C-c C-a" #'git-commit-ack
+ "C-c M-i" #'git-commit-suggested
+ "C-c C-m" #'git-commit-modified
+ "C-c C-o" #'git-commit-cc
+ "C-c C-p" #'git-commit-reported
+ "C-c C-r" #'git-commit-review
+ "C-c C-s" #'git-commit-signoff
+ "C-c C-t" #'git-commit-test
+ "C-c M-s" #'git-commit-save-message)
+
+;;; Menu
+
+(require 'easymenu)
+(easy-menu-define git-commit-mode-menu git-commit-mode-map
+ "Git Commit Mode Menu"
+ '("Commit"
+ ["Previous" git-commit-prev-message t]
+ ["Next" git-commit-next-message t]
+ "-"
+ ["Ack" git-commit-ack :active t
+ :help "Insert an 'Acked-by' header"]
+ ["Sign-Off" git-commit-signoff :active t
+ :help "Insert a 'Signed-off-by' header"]
+ ["Modified-by" git-commit-modified :active t
+ :help "Insert a 'Modified-by' header"]
+ ["Tested-by" git-commit-test :active t
+ :help "Insert a 'Tested-by' header"]
+ ["Reviewed-by" git-commit-review :active t
+ :help "Insert a 'Reviewed-by' header"]
+ ["CC" git-commit-cc t
+ :help "Insert a 'Cc' header"]
+ ["Reported" git-commit-reported :active t
+ :help "Insert a 'Reported-by' header"]
+ ["Suggested" git-commit-suggested t
+ :help "Insert a 'Suggested-by' header"]
+ ["Co-authored-by" git-commit-co-authored t
+ :help "Insert a 'Co-authored-by' header"]
+ ["Co-developed-by" git-commit-co-developed t
+ :help "Insert a 'Co-developed-by' header"]
+ "-"
+ ["Save" git-commit-save-message t]
+ ["Cancel" with-editor-cancel t]
+ ["Commit" with-editor-finish t]))
+
+;;; Hooks
+
+(defconst git-commit-filename-regexp "/\\(\
+\\(\\(COMMIT\\|NOTES\\|PULLREQ\\|MERGEREQ\\|TAG\\)_EDIT\\|MERGE_\\|\\)MSG\
+\\|\\(BRANCH\\|EDIT\\)_DESCRIPTION\\)\\'")
+
+(with-eval-after-load 'recentf
+ (add-to-list 'recentf-exclude git-commit-filename-regexp))
+
+(add-to-list 'with-editor-file-name-history-exclude git-commit-filename-regexp)
+
+(defun git-commit-setup-font-lock-in-buffer ()
+ (and buffer-file-name
+ (string-match-p git-commit-filename-regexp buffer-file-name)
+ (git-commit-setup-font-lock)))
+
+(add-hook 'after-change-major-mode-hook #'git-commit-setup-font-lock-in-buffer)
+
+(defun git-commit-setup-check-buffer ()
+ (and buffer-file-name
+ (string-match-p git-commit-filename-regexp buffer-file-name)
+ (git-commit-setup)))
+
+(defvar git-commit-mode)
+
+(defun git-commit-file-not-found ()
+ ;; cygwin git will pass a cygwin path (/cygdrive/c/foo/.git/...),
+ ;; try to handle this in window-nt Emacs.
+ (when-let
+ ((file (and (or (string-match-p git-commit-filename-regexp
+ buffer-file-name)
+ (and (boundp 'git-rebase-filename-regexp)
+ (string-match-p git-rebase-filename-regexp
+ buffer-file-name)))
+ (not (file-accessible-directory-p
+ (file-name-directory buffer-file-name)))
+ (if (require 'magit-git nil t)
+ ;; Emacs prepends a "c:".
+ (magit-expand-git-file-name
+ (substring buffer-file-name 2))
+ ;; Fallback if we can't load `magit-git'.
+ (and (string-match
+ "\\`[a-z]:/\\(cygdrive/\\)?\\([a-z]\\)/\\(.*\\)"
+ buffer-file-name)
+ (concat (match-string 2 buffer-file-name) ":/"
+ (match-string 3 buffer-file-name)))))))
+ (when (file-accessible-directory-p (file-name-directory file))
+ (let ((inhibit-read-only t))
+ (insert-file-contents file t)
+ t))))
+
+(when (eq system-type 'windows-nt)
+ (add-hook 'find-file-not-found-functions #'git-commit-file-not-found))
+
+(defconst git-commit-default-usage-message "\
+Type \\[with-editor-finish] to finish, \
+\\[with-editor-cancel] to cancel, and \
+\\[git-commit-prev-message] and \\[git-commit-next-message] \
+to recover older messages")
+
+(defvar git-commit-usage-message git-commit-default-usage-message
+ "Message displayed when editing a commit message.
+When this is nil, then `with-editor-usage-message' is displayed
+instead. One of these messages has to be displayed; otherwise
+the user gets to see the message displayed by `server-execute'.
+That message is misleading and because we cannot prevent it from
+being displayed, we have to immediately show another message to
+prevent the user from seeing it.")
+
+(defvar git-commit-header-line-format nil
+ "If non-nil, header line format used by `git-commit-mode'.
+Used as the local value of `header-line-format', in buffer using
+`git-commit-mode'. If it is a string, then it is passed through
+`substitute-command-keys' first. A useful setting may be:
+ (setq git-commit-header-line-format git-commit-default-usage-message)
+ (setq git-commit-usage-message nil) ; show a shorter message")
+
+(defun git-commit-setup ()
+ (when (fboundp 'magit-toplevel)
+ ;; `magit-toplevel' is autoloaded and defined in magit-git.el,
+ ;; That library declares this functions without loading
+ ;; magit-process.el, which defines it.
+ (require 'magit-process nil t))
+ ;; Pretend that git-commit-mode is a major-mode,
+ ;; so that directory-local settings can be used.
+ (let ((default-directory
+ (or (and (not (file-exists-p ".dir-locals.el"))
+ ;; When $GIT_DIR/.dir-locals.el doesn't exist,
+ ;; fallback to $GIT_WORK_TREE/.dir-locals.el,
+ ;; because the maintainer can use the latter
+ ;; to enforce conventions, while s/he has no
+ ;; control over the former.
+ (fboundp 'magit-toplevel) ; silence byte-compiler
+ (magit-toplevel))
+ default-directory)))
+ (let ((buffer-file-name nil) ; trick hack-dir-local-variables
+ (major-mode 'git-commit-mode)) ; trick dir-locals-collect-variables
+ (hack-dir-local-variables)
+ (hack-local-variables-apply)))
+ (when git-commit-major-mode
+ (let ((auto-mode-alist (list (cons (concat "\\`"
+ (regexp-quote buffer-file-name)
+ "\\'")
+ git-commit-major-mode)))
+ ;; The major-mode hook might want to consult these minor
+ ;; modes, while the minor-mode hooks might want to consider
+ ;; the major mode.
+ (git-commit-mode t)
+ (with-editor-mode t))
+ (normal-mode t)))
+ ;; Below we instead explicitly show a message.
+ (setq with-editor-show-usage nil)
+ (unless with-editor-mode
+ ;; Maybe already enabled when using `shell-command' or an Emacs shell.
+ (with-editor-mode 1))
+ (add-hook 'with-editor-finish-query-functions
+ #'git-commit-finish-query-functions nil t)
+ (add-hook 'with-editor-pre-finish-hook
+ #'git-commit-save-message nil t)
+ (add-hook 'with-editor-pre-cancel-hook
+ #'git-commit-save-message nil t)
+ (when (fboundp 'magit-commit--reset-command)
+ (add-hook 'with-editor-post-finish-hook #'magit-commit--reset-command)
+ (add-hook 'with-editor-post-cancel-hook #'magit-commit--reset-command))
+ (when (and (fboundp 'magit-rev-parse)
+ (not (memq last-command
+ '(magit-sequencer-continue
+ magit-sequencer-skip
+ magit-am-continue
+ magit-am-skip
+ magit-rebase-continue
+ magit-rebase-skip))))
+ (add-hook 'with-editor-post-finish-hook
+ (apply-partially #'git-commit-run-post-finish-hook
+ (magit-rev-parse "HEAD"))
+ nil t)
+ (when (fboundp 'magit-wip-maybe-add-commit-hook)
+ (magit-wip-maybe-add-commit-hook)))
+ (setq with-editor-cancel-message
+ #'git-commit-cancel-message)
+ (git-commit-mode 1)
+ (git-commit-setup-font-lock)
+ (git-commit-prepare-message-ring)
+ (when (boundp 'save-place)
+ (setq save-place nil))
+ (save-excursion
+ (goto-char (point-min))
+ (when (looking-at "\\`\\(\\'\\|\n[^\n]\\)")
+ (open-line 1)))
+ (with-demoted-errors "Error running git-commit-setup-hook: %S"
+ (run-hooks 'git-commit-setup-hook))
+ (when git-commit-usage-message
+ (setq with-editor-usage-message git-commit-usage-message))
+ (with-editor-usage-message)
+ (when-let ((format git-commit-header-line-format))
+ (setq header-line-format
+ (if (stringp format) (substitute-command-keys format) format)))
+ (set-buffer-modified-p nil))
+
+(defun git-commit-run-post-finish-hook (previous)
+ (when (and git-commit-post-finish-hook
+ (require 'magit nil t)
+ (fboundp 'magit-rev-parse))
+ (cl-block nil
+ (let ((break (time-add (current-time)
+ (seconds-to-time
+ git-commit-post-finish-hook-timeout))))
+ (while (equal (magit-rev-parse "HEAD") previous)
+ (if (time-less-p (current-time) break)
+ (sit-for 0.01)
+ (message "No commit created after 1 second. Not running %s."
+ 'git-commit-post-finish-hook)
+ (cl-return))))
+ (run-hooks 'git-commit-post-finish-hook))))
+
+(define-minor-mode git-commit-mode
+ "Auxiliary minor mode used when editing Git commit messages.
+This mode is only responsible for setting up some key bindings.
+Don't use it directly, instead enable `global-git-commit-mode'."
+ :lighter "")
+
+(put 'git-commit-mode 'permanent-local t)
+
+(defun git-commit-setup-changelog-support ()
+ "Treat ChangeLog entries as unindented paragraphs."
+ (when (fboundp 'log-indent-fill-entry) ; New in Emacs 27.
+ (setq-local fill-paragraph-function #'log-indent-fill-entry))
+ (setq-local fill-indent-according-to-mode t)
+ (setq-local paragraph-start (concat paragraph-start "\\|\\*\\|(")))
+
+(defun git-commit-turn-on-auto-fill ()
+ "Unconditionally turn on Auto Fill mode."
+ (setq-local comment-auto-fill-only-comments nil)
+ (turn-on-auto-fill))
+
+(defun git-commit-turn-on-orglink ()
+ "Turn on Orglink mode if it is available.
+If `git-commit-major-mode' is `org-mode', then silently forgo
+turning on `orglink-mode'."
+ (when (and (not (derived-mode-p 'org-mode))
+ (boundp 'orglink-match-anywhere)
+ (fboundp 'orglink-mode))
+ (setq-local orglink-match-anywhere t)
+ (orglink-mode 1)))
+
+(defun git-commit-turn-on-flyspell ()
+ "Unconditionally turn on Flyspell mode.
+Also prevent comments from being checked and
+finally check current non-comment text."
+ (require 'flyspell)
+ (turn-on-flyspell)
+ (setq flyspell-generic-check-word-predicate
+ #'git-commit-flyspell-verify)
+ (let ((end)
+ (comment-start-regex (format "^\\(%s\\|$\\)" comment-start)))
+ (save-excursion
+ (goto-char (point-max))
+ (while (and (not (bobp)) (looking-at comment-start-regex))
+ (forward-line -1))
+ (unless (looking-at comment-start-regex)
+ (forward-line))
+ (setq end (point)))
+ (flyspell-region (point-min) end)))
+
+(defun git-commit-flyspell-verify ()
+ (not (= (char-after (line-beginning-position))
+ (aref comment-start 0))))
+
+(defun git-commit-finish-query-functions (force)
+ (run-hook-with-args-until-failure
+ 'git-commit-finish-query-functions force))
+
+(defun git-commit-check-style-conventions (force)
+ "Check for violations of certain basic style conventions.
+
+For each violation ask the user if she wants to proceed anyway.
+Option `git-commit-style-convention-checks' controls which
+conventions are checked."
+ (or force
+ (save-excursion
+ (goto-char (point-min))
+ (re-search-forward (git-commit-summary-regexp) nil t)
+ (if (equal (match-string 1) "")
+ t ; Just try; we don't know whether --allow-empty-message was used.
+ (and (or (not (memq 'overlong-summary-line
+ git-commit-style-convention-checks))
+ (equal (match-string 2) "")
+ (y-or-n-p "Summary line is too long. Commit anyway? "))
+ (or (not (memq 'non-empty-second-line
+ git-commit-style-convention-checks))
+ (not (match-string 3))
+ (y-or-n-p "Second line is not empty. Commit anyway? ")))))))
+
+(defun git-commit-cancel-message ()
+ (message
+ (concat "Commit canceled"
+ (and (memq 'git-commit-save-message with-editor-pre-cancel-hook)
+ ". Message saved to `log-edit-comment-ring'"))))
+
+;;; History
+
+(defun git-commit-prev-message (arg)
+ "Cycle backward through message history, after saving current message.
+With a numeric prefix ARG, go back ARG comments."
+ (interactive "*p")
+ (let ((len (ring-length log-edit-comment-ring)))
+ (if (<= len 0)
+ (progn (message "Empty comment ring") (ding))
+ ;; Unlike `log-edit-previous-comment' we save the current
+ ;; non-empty and newly written comment, because otherwise
+ ;; it would be irreversibly lost.
+ (when-let ((message (git-commit-buffer-message)))
+ (unless (ring-member log-edit-comment-ring message)
+ (ring-insert log-edit-comment-ring message)
+ (cl-incf arg)
+ (setq len (ring-length log-edit-comment-ring))))
+ ;; Delete the message but not the instructions at the end.
+ (save-restriction
+ (goto-char (point-min))
+ (narrow-to-region
+ (point)
+ (if (re-search-forward (concat "^" comment-start) nil t)
+ (max 1 (- (point) 2))
+ (point-max)))
+ (delete-region (point-min) (point)))
+ (setq log-edit-comment-ring-index (log-edit-new-comment-index arg len))
+ (message "Comment %d" (1+ log-edit-comment-ring-index))
+ (insert (ring-ref log-edit-comment-ring log-edit-comment-ring-index)))))
+
+(defun git-commit-next-message (arg)
+ "Cycle forward through message history, after saving current message.
+With a numeric prefix ARG, go forward ARG comments."
+ (interactive "*p")
+ (git-commit-prev-message (- arg)))
+
+(defun git-commit-search-message-backward (string)
+ "Search backward through message history for a match for STRING.
+Save current message first."
+ (interactive
+ ;; Avoid `format-prompt' because it isn't available until Emacs 28.
+ (list (read-string (format "Comment substring (default %s): "
+ log-edit-last-comment-match)
+ nil nil log-edit-last-comment-match)))
+ (cl-letf (((symbol-function #'log-edit-previous-comment)
+ (symbol-function #'git-commit-prev-message)))
+ (log-edit-comment-search-backward string)))
+
+(defun git-commit-search-message-forward (string)
+ "Search forward through message history for a match for STRING.
+Save current message first."
+ (interactive
+ ;; Avoid `format-prompt' because it isn't available until Emacs 28.
+ (list (read-string (format "Comment substring (default %s): "
+ log-edit-last-comment-match)
+ nil nil log-edit-last-comment-match)))
+ (cl-letf (((symbol-function #'log-edit-previous-comment)
+ (symbol-function #'git-commit-prev-message)))
+ (log-edit-comment-search-forward string)))
+
+(defun git-commit-save-message ()
+ "Save current message to `log-edit-comment-ring'."
+ (interactive)
+ (if-let ((message (git-commit-buffer-message)))
+ (progn
+ (when-let ((index (ring-member log-edit-comment-ring message)))
+ (ring-remove log-edit-comment-ring index))
+ (ring-insert log-edit-comment-ring message)
+ (when (and git-commit-use-local-message-ring
+ (fboundp 'magit-repository-local-set))
+ (magit-repository-local-set 'log-edit-comment-ring
+ log-edit-comment-ring))
+ (message "Message saved"))
+ (message "Only whitespace and/or comments; message not saved")))
+
+(defun git-commit-prepare-message-ring ()
+ (make-local-variable 'log-edit-comment-ring-index)
+ (when (and git-commit-use-local-message-ring
+ (fboundp 'magit-repository-local-get))
+ (setq-local log-edit-comment-ring
+ (magit-repository-local-get
+ 'log-edit-comment-ring
+ (make-ring log-edit-maximum-comment-ring-size)))))
+
+(defun git-commit-buffer-message ()
+ (let ((flush (concat "^" comment-start))
+ (str (buffer-substring-no-properties (point-min) (point-max))))
+ (with-temp-buffer
+ (insert str)
+ (goto-char (point-min))
+ (when (re-search-forward (concat flush " -+ >8 -+$") nil t)
+ (delete-region (line-beginning-position) (point-max)))
+ (goto-char (point-min))
+ (flush-lines flush)
+ (goto-char (point-max))
+ (unless (eq (char-before) ?\n)
+ (insert ?\n))
+ (setq str (buffer-string)))
+ (unless (string-match "\\`[ \t\n\r]*\\'" str)
+ (when (string-match "\\`\n\\{2,\\}" str)
+ (setq str (replace-match "\n" t t str)))
+ (when (string-match "\n\\{2,\\}\\'" str)
+ (setq str (replace-match "\n" t t str)))
+ str)))
+
+;;; Utilities
+
+(defsubst git-commit-executable ()
+ (if (fboundp 'magit-git-executable)
+ (magit-git-executable)
+ "git"))
+
+;;; Headers
+
+(transient-define-prefix git-commit-insert-pseudo-header ()
+ "Insert a commit message pseudo header."
+ [["Insert ... by yourself"
+ ("a" "Ack" git-commit-ack)
+ ("m" "Modified" git-commit-modified)
+ ("r" "Reviewed" git-commit-review)
+ ("s" "Signed-off" git-commit-signoff)
+ ("t" "Tested" git-commit-test)]
+ ["Insert ... by someone"
+ ("C-c" "Cc" git-commit-cc)
+ ("C-r" "Reported" git-commit-reported)
+ ("C-i" "Suggested" git-commit-suggested)
+ ("C-a" "Co-authored" git-commit-co-authored)
+ ("C-d" "Co-developed" git-commit-co-developed)]])
+
+(defun git-commit-ack (name mail)
+ "Insert a header acknowledging that you have looked at the commit."
+ (interactive (git-commit-self-ident))
+ (git-commit-insert-header "Acked-by" name mail))
+
+(defun git-commit-modified (name mail)
+ "Insert a header to signal that you have modified the commit."
+ (interactive (git-commit-self-ident))
+ (git-commit-insert-header "Modified-by" name mail))
+
+(defun git-commit-review (name mail)
+ "Insert a header acknowledging that you have reviewed the commit."
+ (interactive (git-commit-self-ident))
+ (git-commit-insert-header "Reviewed-by" name mail))
+
+(defun git-commit-signoff (name mail)
+ "Insert a header to sign off the commit."
+ (interactive (git-commit-self-ident))
+ (git-commit-insert-header "Signed-off-by" name mail))
+
+(defun git-commit-test (name mail)
+ "Insert a header acknowledging that you have tested the commit."
+ (interactive (git-commit-self-ident))
+ (git-commit-insert-header "Tested-by" name mail))
+
+(defun git-commit-cc (name mail)
+ "Insert a header mentioning someone who might be interested."
+ (interactive (git-commit-read-ident "Cc"))
+ (git-commit-insert-header "Cc" name mail))
+
+(defun git-commit-reported (name mail)
+ "Insert a header mentioning the person who reported the issue."
+ (interactive (git-commit-read-ident "Reported-by"))
+ (git-commit-insert-header "Reported-by" name mail))
+
+(defun git-commit-suggested (name mail)
+ "Insert a header mentioning the person who suggested the change."
+ (interactive (git-commit-read-ident "Suggested-by"))
+ (git-commit-insert-header "Suggested-by" name mail))
+
+(defun git-commit-co-authored (name mail)
+ "Insert a header mentioning the person who co-authored the commit."
+ (interactive (git-commit-read-ident "Co-authored-by"))
+ (git-commit-insert-header "Co-authored-by" name mail))
+
+(defun git-commit-co-developed (name mail)
+ "Insert a header mentioning the person who co-developed the commit."
+ (interactive (git-commit-read-ident "Co-developed-by"))
+ (git-commit-insert-header "Co-developed-by" name mail))
+
+(defun git-commit-self-ident ()
+ (list (or (getenv "GIT_AUTHOR_NAME")
+ (getenv "GIT_COMMITTER_NAME")
+ (with-demoted-errors "Error running 'git config user.name': %S"
+ (car (process-lines
+ (git-commit-executable) "config" "user.name")))
+ user-full-name
+ (read-string "Name: "))
+ (or (getenv "GIT_AUTHOR_EMAIL")
+ (getenv "GIT_COMMITTER_EMAIL")
+ (getenv "EMAIL")
+ (with-demoted-errors "Error running 'git config user.email': %S"
+ (car (process-lines
+ (git-commit-executable) "config" "user.email")))
+ (read-string "Email: "))))
+
+(defvar git-commit-read-ident-history nil)
+
+(defun git-commit-read-ident (prompt)
+ (if (require 'magit-git nil t)
+ (let ((str (magit-completing-read
+ prompt
+ (sort (delete-dups
+ (magit-git-lines "log" "-n9999" "--format=%aN <%ae>"))
+ #'string<)
+ nil nil nil 'git-commit-read-ident-history)))
+ (save-match-data
+ (if (string-match "\\`\\([^<]+\\) *<\\([^>]+\\)>\\'" str)
+ (list (save-match-data (string-trim (match-string 1 str)))
+ (string-trim (match-string 2 str)))
+ (user-error "Invalid input"))))
+ (list (read-string "Name: ")
+ (read-string "Email: "))))
+
+(defun git-commit-insert-header (header name email)
+ (setq header (format "%s: %s <%s>" header name email))
+ (save-excursion
+ (goto-char (point-max))
+ (cond ((re-search-backward "^[-a-zA-Z]+: [^<]+? <[^>]+>" nil t)
+ (end-of-line)
+ (insert ?\n header)
+ (unless (= (char-after) ?\n)
+ (insert ?\n)))
+ (t
+ (while (re-search-backward (concat "^" comment-start) nil t))
+ (unless (looking-back "\n\n" nil)
+ (insert ?\n))
+ (insert header ?\n)))
+ (unless (or (eobp) (= (char-after) ?\n))
+ (insert ?\n))))
+
+;;; Font-Lock
+
+(defvar-local git-commit-need-summary-line t
+ "Whether the text should have a heading that is separated from the body.
+
+For commit messages that is a convention that should not
+be violated. For notes it is up to the user. If you do
+not want to insist on an empty second line here, then use
+something like:
+
+ (add-hook \\='git-commit-setup-hook
+ (lambda ()
+ (when (equal (file-name-nondirectory (buffer-file-name))
+ \"NOTES_EDITMSG\")
+ (setq git-commit-need-summary-line nil))))")
+
+(defun git-commit-summary-regexp ()
+ (if git-commit-need-summary-line
+ (concat
+ ;; Leading empty lines and comments
+ (format "\\`\\(?:^\\(?:\\s-*\\|%s.*\\)\n\\)*" comment-start)
+ ;; Summary line
+ (format "\\(.\\{0,%d\\}\\)\\(.*\\)" git-commit-summary-max-length)
+ ;; Non-empty non-comment second line
+ (format "\\(?:\n%s\\|\n\\(.+\\)\\)?" comment-start))
+ "\\(EASTER\\) \\(EGG\\)"))
+
+(defun git-commit-extend-region-summary-line ()
+ "Identify the multiline summary-regexp construct.
+Added to `font-lock-extend-region-functions'."
+ (save-excursion
+ (save-match-data
+ (goto-char (point-min))
+ (when (looking-at (git-commit-summary-regexp))
+ (let ((summary-beg (match-beginning 0))
+ (summary-end (match-end 0)))
+ (when (or (< summary-beg font-lock-beg summary-end)
+ (< summary-beg font-lock-end summary-end))
+ (setq font-lock-beg (min font-lock-beg summary-beg))
+ (setq font-lock-end (max font-lock-end summary-end))))))))
+
+(defvar-local git-commit--branch-name-regexp nil)
+
+(defconst git-commit-comment-headings
+ '("Changes to be committed:"
+ "Untracked files:"
+ "Changed but not updated:"
+ "Changes not staged for commit:"
+ "Unmerged paths:"
+ "Author:"
+ "Date:")
+ "Also fontified outside of comments in `git-commit-font-lock-keywords-2'.")
+
+(defconst git-commit-font-lock-keywords-1
+ '(;; Pseudo headers
+ (eval . `(,(format "^\\(%s:\\)\\( .*\\)"
+ (regexp-opt git-commit-known-pseudo-headers))
+ (1 'git-commit-known-pseudo-header)
+ (2 'git-commit-pseudo-header)))
+ ;; Summary
+ (eval . `(,(git-commit-summary-regexp)
+ (1 'git-commit-summary)))
+ ;; - Keyword [aka "text in brackets"] (overrides summary)
+ ("\\[[^][]+?\\]"
+ (0 'git-commit-keyword t))
+ ;; - Non-empty second line (overrides summary and note)
+ (eval . `(,(git-commit-summary-regexp)
+ (2 'git-commit-overlong-summary t t)
+ (3 'git-commit-nonempty-second-line t t)))))
+
+(defconst git-commit-font-lock-keywords-2
+ `(,@git-commit-font-lock-keywords-1
+ ;; Comments
+ (eval . `(,(format "^%s.*" comment-start)
+ (0 'font-lock-comment-face append)))
+ (eval . `(,(format "^%s On branch \\(.*\\)" comment-start)
+ (1 'git-commit-comment-branch-local t)))
+ (eval . `(,(format "^%s \\(HEAD\\) detached at" comment-start)
+ (1 'git-commit-comment-detached t)))
+ (eval . `(,(format "^%s %s" comment-start
+ (regexp-opt git-commit-comment-headings t))
+ (1 'git-commit-comment-heading t)))
+ (eval . `(,(format "^%s\t\\(?:\\([^:\n]+\\):\\s-+\\)?\\(.*\\)" comment-start)
+ (1 'git-commit-comment-action t t)
+ (2 'git-commit-comment-file t)))
+ ;; "commit HASH"
+ (eval . `(,(rx bol "commit " (1+ alnum) eol)
+ (0 'git-commit-pseudo-header)))
+ ;; `git-commit-comment-headings' (but not in commented lines)
+ (eval . `(,(rx-to-string `(seq bol (or ,@git-commit-comment-headings) (1+ blank) (1+ nonl) eol))
+ (0 'git-commit-pseudo-header)))))
+
+(defconst git-commit-font-lock-keywords-3
+ `(,@git-commit-font-lock-keywords-2
+ ;; More comments
+ (eval
+ ;; Your branch is ahead of 'master' by 3 commits.
+ ;; Your branch is behind 'master' by 2 commits, and can be fast-forwarded.
+ . `(,(format
+ "^%s Your branch is \\(?:ahead\\|behind\\) of '%s' by \\([0-9]*\\)"
+ comment-start git-commit--branch-name-regexp)
+ (1 'git-commit-comment-branch-local t)
+ (2 'git-commit-comment-branch-remote t)
+ (3 'bold t)))
+ (eval
+ ;; Your branch is up to date with 'master'.
+ ;; Your branch and 'master' have diverged,
+ . `(,(format
+ "^%s Your branch \\(?:is up[- ]to[- ]date with\\|and\\) '%s'"
+ comment-start git-commit--branch-name-regexp)
+ (1 'git-commit-comment-branch-local t)
+ (2 'git-commit-comment-branch-remote t)))
+ (eval
+ ;; and have 1 and 2 different commits each, respectively.
+ . `(,(format
+ "^%s and have \\([0-9]*\\) and \\([0-9]*\\) commits each"
+ comment-start)
+ (1 'bold t)
+ (2 'bold t)))))
+
+(defvar git-commit-font-lock-keywords git-commit-font-lock-keywords-3
+ "Font-Lock keywords for Git-Commit mode.")
+
+(defun git-commit-setup-font-lock ()
+ (with-demoted-errors "Error running git-commit-setup-font-lock: %S"
+ (let ((table (make-syntax-table (syntax-table))))
+ (when comment-start
+ (modify-syntax-entry (string-to-char comment-start) "." table))
+ (modify-syntax-entry ?# "." table)
+ (modify-syntax-entry ?\" "." table)
+ (modify-syntax-entry ?\' "." table)
+ (modify-syntax-entry ?` "." table)
+ (set-syntax-table table))
+ (setq-local comment-start
+ (or (with-temp-buffer
+ (and (zerop
+ (call-process
+ (git-commit-executable) nil (list t nil) nil
+ "config" "core.commentchar"))
+ (not (bobp))
+ (progn
+ (goto-char (point-min))
+ (buffer-substring (point) (line-end-position)))))
+ "#"))
+ (setq-local comment-start-skip (format "^%s+[\s\t]*" comment-start))
+ (setq-local comment-end-skip "\n")
+ (setq-local comment-use-syntax nil)
+ (setq-local git-commit--branch-name-regexp
+ (if (and (featurep 'magit-git)
+ ;; When using cygwin git, we may end up in a
+ ;; non-existing directory, which would cause
+ ;; any git calls to signal an error.
+ (file-accessible-directory-p default-directory))
+ (progn
+ ;; Make sure the below functions are available.
+ (require 'magit)
+ ;; Font-Lock wants every submatch to succeed, so
+ ;; also match the empty string. Avoid listing
+ ;; remote branches and using `regexp-quote',
+ ;; because in repositories have thousands of
+ ;; branches that would be very slow. See #4353.
+ (format "\\(\\(?:%s\\)\\|\\)\\([^']+\\)"
+ (mapconcat #'identity
+ (magit-list-local-branch-names)
+ "\\|")))
+ "\\([^']*\\)"))
+ (setq-local font-lock-multiline t)
+ (add-hook 'font-lock-extend-region-functions
+ #'git-commit-extend-region-summary-line
+ t t)
+ (font-lock-add-keywords nil git-commit-font-lock-keywords)))
+
+(defun git-commit-propertize-diff ()
+ (require 'diff-mode)
+ (save-excursion
+ (goto-char (point-min))
+ (when (re-search-forward "^diff --git" nil t)
+ (beginning-of-line)
+ (let ((buffer (current-buffer)))
+ (insert
+ (with-temp-buffer
+ (insert
+ (with-current-buffer buffer
+ (prog1 (buffer-substring-no-properties (point) (point-max))
+ (delete-region (point) (point-max)))))
+ (let ((diff-default-read-only nil))
+ (diff-mode))
+ (let (font-lock-verbose font-lock-support-mode)
+ (if (fboundp 'font-lock-ensure)
+ (font-lock-ensure)
+ (with-no-warnings
+ (font-lock-fontify-buffer))))
+ (let (next (pos (point-min)))
+ (while (setq next (next-single-property-change pos 'face))
+ (put-text-property pos next 'font-lock-face
+ (get-text-property pos 'face))
+ (setq pos next))
+ (put-text-property pos (point-max) 'font-lock-face
+ (get-text-property pos 'face)))
+ (buffer-string)))))))
+
+;;; Elisp Text Mode
+
+(define-derived-mode git-commit-elisp-text-mode text-mode "ElText"
+ "Major mode for editing commit messages of elisp projects.
+This is intended for use as `git-commit-major-mode' for projects
+that expect `symbols' to look like this. I.e., like they look in
+Elisp doc-strings, including this one. Unlike in doc-strings,
+\"strings\" also look different than the other text."
+ (setq font-lock-defaults '(git-commit-elisp-text-mode-keywords)))
+
+(defvar git-commit-elisp-text-mode-keywords
+ `((,(concat "[`‘]\\(" lisp-mode-symbol-regexp "\\)['’]")
+ (1 font-lock-constant-face prepend))
+ ("\"[^\"]*\"" (0 font-lock-string-face prepend))))
+
+;;; _
+(provide 'git-commit)
+;;; git-commit.el ends here
+++ /dev/null
-;;; git-link-autoloads.el --- automatically extracted autoloads -*- lexical-binding: t -*-
-;;
-;;; Code:
-
-(add-to-list 'load-path (directory-file-name
- (or (file-name-directory #$) (car load-path))))
-
-\f
-;;;### (autoloads nil "git-link" "git-link.el" (0 0 0 0))
-;;; Generated autoloads from git-link.el
-
-(autoload 'git-link "git-link" "\
-Create a URL representing the current buffer's location in its
-GitHub/Bitbucket/GitLab/... repository at the current line number
-or active region. The URL will be added to the kill ring. If
-`git-link-open-in-browser' is non-nil also call `browse-url'.
-
-With a prefix argument of - generate a link without line number(s).
-Also see `git-link-use-single-line-number'.
-
-With a single prefix argument prompt for the remote's name.
-Defaults to \"origin\".
-
-With a double prefix argument invert the value of
-`git-link-use-commit'.
-
-\(fn REMOTE START END)" t nil)
-
-(autoload 'git-link-commit "git-link" "\
-Create a URL representing the commit for the hash under point
-in the current buffer's GitHub/Bitbucket/GitLab/...
-repository. The URL will be added to the kill ring.
-
-With a prefix argument prompt for the remote's name.
-Defaults to \"origin\".
-
-\(fn REMOTE)" t nil)
-
-(autoload 'git-link-homepage "git-link" "\
-Create a URL representing the homepage of the current
-buffer's GitHub/Bitbucket/GitLab/... repository. The
-URL will be added to the kill ring.
-
-With a prefix argument prompt for the remote's name.
-Defaults to \"origin\".
-
-\(fn REMOTE)" t nil)
-
-(register-definition-prefixes "git-link" '("git-link-"))
-
-;;;***
-\f
-;; Local Variables:
-;; version-control: never
-;; no-byte-compile: t
-;; no-update-autoloads: t
-;; coding: utf-8
-;; End:
-;;; git-link-autoloads.el ends here
+++ /dev/null
-;;; Generated package description from git-link.el -*- no-byte-compile: t -*-
-(define-package "git-link" "20230420.48" "Get the GitHub/Bitbucket/GitLab URL for a buffer location" '((emacs "24.3")) :commit "1727e9b7ec41a234a2cc14151b9ffd9c1c4f4b14" :authors '(("Skye Shaw" . "skye.shawNOSPAMgmail.com")) :maintainers '(("Skye Shaw" . "skye.shawNOSPAMgmail.com")) :maintainer '("Skye Shaw" . "skye.shawNOSPAMgmail.com") :keywords '("git" "vc" "github" "bitbucket" "gitlab" "sourcehut" "aws" "azure" "convenience") :url "http://github.com/sshaw/git-link")
+++ /dev/null
-;;; git-link.el --- Get the GitHub/Bitbucket/GitLab URL for a buffer location -*- lexical-binding: t -*-
-
-;; Copyright (C) 2013-2022 Skye Shaw and others
-;; Author: Skye Shaw <skye.shawNOSPAMgmail.com>
-;; Version: 0.9.0
-;; Package-Version: 20230420.48
-;; Package-Commit: 1727e9b7ec41a234a2cc14151b9ffd9c1c4f4b14
-;; Keywords: git, vc, github, bitbucket, gitlab, sourcehut, aws, azure, convenience
-;; URL: http://github.com/sshaw/git-link
-;; Package-Requires: ((emacs "24.3"))
-
-;; This file is NOT part of GNU Emacs.
-
-;;; License:
-
-;; This program is free software: you can redistribute it and/or modify
-;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation, either version 3 of the License, or
-;; (at your option) any later version.
-
-;; This program is distributed in the hope that it will be useful,
-;; but WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-;; GNU General Public License for more details.
-
-;; You should have received a copy of the GNU General Public License
-;; along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-;;; Commentary:
-
-;; Create URLs for files and commits in GitHub/Bitbucket/GitLab/...
-;; repositories. `git-link' returns the URL for the current buffer's file
-;; location at the current line number or active region. `git-link-commit'
-;; returns the URL for a commit. URLs are added to the kill ring.
-;;
-;; With a prefix argument prompt for the remote's name. Defaults to "origin".
-
-;;; Change Log:
-
-;; 2023-02-15 - v0.9.0
-;; * Add support for GoogleSource (thanks Peter Becich)
-;; * Add plain=1 to force showing a non rendered GitHub links (thanks Erick Navarro)
-;; * Add prefix arg to override git-link-use-commit when calling git-link (thanks Nacho Barrientos)
-;; * Fix git-link-homepage for Bitbucket (Issue #97 thanks Sibi Prabakaran)
-;; * Fix the Codeberg links to line ranges (Issue #109 thanks Wojciech Siewierski)
-;;
-;; 2022-02-17 - v0.8.6
-;; * Fix URL escaping of pathnames on Emacs < 27 (Issue #93 thanks David Ongaro)
-;;
-;; 2022-02-06 - v0.8.5
-;; * URL escape pathnames (thanks David Ongaro)
-;; * Mark misspelled Savannah functions as obsolete (thanks Hendursaga)
-;; * Add support for AWS CodeCommit (thanks Ram Krishnan)
-;; * Add support for cgit (thanks Hendursaga)
-;;
-;; 2020-01-20 - v0.8.4
-;; * Add support for Codeberg (thanks Jeremy Friesen)
-;; * Add git-link-homepage-remote-alist (thanks Wim Van Deun)
-;;
-;; 2020-01-20 - v0.8.3
-;; * Add support for Savannah
-;;
-;; 2020-12-14 - v0.8.2
-;; * Fix sourcehut URL, don't link to raw version (Issue #77)
-;; * Fix sourcehut multi-line URLs
-;;
-;; 2020-11-23 - v0.8.1
-;; * Fix URL casing (Issue #57)
-;; * Fix byte-compile warnings (Issue #75 thanks Brian Leung)
-;;
-;; 2020-07-21 - v0.8.0
-;; * Add `-' prefix argument to git-link to generate links without line numbers
-;; * Add git-link-use-single-line-number
-;; * Fix sourcehut's git-link handler
-;;
-;; 2020-03-31 - v0.7.6
-;; * Adapt to changes in Azure interface (Issue #65, thanks Roey Darwish Dror)
-;;
-;; 2019-08-28 - v0.7.5
-;; * Add support for Azure DevOps (Issue #62, thanks Roey Darwish Dror)
-;;
-;; 2019-08-16 - v0.7.4
-;; * Add support for Magit-Blob buffers (Issue #61, thanks Miciah Dashiel Butler Masters)
-;;
-;; 2019-03-09 - v0.7.3
-;; * Add support for sourcehut
-;;
-;; 2018-10-30 - v0.7.2
-;; * Fix suffix stripping on remote path only if it ends in .git (Issue #58, thanks Marko Crnic)
-;;
-;; 2018-07-08 - v0.7.1
-;; * Add support for vc-revision-other-window files (Issue #54)
-;;
-;; 2018-06-07 - v0.7.0
-;; * Add support for Tramp (Issue #49, thanks Jürgen Hötzel)
-;; * Fix various compiler warnings
-;; * Fix differences between url-path-and-query across Emacs versions
-;; * Require Emacs 24.3
-;;
-;; 2018-04-23 - v0.6.0
-;; * Fix parsing of remotes with auth info (Issue #51)
-;; * Removed remote regex in favor of url-parse
-;;
-;; 2017-06-03 - v0.5.1
-;; * Add support for more magit modes
-;;
-;; 2017-06-01 - v0.5.0
-;; * Add support for linking in dired and magit modes
-;; * Add support for defcustom
-;; * Change git-link-remote-regex to support more remote URL formats (Thanks Kaushal Modi)
-;; * Change git-link-remote-alist to use regex matching (Thanks Kaushal Modi)
-;; * Fix point on commit hash regex and support uppercase SHAs (Thanks Kaushal Modi!)
-;; * Fix git-link-commit message so that SHA text is displayed without properties
-;; * Enabled lexical-binding (Thanks Kaushal Modi!!)
-;;
-;; -- Note that v0.5.0 was released as "v0.5.0 (unreleased)"
-;;
-;; 2016-10-19 - v0.4.5
-;; * Fix for branches containing reserved URLs characters (Issue #36)
-;;
-;; 2016-09-11 - v0.4.4
-;; * Added support for git-link-homepage
-;;
-;; 2016-08-13 - v0.4.3
-;; * Added support for git-timemachine (Issue #22, thanks Diego Berrocal)
-;;
-;; 2016-08-09 - v0.4.2
-;; * Fix for URLs with ports (Issue #32)
-;;
-;; 2016-04-01 - v0.4.1
-;; * Better handling for branches that have no explicit remote
-;; * Better error messages
-;;
-;; 2016-02-16 - v0.4.0
-;; * Try branch's tracking remote when other branch settings are not specified
-;; * git-link-default-remote now defaults to nil
-;;
-;; 2015-09-21 - v0.3.0
-;; * Support for setting branch and remote names via `git config`
-;; * Added git-link-default-branch
-;; * Removed some functions, use emacs "private" convention for others
-;;
-;; 2015-09-12 - v0.2.2
-;; * Support for BitBucket's multiline format
-;;
-;; 2015-07-25 - v0.2.1
-;; * Fix for BitBucket's new URL format (Thanks Ev Dolzhenko)
-;; * Fix for GitLab's multiline format (Thanks Enrico Carlesso)
-;;
-;; 2015-06-05 - v0.2.0
-;; * Deactivate mark after killing the link (Thanks Kaushal Modi)
-;; * Support for GitLab (Thanks Swaroop C H)
-;; * Use completing-read when prompting for remotes (Thanks Andrew Gwozdziewycz)
-;; * Display URL in minibuffer when adding to kill ring (Thanks Andrew Gwozdziewycz)
-;; * Added git-link-use-commit variable (Thanks Kaushal Modi)
-;; * Fix for displaying link in minibuffer when interprogram-cut-function is set (Thanks Ric Lister)
-;; * Fix to ignore point at beginning of line in regions (Thanks Kaushal Modi)
-;; * Fix for narrow-to-region (Bug #10, thanks Andrew Gwozdziewycz)
-;; * Fix to use remote hostname when constructing link URLs (Thanks David Hull)
-;;
-;; 2015-02-05 - v0.1.0
-;; * Added git-link-commit (Thanks Ryan Barrett)
-;; * Added git-link-open-in-browser variable (Thanks Ryan Barrett)
-;; * Use call-process instead of shell-command-to-string
-;; * Use --short option when calling symbolic-ref (Thanks Steven Huwig)
-;;
-;; 2014-02-27 - v0.0.2
-;; * Fix for buffers visiting files through symlinks (Issue #1, thanks Evgeniy Dolzhenko)
-
-;;; Code:
-
-(require 'cl-lib)
-(require 'dired)
-(require 'thingatpt)
-(require 'url-util)
-(require 'url-parse)
-
-(defgroup git-link nil
- "Get the GitHub/Bitbucket/GitLab URL for a buffer location"
- :prefix "git-link-"
- :link '(url-link :tag "Report a Bug" "https://github.com/sshaw/git-link/issues")
- :link '(url-link :tag "Homepage" "https://github.com/sshaw/git-link")
- :group 'convenience)
-
-(eval-when-compile
- (defvar git-timemachine-revision)) ;; silence reference to free variable warning
-
-(defcustom git-link-default-remote nil
- "Name of the remote to link to."
- :type 'string
- :group 'git-link)
-
-(defcustom git-link-default-branch nil
- "Name of the branch to link to."
- :type 'string
- :group 'git-link)
-
-(defcustom git-link-open-in-browser nil
- "If t also open the link via `browse-url'. To use an alternate
-function set to that function's symbol."
- :type '(choice boolean function)
- :group 'git-link)
-
-(defcustom git-link-use-commit nil
- "If non-nil use the latest commit's hash in the link instead of the branch name."
- :type 'boolean
- :group 'git-link)
-
-(defcustom git-link-use-single-line-number t
- "If t a link to a single line will always contain the line number.
-If nil line numbers will only be added when a selection contains
-more than 1 line.
-
-Note that `git-link' can exclude line numbers on a per invocation basis.
-See its docs."
- :type 'boolean
- :group 'git-link)
-
-(defcustom git-link-remote-alist
- '(("git.sr.ht" git-link-sourcehut)
- ("codeberg.org" git-link-codeberg)
- ("github" git-link-github)
- ("bitbucket" git-link-bitbucket)
- ("gitorious" git-link-gitorious)
- ("gitlab" git-link-gitlab)
- ("git\\.\\(sv\\|savannah\\)\\.gnu\\.org" git-link-savannah)
- ("googlesource.com" git-link-googlesource)
- ("visualstudio\\|azure" git-link-azure)
- ("sourcegraph" git-link-sourcegraph)
- ("\\(amazonaws\\|amazon\\)\\.com" git-link-codecommit))
- "Alist of host names and functions creating file links for those.
-Each element looks like (REGEXP FUNCTION) where REGEXP is used to
-match the remote's host name and FUNCTION is used to generate a link
-to the file on remote host.
-
-As an example, \"gitlab\" will match with both \"gitlab.com\" and
-\"gitlab.example.com\"."
- :type '(alist :key-type string :value-type (group function))
- :group 'git-link)
-
-(defcustom git-link-commit-remote-alist
- '(("git.sr.ht" git-link-commit-github)
- ("codeberg.org" git-link-commit-codeberg)
- ("github" git-link-commit-github)
- ("bitbucket" git-link-commit-bitbucket)
- ("gitorious" git-link-commit-gitorious)
- ("gitlab" git-link-commit-github)
- ("git\\.\\(sv\\|savannah\\)\\.gnu\\.org" git-link-commit-savannah)
- ("googlesource.com" git-link-commit-googlesource)
- ("visualstudio\\|azure" git-link-commit-azure)
- ("sourcegraph" git-link-commit-sourcegraph)
- ("\\(amazonaws\\|amazon\\)\\.com" git-link-commit-codecommit))
- "Alist of host names and functions creating commit links for those.
-Each element looks like (REGEXP FUNCTION) where REGEXP is used to
-match the remote's host name and FUNCTION is used to generate a link
-to the commit on remote host.
-
-As an example, \"gitlab\" will match with both \"gitlab.com\" and
-\"gitlab.example.com\"."
- :type '(alist :key-type string :value-type (group function))
- :group 'git-link)
-
-(defcustom git-link-homepage-remote-alist
- '(("git.sr.ht" git-link-homepage-github)
- ("github" git-link-homepage-github)
- ("bitbucket" git-link-homepage-github)
- ("gitorious" git-link-homepage-github)
- ("gitlab" git-link-homepage-github)
- ("git\\.\\(sv\\|savannah\\)\\.gnu\\.org" git-link-homepage-savannah)
- ("googlesource.com" git-link-homepage-github)
- ("visualstudio\\|azure" git-link-homepage-github)
- ("sourcegraph" git-link-homepage-github)
- ("\\(amazonaws\\|amazon\\)\\.com" git-link-homepage-codecommit))
- "Alist of host names and functions creating homepage links for those.
-Each element looks like (REGEXP FUNCTION) where REGEXP is used to
-match the remote's host name and FUNCTION is used to generate a link
-to the commit on remote host.
-
-As an example, \"gitlab\" will match with both \"gitlab.com\" and
-\"gitlab.example.com\"."
- :type '(alist :key-type string :value-type (group function))
- :group 'git-link)
-
-(defcustom git-link-extensions-rendered-plain '("org" "md" "rst" "adoc" "markdown" "asciidoc")
- "List of extensions that should be rendered in plain mode, systems like
-Github, Gitlab, etc show a rendered version by default, for these extensions
-we can prevent that behaviour."
- :type 'list
- :group 'git-link)
-
-(defun git-link--exec(&rest args)
- (ignore-errors
- (with-temp-buffer
- (when (zerop (apply #'process-file "git" nil (current-buffer) nil args))
- (goto-char (point-min))
- (cl-loop until (eobp)
- collect (buffer-substring-no-properties
- (line-beginning-position)
- (line-end-position))
- do (forward-line 1))))))
-
-(defun git-link--get-config (name)
- (car (git-link--exec "config" "--get" name)))
-
-(defun git-link--remotes ()
- (git-link--exec "remote"))
-
-(defun git-link--last-commit ()
- (car (git-link--exec "--no-pager" "log" "-n1" "--pretty=format:%H")))
-
-(defvar magit-buffer-revision)
-
-(defun git-link--commit ()
- (cond
- ((git-link--using-git-timemachine)
- (car git-timemachine-revision))
- ((git-link--using-magit-blob-mode)
- magit-buffer-revision)
- (t (git-link--last-commit))))
-
-(defun git-link--current-branch ()
- (car (git-link--exec "symbolic-ref" "--short" "HEAD")))
-
-(defun git-link--repo-root ()
- (let ((dir (car (git-link--exec "rev-parse" "--show-toplevel"))))
- (if (file-remote-p default-directory)
- (concat (file-remote-p default-directory) dir)
- dir)))
-
-(defun git-link--remote-url (name)
- (car (git-link--exec "remote" "get-url" name)))
-
-(defun git-link--branch-remote (branch)
- (git-link--get-config (format "branch.%s.remote" branch)))
-
-(declare-function magit-rev-branch "ext:magit-git")
-
-(defun git-link--branch ()
- (or (git-link--get-config "git-link.branch")
- git-link-default-branch
- (when (git-link--using-magit-blob-mode)
- (magit-rev-branch magit-buffer-revision))
- (git-link--current-branch)))
-
-(defun git-link--remote ()
- (let* ((branch (git-link--current-branch))
- (remote (or (git-link--get-config "git-link.remote")
- git-link-default-remote
- (git-link--branch-remote branch))))
-
- ;; Git defaults to "." if the branch has no remote.
- ;; If the branch has no remote we try master's, which may be set.
- (if (or (null remote)
- (and (string= remote ".")
- (not (string= branch "master"))))
- (setq remote (git-link--branch-remote "master")))
-
- (if (or (null remote) (string= remote "."))
- "origin"
- remote)))
-
-(defun git-link--handler (alist str)
- "For an ALIST whose `car' (a regexp) matches STR, return cadr.
-
-The ALIST consists of (REGEXP FN) list elements.
-Valid ALISTs are `git-link-remote-alist',`git-link-commit-remote-alist'.
-
-For the first ALIST element whose REGEXP matches with STR, FN is
-returned.
-
-Return nil,
-- if STR does not match with REGEXP in any of the elements of ALIST, or
-- if STR is not a string"
- (when (stringp str)
- (cadr (cl-find-if (lambda (lst)
- (string-match-p (car lst) str))
- alist))))
-
-(defun git-link--parse-vc-revision (filename)
-"If FILENAME appears to be from `vc-revision-other-window'
-return (FILENAME . REVISION) otherwise nil."
- (when (and (string-match "\\(.+\\)\\.~\\([^~]+\\)~$" filename)
- (file-exists-p (match-string 1 filename)))
- (cons (match-string 1 filename)
- (match-string 2 filename))))
-
-(defvar magit-buffer-file-name)
-
-(defun git-link--relative-filename ()
- (let* ((filename (buffer-file-name (buffer-base-buffer)))
- (dir (git-link--repo-root)))
-
- (when (null filename)
- (cond
- ((eq major-mode 'dired-mode)
- (setq filename (dired-file-name-at-point)))
- ((git-link--using-magit-blob-mode)
- (setq filename magit-buffer-file-name))
- ((and (string-match-p "^magit-" (symbol-name major-mode))
- (fboundp 'magit-file-at-point))
- (setq filename (magit-file-at-point)))))
-
- (if (and dir filename
- ;; Make sure filename is not above dir, e.g. "/foo/repo-root/.."
- (< (length dir) (length (file-truename filename))))
- (substring (file-truename filename)
- (1+ (length dir))))))
-
-(defun git-link--parse-remote (url)
- "Parse URL and return a list as (HOST DIR). DIR has no leading slash or `git' extension."
- (let (host path parsed)
- (unless (string-match "^[a-zA-Z0-9]+://" url)
- (setq url (concat "ssh://" url)))
-
- (setq parsed (url-generic-parse-url url)
- ;; Normalize path.
- ;; If none, will be nil on Emacs < 25. Later versions return "".
- path (or (car (url-path-and-query parsed)) "")
- host (url-host parsed))
-
- (when host
- (when (and (not (string= "/" path))
- (not (string= "" path)))
- (setq path (substring
- (if (string-match "\\.git\\'" path)
- (file-name-sans-extension path)
- path)
- 1)))
-
- ;; Fix-up scp style URLs.
- ;; git@foo:UsEr/repo gives a host of foo:user
- ;; We also need to preserve case so we take UsEr from the original url
- (when (string-match ":" host)
- (let ((parts (split-string host ":" t))
- (case-fold-search t))
- (string-match (concat (car parts) ":\\(" (cadr parts) "\\)/") url)
- (setq host (car parts)
- path (concat (match-string 1 url) "/" path))))
-
- ;; Fix-up Azure SSH URLs
- (when (string= "ssh.dev.azure.com" host)
- (setq host "dev.azure.com")
- (setq path (replace-regexp-in-string
- "v3/\\([^/]+\\)/\\([^/]+\\)/\\([^/]+\\)"
- "\\1/\\2/_git/\\3"
- path)))
- (when (string= "vs-ssh.visualstudio.com" host)
- (setq host (concat (url-user parsed) ".visualstudio.com"))
- (setq path (replace-regexp-in-string
- (concat "^v3/" (url-user parsed) "/\\([^/]+\\)/")
- "\\1/_git/"
- path)))
-
- ;; For Savannah
- (when (string= "git.savannah.gnu.org" host)
- (cond
- ((string-match "\\`git/" path)
- (setq path (substring path 4)))
- ((string-match "\\`srv/git/" path)
- (setq path (substring path 8)))))
-
- ;; For AWS CodeCommit
- (when (string-match "git-codecommit\\.\\(.*\\)\\.amazonaws.com" host)
- (let* ((matchp (string-match "\\([^\\.]*\\)\\.\\([^\\.]*\\)" host))
- (region (when matchp
- (match-string 2 host)))
- (domainname ".console.aws.amazon.com"))
- (when region
- (setq host (concat region domainname))))
- (when (string-match "v1/repos/" path)
- (setq path (concat "codesuite/codecommit/repositories/"
- (substring path 9)))))
-
- (list host path))))
-
-(defun git-link--using-git-timemachine ()
- (and (boundp 'git-timemachine-revision)
- git-timemachine-revision))
-
-(defun git-link--using-magit-blob-mode ()
- (bound-and-true-p magit-blob-mode))
-
-(defun git-link--read-remote ()
- (let ((remotes (git-link--remotes))
- (current (git-link--remote)))
- (completing-read "Remote: "
- remotes
- nil
- t
- ""
- nil
- (if (member current remotes)
- current
- (car remotes)))))
-
-(defun git-link--get-region ()
- (save-restriction
- (widen)
- (save-excursion
- (let* ((use-region (use-region-p))
- (start (when use-region (region-beginning)))
- (end (when use-region (region-end)))
- (line-start (line-number-at-pos start))
- line-end)
- (when use-region
- ;; Avoid adding an extra blank line to the selection.
- ;; This happens when point or mark is at the start of the next line.
- ;;
- ;; When selection is from bottom to top, exchange point and mark
- ;; so that the `point' and `(region-end)' are the same.
- (when (< (point) (mark))
- (exchange-point-and-mark))
- (when (= end (line-beginning-position))
- ;; Go up and avoid the blank line
- (setq end (1- end)))
- (setq line-end (line-number-at-pos end))
- (when (<= line-end line-start)
- (setq line-end nil)))
- (list line-start line-end)))))
-
-(defun git-link--new (link)
- (kill-new link)
- ;; prevent URL escapes from being interpreted as format strings
- (message (replace-regexp-in-string "%" "%%" link t t))
- (setq deactivate-mark t)
- (when git-link-open-in-browser
- (if (fboundp git-link-open-in-browser)
- (funcall git-link-open-in-browser link)
- (browse-url link))))
-
-(defun git-link-codeberg (hostname dirname filename branch commit start end)
- (format "https://%s/%s/src/%s/%s"
- hostname
- dirname
- (or branch commit)
- (concat filename
- (when start
- (concat "#"
- (if end
- (format "L%s-L%s" start end)
- (format "L%s" start)))))))
-
-(defun git-link-gitlab (hostname dirname filename branch commit start end)
- (format "https://%s/%s/blob/%s/%s"
- hostname
- dirname
- (or branch commit)
- (concat filename
- (when start
- (concat "#"
- (if end
- (format "L%s-%s" start end)
- (format "L%s" start)))))))
-
-(defun git-link-github (hostname dirname filename branch commit start end)
- (format "https://%s/%s/blob/%s/%s"
- hostname
- dirname
- (or branch commit)
- (concat filename
- (when start
- (concat (if (git-link--should-render-plain filename) "?plain=1#" "#")
- (if end
- (format "L%s-L%s" start end)
- (format "L%s" start)))))))
-
-(defun git-link-googlesource (hostname dirname filename branch commit start end)
- (format "https://%s/%s/+/%s/%s"
- hostname
- dirname
- (or branch commit)
- (concat filename
- (when start
- (format "#%s" start)
- ))))
-
-(defun git-link-azure (hostname dirname filename branch commit start end)
- (format "https://%s/%s?path=%%2F%s&version=%s&line=%s&lineEnd=%s&lineStartColumn=1&lineEndColumn=9999&lineStyle=plain"
- hostname
- dirname
- filename
- (concat "G" (if branch "B" "C") (or branch commit))
- (or start "")
- (or end start "")))
-
-(defun git-link-sourcehut (hostname dirname filename branch commit start end)
- (format "https://%s/%s/tree/%s/%s"
- hostname
- dirname
- (or branch commit)
- (concat filename
- (when start
- (concat "#"
- (if end
- (format "L%s-%s" start end)
- (format "L%s" start)))))))
-
-
-(defun git-link-commit-github (hostname dirname commit)
- (format "https://%s/%s/commit/%s"
- hostname
- dirname
- commit))
-
-(defun git-link-commit-googlesource (hostname dirname commit)
- (format "https://%s/%s/+/%s"
- hostname
- dirname
- commit))
-
-(defun git-link-commit-azure (hostname dirname commit)
- (format "https://%s/%s/commit/%s"
- hostname
- dirname
-
- ;; Azure only supports full 32 characters SHA
- (car (git-link--exec "rev-parse" commit))))
-
-(defun git-link-commit-codeberg (hostname dirname commit)
- (format "https://%s/%s/commit/%s"
- hostname
- dirname
- commit))
-
-(defun git-link-gitorious (hostname dirname filename _branch commit start _end)
- (format "https://%s/%s/source/%s:%s#L%s"
- hostname
- dirname
- commit
- filename
- start))
-
-(defun git-link-commit-gitorious (hostname dirname commit)
- (format "https://%s/%s/commit/%s"
- hostname
- dirname
- commit))
-
-(defun git-link-bitbucket (hostname dirname filename _branch commit start end)
- ;; ?at=branch-name
- (format "https://%s/%s/src/%s/%s"
- hostname
- dirname
- commit
- (if (string= "" (file-name-nondirectory filename))
- filename
- (concat filename
- "#"
- (file-name-nondirectory filename)
- (when start
- (if end
- (format "-%s:%s" start end)
- (format "-%s" start)))))))
-
-(defun git-link-commit-bitbucket (hostname dirname commit)
- ;; ?at=branch-name
- (format "https://%s/%s/commits/%s"
- hostname
- dirname
- commit))
-
-(defun git-link-cgit (hostname dirname filename branch commit start _end)
- (format "https://%s/%s/tree/%s?h=%s"
- hostname
- dirname
- filename
- (concat
- (or branch commit)
- (when start
- (concat "#" (format "n%s" start))))))
-
-(defun git-link-commit-cgit (hostname dirname commit)
- (format "https://%s/%s/commit/?id=%s"
- hostname
- dirname
- commit))
-
-(defun git-link-savannah (hostname dirname filename branch commit start end)
- (git-link-cgit hostname
- (format "cgit/%s.git" dirname) ; unique to Savannah
- filename
- branch
- commit
- start
- end))
-
-(defun git-link-commit-savannah (hostname dirname commit)
- (git-link-commit-cgit hostname
- (format "cgit/%s.git" dirname) ; also unique to Savannah
- commit))
-
-(defun git-link-sourcegraph (hostname dirname filename branch commit start end)
- (let ((line-or-range (cond ((and start end) (format "#L%s-%s" start end))
- (start (format "#L%s" start))
- (t "")))
- (branch-or-commit (or branch commit))
- (dir-file-name (directory-file-name dirname)))
- (format "https://%s/%s@%s/-/blob/%s%s"
- hostname
- dir-file-name
- branch-or-commit
- filename
- line-or-range)))
-
-(defun git-link-commit-sourcegraph (hostname dirname commit)
- (let ((dir-file-name (directory-file-name dirname)))
- (format "https://%s/%s/-/commit/%s"
- hostname
- dir-file-name
- commit)))
-
-(defun git-link-homepage-github (hostname dirname)
- (format "https://%s/%s"
- hostname
- dirname))
-
-(defun git-link-homepage-savannah (hostname dirname)
- (format "https://%s/cgit/%s.git/"
- hostname
- dirname))
-
-(defun git-link-codecommit (hostname
- dirname
- filename
- branch
- commit
- start
- end)
- (format "https://%s/%s/browse/refs/heads/%s/--/%s"
- hostname
- dirname
- (or branch commit)
- (concat filename
- (when start
- (format "?lines=%s-%s"
- start
- (or end start))))))
-
-(defun git-link-commit-codecommit (hostname dirname commit)
- (format "https://%s/%s/commit/%s" hostname dirname commit))
-
-(defun git-link-homepage-codecommit (hostname dirname)
- (format "https://%s/%s/browse" hostname dirname))
-
-(define-obsolete-function-alias
- 'git-link-homepage-svannah 'git-link-homepage-savannah "cf947f9")
-
-(defun git-link--select-remote ()
- (if (equal '(4) current-prefix-arg)
- (git-link--read-remote)
- (git-link--remote)))
-
-(defun git-link--should-render-plain (filename)
- "Check if the extension of the given filename belongs
-to the list of extensions which generated link should be
-shown as a plain file"
- (let ((extension (or (file-name-extension filename) "")))
- (member (downcase extension) git-link-extensions-rendered-plain)))
-
-;;;###autoload
-(defun git-link (remote start end)
- "Create a URL representing the current buffer's location in its
-GitHub/Bitbucket/GitLab/... repository at the current line number
-or active region. The URL will be added to the kill ring. If
-`git-link-open-in-browser' is non-nil also call `browse-url'.
-
-With a prefix argument of - generate a link without line number(s).
-Also see `git-link-use-single-line-number'.
-
-With a single prefix argument prompt for the remote's name.
-Defaults to \"origin\".
-
-With a double prefix argument invert the value of
-`git-link-use-commit'."
- (interactive
- (if (equal '- current-prefix-arg)
- (list (git-link--remote) nil nil)
- (let* ((remote (git-link--select-remote))
- (region (when (or buffer-file-name (git-link--using-magit-blob-mode))
- (git-link--get-region))))
-
- (if (and (null git-link-use-single-line-number) (null (cadr region)))
- (list remote nil nil)
- (list remote (car region) (cadr region))))))
-
- (let (filename branch commit handler remote-info (remote-url (git-link--remote-url remote)))
- (if (null remote-url)
- (message "Remote `%s' not found" remote)
-
- (setq remote-info (git-link--parse-remote remote-url)
- filename (git-link--relative-filename)
- branch (git-link--branch)
- commit (git-link--commit)
- handler (git-link--handler git-link-remote-alist (car remote-info)))
-
- (cond ((null filename)
- (message "Can't figure out what to link to"))
- ((null (car remote-info))
- (message "Remote `%s' contains an unsupported URL" remote))
- ((not (functionp handler))
- (message "No handler found for %s" (car remote-info)))
- ;; TODO: null ret val
- (t
- (let ((vc-revison (git-link--parse-vc-revision filename)))
- (when vc-revison
- (setq filename (car vc-revison)
- commit (cdr vc-revison)))
-
- (git-link--new
- (funcall handler
- (car remote-info)
- (cadr remote-info)
- (url-hexify-string filename (url--allowed-chars (cons ?/ url-unreserved-chars)))
- (if (or (git-link--using-git-timemachine)
- (git-link--using-magit-blob-mode)
- vc-revison
- (if (equal '(16) current-prefix-arg)
- (not git-link-use-commit)
- git-link-use-commit))
- nil
- (url-hexify-string branch))
- commit
- start
- end))))))))
-
-;;;###autoload
-(defun git-link-commit (remote)
- "Create a URL representing the commit for the hash under point
-in the current buffer's GitHub/Bitbucket/GitLab/...
-repository. The URL will be added to the kill ring.
-
-With a prefix argument prompt for the remote's name.
-Defaults to \"origin\"."
-
- (interactive (list (git-link--select-remote)))
- (let* (commit handler remote-info (remote-url (git-link--remote-url remote)))
- (if (null remote-url)
- (message "Remote `%s' not found" remote)
-
- (setq remote-info (git-link--parse-remote remote-url)
- commit (word-at-point)
- handler (git-link--handler git-link-commit-remote-alist (car remote-info)))
-
- (cond ((null (car remote-info))
- (message "Remote `%s' contains an unsupported URL" remote))
- ((not (string-match-p "[a-fA-F0-9]\\{7,40\\}" (or commit "")))
- (message "Point is not on a commit hash"))
- ((not (functionp handler))
- (message "No handler for %s" (car remote-info)))
- ;; null ret val
- ((git-link--new
- (funcall handler
- (car remote-info)
- (cadr remote-info)
- (substring-no-properties commit))))))))
-
-;;;###autoload
-(defun git-link-homepage (remote)
- "Create a URL representing the homepage of the current
-buffer's GitHub/Bitbucket/GitLab/... repository. The
-URL will be added to the kill ring.
-
-With a prefix argument prompt for the remote's name.
-Defaults to \"origin\"."
-
- (interactive (list (git-link--select-remote)))
- (let* (handler remote-info (remote-url (git-link--remote-url remote)))
- (if (null remote-url)
- (message "Remote `%s' not found" remote)
-
- (setq remote-info (git-link--parse-remote remote-url)
- handler (git-link--handler git-link-homepage-remote-alist (car remote-info)))
-
- (cond ((null (car remote-info))
- (message "Remote `%s' contains an unsupported URL" remote))
- ((not (functionp handler))
- (message "No handler for %s" (car remote-info)))
- ;; null ret val
- ((git-link--new
- (funcall handler
- (car remote-info)
- (cadr remote-info))))))))
-
-(provide 'git-link)
-;;; git-link.el ends here
--- /dev/null
+;;; git-link-autoloads.el --- automatically extracted autoloads -*- lexical-binding: t -*-
+;;
+;;; Code:
+
+(add-to-list 'load-path (directory-file-name
+ (or (file-name-directory #$) (car load-path))))
+
+\f
+;;;### (autoloads nil "git-link" "git-link.el" (0 0 0 0))
+;;; Generated autoloads from git-link.el
+
+(autoload 'git-link "git-link" "\
+Create a URL representing the current buffer's location in its
+GitHub/Bitbucket/GitLab/... repository at the current line number
+or active region. The URL will be added to the kill ring. If
+`git-link-open-in-browser' is non-nil also call `browse-url'.
+
+With a prefix argument of - generate a link without line number(s).
+Also see `git-link-use-single-line-number'.
+
+With a single prefix argument prompt for the remote's name.
+Defaults to \"origin\".
+
+With a double prefix argument invert the value of
+`git-link-use-commit'.
+
+\(fn REMOTE START END)" t nil)
+
+(autoload 'git-link-commit "git-link" "\
+Create a URL representing the commit for the hash under point
+in the current buffer's GitHub/Bitbucket/GitLab/...
+repository. The URL will be added to the kill ring.
+
+With a prefix argument prompt for the remote's name.
+Defaults to \"origin\".
+
+\(fn REMOTE)" t nil)
+
+(autoload 'git-link-homepage "git-link" "\
+Create a URL representing the homepage of the current
+buffer's GitHub/Bitbucket/GitLab/... repository. The
+URL will be added to the kill ring.
+
+With a prefix argument prompt for the remote's name.
+Defaults to \"origin\".
+
+\(fn REMOTE)" t nil)
+
+(register-definition-prefixes "git-link" '("git-link-"))
+
+;;;***
+\f
+;;;### (autoloads nil nil ("git-link-pkg.el") (0 0 0 0))
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; coding: utf-8
+;; End:
+;;; git-link-autoloads.el ends here
--- /dev/null
+(define-package "git-link" "20230518.204" "Get the GitHub/Bitbucket/GitLab URL for a buffer location"
+ '((emacs "24.3"))
+ :commit "aded95807f277f30e1607313bdf9ac9a016a2305" :authors
+ '(("Skye Shaw" . "skye.shawNOSPAMgmail.com"))
+ :maintainers
+ '(("Skye Shaw" . "skye.shawNOSPAMgmail.com"))
+ :maintainer
+ '("Skye Shaw" . "skye.shawNOSPAMgmail.com")
+ :keywords
+ '("git" "vc" "github" "bitbucket" "gitlab" "sourcehut" "aws" "azure" "convenience")
+ :url "http://github.com/sshaw/git-link")
+;; Local Variables:
+;; no-byte-compile: t
+;; End:
--- /dev/null
+;;; git-link.el --- Get the GitHub/Bitbucket/GitLab URL for a buffer location -*- lexical-binding: t -*-
+
+;; Copyright (C) 2013-2022 Skye Shaw and others
+;; Author: Skye Shaw <skye.shawNOSPAMgmail.com>
+;; Version: 0.9.0
+;; Keywords: git, vc, github, bitbucket, gitlab, sourcehut, aws, azure, convenience
+;; URL: http://github.com/sshaw/git-link
+;; Package-Requires: ((emacs "24.3"))
+
+;; This file is NOT part of GNU Emacs.
+
+;;; License:
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Create URLs for files and commits in GitHub/Bitbucket/GitLab/...
+;; repositories. `git-link' returns the URL for the current buffer's file
+;; location at the current line number or active region. `git-link-commit'
+;; returns the URL for a commit. URLs are added to the kill ring.
+;;
+;; With a prefix argument prompt for the remote's name. Defaults to "origin".
+
+;;; Change Log:
+
+;; 2023-02-15 - v0.9.0
+;; * Add support for GoogleSource (thanks Peter Becich)
+;; * Add plain=1 to force showing a non rendered GitHub links (thanks Erick Navarro)
+;; * Add prefix arg to override git-link-use-commit when calling git-link (thanks Nacho Barrientos)
+;; * Fix git-link-homepage for Bitbucket (Issue #97 thanks Sibi Prabakaran)
+;; * Fix the Codeberg links to line ranges (Issue #109 thanks Wojciech Siewierski)
+;;
+;; 2022-02-17 - v0.8.6
+;; * Fix URL escaping of pathnames on Emacs < 27 (Issue #93 thanks David Ongaro)
+;;
+;; 2022-02-06 - v0.8.5
+;; * URL escape pathnames (thanks David Ongaro)
+;; * Mark misspelled Savannah functions as obsolete (thanks Hendursaga)
+;; * Add support for AWS CodeCommit (thanks Ram Krishnan)
+;; * Add support for cgit (thanks Hendursaga)
+;;
+;; 2020-01-20 - v0.8.4
+;; * Add support for Codeberg (thanks Jeremy Friesen)
+;; * Add git-link-homepage-remote-alist (thanks Wim Van Deun)
+;;
+;; 2020-01-20 - v0.8.3
+;; * Add support for Savannah
+;;
+;; 2020-12-14 - v0.8.2
+;; * Fix sourcehut URL, don't link to raw version (Issue #77)
+;; * Fix sourcehut multi-line URLs
+;;
+;; 2020-11-23 - v0.8.1
+;; * Fix URL casing (Issue #57)
+;; * Fix byte-compile warnings (Issue #75 thanks Brian Leung)
+;;
+;; 2020-07-21 - v0.8.0
+;; * Add `-' prefix argument to git-link to generate links without line numbers
+;; * Add git-link-use-single-line-number
+;; * Fix sourcehut's git-link handler
+;;
+;; 2020-03-31 - v0.7.6
+;; * Adapt to changes in Azure interface (Issue #65, thanks Roey Darwish Dror)
+;;
+;; 2019-08-28 - v0.7.5
+;; * Add support for Azure DevOps (Issue #62, thanks Roey Darwish Dror)
+;;
+;; 2019-08-16 - v0.7.4
+;; * Add support for Magit-Blob buffers (Issue #61, thanks Miciah Dashiel Butler Masters)
+;;
+;; 2019-03-09 - v0.7.3
+;; * Add support for sourcehut
+;;
+;; 2018-10-30 - v0.7.2
+;; * Fix suffix stripping on remote path only if it ends in .git (Issue #58, thanks Marko Crnic)
+;;
+;; 2018-07-08 - v0.7.1
+;; * Add support for vc-revision-other-window files (Issue #54)
+;;
+;; 2018-06-07 - v0.7.0
+;; * Add support for Tramp (Issue #49, thanks Jürgen Hötzel)
+;; * Fix various compiler warnings
+;; * Fix differences between url-path-and-query across Emacs versions
+;; * Require Emacs 24.3
+;;
+;; 2018-04-23 - v0.6.0
+;; * Fix parsing of remotes with auth info (Issue #51)
+;; * Removed remote regex in favor of url-parse
+;;
+;; 2017-06-03 - v0.5.1
+;; * Add support for more magit modes
+;;
+;; 2017-06-01 - v0.5.0
+;; * Add support for linking in dired and magit modes
+;; * Add support for defcustom
+;; * Change git-link-remote-regex to support more remote URL formats (Thanks Kaushal Modi)
+;; * Change git-link-remote-alist to use regex matching (Thanks Kaushal Modi)
+;; * Fix point on commit hash regex and support uppercase SHAs (Thanks Kaushal Modi!)
+;; * Fix git-link-commit message so that SHA text is displayed without properties
+;; * Enabled lexical-binding (Thanks Kaushal Modi!!)
+;;
+;; -- Note that v0.5.0 was released as "v0.5.0 (unreleased)"
+;;
+;; 2016-10-19 - v0.4.5
+;; * Fix for branches containing reserved URLs characters (Issue #36)
+;;
+;; 2016-09-11 - v0.4.4
+;; * Added support for git-link-homepage
+;;
+;; 2016-08-13 - v0.4.3
+;; * Added support for git-timemachine (Issue #22, thanks Diego Berrocal)
+;;
+;; 2016-08-09 - v0.4.2
+;; * Fix for URLs with ports (Issue #32)
+;;
+;; 2016-04-01 - v0.4.1
+;; * Better handling for branches that have no explicit remote
+;; * Better error messages
+;;
+;; 2016-02-16 - v0.4.0
+;; * Try branch's tracking remote when other branch settings are not specified
+;; * git-link-default-remote now defaults to nil
+;;
+;; 2015-09-21 - v0.3.0
+;; * Support for setting branch and remote names via `git config`
+;; * Added git-link-default-branch
+;; * Removed some functions, use emacs "private" convention for others
+;;
+;; 2015-09-12 - v0.2.2
+;; * Support for BitBucket's multiline format
+;;
+;; 2015-07-25 - v0.2.1
+;; * Fix for BitBucket's new URL format (Thanks Ev Dolzhenko)
+;; * Fix for GitLab's multiline format (Thanks Enrico Carlesso)
+;;
+;; 2015-06-05 - v0.2.0
+;; * Deactivate mark after killing the link (Thanks Kaushal Modi)
+;; * Support for GitLab (Thanks Swaroop C H)
+;; * Use completing-read when prompting for remotes (Thanks Andrew Gwozdziewycz)
+;; * Display URL in minibuffer when adding to kill ring (Thanks Andrew Gwozdziewycz)
+;; * Added git-link-use-commit variable (Thanks Kaushal Modi)
+;; * Fix for displaying link in minibuffer when interprogram-cut-function is set (Thanks Ric Lister)
+;; * Fix to ignore point at beginning of line in regions (Thanks Kaushal Modi)
+;; * Fix for narrow-to-region (Bug #10, thanks Andrew Gwozdziewycz)
+;; * Fix to use remote hostname when constructing link URLs (Thanks David Hull)
+;;
+;; 2015-02-05 - v0.1.0
+;; * Added git-link-commit (Thanks Ryan Barrett)
+;; * Added git-link-open-in-browser variable (Thanks Ryan Barrett)
+;; * Use call-process instead of shell-command-to-string
+;; * Use --short option when calling symbolic-ref (Thanks Steven Huwig)
+;;
+;; 2014-02-27 - v0.0.2
+;; * Fix for buffers visiting files through symlinks (Issue #1, thanks Evgeniy Dolzhenko)
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'dired)
+(require 'thingatpt)
+(require 'url-util)
+(require 'url-parse)
+
+(defgroup git-link nil
+ "Get the GitHub/Bitbucket/GitLab URL for a buffer location"
+ :prefix "git-link-"
+ :link '(url-link :tag "Report a Bug" "https://github.com/sshaw/git-link/issues")
+ :link '(url-link :tag "Homepage" "https://github.com/sshaw/git-link")
+ :group 'convenience)
+
+(eval-when-compile
+ (defvar git-timemachine-revision)) ;; silence reference to free variable warning
+
+(defcustom git-link-default-remote nil
+ "Name of the remote to link to."
+ :type 'string
+ :group 'git-link)
+
+(defcustom git-link-default-branch nil
+ "Name of the branch to link to."
+ :type 'string
+ :group 'git-link)
+
+(defcustom git-link-open-in-browser nil
+ "If t also open the link via `browse-url'. To use an alternate
+function set to that function's symbol."
+ :type '(choice boolean function)
+ :group 'git-link)
+
+(defcustom git-link-use-commit nil
+ "If non-nil use the latest commit's hash in the link instead of the branch name."
+ :type 'boolean
+ :group 'git-link)
+
+(defcustom git-link-use-single-line-number t
+ "If t a link to a single line will always contain the line number.
+If nil line numbers will only be added when a selection contains
+more than 1 line.
+
+Note that `git-link' can exclude line numbers on a per invocation basis.
+See its docs."
+ :type 'boolean
+ :group 'git-link)
+
+(defcustom git-link-remote-alist
+ '(("git.sr.ht" git-link-sourcehut)
+ ("codeberg.org" git-link-codeberg)
+ ("github" git-link-github)
+ ("bitbucket" git-link-bitbucket)
+ ("gitorious" git-link-gitorious)
+ ("gitlab" git-link-gitlab)
+ ("git\\.\\(sv\\|savannah\\)\\.gnu\\.org" git-link-savannah)
+ ("googlesource.com" git-link-googlesource)
+ ("visualstudio\\|azure" git-link-azure)
+ ("sourcegraph" git-link-sourcegraph)
+ ("\\(amazonaws\\|amazon\\)\\.com" git-link-codecommit))
+ "Alist of host names and functions creating file links for those.
+Each element looks like (REGEXP FUNCTION) where REGEXP is used to
+match the remote's host name and FUNCTION is used to generate a link
+to the file on remote host.
+
+As an example, \"gitlab\" will match with both \"gitlab.com\" and
+\"gitlab.example.com\"."
+ :type '(alist :key-type string :value-type (group function))
+ :group 'git-link)
+
+(defcustom git-link-commit-remote-alist
+ '(("git.sr.ht" git-link-commit-github)
+ ("codeberg.org" git-link-commit-codeberg)
+ ("github" git-link-commit-github)
+ ("bitbucket" git-link-commit-bitbucket)
+ ("gitorious" git-link-commit-gitorious)
+ ("gitlab" git-link-commit-gitlab)
+ ("git\\.\\(sv\\|savannah\\)\\.gnu\\.org" git-link-commit-savannah)
+ ("googlesource.com" git-link-commit-googlesource)
+ ("visualstudio\\|azure" git-link-commit-azure)
+ ("sourcegraph" git-link-commit-sourcegraph)
+ ("\\(amazonaws\\|amazon\\)\\.com" git-link-commit-codecommit))
+ "Alist of host names and functions creating commit links for those.
+Each element looks like (REGEXP FUNCTION) where REGEXP is used to
+match the remote's host name and FUNCTION is used to generate a link
+to the commit on remote host.
+
+As an example, \"gitlab\" will match with both \"gitlab.com\" and
+\"gitlab.example.com\"."
+ :type '(alist :key-type string :value-type (group function))
+ :group 'git-link)
+
+(defcustom git-link-homepage-remote-alist
+ '(("git.sr.ht" git-link-homepage-github)
+ ("github" git-link-homepage-github)
+ ("bitbucket" git-link-homepage-github)
+ ("gitorious" git-link-homepage-github)
+ ("gitlab" git-link-homepage-github)
+ ("git\\.\\(sv\\|savannah\\)\\.gnu\\.org" git-link-homepage-savannah)
+ ("googlesource.com" git-link-homepage-github)
+ ("visualstudio\\|azure" git-link-homepage-github)
+ ("sourcegraph" git-link-homepage-github)
+ ("\\(amazonaws\\|amazon\\)\\.com" git-link-homepage-codecommit))
+ "Alist of host names and functions creating homepage links for those.
+Each element looks like (REGEXP FUNCTION) where REGEXP is used to
+match the remote's host name and FUNCTION is used to generate a link
+to the commit on remote host.
+
+As an example, \"gitlab\" will match with both \"gitlab.com\" and
+\"gitlab.example.com\"."
+ :type '(alist :key-type string :value-type (group function))
+ :group 'git-link)
+
+(defcustom git-link-extensions-rendered-plain '("org" "md" "rst" "adoc" "markdown" "asciidoc")
+ "List of extensions that should be rendered in plain mode, systems like
+Github, Gitlab, etc show a rendered version by default, for these extensions
+we can prevent that behaviour."
+ :type 'list
+ :group 'git-link)
+
+(defun git-link--exec(&rest args)
+ (ignore-errors
+ (with-temp-buffer
+ (when (zerop (apply #'process-file "git" nil (current-buffer) nil args))
+ (goto-char (point-min))
+ (cl-loop until (eobp)
+ collect (buffer-substring-no-properties
+ (line-beginning-position)
+ (line-end-position))
+ do (forward-line 1))))))
+
+(defun git-link--get-config (name)
+ (car (git-link--exec "config" "--get" name)))
+
+(defun git-link--remotes ()
+ (git-link--exec "remote"))
+
+(defun git-link--last-commit ()
+ (car (git-link--exec "--no-pager" "log" "-n1" "--pretty=format:%H")))
+
+(defvar magit-buffer-revision)
+
+(defun git-link--commit ()
+ (cond
+ ((git-link--using-git-timemachine)
+ (car git-timemachine-revision))
+ ((git-link--using-magit-blob-mode)
+ magit-buffer-revision)
+ (t (git-link--last-commit))))
+
+(defun git-link--current-branch ()
+ (car (git-link--exec "symbolic-ref" "--short" "HEAD")))
+
+(defun git-link--repo-root ()
+ (let ((dir (car (git-link--exec "rev-parse" "--show-toplevel"))))
+ (if (file-remote-p default-directory)
+ (concat (file-remote-p default-directory) dir)
+ dir)))
+
+(defun git-link--remote-url (name)
+ (car (git-link--exec "remote" "get-url" name)))
+
+(defun git-link--branch-remote (branch)
+ (git-link--get-config (format "branch.%s.remote" branch)))
+
+(declare-function magit-rev-branch "ext:magit-git")
+
+(defun git-link--branch ()
+ (or (git-link--get-config "git-link.branch")
+ git-link-default-branch
+ (when (git-link--using-magit-blob-mode)
+ (magit-rev-branch magit-buffer-revision))
+ (git-link--current-branch)))
+
+(defun git-link--remote ()
+ (let* ((branch (git-link--current-branch))
+ (remote (or (git-link--get-config "git-link.remote")
+ git-link-default-remote
+ (git-link--branch-remote branch))))
+
+ ;; Git defaults to "." if the branch has no remote.
+ ;; If the branch has no remote we try master's, which may be set.
+ (if (or (null remote)
+ (and (string= remote ".")
+ (not (string= branch "master"))))
+ (setq remote (git-link--branch-remote "master")))
+
+ (if (or (null remote) (string= remote "."))
+ "origin"
+ remote)))
+
+(defun git-link--handler (alist str)
+ "For an ALIST whose `car' (a regexp) matches STR, return cadr.
+
+The ALIST consists of (REGEXP FN) list elements.
+Valid ALISTs are `git-link-remote-alist',`git-link-commit-remote-alist'.
+
+For the first ALIST element whose REGEXP matches with STR, FN is
+returned.
+
+Return nil,
+- if STR does not match with REGEXP in any of the elements of ALIST, or
+- if STR is not a string"
+ (when (stringp str)
+ (cadr (cl-find-if (lambda (lst)
+ (string-match-p (car lst) str))
+ alist))))
+
+(defun git-link--parse-vc-revision (filename)
+"If FILENAME appears to be from `vc-revision-other-window'
+return (FILENAME . REVISION) otherwise nil."
+ (when (and (string-match "\\(.+\\)\\.~\\([^~]+\\)~$" filename)
+ (file-exists-p (match-string 1 filename)))
+ (cons (match-string 1 filename)
+ (match-string 2 filename))))
+
+(defvar magit-buffer-file-name)
+
+(defun git-link--relative-filename ()
+ (let* ((filename (buffer-file-name (buffer-base-buffer)))
+ (dir (git-link--repo-root)))
+
+ (when (null filename)
+ (cond
+ ((eq major-mode 'dired-mode)
+ (setq filename (dired-file-name-at-point)))
+ ((git-link--using-magit-blob-mode)
+ (setq filename magit-buffer-file-name))
+ ((and (string-match-p "^magit-" (symbol-name major-mode))
+ (fboundp 'magit-file-at-point))
+ (setq filename (magit-file-at-point)))))
+
+ (if (and dir filename
+ ;; Make sure filename is not above dir, e.g. "/foo/repo-root/.."
+ (< (length dir) (length (file-truename filename))))
+ (substring (file-truename filename)
+ (1+ (length dir))))))
+
+(defun git-link--parse-remote (url)
+ "Parse URL and return a list as (HOST DIR). DIR has no leading slash or `git' extension."
+ (let (host path parsed)
+ (unless (string-match "^[a-zA-Z0-9]+://" url)
+ (setq url (concat "ssh://" url)))
+
+ (setq parsed (url-generic-parse-url url)
+ ;; Normalize path.
+ ;; If none, will be nil on Emacs < 25. Later versions return "".
+ path (or (car (url-path-and-query parsed)) "")
+ host (url-host parsed))
+
+ (when host
+ (when (and (not (string= "/" path))
+ (not (string= "" path)))
+ (setq path (substring
+ (if (string-match "\\.git\\'" path)
+ (file-name-sans-extension path)
+ path)
+ 1)))
+
+ ;; Fix-up scp style URLs.
+ ;; git@foo:UsEr/repo gives a host of foo:user
+ ;; We also need to preserve case so we take UsEr from the original url
+ (when (string-match ":" host)
+ (let ((parts (split-string host ":" t))
+ (case-fold-search t))
+ (string-match (concat (car parts) ":\\(" (cadr parts) "\\)/") url)
+ (setq host (car parts)
+ path (concat (match-string 1 url) "/" path))))
+
+ ;; Fix-up Azure SSH URLs
+ (when (string= "ssh.dev.azure.com" host)
+ (setq host "dev.azure.com")
+ (setq path (replace-regexp-in-string
+ "v3/\\([^/]+\\)/\\([^/]+\\)/\\([^/]+\\)"
+ "\\1/\\2/_git/\\3"
+ path)))
+ (when (string= "vs-ssh.visualstudio.com" host)
+ (setq host (concat (url-user parsed) ".visualstudio.com"))
+ (setq path (replace-regexp-in-string
+ (concat "^v3/" (url-user parsed) "/\\([^/]+\\)/")
+ "\\1/_git/"
+ path)))
+
+ ;; For Savannah
+ (when (string= "git.savannah.gnu.org" host)
+ (cond
+ ((string-match "\\`git/" path)
+ (setq path (substring path 4)))
+ ((string-match "\\`srv/git/" path)
+ (setq path (substring path 8)))))
+
+ ;; For AWS CodeCommit
+ (when (string-match "git-codecommit\\.\\(.*\\)\\.amazonaws.com" host)
+ (let* ((matchp (string-match "\\([^\\.]*\\)\\.\\([^\\.]*\\)" host))
+ (region (when matchp
+ (match-string 2 host)))
+ (domainname ".console.aws.amazon.com"))
+ (when region
+ (setq host (concat region domainname))))
+ (when (string-match "v1/repos/" path)
+ (setq path (concat "codesuite/codecommit/repositories/"
+ (substring path 9)))))
+
+ (list host path))))
+
+(defun git-link--using-git-timemachine ()
+ (and (boundp 'git-timemachine-revision)
+ git-timemachine-revision))
+
+(defun git-link--using-magit-blob-mode ()
+ (bound-and-true-p magit-blob-mode))
+
+(defun git-link--read-remote ()
+ (let ((remotes (git-link--remotes))
+ (current (git-link--remote)))
+ (completing-read "Remote: "
+ remotes
+ nil
+ t
+ ""
+ nil
+ (if (member current remotes)
+ current
+ (car remotes)))))
+
+(defun git-link--get-region ()
+ (save-restriction
+ (widen)
+ (save-excursion
+ (let* ((use-region (use-region-p))
+ (start (when use-region (region-beginning)))
+ (end (when use-region (region-end)))
+ (line-start (line-number-at-pos start))
+ line-end)
+ (when use-region
+ ;; Avoid adding an extra blank line to the selection.
+ ;; This happens when point or mark is at the start of the next line.
+ ;;
+ ;; When selection is from bottom to top, exchange point and mark
+ ;; so that the `point' and `(region-end)' are the same.
+ (when (< (point) (mark))
+ (exchange-point-and-mark))
+ (when (= end (line-beginning-position))
+ ;; Go up and avoid the blank line
+ (setq end (1- end)))
+ (setq line-end (line-number-at-pos end))
+ (when (<= line-end line-start)
+ (setq line-end nil)))
+ (list line-start line-end)))))
+
+(defun git-link--new (link)
+ (kill-new link)
+ ;; prevent URL escapes from being interpreted as format strings
+ (message (replace-regexp-in-string "%" "%%" link t t))
+ (setq deactivate-mark t)
+ (when git-link-open-in-browser
+ (if (fboundp git-link-open-in-browser)
+ (funcall git-link-open-in-browser link)
+ (browse-url link))))
+
+(defun git-link-codeberg (hostname dirname filename branch commit start end)
+ (format "https://%s/%s/src/%s/%s"
+ hostname
+ dirname
+ (or branch commit)
+ (concat filename
+ (when start
+ (concat "#"
+ (if end
+ (format "L%s-L%s" start end)
+ (format "L%s" start)))))))
+
+(defun git-link-gitlab (hostname dirname filename branch commit start end)
+ (format "https://%s/%s/-/blob/%s/%s"
+ hostname
+ dirname
+ (or branch commit)
+ (concat filename
+ (when start
+ (concat "#"
+ (if end
+ (format "L%s-%s" start end)
+ (format "L%s" start)))))))
+
+(defun git-link-github (hostname dirname filename branch commit start end)
+ (format "https://%s/%s/blob/%s/%s"
+ hostname
+ dirname
+ (or branch commit)
+ (concat filename
+ (when start
+ (concat (if (git-link--should-render-plain filename) "?plain=1#" "#")
+ (if end
+ (format "L%s-L%s" start end)
+ (format "L%s" start)))))))
+
+(defun git-link-googlesource (hostname dirname filename branch commit start end)
+ (format "https://%s/%s/+/%s/%s"
+ hostname
+ dirname
+ (or branch commit)
+ (concat filename
+ (when start
+ (format "#%s" start)
+ ))))
+
+(defun git-link-azure (hostname dirname filename branch commit start end)
+ (format "https://%s/%s?path=%%2F%s&version=%s&line=%s&lineEnd=%s&lineStartColumn=1&lineEndColumn=9999&lineStyle=plain"
+ hostname
+ dirname
+ filename
+ (concat "G" (if branch "B" "C") (or branch commit))
+ (or start "")
+ (or end start "")))
+
+(defun git-link-sourcehut (hostname dirname filename branch commit start end)
+ (format "https://%s/%s/tree/%s/%s"
+ hostname
+ dirname
+ (or branch commit)
+ (concat filename
+ (when start
+ (concat "#"
+ (if end
+ (format "L%s-%s" start end)
+ (format "L%s" start)))))))
+
+(defun git-link-commit-gitlab (hostname dirname commit)
+ (format "https://%s/%s/-/commit/%s"
+ hostname
+ dirname
+ commit))
+
+(defun git-link-commit-github (hostname dirname commit)
+ (format "https://%s/%s/commit/%s"
+ hostname
+ dirname
+ commit))
+
+(defun git-link-commit-googlesource (hostname dirname commit)
+ (format "https://%s/%s/+/%s"
+ hostname
+ dirname
+ commit))
+
+(defun git-link-commit-azure (hostname dirname commit)
+ (format "https://%s/%s/commit/%s"
+ hostname
+ dirname
+
+ ;; Azure only supports full 32 characters SHA
+ (car (git-link--exec "rev-parse" commit))))
+
+(defun git-link-commit-codeberg (hostname dirname commit)
+ (format "https://%s/%s/commit/%s"
+ hostname
+ dirname
+ commit))
+
+(defun git-link-gitorious (hostname dirname filename _branch commit start _end)
+ (format "https://%s/%s/source/%s:%s#L%s"
+ hostname
+ dirname
+ commit
+ filename
+ start))
+
+(defun git-link-commit-gitorious (hostname dirname commit)
+ (format "https://%s/%s/commit/%s"
+ hostname
+ dirname
+ commit))
+
+(defun git-link-bitbucket (hostname dirname filename _branch commit start end)
+ ;; ?at=branch-name
+ (format "https://%s/%s/src/%s/%s"
+ hostname
+ dirname
+ commit
+ (if (string= "" (file-name-nondirectory filename))
+ filename
+ (concat filename
+ "#"
+ (file-name-nondirectory filename)
+ (when start
+ (if end
+ (format "-%s:%s" start end)
+ (format "-%s" start)))))))
+
+(defun git-link-commit-bitbucket (hostname dirname commit)
+ ;; ?at=branch-name
+ (format "https://%s/%s/commits/%s"
+ hostname
+ dirname
+ commit))
+
+(defun git-link-cgit (hostname dirname filename branch commit start _end)
+ (format "https://%s/%s/tree/%s?h=%s"
+ hostname
+ dirname
+ filename
+ (concat
+ (or branch commit)
+ (when start
+ (concat "#" (format "n%s" start))))))
+
+(defun git-link-commit-cgit (hostname dirname commit)
+ (format "https://%s/%s/commit/?id=%s"
+ hostname
+ dirname
+ commit))
+
+(defun git-link-savannah (hostname dirname filename branch commit start end)
+ (git-link-cgit hostname
+ (format "cgit/%s.git" dirname) ; unique to Savannah
+ filename
+ branch
+ commit
+ start
+ end))
+
+(defun git-link-commit-savannah (hostname dirname commit)
+ (git-link-commit-cgit hostname
+ (format "cgit/%s.git" dirname) ; also unique to Savannah
+ commit))
+
+(defun git-link-sourcegraph (hostname dirname filename branch commit start end)
+ (let ((line-or-range (cond ((and start end) (format "#L%s-%s" start end))
+ (start (format "#L%s" start))
+ (t "")))
+ (branch-or-commit (or branch commit))
+ (dir-file-name (directory-file-name dirname)))
+ (format "https://%s/%s@%s/-/blob/%s%s"
+ hostname
+ dir-file-name
+ branch-or-commit
+ filename
+ line-or-range)))
+
+(defun git-link-commit-sourcegraph (hostname dirname commit)
+ (let ((dir-file-name (directory-file-name dirname)))
+ (format "https://%s/%s/-/commit/%s"
+ hostname
+ dir-file-name
+ commit)))
+
+(defun git-link-homepage-github (hostname dirname)
+ (format "https://%s/%s"
+ hostname
+ dirname))
+
+(defun git-link-homepage-savannah (hostname dirname)
+ (format "https://%s/cgit/%s.git/"
+ hostname
+ dirname))
+
+(defun git-link-codecommit (hostname
+ dirname
+ filename
+ branch
+ commit
+ start
+ end)
+ (format "https://%s/%s/browse/refs/heads/%s/--/%s"
+ hostname
+ dirname
+ (or branch commit)
+ (concat filename
+ (when start
+ (format "?lines=%s-%s"
+ start
+ (or end start))))))
+
+(defun git-link-commit-codecommit (hostname dirname commit)
+ (format "https://%s/%s/commit/%s" hostname dirname commit))
+
+(defun git-link-homepage-codecommit (hostname dirname)
+ (format "https://%s/%s/browse" hostname dirname))
+
+(define-obsolete-function-alias
+ 'git-link-homepage-svannah 'git-link-homepage-savannah "cf947f9")
+
+(defun git-link--select-remote ()
+ (if (equal '(4) current-prefix-arg)
+ (git-link--read-remote)
+ (git-link--remote)))
+
+(defun git-link--should-render-plain (filename)
+ "Check if the extension of the given filename belongs
+to the list of extensions which generated link should be
+shown as a plain file"
+ (let ((extension (or (file-name-extension filename) "")))
+ (member (downcase extension) git-link-extensions-rendered-plain)))
+
+;;;###autoload
+(defun git-link (remote start end)
+ "Create a URL representing the current buffer's location in its
+GitHub/Bitbucket/GitLab/... repository at the current line number
+or active region. The URL will be added to the kill ring. If
+`git-link-open-in-browser' is non-nil also call `browse-url'.
+
+With a prefix argument of - generate a link without line number(s).
+Also see `git-link-use-single-line-number'.
+
+With a single prefix argument prompt for the remote's name.
+Defaults to \"origin\".
+
+With a double prefix argument invert the value of
+`git-link-use-commit'."
+ (interactive
+ (if (equal '- current-prefix-arg)
+ (list (git-link--remote) nil nil)
+ (let* ((remote (git-link--select-remote))
+ (region (when (or buffer-file-name (git-link--using-magit-blob-mode))
+ (git-link--get-region))))
+
+ (if (and (null git-link-use-single-line-number) (null (cadr region)))
+ (list remote nil nil)
+ (list remote (car region) (cadr region))))))
+
+ (let (filename branch commit handler remote-info (remote-url (git-link--remote-url remote)))
+ (if (null remote-url)
+ (message "Remote `%s' not found" remote)
+
+ (setq remote-info (git-link--parse-remote remote-url)
+ filename (git-link--relative-filename)
+ branch (git-link--branch)
+ commit (git-link--commit)
+ handler (git-link--handler git-link-remote-alist (car remote-info)))
+
+ (cond ((null filename)
+ (message "Can't figure out what to link to"))
+ ((null (car remote-info))
+ (message "Remote `%s' contains an unsupported URL" remote))
+ ((not (functionp handler))
+ (message "No handler found for %s" (car remote-info)))
+ ;; TODO: null ret val
+ (t
+ (let ((vc-revison (git-link--parse-vc-revision filename)))
+ (when vc-revison
+ (setq filename (car vc-revison)
+ commit (cdr vc-revison)))
+
+ (git-link--new
+ (funcall handler
+ (car remote-info)
+ (cadr remote-info)
+ (url-hexify-string filename (url--allowed-chars (cons ?/ url-unreserved-chars)))
+ (if (or (git-link--using-git-timemachine)
+ (git-link--using-magit-blob-mode)
+ vc-revison
+ (if (equal '(16) current-prefix-arg)
+ (not git-link-use-commit)
+ git-link-use-commit))
+ nil
+ (url-hexify-string branch))
+ commit
+ start
+ end))))))))
+
+;;;###autoload
+(defun git-link-commit (remote)
+ "Create a URL representing the commit for the hash under point
+in the current buffer's GitHub/Bitbucket/GitLab/...
+repository. The URL will be added to the kill ring.
+
+With a prefix argument prompt for the remote's name.
+Defaults to \"origin\"."
+
+ (interactive (list (git-link--select-remote)))
+ (let* (commit handler remote-info (remote-url (git-link--remote-url remote)))
+ (if (null remote-url)
+ (message "Remote `%s' not found" remote)
+
+ (setq remote-info (git-link--parse-remote remote-url)
+ commit (word-at-point)
+ handler (git-link--handler git-link-commit-remote-alist (car remote-info)))
+
+ (cond ((null (car remote-info))
+ (message "Remote `%s' contains an unsupported URL" remote))
+ ((not (string-match-p "[a-fA-F0-9]\\{7,40\\}" (or commit "")))
+ (message "Point is not on a commit hash"))
+ ((not (functionp handler))
+ (message "No handler for %s" (car remote-info)))
+ ;; null ret val
+ ((git-link--new
+ (funcall handler
+ (car remote-info)
+ (cadr remote-info)
+ (substring-no-properties commit))))))))
+
+;;;###autoload
+(defun git-link-homepage (remote)
+ "Create a URL representing the homepage of the current
+buffer's GitHub/Bitbucket/GitLab/... repository. The
+URL will be added to the kill ring.
+
+With a prefix argument prompt for the remote's name.
+Defaults to \"origin\"."
+
+ (interactive (list (git-link--select-remote)))
+ (let* (handler remote-info (remote-url (git-link--remote-url remote)))
+ (if (null remote-url)
+ (message "Remote `%s' not found" remote)
+
+ (setq remote-info (git-link--parse-remote remote-url)
+ handler (git-link--handler git-link-homepage-remote-alist (car remote-info)))
+
+ (cond ((null (car remote-info))
+ (message "Remote `%s' contains an unsupported URL" remote))
+ ((not (functionp handler))
+ (message "No handler for %s" (car remote-info)))
+ ;; null ret val
+ ((git-link--new
+ (funcall handler
+ (car remote-info)
+ (cadr remote-info))))))))
+
+(provide 'git-link)
+;;; git-link.el ends here
+++ /dev/null
-;;; git-timemachine-autoloads.el --- automatically extracted autoloads -*- lexical-binding: t -*-
-;;
-;;; Code:
-
-(add-to-list 'load-path (directory-file-name
- (or (file-name-directory #$) (car load-path))))
-
-\f
-;;;### (autoloads nil "git-timemachine" "git-timemachine.el" (0 0
-;;;;;; 0 0))
-;;; Generated autoloads from git-timemachine.el
-
-(autoload 'git-timemachine-toggle "git-timemachine" "\
-Toggle git timemachine mode." t nil)
-
-(autoload 'git-timemachine "git-timemachine" "\
-Enable git timemachine for file of current buffer." t nil)
-
-(autoload 'git-timemachine-switch-branch "git-timemachine" "\
-Enable git timemachine for current buffer, switching to GIT-BRANCH.
-
-\(fn GIT-BRANCH)" t nil)
-
-(register-definition-prefixes "git-timemachine" '("git-timemachine-"))
-
-;;;***
-\f
-;; Local Variables:
-;; version-control: never
-;; no-byte-compile: t
-;; no-update-autoloads: t
-;; coding: utf-8
-;; End:
-;;; git-timemachine-autoloads.el ends here
+++ /dev/null
-;;; Generated package description from git-timemachine.el -*- no-byte-compile: t -*-
-(define-package "git-timemachine" "20230413.620" "Walk through git revisions of a file" '((emacs "24.3") (transient "0.1.0")) :commit "13769fb603ae88c64566529eae4525ce88026e86" :authors '(("Peter Stiernström" . "peterNOSPAMstiernstrom.se")) :maintainers '(("Peter Stiernström" . "peterNOSPAMstiernstrom.se")) :maintainer '("Peter Stiernström" . "peterNOSPAMstiernstrom.se") :keywords '("vc") :url "https://gitlab.com/pidu/git-timemachine")
+++ /dev/null
-;;; git-timemachine.el --- Walk through git revisions of a file
-
-;; Copyright (C) 2014 Peter Stiernström
-
-;; Author: Peter Stiernström <peterNOSPAMstiernstrom.se>
-;; Version: 4.12
-;; Package-Version: 20230413.620
-;; Package-Commit: 13769fb603ae88c64566529eae4525ce88026e86
-;; URL: https://gitlab.com/pidu/git-timemachine
-;; Keywords: vc
-;; Package-Requires: ((emacs "24.3") (transient "0.1.0"))
-
-;; This file is not part of GNU Emacs
-
-;; This program is free software: you can redistribute it and/or modify
-;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation, either version 3 of the License, or
-;; (at your option) any later version.
-
-;; This program is distributed in the hope that it will be useful,
-;; but WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-;; GNU General Public License for more details.
-
-;; You should have received a copy of the GNU General Public License
-;; along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-;;; Commentary:
-
-;;; Use git-timemachine to browse historic versions of a file with p
-;;; (previous) and n (next).
-
-;;; Code:
-
-(require 'vc-git)
-(require 'cl-lib)
-(require 'transient)
-
-(defgroup git-timemachine nil
- "Walk through git revisions of a file."
- :link '(url-link "https://codeberg.org/pidu/git-timemachine")
- :group 'tools)
-
-(defcustom git-timemachine-abbreviation-length 12
- "Number of chars from the full sha1 hash to use for abbreviation."
- :type 'integer
- :group 'git-timemachine)
-
-(defcustom git-timemachine-show-minibuffer-details t
- "Non-nil means that details of the commit (its hash and date)
-will be shown in the minibuffer while navigating commits."
- :type 'boolean
- :group 'git-timemachine)
-
-(defface git-timemachine-commit
- '((default :weight bold))
- "Face for git timemachine commit sha."
- :group 'git-timemachine)
-
-(defface git-timemachine-minibuffer-detail-face
- '((((class color) (background dark))
- :foreground "yellow")
- (((class color) (background light))
- :foreground "yellow4"))
- "How to display the minibuffer detail."
- :group 'git-timemachine)
-
-(defface git-timemachine-minibuffer-author-face
- '((((class color) (background dark))
- :foreground "orange")
- (((class color) (background light))
- :foreground "DarkOrange4"))
- "How to display the author in minibuffer."
- :group 'git-timemachine)
-
-(defcustom git-timemachine-minibuffer-detail
- 'subject
- "What to display when `git-timemachine-show-minibuffer-details` is t.
-Available values are:
-`commit` : The SHA hash of the commit
-`subject`: The subject of the commit message"
- :type '(radio (const :tag "Commit SHA" commit) (const :tag "Commit Subject" subject))
- :group 'git-timemachine)
-
-(defcustom git-timemachine-show-author
- t
- "Prepend author to minibuffer details."
- :type 'boolean
- :group 'git-timemachine)
-
-(defcustom git-timemachine-global-git-arguments
- '("-c" "log.showSignature=false" "--no-pager")
- "Common arguments for all git commands."
- :type 'list
- :group 'git-timemachine)
-
-(defcustom git-timemachine-quit-to-invoking-buffer
- t
- "Switch to invoking buffer on `git-timemachine-quit`."
- :type 'boolean
- :group 'git-timemachine)
-
-(defvar-local git-timemachine-directory nil)
-(defvar-local git-timemachine-revision nil)
-(defvar-local git-timemachine-file nil)
-(defvar-local git-timemachine--revisions-cache nil)
-
-(defun git-timemachine--process-file (&rest args)
- "Run `process-file` with ARGS and `git-timemachine-global-git-arguments` applied."
- (apply #'process-file vc-git-program nil t nil (append git-timemachine-global-git-arguments args)))
-
-(defun git-timemachine--revisions (&optional git-branch)
- "List git revisions of current buffers file.
-
-When passed a GIT-BRANCH, lists revisions from that branch."
- (if git-timemachine--revisions-cache
- git-timemachine--revisions-cache
- (setq git-timemachine--revisions-cache
- (prog2
- (message "Fetching Revisions...")
- (let ((default-directory git-timemachine-directory)
- (file git-timemachine-file))
- (with-temp-buffer
-
- (unless (zerop (if git-branch
- (git-timemachine--process-file "log" git-branch "--name-only" "--follow" "--pretty=format:%H%x00%ar%x00%ad%x00%s%x00%an" "--" file)
- (git-timemachine--process-file "log" "--name-only" "--follow" "--pretty=format:%H%x00%ar%x00%ad%x00%s%x00%an" "--" file)))
- (error "Git log command exited with non-zero exit status for file: %s" file))
-
- (goto-char (point-min))
- (let ((lines)
- (commit-number (/ (1+ (count-lines (point-min) (point-max))) 3)))
- (while (not (eobp))
- (let ((line (buffer-substring-no-properties (line-beginning-position) (line-end-position))))
- (string-match "\\([^\0]*\\)\0\\([^\0]*\\)\0\\([^\0]*\\)\0\\(.*\\)\0\\(.*\\)" line)
- (let ((commit (match-string 1 line))
- (date-relative (match-string 2 line))
- (date-full (match-string 3 line))
- (subject (match-string 4 line))
- (author (match-string 5 line)))
- (forward-line 1)
- (let ((file-name (buffer-substring-no-properties (line-beginning-position) (line-end-position))))
- (push (list commit file-name commit-number date-relative date-full subject author) lines))))
- (setq commit-number (1- commit-number))
- (forward-line 2))
- (nreverse lines))))
- (message "Fetching Revisions...done")))))
-
-(defun git-timemachine-show-current-revision ()
- "Show last (current) revision of file."
- (interactive)
- (git-timemachine-show-revision (car (git-timemachine--revisions))))
-
-(defun git-timemachine-show-latest-revision-in-branch (git-branch)
- "Show last (current) revision of file in GIT-BRANCH."
- (interactive "MGit branch: ")
- (git-timemachine-show-revision (car (git-timemachine--revisions git-branch))))
-
-(defun git-timemachine--next-revision (revisions)
- "Return the revision following the current revision in REVISIONS."
- (or (cadr (cl-member (car git-timemachine-revision) revisions :key #'car :test #'string=))
- (car (reverse revisions))))
-
-(defun git-timemachine-show-previous-revision ()
- "Show previous revision of file."
- (interactive)
- (let ((new-line nil)
- (curr-revision git-timemachine-revision)
- (new-revision (git-timemachine--next-revision (git-timemachine--revisions)))
- (cursor-win-pos (git-timemachine--get-cursor-position)))
- (setq new-line (git-timemachine--find-new-current-line curr-revision new-revision (line-number-at-pos)))
- (git-timemachine-show-revision new-revision)
- (forward-line (- new-line (line-number-at-pos)))
- (git-timemachine--set-cursor-position cursor-win-pos)))
-
-(defun git-timemachine-show-next-revision ()
- "Show next revision of file."
- (interactive)
- (let ((new-line nil)
- (curr-revision git-timemachine-revision)
- (new-revision (git-timemachine--next-revision (reverse (git-timemachine--revisions))))
- (cursor-win-pos (git-timemachine--get-cursor-position)))
- (setq new-line (git-timemachine--find-new-current-line curr-revision new-revision (line-number-at-pos)))
- (git-timemachine-show-revision new-revision)
- (forward-line (- new-line (line-number-at-pos)))
- (git-timemachine--set-cursor-position cursor-win-pos)))
-
-(defun git-timemachine-show-revision-fuzzy ()
- "Show the revision with the chosen commit message."
- (interactive)
- (let* ((revisions (git-timemachine--revisions))
- (wanted
- (funcall #'completing-read "Commit message: "
- (mapcar (apply-partially #'nth 5) revisions))))
- (git-timemachine-show-revision
- (cl-find wanted revisions
- :key (apply-partially #'nth 5)
- :test #'equal))))
-
-(defun git-timemachine-show-nth-revision (rev-number)
- "Show the REV-NUMBER revision."
- (interactive "nEnter revision number: ")
- (let* ((revisions (reverse (git-timemachine--revisions)))
- (num-revisions (length revisions))
- (curr-revision git-timemachine-revision)
- (new-revision (nth (1- rev-number) revisions))
- (new-line nil)
- (cursor-win-pos (git-timemachine--get-cursor-position)))
- (if (not new-revision)
- (message "Only %d revisions exist." num-revisions)
- (setq new-line (git-timemachine--find-new-current-line curr-revision new-revision (line-number-at-pos)))
- (git-timemachine-show-revision new-revision)
- (forward-line (- new-line (line-number-at-pos)))
- (git-timemachine--set-cursor-position cursor-win-pos))))
-
-(defun git-timemachine-show-revision (revision)
- "Show a REVISION (commit hash) of the current file."
- (when revision
- (let ((current-position (point))
- (commit (car revision))
- (revision-file-name (nth 1 revision))
- (commit-index (nth 2 revision))
- (date-relative (nth 3 revision))
- (date-full (nth 4 revision))
- (subject (nth 5 revision)))
- (setq buffer-read-only nil)
- (erase-buffer)
- (let ((default-directory git-timemachine-directory)
- (process-coding-system-alist (list (cons "" (cons buffer-file-coding-system default-process-coding-system)))))
- (git-timemachine--process-file "show" (concat commit ":" revision-file-name)))
- (setq buffer-read-only t)
- (set-buffer-modified-p nil)
- (let* ((revisions (git-timemachine--revisions))
- (n-of-m (format "(%d/%d %s)" commit-index (length revisions) date-relative)))
- (setq mode-line-buffer-identification
- (list (propertized-buffer-identification "%12b") "@"
- (propertize (git-timemachine-abbreviate commit) 'face 'git-timemachine-commit) " name:" revision-file-name" " n-of-m)))
- (setq git-timemachine-revision revision)
- (goto-char current-position)
- (when git-timemachine-show-minibuffer-details
- (git-timemachine--show-minibuffer-details revision))
- (git-timemachine--erm-workaround))))
-
-(declare-function erm-reset-buffer "ext:enh-ruby-mode")
-
-(defun git-timemachine--erm-workaround ()
- "Workaround for enhanced ruby mode not detecting revision change."
- (when (eq major-mode 'enh-ruby-mode)
- (ignore-errors (erm-reset-buffer))))
-
-(defun git-timemachine--show-minibuffer-details (revision)
- "Show details for REVISION in minibuffer."
- (let* ((date-relative (nth 3 revision))
- (date-full (nth 4 revision))
- (author (if git-timemachine-show-author (concat (nth 6 revision) ": ") ""))
- (sha-or-subject (if (eq git-timemachine-minibuffer-detail 'commit) (car revision) (nth 5 revision))))
- (message "%s%s [%s (%s)]"
- (propertize author 'face 'git-timemachine-minibuffer-author-face)
- (propertize sha-or-subject 'face 'git-timemachine-minibuffer-detail-face) date-full date-relative)))
-
-(defun git-timemachine--find-new-current-line (curr-revision new-revision current-line)
- "Return the new current line after a revision jump.
-
-Given CURR-REVISION and NEW-REVISION determine if we need to
-updated CURRENT-LINE."
- (let* ((revisions (reverse (git-timemachine--revisions)))
- (current-commit (car curr-revision))
- (curr-rev-number (+ (or (cl-position curr-revision revisions) 0) 1))
- (new-commit (car new-revision))
- (new-rev-number (+ (or (cl-position new-revision revisions) 0) 1))
- (new-line nil)
- (file git-timemachine-file)
- (reverse (< curr-rev-number new-rev-number)))
- ;; If no commit change, do nothing
- (if (= curr-rev-number new-rev-number)
- current-line
- ;; Get new current line number using `git-blame`
- (with-temp-buffer
- (if reverse
- (git-timemachine--process-file "blame" "--reverse" "-n" (format "-L %s,%s" current-line current-line) file (format "%s..%s" current-commit new-commit))
- (git-timemachine--process-file "blame" "-n" (format "-L %s,%s" current-line current-line) file (format "%s..%s" new-commit current-commit)))
- (goto-char (point-min))
- ;; If end-of-buffer problem
- (when (search-forward-regexp "^fatal: file .+ has only .+ lines" nil t)
- (setq current-line (- current-line 1))
- (erase-buffer)
- (if reverse
- (git-timemachine--process-file "blame" "--reverse" "-n" (format "-L %s,%s" current-line current-line) file (format "%s..%s" current-commit new-commit))
- (git-timemachine--process-file "blame" "-n" (format "-L %s,%s" current-line current-line) file (format "%s..%s" new-commit current-commit))))
- (goto-char (point-min))
- (search-forward-regexp "^[^ ]+ \\([^ ]+\\)")
- (setq new-line (string-to-number (match-string 1)))
- ;; In case git blame doesn't give what we expect
- (when (= new-line 0) (setq new-line current-line))
- new-line))))
-
-(defun git-timemachine--get-cursor-position ()
- "Return the cursor visual line number with respect to the current window first line."
- (let* ((win-point-min (save-excursion (move-to-window-line 0) (point)))
- (cur-pos (count-screen-lines win-point-min (point))))
- cur-pos))
-
-(defun git-timemachine--set-cursor-position (POS)
- "Set the cursor position to the POS visual line with respect to the window first line."
- (recenter POS))
-
-(defun git-timemachine-abbreviate (revision)
- "Return REVISION abbreviated to `git-timemachine-abbreviation-length' chars."
- (substring revision 0 git-timemachine-abbreviation-length))
-
-(defun git-timemachine-quit ()
- "Exit the timemachine."
- (interactive)
- (let ((parent-buffer-name buffer-file-name))
- (kill-buffer)
- (let ((parent-buffer (find-buffer-visiting parent-buffer-name)))
- (when (and parent-buffer git-timemachine-quit-to-invoking-buffer)
- (switch-to-buffer parent-buffer nil t)))))
-
-(defun git-timemachine-blame ()
- "Call `magit-blame` on current revision."
- (interactive)
- (if (fboundp 'magit-blame)
- (let ((magit-buffer-revision (car git-timemachine-revision)))
- (magit-blame))
- (message "You need to install magit for blame capabilities")))
-
-(defun git-timemachine-kill-revision ()
- "Kill the current revisions abbreviated commit hash."
- (interactive)
- (let ((revision (car git-timemachine-revision)))
- (message revision)
- (kill-new revision)))
-
-(defun git-timemachine-kill-abbreviated-revision ()
- "Kill the current revisions full commit hash."
- (interactive)
- (let ((revision (git-timemachine-abbreviate (car git-timemachine-revision))))
- (message revision)
- (kill-new revision)))
-
-(defun git-timemachine-show-commit ()
- "Show commit for current revision."
- (interactive)
- (let ((rev (car git-timemachine-revision)))
- (if (fboundp 'magit-show-commit)
- (magit-show-commit rev)
- (message "You need to install magit to show commit"))))
-
-(transient-define-prefix git-timemachine-help ()
- "Show online help."
- ["Navigate"
- [("p" "show previous revision" git-timemachine-show-previous-revision)
- ("n" "show next revision" git-timemachine-show-next-revision)
- ("g" "show nth revision" git-timemachine-show-nth-revision)
- ("t" "show fuzzy revision" git-timemachine-show-revision-fuzzy)]]
- ["Kill current revision"
- [("w" "kill abbreviated revision" git-timemachine-kill-abbreviated-revision)
- ("W" "kill revision" git-timemachine-kill-revision)]]
- ["Misc"
- [("b" "blame current revision" git-timemachine-blame)
- ("c" "show commit" git-timemachine-show-commit)
- ("?" "show help" git-timemachine-help)
- ("q" "quit" git-timemachine-quit)]])
-
-(define-minor-mode git-timemachine-mode
- "Git Timemachine, feel the wings of history."
- :init-value nil
- :lighter " Timemachine"
- :keymap
- '(("p" . git-timemachine-show-previous-revision)
- ("n" . git-timemachine-show-next-revision)
- ("g" . git-timemachine-show-nth-revision)
- ("t" . git-timemachine-show-revision-fuzzy)
- ("q" . git-timemachine-quit)
- ("w" . git-timemachine-kill-abbreviated-revision)
- ("W" . git-timemachine-kill-revision)
- ("b" . git-timemachine-blame)
- ("c" . git-timemachine-show-commit)
- ("?" . git-timemachine-help))
- :group 'git-timemachine)
-
-(defun git-timemachine-validate (file)
- "Validate that there is a FILE and that it belongs to a git repository.
-Call with the value of 'buffer-file-name."
- (unless file
- (error "This buffer is not visiting a file"))
- (unless (vc-git-registered file)
- (error "This file is not git tracked")))
-
-(defun git-timemachine--start (get-revision-fn)
- "Setup a timemachine buffer and populate it from the result of GET-REVISION-FN."
- (setq git-timemachine--revisions-cache nil)
- (git-timemachine-validate (buffer-file-name))
- (let ((git-directory (expand-file-name (vc-git-root (buffer-file-name))))
- (file-name (buffer-file-name))
- (timemachine-buffer (format "timemachine:%s" (buffer-name)))
- (cur-line (line-number-at-pos))
- (cursor-position (git-timemachine--get-cursor-position))
- (new-line nil)
- (mode major-mode)
- (coding-system buffer-file-coding-system))
- (with-current-buffer (get-buffer-create timemachine-buffer)
- (switch-to-buffer timemachine-buffer)
- (setq buffer-file-name file-name)
- (setq buffer-file-coding-system coding-system)
- (delay-mode-hooks (funcall mode))
- (setq git-timemachine-directory git-directory
- git-timemachine-file (file-relative-name file-name git-directory)
- git-timemachine-revision nil)
- (funcall get-revision-fn)
- (setq new-line (git-timemachine--find-new-current-line git-timemachine-revision (list "HEAD" "" 0 "" "" "" "") cur-line)) ;; Allow to stay on the same line
- (goto-char (point-min))
- (forward-line (- new-line 1))
- (git-timemachine--set-cursor-position cursor-position)
- (git-timemachine-mode))))
-
-;;;###autoload
-(defun git-timemachine-toggle ()
- "Toggle git timemachine mode."
- (interactive)
- (if (bound-and-true-p git-timemachine-mode)
- (git-timemachine-quit)
- (git-timemachine)))
-
-;;;###autoload
-(defun git-timemachine ()
- "Enable git timemachine for file of current buffer."
- (interactive)
- (git-timemachine--start #'git-timemachine-show-current-revision))
-
-;;;###autoload
-(defun git-timemachine-switch-branch (git-branch)
- "Enable git timemachine for current buffer, switching to GIT-BRANCH."
- (interactive (list (completing-read "Branch to switch to: "(vc-git-branches))))
- (git-timemachine--start (lambda () (git-timemachine-show-latest-revision-in-branch git-branch))))
-
-(provide 'git-timemachine)
-
-;;; git-timemachine.el ends here
--- /dev/null
+;;; git-timemachine-autoloads.el --- automatically extracted autoloads -*- lexical-binding: t -*-
+;;
+;;; Code:
+
+(add-to-list 'load-path (directory-file-name
+ (or (file-name-directory #$) (car load-path))))
+
+\f
+;;;### (autoloads nil "git-timemachine" "git-timemachine.el" (0 0
+;;;;;; 0 0))
+;;; Generated autoloads from git-timemachine.el
+
+(autoload 'git-timemachine-toggle "git-timemachine" "\
+Toggle git timemachine mode." t nil)
+
+(autoload 'git-timemachine "git-timemachine" "\
+Enable git timemachine for file of current buffer." t nil)
+
+(autoload 'git-timemachine-switch-branch "git-timemachine" "\
+Enable git timemachine for current buffer, switching to GIT-BRANCH.
+
+\(fn GIT-BRANCH)" t nil)
+
+(register-definition-prefixes "git-timemachine" '("git-timemachine-"))
+
+;;;***
+\f
+;;;### (autoloads nil nil ("git-timemachine-pkg.el") (0 0 0 0))
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; coding: utf-8
+;; End:
+;;; git-timemachine-autoloads.el ends here
--- /dev/null
+(define-package "git-timemachine" "20230630.1214" "Walk through git revisions of a file"
+ '((emacs "24.3")
+ (transient "0.1.0"))
+ :commit "ac933e5cd29583c131401f3bd991d98129c316df" :authors
+ '(("Peter Stiernström" . "peterNOSPAMstiernstrom.se"))
+ :maintainers
+ '(("Peter Stiernström" . "peterNOSPAMstiernstrom.se"))
+ :maintainer
+ '("Peter Stiernström" . "peterNOSPAMstiernstrom.se")
+ :keywords
+ '("vc")
+ :url "https://gitlab.com/pidu/git-timemachine")
+;; Local Variables:
+;; no-byte-compile: t
+;; End:
--- /dev/null
+;;; git-timemachine.el --- Walk through git revisions of a file
+
+;; Copyright (C) 2014 Peter Stiernström
+
+;; Author: Peter Stiernström <peterNOSPAMstiernstrom.se>
+;; Version: 4.13
+;; URL: https://gitlab.com/pidu/git-timemachine
+;; Keywords: vc
+;; Package-Requires: ((emacs "24.3") (transient "0.1.0"))
+
+;; This file is not part of GNU Emacs
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; Use git-timemachine to browse historic versions of a file with p
+;;; (previous) and n (next).
+
+;;; Code:
+
+(require 'vc-git)
+(require 'cl-lib)
+(require 'transient)
+
+(defgroup git-timemachine nil
+ "Walk through git revisions of a file."
+ :link '(url-link "https://codeberg.org/pidu/git-timemachine")
+ :group 'tools)
+
+(defcustom git-timemachine-abbreviation-length 12
+ "Number of chars from the full sha1 hash to use for abbreviation."
+ :type 'integer
+ :group 'git-timemachine)
+
+(defcustom git-timemachine-show-minibuffer-details t
+ "Non-nil means that details of the commit (its hash and date)
+will be shown in the minibuffer while navigating commits."
+ :type 'boolean
+ :group 'git-timemachine)
+
+(defface git-timemachine-commit
+ '((default :weight bold))
+ "Face for git timemachine commit sha."
+ :group 'git-timemachine)
+
+(defface git-timemachine-minibuffer-detail-face
+ '((((class color) (background dark))
+ :foreground "yellow")
+ (((class color) (background light))
+ :foreground "yellow4"))
+ "How to display the minibuffer detail."
+ :group 'git-timemachine)
+
+(defface git-timemachine-minibuffer-author-face
+ '((((class color) (background dark))
+ :foreground "orange")
+ (((class color) (background light))
+ :foreground "DarkOrange4"))
+ "How to display the author in minibuffer."
+ :group 'git-timemachine)
+
+(defcustom git-timemachine-minibuffer-detail
+ 'subject
+ "What to display when `git-timemachine-show-minibuffer-details` is t.
+Available values are:
+`commit` : The SHA hash of the commit
+`subject`: The subject of the commit message"
+ :type '(radio (const :tag "Commit SHA" commit) (const :tag "Commit Subject" subject))
+ :group 'git-timemachine)
+
+(defcustom git-timemachine-show-author
+ t
+ "Prepend author to minibuffer details."
+ :type 'boolean
+ :group 'git-timemachine)
+
+(defcustom git-timemachine-global-git-arguments
+ '("-c" "log.showSignature=false" "--no-pager")
+ "Common arguments for all git commands."
+ :type 'list
+ :group 'git-timemachine)
+
+(defcustom git-timemachine-quit-to-invoking-buffer
+ t
+ "Switch to invoking buffer on `git-timemachine-quit`."
+ :type 'boolean
+ :group 'git-timemachine)
+
+(defvar-local git-timemachine-directory nil)
+(defvar-local git-timemachine-revision nil)
+(defvar-local git-timemachine-file nil)
+(defvar-local git-timemachine--revisions-cache nil)
+
+(defun git-timemachine--process-file (&rest args)
+ "Run `process-file` with ARGS and `git-timemachine-global-git-arguments` applied."
+ (apply #'process-file vc-git-program nil t nil (append git-timemachine-global-git-arguments args)))
+
+(defun git-timemachine--revisions (&optional git-branch)
+ "List git revisions of current buffers file.
+
+When passed a GIT-BRANCH, lists revisions from that branch."
+ (if git-timemachine--revisions-cache
+ git-timemachine--revisions-cache
+ (setq git-timemachine--revisions-cache
+ (prog2
+ (message "Fetching Revisions...")
+ (let ((default-directory git-timemachine-directory)
+ (file git-timemachine-file))
+ (with-temp-buffer
+
+ (unless (zerop (if git-branch
+ (git-timemachine--process-file "log" git-branch "--name-only" "--follow" "--pretty=format:%H%x00%ar%x00%ad%x00%s%x00%an" "--" file)
+ (git-timemachine--process-file "log" "--name-only" "--follow" "--pretty=format:%H%x00%ar%x00%ad%x00%s%x00%an" "--" file)))
+ (error "Git log command exited with non-zero exit status for file: %s" file))
+
+ (goto-char (point-min))
+ (let ((lines)
+ (commit-number (/ (1+ (count-lines (point-min) (point-max))) 3)))
+ (while (not (eobp))
+ (let ((line (buffer-substring-no-properties (line-beginning-position) (line-end-position))))
+ (string-match "\\([^\0]*\\)\0\\([^\0]*\\)\0\\([^\0]*\\)\0\\(.*\\)\0\\(.*\\)" line)
+ (let ((commit (match-string 1 line))
+ (date-relative (match-string 2 line))
+ (date-full (match-string 3 line))
+ (subject (match-string 4 line))
+ (author (match-string 5 line)))
+ (forward-line 1)
+ (let ((file-name (buffer-substring-no-properties (line-beginning-position) (line-end-position))))
+ (push (list commit file-name commit-number date-relative date-full subject author) lines))))
+ (setq commit-number (1- commit-number))
+ (forward-line 2))
+ (nreverse lines))))
+ (message "Fetching Revisions...done")))))
+
+(defun git-timemachine-show-current-revision ()
+ "Show last (current) revision of file."
+ (interactive)
+ (git-timemachine-show-revision (car (git-timemachine--revisions))))
+
+(defun git-timemachine-show-latest-revision-in-branch (git-branch)
+ "Show last (current) revision of file in GIT-BRANCH."
+ (interactive "MGit branch: ")
+ (git-timemachine-show-revision (car (git-timemachine--revisions git-branch))))
+
+(defun git-timemachine--next-revision (revisions)
+ "Return the revision following the current revision in REVISIONS."
+ (or (cadr (cl-member (car git-timemachine-revision) revisions :key #'car :test #'string=))
+ (car (reverse revisions))))
+
+(defun git-timemachine-show-previous-revision ()
+ "Show previous revision of file."
+ (interactive)
+ (let ((new-line nil)
+ (curr-revision git-timemachine-revision)
+ (new-revision (git-timemachine--next-revision (git-timemachine--revisions)))
+ (cursor-win-pos (git-timemachine--get-cursor-position)))
+ (setq new-line (git-timemachine--find-new-current-line curr-revision new-revision (line-number-at-pos)))
+ (git-timemachine-show-revision new-revision)
+ (forward-line (- new-line (line-number-at-pos)))
+ (git-timemachine--set-cursor-position cursor-win-pos)))
+
+(defun git-timemachine-show-next-revision ()
+ "Show next revision of file."
+ (interactive)
+ (let ((new-line nil)
+ (curr-revision git-timemachine-revision)
+ (new-revision (git-timemachine--next-revision (reverse (git-timemachine--revisions))))
+ (cursor-win-pos (git-timemachine--get-cursor-position)))
+ (setq new-line (git-timemachine--find-new-current-line curr-revision new-revision (line-number-at-pos)))
+ (git-timemachine-show-revision new-revision)
+ (forward-line (- new-line (line-number-at-pos)))
+ (git-timemachine--set-cursor-position cursor-win-pos)))
+
+(defun git-timemachine-show-revision-fuzzy ()
+ "Show the revision with the chosen commit message."
+ (interactive)
+ (let* ((revisions (git-timemachine--revisions))
+ (wanted
+ (funcall #'completing-read "Commit message: "
+ (mapcar (apply-partially #'nth 5) revisions))))
+ (git-timemachine-show-revision
+ (cl-find wanted revisions
+ :key (apply-partially #'nth 5)
+ :test #'equal))))
+
+(defun git-timemachine-show-nth-revision (rev-number)
+ "Show the REV-NUMBER revision."
+ (interactive "nEnter revision number: ")
+ (let* ((revisions (reverse (git-timemachine--revisions)))
+ (num-revisions (length revisions))
+ (curr-revision git-timemachine-revision)
+ (new-revision (nth (1- rev-number) revisions))
+ (new-line nil)
+ (cursor-win-pos (git-timemachine--get-cursor-position)))
+ (if (not new-revision)
+ (message "Only %d revisions exist." num-revisions)
+ (setq new-line (git-timemachine--find-new-current-line curr-revision new-revision (line-number-at-pos)))
+ (git-timemachine-show-revision new-revision)
+ (forward-line (- new-line (line-number-at-pos)))
+ (git-timemachine--set-cursor-position cursor-win-pos))))
+
+(defun git-timemachine-show-revision (revision)
+ "Show a REVISION (commit hash) of the current file."
+ (when revision
+ (let ((current-position (point))
+ (commit (car revision))
+ (revision-file-name (nth 1 revision))
+ (commit-index (nth 2 revision))
+ (date-relative (nth 3 revision))
+ (date-full (nth 4 revision))
+ (subject (nth 5 revision)))
+ (setq buffer-read-only nil)
+ (erase-buffer)
+ (let ((default-directory git-timemachine-directory)
+ (process-coding-system-alist (list (cons "" (cons buffer-file-coding-system default-process-coding-system)))))
+ (git-timemachine--process-file "show" (concat commit ":" revision-file-name)))
+ (setq buffer-read-only t)
+ (set-buffer-modified-p nil)
+ (let* ((revisions (git-timemachine--revisions))
+ (n-of-m (format "(%d/%d %s)" commit-index (length revisions) date-relative)))
+ (setq mode-line-buffer-identification
+ (list (propertized-buffer-identification "%12b") "@"
+ (propertize (git-timemachine-abbreviate commit) 'face 'git-timemachine-commit) " name:" revision-file-name" " n-of-m)))
+ (setq git-timemachine-revision revision)
+ (goto-char current-position)
+ (when git-timemachine-show-minibuffer-details
+ (git-timemachine--show-minibuffer-details revision))
+ (git-timemachine--erm-workaround))))
+
+(declare-function erm-reset-buffer "ext:enh-ruby-mode")
+
+(defun git-timemachine--erm-workaround ()
+ "Workaround for enhanced ruby mode not detecting revision change."
+ (when (eq major-mode 'enh-ruby-mode)
+ (ignore-errors (erm-reset-buffer))))
+
+(defun git-timemachine--show-minibuffer-details (revision)
+ "Show details for REVISION in minibuffer."
+ (let* ((date-relative (nth 3 revision))
+ (date-full (nth 4 revision))
+ (author (if git-timemachine-show-author (concat (nth 6 revision) ": ") ""))
+ (sha-or-subject (if (eq git-timemachine-minibuffer-detail 'commit) (car revision) (nth 5 revision))))
+ (message "%s%s [%s (%s)]"
+ (propertize author 'face 'git-timemachine-minibuffer-author-face)
+ (propertize sha-or-subject 'face 'git-timemachine-minibuffer-detail-face) date-full date-relative)))
+
+(defun git-timemachine--find-new-current-line (curr-revision new-revision current-line)
+ "Return the new current line after a revision jump.
+
+Given CURR-REVISION and NEW-REVISION determine if we need to
+updated CURRENT-LINE."
+ (let* ((revisions (reverse (git-timemachine--revisions)))
+ (current-commit (car curr-revision))
+ (curr-rev-number (+ (or (cl-position curr-revision revisions) 0) 1))
+ (new-commit (car new-revision))
+ (new-rev-number (+ (or (cl-position new-revision revisions) 0) 1))
+ (new-line nil)
+ (file git-timemachine-file)
+ (reverse (< curr-rev-number new-rev-number)))
+ ;; If no commit change, do nothing
+ (if (= curr-rev-number new-rev-number)
+ current-line
+ ;; Get new current line number using `git-blame`
+ (with-temp-buffer
+ (if reverse
+ (git-timemachine--process-file "blame" "--reverse" "-n" (format "-L %s,%s" current-line current-line) file (format "%s..%s" current-commit new-commit))
+ (git-timemachine--process-file "blame" "-n" (format "-L %s,%s" current-line current-line) file (format "%s..%s" new-commit current-commit)))
+ (goto-char (point-min))
+ ;; If end-of-buffer problem
+ (when (search-forward-regexp "^fatal: file .+ has only .+ lines" nil t)
+ (setq current-line (- current-line 1))
+ (erase-buffer)
+ (if reverse
+ (git-timemachine--process-file "blame" "--reverse" "-n" (format "-L %s,%s" current-line current-line) file (format "%s..%s" current-commit new-commit))
+ (git-timemachine--process-file "blame" "-n" (format "-L %s,%s" current-line current-line) file (format "%s..%s" new-commit current-commit))))
+ (goto-char (point-min))
+ (search-forward-regexp "^[^ ]+ \\([^ ]+\\)")
+ (setq new-line (string-to-number (match-string 1)))
+ ;; In case git blame doesn't give what we expect
+ (when (= new-line 0) (setq new-line current-line))
+ new-line))))
+
+(defun git-timemachine--get-cursor-position ()
+ "Return the cursor visual line number with respect to the current window first line."
+ (let* ((win-point-min (save-excursion (move-to-window-line 0) (point)))
+ (cur-pos (count-screen-lines win-point-min (point))))
+ cur-pos))
+
+(defun git-timemachine--set-cursor-position (POS)
+ "Set the cursor position to the POS visual line with respect to the window first line."
+ (recenter POS))
+
+(defun git-timemachine-abbreviate (revision)
+ "Return REVISION abbreviated to `git-timemachine-abbreviation-length' chars."
+ (substring revision 0 git-timemachine-abbreviation-length))
+
+(defun git-timemachine-quit ()
+ "Exit the timemachine."
+ (interactive)
+ (let ((parent-buffer-name buffer-file-name))
+ (kill-buffer)
+ (let ((parent-buffer (find-buffer-visiting parent-buffer-name)))
+ (when (and parent-buffer git-timemachine-quit-to-invoking-buffer)
+ (switch-to-buffer parent-buffer nil t)))))
+
+(defun git-timemachine-blame ()
+ "Call `magit-blame` on current revision."
+ (interactive)
+ (if (fboundp 'magit-blame)
+ (let ((magit-buffer-revision (car git-timemachine-revision)))
+ (magit-blame))
+ (message "You need to install magit for blame capabilities")))
+
+(defun git-timemachine-kill-revision ()
+ "Kill the current revision's full commit hash."
+ (interactive)
+ (let ((revision (car git-timemachine-revision)))
+ (message revision)
+ (kill-new revision)))
+
+(defun git-timemachine-kill-abbreviated-revision ()
+ "Kill the current revision's abbreviated commit hash."
+ (interactive)
+ (let ((revision (git-timemachine-abbreviate (car git-timemachine-revision))))
+ (message revision)
+ (kill-new revision)))
+
+(defun git-timemachine-show-commit ()
+ "Show commit for current revision."
+ (interactive)
+ (let ((rev (car git-timemachine-revision)))
+ (if (fboundp 'magit-show-commit)
+ (magit-show-commit rev)
+ (message "You need to install magit to show commit"))))
+
+(transient-define-prefix git-timemachine-help ()
+ "Show online help."
+ ["Navigate"
+ [("p" "show previous revision" git-timemachine-show-previous-revision)
+ ("n" "show next revision" git-timemachine-show-next-revision)
+ ("g" "show nth revision" git-timemachine-show-nth-revision)
+ ("t" "show fuzzy revision" git-timemachine-show-revision-fuzzy)]]
+ ["Kill current revision"
+ [("w" "kill abbreviated revision" git-timemachine-kill-abbreviated-revision)
+ ("W" "kill revision" git-timemachine-kill-revision)]]
+ ["Misc"
+ [("b" "blame current revision" git-timemachine-blame)
+ ("c" "show commit" git-timemachine-show-commit)
+ ("?" "show help" git-timemachine-help)
+ ("q" "quit" git-timemachine-quit)]])
+
+(define-minor-mode git-timemachine-mode
+ "Git Timemachine, feel the wings of history."
+ :init-value nil
+ :lighter " Timemachine"
+ :keymap
+ '(("p" . git-timemachine-show-previous-revision)
+ ("n" . git-timemachine-show-next-revision)
+ ("g" . git-timemachine-show-nth-revision)
+ ("t" . git-timemachine-show-revision-fuzzy)
+ ("q" . git-timemachine-quit)
+ ("w" . git-timemachine-kill-abbreviated-revision)
+ ("W" . git-timemachine-kill-revision)
+ ("b" . git-timemachine-blame)
+ ("c" . git-timemachine-show-commit)
+ ("?" . git-timemachine-help))
+ :group 'git-timemachine)
+
+(defun git-timemachine-validate (file)
+ "Validate that there is a FILE and that it belongs to a git repository.
+Call with the value of 'buffer-file-name."
+ (unless file
+ (error "This buffer is not visiting a file"))
+ (unless (vc-git-registered file)
+ (error "This file is not git tracked")))
+
+(defun git-timemachine--start (get-revision-fn)
+ "Setup a timemachine buffer and populate it from the result of GET-REVISION-FN."
+ (setq git-timemachine--revisions-cache nil)
+ (git-timemachine-validate (buffer-file-name))
+ (let ((git-directory (expand-file-name (vc-git-root (buffer-file-name))))
+ (file-name (buffer-file-name))
+ (timemachine-buffer (format "timemachine:%s" (buffer-name)))
+ (cur-line (line-number-at-pos))
+ (cursor-position (git-timemachine--get-cursor-position))
+ (new-line nil)
+ (mode major-mode)
+ (coding-system buffer-file-coding-system))
+ (with-current-buffer (get-buffer-create timemachine-buffer)
+ (switch-to-buffer timemachine-buffer)
+ (setq buffer-file-name file-name)
+ (setq buffer-file-coding-system coding-system)
+ (delay-mode-hooks (funcall mode))
+ (setq git-timemachine-directory git-directory
+ git-timemachine-file (file-relative-name file-name git-directory)
+ git-timemachine-revision nil)
+ (funcall get-revision-fn)
+ (setq new-line (git-timemachine--find-new-current-line git-timemachine-revision (list "HEAD" "" 0 "" "" "" "") cur-line)) ;; Allow to stay on the same line
+ (goto-char (point-min))
+ (forward-line (- new-line 1))
+ (git-timemachine--set-cursor-position cursor-position)
+ (git-timemachine-mode))))
+
+;;;###autoload
+(defun git-timemachine-toggle ()
+ "Toggle git timemachine mode."
+ (interactive)
+ (if (bound-and-true-p git-timemachine-mode)
+ (git-timemachine-quit)
+ (git-timemachine)))
+
+;;;###autoload
+(defun git-timemachine ()
+ "Enable git timemachine for file of current buffer."
+ (interactive)
+ (git-timemachine--start #'git-timemachine-show-current-revision))
+
+;;;###autoload
+(defun git-timemachine-switch-branch (git-branch)
+ "Enable git timemachine for current buffer, switching to GIT-BRANCH."
+ (interactive (list (completing-read "Branch to switch to: "(vc-git-branches))))
+ (git-timemachine--start (lambda () (git-timemachine-show-latest-revision-in-branch git-branch))))
+
+(provide 'git-timemachine)
+
+;;; git-timemachine.el ends here