Great Teeming Workspaces (GTWS) 是一个用于 Git 的复杂工作区管理包,它可以轻松地为不同的项目和项目的不同版本设置开发环境。
GTWS 有点像 Python 的 venv,但适用于 Python 以外的语言,它处理多个项目的多个版本的工作区。您可以轻松地创建、更新、进入和离开工作区,并且每个项目或版本组合(最多)有一个本地 origin,它与上游同步—所有其他工作区都从本地 origin 更新。
布局
${GTWS_ORIGIN}/<project>/<repo>[/<version>]
${GTWS_BASE_SRCDIR}/<project>/<version>/<workspacename>/{<repo>[,<repo>...]}
源树中的每个级别(加上全局变量的 homedir)都可以包含一个 .gtwsrc 文件,该文件维护与该级别相关的设置和 Bash 代码。每个更具体的级别都会覆盖更高级别的设置。
设置
使用以下命令检出 GTWS
git clone https://github.com/dang/gtws.git
设置您的 ${HOME}/.gtwsrc。它应该包含 GTWS_ORIGIN,并可选择包含 GTWS_SETPROMPT。
将 repo 目录添加到您的路径
export PATH="${PATH}:/path/to/gtws
配置
配置通过级联的 .gtwsrc 文件完成。它从根目录向下遍历实际路径,并依次加载找到的每个 .gtwsrc 文件。更具体的文件会覆盖不太具体的文件。
在您的顶级 ~/.gtws/.gtwsrc 中设置以下内容
- GTWS_BASE_SRCDIR: 这是所有项目源树的根目录。它默认为 $HOME/src。
- GTWS_ORIGIN: 这设置了 origin Git 树的位置。它默认为 $HOME/origin。
- GTWS_SETPROMPT: 这是可选的。如果设置了,shell 提示符将包含工作区名称。
- GTWS_DEFAULT_PROJECT: 这是在未给出或未知项目时使用的项目。如果未给出,则必须在命令行上指定项目。
- GTWS_DEFAULT_PROJECT_VERSION: 这是要检出的默认版本。它默认为 master。
在每个项目的项目级别设置以下内容
- GTWS_PROJECT: 项目的名称(和基本目录)。
- gtws_project_clone: 此函数用于克隆项目的特定版本。如果未定义,则假定项目的 origin 包含每个版本的单个目录,并且该目录包含一组要克隆的 Git 仓库。
- gtws_project_setup: 此可选函数在所有克隆完成后调用,并允许项目所需的任何其他设置,例如在 IDE 中设置工作区。
在项目版本级别设置此项
- GTWS_PROJECT_VERSION: 这是项目的版本。它用于从 origin 正确拉取。在 Git 中,这很可能是分支名称。
这些内容可以放在树中的任何位置,并且可以多次覆盖,如果这样做有意义的话
- GTWS_PATH_EXTRA: 这些是要添加到工作区内部路径的额外路径元素。
- GTWS_FILES_EXTRA: 这些是不在版本控制下的额外文件,应复制到工作区中的每个检出中。这包括诸如 .git/info/exclude 之类的文件,并且每个文件都相对于其仓库的根目录。
Origin 目录
GTWS_ORIGIN(在大多数脚本中)指向要从中拉取和推送的原始 Git 检出。
${GTWS_ORIGIN} 的布局
- /<项目>
- 这是项目仓库的根目录。
- 如果给定了 gtws_project_clone,则此目录可以具有您想要的任何布局。
- 如果未给出 gtws_project_clone,则此目录必须包含一个名为 git 的子目录,该子目录包含一组要克隆的裸 Git 仓库。
工作流程示例
假设您有一个名为 Foo 的项目,它在上游仓库 github.com/foo/foo.git 中。此仓库有一个名为 bar 的子模块,其上游位于 github.com/bar/bar.git。Foo 项目在 master 分支中进行开发,并使用稳定的版本分支。
在您可以使用 GTWS 和 Foo 之前,首先必须设置目录结构。这些示例假设您正在使用默认目录结构。
- 设置您的顶级 .gtwsrc
- cp ${GTWS_LOC}/examples/gtwsrc.top ~/.gtwsrc
- 编辑 ~/.gtwsrc 并根据需要进行更改。
- 创建顶级目录
- mkdir -p ~/origin ~/src
- 创建并设置项目目录
- mkdir -p ~/src/foo
cp ${GTWS_LOC}/examples/gtwsrc.project ~/src/foo/.gtwsrc - 编辑 ~/src/foo/.gtwsrc 并根据需要进行更改。
- mkdir -p ~/src/foo
- 创建并设置 master 版本目录
- mkdir -p ~/src/foo/master
cp ${GTWS_LOC}/examples/gtwsrc.version ~/src/foo/master/.gtwsrc - 编辑 ~/src/foo/master/.gtwsrc 并根据需要进行更改。
- mkdir -p ~/src/foo/master
- 转到版本目录并创建一个临时工作区来设置镜像
- mkdir -p ~/src/foo/master/tmp
cd ~/src/foo/master/tmp
git clone --recurse-submodules git://github.com/foo/foo.git
cd foo
gtws-mirror -o ~/origin -p foo - 这将创建 ~/origin/foo/git/foo.git 和 ~/origin/foo/submodule/bar.git。
- 未来的克隆将从这些 origin 而不是从上游克隆。
- 此工作区现在可以删除。
- mkdir -p ~/src/foo/master/tmp
此时,可以在 Foo 的 master 分支上完成工作。假设您想修复一个名为 bug1234 的错误。您可以为此工作创建一个工作区,以使其与您正在处理的任何其他内容隔离,然后在该工作区内工作。
- 转到版本目录,并创建一个新的工作区
- cd ~/src/foo/master
mkws bug1234 - 这将创建 bug1234/,并在其中检出 Foo(及其子模块 bar)并创建 build/foo 用于构建它。
- cd ~/src/foo/master
- 进入工作区。有两种方法可以执行此操作
- cd ~/src/foo/master/bug1234
startws
或
cd ~/src/foo/master/
startws bug1234 - 这会在 bug1234 工作区内启动一个子 shell。此 shell 具有 GTWS 环境以及您在堆叠的 .gtwsrc 文件中设置的任何环境。它还将工作区的根目录添加到您的 CD 路径,因此您可以从该根目录 cd 进入相对路径。
- 此时,您可以处理 bug1234,构建它,测试它,并提交您的更改。当您准备好推送到上游时,请执行以下操作:
cd foo
wspush - wspush 将推送与您的工作区关联的分支—首先推送到您的本地 origin,然后再推送到上游。
- 如果上游发生更改。您可以使用以下命令同步您的本地检出:
git sync - 这将调用 GTWS 中的 git-sync 脚本,该脚本将从本地 origin 更新您的检出。要更新本地 origin,请使用:
git sync -o - 这将更新您的本地 origin 和子模块的镜像,然后使用它们来更新您的检出。git-sync 还有其他不错的功能。
- 当您完成使用工作区时,只需退出 shell
exit - 您可以随时重新进入工作区,并在同一工作区中同时拥有多个 shell。
- cd ~/src/foo/master/bug1234
- 当您完成使用工作区后,可以使用 rmws 命令删除它,或者直接删除其目录树。
- 有一个名为 tmws 的脚本,它在 tmux 中进入工作区,创建一组非常特定于我的工作流程的窗口/窗格。 随意修改它以满足您的需求。
脚本
#!/bin/bash
# Functions for gtws
#
GTWS_LOC=$(readlink -f $(dirname "${BASH_SOURCE[0]}"))
export GTWS_LOC
# if is_interactive; then echo "interactive" fi
#
# Check for an interactive shell
is_interactive() {
case $- in
*i*)
# Don't die in interactive shells
return 0
;;
*)
return 1
;;
esac
}
# if can_die; then exit
#
# Check to see if it's legal to exit during die
can_die() {
if (( BASH_SUBSHELL > 0 )); then
debug_print "\t\tbaby shell; exiting"
return 0
fi
if ! is_interactive; then
debug_print "\t\tNot interactive; exiting"
return 0
fi
debug_print "\t\tParent interactive; not exiting"
return 1
}
# In a function:
# command || die "message" || return 1
# Outside a function:
# command || die "message"
#
# Print a message and exit with failure
die() {
echo -e "Failed: $1" >&2
if [ ! -z "$(declare -F | grep "GTWScleanup")" ]; then
GTWScleanup
fi
if can_die; then
exit 1
fi
return 1
}
# Alternativess for using die properly to handle both interactive and script useage:
#
# Version 1:
#
#testfunc() {
# command1 || die "${FUNCNAME}: command1 failed" || return 1
# command2 || die "${FUNCNAME}: command2 failed" || return 1
# command3 || die "${FUNCNAME}: command3 failed" || return 1
#}
#
# Version 2:
#
#testfunc() {
# (
# command1 || die "${FUNCNAME}: command1 failed"
# command2 || die "${FUNCNAME}: command2 failed"
# command3 || die "${FUNCNAME}: command3 failed"
# )
# return $?
#}
#
# Optionally, the return can be replaced with this:
# local val=$?
# [[ "${val}" == "0" ]] || die
# return ${val}
# This will cause the contaning script to abort
# usage "You need to provide a frobnicator"
#
# Print a message and the usage for the current script and exit with failure.
usage() {
local myusage;
if [ -n "${USAGE}" ]; then
myusage=${USAGE}
else
myusage="No usage given"
fi
local me;
if [ -n "${ME}" ]; then
me=${ME}
else
me=$(basename $0)
fi
if [ -n "$1" ]; then
echo "$@"
fi
echo ""
if [ -n "${DESCRIPTION}" ]; then
echo -e "${me}: ${DESCRIPTION}"
echo ""
fi
echo "Usage:"
echo "${me} ${myusage}"
if [ -n "${LONGUSAGE}" ]; then
echo -e "${LONGUSAGE}"
fi
exit 1
}
# debug_print "Print debug information"
#
# Print debug information based on GTWS_VERBOSE
debug_print() {
if [ -n "${GTWS_VERBOSE}" ]; then
echo -e "${GTWS_INDENT}$@" >&2
fi
}
# debug_trace_start
#
# Start tracing all commands
debug_trace_start() {
if [ -n "${GTWS_VERBOSE}" ]; then
set -x
fi
}
# debug_trace_stop
#
# Stop tracing all commands
debug_trace_stop() {
set +x
}
# cmd_exists ${cmd}
#
# Determine if a command exists on the system
function cmd_exists {
which $1 > /dev/null 2>&1
if [ "$?" == "1" ]; then
die "You don't have $1 installed, sorry" || return 1
fi
}
# is_git_repo ${dir}
#
# return success if ${dir} is in a git repo, or failure otherwise
is_git_repo() {
debug_print "is_git_repo $1"
if [[ $1 == *:* ]]; then
debug_print " remote; assume good"
return 0
elif [ ! -d "$1" ]; then
debug_print " fail: not dir"
return 1
fi
cd "$1"
git rev-parse --git-dir >/dev/null 2>&1
local ret=$?
cd - > /dev/null
debug_print " retval: $ret"
return $ret
}
# find_git_repo ${basedir} ${repo_name} repo_dir
#
# Find the git repo for ${repo_name} in ${basedir}. It's one of ${repo_name}
# or ${repo_name}.git
#
# Result will be in the local variable repo_dir Or:
#
# repo_dir=$(find_git_repo ${basedir} ${repo_name})
#
function find_git_repo {
local basedir=$1
local repo_name=$2
local __resultvar=$3
local try="${basedir}/${repo_name}"
if ! is_git_repo "${try}" ; then
try=${try}.git
fi
is_git_repo "${try}" || die "${repo_name} in ${basedir} is not a git repository" || return 1
if [[ "$__resultvar" ]]; then
eval $__resultvar="'$try'"
else
echo "$try"
fi
}
# git_top_dir top
#
# Get the top level of the git repo contaning PWD, or return failure;
#
# Result will be in local variable top Or:
#
# top = $(git_top_dir)
#
# Result will be in local variable top
function git_top_dir {
local __resultvar=$1
local __top="$(git rev-parse --show-toplevel 2>/dev/null)"
if [ -z "${__top}" ]; then
die "${PWD} is not a git repo" || return 1
fi
if [[ "$__resultvar" ]]; then
eval $__resultvar="'$__top'"
else
echo "$__top"
fi
}
# is_git_rebase
#
# return success if git repo is in a rebase
is_git_rebase() {
debug_print "is_git_rebase $1"
(test -d "$(git rev-parse --git-path rebase-merge)" || \
test -d "$(git rev-parse --git-path rebase-apply)" )
local ret=$?
debug_print " retval: $ret"
return $ret
}
# is_docker
#
# return success if process is running inside docker
is_docker() {
debug_print "is_docker"
grep -q docker /proc/self/cgroup
return $?
}
# is_gtws
#
# return success if process is running inside a workspace
is_gtws() {
if [ -n "${GTWS_WS_GUARD}" ]; then
return 0
fi
return 1
}
function gtws_rcp {
rsync --rsh=ssh -avzS --progress --ignore-missing-args --quiet "$@"
}
function gtws_cpdot {
local srcdir=$1
local dstdir=$2
debug_print "${FUNCNAME} - ${srcdir} to ${dstdir}"
if [ -d "${srcdir}" ] && [ -d "${dstdir}" ]; then
shopt -s dotglob
cp -a "${srcdir}"/* "${dstdir}"/
shopt -u dotglob
fi
}
# gtws_find_dockerfile dockerfile
#
# Result will be in local variable dockerfile Or:
#
# dockerfile = $(gtws_find_dockerfile)
#
# Result will be in local variable dockerfile
#
# Get the path to the most-specific Dockerfile
function gtws_find_dockerfile {
local __resultvar=$1
local __dir="${GTWS_WSPATH}"
local __file="Dockerfile"
debug_print "${FUNCNAME} - trying ${__dir}/${__file}"
if [ ! -f "${__dir}/${__file}" ]; then
# Version dir
__dir=$(dirname "${__dir}")
debug_print "${FUNCNAME} - trying ${__dir}/${__file}"
fi
if [ ! -f "${__dir}/${__file}" ]; then
# Project dir
__dir=$(dirname "${__dir}")
debug_print "${FUNCNAME} - trying ${__dir}/${__file}"
fi
if [ ! -f "${__dir}/${__file}" ]; then
# Top level, flavor
__dir="${GTWS_LOC}/dockerfiles"
__file="Dockerfile-${FLAVOR}"
debug_print "${FUNCNAME} - trying ${__dir}/${__file}"
fi
if [ ! -f "${__dir}/${__file}" ]; then
# Top level, base
__dir="${GTWS_LOC}/dockerfiles"
__file="Dockerfile-base"
debug_print "${FUNCNAME} - trying ${__dir}/${__file}"
fi
if [ ! -f "${__dir}/${__file}" ]; then
die "Could not find a Dockerfile" || return 1
fi
debug_print "${FUNCNAME} - found ${__dir}/${__file}"
if [[ "$__resultvar" ]]; then
eval $__resultvar="'${__dir}/${__file}'"
else
echo "$__dir"
fi
}
# gtws_smopvn ${GTWS_SUBMODULE_ORIGIN:-${GTWS_ORIGIN}} ${GTWS_PROJECT} ${GTWS_PROJECT_VERSION} ${GTWS_WSNAME} smopvn
#
# Result will be in local variable smopvn. Or:
#
# smopvn = $(gtws_smopvn ${GTWS_SUBMODULE_ORIGIN:-${GTWS_ORIGIN}} ${GTWS_PROJECT} ${GTWS_PROJECT_VERSION} ${GTWS_WSNAME})
#
# Result will be in local variable smovpn
#
# Get the path to submodules for this workspace
function gtws_smopvn {
local origin=$1
local project=$2
local version=$3
local name=$4
local __resultvar=$5
local __smopv="${origin}/${project}/submodule"
if [[ "$__resultvar" ]]; then
eval $__resultvar="'$__smopv'"
else
echo "$__smopv"
fi
}
# gtws_opvn ${GTWS_ORIGIN} ${GTWS_PROJECT} ${GTWS_PROJECT_VERSION} ${GTWS_WSNAME} opvn
#
# Result will be in local variable opvn. Or:
#
# opvn = $(gtws_opvn ${GTWS_ORIGIN} ${GTWS_PROJECT} ${GTWS_PROJECT_VERSION} ${GTWS_WSNAME})
#
# Result will be in local variable opvn.
#
# Get the path to git repos for this workspace
function gtws_opvn {
local origin=$1
local project=$2
local version=$3
local name=$4
local __resultvar=$5
local __opv="${origin}/${project}/${version}"
if [[ $__opv == *:* ]]; then
__opv="${__opv}/${name}"
debug_print "remote; using opvn $__opv"
elif [ ! -d "${__opv}" ]; then
__opv="${origin}/${project}/git"
if [ ! -d "${__opv}" ]; then
die "No opvn for ${origin} ${project} ${version}" || return 1
fi
fi
if [[ "$__resultvar" ]]; then
eval $__resultvar="'$__opv'"
else
echo "$__opv"
fi
}
# gtws_submodule_url ${submodule} url
#
# Result will be in local variable url Or:
#
# url = $(gtws_submodule_url ${submodule})
#
# Result will be in local variable url
#
# Get the URL for a submodule
function gtws_submodule_url {
local sub=$1
local __resultvar=$2
local __url=$(git config --list | grep "submodule.*url" | grep "\<${sub}\>" | cut -d = -f 2)
if [ -z "${__url}" ]; then
local rpath=${PWD}
local subsub=$(basename "${sub}")
cd "$(dirname "${sub}")"
debug_print "${FUNCNAME} trying ${PWD}"
__url=$(git config --list | grep submodule | grep "\<${subsub}\>" | cut -d = -f 2)
cd "${rpath}"
fi
debug_print "${FUNCNAME} $sub url: $__url"
if [[ "$__resultvar" ]]; then
eval $__resultvar="'$__url'"
else
echo "$__url"
fi
}
# gtws_submodule_mirror ${smopv} ${submodule} ${sub_sub_basename} mloc
#
# Result will be in local variable mloc Or:
#
# mloc = $(gtws_submodule_mirror ${smopv} ${submodule} ${sub_sub_basename})
#
# Result will be in local variable mloc
#
# Get the path to a local mirror of the submodule, if it exists
function gtws_submodule_mirror {
local smopv=$1
local sub=$2
local sub_sub=$3
local __resultvar=$4
local __mloc=""
local url=$(gtws_submodule_url ${sub})
if [ -n "${url}" ]; then
local urlbase=$(basename ${url})
# XXX TODO - handle remote repositories
#if [[ ${smopv} == *:* ]]; then
## Remote SMOPV means clone from that checkout; I don't cm
#refopt="--reference ${smopv}/${name}/${sub}"
if [ -d "${smopv}/${urlbase}" ]; then
__mloc="${smopv}/${urlbase}"
fi
fi
if [[ "$__resultvar" ]]; then
eval $__resultvar="'$__mloc'"
else
echo "$__mloc"
fi
}
# gtws_submodule_paths subpaths
#
# Result will be in local variable subpaths Or:
#
# subpaths = $(gtws_submodule_paths)
#
# Result will be in local variable subpaths
#
# Get the paths to submodules in a get repo. Does not recurse
function gtws_submodule_paths {
local __resultvar=$1
local __subpaths=$(git submodule status | sed 's/^ *//' | cut -d ' ' -f 2)
if [[ "$__resultvar" ]]; then
eval $__resultvar="'$__subpaths'"
else
echo "$__subpaths"
fi
}
# gtws_submodule_clone [<base-submodule-path>] [<sub-sub-basename>]
#
# This will set up all the submodules in a repo. Should be called from inside
# the parent repo
function gtws_submodule_clone {
local smopv=$1
local sub_sub=$2
local sub_paths=$(gtws_submodule_paths)
local rpath="${PWD}"
if [ -z "${smopv}" ]; then
smopv=$(gtws_smopvn "${GTWS_SUBMODULE_ORIGIN:-${GTWS_ORIGIN}}" "${GTWS_PROJECT}" "${GTWS_PROJECT_VERSION}" "${GTWS_WSNAME}")
fi
git submodule init || die "${FUNCNAME}: Failed to init submodules" || return 1
for sub in ${sub_paths}; do
local refopt=""
local mirror=$(gtws_submodule_mirror "${smopv}" "${sub}" "${sub_sub}")
debug_print "${FUNCNAME} mirror: ${mirror}"
if [ -n "${mirror}" ]; then
refopt="--reference ${mirror}"
fi
git submodule update ${refopt} "${sub}"
# Now see if there are recursive submodules
cd "${sub}"
gtws_submodule_clone "${smopv}/${sub}_submodule" "${sub}" || return 1
cd "${rpath}"
done
}
# gtws_repo_clone <base-repo-path> <repo> <branch> [<base-submodule-path>] [<target-directory>]
function gtws_repo_clone {
local baserpath=${1%/}
local repo=$2
local branch=$3
local basesmpath=$4
local rname=${5:-${repo%.git}}
local rpath="${baserpath}/${repo}"
local origpath=${PWD}
if [[ ${rpath} != *:* ]]; then
if [ ! -d "${rpath}" ]; then
rpath="${rpath}.git"
fi
fi
if [ -z "${basesmpath}" ]; then
basesmpath="${baserpath}"
fi
debug_print "${FUNCNAME}: cloning ${baserpath} - ${repo} : ${branch} into ${GTWS_WSNAME}/${rname} submodules: ${basesmpath}"
# Main repo
#git clone --recurse-submodules -b "${branch}" "${rpath}" || die "failed to clone ${rpath}:${branch}" || return 1
git clone -b "${branch}" "${rpath}" ${rname} || die "${FUNCNAME}: failed to clone ${rpath}:${branch}" || return 1
# Update submodules
cd "${rname}" || die "${FUNCNAME}: failed to cd to ${rpath}" || return 1
gtws_submodule_clone "${basesmpath}" || return 1
cd "${origpath}" || die "${FUNCNAME}: Failed to cd to ${origpath}" || return 1
# Copy per-repo settings, if they exist
gtws_cpdot "${baserpath%/git}/extra/repo/${rname}" "${origpath}/${rname}"
# Extra files
for i in ${GTWS_FILES_EXTRA}; do
local esrc=
IFS=':' read -ra ARR <<< "$i"
if [ -n "${ARR[1]}" ]; then
dst="${rname}/${ARR[1]}"
else
dst="${rname}/${ARR[0]}"
fi
if [ -n "${GTWS_REMOTE_IS_WS}" ]; then
esrc="${baserpath}/${dst}"
else
esrc="${baserpath%/git}"
fi
gtws_rcp "${esrc}/${ARR[0]}" "${dst}"
done
}
# gtws_project_clone_default ${GTWS_ORIGIN} ${GTWS_PROJECT} ${GTWS_PROJECT_VERSION} ${GTWS_WSNAME} [${SUBMODULE_BASE}]
#
# Clone a version of a project into ${GTWS_WSPATH} (which is the current working directory). This is the default version of this that clones <origin>/<project>/<version>/*
function gtws_project_clone_default {
local origin=$1
local project=$2
local version=$3
local name=$4
local basesmpath=$5
local opv=$(gtws_opvn "${origin}" "${project}" "${version}" "${name}")
local wspath=${PWD}
local repos=
local -A branches
if [ -z "${GTWS_PROJECT_REPOS}" ]; then
for i in "${opv}"/*; do
repos="$(basename $i) $repos"
branches[$i]=${version}
done
else
for i in ${GTWS_PROJECT_REPOS}; do
IFS=':' read -ra ARR <<< "$i"
repos="${ARR[0]} $repos"
if [ -n "${ARR[1]}" ]; then
branches[${ARR[0]}]=${ARR[1]}
else
branches[${ARR[0]}]=${version}
fi
done
fi
if [ -z "${basesmpath}" ] || [ ! -d "${basesmpath}" ]; then
basesmpath="${opv}"
fi
for repo in ${repos}; do
gtws_repo_clone "${opv}" "${repo}" "${branches[${repo}]}" "${basesmpath}"
done
# Copy per-WS settings, if they exist
gtws_cpdot "${opv%/git}/extra/ws" "${wspath}"
}
# gtws_repo_setup ${wspath} ${repo_path}
#
# The project can define gtws_repo_setup_local taking the same args to do
# project-specific setup. It will be called last.
#
# Post-clone setup for an individual repo
function gtws_repo_setup {
local wspath=$1
local rpath=$2
local savedir="${PWD}"
if [ ! -d "${rpath}" ]; then
return 0
fi
cd "${rpath}/src" 2>/dev/null \
|| cd ${rpath} \
|| die "Couldn't cd to ${rpath}" || return 1
maketags ${GTWS_MAKETAGS_OPTS} > /dev/null 2> /dev/null &
cd ${wspath} || die "Couldn't cd to ${wspath}" || return 1
mkdir -p "${wspath}/build/$(basename ${rpath})"
cd "${savedir}"
if [ -n "$(declare -F | grep "\<gtws_repo_setup_local\>")" ]; then
gtws_repo_setup_local "${wspath}" "${rpath}" \
|| die "local repo setup failed" || return 1
fi
}
# gtws_project_setup${GTWS_WSNAME} ${GTWS_ORIGIN} ${GTWS_PROJECT} ${GTWS_PROJECT_VERSION}
#
# The project can define gtws_project_setup_local taking the same args to do
# project-specific setup. It will be called last.
#
# Post clone setup of a workspace in ${GTWS_WSPATH} (which is PWD)
function gtws_project_setup {
local wsname=$1
local origin=$2
local project=$3
local version=$4
local wspath=${PWD}
local opv=$(gtws_opvn "${origin}" "${project}" "${version}" "placeholder")
for i in "${wspath}"/*; do
gtws_repo_setup "${wspath}" "${i}"
done
mkdir "${wspath}"/install
mkdir "${wspath}"/chroots
mkdir "${wspath}"/patches
if [ -n "$(declare -F | grep "\<gtws_project_setup_local\>")" ]; then
gtws_project_setup_local "${wsname}" "${origin}" "${project}" \
"${version}" || die "local project setup failed" || return 1
fi
}
# load_rc /path/to/workspace
#
# This should be in the workspace-level gtwsrc file
# Recursively load all RC files, starting at /
function load_rc {
local BASE=$(readlink -f "${1}")
# Load base RC first
debug_print "load_rc: Enter + Top: ${BASE}"
source "${HOME}"/.gtwsrc
while [ "${BASE}" != "/" ]; do
if [ -f "${BASE}"/.gtwsrc ]; then
load_rc "$(dirname ${BASE})"
debug_print "\tLoading ${BASE}/.gtwsrc"
source "${BASE}"/.gtwsrc
return 0
fi
BASE=$(readlink -f $(dirname "${BASE}"))
done
# Stop at /
return 1
}
# clear_env
#
# Clear the environment of GTWS_* except for the contents of GTWS_SAVEVARS.
# The default values for GTWS_SAVEVARS are below.
function clear_env {
local savevars=${GTWS_SAVEVARS:-"LOC PROJECT PROJECT_VERSION VERBOSE WSNAME"}
local verbose="${GTWS_VERBOSE}"
debug_print "savevars=$savevars"
# Reset prompt
if [ -n "${GTWS_SAVEPS1}" ]; then
PS1="${GTWS_SAVEPS1}"
fi
if [ -n "${GTWS_SAVEPATH}" ]; then
export PATH=${GTWS_SAVEPATH}
fi
unset LD_LIBRARY_PATH
unset PYTHONPATH
unset PROMPT_COMMAND
unset CDPATH
unset SDIRS
# Save variables
for i in ${savevars}; do
SRC=GTWS_${i}
DST=SAVE_${i}
debug_print "\t $i: ${DST} = ${!SRC}"
eval ${DST}=${!SRC}
done
# Clear GTWS evironment
for i in ${!GTWS*} ; do
if [ -n "${verbose}" ]; then
echo -e "unset $i" >&2
fi
unset $i
done
# Restore variables
for i in ${savevars}; do
SRC=SAVE_${i}
DST=GTWS_${i}
if [ -n "${verbose}" ]; then
echo -e "\t $i: ${DST} = ${!SRC}" >&2
fi
if [ -n "${!SRC}" ]; then
eval export ${DST}=${!SRC}
fi
unset ${SRC}
done
}
# save_env ${file} ${nukevars}
#
# Save the environment of GTWS_* to the give file, except for the variables
# given to nuke. The default values to nuke are given below.
function save_env {
local fname=${1}
local nukevars=${2:-"SAVEPATH ORIGIN WS_GUARD LOC SAVEPS1"}
debug_print "nukevars=$nukevars"
for i in ${!GTWS*} ; do
for j in ${nukevars}; do
if [ "${i}" == "GTWS_${j}" ]; then
debug_print "skipping $i"
continue 2
fi
done
debug_print "saving $i"
echo "export $i=\"${!i}\"" >> "${fname}"
done
}
# gtws_tmux_session_name ${PROJECT} ${VERSION} ${WSNAME} sesname
#
# Result will be in local variable sesname Or:
#
# sesname = $(gtws_tmux_session_name ${PROJECT} ${VERSION} ${WSNAME})
#
# Result will be in local variable sesname
#
# Get the tmux session name for a given workspace
function gtws_tmux_session_name {
local project=$1
local version=$2
local wsname=$3
local __resultvar=$4
local sesname="${project//./_}/${version//./_}/${wsname//./_}"
if [[ "$__resultvar" ]]; then
eval $__resultvar="'$sesname'"
else
echo "$sesname"
fi
}
# gtws_tmux_session_info ${SESSION_NAME} running attached
#
# Determine if a session is running, and if it is attached
#
# Result will be in local variables running and attached
#
# Test with:
# if $running ; then
# echo "is running"
# fi
function gtws_tmux_session_info {
local ses_name=$1
local __result_running=$2
local __result_attached=$3
local __num_ses=$(tmux ls | grep "^${ses_name}" | wc -l)
local __attached=$(tmux ls | grep "^${ses_name}" | grep attached)
echo "$ses_name ses=${__num_ses}"
if [[ "$__result_running" ]]; then
if [ "${__num_ses}" != "0" ]; then
eval $__result_running="true"
else
eval $__result_running="false"
fi
fi
if [[ "$__result_attached" ]]; then
if [ -n "${__attached}" ]; then
eval $__result_attached="true"
else
eval $__result_attached="false"
fi
fi
}
# gtws_tmux_kill ${BASENAME}
#
# Kill all sessiont matching a pattern
function gtws_tmux_kill {
local basename=$1
local old_sessions=$(tmux ls 2>/dev/null | fgrep "${basename}" | cut -f 1 -d:)
for session in ${old_sessions}; do
tmux kill-session -t "${session}"
done
}
# gtws_tmux_cleanup
#
# Clean up defunct tmux sessions
function gtws_tmux_cleanup {
local old_sessions=$(tmux ls 2>/dev/null | egrep "^[0-9]{14}.*[0-9]+\)$" | cut -f 1 -d:)
for session in ${old_sessions}; do
tmux kill-session -t "${session}"
done
}
# gtws_tmux_attach ${SESSION_NAME}
#
# Attach to a primary session. It will remain after detaching.
function gtws_tmux_attach {
local ses_name=$1
tmux attach-session -t "${ses_name}"
}
# gtws_tmux_slave ${SESSION_NAME}
#
# Create a secondary session attached to the primary session. It will exit it
# is detached.
function gtws_tmux_slave {
local ses_name=$1
# Session is is date and time to prevent conflict
local session=`date +%Y%m%d%H%M%S`
# Create a new session (without attaching it) and link to base session
# to share windows
tmux new-session -d -t "${ses_name}" -s "${session}"
# Attach to the new session
gtws_tmux_attach "${session}"
# When we detach from it, kill the session
tmux kill-session -t "${session}"
}
function cdorigin() {
if [ -n "$(declare -F | grep "gtws_project_cdorigin")" ]; then
gtws_project_cdorigin $@
else
gtws_cdorigin $@
fi
}
function gtws_get_origin {
local opv=$1
local target=$2
local __origin=
local __resultvar=$3
# If it's a git repo with a local origin, use that.
__origin=$(git config --get remote.origin.url)
if [ ! -d "${__origin}" ]; then
__origin="${__origin}.git"
fi
if [ ! -d "${__origin}" ]; then
# Try to figure it out
if [ ! -d "${opv}" ]; then
die "No opv for $target" || return 1
fi
find_git_repo "${opv}" "${target}" __origin || return 1
fi
if [[ "$__resultvar" ]]; then
eval $__resultvar="'$__origin'"
else
echo "$__origin"
fi
}
function gtws_cdorigin() {
local opv=$(gtws_opvn "${GTWS_ORIGIN}" "${GTWS_PROJECT}" "${GTWS_PROJECT_VERSION}" "${GTWS_WSNAME}")
local gitdir=""
local target=""
if [ -n "$1" ]; then
target="$@"
else
git_top_dir gitdir || return 1
target=$(basename $gitdir)
fi
gtws_get_origin $opv $target origin || return 1
cd "${origin}"
}
# Copy files to another machine in the same workspace
function wsrcp {
local target="${!#}"
local length=$(($#-1))
local base=${PWD}
if [ -z "${1}" -o -z "${2}" ]; then
echo "usage: ${FUNCNAME} <path> [<path>...] <target>"
return 1
fi
for path in "${@:1:$length}"; do
gtws_rcp "${path}" "${target}:${base}/${path}"
done
}
# Override "cd" inside the workspace to go to GTWS_WSPATH by default
function cd {
if [ -z "$@" ]; then
cd "${GTWS_WSPATH}"
else
builtin cd $@
fi
}
# Generate diffs/interdiffs for changes and ship to WS on other boxes
function gtws_interdiff {
local targets=$@
local target=
local savedir=${PWD}
local topdir=$(git_top_dir)
local repo=$(basename ${topdir})
local mainpatch="${GTWS_WSPATH}/patches/${repo}-full.patch"
local interpatch="${GTWS_WSPATH}/patches/${repo}-incremental.patch"
if [ -z "${targets}" ]; then
echo "Usage: ${FUNCNAME} <targethost>"
die "Must give targethost" || return 1
fi
cd "${topdir}"
if [ -f "${mainpatch}" ]; then
git diff | interdiff "${mainpatch}" - > "${interpatch}"
fi
git diff > "${mainpatch}"
for target in ${targets}; do
gtws_rcp "${mainpatch}" "${interpatch}" \
"${target}:${GTWS_WSPATH}/patches"
done
cd "${savedir}"
}
function gtws_debug {
local cmd=$1
if [ -z "${cmd}" ]; then
echo "Must give a command"
echo
die "${FUNCNAME} <cmd-path>" || return 1
fi
local cmdbase=$(basename $cmd)
local pid=$(pgrep "${cmdbase}")
ASAN_OPTIONS="abort_on_error=1" cgdb ${cmd} ${pid}
}
# remote_cmd "${target}" "${command}" output
#
# Result will be in local variable output Or:
#
# output = $(remote_cmd "${target}" "${command}")
#
# Result will be in local variable output
#
# Run a command remotely and capture sdtout. Make sure to quote the command
# appropriately.
remote_cmd() {
local target=$1
local cmd=$2
local __resultvar=$3
local output=
if [ -z "${GTWS_VERBOSE}" ]; then
output=$(ssh "${target}" "${cmd}" 2>/dev/null)
else
output=$(ssh "${target}" "${cmd}")
fi
local ret=$?
if [[ "$__resultvar" ]]; then
eval $__resultvar="'$output'"
else
echo "${output}"
fi
return ${ret}
}
1 条评论