#+TITLE: GitHub to Codeberg Migration Script
#+AUTHOR: Rahul Martim Juliato
#+EMAIL: rahul.juliato@gmail.com
#+OPTIONS: toc:nil
#+begin_quote
[!NOTE]
The Rust TUI addition was built with heavy LLM assistance and should be
considered proof-of-concept. Contributions are welcome, but I can't
guarantee the accuracy of the code or that I will continue to maintain
it. If there are any mistakes in attribution or anything else, please
let me know.
#+end_quote
* Overview
This script automates the migration of GitHub repositories to
Codeberg. It allows you to seamlessly transfer your projects from
GitHub to Codeberg while preserving their metadata, such as
descriptions and access permissions.
The Rust TUI provides a two-step workflow for users who want to copy
repositories to Codeberg while keeping GitHub remotes available locally.
It never removes code from GitHub.
* Features
- Migrate all or selected repositories from GitHub to Codeberg.
- Customizable prefix for repository descriptions.
- Customizable filter for repository owners.
- Supports pagination for fetching large numbers of repositories.
- Error handling for repository migration failures.
* Limitations
Due to the complex nature of both Github and Codeberg apis, it was a
design choice for this script to omit several features like:
- distinction between forks and original repositories
- wiki
- pull requests
- avatars for projects
...and so on. Please modify this script to your own needs.
* Usage
** Rust TUI
Set the API tokens in your shell. Usernames are optional when the
tokens can be used to detect them.
#+BEGIN_SRC sh
export GITHUB_TOKEN=
export CODEBERG_TOKEN=
export GITHUB_USERNAME=
export CODEBERG_USERNAME=
cargo run -- --check --dir "$HOME/dev"
cargo run -- --export inventory.json --dir "$HOME/dev"
cargo run -- --dir "$HOME/dev"
#+END_SRC
The TUI shows one unified table. Rows are merged from local Git repos
under the selected directory, GitHub repositories visible to the token,
and Codeberg repositories visible to the token. The ~⌂~, ~◆~, and ~●~
columns show whether a repo exists locally, on GitHub, and on Codeberg.
Local paths are shown relative to the selected directory, and top-level
local repos do not repeat the directory path.
If a required value is missing, the TUI opens a small configuration
form for the GitHub user, GitHub token, Codeberg user, and Codeberg
token. Press ~e~ to reopen it later, or click ~GitHub [user]~ or
~Codeberg [user]~ in the header to edit the corresponding owner.
Press ~Tab~ to move between fields and ~Enter~ to save. Values are kept
in memory for the current run.
Press ~Enter~ or ~m~ to process selected rows. If a selected repo is not
on Codeberg yet, the TUI starts the Codeberg copy first. For local rows,
the origin update is deferred until the copy succeeds or Codeberg
already reports that the repo exists. It will not set a local ~origin~
to a Codeberg URL that is not known to exist.
Other keys: ~Space~ toggles one repo, ~v~ sets a range anchor, ~V~
toggles the anchor-to-cursor range, ~a~ toggles all repos, ~s~ cycles
sort order (~local first~, ~recent~, ~name asc~, ~name desc~), ~k~ runs
the non-mutating access check, ~r~ reloads local/API state, and ~q~
quits.
If the GitHub API listing does not include a local repo's GitHub
remote, the row is actionable only when the remote owner matches your
GitHub username. Remotes owned by someone else are marked for review
instead of copied automatically.
Local repos without a GitHub remote can be selected too. The TUI
creates a private empty Codeberg repository with the local directory
name, adds Codeberg as ~origin~ when there is no conflicting origin, and
pushes local branches and tags. Codeberg push-to-create is disabled, so
the repository is created through the API before Git pushes.
Rows with an ~upstream~ remote remain actionable, but are flagged as
~upstream remote~ so forks and vendored clones are easier to review.
Extra remotes are not removed.
The ~--export~ command writes a JSON inventory with the same rows used
by the TUI, plus remotes, local paths, GitHub metadata, Codeberg target
URLs, recommendations, risk flags, and load messages. Use it to review
ambiguous repos before starting migration:
#+BEGIN_SRC sh
cargo run -- --export inventory.json --dir "$HOME/dev"
#+END_SRC
The ~--check~ command and ~k~ key verify the non-mutating prerequisites:
GitHub token presence, GitHub identity, GitHub repository listing,
Codeberg token presence, Codeberg identity, Codeberg repository
listing, and local directory scanning. They do not call the Codeberg
migration endpoint because that endpoint creates remote state.
Optional environment variables:
- ~MIGRATE_PROJECT_DIR~: default directory when ~--dir~ is omitted.
- ~MIGRATE_EXCLUDE~: comma-separated relative path prefixes to skip
during local scanning. The CLI also accepts repeated ~--exclude~
flags.
- ~MIGRATE_CONCURRENCY~: copy concurrency, from 1 to 8. Default: 4.
- ~DESCRIPTION_PREFIX~: prefix for migrated Codeberg descriptions.
- ~CODEBERG_BASE_URL~: Forgejo-compatible base URL. Default:
~https://codeberg.org~.
** Configure User Settings
- Open the script file (~migrate_github_to_codeberg.sh~) in a text editor.
- Update the user configuration section with your GitHub and
Codeberg credentials.
- Optionally, define the repositories you want to migrate by adding
their names to the ~REPOSITORIES~ array.
- Optionally, define the owners of the repositories that you want to
migrate by adding their usernames to the ~OWNERS~ array.
** Install Dependencies
- Ensure that ~curl~ and ~jq~ are installed on your system.
- On Debian/Ubuntu: ~sudo apt install curl jq~
- On macOS with Homebrew: ~brew install curl jq~
** Run the Script
- Execute the script by running ~./migrate_repositories.sh~ in your terminal.
- Follow the on-screen instructions to proceed with the migration.
- Press ENTER to start the migration process.
** Monitor Progress
- The script will display information about the migration progress,
including successful migrations and any errors encountered.
** Review Results
- Once the migration is complete, review the results to ensure all
desired repositories have been migrated successfully.
* Requirements
- Bash shell
- curl
- jq (JSON processor)
- Rust 1.93+ for the TUI
* Screenshots (in text mode)
#+BEGIN_SRC txt
----------------------------------------------
Welcome to Github to Codeberg Migration Script
----------------------------------------------
User on Github : LionyxML
User on Codeberg : LionyxML
Using description prefix: [MIRROR]
Migrating repos owned by: all users
Migrating repo : aa
Migrating repo : dotfiles
Migrating repo : flycheck
Migrating repo : my_emacs_config
If you wish to change this, abort and change this script.
Press ENTER to continue, C-c to abort.
>>> Working...
>>> Migrating: aa... Error! Already exists on Codeberg.
>>> Migrating: flycheck... Success!
>>> Migration complete!
#+END_SRC
#+BEGIN_SRC txt
----------------------------------------------
Welcome to Github to Codeberg Migration Script
----------------------------------------------
User on Github : LionyxML
User on Codeberg : LionyxML
Using description prefix: [MIRROR]
Migrating repos owned by: LionyxML
Migrating repo : all
If you wish to change this, abort and change this script.
Press ENTER to continue, C-c to abort.
>>> Working...
>>> Migrating: 100_computer_science_concepts... Error! Already exists on Codeberg.
>>> Migrating: 10_design_patterns... Error! Already exists on Codeberg.
>>> Migrating: aa... Error! Already exists on Codeberg.
#+END_SRC
* License
This project is licensed under [[file:LICENSE][GPL-3.0-only]].
Clone this repository
For self-hosted knots, clone URLs may differ based on your setup.
The original script handled bulk server-side migration, but the local origin update step needed a safer review surface. This adds a Rust Ratatui flow that merges local, GitHub, and Codeberg inventory before mutating remotes, defers origin updates until Codeberg existence is known, and exports the same decision data for offline review.
Constraint: Keep GitHub code intact while making Codeberg the primary local origin only after the target exists.
Rejected: Commit generated inventory and build output | they contain machine-local state and are not part of the tool.
Confidence: high
Scope-risk: moderate
Directive: Do not relax the origin-update guard without verifying Codeberg repo existence first.
Tested: cargo test
Tested: cargo fmt --check
Tested: cargo clippy --all-targets -- -D warnings
Tested: git diff --check
Use jq to build the migration request body instead of shell string
interpolation, preventing 422 errors when descriptions contain special
characters (e.g. double quotes). Also improve error output to display
the actual message returned by Codeberg instead of just the HTTP status
code.
Probably fixes #3 too.
correct name of script