{"id":22248931,"url":"https://github.com/purplebooth/git-moves-together","last_synced_at":"2025-07-28T04:31:24.246Z","repository":{"id":37862705,"uuid":"400505306","full_name":"PurpleBooth/git-moves-together","owner":"PurpleBooth","description":"Spot coupling by finding out which files are always in the same commit (Mirror)","archived":false,"fork":false,"pushed_at":"2025-07-26T15:01:34.000Z","size":385,"stargazers_count":14,"open_issues_count":4,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-07-26T20:43:54.699Z","etag":null,"topics":["cli","coupling","dev-tools","git"],"latest_commit_sha":null,"homepage":"https://codeberg.org/PurpleBooth/git-moves-together/","language":"Rust","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"cc0-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/PurpleBooth.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","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}},"created_at":"2021-08-27T12:41:52.000Z","updated_at":"2025-07-08T18:17:20.000Z","dependencies_parsed_at":"2024-09-07T08:37:55.449Z","dependency_job_id":"a3906674-0586-4187-91bf-edc3d8a4b5a5","html_url":"https://github.com/PurpleBooth/git-moves-together","commit_stats":null,"previous_names":[],"tags_count":101,"template":false,"template_full_name":null,"purl":"pkg:github/PurpleBooth/git-moves-together","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PurpleBooth%2Fgit-moves-together","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PurpleBooth%2Fgit-moves-together/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PurpleBooth%2Fgit-moves-together/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PurpleBooth%2Fgit-moves-together/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/PurpleBooth","download_url":"https://codeload.github.com/PurpleBooth/git-moves-together/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PurpleBooth%2Fgit-moves-together/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267464016,"owners_count":24091491,"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","status":"online","status_checked_at":"2025-07-28T02:00:09.689Z","response_time":68,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["cli","coupling","dev-tools","git"],"created_at":"2024-12-03T06:20:13.764Z","updated_at":"2025-07-28T04:31:24.239Z","avatar_url":"https://github.com/PurpleBooth.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# git moves-together\n\nThis tells you when files in the repository frequently move together.\nThis lets you identify where the coupling is in the system. Coupling is\nnegative and to an extent unavoidable, this tool aims to make it\nvisible.\n\n## Getting Started\n\nIf every time I commit no file moves at the same time, that's 0 coupling\n\n``` shell,script(name=\"no-coupling-setup\",expected_exit_code=0)\necho \"no-coupling-setup - file_1\" \u003e file_1\ngit add .\ngit commit --message \"demo: no-coupling-setup\"\necho \"no-coupling-setup - file_2\" \u003e file_2\ngit add .\ngit commit --message \"demo: no-coupling-setup\"\n```\n\nWhen we run git-moves-together we can see that these files have no\ndirect commit based coupling\n\n``` shell,script(name=\"no-coupling\",expected_exit_code=0)\ngit-moves-together\n```\n\n``` text,verify(script_name=\"no-coupling\",stream=stdout)\n0 files move together\n```\n\nIf we then make a change to both files in the same commit\n\n``` shell,script(name=\"coupling-setup\",expected_exit_code=0)\necho \"coupling-setup - file_1\" \u003e file_1\necho \"coupling-setup - file_2\" \u003e file_2\necho \"coupling-setup - file_3\" \u003e file_3\ngit add .\ngit commit --message \"demo: coupling-setup\"\n```\n\nWhen we run git-moves-together we can see that these files have no\ndirect commit based coupling\n\n``` shell,script(name=\"coupling\",expected_exit_code=0)\ngit-moves-together $PWD\n```\n\n``` text,verify(script_name=\"coupling\",stream=stdout)\n╭──────────────────┬──────────────────┬────────────┬──────────┬─────────╮\n│ File A           ┆ File B           ┆ Together % ┆ Together ┆ Commits │\n╞══════════════════╪══════════════════╪════════════╪══════════╪═════════╡\n│ some-repo@file_1 ┆ some-repo@file_2 ┆ 50.00%     ┆ 1        ┆ 2       │\n├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤\n│ some-repo@file_1 ┆ some-repo@file_3 ┆ 100.00%    ┆ 1        ┆ 1       │\n├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤\n│ some-repo@file_2 ┆ some-repo@file_3 ┆ 50.00%     ┆ 1        ┆ 2       │\n╰──────────────────┴──────────────────┴────────────┴──────────┴─────────╯\n```\n\nYou can also reduce the commits you're including, by limiting the\nchanges to a specific time period\n\n``` shell,script(name=\"day-limit-setup\",expected_exit_code=0)\necho \"day-limit-setup - file_1\" \u003e file_1\ngit add .\nGIT_COMMITTER_DATE=\"2005-04-07T22:13:13\" git commit --message \"demo: day-limit-setup\"\n```\n\n``` shell,script(name=\"day-limit\",expected_exit_code=0)\ngit-moves-together -d 30 $PWD\n```\n\n``` text,verify(script_name=\"day-limit\",stream=stdout)\n╭──────────────────┬──────────────────┬────────────┬──────────┬─────────╮\n│ File A           ┆ File B           ┆ Together % ┆ Together ┆ Commits │\n╞══════════════════╪══════════════════╪════════════╪══════════╪═════════╡\n│ some-repo@file_1 ┆ some-repo@file_2 ┆ 50.00%     ┆ 1        ┆ 2       │\n├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤\n│ some-repo@file_1 ┆ some-repo@file_3 ┆ 100.00%    ┆ 1        ┆ 1       │\n├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤\n│ some-repo@file_2 ┆ some-repo@file_3 ┆ 50.00%     ┆ 1        ┆ 2       │\n╰──────────────────┴──────────────────┴────────────┴──────────┴─────────╯\n```\n\nYou can also set a window of time to group by rather than the commit id,\nwhich is useful when you're looking for coupling over multiple\nrepositories\n\nLet's make another git repository\n\n``` shell,script(name=\"time-windo-setup\",expected_exit_code=0)\necho \"time-window-setup - file_1\" \u003e \"../other-repo/file_1\"\necho \"time-window-setup - file_2\" \u003e \"../other-repo/file_2\"\necho \"time-window-setup - file_3\" \u003e \"../other-repo/file_3\"\ngit -C \"../other-repo\" add .\ngit -C \"../other-repo\" commit --message \"demo: time-window-setup\"\necho \"time-window-setup - file_1 update\" \u003e \"../other-repo/file_1\"\necho \"time-window-setup - file_2 update\" \u003e \"../other-repo/file_2\"\necho \"time-window-setup - file_3 update\" \u003e \"../other-repo/file_3\"\ngit -C \"../other-repo\" add .\ngit -C \"../other-repo\" commit --message \"demo: time-window-setup\"\n```\n\nNow we can look at the coupling across two repositories\n\n``` shell,script(name=\"time-window\",expected_exit_code=0)\ngit-moves-together -t 30 \"$PWD\" \"$PWD/../other-repo\"\n```\n\n``` text,verify(script_name=\"time-window\",stream=stdout)\n╭───────────────────┬───────────────────┬────────────┬──────────┬─────────╮\n│ File A            ┆ File B            ┆ Together % ┆ Together ┆ Commits │\n╞═══════════════════╪═══════════════════╪════════════╪══════════╪═════════╡\n│ other-repo@file_1 ┆ other-repo@file_2 ┆ 100.00%    ┆ 2        ┆ 2       │\n├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤\n│ other-repo@file_1 ┆ other-repo@file_3 ┆ 100.00%    ┆ 2        ┆ 2       │\n├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤\n│ other-repo@file_1 ┆ some-repo@file_1  ┆ 33.33%     ┆ 2        ┆ 6       │\n├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤\n│ other-repo@file_1 ┆ some-repo@file_2  ┆ 40.00%     ┆ 2        ┆ 5       │\n├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤\n│ other-repo@file_1 ┆ some-repo@file_3  ┆ 40.00%     ┆ 2        ┆ 5       │\n├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤\n│ other-repo@file_2 ┆ other-repo@file_3 ┆ 100.00%    ┆ 2        ┆ 2       │\n├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤\n│ other-repo@file_2 ┆ some-repo@file_1  ┆ 33.33%     ┆ 2        ┆ 6       │\n├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤\n│ other-repo@file_2 ┆ some-repo@file_2  ┆ 40.00%     ┆ 2        ┆ 5       │\n├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤\n│ other-repo@file_2 ┆ some-repo@file_3  ┆ 40.00%     ┆ 2        ┆ 5       │\n├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤\n│ other-repo@file_3 ┆ some-repo@file_1  ┆ 33.33%     ┆ 2        ┆ 6       │\n├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤\n│ other-repo@file_3 ┆ some-repo@file_2  ┆ 40.00%     ┆ 2        ┆ 5       │\n├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤\n│ other-repo@file_3 ┆ some-repo@file_3  ┆ 40.00%     ┆ 2        ┆ 5       │\n├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤\n│ some-repo@file_1  ┆ some-repo@file_2  ┆ 83.33%     ┆ 5        ┆ 6       │\n├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤\n│ some-repo@file_1  ┆ some-repo@file_3  ┆ 83.33%     ┆ 5        ┆ 6       │\n├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤\n│ some-repo@file_2  ┆ some-repo@file_3  ┆ 100.00%    ┆ 5        ┆ 5       │\n╰───────────────────┴───────────────────┴────────────┴──────────┴─────────╯\n```\n\nWhich is why you see the coupling as shown above\n\n## Usage\n\n``` shell,script(name=\"help\",expected_exit_code=0)\ngit-moves-together -h\n```\n\n``` text,verify(script_name=\"help\",stream=stdout)\nFind files that move at the same time in a git repository to identify coupling\n\nUsage: git-moves-together [OPTIONS] [GIT_REPO]...\n\nArguments:\n  [GIT_REPO]...  A repository to analyse [env: GIT_REPO=] [default: .]\n\nOptions:\n  -d, --from-days \u003cMAX_DAYS_AGO\u003e\n          Ignore deltas older than the given days [env: MAX_DAYS_AGO=]\n  -t, --time-window-minutes \u003cTIME_WINDOW_MINUTES\u003e\n          Group commits by similar time window rather than by commit id [env: TIME_WINDOW_MINUTES=]\n  -h, --help\n          Print help\n  -V, --version\n          Print version\n```\n\n## Installing\n\nSee the [releases\npage](https://codeberg.org/PurpleBooth/git-moves-together/releases/latest)\nwe build for linux and mac, alternatively use brew\n\n``` shell,skip()\nbrew install PurpleBooth/repo/git-moves-together\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpurplebooth%2Fgit-moves-together","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpurplebooth%2Fgit-moves-together","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpurplebooth%2Fgit-moves-together/lists"}