• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

repo-helper / southwark / 15056466000

15 May 2025 10:31PM UTC coverage: 89.7% (+3.4%) from 86.322%
15056466000

push

github

web-flow
Updated files with 'repo_helper'. (#66)

Co-authored-by: repo-helper[bot] <74742576+repo-helper[bot]@users.noreply.github.com>

479 of 534 relevant lines covered (89.7%)

0.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.42
/southwark/repo.py
1
#!/usr/bin/env python3
2
#
3
#  repo.py
4
"""
5
Modified Dulwich repository object.
6

7
.. versionadded:: 0.3.0
8
"""
9
#
10
#  Copyright © 2020 Dominic Davis-Foster <dominic@davis-foster.co.uk>
11
#
12
#  Permission is hereby granted, free of charge, to any person obtaining a copy
13
#  of this software and associated documentation files (the "Software"), to deal
14
#  in the Software without restriction, including without limitation the rights
15
#  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
#  copies of the Software, and to permit persons to whom the Software is
17
#  furnished to do so, subject to the following conditions:
18
#
19
#  The above copyright notice and this permission notice shall be included in all
20
#  copies or substantial portions of the Software.
21
#
22
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23
#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24
#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
25
#  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
26
#  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
27
#  OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
28
#  OR OTHER DEALINGS IN THE SOFTWARE.
29
#
30
#  get_user_identity, Repo and DiskRefsContainer based on https://github.com/dulwich/dulwich
31
#  Copyright (C) 2013 Jelmer Vernooij <jelmer@jelmer.uk>
32
#  |  Licensed under the Apache License, Version 2.0 (the "License"); you may
33
#  |  not use this file except in compliance with the License. You may obtain
34
#  |  a copy of the License at
35
#  |
36
#  |          http://www.apache.org/licenses/LICENSE-2.0
37
#  |
38
#  |  Unless required by applicable law or agreed to in writing, software
39
#  |  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
40
#  |  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
41
#  |  License for the specific language governing permissions and limitations
42
#  |  under the License.
43
#
44

45
# stdlib
46
import os
1✔
47
from itertools import chain
1✔
48
from typing import Any, Dict, Iterator, Optional, Type, TypeVar, Union, cast
1✔
49

50
# 3rd party
51
import click
1✔
52
import dulwich.index
1✔
53
from domdf_python_tools.paths import PathPlus
1✔
54
from domdf_python_tools.typing import PathLike
1✔
55
from dulwich import repo
1✔
56
from dulwich.config import StackedConfig
1✔
57
from dulwich.objects import Commit, Tree, TreeEntry
1✔
58

59
__all__ = ["get_user_identity", "Repo", "_R"]
1✔
60

61
_R = TypeVar("_R", bound="Repo")
1✔
62

63

64
def get_user_identity(config: StackedConfig, kind: Optional[str] = None) -> bytes:
1✔
65
        """
66
        Determine the identity to use for new commits.
67

68
        If kind is set, this first checks
69
        ``GIT_${KIND}_NAME`` and ``GIT_${KIND}_EMAIL``.
70

71
        If those variables are not set, then it will fall back
72
        to reading the ``user.name`` and ``user.email`` settings from
73
        the specified configuration.
74

75
        If that also fails, then it will fall back to using
76
        the current users' identity as obtained from the host
77
        system (e.g. the gecos field, ``$EMAIL``, ``$USER@$(hostname -f)``.
78

79
        :param config:
80
        :param kind: Optional kind to return identity for, usually either ``'AUTHOR'`` or ``'COMMITTER'``.
81

82
        :returns: A user identity
83
        """
84

85
        user: Optional[bytes] = None
1✔
86
        email: Optional[bytes] = None
1✔
87

88
        if kind:
1✔
89
                user_uc = os.environ.get("GIT_" + kind + "_NAME")
×
90
                if user_uc is not None:
×
91
                        user = user_uc.encode("UTF-8")
×
92
                email_uc = os.environ.get("GIT_" + kind + "_EMAIL")
×
93
                if email_uc is not None:
×
94
                        email = email_uc.encode("UTF-8")
×
95

96
        if user is None:
1✔
97
                try:
1✔
98
                        user = config.get(("user", ), "name")
1✔
99
                except KeyError:
1✔
100
                        user = None
1✔
101

102
        if email is None:
1✔
103
                try:
1✔
104
                        email = config.get(("user", ), "email")
1✔
105
                except KeyError:
1✔
106
                        email = None
1✔
107

108
        if user is None or email is None:
1✔
109
                default_user, default_email = repo._get_default_identity()  # type: ignore[attr-defined]
1✔
110

111
                if user is None:
1✔
112
                        user = default_user.encode("UTF-8")
1✔
113
                if email is None:
1✔
114
                        email = default_email.encode("UTF-8")
1✔
115

116
        if email.startswith(b'<') and email.endswith(b'>'):
1✔
117
                email = email[1:-1]
×
118

119
        return user + b" <" + email + b">"
1✔
120

121

122
class Repo(repo.Repo):
1✔
123
        """
124
        Modified Dulwich repository object.
125

126
        A git repository backed by local disk.
127

128
        To open an existing repository, call the constructor with
129
        the path of the repository.
130

131
        To create a new repository, use the Repo.init class method.
132

133
        :param root:
134

135
        .. autosummary-widths:: 47/100
136
        """
137

138
        def do_commit(  # type: ignore[override]
1✔
139
                self,
140
                message: Optional[Union[str, bytes]] = None,
141
                committer: Optional[Union[str, bytes]] = None,
142
                author: Optional[Union[str, bytes]] = None,
143
                commit_timestamp: Optional[float] = None,
144
                commit_timezone: Optional[float] = None,
145
                author_timestamp: Optional[float] = None,
146
                author_timezone: Optional[float] = None,
147
                tree: Optional[Any] = None,
148
                encoding: Optional[Union[str, bytes]] = None,
149
                ref: bytes = b'HEAD',
150
                merge_heads: Optional[Any] = None
151
                ) -> bytes:
152
                """
153
                Create a new commit.
154

155
                If not specified, `committer` and `author` default to
156
                :func:`get_user_identity(..., 'COMMITTER') <.get_user_identity>`
157
                and :func:`get_user_identity(..., 'AUTHOR') <.get_user_identity>` respectively.
158

159
                :param message: Commit message
160
                :param committer: Committer fullname
161
                :param author: Author fullname
162
                :param commit_timestamp: Commit timestamp (defaults to now)
163
                :param commit_timezone: Commit timestamp timezone (defaults to GMT)
164
                :param author_timestamp: Author timestamp (defaults to commit timestamp)
165
                :param author_timezone: Author timestamp timezone (defaults to commit timestamp timezone)
166
                :param tree: SHA1 of the tree root to use (if not specified the current index will be committed).
167
                :param encoding: Encoding
168
                :param ref: Optional ref to commit to (defaults to current branch)
169
                :param merge_heads: Merge heads (defaults to .git/MERGE_HEADS)
170

171
                :returns: New commit SHA1
172
                """
173

174
                config = self.get_config_stack()
×
175

176
                if committer is None:
×
177
                        committer = get_user_identity(config, kind="COMMITTER")
×
178

179
                if author is None:
×
180
                        try:
×
181
                                author = get_user_identity(config, kind="AUTHOR")
×
182
                        except ModuleNotFoundError as e:
×
183
                                if str(e) == "No module named 'pwd'":
×
184
                                        author = committer
×
185
                                else:
186
                                        raise
×
187

188
                return super().do_commit(
×
189
                                message=message,
190
                                committer=committer,
191
                                author=author,
192
                                commit_timestamp=commit_timestamp,
193
                                commit_timezone=commit_timezone,
194
                                author_timestamp=author_timestamp,
195
                                author_timezone=author_timezone,
196
                                tree=tree,
197
                                encoding=encoding,
198
                                ref=ref,
199
                                merge_heads=merge_heads,
200
                                )
201

202
        def _get_user_identity(
1✔
203
                        self,
204
                        config: "StackedConfig",
205
                        kind: Optional[str] = None,
206
                        ) -> bytes:
207
                """
208
                Determine the identity to use for new commits.
209

210
                :param config:
211
                :param kind:
212
                """
213

214
                return get_user_identity(config)
1✔
215

216
        @classmethod
1✔
217
        def init(cls: Type[_R], path: PathLike, mkdir: bool = False) -> _R:
1✔
218
                """
219
                Create a new repository.
220

221
                :param path: Path in which to create the repository.
222
                :param mkdir: Whether to create the directory if it doesn't exist.
223
                """
224

225
                return super().init(path, mkdir=mkdir)
1✔
226

227
        @classmethod
1✔
228
        def init_bare(cls: Type[_R], path: PathLike, mkdir: bool = False) -> _R:
1✔
229
                """
230
                Create a new bare repository.
231

232
                :param path: Path in which to create the repository.
233
                :param mkdir:
234
                """
235

236
                return super().init_bare(path, mkdir=mkdir)
×
237

238
        def list_remotes(self) -> Dict[str, str]:
1✔
239
                """
240
                Returns a mapping of remote names to remote URLs, for the repo's current remotes.
241

242
                .. versionadded:: 0.7.0
243
                """
244

245
                remotes = {}
1✔
246
                config = self.get_config()
1✔
247

248
                for key in list(config.keys()):
1✔
249
                        if key[0] == b"remote":
1✔
250
                                remotes[key[1].decode("UTF-8")] = config.get(key, "url").decode("UTF-8")
1✔
251

252
                return remotes
1✔
253

254
        def reset_to(self, sha: Union[str, bytes]) -> None:
1✔
255
                """
256
                Reset the state of the repository to the given commit sha.
257

258
                Any files added in subsequent commits will be removed,
259
                any deleted will be restored,
260
                and any modified will be reverted.
261

262
                .. versionadded:: 0.8.0
263

264
                :param sha:
265
                """
266

267
                # this package
268
                from southwark import status
1✔
269

270
                if isinstance(sha, str):
1✔
271
                        sha = sha.encode("UTF-8")
1✔
272

273
                index = self.open_index()
1✔
274
                directory = PathPlus(self.path)
1✔
275

276
                tree_for_sha: Tree = cast(Commit, self[sha]).tree
1✔
277
                tree_for_head: Tree = cast(Commit, self[b'HEAD']).tree
1✔
278

279
                dulwich.index.build_index_from_tree(
1✔
280
                                root_path=directory,
281
                                index_path=self.index_path(),
282
                                object_store=self.object_store,
283
                                tree_id=tree_for_sha,
284
                                )
285

286
                try:
1✔
287
                        # Based on https://github.com/dulwich/dulwich/issues/588#issuecomment-348412641
288
                        oldtree: Tree = cast(Tree, self[tree_for_head])
1✔
289
                        newtree: Tree = cast(Tree, self[tree_for_sha])
1✔
290

291
                        contents_iterator: Iterator[TreeEntry] = self.object_store.iter_tree_contents(newtree.id)
1✔
292
                        desired_filenames = [f.path for f in contents_iterator]
1✔
293

294
                        for f in self.object_store.iter_tree_contents(oldtree.id):
1✔
295
                                if f.path not in desired_filenames:
1✔
296
                                        # delete files that were in old branch, but not new
297
                                        (directory / f.path.decode("UTF-8")).unlink()
1✔
298

299
                except KeyError:
×
300
                        click.echo("Unable to delete files added in later commits", err=True)
×
301

302
                self[b'HEAD'] = sha
1✔
303
                index.write()
1✔
304

305
                current_status = status(self)
1✔
306

307
                for filename in chain(
1✔
308
                                current_status.staged["add"],
309
                                current_status.staged["delete"],
310
                                current_status.staged["modify"],
311
                                ):
312
                        self.stage(os.path.normpath(filename.as_posix()))
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc