Skip to content

Commit

Permalink
Add Submodule.init and Submodule.update
Browse files Browse the repository at this point in the history
This allows updating individual Submodule instances when you already have them, instead of going through Repository (which would do a redundant submodule lookup).
  • Loading branch information
jorio committed Nov 18, 2023
1 parent 026048a commit 82238f1
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 94 deletions.
1 change: 0 additions & 1 deletion pygit2/_pygit2.pyi
Expand Up @@ -495,7 +495,6 @@ class Repository:
def expand_id(self, hex: str) -> Oid: ...
def free(self) -> None: ...
def git_object_lookup_prefix(self, oid: _OidArg) -> Object: ...
def init_submodules(self, submodules: Optional[list[Submodule]] = None, overwrite = False) -> None: ...
def list_worktrees(self) -> list[str]: ...
def listall_branches(self, flag: int = GIT_BRANCH_LOCAL) -> list[str]: ...
def listall_stashes(self) -> list[Stash]: ...
Expand Down
5 changes: 2 additions & 3 deletions pygit2/decl/submodule.h
Expand Up @@ -30,9 +30,8 @@ int git_submodule_lookup(
const char *name);

void git_submodule_free(git_submodule *submodule);
int git_submodule_open(
git_repository **repo,
git_submodule *submodule);
int git_submodule_open(git_repository **repo, git_submodule *submodule);
int git_submodule_init(git_submodule *submodule, int overwrite);

const char * git_submodule_name(git_submodule *submodule);
const char * git_submodule_path(git_submodule *submodule);
Expand Down
40 changes: 29 additions & 11 deletions pygit2/repository.py
Expand Up @@ -191,6 +191,32 @@ def lookup_submodule(self, path: str) -> Submodule:
check_error(err)
return Submodule._from_c(self, csub[0])

def init_submodules(
self,
submodules: typing.Optional[typing.Iterable[str]] = None,
overwrite: bool = False):
"""
Initialize submodules in the repository. Just like "git submodule init",
this copies information about the submodules into ".git/config".
Parameters:
submodules
Optional list of submodule paths or names to initialize
(the submodule is ultimately resolved with Repository.lookup_submodule()).
Default argument initializes all submodules.
overwrite
Flag indicating if initialization should overwrite submodule entries.
"""
if submodules is None:
submodules = self.listall_submodules()

instances = [self.lookup_submodule(s) for s in submodules]

for submodule in instances:
submodule.init(overwrite)

def update_submodules(
self,
submodules: typing.Optional[typing.Iterable[str]] = None,
Expand Down Expand Up @@ -221,18 +247,10 @@ def update_submodules(
if submodules is None:
submodules = self.listall_submodules()

# prepare options
opts = ffi.new('git_submodule_update_options *')
C.git_submodule_update_options_init(opts, C.GIT_SUBMODULE_UPDATE_OPTIONS_VERSION)

with git_fetch_options(callbacks, opts=opts.fetch_opts) as payload:
i = 1 if init else 0
for submodule in submodules:
submodule_instance = self.lookup_submodule(submodule)
err = C.git_submodule_update(submodule_instance._subm, i, opts)
payload.check_error(err)
instances = [self.lookup_submodule(s) for s in submodules]

return None
for submodule in instances:
submodule.update(init, callbacks)

#
# Mapping interface
Expand Down
41 changes: 41 additions & 0 deletions pygit2/submodule.py
Expand Up @@ -24,6 +24,7 @@
# Boston, MA 02110-1301, USA.

from ._pygit2 import Oid
from .callbacks import git_fetch_options, RemoteCallbacks
from .errors import check_error
from .ffi import ffi, C

Expand All @@ -50,6 +51,46 @@ def open(self):

return self._repo._from_c(crepo[0], True)

def init(self, overwrite: bool = False):
"""
Just like "git submodule init", this copies information about the submodule
into ".git/config".
Parameters:
overwrite
By default, existing submodule entries will not be overwritten,
but setting this to True forces them to be updated.
"""
err = C.git_submodule_init(self._subm, int(overwrite))
check_error(err)

def update(self, init: bool = False, callbacks: RemoteCallbacks = None):
"""
Update a submodule. This will clone a missing submodule and checkout
the subrepository to the commit specified in the index of the
containing repository. If the submodule repository doesn't contain the
target commit (e.g. because fetchRecurseSubmodules isn't set), then the
submodule is fetched using the fetch options supplied in options.
Parameters:
init
If the submodule is not initialized, setting this flag to True will
initialize the submodule before updating. Otherwise, this will raise
an error if attempting to update an uninitialized repository.
callbacks
Optional RemoteCallbacks to clone or fetch the submodule.
"""

opts = ffi.new('git_submodule_update_options *')
C.git_submodule_update_options_init(opts, C.GIT_SUBMODULE_UPDATE_OPTIONS_VERSION)

with git_fetch_options(callbacks, opts=opts.fetch_opts) as payload:
err = C.git_submodule_update(self._subm, int(init), opts)
payload.check_error(err)

@property
def name(self):
"""Name of the submodule."""
Expand Down
76 changes: 0 additions & 76 deletions src/repository.c
Expand Up @@ -1508,81 +1508,6 @@ Repository_listall_submodules(Repository *self, PyObject *args)
}


PyDoc_STRVAR(Repository_init_submodules__doc__,
"init_submodules(submodules: list[Submodule] = None, overwrite=False)\n"
"\n"
"Initialize all submodules in repository.\n"
"submodules: List of submodules to initialize. Default argument initializes all submodules.\n"
"overwrite: Flag indicating if initialization should overwrite submodule entries.\n");

static int foreach_sub_init_cb(git_submodule *submodule, const char *name, void *payload)
{
return git_submodule_init(submodule, *(int*)payload);
}

PyObject *
Repository_init_submodules(Repository* self, PyObject *args, PyObject *kwds)
{
PyObject *list = Py_None;
PyObject *oflag = Py_False;
char *kwlist[] = {"submodules", "overwrite", NULL};
int err;
const char *c_subpath;
git_submodule *submodule;

if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OO", kwlist, &list, &oflag))
return NULL;

int fflag = PyObject_IsTrue(oflag);
if (fflag != 0 && fflag != 1)
fflag = 0;

//Init all submodules listed in repository
if (list == Py_None) {
err = git_submodule_foreach(self->repo, foreach_sub_init_cb, &fflag);
if (err != 0)
return Error_set(err);
Py_RETURN_NONE;
}

PyObject *iter = PyObject_GetIter(list);
if (!iter)
return NULL;

PyObject *next = NULL;
while (1) {
Py_XDECREF(next); // Decref from the previous iteration
next = PyIter_Next(iter);
if (next == NULL) // List exhausted
break;

c_subpath = pgit_borrow(next);
if (c_subpath == NULL)
goto error;

err = git_submodule_lookup(&submodule, self->repo, c_subpath);
if (!submodule) {
PyErr_SetString(PyExc_KeyError, "Submodule does not exist");
goto error;
}

err = git_submodule_init(submodule, fflag);
if (err) {
Error_set(err);
goto error;
}
}

// Success
Py_DECREF(iter);
Py_RETURN_NONE;

error:
Py_DECREF(iter);
Py_XDECREF(next);
return NULL;
}

PyDoc_STRVAR(Repository_lookup_reference__doc__,
"lookup_reference(name: str) -> Reference\n"
"\n"
Expand Down Expand Up @@ -2439,7 +2364,6 @@ PyMethodDef Repository_methods[] = {
METHOD(Repository, references_iterator_init, METH_NOARGS),
METHOD(Repository, references_iterator_next, METH_VARARGS),
METHOD(Repository, listall_submodules, METH_NOARGS),
METHOD(Repository, init_submodules, METH_VARARGS | METH_KEYWORDS),
METHOD(Repository, lookup_reference, METH_O),
METHOD(Repository, lookup_reference_dwim, METH_O),
METHOD(Repository, revparse_single, METH_O),
Expand Down
23 changes: 20 additions & 3 deletions test/test_submodule.py
Expand Up @@ -81,27 +81,44 @@ def test_url(repo):

@utils.requires_network
def test_init_and_update(repo):
subrepo_file_path = Path(repo.workdir) / 'TestGitRepository' / 'master.txt'
subrepo_file_path = Path(repo.workdir) / SUBM_PATH / 'master.txt'
assert not subrepo_file_path.exists()
repo.init_submodules()
repo.update_submodules()
assert subrepo_file_path.exists()

@utils.requires_network
def test_specified_update(repo):
subrepo_file_path = Path(repo.workdir) / 'TestGitRepository' / 'master.txt'
subrepo_file_path = Path(repo.workdir) / SUBM_PATH / 'master.txt'
assert not subrepo_file_path.exists()
repo.init_submodules(submodules=['TestGitRepository'])
repo.update_submodules(submodules=['TestGitRepository'])
assert subrepo_file_path.exists()

@utils.requires_network
def test_update_instance(repo):
subrepo_file_path = Path(repo.workdir) / SUBM_PATH / 'master.txt'
assert not subrepo_file_path.exists()
sm = repo.lookup_submodule('TestGitRepository')
sm.init()
sm.update()
assert subrepo_file_path.exists()

@utils.requires_network
def test_oneshot_update(repo):
subrepo_file_path = Path(repo.workdir) / 'TestGitRepository' / 'master.txt'
subrepo_file_path = Path(repo.workdir) / SUBM_PATH / 'master.txt'
assert not subrepo_file_path.exists()
repo.update_submodules(init=True)
assert subrepo_file_path.exists()

@utils.requires_network
def test_oneshot_update_instance(repo):
subrepo_file_path = Path(repo.workdir) / SUBM_PATH / 'master.txt'
assert not subrepo_file_path.exists()
sm = repo.lookup_submodule(SUBM_NAME)
sm.update(init=True)
assert subrepo_file_path.exists()

@utils.requires_network
def test_head_id(repo):
s = repo.lookup_submodule(SUBM_PATH)
Expand Down

0 comments on commit 82238f1

Please sign in to comment.