Skip to content

Commit 3454836

Browse files
segevfinertechknowlogick
authored andcommitted
Add API for manipulating Git hooks (#6436)
* Add API for manipulating Git hooks Signed-off-by: Segev Finer <[email protected]> * Replace code.gitea.io/sdk with PR branch temporarily for CI * Switch back to code.gitea.io/sdk@master * Return 403 instead of 404 on no permission to edit hooks in API * Add tests for Git hooks API * Update models/repo_list_test.go Co-Authored-By: segevfiner <[email protected]> * Update models/repo_list_test.go Co-Authored-By: segevfiner <[email protected]> * empty line
1 parent 827ab6b commit 3454836

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1306
-19
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module code.gitea.io/gitea
33
go 1.12
44

55
require (
6-
code.gitea.io/sdk v0.0.0-20190321154058-a669487e86e0
6+
code.gitea.io/sdk v0.0.0-20190416172854-7d954d775498
77
github.com/BurntSushi/toml v0.3.1 // indirect
88
github.com/PuerkitoBio/goquery v0.0.0-20170324135448-ed7d758e9a34
99
github.com/RoaringBitmap/roaring v0.4.7 // indirect

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
cloud.google.com/go v0.30.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2-
code.gitea.io/sdk v0.0.0-20190321154058-a669487e86e0 h1:pIKKrTUox+0pGcZwFl19Glw9gKfoeNkA02EqeiSmLjs=
3-
code.gitea.io/sdk v0.0.0-20190321154058-a669487e86e0/go.mod h1:5bZt0dRznpn2JysytQnV0yCru3FwDv9O5G91jo+lDAk=
2+
code.gitea.io/sdk v0.0.0-20190416172854-7d954d775498 h1:rcjwXMYIjYts88akPiyy/GB+imecpf159jojChciEEw=
3+
code.gitea.io/sdk v0.0.0-20190416172854-7d954d775498/go.mod h1:5bZt0dRznpn2JysytQnV0yCru3FwDv9O5G91jo+lDAk=
44
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
55
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
66
github.com/PuerkitoBio/goquery v0.0.0-20170324135448-ed7d758e9a34 h1:UsHpWO0Elp6NaWVARdZHjiYwkhrspHVEGsyIKPb9OI8=
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
// Copyright 2019 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package integrations
6+
7+
import (
8+
"fmt"
9+
"net/http"
10+
"testing"
11+
12+
"code.gitea.io/gitea/models"
13+
api "code.gitea.io/sdk/gitea"
14+
15+
"github.com/stretchr/testify/assert"
16+
)
17+
18+
const testHookContent = `#!/bin/bash
19+
20+
echo Hello, World!
21+
`
22+
23+
func TestAPIListGitHooks(t *testing.T) {
24+
prepareTestEnv(t)
25+
26+
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 37}).(*models.Repository)
27+
owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User)
28+
29+
// user1 is an admin user
30+
session := loginUser(t, "user1")
31+
token := getTokenForLoggedInUser(t, session)
32+
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git?token=%s",
33+
owner.Name, repo.Name, token)
34+
resp := MakeRequest(t, req, http.StatusOK)
35+
var apiGitHooks []*api.GitHook
36+
DecodeJSON(t, resp, &apiGitHooks)
37+
assert.Len(t, apiGitHooks, 3)
38+
for _, apiGitHook := range apiGitHooks {
39+
if apiGitHook.Name == "pre-receive" {
40+
assert.True(t, apiGitHook.IsActive)
41+
assert.Equal(t, testHookContent, apiGitHook.Content)
42+
} else {
43+
assert.False(t, apiGitHook.IsActive)
44+
assert.Empty(t, apiGitHook.Content)
45+
}
46+
}
47+
}
48+
49+
func TestAPIListGitHooksNoHooks(t *testing.T) {
50+
prepareTestEnv(t)
51+
52+
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
53+
owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User)
54+
55+
// user1 is an admin user
56+
session := loginUser(t, "user1")
57+
token := getTokenForLoggedInUser(t, session)
58+
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git?token=%s",
59+
owner.Name, repo.Name, token)
60+
resp := MakeRequest(t, req, http.StatusOK)
61+
var apiGitHooks []*api.GitHook
62+
DecodeJSON(t, resp, &apiGitHooks)
63+
assert.Len(t, apiGitHooks, 3)
64+
for _, apiGitHook := range apiGitHooks {
65+
assert.False(t, apiGitHook.IsActive)
66+
assert.Empty(t, apiGitHook.Content)
67+
}
68+
}
69+
70+
func TestAPIListGitHooksNoAccess(t *testing.T) {
71+
prepareTestEnv(t)
72+
73+
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
74+
owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User)
75+
76+
session := loginUser(t, owner.Name)
77+
token := getTokenForLoggedInUser(t, session)
78+
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git?token=%s",
79+
owner.Name, repo.Name, token)
80+
MakeRequest(t, req, http.StatusForbidden)
81+
}
82+
83+
func TestAPIGetGitHook(t *testing.T) {
84+
prepareTestEnv(t)
85+
86+
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 37}).(*models.Repository)
87+
owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User)
88+
89+
// user1 is an admin user
90+
session := loginUser(t, "user1")
91+
token := getTokenForLoggedInUser(t, session)
92+
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git/pre-receive?token=%s",
93+
owner.Name, repo.Name, token)
94+
resp := MakeRequest(t, req, http.StatusOK)
95+
var apiGitHook *api.GitHook
96+
DecodeJSON(t, resp, &apiGitHook)
97+
assert.True(t, apiGitHook.IsActive)
98+
assert.Equal(t, testHookContent, apiGitHook.Content)
99+
}
100+
101+
func TestAPIGetGitHookNoAccess(t *testing.T) {
102+
prepareTestEnv(t)
103+
104+
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
105+
owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User)
106+
107+
session := loginUser(t, owner.Name)
108+
token := getTokenForLoggedInUser(t, session)
109+
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git/pre-receive?token=%s",
110+
owner.Name, repo.Name, token)
111+
MakeRequest(t, req, http.StatusForbidden)
112+
}
113+
114+
func TestAPIEditGitHook(t *testing.T) {
115+
prepareTestEnv(t)
116+
117+
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
118+
owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User)
119+
120+
// user1 is an admin user
121+
session := loginUser(t, "user1")
122+
token := getTokenForLoggedInUser(t, session)
123+
124+
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/hooks/git/pre-receive?token=%s",
125+
owner.Name, repo.Name, token)
126+
req := NewRequestWithJSON(t, "PATCH", urlStr, &api.EditGitHookOption{
127+
Content: testHookContent,
128+
})
129+
resp := MakeRequest(t, req, http.StatusOK)
130+
var apiGitHook *api.GitHook
131+
DecodeJSON(t, resp, &apiGitHook)
132+
assert.True(t, apiGitHook.IsActive)
133+
assert.Equal(t, testHookContent, apiGitHook.Content)
134+
135+
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git/pre-receive?token=%s",
136+
owner.Name, repo.Name, token)
137+
resp = MakeRequest(t, req, http.StatusOK)
138+
var apiGitHook2 *api.GitHook
139+
DecodeJSON(t, resp, &apiGitHook2)
140+
assert.True(t, apiGitHook2.IsActive)
141+
assert.Equal(t, testHookContent, apiGitHook2.Content)
142+
}
143+
144+
func TestAPIEditGitHookNoAccess(t *testing.T) {
145+
prepareTestEnv(t)
146+
147+
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
148+
owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User)
149+
150+
session := loginUser(t, owner.Name)
151+
token := getTokenForLoggedInUser(t, session)
152+
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/hooks/git/pre-receive?token=%s",
153+
owner.Name, repo.Name, token)
154+
req := NewRequestWithJSON(t, "PATCH", urlStr, &api.EditGitHookOption{
155+
Content: testHookContent,
156+
})
157+
MakeRequest(t, req, http.StatusForbidden)
158+
}
159+
160+
func TestAPIDeleteGitHook(t *testing.T) {
161+
prepareTestEnv(t)
162+
163+
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 37}).(*models.Repository)
164+
owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User)
165+
166+
// user1 is an admin user
167+
session := loginUser(t, "user1")
168+
token := getTokenForLoggedInUser(t, session)
169+
170+
req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/hooks/git/pre-receive?token=%s",
171+
owner.Name, repo.Name, token)
172+
MakeRequest(t, req, http.StatusNoContent)
173+
174+
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git/pre-receive?token=%s",
175+
owner.Name, repo.Name, token)
176+
resp := MakeRequest(t, req, http.StatusOK)
177+
var apiGitHook2 *api.GitHook
178+
DecodeJSON(t, resp, &apiGitHook2)
179+
assert.False(t, apiGitHook2.IsActive)
180+
assert.Empty(t, apiGitHook2.Content)
181+
}
182+
183+
func TestAPIDeleteGitHookNoAccess(t *testing.T) {
184+
prepareTestEnv(t)
185+
186+
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
187+
owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User)
188+
189+
session := loginUser(t, owner.Name)
190+
token := getTokenForLoggedInUser(t, session)
191+
req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/hooks/git/pre-receive?token=%s",
192+
owner.Name, repo.Name, token)
193+
MakeRequest(t, req, http.StatusForbidden)
194+
}

integrations/api_repo_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ func TestAPISearchRepo(t *testing.T) {
7070
expectedResults
7171
}{
7272
{name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50", expectedResults: expectedResults{
73-
nil: {count: 20},
74-
user: {count: 20},
75-
user2: {count: 20}},
73+
nil: {count: 21},
74+
user: {count: 21},
75+
user2: {count: 21}},
7676
},
7777
{name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10", expectedResults: expectedResults{
7878
nil: {count: 10},
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ref: refs/heads/master
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[core]
2+
repositoryformatversion = 0
3+
filemode = true
4+
bare = true
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Unnamed repository; edit this file 'description' to name the repository.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/bin/sh
2+
#
3+
# An example hook script to check the commit log message taken by
4+
# applypatch from an e-mail message.
5+
#
6+
# The hook should exit with non-zero status after issuing an
7+
# appropriate message if it wants to stop the commit. The hook is
8+
# allowed to edit the commit message file.
9+
#
10+
# To enable this hook, rename this file to "applypatch-msg".
11+
12+
. git-sh-setup
13+
commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
14+
test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
15+
:
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/bin/sh
2+
#
3+
# An example hook script to check the commit log message.
4+
# Called by "git commit" with one argument, the name of the file
5+
# that has the commit message. The hook should exit with non-zero
6+
# status after issuing an appropriate message if it wants to stop the
7+
# commit. The hook is allowed to edit the commit message file.
8+
#
9+
# To enable this hook, rename this file to "commit-msg".
10+
11+
# Uncomment the below to add a Signed-off-by line to the message.
12+
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
13+
# hook is more suited to it.
14+
#
15+
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
16+
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
17+
18+
# This example catches duplicate Signed-off-by lines.
19+
20+
test "" = "$(grep '^Signed-off-by: ' "$1" |
21+
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
22+
echo >&2 Duplicate Signed-off-by lines.
23+
exit 1
24+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/env bash
2+
ORI_DIR=`pwd`
3+
SHELL_FOLDER=$(cd "$(dirname "$0")";pwd)
4+
cd "$ORI_DIR"
5+
for i in `ls "$SHELL_FOLDER/post-receive.d"`; do
6+
sh "$SHELL_FOLDER/post-receive.d/$i"
7+
done
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/usr/bin/env bash
2+
"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" post-receive
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/sh
2+
#
3+
# An example hook script to prepare a packed repository for use over
4+
# dumb transports.
5+
#
6+
# To enable this hook, rename this file to "post-update".
7+
8+
exec git update-server-info
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/bin/sh
2+
#
3+
# An example hook script to verify what is about to be committed
4+
# by applypatch from an e-mail message.
5+
#
6+
# The hook should exit with non-zero status after issuing an
7+
# appropriate message if it wants to stop the commit.
8+
#
9+
# To enable this hook, rename this file to "pre-applypatch".
10+
11+
. git-sh-setup
12+
precommit="$(git rev-parse --git-path hooks/pre-commit)"
13+
test -x "$precommit" && exec "$precommit" ${1+"$@"}
14+
:
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#!/bin/sh
2+
#
3+
# An example hook script to verify what is about to be committed.
4+
# Called by "git commit" with no arguments. The hook should
5+
# exit with non-zero status after issuing an appropriate message if
6+
# it wants to stop the commit.
7+
#
8+
# To enable this hook, rename this file to "pre-commit".
9+
10+
if git rev-parse --verify HEAD >/dev/null 2>&1
11+
then
12+
against=HEAD
13+
else
14+
# Initial commit: diff against an empty tree object
15+
against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
16+
fi
17+
18+
# If you want to allow non-ASCII filenames set this variable to true.
19+
allownonascii=$(git config --bool hooks.allownonascii)
20+
21+
# Redirect output to stderr.
22+
exec 1>&2
23+
24+
# Cross platform projects tend to avoid non-ASCII filenames; prevent
25+
# them from being added to the repository. We exploit the fact that the
26+
# printable range starts at the space character and ends with tilde.
27+
if [ "$allownonascii" != "true" ] &&
28+
# Note that the use of brackets around a tr range is ok here, (it's
29+
# even required, for portability to Solaris 10's /usr/bin/tr), since
30+
# the square bracket bytes happen to fall in the designated range.
31+
test $(git diff --cached --name-only --diff-filter=A -z $against |
32+
LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
33+
then
34+
cat <<\EOF
35+
Error: Attempt to add a non-ASCII file name.
36+
37+
This can cause problems if you want to work with people on other platforms.
38+
39+
To be portable it is advisable to rename the file.
40+
41+
If you know what you are doing you can disable this check using:
42+
43+
git config hooks.allownonascii true
44+
EOF
45+
exit 1
46+
fi
47+
48+
# If there are whitespace errors, print the offending file names and fail.
49+
exec git diff-index --check --cached $against --

0 commit comments

Comments
 (0)