mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-29 10:57:44 +09:00 
			
		
		
		
	Move mirror to a standalone package from models (#7486)
* move mirror to a standalone package * fix mirror address in template * fix tests * fix lint * fix comment * fix tests * fix tests * fix vendor * fix fmt * fix lint * remove wrong file submitted * fix conflict * remove unrelated changes * fix go mod * fix tests * clean go mod * make vendor work * make vendor work * fix tests * remove duplicated test
This commit is contained in:
		
							
								
								
									
										6
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								go.sum
									
									
									
									
									
								
							| @@ -302,6 +302,7 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ | |||||||
| github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= | ||||||
| github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= | ||||||
| github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= | ||||||
|  | github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= | ||||||
| github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= | github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= | ||||||
| github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4= | github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4= | ||||||
| github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= | github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= | ||||||
| @@ -447,9 +448,11 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn | |||||||
| github.com/oliamb/cutter v0.2.2 h1:Lfwkya0HHNU1YLnGv2hTkzHfasrSMkgv4Dn+5rmlk3k= | github.com/oliamb/cutter v0.2.2 h1:Lfwkya0HHNU1YLnGv2hTkzHfasrSMkgv4Dn+5rmlk3k= | ||||||
| github.com/oliamb/cutter v0.2.2/go.mod h1:4BenG2/4GuRBDbVm/OPahDVqbrOemzpPiG5mi1iryBU= | github.com/oliamb/cutter v0.2.2/go.mod h1:4BenG2/4GuRBDbVm/OPahDVqbrOemzpPiG5mi1iryBU= | ||||||
| github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | ||||||
|  | github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= | ||||||
| github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | ||||||
| github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= | github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= | ||||||
| github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | ||||||
|  | github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= | ||||||
| github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= | ||||||
| github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= | github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= | ||||||
| github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= | github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= | ||||||
| @@ -527,6 +530,7 @@ github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z | |||||||
| github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= | ||||||
| github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= | ||||||
| github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= | ||||||
|  | github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY= | ||||||
| github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= | github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= | ||||||
| github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w= | github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w= | ||||||
| github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= | github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= | ||||||
| @@ -560,6 +564,7 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 | |||||||
| github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||||||
| github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= | ||||||
| github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | ||||||
|  | github.com/syndtr/goleveldb v0.0.0-20190203031304-2f17a3356c66 h1:AwmkkZT+TucFotNCL+aNJ/0KCMsRtlXN9fs8uoOMSRk= | ||||||
| github.com/syndtr/goleveldb v0.0.0-20190203031304-2f17a3356c66/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= | github.com/syndtr/goleveldb v0.0.0-20190203031304-2f17a3356c66/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= | ||||||
| github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= | github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= | ||||||
| github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= | github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= | ||||||
| @@ -745,6 +750,7 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl | |||||||
| google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= | google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= | ||||||
| google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= | ||||||
| google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= | ||||||
|  | google.golang.org/appengine v1.6.0 h1:Tfd7cKwKbFRsI8RMAD3oqqw7JPFRrvFlOsfbgVkjOOw= | ||||||
| google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= | google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= | ||||||
| google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= | google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= | ||||||
| google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= | ||||||
|   | |||||||
| @@ -1720,6 +1720,12 @@ func UpdateRepository(repo *Repository, visibilityChanged bool) (err error) { | |||||||
| 	return sess.Commit() | 	return sess.Commit() | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // UpdateRepositoryUpdatedTime updates a repository's updated time | ||||||
|  | func UpdateRepositoryUpdatedTime(repoID int64, updateTime time.Time) error { | ||||||
|  | 	_, err := x.Exec("UPDATE repository SET updated_unix = ? WHERE id = ?", updateTime.Unix(), repoID) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  |  | ||||||
| // UpdateRepositoryUnits updates a repository's units | // UpdateRepositoryUnits updates a repository's units | ||||||
| func UpdateRepositoryUnits(repo *Repository, units []RepoUnit) (err error) { | func UpdateRepositoryUnits(repo *Repository, units []RepoUnit) (err error) { | ||||||
| 	sess := x.NewSession() | 	sess := x.NewSession() | ||||||
|   | |||||||
| @@ -6,28 +6,14 @@ | |||||||
| package models | package models | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" |  | ||||||
| 	"net/url" |  | ||||||
| 	"strings" |  | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/modules/cache" |  | ||||||
| 	"code.gitea.io/gitea/modules/git" |  | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/process" |  | ||||||
| 	"code.gitea.io/gitea/modules/setting" |  | ||||||
| 	"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/go-xorm/xorm" | 	"github.com/go-xorm/xorm" | ||||||
| 	"github.com/mcuadros/go-version" |  | ||||||
| 	"github.com/unknwon/com" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // MirrorQueue holds an UniqueQueue object of the mirror |  | ||||||
| var MirrorQueue = sync.NewUniqueQueue(setting.Repository.MirrorQueueLength) |  | ||||||
|  |  | ||||||
| // Mirror represents mirror information of a repository. | // Mirror represents mirror information of a repository. | ||||||
| type Mirror struct { | type Mirror struct { | ||||||
| 	ID          int64       `xorm:"pk autoincr"` | 	ID          int64       `xorm:"pk autoincr"` | ||||||
| @@ -39,7 +25,7 @@ type Mirror struct { | |||||||
| 	UpdatedUnix    timeutil.TimeStamp `xorm:"INDEX"` | 	UpdatedUnix    timeutil.TimeStamp `xorm:"INDEX"` | ||||||
| 	NextUpdateUnix timeutil.TimeStamp `xorm:"INDEX"` | 	NextUpdateUnix timeutil.TimeStamp `xorm:"INDEX"` | ||||||
|  |  | ||||||
| 	address string `xorm:"-"` | 	Address string `xorm:"-"` | ||||||
| } | } | ||||||
|  |  | ||||||
| // BeforeInsert will be invoked by XORM before inserting a record | // BeforeInsert will be invoked by XORM before inserting a record | ||||||
| @@ -72,258 +58,6 @@ func (m *Mirror) ScheduleNextUpdate() { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func remoteAddress(repoPath string) (string, error) { |  | ||||||
| 	var cmd *git.Command |  | ||||||
| 	binVersion, err := git.BinVersion() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 	if version.Compare(binVersion, "2.7", ">=") { |  | ||||||
| 		cmd = git.NewCommand("remote", "get-url", "origin") |  | ||||||
| 	} else { |  | ||||||
| 		cmd = git.NewCommand("config", "--get", "remote.origin.url") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	result, err := cmd.RunInDir(repoPath) |  | ||||||
| 	if err != nil { |  | ||||||
| 		if strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") { |  | ||||||
| 			return "", nil |  | ||||||
| 		} |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 	if len(result) > 0 { |  | ||||||
| 		return result[:len(result)-1], nil |  | ||||||
| 	} |  | ||||||
| 	return "", nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *Mirror) readAddress() { |  | ||||||
| 	if len(m.address) > 0 { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	var err error |  | ||||||
| 	m.address, err = remoteAddress(m.Repo.RepoPath()) |  | ||||||
| 	if err != nil { |  | ||||||
| 		log.Error("remoteAddress: %v", err) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // sanitizeOutput sanitizes output of a command, replacing occurrences of the |  | ||||||
| // repository's remote address with a sanitized version. |  | ||||||
| func sanitizeOutput(output, repoPath string) (string, error) { |  | ||||||
| 	remoteAddr, err := remoteAddress(repoPath) |  | ||||||
| 	if err != nil { |  | ||||||
| 		// if we're unable to load the remote address, then we're unable to |  | ||||||
| 		// sanitize. |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 	return util.SanitizeMessage(output, remoteAddr), nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Address returns mirror address from Git repository config with credentials censored. |  | ||||||
| func (m *Mirror) Address() string { |  | ||||||
| 	m.readAddress() |  | ||||||
| 	return util.SanitizeURLCredentials(m.address, false) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // FullAddress returns mirror address from Git repository config. |  | ||||||
| func (m *Mirror) FullAddress() string { |  | ||||||
| 	m.readAddress() |  | ||||||
| 	return m.address |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // AddressNoCredentials returns mirror address from Git repository config without credentials. |  | ||||||
| func (m *Mirror) AddressNoCredentials() string { |  | ||||||
| 	m.readAddress() |  | ||||||
| 	u, err := url.Parse(m.address) |  | ||||||
| 	if err != nil { |  | ||||||
| 		// this shouldn't happen but just return it unsanitised |  | ||||||
| 		return m.address |  | ||||||
| 	} |  | ||||||
| 	u.User = nil |  | ||||||
| 	return u.String() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Username returns the mirror address username |  | ||||||
| func (m *Mirror) Username() string { |  | ||||||
| 	m.readAddress() |  | ||||||
| 	u, err := url.Parse(m.address) |  | ||||||
| 	if err != nil { |  | ||||||
| 		// this shouldn't happen but if it does return "" |  | ||||||
| 		return "" |  | ||||||
| 	} |  | ||||||
| 	return u.User.Username() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Password returns the mirror address password |  | ||||||
| func (m *Mirror) Password() string { |  | ||||||
| 	m.readAddress() |  | ||||||
| 	u, err := url.Parse(m.address) |  | ||||||
| 	if err != nil { |  | ||||||
| 		// this shouldn't happen but if it does return "" |  | ||||||
| 		return "" |  | ||||||
| 	} |  | ||||||
| 	password, _ := u.User.Password() |  | ||||||
| 	return password |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // SaveAddress writes new address to Git repository config. |  | ||||||
| func (m *Mirror) SaveAddress(addr string) error { |  | ||||||
| 	repoPath := m.Repo.RepoPath() |  | ||||||
| 	// Remove old origin |  | ||||||
| 	_, err := git.NewCommand("remote", "remove", "origin").RunInDir(repoPath) |  | ||||||
| 	if err != nil && !strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	_, err = git.NewCommand("remote", "add", "origin", "--mirror=fetch", addr).RunInDir(repoPath) |  | ||||||
| 	return err |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // gitShortEmptySha Git short empty SHA |  | ||||||
| const gitShortEmptySha = "0000000" |  | ||||||
|  |  | ||||||
| // mirrorSyncResult contains information of a updated reference. |  | ||||||
| // If the oldCommitID is "0000000", it means a new reference, the value of newCommitID is empty. |  | ||||||
| // If the newCommitID is "0000000", it means the reference is deleted, the value of oldCommitID is empty. |  | ||||||
| type mirrorSyncResult struct { |  | ||||||
| 	refName     string |  | ||||||
| 	oldCommitID string |  | ||||||
| 	newCommitID string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // parseRemoteUpdateOutput detects create, update and delete operations of references from upstream. |  | ||||||
| func parseRemoteUpdateOutput(output string) []*mirrorSyncResult { |  | ||||||
| 	results := make([]*mirrorSyncResult, 0, 3) |  | ||||||
| 	lines := strings.Split(output, "\n") |  | ||||||
| 	for i := range lines { |  | ||||||
| 		// Make sure reference name is presented before continue |  | ||||||
| 		idx := strings.Index(lines[i], "-> ") |  | ||||||
| 		if idx == -1 { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		refName := lines[i][idx+3:] |  | ||||||
|  |  | ||||||
| 		switch { |  | ||||||
| 		case strings.HasPrefix(lines[i], " * "): // New reference |  | ||||||
| 			results = append(results, &mirrorSyncResult{ |  | ||||||
| 				refName:     refName, |  | ||||||
| 				oldCommitID: gitShortEmptySha, |  | ||||||
| 			}) |  | ||||||
| 		case strings.HasPrefix(lines[i], " - "): // Delete reference |  | ||||||
| 			results = append(results, &mirrorSyncResult{ |  | ||||||
| 				refName:     refName, |  | ||||||
| 				newCommitID: gitShortEmptySha, |  | ||||||
| 			}) |  | ||||||
| 		case strings.HasPrefix(lines[i], "   "): // New commits of a reference |  | ||||||
| 			delimIdx := strings.Index(lines[i][3:], " ") |  | ||||||
| 			if delimIdx == -1 { |  | ||||||
| 				log.Error("SHA delimiter not found: %q", lines[i]) |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			shas := strings.Split(lines[i][3:delimIdx+3], "..") |  | ||||||
| 			if len(shas) != 2 { |  | ||||||
| 				log.Error("Expect two SHAs but not what found: %q", lines[i]) |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			results = append(results, &mirrorSyncResult{ |  | ||||||
| 				refName:     refName, |  | ||||||
| 				oldCommitID: shas[0], |  | ||||||
| 				newCommitID: shas[1], |  | ||||||
| 			}) |  | ||||||
|  |  | ||||||
| 		default: |  | ||||||
| 			log.Warn("parseRemoteUpdateOutput: unexpected update line %q", lines[i]) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return results |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // runSync returns true if sync finished without error. |  | ||||||
| func (m *Mirror) runSync() ([]*mirrorSyncResult, bool) { |  | ||||||
| 	repoPath := m.Repo.RepoPath() |  | ||||||
| 	wikiPath := m.Repo.WikiPath() |  | ||||||
| 	timeout := time.Duration(setting.Git.Timeout.Mirror) * time.Second |  | ||||||
|  |  | ||||||
| 	gitArgs := []string{"remote", "update"} |  | ||||||
| 	if m.EnablePrune { |  | ||||||
| 		gitArgs = append(gitArgs, "--prune") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	_, stderr, err := process.GetManager().ExecDir( |  | ||||||
| 		timeout, repoPath, fmt.Sprintf("Mirror.runSync: %s", repoPath), |  | ||||||
| 		git.GitExecutable, gitArgs...) |  | ||||||
| 	if err != nil { |  | ||||||
| 		// sanitize the output, since it may contain the remote address, which may |  | ||||||
| 		// contain a password |  | ||||||
| 		message, err := sanitizeOutput(stderr, repoPath) |  | ||||||
| 		if err != nil { |  | ||||||
| 			log.Error("sanitizeOutput: %v", err) |  | ||||||
| 			return nil, false |  | ||||||
| 		} |  | ||||||
| 		desc := fmt.Sprintf("Failed to update mirror repository '%s': %s", repoPath, message) |  | ||||||
| 		log.Error(desc) |  | ||||||
| 		if err = CreateRepositoryNotice(desc); err != nil { |  | ||||||
| 			log.Error("CreateRepositoryNotice: %v", err) |  | ||||||
| 		} |  | ||||||
| 		return nil, false |  | ||||||
| 	} |  | ||||||
| 	output := stderr |  | ||||||
|  |  | ||||||
| 	gitRepo, err := git.OpenRepository(repoPath) |  | ||||||
| 	if err != nil { |  | ||||||
| 		log.Error("OpenRepository: %v", err) |  | ||||||
| 		return nil, false |  | ||||||
| 	} |  | ||||||
| 	if err = SyncReleasesWithTags(m.Repo, gitRepo); err != nil { |  | ||||||
| 		log.Error("Failed to synchronize tags to releases for repository: %v", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if err := m.Repo.UpdateSize(); err != nil { |  | ||||||
| 		log.Error("Failed to update size for mirror repository: %v", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if m.Repo.HasWiki() { |  | ||||||
| 		if _, stderr, err := process.GetManager().ExecDir( |  | ||||||
| 			timeout, wikiPath, fmt.Sprintf("Mirror.runSync: %s", wikiPath), |  | ||||||
| 			git.GitExecutable, "remote", "update", "--prune"); err != nil { |  | ||||||
| 			// sanitize the output, since it may contain the remote address, which may |  | ||||||
| 			// contain a password |  | ||||||
| 			message, err := sanitizeOutput(stderr, wikiPath) |  | ||||||
| 			if err != nil { |  | ||||||
| 				log.Error("sanitizeOutput: %v", err) |  | ||||||
| 				return nil, false |  | ||||||
| 			} |  | ||||||
| 			desc := fmt.Sprintf("Failed to update mirror wiki repository '%s': %s", wikiPath, message) |  | ||||||
| 			log.Error(desc) |  | ||||||
| 			if err = CreateRepositoryNotice(desc); err != nil { |  | ||||||
| 				log.Error("CreateRepositoryNotice: %v", err) |  | ||||||
| 			} |  | ||||||
| 			return nil, false |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	branches, err := m.Repo.GetBranches() |  | ||||||
| 	if err != nil { |  | ||||||
| 		log.Error("GetBranches: %v", err) |  | ||||||
| 		return nil, false |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for i := range branches { |  | ||||||
| 		cache.Remove(m.Repo.GetCommitsCountCacheKey(branches[i].Name, true)) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	m.UpdatedUnix = timeutil.TimeStampNow() |  | ||||||
| 	return parseRemoteUpdateOutput(output), true |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // RunMirrorSync will invoke Mirror's runSync |  | ||||||
| func RunMirrorSync(mirror *Mirror) bool { |  | ||||||
| 	_, ok := mirror.runSync() |  | ||||||
| 	return ok |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func getMirrorByRepoID(e Engine, repoID int64) (*Mirror, error) { | func getMirrorByRepoID(e Engine, repoID int64) (*Mirror, error) { | ||||||
| 	m := &Mirror{RepoID: repoID} | 	m := &Mirror{RepoID: repoID} | ||||||
| 	has, err := e.Get(m) | 	has, err := e.Get(m) | ||||||
| @@ -356,129 +90,10 @@ func DeleteMirrorByRepoID(repoID int64) error { | |||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|  |  | ||||||
| // MirrorUpdate checks and updates mirror repositories. | // MirrorsIterate iterates all mirror repositories. | ||||||
| func MirrorUpdate() { | func MirrorsIterate(f func(idx int, bean interface{}) error) error { | ||||||
| 	log.Trace("Doing: MirrorUpdate") | 	return x. | ||||||
|  |  | ||||||
| 	if err := x. |  | ||||||
| 		Where("next_update_unix<=?", time.Now().Unix()). | 		Where("next_update_unix<=?", time.Now().Unix()). | ||||||
| 		And("next_update_unix!=0"). | 		And("next_update_unix!=0"). | ||||||
| 		Iterate(new(Mirror), func(idx int, bean interface{}) error { | 		Iterate(new(Mirror), f) | ||||||
| 			m := bean.(*Mirror) |  | ||||||
| 			if m.Repo == nil { |  | ||||||
| 				log.Error("Disconnected mirror repository found: %d", m.ID) |  | ||||||
| 				return nil |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			MirrorQueue.Add(m.RepoID) |  | ||||||
| 			return nil |  | ||||||
| 		}); err != nil { |  | ||||||
| 		log.Error("MirrorUpdate: %v", err) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // SyncMirrors checks and syncs mirrors. |  | ||||||
| // TODO: sync more mirrors at same time. |  | ||||||
| func SyncMirrors() { |  | ||||||
| 	sess := x.NewSession() |  | ||||||
| 	defer sess.Close() |  | ||||||
| 	// Start listening on new sync requests. |  | ||||||
| 	for repoID := range MirrorQueue.Queue() { |  | ||||||
| 		log.Trace("SyncMirrors [repo_id: %v]", repoID) |  | ||||||
| 		MirrorQueue.Remove(repoID) |  | ||||||
|  |  | ||||||
| 		m, err := GetMirrorByRepoID(com.StrTo(repoID).MustInt64()) |  | ||||||
| 		if err != nil { |  | ||||||
| 			log.Error("GetMirrorByRepoID [%s]: %v", repoID, err) |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		results, ok := m.runSync() |  | ||||||
| 		if !ok { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		m.ScheduleNextUpdate() |  | ||||||
| 		if err = updateMirror(sess, m); err != nil { |  | ||||||
| 			log.Error("UpdateMirror [%s]: %v", repoID, err) |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		var gitRepo *git.Repository |  | ||||||
| 		if len(results) == 0 { |  | ||||||
| 			log.Trace("SyncMirrors [repo_id: %d]: no commits fetched", m.RepoID) |  | ||||||
| 		} else { |  | ||||||
| 			gitRepo, err = git.OpenRepository(m.Repo.RepoPath()) |  | ||||||
| 			if err != nil { |  | ||||||
| 				log.Error("OpenRepository [%d]: %v", m.RepoID, err) |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		for _, result := range results { |  | ||||||
| 			// Discard GitHub pull requests, i.e. refs/pull/* |  | ||||||
| 			if strings.HasPrefix(result.refName, "refs/pull/") { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// Create reference |  | ||||||
| 			if result.oldCommitID == gitShortEmptySha { |  | ||||||
| 				if err = MirrorSyncCreateAction(m.Repo, result.refName); err != nil { |  | ||||||
| 					log.Error("MirrorSyncCreateAction [repo_id: %d]: %v", m.RepoID, err) |  | ||||||
| 				} |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// Delete reference |  | ||||||
| 			if result.newCommitID == gitShortEmptySha { |  | ||||||
| 				if err = MirrorSyncDeleteAction(m.Repo, result.refName); err != nil { |  | ||||||
| 					log.Error("MirrorSyncDeleteAction [repo_id: %d]: %v", m.RepoID, err) |  | ||||||
| 				} |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// Push commits |  | ||||||
| 			oldCommitID, err := git.GetFullCommitID(gitRepo.Path, result.oldCommitID) |  | ||||||
| 			if err != nil { |  | ||||||
| 				log.Error("GetFullCommitID [%d]: %v", m.RepoID, err) |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			newCommitID, err := git.GetFullCommitID(gitRepo.Path, result.newCommitID) |  | ||||||
| 			if err != nil { |  | ||||||
| 				log.Error("GetFullCommitID [%d]: %v", m.RepoID, err) |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			commits, err := gitRepo.CommitsBetweenIDs(newCommitID, oldCommitID) |  | ||||||
| 			if err != nil { |  | ||||||
| 				log.Error("CommitsBetweenIDs [repo_id: %d, new_commit_id: %s, old_commit_id: %s]: %v", m.RepoID, newCommitID, oldCommitID, err) |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			if err = MirrorSyncPushAction(m.Repo, MirrorSyncPushActionOptions{ |  | ||||||
| 				RefName:     result.refName, |  | ||||||
| 				OldCommitID: oldCommitID, |  | ||||||
| 				NewCommitID: newCommitID, |  | ||||||
| 				Commits:     ListToPushCommits(commits), |  | ||||||
| 			}); err != nil { |  | ||||||
| 				log.Error("MirrorSyncPushAction [repo_id: %d]: %v", m.RepoID, err) |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Get latest commit date and update to current repository updated time |  | ||||||
| 		commitDate, err := git.GetLatestCommitTime(m.Repo.RepoPath()) |  | ||||||
| 		if err != nil { |  | ||||||
| 			log.Error("GetLatestCommitDate [%d]: %v", m.RepoID, err) |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if _, err = sess.Exec("UPDATE repository SET updated_unix = ? WHERE id = ?", commitDate.Unix(), m.RepoID); err != nil { |  | ||||||
| 			log.Error("Update repository 'updated_unix' [%d]: %v", m.RepoID, err) |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // InitSyncMirrors initializes a go routine to sync the mirrors |  | ||||||
| func InitSyncMirrors() { |  | ||||||
| 	go SyncMirrors() |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/sync" | 	"code.gitea.io/gitea/modules/sync" | ||||||
|  | 	mirror_service "code.gitea.io/gitea/services/mirror" | ||||||
|  |  | ||||||
| 	"github.com/gogs/cron" | 	"github.com/gogs/cron" | ||||||
| ) | ) | ||||||
| @@ -51,14 +52,14 @@ func NewContext() { | |||||||
| 		err   error | 		err   error | ||||||
| 	) | 	) | ||||||
| 	if setting.Cron.UpdateMirror.Enabled { | 	if setting.Cron.UpdateMirror.Enabled { | ||||||
| 		entry, err = c.AddFunc("Update mirrors", setting.Cron.UpdateMirror.Schedule, WithUnique(mirrorUpdate, models.MirrorUpdate)) | 		entry, err = c.AddFunc("Update mirrors", setting.Cron.UpdateMirror.Schedule, WithUnique(mirrorUpdate, mirror_service.Update)) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			log.Fatal("Cron[Update mirrors]: %v", err) | 			log.Fatal("Cron[Update mirrors]: %v", err) | ||||||
| 		} | 		} | ||||||
| 		if setting.Cron.UpdateMirror.RunAtStart { | 		if setting.Cron.UpdateMirror.RunAtStart { | ||||||
| 			entry.Prev = time.Now() | 			entry.Prev = time.Now() | ||||||
| 			entry.ExecTimes++ | 			entry.ExecTimes++ | ||||||
| 			go WithUnique(mirrorUpdate, models.MirrorUpdate)() | 			go WithUnique(mirrorUpdate, mirror_service.Update)() | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if setting.Cron.RepoHealthCheck.Enabled { | 	if setting.Cron.RepoHealthCheck.Enabled { | ||||||
|   | |||||||
| @@ -28,6 +28,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/timeutil" | 	"code.gitea.io/gitea/modules/timeutil" | ||||||
| 	"code.gitea.io/gitea/modules/util" | 	"code.gitea.io/gitea/modules/util" | ||||||
| 	"code.gitea.io/gitea/services/gitdiff" | 	"code.gitea.io/gitea/services/gitdiff" | ||||||
|  | 	mirror_service "code.gitea.io/gitea/services/mirror" | ||||||
|  |  | ||||||
| 	"gopkg.in/editorconfig/editorconfig-core-go.v1" | 	"gopkg.in/editorconfig/editorconfig-core-go.v1" | ||||||
| ) | ) | ||||||
| @@ -233,6 +234,8 @@ func NewFuncMap() []template.FuncMap { | |||||||
| 			return float32(n) * 100 / float32(sum) | 			return float32(n) * 100 / float32(sum) | ||||||
| 		}, | 		}, | ||||||
| 		"CommentMustAsDiff": gitdiff.CommentMustAsDiff, | 		"CommentMustAsDiff": gitdiff.CommentMustAsDiff, | ||||||
|  | 		"MirrorAddress":     mirror_service.Address, | ||||||
|  | 		"MirrorFullAddress": mirror_service.AddressNoCredentials, | ||||||
| 	}} | 	}} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ import ( | |||||||
| 	api "code.gitea.io/gitea/modules/structs" | 	api "code.gitea.io/gitea/modules/structs" | ||||||
| 	"code.gitea.io/gitea/modules/util" | 	"code.gitea.io/gitea/modules/util" | ||||||
| 	"code.gitea.io/gitea/routers/api/v1/convert" | 	"code.gitea.io/gitea/routers/api/v1/convert" | ||||||
|  | 	mirror_service "code.gitea.io/gitea/services/mirror" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var searchOrderByMap = map[string]map[string]models.SearchOrderBy{ | var searchOrderByMap = map[string]map[string]models.SearchOrderBy{ | ||||||
| @@ -869,6 +870,7 @@ func MirrorSync(ctx *context.APIContext) { | |||||||
| 		ctx.Error(403, "MirrorSync", "Must have write access") | 		ctx.Error(403, "MirrorSync", "Must have write access") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	go models.MirrorQueue.Add(repo.ID) | 	mirror_service.StartToMirror(repo.ID) | ||||||
|  |  | ||||||
| 	ctx.Status(200) | 	ctx.Status(200) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/ssh" | 	"code.gitea.io/gitea/modules/ssh" | ||||||
| 	"code.gitea.io/gitea/services/mailer" | 	"code.gitea.io/gitea/services/mailer" | ||||||
|  | 	mirror_service "code.gitea.io/gitea/services/mirror" | ||||||
|  |  | ||||||
| 	"gitea.com/macaron/macaron" | 	"gitea.com/macaron/macaron" | ||||||
| ) | ) | ||||||
| @@ -98,7 +99,7 @@ func GlobalInit() { | |||||||
| 			log.Fatal("Failed to initialize issue indexer: %v", err) | 			log.Fatal("Failed to initialize issue indexer: %v", err) | ||||||
| 		} | 		} | ||||||
| 		models.InitRepoIndexer() | 		models.InitRepoIndexer() | ||||||
| 		models.InitSyncMirrors() | 		mirror_service.InitSyncMirrors() | ||||||
| 		models.InitDeliverHooks() | 		models.InitDeliverHooks() | ||||||
| 		models.InitTestPullRequests() | 		models.InitTestPullRequests() | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -25,6 +25,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/validation" | 	"code.gitea.io/gitea/modules/validation" | ||||||
| 	"code.gitea.io/gitea/routers/utils" | 	"code.gitea.io/gitea/routers/utils" | ||||||
| 	"code.gitea.io/gitea/services/mailer" | 	"code.gitea.io/gitea/services/mailer" | ||||||
|  | 	mirror_service "code.gitea.io/gitea/services/mirror" | ||||||
|  |  | ||||||
| 	"github.com/unknwon/com" | 	"github.com/unknwon/com" | ||||||
| 	"mvdan.cc/xurls/v2" | 	"mvdan.cc/xurls/v2" | ||||||
| @@ -190,7 +191,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { | |||||||
|  |  | ||||||
| 		address = u.String() | 		address = u.String() | ||||||
|  |  | ||||||
| 		if err := ctx.Repo.Mirror.SaveAddress(address); err != nil { | 		if err := mirror_service.SaveAddress(ctx.Repo.Mirror, address); err != nil { | ||||||
| 			ctx.ServerError("SaveAddress", err) | 			ctx.ServerError("SaveAddress", err) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| @@ -204,7 +205,8 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { | |||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		go models.MirrorQueue.Add(repo.ID) | 		mirror_service.StartToMirror(repo.ID) | ||||||
|  |  | ||||||
| 		ctx.Flash.Info(ctx.Tr("repo.settings.mirror_sync_in_progress")) | 		ctx.Flash.Info(ctx.Tr("repo.settings.mirror_sync_in_progress")) | ||||||
| 		ctx.Redirect(repo.Link() + "/settings") | 		ctx.Redirect(repo.Link() + "/settings") | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										395
									
								
								services/mirror/mirror.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										395
									
								
								services/mirror/mirror.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,395 @@ | |||||||
|  | // 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 mirror | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"net/url" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/models" | ||||||
|  | 	"code.gitea.io/gitea/modules/cache" | ||||||
|  | 	"code.gitea.io/gitea/modules/git" | ||||||
|  | 	"code.gitea.io/gitea/modules/log" | ||||||
|  | 	"code.gitea.io/gitea/modules/process" | ||||||
|  | 	"code.gitea.io/gitea/modules/setting" | ||||||
|  | 	"code.gitea.io/gitea/modules/sync" | ||||||
|  | 	"code.gitea.io/gitea/modules/timeutil" | ||||||
|  | 	"code.gitea.io/gitea/modules/util" | ||||||
|  |  | ||||||
|  | 	"github.com/mcuadros/go-version" | ||||||
|  | 	"github.com/unknwon/com" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // mirrorQueue holds an UniqueQueue object of the mirror | ||||||
|  | var mirrorQueue = sync.NewUniqueQueue(setting.Repository.MirrorQueueLength) | ||||||
|  |  | ||||||
|  | func readAddress(m *models.Mirror) { | ||||||
|  | 	if len(m.Address) > 0 { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	var err error | ||||||
|  | 	m.Address, err = remoteAddress(m.Repo.RepoPath()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Error("remoteAddress: %v", err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func remoteAddress(repoPath string) (string, error) { | ||||||
|  | 	var cmd *git.Command | ||||||
|  | 	binVersion, err := git.BinVersion() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	if version.Compare(binVersion, "2.7", ">=") { | ||||||
|  | 		cmd = git.NewCommand("remote", "get-url", "origin") | ||||||
|  | 	} else { | ||||||
|  | 		cmd = git.NewCommand("config", "--get", "remote.origin.url") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	result, err := cmd.RunInDir(repoPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		if strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") { | ||||||
|  | 			return "", nil | ||||||
|  | 		} | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	if len(result) > 0 { | ||||||
|  | 		return result[:len(result)-1], nil | ||||||
|  | 	} | ||||||
|  | 	return "", nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // sanitizeOutput sanitizes output of a command, replacing occurrences of the | ||||||
|  | // repository's remote address with a sanitized version. | ||||||
|  | func sanitizeOutput(output, repoPath string) (string, error) { | ||||||
|  | 	remoteAddr, err := remoteAddress(repoPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		// if we're unable to load the remote address, then we're unable to | ||||||
|  | 		// sanitize. | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	return util.SanitizeMessage(output, remoteAddr), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // AddressNoCredentials returns mirror address from Git repository config without credentials. | ||||||
|  | func AddressNoCredentials(m *models.Mirror) string { | ||||||
|  | 	readAddress(m) | ||||||
|  | 	u, err := url.Parse(m.Address) | ||||||
|  | 	if err != nil { | ||||||
|  | 		// this shouldn't happen but just return it unsanitised | ||||||
|  | 		return m.Address | ||||||
|  | 	} | ||||||
|  | 	u.User = nil | ||||||
|  | 	return u.String() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SaveAddress writes new address to Git repository config. | ||||||
|  | func SaveAddress(m *models.Mirror, addr string) error { | ||||||
|  | 	repoPath := m.Repo.RepoPath() | ||||||
|  | 	// Remove old origin | ||||||
|  | 	_, err := git.NewCommand("remote", "remove", "origin").RunInDir(repoPath) | ||||||
|  | 	if err != nil && !strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_, err = git.NewCommand("remote", "add", "origin", "--mirror=fetch", addr).RunInDir(repoPath) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // gitShortEmptySha Git short empty SHA | ||||||
|  | const gitShortEmptySha = "0000000" | ||||||
|  |  | ||||||
|  | // mirrorSyncResult contains information of a updated reference. | ||||||
|  | // If the oldCommitID is "0000000", it means a new reference, the value of newCommitID is empty. | ||||||
|  | // If the newCommitID is "0000000", it means the reference is deleted, the value of oldCommitID is empty. | ||||||
|  | type mirrorSyncResult struct { | ||||||
|  | 	refName     string | ||||||
|  | 	oldCommitID string | ||||||
|  | 	newCommitID string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // parseRemoteUpdateOutput detects create, update and delete operations of references from upstream. | ||||||
|  | func parseRemoteUpdateOutput(output string) []*mirrorSyncResult { | ||||||
|  | 	results := make([]*mirrorSyncResult, 0, 3) | ||||||
|  | 	lines := strings.Split(output, "\n") | ||||||
|  | 	for i := range lines { | ||||||
|  | 		// Make sure reference name is presented before continue | ||||||
|  | 		idx := strings.Index(lines[i], "-> ") | ||||||
|  | 		if idx == -1 { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		refName := lines[i][idx+3:] | ||||||
|  |  | ||||||
|  | 		switch { | ||||||
|  | 		case strings.HasPrefix(lines[i], " * "): // New reference | ||||||
|  | 			results = append(results, &mirrorSyncResult{ | ||||||
|  | 				refName:     refName, | ||||||
|  | 				oldCommitID: gitShortEmptySha, | ||||||
|  | 			}) | ||||||
|  | 		case strings.HasPrefix(lines[i], " - "): // Delete reference | ||||||
|  | 			results = append(results, &mirrorSyncResult{ | ||||||
|  | 				refName:     refName, | ||||||
|  | 				newCommitID: gitShortEmptySha, | ||||||
|  | 			}) | ||||||
|  | 		case strings.HasPrefix(lines[i], "   "): // New commits of a reference | ||||||
|  | 			delimIdx := strings.Index(lines[i][3:], " ") | ||||||
|  | 			if delimIdx == -1 { | ||||||
|  | 				log.Error("SHA delimiter not found: %q", lines[i]) | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			shas := strings.Split(lines[i][3:delimIdx+3], "..") | ||||||
|  | 			if len(shas) != 2 { | ||||||
|  | 				log.Error("Expect two SHAs but not what found: %q", lines[i]) | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			results = append(results, &mirrorSyncResult{ | ||||||
|  | 				refName:     refName, | ||||||
|  | 				oldCommitID: shas[0], | ||||||
|  | 				newCommitID: shas[1], | ||||||
|  | 			}) | ||||||
|  |  | ||||||
|  | 		default: | ||||||
|  | 			log.Warn("parseRemoteUpdateOutput: unexpected update line %q", lines[i]) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return results | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // runSync returns true if sync finished without error. | ||||||
|  | func runSync(m *models.Mirror) ([]*mirrorSyncResult, bool) { | ||||||
|  | 	repoPath := m.Repo.RepoPath() | ||||||
|  | 	wikiPath := m.Repo.WikiPath() | ||||||
|  | 	timeout := time.Duration(setting.Git.Timeout.Mirror) * time.Second | ||||||
|  |  | ||||||
|  | 	gitArgs := []string{"remote", "update"} | ||||||
|  | 	if m.EnablePrune { | ||||||
|  | 		gitArgs = append(gitArgs, "--prune") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_, stderr, err := process.GetManager().ExecDir( | ||||||
|  | 		timeout, repoPath, fmt.Sprintf("Mirror.runSync: %s", repoPath), | ||||||
|  | 		git.GitExecutable, gitArgs...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		// sanitize the output, since it may contain the remote address, which may | ||||||
|  | 		// contain a password | ||||||
|  | 		message, err := sanitizeOutput(stderr, repoPath) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Error("sanitizeOutput: %v", err) | ||||||
|  | 			return nil, false | ||||||
|  | 		} | ||||||
|  | 		desc := fmt.Sprintf("Failed to update mirror repository '%s': %s", repoPath, message) | ||||||
|  | 		log.Error(desc) | ||||||
|  | 		if err = models.CreateRepositoryNotice(desc); err != nil { | ||||||
|  | 			log.Error("CreateRepositoryNotice: %v", err) | ||||||
|  | 		} | ||||||
|  | 		return nil, false | ||||||
|  | 	} | ||||||
|  | 	output := stderr | ||||||
|  |  | ||||||
|  | 	gitRepo, err := git.OpenRepository(repoPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Error("OpenRepository: %v", err) | ||||||
|  | 		return nil, false | ||||||
|  | 	} | ||||||
|  | 	if err = models.SyncReleasesWithTags(m.Repo, gitRepo); err != nil { | ||||||
|  | 		log.Error("Failed to synchronize tags to releases for repository: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := m.Repo.UpdateSize(); err != nil { | ||||||
|  | 		log.Error("Failed to update size for mirror repository: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if m.Repo.HasWiki() { | ||||||
|  | 		if _, stderr, err := process.GetManager().ExecDir( | ||||||
|  | 			timeout, wikiPath, fmt.Sprintf("Mirror.runSync: %s", wikiPath), | ||||||
|  | 			git.GitExecutable, "remote", "update", "--prune"); err != nil { | ||||||
|  | 			// sanitize the output, since it may contain the remote address, which may | ||||||
|  | 			// contain a password | ||||||
|  | 			message, err := sanitizeOutput(stderr, wikiPath) | ||||||
|  | 			if err != nil { | ||||||
|  | 				log.Error("sanitizeOutput: %v", err) | ||||||
|  | 				return nil, false | ||||||
|  | 			} | ||||||
|  | 			desc := fmt.Sprintf("Failed to update mirror wiki repository '%s': %s", wikiPath, message) | ||||||
|  | 			log.Error(desc) | ||||||
|  | 			if err = models.CreateRepositoryNotice(desc); err != nil { | ||||||
|  | 				log.Error("CreateRepositoryNotice: %v", err) | ||||||
|  | 			} | ||||||
|  | 			return nil, false | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	branches, err := m.Repo.GetBranches() | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Error("GetBranches: %v", err) | ||||||
|  | 		return nil, false | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for i := range branches { | ||||||
|  | 		cache.Remove(m.Repo.GetCommitsCountCacheKey(branches[i].Name, true)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	m.UpdatedUnix = timeutil.TimeStampNow() | ||||||
|  | 	return parseRemoteUpdateOutput(output), true | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Address returns mirror address from Git repository config without credentials. | ||||||
|  | func Address(m *models.Mirror) string { | ||||||
|  | 	readAddress(m) | ||||||
|  | 	return util.SanitizeURLCredentials(m.Address, false) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Username returns the mirror address username | ||||||
|  | func Username(m *models.Mirror) string { | ||||||
|  | 	readAddress(m) | ||||||
|  | 	u, err := url.Parse(m.Address) | ||||||
|  | 	if err != nil { | ||||||
|  | 		// this shouldn't happen but if it does return "" | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 	return u.User.Username() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Password returns the mirror address password | ||||||
|  | func Password(m *models.Mirror) string { | ||||||
|  | 	readAddress(m) | ||||||
|  | 	u, err := url.Parse(m.Address) | ||||||
|  | 	if err != nil { | ||||||
|  | 		// this shouldn't happen but if it does return "" | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 	password, _ := u.User.Password() | ||||||
|  | 	return password | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Update checks and updates mirror repositories. | ||||||
|  | func Update() { | ||||||
|  | 	log.Trace("Doing: Update") | ||||||
|  |  | ||||||
|  | 	if err := models.MirrorsIterate(func(idx int, bean interface{}) error { | ||||||
|  | 		m := bean.(*models.Mirror) | ||||||
|  | 		if m.Repo == nil { | ||||||
|  | 			log.Error("Disconnected mirror repository found: %d", m.ID) | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		mirrorQueue.Add(m.RepoID) | ||||||
|  | 		return nil | ||||||
|  | 	}); err != nil { | ||||||
|  | 		log.Error("Update: %v", err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SyncMirrors checks and syncs mirrors. | ||||||
|  | // TODO: sync more mirrors at same time. | ||||||
|  | func SyncMirrors() { | ||||||
|  | 	// Start listening on new sync requests. | ||||||
|  | 	for repoID := range mirrorQueue.Queue() { | ||||||
|  | 		log.Trace("SyncMirrors [repo_id: %v]", repoID) | ||||||
|  | 		mirrorQueue.Remove(repoID) | ||||||
|  |  | ||||||
|  | 		m, err := models.GetMirrorByRepoID(com.StrTo(repoID).MustInt64()) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Error("GetMirrorByRepoID [%s]: %v", repoID, err) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		results, ok := runSync(m) | ||||||
|  | 		if !ok { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		m.ScheduleNextUpdate() | ||||||
|  | 		if err = models.UpdateMirror(m); err != nil { | ||||||
|  | 			log.Error("UpdateMirror [%s]: %v", repoID, err) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		var gitRepo *git.Repository | ||||||
|  | 		if len(results) == 0 { | ||||||
|  | 			log.Trace("SyncMirrors [repo_id: %d]: no commits fetched", m.RepoID) | ||||||
|  | 		} else { | ||||||
|  | 			gitRepo, err = git.OpenRepository(m.Repo.RepoPath()) | ||||||
|  | 			if err != nil { | ||||||
|  | 				log.Error("OpenRepository [%d]: %v", m.RepoID, err) | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		for _, result := range results { | ||||||
|  | 			// Discard GitHub pull requests, i.e. refs/pull/* | ||||||
|  | 			if strings.HasPrefix(result.refName, "refs/pull/") { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Create reference | ||||||
|  | 			if result.oldCommitID == gitShortEmptySha { | ||||||
|  | 				if err = models.MirrorSyncCreateAction(m.Repo, result.refName); err != nil { | ||||||
|  | 					log.Error("MirrorSyncCreateAction [repo_id: %d]: %v", m.RepoID, err) | ||||||
|  | 				} | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Delete reference | ||||||
|  | 			if result.newCommitID == gitShortEmptySha { | ||||||
|  | 				if err = models.MirrorSyncDeleteAction(m.Repo, result.refName); err != nil { | ||||||
|  | 					log.Error("MirrorSyncDeleteAction [repo_id: %d]: %v", m.RepoID, err) | ||||||
|  | 				} | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Push commits | ||||||
|  | 			oldCommitID, err := git.GetFullCommitID(gitRepo.Path, result.oldCommitID) | ||||||
|  | 			if err != nil { | ||||||
|  | 				log.Error("GetFullCommitID [%d]: %v", m.RepoID, err) | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			newCommitID, err := git.GetFullCommitID(gitRepo.Path, result.newCommitID) | ||||||
|  | 			if err != nil { | ||||||
|  | 				log.Error("GetFullCommitID [%d]: %v", m.RepoID, err) | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			commits, err := gitRepo.CommitsBetweenIDs(newCommitID, oldCommitID) | ||||||
|  | 			if err != nil { | ||||||
|  | 				log.Error("CommitsBetweenIDs [repo_id: %d, new_commit_id: %s, old_commit_id: %s]: %v", m.RepoID, newCommitID, oldCommitID, err) | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			if err = models.MirrorSyncPushAction(m.Repo, models.MirrorSyncPushActionOptions{ | ||||||
|  | 				RefName:     result.refName, | ||||||
|  | 				OldCommitID: oldCommitID, | ||||||
|  | 				NewCommitID: newCommitID, | ||||||
|  | 				Commits:     models.ListToPushCommits(commits), | ||||||
|  | 			}); err != nil { | ||||||
|  | 				log.Error("MirrorSyncPushAction [repo_id: %d]: %v", m.RepoID, err) | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Get latest commit date and update to current repository updated time | ||||||
|  | 		commitDate, err := git.GetLatestCommitTime(m.Repo.RepoPath()) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Error("GetLatestCommitDate [%d]: %v", m.RepoID, err) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if err = models.UpdateRepositoryUpdatedTime(m.RepoID, commitDate); err != nil { | ||||||
|  | 			log.Error("Update repository 'updated_unix' [%d]: %v", m.RepoID, err) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // InitSyncMirrors initializes a go routine to sync the mirrors | ||||||
|  | func InitSyncMirrors() { | ||||||
|  | 	go SyncMirrors() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // StartToMirror adds repoID to mirror queue | ||||||
|  | func StartToMirror(repoID int64) { | ||||||
|  | 	go mirrorQueue.Add(repoID) | ||||||
|  | } | ||||||
							
								
								
									
										78
									
								
								services/mirror/mirror_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								services/mirror/mirror_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | |||||||
|  | // 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 mirror | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/models" | ||||||
|  | 	"code.gitea.io/gitea/modules/git" | ||||||
|  | 	release_service "code.gitea.io/gitea/services/release" | ||||||
|  |  | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestMain(m *testing.M) { | ||||||
|  | 	models.MainTest(m, filepath.Join("..", "..")) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestRelease_MirrorDelete(t *testing.T) { | ||||||
|  | 	assert.NoError(t, models.PrepareTestDatabase()) | ||||||
|  |  | ||||||
|  | 	user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) | ||||||
|  | 	repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) | ||||||
|  | 	repoPath := models.RepoPath(user.Name, repo.Name) | ||||||
|  |  | ||||||
|  | 	migrationOptions := models.MigrateRepoOptions{ | ||||||
|  | 		Name:                 "test_mirror", | ||||||
|  | 		Description:          "Test mirror", | ||||||
|  | 		IsPrivate:            false, | ||||||
|  | 		IsMirror:             true, | ||||||
|  | 		RemoteAddr:           repoPath, | ||||||
|  | 		Wiki:                 true, | ||||||
|  | 		SyncReleasesWithTags: true, | ||||||
|  | 	} | ||||||
|  | 	mirror, err := models.MigrateRepository(user, user, migrationOptions) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	gitRepo, err := git.OpenRepository(repoPath) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	findOptions := models.FindReleasesOptions{IncludeDrafts: true, IncludeTags: true} | ||||||
|  | 	initCount, err := models.GetReleaseCountByRepoID(mirror.ID, findOptions) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	assert.NoError(t, release_service.CreateRelease(gitRepo, &models.Release{ | ||||||
|  | 		RepoID:       repo.ID, | ||||||
|  | 		PublisherID:  user.ID, | ||||||
|  | 		TagName:      "v0.2", | ||||||
|  | 		Target:       "master", | ||||||
|  | 		Title:        "v0.2 is released", | ||||||
|  | 		Note:         "v0.2 is released", | ||||||
|  | 		IsDraft:      false, | ||||||
|  | 		IsPrerelease: false, | ||||||
|  | 		IsTag:        true, | ||||||
|  | 	}, nil)) | ||||||
|  |  | ||||||
|  | 	err = mirror.GetMirror() | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	_, ok := runSync(mirror.Mirror) | ||||||
|  | 	assert.True(t, ok) | ||||||
|  |  | ||||||
|  | 	count, err := models.GetReleaseCountByRepoID(mirror.ID, findOptions) | ||||||
|  | 	assert.EqualValues(t, initCount+1, count) | ||||||
|  |  | ||||||
|  | 	release, err := models.GetRelease(repo.ID, "v0.2") | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.NoError(t, release_service.DeleteReleaseByID(release.ID, user, true)) | ||||||
|  |  | ||||||
|  | 	_, ok = runSync(mirror.Mirror) | ||||||
|  | 	assert.True(t, ok) | ||||||
|  |  | ||||||
|  | 	count, err = models.GetReleaseCountByRepoID(mirror.ID, findOptions) | ||||||
|  | 	assert.EqualValues(t, initCount, count) | ||||||
|  | } | ||||||
| @@ -100,62 +100,3 @@ func TestRelease_Create(t *testing.T) { | |||||||
| 		IsTag:        true, | 		IsTag:        true, | ||||||
| 	}, nil)) | 	}, nil)) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestRelease_MirrorDelete(t *testing.T) { |  | ||||||
| 	assert.NoError(t, models.PrepareTestDatabase()) |  | ||||||
|  |  | ||||||
| 	user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) |  | ||||||
| 	repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) |  | ||||||
| 	repoPath := models.RepoPath(user.Name, repo.Name) |  | ||||||
| 	migrationOptions := models.MigrateRepoOptions{ |  | ||||||
| 		Name:                 "test_mirror", |  | ||||||
| 		Description:          "Test mirror", |  | ||||||
| 		IsPrivate:            false, |  | ||||||
| 		IsMirror:             true, |  | ||||||
| 		RemoteAddr:           repoPath, |  | ||||||
| 		Wiki:                 true, |  | ||||||
| 		SyncReleasesWithTags: true, |  | ||||||
| 	} |  | ||||||
| 	mirror, err := models.MigrateRepository(user, user, migrationOptions) |  | ||||||
| 	assert.NoError(t, err) |  | ||||||
|  |  | ||||||
| 	gitRepo, err := git.OpenRepository(repoPath) |  | ||||||
| 	assert.NoError(t, err) |  | ||||||
|  |  | ||||||
| 	findOptions := models.FindReleasesOptions{IncludeDrafts: true, IncludeTags: true} |  | ||||||
| 	initCount, err := models.GetReleaseCountByRepoID(mirror.ID, findOptions) |  | ||||||
| 	assert.NoError(t, err) |  | ||||||
|  |  | ||||||
| 	assert.NoError(t, CreateRelease(gitRepo, &models.Release{ |  | ||||||
| 		RepoID:       repo.ID, |  | ||||||
| 		PublisherID:  user.ID, |  | ||||||
| 		TagName:      "v0.2", |  | ||||||
| 		Target:       "master", |  | ||||||
| 		Title:        "v0.2 is released", |  | ||||||
| 		Note:         "v0.2 is released", |  | ||||||
| 		IsDraft:      false, |  | ||||||
| 		IsPrerelease: false, |  | ||||||
| 		IsTag:        true, |  | ||||||
| 	}, nil)) |  | ||||||
|  |  | ||||||
| 	err = mirror.GetMirror() |  | ||||||
| 	assert.NoError(t, err) |  | ||||||
|  |  | ||||||
| 	ok := models.RunMirrorSync(mirror.Mirror) |  | ||||||
| 	assert.True(t, ok) |  | ||||||
|  |  | ||||||
| 	count, err := models.GetReleaseCountByRepoID(mirror.ID, findOptions) |  | ||||||
| 	assert.NoError(t, err) |  | ||||||
| 	assert.EqualValues(t, initCount+1, count) |  | ||||||
|  |  | ||||||
| 	release, err := models.GetRelease(repo.ID, "v0.2") |  | ||||||
| 	assert.NoError(t, err) |  | ||||||
| 	assert.NoError(t, DeleteReleaseByID(release.ID, user, true)) |  | ||||||
|  |  | ||||||
| 	ok = models.RunMirrorSync(mirror.Mirror) |  | ||||||
| 	assert.True(t, ok) |  | ||||||
|  |  | ||||||
| 	count, err = models.GetReleaseCountByRepoID(mirror.ID, findOptions) |  | ||||||
| 	assert.NoError(t, err) |  | ||||||
| 	assert.EqualValues(t, initCount, count) |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ | |||||||
| 				<a href="{{$.RepoLink}}">{{.Name}}</a> | 				<a href="{{$.RepoLink}}">{{.Name}}</a> | ||||||
| 				{{if and .RelAvatarLink .IsPrivate}}<i class="mega-octicon octicon-lock"></i>{{end}} | 				{{if and .RelAvatarLink .IsPrivate}}<i class="mega-octicon octicon-lock"></i>{{end}} | ||||||
| 				{{if .IsArchived}}<i class="archive icon archived-icon"></i>{{end}} | 				{{if .IsArchived}}<i class="archive icon archived-icon"></i>{{end}} | ||||||
| 				{{if .IsMirror}}<div class="fork-flag">{{$.i18n.Tr "repo.mirror_from"}} <a target="_blank" rel="noopener noreferrer" href="{{$.Mirror.Address}}">{{$.Mirror.Address}}</a></div>{{end}} | 				{{if .IsMirror}}<div class="fork-flag">{{$.i18n.Tr "repo.mirror_from"}} <a target="_blank" rel="noopener noreferrer" href="{{MirrorAddress $.Mirror}}">{{MirrorAddress $.Mirror}}</a></div>{{end}} | ||||||
| 				{{if .IsFork}}<div class="fork-flag">{{$.i18n.Tr "repo.forked_from"}} <a href="{{.BaseRepo.Link}}">{{SubStr .BaseRepo.RelLink 1 -1}}</a></div>{{end}} | 				{{if .IsFork}}<div class="fork-flag">{{$.i18n.Tr "repo.forked_from"}} <a href="{{.BaseRepo.Link}}">{{SubStr .BaseRepo.RelLink 1 -1}}</a></div>{{end}} | ||||||
| 			</div> | 			</div> | ||||||
| 			<div class="repo-buttons"> | 			<div class="repo-buttons"> | ||||||
|   | |||||||
| @@ -80,7 +80,7 @@ | |||||||
| 					</div> | 					</div> | ||||||
| 					<div class="field {{if .Err_MirrorAddress}}error{{end}}"> | 					<div class="field {{if .Err_MirrorAddress}}error{{end}}"> | ||||||
| 						<label for="mirror_address">{{.i18n.Tr "repo.mirror_address"}}</label> | 						<label for="mirror_address">{{.i18n.Tr "repo.mirror_address"}}</label> | ||||||
| 						<input id="mirror_address" name="mirror_address" value="{{.Mirror.AddressNoCredentials}}" required> | 						<input id="mirror_address" name="mirror_address" value="{{MirrorFullAddress .Mirror}}" required> | ||||||
| 						<p class="help">{{.i18n.Tr "repo.mirror_address_desc"}}</p> | 						<p class="help">{{.i18n.Tr "repo.mirror_address_desc"}}</p> | ||||||
| 					</div> | 					</div> | ||||||
| 					<div class="ui accordion optional field"> | 					<div class="ui accordion optional field"> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user