Last active 1 month ago

botanikanet revised this gist 1 month ago. Go to revision

1 file changed, 160 insertions, 89 deletions

update_all_git_repo.sh

@@ -1,107 +1,178 @@
1 1 #!/bin/bash
2 + set -euo pipefail
2 3
4 + # ── Colors ───────────────────────────────────────────────────────────
5 + RED='\033[0;31m'
6 + GREEN='\033[0;32m'
7 + YELLOW='\033[1;33m'
8 + CYAN='\033[0;36m'
9 + NC='\033[0m' # No Color
10 +
11 + # ── Logging helpers ──────────────────────────────────────────────────
12 + log_info() { printf "${CYAN}[INFO]${NC} %s\n" "$*"; }
13 + log_warn() { printf "${YELLOW}[WARN]${NC} %s\n" "$*"; }
14 + log_ok() { printf "${GREEN}[ OK ]${NC} %s\n" "$*"; }
15 + log_error() { printf "${RED}[ERR]${NC} %s\n" "$*" >&2; }
16 +
17 + # ── Usage ────────────────────────────────────────────────────────────
3 18 usage() {
4 - echo "Usage ${0} [[--ignore|-i] dir_name] [--main]"
5 - echo " --help Show this help"
6 - echo " --main Checkout to main branch"
7 - echo " --ignore=* Ignore folders"
8 - echo " --only=* Update only folders (comma-separated)"
9 - exit 1
10 - }
19 + cat <<EOF
20 + Usage: ${0##*/} [OPTIONS]
11 21
12 - find_folders() {
13 - local FIND_ARGS=(.)
14 - FIND_ARGS+=("-name" ".git")
15 - FIND_ARGS+=("-type" "d")
16 -
17 - # If only specific folders are requested
18 - local ONLY_PATHS=()
19 - for ONLY_FOLDER in ${ONLY_FOLDERS}
20 - do
21 - ONLY_PATHS+=("-o" "-path" "*/${ONLY_FOLDER}/*")
22 - # Remove first -o and add parentheses
23 - done
24 - if [[ ${#ONLY_PATHS[@]} -gt 1 ]]
25 - then
26 - unset 'ONLY_PATHS[0]'
27 - FIND_ARGS+=("(" "${ONLY_PATHS[@]}" ")")
28 - fi
29 -
30 - # Exclude ignored folders
31 - for IGNORE_FOLDER in ${IGNORE_FOLDERS}
32 - do
33 - FIND_ARGS+=("-not" "-path" "*/${IGNORE_FOLDER}/*")
34 - done
35 - find "${FIND_ARGS[@]}"
22 + Update all git repositories found under the current directory.
23 +
24 + Options:
25 + -h, --help Show this help
26 + -m, --main Checkout to main/master branch after update
27 + -i, --ignore=DIR Ignore folder(s) (repeatable, comma-separated)
28 + -o, --only=DIR Update only these folder(s) (repeatable, comma-separated)
29 +
30 + Examples:
31 + ${0##*/} --ignore=vendor,node_modules
32 + ${0##*/} --only=project-a,project-b --main
33 + EOF
34 + exit 1
36 35 }
37 36
38 - # Map long options to short ones and rebuild argv
37 + # ── Parse arguments ──────────────────────────────────────────────────
38 + IGNORE_FOLDERS=()
39 + ONLY_FOLDERS=()
40 + FLAG_SWITCH_TO_MAIN=""
41 +
42 + # Map long options to short ones
39 43 translated=()
40 44 while (( $# )); do
41 - case "$1" in
42 - --help) translated+=("-h"); shift ;;
43 - --main) translated+=("-m"); shift ;;
44 - --ignore=*) translated+=("-i" "${1#*=}"); shift ;;
45 - --only=*) translated+=("-o" "${1#*=}"); shift ;;
46 - --) translated+=("--"); shift; translated+=("$@"); set -- ;;
47 - *) translated+=("$1"); shift ;;
48 - esac
45 + case "$1" in
46 + --help) translated+=("-h"); shift ;;
47 + --main) translated+=("-m"); shift ;;
48 + --ignore=*) translated+=("-i" "${1#*=}"); shift ;;
49 + --only=*) translated+=("-o" "${1#*=}"); shift ;;
50 + --) translated+=("--"); shift; translated+=("$@"); break ;;
51 + *) translated+=("$1"); shift ;;
52 + esac
49 53 done
50 54 set -- "${translated[@]}"
51 55
52 - IGNORE_FOLDERS=""
53 - ONLY_FOLDERS=""
54 - FLAG_SWITCH_TO_MAIN=""
55 -
56 56 while getopts ":hmi:o:" opt; do
57 - case "$opt" in
58 - h) usage;;
59 - m) FLAG_SWITCH_TO_MAIN=1;;
60 - i) IGNORE_FOLDERS="${IGNORE_FOLDERS} ${OPTARG}";;
61 - o) ONLY_FOLDERS="${ONLY_FOLDERS} ${OPTARG}";;
62 - :) echo "Missing arg for -$OPTARG"; usage;;
63 - \?) echo "Invalid option: -$OPTARG"; usage;;
64 - esac
57 + case "$opt" in
58 + h) usage ;;
59 + m) FLAG_SWITCH_TO_MAIN=1 ;;
60 + i)
61 + # Support comma-separated values
62 + IFS=',' read -ra parts <<< "$OPTARG"
63 + IGNORE_FOLDERS+=("${parts[@]}")
64 + ;;
65 + o)
66 + IFS=',' read -ra parts <<< "$OPTARG"
67 + ONLY_FOLDERS+=("${parts[@]}")
68 + ;;
69 + :) log_error "Missing argument for -${OPTARG}"; usage ;;
70 + \?) log_error "Invalid option: -${OPTARG}"; usage ;;
71 + esac
65 72 done
73 + shift $((OPTIND - 1))
66 74
67 - if [ ${#IGNORE_FOLDERS[@]} -gt 0 ]; then
68 - echo "[DEBUG] Ignoring folders to update: ${IGNORE_FOLDERS[*]}"
75 + # ── Debug: show filters ─────────────────────────────────────────────
76 + if (( ${#IGNORE_FOLDERS[@]} > 0 )); then
77 + log_info "Ignoring folders: ${IGNORE_FOLDERS[*]}"
69 78 fi
70 - if [ ${#ONLY_FOLDERS[@]} -gt 0 ]; then
71 - echo "[DEBUG] Only folders to update: ${ONLY_FOLDERS[*]}"
79 + if (( ${#ONLY_FOLDERS[@]} > 0 )); then
80 + log_info "Only updating folders: ${ONLY_FOLDERS[*]}"
72 81 fi
73 82
74 - for DIR in $(find_folders)
75 - do
76 - pushd ${PWD} > /dev/null
77 - echo "[DEBUG] Work with $(dirname ${DIR})"
78 - cd $(dirname ${DIR})
79 - if [[ -n $(git status --porcelain) ]]
80 - then
81 - echo "[WARN] There are uncommitted changes."
82 - declare UNCOMMIT_CHANGES=1
83 - git stash
84 - fi
85 - declare MASTER_BRANCH="$(git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@')"
86 - declare CURRENT_BRANCH="$(git branch --show-current)"
87 - if [[ "$MASTER_BRANCH" == "$CURRENT_BRANCH" ]]
88 - then
89 - git pull --all
90 - git remote prune origin
91 - else
92 - git checkout ${MASTER_BRANCH}
93 - git pull --all
94 - git remote prune origin
95 - if [[ -z "${FLAG_SWITCH_TO_MAIN}" ]]
96 - then
97 - git checkout ${CURRENT_BRANCH} || echo "[WARN] current brach has been deleted"
98 - fi
99 - fi
100 - git branch --merged | grep -v "^*\|${MASTER_BRANCH}" | xargs -r git branch -D
101 - if [ ! -z "$UNCOMMIT_CHANGES" ]
102 - then
103 - git stash pop
83 + # ── Find git repositories ───────────────────────────────────────────
84 + find_repos() {
85 + local find_args=(. -name ".git" -type d)
86 +
87 + # Whitelist: only matching paths
88 + if (( ${#ONLY_FOLDERS[@]} > 0 )); then
89 + local only_paths=()
90 + for folder in "${ONLY_FOLDERS[@]}"; do
91 + only_paths+=("-o" "-path" "*/${folder}/*")
92 + done
93 + # Remove the leading -o and wrap in parentheses
94 + unset 'only_paths[0]'
95 + find_args+=("(" "${only_paths[@]}" ")")
96 + fi
97 +
98 + # Blacklist: excluded paths
99 + for folder in "${IGNORE_FOLDERS[@]}"; do
100 + find_args+=("-not" "-path" "*/${folder}/*")
101 + done
102 +
103 + find "${find_args[@]}"
104 + }
105 +
106 + # ── Update a single repository ───────────────────────────────────────
107 + update_repo() {
108 + local repo_dir="$1"
109 + local had_stash=0
110 +
111 + # Stash uncommitted changes if any
112 + if [[ -n "$(git -C "$repo_dir" status --porcelain)" ]]; then
113 + log_warn "Uncommitted changes in ${repo_dir} — stashing"
114 + git -C "$repo_dir" stash --quiet
115 + had_stash=1
116 + fi
117 +
118 + # Determine main and current branches
119 + local master_branch current_branch
120 + master_branch="$(git -C "$repo_dir" symbolic-ref refs/remotes/origin/HEAD 2>/dev/null \
121 + | sed 's@^refs/remotes/origin/@@')" || true
122 + current_branch="$(git -C "$repo_dir" branch --show-current)"
123 +
124 + if [[ -z "$master_branch" ]]; then
125 + log_warn "Cannot determine default branch for ${repo_dir} — skipping"
126 + return
127 + fi
128 +
129 + if [[ "$master_branch" == "$current_branch" ]]; then
130 + git -C "$repo_dir" pull --all --quiet
131 + else
132 + git -C "$repo_dir" checkout "$master_branch" --quiet
133 + git -C "$repo_dir" pull --all --quiet
134 + if [[ -z "$FLAG_SWITCH_TO_MAIN" ]]; then
135 + git -C "$repo_dir" checkout "$current_branch" --quiet 2>/dev/null \
136 + || log_warn "Branch '${current_branch}' was deleted on remote"
104 137 fi
105 - unset UNCOMMIT_CHANGES MASTER_BRANCH CURRENT_BRANCH
106 - popd > /dev/null
107 - done
138 + fi
139 +
140 + # Prune stale remote-tracking branches
141 + git -C "$repo_dir" remote prune origin --quiet 2>/dev/null || true
142 +
143 + # Delete local branches that have been merged into the default branch
144 + local merged
145 + merged="$(git -C "$repo_dir" branch --merged \
146 + | grep -v -E "^\*|${master_branch}" || true)"
147 + if [[ -n "$merged" ]]; then
148 + echo "$merged" | xargs git -C "$repo_dir" branch -D --quiet 2>/dev/null || true
149 + fi
150 +
151 + # Restore stashed changes
152 + if (( had_stash )); then
153 + git -C "$repo_dir" stash pop --quiet || log_warn "Stash pop failed in ${repo_dir}"
154 + fi
155 +
156 + log_ok "$repo_dir"
157 + }
158 +
159 + # ── Main ─────────────────────────────────────────────────────────────
160 + main() {
161 + local count=0
162 +
163 + while IFS= read -r git_dir; do
164 + local repo_dir
165 + repo_dir="$(dirname "$git_dir")"
166 + log_info "Updating ${repo_dir} …"
167 + update_repo "$repo_dir"
168 + (( count++ )) || true
169 + done < <(find_repos)
170 +
171 + if (( count == 0 )); then
172 + log_warn "No git repositories found"
173 + else
174 + log_ok "Done — updated ${count} repo(s)"
175 + fi
176 + }
177 +
178 + main

botanikanet revised this gist 2 months ago. Go to revision

1 file changed, 28 insertions, 2 deletions

update_all_git_repo.sh

@@ -5,13 +5,29 @@ usage() {
5 5 echo " --help Show this help"
6 6 echo " --main Checkout to main branch"
7 7 echo " --ignore=* Ignore folders"
8 + echo " --only=* Update only folders (comma-separated)"
8 9 exit 1
9 10 }
10 11
11 12 find_folders() {
12 - FIND_ARGS=(.)
13 + local FIND_ARGS=(.)
13 14 FIND_ARGS+=("-name" ".git")
14 15 FIND_ARGS+=("-type" "d")
16 +
17 + # If only specific folders are requested
18 + local ONLY_PATHS=()
19 + for ONLY_FOLDER in ${ONLY_FOLDERS}
20 + do
21 + ONLY_PATHS+=("-o" "-path" "*/${ONLY_FOLDER}/*")
22 + # Remove first -o and add parentheses
23 + done
24 + if [[ ${#ONLY_PATHS[@]} -gt 1 ]]
25 + then
26 + unset 'ONLY_PATHS[0]'
27 + FIND_ARGS+=("(" "${ONLY_PATHS[@]}" ")")
28 + fi
29 +
30 + # Exclude ignored folders
15 31 for IGNORE_FOLDER in ${IGNORE_FOLDERS}
16 32 do
17 33 FIND_ARGS+=("-not" "-path" "*/${IGNORE_FOLDER}/*")
@@ -26,6 +42,7 @@ while (( $# )); do
26 42 --help) translated+=("-h"); shift ;;
27 43 --main) translated+=("-m"); shift ;;
28 44 --ignore=*) translated+=("-i" "${1#*=}"); shift ;;
45 + --only=*) translated+=("-o" "${1#*=}"); shift ;;
29 46 --) translated+=("--"); shift; translated+=("$@"); set -- ;;
30 47 *) translated+=("$1"); shift ;;
31 48 esac
@@ -33,18 +50,27 @@ done
33 50 set -- "${translated[@]}"
34 51
35 52 IGNORE_FOLDERS=""
53 + ONLY_FOLDERS=""
36 54 FLAG_SWITCH_TO_MAIN=""
37 55
38 - while getopts ":hmi:" opt; do
56 + while getopts ":hmi:o:" opt; do
39 57 case "$opt" in
40 58 h) usage;;
41 59 m) FLAG_SWITCH_TO_MAIN=1;;
42 60 i) IGNORE_FOLDERS="${IGNORE_FOLDERS} ${OPTARG}";;
61 + o) ONLY_FOLDERS="${ONLY_FOLDERS} ${OPTARG}";;
43 62 :) echo "Missing arg for -$OPTARG"; usage;;
44 63 \?) echo "Invalid option: -$OPTARG"; usage;;
45 64 esac
46 65 done
47 66
67 + if [ ${#IGNORE_FOLDERS[@]} -gt 0 ]; then
68 + echo "[DEBUG] Ignoring folders to update: ${IGNORE_FOLDERS[*]}"
69 + fi
70 + if [ ${#ONLY_FOLDERS[@]} -gt 0 ]; then
71 + echo "[DEBUG] Only folders to update: ${ONLY_FOLDERS[*]}"
72 + fi
73 +
48 74 for DIR in $(find_folders)
49 75 do
50 76 pushd ${PWD} > /dev/null

botanikanet revised this gist 6 months ago. Go to revision

1 file changed, 18 insertions, 16 deletions

update_all_git_repo.sh

@@ -1,20 +1,22 @@
1 1 #!/bin/bash
2 2
3 3 usage() {
4 - echo "Usage ${0} [[--ignore|-i] dir_name] [--main]"
4 + echo "Usage ${0} [[--ignore|-i] dir_name] [--main]"
5 5 echo " --help Show this help"
6 6 echo " --main Checkout to main branch"
7 7 echo " --ignore=* Ignore folders"
8 - exit 1
8 + exit 1
9 9 }
10 10
11 11 find_folders() {
12 - set -- ./ -name ".git" -type d
13 - for IGNORE_FOLDER in ${IGNORE_FOLDERS}
14 - do
15 - set -- "$@" -not -path '"*/'${IGNORE_FOLDER}'/*"'
16 - done
17 - find $@
12 + FIND_ARGS=(.)
13 + FIND_ARGS+=("-name" ".git")
14 + FIND_ARGS+=("-type" "d")
15 + for IGNORE_FOLDER in ${IGNORE_FOLDERS}
16 + do
17 + FIND_ARGS+=("-not" "-path" "*/${IGNORE_FOLDER}/*")
18 + done
19 + find "${FIND_ARGS[@]}"
18 20 }
19 21
20 22 # Map long options to short ones and rebuild argv
@@ -59,21 +61,21 @@ do
59 61 if [[ "$MASTER_BRANCH" == "$CURRENT_BRANCH" ]]
60 62 then
61 63 git pull --all
62 - git remote prune origin
64 + git remote prune origin
63 65 else
64 66 git checkout ${MASTER_BRANCH}
65 67 git pull --all
66 - git remote prune origin
67 - if [[ -z "${FLAG_SWITCH_TO_MAIN}" ]]
68 - then
69 - git checkout ${CURRENT_BRANCH} || echo "[WARN] current brach has been deleted"
70 - fi
68 + git remote prune origin
69 + if [[ -z "${FLAG_SWITCH_TO_MAIN}" ]]
70 + then
71 + git checkout ${CURRENT_BRANCH} || echo "[WARN] current brach has been deleted"
72 + fi
71 73 fi
72 - git branch --merged | grep -v "^*\|${MASTER_BRANCH}" | xargs -r git branch -D
74 + git branch --merged | grep -v "^*\|${MASTER_BRANCH}" | xargs -r git branch -D
73 75 if [ ! -z "$UNCOMMIT_CHANGES" ]
74 76 then
75 77 git stash pop
76 78 fi
77 79 unset UNCOMMIT_CHANGES MASTER_BRANCH CURRENT_BRANCH
78 80 popd > /dev/null
79 - done
81 + done

botanikanet revised this gist 8 months ago. Go to revision

1 file changed, 79 insertions

update_all_git_repo.sh(file created)

@@ -0,0 +1,79 @@
1 + #!/bin/bash
2 +
3 + usage() {
4 + echo "Usage ${0} [[--ignore|-i] dir_name] [--main]"
5 + echo " --help Show this help"
6 + echo " --main Checkout to main branch"
7 + echo " --ignore=* Ignore folders"
8 + exit 1
9 + }
10 +
11 + find_folders() {
12 + set -- ./ -name ".git" -type d
13 + for IGNORE_FOLDER in ${IGNORE_FOLDERS}
14 + do
15 + set -- "$@" -not -path '"*/'${IGNORE_FOLDER}'/*"'
16 + done
17 + find $@
18 + }
19 +
20 + # Map long options to short ones and rebuild argv
21 + translated=()
22 + while (( $# )); do
23 + case "$1" in
24 + --help) translated+=("-h"); shift ;;
25 + --main) translated+=("-m"); shift ;;
26 + --ignore=*) translated+=("-i" "${1#*=}"); shift ;;
27 + --) translated+=("--"); shift; translated+=("$@"); set -- ;;
28 + *) translated+=("$1"); shift ;;
29 + esac
30 + done
31 + set -- "${translated[@]}"
32 +
33 + IGNORE_FOLDERS=""
34 + FLAG_SWITCH_TO_MAIN=""
35 +
36 + while getopts ":hmi:" opt; do
37 + case "$opt" in
38 + h) usage;;
39 + m) FLAG_SWITCH_TO_MAIN=1;;
40 + i) IGNORE_FOLDERS="${IGNORE_FOLDERS} ${OPTARG}";;
41 + :) echo "Missing arg for -$OPTARG"; usage;;
42 + \?) echo "Invalid option: -$OPTARG"; usage;;
43 + esac
44 + done
45 +
46 + for DIR in $(find_folders)
47 + do
48 + pushd ${PWD} > /dev/null
49 + echo "[DEBUG] Work with $(dirname ${DIR})"
50 + cd $(dirname ${DIR})
51 + if [[ -n $(git status --porcelain) ]]
52 + then
53 + echo "[WARN] There are uncommitted changes."
54 + declare UNCOMMIT_CHANGES=1
55 + git stash
56 + fi
57 + declare MASTER_BRANCH="$(git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@')"
58 + declare CURRENT_BRANCH="$(git branch --show-current)"
59 + if [[ "$MASTER_BRANCH" == "$CURRENT_BRANCH" ]]
60 + then
61 + git pull --all
62 + git remote prune origin
63 + else
64 + git checkout ${MASTER_BRANCH}
65 + git pull --all
66 + git remote prune origin
67 + if [[ -z "${FLAG_SWITCH_TO_MAIN}" ]]
68 + then
69 + git checkout ${CURRENT_BRANCH} || echo "[WARN] current brach has been deleted"
70 + fi
71 + fi
72 + git branch --merged | grep -v "^*\|${MASTER_BRANCH}" | xargs -r git branch -D
73 + if [ ! -z "$UNCOMMIT_CHANGES" ]
74 + then
75 + git stash pop
76 + fi
77 + unset UNCOMMIT_CHANGES MASTER_BRANCH CURRENT_BRANCH
78 + popd > /dev/null
79 + done
Newer Older