summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorGavin Mak <gavinmak@google.com>2026-04-01 23:03:09 +0000
committerLUCI <gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com>2026-04-09 12:09:40 -0700
commit3b0eebeccfd4447e4a50ae143f37d0f7817723be (patch)
tree5bffc4647d4f883eac0a514ef304b8f2fd75c32c /tests
parent00991bfb42c4f3b0b7a50aa8f475ac1c8369924b (diff)
downloadgit-repo-3b0eebeccfd4447e4a50ae143f37d0f7817723be.tar.gz
project: implement stateless sync pruning logic
Implement in-situ shallow re-fetching and garbage collection logic. Enables repositories with sync-strategy="stateless" to reclaim disk space by running reflog expire and git gc --prune=now if the working tree is clean and has no local commits. Bug: 498730431 Change-Id: I940bdc9b74da29d3f7b13566667dcddea769ebd3 Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/568463 Reviewed-by: Mike Frysinger <vapier@google.com> Tested-by: Gavin Mak <gavinmak@google.com> Commit-Queue: Gavin Mak <gavinmak@google.com>
Diffstat (limited to 'tests')
-rw-r--r--tests/test_project.py118
1 files changed, 118 insertions, 0 deletions
diff --git a/tests/test_project.py b/tests/test_project.py
index 501707eaf..a2d90d803 100644
--- a/tests/test_project.py
+++ b/tests/test_project.py
@@ -21,6 +21,7 @@ import subprocess
21import tempfile 21import tempfile
22from typing import Optional 22from typing import Optional
23import unittest 23import unittest
24from unittest import mock
24 25
25import utils_for_test 26import utils_for_test
26 27
@@ -565,3 +566,120 @@ class ManifestPropertiesFetchedCorrectly(unittest.TestCase):
565 566
566 fakeproj.config.SetString("manifest.platform", "auto") 567 fakeproj.config.SetString("manifest.platform", "auto")
567 self.assertEqual(fakeproj.manifest_platform, "auto") 568 self.assertEqual(fakeproj.manifest_platform, "auto")
569
570
571class StatelessSyncTests(unittest.TestCase):
572 """Tests for stateless sync strategy."""
573
574 def _get_project(self, tempdir):
575 manifest = mock.MagicMock()
576 manifest.manifestProject.depth = None
577 manifest.manifestProject.dissociate = False
578 manifest.manifestProject.clone_filter = None
579 manifest.is_multimanifest = False
580 manifest.manifestProject.config.GetBoolean.return_value = False
581
582 remote = mock.MagicMock()
583 remote.name = "origin"
584 remote.url = "http://"
585
586 proj = project.Project(
587 manifest=manifest,
588 name="test-project",
589 remote=remote,
590 gitdir=os.path.join(tempdir, ".git"),
591 objdir=os.path.join(tempdir, ".git"),
592 worktree=tempdir,
593 relpath="test-project",
594 revisionExpr="1234abcd",
595 revisionId=None,
596 sync_strategy="stateless",
597 )
598 proj._CheckForImmutableRevision = mock.MagicMock(return_value=False)
599 proj._LsRemote = mock.MagicMock(
600 return_value="1234abcd\trefs/heads/main\n"
601 )
602 proj.bare_git = mock.MagicMock()
603 proj.bare_git.rev_parse.return_value = "5678abcd"
604 proj.bare_git.rev_list.return_value = ["0"]
605 proj.IsDirty = mock.MagicMock(return_value=False)
606 proj.GetBranches = mock.MagicMock(return_value=[])
607 proj.DeleteWorktree = mock.MagicMock()
608 proj._InitGitDir = mock.MagicMock()
609 proj._RemoteFetch = mock.MagicMock(return_value=True)
610 proj._InitRemote = mock.MagicMock()
611 proj._InitMRef = mock.MagicMock()
612 return proj
613
614 def test_sync_network_half_stateless_prune_needed(self):
615 """Test stateless sync queues prune when needed."""
616 with utils_for_test.TempGitTree() as tempdir:
617 proj = self._get_project(tempdir)
618 res = proj.Sync_NetworkHalf()
619
620 self.assertTrue(res.success)
621 proj.DeleteWorktree.assert_not_called()
622 self.assertTrue(proj.stateless_prune_needed)
623 proj._RemoteFetch.assert_called_once()
624
625 def test_sync_local_half_stateless_prune(self):
626 """Test stateless GC pruning is queued in Sync_LocalHalf."""
627 with utils_for_test.TempGitTree() as tempdir:
628 proj = self._get_project(tempdir)
629 proj.stateless_prune_needed = True
630
631 proj._Checkout = mock.MagicMock()
632 proj._InitWorkTree = mock.MagicMock()
633 proj.IsRebaseInProgress = mock.MagicMock(return_value=False)
634 proj.IsCherryPickInProgress = mock.MagicMock(return_value=False)
635 proj.bare_ref = mock.MagicMock()
636 proj.bare_ref.all = {}
637 proj.GetRevisionId = mock.MagicMock(return_value="1234abcd")
638 proj._CopyAndLinkFiles = mock.MagicMock()
639
640 proj.work_git = mock.MagicMock()
641 proj.work_git.GetHead.return_value = "5678abcd"
642
643 syncbuf = project.SyncBuffer(proj.config)
644
645 with mock.patch("project.GitCommand") as mock_git_cmd:
646 mock_cmd_instance = mock.MagicMock()
647 mock_cmd_instance.Wait.return_value = 0
648 mock_git_cmd.return_value = mock_cmd_instance
649
650 proj.Sync_LocalHalf(syncbuf)
651 syncbuf.Finish()
652
653 self.assertEqual(mock_git_cmd.call_count, 2)
654 mock_git_cmd.assert_any_call(
655 proj, ["reflog", "expire", "--expire=all", "--all"], bare=True
656 )
657 mock_git_cmd.assert_any_call(
658 proj,
659 ["gc", "--prune=now"],
660 bare=True,
661 capture_stdout=True,
662 capture_stderr=True,
663 )
664
665 def test_sync_network_half_stateless_skips_if_stash(self):
666 """Test stateless sync skips if stash exists."""
667 with utils_for_test.TempGitTree() as tempdir:
668 proj = self._get_project(tempdir)
669 proj.HasStash = mock.MagicMock(return_value=True)
670
671 res = proj.Sync_NetworkHalf()
672
673 self.assertTrue(res.success)
674 self.assertFalse(getattr(proj, "stateless_prune_needed", False))
675
676 def test_sync_network_half_stateless_skips_if_local_commits(self):
677 """Test stateless sync skips if there are local-only commits."""
678 with utils_for_test.TempGitTree() as tempdir:
679 proj = self._get_project(tempdir)
680 proj.bare_git.rev_list.return_value = ["1"]
681
682 res = proj.Sync_NetworkHalf()
683
684 self.assertTrue(res.success)
685 self.assertFalse(getattr(proj, "stateless_prune_needed", False))