mirror of
https://github.com/vxcontrol/multigit.git
synced 2026-07-01 05:52:21 -04:00
faster mgit which
This commit is contained in:
@@ -120,8 +120,8 @@ tracked_files() {
|
||||
|
||||
which_tracks() {
|
||||
[ "$1" ] || die "Filename expected."
|
||||
list_tracked | while IFS=" " read repo file; do
|
||||
[ "$file" = "$1" ] && echo "$repo"
|
||||
list_cloned | while IFS=" " read repo; do
|
||||
[ "$(GIT_DIR=".mgit/$repo/.git" git ls-files "$1")" = "$1" ] && echo "$repo"
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,724 @@
|
||||
#!/bin/bash
|
||||
shopt -s nullglob
|
||||
IFS=$'\n\b'
|
||||
|
||||
# die hard, see https://github.com/capr/die
|
||||
say() { echo "$@" >&2; }
|
||||
die() { echo -n "ABORT: " >&2; echo "$@" >&2; exit 1; }
|
||||
debug() { [ "$DEBUG" ] && echo "$@" >&2; }
|
||||
run() { debug -n "EXEC: $@ "; "$@"; local ret=$?; debug "[$ret]"; return $ret; }
|
||||
must() { debug -n "MUST: $@ "; "$@"; local ret=$?; debug "[$ret]"; [ $ret = 0 ] || die "$@ [$ret]"; }
|
||||
dry() { if [ "$DRY" ]; then [ "$VERBOSE" ] && say "$@"; else "$@"; fi; }
|
||||
|
||||
usage() {
|
||||
say
|
||||
say " multigit 4.4b - git wrapper for working with overlaid repos."
|
||||
say " Cosmin Apreutesei | public domain | https://github.com/capr/multigit"
|
||||
say
|
||||
say " USAGE: mgit [OPTIONS...] COMMAND ..."
|
||||
say
|
||||
say " ls list cloned repos"
|
||||
say " ls-all list all known repos"
|
||||
say " ls-uncloned list all known but not cloned repos"
|
||||
say
|
||||
say " ls-modified|st[atus] list modified files across all repos"
|
||||
say " ls-unpushed list repos that are ahead of origin"
|
||||
say " ls-untracked list files untracked by any repo"
|
||||
say " ls-double-tracked list files tracked by multiple repos"
|
||||
say " ls-tracked list files and which repos are tracking them"
|
||||
say " which FILENAME list which repo(s) are tracking a file"
|
||||
say
|
||||
say " init REPO create a local repo"
|
||||
say " clone [REMOTE/]REPO|URL[=VERSION] ... clone one ore more repos"
|
||||
say " clone-all clone all known uncloned repos"
|
||||
say " clone-release REL|RELFILE clone/checkout all repos from a release (file)"
|
||||
say " remove REPO ... remove repos from disk (!)"
|
||||
say " convert [NAME] convert current git repo to mgit"
|
||||
say
|
||||
say " baseurl [REMOTE [URL|-]] get/set/delete the baseurl of a remote"
|
||||
say " origin [REPO [REMOTE|URL|-]] get/set/delete the known origin of a repo"
|
||||
say
|
||||
say " [-] REPO1,... start a shell for using git on a repo"
|
||||
say " [-] REPO1,...|--all COMMAND ... execute any git command on a repo"
|
||||
say " [-] REPO1,...|--all exec ... execute a shell command in a repo context"
|
||||
say " [-] REPO1,...|--all ver[sion] [tag] show repo version or tag (as enum or list)"
|
||||
say " [-] REPO1,...|--all make-symlinks make symbolic links in .mgit/REPO"
|
||||
say " [-] REPO1,...|--all make-hardlinks make hard links in .mgit/REPO"
|
||||
say
|
||||
say " release [REL] show a release or list releases"
|
||||
say " release REL update [tag] create/update a release based on HEADs"
|
||||
say " release REL clone clone/checkout all repos from a release"
|
||||
say " release REL remove remove a release file"
|
||||
say
|
||||
say " bash drop to bash (even on Windows)"
|
||||
say " [help|--help] show this screen"
|
||||
say
|
||||
say " OPTIONS:"
|
||||
say
|
||||
say " -v verbose"
|
||||
say " --debug print commands"
|
||||
say " --dry don't actually remove stuff"
|
||||
say " --yes choose yes when asked to remove stuff"
|
||||
say " -SS reuse SSH connections (Linux only)"
|
||||
say " -P N number of parallel processes for clone (1)"
|
||||
say
|
||||
# append any plugin help files
|
||||
for f in .mgit/*.help; do
|
||||
cat "$f"
|
||||
done
|
||||
exit
|
||||
}
|
||||
|
||||
check_root() { [ -d .mgit ] || die "'.mgit' dir not found."; }
|
||||
|
||||
list_known() {
|
||||
check_root
|
||||
(cd .mgit && \
|
||||
for f in *.origin; do
|
||||
echo "${f%.origin}"
|
||||
done)
|
||||
}
|
||||
|
||||
list_cloned() {
|
||||
check_root
|
||||
(cd .mgit && \
|
||||
for f in *; do
|
||||
[ -d "$f/.git" ] && echo "$f"
|
||||
done)
|
||||
}
|
||||
|
||||
list_uncloned() {
|
||||
check_root
|
||||
(cd .mgit && \
|
||||
for f in *.origin; do
|
||||
f="${f%.origin}"
|
||||
[ ! -d "$f/.git" ] && echo $f
|
||||
done)
|
||||
}
|
||||
|
||||
list_modified() {
|
||||
check_root
|
||||
"$0" --all "$@" status -s
|
||||
}
|
||||
|
||||
list_unpushed() {
|
||||
check_root
|
||||
for repo in `list_cloned`; do
|
||||
[ "$(GIT_DIR=".mgit/$repo/.git" \
|
||||
git rev-list HEAD...origin/master --count 2>/dev/null)" \
|
||||
!= "0" ] && echo "$repo"
|
||||
done
|
||||
}
|
||||
|
||||
tracked_files() {
|
||||
check_root
|
||||
([ -d .git ] && git ls-files
|
||||
for repo in `list_cloned`; do
|
||||
GIT_DIR=".mgit/$repo/.git" git ls-files
|
||||
done) | sort | uniq $1
|
||||
}
|
||||
|
||||
which_tracks() {
|
||||
[ "$1" ] || die "Filename expected."
|
||||
list_cloned | while IFS=" " read repo; do
|
||||
# xargs -i -P "$MAXPROC" --
|
||||
[ "$(GIT_DIR=".mgit/$repo/.git" git ls-files "$1")" = "$1" ] && echo "$repo"
|
||||
# [ "$file" = "$1" ] && echo "$repo"
|
||||
done
|
||||
}
|
||||
|
||||
existing_files() {
|
||||
find * -type f | grep -v "^$1$" | sort
|
||||
}
|
||||
|
||||
list_tracked() {
|
||||
check_root
|
||||
local MGIT_DIR="$PWD"
|
||||
for repo in `list_cloned`; do
|
||||
(cd "$PWD0"; GIT_DIR="$MGIT_DIR/.mgit/$repo/.git" git ls-files) | while read f; do
|
||||
printf "%-16s %s\n" "$repo" "$f"
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
list_sub() { # list_sub A B removes from list A the lines found in list B.
|
||||
# NOTE: using a temp file because bash on Windows can't do <(tracked_files)
|
||||
"$2" | sort > $$.tmp
|
||||
if [ "$OSTYPE" != "msys" ]; then
|
||||
# NOTE: not using grep because it's buggy on OSX (doesn't match all).
|
||||
"$1" $$.tmp | comm -23 - $$.tmp
|
||||
else
|
||||
# NOTE: using grep because Git for Windows doesn't have comm,
|
||||
# and because if MSYS is in PATH, comm from MSYS can't be used with
|
||||
# Git's bash (it crashes).
|
||||
"$1" $$.tmp | grep -v -F -x -f $$.tmp
|
||||
fi
|
||||
rm $$.tmp
|
||||
}
|
||||
|
||||
list_untracked() {
|
||||
check_root
|
||||
list_sub existing_files tracked_files
|
||||
}
|
||||
|
||||
list_double_tracked() {
|
||||
check_root
|
||||
# NOTE: using a temp file because bash on Windows can't do <(tracked_files -d)
|
||||
tracked_files -d > $$.tmp
|
||||
[ "$(cat $$.tmp)" ] && \
|
||||
PWD0="$PWD" list_tracked | grep -F -w -f $$.tmp
|
||||
rm $$.tmp
|
||||
}
|
||||
|
||||
clone_all() {
|
||||
for repo in `list_uncloned`; do
|
||||
"$0" clone "$repo"
|
||||
done
|
||||
}
|
||||
|
||||
check_repo_name() {
|
||||
[ "$1" ] || die "Repo name expected."
|
||||
local name="$1"
|
||||
name="${name//[^\.\-_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]/}"
|
||||
[ "$name" = "$1" ] || die "Invalid name: '$1'. Only letters, numbers, '.', '-' and '_' allowed."
|
||||
name="${name//^[\.\-]/}"
|
||||
[ "$name" = "$1" ] || die "Invalid name: '$1'. Must not start with '.' or '-'."
|
||||
}
|
||||
|
||||
init() {
|
||||
check_repo_name "$1"
|
||||
local name="$1"
|
||||
|
||||
[ -d ".mgit/$name" ] && die "Already cloned: '$name'."
|
||||
|
||||
must mkdir -p ".mgit/$name"
|
||||
export GIT_DIR=".mgit/$name/.git"
|
||||
must git init $GITOPT $MULTIGIT_INIT_OPTS >&2
|
||||
must git config core.worktree ../../..
|
||||
|
||||
must git config core.excludesfile ".mgit/$name.exclude"
|
||||
must make_exclude_file "$name"
|
||||
}
|
||||
|
||||
make_exclude_file() {
|
||||
[ "$no_exclude_file" ] && return
|
||||
[ -f ".mgit/$1.exclude" ] && return
|
||||
# make a default "exclude-all" exclude file
|
||||
echo '*' > ".mgit/$1.exclude"
|
||||
}
|
||||
|
||||
clone_one() {
|
||||
local arg="$1"
|
||||
local name
|
||||
local origin
|
||||
local rorigin
|
||||
local url
|
||||
local ver
|
||||
|
||||
arg="${arg//[[:blank:]]/}" # spaces not allowed
|
||||
[ "$arg" = "$1" ] || die "$name is an invalid name"
|
||||
|
||||
# extract `=version` if any
|
||||
ver="${arg##*=}"
|
||||
[ "$ver" = "$arg" ] && ver=""
|
||||
arg="${arg%=*}"
|
||||
|
||||
# check if the arg is a full url or just `[origin/]name`
|
||||
if [ "${arg#*:}" != "$arg" ]; then
|
||||
url="$arg"
|
||||
origin="$arg"
|
||||
name="${url##*/}"
|
||||
name="${name%.git}"
|
||||
else
|
||||
name="${arg##*/}"
|
||||
origin="${arg%/*}"
|
||||
[ "$origin" = "$arg" ] && origin=""
|
||||
fi
|
||||
|
||||
# check that arg is not `/` or `origin/`
|
||||
[ "$name" ] || die "$name is an invalid name"
|
||||
|
||||
export GIT_DIR=".mgit/$name/.git"
|
||||
|
||||
# if if repo is already cloned do a checkout instead.
|
||||
[ -d ".mgit/$name" ] && {
|
||||
local ver0="$(git_ver_for "$name")"
|
||||
[ "$ver" -a "$ver" = "$ver0" ] && {
|
||||
[ "$VERBOSE" ] && printf "SKIP: %-16s already at %s\n" "$name" "$ver"
|
||||
exit 0
|
||||
}
|
||||
printf "PULL: %-16s %-7s %s\n" "$name" "$ver" "(was: $ver0)" >&2
|
||||
must git fetch $GITOPT $MULTIGIT_FETCH_OPTS >&2
|
||||
if [ "$ver" ]; then
|
||||
must git -c advice.detachedHead=false checkout $GITOPT "$ver" >&2
|
||||
else
|
||||
must git checkout $GITOPT -B master origin/master >&2
|
||||
fi
|
||||
exit 0
|
||||
}
|
||||
|
||||
# check for a registered origin
|
||||
[ -f ".mgit/$name.origin" ] && \
|
||||
rorigin=$(cat .mgit/$name.origin)
|
||||
|
||||
# decide the origin
|
||||
if [ "$origin" ]; then
|
||||
[ "$rorigin" -a "$origin" != "$rorigin" ] && \
|
||||
say "NOTE: $name using different origin: '$origin' (was '$rorigin')."
|
||||
else
|
||||
origin="$rorigin"
|
||||
[ "$origin" ] || die "$name has no origin file"
|
||||
fi
|
||||
|
||||
# find the origin url
|
||||
if [ ! "$url" ]; then
|
||||
if [ -f ".mgit/$origin.baseurl" ]; then
|
||||
local baseurl=$(cat .mgit/$origin.baseurl)
|
||||
url="$baseurl$name"
|
||||
else
|
||||
# assume the origin on file is a full url: check if it is
|
||||
if [ "${origin#*:}" != "$origin" ]; then
|
||||
url="$origin"
|
||||
else
|
||||
say "ABORT: $name has unknown origin: '$origin'."
|
||||
say "HINT: To register '$origin' to be used as an origin, type, eg.:"
|
||||
say "HINT: "$(basename "$0")" baseurl $origin https://github.com/$origin/"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# finally, clone the repo
|
||||
(
|
||||
printf "CLONE: %-16s %s\n" "$name" "$ver" >&2
|
||||
no_exclude_file=1 must init "$name"
|
||||
must git remote add origin "$url"
|
||||
must git fetch $GITOPT $MULTIGIT_FETCH_OPTS >&2
|
||||
) || {
|
||||
# cleanup on failed fetch because git doesn't.
|
||||
rm -rf ".mgit/$name/"
|
||||
rm -f ".mgit/$name.exclude"
|
||||
die "$name clone failed"
|
||||
}
|
||||
if run git rev-parse $GITOPT --verify origin/master >/dev/null; then
|
||||
run git branch $GITOPT --track master origin/master >&2
|
||||
must git -c advice.detachedHead=false checkout $GITOPT $ver >&2
|
||||
else # new repo
|
||||
must git checkout $GITOPT -b master
|
||||
git config branch.master.remote origin
|
||||
git config branch.master.merge refs/heads/master
|
||||
fi
|
||||
# make an exclude file if one wasn't checked out already.
|
||||
must make_exclude_file "$name"
|
||||
|
||||
# (re)register the repo's origin.
|
||||
if [ "$origin" != "$rorigin" ]; then
|
||||
if [ "$rorigin" ]; then
|
||||
say "NOTE: $name has new origin: '$origin' (was: '$rorigin')"
|
||||
else
|
||||
[ "$VERBOSE" ] && {
|
||||
say "NOTE: $name has origin: '$origin'"
|
||||
say " (url: $url)"
|
||||
}
|
||||
fi
|
||||
must mkdir -p .mgit
|
||||
echo "$origin" > ".mgit/$name.origin" \
|
||||
|| die "Could not create origin file .mgit/$name.origin".
|
||||
fi
|
||||
}
|
||||
|
||||
clone_from_stdin() {
|
||||
xargs -i -P "$MAXPROC" -- "$0" clone "{}"
|
||||
}
|
||||
|
||||
clone() {
|
||||
[ "$1" ] || die "Repo name expected."
|
||||
if [ "$1" = "-" ]; then
|
||||
clone_from_stdin
|
||||
elif [ $# = 1 ]; then
|
||||
clone_one "$1"
|
||||
else
|
||||
while [ $# != 0 ]; do
|
||||
printf "%s\n" "$1"
|
||||
shift
|
||||
done | clone_from_stdin
|
||||
fi
|
||||
}
|
||||
|
||||
remove_one() {
|
||||
|
||||
[ -d ".mgit/$1/" ] || die "$1 is not a cloned repo"
|
||||
|
||||
# don't remove from a subshell
|
||||
[ "$MULTIGIT_REPO" = "$1" ] && die "Refusing to remove '$1' from a subshell."
|
||||
|
||||
# get tracked files for this repo
|
||||
files="$(GIT_DIR=".mgit/$1/.git" git ls-files)" || {
|
||||
say "ABORT: Could not get the list of files for '$1'."
|
||||
say "HINT: If you know that there are no checked out files,"
|
||||
say "HINT: feel free to \`rm -rf .mgit/$1/ .mgit/$1.exclude\`."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ask for confirmation if there are files to delete
|
||||
[ "$files" -a -z "$YES" ] && {
|
||||
local n=$(echo "$files" | wc -l)
|
||||
say "Remove ALL $((n)) files of '$1'? You can't undo this [yes/N]"
|
||||
read yes
|
||||
[ "$yes" = "yes" ] || { say "Canceled."; exit 1; }
|
||||
}
|
||||
|
||||
# remove files
|
||||
for file in $files; do
|
||||
dry rm "$file"
|
||||
done
|
||||
|
||||
# remove empty directories
|
||||
for file in $files; do
|
||||
echo "$(dirname "$file")"
|
||||
done | uniq | while read dir; do
|
||||
[ "$dir" != "." ] && dry /bin/rmdir -p "$dir" 2>/dev/null
|
||||
done
|
||||
|
||||
# remove the git dir
|
||||
dry rm -rf ".mgit/$1/"
|
||||
dry rm -f ".mgit/$1.exclude"
|
||||
|
||||
say "REMOVED: $1"
|
||||
}
|
||||
|
||||
remove() {
|
||||
[ "$1" ] || die "Repo name expected."
|
||||
if [ $# = 1 ]; then
|
||||
remove_one "$1"
|
||||
else
|
||||
while true; do
|
||||
case "$1" in
|
||||
--dry) export DRY=1; shift ;;
|
||||
--yes) export YES=1; shift ;;
|
||||
*) break ;;
|
||||
esac
|
||||
done
|
||||
while [ $# != 0 ]; do
|
||||
"$0" remove "$1"
|
||||
shift
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
convert() {
|
||||
local name="$1"
|
||||
[ "$name" ] || name="$(basename "$PWD")"
|
||||
check_repo_name "$name"
|
||||
[ -d ".mgit/$name" ] && {
|
||||
[ -d "$PWD0/.git" ] && die "$name already taken"
|
||||
[ "$VERBOSE" ] && say "SKIP: $name already in multigit"
|
||||
exit 0
|
||||
}
|
||||
[ -d "$PWD0/.git" ] || die "No .git dir in current dir"
|
||||
must mkdir -p ".mgit/$name"
|
||||
must mv "$PWD0/.git" ".mgit/$name/.git"
|
||||
export GIT_DIR=".mgit/$name/.git"
|
||||
must git config core.worktree ../../..
|
||||
must git config core.excludesfile ".mgit/$name.exclude"
|
||||
must make_exclude_file "$name"
|
||||
say "CONVERTED: $name"
|
||||
}
|
||||
|
||||
baseurl() {
|
||||
[ "$1" ] || {
|
||||
for f in .mgit/*.baseurl; do
|
||||
f="${f#.mgit/}"
|
||||
f="${f%.baseurl}"
|
||||
printf "%-20s %s\n" "$f" "$(baseurl "$f")"
|
||||
done
|
||||
return
|
||||
}
|
||||
local origin="$1"
|
||||
local url="$2"
|
||||
# spaces not allowed
|
||||
origin="${origin//[[:blank:]]/}"
|
||||
url="${url//[[:blank:]]/}"
|
||||
[ -z "$1" -o "$1" != "$origin" ] && die "Invalid origin name '$1'."
|
||||
[ "$2" -a "$2" != "$url" ] && die "Invalid baseurl '$2'."
|
||||
|
||||
if [ "X$url" = "X-" ]; then
|
||||
rm ".mgit/$origin.baseurl"
|
||||
elif [ "$url" ]; then
|
||||
[ "${url##*/}" != "" ] && die "A base URL must end with a '/'."
|
||||
mkdir -p .mgit
|
||||
echo "$url" > ".mgit/$origin.baseurl"
|
||||
else
|
||||
cat ".mgit/$origin.baseurl"
|
||||
fi
|
||||
}
|
||||
|
||||
origin() {
|
||||
[ "$1" ] || {
|
||||
for f in .mgit/*.origin; do
|
||||
f="${f#.mgit/}"
|
||||
f="${f%.origin}"
|
||||
printf "%-20s %s\n" "$f" "$(origin "$f")"
|
||||
done
|
||||
return
|
||||
}
|
||||
local repo="$1"
|
||||
local origin="$2"
|
||||
|
||||
# spaces not allowed
|
||||
repo="${repo//[[:blank:]]/}"
|
||||
origin="${origin//[[:blank:]]/}"
|
||||
[ -z "$1" -o "$1" != "$repo" ] && die "Invalid repo name '$1'."
|
||||
[ "$2" -a "$2" != "$origin" ] && die "Invalid origin '$2'."
|
||||
|
||||
if [ "X$origin" = "X-" ]; then
|
||||
rm ".mgit/$repo.origin"
|
||||
elif [ "$origin" ]; then
|
||||
mkdir -p .mgit
|
||||
echo "$origin" > ".mgit/$repo.origin"
|
||||
else
|
||||
cat ".mgit/$repo.origin"
|
||||
fi
|
||||
}
|
||||
|
||||
list_releases() {
|
||||
for f in .mgit/*.release; do
|
||||
f="${f#.mgit/}"
|
||||
f="${f%.release}"
|
||||
echo "$f"
|
||||
done
|
||||
}
|
||||
|
||||
release_file() {
|
||||
local rel="$1"
|
||||
[ "$rel" ] || die "Release name or file expected."
|
||||
[ "${rel##*.}" = "release" -a -f "$rel" ] || rel=".mgit/$rel.release"
|
||||
[ "$2" = "nocheck" -o -f "$rel" ] || die "Release not found: '$1'."
|
||||
echo "$rel"
|
||||
}
|
||||
|
||||
show_release() {
|
||||
local f; f="$(release_file "$1")" || exit; cat "$f"
|
||||
}
|
||||
|
||||
update_release() {
|
||||
local rel; rel="$(release_file "$1" nocheck)" || exit
|
||||
if [ -f "$rel" ]; then
|
||||
local s="$(cat "$rel")"
|
||||
echo "$s" | (IFS=" "; while read repo ver0; do
|
||||
local name="${repo##*/}"
|
||||
local ver="$(git_ver_for "$name" $2)"
|
||||
[ "$ver0" = "*" ] && ver="*" # not touching those
|
||||
printf "%-20s %s\n" "$repo" "$ver"
|
||||
[ "$ver" != "$ver0" ] && printf "%-20s %s -> %s\n" "$repo" "$ver0" "$ver" >&2
|
||||
done) > "$rel"
|
||||
else
|
||||
"$0" --all version $2 > "$rel"
|
||||
fi
|
||||
}
|
||||
|
||||
remove_release() {
|
||||
local f; f="$(release_file "$1")" || exit; rm "$f"
|
||||
}
|
||||
|
||||
list_release_repos() {
|
||||
printf "%s\n" "$RELEASE" | (IFS=" "; while read repo ver; do
|
||||
local name="${repo##*/}"
|
||||
printf "%s\n" "$name"
|
||||
done)
|
||||
}
|
||||
|
||||
clone_release() {
|
||||
local rel; rel="$(release_file "$1")" || exit
|
||||
local s="$(cat "$rel")" # load it in memory to make sure we have it till the end.
|
||||
|
||||
# step 1: remove any repos that are not part of the release.
|
||||
RELEASE="$s" list_sub list_cloned list_release_repos | while read repo; do
|
||||
"$0" --yes remove "$repo"
|
||||
done
|
||||
|
||||
# step 2: clone/checkout repos present in the release.
|
||||
printf "%s\n" "$s" | (IFS=" "; while read repo ver; do
|
||||
# Version "*" in a release file specifies that the repo is already cloned
|
||||
# and checked out: this is the repo that contains the release file itself.
|
||||
# Even if cloned manually beforehand, the repo that contains the release file
|
||||
# must still be listed in the release file to avoid removing it in step 1.
|
||||
if [ "$ver" != "*" ]; then
|
||||
printf "%s\n" "$repo=$ver"
|
||||
fi
|
||||
done
|
||||
) | "$0" clone -
|
||||
[ $? = 0 ] || die "Failed."
|
||||
}
|
||||
|
||||
release() {
|
||||
local rel="$1"
|
||||
local cmd="$2"
|
||||
rel="${rel//[[:blank:]]/}" # spaces not allowed
|
||||
[ "$rel" -a "$1" != "$rel" ] && die "Invalid release name '$1'."
|
||||
[ "$rel" ] || { list_releases; return; }
|
||||
shift 2
|
||||
case "$cmd" in
|
||||
"") show_release "$rel" "$@" ;;
|
||||
update) update_release "$rel" "$@" ;;
|
||||
clone) clone_release "$rel" "$@" ;;
|
||||
remove) remove_release "$rel" "$@" ;;
|
||||
*) die "Invalid release command '$cmd'."
|
||||
esac
|
||||
}
|
||||
|
||||
git_shell() {
|
||||
cd "$PWD0" || return
|
||||
say "Entering subshell: git commands will affect the repo '$MULTIGIT_REPO'."
|
||||
say "Type \`exit' to exit subshell."
|
||||
git status -s
|
||||
say
|
||||
if [ "$OSTYPE" = "msys" ]; then
|
||||
export PROMPT="[$MULTIGIT_REPO] \$P\$G"
|
||||
"$COMSPEC" /k
|
||||
else
|
||||
export PS1="[$MULTIGIT_REPO] \u@\h:\w\$ "
|
||||
"$SHELL" --norc -i
|
||||
fi
|
||||
}
|
||||
|
||||
git_ver_for() {
|
||||
if [ "$2" = "tag" ]; then
|
||||
GIT_DIR=".mgit/$1/.git" git describe --tags --abbrev=0 2>/dev/null $3
|
||||
else
|
||||
GIT_DIR=".mgit/$1/.git" git describe --tags --long --always $3
|
||||
fi
|
||||
}
|
||||
|
||||
git_ver() {
|
||||
printf "$MULTIGIT_REPO=$(git_ver_for "$MULTIGIT_REPO" $1) "
|
||||
}
|
||||
|
||||
git_version() {
|
||||
printf "%-20s %s" "$MULTIGIT_REPO"
|
||||
git_ver_for "$MULTIGIT_REPO" $1
|
||||
}
|
||||
|
||||
git_remove_links() {
|
||||
[ "$OSTYPE" = "msys" ] && die "Not for Windows."
|
||||
([ "$MULTIGIT_REPO" ] && cd ".mgit/$MULTIGIT_REPO" || exit 1
|
||||
find . ! -path './.git/*' ! -path './.git' ! -path '.' -exec rm -rf {} \; 2>/dev/null)
|
||||
}
|
||||
git_make_hardlinks() {
|
||||
git_remove_links
|
||||
git ls-files | while read f; do
|
||||
mkdir -p "$(dirname ".mgit/$MULTIGIT_REPO/$f")"
|
||||
ln -f "$f" ".mgit/$MULTIGIT_REPO/$f"
|
||||
done
|
||||
}
|
||||
git_make_symlinks() {
|
||||
git_remove_links
|
||||
git ls-files | while read f; do
|
||||
mkdir -p "$(dirname ".mgit/$MULTIGIT_REPO/$f")"
|
||||
ln -sf "$PWD/$f" ".mgit/$MULTIGIT_REPO/$f"
|
||||
done
|
||||
}
|
||||
|
||||
git_cmd_one() {
|
||||
local repo="$1"
|
||||
local cmd="$2"
|
||||
shift 2
|
||||
[ "$VERBOSE" ] && say "@$repo:"
|
||||
export GIT_DIR="$PWD/.mgit/$repo/.git"
|
||||
export MULTIGIT_REPO="$repo"
|
||||
[ -d "$GIT_DIR" ] || die "Unknown repo: '$repo'."
|
||||
case "$cmd" in
|
||||
exec) cd "$PWD0" && "$@" ;;
|
||||
ver) git_ver "$@" ;;
|
||||
version) git_version "$@" ;;
|
||||
make-symlinks) git_make_symlinks "$@" ;;
|
||||
make-hardlinks) git_make_hardlinks "$@" ;;
|
||||
"") git_shell "$@" ;;
|
||||
*)
|
||||
# look for and execute a git plugin command
|
||||
if [ -f ".mgit/git-$cmd.sh" ]; then
|
||||
PWD0="$PWD0" ".mgit/git-$cmd.sh" "$@"
|
||||
else
|
||||
(cd "$PWD0" && git "$cmd" "$@")
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
git_cmd() {
|
||||
local repos="$1"; shift
|
||||
if [ "$repos" = "--all" ]; then
|
||||
[ "$1" ] || die "Refusing to start a subshell for each repo."
|
||||
for repo in `list_cloned`; do
|
||||
git_cmd_one "$repo" "$@"
|
||||
done
|
||||
else
|
||||
(IFS=","; for repo in $repos; do
|
||||
git_cmd_one "$repo" "$@"
|
||||
done)
|
||||
fi
|
||||
[ "$1" = "ver" ] && say
|
||||
}
|
||||
|
||||
cd_root() {
|
||||
local pwd1
|
||||
while [ "$PWD" != "$pwd1" ]; do
|
||||
[ -d .mgit ] && return
|
||||
pwd1="$PWD"
|
||||
cd .. || die "Could not cd to '$PWD/..'."
|
||||
done
|
||||
cd "$PWD0" # root dir not found, go back to initial dir
|
||||
}
|
||||
|
||||
PWD0="$PWD"
|
||||
cd_root
|
||||
|
||||
while true; do
|
||||
case "$1" in
|
||||
-v) shift; export VERBOSE=1;;
|
||||
--debug) shift; export DEBUG=1;;
|
||||
--dry) shift; export DRY=1;;
|
||||
--yes) shift; export YES=1;;
|
||||
-SS) shift; [ "$OSTYPE" != "msys" ] && export GIT_SSH_COMMAND="ssh -o ControlMaster=auto -o ControlPersist=10 -o ControlPath=~/.ssh/sock-mgit";;
|
||||
-P) shift; export MAXPROC="$1"; shift;;
|
||||
*) break;;
|
||||
esac
|
||||
done
|
||||
[ "$DEBUG" ] || export GITOPT=-q
|
||||
[ "$MAXPROC" ] || export MAXPROC=1
|
||||
cmd="$1"; shift
|
||||
case "$cmd" in
|
||||
"") usage ;;
|
||||
help) usage ;;
|
||||
--help) usage ;;
|
||||
ls) list_cloned ;;
|
||||
ls-all) list_known ;;
|
||||
ls-uncloned) list_uncloned ;;
|
||||
ls-modified) list_modified "$@" ;;
|
||||
st) list_modified "$@" ;;
|
||||
status) list_modified "$@" ;;
|
||||
which) which_tracks "$@" ;;
|
||||
ls-unpushed) list_unpushed ;;
|
||||
ls-untracked) list_untracked ;;
|
||||
ls-double-tracked) list_double_tracked ;;
|
||||
ls-tracked) list_tracked ;;
|
||||
init) init "$@" ;;
|
||||
clone) clone "$@" ;;
|
||||
clone-all) clone_all "$@" ;;
|
||||
clone-release) clone_release "$@" ;;
|
||||
remove) remove "$@" ;;
|
||||
convert) convert "$@" ;;
|
||||
baseurl) baseurl "$@" ;;
|
||||
origin) origin "$@" ;;
|
||||
release) release "$@" ;;
|
||||
bash) cd "$PWD0" && exec bash "$@" ;;
|
||||
-) git_cmd "$@" ;;
|
||||
*)
|
||||
# look for and execute a plugin command
|
||||
if [ -f ".mgit/$cmd.sh" ]; then
|
||||
".mgit/$cmd.sh" "$@"
|
||||
else
|
||||
git_cmd "$cmd" "$@"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user