"github.com/urfave/cli"
)
+const (
+ hookBatchSize = 30
+)
+
var (
// CmdHook represents the available hooks sub-command.
CmdHook = cli.Command{
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
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
}
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
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
}
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.
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)
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)
"fmt"
"net/http"
"net/url"
+ "time"
"code.gitea.io/gitea/modules/setting"
)
// 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
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())
}
}
// 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())
}
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
+}
// 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
}
"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)
s.action.Repo = repo
s.action.IsPrivate = repo.IsPrivate
- testCorrectRepoAction(t, s.commitRepoActionOptions, &s.action)
+ testCorrectRepoAction(t, &s.commitRepoActionOptions, &s.action)
}
}
RefFullName string
OldCommitID string
NewCommitID string
+ Branch string
}
// PushUpdate must be called for any push actions in order to
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 {
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.
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,
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
}
}
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{}{}
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)
}
}
}
)
// 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)
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"))
}
"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"
)
// 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)