summaryrefslogtreecommitdiff
path: root/git-repo
blob: ea11d54f38a0b69bb7b6885b0e557e58a0e820a0 (plain)
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
#!/usr/bin/env bash
BASEDIR=/git

C_bg_green=$(echo -e "\\e[42m")
C_bg_magenta=$(echo -e "\\e[45m")
C_bg_default=$(echo -e "\\e[49m")
C_bg_lblue=$(echo -e "\\e[104m")

_getent() {
    local db=$1
    local key=$2
    getent "$db" "$key" | cut -d ':' -f 1
}

_list_repo_dirs() {
    find "$1" -maxdepth 6 -type d -not -path '*.git/*' -name '*.git' 2>/dev/null
}

# shellcheck disable=SC2154
do_list() {
    local root_path
    if [ -n "$1" ]; then
	root_path=$(readlink -f "$1")
    else
	root_path=$BASEDIR
    fi
    echo -n "Working.. listing repositories under $root_path"
    for repo_path in $(_list_repo_dirs $root_path | sort); do
        echo -ne "\\r" # to remove "Working..."
	eval "$(stat --format 'local repo_path_uid=%u repo_path_gid=%g repo_path_mode=%a' "$repo_path")"
        if [ "$repo_path_uid" != "$UID" ]; then
            # so there must be a matching gid
            local pass=false
            for user_gid in $(id -G); do
                if [ "$repo_path_gid" == "$user_gid" ]; then
                    pass=true
                fi
            done
            if ! $pass; then
                continue # skip that repo since uid and gids don't match
            fi

        fi
	
        local shortdesc permissions color
	shortdesc=$(cut -c 1-40 < "$repo_path/description")
	permissions="owner=$(_getent passwd "$repo_path_uid")"
	color="$C_bg_green"
        if [ $(( 0$repo_path_mode & 070 )) -gt 0 ]; then # shared
	    permissions="$permissions group=$(_getent group "$repo_path_gid")"
	    color="$C_bg_lblue"
	fi
        if [ $(( 0$repo_path_mode & 07 )) -gt 0 ]; then # public
	    permissions="$permissions PUBLIC"
            if ! [ -f "$repo_path/PUBLIC" ]; then
                permissions="${permissions}-but-cgit-marker-file-missing"
            fi
	    color="$C_bg_magenta"
	fi
	printf "%s%-60s (%s) (%s)\\n" "$color" "$repo_path$C_bg_default" "$permissions" "$shortdesc"
    done
}

_create_repo() {
    local shared=$2
    local group=$3
    local is_public=$4

    read -r -p 'New repository name.git: '
    local repo_name=$REPLY

    if ! expr "$repo_name" : '.*\.git' >/dev/null; then
	echo "Repository name must end with .git"
	return 1
    fi

    local repo_dir=$BASEDIR/$USER/${repo_name}

    test \! -e "$repo_dir" || { echo "Repo $repo_dir already exist"; exit 1; }

    read -r -p 'Set Description:         '
    local repo_desc=$REPLY

    read -r -p "Create $repo_name in $repo_dir Ok? (y/N)"
    test "$REPLY" == "y" || exit 1

    mkdir -p "$repo_dir"
    git init -q --bare --shared="$shared" "$repo_dir"
    GIT_DIR="$repo_dir" git config receive.denyNonFastforwards false
    chgrp -R "$group" "$repo_dir"
    echo "$repo_desc" > "$repo_dir/description"

    if $is_public; then
	touch "$repo_dir/PUBLIC"
	echo "created $repo_dir/PUBLIC to expose via cgit"
    fi

    echo "done creating $repo_name in $repo_dir"
    echo "use 'git repo show $repo_dir' for details"
    return 0
}

do_create_public() {
    _create_repo "public repository" "0664" "share" true
    return $?
}

do_create_shared() {
    _create_repo "group-writeable repository" "0660" "share" false
    return $?
}

do_create_private() {
    _create_repo "private repository" "0600" "$USER" false
    return $?
}

do_show() {
    local repo_file_mode repo_file_uid repo_file_user repo_file_gid repo_file_group repo_git_shared
    for repo_path in "$@"; do
	if ! [ -f "$repo_path/HEAD" ]; then
	    repo_path="$BASEDIR/$repo_path"
	fi
        if ! [ -f "$repo_path/HEAD" ]; then
            echo "Not a git repository: $repo_path"
            continue
        fi
        repo_file_mode=$(stat --format %a "$repo_path")
        repo_file_uid=$(stat --format %u "$repo_path")
        repo_file_user=$(_getent passwd "$repo_file_uid")
        repo_file_gid=$(stat --format %g "$repo_path")
        repo_file_group=$(_getent group "$repo_file_gid")
        repo_git_shared=$(GIT_DIR="$repo_path" git config --get core.sharedrepository)

	echo "  Directory: $repo_path"
        echo "  ✔ Permissions mode ${repo_file_mode} (uid=${repo_file_uid}/${repo_file_user} gid=${repo_file_gid}/${repo_file_group})"
	echo "    git core.sharedrepository=$repo_git_shared"
        echo " ⚡ Clone read/write SSH: git clone 'ssh://localnet.cc${repo_path}'"
        if [ $(( 0$repo_file_mode & 07 )) -gt 0 ]; then
	    echo "    Clone read/-  HTTP:   git clone 'http://localnet.cc/cgit/cgit.cgi/${repo_path#$BASEDIR/}'"
	fi
	echo "    Update remote:        git remote set-url origin 'ssh://localnet.cc${repo_path}"
        echo "  ☛ To update description execute: $EDITOR $repo_path/description"
	echo ""
    done
}

do_mirror() {
    local root_path is_mirror
    root_path=$(readlink -f "$1")
    for repo_path in $(_list_repo_dirs "$root_path"); do
	is_mirror=$(GIT_DIR="$repo_path" git config --get remote.origin.mirror)
	if [ "$is_mirror" != "true" ]; then
	    continue # skip non-mirror repo
	fi
	echo "Mirror $repo_path from $(GIT_DIR="$repo_path" git config --get remote.origin.url)"
	GIT_DIR="$repo_path" git fetch --force --prune origin
    done
}

_do_make() {
    local repo_path=$1
    local repo_file_mode=$2
    local repo_dir_mode=$3
    local repo_is_public=$4
    test -f "$repo_path/HEAD" || { echo "$repo_path is not a git repository"; exit 1; }
    git init -q --bare --shared="$repo_file_mode" "$repo_path"
    find "$repo_path" -type d -exec chown "$USER:share" \{\} \;
    find "$repo_path" -type d -exec chmod "$repo_dir_mode" \{\} \;
    find "$repo_path" -type f -exec chmod "$repo_file_mode" \{\} \;
    if $repo_is_public; then
	touch "$repo_path/PUBLIC"
    else
	rm -f "$repo_path/PUBLIC"
    fi
    do_list "$repo_path"
}

do_make_public() {
    _do_make "$1" 0664 2775 true
}

do_make_shared() {
    _do_make "$1" 0660 2770 false
}

do_make_private() {
    _do_make "$1" 0600 0700 false
}

do_help() {
    cat <<HERE
Subcommands of git repo:
  list [<dir>]        List infos (optional: only below <dir>)
  show <dir...>       Show Commands for repo <dir>
  mirror <dir>        Mirror git repositories under <dir> by looking
                      for 'remote.origin.mirror = true' config flag.
		      Also set: 'fetch = +refs/*:refs/*'
  create-public       Create new repository that is
                        - Read/Write by Owner
                        - Read/Write by 'share' group.
                        - Read-Only through www via cgit
  create-shared       Create new repository that is
                        - Read/Write by Owner
                        - Read/Write by 'share' group.
  create-private      Create new repository that is
                        - Read/Write by Owner only
  make-public <dir>   Change permissions to public
  make-shared <dir>   Change permissions to shared
  make-private <dir>  Change permissions to private


  ☛ Base directory of repositories: $BASEDIR
  ☛ Color codes: ${C_bg_magenta}public${C_bg_default}, ${C_bg_lblue}shared${C_bg_default}, ${C_bg_green}private${C_bg_default}
  ☛ For limited ssh-access with current user configure ~/.ssh/authorized_keys with:
      command=\"git shell -c \\\"\$SSH_ORIGINAL_COMMAND\\\"\",no-port-forwarding,no-agent-forwarding,no-X11-forwarding,no-pty ssh-rsa AAAAB3.....
HERE
}

case "$1" in
    list) do_list "$2" ;;
    create-public) do_create_public ;;
    create-shared) do_create_shared ;;
    create-private) do_create_private ;;
    make-public) do_make_public "$2" ;;
    make-shared) do_make_shared "$2" ;;
    make-private) do_make_private "$2" ;;
    show)
        shift
        do_show "$@"
        ;;
    mirror) do_mirror "$2" ;;
    help)
	do_help
	;;
    *)
        cat <<HERE
Unknown subcommand: '$1'
HERE
	do_help
        ;;
esac