home-git-clone is a Home Manager module that lets you define repositories in your Nix configuration and have them automatically cloned during home-manager switch.
Instead of running this on every machineβ¦
git clone git@github.com:user/dotfiles.git ~/Coding/dotfiles
git clone git@github.com:user/project.git ~/Coding/projectJust declare this once:
home.gitClone."Coding/dotfiles".url = "git@github.com:user/dotfiles.git";
home.gitClone."Coding/project".url = "git@github.com:user/project.git";Run home-manager switch and your repositories appear. Define once, deploy everywhere.
- Declarative β Define repositories in Nix, clone automatically
- Auto Default Branch β Detects βmainβ vs βmasterβ automatically
- Git + Jujutsu β Full support for both VCS systems
- Worktree Ready β Optional path structure for easy worktree workflows
- Smart HTTPS β Bypasses git config rewrites for HTTPS URLs
- Auto-Update β Optionally pull/fetch on every activation
- Multi-User Safe β Each userβs credentials, isolated operations
- GPG Agent β Automatic SSH socket detection
# flake.nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
home-manager.url = "github:nix-community/home-manager";
home-git-clone.url = "github:rytswd/home-git-clone";
};
outputs = { nixpkgs, home-manager, home-git-clone, ... }: {
homeConfigurations.youruser = home-manager.lib.homeManagerConfiguration {
pkgs = nixpkgs.legacyPackages.x86_64-linux;
modules = [
home-git-clone.homeManagerModules.default
./home.nix
];
};
};
}# home.nix or configuration.nix
{ config, pkgs, ... }:
{
imports = [
(builtins.fetchGit {
url = "https://github.com/rytswd/home-git-clone.git";
ref = "main";
} + "/nix")
];
}# home.nix
{
home.gitClone = {
"Coding/dotfiles" = {
url = "git@github.com:user/dotfiles.git";
};
};
}home-manager switchYour repository is now at ~/Coding/dotfiles/.
Basic Clone β Simplest configuration with auto-detected branch
home.gitClone = {
"Coding/my-project" = {
url = "git@github.com:user/project.git";
};
};Result: ~/Coding/my-project/
Specific Branch β Clone a specific branch instead of default
home.gitClone = {
"Coding/my-project" = {
url = "git@github.com:user/project.git";
rev = "develop";
};
};HTTPS Repository β Public repos without SSH keys
HTTPS URLs work seamlessly. Git config rewrites are automatically bypassed.
home.gitClone = {
"Tools/public-repo" = {
url = "https://github.com/user/repo.git";
};
};Auto-Update β Pull latest on every activation
Pull latest changes every time you run home-manager switch.
home.gitClone = {
"Coding/always-fresh" = {
url = "git@github.com:user/repo.git";
update = true;
};
};Worktree Mode β Structure for git worktree workflows
Appends the branch name to the path, perfect for Git worktree workflows.
home.gitClone = {
"Coding/my-project" = {
url = "git@github.com:user/project.git";
rev = "main";
useWorktree = true;
};
};Result: ~/Coding/my-project/main/
Add more worktrees easily:
cd ~/Coding/my-project/main
git worktree add ../develop
git worktree add ../feature-x~/Coding/my-project/ |-- main/ # Created by home-git-clone |-- develop/ # Added manually +-- feature-x/ # Added manually
Use home.jjClone for Jujutsu-native workflows.
Basic Clone β Git-colocated by default
Creates a Git-colocated repository by default (both .git and .jj directories).
home.jjClone = {
"Coding/jj-repo" = {
url = "git@github.com:user/repo.git";
};
};Pure Jujutsu β Non-colocated, no .git directory
No .git directory, pure Jujutsu only.
home.jjClone = {
"Coding/pure-jj" = {
url = "git@github.com:user/repo.git";
colocate = false;
};
};Auto-Update β Fetch latest on every activation
home.jjClone = {
"Coding/jj-fresh" = {
url = "git@github.com:user/repo.git";
update = true; # Runs: jj git fetch
};
};Workspace Mode β Structure for jj workspace workflows
home.jjClone = {
"Coding/jj-workspaces" = {
url = "git@github.com:user/repo.git";
rev = "main";
useWorkspace = true;
};
};Result: ~/Coding/jj-workspaces/main/
Add more workspaces:
cd ~/Coding/jj-workspaces/main
jj workspace add ../developSince repositories are cloned directly to your home directory (outside the Nix store), you can symlink files or directories from them using config.lib.file.mkOutOfStoreSymlink.
This is particularly useful for managing configuration files:
{ config, ... }: {
home.gitClone."Coding/dotfiles" = {
url = "git@github.com:user/dotfiles.git";
};
# Link a directory from the cloned repo to ~/.config
home.file.".config/nvim".source =
config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/Coding/dotfiles/nvim";
}| Option | Type | Default | Description |
|---|---|---|---|
url | string | required | Repository URL (SSH or HTTPS) |
rev | string or null | null | Branch/revision. Auto-detects if null. |
useWorktree | bool | false | Append rev to path for worktree structure |
bypassGitConfig | bool or null | null | Override git config bypass (auto for HTTPS) |
update | bool | false | Pull on every activation |
| Option | Type | Default | Description |
|---|---|---|---|
url | string | required | Repository URL (SSH or HTTPS) |
rev | string or null | null | Branch/bookmark. Auto-detects if null. |
colocate | bool | true | Create Git-colocated repo (.git + .jj) |
useWorkspace | bool | false | Append rev to path for workspace structure |
bypassGitConfig | bool or null | null | Override git config bypass (auto for HTTPS) |
update | bool | false | Fetch on every activation (jj git fetch) |
Override Git Config Bypass β Control HTTPS/SSH rewriting behavior
By default, HTTPS URLs bypass your git config to prevent url.*.insteadOf rewrites. Override this behavior:
home.gitClone = {
# Force bypass even for SSH
"special/repo1" = {
url = "git@github.com:user/repo.git";
bypassGitConfig = true;
};
# Use git config even for HTTPS
"special/repo2" = {
url = "https://github.com/user/repo.git";
bypassGitConfig = false;
};
};Multi-User Setup β Safe for shared systems
Each user runs home-manager with their own credentials. Safe for shared systems.
# User alice (SSH keys)
home.gitClone = {
"Work/private" = {
url = "git@github.com:company/private.git";
};
};
# User bob (HTTPS only)
home.gitClone = {
"Projects/public" = {
url = "https://github.com/user/public.git";
};
};Combining Git and Jujutsu β Use both in the same config
Use both module types in the same configuration:
{
# Standard Git repositories
home.gitClone = {
"Coding/legacy-project" = {
url = "git@github.com:user/legacy.git";
};
};
# Jujutsu repositories
home.jjClone = {
"Coding/modern-project" = {
url = "git@github.com:user/modern.git";
};
};
}βββββββββββββββββββββββββββββββββββ
β Your Nix Configuration β
β (home.nix / flake.nix) β
βββββββββββββββββ¬ββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββ
β home.gitClone = { ... } β
β home.jjClone = { ... } β
βββββββββββββββββ¬ββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββ
β Home Manager Module β
β Validates config, generates β
β activation scripts per-repo β
βββββββββββββββββ¬ββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββ
β $ home-manager switch β
βββββββββββββββββ¬ββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββ
β For each repository: β
β 1. Check if already cloned β
β 2. Auto-detect default branch β
β 3. Setup SSH (GPG agent) β
β 4. Clone or update β
βββββββββββββββββ¬ββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββ
β β Repositories ready β
β ~/Coding/your-repos/ β
βββββββββββββββββββββββββββββββββββ
| Behavior | Description |
|---|---|
| Idempotent | Safe to run repeatedly β existing repos left untouched |
| SSH Detection | Auto-uses GPG agent socket at ~/.gnupg/S.gpg-agent.ssh |
| HTTPS Bypass | Prevents url.*.insteadOf rewrites for HTTPS URLs |
| Update Mode | Only fetches/pulls when update = true |
| Parallel Safe | Multiple repos clone independently |
Removing a repository from your configuration does not delete the directory from your disk. This is a deliberate safety feature to prevent accidental data loss. You must manually remove the directory if it is no longer needed.
If the target directory already contains a Git repository, the module assumes it is correct. It does not validate that the local repositoryβs remote URL matches the one specified in your configuration. This ensures that manually modified remotes (e.g., using different push URLs) are not overwritten or flagged as errors.
Cloning happens during the activation phase of Home Manager. If you are cloning many large repositories for the first time, home-manager switch will wait for all clones to complete before finishing.
Repository Not Cloning
- Check if the directory already exists:
ls -la ~/path/to/repo- Remove and retry:
rm -rf ~/path/to/repo
home-manager switchSSH Authentication Fails
- Verify your SSH agent:
ssh-add -l- Check GPG agent socket:
ls -la ~/.gnupg/S.gpg-agent.sshHTTPS Being Rewritten to SSH
This happens when you have url.*.insteadOf in your git config. The module should bypass this automatically for HTTPS URLs. If not:
home.gitClone."path" = {
url = "https://github.com/user/repo.git";
bypassGitConfig = true; # Force bypass
};MIT
Contributions are welcome! Please open an issue or pull request.