subgit

Simple git-submodule alternative with versioning and multiple remotes
git clone https://git.sinitax.com/sinitax/subgit
Log | Files | Refs | sfeed.txt

commit f62f4dbd6dabaf7e889714f60d48ea3fa8696e47
parent f16259717222a423c2725e58d318c2ee19c358a4
Author: Louis Burda <quent.burda@gmail.com>
Date:   Fri, 10 May 2024 00:25:31 +0200

Enhanced version

Store repositories in .subgit such that they are not tracked

Diffstat:
MMakefile | 4+++-
Msubgit | 27++++++++++++---------------
Msubgit-clone | 42+++++++++++++++++++++++++++++++-----------
Asubgit-init | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Asubgit-run | 31+++++++++++++++++++++++++++++++
Asubgit-write | 28++++++++++++++++++++++++++++
6 files changed, 155 insertions(+), 27 deletions(-)

diff --git a/Makefile b/Makefile @@ -2,9 +2,11 @@ DESTDIR ?= PREFIX ?= /usr/local BINDIR ?= /bin +BINS = subgit subgit-run subgit-init subgit-write subgit-clone + all: install: - install -m755 subgit subgit-clone -t "$(DESTDIR)$(PREFIX)$(BINDIR)" + install -m755 $(BINS) -t "$(DESTDIR)$(PREFIX)$(BINDIR)" .PHONY: install all diff --git a/subgit b/subgit @@ -2,20 +2,17 @@ set -e -# Find parent .subgit directory. -path=$PWD -while [ 1 ]; do - [ -d "$path/.subgit" ] && break - path=$(dirname "$path") - if [ "$path" = "/" ]; then - echo "No subgit found" 1>&2 - exit 1 - fi -done +if [ "$1" = "-C" ]; then + cd "$2" + shift + shift +fi -# Create a temporary symlink. -trap 'unlink "$path/.git"' EXIT -ln -sf ".subgit" "$path/.git" +cmd="$1" +shift -# Proxy the command. -git "$@" +if [ "$cmd" = "--" ]; then + cmd="run" +fi + +exec "subgit-$cmd" "$@" diff --git a/subgit-clone b/subgit-clone @@ -1,21 +1,41 @@ -#!/bin/sh +#!/bin/bash -set -e +set -xe -if [ $# -lt 2 ]; then - echo "Usage: subgit-clone OPT.. REMOTE PATH" +die() { + echo "$@" 2>&1 exit 1 +} + +[ $# -lt 2 ] && die "Usage: subgit-clone OPT.. REMOTE PATH" + +repo=$(realpath "$(git rev-parse --show-toplevel)") +subrepo=$(realpath "${@: -1}") +subrepo=${subrepo#$repo/} + +[ ! -z "$subrepo" -a "$subrepo" != "/" ] + +if [ -e "$repo/.subgitrc" ]; then + source "$repo/.subgitrc" +else + declare -A subgit subgitinfo fi -path=${@: -1} +[ -e "$repo/$subrepo" ] && die "Subrepo $subrepo already exists" +#trap '[ ! -z "$repo" -a ! -z "$subrepo" ] && rm -rf "$repo/$subrepo"' exit +trap 'echo "$repo/$subrepo"' exit git clone "$@" +[ ! -e "$repo/$subrepo" ] && die "Subrepo $subrepo missing" + +subgit[$subrepo]=1 +subgitinfo[$subrepo/remote]=$(git -C "$repo/$subrepo" remote get-url origin) +subgitinfo[$subrepo/branch]=$(git -C "$repo/$subrepo" branch --show-current) +subgitinfo[$subrepo/commit]=$(git -C "$repo/$subrepo" rev-parse --verify HEAD) -echo "remote=$(git -C "$path" remote get-url origin)" > "$path/.subgitrc" -echo "branch=$(git -C "$path" branch --show-current)" >> "$path/.subgitrc" -echo "commit=$(git -C "$path" rev-parse --verify HEAD)" >> "$path/.subgitrc" +source subgit-write -mv "$path/.git" "$path/.subgit" +mkdir -p $(dirname "$repo/.subgit/$subrepo") +mv "$repo/$subrepo/.git" "$repo/.subgit/$subrepo" -[ ! -f "$path/.gitignore" ] && echo "/.gitignore" >> "$path/.gitignore" -echo "/.subgit" >> "$path/.gitignore" +trap - exit diff --git a/subgit-init b/subgit-init @@ -0,0 +1,50 @@ +#!/bin/bash + +set -xe + +die() { + echo "$@" 2>&1 + exit 1 +} + +recursive=0 +update=0 +while [ $# -ge 1 ]; do + if [ "$1" = "-r" ]; then + recursive=1 + shift + elif [ "$1" = "-u" ]; then + update=1 + shift + else + die "Unknown opt: $1" + fi +done + +[ $# -ne 0 ] && die "Usage: subgit-init" + +repo=$(realpath "$(git rev-parse --show-toplevel)") +source "$repo/.subgitrc" + +for subrepo in ${!subgit[@]}; do + path="$repo/.subgit/$subrepo" + if [ ! -d "$path" ]; then + mkdir -p "$(dirname "$path")" + git clone --bare --single-branch -b "${subgitinfo[$subrepo/branch]}" \ + "${subgitinfo[$subrepo/remote]}" "$path" + git -C "$path" config --local --bool core.bare false + fi + [ -d "$repo/$subrepo" ] || mkdir -p "$repo/$subrepo" + subgit -C "$repo/$subrepo" -- git checkout "${subgitinfo[$subrepo/branch]}" + if [ $update -ne 0 ]; then + subgitinfo[$subrepo/remote]=$(git -C "$path" remote get-url origin) + subgitinfo[$subrepo/branch]=$(git -C "$path" branch --show-current) + subgitinfo[$subrepo/commit]=$(git -C "$path" rev-parse --verify HEAD) + fi + subgit -C "$repo/$subrepo" -- git reset "${subgitinfo[$subrepo/commit]}" + if [ $recursive -ne 0 -a -e "$repo/$subrepo/.subgitrc" ]; then + subgit -C "$repo/$subrepo" -- subgit-init + fi +done + +source subgit-write diff --git a/subgit-run b/subgit-run @@ -0,0 +1,31 @@ +#!/bin/sh + +set -e + +die() { + echo "$@" 1>&2 + exit 1 +} + +if [ "$1" = "-C" ]; then + cd "$2" + shift + shift +fi + +repo=$(realpath "$(git rev-parse --show-toplevel)") +[ "${PWD#$repo/}" != "$PWD" ] || die "Bad path $repo > $PWD" + +path=$PWD +while [ 1 ]; do + relpath=${path#$repo/} + [ -d "$repo/.subgit/$relpath" ] && break + path=$(dirname "$path") + echo "$path" + [ "$path" = "$repo" ] && die "No subgit found" +done + +trap 'unlink "$path/.git"' EXIT +ln -sf "$repo/.subgit/$relpath" "$path/.git" + +"$@" diff --git a/subgit-write b/subgit-write @@ -0,0 +1,28 @@ +#!/bin/bash + +subgit-write-inner() { + set -e + + repo=$(realpath "$(git rev-parse --show-toplevel)") + + echo -e "#!/bin/bash\n" > "$repo/.subgitrc" + echo -e "declare -A subgit subgitinfo\n" >> "$repo/.subgitrc" + + for subrepo in ${!subgit[@]}; do + echo "subgit[$subrepo]=1" + echo "subgitinfo[$subrepo/remote]='${subgitinfo[$subrepo/remote]}'" + echo "subgitinfo[$subrepo/branch]='${subgitinfo[$subrepo/branch]}'" + echo "subgitinfo[$subrepo/commit]='${subgitinfo[$subrepo/commit]}'" + echo "" + done >> "$repo/.subgitrc" + + if [ ! -e "$repo/.gitignore" ]; then + echo "/.gitignore" > "$repo/.gitignore" + fi + cat "$repo/.gitignore" | grep -q "^/.subgit$" \ + || echo "/.subgit" >> "$repo/.gitignore" +} + +( subgit-write-inner "$@"; ) + +unset subgit-write-inner