mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-28 02:38:44 +09:00
Merge branch 'master' into refactor_issues-subscription
This commit is contained in:
@@ -438,9 +438,9 @@ js: npm
|
||||
|
||||
.PHONY: css
|
||||
css: npm
|
||||
npx stylelint public/less
|
||||
npx lessc --clean-css="--s0 -b" public/less/index.less public/css/index.css
|
||||
$(foreach file, $(filter-out public/less/themes/_base.less, $(wildcard public/less/themes/*)),npx lessc --clean-css="--s0 -b" public/less/themes/$(notdir $(file)) > public/css/theme-$(notdir $(call strip-suffix,$(file))).css;)
|
||||
npx stylelint web_src/less
|
||||
npx lessc --clean-css="--s0 -b" web_src/less/index.less public/css/index.css
|
||||
$(foreach file, $(filter-out web_src/less/themes/_base.less, $(wildcard web_src/less/themes/*)),npx lessc --clean-css="--s0 -b" web_src/less/themes/$(notdir $(file)) > public/css/theme-$(notdir $(call strip-suffix,$(file))).css;)
|
||||
npx postcss --use autoprefixer --no-map --replace public/css/*
|
||||
|
||||
@diff=$$(git diff public/css/*); \
|
||||
|
||||
@@ -375,17 +375,20 @@ func runRepoSyncReleases(c *cli.Context) error {
|
||||
|
||||
if err = models.SyncReleasesWithTags(repo, gitRepo); err != nil {
|
||||
log.Warn(" SyncReleasesWithTags: %v", err)
|
||||
gitRepo.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
count, err = getReleaseCount(repo.ID)
|
||||
if err != nil {
|
||||
log.Warn(" GetReleaseCountByRepoID: %v", err)
|
||||
gitRepo.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
log.Trace(" repo %s releases synchronized to tags: from %d to %d",
|
||||
repo.FullName(), oldnum, count)
|
||||
gitRepo.Close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,8 +51,8 @@ Type=simple
|
||||
User=git
|
||||
Group=git
|
||||
WorkingDirectory=/var/lib/gitea/
|
||||
# If using unix socket: Tells Systemd to create /run/gitea folder to home gitea.sock
|
||||
# Manual cration would vanish after reboot.
|
||||
# If using Unix socket: tells systemd to create the /run/gitea folder, which will contain the gitea.sock file
|
||||
# (manually creating /run/gitea doesn't work, because it would not persist across reboots)
|
||||
#RuntimeDirectory=gitea
|
||||
ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini
|
||||
Restart=always
|
||||
|
||||
@@ -501,6 +501,9 @@ SHOW_REGISTRATION_BUTTON = true
|
||||
; When adding a repo to a team or creating a new repo all team members will watch the
|
||||
; repo automatically if enabled
|
||||
AUTO_WATCH_NEW_REPOS = true
|
||||
; Default value for AutoWatchOnChanges
|
||||
; Make the user watch a repository When they commit for the first time
|
||||
AUTO_WATCH_ON_CHANGES = false
|
||||
|
||||
[webhook]
|
||||
; Hook task queue length, increase if webhook shooting starts hanging
|
||||
|
||||
@@ -303,6 +303,7 @@ relation to port exhaustion.
|
||||
on this instance.
|
||||
- `SHOW_REGISTRATION_BUTTON`: **! DISABLE\_REGISTRATION**: Show Registration Button
|
||||
- `AUTO_WATCH_NEW_REPOS`: **true**: Enable this to let all organisation users watch new repos when they are created
|
||||
- `AUTO_WATCH_ON_CHANGES`: **false**: Enable this to make users watch a repository after their first commit to it
|
||||
- `DEFAULT_ORG_VISIBILITY`: **public**: Set default visibility mode for organisations, either "public", "limited" or "private".
|
||||
- `DEFAULT_ORG_MEMBER_VISIBLE`: **false** True will make the membership of the users visible when added to the organisation.
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ type Uploader interface {
|
||||
CreateComment(issueNumber int64, comment *Comment) error
|
||||
CreatePullRequest(pr *PullRequest) error
|
||||
Rollback() error
|
||||
Close()
|
||||
}
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
@@ -13,7 +13,7 @@ menu:
|
||||
identifier: "fail2ban-setup"
|
||||
---
|
||||
|
||||
# Fail2ban setup to block users after failed login attemts
|
||||
# Fail2ban setup to block users after failed login attempts
|
||||
|
||||
**Remember that fail2ban is powerful and can cause lots of issues if you do it incorrectly, so make
|
||||
sure to test this before relying on it so you don't lock yourself out.**
|
||||
|
||||
@@ -51,6 +51,7 @@ func TestAPICreateAndUpdateRelease(t *testing.T) {
|
||||
|
||||
gitRepo, err := git.OpenRepository(repo.RepoPath())
|
||||
assert.NoError(t, err)
|
||||
defer gitRepo.Close()
|
||||
|
||||
err = gitRepo.CreateTag("v0.0.1", "master")
|
||||
assert.NoError(t, err)
|
||||
@@ -112,6 +113,7 @@ func TestAPICreateReleaseToDefaultBranchOnExistingTag(t *testing.T) {
|
||||
|
||||
gitRepo, err := git.OpenRepository(repo.RepoPath())
|
||||
assert.NoError(t, err)
|
||||
defer gitRepo.Close()
|
||||
|
||||
err = gitRepo.CreateTag("v0.0.1", "master")
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -139,6 +139,7 @@ func TestAPICreateFile(t *testing.T) {
|
||||
assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL)
|
||||
assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email)
|
||||
assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name)
|
||||
gitRepo.Close()
|
||||
}
|
||||
|
||||
// Test creating a file in a new branch
|
||||
|
||||
@@ -143,6 +143,7 @@ func TestAPIUpdateFile(t *testing.T) {
|
||||
assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL)
|
||||
assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email)
|
||||
assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name)
|
||||
gitRepo.Close()
|
||||
}
|
||||
|
||||
// Test updating a file in a new branch
|
||||
|
||||
@@ -74,6 +74,8 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) {
|
||||
repo1.CreateNewBranch(user2, repo1.DefaultBranch, newBranch)
|
||||
// Get the commit ID of the default branch
|
||||
gitRepo, _ := git.OpenRepository(repo1.RepoPath())
|
||||
defer gitRepo.Close()
|
||||
|
||||
commitID, _ := gitRepo.GetBranchCommitID(repo1.DefaultBranch)
|
||||
// Make a new tag in repo1
|
||||
newTag := "test_tag"
|
||||
|
||||
@@ -75,6 +75,8 @@ func testAPIGetContents(t *testing.T, u *url.URL) {
|
||||
repo1.CreateNewBranch(user2, repo1.DefaultBranch, newBranch)
|
||||
// Get the commit ID of the default branch
|
||||
gitRepo, _ := git.OpenRepository(repo1.RepoPath())
|
||||
defer gitRepo.Close()
|
||||
|
||||
commitID, _ := gitRepo.GetBranchCommitID(repo1.DefaultBranch)
|
||||
// Make a new tag in repo1
|
||||
newTag := "test_tag"
|
||||
|
||||
@@ -29,6 +29,8 @@ func TestAPIGitTags(t *testing.T) {
|
||||
git.NewCommand("config", "user.email", user.Email).RunInDir(repo.RepoPath())
|
||||
|
||||
gitRepo, _ := git.OpenRepository(repo.RepoPath())
|
||||
defer gitRepo.Close()
|
||||
|
||||
commit, _ := gitRepo.GetBranchCommit("master")
|
||||
lTagName := "lightweightTag"
|
||||
gitRepo.CreateTag(lTagName, commit.ID.String())
|
||||
|
||||
@@ -70,9 +70,9 @@ func TestAPISearchRepo(t *testing.T) {
|
||||
expectedResults
|
||||
}{
|
||||
{name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50&private=false", expectedResults: expectedResults{
|
||||
nil: {count: 22},
|
||||
user: {count: 22},
|
||||
user2: {count: 22}},
|
||||
nil: {count: 24},
|
||||
user: {count: 24},
|
||||
user2: {count: 24}},
|
||||
},
|
||||
{name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10&private=false", expectedResults: expectedResults{
|
||||
nil: {count: 10},
|
||||
@@ -92,7 +92,7 @@ func TestAPISearchRepo(t *testing.T) {
|
||||
{name: "RepositoriesAccessibleAndRelatedToUser", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user.ID), expectedResults: expectedResults{
|
||||
nil: {count: 5},
|
||||
user: {count: 9, includesPrivate: true},
|
||||
user2: {count: 5, includesPrivate: true}},
|
||||
user2: {count: 6, includesPrivate: true}},
|
||||
},
|
||||
{name: "RepositoriesAccessibleAndRelatedToUser2", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user2.ID), expectedResults: expectedResults{
|
||||
nil: {count: 1},
|
||||
@@ -103,7 +103,7 @@ func TestAPISearchRepo(t *testing.T) {
|
||||
{name: "RepositoriesAccessibleAndRelatedToUser3", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user3.ID), expectedResults: expectedResults{
|
||||
nil: {count: 1},
|
||||
user: {count: 4, includesPrivate: true},
|
||||
user2: {count: 2, includesPrivate: true},
|
||||
user2: {count: 3, includesPrivate: true},
|
||||
user3: {count: 4, includesPrivate: true}},
|
||||
},
|
||||
{name: "RepositoriesOwnedByOrganization", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", orgUser.ID), expectedResults: expectedResults{
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/convert"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/routers/api/v1/convert"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/convert"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/routers/api/v1/convert"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ref: refs/heads/master
|
||||
@@ -0,0 +1,6 @@
|
||||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = false
|
||||
bare = true
|
||||
symlinks = false
|
||||
ignorecase = true
|
||||
@@ -0,0 +1 @@
|
||||
Unnamed repository; edit this file 'description' to name the repository.
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to check the commit log message taken by
|
||||
# applypatch from an e-mail message.
|
||||
#
|
||||
# The hook should exit with non-zero status after issuing an
|
||||
# appropriate message if it wants to stop the commit. The hook is
|
||||
# allowed to edit the commit message file.
|
||||
#
|
||||
# To enable this hook, rename this file to "applypatch-msg".
|
||||
|
||||
. git-sh-setup
|
||||
commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
|
||||
test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
|
||||
:
|
||||
@@ -0,0 +1,24 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to check the commit log message.
|
||||
# Called by "git commit" with one argument, the name of the file
|
||||
# that has the commit message. The hook should exit with non-zero
|
||||
# status after issuing an appropriate message if it wants to stop the
|
||||
# commit. The hook is allowed to edit the commit message file.
|
||||
#
|
||||
# To enable this hook, rename this file to "commit-msg".
|
||||
|
||||
# Uncomment the below to add a Signed-off-by line to the message.
|
||||
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
|
||||
# hook is more suited to it.
|
||||
#
|
||||
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
|
||||
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
|
||||
|
||||
# This example catches duplicate Signed-off-by lines.
|
||||
|
||||
test "" = "$(grep '^Signed-off-by: ' "$1" |
|
||||
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
|
||||
echo >&2 Duplicate Signed-off-by lines.
|
||||
exit 1
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use IPC::Open2;
|
||||
|
||||
# An example hook script to integrate Watchman
|
||||
# (https://facebook.github.io/watchman/) with git to speed up detecting
|
||||
# new and modified files.
|
||||
#
|
||||
# The hook is passed a version (currently 1) and a time in nanoseconds
|
||||
# formatted as a string and outputs to stdout all files that have been
|
||||
# modified since the given time. Paths must be relative to the root of
|
||||
# the working tree and separated by a single NUL.
|
||||
#
|
||||
# To enable this hook, rename this file to "query-watchman" and set
|
||||
# 'git config core.fsmonitor .git/hooks/query-watchman'
|
||||
#
|
||||
my ($version, $time) = @ARGV;
|
||||
|
||||
# Check the hook interface version
|
||||
|
||||
if ($version == 1) {
|
||||
# convert nanoseconds to seconds
|
||||
$time = int $time / 1000000000;
|
||||
} else {
|
||||
die "Unsupported query-fsmonitor hook version '$version'.\n" .
|
||||
"Falling back to scanning...\n";
|
||||
}
|
||||
|
||||
my $git_work_tree;
|
||||
if ($^O =~ 'msys' || $^O =~ 'cygwin') {
|
||||
$git_work_tree = Win32::GetCwd();
|
||||
$git_work_tree =~ tr/\\/\//;
|
||||
} else {
|
||||
require Cwd;
|
||||
$git_work_tree = Cwd::cwd();
|
||||
}
|
||||
|
||||
my $retry = 1;
|
||||
|
||||
launch_watchman();
|
||||
|
||||
sub launch_watchman {
|
||||
|
||||
my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty')
|
||||
or die "open2() failed: $!\n" .
|
||||
"Falling back to scanning...\n";
|
||||
|
||||
# In the query expression below we're asking for names of files that
|
||||
# changed since $time but were not transient (ie created after
|
||||
# $time but no longer exist).
|
||||
#
|
||||
# To accomplish this, we're using the "since" generator to use the
|
||||
# recency index to select candidate nodes and "fields" to limit the
|
||||
# output to file names only. Then we're using the "expression" term to
|
||||
# further constrain the results.
|
||||
#
|
||||
# The category of transient files that we want to ignore will have a
|
||||
# creation clock (cclock) newer than $time_t value and will also not
|
||||
# currently exist.
|
||||
|
||||
my $query = <<" END";
|
||||
["query", "$git_work_tree", {
|
||||
"since": $time,
|
||||
"fields": ["name"],
|
||||
"expression": ["not", ["allof", ["since", $time, "cclock"], ["not", "exists"]]]
|
||||
}]
|
||||
END
|
||||
|
||||
print CHLD_IN $query;
|
||||
close CHLD_IN;
|
||||
my $response = do {local $/; <CHLD_OUT>};
|
||||
|
||||
die "Watchman: command returned no output.\n" .
|
||||
"Falling back to scanning...\n" if $response eq "";
|
||||
die "Watchman: command returned invalid output: $response\n" .
|
||||
"Falling back to scanning...\n" unless $response =~ /^\{/;
|
||||
|
||||
my $json_pkg;
|
||||
eval {
|
||||
require JSON::XS;
|
||||
$json_pkg = "JSON::XS";
|
||||
1;
|
||||
} or do {
|
||||
require JSON::PP;
|
||||
$json_pkg = "JSON::PP";
|
||||
};
|
||||
|
||||
my $o = $json_pkg->new->utf8->decode($response);
|
||||
|
||||
if ($retry > 0 and $o->{error} and $o->{error} =~ m/unable to resolve root .* directory (.*) is not watched/) {
|
||||
print STDERR "Adding '$git_work_tree' to watchman's watch list.\n";
|
||||
$retry--;
|
||||
qx/watchman watch "$git_work_tree"/;
|
||||
die "Failed to make watchman watch '$git_work_tree'.\n" .
|
||||
"Falling back to scanning...\n" if $? != 0;
|
||||
|
||||
# Watchman will always return all files on the first query so
|
||||
# return the fast "everything is dirty" flag to git and do the
|
||||
# Watchman query just to get it over with now so we won't pay
|
||||
# the cost in git to look up each individual file.
|
||||
print "/\0";
|
||||
eval { launch_watchman() };
|
||||
exit 0;
|
||||
}
|
||||
|
||||
die "Watchman: $o->{error}.\n" .
|
||||
"Falling back to scanning...\n" if $o->{error};
|
||||
|
||||
binmode STDOUT, ":utf8";
|
||||
local $, = "\0";
|
||||
print @{$o->{files}};
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
data=$(cat)
|
||||
exitcodes=""
|
||||
hookname=$(basename $0)
|
||||
GIT_DIR=${GIT_DIR:-$(dirname $0)}
|
||||
|
||||
for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do
|
||||
test -x "${hook}" || continue
|
||||
echo "${data}" | "${hook}"
|
||||
exitcodes="${exitcodes} $?"
|
||||
done
|
||||
|
||||
for i in ${exitcodes}; do
|
||||
[ ${i} -eq 0 ] || exit ${i}
|
||||
done
|
||||
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" post-receive
|
||||
@@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to prepare a packed repository for use over
|
||||
# dumb transports.
|
||||
#
|
||||
# To enable this hook, rename this file to "post-update".
|
||||
|
||||
exec git update-server-info
|
||||
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to verify what is about to be committed
|
||||
# by applypatch from an e-mail message.
|
||||
#
|
||||
# The hook should exit with non-zero status after issuing an
|
||||
# appropriate message if it wants to stop the commit.
|
||||
#
|
||||
# To enable this hook, rename this file to "pre-applypatch".
|
||||
|
||||
. git-sh-setup
|
||||
precommit="$(git rev-parse --git-path hooks/pre-commit)"
|
||||
test -x "$precommit" && exec "$precommit" ${1+"$@"}
|
||||
:
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to verify what is about to be committed.
|
||||
# Called by "git commit" with no arguments. The hook should
|
||||
# exit with non-zero status after issuing an appropriate message if
|
||||
# it wants to stop the commit.
|
||||
#
|
||||
# To enable this hook, rename this file to "pre-commit".
|
||||
|
||||
if git rev-parse --verify HEAD >/dev/null 2>&1
|
||||
then
|
||||
against=HEAD
|
||||
else
|
||||
# Initial commit: diff against an empty tree object
|
||||
against=$(git hash-object -t tree /dev/null)
|
||||
fi
|
||||
|
||||
# If you want to allow non-ASCII filenames set this variable to true.
|
||||
allownonascii=$(git config --bool hooks.allownonascii)
|
||||
|
||||
# Redirect output to stderr.
|
||||
exec 1>&2
|
||||
|
||||
# Cross platform projects tend to avoid non-ASCII filenames; prevent
|
||||
# them from being added to the repository. We exploit the fact that the
|
||||
# printable range starts at the space character and ends with tilde.
|
||||
if [ "$allownonascii" != "true" ] &&
|
||||
# Note that the use of brackets around a tr range is ok here, (it's
|
||||
# even required, for portability to Solaris 10's /usr/bin/tr), since
|
||||
# the square bracket bytes happen to fall in the designated range.
|
||||
test $(git diff --cached --name-only --diff-filter=A -z $against |
|
||||
LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
|
||||
then
|
||||
cat <<\EOF
|
||||
Error: Attempt to add a non-ASCII file name.
|
||||
|
||||
This can cause problems if you want to work with people on other platforms.
|
||||
|
||||
To be portable it is advisable to rename the file.
|
||||
|
||||
If you know what you are doing you can disable this check using:
|
||||
|
||||
git config hooks.allownonascii true
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# If there are whitespace errors, print the offending file names and fail.
|
||||
exec git diff-index --check --cached $against --
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/bin/sh
|
||||
|
||||
# An example hook script to verify what is about to be pushed. Called by "git
|
||||
# push" after it has checked the remote status, but before anything has been
|
||||
# pushed. If this script exits with a non-zero status nothing will be pushed.
|
||||
#
|
||||
# This hook is called with the following parameters:
|
||||
#
|
||||
# $1 -- Name of the remote to which the push is being done
|
||||
# $2 -- URL to which the push is being done
|
||||
#
|
||||
# If pushing without using a named remote those arguments will be equal.
|
||||
#
|
||||
# Information about the commits which are being pushed is supplied as lines to
|
||||
# the standard input in the form:
|
||||
#
|
||||
# <local ref> <local sha1> <remote ref> <remote sha1>
|
||||
#
|
||||
# This sample shows how to prevent push of commits where the log message starts
|
||||
# with "WIP" (work in progress).
|
||||
|
||||
remote="$1"
|
||||
url="$2"
|
||||
|
||||
z40=0000000000000000000000000000000000000000
|
||||
|
||||
while read local_ref local_sha remote_ref remote_sha
|
||||
do
|
||||
if [ "$local_sha" = $z40 ]
|
||||
then
|
||||
# Handle delete
|
||||
:
|
||||
else
|
||||
if [ "$remote_sha" = $z40 ]
|
||||
then
|
||||
# New branch, examine all commits
|
||||
range="$local_sha"
|
||||
else
|
||||
# Update to existing branch, examine new commits
|
||||
range="$remote_sha..$local_sha"
|
||||
fi
|
||||
|
||||
# Check for WIP commit
|
||||
commit=`git rev-list -n 1 --grep '^WIP' "$range"`
|
||||
if [ -n "$commit" ]
|
||||
then
|
||||
echo >&2 "Found WIP commit in $local_ref, not pushing"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,169 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Copyright (c) 2006, 2008 Junio C Hamano
|
||||
#
|
||||
# The "pre-rebase" hook is run just before "git rebase" starts doing
|
||||
# its job, and can prevent the command from running by exiting with
|
||||
# non-zero status.
|
||||
#
|
||||
# The hook is called with the following parameters:
|
||||
#
|
||||
# $1 -- the upstream the series was forked from.
|
||||
# $2 -- the branch being rebased (or empty when rebasing the current branch).
|
||||
#
|
||||
# This sample shows how to prevent topic branches that are already
|
||||
# merged to 'next' branch from getting rebased, because allowing it
|
||||
# would result in rebasing already published history.
|
||||
|
||||
publish=next
|
||||
basebranch="$1"
|
||||
if test "$#" = 2
|
||||
then
|
||||
topic="refs/heads/$2"
|
||||
else
|
||||
topic=`git symbolic-ref HEAD` ||
|
||||
exit 0 ;# we do not interrupt rebasing detached HEAD
|
||||
fi
|
||||
|
||||
case "$topic" in
|
||||
refs/heads/??/*)
|
||||
;;
|
||||
*)
|
||||
exit 0 ;# we do not interrupt others.
|
||||
;;
|
||||
esac
|
||||
|
||||
# Now we are dealing with a topic branch being rebased
|
||||
# on top of master. Is it OK to rebase it?
|
||||
|
||||
# Does the topic really exist?
|
||||
git show-ref -q "$topic" || {
|
||||
echo >&2 "No such branch $topic"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Is topic fully merged to master?
|
||||
not_in_master=`git rev-list --pretty=oneline ^master "$topic"`
|
||||
if test -z "$not_in_master"
|
||||
then
|
||||
echo >&2 "$topic is fully merged to master; better remove it."
|
||||
exit 1 ;# we could allow it, but there is no point.
|
||||
fi
|
||||
|
||||
# Is topic ever merged to next? If so you should not be rebasing it.
|
||||
only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`
|
||||
only_next_2=`git rev-list ^master ${publish} | sort`
|
||||
if test "$only_next_1" = "$only_next_2"
|
||||
then
|
||||
not_in_topic=`git rev-list "^$topic" master`
|
||||
if test -z "$not_in_topic"
|
||||
then
|
||||
echo >&2 "$topic is already up to date with master"
|
||||
exit 1 ;# we could allow it, but there is no point.
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
|
||||
/usr/bin/perl -e '
|
||||
my $topic = $ARGV[0];
|
||||
my $msg = "* $topic has commits already merged to public branch:\n";
|
||||
my (%not_in_next) = map {
|
||||
/^([0-9a-f]+) /;
|
||||
($1 => 1);
|
||||
} split(/\n/, $ARGV[1]);
|
||||
for my $elem (map {
|
||||
/^([0-9a-f]+) (.*)$/;
|
||||
[$1 => $2];
|
||||
} split(/\n/, $ARGV[2])) {
|
||||
if (!exists $not_in_next{$elem->[0]}) {
|
||||
if ($msg) {
|
||||
print STDERR $msg;
|
||||
undef $msg;
|
||||
}
|
||||
print STDERR " $elem->[1]\n";
|
||||
}
|
||||
}
|
||||
' "$topic" "$not_in_next" "$not_in_master"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
<<\DOC_END
|
||||
|
||||
This sample hook safeguards topic branches that have been
|
||||
published from being rewound.
|
||||
|
||||
The workflow assumed here is:
|
||||
|
||||
* Once a topic branch forks from "master", "master" is never
|
||||
merged into it again (either directly or indirectly).
|
||||
|
||||
* Once a topic branch is fully cooked and merged into "master",
|
||||
it is deleted. If you need to build on top of it to correct
|
||||
earlier mistakes, a new topic branch is created by forking at
|
||||
the tip of the "master". This is not strictly necessary, but
|
||||
it makes it easier to keep your history simple.
|
||||
|
||||
* Whenever you need to test or publish your changes to topic
|
||||
branches, merge them into "next" branch.
|
||||
|
||||
The script, being an example, hardcodes the publish branch name
|
||||
to be "next", but it is trivial to make it configurable via
|
||||
$GIT_DIR/config mechanism.
|
||||
|
||||
With this workflow, you would want to know:
|
||||
|
||||
(1) ... if a topic branch has ever been merged to "next". Young
|
||||
topic branches can have stupid mistakes you would rather
|
||||
clean up before publishing, and things that have not been
|
||||
merged into other branches can be easily rebased without
|
||||
affecting other people. But once it is published, you would
|
||||
not want to rewind it.
|
||||
|
||||
(2) ... if a topic branch has been fully merged to "master".
|
||||
Then you can delete it. More importantly, you should not
|
||||
build on top of it -- other people may already want to
|
||||
change things related to the topic as patches against your
|
||||
"master", so if you need further changes, it is better to
|
||||
fork the topic (perhaps with the same name) afresh from the
|
||||
tip of "master".
|
||||
|
||||
Let's look at this example:
|
||||
|
||||
o---o---o---o---o---o---o---o---o---o "next"
|
||||
/ / / /
|
||||
/ a---a---b A / /
|
||||
/ / / /
|
||||
/ / c---c---c---c B /
|
||||
/ / / \ /
|
||||
/ / / b---b C \ /
|
||||
/ / / / \ /
|
||||
---o---o---o---o---o---o---o---o---o---o---o "master"
|
||||
|
||||
|
||||
A, B and C are topic branches.
|
||||
|
||||
* A has one fix since it was merged up to "next".
|
||||
|
||||
* B has finished. It has been fully merged up to "master" and "next",
|
||||
and is ready to be deleted.
|
||||
|
||||
* C has not merged to "next" at all.
|
||||
|
||||
We would want to allow C to be rebased, refuse A, and encourage
|
||||
B to be deleted.
|
||||
|
||||
To compute (1):
|
||||
|
||||
git rev-list ^master ^topic next
|
||||
git rev-list ^master next
|
||||
|
||||
if these match, topic has not merged in next at all.
|
||||
|
||||
To compute (2):
|
||||
|
||||
git rev-list master..topic
|
||||
|
||||
if this is empty, it is fully merged to "master".
|
||||
|
||||
DOC_END
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
data=$(cat)
|
||||
exitcodes=""
|
||||
hookname=$(basename $0)
|
||||
GIT_DIR=${GIT_DIR:-$(dirname $0)}
|
||||
|
||||
for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do
|
||||
test -x "${hook}" || continue
|
||||
echo "${data}" | "${hook}"
|
||||
exitcodes="${exitcodes} $?"
|
||||
done
|
||||
|
||||
for i in ${exitcodes}; do
|
||||
[ ${i} -eq 0 ] || exit ${i}
|
||||
done
|
||||
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" pre-receive
|
||||
@@ -0,0 +1,24 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to make use of push options.
|
||||
# The example simply echoes all push options that start with 'echoback='
|
||||
# and rejects all pushes when the "reject" push option is used.
|
||||
#
|
||||
# To enable this hook, rename this file to "pre-receive".
|
||||
|
||||
if test -n "$GIT_PUSH_OPTION_COUNT"
|
||||
then
|
||||
i=0
|
||||
while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"
|
||||
do
|
||||
eval "value=\$GIT_PUSH_OPTION_$i"
|
||||
case "$value" in
|
||||
echoback=*)
|
||||
echo "echo from the pre-receive-hook: ${value#*=}" >&2
|
||||
;;
|
||||
reject)
|
||||
exit 1
|
||||
esac
|
||||
i=$((i + 1))
|
||||
done
|
||||
fi
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to prepare the commit log message.
|
||||
# Called by "git commit" with the name of the file that has the
|
||||
# commit message, followed by the description of the commit
|
||||
# message's source. The hook's purpose is to edit the commit
|
||||
# message file. If the hook fails with a non-zero status,
|
||||
# the commit is aborted.
|
||||
#
|
||||
# To enable this hook, rename this file to "prepare-commit-msg".
|
||||
|
||||
# This hook includes three examples. The first one removes the
|
||||
# "# Please enter the commit message..." help message.
|
||||
#
|
||||
# The second includes the output of "git diff --name-status -r"
|
||||
# into the message, just before the "git status" output. It is
|
||||
# commented because it doesn't cope with --amend or with squashed
|
||||
# commits.
|
||||
#
|
||||
# The third example adds a Signed-off-by line to the message, that can
|
||||
# still be edited. This is rarely a good idea.
|
||||
|
||||
COMMIT_MSG_FILE=$1
|
||||
COMMIT_SOURCE=$2
|
||||
SHA1=$3
|
||||
|
||||
/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE"
|
||||
|
||||
# case "$COMMIT_SOURCE,$SHA1" in
|
||||
# ,|template,)
|
||||
# /usr/bin/perl -i.bak -pe '
|
||||
# print "\n" . `git diff --cached --name-status -r`
|
||||
# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;;
|
||||
# *) ;;
|
||||
# esac
|
||||
|
||||
# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
|
||||
# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE"
|
||||
# if test -z "$COMMIT_SOURCE"
|
||||
# then
|
||||
# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE"
|
||||
# fi
|
||||
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
exitcodes=""
|
||||
hookname=$(basename $0)
|
||||
GIT_DIR=${GIT_DIR:-$(dirname $0)}
|
||||
|
||||
for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do
|
||||
test -x "${hook}" || continue
|
||||
"${hook}" $1 $2 $3
|
||||
exitcodes="${exitcodes} $?"
|
||||
done
|
||||
|
||||
for i in ${exitcodes}; do
|
||||
[ ${i} -eq 0 ] || exit ${i}
|
||||
done
|
||||
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" update $1 $2 $3
|
||||
@@ -0,0 +1,128 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to block unannotated tags from entering.
|
||||
# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
|
||||
#
|
||||
# To enable this hook, rename this file to "update".
|
||||
#
|
||||
# Config
|
||||
# ------
|
||||
# hooks.allowunannotated
|
||||
# This boolean sets whether unannotated tags will be allowed into the
|
||||
# repository. By default they won't be.
|
||||
# hooks.allowdeletetag
|
||||
# This boolean sets whether deleting tags will be allowed in the
|
||||
# repository. By default they won't be.
|
||||
# hooks.allowmodifytag
|
||||
# This boolean sets whether a tag may be modified after creation. By default
|
||||
# it won't be.
|
||||
# hooks.allowdeletebranch
|
||||
# This boolean sets whether deleting branches will be allowed in the
|
||||
# repository. By default they won't be.
|
||||
# hooks.denycreatebranch
|
||||
# This boolean sets whether remotely creating branches will be denied
|
||||
# in the repository. By default this is allowed.
|
||||
#
|
||||
|
||||
# --- Command line
|
||||
refname="$1"
|
||||
oldrev="$2"
|
||||
newrev="$3"
|
||||
|
||||
# --- Safety check
|
||||
if [ -z "$GIT_DIR" ]; then
|
||||
echo "Don't run this script from the command line." >&2
|
||||
echo " (if you want, you could supply GIT_DIR then run" >&2
|
||||
echo " $0 <ref> <oldrev> <newrev>)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
|
||||
echo "usage: $0 <ref> <oldrev> <newrev>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- Config
|
||||
allowunannotated=$(git config --bool hooks.allowunannotated)
|
||||
allowdeletebranch=$(git config --bool hooks.allowdeletebranch)
|
||||
denycreatebranch=$(git config --bool hooks.denycreatebranch)
|
||||
allowdeletetag=$(git config --bool hooks.allowdeletetag)
|
||||
allowmodifytag=$(git config --bool hooks.allowmodifytag)
|
||||
|
||||
# check for no description
|
||||
projectdesc=$(sed -e '1q' "$GIT_DIR/description")
|
||||
case "$projectdesc" in
|
||||
"Unnamed repository"* | "")
|
||||
echo "*** Project description file hasn't been set" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# --- Check types
|
||||
# if $newrev is 0000...0000, it's a commit to delete a ref.
|
||||
zero="0000000000000000000000000000000000000000"
|
||||
if [ "$newrev" = "$zero" ]; then
|
||||
newrev_type=delete
|
||||
else
|
||||
newrev_type=$(git cat-file -t $newrev)
|
||||
fi
|
||||
|
||||
case "$refname","$newrev_type" in
|
||||
refs/tags/*,commit)
|
||||
# un-annotated tag
|
||||
short_refname=${refname##refs/tags/}
|
||||
if [ "$allowunannotated" != "true" ]; then
|
||||
echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
|
||||
echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
refs/tags/*,delete)
|
||||
# delete tag
|
||||
if [ "$allowdeletetag" != "true" ]; then
|
||||
echo "*** Deleting a tag is not allowed in this repository" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
refs/tags/*,tag)
|
||||
# annotated tag
|
||||
if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
|
||||
then
|
||||
echo "*** Tag '$refname' already exists." >&2
|
||||
echo "*** Modifying a tag is not allowed in this repository." >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
refs/heads/*,commit)
|
||||
# branch
|
||||
if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
|
||||
echo "*** Creating a branch is not allowed in this repository" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
refs/heads/*,delete)
|
||||
# delete branch
|
||||
if [ "$allowdeletebranch" != "true" ]; then
|
||||
echo "*** Deleting a branch is not allowed in this repository" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
refs/remotes/*,commit)
|
||||
# tracking branch
|
||||
;;
|
||||
refs/remotes/*,delete)
|
||||
# delete tracking branch
|
||||
if [ "$allowdeletebranch" != "true" ]; then
|
||||
echo "*** Deleting a tracking branch is not allowed in this repository" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
# Anything else (is there anything else?)
|
||||
echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# --- Finished
|
||||
exit 0
|
||||
@@ -0,0 +1,6 @@
|
||||
# git ls-files --others --exclude-from=.git/info/exclude
|
||||
# Lines that start with '#' are comments.
|
||||
# For a project mostly in C, the following would be a good set of
|
||||
# exclude patterns (uncomment them if you want to use them):
|
||||
# *.[oa]
|
||||
# *~
|
||||
@@ -0,0 +1 @@
|
||||
aacbdfe9e1c4b47f60abe81849045fa4e96f1d75 refs/heads/master
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
aacbdfe9e1c4b47f60abe81849045fa4e96f1d75
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -102,7 +103,11 @@ func initIntegrationTest() {
|
||||
fmt.Println("Environment variable $GITEA_ROOT not set")
|
||||
os.Exit(1)
|
||||
}
|
||||
setting.AppPath = path.Join(giteaRoot, "gitea")
|
||||
giteaBinary := "gitea"
|
||||
if runtime.GOOS == "windows" {
|
||||
giteaBinary += ".exe"
|
||||
}
|
||||
setting.AppPath = path.Join(giteaRoot, giteaBinary)
|
||||
if _, err := os.Stat(setting.AppPath); err != nil {
|
||||
fmt.Printf("Could not find gitea binary at %s\n", setting.AppPath)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -5,15 +5,22 @@
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/services/pull"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/unknwon/i18n"
|
||||
@@ -202,3 +209,133 @@ func TestCantMergeWorkInProgress(t *testing.T) {
|
||||
assert.Equal(t, replacer.Replace(expected), text, "Unable to find WIP text")
|
||||
})
|
||||
}
|
||||
|
||||
func TestCantMergeConflict(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
prepareTestEnv(t)
|
||||
session := loginUser(t, "user1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
|
||||
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "conflict", "README.md", "Hello, World (Edited Once)\n")
|
||||
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "base", "README.md", "Hello, World (Edited Twice)\n")
|
||||
|
||||
// Use API to create a conflicting pr
|
||||
token := getTokenForLoggedInUser(t, session)
|
||||
req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls?token=%s", "user1", "repo1", token), &api.CreatePullRequestOption{
|
||||
Head: "conflict",
|
||||
Base: "base",
|
||||
Title: "create a conflicting pr",
|
||||
})
|
||||
session.MakeRequest(t, req, 201)
|
||||
|
||||
// Now this PR will be marked conflict - or at least a race will do - so drop down to pure code at this point...
|
||||
user1 := models.AssertExistsAndLoadBean(t, &models.User{
|
||||
Name: "user1",
|
||||
}).(*models.User)
|
||||
repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{
|
||||
OwnerID: user1.ID,
|
||||
Name: "repo1",
|
||||
}).(*models.Repository)
|
||||
|
||||
pr := models.AssertExistsAndLoadBean(t, &models.PullRequest{
|
||||
HeadRepoID: repo1.ID,
|
||||
BaseRepoID: repo1.ID,
|
||||
HeadBranch: "conflict",
|
||||
BaseBranch: "base",
|
||||
}).(*models.PullRequest)
|
||||
|
||||
gitRepo, err := git.OpenRepository(models.RepoPath(user1.Name, repo1.Name))
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = pull.Merge(pr, user1, gitRepo, models.MergeStyleMerge, "CONFLICT")
|
||||
assert.Error(t, err, "Merge should return an error due to conflict")
|
||||
assert.True(t, models.IsErrMergeConflicts(err), "Merge error is not a conflict error")
|
||||
|
||||
err = pull.Merge(pr, user1, gitRepo, models.MergeStyleRebase, "CONFLICT")
|
||||
assert.Error(t, err, "Merge should return an error due to conflict")
|
||||
assert.True(t, models.IsErrRebaseConflicts(err), "Merge error is not a conflict error")
|
||||
})
|
||||
}
|
||||
|
||||
func TestCantMergeUnrelated(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
prepareTestEnv(t)
|
||||
session := loginUser(t, "user1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
|
||||
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "base", "README.md", "Hello, World (Edited Twice)\n")
|
||||
|
||||
// Now we want to create a commit on a branch that is totally unrelated to our current head
|
||||
// Drop down to pure code at this point
|
||||
user1 := models.AssertExistsAndLoadBean(t, &models.User{
|
||||
Name: "user1",
|
||||
}).(*models.User)
|
||||
repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{
|
||||
OwnerID: user1.ID,
|
||||
Name: "repo1",
|
||||
}).(*models.Repository)
|
||||
path := models.RepoPath(user1.Name, repo1.Name)
|
||||
|
||||
_, err := git.NewCommand("read-tree", "--empty").RunInDir(path)
|
||||
assert.NoError(t, err)
|
||||
|
||||
stdin := bytes.NewBufferString("Unrelated File")
|
||||
var stdout strings.Builder
|
||||
err = git.NewCommand("hash-object", "-w", "--stdin").RunInDirFullPipeline(path, &stdout, nil, stdin)
|
||||
assert.NoError(t, err)
|
||||
sha := strings.TrimSpace(stdout.String())
|
||||
|
||||
_, err = git.NewCommand("update-index", "--add", "--replace", "--cacheinfo", "100644", sha, "somewher-over-the-rainbow").RunInDir(path)
|
||||
assert.NoError(t, err)
|
||||
|
||||
treeSha, err := git.NewCommand("write-tree").RunInDir(path)
|
||||
assert.NoError(t, err)
|
||||
treeSha = strings.TrimSpace(treeSha)
|
||||
|
||||
commitTimeStr := time.Now().Format(time.RFC3339)
|
||||
doerSig := user1.NewGitSig()
|
||||
env := append(os.Environ(),
|
||||
"GIT_AUTHOR_NAME="+doerSig.Name,
|
||||
"GIT_AUTHOR_EMAIL="+doerSig.Email,
|
||||
"GIT_AUTHOR_DATE="+commitTimeStr,
|
||||
"GIT_COMMITTER_NAME="+doerSig.Name,
|
||||
"GIT_COMMITTER_EMAIL="+doerSig.Email,
|
||||
"GIT_COMMITTER_DATE="+commitTimeStr,
|
||||
)
|
||||
|
||||
messageBytes := new(bytes.Buffer)
|
||||
_, _ = messageBytes.WriteString("Unrelated")
|
||||
_, _ = messageBytes.WriteString("\n")
|
||||
|
||||
stdout.Reset()
|
||||
err = git.NewCommand("commit-tree", treeSha).RunInDirTimeoutEnvFullPipeline(env, -1, path, &stdout, nil, messageBytes)
|
||||
assert.NoError(t, err)
|
||||
commitSha := strings.TrimSpace(stdout.String())
|
||||
|
||||
_, err = git.NewCommand("branch", "unrelated", commitSha).RunInDir(path)
|
||||
assert.NoError(t, err)
|
||||
|
||||
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "conflict", "README.md", "Hello, World (Edited Once)\n")
|
||||
|
||||
// Use API to create a conflicting pr
|
||||
token := getTokenForLoggedInUser(t, session)
|
||||
req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls?token=%s", "user1", "repo1", token), &api.CreatePullRequestOption{
|
||||
Head: "unrelated",
|
||||
Base: "base",
|
||||
Title: "create an unrelated pr",
|
||||
})
|
||||
session.MakeRequest(t, req, 201)
|
||||
|
||||
// Now this PR could be marked conflict - or at least a race may occur - so drop down to pure code at this point...
|
||||
gitRepo, err := git.OpenRepository(path)
|
||||
assert.NoError(t, err)
|
||||
pr := models.AssertExistsAndLoadBean(t, &models.PullRequest{
|
||||
HeadRepoID: repo1.ID,
|
||||
BaseRepoID: repo1.ID,
|
||||
HeadBranch: "unrelated",
|
||||
BaseBranch: "base",
|
||||
}).(*models.PullRequest)
|
||||
|
||||
err = pull.Merge(pr, user1, gitRepo, models.MergeStyleMerge, "UNRELATED")
|
||||
assert.Error(t, err, "Merge should return an error due to unrelated")
|
||||
assert.True(t, models.IsErrMergeUnrelatedHistories(err), "Merge error is not a unrelated histories error")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func testRepoGenerate(t *testing.T, session *TestSession, templateOwnerName, templateRepoName, generateOwnerName, generateRepoName string) *httptest.ResponseRecorder {
|
||||
generateOwner := models.AssertExistsAndLoadBean(t, &models.User{Name: generateOwnerName}).(*models.User)
|
||||
|
||||
// Step0: check the existence of the generated repo
|
||||
req := NewRequestf(t, "GET", "/%s/%s", generateOwnerName, generateRepoName)
|
||||
resp := session.MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
// Step1: go to the main page of template repo
|
||||
req = NewRequestf(t, "GET", "/%s/%s", templateOwnerName, templateRepoName)
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
// Step2: click the "Use this template" button
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
link, exists := htmlDoc.doc.Find("a.ui.button[href^=\"/repo/create\"]").Attr("href")
|
||||
assert.True(t, exists, "The template has changed")
|
||||
req = NewRequest(t, "GET", link)
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
// Step3: fill the form of the create
|
||||
htmlDoc = NewHTMLParser(t, resp.Body)
|
||||
link, exists = htmlDoc.doc.Find("form.ui.form[action^=\"/repo/create\"]").Attr("action")
|
||||
assert.True(t, exists, "The template has changed")
|
||||
_, exists = htmlDoc.doc.Find(fmt.Sprintf(".owner.dropdown .item[data-value=\"%d\"]", generateOwner.ID)).Attr("data-value")
|
||||
assert.True(t, exists, fmt.Sprintf("Generate owner '%s' is not present in select box", generateOwnerName))
|
||||
req = NewRequestWithValues(t, "POST", link, map[string]string{
|
||||
"_csrf": htmlDoc.GetCSRF(),
|
||||
"uid": fmt.Sprintf("%d", generateOwner.ID),
|
||||
"repo_name": generateRepoName,
|
||||
"git_content": "true",
|
||||
})
|
||||
resp = session.MakeRequest(t, req, http.StatusFound)
|
||||
|
||||
// Step4: check the existence of the generated repo
|
||||
req = NewRequestf(t, "GET", "/%s/%s", generateOwnerName, generateRepoName)
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
func TestRepoGenerate(t *testing.T) {
|
||||
prepareTestEnv(t)
|
||||
session := loginUser(t, "user1")
|
||||
testRepoGenerate(t, session, "user27", "template1", "user1", "generated1")
|
||||
}
|
||||
|
||||
func TestRepoGenerateToOrg(t *testing.T) {
|
||||
prepareTestEnv(t)
|
||||
session := loginUser(t, "user2")
|
||||
testRepoGenerate(t, session, "user27", "template1", "user2", "generated2")
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
func TestRepoWatch(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
// Test round-trip auto-watch
|
||||
setting.Service.AutoWatchOnChanges = true
|
||||
session := loginUser(t, "user2")
|
||||
models.AssertNotExistsBean(t, &models.Watch{UserID: 2, RepoID: 3})
|
||||
testEditFile(t, session, "user3", "repo3", "master", "README.md", "Hello, World (Edited for watch)\n")
|
||||
models.AssertExistsAndLoadBean(t, &models.Watch{UserID: 2, RepoID: 3, Mode: models.RepoWatchModeAuto})
|
||||
})
|
||||
}
|
||||
@@ -73,6 +73,7 @@ func testDeleteRepoFile(t *testing.T, u *url.URL) {
|
||||
test.LoadRepoCommit(t, ctx)
|
||||
test.LoadUser(t, ctx, 2)
|
||||
test.LoadGitRepo(t, ctx)
|
||||
defer ctx.Repo.GitRepo.Close()
|
||||
repo := ctx.Repo.Repository
|
||||
doer := ctx.User
|
||||
opts := getDeleteRepoFileOptions(repo)
|
||||
@@ -111,6 +112,8 @@ func testDeleteRepoFileWithoutBranchNames(t *testing.T, u *url.URL) {
|
||||
test.LoadRepoCommit(t, ctx)
|
||||
test.LoadUser(t, ctx, 2)
|
||||
test.LoadGitRepo(t, ctx)
|
||||
defer ctx.Repo.GitRepo.Close()
|
||||
|
||||
repo := ctx.Repo.Repository
|
||||
doer := ctx.User
|
||||
opts := getDeleteRepoFileOptions(repo)
|
||||
@@ -139,6 +142,8 @@ func TestDeleteRepoFileErrors(t *testing.T) {
|
||||
test.LoadRepoCommit(t, ctx)
|
||||
test.LoadUser(t, ctx, 2)
|
||||
test.LoadGitRepo(t, ctx)
|
||||
defer ctx.Repo.GitRepo.Close()
|
||||
|
||||
repo := ctx.Repo.Repository
|
||||
doer := ctx.User
|
||||
|
||||
|
||||
@@ -191,6 +191,8 @@ func TestCreateOrUpdateRepoFileForCreate(t *testing.T) {
|
||||
test.LoadRepoCommit(t, ctx)
|
||||
test.LoadUser(t, ctx, 2)
|
||||
test.LoadGitRepo(t, ctx)
|
||||
defer ctx.Repo.GitRepo.Close()
|
||||
|
||||
repo := ctx.Repo.Repository
|
||||
doer := ctx.User
|
||||
opts := getCreateRepoFileOptions(repo)
|
||||
@@ -201,6 +203,8 @@ func TestCreateOrUpdateRepoFileForCreate(t *testing.T) {
|
||||
// asserts
|
||||
assert.Nil(t, err)
|
||||
gitRepo, _ := git.OpenRepository(repo.RepoPath())
|
||||
defer gitRepo.Close()
|
||||
|
||||
commitID, _ := gitRepo.GetBranchCommitID(opts.NewBranch)
|
||||
expectedFileResponse := getExpectedFileResponseForRepofilesCreate(commitID)
|
||||
assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content)
|
||||
@@ -220,6 +224,8 @@ func TestCreateOrUpdateRepoFileForUpdate(t *testing.T) {
|
||||
test.LoadRepoCommit(t, ctx)
|
||||
test.LoadUser(t, ctx, 2)
|
||||
test.LoadGitRepo(t, ctx)
|
||||
defer ctx.Repo.GitRepo.Close()
|
||||
|
||||
repo := ctx.Repo.Repository
|
||||
doer := ctx.User
|
||||
opts := getUpdateRepoFileOptions(repo)
|
||||
@@ -230,6 +236,8 @@ func TestCreateOrUpdateRepoFileForUpdate(t *testing.T) {
|
||||
// asserts
|
||||
assert.Nil(t, err)
|
||||
gitRepo, _ := git.OpenRepository(repo.RepoPath())
|
||||
defer gitRepo.Close()
|
||||
|
||||
commitID, _ := gitRepo.GetBranchCommitID(opts.NewBranch)
|
||||
expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commitID, opts.TreePath)
|
||||
assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content)
|
||||
@@ -249,6 +257,8 @@ func TestCreateOrUpdateRepoFileForUpdateWithFileMove(t *testing.T) {
|
||||
test.LoadRepoCommit(t, ctx)
|
||||
test.LoadUser(t, ctx, 2)
|
||||
test.LoadGitRepo(t, ctx)
|
||||
defer ctx.Repo.GitRepo.Close()
|
||||
|
||||
repo := ctx.Repo.Repository
|
||||
doer := ctx.User
|
||||
opts := getUpdateRepoFileOptions(repo)
|
||||
@@ -261,6 +271,8 @@ func TestCreateOrUpdateRepoFileForUpdateWithFileMove(t *testing.T) {
|
||||
// asserts
|
||||
assert.Nil(t, err)
|
||||
gitRepo, _ := git.OpenRepository(repo.RepoPath())
|
||||
defer gitRepo.Close()
|
||||
|
||||
commit, _ := gitRepo.GetBranchCommit(opts.NewBranch)
|
||||
expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commit.ID.String(), opts.TreePath)
|
||||
// assert that the old file no longer exists in the last commit of the branch
|
||||
@@ -288,6 +300,8 @@ func TestCreateOrUpdateRepoFileWithoutBranchNames(t *testing.T) {
|
||||
test.LoadRepoCommit(t, ctx)
|
||||
test.LoadUser(t, ctx, 2)
|
||||
test.LoadGitRepo(t, ctx)
|
||||
defer ctx.Repo.GitRepo.Close()
|
||||
|
||||
repo := ctx.Repo.Repository
|
||||
doer := ctx.User
|
||||
opts := getUpdateRepoFileOptions(repo)
|
||||
@@ -300,6 +314,8 @@ func TestCreateOrUpdateRepoFileWithoutBranchNames(t *testing.T) {
|
||||
// asserts
|
||||
assert.Nil(t, err)
|
||||
gitRepo, _ := git.OpenRepository(repo.RepoPath())
|
||||
defer gitRepo.Close()
|
||||
|
||||
commitID, _ := gitRepo.GetBranchCommitID(repo.DefaultBranch)
|
||||
expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commitID, opts.TreePath)
|
||||
assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content)
|
||||
@@ -315,6 +331,8 @@ func TestCreateOrUpdateRepoFileErrors(t *testing.T) {
|
||||
test.LoadRepoCommit(t, ctx)
|
||||
test.LoadUser(t, ctx, 2)
|
||||
test.LoadGitRepo(t, ctx)
|
||||
defer ctx.Repo.GitRepo.Close()
|
||||
|
||||
repo := ctx.Repo.Repository
|
||||
doer := ctx.User
|
||||
|
||||
|
||||
@@ -84,14 +84,17 @@ func (user *User) checkForConsistency(t *testing.T) {
|
||||
func (repo *Repository) checkForConsistency(t *testing.T) {
|
||||
assert.Equal(t, repo.LowerName, strings.ToLower(repo.Name), "repo: %+v", repo)
|
||||
assertCount(t, &Star{RepoID: repo.ID}, repo.NumStars)
|
||||
assertCount(t, &Watch{RepoID: repo.ID}, repo.NumWatches)
|
||||
assertCount(t, &Milestone{RepoID: repo.ID}, repo.NumMilestones)
|
||||
assertCount(t, &Repository{ForkID: repo.ID}, repo.NumForks)
|
||||
if repo.IsFork {
|
||||
AssertExistsAndLoadBean(t, &Repository{ID: repo.ForkID})
|
||||
}
|
||||
|
||||
actual := getCount(t, x.Where("is_pull=?", false), &Issue{RepoID: repo.ID})
|
||||
actual := getCount(t, x.Where("Mode<>?", RepoWatchModeDont), &Watch{RepoID: repo.ID})
|
||||
assert.EqualValues(t, repo.NumWatches, actual,
|
||||
"Unexpected number of watches for repo %+v", repo)
|
||||
|
||||
actual = getCount(t, x.Where("is_pull=?", false), &Issue{RepoID: repo.ID})
|
||||
assert.EqualValues(t, repo.NumIssues, actual,
|
||||
"Unexpected number of issues for repo %+v", repo)
|
||||
|
||||
|
||||
@@ -1206,6 +1206,79 @@ func (err ErrInvalidMergeStyle) Error() string {
|
||||
err.ID, err.Style)
|
||||
}
|
||||
|
||||
// ErrMergeConflicts represents an error if merging fails with a conflict
|
||||
type ErrMergeConflicts struct {
|
||||
Style MergeStyle
|
||||
StdOut string
|
||||
StdErr string
|
||||
Err error
|
||||
}
|
||||
|
||||
// IsErrMergeConflicts checks if an error is a ErrMergeConflicts.
|
||||
func IsErrMergeConflicts(err error) bool {
|
||||
_, ok := err.(ErrMergeConflicts)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrMergeConflicts) Error() string {
|
||||
return fmt.Sprintf("Merge Conflict Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut)
|
||||
}
|
||||
|
||||
// ErrMergeUnrelatedHistories represents an error if merging fails due to unrelated histories
|
||||
type ErrMergeUnrelatedHistories struct {
|
||||
Style MergeStyle
|
||||
StdOut string
|
||||
StdErr string
|
||||
Err error
|
||||
}
|
||||
|
||||
// IsErrMergeUnrelatedHistories checks if an error is a ErrMergeUnrelatedHistories.
|
||||
func IsErrMergeUnrelatedHistories(err error) bool {
|
||||
_, ok := err.(ErrMergeUnrelatedHistories)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrMergeUnrelatedHistories) Error() string {
|
||||
return fmt.Sprintf("Merge UnrelatedHistories Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut)
|
||||
}
|
||||
|
||||
// ErrMergePushOutOfDate represents an error if merging fails due to unrelated histories
|
||||
type ErrMergePushOutOfDate struct {
|
||||
Style MergeStyle
|
||||
StdOut string
|
||||
StdErr string
|
||||
Err error
|
||||
}
|
||||
|
||||
// IsErrMergePushOutOfDate checks if an error is a ErrMergePushOutOfDate.
|
||||
func IsErrMergePushOutOfDate(err error) bool {
|
||||
_, ok := err.(ErrMergePushOutOfDate)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrMergePushOutOfDate) Error() string {
|
||||
return fmt.Sprintf("Merge PushOutOfDate Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut)
|
||||
}
|
||||
|
||||
// ErrRebaseConflicts represents an error if rebase fails with a conflict
|
||||
type ErrRebaseConflicts struct {
|
||||
Style MergeStyle
|
||||
CommitSHA string
|
||||
StdOut string
|
||||
StdErr string
|
||||
Err error
|
||||
}
|
||||
|
||||
// IsErrRebaseConflicts checks if an error is a ErrRebaseConflicts.
|
||||
func IsErrRebaseConflicts(err error) bool {
|
||||
_, ok := err.(ErrRebaseConflicts)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrRebaseConflicts) Error() string {
|
||||
return fmt.Sprintf("Rebase Error: %v: Whilst Rebasing: %s\n%s\n%s", err.Err, err.CommitSHA, err.StdErr, err.StdOut)
|
||||
}
|
||||
|
||||
// _________ __
|
||||
// \_ ___ \ ____ _____ _____ ____ _____/ |_
|
||||
// / \ \/ / _ \ / \ / \_/ __ \ / \ __\
|
||||
|
||||
@@ -438,3 +438,17 @@
|
||||
type: 3
|
||||
config: "{\"IgnoreWhitespaceConflicts\":false,\"AllowMerge\":true,\"AllowRebase\":true,\"AllowRebaseMerge\":true,\"AllowSquash\":true}"
|
||||
created_unix: 946684810
|
||||
|
||||
-
|
||||
id: 64
|
||||
repo_id: 44
|
||||
type: 1
|
||||
config: "{}"
|
||||
created_unix: 946684810
|
||||
|
||||
-
|
||||
id: 65
|
||||
repo_id: 45
|
||||
type: 1
|
||||
config: "{}"
|
||||
created_unix: 946684810
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
num_closed_pulls: 0
|
||||
num_milestones: 3
|
||||
num_closed_milestones: 1
|
||||
num_watches: 3
|
||||
num_watches: 4
|
||||
status: 0
|
||||
|
||||
-
|
||||
@@ -561,3 +561,29 @@
|
||||
num_issues: 0
|
||||
is_mirror: false
|
||||
status: 0
|
||||
|
||||
-
|
||||
id: 44
|
||||
owner_id: 27
|
||||
lower_name: template1
|
||||
name: template1
|
||||
is_private: false
|
||||
is_template: true
|
||||
num_stars: 0
|
||||
num_forks: 0
|
||||
num_issues: 0
|
||||
is_mirror: false
|
||||
status: 0
|
||||
|
||||
-
|
||||
id: 45
|
||||
owner_id: 27
|
||||
lower_name: template2
|
||||
name: template2
|
||||
is_private: false
|
||||
is_template: true
|
||||
num_stars: 0
|
||||
num_forks: 0
|
||||
num_issues: 0
|
||||
is_mirror: false
|
||||
status: 0
|
||||
|
||||
@@ -427,4 +427,19 @@
|
||||
num_repos: 1
|
||||
num_members: 0
|
||||
num_teams: 1
|
||||
repo_admin_change_team_access: true
|
||||
repo_admin_change_team_access: true
|
||||
|
||||
-
|
||||
id: 27
|
||||
lower_name: user27
|
||||
name: user27
|
||||
full_name: User Twenty-Seven
|
||||
email: user27@example.com
|
||||
email_notifications_preference: enabled
|
||||
passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password
|
||||
type: 0 # individual
|
||||
salt: ZogKvWdyEx
|
||||
is_admin: false
|
||||
avatar: avatar27
|
||||
avatar_email: user27@example.com
|
||||
num_repos: 2
|
||||
|
||||
@@ -2,13 +2,28 @@
|
||||
id: 1
|
||||
user_id: 1
|
||||
repo_id: 1
|
||||
mode: 1 # normal
|
||||
|
||||
-
|
||||
id: 2
|
||||
user_id: 4
|
||||
repo_id: 1
|
||||
mode: 1 # normal
|
||||
|
||||
-
|
||||
id: 3
|
||||
user_id: 9
|
||||
repo_id: 1
|
||||
mode: 1 # normal
|
||||
|
||||
-
|
||||
id: 4
|
||||
user_id: 8
|
||||
repo_id: 1
|
||||
mode: 2 # don't watch
|
||||
|
||||
-
|
||||
id: 5
|
||||
user_id: 11
|
||||
repo_id: 1
|
||||
mode: 3 # auto
|
||||
|
||||
@@ -17,6 +17,7 @@ func BenchmarkGetCommitGraph(b *testing.B) {
|
||||
if err != nil {
|
||||
b.Error("Could not open repository")
|
||||
}
|
||||
defer currentRepo.Close()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
graph, err := GetCommitGraph(currentRepo, 1)
|
||||
|
||||
+11
-14
@@ -7,6 +7,8 @@ package models
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
@@ -171,25 +173,20 @@ func toggleUserAssignee(e *xorm.Session, issue *Issue, assigneeID int64) (remove
|
||||
// MakeIDsFromAPIAssigneesToAdd returns an array with all assignee IDs
|
||||
func MakeIDsFromAPIAssigneesToAdd(oneAssignee string, multipleAssignees []string) (assigneeIDs []int64, err error) {
|
||||
|
||||
var requestAssignees []string
|
||||
|
||||
// Keeping the old assigning method for compatibility reasons
|
||||
if oneAssignee != "" {
|
||||
if oneAssignee != "" && !util.IsStringInSlice(oneAssignee, multipleAssignees) {
|
||||
requestAssignees = append(requestAssignees, oneAssignee)
|
||||
}
|
||||
|
||||
// Prevent double adding assignees
|
||||
var isDouble bool
|
||||
for _, assignee := range multipleAssignees {
|
||||
if assignee == oneAssignee {
|
||||
isDouble = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !isDouble {
|
||||
multipleAssignees = append(multipleAssignees, oneAssignee)
|
||||
}
|
||||
//Prevent empty assignees
|
||||
if len(multipleAssignees) > 0 && multipleAssignees[0] != "" {
|
||||
requestAssignees = append(requestAssignees, multipleAssignees...)
|
||||
}
|
||||
|
||||
// Get the IDs of all assignees
|
||||
assigneeIDs, err = GetUserIDsByNames(multipleAssignees, false)
|
||||
assigneeIDs, err = GetUserIDsByNames(requestAssignees, false)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -59,3 +59,24 @@ func TestUpdateAssignee(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, isAssigned)
|
||||
}
|
||||
|
||||
func TestMakeIDsFromAPIAssigneesToAdd(t *testing.T) {
|
||||
IDs, err := MakeIDsFromAPIAssigneesToAdd("", []string{""})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []int64{}, IDs)
|
||||
|
||||
IDs, err = MakeIDsFromAPIAssigneesToAdd("", []string{"none_existing_user"})
|
||||
assert.Error(t, err)
|
||||
|
||||
IDs, err = MakeIDsFromAPIAssigneesToAdd("user1", []string{"user1"})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []int64{1}, IDs)
|
||||
|
||||
IDs, err = MakeIDsFromAPIAssigneesToAdd("user2", []string{""})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []int64{2}, IDs)
|
||||
|
||||
IDs, err = MakeIDsFromAPIAssigneesToAdd("", []string{"user1", "user2"})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []int64{1, 2}, IDs)
|
||||
}
|
||||
|
||||
@@ -266,6 +266,12 @@ var migrations = []Migration{
|
||||
NewMigration("remove unnecessary columns from label", removeLabelUneededCols),
|
||||
// v105 -> v106
|
||||
NewMigration("add includes_all_repositories to teams", addTeamIncludesAllRepositories),
|
||||
// v106 -> v107
|
||||
NewMigration("add column `mode` to table watch", addModeColumnToWatch),
|
||||
// v107 -> v108
|
||||
NewMigration("Add template options to repository", addTemplateToRepo),
|
||||
// v108 -> v109
|
||||
NewMigration("Add comment_id on table notification", addCommentIDOnNotification),
|
||||
}
|
||||
|
||||
// Migrate database to current version
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// RepoWatchMode specifies what kind of watch the user has on a repository
|
||||
type RepoWatchMode int8
|
||||
|
||||
// Watch is connection request for receiving repository notification.
|
||||
type Watch struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Mode RepoWatchMode `xorm:"SMALLINT NOT NULL DEFAULT 1"`
|
||||
}
|
||||
|
||||
func addModeColumnToWatch(x *xorm.Engine) (err error) {
|
||||
if err = x.Sync2(new(Watch)); err != nil {
|
||||
return
|
||||
}
|
||||
_, err = x.Exec("UPDATE `watch` SET `mode` = 1")
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func addTemplateToRepo(x *xorm.Engine) error {
|
||||
|
||||
type Repository struct {
|
||||
IsTemplate bool `xorm:"INDEX NOT NULL DEFAULT false"`
|
||||
TemplateID int64 `xorm:"INDEX"`
|
||||
}
|
||||
|
||||
return x.Sync2(new(Repository))
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func addCommentIDOnNotification(x *xorm.Engine) error {
|
||||
type Notification struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
CommentID int64
|
||||
}
|
||||
|
||||
return x.Sync2(new(Notification))
|
||||
}
|
||||
@@ -60,9 +60,9 @@ func addRepoSize(x *xorm.Engine) (err error) {
|
||||
}
|
||||
|
||||
repoPath := filepath.Join(setting.RepoRootPath, strings.ToLower(user.Name), strings.ToLower(repo.Name)) + ".git"
|
||||
countObject, err := git.GetRepoSize(repoPath)
|
||||
countObject, err := git.CountObjects(repoPath)
|
||||
if err != nil {
|
||||
log.Warn("GetRepoSize: %v", err)
|
||||
log.Warn("CountObjects: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ func releaseAddColumnIsTagAndSyncTags(x *xorm.Engine) error {
|
||||
if err = models.SyncReleasesWithTags(repo, gitRepo); err != nil {
|
||||
log.Warn("SyncReleasesWithTags: %v", err)
|
||||
}
|
||||
gitRepo.Close()
|
||||
}
|
||||
if len(repos) < pageSize {
|
||||
break
|
||||
|
||||
@@ -91,6 +91,7 @@ func fixReleaseSha1OnReleaseTable(x *xorm.Engine) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
gitRepoCache[release.RepoID] = gitRepo
|
||||
}
|
||||
|
||||
|
||||
+230
-15
@@ -44,8 +44,10 @@ type Notification struct {
|
||||
Status NotificationStatus `xorm:"SMALLINT INDEX NOT NULL"`
|
||||
Source NotificationSource `xorm:"SMALLINT INDEX NOT NULL"`
|
||||
|
||||
IssueID int64 `xorm:"INDEX NOT NULL"`
|
||||
CommitID string `xorm:"INDEX"`
|
||||
IssueID int64 `xorm:"INDEX NOT NULL"`
|
||||
CommitID string `xorm:"INDEX"`
|
||||
CommentID int64
|
||||
Comment *Comment `xorm:"-"`
|
||||
|
||||
UpdatedBy int64 `xorm:"INDEX NOT NULL"`
|
||||
|
||||
@@ -58,22 +60,27 @@ type Notification struct {
|
||||
|
||||
// CreateOrUpdateIssueNotifications creates an issue notification
|
||||
// for each watcher, or updates it if already exists
|
||||
func CreateOrUpdateIssueNotifications(issue *Issue, notificationAuthorID int64) error {
|
||||
func CreateOrUpdateIssueNotifications(issueID, commentID int64, notificationAuthorID int64) error {
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := createOrUpdateIssueNotifications(sess, issue, notificationAuthorID); err != nil {
|
||||
if err := createOrUpdateIssueNotifications(sess, issueID, commentID, notificationAuthorID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
func createOrUpdateIssueNotifications(e Engine, issue *Issue, notificationAuthorID int64) error {
|
||||
issueWatches, err := getIssueWatchers(e, issue.ID)
|
||||
func createOrUpdateIssueNotifications(e Engine, issueID, commentID int64, notificationAuthorID int64) error {
|
||||
issueWatches, err := getIssueWatchers(e, issueID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
issue, err := getIssueByID(e, issueID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -83,7 +90,7 @@ func createOrUpdateIssueNotifications(e Engine, issue *Issue, notificationAuthor
|
||||
return err
|
||||
}
|
||||
|
||||
notifications, err := getNotificationsByIssueID(e, issue.ID)
|
||||
notifications, err := getNotificationsByIssueID(e, issueID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -102,9 +109,9 @@ func createOrUpdateIssueNotifications(e Engine, issue *Issue, notificationAuthor
|
||||
alreadyNotified[userID] = struct{}{}
|
||||
|
||||
if notificationExists(notifications, issue.ID, userID) {
|
||||
return updateIssueNotification(e, userID, issue.ID, notificationAuthorID)
|
||||
return updateIssueNotification(e, userID, issue.ID, commentID, notificationAuthorID)
|
||||
}
|
||||
return createIssueNotification(e, userID, issue, notificationAuthorID)
|
||||
return createIssueNotification(e, userID, issue, commentID, notificationAuthorID)
|
||||
}
|
||||
|
||||
for _, issueWatch := range issueWatches {
|
||||
@@ -157,12 +164,13 @@ func notificationExists(notifications []*Notification, issueID, userID int64) bo
|
||||
return false
|
||||
}
|
||||
|
||||
func createIssueNotification(e Engine, userID int64, issue *Issue, updatedByID int64) error {
|
||||
func createIssueNotification(e Engine, userID int64, issue *Issue, commentID, updatedByID int64) error {
|
||||
notification := &Notification{
|
||||
UserID: userID,
|
||||
RepoID: issue.RepoID,
|
||||
Status: NotificationStatusUnread,
|
||||
IssueID: issue.ID,
|
||||
CommentID: commentID,
|
||||
UpdatedBy: updatedByID,
|
||||
}
|
||||
|
||||
@@ -176,16 +184,25 @@ func createIssueNotification(e Engine, userID int64, issue *Issue, updatedByID i
|
||||
return err
|
||||
}
|
||||
|
||||
func updateIssueNotification(e Engine, userID, issueID, updatedByID int64) error {
|
||||
func updateIssueNotification(e Engine, userID, issueID, commentID, updatedByID int64) error {
|
||||
notification, err := getIssueNotification(e, userID, issueID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
notification.Status = NotificationStatusUnread
|
||||
notification.UpdatedBy = updatedByID
|
||||
// NOTICE: Only update comment id when the before notification on this issue is read, otherwise you may miss some old comments.
|
||||
// But we need update update_by so that the notification will be reorder
|
||||
var cols []string
|
||||
if notification.Status == NotificationStatusRead {
|
||||
notification.Status = NotificationStatusUnread
|
||||
notification.CommentID = commentID
|
||||
cols = []string{"status", "update_by", "comment_id"}
|
||||
} else {
|
||||
notification.UpdatedBy = updatedByID
|
||||
cols = []string{"update_by"}
|
||||
}
|
||||
|
||||
_, err = e.ID(notification.ID).Update(notification)
|
||||
_, err = e.ID(notification.ID).Cols(cols...).Update(notification)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -199,7 +216,7 @@ func getIssueNotification(e Engine, userID, issueID int64) (*Notification, error
|
||||
}
|
||||
|
||||
// NotificationsForUser returns notifications for a given user and status
|
||||
func NotificationsForUser(user *User, statuses []NotificationStatus, page, perPage int) ([]*Notification, error) {
|
||||
func NotificationsForUser(user *User, statuses []NotificationStatus, page, perPage int) (NotificationList, error) {
|
||||
return notificationsForUser(x, user, statuses, page, perPage)
|
||||
}
|
||||
|
||||
@@ -239,6 +256,204 @@ func (n *Notification) GetIssue() (*Issue, error) {
|
||||
return n.Issue, err
|
||||
}
|
||||
|
||||
// HTMLURL formats a URL-string to the notification
|
||||
func (n *Notification) HTMLURL() string {
|
||||
if n.Comment != nil {
|
||||
return n.Comment.HTMLURL()
|
||||
}
|
||||
return n.Issue.HTMLURL()
|
||||
}
|
||||
|
||||
// NotificationList contains a list of notifications
|
||||
type NotificationList []*Notification
|
||||
|
||||
func (nl NotificationList) getPendingRepoIDs() []int64 {
|
||||
var ids = make(map[int64]struct{}, len(nl))
|
||||
for _, notification := range nl {
|
||||
if notification.Repository != nil {
|
||||
continue
|
||||
}
|
||||
if _, ok := ids[notification.RepoID]; !ok {
|
||||
ids[notification.RepoID] = struct{}{}
|
||||
}
|
||||
}
|
||||
return keysInt64(ids)
|
||||
}
|
||||
|
||||
// LoadRepos loads repositories from database
|
||||
func (nl NotificationList) LoadRepos() (RepositoryList, error) {
|
||||
if len(nl) == 0 {
|
||||
return RepositoryList{}, nil
|
||||
}
|
||||
|
||||
var repoIDs = nl.getPendingRepoIDs()
|
||||
var repos = make(map[int64]*Repository, len(repoIDs))
|
||||
var left = len(repoIDs)
|
||||
for left > 0 {
|
||||
var limit = defaultMaxInSize
|
||||
if left < limit {
|
||||
limit = left
|
||||
}
|
||||
rows, err := x.
|
||||
In("id", repoIDs[:limit]).
|
||||
Rows(new(Repository))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var repo Repository
|
||||
err = rows.Scan(&repo)
|
||||
if err != nil {
|
||||
rows.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repos[repo.ID] = &repo
|
||||
}
|
||||
_ = rows.Close()
|
||||
|
||||
left -= limit
|
||||
repoIDs = repoIDs[limit:]
|
||||
}
|
||||
|
||||
var reposList = make(RepositoryList, 0, len(repoIDs))
|
||||
for _, notification := range nl {
|
||||
if notification.Repository == nil {
|
||||
notification.Repository = repos[notification.RepoID]
|
||||
}
|
||||
var found bool
|
||||
for _, r := range reposList {
|
||||
if r.ID == notification.Repository.ID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
reposList = append(reposList, notification.Repository)
|
||||
}
|
||||
}
|
||||
return reposList, nil
|
||||
}
|
||||
|
||||
func (nl NotificationList) getPendingIssueIDs() []int64 {
|
||||
var ids = make(map[int64]struct{}, len(nl))
|
||||
for _, notification := range nl {
|
||||
if notification.Issue != nil {
|
||||
continue
|
||||
}
|
||||
if _, ok := ids[notification.IssueID]; !ok {
|
||||
ids[notification.IssueID] = struct{}{}
|
||||
}
|
||||
}
|
||||
return keysInt64(ids)
|
||||
}
|
||||
|
||||
// LoadIssues loads issues from database
|
||||
func (nl NotificationList) LoadIssues() error {
|
||||
if len(nl) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var issueIDs = nl.getPendingIssueIDs()
|
||||
var issues = make(map[int64]*Issue, len(issueIDs))
|
||||
var left = len(issueIDs)
|
||||
for left > 0 {
|
||||
var limit = defaultMaxInSize
|
||||
if left < limit {
|
||||
limit = left
|
||||
}
|
||||
rows, err := x.
|
||||
In("id", issueIDs[:limit]).
|
||||
Rows(new(Issue))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var issue Issue
|
||||
err = rows.Scan(&issue)
|
||||
if err != nil {
|
||||
rows.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
issues[issue.ID] = &issue
|
||||
}
|
||||
_ = rows.Close()
|
||||
|
||||
left -= limit
|
||||
issueIDs = issueIDs[limit:]
|
||||
}
|
||||
|
||||
for _, notification := range nl {
|
||||
if notification.Issue == nil {
|
||||
notification.Issue = issues[notification.IssueID]
|
||||
notification.Issue.Repo = notification.Repository
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nl NotificationList) getPendingCommentIDs() []int64 {
|
||||
var ids = make(map[int64]struct{}, len(nl))
|
||||
for _, notification := range nl {
|
||||
if notification.CommentID == 0 || notification.Comment != nil {
|
||||
continue
|
||||
}
|
||||
if _, ok := ids[notification.CommentID]; !ok {
|
||||
ids[notification.CommentID] = struct{}{}
|
||||
}
|
||||
}
|
||||
return keysInt64(ids)
|
||||
}
|
||||
|
||||
// LoadComments loads comments from database
|
||||
func (nl NotificationList) LoadComments() error {
|
||||
if len(nl) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var commentIDs = nl.getPendingCommentIDs()
|
||||
var comments = make(map[int64]*Comment, len(commentIDs))
|
||||
var left = len(commentIDs)
|
||||
for left > 0 {
|
||||
var limit = defaultMaxInSize
|
||||
if left < limit {
|
||||
limit = left
|
||||
}
|
||||
rows, err := x.
|
||||
In("id", commentIDs[:limit]).
|
||||
Rows(new(Comment))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var comment Comment
|
||||
err = rows.Scan(&comment)
|
||||
if err != nil {
|
||||
rows.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
comments[comment.ID] = &comment
|
||||
}
|
||||
_ = rows.Close()
|
||||
|
||||
left -= limit
|
||||
commentIDs = commentIDs[limit:]
|
||||
}
|
||||
|
||||
for _, notification := range nl {
|
||||
if notification.CommentID > 0 && notification.Comment == nil {
|
||||
notification.Comment = comments[notification.CommentID]
|
||||
notification.Comment.Issue = notification.Issue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetNotificationCount returns the notification count for user
|
||||
func GetNotificationCount(user *User, status NotificationStatus) (int64, error) {
|
||||
return getNotificationCount(x, user, status)
|
||||
|
||||
@@ -14,7 +14,7 @@ func TestCreateOrUpdateIssueNotifications(t *testing.T) {
|
||||
assert.NoError(t, PrepareTestDatabase())
|
||||
issue := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
|
||||
|
||||
assert.NoError(t, CreateOrUpdateIssueNotifications(issue, 2))
|
||||
assert.NoError(t, CreateOrUpdateIssueNotifications(issue.ID, 0, 2))
|
||||
|
||||
// User 9 is inactive, thus notifications for user 1 and 4 are created
|
||||
notf := AssertExistsAndLoadBean(t, &Notification{UserID: 1, IssueID: issue.ID}).(*Notification)
|
||||
|
||||
@@ -380,6 +380,7 @@ func (pr *PullRequest) GetLastCommitStatus() (status *CommitStatus, err error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer headGitRepo.Close()
|
||||
|
||||
lastCommitID, err := headGitRepo.GetBranchCommitID(pr.HeadBranch)
|
||||
if err != nil {
|
||||
@@ -569,6 +570,7 @@ func (pr *PullRequest) getMergeCommit() (*git.Commit, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("OpenRepository: %v", err)
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
|
||||
commit, err := gitRepo.GetCommit(mergeCommit[:40])
|
||||
if err != nil {
|
||||
@@ -870,6 +872,7 @@ func (pr *PullRequest) UpdatePatch() (err error) {
|
||||
if err != nil {
|
||||
return fmt.Errorf("OpenRepository: %v", err)
|
||||
}
|
||||
defer headGitRepo.Close()
|
||||
|
||||
// Add a temporary remote.
|
||||
tmpRemote := com.ToStr(time.Now().UnixNano())
|
||||
@@ -911,6 +914,7 @@ func (pr *PullRequest) PushToBaseRepo() (err error) {
|
||||
if err != nil {
|
||||
return fmt.Errorf("OpenRepository: %v", err)
|
||||
}
|
||||
defer headGitRepo.Close()
|
||||
|
||||
tmpRemoteName := fmt.Sprintf("tmp-pull-%d", pr.ID)
|
||||
if err = headGitRepo.AddRemote(tmpRemoteName, pr.BaseRepo.RepoPath(), false); err != nil {
|
||||
|
||||
+213
-28
@@ -36,6 +36,7 @@ import (
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/sync"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/mcuadros/go-version"
|
||||
"github.com/unknwon/com"
|
||||
@@ -178,6 +179,9 @@ type Repository struct {
|
||||
IsFork bool `xorm:"INDEX NOT NULL DEFAULT false"`
|
||||
ForkID int64 `xorm:"INDEX"`
|
||||
BaseRepo *Repository `xorm:"-"`
|
||||
IsTemplate bool `xorm:"INDEX NOT NULL DEFAULT false"`
|
||||
TemplateID int64 `xorm:"INDEX"`
|
||||
TemplateRepo *Repository `xorm:"-"`
|
||||
Size int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
IndexerStatus *RepoIndexerStatus `xorm:"-"`
|
||||
IsFsckEnabled bool `xorm:"NOT NULL DEFAULT true"`
|
||||
@@ -350,6 +354,7 @@ func (repo *Repository) innerAPIFormat(e Engine, mode AccessMode, isParent bool)
|
||||
FullName: repo.FullName(),
|
||||
Description: repo.Description,
|
||||
Private: repo.IsPrivate,
|
||||
Template: repo.IsTemplate,
|
||||
Empty: repo.IsEmpty,
|
||||
Archived: repo.IsArchived,
|
||||
Size: int(repo.Size / 1024),
|
||||
@@ -662,6 +667,27 @@ func (repo *Repository) getBaseRepo(e Engine) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// IsGenerated returns whether _this_ repository was generated from a template
|
||||
func (repo *Repository) IsGenerated() bool {
|
||||
return repo.TemplateID != 0
|
||||
}
|
||||
|
||||
// GetTemplateRepo populates repo.TemplateRepo for a generated repository and
|
||||
// returns an error on failure (NOTE: no error is returned for
|
||||
// non-generated repositories, and TemplateRepo will be left untouched)
|
||||
func (repo *Repository) GetTemplateRepo() (err error) {
|
||||
return repo.getTemplateRepo(x)
|
||||
}
|
||||
|
||||
func (repo *Repository) getTemplateRepo(e Engine) (err error) {
|
||||
if !repo.IsGenerated() {
|
||||
return nil
|
||||
}
|
||||
|
||||
repo.TemplateRepo, err = getRepositoryByID(e, repo.TemplateID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (repo *Repository) repoPath(e Engine) string {
|
||||
return RepoPath(repo.mustOwnerName(e), repo.Name)
|
||||
}
|
||||
@@ -708,17 +734,17 @@ func (repo *Repository) IsOwnedBy(userID int64) bool {
|
||||
}
|
||||
|
||||
func (repo *Repository) updateSize(e Engine) error {
|
||||
repoInfoSize, err := git.GetRepoSize(repo.repoPath(e))
|
||||
size, err := util.GetDirectorySize(repo.repoPath(e))
|
||||
if err != nil {
|
||||
return fmt.Errorf("UpdateSize: %v", err)
|
||||
}
|
||||
|
||||
repo.Size = repoInfoSize.Size + repoInfoSize.SizePack
|
||||
repo.Size = size
|
||||
_, err = e.ID(repo.ID).Cols("size").Update(repo)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateSize updates the repository size, calculating it using git.GetRepoSize
|
||||
// UpdateSize updates the repository size, calculating it using util.GetDirectorySize
|
||||
func (repo *Repository) UpdateSize() error {
|
||||
return repo.updateSize(x)
|
||||
}
|
||||
@@ -1021,6 +1047,7 @@ func MigrateRepositoryGitData(doer, u *User, repo *Repository, opts api.MigrateR
|
||||
if err != nil {
|
||||
return repo, fmt.Errorf("OpenRepository: %v", err)
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
|
||||
repo.IsEmpty, err = gitRepo.IsEmpty()
|
||||
if err != nil {
|
||||
@@ -1219,6 +1246,20 @@ type CreateRepoOptions struct {
|
||||
Status RepositoryStatus
|
||||
}
|
||||
|
||||
// GenerateRepoOptions contains the template units to generate
|
||||
type GenerateRepoOptions struct {
|
||||
Name string
|
||||
Description string
|
||||
Private bool
|
||||
GitContent bool
|
||||
Topics bool
|
||||
}
|
||||
|
||||
// IsValid checks whether at least one option is chosen for generation
|
||||
func (gro GenerateRepoOptions) IsValid() bool {
|
||||
return gro.GitContent || gro.Topics // or other items as they are added
|
||||
}
|
||||
|
||||
func getRepoInitFile(tp, name string) ([]byte, error) {
|
||||
cleanedName := strings.TrimLeft(path.Clean("/"+name), "/")
|
||||
relPath := path.Join("options", tp, cleanedName)
|
||||
@@ -1322,8 +1363,55 @@ func prepareRepoCommit(e Engine, repo *Repository, tmpDir, repoPath string, opts
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitRepository initializes README and .gitignore if needed.
|
||||
func initRepository(e Engine, repoPath string, u *User, repo *Repository, opts CreateRepoOptions) (err error) {
|
||||
func generateRepoCommit(e Engine, repo, templateRepo *Repository, tmpDir string) error {
|
||||
commitTimeStr := time.Now().Format(time.RFC3339)
|
||||
authorSig := repo.Owner.NewGitSig()
|
||||
|
||||
// Because this may call hooks we should pass in the environment
|
||||
env := append(os.Environ(),
|
||||
"GIT_AUTHOR_NAME="+authorSig.Name,
|
||||
"GIT_AUTHOR_EMAIL="+authorSig.Email,
|
||||
"GIT_AUTHOR_DATE="+commitTimeStr,
|
||||
"GIT_COMMITTER_NAME="+authorSig.Name,
|
||||
"GIT_COMMITTER_EMAIL="+authorSig.Email,
|
||||
"GIT_COMMITTER_DATE="+commitTimeStr,
|
||||
)
|
||||
|
||||
// Clone to temporary path and do the init commit.
|
||||
templateRepoPath := templateRepo.repoPath(e)
|
||||
_, stderr, err := process.GetManager().ExecDirEnv(
|
||||
-1, "",
|
||||
fmt.Sprintf("generateRepoCommit(git clone): %s", templateRepoPath),
|
||||
env,
|
||||
git.GitExecutable, "clone", "--depth", "1", templateRepoPath, tmpDir,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("git clone: %v - %s", err, stderr)
|
||||
}
|
||||
|
||||
if err := os.RemoveAll(path.Join(tmpDir, ".git")); err != nil {
|
||||
return fmt.Errorf("remove git dir: %v", err)
|
||||
}
|
||||
|
||||
if err := git.InitRepository(tmpDir, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repoPath := repo.repoPath(e)
|
||||
_, stderr, err = process.GetManager().ExecDirEnv(
|
||||
-1, tmpDir,
|
||||
fmt.Sprintf("generateRepoCommit(git remote add): %s", repoPath),
|
||||
env,
|
||||
git.GitExecutable, "remote", "add", "origin", repoPath,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("git remote add: %v - %s", err, stderr)
|
||||
}
|
||||
|
||||
return initRepoCommit(tmpDir, repo.Owner)
|
||||
}
|
||||
|
||||
func checkInitRepository(repoPath string) (err error) {
|
||||
// Somehow the directory could exist.
|
||||
if com.IsExist(repoPath) {
|
||||
return fmt.Errorf("initRepository: path already exists: %s", repoPath)
|
||||
@@ -1335,6 +1423,14 @@ func initRepository(e Engine, repoPath string, u *User, repo *Repository, opts C
|
||||
} else if err = createDelegateHooks(repoPath); err != nil {
|
||||
return fmt.Errorf("createDelegateHooks: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitRepository initializes README and .gitignore if needed.
|
||||
func initRepository(e Engine, repoPath string, u *User, repo *Repository, opts CreateRepoOptions) (err error) {
|
||||
if err = checkInitRepository(repoPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpDir := filepath.Join(os.TempDir(), "gitea-"+repo.Name+"-"+com.ToStr(time.Now().Nanosecond()))
|
||||
|
||||
@@ -1375,6 +1471,37 @@ func initRepository(e Engine, repoPath string, u *User, repo *Repository, opts C
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateRepository initializes repository from template
|
||||
func generateRepository(e Engine, repo, templateRepo *Repository) (err error) {
|
||||
tmpDir := filepath.Join(os.TempDir(), "gitea-"+repo.Name+"-"+com.ToStr(time.Now().Nanosecond()))
|
||||
|
||||
if err := os.MkdirAll(tmpDir, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("Failed to create dir %s: %v", tmpDir, err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := os.RemoveAll(tmpDir); err != nil {
|
||||
log.Error("RemoveAll: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if err = generateRepoCommit(e, repo, templateRepo, tmpDir); err != nil {
|
||||
return fmt.Errorf("generateRepoCommit: %v", err)
|
||||
}
|
||||
|
||||
// re-fetch repo
|
||||
if repo, err = getRepositoryByID(e, repo.ID); err != nil {
|
||||
return fmt.Errorf("getRepositoryByID: %v", err)
|
||||
}
|
||||
|
||||
repo.DefaultBranch = "master"
|
||||
if err = updateRepository(e, repo, false); err != nil {
|
||||
return fmt.Errorf("updateRepository: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
reservedRepoNames = []string{".", ".."}
|
||||
reservedRepoPatterns = []string{"*.git", "*.wiki"}
|
||||
@@ -1469,16 +1596,6 @@ func createRepository(e *xorm.Session, doer, u *User, repo *Repository) (err err
|
||||
return fmt.Errorf("watchRepo: %v", err)
|
||||
}
|
||||
}
|
||||
if err = notifyWatchers(e, &Action{
|
||||
ActUserID: doer.ID,
|
||||
ActUser: doer,
|
||||
OpType: ActionCreateRepo,
|
||||
RepoID: repo.ID,
|
||||
Repo: repo,
|
||||
IsPrivate: repo.IsPrivate,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("notify watchers '%d/%d': %v", doer.ID, repo.ID, err)
|
||||
}
|
||||
|
||||
if err = copyDefaultWebhooksToRepo(e, repo.ID); err != nil {
|
||||
return fmt.Errorf("copyDefaultWebhooksToRepo: %v", err)
|
||||
@@ -2410,8 +2527,8 @@ func CheckRepoStats() {
|
||||
checkers := []*repoChecker{
|
||||
// Repository.NumWatches
|
||||
{
|
||||
"SELECT repo.id FROM `repository` repo WHERE repo.num_watches!=(SELECT COUNT(*) FROM `watch` WHERE repo_id=repo.id)",
|
||||
"UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=?) WHERE id=?",
|
||||
"SELECT repo.id FROM `repository` repo WHERE repo.num_watches!=(SELECT COUNT(*) FROM `watch` WHERE repo_id=repo.id AND mode<>2)",
|
||||
"UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=? AND mode<>2) WHERE id=?",
|
||||
"repository count 'num_watches'",
|
||||
},
|
||||
// Repository.NumStars
|
||||
@@ -2532,6 +2649,28 @@ func HasForkedRepo(ownerID, repoID int64) (*Repository, bool) {
|
||||
return repo, has
|
||||
}
|
||||
|
||||
// CopyLFS copies LFS data from one repo to another
|
||||
func CopyLFS(newRepo, oldRepo *Repository) error {
|
||||
return copyLFS(x, newRepo, oldRepo)
|
||||
}
|
||||
|
||||
func copyLFS(e Engine, newRepo, oldRepo *Repository) error {
|
||||
var lfsObjects []*LFSMetaObject
|
||||
if err := e.Where("repository_id=?", oldRepo.ID).Find(&lfsObjects); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, v := range lfsObjects {
|
||||
v.ID = 0
|
||||
v.RepositoryID = newRepo.ID
|
||||
if _, err := e.Insert(v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ForkRepository forks a repository
|
||||
func ForkRepository(doer, owner *User, oldRepo *Repository, name, desc string) (_ *Repository, err error) {
|
||||
forkedRepo, err := oldRepo.GetUserFork(owner.ID)
|
||||
@@ -2602,27 +2741,73 @@ func ForkRepository(doer, owner *User, oldRepo *Repository, name, desc string) (
|
||||
log.Error("Failed to update size for repository: %v", err)
|
||||
}
|
||||
|
||||
// Copy LFS meta objects in new session
|
||||
sess2 := x.NewSession()
|
||||
defer sess2.Close()
|
||||
if err = sess2.Begin(); err != nil {
|
||||
return repo, CopyLFS(repo, oldRepo)
|
||||
}
|
||||
|
||||
// GenerateRepository generates a repository from a template
|
||||
func GenerateRepository(doer, owner *User, templateRepo *Repository, opts GenerateRepoOptions) (_ *Repository, err error) {
|
||||
repo := &Repository{
|
||||
OwnerID: owner.ID,
|
||||
Owner: owner,
|
||||
Name: opts.Name,
|
||||
LowerName: strings.ToLower(opts.Name),
|
||||
Description: opts.Description,
|
||||
IsPrivate: opts.Private,
|
||||
IsEmpty: !opts.GitContent || templateRepo.IsEmpty,
|
||||
IsFsckEnabled: templateRepo.IsFsckEnabled,
|
||||
TemplateID: templateRepo.ID,
|
||||
}
|
||||
|
||||
createSess := x.NewSession()
|
||||
defer createSess.Close()
|
||||
if err = createSess.Begin(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = createRepository(createSess, doer, owner, repo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//Commit repo to get created repo ID
|
||||
err = createSess.Commit()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
if err = sess.Begin(); err != nil {
|
||||
return repo, err
|
||||
}
|
||||
|
||||
var lfsObjects []*LFSMetaObject
|
||||
if err = sess2.Where("repository_id=?", oldRepo.ID).Find(&lfsObjects); err != nil {
|
||||
repoPath := RepoPath(owner.Name, repo.Name)
|
||||
if err = checkInitRepository(repoPath); err != nil {
|
||||
return repo, err
|
||||
}
|
||||
|
||||
for _, v := range lfsObjects {
|
||||
v.ID = 0
|
||||
v.RepositoryID = repo.ID
|
||||
if _, err = sess2.Insert(v); err != nil {
|
||||
if opts.GitContent && !templateRepo.IsEmpty {
|
||||
if err = generateRepository(sess, repo, templateRepo); err != nil {
|
||||
return repo, err
|
||||
}
|
||||
|
||||
if err = repo.updateSize(sess); err != nil {
|
||||
return repo, fmt.Errorf("failed to update size for repository: %v", err)
|
||||
}
|
||||
|
||||
if err = copyLFS(sess, repo, templateRepo); err != nil {
|
||||
return repo, fmt.Errorf("failed to copy LFS: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return repo, sess2.Commit()
|
||||
if opts.Topics {
|
||||
for _, topic := range templateRepo.Topics {
|
||||
if _, err = addTopicByNameToRepo(sess, repo.ID, topic); err != nil {
|
||||
return repo, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return repo, sess.Commit()
|
||||
}
|
||||
|
||||
// GetForks returns all the forks of the repository
|
||||
|
||||
@@ -64,6 +64,8 @@ func GetActivityStats(repo *Repository, timeFrom time.Time, releases, issues, pr
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("OpenRepository: %v", err)
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
|
||||
code, err := gitRepo.GetCodeActivityStats(timeFrom, repo.DefaultBranch)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("FillFromGit: %v", err)
|
||||
@@ -79,6 +81,8 @@ func GetActivityStatsTopAuthors(repo *Repository, timeFrom time.Time, count int)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("OpenRepository: %v", err)
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
|
||||
code, err := gitRepo.GetCodeActivityStats(timeFrom, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("FillFromGit: %v", err)
|
||||
|
||||
@@ -23,6 +23,7 @@ func (repo *Repository) GetBranch(branch string) (*git.Branch, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
|
||||
return gitRepo.GetBranch(branch)
|
||||
}
|
||||
@@ -38,6 +39,7 @@ func (repo *Repository) CheckBranchName(name string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
|
||||
branches, err := repo.GetBranches()
|
||||
if err != nil {
|
||||
@@ -94,6 +96,7 @@ func (repo *Repository) CreateNewBranch(doer *User, oldBranchName, branchName st
|
||||
log.Error("Unable to open temporary repository: %s (%v)", basePath, err)
|
||||
return fmt.Errorf("Failed to open new temporary repository in: %s %v", basePath, err)
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
|
||||
if err = gitRepo.CreateBranch(branchName, oldBranchName); err != nil {
|
||||
log.Error("Unable to create branch: %s from %s. (%v)", branchName, oldBranchName, err)
|
||||
@@ -140,6 +143,7 @@ func (repo *Repository) CreateNewBranchFromCommit(doer *User, commit, branchName
|
||||
log.Error("Unable to open temporary repository: %s (%v)", basePath, err)
|
||||
return fmt.Errorf("Failed to open new temporary repository in: %s %v", basePath, err)
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
|
||||
if err = gitRepo.CreateBranch(branchName, commit); err != nil {
|
||||
log.Error("Unable to create branch: %s from %s. (%v)", branchName, commit, err)
|
||||
|
||||
+33
-16
@@ -111,17 +111,18 @@ func (repos MirrorRepositoryList) LoadAttributes() error {
|
||||
|
||||
// SearchRepoOptions holds the search options
|
||||
type SearchRepoOptions struct {
|
||||
UserID int64
|
||||
UserIsAdmin bool
|
||||
Keyword string
|
||||
OwnerID int64
|
||||
OrderBy SearchOrderBy
|
||||
Private bool // Include private repositories in results
|
||||
StarredByID int64
|
||||
Page int
|
||||
IsProfile bool
|
||||
AllPublic bool // Include also all public repositories
|
||||
PageSize int // Can be smaller than or equal to setting.ExplorePagingNum
|
||||
UserID int64
|
||||
UserIsAdmin bool
|
||||
Keyword string
|
||||
OwnerID int64
|
||||
PriorityOwnerID int64
|
||||
OrderBy SearchOrderBy
|
||||
Private bool // Include private repositories in results
|
||||
StarredByID int64
|
||||
Page int
|
||||
IsProfile bool
|
||||
AllPublic bool // Include also all public repositories
|
||||
PageSize int // Can be smaller than or equal to setting.ExplorePagingNum
|
||||
// None -> include collaborative AND non-collaborative
|
||||
// True -> include just collaborative
|
||||
// False -> incude just non-collaborative
|
||||
@@ -130,6 +131,10 @@ type SearchRepoOptions struct {
|
||||
// True -> include just forks
|
||||
// False -> include just non-forks
|
||||
Fork util.OptionalBool
|
||||
// None -> include templates AND non-templates
|
||||
// True -> include just templates
|
||||
// False -> include just non-templates
|
||||
Template util.OptionalBool
|
||||
// None -> include mirrors AND non-mirrors
|
||||
// True -> include just mirrors
|
||||
// False -> include just non-mirrors
|
||||
@@ -190,6 +195,10 @@ func SearchRepository(opts *SearchRepoOptions) (RepositoryList, int64, error) {
|
||||
cond = cond.And(accessCond)
|
||||
}
|
||||
|
||||
if opts.Template != util.OptionalBoolNone {
|
||||
cond = cond.And(builder.Eq{"is_template": opts.Template == util.OptionalBoolTrue})
|
||||
}
|
||||
|
||||
// Restrict to starred repositories
|
||||
if opts.StarredByID > 0 {
|
||||
cond = cond.And(builder.In("id", builder.Select("repo_id").From("star").Where(builder.Eq{"uid": opts.StarredByID})))
|
||||
@@ -266,6 +275,10 @@ func SearchRepository(opts *SearchRepoOptions) (RepositoryList, int64, error) {
|
||||
opts.OrderBy = SearchOrderByAlphabetically
|
||||
}
|
||||
|
||||
if opts.PriorityOwnerID > 0 {
|
||||
opts.OrderBy = SearchOrderBy(fmt.Sprintf("CASE WHEN owner_id = %d THEN 0 ELSE owner_id END, %s", opts.PriorityOwnerID, opts.OrderBy))
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
|
||||
@@ -308,11 +321,15 @@ func accessibleRepositoryCondition(userID int64) builder.Cond {
|
||||
builder.NotIn("`repository`.owner_id", builder.Select("id").From("`user`").Where(builder.Eq{"visibility": structs.VisibleTypePrivate}))),
|
||||
),
|
||||
// 2. Be able to see all repositories that we have access to
|
||||
builder.In("`repository`.id", builder.Select("repo_id").
|
||||
From("`access`").
|
||||
Where(builder.And(
|
||||
builder.Eq{"user_id": userID},
|
||||
builder.Gt{"mode": int(AccessModeNone)}))),
|
||||
builder.Or(
|
||||
builder.In("`repository`.id", builder.Select("repo_id").
|
||||
From("`access`").
|
||||
Where(builder.And(
|
||||
builder.Eq{"user_id": userID},
|
||||
builder.Gt{"mode": int(AccessModeNone)}))),
|
||||
builder.In("`repository`.id", builder.Select("id").
|
||||
From("`repository`").
|
||||
Where(builder.Eq{"owner_id": userID}))),
|
||||
// 3. Be able to see all repositories that we are in a team
|
||||
builder.In("`repository`.id", builder.Select("`team_repo`.repo_id").
|
||||
From("team_repo").
|
||||
|
||||
@@ -174,10 +174,10 @@ func TestSearchRepository(t *testing.T) {
|
||||
opts: &SearchRepoOptions{Keyword: "big_test_", Page: 1, PageSize: 10, Private: true, AllPublic: true, Collaborate: util.OptionalBoolFalse},
|
||||
count: 14},
|
||||
{name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
|
||||
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, AllPublic: true},
|
||||
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, AllPublic: true, Template: util.OptionalBoolFalse},
|
||||
count: 22},
|
||||
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
|
||||
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
|
||||
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true, Template: util.OptionalBoolFalse},
|
||||
count: 28},
|
||||
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
|
||||
opts: &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
|
||||
@@ -186,8 +186,11 @@ func TestSearchRepository(t *testing.T) {
|
||||
opts: &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 18, Private: true, AllPublic: true},
|
||||
count: 13},
|
||||
{name: "AllPublic/PublicRepositoriesOfOrganization",
|
||||
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse},
|
||||
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse, Template: util.OptionalBoolFalse},
|
||||
count: 22},
|
||||
{name: "AllTemplates",
|
||||
opts: &SearchRepoOptions{Page: 1, PageSize: 10, Template: util.OptionalBoolTrue},
|
||||
count: 2},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
|
||||
@@ -149,6 +149,7 @@ func (repo *Repository) SignWikiCommit(u *User) (bool, string) {
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
commit, err := gitRepo.GetCommit("HEAD")
|
||||
if err != nil {
|
||||
return false, ""
|
||||
@@ -194,6 +195,7 @@ func (repo *Repository) SignCRUDAction(u *User, tmpBasePath, parentCommit string
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
commit, err := gitRepo.GetCommit(parentCommit)
|
||||
if err != nil {
|
||||
return false, ""
|
||||
@@ -242,6 +244,7 @@ func (repo *Repository) SignMerge(u *User, tmpBasePath, baseCommit, headCommit s
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
}
|
||||
commit, err := gitRepo.GetCommit(baseCommit)
|
||||
if err != nil {
|
||||
@@ -257,6 +260,7 @@ func (repo *Repository) SignMerge(u *User, tmpBasePath, baseCommit, headCommit s
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
}
|
||||
commit, err := gitRepo.GetCommit(headCommit)
|
||||
if err != nil {
|
||||
@@ -272,6 +276,7 @@ func (repo *Repository) SignMerge(u *User, tmpBasePath, baseCommit, headCommit s
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
}
|
||||
commit, err := gitRepo.GetCommit(headCommit)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
)
|
||||
|
||||
// GetTagsByPath returns repo tags by its path
|
||||
func GetTagsByPath(path string) ([]*git.Tag, error) {
|
||||
gitRepo, err := git.OpenRepository(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return gitRepo.GetTagInfos()
|
||||
}
|
||||
|
||||
// GetTags return repo's tags
|
||||
func (repo *Repository) GetTags() ([]*git.Tag, error) {
|
||||
return GetTagsByPath(repo.RepoPath())
|
||||
}
|
||||
+120
-23
@@ -4,42 +4,118 @@
|
||||
|
||||
package models
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
// RepoWatchMode specifies what kind of watch the user has on a repository
|
||||
type RepoWatchMode int8
|
||||
|
||||
const (
|
||||
// RepoWatchModeNone don't watch
|
||||
RepoWatchModeNone RepoWatchMode = iota // 0
|
||||
// RepoWatchModeNormal watch repository (from other sources)
|
||||
RepoWatchModeNormal // 1
|
||||
// RepoWatchModeDont explicit don't auto-watch
|
||||
RepoWatchModeDont // 2
|
||||
// RepoWatchModeAuto watch repository (from AutoWatchOnChanges)
|
||||
RepoWatchModeAuto // 3
|
||||
)
|
||||
|
||||
// Watch is connection request for receiving repository notification.
|
||||
type Watch struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UserID int64 `xorm:"UNIQUE(watch)"`
|
||||
RepoID int64 `xorm:"UNIQUE(watch)"`
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UserID int64 `xorm:"UNIQUE(watch)"`
|
||||
RepoID int64 `xorm:"UNIQUE(watch)"`
|
||||
Mode RepoWatchMode `xorm:"SMALLINT NOT NULL DEFAULT 1"`
|
||||
}
|
||||
|
||||
func isWatching(e Engine, userID, repoID int64) bool {
|
||||
has, _ := e.Get(&Watch{UserID: userID, RepoID: repoID})
|
||||
return has
|
||||
// getWatch gets what kind of subscription a user has on a given repository; returns dummy record if none found
|
||||
func getWatch(e Engine, userID, repoID int64) (Watch, error) {
|
||||
watch := Watch{UserID: userID, RepoID: repoID}
|
||||
has, err := e.Get(&watch)
|
||||
if err != nil {
|
||||
return watch, err
|
||||
}
|
||||
if !has {
|
||||
watch.Mode = RepoWatchModeNone
|
||||
}
|
||||
return watch, nil
|
||||
}
|
||||
|
||||
// Decodes watchability of RepoWatchMode
|
||||
func isWatchMode(mode RepoWatchMode) bool {
|
||||
return mode != RepoWatchModeNone && mode != RepoWatchModeDont
|
||||
}
|
||||
|
||||
// IsWatching checks if user has watched given repository.
|
||||
func IsWatching(userID, repoID int64) bool {
|
||||
return isWatching(x, userID, repoID)
|
||||
watch, err := getWatch(x, userID, repoID)
|
||||
return err == nil && isWatchMode(watch.Mode)
|
||||
}
|
||||
|
||||
func watchRepo(e Engine, userID, repoID int64, watch bool) (err error) {
|
||||
if watch {
|
||||
if isWatching(e, userID, repoID) {
|
||||
return nil
|
||||
}
|
||||
if _, err = e.Insert(&Watch{RepoID: repoID, UserID: userID}); err != nil {
|
||||
func watchRepoMode(e Engine, watch Watch, mode RepoWatchMode) (err error) {
|
||||
if watch.Mode == mode {
|
||||
return nil
|
||||
}
|
||||
if mode == RepoWatchModeAuto && (watch.Mode == RepoWatchModeDont || isWatchMode(watch.Mode)) {
|
||||
// Don't auto watch if already watching or deliberately not watching
|
||||
return nil
|
||||
}
|
||||
|
||||
hadrec := watch.Mode != RepoWatchModeNone
|
||||
needsrec := mode != RepoWatchModeNone
|
||||
repodiff := 0
|
||||
|
||||
if isWatchMode(mode) && !isWatchMode(watch.Mode) {
|
||||
repodiff = 1
|
||||
} else if !isWatchMode(mode) && isWatchMode(watch.Mode) {
|
||||
repodiff = -1
|
||||
}
|
||||
|
||||
watch.Mode = mode
|
||||
|
||||
if !hadrec && needsrec {
|
||||
watch.Mode = mode
|
||||
if _, err = e.Insert(watch); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = e.Exec("UPDATE `repository` SET num_watches = num_watches + 1 WHERE id = ?", repoID)
|
||||
} else if needsrec {
|
||||
watch.Mode = mode
|
||||
if _, err := e.ID(watch.ID).AllCols().Update(watch); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if _, err = e.Delete(Watch{ID: watch.ID}); err != nil {
|
||||
return err
|
||||
}
|
||||
if repodiff != 0 {
|
||||
_, err = e.Exec("UPDATE `repository` SET num_watches = num_watches + ? WHERE id = ?", repodiff, watch.RepoID)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// WatchRepoMode watch repository in specific mode.
|
||||
func WatchRepoMode(userID, repoID int64, mode RepoWatchMode) (err error) {
|
||||
var watch Watch
|
||||
if watch, err = getWatch(x, userID, repoID); err != nil {
|
||||
return err
|
||||
}
|
||||
return watchRepoMode(x, watch, mode)
|
||||
}
|
||||
|
||||
func watchRepo(e Engine, userID, repoID int64, doWatch bool) (err error) {
|
||||
var watch Watch
|
||||
if watch, err = getWatch(e, userID, repoID); err != nil {
|
||||
return err
|
||||
}
|
||||
if !doWatch && watch.Mode == RepoWatchModeAuto {
|
||||
err = watchRepoMode(e, watch, RepoWatchModeDont)
|
||||
} else if !doWatch {
|
||||
err = watchRepoMode(e, watch, RepoWatchModeNone)
|
||||
} else {
|
||||
if !isWatching(e, userID, repoID) {
|
||||
return nil
|
||||
}
|
||||
if _, err = e.Delete(&Watch{0, userID, repoID}); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = e.Exec("UPDATE `repository` SET num_watches = num_watches - 1 WHERE id = ?", repoID)
|
||||
err = watchRepoMode(e, watch, RepoWatchModeNormal)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -52,6 +128,7 @@ func WatchRepo(userID, repoID int64, watch bool) (err error) {
|
||||
func getWatchers(e Engine, repoID int64) ([]*Watch, error) {
|
||||
watches := make([]*Watch, 0, 10)
|
||||
return watches, e.Where("`watch`.repo_id=?", repoID).
|
||||
And("`watch`.mode<>?", RepoWatchModeDont).
|
||||
And("`user`.is_active=?", true).
|
||||
And("`user`.prohibit_login=?", false).
|
||||
Join("INNER", "`user`", "`user`.id = `watch`.user_id").
|
||||
@@ -67,7 +144,8 @@ func GetWatchers(repoID int64) ([]*Watch, error) {
|
||||
func (repo *Repository) GetWatchers(page int) ([]*User, error) {
|
||||
users := make([]*User, 0, ItemsPerPage)
|
||||
sess := x.Where("watch.repo_id=?", repo.ID).
|
||||
Join("LEFT", "watch", "`user`.id=`watch`.user_id")
|
||||
Join("LEFT", "watch", "`user`.id=`watch`.user_id").
|
||||
And("`watch`.mode<>?", RepoWatchModeDont)
|
||||
if page > 0 {
|
||||
sess = sess.Limit(ItemsPerPage, (page-1)*ItemsPerPage)
|
||||
}
|
||||
@@ -137,3 +215,22 @@ func notifyWatchers(e Engine, act *Action) error {
|
||||
func NotifyWatchers(act *Action) error {
|
||||
return notifyWatchers(x, act)
|
||||
}
|
||||
|
||||
func watchIfAuto(e Engine, userID, repoID int64, isWrite bool) error {
|
||||
if !isWrite || !setting.Service.AutoWatchOnChanges {
|
||||
return nil
|
||||
}
|
||||
watch, err := getWatch(e, userID, repoID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if watch.Mode != RepoWatchModeNone {
|
||||
return nil
|
||||
}
|
||||
return watchRepoMode(e, watch, RepoWatchModeAuto)
|
||||
}
|
||||
|
||||
// WatchIfAuto subscribes to repo if AutoWatchOnChanges is set
|
||||
func WatchIfAuto(userID int64, repoID int64, isWrite bool) error {
|
||||
return watchIfAuto(x, userID, repoID, isWrite)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ package models
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -15,8 +17,10 @@ func TestIsWatching(t *testing.T) {
|
||||
|
||||
assert.True(t, IsWatching(1, 1))
|
||||
assert.True(t, IsWatching(4, 1))
|
||||
assert.True(t, IsWatching(11, 1))
|
||||
|
||||
assert.False(t, IsWatching(1, 5))
|
||||
assert.False(t, IsWatching(8, 1))
|
||||
assert.False(t, IsWatching(NonexistentID, NonexistentID))
|
||||
}
|
||||
|
||||
@@ -78,7 +82,7 @@ func TestNotifyWatchers(t *testing.T) {
|
||||
}
|
||||
assert.NoError(t, NotifyWatchers(action))
|
||||
|
||||
// One watchers are inactive, thus action is only created for user 8, 1, 4
|
||||
// One watchers are inactive, thus action is only created for user 8, 1, 4, 11
|
||||
AssertExistsAndLoadBean(t, &Action{
|
||||
ActUserID: action.ActUserID,
|
||||
UserID: 8,
|
||||
@@ -97,4 +101,88 @@ func TestNotifyWatchers(t *testing.T) {
|
||||
RepoID: action.RepoID,
|
||||
OpType: action.OpType,
|
||||
})
|
||||
AssertExistsAndLoadBean(t, &Action{
|
||||
ActUserID: action.ActUserID,
|
||||
UserID: 11,
|
||||
RepoID: action.RepoID,
|
||||
OpType: action.OpType,
|
||||
})
|
||||
}
|
||||
|
||||
func TestWatchIfAuto(t *testing.T) {
|
||||
assert.NoError(t, PrepareTestDatabase())
|
||||
|
||||
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
|
||||
watchers, err := repo.GetWatchers(1)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, watchers, repo.NumWatches)
|
||||
|
||||
setting.Service.AutoWatchOnChanges = false
|
||||
|
||||
prevCount := repo.NumWatches
|
||||
|
||||
// Must not add watch
|
||||
assert.NoError(t, WatchIfAuto(8, 1, true))
|
||||
watchers, err = repo.GetWatchers(1)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, watchers, prevCount)
|
||||
|
||||
// Should not add watch
|
||||
assert.NoError(t, WatchIfAuto(10, 1, true))
|
||||
watchers, err = repo.GetWatchers(1)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, watchers, prevCount)
|
||||
|
||||
setting.Service.AutoWatchOnChanges = true
|
||||
|
||||
// Must not add watch
|
||||
assert.NoError(t, WatchIfAuto(8, 1, true))
|
||||
watchers, err = repo.GetWatchers(1)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, watchers, prevCount)
|
||||
|
||||
// Should not add watch
|
||||
assert.NoError(t, WatchIfAuto(12, 1, false))
|
||||
watchers, err = repo.GetWatchers(1)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, watchers, prevCount)
|
||||
|
||||
// Should add watch
|
||||
assert.NoError(t, WatchIfAuto(12, 1, true))
|
||||
watchers, err = repo.GetWatchers(1)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, watchers, prevCount+1)
|
||||
|
||||
// Should remove watch, inhibit from adding auto
|
||||
assert.NoError(t, WatchRepo(12, 1, false))
|
||||
watchers, err = repo.GetWatchers(1)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, watchers, prevCount)
|
||||
|
||||
// Must not add watch
|
||||
assert.NoError(t, WatchIfAuto(12, 1, true))
|
||||
watchers, err = repo.GetWatchers(1)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, watchers, prevCount)
|
||||
}
|
||||
|
||||
func TestWatchRepoMode(t *testing.T) {
|
||||
assert.NoError(t, PrepareTestDatabase())
|
||||
|
||||
AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 0)
|
||||
|
||||
assert.NoError(t, WatchRepoMode(12, 1, RepoWatchModeAuto))
|
||||
AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 1)
|
||||
AssertCount(t, &Watch{UserID: 12, RepoID: 1, Mode: RepoWatchModeAuto}, 1)
|
||||
|
||||
assert.NoError(t, WatchRepoMode(12, 1, RepoWatchModeNormal))
|
||||
AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 1)
|
||||
AssertCount(t, &Watch{UserID: 12, RepoID: 1, Mode: RepoWatchModeNormal}, 1)
|
||||
|
||||
assert.NoError(t, WatchRepoMode(12, 1, RepoWatchModeDont))
|
||||
AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 1)
|
||||
AssertCount(t, &Watch{UserID: 12, RepoID: 1, Mode: RepoWatchModeDont}, 1)
|
||||
|
||||
assert.NoError(t, WatchRepoMode(12, 1, RepoWatchModeNone))
|
||||
AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 0)
|
||||
}
|
||||
|
||||
+2
-1
@@ -1082,7 +1082,7 @@ func deleteUser(e *xorm.Session, u *User) error {
|
||||
// ***** START: Watch *****
|
||||
watchedRepoIDs := make([]int64, 0, 10)
|
||||
if err = e.Table("watch").Cols("watch.repo_id").
|
||||
Where("watch.user_id = ?", u.ID).Find(&watchedRepoIDs); err != nil {
|
||||
Where("watch.user_id = ?", u.ID).And("watch.mode <>?", RepoWatchModeDont).Find(&watchedRepoIDs); err != nil {
|
||||
return fmt.Errorf("get all watches: %v", err)
|
||||
}
|
||||
if _, err = e.Decr("num_watches").In("id", watchedRepoIDs).NoAutoTime().Update(new(Repository)); err != nil {
|
||||
@@ -1543,6 +1543,7 @@ func GetStarredRepos(userID int64, private bool) ([]*Repository, error) {
|
||||
// GetWatchedRepos returns the repos watched by a particular user
|
||||
func GetWatchedRepos(userID int64, private bool) ([]*Repository, error) {
|
||||
sess := x.Where("watch.user_id=?", userID).
|
||||
And("`watch`.mode<>?", RepoWatchModeDont).
|
||||
Join("LEFT", "watch", "`repository`.id=`watch`.repo_id")
|
||||
if !private {
|
||||
sess = sess.And("is_private=?", false)
|
||||
|
||||
+14
-1
@@ -153,7 +153,7 @@ func TestSearchUsers(t *testing.T) {
|
||||
}
|
||||
|
||||
testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1},
|
||||
[]int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24})
|
||||
[]int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27})
|
||||
|
||||
testUserSuccess(&SearchUserOptions{Page: 1, IsActive: util.OptionalBoolFalse},
|
||||
[]int64{9})
|
||||
@@ -373,3 +373,16 @@ func TestCreateUser_Issue5882(t *testing.T) {
|
||||
assert.NoError(t, DeleteUser(v.user))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUserIDsByNames(t *testing.T) {
|
||||
|
||||
//ignore non existing
|
||||
IDs, err := GetUserIDsByNames([]string{"user1", "user2", "none_existing_user"}, true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []int64{1, 2}, IDs)
|
||||
|
||||
//ignore non existing
|
||||
IDs, err = GetUserIDsByNames([]string{"user1", "do_not_exist"}, false)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, []int64(nil), IDs)
|
||||
}
|
||||
|
||||
@@ -140,6 +140,7 @@ func (repo *Repository) updateWikiPage(doer *User, oldWikiName, newWikiName, con
|
||||
log.Error("Unable to open temporary repository: %s (%v)", basePath, err)
|
||||
return fmt.Errorf("Failed to open new temporary repository in: %s %v", basePath, err)
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
|
||||
if hasMasterBranch {
|
||||
if err := gitRepo.ReadTreeToIndex("HEAD"); err != nil {
|
||||
@@ -283,6 +284,7 @@ func (repo *Repository) DeleteWikiPage(doer *User, wikiName string) (err error)
|
||||
log.Error("Unable to open temporary repository: %s (%v)", basePath, err)
|
||||
return fmt.Errorf("Failed to open new temporary repository in: %s %v", basePath, err)
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
|
||||
if err := gitRepo.ReadTreeToIndex("HEAD"); err != nil {
|
||||
log.Error("Unable to read HEAD tree to index in: %s %v", basePath, err)
|
||||
|
||||
@@ -161,6 +161,7 @@ func TestRepository_AddWikiPage(t *testing.T) {
|
||||
// Now need to show that the page has been added:
|
||||
gitRepo, err := git.OpenRepository(repo.WikiPath())
|
||||
assert.NoError(t, err)
|
||||
defer gitRepo.Close()
|
||||
masterTree, err := gitRepo.GetTree("master")
|
||||
assert.NoError(t, err)
|
||||
wikiPath := WikiNameToFilename(wikiName)
|
||||
@@ -214,6 +215,7 @@ func TestRepository_EditWikiPage(t *testing.T) {
|
||||
_, err := masterTree.GetTreeEntryByPath("Home.md")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
gitRepo.Close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,6 +228,7 @@ func TestRepository_DeleteWikiPage(t *testing.T) {
|
||||
// Now need to show that the page has been added:
|
||||
gitRepo, err := git.OpenRepository(repo.WikiPath())
|
||||
assert.NoError(t, err)
|
||||
defer gitRepo.Close()
|
||||
masterTree, err := gitRepo.GetTree("master")
|
||||
assert.NoError(t, err)
|
||||
wikiPath := WikiNameToFilename("Home")
|
||||
|
||||
@@ -36,6 +36,10 @@ type CreateRepoForm struct {
|
||||
IssueLabels string
|
||||
License string
|
||||
Readme string
|
||||
|
||||
RepoTemplate int64
|
||||
GitContent bool
|
||||
Topics bool
|
||||
}
|
||||
|
||||
// Validate validates the fields
|
||||
@@ -107,6 +111,7 @@ type RepoSettingForm struct {
|
||||
MirrorUsername string
|
||||
MirrorPassword string
|
||||
Private bool
|
||||
Template bool
|
||||
EnablePrune bool
|
||||
|
||||
// Advanced settings
|
||||
|
||||
@@ -186,7 +186,16 @@ func ReferencesGitRepo(allowEmpty bool) macaron.Handler {
|
||||
return
|
||||
}
|
||||
ctx.Repo.GitRepo = gitRepo
|
||||
// We opened it, we should close it
|
||||
defer func() {
|
||||
// If it's been set to nil then assume someone else has closed it.
|
||||
if ctx.Repo.GitRepo != nil {
|
||||
ctx.Repo.GitRepo.Close()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
ctx.Next()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+64
-2
@@ -189,6 +189,26 @@ func RetrieveBaseRepo(ctx *Context, repo *models.Repository) {
|
||||
}
|
||||
}
|
||||
|
||||
// RetrieveTemplateRepo retrieves template repository used to generate this repository
|
||||
func RetrieveTemplateRepo(ctx *Context, repo *models.Repository) {
|
||||
// Non-generated repository will not return error in this method.
|
||||
if err := repo.GetTemplateRepo(); err != nil {
|
||||
if models.IsErrRepoNotExist(err) {
|
||||
repo.TemplateID = 0
|
||||
return
|
||||
}
|
||||
ctx.ServerError("GetTemplateRepo", err)
|
||||
return
|
||||
} else if err = repo.TemplateRepo.GetOwner(); err != nil {
|
||||
ctx.ServerError("TemplateRepo.GetOwner", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !repo.TemplateRepo.CheckUnitUser(ctx.User.ID, ctx.User.IsAdmin, models.UnitTypeCode) {
|
||||
repo.TemplateID = 0
|
||||
}
|
||||
}
|
||||
|
||||
// ComposeGoGetImport returns go-get-import meta content.
|
||||
func ComposeGoGetImport(owner, repo string) string {
|
||||
/// setting.AppUrl is guaranteed to be parse as url
|
||||
@@ -414,6 +434,13 @@ func RepoAssignment() macaron.Handler {
|
||||
}
|
||||
}
|
||||
|
||||
if repo.IsGenerated() {
|
||||
RetrieveTemplateRepo(ctx, repo)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Disable everything when the repo is being created
|
||||
if ctx.Repo.Repository.IsBeingCreated() {
|
||||
ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
|
||||
@@ -427,9 +454,18 @@ func RepoAssignment() macaron.Handler {
|
||||
}
|
||||
ctx.Repo.GitRepo = gitRepo
|
||||
|
||||
// We opened it, we should close it
|
||||
defer func() {
|
||||
// If it's been set to nil then assume someone else has closed it.
|
||||
if ctx.Repo.GitRepo != nil {
|
||||
ctx.Repo.GitRepo.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
// Stop at this point when the repo is empty.
|
||||
if ctx.Repo.Repository.IsEmpty {
|
||||
ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
|
||||
ctx.Next()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -488,6 +524,7 @@ func RepoAssignment() macaron.Handler {
|
||||
ctx.Data["GoDocDirectory"] = prefix + "{/dir}"
|
||||
ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}"
|
||||
}
|
||||
ctx.Next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -518,6 +555,22 @@ func RepoRef() macaron.Handler {
|
||||
return RepoRefByType(RepoRefBranch)
|
||||
}
|
||||
|
||||
// RefTypeIncludesBranches returns true if ref type can be a branch
|
||||
func (rt RepoRefType) RefTypeIncludesBranches() bool {
|
||||
if rt == RepoRefLegacy || rt == RepoRefAny || rt == RepoRefBranch {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RefTypeIncludesTags returns true if ref type can be a tag
|
||||
func (rt RepoRefType) RefTypeIncludesTags() bool {
|
||||
if rt == RepoRefLegacy || rt == RepoRefAny || rt == RepoRefTag {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getRefNameFromPath(ctx *Context, path string, isExist func(string) bool) string {
|
||||
refName := ""
|
||||
parts := strings.Split(path, "/")
|
||||
@@ -593,6 +646,13 @@ func RepoRefByType(refType RepoRefType) macaron.Handler {
|
||||
ctx.ServerError("RepoRef Invalid repo "+repoPath, err)
|
||||
return
|
||||
}
|
||||
// We opened it, we should close it
|
||||
defer func() {
|
||||
// If it's been set to nil then assume someone else has closed it.
|
||||
if ctx.Repo.GitRepo != nil {
|
||||
ctx.Repo.GitRepo.Close()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Get default branch.
|
||||
@@ -623,7 +683,7 @@ func RepoRefByType(refType RepoRefType) macaron.Handler {
|
||||
} else {
|
||||
refName = getRefName(ctx, refType)
|
||||
ctx.Repo.BranchName = refName
|
||||
if ctx.Repo.GitRepo.IsBranchExist(refName) {
|
||||
if refType.RefTypeIncludesBranches() && ctx.Repo.GitRepo.IsBranchExist(refName) {
|
||||
ctx.Repo.IsViewBranch = true
|
||||
|
||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
|
||||
@@ -633,7 +693,7 @@ func RepoRefByType(refType RepoRefType) macaron.Handler {
|
||||
}
|
||||
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
|
||||
|
||||
} else if ctx.Repo.GitRepo.IsTagExist(refName) {
|
||||
} else if refType.RefTypeIncludesTags() && ctx.Repo.GitRepo.IsTagExist(refName) {
|
||||
ctx.Repo.IsViewTag = true
|
||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName)
|
||||
if err != nil {
|
||||
@@ -681,6 +741,8 @@ func RepoRefByType(refType RepoRefType) macaron.Handler {
|
||||
return
|
||||
}
|
||||
ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
|
||||
|
||||
ctx.Next()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,10 +87,11 @@ func (r *BlameReader) Close() error {
|
||||
|
||||
// CreateBlameReader creates reader for given repository, commit and file
|
||||
func CreateBlameReader(repoPath, commitID, file string) (*BlameReader, error) {
|
||||
_, err := OpenRepository(repoPath)
|
||||
gitRepo, err := OpenRepository(repoPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gitRepo.Close()
|
||||
|
||||
return createBlameReader(repoPath, GitExecutable, "blame", commitID, "--porcelain", "--", file)
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ THE SOFTWARE.
|
||||
`
|
||||
repo, err := OpenRepository("../../.git")
|
||||
assert.NoError(t, err)
|
||||
defer repo.Close()
|
||||
|
||||
testBlob, err := repo.GetBlob("a8d4b49dd073a4a38a7e58385eeff7cc52568697")
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -55,6 +57,8 @@ func Benchmark_Blob_Data(b *testing.B) {
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer repo.Close()
|
||||
|
||||
testBlob, err := repo.GetBlob("a8d4b49dd073a4a38a7e58385eeff7cc52568697")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
|
||||
+21
-1
@@ -9,6 +9,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -24,6 +25,9 @@ var (
|
||||
DefaultCommandExecutionTimeout = 60 * time.Second
|
||||
)
|
||||
|
||||
// DefaultLocale is the default LC_ALL to run git commands in.
|
||||
const DefaultLocale = "C"
|
||||
|
||||
// Command represents a command with its subcommands or arguments.
|
||||
type Command struct {
|
||||
name string
|
||||
@@ -63,6 +67,13 @@ func (c *Command) RunInDirTimeoutEnvPipeline(env []string, timeout time.Duration
|
||||
// RunInDirTimeoutEnvFullPipeline executes the command in given directory with given timeout,
|
||||
// it pipes stdout and stderr to given io.Writer and passes in an io.Reader as stdin.
|
||||
func (c *Command) RunInDirTimeoutEnvFullPipeline(env []string, timeout time.Duration, dir string, stdout, stderr io.Writer, stdin io.Reader) error {
|
||||
return c.RunInDirTimeoutEnvFullPipelineFunc(env, timeout, dir, stdout, stderr, stdin, nil)
|
||||
}
|
||||
|
||||
// RunInDirTimeoutEnvFullPipelineFunc executes the command in given directory with given timeout,
|
||||
// it pipes stdout and stderr to given io.Writer and passes in an io.Reader as stdin. Between cmd.Start and cmd.Wait the passed in function is run.
|
||||
func (c *Command) RunInDirTimeoutEnvFullPipelineFunc(env []string, timeout time.Duration, dir string, stdout, stderr io.Writer, stdin io.Reader, fn func(context.Context, context.CancelFunc)) error {
|
||||
|
||||
if timeout == -1 {
|
||||
timeout = DefaultCommandExecutionTimeout
|
||||
}
|
||||
@@ -77,7 +88,12 @@ func (c *Command) RunInDirTimeoutEnvFullPipeline(env []string, timeout time.Dura
|
||||
defer cancel()
|
||||
|
||||
cmd := exec.CommandContext(ctx, c.name, c.args...)
|
||||
cmd.Env = env
|
||||
if env == nil {
|
||||
cmd.Env = append(os.Environ(), fmt.Sprintf("LC_ALL=%s", DefaultLocale))
|
||||
} else {
|
||||
cmd.Env = env
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("LC_ALL=%s", DefaultLocale))
|
||||
}
|
||||
cmd.Dir = dir
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = stderr
|
||||
@@ -89,6 +105,10 @@ func (c *Command) RunInDirTimeoutEnvFullPipeline(env []string, timeout time.Dura
|
||||
pid := process.GetManager().Add(fmt.Sprintf("%s %s %s [repo_path: %s]", GitExecutable, c.name, strings.Join(c.args, " "), dir), cmd)
|
||||
defer process.GetManager().Remove(pid)
|
||||
|
||||
if fn != nil {
|
||||
fn(ctx, cancel)
|
||||
}
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -77,6 +77,8 @@ func TestEntries_GetCommitsInfo(t *testing.T) {
|
||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||
bareRepo1, err := OpenRepository(bareRepo1Path)
|
||||
assert.NoError(t, err)
|
||||
defer bareRepo1.Close()
|
||||
|
||||
testGetCommitsInfo(t, bareRepo1)
|
||||
|
||||
clonedPath, err := cloneRepo(bareRepo1Path, testReposDir, "repo1_TestEntries_GetCommitsInfo")
|
||||
@@ -84,6 +86,8 @@ func TestEntries_GetCommitsInfo(t *testing.T) {
|
||||
defer os.RemoveAll(clonedPath)
|
||||
clonedRepo1, err := OpenRepository(clonedPath)
|
||||
assert.NoError(t, err)
|
||||
defer clonedRepo1.Close()
|
||||
|
||||
testGetCommitsInfo(t, clonedRepo1)
|
||||
}
|
||||
|
||||
@@ -101,13 +105,16 @@ func BenchmarkEntries_GetCommitsInfo(b *testing.B) {
|
||||
for _, benchmark := range benchmarks {
|
||||
var commit *Commit
|
||||
var entries Entries
|
||||
var repo *Repository
|
||||
if repoPath, err := cloneRepo(benchmark.url, benchmarkReposDir, benchmark.name); err != nil {
|
||||
b.Fatal(err)
|
||||
} else if repo, err := OpenRepository(repoPath); err != nil {
|
||||
} else if repo, err = OpenRepository(repoPath); err != nil {
|
||||
b.Fatal(err)
|
||||
} else if commit, err = repo.GetBranchCommit("master"); err != nil {
|
||||
repo.Close()
|
||||
b.Fatal(err)
|
||||
} else if entries, err = commit.Tree.ListEntries(); err != nil {
|
||||
repo.Close()
|
||||
b.Fatal(err)
|
||||
}
|
||||
entries.Sort()
|
||||
@@ -120,5 +127,6 @@ func BenchmarkEntries_GetCommitsInfo(b *testing.B) {
|
||||
}
|
||||
}
|
||||
})
|
||||
repo.Close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ func TestGetNotes(t *testing.T) {
|
||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||
bareRepo1, err := OpenRepository(bareRepo1Path)
|
||||
assert.NoError(t, err)
|
||||
defer bareRepo1.Close()
|
||||
|
||||
note := Note{}
|
||||
err = GetNote(bareRepo1, "95bb4d39648ee7e325106df01a621c530863a653", ¬e)
|
||||
@@ -27,6 +28,7 @@ func TestGetNestedNotes(t *testing.T) {
|
||||
repoPath := filepath.Join(testReposDir, "repo3_notes")
|
||||
repo, err := OpenRepository(repoPath)
|
||||
assert.NoError(t, err)
|
||||
defer repo.Close()
|
||||
|
||||
note := Note{}
|
||||
err = GetNote(repo, "3e668dbfac39cbc80a9ff9c61eb565d944453ba4", ¬e)
|
||||
|
||||
+13
-2
@@ -17,6 +17,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
gitealog "code.gitea.io/gitea/modules/log"
|
||||
"github.com/unknwon/com"
|
||||
"gopkg.in/src-d/go-billy.v4/osfs"
|
||||
gogit "gopkg.in/src-d/go-git.v4"
|
||||
@@ -122,6 +123,16 @@ func OpenRepository(repoPath string) (*Repository, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close this repository, in particular close the underlying gogitStorage if this is not nil
|
||||
func (repo *Repository) Close() {
|
||||
if repo == nil || repo.gogitStorage == nil {
|
||||
return
|
||||
}
|
||||
if err := repo.gogitStorage.Close(); err != nil {
|
||||
gitealog.Error("Error closing storage: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// GoGitRepo gets the go-git repo representation
|
||||
func (repo *Repository) GoGitRepo() *gogit.Repository {
|
||||
return repo.gogitRepo
|
||||
@@ -304,8 +315,8 @@ const (
|
||||
statSizeGarbage = "size-garbage: "
|
||||
)
|
||||
|
||||
// GetRepoSize returns disk consumption for repo in path
|
||||
func GetRepoSize(repoPath string) (*CountObject, error) {
|
||||
// CountObjects returns the results of git count-objects on the repoPath
|
||||
func CountObjects(repoPath string) (*CountObject, error) {
|
||||
cmd := NewCommand("count-objects", "-v")
|
||||
stdout, err := cmd.RunInDir(repoPath)
|
||||
if err != nil {
|
||||
|
||||
@@ -17,6 +17,7 @@ func TestRepository_GetBlob_Found(t *testing.T) {
|
||||
repoPath := filepath.Join(testReposDir, "repo1_bare")
|
||||
r, err := OpenRepository(repoPath)
|
||||
assert.NoError(t, err)
|
||||
defer r.Close()
|
||||
|
||||
testCases := []struct {
|
||||
OID string
|
||||
@@ -44,6 +45,7 @@ func TestRepository_GetBlob_NotExist(t *testing.T) {
|
||||
repoPath := filepath.Join(testReposDir, "repo1_bare")
|
||||
r, err := OpenRepository(repoPath)
|
||||
assert.NoError(t, err)
|
||||
defer r.Close()
|
||||
|
||||
testCase := "0000000000000000000000000000000000000000"
|
||||
testError := ErrNotExist{testCase, ""}
|
||||
@@ -57,6 +59,7 @@ func TestRepository_GetBlob_NoId(t *testing.T) {
|
||||
repoPath := filepath.Join(testReposDir, "repo1_bare")
|
||||
r, err := OpenRepository(repoPath)
|
||||
assert.NoError(t, err)
|
||||
defer r.Close()
|
||||
|
||||
testCase := ""
|
||||
testError := fmt.Errorf("Length must be 40: %s", testCase)
|
||||
|
||||
@@ -108,6 +108,7 @@ func GetBranchesByPath(path string) ([]*Branch, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
|
||||
brs, err := gitRepo.GetBranches()
|
||||
if err != nil {
|
||||
|
||||
@@ -15,6 +15,7 @@ func TestRepository_GetBranches(t *testing.T) {
|
||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||
bareRepo1, err := OpenRepository(bareRepo1Path)
|
||||
assert.NoError(t, err)
|
||||
defer bareRepo1.Close()
|
||||
|
||||
branches, err := bareRepo1.GetBranches()
|
||||
|
||||
@@ -29,6 +30,7 @@ func BenchmarkRepository_GetBranches(b *testing.B) {
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer bareRepo1.Close()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := bareRepo1.GetBranches()
|
||||
|
||||
@@ -15,6 +15,7 @@ func TestRepository_GetCommitBranches(t *testing.T) {
|
||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||
bareRepo1, err := OpenRepository(bareRepo1Path)
|
||||
assert.NoError(t, err)
|
||||
defer bareRepo1.Close()
|
||||
|
||||
// these test case are specific to the repo1_bare test repo
|
||||
testCases := []struct {
|
||||
@@ -41,6 +42,7 @@ func TestGetTagCommitWithSignature(t *testing.T) {
|
||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||
bareRepo1, err := OpenRepository(bareRepo1Path)
|
||||
assert.NoError(t, err)
|
||||
defer bareRepo1.Close()
|
||||
|
||||
commit, err := bareRepo1.GetCommit("3ad28a9149a2864384548f3d17ed7f38014c9e8a")
|
||||
assert.NoError(t, err)
|
||||
@@ -54,6 +56,7 @@ func TestGetCommitWithBadCommitID(t *testing.T) {
|
||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||
bareRepo1, err := OpenRepository(bareRepo1Path)
|
||||
assert.NoError(t, err)
|
||||
defer bareRepo1.Close()
|
||||
|
||||
commit, err := bareRepo1.GetCommit("bad_branch")
|
||||
assert.Nil(t, commit)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user