Skip to content

Instantly share code, notes, and snippets.

@hi-ogawa
Last active March 30, 2022 23:43
Show Gist options
  • Save hi-ogawa/54467e74d05aba8b6488f06f770f8c02 to your computer and use it in GitHub Desktop.
Save hi-ogawa/54467e74d05aba8b6488f06f770f8c02 to your computer and use it in GitHub Desktop.
init-monorepo.sh

init-monorepo

TODO

  • we can keep the history under the subdirectory via --to-subdirectory-filter
git filter-repo --source "$tmp_dir/$package" --target "$tmp_dir/$package" --to-subdirectory-filter "packages/$package" --commit-callback "commit.message += b'\n\nPreMonorepoCommit: https://github.com/$repository/commit/' + commit.original_id + b'\n'"

summary

  • git merge --allow-unrelated-histories to merge multiple repositories without common parent commit
  • git filter-repo to inject links to the original commit on Github
  • git tag to rename existing tags

example

shellcheck -x init-monorepo.sh

# initialize monorepo locally
bash init-monorepo.sh |& tee -a init-monorepo.log

# push to remote if the result looks okay
cd demo-init-monorepo
git remote add origin [email protected]:hi-ogawa/demo-init-monorepo.git
git push origin init-monorepo # --force
git push origin --tags # --force

misc

  • reset tags (when testing repeatedly in the same repository)
git fetch
git tag -l | xargs -n 1 git push --delete origin
git tag -l | xargs -n 1 git tag -d
  • port old diff to monorepo (e.g. to migrate old PRs)
# Create diff in an old repository (e.g. diff from master to HEAD in "some-module" repository)
git diff master > some-module.diff

# Convert diff
python prefix-diff.py packages/some-module < some-module.diff > some-module.monorepo.diff

# Apply diff in monorepo
git apply some-module.monorepo.diff

references

#!/bin/bash
set -eoux pipefail
monorepo_root="$PWD/demo-init-monorepo"
monorepo_branch=init-monorepo
repositories=(
hi-ogawa/demo-init-monorepo--node-csv-stringify
hi-ogawa/demo-init-monorepo--node-csv-generate
hi-ogawa/demo-init-monorepo--node-csv-parse
)
packages_dir=packages
tmp_dir="$PWD/tmp"
#!/bin/bash
set -eoux pipefail
source ./init-monorepo-config.sh
#
# Validate
#
if ! type git-filter-repo >/dev/null 2>&1; then
echo "error: git-filter-repo not found"
exit 1
fi
if [ -e "$monorepo_root" ]; then
echo "error: not empty '$monorepo_root'"
exit 1
fi
#
# Setup
#
rm -rf "$tmp_dir"
mkdir -p "$tmp_dir" "$monorepo_root/$packages_dir"
cd "$monorepo_root"
git init
git checkout -b "$monorepo_branch"
#
# Merge each repository
#
for repository in "${repositories[@]}"; do
package="${repository##*/}"
mkdir -p "$tmp_dir/$package" "$packages_dir/$package"
git clone "[email protected]:$repository.git" "$tmp_dir/$package"
#
# Manipulate history within a temporary repository
#
# inject old commit links (convenient to find relevant PRs via git blame)
git filter-repo --source "$tmp_dir/$package" --target "$tmp_dir/$package" --commit-callback "commit.message += b'\n\nPreMonorepoCommit: https://github.com/$repository/commit/' + commit.original_id + b'\n'"
# rename tags (prepend a package name)
cd "$tmp_dir/$package"
for tag in $(git tag -l); do
git tag "$package-$tag" "$tag"
git tag -d "$tag"
done
cd -
#
# Add to monorepo as remote branch and merge
#
git remote add -f "$package" "$tmp_dir/$package"
default_branch=$(git -C "$tmp_dir/$package" rev-parse --abbrev-ref HEAD)
git merge --allow-unrelated-histories "$package/$default_branch" -m "chore(monorepo): merge '$package'"
#
# Move files and commit
#
find . -maxdepth 1 -not \( -name '.' -o -name '.git' -o -name "$packages_dir" \) -exec mv '{}' "$packages_dir/$package" \;
git add .
git commit -m "chore(monorepo): create '$packages_dir/$package'"
done
# cf. https://stackoverflow.com/a/2530012
import re
import argparse
import sys
def main(prefix: str) -> None:
for line in sys.stdin:
#
# diff --git a/src/app/hello.ts b/src/app/foo.ts
# ⇓
# diff --git a/PREFIX/src/app/hello.ts b/PREFIX/src/app/foo.ts
#
line = re.sub(
r"^diff --git a/(.*) b/(.*)$",
f"diff --git a/{prefix}/\\1 b/{prefix}/\\2",
line,
)
#
# --- a/src/app/hello.ts
# ⇓
# --- a/PREFIX/src/app/hello.ts
#
line = re.sub(r"^--- a/(.*)$", f"--- a/{prefix}/\\1", line)
#
# +++ b/src/app/foo.ts
#
# +++ b/PREFIX/src/app/foo.ts
#
line = re.sub(r"^\+\+\+ b/(.*)$", f"+++ b/{prefix}/\\1", line)
print(line, end="")
def main_cli() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("prefix")
main(**parser.parse_args().__dict__)
if __name__ == "__main__":
main_cli()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment