diff --git a/pygit2/_pygit2.pyi b/pygit2/_pygit2.pyi index 4f2e1243..99e5ed2c 100644 --- a/pygit2/_pygit2.pyi +++ b/pygit2/_pygit2.pyi @@ -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]: ... diff --git a/pygit2/decl/submodule.h b/pygit2/decl/submodule.h index df7eb8bc..956cb57d 100644 --- a/pygit2/decl/submodule.h +++ b/pygit2/decl/submodule.h @@ -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); diff --git a/pygit2/repository.py b/pygit2/repository.py index d7f30572..30aeb3cd 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -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, @@ -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 diff --git a/pygit2/submodule.py b/pygit2/submodule.py index 20f3adba..a0a8d32a 100644 --- a/pygit2/submodule.py +++ b/pygit2/submodule.py @@ -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 @@ -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.""" diff --git a/src/repository.c b/src/repository.c index cc234b4e..ae310df5 100644 --- a/src/repository.c +++ b/src/repository.c @@ -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" @@ -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), diff --git a/test/test_submodule.py b/test/test_submodule.py index ba2c5982..6c9d7490 100644 --- a/test/test_submodule.py +++ b/test/test_submodule.py @@ -81,7 +81,7 @@ 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() @@ -89,19 +89,36 @@ def test_init_and_update(repo): @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)