Skip to content

Commit 2f6ce78

Browse files
committed
Merge branch 'fix-webhook-request' of github.com:wxiaoguang/gitea into fix-webhook-request
2 parents d897184 + ec5db50 commit 2f6ce78

File tree

23 files changed

+700
-131
lines changed

23 files changed

+700
-131
lines changed

cmd/cmd.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func initDBDisableConsole(disableConsole bool) error {
6565
setting.InitDBConfig()
6666

6767
setting.NewXORMLogService(disableConsole)
68-
if err := db.SetEngine(); err != nil {
68+
if err := db.InitEngine(); err != nil {
6969
return fmt.Errorf("models.SetEngine: %v", err)
7070
}
7171
return nil

cmd/doctor.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ func runRecreateTable(ctx *cli.Context) error {
9696
setting.Cfg.Section("log").Key("XORM").SetValue(",")
9797

9898
setting.NewXORMLogService(!ctx.Bool("debug"))
99-
if err := db.SetEngine(); err != nil {
99+
if err := db.InitEngine(); err != nil {
100100
fmt.Println(err)
101101
fmt.Println("Check if you are using the right config file. You can use a --config directive to specify one.")
102102
return nil
@@ -114,7 +114,7 @@ func runRecreateTable(ctx *cli.Context) error {
114114
}
115115
recreateTables := migrations.RecreateTables(beans...)
116116

117-
return db.NewEngine(context.Background(), func(x *xorm.Engine) error {
117+
return db.InitEngineWithMigration(context.Background(), func(x *xorm.Engine) error {
118118
if err := migrations.EnsureUpToDate(x); err != nil {
119119
return err
120120
}

cmd/dump.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ func runDump(ctx *cli.Context) error {
173173
}
174174
setting.NewServices() // cannot access session settings otherwise
175175

176-
err := db.SetEngine()
176+
err := db.InitEngine()
177177
if err != nil {
178178
return err
179179
}

cmd/migrate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func runMigrate(ctx *cli.Context) error {
3535
log.Info("Configuration file: %s", setting.CustomConf)
3636
setting.InitDBConfig()
3737

38-
if err := db.NewEngine(context.Background(), migrations.Migrate); err != nil {
38+
if err := db.InitEngineWithMigration(context.Background(), migrations.Migrate); err != nil {
3939
log.Fatal("Failed to initialize ORM engine: %v", err)
4040
return err
4141
}

cmd/migrate_storage.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ func runMigrateStorage(ctx *cli.Context) error {
118118
log.Info("Configuration file: %s", setting.CustomConf)
119119
setting.InitDBConfig()
120120

121-
if err := db.NewEngine(context.Background(), migrations.Migrate); err != nil {
121+
if err := db.InitEngineWithMigration(context.Background(), migrations.Migrate); err != nil {
122122
log.Fatal("Failed to initialize ORM engine: %v", err)
123123
return err
124124
}

contrib/pr/checkout.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ func runPR() {
9595
setting.Database.LogSQL = true
9696
//x, err = xorm.NewEngine("sqlite3", "file::memory:?cache=shared")
9797

98-
db.NewEngine(context.Background(), func(_ *xorm.Engine) error {
98+
db.InitEngineWithMigration(context.Background(), func(_ *xorm.Engine) error {
9999
return nil
100100
})
101101
db.HasEngine = true

integrations/migration-test/migration_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -256,21 +256,21 @@ func doMigrationTest(t *testing.T, version string) {
256256

257257
setting.NewXORMLogService(false)
258258

259-
err := db.NewEngine(context.Background(), wrappedMigrate)
259+
err := db.InitEngineWithMigration(context.Background(), wrappedMigrate)
260260
assert.NoError(t, err)
261261
currentEngine.Close()
262262

263263
beans, _ := db.NamesToBean()
264264

265-
err = db.NewEngine(context.Background(), func(x *xorm.Engine) error {
265+
err = db.InitEngineWithMigration(context.Background(), func(x *xorm.Engine) error {
266266
currentEngine = x
267267
return migrations.RecreateTables(beans...)(x)
268268
})
269269
assert.NoError(t, err)
270270
currentEngine.Close()
271271

272272
// We do this a second time to ensure that there is not a problem with retained indices
273-
err = db.NewEngine(context.Background(), func(x *xorm.Engine) error {
273+
err = db.InitEngineWithMigration(context.Background(), func(x *xorm.Engine) error {
274274
currentEngine = x
275275
return migrations.RecreateTables(beans...)(x)
276276
})

models/db/engine.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ func init() {
9595
}
9696
}
9797

98-
// GetNewEngine returns a new xorm engine from the configuration
99-
func GetNewEngine() (*xorm.Engine, error) {
98+
// NewEngine returns a new xorm engine from the configuration
99+
func NewEngine() (*xorm.Engine, error) {
100100
connStr, err := setting.DBConnStr()
101101
if err != nil {
102102
return nil, err
@@ -128,11 +128,11 @@ func syncTables() error {
128128
return x.StoreEngine("InnoDB").Sync2(tables...)
129129
}
130130

131-
// NewInstallTestEngine creates a new xorm.Engine for testing during install
131+
// InitInstallEngineWithMigration creates a new xorm.Engine for testing during install
132132
//
133133
// This function will cause the basic database schema to be created
134-
func NewInstallTestEngine(ctx context.Context, migrateFunc func(*xorm.Engine) error) (err error) {
135-
x, err = GetNewEngine()
134+
func InitInstallEngineWithMigration(ctx context.Context, migrateFunc func(*xorm.Engine) error) (err error) {
135+
x, err = NewEngine()
136136
if err != nil {
137137
return fmt.Errorf("failed to connect to database: %w", err)
138138
}
@@ -160,9 +160,9 @@ func NewInstallTestEngine(ctx context.Context, migrateFunc func(*xorm.Engine) er
160160
return syncTables()
161161
}
162162

163-
// SetEngine sets the xorm.Engine
164-
func SetEngine() (err error) {
165-
x, err = GetNewEngine()
163+
// InitEngine sets the xorm.Engine
164+
func InitEngine() (err error) {
165+
x, err = NewEngine()
166166
if err != nil {
167167
return fmt.Errorf("Failed to connect to database: %v", err)
168168
}
@@ -178,13 +178,13 @@ func SetEngine() (err error) {
178178
return nil
179179
}
180180

181-
// NewEngine initializes a new xorm.Engine
181+
// InitEngineWithMigration initializes a new xorm.Engine
182182
// This function must never call .Sync2() if the provided migration function fails.
183183
// When called from the "doctor" command, the migration function is a version check
184184
// that prevents the doctor from fixing anything in the database if the migration level
185185
// is different from the expected value.
186-
func NewEngine(ctx context.Context, migrateFunc func(*xorm.Engine) error) (err error) {
187-
if err = SetEngine(); err != nil {
186+
func InitEngineWithMigration(ctx context.Context, migrateFunc func(*xorm.Engine) error) (err error) {
187+
if err = InitEngine(); err != nil {
188188
return err
189189
}
190190

models/migrations/migrations_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,9 @@ func removeAllWithRetry(dir string) error {
8484
return err
8585
}
8686

87-
// SetEngine sets the xorm.Engine
88-
func SetEngine() (*xorm.Engine, error) {
89-
x, err := db.GetNewEngine()
87+
// newEngine sets the xorm.Engine
88+
func newEngine() (*xorm.Engine, error) {
89+
x, err := db.NewEngine()
9090
if err != nil {
9191
return x, fmt.Errorf("Failed to connect to database: %v", err)
9292
}
@@ -212,7 +212,7 @@ func prepareTestEnv(t *testing.T, skip int, syncModels ...interface{}) (*xorm.En
212212
return nil, deferFn
213213
}
214214

215-
x, err := SetEngine()
215+
x, err := newEngine()
216216
assert.NoError(t, err)
217217
if x != nil {
218218
oldDefer := deferFn

modules/csv/csv.go

Lines changed: 88 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,18 @@ package csv
77
import (
88
"bytes"
99
stdcsv "encoding/csv"
10-
"errors"
1110
"io"
11+
"path/filepath"
1212
"regexp"
1313
"strings"
1414

15+
"code.gitea.io/gitea/modules/markup"
1516
"code.gitea.io/gitea/modules/translation"
1617
"code.gitea.io/gitea/modules/util"
1718
)
1819

19-
var quoteRegexp = regexp.MustCompile(`["'][\s\S]+?["']`)
20+
const maxLines = 10
21+
const guessSampleSize = 1e4 // 10k
2022

2123
// CreateReader creates a csv.Reader with the given delimiter.
2224
func CreateReader(input io.Reader, delimiter rune) *stdcsv.Reader {
@@ -30,70 +32,95 @@ func CreateReader(input io.Reader, delimiter rune) *stdcsv.Reader {
3032
return rd
3133
}
3234

33-
// CreateReaderAndGuessDelimiter tries to guess the field delimiter from the content and creates a csv.Reader.
34-
// Reads at most 10k bytes.
35-
func CreateReaderAndGuessDelimiter(rd io.Reader) (*stdcsv.Reader, error) {
36-
var data = make([]byte, 1e4)
35+
// CreateReaderAndDetermineDelimiter tries to guess the field delimiter from the content and creates a csv.Reader.
36+
// Reads at most guessSampleSize bytes.
37+
func CreateReaderAndDetermineDelimiter(ctx *markup.RenderContext, rd io.Reader) (*stdcsv.Reader, error) {
38+
var data = make([]byte, guessSampleSize)
3739
size, err := util.ReadAtMost(rd, data)
3840
if err != nil {
3941
return nil, err
4042
}
4143

4244
return CreateReader(
4345
io.MultiReader(bytes.NewReader(data[:size]), rd),
44-
guessDelimiter(data[:size]),
46+
determineDelimiter(ctx, data[:size]),
4547
), nil
4648
}
4749

48-
// guessDelimiter scores the input CSV data against delimiters, and returns the best match.
49-
func guessDelimiter(data []byte) rune {
50-
maxLines := 10
51-
text := quoteRegexp.ReplaceAllLiteralString(string(data), "")
52-
lines := strings.SplitN(text, "\n", maxLines+1)
53-
lines = lines[:util.Min(maxLines, len(lines))]
54-
55-
delimiters := []rune{',', ';', '\t', '|', '@'}
56-
bestDelim := delimiters[0]
57-
bestScore := 0.0
58-
for _, delim := range delimiters {
59-
score := scoreDelimiter(lines, delim)
60-
if score > bestScore {
61-
bestScore = score
62-
bestDelim = delim
63-
}
50+
// determineDelimiter takes a RenderContext and if it isn't nil and the Filename has an extension that specifies the delimiter,
51+
// it is used as the delimiter. Otherwise we call guessDelimiter with the data passed
52+
func determineDelimiter(ctx *markup.RenderContext, data []byte) rune {
53+
extension := ".csv"
54+
if ctx != nil {
55+
extension = strings.ToLower(filepath.Ext(ctx.Filename))
56+
}
57+
58+
var delimiter rune
59+
switch extension {
60+
case ".tsv":
61+
delimiter = '\t'
62+
case ".psv":
63+
delimiter = '|'
64+
default:
65+
delimiter = guessDelimiter(data)
6466
}
6567

66-
return bestDelim
68+
return delimiter
6769
}
6870

69-
// scoreDelimiter uses a count & regularity metric to evaluate a delimiter against lines of CSV.
70-
func scoreDelimiter(lines []string, delim rune) float64 {
71-
countTotal := 0
72-
countLineMax := 0
73-
linesNotEqual := 0
71+
// quoteRegexp follows the RFC-4180 CSV standard for when double-quotes are used to enclose fields, then a double-quote appearing inside a
72+
// field must be escaped by preceding it with another double quote. https://www.ietf.org/rfc/rfc4180.txt
73+
// This finds all quoted strings that have escaped quotes.
74+
var quoteRegexp = regexp.MustCompile(`"[^"]*"`)
7475

75-
for _, line := range lines {
76-
if len(line) == 0 {
77-
continue
78-
}
76+
// removeQuotedStrings uses the quoteRegexp to remove all quoted strings so that we can reliably have each row on one line
77+
// (quoted strings often have new lines within the string)
78+
func removeQuotedString(text string) string {
79+
return quoteRegexp.ReplaceAllLiteralString(text, "")
80+
}
7981

80-
countLine := strings.Count(line, string(delim))
81-
countTotal += countLine
82-
if countLine != countLineMax {
83-
if countLineMax != 0 {
84-
linesNotEqual++
85-
}
86-
countLineMax = util.Max(countLine, countLineMax)
87-
}
82+
// guessDelimiter takes up to maxLines of the CSV text, iterates through the possible delimiters, and sees if the CSV Reader reads it without throwing any errors.
83+
// If more than one delimiter passes, the delimiter that results in the most columns is returned.
84+
func guessDelimiter(data []byte) rune {
85+
delimiter := guessFromBeforeAfterQuotes(data)
86+
if delimiter != 0 {
87+
return delimiter
8888
}
8989

90-
return float64(countTotal) * (1 - float64(linesNotEqual)/float64(len(lines)))
90+
// Removes quoted values so we don't have columns with new lines in them
91+
text := removeQuotedString(string(data))
92+
93+
// Make the text just be maxLines or less, ignoring truncated lines
94+
lines := strings.SplitN(text, "\n", maxLines+1) // Will contain at least one line, and if there are more than MaxLines, the last item holds the rest of the lines
95+
if len(lines) > maxLines {
96+
// If the length of lines is > maxLines we know we have the max number of lines, trim it to maxLines
97+
lines = lines[:maxLines]
98+
} else if len(lines) > 1 && len(data) >= guessSampleSize {
99+
// Even with data >= guessSampleSize, we don't have maxLines + 1 (no extra lines, must have really long lines)
100+
// thus the last line is probably have a truncated line. Drop the last line if len(lines) > 1
101+
lines = lines[:len(lines)-1]
102+
}
103+
104+
// Put lines back together as a string
105+
text = strings.Join(lines, "\n")
106+
107+
delimiters := []rune{',', '\t', ';', '|', '@'}
108+
validDelim := delimiters[0]
109+
validDelimColCount := 0
110+
for _, delim := range delimiters {
111+
csvReader := stdcsv.NewReader(strings.NewReader(text))
112+
csvReader.Comma = delim
113+
if rows, err := csvReader.ReadAll(); err == nil && len(rows) > 0 && len(rows[0]) > validDelimColCount {
114+
validDelim = delim
115+
validDelimColCount = len(rows[0])
116+
}
117+
}
118+
return validDelim
91119
}
92120

93121
// FormatError converts csv errors into readable messages.
94122
func FormatError(err error, locale translation.Locale) (string, error) {
95-
var perr *stdcsv.ParseError
96-
if errors.As(err, &perr) {
123+
if perr, ok := err.(*stdcsv.ParseError); ok {
97124
if perr.Err == stdcsv.ErrFieldCount {
98125
return locale.Tr("repo.error.csv.invalid_field_count", perr.Line), nil
99126
}
@@ -102,3 +129,20 @@ func FormatError(err error, locale translation.Locale) (string, error) {
102129

103130
return "", err
104131
}
132+
133+
// Looks for possible delimiters right before or after (with spaces after the former) double quotes with closing quotes
134+
var beforeAfterQuotes = regexp.MustCompile(`([,@\t;|]{0,1}) *(?:"[^"]*")+([,@\t;|]{0,1})`)
135+
136+
// guessFromBeforeAfterQuotes guesses the limiter by finding a double quote that has a valid delimiter before it and a closing quote,
137+
// or a double quote with a closing quote and a valid delimiter after it
138+
func guessFromBeforeAfterQuotes(data []byte) rune {
139+
rs := beforeAfterQuotes.FindStringSubmatch(string(data)) // returns first match, or nil if none
140+
if rs != nil {
141+
if rs[1] != "" {
142+
return rune(rs[1][0]) // delimiter found left of quoted string
143+
} else if rs[2] != "" {
144+
return rune(rs[2][0]) // delimiter found right of quoted string
145+
}
146+
}
147+
return 0 // no match found
148+
}

0 commit comments

Comments
 (0)