Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow API to create file on empty repo #19224

Merged
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)
6543 marked this conversation as resolved.
Show resolved Hide resolved
} 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
}

// Open the repository
zeripath marked this conversation as resolved.
Show resolved Hide resolved
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, "HEAD")
zeripath marked this conversation as resolved.
Show resolved Hide resolved
} 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
200 changes: 106 additions & 94 deletions services/repository/files/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func CreateOrUpdateRepoFile(ctx context.Context, repo *repo_model.Repository, do
defer closer.Close()

// oldBranch must exist for this operation
if _, err := gitRepo.GetBranch(opts.OldBranch); err != nil {
if _, err := gitRepo.GetBranch(opts.OldBranch); err != nil && !repo.IsEmpty {
return nil, err
}

Expand Down Expand Up @@ -191,118 +191,130 @@ func CreateOrUpdateRepoFile(ctx context.Context, repo *repo_model.Repository, do
log.Error("%v", err)
}
defer t.Close()
hasOldBranch := true
if err := t.Clone(opts.OldBranch); err != nil {
return nil, err
}
if err := t.SetDefaultIndex(); err != nil {
return nil, err
}

// Get the commit of the original branch
commit, err := t.GetBranchCommit(opts.OldBranch)
if err != nil {
return nil, err // Couldn't get a commit for the branch
if !git.IsErrBranchNotExist(err) || !repo.IsEmpty {
return nil, err
}
if err := t.Init(); err != nil {
return nil, err
}
hasOldBranch = false
opts.LastCommitID = ""
}

// Assigned LastCommitID in opts if it hasn't been set
if opts.LastCommitID == "" {
opts.LastCommitID = commit.ID.String()
} else {
lastCommitID, err := t.gitRepo.ConvertToSHA1(opts.LastCommitID)
if err != nil {
return nil, fmt.Errorf("DeleteRepoFile: Invalid last commit ID: %v", err)
if hasOldBranch {
if err := t.SetDefaultIndex(); err != nil {
return nil, err
}
opts.LastCommitID = lastCommitID.String()

}

encoding := "UTF-8"
bom := false
executable := false

if !opts.IsNewFile {
fromEntry, err := commit.GetTreeEntryByPath(fromTreePath)
if hasOldBranch {
// Get the commit of the original branch
commit, err := t.GetBranchCommit(opts.OldBranch)
if err != nil {
return nil, err
return nil, err // Couldn't get a commit for the branch
}
if opts.SHA != "" {
// If a SHA was given and the SHA given doesn't match the SHA of the fromTreePath, throw error
if opts.SHA != fromEntry.ID.String() {
return nil, models.ErrSHADoesNotMatch{
Path: treePath,
GivenSHA: opts.SHA,
CurrentSHA: fromEntry.ID.String(),
}

// Assigned LastCommitID in opts if it hasn't been set
if opts.LastCommitID == "" {
opts.LastCommitID = commit.ID.String()
} else {
lastCommitID, err := t.gitRepo.ConvertToSHA1(opts.LastCommitID)
if err != nil {
return nil, fmt.Errorf("DeleteRepoFile: Invalid last commit ID: %v", err)
6543 marked this conversation as resolved.
Show resolved Hide resolved
}
} else if opts.LastCommitID != "" {
// If a lastCommitID was given and it doesn't match the commitID of the head of the branch throw
// an error, but only if we aren't creating a new branch.
if commit.ID.String() != opts.LastCommitID && opts.OldBranch == opts.NewBranch {
if changed, err := commit.FileChangedSinceCommit(treePath, opts.LastCommitID); err != nil {
return nil, err
} else if changed {
return nil, models.ErrCommitIDDoesNotMatch{
GivenCommitID: opts.LastCommitID,
CurrentCommitID: opts.LastCommitID,
opts.LastCommitID = lastCommitID.String()

}

if !opts.IsNewFile {
fromEntry, err := commit.GetTreeEntryByPath(fromTreePath)
if err != nil {
return nil, err
}
if opts.SHA != "" {
// If a SHA was given and the SHA given doesn't match the SHA of the fromTreePath, throw error
if opts.SHA != fromEntry.ID.String() {
return nil, models.ErrSHADoesNotMatch{
Path: treePath,
GivenSHA: opts.SHA,
CurrentSHA: fromEntry.ID.String(),
}
}
} else if opts.LastCommitID != "" {
// If a lastCommitID was given and it doesn't match the commitID of the head of the branch throw
// an error, but only if we aren't creating a new branch.
if commit.ID.String() != opts.LastCommitID && opts.OldBranch == opts.NewBranch {
if changed, err := commit.FileChangedSinceCommit(treePath, opts.LastCommitID); err != nil {
return nil, err
} else if changed {
return nil, models.ErrCommitIDDoesNotMatch{
GivenCommitID: opts.LastCommitID,
CurrentCommitID: opts.LastCommitID,
}
}
// The file wasn't modified, so we are good to delete it
}
// The file wasn't modified, so we are good to delete it
} else {
// When updating a file, a lastCommitID or SHA needs to be given to make sure other commits
// haven't been made. We throw an error if one wasn't provided.
return nil, models.ErrSHAOrCommitIDNotProvided{}
}
} else {
// When updating a file, a lastCommitID or SHA needs to be given to make sure other commits
// haven't been made. We throw an error if one wasn't provided.
return nil, models.ErrSHAOrCommitIDNotProvided{}
encoding, bom = detectEncodingAndBOM(fromEntry, repo)
executable = fromEntry.IsExecutable()
}
encoding, bom = detectEncodingAndBOM(fromEntry, repo)
executable = fromEntry.IsExecutable()
}

// For the path where this file will be created/updated, we need to make
// sure no parts of the path are existing files or links except for the last
// item in the path which is the file name, and that shouldn't exist IF it is
// a new file OR is being moved to a new path.
treePathParts := strings.Split(treePath, "/")
subTreePath := ""
for index, part := range treePathParts {
subTreePath = path.Join(subTreePath, part)
entry, err := commit.GetTreeEntryByPath(subTreePath)
if err != nil {
if git.IsErrNotExist(err) {
// Means there is no item with that name, so we're good
break

// For the path where this file will be created/updated, we need to make
// sure no parts of the path are existing files or links except for the last
// item in the path which is the file name, and that shouldn't exist IF it is
// a new file OR is being moved to a new path.
treePathParts := strings.Split(treePath, "/")
subTreePath := ""
for index, part := range treePathParts {
subTreePath = path.Join(subTreePath, part)
entry, err := commit.GetTreeEntryByPath(subTreePath)
if err != nil {
if git.IsErrNotExist(err) {
// Means there is no item with that name, so we're good
break
}
return nil, err
}
return nil, err
}
if index < len(treePathParts)-1 {
if !entry.IsDir() {
if index < len(treePathParts)-1 {
if !entry.IsDir() {
return nil, models.ErrFilePathInvalid{
Message: fmt.Sprintf("a file exists where you’re trying to create a subdirectory [path: %s]", subTreePath),
Path: subTreePath,
Name: part,
Type: git.EntryModeBlob,
}
}
} else if entry.IsLink() {
return nil, models.ErrFilePathInvalid{
Message: fmt.Sprintf("a file exists where you’re trying to create a subdirectory [path: %s]", subTreePath),
Message: fmt.Sprintf("a symbolic link exists where you’re trying to create a subdirectory [path: %s]", subTreePath),
Path: subTreePath,
Name: part,
Type: git.EntryModeBlob,
Type: git.EntryModeSymlink,
}
} else if entry.IsDir() {
return nil, models.ErrFilePathInvalid{
Message: fmt.Sprintf("a directory exists where you’re trying to create a file [path: %s]", subTreePath),
Path: subTreePath,
Name: part,
Type: git.EntryModeTree,
}
} else if fromTreePath != treePath || opts.IsNewFile {
// The entry shouldn't exist if we are creating new file or moving to a new path
return nil, models.ErrRepoFileAlreadyExists{
Path: treePath,
}
}
} else if entry.IsLink() {
return nil, models.ErrFilePathInvalid{
Message: fmt.Sprintf("a symbolic link exists where you’re trying to create a subdirectory [path: %s]", subTreePath),
Path: subTreePath,
Name: part,
Type: git.EntryModeSymlink,
}
} else if entry.IsDir() {
return nil, models.ErrFilePathInvalid{
Message: fmt.Sprintf("a directory exists where you’re trying to create a file [path: %s]", subTreePath),
Path: subTreePath,
Name: part,
Type: git.EntryModeTree,
}
} else if fromTreePath != treePath || opts.IsNewFile {
// The entry shouldn't exist if we are creating new file or moving to a new path
return nil, models.ErrRepoFileAlreadyExists{
Path: treePath,
}
}

}
}

// Get the two paths (might be the same if not moving) from the index if they exist
Expand Down Expand Up @@ -354,7 +366,7 @@ func CreateOrUpdateRepoFile(ctx context.Context, repo *repo_model.Repository, do
opts.Content = content
var lfsMetaObject *models.LFSMetaObject

if setting.LFS.StartServer {
if setting.LFS.StartServer && hasOldBranch {
// Check there is no way this can return multiple infos
filename2attribute2info, err := t.gitRepo.CheckAttribute(git.CheckAttributeOpts{
Attributes: []string{"filter"},
Expand Down Expand Up @@ -401,9 +413,9 @@ func CreateOrUpdateRepoFile(ctx context.Context, repo *repo_model.Repository, do
// 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(opts.LastCommitID, 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(opts.LastCommitID, author, committer, treeHash, message, opts.Signoff)
}
if err != nil {
return nil, err
Expand Down Expand Up @@ -436,7 +448,7 @@ func CreateOrUpdateRepoFile(ctx context.Context, repo *repo_model.Repository, do
return nil, err
}

commit, err = t.GetCommit(commitHash)
commit, err := t.GetCommit(commitHash)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion services/repository/files/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
committer := doer

// Now commit the tree
commitHash, err := t.CommitTree(author, committer, treeHash, opts.Message, opts.Signoff)
commitHash, err := t.CommitTree(opts.LastCommitID, author, committer, treeHash, opts.Message, opts.Signoff)
if err != nil {
return err
}
Expand Down