{"id":47915407,"url":"https://github.com/orbitalimpact/hipmost","last_synced_at":"2026-04-04T05:33:08.892Z","repository":{"id":75802617,"uuid":"146361362","full_name":"orbitalimpact/hipmost","owner":"orbitalimpact","description":"Migrate your Hipchat history to Mattermost","archived":false,"fork":false,"pushed_at":"2026-03-02T01:08:27.000Z","size":110,"stargazers_count":10,"open_issues_count":0,"forks_count":5,"subscribers_count":3,"default_branch":"master","last_synced_at":"2026-03-02T03:42:18.433Z","etag":null,"topics":["hipchat","mattermost","ruby","ruby-gem"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/orbitalimpact.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2018-08-27T22:30:03.000Z","updated_at":"2026-03-02T01:08:30.000Z","dependencies_parsed_at":"2023-03-22T15:03:35.111Z","dependency_job_id":null,"html_url":"https://github.com/orbitalimpact/hipmost","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/orbitalimpact/hipmost","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/orbitalimpact%2Fhipmost","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/orbitalimpact%2Fhipmost/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/orbitalimpact%2Fhipmost/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/orbitalimpact%2Fhipmost/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/orbitalimpact","download_url":"https://codeload.github.com/orbitalimpact/hipmost/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/orbitalimpact%2Fhipmost/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31389384,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T04:26:24.776Z","status":"ssl_error","status_checked_at":"2026-04-04T04:23:34.147Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hipchat","mattermost","ruby","ruby-gem"],"created_at":"2026-04-04T05:33:08.318Z","updated_at":"2026-04-04T05:33:08.866Z","avatar_url":"https://github.com/orbitalimpact.png","language":"Ruby","readme":"# hipmost\n\nMigrate HipChat exports to Mattermost. Audit first, import one room at a time, verify each one before moving on.\n\n## Why not migratemost or varna?\n\n| Feature | hipmost | migratemost | varna |\n|---|---|---|---|\n| Language | Ruby | Python 2 | Python 2 |\n| Audit phase | yes — shows what would happen before touching anything | no | no |\n| Collision detection | yes — skips exact-timestamp duplicates on merge | no | no |\n| Per-room atomic import | yes — generate + import + verify in one command | no | no |\n| Mapping review workflow | yes — audit writes YAML you edit and approve | no | no |\n| Emoji conversion | yes — HipChat shortcodes to MM shortcodes | partial | no |\n| Attachment validation | yes — size check, path resolution, re-import command | no | no |\n| Message splitting | yes — splits at word boundary, preserves timestamps | no | silently drops |\n\nNot claiming perfection. If you have a small HipChat export and don't care about any of the above, any tool will work. This project has managed to import nearly a million messages successfully, so hopefully this helps with legacy projects you might still have floating around since, if you're like some of us, the original import was a bit tedious and you might have not gotten but a fraction into your Mattermost system. Hopefully now with this you'll be able to finish those long-overdue shelved projects.\n\n## Prerequisites\n\n- Ruby 3.x\n- `pg` gem (`gem install pg`)\n- PostgreSQL read access to your Mattermost database\n- `mmctl` at `/opt/mattermost/bin/mmctl` for the import commands\n- `zip` in PATH\n\n## Setup\n\nCreate `~/.hipmost-env`:\n\n```sh\nexport HIPMOST_DB_URL=postgres://mattermost:yourpassword@localhost/mattermost\n```\n\nThe script also checks `~/.mm-search-env` and the `MATTERMOST_DB_URL` env var as fallbacks.\n\n## Workflow\n\n### 1. Audit the export\n\n```sh\nruby hipmost.rb audit /path/to/hipchat-export\n```\n\nThis reads the export, queries your Mattermost DB, and writes `hipmost-audit.yaml`. It shows:\n\n- Which HC users matched MM users (by email, then username)\n- Which HC rooms matched MM channels (fuzzy name match)\n- Suggested action for each: `skip` / `merge` / `new`\n- DM inventory per user\n\nOpen `hipmost-audit.yaml` and review. The audit output is not your import mapping — it's your research. You'll build a separate `mapping.yaml` from it (see format below).\n\n### 2. Build your mapping\n\nConvert the audit output into a `mapping.yaml`. The audit YAML uses a flat structure; the import commands expect the structured format described in the Mapping Format section. This is intentional — the review step forces you to make deliberate decisions per room rather than bulk-approving.\n\n### 3. Import one room at a time\n\n```sh\nruby hipmost.rb import_one /path/to/hipchat-export --map mapping.yaml --room 'Engineering'\n```\n\nThis command:\n1. Generates JSONL for that one room\n2. Packages it as a zip\n3. Calls `mmctl import process`\n4. Polls the import job until done\n5. Verifies post count and samples a few messages\n6. Exits nonzero if verification fails\n\nFor merge targets (room already exists in MM), it loads existing timestamps first and skips exact duplicates.\n\n### 4. Import DMs\n\n```sh\nruby hipmost.rb import_dm /path/to/hipchat-export --map mapping.yaml --pair 'alice,bob'\n```\n\nSame pattern: generate, import, verify. DM channel is created if it doesn't exist.\n\n### 5. Fix attachments (if needed)\n\n```sh\nruby hipmost.rb fix_attachments /path/to/hipchat-export --map mapping.yaml\nruby hipmost.rb fix_attachments /path/to/hipchat-export --map mapping.yaml --room 'Engineering'\n```\n\nRe-imports only the attachment-bearing posts. Mattermost matches posts by `channel + create_at` and attaches the files to the existing posts. Run this after `import_one` if attachments were missing.\n\n### 6. Bulk generate + import (alternative)\n\nIf you'd rather generate everything at once and import with a single `mmctl` call:\n\n```sh\nruby hipmost.rb generate /path/to/hipchat-export --map mapping.yaml\nruby hipmost.rb generate /path/to/hipchat-export --map mapping.yaml --dry-run  # preview counts\nruby hipmost.rb import hipmost-output.zip\n```\n\nThe bulk path does less verification than `import_one`. Good for a final full-run after you've tested individual rooms.\n\n## Mapping Format\n\nThe import commands (`import_one`, `import_dm`, `fix_attachments`) use a structured mapping, not the raw audit YAML.\n\n```yaml\nusers:\n  - hc: alice           # HipChat mention_name\n    hc_id: 12345        # from audit YAML\n    mm: alice           # Mattermost username (omit if same as hc)\n    email: alice@example.com\n    action: map         # map | create | skip\n\n  - hc: bob\n    hc_id: 12346\n    mm: bob.smith       # different MM username\n    email: bob@example.com\n    action: map\n\n  - hc: former-employee\n    hc_id: 99999\n    action: skip        # skip: messages from this user are dropped\n\nrooms_skip:\n  - hc_name: Watercooler\n    hc_id: 1001\n    target: myteam:watercooler   # team:channel-name (MM channel to skip importing into)\n\nrooms_merge:\n  - hc_name: Engineering\n    hc_id: 2001\n    target: myteam:engineering\n    display_name: Engineering\n    type: O              # O=public, P=private\n\n  - hc_name: Infrastructure\n    hc_id: 2002\n    target: myteam:infrastructure\n    display_name: Infrastructure\n    type: P\n\nrooms_new:\n  - hc_name: Old Project\n    hc_id: 3001\n    target: myteam:old-project\n    display_name: Old Project\n    type: P\n    members:             # optional: add these MM users after import\n      - alice\n      - bob\n\ndms:\n  import_dms: yes        # yes | no\n```\n\nField notes:\n\n- `target` format is `team-name:channel-name` where both are the internal names (lowercase, hyphens), not display names\n- `action: create` in users means the user doesn't exist in MM yet; hipmost emits a user record and mmctl creates them\n- `type` for rooms: `O` is public (open), `P` is private\n- `rooms_skip` entries are listed but not imported — useful to document your decisions\n\n## How it works\n\nThe audit-first approach exists because HipChat exports are messy. Rooms get renamed, users change usernames, some content was already imported via other paths. Going in blind overwrites things.\n\nThe flow:\n\n1. `audit` reads the HC export and queries MM, producing a YAML snapshot of the current state with suggested actions\n2. You review and edit that into a `mapping.yaml` — explicit decisions about every room and user\n3. `import_one` generates JSONL for exactly one room, imports it, and verifies the result before you proceed to the next\n\nThe JSONL format is Mattermost's bulk import format. `mmctl import process` handles the actual write to Mattermost. hipmost doesn't touch the database directly — it only reads.\n\nFor merge targets, hipmost loads all existing post timestamps before generating JSONL and skips any HC message whose timestamp exactly matches an existing post. This handles the common case where a partial import was run before.\n\nMessages longer than 16,383 characters (Mattermost's limit) are split at word boundaries. Each chunk gets a `create_at` offset of 1ms to preserve ordering.\n\nAttachments over 50MB are skipped (MM default file size limit). If your MM instance has a different limit, change `MM_MAX_FILE` at the top of the script.\n\n## Limitations\n\n- The fuzzy room matching in `audit` works well for rooms that weren't renamed much. If you renamed rooms significantly between HC and MM, you'll need to set targets manually in your mapping.\n- DMs from guest accounts are skipped.\n- HipChat `/topic` and `/emoticon` messages are not converted.\n- No support for HipChat integrations or bot messages.\n\n## License\n\nAGPL-3.0. See LICENSE.txt.\n\nPRs welcome.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Forbitalimpact%2Fhipmost","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Forbitalimpact%2Fhipmost","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Forbitalimpact%2Fhipmost/lists"}