Skip to content

Commit

Permalink
Generalize publish code action so it can publish to different orgs (#365
Browse files Browse the repository at this point in the history
)

## Summary
Assuming the source repo is `org1/repo1`, the targets can now be of
these forms:
* `dir1/dir2` -> publishes to `org1/dir2`
* `dir2:repo2` -> publishes to `org1/repo2`
* `dir3:org2/repo3` -> publishes to `org2/repo3`

## How was it tested?
Ran locally (without actually publishing anything).

Also tested publishing to `jetify-examples/test-repo`:

https://github.com/jetify-com/opensource/actions/runs/10375981931/job/28726924974

---------

Co-authored-by: Rodrigo Ipince <[email protected]>
  • Loading branch information
ipince and ipince authored Aug 13, 2024
1 parent 59dd0c8 commit 34d6c07
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 25 deletions.
2 changes: 1 addition & 1 deletion action-publish-code/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: "Publish Code"
description: |
A Github action that makes it easy to publish code from one git repository to another
while preserving history. The primary use case is to mirror code from a directory
in a monorepo, and publish it as a standalone repository."
in a monorepo, and publish it as a standalone repository.
author: "jetify"
inputs:
origin:
Expand Down
66 changes: 42 additions & 24 deletions action-publish-code/publish-code.sh
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
#!/bin/bash

# Publishes a subdirectory in a given git repository as a standalone repository of it's own.
# Publishes a subdirectory in a given git repository as a standalone repository of its own.

set -euo pipefail
set -o errexit
set -o nounset

if [ "$#" = 0 ]; then
echo "usage: $0 <org>/<repo> <dir1>[:<repo1>] [<dirN>[:<repoN>]]..." 1>&2
echo "usage: $0 <org>/<repo> <dir1>[:[<owner1>/]<repo1>] [<dirN>[:[<ownerN/]<repoN>]]..." 1>&2
exit 1
fi

if [[ "$1" != *"/"* ]]; then
echo "error: first argument must be in the form <owner>/<repo>" 1>&2
echo "usage: $0 <org>/<repo> <dir1>[:<repo1>] [<dirN>[:<repoN>]]..." 1>&2
echo "error: first argument must be in the form <owner>/<repo>, where <repo> is [<org>/]<repo-name>" 1>&2
echo "usage: $0 <org>/<repo> <dir1>[:[<owner1>/]<repo1>] [<dirN>[:[<ownerN/]<repoN>]]..." 1>&2
echo "example: $0 org1/repo1 dir1 dir2:repo2 dir3:org2/repo3" 1>&2
exit 1
fi

Expand All @@ -22,6 +23,30 @@ org="${1%/*}"
origin_repo="${1##*/}"
shift

function parse_target() {
# Valid target examples: dir1/dir2, dir2:repo2, dir3:org2/repo3

if [[ "$1" =~ ^([^:]+)(:([^/]+/)?([^/]+))?$ ]]; then
dir="${BASH_REMATCH[1]}"
target_org="${BASH_REMATCH[3]}"
target_repo="${BASH_REMATCH[4]}"

if [ -z "$target_repo" ]; then
target_repo=$(basename "$dir")
fi

target_org="${target_org%/}" # remove trailing slash if present
if [ -z "$target_org" ]; then
target_org="$org"
fi

echo "$dir $target_org $target_repo"
else
echo "error: invalid target argument: $1"
exit 1
fi
}

# Create a temporary directory into which to clone the repos:
TMPDIR=$(mktemp -d)
function cleanup() {
Expand All @@ -38,16 +63,12 @@ git clone -b main --single-branch "[email protected]:${org}/${origin_repo}.git" "${
echo -e "\n"
cd "${TMPDIR}/${origin_repo}"
for arg in "$@"; do
dir="${arg%:*}"
repo="${dir##*/}"
if [[ "$arg" != *":"* ]]; then
repo="${arg##*:}"
fi

echo "Validating $arg"
read -r dir target_org target_repo <<< "$(parse_target "$arg")"

# Just to be extra safe, make sure we're not trying to publish to origin repo itself
if [ "${repo}" = "${origin_repo}" ]; then
echo "Error: Cannot publish to the source repo: '${origin_repo}'" 1>&2
if [ "${target_org}/${target_repo}" = "${org}/${origin_repo}" ]; then
echo "Error: Cannot publish to the source repo: '${org}/${origin_repo}'" 1>&2
exit 1
fi

Expand All @@ -59,27 +80,24 @@ for arg in "$@"; do
done

for arg in "$@"; do
dir="${arg%:*}"
repo="${dir##*/}"
if [[ "$arg" = *":"* ]]; then
repo="${arg##*:}"
fi
echo "Publishing dir '${dir}' to repo '${repo}' ..."
read -r dir target_org target_repo <<< "$(parse_target "$arg")"

echo "Publishing dir '${dir}' to repo '${target_org}/${target_repo}' ..."

set -o xtrace # Print commands as they are executed

# Remove everything we don't want from the source repo:

# Only keep the tags belonging to the repo we care about
git tag -d $(git tag -l | grep -v "${repo}/*")
git tag -d $(git tag -l | grep -v "${target_repo}/*")
# TODO: Rewrite using https://github.com/newren/git-filter-repo since filter-branch is no longer
# recommended by git.
FILTER_BRANCH_SQUELCH_WARNING=1 git filter-branch \
--tag-name-filter "grep '^${repo}/' | cut -f 2- -d '/'" \
--tag-name-filter "grep '^${target_repo}/' | cut -f 2- -d '/'" \
--subdirectory-filter "${dir}" --prune-empty -- --all

git clone "[email protected]:${org}/${repo}.git" "${TMPDIR}/${repo}"
pushd "${TMPDIR}/${repo}"
git clone "[email protected]:${target_org}/${target_repo}.git" "${TMPDIR}/${target_repo}"
pushd "${TMPDIR}/${target_repo}"

# Here is the trick. Connect your source repository as a remote using a local reference.
git remote add "${origin_repo}" "../${origin_repo}/"
Expand All @@ -104,9 +122,9 @@ for arg in "$@"; do
# Undo the filtering so we can re-use the source repo for another rewrite.
git for-each-ref --format="update %(refname:lstrip=2) %(objectname)" refs/original/ | git update-ref --stdin
git for-each-ref --format="delete %(refname) %(objectname)" refs/original/ | git update-ref --stdin
git fetch --tags --force # Restore all tags
git fetch --tags --force # Restore all tags
git reset --hard HEAD

echo "[DONE] Published dir '${dir}' to repo '${repo}' ..."
echo "[DONE] Published dir '${dir}' to repo '${target_org}/${target_repo}' ..."
echo -e "\n"
done
Empty file added test/hello.txt
Empty file.

0 comments on commit 34d6c07

Please sign in to comment.