code: func (p *implementation) clEmailAndProjectNoACLs(c context.Context, host string, changeNumber int64) (*gerritpb.ChangeInfo, error) {
  	cache := memcache.NewItem(c, fmt.Sprintf("gerrit-change-owner/%s/%d", host, changeNumber))
  	if err := memcache.Get(c, cache); err == nil {
  		val := &gerritpb.ChangeInfo{}
  		if err = proto.Unmarshal(cache.Value(), val); err == nil && val.Project != "" {
  			return val, nil
  		}
  	}
  
  	client, err := p.gerritClient(c, host)
  	if err != nil {
  		return nil, err
  	}
  	changeInfo, err := client.GetChange(c, &gerritpb.GetChangeRequest{
  		Number: changeNumber,
  		Options: []gerritpb.QueryOption{
  			gerritpb.QueryOption_DETAILED_ACCOUNTS,
  			gerritpb.QueryOption_SKIP_MERGEABLE,
  		},
  	})
  	// We can't cache outcome of not found CL because
  	//  * Milo may not at first have access to a CL, say while CL was hidden or
  	//    becaues of bad ACLs.
  	//  * Gerrit is known to return 404 flakes.
  	if err != nil {
  		return nil, err
  	}
  	// Cache and return only email and project.
  	ret := &gerritpb.ChangeInfo{
  		Project: changeInfo.Project,
  		Owner:   &gerritpb.AccountInfo{Email: changeInfo.GetOwner().GetEmail()},
  	}
  	if blob, err := proto.Marshal(ret); err == nil {
  		cache.SetValue(blob)
  		memcache.Set(c, cache)
  	}
  	return ret, nil
  }
docs: // clEmailAndProjectNoACLs fetches and caches change owner email and project.
  //
  // Gerrit change owner and project are deemed immutable.
  // Caveat: technically only owner's account id is immutable. Owner's email
  // associated with this account id may change, but this is rare.