/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: Canonical.com Patch Queue Manager
  • Date: 2010-02-11 04:02:41 UTC
  • mfrom: (5017.2.2 tariff)
  • Revision ID: pqm@pqm.ubuntu.com-20100211040241-w6n021dz0uus341n
(mbp) add import-tariff tests

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 objects import (
31
 
        ShaFile,
32
 
        Commit,
33
 
        Tree,
34
 
        Blob,
35
 
        )
36
 
from pack import (
37
 
        iter_sha1, 
38
 
        load_packs, 
39
 
        write_pack_index_v2,
40
 
        PackData, 
41
 
        )
42
 
import tempfile
43
 
 
44
 
OBJECTDIR = 'objects'
45
 
PACKDIR = 'pack'
46
 
SYMREF = 'ref: '
47
 
 
48
 
 
49
 
class Tag(object):
50
 
 
51
 
    def __init__(self, name, ref):
52
 
        self.name = name
53
 
        self.ref = ref
54
 
 
55
 
 
56
 
class Repo(object):
57
 
 
58
 
  ref_locs = ['', 'refs', 'refs/tags', 'refs/heads', 'refs/remotes']
59
 
 
60
 
  def __init__(self, root):
61
 
    if os.path.isdir(os.path.join(root, ".git", "objects")):
62
 
      self.bare = False
63
 
      self._controldir = os.path.join(root, ".git")
64
 
    elif os.path.isdir(os.path.join(root, "objects")):
65
 
      self.bare = True
66
 
      self._controldir = root
67
 
    else:
68
 
      raise NotGitRepository(root)
69
 
    self.path = root
70
 
    self.tags = [Tag(name, ref) for name, ref in self.get_tags().items()]
71
 
    self._object_store = None
72
 
 
73
 
  def controldir(self):
74
 
    return self._controldir
75
 
 
76
 
  def find_missing_objects(self, determine_wants, graph_walker, progress):
77
 
    """Fetch the missing objects required for a set of revisions.
78
 
 
79
 
    :param determine_wants: Function that takes a dictionary with heads 
80
 
        and returns the list of heads to fetch.
81
 
    :param graph_walker: Object that can iterate over the list of revisions 
82
 
        to fetch and has an "ack" method that will be called to acknowledge 
83
 
        that a revision is present.
84
 
    :param progress: Simple progress function that will be called with 
85
 
        updated progress strings.
86
 
    """
87
 
    wants = determine_wants(self.heads())
88
 
    commits_to_send = set(wants)
89
 
    ref = graph_walker.next()
90
 
    while ref:
91
 
        commits_to_send.add(ref)
92
 
        if ref in self.object_store:
93
 
            graph_walker.ack(ref)
94
 
        ref = graph_walker.next()
95
 
    sha_done = set()
96
 
    for sha in commits_to_send:
97
 
        if sha in sha_done:
98
 
            continue
99
 
 
100
 
        c = self.commit(sha)
101
 
        assert isinstance(c, Commit)
102
 
        sha_done.add(sha)
103
 
 
104
 
        def parse_tree(tree, sha_done):
105
 
            for mode, name, x in tree.entries():
106
 
                if not x in sha_done:
107
 
                    try:
108
 
                        t = self.tree(x)
109
 
                        sha_done.add(x)
110
 
                        parse_tree(t, sha_done)
111
 
                    except:
112
 
                        sha_done.add(x)
113
 
 
114
 
        treesha = c.tree
115
 
        if treesha not in sha_done:
116
 
            t = self.tree(treesha)
117
 
            sha_done.add(treesha)
118
 
            parse_tree(t, sha_done)
119
 
 
120
 
        progress("counting objects: %d\r" % len(sha_done))
121
 
    return sha_done
122
 
 
123
 
  def fetch_objects(self, determine_wants, graph_walker, progress):
124
 
    """Fetch the missing objects required for a set of revisions.
125
 
 
126
 
    :param determine_wants: Function that takes a dictionary with heads 
127
 
        and returns the list of heads to fetch.
128
 
    :param graph_walker: Object that can iterate over the list of revisions 
129
 
        to fetch and has an "ack" method that will be called to acknowledge 
130
 
        that a revision is present.
131
 
    :param progress: Simple progress function that will be called with 
132
 
        updated progress strings.
133
 
    """
134
 
    shas = self.find_missing_objects(determine_wants, graph_walker, progress)
135
 
    for sha in shas:
136
 
        yield self.get_object(sha)
137
 
 
138
 
  def object_dir(self):
139
 
    return os.path.join(self.controldir(), OBJECTDIR)
140
 
 
141
 
  @property
142
 
  def object_store(self):
143
 
    if self._object_store is None:
144
 
        self._object_store = ObjectStore(self.object_dir())
145
 
    return self._object_store
146
 
 
147
 
  def pack_dir(self):
148
 
    return os.path.join(self.object_dir(), PACKDIR)
149
 
 
150
 
  def _get_ref(self, file):
151
 
    f = open(file, 'rb')
152
 
    try:
153
 
      contents = f.read()
154
 
      if contents.startswith(SYMREF):
155
 
        ref = contents[len(SYMREF):]
156
 
        if ref[-1] == '\n':
157
 
          ref = ref[:-1]
158
 
        return self.ref(ref)
159
 
      assert len(contents) == 41, 'Invalid ref in %s' % file
160
 
      return contents[:-1]
161
 
    finally:
162
 
      f.close()
163
 
 
164
 
  def ref(self, name):
165
 
    for dir in self.ref_locs:
166
 
      file = os.path.join(self.controldir(), dir, name)
167
 
      if os.path.exists(file):
168
 
        return self._get_ref(file)
169
 
 
170
 
  def get_refs(self):
171
 
    ret = {"HEAD": self.head()}
172
 
    for dir in ["refs/heads", "refs/tags"]:
173
 
        for name in os.listdir(os.path.join(self.controldir(), dir)):
174
 
          path = os.path.join(self.controldir(), dir, name)
175
 
          if os.path.isfile(path):
176
 
            ret["/".join([dir, name])] = self._get_ref(path)
177
 
    return ret
178
 
 
179
 
  def set_ref(self, name, value):
180
 
    file = os.path.join(self.controldir(), name)
181
 
    open(file, 'w').write(value+"\n")
182
 
 
183
 
  def remove_ref(self, name):
184
 
    file = os.path.join(self.controldir(), name)
185
 
    if os.path.exists(file):
186
 
      os.remove(file)
187
 
      return
188
 
 
189
 
  def get_tags(self):
190
 
    ret = {}
191
 
    for root, dirs, files in os.walk(os.path.join(self.controldir(), 'refs', 'tags')):
192
 
      for name in files:
193
 
        ret[name] = self._get_ref(os.path.join(root, name))
194
 
    return ret
195
 
 
196
 
  def heads(self):
197
 
    ret = {}
198
 
    for root, dirs, files in os.walk(os.path.join(self.controldir(), 'refs', 'heads')):
199
 
      for name in files:
200
 
        ret[name] = self._get_ref(os.path.join(root, name))
201
 
    return ret
202
 
 
203
 
  def head(self):
204
 
    return self.ref('HEAD')
205
 
 
206
 
  def _get_object(self, sha, cls):
207
 
    assert len(sha) in (20, 40)
208
 
    ret = self.get_object(sha)
209
 
    if ret._type != cls._type:
210
 
        if cls is Commit:
211
 
            raise NotCommitError(ret)
212
 
        elif cls is Blob:
213
 
            raise NotBlobError(ret)
214
 
        elif cls is Tree:
215
 
            raise NotTreeError(ret)
216
 
        else:
217
 
            raise Exception("Type invalid: %r != %r" % (ret._type, cls._type))
218
 
    return ret
219
 
 
220
 
  def get_object(self, sha):
221
 
    return self.object_store[sha]
222
 
 
223
 
  def get_parents(self, sha):
224
 
    return self.commit(sha).parents
225
 
 
226
 
  def commit(self, sha):
227
 
    return self._get_object(sha, Commit)
228
 
 
229
 
  def tree(self, sha):
230
 
    return self._get_object(sha, Tree)
231
 
 
232
 
  def get_blob(self, sha):
233
 
    return self._get_object(sha, Blob)
234
 
 
235
 
  def revision_history(self, head):
236
 
    """Returns a list of the commits reachable from head.
237
 
 
238
 
    Returns a list of commit objects. the first of which will be the commit
239
 
    of head, then following theat will be the parents.
240
 
 
241
 
    Raises NotCommitError if any no commits are referenced, including if the
242
 
    head parameter isn't the sha of a commit.
243
 
 
244
 
    XXX: work out how to handle merges.
245
 
    """
246
 
    # We build the list backwards, as parents are more likely to be older
247
 
    # than children
248
 
    pending_commits = [head]
249
 
    history = []
250
 
    while pending_commits != []:
251
 
      head = pending_commits.pop(0)
252
 
      try:
253
 
          commit = self.commit(head)
254
 
      except KeyError:
255
 
        raise MissingCommitError(head)
256
 
      if commit in history:
257
 
        continue
258
 
      i = 0
259
 
      for known_commit in history:
260
 
        if known_commit.commit_time > commit.commit_time:
261
 
          break
262
 
        i += 1
263
 
      history.insert(i, commit)
264
 
      parents = commit.parents
265
 
      pending_commits += parents
266
 
    history.reverse()
267
 
    return history
268
 
 
269
 
  def __repr__(self):
270
 
      return "<Repo at %r>" % self.path
271
 
 
272
 
  @classmethod
273
 
  def init_bare(cls, path, mkdir=True):
274
 
      for d in [["objects"], 
275
 
                ["objects", "info"], 
276
 
                ["objects", "pack"],
277
 
                ["branches"],
278
 
                ["refs"],
279
 
                ["refs", "tags"],
280
 
                ["refs", "heads"],
281
 
                ["hooks"],
282
 
                ["info"]]:
283
 
          os.mkdir(os.path.join(path, *d))
284
 
      open(os.path.join(path, 'HEAD'), 'w').write("ref: refs/heads/master\n")
285
 
      open(os.path.join(path, 'description'), 'w').write("Unnamed repository")
286
 
      open(os.path.join(path, 'info', 'excludes'), 'w').write("")
287
 
 
288
 
  create = init_bare
289
 
 
290
 
 
291
 
class ObjectStore(object):
292
 
 
293
 
    def __init__(self, path):
294
 
        self.path = path
295
 
        self._packs = None
296
 
 
297
 
    def pack_dir(self):
298
 
        return os.path.join(self.path, PACKDIR)
299
 
 
300
 
    def __contains__(self, sha):
301
 
        # TODO: This can be more efficient
302
 
        try:
303
 
            self[sha]
304
 
            return True
305
 
        except KeyError:
306
 
            return False
307
 
 
308
 
    @property
309
 
    def packs(self):
310
 
        """List with pack objects."""
311
 
        if self._packs is None:
312
 
            self._packs = list(load_packs(self.pack_dir()))
313
 
        return self._packs
314
 
 
315
 
    def _get_shafile(self, sha):
316
 
        dir = sha[:2]
317
 
        file = sha[2:]
318
 
        # Check from object dir
319
 
        path = os.path.join(self.path, dir, file)
320
 
        if os.path.exists(path):
321
 
          return ShaFile.from_file(path)
322
 
        return None
323
 
 
324
 
    def get_raw(self, sha):
325
 
        """Obtain the raw text for an object.
326
 
        
327
 
        :param sha: Sha for the object.
328
 
        :return: tuple with object type and object contents.
329
 
        """
330
 
        for pack in self.packs:
331
 
            if sha in pack:
332
 
                return pack.get_raw(sha, self.get_raw)
333
 
        # FIXME: Are pack deltas ever against on-disk shafiles ?
334
 
        ret = self._get_shafile(sha)
335
 
        if ret is not None:
336
 
            return ret.as_raw_string()
337
 
        raise KeyError(sha)
338
 
 
339
 
    def __getitem__(self, sha):
340
 
        assert len(sha) == 40, "Incorrect length sha: %s" % str(sha)
341
 
        ret = self._get_shafile(sha)
342
 
        if ret is not None:
343
 
            return ret
344
 
        # Check from packs
345
 
        type, uncomp = self.get_raw(sha)
346
 
        return ShaFile.from_raw_string(type, uncomp)
347
 
 
348
 
    def move_in_pack(self, path):
349
 
        """Move a specific file containing a pack into the pack directory.
350
 
 
351
 
        :note: The file should be on the same file system as the 
352
 
            packs directory.
353
 
 
354
 
        :param path: Path to the pack file.
355
 
        """
356
 
        p = PackData(path)
357
 
        entries = p.sorted_entries(self.get_raw)
358
 
        basename = os.path.join(self.pack_dir(), 
359
 
            "pack-%s" % iter_sha1(entry[0] for entry in entries))
360
 
        write_pack_index_v2(basename+".idx", entries, p.calculate_checksum())
361
 
        os.rename(path, basename + ".pack")
362
 
 
363
 
    def add_pack(self):
364
 
        """Add a new pack to this object store. 
365
 
 
366
 
        :return: Fileobject to write to and a commit function to 
367
 
            call when the pack is finished.
368
 
        """
369
 
        fd, path = tempfile.mkstemp(dir=self.pack_dir(), suffix=".pack")
370
 
        f = os.fdopen(fd, 'w')
371
 
        def commit():
372
 
            if os.path.getsize(path) > 0:
373
 
                self.move_in_pack(path)
374
 
        return f, commit