Skip to content

Commit d527b88

Browse files
feat: add discussions-migration script (#124)
* feat: add copy-discussions shell script * feat: add copy-discussions node script * feat: add functionality to close discussions in target repository if closed in source * feat: add functionality to mark discussion comments as answers during copy * feat: add support for copying poll data and visual representation in discussions * fix: incomplete string escaping Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * docs: update README to include detailed usage and features for copy-discussions.js * fix: ensure linting commands do not fail the workflow by adding '|| true' * Revert "docs: update README to include detailed usage and features for copy-discussions.js" This reverts commit ee1e0cc. * docs: enhance README for copy-discussions.js with usage instructions and features * feat: enhance discussion copying by preserving reactions, locked status, and pinned indicators * Revert "fix: ensure linting commands do not fail the workflow by adding '|| true'" This reverts commit 2df58c9. * fix: correct formatting of metadata sections in discussions and comments also noting pinned discussions * fix: update script documentation to clarify copied discussion elements * feat: add copy-discussions.sh script for transferring discussions between repositories * Revert "feat: add copy-discussions.sh script for transferring discussions between repositories" This reverts commit 02cd024. * refactor: remove copy-discussions.sh script for transferring discussions between repositories use the ./scripts/copy-discussions.js instead * fix: enhance help message for copy-discussions.js script * fix: improve help message and clarify environment variable usage in copy-discussions.js * feat: configurable rate limit * fix: reduce rate limit sleep duration and improve discussion processing efficiency * feat: add support for resuming discussion copying with --start-from option * fix: implement retry logic for discussion creation to handle rate limits * docs: update documentation to clarify secondary rate limit guidelines and request limits * feat: enhance rate limit handling with Octokit throttling and remove manual retry logic * fix: adjust discussion processing delay to comply with GitHub's rate limit recommendations * feat: track primary and secondary rate limit hits for improved monitoring * refactor(scripts)!: reorganize and rename copy-discussions to migrate-discussions - Move script to migrate-discussions/ subdirectory structure - Rename from copy-discussions.js to migrate-discussions.js - Update documentation to reflect migration terminology - Add package.json with octokit dependency - Enhance README with rate limiting and resume features - Update .gitignore to allow nested package.json files * docs: update README to clarify npm installation steps and environment variable usage * docs: update README to enhance GitHub App usage instructions and clarify token recommendations * docs: update README and script to enhance rate limit handling and retry logic * fix: update script usage examples to reflect correct script name --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
1 parent 4dea8a1 commit d527b88

File tree

6 files changed

+2222
-0
lines changed

6 files changed

+2222
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
**/.DS_Store
44
*.pem
55
*.json
6+
!/scripts/*/package*.json
67
node_modules*
78
test*.js
89
test*.sh

scripts/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ My use case is to use this list to determine who needs to be added to a organiza
9494
1. Run: `./new-users-to-add-to-project.sh <org> <repo> <file>`
9595
2. Don't delete the `<file>` as it functions as your user database
9696

97+
## migrate-discussions
98+
99+
See: [migrate-discussions](./migrate-discussions/README.md)
100+
97101
## migrate-docker-containers-between-github-instances.sh
98102

99103
Migrate Docker Containers in GitHub Packages (GitHub Container Registry) from one GitHub organization to another.
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# migrate-discussions.js
2+
3+
Migrate GitHub Discussions between repositories, including categories, labels, comments, and replies. This script can migrate discussions across different GitHub instances and enterprises with comprehensive rate limit handling and resume capabilities.
4+
5+
## Prerequisites
6+
7+
- `SOURCE_TOKEN` environment variable with GitHub PAT that has `repo` scope and read access to source repository discussions
8+
- Alternatively, use a GitHub App token (recommended for better rate limits and security)
9+
- `TARGET_TOKEN` environment variable with GitHub PAT that has `repo` scope and write access to target repository discussions
10+
- Alternatively, use a GitHub App token (recommended for better rate limits, security, and authorship) (✨ **recommended for target token!**)
11+
- Dependencies installed via `npm i`
12+
- Both source and target repositories must have GitHub Discussions enabled
13+
14+
### Using a GitHub App (Recommended)
15+
16+
GitHub Apps provide better rate limits and security compared to personal access tokens. To use a GitHub App:
17+
18+
1. Create or use an existing GitHub App with `repo` permissions
19+
2. Install the app on the source and/or target repositories
20+
3. Generate a token using the GitHub CLI and [`gh-token`](https://github.com/Link-/gh-token) extension:
21+
22+
```bash
23+
export SOURCE_TOKEN=$(gh token generate --app-id YOUR_SOURCE_APP_ID --installation-id YOUR_SOURCE_INSTALLATION_ID --key /path/to/source/private-key.pem --token-only)
24+
export TARGET_TOKEN=$(gh token generate --app-id YOUR_TARGET_APP_ID --installation-id YOUR_TARGET_INSTALLATION_ID --key /path/to/target/private-key.pem --token-only)
25+
```
26+
27+
## Script usage
28+
29+
Basic usage:
30+
31+
```bash
32+
export SOURCE_TOKEN=ghp_abc
33+
export TARGET_TOKEN=ghp_xyz
34+
# export SOURCE_TOKEN=$(gh token generate --app-id YOUR_SOURCE_APP_ID --installation-id YOUR_SOURCE_INSTALLATION_ID --key /path/to/source/private-key.pem --token-only)
35+
# export TARGET_TOKEN=$(gh token generate --app-id YOUR_TARGET_APP_ID --installation-id YOUR_TARGET_INSTALLATION_ID --key /path/to/target/private-key.pem --token-only)
36+
# export SOURCE_API_URL= # if GHES
37+
# export TARGET_API_URL= # if GHES/ghe.com
38+
cd ./scripts/migrate-discussions
39+
npm i
40+
node ./migrate-discussions.js source-org source-repo target-org target-repo
41+
```
42+
43+
Resume from a specific discussion number (useful if interrupted):
44+
45+
```bash
46+
node ./migrate-discussions.js source-org source-repo target-org target-repo --start-from 50
47+
```
48+
49+
## Optional environment variables
50+
51+
- `SOURCE_API_URL` - API endpoint for source (defaults to `https://api.github.com`)
52+
- `TARGET_API_URL` - API endpoint for target (defaults to `https://api.github.com`)
53+
54+
Example with GitHub Enterprise Server:
55+
56+
```bash
57+
export SOURCE_API_URL=https://github.mycompany.com/api/v3
58+
export TARGET_API_URL=https://api.github.com
59+
export SOURCE_TOKEN=ghp_abc
60+
export TARGET_TOKEN=ghp_xyz
61+
node ./migrate-discussions.js source-org source-repo target-org target-repo
62+
```
63+
64+
## Features
65+
66+
### Content Migration
67+
68+
- Automatically creates missing discussion categories in the target repository
69+
- Creates labels in the target repository if they don't exist
70+
- Copies all comments and threaded replies with proper attribution
71+
- Copies poll results as static snapshots (with table and optional Mermaid chart)
72+
- Preserves reaction counts on discussions, comments, and replies
73+
- Maintains locked status of discussions
74+
- Indicates pinned discussions with a visual indicator
75+
- Marks answered discussions and preserves the accepted answer
76+
77+
### Rate limiting and reliability
78+
79+
- **Automatic rate limit handling** with Octokit's built-in throttling plugin
80+
- **Intelligent retry logic** with configurable retries for both rate-limit and non-rate-limit errors
81+
- **GitHub-recommended delays** - 3 seconds between discussions/comments to stay under secondary rate limits
82+
- **Resume capability** - Use `--start-from <number>` to resume from a specific discussion if interrupted
83+
- **Rate limit tracking** - Summary shows how many times primary and secondary rate limits were hit
84+
85+
### User experience
86+
87+
- Colored console output with timestamps for better visibility
88+
- Comprehensive summary statistics at completion
89+
- Detailed progress logging for each discussion, comment, and reply
90+
91+
## Configuration options
92+
93+
Edit these constants at the top of the script:
94+
95+
- `INCLUDE_POLL_MERMAID_CHART` - Set to `false` to disable Mermaid pie charts for polls (default: `true`)
96+
- `RATE_LIMIT_SLEEP_SECONDS` - Sleep duration between API calls (default: `0.5` seconds)
97+
- `DISCUSSION_PROCESSING_DELAY_SECONDS` - Delay between processing discussions/comments (default: `3` seconds)
98+
- `MAX_RETRIES` - Maximum retries for both rate-limit and non-rate-limit errors (default: `15`)
99+
100+
## Summary output
101+
102+
After completion, the script displays comprehensive statistics:
103+
104+
- Total discussions found and created
105+
- Discussions skipped (when using `--start-from`)
106+
- Total comments found and copied
107+
- **Primary rate limits hit** - How many times the script hit GitHub's primary rate limit
108+
- **Secondary rate limits hit** - How many times the script hit GitHub's secondary rate limit
109+
- List of missing categories that need manual creation
110+
111+
### Example summary output
112+
113+
```text
114+
[2025-10-02 19:38:44] ============================================================
115+
[2025-10-02 19:38:44] Discussion copy completed!
116+
[2025-10-02 19:38:44] Total discussions found: 10
117+
[2025-10-02 19:38:44] Discussions created: 10
118+
[2025-10-02 19:38:44] Discussions skipped: 0
119+
[2025-10-02 19:38:44] Total comments found: 9
120+
[2025-10-02 19:38:44] Comments copied: 9
121+
[2025-10-02 19:38:44] Primary rate limits hit: 0
122+
[2025-10-02 19:38:44] Secondary rate limits hit: 0
123+
[2025-10-02 19:38:44] WARNING:
124+
The following categories were missing and need to be created manually:
125+
[2025-10-02 19:38:44] WARNING: - Blog posts!
126+
[2025-10-02 19:38:44] WARNING:
127+
[2025-10-02 19:38:44] WARNING: To create categories manually:
128+
[2025-10-02 19:38:44] WARNING: 1. Go to https://github.com/joshjohanning-emu/discussions-test/discussions
129+
[2025-10-02 19:38:44] WARNING: 2. Click 'New discussion'
130+
[2025-10-02 19:38:44] WARNING: 3. Look for category management options
131+
[2025-10-02 19:38:44] WARNING: 4. Create the missing categories with appropriate names and descriptions
132+
[2025-10-02 19:38:44]
133+
All done! ✨
134+
```
135+
136+
## Notes
137+
138+
### Category handling
139+
140+
- If a category doesn't exist in the target repository, discussions will be created in the "General" category as a fallback
141+
- Missing categories are tracked and reported at the end of the script
142+
143+
### Content preservation
144+
145+
- The script preserves discussion metadata by adding attribution text to the body and comments
146+
- Poll results are copied as static snapshots - voting is not available in copied discussions
147+
- Reactions are copied as read-only summaries (users cannot add new reactions to the migrated content)
148+
- Attachments (images and files) will not copy over and require manual handling
149+
150+
### Discussion states
151+
152+
- Locked discussions will be locked in the target repository
153+
- Closed discussions will be closed in the target repository
154+
- Answered discussions will have the same comment marked as the answer
155+
- Pinned status is indicated in the discussion body (GitHub API doesn't allow pinning via GraphQL)
156+
157+
### Rate limiting
158+
159+
- GitHub limits content-generating requests to avoid abuse
160+
- No more than 80 content-generating requests per minute
161+
- No more than 500 content-generating requests per hour
162+
- The script stays under 1 discussion or comment created every 3 seconds (GitHub's recommendation)
163+
- Automatic retry with wait times from GitHub's `retry-after` headers
164+
- If rate limits are consistently hit, the script will retry up to 15 times before failing
165+
166+
### Resume capability
167+
168+
- Use `--start-from <number>` to skip discussions before a specific discussion number
169+
- Useful for resuming after interruptions or failures
170+
- Discussion numbers are the user-friendly numbers (e.g., #50), not GraphQL IDs

0 commit comments

Comments
 (0)