Skip to content

Commit f533e33

Browse files
authored
Merge pull request #644 from MerginMaps/master
Backport 5.0 to maps
2 parents 1c83a26 + 56e867c commit f533e33

30 files changed

Lines changed: 1049 additions & 401 deletions

.github/workflows/auto_tests.yml

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,18 @@ name: Auto Tests
33
on: push
44

55
jobs:
6-
server_tests:
6+
tests:
77
runs-on: ubuntu-24.04
88

9+
strategy:
10+
fail-fast: false
11+
matrix:
12+
include:
13+
- suite: server
14+
pytest_args: "-v --cov=mergin --cov-report=lcov mergin/tests"
15+
- suite: migration
16+
pytest_args: "-v mergin/test_migrations"
17+
918
services:
1019
postgres:
1120
image: postgres:14
@@ -15,13 +24,18 @@ jobs:
1524
POSTGRES_USER: postgres
1625
ports:
1726
- 5435:5432
18-
# Set health checks to wait until postgres has started
1927
options: >-
2028
--health-cmd pg_isready
2129
--health-interval 10s
2230
--health-timeout 5s
2331
--health-retries 5
2432
33+
env:
34+
DB_USER: postgres
35+
DB_PASSWORD: postgres
36+
DB_HOST: localhost
37+
DB_PORT: 5435
38+
2539
steps:
2640
- name: Check out repository
2741
uses: actions/checkout@v3
@@ -36,9 +50,10 @@ jobs:
3650
- name: Run tests
3751
run: |
3852
cd server
39-
pipenv run pytest -v --cov=mergin --cov-report=lcov mergin/tests
53+
pipenv run pytest ${{ matrix.pytest_args }}
4054
4155
- name: Coveralls
56+
if: matrix.suite == 'server'
4257
uses: coverallsapp/github-action@v2
4358
with:
4459
base-path: server

server/mergin/sync/interfaces.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,12 @@ def get_by_name(self, name):
110110
"""
111111
pass
112112

113+
def get_by_names(self, names):
114+
"""
115+
Return list of workspaces whose names are in the given collection.
116+
"""
117+
pass
118+
113119
@abstractmethod
114120
def get_by_project(self, project):
115121
"""

server/mergin/sync/models.py

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import json
77
import logging
88
import os
9+
import re
910
import threading
1011
import time
1112
import uuid
@@ -18,7 +19,9 @@
1819
from blinker import signal
1920
from flask_login import current_user
2021
from pygeodiff import GeoDiff
22+
from functools import cached_property
2123
from sqlalchemy import text, null, desc, nullslast, tuple_
24+
from sqlalchemy.orm import contains_eager, joinedload, load_only
2225
from sqlalchemy.dialects.postgresql import ARRAY, BIGINT, UUID, JSONB, ENUM, insert
2326
from sqlalchemy.types import String
2427
from sqlalchemy.ext.hybrid import hybrid_property
@@ -137,6 +140,12 @@ def workspace(self):
137140
project_workspace = current_app.ws_handler.get(self.workspace_id)
138141
return project_workspace
139142

143+
@cached_property
144+
def _has_conflict(self) -> bool:
145+
"""True if any current project file matches a known conflict-copy pattern."""
146+
pattern = r"(\.gpkg|\.qgs|.qgz)(.*conflict.*)|( \(.*conflict.*)"
147+
return any(re.search(pattern, f.path) for f in self.files)
148+
140149
def get_latest_files_cache(self) -> List[int]:
141150
"""Get latest file history ids either from cached table or calculate them on the fly"""
142151
if self.latest_project_files.file_history_ids is not None:
@@ -669,7 +678,7 @@ def __init__(
669678
def path(self) -> str:
670679
return self.file.path
671680

672-
@property
681+
@cached_property
673682
def diff(self) -> Optional[FileDiff]:
674683
"""Diff file pushed with UPDATE_DIFF change type.
675684
@@ -724,9 +733,37 @@ def changes(
724733
if not (is_versioned_file(file) and since is not None and to is not None):
725734
return []
726735

727-
history = []
736+
# when since=1 the range spans the entire project history; narrow it to
737+
# the most recent CREATE/DELETE so we don't load records from previous
738+
# file lifecycles that the Python break would discard anyway
739+
if since == 1:
740+
boundary = (
741+
FileHistory.query.join(ProjectFilePath)
742+
.filter(
743+
ProjectFilePath.project_id == project_id,
744+
ProjectFilePath.path == file,
745+
FileHistory.project_version_name <= to,
746+
FileHistory.change.in_(
747+
[PushChangeType.CREATE.value, PushChangeType.DELETE.value]
748+
),
749+
)
750+
.order_by(desc(FileHistory.project_version_name))
751+
.with_entities(FileHistory.project_version_name)
752+
.first()
753+
)
754+
since = boundary[0] if boundary else since
755+
728756
full_history = (
729757
FileHistory.query.join(ProjectFilePath)
758+
.join(FileHistory.version)
759+
.join(ProjectVersion.project)
760+
.options(
761+
contains_eager(FileHistory.file).load_only(ProjectFilePath.path),
762+
contains_eager(FileHistory.version)
763+
.load_only(ProjectVersion.name, ProjectVersion.project_id)
764+
.contains_eager(ProjectVersion.project)
765+
.load_only(Project.storage_params),
766+
)
730767
.filter(
731768
ProjectFilePath.project_id == project_id,
732769
FileHistory.project_version_name <= to,
@@ -737,6 +774,7 @@ def changes(
737774
.all()
738775
)
739776

777+
history = []
740778
for item in full_history:
741779
history.append(item)
742780

@@ -1792,11 +1830,15 @@ def diff_summary(self):
17921830

17931831
def changes_count(self) -> Dict:
17941832
"""Return number of changes by type"""
1795-
query = f"SELECT change, COUNT(change) FROM file_history WHERE version_id = :version_id GROUP BY change;"
1833+
query = "SELECT change, COUNT(change) FROM file_history WHERE version_id = :version_id GROUP BY change;"
17961834
params = {"version_id": self.id}
17971835
result = db.session.execute(text(query), params).fetchall()
17981836
return {row[0]: row[1] for row in result}
17991837

1838+
@cached_property
1839+
def _changes_count(self) -> Dict:
1840+
return self.changes_count()
1841+
18001842
@property
18011843
def zip_path(self):
18021844
return os.path.join(

server/mergin/sync/public_api.yaml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1124,11 +1124,6 @@ components:
11241124
- added
11251125
- updated
11261126
- removed
1127-
expiration:
1128-
nullable: true
1129-
type: string
1130-
format: date-time
1131-
example: 2019-02-26T08:47:58.636074Z
11321127
UploadFileInfo:
11331128
allOf:
11341129
- $ref: "#/components/schemas/FileInfo"

0 commit comments

Comments
 (0)