@@ -15,6 +15,7 @@ import type {
15
15
import { configuration } from '../../../configuration' ;
16
16
import { CoreGitConfiguration , GlyphChars , Schemes } from '../../../constants' ;
17
17
import type { Container } from '../../../container' ;
18
+ import { emojify } from '../../../emojis' ;
18
19
import { Features } from '../../../features' ;
19
20
import {
20
21
StashApplyError ,
@@ -42,20 +43,34 @@ import { GitProviderService } from '../../../git/gitProviderService';
42
43
import { encodeGitLensRevisionUriAuthority , GitUri } from '../../../git/gitUri' ;
43
44
import type { GitBlame , GitBlameAuthor , GitBlameLine , GitBlameLines } from '../../../git/models/blame' ;
44
45
import type { BranchSortOptions } from '../../../git/models/branch' ;
45
- import { GitBranch , isDetachedHead , sortBranches } from '../../../git/models/branch' ;
46
+ import {
47
+ getBranchNameWithoutRemote ,
48
+ getRemoteNameFromBranchName ,
49
+ GitBranch ,
50
+ isDetachedHead ,
51
+ sortBranches ,
52
+ } from '../../../git/models/branch' ;
46
53
import type { GitStashCommit } from '../../../git/models/commit' ;
47
- import { GitCommit , GitCommitIdentity } from '../../../git/models/commit' ;
54
+ import { GitCommit , GitCommitIdentity , isStash } from '../../../git/models/commit' ;
48
55
import { GitContributor } from '../../../git/models/contributor' ;
49
56
import type { GitDiff , GitDiffFilter , GitDiffHunkLine , GitDiffShortStat } from '../../../git/models/diff' ;
50
57
import type { GitFile , GitFileStatus } from '../../../git/models/file' ;
51
58
import { GitFileChange } from '../../../git/models/file' ;
59
+ import type {
60
+ GitGraph ,
61
+ GitGraphRow ,
62
+ GitGraphRowHead ,
63
+ GitGraphRowRemoteHead ,
64
+ GitGraphRowTag ,
65
+ } from '../../../git/models/graph' ;
66
+ import { GitGraphRowType } from '../../../git/models/graph' ;
52
67
import type { GitLog } from '../../../git/models/log' ;
53
68
import type { GitMergeStatus } from '../../../git/models/merge' ;
54
69
import type { GitRebaseStatus } from '../../../git/models/rebase' ;
55
70
import type { GitBranchReference } from '../../../git/models/reference' ;
56
71
import { GitReference , GitRevision } from '../../../git/models/reference' ;
57
72
import type { GitReflog } from '../../../git/models/reflog' ;
58
- import { GitRemote } from '../../../git/models/remote' ;
73
+ import { getRemoteIconUri , GitRemote } from '../../../git/models/remote' ;
59
74
import type { RepositoryChangeEvent } from '../../../git/models/repository' ;
60
75
import { Repository , RepositoryChange , RepositoryChangeComparisonMode } from '../../../git/models/repository' ;
61
76
import type { GitStash } from '../../../git/models/stash' ;
@@ -1583,6 +1598,179 @@ export class LocalGitProvider implements GitProvider, Disposable {
1583
1598
}
1584
1599
}
1585
1600
1601
+ @log ( )
1602
+ async getCommitsForGraph (
1603
+ repoPath : string ,
1604
+ asWebviewUri : ( uri : Uri ) => Uri ,
1605
+ options ?: {
1606
+ branch ?: string ;
1607
+ limit ?: number ;
1608
+ mode ?: 'single' | 'local' | 'all' ;
1609
+ ref ?: string ;
1610
+ } ,
1611
+ ) : Promise < GitGraph > {
1612
+ const [ logResult , stashResult , remotesResult ] = await Promise . allSettled ( [
1613
+ this . getLog ( repoPath , { all : true , ordering : 'date' , limit : options ?. limit } ) ,
1614
+ this . getStash ( repoPath ) ,
1615
+ this . getRemotes ( repoPath ) ,
1616
+ ] ) ;
1617
+
1618
+ return this . getCommitsForGraphCore (
1619
+ repoPath ,
1620
+ asWebviewUri ,
1621
+ getSettledValue ( logResult ) ,
1622
+ getSettledValue ( stashResult ) ,
1623
+ getSettledValue ( remotesResult ) ,
1624
+ options ,
1625
+ ) ;
1626
+ }
1627
+
1628
+ private async getCommitsForGraphCore (
1629
+ repoPath : string ,
1630
+ asWebviewUri : ( uri : Uri ) => Uri ,
1631
+ log : GitLog | undefined ,
1632
+ stash : GitStash | undefined ,
1633
+ remotes : GitRemote [ ] | undefined ,
1634
+ options ?: {
1635
+ ref ?: string ;
1636
+ mode ?: 'single' | 'local' | 'all' ;
1637
+ branch ?: string ;
1638
+ } ,
1639
+ ) : Promise < GitGraph > {
1640
+ if ( log == null ) {
1641
+ return {
1642
+ repoPath : repoPath ,
1643
+ rows : [ ] ,
1644
+ } ;
1645
+ }
1646
+
1647
+ const commits = ( log . pagedCommits ?.( ) ?? log . commits ) ?. values ( ) ;
1648
+ if ( commits == null ) {
1649
+ return {
1650
+ repoPath : repoPath ,
1651
+ rows : [ ] ,
1652
+ } ;
1653
+ }
1654
+
1655
+ const rows : GitGraphRow [ ] = [ ] ;
1656
+
1657
+ let current = false ;
1658
+ let refHeads : GitGraphRowHead [ ] ;
1659
+ let refRemoteHeads : GitGraphRowRemoteHead [ ] ;
1660
+ let refTags : GitGraphRowTag [ ] ;
1661
+ let parents : string [ ] ;
1662
+ let remoteName : string ;
1663
+ let isStashCommit : boolean ;
1664
+
1665
+ const remoteMap = remotes != null ? new Map ( remotes . map ( r => [ r . name , r ] ) ) : new Map ( ) ;
1666
+
1667
+ const skipStashParents = new Set ( ) ;
1668
+
1669
+ for ( const commit of commits ) {
1670
+ if ( skipStashParents . has ( commit . sha ) ) continue ;
1671
+
1672
+ refHeads = [ ] ;
1673
+ refRemoteHeads = [ ] ;
1674
+ refTags = [ ] ;
1675
+
1676
+ if ( commit . tips != null ) {
1677
+ for ( let tip of commit . tips ) {
1678
+ if ( tip === 'refs/stash' || tip === 'HEAD' ) continue ;
1679
+
1680
+ if ( tip . startsWith ( 'tag: ' ) ) {
1681
+ refTags . push ( {
1682
+ name : tip . substring ( 5 ) ,
1683
+ // Not currently used, so don't bother filling it out
1684
+ annotated : false ,
1685
+ } ) ;
1686
+
1687
+ continue ;
1688
+ }
1689
+
1690
+ current = tip . startsWith ( 'HEAD -> ' ) ;
1691
+ if ( current ) {
1692
+ tip = tip . substring ( 8 ) ;
1693
+ }
1694
+
1695
+ remoteName = getRemoteNameFromBranchName ( tip ) ;
1696
+ if ( remoteName ) {
1697
+ const remote = remoteMap . get ( remoteName ) ;
1698
+ if ( remote != null ) {
1699
+ const branchName = getBranchNameWithoutRemote ( tip ) ;
1700
+ if ( branchName === 'HEAD' ) continue ;
1701
+
1702
+ refRemoteHeads . push ( {
1703
+ name : branchName ,
1704
+ owner : remote . name ,
1705
+ url : remote . url ,
1706
+ avatarUrl : (
1707
+ remote . provider ?. avatarUri ?? getRemoteIconUri ( this . container , remote , asWebviewUri )
1708
+ ) ?. toString ( true ) ,
1709
+ } ) ;
1710
+
1711
+ continue ;
1712
+ }
1713
+ }
1714
+
1715
+ refHeads . push ( {
1716
+ name : tip ,
1717
+ isCurrentHead : current ,
1718
+ } ) ;
1719
+ }
1720
+ }
1721
+
1722
+ isStashCommit = isStash ( commit ) || ( stash ?. commits . has ( commit . sha ) ?? false ) ;
1723
+
1724
+ parents = commit . parents ;
1725
+ // Remove the second & third parent, if exists, from each stash commit as it is a Git implementation for the index and untracked files
1726
+ if ( isStashCommit && parents . length > 1 ) {
1727
+ // Copy the array to avoid mutating the original
1728
+ parents = [ ...parents ] ;
1729
+
1730
+ // Skip the "index commit" (e.g. contains staged files) of the stash
1731
+ skipStashParents . add ( parents [ 1 ] ) ;
1732
+ // Skip the "untracked commit" (e.g. contains untracked files) of the stash
1733
+ skipStashParents . add ( parents [ 2 ] ) ;
1734
+ parents . splice ( 1 , 2 ) ;
1735
+ }
1736
+
1737
+ rows . push ( {
1738
+ sha : commit . sha ,
1739
+ parents : parents ,
1740
+ author : commit . author . name ,
1741
+ avatarUrl : ! isStashCommit ? ( await commit . getAvatarUri ( ) ) ?. toString ( true ) : undefined ,
1742
+ email : commit . author . email ?? '' ,
1743
+ date : commit . committer . date . getTime ( ) ,
1744
+ message : emojify ( commit . message && String ( commit . message ) . length ? commit . message : commit . summary ) ,
1745
+ // TODO: review logic for stash, wip, etc
1746
+ type : isStashCommit
1747
+ ? GitGraphRowType . Stash
1748
+ : commit . parents . length > 1
1749
+ ? GitGraphRowType . MergeCommit
1750
+ : GitGraphRowType . Commit ,
1751
+ heads : refHeads ,
1752
+ remotes : refRemoteHeads ,
1753
+ tags : refTags ,
1754
+ } ) ;
1755
+ }
1756
+
1757
+ return {
1758
+ repoPath : repoPath ,
1759
+ paging : {
1760
+ limit : log . limit ,
1761
+ endingCursor : log . endingCursor ,
1762
+ startingCursor : log . startingCursor ,
1763
+ more : log . hasMore ,
1764
+ } ,
1765
+ rows : rows ,
1766
+
1767
+ more : async ( limit : number | { until : string } | undefined ) : Promise < GitGraph | undefined > => {
1768
+ const moreLog = await log . more ?.( limit ) ;
1769
+ return this . getCommitsForGraphCore ( repoPath , asWebviewUri , moreLog , stash , remotes , options ) ;
1770
+ } ,
1771
+ } ;
1772
+ }
1773
+
1586
1774
@log ( )
1587
1775
async getOldestUnpushedRefForFile ( repoPath : string , uri : Uri ) : Promise < string | undefined > {
1588
1776
const [ relativePath , root ] = splitPath ( uri , repoPath ) ;
@@ -2242,14 +2430,15 @@ export class LocalGitProvider implements GitProvider, Disposable {
2242
2430
count : commits . size ,
2243
2431
limit : moreUntil == null ? ( log . limit ?? 0 ) + moreLimit : undefined ,
2244
2432
hasMore : moreUntil == null ? moreLog . hasMore : true ,
2433
+ startingCursor : last ( log . commits ) ?. [ 0 ] ,
2434
+ endingCursor : moreLog . endingCursor ,
2245
2435
pagedCommits : ( ) => {
2246
2436
// Remove any duplicates
2247
2437
for ( const sha of log . commits . keys ( ) ) {
2248
2438
moreLog . commits . delete ( sha ) ;
2249
2439
}
2250
2440
return moreLog . commits ;
2251
2441
} ,
2252
- previousCursor : last ( log . commits ) ?. [ 0 ] ,
2253
2442
query : ( limit : number | undefined ) => this . getLog ( log . repoPath , { ...options , limit : limit } ) ,
2254
2443
} ;
2255
2444
mergedLog . more = this . getLogMoreFn ( mergedLog , options ) ;
@@ -3201,7 +3390,13 @@ export class LocalGitProvider implements GitProvider, Disposable {
3201
3390
@log ( )
3202
3391
async getIncomingActivity (
3203
3392
repoPath : string ,
3204
- options ?: { all ?: boolean ; branch ?: string ; limit ?: number ; ordering ?: 'date' | 'author-date' | 'topo' | null ; skip ?: number } ,
3393
+ options ?: {
3394
+ all ?: boolean ;
3395
+ branch ?: string ;
3396
+ limit ?: number ;
3397
+ ordering ?: 'date' | 'author-date' | 'topo' | null ;
3398
+ skip ?: number ;
3399
+ } ,
3205
3400
) : Promise < GitReflog | undefined > {
3206
3401
const scope = getLogScope ( ) ;
3207
3402
@@ -3229,7 +3424,13 @@ export class LocalGitProvider implements GitProvider, Disposable {
3229
3424
3230
3425
private getReflogMoreFn (
3231
3426
reflog : GitReflog ,
3232
- options ?: { all ?: boolean ; branch ?: string ; limit ?: number ; ordering ?: 'date' | 'author-date' | 'topo' | null ; skip ?: number } ,
3427
+ options ?: {
3428
+ all ?: boolean ;
3429
+ branch ?: string ;
3430
+ limit ?: number ;
3431
+ ordering ?: 'date' | 'author-date' | 'topo' | null ;
3432
+ skip ?: number ;
3433
+ } ,
3233
3434
) : ( limit : number ) => Promise < GitReflog > {
3234
3435
return async ( limit : number | undefined ) => {
3235
3436
limit = limit ?? configuration . get ( 'advanced.maxSearchItems' ) ?? 0 ;
@@ -3358,6 +3559,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
3358
3559
) ?? [ ] ,
3359
3560
undefined ,
3360
3561
[ ] ,
3562
+ undefined ,
3361
3563
s . stashName ,
3362
3564
onRef ,
3363
3565
) as GitStashCommit ,
0 commit comments