Update README.md
This commit is contained in:
304
README.md
304
README.md
@@ -0,0 +1,304 @@
|
|||||||
|
# GP - Git Push with Smart Commit Messages
|
||||||
|
|
||||||
|
A powerful, safe, and user-friendly Git workflow automation tool that intelligently generates commit messages and streamlines the commit-push process.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### 🚀 **Smart Commit Messages**
|
||||||
|
- **File type detection** - Recognizes source code, config files, documentation, and tests
|
||||||
|
- **Change analysis** - Detects additions, modifications, deletions, and renames
|
||||||
|
- **Conventional commits** - Uses prefixes like `feat:`, `docs:`, `test:`, `config:`
|
||||||
|
- **Intelligent descriptions** - Context-aware commit messages based on actual changes
|
||||||
|
|
||||||
|
### 🛡️ **Safety First**
|
||||||
|
- **Preview mode** - `--dry-run` shows exactly what will happen before executing
|
||||||
|
- **User confirmation** - Always asks before adding files or pushing changes
|
||||||
|
- **Change detection** - Prevents empty commits and handles unpushed commits
|
||||||
|
- **Clear feedback** - Colored output with detailed status information
|
||||||
|
|
||||||
|
### ⚡ **Workflow Enhancement**
|
||||||
|
- **Add-all default** - Automatically includes untracked and modified files
|
||||||
|
- **Unpushed commit handling** - Detects and offers to push existing commits
|
||||||
|
- **Branch flexibility** - Push to any branch with `--branch` option
|
||||||
|
- **Force mode** - Skip confirmations for automation scripts
|
||||||
|
|
||||||
|
### 🎯 **Professional CLI**
|
||||||
|
- **Comprehensive help** - Built-in documentation with examples
|
||||||
|
- **Bash completion** - Tab completion for all options
|
||||||
|
- **Version tracking** - Semantic versioning support
|
||||||
|
- **Standard conventions** - Follows Unix CLI best practices
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. **Download the script:**
|
||||||
|
```bash
|
||||||
|
curl -o gp https://raw.githubusercontent.com/your-repo/gp/main/gp
|
||||||
|
chmod +x gp
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Place in your PATH:**
|
||||||
|
```bash
|
||||||
|
mv gp ~/.local/bin/
|
||||||
|
# or
|
||||||
|
sudo mv gp /usr/local/bin/
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Verify installation:**
|
||||||
|
```bash
|
||||||
|
gp version
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Basic Usage
|
||||||
|
```bash
|
||||||
|
# Add all files, generate commit message, and push
|
||||||
|
gp
|
||||||
|
|
||||||
|
# Use custom commit message
|
||||||
|
gp "Fix authentication bug in login module"
|
||||||
|
|
||||||
|
# Preview what would happen without executing
|
||||||
|
gp --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example Workflow
|
||||||
|
```bash
|
||||||
|
$ gp --dry-run
|
||||||
|
[INFO] Current branch: main
|
||||||
|
[INFO] Repository: https://github.com/user/project
|
||||||
|
|
||||||
|
[INFO] Modified files (will be added):
|
||||||
|
src/auth.py
|
||||||
|
tests/test_auth.py
|
||||||
|
[INFO] Untracked files (will be added):
|
||||||
|
docs/auth.md
|
||||||
|
|
||||||
|
[WARNING] Files will be automatically added before committing
|
||||||
|
[INFO] Generated commit message: 'feat: Update 3 files'
|
||||||
|
[WARNING] DRY RUN MODE - No changes will be made
|
||||||
|
[INFO] Would commit with message: 'feat: Update 3 files'
|
||||||
|
[INFO] Would push to: origin/main
|
||||||
|
```
|
||||||
|
|
||||||
|
## Command Reference
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `-h, --help` | Show help message with examples |
|
||||||
|
| `-n, --dry-run` | Preview changes without executing |
|
||||||
|
| `-f, --force` | Skip all confirmations |
|
||||||
|
| `-a, --add-all` | Add all files including untracked (default) |
|
||||||
|
| `--staged-only` | Only commit staged changes |
|
||||||
|
| `-b, --branch BRANCH` | Push to specified branch |
|
||||||
|
|
||||||
|
### Commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `gp` | Basic usage with smart defaults |
|
||||||
|
| `gp "message"` | Custom commit message |
|
||||||
|
| `gp version` | Show version information |
|
||||||
|
| `gp autocomplete` | Show completion options |
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Basic Operations
|
||||||
|
```bash
|
||||||
|
# Standard workflow - add all files and push
|
||||||
|
gp
|
||||||
|
|
||||||
|
# Custom commit message
|
||||||
|
gp "Add user authentication feature"
|
||||||
|
|
||||||
|
# Preview before executing
|
||||||
|
gp --dry-run
|
||||||
|
|
||||||
|
# Push to different branch
|
||||||
|
gp --branch develop "Update feature branch"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Advanced Usage
|
||||||
|
```bash
|
||||||
|
# Only commit staged files (don't add untracked)
|
||||||
|
gp --staged-only
|
||||||
|
|
||||||
|
# Force mode (no confirmations) - good for scripts
|
||||||
|
gp --force "Automated update"
|
||||||
|
|
||||||
|
# Combine options
|
||||||
|
gp --dry-run --branch feature/auth "Test authentication changes"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Handling Different Scenarios
|
||||||
|
|
||||||
|
#### When you have unpushed commits:
|
||||||
|
```bash
|
||||||
|
$ gp
|
||||||
|
[INFO] No staged changes, but found 2 unpushed commit(s)
|
||||||
|
[INFO] Latest unpushed commit: abc1234 Fix parser bug
|
||||||
|
|
||||||
|
Push existing commits to origin/main? [y/N] y
|
||||||
|
[SUCCESS] Successfully pushed existing commits to origin/main
|
||||||
|
```
|
||||||
|
|
||||||
|
#### When you have mixed changes:
|
||||||
|
```bash
|
||||||
|
$ gp --staged-only
|
||||||
|
[INFO] Staged changes:
|
||||||
|
src/main.py
|
||||||
|
[INFO] Modified files (unstaged, will NOT be included):
|
||||||
|
README.md
|
||||||
|
[WARNING] Unstaged changes will NOT be included (use -a to include them)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Smart Commit Message Examples
|
||||||
|
|
||||||
|
GP analyzes your changes and generates meaningful commit messages:
|
||||||
|
|
||||||
|
### Single File Changes
|
||||||
|
- `Add src/auth.py` - New file added
|
||||||
|
- `Update README.md` - Existing file modified
|
||||||
|
- `Remove old_config.json` - File deleted
|
||||||
|
|
||||||
|
### Multiple Files by Type
|
||||||
|
- `feat: Update 3 files` - Source code changes
|
||||||
|
- `docs: Update 2 files` - Documentation changes
|
||||||
|
- `test: Update 4 files` - Test file changes
|
||||||
|
- `config: Update 2 files` - Configuration changes
|
||||||
|
|
||||||
|
### File Type Detection
|
||||||
|
- **Source files**: `.py`, `.js`, `.cpp`, `.go`, `.rs`, etc.
|
||||||
|
- **Config files**: `.json`, `.yml`, `.toml`, `CMakeLists.txt`, etc.
|
||||||
|
- **Documentation**: `.md`, `.txt`, `.rst`, `README*`, `docs/*`
|
||||||
|
- **Tests**: `*test*`, `*spec*`, `test/*`, `tests/*`
|
||||||
|
|
||||||
|
## Safety Features
|
||||||
|
|
||||||
|
### Confirmation Prompts
|
||||||
|
```bash
|
||||||
|
$ gp
|
||||||
|
[INFO] Untracked files (will be added):
|
||||||
|
new_feature.py
|
||||||
|
tests/test_new_feature.py
|
||||||
|
|
||||||
|
Add these files and continue? [y/N] _
|
||||||
|
```
|
||||||
|
|
||||||
|
### Change Detection
|
||||||
|
- ✅ **Prevents empty commits** - Exits gracefully when no changes
|
||||||
|
- ✅ **Handles unpushed commits** - Offers to push existing commits
|
||||||
|
- ✅ **Validates git repository** - Ensures you're in a valid repo
|
||||||
|
- ✅ **Branch validation** - Confirms target branch exists
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
- Clear error messages with suggested solutions
|
||||||
|
- Graceful handling of network issues
|
||||||
|
- Proper exit codes for script integration
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
None required - GP works out of the box.
|
||||||
|
|
||||||
|
### Git Configuration
|
||||||
|
GP respects your existing Git configuration:
|
||||||
|
- Uses your configured remote origin
|
||||||
|
- Respects Git ignore files
|
||||||
|
- Works with your existing Git hooks
|
||||||
|
|
||||||
|
## Integration
|
||||||
|
|
||||||
|
### Bash Completion
|
||||||
|
Add to your `.bashrc`:
|
||||||
|
```bash
|
||||||
|
# GP completion
|
||||||
|
_gp_completion() {
|
||||||
|
local cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
local opts=$(gp autocomplete)
|
||||||
|
COMPREPLY=($(compgen -W "$opts" -- "$cur"))
|
||||||
|
}
|
||||||
|
complete -F _gp_completion gp
|
||||||
|
```
|
||||||
|
|
||||||
|
### Git Aliases
|
||||||
|
Add to your `.gitconfig`:
|
||||||
|
```ini
|
||||||
|
[alias]
|
||||||
|
p = !gp
|
||||||
|
pd = !gp --dry-run
|
||||||
|
pf = !gp --force
|
||||||
|
```
|
||||||
|
|
||||||
|
### CI/CD Integration
|
||||||
|
```bash
|
||||||
|
# In your CI script
|
||||||
|
gp --force "Automated deployment [skip ci]"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
#### "Not in a git repository"
|
||||||
|
- **Solution**: Run GP from within a Git repository
|
||||||
|
- **Check**: `git status` should work
|
||||||
|
|
||||||
|
#### "No changes to commit"
|
||||||
|
- **Cause**: Working tree is clean
|
||||||
|
- **Check**: `git status` to see if there are any changes
|
||||||
|
|
||||||
|
#### "Failed to push"
|
||||||
|
- **Cause**: Usually needs to pull first
|
||||||
|
- **Solution**: `git pull origin main` then try again
|
||||||
|
|
||||||
|
#### VSCode showing shellcheck errors
|
||||||
|
- **Solution**: Restart VSCode or reload the shellcheck extension
|
||||||
|
- **Check**: Command line `shellcheck gp` should show no errors
|
||||||
|
|
||||||
|
### Debug Mode
|
||||||
|
For verbose output, modify the script to set:
|
||||||
|
```bash
|
||||||
|
set -x # Add after the shebang line for debug output
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
### Development
|
||||||
|
1. Fork the repository
|
||||||
|
2. Make your changes
|
||||||
|
3. Test with `shellcheck gp`
|
||||||
|
4. Submit a pull request
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
```bash
|
||||||
|
# Lint the script
|
||||||
|
shellcheck gp
|
||||||
|
|
||||||
|
# Test basic functionality
|
||||||
|
./gp --dry-run
|
||||||
|
|
||||||
|
# Test with various scenarios
|
||||||
|
echo "test" > test.txt && ./gp --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License - see LICENSE file for details.
|
||||||
|
|
||||||
|
## Version History
|
||||||
|
|
||||||
|
- **v2.0.0** - Complete rewrite with safety features and smart commit messages
|
||||||
|
- **v1.0.0** - Basic git automation script
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
- **Issues**: Report bugs and feature requests on GitHub
|
||||||
|
- **Documentation**: This README and built-in help (`gp --help`)
|
||||||
|
- **Examples**: See the examples section above
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Made with ❤️ for developers who value safety and efficiency in their Git workflow.**
|
645
gp
Executable file
645
gp
Executable file
@@ -0,0 +1,645 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# gp - Git Push with AI-generated commit messages
|
||||||
|
# Improved version with better error handling, safety checks, and functionality
|
||||||
|
|
||||||
|
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 output
|
||||||
|
print_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||||
|
print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||||
|
print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
|
||||||
|
print_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||||
|
|
||||||
|
# Function to show help
|
||||||
|
show_help() {
|
||||||
|
cat << 'EOF'
|
||||||
|
gp - Git Push with smart commit messages
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
gp [options] [message]
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
-h, --help Show this help message
|
||||||
|
-n, --dry-run Show what would be committed without actually doing it
|
||||||
|
-f, --force Skip safety checks (use with caution)
|
||||||
|
-b, --branch Specify branch to push to (default: current branch)
|
||||||
|
-a, --add-all Add all files including untracked (default)
|
||||||
|
--staged-only Only commit staged changes (don't add untracked files)
|
||||||
|
|
||||||
|
ARGUMENTS:
|
||||||
|
message Optional custom commit message (overrides auto-generation)
|
||||||
|
|
||||||
|
EXAMPLES:
|
||||||
|
gp # Add all files, auto-generate commit message and push
|
||||||
|
gp "Fix bug in parser" # Add all files with custom commit message
|
||||||
|
gp --dry-run # Preview what would be committed
|
||||||
|
gp --staged-only # Only commit staged changes (don't add untracked)
|
||||||
|
gp -b develop # Push to develop branch instead of current
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to generate commit message based on changes
|
||||||
|
generate_commit_message() {
|
||||||
|
# First check if we have staged changes
|
||||||
|
local has_staged_changes=false
|
||||||
|
if ! git diff --cached --quiet; then
|
||||||
|
has_staged_changes=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Determine which changes to analyze based on staging status and ADD_ALL setting
|
||||||
|
local status_command=""
|
||||||
|
if [ "$has_staged_changes" = true ]; then
|
||||||
|
status_command="git diff --cached --name-status"
|
||||||
|
else
|
||||||
|
status_command="git diff --name-status"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get all changes (staged or unstaged depending on context)
|
||||||
|
local all_changes
|
||||||
|
all_changes=$($status_command)
|
||||||
|
|
||||||
|
# If no changes from diff, check for untracked files when add-all is enabled
|
||||||
|
if [ -z "$all_changes" ] && [ "$ADD_ALL" = true ]; then
|
||||||
|
local untracked_files
|
||||||
|
untracked_files=$(git ls-files --others --exclude-standard)
|
||||||
|
if [ -n "$untracked_files" ]; then
|
||||||
|
# Convert untracked files to "A" (added) status format
|
||||||
|
all_changes=$(echo "$untracked_files" | sed 's/^/A\t/')
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$all_changes" ]; then
|
||||||
|
echo "No changes to commit"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Count total files
|
||||||
|
local files_count
|
||||||
|
files_count=$(echo "$all_changes" | wc -l)
|
||||||
|
|
||||||
|
# Generate smart commit message based on file types and changes
|
||||||
|
local has_source_files=false
|
||||||
|
local has_config_files=false
|
||||||
|
local has_docs=false
|
||||||
|
local has_tests=false
|
||||||
|
local message=""
|
||||||
|
|
||||||
|
# Extract just the filenames for type detection
|
||||||
|
while IFS=$'\t' read -r status file; do
|
||||||
|
[ -z "$file" ] && continue
|
||||||
|
|
||||||
|
case "$file" in
|
||||||
|
*.cpp|*.hpp|*.c|*.h|*.js|*.ts|*.py|*.go|*.rs|*.java)
|
||||||
|
has_source_files=true
|
||||||
|
;;
|
||||||
|
*.json|*.yml|*.yaml|*.toml|*.ini|*.conf|CMakeLists.txt|Makefile)
|
||||||
|
has_config_files=true
|
||||||
|
;;
|
||||||
|
*.md|*.txt|*.rst|docs/*|README*)
|
||||||
|
has_docs=true
|
||||||
|
;;
|
||||||
|
test/*|tests/*|*test*|*spec*)
|
||||||
|
has_tests=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done <<< "$all_changes"
|
||||||
|
|
||||||
|
# Create descriptive commit message
|
||||||
|
if [ "$files_count" -eq 1 ]; then
|
||||||
|
local change_line
|
||||||
|
change_line=$(echo "$all_changes" | head -1)
|
||||||
|
local status
|
||||||
|
local single_file
|
||||||
|
status=$(echo "$change_line" | cut -f1)
|
||||||
|
single_file=$(echo "$change_line" | cut -f2)
|
||||||
|
|
||||||
|
case "${status:0:1}" in
|
||||||
|
A) message="Add $single_file" ;;
|
||||||
|
M) message="Update $single_file" ;;
|
||||||
|
D) message="Remove $single_file" ;;
|
||||||
|
R) message="Rename $single_file" ;;
|
||||||
|
*) message="Modify $single_file" ;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
# For multiple files, analyze the types of changes
|
||||||
|
local added_count=0
|
||||||
|
local modified_count=0
|
||||||
|
local deleted_count=0
|
||||||
|
local renamed_count=0
|
||||||
|
|
||||||
|
# Use the all_changes variable we already have
|
||||||
|
|
||||||
|
# Count different types of changes
|
||||||
|
while IFS=$'\t' read -r status file; do
|
||||||
|
[ -z "$status" ] && continue
|
||||||
|
case "${status:0:1}" in
|
||||||
|
A) ((added_count++)) ;;
|
||||||
|
M) ((modified_count++)) ;;
|
||||||
|
D) ((deleted_count++)) ;;
|
||||||
|
R) ((renamed_count++)) ;;
|
||||||
|
esac
|
||||||
|
done <<< "$all_changes"
|
||||||
|
|
||||||
|
# Also count untracked files if add-all is enabled
|
||||||
|
if [ "$ADD_ALL" = true ]; then
|
||||||
|
local untracked_files
|
||||||
|
untracked_files=$(git ls-files --others --exclude-standard)
|
||||||
|
if [ -n "$untracked_files" ]; then
|
||||||
|
local untracked_count
|
||||||
|
untracked_count=$(echo "$untracked_files" | wc -l)
|
||||||
|
((added_count += untracked_count))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generate message based on change types
|
||||||
|
local change_parts=()
|
||||||
|
[ $added_count -gt 0 ] && change_parts+=("add $added_count")
|
||||||
|
[ $modified_count -gt 0 ] && change_parts+=("update $modified_count")
|
||||||
|
[ $deleted_count -gt 0 ] && change_parts+=("remove $deleted_count")
|
||||||
|
[ $renamed_count -gt 0 ] && change_parts+=("rename $renamed_count")
|
||||||
|
|
||||||
|
local change_desc=""
|
||||||
|
if [ ${#change_parts[@]} -eq 1 ]; then
|
||||||
|
change_desc="${change_parts[0]}"
|
||||||
|
elif [ ${#change_parts[@]} -eq 2 ]; then
|
||||||
|
change_desc="${change_parts[0]} and ${change_parts[1]}"
|
||||||
|
else
|
||||||
|
# Join all but last with commas, last with "and"
|
||||||
|
local last_idx=$((${#change_parts[@]} - 1))
|
||||||
|
for i in $(seq 0 $((last_idx - 1))); do
|
||||||
|
[ $i -gt 0 ] && change_desc+=", "
|
||||||
|
change_desc+="${change_parts[i]}"
|
||||||
|
done
|
||||||
|
change_desc+=" and ${change_parts[last_idx]}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local prefix=""
|
||||||
|
if $has_tests; then
|
||||||
|
prefix="test: "
|
||||||
|
elif $has_docs; then
|
||||||
|
prefix="docs: "
|
||||||
|
elif $has_config_files; then
|
||||||
|
prefix="config: "
|
||||||
|
elif $has_source_files; then
|
||||||
|
prefix="feat: "
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Capitalize first letter of change description
|
||||||
|
change_desc="$(echo "${change_desc:0:1}" | tr '[:lower:]' '[:upper:]')${change_desc:1}"
|
||||||
|
|
||||||
|
message="${prefix}${change_desc} files"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$message"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to check if we're in a git repository and change to repo root
|
||||||
|
check_git_repo() {
|
||||||
|
if ! git rev-parse --git-dir >/dev/null 2>&1; then
|
||||||
|
print_error "Not in a git repository"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Change to the git repository root to ensure we operate on the entire repo
|
||||||
|
local git_root
|
||||||
|
git_root=$(git rev-parse --show-toplevel)
|
||||||
|
if [ "$PWD" != "$git_root" ]; then
|
||||||
|
print_info "Changing to git repository root: $git_root"
|
||||||
|
cd "$git_root" || {
|
||||||
|
print_error "Failed to change to git repository root"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to check for uncommitted changes and unpushed commits
|
||||||
|
check_for_changes() {
|
||||||
|
local has_staged=false
|
||||||
|
local has_modified=false
|
||||||
|
local has_untracked=false
|
||||||
|
|
||||||
|
# Check for staged changes
|
||||||
|
if ! git diff --cached --quiet; then
|
||||||
|
has_staged=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for modified files
|
||||||
|
if ! git diff --quiet; then
|
||||||
|
has_modified=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for untracked files
|
||||||
|
if [ -n "$(git ls-files --others --exclude-standard)" ]; then
|
||||||
|
has_untracked=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for unpushed commits
|
||||||
|
local current_branch
|
||||||
|
current_branch=$(git branch --show-current)
|
||||||
|
local unpushed_commits=""
|
||||||
|
if git rev-parse --verify "origin/$current_branch" >/dev/null 2>&1; then
|
||||||
|
unpushed_commits=$(git rev-list "origin/$current_branch..HEAD" -- 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If add-all is enabled, check if we have any changes at all
|
||||||
|
if [ "$ADD_ALL" = true ]; then
|
||||||
|
if [ "$has_staged" = false ] && [ "$has_modified" = false ] && [ "$has_untracked" = false ]; then
|
||||||
|
# No working tree changes, but check for unpushed commits
|
||||||
|
if [ -n "$unpushed_commits" ]; then
|
||||||
|
local commit_count
|
||||||
|
commit_count=$(echo "$unpushed_commits" | wc -l)
|
||||||
|
print_info "No working tree changes, but found $commit_count unpushed commit(s)"
|
||||||
|
print_info "Latest unpushed commit: $(git log --oneline -1)"
|
||||||
|
|
||||||
|
# Set a flag to indicate we should only push, not commit
|
||||||
|
PUSH_ONLY=true
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
print_info "No changes to commit (working tree clean)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# If add-all is disabled, only check staged changes
|
||||||
|
if [ "$has_staged" = false ]; then
|
||||||
|
# No staged changes, but check for unpushed commits
|
||||||
|
if [ -n "$unpushed_commits" ]; then
|
||||||
|
local commit_count
|
||||||
|
commit_count=$(echo "$unpushed_commits" | wc -l)
|
||||||
|
print_info "No staged changes, but found $commit_count unpushed commit(s)"
|
||||||
|
print_info "Latest unpushed commit: $(git log --oneline -1)"
|
||||||
|
|
||||||
|
if [ "$has_modified" = true ] || [ "$has_untracked" = true ]; then
|
||||||
|
print_warning "You have unstaged changes that won't be included"
|
||||||
|
print_info "Use 'gp -a' to include all changes, or stage them first with 'git add'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set a flag to indicate we should only push, not commit
|
||||||
|
PUSH_ONLY=true
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
if [ "$has_modified" = true ] || [ "$has_untracked" = true ]; then
|
||||||
|
print_warning "No staged changes found"
|
||||||
|
print_info "Use 'gp -a' to add all files, or stage changes first with 'git add'"
|
||||||
|
else
|
||||||
|
print_info "No changes to commit (working tree clean)"
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to show what would be committed and get user confirmation
|
||||||
|
show_status_and_confirm() {
|
||||||
|
print_info "Current branch: $(git branch --show-current)"
|
||||||
|
print_info "Repository: $(git remote get-url origin 2>/dev/null || echo 'No remote')"
|
||||||
|
echo
|
||||||
|
|
||||||
|
local has_staged_changes=false
|
||||||
|
local has_unstaged_changes=false
|
||||||
|
local has_untracked_files=false
|
||||||
|
|
||||||
|
# Show staged changes
|
||||||
|
if ! git diff --cached --quiet; then
|
||||||
|
local staged_modified=""
|
||||||
|
local staged_deleted=""
|
||||||
|
local staged_added=""
|
||||||
|
|
||||||
|
# Get staged file status and categorize
|
||||||
|
while IFS=$'\t' read -r status file; do
|
||||||
|
[ -z "$status" ] && continue
|
||||||
|
case "${status:0:1}" in
|
||||||
|
A) staged_added="${staged_added}${file}\n" ;;
|
||||||
|
M) staged_modified="${staged_modified}${file}\n" ;;
|
||||||
|
D) staged_deleted="${staged_deleted}${file}\n" ;;
|
||||||
|
*) staged_modified="${staged_modified}${file}\n" ;; # Default to modified for other statuses
|
||||||
|
esac
|
||||||
|
done < <(git diff --cached --name-status)
|
||||||
|
|
||||||
|
# Show staged added files
|
||||||
|
if [ -n "$staged_added" ]; then
|
||||||
|
print_info "Staged new files:"
|
||||||
|
echo -e "$staged_added" | grep -v '^$' | while IFS= read -r line; do echo " $line"; done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show staged modified files
|
||||||
|
if [ -n "$staged_modified" ]; then
|
||||||
|
print_info "Staged modified files:"
|
||||||
|
echo -e "$staged_modified" | grep -v '^$' | while IFS= read -r line; do echo " $line"; done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show staged deleted files
|
||||||
|
if [ -n "$staged_deleted" ]; then
|
||||||
|
print_info "Staged deleted files:"
|
||||||
|
echo -e "$staged_deleted" | grep -v '^$' | while IFS= read -r line; do echo " $line"; done
|
||||||
|
fi
|
||||||
|
|
||||||
|
has_staged_changes=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show unstaged changes
|
||||||
|
if ! git diff --quiet; then
|
||||||
|
local modified_files=""
|
||||||
|
local deleted_files=""
|
||||||
|
|
||||||
|
# Get file status and categorize
|
||||||
|
while IFS=$'\t' read -r status file; do
|
||||||
|
[ -z "$status" ] && continue
|
||||||
|
case "${status:0:1}" in
|
||||||
|
M) modified_files="${modified_files}${file}\n" ;;
|
||||||
|
D) deleted_files="${deleted_files}${file}\n" ;;
|
||||||
|
*) modified_files="${modified_files}${file}\n" ;; # Default to modified for other statuses
|
||||||
|
esac
|
||||||
|
done < <(git diff --name-status)
|
||||||
|
|
||||||
|
# Show modified files
|
||||||
|
if [ -n "$modified_files" ]; then
|
||||||
|
if [ "$ADD_ALL" = true ]; then
|
||||||
|
print_info "Modified files (will be added):"
|
||||||
|
else
|
||||||
|
print_info "Modified files (unstaged, will NOT be included):"
|
||||||
|
fi
|
||||||
|
echo -e "$modified_files" | grep -v '^$' | while IFS= read -r line; do echo " $line"; done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show deleted files
|
||||||
|
if [ -n "$deleted_files" ]; then
|
||||||
|
if [ "$ADD_ALL" = true ]; then
|
||||||
|
print_info "Deleted files (will be removed):"
|
||||||
|
else
|
||||||
|
print_info "Deleted files (unstaged, will NOT be included):"
|
||||||
|
fi
|
||||||
|
echo -e "$deleted_files" | grep -v '^$' | while IFS= read -r line; do echo " $line"; done
|
||||||
|
fi
|
||||||
|
|
||||||
|
has_unstaged_changes=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show untracked files
|
||||||
|
if [ "$ADD_ALL" = true ]; then
|
||||||
|
local untracked
|
||||||
|
untracked=$(git ls-files --others --exclude-standard)
|
||||||
|
if [ -n "$untracked" ]; then
|
||||||
|
print_info "Untracked files (will be added):"
|
||||||
|
# Use while loop to add indent without sed
|
||||||
|
while IFS= read -r line; do
|
||||||
|
echo " $line"
|
||||||
|
done <<< "$untracked"
|
||||||
|
has_untracked_files=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show summary of what will happen
|
||||||
|
echo
|
||||||
|
if [ "$ADD_ALL" = true ]; then
|
||||||
|
if [ "$has_unstaged_changes" = true ] || [ "$has_untracked_files" = true ]; then
|
||||||
|
print_warning "Files will be automatically added before committing"
|
||||||
|
fi
|
||||||
|
if [ "$has_staged_changes" = false ] && [ "$has_unstaged_changes" = false ] && [ "$has_untracked_files" = false ]; then
|
||||||
|
print_info "No changes found to commit"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ "$has_unstaged_changes" = true ]; then
|
||||||
|
print_warning "Unstaged changes will NOT be included (use -a to include them)"
|
||||||
|
fi
|
||||||
|
if [ "$has_staged_changes" = false ]; then
|
||||||
|
print_info "No staged changes to commit"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Confirmation for file additions (unless forced or dry-run)
|
||||||
|
if [ "$ADD_ALL" = true ] && [ "$FORCE" = false ] && [ "$DRY_RUN" = false ]; then
|
||||||
|
if [ "$has_unstaged_changes" = true ] || [ "$has_untracked_files" = true ]; then
|
||||||
|
echo
|
||||||
|
read -p "Add these files and continue? [y/N] " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
print_info "Aborted by user"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to perform the actual commit and push
|
||||||
|
do_commit_and_push() {
|
||||||
|
local commit_msg="$1"
|
||||||
|
local target_branch="$2"
|
||||||
|
|
||||||
|
# Add files if requested
|
||||||
|
if [ "$ADD_ALL" = true ]; then
|
||||||
|
print_info "Adding all files..."
|
||||||
|
git add -A
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Commit
|
||||||
|
print_info "Committing with message: '$commit_msg'"
|
||||||
|
git commit -m "$commit_msg" --
|
||||||
|
|
||||||
|
# Push
|
||||||
|
print_info "Pushing to $target_branch..."
|
||||||
|
# Check if branch exists on remote to determine if we need -u flag
|
||||||
|
if git ls-remote --exit-code --heads origin "$target_branch" >/dev/null 2>&1; then
|
||||||
|
# Branch exists on remote, normal push
|
||||||
|
if git push origin "$target_branch"; then
|
||||||
|
print_success "Successfully pushed to origin/$target_branch"
|
||||||
|
else
|
||||||
|
print_error "Failed to push to origin/$target_branch"
|
||||||
|
print_info "You may need to pull first: git pull origin $target_branch"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Branch doesn't exist on remote, use -u to set upstream
|
||||||
|
print_info "Setting upstream branch (first push to origin/$target_branch)"
|
||||||
|
if git push -u origin "$target_branch"; then
|
||||||
|
print_success "Successfully pushed and set upstream to origin/$target_branch"
|
||||||
|
else
|
||||||
|
print_error "Failed to push to origin/$target_branch"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Autocomplete function for bash
|
||||||
|
autocomplete() {
|
||||||
|
local args=("$@")
|
||||||
|
if [ ${#args[@]} -eq 0 ]; then
|
||||||
|
printf "%s\n" "--help" "--dry-run" "--force" "--add-all" "--staged-only" "--branch" "-h" "-n" "-f" "-a" "-b"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Default values
|
||||||
|
DRY_RUN=false
|
||||||
|
FORCE=false
|
||||||
|
ADD_ALL=true # Default to adding all files
|
||||||
|
CUSTOM_MESSAGE=""
|
||||||
|
TARGET_BRANCH=""
|
||||||
|
PUSH_ONLY=false
|
||||||
|
|
||||||
|
# Handle special commands first
|
||||||
|
case "${1:-}" in
|
||||||
|
autocomplete)
|
||||||
|
shift
|
||||||
|
autocomplete "$@"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
version)
|
||||||
|
echo "2025.0802.1112"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Parse command line arguments
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-h|--help)
|
||||||
|
show_help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-n|--dry-run)
|
||||||
|
DRY_RUN=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-f|--force)
|
||||||
|
FORCE=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-a|--add-all)
|
||||||
|
ADD_ALL=true # Explicitly set (though it's default)
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--staged-only)
|
||||||
|
ADD_ALL=false
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-b|--branch)
|
||||||
|
if [ -z "${2:-}" ]; then
|
||||||
|
print_error "Option -b/--branch requires a branch name"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
TARGET_BRANCH="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
print_error "Unknown option: $1"
|
||||||
|
echo "Use 'gp --help' for usage information"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
CUSTOM_MESSAGE="$1"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Main execution
|
||||||
|
main() {
|
||||||
|
# Safety checks
|
||||||
|
check_git_repo
|
||||||
|
|
||||||
|
# Set target branch if not specified
|
||||||
|
if [ -z "$TARGET_BRANCH" ]; then
|
||||||
|
TARGET_BRANCH=$(git branch --show-current)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for changes
|
||||||
|
check_for_changes
|
||||||
|
|
||||||
|
# Handle push-only case (unpushed commits but no working tree changes)
|
||||||
|
if [ "$PUSH_ONLY" = true ]; then
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Dry run mode for push-only
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
print_warning "DRY RUN MODE - No changes will be made"
|
||||||
|
print_info "Would push existing commits to: origin/$TARGET_BRANCH"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Safety confirmation for push-only (unless forced)
|
||||||
|
if [ "$FORCE" = false ]; then
|
||||||
|
echo
|
||||||
|
read -p "Push existing commits to origin/$TARGET_BRANCH? [y/N] " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
print_info "Aborted by user"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Execute push only
|
||||||
|
print_info "Pushing existing commits to $TARGET_BRANCH..."
|
||||||
|
# Check if branch exists on remote to determine if we need -u flag
|
||||||
|
if git ls-remote --exit-code --heads origin "$TARGET_BRANCH" >/dev/null 2>&1; then
|
||||||
|
# Branch exists on remote, normal push
|
||||||
|
if git push origin "$TARGET_BRANCH"; then
|
||||||
|
print_success "Successfully pushed existing commits to origin/$TARGET_BRANCH"
|
||||||
|
else
|
||||||
|
print_error "Failed to push to origin/$TARGET_BRANCH"
|
||||||
|
print_info "You may need to pull first: git pull origin $TARGET_BRANCH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Branch doesn't exist on remote, use -u to set upstream
|
||||||
|
print_info "Setting upstream branch (first push to origin/$TARGET_BRANCH)"
|
||||||
|
if git push -u origin "$TARGET_BRANCH"; then
|
||||||
|
print_success "Successfully pushed existing commits and set upstream to origin/$TARGET_BRANCH"
|
||||||
|
else
|
||||||
|
print_error "Failed to push to origin/$TARGET_BRANCH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show current status and get confirmation for file additions
|
||||||
|
show_status_and_confirm
|
||||||
|
|
||||||
|
# Generate or use custom commit message
|
||||||
|
if [ -n "$CUSTOM_MESSAGE" ]; then
|
||||||
|
commit_message="$CUSTOM_MESSAGE"
|
||||||
|
print_info "Using custom commit message: '$commit_message'"
|
||||||
|
else
|
||||||
|
print_info "Generating commit message..."
|
||||||
|
if ! commit_message=$(generate_commit_message); then
|
||||||
|
print_error "Failed to generate commit message"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
print_info "Generated commit message: '$commit_message'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Dry run mode
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
print_warning "DRY RUN MODE - No changes will be made"
|
||||||
|
print_info "Would commit with message: '$commit_message'"
|
||||||
|
print_info "Would push to: origin/$TARGET_BRANCH"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Safety confirmation (unless forced)
|
||||||
|
if [ "$FORCE" = false ]; then
|
||||||
|
echo
|
||||||
|
read -p "Proceed with commit and push? [y/N] " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
print_info "Aborted by user"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Execute the commit and push
|
||||||
|
do_commit_and_push "$commit_message" "$TARGET_BRANCH"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run main function
|
||||||
|
main "$@"
|
20
publish.sh
Executable file
20
publish.sh
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
#set projec to the folder this script is in.
|
||||||
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||||
|
PROJECT=$(basename "$SCRIPT_DIR")
|
||||||
|
SOS_DIR="${SCRIPT_DIR}/../sos"
|
||||||
|
|
||||||
|
# run sos to upload sos
|
||||||
|
"${SOS_DIR}/sos" upload "getbin.xyz" "${SCRIPT_DIR}/${PROJECT}" "${PROJECT}:latest"
|
||||||
|
|
||||||
|
# publish the tool
|
||||||
|
GETPKG="${SCRIPT_DIR}/../getpkg/output/getpkg"
|
||||||
|
[ -f "${GETPKG}" ] || die "Failed to find getpkg"
|
||||||
|
TOOLDIR="${SCRIPT_DIR}/tool"
|
||||||
|
mkdir -p "${TOOLDIR}"
|
||||||
|
cp "${SCRIPT_DIR}/${PROJECT}" "${TOOLDIR}/${PROJECT}"
|
||||||
|
"${GETPKG}" publish "${PROJECT}" "${TOOLDIR}"
|
||||||
|
rm -rf "${TOOLDIR}"
|
Reference in New Issue
Block a user