Skip to content

Commit

Permalink
Merge pull request #989 from jelmer/submodule-commands
Browse files Browse the repository at this point in the history
Add basic commands for submodule listing and adding
  • Loading branch information
jelmer authored Jul 11, 2022
2 parents 3971b9c + 2832f4e commit 64a1ae2
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 2 deletions.
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

0 comments on commit 64a1ae2

Please sign in to comment.