code.gitea.io/gitea@v1.22.3/tests/integration/repofiles_change_test.go (about)

     1  // Copyright 2019 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package integration
     5  
     6  import (
     7  	"net/url"
     8  	"path/filepath"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	repo_model "code.gitea.io/gitea/models/repo"
    14  	"code.gitea.io/gitea/models/unittest"
    15  	"code.gitea.io/gitea/modules/git"
    16  	"code.gitea.io/gitea/modules/gitrepo"
    17  	"code.gitea.io/gitea/modules/setting"
    18  	api "code.gitea.io/gitea/modules/structs"
    19  	"code.gitea.io/gitea/services/contexttest"
    20  	files_service "code.gitea.io/gitea/services/repository/files"
    21  
    22  	"github.com/stretchr/testify/assert"
    23  )
    24  
    25  func getCreateRepoFilesOptions(repo *repo_model.Repository) *files_service.ChangeRepoFilesOptions {
    26  	return &files_service.ChangeRepoFilesOptions{
    27  		Files: []*files_service.ChangeRepoFile{
    28  			{
    29  				Operation:     "create",
    30  				TreePath:      "new/file.txt",
    31  				ContentReader: strings.NewReader("This is a NEW file"),
    32  			},
    33  		},
    34  		OldBranch: repo.DefaultBranch,
    35  		NewBranch: repo.DefaultBranch,
    36  		Message:   "Creates new/file.txt",
    37  		Author:    nil,
    38  		Committer: nil,
    39  	}
    40  }
    41  
    42  func getUpdateRepoFilesOptions(repo *repo_model.Repository) *files_service.ChangeRepoFilesOptions {
    43  	return &files_service.ChangeRepoFilesOptions{
    44  		Files: []*files_service.ChangeRepoFile{
    45  			{
    46  				Operation:     "update",
    47  				TreePath:      "README.md",
    48  				SHA:           "4b4851ad51df6a7d9f25c979345979eaeb5b349f",
    49  				ContentReader: strings.NewReader("This is UPDATED content for the README file"),
    50  			},
    51  		},
    52  		OldBranch: repo.DefaultBranch,
    53  		NewBranch: repo.DefaultBranch,
    54  		Message:   "Updates README.md",
    55  		Author:    nil,
    56  		Committer: nil,
    57  	}
    58  }
    59  
    60  func getDeleteRepoFilesOptions(repo *repo_model.Repository) *files_service.ChangeRepoFilesOptions {
    61  	return &files_service.ChangeRepoFilesOptions{
    62  		Files: []*files_service.ChangeRepoFile{
    63  			{
    64  				Operation: "delete",
    65  				TreePath:  "README.md",
    66  				SHA:       "4b4851ad51df6a7d9f25c979345979eaeb5b349f",
    67  			},
    68  		},
    69  		LastCommitID: "",
    70  		OldBranch:    repo.DefaultBranch,
    71  		NewBranch:    repo.DefaultBranch,
    72  		Message:      "Deletes README.md",
    73  		Author: &files_service.IdentityOptions{
    74  			Name:  "Bob Smith",
    75  			Email: "bob@smith.com",
    76  		},
    77  		Committer: nil,
    78  	}
    79  }
    80  
    81  func getExpectedFileResponseForRepofilesDelete(u *url.URL) *api.FileResponse {
    82  	// Just returns fields that don't change, i.e. fields with commit SHAs and dates can't be determined
    83  	return &api.FileResponse{
    84  		Content: nil,
    85  		Commit: &api.FileCommitResponse{
    86  			Author: &api.CommitUser{
    87  				Identity: api.Identity{
    88  					Name:  "Bob Smith",
    89  					Email: "bob@smith.com",
    90  				},
    91  			},
    92  			Committer: &api.CommitUser{
    93  				Identity: api.Identity{
    94  					Name:  "Bob Smith",
    95  					Email: "bob@smith.com",
    96  				},
    97  			},
    98  			Message: "Deletes README.md\n",
    99  		},
   100  		Verification: &api.PayloadCommitVerification{
   101  			Verified:  false,
   102  			Reason:    "gpg.error.not_signed_commit",
   103  			Signature: "",
   104  			Payload:   "",
   105  		},
   106  	}
   107  }
   108  
   109  func getExpectedFileResponseForRepofilesCreate(commitID, lastCommitSHA string) *api.FileResponse {
   110  	treePath := "new/file.txt"
   111  	encoding := "base64"
   112  	content := "VGhpcyBpcyBhIE5FVyBmaWxl"
   113  	selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + treePath + "?ref=master"
   114  	htmlURL := setting.AppURL + "user2/repo1/src/branch/master/" + treePath
   115  	gitURL := setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/103ff9234cefeee5ec5361d22b49fbb04d385885"
   116  	downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + treePath
   117  	return &api.FileResponse{
   118  		Content: &api.ContentsResponse{
   119  			Name:          filepath.Base(treePath),
   120  			Path:          treePath,
   121  			SHA:           "103ff9234cefeee5ec5361d22b49fbb04d385885",
   122  			LastCommitSHA: lastCommitSHA,
   123  			Type:          "file",
   124  			Size:          18,
   125  			Encoding:      &encoding,
   126  			Content:       &content,
   127  			URL:           &selfURL,
   128  			HTMLURL:       &htmlURL,
   129  			GitURL:        &gitURL,
   130  			DownloadURL:   &downloadURL,
   131  			Links: &api.FileLinksResponse{
   132  				Self:    &selfURL,
   133  				GitURL:  &gitURL,
   134  				HTMLURL: &htmlURL,
   135  			},
   136  		},
   137  		Commit: &api.FileCommitResponse{
   138  			CommitMeta: api.CommitMeta{
   139  				URL: setting.AppURL + "api/v1/repos/user2/repo1/git/commits/" + commitID,
   140  				SHA: commitID,
   141  			},
   142  			HTMLURL: setting.AppURL + "user2/repo1/commit/" + commitID,
   143  			Author: &api.CommitUser{
   144  				Identity: api.Identity{
   145  					Name:  "User Two",
   146  					Email: "user2@noreply.example.org",
   147  				},
   148  				Date: time.Now().UTC().Format(time.RFC3339),
   149  			},
   150  			Committer: &api.CommitUser{
   151  				Identity: api.Identity{
   152  					Name:  "User Two",
   153  					Email: "user2@noreply.example.org",
   154  				},
   155  				Date: time.Now().UTC().Format(time.RFC3339),
   156  			},
   157  			Parents: []*api.CommitMeta{
   158  				{
   159  					URL: setting.AppURL + "api/v1/repos/user2/repo1/git/commits/65f1bf27bc3bf70f64657658635e66094edbcb4d",
   160  					SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
   161  				},
   162  			},
   163  			Message: "Updates README.md\n",
   164  			Tree: &api.CommitMeta{
   165  				URL: setting.AppURL + "api/v1/repos/user2/repo1/git/trees/f93e3a1a1525fb5b91020da86e44810c87a2d7bc",
   166  				SHA: "f93e3a1a1525fb5b91020git dda86e44810c87a2d7bc",
   167  			},
   168  		},
   169  		Verification: &api.PayloadCommitVerification{
   170  			Verified:  false,
   171  			Reason:    "gpg.error.not_signed_commit",
   172  			Signature: "",
   173  			Payload:   "",
   174  		},
   175  	}
   176  }
   177  
   178  func getExpectedFileResponseForRepofilesUpdate(commitID, filename, lastCommitSHA string) *api.FileResponse {
   179  	encoding := "base64"
   180  	content := "VGhpcyBpcyBVUERBVEVEIGNvbnRlbnQgZm9yIHRoZSBSRUFETUUgZmlsZQ=="
   181  	selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + filename + "?ref=master"
   182  	htmlURL := setting.AppURL + "user2/repo1/src/branch/master/" + filename
   183  	gitURL := setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/dbf8d00e022e05b7e5cf7e535de857de57925647"
   184  	downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + filename
   185  	return &api.FileResponse{
   186  		Content: &api.ContentsResponse{
   187  			Name:          filename,
   188  			Path:          filename,
   189  			SHA:           "dbf8d00e022e05b7e5cf7e535de857de57925647",
   190  			LastCommitSHA: lastCommitSHA,
   191  			Type:          "file",
   192  			Size:          43,
   193  			Encoding:      &encoding,
   194  			Content:       &content,
   195  			URL:           &selfURL,
   196  			HTMLURL:       &htmlURL,
   197  			GitURL:        &gitURL,
   198  			DownloadURL:   &downloadURL,
   199  			Links: &api.FileLinksResponse{
   200  				Self:    &selfURL,
   201  				GitURL:  &gitURL,
   202  				HTMLURL: &htmlURL,
   203  			},
   204  		},
   205  		Commit: &api.FileCommitResponse{
   206  			CommitMeta: api.CommitMeta{
   207  				URL: setting.AppURL + "api/v1/repos/user2/repo1/git/commits/" + commitID,
   208  				SHA: commitID,
   209  			},
   210  			HTMLURL: setting.AppURL + "user2/repo1/commit/" + commitID,
   211  			Author: &api.CommitUser{
   212  				Identity: api.Identity{
   213  					Name:  "User Two",
   214  					Email: "user2@noreply.example.org",
   215  				},
   216  				Date: time.Now().UTC().Format(time.RFC3339),
   217  			},
   218  			Committer: &api.CommitUser{
   219  				Identity: api.Identity{
   220  					Name:  "User Two",
   221  					Email: "user2@noreply.example.org",
   222  				},
   223  				Date: time.Now().UTC().Format(time.RFC3339),
   224  			},
   225  			Parents: []*api.CommitMeta{
   226  				{
   227  					URL: setting.AppURL + "api/v1/repos/user2/repo1/git/commits/65f1bf27bc3bf70f64657658635e66094edbcb4d",
   228  					SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
   229  				},
   230  			},
   231  			Message: "Updates README.md\n",
   232  			Tree: &api.CommitMeta{
   233  				URL: setting.AppURL + "api/v1/repos/user2/repo1/git/trees/f93e3a1a1525fb5b91020da86e44810c87a2d7bc",
   234  				SHA: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc",
   235  			},
   236  		},
   237  		Verification: &api.PayloadCommitVerification{
   238  			Verified:  false,
   239  			Reason:    "gpg.error.not_signed_commit",
   240  			Signature: "",
   241  			Payload:   "",
   242  		},
   243  	}
   244  }
   245  
   246  func TestChangeRepoFilesForCreate(t *testing.T) {
   247  	// setup
   248  	onGiteaRun(t, func(t *testing.T, u *url.URL) {
   249  		ctx, _ := contexttest.MockContext(t, "user2/repo1")
   250  		ctx.SetParams(":id", "1")
   251  		contexttest.LoadRepo(t, ctx, 1)
   252  		contexttest.LoadRepoCommit(t, ctx)
   253  		contexttest.LoadUser(t, ctx, 2)
   254  		contexttest.LoadGitRepo(t, ctx)
   255  		defer ctx.Repo.GitRepo.Close()
   256  
   257  		repo := ctx.Repo.Repository
   258  		doer := ctx.Doer
   259  		opts := getCreateRepoFilesOptions(repo)
   260  
   261  		// test
   262  		filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
   263  
   264  		// asserts
   265  		assert.NoError(t, err)
   266  		gitRepo, _ := gitrepo.OpenRepository(git.DefaultContext, repo)
   267  		defer gitRepo.Close()
   268  
   269  		commitID, _ := gitRepo.GetBranchCommitID(opts.NewBranch)
   270  		lastCommit, _ := gitRepo.GetCommitByPath("new/file.txt")
   271  		expectedFileResponse := getExpectedFileResponseForRepofilesCreate(commitID, lastCommit.ID.String())
   272  		assert.NotNil(t, expectedFileResponse)
   273  		if expectedFileResponse != nil {
   274  			assert.EqualValues(t, expectedFileResponse.Content, filesResponse.Files[0])
   275  			assert.EqualValues(t, expectedFileResponse.Commit.SHA, filesResponse.Commit.SHA)
   276  			assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL)
   277  			assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, filesResponse.Commit.Author.Email)
   278  			assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, filesResponse.Commit.Author.Name)
   279  		}
   280  	})
   281  }
   282  
   283  func TestChangeRepoFilesForUpdate(t *testing.T) {
   284  	// setup
   285  	onGiteaRun(t, func(t *testing.T, u *url.URL) {
   286  		ctx, _ := contexttest.MockContext(t, "user2/repo1")
   287  		ctx.SetParams(":id", "1")
   288  		contexttest.LoadRepo(t, ctx, 1)
   289  		contexttest.LoadRepoCommit(t, ctx)
   290  		contexttest.LoadUser(t, ctx, 2)
   291  		contexttest.LoadGitRepo(t, ctx)
   292  		defer ctx.Repo.GitRepo.Close()
   293  
   294  		repo := ctx.Repo.Repository
   295  		doer := ctx.Doer
   296  		opts := getUpdateRepoFilesOptions(repo)
   297  
   298  		// test
   299  		filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
   300  
   301  		// asserts
   302  		assert.NoError(t, err)
   303  		gitRepo, _ := gitrepo.OpenRepository(git.DefaultContext, repo)
   304  		defer gitRepo.Close()
   305  
   306  		commit, _ := gitRepo.GetBranchCommit(opts.NewBranch)
   307  		lastCommit, _ := commit.GetCommitByPath(opts.Files[0].TreePath)
   308  		expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String())
   309  		assert.EqualValues(t, expectedFileResponse.Content, filesResponse.Files[0])
   310  		assert.EqualValues(t, expectedFileResponse.Commit.SHA, filesResponse.Commit.SHA)
   311  		assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL)
   312  		assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, filesResponse.Commit.Author.Email)
   313  		assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, filesResponse.Commit.Author.Name)
   314  	})
   315  }
   316  
   317  func TestChangeRepoFilesForUpdateWithFileMove(t *testing.T) {
   318  	// setup
   319  	onGiteaRun(t, func(t *testing.T, u *url.URL) {
   320  		ctx, _ := contexttest.MockContext(t, "user2/repo1")
   321  		ctx.SetParams(":id", "1")
   322  		contexttest.LoadRepo(t, ctx, 1)
   323  		contexttest.LoadRepoCommit(t, ctx)
   324  		contexttest.LoadUser(t, ctx, 2)
   325  		contexttest.LoadGitRepo(t, ctx)
   326  		defer ctx.Repo.GitRepo.Close()
   327  
   328  		repo := ctx.Repo.Repository
   329  		doer := ctx.Doer
   330  		opts := getUpdateRepoFilesOptions(repo)
   331  		opts.Files[0].FromTreePath = "README.md"
   332  		opts.Files[0].TreePath = "README_new.md" // new file name, README_new.md
   333  
   334  		// test
   335  		filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
   336  
   337  		// asserts
   338  		assert.NoError(t, err)
   339  		gitRepo, _ := gitrepo.OpenRepository(git.DefaultContext, repo)
   340  		defer gitRepo.Close()
   341  
   342  		commit, _ := gitRepo.GetBranchCommit(opts.NewBranch)
   343  		lastCommit, _ := commit.GetCommitByPath(opts.Files[0].TreePath)
   344  		expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String())
   345  		// assert that the old file no longer exists in the last commit of the branch
   346  		fromEntry, err := commit.GetTreeEntryByPath(opts.Files[0].FromTreePath)
   347  		switch err.(type) {
   348  		case git.ErrNotExist:
   349  			// correct, continue
   350  		default:
   351  			t.Fatalf("expected git.ErrNotExist, got:%v", err)
   352  		}
   353  		toEntry, err := commit.GetTreeEntryByPath(opts.Files[0].TreePath)
   354  		assert.NoError(t, err)
   355  		assert.Nil(t, fromEntry)  // Should no longer exist here
   356  		assert.NotNil(t, toEntry) // Should exist here
   357  		// assert SHA has remained the same but paths use the new file name
   358  		assert.EqualValues(t, expectedFileResponse.Content.SHA, filesResponse.Files[0].SHA)
   359  		assert.EqualValues(t, expectedFileResponse.Content.Name, filesResponse.Files[0].Name)
   360  		assert.EqualValues(t, expectedFileResponse.Content.Path, filesResponse.Files[0].Path)
   361  		assert.EqualValues(t, expectedFileResponse.Content.URL, filesResponse.Files[0].URL)
   362  		assert.EqualValues(t, expectedFileResponse.Commit.SHA, filesResponse.Commit.SHA)
   363  		assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL)
   364  	})
   365  }
   366  
   367  // Test opts with branch names removed, should get same results as above test
   368  func TestChangeRepoFilesWithoutBranchNames(t *testing.T) {
   369  	// setup
   370  	onGiteaRun(t, func(t *testing.T, u *url.URL) {
   371  		ctx, _ := contexttest.MockContext(t, "user2/repo1")
   372  		ctx.SetParams(":id", "1")
   373  		contexttest.LoadRepo(t, ctx, 1)
   374  		contexttest.LoadRepoCommit(t, ctx)
   375  		contexttest.LoadUser(t, ctx, 2)
   376  		contexttest.LoadGitRepo(t, ctx)
   377  		defer ctx.Repo.GitRepo.Close()
   378  
   379  		repo := ctx.Repo.Repository
   380  		doer := ctx.Doer
   381  		opts := getUpdateRepoFilesOptions(repo)
   382  		opts.OldBranch = ""
   383  		opts.NewBranch = ""
   384  
   385  		// test
   386  		filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
   387  
   388  		// asserts
   389  		assert.NoError(t, err)
   390  		gitRepo, _ := gitrepo.OpenRepository(git.DefaultContext, repo)
   391  		defer gitRepo.Close()
   392  
   393  		commit, _ := gitRepo.GetBranchCommit(repo.DefaultBranch)
   394  		lastCommit, _ := commit.GetCommitByPath(opts.Files[0].TreePath)
   395  		expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String())
   396  		assert.EqualValues(t, expectedFileResponse.Content, filesResponse.Files[0])
   397  	})
   398  }
   399  
   400  func TestChangeRepoFilesForDelete(t *testing.T) {
   401  	onGiteaRun(t, testDeleteRepoFiles)
   402  }
   403  
   404  func testDeleteRepoFiles(t *testing.T, u *url.URL) {
   405  	// setup
   406  	unittest.PrepareTestEnv(t)
   407  	ctx, _ := contexttest.MockContext(t, "user2/repo1")
   408  	ctx.SetParams(":id", "1")
   409  	contexttest.LoadRepo(t, ctx, 1)
   410  	contexttest.LoadRepoCommit(t, ctx)
   411  	contexttest.LoadUser(t, ctx, 2)
   412  	contexttest.LoadGitRepo(t, ctx)
   413  	defer ctx.Repo.GitRepo.Close()
   414  	repo := ctx.Repo.Repository
   415  	doer := ctx.Doer
   416  	opts := getDeleteRepoFilesOptions(repo)
   417  
   418  	t.Run("Delete README.md file", func(t *testing.T) {
   419  		filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
   420  		assert.NoError(t, err)
   421  		expectedFileResponse := getExpectedFileResponseForRepofilesDelete(u)
   422  		assert.NotNil(t, filesResponse)
   423  		assert.Nil(t, filesResponse.Files[0])
   424  		assert.EqualValues(t, expectedFileResponse.Commit.Message, filesResponse.Commit.Message)
   425  		assert.EqualValues(t, expectedFileResponse.Commit.Author.Identity, filesResponse.Commit.Author.Identity)
   426  		assert.EqualValues(t, expectedFileResponse.Commit.Committer.Identity, filesResponse.Commit.Committer.Identity)
   427  		assert.EqualValues(t, expectedFileResponse.Verification, filesResponse.Verification)
   428  	})
   429  
   430  	t.Run("Verify README.md has been deleted", func(t *testing.T) {
   431  		filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
   432  		assert.Nil(t, filesResponse)
   433  		expectedError := "repository file does not exist [path: " + opts.Files[0].TreePath + "]"
   434  		assert.EqualError(t, err, expectedError)
   435  	})
   436  }
   437  
   438  // Test opts with branch names removed, same results
   439  func TestChangeRepoFilesForDeleteWithoutBranchNames(t *testing.T) {
   440  	onGiteaRun(t, testDeleteRepoFilesWithoutBranchNames)
   441  }
   442  
   443  func testDeleteRepoFilesWithoutBranchNames(t *testing.T, u *url.URL) {
   444  	// setup
   445  	unittest.PrepareTestEnv(t)
   446  	ctx, _ := contexttest.MockContext(t, "user2/repo1")
   447  	ctx.SetParams(":id", "1")
   448  	contexttest.LoadRepo(t, ctx, 1)
   449  	contexttest.LoadRepoCommit(t, ctx)
   450  	contexttest.LoadUser(t, ctx, 2)
   451  	contexttest.LoadGitRepo(t, ctx)
   452  	defer ctx.Repo.GitRepo.Close()
   453  
   454  	repo := ctx.Repo.Repository
   455  	doer := ctx.Doer
   456  	opts := getDeleteRepoFilesOptions(repo)
   457  	opts.OldBranch = ""
   458  	opts.NewBranch = ""
   459  
   460  	t.Run("Delete README.md without Branch Name", func(t *testing.T) {
   461  		filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
   462  		assert.NoError(t, err)
   463  		expectedFileResponse := getExpectedFileResponseForRepofilesDelete(u)
   464  		assert.NotNil(t, filesResponse)
   465  		assert.Nil(t, filesResponse.Files[0])
   466  		assert.EqualValues(t, expectedFileResponse.Commit.Message, filesResponse.Commit.Message)
   467  		assert.EqualValues(t, expectedFileResponse.Commit.Author.Identity, filesResponse.Commit.Author.Identity)
   468  		assert.EqualValues(t, expectedFileResponse.Commit.Committer.Identity, filesResponse.Commit.Committer.Identity)
   469  		assert.EqualValues(t, expectedFileResponse.Verification, filesResponse.Verification)
   470  	})
   471  }
   472  
   473  func TestChangeRepoFilesErrors(t *testing.T) {
   474  	// setup
   475  	onGiteaRun(t, func(t *testing.T, u *url.URL) {
   476  		ctx, _ := contexttest.MockContext(t, "user2/repo1")
   477  		ctx.SetParams(":id", "1")
   478  		contexttest.LoadRepo(t, ctx, 1)
   479  		contexttest.LoadRepoCommit(t, ctx)
   480  		contexttest.LoadUser(t, ctx, 2)
   481  		contexttest.LoadGitRepo(t, ctx)
   482  		defer ctx.Repo.GitRepo.Close()
   483  
   484  		repo := ctx.Repo.Repository
   485  		doer := ctx.Doer
   486  
   487  		t.Run("bad branch", func(t *testing.T) {
   488  			opts := getUpdateRepoFilesOptions(repo)
   489  			opts.OldBranch = "bad_branch"
   490  			filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
   491  			assert.Error(t, err)
   492  			assert.Nil(t, filesResponse)
   493  			expectedError := "branch does not exist [name: " + opts.OldBranch + "]"
   494  			assert.EqualError(t, err, expectedError)
   495  		})
   496  
   497  		t.Run("bad SHA", func(t *testing.T) {
   498  			opts := getUpdateRepoFilesOptions(repo)
   499  			origSHA := opts.Files[0].SHA
   500  			opts.Files[0].SHA = "bad_sha"
   501  			filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
   502  			assert.Nil(t, filesResponse)
   503  			assert.Error(t, err)
   504  			expectedError := "sha does not match [given: " + opts.Files[0].SHA + ", expected: " + origSHA + "]"
   505  			assert.EqualError(t, err, expectedError)
   506  		})
   507  
   508  		t.Run("new branch already exists", func(t *testing.T) {
   509  			opts := getUpdateRepoFilesOptions(repo)
   510  			opts.NewBranch = "develop"
   511  			filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
   512  			assert.Nil(t, filesResponse)
   513  			assert.Error(t, err)
   514  			expectedError := "branch already exists [name: " + opts.NewBranch + "]"
   515  			assert.EqualError(t, err, expectedError)
   516  		})
   517  
   518  		t.Run("treePath is empty:", func(t *testing.T) {
   519  			opts := getUpdateRepoFilesOptions(repo)
   520  			opts.Files[0].TreePath = ""
   521  			filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
   522  			assert.Nil(t, filesResponse)
   523  			assert.Error(t, err)
   524  			expectedError := "path contains a malformed path component [path: ]"
   525  			assert.EqualError(t, err, expectedError)
   526  		})
   527  
   528  		t.Run("treePath is a git directory:", func(t *testing.T) {
   529  			opts := getUpdateRepoFilesOptions(repo)
   530  			opts.Files[0].TreePath = ".git"
   531  			filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
   532  			assert.Nil(t, filesResponse)
   533  			assert.Error(t, err)
   534  			expectedError := "path contains a malformed path component [path: " + opts.Files[0].TreePath + "]"
   535  			assert.EqualError(t, err, expectedError)
   536  		})
   537  
   538  		t.Run("create file that already exists", func(t *testing.T) {
   539  			opts := getCreateRepoFilesOptions(repo)
   540  			opts.Files[0].TreePath = "README.md" // already exists
   541  			fileResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
   542  			assert.Nil(t, fileResponse)
   543  			assert.Error(t, err)
   544  			expectedError := "repository file already exists [path: " + opts.Files[0].TreePath + "]"
   545  			assert.EqualError(t, err, expectedError)
   546  		})
   547  	})
   548  }