Browse Source

Integration test framework (#1290)

* Integration test framework

* udpate drone sign

* Formatting fixes and move router.go to routers/

* update sign for drone
Ethan Koenig 2 years ago
parent
commit
c58708d3ee

+ 13 - 2
.drone.yml

@@ -25,6 +25,17 @@ pipeline:
25 25
     when:
26 26
       event: [ push, tag, pull_request ]
27 27
 
28
+  test-sqlite:
29
+    image: webhippie/golang:edge
30
+    pull: true
31
+    environment:
32
+      TAGS: bindata
33
+      GOPATH: /srv/app
34
+    commands:
35
+      - make test-sqlite
36
+    when:
37
+      event: [ push, tag, pull_request ]
38
+
28 39
   test-mysql:
29 40
     image: webhippie/golang:edge
30 41
     pull: true
@@ -32,7 +43,7 @@ pipeline:
32 43
       TAGS: bindata
33 44
       GOPATH: /srv/app
34 45
     commands:
35
-      - make test-mysql
46
+      - echo make test-mysql # Not ready yet
36 47
     when:
37 48
       event: [ push, tag, pull_request ]
38 49
 
@@ -43,7 +54,7 @@ pipeline:
43 54
       TAGS: bindata
44 55
       GOPATH: /srv/app
45 56
     commands:
46
-      - make test-pgsql
57
+      - echo make test-pqsql # Not ready yet
47 58
     when:
48 59
       event: [ push, tag, pull_request ]
49 60
 

File diff suppressed because it is too large
+ 1 - 1
.drone.yml.sig


+ 1 - 0
.gitignore

@@ -44,3 +44,4 @@ coverage.out
44 44
 /indexers
45 45
 /log
46 46
 /public/img/avatar
47
+/integrations/gitea-integration*

+ 18 - 4
Makefile

@@ -86,13 +86,27 @@ test-vendor:
86 86
 	fi
87 87
 	govendor status +outside +unused  || exit 1
88 88
 
89
+.PHONY: test-sqlite
90
+test-sqlite: integrations.test integrations/gitea-integration
91
+	GITEA_CONF=integrations/sqlite.ini ./integrations.test
92
+
89 93
 .PHONY: test-mysql
90
-test-mysql:
91
-	@echo "Not integrated yet!"
94
+test-mysql: integrations.test integrations/gitea-integration
95
+	echo "CREATE DATABASE IF NOT EXISTS testgitea" | mysql -u root
96
+	GITEA_CONF=integrations/mysql.ini ./integrations.test
92 97
 
93 98
 .PHONY: test-pgsql
94
-test-pgsql:
95
-	@echo "Not integrated yet!"
99
+test-pgsql: integrations.test integrations/gitea-integration
100
+	GITEA_CONF=integrations/pgsql.ini ./integrations.test
101
+
102
+integrations.test: $(SOURCES)
103
+	go test -c code.gitea.io/gitea/integrations -tags 'sqlite'
104
+
105
+integrations/gitea-integration:
106
+	curl -L https://github.com/ethantkoenig/gitea-integration/archive/v2.tar.gz > integrations/gitea-integration.tar.gz
107
+	mkdir -p integrations/gitea-integration
108
+	tar -xf integrations/gitea-integration.tar.gz -C integrations/gitea-integration --strip-components 1
109
+
96 110
 
97 111
 .PHONY: check
98 112
 check: test

+ 3 - 629
cmd/web.go

@@ -11,39 +11,15 @@ import (
11 11
 	"net/http/fcgi"
12 12
 	_ "net/http/pprof" // Used for debugging if enabled and a web server is running
13 13
 	"os"
14
-	"path"
15 14
 	"strings"
16 15
 
17
-	"code.gitea.io/gitea/models"
18
-	"code.gitea.io/gitea/modules/auth"
19
-	"code.gitea.io/gitea/modules/context"
20
-	"code.gitea.io/gitea/modules/lfs"
21 16
 	"code.gitea.io/gitea/modules/log"
22
-	"code.gitea.io/gitea/modules/options"
23
-	"code.gitea.io/gitea/modules/public"
24 17
 	"code.gitea.io/gitea/modules/setting"
25
-	"code.gitea.io/gitea/modules/templates"
26
-	"code.gitea.io/gitea/modules/validation"
27 18
 	"code.gitea.io/gitea/routers"
28
-	"code.gitea.io/gitea/routers/admin"
29
-	apiv1 "code.gitea.io/gitea/routers/api/v1"
30
-	"code.gitea.io/gitea/routers/dev"
31
-	"code.gitea.io/gitea/routers/org"
32
-	"code.gitea.io/gitea/routers/private"
33
-	"code.gitea.io/gitea/routers/repo"
34
-	"code.gitea.io/gitea/routers/user"
19
+	"code.gitea.io/gitea/routers/routes"
35 20
 
36
-	"github.com/go-macaron/binding"
37
-	"github.com/go-macaron/cache"
38
-	"github.com/go-macaron/captcha"
39
-	"github.com/go-macaron/csrf"
40
-	"github.com/go-macaron/gzip"
41
-	"github.com/go-macaron/i18n"
42
-	"github.com/go-macaron/session"
43
-	"github.com/go-macaron/toolbox"
44 21
 	context2 "github.com/gorilla/context"
45 22
 	"github.com/urfave/cli"
46
-	macaron "gopkg.in/macaron.v1"
47 23
 )
48 24
 
49 25
 // CmdWeb represents the available web sub-command.
@@ -72,94 +48,6 @@ and it takes care of all the other things for you`,
72 48
 	},
73 49
 }
74 50
 
75
-// newMacaron initializes Macaron instance.
76
-func newMacaron() *macaron.Macaron {
77
-	m := macaron.New()
78
-	if !setting.DisableRouterLog {
79
-		m.Use(macaron.Logger())
80
-	}
81
-	m.Use(macaron.Recovery())
82
-	if setting.EnableGzip {
83
-		m.Use(gzip.Gziper())
84
-	}
85
-	if setting.Protocol == setting.FCGI {
86
-		m.SetURLPrefix(setting.AppSubURL)
87
-	}
88
-	m.Use(public.Custom(
89
-		&public.Options{
90
-			SkipLogging: setting.DisableRouterLog,
91
-		},
92
-	))
93
-	m.Use(public.Static(
94
-		&public.Options{
95
-			Directory:   path.Join(setting.StaticRootPath, "public"),
96
-			SkipLogging: setting.DisableRouterLog,
97
-		},
98
-	))
99
-	m.Use(macaron.Static(
100
-		setting.AvatarUploadPath,
101
-		macaron.StaticOptions{
102
-			Prefix:      "avatars",
103
-			SkipLogging: setting.DisableRouterLog,
104
-			ETag:        true,
105
-		},
106
-	))
107
-
108
-	m.Use(templates.Renderer())
109
-	models.InitMailRender(templates.Mailer())
110
-
111
-	localeNames, err := options.Dir("locale")
112
-
113
-	if err != nil {
114
-		log.Fatal(4, "Failed to list locale files: %v", err)
115
-	}
116
-
117
-	localFiles := make(map[string][]byte)
118
-
119
-	for _, name := range localeNames {
120
-		localFiles[name], err = options.Locale(name)
121
-
122
-		if err != nil {
123
-			log.Fatal(4, "Failed to load %s locale file. %v", name, err)
124
-		}
125
-	}
126
-
127
-	m.Use(i18n.I18n(i18n.Options{
128
-		SubURL:      setting.AppSubURL,
129
-		Files:       localFiles,
130
-		Langs:       setting.Langs,
131
-		Names:       setting.Names,
132
-		DefaultLang: "en-US",
133
-		Redirect:    true,
134
-	}))
135
-	m.Use(cache.Cacher(cache.Options{
136
-		Adapter:       setting.CacheAdapter,
137
-		AdapterConfig: setting.CacheConn,
138
-		Interval:      setting.CacheInterval,
139
-	}))
140
-	m.Use(captcha.Captchaer(captcha.Options{
141
-		SubURL: setting.AppSubURL,
142
-	}))
143
-	m.Use(session.Sessioner(setting.SessionConfig))
144
-	m.Use(csrf.Csrfer(csrf.Options{
145
-		Secret:     setting.SecretKey,
146
-		Cookie:     setting.CSRFCookieName,
147
-		SetCookie:  true,
148
-		Header:     "X-Csrf-Token",
149
-		CookiePath: setting.AppSubURL,
150
-	}))
151
-	m.Use(toolbox.Toolboxer(m, toolbox.Options{
152
-		HealthCheckFuncs: []*toolbox.HealthCheckFuncDesc{
153
-			{
154
-				Desc: "Database connection",
155
-				Func: models.Ping,
156
-			},
157
-		},
158
-	}))
159
-	m.Use(context.Contexter())
160
-	return m
161
-}
162
-
163 51
 func runWeb(ctx *cli.Context) error {
164 52
 	if ctx.IsSet("config") {
165 53
 		setting.CustomConf = ctx.String("config")
@@ -171,522 +59,8 @@ func runWeb(ctx *cli.Context) error {
171 59
 
172 60
 	routers.GlobalInit()
173 61
 
174
-	m := newMacaron()
175
-
176
-	reqSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: true})
177
-	ignSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: setting.Service.RequireSignInView})
178
-	ignSignInAndCsrf := context.Toggle(&context.ToggleOptions{DisableCSRF: true})
179
-	reqSignOut := context.Toggle(&context.ToggleOptions{SignOutRequired: true})
180
-
181
-	bindIgnErr := binding.BindIgnErr
182
-	validation.AddBindingRules()
183
-
184
-	m.Use(user.GetNotificationCount)
185
-
186
-	// FIXME: not all routes need go through same middlewares.
187
-	// Especially some AJAX requests, we can reduce middleware number to improve performance.
188
-	// Routers.
189
-	// for health check
190
-	m.Head("/", func() string {
191
-		return ""
192
-	})
193
-	m.Get("/", ignSignIn, routers.Home)
194
-	m.Group("/explore", func() {
195
-		m.Get("", func(ctx *context.Context) {
196
-			ctx.Redirect(setting.AppSubURL + "/explore/repos")
197
-		})
198
-		m.Get("/repos", routers.ExploreRepos)
199
-		m.Get("/users", routers.ExploreUsers)
200
-		m.Get("/organizations", routers.ExploreOrganizations)
201
-	}, ignSignIn)
202
-	m.Combo("/install", routers.InstallInit).Get(routers.Install).
203
-		Post(bindIgnErr(auth.InstallForm{}), routers.InstallPost)
204
-	m.Get("/^:type(issues|pulls)$", reqSignIn, user.Issues)
205
-
206
-	// ***** START: User *****
207
-	m.Group("/user", func() {
208
-		m.Get("/login", user.SignIn)
209
-		m.Post("/login", bindIgnErr(auth.SignInForm{}), user.SignInPost)
210
-		if setting.Service.EnableOpenIDSignIn {
211
-			m.Combo("/login/openid").
212
-				Get(user.SignInOpenID).
213
-				Post(bindIgnErr(auth.SignInOpenIDForm{}), user.SignInOpenIDPost)
214
-			m.Group("/openid", func() {
215
-				m.Combo("/connect").
216
-					Get(user.ConnectOpenID).
217
-					Post(bindIgnErr(auth.ConnectOpenIDForm{}), user.ConnectOpenIDPost)
218
-				m.Combo("/register").
219
-					Get(user.RegisterOpenID).
220
-					Post(bindIgnErr(auth.SignUpOpenIDForm{}), user.RegisterOpenIDPost)
221
-			})
222
-		}
223
-		m.Get("/sign_up", user.SignUp)
224
-		m.Post("/sign_up", bindIgnErr(auth.RegisterForm{}), user.SignUpPost)
225
-		m.Get("/reset_password", user.ResetPasswd)
226
-		m.Post("/reset_password", user.ResetPasswdPost)
227
-		m.Group("/oauth2", func() {
228
-			m.Get("/:provider", user.SignInOAuth)
229
-			m.Get("/:provider/callback", user.SignInOAuthCallback)
230
-		})
231
-		m.Get("/link_account", user.LinkAccount)
232
-		m.Post("/link_account_signin", bindIgnErr(auth.SignInForm{}), user.LinkAccountPostSignIn)
233
-		m.Post("/link_account_signup", bindIgnErr(auth.RegisterForm{}), user.LinkAccountPostRegister)
234
-		m.Group("/two_factor", func() {
235
-			m.Get("", user.TwoFactor)
236
-			m.Post("", bindIgnErr(auth.TwoFactorAuthForm{}), user.TwoFactorPost)
237
-			m.Get("/scratch", user.TwoFactorScratch)
238
-			m.Post("/scratch", bindIgnErr(auth.TwoFactorScratchAuthForm{}), user.TwoFactorScratchPost)
239
-		})
240
-	}, reqSignOut)
241
-
242
-	m.Group("/user/settings", func() {
243
-		m.Get("", user.Settings)
244
-		m.Post("", bindIgnErr(auth.UpdateProfileForm{}), user.SettingsPost)
245
-		m.Combo("/avatar").Get(user.SettingsAvatar).
246
-			Post(binding.MultipartForm(auth.AvatarForm{}), user.SettingsAvatarPost)
247
-		m.Post("/avatar/delete", user.SettingsDeleteAvatar)
248
-		m.Combo("/email").Get(user.SettingsEmails).
249
-			Post(bindIgnErr(auth.AddEmailForm{}), user.SettingsEmailPost)
250
-		m.Post("/email/delete", user.DeleteEmail)
251
-		m.Get("/password", user.SettingsPassword)
252
-		m.Post("/password", bindIgnErr(auth.ChangePasswordForm{}), user.SettingsPasswordPost)
253
-		if setting.Service.EnableOpenIDSignIn {
254
-			m.Group("/openid", func() {
255
-				m.Combo("").Get(user.SettingsOpenID).
256
-					Post(bindIgnErr(auth.AddOpenIDForm{}), user.SettingsOpenIDPost)
257
-				m.Post("/delete", user.DeleteOpenID)
258
-				m.Post("/toggle_visibility", user.ToggleOpenIDVisibility)
259
-			})
260
-		}
261
-
262
-		m.Combo("/ssh").Get(user.SettingsSSHKeys).
263
-			Post(bindIgnErr(auth.AddSSHKeyForm{}), user.SettingsSSHKeysPost)
264
-		m.Post("/ssh/delete", user.DeleteSSHKey)
265
-		m.Combo("/applications").Get(user.SettingsApplications).
266
-			Post(bindIgnErr(auth.NewAccessTokenForm{}), user.SettingsApplicationsPost)
267
-		m.Post("/applications/delete", user.SettingsDeleteApplication)
268
-		m.Route("/delete", "GET,POST", user.SettingsDelete)
269
-		m.Combo("/account_link").Get(user.SettingsAccountLinks).Post(user.SettingsDeleteAccountLink)
270
-		m.Group("/two_factor", func() {
271
-			m.Get("", user.SettingsTwoFactor)
272
-			m.Post("/regenerate_scratch", user.SettingsTwoFactorRegenerateScratch)
273
-			m.Post("/disable", user.SettingsTwoFactorDisable)
274
-			m.Get("/enroll", user.SettingsTwoFactorEnroll)
275
-			m.Post("/enroll", bindIgnErr(auth.TwoFactorAuthForm{}), user.SettingsTwoFactorEnrollPost)
276
-		})
277
-	}, reqSignIn, func(ctx *context.Context) {
278
-		ctx.Data["PageIsUserSettings"] = true
279
-	})
280
-
281
-	m.Group("/user", func() {
282
-		// r.Get("/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds)
283
-		m.Any("/activate", user.Activate)
284
-		m.Any("/activate_email", user.ActivateEmail)
285
-		m.Get("/email2user", user.Email2User)
286
-		m.Get("/forgot_password", user.ForgotPasswd)
287
-		m.Post("/forgot_password", user.ForgotPasswdPost)
288
-		m.Get("/logout", user.SignOut)
289
-	})
290
-	// ***** END: User *****
291
-
292
-	adminReq := context.Toggle(&context.ToggleOptions{SignInRequired: true, AdminRequired: true})
293
-
294
-	// ***** START: Admin *****
295
-	m.Group("/admin", func() {
296
-		m.Get("", adminReq, admin.Dashboard)
297
-		m.Get("/config", admin.Config)
298
-		m.Post("/config/test_mail", admin.SendTestMail)
299
-		m.Get("/monitor", admin.Monitor)
300
-
301
-		m.Group("/users", func() {
302
-			m.Get("", admin.Users)
303
-			m.Combo("/new").Get(admin.NewUser).Post(bindIgnErr(auth.AdminCreateUserForm{}), admin.NewUserPost)
304
-			m.Combo("/:userid").Get(admin.EditUser).Post(bindIgnErr(auth.AdminEditUserForm{}), admin.EditUserPost)
305
-			m.Post("/:userid/delete", admin.DeleteUser)
306
-		})
307
-
308
-		m.Group("/orgs", func() {
309
-			m.Get("", admin.Organizations)
310
-		})
311
-
312
-		m.Group("/repos", func() {
313
-			m.Get("", admin.Repos)
314
-			m.Post("/delete", admin.DeleteRepo)
315
-		})
316
-
317
-		m.Group("/auths", func() {
318
-			m.Get("", admin.Authentications)
319
-			m.Combo("/new").Get(admin.NewAuthSource).Post(bindIgnErr(auth.AuthenticationForm{}), admin.NewAuthSourcePost)
320
-			m.Combo("/:authid").Get(admin.EditAuthSource).
321
-				Post(bindIgnErr(auth.AuthenticationForm{}), admin.EditAuthSourcePost)
322
-			m.Post("/:authid/delete", admin.DeleteAuthSource)
323
-		})
324
-
325
-		m.Group("/notices", func() {
326
-			m.Get("", admin.Notices)
327
-			m.Post("/delete", admin.DeleteNotices)
328
-			m.Get("/empty", admin.EmptyNotices)
329
-		})
330
-	}, adminReq)
331
-	// ***** END: Admin *****
332
-
333
-	m.Group("", func() {
334
-		m.Group("/:username", func() {
335
-			m.Get("", user.Profile)
336
-			m.Get("/followers", user.Followers)
337
-			m.Get("/following", user.Following)
338
-		})
339
-
340
-		m.Get("/attachments/:uuid", func(ctx *context.Context) {
341
-			attach, err := models.GetAttachmentByUUID(ctx.Params(":uuid"))
342
-			if err != nil {
343
-				if models.IsErrAttachmentNotExist(err) {
344
-					ctx.Error(404)
345
-				} else {
346
-					ctx.Handle(500, "GetAttachmentByUUID", err)
347
-				}
348
-				return
349
-			}
350
-
351
-			fr, err := os.Open(attach.LocalPath())
352
-			if err != nil {
353
-				ctx.Handle(500, "Open", err)
354
-				return
355
-			}
356
-			defer fr.Close()
357
-
358
-			if err := attach.IncreaseDownloadCount(); err != nil {
359
-				ctx.Handle(500, "Update", err)
360
-				return
361
-			}
362
-
363
-			if err = repo.ServeData(ctx, attach.Name, fr); err != nil {
364
-				ctx.Handle(500, "ServeData", err)
365
-				return
366
-			}
367
-		})
368
-		m.Post("/attachments", repo.UploadAttachment)
369
-	}, ignSignIn)
370
-
371
-	m.Group("/:username", func() {
372
-		m.Get("/action/:action", user.Action)
373
-	}, reqSignIn)
374
-
375
-	if macaron.Env == macaron.DEV {
376
-		m.Get("/template/*", dev.TemplatePreview)
377
-	}
378
-
379
-	reqRepoAdmin := context.RequireRepoAdmin()
380
-	reqRepoWriter := context.RequireRepoWriter()
381
-
382
-	// ***** START: Organization *****
383
-	m.Group("/org", func() {
384
-		m.Group("", func() {
385
-			m.Get("/create", org.Create)
386
-			m.Post("/create", bindIgnErr(auth.CreateOrgForm{}), org.CreatePost)
387
-		}, func(ctx *context.Context) {
388
-			if !ctx.User.CanCreateOrganization() {
389
-				ctx.NotFound()
390
-			}
391
-		})
392
-
393
-		m.Group("/:org", func() {
394
-			m.Get("/dashboard", user.Dashboard)
395
-			m.Get("/^:type(issues|pulls)$", user.Issues)
396
-			m.Get("/members", org.Members)
397
-			m.Get("/members/action/:action", org.MembersAction)
398
-
399
-			m.Get("/teams", org.Teams)
400
-		}, context.OrgAssignment(true))
401
-
402
-		m.Group("/:org", func() {
403
-			m.Get("/teams/:team", org.TeamMembers)
404
-			m.Get("/teams/:team/repositories", org.TeamRepositories)
405
-			m.Route("/teams/:team/action/:action", "GET,POST", org.TeamsAction)
406
-			m.Route("/teams/:team/action/repo/:action", "GET,POST", org.TeamsRepoAction)
407
-		}, context.OrgAssignment(true, false, true))
408
-
409
-		m.Group("/:org", func() {
410
-			m.Get("/teams/new", org.NewTeam)
411
-			m.Post("/teams/new", bindIgnErr(auth.CreateTeamForm{}), org.NewTeamPost)
412
-			m.Get("/teams/:team/edit", org.EditTeam)
413
-			m.Post("/teams/:team/edit", bindIgnErr(auth.CreateTeamForm{}), org.EditTeamPost)
414
-			m.Post("/teams/:team/delete", org.DeleteTeam)
415
-
416
-			m.Group("/settings", func() {
417
-				m.Combo("").Get(org.Settings).
418
-					Post(bindIgnErr(auth.UpdateOrgSettingForm{}), org.SettingsPost)
419
-				m.Post("/avatar", binding.MultipartForm(auth.AvatarForm{}), org.SettingsAvatar)
420
-				m.Post("/avatar/delete", org.SettingsDeleteAvatar)
421
-
422
-				m.Group("/hooks", func() {
423
-					m.Get("", org.Webhooks)
424
-					m.Post("/delete", org.DeleteWebhook)
425
-					m.Get("/:type/new", repo.WebhooksNew)
426
-					m.Post("/gogs/new", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksNewPost)
427
-					m.Post("/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
428
-					m.Get("/:id", repo.WebHooksEdit)
429
-					m.Post("/gogs/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
430
-					m.Post("/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
431
-				})
432
-
433
-				m.Route("/delete", "GET,POST", org.SettingsDelete)
434
-			})
435
-
436
-			m.Route("/invitations/new", "GET,POST", org.Invitation)
437
-		}, context.OrgAssignment(true, true))
438
-	}, reqSignIn)
439
-	// ***** END: Organization *****
440
-
441
-	// ***** START: Repository *****
442
-	m.Group("/repo", func() {
443
-		m.Get("/create", repo.Create)
444
-		m.Post("/create", bindIgnErr(auth.CreateRepoForm{}), repo.CreatePost)
445
-		m.Get("/migrate", repo.Migrate)
446
-		m.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), repo.MigratePost)
447
-		m.Combo("/fork/:repoid").Get(repo.Fork).
448
-			Post(bindIgnErr(auth.CreateRepoForm{}), repo.ForkPost)
449
-	}, reqSignIn)
450
-
451
-	m.Group("/:username/:reponame", func() {
452
-		m.Group("/settings", func() {
453
-			m.Combo("").Get(repo.Settings).
454
-				Post(bindIgnErr(auth.RepoSettingForm{}), repo.SettingsPost)
455
-			m.Group("/collaboration", func() {
456
-				m.Combo("").Get(repo.Collaboration).Post(repo.CollaborationPost)
457
-				m.Post("/access_mode", repo.ChangeCollaborationAccessMode)
458
-				m.Post("/delete", repo.DeleteCollaboration)
459
-			})
460
-			m.Group("/branches", func() {
461
-				m.Combo("").Get(repo.ProtectedBranch).Post(repo.ProtectedBranchPost)
462
-				m.Post("/can_push", repo.ChangeProtectedBranch)
463
-				m.Post("/delete", repo.DeleteProtectedBranch)
464
-			}, repo.MustBeNotBare)
465
-
466
-			m.Group("/hooks", func() {
467
-				m.Get("", repo.Webhooks)
468
-				m.Post("/delete", repo.DeleteWebhook)
469
-				m.Get("/:type/new", repo.WebhooksNew)
470
-				m.Post("/gogs/new", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksNewPost)
471
-				m.Post("/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
472
-				m.Get("/:id", repo.WebHooksEdit)
473
-				m.Post("/:id/test", repo.TestWebhook)
474
-				m.Post("/gogs/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
475
-				m.Post("/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
476
-
477
-				m.Group("/git", func() {
478
-					m.Get("", repo.GitHooks)
479
-					m.Combo("/:name").Get(repo.GitHooksEdit).
480
-						Post(repo.GitHooksEditPost)
481
-				}, context.GitHookService())
482
-			})
483
-
484
-			m.Group("/keys", func() {
485
-				m.Combo("").Get(repo.DeployKeys).
486
-					Post(bindIgnErr(auth.AddSSHKeyForm{}), repo.DeployKeysPost)
487
-				m.Post("/delete", repo.DeleteDeployKey)
488
-			})
489
-
490
-		}, func(ctx *context.Context) {
491
-			ctx.Data["PageIsSettings"] = true
492
-		}, context.UnitTypes())
493
-	}, reqSignIn, context.RepoAssignment(), reqRepoAdmin, context.RepoRef())
494
-
495
-	m.Get("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), repo.Action)
496
-	m.Group("/:username/:reponame", func() {
497
-		// FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest.
498
-		// So they can apply their own enable/disable logic on routers.
499
-		m.Group("/issues", func() {
500
-			m.Combo("/new", repo.MustEnableIssues).Get(context.RepoRef(), repo.NewIssue).
501
-				Post(bindIgnErr(auth.CreateIssueForm{}), repo.NewIssuePost)
502
-
503
-			m.Group("/:index", func() {
504
-				m.Post("/title", repo.UpdateIssueTitle)
505
-				m.Post("/content", repo.UpdateIssueContent)
506
-				m.Post("/watch", repo.IssueWatch)
507
-				m.Combo("/comments").Post(bindIgnErr(auth.CreateCommentForm{}), repo.NewComment)
508
-			})
509
-
510
-			m.Post("/labels", repo.UpdateIssueLabel, reqRepoWriter)
511
-			m.Post("/milestone", repo.UpdateIssueMilestone, reqRepoWriter)
512
-			m.Post("/assignee", repo.UpdateIssueAssignee, reqRepoWriter)
513
-			m.Post("/status", repo.UpdateIssueStatus, reqRepoWriter)
514
-		})
515
-		m.Group("/comments/:id", func() {
516
-			m.Post("", repo.UpdateCommentContent)
517
-			m.Post("/delete", repo.DeleteComment)
518
-		})
519
-		m.Group("/labels", func() {
520
-			m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
521
-			m.Post("/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel)
522
-			m.Post("/delete", repo.DeleteLabel)
523
-			m.Post("/initialize", bindIgnErr(auth.InitializeLabelsForm{}), repo.InitializeLabels)
524
-		}, reqRepoWriter, context.RepoRef())
525
-		m.Group("/milestones", func() {
526
-			m.Combo("/new").Get(repo.NewMilestone).
527
-				Post(bindIgnErr(auth.CreateMilestoneForm{}), repo.NewMilestonePost)
528
-			m.Get("/:id/edit", repo.EditMilestone)
529
-			m.Post("/:id/edit", bindIgnErr(auth.CreateMilestoneForm{}), repo.EditMilestonePost)
530
-			m.Get("/:id/:action", repo.ChangeMilestonStatus)
531
-			m.Post("/delete", repo.DeleteMilestone)
532
-		}, reqRepoWriter, context.RepoRef())
533
-		m.Group("/releases", func() {
534
-			m.Get("/new", repo.NewRelease)
535
-			m.Post("/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost)
536
-			m.Post("/delete", repo.DeleteRelease)
537
-		}, repo.MustBeNotBare, reqRepoWriter, context.RepoRef())
538
-		m.Group("/releases", func() {
539
-			m.Get("/edit/*", repo.EditRelease)
540
-			m.Post("/edit/*", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost)
541
-		}, repo.MustBeNotBare, reqRepoWriter, func(ctx *context.Context) {
542
-			var err error
543
-			ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
544
-			if err != nil {
545
-				ctx.Handle(500, "GetBranchCommit", err)
546
-				return
547
-			}
548
-			ctx.Repo.CommitsCount, err = ctx.Repo.Commit.CommitsCount()
549
-			if err != nil {
550
-				ctx.Handle(500, "CommitsCount", err)
551
-				return
552
-			}
553
-			ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
554
-		})
555
-
556
-		m.Combo("/compare/*", repo.MustAllowPulls, repo.SetEditorconfigIfExists).
557
-			Get(repo.CompareAndPullRequest).
558
-			Post(bindIgnErr(auth.CreateIssueForm{}), repo.CompareAndPullRequestPost)
559
-
560
-		m.Group("", func() {
561
-			m.Combo("/_edit/*").Get(repo.EditFile).
562
-				Post(bindIgnErr(auth.EditRepoFileForm{}), repo.EditFilePost)
563
-			m.Combo("/_new/*").Get(repo.NewFile).
564
-				Post(bindIgnErr(auth.EditRepoFileForm{}), repo.NewFilePost)
565
-			m.Post("/_preview/*", bindIgnErr(auth.EditPreviewDiffForm{}), repo.DiffPreviewPost)
566
-			m.Combo("/_delete/*").Get(repo.DeleteFile).
567
-				Post(bindIgnErr(auth.DeleteRepoFileForm{}), repo.DeleteFilePost)
568
-
569
-			m.Group("", func() {
570
-				m.Combo("/_upload/*").Get(repo.UploadFile).
571
-					Post(bindIgnErr(auth.UploadRepoFileForm{}), repo.UploadFilePost)
572
-				m.Post("/upload-file", repo.UploadFileToServer)
573
-				m.Post("/upload-remove", bindIgnErr(auth.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer)
574
-			}, func(ctx *context.Context) {
575
-				if !setting.Repository.Upload.Enabled {
576
-					ctx.Handle(404, "", nil)
577
-					return
578
-				}
579
-			})
580
-		}, repo.MustBeNotBare, reqRepoWriter, context.RepoRef(), func(ctx *context.Context) {
581
-			if !ctx.Repo.Repository.CanEnableEditor() || ctx.Repo.IsViewCommit {
582
-				ctx.Handle(404, "", nil)
583
-				return
584
-			}
585
-		})
586
-	}, reqSignIn, context.RepoAssignment(), context.UnitTypes())
587
-
588
-	m.Group("/:username/:reponame", func() {
589
-		m.Group("", func() {
590
-			m.Get("/releases", repo.MustBeNotBare, repo.Releases)
591
-			m.Get("/^:type(issues|pulls)$", repo.RetrieveLabels, repo.Issues)
592
-			m.Get("/^:type(issues|pulls)$/:index", repo.ViewIssue)
593
-			m.Get("/labels/", repo.RetrieveLabels, repo.Labels)
594
-			m.Get("/milestones", repo.Milestones)
595
-		}, context.RepoRef())
596
-
597
-		// m.Get("/branches", repo.Branches)
598
-		m.Post("/branches/:name/delete", reqSignIn, reqRepoWriter, repo.MustBeNotBare, repo.DeleteBranchPost)
599
-
600
-		m.Group("/wiki", func() {
601
-			m.Get("/?:page", repo.Wiki)
602
-			m.Get("/_pages", repo.WikiPages)
603
-
604
-			m.Group("", func() {
605
-				m.Combo("/_new").Get(repo.NewWiki).
606
-					Post(bindIgnErr(auth.NewWikiForm{}), repo.NewWikiPost)
607
-				m.Combo("/:page/_edit").Get(repo.EditWiki).
608
-					Post(bindIgnErr(auth.NewWikiForm{}), repo.EditWikiPost)
609
-				m.Post("/:page/delete", repo.DeleteWikiPagePost)
610
-			}, reqSignIn, reqRepoWriter)
611
-		}, repo.MustEnableWiki, context.RepoRef())
612
-
613
-		m.Group("/wiki", func() {
614
-			m.Get("/raw/*", repo.WikiRaw)
615
-			m.Get("/*", repo.WikiRaw)
616
-		}, repo.MustEnableWiki)
617
-
618
-		m.Get("/archive/*", repo.MustBeNotBare, repo.Download)
619
-
620
-		m.Group("/pulls/:index", func() {
621
-			m.Get("/commits", context.RepoRef(), repo.ViewPullCommits)
622
-			m.Get("/files", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.ViewPullFiles)
623
-			m.Post("/merge", reqRepoWriter, repo.MergePullRequest)
624
-		}, repo.MustAllowPulls)
625
-
626
-		m.Group("", func() {
627
-			m.Get("/src/*", repo.SetEditorconfigIfExists, repo.Home)
628
-			m.Get("/raw/*", repo.SingleDownload)
629
-			m.Get("/commits/*", repo.RefCommits)
630
-			m.Get("/graph", repo.Graph)
631
-			m.Get("/commit/:sha([a-f0-9]{7,40})$", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.Diff)
632
-			m.Get("/forks", repo.Forks)
633
-		}, context.RepoRef())
634
-		m.Get("/commit/:sha([a-f0-9]{7,40})\\.:ext(patch|diff)", repo.MustBeNotBare, repo.RawDiff)
635
-
636
-		m.Get("/compare/:before([a-z0-9]{40})\\.\\.\\.:after([a-z0-9]{40})", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.MustBeNotBare, repo.CompareDiff)
637
-	}, ignSignIn, context.RepoAssignment(), context.UnitTypes())
638
-	m.Group("/:username/:reponame", func() {
639
-		m.Get("/stars", repo.Stars)
640
-		m.Get("/watchers", repo.Watchers)
641
-	}, ignSignIn, context.RepoAssignment(), context.RepoRef(), context.UnitTypes())
642
-
643
-	m.Group("/:username", func() {
644
-		m.Group("/:reponame", func() {
645
-			m.Get("", repo.SetEditorconfigIfExists, repo.Home)
646
-			m.Get("\\.git$", repo.SetEditorconfigIfExists, repo.Home)
647
-		}, ignSignIn, context.RepoAssignment(), context.RepoRef(), context.UnitTypes())
648
-
649
-		m.Group("/:reponame", func() {
650
-			m.Group("/info/lfs", func() {
651
-				m.Post("/objects/batch", lfs.BatchHandler)
652
-				m.Get("/objects/:oid/:filename", lfs.ObjectOidHandler)
653
-				m.Any("/objects/:oid", lfs.ObjectOidHandler)
654
-				m.Post("/objects", lfs.PostHandler)
655
-				m.Any("/*", func(ctx *context.Context) {
656
-					ctx.Handle(404, "", nil)
657
-				})
658
-			}, ignSignInAndCsrf)
659
-			m.Any("/*", ignSignInAndCsrf, repo.HTTP)
660
-			m.Head("/tasks/trigger", repo.TriggerTask)
661
-		})
662
-	})
663
-	// ***** END: Repository *****
664
-
665
-	m.Group("/notifications", func() {
666
-		m.Get("", user.Notifications)
667
-		m.Post("/status", user.NotificationStatusPost)
668
-	}, reqSignIn)
669
-
670
-	m.Group("/api", func() {
671
-		apiv1.RegisterRoutes(m)
672
-	}, ignSignIn)
673
-
674
-	m.Group("/api/internal", func() {
675
-		// package name internal is ideal but Golang is not allowed, so we use private as package name.
676
-		private.RegisterRoutes(m)
677
-	})
678
-
679
-	// robots.txt
680
-	m.Get("/robots.txt", func(ctx *context.Context) {
681
-		if setting.HasRobotsTxt {
682
-			ctx.ServeFileContent(path.Join(setting.CustomPath, "robots.txt"))
683
-		} else {
684
-			ctx.Error(404)
685
-		}
686
-	})
687
-
688
-	// Not found handler.
689
-	m.NotFound(routers.NotFound)
62
+	m := routes.NewMacaron()
63
+	routes.RegisterRoutes(m)
690 64
 
691 65
 	// Flag for port number in case first time run conflict.
692 66
 	if ctx.IsSet("port") {

+ 0 - 91
integrations/install_test.go

@@ -1,91 +0,0 @@
1
-// Copyright 2017 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 integration
6
-
7
-import (
8
-	"fmt"
9
-	"net/http"
10
-	"os"
11
-	"os/user"
12
-	"testing"
13
-	"time"
14
-
15
-	"code.gitea.io/gitea/integrations/internal/utils"
16
-)
17
-
18
-// The HTTP port listened by the Gitea server.
19
-const ServerHTTPPort = "3001"
20
-
21
-const _RetryLimit = 10
22
-
23
-func makeSimpleSettings(user, port string) map[string][]string {
24
-	return map[string][]string{
25
-		"db_type":        {"SQLite3"},
26
-		"db_host":        {"localhost"},
27
-		"db_path":        {"data/gitea.db"},
28
-		"app_name":       {"Gitea: Git with a cup of tea"},
29
-		"repo_root_path": {"repositories"},
30
-		"run_user":       {user},
31
-		"domain":         {"localhost"},
32
-		"ssh_port":       {"22"},
33
-		"http_port":      {port},
34
-		"app_url":        {"http://localhost:" + port},
35
-		"log_root_path":  {"log"},
36
-	}
37
-}
38
-
39
-func install(t *utils.T) error {
40
-	var r *http.Response
41
-	var err error
42
-
43
-	for i := 1; i <= _RetryLimit; i++ {
44
-
45
-		r, err = http.Get("http://:" + ServerHTTPPort + "/")
46
-		if err == nil {
47
-			fmt.Fprintln(os.Stderr)
48
-			break
49
-		}
50
-
51
-		// Give the server some amount of time to warm up.
52
-		time.Sleep(100 * time.Millisecond)
53
-		fmt.Fprint(os.Stderr, ".")
54
-	}
55
-
56
-	if err != nil {
57
-		return err
58
-	}
59
-
60
-	defer r.Body.Close()
61
-
62
-	_user, err := user.Current()
63
-	if err != nil {
64
-		return err
65
-	}
66
-
67
-	settings := makeSimpleSettings(_user.Username, ServerHTTPPort)
68
-	r, err = http.PostForm("http://:"+ServerHTTPPort+"/install", settings)
69
-	if err != nil {
70
-		return err
71
-	}
72
-	defer r.Body.Close()
73
-
74
-	if r.StatusCode != http.StatusOK {
75
-		return fmt.Errorf("'/install': %s", r.Status)
76
-	}
77
-	return nil
78
-}
79
-
80
-func TestInstall(t *testing.T) {
81
-	conf := utils.Config{
82
-		Program: "../gitea",
83
-		WorkDir: "",
84
-		Args:    []string{"web", "--port", ServerHTTPPort},
85
-		LogFile: os.Stderr,
86
-	}
87
-
88
-	if err := utils.New(t, &conf).RunTest(install); err != nil {
89
-		t.Fatal(err)
90
-	}
91
-}

+ 92 - 0
integrations/integration_test.go

@@ -0,0 +1,92 @@
1
+// Copyright 2017 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
+	"bytes"
9
+	"fmt"
10
+	"io"
11
+	"net/http"
12
+	"os"
13
+	"testing"
14
+
15
+	"code.gitea.io/gitea/models"
16
+	"code.gitea.io/gitea/modules/setting"
17
+	"code.gitea.io/gitea/routers"
18
+	"code.gitea.io/gitea/routers/routes"
19
+
20
+	"gopkg.in/macaron.v1"
21
+	"gopkg.in/testfixtures.v2"
22
+)
23
+
24
+var mac *macaron.Macaron
25
+
26
+func TestMain(m *testing.M) {
27
+	appIniPath := os.Getenv("GITEA_CONF")
28
+	if appIniPath == "" {
29
+		fmt.Println("Environment variable $GITEA_CONF not set")
30
+		os.Exit(1)
31
+	}
32
+	setting.CustomConf = appIniPath
33
+	routers.GlobalInit()
34
+	mac = routes.NewMacaron()
35
+	routes.RegisterRoutes(mac)
36
+
37
+	var helper testfixtures.Helper
38
+	if setting.UseMySQL {
39
+		helper = &testfixtures.MySQL{}
40
+	} else if setting.UsePostgreSQL {
41
+		helper = &testfixtures.PostgreSQL{}
42
+	} else if setting.UseSQLite3 {
43
+		helper = &testfixtures.SQLite{}
44
+	} else {
45
+		fmt.Println("Unsupported RDBMS for integration tests")
46
+		os.Exit(1)
47
+	}
48
+
49
+	err := models.InitFixtures(
50
+		helper,
51
+		"integrations/gitea-integration/fixtures/",
52
+	)
53
+	if err != nil {
54
+		fmt.Printf("Error initializing test database: %v\n", err)
55
+		os.Exit(1)
56
+	}
57
+	os.Exit(m.Run())
58
+}
59
+
60
+type TestResponseWriter struct {
61
+	HeaderCode int
62
+	Writer     io.Writer
63
+}
64
+
65
+func (w *TestResponseWriter) Header() http.Header {
66
+	return make(map[string][]string)
67
+}
68
+
69
+func (w *TestResponseWriter) Write(b []byte) (int, error) {
70
+	return w.Writer.Write(b)
71
+}
72
+
73
+func (w *TestResponseWriter) WriteHeader(n int) {
74
+	w.HeaderCode = n
75
+}
76
+
77
+type TestResponse struct {
78
+	HeaderCode int
79
+	Body       []byte
80
+}
81
+
82
+func MakeRequest(req *http.Request) *TestResponse {
83
+	buffer := bytes.NewBuffer(nil)
84
+	respWriter := &TestResponseWriter{
85
+		Writer: buffer,
86
+	}
87
+	mac.ServeHTTP(respWriter, req)
88
+	return &TestResponse{
89
+		HeaderCode: respWriter.HeaderCode,
90
+		Body:       buffer.Bytes(),
91
+	}
92
+}

+ 0 - 156
integrations/internal/utils/utils.go

@@ -1,156 +0,0 @@
1
-// Copyright 2017 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 utils
6
-
7
-import (
8
-	"errors"
9
-	"fmt"
10
-	"io"
11
-	"io/ioutil"
12
-	"log"
13
-	"net/http"
14
-	"os"
15
-	"os/exec"
16
-	"path/filepath"
17
-	"syscall"
18
-	"testing"
19
-)
20
-
21
-// T wraps testing.T and the configurations of the testing instance.
22
-type T struct {
23
-	*testing.T
24
-	Config *Config
25
-}
26
-
27
-// New create an instance of T
28
-func New(t *testing.T, c *Config) *T {
29
-	return &T{T: t, Config: c}
30
-}
31
-
32
-// Config Settings of the testing program
33
-type Config struct {
34
-	// The executable path of the tested program.
35
-	Program string
36
-	// Working directory prepared for the tested program.
37
-	// If empty, a directory named with random suffixes is picked, and created under the platform-dependent default temporary directory.
38
-	// The directory will be removed when the test finishes.
39
-	WorkDir string
40
-	// Command-line arguments passed to the tested program.
41
-	Args []string
42
-
43
-	// Where to redirect the stdout/stderr to. For debugging purposes.
44
-	LogFile *os.File
45
-}
46
-
47
-func redirect(cmd *exec.Cmd, f *os.File) error {
48
-	stdout, err := cmd.StdoutPipe()
49
-	if err != nil {
50
-		return err
51
-	}
52
-
53
-	stderr, err := cmd.StderrPipe()
54
-	if err != nil {
55
-		return err
56
-	}
57
-
58
-	go io.Copy(f, stdout)
59
-	go io.Copy(f, stderr)
60
-	return nil
61
-}
62
-
63
-// RunTest Helper function for setting up a running Gitea server for functional testing and then gracefully terminating it.
64
-func (t *T) RunTest(tests ...func(*T) error) (err error) {
65
-	if t.Config.Program == "" {
66
-		return errors.New("Need input file")
67
-	}
68
-
69
-	path, err := filepath.Abs(t.Config.Program)
70
-	if err != nil {
71
-		return err
72
-	}
73
-
74
-	workdir := t.Config.WorkDir
75
-	if workdir == "" {
76
-		workdir, err = ioutil.TempDir(os.TempDir(), "gitea_tests-")
77
-		if err != nil {
78
-			return err
79
-		}
80
-		defer os.RemoveAll(workdir)
81
-	}
82
-
83
-	newpath := filepath.Join(workdir, filepath.Base(path))
84
-	if err := os.Symlink(path, newpath); err != nil {
85
-		return err
86
-	}
87
-
88
-	log.Printf("Starting the server: %s args:%s workdir:%s", newpath, t.Config.Args, workdir)
89
-
90
-	cmd := exec.Command(newpath, t.Config.Args...)
91
-	cmd.Dir = workdir
92
-
93
-	if t.Config.LogFile != nil && testing.Verbose() {
94
-		if err := redirect(cmd, t.Config.LogFile); err != nil {
95
-			return err
96
-		}
97
-	}
98
-
99
-	if err := cmd.Start(); err != nil {
100
-		return err
101
-	}
102
-
103
-	log.Println("Server started.")
104
-
105
-	defer func() {
106
-		// Do not early return. We have to call Wait anyway.
107
-		_ = cmd.Process.Signal(syscall.SIGTERM)
108
-
109
-		if _err := cmd.Wait(); _err != nil {
110
-			if _err.Error() != "signal: terminated" {
111
-				err = _err
112
-				return
113
-			}
114
-		}
115
-
116
-		log.Println("Server exited")
117
-	}()
118
-
119
-	for _, fn := range tests {
120
-		if err := fn(t); err != nil {
121
-			return err
122
-		}
123
-	}
124
-
125
-	// Note that the return value 'err' may be updated by the 'defer' statement before despite it's returning nil here.
126
-	return nil
127
-}
128
-
129
-// GetAndPost provides a convenient helper function for testing an HTTP endpoint with GET and POST method.
130
-// The function sends GET first and then POST with the given form.
131
-func GetAndPost(url string, form map[string][]string) error {
132
-	var err error
133
-	var r *http.Response
134
-
135
-	r, err = http.Get(url)
136
-	if err != nil {
137
-		return err
138
-	}
139
-	defer r.Body.Close()
140
-
141
-	if r.StatusCode != http.StatusOK {
142
-		return fmt.Errorf("GET '%s': %s", url, r.Status)
143
-	}
144
-
145
-	r, err = http.PostForm(url, form)
146
-	if err != nil {
147
-		return err
148
-	}
149
-	defer r.Body.Close()
150
-
151
-	if r.StatusCode != http.StatusOK {
152
-		return fmt.Errorf("POST '%s': %s", url, r.Status)
153
-	}
154
-
155
-	return nil
156
-}

+ 56 - 0
integrations/mysql.ini

@@ -0,0 +1,56 @@
1
+APP_NAME = Gitea: Git with a cup of tea
2
+RUN_MODE = prod
3
+
4
+[database]
5
+DB_TYPE  = mysql
6
+HOST     = 127.0.0.1:3306
7
+NAME     = testgitea
8
+USER     = root
9
+PASSWD   =
10
+SSL_MODE = disable
11
+PATH     = data/gitea.db
12
+
13
+[repository]
14
+ROOT = integrations/gitea-integration/gitea-repositories
15
+
16
+[server]
17
+SSH_DOMAIN       = localhost
18
+HTTP_PORT        = 3000
19
+ROOT_URL         = http://localhost:3000/
20
+DISABLE_SSH      = false
21
+SSH_PORT         = 22
22
+LFS_START_SERVER = false
23
+OFFLINE_MODE     = false
24
+
25
+[mailer]
26
+ENABLED = false
27
+
28
+[service]
29
+REGISTER_EMAIL_CONFIRM     = false
30
+ENABLE_NOTIFY_MAIL         = false
31
+DISABLE_REGISTRATION       = false
32
+ENABLE_CAPTCHA             = false
33
+REQUIRE_SIGNIN_VIEW        = false
34
+DEFAULT_KEEP_EMAIL_PRIVATE = false
35
+NO_REPLY_ADDRESS           = noreply.example.org
36
+
37
+[picture]
38
+DISABLE_GRAVATAR        = false
39
+ENABLE_FEDERATED_AVATAR = false
40
+
41
+[session]
42
+PROVIDER = file
43
+
44
+[log]
45
+MODE = console,file
46
+
47
+[log.console]
48
+LEVEL = Warn
49
+
50
+[log.file]
51
+LEVEL     = Info
52
+ROOT_PATH = log
53
+
54
+[security]
55
+INSTALL_LOCK = true
56
+SECRET_KEY   = 9pCviYTWSb

+ 56 - 0
integrations/pgsql.ini

@@ -0,0 +1,56 @@
1
+APP_NAME = Gitea: Git with a cup of tea
2
+RUN_MODE = prod
3
+
4
+[database]
5
+DB_TYPE  = postgres
6
+HOST     = 127.0.0.1:5432
7
+NAME     = testgitea
8
+USER     = postgres
9
+PASSWD   = postgres
10
+SSL_MODE = disable
11
+PATH     = data/gitea.db
12
+
13
+[repository]
14
+ROOT = integrations/gitea-integration/gitea-repositories
15
+
16
+[server]
17
+SSH_DOMAIN       = localhost
18
+HTTP_PORT        = 3000
19
+ROOT_URL         = http://localhost:3000/
20
+DISABLE_SSH      = false
21
+SSH_PORT         = 22
22
+LFS_START_SERVER = false
23
+OFFLINE_MODE     = false
24
+
25
+[mailer]
26
+ENABLED = false
27
+
28
+[service]
29
+REGISTER_EMAIL_CONFIRM     = false
30
+ENABLE_NOTIFY_MAIL         = false
31
+DISABLE_REGISTRATION       = false
32
+ENABLE_CAPTCHA             = false
33
+REQUIRE_SIGNIN_VIEW        = false
34
+DEFAULT_KEEP_EMAIL_PRIVATE = false
35
+NO_REPLY_ADDRESS           = noreply.example.org
36
+
37
+[picture]
38
+DISABLE_GRAVATAR        = false
39
+ENABLE_FEDERATED_AVATAR = false
40
+
41
+[session]
42
+PROVIDER = file
43
+
44
+[log]
45
+MODE = console,file
46
+
47
+[log.console]
48
+LEVEL = Warn
49
+
50
+[log.file]
51
+LEVEL     = Info
52
+ROOT_PATH = log
53
+
54
+[security]
55
+INSTALL_LOCK = true
56
+SECRET_KEY   = 9pCviYTWSb

+ 28 - 22
integrations/signup_test.go

@@ -2,34 +2,40 @@
2 2
 // Use of this source code is governed by a MIT-style
3 3
 // license that can be found in the LICENSE file.
4 4
 
5
-package integration
5
+package integrations
6 6
 
7 7
 import (
8
-	"os"
8
+	"bytes"
9
+	"net/http"
10
+	"net/url"
9 11
 	"testing"
10 12
 
11
-	"code.gitea.io/gitea/integrations/internal/utils"
12
-)
13
-
14
-var signupFormSample map[string][]string = map[string][]string{
15
-	"Name":   {"tester"},
16
-	"Email":  {"user1@example.com"},
17
-	"Passwd": {"12345678"},
18
-}
13
+	"code.gitea.io/gitea/models"
14
+	"code.gitea.io/gitea/modules/setting"
19 15
 
20
-func signup(t *utils.T) error {
21
-	return utils.GetAndPost("http://:"+ServerHTTPPort+"/user/sign_up", signupFormSample)
22
-}
16
+	"github.com/stretchr/testify/assert"
17
+)
23 18
 
24 19
 func TestSignup(t *testing.T) {
25
-	conf := utils.Config{
26
-		Program: "../gitea",
27
-		WorkDir: "",
28
-		Args:    []string{"web", "--port", ServerHTTPPort},
29
-		LogFile: os.Stderr,
30
-	}
20
+	assert.NoError(t, models.LoadFixtures())
21
+	setting.Service.EnableCaptcha = false
22
+
23
+	req, err := http.NewRequest("POST", "/user/sign_up",
24
+		bytes.NewBufferString(url.Values{
25
+			"user_name": []string{"exampleUser"},
26
+			"email":     []string{"exampleUser@example.com"},
27
+			"password":  []string{"examplePassword"},
28
+			"retype":    []string{"examplePassword"},
29
+		}.Encode()),
30
+	)
31
+	assert.NoError(t, err)
32
+	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
33
+	resp := MakeRequest(req)
34
+	assert.EqualValues(t, http.StatusFound, resp.HeaderCode)
31 35
 
32
-	if err := utils.New(t, &conf).RunTest(install, signup); err != nil {
33
-		t.Fatal(err)
34
-	}
36
+	// should be able to view new user's page
37
+	req, err = http.NewRequest("GET", "/exampleUser", nil)
38
+	assert.NoError(t, err)
39
+	resp = MakeRequest(req)
40
+	assert.EqualValues(t, http.StatusOK, resp.HeaderCode)
35 41
 }

+ 58 - 0
integrations/sqlite.ini

@@ -0,0 +1,58 @@
1
+APP_NAME = Gitea: Git with a cup of tea
2
+RUN_MODE = prod
3
+
4
+[database]
5
+DB_TYPE  = sqlite3
6
+HOST     = 127.0.0.1:3306
7
+NAME     = testgitea
8
+USER     = gitea
9
+PASSWD   =
10
+SSL_MODE = disable
11
+PATH     = :memory:
12
+
13
+[repository]
14
+ROOT = integrations/gitea-integration/gitea-repositories
15
+
16
+[server]
17
+SSH_DOMAIN       = localhost
18
+HTTP_PORT        = 3000
19
+ROOT_URL         = http://localhost:3000/
20
+DISABLE_SSH      = false
21
+SSH_PORT         = 22
22
+LFS_START_SERVER = false
23
+OFFLINE_MODE     = false
24
+
25
+[mailer]
26
+ENABLED = false
27
+
28
+[service]
29
+REGISTER_EMAIL_CONFIRM     = false
30
+ENABLE_NOTIFY_MAIL         = false
31
+DISABLE_REGISTRATION       = false
32
+ENABLE_CAPTCHA             = false
33
+REQUIRE_SIGNIN_VIEW        = false
34
+DEFAULT_KEEP_EMAIL_PRIVATE = false
35
+NO_REPLY_ADDRESS           = noreply.example.org
36
+
37
+[picture]
38
+DISABLE_GRAVATAR        = false
39
+ENABLE_FEDERATED_AVATAR = false
40
+
41
+[session]
42
+PROVIDER = file
43
+
44
+[log]
45
+MODE = console,file
46
+
47
+[log.console]
48
+LEVEL = Warn
49
+
50
+[log.file]
51
+LEVEL     = Info
52
+ROOT_PATH = log
53
+
54
+[security]
55
+INSTALL_LOCK   = true
56
+SECRET_KEY     = 9pCviYTWSb
57
+INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0OTI3OTU5ODN9.OQkH5UmzID2XBdwQ9TAI6Jj2t1X-wElVTjbE7aoN4I8
58
+

+ 15 - 63
integrations/version_test.go

@@ -2,81 +2,33 @@
2 2
 // Use of this source code is governed by a MIT-style
3 3
 // license that can be found in the LICENSE file.
4 4
 
5
-package integration
5
+package integrations
6 6
 
7 7
 import (
8
+	"bytes"
8 9
 	"encoding/json"
9
-	"fmt"
10
-	"log"
11 10
 	"net/http"
12
-	"os"
13
-	"os/exec"
14
-	"path/filepath"
15
-	"strings"
16 11
 	"testing"
17 12
 
18
-	"code.gitea.io/gitea/integrations/internal/utils"
13
+	"code.gitea.io/gitea/models"
14
+	"code.gitea.io/gitea/modules/setting"
19 15
 	"code.gitea.io/sdk/gitea"
20 16
 
21 17
 	"github.com/stretchr/testify/assert"
22 18
 )
23 19
 
24
-func version(t *utils.T) error {
25
-	var err error
26
-
27
-	path, err := filepath.Abs(t.Config.Program)
28
-	if err != nil {
29
-		return err
30
-	}
31
-
32
-	cmd := exec.Command(path, "--version")
33
-	out, err := cmd.Output()
34
-	if err != nil {
35
-		return err
36
-	}
37
-
38
-	fields := strings.Fields(string(out))
39
-	if !strings.HasPrefix(string(out), "Gitea version") {
40
-		return fmt.Errorf("unexpected version string '%s' of the gitea executable", out)
41
-	}
42
-
43
-	expected := fields[2]
44
-
45
-	var r *http.Response
46
-	r, err = http.Get("http://:" + ServerHTTPPort + "/api/v1/version")
47
-	if err != nil {
48
-		return err
49
-	}
50
-	defer r.Body.Close()
51
-
52
-	if r.StatusCode != http.StatusOK {
53
-		return fmt.Errorf("'/api/v1/version': %s\n", r.Status)
54
-	}
55
-
56
-	var v gitea.ServerVersion
57
-
58
-	dec := json.NewDecoder(r.Body)
59
-	if err := dec.Decode(&v); err != nil {
60
-		return err
61
-	}
62
-
63
-	actual := v.Version
64
-
65
-	log.Printf("Actual: \"%s\" Expected: \"%s\"\n", actual, expected)
66
-	assert.Equal(t, expected, actual)
20
+func TestVersion(t *testing.T) {
21
+	assert.NoError(t, models.LoadFixtures())
67 22
 
68
-	return nil
69
-}
23
+	setting.AppVer = "1.1.0+dev"
24
+	req, err := http.NewRequest("GET", "/api/v1/version", nil)
25
+	assert.NoError(t, err)
26
+	resp := MakeRequest(req)
70 27
 
71
-func TestVersion(t *testing.T) {
72
-	conf := utils.Config{
73
-		Program: "../gitea",
74
-		WorkDir: "",
75
-		Args:    []string{"web", "--port", ServerHTTPPort},
76
-		LogFile: os.Stderr,
77
-	}
28
+	var version gitea.ServerVersion
29
+	decoder := json.NewDecoder(bytes.NewBuffer(resp.Body))
30
+	assert.NoError(t, decoder.Decode(&version))
78 31
 
79
-	if err := utils.New(t, &conf).RunTest(install, version); err != nil {
80
-		t.Fatal(err)
81
-	}
32
+	assert.EqualValues(t, http.StatusOK, resp.HeaderCode)
33
+	assert.Equal(t, setting.AppVer, string(version.Version))
82 34
 }

+ 32 - 0
integrations/view_test.go

@@ -0,0 +1,32 @@
1
+// Copyright 2017 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
+	"net/http"
9
+	"testing"
10
+
11
+	"code.gitea.io/gitea/models"
12
+
13
+	"github.com/stretchr/testify/assert"
14
+)
15
+
16
+func TestViewRepo(t *testing.T) {
17
+	assert.NoError(t, models.LoadFixtures())
18
+
19
+	req, err := http.NewRequest("GET", "/user1/repo1", nil)
20
+	assert.NoError(t, err)
21
+	resp := MakeRequest(req)
22
+	assert.EqualValues(t, http.StatusOK, resp.HeaderCode)
23
+}
24
+
25
+func TestViewUser(t *testing.T) {
26
+	assert.NoError(t, models.LoadFixtures())
27
+
28
+	req, err := http.NewRequest("GET", "/user1", nil)
29
+	assert.NoError(t, err)
30
+	resp := MakeRequest(req)
31
+	assert.EqualValues(t, http.StatusOK, resp.HeaderCode)
32
+}

+ 2 - 6
models/setup_for_test.go

@@ -37,11 +37,8 @@ func TestMain(m *testing.M) {
37 37
 	os.Exit(m.Run())
38 38
 }
39 39
 
40
-var fixtures *testfixtures.Context
41
-
42 40
 // CreateTestEngine create an xorm engine for testing
43 41
 func CreateTestEngine() error {
44
-	testfixtures.SkipDatabaseNameCheck(true)
45 42
 	var err error
46 43
 	x, err = xorm.NewEngine("sqlite3", "file::memory:?cache=shared")
47 44
 	if err != nil {
@@ -52,8 +49,7 @@ func CreateTestEngine() error {
52 49
 		return err
53 50
 	}
54 51
 
55
-	fixtures, err = testfixtures.NewFolder(x.DB().DB, &testfixtures.SQLite{}, "fixtures/")
56
-	return err
52
+	return InitFixtures(&testfixtures.SQLite{}, "fixtures/")
57 53
 }
58 54
 
59 55
 func TestFixturesAreConsistent(t *testing.T) {
@@ -63,7 +59,7 @@ func TestFixturesAreConsistent(t *testing.T) {
63 59
 
64 60
 // PrepareTestDatabase load test fixtures into test database
65 61
 func PrepareTestDatabase() error {
66
-	return fixtures.Load()
62
+	return LoadFixtures()
67 63
 }
68 64
 
69 65
 func loadBeanIfExists(bean interface{}, conditions ...interface{}) (bool, error) {

+ 23 - 0
models/test_fixtures.go

@@ -0,0 +1,23 @@
1
+// Copyright 2017 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 models
6
+
7
+import (
8
+	"gopkg.in/testfixtures.v2"
9
+)
10
+
11
+var fixtures *testfixtures.Context
12
+
13
+// InitFixtures initialize test fixtures for a test database
14
+func InitFixtures(helper testfixtures.Helper, dir string) (err error) {
15
+	testfixtures.SkipDatabaseNameCheck(true)
16
+	fixtures, err = testfixtures.NewFolder(x.DB().DB, helper, dir)
17
+	return err
18
+}
19
+
20
+// LoadFixtures load fixtures for a test database
21
+func LoadFixtures() error {
22
+	return fixtures.Load()
23
+}

+ 645 - 0
routers/routes/routes.go

@@ -0,0 +1,645 @@
1
+// Copyright 2017 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 routes
6
+
7
+import (
8
+	"os"
9
+	"path"
10
+
11
+	"code.gitea.io/gitea/models"
12
+	"code.gitea.io/gitea/modules/auth"
13
+	"code.gitea.io/gitea/modules/context"
14
+	"code.gitea.io/gitea/modules/lfs"
15
+	"code.gitea.io/gitea/modules/log"
16
+	"code.gitea.io/gitea/modules/options"
17
+	"code.gitea.io/gitea/modules/public"
18
+	"code.gitea.io/gitea/modules/setting"
19
+	"code.gitea.io/gitea/modules/templates"
20
+	"code.gitea.io/gitea/modules/validation"
21
+	"code.gitea.io/gitea/routers"
22
+	"code.gitea.io/gitea/routers/admin"
23
+	apiv1 "code.gitea.io/gitea/routers/api/v1"
24
+	"code.gitea.io/gitea/routers/dev"
25
+	"code.gitea.io/gitea/routers/org"
26
+	"code.gitea.io/gitea/routers/private"
27
+	"code.gitea.io/gitea/routers/repo"
28
+	"code.gitea.io/gitea/routers/user"
29
+
30
+	"github.com/go-macaron/binding"
31
+	"github.com/go-macaron/cache"
32
+	"github.com/go-macaron/captcha"
33
+	"github.com/go-macaron/csrf"
34
+	"github.com/go-macaron/gzip"
35
+	"github.com/go-macaron/i18n"
36
+	"github.com/go-macaron/session"
37
+	"github.com/go-macaron/toolbox"
38
+	"gopkg.in/macaron.v1"
39
+)
40
+
41
+// NewMacaron initializes Macaron instance.
42
+func NewMacaron() *macaron.Macaron {
43
+	m := macaron.New()
44
+	if !setting.DisableRouterLog {
45
+		m.Use(macaron.Logger())
46
+	}
47
+	m.Use(macaron.Recovery())
48
+	if setting.EnableGzip {
49
+		m.Use(gzip.Gziper())
50
+	}
51
+	if setting.Protocol == setting.FCGI {
52
+		m.SetURLPrefix(setting.AppSubURL)
53
+	}
54
+	m.Use(public.Custom(
55
+		&public.Options{
56
+			SkipLogging: setting.DisableRouterLog,
57
+		},
58
+	))
59
+	m.Use(public.Static(
60
+		&public.Options{
61
+			Directory:   path.Join(setting.StaticRootPath, "public"),
62
+			SkipLogging: setting.DisableRouterLog,
63
+		},
64
+	))
65
+	m.Use(macaron.Static(
66
+		setting.AvatarUploadPath,
67
+		macaron.StaticOptions{
68
+			Prefix:      "avatars",
69
+			SkipLogging: setting.DisableRouterLog,
70
+			ETag:        true,
71
+		},
72
+	))
73
+
74
+	m.Use(templates.Renderer())
75
+	models.InitMailRender(templates.Mailer())
76
+
77
+	localeNames, err := options.Dir("locale")
78
+
79
+	if err != nil {
80
+		log.Fatal(4, "Failed to list locale files: %v", err)
81
+	}
82
+
83
+	localFiles := make(map[string][]byte)
84
+
85
+	for _, name := range localeNames {
86
+		localFiles[name], err = options.Locale(name)
87
+
88
+		if err != nil {
89
+			log.Fatal(4, "Failed to load %s locale file. %v", name, err)
90
+		}
91
+	}
92
+
93
+	m.Use(i18n.I18n(i18n.Options{
94
+		SubURL:      setting.AppSubURL,
95
+		Files:       localFiles,
96
+		Langs:       setting.Langs,
97
+		Names:       setting.Names,
98
+		DefaultLang: "en-US",
99
+		Redirect:    true,
100
+	}))
101
+	m.Use(cache.Cacher(cache.Options{
102
+		Adapter:       setting.CacheAdapter,
103
+		AdapterConfig: setting.CacheConn,
104
+		Interval:      setting.CacheInterval,
105
+	}))
106
+	m.Use(captcha.Captchaer(captcha.Options{
107
+		SubURL: setting.AppSubURL,
108
+	}))
109
+	m.Use(session.Sessioner(setting.SessionConfig))
110
+	m.Use(csrf.Csrfer(csrf.Options{
111
+		Secret:     setting.SecretKey,
112
+		Cookie:     setting.CSRFCookieName,
113
+		SetCookie:  true,
114
+		Header:     "X-Csrf-Token",
115
+		CookiePath: setting.AppSubURL,
116
+	}))
117
+	m.Use(toolbox.Toolboxer(m, toolbox.Options{
118
+		HealthCheckFuncs: []*toolbox.HealthCheckFuncDesc{
119
+			{
120
+				Desc: "Database connection",
121
+				Func: models.Ping,
122
+			},
123
+		},
124
+	}))
125
+	m.Use(context.Contexter())
126
+	return m
127
+}
128
+
129
+// RegisterRoutes routes routes to Macaron
130
+func RegisterRoutes(m *macaron.Macaron) {
131
+	reqSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: true})
132
+	ignSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: setting.Service.RequireSignInView})
133
+	ignSignInAndCsrf := context.Toggle(&context.ToggleOptions{DisableCSRF: true})
134
+	reqSignOut := context.Toggle(&context.ToggleOptions{SignOutRequired: true})
135
+
136
+	bindIgnErr := binding.BindIgnErr
137
+	validation.AddBindingRules()
138
+
139
+	m.Use(user.GetNotificationCount)
140
+
141
+	// FIXME: not all routes need go through same middlewares.
142
+	// Especially some AJAX requests, we can reduce middleware number to improve performance.
143
+	// Routers.
144
+	// for health check
145
+	m.Head("/", func() string {
146
+		return ""
147
+	})
148
+	m.Get("/", ignSignIn, routers.Home)
149
+	m.Group("/explore", func() {
150
+		m.Get("", func(ctx *context.Context) {
151
+			ctx.Redirect(setting.AppSubURL + "/explore/repos")
152
+		})
153
+		m.Get("/repos", routers.ExploreRepos)
154
+		m.Get("/users", routers.ExploreUsers)
155
+		m.Get("/organizations", routers.ExploreOrganizations)
156
+	}, ignSignIn)
157
+	m.Combo("/install", routers.InstallInit).Get(routers.Install).
158
+		Post(bindIgnErr(auth.InstallForm{}), routers.InstallPost)
159
+	m.Get("/^:type(issues|pulls)$", reqSignIn, user.Issues)
160
+
161
+	// ***** START: User *****
162
+	m.Group("/user", func() {
163
+		m.Get("/login", user.SignIn)
164
+		m.Post("/login", bindIgnErr(auth.SignInForm{}), user.SignInPost)
165
+		if setting.Service.EnableOpenIDSignIn {
166
+			m.Combo("/login/openid").
167
+				Get(user.SignInOpenID).
168
+				Post(bindIgnErr(auth.SignInOpenIDForm{}), user.SignInOpenIDPost)
169
+			m.Group("/openid", func() {
170
+				m.Combo("/connect").
171
+					Get(user.ConnectOpenID).
172
+					Post(bindIgnErr(auth.ConnectOpenIDForm{}), user.ConnectOpenIDPost)
173
+				m.Combo("/routes").
174
+					Get(user.RegisterOpenID).
175
+					Post(bindIgnErr(auth.SignUpOpenIDForm{}), user.RegisterOpenIDPost)
176
+			})
177
+		}
178
+		m.Get("/sign_up", user.SignUp)
179
+		m.Post("/sign_up", bindIgnErr(auth.RegisterForm{}), user.SignUpPost)
180
+		m.Get("/reset_password", user.ResetPasswd)
181
+		m.Post("/reset_password", user.ResetPasswdPost)
182
+		m.Group("/oauth2", func() {
183
+			m.Get("/:provider", user.SignInOAuth)
184
+			m.Get("/:provider/callback", user.SignInOAuthCallback)
185
+		})
186
+		m.Get("/link_account", user.LinkAccount)
187
+		m.Post("/link_account_signin", bindIgnErr(auth.SignInForm{}), user.LinkAccountPostSignIn)
188
+		m.Post("/link_account_signup", bindIgnErr(auth.RegisterForm{}), user.LinkAccountPostRegister)
189
+		m.Group("/two_factor", func() {
190
+			m.Get("", user.TwoFactor)
191
+			m.Post("", bindIgnErr(auth.TwoFactorAuthForm{}), user.TwoFactorPost)
192
+			m.Get("/scratch", user.TwoFactorScratch)
193
+			m.Post("/scratch", bindIgnErr(auth.TwoFactorScratchAuthForm{}), user.TwoFactorScratchPost)
194
+		})
195
+	}, reqSignOut)
196
+
197
+	m.Group("/user/settings", func() {
198
+		m.Get("", user.Settings)
199
+		m.Post("", bindIgnErr(auth.UpdateProfileForm{}), user.SettingsPost)
200
+		m.Combo("/avatar").Get(user.SettingsAvatar).
201
+			Post(binding.MultipartForm(auth.AvatarForm{}), user.SettingsAvatarPost)
202
+		m.Post("/avatar/delete", user.SettingsDeleteAvatar)
203
+		m.Combo("/email").Get(user.SettingsEmails).
204
+			Post(bindIgnErr(auth.AddEmailForm{}), user.SettingsEmailPost)
205
+		m.Post("/email/delete", user.DeleteEmail)
206
+		m.Get("/password", user.SettingsPassword)
207
+		m.Post("/password", bindIgnErr(auth.ChangePasswordForm{}), user.SettingsPasswordPost)
208
+		if setting.Service.EnableOpenIDSignIn {
209
+			m.Group("/openid", func() {
210
+				m.Combo("").Get(user.SettingsOpenID).
211
+					Post(bindIgnErr(auth.AddOpenIDForm{}), user.SettingsOpenIDPost)
212
+				m.Post("/delete", user.DeleteOpenID)
213
+				m.Post("/toggle_visibility", user.ToggleOpenIDVisibility)
214
+			})
215
+		}
216
+
217
+		m.Combo("/ssh").Get(user.SettingsSSHKeys).
218
+			Post(bindIgnErr(auth.AddSSHKeyForm{}), user.SettingsSSHKeysPost)
219
+		m.Post("/ssh/delete", user.DeleteSSHKey)
220
+		m.Combo("/applications").Get(user.SettingsApplications).
221
+			Post(bindIgnErr(auth.NewAccessTokenForm{}), user.SettingsApplicationsPost)
222
+		m.Post("/applications/delete", user.SettingsDeleteApplication)
223
+		m.Route("/delete", "GET,POST", user.SettingsDelete)
224
+		m.Combo("/account_link").Get(user.SettingsAccountLinks).Post(user.SettingsDeleteAccountLink)
225
+		m.Group("/two_factor", func() {
226
+			m.Get("", user.SettingsTwoFactor)
227
+			m.Post("/regenerate_scratch", user.SettingsTwoFactorRegenerateScratch)
228
+			m.Post("/disable", user.SettingsTwoFactorDisable)
229
+			m.Get("/enroll", user.SettingsTwoFactorEnroll)
230
+			m.Post("/enroll", bindIgnErr(auth.TwoFactorAuthForm{}), user.SettingsTwoFactorEnrollPost)
231
+		})
232
+	}, reqSignIn, func(ctx *context.Context) {
233
+		ctx.Data["PageIsUserSettings"] = true
234
+	})
235
+
236
+	m.Group("/user", func() {
237
+		// r.Get("/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds)
238
+		m.Any("/activate", user.Activate)
239
+		m.Any("/activate_email", user.ActivateEmail)
240
+		m.Get("/email2user", user.Email2User)
241
+		m.Get("/forgot_password", user.ForgotPasswd)
242
+		m.Post("/forgot_password", user.ForgotPasswdPost)
243
+		m.Get("/logout", user.SignOut)
244
+	})
245
+	// ***** END: User *****
246
+
247
+	adminReq := context.Toggle(&context.ToggleOptions{SignInRequired: true, AdminRequired: true})
248
+
249
+	// ***** START: Admin *****
250
+	m.Group("/admin", func() {
251
+		m.Get("", adminReq, admin.Dashboard)
252
+		m.Get("/config", admin.Config)
253
+		m.Post("/config/test_mail", admin.SendTestMail)
254
+		m.Get("/monitor", admin.Monitor)
255
+
256
+		m.Group("/users", func() {
257
+			m.Get("", admin.Users)
258
+			m.Combo("/new").Get(admin.NewUser).Post(bindIgnErr(auth.AdminCreateUserForm{}), admin.NewUserPost)
259
+			m.Combo("/:userid").Get(admin.EditUser).Post(bindIgnErr(auth.AdminEditUserForm{}), admin.EditUserPost)
260
+			m.Post("/:userid/delete", admin.DeleteUser)
261
+		})
262
+
263
+		m.Group("/orgs", func() {
264
+			m.Get("", admin.Organizations)
265
+		})
266
+
267
+		m.Group("/repos", func() {
268
+			m.Get("", admin.Repos)
269
+			m.Post("/delete", admin.DeleteRepo)
270
+		})
271
+
272
+		m.Group("/auths", func() {
273
+			m.Get("", admin.Authentications)
274
+			m.Combo("/new").Get(admin.NewAuthSource).Post(bindIgnErr(auth.AuthenticationForm{}), admin.NewAuthSourcePost)
275
+			m.Combo("/:authid").Get(admin.EditAuthSource).
276
+				Post(bindIgnErr(auth.AuthenticationForm{}), admin.EditAuthSourcePost)
277
+			m.Post("/:authid/delete", admin.DeleteAuthSource)
278
+		})
279
+
280
+		m.Group("/notices", func() {
281
+			m.Get("", admin.Notices)
282
+			m.Post("/delete", admin.DeleteNotices)
283
+			m.Get("/empty", admin.EmptyNotices)
284
+		})
285
+	}, adminReq)
286
+	// ***** END: Admin *****
287
+
288
+	m.Group("", func() {
289
+		m.Group("/:username", func() {
290
+			m.Get("", user.Profile)
291
+			m.Get("/followers", user.Followers)
292
+			m.Get("/following", user.Following)
293
+		})
294
+
295
+		m.Get("/attachments/:uuid", func(ctx *context.Context) {
296
+			attach, err := models.GetAttachmentByUUID(ctx.Params(":uuid"))
297
+			if err != nil {
298
+				if models.IsErrAttachmentNotExist(err) {
299
+					ctx.Error(404)
300
+				} else {
301
+					ctx.Handle(500, "GetAttachmentByUUID", err)
302
+				}
303
+				return
304
+			}
305
+
306
+			fr, err := os.Open(attach.LocalPath())
307
+			if err != nil {
308
+				ctx.Handle(500, "Open", err)
309
+				return
310
+			}
311
+			defer fr.Close()
312
+
313
+			if err := attach.IncreaseDownloadCount(); err != nil {
314
+				ctx.Handle(500, "Update", err)
315
+				return
316
+			}
317
+
318
+			if err = repo.ServeData(ctx, attach.Name, fr); err != nil {
319
+				ctx.Handle(500, "ServeData", err)
320
+				return
321
+			}
322
+		})
323
+		m.Post("/attachments", repo.UploadAttachment)
324
+	}, ignSignIn)
325
+
326
+	m.Group("/:username", func() {
327
+		m.Get("/action/:action", user.Action)
328
+	}, reqSignIn)
329
+
330
+	if macaron.Env == macaron.DEV {
331
+		m.Get("/template/*", dev.TemplatePreview)
332
+	}
333
+
334
+	reqRepoAdmin := context.RequireRepoAdmin()
335
+	reqRepoWriter := context.RequireRepoWriter()
336
+
337
+	// ***** START: Organization *****
338
+	m.Group("/org", func() {
339
+		m.Group("", func() {
340
+			m.Get("/create", org.Create)
341
+			m.Post("/create", bindIgnErr(auth.CreateOrgForm{}), org.CreatePost)
342
+		}, func(ctx *context.Context) {
343
+			if !ctx.User.CanCreateOrganization() {
344
+				ctx.NotFound()
345
+			}
346
+		})
347
+
348
+		m.Group("/:org", func() {
349
+			m.Get("/dashboard", user.Dashboard)
350
+			m.Get("/^:type(issues|pulls)$", user.Issues)
351
+			m.Get("/members", org.Members)
352
+			m.Get("/members/action/:action", org.MembersAction)
353
+
354
+			m.Get("/teams", org.Teams)
355
+		}, context.OrgAssignment(true))
356
+
357
+		m.Group("/:org", func() {
358
+			m.Get("/teams/:team", org.TeamMembers)
359
+			m.Get("/teams/:team/repositories", org.TeamRepositories)
360
+			m.Route("/teams/:team/action/:action", "GET,POST", org.TeamsAction)
361
+			m.Route("/teams/:team/action/repo/:action", "GET,POST", org.TeamsRepoAction)
362
+		}, context.OrgAssignment(true, false, true))
363
+
364
+		m.Group("/:org", func() {
365
+			m.Get("/teams/new", org.NewTeam)
366
+			m.Post("/teams/new", bindIgnErr(auth.CreateTeamForm{}), org.NewTeamPost)
367
+			m.Get("/teams/:team/edit", org.EditTeam)
368
+			m.Post("/teams/:team/edit", bindIgnErr(auth.CreateTeamForm{}), org.EditTeamPost)
369
+			m.Post("/teams/:team/delete", org.DeleteTeam)
370
+
371
+			m.Group("/settings", func() {
372
+				m.Combo("").Get(org.Settings).
373
+					Post(bindIgnErr(auth.UpdateOrgSettingForm{}), org.SettingsPost)
374
+				m.Post("/avatar", binding.MultipartForm(auth.AvatarForm{}), org.SettingsAvatar)
375
+				m.Post("/avatar/delete", org.SettingsDeleteAvatar)
376
+
377
+				m.Group("/hooks", func() {
378
+					m.Get("", org.Webhooks)
379
+					m.Post("/delete", org.DeleteWebhook)
380
+					m.Get("/:type/new", repo.WebhooksNew)
381
+					m.Post("/gogs/new", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksNewPost)
382
+					m.Post("/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
383
+					m.Get("/:id", repo.WebHooksEdit)
384
+					m.Post("/gogs/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
385
+					m.Post("/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
386
+				})
387
+
388
+				m.Route("/delete", "GET,POST", org.SettingsDelete)
389
+			})
390
+
391
+			m.Route("/invitations/new", "GET,POST", org.Invitation)
392
+		}, context.OrgAssignment(true, true))
393
+	}, reqSignIn)
394
+	// ***** END: Organization *****
395
+
396
+	// ***** START: Repository *****
397
+	m.Group("/repo", func() {
398
+		m.Get("/create", repo.Create)
399
+		m.Post("/create", bindIgnErr(auth.CreateRepoForm{}), repo.CreatePost)
400
+		m.Get("/migrate", repo.Migrate)
401
+		m.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), repo.MigratePost)
402
+		m.Combo("/fork/:repoid").Get(repo.Fork).
403
+			Post(bindIgnErr(auth.CreateRepoForm{}), repo.ForkPost)
404
+	}, reqSignIn)
405
+
406
+	m.Group("/:username/:reponame", func() {
407
+		m.Group("/settings", func() {
408
+			m.Combo("").Get(repo.Settings).
409
+				Post(bindIgnErr(auth.RepoSettingForm{}), repo.SettingsPost)
410
+			m.Group("/collaboration", func() {
411
+				m.Combo("").Get(repo.Collaboration).Post(repo.CollaborationPost)
412
+				m.Post("/access_mode", repo.ChangeCollaborationAccessMode)
413
+				m.Post("/delete", repo.DeleteCollaboration)
414
+			})
415
+			m.Group("/branches", func() {
416
+				m.Combo("").Get(repo.ProtectedBranch).Post(repo.ProtectedBranchPost)
417
+				m.Post("/can_push", repo.ChangeProtectedBranch)
418
+				m.Post("/delete", repo.DeleteProtectedBranch)
419
+			}, repo.MustBeNotBare)
420
+
421
+			m.Group("/hooks", func() {
422
+				m.Get("", repo.Webhooks)
423
+				m.Post("/delete", repo.DeleteWebhook)
424
+				m.Get("/:type/new", repo.WebhooksNew)
425
+				m.Post("/gogs/new", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksNewPost)
426
+				m.Post("/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
427
+				m.Get("/:id", repo.WebHooksEdit)
428
+				m.Post("/:id/test", repo.TestWebhook)
429
+				m.Post("/gogs/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
430
+				m.Post("/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
431
+
432
+				m.Group("/git", func() {
433
+					m.Get("", repo.GitHooks)
434
+					m.Combo("/:name").Get(repo.GitHooksEdit).
435
+						Post(repo.GitHooksEditPost)
436
+				}, context.GitHookService())
437
+			})
438
+
439
+			m.Group("/keys", func() {
440
+				m.Combo("").Get(repo.DeployKeys).
441
+					Post(bindIgnErr(auth.AddSSHKeyForm{}), repo.DeployKeysPost)
442
+				m.Post("/delete", repo.DeleteDeployKey)
443
+			})
444
+
445
+		}, func(ctx *context.Context) {
446
+			ctx.Data["PageIsSettings"] = true
447
+		}, context.UnitTypes())
448
+	}, reqSignIn, context.RepoAssignment(), reqRepoAdmin, context.RepoRef())
449
+
450
+	m.Get("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), repo.Action)
451
+	m.Group("/:username/:reponame", func() {
452
+		// FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest.
453
+		// So they can apply their own enable/disable logic on routers.
454
+		m.Group("/issues", func() {
455
+			m.Combo("/new", repo.MustEnableIssues).Get(context.RepoRef(), repo.NewIssue).
456
+				Post(bindIgnErr(auth.CreateIssueForm{}), repo.NewIssuePost)
457
+
458
+			m.Group("/:index", func() {
459
+				m.Post("/title", repo.UpdateIssueTitle)
460
+				m.Post("/content", repo.UpdateIssueContent)
461
+				m.Post("/watch", repo.IssueWatch)
462
+				m.Combo("/comments").Post(bindIgnErr(auth.CreateCommentForm{}), repo.NewComment)
463
+			})
464
+
465
+			m.Post("/labels", repo.UpdateIssueLabel, reqRepoWriter)
466
+			m.Post("/milestone", repo.UpdateIssueMilestone, reqRepoWriter)
467
+			m.Post("/assignee", repo.UpdateIssueAssignee, reqRepoWriter)
468
+			m.Post("/status", repo.UpdateIssueStatus, reqRepoWriter)
469
+		})
470
+		m.Group("/comments/:id", func() {
471
+			m.Post("", repo.UpdateCommentContent)
472
+			m.Post("/delete", repo.DeleteComment)
473
+		})
474
+		m.Group("/labels", func() {
475
+			m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
476
+			m.Post("/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel)
477
+			m.Post("/delete", repo.DeleteLabel)
478
+			m.Post("/initialize", bindIgnErr(auth.InitializeLabelsForm{}), repo.InitializeLabels)
479
+		}, reqRepoWriter, context.RepoRef())
480
+		m.Group("/milestones", func() {
481
+			m.Combo("/new").Get(repo.NewMilestone).
482
+				Post(bindIgnErr(auth.CreateMilestoneForm{}), repo.NewMilestonePost)
483
+			m.Get("/:id/edit", repo.EditMilestone)
484
+			m.Post("/:id/edit", bindIgnErr(auth.CreateMilestoneForm{}), repo.EditMilestonePost)
485
+			m.Get("/:id/:action", repo.ChangeMilestonStatus)
486
+			m.Post("/delete", repo.DeleteMilestone)
487
+		}, reqRepoWriter, context.RepoRef())
488
+		m.Group("/releases", func() {
489
+			m.Get("/new", repo.NewRelease)
490
+			m.Post("/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost)
491
+			m.Post("/delete", repo.DeleteRelease)
492
+		}, repo.MustBeNotBare, reqRepoWriter, context.RepoRef())
493
+		m.Group("/releases", func() {
494
+			m.Get("/edit/*", repo.EditRelease)
495
+			m.Post("/edit/*", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost)
496
+		}, repo.MustBeNotBare, reqRepoWriter, func(ctx *context.Context) {
497
+			var err error
498
+			ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
499
+			if err != nil {
500
+				ctx.Handle(500, "GetBranchCommit", err)
501
+				return
502
+			}
503
+			ctx.Repo.CommitsCount, err = ctx.Repo.Commit.CommitsCount()
504
+			if err != nil {
505
+				ctx.Handle(500, "CommitsCount", err)
506
+				return
507
+			}
508
+			ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
509
+		})
510
+
511
+		m.Combo("/compare/*", repo.MustAllowPulls, repo.SetEditorconfigIfExists).
512
+			Get(repo.CompareAndPullRequest).
513
+			Post(bindIgnErr(auth.CreateIssueForm{}), repo.CompareAndPullRequestPost)
514
+
515
+		m.Group("", func() {
516
+			m.Combo("/_edit/*").Get(repo.EditFile).
517
+				Post(bindIgnErr(auth.EditRepoFileForm{}), repo.EditFilePost)
518
+			m.Combo("/_new/*").Get(repo.NewFile).
519
+				Post(bindIgnErr(auth.EditRepoFileForm{}), repo.NewFilePost)
520
+			m.Post("/_preview/*", bindIgnErr(auth.EditPreviewDiffForm{}), repo.DiffPreviewPost)
521
+			m.Combo("/_delete/*").Get(repo.DeleteFile).
522
+				Post(bindIgnErr(auth.DeleteRepoFileForm{}), repo.DeleteFilePost)
523
+
524
+			m.Group("", func() {
525
+				m.Combo("/_upload/*").Get(repo.UploadFile).
526
+					Post(bindIgnErr(auth.UploadRepoFileForm{}), repo.UploadFilePost)
527
+				m.Post("/upload-file", repo.UploadFileToServer)
528
+				m.Post("/upload-remove", bindIgnErr(auth.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer)
529
+			}, func(ctx *context.Context) {
530
+				if !setting.Repository.Upload.Enabled {
531
+					ctx.Handle(404, "", nil)
532
+					return
533
+				}
534
+			})
535
+		}, repo.MustBeNotBare, reqRepoWriter, context.RepoRef(), func(ctx *context.Context) {
536
+			if !ctx.Repo.Repository.CanEnableEditor() || ctx.Repo.IsViewCommit {
537
+				ctx.Handle(404, "", nil)
538
+				return
539
+			}
540
+		})
541
+	}, reqSignIn, context.RepoAssignment(), context.UnitTypes())
542
+
543
+	m.Group("/:username/:reponame", func() {
544
+		m.Group("", func() {
545
+			m.Get("/releases", repo.MustBeNotBare, repo.Releases)
546
+			m.Get("/^:type(issues|pulls)$", repo.RetrieveLabels, repo.Issues)
547
+			m.Get("/^:type(issues|pulls)$/:index", repo.ViewIssue)
548
+			m.Get("/labels/", repo.RetrieveLabels, repo.Labels)
549
+			m.Get("/milestones", repo.Milestones)
550
+		}, context.RepoRef())
551
+
552
+		// m.Get("/branches", repo.Branches)
553
+		m.Post("/branches/:name/delete", reqSignIn, reqRepoWriter, repo.MustBeNotBare, repo.DeleteBranchPost)
554
+
555
+		m.Group("/wiki", func() {
556
+			m.Get("/?:page", repo.Wiki)
557
+			m.Get("/_pages", repo.WikiPages)
558
+
559
+			m.Group("", func() {
560
+				m.Combo("/_new").Get(repo.NewWiki).
561
+					Post(bindIgnErr(auth.NewWikiForm{}), repo.NewWikiPost)
562
+				m.Combo("/:page/_edit").Get(repo.EditWiki).
563
+					Post(bindIgnErr(auth.NewWikiForm{}), repo.EditWikiPost)
564
+				m.Post("/:page/delete", repo.DeleteWikiPagePost)
565
+			}, reqSignIn, reqRepoWriter)
566
+		}, repo.MustEnableWiki, context.RepoRef())
567
+
568
+		m.Group("/wiki", func() {
569
+			m.Get("/raw/*", repo.WikiRaw)
570
+			m.Get("/*", repo.WikiRaw)
571
+		}, repo.MustEnableWiki)
572
+
573
+		m.Get("/archive/*", repo.MustBeNotBare, repo.Download)
574
+
575
+		m.Group("/pulls/:index", func() {
576
+			m.Get("/commits", context.RepoRef(), repo.ViewPullCommits)
577
+			m.Get("/files", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.ViewPullFiles)
578
+			m.Post("/merge", reqRepoWriter, repo.MergePullRequest)
579
+		}, repo.MustAllowPulls)
580
+
581
+		m.Group("", func() {
582
+			m.Get("/src/*", repo.SetEditorconfigIfExists, repo.Home)
583
+			m.Get("/raw/*", repo.SingleDownload)
584
+			m.Get("/commits/*", repo.RefCommits)
585
+			m.Get("/graph", repo.Graph)
586
+			m.Get("/commit/:sha([a-f0-9]{7,40})$", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.Diff)
587
+			m.Get("/forks", repo.Forks)
588
+		}, context.RepoRef())
589
+		m.Get("/commit/:sha([a-f0-9]{7,40})\\.:ext(patch|diff)", repo.MustBeNotBare, repo.RawDiff)
590
+
591
+		m.Get("/compare/:before([a-z0-9]{40})\\.\\.\\.:after([a-z0-9]{40})", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.MustBeNotBare, repo.CompareDiff)
592
+	}, ignSignIn, context.RepoAssignment(), context.UnitTypes())
593
+	m.Group("/:username/:reponame", func() {
594
+		m.Get("/stars", repo.Stars)
595
+		m.Get("/watchers", repo.Watchers)
596
+	}, ignSignIn, context.RepoAssignment(), context.RepoRef(), context.UnitTypes())
597
+
598
+	m.Group("/:username", func() {
599
+		m.Group("/:reponame", func() {
600
+			m.Get("", repo.SetEditorconfigIfExists, repo.Home)
601
+			m.Get("\\.git$", repo.SetEditorconfigIfExists, repo.Home)
602
+		}, ignSignIn, context.RepoAssignment(), context.RepoRef(), context.UnitTypes())
603
+
604
+		m.Group("/:reponame", func() {
605
+			m.Group("/info/lfs", func() {
606
+				m.Post("/objects/batch", lfs.BatchHandler)
607
+				m.Get("/objects/:oid/:filename", lfs.ObjectOidHandler)
608
+				m.Any("/objects/:oid", lfs.ObjectOidHandler)
609
+				m.Post("/objects", lfs.PostHandler)
610
+				m.Any("/*", func(ctx *context.Context) {
611
+					ctx.Handle(404, "", nil)
612
+				})
613
+			}, ignSignInAndCsrf)
614
+			m.Any("/*", ignSignInAndCsrf, repo.HTTP)
615
+			m.Head("/tasks/trigger", repo.TriggerTask)
616
+		})
617
+	})
618
+	// ***** END: Repository *****
619
+
620
+	m.Group("/notifications", func() {
621
+		m.Get("", user.Notifications)
622
+		m.Post("/status", user.NotificationStatusPost)
623
+	}, reqSignIn)
624
+
625
+	m.Group("/api", func() {
626
+		apiv1.RegisterRoutes(m)
627
+	}, ignSignIn)
628
+
629
+	m.Group("/api/internal", func() {
630
+		// package name internal is ideal but Golang is not allowed, so we use private as package name.
631
+		private.RegisterRoutes(m)
632
+	})
633
+
634
+	// robots.txt
635
+	m.Get("/robots.txt", func(ctx *context.Context) {
636
+		if setting.HasRobotsTxt {
637
+			ctx.ServeFileContent(path.Join(setting.CustomPath, "robots.txt"))
638
+		} else {
639
+			ctx.Error(404)
640
+		}
641
+	})
642
+
643
+	// Not found handler.
644
+	m.NotFound(routers.NotFound)
645
+}