1414from dataclasses import dataclass
1515from collections import OrderedDict
1616from os import path
17- from pathlib import Path
1817from itertools import islice
1918
2019from git import Repo
2120
2221from sphinx .util import logging
23- from sphinx .util .matching import Matcher
2422from sphinx .config import ENUM
2523
2624from sphinxnotes .render import (
@@ -48,16 +46,6 @@ class Revision:
4846 #: Git commit author date
4947 date : datetime
5048
51- # FYI, possible status letters are:
52- # :A: addition of a file
53- # :C: copy of a file into a new one
54- # :D: deletion of a file
55- # :M: modification of the contents or mode of a file
56- # :R: renaming of a file
57- # :T: change in the type of the file
58- # :U: file is unmerged (you must complete the merge before it can be committed)
59- # :X: "unknown" change type (most probably a bug, please report it)
60-
6149 #: List of docname, corresponding to files which are newly added
6250 added_docs : list [str ]
6351 #: List of docname, corresponding to files which are modified
@@ -135,11 +123,10 @@ def compact_groups(
135123def get_git_revisions (
136124 repo : Repo ,
137125 env : BuildEnvironment ,
138- path : str ,
139- current_doc : str | None = None ,
126+ paths : list [str ],
140127) -> Iterator [Revision ]:
141128 """Yield Revision objects from git commits."""
142- for cur in repo .iter_commits (paths = path ):
129+ for cur in repo .iter_commits (paths = paths ):
143130 matches = [x in cur .message for x in env .config .recentupdate_exclude_commit ]
144131 if any (matches ):
145132 logger .debug (
@@ -155,37 +142,42 @@ def get_git_revisions(
155142 for blob in cur .tree .traverse ():
156143 if blob .type != 'blob' :
157144 continue
158- docname = path2docname (repo , env , blob .path )
145+ docname = path2doc (repo , env , blob .path )
159146 if docname is None :
160147 continue
161148 a .append (docname )
162149 else :
163- diff_idx = prev .tree .diff (cur )
164- for diff in diff_idx :
165- if diff .a_path is None :
150+ # Possible status letters are:
151+ # :A: addition of a file
152+ # :C: copy of a file into a new one
153+ # :D: deletion of a file
154+ # :M: modification of the contents or mode of a file
155+ # :R: renaming of a file
156+ # :T: change in the type of the file
157+ # :U: file is unmerged (you must complete the merge before it can be committed)
158+ # :X: "unknown" change type (most probably a bug, please report it)
159+ status_maps = {'M' : m , 'A' : a , 'D' : d }
160+
161+ # Use git diff --name-status with pathspecs for native pathspec matching
162+ name_status = repo .git .diff (
163+ prev .hexsha , cur .hexsha , '--name-status' , '--' , * paths
164+ )
165+ for line in name_status .splitlines ():
166+ if not line .strip ():
166167 continue
167- docname = path2docname (repo , env , diff .a_path )
168+ status , file_path = line .split ('\t ' , 1 )
169+ docname = path2doc (repo , env , file_path )
168170 if docname is None :
169171 continue
170172
171- if diff .change_type == 'M' :
172- m .append (docname )
173- elif diff .change_type == 'A' :
174- a .append (docname )
175- elif diff .change_type == 'D' :
176- d .append (docname )
173+ if status in status_maps :
174+ status_maps [status ].append (docname )
177175 else :
178- logger .info (
179- f'Skip { diff .a_path } : '
180- f'unsupported change type { diff .change_type } '
181- )
176+ logger .info (f'Skip { file_path } : unsupported change type { status } ' )
182177
183178 if len (m ) + len (a ) + len (d ) == 0 :
184179 logger .debug (f'Skip commit { cur .hexsha } : no document changes' )
185180 continue
186- if current_doc is not None and current_doc not in (m + a + d ):
187- logger .debug (f'Skip commit { cur .hexsha } : no changes to { current_doc } ' )
188- continue
189181
190182 yield Revision (
191183 message = str (cur .message ).splitlines (),
@@ -197,28 +189,9 @@ def get_git_revisions(
197189 )
198190
199191
200- def path2docname (repo : Repo , env : BuildEnvironment , file : str ) -> str | None :
201- """Convert a repo-relative file path to a Sphinx docname."""
202- relsrcdir_to_repo = path .relpath (env .srcdir , repo .working_dir )
203- relfn_to_srcdir = path .relpath (file , relsrcdir_to_repo )
204- absfn = Path (repo .working_dir , file )
205- if not absfn .is_relative_to (env .srcdir ):
206- logger .debug (f'Skip { file } : out of srcdir' )
207- return None
208-
209- excluded = Matcher (env .config .exclude_patterns )
210- if excluded (relfn_to_srcdir ):
211- logger .debug (f'Skip { file } : excluded by exclude_patterns' )
212- return None
213-
214- docname , ext = path .splitext (relfn_to_srcdir )
215- source_suffix = list (env .config .source_suffix .keys ())
216- if not ext or ext not in source_suffix :
217- logger .debug (f'Skip { file } : not { source_suffix } files' )
218- return None
219-
220- logger .debug (f'Get docname: { docname } ' )
221- return docname
192+ def path2doc (repo : Repo , env : BuildEnvironment , blob_path : str ) -> str | None :
193+ """Convert a git repo-relative blob path to a Sphinx document name. """
194+ return env .path2doc (path .join (repo .working_dir , blob_path ))
222195
223196
224197@extra_context ('recentupdate' )
@@ -232,15 +205,19 @@ def generate(
232205 self ,
233206 req : ExtraContextRequest ,
234207 count : int = 0 ,
235- path : str = '.' ,
208+ paths : list [ str ] = [ '.' , ] ,
236209 current_doc : bool = False ,
237210 group_by : str = '' ,
238211 ) -> Any :
239212 count = count or req .env .config .recentupdate_count
240213 group_by = group_by or req .env .config .recentupdate_group_by
241- docname = req .env .docname if current_doc else None
242214
243- git_revs = get_git_revisions (self .repo , req .env , path , docname )
215+ if current_doc :
216+ docpath = req .env .doc2path (req .env .docname )
217+ repo_path = path .relpath (docpath , self .repo .working_dir )
218+ paths = [repo_path ]
219+
220+ git_revs = get_git_revisions (self .repo , req .env , paths )
244221
245222 if group_by :
246223 groups = OrderedDict ()
0 commit comments