porcelain.py 7.1 KB

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