mirror of https://codeberg.org/glenneth/stash.git
Compare commits
No commits in common. "main" and "v0.1.0-alpha.1" have entirely different histories.
main
...
v0.1.0-alp
125
.dev-notes.md
125
.dev-notes.md
|
|
@ -1,125 +0,0 @@
|
||||||
# Stash Development Notes
|
|
||||||
|
|
||||||
## Project Status (as of last update)
|
|
||||||
|
|
||||||
### Version
|
|
||||||
|
|
||||||
- Current Version: 0.1.0-alpha.1
|
|
||||||
|
|
||||||
### Core Components
|
|
||||||
|
|
||||||
1. Main Script
|
|
||||||
- Location: `stash.scm`
|
|
||||||
- Status: Functional with recursive stashing support
|
|
||||||
- Features: Command-line interface, recursive mode, help system
|
|
||||||
|
|
||||||
2. Modules
|
|
||||||
- Location: `modules/stash/`
|
|
||||||
- Components:
|
|
||||||
- help.scm: Help text and command documentation
|
|
||||||
- colors.scm: Terminal color support
|
|
||||||
- log.scm: Logging functionality
|
|
||||||
- paths.scm: Path manipulation utilities
|
|
||||||
- conflict.scm: Conflict resolution
|
|
||||||
- file-ops.scm: File operations
|
|
||||||
- package.scm: Package information
|
|
||||||
- tree.scm: Directory tree handling
|
|
||||||
|
|
||||||
### Guix Integration
|
|
||||||
|
|
||||||
1. Channel Configuration
|
|
||||||
- Location: `.guix-channel/`
|
|
||||||
- Status: Configured for distribution
|
|
||||||
- URL: <https://codeberg.org/glenneth/stash>~~~~
|
|
||||||
- Branch: main
|
|
||||||
|
|
||||||
2. Package Definition
|
|
||||||
- Location: `.guix-channel/stash/packages/stash.scm`
|
|
||||||
- Build System: GNU Build System
|
|
||||||
- Dependencies: guile-3.0
|
|
||||||
|
|
||||||
### Documentation
|
|
||||||
|
|
||||||
1. README.md
|
|
||||||
- Basic installation instructions
|
|
||||||
- Key features
|
|
||||||
- Usage examples
|
|
||||||
- Recently updated with Guix installation method
|
|
||||||
|
|
||||||
2. USER_GUIDE.md
|
|
||||||
- Comprehensive installation guide
|
|
||||||
- Shell configuration (Fish, Bash, Zsh)
|
|
||||||
- Usage examples
|
|
||||||
- Troubleshooting
|
|
||||||
- Recently added with detailed setup instructions
|
|
||||||
|
|
||||||
3. channels.scm.example
|
|
||||||
- Example Guix channel configuration
|
|
||||||
- Updated to use Codeberg repository
|
|
||||||
|
|
||||||
### Development Environment
|
|
||||||
|
|
||||||
- Build System: Copy Build System (for local development)
|
|
||||||
- Test Environment: Created test-source/ directory with sample files
|
|
||||||
- Shell Integration: Configured for Fish, Bash, and Zsh
|
|
||||||
|
|
||||||
### Recent Changes
|
|
||||||
|
|
||||||
1. Documentation Updates
|
|
||||||
- Added USER_GUIDE.md with comprehensive setup instructions
|
|
||||||
- Updated README.md with Guix installation method
|
|
||||||
- Improved markdown formatting in documentation
|
|
||||||
- Updated repository links to Codeberg
|
|
||||||
|
|
||||||
2. Package System
|
|
||||||
- Configured proper Guix channel distribution
|
|
||||||
- Updated package definition for channel distribution
|
|
||||||
- Added minimal-package.scm for local development
|
|
||||||
|
|
||||||
### Next Steps
|
|
||||||
|
|
||||||
1. Testing
|
|
||||||
- Implement comprehensive test suite
|
|
||||||
- Add more test cases for recursive mode
|
|
||||||
|
|
||||||
2. Documentation
|
|
||||||
- Add API documentation for modules
|
|
||||||
- Include more advanced usage examples
|
|
||||||
|
|
||||||
3. Features
|
|
||||||
- Enhance conflict resolution
|
|
||||||
- Add backup functionality
|
|
||||||
- Improve error reporting
|
|
||||||
|
|
||||||
### Known Issues
|
|
||||||
|
|
||||||
1. Warnings
|
|
||||||
- Intermittent warning about canonicalize-path override
|
|
||||||
- Auto-compilation messages (resolved with GUILE_AUTO_COMPILE=0)
|
|
||||||
|
|
||||||
### Repository Structure
|
|
||||||
|
|
||||||
```sh
|
|
||||||
stash/
|
|
||||||
├── .guix-channel/
|
|
||||||
│ └── stash/
|
|
||||||
│ └── packages/
|
|
||||||
│ └── stash.scm
|
|
||||||
├── modules/
|
|
||||||
│ └── stash/
|
|
||||||
│ ├── colors.scm
|
|
||||||
│ ├── conflict.scm
|
|
||||||
│ ├── file-ops.scm
|
|
||||||
│ ├── help.scm
|
|
||||||
│ ├── log.scm
|
|
||||||
│ ├── package.scm
|
|
||||||
│ ├── paths.scm
|
|
||||||
│ └── tree.scm
|
|
||||||
├── test-source/
|
|
||||||
├── .gitignore
|
|
||||||
├── README.md
|
|
||||||
├── USER_GUIDE.md
|
|
||||||
├── channels.scm.example
|
|
||||||
├── minimal-package.scm
|
|
||||||
└── stash.scm
|
|
||||||
```
|
|
||||||
|
|
@ -17,8 +17,3 @@ Thumbs.db
|
||||||
.\#*
|
.\#*
|
||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
|
|
||||||
# Test and build files
|
|
||||||
test-channels.scm
|
|
||||||
test-package.scm
|
|
||||||
*.tar.gz
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
(channel
|
|
||||||
(version 0)
|
|
||||||
(url "https://codeberg.org/glenneth/stash")
|
|
||||||
(name 'stash)
|
|
||||||
(introduction
|
|
||||||
(make-channel-introduction
|
|
||||||
"581e2fea9d15e3f9626a2f9bca817354d5b2fcc2" ; Current commit hash
|
|
||||||
(openpgp-fingerprint
|
|
||||||
"581e2fea9d15e3f9626a2f9bca817354d5b2fcc2"))))
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
(define-module (stash packages stash)
|
|
||||||
#:use-module (guix packages)
|
|
||||||
#:use-module (guix build-system gnu)
|
|
||||||
#:use-module (guix git-download)
|
|
||||||
#:use-module (guix gexp)
|
|
||||||
#:use-module ((guix licenses) #:prefix license:)
|
|
||||||
#:use-module (gnu packages guile))
|
|
||||||
|
|
||||||
(define-public stash
|
|
||||||
(package
|
|
||||||
(name "stash")
|
|
||||||
(version "0.1.0-alpha.1")
|
|
||||||
(source (origin
|
|
||||||
(method git-fetch)
|
|
||||||
(uri (git-reference
|
|
||||||
(url "https://codeberg.org/glenneth/stash.git")
|
|
||||||
(commit (string-append "v" version))))
|
|
||||||
(file-name (git-file-name name version))
|
|
||||||
(sha256
|
|
||||||
(base32
|
|
||||||
"0rpir37xdk8f500fdrkbcixjc1q0mis9yi6a6b7r17lmkdn8vx4q"))))
|
|
||||||
(build-system gnu-build-system)
|
|
||||||
(arguments
|
|
||||||
`(#:phases
|
|
||||||
(modify-phases %standard-phases
|
|
||||||
(delete 'configure)
|
|
||||||
(replace 'build
|
|
||||||
(lambda _
|
|
||||||
#t))
|
|
||||||
(replace 'install
|
|
||||||
(lambda* (#:key outputs #:allow-other-keys)
|
|
||||||
(let* ((out (assoc-ref outputs "out"))
|
|
||||||
(bin (string-append out "/bin"))
|
|
||||||
(site (string-append out "/share/guile/site/3.0"))
|
|
||||||
(go (string-append out "/lib/guile/3.0/site-ccache")))
|
|
||||||
;; Install binary
|
|
||||||
(install-file "stash.scm" bin)
|
|
||||||
(rename-file (string-append bin "/stash.scm")
|
|
||||||
(string-append bin "/stash"))
|
|
||||||
(chmod (string-append bin "/stash") #o755)
|
|
||||||
;; Install modules
|
|
||||||
(for-each
|
|
||||||
(lambda (file)
|
|
||||||
(install-file file
|
|
||||||
(string-append site "/stash")))
|
|
||||||
(find-files "modules/stash" "\\.scm$"))
|
|
||||||
#t))))))
|
|
||||||
(inputs
|
|
||||||
(list guile-3.0))
|
|
||||||
(native-inputs
|
|
||||||
(list guile-3.0)) ; For compilation
|
|
||||||
(synopsis "Symlink management utility")
|
|
||||||
(description "Stash is a command-line utility for managing symbolic links")
|
|
||||||
(home-page "https://codeberg.org/glenneth/stash")
|
|
||||||
(license license:gpl3+)))
|
|
||||||
12
DEVLOG.md
12
DEVLOG.md
|
|
@ -60,7 +60,7 @@ The project is organized into several modules:
|
||||||
|
|
||||||
## Current Operation
|
## Current Operation
|
||||||
|
|
||||||
Stash now supports three main modes of operation:
|
Stash now supports two main modes of operation:
|
||||||
|
|
||||||
1. Dot Syntax:
|
1. Dot Syntax:
|
||||||
|
|
||||||
|
|
@ -82,16 +82,6 @@ Stash now supports three main modes of operation:
|
||||||
- Moves package from source to target
|
- Moves package from source to target
|
||||||
- Creates symlink at original location
|
- Creates symlink at original location
|
||||||
|
|
||||||
3. Interactive Mode:
|
|
||||||
|
|
||||||
```scheme
|
|
||||||
stash --source=~/.config/package --interactive
|
|
||||||
```
|
|
||||||
|
|
||||||
- Takes source directory as input
|
|
||||||
- Interactively prompts for target directory
|
|
||||||
- Ideal for first-time users and exploratory stashing
|
|
||||||
|
|
||||||
### Symlink Creation Process
|
### Symlink Creation Process
|
||||||
|
|
||||||
1. Determines source and target paths
|
1. Determines source and target paths
|
||||||
|
|
|
||||||
110
README.md
110
README.md
|
|
@ -1,34 +1,9 @@
|
||||||
# Stash - Enhanced GNU Stow Replacement
|
# Stash
|
||||||
|
|
||||||
`stash` is a powerful command-line utility written in Guile Scheme that serves as an enhanced replacement for GNU Stow. It helps organize your files by moving them to a target location and creating symbolic links (symlinks) in their original location. With intelligent stashing, restoration capabilities, and GNU Stow-like deployment features, it's perfect for managing dotfiles and any other directory organization needs.
|
`stash` is a command-line utility written in Guile Scheme that helps organize your files by moving them to a target location and creating symbolic links (symlinks) in their original location. While it's great for managing dotfiles, it works with any directories you want to organize.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
There are two ways to install Stash:
|
|
||||||
|
|
||||||
### Method 1: Using Guix (Recommended)
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# Install from the local package definition
|
|
||||||
guix package --install-from-file=minimal-package.scm
|
|
||||||
|
|
||||||
# Configure your shell environment:
|
|
||||||
|
|
||||||
# For Fish shell (add to ~/.config/fish/config.fish):
|
|
||||||
set -gx GUIX_PROFILE $HOME/.guix-profile
|
|
||||||
set -gx PATH $GUIX_PROFILE/bin $PATH
|
|
||||||
|
|
||||||
# For Bash (add to ~/.bashrc):
|
|
||||||
export GUIX_PROFILE="$HOME/.guix-profile"
|
|
||||||
. "$GUIX_PROFILE/etc/profile"
|
|
||||||
|
|
||||||
# For Zsh (add to ~/.zshrc):
|
|
||||||
export GUIX_PROFILE="$HOME/.guix-profile"
|
|
||||||
. "$GUIX_PROFILE/etc/profile"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Method 2: Manual Installation
|
|
||||||
|
|
||||||
1. **Prerequisites**:
|
1. **Prerequisites**:
|
||||||
- Guile Scheme 3.0.9 or later
|
- Guile Scheme 3.0.9 or later
|
||||||
- A Unix-like environment (Linux/macOS)
|
- A Unix-like environment (Linux/macOS)
|
||||||
|
|
@ -56,91 +31,49 @@ export GUIX_PROFILE="$HOME/.guix-profile"
|
||||||
stash --help
|
stash --help
|
||||||
```
|
```
|
||||||
|
|
||||||
## Shell Integration
|
|
||||||
|
|
||||||
After installation, you might want to ensure the `stash` command is easily accessible:
|
|
||||||
|
|
||||||
1. **Using Guix Installation**:
|
|
||||||
- The command should be available after setting up your shell environment as shown above
|
|
||||||
- If not, create a symbolic link: `ln -sf ~/.guix-profile/bin/stash ~/.local/bin/stash`
|
|
||||||
|
|
||||||
2. **Using Manual Installation**:
|
|
||||||
|
|
||||||
- Add an alias to your shell config:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# For Fish (in ~/.config/fish/config.fish):
|
|
||||||
alias stash="guile -L /path/to/stash /path/to/stash/stash.scm"
|
|
||||||
|
|
||||||
# For Bash/Zsh:
|
|
||||||
alias stash='guile -L /path/to/stash /path/to/stash/stash.scm'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Key Features
|
## Key Features
|
||||||
|
|
||||||
### GNU Stow Replacement Capabilities
|
|
||||||
- **Deploy Mode**: Batch deployment of all packages (`stash -d`)
|
|
||||||
- **Package Deployment**: Deploy specific packages (`stash package-name`)
|
|
||||||
- **Dot Syntax**: Reverse symlinking from current directory (`stash .`)
|
|
||||||
- **Intelligent Stashing**: Automatically detects existing symlinks and adapts behavior
|
|
||||||
- **Restoration**: Complete file restoration with metadata tracking (`stash -R`)
|
|
||||||
|
|
||||||
### Core Functionality
|
|
||||||
- **Flexible Usage**: Works with any directories, not just config files
|
- **Flexible Usage**: Works with any directories, not just config files
|
||||||
- **Interactive Mode**: Option to interactively specify target directory
|
- **Interactive Mode**: Option to interactively specify target directory
|
||||||
- **Recursive Processing**: Can process entire directory trees
|
- **Recursive Processing**: Can process entire directory trees
|
||||||
- **Advanced Path Handling**: Supports home directory expansion and relative paths
|
- **Advanced Path Handling**: Supports home directory expansion and relative paths
|
||||||
- **Symlink Management**: Creates and manages symlinks while maintaining directory structure
|
- **Symlink Management**: Creates and manages symlinks while maintaining directory structure
|
||||||
- **Ignore Patterns**: Supports local and global ignore patterns
|
- **Ignore Patterns**: Supports local and global ignore patterns
|
||||||
- **Metadata Tracking**: Each stashed file includes restoration metadata
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### GNU Stow-like Deployment (Recommended)
|
Stash offers several ways to organize your files:
|
||||||
|
|
||||||
|
1. **Interactive Mode** (easiest for beginners):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Deploy all packages from dotfiles repository
|
# Move Pictures directory to a backup location
|
||||||
cd ~/.dotfiles && stash -d
|
guile -L . stash.scm --source ~/Pictures --interactive
|
||||||
|
|
||||||
# Deploy specific package
|
|
||||||
cd ~/.dotfiles && stash shell
|
|
||||||
|
|
||||||
# Using dot syntax (reverse symlinking)
|
|
||||||
cd ~/.dotfiles/shell && stash .
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Traditional Stashing (File Organization)
|
2. **Explicit Paths**:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Stash individual files
|
# Move Documents to backup while keeping symlink
|
||||||
stash -s ~/.zshrc -t ~/.files
|
guile -L . stash.scm --source ~/Documents/notes --target ~/backup/notes
|
||||||
|
|
||||||
# Stash with interactive target selection
|
# Move project to code archive
|
||||||
stash -s ~/.config -i
|
guile -L . stash.scm --source ~/projects/webapp --target ~/code/archive/webapp
|
||||||
|
|
||||||
# Recursive stashing
|
|
||||||
stash -s ~/.config -t ~/.files -r
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Restoration
|
3. **Recursive Mode** (for entire directory trees):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Restore stashed files back to original locations
|
# Archive entire projects directory
|
||||||
stash -R -s ~/.files/config/app/config.yml
|
guile -L . stash.scm --source ~/projects --target ~/archive/projects --recursive
|
||||||
```
|
```
|
||||||
|
|
||||||
### Complete Dotfiles Workflow
|
4. **Dot Syntax** (after files are stashed):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# 1. Stash your configurations
|
# Recreate symlink for previously stashed directory
|
||||||
stash -s ~/.zshrc -t ~/.files
|
cd ~/backup/notes
|
||||||
stash -s ~/.config/app -t ~/.files
|
guile -L . stash.scm .
|
||||||
|
|
||||||
# 2. On a new machine, deploy everything
|
|
||||||
cd ~/.files && stash -d
|
|
||||||
|
|
||||||
# 3. If needed, restore individual files
|
|
||||||
stash -R -s ~/.files/config/app/config.yml
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Common Use Cases
|
## Common Use Cases
|
||||||
|
|
@ -203,10 +136,9 @@ stash/
|
||||||
│ ├── conflict.scm # Conflict resolution
|
│ ├── conflict.scm # Conflict resolution
|
||||||
│ ├── colors.scm # Terminal colors
|
│ ├── colors.scm # Terminal colors
|
||||||
│ └── help.scm # Help messages
|
│ └── help.scm # Help messages
|
||||||
├── README.md # Project overview
|
├── README.md
|
||||||
├── USER-GUIDE.md # Comprehensive user documentation
|
|
||||||
├── DEVLOG.md # Development log
|
├── DEVLOG.md # Development log
|
||||||
└── LICENSE # GNU GPL v3
|
└── LICENSE
|
||||||
```
|
```
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
|
||||||
412
USER_GUIDE.md
412
USER_GUIDE.md
|
|
@ -1,412 +0,0 @@
|
||||||
# Stash User Guide - Enhanced GNU Stow Replacement
|
|
||||||
|
|
||||||
## Table of Contents
|
|
||||||
|
|
||||||
1. [Introduction](#introduction)
|
|
||||||
2. [Installation](#installation)
|
|
||||||
- [Using Guix](#using-guix)
|
|
||||||
- [Manual Installation](#manual-installation)
|
|
||||||
3. [Shell Configuration](#shell-configuration)
|
|
||||||
4. [GNU Stow-like Features](#gnu-stow-like-features)
|
|
||||||
- [Deploy Mode](#deploy-mode)
|
|
||||||
- [Dot Syntax](#dot-syntax)
|
|
||||||
- [Package Management](#package-management)
|
|
||||||
5. [Traditional Stashing](#traditional-stashing)
|
|
||||||
6. [Restoration](#restoration)
|
|
||||||
7. [Intelligent Stashing](#intelligent-stashing)
|
|
||||||
8. [Common Use Cases](#common-use-cases)
|
|
||||||
9. [Advanced Features](#advanced-features)
|
|
||||||
10. [Configuration](#configuration)
|
|
||||||
11. [Troubleshooting](#troubleshooting)
|
|
||||||
12. [Tips and Best Practices](#tips-and-best-practices)
|
|
||||||
|
|
||||||
## Introduction
|
|
||||||
|
|
||||||
Stash is a powerful symlink management utility written in Guile Scheme that serves as an enhanced replacement for GNU Stow. It helps you organize your files by moving them to a target location while maintaining easy access through symbolic links. With intelligent stashing, restoration capabilities, and GNU Stow-like deployment features, it's perfect for managing dotfiles and any other directory organization needs.
|
|
||||||
|
|
||||||
### Why Use Stash?
|
|
||||||
|
|
||||||
- **Keep Your Files Organized**: Move files to logical storage locations while maintaining easy access
|
|
||||||
- **Backup with Access**: Store files in backup locations without changing your workflow
|
|
||||||
- **Dotfile Management**: Perfect for managing configuration files across different machines
|
|
||||||
- **Project Organization**: Archive old projects while keeping them accessible
|
|
||||||
- **Cross-device File Management**: Safely manage files across different storage devices
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
- Guile Scheme 3.0.9 or later
|
|
||||||
- Unix-like environment (Linux/macOS)
|
|
||||||
|
|
||||||
### Using Guix
|
|
||||||
|
|
||||||
The recommended way to install Stash is using the Guix package manager:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# Install from local package definition
|
|
||||||
guix package --install-from-file=minimal-package.scm
|
|
||||||
```
|
|
||||||
|
|
||||||
After installation, the `stash` executable will be available in your Guix profile at `~/.guix-profile/bin/stash`.
|
|
||||||
|
|
||||||
### Manual Installation
|
|
||||||
|
|
||||||
If you're not using Guix, you can install Stash manually:
|
|
||||||
|
|
||||||
1. Install prerequisites:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# Debian/Ubuntu
|
|
||||||
sudo apt-get install guile-3.0
|
|
||||||
|
|
||||||
# Fedora
|
|
||||||
sudo dnf install guile30
|
|
||||||
|
|
||||||
# Arch Linux
|
|
||||||
sudo pacman -S guile
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Clone and set up the repository:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
git clone https://codeberg.org/glenneth/stash.git
|
|
||||||
cd stash
|
|
||||||
mkdir -p ~/.guile.d/site/3.0
|
|
||||||
ln -s $(pwd)/modules/stash ~/.guile.d/site/3.0/
|
|
||||||
```
|
|
||||||
|
|
||||||
## Shell Configuration
|
|
||||||
|
|
||||||
### Fish Shell
|
|
||||||
|
|
||||||
1. Add to `~/.config/fish/config.fish`:
|
|
||||||
|
|
||||||
```fish
|
|
||||||
# Guix environment setup
|
|
||||||
set -gx GUIX_PROFILE $HOME/.guix-profile
|
|
||||||
set -gx PATH $GUIX_PROFILE/bin $PATH
|
|
||||||
|
|
||||||
# Load Guix environment variables
|
|
||||||
if test -f $GUIX_PROFILE/etc/profile
|
|
||||||
for line in (cat $GUIX_PROFILE/etc/profile | grep '^export' | string replace 'export ' '')
|
|
||||||
set var (string split '=' $line)
|
|
||||||
set -gx $var[1] (eval echo $var[2])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Alternative method using symlink:
|
|
||||||
|
|
||||||
```fish
|
|
||||||
ln -sf ~/.guix-profile/bin/stash ~/.local/bin/stash
|
|
||||||
```
|
|
||||||
|
|
||||||
### Bash Shell
|
|
||||||
|
|
||||||
Add to `~/.bashrc`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Guix environment setup
|
|
||||||
export GUIX_PROFILE="$HOME/.guix-profile"
|
|
||||||
if [ -f "$GUIX_PROFILE/etc/profile" ]; then
|
|
||||||
. "$GUIX_PROFILE/etc/profile"
|
|
||||||
fi
|
|
||||||
```
|
|
||||||
|
|
||||||
### Zsh Shell
|
|
||||||
|
|
||||||
Add to `~/.zshrc`:
|
|
||||||
|
|
||||||
```zsh
|
|
||||||
# Guix environment setup
|
|
||||||
export GUIX_PROFILE="$HOME/.guix-profile"
|
|
||||||
if [ -f "$GUIX_PROFILE/etc/profile" ]; then
|
|
||||||
. "$GUIX_PROFILE/etc/profile"
|
|
||||||
fi
|
|
||||||
```
|
|
||||||
|
|
||||||
## GNU Stow-like Features
|
|
||||||
|
|
||||||
Stash provides enhanced GNU Stow replacement functionality with intelligent behavior and additional features.
|
|
||||||
|
|
||||||
### Deploy Mode
|
|
||||||
|
|
||||||
Deploy mode (`-d`) allows you to batch deploy all packages from a dotfiles repository, similar to GNU Stow:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# Deploy all packages from current directory
|
|
||||||
cd ~/.dotfiles && stash -d
|
|
||||||
|
|
||||||
# Deploy all packages with explicit path
|
|
||||||
stash -d /path/to/dotfiles
|
|
||||||
```
|
|
||||||
|
|
||||||
**How it works:**
|
|
||||||
- Scans the current directory for subdirectories (packages)
|
|
||||||
- Creates symlinks from each package's contents to the home directory
|
|
||||||
- Automatically handles directory structure creation
|
|
||||||
- Skips system files like `.git`, `README.md`, etc.
|
|
||||||
|
|
||||||
### Dot Syntax
|
|
||||||
|
|
||||||
The dot syntax (`.`) provides reverse symlinking - creating a symlink from the current directory back to its corresponding location in the home directory:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# From within a dotfiles package directory
|
|
||||||
cd ~/.dotfiles/shell
|
|
||||||
stash .
|
|
||||||
# Creates: ~/.shell -> ~/.dotfiles/shell
|
|
||||||
```
|
|
||||||
|
|
||||||
**Use cases:**
|
|
||||||
- Quick reverse linking from dotfiles repo
|
|
||||||
- Testing configurations before full deployment
|
|
||||||
- Selective package activation
|
|
||||||
|
|
||||||
### Package Management
|
|
||||||
|
|
||||||
Deploy specific packages by name:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# Deploy only the shell package
|
|
||||||
cd ~/.dotfiles && stash shell
|
|
||||||
|
|
||||||
# Deploy only the emacs package
|
|
||||||
cd ~/.dotfiles && stash emacs
|
|
||||||
```
|
|
||||||
|
|
||||||
**Package structure example:**
|
|
||||||
```
|
|
||||||
~/.dotfiles/
|
|
||||||
├── shell/
|
|
||||||
│ ├── zshrc
|
|
||||||
│ └── bashrc
|
|
||||||
├── emacs/
|
|
||||||
│ ├── config.org
|
|
||||||
│ └── init.el
|
|
||||||
└── git/
|
|
||||||
└── gitconfig
|
|
||||||
```
|
|
||||||
|
|
||||||
## Restoration
|
|
||||||
|
|
||||||
Stash includes complete restoration capabilities with metadata tracking:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# Restore a specific file
|
|
||||||
stash -R -s ~/.files/config/app/config.yml
|
|
||||||
|
|
||||||
# Restore using metadata
|
|
||||||
stash -R -s /path/to/stashed/file
|
|
||||||
```
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Each stashed file includes `.stash-meta` metadata
|
|
||||||
- Metadata contains original path information
|
|
||||||
- Automatic directory structure recreation
|
|
||||||
- Safe restoration with conflict detection
|
|
||||||
|
|
||||||
## Intelligent Stashing
|
|
||||||
|
|
||||||
Stash automatically detects existing symlinks and adapts its behavior:
|
|
||||||
|
|
||||||
**File-level stashing:** When a directory already contains symlinks, stash operates at the file level to avoid "symlink to symlink" issues.
|
|
||||||
|
|
||||||
**Directory-level stashing:** When no symlinks exist, stash can move entire directories.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# First file - directory level stashing
|
|
||||||
stash -s ~/.config/app/config.yml -t ~/.files
|
|
||||||
|
|
||||||
# Second file - detects existing symlink, uses file-level stashing
|
|
||||||
stash -s ~/.config/app/theme.json -t ~/.files
|
|
||||||
```
|
|
||||||
|
|
||||||
## Traditional Stashing
|
|
||||||
|
|
||||||
### How Stash Works
|
|
||||||
|
|
||||||
1. **Source Directory**: The original location of your files
|
|
||||||
2. **Target Directory**: Where you want to store the files
|
|
||||||
3. **Symbolic Links**: Created in the source location, pointing to the target
|
|
||||||
|
|
||||||
### Key Terms
|
|
||||||
|
|
||||||
- **Stashing**: The process of moving files to a target location and creating symlinks
|
|
||||||
- **Dot Syntax**: A shorthand way to create symlinks for previously stashed files
|
|
||||||
- **Recursive Mode**: Processing entire directory trees
|
|
||||||
|
|
||||||
## Usage Patterns
|
|
||||||
|
|
||||||
### 1. Interactive Mode
|
|
||||||
|
|
||||||
Best for beginners or when you want to choose the target location interactively.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
stash --source ~/Pictures --interactive
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Explicit Paths
|
|
||||||
|
|
||||||
When you know exactly where you want to store files.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
stash --source ~/Documents/notes --target ~/backup/notes
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Dot Syntax
|
|
||||||
|
|
||||||
Quick way to create symlinks for previously stashed files.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd ~/.dotfiles/config/nvim
|
|
||||||
stash . # Creates symlink in ~/.config/nvim
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Recursive Mode
|
|
||||||
|
|
||||||
For processing entire directory trees.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
stash --source ~/.config --target ~/.dotfiles/config --recursive
|
|
||||||
```
|
|
||||||
|
|
||||||
## Common Use Cases
|
|
||||||
|
|
||||||
### 1. Managing Dotfiles
|
|
||||||
|
|
||||||
Keep configuration files in a git repository:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# Initial stash
|
|
||||||
stash --source ~/.config/nvim --target ~/.dotfiles/config/nvim
|
|
||||||
|
|
||||||
# Later, on another machine
|
|
||||||
cd ~/.dotfiles/config/nvim
|
|
||||||
stash .
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Project Organization
|
|
||||||
|
|
||||||
Archive old projects while keeping them accessible:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
stash --source ~/projects/old-webapp --target ~/archive/projects/webapp
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Cross-device File Management
|
|
||||||
|
|
||||||
Store large files on external drives:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
stash --source ~/Videos --target /media/external/videos --recursive
|
|
||||||
```
|
|
||||||
|
|
||||||
## Advanced Features
|
|
||||||
|
|
||||||
### 1. Path Handling
|
|
||||||
|
|
||||||
- Supports home directory expansion (~)
|
|
||||||
- Handles both absolute and relative paths
|
|
||||||
- Maintains directory structure in target location
|
|
||||||
|
|
||||||
### 2. Symlink Management
|
|
||||||
|
|
||||||
- Creates intermediate directories as needed
|
|
||||||
- Handles existing symlinks gracefully
|
|
||||||
- Preserves original file permissions
|
|
||||||
|
|
||||||
### 3. Ignore Patterns
|
|
||||||
|
|
||||||
- Create `.stashignore` in source directory
|
|
||||||
- Add patterns similar to `.gitignore`
|
|
||||||
|
|
||||||
```sh
|
|
||||||
*.tmp
|
|
||||||
.DS_Store
|
|
||||||
node_modules/
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Conflict Resolution
|
|
||||||
|
|
||||||
- Automatically detects existing files/symlinks
|
|
||||||
- Interactive prompts for resolution
|
|
||||||
- Options to skip, replace, or backup
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
### Environment Variables
|
|
||||||
|
|
||||||
- `GUILE_AUTO_COMPILE=0`: Disable auto-compilation
|
|
||||||
- `GUILE_LOAD_PATH`: Add custom module paths
|
|
||||||
- `GUIX_PROFILE`: Set Guix profile location
|
|
||||||
|
|
||||||
### Global Configuration
|
|
||||||
|
|
||||||
- System-wide ignore patterns in `/etc/stash/ignore`
|
|
||||||
- User-specific patterns in `~/.config/stash/ignore`
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Common Issues and Solutions
|
|
||||||
|
|
||||||
1. **Command Not Found**
|
|
||||||
- Verify Guix profile is sourced correctly
|
|
||||||
- Check PATH includes `~/.guix-profile/bin`
|
|
||||||
- Try creating symlink in `~/.local/bin`
|
|
||||||
|
|
||||||
2. **Module Loading Issues**
|
|
||||||
- Ensure GUILE_LOAD_PATH includes module directory
|
|
||||||
- Check module permissions and ownership
|
|
||||||
- Verify Guile version compatibility
|
|
||||||
|
|
||||||
3. **Permission Errors**
|
|
||||||
- Check file/directory permissions
|
|
||||||
- Ensure write access to target location
|
|
||||||
- Verify symlink creation permissions
|
|
||||||
|
|
||||||
4. **Common Warnings**
|
|
||||||
- "canonicalize-path override": Normal, can be ignored
|
|
||||||
- "auto-compilation enabled": Set GUILE_AUTO_COMPILE=0
|
|
||||||
|
|
||||||
### Cross-device Issues
|
|
||||||
|
|
||||||
- Use the recursive flag for cross-device operations
|
|
||||||
- Ensure target location has sufficient space
|
|
||||||
|
|
||||||
## Tips and Best Practices
|
|
||||||
|
|
||||||
1. **Organization**
|
|
||||||
|
|
||||||
- Keep related files together in the target location
|
|
||||||
- Use meaningful directory names
|
|
||||||
- Document your stash organization
|
|
||||||
|
|
||||||
2. **Backup**
|
|
||||||
|
|
||||||
- Always back up important files before stashing
|
|
||||||
- Test symlinks after creation
|
|
||||||
- Use version control for dotfiles
|
|
||||||
|
|
||||||
3. **Maintenance**
|
|
||||||
|
|
||||||
- Regularly check for broken symlinks
|
|
||||||
- Keep your stash locations organized
|
|
||||||
- Document your stash structure
|
|
||||||
|
|
||||||
## Command Reference
|
|
||||||
|
|
||||||
### Basic Commands
|
|
||||||
|
|
||||||
```sh
|
|
||||||
stash --help # Display help
|
|
||||||
stash --version # Show version
|
|
||||||
stash --source DIR # Specify source directory
|
|
||||||
stash --target DIR # Specify target directory
|
|
||||||
stash --recursive # Process directories recursively
|
|
||||||
stash --interactive # Interactive target selection
|
|
||||||
```
|
|
||||||
|
|
||||||
For more information or to report issues, visit:
|
|
||||||
<https://codeberg.org/glenneth/stash>
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
(list (channel
|
|
||||||
(name 'guix)
|
|
||||||
(url "https://git.savannah.gnu.org/git/guix.git"))
|
|
||||||
(channel
|
|
||||||
(name 'stash)
|
|
||||||
(url "https://codeberg.org/glenneth/stash")
|
|
||||||
(branch "main")))
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
(define-module (stash-package)
|
|
||||||
#:use-module (guix packages)
|
|
||||||
#:use-module (guix build-system copy)
|
|
||||||
#:use-module (guix licenses)
|
|
||||||
#:use-module (guix gexp)
|
|
||||||
#:use-module (gnu packages guile))
|
|
||||||
|
|
||||||
(define-public stash
|
|
||||||
(package
|
|
||||||
(name "stash")
|
|
||||||
(version "0.2.0")
|
|
||||||
(source (local-file "." "stash-checkout"
|
|
||||||
#:recursive? #t))
|
|
||||||
(build-system copy-build-system)
|
|
||||||
(arguments
|
|
||||||
'(#:install-plan
|
|
||||||
'(("stash.scm" "share/guile/site/3.0/stash.scm")
|
|
||||||
("stash" "bin/stash")
|
|
||||||
("modules/stash" "share/guile/site/3.0/stash"))
|
|
||||||
#:phases
|
|
||||||
(modify-phases %standard-phases
|
|
||||||
(add-after 'install 'setup-wrapper
|
|
||||||
(lambda* (#:key outputs #:allow-other-keys)
|
|
||||||
(let* ((out (assoc-ref outputs "out"))
|
|
||||||
(bin (string-append out "/bin"))
|
|
||||||
(wrapper (string-append bin "/stash")))
|
|
||||||
;; Make wrapper executable
|
|
||||||
(chmod wrapper #o755)
|
|
||||||
;; Update wrapper script paths
|
|
||||||
(substitute* wrapper
|
|
||||||
(("/home/glenn/Projects/Code/stash/stash")
|
|
||||||
(string-append out "/share/guile/site/3.0/stash.scm"))
|
|
||||||
(("/home/glenn/Projects/Code/stash")
|
|
||||||
out "/share/guile/site/3.0"))
|
|
||||||
#t))))))
|
|
||||||
(inputs
|
|
||||||
(list guile-3.0))
|
|
||||||
(synopsis "Enhanced GNU Stow replacement - symlink management utility")
|
|
||||||
(description "Stash is a powerful command-line utility written in Guile Scheme
|
|
||||||
that serves as an enhanced replacement for GNU Stow. It provides intelligent stashing,
|
|
||||||
restoration capabilities, and GNU Stow-like deployment features for managing dotfiles
|
|
||||||
and organizing any directories through symlink management.")
|
|
||||||
(home-page "https://codeberg.org/glenneth/stash")
|
|
||||||
(license gpl3+)))
|
|
||||||
|
|
||||||
stash
|
|
||||||
|
|
@ -9,49 +9,35 @@
|
||||||
(define (display-help)
|
(define (display-help)
|
||||||
"Display help message explaining how to use the program."
|
"Display help message explaining how to use the program."
|
||||||
(display "\
|
(display "\
|
||||||
Usage: stash [OPTION...] [PACKAGE|.]
|
Usage: stash.scm [OPTION...] [.]
|
||||||
|
|
||||||
Enhanced Stash with GNU Stow-like functionality for dotfiles management.
|
Stash is a symlink management utility that helps organize your files by moving them
|
||||||
|
to a target location and creating symbolic links in their original location.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-s, --source=DIR Source directory to stash
|
-s, --source=DIR Source directory to stash
|
||||||
-t, --target=DIR Target directory where files will be stashed
|
-t, --target=DIR Target directory where files will be stashed
|
||||||
-r, --recursive Recursively process directories under source
|
-r, --recursive Recursively process directories under source
|
||||||
-i, --interactive Interactively prompt for target directory
|
-i, --interactive Interactively prompt for target directory
|
||||||
-d, --deploy Deploy mode: create symlinks from dotfiles repo
|
|
||||||
-R, --restore Restore stashed files back to original locations
|
|
||||||
-h, --help Display this help
|
-h, --help Display this help
|
||||||
-v, --version Display version information
|
-v, --version Display version information
|
||||||
|
|
||||||
Stashing Examples (moving files to storage):
|
Examples:
|
||||||
|
# Using dot syntax (after files are stashed):
|
||||||
|
cd ~/.dotfiles/config/nvim
|
||||||
|
stash.scm . # Creates symlink in ~/.config/nvim
|
||||||
|
|
||||||
# Stash a single directory:
|
# Stash a single directory:
|
||||||
stash -s ~/Documents/notes -t ~/backup/notes
|
stash.scm -s ~/Documents/notes -t ~/backup/notes # Move notes and create symlink
|
||||||
|
|
||||||
# Stash with interactive target selection:
|
# Stash with interactive target selection:
|
||||||
stash -s ~/Pictures -i
|
stash.scm -s ~/Pictures -i # Will prompt for target directory
|
||||||
|
|
||||||
# Recursively stash an entire directory:
|
# Recursively stash an entire directory:
|
||||||
stash -s ~/.config -t ~/.dotfiles/config -r
|
stash.scm -s ~/.config -t ~/.dotfiles/config -r # Stash all config files
|
||||||
|
|
||||||
Deployment Examples (GNU Stow-like functionality):
|
# Stash any directory to any location:
|
||||||
# Deploy all packages from dotfiles directory:
|
stash.scm -s ~/projects/web -t ~/backup/code/web # Not limited to dotfiles
|
||||||
cd ~/.dotfiles && stash -d
|
|
||||||
|
|
||||||
# Deploy specific package:
|
|
||||||
cd ~/.dotfiles && stash shell
|
|
||||||
|
|
||||||
# Using dot syntax (create symlink from current dir to home):
|
|
||||||
cd ~/.dotfiles/shell && stash .
|
|
||||||
|
|
||||||
Restoration Examples:
|
|
||||||
# Restore a stashed file back to original location:
|
|
||||||
stash -R -s ~/.files/config/guix/channels.scm
|
|
||||||
|
|
||||||
Dotfiles Workflow:
|
|
||||||
1. Collect dotfiles: ~/.files/collect-dotfiles.sh
|
|
||||||
2. Stash to dotfiles repo: stash -s ~/.zshrc -t ~/.files
|
|
||||||
3. On new machine: cd ~/.files && stash -d
|
|
||||||
4. Restore if needed: stash -R -s ~/.files/config/guix/channels.scm
|
|
||||||
|
|
||||||
Note: Stash works with any directories, not just config files or dotfiles.
|
Note: Stash works with any directories, not just config files or dotfiles.
|
||||||
You can use it to organize any files by moving them to a backup/storage
|
You can use it to organize any files by moving them to a backup/storage
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
(make-tree-node path type children)
|
(make-tree-node path type children)
|
||||||
tree-node?
|
tree-node?
|
||||||
(path node-path)
|
(path node-path)
|
||||||
(type node-type) ; 'file, 'directory, or 'symlink
|
(type node-type) ; 'file or 'directory
|
||||||
(children node-children))
|
(children node-children))
|
||||||
|
|
||||||
;; Check if a path should be ignored based on patterns
|
;; Check if a path should be ignored based on patterns
|
||||||
|
|
@ -37,10 +37,7 @@
|
||||||
(let analyze ((path root-path))
|
(let analyze ((path root-path))
|
||||||
(if (should-ignore? path ignore-patterns)
|
(if (should-ignore? path ignore-patterns)
|
||||||
#f
|
#f
|
||||||
(cond
|
(if (file-is-directory? path)
|
||||||
((file-is-symlink? path)
|
|
||||||
(make-tree-node path 'symlink '()))
|
|
||||||
((file-is-directory? path)
|
|
||||||
(let* ((entries (scandir path))
|
(let* ((entries (scandir path))
|
||||||
(children (filter-map
|
(children (filter-map
|
||||||
(lambda (entry)
|
(lambda (entry)
|
||||||
|
|
@ -48,9 +45,8 @@
|
||||||
#f
|
#f
|
||||||
(analyze (string-append path "/" entry))))
|
(analyze (string-append path "/" entry))))
|
||||||
entries)))
|
entries)))
|
||||||
(make-tree-node path 'directory children)))
|
(make-tree-node path 'directory children))
|
||||||
(else
|
(make-tree-node path 'file '()))))))
|
||||||
(make-tree-node path 'file '())))))))
|
|
||||||
|
|
||||||
;; Determine if a directory tree can be folded
|
;; Determine if a directory tree can be folded
|
||||||
(define (can-fold-tree? node target-base)
|
(define (can-fold-tree? node target-base)
|
||||||
|
|
|
||||||
14
stash
14
stash
|
|
@ -1,14 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Stash - Enhanced symlink management utility
|
|
||||||
# Wrapper script that sets up the proper Guile load path
|
|
||||||
|
|
||||||
# Get the directory where this script is located
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
|
|
||||||
# Set up Guile load path to include both project root and modules directory
|
|
||||||
export GUILE_LOAD_PATH="$SCRIPT_DIR:$SCRIPT_DIR/modules:$GUILE_LOAD_PATH"
|
|
||||||
export GUILE_AUTO_COMPILE=0
|
|
||||||
|
|
||||||
# Run the modular stash.scm with proper module loading
|
|
||||||
exec guile "$SCRIPT_DIR/stash.scm" "$@"
|
|
||||||
450
stash.scm
450
stash.scm
|
|
@ -1,3 +1,38 @@
|
||||||
|
;;; stash.scm --- A Guile script for moving directories and creating symlinks with conflict resolution
|
||||||
|
;;;
|
||||||
|
;;; Author: Glenn Thompson <glenn@kirstol.org>
|
||||||
|
;;; Version: 0.1.0-alpha.1
|
||||||
|
;;; Created: 2024-12-03
|
||||||
|
;;; Compatibility: Guile 3.0.9
|
||||||
|
;;; Keywords: symlink, file management, conflict resolution, backup
|
||||||
|
;;;
|
||||||
|
;;; Commentary:
|
||||||
|
;;;
|
||||||
|
;;; Stash is a command-line utility written in Guile Scheme designed to facilitate the movement of directories and
|
||||||
|
;;; creation of symbolic links (symlinks) for files and directories. It allows users to specify a source directory
|
||||||
|
;;; and a target directory where the symlink should be created. The utility handles potential conflicts with existing
|
||||||
|
;;; files or directories at the target location, offering users the choice to overwrite, back up, or skip the creation of new symlinks.
|
||||||
|
;;;
|
||||||
|
;;; Main Features:
|
||||||
|
;;; - Command-line argument parsing for specifying source and target paths.
|
||||||
|
;;; - Conflict detection with interactive user resolution options (overwrite, backup, skip, or cancel).
|
||||||
|
;;; - Moving directories and creating symlinks.
|
||||||
|
;;; - Simple and interactive user interface for easy use.
|
||||||
|
;;;
|
||||||
|
;;; Usage:
|
||||||
|
;;;
|
||||||
|
;;; guile -L . stash.scm --target=<target-dir> --source=<source-dir>
|
||||||
|
;;;
|
||||||
|
;;; Replace <target-dir> with the directory where you want the symlink to be created,
|
||||||
|
;;; and <source-dir> with the path to the source directory.
|
||||||
|
;;;
|
||||||
|
;;; License:
|
||||||
|
;;;
|
||||||
|
;;; This project is licensed under the GNU General Public License v3.
|
||||||
|
;;;
|
||||||
|
|
||||||
|
;;; CODE
|
||||||
|
|
||||||
(define-module (stash)
|
(define-module (stash)
|
||||||
#:use-module (ice-9 getopt-long)
|
#:use-module (ice-9 getopt-long)
|
||||||
#:use-module (ice-9 ftw)
|
#:use-module (ice-9 ftw)
|
||||||
|
|
@ -11,8 +46,12 @@
|
||||||
#:use-module (stash package)
|
#:use-module (stash package)
|
||||||
#:use-module (stash tree)
|
#:use-module (stash tree)
|
||||||
#:use-module (srfi srfi-1)
|
#:use-module (srfi srfi-1)
|
||||||
#:use-module (srfi srfi-19)
|
#:use-module (srfi srfi-19))
|
||||||
#:export (main))
|
|
||||||
|
;;; Color constants
|
||||||
|
(define blue-text "\x1b[0;34m")
|
||||||
|
(define yellow-text "\x1b[0;33m")
|
||||||
|
(define red-text "\x1b[0;31m")
|
||||||
|
|
||||||
;;; Command-line options
|
;;; Command-line options
|
||||||
(define %options
|
(define %options
|
||||||
|
|
@ -20,11 +59,62 @@
|
||||||
(source (value #t) (single-char #\s))
|
(source (value #t) (single-char #\s))
|
||||||
(recursive (value #f) (single-char #\r))
|
(recursive (value #f) (single-char #\r))
|
||||||
(interactive (value #f) (single-char #\i))
|
(interactive (value #f) (single-char #\i))
|
||||||
(deploy (value #f) (single-char #\d))
|
|
||||||
(restore (value #f) (single-char #\R))
|
|
||||||
(help (value #f) (single-char #\h))
|
(help (value #f) (single-char #\h))
|
||||||
(version (value #f) (single-char #\v))))
|
(version (value #f) (single-char #\v))))
|
||||||
|
|
||||||
|
;;; Function to handle dot directory stashing
|
||||||
|
(define (handle-dot-stash)
|
||||||
|
"Handle stashing when using the '.' syntax. Uses parent directory as target."
|
||||||
|
(let* ((current-dir (canonicalize-path (getcwd)))
|
||||||
|
(pkg-dir (if (file-is-symlink? current-dir)
|
||||||
|
(canonicalize-path (readlink current-dir))
|
||||||
|
current-dir))
|
||||||
|
(pkg-name (basename pkg-dir))
|
||||||
|
(stow-dir (dirname pkg-dir))
|
||||||
|
(target-dir (dirname stow-dir))
|
||||||
|
(ignore-patterns (read-ignore-patterns pkg-dir)))
|
||||||
|
(format #t "Package directory: ~a~%" pkg-dir)
|
||||||
|
(format #t "Stow directory: ~a~%" stow-dir)
|
||||||
|
(format #t "Target directory: ~a~%" target-dir)
|
||||||
|
(if (file-is-symlink? current-dir)
|
||||||
|
(format #t "Directory is already stashed at: ~a~%" pkg-dir)
|
||||||
|
(let ((package (make-package pkg-name pkg-dir target-dir ignore-patterns)))
|
||||||
|
(process-package package)))))
|
||||||
|
|
||||||
|
;;; Version function
|
||||||
|
(define (display-version)
|
||||||
|
"Display the current version of the program."
|
||||||
|
(newline)
|
||||||
|
(display "Stash version 0.1.0-alpha.1\n")
|
||||||
|
(exit 0))
|
||||||
|
|
||||||
|
;;; Helper function to check for version flag and display version
|
||||||
|
(define (check-for-version args)
|
||||||
|
"Check if --version or -v is in the arguments list."
|
||||||
|
(if (or (member "--version" args)
|
||||||
|
(member "-v" args))
|
||||||
|
(display-version)))
|
||||||
|
|
||||||
|
;;; Main function to handle stowing operations
|
||||||
|
(define (handle-stow package options)
|
||||||
|
(let* ((tree (analyze-tree package))
|
||||||
|
(simulate? (assoc-ref options 'simulate))
|
||||||
|
(no-folding? (assoc-ref options 'no-folding)))
|
||||||
|
(plan-operations tree package)))
|
||||||
|
|
||||||
|
;;; Prompt user for target directory path
|
||||||
|
(define (prompt-for-target source-path)
|
||||||
|
"Prompt user for target directory path."
|
||||||
|
(display (color-message (string-append "\nSource directory: " source-path "\n") blue-text))
|
||||||
|
(display (color-message "Enter target directory path (where files will be stashed): " yellow-text))
|
||||||
|
(let ((input (read-line)))
|
||||||
|
(if (string-null? input)
|
||||||
|
(begin
|
||||||
|
(display (color-message "Target directory cannot be empty. Please try again.\n" red-text))
|
||||||
|
(prompt-for-target source-path))
|
||||||
|
(canonicalize-path (expand-home input)))))
|
||||||
|
|
||||||
|
;;; Main entry point
|
||||||
(define (main args)
|
(define (main args)
|
||||||
"Main function to parse arguments and execute the program."
|
"Main function to parse arguments and execute the program."
|
||||||
(setenv "GUILE_AUTO_COMPILE" "0")
|
(setenv "GUILE_AUTO_COMPILE" "0")
|
||||||
|
|
@ -32,41 +122,29 @@
|
||||||
(let* ((options (getopt-long args %options))
|
(let* ((options (getopt-long args %options))
|
||||||
(help-wanted? (option-ref options 'help #f))
|
(help-wanted? (option-ref options 'help #f))
|
||||||
(version-wanted? (option-ref options 'version #f))
|
(version-wanted? (option-ref options 'version #f))
|
||||||
(source (option-ref options 'source #f))
|
|
||||||
(target (option-ref options 'target #f))
|
|
||||||
(recursive? (option-ref options 'recursive #f))
|
(recursive? (option-ref options 'recursive #f))
|
||||||
(interactive? (option-ref options 'interactive #f))
|
(interactive? (option-ref options 'interactive #f))
|
||||||
(deploy? (option-ref options 'deploy #f))
|
(source (option-ref options 'source #f))
|
||||||
(restore? (option-ref options 'restore #f))
|
(target (option-ref options 'target #f)))
|
||||||
(remaining-args (option-ref options '() '())))
|
|
||||||
|
|
||||||
(cond
|
(cond
|
||||||
(help-wanted?
|
(help-wanted? (display-help) (exit 0))
|
||||||
(display-help)
|
(version-wanted? (display-version) (exit 0))
|
||||||
(exit 0))
|
|
||||||
(version-wanted?
|
;; Handle dot syntax
|
||||||
(display-version)
|
((and (= (length (option-ref options '() '())) 1)
|
||||||
(exit 0))
|
(string=? (car (option-ref options '() '())) "."))
|
||||||
(deploy?
|
(handle-dot-stash))
|
||||||
(handle-deploy-mode remaining-args)
|
|
||||||
#t)
|
;; Handle interactive mode
|
||||||
(restore?
|
|
||||||
(handle-restore-mode source)
|
|
||||||
#t)
|
|
||||||
((and source target)
|
|
||||||
(handle-explicit-stash source target recursive?)
|
|
||||||
#t)
|
|
||||||
((and source interactive?)
|
((and source interactive?)
|
||||||
(handle-interactive-stash source recursive?)
|
(let ((target-path (prompt-for-target (canonicalize-path source))))
|
||||||
#t)
|
(handle-explicit-stash source target-path recursive?)))
|
||||||
;; Handle dot syntax: stash .
|
|
||||||
((and (= (length remaining-args) 1) (string=? (car remaining-args) "."))
|
;; Handle explicit paths with optional recursion
|
||||||
(handle-dot-syntax)
|
((and source target)
|
||||||
#t)
|
(handle-explicit-stash source target recursive?))
|
||||||
;; Handle package deployment: stash package-name
|
|
||||||
((and (= (length remaining-args) 1) (not (string=? (car remaining-args) ".")))
|
|
||||||
(handle-package-deploy (car remaining-args))
|
|
||||||
#t)
|
|
||||||
(else
|
(else
|
||||||
(display-help)
|
(display-help)
|
||||||
(exit 1)))))
|
(exit 1)))))
|
||||||
|
|
@ -74,287 +152,45 @@
|
||||||
(define (handle-explicit-stash source target recursive?)
|
(define (handle-explicit-stash source target recursive?)
|
||||||
"Handle stashing with explicit source and target paths."
|
"Handle stashing with explicit source and target paths."
|
||||||
(let* ((source-path (canonicalize-path source))
|
(let* ((source-path (canonicalize-path source))
|
||||||
(target-base (canonicalize-path target))
|
(target-path (canonicalize-path target))
|
||||||
(target-path (create-smart-target-path source source-path target-base)))
|
(package-name (basename source-path))
|
||||||
|
(ignore-patterns (read-ignore-patterns source-path)))
|
||||||
|
(if recursive?
|
||||||
|
(handle-recursive-stash source-path target-path)
|
||||||
|
(let ((package (make-package package-name source-path target-path ignore-patterns)))
|
||||||
|
(process-package package)))))
|
||||||
|
|
||||||
(cond
|
(define (handle-recursive-stash source target)
|
||||||
;; Handle individual files
|
"Recursively process directories under source."
|
||||||
((file-is-regular? source-path)
|
(let* ((source-path (canonicalize-path source))
|
||||||
(handle-file-stash source-path target-path))
|
(target-path (canonicalize-path target))
|
||||||
|
(source-name (basename source-path))
|
||||||
;; Handle directories
|
(target-config-path (string-append target-path "/" source-name))
|
||||||
((file-is-directory? source-path)
|
(entries (if (file-is-directory? source-path)
|
||||||
(if (directory-has-symlinks? (dirname source-path))
|
(scandir source-path)
|
||||||
;; Parent directory has symlinks, use file-level stashing
|
(list (basename source-path))))
|
||||||
(handle-directory-file-level source-path target-path recursive?)
|
(valid-entries (filter (lambda (entry)
|
||||||
;; No symlinks in parent, use directory-level stashing
|
|
||||||
(handle-directory-level source-path target-path recursive?)))
|
|
||||||
|
|
||||||
(else
|
|
||||||
(display (format #f "Error: ~a is not a regular file or directory\n" source-path))
|
|
||||||
(exit 1)))))
|
|
||||||
|
|
||||||
(define (create-smart-target-path original-source source-path target-base)
|
|
||||||
"Create intelligent target path that preserves directory structure."
|
|
||||||
(let* ((home-dir (getenv "HOME"))
|
|
||||||
;; Use the original source string if it's relative, otherwise extract from absolute path
|
|
||||||
(source-relative
|
|
||||||
(cond
|
|
||||||
;; If original source is relative (doesn't start with /), use it directly
|
|
||||||
((not (string-prefix? "/" original-source))
|
|
||||||
original-source)
|
|
||||||
;; If source is under home directory, make it relative to home
|
|
||||||
((string-prefix? home-dir source-path)
|
|
||||||
(string-drop source-path (+ (string-length home-dir) 1)))
|
|
||||||
;; Otherwise just use basename
|
|
||||||
(else (basename source-path))))
|
|
||||||
;; Remove leading dot from hidden files/dirs for cleaner organization
|
|
||||||
(clean-relative (if (string-prefix? "." source-relative)
|
|
||||||
(string-drop source-relative 1)
|
|
||||||
source-relative)))
|
|
||||||
;; Create clean target path
|
|
||||||
(string-append target-base "/" clean-relative)))
|
|
||||||
|
|
||||||
(define (create-path-metadata source-path target-path)
|
|
||||||
"Create metadata file to track original path structure for restoration."
|
|
||||||
(let* ((metadata-file (string-append target-path ".stash-meta"))
|
|
||||||
(home-dir (getenv "HOME"))
|
|
||||||
(original-relative (if (string-prefix? home-dir source-path)
|
|
||||||
(string-drop source-path (+ (string-length home-dir) 1))
|
|
||||||
source-path))
|
|
||||||
(timestamp (strftime "%Y-%m-%d %H:%M:%S" (localtime (time-second (current-time))))))
|
|
||||||
(call-with-output-file metadata-file
|
|
||||||
(lambda (port)
|
|
||||||
(write `((original-path . ,original-relative)
|
|
||||||
(timestamp . ,timestamp)
|
|
||||||
(stash-version . "0.2.0")) port)))))
|
|
||||||
|
|
||||||
;; Helper functions for intelligent stashing
|
|
||||||
|
|
||||||
(define (file-is-regular? path)
|
|
||||||
"Check if a path is a regular file."
|
|
||||||
(and (file-exists? path)
|
|
||||||
(not (file-is-directory? path))
|
|
||||||
(not (file-is-symlink? path))))
|
|
||||||
|
|
||||||
(define (directory-has-symlinks? dir-path)
|
|
||||||
"Check if a directory contains any symlinks."
|
|
||||||
(if (file-exists? dir-path)
|
|
||||||
(let ((entries (scandir dir-path)))
|
|
||||||
(any (lambda (entry)
|
|
||||||
(and (not (member entry '("." "..")))
|
(and (not (member entry '("." "..")))
|
||||||
(file-is-symlink? (string-append dir-path "/" entry))))
|
(file-is-directory? (string-append source-path "/" entry))))
|
||||||
entries))
|
entries)))
|
||||||
#f))
|
;; First ensure the config directory exists in target
|
||||||
|
(if (not (file-exists? target-config-path))
|
||||||
(define (handle-file-stash source-path target-path)
|
(mkdir-p target-config-path))
|
||||||
"Handle stashing of individual files."
|
;; Then process each subdirectory
|
||||||
(display (format #f "Stashing file: ~a -> ~a\n" source-path target-path))
|
|
||||||
;; Create target directory if it doesn't exist
|
|
||||||
(let ((target-dir (dirname target-path)))
|
|
||||||
(when (not (file-exists? target-dir))
|
|
||||||
(mkdir-p target-dir)))
|
|
||||||
;; Create metadata for restoration
|
|
||||||
(create-path-metadata source-path target-path)
|
|
||||||
;; Move file to target
|
|
||||||
(rename-file source-path target-path)
|
|
||||||
;; Create symlink back to original location
|
|
||||||
(symlink target-path source-path)
|
|
||||||
(display (format #f "Created symlink: ~a -> ~a\n" source-path target-path)))
|
|
||||||
|
|
||||||
(define (handle-directory-level source-path target-path recursive?)
|
|
||||||
"Handle directory-level stashing (traditional stash behavior)."
|
|
||||||
(display (format #f "Directory-level stashing: ~a -> ~a\n" source-path target-path))
|
|
||||||
;; Create target directory if it doesn't exist
|
|
||||||
(let ((target-dir (dirname target-path)))
|
|
||||||
(when (not (file-exists? target-dir))
|
|
||||||
(mkdir-p target-dir)))
|
|
||||||
;; Create metadata for restoration
|
|
||||||
(create-path-metadata source-path target-path)
|
|
||||||
;; Move directory to target
|
|
||||||
(rename-file source-path target-path)
|
|
||||||
;; Create symlink back to original location
|
|
||||||
(symlink target-path source-path)
|
|
||||||
(display (format #f "Created directory symlink: ~a -> ~a\n" source-path target-path)))
|
|
||||||
|
|
||||||
(define (handle-directory-file-level source-path target-path recursive?)
|
|
||||||
"Handle file-level stashing within a directory that has symlinks."
|
|
||||||
(display (format #f "File-level stashing in directory: ~a\n" source-path))
|
|
||||||
(let ((entries (scandir source-path)))
|
|
||||||
(for-each
|
(for-each
|
||||||
(lambda (entry)
|
(lambda (entry)
|
||||||
(when (not (member entry '("." "..")))
|
(let* ((source-dir (string-append source-path "/" entry))
|
||||||
(let* ((entry-source (string-append source-path "/" entry))
|
(package-name entry)
|
||||||
(entry-target (string-append target-path "/" entry)))
|
(ignore-patterns (read-ignore-patterns source-dir)))
|
||||||
(cond
|
(let ((package (make-package package-name source-dir target-config-path ignore-patterns)))
|
||||||
;; Skip existing symlinks
|
(process-package package))))
|
||||||
((file-is-symlink? entry-source)
|
valid-entries)))
|
||||||
(display (format #f "Skipping existing symlink: ~a\n" entry-source)))
|
|
||||||
;; Stash regular files
|
|
||||||
((file-is-regular? entry-source)
|
|
||||||
(handle-file-stash entry-source entry-target))
|
|
||||||
;; Recursively handle subdirectories if recursive mode
|
|
||||||
((and recursive? (file-is-directory? entry-source))
|
|
||||||
(handle-directory-file-level entry-source entry-target recursive?))))))
|
|
||||||
entries)))
|
|
||||||
|
|
||||||
(define (handle-recursive-stash source-path target-path)
|
|
||||||
"Handle recursive stashing of directories."
|
|
||||||
(let* ((base-name (basename source-path))
|
|
||||||
(ignore-patterns (read-ignore-patterns source-path))
|
|
||||||
(package (make-package base-name source-path target-path ignore-patterns)))
|
|
||||||
(process-package package)
|
|
||||||
#t))
|
|
||||||
|
|
||||||
(define (process-package package)
|
(define (process-package package)
|
||||||
"Process a single package for stashing."
|
"Process a single package for stashing."
|
||||||
(let* ((tree (analyze-tree package))
|
(let* ((tree (analyze-tree package))
|
||||||
(operations (plan-operations tree package)))
|
(operations (plan-operations tree package)))
|
||||||
(execute-operations operations)
|
(execute-operations operations)))
|
||||||
#t))
|
|
||||||
|
|
||||||
;;; Enhanced handler functions for GNU Stow-like functionality
|
|
||||||
|
|
||||||
(define (handle-interactive-stash source recursive?)
|
|
||||||
"Handle interactive stashing where user selects target directory."
|
|
||||||
(display "Interactive target selection:\n")
|
|
||||||
(display (format #f "Source: ~a\n" source))
|
|
||||||
(display "Enter target directory: ")
|
|
||||||
(let ((target (read-line)))
|
|
||||||
(if (string-null? target)
|
|
||||||
(begin
|
|
||||||
(display "No target specified. Exiting.\n")
|
|
||||||
(exit 1))
|
|
||||||
(handle-explicit-stash source target recursive?))))
|
|
||||||
|
|
||||||
(define (handle-dot-syntax)
|
|
||||||
"Handle dot syntax: create symlink from current directory to home."
|
|
||||||
(let* ((current-dir (getcwd))
|
|
||||||
(home-dir (getenv "HOME"))
|
|
||||||
(relative-path (string-drop current-dir (+ (string-length home-dir) 1)))
|
|
||||||
(target-path (string-append home-dir "/" relative-path)))
|
|
||||||
(display (format #f "Creating symlink: ~a -> ~a\n" target-path current-dir))
|
|
||||||
(when (file-exists? target-path)
|
|
||||||
(if (file-is-symlink? target-path)
|
|
||||||
(delete-file target-path)
|
|
||||||
(begin
|
|
||||||
(display (format #f "Error: ~a already exists and is not a symlink\n" target-path))
|
|
||||||
(exit 1))))
|
|
||||||
(let ((target-dir (dirname target-path)))
|
|
||||||
(when (not (file-exists? target-dir))
|
|
||||||
(mkdir-p target-dir)))
|
|
||||||
(symlink current-dir target-path)
|
|
||||||
(display (format #f "Created symlink: ~a -> ~a\n" target-path current-dir))))
|
|
||||||
|
|
||||||
(define (handle-package-deploy package-name)
|
|
||||||
"Deploy a specific package from current directory."
|
|
||||||
(let* ((package-dir (string-append (getcwd) "/" package-name))
|
|
||||||
(home-dir (getenv "HOME")))
|
|
||||||
(if (file-exists? package-dir)
|
|
||||||
(deploy-package package-dir home-dir)
|
|
||||||
(begin
|
|
||||||
(display (format #f "Error: Package directory ~a not found\n" package-dir))
|
|
||||||
(exit 1)))))
|
|
||||||
|
|
||||||
(define (handle-deploy-mode args)
|
|
||||||
"Handle deploy mode: deploy all packages or specific package."
|
|
||||||
(let ((current-dir (getcwd))
|
|
||||||
(home-dir (getenv "HOME")))
|
|
||||||
(if (null? args)
|
|
||||||
;; Deploy all packages in current directory
|
|
||||||
(deploy-all-packages current-dir home-dir)
|
|
||||||
;; Deploy specific package
|
|
||||||
(handle-package-deploy (car args)))))
|
|
||||||
|
|
||||||
(define (deploy-all-packages dotfiles-dir home-dir)
|
|
||||||
"Deploy all packages from dotfiles directory."
|
|
||||||
(display (format #f "Deploying all packages from ~a to ~a\n" dotfiles-dir home-dir))
|
|
||||||
(let ((entries (scandir dotfiles-dir)))
|
|
||||||
(for-each
|
|
||||||
(lambda (entry)
|
|
||||||
(let ((entry-path (string-append dotfiles-dir "/" entry)))
|
|
||||||
(when (and (not (member entry '("." ".." ".git" "README.md" "collect-dotfiles.sh" "STASH_GUIDE.md")))
|
|
||||||
(file-is-directory? entry-path))
|
|
||||||
(display (format #f "Deploying package: ~a\n" entry))
|
|
||||||
(deploy-package entry-path home-dir))))
|
|
||||||
entries)))
|
|
||||||
|
|
||||||
(define (deploy-package package-dir home-dir)
|
|
||||||
"Deploy a single package by creating symlinks."
|
|
||||||
(let ((package-name (basename package-dir)))
|
|
||||||
(display (format #f "Deploying package: ~a\n" package-name))
|
|
||||||
(deploy-directory-contents package-dir home-dir)))
|
|
||||||
|
|
||||||
(define (deploy-directory-contents source-dir target-base)
|
|
||||||
"Recursively deploy directory contents by creating symlinks."
|
|
||||||
(let ((entries (scandir source-dir)))
|
|
||||||
(for-each
|
|
||||||
(lambda (entry)
|
|
||||||
(when (not (member entry '("." "..")))
|
|
||||||
(let* ((source-path (string-append source-dir "/" entry))
|
|
||||||
(target-path (string-append target-base "/." entry)))
|
|
||||||
(cond
|
|
||||||
((file-is-directory? source-path)
|
|
||||||
;; For directories, create the directory and recurse
|
|
||||||
(when (not (file-exists? target-path))
|
|
||||||
(mkdir target-path #o755))
|
|
||||||
(deploy-directory-contents source-path (dirname target-path)))
|
|
||||||
(else
|
|
||||||
;; For files, create symlink
|
|
||||||
(when (file-exists? target-path)
|
|
||||||
(if (file-is-symlink? target-path)
|
|
||||||
(delete-file target-path)
|
|
||||||
(display (format #f "Warning: ~a exists and is not a symlink, skipping\n" target-path))))
|
|
||||||
(let ((target-dir (dirname target-path)))
|
|
||||||
(when (not (file-exists? target-dir))
|
|
||||||
(mkdir-p target-dir)))
|
|
||||||
(symlink source-path target-path)
|
|
||||||
(display (format #f "Created symlink: ~a -> ~a\n" target-path source-path)))))))
|
|
||||||
entries)))
|
|
||||||
|
|
||||||
;;; Restoration functionality
|
|
||||||
|
|
||||||
(define (handle-restore-mode source-path)
|
|
||||||
"Handle restoration of stashed files back to original locations."
|
|
||||||
(if source-path
|
|
||||||
(restore-stashed-item source-path)
|
|
||||||
(begin
|
|
||||||
(display "Error: --restore requires --source to specify what to restore\n")
|
|
||||||
(exit 1))))
|
|
||||||
|
|
||||||
(define (restore-stashed-item stashed-path)
|
|
||||||
"Restore a stashed file or directory back to its original location."
|
|
||||||
(let* ((metadata-file (string-append stashed-path ".stash-meta"))
|
|
||||||
(home-dir (getenv "HOME")))
|
|
||||||
(if (file-exists? metadata-file)
|
|
||||||
(let* ((metadata (call-with-input-file metadata-file read))
|
|
||||||
(original-relative (assoc-ref metadata 'original-path))
|
|
||||||
(original-path (string-append home-dir "/" original-relative)))
|
|
||||||
(display (format #f "Restoring: ~a -> ~a\n" stashed-path original-path))
|
|
||||||
|
|
||||||
;; Remove existing symlink if it exists
|
|
||||||
(when (and (file-exists? original-path) (file-is-symlink? original-path))
|
|
||||||
(delete-file original-path))
|
|
||||||
|
|
||||||
;; Create parent directory if needed
|
|
||||||
(let ((parent-dir (dirname original-path)))
|
|
||||||
(when (not (file-exists? parent-dir))
|
|
||||||
(mkdir-p parent-dir)))
|
|
||||||
|
|
||||||
;; Move file/directory back
|
|
||||||
(rename-file stashed-path original-path)
|
|
||||||
|
|
||||||
;; Remove metadata file
|
|
||||||
(delete-file metadata-file)
|
|
||||||
|
|
||||||
(display (format #f "Restored: ~a\n" original-path)))
|
|
||||||
(begin
|
|
||||||
(display (format #f "Error: No metadata found for ~a\n" stashed-path))
|
|
||||||
(display "Cannot restore without metadata file\n")
|
|
||||||
(exit 1)))))
|
|
||||||
|
|
||||||
;; Entry point for stash
|
;; Entry point for stash
|
||||||
(let ((result (main (command-line))))
|
(main (command-line))
|
||||||
(if result
|
|
||||||
(exit 0)
|
|
||||||
(exit 1)))
|
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
content1
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
content2
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
content3
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
content4
|
|
||||||
Loading…
Reference in New Issue