commit 105ed85fd04daa2db2a620fb76315a30071e7a40 Author: Zhongwei Li Date: Sun Nov 30 09:07:33 2025 +0800 Initial commit diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..0164112 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,15 @@ +{ + "name": "git", + "description": "Git Development Workflows", + "version": "1.1.1", + "author": { + "name": "Meng Yan", + "email": "myan@redhat.com" + }, + "skills": [ + "./skills" + ], + "commands": [ + "./commands" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..4981481 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# git + +Git Development Workflows diff --git a/commands/commit-push.md b/commands/commit-push.md new file mode 100644 index 0000000..22af501 --- /dev/null +++ b/commands/commit-push.md @@ -0,0 +1,35 @@ +--- +argument-hint: "[--no-push] or leave empty to commit and push" +description: "Commit changes with sign-off and push to origin by default" +allowed-tools: [Bash] +--- + +Commit staged and unstaged changes with proper sign-off, and push to origin branch by default. + +## Implementation Steps + +1. **Check Repository Status**: Run git status and git diff to review changes that will be committed +2. **Review Recent Commits**: Check git log to follow existing commit message style +3. **Stage and Commit Changes**: Add relevant files and create signed commit with descriptive message following conventional format +4. **Push to Origin** (unless --no-push specified): Push committed changes to the current origin branch + +## Usage Examples + +- `/git:commit-push` - Commit with sign-off and push to origin (default) +- `/git:commit-push --no-push` - Commit with sign-off only, skip push + +## Implementation Details + +The command will: +- Analyze changes with `git status` and `git diff` +- Check recent commits with `git log --oneline -5` for style consistency +- Stage relevant modified and new files (avoiding unnecessary config files) +- Create commit with `-s` flag for sign-off and conventional format +- Push to origin by default unless `--no-push` parameter is provided + +## Notes +- Follows conventional commit format with descriptive messages +- Always includes sign-off as per user preferences +- Avoids staging unnecessary files like configuration files +- Verifies commit success before push (unless disabled with --no-push) +- Respects current branch for push operations \ No newline at end of file diff --git a/commands/compact-commits.md b/commands/compact-commits.md new file mode 100644 index 0000000..808a378 --- /dev/null +++ b/commands/compact-commits.md @@ -0,0 +1,23 @@ +--- +argument-hint: [pr_number] +description: Compact all commits in a GitHub PR into a single commit with comprehensive message and DCO sign-off +allowed-tools: [Bash] +--- + +Compact multiple commits in a GitHub Pull Request into a single commit while preserving all changes and creating a comprehensive commit message with proper DCO sign-off. + +## Implementation Steps + +1. **Examine PR Structure**: View the PR details and commit history to understand what needs to be compacted using `gh pr view $1` +2. **Checkout PR Branch**: Switch to the PR branch using `gh pr checkout $1` to work with the commits locally +3. **Compact Commits**: Use `git reset --soft HEAD~N` (where N is number of commits) to stage all changes from multiple commits, then create a single comprehensive commit with `git commit --signoff` +4. **Force Push Update**: Push the compacted commit back to the PR branch using `git push --force-with-lease` to update the remote PR +5. **Verify Results**: Confirm the PR now shows only one commit with all the original changes preserved and proper DCO sign-off + +## Notes +- This command is useful for cleaning up PR history before merging +- Preserves all code changes while creating a clean, single commit +- Includes DCO sign-off required by many projects +- The comprehensive commit message includes all relevant details from the original commits +- Uses `--force-with-lease` for safer force pushing +- Works with any GitHub repository that has PR access \ No newline at end of file diff --git a/commands/create-pr.md b/commands/create-pr.md new file mode 100644 index 0000000..ee80e35 --- /dev/null +++ b/commands/create-pr.md @@ -0,0 +1,77 @@ +# Create PR to $ARGUMENTS + +Create a pull request following Git best practices: + +## Steps: + +1. **Sync & Branch**: Update main + create feature branch with appropriate prefix +2. **Commit**: Stage relevant files + commit with conventional message and sign-off +3. **Push & PR**: Push branch + create ready PR with detailed description + +## Target: +- If $ARGUMENTS provided: use as target repo/branch +- If empty: default to upstream/main + +## Commands to execute: + +```bash +# Set target +TARGET=${ARGUMENTS:-upstream/main} + +# 1. Sync & Branch +CURRENT_BRANCH=$(git branch --show-current) +if [[ "$CURRENT_BRANCH" == "main" || "$CURRENT_BRANCH" == "master" ]]; then + # Sync with upstream first + git fetch origin + git pull origin main + + # Create feature branch with prefix + # Choose: feature/, fix/, docs/, chore/, refactor/, test/ + git checkout -b feature/descriptive-name +fi + +# 2. Stage & Commit with conventional format +git add path/to/relevant/files +git commit -s -m "feat: descriptive title + +Detailed description of what and why this change is made. + +- List specific changes +- Reference issue numbers if applicable" + +# 3. Push & Create PR +git push -u origin $(git branch --show-current) +gh pr create --base ${TARGET#*/} --title "Title" --body "$(cat <<'EOF' +## Summary +Brief description of the change and its purpose + +## Changes +- Specific change 1 +- Specific change 2 +- Reference any related issues + +## Test plan +- [ ] Unit tests pass +- [ ] Integration tests pass +- [ ] Manual testing completed +- [ ] Documentation updated if needed + +## Checklist +- [ ] Code follows project conventions +- [ ] Tests added/updated +- [ ] Documentation updated +- [ ] Breaking changes documented + +๐Ÿค– Generated with [Claude Code](https://claude.ai/code) +EOF +)" +``` + +## Conventional Commit Types: +- **feat**: New features +- **fix**: Bug fixes +- **docs**: Documentation changes +- **style**: Code style changes (formatting, etc.) +- **refactor**: Code refactoring +- **test**: Adding/updating tests +- **chore**: Maintenance tasks \ No newline at end of file diff --git a/commands/create-worktree.md b/commands/create-worktree.md new file mode 100644 index 0000000..4b4f7ad --- /dev/null +++ b/commands/create-worktree.md @@ -0,0 +1,57 @@ +--- +argument-hint: [worktree-name-or-description] [source-branch] [--cursor|--code|--tmux] +description: Create a git worktree with intelligent naming and branch creation +allowed-tools: [Bash, TodoWrite] +--- + +Creates a new git worktree with automatic naming conventions and branch management. Supports optional parameters for customization and integration with editors/tmux. + +## Implementation Steps + +1. **Plan Worktree Creation**: Create todo list to track worktree creation and navigation tasks + +2. **Parse Arguments**: + - Check if arguments contain `--cursor`, `--code`, or `--tmux` flags + - Extract worktree name/description from remaining arguments (filter out flags) + - Extract source branch if provided (second non-flag argument) + +3. **Determine Worktree Configuration**: + - If worktree name provided: Use as worktree name/description to generate appropriate directory name and branch name + - If not provided: Analyze current git status and staged changes to auto-generate meaningful worktree and branch names + - Use source branch if provided, otherwise default to current branch + +4. **Create Worktree with Smart Naming**: + - Generate worktree directory using format: `../project-name__feature-name` (double underscore separator) + - Create new branch with descriptive name based on the worktree purpose + - Use command: `git worktree add ../worktree-directory -b branch-name source-branch` + +5. **Open in Editor/Terminal** (if flag specified): + - If `--cursor` flag: Run `cursor ` to open in Cursor editor + - If `--code` flag: Run `code ` to open in VS Code + - If `--tmux` flag: Run `tmux split-window -h -c ` to split pane in current tmux session and cd to worktree + - If no flag: Skip this step + +6. **Complete Task Tracking**: Mark all todo items as completed + +## Notes +- Uses double underscore (`__`) separator between project name and feature name for clear visual distinction +- Automatically handles branch name conflicts by generating unique names +- If no parameters provided, analyzes git diff and status to suggest appropriate names +- Worktree is created in parent directory to avoid conflicts with current repository +- Source branch defaults to current branch if not specified + +## Editor/Terminal Integration +- `--cursor`: Opens the worktree in Cursor editor after creation +- `--code`: Opens the worktree in VS Code after creation +- `--tmux`: Creates a horizontal split pane in current tmux session and navigates to worktree +- Flags can be placed anywhere in the command arguments +- Only one editor/terminal flag should be used at a time + +## Usage Examples +- `/git:create-worktree integration-logs` - Creates worktree for integration log work +- `/git:create-worktree "fix auth bug" develop` - Creates worktree from develop branch for auth fix +- `/git:create-worktree` - Auto-generates name based on current changes +- `/git:create-worktree fix-e2e --cursor` - Creates worktree and opens in Cursor editor +- `/git:create-worktree "add feature" --code` - Creates worktree and opens in VS Code +- `/git:create-worktree refactor develop --tmux` - Creates worktree from develop and opens in new tmux pane +- `/git:create-worktree --cursor bug-fix` - Creates worktree and opens in Cursor (flag can be anywhere) \ No newline at end of file diff --git a/commands/rebase-pr.md b/commands/rebase-pr.md new file mode 100644 index 0000000..e3930b3 --- /dev/null +++ b/commands/rebase-pr.md @@ -0,0 +1,118 @@ +--- +argument-hint: [pr_number] [remote] - PR number (optional, defaults to all PRs) and remote name (optional, defaults to PR's target remote) +description: Rebase a PR or all open PRs against their target base branch and force push updates +allowed-tools: [Bash, TodoWrite, Read, Edit] +--- + +Rebase a pull request against its target base branch (from the PR's merge target) and force push the updates. If no PR number is specified, rebases all open PRs. + +## Usage + +### Rebase All Open PRs +```bash +/git:rebase-pr [remote] +``` + +### Rebase Single PR +```bash +/git:rebase-pr [remote] +``` + +## Implementation Steps + +### Determine Mode + +1. **Parse Arguments**: Check if first argument is a PR number or a remote name + - If first argument is numeric: Single PR mode + - If first argument is non-numeric or empty: Batch mode (all PRs) + - Extract remote name from appropriate position (optional) + +### Single PR Mode (when pr_number is provided) + +1. **Get PR Information**: Use `gh pr view --json baseRefName,headRepository,isCrossRepository` to get: + - `baseRefName`: The target branch this PR will merge into (e.g., "main", "develop") + - `headRepository`: The source repository info + - `isCrossRepository`: Whether this is a fork PR +2. **Determine Remote and Base Branch**: + - If `remote` argument is provided: Use `/` + - If PR is cross-repository (fork): Use `upstream/` + - Otherwise: Use `origin/` +3. **Fetch Latest Changes**: Run `git fetch ` to ensure we have the latest commits from the target remote +4. **Checkout PR Branch**: Switch to the PR branch using `gh pr checkout ` +5. **Rebase Against Target Base**: Rebase current branch against `/` +6. **Handle Conflicts**: If conflicts occur, stop and report to user for manual resolution +7. **Force Push Updates**: Push the rebased branch with `--force-with-lease` to update the PR safely + +### Batch Mode (when no pr_number is provided) + +1. **Create Todo List**: Create a todo list to track all PRs that need rebasing +2. **List All Open PRs**: Use `gh pr list --author "@me" --state open --json number,headRefName,baseRefName,isCrossRepository` to get all user's open PRs with their target branches +3. **Check Worktrees**: Use `git worktree list` to identify which PRs are in worktrees vs main directory +4. **Process Each PR Sequentially**: + - Update todo status to "in_progress" for current PR + - Get PR's `baseRefName` and `isCrossRepository` from the PR list + - Determine remote: Use provided remote, or `upstream` for forks, or `origin` for same-repo PRs + - Fetch latest changes: `git fetch ` + - For worktree branches: Navigate to worktree directory and run `git rebase /` + - For non-worktree branches: Use `gh pr checkout ` then rebase against `/` + - If rebase succeeds and branch is up-to-date: Mark as completed, continue to next PR + - If rebase succeeds with changes: Force push with `--force-with-lease`, mark as completed + - If rebase has conflicts: + - Read the conflicted files + - Resolve conflicts using Edit tool + - Stage resolved files with `git add` + - Continue rebase with `git rebase --continue` + - Force push after successful resolution + - Mark PR as completed in todo list + - Handle push failures (e.g., network issues) with retry logic +5. **Summary Report**: Display final status of all PRs (success/failed/skipped) + +## Parameters + +- `pr_number`: The GitHub PR number to rebase (optional, if not provided will rebase all open PRs) +- `remote`: The remote name to fetch and rebase against (optional, defaults to `upstream` for fork PRs or `origin` for same-repo PRs) + +## Examples + +```bash +# Rebase all open PRs against their target branches (auto-detects upstream/origin) +/git:rebase-pr + +# Rebase all open PRs, explicitly using upstream remote +/git:rebase-pr upstream + +# Rebase all open PRs using origin remote +/git:rebase-pr origin + +# Rebase single PR #2067 against its target branch (auto-detects remote) +/git:rebase-pr 2067 + +# Rebase single PR #2067 using upstream remote +/git:rebase-pr 2067 upstream + +# Rebase single PR #2067 using origin remote +/git:rebase-pr 2067 origin +``` + +## Notes + +- **Automatic Mode Detection**: The command automatically detects whether to rebase a single PR or all PRs based on whether a numeric PR number is provided +- **Target Branch Detection**: Each PR is rebased against its actual target branch (from `baseRefName`) rather than a hardcoded branch. For example: + - PR targeting `main` โ†’ rebases against `upstream/main` or `origin/main` + - PR targeting `develop` โ†’ rebases against `upstream/develop` or `origin/develop` + - PR targeting `release-1.0` โ†’ rebases against `upstream/release-1.0` or `origin/release-1.0` +- **Remote Selection**: + - For fork PRs (`isCrossRepository: true`): Defaults to `upstream` remote + - For same-repo PRs: Defaults to `origin` remote + - User can override by providing explicit remote name +- **Conflict Handling**: When conflicts occur in batch mode, the tool will attempt to resolve simple conflicts automatically using the Edit tool. For complex conflicts, it will stop and ask user for guidance +- **Worktree Support**: The command automatically detects and handles PRs in git worktrees by navigating to the worktree directory before rebasing +- **Force Push Safety**: Uses `--force-with-lease` for safer force pushing that prevents overwriting unexpected changes +- **Todo Tracking**: Uses TodoWrite tool to track progress when rebasing multiple PRs +- **Network Issues**: If push fails due to network issues, the command will retry once before reporting failure +- **Skip Up-to-date PRs**: PRs that are already up-to-date with the base branch are quickly marked as completed without requiring a push +- **Prerequisites**: + - GitHub CLI (`gh`) must be authenticated + - Must have appropriate permissions to push to PR branch + - The appropriate remote (`upstream` or `origin`) must be configured +- **Sign-off**: Remember to sign-off commits if making any new commits during the conflict resolution process \ No newline at end of file diff --git a/commands/review-pr.md b/commands/review-pr.md new file mode 100644 index 0000000..3100053 --- /dev/null +++ b/commands/review-pr.md @@ -0,0 +1,49 @@ +# Review PR $ARGUMENTS + +```bash +# Show open PRs if no argument +[[ -z "$ARGUMENTS" ]] && gh pr list --state open && exit 0 + +PR_NUMBER="$ARGUMENTS" +REPO_INFO=$(gh repo view --json owner,name --jq '.owner.login + "/" + .name') +COMMIT_SHA=$(gh api repos/$REPO_INFO/pulls/$PR_NUMBER --jq '.head.sha') + +# Quick overview +gh pr view $PR_NUMBER +gh pr checks $PR_NUMBER +gh pr diff $PR_NUMBER +``` + +## What to Look For + +๐Ÿ”’ **Security first**: Auth, input validation, secrets, injection attacks +๐Ÿ› **Logic bugs**: Edge cases, race conditions, error handling +๐Ÿ—๏ธ **Code quality**: Patterns, duplication, naming, complexity +~~ ๐Ÿงช **Tests**: Coverage, meaningful scenarios, integration tests ~~ +๐Ÿ“ **Docs**: Clear code, comments where needed, breaking changes + +## Leave Comments + +**For issues (blocking):** +```bash +gh api repos/$REPO_INFO/pulls/$PR_NUMBER/comments --method POST \ + --field body="๐Ÿ”’ Security risk: [issue]. Fix: [solution]" \ + --field commit_id="$COMMIT_SHA" --field path="file.js" --field line=42 --field side="RIGHT" +``` + +**For suggestions:** +```bash +gh api repos/$REPO_INFO/pulls/$PR_NUMBER/comments --method POST \ + --field body="๐Ÿ’ก Consider [improvement] for [benefit]" \ + --field commit_id="$COMMIT_SHA" --field path="file.js" --field line=42 --field side="RIGHT" +``` + +## Final Decision + +```bash +# Block it +gh pr review $PR_NUMBER --request-changes --body "Security/bug issues found" + +# Ship it +gh pr review $PR_NUMBER --approve --body "LGTM" +``` \ No newline at end of file diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..1f5c27b --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,85 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:yanmxa/cc-plugins:plugins/git", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "f86166bff717c5adfe7ed70c74ef93ef5bc08c2c", + "treeHash": "b1a3174a75d23221475a521800af84cfd3914d957e9a7eda050eb11169276fa0", + "generatedAt": "2025-11-28T10:29:09.576060Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "git", + "description": "Git Development Workflows", + "version": "1.1.1" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "332bd388871352973e11a3ea0d915231d553a6e2e95fc4cd6c0f03ab31da12bb" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "cd69ce3b3e39045d5da0c08a34a78e0118b34af6171aa6a91acbbac305486299" + }, + { + "path": "commands/review-pr.md", + "sha256": "f7011605ade6baa1ce978c3f185bbbe7b0e8e9c775217472f69ffcd3780481b9" + }, + { + "path": "commands/compact-commits.md", + "sha256": "a31bce01d000dc13acc8926341c3079bdd13cf36b1c3977c116d32bc9d03f54a" + }, + { + "path": "commands/create-pr.md", + "sha256": "1aa2b5fc19c9c28d0a9d73886c04f1272c636ac09f18ad6d497486108b672005" + }, + { + "path": "commands/create-worktree.md", + "sha256": "3a12abcf95442df516044073848888a8e829538e878891a28c2474c49db18c46" + }, + { + "path": "commands/commit-push.md", + "sha256": "d2b4215bd7b2a5e2a43873507d1d918b6635081fe7d9191f208cb45cb5b2c2b4" + }, + { + "path": "commands/rebase-pr.md", + "sha256": "fb9dadb83a1f31c40bb1de0b83468050ddb0f0118da9c972750e0ac8da32a266" + }, + { + "path": "skills/pr/SKILL.md", + "sha256": "b53e79a949eae4cda7f6ba1457c037eabce6669a7f4d24fe5c192d7dae36c3a2" + }, + { + "path": "skills/pr/scripts/03-create-pr.sh", + "sha256": "2dc070d899acc9f8eb199b7b933f10dec7bd633637a1ec9c1d9c0d29538a145c" + }, + { + "path": "skills/pr/scripts/00-pr-workflow.sh", + "sha256": "49d95e9bf05172502dd4221188c06c3fa7f086fa6f40e472d75bb603ca54b7e4" + }, + { + "path": "skills/pr/scripts/01-fork-and-setup.sh", + "sha256": "5b84acf7f71038881285c3b3ec3ea7ba9df49f56db143d9c47f4f812c6a98a08" + }, + { + "path": "skills/pr/scripts/02-check-guidelines.sh", + "sha256": "ba49dccb5d4b470111bf6b6620d9f63b5682496f3916a4ffb96ea0854ef759dd" + } + ], + "dirSha256": "b1a3174a75d23221475a521800af84cfd3914d957e9a7eda050eb11169276fa0" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file diff --git a/skills/pr/SKILL.md b/skills/pr/SKILL.md new file mode 100644 index 0000000..d19ffbe --- /dev/null +++ b/skills/pr/SKILL.md @@ -0,0 +1,212 @@ +--- +name: pr +description: Automate creating pull requests - fork repos, create branches, commit changes, and submit PRs. Works in current directory or creates new clone. Idempotent and safe to re-run. Keywords - PR, pull request, fork, contribute, upstream. +allowed-tools: [Bash, Read, Write, Edit, Glob, Grep] +--- + +# PR - Pull Request Automation + +Automate creating pull requests from start to finish. + +## What This Does + +1. Fork the repository (if needed) +2. Clone or use existing local repo +3. Create feature branch from upstream +4. Auto-commit your changes +5. Push and create PR + +## Quick Start + +**Basic workflow:** +```bash +# 1. Fork, clone, and create branch +bash ~/.claude/skills/pr/scripts/01-fork-and-setup.sh \ + owner/repo \ + ~/code \ + 1 \ + main \ + my-feature + +# 2. Make your changes (use Edit tool)... + +# 3. Auto-commit and create PR +bash ~/.claude/skills/pr/scripts/03-create-pr.sh \ + main \ + "PR title" \ + "PR description" +``` + +**Current directory mode:** +```bash +cd /path/to/your/repo +bash ~/.claude/skills/pr/scripts/01-fork-and-setup.sh owner/repo . "" main my-feature +# Make changes... +bash ~/.claude/skills/pr/scripts/03-create-pr.sh main "Fix bug" +``` + +## Scripts + +### `01-fork-and-setup.sh` - Setup repo and branch + +```bash +01-fork-and-setup.sh [work_dir] [depth] [base_branch] [feature_branch] +``` + +**Parameters:** +- `repo` - Repository to fork (e.g., `owner/repo`) +- `work_dir` - Where to clone (default: `~/tmp/contribute`, use `.` for current dir) +- `depth` - Clone depth (default: full, use `1` for 10x faster shallow clone) +- `base_branch` - Branch to base work on (e.g., `main`) +- `feature_branch` - Your new branch name (e.g., `fix/bug-123`) + +**What it does:** +- Creates fork if needed (detects existing forks, even with different names) +- Clones repo or uses existing local copy +- Sets up `upstream` (original) and `origin` (fork) remotes +- Creates feature branch from latest upstream +- **Idempotent** - Safe to re-run multiple times + +### `03-create-pr.sh` - Commit and create PR + +```bash +03-create-pr.sh [pr_body] [commit_message] +``` + +**Parameters:** +- `base_branch` - Target branch for PR (e.g., `main`) +- `pr_title` - PR title +- `pr_body` - PR description (optional) +- `commit_message` - Commit message (optional, defaults to PR title) + +**What it does:** +- Auto-commits any uncommitted changes with DCO sign-off +- Pushes branch to your fork +- Checks for existing PRs (avoids duplicates) +- Creates PR to upstream +- Returns PR URL +- **Idempotent** - Won't create duplicate PRs + +## Examples + +### Example 1: Fix bug in upstream repo + +```bash +# User request: "Fix version 2.9 to 2.15 in stolostron/multicluster-global-hub" + +# Step 1: Setup +bash ~/.claude/skills/pr/scripts/01-fork-and-setup.sh \ + stolostron/multicluster-global-hub \ + ~/tmp/contribute \ + 1 \ + main \ + docs/fix-version + +# Step 2: Make changes (using Edit tool to modify files) +# ... Edit files to change 2.9 to 2.15 ... + +# Step 3: Create PR +bash ~/.claude/skills/pr/scripts/03-create-pr.sh \ + main \ + "docs: update version from 2.9 to 2.15" \ + "Update documentation links to point to 2.15 instead of 2.9" +``` + +### Example 2: Work in current directory + +```bash +# User is already in a git repo and wants to work there + +cd /path/to/existing/repo + +bash ~/.claude/skills/pr/scripts/01-fork-and-setup.sh \ + owner/repo \ + . \ + "" \ + main \ + fix/issue-123 + +# Make changes... + +bash ~/.claude/skills/pr/scripts/03-create-pr.sh \ + main \ + "Fix issue #123" +``` + +### Example 3: Handles fork name mismatches + +```bash +# Scenario: Your fork is named "hub-of-hubs" but upstream is "multicluster-global-hub" +# The script automatically detects this and handles it + +bash ~/.claude/skills/pr/scripts/01-fork-and-setup.sh \ + stolostron/multicluster-global-hub \ + ~/code \ + 1 \ + main \ + fix/bug + +# Output: "Found existing fork with different name: yanmxa/hub-of-hubs" +# Script uses the correct fork name automatically +``` + +## Key Features + +โœ… **Idempotent** - Safe to re-run, won't duplicate work +โœ… **Smart fork detection** - Finds forks even with different names +โœ… **Auto-fork** - Creates fork if it doesn't exist +โœ… **Auto-commit** - Commits changes with DCO sign-off +โœ… **No duplicates** - Checks for existing PRs before creating +โœ… **Current dir mode** - Can work in existing repos (use `.` as work_dir) +โœ… **Fast clone** - Shallow clone by default (10x faster) +โœ… **Proper remotes** - Sets up upstream (HTTPS) and origin (SSH) + +## Performance + +**Clone speed with depth=1:** +| Repository | Full Clone | Shallow (depth=1) | Speedup | +|------------|------------|-------------------|---------| +| kubernetes | ~3GB | ~300MB | 10x | +| Linux kernel | ~4GB | ~400MB | 10x | +| Typical repo | ~500MB | ~50MB | 10x | + +## Prerequisites + +- **GitHub CLI (`gh`)** - Must be installed and authenticated + ```bash + brew install gh + gh auth login + ``` + +- **Git** - Configured with name and email + ```bash + git config --global user.name "Your Name" + git config --global user.email "your.email@example.com" + ``` + +- **SSH Keys** - Setup for GitHub (for cloning via SSH) + +## Troubleshooting + +**Fork already exists** +โ†’ Script handles automatically, uses existing fork + +**Repository already cloned** +โ†’ Script verifies and updates remotes + +**PR already exists** +โ†’ Script shows existing PR URL instead of creating duplicate + +**Fork has different name** +โ†’ Script detects via GitHub API and uses correct name + +**Current directory not a git repo** +โ†’ Don't use `.` as work_dir, or cd to a git repo first + +## Notes + +- All commits are signed with DCO (`-s` flag) +- Uses `git add -u` (only modified/deleted files, not new untracked files) +- Creates PRs with proper `username:branch` format +- Handles both HTTPS (upstream) and SSH (origin) remotes +- Color-coded output for easy reading diff --git a/skills/pr/scripts/00-pr-workflow.sh b/skills/pr/scripts/00-pr-workflow.sh new file mode 100755 index 0000000..ffc97db --- /dev/null +++ b/skills/pr/scripts/00-pr-workflow.sh @@ -0,0 +1,191 @@ +#!/bin/bash +# 00-pr-workflow.sh - Complete PR workflow (fork โ†’ modify โ†’ PR) +# Usage: ./00-pr-workflow.sh [work_dir] [depth] +# Example: ./00-pr-workflow.sh stolostron/multicluster-global-hub release-1.6 fix/bug-123 "Fix bug in handler" ~/tmp/contribute 1 + +set -euo pipefail + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to print colored messages +info() { echo -e "${GREEN}[INFO]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } +step() { echo -e "${BLUE}[STEP]${NC} $*"; } + +# Check arguments +if [ $# -lt 4 ]; then + error "Usage: $0 [work_dir] [depth]" + error "Example: $0 stolostron/multicluster-global-hub release-1.6 fix/bug-123 \"Fix bug\" ~/tmp/contribute 1" + error "" + error "Parameters:" + error " upstream_repo - Repository to fork (owner/repo)" + error " base_branch - Base branch for PR (e.g., main, release-1.6)" + error " feature_branch - Your feature branch name" + error " pr_title - Pull request title" + error " work_dir - Working directory (default: ~/tmp/contribute)" + error " depth - Git clone depth (default: 1 for fast shallow clone)" + error " Use 'full' for complete history if needed" + exit 1 +fi + +UPSTREAM_REPO="$1" +BASE_BRANCH="$2" +FEATURE_BRANCH="$3" +PR_TITLE="$4" +WORK_DIR="${5:-$HOME/tmp/contribute}" +CLONE_DEPTH="${6:-1}" # Default to shallow clone (depth=1) for speed +REPO_NAME=$(basename "$UPSTREAM_REPO") + +info "=========================================" +info "Contribution Workflow Started" +info "=========================================" +info "Upstream repository: $UPSTREAM_REPO" +info "Base branch: $BASE_BRANCH" +info "Feature branch: $FEATURE_BRANCH" +info "PR title: $PR_TITLE" +info "Work directory: $WORK_DIR" +if [ "$CLONE_DEPTH" = "1" ]; then + info "Clone depth: 1 (shallow clone - faster)" +elif [ -n "$CLONE_DEPTH" ]; then + info "Clone depth: $CLONE_DEPTH" +else + info "Clone depth: full history" +fi +info "=========================================" + +# Check prerequisites +step "1/7: Checking prerequisites..." +if ! command -v gh &> /dev/null; then + error "GitHub CLI (gh) is not installed. Please install it first:" + error " brew install gh" + exit 1 +fi + +if ! gh auth status &> /dev/null; then + error "Not authenticated with GitHub CLI. Please run:" + error " gh auth login" + exit 1 +fi + +GH_USER=$(gh api user --jq '.login') +info "GitHub username: $GH_USER" + +# Step 1: Fork and setup +step "2/7: Forking and setting up repository..." +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +if ! bash "$SCRIPT_DIR/01-fork-and-setup.sh" "$UPSTREAM_REPO" "$WORK_DIR" "$CLONE_DEPTH"; then + error "Failed to fork and setup repository" + exit 1 +fi + +REPO_PATH="$WORK_DIR/$REPO_NAME" +cd "$REPO_PATH" + +# Step 2: Check for contribution guidelines +step "3/7: Checking for contribution guidelines..." +if [ -f "CONTRIBUTING.md" ]; then + info "Found CONTRIBUTING.md - Please review contribution guidelines:" + head -20 CONTRIBUTING.md + echo "" + warn "Press Enter to continue after reviewing guidelines..." + # For automation, we'll skip the wait + # read -r +fi + +# Step 3: Create feature branch +step "4/7: Creating feature branch from latest upstream..." +git fetch upstream +if git rev-parse --verify "$FEATURE_BRANCH" &> /dev/null; then + warn "Branch '$FEATURE_BRANCH' already exists. Switching to it..." + git checkout "$FEATURE_BRANCH" + # Optionally rebase on latest upstream + git rebase "upstream/$BASE_BRANCH" || { + error "Failed to rebase. Please resolve conflicts manually." + exit 1 + } +else + git checkout -b "$FEATURE_BRANCH" "upstream/$BASE_BRANCH" + info "Created branch '$FEATURE_BRANCH' from 'upstream/$BASE_BRANCH'" +fi + +# Step 4: Pause for manual changes +step "5/7: Ready for code changes..." +info "" +info "Repository path: $REPO_PATH" +info "Current branch: $(git branch --show-current)" +info "" +info "Next steps:" +info " 1. Make your code changes" +info " 2. Commit your changes with: git commit -s -m \"message\"" +info " 3. Return here and we'll create the PR" +info "" +warn "This script will now pause. Press Enter when you're ready to create the PR..." +# For automation in Claude, we'll skip this pause +# read -r + +# Step 5: Verify changes are committed +step "6/7: Verifying changes are committed..." +if git diff-index --quiet HEAD --; then + # Check if there are any commits on this branch + COMMITS_AHEAD=$(git rev-list --count "upstream/$BASE_BRANCH..$FEATURE_BRANCH") + if [ "$COMMITS_AHEAD" -eq 0 ]; then + error "No changes committed. Please make changes and commit them first." + exit 1 + fi + info "Found $COMMITS_AHEAD commit(s) ready to push" +else + error "You have uncommitted changes. Please commit or stash them first." + info "Run: git status" + exit 1 +fi + +# Step 6: Run checks (if applicable) +step "7/7: Running pre-PR checks..." +# Check for common files that indicate tests should be run +if [ -f "Makefile" ]; then + if grep -q "^test:" Makefile; then + info "Found 'test' target in Makefile. Consider running: make test" + fi + if grep -q "^lint:" Makefile; then + info "Found 'lint' target in Makefile. Consider running: make lint" + fi +fi + +# Step 7: Create PR +info "" +info "=========================================" +info "Creating Pull Request" +info "=========================================" + +# Get commit messages for PR body +COMMIT_MESSAGES=$(git log --pretty=format:"- %s" "upstream/$BASE_BRANCH..$FEATURE_BRANCH") +PR_BODY="## Changes + +$COMMIT_MESSAGES + +## Checklist +- [x] Code changes are committed with sign-off +- [ ] Tests pass locally (if applicable) +- [ ] Documentation updated (if needed) + +--- +*Generated via contribute script*" + +# Use the fixed create-pr script +if bash "$SCRIPT_DIR/03-create-pr.sh" "$BASE_BRANCH" "$PR_TITLE" "$PR_BODY"; then + info "" + info "=========================================" + info "โœ… Contribution workflow completed!" + info "=========================================" +else + error "Failed to create PR. You can create it manually with:" + error " gh pr create --repo $UPSTREAM_REPO --base $BASE_BRANCH --head $GH_USER:$FEATURE_BRANCH" + exit 1 +fi diff --git a/skills/pr/scripts/01-fork-and-setup.sh b/skills/pr/scripts/01-fork-and-setup.sh new file mode 100755 index 0000000..56df4ec --- /dev/null +++ b/skills/pr/scripts/01-fork-and-setup.sh @@ -0,0 +1,255 @@ +#!/bin/bash +# 01-fork-and-setup.sh - Fork repository and setup local environment +# Usage: ./01-fork-and-setup.sh [work_dir] [depth] [base_branch] [feature_branch] +# Example: ./01-fork-and-setup.sh stolostron/multicluster-global-hub ~/tmp/contribute 1 main fix/version + +set -euo pipefail + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Function to print colored messages +info() { echo -e "${GREEN}[INFO]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } + +# Check arguments +if [ $# -lt 1 ]; then + error "Usage: $0 [work_dir] [depth] [base_branch] [feature_branch]" + error "Example: $0 stolostron/multicluster-global-hub ~/tmp/contribute 1 main fix/version" + error "Example (current dir): cd /path/to/repo && $0 stolostron/multicluster-global-hub . 1 main fix/version" + error "" + error "Parameters:" + error " upstream_repo - Repository to fork (owner/repo)" + error " work_dir - Working directory (default: ~/tmp/contribute)" + error " Use '.' to work in current directory (must be a git repo)" + error " depth - Git clone depth (default: full history)" + error " Use 1 for shallow clone (faster, less history)" + error " base_branch - Base branch to branch from (optional)" + error " feature_branch - Feature branch to create (optional, requires base_branch)" + exit 1 +fi + +UPSTREAM_REPO="$1" +WORK_DIR="${2:-$HOME/tmp/contribute}" +CLONE_DEPTH="${3:-}" +BASE_BRANCH="${4:-}" +FEATURE_BRANCH="${5:-}" +REPO_NAME=$(basename "$UPSTREAM_REPO") + +# Support current directory mode +USE_CURRENT_DIR=false +if [ "$WORK_DIR" = "." ]; then + USE_CURRENT_DIR=true + WORK_DIR="$(pwd)" + info "Using current directory mode" +fi + +info "Upstream repository: $UPSTREAM_REPO" +info "Work directory: $WORK_DIR" +info "Repository name: $REPO_NAME" +if [ -n "$CLONE_DEPTH" ]; then + info "Clone depth: $CLONE_DEPTH (shallow clone for faster download)" +else + info "Clone depth: full history" +fi +if [ -n "$BASE_BRANCH" ] && [ -n "$FEATURE_BRANCH" ]; then + info "Will create feature branch: $FEATURE_BRANCH from $BASE_BRANCH" +fi + +# Check if gh CLI is available +if ! command -v gh &> /dev/null; then + error "GitHub CLI (gh) is not installed. Please install it first:" + error " brew install gh" + exit 1 +fi + +# Check if user is authenticated with gh +if ! gh auth status &> /dev/null; then + error "Not authenticated with GitHub CLI. Please run:" + error " gh auth login" + exit 1 +fi + +# Get current GitHub username +GH_USER=$(gh api user --jq '.login') +info "GitHub username: $GH_USER" + +# Check if fork already exists (with possible different name) +info "Checking if fork already exists..." +FORK_EXISTS=false +FORK_REPO_NAME="" + +# First, try the expected name +if gh repo view "$GH_USER/$REPO_NAME" &> /dev/null; then + warn "Fork already exists: $GH_USER/$REPO_NAME" + FORK_EXISTS=true + FORK_REPO_NAME="$REPO_NAME" +else + # Check if there's a fork with a different name by querying the upstream repo + info "Checking for fork with different name..." + POTENTIAL_FORK=$(gh api "repos/$UPSTREAM_REPO/forks" --jq ".[] | select(.owner.login == \"$GH_USER\") | .name" 2>/dev/null | head -1) + + if [ -n "$POTENTIAL_FORK" ]; then + warn "Found existing fork with different name: $GH_USER/$POTENTIAL_FORK" + warn "Original repo: $REPO_NAME, Fork name: $POTENTIAL_FORK" + FORK_EXISTS=true + FORK_REPO_NAME="$POTENTIAL_FORK" + else + info "Fork does not exist. Creating fork..." + FORK_OUTPUT=$(gh repo fork "$UPSTREAM_REPO" --clone=false 2>&1) + if [ $? -eq 0 ]; then + # Extract the actual fork name from output or check again + FORK_REPO_NAME=$(gh api "repos/$UPSTREAM_REPO/forks" --jq ".[] | select(.owner.login == \"$GH_USER\") | .name" 2>/dev/null | head -1) + if [ -z "$FORK_REPO_NAME" ]; then + FORK_REPO_NAME="$REPO_NAME" # Fallback to expected name + fi + info "Fork created successfully: $GH_USER/$FORK_REPO_NAME" + FORK_EXISTS=true + else + error "Failed to create fork" + error "$FORK_OUTPUT" + exit 1 + fi + fi +fi + +# Use the actual fork name for subsequent operations +if [ -n "$FORK_REPO_NAME" ]; then + info "Using fork: $GH_USER/$FORK_REPO_NAME" +else + error "Could not determine fork repository name" + exit 1 +fi + +# Determine repository path +if [ "$USE_CURRENT_DIR" = true ]; then + # In current directory mode, check if we're already in a git repo + if git rev-parse --git-dir &> /dev/null; then + REPO_PATH="$WORK_DIR" + info "Using current directory as repository: $REPO_PATH" + else + error "Current directory mode requires you to be in a git repository" + error "Either cd to the repository or specify a different work_dir" + exit 1 + fi +else + # Create work directory if it doesn't exist + mkdir -p "$WORK_DIR" + REPO_PATH="$WORK_DIR/$REPO_NAME" +fi + +# Check if repository already cloned (using the original repo name for local directory) +if [ -d "$REPO_PATH/.git" ]; then + warn "Repository already exists at: $REPO_PATH" + info "Checking remotes..." + + cd "$REPO_PATH" + + # Check current remotes + if git remote get-url upstream &> /dev/null; then + CURRENT_UPSTREAM=$(git remote get-url upstream) + info "Current upstream: $CURRENT_UPSTREAM" + else + warn "No upstream remote found. Adding it..." + git remote add upstream "https://github.com/$UPSTREAM_REPO.git" + info "Added upstream: https://github.com/$UPSTREAM_REPO.git" + fi + + if git remote get-url origin &> /dev/null; then + CURRENT_ORIGIN=$(git remote get-url origin) + info "Current origin: $CURRENT_ORIGIN" + else + warn "No origin remote found. Adding it..." + git remote add origin "git@github.com:$GH_USER/$FORK_REPO_NAME.git" + info "Added origin: git@github.com:$GH_USER/$FORK_REPO_NAME.git" + fi +else + if [ "$USE_CURRENT_DIR" = true ]; then + error "Current directory is not a git repository and current directory mode is enabled" + error "Either cd to the repository or use a different work_dir" + exit 1 + fi + + info "Cloning fork to: $REPO_PATH" + cd "$WORK_DIR" + + # Clone the fork with optional depth + CLONE_CMD="git clone" + if [ -n "$CLONE_DEPTH" ]; then + CLONE_CMD="$CLONE_CMD --depth $CLONE_DEPTH" + info "Using shallow clone (depth=$CLONE_DEPTH) for faster download..." + fi + + # Clone using the actual fork name, but rename directory to original repo name + if [ "$FORK_REPO_NAME" = "$REPO_NAME" ]; then + # Fork name matches, no need to rename + if $CLONE_CMD "git@github.com:$GH_USER/$FORK_REPO_NAME.git"; then + info "Fork cloned successfully" + cd "$REPO_NAME" + + # Add upstream remote + info "Adding upstream remote..." + git remote add upstream "https://github.com/$UPSTREAM_REPO.git" + info "Added upstream: https://github.com/$UPSTREAM_REPO.git" + else + error "Failed to clone fork" + exit 1 + fi + else + # Fork name differs, clone and rename + if $CLONE_CMD "git@github.com:$GH_USER/$FORK_REPO_NAME.git" "$REPO_NAME"; then + info "Fork cloned successfully (renamed from $FORK_REPO_NAME to $REPO_NAME)" + cd "$REPO_NAME" + + # Add upstream remote + info "Adding upstream remote..." + git remote add upstream "https://github.com/$UPSTREAM_REPO.git" + info "Added upstream: https://github.com/$UPSTREAM_REPO.git" + else + error "Failed to clone fork" + exit 1 + fi + fi +fi + +# Fetch latest from upstream +info "Fetching latest from upstream..." +if [ -n "$CLONE_DEPTH" ]; then + # For shallow clones, also use depth when fetching + git fetch --depth "$CLONE_DEPTH" upstream +else + git fetch upstream +fi + +# Create feature branch if specified +if [ -n "$BASE_BRANCH" ] && [ -n "$FEATURE_BRANCH" ]; then + info "Creating feature branch..." + + # Check if feature branch already exists + if git rev-parse --verify "$FEATURE_BRANCH" &> /dev/null; then + warn "Branch '$FEATURE_BRANCH' already exists. Switching to it..." + git checkout "$FEATURE_BRANCH" + else + info "Creating branch '$FEATURE_BRANCH' from 'upstream/$BASE_BRANCH'..." + git checkout -b "$FEATURE_BRANCH" "upstream/$BASE_BRANCH" + info "Created and switched to branch '$FEATURE_BRANCH'" + fi +fi + +info "โœ… Setup complete!" +info "" +info "Repository path: $REPO_PATH" +info "Remotes configured:" +git remote -v +if [ -n "$FEATURE_BRANCH" ]; then + info "Current branch: $(git branch --show-current)" +fi +info "" +info "Next steps:" +info " 1. cd $REPO_PATH" +info " 2. Make your changes" +info " 3. Run create-pr script to submit PR" diff --git a/skills/pr/scripts/02-check-guidelines.sh b/skills/pr/scripts/02-check-guidelines.sh new file mode 100755 index 0000000..858d572 --- /dev/null +++ b/skills/pr/scripts/02-check-guidelines.sh @@ -0,0 +1,153 @@ +#!/bin/bash +# 02-check-guidelines.sh - Check repository contribution guidelines and requirements +# Usage: ./02-check-guidelines.sh [repo_path] + +set -euo pipefail + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to print colored messages +info() { echo -e "${GREEN}[INFO]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } +check() { echo -e "${BLUE}[CHECK]${NC} $*"; } + +REPO_PATH="${1:-.}" +cd "$REPO_PATH" + +info "Checking contribution guidelines for repository: $(basename "$PWD")" +echo "" + +# Check for CONTRIBUTING.md +check "Looking for CONTRIBUTING.md..." +if [ -f "CONTRIBUTING.md" ]; then + info "โœ… Found CONTRIBUTING.md" + echo "" + echo "===== Contribution Guidelines (first 30 lines) =====" + head -30 CONTRIBUTING.md + echo "===== (see CONTRIBUTING.md for full details) =====" + echo "" +else + warn "No CONTRIBUTING.md found" +fi + +# Check for CODE_OF_CONDUCT.md +check "Looking for CODE_OF_CONDUCT.md..." +if [ -f "CODE_OF_CONDUCT.md" ]; then + info "โœ… Found CODE_OF_CONDUCT.md - Please review community guidelines" +else + warn "No CODE_OF_CONDUCT.md found" +fi + +# Check for CLA +check "Looking for CLA requirements..." +CLA_FOUND=false +for file in CONTRIBUTING.md README.md .github/CONTRIBUTING.md docs/CONTRIBUTING.md; do + if [ -f "$file" ] && grep -qi "CLA\|contributor license agreement" "$file"; then + warn "โš ๏ธ CLA (Contributor License Agreement) may be required" + warn " Check: $file" + CLA_FOUND=true + break + fi +done +if [ "$CLA_FOUND" = false ]; then + info "No CLA requirement detected" +fi + +# Check for DCO (Developer Certificate of Origin) +check "Looking for DCO requirements..." +DCO_FOUND=false +for file in CONTRIBUTING.md README.md .github/CONTRIBUTING.md docs/CONTRIBUTING.md; do + if [ -f "$file" ] && grep -qi "DCO\|sign-off\|signed-off-by" "$file"; then + info "โœ… DCO (Developer Certificate of Origin) required" + info " Use: git commit -s" + DCO_FOUND=true + break + fi +done +if [ "$DCO_FOUND" = false ]; then + warn "No explicit DCO requirement found, but it's a best practice to use -s flag" +fi + +# Check for PR template +check "Looking for PR template..." +if [ -f ".github/PULL_REQUEST_TEMPLATE.md" ] || [ -f "PULL_REQUEST_TEMPLATE.md" ] || [ -f ".github/pull_request_template.md" ]; then + info "โœ… Found PR template - Your PR description should follow this format" + if [ -f ".github/PULL_REQUEST_TEMPLATE.md" ]; then + echo "" + echo "===== PR Template =====" + cat .github/PULL_REQUEST_TEMPLATE.md + echo "===== (end of template) =====" + echo "" + fi +else + warn "No PR template found" +fi + +# Check for testing requirements +check "Looking for testing requirements..." +if [ -f "Makefile" ]; then + info "โœ… Found Makefile" + if grep -q "^test:" Makefile; then + info " โ†’ Run tests with: make test" + fi + if grep -q "^lint:" Makefile; then + info " โ†’ Run linter with: make lint" + fi + if grep -q "^fmt:" Makefile; then + info " โ†’ Format code with: make fmt" + fi +fi + +# Check for CI configuration +check "Looking for CI/CD configuration..." +CI_FOUND=false +if [ -d ".github/workflows" ]; then + info "โœ… Found GitHub Actions workflows:" + for workflow in .github/workflows/*.{yml,yaml}; do + if [ -f "$workflow" ]; then + info " โ†’ $(basename "$workflow")" + CI_FOUND=true + fi + done +fi +if [ -f ".travis.yml" ]; then + info "โœ… Found Travis CI configuration" + CI_FOUND=true +fi +if [ -f ".circleci/config.yml" ]; then + info "โœ… Found CircleCI configuration" + CI_FOUND=true +fi +if [ "$CI_FOUND" = false ]; then + warn "No CI/CD configuration found" +fi + +# Check commit message conventions +check "Looking for commit message conventions..." +for file in CONTRIBUTING.md README.md .github/CONTRIBUTING.md docs/CONTRIBUTING.md; do + if [ -f "$file" ]; then + if grep -qi "conventional commits\|commit message\|commit format" "$file"; then + info "โœ… Commit message conventions found - Please review: $file" + break + fi + fi +done + +# Summary +echo "" +info "=========================================" +info "Summary" +info "=========================================" +info "Before submitting your PR, ensure you:" +info " 1. โœ“ Read CONTRIBUTING.md (if exists)" +info " 2. โœ“ Sign commits with -s flag (DCO)" +info " 3. โœ“ Run tests locally (make test)" +info " 4. โœ“ Follow commit message conventions" +info " 5. โœ“ Fill out PR template (if exists)" +info "=========================================" diff --git a/skills/pr/scripts/03-create-pr.sh b/skills/pr/scripts/03-create-pr.sh new file mode 100755 index 0000000..398fcc3 --- /dev/null +++ b/skills/pr/scripts/03-create-pr.sh @@ -0,0 +1,124 @@ +#!/bin/bash +# 03-create-pr.sh - Add, commit, push changes and create PR to upstream +# Usage: ./03-create-pr.sh [pr_body] [commit_message] +# Example: ./03-create-pr.sh main "Fix bug in sync handler" "This PR fixes..." "fix: resolve bug" + +set -euo pipefail + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Function to print colored messages +info() { echo -e "${GREEN}[INFO]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } + +# Check arguments +if [ $# -lt 2 ]; then + error "Usage: $0 [pr_body] [commit_message]" + error "Example: $0 main \"Fix bug in sync handler\" \"This PR fixes...\" \"fix: resolve bug\"" + exit 1 +fi + +BASE_BRANCH="$1" +PR_TITLE="$2" +PR_BODY="${3:-}" +COMMIT_MESSAGE="${4:-$PR_TITLE}" + +# Check if gh CLI is available +if ! command -v gh &> /dev/null; then + error "GitHub CLI (gh) is not installed. Please install it first:" + error " brew install gh" + exit 1 +fi + +# Check if in a git repository +if ! git rev-parse --git-dir &> /dev/null; then + error "Not in a git repository" + exit 1 +fi + +# Get current branch +CURRENT_BRANCH=$(git branch --show-current) +info "Current branch: $CURRENT_BRANCH" + +# Check if current branch is the base branch +if [ "$CURRENT_BRANCH" = "$BASE_BRANCH" ]; then + error "You are on the base branch ($BASE_BRANCH). Please create a feature branch first." + exit 1 +fi + +# Check for uncommitted changes and auto-commit if any +if ! git diff-index --quiet HEAD --; then + info "Found uncommitted changes. Auto-committing..." + + # Add all modified/deleted files (not new untracked files) + git add -u + + # Commit with sign-off + git commit -s -m "$COMMIT_MESSAGE" + + info "Changes committed: $COMMIT_MESSAGE" +fi + +# Get upstream repo info +if ! UPSTREAM_URL=$(git remote get-url upstream 2>/dev/null); then + error "No upstream remote found. Please run 01-fork-and-setup.sh first." + exit 1 +fi + +# Extract owner/repo from upstream URL +UPSTREAM_REPO=$(echo "$UPSTREAM_URL" | sed -E 's|.*github\.com[:/](.*)\.git|\1|' | sed 's|\.git$||') +info "Upstream repository: $UPSTREAM_REPO" + +# Get origin repo info and extract username +ORIGIN_URL=$(git remote get-url origin) +ORIGIN_REPO=$(echo "$ORIGIN_URL" | sed -E 's|.*github\.com[:/](.*)\.git|\1|' | sed 's|\.git$||') +ORIGIN_USER=$(echo "$ORIGIN_REPO" | cut -d'/' -f1) +info "Origin repository: $ORIGIN_REPO" +info "Origin user: $ORIGIN_USER" + +# Push current branch to origin +info "Pushing current branch to origin..." +if git push -u origin "$CURRENT_BRANCH" 2>&1 | grep -q "Everything up-to-date"; then + info "Branch already up-to-date on origin" +else + info "Branch pushed to origin" +fi + +# Check if PR already exists +info "Checking if PR already exists..." +EXISTING_PR=$(gh pr list --repo "$UPSTREAM_REPO" --head "$ORIGIN_USER:$CURRENT_BRANCH" --json number,title,url --jq '.[0]') + +if [ -n "$EXISTING_PR" ] && [ "$EXISTING_PR" != "null" ]; then + PR_NUMBER=$(echo "$EXISTING_PR" | jq -r '.number') + PR_URL=$(echo "$EXISTING_PR" | jq -r '.url') + EXISTING_TITLE=$(echo "$EXISTING_PR" | jq -r '.title') + + warn "PR already exists!" + info " PR #$PR_NUMBER: $EXISTING_TITLE" + info " URL: $PR_URL" + info "" + info "Would you like to update the existing PR or create a new one?" + exit 0 +fi + +# Create PR +info "Creating PR to upstream..." +if [ -n "$PR_BODY" ]; then + PR_URL=$(gh pr create --repo "$UPSTREAM_REPO" --base "$BASE_BRANCH" --head "$ORIGIN_USER:$CURRENT_BRANCH" --title "$PR_TITLE" --body "$PR_BODY") +else + # Use interactive editor if no body provided + PR_URL=$(gh pr create --repo "$UPSTREAM_REPO" --base "$BASE_BRANCH" --head "$ORIGIN_USER:$CURRENT_BRANCH" --title "$PR_TITLE") +fi + +if [ $? -eq 0 ]; then + info "โœ… PR created successfully!" + info " URL: $PR_URL" +else + error "Failed to create PR" + exit 1 +fi