Back to Blog

Building GitPulse: Engineering a Multi-Provider Mobile Git Client & In-App Code Editor in React Native

•Emmanouil Athanasopoulos•6 min read•1,303 words
React NativeExpoTypeScriptGitCodeMirrorMobile ArchitectureOpen Source

The Motivation Behind the Project

Modern software teams rarely keep all their code in a single forge: open-source projects thrive on GitHub, internal microservices and sensitive enterprise repos live on self-hosted GitLab CE/EE or Gitea clusters, and enterprise stacks often reside in Bitbucket Cloud. Yet mobile developer tooling remains stubbornly fragmented. Official mobile apps either lock developers into a single closed ecosystem (GitHub Mobile), force clumsy mobile browser workarounds (GitLab, Gitea), or omit code editing entirely. GitPulse was engineered to solve this dilemma: a high-performance, developer-first universal mobile Git client and syntax-aware code editor built on React Native and Expo 58, providing multi-forge triage, in-app commit authoring, line-by-line diff inspection, CI/CD pipeline monitoring, and seamless auto-updates on Android.

Core Features and Design Goals

  • Universal Multi-Provider Engine: First-class API integrations for GitHub (Personal Access Token & Fine-Grained), GitLab (GitLab.com SaaS and self-hosted CE/EE with dynamic API path prefix detection), Gitea/Forgejo/Codeberg (standard and custom self-hosted domains), and Bitbucket Cloud (workspace and repository slug routing).
  • In-App CodeMirror 6 Editor: High-performance WebView code editor powered by CodeMirror 6, featuring dynamic language syntax grammar loading (TypeScript, JavaScript, Python, Rust, Go, Markdown, JSON, YAML, HTML, CSS), fold gutters, active line highlighting, line numbers, search/replace, and dark/light theme pairing.
  • Pre-Commit Diff Review Modal: Inspect every file edit line-by-line in an interactive visual diff modal with green addition (+) and red deletion (-) markers before authoring commit messages and pushing changes upstream.
  • Mobile Branch Lifecycle & Merges: Full branch management capabilities directly from mobile—create feature branches, switch working tree refs, delete remote branches, and trigger pull request or merge request merges.
  • Issue & PR Discussion Drawers: Full comment timeline rendering with formatted Markdown, user avatars, and code snippets, paired with an interactive reply drawer supporting instant comment submission and 1-tap issue close/reopen toggles.
  • Multi-Provider CI/CD Pipeline Monitor: Real-time visibility into GitHub Actions workflow runs, GitLab CI/CD pipelines, and Gitea Actions with live status badges (success, running, failed, canceled), duration metrics, and direct web console deep-links.
  • 52-Week Contribution Activity Heatmap: Cross-forge activity matrix unifying GitHub GraphQL contribution calendars, GitLab user activity events, and Gitea's native /users/{username}/heatmap API into a unified SVG visual matrix.
  • Unified Inbox & Reactive Badge Sync: Centralized notification inbox consolidating GitHub notifications, GitLab Todos (/todos), and Gitea inbox items with 1-tap mark-as-read, bulk triage, and a decoupled NotificationService pub-sub event emitter that updates bottom-tab badge counters in real time.
  • Obtainium Auto-Updates: Continuous delivery release pipeline publishing signed ARM64/V7 universal APKs with release notes and full Obtainium one-tap auto-update compatibility.

Deep Dive: How It Works Under the Hood

The core architectural triumph of GitPulse is the IGitProvider abstraction contract. Each forge organizes code under radically divergent mental models: GitHub relies on flat owner/repo strings and GraphQL nodes; GitLab requires URL-encoded namespace paths (group%2Fsubgroup%2Fproject) or numeric project IDs alongside scoped REST v4 endpoints; Gitea adheres to a modified Swagger v1 schema; and Bitbucket Cloud structures repositories under workspace-scoped /2.0/repositories/{workspace}/{repo_slug} routes. By designing a unified contract, the UI layer communicates exclusively through normalized entity types (GitRepo, GitBranch, GitCommit, GitFileNode, GitIssue, GitNotification). The provider layer encapsulates authentication headers (Bearer vs token vs Basic), pagination transforms, and rate-limit parsing. CodeMirror 6 runs in an optimized local WebView bundle communicating over a bidirectional asynchronous postMessage RPC protocol, ensuring 60fps typing responsiveness without freezing React Native's JS thread.

Technical Implementation

  • Dynamic Multi-Forge Provider Factory: Implemented a runtime ProviderFactory that instantiates forge-specific adapters adhering to the unified IGitProvider interface based on the repository's registered provider type and custom domain.
  • CodeMirror 6 WebView Bridge: Embedded a pre-bundled CodeMirror 6 bundle inside a React Native WebView with bidirectional postMessage RPC, supporting syntax highlighting across 10+ languages, active line tracking, fold gutters, and dark/light theme switching.
  • Pre-Commit Diff Calculation: Built an in-memory diff engine that computes unified additions and deletions against original file buffers, rendering a visual diff modal with green (+) and red (-) line gutter markers prior to commit dispatch.
  • Multi-Provider CI/CD Pipeline Tracking: Unified GitHub Actions workflows (/actions/runs), GitLab CI/CD pipelines (/pipelines), and Gitea Actions into a normalized pipeline monitor displaying run status, duration, and commit context.
  • Cross-Forge 52-Week Contribution Graph: Architected contribution collectors that normalize GitHub GraphQL user calendar data, GitLab event timestamps (/users/{id}/events), and Gitea's /heatmap API into a 52-week SVG activity matrix.
  • Pub-Sub Notification Badge Sync: Implemented NotificationService pub-sub architecture to eliminate badge drift across bottom tab bars when marking individual items or entire inbox feeds as read.
  • Obtainium Auto-Update Automation: Configured automated GitHub Actions CI/CD workflows producing release APKs signed with v2/v3 signatures and formatted release metadata for 1-tap Obtainium updates.

Architecture Overview

GitPulse is organized across four distinct architectural boundaries: (1) Presentation Layer: Built with Expo Router 58, custom theme tokens (Obsidian, Dimmed, High-Contrast, Cyberpunk), bottom-sheet modal drawers, and Lucide React Native icons. (2) Provider Abstraction Layer: A strict IGitProvider TypeScript interface with concrete implementations (GitHubProvider, GitLabProvider, GiteaProvider, BitbucketProvider) resolved dynamically at runtime by ProviderFactory based on the active repository or account. (3) State & Cache Layer: Uses SWR with stale-while-revalidate policies and optimistic mutations for instantaneous offline cache hydration, paired with AES-256 Android Keystore encryption via Expo SecureStore. (4) Notification Pub-Sub Bus: A decoupled NotificationService event emitter connecting background pollers, screen actions, and the navigation root badge listener to guarantee zero desynchronization between read actions and tab icons.

Challenges I Faced Along the Way

Engineering a unified Git client across distinct forges presented subtle edge cases. GitLab's API requires strict URL-encoding for repository paths, meaning nested sub-group repositories like org/team/project must be encoded as org%2Fteam%2Fproject while handling custom domain ports (e.g. gitlab.internal:8443). For in-app code editing, soft keyboards on Android frequently triggered viewport jumping and obscured cursor position; this was resolved by binding CodeMirror's scroll-into-view listener to WebView window resize events. Furthermore, notification badge synchronization initially suffered from race conditions where marking notifications as read locally did not immediately reflect on parent navigation tab badges—solved by implementing a reactive pub-sub event bus that propagates unread count deltas across all app boundaries instantly.

Technology Choices and Alternatives

Traditional desktop Git clients (SourceTree, Fork, GitKraken) are too heavy for mobile screens and demand gigabytes of disk space for full local clones. On mobile, official apps like GitHub Mobile remain locked to a single vendor and treat code as read-only text. Specialized mobile editors like Spck Editor provide syntax editing but lack deep forge integration (PR triage, CI/CD pipelines, contribution graphs, issue discussions). GitPulse strikes the optimal balance: it leverages REST and GraphQL tree APIs for instantaneous repository navigation without cloning repository history, while providing a desktop-grade CodeMirror 6 editor with pre-commit visual diff review for true mobile-first development velocity.

The Technology Stack

Built with React Native 0.88, Expo SDK 58, TypeScript 5.9, CodeMirror 6, React Native WebView, SWR, Lucide React Native, Expo SecureStore, and GitHub Actions APK release automation.

What I Would Do Differently Next Time

API-driven Git operations are significantly more effective on mobile than full Git clones. Developers triage code on the go—reviewing PRs, verifying CI/CD builds, fixing configuration bugs, or applying hotfixes. By orchestrating forge APIs directly behind a unified interface, GitPulse delivers sub-second load times and zero storage overhead while maintaining all the power needed for daily software engineering workflows.

Key Patterns Worth Noting

The foundation of GitPulse's versatility is the IGitProvider interface, ensuring that screens, drawers, and editors interact with a completely provider-agnostic API contract: getRepository(owner, repo), getFileContent(owner, repo, path, ref), commitFileChange(owner, repo, path, content, message, branch), getPipelines(owner, repo), and getComments(owner, repo, number).

Final Reflections

Engineering GitPulse demonstrated how far modern mobile web technologies and React Native have evolved. With clean abstraction layers, high-performance WebView bridges, and thoughtful UX ergonomics, developers can comfortably review, edit, and ship code from anywhere.