1#!/usr/bin/env bash 2# Module Manager 3# 4# Copyright 2009 Colin Mollenhour 5# 6# Licensed under the Apache License, Version 2.0 (the "License"); 7# you may not use this file except in compliance with the License. 8# You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15# See the License for the specific language governing permissions and 16# limitations under the License. 17# 18# System Requirements: 19# - bash 20# - filesystem and project or web server must support symlinks 21# - The following common utilities must be locatable in your $PATH 22# grep (POSIX), find, ln, cp, basename, dirname, readlink 23 24version="1.12" 25script=${0##*/} 26usage="\ 27Module Manager (v$version) 28 29Global Commands: 30 $script <command> [<options>] 31------------------------------ 32 init [basedir] initialize the pwd (or [basedir]) as the modman deploy root 33 list [--absolute] list all valid modules that are currently checked out 34 status show VCS 'status' command for all modules 35 incoming show new VCS remote changesets for all VCS-based modules 36 update-all update all modules that are currently checked out 37 deploy-all deploy all modules (no VCS interaction) 38 repair rebuild all modman-created symlinks (no updates performed) 39 clean clean up broken symlinks (run this after deleting a module) 40 automodman loops through module directory: 41 - asks to include files & directories in new modman file 42 - does not create symlinks 43 --help display this help message 44 --tutorial show a brief tutorial on using modman 45 --version display the modman script's version 46 [--force] overwrite existing files, ignore untrusted cert warnings 47 [--no-local] skip processing of modman.local files 48 [--no-clean] skip cleaning of broken symlinks 49 [--no-shell] skip processing @shell lines 50 51Module Commands: 52 $script <command> [<module>] [<options>] [[--] <VCS options>] 53------------------------------ 54 checkout <src> checkout a new modman-compatible module using subversion 55 clone <src> checkout a new modman-compatible module using git clone 56 hgclone <src> checkout a new modman-compatible module using hg clone 57 link <path> link to a module that already exists on the local filesystem 58 update update module using the appropriate VCS 59 deploy deploy an existing module (VCS not required or used) 60 undeploy remove an existing module without deleting the files 61 skip prevent a module from deploying with deploy-all 62 unskip re-enable a module to be deployed with deploy-all 63 remove remove a module (DELETES MODULE FILES) 64 [--force] overwrite existing files, ignore untrusted cert warnings 65 [--no-local] skip processing of modman.local files 66 [--no-clean] skip cleaning of broken symlinks 67 [--no-shell] skip processing @shell lines 68 [--basedir <path>] on checkout/clone, specifies a base for module deployment 69 [--copy] deploy by copying files instead of symlinks 70 [<VCS options>] specify additional parameters to VCS checkout/clone 71" 72tutorial="\ 73Deploying a modman module: 74------------------------------ 75The 'checkout' and 'clone' commands are used to checkout new modules from a 76repository (subversion and git, respectively). These commands support additional 77arguments which will be passed to the 'svn checkout' and 'git clone' commands. 78This allows you to for example specify a --username (svn) or a --branch (git). 79See 'svn help checkout' and 'git help clone' for a full list of options. 80Both svn:externals and git submodules will be automatically included. 81 82To link to modules that already exist on the local file system you can use 83the 'link' command. The 'update' command will be supported the same as if the 84module had been created using the checkout or clone command. For example: 85 86 $ modman link ~/projects/My_Module 87 $ modman update My_Module 88 89By default, if a module being checked out contains symlink mappings that 90conflict with existing files, an error will be thrown. Use --force to cause 91any existing files or directories that conflict to be removed. Be careful! 92 93Writing modman modules: 94------------------------------ 95Each module should contain a file named \"modman\" which defines which files 96go where relative to the directory where modman was initialized. 97 98==== Start example modman file ==== 99 # Comments are supported, begin a line with a hash 100 code app/code/local/My/Module/ 101 design app/design/frontend/default/default/mymodule/ 102 103 # Source and destination can have different names. 104 en_US.csv app/locale/en_US/My_Module.csv 105 106 # Destination file name is not required if the same as the source. 107 My_Module.xml app/etc/modules/ 108 109 # Leave out the destination entirely to assume the same path as the source. 110 lib/Somelib 111 112 # Bash extended glob patterns supported. 113 skin/css/* skin/frontend/base/default/css/ 114 skin/js/* skin/frontend/base/default/js/ 115 116 # Import another modman module 117 @import modules/Fooman_Speedster 118 119 # Execute a command on the shell 120 @shell rm -rf \$PROJECT/var/cache/* 121==== End example modman file ==== 122 123Globbing: 124------------------------------ 125Bash globbing in the modman file is supported. The result is that each file or 126directory that matches the globbing pattern will get its own symlink in the 127specified location when the module is deployed. 128 129Importing modules: 130------------------------------ 131One modman file can import another modman module using @import and the path to 132the imported module's root relative to the current module's root. Imported 133modules deploy to the same path as checked out modules so @import can be used 134to include common modules that are just stored in a subdirectory, or are 135included via svn:externals or git submodules. Example: 136 > svn propget svn:externals . 137 ^/modules/Fooman_Speedster modules/Fooman_Speedster 138In this example, modules/Fooman_Speedster would contain it's own modman file 139and could therefore be imported by any other module or checked out by itself. 140 141Shell commands: 142------------------------------ 143Actions to be taken any time one of the checkout, clone, update, deploy, 144update-all or repair commands are used can be defined with the @shell 145directive. The rest of the line after @shell will be piped to a new bash 146shell with the working directory being the module's root. The following 147environment variables will be made available: 148 PROJECT The path of the project base dir (where modman was initialized) 149 MODULE The current module's path 150 151Standalone mode: 152------------------------------ 153The modman script can be used without a VCS by placing the module directory in 154the proper location and running \"modman deploy <module>\". The root of the 155module must be located at <project_root>/.modman/<module_name>/ and it must 156contain a modman file. 157 158Shortcut: 159------------------------------ 160Modman can be run without the module name if the current directory's realpath 161is within a module's path. E.g. \"modman update\" or \"modman deploy\". 162 163Local 'modman' file: 164------------------------------ 165You can create a modman file named \"modman.local\" which will also get applied 166like the standard modman file. The intended purpose of this file is to allow you 167to specify additional directives while excluding them from version control. The 168\"--no-local\" command option will prevent these files from being processed. 169 170Custom 'clean' script: 171------------------------------ 172On some systems the dead link search can be brutally slow. You can create a 173custom clean script at .modman/.clean which will override the default clean 174script. Here is an example of a custom clean script that ignores the \"media\" 175and \"var\" directories: 176 177 echo Cleaning dead links in \$PWD 178 find -L . -mount \\( -path ./media -o -path ./var \\) -prune -o -type l -exec rm {} \; 179 180Author: 181------------------------------ 182Colin Mollenhour 183http://colin.mollenhour.com/ 184colin@mollenhour.com 185" 186 187# Action is first argument 188action=$1; shift 189 190# Fix GNU inconsistencies with Mac 191case $OSTYPE in 192 darwin*|*bsd*) 193 stat_type="bsd_stat" 194 readlink_missing="echo" # readlink -m not supported 195 ;; 196 *) 197 stat_type="stat -c %F" 198 readlink_missing="readlink -m" 199 ;; 200esac 201bsd_stat () 202{ 203 case "$(stat -f %T "$1")" in 204 "*") echo file;; 205 "@") echo symlink;; 206 "/") echo directory;; 207 *) echo unknown_type;; 208 esac 209} 210pager=${PAGER:-$(which pager &> /dev/null)} 211if [ -z "$pager" ]; then 212 pager=less 213fi 214 215########################### 216# Handle "init" command, simply create .modman directory 217if [ "$action" = "init" ]; then 218 basedir=$1 219 [[ -n "$basedir" && ! -d "$basedir" ]] && { echo "$basedir is not a directory."; exit 1; } 220 mkdir .modman || { echo "Could not create .modman directory" && exit 1; } 221 if [ -n "$basedir" ]; then 222 echo "$basedir" > .modman/.basedir 223 basedir="with basedir at $basedir" 224 fi 225 echo "Initialized Module Manager at $(pwd) $basedir" 226 exit 0 227########################### 228# Handle "--help" command 229elif [ "$action" = "--help" -o "$action" = "" ]; then 230 echo -e "$usage"; exit 0 231########################### 232# Handle "--tutorial" command 233elif [ "$action" = "--tutorial" ]; then 234 echo -e "$tutorial" | $pager; exit 0 235########################### 236# Handle "--version" command 237elif [ "$action" = "--version" ]; then 238 echo "Module Manager version: $version"; exit 0 239fi 240 241 242############################################################# 243# Echo in bold font if stdout is a terminal 244ISTTY=0; if [ -t 1 ]; then ISTTY=1; fi 245bold () { if [ $ISTTY -eq 1 ]; then tput bold; fi; } 246unbold () { if [ $ISTTY -eq 1 ]; then tput sgr0; fi; } 247echo_b () 248{ 249 if [ "$1" = "-e" ]; then 250 echo -e "$(bold)$2$(unbold)" 251 else 252 echo "$(bold)$1$(unbold)" 253 fi 254} 255 256############################################################# 257# Colors in terminal 258yellow () { if [ $ISTTY -eq 1 ]; then tput setaf 3; fi; } 259red () { if [ $ISTTY -eq 1 ]; then tput setaf 1; fi; } 260 261############################################################# 262# Handling warning messages 263warning () 264{ 265 echo -e "$(bold)$(yellow)WARNING:$(unbold) "$1 266} 267 268############################################################# 269# Handling Error messages 270error () 271{ 272 echo -e "$(bold)$(red)ERROR:$(unbold) "$1 273} 274 275############################################################# 276# Check for existence of a module directory and modman file 277require_wc () 278{ 279 if ! [ -d "$mm/$1" ]; then 280 error "$1 has not been checked out."; return 1 281 fi 282 if ! [ -r "$mm/$1/modman" ]; then 283 error "$1 does not contain a \"modman\" module description file."; return 1 284 fi 285 return 0 286} 287 288############################################################### 289# Removes dead symlinks 290remove_dead_links () 291{ 292 if [ $NOCLEAN -eq 1 ]; then 293 return 0 294 fi 295 296 # Support use of a custom clean script 297 if [ -r "$mm/.clean" ]; then 298 ( cd "$root"; $SHELL "$mm/.clean" ) 299 else 300 # Use -exec rm instead of -delete to avoid bug in Darwin. -Thanks Vinai! 301 ( cd "$root"; find -L . -mount -type l -exec rm {} \; ) 302 fi 303 return $? 304} 305 306############################################################### 307# Removes all .basedir files from submodules 308remove_basedirs () 309{ 310 local module=$1 311 find "$mm/$module" -name .basedir | grep -FZv "$mm/$module/.basedir" | xargs -0 rm -f 312} 313 314############################################################### 315# Reads the base directory for a module 316# Return value is blank, or relative path ending with / 317get_basedir () 318{ 319 local module_dir=$1 320 local basedir='' 321 if [ -r "$module_dir/.basedir" ]; then 322 basedir=$(cat "$module_dir/.basedir" | grep -v '^#') 323 if [ -n "$basedir" ]; then 324 basedir=${basedir%%/}/ 325 fi 326 fi 327 echo -n "$basedir" 328} 329 330############################################################### 331# Writes a file, setting the base directory for a module 332set_basedir () 333{ 334 local module_dir=$1 335 local basedir=${2##/} 336 basedir=${basedir%%/} 337 if [ -n "$basedir" ]; then 338 echo -e "# This file was created by modman. Module base directory:\n$basedir" \ 339 > "$module_dir/.basedir" 340 if ! [ $? ]; then 341 error "Could not write to file: $module_dir/.basedir." 342 return 1 343 fi 344 fi 345 return 0 346} 347 348get_skipped () 349{ 350 local module=$1 351 if [ -f "$root/.modman-skip" ]; then 352 for line in $(grep -v -e '^#' "$root/.modman-skip"); do 353 if [ $line == $module ]; then 354 return 0 355 fi 356 done 357 fi 358 return 1 359} 360 361set_skipped () 362{ 363 local module=$1 364 local skip=$2 365 local SKIP_FILE="$root/.modman-skip" 366 367 if ! [ -f "$SKIP_FILE" ]; then 368 echo "# This file was created by modman. The following modules won't be deployed by deploy-all." \ 369 > "$root/.modman-skip" 370 if ! [ $? ]; then 371 error "Could not write to file: $SKIP_FILE" 372 return 1 373 fi 374 fi 375 376 if [ "$skip" = 1 ]; then 377 if get_skipped $module; then 378 echo "Module $module already skipped." 379 exit 1 380 else 381 echo $module >> "$SKIP_FILE" 382 echo "Module $module added to skip list." 383 fi 384 else 385 grep -v "^$module$" "$SKIP_FILE" > "$SKIP_FILE.tmp"; mv "$SKIP_FILE.tmp" "$SKIP_FILE" 386 echo "Module $module removed from skip list." 387 fi 388 389 return 0 390} 391 392get_abs_filename() { 393 if [ -d "$(dirname "$1")" ]; then 394 echo "$(cd "$(dirname "$1")/$(dirname "$(readlink "$1")")" && pwd)/$(basename "$1")" 395 fi 396} 397 398remove_module_links () 399{ 400 echo "Removing links for module $module." 401 local module_dir="$mm/$module" 402 for line in $(find $root -type l); do 403 if [[ $(get_abs_filename "$line") =~ ^"$module_dir".* ]]; then 404 rm "$line" 405 fi 406 done 407 408 return 0 409} 410 411################################################################################ 412# Reads a modman file and does the following: 413# Creates the symlinks as described 414# Imports external modman files (@import) 415# Runs shell commands (@shell) 416apply_modman_file () 417{ 418 local module=$1 419 local module_dir=$(dirname "$module") 420 local basedir=$(get_basedir "$module_dir") 421 local relpath=${module:$((${#mmroot}+1))} 422 423 # Use argument if module doesn't have a .basedir file 424 if [ -z "$basedir" ]; then 425 basedir=$2 426 fi 427 428 # while loop should not read from stdin or else @shell scripts cannot get stdin 429 IFS=$'\r\n' 430 for line in $(grep -v -e '^#' -e '^\s*$' "$module"); do 431 IFS=$' \t\n' 432 433 # Split <target> <real> 434 read target real <<< $line 435 436 # Assume target == real if only one path is given 437 if [ -z "$real" ]; then 438 real="$target" 439 fi 440 441 # Sanity check for empty data 442 if [ -z "$target" -o -z "$real" ]; then 443 error "Invalid input in modman file ($relpath):\n $line" 444 return 1 445 fi 446 447 # Import other module definitions (e.g. git submodules, svn:externals, etc..) 448 if [ "$target" = "@import" ]; then 449 # check if base defined, create and save base to .basedir file 450 read import_path import_base <<< $real 451 452 import=$module_dir/${import_path%/}/modman 453 if ! [ -r "$import" ]; then 454 relimport=${import:$((${#mmroot}+1))} 455 error "modman file not found ($relimport):\n $line" 456 return 1 457 fi 458 459 if [ -z "$import_base" ]; then 460 import_base=${basedir%%/} 461 else 462 import_base=${import_base##/} 463 import_base=${basedir}${import_base%%/} 464 if ! [ -d "$root/$import_base" ]; then 465 if ! mkdir -p "$root/$import_base"; then 466 error "Could not create import base directory: $import_base" 467 return 1 468 fi 469 echo "Created import base directory: $import_base" 470 fi 471 if ! set_basedir "$module_dir/$import_path" "$import_base"; then 472 return 1 473 fi 474 fi 475 476 apply_modman_file "$import" "$import_base/" || return 1 477 continue 478 fi 479 480 # Run commands on the shell! 481 # temporary file is workaround so that script can receive stdin 482 if [ "$target" = "@shell" ]; then 483 [ $NOSHELL -eq 0 ] || continue 484 cd "$module_dir" 485 export PROJECT=$root/${basedir%%/} 486 export MODULE=$module_dir 487 shell_tmp=$(mktemp "$mm/.tmp.XXXXXXX") 488 echo "($real)" > "$shell_tmp" 489 source "$shell_tmp" 490 rm -f "$shell_tmp" 491 continue 492 fi 493 494 # Create symlink to target 495 local src=$module_dir/$target 496 local dest=$root/${basedir}${real%/} 497 dest=${dest/\/\//\/} # Replace // with / 498 dest=${dest%/} # Strip trailing / 499 500 # Handle globbing (extended globbing enabled) 501 shopt -s extglob 502 if ! [ -e "$src" ] && [ $(ls $src 2> /dev/null | wc -l) -gt 0 ]; then 503 for _src in $src; do 504 apply_path "$_src" "$dest/${_src##*/}" "$target" "${real%/}/${_src##*/}" "$line" || return 1 505 done 506 continue 507 fi # end Handle globbing 508 509 # Handle aliases that do not exist 510 if ! [ -e "$src" ]; 511 then 512 warning "Target does not exist ($relpath):\n $line" 513 continue 514 fi 515 516 # Allow destination to be a dir when src is a file 517 if [ -f "$src" ] && [ -d "$dest" -o "/" = ${real: -1} ]; then 518 dest="$dest/$(basename "$src")" 519 fi 520 521 apply_path "$src" "$dest" "$target" "$real" "$line" || return 1 522 done 523 524 return 0 525} 526 527########################################################################### 528# Creates a symlink or copies a file (with lots of error-checking) 529apply_path () 530{ 531 local src="$1"; local dest="$2"; local target="$3"; local real="$4"; local line="$5" 532 533 # Make symlinks relative 534 if [ $COPY -eq 0 ]; then 535 local realpath=$($readlink_missing "${dest%/*}"); local commonpath="" 536 if [ "${dest%/*}" == "${realpath}" ]; then 537 # Use modman root as common path if destination is not itself a symlinked path 538 commonpath="${mmroot%/}" 539 else 540 # Search for longest common path as symlink target 541 for ((i=0; i<${#dest}; i++)); do 542 if [[ "${dest:i:1}" != "${realpath:i:1}" ]]; then 543 commonpath="${dest:0:i}" 544 commonpath="${commonpath%/*}" 545 break 546 fi 547 done 548 fi 549 # Replace destination (less common path) with ../* 550 if [ "$commonpath" != "" ]; then 551 local reldest="${dest#$commonpath/}" 552 if [ "$reldest" != "${reldest%/*}" ]; then 553 reldest=$(IFS=/; for d in ${reldest%/*}; do echo -n '../'; done) 554 else 555 reldest="" 556 fi 557 src="${reldest}${src#$commonpath/}" 558 fi 559 fi 560 561 # Handle cases where files already exist at the destination or link does not match expected destination 562 if [ -e "$dest" ]; 563 then 564 if ! [ -L "$dest" ] && [ $FORCE -eq 0 ]; then 565 echo_b "CONFLICT: $($stat_type "$dest") already exists and is not a symlink:" 566 echo_b " $line" 567 echo "(Run with $(bold)--force$(unbold) to force removal of existing files and directories.)" 568 return 1 569 elif ! [ -L "$dest" ] || [ "$src" != "$(readlink "$dest")" ]; then 570 warning "Removing conflicting $($stat_type "$dest"): $dest" 571 rm -rf "$dest" || return 1 572 fi 573 fi 574 575 # Create links if they do not already exist 576 if ! [ -e "$dest" ]; 577 then 578 # Delete conflicting symlinks that are broken 579 if [ -L "$dest" ]; then 580 rm -f "$dest" 581 fi 582 # Create parent directories 583 if ! mkdir -p "${dest%/*}"; then 584 error "Unable to create parent directory (${dest%/*}):\n $line" 585 return 1 586 fi 587 # Symlink or copy 588 success=0 589 if [ $COPY -eq 1 ]; then 590 verb='copy' 591 cp -R "$src" "$dest" && success=1 592 else 593 verb='create symlink' 594 ln -s "$src" "$dest" && success=1 595 fi 596 if [ $success -eq 1 ]; then 597 printf " Applied: %-30s %s\n" "$target" "$real" 598 else 599 error "Unable to $verb ($dest):\n $line" 600 return 1 601 fi 602 fi 603 return 0 604} 605########################################################################### 606# Automatically create a modman file 607run_automodman () #"$module" "$mm" "$wc_dir" "$wc_desc" 608{ 609 # returns 0 on success 1 on error 610 amodule=$1 611 amm=$2 612 awc_dir=$3 613 awc_desc=$4 614 615 #echo "building modman file for $amodule" 616 #echo "in base directory $amm" 617 #echo "in working copy $awc_dir" 618 #echo "modman file will be or is $awc_desc" 619 620 #does the modman file exist? 621 #yes 622 if [ -r "$awc_desc" ]; then 623 echo "$awc_desc file already exists" 624 return 0 #exit without error 625 626 #no 627 else 628 #touch modman 629 echo "creating modman file for $amodule" 630 touch $awc_desc 631 fi 632 #loop through files 633 declare -a incpaths 634 635 for f in $(find $awc_dir \( ! -iname 'modman' ! -regex "$awc_dir.*/\..*" \)) #ignore hidden files and modman 636 do 637 #strip path down 638 alngth=${#awc_dir} #length of working directory to remove from path 639 alngth=$((alngth+1)) #increase by one to account for the fact we added a slash 640 pathname=${f:$alngth} 641 642 #first f tends to be empty because of our stripping the path down so test for it -n is default operator 643 if [ "$pathname" ]; then 644 645 prevmatch=0 646 #todo: possibly there is a neater way to do this without a loop?? 647 for incPath in "${incpaths[@]}" #loop through incpaths 648 do 649 if [[ "$pathname" != "${pathname/$incPath/}" ]]; then 650 prevmatch=1 651 fi 652 done 653 654 #if substring then do recurse 655 if [[ $prevmatch -eq 0 ]]; then #make sure we have not previously matched 656 657 #pre-existing path: yes | no? 658 if grep -rq "^[^#].*\s$pathname$" "${amm}/"*"/modman" #target is what is important so find end line not beginning line 659 then #pre-existing path: yes 660 echo "..." 661 echo "A path was found that exists in below modman description file" #error 662 grep -r --color=always "\s$pathname$" "${amm}/"*"/modman" 663 echo "It is therefore presently illegal for it to be included in this module." 664 echo "..." 665 if [[ -d $f ]]; then 666 incpaths+=("$pathname") 667 fi 668 read -p "press enter to continue..." goonthen 669 else #pre-existing path: no 670 671 read -p "include '$pathname': [N][y]" include #include path: yes | no? 672 if [[ "$include" == "" ]]; then 673 include="N" 674 fi 675 676 if [ "$include" == "y" -o "$include" == "Y" ]; then #include path: yes 677 678 echo "..." 679 echo "You answered y:yes modman will include the path" 680 echo "..." 681 682 #write path to modman 683 echo "$pathname $pathname" >> $awc_desc #substring ${var:start} 684 #was it a directory or file 685 if [[ -d $f ]]; then 686 incpaths+=("$pathname") 687 fi 688 689 elif [ "$include" == "n" -o "$include" == "N" ]; then #include path: no 690 echo "You answered N:no, modman is not including the path" 691 else 692 echo "Warning: you answered $include which is invalid, modman is not including path" 693 fi 694 695 fi # test other modules for pre-existing path 696 fi # test prvious match 697 fi #continue 698 done #no more paths 699 #return success 700 return 0 701} 702 703########################################################################### 704# Get git remote tracking branch or an empty string 705get_tracking_branch () 706{ 707 local tracking_branch=$(git rev-parse --symbolic-full-name --abbrev-ref @{u} 2> /dev/null) 708 if [ -n "$tracking_branch" -a "$tracking_branch" != "@{u}" ]; then 709 echo $tracking_branch 710 fi 711} 712 713################################ 714# Find the .modman directory and store parent path in $root 715mm_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." 716_pwd=$(pwd -P) 717root=$_pwd 718while ! [ -d "$root/.modman" ]; do 719 if [ "$root" = "/" ]; then echo -e $mm_not_found && exit 1; fi 720 cd .. || { error "Could not traverse up from $root\n$mm_not_found" && exit 1; } 721 root=$(pwd) 722done 723 724mmroot=$root # parent of .modman directory 725mm=$root/.modman # path to .modman 726 727# Allow a different root to be specified as root for deploying modules, applies to all modules 728newroot=$(get_basedir "$mm") 729if [ -n "$newroot" ]; then 730 cd "$mmroot/$newroot" || { 731 error "Could not change to basedir specified in .basedir file: $newroot" 732 exit 1 733 } 734 root=$(pwd) 735fi 736 737# Check for common option overrides 738FORCE=0 # --force option off by default 739NOLOCAL=0 # --no-local option off by default 740NOCLEAN=0 # --no-clean off by default 741NOSHELL=0 # --no-shell option off by default 742COPY=0 # --copy option off by default 743basedir='' 744while true; do 745 case "$1" in 746 --force) FORCE=1; shift ;; 747 --no-local) NOLOCAL=1; shift ;; 748 --no-clean) NOCLEAN=1; shift ;; 749 --no-shell) NOSHELL=1; shift ;; 750 --copy) COPY=1; shift ;; 751 --basedir) 752 shift; basedir="$1"; shift 753 if ! [ -n "$basedir" -a -d "$root/$basedir" ]; then 754 echo "Specified --basedir does not exist: $basedir"; exit 1 755 fi 756 ;; 757 *) break ;; 758 esac 759done 760 761############################### 762# Handle "list" command 763if [ "$action" = "list" ]; then 764 prefix='' 765 if [ "$1" = "--absolute" ]; then shift; prefix="$mm/"; fi 766 if [ -n "$1" ]; then echo "Too many arguments to list command."; exit 1; fi 767 for module in $(ls -1 "$mm"); do 768 if [ -d "$mm/$module" -a -e "$mm/$module/modman" ]; then 769 echo "${prefix}$module" 770 fi 771 done 772 exit 0 773 774############################### 775# Handle "status" command 776elif [ "$action" = "status" ]; then 777 if [ -n "$1" ]; then echo "Too many arguments to status command."; exit 1; fi 778 for module in $(ls -1 "$mm"); do 779 if [ -d "$mm/$module" -a -e "$mm/$module/modman" ]; then 780 cd "$mm/$module" 781 echo_b "-- $module --" 782 if [ -d "$mm/$module/.git" ]; then 783 git status 784 elif [ -d "$mm/$module/.svn" ]; then 785 svn status 786 elif [ -d "$mm/$module/.hg" ]; then 787 hg status 788 else 789 echo "Not a git, hg or svn repository." 790 fi 791 echo 792 fi 793 done 794 exit 0 795 796############################### 797# Handle "incoming" command 798elif [ "$action" = "incoming" ]; then 799 if [ -n "$1" ]; then echo "Too many arguments to incoming command."; exit 1; fi 800 tmpfile=$(mktemp "$mm/.incoming.XXXX") 801 for module in $(ls -1 "$mm"); do 802 if [ -d "$mm/$module" -a -e "$mm/$module/modman" ]; then 803 cd "$mm/$module" 804 echo_b "-- $module --" >> $tmpfile 805 if [ -d "$mm/$module/.git" ]; then 806 tracking_branch=$(get_tracking_branch) 807 if [ -z $tracking_branch ]; then 808 echo "Could not resolve remote tracking branch for $module module." >> $tmpfile 809 else 810 echo "Fetching updates for $module..." 811 git fetch && git --no-pager log --color ..origin/master >> $tmpfile 812 fi 813 elif [ -d "$mm/$module/.svn" ]; then 814 svn st --show-updates >> $tmpfile 815 elif [ -d "$mm/$module/.hg" ]; then 816 hg incoming >> $tmpfile 817 else 818 echo "Not a git, hg or svn repository." >> $tmpfile 819 fi 820 echo >> $tmpfile 821 fi 822 done 823 less -fFR $tmpfile 824 rm $tmpfile 825 exit 0 826 827############################### 828# Handle "deploy-all" command 829elif [ "$action" = "deploy-all" ]; then 830 if [ -n "$1" ]; then echo "Too many arguments to deploy-all command."; exit 1; fi 831 remove_dead_links 832 errors=0 833 for module in $(ls -1 "$mm"); do 834 test -d "$mm/$module" && require_wc "$module" || continue; 835 if get_skipped "$module"; then 836 echo "Skipping module $module due to .modman-skip file." 837 continue 838 fi 839 echo "Deploying $module to $root" 840 if apply_modman_file "$mm/$module/modman"; then 841 echo -e "Deployment of '$module' complete.\n" 842 if [ $NOLOCAL -eq 0 -a -r "$mm/$module/modman.local" ]; then 843 apply_modman_file "$mm/$module/modman.local" && echo "Applied local modman file for $module" 844 fi 845 else 846 error "Error occurred while deploying '$module'.\n" 847 errors=$((errors+1)) 848 fi 849 done 850 echo "Deployed all modules with $errors errors." 851 exit 0 852 853############################### 854# Handle "update-all" command 855# Updates source code, removes dead links and then deploys modules 856elif [ "$action" = "update-all" ]; then 857 if [ -n "$1" ]; then echo "Too many arguments to update-all command."; exit 1; fi 858 update_errors=0 859 updated='' 860 861 # Fetch first in case an origin is not responding or slow 862 for module in $(ls -1 "$mm"); do 863 test -d "$mm/$module" && require_wc "$module" || continue; 864 cd "$mm/$module" 865 success=1 866 if [ -d .git ] && [ "$(git remote)" != "" ]; then 867 echo "Fetching changes for $module" 868 success=0 869 if [ $FORCE -eq 1 ]; then 870 if git status -s | grep -vq '??'; then 871 echo "Cannot do --force update, module has uncommitted changes." 872 exit 1 873 else 874 git fetch --force && success=1 875 fi 876 else 877 git fetch && success=1 878 fi 879 fi 880 if [ $success -eq 1 ]; then 881 updated="${updated}${module}\n" 882 else 883 echo_b -e "Failed to fetch updates for $module\n" 884 update_errors=$((update_errors+1)) 885 fi 886 done 887 888 # Then update using merge 889 for module in $(echo -en "$updated"); do 890 test -d "$mm/$module" && require_wc "$module" || continue; 891 cd "$mm/$module" 892 success=0 893 if [ -d .svn ]; then 894 echo "Updating $module" 895 if [ $FORCE -eq 1 ]; then 896 svn update --force --non-interactive --trust-server-cert && success=1 897 else 898 svn update && success=1 899 fi 900 elif [ -d .git ] && [ "$(git remote)" != "" ]; then 901 tracking_branch=$(get_tracking_branch) 902 echo "Updating $module" 903 if [ -z $tracking_branch ]; then 904 echo "Could not resolve remote tracking branch, code will not be updated." 905 elif [ $FORCE -eq 1 ]; then 906 git reset --hard $tracking_branch && git submodule update --init --recursive && success=1 907 else 908 git merge $tracking_branch && git submodule update --init --recursive && success=1 909 fi 910 elif [ -d .hg ]; then 911 echo "Updating $module" 912 hg pull && hg update && success=1 913 else 914 success=1 915 fi 916 echo 917 if [ $success -ne 1 ]; then 918 error "Error occurred while updating $module\n" 919 update_errors=$((update_errors+1)) 920 fi 921 done 922 remove_dead_links 923 deploy_errors=0 924 for module in $(ls -1 "$mm"); do 925 test -d "$mm/$module" && require_wc "$module" || continue; 926 if apply_modman_file "$mm/$module/modman"; then 927 echo -e "Deployment of '$module' complete.\n" 928 if [ $NOLOCAL -eq 0 -a -r "$mm/$module/modman.local" ]; then 929 apply_modman_file "$mm/$module/modman.local" && echo "Applied local modman file for $module" 930 fi 931 else 932 error "Error occurred while deploying '$module'.\n" 933 deploy_errors=$((deploy_errors+1)) 934 fi 935 done 936 echo "Updated all modules with $update_errors update errors and $deploy_errors deploy errors." 937 exit 0 938 939########################### 940# Handle "repair" command 941elif [ "$action" = "repair" ]; then 942 echo "Repairing links, do not interrupt." 943 mv "$mm" "$mm-repairing" || { error "Could not temporarily rename .modman directory."; exit 1; } 944 remove_dead_links 945 mv "$mm-repairing" "$mm" || { error "Could not restore .modman directory."; exit 1; } 946 for module in $(ls -1 "$mm"); do 947 test -d "$mm/$module" && require_wc "$module" || continue; 948 remove_basedirs "$module" && 949 apply_modman_file "$mm/$module/modman" && 950 echo -e "Repaired $module.\n" 951 if [ $NOLOCAL -eq 0 -a -r "$mm/$module/modman.local" ]; then 952 apply_modman_file "$mm/$module/modman.local" && echo "Applied local modman file for $module" 953 fi 954 done 955 exit 0 956 957########################### 958# Handle "clean" command 959elif [ "$action" = "clean" ]; then 960 echo "Cleaning broken links." 961 NOCLEAN=0 962 remove_dead_links 963 exit 0 964fi 965 966############################################# 967# Handle all other module-specific commands 968############################################# 969 970REGEX_ACTION='^(update|deploy|undeploy|skip|unskip|checkout|clone|hgclone|link|remove|automodman)$' 971REGEX_NEW_MODULE='^(checkout|clone|hgclone|link)$' 972REGEX_BAD_MODULE="($REGEX_ACTION| )" 973REGEX_MODULE='^[a-zA-Z0-9_-]+$' 974 975if ! [[ "$action" =~ $REGEX_ACTION ]]; then 976 echo "Invalid action specified: $action" 977 exit 1 978fi 979 980module='' 981src='' 982 983# If valid module is specified on command line 984if [ -z "$module" -a -n "$1" -a -d "$mm/$1" ] || [[ "$1" =~ $REGEX_MODULE ]]; then 985 module=$1; shift 986fi 987 988# If module name is not given 989if [ -z "$module" ]; then 990 # Extract from end of next argument assuming it is the repo location 991 if [[ "$action" =~ $REGEX_NEW_MODULE ]]; then 992 if [ $# -eq 1 -o "${2:0:1}" = "-" ]; then 993 module=${1%.git} # strip .git if specified 994 module=${module//:/\/} # replace : with / for basename in case of git SSH 995 module=$(basename "$module") # get the end-most part of the repo url 996 fi 997 998 # Discover if modman is run from within a module directory 999 else 1000 cd "$_pwd" 1001 while [ $(dirname "$mm") != "$(pwd)" ] && [ "$(pwd)" != "/" ]; do 1002 modpath=$(pwd) 1003 if [ $(dirname "$modpath") = "$mm" ]; then 1004 module=$(basename "$modpath") 1005 break 1006 fi 1007 cd .. 1008 done 1009 fi 1010fi 1011 1012# Module must be next argument 1013if [ -z "$module" -a -n "$1" ]; then 1014 module=$1; shift 1015fi 1016if [ -z "$module" ]; then 1017 echo "Not enough arguments (no module specified)" 1018 exit 1 1019fi 1020 1021# Get optional args again (allow them to come after the module name) 1022while true; do 1023 case "$1" in 1024 --force) FORCE=1; shift ;; 1025 --no-local) NOLOCAL=1; shift ;; 1026 --no-clean) NOCLEAN=1; shift ;; 1027 --no-shell) NOSHELL=1; shift ;; 1028 --copy) COPY=1; shift ;; 1029 --basedir) 1030 shift; basedir="$1"; shift 1031 if ! [ -n "$basedir" -a -d "$root/$basedir" ]; then 1032 echo "Specified --basedir does not exist: $basedir"; exit 1 1033 fi 1034 ;; 1035 *) 1036 break 1037 esac 1038done 1039 1040cd "$_pwd"; # restore old root 1041wc_dir=$mm/$module # working copy directory for module 1042wc_desc=$wc_dir/modman # path to modman structure descriptor file 1043 1044case "$action" in 1045 1046 automodman) 1047 run_automodman "$module" "$mm" "$wc_dir" "$wc_desc" 1048 ;; 1049 update) 1050 require_wc "$module" || exit 1 1051 cd "$wc_dir" 1052 success=0 1053 if [ -d .svn ]; then 1054 if [ $FORCE -eq 1 ]; then 1055 svn update --force --non-interactive --trust-server-cert && success=1 1056 else 1057 svn update && success=1 1058 fi 1059 elif [ -d .git ]; then 1060 if [ $FORCE -eq 1 ]; then 1061 tracking_branch=$(git rev-parse --symbolic-full-name --abbrev-ref @{u}) 1062 [[ -n $tracking_branch ]] || { echo "Could not resolve remote tracking branch."; exit 1; } 1063 git fetch --force && git reset --hard $tracking_branch && git submodule update --init --recursive && success=1 1064 else 1065 git pull && git submodule update --init --recursive && success=1 1066 fi 1067 elif [ -d .hg ]; then 1068 hg pull && hg update && success=1 1069 fi 1070 [ $success -eq 1 ] || { echo_b "Failed to update working copy of '$module'."; exit 1; } 1071 1072 remove_dead_links 1073 apply_modman_file "$wc_desc" && echo "Update of $module complete." 1074 if [ $NOLOCAL -eq 0 -a -r "$wc_desc.local" ]; then 1075 apply_modman_file "$wc_desc.local" && echo "Applied local modman file for $module" 1076 fi 1077 ;; 1078 1079 checkout|clone|hgclone|link) 1080 FORCE=1 1081 cd "$mm" 1082 if [[ "$module" =~ $REGEX_BAD_MODULE ]]; then 1083 echo "You cannot $action a module with a name matching $REGEX_BAD_MODULE."; exit 1 1084 fi 1085 if [ -d "$wc_dir" ]; then 1086 echo "A module named '$module' has already been checked out."; exit 1 1087 fi 1088 1089 if [ -z "$src" ]; then 1090 src="$1"; shift 1091 fi 1092 if [ -z "$src" -o "$src" = "--" ]; then 1093 echo "You must specify a source for the '$action' command."; exit 1 1094 fi 1095 if [ "$1" = "--" ]; then shift; fi 1096 1097 success=0 1098 verb='' 1099 if [ "$action" = "checkout" ]; then 1100 verb='checked out' 1101 svn checkout "$src" $@ "$module" && success=1 1102 elif [ "$action" = "clone" ]; then 1103 verb='cloned' 1104 git clone --recursive "$src" $@ "$module" && success=1 1105 elif [ "$action" = "hgclone" ]; then 1106 verb='cloned' 1107 hg clone "$src" $@ "$module" && success=1 1108 elif [ "$action" = "link" ]; then 1109 verb='linked' 1110 cd "$mmroot" 1111 if ! [ -d "$src" ]; then 1112 echo "The path specified does not exist or is not a directory." 1113 echo "The module path must either be an absolute path, or relative to $mmroot" 1114 exit 1 1115 fi 1116 if [ "${src:0:1}" != "/" ]; then 1117 src="../$src" 1118 fi 1119 ln -s "$src" ".modman/$module" && success=1 1120 cd "$mm" 1121 fi 1122 1123 if 1124 [ $success -eq 1 ] && 1125 require_wc "$module" && cd "$wc_dir" && 1126 set_basedir "$wc_dir" "$basedir" && 1127 apply_modman_file "$wc_desc" 1128 then 1129 if [ -n "$basedir" ]; then 1130 using_basedir=" using base directory '$basedir'" 1131 fi 1132 echo "Successfully $verb new module '$module'$using_basedir" 1133 else 1134 if [ -d "$wc_dir" ]; then rm -rf "$wc_dir"; fi 1135 error "trying to $action new module '$module', operation cancelled." 1136 exit 1 1137 fi 1138 ;; 1139 1140 deploy) 1141 require_wc "$module" || exit 1 1142 apply_modman_file "$wc_desc" && echo "$module has been deployed under $root" 1143 if [ $NOLOCAL -eq 0 -a -r "$wc_desc.local" ]; then 1144 apply_modman_file "$wc_desc.local" && echo "Applied local modman file for $module" 1145 fi 1146 ;; 1147 1148 undeploy) 1149 require_wc "$module" || exit 1 1150 remove_module_links "$module" || exit 1 1151 ;; 1152 1153 skip) 1154 set_skipped "$module" 1 || exit 1 1155 ;; 1156 1157 unskip) 1158 set_skipped "$module" 0 || exit 1 1159 ;; 1160 1161 remove) 1162 require_wc "$module" || exit 1 1163 rm -rf "$wc_dir" && remove_dead_links && echo "$module has been removed" 1164 ;; 1165 1166 *) 1167 echo -e "$usage" 1168 echo_b "Invalid action: $action" 1169 exit 1 1170 1171esac 1172 1173