Compare commits
No commits in common. "main" and "10.0.1_base" have entirely different histories.
main
...
10.0.1_bas
80 changed files with 138 additions and 4313 deletions
|
@ -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}}
|
||||||
|
|
|
@ -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') }}
|
|
|
@ -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
|
||||||
|
|
|
@ -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 \
|
||||||
|
|
|
@ -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 \
|
||||||
|
|
4
Makefile
4
Makefile
|
@ -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/\..*//')
|
||||||
|
|
76
cmd/serv.go
76
cmd/serv.go
|
@ -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)
|
||||||
|
|
11
cmd/web.go
11
cmd/web.go
|
@ -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:
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
25
modules/markup/external/external.go
vendored
25
modules/markup/external/external.go
vendored
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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=Το αρχείο είναι κλειδωμένο
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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=پرونده قفل شده است
|
||||||
|
|
|
@ -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é
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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ð
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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=ファイルはロックされています
|
||||||
|
|
|
@ -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=새 파일
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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=Файл заблокирован
|
||||||
|
|
|
@ -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=ගොනුවට අගුළු ලා ඇත
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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=Файл заблоковано
|
||||||
|
|
|
@ -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=文件已锁定
|
||||||
|
|
|
@ -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=或
|
||||||
|
|
|
@ -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
2
package-lock.json
generated
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "forgejo-aneksajo",
|
"name": "forgejo",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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...
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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})
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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" .}}
|
||||||
|
|
|
@ -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>
|
|
|
@ -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}}
|
||||||
|
|
|
@ -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 -->
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Add table
Reference in a new issue