~jelmer/dulwich/lp-pqm

« back to all changes in this revision

Viewing changes to dulwich/repo.py

  • Committer: Jelmer Vernooij
  • Date: 2012-02-01 22:13:51 UTC
  • mfrom: (413.11.554)
  • Revision ID: jelmer@samba.org-20120201221351-b3n2p9zttzh62dwu
Merge trunk.

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
# MA  02110-1301, USA.
20
20
 
21
21
 
22
 
"""Repository access."""
 
22
"""Repository access.
 
23
 
 
24
This module contains the base class for git repositories
 
25
(BaseRepo) and an implementation which uses a repository on 
 
26
local disk (Repo).
 
27
 
 
28
"""
23
29
 
24
30
from cStringIO import StringIO
25
31
import errno
791
797
 
792
798
    :ivar object_store: Dictionary-like object for accessing
793
799
        the objects
794
 
    :ivar refs: Dictionary-like object with the refs in this repository
 
800
    :ivar refs: Dictionary-like object with the refs in this
 
801
        repository
795
802
    """
796
803
 
797
804
    def __init__(self, object_store, refs):
 
805
        """Open a repository.
 
806
 
 
807
        This shouldn't be called directly, but rather through one of the
 
808
        base classes, such as MemoryRepo or Repo.
 
809
 
 
810
        :param object_store: Object store to use
 
811
        :param refs: Refs container to use
 
812
        """
798
813
        self.object_store = object_store
799
814
        self.refs = refs
800
815
 
801
816
    def _init_files(self, bare):
802
817
        """Initialize a default set of named files."""
 
818
        from dulwich.config import ConfigFile
803
819
        self._put_named_file('description', "Unnamed repository")
804
 
        self._put_named_file('config', ('[core]\n'
805
 
                                        'repositoryformatversion = 0\n'
806
 
                                        'filemode = true\n'
807
 
                                        'bare = ' + str(bare).lower() + '\n'
808
 
                                        'logallrefupdates = true\n'))
 
820
        f = StringIO()
 
821
        cf = ConfigFile()
 
822
        cf.set("core", "repositoryformatversion", "0")
 
823
        cf.set("core", "filemode", "true")
 
824
        cf.set("core", "bare", str(bare).lower())
 
825
        cf.set("core", "logallrefupdates", "true")
 
826
        cf.write_to_file(f)
 
827
        self._put_named_file('config', f.getvalue())
809
828
        self._put_named_file(os.path.join('info', 'exclude'), '')
810
829
 
811
830
    def get_named_file(self, path):
877
896
                                                 get_tagged))
878
897
 
879
898
    def get_graph_walker(self, heads=None):
 
899
        """Retrieve a graph walker.
 
900
 
 
901
        A graph walker is used by a remote repository (or proxy)
 
902
        to find out which objects are present in this repository.
 
903
 
 
904
        :param heads: Repository heads to use (optional)
 
905
        :return: A graph walker object
 
906
        """
880
907
        if heads is None:
881
908
            heads = self.refs.as_dict('refs/heads').values()
882
909
        return self.object_store.get_graph_walker(heads)
911
938
        return ret
912
939
 
913
940
    def get_object(self, sha):
 
941
        """Retrieve the object with the specified SHA.
 
942
 
 
943
        :param sha: SHA to retrieve
 
944
        :return: A ShaFile object
 
945
        :raise KeyError: when the object can not be found
 
946
        """
914
947
        return self.object_store[sha]
915
948
 
916
949
    def get_parents(self, sha):
 
950
        """Retrieve the parents of a specific commit.
 
951
 
 
952
        :param sha: SHA of the commit for which to retrieve the parents
 
953
        :return: List of parents
 
954
        """
917
955
        return self.commit(sha).parents
918
956
 
919
957
    def get_config(self):
920
 
        import ConfigParser
921
 
        p = ConfigParser.RawConfigParser()
922
 
        p.read(os.path.join(self._controldir, 'config'))
923
 
        return dict((section, dict(p.items(section)))
924
 
                    for section in p.sections())
 
958
        """Retrieve the config object.
 
959
 
 
960
        :return: `ConfigFile` object for the ``.git/config`` file.
 
961
        """
 
962
        from dulwich.config import ConfigFile
 
963
        path = os.path.join(self._controldir, 'config')
 
964
        try:
 
965
            return ConfigFile.from_path(path)
 
966
        except (IOError, OSError), e:
 
967
            if e.errno != errno.ENOENT:
 
968
                raise
 
969
            ret = ConfigFile()
 
970
            ret.path = path
 
971
            return ret
 
972
 
 
973
    def get_config_stack(self):
 
974
        """Return a config stack for this repository.
 
975
 
 
976
        This stack accesses the configuration for both this repository
 
977
        itself (.git/config) and the global configuration, which usually
 
978
        lives in ~/.gitconfig.
 
979
 
 
980
        :return: `Config` instance for this repository
 
981
        """
 
982
        from dulwich.config import StackedConfig
 
983
        backends = [self.get_config()] + StackedConfig.default_backends()
 
984
        return StackedConfig(backends, writable=backends[0])
925
985
 
926
986
    def commit(self, sha):
927
987
        """Retrieve the commit with a particular SHA.
1028
1088
        return [e.commit for e in self.get_walker(include=[head])]
1029
1089
 
1030
1090
    def __getitem__(self, name):
 
1091
        """Retrieve a Git object by SHA1 or ref.
 
1092
 
 
1093
        :param name: A Git object SHA1 or a ref name
 
1094
        :return: A `ShaFile` object, such as a Commit or Blob
 
1095
        :raise KeyError: when the specified ref or object does not exist
 
1096
        """
1031
1097
        if len(name) in (20, 40):
1032
1098
            try:
1033
1099
                return self.object_store[name]
1038
1104
        except RefFormatError:
1039
1105
            raise KeyError(name)
1040
1106
 
1041
 
    def __iter__(self):
1042
 
        raise NotImplementedError(self.__iter__)
1043
 
 
1044
1107
    def __contains__(self, name):
 
1108
        """Check if a specific Git object or ref is present.
 
1109
 
 
1110
        :param name: Git object SHA1 or ref name
 
1111
        """
1045
1112
        if len(name) in (20, 40):
1046
1113
            return name in self.object_store or name in self.refs
1047
1114
        else:
1048
1115
            return name in self.refs
1049
1116
 
1050
1117
    def __setitem__(self, name, value):
 
1118
        """Set a ref.
 
1119
 
 
1120
        :param name: ref name
 
1121
        :param value: Ref value - either a ShaFile object, or a hex sha
 
1122
        """
1051
1123
        if name.startswith("refs/") or name == "HEAD":
1052
1124
            if isinstance(value, ShaFile):
1053
1125
                self.refs[name] = value.id
1059
1131
            raise ValueError(name)
1060
1132
 
1061
1133
    def __delitem__(self, name):
1062
 
        if name.startswith("refs") or name == "HEAD":
 
1134
        """Remove a ref.
 
1135
 
 
1136
        :param name: Name of the ref to remove
 
1137
        """
 
1138
        if name.startswith("refs/") or name == "HEAD":
1063
1139
            del self.refs[name]
1064
1140
        else:
1065
1141
            raise ValueError(name)
1066
1142
 
 
1143
    def _get_user_identity(self):
 
1144
        config = self.get_config_stack()
 
1145
        return "%s <%s>" % (
 
1146
            config.get(("user", ), "name"),
 
1147
            config.get(("user", ), "email"))
 
1148
 
1067
1149
    def do_commit(self, message=None, committer=None,
1068
1150
                  author=None, commit_timestamp=None,
1069
1151
                  commit_timezone=None, author_timestamp=None,
1098
1180
        if merge_heads is None:
1099
1181
            # FIXME: Read merge heads from .git/MERGE_HEADS
1100
1182
            merge_heads = []
1101
 
        # TODO: Allow username to be missing, and get it from .git/config
1102
1183
        if committer is None:
1103
 
            raise ValueError("committer not set")
 
1184
            committer = self._get_user_identity()
1104
1185
        c.committer = committer
1105
1186
        if commit_timestamp is None:
1106
1187
            commit_timestamp = time.time()
1142
1223
 
1143
1224
 
1144
1225
class Repo(BaseRepo):
1145
 
    """A git repository backed by local disk."""
 
1226
    """A git repository backed by local disk.
 
1227
 
 
1228
    To open an existing repository, call the contructor with
 
1229
    the path of the repository.
 
1230
 
 
1231
    To create a new repository, use the Repo.init class method.
 
1232
    """
1146
1233
 
1147
1234
    def __init__(self, root):
1148
1235
        if os.path.isdir(os.path.join(root, ".git", OBJECTDIR)):
1219
1306
 
1220
1307
        :param paths: List of paths, relative to the repository path
1221
1308
        """
1222
 
        from dulwich.index import cleanup_mode
 
1309
        if isinstance(paths, basestring):
 
1310
            paths = [paths]
 
1311
        from dulwich.index import index_entry_from_stat
1223
1312
        index = self.open_index()
1224
1313
        for path in paths:
1225
1314
            full_path = os.path.join(self.path, path)
1226
 
            blob = Blob()
1227
1315
            try:
1228
1316
                st = os.stat(full_path)
1229
1317
            except OSError:
1231
1319
                try:
1232
1320
                    del index[path]
1233
1321
                except KeyError:
1234
 
                    pass  # Doesn't exist in the index either
 
1322
                    pass # already removed
1235
1323
            else:
 
1324
                blob = Blob()
1236
1325
                f = open(full_path, 'rb')
1237
1326
                try:
1238
1327
                    blob.data = f.read()
1239
1328
                finally:
1240
1329
                    f.close()
1241
1330
                self.object_store.add_object(blob)
1242
 
                # XXX: Cleanup some of the other file properties as well?
1243
 
                index[path] = (st.st_ctime, st.st_mtime, st.st_dev, st.st_ino,
1244
 
                    cleanup_mode(st.st_mode), st.st_uid, st.st_gid, st.st_size,
1245
 
                    blob.id, 0)
 
1331
                index[path] = index_entry_from_stat(st, blob.id, 0)
1246
1332
        index.write()
1247
1333
 
1248
 
    def clone(self, target_path, mkdir=True, bare=False, origin="origin"):
 
1334
    def clone(self, target_path, mkdir=True, bare=False,
 
1335
            origin="origin"):
1249
1336
        """Clone this repository.
1250
1337
 
1251
1338
        :param target_path: Target path
1285
1372
 
1286
1373
    @classmethod
1287
1374
    def init(cls, path, mkdir=False):
 
1375
        """Create a new repository.
 
1376
 
 
1377
        :param path: Path in which to create the repository
 
1378
        :param mkdir: Whether to create the directory
 
1379
        :return: `Repo` instance
 
1380
        """
1288
1381
        if mkdir:
1289
1382
            os.mkdir(path)
1290
1383
        controldir = os.path.join(path, ".git")
1294
1387
 
1295
1388
    @classmethod
1296
1389
    def init_bare(cls, path):
 
1390
        """Create a new bare repository.
 
1391
 
 
1392
        ``path`` should already exist and be an emty directory.
 
1393
 
 
1394
        :param path: Path to create bare repository in
 
1395
        :return: a `Repo` instance
 
1396
        """
1297
1397
        return cls._init_maybe_bare(path, True)
1298
1398
 
1299
1399
    create = init_bare
1340
1440
 
1341
1441
    @classmethod
1342
1442
    def init_bare(cls, objects, refs):
 
1443
        """Create a new bare repository in memory.
 
1444
 
 
1445
        :param objects: Objects for the new repository,
 
1446
            as iterable
 
1447
        :param refs: Refs as dictionary, mapping names
 
1448
            to object SHA1s
 
1449
        """
1343
1450
        ret = cls()
1344
1451
        for obj in objects:
1345
1452
            ret.object_store.add_object(obj)