Git Serverless API
Access and explore Git repository data via APIs.
Contents & Archives
Read files and directories, download tarballs and zips
Branches
Create, list, and manage branches and the default branch
Commits & Tags
Tags, commit history, compare, and create commits
Use Cases
Practical patterns for triggers, file browsing, and more
Contents & Archives
Get File or Directory Contents
Retrieve the contents of a file or directory at a specific ref (branch, commit, tag):
import { freestyle } from "freestyle";
// Get reference to an existing repository
const repo = freestyle.git.repos.ref({
repoId: "your-repo-id"
});
// Get contents of a specific file at a ref
const file = await repo.contents.get({
path: "src/index.ts",
rev: "main"
});
// Decode base64 content
const content = atob(file.content);
console.log(content);The response is a discriminated union based on the type field:
// When path points to a file
interface FileContents {
type: "file";
name: string;
path: string;
sha: string; // Git object hash
size: number; // File size in bytes
content: string; // Base64-encoded content
}
// When path points to a directory
interface DirContents {
type: "dir";
name: string;
path: string;
sha: string; // Git tree hash
entries: DirEntry[];
}
// Directory entries (recursive)
type DirEntry =
| { type: "file"; name: string; path: string; sha: string; size: number }
| { type: "dir"; name: string; path: string; sha: string; entries: DirEntry[] };Download Archives
Download repository contents as compressed archives:
import { freestyle } from "freestyle";
import { writeFile } from "fs/promises";
const repo = freestyle.git.repos.ref({ repoId: "your-repo-id" });
// Download repository as a tarball
const tarball = await repo.contents.downloadTarball({ rev: "main" });
await writeFile("repo.tar", Buffer.from(tarball));
// Download repository as a zip file
const zip = await repo.contents.downloadZip({ rev: "main" });
await writeFile("repo.zip", Buffer.from(zip));Branches
Get branch references:
import { freestyle } from "freestyle";
// Get reference to an existing repository
const repo = freestyle.git.repos.ref({
repoId: "your-repo-id"
});
// Get a specific branch
const branch = await repo.branches.get({ branchName: "main" });
console.log(branch.sha); // Commit hash the branch points to
// List all branches
const branches = await repo.branches.list();
for (const b of branches) {
console.log(b.name);
}Create a Branch
Create a new branch, optionally from a specific commit:
import { freestyle } from "freestyle";
const repo = freestyle.git.repos.ref({ repoId: "your-repo-id" });
// Create a branch from the default branch
const newBranch = await repo.branches.create({
name: "feature/something"
});
console.log(newBranch.name); // "feature/something"
console.log(newBranch.sha); // Commit SHA the branch points to
// Create a branch from a specific commit
const fromCommit = await repo.branches.create({
name: "feature-xyz",
sha: "a1b2c3d4e5f6..."
});Default Branch
Get or set the repository's default branch:
import { freestyle } from "freestyle";
// Get reference to an existing repository
const repo = freestyle.git.repos.ref({
repoId: "your-repo-id"
});
// Get the current default branch
const { defaultBranch } = await repo.branches.getDefaultBranch();
console.log(defaultBranch); // "main"
// Set a new default branch
await repo.branches.setDefaultBranch({ defaultBranch: "develop" });Commits & Tags
Tags
Get tags:
Commits
Get a Commit
Retrieve a specific commit by its SHA hash:
import { freestyle } from "freestyle";
const repo = freestyle.git.repos.ref({ repoId: "your-repo-id" });
const commit = await repo.commits.get({
sha: "a1b2c3d4e5f6..."
});
console.log(commit.message); // "feat: add new feature"
console.log(commit.author.name); // "John Doe"
console.log(commit.tree.sha); // Hash of the root treeResponse structure:
interface CommitObject {
author: {
date: string;
name: string;
email: string;
};
committer: {
date: string;
name: string;
email: string;
};
message: string;
tree: {
sha: string;
};
parents: Array<{
sha: string;
}>;
sha: string;
}List Commits
List commits with optional filtering and pagination:
import { freestyle } from "freestyle";
const repo = freestyle.git.repos.ref({ repoId: "your-repo-id" });
// List recent commits on a branch
const result = await repo.commits.list({
branch: "main",
limit: 20,
order: "desc"
});
for (const commit of result.commits) {
console.log(`${commit.sha.slice(0, 7)} - ${commit.message}`);
}Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
branch | string | HEAD | Filter commits by branch |
limit | number | 50 | Maximum number of commits to return (max 500) |
order | "desc" | "asc" | "desc" | Sort order — newest first or oldest first |
since | string | — | Start SHA for filtering/pagination (see below) |
until | string | — | End SHA for filtering/pagination (see below) |
Response structure:
interface CommitListResponse {
commits: CommitObject[];
// Number of commits returned in this page
count: number;
// Maximum number of commits requested
limit: number;
// Total number of commits available in the branch/range
total: number;
// Sort order used
order: "desc" | "asc";
// SHA of the next commit for pagination (null if no more commits)
next_commit?: string | null;
}Pagination
Use next_commit from the response as a cursor for the next page. The parameter to pass it to depends on the sort order:
order: "desc"(default) — passnext_commitasuntilfor the next pageorder: "asc"— passnext_commitassincefor the next page
import { freestyle } from "freestyle";
const repo = freestyle.git.repos.ref({ repoId: "your-repo-id" });
// Page through all commits (newest first)
let cursor: string | undefined;
let page = 1;
while (true) {
const result = await repo.commits.list({
branch: "main",
limit: 50,
order: "desc",
until: cursor
});
console.log(`Page ${page}: ${result.count} commits`);
if (!result.next_commit) break;
cursor = result.next_commit;
page++;
}You can also use since and until together to get a specific range of commits (similar to git log A..B):
// Get commits between two SHAs
const result = await repo.commits.list({
since: "older-commit-sha",
until: "newer-commit-sha"
});Create a Commit
Create a new commit with file changes:
import { freestyle } from "freestyle";
const repo = freestyle.git.repos.ref({ repoId: "your-repo-id" });
// Create a commit with text files
const { commit } = await repo.commits.create({
message: "Add new feature",
branch: "main",
files: [
{ path: "README.md", content: "# My Project" },
{ path: "src/index.ts", content: "console.log('Hello!');" }
],
author: { name: "John Doe", email: "john@example.com" }
});
console.log(`Created commit: ${commit.sha}`);File changes use a discriminated union — each file entry is either an add/modify or a delete:
// Add or modify a file (text)
{ path: "src/index.ts", content: "file content here" }
// Add or modify a file (binary, base64-encoded)
{ path: "assets/logo.png", content: base64String, encoding: "base64" }
// Delete a file
{ path: "old/deprecated.ts", deleted: true }Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
message | string | Yes | The commit message |
files | Array | Yes | Files to add, modify, or delete |
branch | string | No | Branch to commit to (defaults to the default branch). If the branch doesn't exist, creates an orphan branch. |
author | { name, email } | No | Author information |
expectedSha | string | No | Expected branch tip SHA for optimistic concurrency control |
Optimistic Concurrency Control
Use expectedSha to prevent race conditions when multiple processes commit concurrently. The commit will only succeed if the branch tip still matches the expected SHA — otherwise a 409 Conflict error is returned.
// Read the current branch tip
const branch = await repo.branches.get({ branchName: "main" });
// Commit only if nobody else has pushed in the meantime
await repo.commits.create({
message: "Safe concurrent update",
branch: "main",
files: [{ path: "version.txt", content: "2.0.0" }],
expectedSha: branch.sha
});Comparing Commits
Compare two revisions to see the differences between them:
import { freestyle } from "freestyle";
const repo = freestyle.git.repos.ref({ repoId: "your-repo-id" });
const diff = await repo.compare({
base: "main",
head: "feature-branch",
});
console.log(`Status: ${diff.status}`);
console.log(`Ahead by: ${diff.ahead_by}, Behind by: ${diff.behind_by}`);
for (const file of diff.files) {
console.log(`${file.status}: ${file.filename} (+${file.additions} -${file.deletions})`);
}Response structure:
interface CommitComparison {
// Relationship between head and base
status: "identical" | "ahead" | "behind" | "diverged";
// Number of commits head is ahead of base
ahead_by: number;
// Number of commits head is behind base
behind_by: number;
// Total commits in the comparison
total_commits: number;
// Files changed between base and head
files: DiffFile[];
}
interface DiffFile {
sha: string | null;
filename: string;
status: "added" | "removed" | "modified" | "renamed" | "copied" | "changed" | "unchanged";
additions: number;
deletions: number;
changes: number;
// Present for renamed/copied files
previous_filename?: string;
}Common Use Cases
Processing Files from a Git Trigger
When a Git trigger is invoked by a push to your repository, Freestyle sends a payload containing information about the event, including the commit hash. You can use this to inspect files that were changed in the commit:
import { freestyle } from "freestyle";
// This function would be called by your webhook handler
async function processGitTriggerWebhook(webhookPayload: { repoId: string; commit: string }) {
const repo = freestyle.git.repos.ref({ repoId: webhookPayload.repoId });
// Get the commit
const commit = await repo.commits.get({ sha: webhookPayload.commit });
console.log(`Processing commit: ${commit.message}`);
console.log(`Author: ${commit.author.name} <${commit.author.email}>`);
// Get the tree pointed to by the commit
const rootTree = await repo.trees.get({ sha: commit.tree.sha });
// Example: Find package.json in the repository
const packageJsonEntry = rootTree.tree.find(
(entry) => entry.type === "blob" && entry.path === "package.json"
);
if (packageJsonEntry) {
// Get the content of package.json
const blob = await repo.blobs.get({ sha: packageJsonEntry.sha });
// Parse the package.json content (base64 decode first)
const packageJson = JSON.parse(atob(blob.content));
console.log(`Project name: ${packageJson.name}`);
console.log(`Dependencies: ${Object.keys(packageJson.dependencies || {}).length}`);
}
}Building a File Browser
You can build a recursive file browser:
import { freestyle, GitRepo } from "freestyle";
async function exploreDirectory(repo: GitRepo, treeSha: string, currentPath = "") {
const tree = await repo.trees.get({ sha: treeSha });
for (const entry of tree.tree) {
const entryPath = currentPath ? `${currentPath}/${entry.path}` : entry.path;
if (entry.type === "tree") {
// Recursively explore subdirectories
await exploreDirectory(repo, entry.sha, entryPath);
} else if (entry.type === "blob") {
// Process files
console.log(`File: ${entryPath}`);
// Optionally fetch the blob content
// const blob = await repo.blobs.get({ sha: entry.sha });
// const content = atob(blob.content);
}
}
}
// Usage
const repo = freestyle.git.repos.ref({ repoId: "your-repo-id" });
const mainBranch = await repo.branches.get({ branchName: "main" });
const commit = await repo.commits.get({ sha: mainBranch.sha });
await exploreDirectory(repo, commit.tree.sha);Viewing File Contents from a Specific Commit
The simplest way to get file contents at a specific commit is using contents.get():
import { freestyle } from "freestyle";
const repo = freestyle.git.repos.ref({ repoId: "your-repo-id" });
// Get file contents at a specific commit
const file = await repo.contents.get({
path: "src/index.ts",
rev: "a1b2c3d4e5f6..." // Can be a commit SHA, branch name, or tag
});
// Decode the base64 content
const fileContent = atob(file.content);
console.log(fileContent);For more advanced use cases, you can manually traverse the Git object database:
import { freestyle } from "freestyle";
async function viewFileAtCommit(repoId: string, commitHash: string, filePath: string) {
const repo = freestyle.git.repos.ref({ repoId });
// Get the commit
const commit = await repo.commits.get({ sha: commitHash });
// Get the root tree
let currentTree = await repo.trees.get({ sha: commit.tree.sha });
// Navigate the directory structure
const pathParts = filePath.split('/');
const fileName = pathParts.pop()!;
for (const directory of pathParts) {
const dirEntry = currentTree.tree.find(
(entry) => entry.type === "tree" && entry.path === directory
);
if (!dirEntry) {
throw new Error(`Directory not found: ${directory}`);
}
currentTree = await repo.trees.get({ sha: dirEntry.sha });
}
// Find the file in the current directory
const fileEntry = currentTree.tree.find(
(entry) => entry.type === "blob" && entry.path === fileName
);
if (!fileEntry) {
throw new Error(`File not found: ${fileName}`);
}
// Get the file content and decode
const blob = await repo.blobs.get({ sha: fileEntry.sha });
return atob(blob.content);
}
// Usage
const content = await viewFileAtCommit(
"your-repo-id",
"a1b2c3d4e5f6...",
"src/index.ts"
);
console.log(content);Git Database API
For advanced use cases requiring direct access to Git objects (blobs, trees, commits, annotated tags), see the Git Database API guide.