https://github.com/goldsteinn created https://github.com/llvm/llvm-project/pull/112792
New proposed function `clang-format-git-diffs`. It is the same as calling `clang-format-region` on all diffs between the content of a buffer-file and the content of the file at git revision HEAD. This is essentially the same thing as: `git-clang-format -f {filename}` If the current buffer is saved. The motivation is many project (LLVM included) both have code that is non-compliant with there clang-format style and disallow unrelated format diffs in PRs. This means users can't just run `clang-format-buffer` on the buffer they are working on, and need to manually go through all the regions by hand to get them formatted. This is both an error prone and annoying workflow. >From 3e7bac3fad7939a17c6a588c56ce1dc73679a2da Mon Sep 17 00:00:00 2001 From: Noah Goldstein <goldstein....@gmail.com> Date: Thu, 17 Oct 2024 17:31:24 -0500 Subject: [PATCH] [emacs][clang-format] Add elisp API for clang-format on git diffs New proposed function `clang-format-git-diffs`. It is the same as calling `clang-format-region` on all diffs between the content of a buffer-file and the content of the file at git revision HEAD. This is essentially the same thing as: `git-clang-format -f {filename}` If the current buffer is saved. The motivation is many project (LLVM included) both have code that is non-compliant with there clang-format style and disallow unrelated format diffs in PRs. This means users can't just run `clang-format-buffer` on the buffer they are working on, and need to manually go through all the regions by hand to get them formatted. This is both an error prone and annoying workflow. --- clang/tools/clang-format/clang-format.el | 157 +++++++++++++++++++++-- 1 file changed, 143 insertions(+), 14 deletions(-) diff --git a/clang/tools/clang-format/clang-format.el b/clang/tools/clang-format/clang-format.el index f3da5415f8672b..dfdef2260de06e 100644 --- a/clang/tools/clang-format/clang-format.el +++ b/clang/tools/clang-format/clang-format.el @@ -132,18 +132,97 @@ is a zero-based file offset, assuming ‘utf-8-unix’ coding." (lambda (byte &optional _quality _coding-system) (byte-to-position (1+ byte))))) -;;;###autoload -(defun clang-format-region (start end &optional style assume-file-name) - "Use clang-format to format the code between START and END according to STYLE. -If called interactively uses the region or the current statement if there is no -no active region. If no STYLE is given uses `clang-format-style'. Use -ASSUME-FILE-NAME to locate a style config file, if no ASSUME-FILE-NAME is given -uses the function `buffer-file-name'." - (interactive - (if (use-region-p) - (list (region-beginning) (region-end)) - (list (point) (point)))) +(defun clang-format--git-diffs-get-diff-lines (file-orig file-new) + "Return all line regions that contain diffs between FILE-ORIG and +FILE-NEW. If there is no diff 'nil' is returned. Otherwise the +return is a 'list' of lines in the format '--lines=<start>:<end>' +which can be passed directly to 'clang-format'" + ;; Temporary buffer for output of diff. + (with-temp-buffer + (let ((status (call-process + "diff" + nil + (current-buffer) + nil + ;; Binary diff has different behaviors that we + ;; aren't interested in. + "-a" + ;; Printout changes as only the line groups. + "--changed-group-format=--lines=%dF:%dL " + ;; Ignore unchanged content. + "--unchanged-group-format=" + file-orig + file-new + ) + ) + (stderr (concat (if (zerop (buffer-size)) "" ": ") + (buffer-substring-no-properties + (point-min) (line-end-position))))) + (when (stringp status) + (error "(diff killed by signal %s%s)" status stderr)) + (unless (= status 0) + (unless (= status 1) + (error "(diff returned unsuccessfully %s%s)" status stderr))) + + + (if (= status 0) + ;; Status == 0 -> no Diff. + nil + (progn + ;; Split "--lines=<S0>:<E0>... --lines=<SN>:<SN>" output to + ;; a list for return. + (s-split + " " + (string-trim + (buffer-substring-no-properties + (point-min) (point-max))))))))) + +(defun clang-format--git-diffs-get-git-head-file () + "Returns a temporary file with the content of 'buffer-file-name' at +git revision HEAD. If the current buffer is either not a file or not +in a git repo, this results in an error" + ;; Needs current buffer to be a file + (unless (buffer-file-name) + (error "Buffer is not visiting a file")) + ;; Need to be able to find version control (git) root + (unless (vc-root-dir) + (error "File not known to git")) + ;; Need version control to in fact be git + (unless (string-equal (vc-backend (buffer-file-name)) "Git") + (error "Not using git")) + + (let ((tmpfile-git-head (make-temp-file "clang-format-tmp-git-head-content"))) + ;; Get filename relative to git root + (let ((git-file-name (substring + (expand-file-name (buffer-file-name)) + (string-width (expand-file-name (vc-root-dir))) + nil))) + (let ((status (call-process + "git" + nil + `(:file, tmpfile-git-head) + nil + "show" (concat "HEAD:" git-file-name))) + (stderr (with-temp-buffer + (unless (zerop (cadr (insert-file-contents tmpfile-git-head))) + (insert ": ")) + (buffer-substring-no-properties + (point-min) (line-end-position))))) + (when (stringp status) + (error "(git show HEAD:%s killed by signal %s%s)" + git-file-name status stderr)) + (unless (zerop status) + (error "(git show HEAD:%s returned unsuccessfully %s%s)" + git-file-name status stderr)))) + ;; Return temporary file so we can diff it. + tmpfile-git-head)) +(defun clang-format--region-impl (start end &optional style assume-file-name lines) + "Common implementation for 'clang-format-buffer', +'clang-format-region', and 'clang-format-git-diffs'. START and END +refer to the region to be formatter. STYLE and ASSUME-FILE-NAME are +used for configuring the clang-format. And LINES is used to pass +specific locations for reformatting (i.e diff locations)." (unless style (setq style clang-format-style)) @@ -176,8 +255,12 @@ uses the function `buffer-file-name'." (list "--assume-filename" assume-file-name)) ,@(and style (list "--style" style)) "--fallback-style" ,clang-format-fallback-style - "--offset" ,(number-to-string file-start) - "--length" ,(number-to-string (- file-end file-start)) + ,@(and lines lines) + ,@(and (not lines) + (list + "--offset" (number-to-string file-start) + "--length" (number-to-string + (- file-end file-start)))) "--cursor" ,(number-to-string cursor)))) (stderr (with-temp-buffer (unless (zerop (cadr (insert-file-contents temp-file))) @@ -205,6 +288,48 @@ uses the function `buffer-file-name'." (delete-file temp-file) (when (buffer-name temp-buffer) (kill-buffer temp-buffer))))) +;;;###autoload +(defun clang-format-git-diffs (&optional style assume-file-name) + "The same as 'clang-format-buffer' but only operates on the git +diffs from HEAD in the buffer. If no STYLE is given uses +`clang-format-style'. Use ASSUME-FILE-NAME to locate a style config +file. If no ASSUME-FILE-NAME is given uses the function +`buffer-file-name'." + (interactive) + (let ((tmpfile-git-head + (clang-format--git-diffs-get-git-head-file)) + (tmpfile-curbuf (make-temp-file "clang-format-git-tmp"))) + ;; Move current buffer to a temporary file to take a diff. Even if + ;; current-buffer is backed by a file, we want to diff the buffer + ;; contents which might not be saved. + (write-region nil nil tmpfile-curbuf nil 'nomessage) + ;; Git list of lines with a diff. + (let ((diff-lines + (clang-format--git-diffs-get-diff-lines + tmpfile-git-head tmpfile-curbuf))) + ;; If we have any diffs, format them. + (when diff-lines + (clang-format--region-impl + (point-min) + (point-max) + style + assume-file-name + diff-lines))))) + +;;;###autoload +(defun clang-format-region (start end &optional style assume-file-name) + "Use clang-format to format the code between START and END according +to STYLE. If called interactively uses the region or the current +statement if there is no no active region. If no STYLE is given uses +`clang-format-style'. Use ASSUME-FILE-NAME to locate a style config +file, if no ASSUME-FILE-NAME is given uses the function +`buffer-file-name'." + (interactive + (if (use-region-p) + (list (region-beginning) (region-end)) + (list (point) (point)))) + (clang-format--region-impl start end style assume-file-name)) + ;;;###autoload (defun clang-format-buffer (&optional style assume-file-name) "Use clang-format to format the current buffer according to STYLE. @@ -212,7 +337,11 @@ If no STYLE is given uses `clang-format-style'. Use ASSUME-FILE-NAME to locate a style config file. If no ASSUME-FILE-NAME is given uses the function `buffer-file-name'." (interactive) - (clang-format-region (point-min) (point-max) style assume-file-name)) + (clang-format--region-impl + (point-min) + (point-max) + style + assume-file-name)) ;;;###autoload (defalias 'clang-format 'clang-format-region) _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits