/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to dulwich/dulwich/repo.py

  • Committer: Jelmer Vernooij
  • Date: 2006-09-12 21:25:01 UTC
  • mto: (2001.1.2 jam-integration)
  • mto: This revision was merged to the branch mainline in revision 2002.
  • Revision ID: jelmer@samba.org-20060912212501-350ff35ed68bc2fb
Fix typo in encoding warning.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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>
4
 
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
8
 
# of the License.
9
 
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.
14
 
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,
18
 
# MA  02110-1301, USA.
19
 
 
20
 
import os
21
 
 
22
 
from commit import Commit
23
 
from errors import (
24
 
        MissingCommitError, 
25
 
        NotBlobError, 
26
 
        NotCommitError, 
27
 
        NotGitRepository,
28
 
        NotTreeError, 
29
 
        )
30
 
from object_store import ObjectStore
31
 
from objects import (
32
 
        ShaFile,
33
 
        Commit,
34
 
        Tree,
35
 
        Blob,
36
 
        )
37
 
 
38
 
OBJECTDIR = 'objects'
39
 
SYMREF = 'ref: '
40
 
 
41
 
 
42
 
class Tag(object):
43
 
 
44
 
    def __init__(self, name, ref):
45
 
        self.name = name
46
 
        self.ref = ref
47
 
 
48
 
 
49
 
class Repo(object):
50
 
 
51
 
  ref_locs = ['', 'refs', 'refs/tags', 'refs/heads', 'refs/remotes']
52
 
 
53
 
  def __init__(self, root):
54
 
    if os.path.isdir(os.path.join(root, ".git", "objects")):
55
 
      self.bare = False
56
 
      self._controldir = os.path.join(root, ".git")
57
 
    elif os.path.isdir(os.path.join(root, "objects")):
58
 
      self.bare = True
59
 
      self._controldir = root
60
 
    else:
61
 
      raise NotGitRepository(root)
62
 
    self.path = root
63
 
    self.tags = [Tag(name, ref) for name, ref in self.get_tags().items()]
64
 
    self._object_store = None
65
 
 
66
 
  def controldir(self):
67
 
    return self._controldir
68
 
 
69
 
  def find_missing_objects(self, determine_wants, graph_walker, progress):
70
 
    """Fetch the missing objects required for a set of revisions.
71
 
 
72
 
    :param determine_wants: Function that takes a dictionary with heads 
73
 
        and returns the list of heads to fetch.
74
 
    :param graph_walker: Object that can iterate over the list of revisions 
75
 
        to fetch and has an "ack" method that will be called to acknowledge 
76
 
        that a revision is present.
77
 
    :param progress: Simple progress function that will be called with 
78
 
        updated progress strings.
79
 
    """
80
 
    wants = determine_wants(self.get_refs())
81
 
    commits_to_send = set(wants)
82
 
    sha_done = set()
83
 
    ref = graph_walker.next()
84
 
    while ref:
85
 
        sha_done.add(ref)
86
 
        if ref in self.object_store:
87
 
            graph_walker.ack(ref)
88
 
        ref = graph_walker.next()
89
 
    while commits_to_send:
90
 
        sha = commits_to_send.pop()
91
 
        if sha in sha_done:
92
 
            continue
93
 
 
94
 
        c = self.commit(sha)
95
 
        assert isinstance(c, Commit)
96
 
        sha_done.add(sha)
97
 
 
98
 
        commits_to_send.update([p for p in c.parents if not p in sha_done])
99
 
 
100
 
        def parse_tree(tree, sha_done):
101
 
            for mode, name, x in tree.entries():
102
 
                if not x in sha_done:
103
 
                    try:
104
 
                        t = self.tree(x)
105
 
                        sha_done.add(x)
106
 
                        parse_tree(t, sha_done)
107
 
                    except:
108
 
                        sha_done.add(x)
109
 
 
110
 
        treesha = c.tree
111
 
        if treesha not in sha_done:
112
 
            t = self.tree(treesha)
113
 
            sha_done.add(treesha)
114
 
            parse_tree(t, sha_done)
115
 
 
116
 
        progress("counting objects: %d\r" % len(sha_done))
117
 
    return sha_done
118
 
 
119
 
  def fetch_objects(self, determine_wants, graph_walker, progress):
120
 
    """Fetch the missing objects required for a set of revisions.
121
 
 
122
 
    :param determine_wants: Function that takes a dictionary with heads 
123
 
        and returns the list of heads to fetch.
124
 
    :param graph_walker: Object that can iterate over the list of revisions 
125
 
        to fetch and has an "ack" method that will be called to acknowledge 
126
 
        that a revision is present.
127
 
    :param progress: Simple progress function that will be called with 
128
 
        updated progress strings.
129
 
    """
130
 
    shas = self.find_missing_objects(determine_wants, graph_walker, progress)
131
 
    for sha in shas:
132
 
        yield self.get_object(sha)
133
 
 
134
 
  def object_dir(self):
135
 
    return os.path.join(self.controldir(), OBJECTDIR)
136
 
 
137
 
  @property
138
 
  def object_store(self):
139
 
    if self._object_store is None:
140
 
        self._object_store = ObjectStore(self.object_dir())
141
 
    return self._object_store
142
 
 
143
 
  def pack_dir(self):
144
 
    return os.path.join(self.object_dir(), PACKDIR)
145
 
 
146
 
  def _get_ref(self, file):
147
 
    f = open(file, 'rb')
148
 
    try:
149
 
      contents = f.read()
150
 
      if contents.startswith(SYMREF):
151
 
        ref = contents[len(SYMREF):]
152
 
        if ref[-1] == '\n':
153
 
          ref = ref[:-1]
154
 
        return self.ref(ref)
155
 
      assert len(contents) == 41, 'Invalid ref in %s' % file
156
 
      return contents[:-1]
157
 
    finally:
158
 
      f.close()
159
 
 
160
 
  def ref(self, name):
161
 
    for dir in self.ref_locs:
162
 
      file = os.path.join(self.controldir(), dir, name)
163
 
      if os.path.exists(file):
164
 
        return self._get_ref(file)
165
 
 
166
 
  def get_refs(self):
167
 
    ret = {}
168
 
    if self.head():
169
 
        ret['HEAD'] = self.head()
170
 
    for dir in ["refs/heads", "refs/tags"]:
171
 
        for name in os.listdir(os.path.join(self.controldir(), dir)):
172
 
          path = os.path.join(self.controldir(), dir, name)
173
 
          if os.path.isfile(path):
174
 
            ret["/".join([dir, name])] = self._get_ref(path)
175
 
    return ret
176
 
 
177
 
  def set_ref(self, name, value):
178
 
    file = os.path.join(self.controldir(), name)
179
 
    open(file, 'w').write(value+"\n")
180
 
 
181
 
  def remove_ref(self, name):
182
 
    file = os.path.join(self.controldir(), name)
183
 
    if os.path.exists(file):
184
 
      os.remove(file)
185
 
      return
186
 
 
187
 
  def get_tags(self):
188
 
    ret = {}
189
 
    for root, dirs, files in os.walk(os.path.join(self.controldir(), 'refs', 'tags')):
190
 
      for name in files:
191
 
        ret[name] = self._get_ref(os.path.join(root, name))
192
 
    return ret
193
 
 
194
 
  def heads(self):
195
 
    ret = {}
196
 
    for root, dirs, files in os.walk(os.path.join(self.controldir(), 'refs', 'heads')):
197
 
      for name in files:
198
 
        ret[name] = self._get_ref(os.path.join(root, name))
199
 
    return ret
200
 
 
201
 
  def head(self):
202
 
    return self.ref('HEAD')
203
 
 
204
 
  def _get_object(self, sha, cls):
205
 
    assert len(sha) in (20, 40)
206
 
    ret = self.get_object(sha)
207
 
    if ret._type != cls._type:
208
 
        if cls is Commit:
209
 
            raise NotCommitError(ret)
210
 
        elif cls is Blob:
211
 
            raise NotBlobError(ret)
212
 
        elif cls is Tree:
213
 
            raise NotTreeError(ret)
214
 
        else:
215
 
            raise Exception("Type invalid: %r != %r" % (ret._type, cls._type))
216
 
    return ret
217
 
 
218
 
  def get_object(self, sha):
219
 
    return self.object_store[sha]
220
 
 
221
 
  def get_parents(self, sha):
222
 
    return self.commit(sha).parents
223
 
 
224
 
  def commit(self, sha):
225
 
    return self._get_object(sha, Commit)
226
 
 
227
 
  def tree(self, sha):
228
 
    return self._get_object(sha, Tree)
229
 
 
230
 
  def get_blob(self, sha):
231
 
    return self._get_object(sha, Blob)
232
 
 
233
 
  def revision_history(self, head):
234
 
    """Returns a list of the commits reachable from head.
235
 
 
236
 
    Returns a list of commit objects. the first of which will be the commit
237
 
    of head, then following theat will be the parents.
238
 
 
239
 
    Raises NotCommitError if any no commits are referenced, including if the
240
 
    head parameter isn't the sha of a commit.
241
 
 
242
 
    XXX: work out how to handle merges.
243
 
    """
244
 
    # We build the list backwards, as parents are more likely to be older
245
 
    # than children
246
 
    pending_commits = [head]
247
 
    history = []
248
 
    while pending_commits != []:
249
 
      head = pending_commits.pop(0)
250
 
      try:
251
 
          commit = self.commit(head)
252
 
      except KeyError:
253
 
        raise MissingCommitError(head)
254
 
      if commit in history:
255
 
        continue
256
 
      i = 0
257
 
      for known_commit in history:
258
 
        if known_commit.commit_time > commit.commit_time:
259
 
          break
260
 
        i += 1
261
 
      history.insert(i, commit)
262
 
      parents = commit.parents
263
 
      pending_commits += parents
264
 
    history.reverse()
265
 
    return history
266
 
 
267
 
  def __repr__(self):
268
 
      return "<Repo at %r>" % self.path
269
 
 
270
 
  @classmethod
271
 
  def init(cls, path, mkdir=True):
272
 
      controldir = os.path.join(path, ".git")
273
 
      os.mkdir(controldir)
274
 
      cls.init_bare(controldir)
275
 
 
276
 
  @classmethod
277
 
  def init_bare(cls, path, mkdir=True):
278
 
      for d in [["objects"], 
279
 
                ["objects", "info"], 
280
 
                ["objects", "pack"],
281
 
                ["branches"],
282
 
                ["refs"],
283
 
                ["refs", "tags"],
284
 
                ["refs", "heads"],
285
 
                ["hooks"],
286
 
                ["info"]]:
287
 
          os.mkdir(os.path.join(path, *d))
288
 
      open(os.path.join(path, 'HEAD'), 'w').write("ref: refs/heads/master\n")
289
 
      open(os.path.join(path, 'description'), 'w').write("Unnamed repository")
290
 
      open(os.path.join(path, 'info', 'excludes'), 'w').write("")
291
 
 
292
 
  create = init_bare
293
 
 
294
 
 
295