使用此脚本管理多个 Git 仓库

GTWS 是一组脚本,可以轻松地为不同的项目和项目的不同版本设置开发环境。
101 位读者喜欢这篇文章。
Coding on a computer

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 并根据需要进行更改。
  • 创建并设置 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/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 而不是从上游克隆。
    • 此工作区现在可以删除。

此时,可以在 Foo 的 master 分支上完成工作。假设您想修复一个名为 bug1234 的错误。您可以为此工作创建一个工作区,以使其与您正在处理的任何其他内容隔离,然后在该工作区内工作。

  • 转到版本目录,并创建一个新的工作区
    • cd ~/src/foo/master

      mkws bug1234
    • 这将创建 bug1234/,并在其中检出 Foo(及其子模块 bar)并创建 build/foo 用于构建它。
  • 进入工作区。有两种方法可以执行此操作
    • 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。
  • 当您完成使用工作区后,可以使用 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}
}

 

接下来阅读什么
标签
User profile image.
自 90 年代中期以来,我一直是开源的用户和贡献者。现在,我在 Red Hat 工作,负责 Ceph 和 Ganesha 项目。

1 条评论

这篇文章内容详尽,真的很有帮助

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 许可。
© . All rights reserved.