import { GithubHelper , SimpleLabel } from './githubHelper' ; import GitlabHelper from './gitlabHelper' ; import settings from '../settings' ; import { Octokit as GitHubApi } from '@octokit/rest' ; import { throttling } from '@octokit/plugin-throttling' ; import { Gitlab } from '@gitbeaker/node' ; import * as fs from 'fs' ; import AWS from 'aws-sdk' ; import { sleep } from './utils' ; const issueCounters = { nrOfPlaceholderIssues : 0 , nrOfReplacementIssues : 0 , nrOfFailedIssues : 0 , } ; if ( settings . s3 ) { AWS . config . credentials = new AWS . Credentials ( { accessKeyId : settings . s3 . accessKeyId , secretAccessKey : settings . s3 . secretAccessKey , } ) ; } try { } catch ( e ) { if ( e . code === 'MODULE_NOT_FOUND' ) { console . log ( '\n\nPlease copy the sample_settings.js to settings.js.' ) ; } else { console . log ( e ) ; } process . exit ( 1 ) ; } if ( ! settings . gitlab . token || settings . gitlab . token === '{{gitlab private token}}' ) { console . log ( '\n\nYou have to enter your GitLab private token in the settings.js file.' ) ; process . exit ( 1 ) ; } const gitlabApi = new Gitlab ( { host : settings . gitlab . url ? settings . gitlab . url : 'http://gitlab.com' , token : settings . gitlab . token , } ) ; const MyOctokit = GitHubApi . plugin ( throttling ) ; const githubApi = new MyOctokit ( { previews : settings . useIssueImportAPI ? [ "golden-comet" ] : [ ] , debug : false , baseUrl : settings . github . baseUrl ? settings . github . baseUrl : 'https://api.github.com' , timeout : 5000 , headers : { 'user-agent' : 'node-gitlab-2-github' , accept : 'application/vnd.github.v3+json' , } , auth : 'token ' + settings . github . token , throttle : { onRateLimit : async ( retryAfter , options ) => { console . log ( `Request quota exhausted for request ${ options . method } ${ options . url }` ) ; await sleep ( 60000 ) ; return true ; } , onAbuseLimit : async ( retryAfter , options ) => { console . log ( `Abuse detected for request ${ options . method } ${ options . url }` ) ; await sleep ( 60000 ) ; return true ; } , } , } ) ; const gitlabHelper = new GitlabHelper ( gitlabApi , settings . gitlab ) ; const githubHelper = new GithubHelper ( githubApi , settings . github , gitlabHelper , settings . useIssuesForAllMergeRequests ) ; if ( ! settings . gitlab . projectId ) { gitlabHelper . listProjects ( ) ; } else { migrate ( ) ; } function createPlaceholderIssue ( expectedIdx ) { return { iid : expectedIdx , title : `[PLACEHOLDER ISSUE] - for issue #${ expectedIdx }` , description : 'This is to ensure the issue numbers in GitLab and GitHub are the same' , state : 'closed' , isPlaceholder : true , } ; } function createReplacementIssue ( id , title , state ) { const originalGitlabIssueLink = 'TODO' ; const description = `The original issue\n\n\tId: ${ id }\n\tTitle: ${ title }\n\ncould not be created.\nThis is a dummy issue, replacing the original one. It contains everything but the original issue description. In case the gitlab repository is still existing, visit the following link to show the original issue:\n\n${ originalGitlabIssueLink }` ; return { iid : id , title : `${ title } [REPLACEMENT ISSUE]` , description , state , } ; } /**
 * Performs all of the migration tasks to move a GitLab repo to GitHub
 */ async function migrate ( ) { try { await githubHelper . registerRepoId ( ) ; await gitlabHelper . registerProjectPath ( settings . gitlab . projectId ) ; if ( settings . transfer . description ) { await transferDescription ( ) ; } if ( settings . transfer . milestones ) { await transferMilestones ( ) ; } if ( settings . transfer . labels ) { await transferLabels ( true , settings . conversion . useLowerCaseLabels ) ; } if ( settings . transfer . releases ) { await transferReleases ( ) ; } if ( settings . transfer . issues ) { await transferIssues ( ) ; } if ( settings . transfer . mergeRequests ) { if ( settings . mergeRequests . log ) { await logMergeRequests ( settings . mergeRequests . logFile ) ; } else { await transferMergeRequests ( ) ; } } } catch ( err ) { console . error ( 'Error during transfer:' ) ; console . error ( err ) ; } console . log ( '\n\nTransfer complete!\n\n' ) ; } /**
 * Transfer the description of the repository.
 */ async function transferDescription ( ) { inform ( 'Transferring Description' ) ; let project = await gitlabApi . Projects . show ( settings . gitlab . projectId ) as any ; await githubHelper . updateRepositoryDescription ( project . description ) ; } /**
 * Transfer any milestones that exist in GitLab that do not exist in GitHub.
 */ async function transferMilestones ( ) { inform ( 'Transferring Milestones' ) ; let milestones = await gitlabApi . ProjectMilestones . all ( settings . gitlab . projectId ) ; milestones = milestones . sort ( ( a , b ) => a . id - b . id ) ; const githubMilestones = await githubHelper . getAllGithubMilestones ( ) ; for ( let milestone of milestones ) { if ( ! githubMilestones . find ( m => m . title === milestone . title ) ) { console . log ( 'Creating: ' + milestone . title ) ; try { await githubHelper . createMilestone ( milestone ) ; } catch ( err ) { console . error ( 'Could not create milestone' , milestone . title ) ; console . error ( err ) ; } } else { console . log ( 'Already exists: ' + milestone . title ) ; } } } /**
 * Transfer any labels that exist in GitLab that do not exist in GitHub.
 */ async function transferLabels ( attachmentLabel = true , useLowerCase = true ) { inform ( 'Transferring Labels' ) ; let labels = await gitlabApi . Labels . all ( settings . gitlab . projectId ) ; let githubLabels = await githubHelper . getAllGithubLabelNames ( ) ; if ( attachmentLabel ) { const hasAttachmentLabel = { name : 'has attachment' , color : '#fbca04' , } ; labels . push ( hasAttachmentLabel ) ; } const gitlabMergeRequestLabel = { name : 'gitlab merge request' , color : '#b36b00' , } ; labels . push ( gitlabMergeRequestLabel ) ; for ( let label of labels ) { if ( useLowerCase ) { label . name = label . name . toLowerCase ( ) ; } if ( ! githubLabels . find ( l => l === label . name ) ) { console . log ( 'Creating: ' + label . name ) ; try { await githubHelper . createLabel ( label ) . catch ( x => { } ) ; } catch ( err ) { console . error ( 'Could not create label' , label . name ) ; console . error ( err ) ; } } else { console . log ( 'Already exists: ' + label . name ) ; } } } /**
 * Transfer any issues and their comments that exist in GitLab that do not exist in GitHub.
 */ async function transferIssues ( ) { inform ( 'Transferring Issues' ) ; let milestoneData = await githubHelper . getAllGithubMilestones ( ) ; let issues = ( await gitlabApi . Issues . all ( { projectId : settings . gitlab . projectId , labels : settings . filterByLabel , } ) ) as any [ ] ; issues = issues . sort ( ( a , b ) => a . iid - b . iid ) ; let githubIssues = await githubHelper . getAllGithubIssues ( ) ; console . log ( `Transferring ${ issues . length } issues.` ) ; if ( settings . usePlaceholderIssuesForMissingIssues ) { for ( let i = 0 ; i < issues . length ; i ++ ) { let expectedIdx = i + 1 ; if ( issues [ i ] . iid !== expectedIdx ) { issues . splice ( i , 0 , createPlaceholderIssue ( expectedIdx ) ) ; issueCounters . nrOfPlaceholderIssues ++ ; console . log ( `Added placeholder issue for GitLab issue #${ expectedIdx }.` ) ; } } } for ( let issue of issues ) { let githubIssue = githubIssues . find ( i => i . title . trim ( ) === issue . title . trim ( ) ) ; if ( ! githubIssue ) { console . log ( `\nMigrating issue #${ issue . iid } ('${ issue . title }')...` ) ; try { await githubHelper . createIssueAndComments ( milestoneData , issue ) ; console . log ( `\t...DONE migrating issue #${ issue . iid }.` ) ; } catch ( err ) { console . log ( `\t...ERROR while migrating issue #${ issue . iid }.` ) ; console . error ( 'DEBUG:\n' , err ) ; if ( settings . useReplacementIssuesForCreationFails ) { console . log ( '\t-> creating a replacement issue...' ) ; const replacementIssue = createReplacementIssue ( issue . iid , issue . title , issue . state ) ; try { await githubHelper . createIssueAndComments ( milestoneData , replacementIssue ) ; issueCounters . nrOfReplacementIssues ++ ; console . error ( '\t...DONE.' ) ; } catch ( err ) { issueCounters . nrOfFailedIssues ++ ; console . error ( '\t...ERROR: Could not create replacement issue either!' ) ; } } } } else { console . log ( `Updating issue #${ issue . iid } - ${ issue . title }...` ) ; try { await githubHelper . updateIssueState ( githubIssue , issue ) ; console . log ( `...Done updating issue #${ issue . iid }.` ) ; } catch ( err ) { console . log ( `...ERROR while updating issue #${ issue . iid }.` ) ; } } } console . log ( `DONE creating issues.` ) ; console . log ( `\n\tStatistics:` ) ; console . log ( `\tTotal nr. of issues: ${ issues . length }` ) ; console . log ( `\tNr. of used placeholder issues: ${ issueCounters . nrOfPlaceholderIssues }` ) ; console . log ( `\tNr. of used replacement issues: ${ issueCounters . nrOfReplacementIssues }` ) ; console . log ( `\tNr. of issue migration fails: ${ issueCounters . nrOfFailedIssues }` ) ; } returns { Promise < void > } async function transferMergeRequests ( ) { inform ( 'Transferring Merge Requests' ) ; let milestoneData = await githubHelper . getAllGithubMilestones ( ) ; let mergeRequests = ( await gitlabApi . MergeRequests . all ( { projectId : settings . gitlab . projectId , labels : settings . filterByLabel , } ) ) as any ; mergeRequests = mergeRequests . sort ( ( a , b ) => a . iid - b . iid ) ; let githubPullRequests = await githubHelper . getAllGithubPullRequests ( ) ; let githubIssues = await githubHelper . getAllGithubIssues ( ) ; console . log ( 'Transferring ' + mergeRequests . length . toString ( ) + ' merge requests' ) ; for ( let request of mergeRequests ) { let githubRequest = githubPullRequests . find ( i => i . title . trim ( ) === request . title . trim ( ) ) ; let githubIssue = githubIssues . find ( i => i . title . trim ( ) . includes ( request . title . trim ( ) ) ) ; if ( ! githubRequest && ! githubIssue ) { if ( settings . skipMergeRequestStates . includes ( request . state ) ) { console . log ( `Skipping MR ${ request . iid } in "${ request . state }" state: ${ request . title }` ) ; continue ; } console . log ( 'Creating pull request: !' + request . iid + ' - ' + request . title ) ; try { await githubHelper . createPullRequestAndComments ( milestoneData , request ) ; } catch ( err ) { console . error ( 'Could not create pull request: !' + request . iid + ' - ' + request . title ) ; console . error ( err ) ; } } else { if ( githubRequest ) { console . log ( 'Gitlab merge request already exists (as github pull request): ' + request . iid + ' - ' + request . title ) ; githubHelper . updatePullRequestState ( githubRequest , request ) ; } else { console . log ( 'Gitlab merge request already exists (as github issue): ' + request . iid + ' - ' + request . title ) ; } } } } returns { Promise < void > } async function transferReleases ( ) { inform ( 'Transferring Releases' ) ; let releases = await gitlabApi . Releases . all ( settings . gitlab . projectId ) as any ; releases = releases . sort ( ( a , b ) => { return ( new Date ( a . released_at ) as any ) - ( new Date ( b . released_at ) as any ) ; } ) ; console . log ( 'Transferring ' + releases . length . toString ( ) + ' releases' ) ; for ( let release of releases ) { let githubRelease = await githubHelper . getReleaseByTag ( release . tag_name ) ; if ( ! githubRelease ) { console . log ( 'Creating release: !' + release . name + ' - ' + release . tag_name ) ; try { await githubHelper . createRelease ( release . tag_name , release . name , release . description ) ; } catch ( err ) { console . error ( 'Could not create release: !' + release . name + ' - ' + release . tag_name ) ; console . error ( err ) ; } } else { console . log ( 'Gitlab release already exists (as github release): ' + githubRelease . data . name + ' - ' + githubRelease . data . tag_name ) ; } } } /**
 * logs merge requests that exist in GitLab to a file.
 */ async function logMergeRequests ( logFile ) { inform ( 'Logging Merge Requests' ) ; let mergeRequests = ( await gitlabApi . MergeRequests . all ( { projectId : settings . gitlab . projectId , labels : settings . filterByLabel , } ) ) as any ; mergeRequests = mergeRequests . sort ( ( a , b ) => a . id - b . id ) ; console . log ( 'Logging ' + mergeRequests . length . toString ( ) + ' merge requests' ) ; for ( let mergeRequest of mergeRequests ) { let mergeRequestDiscussions = await gitlabApi . MergeRequestDiscussions . all ( settings . gitlab . projectId , mergeRequest . iid ) ; let mergeRequestNotes = await gitlabApi . MergeRequestNotes . all ( settings . gitlab . projectId , mergeRequest . iid , { } ) ; mergeRequest . discussions = mergeRequestDiscussions ? mergeRequestDiscussions : [ ] ; mergeRequest . notes = mergeRequestNotes ? mergeRequestNotes : [ ] ; } const output = { mergeRequests : mergeRequests , } ; fs . writeFileSync ( logFile , JSON . stringify ( output , null , 2 ) ) ; } /**
 * Print out a section heading to let the user know what is happening
 */ function inform ( msg ) { console . log ( '==================================' ) ; console . log ( msg ) ; console . log ( '==================================' ) ; }