1 # porcelain.py -- Porcelain-like layer on top of Dulwich
2 # Copyright (C) 2013 Jelmer Vernooij <jelmer@samba.org>
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # or (at your option) a later version of the License.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22 from dulwich.client import get_transport_and_path
23 from dulwich.patch import write_tree_diff
24 from dulwich.repo import (BaseRepo, Repo)
25 from dulwich.server import update_server_info as server_update_server_info
27 """Simple wrapper that provides porcelain-like functions on top of Dulwich.
29 Currently implemented:
39 These functions are meant to behave similarly to the git subcommands.
40 Differences in behaviour are considered bugs.
43 __docformat__ = 'restructuredText'
46 def open_repo(path_or_repo):
47 """Open an argument that can be a repository or a path for a repository."""
48 if isinstance(path_or_repo, BaseRepo):
50 return Repo(path_or_repo)
53 def archive(location, committish=None, outstream=sys.stdout,
54 errstream=sys.stderr):
57 :param location: Location of repository for which to generate an archive.
58 :param committish: Commit SHA1 or ref to use
59 :param outstream: Output stream (defaults to stdout)
60 :param errstream: Error stream (defaults to stderr)
63 client, path = get_transport_and_path(location)
64 if committish is None:
66 client.archive(path, committish, outstream.write, errstream.write)
69 def update_server_info(repo="."):
70 """Update server info files for a repository.
72 :param repo: path to the repository
75 server_update_server_info(r)
78 def symbolic_ref(repo, ref_name, force=False):
79 """Set git symbolic ref into HEAD.
81 :param repo: path to the repository
82 :param ref_name: short name of the new ref
83 :param force: force settings without checking if it exists in refs/heads
85 repo_obj = open_repo(repo)
86 ref_path = 'refs/heads/%s' % ref_name
87 if not force and ref_path not in repo_obj.refs.keys():
88 raise ValueError('fatal: ref `%s` is not a ref' % ref_name)
89 repo_obj.refs.set_symbolic_ref('HEAD', ref_path)
92 def commit(repo=".", message=None, author=None, committer=None):
93 """Create a new commit.
95 :param repo: Path to repository
96 :param message: Optional commit message
97 :param author: Optional author name and email
98 :param committer: Optional committer name and email
99 :return: SHA1 of the new commit
101 # FIXME: Support --all argument
102 # FIXME: Support --signoff argument
104 return r.do_commit(message=message, author=author,
108 def init(path=".", bare=False):
109 """Create a new git repository.
111 :param path: Path to repository.
112 :param bare: Whether to create a bare repository.
113 :return: A Repo instance
115 if not os.path.exists(path):
119 return Repo.init_bare(path)
121 return Repo.init(path)
124 def clone(source, target=None, bare=False, outstream=sys.stdout):
125 """Clone a local or remote git repository.
127 :param source: Path or URL for source repository
128 :param target: Path to target repository (optional)
129 :param bare: Whether or not to create a bare repository
130 :param outstream: Optional stream to write progress to
131 :return: The new repository
133 client, host_path = get_transport_and_path(source)
136 target = host_path.split("/")[-1]
138 if not os.path.exists(target):
141 r = Repo.init_bare(target)
143 r = Repo.init(target)
144 remote_refs = client.fetch(host_path, r,
145 determine_wants=r.object_store.determine_wants_all,
146 progress=outstream.write)
147 r["HEAD"] = remote_refs["HEAD"]
151 def add(repo=".", paths=None):
152 """Add files to the staging area.
154 :param repo: Repository for the files
155 :param paths: Paths to add
157 # FIXME: Support patterns, directories, no argument.
162 def rm(repo=".", paths=None):
163 """Remove files from the staging area.
165 :param repo: Repository for the files
166 :param paths: Paths to remove
169 index = r.open_index()
175 def print_commit(commit, outstream):
176 """Write a human-readable commit log entry.
178 :param commit: A `Commit` object
179 :param outstream: A stream file to write to
181 outstream.write("-" * 50 + "\n")
182 outstream.write("commit: %s\n" % commit.id)
183 if len(commit.parents) > 1:
184 outstream.write("merge: %s\n" % "...".join(commit.parents[1:]))
185 outstream.write("author: %s\n" % commit.author)
186 outstream.write("committer: %s\n" % commit.committer)
187 outstream.write("\n")
188 outstream.write(commit.message + "\n")
189 outstream.write("\n")
192 def log(repo=".", outstream=sys.stdout):
193 """Write commit logs.
195 :param repo: Path to repository
196 :param outstream: Stream to write log output to
199 walker = r.get_walker()
201 print_commit(entry.commit, outstream)
204 def show(repo=".", committish=None, outstream=sys.stdout):
205 """Print the changes in a commit.
207 :param repo: Path to repository
208 :param committish: Commit to write
209 :param outstream: Stream to write to
211 if committish is None:
214 commit = r[committish]
215 parent_commit = r[commit.parents[0]]
216 print_commit(commit, outstream)
217 write_tree_diff(outstream, r.object_store, parent_commit.tree, commit.tree)