-
Notifications
You must be signed in to change notification settings - Fork 0
/
cross
executable file
·182 lines (160 loc) · 5.15 KB
/
cross
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
#!/bin/bash
shopt -s expand_aliases
## git workflow to crips git repositories
## NOTESa
## to share features to upstream, commit on upstream branch with `cd patch; git cherry-pick ##### -Xtheirs`
## --untracked-files=no
W=. # git worktree
declare -a FETCHED=('')
export _gitpth=$(which git)
# DEFAULTS
export CROSS_DEFAULT_BRANCH=${CROSS_DEFAULT_BRANCH:-master}
export CROSS_REBASE_ORIGIN=${CROSS_REBASE_ORIGIN:-false} # to rebase local branch with origin
export CROSS_FETCH_DEPTH=${CROSS_FETCH_DEPTH:-20}
export MAGENTA='\033[0;95m'
export YELLOW='\033[1;33m'
export NC='\033[0m' # No Color'
# KEYWORD Fn available in Cross file
# setup git config
setup() {
# require git >=2.20
git config advice.addEmbeddedRepo false
git config extensions.worktreeConfig true
git worktree prune
}
# use, is virtual function for DSL like syntax to configure sources
# it add upstream as remote to tracking branch
use() {
[[ -n "$1" ]] || say "USE called without an position argument 1: remote name"
[[ -n "$2" ]] || say "USE called without an position argument 2: repository url"
name=$1
[[ $(git remote show | grep $name) ]] ||
_git remote add $@
}
# remove, stop tracking a patched source
remove() {
local branch=$(git --git-dir=.git/worktrees/$1 rev-parse --abbrev-ref HEAD)
_git worktree remove --force $1
_git branch --force -D $branch
}
# patch, track 'origin/path' as branch and checkout it to your git worktree
patch() {
# origin:path/to/folder [path/in/my/repo] [-b branch]
# core:habitat/corends coredns -b master
local from=$1
local orig=$(cut -d: -f1 <<<"$from") # origin repository `use` name in Cross file
local opth=$(cut -d: -f2 <<<"$from") # path in origin repository
local path=${2:-$opth} # path in local repository
local branch=${4:-$CROSS_DEFAULT_BRANCH} # branch/tag at origin
local fdepth=${CROSS_FETCH_DEPTH}
# skip already processed dependencies
[[ "${FETCHED[*]}" =~ $path ]] && return 0
# fixture
local opth=${opth#/} # avoid leading slash
local path=${path#/} # avoid leading slash
# check existence of local branch tracking upstream
_branch_exist() {
git rev-parse --verify "$orig/$branch/$opth" &>/dev/null; [[ $? -eq 0 ]]
}
_rebase_active() {
( test -d "$(git rev-parse --git-path rebase-merge)" || test -d "$(git rev-parse --git-path rebase-apply)" )
}
if ! _branch_exist ; then # first time only
# set tracking and fetch from remote/branch
say "Tracking $orig:$opth (branch:$branch) at $path"
_git fetch --prune --depth=$fdepth $orig $branch:$orig/$path
[[ -e $W/$path ]] && mv $W/$path $W/$path.crossed # backup if exist locally
# add worktree as branch per local path
_git worktree add --no-checkout -B "$orig/$branch/$opth" $W/$path --track $orig/$branch
pushd $W/$path
sparse_checkout=$(git rev-parse --git-path info/sparse-checkout)
# configure $name into info/sparse-checkout
if ! [[ $(cat $sparse_checkout 2>/dev/null) =~ ^/$opth/ ]]; then # first time - configure
_git config --worktree --bool core.sparseCheckout true
_git config --worktree --path core.worktree $PWD/..
_git config --worktree status.showUntrackedFiles no
[[ -e $sparse_checkout ]] ||
mkdir -p $(dirname $sparse_checkout)
echo "/${opth}/" | tee -a $sparse_checkout >/dev/null
fi
_git checkout
popd
# add to revision history
_git --git-dir=.git --work-tree=. add $W/$path
elif [[ -e $W/$path && _branch_exist ]]; then
# sync with upstream repo
pushd $W/$path
_git fetch --prune --depth=$fdepth $orig $branch
if ${CROSS_REBASE_ORIGIN}; then
say "$path ($orig)"
git config --worktree status.showUntrackedFiles no
_rebase_active && {
COLOR=$YELLOW say "$W/$path is has rebase in progress. Skipped.";
} || {
# stash, rebase, pop
_git --git-dir=$(sed 's/gitdir: //g' .git) stash clear
_git --git-dir=$(sed 's/gitdir: //g' .git) stash -q
_git rebase
_git --git-dir=$(sed 's/gitdir: //g' .git) stash pop -q || true
}
fi
popd
fi
FETCHED+=($path)
# placeholder
# cross_post_hook $path
}
# STAFF AROUND
_git() {
${VERBOSE:-false} && echo -e "${COLOR:-$YELLOW}git $@${NC}" || true
$_gitpth "$@"
}
repo_is_clean() {
git diff-index --quiet HEAD --
}
pushd() {
export OLDPW=$PWD
cd "$@" >/dev/null
}
popd() {
cd - >/dev/null
}
say() {
(echo >&2 -e "\n${COLOR:-$MAGENTA}$1${NC}")
[[ -z ${2:-''} ]] || exit $2
}
ask() {
local prompt default reply
while true; do
if [[ "${2:-}" =~ ^Y ]]; then
prompt="Y/n"
default=Y
elif [[ "${2:-}" =~ ^N ]]; then
prompt="y/N"
default=N
fi
say "\n$1 [$prompt]"
read reply
reply=${reply:-$default}
case "$reply" in
Y* | y*) return 0 ;;
N* | n*) return 1 ;;
*) return 1 ;;
esac
done
}
# MAIN
# continues if not sourced
if [[ "$BASH_SOURCE" == "$0" ]]; then
set -eu -o pipefail
setup
if [[ $# -gt 0 ]]; then
# exec individual action
fn=$1
shift
$fn $@
else
repo_is_clean || ask "There are uncommitted changes in the repository. Continue?" Y
[ -e Cross* ] && source Cross* || echo "No Cross* file found"
fi
fi