mirror of
https://github.com/go-gitea/gitea.git
synced 2025-12-15 21:45:35 +08:00
Compare commits
7 Commits
e9cb9c3785
...
1029c2fa7b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1029c2fa7b | ||
|
|
29057ea55f | ||
|
|
ac8308b5cb | ||
|
|
5934ac688d | ||
|
|
d93812bed1 | ||
|
|
3ae8c1d828 | ||
|
|
cc0298d955 |
28
.github/workflows/pull-db-tests.yml
vendored
28
.github/workflows/pull-db-tests.yml
vendored
@ -49,15 +49,13 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Add hosts to /etc/hosts
|
- name: Add hosts to /etc/hosts
|
||||||
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 pgsql ldap minio" | sudo tee -a /etc/hosts'
|
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 pgsql ldap minio" | sudo tee -a /etc/hosts'
|
||||||
- run: make deps-backend
|
|
||||||
- run: make backend
|
|
||||||
env:
|
|
||||||
TAGS: bindata
|
|
||||||
- name: run migration tests
|
- name: run migration tests
|
||||||
run: make test-pgsql-migration
|
run: make test-pgsql-migration
|
||||||
|
env:
|
||||||
|
TAGS: bindata
|
||||||
- name: run tests
|
- name: run tests
|
||||||
run: make test-pgsql
|
run: make test-pgsql
|
||||||
timeout-minutes: 50
|
timeout-minutes: 75
|
||||||
env:
|
env:
|
||||||
TAGS: bindata gogit
|
TAGS: bindata gogit
|
||||||
RACE_ENABLED: true
|
RACE_ENABLED: true
|
||||||
@ -77,12 +75,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- run: make deps-backend
|
- name: run migration tests
|
||||||
- run: GOEXPERIMENT='' make backend
|
run: GOEXPERIMENT='' make test-sqlite-migration
|
||||||
env:
|
env:
|
||||||
TAGS: bindata gogit sqlite sqlite_unlock_notify
|
TAGS: bindata gogit sqlite sqlite_unlock_notify
|
||||||
- name: run migration tests
|
|
||||||
run: make test-sqlite-migration
|
|
||||||
- name: run tests
|
- name: run tests
|
||||||
run: GOEXPERIMENT='' make test-sqlite
|
run: GOEXPERIMENT='' make test-sqlite
|
||||||
timeout-minutes: 50
|
timeout-minutes: 50
|
||||||
@ -139,10 +135,6 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Add hosts to /etc/hosts
|
- name: Add hosts to /etc/hosts
|
||||||
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 minio devstoreaccount1.azurite.local mysql elasticsearch meilisearch smtpimap" | sudo tee -a /etc/hosts'
|
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 minio devstoreaccount1.azurite.local mysql elasticsearch meilisearch smtpimap" | sudo tee -a /etc/hosts'
|
||||||
- run: make deps-backend
|
|
||||||
- run: make backend
|
|
||||||
env:
|
|
||||||
TAGS: bindata
|
|
||||||
- name: unit-tests
|
- name: unit-tests
|
||||||
run: make unit-test-coverage test-check
|
run: make unit-test-coverage test-check
|
||||||
env:
|
env:
|
||||||
@ -194,12 +186,10 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Add hosts to /etc/hosts
|
- name: Add hosts to /etc/hosts
|
||||||
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mysql elasticsearch smtpimap" | sudo tee -a /etc/hosts'
|
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mysql elasticsearch smtpimap" | sudo tee -a /etc/hosts'
|
||||||
- run: make deps-backend
|
|
||||||
- run: make backend
|
|
||||||
env:
|
|
||||||
TAGS: bindata
|
|
||||||
- name: run migration tests
|
- name: run migration tests
|
||||||
run: make test-mysql-migration
|
run: make test-mysql-migration
|
||||||
|
env:
|
||||||
|
TAGS: bindata
|
||||||
- name: run tests
|
- name: run tests
|
||||||
# run: make integration-test-coverage (at the moment, no coverage is really handled)
|
# run: make integration-test-coverage (at the moment, no coverage is really handled)
|
||||||
run: make test-mysql
|
run: make test-mysql
|
||||||
@ -236,11 +226,9 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Add hosts to /etc/hosts
|
- name: Add hosts to /etc/hosts
|
||||||
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mssql devstoreaccount1.azurite.local" | sudo tee -a /etc/hosts'
|
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mssql devstoreaccount1.azurite.local" | sudo tee -a /etc/hosts'
|
||||||
- run: make deps-backend
|
- run: make test-mssql-migration
|
||||||
- run: make backend
|
|
||||||
env:
|
env:
|
||||||
TAGS: bindata
|
TAGS: bindata
|
||||||
- run: make test-mssql-migration
|
|
||||||
- name: run tests
|
- name: run tests
|
||||||
run: make test-mssql
|
run: make test-mssql
|
||||||
timeout-minutes: 50
|
timeout-minutes: 50
|
||||||
|
|||||||
45
Makefile
45
Makefile
@ -50,9 +50,10 @@ ifeq ($(HAS_GO), yes)
|
|||||||
CGO_CFLAGS ?= $(shell $(GO) env CGO_CFLAGS) $(CGO_EXTRA_CFLAGS)
|
CGO_CFLAGS ?= $(shell $(GO) env CGO_CFLAGS) $(CGO_EXTRA_CFLAGS)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
CGO_ENABLED ?= 0
|
ifeq (arm64,$(shell $(GO) env GOARCH))
|
||||||
ifneq (,$(findstring sqlite,$(TAGS))$(findstring pam,$(TAGS)))
|
CGO_ENABLED ?= 0
|
||||||
CGO_ENABLED = 1
|
else
|
||||||
|
CGO_ENABLED ?= 1
|
||||||
endif
|
endif
|
||||||
|
|
||||||
STATIC ?=
|
STATIC ?=
|
||||||
@ -462,7 +463,7 @@ coverage:
|
|||||||
$(GO) run tools/gocovmerge.go integration.coverage-bodged.out coverage-bodged.out > coverage.all
|
$(GO) run tools/gocovmerge.go integration.coverage-bodged.out coverage-bodged.out > coverage.all
|
||||||
|
|
||||||
.PHONY: unit-test-coverage
|
.PHONY: unit-test-coverage
|
||||||
unit-test-coverage:
|
unit-test-coverage: backend
|
||||||
@echo "Running unit-test-coverage $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
|
@echo "Running unit-test-coverage $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
|
||||||
@$(GO) test $(GOTESTFLAGS) -timeout=20m -tags='$(TEST_TAGS)' -cover -coverprofile coverage.out $(GO_TEST_PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1
|
@$(GO) test $(GOTESTFLAGS) -timeout=20m -tags='$(TEST_TAGS)' -cover -coverprofile coverage.out $(GO_TEST_PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1
|
||||||
|
|
||||||
@ -502,11 +503,11 @@ generate-ini-sqlite:
|
|||||||
tests/sqlite.ini.tmpl > tests/sqlite.ini
|
tests/sqlite.ini.tmpl > tests/sqlite.ini
|
||||||
|
|
||||||
.PHONY: test-sqlite
|
.PHONY: test-sqlite
|
||||||
test-sqlite: integrations.sqlite.test generate-ini-sqlite
|
test-sqlite: integrations.sqlite.test generate-ini-sqlite backend
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.sqlite.test
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.sqlite.test
|
||||||
|
|
||||||
.PHONY: test-sqlite\#%
|
.PHONY: test-sqlite\#%
|
||||||
test-sqlite\#%: integrations.sqlite.test generate-ini-sqlite
|
test-sqlite\#%: integrations.sqlite.test generate-ini-sqlite backend
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.sqlite.test -test.run $(subst .,/,$*)
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.sqlite.test -test.run $(subst .,/,$*)
|
||||||
|
|
||||||
.PHONY: test-sqlite-migration
|
.PHONY: test-sqlite-migration
|
||||||
@ -523,11 +524,11 @@ generate-ini-mysql:
|
|||||||
tests/mysql.ini.tmpl > tests/mysql.ini
|
tests/mysql.ini.tmpl > tests/mysql.ini
|
||||||
|
|
||||||
.PHONY: test-mysql
|
.PHONY: test-mysql
|
||||||
test-mysql: integrations.mysql.test generate-ini-mysql
|
test-mysql: integrations.mysql.test generate-ini-mysql backend
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./integrations.mysql.test
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./integrations.mysql.test
|
||||||
|
|
||||||
.PHONY: test-mysql\#%
|
.PHONY: test-mysql\#%
|
||||||
test-mysql\#%: integrations.mysql.test generate-ini-mysql
|
test-mysql\#%: integrations.mysql.test generate-ini-mysql backend
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./integrations.mysql.test -test.run $(subst .,/,$*)
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./integrations.mysql.test -test.run $(subst .,/,$*)
|
||||||
|
|
||||||
.PHONY: test-mysql-migration
|
.PHONY: test-mysql-migration
|
||||||
@ -546,11 +547,11 @@ generate-ini-pgsql:
|
|||||||
tests/pgsql.ini.tmpl > tests/pgsql.ini
|
tests/pgsql.ini.tmpl > tests/pgsql.ini
|
||||||
|
|
||||||
.PHONY: test-pgsql
|
.PHONY: test-pgsql
|
||||||
test-pgsql: integrations.pgsql.test generate-ini-pgsql
|
test-pgsql: integrations.pgsql.test generate-ini-pgsql backend
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./integrations.pgsql.test
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./integrations.pgsql.test
|
||||||
|
|
||||||
.PHONY: test-pgsql\#%
|
.PHONY: test-pgsql\#%
|
||||||
test-pgsql\#%: integrations.pgsql.test generate-ini-pgsql
|
test-pgsql\#%: integrations.pgsql.test generate-ini-pgsql backend
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./integrations.pgsql.test -test.run $(subst .,/,$*)
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./integrations.pgsql.test -test.run $(subst .,/,$*)
|
||||||
|
|
||||||
.PHONY: test-pgsql-migration
|
.PHONY: test-pgsql-migration
|
||||||
@ -567,11 +568,11 @@ generate-ini-mssql:
|
|||||||
tests/mssql.ini.tmpl > tests/mssql.ini
|
tests/mssql.ini.tmpl > tests/mssql.ini
|
||||||
|
|
||||||
.PHONY: test-mssql
|
.PHONY: test-mssql
|
||||||
test-mssql: integrations.mssql.test generate-ini-mssql
|
test-mssql: integrations.mssql.test generate-ini-mssql backend
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./integrations.mssql.test
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./integrations.mssql.test
|
||||||
|
|
||||||
.PHONY: test-mssql\#%
|
.PHONY: test-mssql\#%
|
||||||
test-mssql\#%: integrations.mssql.test generate-ini-mssql
|
test-mssql\#%: integrations.mssql.test generate-ini-mssql backend
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./integrations.mssql.test -test.run $(subst .,/,$*)
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./integrations.mssql.test -test.run $(subst .,/,$*)
|
||||||
|
|
||||||
.PHONY: test-mssql-migration
|
.PHONY: test-mssql-migration
|
||||||
@ -622,27 +623,27 @@ test-e2e-mssql\#%: playwright e2e.mssql.test generate-ini-mssql
|
|||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./e2e.mssql.test -test.run TestE2e/$*
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./e2e.mssql.test -test.run TestE2e/$*
|
||||||
|
|
||||||
.PHONY: bench-sqlite
|
.PHONY: bench-sqlite
|
||||||
bench-sqlite: integrations.sqlite.test generate-ini-sqlite
|
bench-sqlite: integrations.sqlite.test generate-ini-sqlite backend
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.sqlite.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.sqlite.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
|
||||||
|
|
||||||
.PHONY: bench-mysql
|
.PHONY: bench-mysql
|
||||||
bench-mysql: integrations.mysql.test generate-ini-mysql
|
bench-mysql: integrations.mysql.test generate-ini-mysql backend
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./integrations.mysql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./integrations.mysql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
|
||||||
|
|
||||||
.PHONY: bench-mssql
|
.PHONY: bench-mssql
|
||||||
bench-mssql: integrations.mssql.test generate-ini-mssql
|
bench-mssql: integrations.mssql.test generate-ini-mssql backend
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./integrations.mssql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./integrations.mssql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
|
||||||
|
|
||||||
.PHONY: bench-pgsql
|
.PHONY: bench-pgsql
|
||||||
bench-pgsql: integrations.pgsql.test generate-ini-pgsql
|
bench-pgsql: integrations.pgsql.test generate-ini-pgsql backend
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./integrations.pgsql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./integrations.pgsql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
|
||||||
|
|
||||||
.PHONY: integration-test-coverage
|
.PHONY: integration-test-coverage
|
||||||
integration-test-coverage: integrations.cover.test generate-ini-mysql
|
integration-test-coverage: integrations.cover.test generate-ini-mysql backend
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./integrations.cover.test -test.coverprofile=integration.coverage.out
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./integrations.cover.test -test.coverprofile=integration.coverage.out
|
||||||
|
|
||||||
.PHONY: integration-test-coverage-sqlite
|
.PHONY: integration-test-coverage-sqlite
|
||||||
integration-test-coverage-sqlite: integrations.cover.sqlite.test generate-ini-sqlite
|
integration-test-coverage-sqlite: integrations.cover.sqlite.test generate-ini-sqlite backend
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.cover.sqlite.test -test.coverprofile=integration.coverage.out
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.cover.sqlite.test -test.coverprofile=integration.coverage.out
|
||||||
|
|
||||||
integrations.mysql.test: git-check $(GO_SOURCES)
|
integrations.mysql.test: git-check $(GO_SOURCES)
|
||||||
@ -664,22 +665,22 @@ integrations.cover.sqlite.test: git-check $(GO_SOURCES)
|
|||||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -coverpkg $(shell echo $(GO_TEST_PACKAGES) | tr ' ' ',') -o integrations.cover.sqlite.test -tags '$(TEST_TAGS)'
|
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -coverpkg $(shell echo $(GO_TEST_PACKAGES) | tr ' ' ',') -o integrations.cover.sqlite.test -tags '$(TEST_TAGS)'
|
||||||
|
|
||||||
.PHONY: migrations.mysql.test
|
.PHONY: migrations.mysql.test
|
||||||
migrations.mysql.test: $(GO_SOURCES) generate-ini-mysql
|
migrations.mysql.test: $(GO_SOURCES) generate-ini-mysql backend
|
||||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.mysql.test
|
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.mysql.test
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./migrations.mysql.test
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./migrations.mysql.test
|
||||||
|
|
||||||
.PHONY: migrations.pgsql.test
|
.PHONY: migrations.pgsql.test
|
||||||
migrations.pgsql.test: $(GO_SOURCES) generate-ini-pgsql
|
migrations.pgsql.test: $(GO_SOURCES) generate-ini-pgsql backend
|
||||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.pgsql.test
|
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.pgsql.test
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./migrations.pgsql.test
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./migrations.pgsql.test
|
||||||
|
|
||||||
.PHONY: migrations.mssql.test
|
.PHONY: migrations.mssql.test
|
||||||
migrations.mssql.test: $(GO_SOURCES) generate-ini-mssql
|
migrations.mssql.test: $(GO_SOURCES) generate-ini-mssql backend
|
||||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.mssql.test
|
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.mssql.test
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./migrations.mssql.test
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./migrations.mssql.test
|
||||||
|
|
||||||
.PHONY: migrations.sqlite.test
|
.PHONY: migrations.sqlite.test
|
||||||
migrations.sqlite.test: $(GO_SOURCES) generate-ini-sqlite
|
migrations.sqlite.test: $(GO_SOURCES) generate-ini-sqlite backend
|
||||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.sqlite.test -tags '$(TEST_TAGS)'
|
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.sqlite.test -tags '$(TEST_TAGS)'
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./migrations.sqlite.test
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./migrations.sqlite.test
|
||||||
|
|
||||||
|
|||||||
@ -5,12 +5,10 @@ package charset
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
@ -23,60 +21,39 @@ import (
|
|||||||
var UTF8BOM = []byte{'\xef', '\xbb', '\xbf'}
|
var UTF8BOM = []byte{'\xef', '\xbb', '\xbf'}
|
||||||
|
|
||||||
type ConvertOpts struct {
|
type ConvertOpts struct {
|
||||||
KeepBOM bool
|
KeepBOM bool
|
||||||
|
ErrorReplacement []byte
|
||||||
|
ErrorReturnOrigin bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ToUTF8WithFallbackReaderPrefetchSize = 16 * 1024
|
||||||
|
|
||||||
// ToUTF8WithFallbackReader detects the encoding of content and converts to UTF-8 reader if possible
|
// ToUTF8WithFallbackReader detects the encoding of content and converts to UTF-8 reader if possible
|
||||||
func ToUTF8WithFallbackReader(rd io.Reader, opts ConvertOpts) io.Reader {
|
func ToUTF8WithFallbackReader(rd io.Reader, opts ConvertOpts) io.Reader {
|
||||||
buf := make([]byte, 2048)
|
buf := make([]byte, ToUTF8WithFallbackReaderPrefetchSize)
|
||||||
n, err := util.ReadAtMost(rd, buf)
|
n, err := util.ReadAtMost(rd, buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return io.MultiReader(bytes.NewReader(MaybeRemoveBOM(buf[:n], opts)), rd)
|
// read error occurs, don't do any processing
|
||||||
}
|
|
||||||
|
|
||||||
charsetLabel, err := DetectEncoding(buf[:n])
|
|
||||||
if err != nil || charsetLabel == "UTF-8" {
|
|
||||||
return io.MultiReader(bytes.NewReader(MaybeRemoveBOM(buf[:n], opts)), rd)
|
|
||||||
}
|
|
||||||
|
|
||||||
encoding, _ := charset.Lookup(charsetLabel)
|
|
||||||
if encoding == nil {
|
|
||||||
return io.MultiReader(bytes.NewReader(buf[:n]), rd)
|
return io.MultiReader(bytes.NewReader(buf[:n]), rd)
|
||||||
}
|
}
|
||||||
|
|
||||||
return transform.NewReader(
|
charsetLabel, _ := DetectEncoding(buf[:n])
|
||||||
io.MultiReader(
|
if charsetLabel == "UTF-8" {
|
||||||
bytes.NewReader(MaybeRemoveBOM(buf[:n], opts)),
|
// is utf-8, try to remove BOM and read it as-is
|
||||||
rd,
|
return io.MultiReader(bytes.NewReader(maybeRemoveBOM(buf[:n], opts)), rd)
|
||||||
),
|
|
||||||
encoding.NewDecoder(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToUTF8 converts content to UTF8 encoding
|
|
||||||
func ToUTF8(content []byte, opts ConvertOpts) (string, error) {
|
|
||||||
charsetLabel, err := DetectEncoding(content)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
} else if charsetLabel == "UTF-8" {
|
|
||||||
return string(MaybeRemoveBOM(content, opts)), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
encoding, _ := charset.Lookup(charsetLabel)
|
encoding, _ := charset.Lookup(charsetLabel)
|
||||||
if encoding == nil {
|
if encoding == nil {
|
||||||
return string(content), fmt.Errorf("Unknown encoding: %s", charsetLabel)
|
// unknown charset, don't do any processing
|
||||||
|
return io.MultiReader(bytes.NewReader(buf[:n]), rd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is an error, we concatenate the nicely decoded part and the
|
// convert from charset to utf-8
|
||||||
// original left over. This way we won't lose much data.
|
return transform.NewReader(
|
||||||
result, n, err := transform.Bytes(encoding.NewDecoder(), content)
|
io.MultiReader(bytes.NewReader(buf[:n]), rd),
|
||||||
if err != nil {
|
encoding.NewDecoder(),
|
||||||
result = append(result, content[n:]...)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
result = MaybeRemoveBOM(result, opts)
|
|
||||||
|
|
||||||
return string(result), err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToUTF8WithFallback detects the encoding of content and converts to UTF-8 if possible
|
// ToUTF8WithFallback detects the encoding of content and converts to UTF-8 if possible
|
||||||
@ -85,73 +62,84 @@ func ToUTF8WithFallback(content []byte, opts ConvertOpts) []byte {
|
|||||||
return bs
|
return bs
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToUTF8DropErrors makes sure the return string is valid utf-8; attempts conversion if possible
|
func ToUTF8DropErrors(content []byte) []byte {
|
||||||
func ToUTF8DropErrors(content []byte, opts ConvertOpts) []byte {
|
return ToUTF8(content, ConvertOpts{ErrorReplacement: []byte{' '}})
|
||||||
charsetLabel, err := DetectEncoding(content)
|
}
|
||||||
if err != nil || charsetLabel == "UTF-8" {
|
|
||||||
return MaybeRemoveBOM(content, opts)
|
func ToUTF8(content []byte, opts ConvertOpts) []byte {
|
||||||
|
charsetLabel, _ := DetectEncoding(content)
|
||||||
|
if charsetLabel == "UTF-8" {
|
||||||
|
return maybeRemoveBOM(content, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
encoding, _ := charset.Lookup(charsetLabel)
|
encoding, _ := charset.Lookup(charsetLabel)
|
||||||
if encoding == nil {
|
if encoding == nil {
|
||||||
|
setting.PanicInDevOrTesting("unsupported detected charset %q, it shouldn't happen", charsetLabel)
|
||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
// We ignore any non-decodable parts from the file.
|
|
||||||
// Some parts might be lost
|
|
||||||
var decoded []byte
|
var decoded []byte
|
||||||
decoder := encoding.NewDecoder()
|
decoder := encoding.NewDecoder()
|
||||||
idx := 0
|
idx := 0
|
||||||
for {
|
for idx < len(content) {
|
||||||
result, n, err := transform.Bytes(decoder, content[idx:])
|
result, n, err := transform.Bytes(decoder, content[idx:])
|
||||||
decoded = append(decoded, result...)
|
decoded = append(decoded, result...)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
decoded = append(decoded, ' ')
|
if opts.ErrorReturnOrigin {
|
||||||
idx = idx + n + 1
|
return content
|
||||||
if idx >= len(content) {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
if opts.ErrorReplacement == nil {
|
||||||
|
decoded = append(decoded, content[idx+n])
|
||||||
|
} else {
|
||||||
|
decoded = append(decoded, opts.ErrorReplacement...)
|
||||||
|
}
|
||||||
|
idx += n + 1
|
||||||
}
|
}
|
||||||
|
return maybeRemoveBOM(decoded, opts)
|
||||||
return MaybeRemoveBOM(decoded, opts)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MaybeRemoveBOM removes a UTF-8 BOM from a []byte when opts.KeepBOM is false
|
// maybeRemoveBOM removes a UTF-8 BOM from a []byte when opts.KeepBOM is false
|
||||||
func MaybeRemoveBOM(content []byte, opts ConvertOpts) []byte {
|
func maybeRemoveBOM(content []byte, opts ConvertOpts) []byte {
|
||||||
if opts.KeepBOM {
|
if opts.KeepBOM {
|
||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
if len(content) > 2 && bytes.Equal(content[0:3], UTF8BOM) {
|
return bytes.TrimPrefix(content, UTF8BOM)
|
||||||
return content[3:]
|
|
||||||
}
|
|
||||||
return content
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DetectEncoding detect the encoding of content
|
// DetectEncoding detect the encoding of content
|
||||||
func DetectEncoding(content []byte) (string, error) {
|
// it always returns a detected or guessed "encoding" string, no matter error happens or not
|
||||||
|
func DetectEncoding(content []byte) (encoding string, _ error) {
|
||||||
// First we check if the content represents valid utf8 content excepting a truncated character at the end.
|
// First we check if the content represents valid utf8 content excepting a truncated character at the end.
|
||||||
|
|
||||||
// Now we could decode all the runes in turn but this is not necessarily the cheapest thing to do
|
// Now we could decode all the runes in turn but this is not necessarily the cheapest thing to do
|
||||||
// instead we walk backwards from the end to trim off a the incomplete character
|
// instead we walk backwards from the end to trim off the incomplete character
|
||||||
toValidate := content
|
toValidate := content
|
||||||
end := len(toValidate) - 1
|
end := len(toValidate) - 1
|
||||||
|
|
||||||
if end < 0 {
|
// U+0000 U+007F 0yyyzzzz
|
||||||
// no-op
|
// U+0080 U+07FF 110xxxyy 10yyzzzz
|
||||||
} else if toValidate[end]>>5 == 0b110 {
|
// U+0800 U+FFFF 1110wwww 10xxxxyy 10yyzzzz
|
||||||
// Incomplete 1 byte extension e.g. © <c2><a9> which has been truncated to <c2>
|
// U+010000 U+10FFFF 11110uvv 10vvwwww 10xxxxyy 10yyzzzz
|
||||||
toValidate = toValidate[:end]
|
cnt := 0
|
||||||
} else if end > 0 && toValidate[end]>>6 == 0b10 && toValidate[end-1]>>4 == 0b1110 {
|
for end >= 0 && cnt < 4 {
|
||||||
// Incomplete 2 byte extension e.g. ⛔ <e2><9b><94> which has been truncated to <e2><9b>
|
c := toValidate[end]
|
||||||
toValidate = toValidate[:end-1]
|
if c>>5 == 0b110 || c>>4 == 0b1110 || c>>3 == 0b11110 {
|
||||||
} else if end > 1 && toValidate[end]>>6 == 0b10 && toValidate[end-1]>>6 == 0b10 && toValidate[end-2]>>3 == 0b11110 {
|
// a leading byte
|
||||||
// Incomplete 3 byte extension e.g. 💩 <f0><9f><92><a9> which has been truncated to <f0><9f><92>
|
toValidate = toValidate[:end]
|
||||||
toValidate = toValidate[:end-2]
|
break
|
||||||
|
} else if c>>6 == 0b10 {
|
||||||
|
// a continuation byte
|
||||||
|
end--
|
||||||
|
} else {
|
||||||
|
// not an utf-8 byte
|
||||||
|
break
|
||||||
|
}
|
||||||
|
cnt++
|
||||||
}
|
}
|
||||||
|
|
||||||
if utf8.Valid(toValidate) {
|
if utf8.Valid(toValidate) {
|
||||||
log.Debug("Detected encoding: utf-8 (fast)")
|
|
||||||
return "UTF-8", nil
|
return "UTF-8", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,7 +148,7 @@ func DetectEncoding(content []byte) (string, error) {
|
|||||||
if len(content) < 1024 {
|
if len(content) < 1024 {
|
||||||
// Check if original content is valid
|
// Check if original content is valid
|
||||||
if _, err := textDetector.DetectBest(content); err != nil {
|
if _, err := textDetector.DetectBest(content); err != nil {
|
||||||
return "", err
|
return util.IfZero(setting.Repository.AnsiCharset, "UTF-8"), err
|
||||||
}
|
}
|
||||||
times := 1024 / len(content)
|
times := 1024 / len(content)
|
||||||
detectContent = make([]byte, 0, times*len(content))
|
detectContent = make([]byte, 0, times*len(content))
|
||||||
@ -171,14 +159,10 @@ func DetectEncoding(content []byte) (string, error) {
|
|||||||
detectContent = content
|
detectContent = content
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now we can't use DetectBest or just results[0] because the result isn't stable - so we need a tie break
|
// Now we can't use DetectBest or just results[0] because the result isn't stable - so we need a tie-break
|
||||||
results, err := textDetector.DetectAll(detectContent)
|
results, err := textDetector.DetectAll(detectContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == chardet.NotDetectedError && len(setting.Repository.AnsiCharset) > 0 {
|
return util.IfZero(setting.Repository.AnsiCharset, "UTF-8"), err
|
||||||
log.Debug("Using default AnsiCharset: %s", setting.Repository.AnsiCharset)
|
|
||||||
return setting.Repository.AnsiCharset, nil
|
|
||||||
}
|
|
||||||
return "", err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
topConfidence := results[0].Confidence
|
topConfidence := results[0].Confidence
|
||||||
@ -201,11 +185,9 @@ func DetectEncoding(content []byte) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: to properly decouple this function the fallback ANSI charset should be passed as an argument
|
// FIXME: to properly decouple this function the fallback ANSI charset should be passed as an argument
|
||||||
if topResult.Charset != "UTF-8" && len(setting.Repository.AnsiCharset) > 0 {
|
if topResult.Charset != "UTF-8" && setting.Repository.AnsiCharset != "" {
|
||||||
log.Debug("Using default AnsiCharset: %s", setting.Repository.AnsiCharset)
|
|
||||||
return setting.Repository.AnsiCharset, err
|
return setting.Repository.AnsiCharset, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("Detected encoding: %s", topResult.Charset)
|
return topResult.Charset, nil
|
||||||
return topResult.Charset, err
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,108 +4,89 @@
|
|||||||
package charset
|
package charset
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func resetDefaultCharsetsOrder() {
|
func TestMain(m *testing.M) {
|
||||||
defaultDetectedCharsetsOrder := make([]string, 0, len(setting.Repository.DetectedCharsetsOrder))
|
|
||||||
for _, charset := range setting.Repository.DetectedCharsetsOrder {
|
|
||||||
defaultDetectedCharsetsOrder = append(defaultDetectedCharsetsOrder, strings.ToLower(strings.TrimSpace(charset)))
|
|
||||||
}
|
|
||||||
setting.Repository.DetectedCharsetScore = map[string]int{}
|
setting.Repository.DetectedCharsetScore = map[string]int{}
|
||||||
i := 0
|
for i, charset := range setting.Repository.DetectedCharsetsOrder {
|
||||||
for _, charset := range defaultDetectedCharsetsOrder {
|
setting.Repository.DetectedCharsetScore[strings.ToLower(charset)] = i
|
||||||
canonicalCharset := strings.ToLower(strings.TrimSpace(charset))
|
|
||||||
if _, has := setting.Repository.DetectedCharsetScore[canonicalCharset]; !has {
|
|
||||||
setting.Repository.DetectedCharsetScore[canonicalCharset] = i
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
os.Exit(m.Run())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMaybeRemoveBOM(t *testing.T) {
|
func TestMaybeRemoveBOM(t *testing.T) {
|
||||||
res := MaybeRemoveBOM([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, ConvertOpts{})
|
res := maybeRemoveBOM([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, ConvertOpts{})
|
||||||
assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res)
|
assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res)
|
||||||
|
|
||||||
res = MaybeRemoveBOM([]byte{0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, ConvertOpts{})
|
res = maybeRemoveBOM([]byte{0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, ConvertOpts{})
|
||||||
assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res)
|
assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToUTF8(t *testing.T) {
|
func TestToUTF8(t *testing.T) {
|
||||||
resetDefaultCharsetsOrder()
|
|
||||||
|
|
||||||
// Note: golang compiler seems so behave differently depending on the current
|
// Note: golang compiler seems so behave differently depending on the current
|
||||||
// locale, so some conversions might behave differently. For that reason, we don't
|
// locale, so some conversions might behave differently. For that reason, we don't
|
||||||
// depend on particular conversions but in expected behaviors.
|
// depend on particular conversions but in expected behaviors.
|
||||||
|
|
||||||
res, err := ToUTF8([]byte{0x41, 0x42, 0x43}, ConvertOpts{})
|
res := ToUTF8([]byte{0x41, 0x42, 0x43}, ConvertOpts{})
|
||||||
assert.NoError(t, err)
|
assert.Equal(t, "ABC", string(res))
|
||||||
assert.Equal(t, "ABC", res)
|
|
||||||
|
|
||||||
// "áéíóú"
|
// "áéíóú"
|
||||||
res, err = ToUTF8([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, ConvertOpts{})
|
res = ToUTF8([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, ConvertOpts{})
|
||||||
assert.NoError(t, err)
|
assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res)
|
||||||
assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, []byte(res))
|
|
||||||
|
|
||||||
// "áéíóú"
|
// "áéíóú"
|
||||||
res, err = ToUTF8([]byte{
|
res = ToUTF8([]byte{
|
||||||
0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3,
|
0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3,
|
||||||
0xc3, 0xba,
|
0xc3, 0xba,
|
||||||
}, ConvertOpts{})
|
}, ConvertOpts{})
|
||||||
assert.NoError(t, err)
|
assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res)
|
||||||
assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, []byte(res))
|
|
||||||
|
|
||||||
res, err = ToUTF8([]byte{
|
res = ToUTF8([]byte{
|
||||||
0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63,
|
0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63,
|
||||||
0xF3, 0x6D, 0x6F, 0x20, 0xF1, 0x6F, 0x73, 0x41, 0x41, 0x41, 0x2e,
|
0xF3, 0x6D, 0x6F, 0x20, 0xF1, 0x6F, 0x73, 0x41, 0x41, 0x41, 0x2e,
|
||||||
}, ConvertOpts{})
|
}, ConvertOpts{})
|
||||||
assert.NoError(t, err)
|
|
||||||
stringMustStartWith(t, "Hola,", res)
|
stringMustStartWith(t, "Hola,", res)
|
||||||
stringMustEndWith(t, "AAA.", res)
|
stringMustEndWith(t, "AAA.", res)
|
||||||
|
|
||||||
res, err = ToUTF8([]byte{
|
res = ToUTF8([]byte{
|
||||||
0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63,
|
0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63,
|
||||||
0xF3, 0x6D, 0x6F, 0x20, 0x07, 0xA4, 0x6F, 0x73, 0x41, 0x41, 0x41, 0x2e,
|
0xF3, 0x6D, 0x6F, 0x20, 0x07, 0xA4, 0x6F, 0x73, 0x41, 0x41, 0x41, 0x2e,
|
||||||
}, ConvertOpts{})
|
}, ConvertOpts{})
|
||||||
assert.NoError(t, err)
|
|
||||||
stringMustStartWith(t, "Hola,", res)
|
stringMustStartWith(t, "Hola,", res)
|
||||||
stringMustEndWith(t, "AAA.", res)
|
stringMustEndWith(t, "AAA.", res)
|
||||||
|
|
||||||
res, err = ToUTF8([]byte{
|
res = ToUTF8([]byte{
|
||||||
0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63,
|
0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63,
|
||||||
0xF3, 0x6D, 0x6F, 0x20, 0x81, 0xA4, 0x6F, 0x73, 0x41, 0x41, 0x41, 0x2e,
|
0xF3, 0x6D, 0x6F, 0x20, 0x81, 0xA4, 0x6F, 0x73, 0x41, 0x41, 0x41, 0x2e,
|
||||||
}, ConvertOpts{})
|
}, ConvertOpts{})
|
||||||
assert.NoError(t, err)
|
|
||||||
stringMustStartWith(t, "Hola,", res)
|
stringMustStartWith(t, "Hola,", res)
|
||||||
stringMustEndWith(t, "AAA.", res)
|
stringMustEndWith(t, "AAA.", res)
|
||||||
|
|
||||||
// Japanese (Shift-JIS)
|
// Japanese (Shift-JIS)
|
||||||
// 日属秘ぞしちゅ。
|
// 日属秘ぞしちゅ。
|
||||||
res, err = ToUTF8([]byte{
|
res = ToUTF8([]byte{
|
||||||
0x93, 0xFA, 0x91, 0xAE, 0x94, 0xE9, 0x82, 0xBC, 0x82, 0xB5, 0x82,
|
0x93, 0xFA, 0x91, 0xAE, 0x94, 0xE9, 0x82, 0xBC, 0x82, 0xB5, 0x82,
|
||||||
0xBF, 0x82, 0xE3, 0x81, 0x42,
|
0xBF, 0x82, 0xE3, 0x81, 0x42,
|
||||||
}, ConvertOpts{})
|
}, ConvertOpts{})
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, []byte{
|
assert.Equal(t, []byte{
|
||||||
0xE6, 0x97, 0xA5, 0xE5, 0xB1, 0x9E, 0xE7, 0xA7, 0x98, 0xE3,
|
0xE6, 0x97, 0xA5, 0xE5, 0xB1, 0x9E, 0xE7, 0xA7, 0x98, 0xE3,
|
||||||
0x81, 0x9E, 0xE3, 0x81, 0x97, 0xE3, 0x81, 0xA1, 0xE3, 0x82, 0x85, 0xE3, 0x80, 0x82,
|
0x81, 0x9E, 0xE3, 0x81, 0x97, 0xE3, 0x81, 0xA1, 0xE3, 0x82, 0x85, 0xE3, 0x80, 0x82,
|
||||||
},
|
}, res)
|
||||||
[]byte(res))
|
|
||||||
|
|
||||||
res, err = ToUTF8([]byte{0x00, 0x00, 0x00, 0x00}, ConvertOpts{})
|
res = ToUTF8([]byte{0x00, 0x00, 0x00, 0x00}, ConvertOpts{})
|
||||||
assert.NoError(t, err)
|
assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00}, res)
|
||||||
assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00}, []byte(res))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToUTF8WithFallback(t *testing.T) {
|
func TestToUTF8WithFallback(t *testing.T) {
|
||||||
resetDefaultCharsetsOrder()
|
|
||||||
// "ABC"
|
// "ABC"
|
||||||
res := ToUTF8WithFallback([]byte{0x41, 0x42, 0x43}, ConvertOpts{})
|
res := ToUTF8WithFallback([]byte{0x41, 0x42, 0x43}, ConvertOpts{})
|
||||||
assert.Equal(t, []byte{0x41, 0x42, 0x43}, res)
|
assert.Equal(t, []byte{0x41, 0x42, 0x43}, res)
|
||||||
@ -152,54 +133,58 @@ func TestToUTF8WithFallback(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestToUTF8DropErrors(t *testing.T) {
|
func TestToUTF8DropErrors(t *testing.T) {
|
||||||
resetDefaultCharsetsOrder()
|
|
||||||
// "ABC"
|
// "ABC"
|
||||||
res := ToUTF8DropErrors([]byte{0x41, 0x42, 0x43}, ConvertOpts{})
|
res := ToUTF8DropErrors([]byte{0x41, 0x42, 0x43})
|
||||||
assert.Equal(t, []byte{0x41, 0x42, 0x43}, res)
|
assert.Equal(t, []byte{0x41, 0x42, 0x43}, res)
|
||||||
|
|
||||||
// "áéíóú"
|
// "áéíóú"
|
||||||
res = ToUTF8DropErrors([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, ConvertOpts{})
|
res = ToUTF8DropErrors([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba})
|
||||||
assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res)
|
assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res)
|
||||||
|
|
||||||
// UTF8 BOM + "áéíóú"
|
// UTF8 BOM + "áéíóú"
|
||||||
res = ToUTF8DropErrors([]byte{0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, ConvertOpts{})
|
res = ToUTF8DropErrors([]byte{0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba})
|
||||||
assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res)
|
assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res)
|
||||||
|
|
||||||
// "Hola, así cómo ños"
|
// "Hola, así cómo ños"
|
||||||
res = ToUTF8DropErrors([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0xF1, 0x6F, 0x73}, ConvertOpts{})
|
res = ToUTF8DropErrors([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0xF1, 0x6F, 0x73})
|
||||||
assert.Equal(t, []byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73}, res[:8])
|
assert.Equal(t, []byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73}, res[:8])
|
||||||
assert.Equal(t, []byte{0x73}, res[len(res)-1:])
|
assert.Equal(t, []byte{0x73}, res[len(res)-1:])
|
||||||
|
|
||||||
// "Hola, así cómo "
|
// "Hola, así cómo "
|
||||||
minmatch := []byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xC3, 0xAD, 0x20, 0x63, 0xC3, 0xB3, 0x6D, 0x6F, 0x20}
|
minmatch := []byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xC3, 0xAD, 0x20, 0x63, 0xC3, 0xB3, 0x6D, 0x6F, 0x20}
|
||||||
|
|
||||||
res = ToUTF8DropErrors([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x07, 0xA4, 0x6F, 0x73}, ConvertOpts{})
|
res = ToUTF8DropErrors([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x07, 0xA4, 0x6F, 0x73})
|
||||||
// Do not fail for differences in invalid cases, as the library might change the conversion criteria for those
|
// Do not fail for differences in invalid cases, as the library might change the conversion criteria for those
|
||||||
assert.Equal(t, minmatch, res[0:len(minmatch)])
|
assert.Equal(t, minmatch, res[0:len(minmatch)])
|
||||||
|
|
||||||
res = ToUTF8DropErrors([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x81, 0xA4, 0x6F, 0x73}, ConvertOpts{})
|
res = ToUTF8DropErrors([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x81, 0xA4, 0x6F, 0x73})
|
||||||
// Do not fail for differences in invalid cases, as the library might change the conversion criteria for those
|
// Do not fail for differences in invalid cases, as the library might change the conversion criteria for those
|
||||||
assert.Equal(t, minmatch, res[0:len(minmatch)])
|
assert.Equal(t, minmatch, res[0:len(minmatch)])
|
||||||
|
|
||||||
// Japanese (Shift-JIS)
|
// Japanese (Shift-JIS)
|
||||||
// "日属秘ぞしちゅ。"
|
// "日属秘ぞしちゅ。"
|
||||||
res = ToUTF8DropErrors([]byte{0x93, 0xFA, 0x91, 0xAE, 0x94, 0xE9, 0x82, 0xBC, 0x82, 0xB5, 0x82, 0xBF, 0x82, 0xE3, 0x81, 0x42}, ConvertOpts{})
|
res = ToUTF8DropErrors([]byte{0x93, 0xFA, 0x91, 0xAE, 0x94, 0xE9, 0x82, 0xBC, 0x82, 0xB5, 0x82, 0xBF, 0x82, 0xE3, 0x81, 0x42})
|
||||||
assert.Equal(t, []byte{
|
assert.Equal(t, []byte{
|
||||||
0xE6, 0x97, 0xA5, 0xE5, 0xB1, 0x9E, 0xE7, 0xA7, 0x98, 0xE3,
|
0xE6, 0x97, 0xA5, 0xE5, 0xB1, 0x9E, 0xE7, 0xA7, 0x98, 0xE3,
|
||||||
0x81, 0x9E, 0xE3, 0x81, 0x97, 0xE3, 0x81, 0xA1, 0xE3, 0x82, 0x85, 0xE3, 0x80, 0x82,
|
0x81, 0x9E, 0xE3, 0x81, 0x97, 0xE3, 0x81, 0xA1, 0xE3, 0x82, 0x85, 0xE3, 0x80, 0x82,
|
||||||
}, res)
|
}, res)
|
||||||
|
|
||||||
res = ToUTF8DropErrors([]byte{0x00, 0x00, 0x00, 0x00}, ConvertOpts{})
|
res = ToUTF8DropErrors([]byte{0x00, 0x00, 0x00, 0x00})
|
||||||
assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00}, res)
|
assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00}, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDetectEncoding(t *testing.T) {
|
func TestDetectEncoding(t *testing.T) {
|
||||||
resetDefaultCharsetsOrder()
|
|
||||||
testSuccess := func(b []byte, expected string) {
|
testSuccess := func(b []byte, expected string) {
|
||||||
encoding, err := DetectEncoding(b)
|
encoding, err := DetectEncoding(b)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, expected, encoding)
|
assert.Equal(t, expected, encoding)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// invalid bytes
|
||||||
|
encoding, err := DetectEncoding([]byte{0xfa})
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, "UTF-8", encoding)
|
||||||
|
|
||||||
// utf-8
|
// utf-8
|
||||||
b := []byte("just some ascii")
|
b := []byte("just some ascii")
|
||||||
testSuccess(b, "UTF-8")
|
testSuccess(b, "UTF-8")
|
||||||
@ -214,169 +199,49 @@ func TestDetectEncoding(t *testing.T) {
|
|||||||
|
|
||||||
// iso-8859-1: d<accented e>cor<newline>
|
// iso-8859-1: d<accented e>cor<newline>
|
||||||
b = []byte{0x44, 0xe9, 0x63, 0x6f, 0x72, 0x0a}
|
b = []byte{0x44, 0xe9, 0x63, 0x6f, 0x72, 0x0a}
|
||||||
encoding, err := DetectEncoding(b)
|
encoding, err = DetectEncoding(b)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Contains(t, encoding, "ISO-8859-1")
|
assert.Contains(t, encoding, "ISO-8859-1")
|
||||||
|
|
||||||
old := setting.Repository.AnsiCharset
|
defer test.MockVariableValue(&setting.Repository.AnsiCharset, "MyEncoding")()
|
||||||
setting.Repository.AnsiCharset = "placeholder"
|
testSuccess(b, "MyEncoding")
|
||||||
defer func() {
|
|
||||||
setting.Repository.AnsiCharset = old
|
|
||||||
}()
|
|
||||||
testSuccess(b, "placeholder")
|
|
||||||
|
|
||||||
// invalid bytes
|
|
||||||
b = []byte{0xfa}
|
|
||||||
_, err = DetectEncoding(b)
|
|
||||||
assert.Error(t, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func stringMustStartWith(t *testing.T, expected, value string) {
|
func stringMustStartWith(t *testing.T, expected string, value []byte) {
|
||||||
assert.Equal(t, expected, value[:len(expected)])
|
assert.Equal(t, expected, string(value[:len(expected)]))
|
||||||
}
|
}
|
||||||
|
|
||||||
func stringMustEndWith(t *testing.T, expected, value string) {
|
func stringMustEndWith(t *testing.T, expected string, value []byte) {
|
||||||
assert.Equal(t, expected, value[len(value)-len(expected):])
|
assert.Equal(t, expected, string(value[len(value)-len(expected):]))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToUTF8WithFallbackReader(t *testing.T) {
|
func TestToUTF8WithFallbackReader(t *testing.T) {
|
||||||
resetDefaultCharsetsOrder()
|
test.MockVariableValue(&ToUTF8WithFallbackReaderPrefetchSize)
|
||||||
|
|
||||||
for testLen := range 2048 {
|
block := "aá啊🤔"
|
||||||
pattern := " test { () }\n"
|
runes := []rune(block)
|
||||||
input := ""
|
assert.Len(t, string(runes[0]), 1)
|
||||||
for len(input) < testLen {
|
assert.Len(t, string(runes[1]), 2)
|
||||||
input += pattern
|
assert.Len(t, string(runes[2]), 3)
|
||||||
}
|
assert.Len(t, string(runes[3]), 4)
|
||||||
input = input[:testLen]
|
|
||||||
input += "// Выключаем"
|
content := strings.Repeat(block, 2)
|
||||||
rd := ToUTF8WithFallbackReader(bytes.NewReader([]byte(input)), ConvertOpts{})
|
for i := 1; i < len(content); i++ {
|
||||||
|
encoding, err := DetectEncoding([]byte(content[:i]))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "UTF-8", encoding)
|
||||||
|
|
||||||
|
ToUTF8WithFallbackReaderPrefetchSize = i
|
||||||
|
rd := ToUTF8WithFallbackReader(strings.NewReader(content), ConvertOpts{})
|
||||||
r, _ := io.ReadAll(rd)
|
r, _ := io.ReadAll(rd)
|
||||||
assert.Equalf(t, input, string(r), "testing string len=%d", testLen)
|
assert.Equal(t, content, string(r))
|
||||||
|
}
|
||||||
|
for _, r := range runes {
|
||||||
|
content = "abc abc " + string(r) + string(r) + string(r)
|
||||||
|
for i := 0; i < len(content); i++ {
|
||||||
|
encoding, err := DetectEncoding([]byte(content[:i]))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "UTF-8", encoding)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
truncatedOneByteExtension := failFastBytes
|
|
||||||
encoding, _ := DetectEncoding(truncatedOneByteExtension)
|
|
||||||
assert.Equal(t, "UTF-8", encoding)
|
|
||||||
|
|
||||||
truncatedTwoByteExtension := failFastBytes
|
|
||||||
truncatedTwoByteExtension[len(failFastBytes)-1] = 0x9b
|
|
||||||
truncatedTwoByteExtension[len(failFastBytes)-2] = 0xe2
|
|
||||||
|
|
||||||
encoding, _ = DetectEncoding(truncatedTwoByteExtension)
|
|
||||||
assert.Equal(t, "UTF-8", encoding)
|
|
||||||
|
|
||||||
truncatedThreeByteExtension := failFastBytes
|
|
||||||
truncatedThreeByteExtension[len(failFastBytes)-1] = 0x92
|
|
||||||
truncatedThreeByteExtension[len(failFastBytes)-2] = 0x9f
|
|
||||||
truncatedThreeByteExtension[len(failFastBytes)-3] = 0xf0
|
|
||||||
|
|
||||||
encoding, _ = DetectEncoding(truncatedThreeByteExtension)
|
|
||||||
assert.Equal(t, "UTF-8", encoding)
|
|
||||||
}
|
|
||||||
|
|
||||||
var failFastBytes = []byte{
|
|
||||||
0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x6f, 0x72, 0x67, 0x2e, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x74, 0x6f,
|
|
||||||
0x6f, 0x6c, 0x73, 0x2e, 0x61, 0x6e, 0x74, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x64, 0x65, 0x66, 0x73, 0x2e, 0x63, 0x6f, 0x6e,
|
|
||||||
0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4f, 0x73, 0x0a, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x6f, 0x72, 0x67,
|
|
||||||
0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x62, 0x6f, 0x6f,
|
|
||||||
0x74, 0x2e, 0x67, 0x72, 0x61, 0x64, 0x6c, 0x65, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x2e, 0x72, 0x75, 0x6e, 0x2e, 0x42,
|
|
||||||
0x6f, 0x6f, 0x74, 0x52, 0x75, 0x6e, 0x0a, 0x0a, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x20,
|
|
||||||
0x20, 0x20, 0x69, 0x64, 0x28, 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d,
|
|
||||||
0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x62, 0x6f, 0x6f, 0x74, 0x22, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x65,
|
|
||||||
0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65,
|
|
||||||
0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x22, 0x3a,
|
|
||||||
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a, 0x61, 0x70, 0x69, 0x22, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d,
|
|
||||||
0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74,
|
|
||||||
0x28, 0x22, 0x3a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a, 0x61, 0x70, 0x69, 0x2d, 0x64, 0x6f, 0x63, 0x73, 0x22, 0x29,
|
|
||||||
0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
|
||||||
0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x22, 0x3a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a, 0x64, 0x62,
|
|
||||||
0x22, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69,
|
|
||||||
0x6f, 0x6e, 0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x22, 0x3a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a,
|
|
||||||
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65,
|
|
||||||
0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x22, 0x3a,
|
|
||||||
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x66,
|
|
||||||
0x73, 0x22, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74,
|
|
||||||
0x69, 0x6f, 0x6e, 0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x22, 0x3a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
|
|
||||||
0x3a, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x6d, 0x71, 0x22, 0x29, 0x29, 0x0a, 0x0a,
|
|
||||||
0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22,
|
|
||||||
0x6a, 0x66, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x65, 0x3a, 0x70, 0x65, 0x2d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
|
|
||||||
0x2d, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2d, 0x73, 0x74, 0x61, 0x72, 0x74,
|
|
||||||
0x65, 0x72, 0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74,
|
|
||||||
0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6a, 0x66, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x65, 0x3a, 0x70, 0x65, 0x2d, 0x63,
|
|
||||||
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2d, 0x68, 0x61, 0x6c, 0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c,
|
|
||||||
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6a, 0x66, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x2e,
|
|
||||||
0x70, 0x65, 0x3a, 0x70, 0x65, 0x2d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x22, 0x29, 0x0a,
|
|
||||||
0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28,
|
|
||||||
0x22, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b,
|
|
||||||
0x2e, 0x62, 0x6f, 0x6f, 0x74, 0x3a, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x62, 0x6f, 0x6f, 0x74, 0x2d, 0x73, 0x74,
|
|
||||||
0x61, 0x72, 0x74, 0x65, 0x72, 0x2d, 0x77, 0x65, 0x62, 0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c,
|
|
||||||
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x70, 0x72, 0x69,
|
|
||||||
0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x62, 0x6f, 0x6f, 0x74, 0x3a, 0x73, 0x70, 0x72,
|
|
||||||
0x69, 0x6e, 0x67, 0x2d, 0x62, 0x6f, 0x6f, 0x74, 0x2d, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x72, 0x2d, 0x61, 0x6f, 0x70,
|
|
||||||
0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f,
|
|
||||||
0x6e, 0x28, 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f,
|
|
||||||
0x72, 0x6b, 0x2e, 0x62, 0x6f, 0x6f, 0x74, 0x3a, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x62, 0x6f, 0x6f, 0x74, 0x2d,
|
|
||||||
0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x72, 0x2d, 0x61, 0x63, 0x74, 0x75, 0x61, 0x74, 0x6f, 0x72, 0x22, 0x29, 0x0a, 0x20,
|
|
||||||
0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6f,
|
|
||||||
0x72, 0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x63,
|
|
||||||
0x6c, 0x6f, 0x75, 0x64, 0x3a, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2d, 0x73, 0x74,
|
|
||||||
0x61, 0x72, 0x74, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x22, 0x29, 0x0a, 0x20, 0x20,
|
|
||||||
0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6f, 0x72,
|
|
||||||
0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x63, 0x6c,
|
|
||||||
0x6f, 0x75, 0x64, 0x3a, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2d, 0x73, 0x74, 0x61,
|
|
||||||
0x72, 0x74, 0x65, 0x72, 0x2d, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2d, 0x61, 0x6c, 0x6c, 0x22, 0x29, 0x0a, 0x20, 0x20,
|
|
||||||
0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6f, 0x72,
|
|
||||||
0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x63, 0x6c,
|
|
||||||
0x6f, 0x75, 0x64, 0x3a, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2d, 0x73, 0x74, 0x61,
|
|
||||||
0x72, 0x74, 0x65, 0x72, 0x2d, 0x73, 0x6c, 0x65, 0x75, 0x74, 0x68, 0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d,
|
|
||||||
0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x70,
|
|
||||||
0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x72, 0x65, 0x74, 0x72, 0x79, 0x3a,
|
|
||||||
0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x72, 0x65, 0x74, 0x72, 0x79, 0x22, 0x29, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
|
|
||||||
0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x63, 0x68, 0x2e, 0x71,
|
|
||||||
0x6f, 0x73, 0x2e, 0x6c, 0x6f, 0x67, 0x62, 0x61, 0x63, 0x6b, 0x3a, 0x6c, 0x6f, 0x67, 0x62, 0x61, 0x63, 0x6b, 0x2d, 0x63,
|
|
||||||
0x6c, 0x61, 0x73, 0x73, 0x69, 0x63, 0x22, 0x29, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d,
|
|
||||||
0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x69, 0x6f, 0x2e, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x6d, 0x65,
|
|
||||||
0x74, 0x65, 0x72, 0x3a, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x2d, 0x72, 0x65, 0x67, 0x69, 0x73,
|
|
||||||
0x74, 0x72, 0x79, 0x2d, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x22, 0x29, 0x0a, 0x0a, 0x20, 0x20,
|
|
||||||
0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6b, 0x6f, 0x74,
|
|
||||||
0x6c, 0x69, 0x6e, 0x28, 0x22, 0x73, 0x74, 0x64, 0x6c, 0x69, 0x62, 0x22, 0x29, 0x29, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
|
|
||||||
0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
|
|
||||||
0x2f, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64,
|
|
||||||
0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
|
|
||||||
0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74,
|
|
||||||
0x65, 0x73, 0x74, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6a,
|
|
||||||
0x66, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x65, 0x3a, 0x70, 0x65, 0x2d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2d,
|
|
||||||
0x74, 0x65, 0x73, 0x74, 0x22, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x76, 0x61, 0x6c, 0x20, 0x70, 0x61, 0x74, 0x63, 0x68, 0x4a,
|
|
||||||
0x61, 0x72, 0x20, 0x62, 0x79, 0x20, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x2e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72,
|
|
||||||
0x69, 0x6e, 0x67, 0x28, 0x4a, 0x61, 0x72, 0x3a, 0x3a, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20,
|
|
||||||
0x20, 0x20, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2e,
|
|
||||||
0x73, 0x65, 0x74, 0x28, 0x22, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x22, 0x29, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
|
|
||||||
0x76, 0x61, 0x6c, 0x20, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x70, 0x61, 0x74, 0x68,
|
|
||||||
0x20, 0x62, 0x79, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x67,
|
|
||||||
0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74,
|
|
||||||
0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65,
|
|
||||||
0x73, 0x28, 0x22, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x2d, 0x50, 0x61, 0x74, 0x68, 0x22, 0x20, 0x74, 0x6f, 0x20, 0x6f, 0x62,
|
|
||||||
0x6a, 0x65, 0x63, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70,
|
|
||||||
0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x20, 0x76, 0x61, 0x6c, 0x20, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x3d,
|
|
||||||
0x20, 0x22, 0x66, 0x69, 0x6c, 0x65, 0x3a, 0x2f, 0x2b, 0x22, 0x2e, 0x74, 0x6f, 0x52, 0x65, 0x67, 0x65, 0x78, 0x28, 0x29,
|
|
||||||
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64,
|
|
||||||
0x65, 0x20, 0x66, 0x75, 0x6e, 0x20, 0x74, 0x6f, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x28, 0x29, 0x3a, 0x20, 0x53, 0x74,
|
|
||||||
0x72, 0x69, 0x6e, 0x67, 0x20, 0x3d, 0x20, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x70,
|
|
||||||
0x61, 0x74, 0x68, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x6a, 0x6f, 0x69, 0x6e, 0x54, 0x6f, 0x53, 0x74, 0x72, 0x69,
|
|
||||||
0x6e, 0x67, 0x28, 0x22, 0x20, 0x22, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
|
||||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x74, 0x2e, 0x74, 0x6f, 0x55, 0x52, 0x49, 0x28, 0x29, 0x2e, 0x74, 0x6f, 0x55,
|
|
||||||
0x52, 0x4c, 0x28, 0x29, 0x2e, 0x74, 0x6f, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x28, 0x29, 0x2e, 0x72, 0x65, 0x70, 0x6c,
|
|
||||||
0x61, 0x63, 0x65, 0x46, 0x69, 0x72, 0x73, 0x74, 0x28, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x2c, 0x20, 0x22, 0x2f,
|
|
||||||
0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20,
|
|
||||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x61, 0x73,
|
|
||||||
0x6b, 0x73, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x64, 0x3c, 0x42, 0x6f, 0x6f, 0x74, 0x52, 0x75, 0x6e, 0x3e, 0x28, 0x22, 0x62,
|
|
||||||
0x6f, 0x6f, 0x74, 0x52, 0x75, 0x6e, 0x22, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x4f,
|
|
||||||
0x73, 0x2e, 0x69, 0x73, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x28, 0x4f, 0x73, 0x2e, 0x46, 0x41, 0x4d, 0x49, 0x4c, 0x59,
|
|
||||||
0x5f, 0x57, 0x49, 0x4e, 0x44, 0x4f, 0x57, 0x53, 0x29, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
|
||||||
0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x70, 0x61, 0x74, 0x68, 0x20, 0x3d, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x28, 0x73,
|
|
||||||
0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x74, 0x73, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x64, 0x28, 0x22, 0x6d, 0x61, 0x69,
|
|
||||||
0x6e, 0x22, 0x29, 0x2e, 0x6d, 0x61, 0x70, 0x20, 0x7b, 0x20, 0x69, 0x74, 0x2e, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x20,
|
|
||||||
0x7d, 0x2c, 0x20, 0x70, 0x61, 0x74, 0x63, 0x68, 0x4a, 0x61, 0x72, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a,
|
|
||||||
0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0xd0,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,6 @@ import (
|
|||||||
charsetModule "code.gitea.io/gitea/modules/charset"
|
charsetModule "code.gitea.io/gitea/modules/charset"
|
||||||
"code.gitea.io/gitea/modules/container"
|
"code.gitea.io/gitea/modules/container"
|
||||||
"code.gitea.io/gitea/modules/httpcache"
|
"code.gitea.io/gitea/modules/httpcache"
|
||||||
"code.gitea.io/gitea/modules/log"
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/typesniffer"
|
"code.gitea.io/gitea/modules/typesniffer"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
@ -109,11 +108,7 @@ func setServeHeadersByFile(r *http.Request, w http.ResponseWriter, mineBuf []byt
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isPlain {
|
if isPlain {
|
||||||
charset, err := charsetModule.DetectEncoding(mineBuf)
|
charset, _ := charsetModule.DetectEncoding(mineBuf)
|
||||||
if err != nil {
|
|
||||||
log.Error("Detect raw file %s charset failed: %v, using by default utf-8", opts.Filename, err)
|
|
||||||
charset = "utf-8"
|
|
||||||
}
|
|
||||||
opts.ContentTypeCharset = strings.ToLower(charset)
|
opts.ContentTypeCharset = strings.ToLower(charset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -203,7 +203,7 @@ func (b *Indexer) addUpdate(ctx context.Context, batchWriter git.WriteCloserErro
|
|||||||
RepoID: repo.ID,
|
RepoID: repo.ID,
|
||||||
CommitID: commitSha,
|
CommitID: commitSha,
|
||||||
Filename: update.Filename,
|
Filename: update.Filename,
|
||||||
Content: string(charset.ToUTF8DropErrors(fileContents, charset.ConvertOpts{})),
|
Content: string(charset.ToUTF8DropErrors(fileContents)),
|
||||||
Language: analyze.GetCodeLanguage(update.Filename, fileContents),
|
Language: analyze.GetCodeLanguage(update.Filename, fileContents),
|
||||||
UpdatedAt: time.Now().UTC(),
|
UpdatedAt: time.Now().UTC(),
|
||||||
})
|
})
|
||||||
|
|||||||
@ -191,7 +191,7 @@ func (b *Indexer) addUpdate(ctx context.Context, batchWriter git.WriteCloserErro
|
|||||||
Doc(map[string]any{
|
Doc(map[string]any{
|
||||||
"repo_id": repo.ID,
|
"repo_id": repo.ID,
|
||||||
"filename": update.Filename,
|
"filename": update.Filename,
|
||||||
"content": string(charset.ToUTF8DropErrors(fileContents, charset.ConvertOpts{})),
|
"content": string(charset.ToUTF8DropErrors(fileContents)),
|
||||||
"commit_id": sha,
|
"commit_id": sha,
|
||||||
"language": analyze.GetCodeLanguage(update.Filename, fileContents),
|
"language": analyze.GetCodeLanguage(update.Filename, fileContents),
|
||||||
"updated_at": timeutil.TimeStampNow(),
|
"updated_at": timeutil.TimeStampNow(),
|
||||||
|
|||||||
@ -240,4 +240,5 @@ func PanicInDevOrTesting(msg string, a ...any) {
|
|||||||
if !IsProd || IsInTesting {
|
if !IsProd || IsInTesting {
|
||||||
panic(fmt.Sprintf(msg, a...))
|
panic(fmt.Sprintf(msg, a...))
|
||||||
}
|
}
|
||||||
|
log.Error(msg, a...)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -317,11 +317,7 @@ func EditFile(ctx *context.Context) {
|
|||||||
ctx.ServerError("ReadAll", err)
|
ctx.ServerError("ReadAll", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if content, err := charset.ToUTF8(buf, charset.ConvertOpts{KeepBOM: true}); err != nil {
|
ctx.Data["FileContent"] = string(charset.ToUTF8(buf, charset.ConvertOpts{KeepBOM: true, ErrorReturnOrigin: true}))
|
||||||
ctx.Data["FileContent"] = string(buf)
|
|
||||||
} else {
|
|
||||||
ctx.Data["FileContent"] = content
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -835,11 +835,11 @@ parsingLoop:
|
|||||||
if buffer.Len() == 0 {
|
if buffer.Len() == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
charsetLabel, err := charset.DetectEncoding(buffer.Bytes())
|
charsetLabel, _ := charset.DetectEncoding(buffer.Bytes())
|
||||||
if charsetLabel != "UTF-8" && err == nil {
|
if charsetLabel != "UTF-8" {
|
||||||
encoding, _ := stdcharset.Lookup(charsetLabel)
|
charsetEncoding, _ := stdcharset.Lookup(charsetLabel)
|
||||||
if encoding != nil {
|
if charsetEncoding != nil {
|
||||||
diffLineTypeDecoders[lineType] = encoding.NewDecoder()
|
diffLineTypeDecoders[lineType] = charsetEncoding.NewDecoder()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1325,10 +1325,10 @@ func GetDiffForRender(ctx context.Context, repoLink string, gitRepo *git.Reposit
|
|||||||
shouldFullFileHighlight := !setting.Git.DisableDiffHighlight && attrDiff.Value() == ""
|
shouldFullFileHighlight := !setting.Git.DisableDiffHighlight && attrDiff.Value() == ""
|
||||||
if shouldFullFileHighlight {
|
if shouldFullFileHighlight {
|
||||||
if limitedContent.LeftContent != nil && limitedContent.LeftContent.buf.Len() < MaxDiffHighlightEntireFileSize {
|
if limitedContent.LeftContent != nil && limitedContent.LeftContent.buf.Len() < MaxDiffHighlightEntireFileSize {
|
||||||
diffFile.highlightedLeftLines = highlightCodeLines(diffFile, true /* left */, limitedContent.LeftContent.buf.String())
|
diffFile.highlightedLeftLines = highlightCodeLines(diffFile, true /* left */, limitedContent.LeftContent.buf.Bytes())
|
||||||
}
|
}
|
||||||
if limitedContent.RightContent != nil && limitedContent.RightContent.buf.Len() < MaxDiffHighlightEntireFileSize {
|
if limitedContent.RightContent != nil && limitedContent.RightContent.buf.Len() < MaxDiffHighlightEntireFileSize {
|
||||||
diffFile.highlightedRightLines = highlightCodeLines(diffFile, false /* right */, limitedContent.RightContent.buf.String())
|
diffFile.highlightedRightLines = highlightCodeLines(diffFile, false /* right */, limitedContent.RightContent.buf.Bytes())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1336,9 +1336,34 @@ func GetDiffForRender(ctx context.Context, repoLink string, gitRepo *git.Reposit
|
|||||||
return diff, nil
|
return diff, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func highlightCodeLines(diffFile *DiffFile, isLeft bool, content string) map[int]template.HTML {
|
func splitHighlightLines(buf []byte) (ret [][]byte) {
|
||||||
|
lineCount := bytes.Count(buf, []byte("\n")) + 1
|
||||||
|
ret = make([][]byte, 0, lineCount)
|
||||||
|
nlTagClose := []byte("\n</")
|
||||||
|
for {
|
||||||
|
pos := bytes.IndexByte(buf, '\n')
|
||||||
|
if pos == -1 {
|
||||||
|
ret = append(ret, buf)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
// Chroma highlighting output sometimes have "</span>" right after \n, sometimes before.
|
||||||
|
// * "<span>text\n</span>"
|
||||||
|
// * "<span>text</span>\n"
|
||||||
|
if bytes.HasPrefix(buf[pos:], nlTagClose) {
|
||||||
|
pos1 := bytes.IndexByte(buf[pos:], '>')
|
||||||
|
if pos1 != -1 {
|
||||||
|
pos += pos1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret = append(ret, buf[:pos+1])
|
||||||
|
buf = buf[pos+1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func highlightCodeLines(diffFile *DiffFile, isLeft bool, rawContent []byte) map[int]template.HTML {
|
||||||
|
content := util.UnsafeBytesToString(charset.ToUTF8(rawContent, charset.ConvertOpts{}))
|
||||||
highlightedNewContent, _ := highlight.Code(diffFile.Name, diffFile.Language, content)
|
highlightedNewContent, _ := highlight.Code(diffFile.Name, diffFile.Language, content)
|
||||||
splitLines := strings.Split(string(highlightedNewContent), "\n")
|
splitLines := splitHighlightLines([]byte(highlightedNewContent))
|
||||||
lines := make(map[int]template.HTML, len(splitLines))
|
lines := make(map[int]template.HTML, len(splitLines))
|
||||||
// only save the highlighted lines we need, but not the whole file, to save memory
|
// only save the highlighted lines we need, but not the whole file, to save memory
|
||||||
for _, sec := range diffFile.Sections {
|
for _, sec := range diffFile.Sections {
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
package gitdiff
|
package gitdiff
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"html/template"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@ -1106,3 +1107,41 @@ func TestDiffLine_GetExpandDirection(t *testing.T) {
|
|||||||
assert.Equal(t, c.direction, c.diffLine.GetExpandDirection(), "case %s expected direction: %s", c.name, c.direction)
|
assert.Equal(t, c.direction, c.diffLine.GetExpandDirection(), "case %s expected direction: %s", c.name, c.direction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHighlightCodeLines(t *testing.T) {
|
||||||
|
t.Run("CharsetDetecting", func(t *testing.T) {
|
||||||
|
diffFile := &DiffFile{
|
||||||
|
Name: "a.c",
|
||||||
|
Language: "c",
|
||||||
|
Sections: []*DiffSection{
|
||||||
|
{
|
||||||
|
Lines: []*DiffLine{{LeftIdx: 1}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ret := highlightCodeLines(diffFile, true, []byte("// abc\xcc def\xcd")) // ISO-8859-1 bytes
|
||||||
|
assert.Equal(t, "<span class=\"c1\">// abcÌ defÍ\n</span>", string(ret[0]))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("LeftLines", func(t *testing.T) {
|
||||||
|
diffFile := &DiffFile{
|
||||||
|
Name: "a.c",
|
||||||
|
Language: "c",
|
||||||
|
Sections: []*DiffSection{
|
||||||
|
{
|
||||||
|
Lines: []*DiffLine{
|
||||||
|
{LeftIdx: 1},
|
||||||
|
{LeftIdx: 2},
|
||||||
|
{LeftIdx: 3},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const nl = "\n"
|
||||||
|
ret := highlightCodeLines(diffFile, true, []byte("a\nb\n"))
|
||||||
|
assert.Equal(t, map[int]template.HTML{
|
||||||
|
0: `<span class="n">a</span>` + nl,
|
||||||
|
1: `<span class="n">b</span>`,
|
||||||
|
}, ret)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -25,12 +25,12 @@ func TestDiffWithHighlight(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("CleanUp", func(t *testing.T) {
|
t.Run("CleanUp", func(t *testing.T) {
|
||||||
hcd := newHighlightCodeDiff()
|
hcd := newHighlightCodeDiff()
|
||||||
codeA := template.HTML(`<span class="cm>this is a comment</span>`)
|
codeA := template.HTML(`<span class="cm">this is a comment</span>`)
|
||||||
codeB := template.HTML(`<span class="cm>this is updated comment</span>`)
|
codeB := template.HTML(`<span class="cm">this is updated comment</span>`)
|
||||||
outDel := hcd.diffLineWithHighlight(DiffLineDel, codeA, codeB)
|
outDel := hcd.diffLineWithHighlight(DiffLineDel, codeA, codeB)
|
||||||
assert.Equal(t, `<span class="cm>this is <span class="removed-code">a</span> comment</span>`, string(outDel))
|
assert.Equal(t, `<span class="cm">this is <span class="removed-code">a</span> comment</span>`, string(outDel))
|
||||||
outAdd := hcd.diffLineWithHighlight(DiffLineAdd, codeA, codeB)
|
outAdd := hcd.diffLineWithHighlight(DiffLineAdd, codeA, codeB)
|
||||||
assert.Equal(t, `<span class="cm>this is <span class="added-code">updated</span> comment</span>`, string(outAdd))
|
assert.Equal(t, `<span class="cm">this is <span class="added-code">updated</span> comment</span>`, string(outAdd))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("OpenCloseTags", func(t *testing.T) {
|
t.Run("OpenCloseTags", func(t *testing.T) {
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
package migrations
|
package migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
@ -21,7 +22,6 @@ import (
|
|||||||
"code.gitea.io/gitea/models/migrations"
|
"code.gitea.io/gitea/models/migrations"
|
||||||
migrate_base "code.gitea.io/gitea/models/migrations/base"
|
migrate_base "code.gitea.io/gitea/models/migrations/base"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
"code.gitea.io/gitea/modules/charset"
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
@ -108,11 +108,11 @@ func readSQLFromFile(version string) (string, error) {
|
|||||||
}
|
}
|
||||||
defer gr.Close()
|
defer gr.Close()
|
||||||
|
|
||||||
bytes, err := io.ReadAll(gr)
|
buf, err := io.ReadAll(gr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return string(charset.MaybeRemoveBOM(bytes, charset.ConvertOpts{})), nil
|
return string(bytes.TrimPrefix(buf, []byte{'\xef', '\xbb', '\xbf'})), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func restoreOldDB(t *testing.T, version string) {
|
func restoreOldDB(t *testing.T, version string) {
|
||||||
|
|||||||
@ -2,20 +2,10 @@
|
|||||||
import {SvgIcon} from '../svg.ts';
|
import {SvgIcon} from '../svg.ts';
|
||||||
import {isPlainClick} from '../utils/dom.ts';
|
import {isPlainClick} from '../utils/dom.ts';
|
||||||
import {shallowRef} from 'vue';
|
import {shallowRef} from 'vue';
|
||||||
import {type createViewFileTreeStore} from './ViewFileTreeStore.ts';
|
import type {createViewFileTreeStore, FileTreeItem} from './ViewFileTreeStore.ts';
|
||||||
|
|
||||||
export type Item = {
|
|
||||||
entryName: string;
|
|
||||||
entryMode: 'blob' | 'exec' | 'tree' | 'commit' | 'symlink' | 'unknown';
|
|
||||||
entryIcon: string;
|
|
||||||
entryIconOpen: string;
|
|
||||||
fullPath: string;
|
|
||||||
submoduleUrl?: string;
|
|
||||||
children?: Item[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
item: Item,
|
item: FileTreeItem,
|
||||||
store: ReturnType<typeof createViewFileTreeStore>
|
store: ReturnType<typeof createViewFileTreeStore>
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|||||||
@ -3,11 +3,20 @@ import {GET} from '../modules/fetch.ts';
|
|||||||
import {pathEscapeSegments} from '../utils/url.ts';
|
import {pathEscapeSegments} from '../utils/url.ts';
|
||||||
import {createElementFromHTML} from '../utils/dom.ts';
|
import {createElementFromHTML} from '../utils/dom.ts';
|
||||||
import {html} from '../utils/html.ts';
|
import {html} from '../utils/html.ts';
|
||||||
import type {Item} from './ViewFileTreeItem.vue';
|
|
||||||
|
export type FileTreeItem = {
|
||||||
|
entryName: string;
|
||||||
|
entryMode: 'blob' | 'exec' | 'tree' | 'commit' | 'symlink' | 'unknown';
|
||||||
|
entryIcon: string;
|
||||||
|
entryIconOpen: string;
|
||||||
|
fullPath: string;
|
||||||
|
submoduleUrl?: string;
|
||||||
|
children?: Array<FileTreeItem>;
|
||||||
|
};
|
||||||
|
|
||||||
export function createViewFileTreeStore(props: {repoLink: string, treePath: string, currentRefNameSubURL: string}) {
|
export function createViewFileTreeStore(props: {repoLink: string, treePath: string, currentRefNameSubURL: string}) {
|
||||||
const store = reactive({
|
const store = reactive({
|
||||||
rootFiles: [] as Array<Item>,
|
rootFiles: [] as Array<FileTreeItem>,
|
||||||
selectedItem: props.treePath,
|
selectedItem: props.treePath,
|
||||||
|
|
||||||
async loadChildren(treePath: string, subPath: string = '') {
|
async loadChildren(treePath: string, subPath: string = '') {
|
||||||
|
|||||||
4
web_src/js/globals.d.ts
vendored
4
web_src/js/globals.d.ts
vendored
@ -12,8 +12,8 @@ declare module '*.vue' {
|
|||||||
import type {DefineComponent} from 'vue';
|
import type {DefineComponent} from 'vue';
|
||||||
const component: DefineComponent<unknown, unknown, any>;
|
const component: DefineComponent<unknown, unknown, any>;
|
||||||
export default component;
|
export default component;
|
||||||
// List of named exports from vue components, used to make `tsc` output clean.
|
// Here we declare all exports from vue files so `tsc` or `tsgo` can work for
|
||||||
// To actually lint .vue files, `vue-tsc` is used because `tsc` can not parse them.
|
// non-vue files. To lint .vue files, `vue-tsc` must be used.
|
||||||
export function initDashboardRepoList(): void;
|
export function initDashboardRepoList(): void;
|
||||||
export function initRepositoryActionView(): void;
|
export function initRepositoryActionView(): void;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user