Compare commits

..

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

357 changed files with 4193 additions and 12982 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/

3
.gitmodules vendored
View file

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

View file

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

10
.travis.yml Normal file
View file

@ -0,0 +1,10 @@
os:
- linux
- osx
script:
- ./check_integrity.sh $(find bin | cut -b 5- | xargs)
# Test for the brew release
- 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"

261
AUTHORS
View file

@ -1,296 +1,191 @@
git-extras is written and maintained by various contributors: git-extras is written and maintained by TJ Holowaychuk and
various contributors:
Development Lead
````````````````
- TJ Holowaychuk <tj@vision-media.ca>
Maintainers Maintainers
``````````` ```````````
- TJ Holowaychuk <tj@vision-media.ca>
- Hemanth.HM <hemanth.hm@gmail.com> - Hemanth.HM <hemanth.hm@gmail.com>
- Nimit Kalra <me@nimit.io> - Nimit Kalra <me@nimit.io>
- Nicolai Skogheim <nicolai.skogheim@gmail.com> - Nicolai Skogheim <nicolai.skogheim@gmail.com>
- spacewander <spacewanderlzx@gmail.com> - spacewander <spacewanderlzx@gmail.com>
- Edwin Kofler <edwin@kofler.dev>
- Leroy <vanpipy@gmail.com>
Patches and Suggestions Patches and Suggestions
``````````````````````` ```````````````````````
- Jonhnny Weslley - Jonhnny Weslley
- Tobias Fendin
- LeeW - LeeW
- timfeirg
- Niklas Schlimm - Niklas Schlimm
- nickl- - nickl-
- timfeirg
- CJ - CJ
- Paul Wise
- Damien Tardy-Panis - Damien Tardy-Panis
- Mark Eissler - Mark Eissler
- Yuriy VG
- Leila Muhtasib - Leila Muhtasib
- Richard Fearn - Richard Fearn
- Bill Wood
- Jesús Espino - Jesús Espino
- Rémy HUBSCHER
- go2null
- Andrew Janke
- David Baumgold
- Jan Schulz
- Kenneth Reitz - Kenneth Reitz
- David Baumgold
- Andrew Janke
- Jan Schulz
- Mark Pitman - Mark Pitman
- Peter Benjamin
- Günther Grill
- Luke Childs - Luke Childs
- Sasha Khamkov - Sasha Khamkov
- equt
- oikarinen
- vyas
- Don Harper
- Pierre Ayoub
- Robin Winslow - Robin Winslow
- Ross Smith II
- Yi EungJun - Yi EungJun
- grindhold - grindhold
- wyattscarpenter - NANRI
- Aggelos Orfanakos
- Camille Reynders
- Carlos Prado
- Chris Hall - Chris Hall
- Damian Krzeminski - Damian Krzeminski
- NANRI - Aggelos Orfanakos
- Alexander Krasnukhin - Camille Reynders
- Phally
- Rico Sta. Cruz - Rico Sta. Cruz
- Rémy HUBSCHER
- Alexander Krasnukhin
- Takuma Yamaguchi - Takuma Yamaguchi
- thomas menzel - Phally
- vr8ce - Julio Napurí
- Brian J Brennan
- Dominik Gedon
- Hogan Long - Hogan Long
- Ivan Malopinsky - Ivan Malopinsky
- John Bachir - Brian J Brennan
- Jonathan "Duke" Leto
- Julio Napurí
- Justin Dugger
- Nils Winkler
- Philipp Klose
- Raphael Boidol
- Richard Russon
- Sam Bostock
- Vladimir Jimenez
- gisphm
- jacobherrington
- overengineer
- phigoro
- wooorm - wooorm
- Alexis GRIMALDI - Philipp Klose
- Allan Odgaard - Jonathan "Duke" Leto
- Andre Cerqueira - Nils Winkler
- Austin Ziegler - gisphm
- Beth Skurrie
- Brice Dutheil
- Curtis McEnroe
- Daniele Paolella
- David Rogers
- Devin Withers
- 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 - William Montgomery
- David Rogers
- Ye Lin Aung - Ye Lin Aung
- luozexuan - Andre Cerqueira
- roxchgt - Gert Van Gool
- Kylie McClain
- Curtis McEnroe
- Guillaume Seren
- go2null
- Hozefa
- Paul Schreiber
- Patryk Małek
- Newell Zhu
- Marc Harter
- Nate Jones
- phigoro
- soffolk - soffolk
- 0xflotus - Alexis GRIMALDI
- Abdullah - Domenico Rotiroti
- Devin Withers
- Jean Jordaan
- Matiss
- Allan Odgaard
- Evan Grim
- Tom Vincent
- Justin Guenther
- Richard Littauer
- Florian H
- Wil Moore III
- Stefan VanBuren
- Stephen Mathieson
- Steve Mao
- Todd Wolfson
- Tom Ashworth
- Tony
- TweeKane
- Valérian Galliat
- Xiaopei Li
- creynders
- Adam Parkin - Adam Parkin
- eszabpt
- jykntr
- meza
- neydroid
- nulltask
- zeroDivisible
- Étienne BERSAC
- ☃ pitr
- dead-horse
- Adriaan Zonnenberg - Adriaan Zonnenberg
- Akim Demaille - Akim Demaille
- Alessandro Pagiaro
- Alex McHale - Alex McHale
- Aloxaf
- Amir Tocker - Amir Tocker
- Ammar Najjar - Ammar Najjar
- Amory Meltzer
- Andreas Duering - Andreas Duering
- Andrei Petcu - Andrei Petcu
- Andrew Griffiths - Andrew Griffiths
- Andrew Marcinkevičius - Andrew Marcinkevičius
- Andrew Starr-Bochicchio - Andrew Starr-Bochicchio
- Andrew Sullivan Cant
- Andrey Elizarov - Andrey Elizarov
- Angel Aguilera - Angel Aguilera
- Antoine Beaupré - Antoine Beaupré
- Aurélien Scoubeau - Aurélien Scoubeau
- Balazs Nadasdi - Balazs Nadasdi
- Ben Parnell - Ben Parnell
- Beni Ben Zikry
- Brandon Zylstra - Brandon Zylstra
- Brian Goad - Brian Goad
- Brian Murrell
- Bruno Sutic - Bruno Sutic
- Caleb Maclennan
- Carl Casbolt - Carl Casbolt
- Carlos Rodríguez - Carlos Rodríguez
- Christophe Badoit
- Ciro Nunes - Ciro Nunes
- CleanMachine1
- CodeByZach
- Craig MacGregor - Craig MacGregor
- Dan Goodliffe
- Dan Jackson - Dan Jackson
- Dan Kilman - Dan Kilman
- Daniel Bruder - Daniel Bruder
- Daniel Lublin
- Daniel Schildt - Daniel Schildt
- Dave James Miller - Dave James Miller
- David Hartmann - David Hartmann
- Dominic Barnes
- Dung Quang - Dung Quang
- Edward Betts
- Eli Schwartz
- Emil Kjelsrud - Emil Kjelsrud
- Erik Skultety
- François M
- George Crabtree - George Crabtree
- Gerrit-K
- Greg Allen - Greg Allen
- Guilhem Saurel
- Guillermo Rauch - Guillermo Rauch
- Gunnlaugur Thor Briem - Gunnlaugur Thor Briem
- Hasse Ramlev Hansen
- Heiko Becker
- Hugo Ruiz-Mireles
- Isaac Mungai
- J.C. Yamokoski - J.C. Yamokoski
- Jack O. Wasey
- James Manning - James Manning
- James Zhu
- Jan Krueger - Jan Krueger
- Jared Baur
- Jarod Stewart - Jarod Stewart
- Jason Young - Jason Young
- Jens K. Mueller - Jens K. Mueller
- Jeremy Lin - Jeremy Lin
- Jesse Sipprell - Jesse Sipprell
- Jianjin Fan - Jianjin Fan
- Jobin Kurian
- Johannes Ewald - Johannes Ewald
- John Evans - John Evans
- John Hoffmann - John Hoffmann
- Jon Ander Peñalba - Jon Ander Peñalba
- Josh McKinney
- Joshua Appelman - Joshua Appelman
- José María Gutiérrez @TheTechOddBug - Justin Dugger
- Katrin Leinweber
- Kevin Woo - Kevin Woo
- Konstantin Schukraft - Konstantin Schukraft
- Kurban Mallachiev
- Leandro López - Leandro López
- Lone
- Luis Miguel Hernanz
- Maarten Winter - Maarten Winter
- Marco Glasbergen
- Matan Rosenberg
- Mathieu D. (MatToufoutu) - Mathieu D. (MatToufoutu)
- Matt
- Matt Colyer - Matt Colyer
- Matt Headley
- Matthew Avant - Matthew Avant
- Mattias Andersson
- Michael Komitee - Michael Komitee
- Michael Matuzak - Michael Matuzak
- Michele Bologna
- Montak Oleg
- Moritz Grauel - Moritz Grauel
- Nate Eagleson
- Nate Fischer - Nate Fischer
- Nathan Rajlich - Nathan Rajlich
- Nick Campbell - Nick Campbell
- Nick Payne - Nick Payne
- Nicolas Kosinski
- Niklas Fiekas - Niklas Fiekas
- Oliver Kopp
- Orestis
- Peter Bittner
- Piotr Górski
- Prayag Verma - Prayag Verma
- Przemek Kitszel
- R. Martinho Fernandes - R. Martinho Fernandes
- Raphael Fleischlin - Raphael Fleischlin
- Rasmus Wriedt Larsen - Rasmus Wriedt Larsen
- René
- Revisor
- Ricardo Fernández Serrata
- Riceball LEE - Riceball LEE
- Rob Kennedy - Rob Kennedy
- Robin von Bülow
- Russ
- Ryan Bohn - Ryan Bohn
- Sachin Gupta
- Sam Thursfield - Sam Thursfield
- Sandro
- Sascha Andres
- SchleimKeim
- Sean Molenaar - 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 - Xavier Krantz
- Xiaopei Li - Oliver Kopp
- Dominic Barnes
- Edward Betts
- Luis Miguel Hernanz
- Timothy Hwang
- Marco Glasbergen
- Daniel Lublin
- Matt
- Sam Bostock
- Zeeshan Ahmed - Zeeshan Ahmed
- ax1036 - Isaac Mungai
- 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 +1,21 @@
# Contributing ## Your new git-extra command should support
Thanks for contributing! Please read this document before you make a PR. * OSX, Linux, BSD (You may need to browse their man page)<sup>*</sup>
* Bash 3.2+ (If you aren't sure, see [the Bash changelog](http://tldp.org/LDP/abs/html/bash2.html))
* Git 2.1+
## Supported Platforms <sup>*</sup>If you aren't able to test your new command on a platform,
make that clear in your PR and someone else may be able to test it on their system.
Any changes must support the following platforms: ## To submit a new command, you should
- macOS Let's assume your new command is named `foo`.
- 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). 1. Write a bash script under `./bin` called `git-foo`.
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`. 2. Read `./man/Readme.md` and write documentation for `git-foo`.
3. Don't forget to introduce it in `Commands.md`. 3. Don't forget to introduce it in `Commands.md`.
4. Update `./etc/git-extras-completion.zsh`. Just follow existing code. 4. Update `./etc/git-extras-completion.zsh`. Just follow existing code.
5. (Optional) Update `./etc/bash_completion.sh`. 5. (Optional) Update `./etc/bash_completion.sh`.
6. (Optional) Update `./etc/git-extras.fish`. 6. Run `./check_integrity.sh foo` to check if all done.
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. 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`.

View file

@ -1,62 +1,53 @@
- [`git abort`](#git-abort)
- [`git alias`](#git-alias) - [`git alias`](#git-alias)
- [`git archive-file`](#git-archive-file) - [`git archive-file`](#git-archive-file)
- [`git authors`](#git-authors) - [`git authors`](#git-authors)
- [`git browse`](#git-browse) - [`git back`](#git-back)
- [`git browse-ci`](#git-browse-ci) - [`git bug`](#git-featurerefactorbugchore)
- [`git bulk`](#git-bulk) - [`git bulk`](#git-bulk)
- [`git brv`](#git-brv)
- [`git changelog`](#git-changelog) - [`git changelog`](#git-changelog)
- [`git chore`](#git-featurerefactorbugchore)
- [`git clear`](#git-clear) - [`git clear`](#git-clear)
- [`git clear-soft`](#git-clear-soft) - [`git clear-soft`](#git-clear-soft)
- [`git coauthor`](#git-coauthor)
- [`git commits-since`](#git-commits-since) - [`git commits-since`](#git-commits-since)
- [`git continue`](#git-continue)
- [`git contrib`](#git-contrib) - [`git contrib`](#git-contrib)
- [`git count`](#git-count) - [`git count`](#git-count)
- [`git cp`](#git-cp)
- [`git create-branch`](#git-create-branch) - [`git create-branch`](#git-create-branch)
- [`git delete-branch`](#git-delete-branch) - [`git delete-branch`](#git-delete-branch)
- [`git delete-gone-branches`](#git-delete-gone-branches)
- [`git delete-merged-branches`](#git-delete-merged-branches) - [`git delete-merged-branches`](#git-delete-merged-branches)
- [`git delete-squashed-branches`](#git-delete-squashed-branches)
- [`git delete-submodule`](#git-delete-submodule) - [`git delete-submodule`](#git-delete-submodule)
- [`git delete-tag`](#git-delete-tag) - [`git delete-tag`](#git-delete-tag)
- [`git delta`](#git-delta) - [`git delta`](#git-delta)
- [`git effort`](#git-effort) - [`git effort`](#git-effort)
- [`git extras`](#git-extras) - [`git extras`](#git-extras)
- [`git feature`](#git-feature) - [`git feature`](#git-featurerefactorbugchore)
- [`git force-clone`](#git-force-clone) - [`git force-clone`](#git-force-clone)
- [`git fork`](#git-fork) - [`git fork`](#git-fork)
- [`git fresh-branch`](#git-fresh-branch) - [`git fresh-branch`](#git-fresh-branch)
- [`git get`](#git-get)
- [`git gh-pages`](#git-gh-pages) - [`git gh-pages`](#git-gh-pages)
- [`git graft`](#git-graft) - [`git graft`](#git-graft)
- [`git guilt`](#git-guilt) - [`git guilt`](#git-guilt)
- [`git ignore`](#git-ignore) - [`git ignore`](#git-ignore)
- [`git ignore-io`](#git-ignore-io) - [`git ignore-io`](#git-ignore-io)
- [`git info`](#git-info) - [`git info`](#git-info)
- [`git line-summary`](#git-line-summary)
- [`git local-commits`](#git-local-commits) - [`git local-commits`](#git-local-commits)
- [`git lock`](#git-lock) - [`git lock`](#git-lock)
- [`git locked`](#git-locked) - [`git locked`](#git-locked)
- [`git magic`](#git-magic)
- [`git merge-into`](#git-merge-into) - [`git merge-into`](#git-merge-into)
- [`git merge-repo`](#git-merge-repo) - [`git merge-repo`](#git-merge-repo)
- [`git missing`](#git-missing) - [`git missing`](#git-missing)
- [`git mr`](#git-mr) - [`git mr`](#git-mr)
- [`git obliterate`](#git-obliterate) - [`git obliterate`](#git-obliterate)
- [`git paste`](#git-paste)
- [`git pr`](#git-pr) - [`git pr`](#git-pr)
- [`git psykorebase`](#git-psykorebase) - [`git psykorebase`](#git-psykorebase)
- [`git pull-request`](#git-pull-request) - [`git pull-request`](#git-pull-request)
- [`git reauthor`](#git-reauthor) - [`git reauthor`](#git-reauthor)
- [`git rebase-patch`](#git-rebase-patch) - [`git rebase-patch`](#git-rebase-patch)
- [`git refactor`](#git-featurerefactorbugchore)
- [`git release`](#git-release) - [`git release`](#git-release)
- [`git rename-branch`](#git-rename-branch) - [`git rename-branch`](#git-rename-branch)
- [`git rename-file`](#git-rename-file)
- [`git rename-tag`](#git-rename-tag) - [`git rename-tag`](#git-rename-tag)
- [`git rename-remote`](#git-rename-remote)
- [`git repl`](#git-repl) - [`git repl`](#git-repl)
- [`git reset-file`](#git-reset-file) - [`git reset-file`](#git-reset-file)
- [`git root`](#git-root) - [`git root`](#git-root)
@ -75,9 +66,6 @@
- [`git touch`](#git-touch) - [`git touch`](#git-touch)
- [`git undo`](#git-undo) - [`git undo`](#git-undo)
- [`git unlock`](#git-unlock) - [`git unlock`](#git-unlock)
- [`git utimes`](#git-utimes)
- [`git unwip`](#git-unwip)
- [`git wip`](#git-wip)
## git extras ## git extras
@ -86,7 +74,7 @@ The main `git-extras` command.
Output the current `--version`: Output the current `--version`:
```bash ```bash
$ git extras --version $ git extras
``` ```
List available commands: List available commands:
@ -102,11 +90,11 @@ $ git extras update
``` ```
## git gh-pages ## git gh-pages
Sets up the `gh-pages` branch. (See [GitHub Pages](https://pages.github.com/) documentation.) Sets up the `gh-pages` branch. (See [GitHub Pages](http://pages.github.com/) documentation.)
## git feature ## git feature|refactor|bug|chore
Create/Merge the given feature branch `name`: Create/Merge the given feature, refactor, bug or chore branch `name`:
```bash ```bash
$ git feature dependencies $ git feature dependencies
@ -135,6 +123,8 @@ $ git feature finish dependencies
_Note_: If a remote is setup to track the branch, it will be deleted. _Note_: If a remote is setup to track the branch, it will be deleted.
All of this works with `feature`, `bug`, `chore` or `refactor`.
## git contrib ## git contrib
Output `author`'s contributions to a project: Output `author`'s contributions to a project:
@ -152,20 +142,17 @@ visionmedia (18):
## git summary ## git summary
Outputs a repo or path summary: Outputs a repo summary:
```bash ```bash
$ git summary $ git summary
project : git-extras project : git-extras
repo age : 10 months ago repo age : 10 months ago
branch : master commits : 163
last active : 3 weeks ago active : 60 days
active on : 93 days files : 93
commits : 163 authors :
files : 93
uncommitted : 3
authors :
97 Tj Holowaychuk 59.5% 97 Tj Holowaychuk 59.5%
37 Jonhnny Weslley 22.7% 37 Jonhnny Weslley 22.7%
8 Kenneth Reitz 4.9% 8 Kenneth Reitz 4.9%
@ -182,8 +169,8 @@ authors :
1 Guillermo Rauch 0.6% 1 Guillermo Rauch 0.6%
``` ```
This command can also take a *committish*, and will print a summary for commits in This command can also take a *commitish*, and will print a summary for commits in
the committish range: the commitish range:
```bash ```bash
$ git summary v42.. $ git summary v42..
@ -226,19 +213,9 @@ project : git-extras
1 Daniel Schildt 0.0% 1 Daniel Schildt 0.0%
``` ```
The `--line` option can also take a path, which will print a filtered summary for that folder or file. ## git line-summary
The option `--output-style` tries to put as much summary information of the repo into defined styled way as possible. WARNING: git line-summary has been replaced by [`git summary --line`](#git-summary) and will be removed in a future release.
This is how the `tabular` output style and `oneline` output style look like
```bash
$ git summary --output-style tabular
# Repo | Age | Last active | Active on | Commits | Uncommitted | Branch
git-extras | 13 years | 7 hours ago | 807 days | 1703 | 3 | master
$ git summary --output-style oneline
git-extras / age: 13 years / last active: 7 hours ago / active on 807 days / commits: 1703 / uncommitted: 3 / branch: master
```
## git effort ## git effort
@ -248,6 +225,8 @@ git-extras / age: 13 years / last active: 7 hours ago / active on 807 days / com
node (master): git effort --above 15 {src,lib}/* node (master): git effort --above 15 {src,lib}/*
``` ```
![git effort](http://f.cl.ly/items/0b0w0S2K1d100e2T1a0D/Screen%20Shot%202012-02-08%20at%206.43.34%20PM.png)
If you wish to ignore files with commits `<=` a value you may use `--above`: If you wish to ignore files with commits `<=` a value you may use `--above`:
``` ```
@ -276,40 +255,17 @@ $ git effort bin/* lib/*
```bash ```bash
usage: git bulk [-g] ([-a]|[-w <ws-name>]) <git command> usage: git bulk [-g] ([-a]|[-w <ws-name>]) <git command>
git bulk --addworkspace <ws-name> <ws-root-directory> (--from <URL or file>) git bulk --addworkspace <ws-name> <ws-root-directory>
git bulk --removeworkspace <ws-name> git bulk --removeworkspace <ws-name>
git bulk --addcurrent <ws-name> git bulk --addcurrent <ws-name>
git bulk --purge git bulk --purge
git bulk --listall git bulk --listall
``` ```
Register a workspace so that `git bulk` knows about it (it will be registered in your `.gitconfig`): Register a workspace so that `git bulk` knows about it (notice that <ws-root-directory> must be absolute path):
```bash ```bash
$ git bulk --addworkspace personal ~/workspaces/personal $ git bulk --addworkspace personal ~/workspaces/personal
```
Notice that `<ws-root-directory>` must be an absolute path (or an environment variable pointing to an absolute path).
In the case of a **single quoted environment variable**, it will be dereferenced at `git-bulk` runtime, suitable for dynamic workspaces (*e.g.*, defined in your `.bashrc`).
As an illustration:
```bash
$ git bulk --addworkspace personal '$PERSONAL_WORKSPACE'
```
With option `--from` the URL to a single repository or a file containing multiple URLs can be added and they will be cloned directly into the workspace. Suitable for the initial setup of a multi-repo project.
```bash
$ git bulk --addworkspace projectX ~/workspaces/projectx --from https://github.com/x/project-x.git
# OR with a file containing many repositories on each line:
$ git bulk --addworkspace projectX ~/workspaces/projectx --from ~/workspaces/repositories.txt
```
with `repositories.txt` be like:
```
https://github.com/x/project-x-1.git
https://github.com/x/project-x-2.git
https://github.com/x/project-x-3.git
``` ```
Register the current directory as a workspace to `git bulk` Register the current directory as a workspace to `git bulk`
@ -323,7 +279,6 @@ $ git bulk --addcurrent personal
```bash ```bash
$ git bulk --listall $ git bulk --listall
bulkworkspaces.personal /Users/niklasschlimm/workspaces/personal bulkworkspaces.personal /Users/niklasschlimm/workspaces/personal
``` ```
Run a git command on the repositories of the current workspace: Run a git command on the repositories of the current workspace:
@ -363,78 +318,21 @@ $ git bulk --removeworkspace personal
$ git bulk --purge $ git bulk --purge
``` ```
## git brv
Pretty listing of branches sorted by the date of their last commit.
```bash
$ git brv
2020-01-14 adds-git-brv fork/adds-git-brv 1ca0d76 Fixes #700: Adds git-brv
2020-01-08 master origin/master 265b03e Merge pull request #816 from spacewander/git-sed-pathspec
```
When your repo has many branches, it can be more convenient to see this list in reverse. This can be set as the default by setting the git-extras.brv.reverse git option to true.
```bash
$ git brv --reverse
2020-01-08 master origin/master 265b03e Merge pull request #816 from spacewander/git-sed-pathspec
2020-01-14 adds-git-brv fork/adds-git-brv 1ca0d76 Fixes #700: Adds git-brv
```
## git repl ## git repl
Git read-eval-print-loop. Lets you run `git` commands without typing 'git'. Git read-eval-print-loop. Let's you run `git` commands without typing 'git'.
Commands can be prefixed with an exclamation mark (!) to be interpreted as Commands can be prefixed with an exclamation mark (!) to be interpreted as
a regular command. a regular command.
Type `exit`, `quit`, or `q` to end the repl session. Type `exit` or `quit` to end the repl session.
Any arguments to git repl will be taken as the first command to execute in
the repl.
### CONFIGURATION
Commands entered in a repl session will be saved to a history file and be available in
future sessions, similar to a shell or programming language repl. By default,
there is one global history file, ~/.git_repl_history. You can specify that your projects
each have their own independent history file. This file will be saved in .git_repl_history
at the top level of the repo, and will need to be added to the repo or global .gitignore.
```bash
# remove the --global flag to configure only an individual project to have its own history file
git config --global git-extras.repl.use-local-history "true"
```
You can specify a default command to run when hitting enter:
```bash
git config --global git-extras.repl.on-enter-command "git status -sb"
```
You can configure which character is used at the end of the prompt: (default `>`):
```bash
git config --global git-extras.repl.prompt-character "±"
```
You can specify the prefix for the prompt, or remove it (default `git`):
```bash
git config --global git-extras.repl.prefix ""
```
You can have the name of the current git repo shown in the prompt (default `false`):
```bash
git config --global git-extras.repl.show-project-name "true"
```
```bash ```bash
$ git repl $ git repl
git version 2.34.1 git version 2.9.2
git-extras version 7.3.0 git-extras version 3.0.0
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. type 'ls' to ls files below current directory,
'!command' to execute any command or just 'subcommand' to execute any git subcommand
git (master)> ls-files git (master)> ls-files
History.md History.md
@ -453,30 +351,6 @@ Straight from the shell!
git (master)> quit git (master)> quit
``` ```
## git coauthor
Add a co-author to the last commit
```bash
$ git coauthor user user@email.com
[master b62ceae] Add documentation files
Date: Sat Aug 17 17:33:53 2019 -0500
2 files changed, 145 insertions(+), 0 deletions(-)
create mode 100644 README.md
create mode 100644 CONTRIBUTING.md
$ git log -1
commit b62ceae2685e6ece071f3c3754e9b77fd0a35c88 (HEAD -> master)
Author: user person <userperson@email.com>
Date: Sat Aug 17 17:33:53 2019 -0500
Add documentation files
Co-authored-by: user <user@email.com>
```
## git commits-since ## git commits-since
List commits since `date` (defaults to "last week"): List commits since `date` (defaults to "last week"):
@ -606,26 +480,12 @@ Rename a branch locally, and sync to remote via `git push`.
``` ```
# renames any branch # renames any branch
$ git rename-branch old-name new-name $ git rename-branch new-name old-name
# renames current branch # renames current branch
$ git rename-branch new-name $ git rename-branch new-name
``` ```
## git rename-file
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.
```bash
# Rename a file
git rename-file old_filename new_filename
# Rename a directory
git rename-file old_directory new_directory
```
## git rename-tag ## git rename-tag
Rename a tag (locally and remotely). Rename a tag (locally and remotely).
@ -649,19 +509,6 @@ $ git tag
test2 test2
``` ```
## git rename-remote
Rename a git remote regardless of name conflict, and then list current git remotes.
```
$ git remote -v
origin git@myserver.com:myuser/foo.git (fetch)
origin git@myserver.com:myuser/foo.git (push)
upstream git@myserver.com:myuser/bar.git (fetch)
upstream git@myserver.com:myuser/bar.git (push)
$ git-rename-remote upstream origin
origin git@myserver.com:myuser/bar.git (fetch)
origin git@myserver.com:myuser/bar.git (push)
```
## git reauthor ## git reauthor
@ -755,7 +602,6 @@ To show just the global or just the local file's contents, you can use the follo
* `-g` or `--global` to show just the global file * `-g` or `--global` to show just the global file
* `-l` or `--local` to show just the local file * `-l` or `--local` to show just the local file
* `-p` or `--private` to show just the repository's file
```bash ```bash
$ git ignore -g $ git ignore -g
@ -774,14 +620,14 @@ build
## git ignore-io ## git ignore-io
Generate sample gitignore file from [gitignore.io](https://www.toptal.com/developers/gitignore) Generate sample gitignore file from [gitignore.io](https://www.gitignore.io)
Without option, `git ignore-io <type>` shows the sample gitignore of specified types on screen. Without option, `git ignore-io <type>` shows the sample gitignore of specified types on screen.
```bash ```bash
$ git ignore-io vim $ git ignore-io vim
# Created by https://www.toptal.com/developers/gitignore/api/vim # Created by https://www.gitignore.io/api/vim
### Vim ### ### Vim ###
[._]*.s[a-w][a-z] [._]*.s[a-w][a-z]
@ -842,13 +688,6 @@ $ git info
myBranch myBranch
* master * master
## Submodule(s):
a234567 path2submodule1/submodule1 (branch/tag)
+ b234567 path2submodule2/submodule2 (branch/tag)
- c234567 path2submodule3/submodule3 (branch/tag)
e234567 path2submodule4/submodule4 (branch/tag)
## Most Recent Commit: ## Most Recent Commit:
commit e3952df2c172c6f3eb533d8d0b1a6c77250769a7 commit e3952df2c172c6f3eb533d8d0b1a6c77250769a7
@ -856,6 +695,8 @@ $ git info
Added git-info command. Added git-info command.
Type 'git log' for more commits, or 'git show <commit id>' for full commit details.
## Configuration (.git/config): ## Configuration (.git/config):
color.diff=auto color.diff=auto
@ -881,14 +722,6 @@ If you wish to omit the config section, you may use `--no-config`:
$ git info --no-config $ git info --no-config
``` ```
## git cp
Copy a file to another one keeping its history and allowing for merge conflicts handling.
```bash
$ git cp README.md README.rst
```
## git create-branch ## git create-branch
Create local branch `name`: Create local branch `name`:
@ -933,21 +766,6 @@ Delete local and remote tag `name`:
$ git delete-tag 0.0.1 $ git delete-tag 0.0.1
``` ```
## git delete-gone-branches
Deletes all local branches whose remote-tracking branch has been deleted (i.e. branches shown as `[gone]` in `git branch -vv`). Use `--dry-run` to preview what would be deleted.
```bash
$ git delete-gone-branches
Deleted branch feature-123 (was abc1234).
Deleted branch feature-456 (was def5678).
$ git delete-gone-branches --dry-run
Would delete the following branches:
feature-123
feature-456
```
## git delete-merged-branches ## git delete-merged-branches
Deletes branches that are listed in `git branch --merged`. Deletes branches that are listed in `git branch --merged`.
@ -960,34 +778,14 @@ Deleted feature/dashboard (was 923befa).
... ...
``` ```
## git delete-squashed-branches
Deletes branches that have been "squashed-merged" into a specified branch; this branch will be checked out as a side-effect. If no branch is specified, then it will default to the current checked out branch.
```bash
$ (feature-branch) git delete-squashed-branches main
Deleted branch dependabot/bundler/kramdown-2.3.1 (was 1d3fb00).
Deleted branch dependabot/bundler/rexml-3.2.5 (was a7e4052).
$ (main) git ...
```
## git fresh-branch ## git fresh-branch
Create empty local branch `docs`: Create empty local branch `name`:
```bash ```bash
$ git fresh-branch docs $ git fresh-branch docs
``` ```
## git get
Clone repository into a subdirectory of the configured path, `"$HOME/some-dir"`:
```bash
$ git config --global --add git-extras.get.clone-path "$HOME/some-dir"
$ git get 'https://github.com/hyperupcall/bake'
```
## git guilt ## git guilt
Calculate the change in blame between two revisions Calculate the change in blame between two revisions
@ -1042,7 +840,9 @@ $ git squash HEAD~3 "Work on a feature"
Populates the file matching `authors|contributors -i` with the authors of commits, according to the number of commits per author. Populates the file matching `authors|contributors -i` with the authors of commits, according to the number of commits per author.
See the ["MAPPING AUTHORS" section](https://git-scm.com/docs/git-shortlog#_mapping_authors) of **git-shortlog**(1) to coalesce together commits by the same person. Opens the file in `$EDITOR` when set.
See the ["MAPPING AUTHORS" section](http://git-scm.com/docs/git-shortlog#_mapping_authors) of **git-shortlog**(1) to coalesce together commits by the same person.
Updating AUTHORS file: Updating AUTHORS file:
@ -1080,6 +880,15 @@ nickl-
Leila Muhtasib Leila Muhtasib
``` ```
## git back
Removes the latest commits, and add their changes to your staging area.
```
$ git back # Removes the latest commit.
$ git back 3 # Remove the latest 3 commits.
```
## git changelog ## git changelog
Generates 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 with a case insensitive match pattern and existing content will be appended to the new output generated--this behavior can be disabled by specifying the prune option (-p|--prune-old). The generated file will be opened in **$EDITOR** when set. Generates 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 with a case insensitive match pattern and existing content will be appended to the new output generated--this behavior can be disabled by specifying the prune option (-p|--prune-old). The generated file will be opened in **$EDITOR** when set.
@ -1184,13 +993,6 @@ git undo 3
Run grep as directed but replace the given files with the pattern. Run grep as directed but replace the given files with the pattern.
For example,
```bash
$ git sed 'this' 'that'
$ git sed 'this' 'that' g
$ git sed 'this' 'that' -- path/ path2/
```
## git setup ## git setup
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. 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.
@ -1203,14 +1005,6 @@ Internally this script uses `rsync` and not `scp` as the name suggests.
`git-rscp` - The reverse of `git-scp`. Copies specific files from the working directory of a remote repository to the current working directory. `git-rscp` - The reverse of `git-scp`. Copies specific files from the working directory of a remote repository to the current working directory.
The following options are available (must precede `<remote>`):
```bash
-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)
```
### Examples ### Examples
Copy unstaged files to remote. Useful when you want to make quick test without making any commits Copy unstaged files to remote. Useful when you want to make quick test without making any commits
@ -1367,11 +1161,11 @@ List all commits on the local branch that have not yet been sent to origin. Any
## git archive-file ## git archive-file
Creates a zip archive of the current git repository. The name of the archive will depend on the current HEAD of your git repository. Creates an zip archive of the current git repository. The name of the archive will depend on the current HEAD of your git repository.
## git missing ## git missing
Print out which commits are on one branch or the other but not both. Optionally, you can specify a path to limit the comparison to a specific directory or file. Print out which commits are on one branch or the other but not both.
```bash ```bash
$ git missing master $ git missing master
@ -1379,12 +1173,6 @@ $ git missing master
> 97ef387 only on master > 97ef387 only on master
``` ```
```bash
$ git missing master -- src/
< ed52989 only on current branch, in src/ directory
> 7988c4b only on master, in src/ directory
```
## git lock ## git lock
Lock a local file `filename`: Lock a local file `filename`:
@ -1428,8 +1216,8 @@ $ git reset-file .htaccess dc82b19
## git mr ## git mr
Checks out a merge request from GitLab, or a pull request from Forgejo/Codeberg. Checks out a merge request from GitLab. Usage: `git mr <ID|URL> [REMOTE]`.
Usage: `git mr <ID|URL> [REMOTE]`. Default remote is `origin`. Default remote is `origin`.
``` bash ``` bash
$ git mr 51 $ git mr 51
@ -1447,42 +1235,9 @@ From gitlab.com:owner/repository
Switched to branch 'mr/51' Switched to branch 'mr/51'
``` ```
A Forgejo/Codeberg pull request URL is also supported:
``` bash
$ git mr https://codeberg.org/owner/repository/pulls/51
From codeberg.org:owner/repository
* [new ref] refs/pull/51/head -> mr/51
Switched to branch 'mr/51'
```
Just like [git pr](#git-pr), `git mr` accepts a `clean` argument to trash all Just like [git pr](#git-pr), `git mr` accepts a `clean` argument to trash all
`mr/` branches. Ensure current branch is not one. `mr/` branches. Ensure current branch is not one.
## git paste
Sends commits to a pastebin site using pastebinit.
By default it sends the commits between your current branch
and the branch your current branch is based on (the upstream branch).
``` bash
$ git paste
https://paste.debian.net/1234567/
```
All options are passed to `git format-patch --stdout`
so you can also pass options understood by `git-rev-parse(1)`
in order to select a different set of commits.
``` bash
$ git paste @^
https://paste.debian.net/1234567/
```
See the [pastebinit documentation](https://manpages.debian.org/pastebinit)
for information about how to chose a different pastebin site to the default.
## git pr ## git pr
Checks out a pull request from GitHub Checks out a pull request from GitHub
@ -1631,99 +1386,3 @@ Sync local branch with origin/master
```bash ```bash
$ git sync origin master $ git sync origin master
``` ```
## git browse
Opens the current git repository website in your default web browser.
```bash
$ git browse
$ git browse upstream
```
## git browse-ci
Opens the current git repository CI website (e.g. GitHub Actions, GitLab CI,
Bitbucket Pipelines) in your default web browser.
```bash
$ git browse-ci
$ git browse-ci upstream
```
## git utimes
Change files modification time to their last commit date. Does not touch files that are in the working tree or index.
The `--newer` flag preserves the original modification time of files that were committed from the local repo, by only touching files that are newer than their last commit date.
```bash
git-extras$ ls -l bin | head
total 308
-rwxr-xr-x 1 vt vt 489 Jul 28 2015 git-alias
-rwxr-xr-x 1 vt vt 1043 Nov 8 13:56 git-archive-file
-rwxr-xr-x 1 vt vt 970 Nov 8 13:56 git-authors
-rwxr-xr-x 1 vt vt 267 Nov 8 13:56 git-back
-rwxr-xr-x 1 vt vt 899 Nov 8 13:56 git-browse
-rwxr-xr-x 1 vt vt 1932 Nov 8 13:56 git-brv
-rwxr-xr-x 1 vt vt 6282 Nov 8 13:56 git-bulk
-rwxr-xr-x 1 vt vt 18561 Nov 8 13:56 git-changelog
-rwxr-xr-x 1 vt vt 215 Nov 8 13:56 git-clear
git-extras$ git utimes --newer
+ touch -d 2020-05-22T10:40:29+08:00 bin/git-archive-file
+ touch -d 2017-05-05T16:02:09+08:00 bin/git-authors
+ touch -d 2020-02-23T11:41:54+08:00 bin/git-back
+ touch -d 2020-06-23T09:31:21+10:00 bin/git-browse
+ touch -d 2020-01-15T10:46:19+01:00 bin/git-brv
+ touch -d 2019-12-21T13:35:59+08:00 bin/git-bulk
+ touch -d 2019-09-05T12:41:38+08:00 bin/git-changelog
+ touch -d 2016-11-19T16:41:19+00:00 bin/git-clear
[...]
git-extras$ ls -l bin | head
total 308
-rwxr-xr-x 1 vt vt 489 Jul 28 2015 git-alias
-rwxr-xr-x 1 vt vt 1043 May 22 05:40 git-archive-file
-rwxr-xr-x 1 vt vt 970 May 5 2017 git-authors
-rwxr-xr-x 1 vt vt 267 Feb 23 2020 git-back
-rwxr-xr-x 1 vt vt 899 Jun 23 02:31 git-browse
-rwxr-xr-x 1 vt vt 1932 Jan 15 2020 git-brv
-rwxr-xr-x 1 vt vt 6282 Dec 21 2019 git-bulk
-rwxr-xr-x 1 vt vt 18561 Sep 5 2019 git-changelog
-rwxr-xr-x 1 vt vt 215 Nov 19 2016 git-clear
```
Note above, that because of the `--newer` flag, the file `git-alias` was not touched since its modified date is earlier than the commit date.
## git abort
Abort current revert, rebase, merge or cherry-pick, without the need to find exact command in history.
## git magic
Commits changes with a generated message.
## git continue
Continue current revert, rebase, merge or cherry-pick, without the need to find exact command in history.
## git wip
Create a Work In Progress(WIP) commit, which will include all changes in the
working directory. (i.e., changes to existing files, new files, removed files)
```bash
$ git wip
```
## git unwip
Undo a Work In Progress(WIP) commit and put all of those changes back into the
working directory.
```bash
$ git unwip
```

View file

@ -1,643 +1,3 @@
7.5.0 / 2026-05-09
==================
* Use standard prefix for home dir installs (#1248)
* refactor(abort): common `gitdir` (#1244)
* Fix man page usage docs (#1241)
* Fix `git-changelog` to handle whitespace in command arguments (#1232)
* chore(deps): bump bats-core/bats-action from 3.0.1 to 4.0.0 (#1230)
* add details when multiple active operations found (#1229)
* Add persistent history to `git-repl` (#1226)
* Support passing ref to `git-commits-since` (#1228)
* ability to specify command when hitting enter (#1223)
* Improve `git-repl` prompt (#1224)
* feat: `delete-branch` multiple unique branch names completions (#1221)
* chore(deps): bump actions/checkout from 5 to 6 (#1219)
* Made Git Magic use `--force-with-lease` (#1218)
* chore(deps): bump actions/stale from 9 to 10 (#1214)
* chore(deps): bump actions/setup-go from 5 to 6 (#1215)
* chore(deps): bump actions/setup-python from 5 to 6 (#1216)
* Fix minor typo in `git rename-branch` man page (#1211)
* Improvements to Bash completion (#1210)
* Fix use of `git-whatchanged` to `git-log` (#1212)
* chore(deps): bump actions/checkout from 4 to 5 (#1209)
* Bump version to 7.5.0-dev (#1207)
7.4.0 / 2025-06-19
==================
* Format comparisons, functions, and redirections to be consistent (#1201)
* add git-wip and git-unwip (#669)
* chore(deps): bump bats-core/bats-action from 3.0.0 to 3.0.1
* Mostly finish pytest to Bats conversion (#1200)
* Implement half of tests in Bats (#1187)
* Add stale bot for old PRs (#1186)
* feat(git-bulk): add new option to not follow hidden directories (#1195)
* Feat: allow git-summary showing full path of repository (#1193)
* feat(git-bulk): add new option to no follow symlinks (#1194)
* docs(git-bulk): Add zsh completion (#1190)
* fix(git-bulk): fix workspace selection when cd fails (#1197)
* fix(git-bulk): quiet find errors by default (#1196)
* fix(git-bulk): fix a bad integer expression (#1198)
* chore(deps): bump astral-sh/ruff-action from 2 to 3 (#1189)
* Fix all ShellCheck errors and add to CI (#1179)
* chore(deps): bump astral-sh/ruff-action from 1 to 2 (#1188)
* fix(ci): use poetry (#1183)
* Delete etc/test.fish (#1185)
* feat: add git-continue (#1176)
* feat: add ruff linter with ci check (#1182)
* fix(ci): missing dollar sign (#1184)
* fix(github-actions): changed files output for editorcondig-checker (#1180)
* Revert "feat: add ruff linter with ci check (#1178)" (#1181)
* feat: add ruff linter with ci check (#1178)
* Support `GITHUB_TOKEN` var for `git-fork` and `git-pull-request` (#1177)
* Bump version to 7.4.0-dev (#1175)
7.3.0 / 2024-10-20
==================
* Fix stripping trailing forward slash for git-get (#1172)
* Change git-cp to use cleaner branch approach (#1169)
* Improve warning for `git clear` (#1168)
* Enhance `git-repl` (#1160)
* Update some documentation that was out of sync (#1164)
* Use filetimes in check_integrity (#1162)
* Update git-alias.md: add brs to prevent incorrect line behavior (#1161)
* Update git-bulk.md: use correct stylization in synopsis (#1163)
* Update git-repl.md: typo: "let's" for "lets" (#1158)
* Update instructions for the OpenSUSE installation (#1157)
* Add pathspec support in `git-missing` (#1156)
* feat: add rename-file command (#1149)
* Update Commands.md (#1148)
* fix: git-summary commit count (#1147)
* tests: update dependencies (#1142)
* chore(deps): bump masesgroup/retrieve-changed-files from 2 to 3 (#1144)
* chore(deps): bump actions/setup-go from 4 to 5 (#1143)
* ci: add dependabot update for GitHub actions (#1141)
* ci: update actions (#1140)
* Bump version to 7.3.0-dev (#1139)
7.2.0 / 2024-04-21
==================
* refactor: test utils and simplify the case code (#1136)
* Add --proceed flag to git-delete-squashed-branches (#1134) (#1135)
* test(browse-ci): add unit tests (#1130)
* test(git-browse): add unit tests (#1127)
* feat: add reverse option to git-brv (#1123)
* fix(utimes): change /dev/stdin to -
* fix(utimes): Support filenames with backspaces, etc. (#1122)
* chore: add poetry to handle the tests of the git extras (#1121)
* Fix typo in ci.yml (#1120)
* Fix: harden utimes, use single quotes (#1109)
* Fix: patch git-utimes (fixes #1118) (#1119)
* add pull request manner (#1113)
* maintenance: Add my name as maintainer in AUTHORS (#1117)
* test(git-authors): add unit test (#1098)
* Add more comprensive dependencies (#1111)
* fix: git-standup find error (tj#1106) (#1110)
* Improve defaults for testing suite (#1104)
* feat: Speed up utimes (#1108)
* Overhaul CONTRIBUTING document (#1103)
* Bump version to 7.2.0-dev (#1099)
7.1.0 / 2023-10-28
==================
* git-cp: don't stop execution when merge.ff is not set (#1096)
* Allow `git cp` to create destination folder automatically (#1091)
* fix: Use `-iname` and expressions instead of `-iregex` (#1094)
* change installation source from master to main (#1092)
* test(git-archive-file): add unit test (#1084)
* ci: Use Go v1.20 for running `editorconfig-checker` (#1086)
* test(git-alias): add its unit test (#1077)
* makefile: Allow bypassing conflict check (#1080)
* fix: Restore zsh completions to working completion (#1079)
* fix: add missing quote in git-extras-completion.zsh (#1078)
* feat: Implement completion for `git coauthor` (#1074)
* fix: Move improvements via ShellCheck (#1075)
* feat(auto-test): CI with pytest (#1066)
* git-feature: add configurable branch separator (#1072)
* git-psykorebase: determine branch names with bash pattern substitution (#1073)
* git-cp: continue to merge when merge.ff is only (#1070)
* git archive-file: change base ARCHIVE_NAME (#1071)
* readme: Add secondary location of screencast (#1065)
* fix: Improve Bash hygiene (#1056)
* feat: Implement `git-get` command (#1045)
* Corrected grammar in bin/git-cp (#1061)
* Improve `git-ignore` command (#1047)
* I have made two improvements to the git-bulk: (#1054)
* Type properly `__gitex_command_successful` calls and capitalizate github to GitHub (#1049)
* Add typo checker
* Fix comment typo in git-authors (#1050)
* tweak: Fix typos (#1046)
* Makefile: init SYSCONFDIR before using it (#1044)
* Makefile: tune MANPREFIX configuration for FreeBSD (#1043)
* Makefile: set bash COMPL_DIR to FreeBSD expected defaults (#1042)
* Bump version to 7.1.0-dev (#1041)
7.0.0 / 2023-04-20
==================
* git-delete-tag: should not be quoted
* Add the current branch git summary (#1034)
* chore: Improve Bash variously (#1032)
* Change the oneline option to a tebular version in the git summary (#1031)
* chore: Remove `X`-style equality comparisons (#1028)
* chore: Various Bash improvements (#1029)
* fix: No longer pollute env with `GREP_OPTIONS`
* tweak editor config (#1027)
* revert the completion dir change on MacOS (#1026)
* Update gitignore.io urls to Toptal urls (#1025)
* Add EditorConfig file (#1022)
* Make the bulk output slimmer (#1020)
* chore: Fix more Bash inconsistencies (#1021)
* Repo status overview (#1017)
* Replace egrep with grep -E (#1019)
* chore: More Bash improvements and removing deprecations (#1016)
* chore: Improve quoting and builtin usage (#1015)
* Add summary fields (#1013)
* Fix quiet mode of the bulk command (#1014)
* git-summary: remove stray \ in grep call (#1010)
* Allow to run git-extras within git-bulk (#1008)
* Make git-summary accept multiple paths for the --lines argument (#1005)
* Makefile: ensure bash is used instead of /bin/sh (#1004)
* fix base branch in the pull-request command (#1003)
* Update docs for git abort change (#1000)
* Bump version to 6.6.0-dev (#997)
6.5.0 / 2022-10-06
==================
* git root: show '.' when using with -r in the root dir (#995)
* git standup: work as expected when no configured (#996)
* rephrase question depending on switch (#984)
* Include revert conflicts in git abort (#992)
* fix(git-effort):fork: retry: Resource temporarily unavailable #979 (#990)
* git-sync --force will sync without interaction (#989)
* fix(git-cp): keep the history of the files
* git-bulk: previous refactor redirected $PWD to stderr by mistake
* Fix lazy loading bash_completion from XDG_DATA_DIRS
* Minor changes to man/git-standup.md
* Replaced -g and -A with -F gpg|authordate
* fix(bin/git-browse): fix commit hash
* fix(bin/git-browse): fix bitbucket url
* feat: open website at file & line number or range
* #970 standup added -A flag for author date
* update docs due to formatting error in git-utimes man page
* update per shellcheck advice
* update git-utimes.{1,html} to add --newer flag
* Don't read git-extras.standup.implicit-week twice in git standup
* quote mod_s and git_s integer vars
* updates per request of spacewander
* Update Commands.md
* allow modification date updates for symbolic links
* add --newer flag and ignore files in the working tree or index
* Documented config git-extras.standup.implicit-week
* Minor change to git standup -w documentation
* Improved check if -w or -d was given to git standup
* Renamed config git-extras.standup.implicit-week from git-extras.standup-implicit-week
* Documented weekend behaviour of git standup -w
* docs(instllation): replace `git.io`
* Bump version to 6.5.0-dev
* Added config git-extras.standup-implicit-week for git standup
* Document git standup -w option
6.4.0 / 2022-04-22
==================
* Merge pull request #964 from spacewander/bro
* git-browse/browse-ci: use powershell.exe only when it is available
* Merge pull request #944 from SimonTate/feature/sem-ver-prefix
* Merge pull request #957 from jackwasey/quiet-or-stderr
* git-release: Add prefix to semver
* Merge pull request #959 from pbnj/feat/git-browse-ci
* fix: fix stderr & stdout redirect
* Merge pull request #962 from spacewander/gicc
* git-ignore-io: make sure .gi_list is created before access
* git-ignore: the directory of target file may not exist
* fix(git-browse-ci): set pipefail
* docs: fix docs
* chore: update docs & completions
* feat: implement git-browse-ci
* quiet option, errors to stderr
* Merge pull request #956 from tfendin/xargs-conflicting-args
* Removed xargs from git-effort
* Merge pull request #955 from katrinleinweber/summary-highlight-path-opt
* Highlight path option more
* Merge pull request #951 from tfendin/local-commits-all-remotes
* Updated manual for git local-commits, must track a branch
* Merge pull request #946 from tfendin/default_branch_logic
* Made local-commits work with all remotes
* Updated Readme.md after review
* Merge pull request #949 from tfendin/git-ignore-ensure-newline
* Added section about main branch to the Readme file
* Merge pull request #947 from tfendin/git-unlock-synopsis-fix
* Merge pull request #948 from tfendin/git-ignore-core-excludesFile
* Clarified comment of when newline should be added
* Updated git-extras manual after review.
* git-ignore: ensure new patterns comes after a newline
* Fixed bug in git-ignore which ignored config core.excludeFiles
* Spell corr in git-unlock synopsis
* Consider init.defaultBranch in git_extra_default_branch, changed default branch to main.
* Merge pull request #885 from spacewander/add_pkg
* doc: add packaging status
* Merge pull request #940 from overengineer/feature/magic
* Ignored error when git restore --staged fails when there is no commit
* Merge branch 'master' into merge/magic
* Added git-magic command
* Bump version to 6.4.0-dev
6.3.0 / 2021-10-02
==================
* Merge pull request #942 from spacewander/ffgd
* git-delete-squashed-branches: fail fast if can't checkout
* Merge pull request #939 from SimonTate/feature/force-clear
* git-clear: add force option
* Merge pull request #936 from tfendin/multiline_synopsis_fix
* Merge pull request #937 from tfendin/dedup_email_case_insensitive
* Merge pull request #934 from tfendin/no-merge-summary
* Adopted git --dedup-by-email to the project coding style
* summary --dedup-by-email now compares email adresses case insensitive.
* Updated lines under Synopsis in the manuals so they are commonly formatted
* Add incompability check for git summary --line --no-merges, split synopsis in its manual.
* Merge pull request #935 from ax1036/git-un-lock
* fix git (un)lock with spaced filenames
* Updated completion for git summary
* Updated documentation for git-summary --no-merges
* Added option --no-merges to git summary
* Merge pull request #932 from spacewander/git-su
* git-summary: support filtering `--line` with path
* Merge pull request #865 from pkitszel/pkitszel-git-abort
* Add git-abort
* Merge pull request #928 from duckunix/master
* Update point to github docs for forks from API
* update working
* git-fork | Update man/md/html pages to talk about need of the github personal access token
* Updated documentation (AUTHORS)
* bin/git-fork | updating for feedback from PR #928
* git-fork | update to match REST-API auth standards as of 2021-06
* Merge pull request #926 from nicokosi/patch-1
* Update install doc: Homebrew is not just for macOS
* Merge pull request #924 from allejo/feature/delete-squashed-branches
* delete-squashed-branches: Make side-effect clear in docs
* delete-squashed-branches: Make branch checkout side effect clear
* Add delete-squashed-branches command
* Merge pull request #923 from CleanMachine1/patch-1
* Picture is broken
* Merge pull request #920 from matan129/patch-1
* Ignore checked-out branches on different worktrees
* Merge pull request #917 from heirecka/remove-git-line-summary-man-page
* Bump version to 6.3.0-dev
* doc: Remove man page for git-line-summary
6.2.0 / 2021-03-26
==================
* Merge pull request #915 from spacewander/typo
* Merge pull request #914 from spacewander/xch
* Merge pull request #916 from spacewander/utimes
* fix(git-utimes): make sure it work under OS X.
* chore: fix typo
* fix(git-ignore): use $XDG_CONFIG_HOME only when it is defined
* Merge pull request #910 from 0xflotus/patch-1
* fixed small error
* Merge pull request #908 from nodeg/fix_installation
* Fix grammer
* Correct macOS spelling
* Correct openSUSE spelling and update Version
* Merge pull request #907 from bbenzikry/zinit-docs
* Update zinit docs
* Merge pull request #906 from prestontim/master
* Remove bug|refactor|chore from documentation
* Merge pull request #899 from spacewander/git-summary
* git-summary: reject invalid option
* ci: use checkout@v2
* Merge pull request #897 from spacewander/ga
* ci: move to GitHub Action
* Merge pull request #882 from vt-alt/git-utimes
* Merge pull request #896 from Aloxaf/fix_completion
* fix: zsh completion of git-coauthor command
* Merge pull request #895 from vanpipy/feature/optional-start-point-when-creeate-branch
* doc: add the usage of the parameter `--from` for create-branch and feature
* feat(feature): checkout from start point if exists
* feat(create-branch): add optional --from to set the start point
* Merge pull request #894 from alessandro308/merge-into-stashed
* Merge-into: add stash before to checkout
* Merge pull request #892 from equt/#891
* Merge pull request #888 from equt/master
* Merge pull request #889 from vanpipy/feature/delete-tag
* [ fix #891 ] Cast branch output if HEAD not exists
* [ feat ] Quote the default_path var
* [ fix ] Quote env var
* [ fix ] Quote the XDG_CACHE_HOME
* [ feat ] Use default global ignore file
* [ refactor ] Optimize var not existing check
* [ fix ] Fix hardcode ignore list file path
* [ feat ] Add XDG_CACHE_HOME check
* Merge pull request #890 from zhujian0805/master
* adding a format for git-contrib
* git-utimes: Change files modification time to their last commit date
* feat(delete-tag): get default remote from git config if exists
* Merge pull request #884 from elonderin/fix-squash-help
* squash help: re-ran make for extras and squash
* squash help: ran make man/git-extras.{html,1}
* squash help: ran make man/git-squash.{html,1}
* squash help: corrects option name in PR
* Merge remote-tracking branch 'u/master' into HEAD
* Merge pull request #886 from TheTechOddBug/master
* Fixing summary for --squash-msg option.
* Updates git-squash.md: fixes help which was unclear and had remnants from copied template it appears
* Bump version to 6.2.0-dev
6.1.0 / 2020-09-26
==================
* Merge pull request #878 from spacewander/default_br
* Merge pull request #880 from spacewander/fix_bsd_sed_regex
* git-info: fix regex for bsd sed
* Merge pull request #879 from spacewander/pass_arg
* feat: pass origin argument down to git execution
* change: migrate default branch
* Merge pull request #872 from yuravg/pr_typo
* doc: fix typo
* Merge pull request #869 from spacewander/typo
* git-back: removed
* fix typo
* Merge pull request #868 from spacewander/zsh_completion
* sort completion in alphabetical order
* remove incorrect completion
* git-info: add zsh completion
* Merge pull request #867 from yuravg/pr2source
* doc: fix argument description
* git-info: moved argument checking
* git-info: fix namespace
* doc: update derived git-info files
* doc: add description for git-info
* Add bash completion for git-info
* git-info: colorized headers
* git-info: extend configuration and commit information
* git-info: remove notes about git-log, git-show
* git-info: add info about submodules
* git-info: fix indentation, remove comment
* Merge pull request #864 from v-y-a-s/pr
* Grammar correction
* Remove $user
* corrections
* Update doc
* fix config message name
* Remove comments
* PR test
* Merge pull request #860 from soraxas/implement-fish-completions
* implement fish completions file
* Merge pull request #853 from bethesque/feat/git-browse-with-multiple-remotes
* feat(browse): automatically select origin remote if one exists, fallback to first otherwise
* Merge pull request #854 from bethesque/fix/git-browse-sed-on-mac
* fix(browse): update sed pattern to work on mac
* Bump version to 6.1.0-dev
6.0.0 / 2020-06-22
==================
* doc: remove gitter
* git-cp: remove trailing space
* Merge pull request #849 from vr8hub/undodoc
* Merge pull request #850 from vr8hub/newundo
* Tighten numeric regex
* Cleanup code
* Modify to work when only a single commit, add parameter checks
* Correct undo documentation to match code order of parameters
* Merge pull request #847 from Amorymeltzer/patch-1
* docs: Note flag for git extra --version
* Merge pull request #846 from spacewander/note
* doc: add a note about the package maintainer.
* Merge pull request #844 from ihoro/installation-via-freebsd-pkg
* Merge pull request #843 from spacewander/remove_git_feature
* break change: remove docs and other stuff of git-feature's alias
* Merge pull request #845 from alerque/remove-feature-aliases
* Remove scripts that are mere alias wrappers for git-feature
* docs: mention installation via FreeBSD pkg
* Merge pull request #840 from spacewander/archive-invalid
* Merge pull request #841 from sgleizes/bugfix/git-release-zsh-completion
* Fix missing newline escapes in git-release zsh completion
* git-archive-file: rename invalid chars in the output filename
* Merge pull request #837 from jldugger/jldugger/pr-remote-default
* Merge pull request #836 from jldugger/jldugger/feature-config
* add a default for the remote to fetch a pr from
* add a config setting for prefix
* Merge pull request #829 from cpradog/master
* update zsh completion for bug and refactor commands
* updated zsh completion script
* updated git-feature documentation
* add squash option to git-feature
* add squash option to git-feature
* Merge pull request #826 from Natim/feature/825/add-git-cp
* Simplify the code.
* Signal if the destination already exists.
* @spacewander review.
* Merge pull request #827 from Natim/fix-man-page-for-git-psykorebase
* Fix checks.
* Update man page export for git-psykorebase with ronn-ng 0.9.0
* @mschneiderwind review.
* Add documentation.
* Add first version of git-cp
* Merge pull request #824 from mapitman/master
* Fix documentation for git-browse
* Merge pull request #823 from mapitman/master
* Implement suggested changes from code review
* Add git-browse command
* Merge pull request #820 from spacewander/deprecate_git_back
* change: deprecate git-back
* Merge pull request #817 from bric3/adds-git-brv
* Fixes #770: Adds git-brv
* Merge pull request #816 from spacewander/git-sed-pathspec
* git-sed: limit paths via pathspec
* Bump version to 5.2.0-dev.
5.1.0 / 2019-12-21
==================
* git-bulk: support cloning from a relative path
* git-standuo: clarify the -a option
* git-paste: check pastebinit before running it
* git-coauthor: quote variable in the comparison
* git-bulk: don't disappoint shellcheck
* Merge pull request #812 from spacewander/git-sed-escape
* git-sed: escape special characters for tr
* Merge pull request #811 from eli-schwartz/portability
* build: do not require the nonstandard and unpredictable 'which' utility
* add repositories to workspace from source (#804)
* Merge pull request #810 from roxchgt/read-not-need-repo
* read COMMANDS_WIHOUT_REPO from not_need_git_repo file
* Merge pull request #806 from spacewander/retire_make_with_msys
* Merge pull request #808 from drasill/master
* git-ignore: support non-default .git directory
* retire the msysgit installation script
* Merge pull request #802 from spacewander/fix-git-bulk-arg-count
* git-bulk: line break is need in the SYNOPSIS doc section
* fix bug in allowedargcount() to not execute command if wrong args count
* Merge pull request #801 from spacewander/fix-delete-submodule
* git-delete-submodule: don't remove the history of deleted submodule
* Merge pull request #798 from wolviecb/signed_release
* Add flags -s and -u for signed tags
* Merge pull request #795 from spacewander/git-squash-commit-msg
* Merge pull request #793 from spacewander/git-standup-group-by-branch
* git-squash: miss completion script change
* git-squash: clean up shellcheck warnings
* git-squash: add --squash-messages to concat commit messages into one.
* git-standup: add option to limit the number of commit
* Merge pull request #789 from go2null/delete-empty-gitmodules
* git-delete-submodule: comment the DUMMY prefix and ignore the output of grep -v.
* git-standup: add separate newline between the output
* add PR changes
* add --force option to continue on error
* improve readability
* display git submodule status output before exiting
* check git submodule status before declaring success
* reorganize flow
* delete empty .gitmodules
* git-standup: add option '-B' to group the commits by branch
* Merge pull request #792 from spacewander/git-summary-cleanup
* git-summary: clean up other shellcheck warnings
* git-summary: fix incorrect active days when commits range is given
* git-summary: remove useless result function.
* Merge pull request #790 from spacewander/git-summary-merge-email
* git-summary: add --dedup-by-email to remove duplicate users
* Merge pull request #788 from spacewander/doc_git_summary_line
* git-summary: add missing example of --line option
* Merge pull request #787 from rvbuelow/patch-1
* Merge pull request #786 from mbologna/patch-1
* Use GIT_DIR environment variable to to set .git directory
* Docs: add OpenSUSE distribution information
* Merge pull request #784 from spacewander/git-effort-col-len
* git-effort: adjust column limit according to the paths
* Merge pull request #708 from pabs3/git-paste
* Regenerate docs
* Add git-paste for sending patches to pastebin
* Merge pull request #778 from pabs3/cleanups
* Switch from using /tmp to using mktemp
* Switch http URLs to https where possible
* Fix typos
* Fix an indefinite article in the documentation
* Merge pull request #776 from spacewander/update_git_pr_doc
* Merge pull request #777 from fengkx/zplugin-install-completion
* docs(Installation): :memo: add src in zplugin ice modifier to source the completion file
* git-pr: mention the limitation of -m option
* Merge pull request #772 from jacobherrington/add-git-coauthor
* Add conditional logic for linebreaks
* Ensure arguments exist
* Add git coauthor command
* improve the quality of the integrity check.
* Merge pull request #774 from jacobherrington/patch-2
* Bump version to 5.1.0-dev.
* Link to the contributing document
5.0.0 / 2019-08-16
==================
* Merge pull request #769 from spacewander/no_line_summary
* git-line-summary: should be the history
* Merge pull request #768 from bric3/fix-more-character-encoding-issues
* git-guilt: protect against encoding issues with LC_ALL=C
* Merge pull request #767 from spacewander/rename_branch_order
* git-rename-branch: change branch argument order
* Merge pull request #765 from spacewander/column_dependency
* Makefile: check dependencies before installation.
* Installation.md: add dependencies section.
* man/Readme.md: we don't need to update git-extras docs separately
* Merge pull request #763 from spacewander/strict_check_integrity
* check_integrity.sh: check more strickly.
* Merge pull request #762 from spacewander/pr_merge
* Merge pull request #761 from btmurrell/preference-for-create-branch
* implements remote pref for create-branch
* git-pr: add -m|--merge option to check out a merge commit
* impl for checking remote pref
* Merge pull request #754 from spacewander/prompt_passwd_before_2fa
* git-fork: prompt for password before 2FA code.
* check_integrity.sh: improve readability.
* Merge pull request #753 from tiemonl/GH-752_update_reauthor_documentation
* GH-752 updated documentation to show rename workaround
* Merge pull request #751 from timfeirg/master
* git-mv-remote: rename a remote regardless of any existing remotes
* Installation instructions added for CRUX (#746)
* Merge pull request #723 from bittner/feature/git-undo-soft-leave-changes-staged
* Merge pull request #744 from spacewander/git-guilt-identation
* git-guilt: avoid exceeding 80 columns.
* Bump version to 4.8.0-dev.
* Make `git undo -s` restore the staging area
4.7.0 / 2019-02-09
==================
* git-pr: emphasized that the remote is required when pulling multiple PRs.
* Merge pull request #743 from spacewander/allow_multiple_pr
* git-pr: accepted multiple GitHub URL or ID with remote.
* git-pr: added <[remote]:pr number> option.
* Merge pull request #740 from spacewander/tweak_man_readme
* doc: clarify the current working directory when building the docs.
* Merge pull request #739 from sleagon/master
* feat: Add -s(--soft) for git sync
* Merge pull request #738 from StuartFeldt/add-symlinks-to-bulk-command
* Adding support for symlink directories in bulk command
* Merge pull request #737 from smancill/standup-ensure-colors
* git-standup: ensure color usage
* Merge pull request #736 from spacewander/git_force_clone_not_need_git_repo
* misc: sort command list in alphabetical order
* git-force-clone: should not need git repo
* Merge pull request #734 from zentarul/master
* Fix missing double quotes after %DEBUG%
* install.cmd: tweak comments
* Merge pull request #732 from sachin-gupta/sg-mods
* Fix: Dev: `/E` option required with MORE for working on higher version of Windows 10 (**_Build 17134_**)
* Merge pull request #729 from orestisf1993/patch-1
* git-rename-branch: Don't fail if remote doesn't exist
* Bump version to 4.7.0-dev.
4.6.0 / 2018-08-11
==================
* Merge pull request #727 from spacewander/strip_caret
* Makefile: change the installed manpage destination in FreeBSD (#725)
* git-changelog: should remove ^0 suffix from the commit description.
* Merge pull request #722 from spacewander/git_changelog_start_commit
* git-changelog: add --start-commit option, like --start-tag but for commit
* Merge pull request #721 from spacewander/git_changelog_list_newline
* git-changelog: add missing newline to the plain output
* Merge pull request #713 from pabs3/docs
* Merge pull request #717 from flatcap/ignore-private
* optimisation of mkdir
* Ensure info dir exists
* add: git-ignore -p (private to repo)
* Merge pull request #716 from francoism90/patch-1
* Added Arch Linux
* Merge pull request #714 from spacewander/fix_detect_sed_i_support
* Documentation rebuild: git-sed.* flags argument
* Documentation rebuild: git-clear.html whitespace removal
* Documentation rebuild: git-extras.* updates
* Documentation rebuild: index.txt updates
* Documentation rebuild: dates and headers
* Remove one of the documentation building tools
* Remove some trailing whitespace
* Merge pull request #715 from tjaartvdwalt/master
* Major/Minor release should reset minor/patch versions to 0
* Fix greedy match of version number
* git-sed: fix previous bsd sed workaround
* Merge pull request #712 from spacewander/detect_sed_i_support
* git-sed: detect sed -i support and work around for the BSD sed
* Merge pull request #709 from pabs3/git-sed
* git-sed: discover a separator when the / character is used in arguments
* git-sed: pass the -r option to the xargs command
* git-sed: Allow the flags to be passed as a third argument
* Merge pull request #706 from MontakOleg/delete-branch
* Fix deleting branches without upstream
* Merge pull request #704 from ramlev/master
* Remove double path delimiters in zsh description
* Merge pull request #703 from psprint/master
* Installation.md: Zsh-plugin method which doesn't require root access
* Bump version to 4.6.0-dev.
4.5.0 / 2018-02-18 4.5.0 / 2018-02-18
================== ==================

View file

@ -1,31 +1,7 @@
# Installation # Installing git-extras
## 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 ## 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 ### Debian
```bash ```bash
@ -39,20 +15,6 @@ $ sudo $apt_pref install git-extras
$ sudo dnf install git-extras $ 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)) ### RHEL/CentOS (requires [EPEL](https://fedoraproject.org/wiki/EPEL))
```bash ```bash
@ -71,11 +33,7 @@ $ sudo apt-get install git-extras
$ nix-env -i git-extras $ nix-env -i git-extras
``` ```
### CRUX ### Mac OS X with Homebrew
[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 ```bash
$ brew install git-extras $ brew install git-extras
@ -83,16 +41,10 @@ $ 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. 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 ### Windows
First, please install `Git for Windows 2.x` from 'https://github.com/git-for-windows/git/releases'. 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. Second, clone the `git-extras` repo into any folder you like.
```bash ```bash
git clone https://github.com/tj/git-extras.git git clone https://github.com/tj/git-extras.git
# checkout the latest tag (optional) # checkout the latest tag (optional)
@ -111,24 +63,16 @@ in `c:\git`:
install.cmd "C:\git" install.cmd "C:\git"
``` ```
Last, to use `git summary` and `git ignore-io`, you need to copy Last, to use `git line-summary`, `git summary` and `git ignore-io`, you need to copy
`column.exe` from a [msys2][1] installation from `folder-your-msys2-installed/usr/bin` `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. 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 ### BSD
Use the instructions to build from source below. Make sure you are using `gmake` (GNU `make`) instead of `make`. Use the instructions to build from source below. Make sure you are using `gmake` (GNU `make`) instead of `make`.
## Building from source ## 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. 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 ```bash
@ -144,7 +88,7 @@ alternate location, specify a `PREFIX` when calling `make`.
```bash ```bash
# Non-root users can install under their home directory # Non-root users can install under their home directory
make install PREFIX=$HOME/.local make install PREFIX=$HOME/software
# For third-party software kept under /opt # For third-party software kept under /opt
make install PREFIX=/opt make install PREFIX=/opt
@ -154,7 +98,7 @@ If you want to relocate the bulk of the installation but still have configuratio
files installed to the system `/etc` dir or other alternate location, you can files installed to the system `/etc` dir or other alternate location, you can
specify `SYSCONFDIR` in addition to `PREFIX`. specify `SYSCONFDIR` in addition to `PREFIX`.
```sh ```
$ sudo make install PREFIX=/usr/local SYSCONFDIR=/etc $ sudo make install PREFIX=/usr/local SYSCONFDIR=/etc
``` ```
@ -165,26 +109,9 @@ See the Makefile for details on advanced configuration options.
One-liner: One-liner:
```bash ```bash
curl -sSL https://raw.githubusercontent.com/tj/git-extras/main/install.sh | sudo bash /dev/stdin curl -sSL http://git.io/git-extras-setup | 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 ## Updating
If you installed git-extras with a package manager, use that package manager's tools to update it. If you installed git-extras with a package manager, use that package manager's tools to update it.
@ -193,4 +120,4 @@ If you installed git-extras from source, you can run `git-extras update` to upda
Enjoy `git-extras`! Enjoy `git-extras`!
[1]: https://sourceforge.net/projects/msys2/ [1]: http://sourceforge.net/projects/msys2/

View file

@ -1,28 +1,9 @@
PREFIX ?= /usr/local PREFIX ?= /usr/local
BINPREFIX ?= "$(PREFIX)/bin" BINPREFIX ?= "$(PREFIX)/bin"
MANPREFIX ?= "$(PREFIX)/share/man/man1"
SYSCONFDIR ?= $(PREFIX)/etc 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)))) CODE_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
@ -36,33 +17,26 @@ default: install
docs: $(MAN_HTML) $(MAN_PAGES) docs: $(MAN_HTML) $(MAN_PAGES)
check: install:
@echo "Check dependencies before installation"
@./check_dependencies.sh
@echo
install: check
@if [ "$(INSTALL_VIA)" = brew ]; then \ @if [ "$(INSTALL_VIA)" = brew ]; then \
git apply brew-release.patch || { echo "Can't apply brew release patch"; exit 1; } \ git apply brew-release.patch || { echo "Can't apply brew release patch"; exit 1; } \
fi fi
mkdir -p $(DESTDIR)$(MANPREFIX) @mkdir -p $(DESTDIR)$(MANPREFIX)
mkdir -p $(DESTDIR)$(BINPREFIX) @mkdir -p $(DESTDIR)$(BINPREFIX)
@echo '... installing bins to '$(DESTDIR)$(BINPREFIX) @echo "... installing bins to $(DESTDIR)$(BINPREFIX)"
@echo '... installing man pages to '$(DESTDIR)$(MANPREFIX) @echo "... installing man pages to $(DESTDIR)$(MANPREFIX)"
$(eval TEMPFILE := $(shell mktemp -q $${TMPDIR:-/tmp}/git-extras.XXXXXX 2>/dev/null || mktemp -q)) $(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 from rw-------(default) to rwxrwxr-x, so that users can exec the scripts
@chmod 775 $(TEMPFILE) @chmod 775 $(TEMPFILE)
$(eval EXISTED_ALIASES := $(shell \ $(eval EXISTED_ALIASES := $(shell \
git config --get-regexp 'alias\..*' | awk '{print "git-" substr($$1, 7)}')) git config --get-regexp 'alias.*' | awk '{print "git-" substr($$1, 7)}'))
@$(foreach COMMAND, $(COMMANDS), \ @$(foreach COMMAND, $(COMMANDS), \
should_install='yes'; \ disable=''; \
if [ ! -z "$(filter $(COMMAND), $(EXISTED_ALIASES))" ] && [ "$$SKIP_CONFLICT_CHECK" != yes ]; then \ if test ! -z "$(filter $(COMMAND), $(EXISTED_ALIASES))"; then \
read -p "$(COMMAND) conflicts with an alias, still install it? [y/n]: " answer; \ read -p "$(COMMAND) conflicts with an alias, still install it and disable the alias? [y/n]" answer; \
if [ "$$answer" = 'n' ] || [ "$$answer" = 'N' ]; then \ test "$$answer" = 'n' -o "$$answer" = 'N' && disable="true"; \
should_install="no"; \
fi; \
fi; \ fi; \
if [ "$$should_install" = 'yes' ]; then \ if test -z "$$disable"; then \
echo "... installing $(COMMAND)"; \ echo "... installing $(COMMAND)"; \
head -1 bin/$(COMMAND) > $(TEMPFILE); \ head -1 bin/$(COMMAND) > $(TEMPFILE); \
cat $(LIB) >> $(TEMPFILE); \ cat $(LIB) >> $(TEMPFILE); \
@ -82,35 +56,13 @@ install: check
cp -f man/git-*.1 $(DESTDIR)$(MANPREFIX); \ cp -f man/git-*.1 $(DESTDIR)$(MANPREFIX); \
echo "cp -f man/git-*.1 $(DESTDIR)$(MANPREFIX)"; \ echo "cp -f man/git-*.1 $(DESTDIR)$(MANPREFIX)"; \
fi fi
@mkdir -p $(COMPL_DIR) @mkdir -p $(DESTDIR)$(SYSCONFDIR)/bash_completion.d
cp -f etc/bash_completion.sh $(COMPL_DIR)/git-extras cp -f etc/bash_completion.sh $(DESTDIR)$(SYSCONFDIR)/bash_completion.d/git-extras
@echo "" @echo ""
@echo "If you are a zsh user, you may want to 'source $(CODE_DIR)etc/git-extras-completion.zsh'" \ @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" "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) man/%.html: man/%.md
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 \
@ -132,7 +84,7 @@ uninstall:
echo "... uninstalling $(DESTDIR)$(MANPREFIX)/$(notdir $(MAN))"; \ echo "... uninstalling $(DESTDIR)$(MANPREFIX)/$(notdir $(MAN))"; \
rm -f $(DESTDIR)$(MANPREFIX)/$(notdir $(MAN)); \ rm -f $(DESTDIR)$(MANPREFIX)/$(notdir $(MAN)); \
) )
rm -f $(COMPL_DIR)/git-extras rm -f $(DESTDIR)$(SYSCONFDIR)/bash_completion.d/git-extras
clean: docclean clean: docclean
@ -140,7 +92,4 @@ docclean:
rm -f man/*.1 rm -f man/*.1
rm -f man/*.html rm -f man/*.html
test: .PHONY: default docs clean docclean install uninstall
bats ./tests
.PHONY: default docs check install uninstall clean docclean test

30
Makefile.msys Normal file
View file

@ -0,0 +1,30 @@
MANPREFIX ?= /doc/git/html
BINS = $(wildcard bin/git-*)
MANS = $(wildcard man/git-*.md)
MAN_HTML = $(MANS:.md=.html)
install:
@mkdir -p $(DESTDIR)$(MANPREFIX)
@mkdir -p $(DESTDIR)/bin
@echo "... installing bins to $(DESTDIR)/bin"
@echo "... installing man pages to $(DESTDIR)$(MANPREFIX)"
@$(foreach BIN, $(BINS), \
echo "... installing `basename $(BIN)`"; \
cp -f $(BIN) $(DESTDIR)/$(BIN); \
)
cp -f man/git-*.html $(DESTDIR)$(MANPREFIX)
@mkdir -p $(DESTDIR)/etc/bash_completion.d
cp -f etc/bash_completion.sh $(DESTDIR)/etc/bash_completion.d/git-extras
uninstall:
@$(foreach BIN, $(BINS), \
echo "... uninstalling $(DESTDIR)/$(BIN)"; \
rm -f $(DESTDIR)/$(BIN); \
)
@$(foreach MAN, $(MAN_HTML), \
echo "... uninstalling $(DESTDIR)$(MANPREFIX)/`basename $(MAN)`"; \
rm -f "$(DESTDIR)$(MANPREFIX)/`basename $(MAN)`"; \
)
rm -f $(DESTDIR)/etc/bash_completion.d/git-extras
.PHONY: install uninstall

View file

@ -1,34 +1,21 @@
# Git Extras # Git Extras
[![Gitter](https://img.shields.io/gitter/room/tj/git-extras.svg?style=flat-square)](https://gitter.im/tj/git-extras)
Little git extras. Little git extras.
## Screencasts ## Screencasts
Just getting started? Check out these 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 * [introduction](https://vimeo.com/45506445) -- covering git-ignore, git-setup, git-changelog, git-release, git-effort and more
## Installation ## Installation
See the [Installation](Installation.md) page. See [Installation](Installation.md) page.
## Commands ## Commands
Go to the [Commands](Commands.md) page for basic usage and examples. Go to [Commands](Commands.md) page for basic usage and examples.
__GIT utilities__ -- repo summary, repl, changelog population, author commit percentages and more __GIT utilities__ -- repo summary, repl, changelog population, author commit percentages and more
## Contributing
Interested in contributing? Awesome!
Please read [Contributing](CONTRIBUTING.md) before you make a PR, thanks!
## The change of the default branch
As of Git Extras 6.4 the assumed default branch name changed from `master` to `main`.
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.
For example, `git config git-extras.default-branch master`.

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

View file

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

View file

@ -3,6 +3,7 @@
LIST=false LIST=false
NO_EMAIL=false NO_EMAIL=false
FILE="" FILE=""
EDITOR=$(git var GIT_EDITOR)
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case $1 in case $1 in
@ -21,9 +22,9 @@ done
if ! $LIST; then if ! $LIST; then
FILE=$1 FILE=$1
if [ -z "$FILE" ]; then if test "$FILE" = ""; then
FILE=$(find . -mindepth 1 -maxdepth 1 \( -iname '*authors*' -o -iname '*contributors*' \) | head -n1) FILE=$(ls | egrep 'authors|contributors' -i|head -n1)
if [ -z "$FILE" ]; then if test "$FILE" = ""; then
FILE='AUTHORS' FILE='AUTHORS'
fi fi
fi fi
@ -35,11 +36,11 @@ fi
authors() { authors() {
if $NO_EMAIL; then if $NO_EMAIL; then
# email will be used to uniq authors. # eamil will be used to uniq authors.
git shortlog HEAD -sne | awk '{$1=""; sub(" ", ""); print}' | awk -F'<' '!x[$1]++' | awk -F'<' '!x[$2]++' \ git shortlog -sne | awk '{$1=""; sub(" ", ""); print}' | awk -F'<' '!x[$1]++' | awk -F'<' '!x[$2]++' \
| awk -F'<' '{gsub(/ +$/, "", $1); print $1}' | awk -F'<' '{gsub(/ +$/, "", $1); print $1}'
else else
git shortlog HEAD -sne | awk '{$1=""; sub(" ", ""); print}' | awk -F'<' '!x[$1]++' | awk -F'<' '!x[$2]++' git shortlog -sne | awk '{$1=""; sub(" ", ""); print}' | awk -F'<' '!x[$1]++' | awk -F'<' '!x[$2]++'
fi fi
} }
@ -50,5 +51,6 @@ authors() {
if $LIST; then if $LIST; then
authors authors
else else
authors >> "$FILE" authors >> $FILE
test -n "$EDITOR" && $EDITOR $FILE
fi fi

11
bin/git-back Executable file
View file

@ -0,0 +1,11 @@
#!/usr/bin/env bash
if test $# -eq 0; then
git reset --soft HEAD~1
else
if `echo $1 | grep -q [^[:digit:]]`; then
echo "$1 is not a number" 1>&2
else
git reset --soft HEAD~$1
fi
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

4
bin/git-bug Executable file
View file

@ -0,0 +1,4 @@
#!/usr/bin/env bash
ALIAS=bug
git feature -a $ALIAS "$@"

View file

@ -1,5 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
inverse=$(tput rev) invers=$(tput rev)
reset=$(tput sgr0) reset=$(tput sgr0)
txtbld=$(tput bold) txtbld=$(tput bold)
bldred=${txtbld}$(tput setaf 1) bldred=${txtbld}$(tput setaf 1)
@ -8,176 +8,115 @@ bldred=${txtbld}$(tput setaf 1)
guardedmode=false guardedmode=false
singlemode=false singlemode=false
allwsmode=false allwsmode=false
quiet=false
no_follow_symlinks=false
no_follow_hidden=false
# #
# print usage message # print usage message
# #
usage() { 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 "usage: git bulk [-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 --addworkspace <ws-name> <ws-root-directory>"
echo 1>&2 " git bulk --removeworkspace <ws-name>" echo 1>&2 " git bulk --removeworkspace <ws-name>"
echo 1>&2 " git bulk --addcurrent <ws-name>" echo 1>&2 " git bulk --addcurrent <ws-name>"
echo 1>&2 " git bulk --purge" echo 1>&2 " git bulk --purge"
echo 1>&2 " git bulk --listall" echo 1>&2 " git bulk --listall"
} }
cdfail() {
echo 1>&2 "failed to change directory: $1"
exit 1
}
# add another workspace to global git config # add another workspace to global git config
addworkspace() { function addworkspace { git config --global bulkworkspaces."$wsname" "$wsdir"; }
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 # add current directory
addcurrent() { git config --global bulkworkspaces."$wsname" "$PWD"; } function addcurrent { git config --global bulkworkspaces."$wsname" "$PWD"; }
# remove workspace from global git config # remove workspace from global git config
removeworkspace() { checkWSName && git config --global --unset bulkworkspaces."$wsname"; } function removeworkspace { checkWSName && git config --global --unset bulkworkspaces."$wsname"; }
# remove workspace from global git config # remove workspace from global git config
purge() { git config --global --remove-section bulkworkspaces; } function purge { git config --global --remove-section bulkworkspaces; }
# list all current workspace locations defined # list all current workspace locations defined
listall() { git config --global --get-regexp bulkworkspaces; } function listall { git config --global --get-regexp bulkworkspaces; }
# guarded execution of a git command in one specific repository # guarded execution of a git command in one specific repository
guardedExecution () { function 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 if $guardedmode; then
echo 1>&2 -n " Execute command here (y/n)? " echo -n "${invers}git $gitcommand${reset} -> execute here (y/n)? "
read -n 1 -r </dev/tty; echo 1>&2 read -n 1 -r </dev/tty; echo
if [[ $REPLY =~ ^[Yy]$ ]]; then if [[ $REPLY =~ ^[Yy]$ ]]; then atomicExecution; fi
git "$@"
fi
else else
git "$@" atomicExecution
fi fi
} }
# atomic git command execution with log
function atomicExecution () {
echo "${bldred}->${reset} executing ${invers}git $gitcommand${reset}" && git $gitcommand
}
# check if the passed command is known as a core git command # check if the passed command is known as a core git command
checkGitCommand () { function checkGitCommand () {
if git help -a | grep -o -q "\b${corecommand}\b"; then if git help -a | grep -o -q "\b${corecommand}\b"; then
echo 1>&2 "Core command \"$corecommand\" accepted." echo "Core command \"$corecommand\" accepted."
else else
if git config --get-regexp alias | grep -o -q "\.${corecommand} "; then if git config --get-regexp alias | grep -o -q "\.${corecommand} "; then
echo 1>&2 "Alias ${corecommand} accepted." echo "Alias ${corecommand} accepted."
else else
usage && echo 1>&2 "error: unknown GIT command: $corecommand" && exit 1 usage && echo "error: unknown GIT command: $corecommand" && exit 1
fi fi
fi fi
} }
# check if workspace name is registered # check if workspace name is registered
checkWSName () { function checkWSName () {
while read -r workspace; do while read workspace; do
parseWsName "$workspace" parseWsName "$workspace"
if [[ $rwsname == "$wsname" ]]; then return; fi if [[ $rwsname == "$wsname" ]]; then return; fi
done <<< "$(listall)" done <<< "$(echo "$(listall)")"
# when here the ws name was not found # when here the ws name was not found
usage && echo 1>&2 "error: unknown workspace name: $wsname" && exit 1 usage && echo "error: unknown workspace name: $wsname" && exit 1
} }
# parse out wsname from workspacespec # parse out wsname from workspacespec
parseWsName () { function parseWsName () {
local wsspec="$1" 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#* } 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%% *} rwsname=${wsspec#*.} && rwsname=${rwsname%% *}
} }
# detects the wsname of the current directory # detects the wsname of the current directory
wsnameToCurrent () { function wsnameToCurrent () {
while read -r workspace; do while read workspace; do
if [ -z "$workspace" ]; then continue; fi if [ -z "$workspace" ]; then continue; fi
parseWsName "$workspace" parseWsName "$workspace"
if echo "$PWD" | grep -o -q "$rwsdir"; then wsname="$rwsname" && return; fi if echo "$PWD" | grep -o -q "$rwsdir"; then wsname="$rwsname" && return; fi
done <<< "$(listall)" done <<< "$(echo "$(listall)")"
# when here then not in workspace dir # when here then not in workspace dir
echo 1>&2 "error: you are not in a workspace directory. your registered workspaces are:" && \ echo "error: you are not in a workspace directory. your registered workspaces are:" && \
wslist="$(listall)" && echo 1>&2 "${wslist:-'<no workspaces defined yet>'}" && exit 1 wslist="$(echo "$(listall)")" && echo "${wslist:-'<no workspaces defined yet>'}" && exit 1
} }
# helper to check number of arguments. # helper to check number of arguments
allowedargcount () { function allowedargcount () {
if [ "$paramcount" -ne "${1:-0}" ] && [ "$paramcount" -ne "${2:-0}" ]; then ( test "$paramcount" -ne "$1" ) && ( usage && echo 1>&2 "error: wrong number of arguments" && exit 1 )
echo 1>&2 "error: wrong number of arguments" && usage;
exit 1;
fi
} }
# execute the bulk operation # execute the bulk operation
executBulkOp () { function executBulkOp () {
checkGitCommand checkGitCommand
if ! $allwsmode && ! $singlemode; then wsnameToCurrent; fi # by default git bulk works within the 'current' workspace if ! $allwsmode && ! $singlemode; then wsnameToCurrent; fi # by default git bulk works within the 'current' workspace
listall | while read -r workspacespec; do listall | while read workspacespec; do
parseWsName "$workspacespec" parseWsName "$workspacespec"
if [[ -n $wsname ]] && [[ $rwsname != "$wsname" ]]; then continue; fi if [[ -n $wsname ]] && [[ $rwsname != "$wsname" ]]; then continue; fi
cd "$rwsdir" || exit 1 eval cd "\"$rwsdir\""
local actual=$PWD local actual=$(pwd)
[ "${quiet?}" != "true" ] && echo 1>&2 "Executing bulk operation in workspace ${inverse}$actual${reset}" echo "Executing bulk operation in workspace ${invers}$actual${reset}"
eval find . -name ".git" | while read line; do
# 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 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 eval cd "\"$gitrepodir\"" # into git repo location
local curdir=$PWD local curdir=$(pwd)
local leadingpath=${curdir#"${actual}"} local leadingpath=${curdir#${actual}}
# do not execute if we do not want to consider a ".git" directory under a hidden directory echo "Current repository: ${leadingpath%/*}/${bldred}${curdir##*/}${reset}"
if [ $no_follow_hidden = false ] || ! [[ "$leadingpath" =~ "/." ]]; then guardedExecution
guardedExecution "$@" eval cd "\"$rwsdir\"" # back to origin location of last find command
fi
cd "$rwsdir" || exit 1 # back to origin location of last find command
done done
done done
} }
@ -190,27 +129,22 @@ if [[ $paramcount -le 0 ]]; then usage; fi
# parse command parameters # parse command parameters
while [ "${#}" -ge 1 ] ; do while [ "${#}" -ge 1 ] ; do
case "$1" in case "$1" in
--quiet|-q) quiet='true' ;;
--listall|--purge) --listall|--purge)
butilcommand="${1:2}" && break ;; butilcommand="${1:2}" && break ;;
--removeworkspace|--addcurrent|--addworkspace) --removeworkspace|--addcurrent|--addworkspace)
butilcommand="${1:2}" && wsname="$2" && wsdir="$3" && if [ "$4" = "--from" ]; then source="$5"; fi && break ;; butilcommand="${1:2}" && wsname="$2" && wsdir="$3" && break ;;
--no-follow-symlinks)
no_follow_symlinks=true ;;
--no-follow-hidden)
no_follow_hidden=true ;;
-a) -a)
allwsmode=true ;; allwsmode=true ;;
-g) -g)
guardedmode=true ;; guardedmode=true ;;
-w) -w)
singlemode=true && shift && wsname="$1" && checkWSName ;; 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 ;; usage && echo 1>&2 "error: unknown argument $1" && exit 1 ;;
--*)
usage && echo 1>&2 "error: unknown argument $1" && exit 1 ;;
*) # git core commands *) # git core commands
butilcommand="executBulkOp" && corecommand="$1" && gitcommand="$*" && break ;; butilcommand="executBulkOp" && corecommand="$1" && gitcommand="$@" && break ;;
esac && shift esac && shift
done done
@ -218,14 +152,13 @@ done
if $allwsmode && $singlemode; then echo 1>&2 "error: options -w and -a are incompatible" && exit 1; fi 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 single mode check the supplied workspace name
if $singlemode; then echo 1>&2 "Selected single workspace mode in workspace: $wsname" && checkWSName; fi if $singlemode; then echo "Selected single workspace mode in workspace: $wsname" && checkWSName; fi
# check right number of arguments # check right number of arguments
case $butilcommand in case $butilcommand in
listall|purge) allowedargcount 1;; listall|purge) allowedargcount 1;;
addcurrent|removeworkspace) allowedargcount 2;; addcurrent|removeworkspace) allowedargcount 2;;
addworkspace) allowedargcount 3 5;; addworkspace) allowedargcount 3;;
esac esac
# pass the origin arguments to the 'executBulkOp' $butilcommand # run user command
$butilcommand "$@" # run user command

View file

@ -9,15 +9,6 @@ GIT_MERGELOG_FORMAT="$(git config changelog.mergeformat)"
GIT_EDITOR="$(git var GIT_EDITOR)" GIT_EDITOR="$(git var GIT_EDITOR)"
PROGNAME="git-changelog" PROGNAME="git-changelog"
git_editor() {
if test -z "${GIT_EDITOR:+set}"
then
GIT_EDITOR="$(git var GIT_EDITOR)" || return $?
fi
eval "$GIT_EDITOR" '"$@"'
}
_usage() { _usage() {
cat << EOF cat << EOF
usage: $PROGNAME options [file] usage: $PROGNAME options [file]
@ -31,12 +22,11 @@ tags exist, then all commits are output; if tags exist, then only the most-
recent commits are output up to the last identified tag. recent commits are output up to the last identified tag.
OPTIONS: OPTIONS:
-a, --all Retrieve all commits (ignores --start-tag/commit, --final-tag) -a, --all Retrieve all commits (ignores --start-tag, --final-tag)
-l, --list Display commits as a list, with no titles -l, --list Display commits as a list, with no titles
-t, --tag Tag label to use for most-recent (untagged) commits -t, --tag Tag label to use for most-recent (untagged) commits
-f, --final-tag Newest tag to retrieve commits from in a range -f, --final-tag Newest tag to retrieve commits from in a range
-s, --start-tag Oldest 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 -n, --no-merges Suppress commits from merged branches
-m, --merges-only Only uses merge commits (uses both subject and body of commit) -m, --merges-only Only uses merge commits (uses both subject and body of commit)
-p, --prune-old Replace existing Changelog entirely with new content -p, --prune-old Replace existing Changelog entirely with new content
@ -155,8 +145,6 @@ _fetchCommitRange() {
local start_tag="$2" local start_tag="$2"
local final_tag="$3" local final_tag="$3"
# This shellcheck disable is applied to the whole if-body
# shellcheck disable=SC2086
if [[ "$list_all" == true ]]; then if [[ "$list_all" == true ]]; then
git log $GIT_LOG_OPTS --pretty=format:"${CUR_GIT_LOG_FORMAT}" git log $GIT_LOG_OPTS --pretty=format:"${CUR_GIT_LOG_FORMAT}"
elif [[ -n "$final_tag" && "$start_tag" == "null" ]]; then elif [[ -n "$final_tag" && "$start_tag" == "null" ]]; then
@ -172,7 +160,7 @@ _formatCommitPlain() {
local start_tag="$1" local start_tag="$1"
local final_tag="$2" local final_tag="$2"
printf "%s\n" "$(_fetchCommitRange "false" "$start_tag" "$final_tag")" printf "%s" "$(_fetchCommitRange "false" "$start_tag" "$final_tag")"
} }
_formatCommitPretty() { _formatCommitPretty() {
@ -199,8 +187,7 @@ commitList() {
local title_tag="$1"; shift local title_tag="$1"; shift
local start_tag="$1"; shift local start_tag="$1"; shift
local final_tag="$1"; shift local final_tag="$1"; shift
local list_style="${1:-false}"; shift # enable/disable list format local list_style="${1:-false}" # enable/disable list format
local start_commit="$1"; shift
local changelog="$FILE" local changelog="$FILE"
local title_date="$(date +'%Y-%m-%d')" local title_date="$(date +'%Y-%m-%d')"
local tags_list=() local tags_list=()
@ -208,19 +195,6 @@ commitList() {
local defaultIFS="$IFS" local defaultIFS="$IFS"
local IFS="$defaultIFS" 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: # Tags look like this:
# #
@ -244,7 +218,7 @@ commitList() {
# fetch our tags # fetch our tags
local _ref _date _tag _tab='%x09' local _ref _date _tag _tab='%x09'
local _tag_regex='tag: *' local _tag_regex='tag: *'
while IFS=$'\t' read -r _ref _date _tag; do while IFS=$'\t' read _ref _date _tag; do
[[ -z "${_tag}" ]] && continue [[ -z "${_tag}" ]] && continue
# strip out tags form () # strip out tags form ()
# git v2.2.0+ supports '%D', like '%d' without the " (", ")" wrapping. One day we should use it instead. # git v2.2.0+ supports '%D', like '%d' without the " (", ")" wrapping. One day we should use it instead.
@ -291,8 +265,7 @@ commitList() {
for (( i=0; i<"${_tags_list_keys_length}"; i++ )); do for (( i=0; i<"${_tags_list_keys_length}"; i++ )); do
local __curr_tag="${tags_list_keys[$i]}" local __curr_tag="${tags_list_keys[$i]}"
local __prev_tag="${tags_list_keys[$i+1]:-null}" local __prev_tag="${tags_list_keys[$i+1]:-null}"
local __curr_date local __curr_date="$(_valueForKeyFakeAssocArray "${__curr_tag}" "${tags_list[*]}")"
__curr_date="$(_valueForKeyFakeAssocArray "${__curr_tag}" "${tags_list[*]}")"
__curr_date="${__curr_date##*=>}" __curr_date="${__curr_date##*=>}"
# output latest commits, up until the most-recent tag, these are all # output latest commits, up until the most-recent tag, these are all
@ -317,23 +290,7 @@ commitList() {
# find the specified start tag, break when found # find the specified start tag, break when found
if [[ -n "$start_tag" ]]; then if [[ -n "$start_tag" ]]; then
[[ "$start_tag" == "${__curr_tag}" ]] && _start_tag_found=true [[ "$start_tag" == "${__curr_tag}" ]] && _start_tag_found=true
if [[ "${_start_tag_found}" == true ]]; then [[ "$start_tag" != "${__curr_tag}" && "${_start_tag_found}" == true ]] && break
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 fi
# output commits made between prev_tag and curr_tag, these are all of the # output commits made between prev_tag and curr_tag, these are all of the
@ -359,9 +316,8 @@ commitListPlain() {
local list_all="${1:-false}" local list_all="${1:-false}"
local start_tag="$2" local start_tag="$2"
local final_tag="$3" local final_tag="$3"
local start_commit="$4"
commitList "$list_all" "" "$start_tag" "$final_tag" "true" "$start_commit" commitList "$list_all" "" "$start_tag" "$final_tag" "true"
} }
commitListPretty() { commitListPretty() {
@ -369,11 +325,9 @@ commitListPretty() {
local title_tag="$2" local title_tag="$2"
local start_tag="$3" local start_tag="$3"
local final_tag="$4" local final_tag="$4"
local start_commit="$5"
local title_date="$(date +'%Y-%m-%d')" local title_date="$(date +'%Y-%m-%d')"
commitList "$list_all" "$title_tag" "$start_tag" "$final_tag" "false" \ commitList "$list_all" "$title_tag" "$start_tag" "$final_tag"
"$start_commit"
} }
_exit() { _exit() {
@ -387,7 +341,7 @@ _exit() {
# The format of `ps` is different between Windows and other platforms, # 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. # so we need to calculate the total column number(COL_NUM) of header first.
# Why don't we just use the last column? # Why don't we just use the last column?
# Because the body of CMD column may contain space and be treated as multiple fields. # Because the body of CMD column may contain space and be treated as multiple fileds.
pid_list=( $(ps -f | pid_list=( $(ps -f |
awk -v ppid=$$ 'NR == 1 { awk -v ppid=$$ 'NR == 1 {
COL_NUM = NF COL_NUM = NF
@ -403,7 +357,7 @@ _exit() {
local _pid local _pid
for _pid in "${pid_list[@]}"; do for _pid in "${pid_list[@]}"; do
echo "killing: ${_pid}" echo "killing: ${_pid}"
kill -TERM "${_pid}" kill -TERM ${_pid}
done done
wait; stty sane; exit 1 wait; stty sane; exit 1
@ -420,7 +374,6 @@ main() {
"list_style:false" "list_style:false"
"title_tag:$DEF_TAG_RECENT" "title_tag:$DEF_TAG_RECENT"
"start_tag:" "start_tag:"
"start_commit:"
"final_tag:" "final_tag:"
"output_file:" "output_file:"
"use_stdout:false" "use_stdout:false"
@ -457,10 +410,6 @@ main() {
option=( $(_setValueForKeyFakeAssocArray "start_tag" "$2" "${option[*]}") ) option=( $(_setValueForKeyFakeAssocArray "start_tag" "$2" "${option[*]}") )
shift shift
;; ;;
--start-commit )
option=( $(_setValueForKeyFakeAssocArray "start_commit" "$2" "${option[*]}") )
shift
;;
-n | --no-merges ) -n | --no-merges )
GIT_LOG_OPTS='--no-merges' GIT_LOG_OPTS='--no-merges'
CUR_GIT_LOG_FORMAT="$GIT_LOG_FORMAT" CUR_GIT_LOG_FORMAT="$GIT_LOG_FORMAT"
@ -491,40 +440,13 @@ main() {
[[ -z "$CUR_GIT_LOG_FORMAT" ]] && CUR_GIT_LOG_FORMAT="$GIT_LOG_FORMAT" [[ -z "$CUR_GIT_LOG_FORMAT" ]] && CUR_GIT_LOG_FORMAT="$GIT_LOG_FORMAT"
local _tag="$(_valueForKeyFakeAssocArray "start_tag" "${option[*]}")" local _tag="$(_valueForKeyFakeAssocArray "start_tag" "${option[*]}")"
local start_commit="$(_valueForKeyFakeAssocArray "start_commit" "${option[*]}")" if [[ -n "${_tag}" ]]; then
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)" start_tag="$(git describe --tags --abbrev=0 "${_tag}" 2>/dev/null)"
if [[ -z "$start_tag" ]]; then if [[ -z "$start_tag" ]]; then
_error "Specified start-tag does not exist!" _error "Specified start-tag does not exist!"
return 1 return 1
fi fi
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 unset _tag
local _tag="$(_valueForKeyFakeAssocArray "final_tag" "${option[*]}")" local _tag="$(_valueForKeyFakeAssocArray "final_tag" "${option[*]}")"
@ -540,29 +462,26 @@ main() {
# #
# generate changelog # generate changelog
# #
local tmpfile changelog title_tag local tmpfile="$(git_extra_mktemp)"
tmpfile="$(git_extra_mktemp)" local changelog="$(_valueForKeyFakeAssocArray "output_file" "${option[*]}")"
changelog="$(_valueForKeyFakeAssocArray "output_file" "${option[*]}")" local title_tag="$(_valueForKeyFakeAssocArray "title_tag" "${option[*]}")"
title_tag="$(_valueForKeyFakeAssocArray "title_tag" "${option[*]}")"
if [[ "$(_valueForKeyFakeAssocArray "list_style" "${option[*]}")" == true ]]; then if [[ "$(_valueForKeyFakeAssocArray "list_style" "${option[*]}")" == true ]]; then
if [[ "$(_valueForKeyFakeAssocArray "list_all" "${option[*]}")" == true ]]; then if [[ "$(_valueForKeyFakeAssocArray "list_all" "${option[*]}")" == true ]]; then
commitListPlain "true" >> "$tmpfile" commitListPlain "true" >> "$tmpfile"
else else
commitListPlain "false" "$start_tag" "$final_tag" \ commitListPlain "false" "$start_tag" "$final_tag" >> "$tmpfile"
"$start_commit" >> "$tmpfile"
fi fi
else else
if [[ "$(_valueForKeyFakeAssocArray "list_all" "${option[*]}")" == true ]]; then if [[ "$(_valueForKeyFakeAssocArray "list_all" "${option[*]}")" == true ]]; then
commitListPretty "true" "$title_tag" >> "$tmpfile" commitListPretty "true" "$title_tag" >> "$tmpfile"
else else
commitListPretty "false" "$title_tag" "$start_tag" "$final_tag" \ commitListPretty "false" "$title_tag" "$start_tag" "$final_tag" >> "$tmpfile"
"$start_commit" >> "$tmpfile"
fi fi
fi fi
if [[ -z "$changelog" ]]; then if [[ -z "$changelog" ]]; then
changelog="$(find . -mindepth 1 -maxdepth 1 \( -iname '*change*' -o -iname '*history*' \) | head -n1)" changelog="$(ls | egrep 'change|history' -i | head -n1)"
if [[ -z "$changelog" ]]; then if [[ -z "$changelog" ]]; then
changelog="History.md"; changelog="History.md";
fi fi
@ -581,11 +500,12 @@ main() {
else else
cp -f "$tmpfile" "$changelog" cp -f "$tmpfile" "$changelog"
rm -f "$tmpfile" rm -f "$tmpfile"
# Use `eval` to handle GIT_EDITOR which contains space and options,
if [[ -n "$GIT_EDITOR" ]]; then # like ""C:\Program Files (x86)\Notepad++\notepad++.exe" -multiInst ".
git_editor "$changelog" || _exit # Thanks @JanSchulz to inspire me this solution
else [[ -n "$GIT_EDITOR" ]] && eval $GIT_EDITOR "$changelog"
less "$changelog" || _exit if [[ $? -ne 0 ]]; then
_exit
fi fi
fi fi

4
bin/git-chore Executable file
View file

@ -0,0 +1,4 @@
#!/usr/bin/env bash
ALIAS=chore
git feature -a $ALIAS "$@"

View file

@ -1,43 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
PROGNAME="git-clear"
FORCE=0
_usage() { echo -n "Sure? - This command may delete files that cannot be recovered, including those in .gitignore [y/N]: "
cat << EOF read ans
usage: $PROGNAME options if [ "$ans" == "y" ]
usage: $PROGNAME -h|help|? then git clean -d -f -x && git reset --hard
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 fi

View file

@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/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]: " 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 read ans
if [ "$answer" = "y" ] if [ "$ans" == "y" ]
then git clean -d -f && git reset --hard then git clean -d -f && git reset --hard
fi 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 #!/usr/bin/env bash
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

@ -4,4 +4,4 @@ user="$*"
test -z "$user" && echo "user name required." 1>&2 && exit 1 test -z "$user" && echo "user name required." 1>&2 && exit 1
git shortlog --format=format:"%h %s" --author="$user" git shortlog --author="$user"

View file

@ -5,4 +5,4 @@ if test "$1" = "--all"; then
echo echo
fi fi
echo total "$(git rev-list --count HEAD)" echo total `git rev-list --count HEAD`

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

View file

@ -2,16 +2,18 @@
set -e set -e
# Assert there is at least one branch provided # Assert there is at least one branch provided
test -z "$1" && echo "branch required." 1>&2 && exit 1 test -z $1 && echo "branch required." 1>&2 && exit 1
for branch in "$@" for branch in "$@"
do do
remote=$(git config "branch.$branch.remote" || echo "origin") remote=$(git config branch.$branch.remote)
ref=$(git config "branch.$branch.merge" || echo "refs/heads/$branch") test -z $remote && remote="origin"
ref=$(git config branch.$branch.merge)
test -z $ref && ref="refs/heads/$branch"
git branch -D "$branch" || true git branch -D $branch
# Avoid deleting local upstream # Avoid deleting local upstream
[ "$remote" = "." ] && continue [ "$remote" == "." ] && continue
git branch -d -r "$remote/$branch" || continue git branch -d -r $remote/$branch
git push "$remote" ":$ref" git push $remote :$ref
done 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 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
branches=$(git branch --no-color --merged | grep -vE "^(\*|\+)" | grep -v "$(git_extra_default_branch)" | grep -v svn) branches=$(git branch --no-color --merged | grep -v "\*" | grep -v master | grep -v svn)
if [ -n "$branches" ] if [ -n "$branches" ]
then then
echo "$branches" | xargs git branch -d echo "$branches" | xargs -n 1 git branch -d
fi 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,24 @@
#!/usr/bin/env bash #!/usr/bin/env bash
abort() { test -z "$1" && echo "submodule required" 1>&2 && exit 1
error="$1" && shift cd "$(git root)"
echo "ERROR: $*" 1>&2 test ! -f .gitmodules && echo ".gitmodules file not found" 1>&2 && exit 2
test -z "$FORCE" && exit "$error"
}
# Don't abort on failures. This allows to cleanup after a previous failure. NAME="$(echo "$1" | sed 's/\/$//g')"
[ "$1" = '--force' ] && FORCE=1 && shift test -z \
"$(git config --file=.gitmodules submodule."$NAME".url)" \
&& echo "submodule not found" 1>&2 && exit 3
test -z "$1" && abort 1 'Submodule required' # 1. Delete the relevant section from .git/config and clean submodule files
cd "$(git root)" || abort 5 'Cannot change to repository root' git submodule deinit -f "$NAME" || exit 4
test ! -f '.gitmodules' && abort 2 '.gitmodules file not found' rmdir "$NAME"
rm -rf .git/modules/"$NAME"
NAME="${1%/}" # 2. Delete the relevant line from .gitmodules
test -z "$(git config --file='.gitmodules' "submodule.$NAME.url")" \ git config --file=.gitmodules --remove-section submodule."$NAME"
&& abort 3 'Submodule not found' git add .gitmodules
# 3. Run git rm --cached path_to_submodule
# 1. Handle the .git directory git rm --cached -rf "$NAME"
# 1.a. Delete the relevant section from .git/config # 4. Need to confirm and commit the changes for yourself
git submodule deinit -f "$NAME" || abort 4 "Failed to deinitialize $NAME" echo
# 1.b. Delete empty submodule directory echo "Now submodule $NAME is deleted."
git rm -f "$NAME" echo 'Confirm with `git submodule status` and commit the changes for yourself.'
# 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,16 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Assert there is at least one tag provided # Assert there is at least one tag provided
test -z "$1" && echo "tag required." 1>&2 && exit 1 test -z $1 && echo "tag required." 1>&2 && exit 1
# 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 # Concatenate all the tag references
local_tags="" local_tags=""
@ -22,7 +13,5 @@ do
done done
# Delete all the tags # Delete all the tags
# shellcheck disable=SC2086
git tag -d $local_tags git tag -d $local_tags
# shellcheck disable=SC2086 git push origin $origin_refs
git push "$origin" $origin_refs

View file

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

View file

@ -59,14 +59,14 @@ active_days() {
color_for() { color_for() {
if [ "$to_tty" = true ]; then if [ "$to_tty" = true ]; then
if [ "$1" -gt 200 ]; then color="$(tputq setaf 1)$(tputq bold)" if [ $1 -gt 200 ]; then color="$(tputq setaf 1)$(tputq bold)"
elif [ "$1" -gt 150 ]; then color="$(tputq setaf 1)" # red elif [ $1 -gt 150 ]; then color="$(tputq setaf 1)" # red
elif [ "$1" -gt 125 ]; then color="$(tputq setaf 2)$(tputq bold)" elif [ $1 -gt 125 ]; then color="$(tputq setaf 2)$(tputq bold)"
elif [ "$1" -gt 100 ]; then color="$(tputq setaf 2)" # green elif [ $1 -gt 100 ]; then color="$(tputq setaf 2)" # green
elif [ "$1" -gt 75 ]; then color="$(tputq setaf 5)$(tputq bold)" elif [ $1 -gt 75 ]; then color="$(tputq setaf 5)$(tputq bold)"
elif [ "$1" -gt 50 ]; then color="$(tputq setaf 5)" # purplish elif [ $1 -gt 50 ]; then color="$(tputq setaf 5)" # purplish
elif [ "$1" -gt 25 ]; then color="$(tputq setaf 3)$(tputq bold)" elif [ $1 -gt 25 ]; then color="$(tputq setaf 3)$(tputq bold)"
elif [ "$1" -gt 10 ]; then color="$(tputq setaf 3)" # yellow elif [ $1 -gt 10 ]; then color="$(tputq setaf 3)" # yellow
else color="$(tputq sgr0)" # default color else color="$(tputq sgr0)" # default color
fi fi
else else
@ -84,38 +84,37 @@ effort() {
local color reset_color commits len dot f_dot i msg active local color reset_color commits len dot f_dot i msg active
reset_color="" reset_color=""
test "$to_tty" = true && reset_color="$(tputq sgr0)" test "$to_tty" = true && reset_color="$(tputq sgr0)"
if ! commit_dates=$(dates "$path"); then commit_dates=`dates "$path"`
exit 255 [ $? -gt 0 ] && exit 255
fi
# Ensure it's not just an empty line # Ensure it's not just an empty line
if [ -z "$(head -c 1 <<<"$commit_dates")" ] if [ -z "`head -c 1 <<<$(echo $commit_dates)`" ]
then then
exit 0 exit 0
fi fi
commits=$(wc -l <<<"$commit_dates") commits=`wc -l <<<"$(echo "$commit_dates")"`
color='90' color='90'
# ignore <= --above # ignore <= --above
test "$commits" -le "$above" && exit 0 test $commits -le $above && exit 0
# commits # commits
color_for $(( commits - above )) color_for $(( $commits - $above ))
len=${#path} len=${#path}
dot="." dot="."
f_dot="$path" f_dot="$path"
i=0 ; while test $i -lt $(( columns - len )) ; do i=0 ; while test $i -lt $(( $columns - $len )) ; do
f_dot=$f_dot$dot f_dot=$f_dot$dot
i=$((i+1)) i=$(($i+1))
done done
msg=$(printf " ${color}%s %-10d" "$f_dot" "$commits") msg=$(printf " ${color}%s %-10d" "$f_dot" $commits)
# active days # active days
active=$(active_days "$commit_dates") active=`active_days "$commit_dates"`
color_for $(( active - above )) color_for $(( $active - $above ))
msg="$msg $(printf "${color} %d${reset_color}\n" "$active")" msg="$msg $(printf "${color} %d${reset_color}\n" $active)"
echo "$msg" echo "$msg"
} }
@ -137,16 +136,7 @@ sort_effort() {
clear clear
echo " " echo " "
heading heading
< "$tmp" sort -rn -k 2 < $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=() declare -a paths=()
@ -187,48 +177,50 @@ fi
args_to_git_log="${args_to_git_log#\ \'\'}" args_to_git_log="${args_to_git_log#\ \'\'}"
export args_to_git_log export args_to_git_log
# set column width to match longest filename
columns=$(( $(git ls-files | awk '{ print length }' | sort -rn | head -1 ) + 5 ))
export columns
# [path ...] # [path ...]
if test "${#paths}" -eq 0; then if test "${#paths}" -eq 0; then
save_ifs=$IFS save_ifs=$IFS
IFS=$(echo -en "\n\b") IFS=`echo -en "\n\b"`
paths=($(git ls-files)) paths=(`git ls-files`)
IFS=$save_ifs IFS=$save_ifs
unset save_ifs unset save_ifs
fi 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
hide_cursor hide_cursor
trap show_cursor_and_cleanup INT trap show_cursor_and_cleanup INT
heading # export functions so subshells can call them
export -f effort
export -f color_for
export -f active_days
export -f dates
export -f tputq
export to_tty
export above
export log_args
bash_params=
# If bash exits successfully with --import-functions,
# then we need to pass it (FreeBSD probably)
bash --import-functions -c ":" 1>/dev/null 2>&1
if [ $? -eq 0 ] ; then
bash_params="--import-functions"
fi
heading
# send paths to effort # send paths to effort
nPaths=${#paths[@]} printf "%s\0" "${paths[@]}" | xargs -0 -n 1 -P 4 -I % bash $bash_params -c "effort \"%\"" | tee $tmp
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 # if more than one path, sort and print
test "$(wc -l "$tmp" | awk '{print $1}')" -gt 1 && sort_effort test "$(wc -l $tmp | awk '{print $1}')" -gt 1 && sort_effort
echo echo
show_cursor_and_cleanup show_cursor_and_cleanup

View file

@ -1,10 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
VERSION="7.6.0-dev" VERSION="4.5.0"
INSTALL_SCRIPT="https://raw.githubusercontent.com/tj/git-extras/main/install.sh" INSTALL_SCRIPT="https://raw.githubusercontent.com/tj/git-extras/master/install.sh"
update() { update() {
local bin="$(command -v git-extras)" local bin="$(which git-extras)"
local prefix=${bin%/*/*} local prefix=${bin%/*/*}
local orig=$PWD local orig=$PWD
@ -14,7 +14,7 @@ update() {
} }
updateForWindows() { updateForWindows() {
local bin="$(command -v git-extras)" local bin="$(which git-extras)"
local prefix=${bin%/*/*} local prefix=${bin%/*/*}
local orig=$PWD local orig=$PWD

View file

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

View file

@ -22,7 +22,7 @@ repository from scratch.
changed files will be reset, local branches and other remotes will be removed. changed files will be reset, local branches and other remotes will be removed.
OPTIONS: OPTIONS:
-b, --branch The branch to pull from the remote -b, --branch The branch to pull from the remote (default: master)
-h, --help Display this help message -h, --help Display this help message
" "
} }
@ -63,21 +63,21 @@ main() {
if [ -d "${destination_path}/.git" ]; then if [ -d "${destination_path}/.git" ]; then
( (
cd "${destination_path}" cd ${destination_path}
# Delete all remotes # Delete all remotes
for remote in $(git remote); do for remote in `git remote`; do
git remote rm "${remote}" git remote rm ${remote}
done done
# Add origin # Add origin
git remote add origin "${remote_url}" git remote add origin ${remote_url}
git fetch origin git fetch origin
# Set default branch # Set default branch
if [ -z "${branch:-}" ]; then if [ -z "${branch:-}" ]; then
branch=$(LC_ALL=C git remote show origin | grep -oP '(?<=HEAD branch: )[^ ]+$') branch=`LC_ALL=C git remote show origin | grep -oP '(?<=HEAD branch: )[^ ]+$'`
git remote set-head origin "${branch}" git remote set-head origin ${branch}
else else
git remote set-head origin -a git remote set-head origin -a
fi fi
@ -87,20 +87,19 @@ main() {
git reset --hard HEAD git reset --hard HEAD
# Get on the desired branch # Get on the desired branch
git checkout "${branch}" git checkout ${branch}
git reset --hard "origin/${branch}" git reset --hard origin/${branch}
# Delete all other branches # Delete all other branches
# shellcheck disable=SC2063 branches=`git branch | grep -v \* | xargs`
branches=$(git branch | grep -v '*' | xargs)
if [ -n "${branches}" ]; then if [ -n "${branches}" ]; then
git branch -D "${branches}" git branch -D ${branches}
fi fi
) )
elif [ -n "${branch:-}" ]; then elif [ -n "${branch:-}" ]; then
git clone -b "${branch}" "${remote_url}" "${destination_path}" git clone -b ${branch} ${remote_url} ${destination_path}
else else
git clone "${remote_url}" "${destination_path}" git clone ${remote_url} ${destination_path}
fi fi
} }

View file

@ -12,18 +12,14 @@ test -z "$url" && abort "github repo needs to be specified as an argument"
# validate user # validate user
echo "Enter your github username" echo "Enter your github username"
read -r user read user
[ -n "$user" ] || abort "git username required" [ -n "$user" ] || abort "git username required"
# personal access token echo "Enter github two-factor authentication code (leave blank if not set up)"
# config name is github-personal-access-token '_' is not allowed in git config read MFA_CODE
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 # extract owner + project from repo url
project=${url##*/} project=${url##*/}
owner=${url%/"$project"} owner=${url%/$project}
project=${project%.git} project=${project%.git}
if [[ $owner == git@* ]]; then if [[ $owner == git@* ]]; then
owner=${owner##*:} owner=${owner##*:}
@ -32,20 +28,19 @@ else
fi fi
# validate # validate
[[ -z "$project" || -z "$owner" ]] && abort "github repo needs to be specified as an argument" [ -z "$project" -o -z "$owner" ] && abort "github repo needs to be specified as an argument"
# create fork # create fork
if ! curl -qsf \ curl -qsf \
-X POST \ -X POST \
-u "$user:$github_personal_access_token" \ -u "$user" \
-H "X-GitHub-OTP: $MFA_CODE" \ -H "X-GitHub-OTP: $MFA_CODE" \
"https://api.github.com/repos/$owner/$project/forks" "https://api.github.com/repos/$owner/$project/forks"
then
abort "fork failed" [ $? = 0 ] || abort "fork failed"
fi
echo "Add GitHub remote branch via SSH (you will be prompted to verify the server's credentials)? (y/n)" echo "Add GitHub remote branch via SSH (you will be prompted to verify the server's credentials)? (y/n)"
read -r use_ssh read use_ssh
# Check if user has ssh configured with GitHub # Check if user has ssh configured with GitHub
if [ -n "$use_ssh" ] && ssh -T git@github.com 2>&1 | grep -qi 'success'; then if [ -n "$use_ssh" ] && ssh -T git@github.com 2>&1 | grep -qi 'success'; then
remote_prefix="git@github.com:" remote_prefix="git@github.com:"
@ -61,7 +56,7 @@ else
# clone forked repo into current dir # clone forked repo into current dir
git clone "${remote_prefix}${user}/${project}.git" "$project" git clone "${remote_prefix}${user}/${project}.git" "$project"
# add reference to origin fork so can merge in upstream changes # add reference to origin fork so can merge in upstream changes
cd "$project" || exit cd "$project"
git remote add upstream "${remote_prefix}${owner}/${project}.git" git remote add upstream "${remote_prefix}${owner}/${project}.git"
git fetch upstream git fetch upstream
fi fi

View file

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

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

@ -4,10 +4,10 @@ echo 'setting up gh-pages'
echo '-------------------' echo '-------------------'
echo 'Tell me your github account username: ' echo 'Tell me your github account username: '
read -r username read username
echo 'Now, tell me your repository name: ' echo 'Now, tell me your repository name: '
read -r repository read repository
git stash \ git stash \
&& git checkout -b 'gh-pages' \ && git checkout -b 'gh-pages' \

View file

@ -3,9 +3,9 @@
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." 1>&2 && exit 1
test -z "$dst" && echo "destination branch required." 1>&2 && 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,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
for param in "$@" for param in $*
do do
case $param in case $param in
-h) -h)
@ -31,7 +31,7 @@ do
esac esac
done done
cd "$(git-root)" || exit # cd for git blame cd "$(git-root)" # cd for git blame
MERGED_LOG=$(git_extra_mktemp) MERGED_LOG=$(git_extra_mktemp)
if [[ $EMAIL == '-e' ]] if [[ $EMAIL == '-e' ]]
then then
@ -44,13 +44,11 @@ for file in $(git diff --name-only "$@")
do do
test -n "$DEBUG" && echo "git blame $file" test -n "$DEBUG" && echo "git blame $file"
# $1 - since $2 - until # $1 - since $2 - until
# shellcheck disable=SC2086
git blame $NOT_WHITESPACE --line-porcelain "$1" -- "$file" 2> /dev/null | 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" sed -n "$PATTERN" | sort | uniq -c | sed 's/^\(.\)/- \1/' >> $MERGED_LOG
# if $2 not given, use current commit as "until" # if $2 not given, use current commit as "until"
# shellcheck disable=SC2086
git blame $NOT_WHITESPACE --line-porcelain "${2-@}" -- "$file" 2> /dev/null | 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" sed -n "$PATTERN" | sort | uniq -c | sed 's/^\(.\)/+ \1/' >> $MERGED_LOG
done done
DEBUG="$DEBUG" awk ' DEBUG="$DEBUG" awk '
@ -73,15 +71,20 @@ END {
printf("%d %s\n", contributors[people], people) printf("%d %s\n", contributors[people], people)
} }
} }
}' "$MERGED_LOG" | sort -nr | # only gawk supports built-in sort function }' $MERGED_LOG | sort -nr | # only gawk supports built-in sort function
while read -r line while read line
do do
people=${line#* } people=${line#* }
num=${line%% *} num=${line%% *}
if [[ $num -gt 0 ]] if [[ $num -gt 0 ]]
then then
printf "%-29s \033[00;32m" "$people" if [[ ${#people} -ge 29 ]]
then
printf "%-29s \033[00;32m" "$people"
else
printf "%-29s\t\033[00;32m" "$people"
fi
if [[ $num -ge 50 ]] if [[ $num -ge 50 ]]
then then
len=${#num} len=${#num}
@ -89,7 +92,7 @@ do
do do
printf "+" printf "+"
done done
printf "(%s)" "$num" printf "(%s)" $num
else else
for (( i = 0; i < num; i++ )) for (( i = 0; i < num; i++ ))
do do
@ -97,7 +100,12 @@ do
done done
fi fi
else else
printf "%-29s \033[00;31m" "$people" if [[ ${#people} -ge 29 ]]
then
printf "%-29s \033[00;31m" "$people"
else
printf "%-29s\t\033[00;31m" "$people"
fi
if [[ $num -le -50 ]] if [[ $num -le -50 ]]
then then
len=${#num} len=${#num}
@ -105,7 +113,7 @@ do
do do
printf "-" printf "-"
done done
printf "(%s)" "$num" printf "(%s)" $num
else else
for (( i = 0; i > num; i-- )) for (( i = 0; i > num; i-- ))
do do

View file

@ -1,28 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) function show_contents {
# awk script that takes 2 input files. The first one is newline deilmeted
# string of patterns to look for, second one is the file to remove them from.
# outfile has to be passed with -v upon call and is the file result is
# written to.
read -r -d '' AWK_SCRIPT <<'EOF'
# process the first "file" (newline split argument list)
NR==FNR { a[b++]=$0; next; }
# process file
{
found=0;
for (i=0; i != b; i++) {
if (a[i] == $0) { found=1; }
}
if (found == 1) { print "... removing '"$0"'"; }
else { print $0 > outfile }
}
EOF
show_contents() {
local file="${2/#~/$HOME}" local file="${2/#~/$HOME}"
if [ -f "$file" ]; then if [ -f "$file" ]; then
echo "$1 gitignore: $2" && cat "$file" echo "$1 gitignore: $2" && cat "$file"
@ -31,255 +9,58 @@ show_contents() {
fi fi
} }
cd_to_git_root() { function show_global {
local error_level="$1" show_contents Global `git config --global core.excludesfile`
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() { function add_global {
if ! git config --global core.excludesFile 2>/dev/null; then local global_gitignore=$(git config --global core.excludesfile)
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 if [ -z "$global_gitignore" ]; then
echo "Can't find global .gitignore." echo "Can't find global .gitignore."
echo "" echo ""
echo "Use 'git config --global --add core.excludesfile ~/.gitignore-global' to set the path to your global gitignore file to '~/.gitignore-global'." echo "Use 'git config --global --add core.excludesfile ~/.gitignore-global' to set the path to your global gitignore file to '~/.gitignore-global'."
echo "" echo ""
else else
add_patterns "$global_gitignore" "$@" add_patterns `git config --global core.excludesfile` "$@"
fi fi
} }
show_local() { function show_local {
cd_to_git_root --warn cd "$(git root)"
show_contents Local .gitignore show_contents Local .gitignore
} }
add_local() { function add_local {
cd_to_git_root --warn cd "$(git root)"
add_patterns .gitignore "$@" add_patterns .gitignore "$@"
} }
show_private() { function add_patterns {
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" echo "Adding pattern(s) to: $1"
local file="${1/#~/$HOME}" 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 for pattern in "${@:2}"; do
echo "... adding '$pattern'" echo "... adding '$pattern'"
(test -f "$file" && test "$pattern" && grep -q -F -x -- "$pattern" "$file") || echo "$pattern" >> "$file" (test -f "$file" && test "$pattern" && grep -q -F -x -- "$pattern" "$file") || echo "$pattern" >> "$file"
done 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 if test $# -eq 0; then
show_global show_global
echo "---------------------------------" echo "---------------------------------"
show_local show_local
echo "---------------------------------"
show_private
else else
# parse flags and args case "$1" in
level="none" -l|--local)
action="none" test $# -gt 1 && add_local "${@:2}" && echo
declare -a args show_local
while [ $# -gt 0 ]; do ;;
case "$1" in -g|--global)
-h|--help) test $# -gt 1 && add_global "${@:2}" && echo
_usage show_global
exit 0 ;;
;; *)
-l|--local) add_local "$@"
level="local" ;;
;; esac
-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,14 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
gitignore_io_url="https://www.toptal.com/developers/gitignore/api/" gitignore_io_url="https://www.gitignore.io/api/"
default_path="$HOME/.gi_list" 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() { update_gi_list() {
curl -L -s "${gitignore_io_url}/list" > "$default_path" curl -L -s "${gitignore_io_url}/list" > ~/.gi_list
} }
print_in_alphabetical_order() { print_in_alphabetical_order() {
@ -17,7 +13,7 @@ print_in_alphabetical_order() {
for ignorable in $(echo "$gi_list" | sort); for ignorable in $(echo "$gi_list" | sort);
do do
first_character=${ignorable:0:1} first_character=${ignorable:0:1}
if [[ $first_character = "$previous_first_character" ]]; then if [[ $first_character = $previous_first_character ]]; then
printf " %s" "$ignorable" printf " %s" "$ignorable"
elif [[ $first = true ]]; then elif [[ $first = true ]]; then
previous_first_character=$first_character previous_first_character=$first_character
@ -46,11 +42,12 @@ search() {
} }
print_last_modified_time() { print_last_modified_time() {
if ! gi_list_date=$(stat -c "%y" "$default_path" 2> /dev/null); then gi_list_date=$(stat -c "%y" "$default_path" 2> /dev/null)
if ! gi_list_date=$(stat -f "%t%Sm" "$default_path" 2> /dev/null); then if [ "$?" -ne 0 ]; then
if ! gi_list_date=$(date -r "$default_path" +%s 2> /dev/null); then gi_list_date=$(stat -f "%t%Sm" "$default_path" 2> /dev/null)
gi_list_date=0 if [ "$?" -ne 0 ]; then
fi gi_list_date=$(date -r "$default_path" +%s 2> /dev/null)
[ "$?" -ne 0 ] && gi_list_date=0
fi fi
fi fi
echo "Last update time: $gi_list_date" echo "Last update time: $gi_list_date"
@ -76,15 +73,15 @@ show_usage() {
echo " [-l|--list-in-table] Print available types in table format" echo " [-l|--list-in-table] Print available types in table format"
echo " [-L|--list-alphabetically] Print available types in alphabetical order " echo " [-L|--list-alphabetically] Print available types in alphabetical order "
echo " [-s|--search] <word> Search word in available types" 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 " [-t|--show-update-time] Show the last modified time of ~/.gi_list (where the list of available types is stored)"
echo " [-u|--update-list] Update $default_path" echo " [-u|--update-list] Update ~/.gi_list"
} }
check_list_exist() { check_list_exist() {
if ! [ -f "$default_path" ]; then if ! [ -f "$default_path" ]; then
echo "-----Initial gitignore.io list----" echo "-----Initial gitignore.io list----"
update_gi_list update_gi_list &
echo "-----Save to $default_path-----" echo "-----Save to $default_path-----"
echo echo
fi fi

View file

@ -1,40 +1,11 @@
#!/usr/bin/env bash #!/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() { get_config() {
cmd_get_config="$(git config --get-all git-extras.info.config-grep)" git config --list
if [ -z "$cmd_get_config" ]; then
git config --list
else
eval "$cmd_get_config"
fi
} }
most_recent_commit() { most_recent_commit() {
cmd_get_log="$(git config --get-all git-extras.info.log)" git log --max-count=1 --pretty=short
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() { local_branches() {
@ -50,30 +21,27 @@ remote_urls() {
} }
echon() { echon() {
echo "$@" echo "$@"
echo echo
} }
# Show info similar to svn
echo echo
echon "${COLOR_TITLE}## Remote URLs:${NORMAL}" echon "## Remote URLs:"
echon "$(remote_urls)" echon "$(remote_urls)"
echon "${COLOR_TITLE}## Remote Branches:${NORMAL}" echon "## Remote Branches:"
echon "$(remote_branches)" echon "$(remote_branches)"
echon "${COLOR_TITLE}## Local Branches:${NORMAL}" echon "## Local Branches:"
echon "$(local_branches)" echon "$(local_branches)"
SUBMODULES_LOG=$(submodules) echon "## Most Recent Commit:"
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)" echon "$(most_recent_commit)"
echon "Type 'git log' for more commits, or 'git show <commit id>' for full commit details."
if [ -n "$HIDE_CONFIG" ]; then if test "$1" != "--no-config"; then
echon "${COLOR_TITLE}## Configuration (.git/config):${NORMAL}" echon "## Configuration (.git/config):"
echon "$(get_config)" echon "$(get_config)"
fi fi

60
bin/git-line-summary Executable file
View file

@ -0,0 +1,60 @@
#!/usr/bin/env bash
project=${PWD##*/}
#
# list the last modified author for each line
#
single_file() {
while read 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
}
#
# list the author for all file
#
lines() {
git ls-files | single_file
}
#
# count the line count
#
count() {
lines | wc -l
}
#
# sort by author modified lines
#
authors() {
lines | sort | uniq -c | sort -rn
}
#
# list as percentage for author modified lines
#
result() {
authors | 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,
}
# summary
echo
echo " project : $project"
echo " lines : $(count)"
echo " authors :"
result
echo

View file

@ -1,3 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
git log "@{upstream}..@" "$@" ref=$(git symbolic-ref HEAD)
branch=${ref#refs/heads/}
git log origin/${branch}..${branch} $*

View file

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

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

@ -9,16 +9,7 @@ usage() {
} }
cur_branch=$(current_branch) cur_branch=$(current_branch)
if [ "${!#}" == '--ff-only' ]; then
stashed=0
if [ -n "$(git status --porcelain)" ];
then
echo "Local modifications detected, stashing"
stashed=1
git stash
fi
if [ "${!#}" = '--ff-only' ]; then
case $# in case $# in
2 ) # dest --ff 2 ) # dest --ff
git push "$(git rev-parse --show-toplevel)" "$cur_branch":"$1";; git push "$(git rev-parse --show-toplevel)" "$cur_branch":"$1";;
@ -41,8 +32,3 @@ else
usage usage
esac esac
fi fi
if [ $stashed -eq 1 ];
then
git stash pop;
fi

View file

@ -5,7 +5,7 @@ branch=$2
prefix=$3 prefix=$3
flat=0 flat=0
if test "$prefix" = "."; then if test $prefix = "."; then
prefix=$(mktemp -u 'git-merge-repo.XXXXXXX') prefix=$(mktemp -u 'git-merge-repo.XXXXXXX')
flat=1 flat=1
fi fi
@ -16,10 +16,10 @@ message=$(git log -1 --pretty=%B)
if test $flat -eq 1; then if test $flat -eq 1; then
git stash -u git stash -u
mv -i "$prefix"/* ./ mv -i $prefix/* ./
git undo git undo
git add . git add .
git commit -am "$message" git commit -am "$message"
git stash apply git stash apply
rm -drf "$prefix" rm -drf $prefix
fi fi

View file

@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
usage() { usage() {
echo 1>&2 "usage: git missing [<first branch>] <second branch> [<git log options>] [[--] <path>...]" echo 1>&2 "usage: git missing [<first branch>] <second branch> [<git log options>]"
} }
if [ "${#}" -lt 1 ] if [ "${#}" -lt 1 ]
@ -12,20 +12,9 @@ fi
declare -a git_log_args=() declare -a git_log_args=()
declare -a branches=() declare -a branches=()
declare -a pathspec=()
declare parse_path=false
for arg in "$@" ; do for arg in "$@" ; do
if [[ $parse_path == true ]]; then
pathspec+=("$@")
break
fi
case "$arg" in case "$arg" in
--)
parse_path=true
;;
--*) --*)
git_log_args+=( "$arg" ) git_log_args+=( "$arg" )
;; ;;
@ -49,4 +38,4 @@ else
exit 1 exit 1
fi fi
git log "${git_log_args[@]}" "$firstbranch"..."$secondbranch" --format="%m %h %s" --left-right -- "${pathspec[@]}" git log "${git_log_args[@]}" "$firstbranch"..."$secondbranch" --format="%m %h %s" --left-right

View file

@ -7,30 +7,22 @@ if [ -z "${1-}" ] ; then
exit 1 exit 1
fi fi
remote_ref_kind=merge-requests
if test "$1" = "clean"; then if test "$1" = "clean"; then
git for-each-ref refs/heads/mr/* --format='%(refname)' | while read -r ref; do git for-each-ref refs/heads/mr/* --format='%(refname)' | while read ref; do
git branch -D "${ref#refs/heads/}" git branch -D ${ref#refs/heads/}
done done
exit 0 exit 0
elif [[ $1 =~ ^(https?://[^/]+/(.+))/merge_requests/([0-9]+).*$ ]]; then elif [[ $1 =~ ^(https?://[^/]+/(.+))/merge_requests/([0-9]+).*$ ]]; then
remote=${BASH_REMATCH[1]}.git remote=${BASH_REMATCH[1]}.git
id=${BASH_REMATCH[3]} 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 else
id=$1 id=$1
remote=${2:-origin} remote=${2:-origin}
fi fi
branch=mr/$id branch=mr/$id
remote_ref=refs/$remote_ref_kind/$id/head remote_ref=refs/merge-requests/$id/head
git fetch -fu "$remote" "$remote_ref:$branch" git fetch -fu $remote $remote_ref:$branch
git checkout "$branch" git checkout $branch
git config --local --replace "branch.$branch.merge" "$remote_ref" git config --local --replace branch.$branch.merge $remote_ref
git config --local --replace "branch.$branch.remote" "$remote" git config --local --replace branch.$branch.remote $remote

View file

@ -9,15 +9,15 @@ do
file="$file"' '"$i" file="$file"' '"$i"
shift shift
done done
test -n "$*" && range=("$@") test -n "$*" && range="$*"
test -z "$file" && echo "file required." 1>&2 && exit 1 test -z "$file" && echo "file required." 1>&2 && exit 1
if [ -z "${range[*]}" ] if [ -z "$range" ]
then then
git filter-branch -f --index-filter "git rm -r --cached ""$file"" --ignore-unmatch" \ git filter-branch -f --index-filter "git rm -r --cached ""$file"" --ignore-unmatch" \
--prune-empty --tag-name-filter cat -- --all --prune-empty --tag-name-filter cat -- --all
else else
# $range is an array so that we can forward multiple rev-list arguments # don't quote $range so that we can forward multiple rev-list arguments
git filter-branch -f --index-filter "git rm -r --cached ""$file"" --ignore-unmatch" \ git filter-branch -f --index-filter "git rm -r --cached ""$file"" --ignore-unmatch" \
--prune-empty --tag-name-filter cat -- "${range[@]}" --prune-empty --tag-name-filter cat -- $range
fi 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 +1,22 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Based on https://gist.github.com/gnarf/5406589 and https://gist.github.com/jhnns/d654d9d6da6d3b749986 # 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 if test "$1" = "clean"; then
git for-each-ref refs/heads/pr/* --format='%(refname)' | while read -r ref; do git for-each-ref refs/heads/pr/* --format='%(refname)' | while read ref; do
git branch -D "${ref#refs/heads/}" git branch -D ${ref#refs/heads/}
done done
exit 0
elif [[ "$1" =~ ^[0-9]+$ ]]; then elif [[ $1 =~ ^(https?://[^/]+/(.+))/pull/([0-9]+).*$ ]]; then
remote_pref=${2:-$(git config --get git-extras.pr.remote)} remote=${BASH_REMATCH[1]}.git
remote=${remote_pref:-origin} id=${BASH_REMATCH[3]}
branch=pr/$id
else
test -z $1 && echo "pr number required." 1>&2 && exit 1
remote=${2:-origin}
id=$1 id=$1
branch=pr/$id branch=pr/$id
pull "$remote" "$id" "$branch"
else
rc=1
while [ "$1" != "" ]; do
pull_pr_if_matched "$1"
rc=$?
shift
done
exit "$rc"
fi fi
git fetch -fu $remote refs/pull/$id/head:$branch && \
git checkout $branch && \
git config --local --replace branch.$branch.merge refs/pull/$id/head && \
git config --local --replace branch.$branch.remote $remote;

View file

@ -5,7 +5,7 @@ SECONDARY_BRANCH=""
FF="--ff" FF="--ff"
CONTINUE="no" CONTINUE="no"
current_branch() { function current_branch() {
git rev-parse --abbrev-ref HEAD git rev-parse --abbrev-ref HEAD
} }
@ -19,7 +19,7 @@ function usage()
echo " -c|--continue: Continue after the user updates conflicts." echo " -c|--continue: Continue after the user updates conflicts."
} }
while [[ $# -gt 0 ]] while [[ $# > 0 ]]
do do
key="$1" key="$1"
@ -50,17 +50,19 @@ fi
if [[ "$CONTINUE" == "yes" ]]; then if [[ "$CONTINUE" == "yes" ]]; then
TARGET_BRANCH=$(current_branch) TARGET_BRANCH=$(current_branch)
SECONDARY_BRANCH=${TARGET_BRANCH%"-rebased-on-top-of-"*} set $(echo "$TARGET_BRANCH" | sed -e "s/-rebased-on-top-of-/\n/g")
PRIMARY_BRANCH=${TARGET_BRANCH#*"-rebased-on-top-of-"} if [[ $# != 2 ]]; then
if [[ "${SECONDARY_BRANCH}-rebased-on-top-of-${PRIMARY_BRANCH}" != $TARGET_BRANCH ]]; then
echo "Couldn't continue rebasing on ${TARGET_BRANCH}" echo "Couldn't continue rebasing on ${TARGET_BRANCH}"
exit 30 # Impossible to detect PRIMARY_BRANCH AND SECONDARY_BRANCH exit 30 # Impossible to detect PRIMARY_BRANCH AND SECONDARY_BRANCH
fi fi
SECONDARY_BRANCH=$1
PRIMARY_BRANCH=$2
echo "Continuing rebasing of $SECONDARY_BRANCH on top of $PRIMARY_BRANCH" echo "Continuing rebasing of $SECONDARY_BRANCH on top of $PRIMARY_BRANCH"
git commit || exit 51 git commit || exit 51
git branch -d "${SECONDARY_BRANCH}" || exit 52 git branch -d ${SECONDARY_BRANCH} || exit 52
git branch -m "${TARGET_BRANCH}" "${SECONDARY_BRANCH}" || exit 53 git branch -m ${TARGET_BRANCH} ${SECONDARY_BRANCH} || exit 53
elif [[ "$PRIMARY_BRANCH" == "" ]]; then elif [[ "$PRIMARY_BRANCH" == "" ]]; then
usage usage
@ -69,13 +71,14 @@ else
echo "Rebasing $SECONDARY_BRANCH on top of $PRIMARY_BRANCH" echo "Rebasing $SECONDARY_BRANCH on top of $PRIMARY_BRANCH"
TARGET_BRANCH="${SECONDARY_BRANCH}-rebased-on-top-of-${PRIMARY_BRANCH}" TARGET_BRANCH="${SECONDARY_BRANCH}-rebased-on-top-of-${PRIMARY_BRANCH}"
git checkout "${PRIMARY_BRANCH}" || exit 41 git checkout ${PRIMARY_BRANCH} || exit 41
git checkout -b "${TARGET_BRANCH}" || exit 42 git checkout -b ${TARGET_BRANCH} || exit 42
git merge ${SECONDARY_BRANCH} ${FF} \
-m "Psycho-rebased branch ${SECONDARY_BRANCH} on top of ${PRIMARY_BRANCH}"
if git merge "${SECONDARY_BRANCH}" ${FF} \ if [[ $? == 0 ]]; then
-m "Psycho-rebased branch ${SECONDARY_BRANCH} on top of ${PRIMARY_BRANCH}"; then git branch -d ${SECONDARY_BRANCH} || exit 43
git branch -d "${SECONDARY_BRANCH}" || exit 43 git branch -m ${TARGET_BRANCH} ${SECONDARY_BRANCH} || exit 44
git branch -m "${TARGET_BRANCH}" "${SECONDARY_BRANCH}" || exit 44
else else
echo "Resolve the conflict and run ``${PROGRAM} --continue``." echo "Resolve the conflict and run ``${PROGRAM} --continue``."
exit 1 exit 1

View file

@ -28,12 +28,15 @@ json() {
EOF EOF
} }
# personal access token # user
# 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) user=$(git config user.email)
test -z "$github_personal_access_token" && abort "GITHUB_TOKEN or git config git-extras.github-personal-access-token required" if [ -z "$user" ]; then
user="$EMAIL"
fi
test -z "$user" && abort "git config user.email required"
# branch # branch
@ -43,7 +46,7 @@ if [ -z "$remote" ]; then
echo 'no upstream found, push to origin as default' echo 'no upstream found, push to origin as default'
remote="origin" remote="origin"
fi fi
[ "$remote" = "." ] && abort "the upstream should be a remote branch." [ "$remote" == "." ] && abort "the upstream should be a remote branch."
# make sure it's pushed # make sure it's pushed
@ -64,7 +67,7 @@ echo " create pull-request for $project '$branch'"
echo echo
printf " title: " && read -r title printf " title: " && read -r title
printf " body: " && read -r body printf " body: " && read -r body
printf " base [%s]: " "$(git_extra_default_branch)" && read -r base printf " base [master]: " && read -r base
printf " GitHub two-factor authentication code (leave blank if not set up): " && read -r mfa_code printf " GitHub two-factor authentication code (leave blank if not set up): " && read -r mfa_code
echo echo
@ -72,14 +75,12 @@ echo
if [ -z "$base" ] if [ -z "$base" ]
then then
base="$(git_extra_default_branch)" base="master"
fi fi
body=$(json "$title" "$body" "$branch" "$base") body=$(json "$title" "$body" "$branch" "$base")
curl \ curl -u "$user" \
-X POST \ -H "X-GitHub-OTP: $mfa_code" \
-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" "https://api.github.com/repos/$project/pulls" -d "$body"

View file

@ -17,26 +17,26 @@ fi
# Use a temporary index. # Use a temporary index.
index=$(git_extra_mktemp) index=$(git_extra_mktemp)
cleanup() { cleanup() {
rm "$index" rm $index
exit 2 exit 2
} }
trap cleanup INT trap cleanup 2
# Go back in history while parent commits are available. # Go back in history while parent commits are available.
echo "Trying to find a commit the patch applies to..." echo "Trying to find a commit the patch applies to..."
rev=$(git rev-parse HEAD) rev=$(git rev-parse HEAD)
while [ $? = 0 ] while [ $? = 0 ]
do do
GIT_INDEX_FILE=$index git read-tree "$rev" GIT_INDEX_FILE=$index git read-tree $rev
# Try to apply the patch. # Try to apply the patch.
GIT_INDEX_FILE=$index git apply --cached "$1" &>/dev/null GIT_INDEX_FILE=$index git apply --cached $1 >/dev/null 2>&1
patch_failed=$? patch_failed=$?
# Do it again, but show the error, if the problem is the patch itself. # Do it again, but show the error, if the problem is the patch itself.
if [ $patch_failed = 128 ] if [ $patch_failed = 128 ]
then then
GIT_INDEX_FILE=$index git apply --index --check "$1" GIT_INDEX_FILE=$index git apply --index --check $1
exit $patch_failed exit $patch_failed
fi fi
@ -45,19 +45,19 @@ do
then then
# Manufacture a commit. # Manufacture a commit.
tree=$(GIT_INDEX_FILE=$index git write-tree) tree=$(GIT_INDEX_FILE=$index git write-tree)
commit=$(git commit-tree "$tree" -p "$rev" -m "$1") commit=$(git commit-tree $tree -p $rev -m "$1")
rm "$index" rm $index
echo "Patch applied to $(git rev-parse --short "$rev") as $(git rev-parse --short "$commit")" echo "Patch applied to $(git rev-parse --short $rev) as $(git rev-parse --short $commit)"
git cherry-pick "$commit" git cherry-pick $commit
exit $? exit $?
fi fi
rev=$(git rev-parse --verify -q "$rev^") rev=$(git rev-parse --verify -q $rev^)
done done
# No compatible commit found. Restore. # No compatible commit found. Restore.
echo "Failed to find a commit the patch applies to." echo "Failed to find a commit the patch applies to."
rm "$index" rm $index
exit 1 exit 1

4
bin/git-refactor Executable file
View file

@ -0,0 +1,4 @@
#!/usr/bin/env bash
ALIAS=refactor
git feature -a $ALIAS "$@"

View file

@ -4,17 +4,17 @@ set -e
hook() { hook() {
local hook=.git/hooks/$1.sh local hook=.git/hooks/$1.sh
# compat without extname # compat without extname
if test ! -f "$hook"; then if test ! -f $hook; then
hook=.git/hooks/$1 hook=.git/hooks/$1
fi fi
if test -f "$hook"; then if test -f $hook; then
echo "... $1" echo "... $1"
shift shift
if test -x "$hook"; then if test -x $hook; then
$hook "$@" $hook "$@"
else else
. "$hook" "$@" . $hook "$@"
fi fi
fi fi
} }
@ -34,29 +34,12 @@ if test $# -gt 0; then
-c) need_changelog=true;; -c) need_changelog=true;;
-r) remote=$2; shift ;; -r) remote=$2; shift ;;
-m) msg=$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) --semver)
test -z "$2" && test -z "$2" &&
exit_with_msg "major/minor/patch required for --semver option" exit_with_msg "major/minor/patch required for --semver option"
semver=$2 semver=$2
shift shift
;; ;;
--prefix)
test -z "$2" &&
exit_with_msg "prefix string required for --prefix option"
prefix="$2"
shift
;;
--no-empty-commit) no_empty_commit=true;; --no-empty-commit) no_empty_commit=true;;
--) shift; hook_args="$hook_args $*"; break;; --) shift; hook_args="$hook_args $*"; break;;
*) test -z "$version" && version=$1 ;; *) test -z "$version" && version=$1 ;;
@ -75,7 +58,7 @@ if test $# -gt 0; then
latest_tag=$(git describe --tags "$(git rev-list --tags --max-count=1)") latest_tag=$(git describe --tags "$(git rev-list --tags --max-count=1)")
if [[ ! "$latest_tag" =~ \ 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 ^([^0-9]*)([0-9]|[1-9][0-9]+)\.([0-9]|[1-9][0-9]+)\.([0-9]|[1-9][0-9]+)(.*) ]]; then
echo "the latest tag doesn't match semver format requirement" 1>&2 echo "the latest tag doesn't match semver format requirement" 1>&2
exit 1 exit 1
fi fi
@ -92,9 +75,9 @@ if test $# -gt 0; then
(( ++version )) (( ++version ))
case "$semver" in case "$semver" in
major ) version="$prefix${BASH_REMATCH[1]}$version.0.0${BASH_REMATCH[5]}" ;; major ) version="${BASH_REMATCH[1]}$version.${BASH_REMATCH[3]}.${BASH_REMATCH[4]}${BASH_REMATCH[5]}" ;;
minor ) version="$prefix${BASH_REMATCH[1]}${BASH_REMATCH[2]}.$version.0${BASH_REMATCH[5]}" ;; minor ) version="${BASH_REMATCH[1]}${BASH_REMATCH[2]}.$version.${BASH_REMATCH[4]}${BASH_REMATCH[5]}" ;;
patch ) version="$prefix${BASH_REMATCH[1]}${BASH_REMATCH[2]}.${BASH_REMATCH[3]}.$version${BASH_REMATCH[5]}" ;; patch ) version="${BASH_REMATCH[1]}${BASH_REMATCH[2]}.${BASH_REMATCH[3]}.$version${BASH_REMATCH[5]}" ;;
esac esac
fi fi
@ -118,17 +101,8 @@ if test $# -gt 0; then
git commit -a -m "$msg" --allow-empty git commit -a -m "$msg" --allow-empty
fi 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 # shellcheck disable=SC2086
git tag "${sign_args[@]}" $version -a -m "$msg" \ git tag $version -a -m "$msg" \
&& git push $remote --tags \ && git push $remote --tags \
&& git push $remote \ && git push $remote \
&& hook post-release $hook_args \ && hook post-release $hook_args \

View file

@ -2,17 +2,11 @@
set -e set -e
# Assert there is at least one branch provided # Assert there is at least one branch provided
test -z "$1" && echo "new branch name required." 1>&2 && exit 1 test -z $1 && echo "new branch name required." 1>&2 && exit 1
if [ -z "$2" ]; then new_branch="$1"
new_branch="$1" old_branch="${2-$(git symbolic-ref --short -q HEAD)}"
old_branch="$(git symbolic-ref --short -q HEAD)" remote=$(git config branch."$old_branch".remote)
else
new_branch="$2"
old_branch="$1"
fi
remote=$(git config branch."$old_branch".remote; true)
git branch -m "$old_branch" "$new_branch" git branch -m "$old_branch" "$new_branch"
# check if the branch is tracking a remote branch # check if the branch is tracking a remote branch

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

@ -3,10 +3,10 @@
old=$1 old=$1
new=$2 new=$2
test -z "$old" && echo "old tag name required." 1>&2 && exit 1 test -z $old && echo "old tag name required." 1>&2 && exit 1
test -z "$new" && echo "new 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 $new $old
git tag -d "$old" git tag -d $old
git push origin "$new" git push origin $new
git push origin ":refs/tags/$old" git push origin :refs/tags/$old

View file

@ -1,99 +1,43 @@
#!/usr/bin/env bash #!/usr/bin/env bash
shopt -s extglob
git version git version
echo "git-extras version ""$(git-extras -v)" 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." echo "type 'ls' to ls files below current directory, '!command' to execute any command or just 'subcommand' to execute any git subcommand"
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 # Current branch
cur=$(git symbolic-ref HEAD 2> /dev/null | cut -d/ -f3-) cur=`git symbolic-ref HEAD 2> /dev/null | cut -d/ -f3-`
# Prompt # Prompt
if test -n "$cur"; then if test -n "$cur"; then
cur_string=" ($cur)" prompt="git ($cur)> "
else else
cur_string="" prompt="git> "
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)
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 fi
prompt_base="$prefix$project_name$cur_string$es_string$prompt_character" # Readline
prompt_base_stripped=$(echo "$prompt_base" | awk '{$1=$1};1') read -e -r -p "$prompt" cmd
prompt="$prompt_base_stripped "
# Use arguments as a command if any are provided. # EOF
if [ $# -ne 0 ]; then test $? -ne 0 && break
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 # History
if [[ ! "$cmd" =~ ^[[:space:]]*$ ]]; then history -s "$cmd"
history -s "$cmd"
fi
# Built-in commands # Built-in commands
case $cmd in case $cmd in
ls) cmd=ls-files;; ls) cmd=ls-files;;
"") "") continue;;
on_enter_cmd=$(git config --get --default '' git-extras.repl.on-enter-command) quit|exit) break;;
if test -n "$on_enter_cmd"; then
cmd="$on_enter_cmd"
else
continue
fi
;;
quit|exit|q) break;;
esac esac
history -a
if [[ $cmd == !* ]]; then if [[ $cmd == !* ]]; then
# shellcheck disable=SC2086
eval ${cmd:1} eval ${cmd:1}
elif [[ $cmd == git* ]]; then elif [[ $cmd == git* ]]; then
# shellcheck disable=SC2086
eval $cmd eval $cmd
else else
eval git "$cmd" eval git "$cmd"
fi fi
exit_status=$?
done done
echo echo

View file

@ -4,14 +4,14 @@ file="$1"
commit="$2" commit="$2"
if [[ -f $file ]]; then if [[ -f $file ]]; then
git rm --cached -q -f -- "$file" git rm --cached -q -f -- $file
if [[ -z $commit ]]; then if [[ -z $commit ]]; then
git checkout HEAD -- "$file" git checkout HEAD -- $file
echo "Reset '$1' to HEAD" echo "Reset '$1' to HEAD"
else else
git checkout "$commit" -- "$file" git checkout $commit -- $file
echo "Reset '$1' to $commit" echo "Reset '$1' to $commit"
fi fi
else else
echo "File '$1' not found in $PWD" echo "File '$1' not found in `pwd`"
fi fi

View file

@ -6,13 +6,7 @@ git_root() {
# get the relative path of current path according to root of repo # get the relative path of current path according to root of repo
git_root_relative() { git_root_relative() {
rel=$(git rev-parse --show-prefix) 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 if test $# -eq 0; then

View file

@ -18,7 +18,7 @@ function _test_git_scp()
function set_remote() function set_remote()
{ {
remote=$1 remote=$1
if [ "$(git remote | grep -c -- "^$remote$")" -eq 0 ] if [ $(git remote | grep -c -- ^$remote$) -eq 0 ]
then then
COLOR_RED COLOR_RED
echo "Remote $remote does not exist in your git config" echo "Remote $remote does not exist in your git config"
@ -39,9 +39,8 @@ function php_lint()
test ! -f "$i" && continue test ! -f "$i" && continue
case "$i" in case "$i" in
*\.php|*\.phtml) *\.php|*\.phtml)
if ! php -l "$i" > /dev/null; then php -l "$i" > /dev/null
error_count[${#error_count[@]}]="$i" [ $? -gt 0 ] && error_count[${#error_count[@]}]="$i"
fi
;; ;;
esac esac
done done
@ -59,17 +58,17 @@ function php_lint()
function _dos2unix() function _dos2unix()
{ {
command -v dos2unix > /dev/null && dos2unix "$@" command -v dos2unix > /dev/null && dos2unix $@
return 0 return 0
} }
function _sanitize() function _sanitize()
{ {
git config --get-all extras.scp.sanitize | while read -r i git config --get-all extras.scp.sanitize | while read i
do do
case $i in case $i in
php_lint) php_lint "$@";; # git config --global --add extras.scp.sanitize php_lint php_lint) php_lint $@;; # git config --global --add extras.scp.sanitize php_lint
dos2unix) _dos2unix "$@";; # git config --global --add extras.scp.sanitize dos2unix dos2unix) _dos2unix $@;; # git config --global --add extras.scp.sanitize dos2unix
esac esac
done done
return $? return $?
@ -77,26 +76,14 @@ function _sanitize()
function scp_and_stage function scp_and_stage
{ {
local verbose=0 interactive=0 dry_run=0 set_remote $1
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 shift
local refhead local refhead="$(git rev-parse --quiet --verify $1)"
refhead="$(git rev-parse --quiet --verify "$1")"
if [ -n "$refhead" ] if [ -n "$refhead" ]
then then
shift shift
[ "$(git branch --contains "$refhead" | grep -c '\*')" -eq 0 ] && [ $(git branch --contains "$refhead" | grep -c '\*') -eq 0 ] &&
_error "refhead provided is not part of current branch" _error "refhead provided is not part of current branch"
fi fi
@ -105,117 +92,46 @@ function scp_and_stage
list=$(git ls-files "$@")" "$(git ls-files -o "$@") list=$(git ls-files "$@")" "$(git ls-files -o "$@")
elif [ -n "$refhead" ] elif [ -n "$refhead" ]
then then
[ "$verbose" -eq 1 ] && git --no-pager diff --stat "$refhead" git diff --stat $refhead
list=$(git diff "$refhead" --name-only) list=$(git diff $refhead --name-only)
else else
[ "$verbose" -eq 1 ] && git --no-pager diff git diff
list=$(git diff --name-only) list=$(git diff --name-only)
fi fi
deleted=$(for i in $list; do [ -f "$i" ] || echo "$i"; done) deleted=$(for i in $list; do [ -f $i ] || echo $i; done)
list=$(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" ] if [ -n "$list" ]
then then
local _TMP=${0///} local _TMP=${0///}
# shellcheck disable=SC2086 echo "$list" > $_TMP &&
echo "$list" > "$_TMP" _sanitize $list &&
if [ "$dry_run" -eq 1 ] || _sanitize $list _info Pushing to $remote \($(git config remote.$remote.url)\) &&
then rsync -rlDv --files-from=$_TMP ./ "$(git config remote.$remote.url)/" &&
_info "Pushing to $remote ($(git config "remote.$remote.url"))" git add --force $list &&
if [ "$dry_run" -eq 1 ] rm $_TMP
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 fi
deleted=$(for i in $deleted; do echo "$(git config "remote.$remote.url" | cut -d: -f2)/$i"; done) deleted=$(for i in $deleted; do echo $(git config remote.$remote.url | cut -d: -f2)/$i; done)
if [ -n "$deleted" ] [ -n "$deleted" ] &&
then COLOR_RED &&
COLOR_RED echo Deleted remote files &&
echo Deleted remote files ssh $(git config remote.$remote.url | cut -d: -f1) -t "rm $deleted" &&
if [ "$dry_run" -eq 1 ] echo "$deleted"
then COLOR_RESET
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() function reverse_scp()
{ {
local verbose=0 interactive=0 dry_run=0 set_remote $1
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 shift
local _TMP=${0///} local _TMP=${0///}
echo "$@" > "$_TMP" echo $@ > $_TMP &&
rsync -rlDv --files-from=$_TMP "$(git config remote.$remote.url)/" ./ &&
local status=0 rm $_TMP
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() function _info()
@ -229,18 +145,13 @@ function _usage()
{ {
echo "Usage: echo "Usage:
git scp -h|help|? git scp -h|help|?
git scp [options] <remote> [ref|file..] # scp and stage your files to specified remote git scp <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 scp <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 git rscp <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 case $1 in
-v|verbose|--verbose) grep -A100 '^#* OPTIONS #*$' "$0" ;; -v|verbose|--verbose) grep -A100 '^#* OPTIONS #*$' $0;;
esac esac
exit exit
} }
@ -250,18 +161,18 @@ function _error()
[ $# -eq 0 ] && _usage && exit 0 [ $# -eq 0 ] && _usage && exit 0
echo echo
echo "ERROR: $*" echo ERROR: "$@"
echo echo
exit 1 exit 1
} }
### OPTIONS ### ### OPTIONS ###
case $(basename "$0") in case $(basename $0) in
git-scp) git-scp)
case $1 in case $1 in
''|-h|'?'|help|--help) shift; _test_git_scp; _usage "$@";; ''|-h|'?'|help|--help) shift; _test_git_scp; _usage $@;;
*) scp_and_stage "$@";; *) scp_and_stage $@;;
esac esac
;; ;;
git-rscp) reverse_scp "$@";; git-rscp) reverse_scp $@;;
esac esac

View file

@ -2,10 +2,10 @@
usage() { usage() {
cat <<EOF cat <<EOF
usage: git sed [ -c ] [ -f <flags> ] <search> <replacement> [ <flags> ] usage: git sed [ -c ] [ -f <flags> ] <search> <replacement>
Run git grep and then send results to sed for replacement with the 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. given flags, if -f is provided.
Also runs git commit if -c is provided. Also runs git commit if -c is provided.
EOF EOF
@ -16,8 +16,7 @@ do_commit() {
true true
} }
pathspec= while [ "X$1" != "X" ]; do
while [ "$1" != "" ]; do
case "$1" in case "$1" in
-c|--commit) -c|--commit)
if git status --porcelain | grep .; then if git status --porcelain | grep .; then
@ -33,7 +32,7 @@ actual command:
} }
;; ;;
-f|--flags) -f|--flags)
if [ "$2" = "" ]; then if [ "X$2" = "X" ]; then
usage usage
echo "missing argument for $1" echo "missing argument for $1"
exit 1 exit 1
@ -45,22 +44,16 @@ actual command:
usage usage
exit exit
;; ;;
--)
pathspec="$*"
break
;;
-*) -*)
usage usage
echo "unknown flag: $1" echo "unknown flag: $1"
exit 1 exit 1
;; ;;
*) *)
if [ "$search" = "" ]; then if [ "X$search" = "X" ]; then
search="$1" search="$1"
elif [ "$replacement" = "" ]; then elif [ "X$replacement" = "X" ]; then
replacement="$1" replacement="$1"
elif [ "$flags" = "" ]; then
flags="$1"
else else
usage usage
echo "too many arguments: $1" echo "too many arguments: $1"
@ -71,34 +64,6 @@ actual command:
shift shift
done done
all="$search$replacement$flags" command="git grep -lz '$search' | xargs -0 sed -i 's/$search/$replacement/$flags'"
case "$all" in git grep -lz "$search" | xargs -0 sed -i "s/$search/$replacement/$flags"
*/*)
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 do_commit

View file

@ -2,7 +2,7 @@
COMMIT_MESSAGE='Initial commit' COMMIT_MESSAGE='Initial commit'
if [ "$1" = "-m" ]; then if [ "$1" == "-m" ]; then
COMMIT_MESSAGE=$2 COMMIT_MESSAGE=$2
shift; shift shift; shift
fi fi

View file

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

View file

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

View file

@ -1,26 +1,7 @@
#!/usr/bin/env bash #!/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" src="$1"
msg="$2" 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() { is_branch() {
git show-ref --verify --quiet "refs/heads/$src" git show-ref --verify --quiet "refs/heads/$src"
@ -31,8 +12,7 @@ is_commit_reference() {
} }
is_on_current_branch() { is_on_current_branch() {
local commit_sha local commit_sha=`git rev-parse "$src"`
commit_sha=$(git rev-parse "$src")
git rev-list HEAD | git rev-list HEAD |
grep -q -- "$commit_sha" grep -q -- "$commit_sha"
} }
@ -43,8 +23,8 @@ commit_if_msg_provided() {
fi fi
} }
prompt_continuation_if_squashing_default_branch() { prompt_continuation_if_squashing_master() {
if [[ $src == $(git_extra_default_branch) ]]; then if [[ $src =~ ^master$ ]]; then
read -p "Warning: squashing '$src'! Continue [y/N]? " -r read -p "Warning: squashing '$src'! Continue [y/N]? " -r
if ! [[ $REPLY =~ ^[Yy]$ ]]; then if ! [[ $REPLY =~ ^[Yy]$ ]]; then
echo "Exiting" echo "Exiting"
@ -54,26 +34,19 @@ prompt_continuation_if_squashing_default_branch() {
} }
squash_branch() { squash_branch() {
prompt_continuation_if_squashing_default_branch prompt_continuation_if_squashing_master
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 git merge --squash "$src" || exit 1 # quits if `git merge` fails
commit_if_msg_provided commit_if_msg_provided
} }
squash_current_branch() { 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 git reset --soft "$src" || exit 1 # quits if `git reset` fails
commit_if_msg_provided commit_if_msg_provided
} }
if is_branch; then if `is_branch`; then
squash_branch squash_branch
elif is_commit_reference && is_on_current_branch; then elif `is_commit_reference` && `is_on_current_branch`; then
squash_current_branch squash_current_branch
else else
echo "Source branch or commit reference required." 1>&2 && exit 1 echo "Source branch or commit reference required." 1>&2 && exit 1

View file

@ -22,7 +22,8 @@ EOF
error() { error() {
if [[ -n "$1" ]]; then if [[ -n "$1" ]]; then
echo "error: $1" >&2 local msg=$( echo "error: $1" )
echo -e "${msg}" >&2
fi fi
usage usage
exit 1 exit 1
@ -72,7 +73,7 @@ parse_options() {
done done
ID="$1" ID="$1"
MSG="${*:2}" MSG="${@:2}"
} }

View file

@ -5,21 +5,17 @@
usage() { usage() {
cat <<EOS cat <<EOS
Usage: 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>] git standup [-a <author name>] [-w <weekstart-weekend>] [-d <days-ago>] [-m <max-dir-depth>] [-g] [-h] [-f]
-a - Specify author to restrict search to, default to current git user. -a - Specify author to restrict search to
Use "-a all" if you don't want the restriction.
-w - Specify weekday range to limit search to -w - Specify weekday range to limit search to
-m - Specify the depth of recursive directory search -m - Specify the depth of recursive directory search
-L - Toggle inclusion of symbolic links in 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 number of days back to include
-D - Specify the date format for "git log" (default: relative) -D - Specify the date format for "git log" (default: relative)
-h - Display this help screen -h - Display this help screen
-g - Show if commit is GPG signed (G) or not (N)
-f - Fetch the latest commits beforehand -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: Examples:
git standup -a "John Doe" -w "MON-FRI" -m 3 git standup -a "John Doe" -w "MON-FRI" -m 3
@ -35,7 +31,7 @@ in_git_repo=$?
# Use colors, but only if connected to a terminal, and that terminal # Use colors, but only if connected to a terminal, and that terminal
# supports them. # supports them.
if command -v tput &>/dev/null; then if which tput >/dev/null 2>&1; then
ncolors=$(tput colors) ncolors=$(tput colors)
fi fi
if [[ -t 1 ]] && [[ -n "$ncolors" ]] && [[ "$ncolors" -ge 8 ]] ; then if [[ -t 1 ]] && [[ -n "$ncolors" ]] && [[ "$ncolors" -ge 8 ]] ; then
@ -47,7 +43,6 @@ if [[ -t 1 ]] && [[ -n "$ncolors" ]] && [[ "$ncolors" -ge 8 ]] ; then
BOLD=$(tput bold) BOLD=$(tput bold)
UNDERLINE=$(tput smul) UNDERLINE=$(tput smul)
NORMAL=$(tput sgr0) NORMAL=$(tput sgr0)
COLOR=always
else else
RED="" RED=""
GREEN="" GREEN=""
@ -57,18 +52,13 @@ else
BOLD="" BOLD=""
UNDERLINE="" UNDERLINE=""
NORMAL="" NORMAL=""
COLOR=never
fi fi
# Only enable exit-on-error after the non-critical colorization stuff, # Only enable exit-on-error after the non-critical colorization stuff,
# which may fail on systems lacking tput or terminfo # which may fail on systems lacking tput or terminfo
set -e set -e
RANGE_SPECIFIED= while getopts "hgfd:a:w:m:D:L" opt; do
COMMIT_DATE_FORMAT=%cd
USE_GPG_FORMAT=no
while getopts "hgfF:Bd:a:w:m:D:n:L" opt; do
case $opt in case $opt in
h) h)
usage usage
@ -82,8 +72,7 @@ while getopts "hgfF:Bd:a:w:m:D:n:L" opt; do
fi fi
;; ;;
d) d)
test -n "$RANGE_SPECIFIED" && warn "-d option is conflict with -w" test -n "$SINCE" && warn "-d option is conflict with -w"
RANGE_SPECIFIED=yes
if [ "$OPTARG" -lt 1 ]; then if [ "$OPTARG" -lt 1 ]; then
>&2 echo "Specify days less than one is invalid" >&2 echo "Specify days less than one is invalid"
exit 1 exit 1
@ -91,11 +80,10 @@ while getopts "hgfF:Bd:a:w:m:D:n:L" opt; do
SINCE="$OPTARG days ago" SINCE="$OPTARG days ago"
;; ;;
w) w)
if [ -n "$RANGE_SPECIFIED" ]; then if [ -n "$SINCE" ]; then
warn "-w option is conflict with -d" warn "-w option is conflict with -d"
continue continue
fi fi
RANGE_SPECIFIED=yes
week_range=${OPTARG} week_range=${OPTARG}
week_start="${week_range%%-*}" week_start="${week_range%%-*}"
@ -127,29 +115,7 @@ while getopts "hgfF:Bd:a:w:m:D:n:L" opt; do
GIT_DATE_FORMAT=${OPTARG} GIT_DATE_FORMAT=${OPTARG}
;; ;;
g) g)
warn "-g option is deprecated, use '-F gpg' instead" GIT_PRETTY_FORMAT="%C(yellow)gpg: %G?%Creset"
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 usage
@ -161,7 +127,7 @@ done
shift $((OPTIND-1)) shift $((OPTIND-1))
if [[ $# -gt 0 ]]; then if [[ $# -gt 0 ]]; then
warn "please upgrade to new-style interface. Run 'git help standup' to get more info." warn "please upgrate to new-style interface. Run 'git help standup' to get more info."
if [[ $# -gt 3 ]] ; then if [[ $# -gt 3 ]] ; then
usage usage
exit 1 exit 1
@ -171,76 +137,30 @@ if [[ $# -gt 0 ]]; then
UNTIL=$3 UNTIL=$3
fi fi
AUTHOR=${AUTHOR:="$(git config user.name || echo '')"} AUTHOR=${AUTHOR:="$(git config user.name)"}
if [ -z "${AUTHOR}" ]; then SINCE=${SINCE:=yesterday}
warn "please configure an author with 'git config user.name' or specify an author via '-a'" UNTIL=${UNTIL:=today}
exit 1
fi
FETCH_LAST_COMMIT=${FETCH_LAST_COMMIT:=false} FETCH_LAST_COMMIT=${FETCH_LAST_COMMIT:=false}
MAXDEPTH=${MAXDEPTH:=2} MAXDEPTH=${MAXDEPTH:=2}
GIT_PRETTY_FORMAT="%Cred%h%Creset - %s %Cgreen(${COMMIT_DATE_FORMAT}) %C(bold blue)<%an>%Creset" GIT_PRETTY_FORMAT="%Cred%h%Creset - %s %Cgreen(%cd) %C(bold blue)<%an>%Creset $GIT_PRETTY_FORMAT"
GIT_DATE_FORMAT=${GIT_DATE_FORMAT:=relative} 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 \ GIT_LOG_COMMAND="git --no-pager log \
--all
--no-merges --no-merges
--since \"$SINCE\" --since \"$SINCE\"
--until \"$UNTIL\" --until \"$UNTIL\"
--author=\"$AUTHOR\" --author=\"$AUTHOR\"
--abbrev-commit --abbrev-commit
--oneline --oneline
--color=$COLOR
--pretty=format:'$GIT_PRETTY_FORMAT' --pretty=format:'$GIT_PRETTY_FORMAT'
--date='$GIT_DATE_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 ## For when the command has been run in a non-repo directory
if [[ $in_git_repo != 0 ]]; then if [[ $in_git_repo != 0 ]]; then
## Set delimiter to newline for the loop ## Set delimiter to newline for the loop
IFS=$'\n' IFS=$'\n'
## Recursively search for git repositories ## Recursively search for git repositories
# shellcheck disable=SC2086
PROJECT_DIRS=$(find $INCLUDE_LINKS . -maxdepth "$MAXDEPTH" -mindepth 0 -name .git) PROJECT_DIRS=$(find $INCLUDE_LINKS . -maxdepth "$MAXDEPTH" -mindepth 0 -name .git)
# Fetch the latest commits, if required # Fetch the latest commits, if required
@ -268,12 +188,11 @@ if [[ $in_git_repo != 0 ]]; then
pushd "$DIR" > /dev/null pushd "$DIR" > /dev/null
## Show the detail only if it is a git repository ## Show the detail only if it is a git repository
if [[ -d ".git" || -f ".git" ]] ; then if [[ -d ".git" || -f ".git" ]] ; then
if GITOUT=$(git_output); then if GITOUT=$(eval ${GIT_LOG_COMMAND}); then
## Only output if there is some activities ## Only output if there is some activities
if [[ -n "$GITOUT" ]] ; then if [[ ! -z "$GITOUT" ]] ; then
echo "${BOLD}${UNDERLINE}${YELLOW}$(basename "$DIR")${NORMAL}" echo "${BOLD}${UNDERLINE}${YELLOW}$(basename "$DIR")${NORMAL}"
echo "$GITOUT" echo "$GITOUT"
echo ""
fi fi
else else
echo "Repository under $DIR could not be queried." >&2 echo "Repository under $DIR could not be queried." >&2
@ -287,8 +206,8 @@ else
git fetch --all > /dev/null 2>&1 git fetch --all > /dev/null 2>&1
fi fi
if GITOUT=$(git_output); then if GITOUT=$(eval ${GIT_LOG_COMMAND}); then
if [[ -n "$GITOUT" ]] ; then if [[ ! -z "$GITOUT" ]] ; then
echo "$GITOUT" echo "$GITOUT"
else else
if [[ $AUTHOR = '.*' ]] ; then if [[ $AUTHOR = '.*' ]] ; then

View file

@ -3,79 +3,24 @@
cd "$(git root)" || { echo "Can't cd to top level directory";exit 1; } cd "$(git root)" || { echo "Can't cd to top level directory";exit 1; }
PROJECT_FULL_PATH= commit=""
SUMMARY_BY_LINE= test $# -ne 0 && commit=$@
DEDUP_BY_EMAIL= project=${PWD##*/}
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> # get date for the given <commit>
# #
commit_date() {
# the $1 can be empty date() {
# shellcheck disable=SC2086 git log --pretty='format: %ai' $1 | cut -d ' ' -f 2
git log $MERGES_ARG --pretty='format: %ai' "$1" | cut -d ' ' -f 2
} }
# #
# get active days for the given <commit> # get active days for the given <commit>
# #
active_days() { active_days() {
commit_date "$1" | sort -r | uniq | awk ' date $1 | sort -r | uniq | awk '
{ sum += 1 } { sum += 1 }
END { print sum } END { print sum }
' '
@ -84,66 +29,28 @@ active_days() {
# #
# get the commit total # get the commit total
# #
commit_count() { commit_count() {
# shellcheck disable=SC2086 git log --oneline $commit | wc -l | tr -d ' '
git rev-list $MERGES_ARG --count "$commit"
} }
# #
# total file count # total file count
# #
file_count() { file_count() {
git ls-files | wc -l | tr -d ' ' 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 # list authors
# #
format_authors() {
authors() {
# a rare unicode character is used as separator to avoid conflicting with # a rare unicode character is used as separator to avoid conflicting with
# author name. However, Linux column utility will escape tab if separator # author name. However, Linux column utility will escape tab if separator
# specified, so we do unesaping after it. # specified, so we do unesaping after it.
LC_ALL=C awk ' git shortlog -n -s $commit | LC_ALL=C awk '
{ args[NR] = $0; sum += $0 } { args[NR] = $0; sum += $0 }
END { END {
for (i = 1; i <= NR; ++i) { for (i = 1; i <= NR; ++i) {
@ -156,104 +63,29 @@ format_authors() {
# #
# fetch repository age from oldest commit # fetch repository age from oldest commit
# #
repository_age() { repository_age() {
git log --reverse --pretty=oneline --format="%ar" | head -n 1 | LC_ALL=C sed 's/ago//' git log --reverse --pretty=oneline --format="%ar" | head -n 1 | LC_ALL=C sed 's/ago//'
} }
# # summary
# fetch repository age of the latest commit
#
last_active() {
git log --pretty=oneline --format="%ar" -n 1
}
# if test "$1" = "--line"; then
# list the last modified author for each line shift
# git line-summary "$@"
single_file() { echo
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 else
print_summary
echo
echo " project : $project"
echo " repo age :" $(repository_age)
echo " active :" $(active_days) days
echo " commits :" $(commit_count)
if test "$commit" = ""; then
echo " files :" $(file_count)
fi
echo " authors : "
authors
echo
fi fi

View file

@ -7,12 +7,10 @@ function _usage()
Usage: Usage:
${command} [<remote> <branch>] ${command} [<remote> <branch>]
${command} -h | --help ${command} -h | --help
${command} -s | --soft
${command} -f | --force
Sync local branch with <remote>/<branch>. 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. 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). All changes and untracked files and directories will be removed.
Examples: Examples:
Sync with upstream of local branch: Sync with upstream of local branch:
@ -20,12 +18,6 @@ Examples:
Sync with origin/master: Sync with origin/master:
${command} origin master ${command} origin master
Sync without cleaning untracked files:
${command} -s
Sync without interaction:
${command} -f
EOS EOS
} }
@ -37,12 +29,6 @@ function main()
_usage _usage
exit exit
;; ;;
-s | --soft)
local soft="true"
;;
-f | --force)
local force="YES"
;;
* ) * )
if [ "${remote}" = "" ]; then if [ "${remote}" = "" ]; then
local remote="$1" local remote="$1"
@ -59,14 +45,14 @@ function main()
done done
local remote_branch local remote_branch
if [ -z "${remote}" ]; then if [ "${remote}" = "" ]; then
if ! remote_branch="$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null)"; 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." echo "There is no upstream information of local branch."
exit 1 exit 1
fi fi
local branch="$(git rev-parse --abbrev-ref --symbolic-full-name @)" local branch="$(git rev-parse --abbrev-ref --symbolic-full-name @)"
local remote=$(git config "branch.${branch}.remote") local remote=$(git config "branch.${branch}.remote")
elif [ -z "${branch}" ]; then elif [ "${branch}" = "" ]; then
echo -e "Error: too few arguments.\n" echo -e "Error: too few arguments.\n"
_usage _usage
exit 1 exit 1
@ -74,22 +60,12 @@ function main()
remote_branch="${remote}/${branch}" remote_branch="${remote}/${branch}"
fi fi
if [ "${force}" != "YES" ]; then echo -n "Are you sure you want to clean all changes & sync with '${remote_branch}'? [y/N]: "
if [ "${soft}" = "true" ]; then local res
echo -n "Are you sure you want to sync with '${remote_branch}'? [y/N]: " read res
else case "${res}" in
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" ) "Y" | "y" | "yes" | "Yes" | "YES" )
if [ "${soft}" = "true" ]; then git fetch "${remote}" "${branch}" && git reset --hard "${remote_branch}" && git clean -d -f -x
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." echo "Canceled."

View file

@ -7,7 +7,7 @@ USAGE(){
test $# -lt 1 && USAGE test $# -lt 1 && USAGE
for filename in "$@"; do for filename in $@; do
touch "$filename" \ touch "$filename" \
&& git add "$filename" && git add "$filename"
done done

View file

@ -1,27 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
cstr="commits" back="^"
ccnt=$(git rev-list --count HEAD)
if [ "$ccnt" -eq 1 ]; then cstr="commit"; fi
parm3=""
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
git reset "--$type" "HEAD~$undo_cnt"
fi
if [ "$reset" != "" ]; then git reset; fi
}
case "$1" in case "$1" in
-h) -h)
@ -33,8 +12,9 @@ EOL
read -r res read -r res
case "${res}" in case "${res}" in
"Y" | "y") "Y" | "y")
parm1=hard test $2 -gt 1 > /dev/null 2>&1 && back="~$2"
parm2=${2:-1} git reset --hard HEAD$back
exit $?
;; ;;
* ) * )
exit 0 exit 0
@ -42,31 +22,16 @@ EOL
esac esac
;; ;;
--hard) --hard)
parm1=hard test $2 -gt 1 > /dev/null 2>&1 && back="~$2"
parm2=${2:-1} git reset --hard HEAD$back && exit 0;
;; ;;
-s|--soft) -s|--soft)
parm1=soft test $2 -gt 1 > /dev/null 2>&1 && back="~$2"
parm2=${2:-1}
parm3=reset
;;
"")
parm1=soft
parm2=1
;;
*[!0-9]*)
echo "Invalid parameter: $1"
exit 1
;; ;;
*) *)
parm1=soft test $1 -gt 1 > /dev/null 2>&1 && back="~$1"
parm2="$1"
;; ;;
esac esac
if [[ ! $parm2 =~ ^[1-9][0-9]*$ ]]; then git reset --soft HEAD$back
echo "Invalid undo count: $parm2" git reset
exit 1
fi
_undo "$parm1" "$parm2" "$parm3"

View file

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

@ -3,10 +3,10 @@ index e49cd24..4ae28b5 100755
--- a/bin/git-extras --- a/bin/git-extras
+++ b/bin/git-extras +++ b/bin/git-extras
@@ -4,13 +4,12 @@ VERSION="4.3.0" @@ -4,13 +4,12 @@ VERSION="4.3.0"
INSTALL_SCRIPT="https://raw.githubusercontent.com/tj/git-extras/main/install.sh" INSTALL_SCRIPT="https://raw.githubusercontent.com/tj/git-extras/master/install.sh"
update() { update() {
- local bin="$(command -v git-extras)" - local bin="$(which git-extras)"
- local prefix=${bin%/*/*} - local prefix=${bin%/*/*}
- local orig=$PWD - local orig=$PWD
- -

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