b53ee74002d8be3dc02cc9bc9c272f607cf2849e
[jelmer/dulwich-libgit2.git] / dulwich / repo.py
1 # repo.py -- For dealing with git repositories.
2 # Copyright (C) 2007 James Westby <jw+debian@jameswestby.net>
3 # Copyright (C) 2008-2009 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
22 """Repository access."""
23
24 from cStringIO import StringIO
25 import errno
26 import os
27
28 from dulwich.errors import (
29     MissingCommitError,
30     NoIndexPresent,
31     NotBlobError,
32     NotCommitError,
33     NotGitRepository,
34     NotTreeError,
35     NotTagError,
36     PackedRefsException,
37     CommitError,
38     )
39 from dulwich.file import (
40     ensure_dir_exists,
41     GitFile,
42     )
43 from dulwich.object_store import (
44     DiskObjectStore,
45     MemoryObjectStore,
46     )
47 from dulwich.objects import (
48     Blob,
49     Commit,
50     ShaFile,
51     Tag,
52     Tree,
53     hex_to_sha,
54     object_class,
55     )
56 import warnings
57
58
59 OBJECTDIR = 'objects'
60 SYMREF = 'ref: '
61 REFSDIR = 'refs'
62 REFSDIR_TAGS = 'tags'
63 REFSDIR_HEADS = 'heads'
64 INDEX_FILENAME = "index"
65
66 BASE_DIRECTORIES = [
67     ["branches"],
68     [REFSDIR],
69     [REFSDIR, REFSDIR_TAGS],
70     [REFSDIR, REFSDIR_HEADS],
71     ["hooks"],
72     ["info"]
73     ]
74
75
76 def read_info_refs(f):
77     ret = {}
78     for l in f.readlines():
79         (sha, name) = l.rstrip("\r\n").split("\t", 1)
80         ret[name] = sha
81     return ret
82
83
84 def check_ref_format(refname):
85     """Check if a refname is correctly formatted.
86
87     Implements all the same rules as git-check-ref-format[1].
88
89     [1] http://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html
90
91     :param refname: The refname to check
92     :return: True if refname is valid, False otherwise
93     """
94     # These could be combined into one big expression, but are listed separately
95     # to parallel [1].
96     if '/.' in refname or refname.startswith('.'):
97         return False
98     if '/' not in refname:
99         return False
100     if '..' in refname:
101         return False
102     for c in refname:
103         if ord(c) < 040 or c in '\177 ~^:?*[':
104             return False
105     if refname[-1] in '/.':
106         return False
107     if refname.endswith('.lock'):
108         return False
109     if '@{' in refname:
110         return False
111     if '\\' in refname:
112         return False
113     return True
114
115
116 class RefsContainer(object):
117     """A container for refs."""
118
119     def set_ref(self, name, other):
120         warnings.warn("RefsContainer.set_ref() is deprecated."
121             "Use set_symblic_ref instead.",
122             category=DeprecationWarning, stacklevel=2)
123         return self.set_symbolic_ref(name, other)
124
125     def set_symbolic_ref(self, name, other):
126         """Make a ref point at another ref.
127
128         :param name: Name of the ref to set
129         :param other: Name of the ref to point at
130         """
131         raise NotImplementedError(self.set_symbolic_ref)
132
133     def get_packed_refs(self):
134         """Get contents of the packed-refs file.
135
136         :return: Dictionary mapping ref names to SHA1s
137
138         :note: Will return an empty dictionary when no packed-refs file is
139             present.
140         """
141         raise NotImplementedError(self.get_packed_refs)
142
143     def get_peeled(self, name):
144         """Return the cached peeled value of a ref, if available.
145
146         :param name: Name of the ref to peel
147         :return: The peeled value of the ref. If the ref is known not point to a
148             tag, this will be the SHA the ref refers to. If the ref may point to
149             a tag, but no cached information is available, None is returned.
150         """
151         return None
152
153     def import_refs(self, base, other):
154         for name, value in other.iteritems():
155             self["%s/%s" % (base, name)] = value
156
157     def allkeys(self):
158         """All refs present in this container."""
159         raise NotImplementedError(self.allkeys)
160
161     def keys(self, base=None):
162         """Refs present in this container.
163
164         :param base: An optional base to return refs under.
165         :return: An unsorted set of valid refs in this container, including
166             packed refs.
167         """
168         if base is not None:
169             return self.subkeys(base)
170         else:
171             return self.allkeys()
172
173     def subkeys(self, base):
174         """Refs present in this container under a base.
175
176         :param base: The base to return refs under.
177         :return: A set of valid refs in this container under the base; the base
178             prefix is stripped from the ref names returned.
179         """
180         keys = set()
181         base_len = len(base) + 1
182         for refname in self.allkeys():
183             if refname.startswith(base):
184                 keys.add(refname[base_len:])
185         return keys
186
187     def as_dict(self, base=None):
188         """Return the contents of this container as a dictionary.
189
190         """
191         ret = {}
192         keys = self.keys(base)
193         if base is None:
194             base = ""
195         for key in keys:
196             try:
197                 ret[key] = self[("%s/%s" % (base, key)).strip("/")]
198             except KeyError:
199                 continue # Unable to resolve
200
201         return ret
202
203     def _check_refname(self, name):
204         """Ensure a refname is valid and lives in refs or is HEAD.
205
206         HEAD is not a valid refname according to git-check-ref-format, but this
207         class needs to be able to touch HEAD. Also, check_ref_format expects
208         refnames without the leading 'refs/', but this class requires that
209         so it cannot touch anything outside the refs dir (or HEAD).
210
211         :param name: The name of the reference.
212         :raises KeyError: if a refname is not HEAD or is otherwise not valid.
213         """
214         if name == 'HEAD':
215             return
216         if not name.startswith('refs/') or not check_ref_format(name[5:]):
217             raise KeyError(name)
218
219     def read_ref(self, refname):
220         """Read a reference without following any references.
221
222         :param refname: The name of the reference
223         :return: The contents of the ref file, or None if it does
224             not exist.
225         """
226         contents = self.read_loose_ref(refname)
227         if not contents:
228             contents = self.get_packed_refs().get(refname, None)
229         return contents
230
231     def read_loose_ref(self, name):
232         """Read a loose reference and return its contents.
233
234         :param name: the refname to read
235         :return: The contents of the ref file, or None if it does
236             not exist.
237         """
238         raise NotImplementedError(self.read_loose_ref)
239
240     def _follow(self, name):
241         """Follow a reference name.
242
243         :return: a tuple of (refname, sha), where refname is the name of the
244             last reference in the symbolic reference chain
245         """
246         self._check_refname(name)
247         contents = SYMREF + name
248         depth = 0
249         while contents.startswith(SYMREF):
250             refname = contents[len(SYMREF):]
251             contents = self.read_ref(refname)
252             if not contents:
253                 break
254             depth += 1
255             if depth > 5:
256                 raise KeyError(name)
257         return refname, contents
258
259     def __contains__(self, refname):
260         if self.read_ref(refname):
261             return True
262         return False
263
264     def __getitem__(self, name):
265         """Get the SHA1 for a reference name.
266
267         This method follows all symbolic references.
268         """
269         _, sha = self._follow(name)
270         if sha is None:
271             raise KeyError(name)
272         return sha
273
274     def set_if_equals(self, name, old_ref, new_ref):
275         """Set a refname to new_ref only if it currently equals old_ref.
276
277         This method follows all symbolic references if applicable for the
278         subclass, and can be used to perform an atomic compare-and-swap
279         operation.
280
281         :param name: The refname to set.
282         :param old_ref: The old sha the refname must refer to, or None to set
283             unconditionally.
284         :param new_ref: The new sha the refname will refer to.
285         :return: True if the set was successful, False otherwise.
286         """
287         raise NotImplementedError(self.set_if_equals)
288
289     def add_if_new(self, name, ref):
290         """Add a new reference only if it does not already exist."""
291         raise NotImplementedError(self.add_if_new)
292
293     def __setitem__(self, name, ref):
294         """Set a reference name to point to the given SHA1.
295
296         This method follows all symbolic references if applicable for the
297         subclass.
298
299         :note: This method unconditionally overwrites the contents of a
300             reference. To update atomically only if the reference has not
301             changed, use set_if_equals().
302         :param name: The refname to set.
303         :param ref: The new sha the refname will refer to.
304         """
305         self.set_if_equals(name, None, ref)
306
307     def remove_if_equals(self, name, old_ref):
308         """Remove a refname only if it currently equals old_ref.
309
310         This method does not follow symbolic references, even if applicable for
311         the subclass. It can be used to perform an atomic compare-and-delete
312         operation.
313
314         :param name: The refname to delete.
315         :param old_ref: The old sha the refname must refer to, or None to delete
316             unconditionally.
317         :return: True if the delete was successful, False otherwise.
318         """
319         raise NotImplementedError(self.remove_if_equals)
320
321     def __delitem__(self, name):
322         """Remove a refname.
323
324         This method does not follow symbolic references, even if applicable for
325         the subclass.
326
327         :note: This method unconditionally deletes the contents of a reference.
328             To delete atomically only if the reference has not changed, use
329             remove_if_equals().
330
331         :param name: The refname to delete.
332         """
333         self.remove_if_equals(name, None)
334
335
336 class DictRefsContainer(RefsContainer):
337     """RefsContainer backed by a simple dict.
338
339     This container does not support symbolic or packed references and is not
340     threadsafe.
341     """
342
343     def __init__(self, refs):
344         self._refs = refs
345
346     def allkeys(self):
347         return self._refs.keys()
348
349     def read_loose_ref(self, name):
350         return self._refs.get(name, None)
351
352     def get_packed_refs(self):
353         return {}
354
355     def set_symbolic_ref(self, name, other):
356         self._refs[name] = SYMREF + other
357
358     def set_if_equals(self, name, old_ref, new_ref):
359         if old_ref is not None and self._refs.get(name, None) != old_ref:
360             return False
361         realname, _ = self._follow(name)
362         self._refs[realname] = new_ref
363         return True
364
365     def add_if_new(self, name, ref):
366         if name in self._refs:
367             return False
368         self._refs[name] = ref
369         return True
370
371     def remove_if_equals(self, name, old_ref):
372         if old_ref is not None and self._refs.get(name, None) != old_ref:
373             return False
374         del self._refs[name]
375         return True
376
377
378 class DiskRefsContainer(RefsContainer):
379     """Refs container that reads refs from disk."""
380
381     def __init__(self, path):
382         self.path = path
383         self._packed_refs = None
384         self._peeled_refs = None
385
386     def __repr__(self):
387         return "%s(%r)" % (self.__class__.__name__, self.path)
388
389     def subkeys(self, base):
390         keys = set()
391         path = self.refpath(base)
392         for root, dirs, files in os.walk(path):
393             dir = root[len(path):].strip(os.path.sep).replace(os.path.sep, "/")
394             for filename in files:
395                 refname = ("%s/%s" % (dir, filename)).strip("/")
396                 # check_ref_format requires at least one /, so we prepend the
397                 # base before calling it.
398                 if check_ref_format("%s/%s" % (base, refname)):
399                     keys.add(refname)
400         for key in self.get_packed_refs():
401             if key.startswith(base):
402                 keys.add(key[len(base):].strip("/"))
403         return keys
404
405     def allkeys(self):
406         keys = set()
407         if os.path.exists(self.refpath("HEAD")):
408             keys.add("HEAD")
409         path = self.refpath("")
410         for root, dirs, files in os.walk(self.refpath("refs")):
411             dir = root[len(path):].strip(os.path.sep).replace(os.path.sep, "/")
412             for filename in files:
413                 refname = ("%s/%s" % (dir, filename)).strip("/")
414                 if check_ref_format(refname):
415                     keys.add(refname)
416         keys.update(self.get_packed_refs())
417         return keys
418
419     def refpath(self, name):
420         """Return the disk path of a ref.
421
422         """
423         if os.path.sep != "/":
424             name = name.replace("/", os.path.sep)
425         return os.path.join(self.path, name)
426
427     def get_packed_refs(self):
428         """Get contents of the packed-refs file.
429
430         :return: Dictionary mapping ref names to SHA1s
431
432         :note: Will return an empty dictionary when no packed-refs file is
433             present.
434         """
435         # TODO: invalidate the cache on repacking
436         if self._packed_refs is None:
437             # set both to empty because we want _peeled_refs to be
438             # None if and only if _packed_refs is also None.
439             self._packed_refs = {}
440             self._peeled_refs = {}
441             path = os.path.join(self.path, 'packed-refs')
442             try:
443                 f = GitFile(path, 'rb')
444             except IOError, e:
445                 if e.errno == errno.ENOENT:
446                     return {}
447                 raise
448             try:
449                 first_line = iter(f).next().rstrip()
450                 if (first_line.startswith("# pack-refs") and " peeled" in
451                         first_line):
452                     for sha, name, peeled in read_packed_refs_with_peeled(f):
453                         self._packed_refs[name] = sha
454                         if peeled:
455                             self._peeled_refs[name] = peeled
456                 else:
457                     f.seek(0)
458                     for sha, name in read_packed_refs(f):
459                         self._packed_refs[name] = sha
460             finally:
461                 f.close()
462         return self._packed_refs
463
464     def get_peeled(self, name):
465         """Return the cached peeled value of a ref, if available.
466
467         :param name: Name of the ref to peel
468         :return: The peeled value of the ref. If the ref is known not point to a
469             tag, this will be the SHA the ref refers to. If the ref may point to
470             a tag, but no cached information is available, None is returned.
471         """
472         self.get_packed_refs()
473         if self._peeled_refs is None or name not in self._packed_refs:
474             # No cache: no peeled refs were read, or this ref is loose
475             return None
476         if name in self._peeled_refs:
477             return self._peeled_refs[name]
478         else:
479             # Known not peelable
480             return self[name]
481
482     def read_loose_ref(self, name):
483         """Read a reference file and return its contents.
484
485         If the reference file a symbolic reference, only read the first line of
486         the file. Otherwise, only read the first 40 bytes.
487
488         :param name: the refname to read, relative to refpath
489         :return: The contents of the ref file, or None if the file does not
490             exist.
491         :raises IOError: if any other error occurs
492         """
493         filename = self.refpath(name)
494         try:
495             f = GitFile(filename, 'rb')
496             try:
497                 header = f.read(len(SYMREF))
498                 if header == SYMREF:
499                     # Read only the first line
500                     return header + iter(f).next().rstrip("\r\n")
501                 else:
502                     # Read only the first 40 bytes
503                     return header + f.read(40-len(SYMREF))
504             finally:
505                 f.close()
506         except IOError, e:
507             if e.errno == errno.ENOENT:
508                 return None
509             raise
510
511     def _remove_packed_ref(self, name):
512         if self._packed_refs is None:
513             return
514         filename = os.path.join(self.path, 'packed-refs')
515         # reread cached refs from disk, while holding the lock
516         f = GitFile(filename, 'wb')
517         try:
518             self._packed_refs = None
519             self.get_packed_refs()
520
521             if name not in self._packed_refs:
522                 return
523
524             del self._packed_refs[name]
525             if name in self._peeled_refs:
526                 del self._peeled_refs[name]
527             write_packed_refs(f, self._packed_refs, self._peeled_refs)
528             f.close()
529         finally:
530             f.abort()
531
532     def set_symbolic_ref(self, name, other):
533         """Make a ref point at another ref.
534
535         :param name: Name of the ref to set
536         :param other: Name of the ref to point at
537         """
538         self._check_refname(name)
539         self._check_refname(other)
540         filename = self.refpath(name)
541         try:
542             f = GitFile(filename, 'wb')
543             try:
544                 f.write(SYMREF + other + '\n')
545             except (IOError, OSError):
546                 f.abort()
547                 raise
548         finally:
549             f.close()
550
551     def set_if_equals(self, name, old_ref, new_ref):
552         """Set a refname to new_ref only if it currently equals old_ref.
553
554         This method follows all symbolic references, and can be used to perform
555         an atomic compare-and-swap operation.
556
557         :param name: The refname to set.
558         :param old_ref: The old sha the refname must refer to, or None to set
559             unconditionally.
560         :param new_ref: The new sha the refname will refer to.
561         :return: True if the set was successful, False otherwise.
562         """
563         try:
564             realname, _ = self._follow(name)
565         except KeyError:
566             realname = name
567         filename = self.refpath(realname)
568         ensure_dir_exists(os.path.dirname(filename))
569         f = GitFile(filename, 'wb')
570         try:
571             if old_ref is not None:
572                 try:
573                     # read again while holding the lock
574                     orig_ref = self.read_loose_ref(realname)
575                     if orig_ref is None:
576                         orig_ref = self.get_packed_refs().get(realname, None)
577                     if orig_ref != old_ref:
578                         f.abort()
579                         return False
580                 except (OSError, IOError):
581                     f.abort()
582                     raise
583             try:
584                 f.write(new_ref+"\n")
585             except (OSError, IOError):
586                 f.abort()
587                 raise
588         finally:
589             f.close()
590         return True
591
592     def add_if_new(self, name, ref):
593         """Add a new reference only if it does not already exist.
594
595         This method follows symrefs, and only ensures that the last ref in the
596         chain does not exist.
597
598         :param name: The refname to set.
599         :param ref: The new sha the refname will refer to.
600         :return: True if the add was successful, False otherwise.
601         """
602         try:
603             realname, contents = self._follow(name)
604             if contents is not None:
605                 return False
606         except KeyError:
607             realname = name
608         self._check_refname(realname)
609         filename = self.refpath(realname)
610         ensure_dir_exists(os.path.dirname(filename))
611         f = GitFile(filename, 'wb')
612         try:
613             if os.path.exists(filename) or name in self.get_packed_refs():
614                 f.abort()
615                 return False
616             try:
617                 f.write(ref+"\n")
618             except (OSError, IOError):
619                 f.abort()
620                 raise
621         finally:
622             f.close()
623         return True
624
625     def remove_if_equals(self, name, old_ref):
626         """Remove a refname only if it currently equals old_ref.
627
628         This method does not follow symbolic references. It can be used to
629         perform an atomic compare-and-delete operation.
630
631         :param name: The refname to delete.
632         :param old_ref: The old sha the refname must refer to, or None to delete
633             unconditionally.
634         :return: True if the delete was successful, False otherwise.
635         """
636         self._check_refname(name)
637         filename = self.refpath(name)
638         ensure_dir_exists(os.path.dirname(filename))
639         f = GitFile(filename, 'wb')
640         try:
641             if old_ref is not None:
642                 orig_ref = self.read_loose_ref(name)
643                 if orig_ref is None:
644                     orig_ref = self.get_packed_refs().get(name, None)
645                 if orig_ref != old_ref:
646                     return False
647             # may only be packed
648             try:
649                 os.remove(filename)
650             except OSError, e:
651                 if e.errno != errno.ENOENT:
652                     raise
653             self._remove_packed_ref(name)
654         finally:
655             # never write, we just wanted the lock
656             f.abort()
657         return True
658
659
660 def _split_ref_line(line):
661     """Split a single ref line into a tuple of SHA1 and name."""
662     fields = line.rstrip("\n").split(" ")
663     if len(fields) != 2:
664         raise PackedRefsException("invalid ref line '%s'" % line)
665     sha, name = fields
666     try:
667         hex_to_sha(sha)
668     except (AssertionError, TypeError), e:
669         raise PackedRefsException(e)
670     if not check_ref_format(name):
671         raise PackedRefsException("invalid ref name '%s'" % name)
672     return (sha, name)
673
674
675 def read_packed_refs(f):
676     """Read a packed refs file.
677
678     :param f: file-like object to read from
679     :return: Iterator over tuples with SHA1s and ref names.
680     """
681     for l in f:
682         if l[0] == "#":
683             # Comment
684             continue
685         if l[0] == "^":
686             raise PackedRefsException(
687               "found peeled ref in packed-refs without peeled")
688         yield _split_ref_line(l)
689
690
691 def read_packed_refs_with_peeled(f):
692     """Read a packed refs file including peeled refs.
693
694     Assumes the "# pack-refs with: peeled" line was already read. Yields tuples
695     with ref names, SHA1s, and peeled SHA1s (or None).
696
697     :param f: file-like object to read from, seek'ed to the second line
698     """
699     last = None
700     for l in f:
701         if l[0] == "#":
702             continue
703         l = l.rstrip("\r\n")
704         if l[0] == "^":
705             if not last:
706                 raise PackedRefsException("unexpected peeled ref line")
707             try:
708                 hex_to_sha(l[1:])
709             except (AssertionError, TypeError), e:
710                 raise PackedRefsException(e)
711             sha, name = _split_ref_line(last)
712             last = None
713             yield (sha, name, l[1:])
714         else:
715             if last:
716                 sha, name = _split_ref_line(last)
717                 yield (sha, name, None)
718             last = l
719     if last:
720         sha, name = _split_ref_line(last)
721         yield (sha, name, None)
722
723
724 def write_packed_refs(f, packed_refs, peeled_refs=None):
725     """Write a packed refs file.
726
727     :param f: empty file-like object to write to
728     :param packed_refs: dict of refname to sha of packed refs to write
729     :param peeled_refs: dict of refname to peeled value of sha
730     """
731     if peeled_refs is None:
732         peeled_refs = {}
733     else:
734         f.write('# pack-refs with: peeled\n')
735     for refname in sorted(packed_refs.iterkeys()):
736         f.write('%s %s\n' % (packed_refs[refname], refname))
737         if refname in peeled_refs:
738             f.write('^%s\n' % peeled_refs[refname])
739
740
741 class BaseRepo(object):
742     """Base class for a git repository.
743
744     :ivar object_store: Dictionary-like object for accessing
745         the objects
746     :ivar refs: Dictionary-like object with the refs in this repository
747     """
748
749     def __init__(self, object_store, refs):
750         self.object_store = object_store
751         self.refs = refs
752
753     def _init_files(self):
754         """Initialize a default set of named files."""
755         self._put_named_file('description', "Unnamed repository")
756         self._put_named_file('config', ('[core]\n'
757                                         'repositoryformatversion = 0\n'
758                                         'filemode = true\n'
759                                         'bare = false\n'
760                                         'logallrefupdates = true\n'))
761         self._put_named_file(os.path.join('info', 'exclude'), '')
762
763     def get_named_file(self, path):
764         """Get a file from the control dir with a specific name.
765
766         Although the filename should be interpreted as a filename relative to
767         the control dir in a disk-baked Repo, the object returned need not be
768         pointing to a file in that location.
769
770         :param path: The path to the file, relative to the control dir.
771         :return: An open file object, or None if the file does not exist.
772         """
773         raise NotImplementedError(self.get_named_file)
774
775     def _put_named_file(self, path, contents):
776         """Write a file to the control dir with the given name and contents.
777
778         :param path: The path to the file, relative to the control dir.
779         :param contents: A string to write to the file.
780         """
781         raise NotImplementedError(self._put_named_file)
782
783     def open_index(self):
784         """Open the index for this repository.
785
786         :raises NoIndexPresent: If no index is present
787         :return: Index instance
788         """
789         raise NotImplementedError(self.open_index)
790
791     def fetch(self, target, determine_wants=None, progress=None):
792         """Fetch objects into another repository.
793
794         :param target: The target repository
795         :param determine_wants: Optional function to determine what refs to
796             fetch.
797         :param progress: Optional progress function
798         """
799         if determine_wants is None:
800             determine_wants = lambda heads: heads.values()
801         target.object_store.add_objects(
802           self.fetch_objects(determine_wants, target.get_graph_walker(),
803                              progress))
804         return self.get_refs()
805
806     def fetch_objects(self, determine_wants, graph_walker, progress,
807                       get_tagged=None):
808         """Fetch the missing objects required for a set of revisions.
809
810         :param determine_wants: Function that takes a dictionary with heads
811             and returns the list of heads to fetch.
812         :param graph_walker: Object that can iterate over the list of revisions
813             to fetch and has an "ack" method that will be called to acknowledge
814             that a revision is present.
815         :param progress: Simple progress function that will be called with
816             updated progress strings.
817         :param get_tagged: Function that returns a dict of pointed-to sha -> tag
818             sha for including tags.
819         :return: iterator over objects, with __len__ implemented
820         """
821         wants = determine_wants(self.get_refs())
822         if not wants:
823             return []
824         haves = self.object_store.find_common_revisions(graph_walker)
825         return self.object_store.iter_shas(
826           self.object_store.find_missing_objects(haves, wants, progress,
827                                                  get_tagged))
828
829     def get_graph_walker(self, heads=None):
830         if heads is None:
831             heads = self.refs.as_dict('refs/heads').values()
832         return self.object_store.get_graph_walker(heads)
833
834     def ref(self, name):
835         """Return the SHA1 a ref is pointing to."""
836         return self.refs[name]
837
838     def get_refs(self):
839         """Get dictionary with all refs."""
840         return self.refs.as_dict()
841
842     def head(self):
843         """Return the SHA1 pointed at by HEAD."""
844         return self.refs['HEAD']
845
846     def _get_object(self, sha, cls):
847         assert len(sha) in (20, 40)
848         ret = self.get_object(sha)
849         if not isinstance(ret, cls):
850             if cls is Commit:
851                 raise NotCommitError(ret)
852             elif cls is Blob:
853                 raise NotBlobError(ret)
854             elif cls is Tree:
855                 raise NotTreeError(ret)
856             elif cls is Tag:
857                 raise NotTagError(ret)
858             else:
859                 raise Exception("Type invalid: %r != %r" % (
860                   ret.type_name, cls.type_name))
861         return ret
862
863     def get_object(self, sha):
864         return self.object_store[sha]
865
866     def get_parents(self, sha):
867         return self.commit(sha).parents
868
869     def get_config(self):
870         import ConfigParser
871         p = ConfigParser.RawConfigParser()
872         p.read(os.path.join(self._controldir, 'config'))
873         return dict((section, dict(p.items(section)))
874                     for section in p.sections())
875
876     def commit(self, sha):
877         """Retrieve the commit with a particular SHA.
878
879         :param sha: SHA of the commit to retrieve
880         :raise NotCommitError: If the SHA provided doesn't point at a Commit
881         :raise KeyError: If the SHA provided didn't exist
882         :return: A `Commit` object
883         """
884         warnings.warn("Repo.commit(sha) is deprecated. Use Repo[sha] instead.",
885             category=DeprecationWarning, stacklevel=2)
886         return self._get_object(sha, Commit)
887
888     def tree(self, sha):
889         """Retrieve the tree with a particular SHA.
890
891         :param sha: SHA of the tree to retrieve
892         :raise NotTreeError: If the SHA provided doesn't point at a Tree
893         :raise KeyError: If the SHA provided didn't exist
894         :return: A `Tree` object
895         """
896         warnings.warn("Repo.tree(sha) is deprecated. Use Repo[sha] instead.",
897             category=DeprecationWarning, stacklevel=2)
898         return self._get_object(sha, Tree)
899
900     def tag(self, sha):
901         """Retrieve the tag with a particular SHA.
902
903         :param sha: SHA of the tag to retrieve
904         :raise NotTagError: If the SHA provided doesn't point at a Tag
905         :raise KeyError: If the SHA provided didn't exist
906         :return: A `Tag` object
907         """
908         warnings.warn("Repo.tag(sha) is deprecated. Use Repo[sha] instead.",
909             category=DeprecationWarning, stacklevel=2)
910         return self._get_object(sha, Tag)
911
912     def get_blob(self, sha):
913         """Retrieve the blob with a particular SHA.
914
915         :param sha: SHA of the blob to retrieve
916         :raise NotBlobError: If the SHA provided doesn't point at a Blob
917         :raise KeyError: If the SHA provided didn't exist
918         :return: A `Blob` object
919         """
920         warnings.warn("Repo.get_blob(sha) is deprecated. Use Repo[sha] "
921             "instead.", category=DeprecationWarning, stacklevel=2)
922         return self._get_object(sha, Blob)
923
924     def get_peeled(self, ref):
925         """Get the peeled value of a ref.
926
927         :param ref: the refname to peel
928         :return: the fully-peeled SHA1 of a tag object, after peeling all
929             intermediate tags; if the original ref does not point to a tag, this
930             will equal the original SHA1.
931         """
932         cached = self.refs.get_peeled(ref)
933         if cached is not None:
934             return cached
935         obj = self[ref]
936         obj_class = object_class(obj.type_name)
937         while obj_class is Tag:
938             obj_class, sha = obj.object
939             obj = self.get_object(sha)
940         return obj.id
941
942     def revision_history(self, head):
943         """Returns a list of the commits reachable from head.
944
945         Returns a list of commit objects. the first of which will be the commit
946         of head, then following theat will be the parents.
947
948         Raises NotCommitError if any no commits are referenced, including if the
949         head parameter isn't the sha of a commit.
950
951         XXX: work out how to handle merges.
952         """
953         # We build the list backwards, as parents are more likely to be older
954         # than children
955         pending_commits = [head]
956         history = []
957         while pending_commits != []:
958             head = pending_commits.pop(0)
959             try:
960                 commit = self[head]
961             except KeyError:
962                 raise MissingCommitError(head)
963             if type(commit) != Commit:
964                 raise NotCommitError(commit)
965             if commit in history:
966                 continue
967             i = 0
968             for known_commit in history:
969                 if known_commit.commit_time > commit.commit_time:
970                     break
971                 i += 1
972             history.insert(i, commit)
973             pending_commits += commit.parents
974         history.reverse()
975         return history
976
977     def __getitem__(self, name):
978         if len(name) in (20, 40):
979             try:
980                 return self.object_store[name]
981             except KeyError:
982                 pass
983         return self.object_store[self.refs[name]]
984
985     def __contains__(self, name):
986         if len(name) in (20, 40):
987             return name in self.object_store or name in self.refs
988         else:
989             return name in self.refs
990
991     def __setitem__(self, name, value):
992         if name.startswith("refs/") or name == "HEAD":
993             if isinstance(value, ShaFile):
994                 self.refs[name] = value.id
995             elif isinstance(value, str):
996                 self.refs[name] = value
997             else:
998                 raise TypeError(value)
999         else:
1000             raise ValueError(name)
1001
1002     def __delitem__(self, name):
1003         if name.startswith("refs") or name == "HEAD":
1004             del self.refs[name]
1005         raise ValueError(name)
1006
1007     def do_commit(self, message, committer=None,
1008                   author=None, commit_timestamp=None,
1009                   commit_timezone=None, author_timestamp=None,
1010                   author_timezone=None, tree=None):
1011         """Create a new commit.
1012
1013         :param message: Commit message
1014         :param committer: Committer fullname
1015         :param author: Author fullname (defaults to committer)
1016         :param commit_timestamp: Commit timestamp (defaults to now)
1017         :param commit_timezone: Commit timestamp timezone (defaults to GMT)
1018         :param author_timestamp: Author timestamp (defaults to commit timestamp)
1019         :param author_timezone: Author timestamp timezone
1020             (defaults to commit timestamp timezone)
1021         :param tree: SHA1 of the tree root to use (if not specified the current index will be committed).
1022         :return: New commit SHA1
1023         """
1024         import time
1025         index = self.open_index()
1026         c = Commit()
1027         if tree is None:
1028             c.tree = index.commit(self.object_store)
1029         else:
1030             c.tree = tree
1031         # TODO: Allow username to be missing, and get it from .git/config
1032         if committer is None:
1033             raise ValueError("committer not set")
1034         c.committer = committer
1035         if commit_timestamp is None:
1036             commit_timestamp = time.time()
1037         c.commit_time = int(commit_timestamp)
1038         if commit_timezone is None:
1039             # FIXME: Use current user timezone rather than UTC
1040             commit_timezone = 0
1041         c.commit_timezone = commit_timezone
1042         if author is None:
1043             author = committer
1044         c.author = author
1045         if author_timestamp is None:
1046             author_timestamp = commit_timestamp
1047         c.author_time = int(author_timestamp)
1048         if author_timezone is None:
1049             author_timezone = commit_timezone
1050         c.author_timezone = author_timezone
1051         c.message = message
1052         try:
1053             old_head = self.refs["HEAD"]
1054             c.parents = [old_head]
1055             self.object_store.add_object(c)
1056             ok = self.refs.set_if_equals("HEAD", old_head, c.id)
1057         except KeyError:
1058             c.parents = []
1059             self.object_store.add_object(c)
1060             ok = self.refs.add_if_new("HEAD", c.id)
1061         if not ok:
1062             # Fail if the atomic compare-and-swap failed, leaving the commit and
1063             # all its objects as garbage.
1064             raise CommitError("HEAD changed during commit")
1065
1066         return c.id
1067
1068
1069 class Repo(BaseRepo):
1070     """A git repository backed by local disk."""
1071
1072     def __init__(self, root):
1073         if os.path.isdir(os.path.join(root, ".git", OBJECTDIR)):
1074             self.bare = False
1075             self._controldir = os.path.join(root, ".git")
1076         elif (os.path.isdir(os.path.join(root, OBJECTDIR)) and
1077               os.path.isdir(os.path.join(root, REFSDIR))):
1078             self.bare = True
1079             self._controldir = root
1080         else:
1081             raise NotGitRepository(root)
1082         self.path = root
1083         object_store = DiskObjectStore(os.path.join(self.controldir(),
1084                                                     OBJECTDIR))
1085         refs = DiskRefsContainer(self.controldir())
1086         BaseRepo.__init__(self, object_store, refs)
1087
1088     def controldir(self):
1089         """Return the path of the control directory."""
1090         return self._controldir
1091
1092     def _put_named_file(self, path, contents):
1093         """Write a file to the control dir with the given name and contents.
1094
1095         :param path: The path to the file, relative to the control dir.
1096         :param contents: A string to write to the file.
1097         """
1098         path = path.lstrip(os.path.sep)
1099         f = GitFile(os.path.join(self.controldir(), path), 'wb')
1100         try:
1101             f.write(contents)
1102         finally:
1103             f.close()
1104
1105     def get_named_file(self, path):
1106         """Get a file from the control dir with a specific name.
1107
1108         Although the filename should be interpreted as a filename relative to
1109         the control dir in a disk-baked Repo, the object returned need not be
1110         pointing to a file in that location.
1111
1112         :param path: The path to the file, relative to the control dir.
1113         :return: An open file object, or None if the file does not exist.
1114         """
1115         # TODO(dborowitz): sanitize filenames, since this is used directly by
1116         # the dumb web serving code.
1117         path = path.lstrip(os.path.sep)
1118         try:
1119             return open(os.path.join(self.controldir(), path), 'rb')
1120         except (IOError, OSError), e:
1121             if e.errno == errno.ENOENT:
1122                 return None
1123             raise
1124
1125     def index_path(self):
1126         """Return path to the index file."""
1127         return os.path.join(self.controldir(), INDEX_FILENAME)
1128
1129     def open_index(self):
1130         """Open the index for this repository."""
1131         from dulwich.index import Index
1132         if not self.has_index():
1133             raise NoIndexPresent()
1134         return Index(self.index_path())
1135
1136     def has_index(self):
1137         """Check if an index is present."""
1138         # Bare repos must never have index files; non-bare repos may have a
1139         # missing index file, which is treated as empty.
1140         return not self.bare
1141
1142     def stage(self, paths):
1143         """Stage a set of paths.
1144
1145         :param paths: List of paths, relative to the repository path
1146         """
1147         from dulwich.index import cleanup_mode
1148         index = self.open_index()
1149         for path in paths:
1150             full_path = os.path.join(self.path, path)
1151             blob = Blob()
1152             try:
1153                 st = os.stat(full_path)
1154             except OSError:
1155                 # File no longer exists
1156                 try:
1157                     del index[path]
1158                 except KeyError:
1159                     pass  # Doesn't exist in the index either
1160             else:
1161                 f = open(full_path, 'rb')
1162                 try:
1163                     blob.data = f.read()
1164                 finally:
1165                     f.close()
1166                 self.object_store.add_object(blob)
1167                 # XXX: Cleanup some of the other file properties as well?
1168                 index[path] = (st.st_ctime, st.st_mtime, st.st_dev, st.st_ino,
1169                     cleanup_mode(st.st_mode), st.st_uid, st.st_gid, st.st_size,
1170                     blob.id, 0)
1171         index.write()
1172
1173     def __repr__(self):
1174         return "<Repo at %r>" % self.path
1175
1176     @classmethod
1177     def init(cls, path, mkdir=True):
1178         controldir = os.path.join(path, ".git")
1179         os.mkdir(controldir)
1180         cls.init_bare(controldir)
1181         return cls(path)
1182
1183     @classmethod
1184     def init_bare(cls, path, mkdir=True):
1185         for d in BASE_DIRECTORIES:
1186             os.mkdir(os.path.join(path, *d))
1187         DiskObjectStore.init(os.path.join(path, OBJECTDIR))
1188         ret = cls(path)
1189         ret.refs.set_symbolic_ref("HEAD", "refs/heads/master")
1190         ret._init_files()
1191         return ret
1192
1193     create = init_bare
1194
1195
1196 class MemoryRepo(BaseRepo):
1197     """Repo that stores refs, objects, and named files in memory.
1198
1199     MemoryRepos are always bare: they have no working tree and no index, since
1200     those have a stronger dependency on the filesystem.
1201     """
1202
1203     def __init__(self):
1204         BaseRepo.__init__(self, MemoryObjectStore(), DictRefsContainer({}))
1205         self._named_files = {}
1206         self.bare = True
1207
1208     def _put_named_file(self, path, contents):
1209         """Write a file to the control dir with the given name and contents.
1210
1211         :param path: The path to the file, relative to the control dir.
1212         :param contents: A string to write to the file.
1213         """
1214         self._named_files[path] = contents
1215
1216     def get_named_file(self, path):
1217         """Get a file from the control dir with a specific name.
1218
1219         Although the filename should be interpreted as a filename relative to
1220         the control dir in a disk-baked Repo, the object returned need not be
1221         pointing to a file in that location.
1222
1223         :param path: The path to the file, relative to the control dir.
1224         :return: An open file object, or None if the file does not exist.
1225         """
1226         contents = self._named_files.get(path, None)
1227         if contents is None:
1228             return None
1229         return StringIO(contents)
1230
1231     def open_index(self):
1232         """Fail to open index for this repo, since it is bare."""
1233         raise NoIndexPresent()
1234
1235     @classmethod
1236     def init_bare(cls, objects, refs):
1237         ret = cls()
1238         for obj in objects:
1239             ret.object_store.add_object(obj)
1240         for refname, sha in refs.iteritems():
1241             ret.refs[refname] = sha
1242         ret._init_files()
1243         return ret