Esempio n. 1
0
func codelogin(ctxt appengine.Context, w http.ResponseWriter, req *http.Request) {
	randState, err := randomID()
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	clientID := req.FormValue("clientid")
	if clientID != "" {
		app.WriteMeta(ctxt, "googleapi.clientid", &clientID)
	}
	clientSecret := req.FormValue("clientsecret")
	if clientSecret != "" {
		app.WriteMeta(ctxt, "googleapi.clientsecret", &clientSecret)
	}

	if err := app.WriteMeta(ctxt, "codelogin.random", &randState); err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	cfg, err := oauthConfig(ctxt)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	authURL := cfg.AuthCodeURL(randState)
	http.Redirect(w, req, authURL, 301)
}
Esempio n. 2
0
func writeCL(ctxt appengine.Context, cl *CL, mtimeKey, modified string) error {
	err := app.Transaction(ctxt, func(ctxt appengine.Context) error {
		var old CL
		if err := app.ReadData(ctxt, "CL", cl.CL, &old); err != nil && err != datastore.ErrNoSuchEntity {
			return err
		}
		if old.CL == "" { // no old data
			var count int64
			app.ReadMeta(ctxt, "codereview.count", &count)
			app.WriteMeta(ctxt, "codereview.count", count+1)
		}

		// Copy CL into original structure.
		// This allows us to maintain other information in the CL structure
		// and not overwrite it when the Rietveld information is updated.
		if cl.Dead {
			old.Dead = true
		} else {
			old.Dead = false
			if old.Modified.After(cl.Modified) {
				return fmt.Errorf("CL %v: have %v but Rietveld sent %v", cl.CL, old.Modified, cl.Modified)
			}
			old.CL = cl.CL
			old.Desc = cl.Desc
			old.Owner = cl.Owner
			old.OwnerEmail = cl.OwnerEmail
			old.Created = cl.Created
			old.Modified = cl.Modified
			old.MessagesLoaded = cl.MessagesLoaded
			if cl.MessagesLoaded {
				old.Messages = cl.Messages
				old.Submitted = cl.Submitted
			}
			old.Reviewers = cl.Reviewers
			old.CC = cl.CC
			old.Closed = cl.Closed
			if !reflect.DeepEqual(old.PatchSets, cl.PatchSets) {
				old.PatchSets = cl.PatchSets
				old.PatchSetsLoaded = false
			}
		}

		if err := app.WriteData(ctxt, "CL", cl.CL, &old); err != nil {
			return err
		}
		if mtimeKey != "" {
			app.WriteMeta(ctxt, mtimeKey, modified)
		}
		return nil
	})
	if err != nil {
		ctxt.Errorf("storing CL %v: %v", cl.CL, err)
	}
	return err
}
Esempio n. 3
0
func writeIssue(ctxt appengine.Context, issue *Issue, stateKey string, state interface{}) error {
	err := app.Transaction(ctxt, func(ctxt appengine.Context) error {
		var old Issue
		if err := app.ReadData(ctxt, "Issue", fmt.Sprint(issue.ID), &old); err != nil && err != datastore.ErrNoSuchEntity {
			return err
		}
		if old.ID == 0 { // no old data
			var count int64
			app.ReadMeta(ctxt, "issue.count", &count)
			app.WriteMeta(ctxt, "issue.count", count+1)
		}

		if old.Modified.After(issue.Modified) {
			return fmt.Errorf("issue %v: have %v but code.google.com sent %v", issue.ID, old.Modified, issue.Modified)
		}

		// Copy Issue into original structure.
		// This allows us to maintain other information in the Issue structure
		// and not overwrite it when the issue information is updated.
		old.ID = issue.ID
		old.Summary = issue.Summary
		old.Status = issue.Status
		old.Duplicate = issue.Duplicate
		old.Owner = issue.Owner
		old.CC = issue.CC
		old.Label = issue.Label
		old.Comment = issue.Comment
		old.State = issue.State
		old.Created = issue.Created
		old.Modified = issue.Modified
		old.Stars = issue.Stars
		old.ClosedDate = issue.ClosedDate
		updateIssue(&old)

		if err := app.WriteData(ctxt, "Issue", fmt.Sprint(issue.ID), &old); err != nil {
			return err
		}
		if stateKey != "" {
			app.WriteMeta(ctxt, stateKey, state)
		}
		return nil
	})
	if err != nil {
		ctxt.Errorf("storing issue %v: %v", issue.ID, err)
	}
	return err
}
Esempio n. 4
0
func codetoken(ctxt appengine.Context, w http.ResponseWriter, req *http.Request) {
	var randState string
	if err := app.ReadMeta(ctxt, "codelogin.random", &randState); err != nil {
		panic(err)
	}

	cfg, err := oauthConfig(ctxt)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	if req.FormValue("state") != randState {
		http.Error(w, "bad state", 500)
		return
	}
	code := req.FormValue("code")
	if code == "" {
		http.Error(w, "missing code", 500)
		return
	}

	tr := &oauth.Transport{
		Config:    cfg,
		Transport: urlfetch.Client(ctxt).Transport,
	}

	_, err = tr.Exchange(code)
	if err != nil {
		http.Error(w, "exchanging code: "+err.Error(), 500)
		return
	}

	if err := app.WriteMeta(ctxt, "codelogin.token", tr.Token); err != nil {
		http.Error(w, "writing token: "+err.Error(), 500)
		return
	}

	app.DeleteMeta(ctxt, "codelogin.random")

	fmt.Fprintf(w, "have token; expires at %v\n", tr.Token.Expiry)
}
Esempio n. 5
0
func load(ctxt appengine.Context) error {
	mtime := time.Date(2009, 1, 1, 0, 0, 0, 0, time.UTC)
	if appengine.IsDevAppServer() {
		mtime = time.Now().UTC().Add(-24 * time.Hour)
	}
	app.ReadMeta(ctxt, "issue.mtime", &mtime)

	now := time.Now()

	var issues []*Issue
	var err error
	const maxResults = 500
	var try int
	needMore := false
	for try = 0; ; try++ {
		issues, err = search(ctxt, "go", "all", "", false, mtime, now, maxResults)
		if err != nil {
			ctxt.Errorf("load issues since %v: %v", mtime, err)
			return nil
		}

		if len(issues) == 0 {
			ctxt.Infof("no updates found from %v to %v", mtime, now)
			app.WriteMeta(ctxt, "issue.mtime", now.Add(-1*time.Minute))
			if try > 0 {
				// We shortened the time range; try again now that we've updated mtime.
				return app.ErrMoreCron
			}
			return nil
		}
		if len(issues) < maxResults {
			ctxt.Infof("%d issues from %v to %v", len(issues), mtime, now)
			if try > 0 {
				// Keep exploring once we finish this load.
				needMore = true
			}
			break
		}

		ctxt.Errorf("updater found too many updates from %v to %v", mtime, now)
		if now.Sub(mtime) <= 2*time.Second {
			ctxt.Errorf("cannot shorten update time frame")
			return nil
		}

		if now.Sub(mtime) > 1*time.Hour {
			now = mtime.Add(now.Sub(mtime) / 10)
		} else {
			now = mtime.Add(now.Sub(mtime) / 2)
		}
		ctxt.Infof("shortened to %v to %v", mtime, now)
	}

	issues, err = search(ctxt, "go", "all", "", true, mtime, now, maxResults)
	if err != nil {
		ctxt.Errorf("full load of issues from %v to %v: %v", mtime, now, err)
		return nil
	}

	if len(issues) == 0 {
		ctxt.Errorf("unexpected: no issues from %v to %v", mtime, now)
		return nil
	}

	for _, issue := range issues {
		println("WRITE ISSUE", issue.ID)
		if err := writeIssue(ctxt, issue, "", nil); err != nil {
			return nil
		}
		if mtime.Before(issue.Modified) {
			mtime = issue.Modified
		}
	}

	if try > 0 {
		mtime = now.Add(-1 * time.Second)
	}
	app.WriteMeta(ctxt, "issue.mtime", mtime.UTC())

	if needMore {
		return app.ErrMoreCron
	}
	return nil
}
Esempio n. 6
0
func loadRevOnce(ctxt appengine.Context, repo, branch, hash string) (nextHash string) {
	ctxt.Infof("load todo %s %s %s", repo, branch, hash)

	// Check that this todo is still valid.
	// If so, extend the expiry time so that no one else tries it while we do.
	// This supercedes the usual use of app.Lock and app.Unlock and also
	// provides a way to rate limit the polling.
	todoKey := fmt.Sprintf("commit.todo.%s.%s", repo, hash)

	err := app.Transaction(ctxt, func(ctxt appengine.Context) error {
		var todo revTodo
		if err := app.ReadData(ctxt, "RevTodo", todoKey, &todo); err != nil {
			return err
		}
		if time.Now().Before(todo.Time) {
			ctxt.Infof("poll %s %s not scheduled until %v", repo, hash, todo.Time)
			return errWait
		}
		dtAll := todo.Time.Sub(todo.Start)
		dtOne := todo.Time.Sub(todo.Last)
		var dtMax time.Duration
		if dtAll < 24*time.Hour {
			dtMax = 5 * time.Minute
		} else if dtAll < 7*24*time.Hour {
			dtMax = 1 * time.Hour
		} else {
			dtMax = 24 * time.Hour
		}
		if dtOne *= 2; dtOne > dtMax {
			dtOne = dtMax
		} else if dtOne == 0 {
			dtOne = 1 * time.Minute
		}
		todo.Last = time.Now()
		todo.Time = todo.Last.Add(dtOne)

		if err := app.WriteData(ctxt, "RevTodo", todoKey, &todo); err != nil {
			return err
		}
		return nil
	})

	if err != nil {
		ctxt.Errorf("skipping poll: %v", err)
		return ""
	}

	r, err := fetchRev(ctxt, repo, hash)
	if err != nil {
		ctxt.Errorf("fetching %v %v: %v", repo, hash, err)
		return ""
	}

	err = app.Transaction(ctxt, func(ctxt appengine.Context) error {
		var old Rev
		if err := app.ReadData(ctxt, "Rev", repo+"."+hash, &old); err != nil && err != datastore.ErrNoSuchEntity {
			return err
		}
		if old.Hash == r.Hash && len(old.Next) == len(r.Next) {
			// up to date
			return nil
		}
		if old.Hash == "" { // no old data
			var count int
			if err := app.ReadMeta(ctxt, "commit.count."+repo, &count); err != nil && err != datastore.ErrNoSuchEntity {
				return err
			}
			count++
			old.Seq = count
			if err := app.WriteMeta(ctxt, "commit.count."+repo, count); err != nil {
				return err
			}
			if r.Branch != branch && len(r.Prev) == 1 {
				ctxt.Infof("detected branch; forcing todo of parent")
				err := writeTodo(ctxt, repo, branch, r.Prev[0], true)
				if err != nil {
					ctxt.Errorf("re-adding todo: %v", err)
				}
				nextHash = r.Prev[0]
			}
		}
		old.Repo = r.Repo
		old.Branch = r.Branch
		// old.Seq already correct; r.Seq is not
		old.Hash = r.Hash
		old.ShortHash = old.Hash[:12]
		old.Prev = r.Prev
		old.Next = r.Next
		old.Author = r.Author
		old.AuthorEmail = r.AuthorEmail
		old.Time = r.Time
		old.Log = r.Log
		old.Files = r.Files

		if err := app.WriteData(ctxt, "Rev", repo+"."+hash, &old); err != nil {
			return err
		}

		return nil
	})

	if err != nil {
		ctxt.Errorf("updating %v %v: %v", repo, hash, err)
		return ""
	}

	if r.Next == nil {
		ctxt.Errorf("leaving todo for %s %s - no next yet", repo, hash)
		return ""
	}

	success := true
	forward := false
	for _, next := range r.Next {
		err := addTodo(ctxt, repo, r.Branch, next)
		if err == errDone {
			forward = true
			continue
		}
		if err == errBranched {
			ctxt.Infof("%v -> %v is a branch", r.Hash[:12], next[:12])
			continue
		}
		if err != nil {
			ctxt.Errorf("storing todo for %s %s: %v %p %p", repo, next, err, err, errDone)
			success = false
		}
		forward = true // innocent until proven guilty
		if nextHash == "" {
			nextHash = next
		} else {
			laterLoadRev.Call(ctxt, repo, r.Branch, next)
		}
	}

	if forward && success {
		ctxt.Infof("delete todo %s\n", todoKey)
		app.DeleteData(ctxt, "RevTodo", todoKey)
	} else {
		ctxt.Errorf("leaving todo for %s %s due to errors or branching", repo, hash)
	}

	return nextHash
}