/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

Return mapping in revision_id_bzr_to_foreign() as required by the interface.

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