Browse Source

#2018 able to sync now for mirrors

- Refactor code to use sync.UniqueQueue
- Closes #3509
Unknwon 4 years ago
parent
commit
8516dfcb6c

+ 5 - 1
.editorconfig

@@ -8,10 +8,14 @@ end_of_line = lf
8 8
 insert_final_newline = true
9 9
 trim_trailing_whitespace = true
10 10
 
11
-[*.{go,tmpl}]
11
+[*.go]
12 12
 indent_style = tab
13 13
 indent_size = 4
14 14
 
15
+[*.tmpl]
16
+indent_style = tab
17
+indent_size = 2
18
+
15 19
 [*.{less,yml}]
16 20
 indent_style = space
17 21
 indent_size = 2

+ 1 - 1
README.md

@@ -3,7 +3,7 @@ Gogs - Go Git Service [![Build Status](https://travis-ci.org/gogits/gogs.svg?bra
3 3
 
4 4
 ![](https://github.com/gogits/gogs/blob/master/public/img/gogs-large-resize.png?raw=true)
5 5
 
6
-##### Current tip version: 0.9.95 (see [Releases](https://github.com/gogits/gogs/releases) for binary versions)
6
+##### Current tip version: 0.9.96 (see [Releases](https://github.com/gogits/gogs/releases) for binary versions)
7 7
 
8 8
 | Web | UI  | Preview  |
9 9
 |:-------------:|:-------:|:-------:|

+ 6 - 4
conf/app.ini

@@ -17,8 +17,10 @@ ANSI_CHARSET =
17 17
 FORCE_PRIVATE = false
18 18
 ; Global maximum creation limit of repository per user, -1 means no limit
19 19
 MAX_CREATION_LIMIT = -1
20
-; Patch test queue length, make it as large as possible
21
-PULL_REQUEST_QUEUE_LENGTH = 10000
20
+; Mirror sync queue length, increase if mirror syncing starts hanging
21
+MIRROR_QUEUE_LENGTH = 1000
22
+; Patch test queue length, increase if pull request patch testing starts hanging
23
+PULL_REQUEST_QUEUE_LENGTH = 1000
22 24
 ; Preferred Licenses to place at the top of the List
23 25
 ; Name must match file name in conf/license or custom/conf/license
24 26
 PREFERRED_LICENSES = Apache License 2.0,MIT License
@@ -184,7 +186,7 @@ ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = false
184 186
 ENABLE_CAPTCHA = true
185 187
 
186 188
 [webhook]
187
-; Hook task queue length
189
+; Hook task queue length, increase if webhook shooting starts hanging
188 190
 QUEUE_LENGTH = 1000
189 191
 ; Deliver timeout in seconds
190 192
 DELIVER_TIMEOUT = 5
@@ -389,7 +391,7 @@ GC = 60
389 391
 
390 392
 [mirror]
391 393
 ; Default interval in hours between each check
392
-DEFAULT_INTERVAL = 24
394
+DEFAULT_INTERVAL = 8
393 395
 
394 396
 [api]
395 397
 ; Max number of items will response in a page

+ 4 - 0
conf/locale/locale_en-US.ini

@@ -370,6 +370,7 @@ mirror_prune_desc = Remove any remote-tracking references that no longer exist o
370 370
 mirror_interval = Mirror Interval (hour)
371 371
 mirror_address = Mirror Address
372 372
 mirror_address_desc = Please include necessary user credentials in the address.
373
+mirror_last_synced = Last Synced
373 374
 watchers = Watchers
374 375
 stargazers = Stargazers
375 376
 forks = Forks
@@ -631,6 +632,9 @@ settings.collaboration.undefined = Undefined
631 632
 settings.hooks = Webhooks
632 633
 settings.githooks = Git Hooks
633 634
 settings.basic_settings = Basic Settings
635
+settings.mirror_settings = Mirror Settings
636
+settings.sync_mirror = Sync Now
637
+settings.mirror_sync_in_progress = Mirror syncing is in progress, please refresh page in about a minute.
634 638
 settings.site = Official Site
635 639
 settings.update_settings = Update Settings
636 640
 settings.change_reponame_prompt = This change will affect how links relate to the repository.

+ 1 - 1
gogs.go

@@ -17,7 +17,7 @@ import (
17 17
 	"github.com/gogits/gogs/modules/setting"
18 18
 )
19 19
 
20
-const APP_VER = "0.9.95.0830"
20
+const APP_VER = "0.9.96.0830"
21 21
 
22 22
 func init() {
23 23
 	runtime.GOMAXPROCS(runtime.NumCPU())

+ 6 - 195
models/repo.go

@@ -381,7 +381,7 @@ func (repo *Repository) IssueStats(uid int64, filterMode int, isPull bool) (int6
381 381
 }
382 382
 
383 383
 func (repo *Repository) GetMirror() (err error) {
384
-	repo.Mirror, err = GetMirror(repo.ID)
384
+	repo.Mirror, err = GetMirrorByRepoID(repo.ID)
385 385
 	return err
386 386
 }
387 387
 
@@ -574,136 +574,6 @@ func (repo *Repository) CloneLink() (cl *CloneLink) {
574 574
 	return repo.cloneLink(false)
575 575
 }
576 576
 
577
-// Mirror represents a mirror information of repository.
578
-type Mirror struct {
579
-	ID          int64 `xorm:"pk autoincr"`
580
-	RepoID      int64
581
-	Repo        *Repository `xorm:"-"`
582
-	Interval    int         // Hour.
583
-	EnablePrune bool        `xorm:"NOT NULL DEFAULT true"`
584
-
585
-	Updated        time.Time `xorm:"-"`
586
-	UpdatedUnix    int64
587
-	NextUpdate     time.Time `xorm:"-"`
588
-	NextUpdateUnix int64
589
-
590
-	address string `xorm:"-"`
591
-}
592
-
593
-func (m *Mirror) BeforeInsert() {
594
-	m.NextUpdateUnix = m.NextUpdate.Unix()
595
-}
596
-
597
-func (m *Mirror) BeforeUpdate() {
598
-	m.UpdatedUnix = time.Now().Unix()
599
-	m.NextUpdateUnix = m.NextUpdate.Unix()
600
-}
601
-
602
-func (m *Mirror) AfterSet(colName string, _ xorm.Cell) {
603
-	var err error
604
-	switch colName {
605
-	case "repo_id":
606
-		m.Repo, err = GetRepositoryByID(m.RepoID)
607
-		if err != nil {
608
-			log.Error(3, "GetRepositoryByID[%d]: %v", m.ID, err)
609
-		}
610
-	case "updated_unix":
611
-		m.Updated = time.Unix(m.UpdatedUnix, 0).Local()
612
-	case "next_updated_unix":
613
-		m.NextUpdate = time.Unix(m.NextUpdateUnix, 0).Local()
614
-	}
615
-}
616
-
617
-func (m *Mirror) readAddress() {
618
-	if len(m.address) > 0 {
619
-		return
620
-	}
621
-
622
-	cfg, err := ini.Load(m.Repo.GitConfigPath())
623
-	if err != nil {
624
-		log.Error(4, "Load: %v", err)
625
-		return
626
-	}
627
-	m.address = cfg.Section("remote \"origin\"").Key("url").Value()
628
-}
629
-
630
-// HandleCloneUserCredentials replaces user credentials from HTTP/HTTPS URL
631
-// with placeholder <credentials>.
632
-// It will fail for any other forms of clone addresses.
633
-func HandleCloneUserCredentials(url string, mosaics bool) string {
634
-	i := strings.Index(url, "@")
635
-	if i == -1 {
636
-		return url
637
-	}
638
-	start := strings.Index(url, "://")
639
-	if start == -1 {
640
-		return url
641
-	}
642
-	if mosaics {
643
-		return url[:start+3] + "<credentials>" + url[i:]
644
-	}
645
-	return url[:start+3] + url[i+1:]
646
-}
647
-
648
-// Address returns mirror address from Git repository config without credentials.
649
-func (m *Mirror) Address() string {
650
-	m.readAddress()
651
-	return HandleCloneUserCredentials(m.address, false)
652
-}
653
-
654
-// FullAddress returns mirror address from Git repository config.
655
-func (m *Mirror) FullAddress() string {
656
-	m.readAddress()
657
-	return m.address
658
-}
659
-
660
-// SaveAddress writes new address to Git repository config.
661
-func (m *Mirror) SaveAddress(addr string) error {
662
-	configPath := m.Repo.GitConfigPath()
663
-	cfg, err := ini.Load(configPath)
664
-	if err != nil {
665
-		return fmt.Errorf("Load: %v", err)
666
-	}
667
-
668
-	cfg.Section("remote \"origin\"").Key("url").SetValue(addr)
669
-	return cfg.SaveToIndent(configPath, "\t")
670
-}
671
-
672
-func getMirror(e Engine, repoId int64) (*Mirror, error) {
673
-	m := &Mirror{RepoID: repoId}
674
-	has, err := e.Get(m)
675
-	if err != nil {
676
-		return nil, err
677
-	} else if !has {
678
-		return nil, ErrMirrorNotExist
679
-	}
680
-	return m, nil
681
-}
682
-
683
-// GetMirror returns mirror object by given repository ID.
684
-func GetMirror(repoId int64) (*Mirror, error) {
685
-	return getMirror(x, repoId)
686
-}
687
-
688
-func updateMirror(e Engine, m *Mirror) error {
689
-	_, err := e.Id(m.ID).AllCols().Update(m)
690
-	return err
691
-}
692
-
693
-func UpdateMirror(m *Mirror) error {
694
-	return updateMirror(x, m)
695
-}
696
-
697
-func DeleteMirrorByRepoID(repoID int64) error {
698
-	_, err := x.Delete(&Mirror{RepoID: repoID})
699
-	return err
700
-}
701
-
702
-func createUpdateHook(repoPath string) error {
703
-	return git.SetUpdateHook(repoPath,
704
-		fmt.Sprintf(_TPL_UPDATE_HOOK, setting.ScriptType, "\""+setting.AppPath+"\"", setting.CustomConf))
705
-}
706
-
707 577
 type MigrateRepoOptions struct {
708 578
 	Name        string
709 579
 	Description string
@@ -839,6 +709,11 @@ func cleanUpMigrateGitConfig(configPath string) error {
839 709
 	return nil
840 710
 }
841 711
 
712
+func createUpdateHook(repoPath string) error {
713
+	return git.SetUpdateHook(repoPath,
714
+		fmt.Sprintf(_TPL_UPDATE_HOOK, setting.ScriptType, "\""+setting.AppPath+"\"", setting.CustomConf))
715
+}
716
+
842 717
 // Finish migrating repository and/or wiki with things that don't need to be done for mirrors.
843 718
 func CleanUpMigrateInfo(repo *Repository) (*Repository, error) {
844 719
 	repoPath := repo.RepoPath()
@@ -1748,70 +1623,6 @@ const (
1748 1623
 	_CHECK_REPOs   = "check_repos"
1749 1624
 )
1750 1625
 
1751
-// MirrorUpdate checks and updates mirror repositories.
1752
-func MirrorUpdate() {
1753
-	if taskStatusTable.IsRunning(_MIRROR_UPDATE) {
1754
-		return
1755
-	}
1756
-	taskStatusTable.Start(_MIRROR_UPDATE)
1757
-	defer taskStatusTable.Stop(_MIRROR_UPDATE)
1758
-
1759
-	log.Trace("Doing: MirrorUpdate")
1760
-
1761
-	mirrors := make([]*Mirror, 0, 10)
1762
-	if err := x.Where("next_update_unix<=?", time.Now().Unix()).Iterate(new(Mirror), func(idx int, bean interface{}) error {
1763
-		m := bean.(*Mirror)
1764
-		if m.Repo == nil {
1765
-			log.Error(4, "Disconnected mirror repository found: %d", m.ID)
1766
-			return nil
1767
-		}
1768
-
1769
-		repoPath := m.Repo.RepoPath()
1770
-		wikiPath := m.Repo.WikiPath()
1771
-		timeout := time.Duration(setting.Git.Timeout.Mirror) * time.Second
1772
-
1773
-		gitArgs := []string{"remote", "update"}
1774
-		if m.EnablePrune {
1775
-			gitArgs = append(gitArgs, "--prune")
1776
-		}
1777
-
1778
-		if _, stderr, err := process.ExecDir(
1779
-			timeout, repoPath, fmt.Sprintf("MirrorUpdate: %s", repoPath),
1780
-			"git", gitArgs...); err != nil {
1781
-			desc := fmt.Sprintf("Fail to update mirror repository(%s): %s", repoPath, stderr)
1782
-			log.Error(4, desc)
1783
-			if err = CreateRepositoryNotice(desc); err != nil {
1784
-				log.Error(4, "CreateRepositoryNotice: %v", err)
1785
-			}
1786
-			return nil
1787
-		}
1788
-		if m.Repo.HasWiki() {
1789
-			if _, stderr, err := process.ExecDir(
1790
-				timeout, wikiPath, fmt.Sprintf("MirrorUpdate: %s", wikiPath),
1791
-				"git", "remote", "update", "--prune"); err != nil {
1792
-				desc := fmt.Sprintf("Fail to update mirror wiki repository(%s): %s", wikiPath, stderr)
1793
-				log.Error(4, desc)
1794
-				if err = CreateRepositoryNotice(desc); err != nil {
1795
-					log.Error(4, "CreateRepositoryNotice: %v", err)
1796
-				}
1797
-				return nil
1798
-			}
1799
-		}
1800
-
1801
-		m.NextUpdate = time.Now().Add(time.Duration(m.Interval) * time.Hour)
1802
-		mirrors = append(mirrors, m)
1803
-		return nil
1804
-	}); err != nil {
1805
-		log.Error(4, "MirrorUpdate: %v", err)
1806
-	}
1807
-
1808
-	for i := range mirrors {
1809
-		if err := UpdateMirror(mirrors[i]); err != nil {
1810
-			log.Error(4, "UpdateMirror[%d]: %v", mirrors[i].ID, err)
1811
-		}
1812
-	}
1813
-}
1814
-
1815 1626
 // GitFsck calls 'git fsck' to check repository health.
1816 1627
 func GitFsck() {
1817 1628
 	if taskStatusTable.IsRunning(_GIT_FSCK) {

+ 243 - 0
models/repo_mirror.go

@@ -0,0 +1,243 @@
1
+// Copyright 2016 The Gogs Authors. All rights reserved.
2
+// Use of this source code is governed by a MIT-style
3
+// license that can be found in the LICENSE file.
4
+
5
+package models
6
+
7
+import (
8
+	"fmt"
9
+	"strings"
10
+	"time"
11
+
12
+	"github.com/Unknwon/com"
13
+	"github.com/go-xorm/xorm"
14
+	"gopkg.in/ini.v1"
15
+
16
+	"github.com/gogits/gogs/modules/log"
17
+	"github.com/gogits/gogs/modules/process"
18
+	"github.com/gogits/gogs/modules/setting"
19
+	"github.com/gogits/gogs/modules/sync"
20
+)
21
+
22
+var MirrorQueue = sync.NewUniqueQueue(setting.Repository.MirrorQueueLength)
23
+
24
+// Mirror represents mirror information of a repository.
25
+type Mirror struct {
26
+	ID          int64 `xorm:"pk autoincr"`
27
+	RepoID      int64
28
+	Repo        *Repository `xorm:"-"`
29
+	Interval    int         // Hour.
30
+	EnablePrune bool        `xorm:"NOT NULL DEFAULT true"`
31
+
32
+	Updated        time.Time `xorm:"-"`
33
+	UpdatedUnix    int64
34
+	NextUpdate     time.Time `xorm:"-"`
35
+	NextUpdateUnix int64
36
+
37
+	address string `xorm:"-"`
38
+}
39
+
40
+func (m *Mirror) BeforeInsert() {
41
+	m.NextUpdateUnix = m.NextUpdate.Unix()
42
+}
43
+
44
+func (m *Mirror) BeforeUpdate() {
45
+	m.UpdatedUnix = time.Now().Unix()
46
+	m.NextUpdateUnix = m.NextUpdate.Unix()
47
+}
48
+
49
+func (m *Mirror) AfterSet(colName string, _ xorm.Cell) {
50
+	var err error
51
+	switch colName {
52
+	case "repo_id":
53
+		m.Repo, err = GetRepositoryByID(m.RepoID)
54
+		if err != nil {
55
+			log.Error(3, "GetRepositoryByID[%d]: %v", m.ID, err)
56
+		}
57
+	case "updated_unix":
58
+		m.Updated = time.Unix(m.UpdatedUnix, 0).Local()
59
+	case "next_updated_unix":
60
+		m.NextUpdate = time.Unix(m.NextUpdateUnix, 0).Local()
61
+	}
62
+}
63
+
64
+// ScheduleNextUpdate calculates and sets next update time.
65
+func (m *Mirror) ScheduleNextUpdate() {
66
+	m.NextUpdate = time.Now().Add(time.Duration(m.Interval) * time.Hour)
67
+}
68
+
69
+func (m *Mirror) readAddress() {
70
+	if len(m.address) > 0 {
71
+		return
72
+	}
73
+
74
+	cfg, err := ini.Load(m.Repo.GitConfigPath())
75
+	if err != nil {
76
+		log.Error(4, "Load: %v", err)
77
+		return
78
+	}
79
+	m.address = cfg.Section("remote \"origin\"").Key("url").Value()
80
+}
81
+
82
+// HandleCloneUserCredentials replaces user credentials from HTTP/HTTPS URL
83
+// with placeholder <credentials>.
84
+// It will fail for any other forms of clone addresses.
85
+func HandleCloneUserCredentials(url string, mosaics bool) string {
86
+	i := strings.Index(url, "@")
87
+	if i == -1 {
88
+		return url
89
+	}
90
+	start := strings.Index(url, "://")
91
+	if start == -1 {
92
+		return url
93
+	}
94
+	if mosaics {
95
+		return url[:start+3] + "<credentials>" + url[i:]
96
+	}
97
+	return url[:start+3] + url[i+1:]
98
+}
99
+
100
+// Address returns mirror address from Git repository config without credentials.
101
+func (m *Mirror) Address() string {
102
+	m.readAddress()
103
+	return HandleCloneUserCredentials(m.address, false)
104
+}
105
+
106
+// FullAddress returns mirror address from Git repository config.
107
+func (m *Mirror) FullAddress() string {
108
+	m.readAddress()
109
+	return m.address
110
+}
111
+
112
+// SaveAddress writes new address to Git repository config.
113
+func (m *Mirror) SaveAddress(addr string) error {
114
+	configPath := m.Repo.GitConfigPath()
115
+	cfg, err := ini.Load(configPath)
116
+	if err != nil {
117
+		return fmt.Errorf("Load: %v", err)
118
+	}
119
+
120
+	cfg.Section("remote \"origin\"").Key("url").SetValue(addr)
121
+	return cfg.SaveToIndent(configPath, "\t")
122
+}
123
+
124
+// runSync returns true if sync finished without error.
125
+func (m *Mirror) runSync() bool {
126
+	repoPath := m.Repo.RepoPath()
127
+	wikiPath := m.Repo.WikiPath()
128
+	timeout := time.Duration(setting.Git.Timeout.Mirror) * time.Second
129
+
130
+	gitArgs := []string{"remote", "update"}
131
+	if m.EnablePrune {
132
+		gitArgs = append(gitArgs, "--prune")
133
+	}
134
+
135
+	if _, stderr, err := process.ExecDir(
136
+		timeout, repoPath, fmt.Sprintf("runSync: %s", repoPath),
137
+		"git", gitArgs...); err != nil {
138
+		desc := fmt.Sprintf("Fail to update mirror repository '%s': %s", repoPath, stderr)
139
+		log.Error(4, desc)
140
+		if err = CreateRepositoryNotice(desc); err != nil {
141
+			log.Error(4, "CreateRepositoryNotice: %v", err)
142
+		}
143
+		return false
144
+	}
145
+	if m.Repo.HasWiki() {
146
+		if _, stderr, err := process.ExecDir(
147
+			timeout, wikiPath, fmt.Sprintf("runSync: %s", wikiPath),
148
+			"git", "remote", "update", "--prune"); err != nil {
149
+			desc := fmt.Sprintf("Fail to update mirror wiki repository '%s': %s", wikiPath, stderr)
150
+			log.Error(4, desc)
151
+			if err = CreateRepositoryNotice(desc); err != nil {
152
+				log.Error(4, "CreateRepositoryNotice: %v", err)
153
+			}
154
+			return false
155
+		}
156
+	}
157
+
158
+	return true
159
+}
160
+
161
+func getMirrorByRepoID(e Engine, repoID int64) (*Mirror, error) {
162
+	m := &Mirror{RepoID: repoID}
163
+	has, err := e.Get(m)
164
+	if err != nil {
165
+		return nil, err
166
+	} else if !has {
167
+		return nil, ErrMirrorNotExist
168
+	}
169
+	return m, nil
170
+}
171
+
172
+// GetMirrorByRepoID returns mirror information of a repository.
173
+func GetMirrorByRepoID(repoID int64) (*Mirror, error) {
174
+	return getMirrorByRepoID(x, repoID)
175
+}
176
+
177
+func updateMirror(e Engine, m *Mirror) error {
178
+	_, err := e.Id(m.ID).AllCols().Update(m)
179
+	return err
180
+}
181
+
182
+func UpdateMirror(m *Mirror) error {
183
+	return updateMirror(x, m)
184
+}
185
+
186
+func DeleteMirrorByRepoID(repoID int64) error {
187
+	_, err := x.Delete(&Mirror{RepoID: repoID})
188
+	return err
189
+}
190
+
191
+// MirrorUpdate checks and updates mirror repositories.
192
+func MirrorUpdate() {
193
+	if taskStatusTable.IsRunning(_MIRROR_UPDATE) {
194
+		return
195
+	}
196
+	taskStatusTable.Start(_MIRROR_UPDATE)
197
+	defer taskStatusTable.Stop(_MIRROR_UPDATE)
198
+
199
+	log.Trace("Doing: MirrorUpdate")
200
+
201
+	if err := x.Where("next_update_unix<=?", time.Now().Unix()).Iterate(new(Mirror), func(idx int, bean interface{}) error {
202
+		m := bean.(*Mirror)
203
+		if m.Repo == nil {
204
+			log.Error(4, "Disconnected mirror repository found: %d", m.ID)
205
+			return nil
206
+		}
207
+
208
+		MirrorQueue.Add(m.RepoID)
209
+		return nil
210
+	}); err != nil {
211
+		log.Error(4, "MirrorUpdate: %v", err)
212
+	}
213
+}
214
+
215
+// SyncMirrors checks and syncs mirrors.
216
+// TODO: sync more mirrors at same time.
217
+func SyncMirrors() {
218
+	// Start listening on new sync requests.
219
+	for repoID := range MirrorQueue.Queue() {
220
+		log.Trace("SyncMirrors [repo_id: %v]", repoID)
221
+		MirrorQueue.Remove(repoID)
222
+
223
+		m, err := GetMirrorByRepoID(com.StrTo(repoID).MustInt64())
224
+		if err != nil {
225
+			log.Error(4, "GetMirrorByRepoID [%d]: %v", repoID, err)
226
+			continue
227
+		}
228
+
229
+		if !m.runSync() {
230
+			continue
231
+		}
232
+
233
+		m.ScheduleNextUpdate()
234
+		if err = UpdateMirror(m); err != nil {
235
+			log.Error(4, "UpdateMirror [%d]: %v", repoID, err)
236
+			continue
237
+		}
238
+	}
239
+}
240
+
241
+func InitSyncMirrors() {
242
+	go SyncMirrors()
243
+}

+ 1 - 1
models/webhook.go

@@ -597,7 +597,7 @@ func DeliverHooks() {
597 597
 
598 598
 	// Start listening on new hook requests.
599 599
 	for repoID := range HookQueue.Queue() {
600
-		log.Trace("DeliverHooks [%v]: processing delivery hooks", repoID)
600
+		log.Trace("DeliverHooks [repo_id: %v]", repoID)
601 601
 		HookQueue.Remove(repoID)
602 602
 
603 603
 		tasks = make([]*HookTask, 0, 5)

File diff suppressed because it is too large
+ 4 - 4
modules/bindata/bindata.go


+ 1 - 1
modules/context/repo.go

@@ -216,7 +216,7 @@ func RepoAssignment(args ...bool) macaron.Handler {
216 216
 		ctx.Data["HasAccess"] = true
217 217
 
218 218
 		if repo.IsMirror {
219
-			ctx.Repo.Mirror, err = models.GetMirror(repo.ID)
219
+			ctx.Repo.Mirror, err = models.GetMirrorByRepoID(repo.ID)
220 220
 			if err != nil {
221 221
 				ctx.Handle(500, "GetMirror", err)
222 222
 				return

+ 1 - 0
modules/setting/setting.go

@@ -113,6 +113,7 @@ var (
113 113
 		AnsiCharset            string
114 114
 		ForcePrivate           bool
115 115
 		MaxCreationLimit       int
116
+		MirrorQueueLength      int
116 117
 		PullRequestQueueLength int
117 118
 		PreferredLicenses      []string
118 119
 

+ 1 - 0
routers/install.go

@@ -75,6 +75,7 @@ func GlobalInit() {
75 75
 
76 76
 		// Booting long running goroutines.
77 77
 		cron.NewContext()
78
+		models.InitSyncMirrors()
78 79
 		models.InitDeliverHooks()
79 80
 		models.InitTestPullRequests()
80 81
 		log.NewGitLogger(path.Join(setting.LogRootPath, "http.log"))

+ 0 - 1
routers/repo/issue.go

@@ -490,7 +490,6 @@ func UploadIssueAttachment(ctx *context.Context) {
490 490
 
491 491
 func ViewIssue(ctx *context.Context) {
492 492
 	ctx.Data["RequireHighlightJS"] = true
493
-	ctx.Data["RequireSimpleMDE"] = true
494 493
 	ctx.Data["RequireDropzone"] = true
495 494
 	renderAttachmentSettings(ctx)
496 495
 

+ 31 - 20
routers/repo/setting.go

@@ -104,34 +104,42 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
104 104
 			}
105 105
 		}
106 106
 
107
-		if repo.IsMirror {
108
-			if isNameChanged {
109
-				var err error
110
-				ctx.Repo.Mirror, err = models.GetMirror(repo.ID)
111
-				if err != nil {
112
-					ctx.Handle(500, "RefreshRepositoryMirror", err)
113
-					return
114
-				}
115
-			}
107
+		ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
108
+		ctx.Redirect(repo.Link() + "/settings")
116 109
 
117
-			if form.Interval > 0 {
118
-				ctx.Repo.Mirror.EnablePrune = form.EnablePrune
119
-				ctx.Repo.Mirror.Interval = form.Interval
120
-				ctx.Repo.Mirror.NextUpdate = time.Now().Add(time.Duration(form.Interval) * time.Hour)
121
-				if err := models.UpdateMirror(ctx.Repo.Mirror); err != nil {
122
-					ctx.Handle(500, "UpdateMirror", err)
123
-					return
124
-				}
125
-			}
126
-			if err := ctx.Repo.Mirror.SaveAddress(form.MirrorAddress); err != nil {
127
-				ctx.Handle(500, "SaveAddress", err)
110
+	case "mirror":
111
+		if !repo.IsMirror {
112
+			ctx.Handle(404, "", nil)
113
+			return
114
+		}
115
+
116
+		if form.Interval > 0 {
117
+			ctx.Repo.Mirror.EnablePrune = form.EnablePrune
118
+			ctx.Repo.Mirror.Interval = form.Interval
119
+			ctx.Repo.Mirror.NextUpdate = time.Now().Add(time.Duration(form.Interval) * time.Hour)
120
+			if err := models.UpdateMirror(ctx.Repo.Mirror); err != nil {
121
+				ctx.Handle(500, "UpdateMirror", err)
128 122
 				return
129 123
 			}
130 124
 		}
125
+		if err := ctx.Repo.Mirror.SaveAddress(form.MirrorAddress); err != nil {
126
+			ctx.Handle(500, "SaveAddress", err)
127
+			return
128
+		}
131 129
 
132 130
 		ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
133 131
 		ctx.Redirect(repo.Link() + "/settings")
134 132
 
133
+	case "mirror-sync":
134
+		if !repo.IsMirror {
135
+			ctx.Handle(404, "", nil)
136
+			return
137
+		}
138
+
139
+		go models.MirrorQueue.Add(repo.ID)
140
+		ctx.Flash.Info(ctx.Tr("repo.settings.mirror_sync_in_progress"))
141
+		ctx.Redirect(repo.Link() + "/settings")
142
+
135 143
 	case "advanced":
136 144
 		repo.EnableWiki = form.EnableWiki
137 145
 		repo.EnableExternalWiki = form.EnableExternalWiki
@@ -278,6 +286,9 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
278 286
 
279 287
 		ctx.Flash.Success(ctx.Tr("repo.settings.wiki_deletion_success"))
280 288
 		ctx.Redirect(ctx.Repo.RepoLink + "/settings")
289
+
290
+	default:
291
+		ctx.Handle(404, "", nil)
281 292
 	}
282 293
 }
283 294
 

+ 1 - 1
templates/.VERSION

@@ -1 +1 @@
1
-0.9.95.0830
1
+0.9.96.0830

+ 39 - 12
templates/repo/settings/options.tmpl

@@ -50,12 +50,26 @@
50 50
 								</div>
51 51
 							</div>
52 52
 						{{end}}
53
-						{{if .Repository.IsMirror}}
53
+
54
+						<div class="field">
55
+							<button class="ui green button">{{$.i18n.Tr "repo.settings.update_settings"}}</button>
56
+						</div>
57
+					</form>
58
+				</div>
59
+
60
+				{{if .Repository.IsMirror}}
61
+					<h4 class="ui top attached header">
62
+						{{.i18n.Tr "repo.settings.mirror_settings"}}
63
+					</h4>
64
+					<div class="ui attached segment">
65
+						<form class="ui form" method="post">
66
+							{{.CsrfTokenHtml}}
67
+							<input type="hidden" name="action" value="mirror">
54 68
 							<div class="inline field {{if .Err_EnablePrune}}error{{end}}">
55
-							        <label>{{.i18n.Tr "repo.mirror_prune"}}</label>
69
+							  <label>{{.i18n.Tr "repo.mirror_prune"}}</label>
56 70
 								<div class="ui checkbox">
57
-								        <input id="enable_prune" name="enable_prune" type="checkbox" {{if .MirrorEnablePrune}}checked{{end}}>
58
-								        <label>{{.i18n.Tr "repo.mirror_prune_desc"}}</label>
71
+					        <input id="enable_prune" name="enable_prune" type="checkbox" {{if .MirrorEnablePrune}}checked{{end}}>
72
+					        <label>{{.i18n.Tr "repo.mirror_prune_desc"}}</label>
59 73
 								</div>
60 74
 							</div>
61 75
 							<div class="inline field {{if .Err_Interval}}error{{end}}">
@@ -64,23 +78,36 @@
64 78
 							</div>
65 79
 							<div class="field">
66 80
 								<label for="mirror_address">{{.i18n.Tr "repo.mirror_address"}}</label>
67
-								<input id="mirror_address" name="mirror_address" value="{{.Mirror.FullAddress}}">
81
+								<input id="mirror_address" name="mirror_address" value="{{.Mirror.FullAddress}}" required>
68 82
 								<p class="help">{{.i18n.Tr "repo.mirror_address_desc"}}</p>
69 83
 							</div>
70
-						{{end}}
84
+
85
+							<div class="field">
86
+								<button class="ui green button">{{$.i18n.Tr "repo.settings.update_settings"}}</button>
87
+							</div>
88
+						</form>
71 89
 
72 90
 						<div class="ui divider"></div>
73
-						<div class="field">
74
-							<button class="ui green button">{{$.i18n.Tr "repo.settings.update_settings"}}</button>
75
-						</div>
76
-					</form>
77
-				</div>
91
+
92
+						<form class="ui form" method="post">
93
+							{{.CsrfTokenHtml}}
94
+							<input type="hidden" name="action" value="mirror-sync">
95
+							<div class="inline field">
96
+								<label>{{.i18n.Tr "repo.mirror_last_synced"}}</label>
97
+								<span>{{.Mirror.Updated}}</span>
98
+							</div>
99
+							<div class="field">
100
+								<button class="ui blue button">{{$.i18n.Tr "repo.settings.sync_mirror"}}</button>
101
+							</div>
102
+						</form>
103
+					</div>
104
+				{{end}}
78 105
 
79 106
 				<h4 class="ui top attached header">
80 107
 					{{.i18n.Tr "repo.settings.advanced_settings"}}
81 108
 				</h4>
82 109
 				<div class="ui attached segment">
83
-					<form class="ui form" action="{{.Link}}" method="post">
110
+					<form class="ui form" method="post">
84 111
 						{{.CsrfTokenHtml}}
85 112
 						<input type="hidden" name="action" value="advanced">
86 113