LogoFreestyle

Git Serverless API

Access and explore Git repository data via APIs.

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:

import { freestyle } from "freestyle";

// Get reference to an existing repository
const repo = freestyle.git.repos.ref({
  repoId: "your-repo-id"
});

// Get a specific tag by name
const tag = await repo.tags.get({ tagName: "v1.0.0" });
console.log(tag.sha); // Commit hash the tag points to

// List all tags in the repository
const tags = await repo.tags.list();
for (const t of tags) {
  console.log(t.name);
}

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 tree

Response 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:

ParameterTypeDefaultDescription
branchstringHEADFilter commits by branch
limitnumber50Maximum number of commits to return (max 500)
order"desc" | "asc""desc"Sort order — newest first or oldest first
sincestringStart SHA for filtering/pagination (see below)
untilstringEnd 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) — pass next_commit as until for the next page
  • order: "asc" — pass next_commit as since for 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:

ParameterTypeRequiredDescription
messagestringYesThe commit message
filesArrayYesFiles to add, modify, or delete
branchstringNoBranch to commit to (defaults to the default branch). If the branch doesn't exist, creates an orphan branch.
author{ name, email }NoAuthor information
expectedShastringNoExpected 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.

On this page

Freestyle AI

Documentation assistant

Experimental: AI responses may not always be accurate—please verify important details with the official documentation.

How can I help?

Ask me about Freestyle while you browse the docs.