Skip to content

Commit 8354670

Browse files
authored
Prevent hang in git cat-file if repository is not a valid repository and other fixes (#17991)
This PR contains multiple fixes. The most important of which is: * Prevent hang in git cat-file if the repository is not a valid repository Unfortunately it appears that if git cat-file is run in an invalid repository it will hang until stdin is closed. This will result in deadlocked /pulls pages and dangling git cat-file calls if a broken repository is tried to be reviewed or pulls exists for a broken repository. Fix #14734 Fix #9271 Fix #16113 Otherwise there are a few small other fixes included which this PR was initially intending to fix: * Fix panic on partial compares due to missing PullRequestWorkInProgressPrefixes * Fix links on pulls pages due to regression from #17551 - by making most /issues routes match /pulls too - Fix #17983 * Fix links on feeds pages due to another regression from #17551 but also fix issue with syncing tags - Fix #17943 * Add missing locale entries for oauth group claims * Prevent NPEs if ColorFormat is called on nil users, repos or teams.
1 parent 6e7d28c commit 8354670

File tree

18 files changed

+209
-26
lines changed

18 files changed

+209
-26
lines changed

integrations/integration_test.go

+38
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,25 @@ func prepareTestEnv(t testing.TB, skip ...int) func() {
255255
assert.NoError(t, util.RemoveAll(setting.RepoRootPath))
256256

257257
assert.NoError(t, util.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), setting.RepoRootPath))
258+
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
259+
if err != nil {
260+
assert.NoError(t, err, "unable to read the new repo root: %v\n", err)
261+
}
262+
for _, ownerDir := range ownerDirs {
263+
if !ownerDir.Type().IsDir() {
264+
continue
265+
}
266+
repoDirs, err := os.ReadDir(filepath.Join(setting.RepoRootPath, ownerDir.Name()))
267+
if err != nil {
268+
assert.NoError(t, err, "unable to read the new repo root: %v\n", err)
269+
}
270+
for _, repoDir := range repoDirs {
271+
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "pack"), 0755)
272+
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "info"), 0755)
273+
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "heads"), 0755)
274+
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "tag"), 0755)
275+
}
276+
}
258277

259278
return deferFn
260279
}
@@ -532,4 +551,23 @@ func resetFixtures(t *testing.T) {
532551
assert.NoError(t, unittest.LoadFixtures())
533552
assert.NoError(t, util.RemoveAll(setting.RepoRootPath))
534553
assert.NoError(t, util.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), setting.RepoRootPath))
554+
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
555+
if err != nil {
556+
assert.NoError(t, err, "unable to read the new repo root: %v\n", err)
557+
}
558+
for _, ownerDir := range ownerDirs {
559+
if !ownerDir.Type().IsDir() {
560+
continue
561+
}
562+
repoDirs, err := os.ReadDir(filepath.Join(setting.RepoRootPath, ownerDir.Name()))
563+
if err != nil {
564+
assert.NoError(t, err, "unable to read the new repo root: %v\n", err)
565+
}
566+
for _, repoDir := range repoDirs {
567+
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "pack"), 0755)
568+
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "info"), 0755)
569+
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "heads"), 0755)
570+
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "tag"), 0755)
571+
}
572+
}
535573
}

integrations/migration-test/migration_test.go

+19
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,25 @@ func initMigrationTest(t *testing.T) func() {
6161
assert.True(t, len(setting.RepoRootPath) != 0)
6262
assert.NoError(t, util.RemoveAll(setting.RepoRootPath))
6363
assert.NoError(t, util.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), setting.RepoRootPath))
64+
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
65+
if err != nil {
66+
assert.NoError(t, err, "unable to read the new repo root: %v\n", err)
67+
}
68+
for _, ownerDir := range ownerDirs {
69+
if !ownerDir.Type().IsDir() {
70+
continue
71+
}
72+
repoDirs, err := os.ReadDir(filepath.Join(setting.RepoRootPath, ownerDir.Name()))
73+
if err != nil {
74+
assert.NoError(t, err, "unable to read the new repo root: %v\n", err)
75+
}
76+
for _, repoDir := range repoDirs {
77+
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "pack"), 0755)
78+
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "info"), 0755)
79+
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "heads"), 0755)
80+
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "tag"), 0755)
81+
}
82+
}
6483

6584
git.CheckLFSVersion()
6685
setting.InitDBConfig()

models/action.go

+16
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"code.gitea.io/gitea/modules/log"
2424
"code.gitea.io/gitea/modules/setting"
2525
"code.gitea.io/gitea/modules/timeutil"
26+
"code.gitea.io/gitea/modules/util"
2627

2728
"xorm.io/builder"
2829
)
@@ -252,6 +253,21 @@ func (a *Action) GetBranch() string {
252253
return strings.TrimPrefix(a.RefName, git.BranchPrefix)
253254
}
254255

256+
// GetRefLink returns the action's ref link.
257+
func (a *Action) GetRefLink() string {
258+
switch {
259+
case strings.HasPrefix(a.RefName, git.BranchPrefix):
260+
return a.GetRepoLink() + "/src/branch/" + util.PathEscapeSegments(strings.TrimPrefix(a.RefName, git.BranchPrefix))
261+
case strings.HasPrefix(a.RefName, git.TagPrefix):
262+
return a.GetRepoLink() + "/src/tag/" + util.PathEscapeSegments(strings.TrimPrefix(a.RefName, git.TagPrefix))
263+
case len(a.RefName) == 40 && git.SHAPattern.MatchString(a.RefName):
264+
return a.GetRepoLink() + "/src/commit/" + a.RefName
265+
default:
266+
// FIXME: we will just assume it's a branch - this was the old way - at some point we may want to enforce that there is always a ref here.
267+
return a.GetRepoLink() + "/src/branch/" + util.PathEscapeSegments(strings.TrimPrefix(a.RefName, git.BranchPrefix))
268+
}
269+
}
270+
255271
// GetTag returns the action's repository tag.
256272
func (a *Action) GetTag() string {
257273
return strings.TrimPrefix(a.RefName, git.TagPrefix)

models/migrations/migrations_test.go

+19
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,25 @@ func prepareTestEnv(t *testing.T, skip int, syncModels ...interface{}) (*xorm.En
207207

208208
assert.NoError(t, com.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"),
209209
setting.RepoRootPath))
210+
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
211+
if err != nil {
212+
assert.NoError(t, err, "unable to read the new repo root: %v\n", err)
213+
}
214+
for _, ownerDir := range ownerDirs {
215+
if !ownerDir.Type().IsDir() {
216+
continue
217+
}
218+
repoDirs, err := os.ReadDir(filepath.Join(setting.RepoRootPath, ownerDir.Name()))
219+
if err != nil {
220+
assert.NoError(t, err, "unable to read the new repo root: %v\n", err)
221+
}
222+
for _, repoDir := range repoDirs {
223+
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "pack"), 0755)
224+
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "info"), 0755)
225+
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "heads"), 0755)
226+
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "tag"), 0755)
227+
}
228+
}
210229

211230
if err := deleteDB(); err != nil {
212231
t.Errorf("unable to reset database: %v", err)

models/org_team.go

+8
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,14 @@ func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) {
114114

115115
// ColorFormat provides a basic color format for a Team
116116
func (t *Team) ColorFormat(s fmt.State) {
117+
if t == nil {
118+
log.ColorFprintf(s, "%d:%s (OrgID: %d) %-v",
119+
log.NewColoredIDValue(0),
120+
"<nil>",
121+
log.NewColoredIDValue(0),
122+
0)
123+
return
124+
}
117125
log.ColorFprintf(s, "%d:%s (OrgID: %d) %-v",
118126
log.NewColoredIDValue(t.ID),
119127
t.Name,

models/repo/repo.go

+7
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,13 @@ func (repo *Repository) SanitizedOriginalURL() string {
173173

174174
// ColorFormat returns a colored string to represent this repo
175175
func (repo *Repository) ColorFormat(s fmt.State) {
176+
if repo == nil {
177+
log.ColorFprintf(s, "%d:%s/%s",
178+
log.NewColoredIDValue(0),
179+
"<nil>",
180+
"<nil>")
181+
return
182+
}
176183
log.ColorFprintf(s, "%d:%s/%s",
177184
log.NewColoredIDValue(repo.ID),
178185
repo.OwnerName,

models/unittest/testdb.go

+37
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,26 @@ func MainTest(m *testing.M, pathToGiteaRoot string, fixtureFiles ...string) {
104104
fatalTestError("util.CopyDir: %v\n", err)
105105
}
106106

107+
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
108+
if err != nil {
109+
fatalTestError("unable to read the new repo root: %v\n", err)
110+
}
111+
for _, ownerDir := range ownerDirs {
112+
if !ownerDir.Type().IsDir() {
113+
continue
114+
}
115+
repoDirs, err := os.ReadDir(filepath.Join(setting.RepoRootPath, ownerDir.Name()))
116+
if err != nil {
117+
fatalTestError("unable to read the new repo root: %v\n", err)
118+
}
119+
for _, repoDir := range repoDirs {
120+
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "pack"), 0755)
121+
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "info"), 0755)
122+
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "heads"), 0755)
123+
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "tag"), 0755)
124+
}
125+
}
126+
107127
exitStatus := m.Run()
108128
if err = util.RemoveAll(repoRootPath); err != nil {
109129
fatalTestError("util.RemoveAll: %v\n", err)
@@ -152,5 +172,22 @@ func PrepareTestEnv(t testing.TB) {
152172
assert.NoError(t, util.RemoveAll(setting.RepoRootPath))
153173
metaPath := filepath.Join(giteaRoot, "integrations", "gitea-repositories-meta")
154174
assert.NoError(t, util.CopyDir(metaPath, setting.RepoRootPath))
175+
176+
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
177+
assert.NoError(t, err)
178+
for _, ownerDir := range ownerDirs {
179+
if !ownerDir.Type().IsDir() {
180+
continue
181+
}
182+
repoDirs, err := os.ReadDir(filepath.Join(setting.RepoRootPath, ownerDir.Name()))
183+
assert.NoError(t, err)
184+
for _, repoDir := range repoDirs {
185+
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "pack"), 0755)
186+
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "info"), 0755)
187+
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "heads"), 0755)
188+
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "tag"), 0755)
189+
}
190+
}
191+
155192
base.SetupGiteaRoot() // Makes sure GITEA_ROOT is set
156193
}

models/user/user.go

+6
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,12 @@ type SearchOrganizationsOptions struct {
160160

161161
// ColorFormat writes a colored string to identify this struct
162162
func (u *User) ColorFormat(s fmt.State) {
163+
if u == nil {
164+
log.ColorFprintf(s, "%d:%s",
165+
log.NewColoredIDValue(0),
166+
log.NewColoredValue("<nil>"))
167+
return
168+
}
163169
log.ColorFprintf(s, "%d:%s",
164170
log.NewColoredIDValue(u.ID),
165171
log.NewColoredValue(u.Name))

modules/git/batch_reader.go

+14
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,20 @@ type WriteCloserError interface {
2727
CloseWithError(err error) error
2828
}
2929

30+
// EnsureValidGitRepository runs git rev-parse in the repository path - thus ensuring that the repository is a valid repository.
31+
// Run before opening git cat-file.
32+
// This is needed otherwise the git cat-file will hang for invalid repositories.
33+
func EnsureValidGitRepository(ctx context.Context, repoPath string) error {
34+
stderr := strings.Builder{}
35+
err := NewCommandContext(ctx, "rev-parse").
36+
SetDescription(fmt.Sprintf("%s rev-parse [repo_path: %s]", GitExecutable, repoPath)).
37+
RunInDirFullPipeline(repoPath, nil, &stderr, nil)
38+
if err != nil {
39+
return ConcatenateError(err, (&stderr).String())
40+
}
41+
return nil
42+
}
43+
3044
// CatFileBatchCheck opens git cat-file --batch-check in the provided repo and returns a stdin pipe, a stdout reader and cancel function
3145
func CatFileBatchCheck(ctx context.Context, repoPath string) (WriteCloserError, *bufio.Reader, func()) {
3246
batchStdinReader, batchStdinWriter := io.Pipe()

modules/git/repo_base_nogogit.go

+5
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ func OpenRepositoryCtx(ctx context.Context, repoPath string) (*Repository, error
5050
return nil, errors.New("no such file or directory")
5151
}
5252

53+
// Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first!
54+
if err := EnsureValidGitRepository(ctx, repoPath); err != nil {
55+
return nil, err
56+
}
57+
5358
repo := &Repository{
5459
Path: repoPath,
5560
tagCache: newObjectCache(),

modules/git/repo_commit_nogogit.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ func (repo *Repository) ResolveReference(name string) (string, error) {
3737
func (repo *Repository) GetRefCommitID(name string) (string, error) {
3838
wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx)
3939
defer cancel()
40-
_, _ = wr.Write([]byte(name + "\n"))
40+
_, err := wr.Write([]byte(name + "\n"))
41+
if err != nil {
42+
return "", err
43+
}
4144
shaBs, _, _, err := ReadBatchLine(rd)
4245
if IsErrNotExist(err) {
4346
return "", ErrNotExist{name, ""}

modules/indexer/code/bleve.go

+6
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,12 @@ func (b *BleveIndexer) Index(repo *repo_model.Repository, sha string, changes *r
275275
batch := gitea_bleve.NewFlushingBatch(b.indexer, maxBatchSize)
276276
if len(changes.Updates) > 0 {
277277

278+
// Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first!
279+
if err := git.EnsureValidGitRepository(git.DefaultContext, repo.RepoPath()); err != nil {
280+
log.Error("Unable to open git repo: %s for %-v: %v", repo.RepoPath(), repo, err)
281+
return err
282+
}
283+
278284
batchWriter, batchReader, cancel := git.CatFileBatch(git.DefaultContext, repo.RepoPath())
279285
defer cancel()
280286

modules/indexer/code/elastic_search.go

+5
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,11 @@ func (b *ElasticSearchIndexer) addDelete(filename string, repo *repo_model.Repos
247247
func (b *ElasticSearchIndexer) Index(repo *repo_model.Repository, sha string, changes *repoChanges) error {
248248
reqs := make([]elastic.BulkableRequest, 0)
249249
if len(changes.Updates) > 0 {
250+
// Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first!
251+
if err := git.EnsureValidGitRepository(git.DefaultContext, repo.RepoPath()); err != nil {
252+
log.Error("Unable to open git repo: %s for %-v: %v", repo.RepoPath(), repo, err)
253+
return err
254+
}
250255

251256
batchWriter, batchReader, cancel := git.CatFileBatch(git.DefaultContext, repo.RepoPath())
252257
defer cancel()

options/locale/locale_en-US.ini

+3
Original file line numberDiff line numberDiff line change
@@ -2532,6 +2532,9 @@ auths.oauth2_required_claim_name = Required Claim Name
25322532
auths.oauth2_required_claim_name_helper = Set this name to restrict login from this source to users with a claim with this name
25332533
auths.oauth2_required_claim_value = Required Claim Value
25342534
auths.oauth2_required_claim_value_helper = Set this value to restrict login from this source to users with a claim with this name and value
2535+
auths.oauth2_group_claim_name = Claim name providing group names for this source. (Optional)
2536+
auths.oauth2_admin_group = Group Claim value for administrator users. (Optional - requires claim name above)
2537+
auths.oauth2_restricted_group = Group Claim value for restricted users. (Optional - requires claim name above)
25352538
auths.enable_auto_register = Enable Auto Registration
25362539
auths.sspi_auto_create_users = Automatically create users
25372540
auths.sspi_auto_create_users_helper = Allow SSPI auth method to automatically create new accounts for users that login for the first time

routers/web/repo/compare.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,7 @@ func CompareDiff(ctx *context.Context) {
685685
return
686686
}
687687

688+
ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
688689
ctx.Data["DirectComparison"] = ci.DirectComparison
689690
ctx.Data["OtherCompareSeparator"] = ".."
690691
ctx.Data["CompareSeparator"] = "..."
@@ -762,7 +763,6 @@ func CompareDiff(ctx *context.Context) {
762763
ctx.Data["IsDiffCompare"] = true
763764
ctx.Data["RequireTribute"] = true
764765
ctx.Data["RequireEasyMDE"] = true
765-
ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
766766
setTemplateIfExists(ctx, pullRequestTemplateKey, nil, pullRequestTemplateCandidates)
767767
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
768768
upload.AddUploadContext(ctx, "comment")

routers/web/web.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -718,7 +718,7 @@ func RegisterRoutes(m *web.Route) {
718718
}, context.RepoMustNotBeArchived(), reqRepoIssueReader)
719719
// FIXME: should use different URLs but mostly same logic for comments of issue and pull request.
720720
// So they can apply their own enable/disable logic on routers.
721-
m.Group("/issues", func() {
721+
m.Group("/{type:issues|pulls}", func() {
722722
m.Group("/{index}", func() {
723723
m.Post("/title", repo.UpdateIssueTitle)
724724
m.Post("/content", repo.UpdateIssueContent)

services/pull/pull.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,8 @@ func GetIssuesLastCommitStatus(issues models.IssueList) (map[int64]*models.Commi
746746
if !ok {
747747
gitRepo, err = git.OpenRepository(issue.Repo.RepoPath())
748748
if err != nil {
749-
return nil, err
749+
log.Error("Cannot open git repository %-v for issue #%d[%d]. Error: %v", issue.Repo, issue.Index, issue.ID, err)
750+
continue
750751
}
751752
gitRepos[issue.RepoID] = gitRepo
752753
}

0 commit comments

Comments
 (0)