├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── casual-git-dummy-rebase-editor.sh └── gh.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .idea/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DUMMY_REBASE_EDITOR_NAME=casual-git-dummy-rebase-editor 2 | PREFIX=/usr/local 3 | NAME=gh 4 | PWD=$(shell pwd) 5 | 6 | install: uninstall 7 | mkdir -p $(PREFIX)/bin 8 | ln -s $(PWD)/gh.sh $(PREFIX)/bin/$(NAME) 9 | ln -s $(PWD)/casual-git-dummy-rebase-editor.sh $(PREFIX)/bin/$(DUMMY_REBASE_EDITOR_NAME) 10 | 11 | uninstall: 12 | rm -f $(PREFIX)/bin/$(NAME) 13 | rm -f $(PREFIX)/bin/$(DUMMY_REBASE_EDITOR_NAME) 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## What is that? 2 | *casual-git* is a bash script that helps you to automate interaction with Git. It's a wrapper for Git. 3 | 4 | It can: 5 | - `git push` or `git pull` or `git log` or `git commit` or `git commit --amend --no-edit` by only pressing 4 keys 6 | - `git push --force` or `git pull` + `git reset --hard origin/HEAD` by only pressing 5 keys 7 | - switch to a branch if you only remember a part of the full name 8 | - switch to one of branches matching a pattern 9 | - make a commit containing long file names with no need to enter them at all 10 | - fast and easily rebase onto a commit to modify its name or files 11 | 12 | ``` 13 | d - push 14 | f - push --force 15 | p - pull 16 | o - pull --force 17 | c - commit 18 | a - commit --amend 19 | s - commit --smart 20 | l - log --pretty 21 | h - checkout --smart 22 | m - modify commit 23 | ``` 24 | 25 | It is hardcoded to work with *origin* only . 26 | 27 | ## How to install 28 | 1. `git clone https://github.com/x0st/casual-git` 29 | 2. `cd casual-git` 30 | 3. `make` 31 | 4. Invoke the `gh` command within git repositories to open `casual-git` 32 | 33 | ## Some examples of usage 34 | 35 | ### Smart commit 36 | ``` 37 | d - push 38 | f - push --force 39 | p - pull 40 | o - pull --force 41 | c - commit 42 | a - commit --amend 43 | s - commit --smart 44 | l - log --pretty 45 | h - checkout --smart 46 | 47 | Delete files from the next commit: 48 | [-1] new file: test1 49 | 50 | Add files to the next commit: 51 | [1] modified: gulpfile.js 52 | [2] modified: package.json 53 | [3] untracked: test 54 | 55 | Enter file numbers separating by spaces: -1 2 3 56 | 57 | Enter a comment: my commit 58 | ``` 59 | 60 | ### Smart branch switching 61 | The `S`/`s` `W`/`w` keys are available for pagination. 62 | ``` 63 | d - push 64 | f - push --force 65 | p - pull 66 | o - pull --force 67 | c - commit 68 | a - commit --amend 69 | s - commit --smart 70 | l - log --pretty 71 | h - checkout --smart 72 | m - modify commit 73 | 74 | Enter a branch name or a part of name: t 75 | 76 | More than one git branch were found. 77 | Use the s|S and w|W keys on your keyboard for pagination. 78 | 10 first branches are being shown. 79 | Please choose a desired branch. 80 | 81 | [0] API-2168-add-merchant-data-to-document 82 | [1] APII-1601-multiple-eventsubs-for-sametype 83 | [2] APII-2156-stripe-integration 84 | [3] APII-2157-extend-api-to-store-new-merchant-acc 85 | [4] APII-2168-payment-request-merchant-info 86 | [5] APII-2169-fix-bug-duplicate-payment 87 | [6] APII-2171-stripe-ach 88 | [7] Improvement_web_2689 89 | [8] Release_2.1.9.64_20140801_hotfix 90 | [9] Release_2.1.9.70_20140807_hotfix 91 | 92 | Switched to branch 'feature/EGNYTE-20-watermarks-over-documents' 93 | ``` 94 | 95 | ### Commit modification 96 | The `S`/`s` `W`/`w` keys are available for pagination. 97 | ``` 98 | d - push 99 | f - push --force 100 | p - pull 101 | o - pull --force 102 | c - commit 103 | a - commit --amend 104 | s - commit --smart 105 | l - log --pretty 106 | h - checkout --smart 107 | m - modify commit 108 | 109 | [0] b866b85a5 - test commit 110 | [1] 288c39853 - Add new weekly-report-statistic report 111 | [2] cf87c03e1 - added code to support xls and ppt formats 112 | [3] f1f7cc448 - fixed conditionalfields.php 113 | [4] 60c1b46c9 - changed conditional fields' 114 | [5] dd39e20b3 - fixed code to consider tls1.2 115 | [6] 80e8c1881 - bla bla 116 | [7] bb8dd1d15 - fixed function call in expired invite cron 117 | [8] 62eee0417 - fixed function call in expired invite cron 118 | [9] 45ee1676e - Merge branch 'master' into feature-active 119 | 120 | ``` 121 | -------------------------------------------------------------------------------- /casual-git-dummy-rebase-editor.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "$(awk 'NR==1,/pick/{sub(/pick/, "edit")} 1' ${1})" > ${1} -------------------------------------------------------------------------------- /gh.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # return 1 if the given string contains at least 1 space, 0 otherwise. 4 | function _string_contains_spaces { 5 | [[ "$1" != "${1%[[:space:]]*}" ]] && return 0 || return 1 6 | } 7 | 8 | # show help to the user 9 | function _show_usage() { 10 | _print_empty_line 11 | _print_newline_message "\033[1;31md \033[0m - push" 12 | _print_newline_message "\033[1;31mf \033[0m - push --force" 13 | _print_newline_message "\033[1;31mp \033[0m - pull" 14 | _print_newline_message "\033[1;31mo \033[0m - pull --force" 15 | _print_newline_message "\033[1;31mc \033[0m - commit" 16 | _print_newline_message "\033[1;31ma \033[0m - commit --amend" 17 | _print_newline_message "\033[1;31ms \033[0m - commit --smart" 18 | _print_newline_message "\033[1;31ml \033[0m - log --pretty" 19 | _print_newline_message "\033[1;31mh \033[0m - checkout --smart" 20 | _print_newline_message "\033[1;31mm \033[0m - modify commit" 21 | _print_empty_line 22 | } 23 | 24 | # print the current project's branch. 25 | function _current_branch() { 26 | git rev-parse --abbrev-ref HEAD 27 | } 28 | 29 | # make user input visible 30 | function _turn_on_user_input() { 31 | stty echo 32 | } 33 | 34 | # make user input unvisible 35 | function _turn_off_user_input() { 36 | stty -echo 37 | } 38 | 39 | # print a message with a line break 40 | function _print_newline_message() { 41 | printf " $1\n" 42 | } 43 | 44 | # print a message with no line break 45 | function _print_input_request_message() { 46 | printf " $1" 47 | } 48 | 49 | # print a line break 50 | function _print_empty_line() { 51 | printf "\n" 52 | } 53 | 54 | # ask the user to input a character 55 | function _ask_for_a_char() { 56 | local _answer; 57 | 58 | read -r -s -n 1 _answer 59 | 60 | echo ${_answer} 61 | } 62 | 63 | # ask the user to input 'y' or 'n'. 64 | function _ask_yes_or_no() { 65 | local _message=${1} 66 | local _reply 67 | 68 | read -p " ${_message} " -n 1 -r _reply 69 | 70 | _print_empty_line 71 | _print_empty_line 72 | 73 | if [[ ${_reply} =~ ^[Yy]$ ]] 74 | then 75 | return 0 76 | fi 77 | 78 | return 1 79 | } 80 | 81 | # return 1 in case the given remote exists, 0 otherwise 82 | function _remote_exists() { 83 | local -a _all_remotes=(`git remote`) 84 | local _given_remote=${1} 85 | 86 | # iterating over all the existing remotes 87 | for remote in "${_all_remotes[@]}" 88 | do 89 | if [[ "${remote}" == "${_given_remote}" ]]; then 90 | return 0 91 | fi 92 | done 93 | 94 | return 1 95 | } 96 | 97 | # retrieve a remote out of the name of a branch 98 | function _retrieve_remote() { 99 | local _given_branch=${1} 100 | local -a _slash_breakdown=(`echo ${_given_branch} | awk -F / '{for (i = 1; i < NF; i++) { print $i; }}'`) 101 | local _possible_remote; 102 | 103 | # no slashes have been found 104 | if [[ ${#_slash_breakdown[@]} -eq 0 ]]; then 105 | return 1 106 | fi 107 | 108 | for i in "${_slash_breakdown[@]}" 109 | do 110 | _possible_remote="${_possible_remote}${i}" 111 | 112 | if `_remote_exists "${_possible_remote}"` 113 | then 114 | echo "${_possible_remote}" 115 | return 0 116 | fi 117 | 118 | _possible_remote="${_possible_remote}/" 119 | done 120 | 121 | return 1 122 | } 123 | 124 | # print all the local and remote branches 125 | function _all_git_branches() { 126 | git branch -a --format="%(refname)" \ 127 | | sed 's/refs\/heads\///g' \ 128 | | sed 's/refs\/remotes\///g' \ 129 | | sed '/HEAD detached at/d' 130 | } 131 | 132 | # Accepts an approximate or the exact name of a branch as first argument. 133 | # Tries to find a branch that match the given one. 134 | # If only one branch matches the given one then it is switched. 135 | function _find_and_switch_desired_branch() { 136 | local _desired_branch=${1} 137 | local _matching_branches_count=0 138 | local _matching_branch 139 | local _remote 140 | local -a _all_branches=(`_all_git_branches`) 141 | 142 | # the user did not provide the name of a branch 143 | if [[ -z ${_desired_branch} ]]; 144 | then 145 | return 1 146 | fi 147 | 148 | # iterating over all the branches and checking if any branch matches the desired one 149 | for i in "${_all_branches[@]}" 150 | do 151 | # the desired branch has been found, so we can switch to it instantly 152 | if [[ "${i}" == "${_desired_branch}" ]]; then 153 | git checkout "${_desired_branch}" > /dev/null 154 | return 0 155 | fi 156 | 157 | # if there is no exact name then the matched names are written 158 | if [[ "${i}" =~ "${_desired_branch}" ]]; then 159 | ((_matching_branches_count++)) 160 | _matching_branch="${i}" 161 | fi 162 | done 163 | 164 | # if only one branch matched, then switch to it 165 | if [[ ${_matching_branches_count} -eq 1 ]]; then 166 | _remote="`_retrieve_remote "${_matching_branch}"`" 167 | 168 | # if the matched branch contains a remote 169 | if [[ $? -eq 0 ]]; then 170 | git checkout "${_matching_branch:${#_remote}+1}" > /dev/null 171 | else 172 | git checkout "${_matching_branch}" > /dev/null 173 | fi 174 | return 0 175 | fi 176 | 177 | return 1 178 | } 179 | 180 | # Accepts an approximate or the exact name of a branch as first argument. 181 | # Counts the amount of the branches that match the given one. 182 | function _how_many_branches_match() { 183 | local _desired_branch=${1} 184 | local _matching_branches_count=0 185 | local -a _all_branches=(`_all_git_branches`) 186 | 187 | # iterating over all the branches and checking if any branch matches the given one 188 | for i in "${_all_branches[@]}" 189 | do 190 | if [[ "${i}" =~ "${_desired_branch}" ]]; then 191 | ((_matching_branches_count++)) 192 | fi 193 | done 194 | 195 | echo ${_matching_branches_count} 196 | } 197 | 198 | # Accepts an approximate or the exact name of a branch as first argument. 199 | # Finds the branches that match the given one. 200 | function _get_matching_branches() { 201 | local _desired_branch="${1}" 202 | local -a _all_branches=(`_all_git_branches`) 203 | local -a _matching_branches=() 204 | 205 | # iterating over all then branches and checking if any branch matches the given one 206 | for i in "${_all_branches[@]}" 207 | do 208 | if [[ "${i}" =~ "${_desired_branch}" ]]; then 209 | _matching_branches=("${_matching_branches[@]}" "${i}") 210 | fi 211 | done 212 | 213 | echo "${_matching_branches[@]}" 214 | } 215 | 216 | # Print all staged files 217 | # Approximate output: 218 | # 219 | # file1.txt:new_file 220 | # file2.txt:deleted 221 | # folder/:modified 222 | function _all_staged_files() { 223 | local _file_with_status 224 | local _git_file_status 225 | local _file_status 226 | local -a _files 227 | local -a _one_file 228 | 229 | while IFS= read -r _file_with_status; 230 | do 231 | `_string_contains_spaces "$(echo "${_file_with_status}" | sed -E 's/^.{0,3}//g')"` && continue 232 | [[ -z "${_file_with_status}" ]] && continue 233 | 234 | _git_file_status="$(echo "${_file_with_status}" | cut -c1-1)" 235 | 236 | case "${_git_file_status}" in 237 | "M") 238 | _file_status="modified" 239 | ;; 240 | "D") 241 | _file_status="deleted" 242 | ;; 243 | "A") 244 | _file_status="new_file" 245 | ;; 246 | "R") 247 | _file_status="renamed" 248 | ;; 249 | *) 250 | _file_status="unknown" 251 | ;; 252 | esac 253 | 254 | _one_file="$(echo "${_file_with_status}" | sed -E 's/^.{0,3}//g'):${_file_status}" 255 | 256 | _files=("${_files[@]}" "${_one_file}") 257 | done <<< "$(git status -s | grep -E 'M. |D. |A. |R. ')" 258 | 259 | echo "${_files[@]}" 260 | } 261 | 262 | # Print all staged files 263 | # Approximate output: 264 | # 265 | # file1.txt:untracked 266 | # file2.txt:modified 267 | # folder/:deleted 268 | function _all_unstaged_files() { 269 | local _file_with_status 270 | local _git_file_status 271 | local _file_status 272 | local _one_file 273 | local -a _files 274 | 275 | while IFS= read -r _file_with_status; 276 | do 277 | `_string_contains_spaces "$(echo "${_file_with_status}" | sed -E 's/^.{0,3}//g')"` && continue 278 | [[ -z "${_file_with_status}" ]] && continue 279 | 280 | _git_file_status=$(echo "${_file_with_status}" | cut -c1-2) 281 | 282 | case "${_git_file_status:1:1}" in 283 | '?') 284 | _file_status="untracked" 285 | ;; 286 | 'M') 287 | _file_status="modified" 288 | ;; 289 | 'D') 290 | _file_status="deleted" 291 | ;; 292 | esac 293 | 294 | _one_file="$(echo "${_file_with_status}" | sed -E 's/^.{0,3}//g'):${_file_status}" 295 | 296 | _files=("${_files[@]}" "${_one_file}") 297 | done <<< "$(git status -s | grep -E '\?\? |.M |.D ')" 298 | 299 | echo "${_files[@]}" 300 | } 301 | 302 | function _all_git_commits() { 303 | git --no-pager log --pretty='%h:%s' 304 | } 305 | 306 | function _command_git_push() { 307 | _print_newline_message "Pushing..." 308 | git push origin "`_current_branch`" 309 | } 310 | 311 | function _command_git_force_push() { 312 | if `_ask_yes_or_no "This will replace the remote history with yours! Are you sure?"` 313 | then 314 | git push --force origin "`_current_branch`" 315 | fi 316 | } 317 | 318 | function _command_git_force_pull() { 319 | if `_ask_yes_or_no "This will replace your local history with remote one! Are you sure?"` 320 | then 321 | _print_newline_message "Pulling..." 322 | git pull origin "`_current_branch`" 323 | git reset --hard "origin/`_current_branch`" 324 | fi 325 | } 326 | 327 | function _command_git_pull() { 328 | _print_newline_message "Pulling..." 329 | git pull origin "`_current_branch`" 330 | } 331 | 332 | function _command_git_pretty_log() { 333 | git log \ 334 | --color \ 335 | --graph \ 336 | --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' \ 337 | --abbrev-commit 338 | } 339 | 340 | function _command_git_commit() { 341 | local -a _comment 342 | 343 | _print_empty_line 344 | _print_input_request_message "Enter a comment: " 345 | 346 | read _comment 347 | 348 | git commit -m "${_comment}" 349 | } 350 | 351 | function _command_git_amend_commit() { 352 | git commit --amend --no-edit 353 | } 354 | 355 | function _command_git_smart_commit() { 356 | local -ax _all_staged_files=(`_all_staged_files`) 357 | local -ax _all_unstaged_files=(`_all_unstaged_files`) 358 | local -ax _all_staged_and_unstaged_files=("${_all_staged_files[@]}" "${_all_unstaged_files[@]}") 359 | local -x _file_counter=0 360 | local -ax _file_indexes=() 361 | local _file_indexes_user_input 362 | local _file_name 363 | local _file_status 364 | 365 | # printing all the staged files 366 | _print_newline_message "Delete files from the next commit: " 367 | 368 | for file in "${_all_staged_files[@]}"; 369 | do 370 | ((_file_counter--)) 371 | 372 | _file_name="$(echo "${file}" | awk -F: '{OFS=":";$NF="";print $0;}')" 373 | _file_name="${_file_name%?}" 374 | 375 | _file_status="$(echo "${file}" | awk -F: '{print $NF}')" 376 | 377 | _print_newline_message "[${_file_counter}] \e[32m${_file_status}: ${_file_name}\e[0m" 378 | done 379 | 380 | if [[ _file_counter -eq 0 ]]; 381 | then 382 | _print_newline_message "\e[32mNo staged files\e[0m" 383 | fi 384 | 385 | # printing all the unstaged files 386 | 387 | _file_counter=0 388 | 389 | _print_empty_line 390 | _print_newline_message "Add files to the next commit: " 391 | 392 | for file in "${_all_unstaged_files[@]}"; 393 | do 394 | _file_name="$(echo "${file}" | sed -E 's/\[\[:space:\]\]/ /g' | awk -F: '{OFS=":";$NF="";print $0;}')" 395 | _file_name="${_file_name%?}" 396 | 397 | _file_status="$(echo "${file}" | awk -F: '{print $NF}')" 398 | 399 | case "${_file_status}" in 400 | "modified") 401 | _print_newline_message "[${_file_counter}] \e[34m${_file_status}\e[0m: ${_file_name}" 402 | ;; 403 | "untracked") 404 | _print_newline_message "[${_file_counter}] \e[31m${_file_status}\e[0m: ${_file_name}" 405 | ;; 406 | "deleted") 407 | _print_newline_message "[${_file_counter}] \e[37m${_file_status}\e[0m: ${_file_name}" 408 | ;; 409 | esac 410 | 411 | ((_file_counter++)) 412 | done 413 | 414 | if [[ _file_counter -eq 0 ]]; 415 | then 416 | _print_newline_message "\e[32mNo unstaged files\e[0m" 417 | fi 418 | 419 | # asking the user to input indexes of the files that should be deleted or added 420 | _print_empty_line 421 | _print_input_request_message "Enter file numbers separating by a space: " 422 | read _file_indexes_user_input 423 | 424 | _file_indexes=(`echo "${_file_indexes_user_input}"`) 425 | 426 | for index in "${_file_indexes[@]}"; 427 | do 428 | # delete a staged file from the next commit 429 | if [[ "$(echo ${index} | cut -c 1)" == '-' ]]; 430 | then 431 | if [[ -n "${_all_staged_files[$(($(echo ${index} | cut -c2-)-1))]}" ]]; 432 | then 433 | _file_name="$(echo "${_all_staged_files[$(($(echo ${index} | cut -c2-)-1))]}" | awk -F: '{OFS=":";$NF="";print $0;}')" 434 | _file_name="${_file_name%?}" 435 | 436 | git reset HEAD "${_file_name}" 437 | fi 438 | else 439 | if [[ -n "${_all_unstaged_files[${index}]}" ]]; 440 | then 441 | _file_name="$(echo "${_all_unstaged_files[${index}]}" | awk -F: '{OFS=":";$NF="";print $0;}')" 442 | _file_name="${_file_name%?}" 443 | 444 | git add "${_file_name}" 445 | fi 446 | fi 447 | done 448 | 449 | _command_git_commit 450 | } 451 | 452 | function _command_git_smart_checkout() { 453 | local _user_choice_branch_counter=0 454 | local _top_branch_index 455 | local _bottom_branch_index 456 | local _desired_branch_index 457 | local _desired_branch 458 | local _branch_counter=0 459 | local -x _branches_per_page=10 460 | local -x _page=1 461 | local -a _matching_branches 462 | 463 | # ask the user to input the name of a branch 464 | _print_input_request_message "Enter a branch name or a part of name: " 465 | read _desired_branch 466 | _print_empty_line 467 | 468 | # there is only one branch that match the desired one 469 | if `_find_and_switch_desired_branch "${_desired_branch}"` 470 | then 471 | return 0 472 | fi 473 | 474 | # there is more than one branch that match the desired one 475 | if [[ `_how_many_branches_match "${_desired_branch}"` -gt 0 ]]; then 476 | _print_newline_message "More than one git branch were found." 477 | _print_newline_message "Use the s|S and w|W keys on your keyboard for pagination." 478 | _print_newline_message "10 first branches are being shown." 479 | _print_newline_message "Please choose a desired branch." 480 | _print_empty_line 481 | 482 | # all the branches that match the desired one 483 | _matching_branches=(`_get_matching_branches "${_desired_branch}"`) 484 | 485 | while [[ 1 -eq 1 ]]; 486 | do 487 | _branch_counter=0 488 | _user_choice_branch_counter=0 489 | 490 | # printing all the branches that match the desired one 491 | for branch in "${_matching_branches[@]}"; 492 | do 493 | _bottom_branch_index=$(( ((${_page} - 1)) * ${_branches_per_page} )) 494 | _top_branch_index=$(( ${_bottom_branch_index} + ${_branches_per_page} )) 495 | 496 | # if the current branch index is between the range of the page 497 | if [[ ${_branch_counter} -ge ${_bottom_branch_index} && ${_branch_counter} -lt ${_top_branch_index} ]]; 498 | then 499 | _print_newline_message "\033[1;31m[${_user_choice_branch_counter}] "${branch}"\033[0m" 500 | ((_user_choice_branch_counter++)) 501 | fi 502 | 503 | ((_branch_counter++)) 504 | done 505 | 506 | _print_empty_line 507 | 508 | # ask the user to input a branch index 509 | _desired_branch_index=`_ask_for_a_char` 510 | 511 | # if the user entered a correct index 512 | if [[ ${_desired_branch_index} =~ [0-9] ]]; 513 | then 514 | # if the branch with the entered index exists 515 | if [[ -n "${_matching_branches[${_desired_branch_index}]}" ]]; 516 | then 517 | git checkout "${_matching_branches[$(( $(( ${_page} - 1 )) * ${_branches_per_page} + ${_desired_branch_index} ))]}" 518 | return 0 519 | else 520 | return 1 521 | fi 522 | elif 523 | # show the next page with branches 524 | [[ ${_desired_branch_index} == "s" || ${_desired_branch_index} == "S" ]]; 525 | then 526 | # incrementing the page's value 527 | [[ $(( ${_page} * ${_branches_per_page} )) -lt ${#_matching_branches[@]} ]] && ((_page++)) 528 | 529 | if [[ ${#_matching_branches[@]} -gt ${_branches_per_page} ]]; 530 | then 531 | tput cup $(($(tput lines) - 12)) 0 532 | tput il 12 533 | else 534 | tput cup $(( $(tput lines) - $(( ${#_matching_branches[@]} + 2 )) )) 0 535 | tput il $(( ${#_matching_branches[@]} + 2 )) 536 | fi 537 | elif 538 | # show the previous page with branches 539 | [[ ${_desired_branch_index} == "w" || ${_desired_branch_index} == "W" ]]; 540 | then 541 | # decrementing the page's value 542 | [[ ${_page} -ne 1 ]] && ((_page--)) 543 | 544 | if [[ ${#_matching_branches[@]} -gt ${_branches_per_page} ]]; 545 | then 546 | tput cup $(($(tput lines) - 12)) 0 547 | tput il 12 548 | else 549 | tput cup $(( $(tput lines) - $(( ${#_matching_branches[@]} + 2 )) )) 0 550 | tput il $(( ${#_matching_branches[@]} + 2 )) 551 | fi 552 | else 553 | break 554 | fi 555 | done 556 | fi 557 | 558 | _print_newline_message "There is no such branch." 559 | 560 | _command_git_smart_checkout 561 | } 562 | 563 | function _command_git_modify_commit() { 564 | IFS=$'\n' 565 | local -ax _all_commits=(`_all_git_commits`) 566 | local _top_commit_index 567 | local _bottom_commit_index 568 | local _desired_commit_index 569 | local -x _user_choice_commit_counter=0 570 | local -x _commit_counter=0 571 | local -x _commits_per_page=10 572 | local -x _page=1 573 | local _commit_hash 574 | local _commit_message 575 | local _commit_index_in_array 576 | local _old_git_sequence_editor 577 | 578 | while [[ 1 -eq 1 ]]; 579 | do 580 | _user_choice_commit_counter=0 581 | _commit_counter=0 582 | 583 | _bottom_commit_index=$(( ((${_page} - 1)) * ${_commits_per_page} )) 584 | _top_commit_index=$(( ${_bottom_commit_index} + ${_commits_per_page} )) 585 | 586 | for commit in "${_all_commits[@]}"; 587 | do 588 | # if the current commit index is between the range 589 | if [[ ${_commit_counter} -ge ${_bottom_commit_index} && ${_commit_counter} -lt ${_top_commit_index} ]]; 590 | then 591 | _commit_hash="$(echo ${commit} | awk -F ':' '{print $1}')" 592 | _commit_message="$(echo ${commit} | awk -F ':' '{$1="";print $0}')" 593 | 594 | _print_newline_message "[${_user_choice_commit_counter}] \033[31m"${_commit_hash}"\033[0m -"${_commit_message}"" 595 | ((_user_choice_commit_counter++)) 596 | else 597 | [[ ${_commit_counter} -gt ${_top_commit_index} ]] && break 598 | fi 599 | 600 | ((_commit_counter++)) 601 | done 602 | 603 | _print_empty_line 604 | 605 | # ask the user to input a commit index 606 | _turn_on_user_input 607 | _desired_commit_index=`_ask_for_a_char` 608 | _turn_off_user_input 609 | 610 | # if the user entered a correct index 611 | if [[ ${_desired_commit_index} =~ [0-9] ]]; 612 | then 613 | _commit_index_in_array="$(( $(( ${_page} - 1 )) * ${_commits_per_page} + ${_desired_commit_index} ))" 614 | # if the commit with the entered index exists 615 | if [[ -n "${_all_commits[${_commit_index_in_array}]}" ]]; 616 | then 617 | _commit_hash=$(echo ${_all_commits[${_commit_index_in_array}]} | awk -F ':' '{print $1}') 618 | 619 | if [[ -z ${GIT_SEQUENCE_EDITOR} ]]; 620 | then 621 | export GIT_SEQUENCE_EDITOR="casual-git-dummy-rebase-editor" 622 | git rebase --interactive "${_commit_hash}^" 623 | unset GIT_SEQUENCE_EDITOR 624 | else 625 | _old_git_sequence_editor=${GIT_SEQUENCE_EDITOR} 626 | export GIT_SEQUENCE_EDITOR="casual-git-dummy-rebase-editor" 627 | git rebase --interactive "${_commit_hash}^" 628 | export GIT_SEQUENCE_EDITOR=${_old_git_sequence_editor} 629 | fi 630 | 631 | return 0 632 | else 633 | return 1 634 | fi 635 | elif 636 | # show the next page 637 | [[ ${_desired_commit_index} == "s" || ${_desired_commit_index} == "S" ]]; 638 | then 639 | # incrementing the page's value 640 | [[ $(( ${_page} * ${_commits_per_page} )) -lt ${#_all_commits[@]} ]] && ((_page++)) 641 | 642 | if [[ ${#_all_commits[@]} -gt ${_commits_per_page} ]]; 643 | then 644 | tput cup $(($(tput lines) - 12)) 0 645 | tput il 12 646 | else 647 | tput cup $(( $(tput lines) - $(( ${#_all_commits[@]} + 2 )) )) 0 648 | tput il $(( ${#_all_commits[@]} + 2 )) 649 | fi 650 | elif 651 | # show the previous page 652 | [[ ${_desired_commit_index} == "w" || ${_desired_commit_index} == "W" ]]; 653 | then 654 | # decrementing the page's value 655 | [[ ${_page} -ne 1 ]] && ((_page--)) 656 | 657 | if [[ ${#_all_commits[@]} -gt ${_commits_per_page} ]]; 658 | then 659 | tput cup $(($(tput lines) - 12)) 0 660 | tput il 12 661 | else 662 | tput cup $(( $(tput lines) - $(( ${#_all_commits[@]} + 2 )) )) 0 663 | tput il $(( ${#_all_commits[@]} + 2 )) 664 | fi 665 | else 666 | break 667 | fi 668 | done 669 | } 670 | 671 | _show_usage 672 | _turn_off_user_input 673 | 674 | case `_ask_for_a_char` in 675 | s|S) 676 | _turn_on_user_input 677 | _command_git_smart_commit 678 | ;; 679 | l|L) 680 | _command_git_pretty_log 681 | ;; 682 | d|D) 683 | _command_git_push 684 | ;; 685 | f|F) 686 | _command_git_force_push 687 | ;; 688 | p|P) 689 | _command_git_pull 690 | ;; 691 | o|O) 692 | _command_git_force_pull 693 | ;; 694 | c|C) 695 | _turn_on_user_input 696 | _command_git_commit 697 | ;; 698 | a|A) 699 | _command_git_amend_commit 700 | ;; 701 | h|H) 702 | _turn_on_user_input 703 | _command_git_smart_checkout 704 | ;; 705 | m|M) 706 | _command_git_modify_commit 707 | esac 708 | 709 | _turn_on_user_input 710 | --------------------------------------------------------------------------------