Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic commands for submodule listing and adding #989

Merged
merged 2 commits into from
Jul 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
0.20.45 UNRELEASED

* Add basic ``dulwich.porcelain.submodule_list`` and ``dulwich.porcelain.submodule_add``
(Jelmer Vernooij)

0.20.44 2022-06-30

* Fix reading of chunks in server. (Jelmer Vernooij, #977)
Expand Down
9 changes: 9 additions & 0 deletions dulwich/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,14 @@ def run(self, args):
porcelain.rev_list(".", args)


class cmd_submodule(Command):
def run(self, args):
parser = optparse.OptionParser()
options, args = parser.parse_args(args)
for path, sha in porcelain.submodule_list("."):
sys.stdout.write(' %s %s\n' % (sha, path))


class cmd_tag(Command):
def run(self, args):
parser = optparse.OptionParser()
Expand Down Expand Up @@ -721,6 +729,7 @@ def run(self, args):
"stash": cmd_stash,
"status": cmd_status,
"symbolic-ref": cmd_symbolic_ref,
"submodule": cmd_submodule,
"tag": cmd_tag,
"update-server-info": cmd_update_server_info,
"upload-pack": cmd_upload_pack,
Expand Down
51 changes: 49 additions & 2 deletions dulwich/porcelain.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
* remote{_add}
* receive-pack
* reset
* submodule_list
* rev-list
* tag{_create,_delete,_list}
* upload-pack
Expand Down Expand Up @@ -86,6 +87,7 @@
get_transport_and_path,
)
from dulwich.config import (
ConfigFile,
StackedConfig,
)
from dulwich.diff_tree import (
Expand Down Expand Up @@ -858,6 +860,51 @@ def rev_list(repo, commits, outstream=sys.stdout):
outstream.write(entry.commit.id + b"\n")


def _canonical_part(url: str) -> str:
name = url.rsplit('/', 1)[-1]
if name.endswith('.git'):
name = name[:-4]
return name


def submodule_add(repo, url, path=None, name=None):
"""Add a new submodule.

Args:
repo: Path to repository
url: URL of repository to add as submodule
path: Path where submodule should live
"""
with open_repo_closing(repo) as r:
if path is None:
path = os.path.relpath(_canonical_part(url), r.path)
if name is None:
name = path

# TODO(jelmer): Move this logic to dulwich.submodule
gitmodules_path = os.path.join(r.path, ".gitmodules")
try:
config = ConfigFile.from_path(gitmodules_path)
except FileNotFoundError:
config = ConfigFile()
config.path = gitmodules_path
config.set(("submodule", name), "url", url)
config.set(("submodule", name), "path", path)
config.write_to_path()


def submodule_list(repo):
"""List submodules.

Args:
repo: Path to repository
"""
from .submodule import iter_cached_submodules
with open_repo_closing(repo) as r:
for path, sha in iter_cached_submodules(r.object_store, r[r.head()].tree):
yield path.decode(DEFAULT_ENCODING), sha.decode(DEFAULT_ENCODING)


def tag(*args, **kwargs):
import warnings

Expand Down Expand Up @@ -1456,7 +1503,7 @@ def branch_create(repo, name, objectish=None, force=False):
objectish = "HEAD"
object = parse_object(r, objectish)
refname = _make_branch_ref(name)
ref_message = b"branch: Created from " + objectish.encode("utf-8")
ref_message = b"branch: Created from " + objectish.encode(DEFAULT_ENCODING)
if force:
r.refs.set_if_equals(refname, None, object.id, message=ref_message)
else:
Expand Down Expand Up @@ -1541,7 +1588,7 @@ def fetch(
with open_repo_closing(repo) as r:
(remote_name, remote_location) = get_remote_repo(r, remote_location)
if message is None:
message = b"fetch: from " + remote_location.encode("utf-8")
message = b"fetch: from " + remote_location.encode(DEFAULT_ENCODING)
client, path = get_transport_and_path(
remote_location, config=r.get_config_stack(), **kwargs
)
Expand Down
40 changes: 40 additions & 0 deletions dulwich/submodule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# config.py - Reading and writing Git config files
# Copyright (C) 2011-2013 Jelmer Vernooij <[email protected]>
#
# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
# General Public License as public by the Free Software Foundation; version 2.0
# or (at your option) any later version. You can redistribute it and/or
# modify it under the terms of either of these two licenses.
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# You should have received a copy of the licenses; if not, see
# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
# License, Version 2.0.
#

"""Working with Git submodules.
"""

from typing import Iterator, Tuple
from .objects import S_ISGITLINK


def iter_cached_submodules(store, root_tree_id: bytes) -> Iterator[Tuple[str, bytes]]:
"""iterate over cached submodules.

Args:
store: Object store to iterate
root_tree_id: SHA of root tree

Returns:
Iterator over over (path, sha) tuples
"""
for entry in store.iter_tree_contents(root_tree_id):
if S_ISGITLINK(entry.mode):
yield entry.path, entry.sha
22 changes: 22 additions & 0 deletions dulwich/tests/test_porcelain.py
Original file line number Diff line number Diff line change
Expand Up @@ -1408,6 +1408,28 @@ def test_resetfile_with_dir(self):
self.assertEqual('hello', f.read())


class SubmoduleTests(PorcelainTestCase):

def test_empty(self):
porcelain.commit(
repo=self.repo.path,
message=b"init",
author=b"author <email>",
committer=b"committer <email>",
)

self.assertEqual([], list(porcelain.submodule_list(self.repo)))

def test_add(self):
porcelain.submodule_add(self.repo, "../bar.git", "bar")
with open('%s/.gitmodules' % self.repo.path, 'r') as f:
self.assertEqual("""\
[submodule "bar"]
\turl = ../bar.git
\tpath = bar
""", f.read())


class PushTests(PorcelainTestCase):
def test_simple(self):
"""
Expand Down