Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
d7bbfc0
Introduce file diff table
varmar05 Jul 15, 2025
f50eb96
Add alembic migration for file diff table
varmar05 Jul 17, 2025
32b3c5b
Merge pull request #482 from MerginMaps/new_diffs_table
MarcelGeo Jul 23, 2025
feeafd9
Merge branch 'dev-r65-pull' into dev-r85-v2-pull
varmar05 Aug 26, 2025
8c18891
Utils for generating cached version levels from versions
varmar05 Aug 29, 2025
afb42e5
Add db hook to trigger caching on project version created
varmar05 Sep 1, 2025
b14e55b
create celery task to generate diff checkpoint
varmar05 Sep 4, 2025
4461ec9
Use diff checkpoint in gpkg restore function
varmar05 Sep 5, 2025
00f6211
ee docker compose files
MarcelGeo Sep 24, 2025
dd6463e
API: Add new v2 endpoint to download diff file
varmar05 Sep 25, 2025
85ef488
Remove celery caching job and trigger on project push
varmar05 Sep 25, 2025
3a281fa
Update redis service name
fernandinand Sep 25, 2025
f14e7a8
change default of archive size
MarcelGeo Sep 29, 2025
4ab69f6
Initial migration
MarcelGeo Oct 1, 2025
71f9f0f
Modify changes table in name
MarcelGeo Oct 1, 2025
a9d1119
Merge branch 'dev-r84-concurrent-push' into dev-r85-v2-pull
varmar05 Oct 2, 2025
0025b3b
Cosmetic changes
varmar05 Oct 2, 2025
30be795
Merge branch 'dev-r85-v2-pull' into merged-diffs
varmar05 Oct 2, 2025
3c2153a
Return custom error on failed diff download + small functions renamin…
varmar05 Oct 3, 2025
202eae7
Merge pull request #504 from MerginMaps/merged-diffs
varmar05 Oct 3, 2025
83f858f
Initial version for merging diffs
MarcelGeo Oct 3, 2025
0bfd6b5
Merge remote-tracking branch 'origin/dev-r85-v2-pull' into cache-vers…
MarcelGeo Oct 3, 2025
3db1550
Adapt merge versions
MarcelGeo Oct 3, 2025
03f5098
Delta endpoints + logic improvements:
MarcelGeo Oct 7, 2025
f4388a5
Cleanup env template fom qgis extactor urls
MarcelGeo Oct 8, 2025
55fac10
Upgrade to 7.3 images
MarcelGeo Oct 8, 2025
4fb5c83
extract method for create checkpoint
MarcelGeo Oct 8, 2025
63100a6
Final fixes and changing schema
MarcelGeo Oct 9, 2025
29a9eef
Fix missing import
MarcelGeo Oct 9, 2025
c53b928
Fix tests and add checkpoints just i UPDATE_DIFF
MarcelGeo Oct 9, 2025
dce0e11
add safe check for dwonloading
MarcelGeo Oct 9, 2025
832603d
Imporve tests
MarcelGeo Oct 9, 2025
a9140e8
Address comments @varmar05 1. part
MarcelGeo Oct 10, 2025
8f13613
Fix alembic migration for file diff
varmar05 Oct 13, 2025
c6b60ce
Merge pull request #521 from MerginMaps/update_migration
MarcelGeo Oct 14, 2025
d0ef271
enhancements v2
MarcelGeo Oct 16, 2025
36518fb
Merge pull request #514 from MerginMaps/ee-ce-2025.7.2
MarcelGeo Oct 22, 2025
5da31ec
Run celery job extractor
MarcelGeo Oct 23, 2025
bc93f92
add qgis extractor celery route to docker-compose to prevent unnecess…
MarcelGeo Oct 23, 2025
8b83a24
Merge pull request #524 from MerginMaps/run-extractor-celery
MarcelGeo Oct 24, 2025
8ceee04
Address disscussions:
MarcelGeo Oct 24, 2025
6e59b27
Merge pull request #527 from MerginMaps/develop
MarcelGeo Oct 27, 2025
99194b0
add mechanism for handling previous history files.
MarcelGeo Oct 28, 2025
2077d89
fix integrity test
MarcelGeo Oct 28, 2025
3685e43
Upgrade logic
MarcelGeo Oct 29, 2025
2c9bdad
Merge pull request #520 from MerginMaps/cache-versions
MarcelGeo Oct 30, 2025
02ff027
API: add 'v' prefix to version in delta endpoint
varmar05 Nov 6, 2025
f01986e
Merge pull request #532 from MerginMaps/delta_add_v_prefix
varmar05 Nov 6, 2025
537429e
Merge pull request #533 from MerginMaps/develop
MarcelGeo Nov 14, 2025
f4f00f0
Make construct diff method recursive
varmar05 Nov 14, 2025
21dfb2d
Make delta project function to create missing checkpoints recursively
varmar05 Nov 14, 2025
f7da890
Make diff checkoint validation check more robust
varmar05 Nov 25, 2025
2938e21
Add more tests
varmar05 Nov 25, 2025
a9986b1
Add cli command to trigger checkpoints caching
varmar05 Nov 25, 2025
2b872e5
Fix failing tests with random 504
varmar05 Nov 25, 2025
3a51545
Merge pull request #535 from MerginMaps/create_checkpoint_recursively
varmar05 Nov 26, 2025
54a6f16
Merge branch 'develop' into dev-r85-v2-pull
varmar05 Nov 26, 2025
80adef2
Publish v2 pull enabled flag
MarcelGeo Nov 27, 2025
bac1498
Merge pull request #539 from MerginMaps/v2-pull-flag
MarcelGeo Nov 27, 2025
6762f82
Merge pull request #542 from MerginMaps/develop
MarcelGeo Dec 9, 2025
169492d
Added to_version as latest project version to delta reponse for trans…
MarcelGeo Dec 12, 2025
decf2b1
Try to rename diff path to diff id
MarcelGeo Dec 12, 2025
81eb324
Some tweaks to comments
MarcelGeo Dec 12, 2025
ab7aeda
Merge pull request #547 from MerginMaps/provide-to-version
MarcelGeo Dec 16, 2025
2183811
Merge remote-tracking branch 'origin/develop' into dev-r85-v2-pull
MarcelGeo Jan 12, 2026
ad699b0
Merge pull request #556 from MerginMaps/develop
MarcelGeo Jan 16, 2026
8c7eb33
bump version
MarcelGeo Jan 16, 2026
181cb97
Merge pull request #559 from MerginMaps/bump_2026.1.0
varmar05 Jan 19, 2026
97a5a45
Merge branch 'master' into dev-r85-v2-pull
varmar05 Jan 19, 2026
2c745fa
Fix broken yaml file
varmar05 Jan 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions deployment/community/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ services:
networks:
- merginmaps
server:
image: lutraconsulting/merginmaps-backend:2025.2.2
image: lutraconsulting/merginmaps-backend:2025.7.3
container_name: merginmaps-server
restart: always
user: 901:999
Expand All @@ -39,7 +39,7 @@ services:
networks:
- merginmaps
celery-beat:
image: lutraconsulting/merginmaps-backend:2025.2.2
image: lutraconsulting/merginmaps-backend:2025.7.3
container_name: celery-beat
restart: always
env_file:
Expand All @@ -54,7 +54,7 @@ services:
networks:
- merginmaps
celery-worker:
image: lutraconsulting/merginmaps-backend:2025.2.2
image: lutraconsulting/merginmaps-backend:2025.7.3
container_name: celery-worker
restart: always
user: 901:999
Expand All @@ -73,7 +73,7 @@ services:
networks:
- merginmaps
web:
image: lutraconsulting/merginmaps-frontend:2025.2.2
image: lutraconsulting/merginmaps-frontend:2025.7.3
container_name: merginmaps-web
restart: always
depends_on:
Expand Down
6 changes: 1 addition & 5 deletions deployment/enterprise/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ LOCAL_PROJECTS=/data

# data download

#MAX_DOWNLOAD_ARCHIVE_SIZE=1024 * 1024 * 1024 # max total files size in bytes for archive download
#MAX_DOWNLOAD_ARCHIVE_SIZE=1024 * 1024 * 1024 * 10 # max total files size in bytes for archive download

#USE_X_ACCEL=False # use nginx (in front of gunicorn) to serve files (https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/)
USE_X_ACCEL=1
Expand Down Expand Up @@ -214,12 +214,8 @@ VECTOR_TILES_URL=https://tiles-ee.merginmaps.com/data/default/{z}/{x}/{y}.pbf

VECTOR_TILES_STYLE_URL=https://tiles-ee.merginmaps.com//styles/default.json

#QGIS_EXTRACTOR_API_URL=http://mergin-qgis-extractor:8000

#WMTS_SERVER_URL=http://mergin-qgis-nginx:80

#QGIS_EXTRACTOR_TIMEOUT=60

#OVERVIEW_MAX_FILE_SIZE=1048576 # 1MB

### Diagnostic logs from Mobile and QGIS Plugin
Expand Down
5 changes: 4 additions & 1 deletion deployment/enterprise/docker-compose.maps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,16 @@ services:
- ./qgis_nginx.conf:/etc/nginx/conf.d/default.conf
qgis_extractor:
container_name: mergin-qgis-extractor
image: 433835555346.dkr.ecr.eu-west-1.amazonaws.com/mergin/qgis-extractor-ee:2025.1.0
image: 433835555346.dkr.ecr.eu-west-1.amazonaws.com/mergin/qgis-extractor-ee:2025.3.0
user: 901:999
networks:
- mergin-net
environment:
- OVERVIEWS_DATA_DIR=/data
- MM_WMS_TILE_BUFFER=100
- MM_WMS_AVOID_ARTIFACTS=1
- BROKER_URL=redis://merginmaps-redis:6379/0
- CELERY_RESULT_BACKEND=redis://merginmaps-redis:6379/0
- 'CELERY_TASK_ROUTES={"src.maps.tasks.finish_overview": {"queue": "celery"}}'
volumes:
- ./map_data:/data
10 changes: 6 additions & 4 deletions deployment/enterprise/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ networks:

services:
server:
image: 433835555346.dkr.ecr.eu-west-1.amazonaws.com/mergin/mergin-ee-back:2025.5.1
image: 433835555346.dkr.ecr.eu-west-1.amazonaws.com/mergin/mergin-ee-back:2025.7.3
container_name: merginmaps-server
restart: always
user: 901:999
Expand All @@ -22,7 +22,7 @@ services:
networks:
- mergin
web:
image: 433835555346.dkr.ecr.eu-west-1.amazonaws.com/mergin/mergin-ee-front:2025.5.1
image: 433835555346.dkr.ecr.eu-west-1.amazonaws.com/mergin/mergin-ee-front:2025.7.3
container_name: merginmaps-web
restart: always
depends_on:
Expand Down Expand Up @@ -52,7 +52,7 @@ services:
- server

celery-beat:
image: 433835555346.dkr.ecr.eu-west-1.amazonaws.com/mergin/mergin-ee-back:2025.5.1
image: 433835555346.dkr.ecr.eu-west-1.amazonaws.com/mergin/mergin-ee-back:2025.7.3
container_name: merginmaps-celery-beat
restart: always
user: 901:999
Expand All @@ -66,7 +66,7 @@ services:
- mergin

celery-worker:
image: 433835555346.dkr.ecr.eu-west-1.amazonaws.com/mergin/mergin-ee-back:2025.5.1
image: 433835555346.dkr.ecr.eu-west-1.amazonaws.com/mergin/mergin-ee-back:2025.7.3
container_name: merginmaps-celery-worker
restart: always
user: 901:999
Expand All @@ -76,6 +76,8 @@ services:
- ./map_data:/overviews
env_file:
- .prod.env
environment:
- CELERY_ROUTES={ 'qgis-extractor.*':{'queue':'extractor'} }
depends_on:
- db
- redis
Expand Down
47 changes: 47 additions & 0 deletions server/mergin/sync/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,50 @@ def remove(project_name):
project.removed_by = None
db.session.commit()
click.secho("Project removed", fg="green")

@project.command()
@click.argument("project-name", callback=normalize_input(lowercase=False))
@click.option("--since", type=int, required=False)
@click.option("--to", type=int, required=False)
def create_checkpoint(project_name, since=None, to=None):
"""Create project delta checkpoint, corresponding lower checkpoints and merged diffs for project"""
ws, name = split_project_path(project_name)
workspace = current_app.ws_handler.get_by_name(ws)
if not workspace:
click.secho("ERROR: Workspace does not exist", fg="red", err=True)
sys.exit(1)
project = (
Project.query.filter_by(workspace_id=workspace.id, name=name)
.filter(Project.storage_params.isnot(None))
.first()
)
if not project:
click.secho("ERROR: Project does not exist", fg="red", err=True)
sys.exit(1)

since = since if since is not None else 0
to = to if to is not None else project.latest_version
if since < 0 or to < 1:
click.secho(
"ERROR: Invalid version number, minimum version for 'since' is 0 and minimum version for 'to' is 1",
fg="red",
err=True,
)
sys.exit(1)

if to > project.latest_version:
click.secho(
"ERROR: 'to' version exceeds latest project version", fg="red", err=True
)
sys.exit(1)

if since >= to:
click.secho(
"ERROR: 'since' version must be less than 'to' version",
fg="red",
err=True,
)
sys.exit(1)

project.get_delta_changes(since, to)
click.secho("Project checkpoint(s) created", fg="green")
2 changes: 2 additions & 0 deletions server/mergin/sync/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ class Configuration(object):
UPLOAD_CHUNKS_EXPIRATION = config(
"UPLOAD_CHUNKS_EXPIRATION", default=86400, cast=int
)
# whether client can pull using v2 apis
V2_PULL_ENABLED = config("V2_PULL_ENABLED", default=True, cast=bool)
EXCLUDED_CLONE_FILENAMES = config(
"EXCLUDED_CLONE_FILENAMES", default="qgis_cfg.xml", cast=Csv()
)
7 changes: 7 additions & 0 deletions server/mergin/sync/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,10 @@ def to_dict(self) -> Dict:
class BigChunkError(ResponseError):
code = "BigChunkError"
detail = f"Chunk size exceeds maximum allowed size {MAX_CHUNK_SIZE} MB"


class DiffDownloadError(ResponseError):
code = "DiffDownloadError"
detail = (
"Required diff file could not be downloaded as it could not be reconstructed"
)
127 changes: 124 additions & 3 deletions server/mergin/sync/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,18 @@
import datetime
from enum import Enum
import os
from dataclasses import dataclass
from dataclasses import dataclass, field
from typing import Optional, List
import uuid
from flask import current_app
from marshmallow import ValidationError, fields, EXCLUDE, post_dump, validates_schema
from marshmallow import (
ValidationError,
fields,
EXCLUDE,
post_dump,
validates_schema,
post_load,
)
from pathvalidate import sanitize_filename

from .utils import (
Expand Down Expand Up @@ -231,11 +238,125 @@ def validate(self, data, **kwargs):

class ProjectFileSchema(FileSchema):
mtime = DateTimeWithZ()
diff = fields.Nested(FileSchema())
diff = fields.Nested(
FileSchema(),
)

@post_dump
def patch_field(self, data, **kwargs):
# drop 'diff' key entirely if empty or None as clients would expect
if not data.get("diff"):
data.pop("diff", None)
return data


@dataclass
class DeltaDiffFile:
"""Diff file path in diffs list"""

id: str


class DeltaChangeDiffFileSchema(ma.Schema):
"""Schema for diff file path in diffs list"""

id = fields.String(required=True)


@dataclass
class DeltaChangeBase(File):
"""Base class for changes stored in json list or returned from delta endpoint"""

change: PushChangeType
version: int


@dataclass
class DeltaChangeMerged(DeltaChangeBase):
"""Delta item with merged diffs to list of multiple diff files"""

diffs: List[DeltaDiffFile] = field(default_factory=list)

def to_data_delta(self):
"""Convert DeltaMerged to DeltaData with single diff"""
result = DeltaChange(
path=self.path,
size=self.size,
checksum=self.checksum,
change=self.change,
version=self.version,
)
if self.diffs:
result.diff = self.diffs[0].id
return result


@dataclass
class DeltaChange(DeltaChangeBase):
"""Change items stored in database as list of this item with single diff file"""

diff: Optional[str] = None

def to_merged(self) -> DeltaChangeMerged:
"""Convert to DeltaMerged with multiple diffs"""
result = DeltaChangeMerged(
path=self.path,
size=self.size,
checksum=self.checksum,
change=self.change,
version=self.version,
)
if self.diff:
result.diffs = [DeltaDiffFile(id=self.diff)]
return result


class DeltaChangeBaseSchema(ma.Schema):
"""Base schema for delta json and response from delta endpoint"""

path = fields.String(required=True)
size = fields.Integer(required=True)
checksum = fields.String(required=True)
version = fields.Integer(required=True)
change = fields.Enum(PushChangeType, by_value=True, required=True)


class DeltaChangeSchema(DeltaChangeBaseSchema):
"""Schema for change data in changes column"""

diff = fields.String(required=False)

@post_load
def make_object(self, data, **kwargs):
return DeltaChange(**data)

@post_dump
def patch_field(self, data, **kwargs):
# drop 'diff' key entirely if empty or None as database would expect
if not data.get("diff"):
data.pop("diff", None)
return data


class DeltaChangeItemSchema(DeltaChangeBaseSchema):
"""Schema for delta changes response"""

version = fields.Function(lambda obj: f"v{obj.version}")
diffs = fields.List(fields.Nested(DeltaChangeDiffFileSchema()))

@post_dump
def patch_field(self, data, **kwargs):
# drop 'diffs' key entirely if empty or None as clients would expect
if not data.get("diffs"):
data.pop("diffs", None)
return data


class DeltaChangeRespSchema(ma.Schema):
"""Schema for list of delta changes wrapped in items field"""

items = fields.List(fields.Nested(DeltaChangeItemSchema()))
to_version = fields.String(required=True)

class Meta:
unknown = EXCLUDE
Loading
Loading