* make notifyWatchers work on multiple actions * more efficient multiple notifyWatchers * Make CommitRepoAction take advantage of multiple actions * Batch post and pre-receive results * Set batch to 30 * Auto adjust timeout & add logging * adjust processing message * Add some messages to pre-receive * Make any non-200 status code from pre-receive an error * Add missing hookPrintResults * Remove shortcut for single action * mistaken merge fix * oops * Move master branch to the front * If repo was empty and the master branch is pushed ensure that that is set as the default branch * fixup * fixup * Missed HookOptions in setdefaultbranch * Batch PushUpdateAddTag and PushUpdateDelTag Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>tags/v1.21.12.1
| @@ -21,6 +21,10 @@ import ( | |||
| "github.com/urfave/cli" | |||
| ) | |||
| const ( | |||
| hookBatchSize = 30 | |||
| ) | |||
| var ( | |||
| // CmdHook represents the available hooks sub-command. | |||
| CmdHook = cli.Command{ | |||
| @@ -75,12 +79,25 @@ Gitea or set your environment appropriately.`, "") | |||
| prID, _ := strconv.ParseInt(os.Getenv(models.ProtectedBranchPRID), 10, 64) | |||
| isDeployKey, _ := strconv.ParseBool(os.Getenv(models.EnvIsDeployKey)) | |||
| buf := bytes.NewBuffer(nil) | |||
| hookOptions := private.HookOptions{ | |||
| UserID: userID, | |||
| GitAlternativeObjectDirectories: os.Getenv(private.GitAlternativeObjectDirectories), | |||
| GitObjectDirectory: os.Getenv(private.GitObjectDirectory), | |||
| GitQuarantinePath: os.Getenv(private.GitQuarantinePath), | |||
| ProtectedBranchID: prID, | |||
| IsDeployKey: isDeployKey, | |||
| } | |||
| scanner := bufio.NewScanner(os.Stdin) | |||
| for scanner.Scan() { | |||
| buf.Write(scanner.Bytes()) | |||
| buf.WriteByte('\n') | |||
| oldCommitIDs := make([]string, hookBatchSize) | |||
| newCommitIDs := make([]string, hookBatchSize) | |||
| refFullNames := make([]string, hookBatchSize) | |||
| count := 0 | |||
| total := 0 | |||
| lastline := 0 | |||
| for scanner.Scan() { | |||
| // TODO: support news feeds for wiki | |||
| if isWiki { | |||
| continue | |||
| @@ -94,29 +111,72 @@ Gitea or set your environment appropriately.`, "") | |||
| oldCommitID := string(fields[0]) | |||
| newCommitID := string(fields[1]) | |||
| refFullName := string(fields[2]) | |||
| total++ | |||
| lastline++ | |||
| // If the ref is a branch, check if it's protected | |||
| if strings.HasPrefix(refFullName, git.BranchPrefix) { | |||
| statusCode, msg := private.HookPreReceive(username, reponame, private.HookOptions{ | |||
| OldCommitID: oldCommitID, | |||
| NewCommitID: newCommitID, | |||
| RefFullName: refFullName, | |||
| UserID: userID, | |||
| GitAlternativeObjectDirectories: os.Getenv(private.GitAlternativeObjectDirectories), | |||
| GitObjectDirectory: os.Getenv(private.GitObjectDirectory), | |||
| GitQuarantinePath: os.Getenv(private.GitQuarantinePath), | |||
| ProtectedBranchID: prID, | |||
| IsDeployKey: isDeployKey, | |||
| }) | |||
| switch statusCode { | |||
| case http.StatusInternalServerError: | |||
| fail("Internal Server Error", msg) | |||
| case http.StatusForbidden: | |||
| fail(msg, "") | |||
| oldCommitIDs[count] = oldCommitID | |||
| newCommitIDs[count] = newCommitID | |||
| refFullNames[count] = refFullName | |||
| count++ | |||
| fmt.Fprintf(os.Stdout, "*") | |||
| os.Stdout.Sync() | |||
| if count >= hookBatchSize { | |||
| fmt.Fprintf(os.Stdout, " Checking %d branches\n", count) | |||
| os.Stdout.Sync() | |||
| hookOptions.OldCommitIDs = oldCommitIDs | |||
| hookOptions.NewCommitIDs = newCommitIDs | |||
| hookOptions.RefFullNames = refFullNames | |||
| statusCode, msg := private.HookPreReceive(username, reponame, hookOptions) | |||
| switch statusCode { | |||
| case http.StatusOK: | |||
| // no-op | |||
| case http.StatusInternalServerError: | |||
| fail("Internal Server Error", msg) | |||
| default: | |||
| fail(msg, "") | |||
| } | |||
| count = 0 | |||
| lastline = 0 | |||
| } | |||
| } else { | |||
| fmt.Fprintf(os.Stdout, ".") | |||
| os.Stdout.Sync() | |||
| } | |||
| if lastline >= hookBatchSize { | |||
| fmt.Fprintf(os.Stdout, "\n") | |||
| os.Stdout.Sync() | |||
| lastline = 0 | |||
| } | |||
| } | |||
| if count > 0 { | |||
| hookOptions.OldCommitIDs = oldCommitIDs[:count] | |||
| hookOptions.NewCommitIDs = newCommitIDs[:count] | |||
| hookOptions.RefFullNames = refFullNames[:count] | |||
| fmt.Fprintf(os.Stdout, " Checking %d branches\n", count) | |||
| os.Stdout.Sync() | |||
| statusCode, msg := private.HookPreReceive(username, reponame, hookOptions) | |||
| switch statusCode { | |||
| case http.StatusInternalServerError: | |||
| fail("Internal Server Error", msg) | |||
| case http.StatusForbidden: | |||
| fail(msg, "") | |||
| } | |||
| } else if lastline > 0 { | |||
| fmt.Fprintf(os.Stdout, "\n") | |||
| os.Stdout.Sync() | |||
| lastline = 0 | |||
| } | |||
| fmt.Fprintf(os.Stdout, "Checked %d references in total\n", total) | |||
| os.Stdout.Sync() | |||
| return nil | |||
| } | |||
| @@ -156,12 +216,24 @@ Gitea or set your environment appropriately.`, "") | |||
| pusherID, _ := strconv.ParseInt(os.Getenv(models.EnvPusherID), 10, 64) | |||
| pusherName := os.Getenv(models.EnvPusherName) | |||
| buf := bytes.NewBuffer(nil) | |||
| hookOptions := private.HookOptions{ | |||
| UserName: pusherName, | |||
| UserID: pusherID, | |||
| GitAlternativeObjectDirectories: os.Getenv(private.GitAlternativeObjectDirectories), | |||
| GitObjectDirectory: os.Getenv(private.GitObjectDirectory), | |||
| GitQuarantinePath: os.Getenv(private.GitQuarantinePath), | |||
| } | |||
| oldCommitIDs := make([]string, hookBatchSize) | |||
| newCommitIDs := make([]string, hookBatchSize) | |||
| refFullNames := make([]string, hookBatchSize) | |||
| count := 0 | |||
| total := 0 | |||
| wasEmpty := false | |||
| masterPushed := false | |||
| results := make([]private.HookPostReceiveBranchResult, 0) | |||
| scanner := bufio.NewScanner(os.Stdin) | |||
| for scanner.Scan() { | |||
| buf.Write(scanner.Bytes()) | |||
| buf.WriteByte('\n') | |||
| // TODO: support news feeds for wiki | |||
| if isWiki { | |||
| continue | |||
| @@ -172,36 +244,95 @@ Gitea or set your environment appropriately.`, "") | |||
| continue | |||
| } | |||
| oldCommitID := string(fields[0]) | |||
| newCommitID := string(fields[1]) | |||
| refFullName := string(fields[2]) | |||
| fmt.Fprintf(os.Stdout, ".") | |||
| oldCommitIDs[count] = string(fields[0]) | |||
| newCommitIDs[count] = string(fields[1]) | |||
| refFullNames[count] = string(fields[2]) | |||
| if refFullNames[count] == git.BranchPrefix+"master" && newCommitIDs[count] != git.EmptySHA && count == total { | |||
| masterPushed = true | |||
| } | |||
| count++ | |||
| total++ | |||
| os.Stdout.Sync() | |||
| if count >= hookBatchSize { | |||
| fmt.Fprintf(os.Stdout, " Processing %d references\n", count) | |||
| os.Stdout.Sync() | |||
| hookOptions.OldCommitIDs = oldCommitIDs | |||
| hookOptions.NewCommitIDs = newCommitIDs | |||
| hookOptions.RefFullNames = refFullNames | |||
| resp, err := private.HookPostReceive(repoUser, repoName, hookOptions) | |||
| if resp == nil { | |||
| hookPrintResults(results) | |||
| fail("Internal Server Error", err) | |||
| } | |||
| wasEmpty = wasEmpty || resp.RepoWasEmpty | |||
| results = append(results, resp.Results...) | |||
| count = 0 | |||
| } | |||
| } | |||
| if count == 0 { | |||
| if wasEmpty && masterPushed { | |||
| // We need to tell the repo to reset the default branch to master | |||
| err := private.SetDefaultBranch(repoUser, repoName, "master") | |||
| if err != nil { | |||
| fail("Internal Server Error", "SetDefaultBranch failed with Error: %v", err) | |||
| } | |||
| } | |||
| fmt.Fprintf(os.Stdout, "Processed %d references in total\n", total) | |||
| os.Stdout.Sync() | |||
| hookPrintResults(results) | |||
| return nil | |||
| } | |||
| res, err := private.HookPostReceive(repoUser, repoName, private.HookOptions{ | |||
| OldCommitID: oldCommitID, | |||
| NewCommitID: newCommitID, | |||
| RefFullName: refFullName, | |||
| UserID: pusherID, | |||
| UserName: pusherName, | |||
| }) | |||
| hookOptions.OldCommitIDs = oldCommitIDs[:count] | |||
| hookOptions.NewCommitIDs = newCommitIDs[:count] | |||
| hookOptions.RefFullNames = refFullNames[:count] | |||
| if res == nil { | |||
| fail("Internal Server Error", err) | |||
| fmt.Fprintf(os.Stdout, " Processing %d references\n", count) | |||
| os.Stdout.Sync() | |||
| resp, err := private.HookPostReceive(repoUser, repoName, hookOptions) | |||
| if resp == nil { | |||
| hookPrintResults(results) | |||
| fail("Internal Server Error", err) | |||
| } | |||
| wasEmpty = wasEmpty || resp.RepoWasEmpty | |||
| results = append(results, resp.Results...) | |||
| fmt.Fprintf(os.Stdout, "Processed %d references in total\n", total) | |||
| os.Stdout.Sync() | |||
| if wasEmpty && masterPushed { | |||
| // We need to tell the repo to reset the default branch to master | |||
| err := private.SetDefaultBranch(repoUser, repoName, "master") | |||
| if err != nil { | |||
| fail("Internal Server Error", "SetDefaultBranch failed with Error: %v", err) | |||
| } | |||
| } | |||
| hookPrintResults(results) | |||
| if res["message"] == false { | |||
| return nil | |||
| } | |||
| func hookPrintResults(results []private.HookPostReceiveBranchResult) { | |||
| for _, res := range results { | |||
| if !res.Message { | |||
| continue | |||
| } | |||
| fmt.Fprintln(os.Stderr, "") | |||
| if res["create"] == true { | |||
| fmt.Fprintf(os.Stderr, "Create a new pull request for '%s':\n", res["branch"]) | |||
| fmt.Fprintf(os.Stderr, " %s\n", res["url"]) | |||
| if res.Create { | |||
| fmt.Fprintf(os.Stderr, "Create a new pull request for '%s':\n", res.Branch) | |||
| fmt.Fprintf(os.Stderr, " %s\n", res.URL) | |||
| } else { | |||
| fmt.Fprint(os.Stderr, "Visit the existing pull request:\n") | |||
| fmt.Fprintf(os.Stderr, " %s\n", res["url"]) | |||
| fmt.Fprintf(os.Stderr, " %s\n", res.URL) | |||
| } | |||
| fmt.Fprintln(os.Stderr, "") | |||
| os.Stderr.Sync() | |||
| } | |||
| return nil | |||
| } | |||
| @@ -164,68 +164,111 @@ func (repo *Repository) GetWatchers(page int) ([]*User, error) { | |||
| return users, sess.Find(&users) | |||
| } | |||
| func notifyWatchers(e Engine, act *Action) error { | |||
| // Add feeds for user self and all watchers. | |||
| watches, err := getWatchers(e, act.RepoID) | |||
| if err != nil { | |||
| return fmt.Errorf("get watchers: %v", err) | |||
| } | |||
| // Add feed for actioner. | |||
| act.UserID = act.ActUserID | |||
| if _, err = e.InsertOne(act); err != nil { | |||
| return fmt.Errorf("insert new actioner: %v", err) | |||
| } | |||
| act.loadRepo() | |||
| // check repo owner exist. | |||
| if err := act.Repo.getOwner(e); err != nil { | |||
| return fmt.Errorf("can't get repo owner: %v", err) | |||
| } | |||
| func notifyWatchers(e Engine, actions ...*Action) error { | |||
| var watchers []*Watch | |||
| var repo *Repository | |||
| var err error | |||
| var permCode []bool | |||
| var permIssue []bool | |||
| var permPR []bool | |||
| for _, act := range actions { | |||
| repoChanged := repo == nil || repo.ID != act.RepoID | |||
| if repoChanged { | |||
| // Add feeds for user self and all watchers. | |||
| watchers, err = getWatchers(e, act.RepoID) | |||
| if err != nil { | |||
| return fmt.Errorf("get watchers: %v", err) | |||
| } | |||
| } | |||
| // Add feed for organization | |||
| if act.Repo.Owner.IsOrganization() && act.ActUserID != act.Repo.Owner.ID { | |||
| act.ID = 0 | |||
| act.UserID = act.Repo.Owner.ID | |||
| // Add feed for actioner. | |||
| act.UserID = act.ActUserID | |||
| if _, err = e.InsertOne(act); err != nil { | |||
| return fmt.Errorf("insert new actioner: %v", err) | |||
| } | |||
| } | |||
| for i := range watches { | |||
| if act.ActUserID == watches[i].UserID { | |||
| continue | |||
| if repoChanged { | |||
| act.loadRepo() | |||
| repo = act.Repo | |||
| // check repo owner exist. | |||
| if err := act.Repo.getOwner(e); err != nil { | |||
| return fmt.Errorf("can't get repo owner: %v", err) | |||
| } | |||
| } else if act.Repo == nil { | |||
| act.Repo = repo | |||
| } | |||
| act.ID = 0 | |||
| act.UserID = watches[i].UserID | |||
| act.Repo.Units = nil | |||
| // Add feed for organization | |||
| if act.Repo.Owner.IsOrganization() && act.ActUserID != act.Repo.Owner.ID { | |||
| act.ID = 0 | |||
| act.UserID = act.Repo.Owner.ID | |||
| if _, err = e.InsertOne(act); err != nil { | |||
| return fmt.Errorf("insert new actioner: %v", err) | |||
| } | |||
| } | |||
| switch act.OpType { | |||
| case ActionCommitRepo, ActionPushTag, ActionDeleteTag, ActionDeleteBranch: | |||
| if !act.Repo.checkUnitUser(e, act.UserID, false, UnitTypeCode) { | |||
| continue | |||
| if repoChanged { | |||
| permCode = make([]bool, len(watchers)) | |||
| permIssue = make([]bool, len(watchers)) | |||
| permPR = make([]bool, len(watchers)) | |||
| for i, watcher := range watchers { | |||
| user, err := getUserByID(e, watcher.UserID) | |||
| if err != nil { | |||
| permCode[i] = false | |||
| permIssue[i] = false | |||
| permPR[i] = false | |||
| continue | |||
| } | |||
| perm, err := getUserRepoPermission(e, repo, user) | |||
| if err != nil { | |||
| permCode[i] = false | |||
| permIssue[i] = false | |||
| permPR[i] = false | |||
| continue | |||
| } | |||
| permCode[i] = perm.CanRead(UnitTypeCode) | |||
| permIssue[i] = perm.CanRead(UnitTypeIssues) | |||
| permPR[i] = perm.CanRead(UnitTypePullRequests) | |||
| } | |||
| case ActionCreateIssue, ActionCommentIssue, ActionCloseIssue, ActionReopenIssue: | |||
| if !act.Repo.checkUnitUser(e, act.UserID, false, UnitTypeIssues) { | |||
| } | |||
| for i, watcher := range watchers { | |||
| if act.ActUserID == watcher.UserID { | |||
| continue | |||
| } | |||
| case ActionCreatePullRequest, ActionCommentPull, ActionMergePullRequest, ActionClosePullRequest, ActionReopenPullRequest: | |||
| if !act.Repo.checkUnitUser(e, act.UserID, false, UnitTypePullRequests) { | |||
| continue | |||
| act.ID = 0 | |||
| act.UserID = watcher.UserID | |||
| act.Repo.Units = nil | |||
| switch act.OpType { | |||
| case ActionCommitRepo, ActionPushTag, ActionDeleteTag, ActionDeleteBranch: | |||
| if !permCode[i] { | |||
| continue | |||
| } | |||
| case ActionCreateIssue, ActionCommentIssue, ActionCloseIssue, ActionReopenIssue: | |||
| if !permIssue[i] { | |||
| continue | |||
| } | |||
| case ActionCreatePullRequest, ActionCommentPull, ActionMergePullRequest, ActionClosePullRequest, ActionReopenPullRequest: | |||
| if !permPR[i] { | |||
| continue | |||
| } | |||
| } | |||
| } | |||
| if _, err = e.InsertOne(act); err != nil { | |||
| return fmt.Errorf("insert new action: %v", err) | |||
| if _, err = e.InsertOne(act); err != nil { | |||
| return fmt.Errorf("insert new action: %v", err) | |||
| } | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| // NotifyWatchers creates batch of actions for every watcher. | |||
| func NotifyWatchers(act *Action) error { | |||
| return notifyWatchers(x, act) | |||
| func NotifyWatchers(actions ...*Action) error { | |||
| return notifyWatchers(x, actions...) | |||
| } | |||
| // NotifyWatchersActions creates batch of actions for every watcher. | |||
| @@ -53,6 +53,66 @@ func ListToPushCommits(l *list.List) *PushCommits { | |||
| return &PushCommits{l.Len(), commits, "", make(map[string]string), make(map[string]*User)} | |||
| } | |||
| // PushUpdateAddDeleteTags updates a number of added and delete tags | |||
| func PushUpdateAddDeleteTags(repo *Repository, gitRepo *git.Repository, addTags, delTags []string) error { | |||
| sess := x.NewSession() | |||
| defer sess.Close() | |||
| if err := sess.Begin(); err != nil { | |||
| return fmt.Errorf("Unable to begin sess in PushUpdateDeleteTags: %v", err) | |||
| } | |||
| if err := pushUpdateDeleteTags(sess, repo, delTags); err != nil { | |||
| return err | |||
| } | |||
| if err := pushUpdateAddTags(sess, repo, gitRepo, addTags); err != nil { | |||
| return err | |||
| } | |||
| return sess.Commit() | |||
| } | |||
| // PushUpdateDeleteTags updates a number of delete tags | |||
| func PushUpdateDeleteTags(repo *Repository, tags []string) error { | |||
| sess := x.NewSession() | |||
| defer sess.Close() | |||
| if err := sess.Begin(); err != nil { | |||
| return fmt.Errorf("Unable to begin sess in PushUpdateDeleteTags: %v", err) | |||
| } | |||
| if err := pushUpdateDeleteTags(sess, repo, tags); err != nil { | |||
| return err | |||
| } | |||
| return sess.Commit() | |||
| } | |||
| func pushUpdateDeleteTags(e Engine, repo *Repository, tags []string) error { | |||
| if len(tags) == 0 { | |||
| return nil | |||
| } | |||
| lowerTags := make([]string, 0, len(tags)) | |||
| for _, tag := range tags { | |||
| lowerTags = append(lowerTags, strings.ToLower(tag)) | |||
| } | |||
| if _, err := e. | |||
| Where("repo_id = ? AND is_tag = ?", repo.ID, true). | |||
| In("lower_tag_name", lowerTags). | |||
| Delete(new(Release)); err != nil { | |||
| return fmt.Errorf("Delete: %v", err) | |||
| } | |||
| if _, err := e. | |||
| Where("repo_id = ? AND is_tag = ?", repo.ID, false). | |||
| In("lower_tag_name", lowerTags). | |||
| SetExpr("is_draft", true). | |||
| SetExpr("num_commits", 0). | |||
| SetExpr("sha1", ""). | |||
| Update(new(Release)); err != nil { | |||
| return fmt.Errorf("Update: %v", err) | |||
| } | |||
| return nil | |||
| } | |||
| // PushUpdateDeleteTag must be called for any push actions to delete tag | |||
| func PushUpdateDeleteTag(repo *Repository, tagName string) error { | |||
| rel, err := GetRelease(repo.ID, tagName) | |||
| @@ -78,6 +138,125 @@ func PushUpdateDeleteTag(repo *Repository, tagName string) error { | |||
| return nil | |||
| } | |||
| // PushUpdateAddTags updates a number of add tags | |||
| func PushUpdateAddTags(repo *Repository, gitRepo *git.Repository, tags []string) error { | |||
| sess := x.NewSession() | |||
| defer sess.Close() | |||
| if err := sess.Begin(); err != nil { | |||
| return fmt.Errorf("Unable to begin sess in PushUpdateAddTags: %v", err) | |||
| } | |||
| if err := pushUpdateAddTags(sess, repo, gitRepo, tags); err != nil { | |||
| return err | |||
| } | |||
| return sess.Commit() | |||
| } | |||
| func pushUpdateAddTags(e Engine, repo *Repository, gitRepo *git.Repository, tags []string) error { | |||
| if len(tags) == 0 { | |||
| return nil | |||
| } | |||
| lowerTags := make([]string, 0, len(tags)) | |||
| for _, tag := range tags { | |||
| lowerTags = append(lowerTags, strings.ToLower(tag)) | |||
| } | |||
| releases := make([]Release, 0, len(tags)) | |||
| if err := e.Where("repo_id = ?", repo.ID). | |||
| In("lower_tag_name", lowerTags).Find(&releases); err != nil { | |||
| return fmt.Errorf("GetRelease: %v", err) | |||
| } | |||
| relMap := make(map[string]*Release) | |||
| for _, rel := range releases { | |||
| relMap[rel.LowerTagName] = &rel | |||
| } | |||
| newReleases := make([]*Release, 0, len(lowerTags)-len(relMap)) | |||
| emailToUser := make(map[string]*User) | |||
| for i, lowerTag := range lowerTags { | |||
| tag, err := gitRepo.GetTag(tags[i]) | |||
| if err != nil { | |||
| return fmt.Errorf("GetTag: %v", err) | |||
| } | |||
| commit, err := tag.Commit() | |||
| if err != nil { | |||
| return fmt.Errorf("Commit: %v", err) | |||
| } | |||
| sig := tag.Tagger | |||
| if sig == nil { | |||
| sig = commit.Author | |||
| } | |||
| if sig == nil { | |||
| sig = commit.Committer | |||
| } | |||
| var author *User | |||
| var createdAt = time.Unix(1, 0) | |||
| if sig != nil { | |||
| var ok bool | |||
| author, ok = emailToUser[sig.Email] | |||
| if !ok { | |||
| author, err = GetUserByEmail(sig.Email) | |||
| if err != nil && !IsErrUserNotExist(err) { | |||
| return fmt.Errorf("GetUserByEmail: %v", err) | |||
| } | |||
| } | |||
| createdAt = sig.When | |||
| } | |||
| commitsCount, err := commit.CommitsCount() | |||
| if err != nil { | |||
| return fmt.Errorf("CommitsCount: %v", err) | |||
| } | |||
| rel, has := relMap[lowerTag] | |||
| if !has { | |||
| rel = &Release{ | |||
| RepoID: repo.ID, | |||
| Title: "", | |||
| TagName: tags[i], | |||
| LowerTagName: lowerTag, | |||
| Target: "", | |||
| Sha1: commit.ID.String(), | |||
| NumCommits: commitsCount, | |||
| Note: "", | |||
| IsDraft: false, | |||
| IsPrerelease: false, | |||
| IsTag: true, | |||
| CreatedUnix: timeutil.TimeStamp(createdAt.Unix()), | |||
| } | |||
| if author != nil { | |||
| rel.PublisherID = author.ID | |||
| } | |||
| newReleases = append(newReleases, rel) | |||
| } else { | |||
| rel.Sha1 = commit.ID.String() | |||
| rel.CreatedUnix = timeutil.TimeStamp(createdAt.Unix()) | |||
| rel.NumCommits = commitsCount | |||
| rel.IsDraft = false | |||
| if rel.IsTag && author != nil { | |||
| rel.PublisherID = author.ID | |||
| } | |||
| if _, err = e.ID(rel.ID).AllCols().Update(rel); err != nil { | |||
| return fmt.Errorf("Update: %v", err) | |||
| } | |||
| } | |||
| } | |||
| if len(newReleases) > 0 { | |||
| if _, err := e.Insert(newReleases); err != nil { | |||
| return fmt.Errorf("Insert: %v", err) | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| // PushUpdateAddTag must be called for any push actions to add tag | |||
| func PushUpdateAddTag(repo *Repository, gitRepo *git.Repository, tagName string) error { | |||
| rel, err := GetRelease(repo.ID, tagName) | |||
| @@ -9,6 +9,7 @@ import ( | |||
| "fmt" | |||
| "net/http" | |||
| "net/url" | |||
| "time" | |||
| "code.gitea.io/gitea/modules/setting" | |||
| ) | |||
| @@ -22,9 +23,9 @@ const ( | |||
| // HookOptions represents the options for the Hook calls | |||
| type HookOptions struct { | |||
| OldCommitID string | |||
| NewCommitID string | |||
| RefFullName string | |||
| OldCommitIDs []string | |||
| NewCommitIDs []string | |||
| RefFullNames []string | |||
| UserID int64 | |||
| UserName string | |||
| GitObjectDirectory string | |||
| @@ -34,23 +35,33 @@ type HookOptions struct { | |||
| IsDeployKey bool | |||
| } | |||
| // HookPostReceiveResult represents an individual result from PostReceive | |||
| type HookPostReceiveResult struct { | |||
| Results []HookPostReceiveBranchResult | |||
| RepoWasEmpty bool | |||
| Err string | |||
| } | |||
| // HookPostReceiveBranchResult represents an individual branch result from PostReceive | |||
| type HookPostReceiveBranchResult struct { | |||
| Message bool | |||
| Create bool | |||
| Branch string | |||
| URL string | |||
| } | |||
| // HookPreReceive check whether the provided commits are allowed | |||
| func HookPreReceive(ownerName, repoName string, opts HookOptions) (int, string) { | |||
| reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/pre-receive/%s/%s?old=%s&new=%s&ref=%s&userID=%d&gitObjectDirectory=%s&gitAlternativeObjectDirectories=%s&gitQuarantinePath=%s&prID=%d&isDeployKey=%t", | |||
| reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/pre-receive/%s/%s", | |||
| url.PathEscape(ownerName), | |||
| url.PathEscape(repoName), | |||
| url.QueryEscape(opts.OldCommitID), | |||
| url.QueryEscape(opts.NewCommitID), | |||
| url.QueryEscape(opts.RefFullName), | |||
| opts.UserID, | |||
| url.QueryEscape(opts.GitObjectDirectory), | |||
| url.QueryEscape(opts.GitAlternativeObjectDirectories), | |||
| url.QueryEscape(opts.GitQuarantinePath), | |||
| opts.ProtectedBranchID, | |||
| opts.IsDeployKey, | |||
| ) | |||
| resp, err := newInternalRequest(reqURL, "GET").Response() | |||
| req := newInternalRequest(reqURL, "POST") | |||
| req = req.Header("Content-Type", "application/json") | |||
| jsonBytes, _ := json.Marshal(opts) | |||
| req.Body(jsonBytes) | |||
| req.SetTimeout(60*time.Second, time.Duration(60+len(opts.OldCommitIDs))*time.Second) | |||
| resp, err := req.Response() | |||
| if err != nil { | |||
| return http.StatusInternalServerError, fmt.Sprintf("Unable to contact gitea: %v", err.Error()) | |||
| } | |||
| @@ -64,17 +75,18 @@ func HookPreReceive(ownerName, repoName string, opts HookOptions) (int, string) | |||
| } | |||
| // HookPostReceive updates services and users | |||
| func HookPostReceive(ownerName, repoName string, opts HookOptions) (map[string]interface{}, string) { | |||
| reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/post-receive/%s/%s?old=%s&new=%s&ref=%s&userID=%d&username=%s", | |||
| func HookPostReceive(ownerName, repoName string, opts HookOptions) (*HookPostReceiveResult, string) { | |||
| reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/post-receive/%s/%s", | |||
| url.PathEscape(ownerName), | |||
| url.PathEscape(repoName), | |||
| url.QueryEscape(opts.OldCommitID), | |||
| url.QueryEscape(opts.NewCommitID), | |||
| url.QueryEscape(opts.RefFullName), | |||
| opts.UserID, | |||
| url.QueryEscape(opts.UserName)) | |||
| ) | |||
| resp, err := newInternalRequest(reqURL, "GET").Response() | |||
| req := newInternalRequest(reqURL, "POST") | |||
| req = req.Header("Content-Type", "application/json") | |||
| req.SetTimeout(60*time.Second, time.Duration(60+len(opts.OldCommitIDs))*time.Second) | |||
| jsonBytes, _ := json.Marshal(opts) | |||
| req.Body(jsonBytes) | |||
| resp, err := req.Response() | |||
| if err != nil { | |||
| return nil, fmt.Sprintf("Unable to contact gitea: %v", err.Error()) | |||
| } | |||
| @@ -83,8 +95,30 @@ func HookPostReceive(ownerName, repoName string, opts HookOptions) (map[string]i | |||
| if resp.StatusCode != http.StatusOK { | |||
| return nil, decodeJSONError(resp).Err | |||
| } | |||
| res := map[string]interface{}{} | |||
| _ = json.NewDecoder(resp.Body).Decode(&res) | |||
| res := &HookPostReceiveResult{} | |||
| _ = json.NewDecoder(resp.Body).Decode(res) | |||
| return res, "" | |||
| } | |||
| // SetDefaultBranch will set the default branch to the provided branch for the provided repository | |||
| func SetDefaultBranch(ownerName, repoName, branch string) error { | |||
| reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/set-default-branch/%s/%s/%s", | |||
| url.PathEscape(ownerName), | |||
| url.PathEscape(repoName), | |||
| url.PathEscape(branch), | |||
| ) | |||
| req := newInternalRequest(reqURL, "POST") | |||
| req = req.Header("Content-Type", "application/json") | |||
| req.SetTimeout(60*time.Second, 60*time.Second) | |||
| resp, err := req.Response() | |||
| if err != nil { | |||
| return fmt.Errorf("Unable to contact gitea: %v", err) | |||
| } | |||
| defer resp.Body.Close() | |||
| if resp.StatusCode != http.StatusOK { | |||
| return fmt.Errorf("Error returned from gitea: %v", decodeJSONError(resp).Err) | |||
| } | |||
| return nil | |||
| } | |||
| @@ -159,112 +159,132 @@ type CommitRepoActionOptions struct { | |||
| // CommitRepoAction adds new commit action to the repository, and prepare | |||
| // corresponding webhooks. | |||
| func CommitRepoAction(opts CommitRepoActionOptions) error { | |||
| pusher, err := models.GetUserByName(opts.PusherName) | |||
| if err != nil { | |||
| return fmt.Errorf("GetUserByName [%s]: %v", opts.PusherName, err) | |||
| } | |||
| repo, err := models.GetRepositoryByName(opts.RepoOwnerID, opts.RepoName) | |||
| if err != nil { | |||
| return fmt.Errorf("GetRepositoryByName [owner_id: %d, name: %s]: %v", opts.RepoOwnerID, opts.RepoName, err) | |||
| } | |||
| refName := git.RefEndName(opts.RefFullName) | |||
| func CommitRepoAction(optsList ...*CommitRepoActionOptions) error { | |||
| var pusher *models.User | |||
| var repo *models.Repository | |||
| actions := make([]*models.Action, len(optsList)) | |||
| for i, opts := range optsList { | |||
| if pusher == nil || pusher.Name != opts.PusherName { | |||
| var err error | |||
| pusher, err = models.GetUserByName(opts.PusherName) | |||
| if err != nil { | |||
| return fmt.Errorf("GetUserByName [%s]: %v", opts.PusherName, err) | |||
| } | |||
| } | |||
| // Change default branch and empty status only if pushed ref is non-empty branch. | |||
| if repo.IsEmpty && opts.NewCommitID != git.EmptySHA && strings.HasPrefix(opts.RefFullName, git.BranchPrefix) { | |||
| repo.DefaultBranch = refName | |||
| repo.IsEmpty = false | |||
| if refName != "master" { | |||
| gitRepo, err := git.OpenRepository(repo.RepoPath()) | |||
| if repo == nil || repo.OwnerID != opts.RepoOwnerID || repo.Name != opts.RepoName { | |||
| var err error | |||
| if repo != nil { | |||
| // Change repository empty status and update last updated time. | |||
| if err := models.UpdateRepository(repo, false); err != nil { | |||
| return fmt.Errorf("UpdateRepository: %v", err) | |||
| } | |||
| } | |||
| repo, err = models.GetRepositoryByName(opts.RepoOwnerID, opts.RepoName) | |||
| if err != nil { | |||
| return err | |||
| return fmt.Errorf("GetRepositoryByName [owner_id: %d, name: %s]: %v", opts.RepoOwnerID, opts.RepoName, err) | |||
| } | |||
| if err := gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { | |||
| if !git.IsErrUnsupportedVersion(err) { | |||
| gitRepo.Close() | |||
| } | |||
| refName := git.RefEndName(opts.RefFullName) | |||
| // Change default branch and empty status only if pushed ref is non-empty branch. | |||
| if repo.IsEmpty && opts.NewCommitID != git.EmptySHA && strings.HasPrefix(opts.RefFullName, git.BranchPrefix) { | |||
| repo.DefaultBranch = refName | |||
| repo.IsEmpty = false | |||
| if refName != "master" { | |||
| gitRepo, err := git.OpenRepository(repo.RepoPath()) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if err := gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { | |||
| if !git.IsErrUnsupportedVersion(err) { | |||
| gitRepo.Close() | |||
| return err | |||
| } | |||
| } | |||
| gitRepo.Close() | |||
| } | |||
| gitRepo.Close() | |||
| } | |||
| } | |||
| // Change repository empty status and update last updated time. | |||
| if err = models.UpdateRepository(repo, false); err != nil { | |||
| return fmt.Errorf("UpdateRepository: %v", err) | |||
| } | |||
| isNewBranch := false | |||
| opType := models.ActionCommitRepo | |||
| isNewBranch := false | |||
| opType := models.ActionCommitRepo | |||
| // Check it's tag push or branch. | |||
| if strings.HasPrefix(opts.RefFullName, git.TagPrefix) { | |||
| opType = models.ActionPushTag | |||
| if opts.NewCommitID == git.EmptySHA { | |||
| opType = models.ActionDeleteTag | |||
| } | |||
| opts.Commits = &models.PushCommits{} | |||
| } else if opts.NewCommitID == git.EmptySHA { | |||
| opType = models.ActionDeleteBranch | |||
| opts.Commits = &models.PushCommits{} | |||
| } else { | |||
| // if not the first commit, set the compare URL. | |||
| if opts.OldCommitID == git.EmptySHA { | |||
| isNewBranch = true | |||
| // Check it's tag push or branch. | |||
| if strings.HasPrefix(opts.RefFullName, git.TagPrefix) { | |||
| opType = models.ActionPushTag | |||
| if opts.NewCommitID == git.EmptySHA { | |||
| opType = models.ActionDeleteTag | |||
| } | |||
| opts.Commits = &models.PushCommits{} | |||
| } else if opts.NewCommitID == git.EmptySHA { | |||
| opType = models.ActionDeleteBranch | |||
| opts.Commits = &models.PushCommits{} | |||
| } else { | |||
| opts.Commits.CompareURL = repo.ComposeCompareURL(opts.OldCommitID, opts.NewCommitID) | |||
| } | |||
| // if not the first commit, set the compare URL. | |||
| if opts.OldCommitID == git.EmptySHA { | |||
| isNewBranch = true | |||
| } else { | |||
| opts.Commits.CompareURL = repo.ComposeCompareURL(opts.OldCommitID, opts.NewCommitID) | |||
| } | |||
| if err = UpdateIssuesCommit(pusher, repo, opts.Commits.Commits, refName); err != nil { | |||
| log.Error("updateIssuesCommit: %v", err) | |||
| if err := UpdateIssuesCommit(pusher, repo, opts.Commits.Commits, refName); err != nil { | |||
| log.Error("updateIssuesCommit: %v", err) | |||
| } | |||
| } | |||
| } | |||
| if len(opts.Commits.Commits) > setting.UI.FeedMaxCommitNum { | |||
| opts.Commits.Commits = opts.Commits.Commits[:setting.UI.FeedMaxCommitNum] | |||
| } | |||
| data, err := json.Marshal(opts.Commits) | |||
| if err != nil { | |||
| return fmt.Errorf("Marshal: %v", err) | |||
| } | |||
| if len(opts.Commits.Commits) > setting.UI.FeedMaxCommitNum { | |||
| opts.Commits.Commits = opts.Commits.Commits[:setting.UI.FeedMaxCommitNum] | |||
| } | |||
| if err = models.NotifyWatchers(&models.Action{ | |||
| ActUserID: pusher.ID, | |||
| ActUser: pusher, | |||
| OpType: opType, | |||
| Content: string(data), | |||
| RepoID: repo.ID, | |||
| Repo: repo, | |||
| RefName: refName, | |||
| IsPrivate: repo.IsPrivate, | |||
| }); err != nil { | |||
| return fmt.Errorf("NotifyWatchers: %v", err) | |||
| } | |||
| data, err := json.Marshal(opts.Commits) | |||
| if err != nil { | |||
| return fmt.Errorf("Marshal: %v", err) | |||
| } | |||
| var isHookEventPush = true | |||
| switch opType { | |||
| case models.ActionCommitRepo: // Push | |||
| if isNewBranch { | |||
| notification.NotifyCreateRef(pusher, repo, "branch", opts.RefFullName) | |||
| actions[i] = &models.Action{ | |||
| ActUserID: pusher.ID, | |||
| ActUser: pusher, | |||
| OpType: opType, | |||
| Content: string(data), | |||
| RepoID: repo.ID, | |||
| Repo: repo, | |||
| RefName: refName, | |||
| IsPrivate: repo.IsPrivate, | |||
| } | |||
| case models.ActionDeleteBranch: // Delete Branch | |||
| notification.NotifyDeleteRef(pusher, repo, "branch", opts.RefFullName) | |||
| var isHookEventPush = true | |||
| switch opType { | |||
| case models.ActionCommitRepo: // Push | |||
| if isNewBranch { | |||
| notification.NotifyCreateRef(pusher, repo, "branch", opts.RefFullName) | |||
| } | |||
| case models.ActionDeleteBranch: // Delete Branch | |||
| notification.NotifyDeleteRef(pusher, repo, "branch", opts.RefFullName) | |||
| case models.ActionPushTag: // Create | |||
| notification.NotifyCreateRef(pusher, repo, "tag", opts.RefFullName) | |||
| case models.ActionPushTag: // Create | |||
| notification.NotifyCreateRef(pusher, repo, "tag", opts.RefFullName) | |||
| case models.ActionDeleteTag: // Delete Tag | |||
| notification.NotifyDeleteRef(pusher, repo, "tag", opts.RefFullName) | |||
| default: | |||
| isHookEventPush = false | |||
| } | |||
| case models.ActionDeleteTag: // Delete Tag | |||
| notification.NotifyDeleteRef(pusher, repo, "tag", opts.RefFullName) | |||
| default: | |||
| isHookEventPush = false | |||
| if isHookEventPush { | |||
| notification.NotifyPushCommits(pusher, repo, opts.RefFullName, opts.OldCommitID, opts.NewCommitID, opts.Commits) | |||
| } | |||
| } | |||
| if isHookEventPush { | |||
| notification.NotifyPushCommits(pusher, repo, opts.RefFullName, opts.OldCommitID, opts.NewCommitID, opts.Commits) | |||
| if repo != nil { | |||
| // Change repository empty status and update last updated time. | |||
| if err := models.UpdateRepository(repo, false); err != nil { | |||
| return fmt.Errorf("UpdateRepository: %v", err) | |||
| } | |||
| } | |||
| if err := models.NotifyWatchers(actions...); err != nil { | |||
| return fmt.Errorf("NotifyWatchers: %v", err) | |||
| } | |||
| return nil | |||
| } | |||
| @@ -13,7 +13,7 @@ import ( | |||
| "github.com/stretchr/testify/assert" | |||
| ) | |||
| func testCorrectRepoAction(t *testing.T, opts CommitRepoActionOptions, actionBean *models.Action) { | |||
| func testCorrectRepoAction(t *testing.T, opts *CommitRepoActionOptions, actionBean *models.Action) { | |||
| models.AssertNotExistsBean(t, actionBean) | |||
| assert.NoError(t, CommitRepoAction(opts)) | |||
| models.AssertExistsAndLoadBean(t, actionBean) | |||
| @@ -121,7 +121,7 @@ func TestCommitRepoAction(t *testing.T) { | |||
| s.action.Repo = repo | |||
| s.action.IsPrivate = repo.IsPrivate | |||
| testCorrectRepoAction(t, s.commitRepoActionOptions, &s.action) | |||
| testCorrectRepoAction(t, &s.commitRepoActionOptions, &s.action) | |||
| } | |||
| } | |||
| @@ -432,6 +432,7 @@ type PushUpdateOptions struct { | |||
| RefFullName string | |||
| OldCommitID string | |||
| NewCommitID string | |||
| Branch string | |||
| } | |||
| // PushUpdate must be called for any push actions in order to | |||
| @@ -460,21 +461,161 @@ func PushUpdate(repo *models.Repository, branch string, opts PushUpdateOptions) | |||
| log.Error("Failed to update size for repository: %v", err) | |||
| } | |||
| commitRepoActionOptions, err := createCommitRepoActionOption(repo, gitRepo, &opts) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if err := CommitRepoAction(commitRepoActionOptions); err != nil { | |||
| return fmt.Errorf("CommitRepoAction: %v", err) | |||
| } | |||
| pusher, err := models.GetUserByID(opts.PusherID) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name) | |||
| go pull_service.AddTestPullRequestTask(pusher, repo.ID, branch, true) | |||
| if err = models.WatchIfAuto(opts.PusherID, repo.ID, true); err != nil { | |||
| log.Warn("Fail to perform auto watch on user %v for repo %v: %v", opts.PusherID, repo.ID, err) | |||
| } | |||
| return nil | |||
| } | |||
| // PushUpdates generates push action history feeds for push updating multiple refs | |||
| func PushUpdates(repo *models.Repository, optsList []*PushUpdateOptions) error { | |||
| repoPath := repo.RepoPath() | |||
| _, err := git.NewCommand("update-server-info").RunInDir(repoPath) | |||
| if err != nil { | |||
| return fmt.Errorf("Failed to call 'git update-server-info': %v", err) | |||
| } | |||
| gitRepo, err := git.OpenRepository(repoPath) | |||
| if err != nil { | |||
| return fmt.Errorf("OpenRepository: %v", err) | |||
| } | |||
| if err = repo.UpdateSize(); err != nil { | |||
| log.Error("Failed to update size for repository: %v", err) | |||
| } | |||
| actions, err := createCommitRepoActions(repo, gitRepo, optsList) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if err := CommitRepoAction(actions...); err != nil { | |||
| return fmt.Errorf("CommitRepoAction: %v", err) | |||
| } | |||
| var pusher *models.User | |||
| for _, opts := range optsList { | |||
| if pusher == nil || pusher.ID != opts.PusherID { | |||
| var err error | |||
| pusher, err = models.GetUserByID(opts.PusherID) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| } | |||
| log.Trace("TriggerTask '%s/%s' by %s", repo.Name, opts.Branch, pusher.Name) | |||
| go pull_service.AddTestPullRequestTask(pusher, repo.ID, opts.Branch, true) | |||
| if err = models.WatchIfAuto(opts.PusherID, repo.ID, true); err != nil { | |||
| log.Warn("Fail to perform auto watch on user %v for repo %v: %v", opts.PusherID, repo.ID, err) | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| func createCommitRepoActions(repo *models.Repository, gitRepo *git.Repository, optsList []*PushUpdateOptions) ([]*CommitRepoActionOptions, error) { | |||
| addTags := make([]string, 0, len(optsList)) | |||
| delTags := make([]string, 0, len(optsList)) | |||
| actions := make([]*CommitRepoActionOptions, 0, len(optsList)) | |||
| for _, opts := range optsList { | |||
| isNewRef := opts.OldCommitID == git.EmptySHA | |||
| isDelRef := opts.NewCommitID == git.EmptySHA | |||
| if isNewRef && isDelRef { | |||
| return nil, fmt.Errorf("Old and new revisions are both %s", git.EmptySHA) | |||
| } | |||
| var commits = &models.PushCommits{} | |||
| if strings.HasPrefix(opts.RefFullName, git.TagPrefix) { | |||
| // If is tag reference | |||
| tagName := opts.RefFullName[len(git.TagPrefix):] | |||
| if isDelRef { | |||
| delTags = append(delTags, tagName) | |||
| } else { | |||
| cache.Remove(repo.GetCommitsCountCacheKey(tagName, true)) | |||
| addTags = append(addTags, tagName) | |||
| } | |||
| } else if !isDelRef { | |||
| // If is branch reference | |||
| // Clear cache for branch commit count | |||
| cache.Remove(repo.GetCommitsCountCacheKey(opts.RefFullName[len(git.BranchPrefix):], true)) | |||
| newCommit, err := gitRepo.GetCommit(opts.NewCommitID) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("gitRepo.GetCommit: %v", err) | |||
| } | |||
| // Push new branch. | |||
| var l *list.List | |||
| if isNewRef { | |||
| l, err = newCommit.CommitsBeforeLimit(10) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("newCommit.CommitsBeforeLimit: %v", err) | |||
| } | |||
| } else { | |||
| l, err = newCommit.CommitsBeforeUntil(opts.OldCommitID) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("newCommit.CommitsBeforeUntil: %v", err) | |||
| } | |||
| } | |||
| commits = models.ListToPushCommits(l) | |||
| } | |||
| actions = append(actions, &CommitRepoActionOptions{ | |||
| PusherName: opts.PusherName, | |||
| RepoOwnerID: repo.OwnerID, | |||
| RepoName: repo.Name, | |||
| RefFullName: opts.RefFullName, | |||
| OldCommitID: opts.OldCommitID, | |||
| NewCommitID: opts.NewCommitID, | |||
| Commits: commits, | |||
| }) | |||
| } | |||
| if err := models.PushUpdateAddDeleteTags(repo, gitRepo, addTags, delTags); err != nil { | |||
| return nil, fmt.Errorf("PushUpdateAddDeleteTags: %v", err) | |||
| } | |||
| return actions, nil | |||
| } | |||
| func createCommitRepoActionOption(repo *models.Repository, gitRepo *git.Repository, opts *PushUpdateOptions) (*CommitRepoActionOptions, error) { | |||
| isNewRef := opts.OldCommitID == git.EmptySHA | |||
| isDelRef := opts.NewCommitID == git.EmptySHA | |||
| if isNewRef && isDelRef { | |||
| return nil, fmt.Errorf("Old and new revisions are both %s", git.EmptySHA) | |||
| } | |||
| var commits = &models.PushCommits{} | |||
| if strings.HasPrefix(opts.RefFullName, git.TagPrefix) { | |||
| // If is tag reference | |||
| tagName := opts.RefFullName[len(git.TagPrefix):] | |||
| if isDelRef { | |||
| err = models.PushUpdateDeleteTag(repo, tagName) | |||
| if err != nil { | |||
| return fmt.Errorf("PushUpdateDeleteTag: %v", err) | |||
| if err := models.PushUpdateDeleteTag(repo, tagName); err != nil { | |||
| return nil, fmt.Errorf("PushUpdateDeleteTag: %v", err) | |||
| } | |||
| } else { | |||
| // Clear cache for tag commit count | |||
| cache.Remove(repo.GetCommitsCountCacheKey(tagName, true)) | |||
| err = models.PushUpdateAddTag(repo, gitRepo, tagName) | |||
| if err != nil { | |||
| return fmt.Errorf("PushUpdateAddTag: %v", err) | |||
| if err := models.PushUpdateAddTag(repo, gitRepo, tagName); err != nil { | |||
| return nil, fmt.Errorf("PushUpdateAddTag: %v", err) | |||
| } | |||
| } | |||
| } else if !isDelRef { | |||
| @@ -485,7 +626,7 @@ func PushUpdate(repo *models.Repository, branch string, opts PushUpdateOptions) | |||
| newCommit, err := gitRepo.GetCommit(opts.NewCommitID) | |||
| if err != nil { | |||
| return fmt.Errorf("gitRepo.GetCommit: %v", err) | |||
| return nil, fmt.Errorf("gitRepo.GetCommit: %v", err) | |||
| } | |||
| // Push new branch. | |||
| @@ -493,19 +634,19 @@ func PushUpdate(repo *models.Repository, branch string, opts PushUpdateOptions) | |||
| if isNewRef { | |||
| l, err = newCommit.CommitsBeforeLimit(10) | |||
| if err != nil { | |||
| return fmt.Errorf("newCommit.CommitsBeforeLimit: %v", err) | |||
| return nil, fmt.Errorf("newCommit.CommitsBeforeLimit: %v", err) | |||
| } | |||
| } else { | |||
| l, err = newCommit.CommitsBeforeUntil(opts.OldCommitID) | |||
| if err != nil { | |||
| return fmt.Errorf("newCommit.CommitsBeforeUntil: %v", err) | |||
| return nil, fmt.Errorf("newCommit.CommitsBeforeUntil: %v", err) | |||
| } | |||
| } | |||
| commits = models.ListToPushCommits(l) | |||
| } | |||
| if err := CommitRepoAction(CommitRepoActionOptions{ | |||
| return &CommitRepoActionOptions{ | |||
| PusherName: opts.PusherName, | |||
| RepoOwnerID: repo.OwnerID, | |||
| RepoName: repo.Name, | |||
| @@ -513,22 +654,5 @@ func PushUpdate(repo *models.Repository, branch string, opts PushUpdateOptions) | |||
| OldCommitID: opts.OldCommitID, | |||
| NewCommitID: opts.NewCommitID, | |||
| Commits: commits, | |||
| }); err != nil { | |||
| return fmt.Errorf("CommitRepoAction: %v", err) | |||
| } | |||
| pusher, err := models.GetUserByID(opts.PusherID) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name) | |||
| go pull_service.AddTestPullRequestTask(pusher, repo.ID, branch, true) | |||
| if err = models.WatchIfAuto(opts.PusherID, repo.ID, true); err != nil { | |||
| log.Warn("Fail to perform auto watch on user %v for repo %v: %v", opts.PusherID, repo.ID, err) | |||
| } | |||
| return nil | |||
| }, nil | |||
| } | |||
| @@ -197,11 +197,11 @@ func SyncReleasesWithTags(repo *models.Repository, gitRepo *git.Repository) erro | |||
| } | |||
| commitID, err := gitRepo.GetTagCommitID(rel.TagName) | |||
| if err != nil && !git.IsErrNotExist(err) { | |||
| return fmt.Errorf("GetTagCommitID: %v", err) | |||
| return fmt.Errorf("GetTagCommitID: %s: %v", rel.TagName, err) | |||
| } | |||
| if git.IsErrNotExist(err) || commitID != rel.Sha1 { | |||
| if err := models.PushUpdateDeleteTag(repo, rel.TagName); err != nil { | |||
| return fmt.Errorf("PushUpdateDeleteTag: %v", err) | |||
| return fmt.Errorf("PushUpdateDeleteTag: %s: %v", rel.TagName, err) | |||
| } | |||
| } else { | |||
| existingRelTags[strings.ToLower(rel.TagName)] = struct{}{} | |||
| @@ -215,7 +215,7 @@ func SyncReleasesWithTags(repo *models.Repository, gitRepo *git.Repository) erro | |||
| for _, tagName := range tags { | |||
| if _, ok := existingRelTags[strings.ToLower(tagName)]; !ok { | |||
| if err := models.PushUpdateAddTag(repo, gitRepo, tagName); err != nil { | |||
| return fmt.Errorf("pushUpdateAddTag: %v", err) | |||
| return fmt.Errorf("pushUpdateAddTag: %s: %v", tagName, err) | |||
| } | |||
| } | |||
| } | |||
| @@ -22,20 +22,9 @@ import ( | |||
| ) | |||
| // HookPreReceive checks whether a individual commit is acceptable | |||
| func HookPreReceive(ctx *macaron.Context) { | |||
| func HookPreReceive(ctx *macaron.Context, opts private.HookOptions) { | |||
| ownerName := ctx.Params(":owner") | |||
| repoName := ctx.Params(":repo") | |||
| oldCommitID := ctx.QueryTrim("old") | |||
| newCommitID := ctx.QueryTrim("new") | |||
| refFullName := ctx.QueryTrim("ref") | |||
| userID := ctx.QueryInt64("userID") | |||
| gitObjectDirectory := ctx.QueryTrim("gitObjectDirectory") | |||
| gitAlternativeObjectDirectories := ctx.QueryTrim("gitAlternativeObjectDirectories") | |||
| gitQuarantinePath := ctx.QueryTrim("gitQuarantinePath") | |||
| prID := ctx.QueryInt64("prID") | |||
| isDeployKey := ctx.QueryBool("isDeployKey") | |||
| branchName := strings.TrimPrefix(refFullName, git.BranchPrefix) | |||
| repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName) | |||
| if err != nil { | |||
| log.Error("Unable to get repository: %s/%s Error: %v", ownerName, repoName, err) | |||
| @@ -45,206 +34,304 @@ func HookPreReceive(ctx *macaron.Context) { | |||
| return | |||
| } | |||
| repo.OwnerName = ownerName | |||
| protectBranch, err := models.GetProtectedBranchBy(repo.ID, branchName) | |||
| if err != nil { | |||
| log.Error("Unable to get protected branch: %s in %-v Error: %v", branchName, repo, err) | |||
| ctx.JSON(500, map[string]interface{}{ | |||
| "err": err.Error(), | |||
| }) | |||
| return | |||
| } | |||
| if protectBranch != nil && protectBranch.IsProtected() { | |||
| // check and deletion | |||
| if newCommitID == git.EmptySHA { | |||
| log.Warn("Forbidden: Branch: %s in %-v is protected from deletion", branchName, repo) | |||
| ctx.JSON(http.StatusForbidden, map[string]interface{}{ | |||
| "err": fmt.Sprintf("branch %s is protected from deletion", branchName), | |||
| for i := range opts.OldCommitIDs { | |||
| oldCommitID := opts.OldCommitIDs[i] | |||
| newCommitID := opts.NewCommitIDs[i] | |||
| refFullName := opts.RefFullNames[i] | |||
| branchName := strings.TrimPrefix(refFullName, git.BranchPrefix) | |||
| protectBranch, err := models.GetProtectedBranchBy(repo.ID, branchName) | |||
| if err != nil { | |||
| log.Error("Unable to get protected branch: %s in %-v Error: %v", branchName, repo, err) | |||
| ctx.JSON(500, map[string]interface{}{ | |||
| "err": err.Error(), | |||
| }) | |||
| return | |||
| } | |||
| // detect force push | |||
| if git.EmptySHA != oldCommitID { | |||
| env := os.Environ() | |||
| if gitAlternativeObjectDirectories != "" { | |||
| env = append(env, | |||
| private.GitAlternativeObjectDirectories+"="+gitAlternativeObjectDirectories) | |||
| } | |||
| if gitObjectDirectory != "" { | |||
| env = append(env, | |||
| private.GitObjectDirectory+"="+gitObjectDirectory) | |||
| } | |||
| if gitQuarantinePath != "" { | |||
| env = append(env, | |||
| private.GitQuarantinePath+"="+gitQuarantinePath) | |||
| } | |||
| output, err := git.NewCommand("rev-list", "--max-count=1", oldCommitID, "^"+newCommitID).RunInDirWithEnv(repo.RepoPath(), env) | |||
| if err != nil { | |||
| log.Error("Unable to detect force push between: %s and %s in %-v Error: %v", oldCommitID, newCommitID, repo, err) | |||
| ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ | |||
| "err": fmt.Sprintf("Fail to detect force push: %v", err), | |||
| }) | |||
| return | |||
| } else if len(output) > 0 { | |||
| log.Warn("Forbidden: Branch: %s in %-v is protected from force push", branchName, repo) | |||
| if protectBranch != nil && protectBranch.IsProtected() { | |||
| // check and deletion | |||
| if newCommitID == git.EmptySHA { | |||
| log.Warn("Forbidden: Branch: %s in %-v is protected from deletion", branchName, repo) | |||
| ctx.JSON(http.StatusForbidden, map[string]interface{}{ | |||
| "err": fmt.Sprintf("branch %s is protected from force push", branchName), | |||
| "err": fmt.Sprintf("branch %s is protected from deletion", branchName), | |||
| }) | |||
| return | |||
| } | |||
| } | |||
| canPush := false | |||
| if isDeployKey { | |||
| canPush = protectBranch.CanPush && (!protectBranch.EnableWhitelist || protectBranch.WhitelistDeployKeys) | |||
| } else { | |||
| canPush = protectBranch.CanUserPush(userID) | |||
| } | |||
| if !canPush && prID > 0 { | |||
| pr, err := models.GetPullRequestByID(prID) | |||
| if err != nil { | |||
| log.Error("Unable to get PullRequest %d Error: %v", prID, err) | |||
| ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ | |||
| "err": fmt.Sprintf("Unable to get PullRequest %d Error: %v", prID, err), | |||
| }) | |||
| return | |||
| // detect force push | |||
| if git.EmptySHA != oldCommitID { | |||
| env := os.Environ() | |||
| if opts.GitAlternativeObjectDirectories != "" { | |||
| env = append(env, | |||
| private.GitAlternativeObjectDirectories+"="+opts.GitAlternativeObjectDirectories) | |||
| } | |||
| if opts.GitObjectDirectory != "" { | |||
| env = append(env, | |||
| private.GitObjectDirectory+"="+opts.GitObjectDirectory) | |||
| } | |||
| if opts.GitQuarantinePath != "" { | |||
| env = append(env, | |||
| private.GitQuarantinePath+"="+opts.GitQuarantinePath) | |||
| } | |||
| output, err := git.NewCommand("rev-list", "--max-count=1", oldCommitID, "^"+newCommitID).RunInDirWithEnv(repo.RepoPath(), env) | |||
| if err != nil { | |||
| log.Error("Unable to detect force push between: %s and %s in %-v Error: %v", oldCommitID, newCommitID, repo, err) | |||
| ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ | |||
| "err": fmt.Sprintf("Fail to detect force push: %v", err), | |||
| }) | |||
| return | |||
| } else if len(output) > 0 { | |||
| log.Warn("Forbidden: Branch: %s in %-v is protected from force push", branchName, repo) | |||
| ctx.JSON(http.StatusForbidden, map[string]interface{}{ | |||
| "err": fmt.Sprintf("branch %s is protected from force push", branchName), | |||
| }) | |||
| return | |||
| } | |||
| } | |||
| canPush := false | |||
| if opts.IsDeployKey { | |||
| canPush = protectBranch.CanPush && (!protectBranch.EnableWhitelist || protectBranch.WhitelistDeployKeys) | |||
| } else { | |||
| canPush = protectBranch.CanUserPush(opts.UserID) | |||
| } | |||
| if !protectBranch.HasEnoughApprovals(pr) { | |||
| log.Warn("Forbidden: User %d cannot push to protected branch: %s in %-v and pr #%d does not have enough approvals", userID, branchName, repo, pr.Index) | |||
| if !canPush && opts.ProtectedBranchID > 0 { | |||
| pr, err := models.GetPullRequestByID(opts.ProtectedBranchID) | |||
| if err != nil { | |||
| log.Error("Unable to get PullRequest %d Error: %v", opts.ProtectedBranchID, err) | |||
| ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ | |||
| "err": fmt.Sprintf("Unable to get PullRequest %d Error: %v", opts.ProtectedBranchID, err), | |||
| }) | |||
| return | |||
| } | |||
| if !protectBranch.HasEnoughApprovals(pr) { | |||
| log.Warn("Forbidden: User %d cannot push to protected branch: %s in %-v and pr #%d does not have enough approvals", opts.UserID, branchName, repo, pr.Index) | |||
| ctx.JSON(http.StatusForbidden, map[string]interface{}{ | |||
| "err": fmt.Sprintf("protected branch %s can not be pushed to and pr #%d does not have enough approvals", branchName, opts.ProtectedBranchID), | |||
| }) | |||
| return | |||
| } | |||
| } else if !canPush { | |||
| log.Warn("Forbidden: User %d cannot push to protected branch: %s in %-v", opts.UserID, branchName, repo) | |||
| ctx.JSON(http.StatusForbidden, map[string]interface{}{ | |||
| "err": fmt.Sprintf("protected branch %s can not be pushed to and pr #%d does not have enough approvals", branchName, prID), | |||
| "err": fmt.Sprintf("protected branch %s can not be pushed to", branchName), | |||
| }) | |||
| return | |||
| } | |||
| } else if !canPush { | |||
| log.Warn("Forbidden: User %d cannot push to protected branch: %s in %-v", userID, branchName, repo) | |||
| ctx.JSON(http.StatusForbidden, map[string]interface{}{ | |||
| "err": fmt.Sprintf("protected branch %s can not be pushed to", branchName), | |||
| }) | |||
| return | |||
| } | |||
| } | |||
| ctx.PlainText(http.StatusOK, []byte("ok")) | |||
| } | |||
| // HookPostReceive updates services and users | |||
| func HookPostReceive(ctx *macaron.Context) { | |||
| func HookPostReceive(ctx *macaron.Context, opts private.HookOptions) { | |||
| ownerName := ctx.Params(":owner") | |||
| repoName := ctx.Params(":repo") | |||
| oldCommitID := ctx.Query("old") | |||
| newCommitID := ctx.Query("new") | |||
| refFullName := ctx.Query("ref") | |||
| userID := ctx.QueryInt64("userID") | |||
| userName := ctx.Query("username") | |||
| branch := refFullName | |||
| if strings.HasPrefix(refFullName, git.BranchPrefix) { | |||
| branch = strings.TrimPrefix(refFullName, git.BranchPrefix) | |||
| } else if strings.HasPrefix(refFullName, git.TagPrefix) { | |||
| branch = strings.TrimPrefix(refFullName, git.TagPrefix) | |||
| } | |||
| // Only trigger activity updates for changes to branches or | |||
| // tags. Updates to other refs (eg, refs/notes, refs/changes, | |||
| // or other less-standard refs spaces are ignored since there | |||
| // may be a very large number of them). | |||
| if strings.HasPrefix(refFullName, git.BranchPrefix) || strings.HasPrefix(refFullName, git.TagPrefix) { | |||
| repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName) | |||
| if err != nil { | |||
| log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err) | |||
| ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ | |||
| "err": fmt.Sprintf("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err), | |||
| }) | |||
| return | |||
| var repo *models.Repository | |||
| updates := make([]*repofiles.PushUpdateOptions, 0, len(opts.OldCommitIDs)) | |||
| wasEmpty := false | |||
| for i := range opts.OldCommitIDs { | |||
| refFullName := opts.RefFullNames[i] | |||
| branch := opts.RefFullNames[i] | |||
| if strings.HasPrefix(branch, git.BranchPrefix) { | |||
| branch = strings.TrimPrefix(branch, git.BranchPrefix) | |||
| } else { | |||
| branch = strings.TrimPrefix(branch, git.TagPrefix) | |||
| } | |||
| if err := repofiles.PushUpdate(repo, branch, repofiles.PushUpdateOptions{ | |||
| RefFullName: refFullName, | |||
| OldCommitID: oldCommitID, | |||
| NewCommitID: newCommitID, | |||
| PusherID: userID, | |||
| PusherName: userName, | |||
| RepoUserName: ownerName, | |||
| RepoName: repoName, | |||
| }); err != nil { | |||
| log.Error("Failed to Update: %s/%s Branch: %s Error: %v", ownerName, repoName, branch, err) | |||
| ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ | |||
| "err": fmt.Sprintf("Failed to Update: %s/%s Branch: %s Error: %v", ownerName, repoName, branch, err), | |||
| }) | |||
| return | |||
| // Only trigger activity updates for changes to branches or | |||
| // tags. Updates to other refs (eg, refs/notes, refs/changes, | |||
| // or other less-standard refs spaces are ignored since there | |||
| // may be a very large number of them). | |||
| if strings.HasPrefix(refFullName, git.BranchPrefix) || strings.HasPrefix(refFullName, git.TagPrefix) { | |||
| if repo == nil { | |||
| var err error | |||
| repo, err = models.GetRepositoryByOwnerAndName(ownerName, repoName) | |||
| if err != nil { | |||
| log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err) | |||
| ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ | |||
| Err: fmt.Sprintf("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err), | |||
| }) | |||
| return | |||
| } | |||
| if repo.OwnerName == "" { | |||
| repo.OwnerName = ownerName | |||
| } | |||
| wasEmpty = repo.IsEmpty | |||
| } | |||
| option := repofiles.PushUpdateOptions{ | |||
| RefFullName: refFullName, | |||
| OldCommitID: opts.OldCommitIDs[i], | |||
| NewCommitID: opts.NewCommitIDs[i], | |||
| Branch: branch, | |||
| PusherID: opts.UserID, | |||
| PusherName: opts.UserName, | |||
| RepoUserName: ownerName, | |||
| RepoName: repoName, | |||
| } | |||
| updates = append(updates, &option) | |||
| if repo.IsEmpty && branch == "master" && strings.HasPrefix(refFullName, git.BranchPrefix) { | |||
| // put the master branch first | |||
| copy(updates[1:], updates) | |||
| updates[0] = &option | |||
| } | |||
| } | |||
| } | |||
| if newCommitID != git.EmptySHA && strings.HasPrefix(refFullName, git.BranchPrefix) { | |||
| repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName) | |||
| if err != nil { | |||
| log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err) | |||
| ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ | |||
| "err": fmt.Sprintf("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err), | |||
| }) | |||
| return | |||
| } | |||
| repo.OwnerName = ownerName | |||
| if repo != nil && len(updates) > 0 { | |||
| if err := repofiles.PushUpdates(repo, updates); err != nil { | |||
| log.Error("Failed to Update: %s/%s Total Updates: %d", ownerName, repoName, len(updates)) | |||
| for i, update := range updates { | |||
| log.Error("Failed to Update: %s/%s Update: %d/%d: Branch: %s", ownerName, repoName, i, len(updates), update.Branch) | |||
| } | |||
| log.Error("Failed to Update: %s/%s Error: %v", ownerName, repoName, err) | |||
| pullRequestAllowed := repo.AllowsPulls() | |||
| if !pullRequestAllowed { | |||
| ctx.JSON(http.StatusOK, map[string]interface{}{ | |||
| "message": false, | |||
| ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ | |||
| Err: fmt.Sprintf("Failed to Update: %s/%s Error: %v", ownerName, repoName, err), | |||
| }) | |||
| return | |||
| } | |||
| } | |||
| results := make([]private.HookPostReceiveBranchResult, 0, len(opts.OldCommitIDs)) | |||
| // We have to reload the repo in case its state is changed above | |||
| repo = nil | |||
| var baseRepo *models.Repository | |||
| for i := range opts.OldCommitIDs { | |||
| refFullName := opts.RefFullNames[i] | |||
| newCommitID := opts.NewCommitIDs[i] | |||
| branch := git.RefEndName(opts.RefFullNames[i]) | |||
| if newCommitID != git.EmptySHA && strings.HasPrefix(refFullName, git.BranchPrefix) { | |||
| if repo == nil { | |||
| var err error | |||
| repo, err = models.GetRepositoryByOwnerAndName(ownerName, repoName) | |||
| if err != nil { | |||
| log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err) | |||
| ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ | |||
| Err: fmt.Sprintf("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err), | |||
| RepoWasEmpty: wasEmpty, | |||
| }) | |||
| return | |||
| } | |||
| if repo.OwnerName == "" { | |||
| repo.OwnerName = ownerName | |||
| } | |||
| if !repo.AllowsPulls() { | |||
| // We can stop there's no need to go any further | |||
| ctx.JSON(http.StatusOK, private.HookPostReceiveResult{ | |||
| RepoWasEmpty: wasEmpty, | |||
| }) | |||
| return | |||
| } | |||
| baseRepo = repo | |||
| if repo.IsFork { | |||
| if err := repo.GetBaseRepo(); err != nil { | |||
| log.Error("Failed to get Base Repository of Forked repository: %-v Error: %v", repo, err) | |||
| ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ | |||
| Err: fmt.Sprintf("Failed to get Base Repository of Forked repository: %-v Error: %v", repo, err), | |||
| RepoWasEmpty: wasEmpty, | |||
| }) | |||
| return | |||
| } | |||
| baseRepo = repo.BaseRepo | |||
| } | |||
| } | |||
| if !repo.IsFork && branch == baseRepo.DefaultBranch { | |||
| results = append(results, private.HookPostReceiveBranchResult{}) | |||
| continue | |||
| } | |||
| baseRepo := repo | |||
| if repo.IsFork { | |||
| if err := repo.GetBaseRepo(); err != nil { | |||
| log.Error("Failed to get Base Repository of Forked repository: %-v Error: %v", repo, err) | |||
| ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ | |||
| "err": fmt.Sprintf("Failed to get Base Repository of Forked repository: %-v Error: %v", repo, err), | |||
| pr, err := models.GetUnmergedPullRequest(repo.ID, baseRepo.ID, branch, baseRepo.DefaultBranch) | |||
| if err != nil && !models.IsErrPullRequestNotExist(err) { | |||
| log.Error("Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err) | |||
| ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ | |||
| Err: fmt.Sprintf( | |||
| "Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err), | |||
| RepoWasEmpty: wasEmpty, | |||
| }) | |||
| return | |||
| } | |||
| baseRepo = repo.BaseRepo | |||
| } | |||
| if !repo.IsFork && branch == baseRepo.DefaultBranch { | |||
| ctx.JSON(http.StatusOK, map[string]interface{}{ | |||
| "message": false, | |||
| }) | |||
| return | |||
| if pr == nil { | |||
| if repo.IsFork { | |||
| branch = fmt.Sprintf("%s:%s", repo.OwnerName, branch) | |||
| } | |||
| results = append(results, private.HookPostReceiveBranchResult{ | |||
| Message: true, | |||
| Create: true, | |||
| Branch: branch, | |||
| URL: fmt.Sprintf("%s/compare/%s...%s", baseRepo.HTMLURL(), util.PathEscapeSegments(baseRepo.DefaultBranch), util.PathEscapeSegments(branch)), | |||
| }) | |||
| } else { | |||
| results = append(results, private.HookPostReceiveBranchResult{ | |||
| Message: true, | |||
| Create: false, | |||
| Branch: branch, | |||
| URL: fmt.Sprintf("%s/pulls/%d", baseRepo.HTMLURL(), pr.Index), | |||
| }) | |||
| } | |||
| } | |||
| } | |||
| ctx.JSON(http.StatusOK, private.HookPostReceiveResult{ | |||
| Results: results, | |||
| RepoWasEmpty: wasEmpty, | |||
| }) | |||
| } | |||
| pr, err := models.GetUnmergedPullRequest(repo.ID, baseRepo.ID, branch, baseRepo.DefaultBranch) | |||
| if err != nil && !models.IsErrPullRequestNotExist(err) { | |||
| log.Error("Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err) | |||
| // SetDefaultBranch updates the default branch | |||
| func SetDefaultBranch(ctx *macaron.Context) { | |||
| ownerName := ctx.Params(":owner") | |||
| repoName := ctx.Params(":repo") | |||
| branch := ctx.Params(":branch") | |||
| repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName) | |||
| if err != nil { | |||
| log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err) | |||
| ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ | |||
| "Err": fmt.Sprintf("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err), | |||
| }) | |||
| return | |||
| } | |||
| if repo.OwnerName == "" { | |||
| repo.OwnerName = ownerName | |||
| } | |||
| repo.DefaultBranch = branch | |||
| gitRepo, err := git.OpenRepository(repo.RepoPath()) | |||
| if err != nil { | |||
| ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ | |||
| "Err": fmt.Sprintf("Failed to get git repository: %s/%s Error: %v", ownerName, repoName, err), | |||
| }) | |||
| return | |||
| } | |||
| if err := gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { | |||
| if !git.IsErrUnsupportedVersion(err) { | |||
| gitRepo.Close() | |||
| ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ | |||
| "err": fmt.Sprintf( | |||
| "Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err), | |||
| "Err": fmt.Sprintf("Unable to set default branch onrepository: %s/%s Error: %v", ownerName, repoName, err), | |||
| }) | |||
| return | |||
| } | |||
| } | |||
| gitRepo.Close() | |||
| if pr == nil { | |||
| if repo.IsFork { | |||
| branch = fmt.Sprintf("%s:%s", repo.OwnerName, branch) | |||
| } | |||
| ctx.JSON(http.StatusOK, map[string]interface{}{ | |||
| "message": true, | |||
| "create": true, | |||
| "branch": branch, | |||
| "url": fmt.Sprintf("%s/compare/%s...%s", baseRepo.HTMLURL(), util.PathEscapeSegments(baseRepo.DefaultBranch), util.PathEscapeSegments(branch)), | |||
| }) | |||
| } else { | |||
| ctx.JSON(http.StatusOK, map[string]interface{}{ | |||
| "message": true, | |||
| "create": false, | |||
| "branch": branch, | |||
| "url": fmt.Sprintf("%s/pulls/%d", baseRepo.HTMLURL(), pr.Index), | |||
| }) | |||
| } | |||
| if err := repo.UpdateDefaultBranch(); err != nil { | |||
| ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ | |||
| "Err": fmt.Sprintf("Unable to set default branch onrepository: %s/%s Error: %v", ownerName, repoName, err), | |||
| }) | |||
| return | |||
| } | |||
| ctx.JSON(http.StatusOK, map[string]interface{}{ | |||
| "message": false, | |||
| }) | |||
| ctx.PlainText(200, []byte("success")) | |||
| } | |||
| @@ -10,8 +10,10 @@ import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/modules/private" | |||
| "code.gitea.io/gitea/modules/setting" | |||
| "gitea.com/macaron/binding" | |||
| "gitea.com/macaron/macaron" | |||
| ) | |||
| @@ -77,11 +79,14 @@ func CheckUnitUser(ctx *macaron.Context) { | |||
| // RegisterRoutes registers all internal APIs routes to web application. | |||
| // These APIs will be invoked by internal commands for example `gitea serv` and etc. | |||
| func RegisterRoutes(m *macaron.Macaron) { | |||
| bind := binding.Bind | |||
| m.Group("/", func() { | |||
| m.Post("/ssh/authorized_keys", AuthorizedPublicKeyByContent) | |||
| m.Post("/ssh/:id/update/:repoid", UpdatePublicKeyInRepo) | |||
| m.Get("/hook/pre-receive/:owner/:repo", HookPreReceive) | |||
| m.Get("/hook/post-receive/:owner/:repo", HookPostReceive) | |||
| m.Post("/hook/pre-receive/:owner/:repo", bind(private.HookOptions{}), HookPreReceive) | |||
| m.Post("/hook/post-receive/:owner/:repo", bind(private.HookOptions{}), HookPostReceive) | |||
| m.Post("/hook/set-default-branch/:owner/:repo/:branch", SetDefaultBranch) | |||
| m.Get("/serv/none/:keyid", ServNoCommand) | |||
| m.Get("/serv/command/:keyid/:owner/:repo", ServCommand) | |||
| }, CheckInternalToken) | |||