Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c6fcd81155 | |||
| 5b99160266 | |||
| 38e71f6f9d | |||
| fa9b2a38b1 | |||
| d3f7a39b96 | |||
| bc874fa5bd | |||
| f0c9f902a4 | |||
| b9e145abdf | |||
| a129ad766a | |||
| 8f4a8a326e | |||
| 8974b650ac | |||
| e5ac597258 | |||
| 9999e54375 | |||
| 30657dcd75 | |||
| 0b191ec2f9 | |||
| 562a4b94ca | |||
| 799199dcc2 | |||
| 33df53a989 | |||
| ad4a370334 | |||
| 58851c4c15 | |||
| d33bde493a | |||
| 9a061ce30b | |||
| 236f95ea18 | |||
| 2da11516b4 | |||
| e735f44727 | |||
| 8f40e82492 | |||
| da4e21fe5c | |||
| 01a44ddc7c | |||
| 0702c3a67b | |||
| bcde137050 | |||
| da31f19926 | |||
| 2b1fb56c4a | |||
| 4f142bf0dd | |||
| 5c76da3ebd | |||
| 0d632b15e1 | |||
| f6898b7056 | |||
| ae2fa19df6 | |||
| 2798299f11 | |||
| f3fb0fc3f9 | |||
| c7d9abc59e | |||
| 80593359ca | |||
| 337c9679bc | |||
| f18a2dc6ba | |||
| 97c7dfb4b5 | |||
| 46a74c1b08 | |||
| b9336549c1 | |||
| 30ee8139f7 | |||
| 4482885845 | |||
| 615988dc01 | |||
| c9b1f5c2e0 | |||
| 85ee0683eb | |||
| c48f1d592a | |||
| 15e62530d9 | |||
| 8e81d7276e | |||
| b6d35128c4 | |||
| 6dc3f6870d | |||
| 28f347b89d | |||
| a94af51b11 | |||
| b11805e886 | |||
| 5327cb53c9 | |||
| c5db0306de | |||
| a16bc0b0c8 |
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Ivan Malopinsky
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,38 +1,53 @@
|
||||
# git-fresh :lemon:
|
||||
# git-fresh :lemon:
|
||||
|
||||
Keep your repo fresh with one command.
|
||||
|
||||
* Stashes your unstaged changes
|
||||
* Updates local master to match remote, prunes stale branches
|
||||
* Deletes stale local and remote branches with `-f` flag
|
||||
* Merges remote master into current branch with `-m` flag
|
||||
* Rebases current branch against remote master with `-r` flag
|
||||
* Restores your stashed changes with `-s` flag
|
||||
* Wipes the slate clean with `-F` flag
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
Usage: git fresh [-fmrsF] [remote] [root]
|
||||
Usage: git fresh [-fmrtRW] [-sl] [remote] [root]
|
||||
By default, git-fresh will:
|
||||
- update local root (master) to match remote root
|
||||
- stash changes
|
||||
- prune remote branches
|
||||
|
||||
git-fresh will ignore any branches listed in a .freshignore file.
|
||||
.freshignore should contain branch names you would like to ignore
|
||||
on separate lines. The file can exist in the current Git repo
|
||||
or in the home directory, i.e. ~/.freshignore.
|
||||
|
||||
-f: Delete stale local and remote branches
|
||||
-m: Merge remote root into current branch
|
||||
-r: Rebase current branch against remote root
|
||||
-t: Remove local tags that do not exist on remote
|
||||
-R: Reset local root to remote root
|
||||
-W: Wipe workspace clean
|
||||
|
||||
-s: Apply stashed changes after run
|
||||
-F: Reset local root to remote root, wipe workspace
|
||||
-l: Only delete local stale branches
|
||||
|
||||
-v: Print git-fresh version and exit
|
||||
|
||||
remote: remote name, origin by default
|
||||
root: root branch, master by default
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
### Manual
|
||||
### Manual on Linux or MacOSX
|
||||
|
||||
1. Clone or download
|
||||
2. `cd git-fresh`
|
||||
3. `sudo make install`
|
||||
3. `sudo ./install`
|
||||
|
||||
### Manual on Windows
|
||||
|
||||
Copy the file [git-fresh](https://raw.githubusercontent.com/imsky/git-fresh/master/git-fresh) to `usr\bin` in your git installation directory.
|
||||
This usually is `C:\Program Files\Git\usr\bin`.
|
||||
|
||||
### Package
|
||||
|
||||
* [Homebrew](http://brew.sh/): `brew install git-fresh`
|
||||
* [bpkg](http://www.bpkg.io/): `bpkg install imsky/git-fresh`
|
||||
|
||||
## License
|
||||
|
||||
@@ -1,24 +1,58 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# git-fresh
|
||||
# https://github.com/imsky/git-fresh
|
||||
# By Ivan Malopinsky - http://imsky.co
|
||||
# MIT License
|
||||
|
||||
usage () {
|
||||
echo "Usage: git fresh [-fmrsF] [remote] [root]"
|
||||
echo "-f: Delete stale local and remote branches"
|
||||
echo "-m: Merge remote root into current branch"
|
||||
echo "-r: Rebase current branch against remote root"
|
||||
echo "-s: Apply stashed changes after run"
|
||||
echo "-F: Reset local root to remote root, wipe workspace"
|
||||
echo "remote: remote name, origin by default"
|
||||
echo "root: root branch, master by default"
|
||||
exit 1;
|
||||
cat << EOD
|
||||
Usage: git fresh [-fmrtRW] [-sl] [remote] [root]
|
||||
By default, git-fresh will:
|
||||
- update local root (master) to match remote root
|
||||
- stash changes
|
||||
- prune remote branches
|
||||
|
||||
git-fresh will ignore any branches listed in a .freshignore file.
|
||||
.freshignore should contain branch names you would like to ignore
|
||||
on separate lines. The file can exist in the current Git repo
|
||||
or in the home directory, i.e. ~/.freshignore.
|
||||
|
||||
-f: Delete stale local and remote branches
|
||||
-m: Merge remote root into current branch
|
||||
-r: Rebase current branch against remote root
|
||||
-t: Remove local tags that do not exist on remote
|
||||
-R: Reset local root to remote root
|
||||
-W: Wipe workspace clean
|
||||
|
||||
-s: Apply stashed changes after run
|
||||
-l: Only delete local stale branches
|
||||
|
||||
-v: Print git-fresh version and exit
|
||||
|
||||
remote: remote name, origin by default
|
||||
root: root branch, master by default
|
||||
EOD
|
||||
|
||||
exit 0
|
||||
}
|
||||
|
||||
say () {
|
||||
echo "[git-fresh] $@" 1>&2
|
||||
}
|
||||
|
||||
die () {
|
||||
say $@
|
||||
exit 1
|
||||
}
|
||||
|
||||
error () {
|
||||
echo -n "[git-fresh] error on line $1"
|
||||
die "Error on line $1: $(head -n $1 $0 | tail -1)"
|
||||
}
|
||||
|
||||
trap 'error $LINENO' ERR
|
||||
|
||||
while getopts "fmrsF" opt; do
|
||||
while getopts ":fmrtslRWTv" opt; do
|
||||
case $opt in
|
||||
f)
|
||||
FORCE_DELETE_STALE=true
|
||||
@@ -29,42 +63,150 @@ while getopts "fmrsF" opt; do
|
||||
r)
|
||||
REBASE=true
|
||||
;;
|
||||
t)
|
||||
TAGS=true
|
||||
;;
|
||||
s)
|
||||
APPLY_STASH=true
|
||||
;;
|
||||
F)
|
||||
FORCE_LOCAL_RESET=true
|
||||
l)
|
||||
DELETE_ONLY_LOCAL=true
|
||||
;;
|
||||
R)
|
||||
RESET_ROOT=true
|
||||
;;
|
||||
W)
|
||||
WIPE_WORKSPACE=true
|
||||
;;
|
||||
T)
|
||||
TEST=true
|
||||
;;
|
||||
v)
|
||||
VERSION=1.11.0
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
shift $((OPTIND-1))
|
||||
|
||||
CURRENT=$(git rev-parse --abbrev-ref HEAD)
|
||||
# Are we in version mode?
|
||||
|
||||
if [[ ! -z $VERSION ]]; then
|
||||
echo git-fresh $VERSION
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Are we in testing mode?
|
||||
|
||||
if [[ $TEST = true ]]; then
|
||||
PATH=$(pwd):$PATH
|
||||
TEST_DIR=/tmp/git-fresh-test
|
||||
fail_test () {
|
||||
echo 'Tests failed!'
|
||||
rm -rf $TEST_DIR
|
||||
exit 1
|
||||
}
|
||||
|
||||
rm -rf $TEST_DIR; mkdir -p $TEST_DIR; cd $TEST_DIR
|
||||
git init; touch test; git add test; git commit -am 'test'
|
||||
git checkout -b test; rm test; git commit -am 'delete test'
|
||||
git checkout master; git merge test; git checkout test
|
||||
git-fresh -fr; git rev-parse --verify test && fail_test || true
|
||||
git checkout -b test; git checkout master; git-fresh; git checkout -
|
||||
git rev-parse --abbrev-ref HEAD | grep -q test || fail_test
|
||||
rm -rf $TEST_DIR
|
||||
echo 'Tests passed!'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Are we inside a git repository?
|
||||
|
||||
INSIDE_GIT_REPO=$(git rev-parse --is-inside-work-tree 2> /dev/null)
|
||||
|
||||
if [[ -z "$INSIDE_GIT_REPO" ]]; then
|
||||
die "Not a git repository"
|
||||
fi
|
||||
|
||||
# Are we in a non-empty git repository?
|
||||
|
||||
TOP_LEVEL_DIRECTORY=$(git rev-parse --show-toplevel)
|
||||
REMOTE=${1:-origin}
|
||||
ROOT=${2:-master}
|
||||
|
||||
git remote update
|
||||
git remote prune $REMOTE
|
||||
if [[ $(ls -1 "$TOP_LEVEL_DIRECTORY/.git/refs/heads" | wc -l | xargs) -eq "0" ]]; then
|
||||
if git rev-parse --verify "$ROOT"; then
|
||||
git rev-parse "$ROOT" > "$TOP_LEVEL_DIRECTORY/.git/refs/heads/$ROOT"
|
||||
else
|
||||
(git fsck --lost-found &> /dev/null; git checkout "$ROOT") || die "No HEAD ref available"
|
||||
fi
|
||||
fi
|
||||
|
||||
CURRENT=$(git rev-parse --abbrev-ref HEAD)
|
||||
|
||||
if [[ $(git remote -v | wc -l) -gt "0" ]]; then
|
||||
REMOTES=true
|
||||
fi
|
||||
|
||||
# Is this branch in .freshignore?
|
||||
|
||||
FRESH_IGNORE="$TOP_LEVEL_DIRECTORY/.freshignore"
|
||||
|
||||
if [[ ! -f $FRESH_IGNORE ]]; then
|
||||
FRESH_IGNORE="~/.freshignore"
|
||||
fi
|
||||
|
||||
if [[ -f $FRESH_IGNORE ]]; then
|
||||
if [[ ! -z $(grep -Fx "$CURRENT" "$FRESH_IGNORE") ]]; then
|
||||
die "Branch $CURRENT is ignored"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
STASH_STAMP=git-fresh-$(date +%s)
|
||||
|
||||
# Stash changed files
|
||||
|
||||
if ! git diff-files --quiet; then
|
||||
git stash save $STASH_STAMP
|
||||
fi
|
||||
|
||||
git checkout $ROOT > /dev/null 2>&1
|
||||
if [[ $REMOTES = true ]]; then
|
||||
# Update remotes and prune stale remotes
|
||||
|
||||
if [[ "$FORCE_LOCAL_RESET" = true ]]; then
|
||||
git clean -dfx
|
||||
git reset --hard $REMOTE/$ROOT
|
||||
else
|
||||
git rebase -q $REMOTE/$ROOT
|
||||
git remote prune $REMOTE
|
||||
git remote update $REMOTE
|
||||
git remote prune $REMOTE
|
||||
fi
|
||||
|
||||
# If we are not already on root branch, switch to root branch (master)
|
||||
|
||||
if [[ "$ROOT" != "$CURRENT" ]]; then
|
||||
git checkout $ROOT > /dev/null 2>&1
|
||||
fi
|
||||
|
||||
# Wipe workspace?
|
||||
|
||||
if [[ $WIPE_WORKSPACE = true ]]; then
|
||||
git clean -dfx
|
||||
fi
|
||||
|
||||
if [[ $REMOTES = true ]]; then
|
||||
|
||||
# Reset root?
|
||||
|
||||
if [[ $RESET_ROOT = true ]]; then
|
||||
git reset --hard $REMOTE/$ROOT
|
||||
fi
|
||||
|
||||
git pull --quiet --ff-only $REMOTE $ROOT || say "Fast forward merge failed on $ROOT. You can reset local $ROOT by running git fresh -R."
|
||||
fi
|
||||
|
||||
# Compute stale branches
|
||||
|
||||
SMART_STALE=$(git branch -a --merged | tr -d "\* " | grep -Ev ">|$ROOT" | cat)
|
||||
|
||||
LOCAL_STALE=$(grep -Ev "^remotes/" <<< "$SMART_STALE" | cat)
|
||||
@@ -76,51 +218,100 @@ REMOTE_STALE=${REMOTE_STALE//remotes\/$REMOTE\/}
|
||||
if [[ ! -z "${SMART_STALE// }" ]]; then
|
||||
if [[ ! -z "${LOCAL_STALE// }" ]]; then
|
||||
STALE_BRANCHES=true
|
||||
if [[ -f "$FRESH_IGNORE" ]]; then
|
||||
LOCAL_STALE=$(echo -n $LOCAL_STALE | tr " " "\n" | grep -Fxvf "$FRESH_IGNORE" | tr "\n" " ")
|
||||
if [[ -z $LOCAL_STALE ]]; then
|
||||
STALE_BRANCHES=false
|
||||
fi
|
||||
fi
|
||||
if [[ "$FORCE_DELETE_STALE" = true ]]; then
|
||||
echo -n $LOCAL_STALE | xargs git branch -d 2> /dev/null
|
||||
echo -n $LOCAL_STALE | tr " " "\0" | xargs -0 git branch -d 2> /dev/null
|
||||
else
|
||||
echo "Local stale branches found:" $(echo -n $LOCAL_STALE | tr "\n" " ")
|
||||
if [[ $STALE_BRANCHES = true ]]; then
|
||||
say "Local stale branches found:" $(echo -n $LOCAL_STALE | tr "\n" " ")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ ! -z "${REMOTE_STALE// }" ]]; then
|
||||
STALE_BRANCHES=true
|
||||
if [[ -f "$FRESH_IGNORE" ]]; then
|
||||
REMOTE_STALE=$(echo -n $REMOTE_STALE | tr " " "\n" | grep -Fxvf "$FRESH_IGNORE" | tr "\n" " ")
|
||||
if [[ -z $REMOTE_STALE ]]; then
|
||||
STALE_BRANCHES=false
|
||||
fi
|
||||
fi
|
||||
if [[ "$FORCE_DELETE_STALE" = true ]]; then
|
||||
echo -n $REMOTE_STALE | xargs git push $REMOTE --delete
|
||||
if [[ "$DELETE_ONLY_LOCAL" != true ]]; then
|
||||
echo -n $REMOTE_STALE | tr " " "\0" | xargs -0 git push $REMOTE --delete
|
||||
fi
|
||||
else
|
||||
echo "Remote stale branches found:" $(echo -n $REMOTE_STALE | tr "\n" " ")
|
||||
if [[ $STALE_BRANCHES = true ]]; then
|
||||
say "Remote stale branches found:" $(echo -n $REMOTE_STALE | tr "\n" " ")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$FORCE_DELETE_STALE" != true && $STALE_BRANCHES = true ]]; then
|
||||
echo "Delete stale branches with: git fresh -f"
|
||||
if [[ "$FORCE_DELETE_STALE" != true && "$STALE_BRANCHES" = true ]]; then
|
||||
say "Delete stale branches with: git fresh -f"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Remove tracking information for missing upstreams
|
||||
|
||||
if [[ ! -z $(git branch -vv | grep -F "[$REMOTE/$CURRENT: gone]") ]]; then
|
||||
git branch --unset-upstream $CURRENT
|
||||
fi
|
||||
|
||||
# Rebase or merge remote root against local branch
|
||||
|
||||
if [[ ! -z $(git rev-parse --verify --quiet "$CURRENT") ]]; then
|
||||
git checkout $CURRENT 2> /dev/null
|
||||
if [[ "$ROOT" != "$CURRENT" ]]; then
|
||||
git checkout $CURRENT
|
||||
fi
|
||||
|
||||
if [ "$REBASE" = true ] && [ "$MERGE" = true ]; then
|
||||
echo "Rebase and merge enabled, skipping both"
|
||||
say "Rebase and merge enabled, skipping both"
|
||||
else
|
||||
if [[ "$REBASE" = true ]]; then
|
||||
git rebase $REMOTE/$ROOT
|
||||
fi
|
||||
if [[ "$REMOTES" = true ]]; then
|
||||
if [[ "$REBASE" = true ]]; then
|
||||
git rebase $ROOT
|
||||
fi
|
||||
|
||||
if [[ "$MERGE" = true ]]; then
|
||||
git merge --no-edit $REMOTE/$ROOT
|
||||
if [[ "$MERGE" = true ]]; then
|
||||
git merge --no-edit $ROOT
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "$CURRENT branch was stale, staying on $ROOT"
|
||||
fi
|
||||
|
||||
# Remove local tags that are missing on the remote
|
||||
|
||||
if [[ "$TAGS" = true ]]; then
|
||||
REMOTE_TAGS=$(git ls-remote --tags $REMOTE | cut -f 2)
|
||||
LOCAL_TAGS=$(git show-ref --tags | cut -d' ' -f 2)
|
||||
|
||||
for tag in $LOCAL_TAGS; do
|
||||
if [[ -z $(grep $tag <<< "$REMOTE_TAGS" | cat) ]]; then
|
||||
MISSING_TAG="${tag//refs\/tags\/}"
|
||||
git tag -d $MISSING_TAG
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Restore stashed changes
|
||||
|
||||
if [[ ! -z $(git stash list | grep $STASH_STAMP | cat) ]]; then
|
||||
if [[ "$APPLY_STASH" = true ]]; then
|
||||
git stash pop
|
||||
else
|
||||
echo "Stashed changes present, apply with: git stash pop"
|
||||
say "Stashed changes present, apply with: git stash pop"
|
||||
fi
|
||||
fi
|
||||
|
||||
git gc --auto --prune=now
|
||||
if ! git gc --auto --force; then
|
||||
git prune
|
||||
rm -rf "$TOP_LEVEL_DIRECTORY/.git/gc.log"
|
||||
fi
|
||||
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
INSTALL_DIR=${1:-/usr/local/bin}
|
||||
|
||||
mkdir -p $INSTALL_DIR
|
||||
cp git-fresh $INSTALL_DIR && ([ -e $INSTALL_DIR/git-fresh ] && echo git-fresh installed in $INSTALL_DIR)
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "git-fresh",
|
||||
"version": "1.0.0",
|
||||
"version": "1.11.0",
|
||||
"description": "Utility to keep Git repositories fresh",
|
||||
"global": true,
|
||||
"repo": "imsky/git-fresh",
|
||||
"install": "sudo make install"
|
||||
"install": "sudo ./install.sh"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user