@@ -4,7 +4,6 @@ import * as github from '@actions/github'
4
4
import { GitHub , getOctokitOptions } from '@actions/github/lib/utils.js'
5
5
import { throttling } from '@octokit/plugin-throttling'
6
6
import * as path from 'path'
7
- import * as fs from 'fs/promises'
8
7
9
8
import config from './config.js'
10
9
@@ -200,13 +199,6 @@ export default class Git {
200
199
}
201
200
}
202
201
203
- async getBlobBase64Content ( file ) {
204
- const fileRelativePath = path . join ( this . workingDir , file )
205
- const fileContent = await fs . readFile ( fileRelativePath )
206
-
207
- return fileContent . toString ( 'base64' )
208
- }
209
-
210
202
async getLastCommitSha ( ) {
211
203
this . lastCommitSha = await execCmd (
212
204
`git rev-parse HEAD` ,
@@ -243,56 +235,70 @@ export default class Git {
243
235
}
244
236
245
237
// Returns a git tree parsed for the specified commit sha
246
- async getTree ( commitSha ) {
247
- const output = await execCmd (
248
- `git ls-tree -r --full-tree ${ commitSha } ` ,
238
+ async getTreeId ( commitSha ) {
239
+ core . debug ( `Getting treeId for commit ${ commitSha } ` )
240
+ const output = ( await execCmd (
241
+ `git cat-file -p ${ commitSha } ` ,
249
242
this . workingDir
250
- )
243
+ ) ) . split ( '\n' )
251
244
252
- const tree = [ ]
253
- for ( const treeObject of output . split ( '\n' ) ) {
254
- const [ mode , type , sha ] = treeObject . split ( / \s / )
255
- const file = treeObject . split ( '\t' ) [ 1 ]
256
-
257
- const treeEntry = {
258
- mode,
259
- type,
260
- sha,
261
- path : file
262
- }
263
-
264
- tree . push ( treeEntry )
265
- }
245
+ const commitHeaders = output . slice ( 0 , output . findIndex ( ( e ) => e === '' ) )
246
+ const tree = commitHeaders . find ( ( e ) => e . startsWith ( 'tree' ) ) . replace ( 'tree ' , '' )
266
247
267
248
return tree
268
249
}
269
250
270
- // Creates the blob objects in GitHub for the files that are not in the previous commit only
271
- async createGithubBlobs ( commitSha ) {
272
- core . debug ( 'Creating missing blobs on GitHub' )
273
- const [ previousTree , tree ] = await Promise . all ( [ this . getTree ( `${ commitSha } ~1` ) , this . getTree ( commitSha ) ] )
274
- const promisesGithubCreateBlobs = [ ]
275
-
276
- for ( const treeEntry of tree ) {
277
- // If the current treeEntry are in the previous tree, that means that the blob is uploaded and it doesn't need to be uploaded to GitHub again.
278
- if ( previousTree . findIndex ( ( entry ) => entry . sha === treeEntry . sha ) !== - 1 ) {
279
- continue
280
- }
281
-
282
- const base64Content = await this . getBlobBase64Content ( treeEntry . path )
251
+ async getTreeDiff ( referenceTreeId , differenceTreeId ) {
252
+ const output = await execCmd (
253
+ `git diff-tree ${ referenceTreeId } ${ differenceTreeId } -r` ,
254
+ this . workingDir
255
+ )
283
256
284
- // Creates the blob. We don't need to store the response because the local sha is the same and we can use it to reference the blob
285
- const githubCreateBlobRequest = this . github . git . createBlob ( {
286
- owner : this . repo . user ,
287
- repo : this . repo . name ,
288
- content : base64Content ,
289
- encoding : 'base64'
257
+ const diff = [ ]
258
+ for ( const line of output . split ( '\n' ) ) {
259
+ const splitted = line
260
+ . replace ( / ^ : / , '' )
261
+ . replace ( '\t' , ' ' )
262
+ . split ( ' ' )
263
+
264
+ const [
265
+ newMode ,
266
+ previousMode ,
267
+ newBlob ,
268
+ previousBlob ,
269
+ change ,
270
+ path
271
+ ] = splitted
272
+
273
+ diff . push ( {
274
+ newMode,
275
+ previousMode,
276
+ newBlob,
277
+ previousBlob,
278
+ change,
279
+ path
290
280
} )
291
- promisesGithubCreateBlobs . push ( githubCreateBlobRequest )
292
281
}
293
282
294
- // Wait for all the file uploads to be completed
295
- await Promise . all ( promisesGithubCreateBlobs )
283
+ return diff
284
+ }
285
+
286
+ // Creates the blob objects in GitHub for the files that are not in the previous commit only
287
+ async uploadGitHubBlob ( blob ) {
288
+ core . debug ( `Uploading GitHub Blob for blob ${ blob } ` )
289
+ const fileContent = await execCmd (
290
+ `git cat-file -p ${ blob } ` ,
291
+ this . workingDir ,
292
+ false
293
+ )
294
+
295
+ // Creates the blob. We don't need to store the response because the local sha is the same and we can use it to reference the blob
296
+ return this . github . git . createBlob ( {
297
+ owner : this . repo . user ,
298
+ repo : this . repo . name ,
299
+ content : Buffer . from ( fileContent ) . toString ( 'base64' ) ,
300
+ encoding : 'base64'
301
+ } )
296
302
}
297
303
298
304
// Gets the commit list in chronological order
@@ -313,25 +319,10 @@ export default class Git {
313
319
)
314
320
}
315
321
316
- // Returns an array of objects with the git tree and the commit, one entry for each pending commit to push
317
- async getCommitsDataToPush ( ) {
318
- const commitsToPush = await this . getCommitsToPush ( )
319
-
320
- const commitsData = [ ]
321
- for ( const commitSha of commitsToPush ) {
322
- const [ commitMessage , tree ] = await Promise . all ( [ this . getCommitMessage ( commitSha ) , this . getTree ( commitSha ) , this . createGithubBlobs ( commitSha ) ] )
323
- const commitData = {
324
- commitMessage,
325
- tree
326
- }
327
- commitsData . push ( commitData )
328
- }
329
- return commitsData
330
- }
331
-
332
322
// A wrapper for running all the flow to generate all the pending commits using the GitHub API
333
323
async createGithubVerifiedCommits ( ) {
334
- const commitsData = await this . getCommitsDataToPush ( )
324
+ core . debug ( `Creating Commits using GitHub API` )
325
+ const commits = await this . getCommitsToPush ( )
335
326
336
327
if ( SKIP_PR === false ) {
337
328
// Creates the PR branch if doesn't exists
@@ -350,8 +341,8 @@ export default class Git {
350
341
}
351
342
}
352
343
353
- for ( const commitData of commitsData ) {
354
- await this . createGithubTreeAndCommit ( commitData . tree , commitData . commitMessage )
344
+ for ( const commit of commits ) {
345
+ await this . createGithubCommit ( commit )
355
346
}
356
347
357
348
core . debug ( `Updating branch ${ SKIP_PR === false ? this . prBranch : this . baseBranch } ref` )
@@ -502,14 +493,43 @@ export default class Git {
502
493
} )
503
494
}
504
495
505
- async createGithubTreeAndCommit ( tree , commitMessage ) {
496
+ async createGithubCommit ( commitSha ) {
497
+ const [ treeId , parentTreeId , commitMessage ] = await Promise . all ( [
498
+ this . getTreeId ( `${ commitSha } ` ) ,
499
+ this . getTreeId ( `${ commitSha } ~1` ) ,
500
+ this . getCommitMessage ( commitSha )
501
+ ] )
502
+
503
+ const treeDiff = await this . getTreeDiff ( treeId , parentTreeId )
504
+ core . debug ( `Uploading the blobs to GitHub` )
505
+ const blobsToCreate = treeDiff
506
+ . filter ( ( e ) => e . newMode !== '000000' ) // Do not upload the blob if it is being removed
507
+
508
+ await Promise . all ( blobsToCreate . map ( ( e ) => this . uploadGitHubBlob ( e . newBlob ) ) )
506
509
core . debug ( `Creating a GitHub tree` )
510
+ const tree = treeDiff . map ( ( e ) => {
511
+ if ( e . newMode === '000000' ) { // Set the sha to null to remove the file
512
+ e . newMode = e . previousMode
513
+ e . newBlob = null
514
+ }
515
+
516
+ const entry = {
517
+ path : e . path ,
518
+ mode : e . newMode ,
519
+ type : 'blob' ,
520
+ sha : e . newBlob
521
+ }
522
+
523
+ return entry
524
+ } )
525
+
507
526
let treeSha
508
527
try {
509
528
const request = await this . github . git . createTree ( {
510
529
owner : this . repo . user ,
511
530
repo : this . repo . name ,
512
- tree
531
+ tree,
532
+ base_tree : parentTreeId
513
533
} )
514
534
treeSha = request . data . sha
515
535
} catch ( error ) {
0 commit comments