11import { useMemo } from 'react'
22
3+ import { fetchEventSource } from '@microsoft/fetch-event-source'
34import { formatDistanceStrict } from 'date-fns'
45import { truncate } from 'lodash'
56import { Observable , of } from 'rxjs'
6- import { map } from 'rxjs/operators'
7+ import { map , throttleTime } from 'rxjs/operators'
78
89import { memoizeObservable } from '@sourcegraph/common'
910import { dataOrThrowErrors , gql } from '@sourcegraph/http-client'
1011import { makeRepoURI } from '@sourcegraph/shared/src/util/url'
1112import { useObservable } from '@sourcegraph/wildcard'
1213
1314import { requestGraphQL } from '../../backend/graphql'
15+ import { useFeatureFlag } from '../../featureFlags/useFeatureFlag'
1416import { GitBlameResult , GitBlameVariables } from '../../graphql-operations'
1517
1618import { useBlameVisibility } from './useBlameVisibility'
@@ -24,20 +26,45 @@ interface BlameHunkDisplayInfo {
2426 message : string
2527}
2628
27- export type BlameHunk = NonNullable <
28- NonNullable < NonNullable < GitBlameResult [ 'repository' ] > [ 'commit' ] > [ 'blob' ]
29- > [ 'blame' ] [ number ] & { displayInfo : BlameHunkDisplayInfo }
29+ export interface BlameHunk {
30+ startLine : number
31+ endLine : number
32+ message : string
33+ rev : string
34+ author : {
35+ date : string
36+ person : {
37+ email : string
38+ displayName : string
39+ user :
40+ | undefined
41+ | null
42+ | {
43+ username : string
44+ }
45+ }
46+ }
47+ commit : {
48+ url : string
49+ parents : {
50+ oid : string
51+ } [ ]
52+ }
53+ displayInfo : BlameHunkDisplayInfo
54+ }
3055
31- const fetchBlame = memoizeObservable (
56+ const fetchBlameViaGraphQL = memoizeObservable (
3257 ( {
3358 repoName,
3459 revision,
3560 filePath,
61+ sourcegraphURL,
3662 } : {
3763 repoName : string
3864 revision : string
3965 filePath : string
40- } ) : Observable < Omit < BlameHunk , 'displayInfo' > [ ] | undefined > =>
66+ sourcegraphURL : string
67+ } ) : Observable < { current : BlameHunk [ ] | undefined } > =>
4168 requestGraphQL < GitBlameResult , GitBlameVariables > (
4269 gql `
4370 query GitBlame($repo: String!, $rev: String!, $path: String!) {
@@ -74,65 +101,151 @@ const fetchBlame = memoizeObservable(
74101 { repo : repoName , rev : revision , path : filePath }
75102 ) . pipe (
76103 map ( dataOrThrowErrors ) ,
77- map ( ( { repository } ) => repository ?. commit ?. blob ?. blame )
104+ map ( ( { repository } ) => repository ?. commit ?. blob ?. blame ) ,
105+ map ( hunks => ( hunks ? hunks . map ( blame => addDisplayInfoForHunk ( blame , sourcegraphURL ) ) : undefined ) ) ,
106+ map ( hunks => ( { current : hunks } ) )
78107 ) ,
79108 makeRepoURI
80109)
81110
111+ interface RawStreamHunk {
112+ author : {
113+ Name : string
114+ Email : string
115+ Date : string
116+ }
117+ commit : {
118+ parents : string [ ]
119+ url : string
120+ }
121+ commitID : string
122+ endLine : number
123+ startLine : number
124+ filename : string
125+ message : string
126+ }
127+
128+ const fetchBlameViaStreaming = memoizeObservable (
129+ ( {
130+ repoName,
131+ revision,
132+ filePath,
133+ sourcegraphURL,
134+ } : {
135+ repoName : string
136+ revision : string
137+ filePath : string
138+ sourcegraphURL : string
139+ } ) : Observable < { current : BlameHunk [ ] | undefined } > =>
140+ new Observable < { current : BlameHunk [ ] | undefined } > ( subscriber => {
141+ const assembledHunks : BlameHunk [ ] = [ ]
142+ const repoAndRevisionPath = `/${ repoName } ${ revision ? `@${ revision } ` : '' } `
143+ fetchEventSource ( `/.api/blame${ repoAndRevisionPath } /stream/${ filePath } ` , {
144+ method : 'GET' ,
145+ headers : {
146+ 'X-Requested-With' : 'Sourcegraph' ,
147+ 'X-Sourcegraph-Should-Trace' : new URLSearchParams ( window . location . search ) . get ( 'trace' ) || 'false' ,
148+ } ,
149+ onmessage ( event ) {
150+ if ( event . event === 'hunk' ) {
151+ const rawHunks : RawStreamHunk [ ] = JSON . parse ( event . data )
152+ for ( const rawHunk of rawHunks ) {
153+ const hunk : Omit < BlameHunk , 'displayInfo' > = {
154+ startLine : rawHunk . startLine ,
155+ endLine : rawHunk . endLine ,
156+ message : rawHunk . message ,
157+ rev : rawHunk . commitID ,
158+ author : {
159+ date : rawHunk . author . Date ,
160+ person : {
161+ email : rawHunk . author . Email ,
162+ displayName : rawHunk . author . Name ,
163+ user : null ,
164+ } ,
165+ } ,
166+ commit : {
167+ url : rawHunk . commit . url ,
168+ parents : rawHunk . commit . parents ? rawHunk . commit . parents . map ( oid => ( { oid } ) ) : [ ] ,
169+ } ,
170+ }
171+ assembledHunks . push ( addDisplayInfoForHunk ( hunk , sourcegraphURL ) )
172+ }
173+ subscriber . next ( { current : assembledHunks } )
174+ }
175+ } ,
176+ onerror ( event ) {
177+ // eslint-disable-next-line no-console
178+ console . error ( event )
179+ } ,
180+ } ) . then (
181+ ( ) => subscriber . complete ( ) ,
182+ error => subscriber . error ( error )
183+ )
184+ // Throttle the results to avoid re-rendering the blame sidebar for every hunk
185+ } ) . pipe ( throttleTime ( 1000 , undefined , { leading : true , trailing : true } ) ) ,
186+ makeRepoURI
187+ )
188+
82189/**
83190 * Get display info shared between status bar items and text document decorations.
84191 */
85- const getDisplayInfoFromHunk = (
86- { author, commit, message } : Omit < BlameHunk , 'displayInfo' > ,
87- sourcegraphURL : string ,
88- now : number
89- ) : BlameHunkDisplayInfo => {
192+ const addDisplayInfoForHunk = ( hunk : Omit < BlameHunk , 'displayInfo' > , sourcegraphURL : string ) : BlameHunk => {
193+ const now = Date . now ( )
194+ const { author, commit, message } = hunk
195+
90196 const displayName = truncate ( author . person . displayName , { length : 25 } )
91197 const username = author . person . user ? `(${ author . person . user . username } ) ` : ''
92198 const dateString = formatDistanceStrict ( new Date ( author . date ) , now , { addSuffix : true } )
93199 const timestampString = new Date ( author . date ) . toLocaleString ( )
94200 const linkURL = new URL ( commit . url , sourcegraphURL ) . href
95201 const content = `${ dateString } • ${ username } ${ displayName } [${ truncate ( message , { length : 45 } ) } ]`
96202
97- return {
203+ ; ( hunk as BlameHunk ) . displayInfo = {
98204 displayName,
99205 username,
100206 dateString,
101207 timestampString,
102208 linkURL,
103209 message : content ,
104210 }
211+ return hunk as BlameHunk
105212}
106213
214+ /**
215+ * For performance reasons, the hunks array can be mutated in place. To still be
216+ * able to propagate updates accordingly, this is wrapped in a ref object that
217+ * can be recreated whenever we emit new values.
218+ */
107219export const useBlameHunks = (
108220 {
109221 repoName,
110222 revision,
111223 filePath,
224+ enableCodeMirror,
112225 } : {
113226 repoName : string
114227 revision : string
115228 filePath : string
229+ enableCodeMirror : boolean
116230 } ,
117231 sourcegraphURL : string
118- ) : BlameHunk [ ] | undefined => {
232+ ) : { current : BlameHunk [ ] | undefined } => {
233+ const [ enableStreamingGitBlame , status ] = useFeatureFlag ( 'enable-streaming-git-blame' )
234+
119235 const [ isBlameVisible ] = useBlameVisibility ( )
236+ const shouldFetchBlame = isBlameVisible && status !== 'initial'
237+
120238 const hunks = useObservable (
121- useMemo ( ( ) => ( isBlameVisible ? fetchBlame ( { revision, repoName, filePath } ) : of ( undefined ) ) , [
122- isBlameVisible ,
123- revision ,
124- repoName ,
125- filePath ,
126- ] )
239+ useMemo (
240+ ( ) =>
241+ shouldFetchBlame
242+ ? enableCodeMirror && enableStreamingGitBlame
243+ ? fetchBlameViaStreaming ( { revision, repoName, filePath, sourcegraphURL } )
244+ : fetchBlameViaGraphQL ( { revision, repoName, filePath, sourcegraphURL } )
245+ : of ( { current : undefined } ) ,
246+ [ shouldFetchBlame , enableCodeMirror , enableStreamingGitBlame , revision , repoName , filePath , sourcegraphURL ]
247+ )
127248 )
128249
129- const hunksWithDisplayInfo = useMemo ( ( ) => {
130- const now = Date . now ( )
131- return hunks ?. map ( hunk => ( {
132- ...hunk ,
133- displayInfo : getDisplayInfoFromHunk ( hunk , sourcegraphURL , now ) ,
134- } ) )
135- } , [ hunks , sourcegraphURL ] )
136-
137- return hunksWithDisplayInfo
250+ return hunks || { current : undefined }
138251}
0 commit comments