forked from crowbar/crowbar
-
Notifications
You must be signed in to change notification settings - Fork 0
/
dev
executable file
·4078 lines (3879 loc) · 157 KB
/
dev
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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/bin/bash
export LANG="C"
export LC_ALL="C"
GEM_RE='([^0-9].*)-([0-9].*)'
PULL_BUNDLE_RE='Crowbar-Pull-ID: ([0-9a-f]{40})'
PULL_RELEASE_RE='Crowbar-Release: ([^ ]+)'
PULL_TITLE_RE='^(.*)\[([0-9]+)/([0-9]+)\]$'
readonly currdir="$PWD"
export PATH="$PATH:/sbin:/usr/sbin:/usr/local/sbin"
# Ubuntu hack to make sure gem executables are in our path.
[[ -d /var/lib/gems/1.8/bin ]] && export PATH=$PATH:/var/lib/gems/1.8/bin
declare -A DEV_BRANCHES DEV_REMOTE_SOURCES DEV_REMOTE_BRANCHES DEV_ORIGIN_TYPES
declare -A DEV_REMOTE_PRIORITY DEV_REMOTE_URLBASE __AVAILABLE_REMOTES
declare -a DEV_SORTED_REMOTES
declare -A DEV_COMMANDS DEV_SHORT_HELP DEV_LONG_HELP
# The generation of the dev tool this is.
readonly DEV_VERSION=2
# The key -> value mapping in DEV_BRANCHES defines child -> parent relations
# between branches. A branch is considered a root branch if it has itself
# as a parent.
DEV_BRANCHES["master"]="master"
DEV_BRANCHES["openstack-os-build"]="master"
DEV_BRANCHES["hadoop-os-build"]="master"
DEV_BRANCHES["cloudera-os-build"]="master"
github_re='^(https?|ssh|git)://.*github\.com/([^/]+)'
# Associative array to handle skeleton of the state transition machine.
# This is roughly a reverse lookup table for use by ci_get_state().
# The state transition rules are implemented by ci_next_states()
declare -A CI_STATES
CI_STATES["new"]="failed needs-work"
CI_STATES["merge-testing"]="new"
CI_STATES["merge-tested"]="merge-testing"
# These are shortened versions of the real next states.
# The real next states are unit-(testing|tested)-release/build
CI_STATES["unit-testing"]="merge-tested"
CI_STATES["unit-tested"]="unit-testing"
# These are also shortened versions. The real states are:
# (build|smoke)-(testing|tested)-<release>/<build>/<os>
CI_STATES["build-testing"]="merge-tested unit-tested"
CI_STATES["build-tested"]="build-testing"
CI_STATES["smoke-testing"]="build-tested"
CI_STATES["smoke-tested"]="smoke-testing"
# These are the actual states.
CI_STATES["code-reviewing"]="smoke-tested"
CI_STATES["needs-work"]="code-reviewing"
CI_STATES["code-reviewed"]="code-reviewing"
CI_STATES["mergeable"]="code-reviewed"
CI_STATES["merged"]="mergeable"
CI_STATES["failed"]="all"
CI_STATES["closed"]="all"
# DEV_REMOTE_BRANCHES defines what branches in the main Crowbar repository
# should be pulled and synced with what remotes.
# Barclamps do care about remote branches.
DEV_REMOTE_BRANCHES["origin"]="master openstack-os-build hadoop-os-build cloudera-os-build"
VERBOSE=true
# Source our config file if we have one
[[ -f $HOME/.build-crowbar.conf ]] && \
. "$HOME/.build-crowbar.conf"
# Look for a local one.
[[ -f build-crowbar.conf ]] && \
. "build-crowbar.conf"
# Location of the Crowbar checkout we are building from.
[[ $CROWBAR_DIR ]] || CROWBAR_DIR="${0%/*}"
[[ $CROWBAR_DIR = /* ]] || CROWBAR_DIR="$currdir/$CROWBAR_DIR"
[[ -f $CROWBAR_DIR/build_crowbar.sh && -d $CROWBAR_DIR/.git ]] || \
die "$CROWBAR_DIR is not a git checkout of Crowbar!"
[[ $CI_TRACKING_REPO ]] || CI_TRACKING_REPO=ci-tracking
[[ $CROWBAR_TEST_DIR ]] || CROWBAR_TEST_DIR="/tmp/crowbar-dev-test"
[[ $LOCAL_PULL_TRACKING ]] || LOCAL_PULL_TRACKING="$CROWBAR_DIR/.ci-tracking"
[[ $OPEN_PULL_REQUESTS ]] || OPEN_PULL_REQUESTS="$LOCAL_PULL_TRACKING/local"
export CROWBAR_DIR CROWBAR_TEST_DIR LOCAL_PULL_TRACKING
export OPEN_PULL_REQUESTS
. "$CROWBAR_DIR/build_lib.sh" || exit 1
trap - 0 INT QUIT TERM
trap 'rm -rf "$CROWBAR_TMP"' 0 INT QUIT TERM
which gem &>/dev/null || \
die "Rubygems not installed, and some of our helpers need a JSON gem."
gem list -i json &>/dev/null || \
die "JSON gem not installed. Please install it with gem install json."
set -o pipefail
# A little wrapper for git to help people see what dev is doing.
git() {
[[ $SHOW_GIT_OPERATIONS ]] && debug "$PWD: git $@"
command git "$@"
}
# Test to see if a remote is available.
# Filters based on $DEV_AVAILABLE_REMOTES
remote_available() {
# $1 = remote to test.
# Returns 0 if the remote is available, 1 otherwise.
local r urlbase url_re='^(file|https?|ssh|git)://([^/]+)'
local -A remote_hash
if [[ ! $DEV_AVAILABLE_REMOTES ]]; then
crowbar_remote_exists "$1"
return $?
fi
if [[ ${__AVAILABLE_REMOTES[$1]} ]]; then
[[ ${__AVAILABLE_REMOTES[$1]} = true ]]
return $?
fi
# Handle "origin" specially as shorthand for $(origin_remote)
for r in ${DEV_AVAILABLE_REMOTES//origin/$(origin_remote)}; do
crowbar_remote_exists "$r" || \
die "Unknown remote $r in DEV_AVAILABLE_REMOTES!"
urlbase=${DEV_REMOTE_URLBASE[$r]}
[[ $urlbase =~ $url_re ]] || continue
remote_hash["${BASH_REMATCH[2]}"]=true
done
urlbase=${DEV_REMOTE_URLBASE[$1]}
[[ $urlbase =~ $url_re ]] || return 1
if [[ ${BASH_REMATCH[1]} = file || ${remote_hash["${BASH_REMATCH[2]}"]} ]]; then
__AVAILABLE_REMOTES[$1]=true
return 0
else
__AVAILABLE_REMOTES[$1]=false
return 1
fi
}
# The remote with the highest priority is considered to be the "origin" remote.
# This is a function and not a variable because not all the repos we work
# with will have the same origin remotes.
origin_remote() {
local r
for r in "${DEV_SORTED_REMOTES[@]}"; do
git_remote_exists "$r" || continue
echo "$r"
return 0
done
return 1
}
# Check out the appropriate branches in each barclamp needed to switch to
# the new build. This function takes care to minimize the amount of branch
# swizzling that occurs.
switch_barclamps_to_build() {
# $1 = new build
local bc new_head current_head
build_exists "$1" || die "Build $1 does not exist"
for bc in $(barclamps_in_build "$1"); do
[[ -d $CROWBAR_DIR/barclamps/$bc/.git ]] || \
die "Build $1 requires $bc, which is not available locally." \
"dev clone-barclamps should fetch it, if not you may not have access to it."
done
for bc in "$CROWBAR_DIR/barclamps/"*; do
bc=${bc##*/}
new_branch=$(barclamp_branch_for_build "$1" "$bc")
current_head=$(in_barclamp "$bc" git symbolic-ref HEAD 2>/dev/null || \
in_barclamp "$bc" git rev-parse HEAD)
if [[ $new_branch =~ [0-9a-f]{40} && $new_branch = $current_head ]]; then
# We want HEAD to be on a raw commit, and it is on the one we want.
continue
elif [[ $current_head = refs/heads/$new_branch ]]; then
# We want HEAD to be on a branch, and it is on the one we want.
continue
elif [[ $new_branch = empty-branch ]]; then
# We want to be on the empty branch, and we are not. Switch to the
# empty branch, creating it if we have to.
debug "Switching $bc to the empty branch."
in_barclamp "$bc" to_empty_branch
else
# We are not where we want to be. Go there.
debug "Switching $bc to $new_branch"
in_barclamp "$bc" quiet_checkout "$new_branch" && continue
die "$new_branch does not exist in $bc!" \
"You should try to fix it with a ./dev fetch followed by ./dev sync." \
"If that does not work, then someone forgot to push the branch when" \
"$1 was created."
fi
done
# Record that we are on the new build.
in_repo git config 'crowbar.release' "${1%/*}"
in_repo git config 'crowbar.build' "$1"
}
# Check out the appropriate release branch for all the barclamps in a given
# release or build.
switch_release() {
# $1 = build or release to switch to
local l br bc current_branch new_base rel repo
local -A barclamps
new_build="$(current_build)"
if build_exists "$1"; then
new_build="$1"
elif release_exists "$1"; then
new_build="$1/${new_build##*/}"
if ! build_exists "$new_build"; then
debug "Release $1 does not have build $new_build, switching to $1/master instead."
new_build="$1/master"
fi
elif [[ $1 ]]; then
die "$1 is not a release or a build I can switch to!"
fi
barclamps_are_clean || \
die "Crowbar repo must be clean before trying to switch releases!"
switch_barclamps_to_build "$new_build"
in_repo quiet_checkout master
for l in change-image extra; do
[[ $(in_repo readlink -f $l) = "releases/$new_build/$l" ]] && continue
in_repo rm -f "$l"
in_repo ln -sf "releases/$new_build/$l" "$l"
done
debug "Switched to $new_build"
}
# This function is kept around for legacy workflow reasons.
checkout() {
local br new_build rel
rel="$(current_release)"
new_build="$rel/$1"
switch_barclamps_to_build "$rel/$1"
}
# Test to see of we are on the right metadata version.
dev_is_setup() {
in_repo git_config_has crowbar.dev.version || [[ $1 = setup ]] || return 1
local thisrev
thisrev=$(get_repo_cfg crowbar.dev.version) && \
(( thisrev == DEV_VERSION )) || [[ $1 = setup ]] || {
VERBOSE=true
debug "Crowbar repository out of sync with dev tool revision."
debug "Please run $0 setup to update it."
exit 1
}
}
# Given a branch, print the first remote that branch is "owned" by.
# This assumes update_tracking_branches is keeping things up to date.
remote_for_branch() {
local -a remotes
local remote
if [[ ! $DEV_FROM_REMOTES ]]; then
if git_config_has "branch.$1.remote"; then
git config --get "branch.$1.remote"
return $?
else
remotes=("${DEV_SORTED_REMOTES[@]}")
fi
fi
[[ $DEV_FROM_REMOTES ]] && remotes=("${DEV_FROM_REMOTES[@]}")
for remote in "${remotes[@]}"; do
git show-ref --quiet --verify "refs/remotes/$remote/$1" || continue
echo "$remote"
return 0
done
return 1
}
# Test to see if a specific repository is clean.
# Ignores submodules and unchecked-in directories that are git repositories.
git_is_clean() {
local line hpath ret=0 opt
local stat_cmd="git status --porcelain" quiet=''
local paths=()
while [[ $1 ]]; do
opt="$1"; shift
case $opt in
--barclamps) stat_cmd="git status --porcelain";;
-q|--quiet) quiet=true;;
--paths)
while [[ $1 && $1 != '-'* ]]; do
paths+=("$1")
shift
done;;
*) die "Unknown option $opt passed to git_is_clean.";;
esac
done
[[ $paths ]] && stat_cmd+=" -- ${paths[*]}"
while read line; do
case $line in
# Untracked file. Ignore it if it is also a git repo,
# complain otherwise.
'??'*) hpath=${line%% ->*}
hpath=${hpath#* }
[[ -d $PWD/$hpath.git || -f $PWD/$hpath.git || $hpath = barclamps/ ]] && continue
ret=1; break;;
'') continue;;
*) ret=1; break;;
esac
done < <($stat_cmd)
[[ $ret = 0 ]] && return
[[ $quiet ]] || {
echo "$PWD:"
git status -- "${paths[@]}"
}
[[ $IGNORE_CLEAN ]] && return 0
return 1
}
# Stupid wrapper around git push for debugging.
git_push() {
if [[ $DEBUG = true || $DRY_RUN ]]; then
echo "would have done git push $@"
return
fi
git push "$@"
}
# Test to see if a barclamp is clean.
barclamp_is_clean() {
local bc="$1"; shift
in_barclamp "$bc" git_is_clean "$@"
}
# Test to see if all our barclamps are clean.
barclamps_are_clean() {
local bc res=0
for bc in "$CROWBAR_DIR/barclamps/"*; do
is_barclamp "${bc##*/}" || continue
in_barclamp "${bc##*/}" git_is_clean "$@" || res=1
done
return $res
}
# Test to see if all the Crowbar repositories are clean.
crowbar_is_clean() {
local res=0
barclamps_are_clean "$@" && in_repo git_is_clean "$@" && return 0
echo "Your crowbar repositories are not clean."
echo "Please review the git status output above, and add and commit/stash as needed."
return 1
}
# Check to see if a remote exists.
test_remote() { git ls-remote "$1" "refs/heads/master" &> /dev/null; }
# Fork a barclamp on Github.
fork_barclamp() {
# $1 = remote to fork from. Must be a github remote.
# $2 = barclamp to fork.
# $3 = remote on Github to fork to. Must be on Github. and defaults to personal.
local from_remote="${DEV_REMOTE_URLBASE[$1]}"
local to_remote="${DEV_REMOTE_URLBASE[${3:-personal}]}"
if ! [[ $from_remote && $from_remote =~ $github_re ]]; then
die "Source remote $1 is not Github remote!"
elif ! [[ $to_remote && $to_remote =~ $github_re ]]; then
[[ $3 ]] && die "Target remote $3 is not a Github remote!"
[[ $to_remote ]] || die "Personal remote not configured, cannot fork $2 to it."
die "Personal remote does not point at a Github remote, cannot fork $2 to it."
elif [[ $to_remote != */$DEV_GITHUB_ID ]]; then
die "Remote ${3:-personal} does not map to your Github account!"
fi
test_remote "$to_remote/barclamp-$2.git" && return 0 # already forked
test_remote "$from_remote/barclamp-$2.git" || die "Barclamp $2 does not exist at remote $1"
github_fork "${DEV_REMOTE_URLBASE[$1]##*/}" "barclamp-$2"
}
# Look for barclamps that a build references, but that we don't have locally
find_missing_barclamps() {
# $1 = '', release, release/build
local bc barclamps=()
if build_exists "$1"; then
barclamps=($(barclamps_in_build "$1"))
elif release_exists "$1"; then
barclamps=($(barclamps_in_release "$1"))
else
barclamps=($(all_barclamps))
fi
for bc in "${barclamps[@]}"; do
bc="$CROWBAR_DIR/barclamps/$bc"
[[ -d $bc/.git || -f $bc/.git ]] && continue
echo "${bc##*/}"
done
}
# Look for barclamps that exist locally, but that are not referenced by
# any local builds.
find_orphaned_barclamps() {
local bc
local -A barclamps
for bc in "$CROWBAR_DIR/barclamps/"*; do
[[ -d $bc/.git || -f $bc/.git ]] || continue
barclamps["${bc##*/}"]=present
done
for bc in $(all_barclamps); do
[[ ${barclamps[$bc]} ]] && continue
echo "$bc"
done
}
# Check to see if a barclamp exists at a given remote.
# Barclamps can either exist at:
# barclamp-$1 for Github, Bitbucket and the like, or
# barclamps/$1 for trees that have been layed out by dev.
# We test for both to make it possible to use dev setup for local clones.
probe_barclamp_remote() {
# $1 = barclamp
# $2 = urlpart
local remote
for remote in "barclamp-$1" "crowbar/barclamps/$1"; do
test_remote "$2/$remote" || continue
echo "$remote"
return 0
done
return 1
}
# Perform an initial clone of a barclamp.
# Takes care to ensure that the origin remote is set appropriatly.
# This will also create a personal remote if needed.
clone_and_sync_barclamp() {
# $1 = name of the barclamp
local repo remote build head urlbase
if ! [[ -d $CROWBAR_DIR/barclamps/$1/.git || \
-f $CROWBAR_DIR/barclamps/$1/.git ]]; then
for remote in "${DEV_SORTED_REMOTES[@]}"; do
urlbase="${DEV_REMOTE_URLBASE[$remote]}"
repo="$(probe_barclamp_remote "$1" "$urlbase")" && break
done
[[ $repo ]] || return 1
in_repo git clone -l -o "$remote" "$urlbase/$repo" "barclamps/$1" || {
rm -rf "$CROWBAR_DIR/barclamps/$1"
die "Unable to clone barclamp $bc from $urlbase/$repo.git"
}
fi
[[ -f $CROWBAR_DIR/barclamps/$1/.git && \
! -d $CROWBAR_DIR/barclamps/$1/.git ]] && (
cd "$CROWBAR_DIR/barclamps/$1/"
debug "De-submoduleizing barclamp ${1}"
read gpath < ".git"
if [[ $gpath = 'gitdir: '* ]]; then
rm -f ".git"
mv "${gpath#gitdir: }" ".git"
(export GIT_WORK_TREE=.; git config --unset core.worktree)
git reset --hard HEAD
git clean -f -x -d
else
echo "Malformed .git file in $1. Skipping." >&2
fi
)
sync_barclamp_remotes "$1"
[[ $remote ]] || remote=$(in_barclamp "$1" origin_remote) || \
die "Cannot find origin remote for barclamp $1!"
urlbase="${DEV_REMOTE_URLBASE[$remote]}"
if [[ $urlbase =~ $github_re ]] && remote_available personal && \
! in_barclamp "$1" git_remote_exists personal; then
# test to see if we need to fork this barclamp at Github
if ! github_repo_exists "barclamp-$1"; then
debug "Creating a personal fork of barclamp-$1"
fork_barclamp "$remote" "$1" || return 2
sync_barclamp_remotes "$1"
fi
fi
in_barclamp "$1" git_remote_exists origin && in_barclamp "$1" git remote rm origin
in_barclamp "$1" update_tracking_branches
}
# Clone and synchronize remotes for barclamps on the command line, as needed.
clone_barclamps() {
# $@ = barclamps to clone. If none are passed, defaults to missing ones.
local barclamps=() bc
if [[ $1 = all ]]; then
barclamps=($(all_barclamps))
elif [[ $1 ]]; then
barclamps=("$@")
else
barclamps=($(find_missing_barclamps))
fi
for bc in "${barclamps[@]}"; do
clone_and_sync_barclamp "$bc" && continue
case $? in
1) debug "Unable to find barclamp $bc in any available remotes.";;
2) debug "Unable to create a personal fork of barclamp $bc";;
esac
done
# Create a fork of Crowbar if we just created a personal remote.
if crowbar_remote_exists personal && ! github_repo_exists crowbar; then
echo "Creating your fork of Crowbar on Github."
github_fork "$(origin_remote)" crowbar || \
die "Unable to create your fork of Crowbar."
fi
}
# Check out a branch, but be quiet about it unless something goes wrong.
quiet_checkout() {
local res=''
res=$(git checkout -q "$@" 2>&1) && return
echo "$res" >&2
return 1
}
# Update tracking branches for all remotes in a specific repo.
# Expects to be called within a specific repository
# It is structured to minimize forking, please be careful modifying it.
__update_tracking_branches() {
# Create tracking branches for any branches from this remote
# that do not already exist.
local remote p br in_tracking_update=true
local -A branches
while read p br; do
# We never care about HEAD branches at all.
[[ ${br##*/} = HEAD ]] && continue
if [[ $br = refs/heads/* ]]; then
br="${br#refs/heads/}"
if [[ $br = personal/* ]]; then
# This should not be here. Kill it.
git branch -D "$br"
# Nuke any config that might have come from a remote we don't care about.
git_config_has "branch.$br.remote" || continue
git config --remove-section "branch.$br"
continue
fi
# Record that we have seen this branch, but don't know what its remote should be.
branches["$br"]="no remote"
continue
elif [[ $br = refs/remotes/* ]]; then
br="${br#refs/remotes/}"
# Grab our remote, and go on to the next ref if we don't care about it.
remote="${br%%/*}"
[[ ${remotes[$remote]} ]] || continue
br="${br#${remote}/}"
# Skip personal or pull-request branches.
[[ $br = personal/* || $br = pull-req* ]] && continue
# If we have never seen this branch before, or
# we don't have a remote for it, or
# the remote we have for it is lower priority than our remote, then
# give this branch us as a remote instead.
if [[ ! ${branches[$br]} || \
${branches[$br]} = "no remote" ]] || \
(( ${remotes[${branches[$br]}]} > ${remotes[$remote]} )); then
branches[$br]="$remote"
fi
fi
done < <(LC_ALL=C git show-ref |sort -k2) # Ensure that heads come first!
# Now, we have our list of branches. Operate on it.
for br in "${!branches[@]}"; do
remote="${branches[$br]}"
if ! branch_exists "$br"; then
# We need to create a local ref for this branch.
git branch "$br" "$remote/$br"
git config "branch.$br.remote" "$remote"
git config "branch.$br.merge" "refs/heads/$br"
elif [[ $remote = "no remote" ]]; then
continue
else
git config --remove-section "branch.$br"
git config "branch.$br.remote" "$remote"
git config "branch.$br.merge" "refs/heads/$br"
fi
done
}
update_tracking_branches() {
local p=0 remote
local -A remotes
for remote in "${DEV_SORTED_REMOTES[@]}"; do
remotes[$remote]="$p"
p=$((p + 1))
done
__update_tracking_branches
}
update_cache_tracking_branches() {
local -A remotes
remotes["origin"]=0
in_cache __update_tracking_branches
}
# Update tracking references for all branches in all the
# repositories that dev is managing.
update_all_tracking_branches() {
local bc
debug "Updating tracking branch references in Crowbar"
in_repo update_tracking_branches
for bc in "$CROWBAR_DIR/barclamps/"*; do
[[ -d $bc/.git || -f $bc/.git ]] || continue
debug "Updating tracking branch references in barclamp ${bc##*/}"
(cd "$bc"; update_tracking_branches)
done
if [[ -d $LOCAL_PULL_TRACKING/.git ]]; then
debug "Updating tracking branch references in the CI tracking repo."
(cd "$LOCAL_PULL_TRACKING"; update_tracking_branches)
fi
}
source_prq_vars() (
# $1 = path to pull request metadata
# $2 = whether to scope the variables as local
local f val
cd "$1" || return 1
[[ -f source_repo && -f created_at && -f title ]] || return 1
for f in *; do
[[ -f $f ]] || continue
read val < "$f"
local s=''
[[ $3 && $3 = "unset" ]] && s+="unset prq_$f; "
[[ $2 && $2 = "local" ]] && s+="local "
s+="prq_$f=\"$val\""
echo "$s"
done
)
# Check remote references at the personal remote to see if there
# are any merged pull requests. If there are, delete them.
scrub_merged_pull_requests() {
remote_available personal && remote_is_github personal || return 0
# $@ = branches to test for mergedness
local br ref pull_req remote
local -A to_remove pull_reqs heads
remote_available personal || return
while read ref br; do
case $br in
refs/heads/*)
ref=${br#refs/heads/}
heads[${ref//\//-}]+="$br ";;
refs/remotes/personal/pull-req-*)
ref=${br#refs/remotes/personal/pull-req-}
ref=${ref#heads-}
ref=${ref%-*}
ref=${ref%-0}
pull_reqs["$br"]="$ref";;
esac
done < <(git show-ref)
[[ ${pull_reqs[*]} ]] || return 0
for pull_req in "${!pull_reqs[@]}"; do
ref="${pull_reqs[$pull_req]}"
[[ ${heads[$ref]} ]] || continue
for br in ${heads["$ref"]}; do
remote=$(remote_for_branch "$br")
branches_synced . "$remote/$br" "$pull_req" || \
[[ $1 = '--all' ]] || continue
to_remove["${pull_req#refs/remotes/personal/}"]="true"
continue 2
done
done
[[ ${!to_remove[*]} ]] || return
git_push --delete personal "${!to_remove[@]}"
git remote prune personal
}
# Helper function only for use by fetch_pull_request_metadata.
# If we have applicable metadata from the CI repo, we will
# pull it in as well.
save_pull_request_metadata() (
local fetchcmd
[[ $base_branch && \
$pull_req_branch && \
$pull_req_repo && \
$pull_req_target_repo && \
$pull_req_sha && \
$pull_req_state && \
$repo && \
$title && \
$github_id && \
$created_at && \
$github_target_user ]] || return
if [[ $pull_req_id ]]; then
prq="bundles/$pull_req_id/$repo"
if [[ -d $LOCAL_CI_TRACKING/${prq%/*} && \
! -d $OPEN_PULL_REQUESTS/${prq%/*} ]]; then
mkdir -p "$OPEN_PULL_REQUESTS/${prq%/*}"
cp -a "$LOCAL_CI_TRACKING/${prq%/*}/." \
"$OPEN_PULL_REQUESTS/${prq%/*}/."
fi
else
prq="singletons/$github_target_user/$repo/$github_id"
[[ $order ]] || order='1:1'
if [[ -d $LOCAL_CI_TRACKING/${prq} && \
! -d $OPEN_PULL_REQUESTS/${prq} ]]; then
mkdir -p "$OPEN_PULL_REQUESTS/${prq}"
cp -a "$LOCAL_CI_TRACKING/${prq}/." \
"$OPEN_PULL_REQUESTS/${prq}/."
fi
fi
local dest="$OPEN_PULL_REQUESTS/$prq"
mkdir -p "$dest"
case $repo in
crowbar) fetchcmd="in_repo git fetch";;
barclamp-*) fetchcmd="in_barclamp "${repo#barclamp-}" git fetch";;
*) die "Unknown repo $repo!";;
esac
local local_branch="pull-req/$github_user/$github_id"
$fetchcmd "$pull_req_repo" "+$pull_req_branch:$local_branch" &>/dev/null || return
cd "$dest"
echo "$order" > order
echo "$pull_req_repo" > source_repo
echo "$repo" > local_repo
echo "$pull_req_target_repo" > target_repo
echo "$pull_req_branch" > source_branch
echo "$pull_req_sha" > source_sha
echo "$pull_req_target_sha" > target_sha
echo "$base_branch" > target_branch
echo "$github_id" > number
echo "$created_at" > created_at
echo "$title" > title
echo "$pull_req_state" > state
echo "$github_url" > github_url
echo "$local_branch" > local_branch
echo "$github_user" > source_account
echo "$github_target_user" > target_account
[[ $updated_at ]] && echo "$updated_at" > updated_at
if [[ $rel ]]; then
echo "$rel" > release
else
echo "$(release_for_branch "$base_branch")" >release
fi
)
# Clear out local pull request metadata, including local pull request branches.
clear_pull_request_metadata() (
for bc in "$CROWBAR_DIR/barclamps/"* "$CROWBAR_DIR"; do
[[ -d $bc/.git || -f $bc/.git ]] || continue
cd "$bc"
while read sha ref; do
[[ $ref = refs/heads/pull-req/* ]] || continue
git branch -D ${ref#refs/heads/} &>/dev/null
done < <(git show-ref --heads)
done
[[ -d $OPEN_PULL_REQUESTS ]] && rm -rf "$OPEN_PULL_REQUESTS"
)
# Fetch pull request metadata from Github.
# When this function is done, metadata for all pull requests generated by
# dev for this repo will have the right metadata fragments in the tracking directory
# for the repository in question.
fetch_pull_request_metadata() {
# $1 = remote name
# $2 = repository name.
# Not a github remote? Goodbye.
[[ ${DEV_REMOTE_URLBASE[$1]} =~ $github_re ]] || return 0
local acct="${BASH_REMATCH[2]}"
# Get our raw data. This is what we in the business call fugly.
local -A pulls
. <(parse_yml_or_json - pulls < <(
curl_and_res "https://api.github.com/repos/$acct/$2/pulls"))
local pull_req_id pull_req_sha order pull_req_repo pull_req_branch created_at
local base_branch key ord repo title rel github_id updated_at github_url github_user
local github_target_user pull_req_target_repo pull_req_target_sha pull_req_state
for key in $(printf '%s\n' "${!pulls[@]}" |sort); do
if [[ $ord && $ord != ${key%%.*} ]]; then
# The first part of the key has changed, so we should have a complete set of data.
save_pull_request_metadata
unset pull_req_id pull_req_sha order pull_req_repo pull_req_branch created_at
unset base_branch key ord repo title rel github_id updated_at github_url github_user
unset github_target_user pull_req_target_repo pull_req_target_sha pull_req_state
local pull_req_id pull_req_sha order pull_req_repo pull_req_branch created_at
local base_branch key ord repo title rel github_id updated_at github_url github_user
local github_target_user pull_req_target_repo pull_req_target_sha pull_req_state
fi
ord=${key%%.*}
# Strip off the inital array component of the hash key.
# We will not need it anyways.
case ${key#*.} in
body)
# If the body text does not have a pull request bundle ID, skip it.
[[ ${pulls[$key]} =~ $PULL_BUNDLE_RE ]] && pull_req_id="${BASH_REMATCH[1]}"
[[ ${pulls[$key]} =~ $PULL_RELEASE_RE ]] && rel="${BASH_REMATCH[1]}";;
# The repo and branch that contains changes to be tested.
head.ref) pull_req_branch="${pulls[$key]}";;
head.repo.owner.login) github_user="${pulls[$key]}";;
base.repo.owner.login) github_target_user="${pulls[$key]}";;
head.sha) pull_req_sha="${pulls[$key]}";;
head.repo.clone_url) pull_req_repo="${pulls[$key]}";;
base.repo.clone_url) pull_req_target_repo="${pulls[$key]}";;
base.sha) pull_req_target_sha="${pulls[$key]}";;
title)
if [[ ${pulls[$key]} =~ $PULL_TITLE_RE ]]; then
order="${BASH_REMATCH[2]}:${BASH_REMATCH[3]}"
title="${BASH_REMATCH[1]}"
else
title="${pulls[$key]}"
fi;;
base.ref) base_branch="${pulls[$key]}";;
base.repo.name) repo="${pulls[$key]}";;
number) github_id="${pulls[$key]}";;
state) pull_req_state="${pulls[$key]}";;
created_at) created_at="${pulls[$key]}";;
updated_at) updated_at="${pulls[$key]}";;
html_url) github_url="${pulls[$key]}";;
esac
done
save_pull_request_metadata
}
# Fetch pull request metadata for all repostories at a given remote.
fetch_pull_requests_for_remote() {
# $@ = remote to check for updates from
local -a remotes
local remote
[[ $1 ]] && remotes=("$@") || remotes=($(origin_remote))
fetch_ci_tracking
clear_pull_request_metadata
for remote in "${remotes[@]}"; do
if ! (crowbar_remote_exists "$remote" && remote_available "$remote" && \
[[ ${DEV_REMOTE_URLBASE[$remote]} =~ $github_re ]]); then
debug "Cannot fetch pull requests from $remote, skipping."
continue
fi
debug "Fetching pull requests from $remote:"
for bc in "$CROWBAR_DIR/barclamps/"*; do
[[ -d $bc/.git || -f $bc/.git ]] || continue
in_barclamp "${bc##*/}" git_remote_exists "$remote" || continue
debug "Fetching open pull requests for barclamp ${bc##*/}"
fetch_pull_request_metadata "$remote" "barclamp-${bc##*/}"
done
debug "Fetching open pull requests for Crowbar"
fetch_pull_request_metadata "$remote" "crowbar"
echo
done
sort_pull_requests
show_open_pull_requests
}
# Check to see of a pull request bundle is sane.
# Sanity consists of having all the pull requests we need and
# ensuring that the branch -> release mapping is consistent.
pull_request_bundle_sane() {
# $1 = bundle ID.
# Returns true if all components of the bundle are present, and if
# all the target branches in the barclamps match the release we are
# pulling against.
local -A orders releases branches
local -a order
local total_count=0 indicated_count=0 d f r repo o c release branch
local branch_for_release build bc source_account su tu
for d in "$OPEN_PULL_REQUESTS/bundles/$1/"*; do
[[ -d "$d" ]] || continue
repo="${d##*/}"
for f in order source_repo source_branch source_sha source_account \
target_branch number created_at title release; do
[[ -f $d/$f ]] && continue
debug "Missing metadata info $d/$f"
return 1
done
read o < "$d/order"
read r < "$d/release"
read su < "$d/source_account"
read branch < "$d/target_branch"
if ((indicated_count == 0)); then
indicated_count="${o##*:}"
elif ((indicated_count != "${o##*:}")); then
debug "Indicated count changed for pull request bundle $1"
return 1
fi
if [[ ! $release ]]; then
release=$r
if ! release_exists "$release"; then
debug "Release $release does not exist locally."
return 1
fi
elif [[ $r != $release ]]; then
debug "Release changed in pull request bundle $1"
return 1
fi
if [[ ! $source_account ]]; then
source_account=$su
elif
[[ $su != $source_account ]]; then
debug "Source Github account for pull request changed"
return 1
fi
if [[ $d = *barclamp-* ]]; then
bc="${d##barclamp-}"
for build in $(builds_for_barclamp_in_release "$bc" "$release"); do
[[ $branch = $(barclamp_branch_for_build "$build" "$bc") ]] && continue
debug "Branch $branch for barclamp $bc in bundle $1 does not match metadata."
return 1
done
fi
( cd "$d"
for f in title release source_account; do
[[ -f ../$f ]] || cp "$f" ..
done )
orders[$repo]=$o
releases[$repo]=$release
branches[$repo]=$branch
total_count=$(($total_count + 1))
done
if ((indicated_count != total_count)); then
debug "Not all barclamps in pull request bundle $1 at Github yet."
fi
}
# Show a count of all open pull request bundles and singleton pull requests.
show_open_pull_requests() {
local -a open_bundles open_singletons
local d r
for d in "$OPEN_PULL_REQUESTS/bundles/"*; do
[[ -d $d ]] || continue
pull_request_bundle_sane "${d##*/}" || continue
open_bundles+=("$d")
done
for d in "$OPEN_PULL_REQUESTS/singletons/"*/*/*; do
[[ -d $d ]] || continue
open_singletons+=("$d")
done
if (( ${#open_bundles[@]} == 0 && ${#open_singletons[@]} == 0)); then
r+="No open pull requests"
elif (( ${#open_bundles[@]} == 0 )); then
r+="${#open_singletons[@]} open singleton pull requests"
elif (( ${#open_singletons[@]} == 0)); then
r+="${#open_bundles[@]} open pull request bundles"
else
r+="${#open_bundles[@]} open pull request bundles and ${#open_singletons[@]} open singleton pull requests"
fi
debug "$r"
}
# Helper to determine when a pull request bundle was last touched.
pull_request_last_touched() (
# $1 = bundle
# Returns the most recent date that any of the bundle components were touched.
local mtime
if [[ $1 = bundles/* ]]; then
cd "$OPEN_PULL_REQUESTS/$1" || die "Bundle $1 does not exist!"
for d in *; do
[[ -d $d ]] || continue
[[ $ltime ]] || read ltime <"$d/created_at"
[[ -f updated_at ]] && read $d/ltime <"$d/updated_at"
[[ $ltime > $mtime ]] && mtime=$ltime
done
echo $mtime
elif [[ $1 = singletons/* ]]; then
if [[ -f $OPEN_PULL_REQUESTS/$1/updated_at ]]; then
cat "$OPEN_PULL_REQUESTS/$1/updated_at"
else
cat "$OPEN_PULL_REQUESTS/$1/created_at"
fi
else
die "No idea how to find last touched time for $1"
fi
)
# Helper for finding out all the pull request metadata we have.
pull_request_directories() (
[[ -d $OPEN_PULL_REQUESTS ]] || return 0
cd "$OPEN_PULL_REQUESTS"
for d in bundles/* singletons/*/*/*; do
if [[ $d = bundles/* ]]; then
pull_request_bundle_sane "${d##*/}" || continue
fi
[[ -f $d/title ]] || continue
echo "$(pull_request_last_touched "$d")|$d"
done |sort |cut -d \| -f 2
)
# Sort pull requests according to their last modification time.
sort_pull_requests() {
local count=1 p
rm "$OPEN_PULL_REQUESTS/pull_request_index" &>/dev/null
for p in $(pull_request_directories); do
echo "$count $p" >>"$OPEN_PULL_REQUESTS/pull_request_index"
count=$((count + 1))
done
}
# translate a pull request number (as displayed by dev pull-requests list)
# into an internal ID.
pull_request_number_to_id() {
if [[ $1 && $1 =~ [0-9]+ ]]; then
read idx id < <(grep "^$1 " "$OPEN_PULL_REQUESTS/pull_request_index")
[[ $1 = $idx ]] || die "$1 is not a valid pull request." \
"dev pull-requests list shows the open ones we know about."
elif grep -q " $1\$" "$OPEN_PULL_REQUESTS/pull_request_index"; then
id="$1"
else
die "$1 is not a valid pull request ID"
fi
echo $id
}
builds_for_one_pull_request() {
# $1 =path to pull request
local -a builds
case $prq_local_repo in
barclamp-*)
builds=($(builds_for_barclamp_in_release "${prq_local_repo#barclamp-}" "$prq_release" ));;
crowbar)
builds=($(builds_in_release "$prq_release"));;
*) die "Unknown repo $prq_local_repo in builds_for_one_pull_request!";;
esac
echo "${builds[*]}"
}
# Return a list of builds that a pull request should trigger.
# This is based on barclamp membership.
# Returns a list of builds in the order in which they should be built.
builds_for_pull_request() {
local idx id b p release build os repo prq res=() all_oses=()