Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
1566e9a
Collapse ObjectChanges when merging with dependency graph
arthanson Nov 17, 2025
6369f74
Collapse ObjectChanges when merging simplified
arthanson Nov 18, 2025
5fa597d
tests
arthanson Nov 18, 2025
c19d82d
fix tests
arthanson Nov 18, 2025
c717166
update tests
arthanson Nov 18, 2025
44af813
dependency graph
arthanson Nov 18, 2025
dca51ea
fix ruff
arthanson Nov 18, 2025
f80fed9
truncate delete
arthanson Nov 19, 2025
9e94a21
Improved dependency merge
arthanson Nov 19, 2025
86a2e89
fix ruff
arthanson Nov 19, 2025
4b316ad
revert changes with collapse
arthanson Nov 19, 2025
50df037
revert tests and fixes
arthanson Nov 19, 2025
14a9e6c
revert tests and fixes
arthanson Nov 20, 2025
177a978
fix tests
arthanson Nov 20, 2025
c2dfa0b
fix tests
arthanson Nov 20, 2025
5bea48b
retain old code
arthanson Nov 20, 2025
874a263
refactor
arthanson Nov 20, 2025
dafd988
add selection for merge strategy
arthanson Nov 20, 2025
f7e5360
cleanup, simplify revert, fix merge ordering for delete
arthanson Nov 20, 2025
506d56d
dependency checking fixes
arthanson Nov 21, 2025
20b32a5
dependency checking fixes
arthanson Nov 21, 2025
53a48da
cleanup
arthanson Nov 21, 2025
a92f101
cleanup
arthanson Nov 21, 2025
39c1930
cleanup
arthanson Nov 21, 2025
3739d00
job timeout
arthanson Nov 21, 2025
c43d1a7
fix circuit termination
arthanson Nov 22, 2025
e65304f
fix circuit termination
arthanson Nov 22, 2025
01780c6
cleanup
arthanson Nov 24, 2025
7286c2c
cleanup
arthanson Nov 24, 2025
a5e59ba
cleanup
arthanson Nov 24, 2025
e55d714
refactor to separate file and add class methods for apply, undo
arthanson Nov 26, 2025
6def45d
collapse_changes to merge_strategy
arthanson Nov 26, 2025
722585e
ruff fixes
arthanson Nov 26, 2025
71a3232
move to merge strategies
arthanson Nov 26, 2025
944382f
remove Django comment from migration
arthanson Nov 26, 2025
0696909
fixes
arthanson Nov 26, 2025
1f5d1f5
cleanup
arthanson Nov 26, 2025
f08a421
cleanup
arthanson Nov 26, 2025
0fd952b
review changes
arthanson Dec 2, 2025
5e610d2
review changes - split forms for merge, revert, sync
arthanson Dec 2, 2025
7a38aa0
cleanup
arthanson Dec 2, 2025
40bc089
review cleanup - move clean method
arthanson Dec 2, 2025
d202937
review cleanup
arthanson Dec 3, 2025
6e71fa6
review cleanup - use dictionary for get_merge_strategy
arthanson Dec 3, 2025
a2288a6
review cleanup - add translation context to choices
arthanson Dec 3, 2025
e5cf935
use user who requested revert/merge in ChangeLog
arthanson Dec 3, 2025
cad3433
create temp ChangeLog record and use that apply/revert - get rid of c…
arthanson Dec 3, 2025
6fe5967
add test to check multiple modifications are squashed correctly
arthanson Dec 3, 2025
77595e8
review changes - refactor squash private functions into SquashMergeSt…
arthanson Dec 3, 2025
571fc56
review changes - refactor squash private functions into SquashMergeSt…
arthanson Dec 3, 2025
b13b082
remove redundant reset of vars
arthanson Dec 3, 2025
5f3e646
resolve merge conflict
arthanson Dec 3, 2025
ceb3e7a
resolve merge conflict
arthanson Dec 4, 2025
fb33af9
Nullable merge_strategy
arthanson Dec 8, 2025
3e9e229
move raise of AbortMerge back to branches.py
arthanson Dec 8, 2025
095387e
change to use model.name for key
arthanson Dec 8, 2025
3ef41dc
use enum for collapse action
arthanson Dec 8, 2025
98499d3
re-indent collapse check
arthanson Dec 8, 2025
fed0f3d
cleanup init of prechange_data
arthanson Dec 8, 2025
528efab
use form to control default value of merge_strategy
arthanson Dec 8, 2025
e8e3315
progressively collapse changes instead of keeping all in memory
arthanson Dec 8, 2025
d7f7c0f
cleanup
arthanson Dec 8, 2025
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
5 changes: 3 additions & 2 deletions netbox_branching/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from core.api.serializers import JobSerializer
from netbox.api.viewsets import BaseViewSet, NetBoxReadOnlyModelViewSet
from netbox_branching import filtersets
from netbox_branching.jobs import MergeBranchJob, RevertBranchJob, SyncBranchJob
from netbox_branching.jobs import JOB_TIMEOUT, MergeBranchJob, RevertBranchJob, SyncBranchJob
from netbox_branching.models import Branch, BranchEvent, ChangeDiff
from . import serializers

Expand Down Expand Up @@ -78,7 +78,8 @@ def merge(self, request, pk):
job = MergeBranchJob.enqueue(
instance=branch,
user=request.user,
commit=commit
commit=commit,
job_timeout=JOB_TIMEOUT
)

return Response(JobSerializer(job, context={'request': request}).data)
Expand Down
12 changes: 11 additions & 1 deletion netbox_branching/choices.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext_lazy as _, pgettext_lazy

from utilities.choices import ChoiceSet

Expand Down Expand Up @@ -46,6 +46,16 @@ class BranchStatusChoices(ChoiceSet):
)


class BranchMergeStrategyChoices(ChoiceSet):
ITERATIVE = 'iterative'
SQUASH = 'squash'

CHOICES = (
(ITERATIVE, _('Iterative')),
(SQUASH, pgettext_lazy('The act of compressing multiple records into one', 'Squash')),
)


class BranchEventTypeChoices(ChoiceSet):
PROVISIONED = 'provisioned'
SYNCED = 'synced'
Expand Down
32 changes: 29 additions & 3 deletions netbox_branching/forms/misc.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
from django import forms
from django.utils.translation import gettext_lazy as _

from netbox_branching.choices import BranchMergeStrategyChoices
from netbox_branching.models import ChangeDiff

__all__ = (
'BranchActionForm',
'BranchSyncForm',
'BranchMergeForm',
'BranchRevertForm',
'ConfirmationForm',
'MigrateBranchForm',
)


class BranchActionForm(forms.Form):
class BaseBranchActionForm(forms.Form):
"""Base form for branch actions (sync, merge, revert)."""
pk = forms.ModelMultipleChoiceField(
queryset=ChangeDiff.objects.all(),
required=False
required=False,
widget=forms.MultipleHiddenInput()
)
commit = forms.BooleanField(
required=False,
Expand Down Expand Up @@ -43,6 +48,27 @@ def clean(self):
return self.cleaned_data


class BranchSyncForm(BaseBranchActionForm):
"""Form for syncing a branch."""
pass


class BranchMergeForm(BaseBranchActionForm):
"""Form for merging a branch."""
merge_strategy = forms.ChoiceField(
choices=BranchMergeStrategyChoices,
initial=BranchMergeStrategyChoices.ITERATIVE,
required=True,
label=_('Merge Strategy'),
help_text=_('Strategy to use when merging changes.')
)


class BranchRevertForm(BaseBranchActionForm):
"""Form for reverting a branch."""
pass


class ConfirmationForm(forms.Form):
confirm = forms.BooleanField(
required=True,
Expand Down
16 changes: 16 additions & 0 deletions netbox_branching/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from .signal_receivers import validate_object_deletion_in_branch
from .utilities import ListHandler

JOB_TIMEOUT = 3600 # 1 hour - increased for large operations

__all__ = (
'MergeBranchJob',
'MigrateBranchJob',
Expand Down Expand Up @@ -51,6 +53,12 @@ class SyncBranchJob(JobRunner):
"""
class Meta:
name = 'Sync branch'
job_timeout = 3600 # 1 hour - increased for large syncs

@property
def job_timeout(self):
"""Return the job timeout from Meta."""
return getattr(self.Meta, 'job_timeout', None)

def _disconnect_signal_receivers(self):
"""
Expand Down Expand Up @@ -102,6 +110,10 @@ class MergeBranchJob(JobRunner):
class Meta:
name = 'Merge branch'

@property
def job_timeout(self):
return JOB_TIMEOUT

def run(self, commit=True, *args, **kwargs):
# Initialize logging
logger = logging.getLogger('netbox_branching.branch.merge')
Expand All @@ -123,6 +135,10 @@ class RevertBranchJob(JobRunner):
class Meta:
name = 'Revert branch'

@property
def job_timeout(self):
return JOB_TIMEOUT

def run(self, commit=True, *args, **kwargs):
# Initialize logging
logger = logging.getLogger('netbox_branching.branch.revert')
Expand Down
14 changes: 14 additions & 0 deletions netbox_branching/merge_strategies/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""
Merge strategy implementations for branch operations.
"""
from .strategy import MergeStrategy, get_merge_strategy
from .iterative import IterativeMergeStrategy
from .squash import SquashMergeStrategy


__all__ = (
'MergeStrategy',
'IterativeMergeStrategy',
'SquashMergeStrategy',
'get_merge_strategy',
)
51 changes: 51 additions & 0 deletions netbox_branching/merge_strategies/iterative.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""
Iterative merge strategy implementation.
"""
from django.db import DEFAULT_DB_ALIAS

from netbox.context_managers import event_tracking

from .strategy import MergeStrategy


__all__ = (
'IterativeMergeStrategy',
)


class IterativeMergeStrategy(MergeStrategy):
"""
Iterative merge strategy that applies/reverts changes one at a time in chronological order.
"""

def merge(self, branch, changes, request, logger, user):
"""
Apply changes iteratively in chronological order.
"""
models = set()

for change in changes:
models.add(change.changed_object_type.model_class())
with event_tracking(request):
request.id = change.request_id
request.user = change.user
change.apply(branch, using=DEFAULT_DB_ALIAS, logger=logger)

self._clean(models)

def revert(self, branch, changes, request, logger, user):
"""
Undo changes iteratively (one at a time) in reverse chronological order.
"""
models = set()

# Undo each change from the Branch
for change in changes:
models.add(change.changed_object_type.model_class())
with event_tracking(request):
request.id = change.request_id
request.user = change.user
change.undo(branch, logger=logger)

# Perform cleanup tasks
self._clean(models)
Loading
Loading