Skip to content

Commit

Permalink
Allow API to create file on empty repo (#19224)
Browse files Browse the repository at this point in the history
This PR adds the necessary work to make it possible to create files on empty
repos using the API.

Fix #10993

Signed-off-by: Andrew Thornton <art27@cantab.net>
  • Loading branch information
zeripath authored Mar 28, 2022
1 parent 54961f3 commit e69b7a9
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 117 deletions.
40 changes: 32 additions & 8 deletions integrations/api_repo_file_create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,14 @@ func getCreateFileOptions() api.CreateFileOptions {
}
}

func getExpectedFileResponseForCreate(commitID, treePath string) *api.FileResponse {
func getExpectedFileResponseForCreate(repoFullName, commitID, treePath string) *api.FileResponse {
sha := "a635aa942442ddfdba07468cf9661c08fbdf0ebf"
encoding := "base64"
content := "VGhpcyBpcyBuZXcgdGV4dA=="
selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + treePath + "?ref=master"
htmlURL := setting.AppURL + "user2/repo1/src/branch/master/" + treePath
gitURL := setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/" + sha
downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + treePath
selfURL := setting.AppURL + "api/v1/repos/" + repoFullName + "/contents/" + treePath + "?ref=master"
htmlURL := setting.AppURL + repoFullName + "/src/branch/master/" + treePath
gitURL := setting.AppURL + "api/v1/repos/" + repoFullName + "/git/blobs/" + sha
downloadURL := setting.AppURL + repoFullName + "/raw/branch/master/" + treePath
return &api.FileResponse{
Content: &api.ContentsResponse{
Name: filepath.Base(treePath),
Expand All @@ -78,10 +78,10 @@ func getExpectedFileResponseForCreate(commitID, treePath string) *api.FileRespon
},
Commit: &api.FileCommitResponse{
CommitMeta: api.CommitMeta{
URL: setting.AppURL + "api/v1/repos/user2/repo1/git/commits/" + commitID,
URL: setting.AppURL + "api/v1/repos/" + repoFullName + "/git/commits/" + commitID,
SHA: commitID,
},
HTMLURL: setting.AppURL + "user2/repo1/commit/" + commitID,
HTMLURL: setting.AppURL + repoFullName + "/commit/" + commitID,
Author: &api.CommitUser{
Identity: api.Identity{
Name: "Anne Doe",
Expand Down Expand Up @@ -169,7 +169,7 @@ func TestAPICreateFile(t *testing.T) {
resp := session.MakeRequest(t, req, http.StatusCreated)
gitRepo, _ := git.OpenRepository(repo1.RepoPath())
commitID, _ := gitRepo.GetBranchCommitID(createFileOptions.NewBranchName)
expectedFileResponse := getExpectedFileResponseForCreate(commitID, treePath)
expectedFileResponse := getExpectedFileResponseForCreate("user2/repo1", commitID, treePath)
var fileResponse api.FileResponse
DecodeJSON(t, resp, &fileResponse)
assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content)
Expand Down Expand Up @@ -276,5 +276,29 @@ func TestAPICreateFile(t *testing.T) {
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token4)
req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
session.MakeRequest(t, req, http.StatusForbidden)

// Test creating a file in an empty repository
doAPICreateRepository(NewAPITestContext(t, "user2", "empty-repo"), true)(t)
createFileOptions = getCreateFileOptions()
fileID++
treePath = fmt.Sprintf("new/file%d.txt", fileID)
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, "empty-repo", treePath, token2)
req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
resp = session.MakeRequest(t, req, http.StatusCreated)
emptyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "empty-repo"}).(*repo_model.Repository) // public repo
gitRepo, _ := git.OpenRepository(emptyRepo.RepoPath())
commitID, _ := gitRepo.GetBranchCommitID(createFileOptions.NewBranchName)
expectedFileResponse := getExpectedFileResponseForCreate("user2/empty-repo", commitID, treePath)
DecodeJSON(t, resp, &fileResponse)
assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content)
assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA)
assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL)
assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email)
assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name)
assert.EqualValues(t, expectedFileResponse.Commit.Author.Date, fileResponse.Commit.Author.Date)
assert.EqualValues(t, expectedFileResponse.Commit.Committer.Email, fileResponse.Commit.Committer.Email)
assert.EqualValues(t, expectedFileResponse.Commit.Committer.Name, fileResponse.Commit.Committer.Name)
assert.EqualValues(t, expectedFileResponse.Commit.Committer.Date, fileResponse.Commit.Committer.Date)
gitRepo.Close()
})
}
3 changes: 0 additions & 3 deletions routers/api/v1/repo/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,6 @@ func CreateFile(ctx *context.APIContext) {
// "$ref": "#/responses/error"

apiOpts := web.GetForm(ctx).(*api.CreateFileOptions)
if ctx.Repo.Repository.IsEmpty {
ctx.Error(http.StatusUnprocessableEntity, "RepoIsEmpty", fmt.Errorf("repo is empty"))
}

if apiOpts.BranchName == "" {
apiOpts.BranchName = ctx.Repo.Repository.DefaultBranch
Expand Down
4 changes: 2 additions & 2 deletions services/repository/files/cherry_pick.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,9 @@ func CherryPick(ctx context.Context, repo *repo_model.Repository, doer *user_mod
// Now commit the tree
var commitHash string
if opts.Dates != nil {
commitHash, err = t.CommitTreeWithDate(author, committer, treeHash, message, opts.Signoff, opts.Dates.Author, opts.Dates.Committer)
commitHash, err = t.CommitTreeWithDate("HEAD", author, committer, treeHash, message, opts.Signoff, opts.Dates.Author, opts.Dates.Committer)
} else {
commitHash, err = t.CommitTree(author, committer, treeHash, message, opts.Signoff)
commitHash, err = t.CommitTree("HEAD", author, committer, treeHash, message, opts.Signoff)
}
if err != nil {
return nil, err
Expand Down
4 changes: 2 additions & 2 deletions services/repository/files/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,9 @@ func DeleteRepoFile(ctx context.Context, repo *repo_model.Repository, doer *user
// Now commit the tree
var commitHash string
if opts.Dates != nil {
commitHash, err = t.CommitTreeWithDate(author, committer, treeHash, message, opts.Signoff, opts.Dates.Author, opts.Dates.Committer)
commitHash, err = t.CommitTreeWithDate("HEAD", author, committer, treeHash, message, opts.Signoff, opts.Dates.Author, opts.Dates.Committer)
} else {
commitHash, err = t.CommitTree(author, committer, treeHash, message, opts.Signoff)
commitHash, err = t.CommitTree("HEAD", author, committer, treeHash, message, opts.Signoff)
}
if err != nil {
return nil, err
Expand Down
4 changes: 2 additions & 2 deletions services/repository/files/patch.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,9 @@ func ApplyDiffPatch(ctx context.Context, repo *repo_model.Repository, doer *user
// Now commit the tree
var commitHash string
if opts.Dates != nil {
commitHash, err = t.CommitTreeWithDate(author, committer, treeHash, message, opts.Signoff, opts.Dates.Author, opts.Dates.Committer)
commitHash, err = t.CommitTreeWithDate("HEAD", author, committer, treeHash, message, opts.Signoff, opts.Dates.Author, opts.Dates.Committer)
} else {
commitHash, err = t.CommitTree(author, committer, treeHash, message, opts.Signoff)
commitHash, err = t.CommitTree("HEAD", author, committer, treeHash, message, opts.Signoff)
}
if err != nil {
return nil, err
Expand Down
35 changes: 30 additions & 5 deletions services/repository/files/temp_repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,19 @@ func (t *TemporaryUploadRepository) Clone(branch string) error {
return nil
}

// Init the repository
func (t *TemporaryUploadRepository) Init() error {
if err := git.InitRepository(t.ctx, t.basePath, false); err != nil {
return err
}
gitRepo, err := git.OpenRepositoryCtx(t.ctx, t.basePath)
if err != nil {
return err
}
t.gitRepo = gitRepo
return nil
}

// SetDefaultIndex sets the git index to our HEAD
func (t *TemporaryUploadRepository) SetDefaultIndex() error {
if _, err := git.NewCommand(t.ctx, "read-tree", "HEAD").RunInDir(t.basePath); err != nil {
Expand Down Expand Up @@ -209,12 +222,12 @@ func (t *TemporaryUploadRepository) GetLastCommitByRef(ref string) (string, erro
}

// CommitTree creates a commit from a given tree for the user with provided message
func (t *TemporaryUploadRepository) CommitTree(author, committer *user_model.User, treeHash, message string, signoff bool) (string, error) {
return t.CommitTreeWithDate(author, committer, treeHash, message, signoff, time.Now(), time.Now())
func (t *TemporaryUploadRepository) CommitTree(parent string, author, committer *user_model.User, treeHash, message string, signoff bool) (string, error) {
return t.CommitTreeWithDate(parent, author, committer, treeHash, message, signoff, time.Now(), time.Now())
}

// CommitTreeWithDate creates a commit from a given tree for the user with provided message
func (t *TemporaryUploadRepository) CommitTreeWithDate(author, committer *user_model.User, treeHash, message string, signoff bool, authorDate, committerDate time.Time) (string, error) {
func (t *TemporaryUploadRepository) CommitTreeWithDate(parent string, author, committer *user_model.User, treeHash, message string, signoff bool, authorDate, committerDate time.Time) (string, error) {
authorSig := author.NewGitSig()
committerSig := committer.NewGitSig()

Expand All @@ -235,11 +248,23 @@ func (t *TemporaryUploadRepository) CommitTreeWithDate(author, committer *user_m
_, _ = messageBytes.WriteString(message)
_, _ = messageBytes.WriteString("\n")

args := []string{"commit-tree", treeHash, "-p", "HEAD"}
var args []string
if parent != "" {
args = []string{"commit-tree", treeHash, "-p", parent}
} else {
args = []string{"commit-tree", treeHash}
}

// Determine if we should sign
if git.CheckGitVersionAtLeast("1.7.9") == nil {
sign, keyID, signer, _ := asymkey_service.SignCRUDAction(t.ctx, t.repo.RepoPath(), author, t.basePath, "HEAD")
var sign bool
var keyID string
var signer *git.Signature
if parent != "" {
sign, keyID, signer, _ = asymkey_service.SignCRUDAction(t.ctx, t.repo.RepoPath(), author, t.basePath, parent)
} else {
sign, keyID, signer, _ = asymkey_service.SignInitialCommit(t.ctx, t.repo.RepoPath(), author)
}
if sign {
args = append(args, "-S"+keyID)
if t.repo.GetTrustModel() == repo_model.CommitterTrustModel || t.repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel {
Expand Down
Loading

0 comments on commit e69b7a9

Please sign in to comment.