Last active
March 25, 2019 05:39
-
-
Save drmingdrmer/ff72ee00df7b7a359263fc3b15d093b0 to your computer and use it in GitHub Desktop.
add/update git sub repository with git-subtree by reading a config file
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
clr() | |
{ | |
# clr light read blabla | |
local black=0 | |
local white=7 | |
local red=1 | |
local green=2 | |
local brown=3 | |
local blue=4 | |
local purple=5 | |
local cyan=6 | |
local light="" | |
local color=$1 | |
shift | |
if [ "$color" == "light" ]; then | |
light="tput bold; " | |
color=$1 | |
shift | |
fi | |
local code=$(eval 'echo $'$color) | |
local cmd="${light}tput setaf $code" | |
local color_str="$(eval "$cmd")" | |
echo $color_str"$@""$(tput sgr0)" | |
} | |
shlib_init_colors() | |
{ | |
Black="$( tput setaf 0)" | |
BlackBG="$( tput setab 0)" | |
DarkGrey="$( tput bold; tput setaf 0)" | |
LightGrey="$( tput setaf 7)" | |
LightGreyBG="$( tput setab 7)" | |
White="$( tput bold; tput setaf 7)" | |
Red="$( tput setaf 1)" | |
RedBG="$( tput setab 1)" | |
LightRed="$( tput bold; tput setaf 1)" | |
Green="$( tput setaf 2)" | |
GreenBG="$( tput setab 2)" | |
LightGreen="$( tput bold; tput setaf 2)" | |
Brown="$( tput setaf 3)" | |
BrownBG="$( tput setab 3)" | |
Yellow="$( tput bold; tput setaf 3)" | |
Blue="$( tput setaf 4)" | |
BlueBG="$( tput setab 4)" | |
LightBlue="$( tput bold; tput setaf 4)" | |
Purple="$( tput setaf 5)" | |
PurpleBG="$( tput setab 5)" | |
Pink="$( tput bold; tput setaf 5)" | |
Cyan="$( tput setaf 6)" | |
CyanBG="$( tput setab 6)" | |
LightCyan="$( tput bold; tput setaf 6)" | |
NC="$( tput sgr0)" # No Color | |
} | |
screen_width() | |
{ | |
local chr="${1--}" | |
chr="${chr:0:1}" | |
local width=$(tput cols 2||echo 80) | |
width="${COLUMNS:-$width}" | |
echo $width | |
} | |
hr() | |
{ | |
# generate a full screen width horizontal ruler | |
local width=$(screen_width) | |
printf -vl "%${width}s\n" && echo ${l// /$chr}; | |
} | |
remove_color() | |
{ | |
# remove color control chars from stdin or first argument | |
local sed=gsed | |
which -s $sed || sed=sed | |
local s="$1" | |
if [ -z "$s" ]; then | |
$sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g" | |
else | |
echo "$s" | remove_color | |
fi | |
} | |
text_hr() | |
{ | |
# generate a full screen width sperator line with text. | |
# text_hr "-" "a title" | |
# > a title ----------------------------------------- | |
# | |
# variable LR=l|m|r controls alignment | |
local chr="$1" | |
shift | |
local bb="$(echo "$@" | remove_color)" | |
local text_len=${#bb} | |
local width=$(screen_width) | |
let width=width-text_len | |
local lr=${LR-m} | |
case $lr in | |
m) | |
let left=width/2 | |
let right=width-left | |
echo "$(printf -vl "%${left}s\n" && echo ${l// /$chr})$@$(printf -vl "%${right}s\n" && echo ${l// /$chr})" | |
;; | |
r) | |
echo "$(printf -vl "%${width}s\n" && echo ${l// /$chr})$@" | |
;; | |
*) | |
# l by default | |
echo "$@$(printf -vl "%${width}s\n" && echo ${l// /$chr})" | |
;; | |
esac | |
} | |
SHLIB_LOG_VERBOSE=1 | |
SHLIB_LOG_FORMAT='[$(date +"%Y-%m-%d %H:%M:%S")] $level $title $mes' | |
die() | |
{ | |
err "$@" >&2 | |
exit 1 | |
} | |
die_empty() | |
{ | |
if test -z "$1" | |
then | |
shift | |
die empty: "$@" | |
fi | |
} | |
set_verbose() | |
{ | |
SHLIB_LOG_VERBOSE=${1-1} | |
} | |
log() | |
{ | |
local color="$1" | |
local title="$2" | |
local level="$_LOG_LEVEL" | |
shift | |
shift | |
local mes="$@" | |
local NC="$(tput sgr0)" | |
if [ -t 1 ]; then | |
title="${color}${title}${NC}" | |
level="${color}${level}${NC}" | |
fi | |
eval "echo \"$SHLIB_LOG_FORMAT\"" | |
} | |
dd() | |
{ | |
debug "$@" | |
} | |
debug() | |
{ | |
if [ ".$SHLIB_LOG_VERBOSE" = ".1" ]; then | |
local LightCyan="$(tput bold ; tput setaf 6)" | |
_LOG_LEVEL=DEBUG log "$LightCyan" "$@" >&2 | |
fi | |
} | |
info() | |
{ | |
local Brown="$(tput setaf 3)" | |
_LOG_LEVEL=" INFO" log "$Brown" "$@" | |
} | |
ok() { | |
local Green="$(tput setaf 2)" | |
_LOG_LEVEL=" OK" log "${Green}" "$@" | |
} | |
err() { | |
local Red="$(tput setaf 1)" | |
_LOG_LEVEL="ERROR" log "${Red}" "$@" | |
} | |
git_hash() | |
{ | |
git rev-parse $1 \ | |
|| die "'git_hash $@'" | |
} | |
git_is_merge() | |
{ | |
test $(git cat-file -p "$1" | grep "^parent " | wc -l) -gt 1 | |
} | |
git_parents() | |
{ | |
git rev-list --parents -n 1 ${1-HEAD} | { read self parents; echo $parents; } | |
} | |
git_rev_list() | |
{ | |
# --parents | |
# print parent in this form: | |
# <commit> <parent-1> <parent-2> .. | |
git rev-list \ | |
--reverse \ | |
--topo-order \ | |
--default HEAD \ | |
--simplify-merges \ | |
"$@" \ | |
|| die "'git rev-list $@'" | |
} | |
git_tree_hash() | |
{ | |
git rev-parse "$1^{tree}" | |
} | |
git_ver() | |
{ | |
local git_version=$(git --version | awk '{print $NF}') | |
local git_version_1=${git_version%%.*} | |
local git_version_2=${git_version#*.} | |
git_version_2=${git_version_2%%.*} | |
printf "%03d%03d" $git_version_1 $git_version_2 | |
} | |
git_working_root() | |
{ | |
git rev-parse --show-toplevel | |
} | |
git_rev_exist() | |
{ | |
git rev-parse --verify --quiet "$1" >/dev/null | |
} | |
git_branch_default_remote() | |
{ | |
local branchname=$1 | |
git config --get branch.${branchname}.remote | |
} | |
git_branch_default_upstream_ref() | |
{ | |
local branchname=$1 | |
git config --get branch.${branchname}.merge | |
} | |
git_branch_default_upstream() | |
{ | |
git rev-parse --abbrev-ref --symbolic-full-name "$1"@{upstream} | |
# OR | |
# git_branch_default_upstream_ref "$@" | sed 's/^refs\/heads\///' | |
} | |
git_branch_exist() | |
{ | |
git_rev_exist "refs/heads/$1" | |
} | |
git_head_branch() | |
{ | |
git symbolic-ref --short HEAD | |
} | |
git_commit_date() | |
{ | |
# git_commit_date author|commit <ref> [date-format] | |
# by default output author-date | |
local what_date="%ad" | |
if [ "$1" = "commit" ]; then | |
# commit date instead of author date | |
what_date="%cd" | |
fi | |
shift | |
local ref=$1 | |
shift | |
local fmt="%Y-%m-%d %H:%M:%S" | |
if [ "$#" -gt 0 ]; then | |
fmt="$1" | |
fi | |
shift | |
git log -n1 --format="$what_date" --date=format:"$fmt" "$ref" | |
} | |
git_commit_copy() | |
{ | |
# We're going to set some environment vars here, so | |
# do it in a subshell to get rid of them safely later | |
dd copy_commit "{$1}" "{$2}" "{$3}" | |
git log -1 --pretty=format:'%an%n%ae%n%ad%n%cn%n%ce%n%cd%n%s%n%n%b' "$1" | | |
( | |
read GIT_AUTHOR_NAME | |
read GIT_AUTHOR_EMAIL | |
read GIT_AUTHOR_DATE | |
read GIT_COMMITTER_NAME | |
read GIT_COMMITTER_EMAIL | |
read GIT_COMMITTER_DATE | |
export GIT_AUTHOR_NAME \ | |
GIT_AUTHOR_EMAIL \ | |
GIT_AUTHOR_DATE \ | |
GIT_COMMITTER_NAME \ | |
GIT_COMMITTER_EMAIL \ | |
GIT_COMMITTER_DATE | |
# (echo -n "$annotate"; cat ) | | |
git commit-tree "$2" $3 # reads the rest of stdin | |
) || die "Can't copy commit $1" | |
} | |
git_object_type() | |
{ | |
# $0 ref|hash | |
# output "commit", "tree" etc | |
git cat-file -t "$@" 2>/dev/null | |
} | |
git_object_add_by_commit_path() | |
{ | |
# add an blob or tree object to target_path in index | |
# the object to add is specified by commit and path | |
local target_path="$1" | |
local src_commit="$2" | |
local src_path="$3" | |
local src_dir="$(dirname "$src_path")/" | |
local src_name="$(basename "$src_path")" | |
local src_treeish="$(git rev-parse "$src_commit:$src_dir")" | |
git_object_add_by_tree_name "$target_path" "$src_treeish" "$src_name" | |
} | |
git_object_add_by_tree_name() | |
{ | |
# add an blob or tree object to target_path in index | |
local target_path="$1" | |
local src_treeish="$2" | |
local src_name="$3" | |
dd "arg: target_path: ($target_path) src_treeish: ($src_treeish) src_name: ($src_name)" | |
local target_dir="$(dirname $target_path)/" | |
local target_fn="$(basename $target_path)" | |
local treeish | |
if [ -z "$src_name" ] || [ "$src_name" = "." ] || [ "$src_name" = "./" ]; then | |
treeish="$src_treeish" | |
else | |
treeish=$(git ls-tree "$src_treeish" "$src_name" | awk '{print $3}') | |
if [ -z "$treeish" ]; then | |
die "source treeish not found: in tree: ($src_treeish) name: ($src_name)" | |
fi | |
fi | |
dd "hash of object to add is: $treeish" | |
if [ "$(git_object_type $treeish)" = "blob" ]; then | |
# the treeish imported is a file, not a dir | |
# first create a wrapper tree or replace its containing tree | |
dd "object to add is blob" | |
local dir_treeish | |
local target_dir_treeish="$(git rev-parse "HEAD:$target_dir")" | |
if [ -n "target_dir_treeish" ]; then | |
dir_treeish="$(git rev-parse "HEAD:$target_dir")" | |
dd "target dir presents: $target_dir" | |
else | |
dd "target dir absent" | |
dir_treeish="" | |
fi | |
treeish=$(git_tree_add_blob "$dir_treeish" "$target_fn" $src_treeish $src_name) || die create wrapper treeish | |
target_path="$target_dir" | |
dd "wrapper treeish: $treeish" | |
dd "target_path set to: $target_path" | |
else | |
dd "object to add is tree" | |
fi | |
git_treeish_add_to_prefix "$target_path" "$treeish" | |
} | |
git_treeish_add_to_prefix() | |
{ | |
local target_path="$1" | |
local treeish="$2" | |
dd treeish content: | |
git ls-tree $treeish | |
git rm "$target_path" -r --cached || dd removing target "$target_path" | |
if [ "$target_path" = "./" ]; then | |
git read-tree "$treeish" \ | |
|| die "read-tree $target_path $treeish" | |
else | |
git read-tree --prefix="$target_path" "$treeish" \ | |
|| die "read-tree $target_path $treeish" | |
fi | |
} | |
git_tree_add_tree() | |
{ | |
# output new tree hash in stdout | |
# treeish can be empty | |
local treeish="$1" | |
local target_fn="$2" | |
local item_hash="$3" | |
local item_name="$4" | |
{ | |
if [ -n "$treeish" ]; then | |
git ls-tree "$treeish" \ | |
| fgrep -v " $item_name" | |
fi | |
cat "040000 tree $item_hash $target_fn" | |
} | git mktree | |
} | |
git_tree_add_blob() | |
{ | |
# output new tree hash in stdout | |
# treeish can be empty | |
local treeish="$1" | |
local target_fn="$2" | |
local blob_treeish="$3" | |
local blob_name="$4" | |
{ | |
if [ -n "$treeish" ]; then | |
git ls-tree "$treeish" \ | |
| fgrep -v " $target_fn" | |
fi | |
git ls-tree "$blob_treeish" "$blob_name" \ | |
| awk -v target_fn="$target_fn" -F" " '{print $1" "target_fn}' | |
} | git mktree | |
} | |
git_workdir_save() | |
{ | |
local index_hash=$(git write-tree) | |
# add modified file to index and read index tree | |
git add -u | |
local working_hash=$(git write-tree) | |
# restore index tree | |
git read-tree $index_hash | |
echo $index_hash $working_hash | |
} | |
git_workdir_load() | |
{ | |
local index_hash=$1 | |
local working_hash=$2 | |
git_object_type $index_hash || die "invalid index hash: $index_hash" | |
git_object_type $working_hash || die "invalid workdir hash: $working_hash" | |
# First create a temp commit to restore working tree. | |
# | |
# git-read-index to index and git-reset does not work because deleted file in | |
# index does not apply to working tree. | |
# | |
# But there is an issue with this: | |
# git checkout --orphan br | |
# git_workdir_load | |
# would fails, because ORIG_HEAD is not a commit. | |
local working_commit=$(echo "x" | git commit-tree $working_hash) || die get working commit | |
git reset --hard $working_commit || die reset to tmp commit | |
git reset --soft ORIG_HEAD || die reset to ORIG_HEAD | |
git read-tree $index_hash || die "load saved index tree from $index_hash" | |
} | |
git_workdir_is_clean() | |
{ | |
local untracked="$1" | |
if [ "$untracked" == "untracked" ]; then | |
[ -z "$(git status --porcelain)" ] | |
else | |
[ -z "$(git status --porcelain --untracked-files=no)" ] | |
fi | |
} | |
git_copy_commit() | |
{ | |
git_commit_copy "$@" | |
} | |
git_diff_ln_new() | |
{ | |
# output changed line number of a file: <from> <end>; inclusive: | |
# 27 28 | |
# 307 309 | |
# 350 350 | |
# | |
# Usage: | |
# | |
# diff working tree with HEAD: | |
# git_diff_ln_new HEAD -- <fn> | |
# | |
# diff working tree with staged: | |
# git_diff_ln_new -- <fn> | |
# | |
# diff staged(cached) with HEAD: | |
# git_diff_ln_new --cached -- <fn> | |
# | |
# in git-diff output: | |
# for add lines: | |
# @@ -53 +72,8 | |
# | |
# for remove lines: | |
# @@ -155 +179,0 | |
git diff -U0 "$@" \ | |
| grep '^@@' \ | |
| awk '{ | |
# @@ -155 +179,0 | |
# $1 $2 $3 | |
l = $3 | |
gsub("^+", "", l) | |
# add default offset: ",1" | |
split(l",1", x, ",") | |
# inclusive line range: | |
x[2] = x[1] + x[2] - 1 | |
# line remove format: @@ -155, +179,0 | |
# do need to output line range for removed. | |
if (x[2] >= x[1]) { | |
print x[1] " " x[2] | |
} | |
}' | |
} | |
os_detect() | |
{ | |
local os | |
case $(uname -s) in | |
Linux) | |
os=linux ;; | |
*[bB][sS][dD]) | |
os=bsd ;; | |
Darwin) | |
os=mac ;; | |
*) | |
os=unix ;; | |
esac | |
echo $os | |
} | |
mac_ac_power_connection() | |
{ | |
# Connected: (Yes|No) | |
system_profiler SPPowerDataType \ | |
| sed '1,/^ *AC Charger Information:/d' \ | |
| grep Connected: | |
} | |
mac_power() | |
{ | |
# $0 is-battery exit code 0 if using battery. | |
# $0 is-ac-power exit code 0 if using ac power. | |
local cmd="$1" | |
local os=$(os_detect) | |
if [ "$os" != "mac" ]; then | |
err "not mac but: $os" | |
return 1 | |
fi | |
case $cmd in | |
is-battery) | |
mac_ac_power_connection | grep -q No | |
;; | |
is-ac-power) | |
mac_ac_power_connection | grep -q Yes | |
;; | |
*) | |
err "invalid cmd: $cmd" | |
return 1 | |
;; | |
esac | |
} | |
fn_match() | |
{ | |
# $0 a.txt *.txt | |
case "$1" in | |
$2) | |
return 0 | |
;; | |
esac | |
return 1 | |
} | |
main() | |
{ | |
local cmd="${1-update}" | |
local match="$2" | |
case "$cmd" in | |
-h|--help) | |
usage | |
exit 0 | |
;; | |
init) | |
init_config | |
exit 0 | |
;; | |
esac | |
local root=$(git_working_root) | |
[ "x$root" = "x" ] && die 'looking for git repo root' | |
local conf_fn=./.gitsubrepo | |
cd "$root" | |
[ -f .gitsubrepo_refs ] && rm .gitsubrepo_refs | |
> .gitsubrepo_refs | |
local base= | |
local remote= | |
local remote_suffix= | |
local prefix= | |
local url= | |
local ref= | |
local localtag= | |
while read a b c d; do | |
if [ "x$a" = "x" ] || [ "${a:0:1}" = "#" ]; then | |
continue | |
fi | |
# [ base: xp/vim-d ] | |
# declare base dir for following sub repo | |
# | |
# [ remote: https://github.com/ ] | |
# declare remote base url for following sub repo | |
if test "${a:0:1}" = '[' | |
then | |
case $b in | |
base:) | |
base="$c" | |
base="${base%%]}" | |
base="${base%/}/" | |
if test "$base" = "/"; then | |
base= | |
fi | |
;; | |
remote:) | |
remote="$c" | |
;; | |
remote_suffix:) | |
remote_suffix="$c" | |
;; | |
*) | |
echo "invalid tag: $b" | |
exit 1 | |
esac | |
continue | |
fi | |
prefix="$a" | |
url="$b" | |
ref="$c" | |
dir="$d" | |
if [ "x$match" != "x" ] && ! fn_match "$prefix" *$match*; then | |
continue | |
fi | |
if [ ".$cmd" = ".add" ]; then | |
if [ -d "$prefix" ]; then | |
continue | |
fi | |
fi | |
# "xpt drmingdrmer/${prefix} master" is translated to "xpt drmingdrmer/xpt master" | |
url=$(eval "echo $url") | |
if test "${url%/}" != "$url"; then | |
url="${url}$prefix" | |
fi | |
if test "${url:0:8}" != "https://" && test "${url:0:4}" != "git@" | |
then | |
url="${remote}${url}${remote_suffix}" | |
fi | |
fetch_remote "$base$prefix" "$url" "$ref" "$dir" & | |
done<"$conf_fn" | |
wait | |
while read prefix url ref localtag dir; do | |
dd "$prefix, $url, $ref, $localtag, $dir" | |
local tag_hash=$(git_hash "$localtag") || die get hash of $localtag | |
local tag_author_date=$(git_commit_date author "$localtag") || die get authoer date of $localtag | |
local head_hash=$(git_hash HEAD) | |
dd "tag_hash: $tag_hash" | |
dd "head_hash: $head_hash" | |
# clear old $prefix | |
if [ -d "$prefix" ]; then | |
git rm -r "$prefix" >/dev/null || die rm "$prefix" | |
fi | |
dd "add to ($prefix): from ($dir)" | |
git_object_add_by_commit_path "$prefix" "$tag_hash" "$dir" || die git_object_add_by_commit_path | |
# remove not in index files | |
git checkout -- "$prefix" || die "checkout $prefix" | |
# get tree object | |
local tree=$(git write-tree) || die git write-tree | |
dd "tree: $tree" | |
if [ "$tree" = "$(git_tree_hash HEAD)" ]; then | |
info "Nothing changed: $prefix" | |
continue | |
fi | |
local changes= | |
local latest_hash=$(find_latest_squash "$prefix") || die find_latest_squash of "$prefix" | |
if [ -n "$latest_hash" ]; then | |
info "latest commit of $prefix: $latest_hash ($(git_commit_date author "$latest_hash"))" | |
# add 4 space indent | |
changes="$(git --no-pager shortlog $latest_hash..$tag_hash | awk '{print " "$0}')" | |
else | |
info "latest commit of $prefix: not found" | |
fi | |
local commit=$( | |
{ | |
cat <<-END | |
Squashed $prefix $ref:$dir ${tag_hash:0:9} (${tag_author_date}) | |
url: $url | |
ref: $ref | |
sub-dir: $dir | |
localtag: $localtag $tag_hash | |
git-subrepo-dir: $prefix | |
git-subrepo-hash: $tag_hash | |
changes: from ${latest_hash:0:9} ($(git_commit_date author "$latest_hash")) | |
$changes | |
END | |
} | git commit-tree $tree -p $head_hash | |
) || die commit | |
dd "commit: $commit" | |
git merge --ff-only --no-edit --commit $commit | |
done<".gitsubrepo_refs" | |
while read a b c localtag dir; do | |
dd $a, $b, $c, $localtag, $dir | |
git update-ref -d $localtag | |
done<".gitsubrepo_refs" | |
rm .gitsubrepo_refs | |
} | |
init_config() | |
{ | |
local root=$(git_working_root) | |
[ "x$root" = "x" ] && die 'looking for git repo root' | |
( | |
cd $root | |
if [ -f .gitsubrepo ]; then | |
echo ".gitsubrepo already exists" | |
return 0 | |
fi | |
cat >.gitsubrepo <<-END | |
[ remote_suffix: .git ] | |
[ remote: https://github.com/ ] | |
[ base: ] | |
git-subrepo baishancloud/git-subrepo master git-subrepo | |
[ base: deps ] | |
pykit baishancloud/pykit master | |
# git-subrepo | |
# for maintaining sub git repo | |
# https://github.com/baishancloud/git-subrepo | |
END | |
vim .gitsubrepo | |
git add .gitsubrepo | |
echo "git commit to persistent .gitsubrepo config" | |
) | |
} | |
find_latest_squash() | |
{ | |
# learned from git-subtree: find_latest_squash | |
git log --grep="^Squashed $prefix " \ | |
--pretty=format:'START %H%n%b%nEND%n' HEAD | | |
while read a b junk; do | |
case $a in | |
git-subrepo-hash:) | |
echo $b | |
return 0 | |
;; | |
esac | |
done | |
} | |
fetch_remote() | |
{ | |
local prefix="$1" | |
local url="$2" | |
local ref="$3" | |
local dir="$4" | |
local localtag=refs/tags/_gitsubrepo/$prefix | |
# git tag does not allow leading dot. E.g.: "refs/tags/_gitsubrepo/a/.b" | |
localtag="${localtag//./-}" | |
local mes="$url $ref -> $localtag" | |
dd "Start fetching $mes" | |
git fetch --no-tags "$url" "$ref:$localtag" || die fetch "$mes" | |
echo "$prefix $url $ref $localtag" "$dir" >> .gitsubrepo_refs | |
dd "Done fetching $mes" | |
} | |
usage() | |
{ | |
cat <<-END | |
usage: git subrepo | |
Merge sub git repo into sub-directory in a parent git dir with git-submerge. | |
git-subrepo reads config from ".gitsubrepo" resides in the root of parent | |
git working dir. | |
Configure file ".gitsubrepo" syntax: | |
# set base of remote url to "https://github.com/" | |
[ remote: https://github.com/ ] | |
# set base of local dir to "plugin" | |
[ base: plugin ] | |
# <local dir> <remote uri> <ref to fetch> [<dir>] | |
gutter airblade/vim-gitgutter master src | |
# if <remote uri> ends with "/", <local dir> will be added after "/" | |
ansible-vim DavidWittman/ master | |
# change base to "root" | |
[ base: ] | |
# use a specific commit 1a2b3c4 | |
ultisnips SirVer/ 1a2b3c4 | |
# add a sub-directory | |
ultisnips SirVer/ 1a2b3c4 src | |
With above config, "git subrepo" will: | |
- fetch master of https://github.com/DavidWittman/ansible-vim | |
and put it in: | |
<git-root>/plugin/ansible-vim | |
- fetch master of https://github.com/airblade/vim-gitgutter | |
and put it in: | |
<git-root>/plugin/gutter | |
- fetch commit 1a2b3c4 of https://github.com/SirVer/ultisnips | |
and put it in: | |
<git-root>/ultisnips | |
END | |
} | |
main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment