1
 
# repo.py -- For dealing wih git repositories.
 
2
 
# Copyright (C) 2007 James Westby <jw+debian@jameswestby.net>
 
3
 
# Copyright (C) 2008 Jelmer Vernooij <jelmer@samba.org>
 
5
 
# This program is free software; you can redistribute it and/or
 
6
 
# modify it under the terms of the GNU General Public License
 
7
 
# as published by the Free Software Foundation; version 2
 
10
 
# This program is distributed in the hope that it will be useful,
 
11
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
 
# GNU General Public License for more details.
 
15
 
# You should have received a copy of the GNU General Public License
 
16
 
# along with this program; if not, write to the Free Software
 
17
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 
22
 
from commit import Commit
 
23
 
from errors import MissingCommitError, NotBlobError, NotTreeError, NotCommitError, NotGitRepository
 
24
 
from objects import (ShaFile,
 
29
 
from pack import load_packs, iter_sha1, PackData, write_pack_index_v2
 
39
 
    def __init__(self, name, ref):
 
46
 
  ref_locs = ['', 'refs', 'refs/tags', 'refs/heads', 'refs/remotes']
 
48
 
  def __init__(self, root):
 
49
 
    if os.path.isdir(os.path.join(root, ".git", "objects")):
 
51
 
      self._controldir = os.path.join(root, ".git")
 
52
 
    elif os.path.isdir(os.path.join(root, "objects")):
 
54
 
      self._controldir = root
 
56
 
      raise NotGitRepository(root)
 
58
 
    self.tags = [Tag(name, ref) for name, ref in self.get_tags().items()]
 
59
 
    self._object_store = None
 
62
 
    return self._controldir
 
64
 
  def fetch_objects(self, determine_wants, graph_walker, progress):
 
65
 
    wants = determine_wants(self.heads())
 
67
 
    ref = graph_walker.next()
 
69
 
        commits_to_send.append(ref)
 
70
 
        if ref in self.object_store:
 
72
 
        ref = graph_walker.next()
 
74
 
    for sha in commits_to_send:
 
81
 
        def parse_tree(tree, sha_done):
 
82
 
            for mode, name, x in tree.entries():
 
87
 
                        parse_tree(t, sha_done)
 
92
 
        if treesha not in sha_done:
 
93
 
            t = self.tree(treesha)
 
95
 
            parse_tree(t, sha_done)
 
97
 
        progress("counting objects: %d\r" % len(sha_done))
 
100
 
            yield self.get_object(sha)
 
102
 
  def object_dir(self):
 
103
 
    return os.path.join(self.controldir(), OBJECTDIR)
 
106
 
  def object_store(self):
 
107
 
    if self._object_store is None:
 
108
 
        self._object_store = ObjectStore(self.object_dir())
 
109
 
    return self._object_store
 
112
 
    return os.path.join(self.object_dir(), PACKDIR)
 
114
 
  def _get_ref(self, file):
 
118
 
      if contents.startswith(SYMREF):
 
119
 
        ref = contents[len(SYMREF):]
 
123
 
      assert len(contents) == 41, 'Invalid ref in %s' % file
 
129
 
    for dir in self.ref_locs:
 
130
 
      file = os.path.join(self.controldir(), dir, name)
 
131
 
      if os.path.exists(file):
 
132
 
        return self._get_ref(file)
 
135
 
    ret = {"HEAD": self.head()}
 
136
 
    for dir in ["refs/heads", "refs/tags"]:
 
137
 
        for name in os.listdir(os.path.join(self.controldir(), dir)):
 
138
 
          path = os.path.join(self.controldir(), dir, name)
 
139
 
          if os.path.isfile(path):
 
140
 
            ret["/".join([dir, name])] = self._get_ref(path)
 
143
 
  def set_ref(self, name, value):
 
144
 
    file = os.path.join(self.controldir(), name)
 
145
 
    open(file, 'w').write(value+"\n")
 
147
 
  def remove_ref(self, name):
 
148
 
    file = os.path.join(self.controldir(), name)
 
149
 
    if os.path.exists(file):
 
155
 
    for root, dirs, files in os.walk(os.path.join(self.controldir(), 'refs', 'tags')):
 
157
 
        ret[name] = self._get_ref(os.path.join(root, name))
 
162
 
    for root, dirs, files in os.walk(os.path.join(self.controldir(), 'refs', 'heads')):
 
164
 
        ret[name] = self._get_ref(os.path.join(root, name))
 
168
 
    return self.ref('HEAD')
 
170
 
  def _get_object(self, sha, cls):
 
171
 
    assert len(sha) in (20, 40)
 
172
 
    ret = self.get_object(sha)
 
173
 
    if ret._type != cls._type:
 
175
 
            raise NotCommitError(ret)
 
177
 
            raise NotBlobError(ret)
 
179
 
            raise NotTreeError(ret)
 
181
 
            raise Exception("Type invalid: %r != %r" % (ret._type, cls._type))
 
184
 
  def get_object(self, sha):
 
185
 
    return self.object_store[sha]
 
187
 
  def get_parents(self, sha):
 
188
 
    return self.commit(sha).parents
 
190
 
  def commit(self, sha):
 
191
 
    return self._get_object(sha, Commit)
 
194
 
    return self._get_object(sha, Tree)
 
196
 
  def get_blob(self, sha):
 
197
 
    return self._get_object(sha, Blob)
 
199
 
  def revision_history(self, head):
 
200
 
    """Returns a list of the commits reachable from head.
 
202
 
    Returns a list of commit objects. the first of which will be the commit
 
203
 
    of head, then following theat will be the parents.
 
205
 
    Raises NotCommitError if any no commits are referenced, including if the
 
206
 
    head parameter isn't the sha of a commit.
 
208
 
    XXX: work out how to handle merges.
 
210
 
    # We build the list backwards, as parents are more likely to be older
 
212
 
    pending_commits = [head]
 
214
 
    while pending_commits != []:
 
215
 
      head = pending_commits.pop(0)
 
217
 
          commit = self.commit(head)
 
219
 
        raise MissingCommitError(head)
 
220
 
      if commit in history:
 
223
 
      for known_commit in history:
 
224
 
        if known_commit.commit_time > commit.commit_time:
 
227
 
      history.insert(i, commit)
 
228
 
      parents = commit.parents
 
229
 
      pending_commits += parents
 
234
 
      return "<Repo at %r>" % self.path
 
237
 
  def init_bare(cls, path, mkdir=True):
 
238
 
      for d in [["objects"], 
 
247
 
          os.mkdir(os.path.join(path, *d))
 
248
 
      open(os.path.join(path, 'HEAD'), 'w').write("ref: refs/heads/master\n")
 
249
 
      open(os.path.join(path, 'description'), 'w').write("Unnamed repository")
 
250
 
      open(os.path.join(path, 'info', 'excludes'), 'w').write("")
 
255
 
class ObjectStore(object):
 
257
 
    def __init__(self, path):
 
262
 
        return os.path.join(self.path, PACKDIR)
 
264
 
    def __contains__(self, sha):
 
265
 
        # TODO: This can be more efficient
 
274
 
        """List with pack objects."""
 
275
 
        if self._packs is None:
 
276
 
            self._packs = list(load_packs(self.pack_dir()))
 
279
 
    def _get_shafile(self, sha):
 
282
 
        # Check from object dir
 
283
 
        path = os.path.join(self.path, dir, file)
 
284
 
        if os.path.exists(path):
 
285
 
          return ShaFile.from_file(path)
 
288
 
    def get_raw(self, sha):
 
289
 
        """Obtain the raw text for an object.
 
291
 
        :param sha: Sha for the object.
 
292
 
        :return: tuple with object type and object contents.
 
294
 
        for pack in self.packs:
 
296
 
                return pack.get_raw(sha, self.get_raw)
 
297
 
        # FIXME: Are pack deltas ever against on-disk shafiles ?
 
298
 
        ret = self._get_shafile(sha)
 
300
 
            return ret.as_raw_string()
 
303
 
    def __getitem__(self, sha):
 
304
 
        assert len(sha) == 40, "Incorrect length sha: %s" % str(sha)
 
305
 
        ret = self._get_shafile(sha)
 
309
 
        type, uncomp = self.get_raw(sha)
 
310
 
        return ShaFile.from_raw_string(type, uncomp)
 
312
 
    def move_in_pack(self, path):
 
313
 
        """Move a specific file containing a pack into the pack directory.
 
315
 
        :note: The file should be on the same file system as the 
 
318
 
        :param path: Path to the pack file.
 
321
 
        entries = p.sorted_entries(self.get_raw)
 
322
 
        basename = os.path.join(self.pack_dir(), 
 
323
 
            "pack-%s" % iter_sha1(entry[0] for entry in entries))
 
324
 
        write_pack_index_v2(basename+".idx", entries, p.calculate_checksum())
 
325
 
        os.rename(path, basename + ".pack")
 
328
 
        """Add a new pack to this object store. 
 
330
 
        :return: Fileobject to write to and a commit function to 
 
331
 
            call when the pack is finished.
 
333
 
        fd, path = tempfile.mkstemp(dir=self.pack_dir(), suffix=".pack")
 
334
 
        f = os.fdopen(fd, 'w')
 
336
 
            if os.path.getsize(path) > 0:
 
337
 
                self.move_in_pack(path)