gitstorage.py 5.7 KB
Newer Older
1
2
3
4
#   This file is part of debexpo
#   https://salsa.debian.org/mentors.debian.net-team/debexpo
#
#   Copyright © 2012 Baptiste Mouterde <baptiste.mouterde@gmail.com>
5
#   Copyright © 2020 Baptiste Beauplat <lyknode@cilg.org>
Baptiste Mouterde's avatar
Baptiste Mouterde committed
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#
#   Permission is hereby granted, free of charge, to any person obtaining a
#   copy of this software and associated documentation files (the "Software"),
#   to deal in the Software without restriction, including without limitation
#   the rights to use, copy, modify, merge, publish, distribute, sublicense,
#   and/or sell copies of the Software, and to permit persons to whom the
#   Software is furnished to do so, subject to the following conditions:
#
#   The above copyright notice and this permission notice shall be included in
#   all copies or substantial portions of the Software.
#
#   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
#   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
#   DEALINGS IN THE SOFTWARE.

25
from io import BytesIO
Baptiste Beauplat's avatar
Baptiste Beauplat committed
26
27
28
29
from os import makedirs, walk
from os.path import isdir, join, relpath
from logging import getLogger
from shutil import rmtree, copytree
Baptiste Mouterde's avatar
Baptiste Mouterde committed
30

Baptiste Beauplat's avatar
Baptiste Beauplat committed
31
32
33
from dulwich.repo import Repo
from dulwich.patch import write_tree_diff
from dulwich.index import get_unstaged_changes
Baptiste Mouterde's avatar
Baptiste Mouterde committed
34
35

fileToIgnore = []
Baptiste Beauplat's avatar
Baptiste Beauplat committed
36
log = getLogger(__name__)
Baptiste Mouterde's avatar
Baptiste Mouterde committed
37

Baptiste Beauplat's avatar
Baptiste Beauplat committed
38

39
40
41
class NoOlderContent(Exception):
    pass

Baptiste Beauplat's avatar
Baptiste Beauplat committed
42

Baptiste Beauplat's avatar
Baptiste Beauplat committed
43
class GitStorage():
44
45
46
47
    def __init__(self, git_storage_path, package):
        self.repository = join(git_storage_path, package)
        self.source_dir = join(self.repository, 'sources')
        self.git = GitBackendDulwich(self.repository)
Baptiste Mouterde's avatar
Baptiste Mouterde committed
48

Baptiste Beauplat's avatar
Baptiste Beauplat committed
49
    def install(self, source):
50
51
        self._sync_source(source)
        changes = self._list_files()
Baptiste Beauplat's avatar
Baptiste Beauplat committed
52

53
54
        self.git.stage(changes, True)
        ref = self.git.commit()
Baptiste Beauplat's avatar
Baptiste Beauplat committed
55
56
57

        return ref

58
    def remove(self):
59
60
61
        # Exclude the False branch from coverage since having the repository
        # removed is unlikely to happen and would be an external action
        if isdir(self.repository):  # pragma: no branch
62
            rmtree(self.repository)
Baptiste Beauplat's avatar
Baptiste Beauplat committed
63
64
65

    # def diff(self, upload_from, upload_to):
    #     pass
Baptiste Mouterde's avatar
Baptiste Mouterde committed
66

67
68
69
    def _sync_source(self, source):
        if isdir(self.source_dir):
            rmtree(self.source_dir)
Baptiste Beauplat's avatar
Baptiste Beauplat committed
70
71

        try:
72
            copytree(source.get_source_dir(), self.source_dir)
73
74
        # After dpkg 1.20.0, this will be catched by dpkg-source -x
        except IOError:  # pragma: no cover
Baptiste Beauplat's avatar
Baptiste Beauplat committed
75
76
            pass

77
    def _list_files(self):
Baptiste Beauplat's avatar
Baptiste Beauplat committed
78
79
        files = set()

80
        for (root, _, filenames) in walk(self.source_dir):
Baptiste Beauplat's avatar
Baptiste Beauplat committed
81
82
            for filename in filenames:
                fpath = join(root, filename)
83
                files.add(relpath(fpath, self.repository))
Baptiste Beauplat's avatar
Baptiste Beauplat committed
84
85
86
87
88

        return files


class GitBackendDulwich():
Baptiste Mouterde's avatar
Baptiste Mouterde committed
89
    def __init__(self, path):
Baptiste Beauplat's avatar
Baptiste Beauplat committed
90
        # Creating the repository
Baptiste Beauplat's avatar
Baptiste Beauplat committed
91
        if isdir(path):
Baptiste Mouterde's avatar
Baptiste Mouterde committed
92
93
            self.repo = Repo(path)
        else:
Baptiste Beauplat's avatar
Baptiste Beauplat committed
94
            makedirs(path)
Baptiste Mouterde's avatar
Baptiste Mouterde committed
95
96
            self.repo = Repo.init(path)

Baptiste Beauplat's avatar
Baptiste Beauplat committed
97
    # Only this function will be used on upload
Baptiste Beauplat's avatar
Baptiste Beauplat committed
98
    def stage(self, files, modified=False):
Baptiste Mouterde's avatar
Baptiste Mouterde committed
99
        """
Baptiste Beauplat's avatar
Baptiste Beauplat committed
100
        add files to the staging area before commit.
Baptiste Beauplat's avatar
Baptiste Beauplat committed
101

Baptiste Mouterde's avatar
Baptiste Mouterde committed
102
        ``files``
Baptiste Beauplat's avatar
Baptiste Beauplat committed
103
104
105
106
            a list of file to stage
        ``modified``
            if True, will stage pending modifications (useful for staging
            deletes)
Baptiste Mouterde's avatar
Baptiste Mouterde committed
107
        """
Baptiste Beauplat's avatar
Baptiste Beauplat committed
108
109
110
111
        if modified:
            files.update(set(get_unstaged_changes(self.repo.open_index(),
                                                  'sources')))

112
113
114
        # Exclude the False branch from coverage. In practice, there is always
        # files present in debian/ with accepted packages
        if len(files) != 0:  # pragma: no branch
Baptiste Beauplat's avatar
Baptiste Beauplat committed
115
116
117
            self.repo.stage(list(files))

    def commit(self):
118
        # According to https://github.com/dulwich/dulwich/issues/609 dulwich now
119
        # expects bytes types for all of its string arguments.
Baptiste Beauplat's avatar
Baptiste Beauplat committed
120
121
122
123
124
        ref = self.repo.do_commit(b'this is so awesome that nobody will never '
                                  b'see it',
                                  committer=b'same here <foo@foo.foo>')

        return ref.decode()
Baptiste Mouterde's avatar
Baptiste Mouterde committed
125

126
    def buildTreeDiff(self, old_sha_tree=None, new_sha_tree=None):
Baptiste Mouterde's avatar
Baptiste Mouterde committed
127
        """
128
129
130
        creating files from the diff between 2 trees, it will be used in the
        code browser to get older version (walking on history)

131
132
133
        ``old_sha_tree``
            the tree that you want to compare from
        ``new_sha_tree``
Baptiste Mouterde's avatar
Baptiste Mouterde committed
134
135
            the tree that you want to compare to

136
        by default it returns last changed files
Baptiste Mouterde's avatar
Baptiste Mouterde committed
137
        """
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
        # set default for new_tree (current tree)
        if not new_sha_tree:
            new_sha_tree = self.getLastTree()

        # set default for old_tree (last tree)
        if not old_sha_tree:
            history = self.getAllTrees()
            if (len(history) > 1):
                old_sha_tree = history[1]
            else:
                old_sha_tree = history[0]

        # calculate changes
        changes = BytesIO()
        write_tree_diff(changes, self.repo.object_store, old_sha_tree,
Baptiste Beauplat's avatar
Baptiste Beauplat committed
153
                        new_sha_tree)
154
        return changes.getvalue()
Baptiste Mouterde's avatar
Baptiste Mouterde committed
155

Baptiste Beauplat's avatar
Baptiste Beauplat committed
156
    # get*
Baptiste Mouterde's avatar
Baptiste Mouterde committed
157
158
159
160
    def getLastTree(self):
        """
        return the last tree
        """
161
        return self.repo.get_object(self.repo.head()).tree
Baptiste Mouterde's avatar
Baptiste Mouterde committed
162
163
164
165
166

    def getAllTrees(self):
        """
        return trees
        """
167
        commit = self.repo.head()
Baptiste Beauplat's avatar
Baptiste Beauplat committed
168
        result = [self.repo.get_object(commit).tree]
169
170
        for c in self.repo.get_parents(commit):
            result.append(self.repo.get_object(c).tree)
Baptiste Mouterde's avatar
Baptiste Mouterde committed
171
        return result