I think the main hard part of adding such functionality is figuring out a way to do it that wouldn't be a breaking change. Most ordinary public-style method names could clash with someone's customgit
commands (such as scripts named likegit-*
), which GitPython is generally able to run and thus would begin to break after such a change. The most intuitive names for this, likeinvoke
, would be especially likely to clash (I'm sure some people have agit-invoke
script).
This also makes it impossible to set configuration overrides
Is this because only one-c
can be passed by calling theGit
instance withc=...
, or for some other reason (or am I misunderstanding what you mean)? To use an example inspired bycheck-version.sh
, and withg
as agit.cmd.Git
instance, I can cause-c versionsort.suffix=-pre
to be passed, and in the correct position, with:
g(c="versionsort.suffix=-pre").tag(sort="-v:refname")
That runsgit -c versionsort.suffix=-pre tag --sort=-v:refname
as desired, with-c versionsort.suffix=-pre
before the subcommand name and--sort=-v:refname
following it.
However, I can't pass more than one-c
that way, because a single call can't pass the same keyword argument multiple times, and the preceding arguments are discarded with multiple calls, i.e., these do the same thing:
g(c="versionsort.suffix=-pre")(c="versionsort.suffix=-RC").tag(sort="-v:refname")
g(c="versionsort.suffix=-RC").tag(sort="-v:refname")
But I'm not sure this is the problem you're thinking of, because a solution for passing-c
arguments and their operands, or for passing arbitrary argumentsbefore a subcommand, would not necessarily facilitate runninggit
without a subcommand. Nor would a solution for runninggit
without a subcommand necessarily allow a subcommand to be added in a user-friendly way supporting the keyword argument syntax for specifying the subcommand's own flags.
finding a solution for this will have immediate benefits
Can people just use_call_process
?
For having GitPython rungit
with arbitrarily specified arguments, the nonpublic_call_process
method does that. Does its behavior differ from the desired behavior for doing so?
If not, then that method could be made public simply by documenting it as public, which would avoid breaking any customgit
commands, because(a) it wouldn't change the actual behavior of GitPython at all, and(b) GitPython already doesn't support customgit
commands that start with_
, andgit
itself doesn't support custom commands that start with-
(since an attempt to invoke such a command would pass one or more options instead).
An example of where an attribute with a leading_
that is made public by documenting it as public, for the same reasons as we might want to do so here--that any other name might clash--is how types constructed with thecollections.namedtuple
factory have public_make
,_asdict
,_replace
,_fields
, and_field_defaults
attributes. (In contrast, although the_thread
module is public, this is not really an example of this, because it is not named that way for a similar reason.)
On the other hand, there may be some reasons not to make_call_process
public by declaring it so. The interface forcollections.namedtuple
is simpler than forgit.cmd.Git
, and also more widely known about because it is part of the standard library, so deviations from common naming conventions may be more discoverable. Also, intuitively, even if_call_process
were public, its name suggests that its use from outside GitPython's own code would be rarer thanexecute
. But using aGit
object to run a non-git
command should be rare, so if_call_process
is public then it should be usedmore often thanexecute
.
Making a "submethod" to rungit
with literal arguments
One possibility, again whereg
is aGit
instance, could be to allowg.execute.git(*args)
, accepting zero or more separate positional arguments in place of*args
that GitPython would immediately rungit
with. I find this intuitive, and it could be achieved by making theexecute
method a custom descriptor that works like a bound method, except that it also causesg.execute.git
to resolve tog._call_process
, andGit.execute.git
to resolve toGit._call_process
(so it also works explicitly passg
to the unbound form, as methods are expected to support).
But the problem with this is that it is not obvious whether the "submethod" ought to continue being usable when a class that derives fromGit
overridesexecute
. Secondarily, I think having overrides turn into descriptors that also support.git
would be complicated, and might go against assumptions people make about he effect of writing a subclass.
To be clear, the problem is not that overridingexecute
affects thebehavior. That is already the case with_call_process
and everything that uses it, and is probably the main reason for a subclass ofGit
to overrideexecute
. Rather, the question is whetherMyGit().execute.git(*args)
andMyGit().execute.git(my_g, *args)
should work and, if so, whether the complexity to make it work is justified.
Other ways, which also don't seem ideal
Other possibilities include:
- Naming the method a single underscore:
g._(*args)
. This seems unintuitive. - Versioning the interface, so something has to be passed when a
Git
object is constructed to enable new methods. - Keeping the
Git
class the same but providing a derived class ofGit
that includes new methods. - Using a top-level function that receives the
Git
object as its first argument. - Using a top-level function that does not use the
Git
object. - Picking some name peopleprobably are not using as a custom
git
command (but the more reliably they are not, the less intuitive the command is, probably). - Not adding a feature for this, but adding a convenient way to get the
git
command (relative or absolute path) that_call_process
passes toexecute
, and noting how to useexecute
with it inexecute
's docstring, elsewhere in the documentation, or both.
A hack that shouldn't be used
By the way, it turns out there actuallyis a way I could have used the "public" interface to achieve the effect ofg._call_process("--exec-path")
. Becausegit
accepts a--
after this option with no change in behavior, we can fool GitPython into thinking--
is the subcommand. Where againg
is aGit
instance:
>>>getattr(g(exec_path=True),"--")()'C:/Users/ek/scoop/apps/git/2.42.0.2/mingw64/libexec/git-core'