half-orm-dev 1.0.0a12__tar.gz → 1.0.0a14__tar.gz

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.
Files changed (79) hide show
  1. {half_orm_dev-1.0.0a12/half_orm_dev.egg-info → half_orm_dev-1.0.0a14}/PKG-INFO +1 -1
  2. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/patch_manager.py +19 -0
  3. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/repo.py +76 -61
  4. half_orm_dev-1.0.0a14/half_orm_dev/version.txt +1 -0
  5. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14/half_orm_dev.egg-info}/PKG-INFO +1 -1
  6. half_orm_dev-1.0.0a12/half_orm_dev/version.txt +0 -1
  7. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/AUTHORS +0 -0
  8. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/LICENSE +0 -0
  9. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/README.md +0 -0
  10. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/__init__.py +0 -0
  11. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/bootstrap_manager.py +0 -0
  12. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/cli/__init__.py +0 -0
  13. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/cli/commands/__init__.py +0 -0
  14. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/cli/commands/apply.py +0 -0
  15. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/cli/commands/bootstrap.py +0 -0
  16. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/cli/commands/check.py +0 -0
  17. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/cli/commands/clone.py +0 -0
  18. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/cli/commands/init.py +0 -0
  19. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/cli/commands/migrate.py +0 -0
  20. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/cli/commands/patch.py +0 -0
  21. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/cli/commands/release.py +0 -0
  22. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/cli/commands/restore.py +0 -0
  23. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/cli/commands/revert_migration.py +0 -0
  24. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/cli/commands/set_git_origin.py +0 -0
  25. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/cli/commands/sync.py +0 -0
  26. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/cli/commands/todo.py +0 -0
  27. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/cli/commands/undo.py +0 -0
  28. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/cli/commands/update.py +0 -0
  29. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/cli/commands/upgrade.py +0 -0
  30. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/cli/main.py +0 -0
  31. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/cli_extension.py +0 -0
  32. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/database.py +0 -0
  33. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/decorators.py +0 -0
  34. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/file_executor.py +0 -0
  35. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/hgit.py +0 -0
  36. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/migration_manager.py +0 -0
  37. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/migrations/0/17/1/00_move_to_hop.py +0 -0
  38. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/migrations/0/17/1/01_txt_to_toml.py +0 -0
  39. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/migrations/0/17/4/00_toml_dict_format.py +0 -0
  40. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/migrations/0/17/4/01_add_bootstrap_table.py +0 -0
  41. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/migrations/0/17/4/02_move_patches_to_subdirs.py +0 -0
  42. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/migrations/0/17/5/01_update_pyproject_dependency.py +0 -0
  43. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/migrations/0/18/0/00_add_async_support.py +0 -0
  44. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/migrations/0/18/0/01_update_default_tests.py +0 -0
  45. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/migrations/hop/BREAKING_CHANGES-1.0.0.md +0 -0
  46. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/modules.py +0 -0
  47. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/patch_validator.py +0 -0
  48. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/patches/0/1/0/00_half_orm_meta.database.sql +0 -0
  49. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/patches/0/1/0/01_alter_half_orm_meta.hop_release.sql +0 -0
  50. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/patches/0/1/0/02_half_orm_meta.view.hop_penultimate_release.sql +0 -0
  51. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/patches/log +0 -0
  52. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/patches/sql/half_orm_meta.sql +0 -0
  53. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/release_file.py +0 -0
  54. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/release_manager.py +0 -0
  55. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/scripts/repair-metadata.py +0 -0
  56. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/templates/.gitignore +0 -0
  57. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/templates/MANIFEST.in +0 -0
  58. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/templates/README +0 -0
  59. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/templates/conftest_template +0 -0
  60. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/templates/git-hooks/pre-commit +0 -0
  61. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/templates/git-hooks/prepare-commit-msg +0 -0
  62. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/templates/init_module_template +0 -0
  63. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/templates/module_stub_template +0 -0
  64. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/templates/module_template_1 +0 -0
  65. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/templates/module_template_2 +0 -0
  66. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/templates/module_template_3 +0 -0
  67. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/templates/pyproject.toml +0 -0
  68. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/templates/relation_test +0 -0
  69. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/templates/sql_adapter +0 -0
  70. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/templates/warning +0 -0
  71. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev/utils.py +0 -0
  72. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev.egg-info/SOURCES.txt +0 -0
  73. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev.egg-info/dependency_links.txt +0 -0
  74. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev.egg-info/entry_points.txt +0 -0
  75. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev.egg-info/requires.txt +0 -0
  76. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/half_orm_dev.egg-info/top_level.txt +0 -0
  77. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/pyproject.toml +0 -0
  78. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/setup.cfg +0 -0
  79. {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a14}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: half_orm_dev
3
- Version: 1.0.0a12
3
+ Version: 1.0.0a14
4
4
  Summary: half_orm development Framework.
5
5
  Author-email: Joël Maïzi <joel.maizi@collorg.org>
6
6
  License-Expression: GPL-3.0-or-later
@@ -2002,6 +2002,25 @@ class PatchManager:
2002
2002
  result = patch_mgr.merge_patch()
2003
2003
  # Merges into ho-release/0.17.0, moves to stage
2004
2004
  """
2005
+ # 0. Remove stale local ho-* branches before any state change.
2006
+ # A stale branch (local but no longer on remote) causes
2007
+ # sync_hop_to_active_branches to leave staged-but-uncommitted
2008
+ # .hop/ changes that block subsequent checkouts, silently
2009
+ # skipping branches that should receive the TOML update.
2010
+ try:
2011
+ stale_result = self._repo.hgit.prune_local_branches(
2012
+ pattern="ho-*",
2013
+ dry_run=False,
2014
+ exclude_current=True,
2015
+ )
2016
+ if stale_result.get('deleted'):
2017
+ click.echo(
2018
+ f" ℹ Removed {len(stale_result['deleted'])} stale local "
2019
+ f"branch(es): {', '.join(stale_result['deleted'])}"
2020
+ )
2021
+ except Exception as e:
2022
+ raise PatchManagerError(f"Failed to clean up stale branches: {e}")
2023
+
2005
2024
  # 1. Extract patch_id from current branch
2006
2025
  current_branch = self._repo.hgit.branch
2007
2026
  if not current_branch.startswith('ho-patch/'):
@@ -643,10 +643,19 @@ class Repo:
643
643
  # Get all active branches (including ho-prod, release branches, and patch branches)
644
644
  try:
645
645
  branches_status = self.hgit.get_active_branches_status()
646
- patch_branches = [b['name'] for b in branches_status.get('patch_branches', [])]
647
- release_branches = [b['name'] for b in branches_status.get('release_branches', [])]
648
-
649
- # ho-staged/* branches are frozen after merge excluded from sync
646
+ # Only sync branches that still exist on remote — stale local
647
+ # branches (e.g. ho-patch/X renamed to ho-staged/X) leave
648
+ # staged-but-uncommitted .hop/ changes after a failed pre-commit
649
+ # hook, blocking checkout of subsequent branches in the loop.
650
+ patch_branches = [b['name'] for b in branches_status.get('patch_branches', [])
651
+ if b.get('exists_on_remote', True)]
652
+ release_branches = [b['name'] for b in branches_status.get('release_branches', [])
653
+ if b.get('exists_on_remote', True)]
654
+
655
+ # ho-staged/* branches are frozen after merge — excluded from sync.
656
+ # ho-prod is included but may have a protective hook (e.g. blocking
657
+ # direct commits); a commit failure on it is treated as a soft skip
658
+ # rather than aborting the whole sync (see Phase 1 loop below).
650
659
  all_branches = ['ho-prod'] + release_branches + patch_branches
651
660
 
652
661
  # Filter release branches to avoid syncing to future versions
@@ -685,140 +694,146 @@ class Repo:
685
694
  result['errors'].append(f"Failed to get active branches: {e}")
686
695
  return result
687
696
 
688
- # Sync each target branch
697
+ # Phase 1 local commits only (no push yet).
698
+ # All commits are made locally first so that a failure on any branch
699
+ # can be fully rolled back via git reset --hard, leaving a clean state.
700
+ original_shas = {} # branch → SHA recorded after checkout/fast-forward
701
+ committed_branches = [] # branches that received a new local commit
702
+ commit_msg = f"[HOP] Sync .hop/ from {source_branch} ({reason})"
703
+
704
+ def _rollback_phase1():
705
+ """Undo all local commits made during Phase 1."""
706
+ for rb, sha in original_shas.items():
707
+ try:
708
+ self.hgit.checkout(rb)
709
+ self.hgit._HGit__git_repo.git.reset('--hard', sha)
710
+ except Exception:
711
+ pass
712
+ try:
713
+ self.hgit.checkout(source_branch)
714
+ self.__config = Config(self.base_dir)
715
+ except Exception:
716
+ pass
717
+
689
718
  for branch in target_branches:
690
719
  try:
691
- # Checkout to target branch
692
720
  self.hgit.checkout(branch)
693
721
 
694
- # Reset to origin (source of truth) before syncing .hop/
695
- # This avoids non-fast-forward push failures when another
696
- # actor has already synced this branch.
722
+ # Fast-forward if behind remote (safe no local commits lost)
697
723
  remote_ref = f"origin/{branch}"
698
724
  try:
699
725
  synced, status = self.hgit.is_branch_synced(branch)
700
- # Only fast-forward when origin is strictly ahead (no local commits
701
- # at risk). Never reset on diverged branches — that would destroy
702
- # unmerged local work.
703
726
  if not synced and status == "behind":
704
727
  self.hgit._HGit__git_repo.git.reset('--hard', remote_ref)
705
728
  except GitCommandError:
706
- # Remote branch may not exist yet, continue without reset
707
729
  pass
708
730
 
709
- # Reload config for this branch
731
+ # Record SHA after fast-forward: this is the rollback target
732
+ original_shas[branch] = self.hgit._HGit__git_repo.head.commit.hexsha
733
+
710
734
  self.__config = Config(self.base_dir)
711
735
 
712
- # Use git checkout to copy .hop/ from source branch
713
- # This updates/adds files but doesn't remove deleted files
736
+ # Copy .hop/ from source branch
714
737
  self.hgit._HGit__git_repo.git.checkout(source_branch, '--', '.hop/')
715
738
 
716
- # Also checkout additional files if specified
717
739
  if additional_files:
718
740
  for file_path in additional_files:
719
741
  try:
720
742
  self.hgit._HGit__git_repo.git.checkout(source_branch, '--', file_path)
721
743
  except GitCommandError:
722
- # File might not exist in source branch, skip
723
744
  pass
724
745
 
725
- # Find files that exist in target but not in source and remove them
726
- # IMPORTANT: Only remove files for versions that exist in source
727
- # Don't remove release files (*-patches.toml, *.txt) for other versions
746
+ # Remove files that exist in target but no longer in source
728
747
  try:
729
- # Get list of files in .hop/ on current branch (target)
730
748
  target_files_output = self.hgit._HGit__git_repo.git.ls_files('.hop/')
731
749
  target_files = set(f for f in target_files_output.split('\n') if f.strip())
732
-
733
- # Get list of files in .hop/ on source branch
734
750
  source_files_output = self.hgit._HGit__git_repo.git.ls_tree('-r', '--name-only', source_branch, '.hop/')
735
751
  source_files = set(f for f in source_files_output.split('\n') if f.strip())
736
-
737
- # Get versions present in source (from .toml and .txt release files)
738
752
  source_versions = set()
739
753
  for file_path in source_files:
740
- # Extract version from release files
741
754
  if file_path.startswith('.hop/releases/'):
742
755
  filename = file_path.replace('.hop/releases/', '')
743
- # Match patterns: X.Y.Z-patches.toml, X.Y.Z.txt, X.Y.Z-rcN.txt, X.Y.Z-hotfixN.txt
744
756
  match = re.match(r'^(\d+\.\d+\.\d+)[-.]', filename)
745
757
  if match:
746
758
  source_versions.add(match.group(1))
747
-
748
- # Files to delete = in target but not in source
749
- files_to_delete = target_files - source_files
750
-
751
- # Filter: only delete files for versions present in source
752
- # This prevents deleting release files for unrelated versions
753
759
  safe_to_delete = []
754
- for file_path in files_to_delete:
760
+ for file_path in target_files - source_files:
755
761
  if not file_path:
756
762
  continue
757
- # Check if it's a release file
758
763
  if file_path.startswith('.hop/releases/'):
759
764
  filename = file_path.replace('.hop/releases/', '')
760
765
  match = re.match(r'^(\d+\.\d+\.\d+)[-.]', filename)
761
766
  if match:
762
- file_version = match.group(1)
763
- # Only delete if this version exists in source
764
- if file_version in source_versions:
767
+ if match.group(1) in source_versions:
765
768
  safe_to_delete.append(file_path)
766
769
  else:
767
- # Not a versioned file, safe to delete
768
770
  safe_to_delete.append(file_path)
769
771
  else:
770
- # Not in releases/, safe to delete
771
772
  safe_to_delete.append(file_path)
772
-
773
- # Remove files
774
773
  for file_path in safe_to_delete:
775
774
  self.hgit._HGit__git_repo.git.rm(file_path)
776
- except Exception as e:
777
- # If something fails in deletion detection, log but continue
778
- # The checkout already happened, so we have the updates
775
+ except Exception:
779
776
  pass
780
777
 
781
- # Stage all changes
782
778
  self.hgit.add('.hop/')
783
779
  if additional_files:
784
780
  for file_path in additional_files:
785
781
  try:
786
782
  self.hgit.add(file_path)
787
783
  except GitCommandError:
788
- pass # File might not exist
784
+ pass
789
785
 
790
- # Check if there are changes
791
786
  status = self.hgit._HGit__git_repo.git.status('--porcelain')
792
787
  if not status.strip():
793
- # No changes, skip
794
788
  result['skipped_branches'].append(branch)
789
+ del original_shas[branch] # nothing to roll back for this branch
795
790
  continue
796
791
 
797
- # Commit changes
798
- commit_msg = f"[HOP] Sync .hop/ from {source_branch} ({reason})"
799
792
  self.hgit.commit('-m', commit_msg)
800
793
 
801
- # Record the SHA of the sync commit for potential revert
802
794
  result['branch_commits'][branch] = (
803
795
  self.hgit._HGit__git_repo.head.commit.hexsha
804
796
  )
805
-
806
- # Push to remote
807
- self.hgit.push_branch(branch)
808
-
809
- result['synced_branches'].append(branch)
797
+ committed_branches.append(branch)
810
798
 
811
799
  except Exception as e:
812
- result['errors'].append(f"{branch}: {str(e)}")
800
+ if branch == 'ho-prod':
801
+ # ho-prod may have a protective hook that blocks direct
802
+ # commits. Clean up staged state so the next checkout
803
+ # succeeds, then skip gracefully.
804
+ try:
805
+ self.hgit._HGit__git_repo.git.reset('HEAD')
806
+ self.hgit._HGit__git_repo.git.checkout('--', '.hop/')
807
+ except Exception:
808
+ pass
809
+ original_shas.pop('ho-prod', None)
810
+ result['errors'].append(f"ho-prod: sync skipped (commit blocked): {e}")
811
+ else:
812
+ # Any other branch failure aborts and rolls back all local commits.
813
+ _rollback_phase1()
814
+ raise RepoError(
815
+ f"Sync commit failed on '{branch}': {e}\n"
816
+ f"All local sync commits have been rolled back.\n"
817
+ f"Run 'hop check' to diagnose."
818
+ ) from e
813
819
 
814
- # Return to source branch
820
+ # Return to source branch before pushing
815
821
  try:
816
822
  self.hgit.checkout(source_branch)
817
- # Reload config for source branch
818
823
  self.__config = Config(self.base_dir)
819
824
  except Exception as e:
820
825
  result['errors'].append(f"Failed to return to {source_branch}: {e}")
821
826
 
827
+ # Phase 2 — push all committed branches.
828
+ # The distributed lock ensures no conflicting pushes; errors here are
829
+ # unexpected but collected rather than raised.
830
+ for branch in committed_branches:
831
+ try:
832
+ self.hgit.push_branch(branch)
833
+ result['synced_branches'].append(branch)
834
+ except Exception as e:
835
+ result['errors'].append(f"{branch}: push failed: {e}")
836
+
822
837
  return result
823
838
 
824
839
  def sync_and_validate_ho_prod(self):
@@ -0,0 +1 @@
1
+ 1.0.0-a14
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: half_orm_dev
3
- Version: 1.0.0a12
3
+ Version: 1.0.0a14
4
4
  Summary: half_orm development Framework.
5
5
  Author-email: Joël Maïzi <joel.maizi@collorg.org>
6
6
  License-Expression: GPL-3.0-or-later
@@ -1 +0,0 @@
1
- 1.0.0-a12
File without changes
File without changes