Created
January 24, 2019 04:42
-
-
Save xmodar/eb87b9f82dac4fa9e853fba529d01cb1 to your computer and use it in GitHub Desktop.
A shell script for single file branch version control (sfbvc)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# sfbvc stands for single file branch version control. | |
# | |
# By Modar Alfadly <https://modar.me> on the 24th Jan 2019 | |
# | |
# sfbvc is a convention to create an orphan branch for each file in a repository | |
# and the master branch will be the merge of all the other branches. | |
# This is a niche convention which is trying to simulate file history | |
# in applications similar to cloud storages like Google Drive and Dropbox. | |
# We are using git under the hood and assuming that it is installed and in PATH. | |
# | |
# The limitation here becomes in the naming of the branches. | |
# See: https://wincent.com/wiki/Legal_Git_branch_names | |
# and: https://www.spinics.net/lists/git/msg133704.html | |
# Legal file names can not: | |
# - Have a path component that begins with "." | |
# - Have a double dot "…" | |
# - Have an ASCII control character, "~", "^", ":" or SP, anywhere | |
# - End with a "/" | |
# - End with ".lock" | |
# - Contain a "\" (backslash) | |
# - Contain "*", "?", "[" or "@{" | |
# - Be "master" and placed in the root directory | |
# | |
# To use these commands, run the following: | |
# source sfbvc | |
# | |
# To disable them, run the following: | |
# nosfbvc | |
# | |
# Please, don't use git directly on an sfbvc repositry. | |
# Instead, use the commands defined by this file. | |
# The commands are commit, push and pull. | |
############################################# | |
############################################# | |
# Helper commands | |
############################################# | |
# get the directory of the repository | |
repo-dir () { | |
git rev-parse --show-toplevel | |
} | |
# get the branch name of the given file | |
branch-name () { | |
# allow only one passed argument | |
if [ "$#" -ne 1 ]; then | |
echo "You should pass a single file" | |
else | |
local repodir=$(repo-dir) | |
local filedir=$(realpath $1) | |
# local offset=$((${#repodir} + 1)) | |
# echo ${filedir:offset} | |
echo ${filedir#$repodir/} | |
fi | |
} | |
# list modified and untracked files | |
# this will unstage any staged changes | |
ls-files () { | |
# unstage all staged files | |
git reset > /dev/null 2>&1 # > /dev/null 2>&1 suppresses stdout & stderr | |
# git reset > /dev/null 2>/dev/null # equivalent to the above line | |
# list modified and untracted files | |
git ls-files --modified --others --full-name $(repo-dir) | |
} | |
############################################# | |
# Main commands | |
############################################# | |
# commit a single file | |
commit () { | |
# allow only one passed argument | |
if [ "$#" -ne 1 ]; then | |
echo "You should pass a single file to commit" | |
else | |
# unstage all staged files | |
git reset > /dev/null 2>&1 | |
# stash all changes | |
git stash > /dev/null 2>&1 | |
# get branch name | |
local branch=$(branch-name $1) | |
# try to create an orphan branch and capture output | |
local message=$((git checkout --orphan $branch --) 2>&1) | |
# check if invalid branch name | |
if [[ $message == *"is not a valid branch name." ]]; then | |
echo "$branch is an invalid file name and will not be saved" | |
else | |
# check if new file | |
if [[ $message == "Switched to a new branch"* ]]; then | |
# clear the index and the working tree | |
git rm -rf $(repo-dir) > /dev/null 2>&1 | |
# stage and commit new file | |
git add $1 && git commit -m "add file" > /dev/null 2>&1 | |
else | |
# checkout to the branch | |
git checkout $branch -- > /dev/null 2>&1 | |
# unstash the changes | |
git checkout stash -- $1 && git stash drop > /dev/null 2>&1 | |
# commit the file | |
git commit -m "changes" > /dev/null 2>&1 | |
fi | |
# checkout back to master | |
git checkout master -- > /dev/null 2>&1 | |
# merge branch and fast forward | |
git merge $branch --no-commit --ff > /dev/null 2>&1 | |
fi | |
fi | |
} | |
# push everything to remote | |
push () { | |
git push --all origin | |
} | |
# pull all changes from remote | |
pull () { | |
# track all remote branches | |
for remote in `git branch -r | grep -v '\->'`; do | |
git branch --track ${remote#origin/} $remote > /dev/null 2>&1 | |
done | |
# pull all the branches | |
git pull --all | |
} | |
# unset and deactivate these commands | |
nosfbvc () { | |
unset -f repo-dir branch-name ls-files | |
unset commit push pull | |
unset -f nosfbvc | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment