88c4078ca3aabeaf8492ddc523406f5cee091a3b
[jelmer/dulwich-libgit2.git] / git / repository.py
1 # repository.py -- For dealing wih git repositories.
2 # Copyright (C) 2007 James Westby <jw+debian@jameswestby.net>
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; version 2
7 # 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
19 import os
20
21 from errors import MissingCommitError
22 from objects import (ShaFile,
23                      Commit,
24                      Tree,
25                      Blob,
26                      )
27
28 objectdir = 'objects'
29 symref = 'ref: '
30
31 class Repository(object):
32
33   ref_locs = ['', 'refs', 'refs/tags', 'refs/heads', 'refs/remotes']
34
35   def __init__(self, root):
36     controldir = os.path.join(root, ".git")
37     if os.path.exists(os.path.join(controldir, "objects")):
38       self.bare = False
39       self._basedir = controldir
40     else:
41       self.bare = True
42       self._basedir = root
43
44   def basedir(self):
45     return self._basedir
46
47   def object_dir(self):
48     return os.path.join(self.basedir(), objectdir)
49
50   def _get_ref(self, file):
51     f = open(file, 'rb')
52     try:
53       contents = f.read()
54       if contents.startswith(symref):
55         ref = contents[len(symref):]
56         if ref[-1] == '\n':
57           ref = ref[:-1]
58         return self.ref(ref)
59       assert len(contents) == 41, 'Invalid ref'
60       return contents[:-1]
61     finally:
62       f.close()
63
64   def ref(self, name):
65     for dir in self.ref_locs:
66       file = os.path.join(self.basedir(), dir, name)
67       if os.path.exists(file):
68         return self._get_ref(file)
69
70   def head(self):
71     return self.ref('HEAD')
72
73   def _get_object(self, sha, cls):
74     assert len(sha) == 40, "Incorrect length sha: %s" % str(sha)
75     dir = sha[:2]
76     file = sha[2:]
77     path = os.path.join(self.object_dir(), dir, file)
78     if not os.path.exists(path):
79       # Should this raise instead?
80       return None
81     return cls.from_file(path)
82
83   def get_object(self, sha):
84     return self._get_object(sha, ShaFile)
85
86   def get_commit(self, sha):
87     return self._get_object(sha, Commit)
88
89   def get_tree(self, sha):
90     return self._get_object(sha, Tree)
91
92   def get_blob(self, sha):
93     return self._get_object(sha, Blob)
94
95   def revision_history(self, head):
96     """Returns a list of the commits reachable from head.
97
98     Returns a list of commit objects. the first of which will be the commit
99     of head, then following theat will be the parents.
100
101     Raises NotCommitError if any no commits are referenced, including if the
102     head parameter isn't the sha of a commit.
103
104     XXX: work out how to handle merges.
105     """
106     # We build the list backwards, as parents are more likely to be older
107     # than children
108     pending_commits = [head]
109     history = []
110     while pending_commits != []:
111       head = pending_commits.pop(0)
112       commit = self.get_commit(head)
113       if commit is None:
114         raise MissingCommitError(head)
115       if commit in history:
116         continue
117       i = 0
118       for known_commit in history:
119         if known_commit.commit_time() > commit.commit_time():
120           break
121         i += 1
122       history.insert(i, commit)
123       parents = commit.parents()
124       pending_commits += parents
125     history.reverse()
126     return history
127