11// Copyright 2016 The Gogs Authors. All rights reserved.
2+ // Copyright 2018 The Gitea Authors. All rights reserved.
23// Use of this source code is governed by a MIT-style
34// license that can be found in the LICENSE file.
45
56package models
67
78import (
89 "fmt"
10+ "strings"
911 "time"
1012
1113 "code.gitea.io/git"
@@ -119,8 +121,68 @@ func (m *Mirror) SaveAddress(addr string) error {
119121 return cfg .SaveToIndent (configPath , "\t " )
120122}
121123
124+ // gitShortEmptySha Git short empty SHA
125+ const gitShortEmptySha = "0000000"
126+
127+ // mirrorSyncResult contains information of a updated reference.
128+ // If the oldCommitID is "0000000", it means a new reference, the value of newCommitID is empty.
129+ // If the newCommitID is "0000000", it means the reference is deleted, the value of oldCommitID is empty.
130+ type mirrorSyncResult struct {
131+ refName string
132+ oldCommitID string
133+ newCommitID string
134+ }
135+
136+ // parseRemoteUpdateOutput detects create, update and delete operations of references from upstream.
137+ func parseRemoteUpdateOutput (output string ) []* mirrorSyncResult {
138+ results := make ([]* mirrorSyncResult , 0 , 3 )
139+ lines := strings .Split (output , "\n " )
140+ for i := range lines {
141+ // Make sure reference name is presented before continue
142+ idx := strings .Index (lines [i ], "-> " )
143+ if idx == - 1 {
144+ continue
145+ }
146+
147+ refName := lines [i ][idx + 3 :]
148+
149+ switch {
150+ case strings .HasPrefix (lines [i ], " * " ): // New reference
151+ results = append (results , & mirrorSyncResult {
152+ refName : refName ,
153+ oldCommitID : gitShortEmptySha ,
154+ })
155+ case strings .HasPrefix (lines [i ], " - " ): // Delete reference
156+ results = append (results , & mirrorSyncResult {
157+ refName : refName ,
158+ newCommitID : gitShortEmptySha ,
159+ })
160+ case strings .HasPrefix (lines [i ], " " ): // New commits of a reference
161+ delimIdx := strings .Index (lines [i ][3 :], " " )
162+ if delimIdx == - 1 {
163+ log .Error (2 , "SHA delimiter not found: %q" , lines [i ])
164+ continue
165+ }
166+ shas := strings .Split (lines [i ][3 :delimIdx + 3 ], ".." )
167+ if len (shas ) != 2 {
168+ log .Error (2 , "Expect two SHAs but not what found: %q" , lines [i ])
169+ continue
170+ }
171+ results = append (results , & mirrorSyncResult {
172+ refName : refName ,
173+ oldCommitID : shas [0 ],
174+ newCommitID : shas [1 ],
175+ })
176+
177+ default :
178+ log .Warn ("parseRemoteUpdateOutput: unexpected update line %q" , lines [i ])
179+ }
180+ }
181+ return results
182+ }
183+
122184// runSync returns true if sync finished without error.
123- func (m * Mirror ) runSync () bool {
185+ func (m * Mirror ) runSync () ([] * mirrorSyncResult , bool ) {
124186 repoPath := m .Repo .RepoPath ()
125187 wikiPath := m .Repo .WikiPath ()
126188 timeout := time .Duration (setting .Git .Timeout .Mirror ) * time .Second
@@ -130,28 +192,30 @@ func (m *Mirror) runSync() bool {
130192 gitArgs = append (gitArgs , "--prune" )
131193 }
132194
133- if _ , stderr , err := process .GetManager ().ExecDir (
195+ _ , stderr , err := process .GetManager ().ExecDir (
134196 timeout , repoPath , fmt .Sprintf ("Mirror.runSync: %s" , repoPath ),
135- "git" , gitArgs ... ); err != nil {
197+ "git" , gitArgs ... )
198+ if err != nil {
136199 // sanitize the output, since it may contain the remote address, which may
137200 // contain a password
138201 message , err := sanitizeOutput (stderr , repoPath )
139202 if err != nil {
140203 log .Error (4 , "sanitizeOutput: %v" , err )
141- return false
204+ return nil , false
142205 }
143206 desc := fmt .Sprintf ("Failed to update mirror repository '%s': %s" , repoPath , message )
144207 log .Error (4 , desc )
145208 if err = CreateRepositoryNotice (desc ); err != nil {
146209 log .Error (4 , "CreateRepositoryNotice: %v" , err )
147210 }
148- return false
211+ return nil , false
149212 }
213+ output := stderr
150214
151215 gitRepo , err := git .OpenRepository (repoPath )
152216 if err != nil {
153217 log .Error (4 , "OpenRepository: %v" , err )
154- return false
218+ return nil , false
155219 }
156220 if err = SyncReleasesWithTags (m .Repo , gitRepo ); err != nil {
157221 log .Error (4 , "Failed to synchronize tags to releases for repository: %v" , err )
@@ -170,29 +234,29 @@ func (m *Mirror) runSync() bool {
170234 message , err := sanitizeOutput (stderr , wikiPath )
171235 if err != nil {
172236 log .Error (4 , "sanitizeOutput: %v" , err )
173- return false
237+ return nil , false
174238 }
175239 desc := fmt .Sprintf ("Failed to update mirror wiki repository '%s': %s" , wikiPath , message )
176240 log .Error (4 , desc )
177241 if err = CreateRepositoryNotice (desc ); err != nil {
178242 log .Error (4 , "CreateRepositoryNotice: %v" , err )
179243 }
180- return false
244+ return nil , false
181245 }
182246 }
183247
184248 branches , err := m .Repo .GetBranches ()
185249 if err != nil {
186250 log .Error (4 , "GetBranches: %v" , err )
187- return false
251+ return nil , false
188252 }
189253
190254 for i := range branches {
191255 cache .Remove (m .Repo .GetCommitsCountCacheKey (branches [i ].Name , true ))
192256 }
193257
194258 m .UpdatedUnix = util .TimeStampNow ()
195- return true
259+ return parseRemoteUpdateOutput ( output ), true
196260}
197261
198262func getMirrorByRepoID (e Engine , repoID int64 ) (* Mirror , error ) {
@@ -268,7 +332,8 @@ func SyncMirrors() {
268332 continue
269333 }
270334
271- if ! m .runSync () {
335+ results , ok := m .runSync ()
336+ if ! ok {
272337 continue
273338 }
274339
@@ -278,6 +343,66 @@ func SyncMirrors() {
278343 continue
279344 }
280345
346+ var gitRepo * git.Repository
347+ if len (results ) == 0 {
348+ log .Trace ("SyncMirrors [repo_id: %d]: no commits fetched" , m .RepoID )
349+ } else {
350+ gitRepo , err = git .OpenRepository (m .Repo .RepoPath ())
351+ if err != nil {
352+ log .Error (2 , "OpenRepository [%d]: %v" , m .RepoID , err )
353+ continue
354+ }
355+ }
356+
357+ for _ , result := range results {
358+ // Discard GitHub pull requests, i.e. refs/pull/*
359+ if strings .HasPrefix (result .refName , "refs/pull/" ) {
360+ continue
361+ }
362+
363+ // Create reference
364+ if result .oldCommitID == gitShortEmptySha {
365+ if err = MirrorSyncCreateAction (m .Repo , result .refName ); err != nil {
366+ log .Error (2 , "MirrorSyncCreateAction [repo_id: %d]: %v" , m .RepoID , err )
367+ }
368+ continue
369+ }
370+
371+ // Delete reference
372+ if result .newCommitID == gitShortEmptySha {
373+ if err = MirrorSyncDeleteAction (m .Repo , result .refName ); err != nil {
374+ log .Error (2 , "MirrorSyncDeleteAction [repo_id: %d]: %v" , m .RepoID , err )
375+ }
376+ continue
377+ }
378+
379+ // Push commits
380+ oldCommitID , err := git .GetFullCommitID (gitRepo .Path , result .oldCommitID )
381+ if err != nil {
382+ log .Error (2 , "GetFullCommitID [%d]: %v" , m .RepoID , err )
383+ continue
384+ }
385+ newCommitID , err := git .GetFullCommitID (gitRepo .Path , result .newCommitID )
386+ if err != nil {
387+ log .Error (2 , "GetFullCommitID [%d]: %v" , m .RepoID , err )
388+ continue
389+ }
390+ commits , err := gitRepo .CommitsBetweenIDs (newCommitID , oldCommitID )
391+ if err != nil {
392+ log .Error (2 , "CommitsBetweenIDs [repo_id: %d, new_commit_id: %s, old_commit_id: %s]: %v" , m .RepoID , newCommitID , oldCommitID , err )
393+ continue
394+ }
395+ if err = MirrorSyncPushAction (m .Repo , MirrorSyncPushActionOptions {
396+ RefName : result .refName ,
397+ OldCommitID : oldCommitID ,
398+ NewCommitID : newCommitID ,
399+ Commits : ListToPushCommits (commits ),
400+ }); err != nil {
401+ log .Error (2 , "MirrorSyncPushAction [repo_id: %d]: %v" , m .RepoID , err )
402+ continue
403+ }
404+ }
405+
281406 // Get latest commit date and update to current repository updated time
282407 commitDate , err := git .GetLatestCommitTime (m .Repo .RepoPath ())
283408 if err != nil {
0 commit comments