porcelain.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. # porcelain.py -- Porcelain-like layer on top of Dulwich
  2. # Copyright (C) 2013 Jelmer Vernooij <jelmer@samba.org>
  3. #
  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.
  8. #
  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.
  13. #
  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,
  17. # MA 02110-1301, USA.
  18. import os
  19. import sys
  20. from dulwich.client import get_transport_and_path
  21. from dulwich.patch import write_tree_diff
  22. from dulwich.repo import (BaseRepo, Repo)
  23. from dulwich.server import update_server_info as server_update_server_info
  24. """Simple wrapper that provides porcelain-like functions on top of Dulwich.
  25. Currently implemented:
  26. * archive
  27. * add
  28. * clone
  29. * commit
  30. * init
  31. * remove
  32. * update-server-info
  33. * symbolic-ref
  34. These functions are meant to behave similarly to the git subcommands.
  35. Differences in behaviour are considered bugs.
  36. """
  37. __docformat__ = 'restructuredText'
  38. def open_repo(path_or_repo):
  39. """Open an argument that can be a repository or a path for a repository."""
  40. if isinstance(path_or_repo, BaseRepo):
  41. return path_or_repo
  42. return Repo(path_or_repo)
  43. def archive(location, committish=None, outstream=sys.stdout,
  44. errstream=sys.stderr):
  45. """Create an archive.
  46. :param location: Location of repository for which to generate an archive.
  47. :param committish: Commit SHA1 or ref to use
  48. :param outstream: Output stream (defaults to stdout)
  49. :param errstream: Error stream (defaults to stderr)
  50. """
  51. client, path = get_transport_and_path(location)
  52. if committish is None:
  53. committish = "HEAD"
  54. client.archive(path, committish, outstream.write, errstream.write)
  55. def update_server_info(repo="."):
  56. """Update server info files for a repository.
  57. :param repo: path to the repository
  58. """
  59. r = open_repo(repo)
  60. server_update_server_info(r)
  61. def symbolic_ref(repo, ref_name, force=False):
  62. """Set git symbolic ref into HEAD.
  63. :param repo: path to the repository
  64. :param ref_name: short name of the new ref
  65. :param force: force settings without checking if it exists in refs/heads
  66. """
  67. repo_obj = open_repo(repo)
  68. ref_path = 'refs/heads/%s' % ref_name
  69. if not force and ref_path not in repo_obj.refs.keys():
  70. raise ValueError('fatal: ref `%s` is not a ref' % ref_name)
  71. repo_obj.refs.set_symbolic_ref('HEAD', ref_path)
  72. def commit(repo=".", message=None, author=None, committer=None):
  73. """Create a new commit.
  74. :param repo: Path to repository
  75. :param message: Optional commit message
  76. :param author: Optional author name and email
  77. :param committer: Optional committer name and email
  78. :return: SHA1 of the new commit
  79. """
  80. # FIXME: Support --all argument
  81. # FIXME: Support --signoff argument
  82. r = open_repo(repo)
  83. return r.do_commit(message=message, author=author,
  84. committer=committer)
  85. def init(path=".", bare=False):
  86. """Create a new git repository.
  87. :param path: Path to repository.
  88. :param bare: Whether to create a bare repository.
  89. :return: A Repo instance
  90. """
  91. if not os.path.exists(path):
  92. os.mkdir(path)
  93. if bare:
  94. return Repo.init_bare(path)
  95. else:
  96. return Repo.init(path)
  97. def clone(source, target=None, bare=False, outstream=sys.stdout):
  98. """Clone a local or remote git repository.
  99. :param source: Path or URL for source repository
  100. :param target: Path to target repository (optional)
  101. :param bare: Whether or not to create a bare repository
  102. :param outstream: Optional stream to write progress to
  103. :return: The new repository
  104. """
  105. client, host_path = get_transport_and_path(source)
  106. if target is None:
  107. target = host_path.split("/")[-1]
  108. if not os.path.exists(target):
  109. os.mkdir(target)
  110. if bare:
  111. r = Repo.init_bare(target)
  112. else:
  113. r = Repo.init(target)
  114. remote_refs = client.fetch(host_path, r,
  115. determine_wants=r.object_store.determine_wants_all,
  116. progress=outstream.write)
  117. r["HEAD"] = remote_refs["HEAD"]
  118. return r
  119. def add(repo=".", paths=None):
  120. """Add files to the staging area.
  121. :param repo: Repository for the files
  122. :param paths: Paths to add
  123. """
  124. # FIXME: Support patterns, directories, no argument.
  125. r = open_repo(repo)
  126. r.stage(paths)
  127. def rm(repo=".", paths=None):
  128. """Remove files from the staging area.
  129. :param repo: Repository for the files
  130. :param paths: Paths to remove
  131. """
  132. r = open_repo(repo)
  133. index = r.open_index()
  134. for p in paths:
  135. del index[p]
  136. index.write()
  137. def print_commit(commit, outstream):
  138. """Write a human-readable commit log entry.
  139. :param commit: A `Commit` object
  140. :param outstream: A stream file to write to
  141. """
  142. outstream.write("-" * 50 + "\n")
  143. outstream.write("commit: %s\n" % commit.id)
  144. if len(commit.parents) > 1:
  145. outstream.write("merge: %s\n" % "...".join(commit.parents[1:]))
  146. outstream.write("author: %s\n" % commit.author)
  147. outstream.write("committer: %s\n" % commit.committer)
  148. outstream.write("\n")
  149. outstream.write(commit.message + "\n")
  150. outstream.write("\n")
  151. def log(repo=".", outstream=sys.stdout):
  152. """Write commit logs.
  153. :param repo: Path to repository
  154. :param outstream: Stream to write log output to
  155. """
  156. r = open_repo(repo)
  157. walker = r.get_walker()
  158. for entry in walker:
  159. print_commit(entry.commit, outstream)
  160. def show(repo=".", committish=None, outstream=sys.stdout):
  161. """Print the changes in a commit.
  162. :param repo: Path to repository
  163. :param committish: Commit to write
  164. :param outstream: Stream to write to
  165. """
  166. if committish is None:
  167. committish = "HEAD"
  168. r = open_repo(repo)
  169. commit = r[committish]
  170. parent_commit = r[commit.parents[0]]
  171. print_commit(commit, outstream)
  172. write_tree_diff(outstream, r.object_store, parent_commit.tree, commit.tree)
  173. def diff_tree(repo, old_tree, new_tree, outstream=sys.stdout):
  174. """Compares the content and mode of blobs found via two tree objects.
  175. :param repo: Path to repository
  176. :param old_tree: Id of old tree
  177. :param new_tree: Id of new tree
  178. :param outstream: Stream to write to
  179. """
  180. r = open_repo(repo)
  181. write_tree_diff(outstream, r.object_store, old_tree, new_tree)