mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 08:02:36 +09:00 
			
		
		
		
	Compare commits
	
		
			13 Commits
		
	
	
		
			v1.19.0-de
			...
			v1.16.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					a044ec8b53 | ||
| 
						 | 
					f93d72c09b | ||
| 
						 | 
					2f22337125 | ||
| 
						 | 
					781ad8a79e | ||
| 
						 | 
					cada7202aa | ||
| 
						 | 
					0b331e2213 | ||
| 
						 | 
					0734ca0132 | ||
| 
						 | 
					0b83cc21be | ||
| 
						 | 
					b68e605d56 | ||
| 
						 | 
					42991dc89a | ||
| 
						 | 
					160de9fbda | ||
| 
						 | 
					d644289fcb | ||
| 
						 | 
					fd9ff7cd6f | 
							
								
								
									
										27
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -4,13 +4,15 @@ This changelog goes through all the changes that have been made in each release
 | 
			
		||||
without substantial changes to our git log; to see the highlights of what has
 | 
			
		||||
been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
			
		||||
 | 
			
		||||
## [1.16.0-rc1](https://github.com/go-gitea/gitea/releases/tag/v1.16.0-rc1) - 2022-01-19
 | 
			
		||||
## [1.16.0](https://github.com/go-gitea/gitea/releases/tag/v1.16.0) - 2022-01-30
 | 
			
		||||
 | 
			
		||||
* BREAKING
 | 
			
		||||
  * Remove golang vendored directory (#18277)
 | 
			
		||||
  * Paginate releases page & set default page size to 10 (#16857)
 | 
			
		||||
  * Only allow webhook to send requests to allowed hosts (#17482)
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Disable content sniffing on `PlainTextBytes` (#18359) (#18365)
 | 
			
		||||
  * Only view milestones from current repo (#18414) (#18417)
 | 
			
		||||
  * Sanitize user-input on file name (#17666)
 | 
			
		||||
  * Use `hostmatcher` to replace `matchlist` to improve blocking of bad hosts in Webhooks (#17605)
 | 
			
		||||
* FEATURES
 | 
			
		||||
@@ -228,6 +230,16 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
			
		||||
  * Add left padding for chunk header of split diff view (#13397)
 | 
			
		||||
  * Allow U2F 2FA without TOTP (#11573)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * GitLab reviews may not have the updated_at field set (#18450) (#18461)
 | 
			
		||||
  * Fix detection of no commits when the default branch is not master (#18422) (#18423)
 | 
			
		||||
  * Fix broken oauth2 authentication source edit page (#18412) (#18419)
 | 
			
		||||
  * Place inline diff comment dialogs on split diff in 4th and 8th columns (#18403) (#18404)
 | 
			
		||||
  * Fix restore without topic failure (#18387) (#18400)
 | 
			
		||||
  * Fix commit's time (#18375) (#18392)
 | 
			
		||||
  * Fix partial cloning a repo (#18373) (#18377)
 | 
			
		||||
  * Stop trimming preceding and suffixing spaces from editor filenames (#18334)
 | 
			
		||||
  * Prevent showing webauthn error for every time visiting `/user/settings/security` (#18386)
 | 
			
		||||
  * Fix mime-type detection for HTTP server (#18370) (#18371)
 | 
			
		||||
  * Stop trimming preceding and suffixing spaces from editor filenames (#18334)
 | 
			
		||||
  * Restore propagation of ErrDependenciesLeft (#18325)
 | 
			
		||||
  * Fix PR comments UI (#18323)
 | 
			
		||||
@@ -299,6 +311,19 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
			
		||||
* MISC
 | 
			
		||||
  * Update JS dependencies (#17611)
 | 
			
		||||
 | 
			
		||||
## [1.15.11](https://github.com/go-gitea/gitea/releases/tag/v1.15.11) - 2022-01-29
 | 
			
		||||
 | 
			
		||||
* SECURITY
 | 
			
		||||
  * Only view milestones from current repo (#18414) (#18418)
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Fix broken when no commits and default branch is not master (#18422) (#18424)
 | 
			
		||||
  * Fix commit's time (#18375) (#18409)
 | 
			
		||||
  * Fix restore without topic failure (#18387) (#18401)
 | 
			
		||||
  * Fix mermaid import in 1.15 (it uses ESModule now) (#18382)
 | 
			
		||||
  * Update to go/text 0.3.7 (#18336)
 | 
			
		||||
* MISC
 | 
			
		||||
  * Upgrade EasyMDE to 2.16.1 (#18278) (#18279)
 | 
			
		||||
 | 
			
		||||
## [1.15.10](https://github.com/go-gitea/gitea/releases/tag/v1.15.10) - 2022-01-14
 | 
			
		||||
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@ params:
 | 
			
		||||
  description: Git with a cup of tea
 | 
			
		||||
  author: The Gitea Authors
 | 
			
		||||
  website: https://docs.gitea.io
 | 
			
		||||
  version: 1.15.10
 | 
			
		||||
  version: 1.16.0
 | 
			
		||||
  minGoVersion: 1.16
 | 
			
		||||
  goVersion: 1.17
 | 
			
		||||
  minNodeVersion: 12.17
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								go.mod
									
									
									
									
									
								
							@@ -30,7 +30,7 @@ require (
 | 
			
		||||
	github.com/denisenkom/go-mssqldb v0.10.0
 | 
			
		||||
	github.com/djherbis/buffer v1.2.0
 | 
			
		||||
	github.com/djherbis/nio/v3 v3.0.1
 | 
			
		||||
	github.com/duo-labs/webauthn v0.0.0-20211221191814-a22482edaa3b
 | 
			
		||||
	github.com/duo-labs/webauthn v0.0.0-20220122034320-81aea484c951
 | 
			
		||||
	github.com/dustin/go-humanize v1.0.0
 | 
			
		||||
	github.com/editorconfig/editorconfig-core-go/v2 v2.4.2
 | 
			
		||||
	github.com/emirpasic/gods v1.12.0
 | 
			
		||||
@@ -54,7 +54,7 @@ require (
 | 
			
		||||
	github.com/golang-jwt/jwt/v4 v4.2.0
 | 
			
		||||
	github.com/golang/snappy v0.0.4 // indirect
 | 
			
		||||
	github.com/google/go-github/v39 v39.2.0
 | 
			
		||||
	github.com/google/uuid v1.2.0
 | 
			
		||||
	github.com/google/uuid v1.3.0
 | 
			
		||||
	github.com/gorilla/feeds v1.1.1
 | 
			
		||||
	github.com/gorilla/mux v1.8.0 // indirect
 | 
			
		||||
	github.com/gorilla/sessions v1.2.1
 | 
			
		||||
@@ -145,8 +145,6 @@ replace github.com/markbates/goth v1.68.0 => github.com/zeripath/goth v1.68.1-0.
 | 
			
		||||
 | 
			
		||||
replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0
 | 
			
		||||
 | 
			
		||||
replace github.com/duo-labs/webauthn => github.com/authelia/webauthn v0.0.0-20211225121951-80d1f2a572e4
 | 
			
		||||
 | 
			
		||||
replace github.com/satori/go.uuid v1.2.0 => github.com/gofrs/uuid v4.2.0+incompatible
 | 
			
		||||
 | 
			
		||||
exclude github.com/gofrs/uuid v3.2.0+incompatible
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								go.sum
									
									
									
									
									
								
							@@ -131,8 +131,6 @@ github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:o
 | 
			
		||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
 | 
			
		||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ=
 | 
			
		||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
 | 
			
		||||
github.com/authelia/webauthn v0.0.0-20211225121951-80d1f2a572e4 h1:u3eFvgr4A8IjlAokbFt6XY6VdurX7DEYnQMQ4K2yobc=
 | 
			
		||||
github.com/authelia/webauthn v0.0.0-20211225121951-80d1f2a572e4/go.mod h1:EYSpSkwoEcryMmQGfhol2IiB3IMN9IIIaNd/wcAQMGQ=
 | 
			
		||||
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
 | 
			
		||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 | 
			
		||||
github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
 | 
			
		||||
@@ -276,6 +274,8 @@ github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
 | 
			
		||||
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
 | 
			
		||||
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
 | 
			
		||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
 | 
			
		||||
github.com/duo-labs/webauthn v0.0.0-20220122034320-81aea484c951 h1:17esZ09oW+29rklBtCVphIguql2u3NxYH2OasFPPZoo=
 | 
			
		||||
github.com/duo-labs/webauthn v0.0.0-20220122034320-81aea484c951/go.mod h1:nHy3JdztZWcsjenDeBuE8gn171OAwg12LBN027UP5AE=
 | 
			
		||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 | 
			
		||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
 | 
			
		||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 | 
			
		||||
@@ -491,7 +491,6 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
 | 
			
		||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
 | 
			
		||||
github.com/goccy/go-json v0.7.4 h1:B44qRUFwz/vxPKPISQ1KhvzRi9kZ28RAf6YtjriBZ5k=
 | 
			
		||||
github.com/goccy/go-json v0.7.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 | 
			
		||||
github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
 | 
			
		||||
github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 | 
			
		||||
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
 | 
			
		||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 | 
			
		||||
@@ -585,8 +584,8 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
 | 
			
		||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
			
		||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
			
		||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
			
		||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
 | 
			
		||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
			
		||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 | 
			
		||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
			
		||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 | 
			
		||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 | 
			
		||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 | 
			
		||||
 
 | 
			
		||||
@@ -123,6 +123,17 @@ func doGitClone(dstLocalPath string, u *url.URL) func(*testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func doPartialGitClone(dstLocalPath string, u *url.URL) func(*testing.T) {
 | 
			
		||||
	return func(t *testing.T) {
 | 
			
		||||
		assert.NoError(t, git.CloneWithArgs(context.Background(), u.String(), dstLocalPath, allowLFSFilters(), git.CloneRepoOptions{
 | 
			
		||||
			Filter: "blob:none",
 | 
			
		||||
		}))
 | 
			
		||||
		exist, err := util.IsExist(filepath.Join(dstLocalPath, "README.md"))
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		assert.True(t, exist)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func doGitCloneFail(u *url.URL) func(*testing.T) {
 | 
			
		||||
	return func(t *testing.T) {
 | 
			
		||||
		tmpDir, err := os.MkdirTemp("", "doGitCloneFail")
 | 
			
		||||
 
 | 
			
		||||
@@ -69,6 +69,12 @@ func testGit(t *testing.T, u *url.URL) {
 | 
			
		||||
 | 
			
		||||
		t.Run("Clone", doGitClone(dstPath, u))
 | 
			
		||||
 | 
			
		||||
		dstPath2, err := os.MkdirTemp("", httpContext.Reponame)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		defer util.RemoveAll(dstPath2)
 | 
			
		||||
 | 
			
		||||
		t.Run("Partial Clone", doPartialGitClone(dstPath2, u))
 | 
			
		||||
 | 
			
		||||
		little, big := standardCommitAndPushTest(t, dstPath)
 | 
			
		||||
		littleLFS, bigLFS := lfsCommitAndPushTest(t, dstPath)
 | 
			
		||||
		rawTest(t, &httpContext, little, big, littleLFS, bigLFS)
 | 
			
		||||
 
 | 
			
		||||
@@ -134,22 +134,6 @@ func GetMilestoneByRepoIDANDName(repoID int64, name string) (*Milestone, error)
 | 
			
		||||
	return &mile, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetMilestoneByID returns the milestone via id .
 | 
			
		||||
func GetMilestoneByID(id int64) (*Milestone, error) {
 | 
			
		||||
	return getMilestoneByID(db.GetEngine(db.DefaultContext), id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getMilestoneByID(e db.Engine, id int64) (*Milestone, error) {
 | 
			
		||||
	var m Milestone
 | 
			
		||||
	has, err := e.ID(id).Get(&m)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	} else if !has {
 | 
			
		||||
		return nil, ErrMilestoneNotExist{ID: id, RepoID: 0}
 | 
			
		||||
	}
 | 
			
		||||
	return &m, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateMilestone updates information of given milestone.
 | 
			
		||||
func UpdateMilestone(m *Milestone, oldIsClosed bool) error {
 | 
			
		||||
	ctx, committer, err := db.TxContext()
 | 
			
		||||
 
 | 
			
		||||
@@ -291,6 +291,7 @@ func (ctx *Context) PlainTextBytes(status int, bs []byte) {
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Resp.WriteHeader(status)
 | 
			
		||||
	ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8")
 | 
			
		||||
	ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff")
 | 
			
		||||
	if _, err := ctx.Resp.Write(bs); err != nil {
 | 
			
		||||
		log.Error("Write bytes failed: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -59,27 +59,28 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff
 | 
			
		||||
	ctx, _, finished := process.GetManager().AddContext(repo.Ctx, fmt.Sprintf("GetRawDiffForFile: [repo_path: %s]", repo.Path))
 | 
			
		||||
	defer finished()
 | 
			
		||||
 | 
			
		||||
	var cmd *exec.Cmd
 | 
			
		||||
	cmd := exec.CommandContext(ctx, GitExecutable, GlobalCommandArgs...)
 | 
			
		||||
 | 
			
		||||
	switch diffType {
 | 
			
		||||
	case RawDiffNormal:
 | 
			
		||||
		if len(startCommit) != 0 {
 | 
			
		||||
			cmd = exec.CommandContext(ctx, GitExecutable, append([]string{"diff", "-M", startCommit, endCommit}, fileArgs...)...)
 | 
			
		||||
			cmd.Args = append(cmd.Args, append([]string{"diff", "-M", startCommit, endCommit}, fileArgs...)...)
 | 
			
		||||
		} else if commit.ParentCount() == 0 {
 | 
			
		||||
			cmd = exec.CommandContext(ctx, GitExecutable, append([]string{"show", endCommit}, fileArgs...)...)
 | 
			
		||||
			cmd.Args = append(cmd.Args, append([]string{"show", endCommit}, fileArgs...)...)
 | 
			
		||||
		} else {
 | 
			
		||||
			c, _ := commit.Parent(0)
 | 
			
		||||
			cmd = exec.CommandContext(ctx, GitExecutable, append([]string{"diff", "-M", c.ID.String(), endCommit}, fileArgs...)...)
 | 
			
		||||
			cmd.Args = append(cmd.Args, append([]string{"diff", "-M", c.ID.String(), endCommit}, fileArgs...)...)
 | 
			
		||||
		}
 | 
			
		||||
	case RawDiffPatch:
 | 
			
		||||
		if len(startCommit) != 0 {
 | 
			
		||||
			query := fmt.Sprintf("%s...%s", endCommit, startCommit)
 | 
			
		||||
			cmd = exec.CommandContext(ctx, GitExecutable, append([]string{"format-patch", "--no-signature", "--stdout", "--root", query}, fileArgs...)...)
 | 
			
		||||
			cmd.Args = append(cmd.Args, append([]string{"format-patch", "--no-signature", "--stdout", "--root", query}, fileArgs...)...)
 | 
			
		||||
		} else if commit.ParentCount() == 0 {
 | 
			
		||||
			cmd = exec.CommandContext(ctx, GitExecutable, append([]string{"format-patch", "--no-signature", "--stdout", "--root", endCommit}, fileArgs...)...)
 | 
			
		||||
			cmd.Args = append(cmd.Args, append([]string{"format-patch", "--no-signature", "--stdout", "--root", endCommit}, fileArgs...)...)
 | 
			
		||||
		} else {
 | 
			
		||||
			c, _ := commit.Parent(0)
 | 
			
		||||
			query := fmt.Sprintf("%s...%s", endCommit, c.ID.String())
 | 
			
		||||
			cmd = exec.CommandContext(ctx, GitExecutable, append([]string{"format-patch", "--no-signature", "--stdout", query}, fileArgs...)...)
 | 
			
		||||
			cmd.Args = append(cmd.Args, append([]string{"format-patch", "--no-signature", "--stdout", query}, fileArgs...)...)
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		return fmt.Errorf("invalid diffType: %s", diffType)
 | 
			
		||||
 
 | 
			
		||||
@@ -79,16 +79,21 @@ func InitRepository(repoPath string, bare bool) error {
 | 
			
		||||
 | 
			
		||||
// IsEmpty Check if repository is empty.
 | 
			
		||||
func (repo *Repository) IsEmpty() (bool, error) {
 | 
			
		||||
	var errbuf strings.Builder
 | 
			
		||||
	if err := NewCommand("log", "-1").RunInDirPipeline(repo.Path, nil, &errbuf); err != nil {
 | 
			
		||||
		if strings.Contains(errbuf.String(), "fatal: bad default revision 'HEAD'") ||
 | 
			
		||||
			strings.Contains(errbuf.String(), "fatal: your current branch 'master' does not have any commits yet") {
 | 
			
		||||
			return true, nil
 | 
			
		||||
		}
 | 
			
		||||
	var errbuf, output strings.Builder
 | 
			
		||||
	if err := NewCommandContext(repo.Ctx, "rev-list", "--all", "--count", "--max-count=1").RunWithContext(&RunContext{
 | 
			
		||||
		Timeout: -1,
 | 
			
		||||
		Dir:     repo.Path,
 | 
			
		||||
		Stdout:  &output,
 | 
			
		||||
		Stderr:  &errbuf,
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return true, fmt.Errorf("check empty: %v - %s", err, errbuf.String())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false, nil
 | 
			
		||||
	c, err := strconv.Atoi(strings.TrimSpace(output.String()))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return true, fmt.Errorf("check empty: convert %s to count failed: %v", output.String(), err)
 | 
			
		||||
	}
 | 
			
		||||
	return c == 0, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CloneRepoOptions options when clone a repository
 | 
			
		||||
@@ -101,6 +106,7 @@ type CloneRepoOptions struct {
 | 
			
		||||
	Shared     bool
 | 
			
		||||
	NoCheckout bool
 | 
			
		||||
	Depth      int
 | 
			
		||||
	Filter     string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Clone clones original repository to target path.
 | 
			
		||||
@@ -141,7 +147,9 @@ func CloneWithArgs(ctx context.Context, from, to string, args []string, opts Clo
 | 
			
		||||
	if opts.Depth > 0 {
 | 
			
		||||
		cmd.AddArguments("--depth", strconv.Itoa(opts.Depth))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if opts.Filter != "" {
 | 
			
		||||
		cmd.AddArguments("--filter", opts.Filter)
 | 
			
		||||
	}
 | 
			
		||||
	if len(opts.Branch) > 0 {
 | 
			
		||||
		cmd.AddArguments("-b", opts.Branch)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										41
									
								
								modules/public/mime_types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								modules/public/mime_types.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
// Copyright 2022 The Gitea Authors. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a MIT-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package public
 | 
			
		||||
 | 
			
		||||
import "strings"
 | 
			
		||||
 | 
			
		||||
// wellKnownMimeTypesLower comes from Golang's builtin mime package: `builtinTypesLower`, see the comment of detectWellKnownMimeType
 | 
			
		||||
var wellKnownMimeTypesLower = map[string]string{
 | 
			
		||||
	".avif": "image/avif",
 | 
			
		||||
	".css":  "text/css; charset=utf-8",
 | 
			
		||||
	".gif":  "image/gif",
 | 
			
		||||
	".htm":  "text/html; charset=utf-8",
 | 
			
		||||
	".html": "text/html; charset=utf-8",
 | 
			
		||||
	".jpeg": "image/jpeg",
 | 
			
		||||
	".jpg":  "image/jpeg",
 | 
			
		||||
	".js":   "text/javascript; charset=utf-8",
 | 
			
		||||
	".json": "application/json",
 | 
			
		||||
	".mjs":  "text/javascript; charset=utf-8",
 | 
			
		||||
	".pdf":  "application/pdf",
 | 
			
		||||
	".png":  "image/png",
 | 
			
		||||
	".svg":  "image/svg+xml",
 | 
			
		||||
	".wasm": "application/wasm",
 | 
			
		||||
	".webp": "image/webp",
 | 
			
		||||
	".xml":  "text/xml; charset=utf-8",
 | 
			
		||||
 | 
			
		||||
	// well, there are some types missing from the builtin list
 | 
			
		||||
	".txt": "text/plain; charset=utf-8",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// detectWellKnownMimeType will return the mime-type for a well-known file ext name
 | 
			
		||||
// The purpose of this function is to bypass the unstable behavior of Golang's mime.TypeByExtension
 | 
			
		||||
// mime.TypeByExtension would use OS's mime-type config to overwrite the well-known types (see its document).
 | 
			
		||||
// If the user's OS has incorrect mime-type config, it would make Gitea can not respond a correct Content-Type to browsers.
 | 
			
		||||
// For example, if Gitea returns `text/plain` for a `.js` file, the browser couldn't run the JS due to security reasons.
 | 
			
		||||
// detectWellKnownMimeType makes the Content-Type for well-known files stable.
 | 
			
		||||
func detectWellKnownMimeType(ext string) string {
 | 
			
		||||
	ext = strings.ToLower(ext)
 | 
			
		||||
	return wellKnownMimeTypesLower[ext]
 | 
			
		||||
}
 | 
			
		||||
@@ -95,6 +95,15 @@ func parseAcceptEncoding(val string) map[string]bool {
 | 
			
		||||
	return types
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// setWellKnownContentType will set the Content-Type if the file is a well-known type.
 | 
			
		||||
// See the comments of detectWellKnownMimeType
 | 
			
		||||
func setWellKnownContentType(w http.ResponseWriter, file string) {
 | 
			
		||||
	mimeType := detectWellKnownMimeType(filepath.Ext(file))
 | 
			
		||||
	if mimeType != "" {
 | 
			
		||||
		w.Header().Set("Content-Type", mimeType)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (opts *Options) handle(w http.ResponseWriter, req *http.Request, fs http.FileSystem, file string) bool {
 | 
			
		||||
	// use clean to keep the file is a valid path with no . or ..
 | 
			
		||||
	f, err := fs.Open(path.Clean(file))
 | 
			
		||||
@@ -125,6 +134,8 @@ func (opts *Options) handle(w http.ResponseWriter, req *http.Request, fs http.Fi
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	setWellKnownContentType(w, file)
 | 
			
		||||
 | 
			
		||||
	serveContent(w, req, fi, fi.ModTime(), f)
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,15 +9,12 @@ package public
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"compress/gzip"
 | 
			
		||||
	"io"
 | 
			
		||||
	"mime"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -66,24 +63,16 @@ func serveContent(w http.ResponseWriter, req *http.Request, fi os.FileInfo, modt
 | 
			
		||||
	encodings := parseAcceptEncoding(req.Header.Get("Accept-Encoding"))
 | 
			
		||||
	if encodings["gzip"] {
 | 
			
		||||
		if cf, ok := fi.(*vfsgen۰CompressedFileInfo); ok {
 | 
			
		||||
			rd := bytes.NewReader(cf.GzipBytes())
 | 
			
		||||
			w.Header().Set("Content-Encoding", "gzip")
 | 
			
		||||
			ctype := mime.TypeByExtension(filepath.Ext(fi.Name()))
 | 
			
		||||
			if ctype == "" {
 | 
			
		||||
				// read a chunk to decide between utf-8 text and binary
 | 
			
		||||
				var buf [512]byte
 | 
			
		||||
				grd, _ := gzip.NewReader(rd)
 | 
			
		||||
				n, _ := io.ReadFull(grd, buf[:])
 | 
			
		||||
				ctype = http.DetectContentType(buf[:n])
 | 
			
		||||
				_, err := rd.Seek(0, io.SeekStart) // rewind to output whole file
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					log.Error("rd.Seek error: %v", err)
 | 
			
		||||
					http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
			rdGzip := bytes.NewReader(cf.GzipBytes())
 | 
			
		||||
			// all static files are managed by Gitea, so we can make sure every file has the correct ext name
 | 
			
		||||
			// then we can get the correct Content-Type, we do not need to do http.DetectContentType on the decompressed data
 | 
			
		||||
			mimeType := detectWellKnownMimeType(filepath.Ext(fi.Name()))
 | 
			
		||||
			if mimeType == "" {
 | 
			
		||||
				mimeType = "application/octet-stream"
 | 
			
		||||
			}
 | 
			
		||||
			w.Header().Set("Content-Type", ctype)
 | 
			
		||||
			http.ServeContent(w, req, fi.Name(), modtime, rd)
 | 
			
		||||
			w.Header().Set("Content-Type", mimeType)
 | 
			
		||||
			w.Header().Set("Content-Encoding", "gzip")
 | 
			
		||||
			http.ServeContent(w, req, fi.Name(), modtime, rdGzip)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -491,7 +491,10 @@ func serviceRPC(h serviceHandler, service string) {
 | 
			
		||||
	defer finished()
 | 
			
		||||
 | 
			
		||||
	var stderr bytes.Buffer
 | 
			
		||||
	cmd := exec.CommandContext(ctx, git.GitExecutable, service, "--stateless-rpc", h.dir)
 | 
			
		||||
	args := make([]string, len(git.GlobalCommandArgs))
 | 
			
		||||
	copy(args, git.GlobalCommandArgs)
 | 
			
		||||
	args = append(args, []string{service, "--stateless-rpc", h.dir}...)
 | 
			
		||||
	cmd := exec.CommandContext(ctx, git.GitExecutable, args...)
 | 
			
		||||
	cmd.Dir = h.dir
 | 
			
		||||
	cmd.Env = append(os.Environ(), h.environ...)
 | 
			
		||||
	cmd.Stdout = h.w
 | 
			
		||||
 
 | 
			
		||||
@@ -802,7 +802,7 @@ func NewIssue(ctx *context.Context) {
 | 
			
		||||
 | 
			
		||||
	milestoneID := ctx.FormInt64("milestone")
 | 
			
		||||
	if milestoneID > 0 {
 | 
			
		||||
		milestone, err := models.GetMilestoneByID(milestoneID)
 | 
			
		||||
		milestone, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, milestoneID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error("GetMilestoneByID: %d: %v", milestoneID, err)
 | 
			
		||||
		} else {
 | 
			
		||||
@@ -889,7 +889,7 @@ func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull
 | 
			
		||||
	// Check milestone.
 | 
			
		||||
	milestoneID := form.MilestoneID
 | 
			
		||||
	if milestoneID > 0 {
 | 
			
		||||
		milestone, err := models.GetMilestoneByID(milestoneID)
 | 
			
		||||
		milestone, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, milestoneID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.ServerError("GetMilestoneByID", err)
 | 
			
		||||
			return nil, nil, 0, 0
 | 
			
		||||
 
 | 
			
		||||
@@ -264,7 +264,7 @@ func DeleteMilestone(ctx *context.Context) {
 | 
			
		||||
// MilestoneIssuesAndPulls lists all the issues and pull requests of the milestone
 | 
			
		||||
func MilestoneIssuesAndPulls(ctx *context.Context) {
 | 
			
		||||
	milestoneID := ctx.ParamsInt64(":id")
 | 
			
		||||
	milestone, err := models.GetMilestoneByID(milestoneID)
 | 
			
		||||
	milestone, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, milestoneID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if models.IsErrMilestoneNotExist(err) {
 | 
			
		||||
			ctx.NotFound("GetMilestoneByID", err)
 | 
			
		||||
 
 | 
			
		||||
@@ -32,8 +32,7 @@ func init() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GitlabDownloaderFactory defines a gitlab downloader factory
 | 
			
		||||
type GitlabDownloaderFactory struct {
 | 
			
		||||
}
 | 
			
		||||
type GitlabDownloaderFactory struct{}
 | 
			
		||||
 | 
			
		||||
// New returns a Downloader related to this factory according MigrateOptions
 | 
			
		||||
func (f *GitlabDownloaderFactory) New(ctx context.Context, opts base.MigrateOptions) (base.Downloader, error) {
 | 
			
		||||
@@ -184,16 +183,17 @@ func (g *GitlabDownloader) GetTopics() ([]string, error) {
 | 
			
		||||
 | 
			
		||||
// GetMilestones returns milestones
 | 
			
		||||
func (g *GitlabDownloader) GetMilestones() ([]*base.Milestone, error) {
 | 
			
		||||
	var perPage = g.maxPerPage
 | 
			
		||||
	var state = "all"
 | 
			
		||||
	var milestones = make([]*base.Milestone, 0, perPage)
 | 
			
		||||
	perPage := g.maxPerPage
 | 
			
		||||
	state := "all"
 | 
			
		||||
	milestones := make([]*base.Milestone, 0, perPage)
 | 
			
		||||
	for i := 1; ; i++ {
 | 
			
		||||
		ms, _, err := g.client.Milestones.ListMilestones(g.repoID, &gitlab.ListMilestonesOptions{
 | 
			
		||||
			State: &state,
 | 
			
		||||
			ListOptions: gitlab.ListOptions{
 | 
			
		||||
				Page:    i,
 | 
			
		||||
				PerPage: perPage,
 | 
			
		||||
			}}, nil, gitlab.WithContext(g.ctx))
 | 
			
		||||
			},
 | 
			
		||||
		}, nil, gitlab.WithContext(g.ctx))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
@@ -203,7 +203,7 @@ func (g *GitlabDownloader) GetMilestones() ([]*base.Milestone, error) {
 | 
			
		||||
			if m.Description != "" {
 | 
			
		||||
				desc = m.Description
 | 
			
		||||
			}
 | 
			
		||||
			var state = "open"
 | 
			
		||||
			state := "open"
 | 
			
		||||
			var closedAt *time.Time
 | 
			
		||||
			if m.State != "" {
 | 
			
		||||
				state = m.State
 | 
			
		||||
@@ -255,8 +255,8 @@ func (g *GitlabDownloader) normalizeColor(val string) string {
 | 
			
		||||
 | 
			
		||||
// GetLabels returns labels
 | 
			
		||||
func (g *GitlabDownloader) GetLabels() ([]*base.Label, error) {
 | 
			
		||||
	var perPage = g.maxPerPage
 | 
			
		||||
	var labels = make([]*base.Label, 0, perPage)
 | 
			
		||||
	perPage := g.maxPerPage
 | 
			
		||||
	labels := make([]*base.Label, 0, perPage)
 | 
			
		||||
	for i := 1; ; i++ {
 | 
			
		||||
		ls, _, err := g.client.Labels.ListLabels(g.repoID, &gitlab.ListLabelsOptions{ListOptions: gitlab.ListOptions{
 | 
			
		||||
			Page:    i,
 | 
			
		||||
@@ -327,8 +327,8 @@ func (g *GitlabDownloader) convertGitlabRelease(rel *gitlab.Release) *base.Relea
 | 
			
		||||
 | 
			
		||||
// GetReleases returns releases
 | 
			
		||||
func (g *GitlabDownloader) GetReleases() ([]*base.Release, error) {
 | 
			
		||||
	var perPage = g.maxPerPage
 | 
			
		||||
	var releases = make([]*base.Release, 0, perPage)
 | 
			
		||||
	perPage := g.maxPerPage
 | 
			
		||||
	releases := make([]*base.Release, 0, perPage)
 | 
			
		||||
	for i := 1; ; i++ {
 | 
			
		||||
		ls, _, err := g.client.Releases.ListReleases(g.repoID, &gitlab.ListReleasesOptions{
 | 
			
		||||
			Page:    i,
 | 
			
		||||
@@ -381,7 +381,7 @@ func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var allIssues = make([]*base.Issue, 0, perPage)
 | 
			
		||||
	allIssues := make([]*base.Issue, 0, perPage)
 | 
			
		||||
 | 
			
		||||
	issues, _, err := g.client.Issues.ListProjectIssues(g.repoID, opt, nil, gitlab.WithContext(g.ctx))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -389,7 +389,7 @@ func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er
 | 
			
		||||
	}
 | 
			
		||||
	for _, issue := range issues {
 | 
			
		||||
 | 
			
		||||
		var labels = make([]*base.Label, 0, len(issue.Labels))
 | 
			
		||||
		labels := make([]*base.Label, 0, len(issue.Labels))
 | 
			
		||||
		for _, l := range issue.Labels {
 | 
			
		||||
			labels = append(labels, &base.Label{
 | 
			
		||||
				Name: l,
 | 
			
		||||
@@ -402,7 +402,7 @@ func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var reactions []*base.Reaction
 | 
			
		||||
		var awardPage = 1
 | 
			
		||||
		awardPage := 1
 | 
			
		||||
		for {
 | 
			
		||||
			awards, _, err := g.client.AwardEmoji.ListIssueAwardEmoji(g.repoID, issue.IID, &gitlab.ListAwardEmojiOptions{Page: awardPage, PerPage: perPage}, gitlab.WithContext(g.ctx))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
@@ -456,9 +456,9 @@ func (g *GitlabDownloader) GetComments(opts base.GetCommentOptions) ([]*base.Com
 | 
			
		||||
		return nil, false, fmt.Errorf("unexpected context: %+v", opts.Context)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var allComments = make([]*base.Comment, 0, g.maxPerPage)
 | 
			
		||||
	allComments := make([]*base.Comment, 0, g.maxPerPage)
 | 
			
		||||
 | 
			
		||||
	var page = 1
 | 
			
		||||
	page := 1
 | 
			
		||||
 | 
			
		||||
	for {
 | 
			
		||||
		var comments []*gitlab.Discussion
 | 
			
		||||
@@ -503,7 +503,6 @@ func (g *GitlabDownloader) GetComments(opts base.GetCommentOptions) ([]*base.Com
 | 
			
		||||
					Created:     *c.CreatedAt,
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
		if resp.NextPage == 0 {
 | 
			
		||||
			break
 | 
			
		||||
@@ -526,7 +525,7 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var allPRs = make([]*base.PullRequest, 0, perPage)
 | 
			
		||||
	allPRs := make([]*base.PullRequest, 0, perPage)
 | 
			
		||||
 | 
			
		||||
	prs, _, err := g.client.MergeRequests.ListProjectMergeRequests(g.repoID, opt, nil, gitlab.WithContext(g.ctx))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -534,7 +533,7 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque
 | 
			
		||||
	}
 | 
			
		||||
	for _, pr := range prs {
 | 
			
		||||
 | 
			
		||||
		var labels = make([]*base.Label, 0, len(pr.Labels))
 | 
			
		||||
		labels := make([]*base.Label, 0, len(pr.Labels))
 | 
			
		||||
		for _, l := range pr.Labels {
 | 
			
		||||
			labels = append(labels, &base.Label{
 | 
			
		||||
				Name: l,
 | 
			
		||||
@@ -547,12 +546,12 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque
 | 
			
		||||
			pr.State = "closed"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var mergeTime = pr.MergedAt
 | 
			
		||||
		mergeTime := pr.MergedAt
 | 
			
		||||
		if merged && pr.MergedAt == nil {
 | 
			
		||||
			mergeTime = pr.UpdatedAt
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var closeTime = pr.ClosedAt
 | 
			
		||||
		closeTime := pr.ClosedAt
 | 
			
		||||
		if merged && pr.ClosedAt == nil {
 | 
			
		||||
			closeTime = pr.UpdatedAt
 | 
			
		||||
		}
 | 
			
		||||
@@ -568,7 +567,7 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var reactions []*base.Reaction
 | 
			
		||||
		var awardPage = 1
 | 
			
		||||
		awardPage := 1
 | 
			
		||||
		for {
 | 
			
		||||
			awards, _, err := g.client.AwardEmoji.ListMergeRequestAwardEmoji(g.repoID, pr.IID, &gitlab.ListAwardEmojiOptions{Page: awardPage, PerPage: perPage}, gitlab.WithContext(g.ctx))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
@@ -641,13 +640,22 @@ func (g *GitlabDownloader) GetReviews(context base.IssueContext) ([]*base.Review
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var reviews = make([]*base.Review, 0, len(approvals.ApprovedBy))
 | 
			
		||||
	var createdAt time.Time
 | 
			
		||||
	if approvals.CreatedAt != nil {
 | 
			
		||||
		createdAt = *approvals.CreatedAt
 | 
			
		||||
	} else if approvals.UpdatedAt != nil {
 | 
			
		||||
		createdAt = *approvals.UpdatedAt
 | 
			
		||||
	} else {
 | 
			
		||||
		createdAt = time.Now()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	reviews := make([]*base.Review, 0, len(approvals.ApprovedBy))
 | 
			
		||||
	for _, user := range approvals.ApprovedBy {
 | 
			
		||||
		reviews = append(reviews, &base.Review{
 | 
			
		||||
			IssueIndex:   context.LocalID(),
 | 
			
		||||
			ReviewerID:   int64(user.User.ID),
 | 
			
		||||
			ReviewerName: user.User.Username,
 | 
			
		||||
			CreatedAt:    *approvals.UpdatedAt,
 | 
			
		||||
			CreatedAt:    createdAt,
 | 
			
		||||
			// All we get are approvals
 | 
			
		||||
			State: base.ReviewStateApproved,
 | 
			
		||||
		})
 | 
			
		||||
 
 | 
			
		||||
@@ -8,13 +8,16 @@ import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/http/httptest"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	base "code.gitea.io/gitea/modules/migration"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/xanzy/go-gitlab"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestGitlabDownloadRepo(t *testing.T) {
 | 
			
		||||
@@ -310,12 +313,14 @@ func TestGitlabDownloadRepo(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assertReviewsEqual(t, []*base.Review{
 | 
			
		||||
		{
 | 
			
		||||
			IssueIndex:   1,
 | 
			
		||||
			ReviewerID:   4102996,
 | 
			
		||||
			ReviewerName: "zeripath",
 | 
			
		||||
			CreatedAt:    time.Date(2019, 11, 28, 16, 2, 8, 377000000, time.UTC),
 | 
			
		||||
			State:        "APPROVED",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			IssueIndex:   1,
 | 
			
		||||
			ReviewerID:   527793,
 | 
			
		||||
			ReviewerName: "axifive",
 | 
			
		||||
			CreatedAt:    time.Date(2019, 11, 28, 16, 2, 8, 377000000, time.UTC),
 | 
			
		||||
@@ -327,6 +332,7 @@ func TestGitlabDownloadRepo(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assertReviewsEqual(t, []*base.Review{
 | 
			
		||||
		{
 | 
			
		||||
			IssueIndex:   2,
 | 
			
		||||
			ReviewerID:   4575606,
 | 
			
		||||
			ReviewerName: "real6543",
 | 
			
		||||
			CreatedAt:    time.Date(2020, 4, 19, 19, 24, 21, 108000000, time.UTC),
 | 
			
		||||
@@ -334,3 +340,137 @@ func TestGitlabDownloadRepo(t *testing.T) {
 | 
			
		||||
		},
 | 
			
		||||
	}, rvs)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func gitlabClientMockSetup(t *testing.T) (*http.ServeMux, *httptest.Server, *gitlab.Client) {
 | 
			
		||||
	// mux is the HTTP request multiplexer used with the test server.
 | 
			
		||||
	mux := http.NewServeMux()
 | 
			
		||||
 | 
			
		||||
	// server is a test HTTP server used to provide mock API responses.
 | 
			
		||||
	server := httptest.NewServer(mux)
 | 
			
		||||
 | 
			
		||||
	// client is the Gitlab client being tested.
 | 
			
		||||
	client, err := gitlab.NewClient("", gitlab.WithBaseURL(server.URL))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		server.Close()
 | 
			
		||||
		t.Fatalf("Failed to create client: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return mux, server, client
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func gitlabClientMockTeardown(server *httptest.Server) {
 | 
			
		||||
	server.Close()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type reviewTestCase struct {
 | 
			
		||||
	repoID, prID, reviewerID int
 | 
			
		||||
	reviewerName             string
 | 
			
		||||
	createdAt, updatedAt     *time.Time
 | 
			
		||||
	expectedCreatedAt        time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func convertTestCase(t reviewTestCase) (func(w http.ResponseWriter, r *http.Request), base.Review) {
 | 
			
		||||
	var updatedAtField string
 | 
			
		||||
	if t.updatedAt == nil {
 | 
			
		||||
		updatedAtField = ""
 | 
			
		||||
	} else {
 | 
			
		||||
		updatedAtField = `"updated_at": "` + t.updatedAt.Format(time.RFC3339) + `",`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var createdAtField string
 | 
			
		||||
	if t.createdAt == nil {
 | 
			
		||||
		createdAtField = ""
 | 
			
		||||
	} else {
 | 
			
		||||
		createdAtField = `"created_at": "` + t.createdAt.Format(time.RFC3339) + `",`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	handler := func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		fmt.Fprint(w, `
 | 
			
		||||
{
 | 
			
		||||
  "id": 5,
 | 
			
		||||
  "iid": `+strconv.Itoa(t.prID)+`,
 | 
			
		||||
  "project_id": `+strconv.Itoa(t.repoID)+`,
 | 
			
		||||
  "title": "Approvals API",
 | 
			
		||||
  "description": "Test",
 | 
			
		||||
  "state": "opened",
 | 
			
		||||
  `+createdAtField+`
 | 
			
		||||
  `+updatedAtField+`
 | 
			
		||||
  "merge_status": "cannot_be_merged",
 | 
			
		||||
  "approvals_required": 2,
 | 
			
		||||
  "approvals_left": 1,
 | 
			
		||||
  "approved_by": [
 | 
			
		||||
    {
 | 
			
		||||
      "user": {
 | 
			
		||||
        "name": "Administrator",
 | 
			
		||||
        "username": "`+t.reviewerName+`",
 | 
			
		||||
        "id": `+strconv.Itoa(t.reviewerID)+`,
 | 
			
		||||
        "state": "active",
 | 
			
		||||
        "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
 | 
			
		||||
        "web_url": "http://localhost:3000/root"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}`)
 | 
			
		||||
	}
 | 
			
		||||
	review := base.Review{
 | 
			
		||||
		IssueIndex:   int64(t.prID),
 | 
			
		||||
		ReviewerID:   int64(t.reviewerID),
 | 
			
		||||
		ReviewerName: t.reviewerName,
 | 
			
		||||
		CreatedAt:    t.expectedCreatedAt,
 | 
			
		||||
		State:        "APPROVED",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return handler, review
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGitlabGetReviews(t *testing.T) {
 | 
			
		||||
	mux, server, client := gitlabClientMockSetup(t)
 | 
			
		||||
	defer gitlabClientMockTeardown(server)
 | 
			
		||||
 | 
			
		||||
	repoID := 1324
 | 
			
		||||
 | 
			
		||||
	downloader := &GitlabDownloader{
 | 
			
		||||
		ctx:    context.Background(),
 | 
			
		||||
		client: client,
 | 
			
		||||
		repoID: repoID,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	createdAt := time.Date(2020, 4, 19, 19, 24, 21, 0, time.UTC)
 | 
			
		||||
 | 
			
		||||
	for _, testCase := range []reviewTestCase{
 | 
			
		||||
		{
 | 
			
		||||
			repoID:            repoID,
 | 
			
		||||
			prID:              1,
 | 
			
		||||
			reviewerID:        801,
 | 
			
		||||
			reviewerName:      "someone1",
 | 
			
		||||
			createdAt:         nil,
 | 
			
		||||
			updatedAt:         &createdAt,
 | 
			
		||||
			expectedCreatedAt: createdAt,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			repoID:            repoID,
 | 
			
		||||
			prID:              2,
 | 
			
		||||
			reviewerID:        802,
 | 
			
		||||
			reviewerName:      "someone2",
 | 
			
		||||
			createdAt:         &createdAt,
 | 
			
		||||
			updatedAt:         nil,
 | 
			
		||||
			expectedCreatedAt: createdAt,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			repoID:            repoID,
 | 
			
		||||
			prID:              3,
 | 
			
		||||
			reviewerID:        803,
 | 
			
		||||
			reviewerName:      "someone3",
 | 
			
		||||
			createdAt:         nil,
 | 
			
		||||
			updatedAt:         nil,
 | 
			
		||||
			expectedCreatedAt: time.Now(),
 | 
			
		||||
		},
 | 
			
		||||
	} {
 | 
			
		||||
		mock, review := convertTestCase(testCase)
 | 
			
		||||
		mux.HandleFunc(fmt.Sprintf("/api/v4/projects/%d/merge_requests/%d/approvals", testCase.repoID, testCase.prID), mock)
 | 
			
		||||
 | 
			
		||||
		rvs, err := downloader.GetReviews(base.BasicIssueContext(testCase.prID))
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		assertReviewsEqual(t, []*base.Review{&review}, rvs)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -223,15 +223,15 @@ func assertRepositoryEqual(t *testing.T, expected, actual *base.Repository) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func assertReviewEqual(t *testing.T, expected, actual *base.Review) {
 | 
			
		||||
	assert.Equal(t, expected.ID, actual.ID)
 | 
			
		||||
	assert.Equal(t, expected.IssueIndex, actual.IssueIndex)
 | 
			
		||||
	assert.Equal(t, expected.ReviewerID, actual.ReviewerID)
 | 
			
		||||
	assert.Equal(t, expected.ReviewerName, actual.ReviewerName)
 | 
			
		||||
	assert.Equal(t, expected.Official, actual.Official)
 | 
			
		||||
	assert.Equal(t, expected.CommitID, actual.CommitID)
 | 
			
		||||
	assert.Equal(t, expected.Content, actual.Content)
 | 
			
		||||
	assertTimeEqual(t, expected.CreatedAt, actual.CreatedAt)
 | 
			
		||||
	assert.Equal(t, expected.State, actual.State)
 | 
			
		||||
	assert.Equal(t, expected.ID, actual.ID, "ID")
 | 
			
		||||
	assert.Equal(t, expected.IssueIndex, actual.IssueIndex, "IsssueIndex")
 | 
			
		||||
	assert.Equal(t, expected.ReviewerID, actual.ReviewerID, "ReviewerID")
 | 
			
		||||
	assert.Equal(t, expected.ReviewerName, actual.ReviewerName, "ReviewerName")
 | 
			
		||||
	assert.Equal(t, expected.Official, actual.Official, "Official")
 | 
			
		||||
	assert.Equal(t, expected.CommitID, actual.CommitID, "CommitID")
 | 
			
		||||
	assert.Equal(t, expected.Content, actual.Content, "Content")
 | 
			
		||||
	assert.WithinDuration(t, expected.CreatedAt, actual.CreatedAt, 10*time.Second)
 | 
			
		||||
	assert.Equal(t, expected.State, actual.State, "State")
 | 
			
		||||
	assertReviewCommentsEqual(t, expected.Comments, actual.Comments)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -97,6 +97,9 @@ func (r *RepositoryRestorer) GetTopics() ([]string, error) {
 | 
			
		||||
 | 
			
		||||
	bs, err := os.ReadFile(p)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if os.IsNotExist(err) {
 | 
			
		||||
			return nil, nil
 | 
			
		||||
		}
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -286,6 +286,10 @@
 | 
			
		||||
							<input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if $cfg.SkipLocalTwoFA}}checked{{end}}>
 | 
			
		||||
							<p class="help">{{.i18n.Tr "admin.auths.skip_local_two_fa_helper"}}</p>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="oauth2_use_custom_url inline field">
 | 
			
		||||
						<div class="ui checkbox">
 | 
			
		||||
							<label><strong>{{.i18n.Tr "admin.auths.oauth2_use_custom_url"}}</strong></label>
 | 
			
		||||
							<input id="oauth2_use_custom_url" name="oauth2_use_custom_url" type="checkbox" {{if $cfg.CustomURLMapping}}checked{{end}}>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -75,7 +75,11 @@
 | 
			
		||||
							<pre class="commit-body" style="display: none;">{{RenderCommitBody .Message $commitRepoLink $.Repository.ComposeMetas}}</pre>
 | 
			
		||||
							{{end}}
 | 
			
		||||
						</td>
 | 
			
		||||
						<td class="text right aligned">{{TimeSince .Author.When $.Lang}}</td>
 | 
			
		||||
						{{if .Committer}}
 | 
			
		||||
							<td class="text right aligned">{{TimeSince .Committer.When $.Lang}}</td>
 | 
			
		||||
						{{else}}
 | 
			
		||||
							<td class="text right aligned">{{TimeSince .Author.When $.Lang}}</td>
 | 
			
		||||
						{{end}}
 | 
			
		||||
					</tr>
 | 
			
		||||
				{{end}}
 | 
			
		||||
			</tbody>
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@
 | 
			
		||||
					</span>
 | 
			
		||||
				{{end}}
 | 
			
		||||
			</th>
 | 
			
		||||
			<th class="text grey right age">{{if .LatestCommit}}{{if .LatestCommit.Author}}{{TimeSince .LatestCommit.Author.When $.Lang}}{{end}}{{end}}</th>
 | 
			
		||||
			<th class="text grey right age">{{if .LatestCommit}}{{if .LatestCommit.Committer}}{{TimeSince .LatestCommit.Committer.When $.Lang}}{{end}}{{end}}</th>
 | 
			
		||||
		</tr>
 | 
			
		||||
	</thead>
 | 
			
		||||
	<tbody>
 | 
			
		||||
 
 | 
			
		||||
@@ -496,9 +496,11 @@ export function initRepoPullRequestReview() {
 | 
			
		||||
        <tr class="add-comment" data-line-type="${lineType}">
 | 
			
		||||
          ${isSplit ? `
 | 
			
		||||
            <td class="lines-num"></td>
 | 
			
		||||
            <td class="lines-escape"></td>
 | 
			
		||||
            <td class="lines-type-marker"></td>
 | 
			
		||||
            <td class="add-comment-left"></td>
 | 
			
		||||
            <td class="lines-num"></td>
 | 
			
		||||
            <td class="lines-escape"></td>
 | 
			
		||||
            <td class="lines-type-marker"></td>
 | 
			
		||||
            <td class="add-comment-right"></td>
 | 
			
		||||
          ` : `
 | 
			
		||||
 
 | 
			
		||||
@@ -150,13 +150,12 @@ export function initUserAuthWebAuthnRegister() {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!detectWebAuthnSupport()) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  $('#webauthn-error').modal({allowMultiple: false});
 | 
			
		||||
  $('#register-webauthn').on('click', (e) => {
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
    if (!detectWebAuthnSupport()) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    webAuthnRegisterRequest();
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user