Skip to content

Commit e69b7a9

Browse files
authored
Allow API to create file on empty repo (#19224)
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 <[email protected]>
1 parent 54961f3 commit e69b7a9

File tree

8 files changed

+175
-117
lines changed

8 files changed

+175
-117
lines changed

integrations/api_repo_file_create_test.go

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,14 @@ func getCreateFileOptions() api.CreateFileOptions {
4949
}
5050
}
5151

52-
func getExpectedFileResponseForCreate(commitID, treePath string) *api.FileResponse {
52+
func getExpectedFileResponseForCreate(repoFullName, commitID, treePath string) *api.FileResponse {
5353
sha := "a635aa942442ddfdba07468cf9661c08fbdf0ebf"
5454
encoding := "base64"
5555
content := "VGhpcyBpcyBuZXcgdGV4dA=="
56-
selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + treePath + "?ref=master"
57-
htmlURL := setting.AppURL + "user2/repo1/src/branch/master/" + treePath
58-
gitURL := setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/" + sha
59-
downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + treePath
56+
selfURL := setting.AppURL + "api/v1/repos/" + repoFullName + "/contents/" + treePath + "?ref=master"
57+
htmlURL := setting.AppURL + repoFullName + "/src/branch/master/" + treePath
58+
gitURL := setting.AppURL + "api/v1/repos/" + repoFullName + "/git/blobs/" + sha
59+
downloadURL := setting.AppURL + repoFullName + "/raw/branch/master/" + treePath
6060
return &api.FileResponse{
6161
Content: &api.ContentsResponse{
6262
Name: filepath.Base(treePath),
@@ -78,10 +78,10 @@ func getExpectedFileResponseForCreate(commitID, treePath string) *api.FileRespon
7878
},
7979
Commit: &api.FileCommitResponse{
8080
CommitMeta: api.CommitMeta{
81-
URL: setting.AppURL + "api/v1/repos/user2/repo1/git/commits/" + commitID,
81+
URL: setting.AppURL + "api/v1/repos/" + repoFullName + "/git/commits/" + commitID,
8282
SHA: commitID,
8383
},
84-
HTMLURL: setting.AppURL + "user2/repo1/commit/" + commitID,
84+
HTMLURL: setting.AppURL + repoFullName + "/commit/" + commitID,
8585
Author: &api.CommitUser{
8686
Identity: api.Identity{
8787
Name: "Anne Doe",
@@ -169,7 +169,7 @@ func TestAPICreateFile(t *testing.T) {
169169
resp := session.MakeRequest(t, req, http.StatusCreated)
170170
gitRepo, _ := git.OpenRepository(repo1.RepoPath())
171171
commitID, _ := gitRepo.GetBranchCommitID(createFileOptions.NewBranchName)
172-
expectedFileResponse := getExpectedFileResponseForCreate(commitID, treePath)
172+
expectedFileResponse := getExpectedFileResponseForCreate("user2/repo1", commitID, treePath)
173173
var fileResponse api.FileResponse
174174
DecodeJSON(t, resp, &fileResponse)
175175
assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content)
@@ -276,5 +276,29 @@ func TestAPICreateFile(t *testing.T) {
276276
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token4)
277277
req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
278278
session.MakeRequest(t, req, http.StatusForbidden)
279+
280+
// Test creating a file in an empty repository
281+
doAPICreateRepository(NewAPITestContext(t, "user2", "empty-repo"), true)(t)
282+
createFileOptions = getCreateFileOptions()
283+
fileID++
284+
treePath = fmt.Sprintf("new/file%d.txt", fileID)
285+
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, "empty-repo", treePath, token2)
286+
req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
287+
resp = session.MakeRequest(t, req, http.StatusCreated)
288+
emptyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "empty-repo"}).(*repo_model.Repository) // public repo
289+
gitRepo, _ := git.OpenRepository(emptyRepo.RepoPath())
290+
commitID, _ := gitRepo.GetBranchCommitID(createFileOptions.NewBranchName)
291+
expectedFileResponse := getExpectedFileResponseForCreate("user2/empty-repo", commitID, treePath)
292+
DecodeJSON(t, resp, &fileResponse)
293+
assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content)
294+
assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA)
295+
assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL)
296+
assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email)
297+
assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name)
298+
assert.EqualValues(t, expectedFileResponse.Commit.Author.Date, fileResponse.Commit.Author.Date)
299+
assert.EqualValues(t, expectedFileResponse.Commit.Committer.Email, fileResponse.Commit.Committer.Email)
300+
assert.EqualValues(t, expectedFileResponse.Commit.Committer.Name, fileResponse.Commit.Committer.Name)
301+
assert.EqualValues(t, expectedFileResponse.Commit.Committer.Date, fileResponse.Commit.Committer.Date)
302+
gitRepo.Close()
279303
})
280304
}

routers/api/v1/repo/file.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -233,9 +233,6 @@ func CreateFile(ctx *context.APIContext) {
233233
// "$ref": "#/responses/error"
234234

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

240237
if apiOpts.BranchName == "" {
241238
apiOpts.BranchName = ctx.Repo.Repository.DefaultBranch

services/repository/files/cherry_pick.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,9 @@ func CherryPick(ctx context.Context, repo *repo_model.Repository, doer *user_mod
9797
// Now commit the tree
9898
var commitHash string
9999
if opts.Dates != nil {
100-
commitHash, err = t.CommitTreeWithDate(author, committer, treeHash, message, opts.Signoff, opts.Dates.Author, opts.Dates.Committer)
100+
commitHash, err = t.CommitTreeWithDate("HEAD", author, committer, treeHash, message, opts.Signoff, opts.Dates.Author, opts.Dates.Committer)
101101
} else {
102-
commitHash, err = t.CommitTree(author, committer, treeHash, message, opts.Signoff)
102+
commitHash, err = t.CommitTree("HEAD", author, committer, treeHash, message, opts.Signoff)
103103
}
104104
if err != nil {
105105
return nil, err

services/repository/files/delete.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,9 @@ func DeleteRepoFile(ctx context.Context, repo *repo_model.Repository, doer *user
179179
// Now commit the tree
180180
var commitHash string
181181
if opts.Dates != nil {
182-
commitHash, err = t.CommitTreeWithDate(author, committer, treeHash, message, opts.Signoff, opts.Dates.Author, opts.Dates.Committer)
182+
commitHash, err = t.CommitTreeWithDate("HEAD", author, committer, treeHash, message, opts.Signoff, opts.Dates.Author, opts.Dates.Committer)
183183
} else {
184-
commitHash, err = t.CommitTree(author, committer, treeHash, message, opts.Signoff)
184+
commitHash, err = t.CommitTree("HEAD", author, committer, treeHash, message, opts.Signoff)
185185
}
186186
if err != nil {
187187
return nil, err

services/repository/files/patch.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,9 @@ func ApplyDiffPatch(ctx context.Context, repo *repo_model.Repository, doer *user
164164
// Now commit the tree
165165
var commitHash string
166166
if opts.Dates != nil {
167-
commitHash, err = t.CommitTreeWithDate(author, committer, treeHash, message, opts.Signoff, opts.Dates.Author, opts.Dates.Committer)
167+
commitHash, err = t.CommitTreeWithDate("HEAD", author, committer, treeHash, message, opts.Signoff, opts.Dates.Author, opts.Dates.Committer)
168168
} else {
169-
commitHash, err = t.CommitTree(author, committer, treeHash, message, opts.Signoff)
169+
commitHash, err = t.CommitTree("HEAD", author, committer, treeHash, message, opts.Signoff)
170170
}
171171
if err != nil {
172172
return nil, err

services/repository/files/temp_repo.go

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,19 @@ func (t *TemporaryUploadRepository) Clone(branch string) error {
7777
return nil
7878
}
7979

80+
// Init the repository
81+
func (t *TemporaryUploadRepository) Init() error {
82+
if err := git.InitRepository(t.ctx, t.basePath, false); err != nil {
83+
return err
84+
}
85+
gitRepo, err := git.OpenRepositoryCtx(t.ctx, t.basePath)
86+
if err != nil {
87+
return err
88+
}
89+
t.gitRepo = gitRepo
90+
return nil
91+
}
92+
8093
// SetDefaultIndex sets the git index to our HEAD
8194
func (t *TemporaryUploadRepository) SetDefaultIndex() error {
8295
if _, err := git.NewCommand(t.ctx, "read-tree", "HEAD").RunInDir(t.basePath); err != nil {
@@ -209,12 +222,12 @@ func (t *TemporaryUploadRepository) GetLastCommitByRef(ref string) (string, erro
209222
}
210223

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

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

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

238-
args := []string{"commit-tree", treeHash, "-p", "HEAD"}
251+
var args []string
252+
if parent != "" {
253+
args = []string{"commit-tree", treeHash, "-p", parent}
254+
} else {
255+
args = []string{"commit-tree", treeHash}
256+
}
239257

240258
// Determine if we should sign
241259
if git.CheckGitVersionAtLeast("1.7.9") == nil {
242-
sign, keyID, signer, _ := asymkey_service.SignCRUDAction(t.ctx, t.repo.RepoPath(), author, t.basePath, "HEAD")
260+
var sign bool
261+
var keyID string
262+
var signer *git.Signature
263+
if parent != "" {
264+
sign, keyID, signer, _ = asymkey_service.SignCRUDAction(t.ctx, t.repo.RepoPath(), author, t.basePath, parent)
265+
} else {
266+
sign, keyID, signer, _ = asymkey_service.SignInitialCommit(t.ctx, t.repo.RepoPath(), author)
267+
}
243268
if sign {
244269
args = append(args, "-S"+keyID)
245270
if t.repo.GetTrustModel() == repo_model.CommitterTrustModel || t.repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel {

0 commit comments

Comments
 (0)