--------
[verse]
'git-checkout-index' [-u] [-q] [-a] [-f] [-n] [--prefix=<string>]
- [--stage=<number>] [--] <file>...
+ [--stage=<number>]
+ [-z] [--stdin]
+ [--] [<file>]\*
DESCRIPTION
-----------
Instead of checking out unmerged entries, copy out the
files from named stage. <number> must be between 1 and 3.
+--stdin::
+ Instead of taking list of paths from the command line,
+ read list of paths from the standard input. Paths are
+ separated by LF (i.e. one path per line) by default.
+
+-z::
+ Only meaningful with `--stdin`; paths are separated with
+ NUL character instead of LF.
+
--::
Do not interpret any more arguments as options.
which will force all existing `*.h` files to be replaced with their
cached copies. If an empty command line implied "all", then this would
-force-refresh everything in the index, which was not the point.
+force-refresh everything in the index, which was not the point. But
+since git-checkout-index accepts --stdin it would be faster to use:
+
+----------------
+$ find . -name '*.h' -print0 | git-checkout-index -f -z --stdin
+----------------
The `--` is just a good idea when you know the rest will be filenames;
it will prevent problems with a filename of, for example, `-a`.
-------------------------------------
shows the difference between that previous state and the state two
-commits ago. Also, HEAD~5 can be used as a shorthand for HEAD^^^^^,
+commits ago. Also, HEAD~5 can be used as a shorthand for HEAD{caret}{caret}{caret}{caret}{caret},
and more generally HEAD~n can refer to the nth previous commit.
Commits representing merges have more than one parent, and you can
specify which parent to follow in that case; see
*
* find . -name '*.h' -print0 | xargs -0 git-checkout-index -f --
*
+ * or:
+ *
+ * find . -name '*.h' -print0 | git-checkout-index -f -z --stdin
+ *
* which will force all existing *.h files to be replaced with
* their cached copies. If an empty command line implied "all",
* then this would force-refresh everything in the cache, which
* but get used to it in scripting!).
*/
#include "cache.h"
+#include "strbuf.h"
+#include "quote.h"
static const char *prefix;
static int prefix_length;
int i;
int newfd = -1;
int all = 0;
+ int read_from_stdin = 0;
+ int line_termination = '\n';
prefix = setup_git_directory();
git_config(git_default_config);
die("cannot open index.lock file.");
continue;
}
+ if (!strcmp(arg, "-z")) {
+ line_termination = 0;
+ continue;
+ }
+ if (!strcmp(arg, "--stdin")) {
+ if (i != argc - 1)
+ die("--stdin must be at the end");
+ read_from_stdin = 1;
+ i++; /* do not consider arg as a file name */
+ break;
+ }
if (!strncmp(arg, "--prefix=", 9)) {
state.base_dir = arg+9;
state.base_dir_len = strlen(state.base_dir);
if (all)
die("git-checkout-index: don't mix '--all' and explicit filenames");
+ if (read_from_stdin)
+ die("git-checkout-index: don't mix '--stdin' and explicit filenames");
checkout_file(prefix_path(prefix, prefix_length, arg));
}
+ if (read_from_stdin) {
+ struct strbuf buf;
+ if (all)
+ die("git-checkout-index: don't mix '--all' and '--stdin'");
+ strbuf_init(&buf);
+ while (1) {
+ char *path_name;
+ read_line(&buf, stdin, line_termination);
+ if (buf.eof)
+ break;
+ if (line_termination && buf.buf[0] == '"')
+ path_name = unquote_c_style(buf.buf, NULL);
+ else
+ path_name = buf.buf;
+ checkout_file(prefix_path(prefix, prefix_length, path_name));
+ if (path_name != buf.buf)
+ free(path_name);
+ }
+ }
+
if (all)
checkout_all();
;; - hook into file save (after-save-hook)
;; - diff against other branch
;; - renaming files from the status buffer
-;; - support for appending signed-off-by
;; - creating tags
;; - fetch/pull
;; - switching branches
;; - revlist browser
;; - git-show-branch browser
-;; - customize support
;; - menus
;;
(require 'ewoc)
-;;;; Faces
+;;;; Customizations
;;;; ------------------------------------------------------------
+(defgroup git nil
+ "Git user interface")
+
+(defcustom git-committer-name nil
+ "User name to use for commits.
+The default is to fall back to `add-log-full-name' and then `user-full-name'."
+ :group 'git
+ :type '(choice (const :tag "Default" nil)
+ (string :tag "Name")))
+
+(defcustom git-committer-email nil
+ "Email address to use for commits.
+The default is to fall back to `add-log-mailing-address' and then `user-mail-address'."
+ :group 'git
+ :type '(choice (const :tag "Default" nil)
+ (string :tag "Email")))
+
+(defcustom git-commits-coding-system 'utf-8
+ "Default coding system for the log message of git commits."
+ :group 'git
+ :type 'coding-system)
+
+(defcustom git-append-signed-off-by nil
+ "Whether to append a Signed-off-by line to the commit message before editing."
+ :group 'git
+ :type 'boolean)
+
+(defcustom git-per-dir-ignore-file ".gitignore"
+ "Name of the per-directory ignore file."
+ :group 'git
+ :type 'string)
+
(defface git-status-face
'((((class color) (background light)) (:foreground "purple")))
- "Git mode face used to highlight added and modified files.")
+ "Git mode face used to highlight added and modified files."
+ :group 'git)
(defface git-unmerged-face
'((((class color) (background light)) (:foreground "red" :bold t)))
- "Git mode face used to highlight unmerged files.")
+ "Git mode face used to highlight unmerged files."
+ :group 'git)
(defface git-unknown-face
'((((class color) (background light)) (:foreground "goldenrod" :bold t)))
- "Git mode face used to highlight unknown files.")
+ "Git mode face used to highlight unknown files."
+ :group 'git)
(defface git-uptodate-face
'((((class color) (background light)) (:foreground "grey60")))
- "Git mode face used to highlight up-to-date files.")
+ "Git mode face used to highlight up-to-date files."
+ :group 'git)
(defface git-ignored-face
'((((class color) (background light)) (:foreground "grey60")))
- "Git mode face used to highlight ignored files.")
+ "Git mode face used to highlight ignored files."
+ :group 'git)
(defface git-mark-face
'((((class color) (background light)) (:foreground "red" :bold t)))
- "Git mode face used for the file marks.")
+ "Git mode face used for the file marks."
+ :group 'git)
(defface git-header-face
'((((class color) (background light)) (:foreground "blue")))
- "Git mode face used for commit headers.")
+ "Git mode face used for commit headers."
+ :group 'git)
(defface git-separator-face
'((((class color) (background light)) (:foreground "brown")))
- "Git mode face used for commit separator.")
+ "Git mode face used for commit separator."
+ :group 'git)
(defface git-permission-face
'((((class color) (background light)) (:foreground "green" :bold t)))
- "Git mode face used for permission changes.")
-
-(defvar git-committer-name nil
- "*User name to use for commits.
-If not set, fall back to `add-log-full-name' and then `user-full-name'.")
-
-(defvar git-committer-email nil
- "*Email address to use for commits.
-If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.")
-
-(defvar git-commits-coding-system 'utf-8
- "Default coding system for git commits.")
-
-(defconst git-log-msg-separator "--- log message follows this line ---")
-
-(defconst git-per-dir-ignore-file ".gitignore"
- "Name of the per-directory ignore file.")
+ "Git mode face used for permission changes."
+ :group 'git)
;;;; Utilities
;;;; ------------------------------------------------------------
+(defconst git-log-msg-separator "--- log message follows this line ---")
+
(defun git-get-env-strings (env)
"Build a list of NAME=VALUE strings from a list of environment strings."
(mapcar (lambda (entry) (concat (car entry) "=" (cdr entry))) env))
"Add a file name to the ignore file in its directory."
(let* ((fullname (expand-file-name file))
(dir (file-name-directory fullname))
- (name (file-name-nondirectory fullname)))
+ (name (file-name-nondirectory fullname))
+ (ignore-name (expand-file-name git-per-dir-ignore-file dir))
+ (created (not (file-exists-p ignore-name))))
(save-window-excursion
- (set-buffer (find-file-noselect (expand-file-name git-per-dir-ignore-file dir)))
+ (set-buffer (find-file-noselect ignore-name))
(goto-char (point-max))
(unless (zerop (current-column)) (insert "\n"))
(insert name "\n")
(sort-lines nil (point-min) (point-max))
- (save-buffer))))
+ (save-buffer))
+ (when created
+ (git-run-command nil nil "update-index" "--info-only" "--add" "--" (file-relative-name ignore-name)))
+ (git-add-status-file (if created 'added 'modified) (file-relative-name ignore-name))))
;;;; Wrappers for basic git commands
(with-current-buffer buffer
(goto-char (point-min))
(if
- (setq log-start (re-search-forward (concat "^" git-log-msg-separator "\n") nil t))
+ (setq log-start (re-search-forward (concat "^" (regexp-quote git-log-msg-separator) "\n") nil t))
(save-restriction
(narrow-to-region (point-min) log-start)
(goto-char (point-min))
(propertize
(if (or (not old-perm)
(not new-perm)
- (eq 0 (logand #O111 (logxor old-perm new-perm))))
+ (eq 0 (logand ?\111 (logxor old-perm new-perm))))
" "
- (if (eq 0 (logand #O111 old-perm)) "+x" "-x"))
+ (if (eq 0 (logand ?\111 old-perm)) "+x" "-x"))
'face 'git-permission-face))
(defun git-fileinfo-prettyprint (info)
(unless git-status (error "Not in git-status buffer."))
(let ((buffer (get-buffer-create "*git-commit*"))
(merge-heads (git-get-merge-heads))
- (dir default-directory))
+ (dir default-directory)
+ (sign-off git-append-signed-off-by))
(with-current-buffer buffer
(when (eq 0 (buffer-size))
(cd dir)
'face 'git-header-face)
(propertize git-log-msg-separator 'face 'git-separator-face)
"\n")
- (when (and merge-heads (file-readable-p ".git/MERGE_MSG"))
- (insert-file-contents ".git/MERGE_MSG"))))
- (log-edit #'git-do-commit nil #'git-log-edit-files buffer)))
+ (cond ((and merge-heads (file-readable-p ".git/MERGE_MSG"))
+ (insert-file-contents ".git/MERGE_MSG"))
+ (sign-off
+ (insert (format "\n\nSigned-off-by: %s <%s>\n"
+ (git-get-committer-name) (git-get-committer-email)))))))
+ (let ((log-edit-font-lock-keywords
+ `(("^\\(Author:\\|Date:\\|Parent:\\|Signed-off-by:\\)\\(.*\\)"
+ (1 font-lock-keyword-face)
+ (2 font-lock-function-name-face))
+ (,(concat "^\\(" (regexp-quote git-log-msg-separator) "\\)$")
+ (1 font-lock-comment-face)))))
+ (log-edit #'git-do-commit nil #'git-log-edit-files buffer))))
(defun git-find-file ()
"Visit the current file in its own buffer."
(define-key map "d" diff-map)
(define-key map "=" 'git-diff-file)
(define-key map "f" 'git-find-file)
- (define-key map [RET] 'git-find-file)
+ (define-key map "\r" 'git-find-file)
(define-key map "g" 'git-refresh-status)
(define-key map "i" 'git-ignore-file)
(define-key map "l" 'git-log-file)
(erase-buffer)
(let ((status (ewoc-create 'git-fileinfo-prettyprint "" "")))
(set (make-local-variable 'git-status) status))
+ (set (make-local-variable 'list-buffers-directory) default-directory)
(run-hooks 'git-status-mode-hook)))
(defun git-status (dir)
(if (file-directory-p (concat (file-name-as-directory dir) ".git"))
(let ((buffer (create-file-buffer (expand-file-name "*git-status*" dir))))
(switch-to-buffer buffer)
- (git-status-mode)
(cd dir)
+ (git-status-mode)
(git-refresh-status)
(goto-char (point-min)))
(message "%s is not a git working tree." dir)))
--- /dev/null
+;;; vc-git.el --- VC backend for the git version control system
+
+;; Copyright (C) 2006 Alexandre Julliard
+
+;; 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 2 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, write to the Free
+;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+;; MA 02111-1307 USA
+
+;;; Commentary:
+
+;; This file contains a VC backend for the git version control
+;; system.
+;;
+;; To install: put this file on the load-path and add GIT to the list
+;; of supported backends in `vc-handled-backends'.
+;;
+;; TODO
+;; - changelog generation
+;; - working with revisions other than HEAD
+;;
+
+(defvar git-commits-coding-system 'utf-8
+ "Default coding system for git commits.")
+
+(defun vc-git--run-command-string (file &rest args)
+ "Run a git command on FILE and return its output as string."
+ (let* ((ok t)
+ (str (with-output-to-string
+ (with-current-buffer standard-output
+ (unless (eq 0 (apply #'call-process "git" nil '(t nil) nil
+ (append args (list (file-relative-name file)))))
+ (setq ok nil))))))
+ (and ok str)))
+
+(defun vc-git--run-command (file &rest args)
+ "Run a git command on FILE, discarding any output."
+ (let ((name (file-relative-name file)))
+ (eq 0 (apply #'call-process "git" nil (get-buffer "*Messages") nil (append args (list name))))))
+
+(defun vc-git-registered (file)
+ "Check whether FILE is registered with git."
+ (with-temp-buffer
+ (let* ((dir (file-name-directory file))
+ (name (file-relative-name file dir)))
+ (when dir (cd dir))
+ (and (eq 0 (call-process "git" nil '(t nil) nil "ls-files" "-c" "-z" "--" name))
+ (let ((str (buffer-string)))
+ (and (> (length str) (length name))
+ (string= (substring str 0 (1+ (length name))) (concat name "\0"))))))))
+
+(defun vc-git-state (file)
+ "git-specific version of `vc-state'."
+ (let ((diff (vc-git--run-command-string file "diff-index" "-z" "HEAD" "--")))
+ (if (and diff (string-match ":[0-7]\\{6\\} [0-7]\\{6\\} [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} [ADMU]\0[^\0]+\0" diff))
+ 'edited
+ 'up-to-date)))
+
+(defun vc-git-workfile-version (file)
+ "git-specific version of `vc-workfile-version'."
+ (let ((str (with-output-to-string
+ (with-current-buffer standard-output
+ (call-process "git" nil '(t nil) nil "symbolic-ref" "HEAD")))))
+ (if (string-match "^\\(refs/heads/\\)?\\(.+\\)$" str)
+ (match-string 2 str)
+ str)))
+
+(defun vc-git-revert (file &optional contents-done)
+ "Revert FILE to the version stored in the git repository."
+ (if contents-done
+ (vc-git--run-command file "update-index" "--")
+ (vc-git--run-command file "checkout" "HEAD")))
+
+(defun vc-git-checkout-model (file)
+ 'implicit)
+
+(defun vc-git-workfile-unchanged-p (file)
+ (let ((sha1 (vc-git--run-command-string file "hash-object" "--"))
+ (head (vc-git--run-command-string file "ls-tree" "-z" "HEAD" "--")))
+ (and head
+ (string-match "[0-7]\\{6\\} blob \\([0-9a-f]\\{40\\}\\)\t[^\0]+\0" head)
+ (string= (car (split-string sha1 "\n")) (match-string 1 head)))))
+
+(defun vc-git-register (file &optional rev comment)
+ "Register FILE into the git version-control system."
+ (vc-git--run-command file "update-index" "--add" "--"))
+
+(defun vc-git-print-log (file)
+ (let ((name (file-relative-name file))
+ (coding-system-for-read git-commits-coding-system))
+ (vc-do-command nil 'async "git" name "rev-list" "--pretty" "HEAD" "--")))
+
+(defun vc-git-diff (file &optional rev1 rev2)
+ (let ((name (file-relative-name file)))
+ (if (and rev1 rev2)
+ (vc-do-command "*vc-diff*" 0 "git" name "diff-tree" "-p" rev1 rev2 "--")
+ (vc-do-command "*vc-diff*" 0 "git" name "diff-index" "-p" (or rev1 "HEAD") "--"))
+ ; git-diff-index doesn't set exit status like diff does
+ (if (vc-git-workfile-unchanged-p file) 0 1)))
+
+(defun vc-git-checkin (file rev comment)
+ (let ((coding-system-for-write git-commits-coding-system))
+ (vc-git--run-command file "commit" "-m" comment "--only" "--")))
+
+(defun vc-git-checkout (file &optional editable rev destfile)
+ (vc-git--run-command file "checkout" (or rev "HEAD")))
+
+(defun vc-git-annotate-command (file buf &optional rev)
+ ; FIXME: rev is ignored
+ (let ((name (file-relative-name file)))
+ (call-process "git" nil buf nil "annotate" name)))
+
+(defun vc-git-annotate-time ()
+ (and (re-search-forward "[0-9a-f]+\t(.*\t\\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\) \\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\) \\([-+0-9]+\\)\t[0-9]+)" nil t)
+ (vc-annotate-convert-time
+ (apply #'encode-time (mapcar (lambda (match) (string-to-number (match-string match))) '(6 5 4 3 2 1 7))))))
+
+;; Not really useful since we can't do anything with the revision yet
+;;(defun vc-annotate-extract-revision-at-line ()
+;; (save-excursion
+;; (move-beginning-of-line 1)
+;; (and (looking-at "[0-9a-f]+")
+;; (buffer-substring (match-beginning 0) (match-end 0)))))
+
+(provide 'vc-git)