by
Changes to 5 files · Browse files at 91b8507f80dd Showing diff from parent dd6998855ad9 c71b6367faf9 Diff from another changeset...
|
|
@@ -30,20 +30,34 @@ import sys
from getopt import getopt
+from dulwich import porcelain
from dulwich.client import get_transport_and_path
from dulwich.errors import ApplyDeltaError
from dulwich.index import Index
from dulwich.pack import Pack, sha_to_hex
from dulwich.patch import write_tree_diff
from dulwich.repo import Repo
-from dulwich.server import update_server_info
def cmd_archive(args):
opts, args = getopt(args, "", [])
client, path = get_transport_and_path(args.pop(0))
+ location = args.pop(0)
committish = args.pop(0)
- client.archive(path, committish, sys.stdout.write, sys.stderr.write)
+ porcelain.archive(location, committish, outstream=sys.stdout,
+ errstream=sys.stderr)
+
+
+def cmd_add(args):
+ opts, args = getopt(args, "", [])
+
+ porcelain.add(".", paths=args)
+
+
+def cmd_rm(args):
+ opts, args = getopt(args, "", [])
+
+ porcelain.rm(".", paths=args)
def cmd_fetch_pack(args):
@@ -77,26 +91,7 @@ path = args.pop(0)
else:
path = "."
- r = Repo(path)
- todo = [r.head()]
- done = set()
- while todo:
- sha = todo.pop()
- assert isinstance(sha, str)
- if sha in done:
- continue
- done.add(sha)
- commit = r[sha]
- print "-" * 50
- print "commit: %s" % sha
- if len(commit.parents) > 1:
- print "merge: %s" % "...".join(commit.parents[1:])
- print "author: %s" % commit.author
- print "committer: %s" % commit.committer
- print ""
- print commit.message
- print ""
- todo.extend([p for p in commit.parents if p not in done])
+ porcelain.log(repo=path, outstream=sys.stdout)
def cmd_diff(args):
@@ -159,52 +154,39 @@ else:
path = args[0]
- if not os.path.exists(path):
- os.mkdir(path)
-
- if "--bare" in opts:
- Repo.init_bare(path)
- else:
- Repo.init(path)
+ porcelain.init(path, bare=("--bare" in opts))
def cmd_clone(args):
- opts, args = getopt(args, "", [])
+ opts, args = getopt(args, "", ["bare"])
opts = dict(opts)
if args == []:
print "usage: dulwich clone host:path [PATH]"
sys.exit(1)
- client, host_path = get_transport_and_path(args.pop(0))
-
+
+ source = args.pop(0)
if len(args) > 0:
- path = args.pop(0)
- else:
- path = host_path.split("/")[-1]
-
- if not os.path.exists(path):
- os.mkdir(path)
- r = Repo.init(path)
- remote_refs = client.fetch(host_path, r,
- determine_wants=r.object_store.determine_wants_all,
- progress=sys.stdout.write)
- r["HEAD"] = remote_refs["HEAD"]
+ target = args.pop(0)
+ else:
+ target = None
+
+ porcelain.clone(source, target, bare=("--bare" in opts))
def cmd_commit(args):
opts, args = getopt(args, "", ["message"])
opts = dict(opts)
- r = Repo(".")
- committer = "%s <%s>" % (os.getenv("GIT_COMMITTER_NAME"),
- os.getenv("GIT_COMMITTER_EMAIL"))
- author = "%s <%s>" % (os.getenv("GIT_AUTHOR_NAME"),
- os.getenv("GIT_AUTHOR_EMAIL"))
- r.do_commit(committer=committer, author=author, message=opts["--message"])
+ porcelain.commit(".", message=opts["--message"])
def cmd_update_server_info(args):
- r = Repo(".")
- update_server_info(r)
+ porcelain.update_server_info(".")
+
+
+def cmd_show(args):
+ opts, args = getopt(args, "", [])
+ porcelain.show(".")
commands = {
@@ -219,6 +201,9 @@ "archive": cmd_archive,
"update-server-info": cmd_update_server_info,
"diff": cmd_diff,
+ "add": cmd_add,
+ "rm": cmd_rm,
+ "show": cmd_show,
}
if len(sys.argv) < 2:
|
|
|
@@ -1,0 +1,202 @@ + # porcelain.py -- Porcelain-like layer on top of Dulwich
+# Copyright (C) 2013 Jelmer Vernooij <jelmer@samba.org>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# or (at your option) a later version of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+import os
+import sys
+
+from dulwich.client import get_transport_and_path
+from dulwich.patch import write_tree_diff
+from dulwich.repo import (BaseRepo, Repo)
+from dulwich.server import update_server_info as server_update_server_info
+
+"""Simple wrapper that provides porcelain-like functions on top of Dulwich.
+
+Currently implemented:
+ * archive
+ * add
+ * clone
+ * commit
+ * init
+ * remove
+ * update-server-info
+
+These functions are meant to behave similarly to the git subcommands.
+Differences in behaviour are considered bugs.
+"""
+
+__docformat__ = 'restructuredText'
+
+
+def open_repo(path_or_repo):
+ """Open an argument that can be a repository or a path for a repository."""
+ if isinstance(path_or_repo, BaseRepo):
+ return path_or_repo
+ return Repo(path_or_repo)
+
+
+def archive(location, committish=None, outstream=sys.stdout,
+ errstream=sys.stderr):
+ """Create an archive.
+
+ :param location: Location of repository for which to generate an archive.
+ :param committish: Commit SHA1 or ref to use
+ :param outstream: Output stream (defaults to stdout)
+ :param errstream: Error stream (defaults to stderr)
+ """
+
+ client, path = get_transport_and_path(location)
+ if committish is None:
+ committish = "HEAD"
+ client.archive(path, committish, outstream.write, errstream.write)
+
+
+def update_server_info(repo="."):
+ """Update server info files for a repository.
+
+ :param repo: path to the repository
+ """
+ r = open_repo(repo)
+ server_update_server_info(r)
+
+
+def commit(repo=".", message=None, author=None, committer=None):
+ """Create a new commit.
+
+ :param repo: Path to repository
+ :param message: Optional commit message
+ :param author: Optional author name and email
+ :param committer: Optional committer name and email
+ :return: SHA1 of the new commit
+ """
+ # FIXME: Support --all argument
+ # FIXME: Support --signoff argument
+ r = open_repo(repo)
+ return r.do_commit(message=message, author=author,
+ committer=committer)
+
+
+def init(path=".", bare=False):
+ """Create a new git repository.
+
+ :param path: Path to repository.
+ :param bare: Whether to create a bare repository.
+ :return: A Repo instance
+ """
+ if not os.path.exists(path):
+ os.mkdir(path)
+
+ if bare:
+ return Repo.init_bare(path)
+ else:
+ return Repo.init(path)
+
+
+def clone(source, target=None, bare=False, outstream=sys.stdout):
+ """Clone a local or remote git repository.
+
+ :param source: Path or URL for source repository
+ :param target: Path to target repository (optional)
+ :param bare: Whether or not to create a bare repository
+ :param outstream: Optional stream to write progress to
+ :return: The new repository
+ """
+ client, host_path = get_transport_and_path(source)
+
+ if target is None:
+ target = host_path.split("/")[-1]
+
+ if not os.path.exists(target):
+ os.mkdir(target)
+ if bare:
+ r = Repo.init_bare(target)
+ else:
+ r = Repo.init(target)
+ remote_refs = client.fetch(host_path, r,
+ determine_wants=r.object_store.determine_wants_all,
+ progress=outstream.write)
+ r["HEAD"] = remote_refs["HEAD"]
+ return r
+
+
+def add(repo=".", paths=None):
+ """Add files to the staging area.
+
+ :param repo: Repository for the files
+ :param paths: Paths to add
+ """
+ # FIXME: Support patterns, directories, no argument.
+ r = open_repo(repo)
+ r.stage(paths)
+
+
+def rm(repo=".", paths=None):
+ """Remove files from the staging area.
+
+ :param repo: Repository for the files
+ :param paths: Paths to remove
+ """
+ r = open_repo(repo)
+ index = r.open_index()
+ for p in paths:
+ del index[p]
+ index.write()
+
+
+def print_commit(commit, outstream):
+ """Write a human-readable commit log entry.
+
+ :param commit: A `Commit` object
+ :param outstream: A stream file to write to
+ """
+ outstream.write("-" * 50 + "\n")
+ outstream.write("commit: %s\n" % commit.id)
+ if len(commit.parents) > 1:
+ outstream.write("merge: %s\n" % "...".join(commit.parents[1:]))
+ outstream.write("author: %s\n" % commit.author)
+ outstream.write("committer: %s\n" % commit.committer)
+ outstream.write("\n")
+ outstream.write(commit.message + "\n")
+ outstream.write("\n")
+
+
+def log(repo=".", outstream=sys.stdout):
+ """Write commit logs.
+
+ :param repo: Path to repository
+ :param outstream: Stream to write log output to
+ """
+ r = open_repo(repo)
+ walker = r.get_walker()
+ for entry in walker:
+ print_commit(entry.commit, outstream)
+
+
+def show(repo=".", committish=None, outstream=sys.stdout):
+ """Print the changes in a commit.
+
+ :param repo: Path to repository
+ :param committish: Commit to write
+ :param outstream: Stream to write to
+ """
+ if committish is None:
+ committish = "HEAD"
+ r = open_repo(repo)
+ commit = r[committish]
+ parent_commit = r[commit.parents[0]]
+ print_commit(commit, outstream)
+ write_tree_diff(outstream, r.object_store, parent_commit.tree, commit.tree)
|
@@ -864,6 +864,7 @@ :param determine_wants: Optional function to determine what refs to
fetch.
:param progress: Optional progress function
+ :return: The local refs
"""
if determine_wants is None:
determine_wants = lambda heads: heads.values()
@@ -1212,9 +1213,12 @@ # FIXME: Read merge heads from .git/MERGE_HEADS
merge_heads = []
if committer is None:
+ # FIXME: Support GIT_COMMITTER_NAME/GIT_COMMITTER_EMAIL environment
+ # variables
committer = self._get_user_identity()
c.committer = committer
if commit_timestamp is None:
+ # FIXME: Support GIT_COMMITTER_DATE environment variable
commit_timestamp = time.time()
c.commit_time = int(commit_timestamp)
if commit_timezone is None:
@@ -1222,9 +1226,12 @@ commit_timezone = 0
c.commit_timezone = commit_timezone
if author is None:
+ # FIXME: Support GIT_AUTHOR_NAME/GIT_AUTHOR_EMAIL environment
+ # variables
author = committer
c.author = author
if author_timestamp is None:
+ # FIXME: Support GIT_AUTHOR_DATE environment variable
author_timestamp = commit_timestamp
c.author_time = int(author_timestamp)
if author_timezone is None:
|
@@ -125,6 +125,7 @@ 'missing_obj_finder',
'pack',
'patch',
+ 'porcelain',
'protocol',
'repository',
'server',
|
|
|
@@ -1,0 +1,162 @@ + # test_porcelain.py -- porcelain tests
+# Copyright (C) 2013 Jelmer Vernooij <jelmer@samba.org>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; version 2
+# of the License or (at your option) a later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+"""Tests for dulwich.porcelain."""
+
+from cStringIO import StringIO
+import os
+import shutil
+import tarfile
+import tempfile
+
+from dulwich import porcelain
+from dulwich.repo import Repo
+from dulwich.tests import (
+ TestCase,
+ )
+from dulwich.tests.utils import (
+ build_commit_graph,
+ )
+
+
+class PorcelainTestCase(TestCase):
+
+ def setUp(self):
+ super(TestCase, self).setUp()
+ repo_dir = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, repo_dir)
+ self.repo = Repo.init(repo_dir)
+
+
+class ArchiveTests(PorcelainTestCase):
+ """Tests for the archive command."""
+
+ def test_simple(self):
+ c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1], [3, 1, 2]])
+ self.repo.refs["refs/heads/master"] = c3.id
+ out = StringIO()
+ err = StringIO()
+ porcelain.archive(self.repo.path, "refs/heads/master", outstream=out,
+ errstream=err)
+ self.assertEquals("", err.getvalue())
+ tf = tarfile.TarFile(fileobj=out)
+ self.addCleanup(tf.close)
+ self.assertEquals([], tf.getnames())
+
+
+class UpdateServerInfoTests(PorcelainTestCase):
+
+ def test_simple(self):
+ c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
+ [3, 1, 2]])
+ self.repo.refs["refs/heads/foo"] = c3.id
+ porcelain.update_server_info(self.repo.path)
+ self.assertTrue(os.path.exists(os.path.join(self.repo.controldir(),
+ 'info', 'refs')))
+
+
+class CommitTests(PorcelainTestCase):
+
+ def test_simple(self):
+ c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
+ [3, 1, 2]])
+ self.repo.refs["refs/heads/foo"] = c3.id
+ sha = porcelain.commit(self.repo.path, message="Some message")
+ self.assertTrue(type(sha) is str)
+ self.assertEquals(len(sha), 40)
+
+ def test_custom_author(self):
+ c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
+ [3, 1, 2]])
+ self.repo.refs["refs/heads/foo"] = c3.id
+ sha = porcelain.commit(self.repo.path, message="Some message",
+ author="Joe <joe@example.com>", committer="Bob <bob@example.com>")
+ self.assertTrue(type(sha) is str)
+ self.assertEquals(len(sha), 40)
+
+
+class CloneTests(PorcelainTestCase):
+
+ def test_simple_local(self):
+ c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
+ [3, 1, 2]])
+ self.repo.refs["refs/heads/master"] = c3.id
+ target_path = tempfile.mkdtemp()
+ outstream = StringIO()
+ self.addCleanup(shutil.rmtree, target_path)
+ r = porcelain.clone(self.repo.path, target_path, outstream=outstream)
+ self.assertEquals(r.path, target_path)
+ self.assertEquals(Repo(target_path).head(), c3.id)
+
+
+class InitTests(TestCase):
+
+ def test_non_bare(self):
+ repo_dir = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, repo_dir)
+ porcelain.init(repo_dir)
+
+ def test_bare(self):
+ repo_dir = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, repo_dir)
+ porcelain.init(repo_dir, bare=True)
+
+
+class AddTests(PorcelainTestCase):
+
+ def test_add_file(self):
+ f = open(os.path.join(self.repo.path, 'foo'), 'w')
+ try:
+ f.write("BAR")
+ finally:
+ f.close()
+ porcelain.add(self.repo.path, paths=["foo"])
+
+
+class RemoveTests(PorcelainTestCase):
+
+ def test_remove_file(self):
+ f = open(os.path.join(self.repo.path, 'foo'), 'w')
+ try:
+ f.write("BAR")
+ finally:
+ f.close()
+ porcelain.add(self.repo.path, paths=["foo"])
+ porcelain.rm(self.repo.path, paths=["foo"])
+
+
+class LogTests(PorcelainTestCase):
+
+ def test_simple(self):
+ c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
+ [3, 1, 2]])
+ self.repo.refs["HEAD"] = c3.id
+ outstream = StringIO()
+ porcelain.log(self.repo.path, outstream=outstream)
+ self.assertTrue(outstream.getvalue().startswith("-" * 50))
+
+
+class ShowTests(PorcelainTestCase):
+
+ def test_simple(self):
+ c1, c2, c3 = build_commit_graph(self.repo.object_store, [[1], [2, 1],
+ [3, 1, 2]])
+ self.repo.refs["HEAD"] = c3.id
+ outstream = StringIO()
+ porcelain.show(self.repo.path, committish=c3.id, outstream=outstream)
+ self.assertTrue(outstream.getvalue().startswith("-" * 50))
|
Loading...