Skip to content

Commit 93c22ee

Browse files
julliardgitster
authored andcommitted
git.el: Support for incremental status updates.
When we know which files have been modified, we can now run diff-index or ls-files with a file list to refresh only the specified files instead of the whole project. This also allows proper refreshing of files upon add/delete/resolve, instead of making assumptions about the new file state. Signed-off-by: Alexandre Julliard <julliard@winehq.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 1130845 commit 93c22ee

File tree

1 file changed

+113
-111
lines changed

1 file changed

+113
-111
lines changed

contrib/emacs/git.el

Lines changed: 113 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -314,8 +314,8 @@ and returns the process output as a string."
314314
(sort-lines nil (point-min) (point-max))
315315
(save-buffer))
316316
(when created
317-
(git-run-command nil nil "update-index" "--info-only" "--add" "--" (file-relative-name ignore-name)))
318-
(git-add-status-file (if created 'added 'modified) (file-relative-name ignore-name))))
317+
(git-run-command nil nil "update-index" "--add" "--" (file-relative-name ignore-name)))
318+
(git-update-status-files (list (file-relative-name ignore-name)) 'unknown)))
319319

320320
; propertize definition for XEmacs, stolen from erc-compat
321321
(eval-when-compile
@@ -523,23 +523,39 @@ and returns the process output as a string."
523523
" " (git-escape-file-name (git-fileinfo->name info))
524524
(git-rename-as-string info))))
525525

526-
(defun git-parse-status (status)
527-
"Parse the output of git-diff-index in the current buffer."
528-
(goto-char (point-min))
529-
(while (re-search-forward
530-
":\\([0-7]\\{6\\}\\) \\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} \\(\\([ADMU]\\)\0\\([^\0]+\\)\\|\\([CR]\\)[0-9]*\0\\([^\0]+\\)\0\\([^\0]+\\)\\)\0"
531-
nil t 1)
532-
(let ((old-perm (string-to-number (match-string 1) 8))
533-
(new-perm (string-to-number (match-string 2) 8))
534-
(state (or (match-string 4) (match-string 6)))
535-
(name (or (match-string 5) (match-string 7)))
536-
(new-name (match-string 8)))
537-
(if new-name ; copy or rename
538-
(if (eq ?C (string-to-char state))
539-
(ewoc-enter-last status (git-create-fileinfo 'added new-name old-perm new-perm 'copy name))
540-
(ewoc-enter-last status (git-create-fileinfo 'deleted name 0 0 'rename new-name))
541-
(ewoc-enter-last status (git-create-fileinfo 'added new-name old-perm new-perm 'rename name)))
542-
(ewoc-enter-last status (git-create-fileinfo (git-state-code state) name old-perm new-perm))))))
526+
(defun git-insert-fileinfo (status info &optional refresh)
527+
"Insert INFO in the status buffer, optionally refreshing an existing one."
528+
(let ((node (and refresh
529+
(git-find-status-file status (git-fileinfo->name info)))))
530+
(setf (git-fileinfo->needs-refresh info) t)
531+
(when node ;preserve the marked flag
532+
(setf (git-fileinfo->marked info) (git-fileinfo->marked (ewoc-data node))))
533+
(if node (ewoc-set-data node info) (ewoc-enter-last status info))))
534+
535+
(defun git-run-diff-index (status files)
536+
"Run git-diff-index on FILES and parse the results into STATUS.
537+
Return the list of files that haven't been handled."
538+
(let ((refresh files))
539+
(with-temp-buffer
540+
(apply #'git-run-command t nil "diff-index" "-z" "-M" "HEAD" "--" files)
541+
(goto-char (point-min))
542+
(while (re-search-forward
543+
":\\([0-7]\\{6\\}\\) \\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} \\(\\([ADMU]\\)\0\\([^\0]+\\)\\|\\([CR]\\)[0-9]*\0\\([^\0]+\\)\0\\([^\0]+\\)\\)\0"
544+
nil t 1)
545+
(let ((old-perm (string-to-number (match-string 1) 8))
546+
(new-perm (string-to-number (match-string 2) 8))
547+
(state (or (match-string 4) (match-string 6)))
548+
(name (or (match-string 5) (match-string 7)))
549+
(new-name (match-string 8)))
550+
(if new-name ; copy or rename
551+
(if (eq ?C (string-to-char state))
552+
(git-insert-fileinfo status (git-create-fileinfo 'added new-name old-perm new-perm 'copy name) refresh)
553+
(git-insert-fileinfo status (git-create-fileinfo 'deleted name 0 0 'rename new-name) refresh)
554+
(git-insert-fileinfo status (git-create-fileinfo 'added new-name old-perm new-perm 'rename name)) refresh)
555+
(git-insert-fileinfo status (git-create-fileinfo (git-state-code state) name old-perm new-perm) refresh))
556+
(setq files (delete name files))
557+
(when new-name (setq files (delete new-name files)))))))
558+
files)
543559

544560
(defun git-find-status-file (status file)
545561
"Find a given file in the status ewoc and return its node."
@@ -548,32 +564,59 @@ and returns the process output as a string."
548564
(setq node (ewoc-next status node)))
549565
node))
550566

551-
(defun git-parse-ls-files (status default-state &optional skip-existing)
552-
"Parse the output of git-ls-files in the current buffer."
553-
(goto-char (point-min))
554-
(let (infolist)
555-
(while (re-search-forward "\\([HMRCK?]\\) \\([^\0]*\\)\0" nil t 1)
556-
(let ((state (match-string 1))
557-
(name (match-string 2)))
558-
(unless (and skip-existing (git-find-status-file status name))
559-
(push (git-create-fileinfo (or (git-state-code state) default-state) name) infolist))))
560-
(dolist (info (nreverse infolist))
561-
(ewoc-enter-last status info))))
562-
563-
(defun git-parse-ls-unmerged (status)
564-
"Parse the output of git-ls-files -u in the current buffer."
565-
(goto-char (point-min))
566-
(let (files)
567-
(while (re-search-forward "[0-7]\\{6\\} [0-9a-f]\\{40\\} [123]\t\\([^\0]+\\)\0" nil t)
568-
(let ((node (git-find-status-file status (match-string 1))))
569-
(when node (push (ewoc-data node) files))))
570-
(git-set-files-state files 'unmerged)))
571-
572-
(defun git-add-status-file (state name)
573-
"Add a new file to the status list (if not existing already) and return its node."
567+
(defun git-run-ls-files (status files default-state &rest options)
568+
"Run git-ls-files on FILES and parse the results into STATUS.
569+
Return the list of files that haven't been handled."
570+
(let ((refresh files))
571+
(with-temp-buffer
572+
(apply #'git-run-command t nil "ls-files" "-z" "-t" (append options (list "--") files))
573+
(goto-char (point-min))
574+
(while (re-search-forward "\\([HMRCK?]\\) \\([^\0]*\\)\0" nil t 1)
575+
(let ((state (match-string 1))
576+
(name (match-string 2)))
577+
(git-insert-fileinfo status (git-create-fileinfo (or (git-state-code state) default-state) name) refresh)
578+
(setq files (delete name files))))))
579+
files)
580+
581+
(defun git-run-ls-unmerged (status files)
582+
"Run git-ls-files -u on FILES and parse the results into STATUS."
583+
(with-temp-buffer
584+
(apply #'git-run-command t nil "ls-files" "-z" "-u" "--" files)
585+
(goto-char (point-min))
586+
(let (unmerged-files)
587+
(while (re-search-forward "[0-7]\\{6\\} [0-9a-f]\\{40\\} [123]\t\\([^\0]+\\)\0" nil t)
588+
(let ((node (git-find-status-file status (match-string 1))))
589+
(when node (push (ewoc-data node) unmerged-files))))
590+
(git-set-files-state unmerged-files 'unmerged))))
591+
592+
(defun git-update-status-files (files &optional default-state)
593+
"Update the status of FILES from the index."
574594
(unless git-status (error "Not in git-status buffer."))
575-
(or (git-find-status-file git-status name)
576-
(ewoc-enter-last git-status (git-create-fileinfo state name))))
595+
(let* ((status git-status)
596+
(remaining-files
597+
(if (git-empty-db-p) ; we need some special handling for an empty db
598+
(git-run-ls-files status files 'added "-c")
599+
(git-run-diff-index status files))))
600+
(git-run-ls-unmerged status files)
601+
(when (and (or (not files) remaining-files)
602+
(file-readable-p ".git/info/exclude"))
603+
(setq remaining-files (git-run-ls-files status remaining-files
604+
'unknown "-o" "--exclude-from=.git/info/exclude"
605+
(concat "--exclude-per-directory=" git-per-dir-ignore-file))))
606+
; mark remaining files with the default state (or remove them if nil)
607+
(when remaining-files
608+
(if default-state
609+
(ewoc-map (lambda (info)
610+
(when (member (git-fileinfo->name info) remaining-files)
611+
(git-set-files-state (list info) default-state))
612+
nil)
613+
status)
614+
(ewoc-filter status
615+
(lambda (info files)
616+
(not (member (git-fileinfo->name info) files)))
617+
remaining-files)))
618+
(git-refresh-files)
619+
(git-refresh-ewoc-hf status)))
577620

578621
(defun git-marked-files ()
579622
"Return a list of all marked files, or if none a list containing just the file at cursor position."
@@ -789,54 +832,34 @@ and returns the process output as a string."
789832
(defun git-add-file ()
790833
"Add marked file(s) to the index cache."
791834
(interactive)
792-
(let ((files (git-marked-files-state 'unknown)))
835+
(let ((files (git-get-filenames (git-marked-files-state 'unknown))))
793836
(unless files
794-
(push (ewoc-data
795-
(git-add-status-file 'added (file-relative-name
796-
(read-file-name "File to add: " nil nil t))))
797-
files))
798-
(apply #'git-run-command nil nil "update-index" "--info-only" "--add" "--" (git-get-filenames files))
799-
(git-set-files-state files 'added)
800-
(git-refresh-files)))
837+
(push (file-relative-name (read-file-name "File to add: " nil nil t)) files))
838+
(apply #'git-run-command nil nil "update-index" "--add" "--" files)
839+
(git-update-status-files files 'uptodate)))
801840

802841
(defun git-ignore-file ()
803842
"Add marked file(s) to the ignore list."
804843
(interactive)
805-
(let ((files (git-marked-files-state 'unknown)))
844+
(let ((files (git-get-filenames (git-marked-files-state 'unknown))))
806845
(unless files
807-
(push (ewoc-data
808-
(git-add-status-file 'unknown (file-relative-name
809-
(read-file-name "File to ignore: " nil nil t))))
810-
files))
811-
(dolist (info files) (git-append-to-ignore (git-fileinfo->name info)))
812-
(git-set-files-state files 'ignored)
813-
(git-refresh-files)))
846+
(push (file-relative-name (read-file-name "File to ignore: " nil nil t)) files))
847+
(dolist (f files) (git-append-to-ignore f))
848+
(git-update-status-files files 'ignored)))
814849

815850
(defun git-remove-file ()
816851
"Remove the marked file(s)."
817852
(interactive)
818-
(let ((files (git-marked-files-state 'added 'modified 'unknown 'uptodate)))
853+
(let ((files (git-get-filenames (git-marked-files-state 'added 'modified 'unknown 'uptodate))))
819854
(unless files
820-
(push (ewoc-data
821-
(git-add-status-file 'unknown (file-relative-name
822-
(read-file-name "File to remove: " nil nil t))))
823-
files))
855+
(push (file-relative-name (read-file-name "File to remove: " nil nil t)) files))
824856
(if (yes-or-no-p
825857
(format "Remove %d file%s? " (length files) (if (> (length files) 1) "s" "")))
826858
(progn
827-
(dolist (info files)
828-
(let ((name (git-fileinfo->name info)))
829-
(when (file-exists-p name) (delete-file name))))
830-
(apply #'git-run-command nil nil "update-index" "--info-only" "--remove" "--" (git-get-filenames files))
831-
; remove unknown files from the list, set the others to deleted
832-
(ewoc-filter git-status
833-
(lambda (info files)
834-
(not (and (memq info files) (eq (git-fileinfo->state info) 'unknown))))
835-
files)
836-
(git-set-files-state files 'deleted)
837-
(git-refresh-files)
838-
(unless (ewoc-nth git-status 0) ; refresh header if list is empty
839-
(git-refresh-ewoc-hf git-status)))
859+
(dolist (name files)
860+
(when (file-exists-p name) (delete-file name)))
861+
(apply #'git-run-command nil nil "update-index" "--remove" "--" files)
862+
(git-update-status-files files nil))
840863
(message "Aborting"))))
841864

842865
(defun git-revert-file ()
@@ -849,26 +872,23 @@ and returns the process output as a string."
849872
(format "Revert %d file%s? " (length files) (if (> (length files) 1) "s" ""))))
850873
(dolist (info files)
851874
(case (git-fileinfo->state info)
852-
('added (push info added))
853-
('deleted (push info modified))
854-
('unmerged (push info modified))
855-
('modified (push info modified))))
875+
('added (push (git-fileinfo->name info) added))
876+
('deleted (push (git-fileinfo->name info) modified))
877+
('unmerged (push (git-fileinfo->name info) modified))
878+
('modified (push (git-fileinfo->name info) modified))))
856879
(when added
857-
(apply #'git-run-command nil nil "update-index" "--force-remove" "--" (git-get-filenames added))
858-
(git-set-files-state added 'unknown))
880+
(apply #'git-run-command nil nil "update-index" "--force-remove" "--" added))
859881
(when modified
860-
(apply #'git-run-command nil nil "checkout" "HEAD" (git-get-filenames modified))
861-
(git-set-files-state modified 'uptodate))
862-
(git-refresh-files))))
882+
(apply #'git-run-command nil nil "checkout" "HEAD" modified))
883+
(git-update-status-files (append added modified) 'uptodate))))
863884

864885
(defun git-resolve-file ()
865886
"Resolve conflicts in marked file(s)."
866887
(interactive)
867-
(let ((files (git-marked-files-state 'unmerged)))
888+
(let ((files (git-get-filenames (git-marked-files-state 'unmerged))))
868889
(when files
869-
(apply #'git-run-command nil nil "update-index" "--" (git-get-filenames files))
870-
(git-set-files-state files 'modified)
871-
(git-refresh-files))))
890+
(apply #'git-run-command nil nil "update-index" "--" files)
891+
(git-update-status-files files 'uptodate))))
872892

873893
(defun git-remove-handled ()
874894
"Remove handled files from the status list."
@@ -1071,27 +1091,9 @@ and returns the process output as a string."
10711091
(pos (ewoc-locate status))
10721092
(cur-name (and pos (git-fileinfo->name (ewoc-data pos)))))
10731093
(unless status (error "Not in git-status buffer."))
1094+
(git-run-command nil nil "update-index" "--refresh")
10741095
(git-clear-status status)
1075-
(git-run-command nil nil "update-index" "--info-only" "--refresh")
1076-
(if (git-empty-db-p)
1077-
; we need some special handling for an empty db
1078-
(with-temp-buffer
1079-
(git-run-command t nil "ls-files" "-z" "-t" "-c")
1080-
(git-parse-ls-files status 'added))
1081-
(with-temp-buffer
1082-
(git-run-command t nil "diff-index" "-z" "-M" "HEAD")
1083-
(git-parse-status status)))
1084-
(with-temp-buffer
1085-
(git-run-command t nil "ls-files" "-z" "-u")
1086-
(git-parse-ls-unmerged status))
1087-
(when (file-readable-p ".git/info/exclude")
1088-
(with-temp-buffer
1089-
(git-run-command t nil "ls-files" "-z" "-t" "-o"
1090-
"--exclude-from=.git/info/exclude"
1091-
(concat "--exclude-per-directory=" git-per-dir-ignore-file))
1092-
(git-parse-ls-files status 'unknown)))
1093-
(git-refresh-files)
1094-
(git-refresh-ewoc-hf status)
1096+
(git-update-status-files nil)
10951097
; move point to the current file name if any
10961098
(let ((node (and cur-name (git-find-status-file status cur-name))))
10971099
(when node (ewoc-goto-node status node)))))

0 commit comments

Comments
 (0)