File: //proc/thread-self/root/bin/modman
#!/usr/bin/env bash
# Module Manager
#
# Copyright 2009 Colin Mollenhour
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# System Requirements:
# - bash
# - filesystem and project or web server must support symlinks
# - The following common utilities must be locatable in your $PATH
# grep (POSIX), find, ln, cp, basename, dirname, readlink
version="1.12"
script=${0##*/}
usage="\
Module Manager (v$version)
Global Commands:
$script <command> [<options>]
------------------------------
init [basedir] initialize the pwd (or [basedir]) as the modman deploy root
list [--absolute] list all valid modules that are currently checked out
status show VCS 'status' command for all modules
incoming show new VCS remote changesets for all VCS-based modules
update-all update all modules that are currently checked out
deploy-all deploy all modules (no VCS interaction)
repair rebuild all modman-created symlinks (no updates performed)
clean clean up broken symlinks (run this after deleting a module)
automodman loops through module directory:
- asks to include files & directories in new modman file
- does not create symlinks
--help display this help message
--tutorial show a brief tutorial on using modman
--version display the modman script's version
[--force] overwrite existing files, ignore untrusted cert warnings
[--no-local] skip processing of modman.local files
[--no-clean] skip cleaning of broken symlinks
[--no-shell] skip processing @shell lines
Module Commands:
$script <command> [<module>] [<options>] [[--] <VCS options>]
------------------------------
checkout <src> checkout a new modman-compatible module using subversion
clone <src> checkout a new modman-compatible module using git clone
hgclone <src> checkout a new modman-compatible module using hg clone
link <path> link to a module that already exists on the local filesystem
update update module using the appropriate VCS
deploy deploy an existing module (VCS not required or used)
undeploy remove an existing module without deleting the files
skip prevent a module from deploying with deploy-all
unskip re-enable a module to be deployed with deploy-all
remove remove a module (DELETES MODULE FILES)
[--force] overwrite existing files, ignore untrusted cert warnings
[--no-local] skip processing of modman.local files
[--no-clean] skip cleaning of broken symlinks
[--no-shell] skip processing @shell lines
[--basedir <path>] on checkout/clone, specifies a base for module deployment
[--copy] deploy by copying files instead of symlinks
[<VCS options>] specify additional parameters to VCS checkout/clone
"
tutorial="\
Deploying a modman module:
------------------------------
The 'checkout' and 'clone' commands are used to checkout new modules from a
repository (subversion and git, respectively). These commands support additional
arguments which will be passed to the 'svn checkout' and 'git clone' commands.
This allows you to for example specify a --username (svn) or a --branch (git).
See 'svn help checkout' and 'git help clone' for a full list of options.
Both svn:externals and git submodules will be automatically included.
To link to modules that already exist on the local file system you can use
the 'link' command. The 'update' command will be supported the same as if the
module had been created using the checkout or clone command. For example:
$ modman link ~/projects/My_Module
$ modman update My_Module
By default, if a module being checked out contains symlink mappings that
conflict with existing files, an error will be thrown. Use --force to cause
any existing files or directories that conflict to be removed. Be careful!
Writing modman modules:
------------------------------
Each module should contain a file named \"modman\" which defines which files
go where relative to the directory where modman was initialized.
==== Start example modman file ====
# Comments are supported, begin a line with a hash
code app/code/local/My/Module/
design app/design/frontend/default/default/mymodule/
# Source and destination can have different names.
en_US.csv app/locale/en_US/My_Module.csv
# Destination file name is not required if the same as the source.
My_Module.xml app/etc/modules/
# Leave out the destination entirely to assume the same path as the source.
lib/Somelib
# Bash extended glob patterns supported.
skin/css/* skin/frontend/base/default/css/
skin/js/* skin/frontend/base/default/js/
# Import another modman module
@import modules/Fooman_Speedster
# Execute a command on the shell
@shell rm -rf \$PROJECT/var/cache/*
==== End example modman file ====
Globbing:
------------------------------
Bash globbing in the modman file is supported. The result is that each file or
directory that matches the globbing pattern will get its own symlink in the
specified location when the module is deployed.
Importing modules:
------------------------------
One modman file can import another modman module using @import and the path to
the imported module's root relative to the current module's root. Imported
modules deploy to the same path as checked out modules so @import can be used
to include common modules that are just stored in a subdirectory, or are
included via svn:externals or git submodules. Example:
> svn propget svn:externals .
^/modules/Fooman_Speedster modules/Fooman_Speedster
In this example, modules/Fooman_Speedster would contain it's own modman file
and could therefore be imported by any other module or checked out by itself.
Shell commands:
------------------------------
Actions to be taken any time one of the checkout, clone, update, deploy,
update-all or repair commands are used can be defined with the @shell
directive. The rest of the line after @shell will be piped to a new bash
shell with the working directory being the module's root. The following
environment variables will be made available:
PROJECT The path of the project base dir (where modman was initialized)
MODULE The current module's path
Standalone mode:
------------------------------
The modman script can be used without a VCS by placing the module directory in
the proper location and running \"modman deploy <module>\". The root of the
module must be located at <project_root>/.modman/<module_name>/ and it must
contain a modman file.
Shortcut:
------------------------------
Modman can be run without the module name if the current directory's realpath
is within a module's path. E.g. \"modman update\" or \"modman deploy\".
Local 'modman' file:
------------------------------
You can create a modman file named \"modman.local\" which will also get applied
like the standard modman file. The intended purpose of this file is to allow you
to specify additional directives while excluding them from version control. The
\"--no-local\" command option will prevent these files from being processed.
Custom 'clean' script:
------------------------------
On some systems the dead link search can be brutally slow. You can create a
custom clean script at .modman/.clean which will override the default clean
script. Here is an example of a custom clean script that ignores the \"media\"
and \"var\" directories:
echo Cleaning dead links in \$PWD
find -L . -mount \\( -path ./media -o -path ./var \\) -prune -o -type l -exec rm {} \;
Author:
------------------------------
Colin Mollenhour
http://colin.mollenhour.com/
colin@mollenhour.com
"
# Action is first argument
action=$1; shift
# Fix GNU inconsistencies with Mac
case $OSTYPE in
darwin*|*bsd*)
stat_type="bsd_stat"
readlink_missing="echo" # readlink -m not supported
;;
*)
stat_type="stat -c %F"
readlink_missing="readlink -m"
;;
esac
bsd_stat ()
{
case "$(stat -f %T "$1")" in
"*") echo file;;
"@") echo symlink;;
"/") echo directory;;
*) echo unknown_type;;
esac
}
pager=${PAGER:-$(which pager &> /dev/null)}
if [ -z "$pager" ]; then
pager=less
fi
###########################
# Handle "init" command, simply create .modman directory
if [ "$action" = "init" ]; then
basedir=$1
[[ -n "$basedir" && ! -d "$basedir" ]] && { echo "$basedir is not a directory."; exit 1; }
mkdir .modman || { echo "Could not create .modman directory" && exit 1; }
if [ -n "$basedir" ]; then
echo "$basedir" > .modman/.basedir
basedir="with basedir at $basedir"
fi
echo "Initialized Module Manager at $(pwd) $basedir"
exit 0
###########################
# Handle "--help" command
elif [ "$action" = "--help" -o "$action" = "" ]; then
echo -e "$usage"; exit 0
###########################
# Handle "--tutorial" command
elif [ "$action" = "--tutorial" ]; then
echo -e "$tutorial" | $pager; exit 0
###########################
# Handle "--version" command
elif [ "$action" = "--version" ]; then
echo "Module Manager version: $version"; exit 0
fi
#############################################################
# Echo in bold font if stdout is a terminal
ISTTY=0; if [ -t 1 ]; then ISTTY=1; fi
bold () { if [ $ISTTY -eq 1 ]; then tput bold; fi; }
unbold () { if [ $ISTTY -eq 1 ]; then tput sgr0; fi; }
echo_b ()
{
if [ "$1" = "-e" ]; then
echo -e "$(bold)$2$(unbold)"
else
echo "$(bold)$1$(unbold)"
fi
}
#############################################################
# Colors in terminal
yellow () { if [ $ISTTY -eq 1 ]; then tput setaf 3; fi; }
red () { if [ $ISTTY -eq 1 ]; then tput setaf 1; fi; }
#############################################################
# Handling warning messages
warning ()
{
echo -e "$(bold)$(yellow)WARNING:$(unbold) "$1
}
#############################################################
# Handling Error messages
error ()
{
echo -e "$(bold)$(red)ERROR:$(unbold) "$1
}
#############################################################
# Check for existence of a module directory and modman file
require_wc ()
{
if ! [ -d "$mm/$1" ]; then
error "$1 has not been checked out."; return 1
fi
if ! [ -r "$mm/$1/modman" ]; then
error "$1 does not contain a \"modman\" module description file."; return 1
fi
return 0
}
###############################################################
# Removes dead symlinks
remove_dead_links ()
{
if [ $NOCLEAN -eq 1 ]; then
return 0
fi
# Support use of a custom clean script
if [ -r "$mm/.clean" ]; then
( cd "$root"; $SHELL "$mm/.clean" )
else
# Use -exec rm instead of -delete to avoid bug in Darwin. -Thanks Vinai!
( cd "$root"; find -L . -mount -type l -exec rm {} \; )
fi
return $?
}
###############################################################
# Removes all .basedir files from submodules
remove_basedirs ()
{
local module=$1
find "$mm/$module" -name .basedir | grep -FZv "$mm/$module/.basedir" | xargs -0 rm -f
}
###############################################################
# Reads the base directory for a module
# Return value is blank, or relative path ending with /
get_basedir ()
{
local module_dir=$1
local basedir=''
if [ -r "$module_dir/.basedir" ]; then
basedir=$(cat "$module_dir/.basedir" | grep -v '^#')
if [ -n "$basedir" ]; then
basedir=${basedir%%/}/
fi
fi
echo -n "$basedir"
}
###############################################################
# Writes a file, setting the base directory for a module
set_basedir ()
{
local module_dir=$1
local basedir=${2##/}
basedir=${basedir%%/}
if [ -n "$basedir" ]; then
echo -e "# This file was created by modman. Module base directory:\n$basedir" \
> "$module_dir/.basedir"
if ! [ $? ]; then
error "Could not write to file: $module_dir/.basedir."
return 1
fi
fi
return 0
}
get_skipped ()
{
local module=$1
if [ -f "$root/.modman-skip" ]; then
for line in $(grep -v -e '^#' "$root/.modman-skip"); do
if [ $line == $module ]; then
return 0
fi
done
fi
return 1
}
set_skipped ()
{
local module=$1
local skip=$2
local SKIP_FILE="$root/.modman-skip"
if ! [ -f "$SKIP_FILE" ]; then
echo "# This file was created by modman. The following modules won't be deployed by deploy-all." \
> "$root/.modman-skip"
if ! [ $? ]; then
error "Could not write to file: $SKIP_FILE"
return 1
fi
fi
if [ "$skip" = 1 ]; then
if get_skipped $module; then
echo "Module $module already skipped."
exit 1
else
echo $module >> "$SKIP_FILE"
echo "Module $module added to skip list."
fi
else
grep -v "^$module$" "$SKIP_FILE" > "$SKIP_FILE.tmp"; mv "$SKIP_FILE.tmp" "$SKIP_FILE"
echo "Module $module removed from skip list."
fi
return 0
}
get_abs_filename() {
if [ -d "$(dirname "$1")" ]; then
echo "$(cd "$(dirname "$1")/$(dirname "$(readlink "$1")")" && pwd)/$(basename "$1")"
fi
}
remove_module_links ()
{
echo "Removing links for module $module."
local module_dir="$mm/$module"
for line in $(find $root -type l); do
if [[ $(get_abs_filename "$line") =~ ^"$module_dir".* ]]; then
rm "$line"
fi
done
return 0
}
################################################################################
# Reads a modman file and does the following:
# Creates the symlinks as described
# Imports external modman files (@import)
# Runs shell commands (@shell)
apply_modman_file ()
{
local module=$1
local module_dir=$(dirname "$module")
local basedir=$(get_basedir "$module_dir")
local relpath=${module:$((${#mmroot}+1))}
# Use argument if module doesn't have a .basedir file
if [ -z "$basedir" ]; then
basedir=$2
fi
# while loop should not read from stdin or else @shell scripts cannot get stdin
IFS=$'\r\n'
for line in $(grep -v -e '^#' -e '^\s*$' "$module"); do
IFS=$' \t\n'
# Split <target> <real>
read target real <<< $line
# Assume target == real if only one path is given
if [ -z "$real" ]; then
real="$target"
fi
# Sanity check for empty data
if [ -z "$target" -o -z "$real" ]; then
error "Invalid input in modman file ($relpath):\n $line"
return 1
fi
# Import other module definitions (e.g. git submodules, svn:externals, etc..)
if [ "$target" = "@import" ]; then
# check if base defined, create and save base to .basedir file
read import_path import_base <<< $real
import=$module_dir/${import_path%/}/modman
if ! [ -r "$import" ]; then
relimport=${import:$((${#mmroot}+1))}
error "modman file not found ($relimport):\n $line"
return 1
fi
if [ -z "$import_base" ]; then
import_base=${basedir%%/}
else
import_base=${import_base##/}
import_base=${basedir}${import_base%%/}
if ! [ -d "$root/$import_base" ]; then
if ! mkdir -p "$root/$import_base"; then
error "Could not create import base directory: $import_base"
return 1
fi
echo "Created import base directory: $import_base"
fi
if ! set_basedir "$module_dir/$import_path" "$import_base"; then
return 1
fi
fi
apply_modman_file "$import" "$import_base/" || return 1
continue
fi
# Run commands on the shell!
# temporary file is workaround so that script can receive stdin
if [ "$target" = "@shell" ]; then
[ $NOSHELL -eq 0 ] || continue
cd "$module_dir"
export PROJECT=$root/${basedir%%/}
export MODULE=$module_dir
shell_tmp=$(mktemp "$mm/.tmp.XXXXXXX")
echo "($real)" > "$shell_tmp"
source "$shell_tmp"
rm -f "$shell_tmp"
continue
fi
# Create symlink to target
local src=$module_dir/$target
local dest=$root/${basedir}${real%/}
dest=${dest/\/\//\/} # Replace // with /
dest=${dest%/} # Strip trailing /
# Handle globbing (extended globbing enabled)
shopt -s extglob
if ! [ -e "$src" ] && [ $(ls $src 2> /dev/null | wc -l) -gt 0 ]; then
for _src in $src; do
apply_path "$_src" "$dest/${_src##*/}" "$target" "${real%/}/${_src##*/}" "$line" || return 1
done
continue
fi # end Handle globbing
# Handle aliases that do not exist
if ! [ -e "$src" ];
then
warning "Target does not exist ($relpath):\n $line"
continue
fi
# Allow destination to be a dir when src is a file
if [ -f "$src" ] && [ -d "$dest" -o "/" = ${real: -1} ]; then
dest="$dest/$(basename "$src")"
fi
apply_path "$src" "$dest" "$target" "$real" "$line" || return 1
done
return 0
}
###########################################################################
# Creates a symlink or copies a file (with lots of error-checking)
apply_path ()
{
local src="$1"; local dest="$2"; local target="$3"; local real="$4"; local line="$5"
# Make symlinks relative
if [ $COPY -eq 0 ]; then
local realpath=$($readlink_missing "${dest%/*}"); local commonpath=""
if [ "${dest%/*}" == "${realpath}" ]; then
# Use modman root as common path if destination is not itself a symlinked path
commonpath="${mmroot%/}"
else
# Search for longest common path as symlink target
for ((i=0; i<${#dest}; i++)); do
if [[ "${dest:i:1}" != "${realpath:i:1}" ]]; then
commonpath="${dest:0:i}"
commonpath="${commonpath%/*}"
break
fi
done
fi
# Replace destination (less common path) with ../*
if [ "$commonpath" != "" ]; then
local reldest="${dest#$commonpath/}"
if [ "$reldest" != "${reldest%/*}" ]; then
reldest=$(IFS=/; for d in ${reldest%/*}; do echo -n '../'; done)
else
reldest=""
fi
src="${reldest}${src#$commonpath/}"
fi
fi
# Handle cases where files already exist at the destination or link does not match expected destination
if [ -e "$dest" ];
then
if ! [ -L "$dest" ] && [ $FORCE -eq 0 ]; then
echo_b "CONFLICT: $($stat_type "$dest") already exists and is not a symlink:"
echo_b " $line"
if [[ "$line" != *"$real"* ]]; then
echo " $($stat_type "$dest") path: $real"
fi
echo "(Run with $(bold)--force$(unbold) to force removal of existing files and directories.)"
return 1
elif ! [ -L "$dest" ] || [ "$src" != "$(readlink "$dest")" ]; then
warning "Removing conflicting $($stat_type "$dest"): $dest"
rm -rf "$dest" || return 1
fi
fi
# Create links if they do not already exist
if ! [ -e "$dest" ];
then
# Delete conflicting symlinks that are broken
if [ -L "$dest" ]; then
rm -f "$dest"
fi
# Create parent directories
if ! mkdir -p "${dest%/*}"; then
error "Unable to create parent directory (${dest%/*}):\n $line"
return 1
fi
# Symlink or copy
success=0
if [ $COPY -eq 1 ]; then
verb='copy'
cp -R "$src" "$dest" && success=1
else
verb='create symlink'
ln -s "$src" "$dest" && success=1
fi
if [ $success -eq 1 ]; then
printf " Applied: %-30s %s\n" "$target" "$real"
else
error "Unable to $verb ($dest):\n $line"
return 1
fi
fi
return 0
}
###########################################################################
# Automatically create a modman file
run_automodman () #"$module" "$mm" "$wc_dir" "$wc_desc"
{
# returns 0 on success 1 on error
amodule=$1
amm=$2
awc_dir=$3
awc_desc=$4
#echo "building modman file for $amodule"
#echo "in base directory $amm"
#echo "in working copy $awc_dir"
#echo "modman file will be or is $awc_desc"
#does the modman file exist?
#yes
if [ -r "$awc_desc" ]; then
echo "$awc_desc file already exists"
return 0 #exit without error
#no
else
#touch modman
echo "creating modman file for $amodule"
touch $awc_desc
fi
#loop through files
declare -a incpaths
for f in $(find $awc_dir \( ! -iname 'modman' ! -regex "$awc_dir.*/\..*" \)) #ignore hidden files and modman
do
#strip path down
alngth=${#awc_dir} #length of working directory to remove from path
alngth=$((alngth+1)) #increase by one to account for the fact we added a slash
pathname=${f:$alngth}
#first f tends to be empty because of our stripping the path down so test for it -n is default operator
if [ "$pathname" ]; then
prevmatch=0
#todo: possibly there is a neater way to do this without a loop??
for incPath in "${incpaths[@]}" #loop through incpaths
do
if [[ "$pathname" != "${pathname/$incPath/}" ]]; then
prevmatch=1
fi
done
#if substring then do recurse
if [[ $prevmatch -eq 0 ]]; then #make sure we have not previously matched
#pre-existing path: yes | no?
if grep -rq "^[^#].*\s$pathname$" "${amm}/"*"/modman" #target is what is important so find end line not beginning line
then #pre-existing path: yes
echo "..."
echo "A path was found that exists in below modman description file" #error
grep -r --color=always "\s$pathname$" "${amm}/"*"/modman"
echo "It is therefore presently illegal for it to be included in this module."
echo "..."
if [[ -d $f ]]; then
incpaths+=("$pathname")
fi
read -p "press enter to continue..." goonthen
else #pre-existing path: no
read -p "include '$pathname': [N][y]" include #include path: yes | no?
if [[ "$include" == "" ]]; then
include="N"
fi
if [ "$include" == "y" -o "$include" == "Y" ]; then #include path: yes
echo "..."
echo "You answered y:yes modman will include the path"
echo "..."
#write path to modman
echo "$pathname $pathname" >> $awc_desc #substring ${var:start}
#was it a directory or file
if [[ -d $f ]]; then
incpaths+=("$pathname")
fi
elif [ "$include" == "n" -o "$include" == "N" ]; then #include path: no
echo "You answered N:no, modman is not including the path"
else
echo "Warning: you answered $include which is invalid, modman is not including path"
fi
fi # test other modules for pre-existing path
fi # test prvious match
fi #continue
done #no more paths
#return success
return 0
}
###########################################################################
# Get git remote tracking branch or an empty string
get_tracking_branch ()
{
local tracking_branch=$(git rev-parse --symbolic-full-name --abbrev-ref @{u} 2> /dev/null)
if [ -n "$tracking_branch" -a "$tracking_branch" != "@{u}" ]; then
echo $tracking_branch
fi
}
################################
# Find the .modman directory and store parent path in $root
mm_not_found="Module Manager directory not found.\nRun \"$script init\" in the root of the project with which you would like to use Module Manager."
_pwd=$(pwd -P)
root=$_pwd
while ! [ -d "$root/.modman" ]; do
if [ "$root" = "/" ]; then echo -e $mm_not_found && exit 1; fi
cd .. || { error "Could not traverse up from $root\n$mm_not_found" && exit 1; }
root=$(pwd)
done
mmroot=$root # parent of .modman directory
mm=$root/.modman # path to .modman
# Allow a different root to be specified as root for deploying modules, applies to all modules
newroot=$(get_basedir "$mm")
if [ -n "$newroot" ]; then
cd "$mmroot/$newroot" || {
error "Could not change to basedir specified in .basedir file: $newroot"
exit 1
}
root=$(pwd)
fi
# Check for common option overrides
FORCE=0 # --force option off by default
NOLOCAL=0 # --no-local option off by default
NOCLEAN=0 # --no-clean off by default
NOSHELL=0 # --no-shell option off by default
COPY=0 # --copy option off by default
basedir=''
while true; do
case "$1" in
--force) FORCE=1; shift ;;
--no-local) NOLOCAL=1; shift ;;
--no-clean) NOCLEAN=1; shift ;;
--no-shell) NOSHELL=1; shift ;;
--copy) COPY=1; shift ;;
--basedir)
shift; basedir="$1"; shift
if ! [ -n "$basedir" -a -d "$root/$basedir" ]; then
echo "Specified --basedir does not exist: $basedir"; exit 1
fi
;;
*) break ;;
esac
done
###############################
# Handle "list" command
if [ "$action" = "list" ]; then
prefix=''
if [ "$1" = "--absolute" ]; then shift; prefix="$mm/"; fi
if [ -n "$1" ]; then echo "Too many arguments to list command."; exit 1; fi
for module in $(ls -1 "$mm"); do
if [ -d "$mm/$module" -a -e "$mm/$module/modman" ]; then
echo "${prefix}$module"
fi
done
exit 0
###############################
# Handle "status" command
elif [ "$action" = "status" ]; then
if [ -n "$1" ]; then echo "Too many arguments to status command."; exit 1; fi
for module in $(ls -1 "$mm"); do
if [ -d "$mm/$module" -a -e "$mm/$module/modman" ]; then
cd "$mm/$module"
echo_b "-- $module --"
if [ -d "$mm/$module/.git" ]; then
git status
elif [ -d "$mm/$module/.svn" ]; then
svn status
elif [ -d "$mm/$module/.hg" ]; then
hg status
else
echo "Not a git, hg or svn repository."
fi
echo
fi
done
exit 0
###############################
# Handle "incoming" command
elif [ "$action" = "incoming" ]; then
if [ -n "$1" ]; then echo "Too many arguments to incoming command."; exit 1; fi
tmpfile=$(mktemp "$mm/.incoming.XXXX")
for module in $(ls -1 "$mm"); do
if [ -d "$mm/$module" -a -e "$mm/$module/modman" ]; then
cd "$mm/$module"
echo_b "-- $module --" >> $tmpfile
if [ -d "$mm/$module/.git" ]; then
tracking_branch=$(get_tracking_branch)
if [ -z $tracking_branch ]; then
echo "Could not resolve remote tracking branch for $module module." >> $tmpfile
else
echo "Fetching updates for $module..."
git fetch && git --no-pager log --color ..origin/master >> $tmpfile
fi
elif [ -d "$mm/$module/.svn" ]; then
svn st --show-updates >> $tmpfile
elif [ -d "$mm/$module/.hg" ]; then
hg incoming >> $tmpfile
else
echo "Not a git, hg or svn repository." >> $tmpfile
fi
echo >> $tmpfile
fi
done
less -fFR $tmpfile
rm $tmpfile
exit 0
###############################
# Handle "deploy-all" command
elif [ "$action" = "deploy-all" ]; then
if [ -n "$1" ]; then echo "Too many arguments to deploy-all command."; exit 1; fi
remove_dead_links
errors=0
for module in $(ls -1 "$mm"); do
test -d "$mm/$module" && require_wc "$module" || continue;
if get_skipped "$module"; then
echo "Skipping module $module due to .modman-skip file."
continue
fi
echo "Deploying $module to $root"
if apply_modman_file "$mm/$module/modman"; then
echo -e "Deployment of '$module' complete.\n"
if [ $NOLOCAL -eq 0 -a -r "$mm/$module/modman.local" ]; then
apply_modman_file "$mm/$module/modman.local" && echo "Applied local modman file for $module"
fi
else
error "Error occurred while deploying '$module'.\n"
errors=$((errors+1))
fi
done
echo "Deployed all modules with $errors errors."
exit 0
###############################
# Handle "update-all" command
# Updates source code, removes dead links and then deploys modules
elif [ "$action" = "update-all" ]; then
if [ -n "$1" ]; then echo "Too many arguments to update-all command."; exit 1; fi
update_errors=0
updated=''
# Fetch first in case an origin is not responding or slow
for module in $(ls -1 "$mm"); do
test -d "$mm/$module" && require_wc "$module" || continue;
cd "$mm/$module"
success=1
if [ -e .git ]; then
if [ -d .git ]; then
if [ "$(git remote)" != "" ]; then
echo "Fetching changes for $module"
success=0
if [ $FORCE -eq 1 ]; then
if git status -s | grep -vq '??'; then
echo "Cannot do --force update, module has uncommitted changes."
exit 1
else
git fetch --force && success=1
fi
else
git fetch && success=1
fi
fi
else
echo "Git submodule detected. Determining state..."
if [ $(git rev-parse --symbolic-full-name --abbrev-ref HEAD) = "HEAD" ]; then
echo "In detached state, checking out main branch..."
branch=$(git ls-remote --heads origin | grep $(git rev-parse HEAD) | cut -d / -f 3)
git checkout $branch
fi
echo "Fetching changes for $module"
success=0
if [ $FORCE -eq 1 ]; then
if git status -s | grep -vq '??'; then
echo "Cannot do --force update, module has uncommitted changes."
exit 1
else
git fetch --force && success=1
fi
else
git fetch && success=1
fi
fi
if [ $success -eq 1 ]; then
updated="${updated}${module}\n"
else
echo_b -e "Failed to fetch updates for $module\n"
update_errors=$((update_errors+1))
fi
fi
done
# Then update using merge
for module in $(echo -en "$updated"); do
test -d "$mm/$module" && require_wc "$module" || continue;
cd "$mm/$module"
success=0
if [ -d .svn ]; then
echo "Updating $module"
if [ $FORCE -eq 1 ]; then
svn update --force --non-interactive --trust-server-cert && success=1
else
svn update && success=1
fi
elif [ -e .git ] && [ "$(git remote)" != "" ]; then
tracking_branch=$(get_tracking_branch)
echo "Updating $module"
if [ -z $tracking_branch ]; then
echo "Could not resolve remote tracking branch, code will not be updated."
elif [ $FORCE -eq 1 ]; then
git reset --hard $tracking_branch && git submodule update --init --recursive && success=1
else
git merge $tracking_branch && git submodule update --init --recursive && success=1
fi
elif [ -d .hg ]; then
echo "Updating $module"
hg pull && hg update && success=1
else
success=1
fi
echo
if [ $success -ne 1 ]; then
error "Error occurred while updating $module\n"
update_errors=$((update_errors+1))
fi
done
remove_dead_links
deploy_errors=0
for module in $(ls -1 "$mm"); do
test -d "$mm/$module" && require_wc "$module" || continue;
if apply_modman_file "$mm/$module/modman"; then
echo -e "Deployment of '$module' complete.\n"
if [ $NOLOCAL -eq 0 -a -r "$mm/$module/modman.local" ]; then
apply_modman_file "$mm/$module/modman.local" && echo "Applied local modman file for $module"
fi
else
error "Error occurred while deploying '$module'.\n"
deploy_errors=$((deploy_errors+1))
fi
done
echo "Updated all modules with $update_errors update errors and $deploy_errors deploy errors."
exit 0
###########################
# Handle "repair" command
elif [ "$action" = "repair" ]; then
echo "Repairing links, do not interrupt."
mv "$mm" "$mm-repairing" || { error "Could not temporarily rename .modman directory."; exit 1; }
remove_dead_links
mv "$mm-repairing" "$mm" || { error "Could not restore .modman directory."; exit 1; }
for module in $(ls -1 "$mm"); do
test -d "$mm/$module" && require_wc "$module" || continue;
remove_basedirs "$module" &&
apply_modman_file "$mm/$module/modman" &&
echo -e "Repaired $module.\n"
if [ $NOLOCAL -eq 0 -a -r "$mm/$module/modman.local" ]; then
apply_modman_file "$mm/$module/modman.local" && echo "Applied local modman file for $module"
fi
done
exit 0
###########################
# Handle "clean" command
elif [ "$action" = "clean" ]; then
echo "Cleaning broken links."
NOCLEAN=0
remove_dead_links
exit 0
fi
#############################################
# Handle all other module-specific commands
#############################################
REGEX_ACTION='^(update|deploy|undeploy|skip|unskip|checkout|clone|hgclone|link|remove|automodman)$'
REGEX_NEW_MODULE='^(checkout|clone|hgclone|link)$'
REGEX_BAD_MODULE="($REGEX_ACTION| )"
REGEX_MODULE='^[a-zA-Z0-9_-]+$'
if ! [[ "$action" =~ $REGEX_ACTION ]]; then
echo "Invalid action specified: $action"
exit 1
fi
module=''
src=''
# If valid module is specified on command line
if [ -z "$module" -a -n "$1" -a -d "$mm/$1" ] || [[ "$1" =~ $REGEX_MODULE ]]; then
module=$1; shift
fi
# If module name is not given
if [ -z "$module" ]; then
# Extract from end of next argument assuming it is the repo location
if [[ "$action" =~ $REGEX_NEW_MODULE ]]; then
if [ $# -eq 1 -o "${2:0:1}" = "-" ]; then
module=${1%.git} # strip .git if specified
module=${module//:/\/} # replace : with / for basename in case of git SSH
module=$(basename "$module") # get the end-most part of the repo url
fi
# Discover if modman is run from within a module directory
else
cd "$_pwd"
while [ $(dirname "$mm") != "$(pwd)" ] && [ "$(pwd)" != "/" ]; do
modpath=$(pwd)
if [ $(dirname "$modpath") = "$mm" ]; then
module=$(basename "$modpath")
break
fi
cd ..
done
fi
fi
# Module must be next argument
if [ -z "$module" -a -n "$1" ]; then
module=$1; shift
fi
if [ -z "$module" ]; then
echo "Not enough arguments (no module specified)"
exit 1
fi
# Get optional args again (allow them to come after the module name)
while true; do
case "$1" in
--force) FORCE=1; shift ;;
--no-local) NOLOCAL=1; shift ;;
--no-clean) NOCLEAN=1; shift ;;
--no-shell) NOSHELL=1; shift ;;
--copy) COPY=1; shift ;;
--basedir)
shift; basedir="$1"; shift
if ! [ -n "$basedir" -a -d "$root/$basedir" ]; then
echo "Specified --basedir does not exist: $basedir"; exit 1
fi
;;
*)
break
esac
done
cd "$_pwd"; # restore old root
wc_dir=$mm/$module # working copy directory for module
wc_desc=$wc_dir/modman # path to modman structure descriptor file
case "$action" in
automodman)
run_automodman "$module" "$mm" "$wc_dir" "$wc_desc"
;;
update)
require_wc "$module" || exit 1
cd "$wc_dir"
success=0
if [ -d .svn ]; then
if [ $FORCE -eq 1 ]; then
svn update --force --non-interactive --trust-server-cert && success=1
else
svn update && success=1
fi
elif [ -e .git ]; then
if [ -d .git ]; then
if [ $FORCE -eq 1 ]; then
tracking_branch=$(git rev-parse --symbolic-full-name --abbrev-ref @{u})
[[ -n $tracking_branch ]] || { echo "Could not resolve remote tracking branch."; exit 1; }
git fetch --force && git reset --hard $tracking_branch && git submodule update --init --recursive && success=1
else
git pull && git submodule update --init --recursive && success=1
fi
else
echo "Git submodule detected. Determining state..."
tracking_branch=$(git rev-parse --symbolic-full-name --abbrev-ref HEAD)
if [ $tracking_branch = "HEAD" ]; then
echo "In detached state, checking out main branch..."
git fetch
tracking_branch=$(git ls-remote --heads origin | grep $(git rev-parse HEAD) | cut -d / -f 3)
[[ -n $tracking_branch ]] || { echo "Could not resolve remote tracking branch."; exit 1; }
if [ $FORCE -eq 1 ]; then
git checkout $tracking_branch && git fetch --force && git reset --hard $tracking_branch && git submodule update --init --recursive && success=1
else
git checkout $tracking_branch && git pull && git submodule update --init --recursive && success=1
fi
else
echo "Tracking branch found, proceeding normally..."
if [ $FORCE -eq 1 ]; then
git fetch --force && git reset --hard $tracking_branch && git submodule update --init --recursive && success=1
else
git pull && git submodule update --init --recursive && success=1
fi
fi
fi
elif [ -d .hg ]; then
hg pull && hg update && success=1
fi
[ $success -eq 1 ] || { echo_b "Failed to update working copy of '$module'."; exit 1; }
remove_dead_links
apply_modman_file "$wc_desc" && echo "Update of $module complete."
if [ $NOLOCAL -eq 0 -a -r "$wc_desc.local" ]; then
apply_modman_file "$wc_desc.local" && echo "Applied local modman file for $module"
fi
;;
checkout|clone|hgclone|link)
FORCE=1
cd "$mm"
if [[ "$module" =~ $REGEX_BAD_MODULE ]]; then
echo "You cannot $action a module with a name matching $REGEX_BAD_MODULE."; exit 1
fi
if [ -d "$wc_dir" ]; then
echo "A module named '$module' has already been checked out."; exit 1
fi
if [ -z "$src" ]; then
src="$1"; shift
fi
if [ -z "$src" -o "$src" = "--" ]; then
echo "You must specify a source for the '$action' command."; exit 1
fi
if [ "$1" = "--" ]; then shift; fi
success=0
verb=''
if [ "$action" = "checkout" ]; then
verb='checked out'
svn checkout "$src" $@ "$module" && success=1
elif [ "$action" = "clone" ]; then
verb='cloned'
git clone --recursive "$src" $@ "$module" && success=1
elif [ "$action" = "hgclone" ]; then
verb='cloned'
hg clone "$src" $@ "$module" && success=1
elif [ "$action" = "link" ]; then
verb='linked'
cd "$mmroot"
if ! [ -d "$src" ]; then
echo "The path specified does not exist or is not a directory."
echo "The module path must either be an absolute path, or relative to $mmroot"
exit 1
fi
if [ "${src:0:1}" != "/" ]; then
src="../$src"
fi
ln -s "$src" ".modman/$module" && success=1
cd "$mm"
fi
if
[ $success -eq 1 ] &&
require_wc "$module" && cd "$wc_dir" &&
set_basedir "$wc_dir" "$basedir" &&
apply_modman_file "$wc_desc"
then
if [ -n "$basedir" ]; then
using_basedir=" using base directory '$basedir'"
fi
echo "Successfully $verb new module '$module'$using_basedir"
else
if [ -d "$wc_dir" ]; then rm -rf "$wc_dir"; fi
error "trying to $action new module '$module', operation cancelled."
exit 1
fi
;;
deploy)
require_wc "$module" || exit 1
apply_modman_file "$wc_desc" && echo "$module has been deployed under $root"
if [ $NOLOCAL -eq 0 -a -r "$wc_desc.local" ]; then
apply_modman_file "$wc_desc.local" && echo "Applied local modman file for $module"
fi
;;
undeploy)
require_wc "$module" || exit 1
remove_module_links "$module" || exit 1
;;
skip)
set_skipped "$module" 1 || exit 1
;;
unskip)
set_skipped "$module" 0 || exit 1
;;
remove)
require_wc "$module" || exit 1
rm -rf "$wc_dir" && remove_dead_links && echo "$module has been removed"
;;
*)
echo -e "$usage"
echo_b "Invalid action: $action"
exit 1
esac