mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-04 05:25:15 +01:00 
			
		
		
		
	Refactor "change file" API (#34855)
Follow up the "editor" refactor, use the same approach to simplify code, and fix some docs & comments --------- Signed-off-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: delvh <dev.lh@web.de>
This commit is contained in:
		
							parent
							
								
									1839110ea6
								
							
						
					
					
						commit
						75aa23a665
					
				@ -22,6 +22,23 @@ type FileOptions struct {
 | 
				
			|||||||
	Signoff bool `json:"signoff"`
 | 
						Signoff bool `json:"signoff"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type FileOptionsWithSHA struct {
 | 
				
			||||||
 | 
						FileOptions
 | 
				
			||||||
 | 
						// the blob ID (SHA) for the file that already exists, it is required for changing existing files
 | 
				
			||||||
 | 
						// required: true
 | 
				
			||||||
 | 
						SHA string `json:"sha" binding:"Required"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (f *FileOptions) GetFileOptions() *FileOptions {
 | 
				
			||||||
 | 
						return f
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type FileOptionsInterface interface {
 | 
				
			||||||
 | 
						GetFileOptions() *FileOptions
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var _ FileOptionsInterface = (*FileOptions)(nil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CreateFileOptions options for creating files
 | 
					// CreateFileOptions options for creating files
 | 
				
			||||||
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
 | 
					// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
 | 
				
			||||||
type CreateFileOptions struct {
 | 
					type CreateFileOptions struct {
 | 
				
			||||||
@ -31,29 +48,16 @@ type CreateFileOptions struct {
 | 
				
			|||||||
	ContentBase64 string `json:"content"`
 | 
						ContentBase64 string `json:"content"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Branch returns branch name
 | 
					 | 
				
			||||||
func (o *CreateFileOptions) Branch() string {
 | 
					 | 
				
			||||||
	return o.FileOptions.BranchName
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// DeleteFileOptions options for deleting files (used for other File structs below)
 | 
					// DeleteFileOptions options for deleting files (used for other File structs below)
 | 
				
			||||||
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
 | 
					// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
 | 
				
			||||||
type DeleteFileOptions struct {
 | 
					type DeleteFileOptions struct {
 | 
				
			||||||
	FileOptions
 | 
						FileOptionsWithSHA
 | 
				
			||||||
	// sha is the SHA for the file that already exists
 | 
					 | 
				
			||||||
	// required: true
 | 
					 | 
				
			||||||
	SHA string `json:"sha" binding:"Required"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Branch returns branch name
 | 
					 | 
				
			||||||
func (o *DeleteFileOptions) Branch() string {
 | 
					 | 
				
			||||||
	return o.FileOptions.BranchName
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// UpdateFileOptions options for updating files
 | 
					// UpdateFileOptions options for updating files
 | 
				
			||||||
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
 | 
					// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
 | 
				
			||||||
type UpdateFileOptions struct {
 | 
					type UpdateFileOptions struct {
 | 
				
			||||||
	DeleteFileOptions
 | 
						FileOptionsWithSHA
 | 
				
			||||||
	// content must be base64 encoded
 | 
						// content must be base64 encoded
 | 
				
			||||||
	// required: true
 | 
						// required: true
 | 
				
			||||||
	ContentBase64 string `json:"content"`
 | 
						ContentBase64 string `json:"content"`
 | 
				
			||||||
@ -61,25 +65,21 @@ type UpdateFileOptions struct {
 | 
				
			|||||||
	FromPath string `json:"from_path" binding:"MaxSize(500)"`
 | 
						FromPath string `json:"from_path" binding:"MaxSize(500)"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Branch returns branch name
 | 
					// FIXME: there is no LastCommitID in FileOptions, actually it should be an alternative to the SHA in ChangeFileOperation
 | 
				
			||||||
func (o *UpdateFileOptions) Branch() string {
 | 
					 | 
				
			||||||
	return o.FileOptions.BranchName
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// FIXME: ChangeFileOperation.SHA is NOT required for update or delete if last commit is provided in the options.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ChangeFileOperation for creating, updating or deleting a file
 | 
					// ChangeFileOperation for creating, updating or deleting a file
 | 
				
			||||||
type ChangeFileOperation struct {
 | 
					type ChangeFileOperation struct {
 | 
				
			||||||
	// indicates what to do with the file
 | 
						// indicates what to do with the file: "create" for creating a new file, "update" for updating an existing file,
 | 
				
			||||||
 | 
						// "upload" for creating or updating a file, "rename" for renaming a file, and "delete" for deleting an existing file.
 | 
				
			||||||
	// required: true
 | 
						// required: true
 | 
				
			||||||
	// enum: create,update,delete
 | 
						// enum: create,update,upload,rename,delete
 | 
				
			||||||
	Operation string `json:"operation" binding:"Required"`
 | 
						Operation string `json:"operation" binding:"Required"`
 | 
				
			||||||
	// path to the existing or new file
 | 
						// path to the existing or new file
 | 
				
			||||||
	// required: true
 | 
						// required: true
 | 
				
			||||||
	Path string `json:"path" binding:"Required;MaxSize(500)"`
 | 
						Path string `json:"path" binding:"Required;MaxSize(500)"`
 | 
				
			||||||
	// new or updated file content, must be base64 encoded
 | 
						// new or updated file content, it must be base64 encoded
 | 
				
			||||||
	ContentBase64 string `json:"content"`
 | 
						ContentBase64 string `json:"content"`
 | 
				
			||||||
	// sha is the SHA for the file that already exists, required for update or delete
 | 
						// the blob ID (SHA) for the file that already exists, required for changing existing files
 | 
				
			||||||
	SHA string `json:"sha"`
 | 
						SHA string `json:"sha"`
 | 
				
			||||||
	// old path of the file to move
 | 
						// old path of the file to move
 | 
				
			||||||
	FromPath string `json:"from_path"`
 | 
						FromPath string `json:"from_path"`
 | 
				
			||||||
@ -94,20 +94,10 @@ type ChangeFilesOptions struct {
 | 
				
			|||||||
	Files []*ChangeFileOperation `json:"files" binding:"Required"`
 | 
						Files []*ChangeFileOperation `json:"files" binding:"Required"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Branch returns branch name
 | 
					 | 
				
			||||||
func (o *ChangeFilesOptions) Branch() string {
 | 
					 | 
				
			||||||
	return o.FileOptions.BranchName
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// FileOptionInterface provides a unified interface for the different file options
 | 
					 | 
				
			||||||
type FileOptionInterface interface {
 | 
					 | 
				
			||||||
	Branch() string
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ApplyDiffPatchFileOptions options for applying a diff patch
 | 
					// ApplyDiffPatchFileOptions options for applying a diff patch
 | 
				
			||||||
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
 | 
					// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
 | 
				
			||||||
type ApplyDiffPatchFileOptions struct {
 | 
					type ApplyDiffPatchFileOptions struct {
 | 
				
			||||||
	DeleteFileOptions
 | 
						FileOptions
 | 
				
			||||||
	// required: true
 | 
						// required: true
 | 
				
			||||||
	Content string `json:"content"`
 | 
						Content string `json:"content"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -455,15 +455,6 @@ func reqRepoWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// reqRepoBranchWriter user should have a permission to write to a branch, or be a site admin
 | 
					 | 
				
			||||||
func reqRepoBranchWriter(ctx *context.APIContext) {
 | 
					 | 
				
			||||||
	options, ok := web.GetForm(ctx).(api.FileOptionInterface)
 | 
					 | 
				
			||||||
	if !ok || (!ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, options.Branch()) && !ctx.IsUserSiteAdmin()) {
 | 
					 | 
				
			||||||
		ctx.APIError(http.StatusForbidden, "user should have a permission to write to this branch")
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// reqRepoReader user should have specific read permission or be a repo admin or a site admin
 | 
					// reqRepoReader user should have specific read permission or be a repo admin or a site admin
 | 
				
			||||||
func reqRepoReader(unitType unit.Type) func(ctx *context.APIContext) {
 | 
					func reqRepoReader(unitType unit.Type) func(ctx *context.APIContext) {
 | 
				
			||||||
	return func(ctx *context.APIContext) {
 | 
						return func(ctx *context.APIContext) {
 | 
				
			||||||
@ -744,9 +735,17 @@ func mustEnableWiki(ctx *context.APIContext) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FIXME: for consistency, maybe most mustNotBeArchived checks should be replaced with mustEnableEditor
 | 
				
			||||||
func mustNotBeArchived(ctx *context.APIContext) {
 | 
					func mustNotBeArchived(ctx *context.APIContext) {
 | 
				
			||||||
	if ctx.Repo.Repository.IsArchived {
 | 
						if ctx.Repo.Repository.IsArchived {
 | 
				
			||||||
		ctx.APIError(http.StatusLocked, fmt.Errorf("%s is archived", ctx.Repo.Repository.LogString()))
 | 
							ctx.APIError(http.StatusLocked, fmt.Errorf("%s is archived", ctx.Repo.Repository.FullName()))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func mustEnableEditor(ctx *context.APIContext) {
 | 
				
			||||||
 | 
						if !ctx.Repo.Repository.CanEnableEditor() {
 | 
				
			||||||
 | 
							ctx.APIError(http.StatusLocked, fmt.Errorf("%s is not allowed to edit", ctx.Repo.Repository.FullName()))
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -1424,16 +1423,19 @@ func Routes() *web.Router {
 | 
				
			|||||||
					m.Get("/tags/{sha}", repo.GetAnnotatedTag)
 | 
										m.Get("/tags/{sha}", repo.GetAnnotatedTag)
 | 
				
			||||||
					m.Get("/notes/{sha}", repo.GetNote)
 | 
										m.Get("/notes/{sha}", repo.GetNote)
 | 
				
			||||||
				}, context.ReferencesGitRepo(true), reqRepoReader(unit.TypeCode))
 | 
									}, context.ReferencesGitRepo(true), reqRepoReader(unit.TypeCode))
 | 
				
			||||||
				m.Post("/diffpatch", reqRepoWriter(unit.TypeCode), reqToken(), bind(api.ApplyDiffPatchFileOptions{}), mustNotBeArchived, repo.ApplyDiffPatch)
 | 
					 | 
				
			||||||
				m.Group("/contents", func() {
 | 
									m.Group("/contents", func() {
 | 
				
			||||||
					m.Get("", repo.GetContentsList)
 | 
										m.Get("", repo.GetContentsList)
 | 
				
			||||||
					m.Get("/*", repo.GetContents)
 | 
										m.Get("/*", repo.GetContents)
 | 
				
			||||||
					m.Post("", reqToken(), bind(api.ChangeFilesOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.ChangeFiles)
 | 
										m.Group("", func() {
 | 
				
			||||||
 | 
											// "change file" operations, need permission to write to the target branch provided by the form
 | 
				
			||||||
 | 
											m.Post("", bind(api.ChangeFilesOptions{}), repo.ReqChangeRepoFileOptionsAndCheck, repo.ChangeFiles)
 | 
				
			||||||
						m.Group("/*", func() {
 | 
											m.Group("/*", func() {
 | 
				
			||||||
						m.Post("", bind(api.CreateFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.CreateFile)
 | 
												m.Post("", bind(api.CreateFileOptions{}), repo.ReqChangeRepoFileOptionsAndCheck, repo.CreateFile)
 | 
				
			||||||
						m.Put("", bind(api.UpdateFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.UpdateFile)
 | 
												m.Put("", bind(api.UpdateFileOptions{}), repo.ReqChangeRepoFileOptionsAndCheck, repo.UpdateFile)
 | 
				
			||||||
						m.Delete("", bind(api.DeleteFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.DeleteFile)
 | 
												m.Delete("", bind(api.DeleteFileOptions{}), repo.ReqChangeRepoFileOptionsAndCheck, repo.DeleteFile)
 | 
				
			||||||
					}, reqToken())
 | 
											})
 | 
				
			||||||
 | 
											m.Post("/diffpatch", bind(api.ApplyDiffPatchFileOptions{}), repo.ReqChangeRepoFileOptionsAndCheck, repo.ApplyDiffPatch)
 | 
				
			||||||
 | 
										}, mustEnableEditor, reqToken())
 | 
				
			||||||
				}, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo())
 | 
									}, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo())
 | 
				
			||||||
				m.Group("/contents-ext", func() {
 | 
									m.Group("/contents-ext", func() {
 | 
				
			||||||
					m.Get("", repo.GetContentsExt)
 | 
										m.Get("", repo.GetContentsExt)
 | 
				
			||||||
@ -1441,7 +1443,7 @@ func Routes() *web.Router {
 | 
				
			|||||||
				}, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo())
 | 
									}, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo())
 | 
				
			||||||
				m.Combo("/file-contents", reqRepoReader(unit.TypeCode), context.ReferencesGitRepo()).
 | 
									m.Combo("/file-contents", reqRepoReader(unit.TypeCode), context.ReferencesGitRepo()).
 | 
				
			||||||
					Get(repo.GetFileContentsGet).
 | 
										Get(repo.GetFileContentsGet).
 | 
				
			||||||
					Post(bind(api.GetFilesOptions{}), repo.GetFileContentsPost) // POST method requires "write" permission, so we also support "GET" method above
 | 
										Post(bind(api.GetFilesOptions{}), repo.GetFileContentsPost) // the POST method requires "write" permission, so we also support "GET" method above
 | 
				
			||||||
				m.Get("/signing-key.gpg", misc.SigningKeyGPG)
 | 
									m.Get("/signing-key.gpg", misc.SigningKeyGPG)
 | 
				
			||||||
				m.Get("/signing-key.pub", misc.SigningKeySSH)
 | 
									m.Get("/signing-key.pub", misc.SigningKeySSH)
 | 
				
			||||||
				m.Group("/topics", func() {
 | 
									m.Group("/topics", func() {
 | 
				
			||||||
 | 
				
			|||||||
@ -62,7 +62,7 @@ func GetRawFile(ctx *context.APIContext) {
 | 
				
			|||||||
	//   required: true
 | 
						//   required: true
 | 
				
			||||||
	// - name: ref
 | 
						// - name: ref
 | 
				
			||||||
	//   in: query
 | 
						//   in: query
 | 
				
			||||||
	//   description: "The name of the commit/branch/tag. Default the repository’s default branch"
 | 
						//   description: "The name of the commit/branch/tag. Default to the repository’s default branch"
 | 
				
			||||||
	//   type: string
 | 
						//   type: string
 | 
				
			||||||
	//   required: false
 | 
						//   required: false
 | 
				
			||||||
	// responses:
 | 
						// responses:
 | 
				
			||||||
@ -115,7 +115,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
 | 
				
			|||||||
	//   required: true
 | 
						//   required: true
 | 
				
			||||||
	// - name: ref
 | 
						// - name: ref
 | 
				
			||||||
	//   in: query
 | 
						//   in: query
 | 
				
			||||||
	//   description: "The name of the commit/branch/tag. Default the repository’s default branch"
 | 
						//   description: "The name of the commit/branch/tag. Default to the repository’s default branch"
 | 
				
			||||||
	//   type: string
 | 
						//   type: string
 | 
				
			||||||
	//   required: false
 | 
						//   required: false
 | 
				
			||||||
	// responses:
 | 
						// responses:
 | 
				
			||||||
@ -139,27 +139,27 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
 | 
				
			|||||||
	ctx.RespHeader().Set(giteaObjectTypeHeader, string(files_service.GetObjectTypeFromTreeEntry(entry)))
 | 
						ctx.RespHeader().Set(giteaObjectTypeHeader, string(files_service.GetObjectTypeFromTreeEntry(entry)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// LFS Pointer files are at most 1024 bytes - so any blob greater than 1024 bytes cannot be an LFS file
 | 
						// LFS Pointer files are at most 1024 bytes - so any blob greater than 1024 bytes cannot be an LFS file
 | 
				
			||||||
	if blob.Size() > 1024 {
 | 
						if blob.Size() > lfs.MetaFileMaxSize {
 | 
				
			||||||
		// First handle caching for the blob
 | 
							// First handle caching for the blob
 | 
				
			||||||
		if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) {
 | 
							if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) {
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// OK not cached - serve!
 | 
							// If not cached - serve!
 | 
				
			||||||
		if err := common.ServeBlob(ctx.Base, ctx.Repo.Repository, ctx.Repo.TreePath, blob, lastModified); err != nil {
 | 
							if err := common.ServeBlob(ctx.Base, ctx.Repo.Repository, ctx.Repo.TreePath, blob, lastModified); err != nil {
 | 
				
			||||||
			ctx.APIErrorInternal(err)
 | 
								ctx.APIErrorInternal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// OK, now the blob is known to have at most 1024 bytes we can simply read this in one go (This saves reading it twice)
 | 
						// OK, now the blob is known to have at most 1024 (lfs pointer max size) bytes,
 | 
				
			||||||
 | 
						// we can simply read this in one go (This saves reading it twice)
 | 
				
			||||||
	dataRc, err := blob.DataAsync()
 | 
						dataRc, err := blob.DataAsync()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		ctx.APIErrorInternal(err)
 | 
							ctx.APIErrorInternal(err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// FIXME: code from #19689, what if the file is large ... OOM ...
 | 
					 | 
				
			||||||
	buf, err := io.ReadAll(dataRc)
 | 
						buf, err := io.ReadAll(dataRc)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		_ = dataRc.Close()
 | 
							_ = dataRc.Close()
 | 
				
			||||||
@ -181,7 +181,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
 | 
				
			|||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// OK not cached - serve!
 | 
							// If not cached - serve!
 | 
				
			||||||
		common.ServeContentByReader(ctx.Base, ctx.Repo.TreePath, blob.Size(), bytes.NewReader(buf))
 | 
							common.ServeContentByReader(ctx.Base, ctx.Repo.TreePath, blob.Size(), bytes.NewReader(buf))
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -405,13 +405,6 @@ func GetEditorconfig(ctx *context.APIContext) {
 | 
				
			|||||||
	ctx.JSON(http.StatusOK, def)
 | 
						ctx.JSON(http.StatusOK, def)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// canWriteFiles returns true if repository is editable and user has proper access level.
 | 
					 | 
				
			||||||
func canWriteFiles(ctx *context.APIContext, branch string) bool {
 | 
					 | 
				
			||||||
	return ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, branch) &&
 | 
					 | 
				
			||||||
		!ctx.Repo.Repository.IsMirror &&
 | 
					 | 
				
			||||||
		!ctx.Repo.Repository.IsArchived
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func base64Reader(s string) (io.ReadSeeker, error) {
 | 
					func base64Reader(s string) (io.ReadSeeker, error) {
 | 
				
			||||||
	b, err := base64.StdEncoding.DecodeString(s)
 | 
						b, err := base64.StdEncoding.DecodeString(s)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@ -420,6 +413,45 @@ func base64Reader(s string) (io.ReadSeeker, error) {
 | 
				
			|||||||
	return bytes.NewReader(b), nil
 | 
						return bytes.NewReader(b), nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ReqChangeRepoFileOptionsAndCheck(ctx *context.APIContext) {
 | 
				
			||||||
 | 
						commonOpts := web.GetForm(ctx).(api.FileOptionsInterface).GetFileOptions()
 | 
				
			||||||
 | 
						commonOpts.BranchName = util.IfZero(commonOpts.BranchName, ctx.Repo.Repository.DefaultBranch)
 | 
				
			||||||
 | 
						commonOpts.NewBranchName = util.IfZero(commonOpts.NewBranchName, commonOpts.BranchName)
 | 
				
			||||||
 | 
						if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, commonOpts.NewBranchName) && !ctx.IsUserSiteAdmin() {
 | 
				
			||||||
 | 
							ctx.APIError(http.StatusForbidden, "user should have a permission to write to the target branch")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						changeFileOpts := &files_service.ChangeRepoFilesOptions{
 | 
				
			||||||
 | 
							Message:   commonOpts.Message,
 | 
				
			||||||
 | 
							OldBranch: commonOpts.BranchName,
 | 
				
			||||||
 | 
							NewBranch: commonOpts.NewBranchName,
 | 
				
			||||||
 | 
							Committer: &files_service.IdentityOptions{
 | 
				
			||||||
 | 
								GitUserName:  commonOpts.Committer.Name,
 | 
				
			||||||
 | 
								GitUserEmail: commonOpts.Committer.Email,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Author: &files_service.IdentityOptions{
 | 
				
			||||||
 | 
								GitUserName:  commonOpts.Author.Name,
 | 
				
			||||||
 | 
								GitUserEmail: commonOpts.Author.Email,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Dates: &files_service.CommitDateOptions{
 | 
				
			||||||
 | 
								Author:    commonOpts.Dates.Author,
 | 
				
			||||||
 | 
								Committer: commonOpts.Dates.Committer,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Signoff: commonOpts.Signoff,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if commonOpts.Dates.Author.IsZero() {
 | 
				
			||||||
 | 
							commonOpts.Dates.Author = time.Now()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if commonOpts.Dates.Committer.IsZero() {
 | 
				
			||||||
 | 
							commonOpts.Dates.Committer = time.Now()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ctx.Data["__APIChangeRepoFilesOptions"] = changeFileOpts
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getAPIChangeRepoFileOptions[T api.FileOptionsInterface](ctx *context.APIContext) (apiOpts T, opts *files_service.ChangeRepoFilesOptions) {
 | 
				
			||||||
 | 
						return web.GetForm(ctx).(T), ctx.Data["__APIChangeRepoFilesOptions"].(*files_service.ChangeRepoFilesOptions)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ChangeFiles handles API call for modifying multiple files
 | 
					// ChangeFiles handles API call for modifying multiple files
 | 
				
			||||||
func ChangeFiles(ctx *context.APIContext) {
 | 
					func ChangeFiles(ctx *context.APIContext) {
 | 
				
			||||||
	// swagger:operation POST /repos/{owner}/{repo}/contents repository repoChangeFiles
 | 
						// swagger:operation POST /repos/{owner}/{repo}/contents repository repoChangeFiles
 | 
				
			||||||
@ -456,23 +488,18 @@ func ChangeFiles(ctx *context.APIContext) {
 | 
				
			|||||||
	//     "$ref": "#/responses/error"
 | 
						//     "$ref": "#/responses/error"
 | 
				
			||||||
	//   "423":
 | 
						//   "423":
 | 
				
			||||||
	//     "$ref": "#/responses/repoArchivedError"
 | 
						//     "$ref": "#/responses/repoArchivedError"
 | 
				
			||||||
 | 
						apiOpts, opts := getAPIChangeRepoFileOptions[*api.ChangeFilesOptions](ctx)
 | 
				
			||||||
	apiOpts := web.GetForm(ctx).(*api.ChangeFilesOptions)
 | 
						if ctx.Written() {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
	if apiOpts.BranchName == "" {
 | 
					 | 
				
			||||||
		apiOpts.BranchName = ctx.Repo.Repository.DefaultBranch
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	var files []*files_service.ChangeRepoFile
 | 
					 | 
				
			||||||
	for _, file := range apiOpts.Files {
 | 
						for _, file := range apiOpts.Files {
 | 
				
			||||||
		contentReader, err := base64Reader(file.ContentBase64)
 | 
							contentReader, err := base64Reader(file.ContentBase64)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			ctx.APIError(http.StatusUnprocessableEntity, err)
 | 
								ctx.APIError(http.StatusUnprocessableEntity, err)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		// FIXME: actually now we support more operations like "rename", "upload"
 | 
							// FIXME: ChangeFileOperation.SHA is NOT required for update or delete if last commit is provided in the options
 | 
				
			||||||
		// FIXME: ChangeFileOperation.SHA is NOT required for update or delete if last commit is provided in the options.
 | 
							// But the LastCommitID is not provided in the API options, need to fully fix them in API
 | 
				
			||||||
		// Need to fully fix them in API
 | 
					 | 
				
			||||||
		changeRepoFile := &files_service.ChangeRepoFile{
 | 
							changeRepoFile := &files_service.ChangeRepoFile{
 | 
				
			||||||
			Operation:     file.Operation,
 | 
								Operation:     file.Operation,
 | 
				
			||||||
			TreePath:      file.Path,
 | 
								TreePath:      file.Path,
 | 
				
			||||||
@ -480,41 +507,15 @@ func ChangeFiles(ctx *context.APIContext) {
 | 
				
			|||||||
			ContentReader: contentReader,
 | 
								ContentReader: contentReader,
 | 
				
			||||||
			SHA:           file.SHA,
 | 
								SHA:           file.SHA,
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		files = append(files, changeRepoFile)
 | 
							opts.Files = append(opts.Files, changeRepoFile)
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	opts := &files_service.ChangeRepoFilesOptions{
 | 
					 | 
				
			||||||
		Files:     files,
 | 
					 | 
				
			||||||
		Message:   apiOpts.Message,
 | 
					 | 
				
			||||||
		OldBranch: apiOpts.BranchName,
 | 
					 | 
				
			||||||
		NewBranch: apiOpts.NewBranchName,
 | 
					 | 
				
			||||||
		Committer: &files_service.IdentityOptions{
 | 
					 | 
				
			||||||
			GitUserName:  apiOpts.Committer.Name,
 | 
					 | 
				
			||||||
			GitUserEmail: apiOpts.Committer.Email,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		Author: &files_service.IdentityOptions{
 | 
					 | 
				
			||||||
			GitUserName:  apiOpts.Author.Name,
 | 
					 | 
				
			||||||
			GitUserEmail: apiOpts.Author.Email,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		Dates: &files_service.CommitDateOptions{
 | 
					 | 
				
			||||||
			Author:    apiOpts.Dates.Author,
 | 
					 | 
				
			||||||
			Committer: apiOpts.Dates.Committer,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		Signoff: apiOpts.Signoff,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if opts.Dates.Author.IsZero() {
 | 
					 | 
				
			||||||
		opts.Dates.Author = time.Now()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if opts.Dates.Committer.IsZero() {
 | 
					 | 
				
			||||||
		opts.Dates.Committer = time.Now()
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if opts.Message == "" {
 | 
						if opts.Message == "" {
 | 
				
			||||||
		opts.Message = changeFilesCommitMessage(ctx, files)
 | 
							opts.Message = changeFilesCommitMessage(ctx, opts.Files)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if filesResponse, err := createOrUpdateFiles(ctx, opts); err != nil {
 | 
						if filesResponse, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, opts); err != nil {
 | 
				
			||||||
		handleCreateOrUpdateFileError(ctx, err)
 | 
							handleChangeRepoFilesError(ctx, err)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		ctx.JSON(http.StatusCreated, filesResponse)
 | 
							ctx.JSON(http.StatusCreated, filesResponse)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -562,56 +563,27 @@ func CreateFile(ctx *context.APIContext) {
 | 
				
			|||||||
	//   "423":
 | 
						//   "423":
 | 
				
			||||||
	//     "$ref": "#/responses/repoArchivedError"
 | 
						//     "$ref": "#/responses/repoArchivedError"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	apiOpts := web.GetForm(ctx).(*api.CreateFileOptions)
 | 
						apiOpts, opts := getAPIChangeRepoFileOptions[*api.CreateFileOptions](ctx)
 | 
				
			||||||
 | 
						if ctx.Written() {
 | 
				
			||||||
	if apiOpts.BranchName == "" {
 | 
							return
 | 
				
			||||||
		apiOpts.BranchName = ctx.Repo.Repository.DefaultBranch
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	contentReader, err := base64Reader(apiOpts.ContentBase64)
 | 
						contentReader, err := base64Reader(apiOpts.ContentBase64)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		ctx.APIError(http.StatusUnprocessableEntity, err)
 | 
							ctx.APIError(http.StatusUnprocessableEntity, err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	opts := &files_service.ChangeRepoFilesOptions{
 | 
						opts.Files = append(opts.Files, &files_service.ChangeRepoFile{
 | 
				
			||||||
		Files: []*files_service.ChangeRepoFile{
 | 
					 | 
				
			||||||
			{
 | 
					 | 
				
			||||||
		Operation:     "create",
 | 
							Operation:     "create",
 | 
				
			||||||
		TreePath:      ctx.PathParam("*"),
 | 
							TreePath:      ctx.PathParam("*"),
 | 
				
			||||||
		ContentReader: contentReader,
 | 
							ContentReader: contentReader,
 | 
				
			||||||
			},
 | 
						})
 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		Message:   apiOpts.Message,
 | 
					 | 
				
			||||||
		OldBranch: apiOpts.BranchName,
 | 
					 | 
				
			||||||
		NewBranch: apiOpts.NewBranchName,
 | 
					 | 
				
			||||||
		Committer: &files_service.IdentityOptions{
 | 
					 | 
				
			||||||
			GitUserName:  apiOpts.Committer.Name,
 | 
					 | 
				
			||||||
			GitUserEmail: apiOpts.Committer.Email,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		Author: &files_service.IdentityOptions{
 | 
					 | 
				
			||||||
			GitUserName:  apiOpts.Author.Name,
 | 
					 | 
				
			||||||
			GitUserEmail: apiOpts.Author.Email,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		Dates: &files_service.CommitDateOptions{
 | 
					 | 
				
			||||||
			Author:    apiOpts.Dates.Author,
 | 
					 | 
				
			||||||
			Committer: apiOpts.Dates.Committer,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		Signoff: apiOpts.Signoff,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if opts.Dates.Author.IsZero() {
 | 
					 | 
				
			||||||
		opts.Dates.Author = time.Now()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if opts.Dates.Committer.IsZero() {
 | 
					 | 
				
			||||||
		opts.Dates.Committer = time.Now()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if opts.Message == "" {
 | 
						if opts.Message == "" {
 | 
				
			||||||
		opts.Message = changeFilesCommitMessage(ctx, opts.Files)
 | 
							opts.Message = changeFilesCommitMessage(ctx, opts.Files)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if filesResponse, err := createOrUpdateFiles(ctx, opts); err != nil {
 | 
						if filesResponse, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, opts); err != nil {
 | 
				
			||||||
		handleCreateOrUpdateFileError(ctx, err)
 | 
							handleChangeRepoFilesError(ctx, err)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		fileResponse := files_service.GetFileResponseFromFilesResponse(filesResponse, 0)
 | 
							fileResponse := files_service.GetFileResponseFromFilesResponse(filesResponse, 0)
 | 
				
			||||||
		ctx.JSON(http.StatusCreated, fileResponse)
 | 
							ctx.JSON(http.StatusCreated, fileResponse)
 | 
				
			||||||
@ -659,98 +631,57 @@ func UpdateFile(ctx *context.APIContext) {
 | 
				
			|||||||
	//     "$ref": "#/responses/error"
 | 
						//     "$ref": "#/responses/error"
 | 
				
			||||||
	//   "423":
 | 
						//   "423":
 | 
				
			||||||
	//     "$ref": "#/responses/repoArchivedError"
 | 
						//     "$ref": "#/responses/repoArchivedError"
 | 
				
			||||||
	apiOpts := web.GetForm(ctx).(*api.UpdateFileOptions)
 | 
					
 | 
				
			||||||
	if ctx.Repo.Repository.IsEmpty {
 | 
						apiOpts, opts := getAPIChangeRepoFileOptions[*api.UpdateFileOptions](ctx)
 | 
				
			||||||
		ctx.APIError(http.StatusUnprocessableEntity, errors.New("repo is empty"))
 | 
						if ctx.Written() {
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	if apiOpts.BranchName == "" {
 | 
					 | 
				
			||||||
		apiOpts.BranchName = ctx.Repo.Repository.DefaultBranch
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	contentReader, err := base64Reader(apiOpts.ContentBase64)
 | 
						contentReader, err := base64Reader(apiOpts.ContentBase64)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		ctx.APIError(http.StatusUnprocessableEntity, err)
 | 
							ctx.APIError(http.StatusUnprocessableEntity, err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						opts.Files = append(opts.Files, &files_service.ChangeRepoFile{
 | 
				
			||||||
	opts := &files_service.ChangeRepoFilesOptions{
 | 
					 | 
				
			||||||
		Files: []*files_service.ChangeRepoFile{
 | 
					 | 
				
			||||||
			{
 | 
					 | 
				
			||||||
		Operation:     "update",
 | 
							Operation:     "update",
 | 
				
			||||||
		ContentReader: contentReader,
 | 
							ContentReader: contentReader,
 | 
				
			||||||
		SHA:           apiOpts.SHA,
 | 
							SHA:           apiOpts.SHA,
 | 
				
			||||||
		FromTreePath:  apiOpts.FromPath,
 | 
							FromTreePath:  apiOpts.FromPath,
 | 
				
			||||||
		TreePath:      ctx.PathParam("*"),
 | 
							TreePath:      ctx.PathParam("*"),
 | 
				
			||||||
			},
 | 
						})
 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		Message:   apiOpts.Message,
 | 
					 | 
				
			||||||
		OldBranch: apiOpts.BranchName,
 | 
					 | 
				
			||||||
		NewBranch: apiOpts.NewBranchName,
 | 
					 | 
				
			||||||
		Committer: &files_service.IdentityOptions{
 | 
					 | 
				
			||||||
			GitUserName:  apiOpts.Committer.Name,
 | 
					 | 
				
			||||||
			GitUserEmail: apiOpts.Committer.Email,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		Author: &files_service.IdentityOptions{
 | 
					 | 
				
			||||||
			GitUserName:  apiOpts.Author.Name,
 | 
					 | 
				
			||||||
			GitUserEmail: apiOpts.Author.Email,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		Dates: &files_service.CommitDateOptions{
 | 
					 | 
				
			||||||
			Author:    apiOpts.Dates.Author,
 | 
					 | 
				
			||||||
			Committer: apiOpts.Dates.Committer,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		Signoff: apiOpts.Signoff,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if opts.Dates.Author.IsZero() {
 | 
					 | 
				
			||||||
		opts.Dates.Author = time.Now()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if opts.Dates.Committer.IsZero() {
 | 
					 | 
				
			||||||
		opts.Dates.Committer = time.Now()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if opts.Message == "" {
 | 
						if opts.Message == "" {
 | 
				
			||||||
		opts.Message = changeFilesCommitMessage(ctx, opts.Files)
 | 
							opts.Message = changeFilesCommitMessage(ctx, opts.Files)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if filesResponse, err := createOrUpdateFiles(ctx, opts); err != nil {
 | 
						if filesResponse, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, opts); err != nil {
 | 
				
			||||||
		handleCreateOrUpdateFileError(ctx, err)
 | 
							handleChangeRepoFilesError(ctx, err)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		fileResponse := files_service.GetFileResponseFromFilesResponse(filesResponse, 0)
 | 
							fileResponse := files_service.GetFileResponseFromFilesResponse(filesResponse, 0)
 | 
				
			||||||
		ctx.JSON(http.StatusOK, fileResponse)
 | 
							ctx.JSON(http.StatusOK, fileResponse)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func handleCreateOrUpdateFileError(ctx *context.APIContext, err error) {
 | 
					func handleChangeRepoFilesError(ctx *context.APIContext, err error) {
 | 
				
			||||||
	if files_service.IsErrUserCannotCommit(err) || pull_service.IsErrFilePathProtected(err) {
 | 
						if files_service.IsErrUserCannotCommit(err) || pull_service.IsErrFilePathProtected(err) {
 | 
				
			||||||
		ctx.APIError(http.StatusForbidden, err)
 | 
							ctx.APIError(http.StatusForbidden, err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if git_model.IsErrBranchAlreadyExists(err) || files_service.IsErrFilenameInvalid(err) || pull_service.IsErrSHADoesNotMatch(err) ||
 | 
						if git_model.IsErrBranchAlreadyExists(err) || files_service.IsErrFilenameInvalid(err) || pull_service.IsErrSHADoesNotMatch(err) ||
 | 
				
			||||||
		files_service.IsErrFilePathInvalid(err) || files_service.IsErrRepoFileAlreadyExists(err) {
 | 
							files_service.IsErrFilePathInvalid(err) || files_service.IsErrRepoFileAlreadyExists(err) ||
 | 
				
			||||||
 | 
							files_service.IsErrCommitIDDoesNotMatch(err) || files_service.IsErrSHAOrCommitIDNotProvided(err) {
 | 
				
			||||||
		ctx.APIError(http.StatusUnprocessableEntity, err)
 | 
							ctx.APIError(http.StatusUnprocessableEntity, err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if git_model.IsErrBranchNotExist(err) || git.IsErrBranchNotExist(err) {
 | 
						if git.IsErrBranchNotExist(err) || files_service.IsErrRepoFileDoesNotExist(err) || git.IsErrNotExist(err) {
 | 
				
			||||||
 | 
							ctx.APIError(http.StatusNotFound, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if errors.Is(err, util.ErrNotExist) {
 | 
				
			||||||
		ctx.APIError(http.StatusNotFound, err)
 | 
							ctx.APIError(http.StatusNotFound, err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	ctx.APIErrorInternal(err)
 | 
						ctx.APIErrorInternal(err)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Called from both CreateFile or UpdateFile to handle both
 | 
					 | 
				
			||||||
func createOrUpdateFiles(ctx *context.APIContext, opts *files_service.ChangeRepoFilesOptions) (*api.FilesResponse, error) {
 | 
					 | 
				
			||||||
	if !canWriteFiles(ctx, opts.OldBranch) {
 | 
					 | 
				
			||||||
		return nil, repo_model.ErrUserDoesNotHaveAccessToRepo{
 | 
					 | 
				
			||||||
			UserID:   ctx.Doer.ID,
 | 
					 | 
				
			||||||
			RepoName: ctx.Repo.Repository.LowerName,
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, opts)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// format commit message if empty
 | 
					// format commit message if empty
 | 
				
			||||||
func changeFilesCommitMessage(ctx *context.APIContext, files []*files_service.ChangeRepoFile) string {
 | 
					func changeFilesCommitMessage(ctx *context.APIContext, files []*files_service.ChangeRepoFile) string {
 | 
				
			||||||
	var (
 | 
						var (
 | 
				
			||||||
@ -762,7 +693,7 @@ func changeFilesCommitMessage(ctx *context.APIContext, files []*files_service.Ch
 | 
				
			|||||||
		switch file.Operation {
 | 
							switch file.Operation {
 | 
				
			||||||
		case "create":
 | 
							case "create":
 | 
				
			||||||
			createFiles = append(createFiles, file.TreePath)
 | 
								createFiles = append(createFiles, file.TreePath)
 | 
				
			||||||
		case "update":
 | 
							case "update", "upload", "rename": // upload and rename works like "update", there is no translation for them at the moment
 | 
				
			||||||
			updateFiles = append(updateFiles, file.TreePath)
 | 
								updateFiles = append(updateFiles, file.TreePath)
 | 
				
			||||||
		case "delete":
 | 
							case "delete":
 | 
				
			||||||
			deleteFiles = append(deleteFiles, file.TreePath)
 | 
								deleteFiles = append(deleteFiles, file.TreePath)
 | 
				
			||||||
@ -820,74 +751,27 @@ func DeleteFile(ctx *context.APIContext) {
 | 
				
			|||||||
	//     "$ref": "#/responses/error"
 | 
						//     "$ref": "#/responses/error"
 | 
				
			||||||
	//   "404":
 | 
						//   "404":
 | 
				
			||||||
	//     "$ref": "#/responses/error"
 | 
						//     "$ref": "#/responses/error"
 | 
				
			||||||
 | 
						//   "422":
 | 
				
			||||||
 | 
						//     "$ref": "#/responses/error"
 | 
				
			||||||
	//   "423":
 | 
						//   "423":
 | 
				
			||||||
	//     "$ref": "#/responses/repoArchivedError"
 | 
						//     "$ref": "#/responses/repoArchivedError"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	apiOpts := web.GetForm(ctx).(*api.DeleteFileOptions)
 | 
						apiOpts, opts := getAPIChangeRepoFileOptions[*api.DeleteFileOptions](ctx)
 | 
				
			||||||
	if !canWriteFiles(ctx, apiOpts.BranchName) {
 | 
						if ctx.Written() {
 | 
				
			||||||
		ctx.APIError(http.StatusForbidden, repo_model.ErrUserDoesNotHaveAccessToRepo{
 | 
					 | 
				
			||||||
			UserID:   ctx.Doer.ID,
 | 
					 | 
				
			||||||
			RepoName: ctx.Repo.Repository.LowerName,
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if apiOpts.BranchName == "" {
 | 
						opts.Files = append(opts.Files, &files_service.ChangeRepoFile{
 | 
				
			||||||
		apiOpts.BranchName = ctx.Repo.Repository.DefaultBranch
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	opts := &files_service.ChangeRepoFilesOptions{
 | 
					 | 
				
			||||||
		Files: []*files_service.ChangeRepoFile{
 | 
					 | 
				
			||||||
			{
 | 
					 | 
				
			||||||
		Operation: "delete",
 | 
							Operation: "delete",
 | 
				
			||||||
		SHA:       apiOpts.SHA,
 | 
							SHA:       apiOpts.SHA,
 | 
				
			||||||
		TreePath:  ctx.PathParam("*"),
 | 
							TreePath:  ctx.PathParam("*"),
 | 
				
			||||||
			},
 | 
						})
 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		Message:   apiOpts.Message,
 | 
					 | 
				
			||||||
		OldBranch: apiOpts.BranchName,
 | 
					 | 
				
			||||||
		NewBranch: apiOpts.NewBranchName,
 | 
					 | 
				
			||||||
		Committer: &files_service.IdentityOptions{
 | 
					 | 
				
			||||||
			GitUserName:  apiOpts.Committer.Name,
 | 
					 | 
				
			||||||
			GitUserEmail: apiOpts.Committer.Email,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		Author: &files_service.IdentityOptions{
 | 
					 | 
				
			||||||
			GitUserName:  apiOpts.Author.Name,
 | 
					 | 
				
			||||||
			GitUserEmail: apiOpts.Author.Email,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		Dates: &files_service.CommitDateOptions{
 | 
					 | 
				
			||||||
			Author:    apiOpts.Dates.Author,
 | 
					 | 
				
			||||||
			Committer: apiOpts.Dates.Committer,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		Signoff: apiOpts.Signoff,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if opts.Dates.Author.IsZero() {
 | 
					 | 
				
			||||||
		opts.Dates.Author = time.Now()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if opts.Dates.Committer.IsZero() {
 | 
					 | 
				
			||||||
		opts.Dates.Committer = time.Now()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if opts.Message == "" {
 | 
						if opts.Message == "" {
 | 
				
			||||||
		opts.Message = changeFilesCommitMessage(ctx, opts.Files)
 | 
							opts.Message = changeFilesCommitMessage(ctx, opts.Files)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if filesResponse, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, opts); err != nil {
 | 
						if filesResponse, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, opts); err != nil {
 | 
				
			||||||
		if git.IsErrBranchNotExist(err) || files_service.IsErrRepoFileDoesNotExist(err) || git.IsErrNotExist(err) {
 | 
							handleChangeRepoFilesError(ctx, err)
 | 
				
			||||||
			ctx.APIError(http.StatusNotFound, err)
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		} else if git_model.IsErrBranchAlreadyExists(err) ||
 | 
					 | 
				
			||||||
			files_service.IsErrFilenameInvalid(err) ||
 | 
					 | 
				
			||||||
			pull_service.IsErrSHADoesNotMatch(err) ||
 | 
					 | 
				
			||||||
			files_service.IsErrCommitIDDoesNotMatch(err) ||
 | 
					 | 
				
			||||||
			files_service.IsErrSHAOrCommitIDNotProvided(err) {
 | 
					 | 
				
			||||||
			ctx.APIError(http.StatusBadRequest, err)
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		} else if files_service.IsErrUserCannotCommit(err) {
 | 
					 | 
				
			||||||
			ctx.APIError(http.StatusForbidden, err)
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		ctx.APIErrorInternal(err)
 | 
					 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		fileResponse := files_service.GetFileResponseFromFilesResponse(filesResponse, 0)
 | 
							fileResponse := files_service.GetFileResponseFromFilesResponse(filesResponse, 0)
 | 
				
			||||||
		ctx.JSON(http.StatusOK, fileResponse) // FIXME on APIv2: return http.StatusNoContent
 | 
							ctx.JSON(http.StatusOK, fileResponse) // FIXME on APIv2: return http.StatusNoContent
 | 
				
			||||||
@ -911,6 +795,8 @@ func GetContentsExt(ctx *context.APIContext) {
 | 
				
			|||||||
	// summary: The extended "contents" API, to get file metadata and/or content, or list a directory.
 | 
						// summary: The extended "contents" API, to get file metadata and/or content, or list a directory.
 | 
				
			||||||
	// description: It guarantees that only one of the response fields is set if the request succeeds.
 | 
						// description: It guarantees that only one of the response fields is set if the request succeeds.
 | 
				
			||||||
	//              Users can pass "includes=file_content" or "includes=lfs_metadata" to retrieve more fields.
 | 
						//              Users can pass "includes=file_content" or "includes=lfs_metadata" to retrieve more fields.
 | 
				
			||||||
 | 
						//              "includes=file_content" only works for single file, if you need to retrieve file contents in batch,
 | 
				
			||||||
 | 
						//              use "file-contents" API after listing the directory.
 | 
				
			||||||
	// produces:
 | 
						// produces:
 | 
				
			||||||
	// - application/json
 | 
						// - application/json
 | 
				
			||||||
	// parameters:
 | 
						// parameters:
 | 
				
			||||||
@ -964,12 +850,11 @@ func GetContentsExt(ctx *context.APIContext) {
 | 
				
			|||||||
	ctx.JSON(http.StatusOK, getRepoContents(ctx, opts))
 | 
						ctx.JSON(http.StatusOK, getRepoContents(ctx, opts))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetContents Get the metadata and contents (if a file) of an entry in a repository, or a list of entries if a dir
 | 
					 | 
				
			||||||
func GetContents(ctx *context.APIContext) {
 | 
					func GetContents(ctx *context.APIContext) {
 | 
				
			||||||
	// swagger:operation GET /repos/{owner}/{repo}/contents/{filepath} repository repoGetContents
 | 
						// swagger:operation GET /repos/{owner}/{repo}/contents/{filepath} repository repoGetContents
 | 
				
			||||||
	// ---
 | 
						// ---
 | 
				
			||||||
	// summary: Gets the metadata and contents (if a file) of an entry in a repository, or a list of entries if a dir.
 | 
						// summary: Gets the metadata and contents (if a file) of an entry in a repository, or a list of entries if a dir.
 | 
				
			||||||
	// description: This API follows GitHub's design, and it is not easy to use. Recommend to use our "contents-ext" API instead.
 | 
						// description: This API follows GitHub's design, and it is not easy to use. Recommend users to use the "contents-ext" API instead.
 | 
				
			||||||
	// produces:
 | 
						// produces:
 | 
				
			||||||
	// - application/json
 | 
						// - application/json
 | 
				
			||||||
	// parameters:
 | 
						// parameters:
 | 
				
			||||||
@ -1021,12 +906,11 @@ func getRepoContents(ctx *context.APIContext, opts files_service.GetContentsOrLi
 | 
				
			|||||||
	return &ret
 | 
						return &ret
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetContentsList Get the metadata of all the entries of the root dir
 | 
					 | 
				
			||||||
func GetContentsList(ctx *context.APIContext) {
 | 
					func GetContentsList(ctx *context.APIContext) {
 | 
				
			||||||
	// swagger:operation GET /repos/{owner}/{repo}/contents repository repoGetContentsList
 | 
						// swagger:operation GET /repos/{owner}/{repo}/contents repository repoGetContentsList
 | 
				
			||||||
	// ---
 | 
						// ---
 | 
				
			||||||
	// summary: Gets the metadata of all the entries of the root dir.
 | 
						// summary: Gets the metadata of all the entries of the root dir.
 | 
				
			||||||
	// description: This API follows GitHub's design, and it is not easy to use. Recommend to use our "contents-ext" API instead.
 | 
						// description: This API follows GitHub's design, and it is not easy to use. Recommend users to use our "contents-ext" API instead.
 | 
				
			||||||
	// produces:
 | 
						// produces:
 | 
				
			||||||
	// - application/json
 | 
						// - application/json
 | 
				
			||||||
	// parameters:
 | 
						// parameters:
 | 
				
			||||||
@ -1059,7 +943,7 @@ func GetFileContentsGet(ctx *context.APIContext) {
 | 
				
			|||||||
	// swagger:operation GET /repos/{owner}/{repo}/file-contents repository repoGetFileContents
 | 
						// swagger:operation GET /repos/{owner}/{repo}/file-contents repository repoGetFileContents
 | 
				
			||||||
	// ---
 | 
						// ---
 | 
				
			||||||
	// summary: Get the metadata and contents of requested files
 | 
						// summary: Get the metadata and contents of requested files
 | 
				
			||||||
	// description: See the POST method. This GET method supports to use JSON encoded request body in query parameter.
 | 
						// description: See the POST method. This GET method supports using JSON encoded request body in query parameter.
 | 
				
			||||||
	// produces:
 | 
						// produces:
 | 
				
			||||||
	// - application/json
 | 
						// - application/json
 | 
				
			||||||
	// parameters:
 | 
						// parameters:
 | 
				
			||||||
@ -1089,7 +973,7 @@ func GetFileContentsGet(ctx *context.APIContext) {
 | 
				
			|||||||
	//   "404":
 | 
						//   "404":
 | 
				
			||||||
	//     "$ref": "#/responses/notFound"
 | 
						//     "$ref": "#/responses/notFound"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// POST method requires "write" permission, so we also support this "GET" method
 | 
						// The POST method requires "write" permission, so we also support this "GET" method
 | 
				
			||||||
	handleGetFileContents(ctx)
 | 
						handleGetFileContents(ctx)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1133,7 +1017,7 @@ func GetFileContentsPost(ctx *context.APIContext) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// This is actually a "read" request, but we need to accept a "files" list, then POST method seems easy to use.
 | 
						// This is actually a "read" request, but we need to accept a "files" list, then POST method seems easy to use.
 | 
				
			||||||
	// But the permission system requires that the caller must have "write" permission to use POST method.
 | 
						// But the permission system requires that the caller must have "write" permission to use POST method.
 | 
				
			||||||
	// At the moment there is no other way to get around the permission check, so there is a "GET" workaround method above.
 | 
						// At the moment, there is no other way to get around the permission check, so there is a "GET" workaround method above.
 | 
				
			||||||
	handleGetFileContents(ctx)
 | 
						handleGetFileContents(ctx)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -5,15 +5,10 @@ package repo
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"time"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	git_model "code.gitea.io/gitea/models/git"
 | 
					 | 
				
			||||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/modules/git"
 | 
					 | 
				
			||||||
	api "code.gitea.io/gitea/modules/structs"
 | 
						api "code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/web"
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
	"code.gitea.io/gitea/services/context"
 | 
						"code.gitea.io/gitea/services/context"
 | 
				
			||||||
	pull_service "code.gitea.io/gitea/services/pull"
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/services/repository/files"
 | 
						"code.gitea.io/gitea/services/repository/files"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -49,63 +44,22 @@ func ApplyDiffPatch(ctx *context.APIContext) {
 | 
				
			|||||||
	//     "$ref": "#/responses/notFound"
 | 
						//     "$ref": "#/responses/notFound"
 | 
				
			||||||
	//   "423":
 | 
						//   "423":
 | 
				
			||||||
	//     "$ref": "#/responses/repoArchivedError"
 | 
						//     "$ref": "#/responses/repoArchivedError"
 | 
				
			||||||
	apiOpts := web.GetForm(ctx).(*api.ApplyDiffPatchFileOptions)
 | 
						apiOpts, changeRepoFileOpts := getAPIChangeRepoFileOptions[*api.ApplyDiffPatchFileOptions](ctx)
 | 
				
			||||||
 | 
					 | 
				
			||||||
	opts := &files.ApplyDiffPatchOptions{
 | 
						opts := &files.ApplyDiffPatchOptions{
 | 
				
			||||||
		Content: apiOpts.Content,
 | 
							Content: apiOpts.Content,
 | 
				
			||||||
		SHA:       apiOpts.SHA,
 | 
							Message: util.IfZero(apiOpts.Message, "apply-patch"),
 | 
				
			||||||
		Message:   apiOpts.Message,
 | 
					 | 
				
			||||||
		OldBranch: apiOpts.BranchName,
 | 
					 | 
				
			||||||
		NewBranch: apiOpts.NewBranchName,
 | 
					 | 
				
			||||||
		Committer: &files.IdentityOptions{
 | 
					 | 
				
			||||||
			GitUserName:  apiOpts.Committer.Name,
 | 
					 | 
				
			||||||
			GitUserEmail: apiOpts.Committer.Email,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		Author: &files.IdentityOptions{
 | 
					 | 
				
			||||||
			GitUserName:  apiOpts.Author.Name,
 | 
					 | 
				
			||||||
			GitUserEmail: apiOpts.Author.Email,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		Dates: &files.CommitDateOptions{
 | 
					 | 
				
			||||||
			Author:    apiOpts.Dates.Author,
 | 
					 | 
				
			||||||
			Committer: apiOpts.Dates.Committer,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		Signoff: apiOpts.Signoff,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if opts.Dates.Author.IsZero() {
 | 
					 | 
				
			||||||
		opts.Dates.Author = time.Now()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if opts.Dates.Committer.IsZero() {
 | 
					 | 
				
			||||||
		opts.Dates.Committer = time.Now()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if opts.Message == "" {
 | 
							OldBranch: changeRepoFileOpts.OldBranch,
 | 
				
			||||||
		opts.Message = "apply-patch"
 | 
							NewBranch: changeRepoFileOpts.NewBranch,
 | 
				
			||||||
	}
 | 
							Committer: changeRepoFileOpts.Committer,
 | 
				
			||||||
 | 
							Author:    changeRepoFileOpts.Author,
 | 
				
			||||||
	if !canWriteFiles(ctx, apiOpts.BranchName) {
 | 
							Dates:     changeRepoFileOpts.Dates,
 | 
				
			||||||
		ctx.APIErrorInternal(repo_model.ErrUserDoesNotHaveAccessToRepo{
 | 
							Signoff:   changeRepoFileOpts.Signoff,
 | 
				
			||||||
			UserID:   ctx.Doer.ID,
 | 
					 | 
				
			||||||
			RepoName: ctx.Repo.Repository.LowerName,
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	fileResponse, err := files.ApplyDiffPatch(ctx, ctx.Repo.Repository, ctx.Doer, opts)
 | 
						fileResponse, err := files.ApplyDiffPatch(ctx, ctx.Repo.Repository, ctx.Doer, opts)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if files.IsErrUserCannotCommit(err) || pull_service.IsErrFilePathProtected(err) {
 | 
							handleChangeRepoFilesError(ctx, err)
 | 
				
			||||||
			ctx.APIError(http.StatusForbidden, err)
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if git_model.IsErrBranchAlreadyExists(err) || files.IsErrFilenameInvalid(err) || pull_service.IsErrSHADoesNotMatch(err) ||
 | 
					 | 
				
			||||||
			files.IsErrFilePathInvalid(err) || files.IsErrRepoFileAlreadyExists(err) {
 | 
					 | 
				
			||||||
			ctx.APIError(http.StatusUnprocessableEntity, err)
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if git_model.IsErrBranchNotExist(err) || git.IsErrBranchNotExist(err) {
 | 
					 | 
				
			||||||
			ctx.APIError(http.StatusNotFound, err)
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		ctx.APIErrorInternal(err)
 | 
					 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		ctx.JSON(http.StatusCreated, fileResponse)
 | 
							ctx.JSON(http.StatusCreated, fileResponse)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
@ -12,6 +12,7 @@ import (
 | 
				
			|||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	git_model "code.gitea.io/gitea/models/git"
 | 
						git_model "code.gitea.io/gitea/models/git"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models/issues"
 | 
				
			||||||
	"code.gitea.io/gitea/models/unit"
 | 
						"code.gitea.io/gitea/models/unit"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/charset"
 | 
						"code.gitea.io/gitea/modules/charset"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/git"
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
@ -138,6 +139,11 @@ func prepareEditorCommitSubmittedForm[T forms.CommitCommonFormInterface](ctx *co
 | 
				
			|||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !issues.CanMaintainerWriteToBranch(ctx, ctx.Repo.Permission, targetBranchName, ctx.Doer) {
 | 
				
			||||||
 | 
							ctx.NotFound(nil)
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Committer user info
 | 
						// Committer user info
 | 
				
			||||||
	gitCommitter, valid := WebGitOperationGetCommitChosenEmailIdentity(ctx, commonForm.CommitEmail)
 | 
						gitCommitter, valid := WebGitOperationGetCommitChosenEmailIdentity(ctx, commonForm.CommitEmail)
 | 
				
			||||||
	if !valid {
 | 
						if !valid {
 | 
				
			||||||
 | 
				
			|||||||
@ -44,7 +44,6 @@ type ApplyDiffPatchOptions struct {
 | 
				
			|||||||
	NewBranch    string
 | 
						NewBranch    string
 | 
				
			||||||
	Message      string
 | 
						Message      string
 | 
				
			||||||
	Content      string
 | 
						Content      string
 | 
				
			||||||
	SHA          string
 | 
					 | 
				
			||||||
	Author       *IdentityOptions
 | 
						Author       *IdentityOptions
 | 
				
			||||||
	Committer    *IdentityOptions
 | 
						Committer    *IdentityOptions
 | 
				
			||||||
	Dates        *CommitDateOptions
 | 
						Dates        *CommitDateOptions
 | 
				
			||||||
 | 
				
			|||||||
@ -113,7 +113,7 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
 | 
				
			|||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// If no branch name is set, assume default branch
 | 
						// If no branch name is set, assume the default branch
 | 
				
			||||||
	if opts.OldBranch == "" {
 | 
						if opts.OldBranch == "" {
 | 
				
			||||||
		opts.OldBranch = repo.DefaultBranch
 | 
							opts.OldBranch = repo.DefaultBranch
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										27
									
								
								templates/swagger/v1_json.tmpl
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										27
									
								
								templates/swagger/v1_json.tmpl
									
									
									
										generated
									
									
									
								
							@ -7424,7 +7424,7 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "/repos/{owner}/{repo}/contents": {
 | 
					    "/repos/{owner}/{repo}/contents": {
 | 
				
			||||||
      "get": {
 | 
					      "get": {
 | 
				
			||||||
        "description": "This API follows GitHub's design, and it is not easy to use. Recommend to use our \"contents-ext\" API instead.",
 | 
					        "description": "This API follows GitHub's design, and it is not easy to use. Recommend users to use our \"contents-ext\" API instead.",
 | 
				
			||||||
        "produces": [
 | 
					        "produces": [
 | 
				
			||||||
          "application/json"
 | 
					          "application/json"
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
@ -7521,7 +7521,7 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "/repos/{owner}/{repo}/contents-ext/{filepath}": {
 | 
					    "/repos/{owner}/{repo}/contents-ext/{filepath}": {
 | 
				
			||||||
      "get": {
 | 
					      "get": {
 | 
				
			||||||
        "description": "It guarantees that only one of the response fields is set if the request succeeds. Users can pass \"includes=file_content\" or \"includes=lfs_metadata\" to retrieve more fields.",
 | 
					        "description": "It guarantees that only one of the response fields is set if the request succeeds. Users can pass \"includes=file_content\" or \"includes=lfs_metadata\" to retrieve more fields. \"includes=file_content\" only works for single file, if you need to retrieve file contents in batch, use \"file-contents\" API after listing the directory.",
 | 
				
			||||||
        "produces": [
 | 
					        "produces": [
 | 
				
			||||||
          "application/json"
 | 
					          "application/json"
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
@ -7577,7 +7577,7 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "/repos/{owner}/{repo}/contents/{filepath}": {
 | 
					    "/repos/{owner}/{repo}/contents/{filepath}": {
 | 
				
			||||||
      "get": {
 | 
					      "get": {
 | 
				
			||||||
        "description": "This API follows GitHub's design, and it is not easy to use. Recommend to use our \"contents-ext\" API instead.",
 | 
					        "description": "This API follows GitHub's design, and it is not easy to use. Recommend users to use the \"contents-ext\" API instead.",
 | 
				
			||||||
        "produces": [
 | 
					        "produces": [
 | 
				
			||||||
          "application/json"
 | 
					          "application/json"
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
@ -7802,6 +7802,9 @@
 | 
				
			|||||||
          "404": {
 | 
					          "404": {
 | 
				
			||||||
            "$ref": "#/responses/error"
 | 
					            "$ref": "#/responses/error"
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
 | 
					          "422": {
 | 
				
			||||||
 | 
					            "$ref": "#/responses/error"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
          "423": {
 | 
					          "423": {
 | 
				
			||||||
            "$ref": "#/responses/repoArchivedError"
 | 
					            "$ref": "#/responses/repoArchivedError"
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
@ -7909,7 +7912,7 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "/repos/{owner}/{repo}/file-contents": {
 | 
					    "/repos/{owner}/{repo}/file-contents": {
 | 
				
			||||||
      "get": {
 | 
					      "get": {
 | 
				
			||||||
        "description": "See the POST method. This GET method supports to use JSON encoded request body in query parameter.",
 | 
					        "description": "See the POST method. This GET method supports using JSON encoded request body in query parameter.",
 | 
				
			||||||
        "produces": [
 | 
					        "produces": [
 | 
				
			||||||
          "application/json"
 | 
					          "application/json"
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
@ -12876,7 +12879,7 @@
 | 
				
			|||||||
          },
 | 
					          },
 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
            "type": "string",
 | 
					            "type": "string",
 | 
				
			||||||
            "description": "The name of the commit/branch/tag. Default the repository’s default branch",
 | 
					            "description": "The name of the commit/branch/tag. Default to the repository’s default branch",
 | 
				
			||||||
            "name": "ref",
 | 
					            "name": "ref",
 | 
				
			||||||
            "in": "query"
 | 
					            "in": "query"
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
@ -15020,7 +15023,7 @@
 | 
				
			|||||||
          },
 | 
					          },
 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
            "type": "string",
 | 
					            "type": "string",
 | 
				
			||||||
            "description": "The name of the commit/branch/tag. Default the repository’s default branch",
 | 
					            "description": "The name of the commit/branch/tag. Default to the repository’s default branch",
 | 
				
			||||||
            "name": "ref",
 | 
					            "name": "ref",
 | 
				
			||||||
            "in": "query"
 | 
					            "in": "query"
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
@ -21867,7 +21870,7 @@
 | 
				
			|||||||
      ],
 | 
					      ],
 | 
				
			||||||
      "properties": {
 | 
					      "properties": {
 | 
				
			||||||
        "content": {
 | 
					        "content": {
 | 
				
			||||||
          "description": "new or updated file content, must be base64 encoded",
 | 
					          "description": "new or updated file content, it must be base64 encoded",
 | 
				
			||||||
          "type": "string",
 | 
					          "type": "string",
 | 
				
			||||||
          "x-go-name": "ContentBase64"
 | 
					          "x-go-name": "ContentBase64"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
@ -21877,11 +21880,13 @@
 | 
				
			|||||||
          "x-go-name": "FromPath"
 | 
					          "x-go-name": "FromPath"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "operation": {
 | 
					        "operation": {
 | 
				
			||||||
          "description": "indicates what to do with the file",
 | 
					          "description": "indicates what to do with the file: \"create\" for creating a new file, \"update\" for updating an existing file,\n\"upload\" for creating or updating a file, \"rename\" for renaming a file, and \"delete\" for deleting an existing file.",
 | 
				
			||||||
          "type": "string",
 | 
					          "type": "string",
 | 
				
			||||||
          "enum": [
 | 
					          "enum": [
 | 
				
			||||||
            "create",
 | 
					            "create",
 | 
				
			||||||
            "update",
 | 
					            "update",
 | 
				
			||||||
 | 
					            "upload",
 | 
				
			||||||
 | 
					            "rename",
 | 
				
			||||||
            "delete"
 | 
					            "delete"
 | 
				
			||||||
          ],
 | 
					          ],
 | 
				
			||||||
          "x-go-name": "Operation"
 | 
					          "x-go-name": "Operation"
 | 
				
			||||||
@ -21892,7 +21897,7 @@
 | 
				
			|||||||
          "x-go-name": "Path"
 | 
					          "x-go-name": "Path"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "sha": {
 | 
					        "sha": {
 | 
				
			||||||
          "description": "sha is the SHA for the file that already exists, required for update or delete",
 | 
					          "description": "the blob ID (SHA) for the file that already exists, required for changing existing files",
 | 
				
			||||||
          "type": "string",
 | 
					          "type": "string",
 | 
				
			||||||
          "x-go-name": "SHA"
 | 
					          "x-go-name": "SHA"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -23657,7 +23662,7 @@
 | 
				
			|||||||
          "x-go-name": "NewBranchName"
 | 
					          "x-go-name": "NewBranchName"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "sha": {
 | 
					        "sha": {
 | 
				
			||||||
          "description": "sha is the SHA for the file that already exists",
 | 
					          "description": "the blob ID (SHA) for the file that already exists, it is required for changing existing files",
 | 
				
			||||||
          "type": "string",
 | 
					          "type": "string",
 | 
				
			||||||
          "x-go-name": "SHA"
 | 
					          "x-go-name": "SHA"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
@ -28106,7 +28111,7 @@
 | 
				
			|||||||
          "x-go-name": "NewBranchName"
 | 
					          "x-go-name": "NewBranchName"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "sha": {
 | 
					        "sha": {
 | 
				
			||||||
          "description": "sha is the SHA for the file that already exists",
 | 
					          "description": "the blob ID (SHA) for the file that already exists, it is required for changing existing files",
 | 
				
			||||||
          "type": "string",
 | 
					          "type": "string",
 | 
				
			||||||
          "x-go-name": "SHA"
 | 
					          "x-go-name": "SHA"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
				
			|||||||
@ -20,6 +20,7 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func getDeleteFileOptions() *api.DeleteFileOptions {
 | 
					func getDeleteFileOptions() *api.DeleteFileOptions {
 | 
				
			||||||
	return &api.DeleteFileOptions{
 | 
						return &api.DeleteFileOptions{
 | 
				
			||||||
 | 
							FileOptionsWithSHA: api.FileOptionsWithSHA{
 | 
				
			||||||
			FileOptions: api.FileOptions{
 | 
								FileOptions: api.FileOptions{
 | 
				
			||||||
				BranchName:    "master",
 | 
									BranchName:    "master",
 | 
				
			||||||
				NewBranchName: "master",
 | 
									NewBranchName: "master",
 | 
				
			||||||
@ -34,6 +35,7 @@ func getDeleteFileOptions() *api.DeleteFileOptions {
 | 
				
			|||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885",
 | 
								SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -110,7 +112,7 @@ func TestAPIDeleteFile(t *testing.T) {
 | 
				
			|||||||
		deleteFileOptions.SHA = "badsha"
 | 
							deleteFileOptions.SHA = "badsha"
 | 
				
			||||||
		req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &deleteFileOptions).
 | 
							req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &deleteFileOptions).
 | 
				
			||||||
			AddTokenAuth(token2)
 | 
								AddTokenAuth(token2)
 | 
				
			||||||
		MakeRequest(t, req, http.StatusBadRequest)
 | 
							MakeRequest(t, req, http.StatusUnprocessableEntity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Test creating a file in repo16 by user4 who does not have write access
 | 
							// Test creating a file in repo16 by user4 who does not have write access
 | 
				
			||||||
		fileID++
 | 
							fileID++
 | 
				
			||||||
 | 
				
			|||||||
@ -27,7 +27,7 @@ func getUpdateFileOptions() *api.UpdateFileOptions {
 | 
				
			|||||||
	content := "This is updated text"
 | 
						content := "This is updated text"
 | 
				
			||||||
	contentEncoded := base64.StdEncoding.EncodeToString([]byte(content))
 | 
						contentEncoded := base64.StdEncoding.EncodeToString([]byte(content))
 | 
				
			||||||
	return &api.UpdateFileOptions{
 | 
						return &api.UpdateFileOptions{
 | 
				
			||||||
		DeleteFileOptions: api.DeleteFileOptions{
 | 
							FileOptionsWithSHA: api.FileOptionsWithSHA{
 | 
				
			||||||
			FileOptions: api.FileOptions{
 | 
								FileOptions: api.FileOptions{
 | 
				
			||||||
				BranchName:    "master",
 | 
									BranchName:    "master",
 | 
				
			||||||
				NewBranchName: "master",
 | 
									NewBranchName: "master",
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user