mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 19:51:50 +00:00
batches: Surface commitVerification and tag verified to signed commits (#53135)
Co-authored-by: Kelli Rockwell <kelli@sourcegraph.com>
This commit is contained in:
parent
1afcb3d5b9
commit
ad226a7779
2
.vscode/extensions.json
vendored
2
.vscode/extensions.json
vendored
@ -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",
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -319,6 +319,11 @@ export const externalChangesetFieldsFragment = gql`
|
||||
createdAt
|
||||
updatedAt
|
||||
nextSyncAt
|
||||
commitVerification {
|
||||
... on GitHubCommitVerification {
|
||||
verified
|
||||
}
|
||||
}
|
||||
currentSpec {
|
||||
id
|
||||
type
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
.changeset-label {
|
||||
&__text--dark {
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -233,6 +233,7 @@ const BatchChangeChangesets: (variables: BatchChangeChangesetsVariables) => Batc
|
||||
},
|
||||
],
|
||||
nextSyncAt: null,
|
||||
commitVerification: null,
|
||||
repository: {
|
||||
id: 'repo123',
|
||||
name: 'github.com/sourcegraph/repo',
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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.
|
||||
"""
|
||||
|
||||
@ -71,6 +71,7 @@ go_library(
|
||||
"//internal/extsvc",
|
||||
"//internal/extsvc/auth",
|
||||
"//internal/extsvc/bitbucketserver",
|
||||
"//internal/extsvc/github",
|
||||
"//internal/featureflag",
|
||||
"//internal/gitserver",
|
||||
"//internal/gitserver/gitdomain",
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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{}, ¬FoundError{}
|
||||
})
|
||||
|
||||
@ -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{}, ¬FoundError{}
|
||||
})
|
||||
|
||||
@ -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 ¬FoundError{}
|
||||
})
|
||||
|
||||
@ -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 ¬FoundError{}
|
||||
})
|
||||
|
||||
@ -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 := ¬FoundError{}
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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"),
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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{
|
||||
|
||||
@ -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
|
||||
|
||||
3
migrations/BUILD.bazel
generated
3
migrations/BUILD.bazel
generated
@ -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",
|
||||
|
||||
@ -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;
|
||||
@ -0,0 +1,2 @@
|
||||
name: add_verification_to_changesets
|
||||
parents: [1684207923, 1685453088, 1685727930, 1685645480, 1685984018, 1685999719, 1686042710]
|
||||
@ -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;
|
||||
@ -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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user