Commit fdfb7831fe
Verified · cmc
Layout: unified · split
src/srht_contrib/services/git.py +13 −4
| @@ -22,14 +22,18 @@ logger = logging.getLogger(__name__) | |||
| 22 | 22 | ||
| 23 | 23 | ||
| 24 | REPOSITORY_LOG_QUERY = """ | 24 | REPOSITORY_LOG_QUERY = """ |
| 25 | query RepositoryLog($username: String!, $repoName: String!, $cursor: Cursor) { | 25 | query RepositoryLog($username: String!, $repoName: String!, $cursor: Cursor, $from: String) { |
| 26 | user(username: $username) { | 26 | user(username: $username) { |
| 27 | repository(name: $repoName) { | 27 | repository(name: $repoName) { |
| 28 | name | 28 | name |
| 29 | owner { | 29 | owner { |
| 30 | canonicalName | 30 | canonicalName |
| 31 | } | 31 | } |
| 32 | log(cursor: $cursor) { | 32 | HEAD { |
| 33 | name | ||
| 34 | target | ||
| 35 | } | ||
| 36 | log(cursor: $cursor, from: $from) { | ||
| 33 | results { | 37 | results { |
| 34 | id | 38 | id |
| 35 | shortId | 39 | shortId |
| @@ -235,7 +239,12 @@ class GitIngestionService: | |||
| 235 | owner, repo_name = self._split_repository(actor, repository_name) | 239 | owner, repo_name = self._split_repository(actor, repository_name) |
| 236 | data = self.client.execute( | 240 | data = self.client.execute( |
| 237 | REPOSITORY_LOG_QUERY, | 241 | REPOSITORY_LOG_QUERY, |
| 238 | {"username": owner, "repoName": repo_name, "cursor": state["current_repository"]["cursor"]}, | 242 | { |
| 243 | "username": owner, | ||
| 244 | "repoName": repo_name, | ||
| 245 | "cursor": state["current_repository"]["cursor"], | ||
| 246 | "from": "HEAD", | ||
| 247 | }, | ||
| 239 | ) | 248 | ) |
| 240 | user = data.get("user") or {} | 249 | user = data.get("user") or {} |
| 241 | repository = user.get("repository") or {} | 250 | repository = user.get("repository") or {} |
| @@ -353,7 +362,7 @@ class GitIngestionService: | |||
| 353 | for _ in range(50): | 362 | for _ in range(50): |
| 354 | data = self.client.execute( | 363 | data = self.client.execute( |
| 355 | REPOSITORY_LOG_QUERY, | 364 | REPOSITORY_LOG_QUERY, |
| 356 | {"username": owner, "repoName": repo_name, "cursor": cursor}, | 365 | {"username": owner, "repoName": repo_name, "cursor": cursor, "from": "HEAD"}, |
| 357 | ) | 366 | ) |
| 358 | user = data.get("user") or {} | 367 | user = data.get("user") or {} |
| 359 | repository = user.get("repository") or {} | 368 | repository = user.get("repository") or {} |
tests/test_git_repository_cache.py +2 −1
| @@ -96,6 +96,7 @@ def test_git_poll_reuses_cached_discovered_repositories(db_session) -> None: | |||
| 96 | poller = PollerService(todo_service=todo_service, git_service=git_service, settings=settings) | 96 | poller = PollerService(todo_service=todo_service, git_service=git_service, settings=settings) |
| 97 | 97 | ||
| 98 | first_inserted = poller.poll_all(db_session, "~ccleberg") | 98 | first_inserted = poller.poll_all(db_session, "~ccleberg") |
| 99 | first_poll_user_repository_calls = [call for call in client.calls if "query UserRepositories" in call[0]] | ||
| 99 | second_inserted = poller.poll_all(db_session, "~ccleberg") | 100 | second_inserted = poller.poll_all(db_session, "~ccleberg") |
| 100 | 101 | ||
| 101 | user_repository_calls = [call for call in client.calls if "query UserRepositories" in call[0]] | 102 | user_repository_calls = [call for call in client.calls if "query UserRepositories" in call[0]] |
| @@ -103,5 +104,5 @@ def test_git_poll_reuses_cached_discovered_repositories(db_session) -> None: | |||
| 103 | 104 | ||
| 104 | assert first_inserted == 1 | 105 | assert first_inserted == 1 |
| 105 | assert second_inserted == 0 | 106 | assert second_inserted == 0 |
| 106 | assert len(user_repository_calls) == 1 | 107 | assert len(user_repository_calls) == len(first_poll_user_repository_calls) |
| 107 | assert cached_names == ["~ccleberg/Hutch"] | 108 | assert cached_names == ["~ccleberg/Hutch"] |
tests/test_ingestion.py +52 −3
| @@ -210,7 +210,7 @@ def test_todo_ingestion_is_idempotent(db_session) -> None: | |||
| 210 | "results": [ | 210 | "results": [ |
| 211 | { | 211 | { |
| 212 | "id": "1001", | 212 | "id": "1001", |
| 213 | "created": "2026-03-29T10:00:00Z", | 213 | "created": "2026-05-01T10:00:00Z", |
| 214 | "ticket": { | 214 | "ticket": { |
| 215 | "id": "123", | 215 | "id": "123", |
| 216 | "ref": "~ccleberg/todo/123", | 216 | "ref": "~ccleberg/todo/123", |
| @@ -229,7 +229,7 @@ def test_todo_ingestion_is_idempotent(db_session) -> None: | |||
| 229 | }, | 229 | }, |
| 230 | { | 230 | { |
| 231 | "id": "1002", | 231 | "id": "1002", |
| 232 | "created": "2026-03-30T09:00:00Z", | 232 | "created": "2026-05-02T09:00:00Z", |
| 233 | "ticket": { | 233 | "ticket": { |
| 234 | "id": "123", | 234 | "id": "123", |
| 235 | "ref": "~ccleberg/todo/123", | 235 | "ref": "~ccleberg/todo/123", |
| @@ -248,7 +248,7 @@ def test_todo_ingestion_is_idempotent(db_session) -> None: | |||
| 248 | }, | 248 | }, |
| 249 | { | 249 | { |
| 250 | "id": "1003", | 250 | "id": "1003", |
| 251 | "created": "2026-03-30T10:00:00Z", | 251 | "created": "2026-05-02T10:00:00Z", |
| 252 | "ticket": { | 252 | "ticket": { |
| 253 | "id": "123", | 253 | "id": "123", |
| 254 | "ref": "~ccleberg/todo/123", | 254 | "ref": "~ccleberg/todo/123", |
| @@ -507,6 +507,55 @@ def test_git_ingestion_auto_discovers_owned_repositories(db_session) -> None: | |||
| 507 | assert any("query UserRepositories" in call[0] for call in client.calls) | 507 | assert any("query UserRepositories" in call[0] for call in client.calls) |
| 508 | 508 | ||
| 509 | 509 | ||
| 510 | def test_git_ingestion_reads_repository_log_from_default_head_branch(db_session) -> None: | ||
| 511 | settings = make_settings( | ||
| 512 | ACTOR_ALIASES_JSON={"~ccleberg": ["cmc@example.com", "Chris Cleberg"]}, | ||
| 513 | GIT_TRACKED_REPOSITORIES=["Hutch"], | ||
| 514 | ) | ||
| 515 | git_payload = { | ||
| 516 | "user": { | ||
| 517 | "repository": { | ||
| 518 | "name": "Hutch", | ||
| 519 | "owner": {"canonicalName": "~ccleberg"}, | ||
| 520 | "HEAD": {"name": "refs/heads/trunk", "target": "abc123"}, | ||
| 521 | "log": { | ||
| 522 | "results": [ | ||
| 523 | { | ||
| 524 | "id": "abc123", | ||
| 525 | "shortId": "abc123", | ||
| 526 | "author": { | ||
| 527 | "name": "Chris Cleberg", | ||
| 528 | "email": "cmc@example.com", | ||
| 529 | "time": "2026-03-30T12:00:00Z", | ||
| 530 | }, | ||
| 531 | "committer": { | ||
| 532 | "name": "Chris Cleberg", | ||
| 533 | "email": "cmc@example.com", | ||
| 534 | "time": "2026-03-30T12:00:00Z", | ||
| 535 | }, | ||
| 536 | "message": "Commit on non-standard default branch", | ||
| 537 | } | ||
| 538 | ], | ||
| 539 | "cursor": None, | ||
| 540 | }, | ||
| 541 | } | ||
| 542 | } | ||
| 543 | } | ||
| 544 | client = StubClient(payloads_by_query={"query RepositoryLog": git_payload}) | ||
| 545 | git_service = GitIngestionService(client, settings) | ||
| 546 | |||
| 547 | result = git_service.fetch_recent_events("~ccleberg", since=datetime(2026, 3, 1, tzinfo=UTC)) | ||
| 548 | |||
| 549 | repository_log_calls = [call for call in client.calls if "query RepositoryLog" in call[0]] | ||
| 550 | assert len(result.events) == 1 | ||
| 551 | assert repository_log_calls[0][1] == { | ||
| 552 | "username": "ccleberg", | ||
| 553 | "repoName": "Hutch", | ||
| 554 | "cursor": None, | ||
| 555 | "from": "HEAD", | ||
| 556 | } | ||
| 557 | |||
| 558 | |||
| 510 | def test_sync_overlap_reuses_cursor_window_and_suppresses_duplicates(db_session) -> None: | 559 | def test_sync_overlap_reuses_cursor_window_and_suppresses_duplicates(db_session) -> None: |
| 511 | settings = make_settings() | 560 | settings = make_settings() |
| 512 | event = NormalizedEvent( | 561 | event = NormalizedEvent( |