commit - 3971b9ccd9e783bdb978185ca7ceb03db3da3bf6
commit + 64a1ae22714899eb4237f620a15fa642de198f87
blob - e4fe1c2f831459919e5aff2f7d79553ba64152f5
blob + b348b90793c0825266c1719ceb82d678912668b6
--- NEWS
+++ NEWS
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)
blob - 235f80b996fbd0254f44349709de633d1769f7a9
blob + f95b9f261153cd7fe558ac18c14ddd56b29492f8
--- dulwich/cli.py
+++ dulwich/cli.py
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()
"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,
blob - d34dac7e8a6f3a6113e2b30e97f9f4f27dbe1a1a
blob + e79e90df8ea2fce01ac99c78fa64ceac09c21890
--- dulwich/porcelain.py
+++ dulwich/porcelain.py
* remote{_add}
* receive-pack
* reset
+ * submodule_list
* rev-list
* tag{_create,_delete,_list}
* upload-pack
get_transport_and_path,
)
from dulwich.config import (
+ ConfigFile,
StackedConfig,
)
from dulwich.diff_tree import (
with open_repo_closing(repo) as r:
for entry in r.get_walker(include=[r[c].id for c in commits]):
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
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:
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
)
blob - 6ed9cac2c3b9e595b3eba070fd0ccef979c56148
blob + acd8a6b468cbea39edb1d8a0ac2821978ee490aa
--- dulwich/tests/test_porcelain.py
+++ dulwich/tests/test_porcelain.py
porcelain.reset_file(self.repo, os.path.join('new_dir', 'foo'), target=sha)
with open(full_path, 'r') as f:
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):
blob - /dev/null
blob + dc98d8792450299ff187445bbf197eda758a6580 (mode 644)
--- /dev/null
+++ dulwich/submodule.py
+# config.py - Reading and writing Git config files
+# Copyright (C) 2011-2013 Jelmer Vernooij <jelmer@jelmer.uk>
+#
+# 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