A tool that cherry-picks and synchronizes specific files and directories across multiple Git repositories
Project description
git-grove
A tool that cherry-picks and synchronizes specific files and directories across multiple Git repositories
Introduction
A Python-based tool for synchronizing files and directories from multiple Git repositories into a local workspace. git-grove enables you to cherry-pick specific paths from remote repositories and keep them in sync with minimal overhead.
Features
- Selective File Syncing: Sync specific files or directories from remote repositories
- Multiple Repository Support: Manage files from multiple Git repositories simultaneously
- Flexible Backend Options: Choose between
mktree(default) orsparse-checkoutbackends - Efficient Caching: Uses blobless clones to minimize storage and bandwidth
- Portable Configuration: Maintain sync configurations in a portable JSON registry file
- GitHub Actions Integration: Automated periodic syncing via GitHub Actions workflow
- Light and Portable: All application code resides in a single file. No other dependencies.
Installation
From Source
Clone this repository and install using setup.py:
git clone https://github.com/universetraveller/git-grove.git
cd git-grove
pip install .
This creates a git-grove command-line tool (and git grove subcommand) that can be invoked from anywhere.
From Package Managers
Install directly via package managers (e.g., pip and uv):
pip install git-grove
Requirements
- Python 3
- Git
Quick Start
- Add a repository and paths to sync:
git-grove add https://github.com/owner/repo main path/to/file
- Synchronize:
git-grove sync
This will:
- Clone the repository's metadata (blobless clone if not already cached)
- Extract the specified paths from the
mainbranch - Place them in your local workspace
CLI Usage
git-grove provides several subcommands for managing your sync registry and performing synchronizations.
Global Options
git-grove [OPTIONS] SUBCOMMAND [SUBCOMMAND_OPTIONS]
Options:
-v, --verbose: Enable verbose debug output-r, --registry PATH: Path to the registry file (default:./registry.json)
Subcommands
sync - Synchronize repositories
Synchronize files from registered repositories based on the registry configuration.
git-grove sync [TARGET...] [-f]
Arguments:
TARGET: Optional targets to sync in format<repo_name_or_url>[:<revision>]- If not specified, syncs all registered repositories
- Examples:
myrepo,myrepo:main,https://github.com/owner/repo:v1.0.0
Options:
-
-f, --force: Force synchronization even if already up-to-dateThis option force the tool to skip checks for last-synced has. This is helpful when you modify local files and want to synchronize gain.
Examples:
# Sync all registered repositories
git-grove sync
# Sync specific repository
git-grove sync myrepo
# Sync specific revision only
git-grove sync myrepo:main
# Force sync even if up-to-date
git-grove sync -f
add - Add repository/revision/paths
Add a new repository, revision, and paths to the registry.
git-grove add NAME_OR_URL REVISION PATHS... [OPTIONS]
Arguments:
-
NAME_OR_URL: Repository name or URLFor the first time adding a repository, an URL should be provided. If the name is not provided, it would be derived from repository URL.
-
REVISION: Git revision reference (branch, tag, or commit hash) -
PATHS: One or more paths to sync from the repositoryTo add the whole repository, use empty string (
''in the terminal). It is handled by a special check before building tree-object.
Options:
--name NAME: Set a custom name for the repository (default: derived from repository URL)--target TARGET: Target output directory (relative to the globaltarget_dir)--last_synced HASH: Set the last synced commit hash
Examples:
# Add a single path from a repository
git-grove add https://github.com/owner/repo main src/file.txt
# Add multiple paths
git-grove add https://github.com/owner/repo main src/ docs/ LICENSE
# Add with a custom name
git-grove add https://github.com/owner/repo main src/ --name myrepo
# Add with custom target directory
git-grove add myrepo develop config/ --target dev-config
# Add the whole repository
git-grove add myrepo main ''
# Use default repository name
git-grove add https://github.com/owner/name_a main src/file.txt
git-grove add name_a main README.md
# Specify a special name
git-grove add https://github.com/owner/name_a main src/file.txt --name name_b
git-grove add name_b main README.md
rm - Remove repository/revision/paths
Remove repositories, revisions, or specific paths from the registry.
git-grove rm NAME_OR_URL REVISION [PATHS...]
Arguments:
NAME_OR_URL: Repository name or URL to remove fromREVISION: Revision to removePATHS: Optional specific paths to remove (removes all paths if not specified)
Examples:
# Remove all paths from a revision (deletes the revision)
git-grove rm myrepo main
# Remove specific paths only
git-grove rm myrepo main src/file.txt docs/
set - Set registry values
Set or update values in the registry.
git-grove set PATH VALUE
Arguments:
-
PATH: Dot-separated path to the field (use\\to escape dots in names)It is handled by
Registry._get_path(path: str) -> object | dict | list, str. The root object is the registry (a python dict). Each part of a path could be a key of a dict, an index of a list, the name to find a repository and an URL of a repository. -
VALUE: Value to set
Examples:
# Set cache directory
git-grove set cache_dir /path/to/cache
# Set target directory
git-grove set target_dir ./synced
# Set backend mode
git-grove set backend sparse-checkout
# Set repository name by index
git-grove set repositories.0.name custom-name
# Set repository name by existing name/URL
git-grove set repositories.myrepo.name new-name
unset - Remove registry values
Remove a field from the registry.
git-grove unset PATH
Arguments:
PATH: Dot-separated path to the field to remove
Examples:
# Remove custom cache directory (reverts to default)
git-grove unset cache_dir
ls - List registry contents
List repositories and revisions in the registry, or view a specific registry value.
git-grove ls [PATH]
Arguments:
PATH: Optional dot-separated path to a specific value
Examples:
# List all registered repositories
git-grove ls
# View cache directory setting
git-grove ls cache_dir
# View specific repository details
git-grove ls repositories.myrepo
batch-add - Batch add from file
Add multiple repository entries from a file or stdin.
git-grove batch-add [FILE]
Arguments:
FILE: Optional file path (reads from stdin if not provided)
File Format:
<repo_name_or_url> <revision> <path1> [<path2> ...]
Examples:
# Add from file
git-grove batch-add repos.txt
# Add from stdin
cat << EOF | git-grove batch-add
https://github.com/owner/repo1 main src/
https://github.com/owner/repo2 v1.0.0 docs/ LICENSE
EOF
Registry File Format
The registry file (registry.json) stores all sync configurations:
{
"cache_dir": ".cache",
"target_dir": ".",
"backend": "mktree",
"sparse-checkout": {
"mode": "no-cone"
},
"repositories": [
{
"name": "example-repo",
"url": "https://github.com/owner/repo",
"revisions": {
"main": {
"paths": ["src/", "docs/"],
"target": "example-repo",
"last_synced": "abc123..."
}
}
}
]
}
Registry Fields
cache_dir(optional): Directory for storing cached repository clones (default:.cache)target_dir(optional): Base directory for synced files (default: current directory)backend(optional): Sync backend to use:mktreeorsparse-checkout(default:mktree)sparse-checkout(optional): Configuration for sparse-checkout backendmode:coneorno-cone(default:no-cone)
repositories: Array of repository configurationsname(optional): Custom name (derived from URL if not provided)url(required): Git repository URLrevisions: Object mapping revision names to their configurationspaths(required): Array of paths to synctarget(optional): Output directory relative totarget_dirlast_synced(optional): Last synced commit hash (updated automatically)
Backend Modes
mktree (Default)
Uses Git's mktree command to build custom tree objects from selected paths. This is the recommended backend for most use cases.
Advantages:
- Precise path selection
- Works with any path combination
- No working directory conflicts
How it works:
- Fetches object hashes for requested paths
- Builds a custom Git tree containing only those paths
- Checks out the tree to the target directory
sparse-checkout
Uses Git's native sparse-checkout feature for path filtering.
see sparse-checkout for more information.
Configuration:
git-grove set backend sparse-checkout
git-grove set sparse-checkout.mode no-cone # default or cone
Modes:
cone: Faster, but it also checks out files that are not required in parent directoriesno-cone: Slower but more precise. This mode can correctly check out specified paths
Advantages:
- Native Git feature
- Works by tagging skip-worktree, which gains better performance in specific scenarios
Limitations:
- Requires a full working directory walking
- May have conflicts with parallel syncs
GitHub Actions Integration
The included .github/workflows/sync.yml workflow enables automatic periodic synchronization:
on:
schedule:
- cron: '0 0 * * 0' # Weekly on Sunday at midnight
workflow_dispatch: # Manual trigger
Features:
- Automatically syncs registered repositories on a schedule
- Caches repository clones for faster subsequent runs
- Commits and pushes changes back to your repository
- Manual triggering via GitHub Actions UI
Setup:
- Clone this repository or fork it directly
- Ensure your repository has write permissions for GitHub Actions
- Configure your
registry.jsonwith desired repositories - Set
ACCESS_TOKENin repository setting to your personal access token if you want to access private repositories. If you only want to access public repositories, you can change the nameACCESS_TOKENin thesync.ymltoGITHUB_TOKEN - Change time schedule in the
sync.ymlto your preferred configuration - The workflow will handle the rest automatically
Python API
For advanced use cases, you can use git-grove as a Python library.
Core Classes
Repository
Represents a Git repository to sync from.
Repository(name: str, url: str, output_dir: str, cache_dir: str)
Parameters:
name: Repository identifierurl: Git repository URLoutput_dir: Absolute path to output directorycache_dir: Absolute path to cache directory
Key Methods:
clone()
Clone the repository using blobless clone optimization.
Raises: subprocess.CalledProcessError if clone fails
fetch()
Fetch latest updates from the remote repository.
Raises: subprocess.CalledProcessError if fetch fails
get_latest_commit(rev: str) -> str
Get the commit hash for a given reference.
Parameters:
rev: Branch name, tag, or commit hash
Returns: Commit SHA hash
Raises: ValueError if reference not found
get_object(rev: str, path: str) -> tuple[str, str, str] | None
Get Git object information for a path.
Parameters:
rev: Git referencepath: Path within the repository
Returns: Tuple of (mode, type, hash) or None if not found
mode: Git mode (040000for tree,100644for blob)type: Object type (treeorblob)hash: Object SHA hash
build_mktree_entries(rev: str, paths: list[str]) -> str
Build a Git tree containing only specified paths.
Parameters:
rev: Git reference to extract frompaths: List of paths to include
Returns: Tree hash of the constructed tree
Raises:
ValueErrorif no valid targets foundRuntimeErrorif local path conflicts exist
read_tree(tree_hash: str)
Check out a tree hash to the output directory.
Parameters:
tree_hash: Git tree object hash to check out
Raises: subprocess.CalledProcessError if checkout fails
Registry
Manages the sync registry file and configuration.
Registry(registry_file: str)
Parameters:
registry_file: Path to the JSON registry file
Properties:
repositories: list
List of repository configurations from the registry.
cache_dir: str
Cache directory path (default: .cache in current directory).
target_dir: str
Target directory path (default: current directory).
backend: tuple[str, dict]
Backend mode and configuration. Returns tuple of (mode, config).
Key Methods:
add_revision(name_or_url: str, revision: str, paths: list[str], target: str | None = None, last_synced: str | None = None, name: str | None = None)
Add or update a repository revision in the registry.
Parameters:
name_or_url: Repository name or URLrevision: Git referencepaths: List of paths to synctarget: Optional custom target directorylast_synced: Optional last synced commit hashname: Optional custom repository name
remove_revision(name_or_url: str, revision: str, paths: list[str] | None = None)
Remove a revision or specific paths from the registry.
Parameters:
name_or_url: Repository name or URLrevision: Git reference to removepaths: Optional list of specific paths to remove (removes entire revision ifNone)
set_value(path: str, value: Any)
Set a value in the registry using dot-notation path.
Parameters:
path: Dot-separated path to fieldvalue: Value to set
unset(path: str)
Remove a field from the registry.
Parameters:
path: Dot-separated path to field
dump_registry()
Save the registry to disk if modified.
load_registry()
Load the registry from disk. Initializes empty registry if file doesn't exist.
find_repository(name_or_url: str) -> dict | None
Find a repository by name or URL.
Parameters:
name_or_url: Repository name or URL
Returns: Repository dictionary or None if not found
filter_targets(targets: list[str])
Filter registry to only process specified targets.
Parameters:
targets: List of targets in format<name_or_url>[:<revision>]
__iter__() -> Iterator[SyncData]
Iterate over all configured sync targets.
Yields: SyncData objects for each repository revision
SyncData
Data class representing a single sync target.
SyncData(name: str, url: str, rev: str, paths: list[str], target: str, last_synced: str | None, details: dict)
Attributes:
name: Repository nameurl: Repository URLrev: Git referencepaths: List of paths to synctarget: Absolute path to target output directorylast_synced: Last synced commit hash orNonedetails: Reference to registry revision details dict
Methods:
update_last_synced(commit_hash: str)
Update the last synced commit hash.
Parameters:
commit_hash: New commit hash to record
Utility Functions
sync_single(data: SyncData, cache_dir: str) -> bool
Synchronize a single revision using the mktree backend.
Parameters:
data: Sync configurationcache_dir: Cache directory path
Returns: True if synchronized, False if already up-to-date
sync_single_sparse_checkout(data: SyncData, cache_dir: str, sparse_mode: str) -> bool
Synchronize using sparse-checkout backend.
Parameters:
data: Sync configurationcache_dir: Cache directory pathsparse_mode: Either"cone"or"no-cone"
Returns: True if synchronized, False if already up-to-date
sync(registry: Registry)
Perform synchronization for all configured repositories.
Parameters:
registry: Registry instance containing sync configurations
Side Effects:
- Creates cache and target directories if needed
- Updates
last_syncedvalues in registry - Saves registry to disk
Example Usage
from grove import Registry, Repository, sync
# Initialize registry
registry = Registry('./registry.json')
# Add a repository
registry.add_revision(
name_or_url='https://github.com/owner/repo',
revision='main',
paths=['src/', 'README.md'],
target='my-project'
)
# Perform sync
sync(registry)
# Or use Repository directly
repo = Repository(
name='example',
url='https://github.com/owner/repo',
output_dir='/path/to/output',
cache_dir='/path/to/cache'
)
repo.clone()
repo.fetch()
tree_hash = repo.build_mktree_entries('main', ['src/', 'docs/'])
repo.read_tree(tree_hash)
Technical Architecture
Core Concepts
Blobless Clones
git-grove uses --filter=blob:none when cloning repositories, which only downloads Git metadata (commits, trees) without file contents. File contents are fetched on-demand, significantly reducing storage and bandwidth requirements.
Tree Building
The mktree backend constructs custom Git tree objects by:
- Querying object hashes for requested paths
- Building a tree structure (Trie) that includes only those paths
- Using
git mktreeto create tree objects from this structure - Checking out the resulting tree
This allows precise path selection without requiring a full repository checkout.
Registry-Based Management
All sync configurations are stored in a JSON registry file, enabling:
- Version control of sync configurations
- Declarative sync specifications
- Automated synchronization workflows
- Reproducible sync operations
Incremental Updates
The last_synced field tracks the last processed commit for each revision. Subsequent syncs only update when the remote reference has changed, avoiding unnecessary work.
Directory Structure
When running in default settings, git-grove creates:
.
├── registry.json # Sync configuration
├── .cache/ # Cached repository clones
│ └── <repo-name>/
│ ├── .git/ # Bare-like repository
│ └── sync_index # Custom Git index file
└── <target-dir>/ # Synced files (default: current directory)
└── <repo-name>/
└── <synced-files>
Environment Variables
When performing Git operations, git-grove sets:
GIT_DIR: Points to the cached repository's.gitdirectoryGIT_INDEX_FILE: Points to a custom index file for isolationGIT_WORK_TREE: Points to the output directory
This allows multiple repositories to be managed independently without conflicts.
Use Cases
Centralized Configuration Management
Sync configuration files from a central repository to multiple projects:
git-grove add https://github.com/company/configs main shared/eslint shared/prettier
git-grove sync
Multi-Repository Documentation
Aggregate documentation from multiple repositories:
git-grove add https://github.com/org/api-docs main docs/ --target api-docs
git-grove add https://github.com/org/sdk-docs main docs/ --target sdk-docs
git-grove sync
Shared Resource Libraries
Keep shared assets synchronized across projects:
git-grove add https://github.com/design/assets main icons/ fonts/ --target assets
git-grove sync
Multi-Revision Files
Keep files from multiple branches or commits in a single repository
git-grove add https://github.com/owner/name_a main file_a
git-grove add name_a master file_a
git-grove add name_a commit_a file_a
git-grove sync
Automated Dependency Updates
Use the GitHub Actions workflow to automatically sync and commit updates:
- Fork repositories stay in sync with upstream
- Configuration files update automatically
- Shared resources propagate to all projects
Contributing
Contributions are welcome! This project uses:
- Python 3.6+ with type hints
- Standard library where possible
- Git plumbing commands for repository operations
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file git_grove-0.0.post11.tar.gz.
File metadata
- Download URL: git_grove-0.0.post11.tar.gz
- Upload date:
- Size: 21.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
39fabf031628a11b76993d9636b1fd91ede21feb710a004424084adb7ec6ae15
|
|
| MD5 |
8df2e37463319ab20b62c365c35ae05c
|
|
| BLAKE2b-256 |
b89b736e2bc998705fabd0c4cf8032616e87897995ec50ee03b811d02a16641e
|
File details
Details for the file git_grove-0.0.post11-py3-none-any.whl.
File metadata
- Download URL: git_grove-0.0.post11-py3-none-any.whl
- Upload date:
- Size: 18.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
787454f4971c8b8643dea2af69ecd6b31318ee461937606ee1fe63d017c68911
|
|
| MD5 |
5a24e74e7db8862fad9465ce672088b5
|
|
| BLAKE2b-256 |
ccd5f257f4a67a303dff6bbaf98721c49dc9652c6c2b74d6541e9a47ac8e7f76
|