Unified.to
All articles

How To Track Developer Activity Across GitHub, GitLab, and Bitbucket using a Unified Repository API


April 10, 2026

To track developer activity across GitHub, GitLab, and Bitbucket, you need consistent access to repository and commit data across multiple APIs.

With Unified's Repository API, you can:

  • retrieve repositories
  • fetch commit history
  • track updates over time

using a single, normalized interface.

This guide walks through how to implement a complete commit tracking pipeline.

What makes commit tracking complex

Commits are repository-scoped

There is no global endpoint for commits.

You must:

  1. list repositories
  2. retrieve commits per repository

This applies across GitHub, GitLab, and Bitbucket.

Update models are inconsistent

Commit updates are not handled the same way:

  • GitHub → commit-related events supported
  • GitLab → commit-related events supported
  • Bitbucket → no commit events

This requires:

  • webhook ingestion where available
  • polling using updated_gte where not

Pagination is required

All commit retrieval is paginated:

  • max limit = 100
  • offset is required for iteration

You must loop until all results are retrieved.

Step-by-step implementation

Step 1: List repositories

Start by retrieving repositories for a connection:

const repos = await sdk.repo.listRepoRepositories({
  connectionId,
  limit: 100,
  offset: 0,
  sort: 'updated_at',
  order: 'asc',
  fields: ['id', 'name', 'updated_at', 'org_id'],
});

This defines the scope of commit tracking.

Step 2: Retrieve commits per repository

Commits must be fetched per repository:

const commits = await sdk.repo.listRepoCommits({
  connectionId,
  repo_id: repo.id,
  limit: 100,
  offset: 0,
  sort: 'updated_at',
  order: 'asc',
  fields: [
    'id',
    'user_id',
    'repo_id',
    'message',
    'created_at',
    'updated_at',
    'branch_id',
  ],
});

Step 3: Paginate through commit history

Retrieve all commits using pagination:

let offset = 0;
const limit = 100;

while (true) {
  const page = await sdk.repo.listRepoCommits({
    connectionId,
    repo_id: repo.id,
    limit,
    offset,
    sort: 'updated_at',
    order: 'asc',
  });

  await processCommits(repo.id, page);

  if (page.length < limit) break;
  offset += limit;
}

Step 4: Track incremental updates

Use updated_gte to retrieve only new or modified commits:

const commits = await sdk.repo.listRepoCommits({
  connectionId,
  repo_id: repo.id,
  updated_gte: lastSyncAt,
  limit: 100,
  offset: 0,
  sort: 'updated_at',
  order: 'asc',
});

This avoids reprocessing full history.

Step 5: Use commit events where available

Unified exposes commit-related webhook events for:

  • GitHub
  • GitLab

Bitbucket does not provide commit events.

Recommended pattern:

  • subscribe to commit events for GitHub and GitLab
  • ingest updates as they occur
  • use polling for Bitbucket

Both flows should feed the same processing pipeline.

Step 6: Poll where events are unavailable

For integrations without commit events:

const commits = await sdk.repo.listRepoCommits({
  connectionId,
  repo_id: repo.id,
  updated_gte: lastSyncAt,
  limit: 100,
});

Run this on a schedule to maintain up-to-date data.

Step 7: Reduce payload size

Only request required fields:

fields: ['id', 'user_id', 'repo_id', 'created_at', 'updated_at']

This reduces latency and unnecessary upstream calls.

Step 8: Access provider-specific fields

If needed, include raw data:

const commit = await sdk.repo.getRepoCommit({
  connectionId,
  id: commitId,
  fields: ['id', 'message', 'raw'],
});

The raw field contains original provider payloads.

Step 9: Optional branch-level filtering

If you need branch-specific insights:

const commits = await sdk.repo.listRepoCommits({
  connectionId,
  repo_id: repo.id,
  branch_id: branch.id,
  limit: 100,
});

End-to-end example

const repos = await sdk.repo.listRepoRepositories({
  connectionId,
  limit: 100,
  offset: 0,
});

for (const repo of repos) {
  let offset = 0;
  const limit = 100;

  while (true) {
    const commits = await sdk.repo.listRepoCommits({
      connectionId,
      repo_id: repo.id,
      updated_gte: lastSyncAt,
      limit,
      offset,
      sort: 'updated_at',
      order: 'asc',
      fields: [
        'id',
        'user_id',
        'repo_id',
        'message',
        'created_at',
        'updated_at',
        'branch_id',
      ],
    });

    if (!commits.length) break;

    await processCommits(repo.id, commits);

    if (commits.length < limit) break;
    offset += limit;
  }
}

What this enables

  • developer activity tracking
  • commit-based analytics
  • engineering reporting
  • AI workflows using commit history
  • automation triggered by repository activity

Summary

To track commits across repository integrations:

  • list repositories first
  • retrieve commits per repository
  • paginate through results
  • use updated_gte for incremental updates
  • combine webhook ingestion and polling
  • normalize data into a single pipeline

Unified lets you implement this once and support multiple repository platforms without maintaining separate integrations.

Start your 30-day free trial

Book a demo

All articles