Merge branch 'master' into refactor_issues-subscription

This commit is contained in:
6543 2019-11-13 13:10:06 +01:00 committed by GitHub
commit 212aa8fc13
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
236 changed files with 3521 additions and 910 deletions

View file

@ -438,9 +438,9 @@ js: npm
.PHONY: css .PHONY: css
css: npm css: npm
npx stylelint public/less npx stylelint web_src/less
npx lessc --clean-css="--s0 -b" public/less/index.less public/css/index.css npx lessc --clean-css="--s0 -b" web_src/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;) $(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/* npx postcss --use autoprefixer --no-map --replace public/css/*
@diff=$$(git diff public/css/*); \ @diff=$$(git diff public/css/*); \

View file

@ -375,17 +375,20 @@ func runRepoSyncReleases(c *cli.Context) error {
if err = models.SyncReleasesWithTags(repo, gitRepo); err != nil { if err = models.SyncReleasesWithTags(repo, gitRepo); err != nil {
log.Warn(" SyncReleasesWithTags: %v", err) log.Warn(" SyncReleasesWithTags: %v", err)
gitRepo.Close()
continue continue
} }
count, err = getReleaseCount(repo.ID) count, err = getReleaseCount(repo.ID)
if err != nil { if err != nil {
log.Warn(" GetReleaseCountByRepoID: %v", err) log.Warn(" GetReleaseCountByRepoID: %v", err)
gitRepo.Close()
continue continue
} }
log.Trace(" repo %s releases synchronized to tags: from %d to %d", log.Trace(" repo %s releases synchronized to tags: from %d to %d",
repo.FullName(), oldnum, count) repo.FullName(), oldnum, count)
gitRepo.Close()
} }
} }

View file

@ -51,8 +51,8 @@ Type=simple
User=git User=git
Group=git Group=git
WorkingDirectory=/var/lib/gitea/ WorkingDirectory=/var/lib/gitea/
# If using unix socket: Tells Systemd to create /run/gitea folder to home gitea.sock # If using Unix socket: tells systemd to create the /run/gitea folder, which will contain the gitea.sock file
# Manual cration would vanish after reboot. # (manually creating /run/gitea doesn't work, because it would not persist across reboots)
#RuntimeDirectory=gitea #RuntimeDirectory=gitea
ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini
Restart=always Restart=always

View file

@ -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 ; When adding a repo to a team or creating a new repo all team members will watch the
; repo automatically if enabled ; repo automatically if enabled
AUTO_WATCH_NEW_REPOS = true 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] [webhook]
; Hook task queue length, increase if webhook shooting starts hanging ; Hook task queue length, increase if webhook shooting starts hanging

View file

@ -303,6 +303,7 @@ relation to port exhaustion.
on this instance. on this instance.
- `SHOW_REGISTRATION_BUTTON`: **! DISABLE\_REGISTRATION**: Show Registration Button - `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_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_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. - `DEFAULT_ORG_MEMBER_VISIBLE`: **false** True will make the membership of the users visible when added to the organisation.

View file

@ -68,6 +68,7 @@ type Uploader interface {
CreateComment(issueNumber int64, comment *Comment) error CreateComment(issueNumber int64, comment *Comment) error
CreatePullRequest(pr *PullRequest) error CreatePullRequest(pr *PullRequest) error
Rollback() error Rollback() error
Close()
} }
``` ```

View file

@ -13,7 +13,7 @@ menu:
identifier: "fail2ban-setup" 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 **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.** sure to test this before relying on it so you don't lock yourself out.**

View file

@ -51,6 +51,7 @@ func TestAPICreateAndUpdateRelease(t *testing.T) {
gitRepo, err := git.OpenRepository(repo.RepoPath()) gitRepo, err := git.OpenRepository(repo.RepoPath())
assert.NoError(t, err) assert.NoError(t, err)
defer gitRepo.Close()
err = gitRepo.CreateTag("v0.0.1", "master") err = gitRepo.CreateTag("v0.0.1", "master")
assert.NoError(t, err) assert.NoError(t, err)
@ -112,6 +113,7 @@ func TestAPICreateReleaseToDefaultBranchOnExistingTag(t *testing.T) {
gitRepo, err := git.OpenRepository(repo.RepoPath()) gitRepo, err := git.OpenRepository(repo.RepoPath())
assert.NoError(t, err) assert.NoError(t, err)
defer gitRepo.Close()
err = gitRepo.CreateTag("v0.0.1", "master") err = gitRepo.CreateTag("v0.0.1", "master")
assert.NoError(t, err) assert.NoError(t, err)

View file

@ -139,6 +139,7 @@ func TestAPICreateFile(t *testing.T) {
assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) 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.Email, fileResponse.Commit.Author.Email)
assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name)
gitRepo.Close()
} }
// Test creating a file in a new branch // Test creating a file in a new branch

View file

@ -143,6 +143,7 @@ func TestAPIUpdateFile(t *testing.T) {
assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) 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.Email, fileResponse.Commit.Author.Email)
assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name)
gitRepo.Close()
} }
// Test updating a file in a new branch // Test updating a file in a new branch

View file

@ -74,6 +74,8 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) {
repo1.CreateNewBranch(user2, repo1.DefaultBranch, newBranch) repo1.CreateNewBranch(user2, repo1.DefaultBranch, newBranch)
// Get the commit ID of the default branch // Get the commit ID of the default branch
gitRepo, _ := git.OpenRepository(repo1.RepoPath()) gitRepo, _ := git.OpenRepository(repo1.RepoPath())
defer gitRepo.Close()
commitID, _ := gitRepo.GetBranchCommitID(repo1.DefaultBranch) commitID, _ := gitRepo.GetBranchCommitID(repo1.DefaultBranch)
// Make a new tag in repo1 // Make a new tag in repo1
newTag := "test_tag" newTag := "test_tag"

View file

@ -75,6 +75,8 @@ func testAPIGetContents(t *testing.T, u *url.URL) {
repo1.CreateNewBranch(user2, repo1.DefaultBranch, newBranch) repo1.CreateNewBranch(user2, repo1.DefaultBranch, newBranch)
// Get the commit ID of the default branch // Get the commit ID of the default branch
gitRepo, _ := git.OpenRepository(repo1.RepoPath()) gitRepo, _ := git.OpenRepository(repo1.RepoPath())
defer gitRepo.Close()
commitID, _ := gitRepo.GetBranchCommitID(repo1.DefaultBranch) commitID, _ := gitRepo.GetBranchCommitID(repo1.DefaultBranch)
// Make a new tag in repo1 // Make a new tag in repo1
newTag := "test_tag" newTag := "test_tag"

View file

@ -29,6 +29,8 @@ func TestAPIGitTags(t *testing.T) {
git.NewCommand("config", "user.email", user.Email).RunInDir(repo.RepoPath()) git.NewCommand("config", "user.email", user.Email).RunInDir(repo.RepoPath())
gitRepo, _ := git.OpenRepository(repo.RepoPath()) gitRepo, _ := git.OpenRepository(repo.RepoPath())
defer gitRepo.Close()
commit, _ := gitRepo.GetBranchCommit("master") commit, _ := gitRepo.GetBranchCommit("master")
lTagName := "lightweightTag" lTagName := "lightweightTag"
gitRepo.CreateTag(lTagName, commit.ID.String()) gitRepo.CreateTag(lTagName, commit.ID.String())

View file

@ -70,9 +70,9 @@ func TestAPISearchRepo(t *testing.T) {
expectedResults expectedResults
}{ }{
{name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50&private=false", expectedResults: expectedResults{ {name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50&private=false", expectedResults: expectedResults{
nil: {count: 22}, nil: {count: 24},
user: {count: 22}, user: {count: 24},
user2: {count: 22}}, user2: {count: 24}},
}, },
{name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10&private=false", expectedResults: expectedResults{ {name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10&private=false", expectedResults: expectedResults{
nil: {count: 10}, 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{ {name: "RepositoriesAccessibleAndRelatedToUser", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user.ID), expectedResults: expectedResults{
nil: {count: 5}, nil: {count: 5},
user: {count: 9, includesPrivate: true}, 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{ {name: "RepositoriesAccessibleAndRelatedToUser2", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user2.ID), expectedResults: expectedResults{
nil: {count: 1}, 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{ {name: "RepositoriesAccessibleAndRelatedToUser3", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user3.ID), expectedResults: expectedResults{
nil: {count: 1}, nil: {count: 1},
user: {count: 4, includesPrivate: true}, user: {count: 4, includesPrivate: true},
user2: {count: 2, includesPrivate: true}, user2: {count: 3, includesPrivate: true},
user3: {count: 4, includesPrivate: true}}, user3: {count: 4, includesPrivate: true}},
}, },
{name: "RepositoriesOwnedByOrganization", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", orgUser.ID), expectedResults: expectedResults{ {name: "RepositoriesOwnedByOrganization", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", orgUser.ID), expectedResults: expectedResults{

View file

@ -11,8 +11,8 @@ import (
"testing" "testing"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/convert"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

View file

@ -10,8 +10,8 @@ import (
"time" "time"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/convert"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

View file

@ -0,0 +1 @@
ref: refs/heads/master

View file

@ -0,0 +1,6 @@
[core]
repositoryformatversion = 0
filemode = false
bare = true
symlinks = false
ignorecase = true

View file

@ -0,0 +1 @@
Unnamed repository; edit this file 'description' to name the repository.

View file

@ -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+"$@"}
:

View file

@ -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
}

View file

@ -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}};
}

View file

@ -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

View file

@ -0,0 +1,2 @@
#!/usr/bin/env bash
"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" post-receive

View file

@ -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

View file

@ -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+"$@"}
:

View file

@ -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 --

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,2 @@
#!/usr/bin/env bash
"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" pre-receive

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,2 @@
#!/usr/bin/env bash
"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" update $1 $2 $3

View file

@ -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

View file

@ -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]
# *~

View file

@ -0,0 +1 @@
aacbdfe9e1c4b47f60abe81849045fa4e96f1d75 refs/heads/master

View file

@ -0,0 +1 @@
aacbdfe9e1c4b47f60abe81849045fa4e96f1d75

View file

@ -18,6 +18,7 @@ import (
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"runtime"
"strings" "strings"
"testing" "testing"
@ -102,7 +103,11 @@ func initIntegrationTest() {
fmt.Println("Environment variable $GITEA_ROOT not set") fmt.Println("Environment variable $GITEA_ROOT not set")
os.Exit(1) 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 { if _, err := os.Stat(setting.AppPath); err != nil {
fmt.Printf("Could not find gitea binary at %s\n", setting.AppPath) fmt.Printf("Could not find gitea binary at %s\n", setting.AppPath)
os.Exit(1) os.Exit(1)

View file

@ -5,15 +5,22 @@
package integrations package integrations
import ( import (
"bytes"
"fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url" "net/url"
"os"
"path" "path"
"strings" "strings"
"testing" "testing"
"time"
"code.gitea.io/gitea/models" "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/modules/test"
"code.gitea.io/gitea/services/pull"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/unknwon/i18n" "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") 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")
})
}

View file

@ -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")
}

View file

@ -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})
})
}

View file

@ -73,6 +73,7 @@ func testDeleteRepoFile(t *testing.T, u *url.URL) {
test.LoadRepoCommit(t, ctx) test.LoadRepoCommit(t, ctx)
test.LoadUser(t, ctx, 2) test.LoadUser(t, ctx, 2)
test.LoadGitRepo(t, ctx) test.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository repo := ctx.Repo.Repository
doer := ctx.User doer := ctx.User
opts := getDeleteRepoFileOptions(repo) opts := getDeleteRepoFileOptions(repo)
@ -111,6 +112,8 @@ func testDeleteRepoFileWithoutBranchNames(t *testing.T, u *url.URL) {
test.LoadRepoCommit(t, ctx) test.LoadRepoCommit(t, ctx)
test.LoadUser(t, ctx, 2) test.LoadUser(t, ctx, 2)
test.LoadGitRepo(t, ctx) test.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository repo := ctx.Repo.Repository
doer := ctx.User doer := ctx.User
opts := getDeleteRepoFileOptions(repo) opts := getDeleteRepoFileOptions(repo)
@ -139,6 +142,8 @@ func TestDeleteRepoFileErrors(t *testing.T) {
test.LoadRepoCommit(t, ctx) test.LoadRepoCommit(t, ctx)
test.LoadUser(t, ctx, 2) test.LoadUser(t, ctx, 2)
test.LoadGitRepo(t, ctx) test.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository repo := ctx.Repo.Repository
doer := ctx.User doer := ctx.User

View file

@ -191,6 +191,8 @@ func TestCreateOrUpdateRepoFileForCreate(t *testing.T) {
test.LoadRepoCommit(t, ctx) test.LoadRepoCommit(t, ctx)
test.LoadUser(t, ctx, 2) test.LoadUser(t, ctx, 2)
test.LoadGitRepo(t, ctx) test.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository repo := ctx.Repo.Repository
doer := ctx.User doer := ctx.User
opts := getCreateRepoFileOptions(repo) opts := getCreateRepoFileOptions(repo)
@ -201,6 +203,8 @@ func TestCreateOrUpdateRepoFileForCreate(t *testing.T) {
// asserts // asserts
assert.Nil(t, err) assert.Nil(t, err)
gitRepo, _ := git.OpenRepository(repo.RepoPath()) gitRepo, _ := git.OpenRepository(repo.RepoPath())
defer gitRepo.Close()
commitID, _ := gitRepo.GetBranchCommitID(opts.NewBranch) commitID, _ := gitRepo.GetBranchCommitID(opts.NewBranch)
expectedFileResponse := getExpectedFileResponseForRepofilesCreate(commitID) expectedFileResponse := getExpectedFileResponseForRepofilesCreate(commitID)
assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content)
@ -220,6 +224,8 @@ func TestCreateOrUpdateRepoFileForUpdate(t *testing.T) {
test.LoadRepoCommit(t, ctx) test.LoadRepoCommit(t, ctx)
test.LoadUser(t, ctx, 2) test.LoadUser(t, ctx, 2)
test.LoadGitRepo(t, ctx) test.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository repo := ctx.Repo.Repository
doer := ctx.User doer := ctx.User
opts := getUpdateRepoFileOptions(repo) opts := getUpdateRepoFileOptions(repo)
@ -230,6 +236,8 @@ func TestCreateOrUpdateRepoFileForUpdate(t *testing.T) {
// asserts // asserts
assert.Nil(t, err) assert.Nil(t, err)
gitRepo, _ := git.OpenRepository(repo.RepoPath()) gitRepo, _ := git.OpenRepository(repo.RepoPath())
defer gitRepo.Close()
commitID, _ := gitRepo.GetBranchCommitID(opts.NewBranch) commitID, _ := gitRepo.GetBranchCommitID(opts.NewBranch)
expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commitID, opts.TreePath) expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commitID, opts.TreePath)
assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content)
@ -249,6 +257,8 @@ func TestCreateOrUpdateRepoFileForUpdateWithFileMove(t *testing.T) {
test.LoadRepoCommit(t, ctx) test.LoadRepoCommit(t, ctx)
test.LoadUser(t, ctx, 2) test.LoadUser(t, ctx, 2)
test.LoadGitRepo(t, ctx) test.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository repo := ctx.Repo.Repository
doer := ctx.User doer := ctx.User
opts := getUpdateRepoFileOptions(repo) opts := getUpdateRepoFileOptions(repo)
@ -261,6 +271,8 @@ func TestCreateOrUpdateRepoFileForUpdateWithFileMove(t *testing.T) {
// asserts // asserts
assert.Nil(t, err) assert.Nil(t, err)
gitRepo, _ := git.OpenRepository(repo.RepoPath()) gitRepo, _ := git.OpenRepository(repo.RepoPath())
defer gitRepo.Close()
commit, _ := gitRepo.GetBranchCommit(opts.NewBranch) commit, _ := gitRepo.GetBranchCommit(opts.NewBranch)
expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commit.ID.String(), opts.TreePath) expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commit.ID.String(), opts.TreePath)
// assert that the old file no longer exists in the last commit of the branch // 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.LoadRepoCommit(t, ctx)
test.LoadUser(t, ctx, 2) test.LoadUser(t, ctx, 2)
test.LoadGitRepo(t, ctx) test.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository repo := ctx.Repo.Repository
doer := ctx.User doer := ctx.User
opts := getUpdateRepoFileOptions(repo) opts := getUpdateRepoFileOptions(repo)
@ -300,6 +314,8 @@ func TestCreateOrUpdateRepoFileWithoutBranchNames(t *testing.T) {
// asserts // asserts
assert.Nil(t, err) assert.Nil(t, err)
gitRepo, _ := git.OpenRepository(repo.RepoPath()) gitRepo, _ := git.OpenRepository(repo.RepoPath())
defer gitRepo.Close()
commitID, _ := gitRepo.GetBranchCommitID(repo.DefaultBranch) commitID, _ := gitRepo.GetBranchCommitID(repo.DefaultBranch)
expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commitID, opts.TreePath) expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commitID, opts.TreePath)
assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content)
@ -315,6 +331,8 @@ func TestCreateOrUpdateRepoFileErrors(t *testing.T) {
test.LoadRepoCommit(t, ctx) test.LoadRepoCommit(t, ctx)
test.LoadUser(t, ctx, 2) test.LoadUser(t, ctx, 2)
test.LoadGitRepo(t, ctx) test.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository repo := ctx.Repo.Repository
doer := ctx.User doer := ctx.User

View file

@ -84,14 +84,17 @@ func (user *User) checkForConsistency(t *testing.T) {
func (repo *Repository) checkForConsistency(t *testing.T) { func (repo *Repository) checkForConsistency(t *testing.T) {
assert.Equal(t, repo.LowerName, strings.ToLower(repo.Name), "repo: %+v", repo) assert.Equal(t, repo.LowerName, strings.ToLower(repo.Name), "repo: %+v", repo)
assertCount(t, &Star{RepoID: repo.ID}, repo.NumStars) 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, &Milestone{RepoID: repo.ID}, repo.NumMilestones)
assertCount(t, &Repository{ForkID: repo.ID}, repo.NumForks) assertCount(t, &Repository{ForkID: repo.ID}, repo.NumForks)
if repo.IsFork { if repo.IsFork {
AssertExistsAndLoadBean(t, &Repository{ID: repo.ForkID}) 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, assert.EqualValues(t, repo.NumIssues, actual,
"Unexpected number of issues for repo %+v", repo) "Unexpected number of issues for repo %+v", repo)

View file

@ -1206,6 +1206,79 @@ func (err ErrInvalidMergeStyle) Error() string {
err.ID, err.Style) 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)
}
// _________ __ // _________ __
// \_ ___ \ ____ _____ _____ ____ _____/ |_ // \_ ___ \ ____ _____ _____ ____ _____/ |_
// / \ \/ / _ \ / \ / \_/ __ \ / \ __\ // / \ \/ / _ \ / \ / \_/ __ \ / \ __\

View file

@ -438,3 +438,17 @@
type: 3 type: 3
config: "{\"IgnoreWhitespaceConflicts\":false,\"AllowMerge\":true,\"AllowRebase\":true,\"AllowRebaseMerge\":true,\"AllowSquash\":true}" config: "{\"IgnoreWhitespaceConflicts\":false,\"AllowMerge\":true,\"AllowRebase\":true,\"AllowRebaseMerge\":true,\"AllowSquash\":true}"
created_unix: 946684810 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

View file

@ -10,7 +10,7 @@
num_closed_pulls: 0 num_closed_pulls: 0
num_milestones: 3 num_milestones: 3
num_closed_milestones: 1 num_closed_milestones: 1
num_watches: 3 num_watches: 4
status: 0 status: 0
- -
@ -561,3 +561,29 @@
num_issues: 0 num_issues: 0
is_mirror: false is_mirror: false
status: 0 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

View file

@ -427,4 +427,19 @@
num_repos: 1 num_repos: 1
num_members: 0 num_members: 0
num_teams: 1 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

View file

@ -2,13 +2,28 @@
id: 1 id: 1
user_id: 1 user_id: 1
repo_id: 1 repo_id: 1
mode: 1 # normal
- -
id: 2 id: 2
user_id: 4 user_id: 4
repo_id: 1 repo_id: 1
mode: 1 # normal
- -
id: 3 id: 3
user_id: 9 user_id: 9
repo_id: 1 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

View file

@ -17,6 +17,7 @@ func BenchmarkGetCommitGraph(b *testing.B) {
if err != nil { if err != nil {
b.Error("Could not open repository") b.Error("Could not open repository")
} }
defer currentRepo.Close()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
graph, err := GetCommitGraph(currentRepo, 1) graph, err := GetCommitGraph(currentRepo, 1)

View file

@ -7,6 +7,8 @@ package models
import ( import (
"fmt" "fmt"
"code.gitea.io/gitea/modules/util"
"xorm.io/xorm" "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 // MakeIDsFromAPIAssigneesToAdd returns an array with all assignee IDs
func MakeIDsFromAPIAssigneesToAdd(oneAssignee string, multipleAssignees []string) (assigneeIDs []int64, err error) { func MakeIDsFromAPIAssigneesToAdd(oneAssignee string, multipleAssignees []string) (assigneeIDs []int64, err error) {
var requestAssignees []string
// Keeping the old assigning method for compatibility reasons // Keeping the old assigning method for compatibility reasons
if oneAssignee != "" { if oneAssignee != "" && !util.IsStringInSlice(oneAssignee, multipleAssignees) {
requestAssignees = append(requestAssignees, oneAssignee)
}
// Prevent double adding assignees //Prevent empty assignees
var isDouble bool if len(multipleAssignees) > 0 && multipleAssignees[0] != "" {
for _, assignee := range multipleAssignees { requestAssignees = append(requestAssignees, multipleAssignees...)
if assignee == oneAssignee {
isDouble = true
break
}
}
if !isDouble {
multipleAssignees = append(multipleAssignees, oneAssignee)
}
} }
// Get the IDs of all assignees // Get the IDs of all assignees
assigneeIDs, err = GetUserIDsByNames(multipleAssignees, false) assigneeIDs, err = GetUserIDsByNames(requestAssignees, false)
return return
} }

View file

@ -59,3 +59,24 @@ func TestUpdateAssignee(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.False(t, isAssigned) 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)
}

View file

@ -266,6 +266,12 @@ var migrations = []Migration{
NewMigration("remove unnecessary columns from label", removeLabelUneededCols), NewMigration("remove unnecessary columns from label", removeLabelUneededCols),
// v105 -> v106 // v105 -> v106
NewMigration("add includes_all_repositories to teams", addTeamIncludesAllRepositories), 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 // Migrate database to current version

26
models/migrations/v106.go Normal file
View file

@ -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
}

19
models/migrations/v107.go Normal file
View file

@ -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))
}

18
models/migrations/v108.go Normal file
View file

@ -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))
}

View file

@ -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" 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 { if err != nil {
log.Warn("GetRepoSize: %v", err) log.Warn("CountObjects: %v", err)
continue continue
} }

View file

@ -47,6 +47,7 @@ func releaseAddColumnIsTagAndSyncTags(x *xorm.Engine) error {
if err = models.SyncReleasesWithTags(repo, gitRepo); err != nil { if err = models.SyncReleasesWithTags(repo, gitRepo); err != nil {
log.Warn("SyncReleasesWithTags: %v", err) log.Warn("SyncReleasesWithTags: %v", err)
} }
gitRepo.Close()
} }
if len(repos) < pageSize { if len(repos) < pageSize {
break break

View file

@ -91,6 +91,7 @@ func fixReleaseSha1OnReleaseTable(x *xorm.Engine) error {
if err != nil { if err != nil {
return err return err
} }
defer gitRepo.Close()
gitRepoCache[release.RepoID] = gitRepo gitRepoCache[release.RepoID] = gitRepo
} }

View file

@ -44,8 +44,10 @@ type Notification struct {
Status NotificationStatus `xorm:"SMALLINT INDEX NOT NULL"` Status NotificationStatus `xorm:"SMALLINT INDEX NOT NULL"`
Source NotificationSource `xorm:"SMALLINT INDEX NOT NULL"` Source NotificationSource `xorm:"SMALLINT INDEX NOT NULL"`
IssueID int64 `xorm:"INDEX NOT NULL"` IssueID int64 `xorm:"INDEX NOT NULL"`
CommitID string `xorm:"INDEX"` CommitID string `xorm:"INDEX"`
CommentID int64
Comment *Comment `xorm:"-"`
UpdatedBy int64 `xorm:"INDEX NOT NULL"` UpdatedBy int64 `xorm:"INDEX NOT NULL"`
@ -58,22 +60,27 @@ type Notification struct {
// CreateOrUpdateIssueNotifications creates an issue notification // CreateOrUpdateIssueNotifications creates an issue notification
// for each watcher, or updates it if already exists // 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() sess := x.NewSession()
defer sess.Close() defer sess.Close()
if err := sess.Begin(); err != nil { if err := sess.Begin(); err != nil {
return err return err
} }
if err := createOrUpdateIssueNotifications(sess, issue, notificationAuthorID); err != nil { if err := createOrUpdateIssueNotifications(sess, issueID, commentID, notificationAuthorID); err != nil {
return err return err
} }
return sess.Commit() return sess.Commit()
} }
func createOrUpdateIssueNotifications(e Engine, issue *Issue, notificationAuthorID int64) error { func createOrUpdateIssueNotifications(e Engine, issueID, commentID int64, notificationAuthorID int64) error {
issueWatches, err := getIssueWatchers(e, issue.ID) issueWatches, err := getIssueWatchers(e, issueID)
if err != nil {
return err
}
issue, err := getIssueByID(e, issueID)
if err != nil { if err != nil {
return err return err
} }
@ -83,7 +90,7 @@ func createOrUpdateIssueNotifications(e Engine, issue *Issue, notificationAuthor
return err return err
} }
notifications, err := getNotificationsByIssueID(e, issue.ID) notifications, err := getNotificationsByIssueID(e, issueID)
if err != nil { if err != nil {
return err return err
} }
@ -102,9 +109,9 @@ func createOrUpdateIssueNotifications(e Engine, issue *Issue, notificationAuthor
alreadyNotified[userID] = struct{}{} alreadyNotified[userID] = struct{}{}
if notificationExists(notifications, issue.ID, userID) { 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 { for _, issueWatch := range issueWatches {
@ -157,12 +164,13 @@ func notificationExists(notifications []*Notification, issueID, userID int64) bo
return false 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{ notification := &Notification{
UserID: userID, UserID: userID,
RepoID: issue.RepoID, RepoID: issue.RepoID,
Status: NotificationStatusUnread, Status: NotificationStatusUnread,
IssueID: issue.ID, IssueID: issue.ID,
CommentID: commentID,
UpdatedBy: updatedByID, UpdatedBy: updatedByID,
} }
@ -176,16 +184,25 @@ func createIssueNotification(e Engine, userID int64, issue *Issue, updatedByID i
return err 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) notification, err := getIssueNotification(e, userID, issueID)
if err != nil { if err != nil {
return err return err
} }
notification.Status = NotificationStatusUnread // NOTICE: Only update comment id when the before notification on this issue is read, otherwise you may miss some old comments.
notification.UpdatedBy = updatedByID // 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 return err
} }
@ -199,7 +216,7 @@ func getIssueNotification(e Engine, userID, issueID int64) (*Notification, error
} }
// NotificationsForUser returns notifications for a given user and status // 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) return notificationsForUser(x, user, statuses, page, perPage)
} }
@ -239,6 +256,204 @@ func (n *Notification) GetIssue() (*Issue, error) {
return n.Issue, err 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 // GetNotificationCount returns the notification count for user
func GetNotificationCount(user *User, status NotificationStatus) (int64, error) { func GetNotificationCount(user *User, status NotificationStatus) (int64, error) {
return getNotificationCount(x, user, status) return getNotificationCount(x, user, status)

View file

@ -14,7 +14,7 @@ func TestCreateOrUpdateIssueNotifications(t *testing.T) {
assert.NoError(t, PrepareTestDatabase()) assert.NoError(t, PrepareTestDatabase())
issue := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) 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 // User 9 is inactive, thus notifications for user 1 and 4 are created
notf := AssertExistsAndLoadBean(t, &Notification{UserID: 1, IssueID: issue.ID}).(*Notification) notf := AssertExistsAndLoadBean(t, &Notification{UserID: 1, IssueID: issue.ID}).(*Notification)

View file

@ -380,6 +380,7 @@ func (pr *PullRequest) GetLastCommitStatus() (status *CommitStatus, err error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer headGitRepo.Close()
lastCommitID, err := headGitRepo.GetBranchCommitID(pr.HeadBranch) lastCommitID, err := headGitRepo.GetBranchCommitID(pr.HeadBranch)
if err != nil { if err != nil {
@ -569,6 +570,7 @@ func (pr *PullRequest) getMergeCommit() (*git.Commit, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("OpenRepository: %v", err) return nil, fmt.Errorf("OpenRepository: %v", err)
} }
defer gitRepo.Close()
commit, err := gitRepo.GetCommit(mergeCommit[:40]) commit, err := gitRepo.GetCommit(mergeCommit[:40])
if err != nil { if err != nil {
@ -870,6 +872,7 @@ func (pr *PullRequest) UpdatePatch() (err error) {
if err != nil { if err != nil {
return fmt.Errorf("OpenRepository: %v", err) return fmt.Errorf("OpenRepository: %v", err)
} }
defer headGitRepo.Close()
// Add a temporary remote. // Add a temporary remote.
tmpRemote := com.ToStr(time.Now().UnixNano()) tmpRemote := com.ToStr(time.Now().UnixNano())
@ -911,6 +914,7 @@ func (pr *PullRequest) PushToBaseRepo() (err error) {
if err != nil { if err != nil {
return fmt.Errorf("OpenRepository: %v", err) return fmt.Errorf("OpenRepository: %v", err)
} }
defer headGitRepo.Close()
tmpRemoteName := fmt.Sprintf("tmp-pull-%d", pr.ID) tmpRemoteName := fmt.Sprintf("tmp-pull-%d", pr.ID)
if err = headGitRepo.AddRemote(tmpRemoteName, pr.BaseRepo.RepoPath(), false); err != nil { if err = headGitRepo.AddRemote(tmpRemoteName, pr.BaseRepo.RepoPath(), false); err != nil {

View file

@ -36,6 +36,7 @@ import (
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/sync" "code.gitea.io/gitea/modules/sync"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"github.com/mcuadros/go-version" "github.com/mcuadros/go-version"
"github.com/unknwon/com" "github.com/unknwon/com"
@ -178,6 +179,9 @@ type Repository struct {
IsFork bool `xorm:"INDEX NOT NULL DEFAULT false"` IsFork bool `xorm:"INDEX NOT NULL DEFAULT false"`
ForkID int64 `xorm:"INDEX"` ForkID int64 `xorm:"INDEX"`
BaseRepo *Repository `xorm:"-"` 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"` Size int64 `xorm:"NOT NULL DEFAULT 0"`
IndexerStatus *RepoIndexerStatus `xorm:"-"` IndexerStatus *RepoIndexerStatus `xorm:"-"`
IsFsckEnabled bool `xorm:"NOT NULL DEFAULT true"` IsFsckEnabled bool `xorm:"NOT NULL DEFAULT true"`
@ -350,6 +354,7 @@ func (repo *Repository) innerAPIFormat(e Engine, mode AccessMode, isParent bool)
FullName: repo.FullName(), FullName: repo.FullName(),
Description: repo.Description, Description: repo.Description,
Private: repo.IsPrivate, Private: repo.IsPrivate,
Template: repo.IsTemplate,
Empty: repo.IsEmpty, Empty: repo.IsEmpty,
Archived: repo.IsArchived, Archived: repo.IsArchived,
Size: int(repo.Size / 1024), Size: int(repo.Size / 1024),
@ -662,6 +667,27 @@ func (repo *Repository) getBaseRepo(e Engine) (err error) {
return err 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 { func (repo *Repository) repoPath(e Engine) string {
return RepoPath(repo.mustOwnerName(e), repo.Name) 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 { func (repo *Repository) updateSize(e Engine) error {
repoInfoSize, err := git.GetRepoSize(repo.repoPath(e)) size, err := util.GetDirectorySize(repo.repoPath(e))
if err != nil { if err != nil {
return fmt.Errorf("UpdateSize: %v", err) return fmt.Errorf("UpdateSize: %v", err)
} }
repo.Size = repoInfoSize.Size + repoInfoSize.SizePack repo.Size = size
_, err = e.ID(repo.ID).Cols("size").Update(repo) _, err = e.ID(repo.ID).Cols("size").Update(repo)
return err 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 { func (repo *Repository) UpdateSize() error {
return repo.updateSize(x) return repo.updateSize(x)
} }
@ -1021,6 +1047,7 @@ func MigrateRepositoryGitData(doer, u *User, repo *Repository, opts api.MigrateR
if err != nil { if err != nil {
return repo, fmt.Errorf("OpenRepository: %v", err) return repo, fmt.Errorf("OpenRepository: %v", err)
} }
defer gitRepo.Close()
repo.IsEmpty, err = gitRepo.IsEmpty() repo.IsEmpty, err = gitRepo.IsEmpty()
if err != nil { if err != nil {
@ -1219,6 +1246,20 @@ type CreateRepoOptions struct {
Status RepositoryStatus 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) { func getRepoInitFile(tp, name string) ([]byte, error) {
cleanedName := strings.TrimLeft(path.Clean("/"+name), "/") cleanedName := strings.TrimLeft(path.Clean("/"+name), "/")
relPath := path.Join("options", tp, cleanedName) relPath := path.Join("options", tp, cleanedName)
@ -1322,8 +1363,55 @@ func prepareRepoCommit(e Engine, repo *Repository, tmpDir, repoPath string, opts
return nil return nil
} }
// InitRepository initializes README and .gitignore if needed. func generateRepoCommit(e Engine, repo, templateRepo *Repository, tmpDir string) error {
func initRepository(e Engine, repoPath string, u *User, repo *Repository, opts CreateRepoOptions) (err 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. // Somehow the directory could exist.
if com.IsExist(repoPath) { if com.IsExist(repoPath) {
return fmt.Errorf("initRepository: path already exists: %s", 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 { } else if err = createDelegateHooks(repoPath); err != nil {
return fmt.Errorf("createDelegateHooks: %v", err) 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())) 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 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 ( var (
reservedRepoNames = []string{".", ".."} reservedRepoNames = []string{".", ".."}
reservedRepoPatterns = []string{"*.git", "*.wiki"} 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) 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 { if err = copyDefaultWebhooksToRepo(e, repo.ID); err != nil {
return fmt.Errorf("copyDefaultWebhooksToRepo: %v", err) return fmt.Errorf("copyDefaultWebhooksToRepo: %v", err)
@ -2410,8 +2527,8 @@ func CheckRepoStats() {
checkers := []*repoChecker{ checkers := []*repoChecker{
// Repository.NumWatches // Repository.NumWatches
{ {
"SELECT repo.id FROM `repository` repo WHERE repo.num_watches!=(SELECT COUNT(*) FROM `watch` WHERE repo_id=repo.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=?) WHERE id=?", "UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=? AND mode<>2) WHERE id=?",
"repository count 'num_watches'", "repository count 'num_watches'",
}, },
// Repository.NumStars // Repository.NumStars
@ -2532,6 +2649,28 @@ func HasForkedRepo(ownerID, repoID int64) (*Repository, bool) {
return repo, has 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 // ForkRepository forks a repository
func ForkRepository(doer, owner *User, oldRepo *Repository, name, desc string) (_ *Repository, err error) { func ForkRepository(doer, owner *User, oldRepo *Repository, name, desc string) (_ *Repository, err error) {
forkedRepo, err := oldRepo.GetUserFork(owner.ID) 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) log.Error("Failed to update size for repository: %v", err)
} }
// Copy LFS meta objects in new session return repo, CopyLFS(repo, oldRepo)
sess2 := x.NewSession() }
defer sess2.Close()
if err = sess2.Begin(); err != nil { // 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 return repo, err
} }
var lfsObjects []*LFSMetaObject repoPath := RepoPath(owner.Name, repo.Name)
if err = sess2.Where("repository_id=?", oldRepo.ID).Find(&lfsObjects); err != nil { if err = checkInitRepository(repoPath); err != nil {
return repo, err return repo, err
} }
for _, v := range lfsObjects { if opts.GitContent && !templateRepo.IsEmpty {
v.ID = 0 if err = generateRepository(sess, repo, templateRepo); err != nil {
v.RepositoryID = repo.ID
if _, err = sess2.Insert(v); err != nil {
return repo, err 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 // GetForks returns all the forks of the repository

View file

@ -64,6 +64,8 @@ func GetActivityStats(repo *Repository, timeFrom time.Time, releases, issues, pr
if err != nil { if err != nil {
return nil, fmt.Errorf("OpenRepository: %v", err) return nil, fmt.Errorf("OpenRepository: %v", err)
} }
defer gitRepo.Close()
code, err := gitRepo.GetCodeActivityStats(timeFrom, repo.DefaultBranch) code, err := gitRepo.GetCodeActivityStats(timeFrom, repo.DefaultBranch)
if err != nil { if err != nil {
return nil, fmt.Errorf("FillFromGit: %v", err) return nil, fmt.Errorf("FillFromGit: %v", err)
@ -79,6 +81,8 @@ func GetActivityStatsTopAuthors(repo *Repository, timeFrom time.Time, count int)
if err != nil { if err != nil {
return nil, fmt.Errorf("OpenRepository: %v", err) return nil, fmt.Errorf("OpenRepository: %v", err)
} }
defer gitRepo.Close()
code, err := gitRepo.GetCodeActivityStats(timeFrom, "") code, err := gitRepo.GetCodeActivityStats(timeFrom, "")
if err != nil { if err != nil {
return nil, fmt.Errorf("FillFromGit: %v", err) return nil, fmt.Errorf("FillFromGit: %v", err)

View file

@ -23,6 +23,7 @@ func (repo *Repository) GetBranch(branch string) (*git.Branch, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer gitRepo.Close()
return gitRepo.GetBranch(branch) return gitRepo.GetBranch(branch)
} }
@ -38,6 +39,7 @@ func (repo *Repository) CheckBranchName(name string) error {
if err != nil { if err != nil {
return err return err
} }
defer gitRepo.Close()
branches, err := repo.GetBranches() branches, err := repo.GetBranches()
if err != nil { 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) 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) 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 { if err = gitRepo.CreateBranch(branchName, oldBranchName); err != nil {
log.Error("Unable to create branch: %s from %s. (%v)", branchName, oldBranchName, err) 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) 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) 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 { if err = gitRepo.CreateBranch(branchName, commit); err != nil {
log.Error("Unable to create branch: %s from %s. (%v)", branchName, commit, err) log.Error("Unable to create branch: %s from %s. (%v)", branchName, commit, err)

View file

@ -111,17 +111,18 @@ func (repos MirrorRepositoryList) LoadAttributes() error {
// SearchRepoOptions holds the search options // SearchRepoOptions holds the search options
type SearchRepoOptions struct { type SearchRepoOptions struct {
UserID int64 UserID int64
UserIsAdmin bool UserIsAdmin bool
Keyword string Keyword string
OwnerID int64 OwnerID int64
OrderBy SearchOrderBy PriorityOwnerID int64
Private bool // Include private repositories in results OrderBy SearchOrderBy
StarredByID int64 Private bool // Include private repositories in results
Page int StarredByID int64
IsProfile bool Page int
AllPublic bool // Include also all public repositories IsProfile bool
PageSize int // Can be smaller than or equal to setting.ExplorePagingNum AllPublic bool // Include also all public repositories
PageSize int // Can be smaller than or equal to setting.ExplorePagingNum
// None -> include collaborative AND non-collaborative // None -> include collaborative AND non-collaborative
// True -> include just collaborative // True -> include just collaborative
// False -> incude just non-collaborative // False -> incude just non-collaborative
@ -130,6 +131,10 @@ type SearchRepoOptions struct {
// True -> include just forks // True -> include just forks
// False -> include just non-forks // False -> include just non-forks
Fork util.OptionalBool 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 // None -> include mirrors AND non-mirrors
// True -> include just mirrors // True -> include just mirrors
// False -> include just non-mirrors // False -> include just non-mirrors
@ -190,6 +195,10 @@ func SearchRepository(opts *SearchRepoOptions) (RepositoryList, int64, error) {
cond = cond.And(accessCond) cond = cond.And(accessCond)
} }
if opts.Template != util.OptionalBoolNone {
cond = cond.And(builder.Eq{"is_template": opts.Template == util.OptionalBoolTrue})
}
// Restrict to starred repositories // Restrict to starred repositories
if opts.StarredByID > 0 { if opts.StarredByID > 0 {
cond = cond.And(builder.In("id", builder.Select("repo_id").From("star").Where(builder.Eq{"uid": opts.StarredByID}))) 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 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() sess := x.NewSession()
defer sess.Close() 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}))), 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 // 2. Be able to see all repositories that we have access to
builder.In("`repository`.id", builder.Select("repo_id"). builder.Or(
From("`access`"). builder.In("`repository`.id", builder.Select("repo_id").
Where(builder.And( From("`access`").
builder.Eq{"user_id": userID}, Where(builder.And(
builder.Gt{"mode": int(AccessModeNone)}))), 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 // 3. Be able to see all repositories that we are in a team
builder.In("`repository`.id", builder.Select("`team_repo`.repo_id"). builder.In("`repository`.id", builder.Select("`team_repo`.repo_id").
From("team_repo"). From("team_repo").

View file

@ -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}, opts: &SearchRepoOptions{Keyword: "big_test_", Page: 1, PageSize: 10, Private: true, AllPublic: true, Collaborate: util.OptionalBoolFalse},
count: 14}, count: 14},
{name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative", {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}, count: 22},
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative", {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}, count: 28},
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName", {name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
opts: &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true}, 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}, opts: &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 18, Private: true, AllPublic: true},
count: 13}, count: 13},
{name: "AllPublic/PublicRepositoriesOfOrganization", {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}, count: 22},
{name: "AllTemplates",
opts: &SearchRepoOptions{Page: 1, PageSize: 10, Template: util.OptionalBoolTrue},
count: 2},
} }
for _, testCase := range testCases { for _, testCase := range testCases {

View file

@ -149,6 +149,7 @@ func (repo *Repository) SignWikiCommit(u *User) (bool, string) {
if err != nil { if err != nil {
return false, "" return false, ""
} }
defer gitRepo.Close()
commit, err := gitRepo.GetCommit("HEAD") commit, err := gitRepo.GetCommit("HEAD")
if err != nil { if err != nil {
return false, "" return false, ""
@ -194,6 +195,7 @@ func (repo *Repository) SignCRUDAction(u *User, tmpBasePath, parentCommit string
if err != nil { if err != nil {
return false, "" return false, ""
} }
defer gitRepo.Close()
commit, err := gitRepo.GetCommit(parentCommit) commit, err := gitRepo.GetCommit(parentCommit)
if err != nil { if err != nil {
return false, "" return false, ""
@ -242,6 +244,7 @@ func (repo *Repository) SignMerge(u *User, tmpBasePath, baseCommit, headCommit s
if err != nil { if err != nil {
return false, "" return false, ""
} }
defer gitRepo.Close()
} }
commit, err := gitRepo.GetCommit(baseCommit) commit, err := gitRepo.GetCommit(baseCommit)
if err != nil { if err != nil {
@ -257,6 +260,7 @@ func (repo *Repository) SignMerge(u *User, tmpBasePath, baseCommit, headCommit s
if err != nil { if err != nil {
return false, "" return false, ""
} }
defer gitRepo.Close()
} }
commit, err := gitRepo.GetCommit(headCommit) commit, err := gitRepo.GetCommit(headCommit)
if err != nil { if err != nil {
@ -272,6 +276,7 @@ func (repo *Repository) SignMerge(u *User, tmpBasePath, baseCommit, headCommit s
if err != nil { if err != nil {
return false, "" return false, ""
} }
defer gitRepo.Close()
} }
commit, err := gitRepo.GetCommit(headCommit) commit, err := gitRepo.GetCommit(headCommit)
if err != nil { if err != nil {

View file

@ -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())
}

View file

@ -4,42 +4,118 @@
package models 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. // Watch is connection request for receiving repository notification.
type Watch struct { type Watch struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"UNIQUE(watch)"` UserID int64 `xorm:"UNIQUE(watch)"`
RepoID 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 { // getWatch gets what kind of subscription a user has on a given repository; returns dummy record if none found
has, _ := e.Get(&Watch{UserID: userID, RepoID: repoID}) func getWatch(e Engine, userID, repoID int64) (Watch, error) {
return has 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. // IsWatching checks if user has watched given repository.
func IsWatching(userID, repoID int64) bool { 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) { func watchRepoMode(e Engine, watch Watch, mode RepoWatchMode) (err error) {
if watch { if watch.Mode == mode {
if isWatching(e, userID, repoID) { return nil
return nil }
} if mode == RepoWatchModeAuto && (watch.Mode == RepoWatchModeDont || isWatchMode(watch.Mode)) {
if _, err = e.Insert(&Watch{RepoID: repoID, UserID: userID}); err != nil { // 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 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 { } else {
if !isWatching(e, userID, repoID) { err = watchRepoMode(e, watch, RepoWatchModeNormal)
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)
} }
return err return err
} }
@ -52,6 +128,7 @@ func WatchRepo(userID, repoID int64, watch bool) (err error) {
func getWatchers(e Engine, repoID int64) ([]*Watch, error) { func getWatchers(e Engine, repoID int64) ([]*Watch, error) {
watches := make([]*Watch, 0, 10) watches := make([]*Watch, 0, 10)
return watches, e.Where("`watch`.repo_id=?", repoID). return watches, e.Where("`watch`.repo_id=?", repoID).
And("`watch`.mode<>?", RepoWatchModeDont).
And("`user`.is_active=?", true). And("`user`.is_active=?", true).
And("`user`.prohibit_login=?", false). And("`user`.prohibit_login=?", false).
Join("INNER", "`user`", "`user`.id = `watch`.user_id"). 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) { func (repo *Repository) GetWatchers(page int) ([]*User, error) {
users := make([]*User, 0, ItemsPerPage) users := make([]*User, 0, ItemsPerPage)
sess := x.Where("watch.repo_id=?", repo.ID). 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 { if page > 0 {
sess = sess.Limit(ItemsPerPage, (page-1)*ItemsPerPage) sess = sess.Limit(ItemsPerPage, (page-1)*ItemsPerPage)
} }
@ -137,3 +215,22 @@ func notifyWatchers(e Engine, act *Action) error {
func NotifyWatchers(act *Action) error { func NotifyWatchers(act *Action) error {
return notifyWatchers(x, act) 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)
}

View file

@ -7,6 +7,8 @@ package models
import ( import (
"testing" "testing"
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -15,8 +17,10 @@ func TestIsWatching(t *testing.T) {
assert.True(t, IsWatching(1, 1)) assert.True(t, IsWatching(1, 1))
assert.True(t, IsWatching(4, 1)) assert.True(t, IsWatching(4, 1))
assert.True(t, IsWatching(11, 1))
assert.False(t, IsWatching(1, 5)) assert.False(t, IsWatching(1, 5))
assert.False(t, IsWatching(8, 1))
assert.False(t, IsWatching(NonexistentID, NonexistentID)) assert.False(t, IsWatching(NonexistentID, NonexistentID))
} }
@ -78,7 +82,7 @@ func TestNotifyWatchers(t *testing.T) {
} }
assert.NoError(t, NotifyWatchers(action)) 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{ AssertExistsAndLoadBean(t, &Action{
ActUserID: action.ActUserID, ActUserID: action.ActUserID,
UserID: 8, UserID: 8,
@ -97,4 +101,88 @@ func TestNotifyWatchers(t *testing.T) {
RepoID: action.RepoID, RepoID: action.RepoID,
OpType: action.OpType, 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)
} }

View file

@ -1082,7 +1082,7 @@ func deleteUser(e *xorm.Session, u *User) error {
// ***** START: Watch ***** // ***** START: Watch *****
watchedRepoIDs := make([]int64, 0, 10) watchedRepoIDs := make([]int64, 0, 10)
if err = e.Table("watch").Cols("watch.repo_id"). 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) return fmt.Errorf("get all watches: %v", err)
} }
if _, err = e.Decr("num_watches").In("id", watchedRepoIDs).NoAutoTime().Update(new(Repository)); err != nil { 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 // GetWatchedRepos returns the repos watched by a particular user
func GetWatchedRepos(userID int64, private bool) ([]*Repository, error) { func GetWatchedRepos(userID int64, private bool) ([]*Repository, error) {
sess := x.Where("watch.user_id=?", userID). sess := x.Where("watch.user_id=?", userID).
And("`watch`.mode<>?", RepoWatchModeDont).
Join("LEFT", "watch", "`repository`.id=`watch`.repo_id") Join("LEFT", "watch", "`repository`.id=`watch`.repo_id")
if !private { if !private {
sess = sess.And("is_private=?", false) sess = sess.And("is_private=?", false)

View file

@ -153,7 +153,7 @@ func TestSearchUsers(t *testing.T) {
} }
testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1}, 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}, testUserSuccess(&SearchUserOptions{Page: 1, IsActive: util.OptionalBoolFalse},
[]int64{9}) []int64{9})
@ -373,3 +373,16 @@ func TestCreateUser_Issue5882(t *testing.T) {
assert.NoError(t, DeleteUser(v.user)) 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)
}

View file

@ -140,6 +140,7 @@ func (repo *Repository) updateWikiPage(doer *User, oldWikiName, newWikiName, con
log.Error("Unable to open temporary repository: %s (%v)", basePath, err) 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) return fmt.Errorf("Failed to open new temporary repository in: %s %v", basePath, err)
} }
defer gitRepo.Close()
if hasMasterBranch { if hasMasterBranch {
if err := gitRepo.ReadTreeToIndex("HEAD"); err != nil { 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) 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) return fmt.Errorf("Failed to open new temporary repository in: %s %v", basePath, err)
} }
defer gitRepo.Close()
if err := gitRepo.ReadTreeToIndex("HEAD"); err != nil { if err := gitRepo.ReadTreeToIndex("HEAD"); err != nil {
log.Error("Unable to read HEAD tree to index in: %s %v", basePath, err) log.Error("Unable to read HEAD tree to index in: %s %v", basePath, err)

View file

@ -161,6 +161,7 @@ func TestRepository_AddWikiPage(t *testing.T) {
// Now need to show that the page has been added: // Now need to show that the page has been added:
gitRepo, err := git.OpenRepository(repo.WikiPath()) gitRepo, err := git.OpenRepository(repo.WikiPath())
assert.NoError(t, err) assert.NoError(t, err)
defer gitRepo.Close()
masterTree, err := gitRepo.GetTree("master") masterTree, err := gitRepo.GetTree("master")
assert.NoError(t, err) assert.NoError(t, err)
wikiPath := WikiNameToFilename(wikiName) wikiPath := WikiNameToFilename(wikiName)
@ -214,6 +215,7 @@ func TestRepository_EditWikiPage(t *testing.T) {
_, err := masterTree.GetTreeEntryByPath("Home.md") _, err := masterTree.GetTreeEntryByPath("Home.md")
assert.Error(t, err) 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: // Now need to show that the page has been added:
gitRepo, err := git.OpenRepository(repo.WikiPath()) gitRepo, err := git.OpenRepository(repo.WikiPath())
assert.NoError(t, err) assert.NoError(t, err)
defer gitRepo.Close()
masterTree, err := gitRepo.GetTree("master") masterTree, err := gitRepo.GetTree("master")
assert.NoError(t, err) assert.NoError(t, err)
wikiPath := WikiNameToFilename("Home") wikiPath := WikiNameToFilename("Home")

View file

@ -36,6 +36,10 @@ type CreateRepoForm struct {
IssueLabels string IssueLabels string
License string License string
Readme string Readme string
RepoTemplate int64
GitContent bool
Topics bool
} }
// Validate validates the fields // Validate validates the fields
@ -107,6 +111,7 @@ type RepoSettingForm struct {
MirrorUsername string MirrorUsername string
MirrorPassword string MirrorPassword string
Private bool Private bool
Template bool
EnablePrune bool EnablePrune bool
// Advanced settings // Advanced settings

View file

@ -186,7 +186,16 @@ func ReferencesGitRepo(allowEmpty bool) macaron.Handler {
return return
} }
ctx.Repo.GitRepo = gitRepo 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()
} }
} }

View file

@ -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. // ComposeGoGetImport returns go-get-import meta content.
func ComposeGoGetImport(owner, repo string) string { func ComposeGoGetImport(owner, repo string) string {
/// setting.AppUrl is guaranteed to be parse as url /// 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 // Disable everything when the repo is being created
if ctx.Repo.Repository.IsBeingCreated() { if ctx.Repo.Repository.IsBeingCreated() {
ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
@ -427,9 +454,18 @@ func RepoAssignment() macaron.Handler {
} }
ctx.Repo.GitRepo = gitRepo 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. // Stop at this point when the repo is empty.
if ctx.Repo.Repository.IsEmpty { if ctx.Repo.Repository.IsEmpty {
ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
ctx.Next()
return return
} }
@ -488,6 +524,7 @@ func RepoAssignment() macaron.Handler {
ctx.Data["GoDocDirectory"] = prefix + "{/dir}" ctx.Data["GoDocDirectory"] = prefix + "{/dir}"
ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}" ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}"
} }
ctx.Next()
} }
} }
@ -518,6 +555,22 @@ func RepoRef() macaron.Handler {
return RepoRefByType(RepoRefBranch) 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 { func getRefNameFromPath(ctx *Context, path string, isExist func(string) bool) string {
refName := "" refName := ""
parts := strings.Split(path, "/") parts := strings.Split(path, "/")
@ -593,6 +646,13 @@ func RepoRefByType(refType RepoRefType) macaron.Handler {
ctx.ServerError("RepoRef Invalid repo "+repoPath, err) ctx.ServerError("RepoRef Invalid repo "+repoPath, err)
return 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. // Get default branch.
@ -623,7 +683,7 @@ func RepoRefByType(refType RepoRefType) macaron.Handler {
} else { } else {
refName = getRefName(ctx, refType) refName = getRefName(ctx, refType)
ctx.Repo.BranchName = refName ctx.Repo.BranchName = refName
if ctx.Repo.GitRepo.IsBranchExist(refName) { if refType.RefTypeIncludesBranches() && ctx.Repo.GitRepo.IsBranchExist(refName) {
ctx.Repo.IsViewBranch = true ctx.Repo.IsViewBranch = true
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName) 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() 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.IsViewTag = true
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName) ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName)
if err != nil { if err != nil {
@ -681,6 +741,8 @@ func RepoRefByType(refType RepoRefType) macaron.Handler {
return return
} }
ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
ctx.Next()
} }
} }

View file

@ -87,10 +87,11 @@ func (r *BlameReader) Close() error {
// CreateBlameReader creates reader for given repository, commit and file // CreateBlameReader creates reader for given repository, commit and file
func CreateBlameReader(repoPath, commitID, file string) (*BlameReader, error) { func CreateBlameReader(repoPath, commitID, file string) (*BlameReader, error) {
_, err := OpenRepository(repoPath) gitRepo, err := OpenRepository(repoPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
gitRepo.Close()
return createBlameReader(repoPath, GitExecutable, "blame", commitID, "--porcelain", "--", file) return createBlameReader(repoPath, GitExecutable, "blame", commitID, "--porcelain", "--", file)
} }

View file

@ -37,6 +37,8 @@ THE SOFTWARE.
` `
repo, err := OpenRepository("../../.git") repo, err := OpenRepository("../../.git")
assert.NoError(t, err) assert.NoError(t, err)
defer repo.Close()
testBlob, err := repo.GetBlob("a8d4b49dd073a4a38a7e58385eeff7cc52568697") testBlob, err := repo.GetBlob("a8d4b49dd073a4a38a7e58385eeff7cc52568697")
assert.NoError(t, err) assert.NoError(t, err)
@ -55,6 +57,8 @@ func Benchmark_Blob_Data(b *testing.B) {
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
defer repo.Close()
testBlob, err := repo.GetBlob("a8d4b49dd073a4a38a7e58385eeff7cc52568697") testBlob, err := repo.GetBlob("a8d4b49dd073a4a38a7e58385eeff7cc52568697")
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)

View file

@ -9,6 +9,7 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"os"
"os/exec" "os/exec"
"strings" "strings"
"time" "time"
@ -24,6 +25,9 @@ var (
DefaultCommandExecutionTimeout = 60 * time.Second 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. // Command represents a command with its subcommands or arguments.
type Command struct { type Command struct {
name string 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, // 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. // 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 { 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 { if timeout == -1 {
timeout = DefaultCommandExecutionTimeout timeout = DefaultCommandExecutionTimeout
} }
@ -77,7 +88,12 @@ func (c *Command) RunInDirTimeoutEnvFullPipeline(env []string, timeout time.Dura
defer cancel() defer cancel()
cmd := exec.CommandContext(ctx, c.name, c.args...) 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.Dir = dir
cmd.Stdout = stdout cmd.Stdout = stdout
cmd.Stderr = stderr 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) 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) defer process.GetManager().Remove(pid)
if fn != nil {
fn(ctx, cancel)
}
if err := cmd.Wait(); err != nil { if err := cmd.Wait(); err != nil {
return err return err
} }

View file

@ -77,6 +77,8 @@ func TestEntries_GetCommitsInfo(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
bareRepo1, err := OpenRepository(bareRepo1Path) bareRepo1, err := OpenRepository(bareRepo1Path)
assert.NoError(t, err) assert.NoError(t, err)
defer bareRepo1.Close()
testGetCommitsInfo(t, bareRepo1) testGetCommitsInfo(t, bareRepo1)
clonedPath, err := cloneRepo(bareRepo1Path, testReposDir, "repo1_TestEntries_GetCommitsInfo") clonedPath, err := cloneRepo(bareRepo1Path, testReposDir, "repo1_TestEntries_GetCommitsInfo")
@ -84,6 +86,8 @@ func TestEntries_GetCommitsInfo(t *testing.T) {
defer os.RemoveAll(clonedPath) defer os.RemoveAll(clonedPath)
clonedRepo1, err := OpenRepository(clonedPath) clonedRepo1, err := OpenRepository(clonedPath)
assert.NoError(t, err) assert.NoError(t, err)
defer clonedRepo1.Close()
testGetCommitsInfo(t, clonedRepo1) testGetCommitsInfo(t, clonedRepo1)
} }
@ -101,13 +105,16 @@ func BenchmarkEntries_GetCommitsInfo(b *testing.B) {
for _, benchmark := range benchmarks { for _, benchmark := range benchmarks {
var commit *Commit var commit *Commit
var entries Entries var entries Entries
var repo *Repository
if repoPath, err := cloneRepo(benchmark.url, benchmarkReposDir, benchmark.name); err != nil { if repoPath, err := cloneRepo(benchmark.url, benchmarkReposDir, benchmark.name); err != nil {
b.Fatal(err) b.Fatal(err)
} else if repo, err := OpenRepository(repoPath); err != nil { } else if repo, err = OpenRepository(repoPath); err != nil {
b.Fatal(err) b.Fatal(err)
} else if commit, err = repo.GetBranchCommit("master"); err != nil { } else if commit, err = repo.GetBranchCommit("master"); err != nil {
repo.Close()
b.Fatal(err) b.Fatal(err)
} else if entries, err = commit.Tree.ListEntries(); err != nil { } else if entries, err = commit.Tree.ListEntries(); err != nil {
repo.Close()
b.Fatal(err) b.Fatal(err)
} }
entries.Sort() entries.Sort()
@ -120,5 +127,6 @@ func BenchmarkEntries_GetCommitsInfo(b *testing.B) {
} }
} }
}) })
repo.Close()
} }
} }

View file

@ -15,6 +15,7 @@ func TestGetNotes(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
bareRepo1, err := OpenRepository(bareRepo1Path) bareRepo1, err := OpenRepository(bareRepo1Path)
assert.NoError(t, err) assert.NoError(t, err)
defer bareRepo1.Close()
note := Note{} note := Note{}
err = GetNote(bareRepo1, "95bb4d39648ee7e325106df01a621c530863a653", &note) err = GetNote(bareRepo1, "95bb4d39648ee7e325106df01a621c530863a653", &note)
@ -27,6 +28,7 @@ func TestGetNestedNotes(t *testing.T) {
repoPath := filepath.Join(testReposDir, "repo3_notes") repoPath := filepath.Join(testReposDir, "repo3_notes")
repo, err := OpenRepository(repoPath) repo, err := OpenRepository(repoPath)
assert.NoError(t, err) assert.NoError(t, err)
defer repo.Close()
note := Note{} note := Note{}
err = GetNote(repo, "3e668dbfac39cbc80a9ff9c61eb565d944453ba4", &note) err = GetNote(repo, "3e668dbfac39cbc80a9ff9c61eb565d944453ba4", &note)

View file

@ -17,6 +17,7 @@ import (
"strings" "strings"
"time" "time"
gitealog "code.gitea.io/gitea/modules/log"
"github.com/unknwon/com" "github.com/unknwon/com"
"gopkg.in/src-d/go-billy.v4/osfs" "gopkg.in/src-d/go-billy.v4/osfs"
gogit "gopkg.in/src-d/go-git.v4" gogit "gopkg.in/src-d/go-git.v4"
@ -122,6 +123,16 @@ func OpenRepository(repoPath string) (*Repository, error) {
}, nil }, 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 // GoGitRepo gets the go-git repo representation
func (repo *Repository) GoGitRepo() *gogit.Repository { func (repo *Repository) GoGitRepo() *gogit.Repository {
return repo.gogitRepo return repo.gogitRepo
@ -304,8 +315,8 @@ const (
statSizeGarbage = "size-garbage: " statSizeGarbage = "size-garbage: "
) )
// GetRepoSize returns disk consumption for repo in path // CountObjects returns the results of git count-objects on the repoPath
func GetRepoSize(repoPath string) (*CountObject, error) { func CountObjects(repoPath string) (*CountObject, error) {
cmd := NewCommand("count-objects", "-v") cmd := NewCommand("count-objects", "-v")
stdout, err := cmd.RunInDir(repoPath) stdout, err := cmd.RunInDir(repoPath)
if err != nil { if err != nil {

View file

@ -17,6 +17,7 @@ func TestRepository_GetBlob_Found(t *testing.T) {
repoPath := filepath.Join(testReposDir, "repo1_bare") repoPath := filepath.Join(testReposDir, "repo1_bare")
r, err := OpenRepository(repoPath) r, err := OpenRepository(repoPath)
assert.NoError(t, err) assert.NoError(t, err)
defer r.Close()
testCases := []struct { testCases := []struct {
OID string OID string
@ -44,6 +45,7 @@ func TestRepository_GetBlob_NotExist(t *testing.T) {
repoPath := filepath.Join(testReposDir, "repo1_bare") repoPath := filepath.Join(testReposDir, "repo1_bare")
r, err := OpenRepository(repoPath) r, err := OpenRepository(repoPath)
assert.NoError(t, err) assert.NoError(t, err)
defer r.Close()
testCase := "0000000000000000000000000000000000000000" testCase := "0000000000000000000000000000000000000000"
testError := ErrNotExist{testCase, ""} testError := ErrNotExist{testCase, ""}
@ -57,6 +59,7 @@ func TestRepository_GetBlob_NoId(t *testing.T) {
repoPath := filepath.Join(testReposDir, "repo1_bare") repoPath := filepath.Join(testReposDir, "repo1_bare")
r, err := OpenRepository(repoPath) r, err := OpenRepository(repoPath)
assert.NoError(t, err) assert.NoError(t, err)
defer r.Close()
testCase := "" testCase := ""
testError := fmt.Errorf("Length must be 40: %s", testCase) testError := fmt.Errorf("Length must be 40: %s", testCase)

View file

@ -108,6 +108,7 @@ func GetBranchesByPath(path string) ([]*Branch, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer gitRepo.Close()
brs, err := gitRepo.GetBranches() brs, err := gitRepo.GetBranches()
if err != nil { if err != nil {

View file

@ -15,6 +15,7 @@ func TestRepository_GetBranches(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
bareRepo1, err := OpenRepository(bareRepo1Path) bareRepo1, err := OpenRepository(bareRepo1Path)
assert.NoError(t, err) assert.NoError(t, err)
defer bareRepo1.Close()
branches, err := bareRepo1.GetBranches() branches, err := bareRepo1.GetBranches()
@ -29,6 +30,7 @@ func BenchmarkRepository_GetBranches(b *testing.B) {
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
defer bareRepo1.Close()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, err := bareRepo1.GetBranches() _, err := bareRepo1.GetBranches()

View file

@ -15,6 +15,7 @@ func TestRepository_GetCommitBranches(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
bareRepo1, err := OpenRepository(bareRepo1Path) bareRepo1, err := OpenRepository(bareRepo1Path)
assert.NoError(t, err) assert.NoError(t, err)
defer bareRepo1.Close()
// these test case are specific to the repo1_bare test repo // these test case are specific to the repo1_bare test repo
testCases := []struct { testCases := []struct {
@ -41,6 +42,7 @@ func TestGetTagCommitWithSignature(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
bareRepo1, err := OpenRepository(bareRepo1Path) bareRepo1, err := OpenRepository(bareRepo1Path)
assert.NoError(t, err) assert.NoError(t, err)
defer bareRepo1.Close()
commit, err := bareRepo1.GetCommit("3ad28a9149a2864384548f3d17ed7f38014c9e8a") commit, err := bareRepo1.GetCommit("3ad28a9149a2864384548f3d17ed7f38014c9e8a")
assert.NoError(t, err) assert.NoError(t, err)
@ -54,6 +56,7 @@ func TestGetCommitWithBadCommitID(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
bareRepo1, err := OpenRepository(bareRepo1Path) bareRepo1, err := OpenRepository(bareRepo1Path)
assert.NoError(t, err) assert.NoError(t, err)
defer bareRepo1.Close()
commit, err := bareRepo1.GetCommit("bad_branch") commit, err := bareRepo1.GetCommit("bad_branch")
assert.Nil(t, commit) assert.Nil(t, commit)

Some files were not shown because too many files have changed in this diff Show more