2024-12-07 05:10:35 +08:00
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repository
import (
"context"
"fmt"
git_model "code.gitea.io/gitea/models/git"
issue_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
2025-01-10 15:11:45 +08:00
"code.gitea.io/gitea/modules/gitrepo"
2024-12-07 05:10:35 +08:00
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/pull"
)
type UpstreamDivergingInfo struct {
2025-01-10 15:11:45 +08:00
BaseHasNewCommits bool
CommitsBehind int
CommitsAhead int
2024-12-07 05:10:35 +08:00
}
2025-01-10 15:11:45 +08:00
// MergeUpstream merges the base repository's default branch into the fork repository's current branch.
2024-12-07 05:10:35 +08:00
func MergeUpstream ( ctx context . Context , doer * user_model . User , repo * repo_model . Repository , branch string ) ( mergeStyle string , err error ) {
if err = repo . MustNotBeArchived ( ) ; err != nil {
return "" , err
}
if err = repo . GetBaseRepo ( ctx ) ; err != nil {
return "" , err
}
err = git . Push ( ctx , repo . BaseRepo . RepoPath ( ) , git . PushOptions {
Remote : repo . RepoPath ( ) ,
2025-01-10 15:11:45 +08:00
Branch : fmt . Sprintf ( "%s:%s" , repo . BaseRepo . DefaultBranch , branch ) ,
2024-12-07 05:10:35 +08:00
Env : repo_module . PushingEnvironment ( doer , repo ) ,
} )
if err == nil {
return "fast-forward" , nil
}
if ! git . IsErrPushOutOfDate ( err ) && ! git . IsErrPushRejected ( err ) {
return "" , err
}
// TODO: FakePR: it is somewhat hacky, but it is the only way to "merge" at the moment
// ideally in the future the "merge" functions should be refactored to decouple from the PullRequest
fakeIssue := & issue_model . Issue {
ID : - 1 ,
RepoID : repo . ID ,
Repo : repo ,
Index : - 1 ,
PosterID : doer . ID ,
Poster : doer ,
IsPull : true ,
}
fakePR := & issue_model . PullRequest {
ID : - 1 ,
Status : issue_model . PullRequestStatusMergeable ,
IssueID : - 1 ,
Issue : fakeIssue ,
Index : - 1 ,
HeadRepoID : repo . ID ,
HeadRepo : repo ,
BaseRepoID : repo . BaseRepo . ID ,
BaseRepo : repo . BaseRepo ,
HeadBranch : branch , // maybe HeadCommitID is not needed
2025-01-10 15:11:45 +08:00
BaseBranch : repo . BaseRepo . DefaultBranch ,
2024-12-07 05:10:35 +08:00
}
fakeIssue . PullRequest = fakePR
err = pull . Update ( ctx , fakePR , doer , "merge upstream" , false )
if err != nil {
return "" , err
}
return "merge" , nil
}
2025-01-10 15:11:45 +08:00
// GetUpstreamDivergingInfo returns the information about the divergence between the fork repository's branch and the base repository's default branch.
2024-12-07 05:10:35 +08:00
func GetUpstreamDivergingInfo ( ctx context . Context , repo * repo_model . Repository , branch string ) ( * UpstreamDivergingInfo , error ) {
if ! repo . IsFork {
return nil , util . NewInvalidArgumentErrorf ( "repo is not a fork" )
}
if repo . IsArchived {
return nil , util . NewInvalidArgumentErrorf ( "repo is archived" )
}
if err := repo . GetBaseRepo ( ctx ) ; err != nil {
return nil , err
}
forkBranch , err := git_model . GetBranch ( ctx , repo . ID , branch )
if err != nil {
return nil , err
}
2025-01-10 15:11:45 +08:00
baseBranch , err := git_model . GetBranch ( ctx , repo . BaseRepo . ID , repo . BaseRepo . DefaultBranch )
2024-12-07 05:10:35 +08:00
if err != nil {
return nil , err
}
info := & UpstreamDivergingInfo { }
if forkBranch . CommitID == baseBranch . CommitID {
return info , nil
}
2025-01-10 15:11:45 +08:00
// if the fork repo has new commits, this call will fail because they are not in the base repo
2024-12-07 05:10:35 +08:00
// exit status 128 - fatal: Invalid symmetric difference expression aaaaaaaaaaaa...bbbbbbbbbbbb
2025-01-10 15:11:45 +08:00
// so at the moment, we first check the update time, then check whether the fork branch has base's head
2024-12-07 05:10:35 +08:00
diff , err := git . GetDivergingCommits ( ctx , repo . BaseRepo . RepoPath ( ) , baseBranch . CommitID , forkBranch . CommitID )
if err != nil {
2025-01-10 15:11:45 +08:00
info . BaseHasNewCommits = baseBranch . UpdatedUnix > forkBranch . UpdatedUnix
if info . BaseHasNewCommits {
return info , nil
}
// if the base's update time is before the fork, check whether the base's head is in the fork
baseGitRepo , baseGitRepoCloser , err := gitrepo . RepositoryFromContextOrOpen ( ctx , repo . BaseRepo )
if err != nil {
return nil , err
}
defer baseGitRepoCloser . Close ( )
headGitRepo , headGitRepoCloser , err := gitrepo . RepositoryFromContextOrOpen ( ctx , repo )
if err != nil {
return nil , err
}
defer headGitRepoCloser . Close ( )
baseCommitID , err := baseGitRepo . ConvertToGitID ( baseBranch . CommitID )
if err != nil {
return nil , err
}
headCommit , err := headGitRepo . GetCommit ( forkBranch . CommitID )
if err != nil {
return nil , err
}
hasPreviousCommit , _ := headCommit . HasPreviousCommit ( baseCommitID )
info . BaseHasNewCommits = ! hasPreviousCommit
2024-12-07 05:10:35 +08:00
return info , nil
}
2025-01-10 15:11:45 +08:00
2024-12-07 05:10:35 +08:00
info . CommitsBehind , info . CommitsAhead = diff . Behind , diff . Ahead
return info , nil
}