├── .gitignore ├── screenshot.png ├── .gitbranch ├── LICENSE ├── README.md └── git-ink /.gitignore: -------------------------------------------------------------------------------- 1 | .gitbranch 2 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidosomething/git-ink/HEAD/screenshot.png -------------------------------------------------------------------------------- /.gitbranch: -------------------------------------------------------------------------------- 1 | [branch "note1"] 2 | description = 1-newline-1\\r\\n\\nThis is the updated note for note 1 updateeeeee\\n\\nThis is the updated note for note 1 3 | tags = "tag1,tag2,tag3" 4 | 5 | [branch "note2"] 6 | description = "Another branch note" 7 | tags = "tag1,tag3" 8 | 9 | [branch "nodesc"] 10 | tags = "tag4" 11 | 12 | [branch "notags"] 13 | desc = "no tags on this branch" 14 | 15 | [branch "nonexistent"] 16 | desc = "this branch doesn't exist" 17 | tags = "tag3" 18 | [branch "master"] 19 | description = aabc this is a test 20 | [branch ""] 21 | description = 22 | [branch "b c"] 23 | description = \\r\\n\\n 24 | [branch "-s a b c"] 25 | description = \\r\\n\\n 26 | [branch "-s a"] 27 | description = \\r\\n\\n 28 | [branch "testtt"] 29 | description = 30 | [branch "-r cd"] 31 | description = 32 | [branch "dev"] 33 | description = abcdefgh \n\ncdefgh 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License Copyright (c) 2020 davidosomething 2 | 3 | Permission is hereby granted, 4 | free of charge, to any person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, copy, modify, merge, 7 | publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to the 9 | following conditions: 10 | 11 | The above copyright notice and this permission notice 12 | (including the next paragraph) shall be included in all copies or substantial 13 | portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 18 | EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 19 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # git-ink 2 | 3 | Pull out the `[branch]` description from your `.git/config`; quickly edit and 4 | view your branches, with annotations; hashtag branches for easier management. 5 | 6 | ![Screenshot of output](screenshot.png) 7 | 8 | ## Support 9 | 10 | - OSX - Partial 11 | - the `git ink` command works but options do not (pending a getopts rewrite) 12 | - Use `git branch --edit-description` to add descriptions in the interim 13 | 14 | - Linux - Full 15 | - `git ink` and all its flags work. 16 | 17 | ## Install 18 | 19 | Put `git-ink` somewhere in your path. 20 | 21 | ## Usage 22 | 23 | ```shell 24 | git branch --edit-description # opens $EDITOR to edit a description 25 | git ink # pretty list of branches and descriptions 26 | git ink -h # help 27 | git ink -b dev -g # display description of "dev" branch 28 | git ink -b dev -s "prepend me to existing description" 29 | git ink -b dev -r "overwrite entire description with this" 30 | ``` 31 | 32 | A `.gitbranch` file will is the canonical source of meta data for this plugin 33 | if provided. The `.gitbranch` file uses the same format as gitconfig files. 34 | It MUST be added to `.gitignore` so it can exist in all branches (otherwise 35 | you would not have all the meta for all branches) 36 | 37 | ## TODO 38 | 39 | - sync with the branch info and descriptions stored in `.git/config`. 40 | - find a good way to share the .gitbranch across repos and branches 41 | - rewrite in Ruby or Python for better compatibility 42 | - tests 43 | 44 | ## Changelog 45 | 46 | - 2019-03-24 47 | - Release v1.0.3 48 | - check if user is in a rebase or merge and abort 49 | - print error message on unrecognized flag 50 | 51 | - 2015-10-26 52 | - Release v1.0.1 53 | - add header, _output_line function 54 | 55 | - 2015-10-22 56 | - Release v1.0.0 57 | - Fix arg parsing, var scoping, every function accepts branch name now 58 | - Updated screenshot 59 | 60 | - 2015-10-19 61 | - Change output formatting, indent branch descriptions 62 | 63 | -------------------------------------------------------------------------------- /git-ink: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # git-ink 4 | 5 | semver="1.0.3" 6 | scriptname="$(basename "$0")" 7 | 8 | yellow=$(tput setaf 3) 9 | #orange=$(tput setaf 9) 10 | red=$(tput setaf 1) 11 | magenta=$(tput setaf 5) 12 | #violet=$(tput setaf 13) 13 | #cyan=$(tput setaf 6) 14 | green=$(tput setaf 2) 15 | normal=$(tput sgr0) 16 | 17 | _current_branchname() { 18 | git symbolic-ref --short HEAD 19 | } 20 | 21 | _get_branches() { 22 | git for-each-ref \ 23 | --format="%(refname:short) %(committerdate:short)" \ 24 | refs/heads 25 | } 26 | 27 | _sync_meta() { 28 | #@TODO 29 | # read from .git/config 30 | # get meta values for each branch 31 | # clear from .git/config 32 | # write to .gitbranch 33 | exit 34 | } 35 | 36 | _gitconfig() { 37 | local ink_metafile="" 38 | _has_gitbranch && ink_metafile=".gitbranch" 39 | 40 | if [ -n "$ink_metafile" ]; then 41 | git config -f "$ink_metafile" "$@" 42 | else 43 | git config --local "$@" 44 | fi 45 | } 46 | 47 | _has_gitbranch() { 48 | [ -f ".gitbranch" ] 49 | } 50 | 51 | _replace_description() { 52 | local new_description 53 | new_description="$(printf "%b" "${2}\r\n")" 54 | _gitconfig "branch.${1}.description" "${new_description}" 55 | } 56 | 57 | # @param branchname 58 | # @param new description 59 | _set_description() { 60 | local old_description 61 | local new_description 62 | old_description="$(_get_description "${1}")" 63 | new_description="$(printf '%b' "${2}\r\n\n${old_description}")" 64 | _gitconfig "branch.${1}.description" "${new_description}" 65 | } 66 | 67 | # @param branchname to get unformatted description of 68 | _get_description() { 69 | _gitconfig --get "branch.${1}.description" 70 | } 71 | 72 | # output indented branch description 73 | # with literal newlines (otherwise will diplay all on one line) 74 | # @param branchname to show description of 75 | _output_branch_description() { 76 | local original_IFS=${IFS} 77 | local newlines_IFS=$'\n' 78 | IFS="${newlines_IFS}" 79 | for line in $(_get_description "${branchname}"); do 80 | IFS="${original_IFS}" 81 | printf "%4s%s\n" " " "${line}" 82 | IFS="${newlines_IFS}" 83 | done 84 | IFS="${original_IFS}" 85 | } 86 | 87 | _output_line() { 88 | local lsymbol=$1 89 | local lslength=$2 90 | local lbranch=$3 91 | local ldate=$4 92 | 93 | echo -n "${lsymbol} ${lbranch}${normal} $(printf ".%.0s" {1..60})" | head -c $((68 - lslength)) 94 | echo -n " ${ldate}" 95 | } 96 | 97 | _ink() { 98 | # header 99 | echo "${yellow}? BRANCH NAME $(printf " %.0s" {1..34}) LAST COMMIT" 100 | 101 | _get_branches | while read -r branchname branchdate; do 102 | if [ -n "$target_branchname" ] && [ "$target_branchname" != "$branchname" ]; then 103 | continue 104 | fi 105 | 106 | tags="$(_gitconfig --get "branch.${branchname}.tags")" 107 | 108 | branch_color=$normal 109 | status_color=$normal 110 | status_symbol="•" 111 | symbol_length=0 112 | 113 | if [ "$branchname" == "$(_current_branchname)" ]; then 114 | branch_color=$magenta 115 | current_commit="$(git rev-parse HEAD)" 116 | branch_commit="$(git rev-parse "$branchname")" 117 | 118 | # fix utf-8 symbol length for padding 119 | symbol_length=2 120 | if [ "$current_commit" == "$branch_commit" ]; then 121 | status_symbol="✓" 122 | status_color=$green 123 | else 124 | status_color=$red 125 | status_symbol="✗" 126 | fi 127 | fi 128 | 129 | output_symbol="${status_color}${status_symbol}" 130 | output_branchname="${branch_color}${branchname}" 131 | 132 | _output_line \ 133 | "$output_symbol" \ 134 | "$symbol_length" \ 135 | "$output_branchname" \ 136 | "$branchdate" 137 | 138 | #@TODO support --no-color 139 | #@TODO support --format=config for standard _gitconfig output 140 | #@TODO support --one-line 141 | 142 | if [ -n "$tags" ]; then 143 | printf " - %s" "${tags}" 144 | fi 145 | 146 | echo 147 | 148 | _output_branch_description "${branchname}" 149 | 150 | done 151 | } 152 | 153 | _version() { 154 | echo "$scriptname $semver" 155 | } 156 | 157 | _help() { 158 | echo 159 | echo "target branch defaults to the current branch" 160 | echo "-b BRANCH_NAME -- set target branch" 161 | echo "-g -- get description for target branch" 162 | echo "-r DESCRIPTION -- replace description for branch" 163 | echo " ANYTHING after -r will be considered part of the description" 164 | echo "-s DESCRIPTION -- set description for target branch" 165 | echo " PREPENDS to the previous description" 166 | echo " ANYTHING after -s will be considered part of the description" 167 | echo "-h -- show help" 168 | echo "-v -- version info" 169 | } 170 | 171 | _main() { 172 | if [[ -d "$(git rev-parse --git-path rebase-merge)" ]] || 173 | [[ -d "$(git rev-parse --git-path rebase-apply)" ]]; then 174 | echo "ERROR: Rebase or merge in progress" >&2 175 | exit 1 176 | fi 177 | 178 | local action 179 | local target_branchname 180 | local new_description 181 | 182 | while getopts ":b:r:s:vgh" opt; do 183 | case "$opt" in 184 | 185 | b) # set target branchname 186 | target_branchname="$OPTARG" 187 | ;; 188 | 189 | g) # output branch description 190 | action="get_description" 191 | ;; 192 | 193 | r) # replace branch description 194 | action="replace_description" 195 | new_description="$OPTARG" 196 | ;; 197 | 198 | s) # set branch description 199 | action="set_description" 200 | new_description="$OPTARG" 201 | ;; 202 | 203 | h) 204 | _version 205 | _help 206 | exit 207 | ;; 208 | 209 | v) 210 | _version 211 | exit 212 | ;; 213 | 214 | :) # missing args on any flag 215 | echo "ERROR: Option -$OPTARG requires an argument." >&2 216 | _help 217 | exit 1 218 | ;; 219 | 220 | *) 221 | echo "ERROR: Unrecognized flag -$OPTARG" >&2 222 | _version 223 | _help 224 | exit 1 225 | ;; 226 | esac 227 | done 228 | 229 | if [ -n "$action" ]; then 230 | target_branchname=${target_branchname:-$(_current_branchname)} 231 | fi 232 | 233 | if [ "$action" == "get_description" ]; then 234 | _get_description "$target_branchname" 235 | exit $? 236 | elif [ "$action" == "set_description" ]; then 237 | _set_description "${target_branchname}" "${new_description}" 238 | exit $? 239 | elif [ "$action" == "replace_description" ]; then 240 | _replace_description "${target_branchname}" "${new_description}" 241 | exit $? 242 | fi 243 | 244 | # default action 245 | _ink 246 | } 247 | 248 | _main "$@" 249 | --------------------------------------------------------------------------------