├── .gitignore ├── README.md ├── gmm └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | gmm-debug.log 2 | 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SYNOPSIS 2 | git module manager 3 | 4 | # MOTIVATION 5 | Simpler, faster, less complex dependency management! 6 | 7 | Submodules receive much praise and criticism. Yet both depend 8 | on the interpretation of how they should be used; `gmm` focuses 9 | entirely on providing a workflow for dependency management. 10 | 11 | Don't edit submodules — it's not an easy git workflow to manage. 12 | This is also the basis of most submodule criticism. Instead, use 13 | them as immutable dependencies, when you need changes, publish 14 | them from upstream and simply re-install using `gmm`. 15 | 16 | # STATUS 17 | Idea/Work in Progress 18 | 19 | # INSTALL GMM 20 | You can just run this simple one-liner to install. The google- 21 | shortened link points to the raw github user content (paste it 22 | in the url-bar of your browser to verify). The sudo prompt for 23 | your password is the chmod command asking for your permission 24 | to make the file executable. 25 | 26 | ### USING [`CURL`](https://curl.haxx.se/) 27 | ```bash 28 | (curl -sL https://goo.gl/kS3VRE > /usr/local/sbin/gmm && sudo chmod 700 gmm) 29 | ``` 30 | 31 | ### USING [`BPKG`](https://github.com/bpkg/bpkg) 32 | 33 | ```bash 34 | bpkg install -g 0x00A/gmm 35 | ``` 36 | 37 | ### USING [`GIT`](https://git-scm.com/) 38 | 39 | ```bash 40 | git clone git@github.com:0x00A/gmm.git /usr/local/lib/gmm 41 | sudo chmod 700 /usr/local/lib/gmm/gmm 42 | ln -s /usr/local/lib/gmm/gmm /usr/local/bin/gmm 43 | ``` 44 | 45 | # COMMANDS 46 | There are only a handful, because really that's all you 47 | should need. Here is some example output. 48 | 49 | ``` 50 | git module manager v1.1.0 51 | 52 | usage: gmm [options] 53 | 54 | commands: 55 | i, install [-v] [branch] install modules 56 | u, uninstall uninstall modules 57 | ls [cache] list installed or cached packages 58 | cache do stuff with the cache 59 | search "term" [language] search for stuff 60 | 61 | options: 62 | --help, -h show this help information 63 | --version print the version number 64 | --update self update 65 | ``` 66 | 67 | ## INSTALL MODULES 68 | Install first clones the repo to your `~/.modules` cache, then 69 | adds it to your project from the cache.This is nice for 70 | performance and offline usage. 71 | 72 | Install will ensure your working tree is clean before adding a 73 | submodule. Then, it will add the submodule (at the specified 74 | branch, or master by default), then commit it for you. 75 | 76 | ``` 77 | $ gmm i someorg/somerepo 78 | [OK] pulled latest from git://github.com/someorg/somerepo.git 79 | [INFO] using cached version 80 | [OK] added the submodule 81 | [OK] submodule installed 82 | ``` 83 | 84 | Once your submodule is installed you will see a `modules` 85 | directory in your project, these files will be flagged as read 86 | only. (This directory can be configured to be called whatever 87 | you want using the `MODULES_LOCAL` variable). 88 | 89 | ## LIST INSTALLED MODULES 90 | 91 | ``` 92 | $ gmm ls 93 | [INFO] listing (/Users/username/myproject) 94 | 95 | [INFO] 📦 foo@master in ./modules/someorg/foo 96 | [INFO] 📦 quxx@0.1.0 in ./modules/someorg/quxx 97 | 98 | [OK] found 2 module(s) in myproject 99 | ``` 100 | 101 | ## LIST CACHED MODULES 102 | The cache will **try** to update every time you install. But if 103 | you want to update all items in your cache, you can run the 104 | `gmm cache update` command. 105 | 106 | ``` 107 | $ gmm ls cache 108 | [INFO] listing cache (/Users/username/.modules) 109 | 110 | [INFO] 📦 foo in /someorg/foo 111 | [INFO] 📦 quxx in /someorgb/quxx 112 | 113 | [OK] found 2 repos 114 | ``` 115 | 116 | ## SEARCH FOR MODULES 117 | Any search takes only a few milliseconds. 118 | 119 | ``` 120 | $ gmm search hyperterm 121 | [OK] searching api.github.com for 'hyperterm' 122 | 123 | 📦 zeit/hyperterm - HTML/JS/CSS Terminal 124 | 7858 ★ https://github.com/zeit/hyperterm 125 | 126 | 📦 sindresorhus/hyperterm-snazzy - Snazzy HyperTerm theme 127 | 164 ★ https://github.com/sindresorhus/hyperterm-snazzy 128 | 129 | 📦 matheuss/hpm - ✨ A plugin manager for HyperTerm ✨ 130 | 108 ★ https://github.com/matheuss/hpm 131 | 132 | 📦 zeit/hyperpower - HyperTerm particle effects extension 133 | 92 ★ https://github.com/zeit/hyperpower 134 | 135 | 📦 sibartlett/hyperterm-1password - 1Password extension for HyperTerm 136 | 80 ★ https://github.com/sibartlett/hyperterm-1password 137 | 138 | 📦 staltz/hyperpunk - A cyberpunk theme for HyperTerm 139 | 64 ★ https://github.com/staltz/hyperpunk 140 | 141 | 📦 mxstbr/hyperterm-spacegray - Spacegray theme for hyperterm 142 | 62 ★ https://github.com/mxstbr/hyperterm-spacegray 143 | 144 | 📦 CWSpear/hyperterm-visor - Open your HyperTerm terminal from anywhere with a global hotkey. 145 | 60 ★ https://github.com/CWSpear/hyperterm-visor 146 | ... 147 | ``` 148 | 149 | 150 | # SETTINGS 151 | Environment variables that can be set in your shell 152 | 153 | ### `MODULES_HOME` 154 | By default is `~/.modules`. 155 | 156 | ### `MODULES_LOCAL` 157 | Will determine what the local modules directory should be named. 158 | For example, you might want this to be called `node_modules` or 159 | `cxx_modules` instead of the default which is just `modules`. 160 | 161 | ### `PROTOCOL` 162 | By default is `git`, but can be `https`, etc. 163 | 164 | ### `HOST` 165 | By default is `github.com`, set to whatever your git server is. 166 | 167 | -------------------------------------------------------------------------------- /gmm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | MODULES_HOME=${MODULES_HOME:-$HOME/.modules} 3 | MODULES_LOCAL=${MODULES_LOCAL:-modules} 4 | SEARCH_API=${SEARCH_API:-api.github.com} 5 | 6 | UI_GRAY="\033[1;37m" 7 | UI_BLUE="\033[1;34m" 8 | UI_DARKGRAY="\033[90m" 9 | UI_RED="\033[0;31m" 10 | UI_GREEN="\033[0;32m" 11 | UI_YELLOW="\033[1;33m" 12 | UI_WHITE="\033[1;37m" 13 | UI_NONE="\033[0m" 14 | 15 | function ok { 16 | printf "${UI_NONE}[${UI_GREEN}OK${UI_NONE}] $@\r\n" 17 | } 18 | 19 | function info { 20 | printf "${UI_NONE}[${UI_BLUE}INFO${UI_NONE}] $@\n" 21 | } 22 | 23 | function fail { 24 | printf "${UI_NONE}[${UI_RED}FAIL${UI_NONE}] $@\n" 25 | } 26 | 27 | function assert { 28 | if [ $? -gt 1 ]; then 29 | fail "$@" 30 | exit 1 31 | elif [ $? -eq 1 ]; then 32 | ok "$@" 33 | fi 34 | } 35 | 36 | function ex { 37 | if [ -n "$VERBOSE" ]; then 38 | "$@" 39 | else 40 | "$@" >> $LOG 2>&1 41 | fi 42 | 43 | } 44 | 45 | function ensure_home { 46 | stat $MODULES_HOME &>/dev/null 47 | if [ $? -gt 0 ]; then 48 | mkdir $MODULES_HOME 49 | assert "initialized ${MODULES_HOME}" 50 | fi 51 | } 52 | 53 | function ensure_repo { 54 | git status &>/dev/null 55 | if test $? -eq 128; then 56 | ex git init 57 | assert "initialized as git repo" 58 | fi 59 | } 60 | 61 | function require { 62 | local installer="brew" 63 | local instruction="try running" 64 | 65 | if [ `uname` = "Linux" ]; then 66 | installer="apt-get" 67 | priv="try running sudo" 68 | fi 69 | 70 | for var in "$@"; do 71 | local bin=`which $var` 72 | if [ -z $bin ]; then 73 | fail "$instruction \"$installer install $var\"." 74 | exit 1 75 | fi 76 | done 77 | } 78 | 79 | urlencode() { 80 | local string="${1}" 81 | local strlen=${#string} 82 | local encoded="" 83 | local pos c o 84 | 85 | for (( pos=0 ; pos [options]") 109 | 110 | commands: 111 | i, install [-v] [branch] install modules 112 | u, uninstall uninstall modules 113 | ls [cache] list installed or cached packages 114 | cache do stuff with the cache 115 | search "term" [language] search for stuff 116 | 117 | options: 118 | --help, -h show this help information 119 | --version print the version number 120 | --update self update 121 | 122 | EOF 123 | } 124 | 125 | # 126 | # try to update a submodule 127 | # 128 | function gmm_update { 129 | ensure_repo 130 | 131 | ex git submodule update --recursive 132 | assert "submodule updated recursively" 133 | ok "$1 is up to date" 134 | } 135 | 136 | # 137 | # try to list the modules that are installed in this repo 138 | # 139 | function gmm_ls { 140 | ensure_repo "silent" 141 | 142 | local modules=`git submodule foreach --quiet --recursive \ 143 | 'echo $toplevel/$path@$(git symbolic-ref --short HEAD)'` 144 | 145 | local -a modules=($modules) 146 | local -i count=${#modules[*]} 147 | local -i lastIndex=$(($count - 1)) index 148 | 149 | if [ $count = 0 ] 150 | then 151 | ok "there are no modules, use \"gmm install \" to add one." 152 | exit 0 153 | fi 154 | 155 | info "listing (${PWD})\n" 156 | 157 | for ((index=0; index<=lastIndex; index++)); do 158 | local file=${modules[$index]} 159 | local location=${file%%"@"*} 160 | local branch=${file#*"@"} 161 | local relative=${location/$PWD/} 162 | 163 | info "📦 $(basename $relative)@$branch in .$relative" 164 | done 165 | 166 | local base=$(basename $PWD) 167 | printf "\r\n" 168 | ok "found ${UI_WHITE}$count${UI_NONE} module(s) in ${UI_WHITE}$base${UI_NONE}" 169 | printf "\r\n" 170 | } 171 | 172 | # 173 | # Visit all git repos in the cache, list them 174 | # so that we know what we have on this machine. 175 | # 176 | function gmm_ls_cache { 177 | ensure_repo 178 | let refresh=$1 179 | let counter=0 180 | 181 | function update { 182 | local d="$1" 183 | if [ -d "$d" ]; then 184 | cd $d > /dev/null 185 | if [ -d ".git" ]; then 186 | 187 | if [ -z $refresh ]; then 188 | ex git pull --all 189 | info "📦 $d updated" 190 | else 191 | info "📦 $d in ${PWD/$MODULES_HOME}" 192 | fi 193 | ((counter++)) 194 | else 195 | scan * 196 | fi 197 | cd .. > /dev/null 198 | fi 199 | } 200 | 201 | function scan { 202 | for x in $*; do 203 | update "$x" 204 | done 205 | } 206 | 207 | info "listing cache (${MODULES_HOME})\n" 208 | scan $MODULES_HOME 209 | printf "\n" 210 | ok "found ${counter} repos" 211 | } 212 | 213 | function gmm_search { 214 | require "curl" "jq" 215 | 216 | IFS=$'\n' 217 | local query=`urlencode $1` 218 | local language=`urlencode ${2:-JavaScript}` 219 | local sort=${3:-stars} 220 | 221 | ok "searching ${SEARCH_API} for '${query}'\n" 222 | local dest="https://${SEARCH_API}/search/repositories" 223 | local search="q=${query}+language:${language}&sort=${sort}&order=desc" 224 | 225 | local results=`curl -s "${dest}?${search}" |\ 226 | jq -r '.items[] | .owner.login,\ 227 | .name,\ 228 | if .description == "" then "(no description)" else .description end,\ 229 | .html_url,\ 230 | .watchers'` 231 | 232 | local -a modules=($results) 233 | local -i count=${#modules[*]} 234 | local -i lastIndex=$(($count - 1)) index 235 | 236 | for ((index=0; index<=lastIndex; index++)); do 237 | 238 | local owner=${modules[index]}; index=index+1 239 | local module=${modules[index]}; index=index+1 240 | local description=${modules[index]}; index=index+1 241 | local url=${modules[index]}; index=index+1 242 | local stars=${modules[index]}; 243 | 244 | printf "📦 ${UI_BLUE}%s/%s${UI_NONE} - %s\n ${UI_DARKGRAY} %s ★ %s ${UI_NONE}\n\n" \ 245 | $owner $module $description $stars $url 246 | 247 | done 248 | } 249 | 250 | # 251 | # Check if the repo exists, if it does not, clone it 252 | # if it does exist, just update it and then add it as 253 | # a submodule. 254 | # 255 | function gmm_install { 256 | ensure_repo 257 | 258 | if test -n "$(git status --porcelain)"; then 259 | fail "first you need to commit your changes" 260 | exit 1 261 | fi 262 | 263 | # 264 | # ensure that we ignore our log file, if there is no .gitignore 265 | # or there it doesn't contain the name of our log gile, add it. 266 | # 267 | if [ ! -f ".gitignore" ] || [ $(cat .gitignore) != *"gmm-debug.log"* ]; then 268 | echo "gmm-debug.log" >> ".gitignore" 269 | fi 270 | 271 | local protocol=${PROTOCOL:-git} 272 | local host=${HOST:-github.com} 273 | local branch=${2:-master} 274 | local dest=$MODULES_LOCAL/$1 #${1#*"/"*} 275 | local src=$protocol://$host/$1.git 276 | 277 | local oldpwd=$PWD 278 | 279 | if [ ! -d $MODULES_HOME/$1 ] 280 | then 281 | 282 | ex git clone --depth 1 --recursive $src $MODULES_HOME/$1 283 | 284 | cd $MODULES_HOME/$1 285 | 286 | for remote in `git branch -r `; do 287 | local br=${remote#* } 288 | if [ "$br" != "->" ] && [ "$br" != "HEAD" ]; then 289 | ex git branch --track "$br" 290 | fi 291 | done 292 | 293 | cd $oldpwd 294 | 295 | # error code 128 means that we are either offline or the 296 | # repo cant be found, either way, we should fail and exit. 297 | if [ $? = 128 ]; then 298 | fail "not available (offline?)" 299 | fi 300 | 301 | else 302 | cd $MODULES_HOME/$1 303 | 304 | ex git pull 305 | 306 | if [ $? -eq 0 ]; then 307 | info "cache updated" 308 | else 309 | info "using cached version" 310 | fi 311 | 312 | cd $oldpwd 313 | fi 314 | 315 | ex git config -f .gitmodules submodule.$MODULES_LOCAL/$1.ignore dirty 316 | assert "ignore changes to module" 317 | 318 | # if we got this far, we can add a submodule out of the cache. 319 | ex git submodule add -b $branch $MODULES_HOME/$1 $dest 320 | if [ $? -gt 1 ]; then 321 | assert "submodule added" 322 | elif [ $? -eq 1 ]; then 323 | # the submodule is already in the index, we 324 | # should try to update it instead of add it 325 | ex cd $MODULES_LOCAL/$1 326 | gmm_update $1 327 | ex cd $oldpwd 328 | fi 329 | 330 | ex chmod -R a-w $MODULES_LOCAL/$1 331 | assert "set submodule to read-only" 332 | 333 | ex git add -A . 334 | ex git commit -am "added git submodule $1" 335 | assert "committed submodule" 336 | } 337 | 338 | # 339 | # Check if the submodule is installed, if it is, remove it 340 | # 341 | function gmm_uninstall { 342 | ensure_repo 343 | 344 | local dest=$MODULES_LOCAL/$1 345 | 346 | ex git submodule deinit -f $dest 347 | assert "deinitialized submodule" 348 | 349 | rm -rf .git/$MODULES_LOCAL/$dest 350 | assert "submodule files removed" 351 | 352 | git submodule update 353 | assert "submodules updated" 354 | 355 | ok "$dest successfully removed" 356 | } 357 | 358 | function update { 359 | 360 | local oldpwd=$PWD 361 | local source="https://github.com/0x00A/gmm" 362 | local dest="/usr/local/lib/gmm" 363 | 364 | stat $dest &>/dev/null 365 | if [ $? -gt 0 ]; then 366 | git clone $source $dest 367 | assert "installed" 368 | cd $dest 369 | sudo chmod 700 gmm 370 | assert "permissions updated" 371 | else 372 | cd $dest 373 | ex git pull 374 | assert "updated" 375 | fi 376 | 377 | cd $oldpwd 378 | ok "success" 379 | } 380 | 381 | # 382 | # handle arguments parsing 383 | # 384 | while test $# -gt 0; do 385 | case "$1" in 386 | cache) 387 | shift 388 | if [ "$1" = "update" ]; then 389 | gmm_ls_cache $1 390 | fi 391 | break 392 | ;; 393 | search) 394 | shift 395 | gmm_search $@ 396 | break 397 | ;; 398 | ls) 399 | shift 400 | if [ "$1" = "cache" ]; then 401 | gmm_ls_cache 402 | exit 0 403 | fi 404 | gmm_ls 405 | break 406 | ;; 407 | -h|--help) 408 | help 409 | exit 0 410 | ;; 411 | i|install) 412 | shift 413 | if [ $1 = "-v" ]; then 414 | shift 415 | VERBOSE=1 416 | fi 417 | 418 | if test $# -gt 0; then 419 | gmm_install $@ 420 | else 421 | echo "no user/repo specified" 422 | exit 1 423 | fi 424 | break 425 | ;; 426 | u|uninstall) 427 | shift 428 | if [ $1 = "-v" ]; then 429 | shift 430 | VERBOSE=1 431 | fi 432 | 433 | if test $# -gt 0; then 434 | gmm_uninstall $@ 435 | else 436 | echo "no user/repo specified" 437 | exit 1 438 | fi 439 | break 440 | ;; 441 | --update) 442 | update 443 | break 444 | ;; 445 | --version) 446 | echo $VERSION 447 | break 448 | ;; 449 | *) 450 | fail "unknown command" 451 | help 452 | break 453 | ;; 454 | esac 455 | done 456 | 457 | # vi: expandtab sw=2 ts=2 458 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gmm", 3 | "version": "1.1.0", 4 | "description": "Git Module Manager", 5 | "global": "1", 6 | "install": "install -b gmm ${PREFIX:-/usr/local}/bin/gmm", 7 | "scripts": [ "gmm" ] 8 | } 9 | --------------------------------------------------------------------------------