{"id":15977102,"url":"https://github.com/fornever/git-submerge","last_synced_at":"2026-03-04T20:05:03.679Z","repository":{"id":54178670,"uuid":"95032350","full_name":"ForNeVeR/git-submerge","owner":"ForNeVeR","description":"Merge Git submodule into the main repo as if they've never been separate at all","archived":false,"fork":false,"pushed_at":"2021-03-05T00:48:00.000Z","size":86,"stargazers_count":5,"open_issues_count":16,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-07T06:32:44.305Z","etag":null,"topics":["cli","git","submodules"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ForNeVeR.png","metadata":{"files":{"readme":"README.markdown","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-06-21T18:06:43.000Z","updated_at":"2024-01-05T00:38:10.000Z","dependencies_parsed_at":"2022-08-13T08:30:47.152Z","dependency_job_id":null,"html_url":"https://github.com/ForNeVeR/git-submerge","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/ForNeVeR/git-submerge","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ForNeVeR%2Fgit-submerge","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ForNeVeR%2Fgit-submerge/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ForNeVeR%2Fgit-submerge/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ForNeVeR%2Fgit-submerge/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ForNeVeR","download_url":"https://codeload.github.com/ForNeVeR/git-submerge/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ForNeVeR%2Fgit-submerge/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262094606,"owners_count":23258003,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["cli","git","submodules"],"created_at":"2024-10-07T22:42:28.301Z","updated_at":"2026-03-04T20:05:03.597Z","avatar_url":"https://github.com/ForNeVeR.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"git-submerge [![Travis build status][travis-badge]][travis-build] [![Appveyor build status][appveyor-badge]][appveyor-build] [![Andivionian status umbra][andivionian-status-badge]][andivionian-status-link]\n============\n\n[travis-badge]: https://travis-ci.org/ForNeVeR/git-submerge.svg?branch=master\n[travis-build]: https://travis-ci.org/ForNeVeR/git-submerge\n[appveyor-badge]: https://ci.appveyor.com/api/projects/status/bt4jc3eg5w9411o7/branch/master?svg=true\n[appveyor-build]: https://ci.appveyor.com/project/ForNeVeR/git-submerge-nhw3s/branch/master\n[andivionian-status-badge]: https://img.shields.io/badge/status-enfer-orange.svg\n[andivionian-status-link]: https://github.com/ForNeVeR/andivionian-status-classifier##status-enfer-\n\nSuppose you have a repo with a submodule. Their collective history might look\nlike this:\n\n    repository   A-B----D-E-F----H-----K----M---O   master\n                       ;        ;          ;\n                      ;        ;          ;\n    submodule        C--------G----I-J---L----N     master\n\n(Semicolons are gitlinks; we'll use slashes for merges.)\n\nAfter running `git submerge submodule`, the history will look like this:\n\n    repository   A-B---D'-E'-F'--H'--------K'--M'----O'   master\n                      /         /             /\n                     C---------G'----I'-J'---L'---N'      sub-master\n\nThe following things happened:\n\n* submodule got replaced by an ordinary directory;\n\n* submodule's history became part of the repo's history;\n\n* where submodule updates were previously (commits D, H, M), we now have merge\n  commits;\n\n* the yet-unmerged \"tail\" of the submodule history (commit N) is given its own\n  branch so you can merge it yourself later (#18).\n\n**ATTENTION!** Just as any other kind of history rewriting, `git-submerge`\nchanges the hashes of the commits, so you shouldn't run it on published\nhistories. Furthermore, beware of bugs! Run this on a fresh clone of your\nrepository, and never delete the old history until you're reasonably sure that\nthe new one is what you expect it to be.\n\nBefore using git-submerge, it's recommended to take a look at [a simpler\napproach](https://blog.debiania.in.ua/posts/2017-07-06-pulling-submodule-s-history-into-the-main-repository.html).\n\nDealing with dangling references\n================================\n\nIt might so happen that `git-submerge` stumbles upon a commit in the main repo\nwhich references a submodule's commit *which doesn't exist*. The reason this\nhappens is that submodule's history has been rewritten sometime after the\ncommit to the main repo was made, so now the main repo references something\nthat is gone.\n\nRewriting already published histories is generally frowned upon, precisely due\nto the problem described above, but it still happens. `git-submerge` provides\nyou with a couple of flags that you can use to retain as much of your history\nas possible. Let's quickly describe what they are, and then we'll take a look at\nhow one can use them.\n\nThe first of those options is `--mapping`, accepting two arguments we'll call\n\"old commit id\" and \"new commit id\". Whenever `git-submerge` finds a commit in\nthe main repo that points to \"old commit id\" in the submodule, it'll pretend\nthat it sees \"new commit id\" instead, and will go on with its business.\n\nThe second option is `--default-mapping`, accepting one argument we'll call\n\"default commit id\". If `git-submerge` finds a dangling reference which isn't\nmentioned in any of the `--mapping`s, it'll use `--default-mapping`. Simple, eh?\n\nNow, as promised, let's look at an example. Suppose you've run `git-submerge`,\nand it printed out the following:\n\n```\nThe repository references the following submodule commits,\nbut they couldn't be found in the submodule's history:\n\naaaabbbbccccddddeeeeffff0000111122223333\n4444555566667777888899990000aaaabbbbcccc\nddddeeeeffff0000111122223333444455556666\n\nYou can use --mapping and --default-mapping options to make\ngit-submerge replace these commits with some other, still\nexisting, commits.\n```\n\nThe best-case scenario for you is that you find a repo that still has these\ncommits. You can then look at their metadata (commit message, date etc.) and\nfind the corresponding commits in the submodule's new history.\n\nAnother, much more cumbersome, option is to find the aforementioned dangling IDs\nin your main repo's history (`git log -S` to the rescue!), then compare with\nyour submodule's history and simply *guess* at what new commit IDs you could\nuse.\n\nYou can then add a few `--mapping`s, and the problem will be resolved.\n\nThe worst-case scenario is that you can't find any trace of the old history, and\nguessing didn't help either. In that case, you'll have to create a new commit in\nsubmodule explaining that some of its history has been lost and you can't\nrecover it. Then you can pass that commit's ID to `--default-mapping`, and the\nresulting history will at least have an explanation of why some commits are\nbroken.\n\nBuilding\n========\n\ngit-submerge requires rustc 1.15+ and cargo 0.16+, so you might need to update\nyour build environment first:\n\n```console\n$ rustup update\n```\n\nNixOS users can use the Nix shell; it'll fetch Rust nightly:\n\n```console\n$ nix-shell\n```\n\nAfter that, it's the usual jazz:\n\n```console\n$ cargo build\n```\n\nTesting\n=======\n\nTo check that your build behaves the way the developers expect, do the following:\n\n1. Prepare a directory for your tests:\n\n    ```console\n    $ mkdir /tmp/git-submerge-testbed\n    ```\n\n2. Create the submodule repo:\n\n    ```console\n    $ cd /tmp/git-submerge-testbed\n    # Assuming you have git-submerge cloned to /home/user/git-submerge\n    $ git clone /home/user/git-submerge sub\n    # ...otherwise\n    $ git clone https://github.com/Minoru/git-submerge.git sub\n    $ cd sub\n    $ git reset --hard poc-submodule\n    ```\n\n3. Create the main repo:\n\n    ```console\n    $ cd /tmp/git-submerge-testbed\n    # Assuming you have git-submerge cloned to /home/user/git-submerge\n    $ git clone /home/user/git-submerge repo\n    # ...otherwise\n    $ git clone https://github.com/Minoru/git-submerge.git repo\n    $ cd repo\n    $ git reset --hard poc-repo\n    # Removing upstream remote so that `git submodule` looks for\n    # submodule repo in our testbed, not in the place we cloned from\n    $ git remote rm origin\n    $ git submodule update --init\n    ```\n\n4. Run git-submerge:\n\n    ```console\n    # This assumes you've updated your path like so:\n    # $ export PATH=/home/user/git-submerge/target/debug/:$PATH\n    # Alternatively, you can use full path instead of \"git submerge\".\n    $ git submerge sub\n    ```\n\n5. Check the result:\n\n    ```console\n    $ git fast-export master \u003e /home/user/git-submerge/test/expected.stream\n    $ cd /home/user/git-submerge/test\n    $ git diff expected.stream\n    ```\n\n    If everything went well, `git diff` shouldn't find any differences, and\n    there will be no output.\n\n    Don't forget to clean up afterwards!\n\n    ```console\n    $ git checkout expected.stream\n    ```\n\nUseful tips\n===========\n\n* When viewing the rewritten history with `git log --patch`, add `-m`, `-c` or\n    `--cc` option; they all enable diffs for merge commits (with slightly\n    different presentation—just pick the one you like). The reason this is\n    important is that your original history might have had commits where the\n    submodule is updated *and* some changes are made; now that such commits are\n    turned into merges, Git assumes that file changes were merge conflict\n    resolutions, and hides them from the diffs.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffornever%2Fgit-submerge","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffornever%2Fgit-submerge","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffornever%2Fgit-submerge/lists"}