├── .env.example ├── .gitignore ├── Cakefile ├── README.md ├── lib ├── git.coffee ├── github.coffee └── gitlab.coffee ├── license.md └── package.json /.env.example: -------------------------------------------------------------------------------- 1 | GITLAB_URL= 2 | GITLAB_GIT_URL= 3 | GITLAB_TOKEN= 4 | GITHUB_ORG= 5 | GITHUB_TEAM_ID= 6 | GITHUB_USERNAME= 7 | GITHUB_TOKEN= 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | -------------------------------------------------------------------------------- /Cakefile: -------------------------------------------------------------------------------- 1 | Git = require './lib/git' 2 | GitLab = require './lib/gitlab' 3 | GitHub = require './lib/github' 4 | 5 | option '-i', '--gitlab_id [ID]', 'GitLab project id' 6 | option '-l', '--gitlab_name [NAME]', 'GitLab repo name' 7 | option '-h', '--github_name [NAME]', 'GitHub repo name' 8 | 9 | task 'migrate:repo', 'migrate repository', (options) -> 10 | Git.migrateRepo(options) 11 | 12 | task 'migrate:wiki', 'migrate wiki', (options) -> 13 | Git.migrateWiki(options) 14 | 15 | task 'migrate:issues', 'migrate issues', (options) -> 16 | { gitlab_id, github_name } = options 17 | GitLab.getIssues(gitlab_id) 18 | .then (issues) -> 19 | for issue in issues 20 | do (issue) -> 21 | GitLab.getComments(gitlab_id, issue.id) 22 | .then (comments) -> 23 | GitHub.createIssue(github_name, issue, comments) 24 | 25 | task 'migrate:milestones', 'migrate milestones', (options) -> 26 | { gitlab_id, github_name } = options 27 | GitHub.deleteMilestones(github_name) 28 | GitLab.getMilestones(gitlab_id) 29 | .then (milestones) -> 30 | for milestone in milestones 31 | do (milestone) -> 32 | GitHub.createMilestone(github_name, milestone) 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # GitLab to GitHub migration 3 | 4 | Migrate repositories, wikis, issues and milestones from GitLab to GitHub. 5 | 6 | ## Install 7 | 8 | 1. Set environment variables in `.env` 9 | * `GITLAB_URL` - The URL of your local GitLab instance e.g. `http://GitLab.local` 10 | * `GITLAB_GIT_URL` - The Git URL of your local GitLab instance e.g. `git@GitLab.local` 11 | * `GITLAB_TOKEN` - Your [GitLab API token](http://git.wz/profile/account) 12 | * `GITHUB_ORG` - Your GitHub organization name e.g. `wollzelle` 13 | * `GITHUB_TEAM_ID` - The id of the GitHub team that will be granted access to this repository 14 | * `GITHUB_USERNAME` - Your GitHub username e.g. `meleyal` 15 | * `GITHUB_TOKEN` - Your [GitHub API token](https://github.com/settings/tokens) 16 | 17 | 3. Install dependencies with `npm install` and `npm install -g coffee-script` 18 | 19 | 4. List available tasks with `cake` 20 | 21 | ## Examples 22 | 23 | cake -i 6 -l websites/my-website -h my-website migrate:repo 24 | cake -i 6 -l websites/my-website -h my-website migrate:wiki 25 | cake -i 6 -l websites/my-website -h my-website migrate:issues 26 | cake -i 6 -l websites/my-website -h my-website migrate:milestones 27 | 28 | ## Notes 29 | 30 | * `gitlab_id` can be found by inspecting the `body` tag of the GitLab project page (e.g. `data-project-id="84"`), or via the GitLab API. 31 | 32 | * `GITHUB_TEAM_ID` can be found via `api.github.com/teams` (requires org owner rights). 33 | 34 | * `migrate:wiki` requires that the GitHub wiki must be created first (it's enough to create a single blank page). 35 | -------------------------------------------------------------------------------- /lib/git.coffee: -------------------------------------------------------------------------------- 1 | require('dotenv').load() 2 | require 'shelljs/global' 3 | _ = require 'underscore' 4 | GitHub = require './github' 5 | 6 | Git = module.exports = 7 | 8 | migrateRepo: (options) -> 9 | { gitlab_name, github_name } = options 10 | { GITLAB_GIT_URL, GITHUB_ORG } = process.env 11 | 12 | repo = "#{_.last(gitlab_name.split('/'))}.git" 13 | gitlabRemote = "#{GITLAB_GIT_URL}:#{gitlab_name}.git" 14 | githubRemote = "git@github.com:#{GITHUB_ORG}/#{github_name}.git" 15 | 16 | rm "-rf", repo 17 | exec "git clone --mirror #{gitlabRemote}" 18 | cd repo 19 | exec "git remote add github #{githubRemote}" 20 | 21 | GitHub.createRepo(github_name) 22 | .then -> 23 | exec "git push github --mirror" 24 | rm "-rf", repo 25 | .catch (err) -> console.log err.error 26 | 27 | migrateWiki: (options) -> 28 | { gitlab_name, github_name } = options 29 | { GITLAB_GIT_URL, GITHUB_ORG } = process.env 30 | 31 | repo = "#{_.last(gitlab_name.split('/'))}.git" 32 | gitlabRemote = "#{GITLAB_GIT_URL}:#{gitlab_name}.wiki.git" 33 | githubRemote = "git@github.com:#{GITHUB_ORG}/#{github_name}.wiki.git" 34 | 35 | rm "-rf", repo 36 | exec "git clone #{gitlabRemote} #{repo}" 37 | cd repo 38 | exec "git remote add github #{githubRemote}" 39 | exec "git push github --force" 40 | rm "-rf", repo 41 | -------------------------------------------------------------------------------- /lib/github.coffee: -------------------------------------------------------------------------------- 1 | require('dotenv').load() 2 | request = require 'request-promise' 3 | requestJSON = request.defaults(headers: { 'User-Agent': "#{process.env.GITHUB_ORG}" }, json: true) 4 | 5 | GitHub = module.exports = 6 | 7 | req: (method, path, data = {}) -> 8 | { GITHUB_ORG, GITHUB_USERNAME, GITHUB_TOKEN } = process.env 9 | url = "https://#{GITHUB_USERNAME}:#{GITHUB_TOKEN}@api.github.com/repos/#{GITHUB_ORG}/#{path}" 10 | requestJSON[method] url, { body: data } 11 | 12 | get: (path) -> 13 | GitHub.req 'get', path 14 | 15 | post: (path, data) -> 16 | GitHub.req 'post', path, data 17 | 18 | patch: (path, data) -> 19 | GitHub.req 'patch', path, data 20 | 21 | destroy: (path) -> 22 | GitHub.req 'del', path 23 | 24 | createRepo: (projectName) -> 25 | { GITHUB_ORG, GITHUB_USERNAME, GITHUB_TOKEN, GITHUB_TEAM_ID } = process.env 26 | url = "https://#{GITHUB_USERNAME}:#{GITHUB_TOKEN}@api.github.com/orgs/#{GITHUB_ORG}/repos" 27 | requestJSON.post url, { body: { name: projectName, private: true, team_id: GITHUB_TEAM_ID } } 28 | 29 | createIssue: (projectName, issue, comments) -> 30 | delete issue.milestone 31 | delete issue.assignee 32 | issue.state = 'open' if issue.state in ['opened', 'reopened'] 33 | issue.body = issue.description 34 | 35 | GitHub.post "#{projectName}/issues", issue 36 | .then (newIssue) -> 37 | if issue.state is 'closed' 38 | GitHub.patch "#{projectName}/issues/#{newIssue.number}", { state: 'closed' } 39 | for comment in comments 40 | GitHub.post "#{projectName}/issues/#{newIssue.number}/comments", comment 41 | 42 | createMilestone: (projectName, milestone) -> 43 | return if milestone.state is 'closed' 44 | milestone.state = 'open' if milestone.state is 'active' 45 | delete milestone.due_date 46 | GitHub.post "#{projectName}/milestones", milestone 47 | 48 | deleteMilestones: (projectName) -> 49 | GitHub.get "#{projectName}/milestones?state=all" 50 | .then (milestones) -> 51 | for milestone in milestones 52 | do (milestone) -> 53 | GitHub.destroy "#{projectName}/milestones/#{milestone.number}" 54 | -------------------------------------------------------------------------------- /lib/gitlab.coffee: -------------------------------------------------------------------------------- 1 | require('dotenv').load() 2 | request = require 'request-promise' 3 | requestJSON = request.defaults(json: true) 4 | 5 | GitLab = module.exports = 6 | get: (path) -> 7 | { GITLAB_URL, GITLAB_TOKEN } = process.env 8 | url = "#{GITLAB_URL}/api/v3/#{path}?private_token=#{GITLAB_TOKEN}&per_page=1000" 9 | requestJSON.get url 10 | 11 | getIssues: (projectId) -> 12 | GitLab.get "projects/#{projectId}/issues" 13 | 14 | getComments: (projectId, issueId) -> 15 | GitLab.get "projects/#{projectId}/issues/#{issueId}/notes" 16 | 17 | getMilestones: (projectId) -> 18 | GitLab.get "projects/#{projectId}/milestones" 19 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Wollzelle GmbH. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gitlab-github-migrate", 3 | "version": "1.0.0", 4 | "description": "Migrate repositories, wikis, issues and milestones from GitLab to GitHub", 5 | "main": "Cakefile", 6 | "scripts": { 7 | "start": "cake" 8 | }, 9 | "author": "William Meleyal ", 10 | "license": "MIT", 11 | "repository": "https://github.com/wollzelle/gitlab-github-migrate", 12 | "dependencies": { 13 | "coffee-script": "^1.9.3", 14 | "dotenv": "^1.2.0", 15 | "request-promise": "^0.4.3", 16 | "shelljs": "^0.5.1", 17 | "underscore": "^1.8.3" 18 | } 19 | } 20 | --------------------------------------------------------------------------------