Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7d0d33d32b | |||
| b2fd270558 | |||
| 3d7177e84c | |||
| 8bbe2a7d49 | |||
| 136a777d73 | |||
| 9dd178ded8 | |||
| 6ad55433df | |||
| cf050df1f6 | |||
| 074154e726 | |||
| 900e9120b5 | |||
| 8b0bc89eb8 | |||
| 3825d93ff6 | |||
| 08f63dbdd4 | |||
| 9a768e9b48 | |||
| 83f2c7c80b | |||
| 57cdc7c0fa | |||
| 48429ea4df | |||
| 57f08a9758 | |||
| 23a3a64d90 | |||
| 5953344240 | |||
| c6fcd81155 | |||
| 5b99160266 | |||
| 38e71f6f9d | |||
| fa9b2a38b1 | |||
| d3f7a39b96 | |||
| bc874fa5bd | |||
| f0c9f902a4 | |||
| b9e145abdf |
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017-2020 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.
|
||||
@@ -5,45 +5,57 @@ Keep your repo fresh with one command.
|
||||
## Usage
|
||||
|
||||
```
|
||||
Usage: git fresh [-fmrtRW] [-sl] [remote] [root]
|
||||
By default, git-fresh will:
|
||||
- rebase against remote current branch
|
||||
- stash changes
|
||||
- prune remote branches
|
||||
SYNOPSIS
|
||||
git-fresh [-fmrtRWS] [-sl] [remote] [root]
|
||||
|
||||
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.
|
||||
DESCRIPTION
|
||||
git-fresh helps keep your Git repo fresh.
|
||||
|
||||
-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
|
||||
By default, git-fresh will:
|
||||
- update local root (master) to match remote root
|
||||
- stash changes
|
||||
- prune remote branches
|
||||
|
||||
-s: Apply stashed changes after run
|
||||
-l: Only delete local stale 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.
|
||||
|
||||
-v: Print git-fresh version and exit
|
||||
remote is origin by default. root is master by default.
|
||||
|
||||
remote: remote name, origin by default
|
||||
root: root branch, master by default
|
||||
OPTIONS
|
||||
-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 Clear all stash entries
|
||||
|
||||
-s Apply stashed changes after run
|
||||
-l Only delete local stale branches
|
||||
|
||||
-v Print git-fresh version and exit
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
### Manual
|
||||
### Manual on Linux or macOS
|
||||
|
||||
1. Clone or download
|
||||
2. `cd git-fresh`
|
||||
3. `sudo ./install`
|
||||
3. `sudo ./install.sh`
|
||||
|
||||
### 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`
|
||||
* [AUR](https://aur.archlinux.org/): [git-fresh](https://aur.archlinux.org/packages/git-fresh/)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# generate man page with: txt2man.sh -t git-fresh <(./git-fresh -?) > git-fresh.1
|
||||
|
||||
# git-fresh
|
||||
# https://github.com/imsky/git-fresh
|
||||
@@ -6,33 +7,48 @@
|
||||
# MIT License
|
||||
|
||||
usage () {
|
||||
cat << EOD
|
||||
Usage: git fresh [-fmrtRW] [-sl] [remote] [root]
|
||||
By default, git-fresh will:
|
||||
- rebase against remote current branch
|
||||
- stash changes
|
||||
- prune remote branches
|
||||
cat << EOT
|
||||
NAME
|
||||
git-fresh
|
||||
|
||||
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.
|
||||
SYNOPSIS
|
||||
git-fresh [-fmrtRWS] [-sl] [remote] [root]
|
||||
|
||||
-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
|
||||
DESCRIPTION
|
||||
git-fresh helps keep your Git repo fresh.
|
||||
|
||||
-s: Apply stashed changes after run
|
||||
-l: Only delete local stale branches
|
||||
By default, git-fresh will:
|
||||
- update local root (master) to match remote root
|
||||
- stash changes
|
||||
- prune remote branches
|
||||
|
||||
-v: Print git-fresh version and exit
|
||||
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.
|
||||
|
||||
remote: remote name, origin by default
|
||||
root: root branch, master by default
|
||||
EOD
|
||||
remote is origin by default. root is master by default.
|
||||
|
||||
OPTIONS
|
||||
-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 Clear all stash entries
|
||||
|
||||
-s Apply stashed changes after run
|
||||
-l Only delete local stale branches
|
||||
|
||||
-v Print git-fresh version and exit
|
||||
|
||||
BUGS
|
||||
Issues are tracked on GitHub: https://github.com/imsky/git-fresh
|
||||
|
||||
AUTHOR
|
||||
Ivan Malopinsky - http://imsky.co
|
||||
EOT
|
||||
|
||||
exit 0
|
||||
}
|
||||
@@ -47,12 +63,17 @@ die () {
|
||||
}
|
||||
|
||||
error () {
|
||||
die "Error on line $1: $(head -n $1 $0 | tail -1)"
|
||||
ERR=${ERR:-unknown}
|
||||
die "Error on line $1: $ERR"
|
||||
}
|
||||
|
||||
trap 'error $LINENO' ERR
|
||||
|
||||
while getopts ":fmrtslRWTv" opt; do
|
||||
if [[ "$1" = '--help' ]]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
while getopts ":fmrtslRWSTv" opt; do
|
||||
case $opt in
|
||||
f)
|
||||
FORCE_DELETE_STALE=true
|
||||
@@ -78,11 +99,14 @@ while getopts ":fmrtslRWTv" opt; do
|
||||
W)
|
||||
WIPE_WORKSPACE=true
|
||||
;;
|
||||
S)
|
||||
CLEAR_STASH=true
|
||||
;;
|
||||
T)
|
||||
TEST=true
|
||||
;;
|
||||
v)
|
||||
VERSION=1.8.2
|
||||
VERSION=1.12.1
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
@@ -132,20 +156,48 @@ if [[ -z "$INSIDE_GIT_REPO" ]]; then
|
||||
fi
|
||||
|
||||
# Are we in a non-empty git repository?
|
||||
|
||||
ERR="could not get top-level-directory"
|
||||
TOP_LEVEL_DIRECTORY=$(git rev-parse --show-toplevel)
|
||||
REMOTE=${1:-origin}
|
||||
ROOT=${2:-master}
|
||||
|
||||
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
|
||||
ROOT_GUESS=master
|
||||
|
||||
if [[ -n $(git show-ref refs/heads/main) && -z $(git show-ref refs/heads/master) ]]; then
|
||||
ROOT_GUESS=main
|
||||
fi
|
||||
|
||||
ROOT=${2:-$ROOT_GUESS}
|
||||
|
||||
# Recover the root HEAD if it is missing or corrupt (e.g. master head reads "master")
|
||||
recover_root () {
|
||||
ROOT_HEAD_FILE="$TOP_LEVEL_DIRECTORY/.git/refs/heads/$ROOT"
|
||||
|
||||
if [[ -e "$ROOT_HEAD_FILE" ]]; then
|
||||
if [[ $(cat "$ROOT_HEAD_FILE") = $(echo $ROOT) ]]; then
|
||||
CORRUPT_ROOT_HEAD=true
|
||||
fi
|
||||
else
|
||||
MISSING_ROOT_HEAD=true
|
||||
fi
|
||||
|
||||
if [[ "$CORRUPT_ROOT_HEAD" = "true" || "$MISSING_ROOT_HEAD" = "true" ]]; then
|
||||
ERR="failed to recover $ROOT HEAD"
|
||||
RECOVERED_ROOT_HEAD=$(cat "$TOP_LEVEL_DIRECTORY/.git/logs/refs/heads/$ROOT" | tail -n1 | cut -d' ' -f2)
|
||||
echo "$RECOVERED_ROOT_HEAD" > "$ROOT_HEAD_FILE"
|
||||
say "Recovered $ROOT HEAD, set to $RECOVERED_ROOT_HEAD"
|
||||
CORRUPT_ROOT_HEAD=false
|
||||
MISSING_ROOT_HEAD=false
|
||||
fi
|
||||
}
|
||||
|
||||
recover_root
|
||||
|
||||
LAST_WORKING_DIRECTORY="$(pwd)"
|
||||
cd "$TOP_LEVEL_DIRECTORY"
|
||||
|
||||
ERR="could not get current commit"
|
||||
CURRENT=$(git rev-parse --abbrev-ref HEAD)
|
||||
ERR=""
|
||||
|
||||
if [[ $(git remote -v | wc -l) -gt "0" ]]; then
|
||||
REMOTES=true
|
||||
@@ -165,55 +217,52 @@ if [[ -f $FRESH_IGNORE ]]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
STASH_STAMP=git-fresh-$(date +%s)
|
||||
|
||||
# Stash changed files
|
||||
|
||||
if ! git diff-files --quiet; then
|
||||
ERR="could not stash changes"
|
||||
git stash save $STASH_STAMP
|
||||
fi
|
||||
|
||||
if [[ $REMOTES = true ]]; then
|
||||
# Update remotes and prune stale remotes
|
||||
ERR="could not update and prune remotes"
|
||||
git remote prune $REMOTE
|
||||
git remote update $REMOTE
|
||||
git remote prune $REMOTE
|
||||
|
||||
# If the current branch exists on the remote, rebase against it
|
||||
REMOTE_CURRENT=$(git ls-remote $REMOTE --heads 2> /dev/null | grep "heads/$CURRENT$" | cat)
|
||||
|
||||
if [[ ! -z "$REMOTE_CURRENT" ]]; then
|
||||
git rebase $REMOTE $CURRENT
|
||||
fi
|
||||
fi
|
||||
|
||||
# If we are not already on root branch, switch to root branch (master)
|
||||
|
||||
if [[ "$ROOT" != "$CURRENT" ]]; then
|
||||
ERR="could not check out $ROOT branch"
|
||||
git checkout $ROOT > /dev/null 2>&1
|
||||
fi
|
||||
|
||||
# Wipe workspace?
|
||||
|
||||
if [[ $WIPE_WORKSPACE = true ]]; then
|
||||
ERR="could not wipe workspace"
|
||||
git clean -dfx
|
||||
fi
|
||||
|
||||
# Reset root?
|
||||
|
||||
if [[ $REMOTES = true ]]; then
|
||||
# Reset root?
|
||||
|
||||
if [[ $RESET_ROOT = true ]]; then
|
||||
ERR="could not reset root"
|
||||
git reset --hard $REMOTE/$ROOT
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ $REMOTES = true ]]; then
|
||||
git rebase -q $REMOTE/$ROOT
|
||||
ERR="could not perform fast forward merge"
|
||||
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
|
||||
|
||||
ERR="could not determine stale branches"
|
||||
SMART_STALE=$(git branch -a --merged | tr -d "\* " | grep -Ev ">|$ROOT" | cat)
|
||||
|
||||
LOCAL_STALE=$(grep -Ev "^remotes/" <<< "$SMART_STALE" | cat)
|
||||
@@ -232,6 +281,7 @@ if [[ ! -z "${SMART_STALE// }" ]]; then
|
||||
fi
|
||||
fi
|
||||
if [[ "$FORCE_DELETE_STALE" = true ]]; then
|
||||
ERR="could not delete stale local branches: $LOCAL_STALE"
|
||||
echo -n $LOCAL_STALE | tr " " "\0" | xargs -0 git branch -d 2> /dev/null
|
||||
else
|
||||
if [[ $STALE_BRANCHES = true ]]; then
|
||||
@@ -250,6 +300,7 @@ if [[ ! -z "${SMART_STALE// }" ]]; then
|
||||
fi
|
||||
if [[ "$FORCE_DELETE_STALE" = true ]]; then
|
||||
if [[ "$DELETE_ONLY_LOCAL" != true ]]; then
|
||||
ERR="could not delete stale remote branches: $REMOTE_STALE"
|
||||
echo -n $REMOTE_STALE | tr " " "\0" | xargs -0 git push $REMOTE --delete
|
||||
fi
|
||||
else
|
||||
@@ -274,7 +325,9 @@ fi
|
||||
|
||||
if [[ ! -z $(git rev-parse --verify --quiet "$CURRENT") ]]; then
|
||||
if [[ "$ROOT" != "$CURRENT" ]]; then
|
||||
ERR="could not check out $CURRENT branch"
|
||||
git checkout $CURRENT
|
||||
recover_root
|
||||
fi
|
||||
|
||||
if [ "$REBASE" = true ] && [ "$MERGE" = true ]; then
|
||||
@@ -282,10 +335,12 @@ if [[ ! -z $(git rev-parse --verify --quiet "$CURRENT") ]]; then
|
||||
else
|
||||
if [[ "$REMOTES" = true ]]; then
|
||||
if [[ "$REBASE" = true ]]; then
|
||||
ERR="could not rebase against $ROOT branch"
|
||||
git rebase $ROOT
|
||||
fi
|
||||
|
||||
if [[ "$MERGE" = true ]]; then
|
||||
ERR="could not merge $ROOT branch"
|
||||
git merge --no-edit $ROOT
|
||||
fi
|
||||
fi
|
||||
@@ -297,6 +352,7 @@ fi
|
||||
# Remove local tags that are missing on the remote
|
||||
|
||||
if [[ "$TAGS" = true ]]; then
|
||||
ERR="could not get remote tags"
|
||||
REMOTE_TAGS=$(git ls-remote --tags $REMOTE | cut -f 2)
|
||||
LOCAL_TAGS=$(git show-ref --tags | cut -d' ' -f 2)
|
||||
|
||||
@@ -312,10 +368,32 @@ fi
|
||||
|
||||
if [[ ! -z $(git stash list | grep $STASH_STAMP | cat) ]]; then
|
||||
if [[ "$APPLY_STASH" = true ]]; then
|
||||
ERR="could not apply stashed changes"
|
||||
git stash pop
|
||||
else
|
||||
say "Stashed changes present, apply with: git stash pop"
|
||||
fi
|
||||
fi
|
||||
|
||||
git gc --auto --prune=now
|
||||
# Clear stashed changes
|
||||
|
||||
if [[ "$CLEAR_STASH" = true ]]; then
|
||||
ERR="could not clear stashed changes"
|
||||
git stash clear
|
||||
fi
|
||||
|
||||
if ! git gc --auto --force; then
|
||||
ERR="git prune failed"
|
||||
git prune
|
||||
rm -rf "$TOP_LEVEL_DIRECTORY/.git/gc.log"
|
||||
fi
|
||||
|
||||
if [[ -d "$LAST_WORKING_DIRECTORY" ]]; then
|
||||
cd "$LAST_WORKING_DIRECTORY"
|
||||
else
|
||||
say "Previous working directory does not exist on the branch $ROOT"
|
||||
fi
|
||||
|
||||
recover_root
|
||||
|
||||
ERR=""
|
||||
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
." Text automatically generated by txt2man
|
||||
.TH git-fresh "January 21, 2019" "" ""
|
||||
.SH NAME
|
||||
\fBgit-fresh
|
||||
\fB
|
||||
.SH SYNOPSIS
|
||||
.nf
|
||||
.fam C
|
||||
\fBgit-fresh\fP [\fB-fmrtRWS\fP] [\fB-sl\fP] [\fIremote\fP] [\fIroot\fP]
|
||||
.fam T
|
||||
.fi
|
||||
.SH DESCRIPTION
|
||||
\fBgit-fresh\fP helps keep your Git repo fresh.
|
||||
.PP
|
||||
By default, \fBgit-fresh\fP will:
|
||||
.IP \(hy 3
|
||||
update local \fIroot\fP (master) to match \fIremote\fP \fIroot\fP
|
||||
.IP \(hy 3
|
||||
stash changes
|
||||
.IP \(hy 3
|
||||
prune \fIremote\fP branches
|
||||
.PP
|
||||
\fBgit-fresh\fP 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.
|
||||
.PP
|
||||
\fIremote\fP is origin by default. \fIroot\fP is master by default.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.B
|
||||
\fB-f\fP
|
||||
Delete stale local and \fIremote\fP branches
|
||||
.TP
|
||||
.B
|
||||
\fB-m\fP
|
||||
Merge \fIremote\fP \fIroot\fP into current branch
|
||||
.TP
|
||||
.B
|
||||
\fB-r\fP
|
||||
Rebase current branch against \fIremote\fP \fIroot\fP
|
||||
.TP
|
||||
.B
|
||||
\fB-t\fP
|
||||
Remove local tags that do not exist on \fIremote\fP
|
||||
.TP
|
||||
.B
|
||||
\fB-R\fP
|
||||
Reset local \fIroot\fP to \fIremote\fP \fIroot\fP
|
||||
.TP
|
||||
.B
|
||||
\fB-W\fP
|
||||
Wipe workspace clean
|
||||
.TP
|
||||
.B
|
||||
\fB-S\fP
|
||||
Clear all stash entries
|
||||
.TP
|
||||
.B
|
||||
\fB-s\fP
|
||||
Apply stashed changes after run
|
||||
.TP
|
||||
.B
|
||||
\fB-l\fP
|
||||
Only delete local stale branches
|
||||
.TP
|
||||
.B
|
||||
\fB-v\fP
|
||||
Print \fBgit-fresh\fP version and exit
|
||||
.SH BUGS
|
||||
Issues are tracked on GitHub: https://github.com/imsky/\fBgit-fresh\fP
|
||||
.SH AUTHOR
|
||||
Ivan Malopinsky - http://imsky.co
|
||||
+11
@@ -3,3 +3,14 @@ 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)
|
||||
|
||||
if [[ -e /usr/local/man ]]; then
|
||||
MAN_DIR=/usr/local/man
|
||||
elif [[ -e /usr/local/share/man ]]; then
|
||||
MAN_DIR=/usr/local/share/man
|
||||
fi
|
||||
|
||||
if [[ ! -z "$MAN_DIR" ]]; then
|
||||
mkdir -p "$MAN_DIR/man1"
|
||||
cp git-fresh.1 "$MAN_DIR/man1/" 2> /dev/null || echo Failed to install git-fresh man page
|
||||
fi
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "git-fresh",
|
||||
"version": "1.10.0",
|
||||
"version": "1.13.0",
|
||||
"description": "Utility to keep Git repositories fresh",
|
||||
"global": true,
|
||||
"repo": "imsky/git-fresh",
|
||||
|
||||
Reference in New Issue
Block a user