mirror of
https://github.com/LCTT/TranslateProject.git
synced 2025-01-04 22:00:34 +08:00
1151 lines
41 KiB
Markdown
1151 lines
41 KiB
Markdown
[#]: collector: (lujun9972)
|
||
[#]: translator: (lxbwolf)
|
||
[#]: reviewer: ( )
|
||
[#]: publisher: ( )
|
||
[#]: url: ( )
|
||
[#]: subject: (Manage complex Git workspaces with Great Teeming Workspaces)
|
||
[#]: via: (https://opensource.com/article/20/2/git-great-teeming-workspaces)
|
||
[#]: author: (Daniel Gryniewicz https://opensource.com/users/dang)
|
||
|
||
使用 Great Teeming Workspaces 管理复杂的 Git 工作空间
|
||
======
|
||
GTWS 是一系列脚本,这些脚本能让我们在开发环境中管理一个有不同项目和不同版本的工程时变得更简单。
|
||
![Coding on a computer][1]
|
||
|
||
[GTWS][2] 是一个 Git 的复杂工作空间管理工具包,可以让我们在开发环境中管理一个有不同项目和不同版本的工程时变得更简单。
|
||
|
||
有点像 Python 的 [venv][3],但不是为 Python 语言准备的。GTWS 用来处理多项目的多个版本的工作空间。你可以很容易地创建、更新、进入和离开工作空间,每个项目或版本的组合(最多)有一个本地的 origin,用来与 upstream 同步 — 其余的所有工作空间都从本地的 origin 更新。
|
||
|
||
### 部署
|
||
|
||
```
|
||
${GTWS_ORIGIN}/<project>/<repo>[/<version>]
|
||
${GTWS_BASE_SRCDIR}/<project>/<version>/<workspacename>/{<repo>[,<repo>...]}
|
||
```
|
||
|
||
源目录的每一级(包括全局的 home 目录)可以包含一个 **.gtwsrc** 文件,这个文件中维护与当前级相关的设置和 bash 代码。每一级的配置会覆盖上一级。
|
||
|
||
### 安装
|
||
|
||
用下面的命令检出 GTWS:
|
||
|
||
```
|
||
`git clone https://github.com/dang/gtws.git`
|
||
```
|
||
|
||
配置你的 **${HOME}/.gtwsrc**。它应该包含 **GTWS_ORIGIN**,也可以再包含 **GTWS_SETPROMPT**。
|
||
|
||
把仓库目录加到环境变量中:
|
||
|
||
```
|
||
`export PATH="${PATH}:/path/to/gtws`
|
||
```
|
||
|
||
### 配置
|
||
|
||
通过级联 **.gtwsrc** 文件来进行配置。它从根目录向下遍历,会执行在每级目录中找到的 **.gtwsrc** 文件。下级目录的文件会覆盖上一级。
|
||
|
||
在你最上层的文件 **~/.gtws/.gtwsrc** 中进行如下设置:
|
||
|
||
* **GTWS_BASE_SRCDIR:** 所有项目源文件目录树的 base。默认为 **$HOME/src**。
|
||
* **GTWS_ORIGIN:** 指定 origin git 目录树的路径。默认为 **$HOME/origin**。
|
||
* **GTWS_SETPROMPT:** 可选配置。如果配置了这个参数,shell 提示符会有工作空间的名字。
|
||
* **GTWS_DEFAULT_PROJECT:** 不指定项目或项目未知时默认的项目名。如果不指定,使用命令行时必须指明项目。
|
||
* **GTWS_DEFAULT_PROJECT_VERSION:** 检出的默认版本。默认为 **master**。
|
||
|
||
在每个项目的根目录进行以下设置:
|
||
|
||
* **GTWS_PROJECT:** 项目的名字(和 base 目录)。
|
||
* **gtws_project_clone:** 这个函数用于克隆一个项目的指定版本。如果未定义,它会假定项目的 origin 对每一个版本都有一个单独的目录,这样会导致克隆一堆 Git 仓库。
|
||
* **gtws_project_setup:** 在克隆完所有的仓库后,可以选择是否调用这个函数,调用后可以对项目进行必要的配置,如在 IDE 中配置工作空间。
|
||
|
||
在项目版本级进行以下设置:
|
||
|
||
* **GTWS_PROJECT_VERSION:** 项目的版本。用于正确地从 origin 拉取代码。类似 Git 中的分支名字。
|
||
|
||
下面这些参数可以在目录树的任意地方进行配置,如果能生效,它们可以被重写多次:
|
||
|
||
* **GTWS_PATH_EXTRA:** 这些是工作空间中加到路径后的额外的路径元素。
|
||
* **GTWS_FILES_EXTRA:** 这些是不在版本控制内,但应该在工作空间中被检出的额外的文件。这些文件包括 **.git/info/exclude**,每个文件都与仓库的 base 相关联。
|
||
|
||
### origin 目录
|
||
|
||
**GTWS_ORIGIN** (大部分脚本中)指向拉取和推送的原始 Git 检出目录。
|
||
|
||
**${GTWS_ORIGIN}** 部署:
|
||
|
||
* **/<project>**
|
||
* 这是一个项目的仓库 base。
|
||
* 如果指定了 **gtws_project_clone**,你可以配置任意的部署路径。
|
||
* 如果没有指定 **gtws_project_clone**,这个路径下必须有个名为 **git** 的子目录,且 **git** 目录下有一系列用来克隆的裸 Git 仓库。
|
||
|
||
### 工作流示例
|
||
|
||
假设你有一个项目名为 **Foo**,它的 upstream 为 **github.com/foo/foo.git**。这个仓库有个名为 **bar** 的子模块,它的 upstream 是 **github.com/bar/bar.git**。Foo 项目在 master 分支开发,使用稳定版本的分支。
|
||
|
||
为了能在 Foo 中使用 GTWS,你首先要配置目录结构。本例中假设你使用默认的目录结构。
|
||
|
||
* 配置你最上层的 **.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**(译注:这个地方原文有误,不加 `-s` 参数会报错)
|
||
* 上面命令会创建 **~/origin/foo/git/foo.git** 和 **~/origin/foo/submodule/bar.git**。
|
||
* 以后的克隆操作会从这些 origin 而不是 upstream 克隆。
|
||
* 现在可以删除工作空间了。
|
||
|
||
到现在为止,Foo 的 master 分支的工作可以结束了。假设你现在想修复一个 bug,名为 **bug1234**。你可以脱离你当前的工作空间为修复这个 bug 单独创建一个工作空间,之后在新创建的工作空间中开发。
|
||
|
||
* 进入版本目录,创建一个新的工作空间:
|
||
* **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** 文件中设置的环境。它也把你工作空间的 base 路径加入到了 CD,因此你可以从 base 路径 **cd** 到相关的目录中。
|
||
* 现在你可以修复 bug1234了,构建、测试、提交你的修改。当你可以把代码推送到 upstream 时,执行下面的命令:
|
||
**cd foo
|
||
wspush**
|
||
* **wspush** 会把代码推送到与你工作空间相关的分支 — 先推送到本地的 origin,再推送到 upstream。
|
||
* 当 upstream 有修改时,你可以用下面的命令同步到本地:
|
||
**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}
|
||
}
|
||
```
|
||
|
||
--------------------------------------------------------------------------------
|
||
|
||
via: https://opensource.com/article/20/2/git-great-teeming-workspaces
|
||
|
||
作者:[Daniel Gryniewicz][a]
|
||
选题:[lujun9972][b]
|
||
译者:[lxbwolf](https://github.com/lxbwolf)
|
||
校对:[校对者ID](https://github.com/校对者ID)
|
||
|
||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||
|
||
[a]: https://opensource.com/users/dang
|
||
[b]: https://github.com/lujun9972
|
||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/code_computer_laptop_hack_work.png?itok=aSpcWkcl (Coding on a computer)
|
||
[2]: https://github.com/dang/gtws
|
||
[3]: https://docs.python.org/3/library/venv.html
|