uEdition-Editor 2.0.0b3__py3-none-any.whl → 2.0.0b5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of uEdition-Editor might be problematic. Click here for more details.

Files changed (24) hide show
  1. uedition_editor/__about__.py +1 -1
  2. uedition_editor/__init__.py +16 -1
  3. uedition_editor/api/branches.py +40 -2
  4. uedition_editor/api/configs.py +14 -3
  5. uedition_editor/api/files.py +158 -85
  6. uedition_editor/api/util.py +34 -10
  7. uedition_editor/cron.py +32 -0
  8. uedition_editor/frontend/dist/assets/{CodeMirrorEditor-BFeD7k_m.js → CodeMirrorEditor-B8N4a0_6.js} +1 -1
  9. uedition_editor/frontend/dist/assets/{FolderEditor-DIEGEh84.js → FolderEditor-zEVI7VnV.js} +1 -1
  10. uedition_editor/frontend/dist/assets/ImageEditor-D2waaDFD.js +1 -0
  11. uedition_editor/frontend/dist/assets/TeiEditor-DaKNV6gH.js +192 -0
  12. uedition_editor/frontend/dist/assets/index-DHdOJOZY.js +17 -0
  13. uedition_editor/frontend/dist/assets/{index-C9d-OSiG.css → index-HmBQiVS_.css} +1 -1
  14. uedition_editor/frontend/dist/index.html +2 -2
  15. uedition_editor/settings.py +31 -7
  16. {uedition_editor-2.0.0b3.dist-info → uedition_editor-2.0.0b5.dist-info}/METADATA +7 -3
  17. uedition_editor-2.0.0b5.dist-info/RECORD +28 -0
  18. uedition_editor/frontend/dist/assets/ImageEditor-BRTKiGoH.js +0 -1
  19. uedition_editor/frontend/dist/assets/TeiEditor-BeHkuEAu.js +0 -192
  20. uedition_editor/frontend/dist/assets/index-BtIoSLIQ.js +0 -16
  21. uedition_editor-2.0.0b3.dist-info/RECORD +0 -27
  22. {uedition_editor-2.0.0b3.dist-info → uedition_editor-2.0.0b5.dist-info}/WHEEL +0 -0
  23. {uedition_editor-2.0.0b3.dist-info → uedition_editor-2.0.0b5.dist-info}/entry_points.txt +0 -0
  24. {uedition_editor-2.0.0b3.dist-info → uedition_editor-2.0.0b5.dist-info}/licenses/LICENSE.txt +0 -0
@@ -3,4 +3,4 @@
3
3
  # SPDX-License-Identifier: MIT
4
4
  """About this package."""
5
5
 
6
- __version__ = "2.0.0b3"
6
+ __version__ = "2.0.0b5"
@@ -4,16 +4,31 @@
4
4
  """The main uEditor server."""
5
5
 
6
6
  import logging
7
+ from copy import deepcopy
7
8
 
8
9
  from fastapi import FastAPI
9
10
  from fastapi.responses import RedirectResponse
10
11
  from fastapi.staticfiles import StaticFiles
12
+ from uvicorn.config import LOGGING_CONFIG
11
13
 
14
+ from uedition_editor import cron # noqa:F401
12
15
  from uedition_editor.api import router as api_router
13
16
  from uedition_editor.settings import init_settings
14
17
 
18
+ logger = logging.getLogger(__name__)
19
+ # Configure logging
20
+ conf = deepcopy(LOGGING_CONFIG)
21
+ conf["loggers"]["uedition_editor"] = {
22
+ "level": logging.INFO,
23
+ "qualname": "uedition_editor",
24
+ "handlers": ["default"],
25
+ }
26
+ conf["formatters"]["default"]["fmt"] = "%(levelprefix)s %(name)-40s %(message)s"
27
+ conf["root"] = {"level": logging.INFO}
15
28
  if init_settings.test: # pragma: no cover
16
- logging.basicConfig(level=logging.DEBUG)
29
+ conf["loggers"]["uedition_editor"]["level"] = logging.DEBUG
30
+ logging.config.dictConfig(conf)
31
+ logger.debug("Logging configuration set up")
17
32
 
18
33
  app = FastAPI()
19
34
  app.include_router(api_router)
@@ -9,7 +9,7 @@ from typing import Annotated
9
9
  from fastapi import APIRouter, Depends, Header
10
10
  from fastapi.exceptions import HTTPException
11
11
  from pydantic import BaseModel, Field
12
- from pygit2 import GitError, Repository
12
+ from pygit2 import GitError, Repository, Signature
13
13
  from pygit2.enums import FetchPrune, RepositoryOpenFlag
14
14
 
15
15
  from uedition_editor.api.auth import get_current_user
@@ -18,6 +18,7 @@ from uedition_editor.api.files import router as files_router
18
18
  from uedition_editor.api.util import (
19
19
  BranchContextManager,
20
20
  RemoteRepositoryCallbacks,
21
+ commit_and_push,
21
22
  fetch_and_pull_branch,
22
23
  fetch_repo,
23
24
  pull_branch,
@@ -37,6 +38,7 @@ class BranchModel(BaseModel):
37
38
  id: str
38
39
  title: str
39
40
  nogit: bool = False
41
+ update_from_default: bool = False
40
42
 
41
43
 
42
44
  def slugify(slug: str) -> str:
@@ -63,7 +65,17 @@ async def branches(
63
65
  branches = []
64
66
  if category == "local":
65
67
  for branch_name in repo.branches.local:
66
- branches.append({"id": branch_name, "title": de_slugify(branch_name)})
68
+ repo.checkout(repo.branches[branch_name])
69
+ diff = repo.diff(
70
+ repo.revparse_single(init_settings.git.default_branch),
71
+ )
72
+ branches.append(
73
+ {
74
+ "id": branch_name,
75
+ "title": de_slugify(branch_name),
76
+ "update_from_default": diff.stats.files_changed > 0,
77
+ }
78
+ )
67
79
  elif category == "remote":
68
80
  if init_settings.git.remote_name in list(repo.remotes.names()):
69
81
  repo.remotes[init_settings.git.remote_name].fetch(
@@ -158,6 +170,32 @@ async def fetch_branches(
158
170
  raise HTTPException(500, "Git error") from ge
159
171
 
160
172
 
173
+ @router.post("/{branch_id}/merge-from-default", status_code=204)
174
+ async def merge_from_default(
175
+ branch_id: str,
176
+ current_user: Annotated[dict, Depends(get_current_user)],
177
+ ) -> None:
178
+ """Merge all changes from the default branch."""
179
+ async with BranchContextManager(branch_id) as repo:
180
+ repo.checkout(repo.branches[init_settings.git.default_branch])
181
+ if init_settings.git.remote_name in list(repo.remotes.names()):
182
+ fetch_repo(repo, init_settings.git.remote_name)
183
+ pull_branch(repo, init_settings.git.default_branch)
184
+ repo.checkout(repo.branches[branch_id])
185
+ default_branch_head = repo.revparse_single(init_settings.git.default_branch)
186
+ diff = repo.diff(default_branch_head)
187
+ if diff.stats.files_changed > 0:
188
+ repo.merge(default_branch_head.id)
189
+ commit_and_push(
190
+ repo,
191
+ init_settings.git.remote_name,
192
+ branch_id,
193
+ f"Merged {init_settings.git.default_branch}",
194
+ Signature(current_user["name"], current_user["sub"]),
195
+ extra_parents=[default_branch_head.id],
196
+ )
197
+
198
+
161
199
  @router.delete("/{branch_id}", status_code=204)
162
200
  async def delete_branch(
163
201
  branch_id: str,
@@ -13,7 +13,8 @@ from fastapi.responses import Response
13
13
  from uedition_editor.api.auth import get_current_user
14
14
  from uedition_editor.api.util import BranchContextManager, BranchNotFoundError
15
15
  from uedition_editor.settings import (
16
- UEditonSettings,
16
+ TEINode,
17
+ UEditionSettings,
17
18
  UEditorSettings,
18
19
  get_uedition_settings,
19
20
  get_ueditor_settings,
@@ -23,7 +24,7 @@ from uedition_editor.settings import (
23
24
  router = APIRouter(prefix="/configs")
24
25
 
25
26
 
26
- @router.get("/uedition", response_model=UEditonSettings)
27
+ @router.get("/uedition", response_model=UEditionSettings)
27
28
  async def uedition_config(
28
29
  branch_id: str,
29
30
  current_user: Annotated[dict, Depends(get_current_user)], # noqa:ARG001
@@ -31,7 +32,17 @@ async def uedition_config(
31
32
  """Fetch the uEdition configuration."""
32
33
  try:
33
34
  async with BranchContextManager(branch_id):
34
- return get_uedition_settings().model_dump()
35
+ settings = get_uedition_settings()
36
+ if "tei" in settings.sphinx_config:
37
+ if "blocks" in settings.sphinx_config["tei"]:
38
+ settings.sphinx_config["tei"]["blocks"] = [
39
+ TEINode(**block).model_dump() for block in settings.sphinx_config["tei"]["blocks"]
40
+ ]
41
+ if "marks" in settings.sphinx_config["tei"]:
42
+ settings.sphinx_config["tei"]["marks"] = [
43
+ TEINode(**mark).model_dump() for mark in settings.sphinx_config["tei"]["marks"]
44
+ ]
45
+ return settings.model_dump()
35
46
  except BranchNotFoundError as bnfe:
36
47
  raise HTTPException(404) from bnfe
37
48
 
@@ -12,19 +12,26 @@ import shutil
12
12
  from typing import Annotated
13
13
 
14
14
  import pygit2
15
- from fastapi import APIRouter, Depends, Header, UploadFile
15
+ from fastapi import APIRouter, Depends, Header, Response, UploadFile
16
16
  from fastapi.exceptions import HTTPException
17
17
  from fastapi.responses import FileResponse
18
18
  from lxml import etree
19
19
 
20
20
  from uedition_editor.api.auth import get_current_user
21
- from uedition_editor.api.util import BranchContextManager, BranchNotFoundError, commit_and_push
21
+ from uedition_editor.api.util import (
22
+ BranchContextManager,
23
+ BranchNotFoundError,
24
+ commit_and_push,
25
+ )
22
26
  from uedition_editor.settings import (
23
27
  TEIMetadataSection,
28
+ TEINode,
24
29
  TEINodeAttribute,
25
30
  TEISettings,
26
31
  TEITextSection,
32
+ UEditionSettings,
27
33
  UEditorSettings,
34
+ get_uedition_settings,
28
35
  get_ueditor_settings,
29
36
  init_settings,
30
37
  )
@@ -60,21 +67,25 @@ def guess_type(url: str) -> tuple[str | None, str | None]:
60
67
  return ("application/unknown", None)
61
68
 
62
69
 
63
- def build_file_tree(path: str, strip_len) -> list[dict]:
70
+ def build_file_tree(path: str, strip_len: int, uedition_settings: UEditionSettings) -> list[dict]:
64
71
  """Recursively build a tree of directories and files."""
65
72
  files = []
66
73
  for filename in os.listdir(path):
67
74
  full_filename = os.path.join(path, filename)
68
75
  if os.path.isdir(full_filename):
69
- files.append(
70
- {
71
- "name": filename,
72
- "fullpath": full_filename[strip_len:],
73
- "type": "folder",
74
- "content": build_file_tree(full_filename, strip_len),
75
- "mimetype": "application/folder",
76
- }
77
- )
76
+ if filename != ".git" and full_filename[strip_len:] not in (
77
+ uedition_settings.output.path,
78
+ "_build",
79
+ ):
80
+ files.append(
81
+ {
82
+ "name": filename,
83
+ "fullpath": full_filename[strip_len:],
84
+ "type": "folder",
85
+ "content": build_file_tree(full_filename, strip_len, uedition_settings),
86
+ "mimetype": "application/folder",
87
+ }
88
+ )
78
89
  elif os.path.isfile(full_filename):
79
90
  mimetype = guess_type(filename)
80
91
  files.append(
@@ -104,7 +115,7 @@ async def get_files(
104
115
  "fullpath": "",
105
116
  "type": "folder",
106
117
  "mimetype": "application/folder",
107
- "content": build_file_tree(full_path, len(full_path) + 1),
118
+ "content": build_file_tree(full_path, len(full_path) + 1, get_uedition_settings()),
108
119
  }
109
120
  ]
110
121
  except BranchNotFoundError as bnfe:
@@ -220,91 +231,142 @@ def clean_tei_subdoc(node: dict) -> dict:
220
231
 
221
232
  def parse_tei_file(path: str, settings: UEditorSettings) -> list[dict]:
222
233
  """Parse a TEI file into its constituent parts."""
223
- doc = etree.parse(path) # noqa: S320
224
- result = []
225
- for section in settings.tei.sections:
226
- section_root = doc.xpath(section.selector, namespaces=namespaces)
227
- if len(section_root) == 0:
228
- if section.type == "metadata":
229
- result.append(
230
- {
231
- "name": section.name,
232
- "title": section.title,
233
- "type": section.type,
234
- "content": [],
235
- }
236
- )
237
- elif section.type == "text":
238
- result.append(
239
- {
240
- "name": section.name,
241
- "title": section.title,
242
- "type": section.type,
243
- "content": {},
244
- }
245
- )
246
- elif section.type == "textlist":
247
- result.append(
248
- {
249
- "name": section.name,
250
- "title": section.title,
251
- "type": section.type,
252
- "content": [],
253
- }
254
- )
255
- else: # noqa: PLR5501
256
- if section.type == "metadata":
257
- result.append(
258
- {
259
- "name": section.name,
260
- "title": section.title,
261
- "type": section.type,
262
- "content": [parse_metadata_node(node) for node in section_root[0]],
263
- }
264
- )
265
- elif section.type == "text":
266
- result.append(
267
- {
268
- "name": section.name,
269
- "title": section.title,
270
- "type": section.type,
271
- "content": clean_tei_subdoc(parse_tei_subdoc(section_root[0], settings.tei)),
272
- }
273
- )
274
- elif section.type == "textlist":
275
- content = []
276
- for node in section_root:
277
- content.append(
234
+ try:
235
+ doc = etree.parse(path) # noqa: S320
236
+ result = []
237
+ for section in settings.tei.sections:
238
+ section_root = doc.xpath(section.selector, namespaces=namespaces)
239
+ if len(section_root) == 0:
240
+ if section.type == "metadata":
241
+ result.append(
278
242
  {
279
- "attrs": {"id": node.attrib["{http://www.w3.org/XML/1998/namespace}id"]},
280
- "content": clean_tei_subdoc(parse_tei_subdoc(node, settings.tei)),
243
+ "name": section.name,
244
+ "title": section.title,
245
+ "type": section.type,
246
+ "content": [],
281
247
  }
282
248
  )
283
- result.append(
284
- {
285
- "name": section.name,
286
- "title": section.title,
287
- "type": section.type,
288
- "content": content,
289
- }
290
- )
291
- return result
249
+ elif section.type == "text":
250
+ result.append(
251
+ {
252
+ "name": section.name,
253
+ "title": section.title,
254
+ "type": section.type,
255
+ "content": {},
256
+ }
257
+ )
258
+ elif section.type == "textlist":
259
+ result.append(
260
+ {
261
+ "name": section.name,
262
+ "title": section.title,
263
+ "type": section.type,
264
+ "content": [],
265
+ }
266
+ )
267
+ else: # noqa: PLR5501
268
+ if section.type == "metadata":
269
+ result.append(
270
+ {
271
+ "name": section.name,
272
+ "title": section.title,
273
+ "type": section.type,
274
+ "content": [parse_metadata_node(node) for node in section_root[0]],
275
+ }
276
+ )
277
+ elif section.type == "text":
278
+ result.append(
279
+ {
280
+ "name": section.name,
281
+ "title": section.title,
282
+ "type": section.type,
283
+ "content": clean_tei_subdoc(parse_tei_subdoc(section_root[0], settings.tei)),
284
+ }
285
+ )
286
+ elif section.type == "textlist":
287
+ content = []
288
+ for node in section_root:
289
+ content.append(
290
+ {
291
+ "attrs": {"id": node.attrib["{http://www.w3.org/XML/1998/namespace}id"]},
292
+ "content": clean_tei_subdoc(parse_tei_subdoc(node, settings.tei)),
293
+ }
294
+ )
295
+ result.append(
296
+ {
297
+ "name": section.name,
298
+ "title": section.title,
299
+ "type": section.type,
300
+ "content": content,
301
+ }
302
+ )
303
+ return result
304
+ except etree.XMLSyntaxError as e:
305
+ is_empty = False
306
+ with open(path) as in_f:
307
+ if in_f.read() == "":
308
+ is_empty = True
309
+ if is_empty:
310
+ result = []
311
+ for section in settings.tei.sections:
312
+ if section.type == "metadata":
313
+ result.append(
314
+ {
315
+ "name": section.name,
316
+ "title": section.title,
317
+ "type": section.type,
318
+ "content": [],
319
+ }
320
+ )
321
+ elif section.type == "text":
322
+ result.append(
323
+ {
324
+ "name": section.name,
325
+ "title": section.title,
326
+ "type": section.type,
327
+ "content": {},
328
+ }
329
+ )
330
+ elif section.type == "textlist":
331
+ result.append(
332
+ {
333
+ "name": section.name,
334
+ "title": section.title,
335
+ "type": section.type,
336
+ "content": [],
337
+ }
338
+ )
339
+ return result
340
+ else:
341
+ raise e
292
342
 
293
343
 
294
344
  @router.get("/{path:path}", response_model=None)
295
345
  async def get_file(
296
346
  branch_id: str,
297
347
  path: str,
298
- settings: Annotated[UEditorSettings, Depends(get_ueditor_settings)],
299
348
  current_user: Annotated[dict, Depends(get_current_user)], # noqa:ARG001
349
+ response: Response,
300
350
  ) -> dict | FileResponse:
301
351
  """Fetch a single file from the repo."""
302
352
  try:
303
353
  async with BranchContextManager(branch_id):
354
+ ueditor_settings = get_ueditor_settings()
355
+ uedition_settings = get_uedition_settings()
304
356
  full_path = os.path.abspath(os.path.join(init_settings.base_path, *path.split("/")))
305
357
  if full_path.startswith(os.path.abspath(init_settings.base_path)) and os.path.isfile(full_path):
306
358
  if full_path.endswith(".tei"):
307
- return parse_tei_file(full_path, settings)
359
+ if "tei" in uedition_settings.sphinx_config:
360
+ if "blocks" in uedition_settings.sphinx_config["tei"]:
361
+ ueditor_settings.tei.blocks.extend(
362
+ [TEINode(**block) for block in uedition_settings.sphinx_config["tei"]["blocks"]]
363
+ )
364
+ if "marks" in uedition_settings.sphinx_config["tei"]:
365
+ ueditor_settings.tei.marks.extend(
366
+ [TEINode(**mark) for mark in uedition_settings.sphinx_config["tei"]["marks"]]
367
+ )
368
+ response.headers["Content-Type"] = "application/json+tei"
369
+ return parse_tei_file(full_path, ueditor_settings)
308
370
  else:
309
371
  return FileResponse(full_path, media_type=guess_type(full_path)[0])
310
372
  raise HTTPException(404)
@@ -585,8 +647,9 @@ def serialise_tei_text(root: dict, data: dict, settings: TEITextSection, tei_set
585
647
  parent["children"] = []
586
648
  doc = data["content"]
587
649
  # TODO: Add docu attributes to the parent node
588
- for element in doc["content"]:
589
- parent["children"].append(serialise_tei_text_block(element, tei_settings))
650
+ if "content" in doc:
651
+ for element in doc["content"]:
652
+ parent["children"].append(serialise_tei_text_block(element, tei_settings))
590
653
 
591
654
 
592
655
  def serialise_tei_textlist(root: dict, data: dict, settings: TEITextSection, tei_settings: TEISettings) -> None:
@@ -662,11 +725,21 @@ async def update_file(
662
725
  """Update the file in the repo."""
663
726
  try:
664
727
  async with BranchContextManager(branch_id) as repo:
665
- settings = get_ueditor_settings()
728
+ ueditor_settings = get_ueditor_settings()
666
729
  full_path = os.path.abspath(os.path.join(init_settings.base_path, *path.split("/")))
667
730
  if full_path.startswith(os.path.abspath(init_settings.base_path)) and os.path.isfile(full_path):
668
731
  if full_path.endswith(".tei"):
669
- root = serialise_tei_file(full_path, json.load(content.file), settings)
732
+ uedition_settings = get_uedition_settings()
733
+ if "tei" in uedition_settings.sphinx_config:
734
+ if "blocks" in uedition_settings.sphinx_config["tei"]:
735
+ ueditor_settings.tei.blocks.extend(
736
+ [TEINode(**block) for block in uedition_settings.sphinx_config["tei"]["blocks"]]
737
+ )
738
+ if "marks" in uedition_settings.sphinx_config["tei"]:
739
+ ueditor_settings.tei.marks.extend(
740
+ [TEINode(**mark) for mark in uedition_settings.sphinx_config["tei"]["marks"]]
741
+ )
742
+ root = serialise_tei_file(full_path, json.load(content.file), ueditor_settings)
670
743
  with open(full_path, "wb") as out_f:
671
744
  out_f.write(b'<?xml version="1.0" encoding="UTF-8"?>\n')
672
745
  out_f.write(
@@ -3,7 +3,14 @@
3
3
  import logging
4
4
  from asyncio import Lock
5
5
 
6
- from pygit2 import CredentialType, GitError, KeypairFromAgent, RemoteCallbacks, Repository, Signature
6
+ from pygit2 import (
7
+ CredentialType,
8
+ GitError,
9
+ KeypairFromAgent,
10
+ RemoteCallbacks,
11
+ Repository,
12
+ Signature,
13
+ )
7
14
  from pygit2.enums import FetchPrune, MergeAnalysis, RepositoryOpenFlag
8
15
 
9
16
  from uedition_editor.settings import init_settings
@@ -49,7 +56,12 @@ class BranchContextManager:
49
56
  class RemoteRepositoryCallbacks(RemoteCallbacks):
50
57
  """Callback handler for connecting to remote repositories."""
51
58
 
52
- def credentials(self, url: str, username_from_url: str | None, allowed_types: CredentialType): # noqa:ARG002
59
+ def credentials(
60
+ self,
61
+ url: str, # noqa: ARG002
62
+ username_from_url: str | None,
63
+ allowed_types: CredentialType,
64
+ ):
53
65
  """Return the credentials for the remote connection."""
54
66
  if allowed_types & CredentialType.SSH_KEY == CredentialType.SSH_KEY:
55
67
  return KeypairFromAgent(username_from_url)
@@ -64,13 +76,16 @@ def fetch_repo(repo: Repository, remote: str) -> None:
64
76
 
65
77
  def pull_branch(repo: Repository, branch: str) -> None:
66
78
  """Pull and update the branch from the remote repository."""
67
- remote_default_head = repo.lookup_reference(repo.branches[branch].upstream_name)
68
- result, _ = repo.merge_analysis(remote_default_head.target)
69
- if result & MergeAnalysis.FASTFORWARD == MergeAnalysis.FASTFORWARD:
70
- repo.checkout_tree(repo.get(remote_default_head.target))
71
- local_default_head = repo.lookup_reference(f"refs/heads/{branch}")
72
- local_default_head.set_target(remote_default_head.target)
73
- repo.head.set_target(remote_default_head.target)
79
+ try:
80
+ remote_default_head = repo.lookup_reference(repo.branches[branch].upstream_name)
81
+ result, _ = repo.merge_analysis(remote_default_head.target)
82
+ if result & MergeAnalysis.FASTFORWARD == MergeAnalysis.FASTFORWARD:
83
+ repo.checkout_tree(repo.get(remote_default_head.target))
84
+ local_default_head = repo.lookup_reference(f"refs/heads/{branch}")
85
+ local_default_head.set_target(remote_default_head.target)
86
+ repo.head.set_target(remote_default_head.target)
87
+ except KeyError as e:
88
+ logger.error(e)
74
89
 
75
90
 
76
91
  def fetch_and_pull_branch(repo: Repository, remote: str, branch: str) -> None:
@@ -80,12 +95,21 @@ def fetch_and_pull_branch(repo: Repository, remote: str, branch: str) -> None:
80
95
  pull_branch(repo, branch)
81
96
 
82
97
 
83
- def commit_and_push(repo: Repository, remote: str, branch: str, commit_msg: str, author: Signature) -> None:
98
+ def commit_and_push(
99
+ repo: Repository,
100
+ remote: str,
101
+ branch: str,
102
+ commit_msg: str,
103
+ author: Signature,
104
+ extra_parents: list[str] | None = None,
105
+ ) -> None:
84
106
  """Commit changes to the repository and push."""
85
107
  if len(repo.status()) > 0:
86
108
  logger.debug(f"Committing changes to {', '.join(repo.status().keys())}")
87
109
  ref = repo.head.name
88
110
  parents = [repo.head.target]
111
+ if extra_parents is not None:
112
+ parents.extend(extra_parents)
89
113
  index = repo.index
90
114
  index.add_all()
91
115
  index.write()
@@ -0,0 +1,32 @@
1
+ # SPDX-FileCopyrightText: 2024-present Mark Hall <mark.hall@work.room3b.eu>
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+ """Regular jobs run in the background of the uEditor."""
5
+
6
+ import logging
7
+
8
+ import aiocron
9
+ from pygit2 import GitError, Repository
10
+ from pygit2.enums import RepositoryOpenFlag
11
+
12
+ from uedition_editor.api.util import fetch_repo, pull_branch, uedition_lock
13
+ from uedition_editor.settings import init_settings
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ @aiocron.crontab("* * * * *")
19
+ async def sync_remote() -> None:
20
+ """Synchronise with the remote."""
21
+ async with uedition_lock:
22
+ try:
23
+ repo = Repository(init_settings.base_path, flags=RepositoryOpenFlag.NO_SEARCH)
24
+ repo.checkout(repo.branches[init_settings.git.default_branch])
25
+ if init_settings.git.remote_name in list(repo.remotes.names()):
26
+ logging.debug("Synchronising with remote")
27
+ fetch_repo(repo, init_settings.git.remote_name)
28
+ for branch_id in repo.branches.local:
29
+ if repo.branches[branch_id].upstream is not None:
30
+ pull_branch(repo, branch_id)
31
+ except GitError as ge:
32
+ logger.error(ge)