Skip to content

Instantly share code, notes, and snippets.

@xmodar
Created January 24, 2019 04:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xmodar/eb87b9f82dac4fa9e853fba529d01cb1 to your computer and use it in GitHub Desktop.
Save xmodar/eb87b9f82dac4fa9e853fba529d01cb1 to your computer and use it in GitHub Desktop.
A shell script for single file branch version control (sfbvc)
# 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