@@ -161,6 +161,20 @@ class Repo:
161161 https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---configltkeygtltvaluegt
162162 """
163163
164+ unsafe_git_archive_options = [
165+ # Allows arbitrary command execution through the remote git-upload-archive command.
166+ "--exec" ,
167+ # Writes output to a caller-controlled filesystem path.
168+ "--output" ,
169+ "-o" ,
170+ ]
171+
172+ unsafe_git_revision_options = [
173+ # This option allows output to be written to arbitrary files before revision parsing.
174+ "--output" ,
175+ "-o" ,
176+ ]
177+
164178 # Invariants
165179 config_level : ConfigLevels_Tup = ("system" , "user" , "global" , "repository" )
166180 """Represents the configuration level of a configuration file."""
@@ -775,6 +789,7 @@ def iter_commits(
775789 self ,
776790 rev : Union [str , Commit , "SymbolicReference" , None ] = None ,
777791 paths : Union [PathLike , Sequence [PathLike ]] = "" ,
792+ allow_unsafe_options : bool = False ,
778793 ** kwargs : Any ,
779794 ) -> Iterator [Commit ]:
780795 """An iterator of :class:`~git.objects.commit.Commit` objects representing the
@@ -792,6 +807,9 @@ def iter_commits(
792807 Arguments to be passed to :manpage:`git-rev-list(1)`.
793808 Common ones are ``max_count`` and ``skip``.
794809
810+ :param allow_unsafe_options:
811+ Allow unsafe options in the revision argument, like ``--output``.
812+
795813 :note:
796814 To receive only commits between two named revisions, use the
797815 ``"revA...revB"`` revision specifier.
@@ -802,7 +820,18 @@ def iter_commits(
802820 if rev is None :
803821 rev = self .head .commit
804822
805- return Commit .iter_items (self , rev , paths , ** kwargs )
823+ if not allow_unsafe_options :
824+ Git .check_unsafe_options (
825+ options = Git ._option_candidates ([rev ], kwargs ), unsafe_options = self .unsafe_git_revision_options
826+ )
827+
828+ return Commit .iter_items (
829+ self ,
830+ rev ,
831+ paths ,
832+ allow_unsafe_options = allow_unsafe_options ,
833+ ** kwargs ,
834+ )
806835
807836 def merge_base (self , * rev : TBD , ** kwargs : Any ) -> List [Commit ]:
808837 R"""Find the closest common ancestor for the given revision
@@ -1079,7 +1108,9 @@ def active_branch(self) -> Head:
10791108 )
10801109 return active_branch
10811110
1082- def blame_incremental (self , rev : str | HEAD | None , file : str , ** kwargs : Any ) -> Iterator ["BlameEntry" ]:
1111+ def blame_incremental (
1112+ self , rev : str | HEAD | None , file : str , allow_unsafe_options : bool = False , ** kwargs : Any
1113+ ) -> Iterator ["BlameEntry" ]:
10831114 """Iterator for blame information for the given file at the given revision.
10841115
10851116 Unlike :meth:`blame`, this does not return the actual file's contents, only a
@@ -1090,6 +1121,9 @@ def blame_incremental(self, rev: str | HEAD | None, file: str, **kwargs: Any) ->
10901121 uncommitted changes. Otherwise, anything successfully parsed by
10911122 :manpage:`git-rev-parse(1)` is a valid option.
10921123
1124+ :param allow_unsafe_options:
1125+ Allow unsafe options in revision argument, like ``--output``.
1126+
10931127 :return:
10941128 Lazy iterator of :class:`BlameEntry` tuples, where the commit indicates the
10951129 commit to blame for the line, and range indicates a span of line numbers in
@@ -1098,6 +1132,10 @@ def blame_incremental(self, rev: str | HEAD | None, file: str, **kwargs: Any) ->
10981132 If you combine all line number ranges outputted by this command, you should get
10991133 a continuous range spanning all line numbers in the file.
11001134 """
1135+ if not allow_unsafe_options :
1136+ Git .check_unsafe_options (
1137+ options = Git ._option_candidates ([rev ], kwargs ), unsafe_options = self .unsafe_git_revision_options
1138+ )
11011139
11021140 data : bytes = self .git .blame (rev , "--" , file , p = True , incremental = True , stdout_as_string = False , ** kwargs )
11031141 commits : Dict [bytes , Commit ] = {}
@@ -1176,7 +1214,8 @@ def blame(
11761214 rev : Union [str , HEAD , None ],
11771215 file : str ,
11781216 incremental : bool = False ,
1179- rev_opts : Optional [List [str ]] = None ,
1217+ rev_opts : Optional [Sequence [str ]] = None ,
1218+ allow_unsafe_options : bool = False ,
11801219 ** kwargs : Any ,
11811220 ) -> List [List [Commit | List [str | bytes ] | None ]] | Iterator [BlameEntry ] | None :
11821221 """The blame information for the given file at the given revision.
@@ -1186,6 +1225,9 @@ def blame(
11861225 uncommitted changes. Otherwise, anything successfully parsed by
11871226 :manpage:`git-rev-parse(1)` is a valid option.
11881227
1228+ :param allow_unsafe_options:
1229+ Allow unsafe options in revision argument, like ``--output``.
1230+
11891231 :return:
11901232 list: [git.Commit, list: [<line>]]
11911233
@@ -1195,9 +1237,14 @@ def blame(
11951237 appearance.
11961238 """
11971239 if incremental :
1198- return self .blame_incremental (rev , file , ** kwargs )
1199- rev_opts = rev_opts or []
1200- data : bytes = self .git .blame (rev , * rev_opts , "--" , file , p = True , stdout_as_string = False , ** kwargs )
1240+ return self .blame_incremental (rev , file , allow_unsafe_options = allow_unsafe_options , ** kwargs )
1241+ rev_opts_list = list (rev_opts or [])
1242+ if not allow_unsafe_options :
1243+ Git .check_unsafe_options (
1244+ options = Git ._option_candidates ([rev , rev_opts_list ], kwargs ),
1245+ unsafe_options = self .unsafe_git_revision_options ,
1246+ )
1247+ data : bytes = self .git .blame (rev , * rev_opts_list , "--" , file , p = True , stdout_as_string = False , ** kwargs )
12011248 commits : Dict [str , Commit ] = {}
12021249 blames : List [List [Commit | List [str | bytes ] | None ]] = []
12031250
@@ -1408,7 +1455,10 @@ def _clone(
14081455 if not allow_unsafe_protocols :
14091456 Git .check_unsafe_protocols (url )
14101457 if not allow_unsafe_options :
1411- Git .check_unsafe_options (options = list (kwargs .keys ()), unsafe_options = cls .unsafe_git_clone_options )
1458+ Git .check_unsafe_options (
1459+ options = Git ._option_candidates ([], kwargs ),
1460+ unsafe_options = cls .unsafe_git_clone_options ,
1461+ )
14121462 if not allow_unsafe_options and multi :
14131463 Git .check_unsafe_options (options = multi , unsafe_options = cls .unsafe_git_clone_options )
14141464
@@ -1583,6 +1633,8 @@ def archive(
15831633 ostream : Union [TextIO , BinaryIO ],
15841634 treeish : Optional [str ] = None ,
15851635 prefix : Optional [str ] = None ,
1636+ allow_unsafe_options : bool = False ,
1637+ allow_unsafe_protocols : bool = False ,
15861638 ** kwargs : Any ,
15871639 ) -> Repo :
15881640 """Archive the tree at the given revision.
@@ -1605,6 +1657,12 @@ def archive(
16051657 repository-relative path to a directory or file to place into the archive,
16061658 or a list or tuple of multiple paths.
16071659
1660+ :param allow_unsafe_options:
1661+ Allow unsafe options, like ``--exec`` or ``--output``.
1662+
1663+ :param allow_unsafe_protocols:
1664+ Allow unsafe protocols to be used in ``remote``, like ``ext``.
1665+
16081666 :raise git.exc.GitCommandError:
16091667 If something went wrong.
16101668
@@ -1615,6 +1673,14 @@ def archive(
16151673 treeish = self .head .commit
16161674 if prefix and "prefix" not in kwargs :
16171675 kwargs ["prefix" ] = prefix
1676+ remote = kwargs .get ("remote" )
1677+ if not allow_unsafe_protocols and remote is not None :
1678+ Git .check_unsafe_protocols (str (remote ))
1679+ if not allow_unsafe_options :
1680+ Git .check_unsafe_options (
1681+ options = Git ._option_candidates ([], kwargs ),
1682+ unsafe_options = self .unsafe_git_archive_options ,
1683+ )
16181684 kwargs ["output_stream" ] = ostream
16191685 path = kwargs .pop ("path" , [])
16201686 path = cast (Union [PathLike , List [PathLike ], Tuple [PathLike , ...]], path )
0 commit comments