Skip to content

Commit e194fea

Browse files
committed
adding github discussions migration utility post
1 parent 23348b8 commit e194fea

File tree

4 files changed

+201
-1
lines changed

4 files changed

+201
-1
lines changed

_posts/2024-09-18-github-migration-tools.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Based on my migration experience, here are additional tools I've found useful, o
3030
| - Custom repo roles | [Analysis script](https://github.com/joshjohanning/github-misc-scripts/blob/main/gh-cli/get-organizations-custom-repository-roles-count.sh) | Any custom org roles will need to be migrated as well |
3131
| - Org level webhooks | [Analysis script (count)](https://github.com/joshjohanning/github-misc-scripts/blob/main/gh-cli/gh-cli/get-organizations-webhooks-count.sh),<br>[Analaysis script (detailed)](https://github.com/joshjohanning/github-misc-scripts/blob/main/gh-cli/get-organizations-webhooks.sh),<br>[gh-organization-webhooks](https://github.com/katiem0/gh-organization-webhooks) | Need to know what webhook secrets are, can't retrieve in UI/API |
3232
| - IP allow list | [Get org IP allow list](https://github.com/joshjohanning/github-misc-scripts/blob/main/gh-cli/get-organization-ip-allow-list.sh),<br>[Get enterprise IP allow list](https://github.com/joshjohanning/github-misc-scripts/blob/main/gh-cli/get-enterprise-ip-allow-list.sh),<br>[Set IP allow list rules for](https://github.com/joshjohanning/github-misc-scripts/blob/main/gh-cli/set-ip-allow-list-rules.sh) | The get scripts save rules to CSV and the set script sets them in target |
33-
| **Discussions** | [Analysis script (count) for each org](https://github.com/joshjohanning/github-misc-scripts/blob/main/gh-cli/get-organizations-discussions-count.sh),<br>[Analysis script (count) for each repo in an org](https://github.com/joshjohanning/github-misc-scripts/blob/main/gh-cli/get-organizations-repositories-discussions-count.sh) | Discussions exist in repos, but may have to configure which repo will be used for org discussions |
33+
| **Discussions** | [migrate-discussions script](/posts/github-discussions-migration-utility/),<br>[Analysis script (count) for each org](https://github.com/joshjohanning/github-misc-scripts/blob/main/gh-cli/get-organizations-discussions-count.sh),<br>[Analysis script (count) for each repo in an org](https://github.com/joshjohanning/github-misc-scripts/blob/main/gh-cli/get-organizations-repositories-discussions-count.sh) | Discussions exist in repos, but may have to configure which repo will be used for org discussions |
3434
| **Projects** | | |
3535
| - Projects v2 | [Analysis script](https://github.com/joshjohanning/github-misc-scripts/blob/main/gh-cli/get-organizations-projects-count.sh),<br>[gh-migrate-project](https://github.com/timrogers/gh-migrate-project?tab=readme-ov-file) | CLI utility can help migrate org-level projects |
3636
| - Org Projects (classic) | [Analysis script](https://github.com/joshjohanning/github-misc-scripts/blob/main/gh-cli/get-organizations-projects-count-classic.sh) | Deprecated |
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
---
2+
title: 'GitHub Discussions Migration Utility'
3+
author: Josh Johanning
4+
date: 2025-10-07 12:00:00 -0500
5+
description: A utility to migrate GitHub Discussions between repositories, including categories, labels, comments, and replies
6+
categories: [GitHub, Migrations]
7+
tags: [GitHub, Scripts, GitHub Discussions, Migrations, Node.js]
8+
media_subpath: /assets/screenshots/2025-10-07-github-discussions-migration-utility
9+
image:
10+
path: github-discussions-light.png
11+
width: 100%
12+
height: 100%
13+
alt: GitHub Discussions in a repository
14+
---
15+
16+
## Overview
17+
18+
GitHub Discussions are a great way to facilitate conversations within your repository or organization, but what happens when you need to move them? Whether you're migrating between GitHub instances (server to cloud, cloud to cloud, etc.), moving discussions to a different organization, or consolidating repositories across organizations, moving discussions manually can be a tedious process. Today, the GitHub Enterprise Importer (GEI) [does not support](https://docs.github.com/en/enterprise-cloud@latest/migrations/using-github-enterprise-importer/migrating-between-github-products/about-migrations-between-github-products#data-that-is-not-migrated-1) migrating discussions, so I built a [Node.js utility](https://github.com/joshjohanning/github-misc-scripts/blob/main/scripts/migrate-discussions/migrate-discussions.js) ([README](https://github.com/joshjohanning/github-misc-scripts/blob/main/scripts/migrate-discussions/README.md)) to help with this task.
19+
20+
The utility handles discussions, comments, replies, categories, labels, and even preserves metadata like reactions and poll results. It includes intelligent rate limiting and resume capabilities to handle large migrations reliably. I should note, "reliably" does not mean quickly - GitHub has strict rate limits on content-creating operations to prevent abuse, so patience is key when migrating large numbers of discussions. (See [rate limiting details](#rate-limiting-details) below for more information.)
21+
22+
> See my [GitHub Migration Tools Collection](/posts/github-migration-tools/) post.
23+
{: .prompt-info }
24+
25+
> If you're moving discussions within the same organization, use GitHub's native [transfer discussion](https://docs.github.com/en/discussions/managing-discussions-for-your-community/managing-discussions#transferring-a-discussion) feature instead. This utility is best for cross-organization or cross-instance migrations, or when you need to automate transfers (since there's no GraphQL endpoint for the native transfer feature).
26+
{: .prompt-tip }
27+
28+
## Running the Script
29+
30+
### Prerequisites
31+
32+
1. A clone of my [github-misc-scripts](https://github.com/joshjohanning/github-misc-scripts) repo
33+
2. Node.js installed
34+
3. Both source and target repositories must have GitHub Discussions enabled
35+
4. GitHub tokens with appropriate permissions:
36+
- Source token: `repo` scope with read access to discussions
37+
- Target token: `repo` scope with write access to discussions
38+
- GitHub App tokens recommended for better rate limits (but note they expire after 1 hour - see [Future Improvements](#future-improvements))
39+
5. Dependencies installed via `npm i`
40+
41+
You can generate a GitHub App token using the [`gh-token`](https://github.com/Link-/gh-token) extension:
42+
43+
```bash
44+
export SOURCE_TOKEN=$(gh token generate --app-id YOUR_APP_ID --installation-id YOUR_INSTALLATION_ID --key /path/to/private-key.pem --token-only)
45+
export TARGET_TOKEN=$(gh token generate --app-id YOUR_APP_ID --installation-id YOUR_INSTALLATION_ID --key /path/to/private-key.pem --token-only)
46+
```
47+
{: .nolineno}
48+
49+
### Usage
50+
51+
You can call the script via:
52+
53+
```bash
54+
cd github-misc-scripts
55+
export SOURCE_TOKEN=ghp_abc
56+
export TARGET_TOKEN=ghp_xyz
57+
cd ./scripts/migrate-discussions
58+
npm i
59+
node ./migrate-discussions.js source-org source-repo target-org target-repo
60+
```
61+
{: .nolineno}
62+
63+
To resume from a specific discussion number (useful if interrupted):
64+
65+
```bash
66+
node ./migrate-discussions.js source-org source-repo target-org target-repo --start-from 50
67+
```
68+
{: .nolineno}
69+
70+
### Example
71+
72+
An example of this in practice:
73+
74+
```bash
75+
export SOURCE_TOKEN=ghp_abc
76+
export TARGET_TOKEN=ghp_xyz
77+
cd ./scripts/migrate-discussions
78+
npm i
79+
node ./migrate-discussions.js joshjohanning-org discussions-source joshjohanning-org discussions-target
80+
```
81+
{: .nolineno}
82+
83+
For GitHub Enterprise Server instances, set the API URL environment variables:
84+
85+
```bash
86+
export SOURCE_API_URL=https://github.mycompany.com/api/v3
87+
export TARGET_API_URL=https://api.github.com
88+
export SOURCE_TOKEN=ghp_abc
89+
export TARGET_TOKEN=ghp_xyz
90+
node ./migrate-discussions.js source-org source-repo target-org target-repo
91+
```
92+
{: .nolineno}
93+
94+
## Features
95+
96+
The script migrates discussions with comprehensive support for:
97+
98+
### Content Migration
99+
100+
- **Discussion categories** - Automatically creates missing categories in the target repository (or uses "General" as fallback)
101+
- **Labels** - Creates labels in the target repository if they don't exist
102+
- **Comments and replies** - Copies all comments and threaded replies with proper attribution
103+
- **Poll results** - Copies poll results as static snapshots with tables and optional Mermaid charts
104+
- **Reactions** - Preserves reaction counts on discussions, comments, and replies
105+
- **Discussion states** - Maintains locked and closed status
106+
- **Answered discussions** - Marks answered discussions and preserves the accepted answer
107+
- **Pinned discussions** - Indicates pinned discussions with a visual indicator (GraphQL API doesn't support pinning)
108+
109+
### Rate Limiting and Reliability
110+
111+
- **Automatic rate limit handling** - Uses Octokit's built-in throttling plugin with retry logic
112+
- **GitHub-recommended delays** - Waits 3 seconds between discussions/comments to stay under secondary rate limits
113+
- **Resume capability** - Use `--start-from <number>` to resume from a specific discussion if interrupted
114+
- **Configurable retries** - Retries up to 15 times for both rate-limit and non-rate-limit errors
115+
116+
## Summary Output
117+
118+
After completion, the script displays comprehensive statistics:
119+
120+
```text
121+
[2025-10-02 19:38:44] ============================================================
122+
[2025-10-02 19:38:44] Discussion copy completed!
123+
[2025-10-02 19:38:44] Total discussions found: 10
124+
[2025-10-02 19:38:44] Discussions created: 10
125+
[2025-10-02 19:38:44] Discussions skipped: 0
126+
[2025-10-02 19:38:44] Total comments found: 9
127+
[2025-10-02 19:38:44] Comments copied: 9
128+
[2025-10-02 19:38:44] Primary rate limits hit: 0
129+
[2025-10-02 19:38:44] Secondary rate limits hit: 0
130+
[2025-10-02 19:38:44] WARNING:
131+
The following categories were missing and need to be created manually:
132+
[2025-10-02 19:38:44] WARNING: - Blog posts!
133+
[2025-10-02 19:38:44] WARNING:
134+
[2025-10-02 19:38:44] WARNING: To create categories manually:
135+
[2025-10-02 19:38:44] WARNING: 1. Go to https://github.com/joshjohanning-emu/discussions-test/discussions
136+
[2025-10-02 19:38:44] WARNING: 2. Click 'New discussion'
137+
[2025-10-02 19:38:44] WARNING: 3. Look for category management options
138+
[2025-10-02 19:38:44] WARNING: 4. Create the missing categories with appropriate names and descriptions
139+
[2025-10-02 19:38:44]
140+
All done! ✨
141+
```
142+
143+
## Limitations & Notes
144+
145+
### What It Doesn't Do
146+
147+
While the utility handles most discussion migration scenarios, there are some limitations:
148+
149+
- **Discussion categories** - Cannot create categories via API; must be created manually in the target repository beforehand (discussions will use "General" as fallback)
150+
- **Pinning discussions** - GitHub API doesn't allow pinning via GraphQL; pinned status is indicated in the discussion body
151+
- **Live polls** - Poll results are copied as static snapshots; users cannot vote on migrated polls
152+
- **Attachments** - Images and files referenced in discussions may not copy over and require manual handling
153+
- **Reactions** - Copied as read-only summaries; users cannot add new reactions to migrated content
154+
155+
### Category Handling
156+
157+
- Discussion categories must exist in the target repository before running the script
158+
- If a category doesn't exist, discussions will be created in the "General" category as a fallback
159+
- Missing categories are tracked and reported at the end of the script
160+
161+
### Rate Limiting Details
162+
163+
GitHub limits content-generating requests to avoid abuse:
164+
165+
- No more than 80 content-generating requests per minute
166+
- No more than 500 content-generating requests per hour
167+
168+
The script automatically handles this by:
169+
170+
- Staying under 1 discussion or comment created every 3 seconds (GitHub's recommendation)
171+
- Automatic retry with wait times from GitHub's `retry-after` headers
172+
- Retrying up to 15 times if rate limits are consistently hit
173+
174+
### Configuration Options
175+
176+
You can edit these constants at the top of the script to customize behavior:
177+
178+
- `INCLUDE_POLL_MERMAID_CHART` - Set to `false` to disable Mermaid pie charts for polls (default: `true`)
179+
- `RATE_LIMIT_SLEEP_SECONDS` - Sleep duration between API calls (default: `0.5` seconds)
180+
- `DISCUSSION_PROCESSING_DELAY_SECONDS` - Delay between processing discussions/comments (default: `3` seconds)
181+
- `MAX_RETRIES` - Maximum retries for both rate-limit and non-rate-limit errors (default: `15`)
182+
183+
### Content Preservation
184+
185+
- The script adds attribution text to preserve original author and timestamp information (the API doesn't allow setting creation date or author metadata - all migrated content will show as created by the token user)
186+
- Poll results are copied as static snapshots - voting is not available in copied discussions
187+
- Reactions are copied as read-only summaries
188+
- Locked discussions will be locked in the target repository
189+
- Closed discussions will be closed in the target repository
190+
- Answered discussions will have the same comment marked as the answer
191+
192+
## Future Improvements
193+
194+
- [ ] Native support for GitHub Apps and ability to fetch new tokens before they expire in an hour
195+
196+
## Summary
197+
198+
This utility makes it possible to migrate GitHub Discussions between repositories, whether you're moving between GitHub instances or just consolidating your discussions. The script handles the heavy lifting of copying discussions, comments, and metadata while respecting GitHub's rate limits.
199+
200+
Drop a comment here or an issue or PR on my [github-misc-scripts repo](https://github.com/joshjohanning/github-misc-scripts/tree/main/scripts/migrate-discussions) if you have any feedback or suggestions! Happy migrating! 🚀
158 KB
Loading
161 KB
Loading

0 commit comments

Comments
 (0)