@@ -25,112 +25,141 @@ function git(cwd: string, args: string[], opts: TinyexecOptions = {}) {
2525 } ) ;
2626}
2727
28- async function deleteRef ( cwd : string , ref : string ) {
29- await git ( cwd , [ "update-ref" , "-d" , ref ] , { throwOnError : false } ) ;
28+ interface Ref {
29+ fetchSource : string ;
30+ local : string ;
31+ remote : string ;
3032}
3133
32- function getRefNames ( context : PullRequestContext ) {
34+ function getRefs ( context : PullRequestContext ) : Record < "base" | "head" , Ref > {
3335 const suffix = `${ context . number } -${ randomUUID ( ) } ` ;
3436 return {
35- baseLocalRef : `refs/changesets-action-pr-status/base/${ suffix } ` ,
36- baseRemoteRef : `refs/heads/${ context . base . ref } ` ,
37- headLocalRef : `refs/changesets-action-pr-status/head/${ suffix } ` ,
38- headRemoteRef : `refs/heads/${ context . head . ref } ` ,
37+ base : {
38+ fetchSource : "origin" ,
39+ local : `refs/changesets-action-pr-status/base/${ suffix } ` ,
40+ remote : `refs/heads/${ context . base . ref } ` ,
41+ } ,
42+ head : {
43+ fetchSource : context . head . repo . clone_url ,
44+ local : `refs/changesets-action-pr-status/head/${ suffix } ` ,
45+ remote : `refs/heads/${ context . head . ref } ` ,
46+ } ,
3947 } ;
4048}
4149
50+ async function deepenRef ( cwd : string , ref : Ref , deepenBy : number ) {
51+ await git ( cwd , [
52+ "fetch" ,
53+ "--no-tags" ,
54+ `--deepen=${ deepenBy } ` ,
55+ ref . fetchSource ,
56+ `${ ref . remote } :${ ref . local } ` ,
57+ ] ) ;
58+ }
59+
4260async function ensureMergeBase ( args : {
4361 cwd : string ;
44- refs : ReturnType < typeof getRefNames > ;
45- headRemoteUrl : string ;
62+ refs : ReturnType < typeof getRefs > ;
4663 deepenBy ?: number ;
4764} ) {
48- const { cwd, refs, headRemoteUrl , deepenBy = 50 } = args ;
65+ const { cwd, refs, deepenBy = 50 } = args ;
4966
5067 while ( true ) {
51- const mergeBase = await git (
52- cwd ,
53- [ "merge-base" , refs . baseLocalRef , "HEAD" ] ,
54- { throwOnError : false } ,
55- ) ;
68+ const mergeBase = await git ( cwd , [ "merge-base" , refs . base . local , "HEAD" ] , {
69+ throwOnError : false ,
70+ } ) ;
5671
5772 if ( mergeBase . exitCode === 0 ) {
5873 return mergeBase . stdout . trim ( ) ;
5974 }
6075
6176 if ( ! ( await isRepoShallow ( { cwd } ) ) ) {
6277 throw new Error (
63- `Failed to find merge base between "${ refs . baseLocalRef } " and HEAD, and the repository is no longer shallow.` ,
78+ `Failed to find merge base between "${ refs . base . local } " and HEAD, and the repository is no longer shallow.` ,
6479 ) ;
6580 }
6681
67- await git ( cwd , [
68- "fetch" ,
69- "--no-tags" ,
70- `--deepen=${ deepenBy } ` ,
71- "origin" ,
72- `${ refs . baseRemoteRef } :${ refs . baseLocalRef } ` ,
73- ] ) ;
74- await git ( cwd , [
75- "fetch" ,
76- "--no-tags" ,
77- `--deepen=${ deepenBy } ` ,
78- headRemoteUrl ,
79- `${ refs . headRemoteRef } :${ refs . headLocalRef } ` ,
80- ] ) ;
82+ await deepenRef ( cwd , refs . base , deepenBy ) ;
83+ await deepenRef ( cwd , refs . head , deepenBy ) ;
8184 }
8285}
8386
84- export async function withPullRequestWorktree < T > (
87+ async function mkdtemp ( prefix : string ) {
88+ const dir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , prefix ) ) ;
89+
90+ return {
91+ dir,
92+ async [ Symbol . asyncDispose ] ( ) {
93+ await fs . rm ( dir , { recursive : true , force : true } ) ;
94+ } ,
95+ } ;
96+ }
97+
98+ async function tempRef ( cwd : string , ref : Ref ) {
99+ await git ( cwd , [
100+ "fetch" ,
101+ "--no-tags" ,
102+ "--depth=1" ,
103+ ref . fetchSource ,
104+ `${ ref . remote } :${ ref . local } ` ,
105+ ] ) ;
106+ return {
107+ async [ Symbol . asyncDispose ] ( ) {
108+ await git ( cwd , [ "update-ref" , "-d" , ref . local ] , { throwOnError : false } ) ;
109+ } ,
110+ } ;
111+ }
112+
113+ async function tempWorktree ( cwd : string , dir : string , ref : Ref ) {
114+ await git ( cwd , [ "worktree" , "add" , "--detach" , dir , ref . local ] ) ;
115+
116+ return {
117+ async [ Symbol . asyncDispose ] ( ) {
118+ await git ( cwd , [ "worktree" , "remove" , "--force" , dir ] , {
119+ throwOnError : false ,
120+ } ) ;
121+ } ,
122+ } ;
123+ }
124+
125+ type WithAsyncDispose < T > = T & {
126+ [ Symbol . asyncDispose ] ( ) : Promise < void > ;
127+ } ;
128+
129+ function moveDisposable < T extends object > (
130+ stack : AsyncDisposableStack ,
131+ value : T ,
132+ ) : WithAsyncDispose < T > {
133+ const moved = stack . move ( ) ;
134+ return Object . assign ( value , {
135+ async [ Symbol . asyncDispose ] ( ) {
136+ await moved . disposeAsync ( ) ;
137+ } ,
138+ } ) ;
139+ }
140+
141+ export async function getPullRequestWorktree (
85142 context : PullRequestContext ,
86- fn : ( worktree : WorktreeInfo ) => Promise < T > ,
87- repoCwd : string = process . cwd ( ) ,
88- ) {
89- const worktreeDir = await fs . mkdtemp (
90- path . join ( os . tmpdir ( ) , "changesets-action-pr-status-" ) ,
91- ) ;
92- const refs = getRefNames ( context ) ;
93-
94- try {
95- await git ( repoCwd , [
96- "fetch" ,
97- "--no-tags" ,
98- "--depth=1" ,
99- "origin" ,
100- `${ refs . baseRemoteRef } :${ refs . baseLocalRef } ` ,
101- ] ) ;
102- await git ( repoCwd , [
103- "fetch" ,
104- "--no-tags" ,
105- "--depth=1" ,
106- context . head . repo . clone_url ,
107- `${ refs . headRemoteRef } :${ refs . headLocalRef } ` ,
108- ] ) ;
109- await git ( repoCwd , [
110- "worktree" ,
111- "add" ,
112- "--detach" ,
113- worktreeDir ,
114- refs . headLocalRef ,
115- ] ) ;
116- await ensureMergeBase ( {
117- cwd : worktreeDir ,
118- refs,
119- headRemoteUrl : context . head . repo . clone_url ,
120- } ) ;
143+ cwd : string = process . cwd ( ) ,
144+ ) : Promise < WithAsyncDispose < WorktreeInfo > > {
145+ await using stack = new AsyncDisposableStack ( ) ;
146+ const worktreeDir = stack . use (
147+ await mkdtemp ( "changesets-action-pr-status-" ) ,
148+ ) . dir ;
121149
122- return await fn ( {
123- baseRef : refs . baseLocalRef ,
124- cwd : worktreeDir ,
125- } ) ;
126- } finally {
127- await git ( repoCwd , [ "worktree" , "remove" , "--force" , worktreeDir ] , {
128- throwOnError : false ,
129- } ) ;
130- await Promise . all ( [
131- deleteRef ( repoCwd , refs . baseLocalRef ) ,
132- deleteRef ( repoCwd , refs . headLocalRef ) ,
133- ] ) ;
134- await fs . rm ( worktreeDir , { recursive : true , force : true } ) ;
135- }
150+ const refs = getRefs ( context ) ;
151+
152+ stack . use ( await tempRef ( cwd , refs . base ) ) ;
153+ stack . use ( await tempRef ( cwd , refs . head ) ) ;
154+ stack . use ( await tempWorktree ( cwd , worktreeDir , refs . head ) ) ;
155+
156+ await ensureMergeBase ( {
157+ cwd : worktreeDir ,
158+ refs,
159+ } ) ;
160+
161+ return moveDisposable ( stack , {
162+ baseRef : refs . base . local ,
163+ cwd : worktreeDir ,
164+ } ) ;
136165}
0 commit comments