Repository navigation
fix: preserve proxy identity across worktrees #199
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -291,5 +291,112 @@ def test_manifest_records_repository_relative_graph(self): | |
| record = json.loads(manifest)['imports']['github.com/owner/repo::sub/source::Example'] | ||
| self.assertEqual(record['source_graph'], 'sub/source') | ||
|
|
||
| class WorktreeSyncTests(unittest.TestCase): | ||
| def setUp(self): | ||
| self.temp = tempfile.TemporaryDirectory() | ||
| self.root = Path(self.temp.name).resolve() | ||
|
|
||
| def tearDown(self): | ||
| self.temp.cleanup() | ||
|
|
||
| def git(self, root, *args): | ||
| return subprocess.check_output(['git', '-C', str(root), *args], text=True, stderr=subprocess.DEVNULL).strip() | ||
|
|
||
| def repo(self, name, graph_path): | ||
| repo = self.root / name | ||
| repo.mkdir() | ||
| self.git(repo, 'init', '-q', '-b', 'main') | ||
| self.git(repo, 'config', 'user.name', 'Test') | ||
| self.git(repo, 'config', 'user.email', 'test@example.test') | ||
| self.git(repo, 'remote', 'add', 'origin', f'git@github.com:owner/{name}.git') | ||
| graph = repo / graph_path | ||
| (graph / 'pages').mkdir(parents=True) | ||
| (graph / 'logseq').mkdir() | ||
| (graph / 'logseq/config.edn').write_text('{}') | ||
| (graph / 'pages/.keep').touch() | ||
| return repo, graph | ||
|
|
||
| def commit(self, repo): | ||
| self.git(repo, 'add', '.') | ||
| self.git(repo, 'commit', '-qm', 'fixture') | ||
|
|
||
| def sync(self, source, destination): | ||
| import companions | ||
| plan = build_page_plan(source, destination, 'Example') | ||
| companions.extend_plan(plan) | ||
| apply_plan(plan) | ||
| return plan | ||
|
|
||
| def test_source_and_destination_worktrees_and_relocated_clones(self): | ||
| for scope in ('.', 'sub/source'): | ||
| with self.subTest(scope=scope): | ||
| name = 'source-root' if scope == '.' else 'source-nested' | ||
| repo, source = self.repo(name, scope) | ||
| (repo / 'mise-tasks').mkdir() | ||
| implementation = repo / 'mise-tasks/example' | ||
| implementation.write_text('#!/bin/sh\necho main\n') | ||
| implementation.chmod(0o755) | ||
| (source / 'pages/Example.md').write_text( | ||
| 'task-owner:: [[Owner]]\ntask-config-root:: .\ntask-name:: example\n' | ||
| 'task-entrypoint:: mise-tasks/example\n' | ||
| 'task-files:: {"mise-tasks/example":"mise-tasks/example"}\n' | ||
| f'source-link:: https://github.com/owner/{name}/blob/main/mise-tasks/example\n' | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. where did
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The --AI(gpt-6-unspecified Proxy task import review) |
||
| '- main\n') | ||
| self.commit(repo) | ||
| dest_repo, destination = self.repo('destination-' + name, 'garden') | ||
| self.commit(dest_repo) | ||
| source_worktree = self.root / ('189-' + name) | ||
| destination_worktree = self.root / ('destination-worktree-' + name) | ||
| self.git(repo, 'worktree', 'add', '-qb', 'codex/189-proxy', str(source_worktree)) | ||
| self.git(dest_repo, 'worktree', 'add', '-qb', 'codex/dependent', str(destination_worktree)) | ||
| worktree_graph = source_worktree / scope | ||
| target_graph = destination_worktree / 'garden' | ||
| self.sync(source, target_graph) | ||
| expected_url = graph_url(source, 'Example') | ||
| (worktree_graph / 'pages/Example.md').write_text( | ||
| (worktree_graph / 'pages/Example.md').read_text().replace('- main', '- branch')) | ||
| (source_worktree / 'mise-tasks/example').write_text('#!/bin/sh\necho branch\n') | ||
| plan = self.sync(worktree_graph, target_graph) | ||
| props, body = parse_properties((target_graph / 'pages/Example.md').read_text()) | ||
| self.assertEqual(props['logseq-proxy-url'], expected_url) | ||
| self.assertIn('/blob/codex%2F189-proxy/', props['logseq-proxy-codeforge-url']) | ||
| self.assertEqual(body, '- branch\n') | ||
| self.assertIn('echo branch', (target_graph / 'mise-tasks/example').read_text()) | ||
| manifest = plan.writes['.logseq-proxy/manifest.json'] | ||
| self.assertNotIn(str(self.root).encode(), manifest) | ||
| repeat = self.sync(worktree_graph, target_graph) | ||
| self.assertTrue(all(data == repeat.expected[path] for path, data in repeat.writes.items())) | ||
| # A normal re-sync after merge uses the registered graph identity again. | ||
| manifest = self.sync(source, target_graph).writes['.logseq-proxy/manifest.json'] | ||
| self.commit(destination_worktree) | ||
| relocation = self.root / ('relocated-' + name) | ||
| relocation.mkdir() | ||
| relocated_repo = relocation / name | ||
| self.git(relocation, 'clone', '-q', str(repo), str(relocated_repo)) | ||
| self.git(relocated_repo, 'remote', 'set-url', 'origin', f'git@github.com:owner/{name}.git') | ||
| relocated_destination = relocation / 'destination/garden' | ||
| self.git(relocation, 'clone', '-q', '-b', 'codex/dependent', str(dest_repo), str(relocated_destination.parent)) | ||
| relocated = self.sync(relocated_repo / scope, relocated_destination) | ||
| self.assertEqual(relocated.writes['.logseq-proxy/manifest.json'], manifest) | ||
| # Same graph folder name in another repository still cannot claim the page. | ||
| self.git(relocated_repo, 'remote', 'set-url', 'origin', 'git@github.com:other/repo.git') | ||
| before = (relocated_destination / 'pages/Example.md').read_bytes() | ||
| with self.assertRaisesRegex(SyncError, 'ownership conflict'): | ||
| self.sync(relocated_repo / scope, relocated_destination) | ||
| self.assertEqual((relocated_destination / 'pages/Example.md').read_bytes(), before) | ||
|
|
||
| def test_detached_source_worktree_url_names_commit(self): | ||
| repo, source = self.repo('detached-source', '.') | ||
| (source / 'pages/Example.md').write_text('- main\n') | ||
| self.commit(repo) | ||
| worktree = self.root / 'detached-worktree' | ||
| self.git(repo, 'worktree', 'add', '-q', '--detach', str(worktree)) | ||
| _, destination = self.repo('detached-destination', 'garden') | ||
| plan = build_page_plan(worktree, destination, 'Example') | ||
| props, _ = parse_properties(plan.writes['pages/Example.md'].decode()) | ||
| self.assertEqual(props['logseq-proxy-url'], graph_url(source, 'Example')) | ||
| self.assertIn('/blob/' + self.git(repo, 'rev-parse', 'HEAD') + '/', props['logseq-proxy-codeforge-url']) | ||
|
|
||
|
|
||
| if __name__ == '__main__': | ||
| unittest.main() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,15 @@ see-also:: [[Logseq/Entity/Proxy/Page]], [[Logseq/Entity/Definition]] | |
| - ~~~sh | ||
| mise run logseq:entity:proxy:page:sync --source /path/to/logseq-encode-garden --destination /path/to/other-garden --page 'GitP/A/Session/26/09/24-Thu' --follow-embeds --apply | ||
| ~~~ | ||
| - ## Source and destination worktrees | ||
| - Select each worktree explicitly while its dependent source changes are under review: | ||
| - ~~~sh | ||
| python3 /path/to/source-worktree/mise-tasks/logseq/entity/proxy/page/sync --source /path/to/source-worktree --destination /path/to/destination-worktree/garden --page 'Logseq/Entity/Proxy/Page' --apply | ||
| ~~~ | ||
| - Invoking the source checkout's script imports the current sync implementation along with the entity definition. The destination can then run its imported task for other pages from the same source worktree. | ||
| - The source worktree supplies content while the primary checkout supplies the logical graph name. Code forge URLs point to the selected source branch, or to the commit for a detached worktree. The portable manifest keeps the same source ownership across branches and checkouts. | ||
| - Commit the manifest with the imported pages and task files. Push the source branch and link the source PR as a dependency of the destination PR. After the source PR merges, re-sync with the primary checkout selected as `--source` to update the source URLs to the default branch. | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this where the idea of the "manifest" entered into the system?
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it entered earlier, here - https://github.com/codekiln/logseq-encode-garden/pull/186/changes
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, your PR #186 pointer identifies the earlier documentation introduction: 96b332c7 define proxy companion task contract describes the manifest in the Proxy Page entity and sync task reference. PR #188 supplies the task-import implementation. PR #199 then fixes source worktree identity. The illustrated manifest explanation now lists its sections and explains ownership and onward imports. --AI(gpt-6-unspecified Proxy task import review) |
||
| - Omitting `--source` resolves the registered primary checkout. An unmerged source branch therefore requires an explicit source worktree path on each sync. | ||
| - ## Inputs and outputs | ||
| - Reads the source page and the entity definitions named by `logseq-entity::`. Definitions declare companion tasks with `entity-tasks::`; task references declare implementation paths and required task dependencies through [[Logseq/Entity/Mise/Task]]. | ||
| - Copies pages under their source logical names, adds [[Logseq/Entity/Proxy/Page]] membership, and sets source URL, available code forge URL, and sync date. Source bodies replace destination bodies. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is the purpose of
task-filesexplained?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The purpose is specified by the Mise Task companion-file contract.
task-files::maps each source repository-relative implementation file to a destination graph-relative path undermise-tasks/, including the entrypoint and runtime helpers. That contract was added by PR #186 companion task contract.--AI(gpt-6-unspecified Proxy task import review)