Contributing Guidelines

Welcome

Thank you for your interest in contributing to cpm! This document provides guidelines and best practices for contributing to the project. We welcome contributions of all types: code, documentation, bug reports, feature requests, and more.

Table of Contents

Code of Conduct

Our Standards

  • Be respectful and inclusive
  • Welcome newcomers and beginners
  • Focus on constructive feedback
  • Assume good intentions
  • Respect differing viewpoints

Unacceptable Behavior

  • Harassment or discrimination
  • Trolling or insulting comments
  • Personal or political attacks
  • Publishing others' private information
  • Other unprofessional conduct

Getting Started

Types of Contributions

We welcome:

  • Bug fixes: Fix issues in existing code
  • Features: Implement new functionality
  • Documentation: Improve or add documentation
  • Tests: Add or improve test coverage
  • Performance: Optimize existing code
  • Refactoring: Improve code quality
  • Examples: Add usage examples

Before You Start

  1. Check existing issues and pull requests
  2. For large changes, open an issue to discuss
  3. Ensure your idea aligns with project goals
  4. Ask questions if anything is unclear

Development Setup

Prerequisites

# Required
- Go 1.24.0 or later
- Git 2.x or later
- SQLite3 development libraries
- SSH client
- rsync

# Optional
- make (for Makefile commands)
- Docker (for testing)

Setup Steps

  1. Fork the repository:

    # Click "Fork" on GitHub
    git clone https://github.com/YOUR_USERNAME/cpm.git
    cd cpm
    
  2. Add upstream remote:

    git remote add upstream https://github.com/ORIGINAL_OWNER/cpm.git
    git fetch upstream
    
  3. Install dependencies:

    go mod download
    
  4. Build the project:

    go build -o cpm
    
  5. Run tests:

    go test ./...
    
  6. Initialize cpm:

    ./cpm config init
    

Development Environment

# Set up development config
export GITM_CONFIG=~/.cpm/dev-config.yaml
export GITM_DATA_DIR=~/cpm-dev/data

# Use development database
export GITM_DATABASE=~/cpm-dev/test.db

# Run in development mode
./cpm --verbose <command>

Contribution Workflow

1. Create a Branch

# Update main branch
git checkout main
git pull upstream main

# Create feature branch
git checkout -b feature/your-feature-name

# Or for bug fixes
git checkout -b fix/bug-description

Branch Naming Convention

  • feature/ - New features
  • fix/ - Bug fixes
  • docs/ - Documentation changes
  • refactor/ - Code refactoring
  • test/ - Test additions/changes
  • perf/ - Performance improvements

2. Make Changes

# Make your changes
vim internal/repo/repo.go

# Test your changes
go test ./internal/repo/

# Build and test manually
go build
./cpm <test-command>

3. Commit Changes

# Stage changes
git add <files>

# Commit with descriptive message
git commit -m "feat: add repository tagging support"

Commit Message Format

Use conventional commits format:

<type>(<scope>): <subject>

<body>

<footer>

Types:

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation changes
  • style: Code style changes (formatting)
  • refactor: Code refactoring
  • test: Adding or updating tests
  • perf: Performance improvements
  • chore: Maintenance tasks

Examples:

feat(ssh): add ed25519 key generation support

Implements ed25519 key pair generation with proper
permissions (0600 for private, 0644 for public).

Closes #123
fix(sync): resolve rsync timeout on large repos

Increase default timeout from 60s to 300s for large
repository transfers. Add configurable timeout option.

Fixes #456

4. Push Changes

# Push to your fork
git push origin feature/your-feature-name

5. Create Pull Request

  1. Go to GitHub repository
  2. Click "New Pull Request"
  3. Select your fork and branch
  4. Fill out PR template
  5. Submit for review

Coding Standards

Go Style Guide

Follow standard Go conventions:

// Package documentation
package repo

import (
    "fmt"
    "os"

    "cpm/internal/db"
)

// Function documentation
// InitRepo initializes a new bare git repository at the specified path.
// It creates the directory structure and runs git init --bare.
//
// Parameters:
//   name: Repository name (without .git extension)
//   path: Directory path to create repository in
//
// Returns:
//   error: Initialization error if any
func InitRepo(name, path string) error {
    // Validate inputs
    if name == "" {
        return fmt.Errorf("repository name cannot be empty")
    }

    // Implementation
    repoPath := filepath.Join(path, name+".git")

    // Create directory
    if err := os.MkdirAll(repoPath, 0755); err != nil {
        return fmt.Errorf("failed to create directory: %w", err)
    }

    // Initialize git repository
    cmd := exec.Command("git", "init", "--bare", repoPath)
    if err := cmd.Run(); err != nil {
        return fmt.Errorf("git init failed: %w", err)
    }

    return nil
}

Code Organization

internal/
├── config/          # Configuration management
│   ├── config.go    # Core config functions
│   └── config_test.go
├── db/              # Database operations
│   ├── db.go        # Connection management
│   ├── schema.go    # Table schemas
│   ├── models.go    # Data models
│   ├── crud.go      # CRUD operations
│   └── db_test.go
└── repo/            # Repository operations
    ├── repo.go      # Core repository functions
    ├── transfer.go  # Push/pull operations
    └── repo_test.go

Best Practices

  1. Error Handling: Always wrap errors with context

    if err != nil {
        return fmt.Errorf("operation failed: %w", err)
    }
    
  2. Variable Names: Use descriptive names

    // Good
    repositoryPath := "/path/to/repo"
    
    // Avoid
    p := "/path/to/repo"
    
  3. Function Length: Keep functions focused (< 50 lines)

  4. Comments: Document exported functions and complex logic

  5. Constants: Use constants for magic numbers

    const (
        DefaultSSHPort = 22
        DefaultTimeout = 300 * time.Second
    )
    

Testing Guidelines

Unit Tests

func TestInitRepo(t *testing.T) {
    // Setup
    tmpDir := t.TempDir()
    repoName := "test-repo"

    // Execute
    err := InitRepo(repoName, tmpDir)

    // Assert
    if err != nil {
        t.Fatalf("InitRepo failed: %v", err)
    }

    // Verify repository exists
    repoPath := filepath.Join(tmpDir, repoName+".git")
    if _, err := os.Stat(repoPath); os.IsNotExist(err) {
        t.Errorf("Repository not created at %s", repoPath)
    }
}

Table-Driven Tests

func TestValidateUsername(t *testing.T) {
    tests := []struct {
        name     string
        username string
        wantErr  bool
    }{
        {"valid", "alice", false},
        {"valid-hyphen", "alice-smith", false},
        {"empty", "", true},
        {"invalid-chars", "alice@example", true},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            err := ValidateUsername(tt.username)
            if (err != nil) != tt.wantErr {
                t.Errorf("ValidateUsername() error = %v, wantErr %v", err, tt.wantErr)
            }
        })
    }
}

Test Coverage

Aim for 80%+ test coverage:

# Run tests with coverage
go test -cover ./...

# Generate coverage report
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out

Integration Tests

// +build integration

func TestEndToEnd(t *testing.T) {
    // Setup test environment
    // Run full workflow
    // Verify end-to-end functionality
}

Run integration tests:

go test -tags=integration ./...

Documentation

Code Documentation

Document all exported functions:

// CreateUser creates a new user in the database with the provided details.
// The username must be unique. If a public key is provided, it will be
// validated before storage.
//
// Parameters:
//   username: Unique username (alphanumeric, hyphens, underscores)
//   email: Valid email address
//   publicKey: SSH public key in authorized_keys format (optional)
//
// Returns:
//   int64: New user ID
//   error: Creation error (e.g., duplicate username, invalid email)
//
// Example:
//   userID, err := db.CreateUser("alice", "alice@example.com", "ssh-ed25519 AAAA...")
//   if err != nil {
//       log.Fatal(err)
//   }
func (db *DB) CreateUser(username, email, publicKey string) (int64, error) {
    // Implementation
}

User Documentation

Update relevant documentation files:

  • Command reference for new commands
  • Architecture docs for system changes
  • Troubleshooting for known issues
  • Examples for new features

Changelog

Add entries to CHANGELOG.md:

## [Unreleased]

### Added
- Repository tagging support for categorization

### Fixed
- Sync timeout on repositories larger than 1GB

### Changed
- Default SSH key algorithm to ed25519

Pull Request Process

PR Checklist

Before submitting:

  • Code follows project style guidelines
  • Tests added for new functionality
  • All tests pass locally
  • Documentation updated
  • Commit messages follow convention
  • Branch is up to date with main
  • Self-review completed

PR Template

Fill out the PR template:

## Description
Brief description of changes

## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update

## Testing
Describe testing performed

## Screenshots (if applicable)
Add screenshots for UI changes

## Checklist
- [ ] Tests pass
- [ ] Documentation updated
- [ ] Changelog updated

Review Process

  1. Automated Checks: CI/CD runs tests
  2. Code Review: Maintainers review code
  3. Revisions: Address feedback
  4. Approval: At least one maintainer approval
  5. Merge: Maintainer merges PR

Addressing Feedback

# Make requested changes
git add <files>
git commit -m "address review feedback"

# Update PR
git push origin feature/your-feature-name

Release Process

Versioning

We use Semantic Versioning (SemVer):

  • MAJOR: Breaking changes
  • MINOR: New features (backward compatible)
  • PATCH: Bug fixes

Release Workflow

  1. Update CHANGELOG.md
  2. Update version in code
  3. Create git tag
  4. Build binaries
  5. Create GitHub release
  6. Announce release

Community

Communication Channels

  • GitHub Issues: Bug reports, feature requests
  • GitHub Discussions: Questions, ideas
  • Pull Requests: Code contributions

Getting Help

  • Check documentation
  • Search existing issues
  • Ask in GitHub Discussions
  • Tag maintainers if urgent

Recognition

Contributors are recognized in:

  • CONTRIBUTORS.md file
  • Release notes
  • Project documentation

Questions?

If you have questions about contributing:

  1. Check this guide
  2. Review existing issues/PRs
  3. Ask in GitHub Discussions
  4. Contact maintainers

Thank You!

Your contributions make cpm better for everyone. We appreciate your time and effort!


Happy Contributing!