Automate Branch Creation & Commits to External Repositories with GitHub Apps

Introduction

In modern software development, automation plays a crucial role in enhancing both efficiency and reliability. One common scenario is automating the process of creating branches and committing changes from one repository to another. This is particularly useful when you need to synchronize configurations, back up data, or deploy code across multiple repositories.

In this tutorial, we will guide you through the automation of branch creation and commits to a target repository using GitHub Apps and GitHub Actions. By the end, you'll have a setup where GitHub Actions automates the process of authenticating with a GitHub App, checking for changes, creating branches, and committing changes only when necessary.

Prerequisites

Before we begin, make sure you have the following:

  • GitHub Account: Administrative access to create GitHub Apps and manage repository settings.

  • Familiarity with GitHub Actions: A basic understanding of workflows and YAML configuration.

  • Python Programming Knowledge: Familiarity with Python, especially working with REST APIs.

Overview

We will automate the following steps:

  1. Create or Update a Branch: Create a branch in the target repository, or update it if it already exists.

  2. Commit Changes: Commit changes to the branch only when new changes are detected.

  3. GitHub App for Authentication: Use a GitHub App to authenticate and perform operations securely.

  4. Automate via GitHub Actions: Set up GitHub Actions to handle the entire workflow.

By the end, you'll have a fully functional GitHub Actions workflow that:

  • Authenticates using a GitHub App.

  • Checks for changes in the source repository.

  • Creates or updates a branch in the target repository.

  • Commits changes when necessary, avoiding redundant commits.


Step 1: Setting Up the GitHub App

1.1 Create a New GitHub App

1.2 Configure the GitHub App

  • App Name: Choose a unique name for the app.

  • Homepage URL: Enter your repository or organization URL.

  • Webhook URL: You can skip this for now unless you need webhooks.

  • Permissions:

    • Set Repository Permissions for Contents to Read and Write.
  • Subscribe to Events: This is optional for this use case.

  • Installation Scope: Choose "Any account" unless you want to restrict it.

1.3 Generate App Credentials

  • After creating the app, go to the app's settings page.

  • Generate a Private Key:

    • In the "Private keys" section, click "Generate a private key". A .pem file will be downloaded. Keep this secure.
  • Get the App ID: You'll need the App ID, which is visible on the app settings page.


Step 2: Granting Access to Repositories

2.1 Install the GitHub App

  • Click "Install App" on the app settings page.

  • Select the repositories where the app will be installed, both the source repository (where the workflow will run) and the target repository (where branches and commits will be created).


Step 3: Configuring GitHub Actions Workflow

Create a new workflow file in your source repository, for example: .github/workflows/backup.yml.

name: Backup to Target Repo

on:
  push:
    branches:
      - main  # Trigger the workflow when changes are pushed to the main branch

jobs:
  backup:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v3

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.x'

      - name: Install Dependencies
        run: |
          pip install PyJWT cryptography requests

      - name: Run Backup Script
        env:
          APP_ID: ${{ secrets.APP_ID }}
          PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }}
          TARGET_REPO_OWNER: 'target-owner'  # Replace with actual target repo owner
          TARGET_REPO_NAME: 'target-repo'    # Replace with actual target repo name
        run: |
          python backup_script.py

Environment Variables

  • APP_ID: GitHub App ID.

  • PRIVATE_KEY: The private key file (.pem), stored as a GitHub secret.

  • TARGET_REPO_OWNER: The owner of the target repository.

  • TARGET_REPO_NAME: The target repository name.

Secure Secrets

Make sure to store sensitive information like APP_ID and PRIVATE_KEY in GitHub Actions Secrets:

  • Go to your repository’s Settings > Secrets and variables > Actions.

  • Add secrets such as APP_ID and PRIVATE_KEY.


Step 4: Writing the Automation Script

Create a Python script called backup_script.py in the root of your repository. This script will handle branch creation, updates, and commits.

4.1 Import Necessary Libraries

import os
import jwt
import time
import requests
from cryptography.hazmat.primitives import serialization

4.2 Authenticate as a GitHub App and Installation

def get_jwt(app_id, private_key):
    """Generate a JWT for GitHub App authentication."""
    payload = {
        'iat': int(time.time()) - 60,
        'exp': int(time.time()) + (10 * 60),
        'iss': app_id
    }
    return jwt.encode(payload, private_key, algorithm='RS256')

def get_installation_access_token(jwt_token, installation_id):
    """Get an installation access token."""
    url = f'https://api.github.com/app/installations/{installation_id}/access_tokens'
    headers = {
        'Authorization': f'Bearer {jwt_token}',
        'Accept': 'application/vnd.github+json'
    }
    response = requests.post(url, headers=headers)
    response.raise_for_status()
    return response.json()['token']

4.3 Main Logic for Branch Creation and Commit

def main():
    app_id = os.getenv('APP_ID')
    private_key = os.getenv('PRIVATE_KEY')
    owner = os.getenv('TARGET_REPO_OWNER')
    repo = os.getenv('TARGET_REPO_NAME')

    # Load private key
    private_key = private_key.replace('\\n', '\n').encode()
    private_key = serialization.load_pem_private_key(private_key, password=None)

    # Generate JWT and get access token
    jwt_token = get_jwt(app_id, private_key)
    access_token = get_installation_access_token(jwt_token, installation_id)

4.4 Creating or Updating the Branch

    branch_name = 'backup-branch'  # Define the branch name

    # Check if branch exists
    branch_url = f'https://api.github.com/repos/{owner}/{repo}/git/ref/heads/{branch_name}'
    branch_response = requests.get(branch_url, headers=headers)
    if branch_response.status_code == 200:
        sha_latest_commit = branch_response.json()['object']['sha']
        print(f"Branch '{branch_name}' exists with SHA {sha_latest_commit}")
    else:
        main_branch_url = f'https://api.github.com/repos/{owner}/{repo}/git/ref/heads/main'
        main_branch_response = requests.get(main_branch_url, headers=headers)
        main_sha = main_branch_response.json()['object']['sha']

        # Create new branch
        create_branch_url = f'https://api.github.com/repos/{owner}/{repo}/git/refs'
        data = {"ref": f"refs/heads/{branch_name}", "sha": main_sha}
        create_branch_response = requests.post(create_branch_url, headers=headers, json=data)
        create_branch_response.raise_for_status()
        sha_latest_commit = main_sha
        print(f"Branch '{branch_name}' created with SHA {sha_latest_commit}")

4.5 Committing Changes

    # Prepare new tree for commit
    content = "# Backup Repository\nThis is an automated backup."
    blob_data = {"content": content, "encoding": "utf-8"}
    blob_url = f'https://api.github.com/repos/{owner}/{repo}/git/blobs'
    blob_response = requests.post(blob_url, headers=headers, json=blob_data)
    blob_sha = blob_response.json()['sha']

    # Commit the changes
    tree_data = {"base_tree

": tree_sha, "tree": [{"path": "README.md", "mode": "100644", "type": "blob", "sha": blob_sha}]}
    new_commit_sha = commit_and_update_branch(headers, tree_data, sha_latest_commit)

Step 5: Putting It All Together

With the workflow and script in place, any changes to the source repository will trigger the GitHub Actions workflow, authenticate with the GitHub App, create or update a branch in the target repository, and commit changes as needed.


Conclusion

Automation of branch creation and commits is an essential step in streamlining repository management across multiple projects. By integrating GitHub Apps and GitHub Actions, you can efficiently manage repositories, automate tasks, and improve development workflows.

We covered how to:

  • Set up a GitHub App with appropriate permissions.

  • Configure a GitHub Actions workflow for automation.

  • Write a Python script to manage branches and commits.

Feel free to extend this workflow to include more advanced operations like handling multiple files, dealing with merge conflicts, or adding additional checks for more complex scenarios.

0
Subscribe to my newsletter

Read articles from Swami Buddha Chaitanya directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Swami Buddha Chaitanya
Swami Buddha Chaitanya