Git Integration

Overview

IVPM provides deep integration with Git for managing source dependencies. This includes cloning repositories, tracking changes, and synchronizing with upstream sources.

Clone Options

Authentication and Transport

When cloning a Git dependency, IVPM chooses between SSH (git@host:path) and HTTPS (https://host/path) per host using a configurable auth order. The HTTPS form lets a credential helper – most commonly the GitHub CLI (gh) – authenticate, while the SSH form relies on your SSH key / agent.

An auth order is a list of methods, tried left to right; the first applicable one wins:

gh

Clone the https:// URL as written if gh is installed and authenticated for that host (its credential helper does the auth). Skipped when gh is absent or not logged in.

ssh

Rewrite the URL to git@host:path. Always applicable (terminal).

https

Clone the https:// URL exactly as written. Always applicable (terminal).

The default order is gh, ssh: prefer gh when it can authenticate the host, otherwise fall back to SSH (the historical behavior). A machine without gh therefore behaves exactly as before. file:// URLs and non-URL local paths are never rewritten.

The gh auth status probe is cached per host, so an update fetching many dependencies from one host only runs gh once.

Forcing a transport

Per-package in ivpm.yaml:

deps:
  - name: my-lib
    url: https://github.com/org/my-lib.git
    ssh: true          # force SSH: git@github.com:org/my-lib.git

  - name: other-lib
    url: https://github.com/org/other-lib.git
    ssh: false         # force HTTPS exactly as written

ssh: true forces SSH; ssh: false forces the URL as written. The legacy anonymous: true also forces the URL as written; anonymous: false no longer forces SSH – it defers to the auth order (use ssh: true to force SSH).

Command-line (applies to both clone and update):

$ ivpm clone --ssh https://github.com/org/project.git        # force SSH
$ ivpm clone --anonymous https://github.com/org/project.git  # force HTTPS
$ ivpm clone --git-auth-order gh,https https://github.com/org/project.git

Configuring the auth order

Without a per-package or CLI override, the order is resolved per host. Full precedence, most specific first:

  1. Per-package ssh: / anonymous:.

  2. CLI --ssh / --anonymous.

  3. CLI --git-auth-order gh,ssh,https (flat, this invocation only).

  4. A host-glob rule from the config files / site config – first match wins across: user file, then site file, then the ivpm_site_config package.

  5. Default order: IVPM_GIT_AUTH_ORDER env, then the user config file, then the site config file, then the built-in default (gh, ssh).

A matching host rule beats the flat IVPM_GIT_AUTH_ORDER – the env var is the default for unmatched hosts. --git-auth-order is flat and bypasses host matching for that one command.

Config files

Two declarative files share one schema and are checked user first, then site (a user rule overrides a site rule; the first git-auth-order found wins):

Scope

Default path

Override env

User

$XDG_CONFIG_HOME/ivpm/config.yaml (else ~/.config/ivpm/config.yaml)

IVPM_USER_CONFIG

Site

/etc/ivpm/config.yaml

IVPM_SITE_CONFIG

# Default order for hosts without a matching rule (optional)
git-auth-order: [gh, ssh]

# Per-host rules, most specific first; host is an fnmatch glob (optional)
git-auth:
  - host: "*.internal.corp"   # internal GHE/GitLab -> always SSH
    order: [ssh]
  - host: "github.com"        # public GitHub -> gh, fall back to SSH
    order: [gh, ssh]

Missing files are ignored. A malformed file (bad YAML, or a top level that is not a mapping) is logged at WARNING and skipped – it never aborts a clone.

Org-managed defaults

Sites can set auth defaults programmatically with a SiteConfig subclass (the lowest-priority rule source; the config files layer on top). The recommended way to ship one is an extension that declares an ivpm.site_config entry point (see Writing Custom Handlers); the legacy ivpm_site_config module is also honored.

# src/acme_ivpm/site_config.py
from ivpm.site_config import SiteConfig

class MySiteConfig(SiteConfig):
    def get_default_git_auth_order(self):
        return ["gh", "ssh"]

    def get_git_auth_rules(self):
        return [
            ("*.internal.corp", ["ssh"]),
            ("github.com",      ["gh", "ssh"]),
        ]
# pyproject.toml of the extension package
[project.entry-points."ivpm.site_config"]
acme = "acme_ivpm.site_config:MySiteConfig"

Run ivpm show site-config to confirm the config is registered and to see the resolved auth order it applies. When several site configs are installed, pin one with IVPM_SITE_CONFIG_NAME or a site-config: <name> config-file key.

Diagnostics

Run a clone with --log-level DEBUG to see how the transport was chosen – the effective URL, the resolved auth order, the loaded config files, and the relevant credentials (SSH agent/keys for git@; gh auth status and the git credential helper for https):

$ ivpm clone --log-level DEBUG https://github.com/org/project.git

URL Formats

IVPM supports multiple Git URL formats:

# HTTPS (transport chosen by the auth order: default gh, else SSH)
- name: lib1
  url: https://github.com/org/lib1.git

# SSH (used directly)
- name: lib2
  url: git@github.com:org/lib2.git

# File protocol (local; never rewritten)
- name: lib3
  url: file:///path/to/repo.git

# Git protocol
- name: lib4
  url: git://github.com/org/lib4.git

Version Selection

Branch Selection

Clone a specific branch:

deps:
  - name: my-lib
    url: https://github.com/org/my-lib.git
    branch: develop

Behavior:

  • Checks out the specified branch

  • Tracks the remote branch

  • Can be updated with ivpm sync

Command-line (clone):

$ ivpm clone https://github.com/org/project.git -b feature/new

If the branch exists remotely, it tracks it. If not, creates a new local branch.

Tag Selection

Clone a specific release tag:

deps:
  - name: my-lib
    url: https://github.com/org/my-lib.git
    tag: v1.2.0

Behavior:

  • Checks out the specified tag

  • Detached HEAD state

  • Immutable (won’t change with sync)

Use case: Production deployments, reproducible builds

Commit Selection

Clone to a specific commit:

deps:
  - name: my-lib
    url: https://github.com/org/my-lib.git
    commit: abc123def456

Behavior:

  • Checks out exact commit

  • Detached HEAD state

  • Maximum reproducibility

Use case: Pinning exact versions for critical dependencies

Default Behavior

No branch/tag/commit specified:

deps:
  - name: my-lib
    url: https://github.com/org/my-lib.git

Clones the repository’s default branch (usually main or master).

Clone Depth

Shallow Clones

Limit history depth for faster cloning:

deps:
  - name: large-repo
    url: https://github.com/org/large-repo.git
    depth: 1

Benefits:

  • Faster clone

  • Less disk space

  • Reduced bandwidth

Limitations:

  • Limited history

  • Cannot easily switch branches

  • Some Git operations restricted

Depth values:

  • depth: 1 - Only latest commit

  • depth: 10 - Last 10 commits

  • Unspecified - Full history (default)

Full History

deps:
  - name: my-lib
    url: https://github.com/org/my-lib.git
    # No depth specified = full history

Benefits:

  • Complete history

  • Full Git functionality

  • Easy branch switching

  • Better for development

Use when:

  • Actively developing the package

  • Need to browse history

  • Switching between branches/tags

When to Use Each

Scenario

Recommendation

Reason

Active development

Full history

Need Git features

Cached dependency

depth: 1

Speed, space

Read-only use

depth: 1

Speed, space

Production deploy

depth: 1 + tag

Minimal, reproducible

CI/CD

depth: 1

Speed

Git Submodules

Automatic Support

IVPM automatically initializes submodules when .gitmodules is present:

deps:
  - name: repo-with-submodules
    url: https://github.com/org/repo.git

What happens:

  1. Clone main repository

  2. Detect .gitmodules

  3. Run git submodule update --init --recursive

  4. All submodules ready to use

No Configuration Needed

Submodule initialization is automatic - no special configuration required.

Nested Submodules

The --recursive flag handles nested submodules automatically.

Example Structure:

repo/
├── .gitmodules
├── submodule1/
│   └── .gitmodules
│       └── nested-submodule/
└── submodule2/

All levels initialized automatically.

Status Command

Checking Package Status

View the status of all Git dependencies:

$ ivpm status

Output example:

Package: my-library
  Path: packages/my-library
  Branch: main
  Status: Clean
  Remote: origin/main
  Ahead: 0, Behind: 0

Package: test-utils
  Path: packages/test-utils
  Branch: develop
  Status: Modified
  Modified files:
    M  src/utils.py
    ?? new_file.py
  Remote: origin/develop
  Ahead: 2, Behind: 1

What It Shows

For each Git package:

  • Package name

  • Local path

  • Current branch

  • Status: Clean, Modified, Staged, Ahead/Behind

  • Modified files (if any)

  • Untracked files (if any)

  • Commits ahead/behind remote

Use Cases

Before committing:

$ ivpm status  # Check for uncommitted changes

After pulling:

$ git pull
$ ivpm status  # Check dependency status

Daily standup:

$ ivpm status  # Review what you're working on

Status for Specific Package

$ cd packages/my-library
$ git status

Sync Command

Synchronizing with Upstream

Update all Git packages from their remote origins:

$ ivpm sync

What it does:

For each Git package on a branch:

  1. git fetch origin

  2. git merge origin/<branch>

Packages NOT synced:

  • Packages on tags (immutable)

  • Packages on specific commits (immutable)

  • Packages with uncommitted changes (safety)

Handling Conflicts

If ivpm sync encounters merge conflicts:

$ ivpm sync
# Error in packages/my-library

$ cd packages/my-library
$ git status
# Resolve conflicts manually
$ git add resolved-file.py
$ git commit
$ cd ../..
$ ivpm sync  # Continue with remaining packages

Selective Sync

Sync specific packages manually:

$ cd packages/specific-package
$ git pull
$ cd ../..

Safe Sync Strategy

# Check status first
$ ivpm status

# Stash local changes if needed
$ cd packages/my-library
$ git stash
$ cd ../..

# Sync
$ ivpm sync

# Restore changes
$ cd packages/my-library
$ git stash pop
$ cd ../..

When NOT to Sync

Don’t sync if:

  • You have uncommitted changes you want to keep

  • You’re working on a feature branch

  • You’ve pinned to a specific commit/tag

  • You’re testing local modifications

Complete Git Workflows

Workflow 1: Contributing to a Dependency

# 1. Create feature branch
$ cd packages/dependency
$ git checkout -b fix/issue-123

# 2. Make changes
$ vim src/file.py
$ git add src/file.py
$ git commit -m "Fix issue 123"

# 3. Push to fork
$ git remote add myfork git@github.com:me/dependency.git
$ git push myfork fix/issue-123

# 4. Create pull request on GitHub

# 5. After merge, switch back
$ git checkout main
$ git pull
$ cd ../..

Workflow 2: Testing Upstream Changes

# 1. Check current status
$ ivpm status

# 2. Fetch latest
$ cd packages/my-library
$ git fetch origin

# 3. Check what's new
$ git log HEAD..origin/main

# 4. Test changes
$ git checkout origin/main  # Detached HEAD
$ cd ../..
$ ivpm activate -c "pytest"

# 5. If good, merge
$ cd packages/my-library
$ git checkout main
$ git merge origin/main
$ cd ../..

Workflow 3: Bisecting a Bug

# Start bisect in dependency
$ cd packages/buggy-lib
$ git bisect start
$ git bisect bad  # Current version is bad
$ git bisect good v1.0.0  # Known good version

# Test each commit
$ cd ../..
$ ivpm activate -c "pytest"
$ cd packages/buggy-lib
$ git bisect good  # or 'bad'

# Repeat until found

# Done
$ git bisect reset
$ cd ../..

Workflow 4: Pinning After Testing

# 1. Test current state
$ ivpm activate -c "pytest"

# 2. Get current commit
$ cd packages/my-library
$ git rev-parse HEAD
abc123def456...
$ cd ../..

# 3. Pin in ivpm.yaml
# - name: my-library
#   url: https://github.com/org/my-library.git
#   commit: abc123def456

# 4. Verify
$ rm -rf packages/my-library
$ ivpm update
$ ivpm activate -c "pytest"

Integration with Git Hooks

Pre-commit Hook

Check package status before committing:

# .git/hooks/pre-commit
#!/bin/bash

echo "Checking IVPM package status..."
if ivpm status | grep -q "Modified\|Untracked"; then
    echo "Warning: Uncommitted changes in dependencies"
    ivpm status | grep -A 10 "Modified\|Untracked"
    read -p "Continue anyway? (y/n) " -n 1 -r
    echo
    if [[ ! $REPLY =~ ^[Yy]$ ]]; then
        exit 1
    fi
fi

Post-merge Hook

Update dependencies after merge:

# .git/hooks/post-merge
#!/bin/bash

if git diff-tree --name-only HEAD@{1} HEAD | grep -q "ivpm.yaml"; then
    echo "ivpm.yaml changed, running ivpm update..."
    ivpm update
fi

Advanced Patterns

Multiple Remotes

$ cd packages/my-library

# Add upstream remote
$ git remote add upstream https://github.com/org/my-library.git

# Fetch from upstream
$ git fetch upstream

# Merge upstream changes
$ git merge upstream/main

$ cd ../..

Working with Forks

# Use your fork
deps:
  - name: my-library
    url: https://github.com/myuser/my-library.git
    branch: my-feature
$ cd packages/my-library

# Add original as upstream
$ git remote add upstream https://github.com/org/my-library.git
$ git fetch upstream

# Keep fork updated
$ git merge upstream/main
$ git push origin main

Sparse Checkout

For very large repositories, use sparse checkout:

$ cd packages/huge-repo
$ git sparse-checkout init --cone
$ git sparse-checkout set path/to/needed/files
$ cd ../..

Best Practices

  1. Use branches for development - Not tags or commits

  2. Commit before sync - Don’t lose local changes

  3. Regular status checks - Know what’s modified

  4. Document pin decisions - Comment why you pinned a commit

  5. Use tags for releases - Immutable, semantic

  6. Shallow clones for cache - Speed up cached dependencies

  7. Full history for dev - Better debugging and history

  8. Check before updating - ivpm status first

  9. Stash when needed - git stash for temporary changes

  10. Use .gitignore - Never commit packages/ directory

Troubleshooting

Detached HEAD State

Symptom: Git says “detached HEAD”

Cause: Checked out a tag or commit

Solution:

$ cd packages/my-library
$ git checkout main  # Or any branch
$ cd ../..

Merge Conflicts During Sync

$ cd packages/conflicted-package
$ git status
$ # Resolve conflicts in files
$ git add resolved-files
$ git commit
$ cd ../..

Cannot Push Changes

Symptom: Permission denied when pushing

Check:

  1. Is remote URL correct?

  2. Do you have write access?

  3. Is SSH key configured?

Solution:

$ cd packages/my-library
$ git remote -v  # Check URL
$ git remote set-url origin git@github.com:me/my-library.git
$ cd ../..

Submodule Issues

Symptom: Submodules not initialized

Solution:

$ cd packages/repo-with-submodules
$ git submodule update --init --recursive
$ cd ../..

Worktrees

When you check out a branch into a linked git worktree, IVPM automatically reuses the already-materialized packages from your main worktree – unchanged dependencies resolve from local disk instead of being re-cloned, with no configuration required. See Git Worktrees for the full story.

See Also