Compare commits

..

No commits in common. "main" and "0.5.0" have entirely different histories.
main ... 0.5.0

368 changed files with 1369 additions and 30670 deletions

View file

@ -1,30 +0,0 @@
root = true
[*]
indent_style = space
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[Makefile]
indent_style = tab
[*.patch]
trim_trailing_whitespace = false
[*.md]
trim_trailing_whitespace = false
[*.html]
trim_trailing_whitespace = false
[*.py]
indent_size = 4
[*.bats]
indent_style = tab
indent_size = 4
[tests/*.sh]
indent_style = tab
indent_size = 4

View file

@ -1,2 +0,0 @@
gool
Cant

View file

@ -1,12 +0,0 @@
<!--
Note
* Mark the PR as draft until it's ready to be reviewed.
* Please update the documentation to reflect the changes made in the PR.
* If the command is covered by tests under ./tests, please add/update tests for any changes unless you have a good reason.
* Make a new commit to resolve conversations instead of `push -f`.
* To resolve merge conflicts, merge from the `main` branch instead of rebasing over `main`.
* Please wait for the reviewer to mark a conversation as resolved.
-->

View file

@ -1,6 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

View file

@ -1,116 +0,0 @@
name: ci
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Check out code.
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: 'Get Changed Files'
id: 'files'
run: |
CHANGED_FILES=$(git diff --name-only "${BASE_SHA:-$BEFORE_SHA}...${HEAD_SHA:-$GITHUB_REF}")
echo "::notice::Changed files: $CHANGED_FILES"
{
echo "added_modified<<EOF"
echo "$CHANGED_FILES"
echo "EOF"
} >> "$GITHUB_OUTPUT"
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
BEFORE_SHA: "${{ github.event.before }}"
- name: 'Check Generated Manuals'
run: |
failed=0
while IFS= read -r source; do
case "$source" in
man/git-*.md)
stem=${source%.md}
for generated in "$stem.1" "$stem.html"; do
if ! grep -Fqx -- "$generated" <<< "$CHANGED_FILES"; then
echo "::error file=$source::$generated must be updated with $source"
failed=1
fi
done
;;
esac
done <<< "$CHANGED_FILES"
exit "$failed"
env:
CHANGED_FILES: "${{ steps.files.outputs.added_modified }}"
- uses: 'actions/setup-go@v7'
with:
go-version: '1.20'
- name: 'Install EditorConfig Lint'
run: go install 'github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@latest'
- name: 'Check EditorConfig Lint'
run: echo "$FILES" | xargs ~/go/bin/editorconfig-checker
env:
# NOTE: use env to pass the output in order to avoid possible injection attacks
FILES: "${{ steps.files.outputs.added_modified }}"
- name: checkstyle
run: ./scripts/checkstyle.py
- name: Shellcheck
run: shellcheck --severity=error bin/* ./*.sh
typo:
runs-on: ubuntu-latest
steps:
- name: Check out code.
uses: actions/checkout@v7
- name: Install codespell
run: python3 -m pip install codespell==2.4
- name: spell check
run: |
git grep --cached -l '' . | \
grep -v -e 'History\.md' -e 'AUTHORS' -e 'man/.*\.1' -e 'man/.*\.html' | \
xargs codespell --ignore-words=.github/.ignore_words
test-bats:
name: 'Test with Bats'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
submodules: recursive
- name: Setup Bats
id: setup-bats
uses: bats-core/bats-action@4.0.0
with:
bats-version: 'v1.8.1'
- name: Test with Bats
env:
BATS_LIB_PATH: ${{ steps.setup-bats.outputs.lib-path }}
TERM: xterm
run: bats ./tests
build:
strategy:
fail-fast: false
matrix:
platform:
- ubuntu-latest
- macos-latest
runs-on: ${{ matrix.platform }}
steps:
- name: Check out code.
uses: actions/checkout@v7
- name: Linux Install
if: matrix.platform == 'ubuntu-latest'
run: sudo apt-get install -y bsdmainutils
- name: Script
run: ./check_integrity.sh
- name: Brew release
if: matrix.platform == 'macos-latest'
run: |
mkdir ../release && git archive --format=tar.gz HEAD > ../release/git-extras-release.tar.gz
cd ../release
tar -xzf git-extras-release.tar.gz && make PREFIX=$(pwd) INSTALL_VIA=brew
./bin/git-extras update | grep "brew upgrade git-extras"

View file

@ -1,18 +0,0 @@
name: "Maintenance: Close Stale PRs"
on:
schedule:
- cron: "30 1 * * *"
permissions:
pull-requests: "write"
jobs:
stale:
runs-on: "ubuntu-latest"
if: github.repository_owner == 'tj'
steps:
- uses: "actions/stale@v11"
with:
close-pr-message: "This PR was closed because it has been stalled for 365 days with no activity. Feel free to make a new PR if you wish to continue"
days-before-pr-stale: 350
days-before-pr-close: 15

3
.gitignore vendored
View file

@ -1,3 +0,0 @@
.venv/
__pycache__/
coverage/

6
.gitmodules vendored
View file

@ -1,3 +1,3 @@
[submodule "vendor/bats-all"] [submodule "etc/gitignore"]
path = vendor/bats-all path = etc/gitignore
url = https://github.com/hyperupcall/bats-all url = git://github.com/github/gitignore.git

View file

@ -1,5 +0,0 @@
[pytest]
minversion = 7.4
addopts = -ra -q
testpaths = tests
faulthandler_timeout = 5

295
AUTHORS
View file

@ -1,296 +1,21 @@
git-extras is written and maintained by various contributors: git-extras is written and maintained by TJ Holowaychuk and
various contributors:
Development Lead
````````````````
Maintainers
```````````
- TJ Holowaychuk <tj@vision-media.ca> - TJ Holowaychuk <tj@vision-media.ca>
- Hemanth.HM <hemanth.hm@gmail.com>
- Nimit Kalra <me@nimit.io>
- Nicolai Skogheim <nicolai.skogheim@gmail.com>
- spacewander <spacewanderlzx@gmail.com>
- Edwin Kofler <edwin@kofler.dev>
- Leroy <vanpipy@gmail.com>
Patches and Suggestions Patches and Suggestions
``````````````````````` ```````````````````````
- Jonhnny Weslley
- Tobias Fendin
- LeeW
- timfeirg
- Niklas Schlimm
- nickl-
- CJ
- Paul Wise
- Damien Tardy-Panis
- Mark Eissler
- Yuriy VG
- Leila Muhtasib
- Richard Fearn
- Bill Wood
- Jesús Espino
- Rémy HUBSCHER
- go2null
- Andrew Janke
- David Baumgold
- Jan Schulz
- Kenneth Reitz - Kenneth Reitz
- Mark Pitman
- Peter Benjamin
- Günther Grill
- Luke Childs
- Sasha Khamkov
- equt
- oikarinen
- vyas
- Don Harper
- Pierre Ayoub
- Robin Winslow
- Ross Smith II
- Yi EungJun
- grindhold
- wyattscarpenter
- Aggelos Orfanakos - Aggelos Orfanakos
- Camille Reynders
- Carlos Prado
- Chris Hall
- Damian Krzeminski
- NANRI
- Alexander Krasnukhin
- Phally
- Rico Sta. Cruz
- Takuma Yamaguchi
- thomas menzel
- vr8ce
- Brian J Brennan
- Dominik Gedon
- Hogan Long
- Ivan Malopinsky
- John Bachir
- Jonathan "Duke" Leto - Jonathan "Duke" Leto
- Julio Napurí
- Justin Dugger
- Nils Winkler
- Philipp Klose
- Raphael Boidol
- Richard Russon
- Sam Bostock
- Vladimir Jimenez
- gisphm
- jacobherrington
- overengineer
- phigoro
- wooorm
- Alexis GRIMALDI
- Allan Odgaard
- Andre Cerqueira
- Austin Ziegler
- Beth Skurrie
- Brice Dutheil
- Curtis McEnroe
- Daniele Paolella
- David Rogers
- Devin Withers
- Domenico Rotiroti - Domenico Rotiroti
- Evan Grim
- Florian H
- Gert Van Gool
- Guillaume Seren
- Hozefa
- Igor Ostapenko
- Jean Jordaan
- Joshua Li
- Justin Guenther
- Kylie McClain
- Marc Harter
- Matiss
- Nate Jones
- Newell Zhu
- Patryk Małek
- Paul Schreiber
- Richard Littauer
- Tjaart van der Walt
- Tom Vincent
- Wil Moore III
- William Montgomery
- Ye Lin Aung
- luozexuan
- roxchgt
- soffolk
- 0xflotus
- Abdullah
- Adam Parkin
- Adriaan Zonnenberg
- Akim Demaille
- Alessandro Pagiaro
- Alex McHale
- Aloxaf
- Amir Tocker
- Ammar Najjar
- Amory Meltzer
- Andreas Duering
- Andrei Petcu
- Andrew Griffiths
- Andrew Marcinkevičius
- Andrew Starr-Bochicchio
- Andrew Sullivan Cant
- Andrey Elizarov
- Angel Aguilera
- Antoine Beaupré
- Aurélien Scoubeau
- Balazs Nadasdi
- Ben Parnell
- Beni Ben Zikry
- Brandon Zylstra
- Brian Goad
- Brian Murrell
- Bruno Sutic
- Caleb Maclennan
- Carl Casbolt
- Carlos Rodríguez
- Christophe Badoit
- Ciro Nunes
- CleanMachine1
- CodeByZach
- Craig MacGregor
- Dan Goodliffe
- Dan Jackson
- Dan Kilman
- Daniel Bruder
- Daniel Lublin
- Daniel Schildt
- Dave James Miller
- David Hartmann
- Dominic Barnes
- Dung Quang
- Edward Betts
- Eli Schwartz
- Emil Kjelsrud
- Erik Skultety
- François M
- George Crabtree
- Gerrit-K
- Greg Allen
- Guilhem Saurel
- Guillermo Rauch
- Gunnlaugur Thor Briem
- Hasse Ramlev Hansen
- Heiko Becker
- Hugo Ruiz-Mireles
- Isaac Mungai
- J.C. Yamokoski
- Jack O. Wasey
- James Manning
- James Zhu
- Jan Krueger
- Jared Baur
- Jarod Stewart
- Jason Young - Jason Young
- Jens K. Mueller
- Jeremy Lin
- Jesse Sipprell
- Jianjin Fan
- Jobin Kurian
- Johannes Ewald
- John Evans
- John Hoffmann
- Jon Ander Peñalba
- Josh McKinney
- Joshua Appelman
- José María Gutiérrez @TheTechOddBug
- Katrin Leinweber
- Kevin Woo
- Konstantin Schukraft
- Kurban Mallachiev
- Leandro López
- Lone
- Luis Miguel Hernanz
- Maarten Winter
- Marco Glasbergen
- Matan Rosenberg
- Mathieu D. (MatToufoutu)
- Matt
- Matt Colyer
- Matt Headley
- Matthew Avant
- Mattias Andersson
- Michael Komitee
- Michael Matuzak
- Michele Bologna
- Montak Oleg
- Moritz Grauel
- Nate Eagleson
- Nate Fischer
- Nathan Rajlich
- Nick Campbell - Nick Campbell
- Nick Payne - Jonhnny Weslley
- Nicolas Kosinski - Alex McHale
- Niklas Fiekas - Gert Van Gool
- Oliver Kopp - Devin Withers
- Orestis
- Peter Bittner
- Piotr Górski
- Prayag Verma
- Przemek Kitszel
- R. Martinho Fernandes
- Raphael Fleischlin
- Rasmus Wriedt Larsen
- René
- Revisor
- Ricardo Fernández Serrata
- Riceball LEE
- Rob Kennedy
- Robin von Bülow
- Russ
- Ryan Bohn
- Sachin Gupta
- Sam Thursfield
- Sandro
- Sascha Andres
- SchleimKeim
- Sean Molenaar
- Sebastian Gniazdowski
- Sebastián Mancilla
- Simon Tate
- Stefan VanBuren
- Stephen Mathieson
- Steve Mao
- Stu Feldt
- SukkaW
- Tim Preston
- Timothy Hwang
- Tin Lai
- Todd Wolfson
- Tom Andrade
- Tom Ashworth
- Tony
- TweeKane
- Valérian Galliat
- Vitaly Chikunov
- Wei Lee
- Wei Wu
- Wen Sun
- Wiktor Żurawik
- Xavier Krantz
- Xiaopei Li
- Zeeshan Ahmed
- ax1036
- creynders
- dead-horse
- eszabpt
- fengkx
- johnpyp
- jykntr
- kang
- meza
- neydroid
- nulltask
- phanium
- sgleizes
- tfendin
- tiemonl
- zentarul
- zeroDivisible
- zhiyanfoo
- Áron Hoffmann
- Étienne BERSAC
- ☃ pitr
- 单元源

View file

@ -1,34 +0,0 @@
# Contributing
Thanks for contributing! Please read this document before you make a PR.
## Supported Platforms
Any changes must support the following platforms:
- macOS
- Linux
- OpenBSD (You may need to browse their man page)
Your change must also be compatible with the dependency constraints that we specify in [Installation](./Installation.md). If you aren't sure if a feature is compatible, check the manual or release notes. For example, the Bash changelog is [here](https://git.savannah.gnu.org/cgit/bash.git/tree/NEWS?h=devel).
If you aren't able to test your new command on a platform, make that clear in your PR; someone else may be able to test it on their system.
## Adding a New Command
Let's say you wish to add a new command. Assuming your new command is named `foo`:
1. Write a bash script under `./bin` called `git-foo`. The script should be started with `#!/usr/bin/env bash`.
2. Read `./man/Readme.md` and write documentation for `git-foo`.
3. Don't forget to introduce it in `Commands.md`.
4. Update `./etc/git-extras-completion.zsh`. Just follow existing code.
5. (Optional) Update `./etc/bash_completion.sh`.
6. (Optional) Update `./etc/git-extras.fish`.
7. (Optional) Add a test under `./tests`.
8. Run `./check_integrity.sh foo` to check if all done. (You may also run `./check_integrity.sh` to check all commands.)
You are welcome to open up an issue to discuss new commands or features before opening a pull request.
## Submitting a pull request
Please follow the suggestion in `./.github/PULL_REQUEST_TEMPLATE.md`.

File diff suppressed because it is too large Load diff

1590
History.md

File diff suppressed because it is too large Load diff

View file

@ -1,196 +0,0 @@
# Installation
## Dependencies
Some commands require extra dependencies which are unavailable in some platforms.
You may need to install them manually. They are:
* bash 4.0+
* Git 2.17+
* `column`
* `awk`
* `find`
* `tput`
* cURL (only required for `git-fork`, `git-ignore-io`, `git-pull-request`)
* `ps` (only required for `git-changelog`)
* `rsync` (only required for `git-rscp`, `git-scp`)
* `xargs` (only required for `git-delete-merged-branches`, `git-force-clone`, `git-sed`, `git-scp`)
If `bash --version` shows a lower version, you need to update it.
For example, the default bash in Mac is usually too old and you may need to update it via `brew install bash`.
## Installing with a package manager
Note that only the Homebrew package is maintained by the git-extras developers directly.
Other packages are maintained by the distribution's packagers or third-party volunteers.
[![Packaging status](https://repology.org/badge/vertical-allrepos/git-extras.svg)](https://repology.org/project/git-extras/versions)
### Debian
```bash
$ sudo $apt_pref update
$ sudo $apt_pref install git-extras
```
### Fedora
```bash
$ sudo dnf install git-extras
```
### openSUSE
Substitute your openSUSE version in the command below (in this case we are considering openSUSE Leap 15.6):
```bash
$ sudo zypper ar https://download.opensuse.org/repositories/devel:/tools:/scm/15.6/devel:tools:scm.repo
```
and install it:
```bash
$ sudo zypper in -y git-extras
```
### RHEL/CentOS (requires [EPEL](https://fedoraproject.org/wiki/EPEL))
```bash
$ sudo yum install git-extras
```
### Ubuntu
```bash
$ sudo apt-get install git-extras
```
### Nix/NixOS
```bash
$ nix-env -i git-extras
```
### CRUX
[Abdullah](https://github.com/AWAN) has written a [Pkgfile](https://abdullah.today/ports/git-extras/Pkgfile) for his beloved [distro](https://crux.nu).
### Homebrew
```bash
$ brew install git-extras
```
Installing from Homebrew will not give you the option omit certain `git-extras` if they conflict with existing git aliases. To have this option, build from source.
### Arch Linux
* [git-extras](https://aur.archlinux.org/packages/git-extras/)
* [git-extras-git](https://aur.archlinux.org/packages/git-extras-git/)
### Windows
First, please install `Git for Windows 2.x` from 'https://github.com/git-for-windows/git/releases'.
Second, clone the `git-extras` repo into any folder you like.
```bash
git clone https://github.com/tj/git-extras.git
# checkout the latest tag (optional)
git checkout $(git describe --tags $(git rev-list --tags --max-count=1))
```
After that, execute the `install.cmd` in the command prompt. If you installed
git as admin, you need to run this prompt as admin, too. Per default, it finds
a `Git for Windows 2.x` if it's in the path (first path in `where git.exe` wins)
or installed in the default location `%ProgramFiles%\Git`. The fallback is
`C:\SCM\PortableGit`. If you didn't install git into one of these dirs, you have
to supply the path for the install location, e.g. if git is installed
in `c:\git`:
```batch
install.cmd "C:\git"
```
Last, to use `git summary` and `git ignore-io`, you need to copy
`column.exe` from a [msys2][1] installation from `folder-your-msys2-installed/usr/bin`
to `folder-your-git-installed/usr/bin` or wait for git 2.7.1, which will include column.exe.
### FreeBSD
```bash
$ pkg install git-extras
```
### BSD
Use the instructions to build from source below. Make sure you are using `gmake` (GNU `make`) instead of `make`.
## Building from source
Read [Dependencies](#dependencies) and ensure they are installed.
Obtain the git-extras source by cloning [its GitHub repo](https://github.com/tj/git-extras.git) or downloading a tarball of a [release](https://github.com/tj/git-extras/releases). Then install it by doing `make install` from the source tree.
```bash
$ git clone https://github.com/tj/git-extras.git
$ cd git-extras
# checkout the latest tag
$ git checkout $(git describe --tags $(git rev-list --tags --max-count=1))
$ [sudo] make install
```
By default, git-extras is installed under `/usr/local`. To install it at an
alternate location, specify a `PREFIX` when calling `make`.
```bash
# Non-root users can install under their home directory
make install PREFIX=$HOME/.local
# For third-party software kept under /opt
make install PREFIX=/opt
```
If you want to relocate the bulk of the installation but still have configuration
files installed to the system `/etc` dir or other alternate location, you can
specify `SYSCONFDIR` in addition to `PREFIX`.
```sh
$ sudo make install PREFIX=/usr/local SYSCONFDIR=/etc
```
See the Makefile for details on advanced configuration options.
## Installing from network
One-liner:
```bash
curl -sSL https://raw.githubusercontent.com/tj/git-extras/main/install.sh | sudo bash /dev/stdin
```
## Installing as Zsh plugin
[ZInit](https://github.com/zdharma/zinit) can install git-extras by using:
```zsh
zinit ice as"program" pick"$ZPFX/bin/git-*" src"etc/git-extras-completion.zsh" make"PREFIX=$ZPFX"
zinit light tj/git-extras
# or with the for syntax + async load
zinit lucid wait'0a' for \
as"program" pick"$ZPFX/bin/git-*" src"etc/git-extras-completion.zsh" make"PREFIX=$ZPFX" tj/git-extras
```
`$ZPFX` is `~/.zinit/polaris` by default. Use `zinit update tj/git-extras` to update.
This method installs in `$HOME`, so you don't need to ask administrator to install package.
## Updating
If you installed git-extras with a package manager, use that package manager's tools to update it.
If you installed git-extras from source, you can run `git-extras update` to update it to the latest release. Be aware that this may lose any configuration options you specified during the original installation.
Enjoy `git-extras`!
[1]: https://sourceforge.net/projects/msys2/

View file

@ -1,6 +1,6 @@
(The MIT License) (The MIT License)
Copyright (c) 2010 TJ Holowaychuk <tj@vision-media.ca> and Contributors Copyright (c) 2010-2011 TJ Holowaychuk <tj@vision-media.ca>
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the a copy of this software and associated documentation files (the
@ -19,4 +19,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

131
Makefile
View file

@ -1,116 +1,25 @@
PREFIX ?= /usr/local PREFIX ?= /usr/local
BINPREFIX ?= "$(PREFIX)/bin" MANPREFIX ?= "$(PREFIX)/share/man/man1"
SYSCONFDIR ?= $(PREFIX)/etc
SHELL := bash
OS = $(shell uname)
ifeq ($(OS), FreeBSD)
MANPREFIX ?= "$(PREFIX)/man/man1"
ifeq ($(MANPREFIX), /usr/local)
MANPREFIX = "/usr/local/man/man1"
endif
else
MANPREFIX ?= "$(PREFIX)/share/man/man1"
endif
ifeq ($(OS), Darwin)
COMPL_DIR ?= "$(DESTDIR)$(SYSCONFDIR)/bash_completion.d"
else ifeq ($(OS), FreeBSD)
COMPL_DIR ?= "$(DESTDIR)$(SYSCONFDIR)/bash_completion.d"
else
COMPL_DIR ?= "$(DESTDIR)$(SYSCONFDIR)/bash-completion/completions"
endif
BINS = $(wildcard bin/git-*) BINS = $(wildcard bin/git-*)
MANS = $(wildcard man/git-*.md) MANS = $(wildcard man/git-*.md)
MAN_BINS = $(filter-out man/git-extras.md, $(MANS))
MAN_HTML = $(MANS:.md=.html) MAN_HTML = $(MANS:.md=.html)
MAN_PAGES = $(MANS:.md=.1) MAN_PAGES = $(MANS:.md=.1)
CODE_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
INSTALL_VIA ?= source
# Libraries used by all commands
LIB = "helper/reset-env" "helper/git-extra-utility"
COMMANDS = $(subst bin/, , $(BINS)) install:
@mkdir -p $(MANPREFIX)
default: install @mkdir -p $(PREFIX)/bin
@echo "... installing bins to $(PREFIX)/bin"
@echo "... installing man pages to $(MANPREFIX)"
@$(foreach BIN, $(BINS), \
echo "... installing `basename $(BIN)`"; \
cp -f $(BIN) $(PREFIX)/$(BIN); \
)
cp -f man/git-*.1 $(MANPREFIX)
docs: $(MAN_HTML) $(MAN_PAGES) docs: $(MAN_HTML) $(MAN_PAGES)
check: man/%.html: man/%.md
@echo "Check dependencies before installation"
@./check_dependencies.sh
@echo
install: check
@if [ "$(INSTALL_VIA)" = brew ]; then \
git apply brew-release.patch || { echo "Can't apply brew release patch"; exit 1; } \
fi
mkdir -p $(DESTDIR)$(MANPREFIX)
mkdir -p $(DESTDIR)$(BINPREFIX)
@echo '... installing bins to '$(DESTDIR)$(BINPREFIX)
@echo '... installing man pages to '$(DESTDIR)$(MANPREFIX)
$(eval TEMPFILE := $(shell mktemp -q $${TMPDIR:-/tmp}/git-extras.XXXXXX 2>/dev/null || mktemp -q))
@# chmod from rw-------(default) to rwxrwxr-x, so that users can exec the scripts
@chmod 775 $(TEMPFILE)
$(eval EXISTED_ALIASES := $(shell \
git config --get-regexp 'alias\..*' | awk '{print "git-" substr($$1, 7)}'))
@$(foreach COMMAND, $(COMMANDS), \
should_install='yes'; \
if [ ! -z "$(filter $(COMMAND), $(EXISTED_ALIASES))" ] && [ "$$SKIP_CONFLICT_CHECK" != yes ]; then \
read -p "$(COMMAND) conflicts with an alias, still install it? [y/n]: " answer; \
if [ "$$answer" = 'n' ] || [ "$$answer" = 'N' ]; then \
should_install="no"; \
fi; \
fi; \
if [ "$$should_install" = 'yes' ]; then \
echo "... installing $(COMMAND)"; \
head -1 bin/$(COMMAND) > $(TEMPFILE); \
cat $(LIB) >> $(TEMPFILE); \
if ! grep "$(COMMAND)" not_need_git_repo >/dev/null; then \
cat ./helper/is-git-repo >> $(TEMPFILE); \
fi; \
if grep "$(COMMAND)" need_git_commit >/dev/null; then \
cat ./helper/has-git-commit >> $(TEMPFILE); \
fi; \
tail -n +2 bin/$(COMMAND) >> $(TEMPFILE); \
cp -f $(TEMPFILE) $(DESTDIR)$(BINPREFIX)/$(COMMAND); \
fi; \
)
@if [ -z "$(wildcard man/git-*.1)" ]; then \
echo "WARNING: man pages not created, use 'make docs' (which requires 'ronn' ruby lib)"; \
else \
cp -f man/git-*.1 $(DESTDIR)$(MANPREFIX); \
echo "cp -f man/git-*.1 $(DESTDIR)$(MANPREFIX)"; \
fi
@mkdir -p $(COMPL_DIR)
cp -f etc/bash_completion.sh $(COMPL_DIR)/git-extras
@echo ""
@echo "If you are a zsh user, you may want to 'source $(CODE_DIR)etc/git-extras-completion.zsh'" \
"and put this line into ~/.zshrc to enable zsh completion"
@echo "If you are a fish user, you may want to copy or link '$(CODE_DIR)etc/git-extras.fish'" \
"to '~/.config/fish/completions/'"
man/index.txt: $(MANS)
echo '# manuals' > $@.tmp
for file in $(sort $^) ; do \
extra=$${file%.md} ; \
extra=$${extra#man/} ; \
echo "$$extra(1) $$extra" >> $@.tmp ; \
done
mv -f $@.tmp $@
man/git-extras.md: $(MAN_BINS)
ln=$$(awk '/## COMMANDS/{print NR};' $@) ; \
awk "NR <= $$ln+1" $@ > $@.tmp
for file in $(sort $^) ; do \
head -n1 $$file | \
sed 's/^/ - **/;s/ -- /** /' >> $@.tmp ; \
done
ln=$$(awk '/## AUTHOR/{print NR};' $@) ; \
awk "NR >= $$ln-1" $@ >> $@.tmp
mv -f $@.tmp $@
man/%.html: man/%.md man/index.txt
ronn \ ronn \
--manual "Git Extras" \ --manual "Git Extras" \
--html \ --html \
@ -125,14 +34,13 @@ man/%.1: man/%.md
uninstall: uninstall:
@$(foreach BIN, $(BINS), \ @$(foreach BIN, $(BINS), \
echo "... uninstalling $(DESTDIR)$(BINPREFIX)/$(notdir $(BIN))"; \ echo "... uninstalling $(PREFIX)/$(BIN)"; \
rm -f $(DESTDIR)$(BINPREFIX)/$(notdir $(BIN)); \ rm -f $(PREFIX)/$(BIN); \
) )
@$(foreach MAN, $(MAN_PAGES), \ @$(foreach MAN, $(MAN_PAGES), \
echo "... uninstalling $(DESTDIR)$(MANPREFIX)/$(notdir $(MAN))"; \ echo "... uninstalling $(MANPREFIX)/$(MAN)"; \
rm -f $(DESTDIR)$(MANPREFIX)/$(notdir $(MAN)); \ rm -f $(MANPREFIX)/$(MAN); \
) )
rm -f $(COMPL_DIR)/git-extras
clean: docclean clean: docclean
@ -140,7 +48,4 @@ docclean:
rm -f man/*.1 rm -f man/*.1
rm -f man/*.html rm -f man/*.html
test: .PHONY: docs clean docclean install uninstall
bats ./tests
.PHONY: default docs check install uninstall clean docclean test

343
Readme.md
View file

@ -1,34 +1,341 @@
# Git Extras # Git Extras
Little git extras. Little git extras.
## Screencasts
Just getting started? Check out these screencasts:
* [introduction](https://vimeo.com/45506445) ([archive.org link](https://web.archive.org/web/20230219181235id_/vod-progressive.akamaized.net/exp=1676834145~acl=%2Fvimeo-prod-skyfire-std-us%2F01%2F4101%2F1%2F45506445%2F107111328.mp4~hmac=065b68f23c4b6a222463097b36d1c346995a9559baa9b819972da95550018471/vimeo-prod-skyfire-std-us/01/4101/1/45506445/107111328.mp4)) -- covers `git-ignore`, `git-setup`, `git-changelog`, `git-release`, `git-effort` and more
## Installation ## Installation
See the [Installation](Installation.md) page. Clone / Tarball:
$ make install
One-liner:
$ curl https://github.com/visionmedia/git-extras/raw/master/bin/git-extras | INSTALL=y sh
Brew (buggy):
$ brew install git-extras
## Commands ## Commands
Go to the [Commands](Commands.md) page for basic usage and examples. - git extras
- git summary
- git changelog
- git commits-since
- git pull-request
- git count
- git create-branch
- git delete-branch
- git delete-submodule
- git delete-tag
- git fresh-branch
- git graft
- git alias
- git ignore
- git release
- git contrib
- git repl
- git undo
- git gh-pages
- git setup
- git touch
- git feature
- git refactor
- git bug
__GIT utilities__ -- repo summary, repl, changelog population, author commit percentages and more ## extras
## Contributing The main `git-extras` command, outputting the current `--version`, or listing the commands available via `--help`, or `updating` to the latest release.
Interested in contributing? Awesome! For example if you wish to update to the latest version of git-extras simply execute:
Please read [Contributing](CONTRIBUTING.md) before you make a PR, thanks! $ git extras update
## The change of the default branch ## gh-pages
As of Git Extras 6.4 the assumed default branch name changed from `master` to `main`. Sets up the `gh-pages` branch.
This affects the Git Extras commands `git archive-file`, `git delete-merged-branches`, `git delta`, `git pull-request`, `git show-merged-branches`, `git show-unmerged-branches`, and `git squash`.
To change the default branch name to `master`: change either the configuration `git-extras.default-branch` or `init.defaultBranch` to `master`; the former takes precedence. ## git-[feature|refactor|bug] [finish] &lt;name&gt;
For example, `git config git-extras.default-branch master`. Creates the given feature, refactor, or bug branch `name`:
$ git feature dependencies
Later we can check it out by issuing the same command:
$ git checkout master
$ git feature dependencies
Finally when finished, we can add `finish`, merging it into the current branch:
$ git checkout master
$ git feature finish dependencies
`feature` can be replaced with `bug`, or `refactor`.
## git-pull-request &lt;number&gt;
Pulls the given request `number` from github, and applies it
via `git am`. The git config `github.user` must be present, and
the dirname must map to `https://github.com/<github.user>/<dirname>`
git pull-request 604
## git-contrib
Output a user's contributions to a project, based on the author name:
$ git contrib visionmedia
visionmedia (18):
Export STATUS_CODES
Replaced several Array.prototype.slice.call() calls with Array.prototype.unshift.call()
Moved help msg to node-repl
Added multiple arg support for sys.puts(), print(), etc.
Fix stack output on socket error
...
## git-summary
Outputs a repo summary:
$ git summary
project: express
commits: 1893
files : 111
authors:
1285 visionmedia
478 Tj Holowaychuk
48 Aaron Heckmann
34 csausdev
26 ciaranj
6 Guillermo Rauch
3 Nick Poulden
2 Brian McKinney
2 Benny Wong
1 Justin Lilly
1 James Herdman
1 Adam Sanderson
1 Viktor Kelemen
1 Gregory Ritter
1 Greg Ritter
1 ewoudj
1 isaacs
1 Matt Colyer
This command can also take a commitish, and will print a summary for the range
of commits included in the commitish:
$ git summary v42..
## git-repl
GIT read-eval-print-loop:
$ git repl
git> ls-files
History.md
Makefile
Readme.md
bin/git-changelog
bin/git-count
bin/git-delete-branch
bin/git-delete-tag
bin/git-ignore
bin/git-release
git> quit
## git-commits-since [date]
List of commits since the given _date_, defaulting to "last week":
$ git commits-since
... changes since last week
TJ Holowaychuk - Fixed readme
TJ Holowaychuk - Added git-repl
TJ Holowaychuk - Added git-delete-tag
TJ Holowaychuk - Added git-delete-branch
$ git commits-since yesterday
... changes since yesterday
TJ Holowaychuk - Fixed readme
## git-count
Output commit total:
$ git count
total 1844
Output verbose commit count details:
$ git count --all
visionmedia (1285)
Tj Holowaychuk (430)
Aaron Heckmann (48)
csausdev (34)
ciaranj (26)
Guillermo Rauch (6)
Brian McKinney (2)
Nick Poulden (2)
Benny Wong (2)
Justin Lilly (1)
isaacs (1)
Adam Sanderson (1)
Viktor Kelemen (1)
Gregory Ritter (1)
Greg Ritter (1)
ewoudj (1)
James Herdman (1)
Matt Colyer (1)
total 1844
## git-release
Release commit with the given &lt;tag&gt;.
$ git release 0.1.0
Does the following:
- Commits changes (to changelog etc) with message "Release &lt;tag&gt;"
- Tags with the given &lt;tag&gt;
- Pushes the branch / tags
## git-alias
Define, search and show aliases.
Defining a new alias:
$ git alias last "cat-file commit HEAD"
Providing only one argument, `git-alias` searchs for aliases matching the given value:
$ git alias ^la
last = cat-file commit HEAD
`git-alias` will show all aliases if no argument is given:
$ git alias
s = status
amend = commit --amend
rank = shortlog -sn --no-merges
whatis = show -s --pretty='tformat:%h (%s, %ad)' --date=short
whois = !sh -c 'git log -i -1 --pretty="format:%an <%ae>
## git-ignore
To lazy to open up _.gitignore_? me too! simply pass some patterns:
$ git ignore build "*.o" "*.log"
... added 'build'
... added '*.o'
... added '*.log'
Running `git-ignore` without a pattern will display the current patterns:
$ git ignore
build
*.o
*.log
## git-create-branch &lt;name&gt;
Creates local and remote branch _name_.
$ git create-branch development
Adding patterns from an existing template:
$ git ignore -t rails
## git-delete-branch &lt;name&gt;
Deletes local and remote branch _name_.
$ git delete-branch integration
## git-delete-submodule &lt;name&gt;
Deletes submodule _name_.
$ git delete-submodule lib/foo
## git-delete-tag &lt;name&gt;
Deletes local and remote tag _name_.
$ git delete-tag 0.0.1
## git-fresh-branch &lt;name&gt;
Creates empty local branch _name_.
$ git fresh-branch docs
## git-graft &lt;src-branch&gt; &lt;dest-branch&gt;
Merge commits from _src-branch_ into _dest-branch_ which defaults to _master_.
$ git graft new_feature dev
$ git graft new_feature
## git-changelog
Populates the file named matching _change|history -i_ with the commits
since the previous tag or since the project began when no tags are present. Opens the changelog in **$EDITOR** when set.
$ git changelog && cat History.md
n.n.n / 2010-08-05
==================
* Docs for git-ignore. Closes #3
* Merge branch 'ignore'
* Added git-ignore
* Fixed <tag> in docs
* Install docs
* Merge branch 'release'
* Added git-release
* Passing args to git shortlog
* Added --all support to git-count
* Initial commit
Listing commits:
$ git changelog --list
* Docs for git-ignore. Closes #3
* Merge branch 'ignore'
* Added git-ignore
* Fixed <tag> in docs
* Install docs
* Merge branch 'release'
* Added git-release
* Passing args to git shortlog
* Added --all support to git-count
* Initial commit
## git-undo
Removes the latest commit
git undo
Remove the latest 3 commits:
git undo 3
## git-setup [dir]
Set up a git repository if one doesn't exist, add all files and make an initial commit. _dir_ defaults to the current working directory.
## git-touch [filename]
Call `touch` on the given file and add it to the current index. Used one-step creating new files.

View file

@ -1,45 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# git rev-parse emits an error if not in a git repo so only need to bail out
gitdir="$(git rev-parse --git-dir)" || exit
discover_op() {
local op
for op in cherry_pick merge rebase revert ; do
if [ -f "${gitdir}/${op^^}_HEAD" ]; then
echo "${op/_/-}"
fi
done
}
validate_op() {
local op="$1"
if [ -z "$op" ]; then
echo "No active operation found" >&2
exit 1
fi
if [[ "$(echo "$op" | wc -l)" -gt 1 ]]; then
echo "Multiple active operations found:" >&2
for o in $op; do
echo " - $o: HEAD: $(cat "${gitdir}/${o}_HEAD")" >&2
done
exit 1
fi
}
discover_action() {
local action=${1/git-/}
if [ "$action" != "abort" ] && [ "$action" != "continue" ]; then
echo "Invalid action: $1" >&2
exit 1
fi
echo "$action"
}
action=$(discover_action "$(basename "$0")")
op=$(discover_op)
validate_op "$op"
git "$op" "--$action"

View file

@ -1,45 +1,7 @@
#!/usr/bin/env bash #!/bin/sh
options=""
usage() {
cat <<HERE
usage: git alias [options] # list all aliases
or: git alias [options] <search-pattern> # show aliases matching pattern
or: git alias [options] <alias-name> <command> # alias a command
options:
--global Show or create alias in the system config
--local Show or create alias in the repository config
HERE
}
if [[ "$1" == "--local" || "$1" == "--global" ]]; then
options=$1
shift
fi
case $# in case $# in
0) 0) git config --get-regexp 'alias.*' | colrm 1 6 | sed 's/[ ]/ = /' | sort ;;
if [[ -z "$options" ]]; then 1) git alias | grep -e "$1" ;;
git config --get-regexp 'alias.*' | sed 's/^alias\.//' | sed 's/[ ]/ = /' | sort *) git config --global "alias.$1" "$2" ;;
else
git config "$options" --get-regexp 'alias.*' | sed 's/^alias\.//' | sed 's/[ ]/ = /' | sort
fi
;;
1)
if [[ -z "$options" ]]; then
git config --get-regexp 'alias.*' | sed 's/^alias\.//' | sed 's/[ ]/ = /' | sort | grep -e "$1"
else
git config "$options" --get-regexp 'alias.*' | sed 's/^alias\.//' | sed 's/[ ]/ = /' | sort | grep -e "$1"
fi
;;
2)
if [[ -z "$options" ]]; then
git config alias."$1" "$2"
else
git config "$options" alias."$1" "$2"
fi
;;
*) >&2 echo "error: too many arguments." && usage && exit 1 ;;
esac esac

View file

@ -1,37 +0,0 @@
#!/usr/bin/env bash
# extract current branch name
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
# get name of the most top folder of current directory, used for the
# output filename
ARCHIVE_NAME=$(basename "$(git rev-parse --show-toplevel)")
if [[ $BRANCH = tags* ]]; then
BRANCH=$(git describe)
echo Building for tag "\"$BRANCH\""
FILENAME=$ARCHIVE_NAME.$BRANCH.zip
else
echo Building archive on branch "\"$BRANCH\""
# get a version string, so archives will not be overwritten when creating
# many of them
VERSION=$(git describe --always --long)
# if not on master append branch name into the filename
if [ "$BRANCH" = "$(git_extra_default_branch)" ]; then
FILENAME=$ARCHIVE_NAME.$VERSION.zip
else
FILENAME=$ARCHIVE_NAME.$VERSION.$BRANCH.zip
fi
fi
# rename invalid chars for the file path
FILENAME=${FILENAME//\//-}
FILENAME=${FILENAME//\\/-}
# combine path and filename
OUTPUT=$PWD/$FILENAME
# building archive
git archive --format zip --output "$OUTPUT" "$BRANCH"
# also display size of the resulting file
echo Saved to \""$FILENAME"\" \("$(du -h "$OUTPUT" | cut -f1)"\)

View file

@ -1,54 +0,0 @@
#!/usr/bin/env bash
LIST=false
NO_EMAIL=false
FILE=""
while [[ $# -gt 0 ]]; do
case $1 in
-l|--list )
LIST=true
shift
;;
--no-email )
NO_EMAIL=true
shift
;;
* )
break
esac
done
if ! $LIST; then
FILE=$1
if [ -z "$FILE" ]; then
FILE=$(find . -mindepth 1 -maxdepth 1 \( -iname '*authors*' -o -iname '*contributors*' \) | head -n1)
if [ -z "$FILE" ]; then
FILE='AUTHORS'
fi
fi
fi
#
# list authors sorted by number of commits (descending).
#
authors() {
if $NO_EMAIL; then
# email will be used to uniq authors.
git shortlog HEAD -sne | awk '{$1=""; sub(" ", ""); print}' | awk -F'<' '!x[$1]++' | awk -F'<' '!x[$2]++' \
| awk -F'<' '{gsub(/ +$/, "", $1); print $1}'
else
git shortlog HEAD -sne | awk '{$1=""; sub(" ", ""); print}' | awk -F'<' '!x[$1]++' | awk -F'<' '!x[$2]++'
fi
}
#
# authors.
#
if $LIST; then
authors
else
authors >> "$FILE"
fi

View file

@ -1,92 +0,0 @@
#!/usr/bin/env bash
remote=${1:-""}
branch=""
filename=${2:-""}
line1=${3:-""}
line2=${4:-""}
# get remote name
if [[ $remote == "" ]]; then
branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null)"
remote=$(git config branch."${branch}".remote || echo "origin")
fi
if [[ $remote == "" ]]; then
echo "Remote not found"
exit 1
fi
remote_url=$(git remote get-url "$remote") || exit $?
if [[ $remote_url = git@* ]]; then
url=$(echo "$remote_url" | sed -E -e 's/:/\//' -e 's/\.git$//' -e 's/.*@(.*)/http:\/\/\1/')
elif [[ $remote_url = http* ]]; then
url=${remote_url%.git}
fi
# construct urls
commit_hash=$(git rev-parse HEAD 2>/dev/null)
commit_or_branch=${commit_hash:-${branch}}
if [[ $remote_url =~ gitlab ]]; then
# construct gitlab urls
# https://gitlab.com/<user_or_group>/<repo>/-/blob/<commit_or_branch>/<filename>#L<line1>-<line2>
if [[ -n ${filename} ]]; then
url="${url}/-/blob/${commit_or_branch}/${filename}"
if [[ -n "${line1}" ]]; then
url="${url}#L${line1}"
if [[ -n "${line2}" ]]; then
url="${url}-${line2}"
fi
fi
fi
elif [[ $remote_url =~ github ]]; then
# construct github urls
# https://github.com/<user_or_org>/<repo>/blob/<commit_or_branch>/<filename>#L<line1>-L<line2>
if [[ -n "${filename}" ]]; then
url="${url}/blob/${commit_or_branch}/${filename}"
if [[ -n "${line1}" ]]; then
url="${url}#L${line1}"
if [[ -n "${line2}" ]]; then
url="${url}-L${line2}"
fi
fi
fi
elif [[ $remote_url =~ bitbucket ]]; then
# construct bitbucket urls
# https://bitbucket.org/<user_or_org>/<repo>/src/<commit_or_branch>/<filename>#lines-<line1>:<line2>
if [[ -n ${filename} ]]; then
url=${url}/src/${commit_or_branch}/${filename}
if [[ -n "${line1}" ]]; then
url="${url}#lines-${line1}"
if [[ -n "${line2}" ]]; then
url="${url}:${line2}"
fi
fi
fi
fi
# open url
case "$OSTYPE" in
darwin*)
# MacOS
open "$url"
;;
msys)
# Git-Bash on Windows
start "$url"
;;
linux*)
# Handle WSL on Windows
if uname -a | grep -i -q Microsoft && command -v powershell.exe; then
powershell.exe -NoProfile start "$url"
else
xdg-open "$url"
fi
;;
*)
# fall back to xdg-open for BSDs, etc.
xdg-open "$url"
;;
esac

View file

@ -1,60 +0,0 @@
#!/usr/bin/env bash
set -e -o pipefail
if [[ $1 == "" ]]
then
branch=$(git rev-parse --abbrev-ref HEAD &> /dev/null)
remote=$(git config branch."${branch}".remote || echo "origin")
else
remote=$1
fi
if [[ -z $remote ]]
then
echo "Remote not found"
exit 1
fi
remote_url=$(git remote get-url "${remote}") || exit $?
if [[ $remote_url = git@* ]]
then
url=$(echo "${remote_url}" | sed -E -e 's/:/\//' -e 's/\.git$//' -e 's/.*@(.*)/http:\/\/\1/')
elif [[ $remote_url = http* ]]
then
url=${remote_url%.git}
fi
if [[ $url =~ github ]]
then
ci_url=${url}/actions
elif [[ $url =~ gitlab ]]
then
ci_url=${url}/-/pipelines
elif [[ $url =~ bitbucket ]]
then
ci_url=${url}/addon/pipelines/home
fi
case "$OSTYPE" in
darwin*)
# MacOS
open "${ci_url}"
;;
msys)
# Git-Bash on Windows
start "${ci_url}"
;;
linux*)
# Handle WSL on Windows
if uname -a | grep -i -q Microsoft && command -v powershell.exe
then
powershell.exe -NoProfile start "${ci_url}"
else
xdg-open "${ci_url}"
fi
;;
*)
# fall back to xdg-open for BSDs, etc.
xdg-open "${ci_url}"
;;
esac

View file

@ -1,81 +0,0 @@
#!/usr/bin/env bash
# A sorted prettier branch -vv
if ! (( BASH_VERSINFO[0] > 4 ||
BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] >= 2 )); then
printf >&2 'This script requires bash 4.2 or newer\n'
exit 1
fi
# allow the user to set a default reverse option in an environment variable
reverse=$(git config --get git-extras.brv.reverse || echo false)
while [[ $# -gt 0 ]]; do
case $1 in
-r | --reverse)
reverse=true
shift
;;
*)
break
;;
esac
done
if [[ -t 1 ]]; then
shopt -s checkwinsize
COLUMNS=$(tput cols)
color_branch_local=$(git config --get-color color.branch.local normal)
color_branch_current=$(git config --get-color color.branch.current green)
color_diff_commit=$(git config --get-color color.diff.commit yellow)
color_branch_upstream=$(git config --get-color color.branch.upstream blue)
reset=$(tput sgr0)
fi
declare -A upstream date hash message
eval "$(
git for-each-ref --format='upstream[%(refname:short)]=%(upstream:short)' \
--shell 'refs/heads/**'
)"
for b in "${!upstream[@]}"; do
blen=${#b} ulen=${#upstream[$b]}
(( bwidth = blen > bwidth ? blen : bwidth ))
(( uwidth = ulen > uwidth ? ulen : uwidth ))
IFS=/ read -r 'date[$b]' 'hash[$b]' 'message[$b]' < <(
git log --no-walk=unsorted --format=%ct/%h/%s "$b" --
)
hlen=${#hash[$b]}
(( hwidth = hlen > hwidth ? hlen : hwidth ))
done
mapfile -t ordered < <(
# the reverse option of git-brv causes the sort to be sorted normally rather than in reverse
reverse_opt=""
if [[ $reverse = false ]]; then
reverse_opt="-r"
fi
for b in "${!date[@]}"; do
printf '%d\t%s\n' "${date[$b]}" "$b"
done | sort -n $reverse_opt | cut -f2-
)
current=$(git symbolic-ref -q --short HEAD)
for b in "${ordered[@]}"; do
branch_color=$color_branch_local
if [[ $b = "$current" ]]; then
branch_color=$color_branch_current
fi
if [[ -t 1 ]]; then
msg=${message[$b]:0:COLUMNS-bwidth-uwidth-hwidth-14}
else
msg=${message[$b]}
fi
printf '%(%Y-%m-%d)T %s%*s%s %s%*s%s %s%*s%s %s\n' \
"${date[$b]}" \
"$branch_color" "-$bwidth" "$b" "$reset" \
"$color_branch_upstream" "-$uwidth" "${upstream[$b]}" "$reset" \
"$color_diff_commit" "-$hwidth" "${hash[$b]}" "$reset" \
"$msg"
done

12
bin/git-bug Executable file
View file

@ -0,0 +1,12 @@
#!/bin/sh
if test "$1" = "finish"; then
test -z $2 && echo "bug <name> required." && exit 1
branch=bug/$2
git merge --no-ff $branch && git delete-branch $branch
else
test -z $1 && echo "bug <name> required." && exit 1
branch=bug/$1
git checkout -b $branch &> /dev/null
git checkout $branch &> /dev/null
fi

View file

@ -1,231 +0,0 @@
#!/usr/bin/env bash
inverse=$(tput rev)
reset=$(tput sgr0)
txtbld=$(tput bold)
bldred=${txtbld}$(tput setaf 1)
# default option settings
guardedmode=false
singlemode=false
allwsmode=false
quiet=false
no_follow_symlinks=false
no_follow_hidden=false
#
# print usage message
#
usage() {
echo 1>&2 "usage: git bulk [--no-follow-symlinks] [--no-follow-hidden] [-q|--quiet] [-g] ([-a]|[-w <ws-name>]) <git command>"
echo 1>&2 " git bulk --addworkspace <ws-name> <ws-root-directory> (--from <URL or file>)"
echo 1>&2 " git bulk --removeworkspace <ws-name>"
echo 1>&2 " git bulk --addcurrent <ws-name>"
echo 1>&2 " git bulk --purge"
echo 1>&2 " git bulk --listall"
}
cdfail() {
echo 1>&2 "failed to change directory: $1"
exit 1
}
# add another workspace to global git config
addworkspace() {
git config --global bulkworkspaces."$wsname" "$wsdir";
if [ -n "$source" ]; then
if [ ! -d "$wsdir" ]; then echo 1>&2 "Path of workspace doesn't exist, make it first."; exit 1; fi
regex='http(s)?://|ssh://|(git@)?.*:.*/.*'
if [[ "$source" =~ $regex ]]; then
pushd "$wsdir" > /dev/null || cdfail "$wsdir"
git clone "$source"
popd > /dev/null || cdfail "$OLDPWD"
else
source=$(realpath "$source" 2>/dev/null)
if [ -f "$source" ]; then
pushd "$wsdir" > /dev/null || cdfail "$wsdir"
while IFS= read -r line; do
if [ -n "$line" ]; then
# the git clone command to take the complete line in the repository.txt as separate argument. This facilitated the cloning of the repository with a custom folder name.
# shellcheck disable=SC2086
git clone $line;
fi
done < "$source"
popd > /dev/null || cdfail "$OLDPWD"
else
echo 1>&2 "format of URL or file unknown"
fi
fi
fi
}
# add current directory
addcurrent() { git config --global bulkworkspaces."$wsname" "$PWD"; }
# remove workspace from global git config
removeworkspace() { checkWSName && git config --global --unset bulkworkspaces."$wsname"; }
# remove workspace from global git config
purge() { git config --global --remove-section bulkworkspaces; }
# list all current workspace locations defined
listall() { git config --global --get-regexp bulkworkspaces; }
# guarded execution of a git command in one specific repository
guardedExecution () {
if [ "${quiet?}" != "true" ] || $guardedmode; then
echo 1>&2 "${bldred}->${reset} executing ${inverse}git $gitcommand${reset} in repository ${leadingpath%/*}/${bldred}${curdir##*/}${reset}"
fi
if $guardedmode; then
echo 1>&2 -n " Execute command here (y/n)? "
read -n 1 -r </dev/tty; echo 1>&2
if [[ $REPLY =~ ^[Yy]$ ]]; then
git "$@"
fi
else
git "$@"
fi
}
# check if the passed command is known as a core git command
checkGitCommand () {
if git help -a | grep -o -q "\b${corecommand}\b"; then
echo 1>&2 "Core command \"$corecommand\" accepted."
else
if git config --get-regexp alias | grep -o -q "\.${corecommand} "; then
echo 1>&2 "Alias ${corecommand} accepted."
else
usage && echo 1>&2 "error: unknown GIT command: $corecommand" && exit 1
fi
fi
}
# check if workspace name is registered
checkWSName () {
while read -r workspace; do
parseWsName "$workspace"
if [[ $rwsname == "$wsname" ]]; then return; fi
done <<< "$(listall)"
# when here the ws name was not found
usage && echo 1>&2 "error: unknown workspace name: $wsname" && exit 1
}
# parse out wsname from workspacespec
parseWsName () {
local wsspec="$1"
# Get the workspace value from its specification in the `.gitconfig`.
# May be an absolute path or a variable name of the form: `$VARNAME`
rwsdir=${wsspec#* }
if [[ ${rwsdir:0:1} == '$' ]]; then
# Dereference the `rwsdir` value which is a variable name.
rwsdir_varname=${rwsdir:1}
rwsdir=${!rwsdir_varname}
if [[ -z "${rwsdir}" ]]; then
echo 1>&2 "error: bad environment variable: $rwsdir_varname" && exit 1
fi
fi
rwsname=${wsspec#*.} && rwsname=${rwsname%% *}
}
# detects the wsname of the current directory
wsnameToCurrent () {
while read -r workspace; do
if [ -z "$workspace" ]; then continue; fi
parseWsName "$workspace"
if echo "$PWD" | grep -o -q "$rwsdir"; then wsname="$rwsname" && return; fi
done <<< "$(listall)"
# when here then not in workspace dir
echo 1>&2 "error: you are not in a workspace directory. your registered workspaces are:" && \
wslist="$(listall)" && echo 1>&2 "${wslist:-'<no workspaces defined yet>'}" && exit 1
}
# helper to check number of arguments.
allowedargcount () {
if [ "$paramcount" -ne "${1:-0}" ] && [ "$paramcount" -ne "${2:-0}" ]; then
echo 1>&2 "error: wrong number of arguments" && usage;
exit 1;
fi
}
# execute the bulk operation
executBulkOp () {
checkGitCommand
if ! $allwsmode && ! $singlemode; then wsnameToCurrent; fi # by default git bulk works within the 'current' workspace
listall | while read -r workspacespec; do
parseWsName "$workspacespec"
if [[ -n $wsname ]] && [[ $rwsname != "$wsname" ]]; then continue; fi
cd "$rwsdir" || exit 1
local actual=$PWD
[ "${quiet?}" != "true" ] && echo 1>&2 "Executing bulk operation in workspace ${inverse}$actual${reset}"
# build `find` flags depending on command-line options
local find_flags=()
if [[ "$no_follow_symlinks" == true ]]; then
find_flags+=(-P)
else
find_flags+=(-L)
fi
# find all git repositories under the workspace on which we want to operate
readarray allGitFolders < <(find "${find_flags[@]}" . -name ".git" 2>/dev/null)
for line in "${allGitFolders[@]}"; do
local gitrepodir=${line::${#line}-5} # cut the .git part of find results to have the root git directory of that repository
cd "$gitrepodir" || exit 1 # into git repo location
local curdir=$PWD
local leadingpath=${curdir#"${actual}"}
# do not execute if we do not want to consider a ".git" directory under a hidden directory
if [ $no_follow_hidden = false ] || ! [[ "$leadingpath" =~ "/." ]]; then
guardedExecution "$@"
fi
cd "$rwsdir" || exit 1 # back to origin location of last find command
done
done
}
paramcount="${#}"
# if no arguments show usage
if [[ $paramcount -le 0 ]]; then usage; fi
# parse command parameters
while [ "${#}" -ge 1 ] ; do
case "$1" in
--quiet|-q) quiet='true' ;;
--listall|--purge)
butilcommand="${1:2}" && break ;;
--removeworkspace|--addcurrent|--addworkspace)
butilcommand="${1:2}" && wsname="$2" && wsdir="$3" && if [ "$4" = "--from" ]; then source="$5"; fi && break ;;
--no-follow-symlinks)
no_follow_symlinks=true ;;
--no-follow-hidden)
no_follow_hidden=true ;;
-a)
allwsmode=true ;;
-g)
guardedmode=true ;;
-w)
singlemode=true && shift && wsname="$1" && checkWSName ;;
--*)
usage && echo 1>&2 "error: unknown argument $1" && exit 1 ;;
-*)
usage && echo 1>&2 "error: unknown argument $1" && exit 1 ;;
*) # git core commands
butilcommand="executBulkOp" && corecommand="$1" && gitcommand="$*" && break ;;
esac && shift
done
# check option compatibility
if $allwsmode && $singlemode; then echo 1>&2 "error: options -w and -a are incompatible" && exit 1; fi
# if single mode check the supplied workspace name
if $singlemode; then echo 1>&2 "Selected single workspace mode in workspace: $wsname" && checkWSName; fi
# check right number of arguments
case $butilcommand in
listall|purge) allowedargcount 1;;
addcurrent|removeworkspace) allowedargcount 2;;
addworkspace) allowedargcount 3 5;;
esac
# pass the origin arguments to the 'executBulkOp'
$butilcommand "$@" # run user command

View file

@ -1,597 +1,24 @@
#!/usr/bin/env bash #!/bin/sh
DEF_TAG_RECENT="n.n.n" CHANGELOG=`ls | egrep 'change|history' -i`
GIT_LOG_OPTS="$(git config changelog.opts)" if test "$CHANGELOG" = ""; then CHANGELOG='History.md'; fi
GIT_LOG_FORMAT="$(git config changelog.format)" DATE=`date +'%Y-%m-%d'`
[[ -z "$GIT_LOG_FORMAT" ]] && GIT_LOG_FORMAT=' * %s' HEAD="\nn.n.n / $DATE \n==================\n"
GIT_MERGELOG_FORMAT="$(git config changelog.mergeformat)"
[[ -z "$GIT_MERGELOG_FORMAT" ]] && GIT_MERGELOG_FORMAT=' * %s%n%w(64,4,4)%b' if test "$1" = "--list"; then
GIT_EDITOR="$(git var GIT_EDITOR)" version=`git for-each-ref refs/tags --sort=-authordate --format='%(refname)' \
PROGNAME="git-changelog" --count=1 | sed 's/^refs\/tags\///'`
if test -z "$version"; then
git_editor() { git log --pretty="format: * %s"
if test -z "${GIT_EDITOR:+set}"
then
GIT_EDITOR="$(git var GIT_EDITOR)" || return $?
fi
eval "$GIT_EDITOR" '"$@"'
}
_usage() {
cat << EOF
usage: $PROGNAME options [file]
usage: $PROGNAME -h|help|?
Generate a Changelog from git(1) tags (annotated or lightweight) and commit
messages. Existing Changelog files with filenames that begin with 'Change' or
'History' will be identified automatically and their content will be appended
to the new output generated (unless the -p|--prune-old option is used). If no
tags exist, then all commits are output; if tags exist, then only the most-
recent commits are output up to the last identified tag.
OPTIONS:
-a, --all Retrieve all commits (ignores --start-tag/commit, --final-tag)
-l, --list Display commits as a list, with no titles
-t, --tag Tag label to use for most-recent (untagged) commits
-f, --final-tag Newest tag to retrieve commits from in a range
-s, --start-tag Oldest tag to retrieve commits from in a range
--start-commit Like --start-tag but use commit instead of tag
-n, --no-merges Suppress commits from merged branches
-m, --merges-only Only uses merge commits (uses both subject and body of commit)
-p, --prune-old Replace existing Changelog entirely with new content
-x, --stdout Write output to stdout instead of to a Changelog file
-h, --help, ? Show this message
EOF
}
_error() {
[ $# -eq 0 ] && _usage && exit 0
echo
echo "ERROR: " "$@"
echo
}
# _setValueForKeyFakeAssocArray()
# /*!
# @abstract Set value for key from a fake associative array
# @discussion
# Iterates over target_ary (an indexed array), searching for target_key, if the
# key is found its value is set to new_value otherwise the target_key and
# new_value are appended to the array.
#
# The indexed array values must conform to this format:
# "key:value"
# Where key and value are separated by a single colon character.
#
# Specify empty values as an empty, quoted string.
#
# So-called "fake" associative arrays are useful for environments where the
# installed version of bash(1) precedes 4.0.
# @param target_key Key to retrieve
# @param new_value New or updated value
# @param target_ary Indexed array to scan
# @return Returns new array with updated key (status 0) or an empty array
# (status 1) on failure.
# */
_setValueForKeyFakeAssocArray() {
# parameter list supports empty arguments!
local target_key="$1"; shift
local new_value="$1"; shift
local target_ary=()
local defaultIFS="$IFS"
local IFS="$defaultIFS"
local found=false
IFS=$' ' target_ary=( $1 ) IFS="$defaultIFS"
[[ -z "${target_key}" || "${#target_ary[@]}" -eq 0 ]] && echo "${value}" && return 1
local _target_ary_length="${#target_ary[@]}"
local i
for (( i=0; i<"${_target_ary_length}"; i++ )); do
local __val="${target_ary[$i]}"
if [[ "${__val%%:*}" == "${target_key}" ]]; then
target_ary[$i]="${__val%%:*}:${new_value}"
found=true
break
fi
unset __val
done
unset i _target_ary_length
# key not found, append
[[ "$found" == false ]] && target_ary+=( "${target_key}:${new_value}" )
printf "%s" "${target_ary[*]}"
}
# _valueForKeyFakeAssocArray()
# /*!
# @abstract Fetch value for key from a fake associative array
# @discussion
# Iterates over target_ary (an indexed array), searching for target_key, if the
# key is found its value is returned.
#
# The indexed array values must conform to this format:
# "key:value"
# Where key and value are separated by a single colon character.
#
# So-called "fake" associative arrays are useful for environments where the
# installed version of bash(1) precedes 4.0.
# @param target_key Key to retrieve
# @param target_ary Indexed array to scan
# @return Returns string containing value (status 0) or an empty string
# (status 1) on failure.
# */
_valueForKeyFakeAssocArray() {
local target_key="$1"
local target_ary=()
local defaultIFS="$IFS"
local IFS="$defaultIFS"
local value=""
IFS=$' ' target_ary=( $2 ) IFS="$defaultIFS"
[[ -z "${target_key}" || "${#target_ary[@]}" -eq 0 ]] && echo "${value}" && return 1
local t
for t in "${target_ary[@]}"; do
if [[ "${t%%:*}" == "${target_key}" ]]; then
value="${t#*:}"
break
fi
done
unset t
echo -e "${value}"; return 0
}
_fetchCommitRange() {
local list_all="${1:-false}"
local start_tag="$2"
local final_tag="$3"
# This shellcheck disable is applied to the whole if-body
# shellcheck disable=SC2086
if [[ "$list_all" == true ]]; then
git log $GIT_LOG_OPTS --pretty=format:"${CUR_GIT_LOG_FORMAT}"
elif [[ -n "$final_tag" && "$start_tag" == "null" ]]; then
git log $GIT_LOG_OPTS --pretty=format:"${CUR_GIT_LOG_FORMAT}" "${final_tag}"
elif [[ -n "$final_tag" ]]; then
git log $GIT_LOG_OPTS --pretty=format:"${CUR_GIT_LOG_FORMAT}" "${start_tag}"'..'"${final_tag}"
elif [[ -n "$start_tag" ]]; then
git log $GIT_LOG_OPTS --pretty=format:"${CUR_GIT_LOG_FORMAT}" "${start_tag}"'..'
fi | sed 's/^ \* \*/ */g'
}
_formatCommitPlain() {
local start_tag="$1"
local final_tag="$2"
printf "%s\n" "$(_fetchCommitRange "false" "$start_tag" "$final_tag")"
}
_formatCommitPretty() {
local title_tag="$1"
local title_date="$2"
local start_tag="$3"
local final_tag="$4"
local title="$title_tag / $title_date"
local title_underline=""
local i
for i in $(seq ${#title}); do
title_underline+="="
done
unset i
printf '\n%s\n%s\n' "$title" "$title_underline"
printf "\n%s\n" "$(_fetchCommitRange "false" "$start_tag" "$final_tag")"
}
commitList() {
# parameter list supports empty arguments!
local list_all="${1:-false}"; shift
local title_tag="$1"; shift
local start_tag="$1"; shift
local final_tag="$1"; shift
local list_style="${1:-false}"; shift # enable/disable list format
local start_commit="$1"; shift
local changelog="$FILE"
local title_date="$(date +'%Y-%m-%d')"
local tags_list=()
local tags_list_keys=()
local defaultIFS="$IFS"
local IFS="$defaultIFS"
if [[ -n "$start_commit" && "$final_tag" == "null" \
&& "$start_tag" == "null" ]]; then
# if there is not tag after $start_commit,
# output directly without fetch all tags
if [[ "$list_style" == true ]]; then
_formatCommitPlain "${start_commit}~"
else
_formatCommitPretty "$title_tag" "$title_date" "$start_commit~"
fi
return
fi
#
# Tags look like this:
#
# >git log --tags --simplify-by-decoration --date="short" --pretty="format:%h$%x09%ad$%x09%d"
#
# ecf1f2b$ 2015-03-15$ (HEAD, tag: v1.0.1, origin/master, origin/HEAD, master, hotfix/1.0.2)
# a473e9c$ 2015-03-04$ (tag: v1.0.0)
# f2cb562$ 2015-02-19$ (tag: v0.9.2)
# 6197c2b$ 2015-02-19$ (tag: v0.9.1)
# 1e5f5e6$ 2015-02-16$ (tag: v0.9.0)
# 3de8ab5$ 2015-02-11$ (origin/feature/restore-auto)
# a15afd1$ 2015-02-02$ (origin/feature/versionable)
# 38a44e0$ 2015-02-02$ (origin/feature/save-auto)
# 3244b80$ 2015-01-16$ (origin/feature/silent-history, upstream)
# 85e45f8$ 2014-08-25$
#
# The most-recent tag will be preceded by "HEAD, " if there have been zero
# commits since the tag. Also notice that with gitflow, we see features.
#
# fetch our tags
local _ref _date _tag _tab='%x09'
local _tag_regex='tag: *'
while IFS=$'\t' read -r _ref _date _tag; do
[[ -z "${_tag}" ]] && continue
# strip out tags form ()
# git v2.2.0+ supports '%D', like '%d' without the " (", ")" wrapping. One day we should use it instead.
_tag="${_tag# }"; _tag="${_tag//[()]/}"
# trap tag if it points to last commit (HEAD)
_tag="${_tag#HEAD*, }"
# strip out branches
[[ ! "${_tag}" =~ ${_tag_regex} ]] && continue
# strip out any additional tags pointing to same commit, remove tag label
_tag="${_tag%%,*}"; _tag="${_tag#tag: }"
# add tag to assoc array; copy tag to tag_list_keys for ordered iteration
tags_list+=( "${_tag}:${_ref}=>${_date}" )
tags_list_keys+=( "${_tag}" )
done <<< "$(git log --tags --simplify-by-decoration --date="short" --pretty="format:%h${_tab}%ad${_tab}%d")"
IFS="$defaultIFS"
unset _tag_regex
unset _ref _date _tag _tab
local _tags_list_keys_length="${#tags_list_keys[@]}"
if [[ "${_tags_list_keys_length}" -eq 0 ]]
then
unset _tags_list_keys_length
if [[ "$list_style" == true ]]; then
printf "%s" "$(_fetchCommitRange "true")"
else
local title="$title_tag / $title_date"
local title_underline=""
local i
for i in $(seq ${#title}); do
title_underline+="="
done
unset i
printf '\n%s\n%s\n' "$title" "$title_underline"
printf "\n%s\n" "$(_fetchCommitRange "true")"
fi
return
fi
local _final_tag_found=false
local _start_tag_found=false
local i
for (( i=0; i<"${_tags_list_keys_length}"; i++ )); do
local __curr_tag="${tags_list_keys[$i]}"
local __prev_tag="${tags_list_keys[$i+1]:-null}"
local __curr_date
__curr_date="$(_valueForKeyFakeAssocArray "${__curr_tag}" "${tags_list[*]}")"
__curr_date="${__curr_date##*=>}"
# output latest commits, up until the most-recent tag, these are all
# new commits made since the last tagged commit.
if [[ $i -eq 0 && ( -z "$final_tag" || "$final_tag" == "null" ) ]]; then
if [[ "$list_style" == true ]]; then
_formatCommitPlain "${__curr_tag}" >> "$tmpfile"
else
_formatCommitPretty "$title_tag" "$title_date" "${__curr_tag}"
fi
fi
# both final_tag and start_tag are "null", user just wanted recent commits
[[ "$final_tag" == "null" && "$start_tag" == "null" ]] && break;
# find the specified final tag, continue until found
if [[ -n "$final_tag" && "$final_tag" != "null" ]]; then
[[ "$final_tag" == "${__curr_tag}" ]] && _final_tag_found=true
[[ "$final_tag" != "${__curr_tag}" && "${_final_tag_found}" == false ]] && continue
fi
# find the specified start tag, break when found
if [[ -n "$start_tag" ]]; then
[[ "$start_tag" == "${__curr_tag}" ]] && _start_tag_found=true
if [[ "${_start_tag_found}" == true ]]; then
if [[ -n "$start_commit" ]]; then
# output commits after start_commit to its closest tag
if [[ "$list_style" == true ]]; then
_formatCommitPlain "$start_commit~" "${__curr_tag}"
else
_formatCommitPretty "${__curr_tag}" "${__curr_date}" \
"$start_commit~" "${__curr_tag}"
fi
break
fi
[[ "$start_tag" != "${__curr_tag}" ]] && break
fi
fi
# output commits made between prev_tag and curr_tag, these are all of the
# commits related to the tag of interest.
if [[ "$list_style" == true ]]; then
_formatCommitPlain "${__prev_tag}" "${__curr_tag}"
else
_formatCommitPretty "${__curr_tag}" "${__curr_date}" "${__prev_tag}" "${__curr_tag}"
fi
unset __curr_date
unset __prev_tag
unset __curr_tag
done
unset i
unset _start_tag_found
unset _final_tag_found
unset _tags_list_keys_length
return
}
commitListPlain() {
local list_all="${1:-false}"
local start_tag="$2"
local final_tag="$3"
local start_commit="$4"
commitList "$list_all" "" "$start_tag" "$final_tag" "true" "$start_commit"
}
commitListPretty() {
local list_all="${1:-false}"
local title_tag="$2"
local start_tag="$3"
local final_tag="$4"
local start_commit="$5"
local title_date="$(date +'%Y-%m-%d')"
commitList "$list_all" "$title_tag" "$start_tag" "$final_tag" "false" \
"$start_commit"
}
_exit() {
local pid_list=()
local defaultIFS="$IFS"
local IFS="$defaultIFS"
stty sane; echo; echo "caught signal, shutting down"
IFS=$'\n'
# The format of `ps` is different between Windows and other platforms,
# so we need to calculate the total column number(COL_NUM) of header first.
# Why don't we just use the last column?
# Because the body of CMD column may contain space and be treated as multiple fields.
pid_list=( $(ps -f |
awk -v ppid=$$ 'NR == 1 {
COL_NUM = NF
}
$3 == ppid {
# filter out temp processes created in this subshell
if ($COL_NUM != "ps" && $COL_NUM != "awk" && $COL_NUM !~ "bash$")
print $2
}')
)
IFS="$defaultIFS"
local _pid
for _pid in "${pid_list[@]}"; do
echo "killing: ${_pid}"
kill -TERM "${_pid}"
done
wait; stty sane; exit 1
}
trap '_exit' SIGINT SIGQUIT SIGTERM
main() {
local start_tag="null" # empty string and "null" mean two different things!
local final_tag="null"
local option=(
"list_all:false"
"list_style:false"
"title_tag:$DEF_TAG_RECENT"
"start_tag:"
"start_commit:"
"final_tag:"
"output_file:"
"use_stdout:false"
"prune_old:false"
)
#
# We work chronologically backwards from NOW towards start_tag where NOW also
# includes the most-recent (un-tagged) commits. If no start_tag has been
# specified, we work back to the very first commit; if a final_tag has been
# specified, we begin at the final_tag and work backwards towards start_tag.
#
# An existing ChangeLog/History file will be appended to the output unless the
# prune old (-p | --prune-old) option has been enabled.
while [ "$1" != "" ]; do
case $1 in
-a | --all )
option=( $(_setValueForKeyFakeAssocArray "list_all" true "${option[*]}") )
;;
-l | --list )
option=( $(_setValueForKeyFakeAssocArray "list_style" true "${option[*]}") )
;;
-t | --tag )
option=( $(_setValueForKeyFakeAssocArray "title_tag" "$2" "${option[*]}") )
shift
;;
-f | --final-tag )
option=( $(_setValueForKeyFakeAssocArray "final_tag" "$2" "${option[*]}") )
shift
;;
-s | --start-tag )
option=( $(_setValueForKeyFakeAssocArray "start_tag" "$2" "${option[*]}") )
shift
;;
--start-commit )
option=( $(_setValueForKeyFakeAssocArray "start_commit" "$2" "${option[*]}") )
shift
;;
-n | --no-merges )
GIT_LOG_OPTS='--no-merges'
CUR_GIT_LOG_FORMAT="$GIT_LOG_FORMAT"
;;
-m | --merges-only )
GIT_LOG_OPTS='--merges'
CUR_GIT_LOG_FORMAT="$GIT_MERGELOG_FORMAT"
;;
-p | --prune-old )
option=( $(_setValueForKeyFakeAssocArray "prune_old" true "${option[*]}") )
;;
-x | --stdout )
option=( $(_setValueForKeyFakeAssocArray "use_stdout" true "${option[*]}") )
;;
-h | ? | help | --help )
_usage
exit 1
;;
* )
[[ "${1:0:1}" == '-' ]] && _error "Invalid option: $1" && _usage && exit 1
option=( $(_setValueForKeyFakeAssocArray "output_file" "$1" "${option[*]}") )
;;
esac
shift
done
# The default log format unless already set
[[ -z "$CUR_GIT_LOG_FORMAT" ]] && CUR_GIT_LOG_FORMAT="$GIT_LOG_FORMAT"
local _tag="$(_valueForKeyFakeAssocArray "start_tag" "${option[*]}")"
local start_commit="$(_valueForKeyFakeAssocArray "start_commit" "${option[*]}")"
if [[ -n "$start_commit" ]]; then
if [[ -n "${_tag}" ]]; then
_error "--start-tag could not use with --start-commit!"
return 1
fi
start_tag="$(git describe --tags --contains "$start_commit" 2>/dev/null || echo 'null')"
if [[ -z "$start_tag" ]]; then
_error "Could find the associative tag for the start-commit!"
return 1
fi
# remove suffix from the $start_tag when no tag matched exactly
start_tag="${start_tag%%~*}"
# also remove "^0" added sometimes when tag matched exactly
start_tag="${start_tag%%^0}"
elif [[ -n "${_tag}" ]]; then
start_tag="$(git describe --tags --abbrev=0 "${_tag}" 2>/dev/null)"
if [[ -z "$start_tag" ]]; then
_error "Specified start-tag does not exist!"
return 1
fi
fi
if [[ -n "${_tag}" ]]; then
if [[ -n "$start_commit" ]]; then
_error "--start-tag could not use with --start-commit!"
return 1
fi
fi
unset _tag
local _tag="$(_valueForKeyFakeAssocArray "final_tag" "${option[*]}")"
if [[ -n "${_tag}" ]]; then
final_tag="$(git describe --tags --abbrev=0 "${_tag}" 2>/dev/null)"
if [[ -z "$final_tag" ]]; then
_error "Specified final-tag does not exist!"
return 1
fi
fi
unset _tag
#
# generate changelog
#
local tmpfile changelog title_tag
tmpfile="$(git_extra_mktemp)"
changelog="$(_valueForKeyFakeAssocArray "output_file" "${option[*]}")"
title_tag="$(_valueForKeyFakeAssocArray "title_tag" "${option[*]}")"
if [[ "$(_valueForKeyFakeAssocArray "list_style" "${option[*]}")" == true ]]; then
if [[ "$(_valueForKeyFakeAssocArray "list_all" "${option[*]}")" == true ]]; then
commitListPlain "true" >> "$tmpfile"
else
commitListPlain "false" "$start_tag" "$final_tag" \
"$start_commit" >> "$tmpfile"
fi
else else
if [[ "$(_valueForKeyFakeAssocArray "list_all" "${option[*]}")" == true ]]; then git log --pretty="format: * %s" $version..
commitListPretty "true" "$title_tag" >> "$tmpfile"
else
commitListPretty "false" "$title_tag" "$start_tag" "$final_tag" \
"$start_commit" >> "$tmpfile"
fi
fi fi
else
if [[ -z "$changelog" ]]; then tmp="/tmp/changelog"
changelog="$(find . -mindepth 1 -maxdepth 1 \( -iname '*change*' -o -iname '*history*' \) | head -n1)" echo $HEAD > $tmp
if [[ -z "$changelog" ]]; then git-changelog --list >> $tmp
changelog="History.md"; echo '' >> $tmp
fi if [ -f $CHANGELOG ]; then cat $CHANGELOG >> $tmp; fi
fi mv $tmp $CHANGELOG
test -n "$EDITOR" && $EDITOR $CHANGELOG
# append existing changelog? fi
if [[ -f "$changelog" \
&& "$(_valueForKeyFakeAssocArray "prune_old" "${option[*]}")" == false ]]; then
cat "$changelog" >> "$tmpfile"
fi
# output file to stdout or move into place
if [[ "$(_valueForKeyFakeAssocArray "use_stdout" "${option[*]}")" == true ]]; then
cat "$tmpfile"
rm -f "$tmpfile"
else
cp -f "$tmpfile" "$changelog"
rm -f "$tmpfile"
if [[ -n "$GIT_EDITOR" ]]; then
git_editor "$changelog" || _exit
else
less "$changelog" || _exit
fi
fi
return
}
main "$@"
exit 0

View file

@ -1,43 +0,0 @@
#!/usr/bin/env bash
PROGNAME="git-clear"
FORCE=0
_usage() {
cat << EOF
usage: $PROGNAME options
usage: $PROGNAME -h|help|?
clear git repository
OPTIONS:
-f, --force Force clear without questioning user
-h, --help, ? Show this message
EOF
}
# Read arguments
while [ "$1" != "" ]; do
case $1 in
-f|--force)
FORCE=1
;;
-h|--help|?)
_usage
exit 1
;;
esac
shift
done
# Only wait for answer if not forced by user
if [[ $FORCE == 0 ]]; then
echo -n "Sure? - THIS COMMAND MAY DELETE FILES THAT CANNOT BE RECOVERED, including those in .gitignore [y/N]: "
read -r clean
else
clean=y
fi
if [ "$clean" = "y" ]; then
git clean -d -f -x && git reset --hard
fi

View file

@ -1,7 +0,0 @@
#!/usr/bin/env bash
echo -n "Sure? - This command may delete files that cannot be recovered. Files and directories in .gitignore will be preserved [y/N]: "
read -r answer
if [ "$answer" = "y" ]
then git clean -d -f && git reset --hard
fi

View file

@ -1,17 +0,0 @@
#!/usr/bin/env bash
set -e -o pipefail
coauthor=$1
email=$2
test -z "$coauthor" && echo "co-author required." 1>&2 && exit 1
test -z "$email" && echo "co-author email required." 1>&2 && exit 1
old_message="$(git log --format=%B -n1)"
if [[ "$old_message" == *"Co-authored-by:"* ]]; then
git commit --amend -m "$old_message
Co-authored-by: $coauthor <$email>"
else
git commit --amend -m "$old_message" -m "Co-authored-by: $coauthor <$email>"
fi

View file

@ -1,36 +1,6 @@
#!/usr/bin/env bash #!/bin/sh
REF="" SINCE="last week"
SINCE="" test $# -ne 0 && SINCE=$@
echo "... commits since $SINCE" >&2
while [[ $# -gt 0 ]]; do git log --pretty='%an - %s' --after="@{$SINCE}"
case "$1" in
-r|--ref)
if [[ -z "$2" ]]; then
echo "error: option $1 requires an argument" >&2
exit 1
fi
REF="$2"
shift
shift
;;
*)
# non-flag args accumulation, e.g. "last " + "week" -> "last week"
SINCE="${SINCE:+$SINCE }$1"
shift
;;
esac
done
if [[ -n "$REF" && -n "$SINCE" ]]; then
echo "error: '--ref <ref>' and '<date>' are mutually exclusive" >&2
exit 1
fi
SINCE="${SINCE:-last week}"
if [[ -n "$REF" ]]; then
git log --pretty='%h %an - %s' "$REF..HEAD"
else
git log --pretty='%h %an - %s' --after="@{$SINCE}"
fi

View file

@ -1 +0,0 @@
git-abort

View file

@ -1,7 +1,9 @@
#!/usr/bin/env bash #!/bin/sh
user="$*" user="$*"
test -z "$user" && echo "user name required." 1>&2 && exit 1 test -z "$user" && echo "user name required." && exit 1
git shortlog --format=format:"%h %s" --author="$user" count=`git log --oneline --pretty="format: %an" | grep "$user" | wc -l`
test $count -eq 0 && echo "$user did not contribute." && exit 1
git shortlog | grep "$user (" -A $count

View file

@ -1,8 +1,8 @@
#!/usr/bin/env bash #!/bin/sh
if test "$1" = "--all"; then if test "$1" = "--all"; then
git shortlog -n -s | awk '{print substr($0,index($0,$2)) " (" $1 ")"}' git shortlog -n $@ | grep "):" | sed 's|:||'
echo echo
fi fi
echo total "$(git rev-list --count HEAD)" echo total `git log --oneline | wc -l`

View file

@ -1,78 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
PROGRAM=$0
CURRENT_FILENAME=""
DESTINATION_FILENAME=""
function usage()
{
echo 1>&2 "USAGE: ${PROGRAM} CURRENT_FILENAME DESTINATION_FILENAME"
}
while [[ $# -gt 0 ]]
do
key="$1"
if [[ "$CURRENT_FILENAME" == "" ]]; then
CURRENT_FILENAME=$key
elif [[ "$DESTINATION_FILENAME" == "" ]]; then
DESTINATION_FILENAME=$key
else
usage
exit 30 # Error during arguments parsing
fi
shift # past argument or value
done
if [[ "$DESTINATION_FILENAME" == "" ]]; then
usage
exit 20 # Missing arguments CURRENT_FILENAME
elif [[ "$CURRENT_FILENAME" == "" ]]; then
usage
exit 10 # Missing arguments CURRENT_FILENAME
else
if [ -e "$DESTINATION_FILENAME" ]; then
echo 1>&2 "$DESTINATION_FILENAME already exists."
echo 1>&2 "Make sure to remove the destination first."
exit 40
fi
if [[ "$DESTINATION_FILENAME" == */ ]]; then
echo 1>&2 "$DESTINATION_FILENAME is not a file path."
exit 80
fi
if [[ "$DESTINATION_FILENAME" == */* ]]; then
DESTINATION_DIR="${DESTINATION_FILENAME%/*}"
if ! mkdir -p "$DESTINATION_DIR"; then
echo 1>&2 "Failed to create destination directory: $DESTINATION_DIR"
exit 160
fi
fi
echo "Copying $CURRENT_FILENAME into $DESTINATION_FILENAME"
# Pre-check that the source and destination will work
git mv --dry-run "${CURRENT_FILENAME}" "${DESTINATION_FILENAME}"
# Make a new branch and switch to it
BRANCH_NAME="git-cp-$(date +%s)"
git checkout -b "$BRANCH_NAME"
# Move the original file to the new destination, in this branch
git mv "${CURRENT_FILENAME}" "${DESTINATION_FILENAME}"
git commit -nm "--Duplicate $CURRENT_FILENAME history into $DESTINATION_FILENAME"
# Restore the original file to keep it in the history
git checkout HEAD~ "${CURRENT_FILENAME}"
git commit -nm "--Restore $CURRENT_FILENAME"
# Switch to the original branch and merge this back in.
git checkout -
git merge --no-ff "$BRANCH_NAME" -m "Copy $CURRENT_FILENAME into $DESTINATION_FILENAME"
# We're now done with the branch, so delete it.
# We shouldn't need -D here, as we've already merged it back in.
git branch -d "$BRANCH_NAME"
fi

View file

@ -1,76 +1,9 @@
#!/usr/bin/env bash #!/bin/sh
test $# -eq 0 && echo "branch argument required." 1>&2 && exit 1 branch=$1
test -z $branch && echo "branch required." && exit 1
# preference takes lowest priority; look for remote from prefs first git push origin origin:refs/heads/$branch
REMOTE_PREF=$(git config git-extras.create-branch.remote) git fetch origin
if [ -n "$REMOTE_PREF" ]; then git checkout --track -b $branch origin/$branch
REMOTE=$REMOTE_PREF git pull
fi
while test $# != 0
do
case $1 in
-r|--remote)
if [[ -n $2 ]]
then
REMOTE=$2
shift
else
REMOTE=origin
fi
;;
--from)
if [[ -n $2 ]]; then
START_POINT=$2
shift
fi
;;
*)
BRANCH=$1
esac
shift
done
# handle ambiguous `-r` option argument by shift
if [[ -z $BRANCH ]] && [[ -n $REMOTE ]]
then
BRANCH=$REMOTE
REMOTE=origin
fi
test -z "$BRANCH" && echo "branch argument required." 1>&2 && exit 1
if [[ -n $REMOTE ]]
then
stderr=$(git_extra_mktemp)
git ls-remote --exit-code "$REMOTE" 1>/dev/null 2>"$stderr"
REMOTE_EXIT=$?
REMOTE_ERROR=$(<"$stderr")
rm -f "$stderr"
if [ $REMOTE_EXIT -eq 0 ]
then
if [[ -n $START_POINT ]]; then
git fetch "$REMOTE"
git checkout --track -b "$BRANCH" "$START_POINT"
git push "$REMOTE" "HEAD:refs/heads/$BRANCH"
else
git push "$REMOTE" "HEAD:refs/heads/$BRANCH"
git fetch "$REMOTE"
git checkout --track -b "$BRANCH" "$REMOTE/$BRANCH"
fi
exit $?
else
echo
echo " Error connecting to remote '$REMOTE': $REMOTE_ERROR"
echo
exit $REMOTE_EXIT
fi
fi
if [[ -n $START_POINT ]]
then
git checkout -b "$BRANCH" "$START_POINT"
else
git checkout -b "$BRANCH"
fi

View file

@ -1,17 +1,6 @@
#!/usr/bin/env bash #!/bin/sh
set -e
# Assert there is at least one branch provided branch=$1
test -z "$1" && echo "branch required." 1>&2 && exit 1 test -z $branch && echo "branch required." && exit 1
git branch -D $branch
for branch in "$@" git branch -d -r origin/$branch && git push origin :$branch
do
remote=$(git config "branch.$branch.remote" || echo "origin")
ref=$(git config "branch.$branch.merge" || echo "refs/heads/$branch")
git branch -D "$branch" || true
# Avoid deleting local upstream
[ "$remote" = "." ] && continue
git branch -d -r "$remote/$branch" || continue
git push "$remote" ":$ref"
done

View file

@ -1,60 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
dry_run=false
force=false
prune=false
while [[ $# -gt 0 ]]; do
case "$1" in
-n|--dry-run)
dry_run=true
shift
;;
-f|--force)
force=true
shift
;;
-p|--prune)
prune=true
shift
;;
*)
echo "Unknown option: $1"
echo "Usage: git delete-gone-branches [-n|--dry-run] [-f|--force] [-p|--prune]"
exit 1
;;
esac
done
if [ "$prune" = true ]; then
git fetch --prune
fi
gone_branches=$(git for-each-ref --format \
'%(if:equals=gone)%(upstream:track,nobracket)%(then)%(refname:short)%(end)' refs/heads/ | sed '/^$/d')
if [ -z "$gone_branches" ]; then
echo "No branches with a gone remote found."
exit 0
fi
if [ "$dry_run" = true ]; then
echo "Would delete the following branches:"
echo "$gone_branches"
exit 0
fi
echo "The following branches will be deleted:"
echo "$gone_branches"
if [ "$force" = false ]; then
read -r -p "Delete these branches? [y/N] " confirm
if [[ ! "$confirm" =~ ^[yY]$ ]]; then
echo "Aborted."
exit 0
fi
fi
echo "$gone_branches" | xargs git branch -D

View file

@ -1,7 +0,0 @@
#!/usr/bin/env bash
branches=$(git branch --no-color --merged | grep -vE "^(\*|\+)" | grep -v "$(git_extra_default_branch)" | grep -v svn)
if [ -n "$branches" ]
then
echo "$branches" | xargs git branch -d
fi

View file

@ -1,28 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
proceed=false
if [[ $# -gt 0 && ("$1" == "--proceed" || "$1" == "-p") ]]; then
proceed=true
shift
fi
if [[ $# -eq 0 ]]; then
targetBranch=$(git rev-parse --abbrev-ref HEAD)
else
targetBranch=$1
git checkout "$targetBranch"
fi
git for-each-ref refs/heads/ "--format=%(refname:short)" | while read -r branch; do
mergeBase=$(git merge-base "$targetBranch" "$branch")
if [[ $(git cherry "$targetBranch" "$(git commit-tree "$(git rev-parse "$branch^{tree}")" -p "$mergeBase" -m _)") == "-"* ]]; then
if [[ $proceed == true ]]; then
git branch -D "$branch" || true
else
git branch -D "$branch"
fi
fi
done

View file

@ -1,50 +1,13 @@
#!/usr/bin/env bash #!/bin/sh
abort() { submodule=$1
error="$1" && shift
echo "ERROR: $*" 1>&2
test -z "$FORCE" && exit "$error"
}
# Don't abort on failures. This allows to cleanup after a previous failure. test -z $submodule && echo "submodule required" && exit 1
[ "$1" = '--force' ] && FORCE=1 && shift test ! -f .gitmodules && echo ".gitmodules file not found" && exit 2
test -z "$1" && abort 1 'Submodule required' NAME=$(echo $submodule | sed 's/\/$//g')
cd "$(git root)" || abort 5 'Cannot change to repository root' test -z $(git config --file=.gitmodules submodule.$NAME.url) && echo "submodule not found" && exit 3
test ! -f '.gitmodules' && abort 2 '.gitmodules file not found'
NAME="${1%/}" git config --remove-section submodule.$NAME
test -z "$(git config --file='.gitmodules' "submodule.$NAME.url")" \ git config --file=.gitmodules --remove-section submodule.$NAME
&& abort 3 'Submodule not found' git rm --cached $NAME
# 1. Handle the .git directory
# 1.a. Delete the relevant section from .git/config
git submodule deinit -f "$NAME" || abort 4 "Failed to deinitialize $NAME"
# 1.b. Delete empty submodule directory
git rm -f "$NAME"
# 2. Handle .gitmodules file
# 2.a. Delete the relevant line from .gitmodules
git config --file='.gitmodules' --remove-section "submodule.$NAME" 2>/dev/null || :
# 2.b and stage changes
git add '.gitmodules'
# 2.c. Delete empty .gitmodules
[ "$(wc -l '.gitmodules' | cut -d' ' -f1)" = '0' ] && git rm -f '.gitmodules'
# 3. Need to confirm and commit the changes for yourself
git_status_text="$(git submodule status 2>&1)"
git_status_exit=$?
if [ "$git_status_exit" -eq 0 ] \
&& printf '%s' "DUMMY$git_status_text" | grep -v "$NAME" > /dev/null; then
# grep fails when piping in an empty string, so we add a DUMMY prefix
echo "Successfully deleted $NAME."
else
abort 6 "Failed to delete $NAME with error:\n$git_status_text"
fi
printf '\n%s\n' '== git submodule status =='
printf '%s\n' "$git_status_text"
printf '%s\n' '=========================='
# shellcheck disable=SC2016
echo 'Confirm the output of `git submodule status` above (if any)' \
'and then commit the changes.'

View file

@ -1,28 +1,5 @@
#!/usr/bin/env bash #!/bin/sh
# Assert there is at least one tag provided tagname=$1
test -z "$1" && echo "tag required." 1>&2 && exit 1 test -z $tagname && echo "tag required." && exit 1
git tag -d $tagname && git push origin :refs/tags/$tagname
# Detect the default remote exists or not
default_remote=$(git config git-extras.default-remote)
if [[ -n "$default_remote" ]]; then
origin="$default_remote"
else
origin=origin
fi
# Concatenate all the tag references
local_tags=""
origin_refs=""
for tagname in "$@"
do
local_tags=$local_tags" $tagname"
origin_refs=$origin_refs" :refs/tags/$tagname"
done
# Delete all the tags
# shellcheck disable=SC2086
git tag -d $local_tags
# shellcheck disable=SC2086
git push "$origin" $origin_refs

View file

@ -1,15 +0,0 @@
#!/usr/bin/env bash
branch=$(git_extra_default_branch)
filter=ACM
if test $# -eq 1; then
branch=$1
else
if test $# -eq 2; then
branch=$1
filter=$2
fi
fi
git diff --name-only --diff-filter="$filter" "$branch"

View file

@ -1,234 +0,0 @@
#!/usr/bin/env bash
tmp=$(git_extra_mktemp)
above=0
# if the output won't be printed to tty, disable the color
test -t 1 && to_tty=true
color=
#
# print usage message
#
usage() {
echo 1>&2 "usage: git effort [--above <value>] [<path>...] [-- [<log options>...]]"
}
#
# get dates for the given <commit>
#
dates() {
eval "git log $args_to_git_log --pretty='format: %ad' --date=short -- \"$1\""
}
# tput, being quiet about unknown capabilities
tputq() {
tput "$@" 2>/dev/null
return 0
}
#
# hide cursor
#
hide_cursor() {
tputq civis
}
#
# show cursor, and remove temporary file
#
show_cursor_and_cleanup() {
tputq cnorm
tputq sgr0
rm "$tmp" > /dev/null 2>&1
exit 0
}
#
# get active days for the given <commit>
#
active_days() {
echo "$1" | sort -r | uniq | wc -l
}
#
# set 'color' based on the given <num>
#
color_for() {
if [ "$to_tty" = true ]; then
if [ "$1" -gt 200 ]; then color="$(tputq setaf 1)$(tputq bold)"
elif [ "$1" -gt 150 ]; then color="$(tputq setaf 1)" # red
elif [ "$1" -gt 125 ]; then color="$(tputq setaf 2)$(tputq bold)"
elif [ "$1" -gt 100 ]; then color="$(tputq setaf 2)" # green
elif [ "$1" -gt 75 ]; then color="$(tputq setaf 5)$(tputq bold)"
elif [ "$1" -gt 50 ]; then color="$(tputq setaf 5)" # purplish
elif [ "$1" -gt 25 ]; then color="$(tputq setaf 3)$(tputq bold)"
elif [ "$1" -gt 10 ]; then color="$(tputq setaf 3)" # yellow
else color="$(tputq sgr0)" # default color
fi
else
color=""
fi
}
#
# compute the effort of the given <path ...>
#
effort() {
path=$1
local commit_dates
local color reset_color commits len dot f_dot i msg active
reset_color=""
test "$to_tty" = true && reset_color="$(tputq sgr0)"
if ! commit_dates=$(dates "$path"); then
exit 255
fi
# Ensure it's not just an empty line
if [ -z "$(head -c 1 <<<"$commit_dates")" ]
then
exit 0
fi
commits=$(wc -l <<<"$commit_dates")
color='90'
# ignore <= --above
test "$commits" -le "$above" && exit 0
# commits
color_for $(( commits - above ))
len=${#path}
dot="."
f_dot="$path"
i=0 ; while test $i -lt $(( columns - len )) ; do
f_dot=$f_dot$dot
i=$((i+1))
done
msg=$(printf " ${color}%s %-10d" "$f_dot" "$commits")
# active days
active=$(active_days "$commit_dates")
color_for $(( active - above ))
msg="$msg $(printf "${color} %d${reset_color}\n" "$active")"
echo "$msg"
}
#
# print heading
#
heading() {
echo
printf " %-${columns}s %-10s %s\n" 'path' 'commits' 'active days'
echo
}
#
# output sorted results
#
sort_effort() {
clear
echo " "
heading
< "$tmp" sort -rn -k 2
}
#
# get processor count, default 8
#
procs() {
if ! (nproc --all || getconf NPROCESSORS_ONLN || getconf _NPROCESSORS_ONLN || grep -c processor /proc/cpuinfo) 2>/dev/null; then
echo 8
fi
}
declare -a paths=()
while [ "${#}" -ge 1 ] ; do
case "$1" in
--above)
shift
above=$1
;;
--)
shift
args_to_git_log=$(printf " %q" "${@:1}")
break
;;
--*)
usage
echo 1>&2 "error: unknown argument $1"
echo 1>&2 "error: if that argument was meant for git-log,"
echo 1>&2 "error: please put it after two dashes ( -- )."
exit 1
;;
*)
paths+=( "$1" )
;;
esac
shift
done
# Exit if above-value is not an int
if [ -z "${above##*[!0-9]*}" ] ; then
echo "error: argument to --above was not an integer" 1>&2
exit 1
fi
# remove empty quotes that appear when there are no arguments
args_to_git_log="${args_to_git_log#\ \'\'}"
export args_to_git_log
# [path ...]
if test "${#paths}" -eq 0; then
save_ifs=$IFS
IFS=$(echo -en "\n\b")
paths=($(git ls-files))
IFS=$save_ifs
unset save_ifs
fi
# set column width to match longest filename
max=0
for path in "${paths[@]}"; do
cur=${#path}
if [[ $max -lt $cur ]]; then
max=$cur
fi
done
columns=$(( max + 5 ))
export columns
# hide cursor
hide_cursor
trap show_cursor_and_cleanup INT
heading
# send paths to effort
nPaths=${#paths[@]}
nProcs=$(procs)
nJobs="\j"
for ((i=0; i<nPaths; ++i))
do
while (( ${nJobs@P} >= nProcs )); do
wait -n
done
effort "${paths[i]}" &
done|tee "$tmp"
# if more than one path, sort and print
test "$(wc -l "$tmp" | awk '{print $1}')" -gt 1 && sort_effort
echo
show_cursor_and_cleanup

View file

@ -1,36 +1,16 @@
#!/usr/bin/env bash #!/bin/sh
VERSION="7.6.0-dev" VERSION="0.5.0"
INSTALL_SCRIPT="https://raw.githubusercontent.com/tj/git-extras/main/install.sh"
update() { update() {
local bin="$(command -v git-extras)"
local prefix=${bin%/*/*}
local orig=$PWD local orig=$PWD
curl -s $INSTALL_SCRIPT | PREFIX="$prefix" bash /dev/stdin \
&& cd "$orig" \
&& echo "... updated git-extras $VERSION -> $(git extras --version)"
}
updateForWindows() {
local bin="$(command -v git-extras)"
local prefix=${bin%/*/*}
local orig=$PWD
# we need to clean up /tmp manually on windows
cd /tmp \ cd /tmp \
&& rm -rf ./git-extras \ && rm -fr ./git-extras \
&& echo "Setting up 'git-extras'...." \ && git clone --depth 1 http://github.com/visionmedia/git-extras.git \
&& git clone https://github.com/tj/git-extras.git &> /dev/null \
&& cd git-extras \ && cd git-extras \
&& git checkout \ && make install \
$(git describe --tags $(git rev-list --tags --max-count=1)) \ && cd $orig \
&> /dev/null \ && echo "... updated git-extras $VERSION -> $(git extras --version)"
&& ./install.cmd "$prefix" \
&& cd "$orig" \
&& echo "... updated git-extras $VERSION -> $(git extras --version)"
rm -rf /tmp/git-extras &> /dev/null
} }
case "$1" in case "$1" in
@ -38,17 +18,10 @@ case "$1" in
echo $VERSION && exit 0 echo $VERSION && exit 0
;; ;;
update) update)
platform=$(uname -s) update
if [ "${platform::9}" = "CYGWIN_NT" ] || \
[ "${platform::5}" = "MINGW" ] || \
[ "${platform::7}" = "MSYS_NT" ]
then
updateForWindows
else
update
fi
;; ;;
*) *)
git extras --help test "$INSTALL" = "y" && update && exit
man git-extras
;; ;;
esac esac

View file

@ -1,105 +1,12 @@
#!/usr/bin/env bash #!/bin/sh
branch_prefix=$(git config --get git-extras.feature.prefix) if test "$1" = "finish"; then
test -z $2 && echo "feature <name> required." && exit 1
if [ -z "$branch_prefix" ]; then branch=feature/$2
branch_prefix="feature" git merge --no-ff $branch && git delete-branch $branch
fi
branch_separator=$(git config --get git-extras.feature.separator)
if [ -z "$branch_separator" ]; then
branch_separator="/"
fi
merge_mode="--no-ff"
finish=false
declare -a argv
while test $# != 0
do
case $1 in
-a|--alias )
if [[ -n $2 ]] && [[ $2 != -- ]]
then
shift # shift -a|-alias
branch_prefix=$1
else
echo >&2 "option $1 requires a value"
exit 1
fi
;;
-r|--remote )
if [[ -n $2 ]] && [[ $2 != -- ]]
then
remote=$2
shift
else
remote="origin"
fi
;;
-s|--separator )
if [[ -n $2 ]] && [[ $2 != -- ]]
then
branch_separator=$2
shift
else
echo >&2 "option $1 requires a value"
exit 1
fi
;;
--squash )
merge_mode="--squash"
;;
--from )
start_point=$2
shift
;;
-- )
# terminate argument parsing
shift
argv+=("$@")
break
;;
finish )
finish=true
;;
* )
argv+=("$1")
;;
esac
shift
done
concatargs() {
str=$(IFS='-'; echo "$*")
branch="$branch_prefix$branch_separator$str"
}
test -z "${argv[0]}" && echo "$branch_prefix" "<name> required." 1>&2 && exit 1
concatargs "${argv[@]}"
if "${finish}"
then
git merge ${merge_mode} "$branch" && git delete-branch "$branch"
else else
if [[ -n $remote ]] && [[ -z $start_point ]] test -z $1 && echo "feature <name> required." && exit 1
then branch=feature/$1
git create-branch -r "$remote" "$branch" git checkout -b $branch &> /dev/null
fi git checkout $branch &> /dev/null
fi
if [[ -z $remote ]] && [[ -z $start_point ]]
then
git create-branch "$branch"
fi
if [[ -n $remote ]] && [[ -n $start_point ]]
then
git create-branch -r "$remote" --from "$start_point" "$branch"
fi
if [[ -z $remote ]] && [[ -n $start_point ]]
then
git create-branch --from "$start_point" "$branch"
fi
fi

View file

@ -1,109 +0,0 @@
#! /usr/bin/env bash
set -euo pipefail
_usage() {
echo "
Usage:
git-force-clone -b branch remote_url destination_path
Example:
git-force-clone -b master git@github.com:me/repo.git ./repo_dir
Provides the basic functionality of 'git clone', but if the destination git
repository already exists it will force-reset it to resemble a clone of the
remote.
Because it doesn't actually delete the directory, it is usually significantly
faster than the alternative of deleting the directory and cloning the
repository from scratch.
**CAUTION**: If the repository exists, this will destroy *all* local work:
changed files will be reset, local branches and other remotes will be removed.
OPTIONS:
-b, --branch The branch to pull from the remote
-h, --help Display this help message
"
}
_check() {
if [ -z "$1" ]; then
echo "Error: Missing ${2}"
_usage
exit 1
fi
}
main() {
while [[ -n "${1:-}" ]] && [[ "${1:0:1}" == "-" ]]; do
case $1 in
-b | --branch )
branch=${2:-}
shift
;;
-h | --help )
_usage
exit 0
;;
* )
echo "Error: Invalid option: $1" >>/dev/stderr
_usage
exit 1
;;
esac
shift
done
remote_url=${1:-}
destination_path=${2:-}
_check "${remote_url}" "remote_url"
_check "${destination_path}" "destination_path"
if [ -d "${destination_path}/.git" ]; then
(
cd "${destination_path}"
# Delete all remotes
for remote in $(git remote); do
git remote rm "${remote}"
done
# Add origin
git remote add origin "${remote_url}"
git fetch origin
# Set default branch
if [ -z "${branch:-}" ]; then
branch=$(LC_ALL=C git remote show origin | grep -oP '(?<=HEAD branch: )[^ ]+$')
git remote set-head origin "${branch}"
else
git remote set-head origin -a
fi
# Make sure current branch is clean
git clean -fd
git reset --hard HEAD
# Get on the desired branch
git checkout "${branch}"
git reset --hard "origin/${branch}"
# Delete all other branches
# shellcheck disable=SC2063
branches=$(git branch | grep -v '*' | xargs)
if [ -n "${branches}" ]; then
git branch -D "${branches}"
fi
)
elif [ -n "${branch:-}" ]; then
git clone -b "${branch}" "${remote_url}" "${destination_path}"
else
git clone "${remote_url}" "${destination_path}"
fi
}
main "$@"
exit 0

View file

@ -1,67 +0,0 @@
#!/usr/bin/env bash
abort() {
echo "$@"
exit 1
}
url="$1"
test -z "$url" && url=$(git remote get-url origin 2> /dev/null) && origin=true
# validate repo url
test -z "$url" && abort "github repo needs to be specified as an argument"
# validate user
echo "Enter your github username"
read -r user
[ -n "$user" ] || abort "git username required"
# personal access token
# config name is github-personal-access-token '_' is not allowed in git config
github_personal_access_token=$(git config --default "$GITHUB_TOKEN" git-extras.github-personal-access-token)
test -z "$github_personal_access_token" && abort "GITHUB_TOKEN, or git config git-extras.github-personal-access-token required"
# extract owner + project from repo url
project=${url##*/}
owner=${url%/"$project"}
project=${project%.git}
if [[ $owner == git@* ]]; then
owner=${owner##*:}
else
owner=${owner##*/}
fi
# validate
[[ -z "$project" || -z "$owner" ]] && abort "github repo needs to be specified as an argument"
# create fork
if ! curl -qsf \
-X POST \
-u "$user:$github_personal_access_token" \
-H "X-GitHub-OTP: $MFA_CODE" \
"https://api.github.com/repos/$owner/$project/forks"
then
abort "fork failed"
fi
echo "Add GitHub remote branch via SSH (you will be prompted to verify the server's credentials)? (y/n)"
read -r use_ssh
# Check if user has ssh configured with GitHub
if [ -n "$use_ssh" ] && ssh -T git@github.com 2>&1 | grep -qi 'success'; then
remote_prefix="git@github.com:"
else
remote_prefix="https://github.com/"
fi
if [ "$origin" = true ]; then
git remote rename origin upstream
git remote add origin "${remote_prefix}${user}/${project}.git"
git fetch origin
else
# clone forked repo into current dir
git clone "${remote_prefix}${user}/${project}.git" "$project"
# add reference to origin fork so can merge in upstream changes
cd "$project" || exit
git remote add upstream "${remote_prefix}${owner}/${project}.git"
git fetch upstream
fi

View file

@ -1,24 +1,9 @@
#!/usr/bin/env bash #!/bin/sh
branch=$1 branch=$1
test -z "$branch" && echo "branch required." 1>&2 && exit 1 test -z $branch && echo "branch required." && exit 1
changes=$(git status --porcelain) git symbolic-ref HEAD refs/heads/$branch
rm .git/index
clean() git clean -fdx
{
git symbolic-ref HEAD "refs/heads/$branch"
rm .git/index
git clean -fdx
}
if [ -n "$changes" ]; then
read -rp "All untracked changes will be lost. Continue [y/N]? " res
case $res in
[Yy]* ) ;;
* ) exit 0;;
esac
fi
clean

View file

@ -1,40 +0,0 @@
#!/usr/bin/env bash
_usage() {
printf '%s\n' "usage: ${0##*/} <url>
usage: ${0##*/} --help
Clone a repository in a particular directory."
}
if (( $# == 0 )); then
_usage
exit 0
fi
for arg; do
if [ "$arg" = '-h' ] || [ "$arg" = '--help' ]; then
_usage
exit 0
fi
done
url=$1
if ! shift; then
printf 'ERROR: Failed to shift' >&2
exit 1
fi
clone_path=$(git config --get git-extras.get.clone-path)
if [ -z "$clone_path" ]; then
printf 'ERROR: %s\n' "Git configuration key 'git-extras.get.clone-path' must be set to a directory to clone under" >&2
exit 1
fi
dirname=${url%/}
dirname=${dirname%.git}
dirname=${dirname##*/}
mkdir -p "$clone_path"
git clone "$url" "$clone_path/$dirname" "$@"

View file

@ -1,21 +1,12 @@
#!/usr/bin/env bash #!/bin/sh
echo 'setting up gh-pages' echo 'setting up gh-pages'
echo '-------------------' git symbolic-ref HEAD refs/heads/gh-pages \
&& rm .git/index \
echo 'Tell me your github account username: ' && git clean -fdx \
read -r username && echo 'My Page' > index.html \
&& git add . \
echo 'Now, tell me your repository name: ' && git commit -a -m 'Initial commit' \
read -r repository && git push origin gh-pages \
&& git fetch origin \
git stash \ && echo 'complete'
&& git checkout -b 'gh-pages' \
&& echo 'My Page' > index.html \
&& git add . \
&& git commit -a -m 'Initial commit' \
&& git remote add origin https://github.com/"$username"/"$repository".git \
&& git push -u origin gh-pages \
&& echo 'Complete' \
&& echo '-------------------' \
&& echo 'You can find your last changes in the stash!'

View file

@ -1,11 +1,10 @@
#!/usr/bin/env bash #!/bin/sh
src=$1 src=$1
dst=$2 dst=$2
test -z "$src" && echo "source branch required." 1>&2 && exit 1 test -z $src && echo "source branch required." && exit 1
test -z "$dst" && echo "destination branch required." 1>&2 && exit 1
git checkout "$dst" \ git checkout $dst \
&& git merge --no-ff "$src" \ && git merge --no-ff $src \
&& git branch -d "$src" && git branch -d $src

View file

@ -1,119 +0,0 @@
#!/usr/bin/env bash
for param in "$@"
do
case $param in
-h)
echo 'Usage: git-guilt [<options>] <since> <until>'
echo 'Calculates the change in blame between two revisions'
echo 'Example: git guilt HEAD~3 HEAD'
echo
echo 'Options:'
echo
echo ' -h, --help output usage information'
echo ' -e, --email display author emails instead of names'
echo ' -w, --ignore-whitespace ignore whitespace only changes when attributing blame'
echo ' -d, --debug output debug information'
exit 0
;;
-e|--email )
EMAIL='-e'
shift
;;
-w|--ignore-whitespace )
NOT_WHITESPACE='-w'
shift
;;
-d|--debug )
DEBUG=$(git_extra_mktemp)
shift
;;
esac
done
cd "$(git-root)" || exit # cd for git blame
MERGED_LOG=$(git_extra_mktemp)
if [[ $EMAIL == '-e' ]]
then
PATTERN='s/^author-mail <\(.*\)>/\1/p'
else
PATTERN='s/^author //p'
fi
for file in $(git diff --name-only "$@")
do
test -n "$DEBUG" && echo "git blame $file"
# $1 - since $2 - until
# shellcheck disable=SC2086
git blame $NOT_WHITESPACE --line-porcelain "$1" -- "$file" 2> /dev/null |
LC_ALL=C sed -n "$PATTERN" | sort | uniq -c | LC_ALL=C sed 's/^\(.\)/- \1/' >> "$MERGED_LOG"
# if $2 not given, use current commit as "until"
# shellcheck disable=SC2086
git blame $NOT_WHITESPACE --line-porcelain "${2-@}" -- "$file" 2> /dev/null |
LC_ALL=C sed -n "$PATTERN" | sort | uniq -c | LC_ALL=C sed 's/^\(.\)/+ \1/' >> "$MERGED_LOG"
done
DEBUG="$DEBUG" awk '
NR==1 {
# the index of $2 does not change in each line
name_start_at = index($0, $3)
}
/^\+/ {
contributors[substr($0, name_start_at)] += $2
}
/^-/ {
contributors[substr($0, name_start_at)] -= $2
}
END {
for (people in contributors) {
if (ENVIRON["DEBUG"]) {
printf("%d %s\n", contributors[people], people) >> ENVIRON["DEBUG"]
}
if (contributors[people] != 0) {
printf("%d %s\n", contributors[people], people)
}
}
}' "$MERGED_LOG" | sort -nr | # only gawk supports built-in sort function
while read -r line
do
people=${line#* }
num=${line%% *}
if [[ $num -gt 0 ]]
then
printf "%-29s \033[00;32m" "$people"
if [[ $num -ge 50 ]]
then
len=${#num}
for (( i = 0; i < 48 - len; i++ ))
do
printf "+"
done
printf "(%s)" "$num"
else
for (( i = 0; i < num; i++ ))
do
printf "+"
done
fi
else
printf "%-29s \033[00;31m" "$people"
if [[ $num -le -50 ]]
then
len=${#num}
for (( i = 0; i < 48 - len; i++ ))
do
printf "-"
done
printf "(%s)" "$num"
else
for (( i = 0; i > num; i-- ))
do
printf "-"
done
fi
fi
printf "\033[00m\n"
done
test -n "$DEBUG" && sort -nr "$DEBUG"

View file

@ -1,285 +1,33 @@
#!/usr/bin/env bash #!/bin/sh
GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) template=""
while getopts "t:" opt; do
case "$opt" in
t) template=$OPTARG;shift;shift;;
esac
done
# awk script that takes 2 input files. The first one is newline deilmeted if [ ! -z "$template" ]; then
# string of patterns to look for, second one is the file to remove them from. gitignoredir="`dirname $0`/../etc/gitignore"
# outfile has to be passed with -v upon call and is the file result is available_templates() {
# written to. find $gitignoredir -type f -name *.gitignore | xargs -i basename {} .gitignore | sort
read -r -d '' AWK_SCRIPT <<'EOF' }
# process the first "file" (newline split argument list) selected_template() {
NR==FNR { a[b++]=$0; next; } available_templates | grep $template
# process file }
{ count=$(selected_template | wc -w)
found=0; case "$count" in
for (i=0; i != b; i++) { 0) echo "The pattern '$template' does not match any available templates." && available_templates | less && exit 1;;
if (a[i] == $0) { found=1; } 1) find $gitignoredir -name $(selected_template).gitignore -exec cat {} \; >> .gitignore && echo "... added patterns from template '$(selected_template)'";;
} *) echo "Be more specific. $count matches found:" && selected_template | grep --color $template && exit 2;;
if (found == 1) { print "... removing '"$0"'"; } esac
else { print $0 > outfile } fi
}
EOF if test $# -eq 0; then
test -z $template && test -f .gitignore && cat .gitignore
else
for pattern in $@; do
show_contents() { echo $pattern >> .gitignore
local file="${2/#~/$HOME}" echo "... added '$pattern'"
if [ -f "$file" ]; then done
echo "$1 gitignore: $2" && cat "$file"
else
echo "There is no $1 .gitignore yet"
fi
}
cd_to_git_root() {
local error_level="$1"
if ! git rev-parse --git-dir &>/dev/null; then
if [ "$error_level" = '--warn' ]; then
echo "Warning: Not currently in a Git repository" >&2
elif [ "$error_level" = '--error' ]; then
echo "Error: Not currently in a Git repository" >&2
exit 1
fi
fi
local result=
if result=$(git rev-parse --show-toplevel 2>/dev/null); then
cd "$result" || exit
fi
}
global_ignore() {
if ! git config --global core.excludesFile 2>/dev/null; then
if [ -f "$HOME/.gitignore" ]; then
echo "$HOME/.gitignore"
else
echo "${XDG_CONFIG_HOME:-$HOME/.config}/git/ignore"
fi
fi
}
show_global() {
show_contents Global "$(global_ignore)"
}
add_global() {
local global_gitignore
global_gitignore="$(global_ignore)"
if [ -z "$global_gitignore" ]; then
echo "Can't find global .gitignore."
echo ""
echo "Use 'git config --global --add core.excludesfile ~/.gitignore-global' to set the path to your global gitignore file to '~/.gitignore-global'."
echo ""
else
add_patterns "$global_gitignore" "$@"
fi
}
show_local() {
cd_to_git_root --warn
show_contents Local .gitignore
}
add_local() {
cd_to_git_root --warn
add_patterns .gitignore "$@"
}
show_private() {
cd_to_git_root --error
show_contents Private "${GIT_DIR}/info/exclude"
}
add_private() {
cd_to_git_root --error
test -d "${GIT_DIR}/info" || mkdir -p "${GIT_DIR}/info"
add_patterns "${GIT_DIR}/info/exclude" "$@"
}
add_patterns() {
echo "Adding pattern(s) to: $1"
local file="${1/#~/$HOME}"
dir_name=$(dirname "$file")
if [ ! -d "$dir_name" ]; then
mkdir -p "$dir_name"
fi
if [ -s "$file" ]; then
# If the content of $file doesn't end with a newline, add one
test "$(tail -c 1 "$file")" != "" && echo "" >> "$file"
fi
for pattern in "${@:2}"; do
echo "... adding '$pattern'"
(test -f "$file" && test "$pattern" && grep -q -F -x -- "$pattern" "$file") || echo "$pattern" >> "$file"
done
}
edit_file() {
local file="${2/#~/$HOME}"
if [ -f "$file" ]; then
echo "Editing $1 gitignore: ($2)" && eval "$(git var GIT_EDITOR) $(printf '%q' "$file")"
echo "Done."
else
echo "There is no $1 .gitignore yet." >&2
exit 1
fi
}
_usage() {
cat << EOF
Usage: git ignore [OPTION]... [PATTERN]...
Modify or display local global or private gitignore files.
OPTIONs set context (local, global, private) and action (display, add,
remove, edit). In case of collision, last flag overwrites preceding ones.
Defaults are: local context, action is display (see man git ignore for
more details).
Context examples:
local: .../repo/.gitignore
private: .../repo/.git/info/exclude
global: \$HOME/.gitignore
PATTERNs are the gitignore patterns as specified in git documentation.
Options:
-l, --local Set context to local gitignore
-g, --global Set context to global gitignore
-p, --private Set context to private gitignore
-e, --edit Set action to edit
-r, --remove Set action to remove
-h, --help Print this message
-- End of options delimiter
EOF
}
remove_patterns() {
declare -a args
local tmpfile
args=( "${@:3}" )
local file="${2/#~/$HOME}"
if [ -f "$file" ]; then
tmpfile="$(mktemp -q "${TMPDIR:-/tmp}"/git-extras-ignore.XXXXXX 2>/dev/null)"
echo "Removing patterns from $1 gitignore: $2"
awk -v outfile="$tmpfile" "${AWK_SCRIPT}"\
<(printf '%s\n' "${args[@]}") "$file"
cat "$tmpfile" > "$file"
rm "$tmpfile"
else
echo "There is no $1 .gitignore yet"
exit 1
fi
}
# can have only 1 action and 1 level, if conflicting flags are passed
# latter ones overwrite former
# sanity of args not tested (e.g. there should be no args if -e is supplied)
if test $# -eq 0; then
show_global
echo "---------------------------------"
show_local
echo "---------------------------------"
show_private
else
# parse flags and args
level="none"
action="none"
declare -a args
while [ $# -gt 0 ]; do
case "$1" in
-h|--help)
_usage
exit 0
;;
-l|--local)
level="local"
;;
-g|--global)
level="global"
;;
-p|--private)
level="private"
;;
-e|--edit)
action="edit"
;;
-r|--remove)
action="remove"
;;
--)
shift
args+=("$@")
break
;;
*)
args+=("$1")
;;
esac
shift
done
# compatible with previous version
if [ "$action" = "none" ]; then
case "$level" in
local)
test ${#args[@]} -ne 0 && add_local "${args[@]}" && echo
show_local
;;
global)
test ${#args[@]} -ne 0 && add_global "${args[@]}" && echo
show_global
;;
private)
test ${#args[@]} -ne 0 && add_private "${args[@]}" && echo
show_private
;;
none)
add_local "${args[@]}"
# maybe show_local here too?
;;
esac
exit 0
# open file in editor
elif [ "$action" = "edit" ]; then
case "$level" in
local|none)
cd_to_git_root --warn
edit_file 'local' .gitignore && echo
;;
global)
global_gitignore="$(global_ignore)"
edit_file global "$global_gitignore" && echo
;;
private)
cd_to_git_root --error
test -d "${GIT_DIR}/info" || mkdir -p "${GIT_DIR}/info"
edit_file private "${GIT_DIR}/info/exclude" && echo
;;
esac
exit 0
# remove entries
else
if [ ${#args[@]} -eq 0 ]; then
echo "Nothing to remove."
exit 0
fi
case "$level" in
local|none)
cd_to_git_root --warn
remove_patterns 'local' .gitignore "${args[@]}" && echo
show_local
;;
global)
global_gitignore="$(global_ignore)"
remove_patterns global "$global_gitignore" "${args[@]}" && echo
show_global
;;
private)
cd_to_git_root --error
remove_patterns private "${GIT_DIR}/info/exclude" "${args[@]}" && echo
show_private
;;
esac
exit 0
fi
fi fi

View file

@ -1,151 +0,0 @@
#!/usr/bin/env bash
gitignore_io_url="https://www.toptal.com/developers/gitignore/api/"
default_path="$HOME/.gi_list"
if [[ -n "$XDG_CACHE_HOME" ]]; then
default_path="$XDG_CACHE_HOME/git-extras/gi_list"
mkdir -p "$XDG_CACHE_HOME/git-extras"
fi
update_gi_list() {
curl -L -s "${gitignore_io_url}/list" > "$default_path"
}
print_in_alphabetical_order() {
local first_character previous_first_character ignorable
local first=true
for ignorable in $(echo "$gi_list" | sort);
do
first_character=${ignorable:0:1}
if [[ $first_character = "$previous_first_character" ]]; then
printf " %s" "$ignorable"
elif [[ $first = true ]]; then
previous_first_character=$first_character
first=false
printf "%s" "$ignorable"
else
previous_first_character=$first_character
printf "\n%s" "$ignorable"
fi
done
echo
}
print_in_table_format() {
echo "$gi_list" | column
}
search() {
for type in $gi_list;
do
if [[ "$type" == *$1* ]]
then
echo "$type"
fi
done
}
print_last_modified_time() {
if ! gi_list_date=$(stat -c "%y" "$default_path" 2> /dev/null); then
if ! gi_list_date=$(stat -f "%t%Sm" "$default_path" 2> /dev/null); then
if ! gi_list_date=$(date -r "$default_path" +%s 2> /dev/null); then
gi_list_date=0
fi
fi
fi
echo "Last update time: $gi_list_date"
}
gi() {
curl -L -s $gitignore_io_url/"$1"
}
gi_replace() {
gi "$1" > .gitignore
}
gi_append() {
gi "$1" >> .gitignore
}
show_usage() {
echo "Usage:"
echo " git ignore-io <types>... Show gitignore template"
echo " [-a|--append] <types>... Append new .gitignore content to .gitignore under the current directory"
echo " [-r|--replace] <types>... Export new .gitignore to the current directory (The old one will be replaced)"
echo " [-l|--list-in-table] Print available types in table format"
echo " [-L|--list-alphabetically] Print available types in alphabetical order "
echo " [-s|--search] <word> Search word in available types"
echo " [-t|--show-update-time] Show the last modified time of $default_path (where the list of available types is stored)"
echo " [-u|--update-list] Update $default_path"
}
check_list_exist() {
if ! [ -f "$default_path" ]; then
echo "-----Initial gitignore.io list----"
update_gi_list
echo "-----Save to $default_path-----"
echo
fi
gi_list=$(tr "," "\n" < "$default_path" 2>/dev/null)
}
check_list_exist
if [[ $# -eq 0 ]]; then
show_usage
else
case $1 in
-a|--append|-r|--replace)
opt=$1
shift
if [[ $# -eq 0 ]]; then
echo "There should be at least one type"
echo
show_usage
exit
fi
gi_to_curl=$(echo "$@" | tr " " ",")
case $opt in
-a|--append)
gi_append "$gi_to_curl"
;;
-r|--replace)
gi_replace "$gi_to_curl"
;;
esac
exit
;;
-t|--show-update-time)
print_last_modified_time
;;
-u|--update-list)
update_gi_list
;;
-s|--search)
opt=$1
shift
if [[ $# -eq 0 ]]; then
show_usage
exit
fi
search "$1"
;;
-L|--list-alphabetically)
print_in_alphabetical_order
;;
-l|--list-in-table)
print_in_table_format
;;
-*)
echo No Such option
show_usage
;;
*)
gi_to_curl=$(echo "$@" | tr " " ",")
gi "$gi_to_curl"
;;
esac
fi

View file

@ -1,79 +0,0 @@
#!/usr/bin/env bash
GREEN="$(tput setaf 2)"
NORMAL="$(tput sgr0)"
if [ "$1" = "--color" ] || [ "$2" = "--color" ] || \
[ "$1" = "-c" ] || [ "$2" = "-c" ] ; then
COLOR_TITLE="$GREEN"
else
COLOR_TITLE="$NORMAL"
fi
HIDE_CONFIG=
if [ "$1" != "--no-config" ] && [ "$2" != "--no-config" ]; then
HIDE_CONFIG=1
fi
get_config() {
cmd_get_config="$(git config --get-all git-extras.info.config-grep)"
if [ -z "$cmd_get_config" ]; then
git config --list
else
eval "$cmd_get_config"
fi
}
most_recent_commit() {
cmd_get_log="$(git config --get-all git-extras.info.log)"
if [ -z "$cmd_get_log" ]; then
git log --max-count=1 --pretty=short
else
eval "$cmd_get_log"
fi
}
submodules() {
# short sha1
git submodule status | sed 's/\([^abcdef0-9]\{0,2\}\)\([abcdef0-9]\{7\}\)\([abcdef0-9]\{33\}\)\(.*\)/\1\2\4/'
}
local_branches() {
git branch
}
remote_branches() {
git branch -r
}
remote_urls() {
git remote -v
}
echon() {
echo "$@"
echo
}
echo
echon "${COLOR_TITLE}## Remote URLs:${NORMAL}"
echon "$(remote_urls)"
echon "${COLOR_TITLE}## Remote Branches:${NORMAL}"
echon "$(remote_branches)"
echon "${COLOR_TITLE}## Local Branches:${NORMAL}"
echon "$(local_branches)"
SUBMODULES_LOG=$(submodules)
if [ -n "$SUBMODULES_LOG" ]; then
echon "${COLOR_TITLE}## Submodule(s):${NORMAL}"
echon "$SUBMODULES_LOG"
fi
echon "${COLOR_TITLE}## Most Recent Commit:${NORMAL}"
echon "$(most_recent_commit)"
if [ -n "$HIDE_CONFIG" ]; then
echon "${COLOR_TITLE}## Configuration (.git/config):${NORMAL}"
echon "$(get_config)"
fi

View file

@ -1,3 +0,0 @@
#!/usr/bin/env bash
git log "@{upstream}..@" "$@"

View file

@ -1,5 +0,0 @@
#!/usr/bin/env bash
filename="$1"
test -z "$filename" && echo "filename required." 1>&2 && exit 1
git update-index --skip-worktree "$filename"

View file

@ -1,3 +0,0 @@
#!/usr/bin/env bash
git ls-files -v | grep ^S | sed -e 's|S ||'

View file

@ -1,87 +0,0 @@
#!/usr/bin/env bash
# Bash unofficial strict mode
set -eo pipefail
IFS=$'\n\t'
cd "$(git rev-parse --show-toplevel)"
USAGE='git-magic [-a] [-m msg] [-e] [-p] [-f]'
ALL=false
PUSH=false
FORCE=''
ARGS=()
while getopts "m:eapfh" arg; do
case "${arg}" in
m)
ARGS+=("-m")
ARGS+=("${OPTARG}")
;;
e)
ARGS+=("-e")
;;
a)
ALL=true
;;
p)
PUSH=true
;;
f)
FORCE='--force-with-lease'
;;
h)
echo "$USAGE"
exit 0
;;
?)
echo "${USAGE}"
exit 1
;;
esac
done
shift $((OPTIND-1))
if [[ $# != 0 ]]; then
echo "Unknown arguments: $*"
echo "${USAGE}"
exit 1
fi
set -- "${ARGS[@]}" # restore positional parameters
if [[ $ALL == true ]]; then
# Check if there is no changes to stage
if [[ -z $(git status --porcelain) ]]; then
echo "No changes to commit"
exit 0
fi
# Get confirmation from user
git status
echo "Everything will be added"
read -rp "Press enter to continue"
# Restore staging area so that, for example,
# add and modify will not be separate entries in status
git restore --staged . || true
git add .
fi
# Commit with generated message
git commit --no-edit "$@" -m "$(git status --porcelain -uno)"
if [[ $PUSH == true ]]; then
git push $FORCE
fi
# --no-edit by default. use option -e to override this
# Arguments are passed with quoted "$@" to avoid misparsing
# Generated message comes after user provided arguments
# so that user can insert title before it

View file

@ -1,48 +0,0 @@
#!/usr/bin/env bash
current_branch() {
git rev-parse --abbrev-ref HEAD
}
usage() {
echo "Usage: git merge-into [src] dest [--ff-only]"
}
cur_branch=$(current_branch)
stashed=0
if [ -n "$(git status --porcelain)" ];
then
echo "Local modifications detected, stashing"
stashed=1
git stash
fi
if [ "${!#}" = '--ff-only' ]; then
case $# in
2 ) # dest --ff
git push "$(git rev-parse --show-toplevel)" "$cur_branch":"$1";;
3 )
git push "$(git rev-parse --show-toplevel)" "$1":"$2";;
* )
usage
esac
else
case $# in
1 )
git checkout "$1"
git merge "$cur_branch" "$1" && git checkout "$cur_branch"
;;
2 )
git checkout "$2"
git merge "$1" "$2" && git checkout "$cur_branch"
;;
* )
usage
esac
fi
if [ $stashed -eq 1 ];
then
git stash pop;
fi

View file

@ -1,25 +0,0 @@
#!/usr/bin/env bash
repo=$1
branch=$2
prefix=$3
flat=0
if test "$prefix" = "."; then
prefix=$(mktemp -u 'git-merge-repo.XXXXXXX')
flat=1
fi
git subtree add -P "$prefix" "$repo" "$branch" "${@:4}"
message=$(git log -1 --pretty=%B)
if test $flat -eq 1; then
git stash -u
mv -i "$prefix"/* ./
git undo
git add .
git commit -am "$message"
git stash apply
rm -drf "$prefix"
fi

View file

@ -1,52 +0,0 @@
#!/usr/bin/env bash
usage() {
echo 1>&2 "usage: git missing [<first branch>] <second branch> [<git log options>] [[--] <path>...]"
}
if [ "${#}" -lt 1 ]
then
usage
exit 1
fi
declare -a git_log_args=()
declare -a branches=()
declare -a pathspec=()
declare parse_path=false
for arg in "$@" ; do
if [[ $parse_path == true ]]; then
pathspec+=("$@")
break
fi
case "$arg" in
--)
parse_path=true
;;
--*)
git_log_args+=( "$arg" )
;;
*)
branches+=( "$arg" )
;;
esac
done
firstbranch=
secondbranch=
if [ ${#branches[@]} -eq 2 ]
then
firstbranch="${branches[0]}"
secondbranch="${branches[1]}"
elif [ ${#branches[@]} -eq 1 ]
then
secondbranch="${branches[0]}"
else
echo >&2 "error: at least one branch required"
exit 1
fi
git log "${git_log_args[@]}" "$firstbranch"..."$secondbranch" --format="%m %h %s" --left-right -- "${pathspec[@]}"

View file

@ -1,36 +0,0 @@
#!/usr/bin/env bash
set -eu
if [ -z "${1-}" ] ; then
echo "mr number or URL required. See --help for usage." 1>&2
exit 1
fi
remote_ref_kind=merge-requests
if test "$1" = "clean"; then
git for-each-ref refs/heads/mr/* --format='%(refname)' | while read -r ref; do
git branch -D "${ref#refs/heads/}"
done
exit 0
elif [[ $1 =~ ^(https?://[^/]+/(.+))/merge_requests/([0-9]+).*$ ]]; then
remote=${BASH_REMATCH[1]}.git
id=${BASH_REMATCH[3]}
elif [[ $1 =~ ^(https?://[^/]+/(.+))/pulls/([0-9]+).*$ ]]; then
# Forgejo/Codeberg pull request URL, e.g.
# https://codeberg.org/owner/repository/pulls/453
remote=${BASH_REMATCH[1]}.git
id=${BASH_REMATCH[3]}
remote_ref_kind=pull
else
id=$1
remote=${2:-origin}
fi
branch=mr/$id
remote_ref=refs/$remote_ref_kind/$id/head
git fetch -fu "$remote" "$remote_ref:$branch"
git checkout "$branch"
git config --local --replace "branch.$branch.merge" "$remote_ref"
git config --local --replace "branch.$branch.remote" "$remote"

View file

@ -1,23 +0,0 @@
#!/usr/bin/env bash
file=""
range=""
for i in "$@"
do
# use '--' to separate file list and rev-list arguments
test "$i" == '--' && shift && break
file="$file"' '"$i"
shift
done
test -n "$*" && range=("$@")
test -z "$file" && echo "file required." 1>&2 && exit 1
if [ -z "${range[*]}" ]
then
git filter-branch -f --index-filter "git rm -r --cached ""$file"" --ignore-unmatch" \
--prune-empty --tag-name-filter cat -- --all
else
# $range is an array so that we can forward multiple rev-list arguments
git filter-branch -f --index-filter "git rm -r --cached ""$file"" --ignore-unmatch" \
--prune-empty --tag-name-filter cat -- "${range[@]}"
fi

View file

@ -1,11 +0,0 @@
#!/usr/bin/env bash
set -e
set -o pipefail
if ! command -v pastebinit &>/dev/null; then
echo >&2 "To run 'git paste', you need to install pastebinit in your system"
exit 1
fi
test $# -ne 0 || set -- '@{u}'
git format-patch --stdout "$@" | pastebinit -f diff

View file

@ -1,77 +0,0 @@
#!/usr/bin/env bash
# Based on https://gist.github.com/gnarf/5406589 and https://gist.github.com/jhnns/d654d9d6da6d3b749986
TO_MERGE=
pull() {
local remote="$1"
local id="$2"
local branch="$3"
local ref="refs/pull/$id/head"
if [ -n "$TO_MERGE" ]; then
ref="refs/pull/$id/merge"
fi
git fetch -fu "$remote" "$ref:$branch" && \
git checkout "$branch" && \
git config --local --replace "branch.$branch.merge" "$ref" && \
git config --local --replace "branch.$branch.remote" "$remote"
}
pull_pr_if_matched() {
if [[ $1 =~ ^(.*):([0-9]+)|(https?://[^/]+/.+)/pull/([0-9]+).*$ ]]; then
if [[ -n ${BASH_REMATCH[2]} ]]; then
remote="${BASH_REMATCH[1]:-origin}"
id="${BASH_REMATCH[2]}"
else
remote="${BASH_REMATCH[3]}.git"
id="${BASH_REMATCH[4]}"
fi
branch=pr/$id
pull "$remote" "$id" "$branch"
return $?
fi
echo "$1 doesn't match the pr id pattern."
return 1
}
for arg in "$@"; do
case "$arg" in
-m|--merge)
TO_MERGE=1
;;
*)
# set the argument back
set -- "$@" "$arg"
;;
esac
shift
done
test -z "$1" && echo "pr number required." 1>&2 && exit 1
if test "$1" = "clean"; then
git for-each-ref refs/heads/pr/* --format='%(refname)' | while read -r ref; do
git branch -D "${ref#refs/heads/}"
done
elif [[ "$1" =~ ^[0-9]+$ ]]; then
remote_pref=${2:-$(git config --get git-extras.pr.remote)}
remote=${remote_pref:-origin}
id=$1
branch=pr/$id
pull "$remote" "$id" "$branch"
else
rc=1
while [ "$1" != "" ]; do
pull_pr_if_matched "$1"
rc=$?
shift
done
exit "$rc"
fi

View file

@ -1,83 +0,0 @@
#!/usr/bin/env bash
PROGRAM=$0
PRIMARY_BRANCH=""
SECONDARY_BRANCH=""
FF="--ff"
CONTINUE="no"
current_branch() {
git rev-parse --abbrev-ref HEAD
}
function usage()
{
echo "USAGE: ${PROGRAM} PRIMARY_BRANCH [SECONDARY_BRANCH] [--no-ff]"
echo "USAGE: ${PROGRAM} --continue"
echo ""
echo "OPTIONS:"
echo " --no-ff: Force rebase commit."
echo " -c|--continue: Continue after the user updates conflicts."
}
while [[ $# -gt 0 ]]
do
key="$1"
case $key in
--no-ff)
FF="--no-ff"
;;
-c|--continue)
CONTINUE=yes
;;
*)
if [[ "$PRIMARY_BRANCH" == "" ]]; then
PRIMARY_BRANCH=$key
elif [[ "$SECONDARY_BRANCH" == "" ]]; then
SECONDARY_BRANCH=$key
else
usage
exit 20 # Error during arguments parsing
fi
;;
esac
shift # past argument or value
done
if [[ "$SECONDARY_BRANCH" == "" ]]; then
SECONDARY_BRANCH=$(current_branch)
fi
if [[ "$CONTINUE" == "yes" ]]; then
TARGET_BRANCH=$(current_branch)
SECONDARY_BRANCH=${TARGET_BRANCH%"-rebased-on-top-of-"*}
PRIMARY_BRANCH=${TARGET_BRANCH#*"-rebased-on-top-of-"}
if [[ "${SECONDARY_BRANCH}-rebased-on-top-of-${PRIMARY_BRANCH}" != $TARGET_BRANCH ]]; then
echo "Couldn't continue rebasing on ${TARGET_BRANCH}"
exit 30 # Impossible to detect PRIMARY_BRANCH AND SECONDARY_BRANCH
fi
echo "Continuing rebasing of $SECONDARY_BRANCH on top of $PRIMARY_BRANCH"
git commit || exit 51
git branch -d "${SECONDARY_BRANCH}" || exit 52
git branch -m "${TARGET_BRANCH}" "${SECONDARY_BRANCH}" || exit 53
elif [[ "$PRIMARY_BRANCH" == "" ]]; then
usage
exit 10 # Missing arguments PRIMARY_BRANCH
else
echo "Rebasing $SECONDARY_BRANCH on top of $PRIMARY_BRANCH"
TARGET_BRANCH="${SECONDARY_BRANCH}-rebased-on-top-of-${PRIMARY_BRANCH}"
git checkout "${PRIMARY_BRANCH}" || exit 41
git checkout -b "${TARGET_BRANCH}" || exit 42
if git merge "${SECONDARY_BRANCH}" ${FF} \
-m "Psycho-rebased branch ${SECONDARY_BRANCH} on top of ${PRIMARY_BRANCH}"; then
git branch -d "${SECONDARY_BRANCH}" || exit 43
git branch -m "${TARGET_BRANCH}" "${SECONDARY_BRANCH}" || exit 44
else
echo "Resolve the conflict and run ``${PROGRAM} --continue``."
exit 1
fi
fi

View file

@ -1,85 +1,15 @@
#!/usr/bin/env bash #!/bin/sh
# request=$1
# Echo <msg> and exit repo=${PWD##*/}
# user=`git config github.user`
url="https://github.com/$user/$repo/pull/$request.patch"
abort() { test -z "$request" && echo "pull request number required" && exit 1
echo >&2 "$@" test -z "$repo" && echo "failed to detect repo" && exit 2
exit 1 test -z "$user" && echo "failed to fetch github.user" && exit 3
}
# echo "pulling $url"
# Produce json with <title>, <body>, <head> and <base> curl -# $url \
# | git am --signoff \
&& echo "completed pull request $request"
json() {
local title="${1//\"/\\\"}"
local body="${2//\"/\\\"}"
local head="${3//\"/\\\"}"
local base="${4//\"/\\\"}"
cat <<EOF
{
"title": "$title",
"body": "$body",
"head": "$head",
"base": "$base"
}
EOF
}
# personal access token
# config name is github-personal-access-token '_' is not allowed in git config
github_personal_access_token=$(git config --default "$GITHUB_TOKEN" git-extras.github-personal-access-token)
test -z "$github_personal_access_token" && abort "GITHUB_TOKEN or git config git-extras.github-personal-access-token required"
# branch
branch=${1-$(git symbolic-ref HEAD | sed 's/refs\/heads\///')}
remote=$(git config branch."$branch".remote)
if [ -z "$remote" ]; then
echo 'no upstream found, push to origin as default'
remote="origin"
fi
[ "$remote" = "." ] && abort "the upstream should be a remote branch."
# make sure it's pushed
git push "$remote" "$branch" || abort "failed to push $branch"
remote_url=$(git config remote."$remote".url)
if [[ "$remote_url" == git@* ]]; then
project=${remote_url##*:}
else
project=${remote_url#https://*/}
fi
project=${project%.git}
# prompt
echo
echo " create pull-request for $project '$branch'"
echo
printf " title: " && read -r title
printf " body: " && read -r body
printf " base [%s]: " "$(git_extra_default_branch)" && read -r base
printf " GitHub two-factor authentication code (leave blank if not set up): " && read -r mfa_code
echo
# create pull request
if [ -z "$base" ]
then
base="$(git_extra_default_branch)"
fi
body=$(json "$title" "$body" "$branch" "$base")
curl \
-X POST \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token $github_personal_access_token" \
-H "X-GitHub-OTP: $mfa_code" \
"https://api.github.com/repos/$project/pulls" -d "$body"

View file

@ -1,161 +0,0 @@
#!/usr/bin/env bash
init_variables() {
COMMAND=${0#*-}
CONFIG=false
ALL=false
unset OLD_EMAIL
unset CORRECT_EMAIL
unset CORRECT_NAME
TYPE='both'
}
usage() {
cat << EOF
usage: git ${COMMAND} [<options>]
Options
-a, --all rewrite all identities in commits and tags
-c, --use-config define correct values from user Git config
-e, --correct-email <email> define the correct email to set
-n, --correct-name <name> define the correct name to set
-o, --old-email <email> rewrite identities matching old email in commits and tags
-t, --type <id> define the type of identities affected by the rewrite
author, committer, both (default)
EOF
}
error() {
if [[ -n "$1" ]]; then
local msg=$( echo "error: $1" | sed 's/\\n/\\n /g' )
echo -e "${msg}" >&2
fi
usage
exit 1
}
reauthor() {
local author='
if ${ALL} || [ "${GIT_AUTHOR_EMAIL}" = "${OLD_EMAIL}" ]; then
[ -z "${CORRECT_EMAIL+x}" ] || export GIT_AUTHOR_EMAIL="${CORRECT_EMAIL}"
[ -z "${CORRECT_NAME+x}" ] || export GIT_AUTHOR_NAME="${CORRECT_NAME}"
fi
'
local committer='
if ${ALL} || [ "${GIT_COMMITTER_EMAIL}" = "${OLD_EMAIL}" ]; then
[ -z "${CORRECT_EMAIL+x}" ] || export GIT_COMMITTER_EMAIL="${CORRECT_EMAIL}"
[ -z "${CORRECT_NAME+x}" ] || export GIT_COMMITTER_NAME="${CORRECT_NAME}"
fi
'
local filter
case "${TYPE}" in
author) filter="${author}" ;;
committer) filter="${committer}" ;;
both) filter="${author} ${committer}" ;;
esac
export ALL
export OLD_EMAIL
export CORRECT_EMAIL
export CORRECT_NAME
git filter-branch --force --env-filter "${filter}" \
--tag-name-filter cat -- --branches --tags
}
parse_options() {
while [[ "$#" -gt 0 ]]; do
case "$1" in
--all|-a)
ALL=true
shift
;;
--correct-email|-e)
[[ -n "${2+x}" ]] || error 'Missing correct-email value'
CORRECT_EMAIL="$2"
shift 2
;;
-h)
usage
exit 0
;;
--correct-name|-n)
[[ -n "${2+x}" ]] || error 'Missing correct-name value'
CORRECT_NAME="$2"
shift 2
;;
--old-email|-o)
[[ -n "${2+x}" ]] || error 'Missing old-email value'
OLD_EMAIL="$2"
shift 2
;;
--type|-t)
[[ -n "${2+x}" ]] || error 'Missing type value'
TYPE="$2"
shift 2
;;
--use-config|-c)
CONFIG=true
shift
;;
*)
error "invalid option '$1'"
;;
esac
done
if ${CONFIG}; then
# use config values if not explicitly already defined
[[ -n "${CORRECT_EMAIL+x}" ]] || CORRECT_EMAIL=$( git config user.email )
[[ -n "${CORRECT_NAME+x}" ]] || CORRECT_NAME=$( git config user.name )
fi
}
validate_options() {
# Either OLD_EMAIL should be set or ALL should be true
if [[ -z "${OLD_EMAIL+x}" ]] && ! ${ALL}; then
msg="missing target of the rewrite"
msg="${msg}\nuse either --old-email option or --all flag"
error "${msg}"
fi
# OLD_EMAIL shouldn't be set if ALL is true as well to prevent misuse
if [[ -n "${OLD_EMAIL+x}" ]] && ${ALL}; then
msg="ambiguous target of the rewrite"
msg="${msg}\nuse either --old-email option or --all flag"
error "${msg}"
fi
# CORRECT_NAME should be either unset or set to non-empty string
[[ -n "${CORRECT_NAME-x}" ]] || error "empty name is not allowed"
# Either CORRECT_EMAIL or CORRECT_NAME should be set
if [[ -z "${CORRECT_EMAIL+x}" ]] && [[ -z "${CORRECT_NAME+x}" ]]; then
msg="missing correct email and/or name to set"
msg="${msg}\nuse --correct-email and/or --correct-name options"
msg="${msg}\nor --use-config flag with user values set in Git config"
error "${msg}"
fi
# TYPE should be a valid identifier
if [[ "${TYPE}" != 'both' ]] \
&& [[ "${TYPE}" != 'author' ]] \
&& [[ "${TYPE}" != 'committer' ]]; then
error "invalid type '${TYPE}'"
fi
}
init_variables
parse_options "$@"
validate_options
reauthor

View file

@ -1,63 +0,0 @@
#!/usr/bin/env bash
# Warn on a dirty work tree.
git rev-parse --verify HEAD >/dev/null || exit 1
git update-index -q --ignore-submodules --refresh
if ! git diff-files --quiet --ignore-submodules
then
echo "WARNING (dirty work tree): The patch will only be checked against actual commits."
fi
# Warn on a dirty index.
if ! git diff-index --cached --quiet --ignore-submodules HEAD --
then
echo "WARNING (dirty index): The patch will only be checked against actual commits."
fi
# Use a temporary index.
index=$(git_extra_mktemp)
cleanup() {
rm "$index"
exit 2
}
trap cleanup INT
# Go back in history while parent commits are available.
echo "Trying to find a commit the patch applies to..."
rev=$(git rev-parse HEAD)
while [ $? = 0 ]
do
GIT_INDEX_FILE=$index git read-tree "$rev"
# Try to apply the patch.
GIT_INDEX_FILE=$index git apply --cached "$1" &>/dev/null
patch_failed=$?
# Do it again, but show the error, if the problem is the patch itself.
if [ $patch_failed = 128 ]
then
GIT_INDEX_FILE=$index git apply --index --check "$1"
exit $patch_failed
fi
# The patch applied. Commit and rebase.
if [ $patch_failed = 0 ]
then
# Manufacture a commit.
tree=$(GIT_INDEX_FILE=$index git write-tree)
commit=$(git commit-tree "$tree" -p "$rev" -m "$1")
rm "$index"
echo "Patch applied to $(git rev-parse --short "$rev") as $(git rev-parse --short "$commit")"
git cherry-pick "$commit"
exit $?
fi
rev=$(git rev-parse --verify -q "$rev^")
done
# No compatible commit found. Restore.
echo "Failed to find a commit the patch applies to."
rm "$index"
exit 1

12
bin/git-refactor Executable file
View file

@ -0,0 +1,12 @@
#!/bin/sh
if test "$1" = "finish"; then
test -z $2 && echo "refactor <name> required." && exit 1
branch=refactor/$2
git merge --no-ff $branch && git delete-branch $branch
else
test -z $1 && echo "refactor <name> required." && exit 1
branch=refactor/$1
git checkout -b $branch &> /dev/null
git checkout $branch &> /dev/null
fi

View file

@ -1,138 +1,12 @@
#!/usr/bin/env bash #!/usr/bin/env sh
set -e
hook() {
local hook=.git/hooks/$1.sh
# compat without extname
if test ! -f "$hook"; then
hook=.git/hooks/$1
fi
if test -f "$hook"; then
echo "... $1"
shift
if test -x "$hook"; then
$hook "$@"
else
. "$hook" "$@"
fi
fi
}
exit_with_msg() {
>&2 echo "$1"
exit 1
}
if test $# -gt 0; then if test $# -gt 0; then
remote='' echo "... releasing $1"
git commit -a -m "Release $1" \
# check for flags && git tag $1 \
while test $# != 0 && git push \
do && git push --tags \
case "$1" in
-c) need_changelog=true;;
-r) remote=$2; shift ;;
-m) msg=$2; shift ;;
-s)
test -n "$keyid" &&
exit_with_msg "Please use '-s' OR '-u'"
sign=true
;;
-u)
test -n "$sign" &&
exit_with_msg "Please use '-s' OR '-u'"
keyid=$2
shift
;;
--semver)
test -z "$2" &&
exit_with_msg "major/minor/patch required for --semver option"
semver=$2
shift
;;
--prefix)
test -z "$2" &&
exit_with_msg "prefix string required for --prefix option"
prefix="$2"
shift
;;
--no-empty-commit) no_empty_commit=true;;
--) shift; hook_args="$hook_args $*"; break;;
*) test -z "$version" && version=$1 ;;
esac
shift
done
if [ -n "$semver" ]; then
if [ -z "$(git tag)" ]; then
echo "there is no tag in the git repo" 1>&2
exit 1
fi
latest_tag=$(git describe --tags "$(git rev-list --tags --max-count=1)")
if [[ ! "$latest_tag" =~ \
^$prefix([^0-9]*)([1-9][0-9]+|[0-9])\.([1-9][0-9]+|[0-9])\.([1-9][0-9]+|[0-9])(.*) ]]; then
echo "the latest tag doesn't match semver format requirement" 1>&2
exit 1
fi
case "$semver" in
major ) version="${BASH_REMATCH[2]}" ;;
minor ) version="${BASH_REMATCH[3]}" ;;
patch ) version="${BASH_REMATCH[4]}" ;;
* ) echo "invalid semver argument given: $semver" 1>&2
exit 1
;;
esac
(( ++version ))
case "$semver" in
major ) version="$prefix${BASH_REMATCH[1]}$version.0.0${BASH_REMATCH[5]}" ;;
minor ) version="$prefix${BASH_REMATCH[1]}${BASH_REMATCH[2]}.$version.0${BASH_REMATCH[5]}" ;;
patch ) version="$prefix${BASH_REMATCH[1]}${BASH_REMATCH[2]}.${BASH_REMATCH[3]}.$version${BASH_REMATCH[5]}" ;;
esac
fi
hook_args="$version"
if [ -z "$msg" ]; then
msg="Release ${version}"
fi
# shellcheck disable=SC2086
hook pre-release $hook_args \
|| exit_with_msg "pre-release hook failed! Cancelling release."
echo "... releasing $version"
if [ "$need_changelog" = true ]; then
git-changelog -t "$version"
fi
if [ "$no_empty_commit" = true ]; then
git commit -a -m "$msg" || true
else
git commit -a -m "$msg" --allow-empty
fi
declare -a sign_args
if [ "$sign" = true ]; then
sign_args=("-s")
fi
if [ -n "$keyid" ]; then
sign_args=("-u" "$keyid")
fi
# shellcheck disable=SC2086
git tag "${sign_args[@]}" $version -a -m "$msg" \
&& git push $remote --tags \
&& git push $remote \
&& hook post-release $hook_args \
&& echo "... complete" && echo "... complete"
else else
echo "tag required" 1>&2 && exit 1 echo "tag required" && exit 1
fi fi

View file

@ -1,23 +0,0 @@
#!/usr/bin/env bash
set -e
# Assert there is at least one branch provided
test -z "$1" && echo "new branch name required." 1>&2 && exit 1
if [ -z "$2" ]; then
new_branch="$1"
old_branch="$(git symbolic-ref --short -q HEAD)"
else
new_branch="$2"
old_branch="$1"
fi
remote=$(git config branch."$old_branch".remote; true)
git branch -m "$old_branch" "$new_branch"
# check if the branch is tracking a remote branch
if [[ -n "$remote" && "$remote" != "." ]]
then
git push "$remote" :"$old_branch"
git push --set-upstream "$remote" "$new_branch"
fi

View file

@ -1,101 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# Usage function
usage() {
cat <<EOF
Usage: git rename-file [OPTIONS] <source> <destination>
Description:
Rename a file or directory and ensure Git recognizes the change, regardless of filesystem case-sensitivity.
It combines the functionality of the "mv" command and "git mv". This is particularly useful for renaming files or directories
to change only their case, which might not be detected by Git on case-insensitive filesystems.
Options:
-h, --help Show this help message and exit.
Examples:
git rename-file old_filename new_filename
git rename-file old_directory new_directory
EOF
}
# Check for help option
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
usage
exit 0
fi
# Check for correct number of arguments
if [ "$#" -ne 2 ]; then
echo "Error: Incorrect number of arguments."
echo ""
usage >&2
exit 1
fi
# Assign variables
SOURCE="$1"
DESTINATION="$2"
TEMP_NAME="${SOURCE}.temp"
# Function to check if a file or directory exists in a case-sensitive manner
check_case_sensitive_exists() {
local path="$1"
local dir
local base
dir=$(dirname "$path")
base=$(basename "$path")
if [ -e "$dir" ]; then
if (cd "$dir" && find . -maxdepth 1 -name "$base" | grep -q "$base"); then
return 0
fi
fi
return 1
}
# Check if source exists and is under version control
if ! check_case_sensitive_exists "$SOURCE"; then
echo "Error: Source '$SOURCE' does not exist."
exit 1
fi
if ! git ls-files --error-unmatch "$SOURCE" > /dev/null 2>&1; then
echo "Error: Source '$SOURCE' is not under version control. If file or directory is new, it must at least be staged."
exit 1
fi
# Check if destination already exists
if check_case_sensitive_exists "$DESTINATION"; then
echo "Error: Destination '$DESTINATION' already exists."
exit 1
fi
# Check if the destination directory exists
DEST_DIR=$(dirname "$DESTINATION")
if ! check_case_sensitive_exists "$DEST_DIR"; then
echo "Error: Destination directory '$DEST_DIR' does not exist."
exit 1
fi
# Create a rollback function
rollback() {
echo "Rolling back changes..."
if [ -e "$TEMP_NAME" ]; then
git mv -f "$TEMP_NAME" "$SOURCE"
fi
}
# Trap errors to trigger rollback
trap 'rollback' ERR
# Move the file to a temporary name within the Git repository
git mv "$SOURCE" "$TEMP_NAME"
# Move the temporary file to the desired destination
git mv "$TEMP_NAME" "$DESTINATION"
echo "Successfully renamed '$SOURCE' to '$DESTINATION'."

View file

@ -1,20 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
old=$1
new=$2
test -z "$old" && echo "old remote name required." 1>&2 && exit 1
test -z "$new" && echo "new remote name required." 1>&2 && exit 1
if ! git config --get "remote.$old.fetch" > /dev/null; then
echo "remote $old doesn't exist"
exit 1
fi
if git config --get "remote.$new.fetch" > /dev/null; then
git remote remove "$new"
fi
git remote rename "$old $new"
git remote -v

View file

@ -1,12 +0,0 @@
#!/usr/bin/env bash
old=$1
new=$2
test -z "$old" && echo "old tag name required." 1>&2 && exit 1
test -z "$new" && echo "new tag name required." 1>&2 && exit 1
git tag "$new" "$old"
git tag -d "$old"
git push origin "$new"
git push origin ":refs/tags/$old"

View file

@ -1,99 +1,23 @@
#!/usr/bin/env bash #!/bin/sh
shopt -s extglob
git version
echo "git-extras version ""$(git-extras -v)"
echo "Type 'ls' to ls files below current directory; '!command' to execute any command or just 'subcommand' to execute any git subcommand; 'quit', 'exit', 'q', ^D, or ^C to exit the git repl."
HISTIGNORE=${HISTIGNORE:-+([[:space:]])}
HISTCONTROL=${HISTCONTROL:-ignoredups}
use_local_history=$(git config --get --default 'false' git-extras.repl.use-local-history)
if [[ "$use_local_history" == "true" ]]; then
HISTFILE="$(git rev-parse --show-toplevel)/.git_extras_repl_history"
else
HISTFILE=${XDG_STATE_HOME:-$HOME/.local/state}/git_extras_repl_history
fi
# file doesn't exist, is empty, or contains only whitespace
if [[ ! -f "$HISTFILE" ]] || [[ ! -s "$HISTFILE" ]] || ! grep -q '[^[:space:]]' "$HISTFILE"; then
# `history -r` .... `history -a` are not happy with an empty initial file in bash 3.2.57, which is what MacOS 26 ships with
echo '!echo welcome to git-repl!' >> "$HISTFILE"
fi
history -r
while true; do while true; do
# Current branch # Readline
cur=$(git symbolic-ref HEAD 2> /dev/null | cut -d/ -f3-) read -r -p "git> " cmd
# Prompt # EOF
if test -n "$cur"; then test $? -ne 0 && break
cur_string=" ($cur)"
else
cur_string=""
fi
if test -n "$exit_status" && test "$exit_status" -ne 0; then
es_string=$' \e[31m['"$exit_status"$']\e[0m'
else
es_string=""
fi
prompt_character=$(git config --get --default '>' git-extras.repl.prompt-character)
prefix=$(git config --get --default 'git' git-extras.repl.prefix) # History
history -s "$cmd"
show_project_name=$(git config --get --default 'false' git-extras.repl.show-project-name)
if [[ "$show_project_name" == "true" ]]; then
project_name=" $(basename "$(git rev-parse --show-toplevel)" .git)"
else
project_name=""
fi
prompt_base="$prefix$project_name$cur_string$es_string$prompt_character"
prompt_base_stripped=$(echo "$prompt_base" | awk '{$1=$1};1')
prompt="$prompt_base_stripped "
# Use arguments as a command if any are provided.
if [ $# -ne 0 ]; then
cmd=$*
set --
echo "$prompt" "$cmd" # It is as though you had entered a command.
else
# Readline
read -e -r -p "$prompt" cmd
# Check for EOF, and end the program if so (handles ^D).
test $? -ne 0 && break
fi
# Add command to history if it is not all whitespace
if [[ ! "$cmd" =~ ^[[:space:]]*$ ]]; then
history -s "$cmd"
fi
# Built-in commands # Built-in commands
case $cmd in case $cmd in
ls) cmd=ls-files;; ls) cmd=ls-files;;
"") quit) break;;
on_enter_cmd=$(git config --get --default '' git-extras.repl.on-enter-command)
if test -n "$on_enter_cmd"; then
cmd="$on_enter_cmd"
else
continue
fi
;;
quit|exit|q) break;;
esac esac
history -a # Execute
git $cmd
if [[ $cmd == !* ]]; then
# shellcheck disable=SC2086
eval ${cmd:1}
elif [[ $cmd == git* ]]; then
# shellcheck disable=SC2086
eval $cmd
else
eval git "$cmd"
fi
exit_status=$?
done done
echo echo

View file

@ -1,17 +0,0 @@
#!/usr/bin/env bash
file="$1"
commit="$2"
if [[ -f $file ]]; then
git rm --cached -q -f -- "$file"
if [[ -z $commit ]]; then
git checkout HEAD -- "$file"
echo "Reset '$1' to HEAD"
else
git checkout "$commit" -- "$file"
echo "Reset '$1' to $commit"
fi
else
echo "File '$1' not found in $PWD"
fi

View file

@ -1,30 +0,0 @@
#!/usr/bin/env bash
git_root() {
git rev-parse --show-toplevel
}
# get the relative path of current path according to root of repo
git_root_relative() {
rel=$(git rev-parse --show-prefix)
if [ -z "$rel" ]; then
# git rev-parse --show-prefix will output empty string when we are in the root dir
echo "."
else
echo "$rel"
fi
}
if test $# -eq 0; then
git_root
else
case "$1" in
-r|--relative)
git_root_relative
;;
*)
git_root
;;
esac
fi

View file

@ -1 +0,0 @@
git-scp

View file

@ -1,267 +0,0 @@
#!/usr/bin/env bash
COLOR_RED() { test -t 1 && echo -n "$(tput setaf 1)"; }
COLOR_GREEN() { test -t 1 && echo -n "$(tput setaf 2)"; }
COLOR_YELLOW(){ test -t 1 && echo -n "$(tput setaf 3)"; }
COLOR_BLUE() { test -t 1 && echo -n "$(tput setaf 4)"; }
COLOR_RESET() { test -t 1 && echo -n "$(tput sgr 0)"; }
function _test_git_scp()
{
command -v rsync > /dev/null || _error requires rsync
command -v git > /dev/null || _error requires git
command -v ssh > /dev/null || _error requires ssh
command -v php > /dev/null || _info optional php
command -v dos2unix > /dev/null || _info optional dos2unix
}
function set_remote()
{
remote=$1
if [ "$(git remote | grep -c -- "^$remote$")" -eq 0 ]
then
COLOR_RED
echo "Remote $remote does not exist in your git config"
COLOR_RESET
exit 1
fi
}
# Check file for PHP syntax errors
# takes a list of filenames
function php_lint()
{
local error_count=()
for i
do
# check if file exists
# check if file ends with ".php"
test ! -f "$i" && continue
case "$i" in
*\.php|*\.phtml)
if ! php -l "$i" > /dev/null; then
error_count[${#error_count[@]}]="$i"
fi
;;
esac
done
# syntax check fails, force exit
test ${#error_count[@]} -gt 0 &&
COLOR_RED &&
echo "Error: ${#error_count[@]} PHP syntax error found" &&
echo "${error_count[@]}" | tr " " '\n' &&
COLOR_RESET &&
exit 255
return 0
}
function _dos2unix()
{
command -v dos2unix > /dev/null && dos2unix "$@"
return 0
}
function _sanitize()
{
git config --get-all extras.scp.sanitize | while read -r i
do
case $i in
php_lint) php_lint "$@";; # git config --global --add extras.scp.sanitize php_lint
dos2unix) _dos2unix "$@";; # git config --global --add extras.scp.sanitize dos2unix
esac
done
return $?
}
function scp_and_stage
{
local verbose=0 interactive=0 dry_run=0
while :
do
case "$1" in
-v|--verbose) verbose=1; shift;;
-i|--interactive) verbose=1; interactive=1; shift;;
-n|--dry-run) verbose=1; dry_run=1; shift;;
*) break;;
esac
done
set_remote "$1"
shift
local refhead
refhead="$(git rev-parse --quiet --verify "$1")"
if [ -n "$refhead" ]
then
shift
[ "$(git branch --contains "$refhead" | grep -c '\*')" -eq 0 ] &&
_error "refhead provided is not part of current branch"
fi
if [ $# -ge 1 ]
then
list=$(git ls-files "$@")" "$(git ls-files -o "$@")
elif [ -n "$refhead" ]
then
[ "$verbose" -eq 1 ] && git --no-pager diff --stat "$refhead"
list=$(git diff "$refhead" --name-only)
else
[ "$verbose" -eq 1 ] && git --no-pager diff
list=$(git diff --name-only)
fi
deleted=$(for i in $list; do [ -f "$i" ] || echo "$i"; done)
list=$(for i in $list; do [ -f "$i" ] && echo "$i"; done)
if [ "$interactive" -eq 1 ] && [ "$dry_run" -eq 0 ] && { [ -n "$list" ] || [ -n "$deleted" ]; }
then
local reply
read -r -p "Proceed with sync to $remote? [y/N] " reply
case "$reply" in
y|Y|yes|YES) ;;
*) _info "Aborted, nothing synced."; exit 0;;
esac
fi
local status=0
if [ -n "$list" ]
then
local _TMP=${0///}
# shellcheck disable=SC2086
echo "$list" > "$_TMP"
if [ "$dry_run" -eq 1 ] || _sanitize $list
then
_info "Pushing to $remote ($(git config "remote.$remote.url"))"
if [ "$dry_run" -eq 1 ]
then
rsync -rlDvn --files-from="$_TMP" ./ "$(git config "remote.$remote.url")/"
else
rsync -rlDv --files-from="$_TMP" ./ "$(git config "remote.$remote.url")/" &&
git add --force $list
fi
status=$?
else
status=1
fi
rm "$_TMP"
fi
deleted=$(for i in $deleted; do echo "$(git config "remote.$remote.url" | cut -d: -f2)/$i"; done)
if [ -n "$deleted" ]
then
COLOR_RED
echo Deleted remote files
if [ "$dry_run" -eq 1 ]
then
echo "$deleted"
else
if ssh "$(git config "remote.$remote.url" | cut -d: -f1)" -t "rm $deleted"
then
echo "$deleted"
else
status=1
fi
fi
COLOR_RESET
fi
return "$status"
}
function reverse_scp()
{
local verbose=0 interactive=0 dry_run=0
while :
do
case "$1" in
-v|--verbose) verbose=1; shift;;
-i|--interactive) verbose=1; interactive=1; shift;;
-n|--dry-run) verbose=1; dry_run=1; shift;;
*) break;;
esac
done
set_remote "$1"
shift
local _TMP=${0///}
echo "$@" > "$_TMP"
local status=0
if [ "$verbose" -eq 1 ]
then
rsync -rlDvn --files-from="$_TMP" "$(git config "remote.$remote.url")/" ./
status=$?
fi
if [ "$dry_run" -eq 1 ]
then
rm "$_TMP"
return "$status"
fi
if [ "$interactive" -eq 1 ]
then
local reply
read -r -p "Proceed with copy from $remote? [y/N] " reply
case "$reply" in
y|Y|yes|YES) ;;
*) _info "Aborted, nothing copied."; rm "$_TMP"; exit 0;;
esac
fi
rsync -rlDv --files-from="$_TMP" "$(git config "remote.$remote.url")/" ./ &&
rm "$_TMP"
}
function _info()
{
COLOR_YELLOW
test $# -gt 0 && echo "$@"
COLOR_RESET
}
function _usage()
{
echo "Usage:
git scp -h|help|?
git scp [options] <remote> [ref|file..] # scp and stage your files to specified remote
git scp [options] <remote> [<ref>] # show diff relative to <ref> and upload unstaged files to <remote>
git rscp [options] <remote> [<file|directory>] # copy <remote> files to current working directory
OPTIONS:
-v, --verbose print what will be synced before syncing
-i, --interactive prompt for confirmation before syncing (implies --verbose)
-n, --dry-run show what would be synced, change nothing (implies --verbose; no staging, no remote writes/deletes)
"
case $1 in
-v|verbose|--verbose) grep -A100 '^#* OPTIONS #*$' "$0" ;;
esac
exit
}
function _error()
{
[ $# -eq 0 ] && _usage && exit 0
echo
echo "ERROR: $*"
echo
exit 1
}
### OPTIONS ###
case $(basename "$0") in
git-scp)
case $1 in
''|-h|'?'|help|--help) shift; _test_git_scp; _usage "$@";;
*) scp_and_stage "$@";;
esac
;;
git-rscp) reverse_scp "$@";;
esac

View file

@ -1,104 +0,0 @@
#!/usr/bin/env bash
usage() {
cat <<EOF
usage: git sed [ -c ] [ -f <flags> ] <search> <replacement> [ <flags> ]
Run git grep and then send results to sed for replacement with the
given flags, if they are provided via -f or as the third argument.
Also runs git commit if -c is provided.
EOF
}
# don't commit by default
do_commit() {
true
}
pathspec=
while [ "$1" != "" ]; do
case "$1" in
-c|--commit)
if git status --porcelain | grep .; then
echo "you need to commit your changes before running with --commit"
exit 1
fi
do_commit() {
git commit -m"replace $search with $replacement
actual command:
$command" -a
}
;;
-f|--flags)
if [ "$2" = "" ]; then
usage
echo "missing argument for $1"
exit 1
fi
shift
flags=$1
;;
-h|--help)
usage
exit
;;
--)
pathspec="$*"
break
;;
-*)
usage
echo "unknown flag: $1"
exit 1
;;
*)
if [ "$search" = "" ]; then
search="$1"
elif [ "$replacement" = "" ]; then
replacement="$1"
elif [ "$flags" = "" ]; then
flags="$1"
else
usage
echo "too many arguments: $1"
exit 1
fi
;;
esac
shift
done
all="$search$replacement$flags"
case "$all" in
*/*)
ascii="$(for((i=32;i<=127;i++)) do printf '%b' "\\$(printf '%03o' "$i")"; done)"
escaped="${all//-/\\-}"
escaped="${escaped//[/\\[}"
sep="$(printf '%s' "$ascii" | tr -d "$escaped")"
sep="$(printf %.1s "$sep")"
if [ "$sep" = "" ] ; then
echo 'could not find an unused character for sed separator character'
exit 1
fi
;;
*)
sep=/
;;
esac
r=$(xargs -r false < /dev/null > /dev/null 2>&1 && echo r)
need_bak=$(sed -i s/hello/world/ "$(git_extra_mktemp)" > /dev/null 2>&1 || echo true)
if [ "$need_bak" ]; then
command="git grep -lz '$search' $pathspec | xargs -0$r sed -i '' 's$sep$search$sep$replacement$sep$flags'"
# shellcheck disable=SC2086
git grep -lz "$search" $pathspec | xargs -0"$r" sed -i '' "s$sep$search$sep$replacement$sep$flags"
else
command="git grep -lz '$search' $pathspec | xargs -0$r sed -i 's$sep$search$sep$replacement$sep$flags'"
# shellcheck disable=SC2086
git grep -lz "$search" $pathspec | xargs -0"$r" sed -i "s$sep$search$sep$replacement$sep$flags"
fi
do_commit

View file

@ -1,23 +1,8 @@
#!/usr/bin/env bash #!/bin/sh
COMMIT_MESSAGE='Initial commit'
if [ "$1" = "-m" ]; then
COMMIT_MESSAGE=$2
shift; shift
fi
gitdirexists(){
if [ -d ".git" ]; then
echo ".git directory already exists, aborting"
exit 1
fi
}
dir=$(test -z "$*" && echo "." || echo "$*") dir=$(test -z "$*" && echo "." || echo "$*")
mkdir -p "$dir" \ mkdir -p "$dir" \
&& cd "$dir" \ && cd "$dir" \
&& gitdirexists \
&& git init \ && git init \
&& git add . \ && git add . \
&& git commit --allow-empty -m "$COMMIT_MESSAGE" && git commit -m 'Initial commit'

View file

@ -1,3 +0,0 @@
#!/usr/bin/env bash
git branch --no-color --merged | grep -v "\*" | grep -v "$(git_extra_default_branch)" | tr -d ' '

View file

@ -1,3 +0,0 @@
#!/usr/bin/env bash
git log --all --graph --decorate --oneline --simplify-by-decoration

View file

@ -1,3 +0,0 @@
#!/usr/bin/env bash
git branch --no-color --no-merged | grep -v "\*" | grep -v "$(git_extra_default_branch)" | tr -d ' '

View file

@ -1,80 +0,0 @@
#!/usr/bin/env bash
SQUASH_MSG=
for arg in "$@"; do
case "$arg" in
--squash-msg)
SQUASH_MSG=1
;;
*)
# set the argument back
set -- "$@" "$arg"
;;
esac
shift
done
src="$1"
msg="$2"
if [[ -n "$msg" ]] && [[ -n "$SQUASH_MSG" ]]; then
>&2 echo "When commit message is given, --squash-msg is not allowed."
exit 1
fi
is_branch() {
git show-ref --verify --quiet "refs/heads/$src"
}
is_commit_reference() {
git rev-parse --verify --quiet "$src" > /dev/null 2>&1
}
is_on_current_branch() {
local commit_sha
commit_sha=$(git rev-parse "$src")
git rev-list HEAD |
grep -q -- "$commit_sha"
}
commit_if_msg_provided() {
if test -n "$msg"; then
git commit -a -m "$msg"
fi
}
prompt_continuation_if_squashing_default_branch() {
if [[ $src == $(git_extra_default_branch) ]]; then
read -p "Warning: squashing '$src'! Continue [y/N]? " -r
if ! [[ $REPLY =~ ^[Yy]$ ]]; then
echo "Exiting"
exit 1
fi
fi
}
squash_branch() {
prompt_continuation_if_squashing_default_branch
if [ -n "$SQUASH_MSG" ]; then
base=$(git merge-base "$src" @)
msg=$(git log "$base".."$src" --format="%s%n%n%b" --no-merges --reverse)
fi
git merge --squash "$src" || exit 1 # quits if `git merge` fails
commit_if_msg_provided
}
squash_current_branch() {
if [ -n "$SQUASH_MSG" ]; then
msg=$(git log "$src"..@ --format="%s%n%n%b" --no-merges --reverse)
fi
git reset --soft "$src" || exit 1 # quits if `git reset` fails
commit_if_msg_provided
}
if is_branch; then
squash_branch
elif is_commit_reference && is_on_current_branch; then
squash_current_branch
else
echo "Source branch or commit reference required." 1>&2 && exit 1
fi

View file

@ -1,91 +0,0 @@
#!/usr/bin/env bash
init_variables() {
COMMAND=${0#*-}
REPLACE=false
unset ID
unset MSG
}
usage() {
cat << EOF
usage: git ${COMMAND} [<options>] <id> [<messages>]
Options:
-r, --replace replace all previous stamps with same id
EOF
}
error() {
if [[ -n "$1" ]]; then
echo "error: $1" >&2
fi
usage
exit 1
}
stamp() {
local commit_msg=$( git log -1 --pretty=%B )
local stamp_msg
[[ -n "${MSG}" ]] && stamp_msg="${ID} ${MSG}" || stamp_msg="${ID}"
if ${REPLACE}; then
# remove previous stamps with same ID from the commit message
commit_msg=$(
echo "${commit_msg}" \
| grep --ignore-case --invert-match "^${ID}\b" \
| cat -s
)
fi
# append the stamp to the commit message in a new paragraph
git commit --amend \
--message "${commit_msg}" \
--message "${stamp_msg}" \
> /dev/null
# show result
git log -1 --pretty=full
}
parse_options() {
while [[ "$#" -gt 0 ]]; do
case "$1" in
-h)
usage
exit 0
;;
--replace|-r)
REPLACE=true
shift
;;
*)
break
;;
esac
done
ID="$1"
MSG="${*:2}"
}
validate_options() {
# ID should be set to non-empty string
if [[ -z "${ID}" ]]; then
error "missing stamp identifier"
fi
}
init_variables
parse_options "$@"
validate_options
stamp

View file

@ -1,301 +0,0 @@
#!/usr/bin/env bash
# Code modified from https://github.com/kamranahmedse/git-standup,
# under the MIT LICENSE.
usage() {
cat <<EOS
Usage:
git standup [-a <author name>] [-w <weekstart-weekend>|-d <days-ago>] [-m <max-dir-depth>] [-D date-format] [-L] [-h] [-f] [-B] [-n <number-of-commits] [-F <gpg|authordate>]
-a - Specify author to restrict search to, default to current git user.
Use "-a all" if you don't want the restriction.
-w - Specify weekday range to limit search to
-m - Specify the depth of recursive directory search
-L - Toggle inclusion of symbolic links in recursive directory search
-d - Specify the number of days back to include
-D - Specify the date format for "git log" (default: relative)
-h - Display this help screen
-f - Fetch the latest commits beforehand
-B - Display the commits in branch groups
-n - Limit the number of commits displayed per group
-F gpg - Show if commit is GPG signed (G) or not (N)
-F authordate - Print author date instead of commit date
Examples:
git standup -a "John Doe" -w "MON-FRI" -m 3
EOS
}
warn() {
>&2 echo "${BOLD}${RED}WARNING: $1${NORMAL}"
}
git rev-parse --show-toplevel > /dev/null 2>&1
in_git_repo=$?
# Use colors, but only if connected to a terminal, and that terminal
# supports them.
if command -v tput &>/dev/null; then
ncolors=$(tput colors)
fi
if [[ -t 1 ]] && [[ -n "$ncolors" ]] && [[ "$ncolors" -ge 8 ]] ; then
RED="$(tput setaf 1)"
GREEN="$(tput setaf 2)"
YELLOW="$(tput setaf 3)"
BOLD="$(tput bold)"
NORMAL="$(tput sgr0)"
BOLD=$(tput bold)
UNDERLINE=$(tput smul)
NORMAL=$(tput sgr0)
COLOR=always
else
RED=""
GREEN=""
YELLOW=""
BOLD=""
NORMAL=""
BOLD=""
UNDERLINE=""
NORMAL=""
COLOR=never
fi
# Only enable exit-on-error after the non-critical colorization stuff,
# which may fail on systems lacking tput or terminfo
set -e
RANGE_SPECIFIED=
COMMIT_DATE_FORMAT=%cd
USE_GPG_FORMAT=no
while getopts "hgfF:Bd:a:w:m:D:n:L" opt; do
case $opt in
h)
usage
exit 0
;;
a)
if [[ "$OPTARG" = 'all' ]] ; then
AUTHOR=".*"
else
AUTHOR="$OPTARG"
fi
;;
d)
test -n "$RANGE_SPECIFIED" && warn "-d option is conflict with -w"
RANGE_SPECIFIED=yes
if [ "$OPTARG" -lt 1 ]; then
>&2 echo "Specify days less than one is invalid"
exit 1
fi
SINCE="$OPTARG days ago"
;;
w)
if [ -n "$RANGE_SPECIFIED" ]; then
warn "-w option is conflict with -d"
continue
fi
RANGE_SPECIFIED=yes
week_range=${OPTARG}
week_start="${week_range%%-*}"
week_start="${week_start:="Mon"}"
week_end="${week_range##*-}"
week_end=${week_end:="Fri"}
## In case it is the start of week, we need to
## show the commits since the last weekend
shopt -s nocasematch
if [[ "$week_start" == "$(LC_ALL=C date +%a)" ]] ; then
SINCE="last $week_end";
fi
;;
f)
FETCH_LAST_COMMIT=true
;;
m)
MAXDEPTH=$((OPTARG + 1))
if [ "$MAXDEPTH" -lt 1 ]; then
>&2 echo "Specify depth less than one is invalid"
exit 1
fi
;;
L)
INCLUDE_LINKS=-L
;;
D)
GIT_DATE_FORMAT=${OPTARG}
;;
g)
warn "-g option is deprecated, use '-F gpg' instead"
USE_GPG_FORMAT=yes
;;
B)
GROUP_BY_BRANCHES=true
;;
n)
MAX_COMMIT_NUM=${OPTARG}
;;
F)
case $OPTARG in
gpg)
USE_GPG_FORMAT=yes
;;
authordate)
COMMIT_DATE_FORMAT=%ad
;;
*)
warn "Invalid argument for -F: $OPTARG"
usage
exit 1
;;
esac
;;
\?)
usage
exit 1
;;
esac
done
shift $((OPTIND-1))
if [[ $# -gt 0 ]]; then
warn "please upgrade to new-style interface. Run 'git help standup' to get more info."
if [[ $# -gt 3 ]] ; then
usage
exit 1
fi
AUTHOR=$1
SINCE=$2
UNTIL=$3
fi
AUTHOR=${AUTHOR:="$(git config user.name || echo '')"}
if [ -z "${AUTHOR}" ]; then
warn "please configure an author with 'git config user.name' or specify an author via '-a'"
exit 1
fi
FETCH_LAST_COMMIT=${FETCH_LAST_COMMIT:=false}
MAXDEPTH=${MAXDEPTH:=2}
GIT_PRETTY_FORMAT="%Cred%h%Creset - %s %Cgreen(${COMMIT_DATE_FORMAT}) %C(bold blue)<%an>%Creset"
GIT_DATE_FORMAT=${GIT_DATE_FORMAT:=relative}
if [[ "$USE_GPG_FORMAT" == 'yes' ]]; then
GIT_PRETTY_FORMAT="$GIT_PRETTY_FORMAT %C(yellow)gpg: %G?%Creset"
fi
# Handle config of implicit week
IMPLICIT_WEEK=$(git config --get git-extras.standup.implicit-week || echo '')
if [[ -z "$RANGE_SPECIFIED" ]] && [[ -n "${IMPLICIT_WEEK}" ]]; then
week_start=${IMPLICIT_WEEK%%-*}
week_end=${IMPLICIT_WEEK##*-}
shopt -s nocasematch
if [[ "$week_start" == "$(LC_ALL=C date +%a)" ]]; then
SINCE="last $week_end"
fi
UNTIL=today
else
SINCE=${SINCE:=yesterday}
UNTIL=${UNTIL:=today}
fi
GIT_LOG_COMMAND="git --no-pager log \
--no-merges
--since \"$SINCE\"
--until \"$UNTIL\"
--author=\"$AUTHOR\"
--abbrev-commit
--oneline
--color=$COLOR
--pretty=format:'$GIT_PRETTY_FORMAT'
--date='$GIT_DATE_FORMAT'"
if [[ -n "$MAX_COMMIT_NUM" ]]; then
GIT_LOG_COMMAND="$GIT_LOG_COMMAND --max-count=$MAX_COMMIT_NUM"
fi
git_output() {
if [ "$GROUP_BY_BRANCHES" = true ]; then
local branches
branches=$(git branch --sort=-committerdate | awk '{print substr($0, 3)}')
for branch in $branches; do
# shellcheck disable=SC2086
if output=$(eval $GIT_LOG_COMMAND "$branch"); then
if [[ -n "$output" ]] ; then
echo "${GREEN}${branch}${NORMAL}"
echo "$output"
echo ""
fi
fi
# TODO optimize:return if the latest commit of a branch is earlier than the 'since' day
done
else
# shellcheck disable=SC2086
eval $GIT_LOG_COMMAND --all
fi
}
## For when the command has been run in a non-repo directory
if [[ $in_git_repo != 0 ]]; then
## Set delimiter to newline for the loop
IFS=$'\n'
## Recursively search for git repositories
# shellcheck disable=SC2086
PROJECT_DIRS=$(find $INCLUDE_LINKS . -maxdepth "$MAXDEPTH" -mindepth 0 -name .git)
# Fetch the latest commits, if required
if [ "$FETCH_LAST_COMMIT" = true ]; then
echo "${BOLD}${GREEN}Fetching commits ..${NORMAL}"
# Foreach of the project directories, fetch the commits
for DIR in $PROJECT_DIRS; do
DIR="$(dirname "$DIR")"
pushd "$DIR" > /dev/null
if [[ -d ".git" ]] ; then
echo " ${YELLOW}$(basename "$DIR")${NORMAL}"
git fetch --all > /dev/null 2>&1
fi
popd > /dev/null
done
fi
# Get the standup details for each of the projects
for DIR in $PROJECT_DIRS; do
DIR="$(dirname "$DIR")"
pushd "$DIR" > /dev/null
## Show the detail only if it is a git repository
if [[ -d ".git" || -f ".git" ]] ; then
if GITOUT=$(git_output); then
## Only output if there is some activities
if [[ -n "$GITOUT" ]] ; then
echo "${BOLD}${UNDERLINE}${YELLOW}$(basename "$DIR")${NORMAL}"
echo "$GITOUT"
echo ""
fi
else
echo "Repository under $DIR could not be queried." >&2
fi
fi
popd > /dev/null
done
else
if [ "$FETCH_LAST_COMMIT" = true ]; then
echo "${GREEN}Fetching commits ..${NORMAL}"
git fetch --all > /dev/null 2>&1
fi
if GITOUT=$(git_output); then
if [[ -n "$GITOUT" ]] ; then
echo "$GITOUT"
else
if [[ $AUTHOR = '.*' ]] ; then
AUTHOR="all the contributors"
fi
echo "${YELLOW}Seems like $AUTHOR did nothing!${NORMAL}"
fi
fi
fi

View file

@ -1,259 +1,18 @@
#!/usr/bin/env bash #!/bin/sh
COMMITISH=""
test $# -ne 0 && COMMITISH=$@
project=${PWD##*/}
commit_count=`git log --oneline $COMMITISH | wc -l | tr -d ' '`
file_count=`git ls-files | wc -l | tr -d ' '`
authors=`git shortlog -n -s $COMMITISH`
cd "$(git root)" || { echo "Can't cd to top level directory";exit 1; } echo
echo " project: $project"
PROJECT_FULL_PATH= echo " commits: $commit_count"
SUMMARY_BY_LINE= if test "$COMMITISH" = ""; then
DEDUP_BY_EMAIL= echo " files : $file_count"
MERGES_ARG=
OUTPUT_STYLE=
for arg in "$@"; do
case "$arg" in
--full-path)
PROJECT_FULL_PATH=1
;;
--line)
SUMMARY_BY_LINE=1
;;
--dedup-by-email)
DEDUP_BY_EMAIL=1
;;
--no-merges)
MERGES_ARG="--no-merges"
;;
--output-style)
OUTPUT_STYLE="$2"
shift
;;
-*)
>&2 echo "unknown argument $arg found"
exit 1
;;
*)
# set the argument back
set -- "$@" "$arg"
;;
esac
shift
done
if [ -n "$DEDUP_BY_EMAIL" ] && [ -n "$SUMMARY_BY_LINE" ]; then
>&2 echo "--dedup-by-email used with --line is not supported"
exit 1
fi
if [ -n "$MERGES_ARG" ] && [ -n "$SUMMARY_BY_LINE" ]; then
>&2 echo "--no-merges used with --line is not supported"
exit 1
fi
commit="HEAD"
if [ -n "$SUMMARY_BY_LINE" ]; then
paths=( "$@" )
else
[ $# -ne 0 ] && commit=$*
fi
if [[ -n "$PROJECT_FULL_PATH" ]]; then
project=${PWD/${HOME}/\~}
else
project=${PWD##*/}
fi
#
# get date for the given <commit>
#
commit_date() {
# the $1 can be empty
# shellcheck disable=SC2086
git log $MERGES_ARG --pretty='format: %ai' "$1" | cut -d ' ' -f 2
}
#
# get active days for the given <commit>
#
active_days() {
commit_date "$1" | sort -r | uniq | awk '
{ sum += 1 }
END { print sum }
'
}
#
# get the commit total
#
commit_count() {
# shellcheck disable=SC2086
git rev-list $MERGES_ARG --count "$commit"
}
#
# total file count
#
file_count() {
git ls-files | wc -l | tr -d ' '
}
#
# remove duplicate authors who belong to the same email address
#
dedup_by_email() {
# in:
# 27 luo zexuan <LuoZexuan@xxx.com>
# 7 罗泽轩 <luozexuan@xxx.com>
# out:
# 34 luo zexuan
LC_ALL=C awk '
{
sum += $1
last_field = tolower($NF)
if (last_field in emails) {
emails[last_field] += $1
} else {
email = last_field
emails[email] = $1
# set commits/email to empty
$1=$NF=""
sub(/^[[:space:]]+/, "", $0)
sub(/[[:space:]]+$/, "", $0)
name = $0
if (name in names) {
# when the same name is associated with existed email,
# merge the previous email into the later one.
emails[email] += emails[names[name]]
emails[names[name]] = 0
}
names[name] = email
}
}
END {
for (name in names) {
email = names[name]
printf "%6d\t%s\n", emails[email], name
}
}' | sort -rn -k 1
}
#
# list authors
#
format_authors() {
# a rare unicode character is used as separator to avoid conflicting with
# author name. However, Linux column utility will escape tab if separator
# specified, so we do unesaping after it.
LC_ALL=C awk '
{ args[NR] = $0; sum += $0 }
END {
for (i = 1; i <= NR; ++i) {
printf "%s♪%2.1f%%\n", args[i], 100 * args[i] / sum
}
}
' | column -t -s♪ | sed "s/\\\x09/\t/g"
}
#
# fetch repository age from oldest commit
#
repository_age() {
git log --reverse --pretty=oneline --format="%ar" | head -n 1 | LC_ALL=C sed 's/ago//'
}
#
# fetch repository age of the latest commit
#
last_active() {
git log --pretty=oneline --format="%ar" -n 1
}
#
# list the last modified author for each line
#
single_file() {
while read -r data
do
if [[ $(file "$data") = *text* ]]; then
git blame --line-porcelain "$data" 2>/dev/null | grep "^author " | LC_ALL=C sed -n 's/^author //p';
fi
done
}
current_branch_name() {
git rev-parse --abbrev-ref HEAD
}
#
# list the author for all file
#
lines() {
git ls-files -- "$@" | single_file
}
#
# get the number of the lines
#
line_count() {
lines "$@" | wc -l
}
uncommitted_changes_count() {
git status --porcelain | wc -l
}
COLUMN_CMD_DELIMTER="¬" # Hopefully, this symbol is not used in branch names... I use it as a separator for columns
SP="$COLUMN_CMD_DELIMTER|"
print_summary_by_line() {
if [ "$OUTPUT_STYLE" = "tabular" ]; then
tabular_headers="# Repo $SP Lines"
echo -e "$tabular_headers\n$project $SP $(line_count "${paths[@]}")" | column -t -s "$COLUMN_CMD_DELIMTER"
elif [ "$OUTPUT_STYLE" = "oneline" ]; then
echo "$project / lines: $(line_count "${paths[@]}")"
elif [ -n "$SUMMARY_BY_LINE" ]; then
echo
echo " project : $project"
echo " lines : $(line_count "${paths[@]}")"
echo " authors :"
lines "${paths[@]}" | sort | uniq -c | sort -rn | format_authors
fi
}
print_summary() {
if [ "$OUTPUT_STYLE" = "tabular" ]; then
tabular_headers="# Repo $SP Age $SP Last active $SP Active on $SP Commits $SP Uncommitted $SP Branch"
echo -e "$tabular_headers\n$project $SP $(repository_age) $SP $(last_active) $SP $(active_days "$commit") days $SP $(commit_count "$commit") $SP $(uncommitted_changes_count) $SP $(current_branch_name)" | column -t -s "$COLUMN_CMD_DELIMTER"
elif [ "$OUTPUT_STYLE" = "oneline" ]; then
echo "$project / age: $(repository_age) / last active: $(last_active) / active on $(active_days "$commit") days / commits: $(commit_count "$commit") / uncommitted: $(uncommitted_changes_count) / branch: $(current_branch_name)"
else
echo
echo " project : $project"
echo " repo age : $(repository_age)"
echo " branch: : $(current_branch_name)"
echo " last active : $(last_active)"
echo " active on : $(active_days "$commit") days"
echo " commits : $(commit_count "$commit")"
# The file count doesn't support passing a git ref so ignore it if a ref is given
if [ "$commit" = "HEAD" ]; then
echo " files : $(file_count)"
fi
echo " uncommitted : $(uncommitted_changes_count)"
echo " authors : "
if [ -n "$DEDUP_BY_EMAIL" ]; then
# the $commit can be empty
# shellcheck disable=SC2086
git shortlog $MERGES_ARG -n -s -e "$commit" | dedup_by_email | format_authors
else
# shellcheck disable=SC2086
git shortlog $MERGES_ARG -n -s "$commit" | format_authors
fi
fi
}
if [ -n "$SUMMARY_BY_LINE" ]; then
print_summary_by_line
else
print_summary
fi fi
echo " authors: "
echo "$authors"
echo

View file

@ -1,101 +0,0 @@
#!/usr/bin/env bash
function _usage()
{
local command="git sync"
cat << EOS
Usage:
${command} [<remote> <branch>]
${command} -h | --help
${command} -s | --soft
${command} -f | --force
Sync local branch with <remote>/<branch>.
When <remote> and <branch> are not specified on the command line, upstream of local branch will be used by default.
All changes and untracked files and directories will be removed unless you add -s(--soft).
Examples:
Sync with upstream of local branch:
${command}
Sync with origin/master:
${command} origin master
Sync without cleaning untracked files:
${command} -s
Sync without interaction:
${command} -f
EOS
}
function main()
{
while [ "$1" != "" ]; do
case $1 in
-h | --help)
_usage
exit
;;
-s | --soft)
local soft="true"
;;
-f | --force)
local force="YES"
;;
* )
if [ "${remote}" = "" ]; then
local remote="$1"
elif [ "${branch}" = "" ]; then
local branch="$1"
else
echo -e "Error: too many arguments.\n"
_usage
exit 1
fi
;;
esac
shift
done
local remote_branch
if [ -z "${remote}" ]; then
if ! remote_branch="$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null)"; then
echo "There is no upstream information of local branch."
exit 1
fi
local branch="$(git rev-parse --abbrev-ref --symbolic-full-name @)"
local remote=$(git config "branch.${branch}.remote")
elif [ -z "${branch}" ]; then
echo -e "Error: too few arguments.\n"
_usage
exit 1
else
remote_branch="${remote}/${branch}"
fi
if [ "${force}" != "YES" ]; then
if [ "${soft}" = "true" ]; then
echo -n "Are you sure you want to sync with '${remote_branch}'? [y/N]: "
else
echo -n "Are you sure you want to clean all changes & sync with '${remote_branch}'? [y/N]: "
fi
local force
read -r force
fi
case "${force}" in
"Y" | "y" | "yes" | "Yes" | "YES" )
if [ "${soft}" = "true" ]; then
git fetch "${remote}" "${branch}" && git reset --hard "${remote_branch}"
else
git fetch "${remote}" "${branch}" && git reset --hard "${remote_branch}" && git clean -d -f -x
fi
;;
* )
echo "Canceled."
;;
esac
}
main "$@"

View file

@ -1,13 +1,8 @@
#!/usr/bin/env bash #!/bin/sh
USAGE(){ filename="$*"
echo "git touch <filename> [ <filename> . . .]" 1>&2
exit 1
}
test $# -lt 1 && USAGE test -z "$filename" && echo "filename required" && exit 1
for filename in "$@"; do touch "$filename" \
touch "$filename" \ && git add "$filename"
&& git add "$filename"
done

View file

@ -1,72 +1,11 @@
#!/usr/bin/env bash #!/bin/sh
cstr="commits" if test $# -eq 0; then
ccnt=$(git rev-list --count HEAD) git reset --hard HEAD~1
if [ "$ccnt" -eq 1 ]; then cstr="commit"; fi else
parm3="" if `echo $1 | grep -q [^[:digit:]]`; then
echo $1 is not a number
function _undo()
{
type=${1:-soft}
undo_cnt=${2:-1}
reset=${3:-""}
if [ "$undo_cnt" -gt "$ccnt" ]; then
echo "Only $ccnt $cstr, cannot undo $undo_cnt"
elif [ "$type" = "hard" ] && [ "$ccnt" -eq "$undo_cnt" ]; then
echo "Cannot hard undo all commits"
elif [ "$type" = "soft" ] && [ "$ccnt" -eq 1 ]; then
git update-ref -d HEAD
else else
git reset "--$type" "HEAD~$undo_cnt" git reset --hard HEAD~$1
fi fi
if [ "$reset" != "" ]; then git reset; fi fi
}
case "$1" in
-h)
cat << EOL
This will erase any changes since your last commit.
If you want to get help info, run "git undo --help" instead.
Do you want to continue? [yN]"
EOL
read -r res
case "${res}" in
"Y" | "y")
parm1=hard
parm2=${2:-1}
;;
* )
exit 0
;;
esac
;;
--hard)
parm1=hard
parm2=${2:-1}
;;
-s|--soft)
parm1=soft
parm2=${2:-1}
parm3=reset
;;
"")
parm1=soft
parm2=1
;;
*[!0-9]*)
echo "Invalid parameter: $1"
exit 1
;;
*)
parm1=soft
parm2="$1"
;;
esac
if [[ ! $parm2 =~ ^[1-9][0-9]*$ ]]; then
echo "Invalid undo count: $parm2"
exit 1
fi
_undo "$parm1" "$parm2" "$parm3"

View file

@ -1,5 +0,0 @@
#!/usr/bin/env bash
filename="$1"
test -z "$filename" && echo "filename required." 1>&2 && exit 1
git update-index --no-skip-worktree "$filename"

View file

@ -1,14 +0,0 @@
#!/usr/bin/env bash
# Based on scripts from git-utils:
# * https://github.com/ddollar/git-utils/blob/master/git-unwip
# Check if the last commit is a 'WIP' commit
LAST_COMMIT=`git log -1 --pretty=%B | tr -d '[:space:]'`
if [ 'WIP' != $LAST_COMMIT ]; then
echo 'Last commit is not a WIP commit, so it will not be unWIP-ed.'
exit 1
fi
git undo --soft

View file

@ -1,131 +0,0 @@
#!/usr/bin/env bash
#
# Change files modification time to their last commit date
#
# Bash unofficial strict mode
set -euo pipefail
IFS=$'\n\t'
if [[ "${1:-}" == "--newer" ]]; then
op=le
shift
else
op=eq
fi
# BSD systems
if date -j &>/dev/null; then
stat_flags="-f %m"
date_flags="-r"
else
# Non-BSD systems
stat_flags="-c %Y"
date_flags="-d@"
fi
bash_opts=()
if bash --help 2>&1 | grep -q -- '--noprofile'; then
bash_opts+=(--noprofile)
fi
if bash --help 2>&1 | grep -q -- '--norc'; then
bash_opts+=(--norc)
fi
status_opts=(--porcelain --short)
# %ct: committer date, UNIX timestamp / %at: author date, UNIX timestamp
log_opts=(--format='%ct')
if git status --help 2>&1 | grep -q -- "--no-renames"; then
status_opts+=(--no-renames)
log_opts+=(--no-renames)
fi
if git status --help 2>&1 | grep -q -- "--untracked-files"; then
status_opts+=(--untracked-files=no)
fi
if git status --help 2>&1 | grep -q -- "--ignored"; then
status_opts+=(--ignored=no)
fi
prefix="$(git rev-parse --show-prefix) "
strip="${#prefix}"
tmpfile=$(mktemp)
# shellcheck disable=SC2064
trap "rm -f '${tmpfile}'" 0
awk_flags=(
-F'\t'
-v date_flags="${date_flags}"
-v op="${op}"
-v stat_flags="${stat_flags}"
-v strip="${strip}"
-v tmpfile="${tmpfile}"
)
# sanity check, not required:
if awk --help 2>&1 | grep -q -- '--posix'; then
awk_flags+=(--posix)
fi
read -r -d '' awk_script <<"EOF" || true
BEGIN {
seen[""]=1
print "#!/usr/bin/env bash"
print "set +e"
print "t() {"
print " test -e \"$2\" || return 0"
printf(" test \"$(stat %s \"$2\" 2>/dev/null)\" -%s \"$1\" && return 0\n", stat_flags, op)
if (date_flags == "-d@") {
print " echo \"+ touch -h -d@$1 $2\""
print " touch -h -d@$1 \"$2\""
} else {
print " t=$(date -r$1 \"+%Y%m%d%H%M.%S\")"
print " echo \"+ touch -h -t $t $2\""
print " touch -h -t $t \"$2\""
}
print "}"
}
FILENAME==tmpfile {
skip[$1]=1
next
}
# skip blank lines
!/^$/ {
# skip deletes
if (substr($1, length($1), 1) ~ /D/) {
next
}
if (NF == 1) {
ct=$1
next
}
$2 = substr($2, strip, length($2)- strip + 1)
if ($2 in seen) {
next
}
if ($2 in skip) {
next
}
seen[$2]=1
# remove enclosing double quotes that git adds:
if (substr($2, 1, 1) == "\"" && substr($2, length($2), 1) == "\"") {
$2 = substr($2, 2, length($2) - 2)
# unescape remaining double quotes
gsub(/\\"/, "\"", $2)
# unescape escaped backslashes
gsub(/\\\\/, "\\", $2)
}
# escape apostrophes: ' => '\''
gsub(/'/, "'\\''", $2)
printf("t %s '%s'\n", ct, $2)
}
EOF
# prefix is stripped:
git --no-pager status "${status_opts[@]}" . \
| cut -c 4- >"${tmpfile}"
# prefix is not stripped:
git --no-pager log --raw --no-merges "${log_opts[@]}" . \
| awk "${awk_flags[@]}" "${awk_script}" "${tmpfile}" - \
| BASH_ENV='' bash "${bash_opts[@]}" -

View file

@ -1,8 +0,0 @@
#!/usr/bin/env bash
# Based on scripts from git-utils:
# * https://github.com/ddollar/git-utils/blob/master/git-wip
# * https://github.com/ddollar/git-utils/blob/master/git-addremove
git add --all
git commit --all --message="WIP"

View file

@ -1,24 +0,0 @@
diff --git a/bin/git-extras b/bin/git-extras
index e49cd24..4ae28b5 100755
--- a/bin/git-extras
+++ b/bin/git-extras
@@ -4,13 +4,12 @@ VERSION="4.3.0"
INSTALL_SCRIPT="https://raw.githubusercontent.com/tj/git-extras/main/install.sh"
update() {
- local bin="$(command -v git-extras)"
- local prefix=${bin%/*/*}
- local orig=$PWD
-
- curl -s $INSTALL_SCRIPT | PREFIX="$prefix" bash /dev/stdin \
- && cd "$orig" \
- && echo "... updated git-extras $VERSION -> $(git extras --version)"
+ echo "This git-extras installation is managed by Homebrew."
+ echo "If you'd like to update git-extras, run the following:"
+ echo
+ echo " brew upgrade git-extras"
+ echo
+ return 1
}
updateForWindows() {

View file

@ -1,10 +0,0 @@
#!/usr/bin/env bash
err() {
echo >&2 "$1"
exit 1
}
if ! command -v column >/dev/null 2>&1; then
err "Need to install dependency 'column' before installation (can be found in bsdmainutils)"
fi

Some files were not shown because too many files have changed in this diff Show more