Skip to content

Commit ac4a104

Browse files
committed
api: able to create repo and fix #726
- POST /user/repos - POST /org/:org/repos
1 parent 2f3a7e5 commit ac4a104

File tree

14 files changed

+162
-81
lines changed

14 files changed

+162
-81
lines changed

cmd/web.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import (
2626
"github.com/macaron-contrib/session"
2727
"github.com/macaron-contrib/toolbox"
2828

29+
api "github.com/gogits/go-gogs-client"
30+
2931
"github.com/gogits/gogs/models"
3032
"github.com/gogits/gogs/modules/auth"
3133
"github.com/gogits/gogs/modules/auth/apiv1"
@@ -66,7 +68,7 @@ func checkVersion() {
6668

6769
// Check dependency version.
6870
macaronVer := git.MustParseVersion(strings.Join(strings.Split(macaron.Version(), ".")[:3], "."))
69-
if macaronVer.LessThan(git.MustParseVersion("0.4.2")) {
71+
if macaronVer.LessThan(git.MustParseVersion("0.4.7")) {
7072
log.Fatal(4, "Package macaron version is too old, did you forget to update?(github.com/Unknwon/macaron)")
7173
}
7274
i18nVer := git.MustParseVersion(i18n.Version())
@@ -200,14 +202,15 @@ func runWeb(*cli.Context) {
200202
})
201203

202204
// Repositories.
203-
m.Get("/user/repos", middleware.ApiReqToken(), v1.ListMyRepos)
205+
m.Combo("/user/repos", middleware.ApiReqToken()).Get(v1.ListMyRepos).Post(bind(api.CreateRepoOption{}), v1.CreateRepo)
206+
m.Post("/org/:org/repos", middleware.ApiReqToken(), bind(api.CreateRepoOption{}), v1.CreateOrgRepo)
204207
m.Group("/repos", func() {
205208
m.Get("/search", v1.SearchRepos)
206-
m.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), v1.Migrate)
209+
m.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), v1.MigrateRepo)
207210

208211
m.Group("/:username/:reponame", func() {
209-
m.Combo("/hooks").Get(v1.ListRepoHooks).Post(bind(v1.CreateRepoHookForm{}), v1.CreateRepoHook)
210-
m.Patch("/hooks/:id:int", bind(v1.EditRepoHookForm{}), v1.EditRepoHook)
212+
m.Combo("/hooks").Get(v1.ListRepoHooks).Post(bind(api.CreateHookOption{}), v1.CreateRepoHook)
213+
m.Patch("/hooks/:id:int", bind(api.EditHookOption{}), v1.EditRepoHook)
211214
m.Get("/raw/*", middleware.RepoRef(), v1.GetRepoRawFile)
212215
}, middleware.ApiRepoAssignment(), middleware.ApiReqToken())
213216
})

conf/locale/locale_de-DE.ini

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ enable_custom_avatar_helper=Aktiviere dies, um deinen Avatar nicht von Gravatar
208208
choose_new_avatar=Neuen Avatar auswählen
209209
update_avatar=Avatar-Einstellung aktualisieren
210210
uploaded_avatar_not_a_image=Die hochgeladene Datei ist kein Bild.
211-
no_custom_avatar_available=No custom avatar available, cannot enable it.
211+
no_custom_avatar_available=Kein benutzerdefinierter Avatar verfügbar, Aktivierung ist nicht möglich.
212212
update_avatar_success=Deine Avatar-Einstellung wurde aktualisiert.
213213

214214
change_password=Passwort ändern
@@ -377,6 +377,30 @@ diff.stats_desc=<strong> %d geänderte Dateien</strong> mit <strong>%d neuen Zei
377377
diff.bin=BIN
378378
diff.view_file=Datei anzeigen
379379

380+
release.releases=Releases
381+
release.new_release=Neues Release
382+
release.draft=Entwurf
383+
release.prerelease=Pre-Release
384+
release.stable=Endversion
385+
release.edit=bearbeiten
386+
release.ahead=<strong>%d</strong> Commits zu %s seit diesem Release
387+
release.source_code=Quelltext
388+
release.tag_name=Tag-Name
389+
release.target=Ziel
390+
release.tag_helper=Wähle ein neues Tag oder erstelle ein Tag beim Veröffentlichen.
391+
release.release_title=Release-Titel
392+
release.content_with_md=Inhalt mit <a href="%s">Markdown</a>
393+
release.write=Schreiben
394+
release.preview=Vorschau
395+
release.content_placeholder=Schreibe hier etwas
396+
release.loading=Laden…
397+
release.prerelease_desc=Dies ist eine Pre-Release-Version
398+
release.prerelease_helper=Wir möchten darauf hinweisen, dass dieses Release nicht für den produktiven Einsatz gedacht ist.
399+
release.publish=Release veröffentlichen
400+
release.save_draft=Entwurf speichern
401+
release.edit_release=Release bearbeiten
402+
release.tag_name_already_exist=Ein Release mit diesem Tag existiert bereits.
403+
380404
[org]
381405
org_name_holder=Name der Organisation
382406
org_name_helper=Gute Namen von Organisationen sind kurz und einprägsam.
@@ -476,8 +500,8 @@ dashboard.delete_inactivate_accounts=inaktive Konten löschen
476500
dashboard.delete_inactivate_accounts_success=Alle inaktiven Konten wurden erfolgreich gelöscht.
477501
dashboard.delete_repo_archives=Alle Repository-Archive löschen
478502
dashboard.delete_repo_archives_success=Alle Repositoriy-Archive wurden gelöscht.
479-
dashboard.git_gc_repos=Führe Garbage Collection auf Repositorys aus
480-
dashboard.git_gc_repos_success=Garbage Collection wurde auf allen Repositorys erfolgreich ausgeführt.
503+
dashboard.git_gc_repos=Führe Garbage Collection auf Repositories aus
504+
dashboard.git_gc_repos_success=Garbage Collection wurde auf allen Repositories erfolgreich ausgeführt.
481505
dashboard.server_uptime=Server-Uptime
482506
dashboard.current_goroutine=Aktuelle Goroutines
483507
dashboard.current_memory_usage=Aktuelle Speichernutzung

gogs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import (
1717
"github.com/gogits/gogs/modules/setting"
1818
)
1919

20-
const APP_VER = "0.5.8.1211 Beta"
20+
const APP_VER = "0.5.8.1212 Beta"
2121

2222
func init() {
2323
runtime.GOMAXPROCS(runtime.NumCPU())

models/org.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ var (
2525
ErrLastOrgOwner = errors.New("The user to remove is the last member in owner team")
2626
)
2727

28-
// IsOrgOwner returns true if given user is in the owner team.
29-
func (org *User) IsOrgOwner(uid int64) bool {
28+
// IsOwnedBy returns true if given user is in the owner team.
29+
func (org *User) IsOwnedBy(uid int64) bool {
3030
return IsOrganizationOwner(org.Id, uid)
3131
}
3232

@@ -170,6 +170,24 @@ func CreateOrganization(org, owner *User) (*User, error) {
170170
return org, sess.Commit()
171171
}
172172

173+
// GetOrgByName returns organization by given name.
174+
func GetOrgByName(name string) (*User, error) {
175+
if len(name) == 0 {
176+
return nil, ErrOrgNotExist
177+
}
178+
u := &User{
179+
LowerName: strings.ToLower(name),
180+
Type: ORGANIZATION,
181+
}
182+
has, err := x.Get(u)
183+
if err != nil {
184+
return nil, err
185+
} else if !has {
186+
return nil, ErrOrgNotExist
187+
}
188+
return u, nil
189+
}
190+
173191
// CountOrganizations returns number of organizations.
174192
func CountOrganizations() int64 {
175193
count, _ := x.Where("type=1").Count(new(User))

modules/auth/repo_form.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ type CreateRepoForm struct {
2121
RepoName string `form:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"`
2222
Private bool `form:"private"`
2323
Description string `form:"desc" binding:"MaxSize(255)"`
24+
AutoInit bool `form:"auto_init"`
2425
Gitignore string `form:"gitignore"`
2526
License string `form:"license"`
26-
InitReadme bool `form:"init_readme"`
2727
}
2828

2929
func (f *CreateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {

modules/middleware/org.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func OrgAssignment(redirect bool, args ...bool) macaron.Handler {
4848
ctx.Data["Org"] = org
4949

5050
if ctx.IsSigned {
51-
ctx.Org.IsOwner = org.IsOrgOwner(ctx.User.Id)
51+
ctx.Org.IsOwner = org.IsOwnedBy(ctx.User.Id)
5252
if ctx.Org.IsOwner {
5353
ctx.Org.IsMember = true
5454
ctx.Org.IsAdminTeam = true

modules/middleware/repo.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ func ApiRepoAssignment() macaron.Handler {
5555
ctx.Repo.Owner = u
5656

5757
// Organization owner team members are true owners as well.
58-
if ctx.IsSigned && ctx.Repo.Owner.IsOrganization() && ctx.Repo.Owner.IsOrgOwner(ctx.User.Id) {
58+
if ctx.IsSigned && ctx.Repo.Owner.IsOrganization() && ctx.Repo.Owner.IsOwnedBy(ctx.User.Id) {
5959
ctx.Repo.IsTrueOwner = true
6060
}
6161

@@ -280,7 +280,7 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler {
280280
ctx.Repo.Owner = u
281281

282282
// Organization owner team members are true owners as well.
283-
if ctx.IsSigned && ctx.Repo.Owner.IsOrganization() && ctx.Repo.Owner.IsOrgOwner(ctx.User.Id) {
283+
if ctx.IsSigned && ctx.Repo.Owner.IsOrganization() && ctx.Repo.Owner.IsOwnedBy(ctx.User.Id) {
284284
ctx.Repo.IsTrueOwner = true
285285
}
286286

routers/api/v1/repo.go

Lines changed: 83 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,26 @@ import (
2121
"github.com/gogits/gogs/modules/setting"
2222
)
2323

24+
// ToApiRepository converts repository to API format.
25+
func ToApiRepository(owner *models.User, repo *models.Repository, permission api.Permission) *api.Repository {
26+
sshUrlFmt := "%s@%s:%s/%s.git"
27+
if setting.SshPort != 22 {
28+
sshUrlFmt = "ssh://%s@%s:%d/%s/%s.git"
29+
}
30+
htmlUrl := setting.AppUrl + owner.Name + "/" + repo.Name
31+
return &api.Repository{
32+
Id: repo.Id,
33+
Owner: *ToApiUser(owner),
34+
FullName: owner.Name + "/" + repo.Name,
35+
Private: repo.IsPrivate,
36+
Fork: repo.IsFork,
37+
HtmlUrl: htmlUrl,
38+
SshUrl: fmt.Sprintf(sshUrlFmt, setting.RunUser, setting.Domain, owner.LowerName, repo.LowerName),
39+
CloneUrl: htmlUrl + ".git",
40+
Permissions: permission,
41+
}
42+
}
43+
2444
func SearchRepos(ctx *middleware.Context) {
2545
opt := models.SearchOption{
2646
Keyword: path.Base(ctx.Query("q")),
@@ -44,7 +64,7 @@ func SearchRepos(ctx *middleware.Context) {
4464
})
4565
return
4666
}
47-
if u.IsOrganization() && u.IsOrgOwner(ctx.User.Id) {
67+
if u.IsOrganization() && u.IsOwnedBy(ctx.User.Id) {
4868
opt.Private = true
4969
}
5070
// FIXME: how about collaborators?
@@ -75,13 +95,66 @@ func SearchRepos(ctx *middleware.Context) {
7595
}
7696
}
7797

78-
ctx.Render.JSON(200, map[string]interface{}{
98+
ctx.JSON(200, map[string]interface{}{
7999
"ok": true,
80100
"data": results,
81101
})
82102
}
83103

84-
func Migrate(ctx *middleware.Context, form auth.MigrateRepoForm) {
104+
func createRepo(ctx *middleware.Context, owner *models.User, opt api.CreateRepoOption) {
105+
repo, err := models.CreateRepository(owner, opt.Name, opt.Description,
106+
opt.Gitignore, opt.License, opt.Private, false, opt.AutoInit)
107+
if err != nil {
108+
if err == models.ErrRepoAlreadyExist ||
109+
err == models.ErrRepoNameIllegal {
110+
ctx.JSON(422, &base.ApiJsonErr{err.Error(), base.DOC_URL})
111+
} else {
112+
log.Error(4, "CreateRepository: %v", err)
113+
if repo != nil {
114+
if err = models.DeleteRepository(ctx.User.Id, repo.Id, ctx.User.Name); err != nil {
115+
log.Error(4, "DeleteRepository: %v", err)
116+
}
117+
}
118+
ctx.Error(500)
119+
}
120+
return
121+
}
122+
123+
ctx.JSON(200, ToApiRepository(owner, repo, api.Permission{true, true, true}))
124+
}
125+
126+
// POST /user/repos
127+
// https://developer.github.com/v3/repos/#create
128+
func CreateRepo(ctx *middleware.Context, opt api.CreateRepoOption) {
129+
// Shouldn't reach this condition, but just in case.
130+
if ctx.User.IsOrganization() {
131+
ctx.JSON(422, "not allowed creating repository for organization")
132+
return
133+
}
134+
createRepo(ctx, ctx.User, opt)
135+
}
136+
137+
// POST /orgs/:org/repos
138+
// https://developer.github.com/v3/repos/#create
139+
func CreateOrgRepo(ctx *middleware.Context, opt api.CreateRepoOption) {
140+
org, err := models.GetOrgByName(ctx.Params(":org"))
141+
if err != nil {
142+
if err == models.ErrUserNotExist {
143+
ctx.Error(404)
144+
} else {
145+
ctx.Error(500)
146+
}
147+
return
148+
}
149+
150+
if !org.IsOwnedBy(ctx.User.Id) {
151+
ctx.Error(403)
152+
return
153+
}
154+
createRepo(ctx, org, opt)
155+
}
156+
157+
func MigrateRepo(ctx *middleware.Context, form auth.MigrateRepoForm) {
85158
u, err := models.GetUserByName(ctx.Query("username"))
86159
if err != nil {
87160
ctx.JSON(500, map[string]interface{}{
@@ -103,17 +176,15 @@ func Migrate(ctx *middleware.Context, form auth.MigrateRepoForm) {
103176
if form.Uid != u.Id {
104177
org, err := models.GetUserById(form.Uid)
105178
if err != nil {
106-
ctx.JSON(500, map[string]interface{}{
107-
"ok": false,
108-
"error": err.Error(),
109-
})
179+
log.Error(4, "GetUserById: %v", err)
180+
ctx.Error(500)
110181
return
111182
}
112183
ctxUser = org
113184
}
114185

115186
if ctx.HasError() {
116-
ctx.JSON(500, map[string]interface{}{
187+
ctx.JSON(422, map[string]interface{}{
117188
"ok": false,
118189
"error": ctx.GetErrMsg(),
119190
})
@@ -122,7 +193,7 @@ func Migrate(ctx *middleware.Context, form auth.MigrateRepoForm) {
122193

123194
if ctxUser.IsOrganization() {
124195
// Check ownership of organization.
125-
if !ctxUser.IsOrgOwner(u.Id) {
196+
if !ctxUser.IsOwnedBy(u.Id) {
126197
ctx.JSON(403, map[string]interface{}{
127198
"ok": false,
128199
"error": "given user is not owner of organization",
@@ -173,54 +244,20 @@ func ListMyRepos(ctx *middleware.Context) {
173244
return
174245
}
175246

176-
sshUrlFmt := "%s@%s:%s/%s.git"
177-
if setting.SshPort != 22 {
178-
sshUrlFmt = "ssh://%s@%s:%d/%s/%s.git"
179-
}
180-
181247
repos := make([]*api.Repository, numOwnRepos+len(collaRepos))
182-
// FIXME: make only one loop
183248
for i := range ownRepos {
184-
repos[i] = &api.Repository{
185-
Id: ownRepos[i].Id,
186-
Owner: api.User{
187-
Id: ctx.User.Id,
188-
UserName: ctx.User.Name,
189-
AvatarUrl: string(setting.Protocol) + ctx.User.AvatarLink(),
190-
},
191-
FullName: ctx.User.Name + "/" + ownRepos[i].Name,
192-
Private: ownRepos[i].IsPrivate,
193-
Fork: ownRepos[i].IsFork,
194-
HtmlUrl: setting.AppUrl + ctx.User.Name + "/" + ownRepos[i].Name,
195-
SshUrl: fmt.Sprintf(sshUrlFmt, setting.RunUser, setting.Domain, ctx.User.LowerName, ownRepos[i].LowerName),
196-
Permissions: api.Permission{true, true, true},
197-
}
198-
repos[i].CloneUrl = repos[i].HtmlUrl + ".git"
249+
repos[i] = ToApiRepository(ctx.User, ownRepos[i], api.Permission{true, true, true})
199250
}
200251
for i := range collaRepos {
201252
if err = collaRepos[i].GetOwner(); err != nil {
202253
ctx.JSON(500, &base.ApiJsonErr{"GetOwner: " + err.Error(), base.DOC_URL})
203254
return
204255
}
205256
j := i + numOwnRepos
206-
repos[j] = &api.Repository{
207-
Id: collaRepos[i].Id,
208-
Owner: api.User{
209-
Id: collaRepos[i].Owner.Id,
210-
UserName: collaRepos[i].Owner.Name,
211-
AvatarUrl: string(setting.Protocol) + collaRepos[i].Owner.AvatarLink(),
212-
},
213-
FullName: collaRepos[i].Owner.Name + "/" + collaRepos[i].Name,
214-
Private: collaRepos[i].IsPrivate,
215-
Fork: collaRepos[i].IsFork,
216-
HtmlUrl: setting.AppUrl + collaRepos[i].Owner.Name + "/" + collaRepos[i].Name,
217-
SshUrl: fmt.Sprintf(sshUrlFmt, setting.RunUser, setting.Domain, collaRepos[i].Owner.LowerName, collaRepos[i].LowerName),
218-
Permissions: api.Permission{false, collaRepos[i].CanPush, true},
219-
}
220-
repos[j].CloneUrl = repos[j].HtmlUrl + ".git"
257+
repos[j] = ToApiRepository(collaRepos[i].Owner, collaRepos[i].Repository, api.Permission{false, collaRepos[i].CanPush, true})
221258

222259
// FIXME: cache result to reduce DB query?
223-
if collaRepos[i].Owner.IsOrganization() && collaRepos[i].Owner.IsOrgOwner(ctx.User.Id) {
260+
if collaRepos[i].Owner.IsOrganization() && collaRepos[i].Owner.IsOwnedBy(ctx.User.Id) {
224261
repos[j].Permissions.Admin = true
225262
}
226263
}

routers/api/v1/repo_hooks.go

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,9 @@ func ListRepoHooks(ctx *middleware.Context) {
4848
ctx.JSON(200, &apiHooks)
4949
}
5050

51-
type CreateRepoHookForm struct {
52-
Type string `json:"type" binding:"Required"`
53-
Config map[string]string `json:"config" binding:"Required"`
54-
Active bool `json:"active"`
55-
}
56-
5751
// POST /repos/:username/:reponame/hooks
5852
// https://developer.github.com/v3/repos/hooks/#create-a-hook
59-
func CreateRepoHook(ctx *middleware.Context, form CreateRepoHookForm) {
53+
func CreateRepoHook(ctx *middleware.Context, form api.CreateHookOption) {
6054
if !models.IsValidHookTaskType(form.Type) {
6155
ctx.JSON(422, &base.ApiJsonErr{"invalid hook type", base.DOC_URL})
6256
return
@@ -124,14 +118,9 @@ func CreateRepoHook(ctx *middleware.Context, form CreateRepoHookForm) {
124118
ctx.JSON(201, apiHook)
125119
}
126120

127-
type EditRepoHookForm struct {
128-
Config map[string]string `json:"config"`
129-
Active *bool `json:"active"`
130-
}
131-
132121
// PATCH /repos/:username/:reponame/hooks/:id
133122
// https://developer.github.com/v3/repos/hooks/#edit-a-hook
134-
func EditRepoHook(ctx *middleware.Context, form EditRepoHookForm) {
123+
func EditRepoHook(ctx *middleware.Context, form api.EditHookOption) {
135124
w, err := models.GetWebhookById(ctx.ParamsInt64(":id"))
136125
if err != nil {
137126
ctx.JSON(500, &base.ApiJsonErr{"GetWebhookById: " + err.Error(), base.DOC_URL})

0 commit comments

Comments
 (0)