Compare commits

..

No commits in common. "main" and "10.0.1_base" have entirely different histories.

80 changed files with 138 additions and 4313 deletions

View file

@ -13,8 +13,6 @@ runs:
run: | run: |
export DEBIAN_FRONTEND=noninteractive export DEBIAN_FRONTEND=noninteractive
echo "deb http://deb.debian.org/debian/ ${RELEASE} main" > "/etc/apt/sources.list.d/${RELEASE}.list" echo "deb http://deb.debian.org/debian/ ${RELEASE} main" > "/etc/apt/sources.list.d/${RELEASE}.list"
wget -O- http://neuro.debian.net/lists/bookworm.de-fzj.libre | tee /etc/apt/sources.list.d/neurodebian.sources.list
apt-key adv --recv-keys --keyserver hkps://keyserver.ubuntu.com 0xA5D32F012649A5A9
env: env:
RELEASE: ${{inputs.release}} RELEASE: ${{inputs.release}}
- name: install packages - name: install packages
@ -26,7 +24,6 @@ runs:
- name: remove temporary package list to prevent using it in other steps - name: remove temporary package list to prevent using it in other steps
run: | run: |
rm "/etc/apt/sources.list.d/${RELEASE}.list" rm "/etc/apt/sources.list.d/${RELEASE}.list"
rm "/etc/apt/sources.list.d/neurodebian.sources.list"
apt-get update -qq apt-get update -qq
env: env:
RELEASE: ${{inputs.release}} RELEASE: ${{inputs.release}}

View file

@ -1,41 +0,0 @@
on:
push:
branches:
- 'forgejo'
tags:
- '*-git-annex*'
jobs:
build-oci-image:
runs-on: docker
strategy:
matrix:
type: ["rootful", "rootless"]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # fetch the full history so that the Forgejo version is determined properly
- name: Determine registry and username
id: determine-registry-and-username
run: |
echo "registry=${GITHUB_SERVER_URL#https://}" >> "$GITHUB_OUTPUT"
echo "username=${GITHUB_REPOSITORY%/*}" >> "$GITHUB_OUTPUT"
- name: Install Docker
run: curl -fsSL https://get.docker.com | sh
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
registry: ${{ steps.determine-registry-and-username.outputs.registry }}
username: ${{ steps.determine-registry-and-username.outputs.username }}
password: ${{ secrets.REGISTRY_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: ${{ (matrix.type == 'rootful' && 'Dockerfile') || (matrix.type == 'rootless' && 'Dockerfile.rootless') }}
push: true
tags: ${{ steps.determine-registry-and-username.outputs.registry }}/${{ github.repository }}:${{ github.ref_name }}${{ (matrix.type == 'rootful' && ' ') || (matrix.type == 'rootless' && '-rootless') }}

View file

@ -10,6 +10,7 @@ on:
jobs: jobs:
backend-checks: backend-checks:
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing'
runs-on: docker runs-on: docker
container: container:
image: 'data.forgejo.org/oci/node:20-bookworm' image: 'data.forgejo.org/oci/node:20-bookworm'
@ -26,6 +27,7 @@ jobs:
- run: su forgejo -c 'make --always-make -j$(nproc) lint-backend tidy-check swagger-check fmt-check swagger-validate' # ensure the "go-licenses" make target runs - run: su forgejo -c 'make --always-make -j$(nproc) lint-backend tidy-check swagger-check fmt-check swagger-validate' # ensure the "go-licenses" make target runs
- uses: ./.forgejo/workflows-composite/build-backend - uses: ./.forgejo/workflows-composite/build-backend
frontend-checks: frontend-checks:
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing'
runs-on: docker runs-on: docker
container: container:
image: 'data.forgejo.org/oci/node:20-bookworm' image: 'data.forgejo.org/oci/node:20-bookworm'
@ -174,6 +176,7 @@ jobs:
TAGS: bindata TAGS: bindata
TEST_REDIS_SERVER: cacher:${{ matrix.cacher.port }} TEST_REDIS_SERVER: cacher:${{ matrix.cacher.port }}
test-mysql: test-mysql:
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing'
runs-on: docker runs-on: docker
needs: [backend-checks, frontend-checks] needs: [backend-checks, frontend-checks]
container: container:
@ -196,13 +199,15 @@ jobs:
- name: install dependencies & git >= 2.42 - name: install dependencies & git >= 2.42
uses: ./.forgejo/workflows-composite/apt-install-from uses: ./.forgejo/workflows-composite/apt-install-from
with: with:
packages: git git-annex-standalone git-lfs packages: git git-lfs
- uses: ./.forgejo/workflows-composite/build-backend - uses: ./.forgejo/workflows-composite/build-backend
- run: | - run: |
su forgejo -c 'make test-mysql-migration test-mysql' su forgejo -c 'make test-mysql-migration test-mysql'
timeout-minutes: 120
env: env:
USE_REPO_TEST_DIR: 1 USE_REPO_TEST_DIR: 1
test-pgsql: test-pgsql:
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing'
runs-on: docker runs-on: docker
needs: [backend-checks, frontend-checks] needs: [backend-checks, frontend-checks]
container: container:
@ -231,15 +236,17 @@ jobs:
- name: install dependencies & git >= 2.42 - name: install dependencies & git >= 2.42
uses: ./.forgejo/workflows-composite/apt-install-from uses: ./.forgejo/workflows-composite/apt-install-from
with: with:
packages: git git-annex-standalone git-lfs packages: git git-lfs
- uses: ./.forgejo/workflows-composite/build-backend - uses: ./.forgejo/workflows-composite/build-backend
- run: | - run: |
su forgejo -c 'make test-pgsql-migration test-pgsql' su forgejo -c 'make test-pgsql-migration test-pgsql'
timeout-minutes: 120
env: env:
RACE_ENABLED: true RACE_ENABLED: true
USE_REPO_TEST_DIR: 1 USE_REPO_TEST_DIR: 1
TEST_LDAP: 1 TEST_LDAP: 1
test-sqlite: test-sqlite:
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing'
runs-on: docker runs-on: docker
needs: [backend-checks, frontend-checks] needs: [backend-checks, frontend-checks]
container: container:
@ -251,21 +258,25 @@ jobs:
- name: install dependencies & git >= 2.42 - name: install dependencies & git >= 2.42
uses: ./.forgejo/workflows-composite/apt-install-from uses: ./.forgejo/workflows-composite/apt-install-from
with: with:
packages: git git-annex-standalone git-lfs packages: git git-lfs
- uses: ./.forgejo/workflows-composite/build-backend - uses: ./.forgejo/workflows-composite/build-backend
- run: | - run: |
su forgejo -c 'make test-sqlite-migration test-sqlite' su forgejo -c 'make test-sqlite-migration test-sqlite'
timeout-minutes: 120
env: env:
TAGS: sqlite sqlite_unlock_notify TAGS: sqlite sqlite_unlock_notify
RACE_ENABLED: true RACE_ENABLED: true
TEST_TAGS: sqlite sqlite_unlock_notify TEST_TAGS: sqlite sqlite_unlock_notify
USE_REPO_TEST_DIR: 1 USE_REPO_TEST_DIR: 1
security-check: security-check:
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing'
runs-on: docker runs-on: docker
needs: needs:
- test-sqlite - test-sqlite
- test-pgsql - test-pgsql
- test-mysql - test-mysql
- test-remote-cacher
- test-unit
container: container:
image: 'data.forgejo.org/oci/node:20-bookworm' image: 'data.forgejo.org/oci/node:20-bookworm'
options: --tmpfs /tmp:exec,noatime options: --tmpfs /tmp:exec,noatime

View file

@ -78,7 +78,6 @@ RUN apk --no-cache add \
sqlite \ sqlite \
su-exec \ su-exec \
gnupg \ gnupg \
git-annex \
&& rm -rf /var/cache/apk/* && rm -rf /var/cache/apk/*
RUN addgroup \ RUN addgroup \

View file

@ -71,7 +71,6 @@ RUN apk --no-cache add \
git \ git \
curl \ curl \
gnupg \ gnupg \
git-annex \
&& rm -rf /var/cache/apk/* && rm -rf /var/cache/apk/*
RUN addgroup \ RUN addgroup \

View file

@ -8,7 +8,7 @@ self := $(location)
@tmpdir=`mktemp --tmpdir -d` ; \ @tmpdir=`mktemp --tmpdir -d` ; \
echo Using temporary directory $$tmpdir for test repositories ; \ echo Using temporary directory $$tmpdir for test repositories ; \
USE_REPO_TEST_DIR= $(MAKE) -f $(self) --no-print-directory REPO_TEST_DIR=$$tmpdir/ $@ ; \ USE_REPO_TEST_DIR= $(MAKE) -f $(self) --no-print-directory REPO_TEST_DIR=$$tmpdir/ $@ ; \
STATUS=$$? ; chmod -R +w "$$tmpdir" && rm -r "$$tmpdir" ; exit $$STATUS STATUS=$$? ; rm -r "$$tmpdir" ; exit $$STATUS
else else
@ -104,7 +104,7 @@ else
FORGEJO_VERSION_API ?= $(GITEA_VERSION)+${GITEA_COMPATIBILITY} FORGEJO_VERSION_API ?= $(GITEA_VERSION)+${GITEA_COMPATIBILITY}
else else
# drop the "g" prefix prepended by git describe to the commit hash # drop the "g" prefix prepended by git describe to the commit hash
FORGEJO_VERSION ?= $(shell git describe --exclude '*-test' --tags --always | sed 's/^v//' | sed 's/\-g/-/2')+${GITEA_COMPATIBILITY} FORGEJO_VERSION ?= $(shell git describe --exclude '*-test' --tags --always | sed 's/^v//' | sed 's/\-g/-/')+${GITEA_COMPATIBILITY}
endif endif
endif endif
FORGEJO_VERSION_MAJOR=$(shell echo $(FORGEJO_VERSION) | sed -e 's/\..*//') FORGEJO_VERSION_MAJOR=$(shell echo $(FORGEJO_VERSION) | sed -e 's/\..*//')

View file

@ -38,7 +38,6 @@ import (
const ( const (
lfsAuthenticateVerb = "git-lfs-authenticate" lfsAuthenticateVerb = "git-lfs-authenticate"
gitAnnexShellVerb = "git-annex-shell"
) )
// CmdServ represents the available serv sub-command. // CmdServ represents the available serv sub-command.
@ -80,7 +79,6 @@ var (
"git-upload-archive": perm.AccessModeRead, "git-upload-archive": perm.AccessModeRead,
"git-receive-pack": perm.AccessModeWrite, "git-receive-pack": perm.AccessModeWrite,
lfsAuthenticateVerb: perm.AccessModeNone, lfsAuthenticateVerb: perm.AccessModeNone,
gitAnnexShellVerb: perm.AccessModeNone, // annex permissions are enforced by GIT_ANNEX_SHELL_READONLY, rather than the Gitea API
} }
alphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`) alphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`)
) )
@ -214,28 +212,6 @@ func runServ(c *cli.Context) error {
} }
} }
if verb == gitAnnexShellVerb {
if !setting.Annex.Enabled {
return fail(ctx, "Unknown git command", "git-annex request over SSH denied, git-annex support is disabled")
}
if len(words) < 3 {
return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd)
}
// git-annex always puts the repo in words[2], unlike most other
// git subcommands; and it sometimes names repos like /~/, as if
// $HOME should get expanded while also being rooted. e.g.:
// git-annex-shell 'configlist' '/~/user/repo'
// git-annex-shell 'sendkey' '/user/repo 'key'
repoPath = words[2]
repoPath = strings.TrimPrefix(repoPath, "/")
repoPath = strings.TrimPrefix(repoPath, "~/")
}
// prevent directory traversal attacks
repoPath = filepath.Clean("/" + repoPath)[1:]
rr := strings.SplitN(repoPath, "/", 2) rr := strings.SplitN(repoPath, "/", 2)
if len(rr) != 2 { if len(rr) != 2 {
return fail(ctx, "Invalid repository path", "Invalid repository path: %v", repoPath) return fail(ctx, "Invalid repository path", "Invalid repository path: %v", repoPath)
@ -249,18 +225,6 @@ func runServ(c *cli.Context) error {
// so that username and reponame are not affected. // so that username and reponame are not affected.
repoPath = strings.ToLower(strings.TrimSpace(repoPath)) repoPath = strings.ToLower(strings.TrimSpace(repoPath))
// put the sanitized repoPath back into the argument list for later
if verb == gitAnnexShellVerb {
// git-annex-shell demands an absolute path
absRepoPath, err := filepath.Abs(filepath.Join(setting.RepoRootPath, repoPath))
if err != nil {
return fail(ctx, "Error locating repoPath", "%v", err)
}
words[2] = absRepoPath
} else {
words[1] = repoPath
}
if alphaDashDotPattern.MatchString(reponame) { if alphaDashDotPattern.MatchString(reponame) {
return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame) return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame)
} }
@ -339,45 +303,21 @@ func runServ(c *cli.Context) error {
return nil return nil
} }
gitBinVerb, err := exec.LookPath(verb) var gitcmd *exec.Cmd
if err != nil { gitBinPath := filepath.Dir(git.GitExecutable) // e.g. /usr/bin
gitBinVerb := filepath.Join(gitBinPath, verb) // e.g. /usr/bin/git-upload-pack
if _, err := os.Stat(gitBinVerb); err != nil {
// if the command "git-upload-pack" doesn't exist, try to split "git-upload-pack" to use the sub-command with git // if the command "git-upload-pack" doesn't exist, try to split "git-upload-pack" to use the sub-command with git
// ps: Windows only has "git.exe" in the bin path, so Windows always uses this way // ps: Windows only has "git.exe" in the bin path, so Windows always uses this way
// ps: git-annex-shell and other extensions may not necessarily be in gitBinPath,
// but '{gitBinPath}/git annex-shell' should be able to find them on $PATH.
verbFields := strings.SplitN(verb, "-", 2) verbFields := strings.SplitN(verb, "-", 2)
if len(verbFields) == 2 { if len(verbFields) == 2 {
// use git binary with the sub-command part: "C:\...\bin\git.exe", "upload-pack", ... // use git binary with the sub-command part: "C:\...\bin\git.exe", "upload-pack", ...
gitBinVerb = git.GitExecutable gitcmd = exec.CommandContext(ctx, git.GitExecutable, verbFields[1], repoPath)
words = append([]string{verbFields[1]}, words...)
} }
} }
if gitcmd == nil {
// by default, use the verb (it has been checked above by allowedCommands) // by default, use the verb (it has been checked above by allowedCommands)
gitcmd := exec.CommandContext(ctx, gitBinVerb, words[1:]...) gitcmd = exec.CommandContext(ctx, gitBinVerb, repoPath)
if verb == gitAnnexShellVerb {
// This doesn't get its own isolated section like LFS does, because LFS
// is handled by internal Gitea routines, but git-annex has to be shelled out
// to like other git subcommands, so we need to build up gitcmd.
// TODO: does this work on Windows?
gitcmd.Env = append(gitcmd.Env,
// "If set, disallows running git-shell to handle unknown commands."
// - git-annex-shell(1)
"GIT_ANNEX_SHELL_LIMITED=True",
// "If set, git-annex-shell will refuse to run commands
// that do not operate on the specified directory."
// - git-annex-shell(1)
fmt.Sprintf("GIT_ANNEX_SHELL_DIRECTORY=%s", words[2]),
)
if results.UserMode < perm.AccessModeWrite {
// "If set, disallows any action that could modify the git-annex repository."
// - git-annex-shell(1)
// We set this when the backend API has told us that we don't have write permission to this repo.
log.Debug("Setting GIT_ANNEX_SHELL_READONLY=True")
gitcmd.Env = append(gitcmd.Env, "GIT_ANNEX_SHELL_READONLY=True")
}
} }
process.SetSysProcAttribute(gitcmd) process.SetSysProcAttribute(gitcmd)

View file

@ -9,7 +9,6 @@ import (
"net" "net"
"net/http" "net/http"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
@ -248,12 +247,6 @@ func runWeb(ctx *cli.Context) error {
createPIDFile(ctx.String("pid")) createPIDFile(ctx.String("pid"))
} }
if setting.Annex.Enabled {
if _, err := exec.LookPath("git-annex"); err != nil {
log.Fatal("You have enabled git-annex support but git-annex is not installed. Please make sure that Forgejo's PATH contains the git-annex executable.")
}
}
if !setting.InstallLock { if !setting.InstallLock {
if err := serveInstall(ctx); err != nil { if err := serveInstall(ctx); err != nil {
return err return err
@ -318,10 +311,6 @@ func listen(m http.Handler, handleRedirector bool) error {
log.Info("LFS server enabled") log.Info("LFS server enabled")
} }
if setting.Annex.Enabled {
log.Info("git-annex enabled")
}
var err error var err error
switch setting.Protocol { switch setting.Protocol {
case setting.HTTP: case setting.HTTP:

View file

@ -1,192 +0,0 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
// Unlike modules/lfs, which operates mainly on git.Blobs, this operates on git.TreeEntrys.
// The motivation for this is that TreeEntrys have an easy pointer to the on-disk repo path,
// while blobs do not (in fact, if building with TAGS=gogit, blobs might exist only in a mock
// filesystem, living only in process RAM). We must have the on-disk path to do anything
// useful with git-annex because all of its interesting data is on-disk under .git/annex/.
package annex
import (
"errors"
"fmt"
"io/fs"
"os"
"path"
"path/filepath"
"regexp"
"strings"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/typesniffer"
)
// ErrBlobIsNotAnnexed occurs if a blob does not contain a valid annex key
var ErrBlobIsNotAnnexed = errors.New("not a git-annex pointer")
func LookupKey(blob *git.Blob) (string, error) {
stdout, _, err := git.NewCommand(git.DefaultContext, "annex", "lookupkey", "--ref").AddDynamicArguments(blob.ID.String()).RunStdString(&git.RunOpts{Dir: blob.Repo().Path})
if err != nil {
return "", ErrBlobIsNotAnnexed
}
key := strings.TrimSpace(stdout)
return key, nil
}
func ContentLocationFromKey(repoPath, key string) (string, error) {
contentLocation, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, "annex", "contentlocation").AddDynamicArguments(key).RunStdString(&git.RunOpts{Dir: repoPath})
if err != nil {
return "", fmt.Errorf("in %s: %s does not seem to be a valid annexed file: %w", repoPath, key, err)
}
contentLocation = strings.TrimSpace(contentLocation)
contentLocation = path.Clean("/" + contentLocation)[1:] // prevent directory traversals
contentLocation = path.Join(repoPath, contentLocation)
return contentLocation, nil
}
// return the absolute path of the content pointed to by the annex pointer stored in the git object
// errors if the content is not found in this repo
func ContentLocation(blob *git.Blob) (string, error) {
key, err := LookupKey(blob)
if err != nil {
return "", err
}
return ContentLocationFromKey(blob.Repo().Path, key)
}
// returns a stream open to the annex content
func Content(blob *git.Blob) (*os.File, error) {
contentLocation, err := ContentLocation(blob)
if err != nil {
return nil, err
}
return os.Open(contentLocation)
}
// whether the object appears to be a valid annex pointer
// does *not* verify if the content is actually in this repo;
// for that, use ContentLocation()
func IsAnnexed(blob *git.Blob) (bool, error) {
if !setting.Annex.Enabled {
return false, nil
}
// LookupKey is written to only return well-formed keys
// so the test is just to see if it errors
_, err := LookupKey(blob)
if err != nil {
if errors.Is(err, ErrBlobIsNotAnnexed) {
return false, nil
}
return false, err
}
return true, nil
}
// IsAnnexRepo determines if repo is a git-annex enabled repository
func IsAnnexRepo(repo *git.Repository) bool {
_, _, err := git.NewCommand(repo.Ctx, "config", "annex.uuid").RunStdString(&git.RunOpts{Dir: repo.Path})
return err == nil
}
var repoConfigFileRe = regexp.MustCompile("[^/]+/[^/]+.git/config$")
var (
uuid2repoPathCache = make(map[string]string)
repoPath2uuidCache = make(map[string]string)
)
func Init() error {
if !setting.Annex.Enabled {
return nil
}
log.Info("Populating the git-annex UUID cache with existing repositories")
return updateUUID2RepoPathCache()
}
func updateUUID2RepoPathCache() error {
return filepath.WalkDir(setting.RepoRootPath, func(path string, d fs.DirEntry, err error) error {
if err == nil && repoConfigFileRe.MatchString(path) {
thisRepoPath := strings.TrimSuffix(path, "/config")
_, ok := repoPath2uuidCache[thisRepoPath]
if ok {
return nil
}
stdout, _, err := git.NewCommand(git.DefaultContext, "config", "annex.uuid").RunStdString(&git.RunOpts{Dir: thisRepoPath})
if err != nil {
return nil
}
repoUUID := strings.TrimSpace(stdout)
if repoUUID != "" {
uuid2repoPathCache[repoUUID] = thisRepoPath
repoPath2uuidCache[thisRepoPath] = repoUUID
}
}
return nil
})
}
func repoPathFromUUIDCache(uuid string) (string, error) {
if repoPath, ok := uuid2repoPathCache[uuid]; ok {
return repoPath, nil
}
// If the cache didn't contain an entry for the UUID then update the cache and try again
if err := updateUUID2RepoPathCache(); err != nil {
return "", err
}
if repoPath, ok := uuid2repoPathCache[uuid]; ok {
return repoPath, nil
}
return "", fmt.Errorf("no repository known for UUID '%s'", uuid)
}
func checkValidity(uuid, repoPath string) (bool, error) {
stdout, _, err := git.NewCommand(git.DefaultContext, "config", "annex.uuid").RunStdString(&git.RunOpts{Dir: repoPath})
if err != nil {
return false, err
}
repoUUID := strings.TrimSpace(stdout)
return uuid == repoUUID, nil
}
func removeCachedEntries(uuid, repoPath string) {
delete(uuid2repoPathCache, uuid)
delete(repoPath2uuidCache, repoPath)
}
func UUID2RepoPath(uuid string) (string, error) {
// Get the current cache entry for the UUID
repoPath, err := repoPathFromUUIDCache(uuid)
if err != nil {
return "", err
}
// Check if it is still up-to-date
valid, err := checkValidity(uuid, repoPath)
if err != nil {
return "", err
}
if !valid {
// If it isn't, remove the cache entry and try again
removeCachedEntries(uuid, repoPath)
return UUID2RepoPath(uuid)
}
// Otherwise just return the cached entry
return repoPath, nil
}
// GuessContentType guesses the content type of the annexed blob.
func GuessContentType(blob *git.Blob) (typesniffer.SniffedType, error) {
r, err := Content(blob)
if err != nil {
return typesniffer.SniffedType{}, err
}
defer r.Close()
return typesniffer.DetectContentTypeFromReader(r)
}

View file

@ -16,7 +16,6 @@ import (
"strings" "strings"
"unicode/utf8" "unicode/utf8"
"code.gitea.io/gitea/modules/annex"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@ -102,12 +101,6 @@ func Int64sToStrings(ints []int64) []string {
// EntryIcon returns the octicon class for displaying files/directories // EntryIcon returns the octicon class for displaying files/directories
func EntryIcon(entry *git.TreeEntry) string { func EntryIcon(entry *git.TreeEntry) string {
isAnnexed, _ := annex.IsAnnexed(entry.Blob())
if isAnnexed {
// Show git-annex files as binary files to differentiate them from non-annexed files
// TODO: find a more suitable icon, maybe something related to git-annex
return "file-binary"
}
switch { switch {
case entry.IsLink(): case entry.IsLink():
te, _, err := entry.FollowLink() te, _, err := entry.FollowLink()

View file

@ -126,10 +126,6 @@ func (b *blobReader) Close() error {
return nil return nil
} }
func (b *Blob) Repo() *Repository {
return b.repo
}
// Name returns name of the tree entry this blob object was created from (or empty string) // Name returns name of the tree entry this blob object was created from (or empty string)
func (b *Blob) Name() string { func (b *Blob) Name() string {
return b.name return b.name

View file

@ -457,13 +457,12 @@ func (c *Command) RunStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunS
} }
// AllowLFSFiltersArgs return globalCommandArgs with lfs filter, it should only be used for tests // AllowLFSFiltersArgs return globalCommandArgs with lfs filter, it should only be used for tests
// It also re-enables git-credential(1), which is used to test git-annex's HTTP support
func AllowLFSFiltersArgs() TrustedCmdArgs { func AllowLFSFiltersArgs() TrustedCmdArgs {
// Now here we should explicitly allow lfs filters to run // Now here we should explicitly allow lfs filters to run
filteredLFSGlobalArgs := make(TrustedCmdArgs, len(globalCommandArgs)) filteredLFSGlobalArgs := make(TrustedCmdArgs, len(globalCommandArgs))
j := 0 j := 0
for _, arg := range globalCommandArgs { for _, arg := range globalCommandArgs {
if strings.Contains(string(arg), "lfs") || strings.Contains(string(arg), "credential") { if strings.Contains(string(arg), "lfs") {
j-- j--
} else { } else {
filteredLFSGlobalArgs[j] = arg filteredLFSGlobalArgs[j] = arg

View file

@ -6,14 +6,12 @@ package git
import ( import (
"bytes" "bytes"
"context" "context"
"errors"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/git/internal" //nolint:depguard // only this file can use the internal type CmdArg, other files and packages should use AddXxx functions
) )
// ReadTreeToIndex reads a treeish to the index // ReadTreeToIndex reads a treeish to the index
@ -104,32 +102,6 @@ func (repo *Repository) LsFiles(filenames ...string) ([]string, error) {
return filelist, err return filelist, err
} }
// Gives a list of all files in a directory and below
func (repo *Repository) LsFilesFromDirectory(directory, branch string) ([]string, error) {
if branch == "" {
return nil, errors.New("branch not found in context URL")
}
cmd := NewCommand(repo.Ctx, "ls-files", internal.CmdArg("--with-tree=" + branch))
if len(directory) > 0 {
cmd = NewCommand(repo.Ctx, "ls-files", internal.CmdArg("--with-tree=" + branch), internal.CmdArg("--directory"), internal.CmdArg(directory))
}
res, stderror, err := cmd.RunStdBytes(&RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
if len(stderror) > 0 {
return nil, errors.New(string(stderror))
}
lines := strings.Split(string(res), "\n")
return lines, nil
}
// RemoveFilesFromIndex removes given filenames from the index - it does not check whether they are present. // RemoveFilesFromIndex removes given filenames from the index - it does not check whether they are present.
func (repo *Repository) RemoveFilesFromIndex(filenames ...string) error { func (repo *Repository) RemoveFilesFromIndex(filenames ...string) error {
objectFormat, err := repo.GetObjectFormat() objectFormat, err := repo.GetObjectFormat()

View file

@ -12,7 +12,6 @@ import (
"runtime" "runtime"
"strings" "strings"
"code.gitea.io/gitea/modules/annex"
"code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
@ -87,22 +86,8 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.
commands = strings.Fields(command) commands = strings.Fields(command)
args = commands[1:] args = commands[1:]
) )
isAnnexed, _ := annex.IsAnnexed(ctx.Blob)
// if a renderer wants to read a file, and we have annexed content, we can if p.IsInputFile {
// provide the annex key file location directly to the renderer. git-annex
// takes care of having that location be read-only, so no critical
// protection layer is needed. Moreover, the file readily exists, and
// expensive temporary files can be avoided, also allowing an operator
// to raise MAX_DISPLAY_FILE_SIZE without much negative impact.
if p.IsInputFile && isAnnexed {
// look for annexed content, will be empty, if there is none
annexContentLocation, _ := annex.ContentLocation(ctx.Blob)
// we call the renderer, even if there is no annex content present.
// showing the pointer file content is not much use, and a topical
// renderer might be able to produce something useful from the
// filename alone (present in ENV)
args = append(args, annexContentLocation)
} else if p.IsInputFile {
// write to temp file // write to temp file
f, err := os.CreateTemp("", "gitea_input") f, err := os.CreateTemp("", "gitea_input")
if err != nil { if err != nil {
@ -145,12 +130,6 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.
os.Environ(), os.Environ(),
"GITEA_PREFIX_SRC="+ctx.Links.SrcLink(), "GITEA_PREFIX_SRC="+ctx.Links.SrcLink(),
"GITEA_PREFIX_RAW="+ctx.Links.RawLink(), "GITEA_PREFIX_RAW="+ctx.Links.RawLink(),
// also communicate the relative path of the to-be-rendered item.
// this enables the renderer to make use of the original file name
// and path, e.g., to make rendering or dtype-detection decisions
// that go beyond the originally matched extension. Even if the
// content is directly streamed to STDIN
"GITEA_RELATIVE_PATH="+ctx.RelativePath,
) )
if !p.IsInputFile { if !p.IsInputFile {
cmd.Stdin = input cmd.Stdin = input

View file

@ -67,18 +67,14 @@ type Header struct {
// RenderContext represents a render context // RenderContext represents a render context
type RenderContext struct { type RenderContext struct {
Ctx context.Context Ctx context.Context
RelativePath string // relative path from tree root of the branch RelativePath string // relative path from tree root of the branch
Type string Type string
IsWiki bool IsWiki bool
Links Links Links Links
Metas map[string]string Metas map[string]string
DefaultLink string DefaultLink string
GitRepo *git.Repository GitRepo *git.Repository
// reporting the target blob that is to-be-rendered enables
// deeper inspection in the handler for external renderer
// (i.e., more targeted handling of annexed files)
Blob *git.Blob
ShaExistCache map[string]bool ShaExistCache map[string]bool
cancelFn func() cancelFn func()
SidebarTocNode ast.Node SidebarTocNode ast.Node

View file

@ -40,7 +40,6 @@ type ServCommandResults struct {
UserName string UserName string
UserEmail string UserEmail string
UserID int64 UserID int64
UserMode perm.AccessMode
OwnerName string OwnerName string
RepoName string RepoName string
RepoID int64 RepoID int64

View file

@ -1,25 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package setting
import (
"code.gitea.io/gitea/modules/log"
)
// Annex represents the configuration for git-annex
var Annex = struct {
Enabled bool `ini:"ENABLED"`
DisableP2PHTTP bool `ini:"DISABLE_P2PHTTP"`
}{}
func loadAnnexFrom(rootCfg ConfigProvider) {
sec := rootCfg.Section("annex")
if err := sec.MapTo(&Annex); err != nil {
log.Fatal("Failed to map Annex settings: %v", err)
}
if !sec.HasKey("DISABLE_P2PHTTP") {
// If DisableP2PHTTP is not explicitly set then use DisableHTTPGit as its default
Annex.DisableP2PHTTP = Repository.DisableHTTPGit
}
}

View file

@ -85,8 +85,6 @@ var Service = struct {
DefaultOrgMemberVisible bool DefaultOrgMemberVisible bool
UserDeleteWithCommentsMaxTime time.Duration UserDeleteWithCommentsMaxTime time.Duration
ValidSiteURLSchemes []string ValidSiteURLSchemes []string
LandingPageInfoEnabled bool
SignInForgottenPasswordEnabled bool
// OpenID settings // OpenID settings
EnableOpenIDSignIn bool EnableOpenIDSignIn bool
@ -215,8 +213,6 @@ func loadServiceFrom(rootCfg ConfigProvider) {
if Service.EnableTimetracking { if Service.EnableTimetracking {
Service.DefaultEnableTimetracking = sec.Key("DEFAULT_ENABLE_TIMETRACKING").MustBool(true) Service.DefaultEnableTimetracking = sec.Key("DEFAULT_ENABLE_TIMETRACKING").MustBool(true)
} }
Service.LandingPageInfoEnabled = sec.Key("ENABLE_LANDING_PAGE_INFO").MustBool(true)
Service.SignInForgottenPasswordEnabled = sec.Key("SIGNIN_FORGOTTEN_PASSWORD_ENABLED").MustBool(true)
Service.DefaultEnableDependencies = sec.Key("DEFAULT_ENABLE_DEPENDENCIES").MustBool(true) Service.DefaultEnableDependencies = sec.Key("DEFAULT_ENABLE_DEPENDENCIES").MustBool(true)
Service.AllowCrossRepositoryDependencies = sec.Key("ALLOW_CROSS_REPOSITORY_DEPENDENCIES").MustBool(true) Service.AllowCrossRepositoryDependencies = sec.Key("ALLOW_CROSS_REPOSITORY_DEPENDENCIES").MustBool(true)
Service.DefaultAllowOnlyContributorsToTrackTime = sec.Key("DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME").MustBool(true) Service.DefaultAllowOnlyContributorsToTrackTime = sec.Key("DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME").MustBool(true)

View file

@ -153,7 +153,6 @@ func loadCommonSettingsFrom(cfg ConfigProvider) error {
loadCamoFrom(cfg) loadCamoFrom(cfg)
loadI18nFrom(cfg) loadI18nFrom(cfg)
loadGitFrom(cfg) loadGitFrom(cfg)
loadAnnexFrom(cfg)
loadMirrorFrom(cfg) loadMirrorFrom(cfg)
loadMarkupFrom(cfg) loadMarkupFrom(cfg)
loadQuotaFrom(cfg) loadQuotaFrom(cfg)

View file

@ -4,9 +4,7 @@
package util package util
import ( import (
"io/fs"
"os" "os"
"path/filepath"
"runtime" "runtime"
"syscall" "syscall"
"time" "time"
@ -43,48 +41,10 @@ func Remove(name string) error {
return err return err
} }
// MakeWritable recursively makes the named directory writable. // RemoveAll removes the named file or (empty) directory with at most 5 attempts.
func MakeWritable(name string) error {
return filepath.WalkDir(name, func(path string, d fs.DirEntry, err error) error {
// NB: this is called WalkDir but it works on a single file too
if err == nil {
info, err := d.Info()
if err != nil {
return err
}
// Don't try chmod'ing symlinks (will fail with broken symlinks)
if info.Mode()&os.ModeSymlink != os.ModeSymlink {
// 0200 == u+w, in octal unix permission notation
err = os.Chmod(path, info.Mode()|0o200)
if err != nil {
return err
}
}
}
return nil
})
}
// RemoveAll removes the named file or directory with at most 5 attempts.
func RemoveAll(name string) error { func RemoveAll(name string) error {
var err error var err error
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
// Do chmod -R +w to help ensure the removal succeeds.
// In particular, in the git-annex case, this handles
// https://git-annex.branchable.com/internals/lockdown/ :
//
// > (The only bad consequence of this is that rm -rf .git
// > doesn't work unless you first run chmod -R +w .git)
err = MakeWritable(name)
if err != nil {
// try again
<-time.After(100 * time.Millisecond)
continue
}
err = os.RemoveAll(name) err = os.RemoveAll(name)
if err == nil { if err == nil {
break break

View file

@ -1317,7 +1317,6 @@ view_git_blame=Zobrazit git blame
video_not_supported_in_browser=Váš prohlížeč nepodporuje značku HTML5 „video“. video_not_supported_in_browser=Váš prohlížeč nepodporuje značku HTML5 „video“.
audio_not_supported_in_browser=Váš prohlížeč nepodporuje značku HTML5 „audio“. audio_not_supported_in_browser=Váš prohlížeč nepodporuje značku HTML5 „audio“.
stored_lfs=Uloženo pomocí Git LFS stored_lfs=Uloženo pomocí Git LFS
stored_annex=Uloženo pomocí Git Annex
symbolic_link=Symbolický odkaz symbolic_link=Symbolický odkaz
executable_file=Spustitelný soubor executable_file=Spustitelný soubor
vendored = Vendorováno vendored = Vendorováno
@ -1343,7 +1342,6 @@ editor.upload_file=Nahrát soubor
editor.edit_file=Upravit soubor editor.edit_file=Upravit soubor
editor.preview_changes=Náhled změn editor.preview_changes=Náhled změn
editor.cannot_edit_lfs_files=LFS soubory nemohou být upravovány přes webové rozhraní. editor.cannot_edit_lfs_files=LFS soubory nemohou být upravovány přes webové rozhraní.
editor.cannot_edit_annex_files=Annex soubory nemohou být upravovány přes webové rozhraní.
editor.cannot_edit_non_text_files=Binární soubory nemohou být upravovány přes webové rozhraní. editor.cannot_edit_non_text_files=Binární soubory nemohou být upravovány přes webové rozhraní.
editor.edit_this_file=Upravit soubor editor.edit_this_file=Upravit soubor
editor.this_file_locked=Soubor je uzamčen editor.this_file_locked=Soubor je uzamčen

View file

@ -1317,8 +1317,6 @@ view_git_blame=„git blame“ ansehen
video_not_supported_in_browser=Dein Browser unterstützt das HTML5-„video“-Tag nicht. video_not_supported_in_browser=Dein Browser unterstützt das HTML5-„video“-Tag nicht.
audio_not_supported_in_browser=Dein Browser unterstützt das HTML5-„audio“-Tag nicht. audio_not_supported_in_browser=Dein Browser unterstützt das HTML5-„audio“-Tag nicht.
stored_lfs=Gespeichert mit Git LFS stored_lfs=Gespeichert mit Git LFS
stored_annex=Gespeichert mit Git Annex
stored_annex_not_present = hier nicht vorhanden, versuche git annex whereis
symbolic_link=Softlink symbolic_link=Softlink
executable_file=Ausführbare Datei executable_file=Ausführbare Datei
commit_graph=Commit-Graph commit_graph=Commit-Graph
@ -1342,7 +1340,6 @@ editor.upload_file=Datei hochladen
editor.edit_file=Datei bearbeiten editor.edit_file=Datei bearbeiten
editor.preview_changes=Vorschau der Änderungen editor.preview_changes=Vorschau der Änderungen
editor.cannot_edit_lfs_files=LFS-Dateien können im Webinterface nicht bearbeitet werden. editor.cannot_edit_lfs_files=LFS-Dateien können im Webinterface nicht bearbeitet werden.
editor.cannot_edit_annex_files=Annex-Dateien können im Webinterface nicht bearbeitet werden.
editor.cannot_edit_non_text_files=Binärdateien können nicht im Webinterface bearbeitet werden. editor.cannot_edit_non_text_files=Binärdateien können nicht im Webinterface bearbeitet werden.
editor.edit_this_file=Datei bearbeiten editor.edit_this_file=Datei bearbeiten
editor.this_file_locked=Datei ist gesperrt editor.this_file_locked=Datei ist gesperrt

View file

@ -1314,7 +1314,6 @@ view_git_blame=Προβολή git blame
video_not_supported_in_browser=Το πρόγραμμα περιήγησής σας δεν υποστηρίζει την ετικέτα HTML5 «video». video_not_supported_in_browser=Το πρόγραμμα περιήγησής σας δεν υποστηρίζει την ετικέτα HTML5 «video».
audio_not_supported_in_browser=Το πρόγραμμα περιήγησής σας δεν υποστηρίζει την ετικέτα HTML5 «audio». audio_not_supported_in_browser=Το πρόγραμμα περιήγησής σας δεν υποστηρίζει την ετικέτα HTML5 «audio».
stored_lfs=Αποθηκεύτηκε με το Git LFS stored_lfs=Αποθηκεύτηκε με το Git LFS
stored_annex=Αποθηκεύτηκε με το Git Annex
symbolic_link=Symbolic link symbolic_link=Symbolic link
executable_file=Εκτελέσιμο αρχείο executable_file=Εκτελέσιμο αρχείο
commit_graph=Γράφημα υποβολών commit_graph=Γράφημα υποβολών
@ -1338,7 +1337,6 @@ editor.upload_file=Ανέβασμα αρχείου
editor.edit_file=Επεξεργασία αρχείου editor.edit_file=Επεξεργασία αρχείου
editor.preview_changes=Προεπισκόπηση αλλαγών editor.preview_changes=Προεπισκόπηση αλλαγών
editor.cannot_edit_lfs_files=Τα αρχεία LFS δεν μπορούν να επεξεργαστούν στη διεπαφή web. editor.cannot_edit_lfs_files=Τα αρχεία LFS δεν μπορούν να επεξεργαστούν στη διεπαφή web.
editor.cannot_edit_annex_files=Τα αρχεία Annex δεν μπορούν να επεξεργαστούν στη διεπαφή web.
editor.cannot_edit_non_text_files=Τα δυαδικά αρχεία δεν μπορούν να επεξεργαστούν στη διεπαφή web. editor.cannot_edit_non_text_files=Τα δυαδικά αρχεία δεν μπορούν να επεξεργαστούν στη διεπαφή web.
editor.edit_this_file=Επεξεργασία αρχείου editor.edit_this_file=Επεξεργασία αρχείου
editor.this_file_locked=Το αρχείο είναι κλειδωμένο editor.this_file_locked=Το αρχείο είναι κλειδωμένο

View file

@ -1337,8 +1337,6 @@ view_git_blame = View git blame
video_not_supported_in_browser = Your browser does not support the HTML5 "video" tag. video_not_supported_in_browser = Your browser does not support the HTML5 "video" tag.
audio_not_supported_in_browser = Your browser does not support the HTML5 "audio" tag. audio_not_supported_in_browser = Your browser does not support the HTML5 "audio" tag.
stored_lfs = Stored with Git LFS stored_lfs = Stored with Git LFS
stored_annex = Stored with Git Annex
stored_annex_not_present = not present here, try using git annex whereis
symbolic_link = Symbolic link symbolic_link = Symbolic link
executable_file = Executable file executable_file = Executable file
vendored = Vendored vendored = Vendored
@ -1366,7 +1364,6 @@ editor.upload_file = Upload file
editor.edit_file = Edit file editor.edit_file = Edit file
editor.preview_changes = Preview changes editor.preview_changes = Preview changes
editor.cannot_edit_lfs_files = LFS files cannot be edited in the web interface. editor.cannot_edit_lfs_files = LFS files cannot be edited in the web interface.
editor.cannot_edit_annex_files = Annex files cannot be edited in the web interface.
editor.cannot_edit_non_text_files = Binary files cannot be edited in the web interface. editor.cannot_edit_non_text_files = Binary files cannot be edited in the web interface.
editor.edit_this_file = Edit file editor.edit_this_file = Edit file
editor.this_file_locked = File is locked editor.this_file_locked = File is locked

View file

@ -1312,7 +1312,6 @@ view_git_blame=Ver Git blame
video_not_supported_in_browser=Su navegador no soporta el tag "video" de HTML5. video_not_supported_in_browser=Su navegador no soporta el tag "video" de HTML5.
audio_not_supported_in_browser=Su navegador no soporta el tag "audio" de HTML5. audio_not_supported_in_browser=Su navegador no soporta el tag "audio" de HTML5.
stored_lfs=Almacenados con Git LFS stored_lfs=Almacenados con Git LFS
stored_annex=Almacenados con Git Annex
symbolic_link=Enlace simbólico symbolic_link=Enlace simbólico
executable_file=Archivo ejecutable executable_file=Archivo ejecutable
commit_graph=Gráfico de commits commit_graph=Gráfico de commits
@ -1336,7 +1335,6 @@ editor.upload_file=Subir archivo
editor.edit_file=Editar archivo editor.edit_file=Editar archivo
editor.preview_changes=Vista previa de los cambios editor.preview_changes=Vista previa de los cambios
editor.cannot_edit_lfs_files=Los archivos LFS no se pueden editar en la interfaz web. editor.cannot_edit_lfs_files=Los archivos LFS no se pueden editar en la interfaz web.
editor.cannot_edit_annex_files=Los archivos Annex no se pueden editar en la interfaz web.
editor.cannot_edit_non_text_files=Los archivos binarios no se pueden editar en la interfaz web. editor.cannot_edit_non_text_files=Los archivos binarios no se pueden editar en la interfaz web.
editor.edit_this_file=Editar archivo editor.edit_this_file=Editar archivo
editor.this_file_locked=El archivo está bloqueado editor.this_file_locked=El archivo está bloqueado

View file

@ -951,7 +951,6 @@ file_copy_permalink=پرمالینک را کپی کنید
video_not_supported_in_browser=مرورگر شما از تگ video که در HTML5 تعریف شده است، پشتیبانی نمی کند. video_not_supported_in_browser=مرورگر شما از تگ video که در HTML5 تعریف شده است، پشتیبانی نمی کند.
audio_not_supported_in_browser=مرورگر شما از تگ audio که در HTML5 تعریف شده است، پشتیبانی نمی کند. audio_not_supported_in_browser=مرورگر شما از تگ audio که در HTML5 تعریف شده است، پشتیبانی نمی کند.
stored_lfs=ذخیره شده با GIT LFS stored_lfs=ذخیره شده با GIT LFS
stored_annex=ذخیره شده با GIT Annex
symbolic_link=پیوند نمادین symbolic_link=پیوند نمادین
commit_graph=نمودار کامیت commit_graph=نمودار کامیت
commit_graph.select=انتخاب برنچها commit_graph.select=انتخاب برنچها
@ -969,7 +968,6 @@ editor.upload_file=بارگذاری پرونده
editor.edit_file=ویرایش پرونده editor.edit_file=ویرایش پرونده
editor.preview_changes=پیش نمایش تغییرات editor.preview_changes=پیش نمایش تغییرات
editor.cannot_edit_lfs_files=پرونده های LFS در صحفه وب قابل تغییر نیست. editor.cannot_edit_lfs_files=پرونده های LFS در صحفه وب قابل تغییر نیست.
editor.cannot_edit_annex_files=پرونده های Annex در صحفه وب قابل تغییر نیست.
editor.cannot_edit_non_text_files=پرونده‎های دودویی در صفحه وب قابل تغییر نیست. editor.cannot_edit_non_text_files=پرونده‎های دودویی در صفحه وب قابل تغییر نیست.
editor.edit_this_file=ویرایش پرونده editor.edit_this_file=ویرایش پرونده
editor.this_file_locked=پرونده قفل شده است editor.this_file_locked=پرونده قفل شده است

View file

@ -1318,7 +1318,6 @@ view_git_blame=Voir Git blame
video_not_supported_in_browser=Votre navigateur ne supporte pas la balise « vidéo » HTML5. video_not_supported_in_browser=Votre navigateur ne supporte pas la balise « vidéo » HTML5.
audio_not_supported_in_browser=Votre navigateur ne supporte pas la balise « audio » HTML5. audio_not_supported_in_browser=Votre navigateur ne supporte pas la balise « audio » HTML5.
stored_lfs=Stocké avec Git LFS stored_lfs=Stocké avec Git LFS
stored_annex=Stocké avec Git Annex
symbolic_link=Lien symbolique symbolic_link=Lien symbolique
executable_file=Fichier exécutable executable_file=Fichier exécutable
vendored = Vendored vendored = Vendored
@ -1344,7 +1343,6 @@ editor.upload_file=Téléverser un fichier
editor.edit_file=Modifier le fichier editor.edit_file=Modifier le fichier
editor.preview_changes=Aperçu des modifications editor.preview_changes=Aperçu des modifications
editor.cannot_edit_lfs_files=Les fichiers LFS ne peuvent pas être modifiés dans l'interface web. editor.cannot_edit_lfs_files=Les fichiers LFS ne peuvent pas être modifiés dans l'interface web.
editor.cannot_edit_annex_files=Les fichiers Annex ne peuvent pas être modifiés dans l'interface web.
editor.cannot_edit_non_text_files=Les fichiers binaires ne peuvent pas être édités dans l'interface web. editor.cannot_edit_non_text_files=Les fichiers binaires ne peuvent pas être édités dans l'interface web.
editor.edit_this_file=Modifier le fichier editor.edit_this_file=Modifier le fichier
editor.this_file_locked=Le fichier est verrouillé editor.this_file_locked=Le fichier est verrouillé

View file

@ -787,7 +787,6 @@ file_too_large=Ez a fájl túl nagy ahhoz, hogy megjelenítsük.
video_not_supported_in_browser=A böngésző nem támogatja a HTML5 video tag-et. video_not_supported_in_browser=A böngésző nem támogatja a HTML5 video tag-et.
audio_not_supported_in_browser=A böngésző nem támogatja a HTML5 audio tag-et. audio_not_supported_in_browser=A böngésző nem támogatja a HTML5 audio tag-et.
stored_lfs=Git LFS-el eltárolva stored_lfs=Git LFS-el eltárolva
stored_annex=Git Annex-el eltárolva
symbolic_link=Szimbolikus hivatkozás symbolic_link=Szimbolikus hivatkozás
commit_graph=Commit gráf commit_graph=Commit gráf
commit_graph.hide_pr_refs=Pull request-ek elrejtése commit_graph.hide_pr_refs=Pull request-ek elrejtése
@ -800,7 +799,6 @@ editor.upload_file=Fájl feltöltése
editor.edit_file=Fájl szerkesztése editor.edit_file=Fájl szerkesztése
editor.preview_changes=Változások előnézete editor.preview_changes=Változások előnézete
editor.cannot_edit_lfs_files=LFS fájlok nem szerkeszthetőek a webes felületen. editor.cannot_edit_lfs_files=LFS fájlok nem szerkeszthetőek a webes felületen.
editor.cannot_edit_annex_files=Annex fájlok nem szerkeszthetőek a webes felületen.
editor.cannot_edit_non_text_files=Bináris fájlok nem szerkeszthetőek a webes felületen. editor.cannot_edit_non_text_files=Bináris fájlok nem szerkeszthetőek a webes felületen.
editor.edit_this_file=Fájl szerkesztése editor.edit_this_file=Fájl szerkesztése
editor.this_file_locked=Zárolt állomány editor.this_file_locked=Zárolt állomány

View file

@ -596,7 +596,6 @@ file_permalink=Permalink
file_too_large=Berkas terlalu besar untuk ditampilkan. file_too_large=Berkas terlalu besar untuk ditampilkan.
stored_lfs=Tersimpan dengan GIT LFS stored_lfs=Tersimpan dengan GIT LFS
stored_annex=Tersimpan dengan GIT Annex
commit_graph=Grafik Komit commit_graph=Grafik Komit
blame=Salahkan blame=Salahkan
normal_view=Pandangan Normal normal_view=Pandangan Normal
@ -608,7 +607,6 @@ editor.upload_file=Unggah Berkas
editor.edit_file=Sunting Berkas editor.edit_file=Sunting Berkas
editor.preview_changes=Tinjau Perubahan editor.preview_changes=Tinjau Perubahan
editor.cannot_edit_lfs_files=Berkas LFS tidak dapat disunting dalam antarmuka web. editor.cannot_edit_lfs_files=Berkas LFS tidak dapat disunting dalam antarmuka web.
editor.cannot_edit_annex_files=Berkas Annex tidak dapat disunting dalam antarmuka web.
editor.cannot_edit_non_text_files=Berkas biner tidak dapat disunting dalam antarmuka web. editor.cannot_edit_non_text_files=Berkas biner tidak dapat disunting dalam antarmuka web.
editor.edit_this_file=Sunting Berkas editor.edit_this_file=Sunting Berkas
editor.this_file_locked=Berkas terkunci editor.this_file_locked=Berkas terkunci

View file

@ -680,7 +680,6 @@ file_view_rendered=Skoða Unnið
file_copy_permalink=Afrita Varanlega Slóð file_copy_permalink=Afrita Varanlega Slóð
stored_lfs=Geymt með Git LFS stored_lfs=Geymt með Git LFS
stored_annex=Geymt með Git Annex
commit_graph.hide_pr_refs=Fela Sameiningarbeiðnir commit_graph.hide_pr_refs=Fela Sameiningarbeiðnir
commit_graph.monochrome=Einlitað commit_graph.monochrome=Einlitað
commit_graph.color=Litað commit_graph.color=Litað

View file

@ -1267,7 +1267,6 @@ view_git_blame=Visualizza git incolpa
video_not_supported_in_browser=Il tuo browser non supporta le etichette "video" di HTML5. video_not_supported_in_browser=Il tuo browser non supporta le etichette "video" di HTML5.
audio_not_supported_in_browser=Il tuo browser non supporta le etichette "audio" di HTML5. audio_not_supported_in_browser=Il tuo browser non supporta le etichette "audio" di HTML5.
stored_lfs=Memorizzati con Git LFS stored_lfs=Memorizzati con Git LFS
stored_annex=Memorizzati con Git Annex
symbolic_link=Link Simbolico symbolic_link=Link Simbolico
commit_graph=Grafico dei commit commit_graph=Grafico dei commit
commit_graph.select=Seleziona rami commit_graph.select=Seleziona rami
@ -1286,7 +1285,6 @@ editor.upload_file=Carica file
editor.edit_file=Modifica file editor.edit_file=Modifica file
editor.preview_changes=Anteprima modifiche editor.preview_changes=Anteprima modifiche
editor.cannot_edit_lfs_files=I file LFS non possono essere modificati nell'interfaccia web. editor.cannot_edit_lfs_files=I file LFS non possono essere modificati nell'interfaccia web.
editor.cannot_edit_annex_files=I file Annex non possono essere modificati nell'interfaccia web.
editor.cannot_edit_non_text_files=I file binari non possono essere modificati tramite interfaccia web. editor.cannot_edit_non_text_files=I file binari non possono essere modificati tramite interfaccia web.
editor.edit_this_file=Modifica file editor.edit_this_file=Modifica file
editor.this_file_locked=Il file è bloccato editor.this_file_locked=Il file è bloccato

View file

@ -1305,7 +1305,6 @@ view_git_blame=Git Blameを表示
video_not_supported_in_browser=このブラウザはHTML5のvideoタグをサポートしていません。 video_not_supported_in_browser=このブラウザはHTML5のvideoタグをサポートしていません。
audio_not_supported_in_browser=このブラウザーはHTML5のaudioタグをサポートしていません。 audio_not_supported_in_browser=このブラウザーはHTML5のaudioタグをサポートしていません。
stored_lfs=Git LFSで保管されています stored_lfs=Git LFSで保管されています
stored_annex=Git Annexで保管されています
symbolic_link=シンボリック リンク symbolic_link=シンボリック リンク
executable_file=実行ファイル executable_file=実行ファイル
commit_graph=コミットグラフ commit_graph=コミットグラフ
@ -1329,7 +1328,6 @@ editor.upload_file=ファイルをアップロード
editor.edit_file=ファイルを編集 editor.edit_file=ファイルを編集
editor.preview_changes=変更をプレビュー editor.preview_changes=変更をプレビュー
editor.cannot_edit_lfs_files=LFSのファイルはWebインターフェースで編集できません。 editor.cannot_edit_lfs_files=LFSのファイルはWebインターフェースで編集できません。
editor.cannot_edit_annex_files=AnnexのファイルはWebインターフェースで編集できません。
editor.cannot_edit_non_text_files=バイナリファイルはWebインターフェースで編集できません。 editor.cannot_edit_non_text_files=バイナリファイルはWebインターフェースで編集できません。
editor.edit_this_file=ファイルを編集 editor.edit_this_file=ファイルを編集
editor.this_file_locked=ファイルはロックされています editor.this_file_locked=ファイルはロックされています

View file

@ -821,7 +821,6 @@ file_too_large=보여주기에는 파일이 너무 큽니다.
video_not_supported_in_browser=당신의 브라우저가 HTML5의 "video" 태그를 지원하지 않습니다. video_not_supported_in_browser=당신의 브라우저가 HTML5의 "video" 태그를 지원하지 않습니다.
audio_not_supported_in_browser=당신의 브라우저가 HTML5의 "audio" 태그를 지원하지 않습니다. audio_not_supported_in_browser=당신의 브라우저가 HTML5의 "audio" 태그를 지원하지 않습니다.
stored_lfs=Git LFS에 저장되어 있습니다 stored_lfs=Git LFS에 저장되어 있습니다
stored_annex=Git Annex에 저장되어 있습니다
commit_graph=커밋 그래프 commit_graph=커밋 그래프
editor.new_file=새 파일 editor.new_file=새 파일

View file

@ -1316,7 +1316,6 @@ view_git_blame=Apskatīt Git izmaiņu veicējus
video_not_supported_in_browser=Pārlūks neatbalsta HTML5 tagu "video". video_not_supported_in_browser=Pārlūks neatbalsta HTML5 tagu "video".
audio_not_supported_in_browser=Pārlūks neatbalsta HTML5 tagu "audio". audio_not_supported_in_browser=Pārlūks neatbalsta HTML5 tagu "audio".
stored_lfs=Saglabāts Git LFS stored_lfs=Saglabāts Git LFS
stored_annex=Saglabāts Git Annex
symbolic_link=Simboliska saite symbolic_link=Simboliska saite
executable_file=Izpildāma datne executable_file=Izpildāma datne
commit_graph=Iesūtījumu karte commit_graph=Iesūtījumu karte
@ -1340,7 +1339,6 @@ editor.upload_file=Augšupielādēt datni
editor.edit_file=Labot datni editor.edit_file=Labot datni
editor.preview_changes=Priekšskatīt izmaiņas editor.preview_changes=Priekšskatīt izmaiņas
editor.cannot_edit_lfs_files=LFS datnes tīmekļa saskarnē nevar labot. editor.cannot_edit_lfs_files=LFS datnes tīmekļa saskarnē nevar labot.
editor.cannot_edit_annex_files=Annex datnes tīmekļa saskarnē nevar labot.
editor.cannot_edit_non_text_files=Binārās datnes tīmekļa saskarnē nevar labot. editor.cannot_edit_non_text_files=Binārās datnes tīmekļa saskarnē nevar labot.
editor.edit_this_file=Labot datni editor.edit_this_file=Labot datni
editor.this_file_locked=Datne ir slēgta editor.this_file_locked=Datne ir slēgta

View file

@ -1285,7 +1285,6 @@ view_git_blame=Bekijk git blame
video_not_supported_in_browser=Uw browser ondersteunt de HTML5 "video" element niet. video_not_supported_in_browser=Uw browser ondersteunt de HTML5 "video" element niet.
audio_not_supported_in_browser=Uw browser ondersteunt de HTML5 "audio" element niet. audio_not_supported_in_browser=Uw browser ondersteunt de HTML5 "audio" element niet.
stored_lfs=Opgeslagen met Git LFS stored_lfs=Opgeslagen met Git LFS
stored_annex=Opgeslagen met Git Annex
symbolic_link=Symbolische link symbolic_link=Symbolische link
commit_graph=Commit grafiek commit_graph=Commit grafiek
commit_graph.select=Selecteer branches commit_graph.select=Selecteer branches
@ -1304,7 +1303,6 @@ editor.upload_file=Upload bestand
editor.edit_file=Bewerk bestand editor.edit_file=Bewerk bestand
editor.preview_changes=Voorbeeld tonen editor.preview_changes=Voorbeeld tonen
editor.cannot_edit_lfs_files=LFS-bestanden kunnen niet worden bewerkt in de webinterface. editor.cannot_edit_lfs_files=LFS-bestanden kunnen niet worden bewerkt in de webinterface.
editor.cannot_edit_annex_files=Annex-bestanden kunnen niet worden bewerkt in de webinterface.
editor.cannot_edit_non_text_files=Binaire bestanden kunnen niet worden bewerkt in de webinterface. editor.cannot_edit_non_text_files=Binaire bestanden kunnen niet worden bewerkt in de webinterface.
editor.edit_this_file=Bewerk bestand editor.edit_this_file=Bewerk bestand
editor.this_file_locked=Bestand is vergrendeld editor.this_file_locked=Bestand is vergrendeld

View file

@ -1249,7 +1249,6 @@ file_copy_permalink=Kopiuj bezpośredni odnośnik
video_not_supported_in_browser=Twoja przeglądarka nie obsługuje znacznika HTML5 "video". video_not_supported_in_browser=Twoja przeglądarka nie obsługuje znacznika HTML5 "video".
audio_not_supported_in_browser=Twoja przeglądarka nie obsługuje znacznika HTML5 "audio". audio_not_supported_in_browser=Twoja przeglądarka nie obsługuje znacznika HTML5 "audio".
stored_lfs=Przechowane za pomocą Git LFS stored_lfs=Przechowane za pomocą Git LFS
stored_annex=Przechowane za pomocą Git Annex
symbolic_link=Dowiązanie symboliczne symbolic_link=Dowiązanie symboliczne
commit_graph=Wykres commitów commit_graph=Wykres commitów
commit_graph.select=Wybierz gałęzie commit_graph.select=Wybierz gałęzie
@ -1267,7 +1266,6 @@ editor.upload_file=Wyślij plik
editor.edit_file=Edytuj plik editor.edit_file=Edytuj plik
editor.preview_changes=Podgląd zmian editor.preview_changes=Podgląd zmian
editor.cannot_edit_lfs_files=Pliki LFS nie mogą być edytowane poprzez interfejs przeglądarkowy. editor.cannot_edit_lfs_files=Pliki LFS nie mogą być edytowane poprzez interfejs przeglądarkowy.
editor.cannot_edit_annex_files=Pliki Annex nie mogą być edytowane poprzez interfejs przeglądarkowy.
editor.cannot_edit_non_text_files=Pliki binarne nie mogą być edytowane poprzez interfejs przeglądarkowy. editor.cannot_edit_non_text_files=Pliki binarne nie mogą być edytowane poprzez interfejs przeglądarkowy.
editor.edit_this_file=Edytuj plik editor.edit_this_file=Edytuj plik
editor.this_file_locked=Plik jest zablokowany editor.this_file_locked=Plik jest zablokowany

View file

@ -1310,7 +1310,6 @@ view_git_blame=Ver git blame
video_not_supported_in_browser=Seu navegador não tem suporte para a tag "video" do HTML5. video_not_supported_in_browser=Seu navegador não tem suporte para a tag "video" do HTML5.
audio_not_supported_in_browser=Seu navegador não tem suporte para a tag "audio" do HTML5. audio_not_supported_in_browser=Seu navegador não tem suporte para a tag "audio" do HTML5.
stored_lfs=Armazenado com Git LFS stored_lfs=Armazenado com Git LFS
stored_annex=Armazenado com Git Annex
symbolic_link=Link simbólico symbolic_link=Link simbólico
executable_file=Arquivo executável executable_file=Arquivo executável
commit_graph=Gráfico de commits commit_graph=Gráfico de commits
@ -1334,7 +1333,6 @@ editor.upload_file=Enviar arquivo
editor.edit_file=Editar arquivo editor.edit_file=Editar arquivo
editor.preview_changes=Pré-visualizar alterações editor.preview_changes=Pré-visualizar alterações
editor.cannot_edit_lfs_files=Arquivos LFS não podem ser editados na interface web. editor.cannot_edit_lfs_files=Arquivos LFS não podem ser editados na interface web.
editor.cannot_edit_annex_files=Arquivos Annex não podem ser editados na interface web.
editor.cannot_edit_non_text_files=Arquivos binários não podem ser editados na interface web. editor.cannot_edit_non_text_files=Arquivos binários não podem ser editados na interface web.
editor.edit_this_file=Editar arquivo editor.edit_this_file=Editar arquivo
editor.this_file_locked=Arquivo está bloqueado editor.this_file_locked=Arquivo está bloqueado

View file

@ -1321,7 +1321,6 @@ view_git_blame=Ver git blame
video_not_supported_in_browser=O seu navegador não suporta a etiqueta "video" do HTML5. video_not_supported_in_browser=O seu navegador não suporta a etiqueta "video" do HTML5.
audio_not_supported_in_browser=O seu navegador não suporta a etiqueta "audio" do HTML5. audio_not_supported_in_browser=O seu navegador não suporta a etiqueta "audio" do HTML5.
stored_lfs=Armazenado com Git LFS stored_lfs=Armazenado com Git LFS
stored_annex=Armazenado com Git Annex
symbolic_link=Ligação simbólica symbolic_link=Ligação simbólica
executable_file=Ficheiro executável executable_file=Ficheiro executável
vendored=Externo vendored=Externo
@ -1347,7 +1346,6 @@ editor.upload_file=Carregar ficheiro
editor.edit_file=Editar ficheiro editor.edit_file=Editar ficheiro
editor.preview_changes=Pré-visualizar modificações editor.preview_changes=Pré-visualizar modificações
editor.cannot_edit_lfs_files=Ficheiros LFS não podem ser editados na interface web. editor.cannot_edit_lfs_files=Ficheiros LFS não podem ser editados na interface web.
editor.cannot_edit_annex_files=Ficheiros Annex não podem ser editados na interface web.
editor.cannot_edit_non_text_files=Ficheiros binários não podem ser editados na interface da web. editor.cannot_edit_non_text_files=Ficheiros binários não podem ser editados na interface da web.
editor.edit_this_file=Editar ficheiro editor.edit_this_file=Editar ficheiro
editor.this_file_locked=Ficheiro bloqueado editor.this_file_locked=Ficheiro bloqueado

View file

@ -1304,7 +1304,6 @@ view_git_blame=Показать git blame
video_not_supported_in_browser=Ваш браузер не поддерживает тэг HTML5 «video». video_not_supported_in_browser=Ваш браузер не поддерживает тэг HTML5 «video».
audio_not_supported_in_browser=Ваш браузер не поддерживает тэг HTML5 «audio». audio_not_supported_in_browser=Ваш браузер не поддерживает тэг HTML5 «audio».
stored_lfs=Хранится Git LFS stored_lfs=Хранится Git LFS
stored_annex=Хранится Git Annex
symbolic_link=Символическая ссылка symbolic_link=Символическая ссылка
executable_file=Исполняемый файл executable_file=Исполняемый файл
commit_graph=Граф коммитов commit_graph=Граф коммитов
@ -1328,7 +1327,6 @@ editor.upload_file=Загрузить файл
editor.edit_file=Редактировать файл editor.edit_file=Редактировать файл
editor.preview_changes=Просмотр изменений editor.preview_changes=Просмотр изменений
editor.cannot_edit_lfs_files=LFS файлы невозможно редактировать в веб-интерфейсе. editor.cannot_edit_lfs_files=LFS файлы невозможно редактировать в веб-интерфейсе.
editor.cannot_edit_annex_files=Annex файлы невозможно редактировать в веб-интерфейсе.
editor.cannot_edit_non_text_files=Двоичные файлы нельзя редактировать в веб-интерфейсе. editor.cannot_edit_non_text_files=Двоичные файлы нельзя редактировать в веб-интерфейсе.
editor.edit_this_file=Редактировать файл editor.edit_this_file=Редактировать файл
editor.this_file_locked=Файл заблокирован editor.this_file_locked=Файл заблокирован

View file

@ -889,7 +889,6 @@ file_copy_permalink=පිටපත් මාමලින්ක්
video_not_supported_in_browser=ඔබගේ බ්රව්සරය HTML5 'වීඩියෝ' ටැගය සඳහා සහය නොදක්වයි. video_not_supported_in_browser=ඔබගේ බ්රව්සරය HTML5 'වීඩියෝ' ටැගය සඳහා සහය නොදක්වයි.
audio_not_supported_in_browser=ඔබගේ බ්රව්සරය HTML5 'ශ්රව්ය' ටැගය සඳහා සහය නොදක්වයි. audio_not_supported_in_browser=ඔබගේ බ්රව්සරය HTML5 'ශ්රව්ය' ටැගය සඳහා සහය නොදක්වයි.
stored_lfs=Git LFS සමඟ ගබඩා stored_lfs=Git LFS සමඟ ගබඩා
stored_annex=Git Annex සමඟ ගබඩා
symbolic_link=සංකේතාත්මක සබැඳිය symbolic_link=සංකේතාත්මක සබැඳිය
commit_graph=ප්රස්තාරය කැප commit_graph=ප්රස්තාරය කැප
commit_graph.select=ශාඛා තෝරන්න commit_graph.select=ශාඛා තෝරන්න
@ -907,7 +906,6 @@ editor.upload_file=ගොනුව උඩුගත කරන්න
editor.edit_file=ගොනුව සංස්කරණය editor.edit_file=ගොනුව සංස්කරණය
editor.preview_changes=වෙනස්කම් පෙරදසුන editor.preview_changes=වෙනස්කම් පෙරදසුන
editor.cannot_edit_lfs_files=LFS ගොනු වෙබ් අතුරු මුහුණත තුළ සංස්කරණය කළ නොහැක. editor.cannot_edit_lfs_files=LFS ගොනු වෙබ් අතුරු මුහුණත තුළ සංස්කරණය කළ නොහැක.
editor.cannot_edit_annex_files=Annex ගොනු වෙබ් අතුරු මුහුණත තුළ සංස්කරණය කළ නොහැක.
editor.cannot_edit_non_text_files=ද්විමය ගොනු වෙබ් අතුරු මුහුණත තුළ සංස්කරණය කළ නොහැක. editor.cannot_edit_non_text_files=ද්විමය ගොනු වෙබ් අතුරු මුහුණත තුළ සංස්කරණය කළ නොහැක.
editor.edit_this_file=ගොනුව සංස්කරණය editor.edit_this_file=ගොනුව සංස්කරණය
editor.this_file_locked=ගොනුවට අගුළු ලා ඇත editor.this_file_locked=ගොනුවට අගුළු ලා ඇත

View file

@ -1010,7 +1010,6 @@ view_git_blame=Zobraziť Git Blame
video_not_supported_in_browser=Váš prehliadač nepodporuje HTML5 tag 'video'. video_not_supported_in_browser=Váš prehliadač nepodporuje HTML5 tag 'video'.
audio_not_supported_in_browser=Váš prehliadač nepodporuje HTML5 tag 'audio'. audio_not_supported_in_browser=Váš prehliadač nepodporuje HTML5 tag 'audio'.
stored_lfs=Uložené pomocou Git LFS stored_lfs=Uložené pomocou Git LFS
stored_annex=Uložené pomocou Git Annex
symbolic_link=Symbolický odkaz symbolic_link=Symbolický odkaz
commit_graph=Graf commitov commit_graph=Graf commitov
line=riadok line=riadok

View file

@ -910,7 +910,6 @@ file_too_large=Filen är för stor för att visas.
video_not_supported_in_browser=Din webbläsare stödjer ej HTML5-taggen "video". video_not_supported_in_browser=Din webbläsare stödjer ej HTML5-taggen "video".
audio_not_supported_in_browser=Din webbläsare stödjer ej HTML5-taggen "audio". audio_not_supported_in_browser=Din webbläsare stödjer ej HTML5-taggen "audio".
stored_lfs=Sparad med Git LFS stored_lfs=Sparad med Git LFS
stored_annex=Sparad med Git Annex
symbolic_link=Symbolisk länk symbolic_link=Symbolisk länk
commit_graph=Commitgraf commit_graph=Commitgraf
commit_graph.monochrome=Mono commit_graph.monochrome=Mono
@ -924,7 +923,6 @@ editor.upload_file=Ladda upp fil
editor.edit_file=Redigera fil editor.edit_file=Redigera fil
editor.preview_changes=Förhandsgranska ändringar editor.preview_changes=Förhandsgranska ändringar
editor.cannot_edit_lfs_files=LFS-filer kan inte redigeras i webbgränssnittet. editor.cannot_edit_lfs_files=LFS-filer kan inte redigeras i webbgränssnittet.
editor.cannot_edit_annex_files=Annex-filer kan inte redigeras i webbgränssnittet.
editor.cannot_edit_non_text_files=Binära filer kan inte redigeras genom webbgränssnittet. editor.cannot_edit_non_text_files=Binära filer kan inte redigeras genom webbgränssnittet.
editor.edit_this_file=Redigera fil editor.edit_this_file=Redigera fil
editor.this_file_locked=Filen är låst editor.this_file_locked=Filen är låst

View file

@ -1290,7 +1290,6 @@ view_git_blame=Git Suç Görüntüle
video_not_supported_in_browser=Tarayıcınız HTML5 'video' etiketini desteklemiyor. video_not_supported_in_browser=Tarayıcınız HTML5 'video' etiketini desteklemiyor.
audio_not_supported_in_browser=Tarayıcınız HTML5 'audio' etiketini desteklemiyor. audio_not_supported_in_browser=Tarayıcınız HTML5 'audio' etiketini desteklemiyor.
stored_lfs=Git LFS ile depolandı stored_lfs=Git LFS ile depolandı
stored_annex=Git Annex ile depolandı
symbolic_link=Sembolik Bağlantı symbolic_link=Sembolik Bağlantı
executable_file=Çalıştırılabilir Dosya executable_file=Çalıştırılabilir Dosya
commit_graph=İşleme Grafiği commit_graph=İşleme Grafiği
@ -1314,7 +1313,6 @@ editor.upload_file=Dosya Yükle
editor.edit_file=Dosyayı Düzenle editor.edit_file=Dosyayı Düzenle
editor.preview_changes=Değişiklikleri Önizle editor.preview_changes=Değişiklikleri Önizle
editor.cannot_edit_lfs_files=LFS dosyaları web arayüzünde düzenlenemez. editor.cannot_edit_lfs_files=LFS dosyaları web arayüzünde düzenlenemez.
editor.cannot_edit_annex_files=Annex dosyaları web arayüzünde düzenlenemez.
editor.cannot_edit_non_text_files=Bu tür dosyalar web arayüzünden düzenlenemez. editor.cannot_edit_non_text_files=Bu tür dosyalar web arayüzünden düzenlenemez.
editor.edit_this_file=Dosyayı Düzenle editor.edit_this_file=Dosyayı Düzenle
editor.this_file_locked=Dosya kilitlendi editor.this_file_locked=Dosya kilitlendi

View file

@ -1243,7 +1243,6 @@ file_copy_permalink=Копіювати постійне посилання
video_not_supported_in_browser=Ваш браузер не підтримує тег HTML5 «video». video_not_supported_in_browser=Ваш браузер не підтримує тег HTML5 «video».
audio_not_supported_in_browser=Ваш браузер не підтримує тег HTML5 «audio». audio_not_supported_in_browser=Ваш браузер не підтримує тег HTML5 «audio».
stored_lfs=Збережено з Git LFS stored_lfs=Збережено з Git LFS
stored_annex=Збережено з Git Annex
symbolic_link=Символічне посилання symbolic_link=Символічне посилання
commit_graph=Графік комітів commit_graph=Графік комітів
commit_graph.select=Виберіть гілки commit_graph.select=Виберіть гілки
@ -1261,7 +1260,6 @@ editor.upload_file=Завантажити файл
editor.edit_file=Редагувати файл editor.edit_file=Редагувати файл
editor.preview_changes=Попередній перегляд змін editor.preview_changes=Попередній перегляд змін
editor.cannot_edit_lfs_files=Файли LFS не можна редагувати в веб-інтерфейсі. editor.cannot_edit_lfs_files=Файли LFS не можна редагувати в веб-інтерфейсі.
editor.cannot_edit_annex_files=Файли Annex не можна редагувати в веб-інтерфейсі.
editor.cannot_edit_non_text_files=Бінарні файли не можливо редагувати у веб-інтерфейсі. editor.cannot_edit_non_text_files=Бінарні файли не можливо редагувати у веб-інтерфейсі.
editor.edit_this_file=Редагувати файл editor.edit_this_file=Редагувати файл
editor.this_file_locked=Файл заблоковано editor.this_file_locked=Файл заблоковано

View file

@ -1319,7 +1319,6 @@ view_git_blame=查看 Git Blame
video_not_supported_in_browser=您的浏览器不支持 HTML5 “video” 标签。 video_not_supported_in_browser=您的浏览器不支持 HTML5 “video” 标签。
audio_not_supported_in_browser=您的浏览器不支持 HTML5 “audio” 标签。 audio_not_supported_in_browser=您的浏览器不支持 HTML5 “audio” 标签。
stored_lfs=存储到Git LFS stored_lfs=存储到Git LFS
stored_annex=存储到Git Annex
symbolic_link=符号链接 symbolic_link=符号链接
executable_file=可执行文件 executable_file=可执行文件
vendored = Vendored vendored = Vendored
@ -1345,7 +1344,6 @@ editor.upload_file=上传文件
editor.edit_file=编辑文件 editor.edit_file=编辑文件
editor.preview_changes=预览变更 editor.preview_changes=预览变更
editor.cannot_edit_lfs_files=无法在 web 界面中编辑 lfs 文件。 editor.cannot_edit_lfs_files=无法在 web 界面中编辑 lfs 文件。
editor.cannot_edit_annex_files=无法在 web 界面中编辑 lfs 文件。
editor.cannot_edit_non_text_files=网页不能编辑二进制文件。 editor.cannot_edit_non_text_files=网页不能编辑二进制文件。
editor.edit_this_file=编辑文件 editor.edit_this_file=编辑文件
editor.this_file_locked=文件已锁定 editor.this_file_locked=文件已锁定

View file

@ -472,7 +472,6 @@ file_view_raw=查看原始文件
file_permalink=永久連結 file_permalink=永久連結
stored_lfs=儲存到到 Git LFS stored_lfs=儲存到到 Git LFS
stored_annex=儲存到到 Git Annex
editor.preview_changes=預覽更改 editor.preview_changes=預覽更改
editor.or= editor.or=

View file

@ -1293,7 +1293,6 @@ view_git_blame=檢視 Git Blame
video_not_supported_in_browser=您的瀏覽器不支援 HTML5 的「video」標籤。 video_not_supported_in_browser=您的瀏覽器不支援 HTML5 的「video」標籤。
audio_not_supported_in_browser=您的瀏覽器不支援 HTML5 的「audio」標籤。 audio_not_supported_in_browser=您的瀏覽器不支援 HTML5 的「audio」標籤。
stored_lfs=已使用 Git LFS 儲存 stored_lfs=已使用 Git LFS 儲存
stored_annex=已使用 Git Annex 儲存
symbolic_link=符號連結 symbolic_link=符號連結
commit_graph=提交線圖 commit_graph=提交線圖
commit_graph.select=選擇分支 commit_graph.select=選擇分支
@ -1313,7 +1312,6 @@ editor.upload_file=上傳檔案
editor.edit_file=編輯檔案 editor.edit_file=編輯檔案
editor.preview_changes=預覽變更 editor.preview_changes=預覽變更
editor.cannot_edit_lfs_files=無法在 web 介面中編輯 LFS 檔。 editor.cannot_edit_lfs_files=無法在 web 介面中編輯 LFS 檔。
editor.cannot_edit_annex_files=無法在 web 介面中編輯 Annex 檔。
editor.cannot_edit_non_text_files=網站介面不能編輯二進位檔案。 editor.cannot_edit_non_text_files=網站介面不能編輯二進位檔案。
editor.edit_this_file=編輯檔案 editor.edit_this_file=編輯檔案
editor.this_file_locked=檔案已被鎖定 editor.this_file_locked=檔案已被鎖定

2
package-lock.json generated
View file

@ -1,5 +1,5 @@
{ {
"name": "forgejo-aneksajo", "name": "forgejo",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {

View file

@ -11,7 +11,6 @@ import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
asymkey_model "code.gitea.io/gitea/models/asymkey" asymkey_model "code.gitea.io/gitea/models/asymkey"
authmodel "code.gitea.io/gitea/models/auth" authmodel "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/modules/annex"
"code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/eventsource" "code.gitea.io/gitea/modules/eventsource"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
@ -168,8 +167,6 @@ func InitWebInstalled(ctx context.Context) {
actions_service.Init() actions_service.Init()
mustInit(annex.Init)
// Finally start up the cron // Finally start up the cron
cron.NewContext(ctx) cron.NewContext(ctx)
} }

View file

@ -81,14 +81,12 @@ func ServCommand(ctx *context.PrivateContext) {
ownerName := ctx.Params(":owner") ownerName := ctx.Params(":owner")
repoName := ctx.Params(":repo") repoName := ctx.Params(":repo")
mode := perm.AccessMode(ctx.FormInt("mode")) mode := perm.AccessMode(ctx.FormInt("mode"))
verbs := ctx.FormStrings("verb")
// Set the basic parts of the results to return // Set the basic parts of the results to return
results := private.ServCommandResults{ results := private.ServCommandResults{
RepoName: repoName, RepoName: repoName,
OwnerName: ownerName, OwnerName: ownerName,
KeyID: keyID, KeyID: keyID,
UserMode: perm.AccessModeNone,
} }
// Now because we're not translating things properly let's just default some English strings here // Now because we're not translating things properly let's just default some English strings here
@ -289,10 +287,8 @@ func ServCommand(ctx *context.PrivateContext) {
repo.IsPrivate || repo.IsPrivate ||
owner.Visibility.IsPrivate() || owner.Visibility.IsPrivate() ||
(user != nil && user.IsRestricted) || // user will be nil if the key is a deploykey (user != nil && user.IsRestricted) || // user will be nil if the key is a deploykey
(setting.Annex.Enabled && len(verbs) > 0 && verbs[0] == "git-annex-shell") || // git-annex has its own permission enforcement, for which we expose results.UserMode
setting.Service.RequireSignInView) { setting.Service.RequireSignInView) {
if key.Type == asymkey_model.KeyTypeDeploy { if key.Type == asymkey_model.KeyTypeDeploy {
results.UserMode = deployKey.Mode
if deployKey.Mode < mode { if deployKey.Mode < mode {
ctx.JSON(http.StatusUnauthorized, private.Response{ ctx.JSON(http.StatusUnauthorized, private.Response{
UserMsg: fmt.Sprintf("Deploy Key: %d:%s is not authorized to %s %s/%s.", key.ID, key.Name, modeString, results.OwnerName, results.RepoName), UserMsg: fmt.Sprintf("Deploy Key: %d:%s is not authorized to %s %s/%s.", key.ID, key.Name, modeString, results.OwnerName, results.RepoName),
@ -314,9 +310,9 @@ func ServCommand(ctx *context.PrivateContext) {
return return
} }
results.UserMode = perm.UnitAccessMode(unitType) userMode := perm.UnitAccessMode(unitType)
if results.UserMode < mode { if userMode < mode {
log.Warn("Failed authentication attempt for %s with key %s (not authorized to %s %s/%s) from %s", user.Name, key.Name, modeString, ownerName, repoName, ctx.RemoteAddr()) log.Warn("Failed authentication attempt for %s with key %s (not authorized to %s %s/%s) from %s", user.Name, key.Name, modeString, ownerName, repoName, ctx.RemoteAddr())
ctx.JSON(http.StatusUnauthorized, private.Response{ ctx.JSON(http.StatusUnauthorized, private.Response{
UserMsg: fmt.Sprintf("User: %d:%s with Key: %d:%s is not authorized to %s %s/%s.", user.ID, user.Name, key.ID, key.Name, modeString, ownerName, repoName), UserMsg: fmt.Sprintf("User: %d:%s with Key: %d:%s is not authorized to %s %s/%s.", user.ID, user.Name, key.ID, key.Name, modeString, ownerName, repoName),
@ -357,7 +353,6 @@ func ServCommand(ctx *context.PrivateContext) {
}) })
return return
} }
results.UserMode = perm.AccessModeWrite
results.RepoID = repo.ID results.RepoID = repo.ID
} }
@ -386,14 +381,13 @@ func ServCommand(ctx *context.PrivateContext) {
return return
} }
} }
log.Debug("Serv Results:\nIsWiki: %t\nDeployKeyID: %d\nKeyID: %d\tKeyName: %s\nUserName: %s\nUserID: %d\nUserMode: %d\nOwnerName: %s\nRepoName: %s\nRepoID: %d", log.Debug("Serv Results:\nIsWiki: %t\nDeployKeyID: %d\nKeyID: %d\tKeyName: %s\nUserName: %s\nUserID: %d\nOwnerName: %s\nRepoName: %s\nRepoID: %d",
results.IsWiki, results.IsWiki,
results.DeployKeyID, results.DeployKeyID,
results.KeyID, results.KeyID,
results.KeyName, results.KeyName,
results.UserName, results.UserName,
results.UserID, results.UserID,
results.UserMode,
results.OwnerName, results.OwnerName,
results.RepoName, results.RepoName,
results.RepoID) results.RepoID)

View file

@ -170,8 +170,6 @@ func SignIn(ctx *context.Context) {
context.SetCaptchaData(ctx) context.SetCaptchaData(ctx)
} }
ctx.Data["SignInForgottenPasswordEnabled"] = setting.Service.SignInForgottenPasswordEnabled
ctx.HTML(http.StatusOK, tplSignIn) ctx.HTML(http.StatusOK, tplSignIn)
} }
@ -191,7 +189,6 @@ func SignInPost(ctx *context.Context) {
ctx.Data["PageIsLogin"] = true ctx.Data["PageIsLogin"] = true
ctx.Data["EnableSSPI"] = auth.IsSSPIEnabled(ctx) ctx.Data["EnableSSPI"] = auth.IsSSPIEnabled(ctx)
ctx.Data["EnableInternalSignIn"] = setting.Service.EnableInternalSignIn ctx.Data["EnableInternalSignIn"] = setting.Service.EnableInternalSignIn
ctx.Data["SignInForgottenPasswordEnabled"] = setting.Service.SignInForgottenPasswordEnabled
// Permission denied if EnableInternalSignIn is false // Permission denied if EnableInternalSignIn is false
if !setting.Service.EnableInternalSignIn { if !setting.Service.EnableInternalSignIn {

View file

@ -59,8 +59,6 @@ func Home(ctx *context.Context) {
return return
} }
ctx.Data["LandingPageInfoEnabled"] = setting.Service.LandingPageInfoEnabled
ctx.Data["PageIsHome"] = true ctx.Data["PageIsHome"] = true
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
ctx.HTML(http.StatusOK, tplHome) ctx.HTML(http.StatusOK, tplHome)

View file

@ -1,146 +0,0 @@
package repo
import (
"context"
"net"
"net/http"
"net/http/httputil"
"net/url"
"os"
"os/exec"
"strings"
"syscall"
"time"
"code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/annex"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
services_context "code.gitea.io/gitea/services/context"
)
type p2phttpRecordType struct {
CancelFunc func()
LastUsed time.Time
Port string
}
var p2phttpRecords = make(map[string]*p2phttpRecordType)
// AnnexP2PHTTP implements git-annex smart HTTP support by delegating to git annex p2phttp
func AnnexP2PHTTP(ctx *services_context.Context) {
uuid := ctx.Params(":uuid")
repoPath, err := annex.UUID2RepoPath(uuid)
if err != nil {
ctx.PlainText(http.StatusNotFound, "Repository not found")
return
}
parts := strings.Split(repoPath, "/")
repoName := strings.TrimSuffix(parts[len(parts)-1], ".git")
owner := parts[len(parts)-2]
repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, owner, repoName)
if err != nil {
ctx.PlainText(http.StatusNotFound, "Repository not found")
return
}
p, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
return
}
if !(ctx.Req.Method == "GET" && p.CanAccess(perm.AccessModeRead, unit.TypeCode) ||
ctx.Req.Method == "POST" && p.CanAccess(perm.AccessModeWrite, unit.TypeCode) ||
ctx.Req.Method == "POST" && strings.HasSuffix(ctx.Req.URL.Path, "/checkpresent") && p.CanAccess(perm.AccessModeRead, unit.TypeCode) ||
ctx.Req.Method == "POST" && strings.HasSuffix(ctx.Req.URL.Path, "/keeplocked") ||
ctx.Req.Method == "POST" && strings.HasSuffix(ctx.Req.URL.Path, "/lockcontent")) {
// GET requests require at least read access; POST requests for
// anything but checkpresent, lockcontent, and keeplocked
// require write permissions; POST requests for checkpresent
// only require read permissions, as it really is just a read.
// POST requests for lockcontent and keeplocked require no
// authentication at all, as is also the case for the
// authentication in the git-annex-p2phttp server. See
// https://git-annex.branchable.com/bugs/p2phttp__58___drop_difference_wideopen_unauth-readonly/
// for reasoning.
ctx.Resp.WriteHeader(http.StatusUnauthorized)
return
}
p2phttpRecord, p2phttpProcessExists := p2phttpRecords[uuid]
if p2phttpProcessExists {
p2phttpRecord.LastUsed = time.Now()
} else {
// Start a new p2phttp process for the requested repository
// There is a race condition here with the port selection, ideally git annex p2phttp could just listen on a unix socket...
lis, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
log.Error("Failed to listen on a free port: %v", err)
ctx.Resp.WriteHeader(http.StatusInternalServerError)
return
}
hopefullyFreePort := strings.SplitN(lis.Addr().String(), ":", 2)[1]
lis.Close()
p2phttpCtx, p2phttpCtxCancel := context.WithCancel(context.Background())
go func(ctx context.Context) {
cmd := exec.CommandContext(ctx, "git", "-C", repoPath, "annex", "p2phttp", "-J2", "--bind", "127.0.0.1", "--wideopen", "--port", hopefullyFreePort)
cmd.SysProcAttr = &syscall.SysProcAttr{
Pdeathsig: syscall.SIGINT,
}
cmd.Cancel = func() error { return cmd.Process.Signal(os.Interrupt) }
_ = cmd.Run()
}(p2phttpCtx)
graceful.GetManager().RunAtTerminate(p2phttpCtxCancel)
// Wait for the p2phttp server to get ready
start := time.Now()
sleepDuration := 1 * time.Millisecond
for {
if time.Since(start) > 5*time.Second {
p2phttpCtxCancel()
log.Error("Failed to start the p2phttp server in a reasonable amount of time")
ctx.Resp.WriteHeader(http.StatusInternalServerError)
return
}
conn, err := net.Dial("tcp", "127.0.0.1:"+hopefullyFreePort)
if err == nil {
conn.Close()
break
}
time.Sleep(sleepDuration)
sleepDuration *= 2
if sleepDuration > 1*time.Second {
sleepDuration = 1 * time.Second
}
}
p2phttpRecord = &p2phttpRecordType{CancelFunc: p2phttpCtxCancel, LastUsed: time.Now(), Port: hopefullyFreePort}
p2phttpRecords[uuid] = p2phttpRecord
}
// Cleanup p2phttp processes that haven't been used for a while
for uuid, record := range p2phttpRecords {
if time.Since(record.LastUsed) > 5*time.Minute {
record.CancelFunc()
delete(p2phttpRecords, uuid)
}
}
url, err := url.Parse("http://127.0.0.1:" + p2phttpRecord.Port + strings.TrimPrefix(ctx.Req.RequestURI, "/git-annex-p2phttp"))
if err != nil {
log.Error("Failed to parse URL: %v", err)
ctx.Resp.WriteHeader(http.StatusInternalServerError)
return
}
proxy := httputil.ReverseProxy{
Rewrite: func(r *httputil.ProxyRequest) {
r.Out.URL = url
},
}
proxy.ServeHTTP(ctx.Resp, ctx.Req)
}

View file

@ -23,7 +23,6 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/annex"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/charset"
csv_module "code.gitea.io/gitea/modules/csv" csv_module "code.gitea.io/gitea/modules/csv"
@ -73,21 +72,7 @@ func setCompareContext(ctx *context.Context, before, head *git.Commit, headOwner
return st return st
} }
isAnnexed, err := annex.IsAnnexed(blob) st, err := blob.GuessContentType()
if err != nil {
log.Error("IsAnnexed failed: %v", err)
return st
}
if isAnnexed {
st, err = annex.GuessContentType(blob)
if err != nil {
log.Error("GuessContentType failed: %v", err)
return st
}
return st
}
st, err = blob.GuessContentType()
if err != nil { if err != nil {
log.Error("GuessContentType failed: %v", err) log.Error("GuessContentType failed: %v", err)
return st return st
@ -105,18 +90,18 @@ func SourceCommitURL(owner, name string, commit *git.Commit) string {
return setting.AppSubURL + "/" + url.PathEscape(owner) + "/" + url.PathEscape(name) + "/src/commit/" + url.PathEscape(commit.ID.String()) return setting.AppSubURL + "/" + url.PathEscape(owner) + "/" + url.PathEscape(name) + "/src/commit/" + url.PathEscape(commit.ID.String())
} }
// MediaCommitURL creates a relative URL for the commit media (plain git, LFS, or annex content) in the given repository // RawCommitURL creates a relative URL for the raw commit in the given repository
func MediaCommitURL(owner, name string, commit *git.Commit) string { func RawCommitURL(owner, name string, commit *git.Commit) string {
return setting.AppSubURL + "/" + url.PathEscape(owner) + "/" + url.PathEscape(name) + "/media/commit/" + url.PathEscape(commit.ID.String()) return setting.AppSubURL + "/" + url.PathEscape(owner) + "/" + url.PathEscape(name) + "/raw/commit/" + url.PathEscape(commit.ID.String())
} }
// setPathsCompareContext sets context data for source and raw paths // setPathsCompareContext sets context data for source and raw paths
func setPathsCompareContext(ctx *context.Context, base, head *git.Commit, headOwner, headName string) { func setPathsCompareContext(ctx *context.Context, base, head *git.Commit, headOwner, headName string) {
ctx.Data["SourcePath"] = SourceCommitURL(headOwner, headName, head) ctx.Data["SourcePath"] = SourceCommitURL(headOwner, headName, head)
ctx.Data["RawPath"] = MediaCommitURL(headOwner, headName, head) ctx.Data["RawPath"] = RawCommitURL(headOwner, headName, head)
if base != nil { if base != nil {
ctx.Data["BeforeSourcePath"] = SourceCommitURL(headOwner, headName, base) ctx.Data["BeforeSourcePath"] = SourceCommitURL(headOwner, headName, base)
ctx.Data["BeforeRawPath"] = MediaCommitURL(headOwner, headName, base) ctx.Data["BeforeRawPath"] = RawCommitURL(headOwner, headName, base)
} }
} }

View file

@ -9,7 +9,6 @@ import (
"time" "time"
git_model "code.gitea.io/gitea/models/git" git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/modules/annex"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/httpcache" "code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/lfs"
@ -80,26 +79,6 @@ func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob, lastModified *time.Tim
} }
closed = true closed = true
// check for git-annex files
// (this code is weirdly redundant because I'm trying not to delete any lines in order to make merges easier)
isAnnexed, err := annex.IsAnnexed(blob)
if err != nil {
ctx.ServerError("annex.IsAnnexed", err)
return err
}
if isAnnexed {
content, err := annex.Content(blob)
if err != nil {
// XXX are there any other possible failure cases here?
// there are, there could be unrelated io errors; those should be ctx.ServerError()s
ctx.NotFound("annex.Content", err)
return err
}
defer content.Close()
common.ServeContentByReadSeeker(ctx.Base, ctx.Repo.TreePath, lastModified, content)
return nil
}
return common.ServeBlob(ctx.Base, ctx.Repo.TreePath, blob, lastModified) return common.ServeBlob(ctx.Base, ctx.Repo.TreePath, blob, lastModified)
} }

View file

@ -489,23 +489,8 @@ func DeleteFile(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplDeleteFile) ctx.HTML(http.StatusOK, tplDeleteFile)
} }
// DeletePath render delete file page
func DeletePath(ctx *context.Context) {
DeleteFile(ctx)
}
// DeleteFilePost response for deleting file // DeleteFilePost response for deleting file
func DeleteFilePost(ctx *context.Context) { func DeleteFilePost(ctx *context.Context) {
DeletePathOrFilePost(ctx, false)
}
// DeletePathPost response for deleting path
func DeletePathPost(ctx *context.Context) {
DeletePathOrFilePost(ctx, true)
}
// DeletePathOrFilePost response for deleting path or file
func DeletePathOrFilePost(ctx *context.Context, isdir bool) {
form := web.GetForm(ctx).(*forms.DeleteRepoFileForm) form := web.GetForm(ctx).(*forms.DeleteRepoFileForm)
canCommit := renderCommitRights(ctx) canCommit := renderCommitRights(ctx)
branchName := ctx.Repo.BranchName branchName := ctx.Repo.BranchName
@ -558,7 +543,6 @@ func DeletePathOrFilePost(ctx *context.Context, isdir bool) {
TreePath: ctx.Repo.TreePath, TreePath: ctx.Repo.TreePath,
}, },
}, },
IsDir: isdir, // Add this flag to indicate directory deletion
Message: message, Message: message,
Signoff: form.Signoff, Signoff: form.Signoff,
Author: gitIdentity, Author: gitIdentity,
@ -774,7 +758,6 @@ func UploadFilePost(ctx *context.Context) {
TreePath: form.TreePath, TreePath: form.TreePath,
Message: message, Message: message,
Files: form.Files, Files: form.Files,
FullPaths: form.FullPaths,
Signoff: form.Signoff, Signoff: form.Signoff,
Author: gitIdentity, Author: gitIdentity,
Committer: gitIdentity, Committer: gitIdentity,

View file

@ -10,7 +10,6 @@ import (
gocontext "context" gocontext "context"
"fmt" "fmt"
"net/http" "net/http"
"net/url"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
@ -79,24 +78,7 @@ func httpBase(ctx *context.Context) *serviceHandler {
strings.HasSuffix(ctx.Req.URL.Path, "git-upload-archive") { strings.HasSuffix(ctx.Req.URL.Path, "git-upload-archive") {
isPull = true isPull = true
} else { } else {
// In addition to GET requests, HEAD requests are also "pull" isPull = ctx.Req.Method == "GET"
// operations (reads), so they should also not require
// authentication. This is necessary for git-annex to operate
// properly, as it emits HEAD requests to check for the
// existence of keys, e.g. before dropping locally, and asking
// for authentication would break unauthenticated http usage in
// this situation.
// It should be safe to make all HEAD requests require no
// authentication, but as it is only necessary for the
// annex/objects endpoints to fix git-annex' drop operations it
// is limited to those for now.
r, err := regexp.Compile("^/?" + username + "/" + reponame + "(.git)?/annex/objects")
if err != nil {
ctx.ServerError("failed to create URL path regex", err)
return nil
}
isPull = ctx.Req.Method == "GET" ||
r.MatchString(ctx.Req.URL.Path) && ctx.Req.Method == "HEAD"
} }
var accessMode perm.AccessMode var accessMode perm.AccessMode
@ -563,42 +545,6 @@ func GetInfoRefs(ctx *context.Context) {
} }
} }
// GetConfig implements fetching the git config of a repository
func GetConfig(ctx *context.Context) {
h := httpBase(ctx)
if h != nil {
setHeaderNoCache(ctx)
config, err := os.ReadFile(filepath.Join(h.getRepoDir(), "config"))
if err != nil {
log.Error("Failed to read git config file: %v", err)
ctx.Resp.WriteHeader(http.StatusInternalServerError)
return
}
if !setting.Annex.DisableP2PHTTP {
appURL, err := url.Parse(setting.AppURL)
if err != nil {
log.Error("Could not parse 'setting.AppURL': %v", err)
ctx.Resp.WriteHeader(http.StatusInternalServerError)
return
}
if appURL.Port() == "" {
// If there is no port set then set the http(s) default ports.
// Without this, git-annex would try its own default port (9417) and fail.
if appURL.Scheme == "http" {
appURL.Host += ":80"
}
if appURL.Scheme == "https" {
appURL.Host += ":443"
}
}
config = append(config, []byte("[annex]\n\turl = annex+"+appURL.String()+"git-annex-p2phttp\n")...)
}
ctx.Resp.Header().Set("Content-Type", "text/plain")
ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", len(config)))
http.ServeContent(ctx.Resp, ctx.Req, "config", time.Now(), bytes.NewReader(config))
}
}
// GetTextFile implements Git dumb HTTP // GetTextFile implements Git dumb HTTP
func GetTextFile(p string) func(*context.Context) { func GetTextFile(p string) func(*context.Context) {
return func(ctx *context.Context) { return func(ctx *context.Context) {
@ -651,34 +597,3 @@ func GetIdxFile(ctx *context.Context) {
h.sendFile(ctx, "application/x-git-packed-objects-toc", "objects/pack/pack-"+ctx.Params("file")+".idx") h.sendFile(ctx, "application/x-git-packed-objects-toc", "objects/pack/pack-"+ctx.Params("file")+".idx")
} }
} }
// GetAnnexObject implements git-annex dumb HTTP
func GetAnnexObject(ctx *context.Context) {
h := httpBase(ctx)
if h != nil {
// git-annex objects are stored in .git/annex/objects/{hash1}/{hash2}/{key}/{key}
// where key is a string containing the size and (usually SHA256) checksum of the file,
// and hash1+hash2 are the first few bits of the md5sum of key itself.
// ({hash1}/{hash2}/ is just there to avoid putting too many files in one directory)
// ref: https://git-annex.branchable.com/internals/hashing/
// keyDir should = key, but we don't enforce that
object := filepath.Join(ctx.Params("hash1"), ctx.Params("hash2"), ctx.Params("keyDir"), ctx.Params("key"))
// Sanitize the input against directory traversals.
//
// This works because at the filesystem root, "/.." = "/";
// So if a path starts rooted ("/"), path.Clean(), which
// path.Join() calls internally, removes all '..' prefixes.
// After, this unroots the path unconditionally ([1:]), which
// works because we know the input is never supposed to be rooted.
//
// The router code probably also disallows "..", so this
// should be redundant, but it's defensive to keep it
// whenever touching filesystem paths with user input.
object = filepath.Join(string(filepath.Separator), object)[1:]
setHeaderCacheForever(ctx)
h.sendFile(ctx, "application/octet-stream", "annex/objects/"+object)
}
}

View file

@ -34,7 +34,6 @@ import (
unit_model "code.gitea.io/gitea/models/unit" unit_model "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/actions" "code.gitea.io/gitea/modules/actions"
"code.gitea.io/gitea/modules/annex"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
@ -210,59 +209,14 @@ func localizedExtensions(ext, languageCode string) (localizedExts []string) {
} }
type fileInfo struct { type fileInfo struct {
isTextFile bool isTextFile bool
isLFSFile bool isLFSFile bool
isAnnexFile bool fileSize int64
isAnnexFilePresent bool lfsMeta *lfs.Pointer
fileSize int64 st typesniffer.SniffedType
lfsMeta *lfs.Pointer
st typesniffer.SniffedType
} }
func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) ([]byte, io.ReadCloser, *fileInfo, error) { func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) ([]byte, io.ReadCloser, *fileInfo, error) {
isAnnexed, err := annex.IsAnnexed(blob)
if err != nil {
return nil, nil, nil, err
}
if isAnnexed {
// TODO: this code could be merged with the LFS case, especially the redundant type sniffer,
// but it is *currently* written this way to make merging with the non-annex upstream easier:
// this way, the git-annex patch is (mostly) pure additions.
annexContent, err := annex.Content(blob)
if err != nil {
// If annex.Content returns an error it can mean that the blob does not
// refer to an annexed file or that it is not present here. Since we already
// checked that it is annexed the latter must be the case. So we return the
// content of the blob instead and indicate that the file is indeed annexed,
// but not present here. The template can then communicate the situation.
dataRc, err := blob.DataAsync()
if err != nil {
return nil, nil, nil, err
}
buf := make([]byte, 1024)
n, _ := util.ReadAtMost(dataRc, buf)
buf = buf[:n]
st := typesniffer.DetectContentType(buf)
return buf, dataRc, &fileInfo{st.IsText(), false, true, false, blob.Size(), nil, st}, nil
}
stat, err := annexContent.Stat()
if err != nil {
return nil, nil, nil, err
}
buf := make([]byte, 1024)
n, _ := util.ReadAtMost(annexContent, buf)
buf = buf[:n]
st := typesniffer.DetectContentType(buf)
return buf, annexContent, &fileInfo{st.IsText(), false, true, true, stat.Size(), nil, st}, nil
}
dataRc, err := blob.DataAsync() dataRc, err := blob.DataAsync()
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
@ -277,18 +231,18 @@ func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) ([]byte,
// FIXME: what happens when README file is an image? // FIXME: what happens when README file is an image?
if !isTextFile || !setting.LFS.StartServer { if !isTextFile || !setting.LFS.StartServer {
return buf, dataRc, &fileInfo{isTextFile, false, false, false, blob.Size(), nil, st}, nil return buf, dataRc, &fileInfo{isTextFile, false, blob.Size(), nil, st}, nil
} }
pointer, _ := lfs.ReadPointerFromBuffer(buf) pointer, _ := lfs.ReadPointerFromBuffer(buf)
if !pointer.IsValid() { // fallback to plain file if !pointer.IsValid() { // fallback to plain file
return buf, dataRc, &fileInfo{isTextFile, false, false, false, blob.Size(), nil, st}, nil return buf, dataRc, &fileInfo{isTextFile, false, blob.Size(), nil, st}, nil
} }
meta, err := git_model.GetLFSMetaObjectByOid(ctx, repoID, pointer.Oid) meta, err := git_model.GetLFSMetaObjectByOid(ctx, repoID, pointer.Oid)
if err != nil { // fallback to plain file if err != nil { // fallback to plain file
log.Warn("Unable to access LFS pointer %s in repo %d: %v", pointer.Oid, repoID, err) log.Warn("Unable to access LFS pointer %s in repo %d: %v", pointer.Oid, repoID, err)
return buf, dataRc, &fileInfo{isTextFile, false, false, false, blob.Size(), nil, st}, nil return buf, dataRc, &fileInfo{isTextFile, false, blob.Size(), nil, st}, nil
} }
dataRc.Close() dataRc.Close()
@ -308,7 +262,7 @@ func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) ([]byte,
st = typesniffer.DetectContentType(buf) st = typesniffer.DetectContentType(buf)
return buf, dataRc, &fileInfo{st.IsText(), true, false, false, meta.Size, &meta.Pointer, st}, nil return buf, dataRc, &fileInfo{st.IsText(), true, meta.Size, &meta.Pointer, st}, nil
} }
func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.TreeEntry) { func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.TreeEntry) {
@ -371,7 +325,6 @@ func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.Tr
}, },
Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx), Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx),
GitRepo: ctx.Repo.GitRepo, GitRepo: ctx.Repo.GitRepo,
Blob: target.Blob(),
}, rd) }, rd)
if err != nil { if err != nil {
log.Error("Render failed for %s in %-v: %v Falling back to rendering source", readmeFile.Name(), ctx.Repo.Repository, err) log.Error("Render failed for %s in %-v: %v Falling back to rendering source", readmeFile.Name(), ctx.Repo.Repository, err)
@ -494,17 +447,10 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) {
isDisplayingSource := ctx.FormString("display") == "source" isDisplayingSource := ctx.FormString("display") == "source"
isDisplayingRendered := !isDisplayingSource isDisplayingRendered := !isDisplayingSource
if fInfo.isLFSFile || fInfo.isAnnexFile { if fInfo.isLFSFile {
ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/media/" + ctx.Repo.BranchNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/media/" + ctx.Repo.BranchNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
} }
if fInfo.isAnnexFile {
// pre-git-annex v7, all annexed files were represented in-repo as symlinks;
// but we pretend they aren't, since that's a distracting quirk of git-annex
// and not a meaningful choice on the user's part
ctx.Data["FileIsSymlink"] = false
}
isRepresentableAsText := fInfo.st.IsRepresentableAsText() isRepresentableAsText := fInfo.st.IsRepresentableAsText()
if !isRepresentableAsText { if !isRepresentableAsText {
// If we can't show plain text, always try to render. // If we can't show plain text, always try to render.
@ -512,8 +458,6 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) {
isDisplayingRendered = true isDisplayingRendered = true
} }
ctx.Data["IsLFSFile"] = fInfo.isLFSFile ctx.Data["IsLFSFile"] = fInfo.isLFSFile
ctx.Data["IsAnnexFile"] = fInfo.isAnnexFile
ctx.Data["IsAnnexFilePresent"] = fInfo.isAnnexFilePresent
ctx.Data["FileSize"] = fInfo.fileSize ctx.Data["FileSize"] = fInfo.fileSize
ctx.Data["IsTextFile"] = fInfo.isTextFile ctx.Data["IsTextFile"] = fInfo.isTextFile
ctx.Data["IsRepresentableAsText"] = isRepresentableAsText ctx.Data["IsRepresentableAsText"] = isRepresentableAsText
@ -548,8 +492,6 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) {
// Assume file is not editable first. // Assume file is not editable first.
if fInfo.isLFSFile { if fInfo.isLFSFile {
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_lfs_files") ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_lfs_files")
} else if fInfo.isAnnexFile {
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_annex_files")
} else if !isRepresentableAsText { } else if !isRepresentableAsText {
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_non_text_files") ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_non_text_files")
} }
@ -604,7 +546,6 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) {
}, },
Metas: metas, Metas: metas,
GitRepo: ctx.Repo.GitRepo, GitRepo: ctx.Repo.GitRepo,
Blob: entry.Blob(),
}, rd) }, rd)
if err != nil { if err != nil {
ctx.ServerError("Render", err) ctx.ServerError("Render", err)
@ -658,7 +599,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) {
ctx.Data["FileContent"] = fileContent ctx.Data["FileContent"] = fileContent
ctx.Data["LineEscapeStatus"] = statuses ctx.Data["LineEscapeStatus"] = statuses
} }
if !fInfo.isLFSFile && !fInfo.isAnnexFile { if !fInfo.isLFSFile {
if ctx.Repo.CanEnableEditor(ctx, ctx.Doer) { if ctx.Repo.CanEnableEditor(ctx, ctx.Doer) {
if lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID { if lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID {
ctx.Data["CanEditFile"] = false ctx.Data["CanEditFile"] = false
@ -703,7 +644,6 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) {
}, },
Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx), Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx),
GitRepo: ctx.Repo.GitRepo, GitRepo: ctx.Repo.GitRepo,
Blob: entry.Blob(),
}, rd) }, rd)
if err != nil { if err != nil {
ctx.ServerError("Render", err) ctx.ServerError("Render", err)
@ -1219,36 +1159,6 @@ PostRecentBranchCheck:
} else { } else {
ctx.Data["CodeSearchOptions"] = git.GrepSearchOptions ctx.Data["CodeSearchOptions"] = git.GrepSearchOptions
} }
lfsLock, err := git_model.GetTreePathLock(ctx, ctx.Repo.Repository.ID, ctx.Repo.TreePath)
if err != nil {
ctx.ServerError("git_model.GetTreePathLock", err)
return
}
if ctx.Repo.CanEnableEditor(ctx, ctx.Doer) {
if lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID {
ctx.Data["CanDeleteFile"] = false
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.this_file_locked")
} else {
ctx.Data["CanDeleteFile"] = true
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.delete_this_file")
}
} else if !ctx.Repo.IsViewBranch {
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch")
} else if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) {
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_have_write_access")
}
isAnnexFile, okAnnexFile := ctx.Data["IsAnnexFile"]
isAnnexFilePresent, okAnnexFilePresent := ctx.Data["IsAnnexFilePresent"]
if okAnnexFile && okAnnexFilePresent && isAnnexFile.(bool) && !isAnnexFilePresent.(bool) {
// If the file to be viewed is annexed but not present then render it normally
// (which will show the plain git blob content, i.e. the symlink or pointer target)
// but make the status code a 404.
ctx.HTML(http.StatusNotFound, tplRepoHome)
return
}
ctx.HTML(http.StatusOK, tplRepoHome) ctx.HTML(http.StatusOK, tplRepoHome)
} }

View file

@ -51,7 +51,6 @@ import (
_ "code.gitea.io/gitea/modules/session" // to registers all internal adapters _ "code.gitea.io/gitea/modules/session" // to registers all internal adapters
"code.forgejo.org/go-chi/binding"
"code.forgejo.org/go-chi/captcha" "code.forgejo.org/go-chi/captcha"
chi_middleware "github.com/go-chi/chi/v5/middleware" chi_middleware "github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors" "github.com/go-chi/cors"
@ -357,20 +356,6 @@ func registerRoutes(m *web.Route) {
} }
} }
annexEnabled := func(ctx *context.Context) {
if !setting.Annex.Enabled {
ctx.Error(http.StatusNotFound)
return
}
}
annexP2PHTTPEnabled := func(ctx *context.Context) {
if setting.Annex.DisableP2PHTTP {
ctx.Error(http.StatusNotFound)
return
}
}
federationEnabled := func(ctx *context.Context) { federationEnabled := func(ctx *context.Context) {
if !setting.Federation.Enabled { if !setting.Federation.Enabled {
ctx.Error(http.StatusNotFound) ctx.Error(http.StatusNotFound)
@ -970,9 +955,6 @@ func registerRoutes(m *web.Route) {
// ***** END: Organization ***** // ***** END: Organization *****
// ***** START: Repository ***** // ***** START: Repository *****
m.Group("", func() {
m.Methods("GET,POST", "/git-annex-p2phttp/git-annex/{uuid}/*", repo.AnnexP2PHTTP)
}, ignSignInAndCsrf, annexEnabled, annexP2PHTTPEnabled)
m.Group("/repo", func() { m.Group("/repo", func() {
m.Get("/create", repo.Create) m.Get("/create", repo.Create)
m.Post("/create", web.Bind(forms.CreateRepoForm{}), repo.CreatePost) m.Post("/create", web.Bind(forms.CreateRepoForm{}), repo.CreatePost)
@ -1269,13 +1251,11 @@ func registerRoutes(m *web.Route) {
m.Combo("/_new/*").Get(repo.NewFile). m.Combo("/_new/*").Get(repo.NewFile).
Post(web.Bind(forms.EditRepoFileForm{}), repo.NewFilePost) Post(web.Bind(forms.EditRepoFileForm{}), repo.NewFilePost)
m.Post("/_preview/*", web.Bind(forms.EditPreviewDiffForm{}), repo.DiffPreviewPost) m.Post("/_preview/*", web.Bind(forms.EditPreviewDiffForm{}), repo.DiffPreviewPost)
m.Combo("/_delete_path/*").Get(repo.DeletePath).
Post(web.Bind(forms.DeleteRepoFileForm{}), repo.DeletePathPost)
m.Combo("/_delete/*").Get(repo.DeleteFile). m.Combo("/_delete/*").Get(repo.DeleteFile).
Post(web.Bind(forms.DeleteRepoFileForm{}), repo.DeleteFilePost) Post(web.Bind(forms.DeleteRepoFileForm{}), repo.DeleteFilePost)
m.Combo("/_upload/*", repo.MustBeAbleToUpload). m.Combo("/_upload/*", repo.MustBeAbleToUpload).
Get(repo.UploadFile). Get(repo.UploadFile).
Post(BindUpload(forms.UploadRepoFileForm{}), repo.UploadFilePost) Post(web.Bind(forms.UploadRepoFileForm{}), repo.UploadFilePost)
m.Combo("/_diffpatch/*").Get(repo.NewDiffPatch). m.Combo("/_diffpatch/*").Get(repo.NewDiffPatch).
Post(web.Bind(forms.EditRepoFileForm{}), repo.NewDiffPatchPost) Post(web.Bind(forms.EditRepoFileForm{}), repo.NewDiffPatchPost)
m.Combo("/_cherrypick/{sha:([a-f0-9]{4,64})}/*").Get(repo.CherryPick). m.Combo("/_cherrypick/{sha:([a-f0-9]{4,64})}/*").Get(repo.CherryPick).
@ -1655,12 +1635,6 @@ func registerRoutes(m *web.Route) {
}) })
}, ignSignInAndCsrf, lfsServerEnabled) }, ignSignInAndCsrf, lfsServerEnabled)
m.Group("", func() {
// for git-annex
m.Methods("GET,OPTIONS", "/config", repo.GetConfig) // needed by clients reading annex.uuid during `git annex initremote`
m.Methods("GET,OPTIONS", "/annex/objects/{hash1}/{hash2}/{keyDir}/{key}", repo.GetAnnexObject)
}, ignSignInAndCsrf, annexEnabled, context.UserAssignmentWeb())
gitHTTPRouters(m) gitHTTPRouters(m)
}) })
}) })
@ -1697,20 +1671,3 @@ func registerRoutes(m *web.Route) {
ctx.NotFound("", nil) ctx.NotFound("", nil)
}) })
} }
func BindUpload(f forms.UploadRepoFileForm) http.HandlerFunc {
return func(resp http.ResponseWriter, req *http.Request) {
theObj := new(forms.UploadRepoFileForm) // create a new form obj for every request but not use obj directly
data := middleware.GetContextData(req.Context())
binding.Bind(req, theObj)
files := theObj.Files
var fullpaths []string
for _, fileID := range files {
fullPath := req.Form.Get("files_fullpath[" + fileID + "]")
fullpaths = append(fullpaths, fullPath)
}
theObj.FullPaths = fullpaths
data.GetData()["__form"] = theObj
middleware.AssignForm(theObj, data)
}
}

View file

@ -61,17 +61,6 @@ func isArchivePath(req *http.Request) bool {
return archivePathRe.MatchString(req.URL.Path) return archivePathRe.MatchString(req.URL.Path)
} }
var annexPathRe = regexp.MustCompile(`^(/git-annex-p2phttp/|/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/annex/)`)
func isAnnexPath(req *http.Request) bool {
if setting.Annex.Enabled {
// "/config" is git's config, not specifically git-annex's; but the only current
// user of it is when git-annex downloads the annex.uuid during 'git annex init'.
return strings.HasSuffix(req.URL.Path, "/config") || annexPathRe.MatchString(req.URL.Path)
}
return false
}
// handleSignIn clears existing session variables and stores new ones for the specified user object // handleSignIn clears existing session variables and stores new ones for the specified user object
func handleSignIn(resp http.ResponseWriter, req *http.Request, sess SessionStore, user *user_model.User) { func handleSignIn(resp http.ResponseWriter, req *http.Request, sess SessionStore, user *user_model.User) {
// We need to regenerate the session... // We need to regenerate the session...

View file

@ -43,8 +43,8 @@ func (b *Basic) Name() string {
// name/token on successful validation. // name/token on successful validation.
// Returns nil if header is empty or validation fails. // Returns nil if header is empty or validation fails.
func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) { func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
// Basic authentication should only fire on API, Download or on Git, LFSPaths or Git-Annex paths // Basic authentication should only fire on API, Download or on Git or LFSPaths
if !middleware.IsAPIPath(req) && !isContainerPath(req) && !isAttachmentDownload(req) && !isGitRawOrAttachOrLFSPath(req) && !isAnnexPath(req) { if !middleware.IsAPIPath(req) && !isContainerPath(req) && !isAttachmentDownload(req) && !isGitRawOrAttachOrLFSPath(req) {
return nil, nil return nil, nil
} }

View file

@ -671,7 +671,6 @@ type UploadRepoFileForm struct {
CommitChoice string `binding:"Required;MaxSize(50)"` CommitChoice string `binding:"Required;MaxSize(50)"`
NewBranchName string `binding:"GitRefName;MaxSize(100)"` NewBranchName string `binding:"GitRefName;MaxSize(100)"`
Files []string Files []string
FullPaths []string
CommitMailID int64 `binding:"Required"` CommitMailID int64 `binding:"Required"`
Signoff bool Signoff bool
} }

View file

@ -202,26 +202,6 @@ func (t *TemporaryUploadRepository) AddObjectToIndex(mode, objectHash, objectPat
return nil return nil
} }
// InitPrivateAnnex initializes a private annex in the repository
func (t *TemporaryUploadRepository) InitPrivateAnnex() error {
if _, _, err := git.NewCommand(t.ctx, "config", "annex.private", "true").RunStdString(&git.RunOpts{Dir: t.basePath}); err != nil {
return err
}
if _, _, err := git.NewCommand(t.ctx, "annex", "init").RunStdString(&git.RunOpts{Dir: t.basePath}); err != nil {
return err
}
return nil
}
// AddAnnex adds the file at path to the repository using git annex add
// This requires a non-bare repository
func (t *TemporaryUploadRepository) AddAnnex(path string) error {
if _, _, err := git.NewCommand(t.ctx, "annex", "add").AddDynamicArguments(path).RunStdString(&git.RunOpts{Dir: t.basePath}); err != nil {
return err
}
return nil
}
// WriteTree writes the current index as a tree to the object db and returns its hash // WriteTree writes the current index as a tree to the object db and returns its hash
func (t *TemporaryUploadRepository) WriteTree() (string, error) { func (t *TemporaryUploadRepository) WriteTree() (string, error) {
stdout, _, err := git.NewCommand(t.ctx, "write-tree").RunStdString(&git.RunOpts{Dir: t.basePath}) stdout, _, err := git.NewCommand(t.ctx, "write-tree").RunStdString(&git.RunOpts{Dir: t.basePath})

View file

@ -10,7 +10,6 @@ import (
"path" "path"
"strings" "strings"
"time" "time"
"errors"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
git_model "code.gitea.io/gitea/models/git" git_model "code.gitea.io/gitea/models/git"
@ -57,7 +56,6 @@ type ChangeRepoFilesOptions struct {
Committer *IdentityOptions Committer *IdentityOptions
Dates *CommitDateOptions Dates *CommitDateOptions
Signoff bool Signoff bool
IsDir bool `default:"false"`
} }
type RepoFileOptions struct { type RepoFileOptions struct {
@ -92,30 +90,6 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
return nil, err return nil, err
} }
if opts.IsDir {
var new_opts_files []*ChangeRepoFile
for _, file := range opts.Files {
if file.Operation != "delete" {
return nil, errors.New("invalid operation: only delete is allowed for directory paths")
}
treePath := CleanUploadFileName(file.TreePath)
filelist, err := gitRepo.LsFilesFromDirectory(treePath, opts.OldBranch)
if err != nil {
return nil, err
}
for _, filename := range filelist {
if len(filename) > 0{
new_opts_files = append(new_opts_files, &ChangeRepoFile{
Operation: "delete",
TreePath: filename,
})
}
}
}
opts.Files = new_opts_files
}
var treePaths []string var treePaths []string
for _, file := range opts.Files { for _, file := range opts.Files {
// If FromTreePath is not set, set it to the opts.TreePath // If FromTreePath is not set, set it to the opts.TreePath

View file

@ -6,18 +6,13 @@ package files
import ( import (
"context" "context"
"fmt" "fmt"
"html"
"io"
"os" "os"
"path" "path"
"path/filepath"
"regexp"
"strings" "strings"
git_model "code.gitea.io/gitea/models/git" git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/annex"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -32,8 +27,7 @@ type UploadRepoFileOptions struct {
Message string Message string
Author *IdentityOptions Author *IdentityOptions
Committer *IdentityOptions Committer *IdentityOptions
Files []string // In UUID format Files []string // In UUID format.
FullPaths []string
Signoff bool Signoff bool
} }
@ -62,10 +56,6 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
return nil return nil
} }
if len(opts.Files) != len(opts.FullPaths) {
return nil
}
uploads, err := repo_model.GetUploadsByUUIDs(ctx, opts.Files) uploads, err := repo_model.GetUploadsByUUIDs(ctx, opts.Files)
if err != nil { if err != nil {
return fmt.Errorf("GetUploadsByUUIDs [uuids: %v]: %w", opts.Files, err) return fmt.Errorf("GetUploadsByUUIDs [uuids: %v]: %w", opts.Files, err)
@ -75,7 +65,6 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
infos := make([]uploadInfo, len(uploads)) infos := make([]uploadInfo, len(uploads))
for i, upload := range uploads { for i, upload := range uploads {
// Check file is not lfs locked, will return nil if lock setting not enabled // Check file is not lfs locked, will return nil if lock setting not enabled
upload.Name = fileNameSanitize(html.UnescapeString(opts.FullPaths[i]))
filepath := path.Join(opts.TreePath, upload.Name) filepath := path.Join(opts.TreePath, upload.Name)
lfsLock, err := git_model.GetTreePathLock(ctx, repo.ID, filepath) lfsLock, err := git_model.GetTreePathLock(ctx, repo.ID, filepath)
if err != nil { if err != nil {
@ -100,7 +89,7 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
defer t.Close() defer t.Close()
hasOldBranch := true hasOldBranch := true
if err = t.Clone(opts.OldBranch, false); err != nil { if err = t.Clone(opts.OldBranch, true); err != nil {
if !git.IsErrBranchNotExist(err) || !repo.IsEmpty { if !git.IsErrBranchNotExist(err) || !repo.IsEmpty {
return err return err
} }
@ -116,30 +105,10 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
} }
} }
r, err := git.OpenRepository(ctx, repo.RepoPath()) // Copy uploaded files into repository.
if err != nil { if err := copyUploadedLFSFilesIntoRepository(infos, t, opts.TreePath); err != nil {
return err return err
} }
if annex.IsAnnexRepo(r) {
// Initialize annex privately in temporary clone
if err := t.InitPrivateAnnex(); err != nil {
return err
}
// Copy uploaded files into git-annex repository
if err := copyUploadedFilesIntoAnnexRepository(infos, t, opts.TreePath); err != nil {
return err
}
// Move all annexed content in the temporary repository, i.e. everything we have just added, to the origin
author, committer := GetAuthorAndCommitterUsers(opts.Author, opts.Committer, doer)
if err := moveAnnexedFilesToOrigin(t, author, committer); err != nil {
return err
}
} else {
// Copy uploaded files into repository.
if err := copyUploadedLFSFilesIntoRepository(infos, t, opts.TreePath); err != nil {
return err
}
}
// Now write the tree // Now write the tree
treeHash, err := t.WriteTree() treeHash, err := t.WriteTree()
@ -186,19 +155,6 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
return repo_model.DeleteUploads(ctx, uploads...) return repo_model.DeleteUploads(ctx, uploads...)
} }
// From forgejo/services/repository/generate.go (but now allows /)
var fileNameSanitizeRegexp = regexp.MustCompile(`(?i)\.\.|[<>:\"\\|?*\x{0000}-\x{001F}]|^(con|prn|aux|nul|com\d|lpt\d)$`)
// Sanitize user input to valid OS filenames
//
// Based on https://github.com/sindresorhus/filename-reserved-regex
// Adds ".." to prevent directory traversal
func fileNameSanitize(s string) string {
// Added this because I am not sure what Windows will deliver us \ or / but we need /.
s = strings.ReplaceAll(s, "\\", "/")
return strings.TrimSpace(fileNameSanitizeRegexp.ReplaceAllString(s, "_"))
}
func copyUploadedLFSFilesIntoRepository(infos []uploadInfo, t *TemporaryUploadRepository, treePath string) error { func copyUploadedLFSFilesIntoRepository(infos []uploadInfo, t *TemporaryUploadRepository, treePath string) error {
var storeInLFSFunc func(string) (bool, error) var storeInLFSFunc func(string) (bool, error)
@ -290,57 +246,3 @@ func uploadToLFSContentStore(info uploadInfo, contentStore *lfs.ContentStore) er
} }
return nil return nil
} }
func copyUploadedFilesIntoAnnexRepository(infos []uploadInfo, t *TemporaryUploadRepository, treePath string) error {
for i := range len(infos) {
if err := copyUploadedFileIntoAnnexRepository(&infos[i], t, treePath); err != nil {
return err
}
}
return nil
}
func copyUploadedFileIntoAnnexRepository(info *uploadInfo, t *TemporaryUploadRepository, treePath string) error {
pathInRepo := path.Join(t.basePath, treePath, info.upload.Name)
if err := os.MkdirAll(filepath.Dir(pathInRepo), 0o700); err != nil {
return err
}
if err := os.Rename(info.upload.LocalPath(), pathInRepo); err != nil {
// Rename didn't work, try copy and remove
inputFile, err := os.Open(info.upload.LocalPath())
if err != nil {
return fmt.Errorf("could not open source file: %v", err)
}
defer inputFile.Close()
outputFile, err := os.Create(pathInRepo)
if err != nil {
return fmt.Errorf("could not open dest file: %v", err)
}
defer outputFile.Close()
_, err = io.Copy(outputFile, inputFile)
if err != nil {
return fmt.Errorf("could not copy to dest from source: %v", err)
}
inputFile.Close()
err = os.Remove(info.upload.LocalPath())
if err != nil {
return fmt.Errorf("could not remove source file: %v", err)
}
}
return t.AddAnnex(pathInRepo)
}
func moveAnnexedFilesToOrigin(t *TemporaryUploadRepository, author, committer *user_model.User) error {
authorSig := author.NewGitSig()
committerSig := committer.NewGitSig()
env := append(os.Environ(),
"GIT_AUTHOR_NAME="+authorSig.Name,
"GIT_AUTHOR_EMAIL="+authorSig.Email,
"GIT_COMMITTER_NAME="+committerSig.Name,
"GIT_COMMITTER_EMAIL="+committerSig.Email,
)
if _, _, err := git.NewCommand(t.ctx, "annex", "move", "--to", "origin").RunStdString(&git.RunOpts{Dir: t.basePath, Env: env}); err != nil {
return err
}
return nil
}

View file

@ -11,10 +11,41 @@
</div> </div>
</div> </div>
</div> </div>
<div class="ui stackable middle very relaxed page grid">
{{if .LandingPageInfoEnabled}} <div class="eight wide center column">
{{template "landing-page" .}} <h1 class="hero ui icon header">
{{end}} {{svg "octicon-flame"}} {{ctx.Locale.Tr "startpage.install"}}
</h1>
<p class="large">
{{ctx.Locale.Tr "startpage.install_desc" "https://forgejo.org/download/#installation-from-binary" "https://forgejo.org/download/#container-image" "https://forgejo.org/download"}}
</p>
</div>
<div class="eight wide center column">
<h1 class="hero ui icon header">
{{svg "octicon-device-desktop"}} {{ctx.Locale.Tr "startpage.platform"}}
</h1>
<p class="large">
{{ctx.Locale.Tr "startpage.platform_desc"}}
</p>
</div>
</div>
<div class="ui stackable middle very relaxed page grid">
<div class="eight wide center column">
<h1 class="hero ui icon header">
{{svg "octicon-rocket"}} {{ctx.Locale.Tr "startpage.lightweight"}}
</h1>
<p class="large">
{{ctx.Locale.Tr "startpage.lightweight_desc"}}
</p>
</div>
<div class="eight wide center column">
<h1 class="hero ui icon header">
{{svg "octicon-code"}} {{ctx.Locale.Tr "startpage.license"}}
</h1>
<p class="large">
{{ctx.Locale.Tr "startpage.license_desc" "https://forgejo.org/download" "https://codeberg.org/forgejo/forgejo"}}
</p>
</div>
</div>
</div> </div>
{{template "base/footer" .}} {{template "base/footer" .}}

View file

@ -1,36 +0,0 @@
<div class="ui stackable middle very relaxed page grid">
<div class="eight wide center column">
<h1 class="hero ui icon header">
{{svg "octicon-flame"}} {{ctx.Locale.Tr "startpage.install"}}
</h1>
<p class="large">
{{ctx.Locale.Tr "startpage.install_desc" "https://forgejo.org/download/#installation-from-binary" "https://forgejo.org/download/#container-image" "https://forgejo.org/download"}}
</p>
</div>
<div class="eight wide center column">
<h1 class="hero ui icon header">
{{svg "octicon-device-desktop"}} {{ctx.Locale.Tr "startpage.platform"}}
</h1>
<p class="large">
{{ctx.Locale.Tr "startpage.platform_desc"}}
</p>
</div>
</div>
<div class="ui stackable middle very relaxed page grid">
<div class="eight wide center column">
<h1 class="hero ui icon header">
{{svg "octicon-rocket"}} {{ctx.Locale.Tr "startpage.lightweight"}}
</h1>
<p class="large">
{{ctx.Locale.Tr "startpage.lightweight_desc"}}
</p>
</div>
<div class="eight wide center column">
<h1 class="hero ui icon header">
{{svg "octicon-code"}} {{ctx.Locale.Tr "startpage.license"}}
</h1>
<p class="large">
{{ctx.Locale.Tr "startpage.license_desc" "https://forgejo.org/download" "https://codeberg.org/forgejo/forgejo"}}
</p>
</div>
</div>

View file

@ -17,7 +17,6 @@
{{if .FileSize}} {{if .FileSize}}
<div class="file-info-entry"> <div class="file-info-entry">
{{ctx.Locale.TrSize .FileSize}}{{if .IsLFSFile}} ({{ctx.Locale.Tr "repo.stored_lfs"}}){{end}} {{ctx.Locale.TrSize .FileSize}}{{if .IsLFSFile}} ({{ctx.Locale.Tr "repo.stored_lfs"}}){{end}}
{{if .IsAnnexFile}} ({{ctx.Locale.Tr "repo.stored_annex"}}{{if not .IsAnnexFilePresent}} - {{ctx.Locale.Tr "repo.stored_annex_not_present"}}{{end}}){{end}}
</div> </div>
{{end}} {{end}}
{{if .LFSLock}} {{if .LFSLock}}

View file

@ -114,15 +114,6 @@
{{- end -}} {{- end -}}
</span> </span>
{{end}} {{end}}
{{if (not .IsViewFile)}}
{{if and .CanDeleteFile (not .IsBlame)}}
<a href="{{.RepoLink}}/_delete_path/{{PathEscapeSegments .BranchName}}/{{PathEscapeSegments .TreePath}}"><span class="btn-octicon btn-octicon-danger" data-tooltip-content="{{.DeleteFileTooltip}}">{{svg "octicon-trash"}}</span></a>
{{else}}
<span class="btn-octicon disabled" data-tooltip-content="{{.DeleteFileTooltip}}">{{svg "octicon-trash"}}</span>
{{end}}
{{end}}
</div> </div>
<div class="tw-flex tw-items-center"> <div class="tw-flex tw-items-center">
<!-- Only show clone panel in repository home page --> <!-- Only show clone panel in repository home page -->

View file

@ -50,21 +50,18 @@
</div> </div>
</div> </div>
{{if .SignInForgottenPasswordEnabled}} <div class="ui container fluid">
<div class="ui container fluid"> {{template "user/auth/webauthn_error" .}}
{{template "user/auth/webauthn_error" .}}
<div class="ui attached segment header top tw-max-w-2xl tw-m-auto tw-flex tw-flex-col tw-items-center"> <div class="ui attached segment header top tw-max-w-2xl tw-m-auto tw-flex tw-flex-col tw-items-center">
{{if .ShowRegistrationButton}} {{if .ShowRegistrationButton}}
<div class="field"> <div class="field">
{{ctx.Locale.Tr "auth.hint_register" (printf "%s/user/sign_up" AppSubUrl)}} {{ctx.Locale.Tr "auth.hint_register" (printf "%s/user/sign_up" AppSubUrl)}}
<br> <br>
</div>
{{end}}
<div class="field">
<a href="{{AppSubUrl}}/user/forgot_password">{{ctx.Locale.Tr "auth.forgot_password"}}</a>
</div>
</div> </div>
</div> {{end}}
{{end}} <div class="field">
<a href="{{AppSubUrl}}/user/forgot_password">{{ctx.Locale.Tr "auth.forgot_password"}}</a>
</div>
</div>
</div>

View file

@ -22,7 +22,6 @@ import (
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/forms"
"github.com/google/uuid"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -462,27 +461,3 @@ func doAPIAddRepoToOrganizationTeam(ctx APITestContext, teamID int64, orgName, r
ctx.Session.MakeRequest(t, req, http.StatusNoContent) ctx.Session.MakeRequest(t, req, http.StatusNoContent)
} }
} }
// generate and activate an ssh key for the user attached to the APITestContext
// TODO: pick a better name; golang doesn't do method overriding.
func withCtxKeyFile(t *testing.T, ctx APITestContext, callback func()) {
// we need to have write:public_key to do this step
// the easiest way is to create a throwaway ctx that is identical but only has that permission
ctxKeyWriter := ctx
ctxKeyWriter.Token = getTokenForLoggedInUser(t, ctx.Session, auth.AccessTokenScopeWriteUser)
keyName := "One of " + ctx.Username + "'s keys: #" + uuid.New().String()
withKeyFile(t, keyName, func(keyFile string) {
var key api.PublicKey
doAPICreateUserKey(ctxKeyWriter, keyName, keyFile,
func(t *testing.T, _key api.PublicKey) {
// save the key ID so we can delete it at the end
key = _key
})(t)
defer doAPIDeleteUserKey(ctxKeyWriter, key.ID)(t)
callback()
})
}

File diff suppressed because it is too large Load diff

View file

@ -42,28 +42,6 @@ func withKeyFile(t *testing.T, keyname string, callback func(string)) {
"ssh -o \"UserKnownHostsFile=/dev/null\" -o \"StrictHostKeyChecking=no\" -o \"IdentitiesOnly=yes\" -i \""+keyFile+"\" \"$@\""), 0o700) "ssh -o \"UserKnownHostsFile=/dev/null\" -o \"StrictHostKeyChecking=no\" -o \"IdentitiesOnly=yes\" -i \""+keyFile+"\" \"$@\""), 0o700)
require.NoError(t, err) require.NoError(t, err)
// reset ssh wrapper afterwards
_gitSSH, gitSSHExists := os.LookupEnv("GIT_SSH")
defer func() {
if gitSSHExists {
os.Setenv("GIT_SSH", _gitSSH)
}
}()
_gitSSHCommand, gitSSHCommandExists := os.LookupEnv("GIT_SSH_COMMAND")
defer func() {
if gitSSHCommandExists {
os.Setenv("GIT_SSH_COMMAND", _gitSSHCommand)
}
}()
_gitSSHVariant, gitSSHVariantExists := os.LookupEnv("GIT_SSH_VARIANT")
defer func() {
if gitSSHVariantExists {
os.Setenv("GIT_SSH_VARIANT", _gitSSHVariant)
}
}()
// Setup ssh wrapper // Setup ssh wrapper
t.Setenv("GIT_SSH", path.Join(tmpDir, "ssh")) t.Setenv("GIT_SSH", path.Join(tmpDir, "ssh"))
t.Setenv("GIT_SSH_COMMAND", t.Setenv("GIT_SSH_COMMAND",
@ -73,13 +51,6 @@ func withKeyFile(t *testing.T, keyname string, callback func(string)) {
callback(keyFile) callback(keyFile)
} }
func createHTTPUrl(gitPath string, u *url.URL) *url.URL {
// this assumes u contains the HTTP base URL that Gitea is running on
u2 := *u
u2.Path = gitPath
return &u2
}
func createSSHUrl(gitPath string, u *url.URL) *url.URL { func createSSHUrl(gitPath string, u *url.URL) *url.URL {
u2 := *u u2 := *u
u2.Scheme = "ssh" u2.Scheme = "ssh"

View file

@ -97,9 +97,6 @@ DISABLE_QUERY_AUTH_TOKEN = true
[lfs] [lfs]
PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mysql/data/lfs PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mysql/data/lfs
[annex]
ENABLED = true
[packages] [packages]
ENABLED = true ENABLED = true

View file

@ -122,9 +122,6 @@ MINIO_LOCATION = us-east-1
MINIO_USE_SSL = false MINIO_USE_SSL = false
MINIO_CHECKSUM_ALGORITHM = md5 MINIO_CHECKSUM_ALGORITHM = md5
[annex]
ENABLED = true
[packages] [packages]
ENABLED = true ENABLED = true

View file

@ -102,9 +102,6 @@ JWT_SECRET = KZb_QLUd4fYVyxetjxC4eZkrBgWM2SndOOWDNtgUUko
[lfs] [lfs]
PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-sqlite/data/lfs PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-sqlite/data/lfs
[annex]
ENABLED = true
[packages] [packages]
ENABLED = true ENABLED = true

View file

@ -5,11 +5,9 @@
package tests package tests
import ( import (
"bytes"
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"io"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
@ -490,80 +488,3 @@ func CreateDeclarativeRepo(t *testing.T, owner *user_model.User, name string, en
return CreateDeclarativeRepoWithOptions(t, owner, opts) return CreateDeclarativeRepoWithOptions(t, owner, opts)
} }
// Decide if two files have the same contents or not.
// chunkSize is the size of the blocks to scan by; pass 0 to get a sensible default.
// *Follows* symlinks.
//
// May return an error if something else goes wrong; in this case, you should ignore the value of 'same'.
//
// derived from https://stackoverflow.com/a/30038571
// under CC-BY-SA-4.0 by several contributors
func FileCmp(file1, file2 string, chunkSize int) (same bool, err error) {
if chunkSize == 0 {
chunkSize = 4 * 1024
}
// shortcuts: check file metadata
stat1, err := os.Stat(file1)
if err != nil {
return false, err
}
stat2, err := os.Stat(file2)
if err != nil {
return false, err
}
// are inputs are literally the same file?
if os.SameFile(stat1, stat2) {
return true, nil
}
// do inputs at least have the same size?
if stat1.Size() != stat2.Size() {
return false, nil
}
// long way: compare contents
f1, err := os.Open(file1)
if err != nil {
return false, err
}
defer f1.Close()
f2, err := os.Open(file2)
if err != nil {
return false, err
}
defer f2.Close()
b1 := make([]byte, chunkSize)
b2 := make([]byte, chunkSize)
for {
n1, err1 := io.ReadFull(f1, b1)
n2, err2 := io.ReadFull(f2, b2)
// https://pkg.go.dev/io#Reader
// > Callers should always process the n > 0 bytes returned
// > before considering the error err. Doing so correctly
// > handles I/O errors that happen after reading some bytes
// > and also both of the allowed EOF behaviors.
if !bytes.Equal(b1[:n1], b2[:n2]) {
return false, nil
}
if (err1 == io.EOF && err2 == io.EOF) || (err1 == io.ErrUnexpectedEOF && err2 == io.ErrUnexpectedEOF) {
return true, nil
}
// some other error, like a dropped network connection or a bad transfer
if err1 != nil {
return false, err1
}
if err2 != nil {
return false, err2
}
}
}

View file

@ -244,7 +244,6 @@ td .commit-summary {
} }
.repository.file.list #repo-files-table tbody .svg.octicon-file, .repository.file.list #repo-files-table tbody .svg.octicon-file,
.repository.file.list #repo-files-table tbody .svg.octicon-file-binary,
.repository.file.list #repo-files-table tbody .svg.octicon-file-symlink-file, .repository.file.list #repo-files-table tbody .svg.octicon-file-symlink-file,
.repository.file.list #repo-files-table tbody .svg.octicon-file-directory-symlink { .repository.file.list #repo-files-table tbody .svg.octicon-file-directory-symlink {
color: var(--color-secondary-dark-7); color: var(--color-secondary-dark-7);

View file

@ -1,19 +1,19 @@
import $ from 'jquery'; import $ from 'jquery';
import '../vendor/jquery.are-you-sure.js'; import '../vendor/jquery.are-you-sure.js';
import { clippie } from 'clippie'; import {clippie} from 'clippie';
import { createDropzone } from './dropzone.js'; import {createDropzone} from './dropzone.js';
import { showGlobalErrorMessage } from '../bootstrap.js'; import {showGlobalErrorMessage} from '../bootstrap.js';
import { handleGlobalEnterQuickSubmit } from './comp/QuickSubmit.js'; import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.js';
import { svg } from '../svg.js'; import {svg} from '../svg.js';
import { hideElem, showElem, toggleElem, initSubmitEventPolyfill, submitEventSubmitter } from '../utils/dom.js'; import {hideElem, showElem, toggleElem, initSubmitEventPolyfill, submitEventSubmitter} from '../utils/dom.js';
import { htmlEscape } from 'escape-goat'; import {htmlEscape} from 'escape-goat';
import { showTemporaryTooltip } from '../modules/tippy.js'; import {showTemporaryTooltip} from '../modules/tippy.js';
import { confirmModal } from './comp/ConfirmModal.js'; import {confirmModal} from './comp/ConfirmModal.js';
import { showErrorToast } from '../modules/toast.js'; import {showErrorToast} from '../modules/toast.js';
import { request, POST, GET } from '../modules/fetch.js'; import {request, POST, GET} from '../modules/fetch.js';
import '../htmx.js'; import '../htmx.js';
const { appUrl, appSubUrl, csrfToken, i18n } = window.config; const {appUrl, appSubUrl, csrfToken, i18n} = window.config;
export function initGlobalFormDirtyLeaveConfirm() { export function initGlobalFormDirtyLeaveConfirm() {
// Warn users that try to leave a page after entering data into a form. // Warn users that try to leave a page after entering data into a form.
@ -82,7 +82,7 @@ async function fetchActionDoRequest(actionElem, url, opt) {
try { try {
const resp = await request(url, opt); const resp = await request(url, opt);
if (resp.status === 200) { if (resp.status === 200) {
let { redirect } = await resp.json(); let {redirect} = await resp.json();
redirect = redirect || actionElem.getAttribute('data-redirect'); redirect = redirect || actionElem.getAttribute('data-redirect');
actionElem.classList.remove('dirty'); // remove the areYouSure check before reloading actionElem.classList.remove('dirty'); // remove the areYouSure check before reloading
if (redirect) { if (redirect) {
@ -96,7 +96,7 @@ async function fetchActionDoRequest(actionElem, url, opt) {
// the code was quite messy, sometimes the backend uses "err", sometimes it uses "error", and even "user_error" // the code was quite messy, sometimes the backend uses "err", sometimes it uses "error", and even "user_error"
// but at the moment, as a new approach, we only use "errorMessage" here, backend can use JSONError() to respond. // but at the moment, as a new approach, we only use "errorMessage" here, backend can use JSONError() to respond.
if (data.errorMessage) { if (data.errorMessage) {
showErrorToast(data.errorMessage, { useHtmlBody: data.renderFormat === 'html' }); showErrorToast(data.errorMessage, {useHtmlBody: data.renderFormat === 'html'});
} else { } else {
showErrorToast(`server error: ${resp.status}`); showErrorToast(`server error: ${resp.status}`);
} }
@ -134,7 +134,7 @@ async function formFetchAction(e) {
} }
let reqUrl = formActionUrl; let reqUrl = formActionUrl;
const reqOpt = { method: formMethod.toUpperCase() }; const reqOpt = {method: formMethod.toUpperCase()};
if (formMethod.toLowerCase() === 'get') { if (formMethod.toLowerCase() === 'get') {
const params = new URLSearchParams(); const params = new URLSearchParams();
for (const [key, value] of formData) { for (const [key, value] of formData) {
@ -212,7 +212,7 @@ export function initDropzone(el) {
const $dropzone = $(el); const $dropzone = $(el);
const _promise = createDropzone(el, { const _promise = createDropzone(el, {
url: $dropzone.data('upload-url'), url: $dropzone.data('upload-url'),
headers: { 'X-Csrf-Token': csrfToken }, headers: {'X-Csrf-Token': csrfToken},
maxFiles: $dropzone.data('max-file'), maxFiles: $dropzone.data('max-file'),
maxFilesize: $dropzone.data('max-size'), maxFilesize: $dropzone.data('max-size'),
acceptedFiles: (['*/*', ''].includes($dropzone.data('accepts'))) ? null : $dropzone.data('accepts'), acceptedFiles: (['*/*', ''].includes($dropzone.data('accepts'))) ? null : $dropzone.data('accepts'),
@ -229,8 +229,7 @@ export function initDropzone(el) {
this.on('success', (file, data) => { this.on('success', (file, data) => {
file.uuid = data.uuid; file.uuid = data.uuid;
const $input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid); const $input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
const $inputPath = $(`<input type="hidden" name="files_fullpath[${data.uuid}]" value="${htmlEscape(file.fullPath || file.name)}">`); $dropzone.find('.files').append($input);
$dropzone.find('.files').append($input).append($inputPath);
// Create a "Copy Link" element, to conveniently copy the image // Create a "Copy Link" element, to conveniently copy the image
// or file link as Markdown to the clipboard // or file link as Markdown to the clipboard
const copyLinkElement = document.createElement('div'); const copyLinkElement = document.createElement('div');
@ -251,15 +250,10 @@ export function initDropzone(el) {
file.previewTemplate.append(copyLinkElement); file.previewTemplate.append(copyLinkElement);
}); });
this.on('removedfile', (file) => { this.on('removedfile', (file) => {
// Remove the hidden input for the file
$(`#${file.uuid}`).remove(); $(`#${file.uuid}`).remove();
// Remove the hidden input for files_fullpath
$(`input[name="files_fullpath[${file.uuid}]"]`).remove();
if ($dropzone.data('remove-url')) { if ($dropzone.data('remove-url')) {
POST($dropzone.data('remove-url'), { POST($dropzone.data('remove-url'), {
data: new URLSearchParams({ file: file.uuid }), data: new URLSearchParams({file: file.uuid}),
}); });
} }
}); });
@ -282,7 +276,7 @@ async function linkAction(e) {
const url = el.getAttribute('data-url'); const url = el.getAttribute('data-url');
const doRequest = async () => { const doRequest = async () => {
el.disabled = true; el.disabled = true;
await fetchActionDoRequest(el, url, { method: 'POST' }); await fetchActionDoRequest(el, url, {method: 'POST'});
el.disabled = false; el.disabled = false;
}; };
@ -293,7 +287,7 @@ async function linkAction(e) {
} }
const isRisky = el.classList.contains('red') || el.classList.contains('yellow') || el.classList.contains('orange') || el.classList.contains('negative'); const isRisky = el.classList.contains('red') || el.classList.contains('yellow') || el.classList.contains('orange') || el.classList.contains('negative');
if (await confirmModal({ content: modalConfirmContent, buttonColor: isRisky ? 'orange' : 'primary' })) { if (await confirmModal({content: modalConfirmContent, buttonColor: isRisky ? 'orange' : 'primary'})) {
await doRequest(); await doRequest();
} }
} }
@ -337,7 +331,7 @@ export function initGlobalLinkActions() {
} }
} }
const response = await POST($this.data('url'), { data: postData }); const response = await POST($this.data('url'), {data: postData});
if (response.ok) { if (response.ok) {
const data = await response.json(); const data = await response.json();
window.location.href = data.redirect; window.location.href = data.redirect;

View file

@ -92,17 +92,7 @@ export function initImageDiff() {
return loadElem(img, info.path); return loadElem(img, info.path);
})); }));
// only the first images is associated with $boundsInfo // only the first images is associated with $boundsInfo
if (!success) { if (!success) info.$boundsInfo.text('(image error)');
const blobContent = await GET(info.path.replace('/media/', '/raw/')).then((response) => response.text());
if (blobContent.startsWith('.git/annex/objects')) {
for (const item of document.querySelectorAll('.image-diff .overflow-menu-items .item')) {
item.style.display = 'none';
}
info.$boundsInfo[0].parentElement.textContent = 'annexed file is not present on the server';
} else {
info.$boundsInfo.text('(image error)');
}
}
if (info.mime === 'image/svg+xml') { if (info.mime === 'image/svg+xml') {
const resp = await GET(info.path); const resp = await GET(info.path);
const text = await resp.text(); const text = await resp.text();