code.gitea.io/gitea@v1.22.3/services/mirror/mirror_pull.go (about)

     1  // Copyright 2021 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package mirror
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"strings"
    10  	"time"
    11  
    12  	repo_model "code.gitea.io/gitea/models/repo"
    13  	system_model "code.gitea.io/gitea/models/system"
    14  	"code.gitea.io/gitea/modules/cache"
    15  	"code.gitea.io/gitea/modules/git"
    16  	giturl "code.gitea.io/gitea/modules/git/url"
    17  	"code.gitea.io/gitea/modules/gitrepo"
    18  	"code.gitea.io/gitea/modules/lfs"
    19  	"code.gitea.io/gitea/modules/log"
    20  	"code.gitea.io/gitea/modules/process"
    21  	"code.gitea.io/gitea/modules/proxy"
    22  	repo_module "code.gitea.io/gitea/modules/repository"
    23  	"code.gitea.io/gitea/modules/setting"
    24  	"code.gitea.io/gitea/modules/timeutil"
    25  	"code.gitea.io/gitea/modules/util"
    26  	notify_service "code.gitea.io/gitea/services/notify"
    27  )
    28  
    29  // gitShortEmptySha Git short empty SHA
    30  const gitShortEmptySha = "0000000"
    31  
    32  // UpdateAddress writes new address to Git repository and database
    33  func UpdateAddress(ctx context.Context, m *repo_model.Mirror, addr string) error {
    34  	u, err := giturl.Parse(addr)
    35  	if err != nil {
    36  		return fmt.Errorf("invalid addr: %v", err)
    37  	}
    38  
    39  	remoteName := m.GetRemoteName()
    40  	repoPath := m.GetRepository(ctx).RepoPath()
    41  	// Remove old remote
    42  	_, _, err = git.NewCommand(ctx, "remote", "rm").AddDynamicArguments(remoteName).RunStdString(&git.RunOpts{Dir: repoPath})
    43  	if err != nil && !strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") {
    44  		return err
    45  	}
    46  
    47  	cmd := git.NewCommand(ctx, "remote", "add").AddDynamicArguments(remoteName).AddArguments("--mirror=fetch").AddDynamicArguments(addr)
    48  	if strings.Contains(addr, "://") && strings.Contains(addr, "@") {
    49  		cmd.SetDescription(fmt.Sprintf("remote add %s --mirror=fetch %s [repo_path: %s]", remoteName, util.SanitizeCredentialURLs(addr), repoPath))
    50  	} else {
    51  		cmd.SetDescription(fmt.Sprintf("remote add %s --mirror=fetch %s [repo_path: %s]", remoteName, addr, repoPath))
    52  	}
    53  	_, _, err = cmd.RunStdString(&git.RunOpts{Dir: repoPath})
    54  	if err != nil && !strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") {
    55  		return err
    56  	}
    57  
    58  	if m.Repo.HasWiki() {
    59  		wikiPath := m.Repo.WikiPath()
    60  		wikiRemotePath := repo_module.WikiRemoteURL(ctx, addr)
    61  		// Remove old remote of wiki
    62  		_, _, err = git.NewCommand(ctx, "remote", "rm").AddDynamicArguments(remoteName).RunStdString(&git.RunOpts{Dir: wikiPath})
    63  		if err != nil && !strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") {
    64  			return err
    65  		}
    66  
    67  		cmd = git.NewCommand(ctx, "remote", "add").AddDynamicArguments(remoteName).AddArguments("--mirror=fetch").AddDynamicArguments(wikiRemotePath)
    68  		if strings.Contains(wikiRemotePath, "://") && strings.Contains(wikiRemotePath, "@") {
    69  			cmd.SetDescription(fmt.Sprintf("remote add %s --mirror=fetch %s [repo_path: %s]", remoteName, util.SanitizeCredentialURLs(wikiRemotePath), wikiPath))
    70  		} else {
    71  			cmd.SetDescription(fmt.Sprintf("remote add %s --mirror=fetch %s [repo_path: %s]", remoteName, wikiRemotePath, wikiPath))
    72  		}
    73  		_, _, err = cmd.RunStdString(&git.RunOpts{Dir: wikiPath})
    74  		if err != nil && !strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") {
    75  			return err
    76  		}
    77  	}
    78  
    79  	// erase authentication before storing in database
    80  	u.User = nil
    81  	m.Repo.OriginalURL = u.String()
    82  	return repo_model.UpdateRepositoryCols(ctx, m.Repo, "original_url")
    83  }
    84  
    85  // mirrorSyncResult contains information of a updated reference.
    86  // If the oldCommitID is "0000000", it means a new reference, the value of newCommitID is empty.
    87  // If the newCommitID is "0000000", it means the reference is deleted, the value of oldCommitID is empty.
    88  type mirrorSyncResult struct {
    89  	refName     git.RefName
    90  	oldCommitID string
    91  	newCommitID string
    92  }
    93  
    94  // parseRemoteUpdateOutput detects create, update and delete operations of references from upstream.
    95  // possible output example:
    96  /*
    97  // * [new tag]         v0.1.8     -> v0.1.8
    98  // * [new branch]      master     -> origin/master
    99  // - [deleted]         (none)     -> origin/test // delete a branch
   100  // - [deleted]         (none)     -> 1 // delete a tag
   101  //   957a993..a87ba5f  test       -> origin/test
   102  // + f895a1e...957a993 test       -> origin/test  (forced update)
   103  */
   104  // TODO: return whether it's a force update
   105  func parseRemoteUpdateOutput(output, remoteName string) []*mirrorSyncResult {
   106  	results := make([]*mirrorSyncResult, 0, 3)
   107  	lines := strings.Split(output, "\n")
   108  	for i := range lines {
   109  		// Make sure reference name is presented before continue
   110  		idx := strings.Index(lines[i], "-> ")
   111  		if idx == -1 {
   112  			continue
   113  		}
   114  
   115  		refName := strings.TrimSpace(lines[i][idx+3:])
   116  
   117  		switch {
   118  		case strings.HasPrefix(lines[i], " * [new tag]"): // new tag
   119  			results = append(results, &mirrorSyncResult{
   120  				refName:     git.RefNameFromTag(refName),
   121  				oldCommitID: gitShortEmptySha,
   122  			})
   123  		case strings.HasPrefix(lines[i], " * [new branch]"): // new branch
   124  			refName = strings.TrimPrefix(refName, remoteName+"/")
   125  			results = append(results, &mirrorSyncResult{
   126  				refName:     git.RefNameFromBranch(refName),
   127  				oldCommitID: gitShortEmptySha,
   128  			})
   129  		case strings.HasPrefix(lines[i], " - "): // Delete reference
   130  			isTag := !strings.HasPrefix(refName, remoteName+"/")
   131  			var refFullName git.RefName
   132  			if isTag {
   133  				refFullName = git.RefNameFromTag(refName)
   134  			} else {
   135  				refFullName = git.RefNameFromBranch(strings.TrimPrefix(refName, remoteName+"/"))
   136  			}
   137  			results = append(results, &mirrorSyncResult{
   138  				refName:     refFullName,
   139  				newCommitID: gitShortEmptySha,
   140  			})
   141  		case strings.HasPrefix(lines[i], " + "): // Force update
   142  			if idx := strings.Index(refName, " "); idx > -1 {
   143  				refName = refName[:idx]
   144  			}
   145  			delimIdx := strings.Index(lines[i][3:], " ")
   146  			if delimIdx == -1 {
   147  				log.Error("SHA delimiter not found: %q", lines[i])
   148  				continue
   149  			}
   150  			shas := strings.Split(lines[i][3:delimIdx+3], "...")
   151  			if len(shas) != 2 {
   152  				log.Error("Expect two SHAs but not what found: %q", lines[i])
   153  				continue
   154  			}
   155  			results = append(results, &mirrorSyncResult{
   156  				refName:     git.RefNameFromBranch(strings.TrimPrefix(refName, remoteName+"/")),
   157  				oldCommitID: shas[0],
   158  				newCommitID: shas[1],
   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("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("Expect two SHAs but not what found: %q", lines[i])
   169  				continue
   170  			}
   171  			results = append(results, &mirrorSyncResult{
   172  				refName:     git.RefNameFromBranch(strings.TrimPrefix(refName, remoteName+"/")),
   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  
   184  func pruneBrokenReferences(ctx context.Context,
   185  	m *repo_model.Mirror,
   186  	repoPath string,
   187  	timeout time.Duration,
   188  	stdoutBuilder, stderrBuilder *strings.Builder,
   189  	isWiki bool,
   190  ) error {
   191  	wiki := ""
   192  	if isWiki {
   193  		wiki = "Wiki "
   194  	}
   195  
   196  	stderrBuilder.Reset()
   197  	stdoutBuilder.Reset()
   198  	pruneErr := git.NewCommand(ctx, "remote", "prune").AddDynamicArguments(m.GetRemoteName()).
   199  		SetDescription(fmt.Sprintf("Mirror.runSync %ssPrune references: %s ", wiki, m.Repo.FullName())).
   200  		Run(&git.RunOpts{
   201  			Timeout: timeout,
   202  			Dir:     repoPath,
   203  			Stdout:  stdoutBuilder,
   204  			Stderr:  stderrBuilder,
   205  		})
   206  	if pruneErr != nil {
   207  		stdout := stdoutBuilder.String()
   208  		stderr := stderrBuilder.String()
   209  
   210  		// sanitize the output, since it may contain the remote address, which may
   211  		// contain a password
   212  		stderrMessage := util.SanitizeCredentialURLs(stderr)
   213  		stdoutMessage := util.SanitizeCredentialURLs(stdout)
   214  
   215  		log.Error("Failed to prune mirror repository %s%-v references:\nStdout: %s\nStderr: %s\nErr: %v", wiki, m.Repo, stdoutMessage, stderrMessage, pruneErr)
   216  		desc := fmt.Sprintf("Failed to prune mirror repository %s'%s' references: %s", wiki, repoPath, stderrMessage)
   217  		if err := system_model.CreateRepositoryNotice(desc); err != nil {
   218  			log.Error("CreateRepositoryNotice: %v", err)
   219  		}
   220  		// this if will only be reached on a successful prune so try to get the mirror again
   221  	}
   222  	return pruneErr
   223  }
   224  
   225  // runSync returns true if sync finished without error.
   226  func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bool) {
   227  	repoPath := m.Repo.RepoPath()
   228  	wikiPath := m.Repo.WikiPath()
   229  	timeout := time.Duration(setting.Git.Timeout.Mirror) * time.Second
   230  
   231  	log.Trace("SyncMirrors [repo: %-v]: running git remote update...", m.Repo)
   232  
   233  	// use fetch but not remote update because git fetch support --tags but remote update doesn't
   234  	cmd := git.NewCommand(ctx, "fetch")
   235  	if m.EnablePrune {
   236  		cmd.AddArguments("--prune")
   237  	}
   238  	cmd.AddArguments("--tags").AddDynamicArguments(m.GetRemoteName())
   239  
   240  	remoteURL, remoteErr := git.GetRemoteURL(ctx, repoPath, m.GetRemoteName())
   241  	if remoteErr != nil {
   242  		log.Error("SyncMirrors [repo: %-v]: GetRemoteAddress Error %v", m.Repo, remoteErr)
   243  		return nil, false
   244  	}
   245  
   246  	envs := proxy.EnvWithProxy(remoteURL.URL)
   247  
   248  	stdoutBuilder := strings.Builder{}
   249  	stderrBuilder := strings.Builder{}
   250  	if err := cmd.
   251  		SetDescription(fmt.Sprintf("Mirror.runSync: %s", m.Repo.FullName())).
   252  		Run(&git.RunOpts{
   253  			Timeout: timeout,
   254  			Dir:     repoPath,
   255  			Env:     envs,
   256  			Stdout:  &stdoutBuilder,
   257  			Stderr:  &stderrBuilder,
   258  		}); err != nil {
   259  		stdout := stdoutBuilder.String()
   260  		stderr := stderrBuilder.String()
   261  
   262  		// sanitize the output, since it may contain the remote address, which may contain a password
   263  		stderrMessage := util.SanitizeCredentialURLs(stderr)
   264  		stdoutMessage := util.SanitizeCredentialURLs(stdout)
   265  
   266  		// Now check if the error is a resolve reference due to broken reference
   267  		if strings.Contains(stderr, "unable to resolve reference") && strings.Contains(stderr, "reference broken") {
   268  			log.Warn("SyncMirrors [repo: %-v]: failed to update mirror repository due to broken references:\nStdout: %s\nStderr: %s\nErr: %v\nAttempting Prune", m.Repo, stdoutMessage, stderrMessage, err)
   269  			err = nil
   270  
   271  			// Attempt prune
   272  			pruneErr := pruneBrokenReferences(ctx, m, repoPath, timeout, &stdoutBuilder, &stderrBuilder, false)
   273  			if pruneErr == nil {
   274  				// Successful prune - reattempt mirror
   275  				stderrBuilder.Reset()
   276  				stdoutBuilder.Reset()
   277  				if err = cmd.
   278  					SetDescription(fmt.Sprintf("Mirror.runSync: %s", m.Repo.FullName())).
   279  					Run(&git.RunOpts{
   280  						Timeout: timeout,
   281  						Dir:     repoPath,
   282  						Stdout:  &stdoutBuilder,
   283  						Stderr:  &stderrBuilder,
   284  					}); err != nil {
   285  					stdout := stdoutBuilder.String()
   286  					stderr := stderrBuilder.String()
   287  
   288  					// sanitize the output, since it may contain the remote address, which may
   289  					// contain a password
   290  					stderrMessage = util.SanitizeCredentialURLs(stderr)
   291  					stdoutMessage = util.SanitizeCredentialURLs(stdout)
   292  				}
   293  			}
   294  		}
   295  
   296  		// If there is still an error (or there always was an error)
   297  		if err != nil {
   298  			log.Error("SyncMirrors [repo: %-v]: failed to update mirror repository:\nStdout: %s\nStderr: %s\nErr: %v", m.Repo, stdoutMessage, stderrMessage, err)
   299  			desc := fmt.Sprintf("Failed to update mirror repository '%s': %s", repoPath, stderrMessage)
   300  			if err = system_model.CreateRepositoryNotice(desc); err != nil {
   301  				log.Error("CreateRepositoryNotice: %v", err)
   302  			}
   303  			return nil, false
   304  		}
   305  	}
   306  	output := stderrBuilder.String()
   307  
   308  	if err := git.WriteCommitGraph(ctx, repoPath); err != nil {
   309  		log.Error("SyncMirrors [repo: %-v]: %v", m.Repo, err)
   310  	}
   311  
   312  	gitRepo, err := gitrepo.OpenRepository(ctx, m.Repo)
   313  	if err != nil {
   314  		log.Error("SyncMirrors [repo: %-v]: failed to OpenRepository: %v", m.Repo, err)
   315  		return nil, false
   316  	}
   317  
   318  	log.Trace("SyncMirrors [repo: %-v]: syncing branches...", m.Repo)
   319  	if _, err = repo_module.SyncRepoBranchesWithRepo(ctx, m.Repo, gitRepo, 0); err != nil {
   320  		log.Error("SyncMirrors [repo: %-v]: failed to synchronize branches: %v", m.Repo, err)
   321  	}
   322  
   323  	log.Trace("SyncMirrors [repo: %-v]: syncing releases with tags...", m.Repo)
   324  	if err = repo_module.SyncReleasesWithTags(ctx, m.Repo, gitRepo); err != nil {
   325  		log.Error("SyncMirrors [repo: %-v]: failed to synchronize tags to releases: %v", m.Repo, err)
   326  	}
   327  
   328  	if m.LFS && setting.LFS.StartServer {
   329  		log.Trace("SyncMirrors [repo: %-v]: syncing LFS objects...", m.Repo)
   330  		endpoint := lfs.DetermineEndpoint(remoteURL.String(), m.LFSEndpoint)
   331  		lfsClient := lfs.NewClient(endpoint, nil)
   332  		if err = repo_module.StoreMissingLfsObjectsInRepository(ctx, m.Repo, gitRepo, lfsClient); err != nil {
   333  			log.Error("SyncMirrors [repo: %-v]: failed to synchronize LFS objects for repository: %v", m.Repo, err)
   334  		}
   335  	}
   336  	gitRepo.Close()
   337  
   338  	log.Trace("SyncMirrors [repo: %-v]: updating size of repository", m.Repo)
   339  	if err := repo_module.UpdateRepoSize(ctx, m.Repo); err != nil {
   340  		log.Error("SyncMirrors [repo: %-v]: failed to update size for mirror repository: %v", m.Repo, err)
   341  	}
   342  
   343  	if m.Repo.HasWiki() {
   344  		log.Trace("SyncMirrors [repo: %-v Wiki]: running git remote update...", m.Repo)
   345  		stderrBuilder.Reset()
   346  		stdoutBuilder.Reset()
   347  		if err := git.NewCommand(ctx, "remote", "update", "--prune").AddDynamicArguments(m.GetRemoteName()).
   348  			SetDescription(fmt.Sprintf("Mirror.runSync Wiki: %s ", m.Repo.FullName())).
   349  			Run(&git.RunOpts{
   350  				Timeout: timeout,
   351  				Dir:     wikiPath,
   352  				Stdout:  &stdoutBuilder,
   353  				Stderr:  &stderrBuilder,
   354  			}); err != nil {
   355  			stdout := stdoutBuilder.String()
   356  			stderr := stderrBuilder.String()
   357  
   358  			// sanitize the output, since it may contain the remote address, which may contain a password
   359  			stderrMessage := util.SanitizeCredentialURLs(stderr)
   360  			stdoutMessage := util.SanitizeCredentialURLs(stdout)
   361  
   362  			// Now check if the error is a resolve reference due to broken reference
   363  			if strings.Contains(stderrMessage, "unable to resolve reference") && strings.Contains(stderrMessage, "reference broken") {
   364  				log.Warn("SyncMirrors [repo: %-v Wiki]: failed to update mirror wiki repository due to broken references:\nStdout: %s\nStderr: %s\nErr: %v\nAttempting Prune", m.Repo, stdoutMessage, stderrMessage, err)
   365  				err = nil
   366  
   367  				// Attempt prune
   368  				pruneErr := pruneBrokenReferences(ctx, m, repoPath, timeout, &stdoutBuilder, &stderrBuilder, true)
   369  				if pruneErr == nil {
   370  					// Successful prune - reattempt mirror
   371  					stderrBuilder.Reset()
   372  					stdoutBuilder.Reset()
   373  
   374  					if err = git.NewCommand(ctx, "remote", "update", "--prune").AddDynamicArguments(m.GetRemoteName()).
   375  						SetDescription(fmt.Sprintf("Mirror.runSync Wiki: %s ", m.Repo.FullName())).
   376  						Run(&git.RunOpts{
   377  							Timeout: timeout,
   378  							Dir:     wikiPath,
   379  							Stdout:  &stdoutBuilder,
   380  							Stderr:  &stderrBuilder,
   381  						}); err != nil {
   382  						stdout := stdoutBuilder.String()
   383  						stderr := stderrBuilder.String()
   384  						stderrMessage = util.SanitizeCredentialURLs(stderr)
   385  						stdoutMessage = util.SanitizeCredentialURLs(stdout)
   386  					}
   387  				}
   388  			}
   389  
   390  			// If there is still an error (or there always was an error)
   391  			if err != nil {
   392  				log.Error("SyncMirrors [repo: %-v Wiki]: failed to update mirror repository wiki:\nStdout: %s\nStderr: %s\nErr: %v", m.Repo, stdoutMessage, stderrMessage, err)
   393  				desc := fmt.Sprintf("Failed to update mirror repository wiki '%s': %s", wikiPath, stderrMessage)
   394  				if err = system_model.CreateRepositoryNotice(desc); err != nil {
   395  					log.Error("CreateRepositoryNotice: %v", err)
   396  				}
   397  				return nil, false
   398  			}
   399  
   400  			if err := git.WriteCommitGraph(ctx, wikiPath); err != nil {
   401  				log.Error("SyncMirrors [repo: %-v]: %v", m.Repo, err)
   402  			}
   403  		}
   404  		log.Trace("SyncMirrors [repo: %-v Wiki]: git remote update complete", m.Repo)
   405  	}
   406  
   407  	log.Trace("SyncMirrors [repo: %-v]: invalidating mirror branch caches...", m.Repo)
   408  	branches, _, err := gitrepo.GetBranchesByPath(ctx, m.Repo, 0, 0)
   409  	if err != nil {
   410  		log.Error("SyncMirrors [repo: %-v]: failed to GetBranches: %v", m.Repo, err)
   411  		return nil, false
   412  	}
   413  
   414  	for _, branch := range branches {
   415  		cache.Remove(m.Repo.GetCommitsCountCacheKey(branch.Name, true))
   416  	}
   417  
   418  	m.UpdatedUnix = timeutil.TimeStampNow()
   419  	return parseRemoteUpdateOutput(output, m.GetRemoteName()), true
   420  }
   421  
   422  // SyncPullMirror starts the sync of the pull mirror and schedules the next run.
   423  func SyncPullMirror(ctx context.Context, repoID int64) bool {
   424  	log.Trace("SyncMirrors [repo_id: %v]", repoID)
   425  	defer func() {
   426  		err := recover()
   427  		if err == nil {
   428  			return
   429  		}
   430  		// There was a panic whilst syncMirrors...
   431  		log.Error("PANIC whilst SyncMirrors[repo_id: %d] Panic: %v\nStacktrace: %s", repoID, err, log.Stack(2))
   432  	}()
   433  
   434  	m, err := repo_model.GetMirrorByRepoID(ctx, repoID)
   435  	if err != nil {
   436  		log.Error("SyncMirrors [repo_id: %v]: unable to GetMirrorByRepoID: %v", repoID, err)
   437  		return false
   438  	}
   439  	_ = m.GetRepository(ctx) // force load repository of mirror
   440  
   441  	ctx, _, finished := process.GetManager().AddContext(ctx, fmt.Sprintf("Syncing Mirror %s/%s", m.Repo.OwnerName, m.Repo.Name))
   442  	defer finished()
   443  
   444  	log.Trace("SyncMirrors [repo: %-v]: Running Sync", m.Repo)
   445  	results, ok := runSync(ctx, m)
   446  	if !ok {
   447  		if err = repo_model.TouchMirror(ctx, m); err != nil {
   448  			log.Error("SyncMirrors [repo: %-v]: failed to TouchMirror: %v", m.Repo, err)
   449  		}
   450  		return false
   451  	}
   452  
   453  	log.Trace("SyncMirrors [repo: %-v]: Scheduling next update", m.Repo)
   454  	m.ScheduleNextUpdate()
   455  	if err = repo_model.UpdateMirror(ctx, m); err != nil {
   456  		log.Error("SyncMirrors [repo: %-v]: failed to UpdateMirror with next update date: %v", m.Repo, err)
   457  		return false
   458  	}
   459  
   460  	gitRepo, err := gitrepo.OpenRepository(ctx, m.Repo)
   461  	if err != nil {
   462  		log.Error("SyncMirrors [repo: %-v]: unable to OpenRepository: %v", m.Repo, err)
   463  		return false
   464  	}
   465  	defer gitRepo.Close()
   466  
   467  	log.Trace("SyncMirrors [repo: %-v]: %d branches updated", m.Repo, len(results))
   468  	if len(results) > 0 {
   469  		if ok := checkAndUpdateEmptyRepository(ctx, m, results); !ok {
   470  			log.Error("SyncMirrors [repo: %-v]: checkAndUpdateEmptyRepository: %v", m.Repo, err)
   471  			return false
   472  		}
   473  	}
   474  
   475  	for _, result := range results {
   476  		// Discard GitHub pull requests, i.e. refs/pull/*
   477  		if result.refName.IsPull() {
   478  			continue
   479  		}
   480  
   481  		// Create reference
   482  		if result.oldCommitID == gitShortEmptySha {
   483  			commitID, err := gitRepo.GetRefCommitID(result.refName.String())
   484  			if err != nil {
   485  				log.Error("SyncMirrors [repo: %-v]: unable to GetRefCommitID [ref_name: %s]: %v", m.Repo, result.refName, err)
   486  				continue
   487  			}
   488  			objectFormat := git.ObjectFormatFromName(m.Repo.ObjectFormatName)
   489  			notify_service.SyncPushCommits(ctx, m.Repo.MustOwner(ctx), m.Repo, &repo_module.PushUpdateOptions{
   490  				RefFullName: result.refName,
   491  				OldCommitID: objectFormat.EmptyObjectID().String(),
   492  				NewCommitID: commitID,
   493  			}, repo_module.NewPushCommits())
   494  			notify_service.SyncCreateRef(ctx, m.Repo.MustOwner(ctx), m.Repo, result.refName, commitID)
   495  			continue
   496  		}
   497  
   498  		// Delete reference
   499  		if result.newCommitID == gitShortEmptySha {
   500  			notify_service.SyncDeleteRef(ctx, m.Repo.MustOwner(ctx), m.Repo, result.refName)
   501  			continue
   502  		}
   503  
   504  		// Push commits
   505  		oldCommitID, err := git.GetFullCommitID(gitRepo.Ctx, gitRepo.Path, result.oldCommitID)
   506  		if err != nil {
   507  			log.Error("SyncMirrors [repo: %-v]: unable to get GetFullCommitID[%s]: %v", m.Repo, result.oldCommitID, err)
   508  			continue
   509  		}
   510  		newCommitID, err := git.GetFullCommitID(gitRepo.Ctx, gitRepo.Path, result.newCommitID)
   511  		if err != nil {
   512  			log.Error("SyncMirrors [repo: %-v]: unable to get GetFullCommitID [%s]: %v", m.Repo, result.newCommitID, err)
   513  			continue
   514  		}
   515  		commits, err := gitRepo.CommitsBetweenIDs(newCommitID, oldCommitID)
   516  		if err != nil {
   517  			log.Error("SyncMirrors [repo: %-v]: unable to get CommitsBetweenIDs [new_commit_id: %s, old_commit_id: %s]: %v", m.Repo, newCommitID, oldCommitID, err)
   518  			continue
   519  		}
   520  
   521  		theCommits := repo_module.GitToPushCommits(commits)
   522  		if len(theCommits.Commits) > setting.UI.FeedMaxCommitNum {
   523  			theCommits.Commits = theCommits.Commits[:setting.UI.FeedMaxCommitNum]
   524  		}
   525  
   526  		newCommit, err := gitRepo.GetCommit(newCommitID)
   527  		if err != nil {
   528  			log.Error("SyncMirrors [repo: %-v]: unable to get commit %s: %v", m.Repo, newCommitID, err)
   529  			continue
   530  		}
   531  
   532  		theCommits.HeadCommit = repo_module.CommitToPushCommit(newCommit)
   533  		theCommits.CompareURL = m.Repo.ComposeCompareURL(oldCommitID, newCommitID)
   534  
   535  		notify_service.SyncPushCommits(ctx, m.Repo.MustOwner(ctx), m.Repo, &repo_module.PushUpdateOptions{
   536  			RefFullName: result.refName,
   537  			OldCommitID: oldCommitID,
   538  			NewCommitID: newCommitID,
   539  		}, theCommits)
   540  	}
   541  	log.Trace("SyncMirrors [repo: %-v]: done notifying updated branches/tags - now updating last commit time", m.Repo)
   542  
   543  	isEmpty, err := gitRepo.IsEmpty()
   544  	if err != nil {
   545  		log.Error("SyncMirrors [repo: %-v]: unable to check empty git repo: %v", m.Repo, err)
   546  		return false
   547  	}
   548  	if !isEmpty {
   549  		// Get latest commit date and update to current repository updated time
   550  		commitDate, err := git.GetLatestCommitTime(ctx, m.Repo.RepoPath())
   551  		if err != nil {
   552  			log.Error("SyncMirrors [repo: %-v]: unable to GetLatestCommitDate: %v", m.Repo, err)
   553  			return false
   554  		}
   555  
   556  		if err = repo_model.UpdateRepositoryUpdatedTime(ctx, m.RepoID, commitDate); err != nil {
   557  			log.Error("SyncMirrors [repo: %-v]: unable to update repository 'updated_unix': %v", m.Repo, err)
   558  			return false
   559  		}
   560  	}
   561  
   562  	log.Trace("SyncMirrors [repo: %-v]: Successfully updated", m.Repo)
   563  
   564  	return true
   565  }
   566  
   567  func checkAndUpdateEmptyRepository(ctx context.Context, m *repo_model.Mirror, results []*mirrorSyncResult) bool {
   568  	if !m.Repo.IsEmpty {
   569  		return true
   570  	}
   571  
   572  	hasDefault := false
   573  	hasMaster := false
   574  	hasMain := false
   575  	defaultBranchName := m.Repo.DefaultBranch
   576  	if len(defaultBranchName) == 0 {
   577  		defaultBranchName = setting.Repository.DefaultBranch
   578  	}
   579  	firstName := ""
   580  	for _, result := range results {
   581  		if !result.refName.IsBranch() {
   582  			continue
   583  		}
   584  
   585  		name := result.refName.BranchName()
   586  		if len(firstName) == 0 {
   587  			firstName = name
   588  		}
   589  
   590  		hasDefault = hasDefault || name == defaultBranchName
   591  		hasMaster = hasMaster || name == "master"
   592  		hasMain = hasMain || name == "main"
   593  	}
   594  
   595  	if len(firstName) > 0 {
   596  		if hasDefault {
   597  			m.Repo.DefaultBranch = defaultBranchName
   598  		} else if hasMaster {
   599  			m.Repo.DefaultBranch = "master"
   600  		} else if hasMain {
   601  			m.Repo.DefaultBranch = "main"
   602  		} else {
   603  			m.Repo.DefaultBranch = firstName
   604  		}
   605  		// Update the git repository default branch
   606  		if err := gitrepo.SetDefaultBranch(ctx, m.Repo, m.Repo.DefaultBranch); err != nil {
   607  			if !git.IsErrUnsupportedVersion(err) {
   608  				log.Error("Failed to update default branch of underlying git repository %-v. Error: %v", m.Repo, err)
   609  				desc := fmt.Sprintf("Failed to update default branch of underlying git repository '%s': %v", m.Repo.RepoPath(), err)
   610  				if err = system_model.CreateRepositoryNotice(desc); err != nil {
   611  					log.Error("CreateRepositoryNotice: %v", err)
   612  				}
   613  				return false
   614  			}
   615  		}
   616  		m.Repo.IsEmpty = false
   617  		// Update the is empty and default_branch columns
   618  		if err := repo_model.UpdateRepositoryCols(ctx, m.Repo, "default_branch", "is_empty"); err != nil {
   619  			log.Error("Failed to update default branch of repository %-v. Error: %v", m.Repo, err)
   620  			desc := fmt.Sprintf("Failed to update default branch of repository '%s': %v", m.Repo.RepoPath(), err)
   621  			if err = system_model.CreateRepositoryNotice(desc); err != nil {
   622  				log.Error("CreateRepositoryNotice: %v", err)
   623  			}
   624  			return false
   625  		}
   626  	}
   627  	return true
   628  }