batches: Surface commitVerification and tag verified to signed commits (#53135)

Co-authored-by: Kelli Rockwell <kelli@sourcegraph.com>
This commit is contained in:
Becca Steinbrecher 2023-06-16 10:48:26 -06:00 committed by GitHub
parent 1afcb3d5b9
commit ad226a7779
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 576 additions and 117 deletions

View File

@ -3,7 +3,7 @@
// for the documentation about the extensions.json format
"recommendations": [
"sourcegraph.sourcegraph",
"EditorConfig.editorconfig",
"editorconfig.editorconfig",
"esbenp.prettier-vscode",
"graphql.vscode-graphql",
"golang.go",

View File

@ -30,6 +30,7 @@ export const Branch: React.FunctionComponent<React.PropsWithChildren<BranchProps
className={classNames('text-monospace', className)}
as={deleted ? 'del' : 'span'}
aria-label={deleted ? 'Deleted' : ''}
pill={true}
>
{!forkTarget || forkTarget.namespace === null ? (
name

View File

@ -287,6 +287,7 @@ export const BATCH_CHANGE_CHANGESETS_RESULT: BatchChangeChangesetsResult['node']
text: 'Some label',
},
],
commitVerification: null,
repository: {
id: 'repoid',
name: 'github.com/sourcegraph/awesome',
@ -330,6 +331,7 @@ export const BATCH_CHANGE_CHANGESETS_RESULT: BatchChangeChangesetsResult['node']
name: 'github.com/sourcegraph/awesome',
url: 'http://test.test/awesome',
},
commitVerification: null,
reviewState: null,
title: 'Add prettier to all projects',
createdAt: subDays(now, 5).toISOString(),

View File

@ -319,6 +319,11 @@ export const externalChangesetFieldsFragment = gql`
createdAt
updatedAt
nextSyncAt
commitVerification {
... on GitHubCommitVerification {
verified
}
}
currentSpec {
id
type

View File

@ -25,6 +25,7 @@ export const BATCH_CHANGE_CHANGESETS: (ExternalChangesetFields | HiddenExternalC
body: 'This changeset does the following things:\nIs awesome\nIs useful',
checkState: ChangesetCheckState.PENDING,
createdAt: now.toISOString(),
commitVerification: null,
externalID: '123',
externalURL: {
url: 'http://test.test/pr/123',

View File

@ -1,5 +0,0 @@
.changeset-label {
&__text--dark {
color: #000000;
}
}

View File

@ -6,17 +6,15 @@ import { Badge } from '@sourcegraph/wildcard'
import { ChangesetLabelFields } from '../../../../graphql-operations'
import styles from './ChangesetLabel.module.scss'
interface Props {
label: ChangesetLabelFields
}
/**
* Converts a hex color to a perceived brightness. Algorithm to determine color brightness taken from https://www.w3.org/TR/AERT/#color-contrast
* Converts a hex color to a perceived brightness. Algorithm to determine color brightness
* taken from https://www.w3.org/TR/AERT/#color-contrast
*
* @param color The hex encoded RGB color without the #
*
* @returns The perceived brightness from 0 to 255
*/
export function colorBrightness(color: string): number {
@ -33,9 +31,10 @@ export const ChangesetLabel: React.FunctionComponent<React.PropsWithChildren<Pro
return (
<Badge
variant="secondary"
className={classNames('mr-2', labelBrightness < 127 ? 'text-white' : styles.changesetLabelTextDark)}
className={classNames('mr-2', labelBrightness < 127 ? 'text-white' : 'text-dark')}
style={{ backgroundColor: '#' + label.color }}
tooltip={label.description || undefined}
pill={true}
>
{label.text}
</Badge>

View File

@ -2,7 +2,7 @@ import React from 'react'
import classNames from 'classnames'
import { Link, H3 } from '@sourcegraph/wildcard'
import { Link, H3, Badge, Tooltip } from '@sourcegraph/wildcard'
import { ExternalChangesetFields, ChangesetState } from '../../../../graphql-operations'
import { BranchMerge } from '../../Branch'
@ -35,15 +35,26 @@ export const ExternalChangesetInfoCell: React.FunctionComponent<
return (
<div className={classNames('d-flex flex-column', className)}>
<div className="m-0">
<H3 className={classNames('m-0 d-md-inline-block', { 'mr-2': node.labels.length > 0 })}>
<H3
className={classNames('m-0 d-md-inline-block', {
'mr-2': node.labels.length > 0 || node.commitVerification?.verified,
})}
>
{changesetTitle}
</H3>
{node.labels.length > 0 && (
<span className="d-block d-md-inline-block mr-2">
<>
{node.labels.map(label => (
<ChangesetLabel label={label} key={label.text} />
))}
</span>
</>
)}
{node.commitVerification?.verified && (
<Tooltip content="This commit was signed and verified by the code host.">
<Badge pill={true} className="mr-2">
Verified
</Badge>
</Tooltip>
)}
</div>
<div>

View File

@ -27,6 +27,14 @@ const config: Meta = {
control: { type: 'boolean' },
defaultValue: true,
},
labeled: {
control: { type: 'boolean' },
defaultValue: true,
},
commitsSigned: {
control: { type: 'boolean' },
defaultValue: true,
},
},
}
@ -56,6 +64,7 @@ export const AllStates: Story = args => {
body: 'This changeset does the following things:\nIs awesome\nIs useful',
checkState: ChangesetCheckState.PENDING,
createdAt: now.toISOString(),
commitVerification: args.commitsSigned ? { verified: true } : null,
externalID: '123',
externalURL: {
url: 'http://test.test/pr/123',
@ -66,14 +75,16 @@ export const AllStates: Story = args => {
added: 30,
deleted: 28,
},
labels: [
{
__typename: 'ChangesetLabel',
color: '93ba13',
description: 'Very awesome description',
text: 'Some label',
},
],
labels: args.labeled
? [
{
__typename: 'ChangesetLabel',
color: '93ba13',
description: 'Very awesome description',
text: 'Some label',
},
]
: [],
repository: {
id: 'repoid',
name: 'github.com/sourcegraph/sourcegraph',
@ -142,6 +153,7 @@ export const Unpublished: Story = args => {
body: 'This changeset does the following things:\nIs awesome\nIs useful',
checkState: null,
createdAt: now.toISOString(),
commitVerification: null,
externalID: null,
externalURL: null,
forkNamespace: null,
@ -217,6 +229,7 @@ export const Importing: Story = args => {
body: null,
checkState: null,
createdAt: now.toISOString(),
commitVerification: null,
externalID: '12345',
externalURL: null,
forkNamespace: null,
@ -279,6 +292,7 @@ export const ImportingFailed: Story = args => {
body: null,
checkState: null,
createdAt: now.toISOString(),
commitVerification: null,
externalID: '99999',
externalURL: null,
forkNamespace: null,
@ -333,6 +347,7 @@ export const SyncFailed: Story = args => {
body: null,
checkState: null,
createdAt: now.toISOString(),
commitVerification: null,
externalID: '99999',
externalURL: null,
forkNamespace: null,

View File

@ -42,6 +42,7 @@ const READY_EXTERNAL_CHANGESET: ChangesetFields = {
id: 'somev1',
error: null,
syncerError: null,
commitVerification: null,
currentSpec: {
id: 'spec-rand-id-1',
type: ChangesetSpecType.BRANCH,
@ -81,6 +82,7 @@ const FAILED_EXTERNAL_CHANGESET: ChangesetFields = {
id: 'somev2',
error: 'Cannot create PR, insufficient token scope.',
syncerError: null,
commitVerification: null,
currentSpec: {
id: 'spec-rand-id-2',
type: ChangesetSpecType.BRANCH,

View File

@ -233,6 +233,7 @@ const BatchChangeChangesets: (variables: BatchChangeChangesetsVariables) => Batc
},
],
nextSyncAt: null,
commitVerification: null,
repository: {
id: 'repo123',
name: 'github.com/sourcegraph/repo',

View File

@ -798,6 +798,8 @@ type ExternalChangesetResolver interface {
// If the changeset is a fork, this corresponds to the name of the fork.
ForkName() *string
CommitVerification(context.Context) (CommitVerificationResolver, error)
// ReviewState returns a value of type *btypes.ChangesetReviewState.
ReviewState(context.Context) *string
// CheckState returns a value of type *btypes.ChangesetCheckState.
@ -816,6 +818,18 @@ type ExternalChangesetResolver interface {
CurrentSpec(ctx context.Context) (VisibleChangesetSpecResolver, error)
}
// Only GitHubApps are supported for commit signing for now.
type CommitVerificationResolver interface {
ToGitHubCommitVerification() (GitHubCommitVerificationResolver, bool)
}
type GitHubCommitVerificationResolver interface {
Verified() bool
Reason() string
Signature() string
Payload() string
}
type ChangesetEventsConnectionResolver interface {
Nodes(ctx context.Context) ([]ChangesetEventResolver, error)
TotalCount(ctx context.Context) (int32, error)

View File

@ -435,6 +435,11 @@ type ExternalChangeset implements Node & Changeset {
"""
forkName: String
"""
Commit verification object for this changeset's commit, if it was signed.
"""
commitVerification: CommitVerification
"""
The review state of this changeset. This is only set once the changeset is published on the code host.
@ -480,6 +485,35 @@ type ExternalChangeset implements Node & Changeset {
currentSpec: VisibleChangesetSpec
}
"""
Commit signing verification for a code host, e.g. if it was signed via GitHub App or
an SSH key.
"""
union CommitVerification = GitHubCommitVerification
"""
The verification object describing the result of verifying the commit's signature.
https://docs.github.com/en/rest/git/commits?apiVersion=2022-11-28#create-a-commit
"""
type GitHubCommitVerification {
"""
Indicates whether GitHub considers the signature in the commit verified
"""
verified: Boolean!
"""
The reason for the verified value
"""
reason: String!
"""
The signature extracted from the commit
"""
signature: String!
"""
The value that was signed
"""
payload: String!
}
"""
Used in the batch change page for the overview component.
"""

View File

@ -71,6 +71,7 @@ go_library(
"//internal/extsvc",
"//internal/extsvc/auth",
"//internal/extsvc/bitbucketserver",
"//internal/extsvc/github",
"//internal/featureflag",
"//internal/gitserver",
"//internal/gitserver/gitdomain",

View File

@ -124,6 +124,13 @@ type ExternalURL struct {
ServiceType string
}
type GitHubCommitVerification struct {
Verified bool
Reason string
Signature string
Payload string
}
type Changeset struct {
Typename string `json:"__typename"`
ID string
@ -148,6 +155,8 @@ type Changeset struct {
CheckState string
Events ChangesetEventConnection
CommitVerification *GitHubCommitVerification
Diff Comparison
Labels []Label

View File

@ -22,6 +22,8 @@ import (
sgactor "github.com/sourcegraph/sourcegraph/internal/actor"
"github.com/sourcegraph/sourcegraph/internal/auth"
"github.com/sourcegraph/sourcegraph/internal/conf"
"github.com/sourcegraph/sourcegraph/internal/extsvc"
"github.com/sourcegraph/sourcegraph/internal/extsvc/github"
"github.com/sourcegraph/sourcegraph/internal/gitserver"
"github.com/sourcegraph/sourcegraph/internal/gqlutil"
"github.com/sourcegraph/sourcegraph/internal/types"
@ -352,6 +354,18 @@ func (r *changesetResolver) ForkName() *string {
return nil
}
func (r *changesetResolver) CommitVerification(ctx context.Context) (graphqlbackend.CommitVerificationResolver, error) {
switch r.changeset.ExternalServiceType {
case extsvc.TypeGitHub:
if r.changeset.CommitVerification != nil {
return &commitVerificationResolver{
commitVerification: r.changeset.CommitVerification,
}, nil
}
}
return nil, nil
}
func (r *changesetResolver) ReviewState(ctx context.Context) *string {
if !r.changeset.Published() {
return nil
@ -558,3 +572,39 @@ func (r *changesetLabelResolver) Description() *string {
}
return &r.label.Description
}
var _ graphqlbackend.CommitVerificationResolver = &commitVerificationResolver{}
type commitVerificationResolver struct {
commitVerification *github.Verification
}
func (c *commitVerificationResolver) ToGitHubCommitVerification() (graphqlbackend.GitHubCommitVerificationResolver, bool) {
if c.commitVerification != nil {
return &gitHubCommitVerificationResolver{commitVerification: c.commitVerification}, true
}
return nil, false
}
var _ graphqlbackend.GitHubCommitVerificationResolver = &gitHubCommitVerificationResolver{}
type gitHubCommitVerificationResolver struct {
commitVerification *github.Verification
}
func (r *gitHubCommitVerificationResolver) Verified() bool {
return r.commitVerification.Verified
}
func (r *gitHubCommitVerificationResolver) Reason() string {
return r.commitVerification.Reason
}
func (r *gitHubCommitVerificationResolver) Signature() string {
return r.commitVerification.Signature
}
func (r *gitHubCommitVerificationResolver) Payload() string {
return r.commitVerification.Payload
}

View File

@ -125,6 +125,7 @@ func TestChangesetResolver(t *testing.T) {
ExternalState: btypes.ChangesetExternalStateOpen,
ExternalCheckState: btypes.ChangesetCheckStatePending,
ExternalReviewState: btypes.ChangesetReviewStateChangesRequested,
CommitVerified: true,
PublicationState: btypes.ChangesetPublicationStatePublished,
ReconcilerState: btypes.ReconcilerStateCompleted,
Metadata: &github.PullRequest{
@ -415,6 +416,9 @@ func TestChangesetResolver(t *testing.T) {
Typename: "RepositoryComparison",
FileDiffs: testDiffGraphQL,
},
CommitVerification: &apitest.GitHubCommitVerification{
Verified: true,
},
},
},
{
@ -596,6 +600,12 @@ query($changeset: ID!) {
ownedByBatchChange
commitVerification {
... on GitHubCommitVerification {
verified
}
}
diff {
__typename

View File

@ -662,10 +662,18 @@ func (e *executor) runAfterCommit(ctx context.Context, css sources.ChangesetSour
// We use the existing commit as the basis for the new commit, duplicating it
// over the REST API in order to produce a signed version of it to replace the
// original one with.
err = gcss.DuplicateCommit(ctx, opts, remoteRepo, rev)
newCommit, err := gcss.DuplicateCommit(ctx, opts, remoteRepo, rev)
if err != nil {
return errors.Wrap(err, "failed to duplicate commit")
}
if newCommit.Verification.Verified {
err = e.tx.UpdateChangesetCommitVerification(ctx, e.ch, newCommit)
if err != nil {
return errors.Wrap(err, "failed to update changeset with commit verification")
}
} else {
log15.Warn("Commit created with GitHub App was not signed", "changeset", e.ch.ID, "commit", newCommit.SHA)
}
}
}
return nil

View File

@ -18,7 +18,6 @@ import (
var (
testGerritProjectName = "testrepo"
testGerritChangeID = "I467fc6636bb40b5bf33e7e20cefd2c5fa52dae63"
testProject = gerrit.Project{ID: "testrepoid", Name: testGerritProjectName}
)
@ -92,11 +91,12 @@ func TestGerritSource_LoadChangeset(t *testing.T) {
ctx := context.Background()
t.Run("error getting pull request", func(t *testing.T) {
cs, _ := mockGerritChangeset()
cs, id, _ := mockGerritChangeset()
cs.ExternalID = id
s, client := mockGerritSource()
want := errors.New("error")
client.GetChangeFunc.SetDefaultHook(func(ctx context.Context, changeID string) (*gerrit.Change, error) {
assert.Equal(t, changeID, testGerritChangeID)
assert.Equal(t, changeID, id)
return &gerrit.Change{}, want
})
@ -106,10 +106,11 @@ func TestGerritSource_LoadChangeset(t *testing.T) {
})
t.Run("pull request not found", func(t *testing.T) {
cs, _ := mockGerritChangeset()
cs, id, _ := mockGerritChangeset()
cs.ExternalID = id
s, client := mockGerritSource()
client.GetChangeFunc.SetDefaultHook(func(ctx context.Context, changeID string) (*gerrit.Change, error) {
assert.Equal(t, changeID, testGerritChangeID)
assert.Equal(t, changeID, id)
return &gerrit.Change{}, &notFoundError{}
})
@ -121,13 +122,14 @@ func TestGerritSource_LoadChangeset(t *testing.T) {
})
t.Run("success", func(t *testing.T) {
cs, _ := mockGerritChangeset()
cs, id, _ := mockGerritChangeset()
cs.ExternalID = id
s, client := mockGerritSource()
change := mockGerritChange(&testProject)
change := mockGerritChange(&testProject, id)
client.GetURLFunc.SetDefaultReturn(&url.URL{})
client.GetChangeFunc.SetDefaultHook(func(ctx context.Context, changeID string) (*gerrit.Change, error) {
assert.Equal(t, changeID, testGerritChangeID)
assert.Equal(t, changeID, id)
return change, nil
})
client.GetChangeReviewsFunc.SetDefaultReturn(&[]gerrit.Reviewer{}, nil)
@ -141,7 +143,7 @@ func TestGerritSource_CreateChangeset(t *testing.T) {
ctx := context.Background()
t.Run("error getting pull request", func(t *testing.T) {
cs, _ := mockGerritChangeset()
cs, _, _ := mockGerritChangeset()
s, client := mockGerritSource()
testChangeID := GenerateGerritChangeID(*cs.Changeset)
want := errors.New("error")
@ -157,11 +159,10 @@ func TestGerritSource_CreateChangeset(t *testing.T) {
})
t.Run("change not found", func(t *testing.T) {
cs, _ := mockGerritChangeset()
cs, id, _ := mockGerritChangeset()
s, client := mockGerritSource()
testChangeID := GenerateGerritChangeID(*cs.Changeset)
client.GetChangeFunc.SetDefaultHook(func(ctx context.Context, changeID string) (*gerrit.Change, error) {
assert.Equal(t, changeID, testChangeID)
assert.Equal(t, changeID, id)
return &gerrit.Change{}, &notFoundError{}
})
@ -174,14 +175,13 @@ func TestGerritSource_CreateChangeset(t *testing.T) {
})
t.Run("success", func(t *testing.T) {
cs, _ := mockGerritChangeset()
cs, id, _ := mockGerritChangeset()
s, client := mockGerritSource()
change := mockGerritChange(&testProject)
change := mockGerritChange(&testProject, id)
client.GetURLFunc.SetDefaultReturn(&url.URL{})
testChangeID := GenerateGerritChangeID(*cs.Changeset)
client.GetChangeFunc.SetDefaultHook(func(ctx context.Context, changeID string) (*gerrit.Change, error) {
assert.Equal(t, changeID, testChangeID)
assert.Equal(t, changeID, id)
return change, nil
})
client.GetChangeReviewsFunc.SetDefaultReturn(&[]gerrit.Reviewer{}, nil)
@ -196,12 +196,11 @@ func TestGerritSource_CreateDraftChangeset(t *testing.T) {
ctx := context.Background()
t.Run("error setting WIP", func(t *testing.T) {
cs, _ := mockGerritChangeset()
cs, id, _ := mockGerritChangeset()
s, client := mockGerritSource()
want := errors.New("error")
testChangeID := GenerateGerritChangeID(*cs.Changeset)
client.SetWIPFunc.SetDefaultHook(func(ctx context.Context, changeID string) error {
assert.Equal(t, changeID, testChangeID)
assert.Equal(t, changeID, id)
return want
})
@ -212,11 +211,10 @@ func TestGerritSource_CreateDraftChangeset(t *testing.T) {
})
t.Run("change not found", func(t *testing.T) {
cs, _ := mockGerritChangeset()
cs, id, _ := mockGerritChangeset()
s, client := mockGerritSource()
testChangeID := GenerateGerritChangeID(*cs.Changeset)
client.SetWIPFunc.SetDefaultHook(func(ctx context.Context, changeID string) error {
assert.Equal(t, changeID, testChangeID)
assert.Equal(t, changeID, id)
return &notFoundError{}
})
@ -229,18 +227,16 @@ func TestGerritSource_CreateDraftChangeset(t *testing.T) {
})
t.Run("GetChange error", func(t *testing.T) {
cs, _ := mockGerritChangeset()
cs, id, _ := mockGerritChangeset()
s, client := mockGerritSource()
want := errors.New("error")
testChangeID := GenerateGerritChangeID(*cs.Changeset)
client.GetURLFunc.SetDefaultReturn(&url.URL{})
client.SetWIPFunc.SetDefaultHook(func(ctx context.Context, changeID string) error {
assert.Equal(t, changeID, testChangeID)
assert.Equal(t, changeID, id)
return nil
})
client.GetChangeFunc.SetDefaultHook(func(ctx context.Context, changeID string) (*gerrit.Change, error) {
assert.Equal(t, changeID, testChangeID)
assert.Equal(t, changeID, id)
return &gerrit.Change{}, want
})
client.GetChangeReviewsFunc.SetDefaultReturn(&[]gerrit.Reviewer{}, nil)
@ -252,18 +248,16 @@ func TestGerritSource_CreateDraftChangeset(t *testing.T) {
})
t.Run("success", func(t *testing.T) {
cs, _ := mockGerritChangeset()
cs, id, _ := mockGerritChangeset()
s, client := mockGerritSource()
testChangeID := GenerateGerritChangeID(*cs.Changeset)
change := mockGerritChange(&testProject)
change := mockGerritChange(&testProject, id)
client.GetURLFunc.SetDefaultReturn(&url.URL{})
client.SetWIPFunc.SetDefaultHook(func(ctx context.Context, changeID string) error {
assert.Equal(t, changeID, testChangeID)
assert.Equal(t, changeID, id)
return nil
})
client.GetChangeFunc.SetDefaultHook(func(ctx context.Context, changeID string) (*gerrit.Change, error) {
assert.Equal(t, changeID, testChangeID)
assert.Equal(t, changeID, id)
return change, nil
})
client.GetChangeReviewsFunc.SetDefaultReturn(&[]gerrit.Reviewer{}, nil)
@ -278,11 +272,12 @@ func TestGerritSource_UndraftChangeset(t *testing.T) {
ctx := context.Background()
t.Run("error setting ReadyForReview", func(t *testing.T) {
cs, _ := mockGerritChangeset()
cs, id, _ := mockGerritChangeset()
cs.ExternalID = id
s, client := mockGerritSource()
want := errors.New("error")
client.SetReadyForReviewFunc.SetDefaultHook(func(ctx context.Context, changeID string) error {
assert.Equal(t, changeID, testGerritChangeID)
assert.Equal(t, changeID, id)
return want
})
@ -292,10 +287,11 @@ func TestGerritSource_UndraftChangeset(t *testing.T) {
})
t.Run("change not found", func(t *testing.T) {
cs, _ := mockGerritChangeset()
cs, id, _ := mockGerritChangeset()
cs.ExternalID = id
s, client := mockGerritSource()
client.SetReadyForReviewFunc.SetDefaultHook(func(ctx context.Context, changeID string) error {
assert.Equal(t, changeID, testGerritChangeID)
assert.Equal(t, changeID, id)
return &notFoundError{}
})
@ -307,17 +303,18 @@ func TestGerritSource_UndraftChangeset(t *testing.T) {
})
t.Run("GetChange error", func(t *testing.T) {
cs, _ := mockGerritChangeset()
cs, id, _ := mockGerritChangeset()
cs.ExternalID = id
s, client := mockGerritSource()
want := errors.New("error")
client.GetURLFunc.SetDefaultReturn(&url.URL{})
client.SetReadyForReviewFunc.SetDefaultHook(func(ctx context.Context, changeID string) error {
assert.Equal(t, changeID, testGerritChangeID)
assert.Equal(t, changeID, id)
return nil
})
client.GetChangeFunc.SetDefaultHook(func(ctx context.Context, changeID string) (*gerrit.Change, error) {
assert.Equal(t, changeID, testGerritChangeID)
assert.Equal(t, changeID, id)
return &gerrit.Change{}, want
})
client.GetChangeReviewsFunc.SetDefaultReturn(&[]gerrit.Reviewer{}, nil)
@ -328,17 +325,18 @@ func TestGerritSource_UndraftChangeset(t *testing.T) {
})
t.Run("success", func(t *testing.T) {
cs, _ := mockGerritChangeset()
cs, id, _ := mockGerritChangeset()
cs.ExternalID = id
s, client := mockGerritSource()
change := mockGerritChange(&testProject)
change := mockGerritChange(&testProject, id)
client.GetURLFunc.SetDefaultReturn(&url.URL{})
client.SetReadyForReviewFunc.SetDefaultHook(func(ctx context.Context, changeID string) error {
assert.Equal(t, changeID, testGerritChangeID)
assert.Equal(t, changeID, id)
return nil
})
client.GetChangeFunc.SetDefaultHook(func(ctx context.Context, changeID string) (*gerrit.Change, error) {
assert.Equal(t, changeID, testGerritChangeID)
assert.Equal(t, changeID, id)
return change, nil
})
client.GetChangeReviewsFunc.SetDefaultReturn(&[]gerrit.Reviewer{}, nil)
@ -352,12 +350,13 @@ func TestGerritSource_CloseChangeset(t *testing.T) {
ctx := context.Background()
t.Run("error declining pull request", func(t *testing.T) {
cs, _ := mockGerritChangeset()
cs, id, _ := mockGerritChangeset()
cs.ExternalID = id
s, client := mockGerritSource()
want := errors.New("error")
client.AbandonChangeFunc.SetDefaultHook(func(ctx context.Context, changeID string) (*gerrit.Change, error) {
assert.Equal(t, changeID, testGerritChangeID)
assert.Equal(t, changeID, id)
return &gerrit.Change{}, want
})
@ -367,13 +366,14 @@ func TestGerritSource_CloseChangeset(t *testing.T) {
})
t.Run("success", func(t *testing.T) {
cs, _ := mockGerritChangeset()
cs, id, _ := mockGerritChangeset()
cs.ExternalID = id
s, client := mockGerritSource()
pr := mockGerritChange(&testProject)
pr := mockGerritChange(&testProject, id)
client.GetURLFunc.SetDefaultReturn(&url.URL{})
client.AbandonChangeFunc.SetDefaultHook(func(ctx context.Context, changeID string) (*gerrit.Change, error) {
assert.Equal(t, changeID, testGerritChangeID)
assert.Equal(t, changeID, id)
return pr, nil
})
client.GetChangeReviewsFunc.SetDefaultReturn(&[]gerrit.Reviewer{}, nil)
@ -388,11 +388,13 @@ func TestGerritSource_CreateComment(t *testing.T) {
ctx := context.Background()
t.Run("error creating comment", func(t *testing.T) {
cs, _ := mockGerritChangeset()
cs, id, _ := mockGerritChangeset()
cs.ExternalID = id
s, client := mockGerritSource()
want := errors.New("error")
client.WriteReviewCommentFunc.SetDefaultHook(func(ctx context.Context, changeID string, ci gerrit.ChangeReviewComment) error {
assert.Equal(t, changeID, id)
assert.Equal(t, "comment", ci.Message)
return want
})
@ -403,10 +405,12 @@ func TestGerritSource_CreateComment(t *testing.T) {
})
t.Run("success", func(t *testing.T) {
cs, _ := mockGerritChangeset()
cs, id, _ := mockGerritChangeset()
cs.ExternalID = id
s, client := mockGerritSource()
client.WriteReviewCommentFunc.SetDefaultHook(func(ctx context.Context, changeID string, ci gerrit.ChangeReviewComment) error {
assert.Equal(t, changeID, id)
assert.Equal(t, "comment", ci.Message)
return nil
})
@ -420,12 +424,13 @@ func TestGerritSource_MergeChangeset(t *testing.T) {
ctx := context.Background()
t.Run("error merging pull request", func(t *testing.T) {
cs, _ := mockGerritChangeset()
cs, id, _ := mockGerritChangeset()
cs.ExternalID = id
s, client := mockGerritSource()
want := errors.New("error")
client.SubmitChangeFunc.SetDefaultHook(func(ctx context.Context, changeID string) (*gerrit.Change, error) {
assert.Equal(t, testGerritChangeID, changeID)
assert.Equal(t, id, changeID)
return &gerrit.Change{}, want
})
@ -437,12 +442,13 @@ func TestGerritSource_MergeChangeset(t *testing.T) {
})
t.Run("change not found", func(t *testing.T) {
cs, _ := mockGerritChangeset()
cs, id, _ := mockGerritChangeset()
cs.ExternalID = id
s, client := mockGerritSource()
want := &notFoundError{}
client.SubmitChangeFunc.SetDefaultHook(func(ctx context.Context, changeID string) (*gerrit.Change, error) {
assert.Equal(t, testGerritChangeID, changeID)
assert.Equal(t, id, changeID)
return &gerrit.Change{}, want
})
@ -452,13 +458,14 @@ func TestGerritSource_MergeChangeset(t *testing.T) {
})
t.Run("success with squash", func(t *testing.T) {
cs, _ := mockGerritChangeset()
cs, id, _ := mockGerritChangeset()
cs.ExternalID = id
s, client := mockGerritSource()
pr := mockGerritChange(&testProject)
pr := mockGerritChange(&testProject, id)
client.GetURLFunc.SetDefaultReturn(&url.URL{})
client.SubmitChangeFunc.SetDefaultHook(func(ctx context.Context, changeID string) (*gerrit.Change, error) {
assert.Equal(t, testGerritChangeID, changeID)
assert.Equal(t, id, changeID)
return pr, nil
})
client.GetChangeReviewsFunc.SetDefaultReturn(&[]gerrit.Reviewer{}, nil)
@ -470,13 +477,14 @@ func TestGerritSource_MergeChangeset(t *testing.T) {
})
t.Run("success with no squash", func(t *testing.T) {
cs, _ := mockGerritChangeset()
cs, id, _ := mockGerritChangeset()
cs.ExternalID = id
s, client := mockGerritSource()
pr := mockGerritChange(&testProject)
pr := mockGerritChange(&testProject, id)
client.GetURLFunc.SetDefaultReturn(&url.URL{})
client.SubmitChangeFunc.SetDefaultHook(func(ctx context.Context, changeID string) (*gerrit.Change, error) {
assert.Equal(t, testGerritChangeID, changeID)
assert.Equal(t, id, changeID)
return pr, nil
})
client.GetChangeReviewsFunc.SetDefaultReturn(&[]gerrit.Reviewer{}, nil)
@ -499,9 +507,9 @@ func assertGerritChangesetMatchesPullRequest(t *testing.T, cs *Changeset, pr *ge
// mockGerritChangeset creates a plausible non-forked changeset, repo,
// and Gerrit specific repo.
func mockGerritChangeset() (*Changeset, *types.Repo) {
repo := &types.Repo{Metadata: &testProject}
cs := &Changeset{
func mockGerritChangeset() (cs *Changeset, id string, repo *types.Repo) {
repo = &types.Repo{Metadata: &testProject}
cs = &Changeset{
Title: "title",
Body: "description",
Changeset: &btypes.Changeset{},
@ -510,16 +518,16 @@ func mockGerritChangeset() (*Changeset, *types.Repo) {
BaseRef: "refs/heads/targetbranch",
}
cs.Changeset.ExternalID = GenerateGerritChangeID(*cs.Changeset)
id = GenerateGerritChangeID(*cs.Changeset)
return cs, repo
return cs, id, repo
}
// mockGerritChange returns a plausible pull request that would be
// returned from Bitbucket Cloud for a non-forked changeset.
func mockGerritChange(project *gerrit.Project) *gerrit.Change {
func mockGerritChange(project *gerrit.Project, id string) *gerrit.Change {
return &gerrit.Change{
ChangeID: testGerritChangeID,
ChangeID: id,
Project: project.Name,
}
}

View File

@ -118,18 +118,18 @@ func (s GitHubSource) ValidateAuthenticator(ctx context.Context) error {
// to sign commits with the GitHub App authenticating on behalf of the user, rather than
// authenticating as the installation. See here for more details:
// https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app
func (s GitHubSource) DuplicateCommit(ctx context.Context, opts protocol.CreateCommitFromPatchRequest, repo *types.Repo, rev string) error {
func (s GitHubSource) DuplicateCommit(ctx context.Context, opts protocol.CreateCommitFromPatchRequest, repo *types.Repo, rev string) (*github.RestCommit, error) {
message := strings.Join(opts.CommitInfo.Messages, "\n")
repoMetadata := repo.Metadata.(*github.Repository)
owner, repoName, err := github.SplitRepositoryNameWithOwner(repoMetadata.NameWithOwner)
if err != nil {
return errors.Wrap(err, "getting owner and repo name to duplicate commit")
return nil, errors.Wrap(err, "getting owner and repo name to duplicate commit")
}
// Get the original, unsigned commit.
commit, err := s.client.GetRef(ctx, owner, repoName, rev)
if err != nil {
return errors.Wrap(err, "getting commit to duplicate")
return nil, errors.Wrap(err, "getting commit to duplicate")
}
// Our new signed commit should have the same parents as the original commit.
@ -142,7 +142,7 @@ func (s GitHubSource) DuplicateCommit(ctx context.Context, opts protocol.CreateC
// installation, so we just omit them.
newCommit, err := s.client.CreateCommit(ctx, owner, repoName, message, commit.Commit.Tree.SHA, parents, nil, nil)
if err != nil {
return errors.Wrap(err, "creating new commit")
return nil, errors.Wrap(err, "creating new commit")
}
// Update the branch ref to point to the new commit, orphaning the original. There's
@ -150,9 +150,10 @@ func (s GitHubSource) DuplicateCommit(ctx context.Context, opts protocol.CreateC
// collected automatically by GitHub so it's okay to leave it.
_, err = s.client.UpdateRef(ctx, owner, repoName, rev, newCommit.SHA)
if err != nil {
return errors.Wrap(err, "updating ref to point to new commit")
return nil, errors.Wrap(err, "updating ref to point to new commit")
}
return nil
return newCommit, nil
}
// CreateChangeset creates the given changeset on the code host.

View File

@ -849,7 +849,7 @@ func TestGithubSource_DuplicateCommit(t *testing.T) {
src, save := setup(t, ctx, tc.name)
defer save(t)
err := src.DuplicateCommit(ctx, opts, repo, tc.rev)
_, err := src.DuplicateCommit(ctx, opts, repo, tc.rev)
if err != nil && tc.err == nil {
t.Fatalf("unexpected error: %s", err)
}

View File

@ -539,6 +539,7 @@ func deleteChangesetSpecsQuery(opts *DeleteChangesetSpecsOpts) *sqlf.Query {
func scanChangesetSpec(c *btypes.ChangesetSpec, s dbutil.Scanner) error {
var published []byte
var typ string
err := s.Scan(
&c.ID,
&c.RandID,

View File

@ -10,11 +10,12 @@ import (
"strings"
"time"
"go.opentelemetry.io/otel/attribute"
adobatches "github.com/sourcegraph/sourcegraph/enterprise/internal/batches/sources/azuredevops"
gerritbatches "github.com/sourcegraph/sourcegraph/enterprise/internal/batches/sources/gerrit"
"github.com/sourcegraph/sourcegraph/internal/extsvc/azuredevops"
"github.com/sourcegraph/sourcegraph/internal/extsvc/gerrit"
"go.opentelemetry.io/otel/attribute"
"github.com/keegancsmith/sqlf"
"github.com/lib/pq"
@ -53,6 +54,7 @@ var changesetStringColumns = SQLColumns{
"external_state",
"external_review_state",
"external_check_state",
"commit_verification",
"diff_stat_added",
"diff_stat_deleted",
"sync_state",
@ -97,6 +99,7 @@ var ChangesetColumns = []*sqlf.Query{
sqlf.Sprintf("changesets.external_state"),
sqlf.Sprintf("changesets.external_review_state"),
sqlf.Sprintf("changesets.external_check_state"),
sqlf.Sprintf("changesets.commit_verification"),
sqlf.Sprintf("changesets.diff_stat_added"),
sqlf.Sprintf("changesets.diff_stat_deleted"),
sqlf.Sprintf("changesets.sync_state"),
@ -140,6 +143,7 @@ var changesetInsertColumns = []*sqlf.Query{
sqlf.Sprintf("external_state"),
sqlf.Sprintf("external_review_state"),
sqlf.Sprintf("external_check_state"),
sqlf.Sprintf("commit_verification"),
sqlf.Sprintf("diff_stat_added"),
sqlf.Sprintf("diff_stat_deleted"),
sqlf.Sprintf("sync_state"),
@ -205,6 +209,7 @@ var changesetInsertStringColumns = []string{
"external_state",
"external_review_state",
"external_check_state",
"commit_verification",
"diff_stat_added",
"diff_stat_deleted",
"sync_state",
@ -277,6 +282,17 @@ func (s *Store) CreateChangeset(ctx context.Context, cs ...*btypes.Changeset) (e
return err
}
var cv json.RawMessage
// Don't bother to record the result of verification if it's not even verified.
if c.CommitVerification != nil && c.CommitVerification.Verified {
cv, err = jsonbColumn(c.CommitVerification)
} else {
cv, err = jsonbColumn(nil)
}
if err != nil {
return err
}
// Not being able to find a title is fine, we just have a NULL in the database then.
title, _ := c.Title()
@ -300,6 +316,7 @@ func (s *Store) CreateChangeset(ctx context.Context, cs ...*btypes.Changeset) (e
dbutil.NullStringColumn(string(c.ExternalState)),
dbutil.NullStringColumn(string(c.ExternalReviewState)),
dbutil.NullStringColumn(string(c.ExternalCheckState)),
cv,
c.DiffStatAdded,
c.DiffStatDeleted,
syncState,
@ -858,6 +875,17 @@ func (s *Store) changesetWriteQuery(q string, includeID bool, c *btypes.Changese
return nil, err
}
var cv json.RawMessage
// Don't bother to record the result of verification if it's not even verified.
if c.CommitVerification != nil && c.CommitVerification.Verified {
cv, err = jsonbColumn(c.CommitVerification)
} else {
cv, err = jsonbColumn(nil)
}
if err != nil {
return nil, err
}
// Not being able to find a title is fine, we just have a NULL in the database then.
title, _ := c.Title()
@ -881,6 +909,7 @@ func (s *Store) changesetWriteQuery(q string, includeID bool, c *btypes.Changese
dbutil.NullStringColumn(string(c.ExternalState)),
dbutil.NullStringColumn(string(c.ExternalReviewState)),
dbutil.NullStringColumn(string(c.ExternalCheckState)),
cv,
c.DiffStatAdded,
c.DiffStatDeleted,
syncState,
@ -913,7 +942,7 @@ func (s *Store) changesetWriteQuery(q string, includeID bool, c *btypes.Changese
var updateChangesetQueryFmtstr = `
UPDATE changesets
SET (%s) = (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
SET (%s) = (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
WHERE id = %s
RETURNING
%s
@ -1054,6 +1083,28 @@ func (s *Store) UpdateChangesetUiPublicationState(ctx context.Context, cs *btype
return s.updateChangesetColumn(ctx, cs, "ui_publication_state", uiPublicationState)
}
// UpdateChangesetSCommitVerification records the commit verification object for a commit
// to the Changeset if it was signed and verified.
func (s *Store) UpdateChangesetCommitVerification(ctx context.Context, cs *btypes.Changeset, commit *github.RestCommit) (err error) {
ctx, _, endObservation := s.operations.updateChangesetCommitVerification.With(ctx, &err, observation.Args{Attrs: []attribute.KeyValue{
attribute.Int("ID", int(cs.ID)),
}})
defer endObservation(1, observation.Args{})
var cv json.RawMessage
// Don't bother to record the result of verification if it's not even verified.
if commit.Verification.Verified {
cv, err = jsonbColumn(commit.Verification)
} else {
cv, err = jsonbColumn(nil)
}
if err != nil {
return err
}
return s.updateChangesetColumn(ctx, cs, "commit_verification", cv)
}
// updateChangesetColumn updates the column with the given name, setting it to
// the given value, and updating the updated_at column.
func (s *Store) updateChangesetColumn(ctx context.Context, cs *btypes.Changeset, name string, val any) error {
@ -1385,7 +1436,7 @@ func (n jsonBatchChangeChangesetSet) Value() (driver.Value, error) {
}
func ScanChangeset(t *btypes.Changeset, s dbutil.Scanner) error {
var metadata, syncState json.RawMessage
var metadata, syncState, commitVerification json.RawMessage
var (
externalState string
@ -1413,6 +1464,7 @@ func ScanChangeset(t *btypes.Changeset, s dbutil.Scanner) error {
&dbutil.NullString{S: &externalState},
&dbutil.NullString{S: &externalReviewState},
&dbutil.NullString{S: &externalCheckState},
&commitVerification,
&t.DiffStatAdded,
&t.DiffStatDeleted,
&syncState,
@ -1483,6 +1535,14 @@ func ScanChangeset(t *btypes.Changeset, s dbutil.Scanner) error {
if err = json.Unmarshal(syncState, &t.SyncState); err != nil {
return errors.Wrapf(err, "scanChangeset: failed to unmarshal sync state: %s", syncState)
}
var cv *github.Verification
if err = json.Unmarshal(commitVerification, &cv); err != nil {
return errors.Wrapf(err, "scanChangesetSpecs: failed to unmarshal commitVerification: %s", commitVerification)
}
// Only set the commit verification if it's actually verified.
if cv.Verified {
t.CommitVerification = cv
}
return nil
}

View File

@ -1594,6 +1594,62 @@ func testStoreChangesets(t *testing.T, ctx context.Context, s *Store, clock bt.C
t.Fatalf("invalid changeset: %s", diff)
}
})
t.Run("UpdateChangesetCommitVerification", func(t *testing.T) {
c1 := bt.CreateChangeset(t, ctx, s, bt.TestChangesetOpts{Repo: repo.ID})
// Once with a verified commit
commitVerification := github.Verification{
Verified: true,
Reason: "valid",
Signature: "*********",
Payload: "*********",
}
commit := github.RestCommit{
URL: "https://api.github.com/repos/Birth-control-tech/birth-control-tech-BE/git/commits/dabd9bb07fdb5b580f168e942f2160b1719fc98f",
SHA: "dabd9bb07fdb5b580f168e942f2160b1719fc98f",
NodeID: "C_kwDOEW0OxtoAKGRhYmQ5YmIwN2ZkYjViNTgwZjE2OGU5NDJmMjE2MGIxNzE5ZmM5OGY",
Message: "Append Hello World to all README.md files",
Verification: commitVerification,
}
c1.CommitVerification = &commitVerification
want := c1.Clone()
if err := s.UpdateChangesetCommitVerification(ctx, c1, &commit); err != nil {
t.Fatal(err)
}
have, err := s.GetChangesetByID(ctx, c1.ID)
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(have, want); diff != "" {
t.Fatalf("found diff with signed commit: %s", diff)
}
// Once with a commit that's not verified
commitVerification = github.Verification{
Verified: false,
Reason: "unsigned",
}
commit.Verification = commitVerification
// A changeset spec with an unsigned commit should not have a commit
// verification set.
c1.CommitVerification = nil
want = c1.Clone()
if err := s.UpdateChangesetCommitVerification(ctx, c1, &commit); err != nil {
t.Fatal(err)
}
have, err = s.GetChangesetByID(ctx, c1.ID)
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(have, want); diff != "" {
t.Fatalf("found diff with unsigned commit: %s", diff)
}
})
}
func testStoreListChangesetSyncData(t *testing.T, ctx context.Context, s *Store, clock bt.Clock) {

View File

@ -244,6 +244,7 @@ type operations struct {
updateChangesetBatchChanges *observation.Operation
updateChangesetUIPublicationState *observation.Operation
updateChangesetCodeHostState *observation.Operation
updateChangesetCommitVerification *observation.Operation
getChangesetExternalIDs *observation.Operation
cancelQueuedBatchChangeChangesets *observation.Operation
enqueueChangesetsToClose *observation.Operation
@ -388,6 +389,7 @@ func newOperations(observationCtx *observation.Context) *operations {
updateChangesetBatchChanges: op("UpdateChangesetBatchChanges"),
updateChangesetUIPublicationState: op("UpdateChangesetUIPublicationState"),
updateChangesetCodeHostState: op("UpdateChangesetCodeHostState"),
updateChangesetCommitVerification: op("UpdateChangesetCommitVerification"),
getChangesetExternalIDs: op("GetChangesetExternalIDs"),
cancelQueuedBatchChangeChangesets: op("CancelQueuedBatchChangeChangesets"),
enqueueChangesetsToClose: op("EnqueueChangesetsToClose"),

View File

@ -11,6 +11,7 @@ import (
btypes "github.com/sourcegraph/sourcegraph/enterprise/internal/batches/types"
"github.com/sourcegraph/sourcegraph/internal/api"
"github.com/sourcegraph/sourcegraph/internal/extsvc"
"github.com/sourcegraph/sourcegraph/internal/extsvc/github"
"github.com/sourcegraph/sourcegraph/internal/gitserver/gitdomain"
)
@ -30,6 +31,7 @@ type TestChangesetOpts struct {
ExternalState btypes.ChangesetExternalState
ExternalReviewState btypes.ChangesetReviewState
ExternalCheckState btypes.ChangesetCheckState
CommitVerified bool
DiffStatAdded int32
DiffStatDeleted int32
@ -126,6 +128,15 @@ func BuildChangeset(opts TestChangesetOpts) *btypes.Changeset {
changeset.ExternalForkName = opts.ExternalForkName
}
if opts.CommitVerified {
changeset.CommitVerification = &github.Verification{
Verified: true,
Reason: "valid",
Signature: "*********",
Payload: "*********",
}
}
if opts.FailureMessage != "" {
changeset.FailureMessage = &opts.FailureMessage
}

View File

@ -10,6 +10,7 @@ import (
"github.com/goware/urlx"
"github.com/inconshreveable/log15"
"github.com/sourcegraph/go-diff/diff"
adobatches "github.com/sourcegraph/sourcegraph/enterprise/internal/batches/sources/azuredevops"
bbcs "github.com/sourcegraph/sourcegraph/enterprise/internal/batches/sources/bitbucketcloud"
gerritbatches "github.com/sourcegraph/sourcegraph/enterprise/internal/batches/sources/gerrit"
@ -270,9 +271,14 @@ type Changeset struct {
ExternalState ChangesetExternalState
ExternalReviewState ChangesetReviewState
ExternalCheckState ChangesetCheckState
DiffStatAdded *int32
DiffStatDeleted *int32
SyncState ChangesetSyncState
// If the commit created for a changeset is signed, commit verification is the
// signature verification result from the code host.
CommitVerification *github.Verification
DiffStatAdded *int32
DiffStatDeleted *int32
SyncState ChangesetSyncState
// The batch change that "owns" this changeset: it can create/close
// it on code host. If this is 0, it is imported/tracked by a batch change.

View File

@ -4876,6 +4876,19 @@
"GenerationExpression": "",
"Comment": ""
},
{
"Name": "commit_verification",
"Index": 45,
"TypeName": "jsonb",
"IsNullable": false,
"Default": "'{}'::jsonb",
"CharacterMaximumLength": 0,
"IsIdentity": false,
"IdentityGeneration": "",
"IsGenerated": "NEVER",
"GenerationExpression": "",
"Comment": ""
},
{
"Name": "computed_state",
"Index": 42,
@ -28285,7 +28298,7 @@
},
{
"Name": "reconciler_changesets",
"Definition": " SELECT c.id,\n c.batch_change_ids,\n c.repo_id,\n c.queued_at,\n c.created_at,\n c.updated_at,\n c.metadata,\n c.external_id,\n c.external_service_type,\n c.external_deleted_at,\n c.external_branch,\n c.external_updated_at,\n c.external_state,\n c.external_review_state,\n c.external_check_state,\n c.diff_stat_added,\n c.diff_stat_deleted,\n c.sync_state,\n c.current_spec_id,\n c.previous_spec_id,\n c.publication_state,\n c.owned_by_batch_change_id,\n c.reconciler_state,\n c.computed_state,\n c.failure_message,\n c.started_at,\n c.finished_at,\n c.process_after,\n c.num_resets,\n c.closing,\n c.num_failures,\n c.log_contents,\n c.execution_logs,\n c.syncer_error,\n c.external_title,\n c.worker_hostname,\n c.ui_publication_state,\n c.last_heartbeat_at,\n c.external_fork_name,\n c.external_fork_namespace,\n c.detached_at,\n c.previous_failure_message\n FROM (changesets c\n JOIN repo r ON ((r.id = c.repo_id)))\n WHERE ((r.deleted_at IS NULL) AND (EXISTS ( SELECT 1\n FROM ((batch_changes\n LEFT JOIN users namespace_user ON ((batch_changes.namespace_user_id = namespace_user.id)))\n LEFT JOIN orgs namespace_org ON ((batch_changes.namespace_org_id = namespace_org.id)))\n WHERE ((c.batch_change_ids ? (batch_changes.id)::text) AND (namespace_user.deleted_at IS NULL) AND (namespace_org.deleted_at IS NULL)))));"
"Definition": " SELECT c.id,\n c.batch_change_ids,\n c.repo_id,\n c.queued_at,\n c.created_at,\n c.updated_at,\n c.metadata,\n c.external_id,\n c.external_service_type,\n c.external_deleted_at,\n c.external_branch,\n c.external_updated_at,\n c.external_state,\n c.external_review_state,\n c.external_check_state,\n c.commit_verification,\n c.diff_stat_added,\n c.diff_stat_deleted,\n c.sync_state,\n c.current_spec_id,\n c.previous_spec_id,\n c.publication_state,\n c.owned_by_batch_change_id,\n c.reconciler_state,\n c.computed_state,\n c.failure_message,\n c.started_at,\n c.finished_at,\n c.process_after,\n c.num_resets,\n c.closing,\n c.num_failures,\n c.log_contents,\n c.execution_logs,\n c.syncer_error,\n c.external_title,\n c.worker_hostname,\n c.ui_publication_state,\n c.last_heartbeat_at,\n c.external_fork_name,\n c.external_fork_namespace,\n c.detached_at,\n c.previous_failure_message\n FROM (changesets c\n JOIN repo r ON ((r.id = c.repo_id)))\n WHERE ((r.deleted_at IS NULL) AND (EXISTS ( SELECT 1\n FROM ((batch_changes\n LEFT JOIN users namespace_user ON ((batch_changes.namespace_user_id = namespace_user.id)))\n LEFT JOIN orgs namespace_org ON ((batch_changes.namespace_org_id = namespace_org.id)))\n WHERE ((c.batch_change_ids ? (batch_changes.id)::text) AND (namespace_user.deleted_at IS NULL) AND (namespace_org.deleted_at IS NULL)))));"
},
{
"Name": "site_config",

View File

@ -520,6 +520,7 @@ Referenced by:
computed_state | text | | not null |
external_fork_name | citext | | |
previous_failure_message | text | | |
commit_verification | jsonb | | not null | '{}'::jsonb
Indexes:
"changesets_pkey" PRIMARY KEY, btree (id)
"changesets_repo_external_id_unique" UNIQUE CONSTRAINT, btree (repo_id, external_id)
@ -4775,6 +4776,7 @@ Foreign-key constraints:
c.external_state,
c.external_review_state,
c.external_check_state,
c.commit_verification,
c.diff_stat_added,
c.diff_stat_deleted,
c.sync_state,

View File

@ -158,7 +158,7 @@ type restCommitRef struct {
}
// A single Commit in a Repository, from the REST API.
type restCommit struct {
type RestCommit struct {
URL string `json:"url"`
SHA string `json:"sha"`
NodeID string `json:"node_id"`
@ -167,12 +167,14 @@ type restCommit struct {
Message string `json:"message"`
Tree restCommitTree `json:"tree"`
Parents []restCommitParent `json:"parents"`
Verification struct {
Verified bool `json:"verified"`
Reason string `json:"reason"`
Signature string `json:"signature"`
Payload string `json:"payload"`
} `json:"verification"`
Verification Verification `json:"verification"`
}
type Verification struct {
Verified bool `json:"verified"`
Reason string `json:"reason"`
Signature string `json:"signature"`
Payload string `json:"payload"`
}
// An updated reference in a Repository, returned from the REST API `update-ref` endpoint.

View File

@ -833,7 +833,7 @@ func (c *V3Client) GetRef(ctx context.Context, owner, repo, ref string) (*restCo
}
// CreateCommit creates a commit in the given repository based on a tree object.
func (c *V3Client) CreateCommit(ctx context.Context, owner, repo, message, tree string, parents []string, author, committer *restAuthorCommiter) (*restCommit, error) {
func (c *V3Client) CreateCommit(ctx context.Context, owner, repo, message, tree string, parents []string, author, committer *restAuthorCommiter) (*RestCommit, error) {
payload := struct {
Message string `json:"message"`
Tree string `json:"tree"`
@ -842,7 +842,7 @@ func (c *V3Client) CreateCommit(ctx context.Context, owner, repo, message, tree
Committer *restAuthorCommiter `json:"committer,omitempty"`
}{Message: message, Tree: tree, Parents: parents, Author: author, Committer: committer}
var commit restCommit
var commit RestCommit
if _, err := c.post(ctx, "repos/"+owner+"/"+repo+"/git/commits", payload, &commit); err != nil {
return nil, err
}

View File

@ -877,7 +877,7 @@ func TestV3Client_UpdateRef(t *testing.T) {
// currently at its HEAD, and update the branch to point to the new commit. Then
// we'll put it back to the original commit so this test can easily be run again.
originalCommit := &restCommit{
originalCommit := &RestCommit{
URL: "https://api.github.com/repos/sourcegraph/automation-testing/commits/c2f0a019668a800df480f07dba5d9dcaa0f64350",
SHA: "c2f0a019668a800df480f07dba5d9dcaa0f64350",
Tree: restCommitTree{

View File

@ -675,7 +675,7 @@ func (c *V4Client) GetRef(ctx context.Context, owner, repo, ref string) (*restCo
}
// CreateCommit creates a commit in the given repository based on a tree object.
func (c *V4Client) CreateCommit(ctx context.Context, owner, repo, message, tree string, parents []string, author, committer *restAuthorCommiter) (*restCommit, error) {
func (c *V4Client) CreateCommit(ctx context.Context, owner, repo, message, tree string, parents []string, author, committer *restAuthorCommiter) (*RestCommit, error) {
logger := c.log.Scoped("CreateCommit", "temporary client for creating a commit on GitHub")
// As of May 2023, the GraphQL API does not expose any mutations for creating commits
// other than one which requires sending the entire file contents for any files

View File

@ -1078,6 +1078,9 @@ go_library(
"frontend/1686749117_add_last_indexed_at_to_zoekt_repos/down.sql",
"frontend/1686749117_add_last_indexed_at_to_zoekt_repos/metadata.yaml",
"frontend/1686749117_add_last_indexed_at_to_zoekt_repos/up.sql",
"frontend/1686169273_add_verification_to_changesets/down.sql",
"frontend/1686169273_add_verification_to_changesets/metadata.yaml",
"frontend/1686169273_add_verification_to_changesets/up.sql",
"frontend/1686415661_change_product_subscriptions_cody_gateway_limits_to_int64/down.sql",
"frontend/1686415661_change_product_subscriptions_cody_gateway_limits_to_int64/metadata.yaml",
"frontend/1686415661_change_product_subscriptions_cody_gateway_limits_to_int64/up.sql",

View File

@ -0,0 +1,66 @@
BEGIN;
-- Note that we have to regenerate the reconciler_changesets view, as the SELECT
-- statement in the view definition isn't refreshed when the fields change within the
-- changesets table.
DROP VIEW IF EXISTS
reconciler_changesets;
ALTER TABLE changesets
DROP COLUMN IF EXISTS commit_verification;
CREATE VIEW reconciler_changesets AS
SELECT c.id,
c.batch_change_ids,
c.repo_id,
c.queued_at,
c.created_at,
c.updated_at,
c.metadata,
c.external_id,
c.external_service_type,
c.external_deleted_at,
c.external_branch,
c.external_updated_at,
c.external_state,
c.external_review_state,
c.external_check_state,
c.diff_stat_added,
c.diff_stat_deleted,
c.sync_state,
c.current_spec_id,
c.previous_spec_id,
c.publication_state,
c.owned_by_batch_change_id,
c.reconciler_state,
c.computed_state,
c.failure_message,
c.started_at,
c.finished_at,
c.process_after,
c.num_resets,
c.closing,
c.num_failures,
c.log_contents,
c.execution_logs,
c.syncer_error,
c.external_title,
c.worker_hostname,
c.ui_publication_state,
c.last_heartbeat_at,
c.external_fork_name,
c.external_fork_namespace,
c.detached_at,
c.previous_failure_message
FROM changesets c
JOIN repo r ON r.id = c.repo_id
WHERE r.deleted_at IS NULL AND EXISTS (
SELECT 1
FROM batch_changes
LEFT JOIN users namespace_user ON batch_changes.namespace_user_id = namespace_user.id
LEFT JOIN orgs namespace_org ON batch_changes.namespace_org_id = namespace_org.id
WHERE c.batch_change_ids ? batch_changes.id::text AND namespace_user.deleted_at IS NULL AND namespace_org.deleted_at IS NULL
);
COMMIT;

View File

@ -0,0 +1,2 @@
name: add_verification_to_changesets
parents: [1684207923, 1685453088, 1685727930, 1685645480, 1685984018, 1685999719, 1686042710]

View File

@ -0,0 +1,66 @@
BEGIN;
-- Note that we have to regenerate the reconciler_changesets view, as the SELECT
-- statement in the view definition isn't refreshed when the fields change within the
-- changesets table.
DROP VIEW IF EXISTS
reconciler_changesets;
ALTER TABLE changesets
ADD COLUMN IF NOT EXISTS commit_verification jsonb DEFAULT '{}'::jsonb NOT NULL;
CREATE VIEW reconciler_changesets AS
SELECT c.id,
c.batch_change_ids,
c.repo_id,
c.queued_at,
c.created_at,
c.updated_at,
c.metadata,
c.external_id,
c.external_service_type,
c.external_deleted_at,
c.external_branch,
c.external_updated_at,
c.external_state,
c.external_review_state,
c.external_check_state,
c.commit_verification,
c.diff_stat_added,
c.diff_stat_deleted,
c.sync_state,
c.current_spec_id,
c.previous_spec_id,
c.publication_state,
c.owned_by_batch_change_id,
c.reconciler_state,
c.computed_state,
c.failure_message,
c.started_at,
c.finished_at,
c.process_after,
c.num_resets,
c.closing,
c.num_failures,
c.log_contents,
c.execution_logs,
c.syncer_error,
c.external_title,
c.worker_hostname,
c.ui_publication_state,
c.last_heartbeat_at,
c.external_fork_name,
c.external_fork_namespace,
c.detached_at,
c.previous_failure_message
FROM changesets c
JOIN repo r ON r.id = c.repo_id
WHERE r.deleted_at IS NULL AND EXISTS (
SELECT 1
FROM batch_changes
LEFT JOIN users namespace_user ON batch_changes.namespace_user_id = namespace_user.id
LEFT JOIN orgs namespace_org ON batch_changes.namespace_org_id = namespace_org.id
WHERE c.batch_change_ids ? batch_changes.id::text AND namespace_user.deleted_at IS NULL AND namespace_org.deleted_at IS NULL
);
COMMIT;

View File

@ -1280,6 +1280,7 @@ CREATE TABLE changesets (
computed_state text NOT NULL,
external_fork_name citext,
previous_failure_message text,
commit_verification jsonb DEFAULT '{}'::jsonb NOT NULL,
CONSTRAINT changesets_batch_change_ids_check CHECK ((jsonb_typeof(batch_change_ids) = 'object'::text)),
CONSTRAINT changesets_external_id_check CHECK ((external_id <> ''::text)),
CONSTRAINT changesets_external_service_type_not_blank CHECK ((external_service_type <> ''::text)),
@ -4101,6 +4102,7 @@ CREATE VIEW reconciler_changesets AS
c.external_state,
c.external_review_state,
c.external_check_state,
c.commit_verification,
c.diff_stat_added,
c.diff_stat_deleted,
c.sync_state,