Skip to content

GitHub CLI extension to publish content from source repository into multiple target repositories.

License

Notifications You must be signed in to change notification settings

andyfeller/gh-publicize

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

28 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

gh-publicize

A gh extension to publish content from source repository into multiple target repositories.

Built on top of gruntwork-io/git-xargs, gh-publicize is more opinionated on how to publish content from a centralized location with specific default behaviors:

  1. Use of a centralized source repository to publish content from, whether used as a template repository or not
  2. Leverage scripts from source repository, smoothing over need for fully qualified scripts
  3. Provide helper library for shell scripts to simplify managing content
  4. Avoid making changes unless explicitly indicated (-r,--run flag)
  5. Avoid archived repositories unless explicitly indicated (-a,--include-archived-repos flag)

High-level overview of gruntwork-io/git-xargs tool for creating pull requests for repositories containing new or updated content

Quickstart

  1. Download and install git-xargs
  2. gh extension install andyfeller/gh-publicize
  3. gh publicize --repo=<owner>/<repo> <cmd>
  4. gh publicize --repo=<owner>/<repo> --run <cmd>
  5. Profit! πŸ’° πŸ’Έ πŸ€‘ πŸ’Έ πŸ’°

Usage

Note gh-publicize requires the use of coarse-grained v1 PAT token with repo scope.

Publish content in target repositories from source repository.

USAGE
  gh publicize [flags] --repo=<owner>/<repo> [...] <cmd>
  gh publicize [flags] --repos=<file> <cmd>
  gh publicize [flags] --org=<org> <cmd>

FLAGS
  -a, --include-archived-repos         Whether to include archived repositories
  -c, --cache-dir=<cache-dir>          Name of directory containing preserved data to reuse
  -C, --commit-message=<msg>           Commit message to use when pushing changes  
  -d, --debug                          Enable debug logging
  -h, --help                           Displays help usage
  -m, --repo=<owner>/<repo>            Target a specific repo, can be passed multiple times to target several repos
  -n, --repos=<file>                   Target multiple repos, file contains <owner>/<repo> entry per line
  -o, --org=<org>                      Target all repos in GitHub organization
  -P, --source-repo-path=<dir>         Directory within source repository to add to path; default 'bin'
  -p, --preserve                       Preserve temporary directory containing data
  -R, --reviewers=<csv-usernames>      Comma-separated list of usernames to review pull request
  -r, --run                            Apply changes; defaults to dryrun
  -s, --source-repo=<owner>/<repo>     Name of source repository to clone
  -t, --source-repo-revision=<rev>     Revision of source repository to checkout; default 'main'

ENVIRONMENT VARIABLES
  PUBLICIZE_HOME                       Path to gh-publicize
  PUBLICIZE_LIB                        Path to gh-publicize/lib
  PUBLICIZE_SOURCE_DIR                 Path to source repository cloned for use

Example of source repository and shell script to use with gh-publicize

When creating a new GitHub repository, there are several common needs that may go overlooked:

  1. Adding a code owners file
  2. Adding a code of conduct such as Contributor Covenant
  3. Adding a license from choosealicense.com
  4. Adding a .gitignore based upon gitignore.io

One option would be using a template repository, except it relies upon people to use it and only supports static content. This is where gh-publicize can offer a reactive approach using a source repository and a simple shell script.

  • Source Repository

    .
    β”œβ”€β”€ CODEOWNERS
    β”œβ”€β”€ CODE_OF_CONDUCT.md
    β”œβ”€β”€ LICENSE.txt
    β”œβ”€β”€ README.md
    └── bin
        └── 00-base.sh
  • Shell Script bin/00-base.sh

    #! /usr/bin/env bash
    
    # Invoke command(s) and/or script(s) doing work, assuming environment including PWD will be preserved for invoked scripts
    source $PUBLICIZE_LIB/helpers.sh
    
    # Ensure base files every repository needs are there if missing; do not override
    copyMissingFile $PUBLICIZE_SOURCE_DIR ".gitignore"
    copyMissingFile $PUBLICIZE_SOURCE_DIR "CODEOWNERS"
    copyMissingFile $PUBLICIZE_SOURCE_DIR "CODE_OF_CONDUCT.md"
    copyMissingFile $PUBLICIZE_SOURCE_DIR "LICENSE.txt"
    
    # Update repository labels as appropriate
    updateLabels

The source repository above - which may be a template repository or not - contains static content and scripts I want within all of my repositories. The bin/00-base.sh script leverages helper functions to copy missing files and update labels when invoked by gh-publicize.

In the following commands, gh-publicize will execute 00-base.sh from andyfeller/template in dryrun mode then run mode, creating pull requests for multiple repositories:

  1. Create repositories for testing:

    $ gh repo create andyfeller/test-1 --private --add-readme
    $ gh repo create andyfeller/test-2 --private --add-readme
  2. Run gh-publicize in dryrun mode:

    $ gh publicize --repo=andyfeller/test-1 --repo=andyfeller/test-2 --source-repo=andyfeller/template 00-base.sh
    gh publicize output
    Created temporary directory for caching data:  /var/folders/xb/svzskj1x77x3qsmwx1d84nqc0000gn/T/gh-publicizeXXX.BMdK3T1L
    Cloning andyfeller/template, checking out main
    Cloning into '/var/folders/xb/svzskj1x77x3qsmwx1d84nqc0000gn/T/gh-publicizeXXX.BMdK3T1L/_source-repo'...
    remote: Enumerating objects: 22, done.
    remote: Counting objects: 100% (22/22), done.
    remote: Compressing objects: 100% (15/15), done.
    remote: Total 22 (delta 5), reused 16 (delta 2), pack-reused 0
    Receiving objects: 100% (22/22), 6.04 KiB | 3.02 MiB/s, done.
    Resolving deltas: 100% (5/5), done.
    Already on 'main'
    Your branch is up to date with 'origin/main'.
    Executing git-xargs command
    [git-xargs] INFO[2023-07-30T17:44:59-04:00] git-xargs running...
    [git-xargs] INFO[2023-07-30T17:44:59-04:00] Dry run setting enabled. No local branches will be pushed and no PRs will be opened in Github
    Processing repos [2/2] β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ 100% | 2s
    
    Git-xargs run summary @ 2023-07-30 21:45:04.025872 +0000 UTC
    
    β€’ Runtime in seconds: 5
    β€’ Command supplied: [00-base.sh]
    β€’ Repo selection method: repo-flag
    
    
    All repos that were targeted for processing after filtering missing / malformed repos
    
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    | Repo name | Repo URL                             |
    | test-1    | https://github.com/andyfeller/test-1 |
    | ------------------------------------------------ |
    | test-2    | https://github.com/andyfeller/test-2 |
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    
    Repos that were successfully cloned to the local filesystem
    
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    | Repo name | Repo URL                             |
    | test-1    | https://github.com/andyfeller/test-1 |
    | ------------------------------------------------ |
    | test-2    | https://github.com/andyfeller/test-2 |
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    
    Repos that showed file changes to their working directory following command execution
    
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    | Repo name | Repo URL                             |
    | test-1    | https://github.com/andyfeller/test-1 |
    | ------------------------------------------------ |
    | test-2    | https://github.com/andyfeller/test-2 |
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    
    Repos whose local branch was not pushed because the --dry-run flag was set
    
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    | Repo name | Repo URL                             |
    | test-1    | https://github.com/andyfeller/test-1 |
    | ------------------------------------------------ |
    | test-2    | https://github.com/andyfeller/test-2 |
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    
    Repos whose specified branches did not exist on the remote, and so were first created locally
    
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    | Repo name | Repo URL                             |
    | test-1    | https://github.com/andyfeller/test-1 |
    | ------------------------------------------------ |
    | test-2    | https://github.com/andyfeller/test-2 |
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  3. Run gh-publicize in run mode:

    $ gh publicize --run --repo=andyfeller/test-1 --repo=andyfeller/test-2 --source-repo=andyfeller/template 00-base.sh
    gh publicize --run output
    Created temporary directory for caching data:  /var/folders/xb/svzskj1x77x3qsmwx1d84nqc0000gn/T/gh-publicizeXXX.PxYKGc7A
    Cloning andyfeller/template, checking out main
    Cloning into '/var/folders/xb/svzskj1x77x3qsmwx1d84nqc0000gn/T/gh-publicizeXXX.PxYKGc7A/_source-repo'...
    remote: Enumerating objects: 22, done.
    remote: Counting objects: 100% (22/22), done.
    remote: Compressing objects: 100% (15/15), done.
    remote: Total 22 (delta 5), reused 16 (delta 2), pack-reused 0
    Receiving objects: 100% (22/22), 6.04 KiB | 3.02 MiB/s, done.
    Resolving deltas: 100% (5/5), done.
    Already on 'main'
    Your branch is up to date with 'origin/main'.
    Executing git-xargs command
    [git-xargs] INFO[2023-07-30T17:45:53-04:00] git-xargs running...
    Processing repos [2/2] β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ 100% | 4s
    
    Git-xargs run summary @ 2023-07-30 21:45:57.525786 +0000 UTC
    
    β€’ Runtime in seconds: 4
    β€’ Command supplied: [00-base.sh]
    β€’ Repo selection method: repo-flag
    
    
    All repos that were targeted for processing after filtering missing / malformed repos
    
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    | Repo name | Repo URL                             |
    | test-1    | https://github.com/andyfeller/test-1 |
    | ------------------------------------------------ |
    | test-2    | https://github.com/andyfeller/test-2 |
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    
    Repos that were successfully cloned to the local filesystem
    
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    | Repo name | Repo URL                             |
    | test-1    | https://github.com/andyfeller/test-1 |
    | ------------------------------------------------ |
    | test-2    | https://github.com/andyfeller/test-2 |
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    
    Repos that showed file changes to their working directory following command execution
    
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    | Repo name | Repo URL                             |
    | test-1    | https://github.com/andyfeller/test-1 |
    | ------------------------------------------------ |
    | test-2    | https://github.com/andyfeller/test-2 |
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    
    Repos whose specified branches did not exist on the remote, and so were first created locally
    
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    | Repo name | Repo URL                             |
    | test-1    | https://github.com/andyfeller/test-1 |
    | ------------------------------------------------ |
    | test-2    | https://github.com/andyfeller/test-2 |
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    
    Pull requests opened
    
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    | Repo name | Pull request URL                            |
    | test-1    | https://github.com/andyfeller/test-1/pull/1 |
    | ------------------------------------------------------- |
    | test-2    | https://github.com/andyfeller/test-2/pull/1 |
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Setup

Like any other gh CLI extension, gh-publicize is trivial to install or upgrade and works on most operating systems:

✨ Thanks

This effort couldn't have happened without the support from many people, so thank you to the following who helped throughout:

@karlwithak1 @bval @apdarr @evgenyrahman @katiem0 @gr2m

About

GitHub CLI extension to publish content from source repository into multiple target repositories.

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published

Languages