├── .gitignore ├── README.md ├── commit ├── git-config ├── git-find-repos ├── git-generate-changes ├── git-list-branches ├── git-practice-platform ├── git-wtf ├── gitanalyzer ├── gitcatfile ├── live-findfiles ├── live-git-index ├── live-git-log ├── live-git-reflog ├── live-git-status └── live-tree /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | out/ 3 | *.iml 4 | *.swp 5 | git-kata-directory/ 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Git-Extras 3 | 4 | The best way of learning Git is practicing it. While practicing, it is beneficial to monitor the index, the status, 5 | the objects database and the working copy in some cases. A set of bash/groovy/ruby scripts are created to monitor 6 | and analyze Git internals and collected in this repository. 7 | 8 | ## Prerequisites 9 | 10 | The scrips in this repository are written in Bash, Groovy and Ruby and execute native Git commands. So in order to run 11 | all the scripts, the following applications should already be installed on your machine. 12 | 13 | * Git 1.7+ 14 | * Groovy 2+ 15 | * Ruby 2+ 16 | 17 | ## Installation 18 | 19 | Just clone this repository to a folder (e.g. `/usr/local/bin`) which is in the list of `PATH` environment variable. 20 | 21 | ## Scripts 22 | 23 | **Bash Scripts** 24 | * [git-config-sample](#git-config-sample) 25 | * [git-find-repos](#git-find-repos) 26 | * [git-generate-changes](#git-generate-changes) 27 | * [git-practice-platform](#git-practice-platform) 28 | 29 | **Bash Scripts for Monitoring** 30 | * [live-findfiles](#live-findfiles) 31 | * [live-git-index](#live-git-index) 32 | * [live-git-log](#live-git-log) 33 | * [live-git-reflog](#live-git-reflog) 34 | * [live-git-status](#live-git-status) 35 | * [live-tree](#live-tree) 36 | 37 | **Groovy Scripts** 38 | * [gitanalyzer](#gitanalyzer) 39 | * [gitcatfile](#gitcatfile) 40 | 41 | **Ruby Scripts** 42 | * [git-wtf](#git-wtf) 43 | 44 | ## Usage 45 | 46 | 47 | #### git-config-sample 48 | 49 | ``` 50 | NAME 51 | git-config-sample - Updates your git configuration with the ones we advice 52 | 53 | SYNOPSIS 54 | git-config-sample 55 | 56 | DESCRIPTION 57 | git-config-sample updates your git configuration with a set of config commands that we 58 | advice to use without fear. I also includes detailed explanation for each config entry. 59 | 60 | USAGE 61 | You can directly call the script and let it update the configuration. 62 | 63 | git-config-sample 64 | 65 | In order to check whether the configurations are updated or not, you can check 66 | `~/.gitconfig` file. 67 | 68 | DISCUSSIONS 69 | The content of this script is gathered after using Git for years with many tweaks. 70 | We also made a good search to learn how git-geeks use on their systems. 71 | ``` 72 | [Go to top](#top) 73 | 74 | 75 | #### git-find-repos 76 | 77 | ``` 78 | NAME 79 | git-find-repos - Finds all existing git repositories 80 | 81 | SYNOPSIS 82 | git-find-repos [-h | --help] [-d | --dirty] 83 | 84 | DESCRIPTION 85 | git-find-repos is a Bash script that searches .git folders recursively under the given folder 86 | path. 87 | 88 | OPTIONS 89 | 90 | Path of a folder to run the recursive search. If it is not provided, th directory that 91 | you are in is used for doing the search. 92 | -d, --dirty 93 | Checks the status of detected git repositories. Whenever it finds a repository having 94 | updates that hasn't been push to upstream (these are the updates not added yet, the 95 | updates added but not committed yet, and the updates committed but not pushed yet), it 96 | marks the repository as dirty. With this option, only the dirty repositories are printed, 97 | not all of them. 98 | -h, --help 99 | Prints usage information for help. 100 | 101 | USAGE 102 | Typically you can search for all available git repositories. Do not provide the path if you 103 | want to search in your current directory. 104 | 105 | git-find-repos 106 | git-find-repos /path/to/search 107 | 108 | In order to run the search just the dirty repositories, use -d option. 109 | 110 | git-find-repos -d 111 | git-find-repos -d /path/to/search 112 | git-find-repos --dirty /path/to/search 113 | 114 | DISCUSSIONS 115 | We clone git repositories to our machines. We use many different source directories to clone 116 | into. For instance, sometimes we clone repositories to our home folder, sometimes to our 117 | `/usr/local/` folder. From time to time we lose track of what we changed in these 118 | repositories. This script is mainly designed for searching git repositories having local 119 | modifications (i.e. -d option). It can also be used to list all git repositories in your 120 | machine to see the overall picture of what you cloned so far. 121 | 122 | It might be not possible to search git repositories in some directories due to missing access 123 | rights. On those directories, this script does not warn you. 124 | ``` 125 | [Go to top](#top) 126 | 127 | 128 | #### git-generate-changes 129 | 130 | ``` 131 | NAME 132 | git-generate-changes - Generates random file changes and commits to Git repository 133 | 134 | SYNOPSIS 135 | git-generate-changes [-h | --help] [-g | --commit] [(-p | --prefix) ] 136 | [(-c | --count) ] [(-e | --extension) ] 137 | 138 | DESCRIPTION 139 | git-generate-changes is a Bash script for creating bulk random commits in repository. 140 | 141 | OPTIONS 142 | -g, --commit 143 | Commit all newly created files to the repository as one file per commit. 144 | -p, --prefix 145 | The prefix of the filename used in the new files. If not given, "temp" is used. 146 | -c, --count 147 | Number of files to create. If not given, count is set to 1. 148 | -e, --extension 149 | The extension of the filename used in new files. If not given, "txt" is used. 150 | -h, --help 151 | Prints usage information for help. 152 | 153 | USAGE 154 | If no parameters are entered, the script creates 1 file with the default prefix and extension. 155 | The file is created in the root of the git repository. The number in filename is randomly 156 | selected between 1 and 100. 157 | 158 | $ git-generate-changes 159 | temp27.txt ... created 160 | 161 | You can define your own file name convention and decide to commit these files as one file 162 | per file. 163 | 164 | $ git-generate-changes -g -p catalina -e log -c 3 165 | catalina84.log ... created, committed 166 | catalina17.log ... created, committed 167 | catalina19.log ... created, committed 168 | 169 | If the random file name already exists in the same directory, the script updates it. 170 | 171 | $ git-generate-changes -g -p catalina -e log -c 3 172 | catalina63.log ... created, committed 173 | catalina17.log ... modified, committed 174 | catalina19.log ... modified, committed 175 | 176 | DISCUSSIONS 177 | This script is mainly used when you need random commits while practicing Git. It speeds up 178 | your exercise if your aim is trying -for instance- merge or rebase, not commit. 179 | ``` 180 | [Go to top](#top) 181 | 182 | 183 | #### git-wtf 184 | 185 | ``` 186 | NAME 187 | git-wtf - Displays the state of your repository in a readable, easy-to-scan format 188 | 189 | SYNOPSIS 190 | git-wtf [branch+] [options] 191 | 192 | DESCRIPTION 193 | git-wtf is a Ruby script. It displays the state of your repository in a readable, easy-to-scan 194 | format. It's useful for getting a summary of how a branch relates to a remote server, and for 195 | wrangling many topic branches. 196 | 197 | OPTIONS 198 | -l, --long 199 | include author info and date for each commit 200 | -a, --all 201 | show all branches across all remote repos, not just those from origin 202 | -A, --all-commits 203 | show all commits, not just the first 5 204 | -s, --short 205 | don't show commits 206 | -k, --key 207 | show key 208 | -r, --relations 209 | show relation to features / integration branches 210 | --dump-config 211 | print out current configuration and exit 212 | 213 | USAGE 214 | git-wtf uses some heuristics to determine which branches are integration branches, and which 215 | are feature branches. (Specifically, it assumes the integration branches are named "master", 216 | "next" and "edge".) If it guesses incorrectly, you will have to create a .git-wtfrc file. 217 | 218 | To start building a configuration file, run "git-wtf --dump-config > .git-wtfrc" and edit it. 219 | The config file is a YAML file that specifies the integration branches, any branches to 220 | ignore, and the max number of commits to display when --all-commits isn't used. git-wtf will 221 | look for a .git-wtfrc file starting in the current directory, and recursively up to the root. 222 | 223 | IMPORTANT NOTE: all local branches referenced in .git-wtfrc must be prefixed with heads/, e.g. 224 | "heads/master". Remote branches must be of the form remotes//. 225 | 226 | DISCUSSIONS 227 | This script is originally copyrighted to William Morgan . 228 | It is under terms of licensed in GNU General Public License. 229 | ``` 230 | [Go to top](#top) 231 | 232 | 233 | #### gitanalyzer 234 | 235 | ``` 236 | NAME 237 | gitanalyzer - Analyzes the index and object database of git repositories 238 | 239 | SYNOPSIS 240 | gitanalyzer 241 | 242 | DESCRIPTION 243 | gitanalyzer is a Groovy script using native Git commands. It analyzes the index (staging area) 244 | and the object database (i.e. .git/objects directory) and produces a report. 245 | 246 | The script checks all the objects in object database, compares them with the entries in index 247 | and detects the obsolete ones. As a result, the script creates a report listing all the blobs, 248 | trees, commits and tags with a mark implying the unreferenced objects. 249 | 250 | OPTIONS 251 | -d, --debug 252 | prints debug log information on the report 253 | 254 | USAGE 255 | The script should be called in a git repository. 256 | 257 | gitanalyzer 258 | 259 | The report displays all objects including files, folders, commits and tags as a list. At the 260 | end a summary is displayed. 261 | 262 | *** INDEX vs OBJECT DATABASE: 263 | index count : 117597 264 | object count : 117597 265 | obsolete count : 0 266 | 267 | *** OBJECT TYPES: 268 | commit : 5315 269 | tree : 89948 270 | blob : 22308 271 | tag : 26 272 | 273 | *** TIMINGS: 274 | execution time : 1185894.095 ms (20 min) 275 | 276 | DISCUSSIONS 277 | The initial aim of this script was to see the impact of garbage collection on objects. But 278 | then the report extended a little bit to prepare overall picture of the index and the object 279 | database. 280 | 281 | The script runs slow for big projects due to analysis per object. We plan to improve the 282 | performance in the coming releases. 283 | ``` 284 | [Go to top](#top) 285 | 286 | 287 | #### git-practice-platform 288 | 289 | ``` 290 | NAME 291 | git-practice-platform - Creates a bare repo and multiple users for practicing git 292 | 293 | SYNOPSIS 294 | git-practice-platform 295 | [-f | --folder ] 296 | [-r | --repository ] 297 | [-u | --usercount ] 298 | 299 | DESCRIPTION 300 | git-practice-platform is a Bash script that creates a bare repository for simulating remote 301 | repositories, multiple clients cloning the bare repository simulating a teamwork on the 302 | repository. 303 | 304 | OPTIONS 305 | -f | --folder 306 | The name of the root folder where all users clone the repository in it 307 | -r | --repository 308 | The name of the repository which is created for testing purposes 309 | -u | --usercount 310 | Number of cloning users. If not defined, 2 users are created by default. 311 | USAGE 312 | In order to create 3 users, the following command can be used. 313 | 314 | $ git-practice-platform -u 3 -f practicegit -r uberproject 315 | Root folder >> 316 | Created as /Users/user/practicegit 317 | Bare repository >> 318 | Created under server/uberproject.git 319 | User1 repository >> 320 | Created folder server/uberproject.git 321 | Cloned under clients/user1/uberproject.git 322 | Applied initial git configurations 323 | Created file .gitignore 324 | User2 repository >> 325 | Created folder server/uberproject.git 326 | Cloned under clients/user2/uberproject.git 327 | Applied initial git configurations 328 | Created file .gitignore 329 | User3 repository >> 330 | Created folder server/uberproject.git 331 | Cloned under clients/user3/uberproject.git 332 | Applied initial git configurations 333 | Created file .gitignore 334 | 1 bare repository and 3 clients are created. Ready for practicing. 335 | 336 | And the following folder structure is created. 337 | 338 | practicegit/ 339 | |-- clients 340 | | |-- user1 341 | | | `-- uberproject.git 342 | | |-- user2 343 | | | `-- uberproject.git 344 | | `-- user3 345 | | `-- uberproject.git 346 | `-- server 347 | `-- uberproject.git 348 | 349 | DISCUSSIONS 350 | The best way of learning git is practicing it. We've been practicing git by creating bare 351 | and cloning repositories on our locals for a long time. This script automatises the whole 352 | create folders process and lets you initialize the practicing platform with one command. 353 | ``` 354 | [Go to top](#top) 355 | 356 | 357 | #### live-findfiles 358 | 359 | ``` 360 | NAME 361 | live-findfiles - Continuously displays the files with their paths as a list under the current 362 | folder 363 | 364 | SYNOPSIS 365 | live-findfiles [] 366 | 367 | DESCRIPTION 368 | live-findfiles is a Bash script that runs find command on every second and displays the files 369 | with their paths (including hidden files and folders) 370 | 371 | USAGE 372 | The script runs `find` command and displays the content as a list. By default, it displays 373 | content of the current directory. 374 | 375 | live-findfiles 376 | live-findfiles . 377 | 378 | It also displays the content of any given directory continuously. 379 | 380 | live-findfiles /home/user/project/logs 381 | live-findfiles ~/data 382 | live-findfiles ../../items 383 | 384 | DISCUSSIONS 385 | The script can be used to monitor the contents of folders and visualize the impact of git 386 | commands. We use it to monitor object warehouse (.git/objects folder) while using git 387 | commands (add, commit and gc). 388 | ``` 389 | [Go to top](#top) 390 | 391 | 392 | #### gitcatfile 393 | 394 | ``` 395 | NAME 396 | gitcatfile - Displays the raw content of a git object 397 | 398 | SYNOPSIS 399 | gitcatfile [-e ] 400 | 401 | DESCRIPTION 402 | gitanalyzer is a Groovy script. It inflates git objects and displays the raw content. The 403 | objects could be any files under `.git/objects/` directory. 404 | 405 | OPTIONS 406 | -e, --extracts 407 | Extracts the object in the given path 408 | 409 | USAGE 410 | A valid git object file should be passed to display the raw content. 411 | 412 | $ gitcatfile -e .git/objects/1e/8a2b3dcc1092004df16fd151334a963815f4d5 413 | tree 38\0 414 | 100644 readme.txt\0 aba8d507fce75a7b9e7b98f800618695c483608b 415 | 416 | DISCUSSIONS 417 | Normally we can also view the content of each object by using `git cat-file -p` command. 418 | However it formats the output and does not let you see the raw content. This script 419 | inflates the file via ZLib inflator and parses the content byte by byte. Then converts it 420 | into human-friendly form. 421 | 422 | The following the the output of git cat-file command. 423 | 424 | $ git cat-file -p 1e8a2b 425 | 100644 blob aba8d507fce75a7b9e7b98f800618695c483608b readme.txt 426 | 427 | And the following is the output for the same object by our script. 428 | 429 | $ gitcatfile -e .git/objects/1e/8a2b3dcc1092004df16fd151334a963815f4d5 430 | tree 38\0 431 | 100644 readme.txt\0 aba8d507fce75a7b9e7b98f800618695c483608b 432 | 433 | We see null character as "\0" and 20 byte SHA-1 of referencing objects as hex string. New 434 | lines are added between entries to visualize better. 435 | ``` 436 | [Go to top](#top) 437 | 438 | 439 | #### live-git-index 440 | 441 | ``` 442 | NAME 443 | live-git-index - Generates random file changes and commits to Git repository 444 | 445 | SYNOPSIS 446 | live-git-index [-a] 447 | 448 | DESCRIPTION 449 | live-git-index is a Bash script for continuously displaying the content of the index. 450 | 451 | OPTIONS 452 | -a 453 | Displays the content stored in index for all references. The script displays the 454 | content for the current branch by default. 455 | 456 | USAGE 457 | If no parameters are given, the script displays the content of index for the current 458 | branch. 459 | 460 | $ live-git-index 461 | Current Branch with Staged Content: 462 | 100644 266df426af79b41503d47474e8d0703ed03cfe77 0 readme.txt 463 | 464 | In order to display all the content for all references, use the `-a` option. 465 | 466 | $ live-git-index 467 | Current Branch with Staged Content: 468 | 100644 266df426af79b41503d47474e8d0703ed03cfe77 0 readme.txt 469 | 470 | master: 471 | 100644 blob 266df426af79b41503d47474e8d0703ed03cfe77 readme.txt 472 | 473 | development: 474 | 100644 blob aba8d507fce75a7b9e7b98f800618695c483608b readme.txt 475 | 476 | test: 477 | 100644 blob 5b65f7b6a270e011bdbba46884b0d00a392e8e9f readme.txt 478 | 479 | In the sample above, 3 branches show 3 different version of the same `readme.txt` file. 480 | 481 | DISCUSSIONS 482 | This script can be used to display the content of the index and visualize the changes 483 | after each git command (i.e. add, commit, reset, etc.) 484 | ``` 485 | [Go to top](#top) 486 | 487 | 488 | #### live-git-log 489 | 490 | ``` 491 | NAME 492 | live-git-log - Continuously displays log graph 493 | 494 | SYNOPSIS 495 | live-git-log [-c | --count ] 496 | 497 | DESCRIPTION 498 | live-git-log is a Bash script that continuously displays the commit graph. 499 | 500 | OPTIONS 501 | -c, --count 502 | Max number of commits displayed in the graph is 20 by default. You can change the 503 | number by using `count` option. 504 | 505 | USAGE 506 | The script uses one-line format for `git log` command. It displays the logs in one of the 507 | most human-friendly format (i.e. colored, one line, showing graph) 508 | 509 | live-git-log 510 | live-git-log -c 10 511 | 512 | DISCUSSIONS 513 | This script is one of the most used script during git practices. With the help of this 514 | script, you can display the changes in the commit graph whenever it is applied. 515 | ``` 516 | [Go to top](#top) 517 | 518 | 519 | #### live-git-reflog 520 | 521 | ``` 522 | NAME 523 | live-git-reflog - Continuously displays log graph 524 | 525 | SYNOPSIS 526 | live-git-reflog [-c | --count ] 527 | 528 | DESCRIPTION 529 | live-git-reflog is a Bash script that continuously displays the entries in your reflog. 530 | 531 | OPTIONS 532 | -c, --count 533 | Max number of reflog entries displayed is 20 by default. You can change the 534 | number by using `count` option. 535 | 536 | USAGE 537 | The script uses one-line format for `git log` command. It displays the logs in one of the 538 | most human-friendly format (i.e. colored, one line, showing graph) 539 | 540 | live-git-log 541 | live-git-log -c 10 542 | 543 | DISCUSSIONS 544 | This script is one of the most used script during git practices. With the help of this 545 | script, you can display the changes in the commit graph whenever it is applied. 546 | ``` 547 | [Go to top](#top) 548 | 549 | 550 | #### live-git-status 551 | 552 | ``` 553 | NAME 554 | live-git-status - Continuously displays the status of git repository 555 | 556 | SYNOPSIS 557 | live-git-status 558 | 559 | DESCRIPTION 560 | live-git-status is a Bash script that continuously runs `git status` and displays the result. 561 | 562 | USAGE 563 | The script uses one-line short format for `git status` command to consume minimum space. 564 | 565 | live-git-status 566 | 567 | DISCUSSIONS 568 | This script is used to monitor status of git repository after your changes. 569 | ``` 570 | [Go to top](#top) 571 | 572 | 573 | #### live-tree 574 | 575 | ``` 576 | NAME 577 | live-tree - Continuously displays files and folders of the given path in a tree structure 578 | 579 | SYNOPSIS 580 | live-tree [] 581 | 582 | DESCRIPTION 583 | live-tree is a Bash script that continuously displays files and folders of the given path 584 | in a tree structure. If path is not provided, the script uses the current path. 585 | 586 | OPTIONS 587 | 588 | Any directory to show the contents in tree format. 589 | 590 | USAGE 591 | The script uses `tree`command to build the tree structure. So if tree command is not 592 | installed in your system, you should install it. The script shows hidden files and folders 593 | too. 594 | 595 | live-tree 596 | live-tree /opt/data 597 | 598 | DISCUSSIONS 599 | This script is useful when continuously you want to display the contents of the folder having 600 | small number files and folders in it. If the contents is a lot, the terminal window might not 601 | display everything in one place. 602 | ``` 603 | [Go to top](#top) 604 | 605 | ### License 606 | 607 | The programs in this repository is free software: you can redistribute it and/or modify it under the 608 | terms of the GNU General Public License as published by the Free Software Foundation, either version 609 | 3 of the License, or (at your option) any later version. 610 | 611 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even 612 | the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 613 | License for more details. 614 | 615 | You can find the GNU General Public License at: [http://www.gnu.org/licenses](http://www.gnu.org/licenses) 616 | -------------------------------------------------------------------------------- /commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | function getFileName() { 6 | randomNumber=$(( ( RANDOM % 100 ) + 1 )) 7 | file="file$randomNumber" 8 | echo $file 9 | } 10 | 11 | filename=$1 12 | 13 | if [ -z "$filename" ]; then 14 | filename=$(getFileName) 15 | fi 16 | 17 | if [ -f "$filename" ]; then 18 | echo "File $filename already exists" 19 | exit 0 20 | fi 21 | 22 | touch $filename 23 | git add $filename 24 | git commit -m "creates $filename" 25 | -------------------------------------------------------------------------------- /git-config: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ###################### 4 | # CONFIGURATIONS TO CHANGE 5 | ###################### 6 | 7 | YOUR_NAME="name surname" 8 | YOUR_EMAIL="email@sample.com" 9 | PATH_TO_KDIFF3="/Applications/kdiff3.app/Contents/MacOS/kdiff3" 10 | 11 | ###################### 12 | # This file contains useful git config commands with definitions 13 | # All configs are set globally in order to make it available for all your projects 14 | # Before running the commands, DO NOT FORGET to replace the words in curly braces with corresponding the data 15 | ###################### 16 | 17 | ###################### 18 | # INITIAL SETUP 19 | ###################### 20 | 21 | # First config you have to do is to add your name and email to git 22 | git config --global user.name "${YOUR_NAME}" 23 | git config --global user.email "${YOUR_EMAIL}" 24 | 25 | ###################### 26 | # ALIASES 27 | ###################### 28 | 29 | # Shortcut for most used command as checkout 30 | git config --global alias.co "checkout" 31 | 32 | # Shortcut for most used command as status 33 | git config --global alias.s "status -uall" 34 | 35 | # Shows all branches including local and remote ones 36 | git config --global alias.b "branch -av" 37 | 38 | # Shows remote name - url mapping for fetch and push 39 | git config --global alias.r "remote -v" 40 | 41 | # Shows all existing tags 42 | git config --global alias.t "tag -l" 43 | 44 | # Combines git add all and git commit in one command 45 | git config --global alias.ac '!git add -A && git commit' 46 | 47 | # sychronize master branch without checking in it 48 | git config --global alias.master 'fetch origin master:master' 49 | 50 | # Shows diff between the version in working copy and the object database 51 | git config --global alias.d "difftool" 52 | 53 | # Shows diff between the version in working copy and staging area 54 | git config --global alias.dc "difftool --cached" 55 | 56 | # Shows diff between the version in working copy and staging area 57 | git config --global alias.dl "difftool HEAD..HEAD~" 58 | 59 | # FOR VERSIONS AFTER GIT v2.0 60 | # Shows all existing tags in a sorted list 61 | git config --global alias.t "tag -l --sort=version:refname" 62 | 63 | # Displays commit graph is a nice colored view for all branches including stashes 64 | # Use "-N" for displaying the last N commits 65 | git config --global alias.las "log --graph --all --pretty=format:'%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative" 66 | 67 | # Displays commit graph is a nice colored view for all branches 68 | git config --global alias.la "log --branches --remotes --tags --graph --pretty=format:'%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative" 69 | 70 | # Displays commit graph is a nice colored view for just the current branch 71 | # Use "-N" for displaying the last N commits 72 | git config --global alias.lb "log --graph --pretty=format:'%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative" 73 | 74 | # Displays each commit with full details one after another in a list view 75 | # Use "-N" for displaying the last N commits 76 | git config --global alias.lf "log --format=fuller" 77 | 78 | # Displays each commit as 1 line. It is mainly used to see commits in one shot. 79 | # Use "-N" for displaying the last N commits 80 | git config --global alias.l1 "log --stat --pretty=oneline" 81 | 82 | # Displays reflog data with the information of related commits 83 | git config --global alias.ref "log -g --pretty=format:'%C(yellow)%h%Creset %gD: %gs %Cred==>%Creset %C(yellow)%s%Creset (%cr) <%an>' --date=iso" 84 | 85 | # Removes the change set of your last commit from the commit store (your project's object database) 86 | # And keeps it in Index. The commit message of your last commit is also re-used in your next commit. 87 | git config --global alias.amend "commit --amend -C HEAD" 88 | 89 | # push new branch without setting a branch name to the upstream 90 | git config --global alias.pnb "push -u origin HEAD" 91 | 92 | # Displays the content of your given commit 93 | # Usage: git showco COMMIT_ID 94 | git config --global alias.showco "!git --no-pager show --stat --pretty=format:'%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit" 95 | 96 | # Removes all un-committed changes, including untracked files and the ones in Index 97 | git config --global alias.clear "!git reset --hard HEAD; git clean -f" 98 | 99 | # Removes all changesets from staging area 100 | git config --global alias.unstage "reset HEAD" 101 | 102 | # Shows commit messages one line per commit between 2 commits 103 | # Use like "git changelog v01..HEAD" 104 | git config --global alias.changelog "log --pretty=format:' * %s'" 105 | 106 | # Displays last update date for all existing tags and branches in a graph view 107 | # Then you can learn all old legacy branches and tags from old days 108 | git config --global alias.dates "log --date-order --graph --tags --branches --simplify-by-decoration --pretty=format:'%ai %h %d'" 109 | 110 | # Forces to run garbage collector to remove unreferenced objects and pack the others 111 | git config --global alias.gc-all "-c gc.reflogExpire=0 -c gc.reflogExpireUnreachable=0 -c gc.rerereresolved=0 -c gc.rerereunresolved=0 -c gc.pruneExpire=now gc --force" 112 | 113 | ###################### 114 | # CORE 115 | ###################### 116 | 117 | # Git does not mark your code if the line endings are changed 118 | git config --global core.autocrlf "false" 119 | 120 | # Sets the default editor for writing commit messages or editing interactive rebase commands to vim 121 | git config --global core.editor "vim" 122 | 123 | # Change the default pager application to 'less' 124 | git config --global --replace-all core.pager "less -iXFR" 125 | 126 | 127 | ###################### 128 | # PUSH 129 | ###################### 130 | 131 | # FOR VERSIONS BEFORE GIT v2.0 132 | # Before git v2, push command sends all your un-pushed commits regardless of checking the source branch 133 | # It means you will push commits from other branches if you don't specify branch like "git push origin BRANCH_NAME" 134 | # By changing the default behavior to simple, you guarantee to push the commits of your current branch only 135 | git config --global push.default "simple" 136 | 137 | ###################### 138 | # BRANCH 139 | ###################### 140 | 141 | # Pull will always do fetch + rebase instead of fetch + merge as default behavior 142 | git config --global branch.autosetuprebase "always" 143 | 144 | # Whenever you create a branch via "branch" or "checkout -b" commands, git will automatically track the remote one 145 | git config --global branch.autosetupmerge "always" 146 | 147 | ###################### 148 | # LOG 149 | ###################### 150 | 151 | # Print out the ref names of any commits that are shown. The ref name prefixes refs/heads/, refs/tags/ and refs/remotes/ will be printed. 152 | git config --global log.decorate "full" 153 | 154 | # Shows timestamps in ISO 8601 format: http://en.wikipedia.org/wiki/ISO_8601 155 | git config --global log.date "iso" 156 | 157 | ###################### 158 | # COLOR 159 | ###################### 160 | 161 | # Prints all outputs in color whenever possible 162 | git config --global color.diff "auto" 163 | git config --global color.status "auto" 164 | git config --global color.branch "auto" 165 | git config --global color.interactive "auto" 166 | git config --global color.ui "auto" 167 | 168 | ###################### 169 | # DIFF 170 | ###################### 171 | 172 | # Changes the diff algorithm to a more efficient one 173 | git config --global diff.algorithm "patience" 174 | 175 | # Tell git diff to use mnemonic prefixes (index, work tree, commit, object) instead of the standard a and b notation 176 | git config --global diff.mnemonicprefix "true" 177 | 178 | # Allow git diff to do basic rename and copy detection 179 | git config --global diff.renames "copies" 180 | 181 | ###################### 182 | # DIFFTOOL 183 | ###################### 184 | 185 | # Using kdiff3 for resolving for diffs. 186 | git config --global difftool.kdiff3.path "${PATH_TO_KDIFF3}" 187 | git config --global difftool.kdiff3.trustExitCode "true" 188 | git config --global difftool.prompt "false" 189 | git config --global diff.tool "kdiff3" 190 | 191 | ###################### 192 | # MERGE 193 | ###################### 194 | 195 | # Puts the commit messages of the ones that are merged to the commit message of the merge 196 | git config --global merge.summary "true" 197 | 198 | # Always show a diffstat at the end of a merge 199 | git config --global merge.stat "true" 200 | 201 | ###################### 202 | # MERGETOOL 203 | ###################### 204 | 205 | # Using kdiff3 for resolving for resolving merge conflicts. 206 | git config --global mergetool.kdiff3.path "{PATH_TO_KDIFF3}" 207 | git config --global mergetool.kdiff3.trustExitCode "true" 208 | git config --global mergetool.keepBackup "false" 209 | git config --global merge.tool "kdiff3" 210 | 211 | ###################### 212 | # CREDENTIALS 213 | ###################### 214 | 215 | # if you're on Mac OS and used homebrew to install git, you can use the native Mac OS keystore 216 | # Then there will be no need to enter your passphrase on every pull & push 217 | git config --global credential.helper "osxkeychain" 218 | -------------------------------------------------------------------------------- /git-find-repos: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | usage() { 6 | echo "USAGE: git-find-repos [-h | --help] [-d | --dirty] " 7 | exit 1 8 | } 9 | 10 | dirty= 11 | path=$(pwd) 12 | 13 | realargs="$@" 14 | while [ $# -gt 0 ]; do 15 | case "$1" in 16 | -h | --help) 17 | usage 18 | ;; 19 | -d | --dirty) 20 | dirty=1 21 | ;; 22 | *) 23 | path=$1 24 | ;; 25 | esac 26 | shift 27 | done 28 | set -- $realargs 29 | 30 | COUNTER=0 31 | DIRTYCOUNTER=0 32 | 33 | start_time=`date +%s` 34 | 35 | if [ -n "$dirty" ]; then 36 | while IFS= read -r -d $'\0' line; do 37 | COUNTER=$(( $COUNTER + 1 )) 38 | tmpPath=$(dirname ${line}) 39 | 40 | cd ${tmpPath} 41 | gitStatus=$(git status -uall -s 2>/dev/null) 42 | gitUnpushedCommits=$(git --no-pager log --graph --branches --not --remotes --oneline 2>/dev/null) 43 | 44 | if [ -n "$gitStatus" ] || [ -n "$gitUnpushedCommits" ]; then 45 | DIRTYCOUNTER=$(( $DIRTYCOUNTER + 1 )) 46 | echo "[$DIRTYCOUNTER.] $tmpPath" 47 | fi 48 | done < <(find $path -type d -name ".git" -print0 2>/dev/null) 49 | echo "Found $DIRTYCOUNTER changed repos among $COUNTER git repositories under $path in $(expr `date +%s` - $start_time) seconds" 50 | else 51 | while IFS= read -r -d $'\0' line; do 52 | COUNTER=$(( $COUNTER + 1 )) 53 | echo "[$COUNTER.] ${line}" 54 | done < <(find $path -type d -name ".git" -print0 2>/dev/null) 55 | echo "Found $COUNTER git repositories under $path in $(expr `date +%s` - $start_time) seconds" 56 | fi 57 | 58 | -------------------------------------------------------------------------------- /git-generate-changes: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | fileNameNumberUpperLimit=100 6 | commentNumberUpperLimit=1000 7 | 8 | function getFileName() { 9 | randomNumber=$(( ( RANDOM % $fileNameNumberUpperLimit ) + 1 )) 10 | file="$1$randomNumber.$2" 11 | echo $file 12 | } 13 | 14 | function create() { 15 | if [ -f $1 ]; 16 | then 17 | echo "Modified file $randomNumber" >> $1 18 | echo -n " ... modified" 19 | else 20 | echo "Created file $randomNumber" >> $1 21 | echo -n " ... created" 22 | fi 23 | } 24 | 25 | function isInGitRepo() { 26 | git rev-parse --is-inside-work-tree 2>/dev/null 27 | statusCode=$? 28 | if [ $statusCode -eq 0 ]; then 29 | echo 1 30 | else 31 | echo 0 32 | fi 33 | } 34 | 35 | function commitToGitRepo() { 36 | if [ -n "$1" ]; then 37 | git add -A 38 | randomNumber=$(( ( RANDOM % $commentNumberUpperLimit ) + 1 )) 39 | git commit -m "Random changes #${randomNumber}" >/dev/null 2>&1 40 | echo -n ", committed" 41 | fi 42 | } 43 | 44 | usage() { echo "USAGE: git-generate-changes -g -p -c -e "; exit 1; } 45 | 46 | prefix="temp" 47 | count=1 48 | extension="txt" 49 | togit= 50 | 51 | realargs="$@" 52 | while [ $# -gt 0 ]; do 53 | case "$1" in 54 | -h | --help) 55 | usage 56 | ;; 57 | -p | --prefix) 58 | prefix=$2 59 | shift 60 | ;; 61 | -c | --count) 62 | count=$2 63 | shift 64 | ;; 65 | -e | --extension) 66 | extension=$2 67 | shift 68 | ;; 69 | -g | --commit) 70 | togit=1 71 | ;; 72 | *) 73 | usage 74 | break 75 | ;; 76 | esac 77 | shift 78 | done 79 | set -- $realargs 80 | 81 | gitStatus=$(isInGitRepo) 82 | if [ -n "$togit" ] && [ "$gitStatus" == true ]; then 83 | echo "FATAL: The current directory is not in a git repository" 84 | exit 0 85 | fi 86 | 87 | counter=1; 88 | while [[ $counter -le "$count" ]]; 89 | do 90 | file=$(getFileName $prefix $extension) 91 | echo -n "$file" 92 | create $file 93 | commitToGitRepo $togit 94 | echo "" 95 | let "counter += 1"; 96 | done 97 | 98 | 99 | -------------------------------------------------------------------------------- /git-list-branches: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "" 3 | echo -e "###> \033[31mMANDATORY BRANCHES\033[0m" 4 | echo "" 5 | echo -e " \033[32mmaster\033[0m and \033[32mdevelopment\033[0m branches are excluded from the list" 6 | 7 | count=$(git branch -r --no-merged | grep -v master | grep -v development | xargs -L1 git --no-pager log --pretty=tformat:'%Cgreen%d%Creset - %h by %an (%Cblue%ar%Creset)' -1 | wc -l) 8 | echo "" 9 | echo -e "###> \033[31m${count//[[:space:]]/} UNMERGED REMOTE BRANCHES\033[0m" 10 | echo "" 11 | 12 | git branch -r --no-merged | grep -v master | grep -v development | xargs -L1 git --no-pager log --pretty=tformat:'%Cgreen%d%Creset - %h by %an (%Cblue%ar%Creset)' -1 13 | 14 | count=$(git branch --no-merged | grep -v master | grep -v development | xargs -L1 git --no-pager log --pretty=tformat:'%Cgreen%d%Creset - %h by %an (%Cblue%ar%Creset)' -1 | wc -l) 15 | echo "" 16 | echo -e "###> \033[31m${count//[[:space:]]/} UNMERGED LOCAL BRANCHES\033[0m" 17 | echo "" 18 | 19 | git branch --no-merged | grep -v master | grep -v development | xargs -L1 git --no-pager log --pretty=tformat:'%Cgreen%d%Creset - %h by %an (%Cblue%ar%Creset)' -1 20 | 21 | count=$(git branch -r --merged | grep -v master | grep -v development | xargs -L1 git --no-pager log --pretty=tformat:'%Cgreen%d%Creset - %h by %an (%Cblue%ar%Creset)' -1 | wc -l) 22 | echo "" 23 | echo -e "###> \033[31m${count//[[:space:]]/} MERGED REMOTE BRANCHES\033[0m" 24 | echo "" 25 | 26 | git branch -r --merged | grep -v master | grep -v development | xargs -L1 git --no-pager log --pretty=tformat:'%Cgreen%d%Creset - %h by %an (%Cblue%ar%Creset)' -1 27 | 28 | count=$(git branch --merged | grep -v master | grep -v development | xargs -L1 git --no-pager log --pretty=tformat:'%Cgreen%d%Creset - %h by %an (%Cblue%ar%Creset)' -1 | wc -l) 29 | echo "" 30 | echo -e "###> \033[31m${count//[[:space:]]/} MERGED LOCAL BRANCHES\033[0m" 31 | echo "" 32 | 33 | git branch --merged | grep -v master | grep -v development | xargs -L1 git --no-pager log --pretty=tformat:'%Cgreen%d%Creset - %h by %an (%Cblue%ar%Creset)' -1 34 | 35 | echo "" 36 | -------------------------------------------------------------------------------- /git-practice-platform: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | usage() { 6 | echo "USAGE: git-practice-platform [-f | --folder ] [-r | --repository ] [-u | --usercount ]" 7 | exit 1 8 | } 9 | 10 | repo= 11 | folder= 12 | user_count=2 13 | path=$(pwd) 14 | 15 | realargs="$@" 16 | while [ $# -gt 0 ]; do 17 | case "$1" in 18 | -r | --repository) 19 | repo=$2 20 | shift 21 | ;; 22 | -f | --folder) 23 | folder=$2 24 | shift 25 | ;; 26 | -u | --usercount) 27 | user_count=$2 28 | shift 29 | ;; 30 | *) 31 | usage 32 | ;; 33 | esac 34 | shift 35 | done 36 | set -- $realargs 37 | 38 | if [ -z "$repo" ] || [ -z "$folder" ]; then 39 | usage 40 | return 41 | fi 42 | 43 | if [ -d "$path/$folder" ]; then 44 | echo "Folder already exists in $path/$folder" 45 | echo "Quited" 46 | exit 0 47 | fi 48 | 49 | root=${path}/${folder} 50 | mkdir -p ${root} 51 | echo "Root folder >>" 52 | echo " Created as ${root}" 53 | 54 | mkdir -p ${root}/server/${repo}.git > /dev/null 2>&1 55 | cd ${root}/server/${repo}.git 56 | git init --bare > /dev/null 2>&1 57 | echo "Bare repository >>" 58 | echo " Created under server/${repo}.git" 59 | 60 | for (( i=1; i<=${user_count}; i++ )) 61 | do 62 | echo "User${i} repository >>" 63 | 64 | mkdir -p ${root}/clients > /dev/null 2>&1 65 | mkdir -p ${root}/clients/user${i} > /dev/null 2>&1 66 | echo " Created folder server/${repo}.git" 67 | 68 | cd ${root}/clients/user${i} 69 | git clone "file://${root}/server/${repo}.git" ${repo}.git > /dev/null 2>&1 70 | echo " Cloned under clients/user${i}/${repo}.git" 71 | 72 | cd ${root}/clients/user${i}/${repo}.git 73 | git config user.name "User${i}" 74 | git config user.email "user${i}@gittraining.com" 75 | git config branch.autosetuprebase never 76 | git config branch.autosetupmerge true 77 | git config branch.master.rebase false 78 | git config alias.la "log --graph --all --pretty=format:'%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative" 79 | git config alias.lb "log --graph --pretty=format:'%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative" 80 | git config alias.lf "log --format=fuller" 81 | git config alias.l1 "log --stat --pretty=oneline" 82 | 83 | echo " Applied initial git configurations" 84 | 85 | done 86 | 87 | echo "1 bare repository and ${user_count} clients are created. Ready for practicing." 88 | -------------------------------------------------------------------------------- /git-wtf: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | HELP = < 54 | .git-wtfrc" and edit it. The config file is a YAML file that specifies the 55 | integration branches, any branches to ignore, and the max number of commits to 56 | display when --all-commits isn't used. git-wtf will look for a .git-wtfrc file 57 | starting in the current directory, and recursively up to the root. 58 | 59 | IMPORTANT NOTE: all local branches referenced in .git-wtfrc must be prefixed 60 | with heads/, e.g. "heads/master". Remote branches must be of the form 61 | remotes//. 62 | EOS 63 | 64 | COPYRIGHT = <. 66 | This program is free software: you can redistribute it and/or modify it 67 | under the terms of the GNU General Public License as published by the Free 68 | Software Foundation, either version 3 of the License, or (at your option) 69 | any later version. 70 | 71 | This program is distributed in the hope that it will be useful, but WITHOUT 72 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 73 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 74 | more details. 75 | 76 | You can find the GNU General Public License at: http://www.gnu.org/licenses/ 77 | EOS 78 | 79 | require 'yaml' 80 | CONFIG_FN = ".git-wtfrc" 81 | 82 | class Numeric; def pluralize s; "#{to_s} #{s}" + (self != 1 ? "s" : "") end end 83 | 84 | if ARGV.delete("--help") || ARGV.delete("-h") 85 | puts USAGE 86 | exit 87 | end 88 | 89 | ## poor man's trollop 90 | $long = ARGV.delete("--long") || ARGV.delete("-l") 91 | $short = ARGV.delete("--short") || ARGV.delete("-s") 92 | $all = ARGV.delete("--all") || ARGV.delete("-a") 93 | $all_commits = ARGV.delete("--all-commits") || ARGV.delete("-A") 94 | $dump_config = ARGV.delete("--dump-config") 95 | $key = ARGV.delete("--key") || ARGV.delete("-k") 96 | $show_relations = ARGV.delete("--relations") || ARGV.delete("-r") 97 | ARGV.each { |a| abort "Error: unknown argument #{a}." if a =~ /^--/ } 98 | 99 | ## search up the path for a file 100 | def find_file fn 101 | while true 102 | return fn if File.exist? fn 103 | fn2 = File.join("..", fn) 104 | return nil if File.expand_path(fn2) == File.expand_path(fn) 105 | fn = fn2 106 | end 107 | end 108 | 109 | want_color = `git config color.wtf` 110 | want_color = `git config color.ui` if want_color.empty? 111 | $color = case want_color.chomp 112 | when "true"; true 113 | when "auto"; $stdout.tty? 114 | end 115 | 116 | def red s; $color ? "\033[31m#{s}\033[0m" : s end 117 | def green s; $color ? "\033[32m#{s}\033[0m" : s end 118 | def yellow s; $color ? "\033[33m#{s}\033[0m" : s end 119 | def cyan s; $color ? "\033[36m#{s}\033[0m" : s end 120 | def grey s; $color ? "\033[1;30m#{s}\033[0m" : s end 121 | def purple s; $color ? "\033[35m#{s}\033[0m" : s end 122 | 123 | ## the set of commits in 'to' that aren't in 'from'. 124 | ## if empty, 'to' has been merged into 'from'. 125 | def commits_between from, to 126 | if $long 127 | `git log --pretty=format:"- %s [#{yellow "%h"}] (#{purple "%ae"}; %ar)" #{from}..#{to}` 128 | else 129 | `git log --pretty=format:"- %s [#{yellow "%h"}]" #{from}..#{to}` 130 | end.split(/[\r\n]+/) 131 | end 132 | 133 | def show_commits commits, prefix=" " 134 | if commits.empty? 135 | puts "#{prefix} none" 136 | else 137 | max = $all_commits ? commits.size : $config["max_commits"] 138 | max -= 1 if max == commits.size - 1 # never show "and 1 more" 139 | commits[0 ... max].each { |c| puts "#{prefix}#{c}" } 140 | puts grey("#{prefix}... and #{commits.size - max} more (use -A to see all).") if commits.size > max 141 | end 142 | end 143 | 144 | def ahead_behind_string ahead, behind 145 | [ahead.empty? ? nil : "#{ahead.size.pluralize 'commit'} ahead", 146 | behind.empty? ? nil : "#{behind.size.pluralize 'commit'} behind"]. 147 | compact.join("; ") 148 | end 149 | 150 | def widget merged_in, remote_only=false, local_only=false, local_only_merge=false 151 | left, right = case 152 | when remote_only; %w({ }) 153 | when local_only; %w{( )} 154 | else %w([ ]) 155 | end 156 | middle = case 157 | when merged_in && local_only_merge; green("~") 158 | when merged_in; green("x") 159 | else " " 160 | end 161 | print left, middle, right 162 | end 163 | 164 | def show b 165 | have_both = b[:local_branch] && b[:remote_branch] 166 | 167 | pushc, pullc, oosync = if have_both 168 | [x = commits_between(b[:remote_branch], b[:local_branch]), 169 | y = commits_between(b[:local_branch], b[:remote_branch]), 170 | !x.empty? && !y.empty?] 171 | end 172 | 173 | if b[:local_branch] 174 | puts "Local branch: " + green(b[:local_branch].sub(/^heads\//, "")) 175 | 176 | if have_both 177 | if pushc.empty? 178 | puts "#{widget true} in sync with remote" 179 | else 180 | action = oosync ? "push after rebase / merge" : "push" 181 | puts "#{widget false} NOT in sync with remote (you should #{action})" 182 | show_commits pushc unless $short 183 | end 184 | end 185 | end 186 | 187 | if b[:remote_branch] 188 | puts "Remote branch: #{cyan b[:remote_branch]} (#{b[:remote_url]})" 189 | 190 | if have_both 191 | if pullc.empty? 192 | puts "#{widget true} in sync with local" 193 | else 194 | action = pushc.empty? ? "merge" : "rebase / merge" 195 | puts "#{widget false} NOT in sync with local (you should #{action})" 196 | show_commits pullc unless $short 197 | end 198 | end 199 | end 200 | 201 | puts "\n#{red "WARNING"}: local and remote branches have diverged. A merge will occur unless you rebase." if oosync 202 | end 203 | 204 | def show_relations b, all_branches 205 | ibs, fbs = all_branches.partition { |name, br| $config["integration-branches"].include?(br[:local_branch]) || $config["integration-branches"].include?(br[:remote_branch]) } 206 | if $config["integration-branches"].include? b[:local_branch] 207 | puts "\nFeature branches:" unless fbs.empty? 208 | fbs.each do |name, br| 209 | next if $config["ignore"].member?(br[:local_branch]) || $config["ignore"].member?(br[:remote_branch]) 210 | next if br[:ignore] 211 | local_only = br[:remote_branch].nil? 212 | remote_only = br[:local_branch].nil? 213 | name = if local_only 214 | purple br[:name] 215 | elsif remote_only 216 | cyan br[:name] 217 | else 218 | green br[:name] 219 | end 220 | 221 | ## for remote_only branches, we'll compute wrt the remote branch head. otherwise, we'll 222 | ## use the local branch head. 223 | head = remote_only ? br[:remote_branch] : br[:local_branch] 224 | 225 | remote_ahead = b[:remote_branch] ? commits_between(b[:remote_branch], head) : [] 226 | local_ahead = b[:local_branch] ? commits_between(b[:local_branch], head) : [] 227 | 228 | if local_ahead.empty? && remote_ahead.empty? 229 | puts "#{widget true, remote_only, local_only} #{name} #{local_only ? "(local-only) " : ""}is merged in" 230 | elsif local_ahead.empty? 231 | puts "#{widget true, remote_only, local_only, true} #{name} merged in (only locally)" 232 | else 233 | behind = commits_between head, (br[:local_branch] || br[:remote_branch]) 234 | ahead = remote_only ? remote_ahead : local_ahead 235 | puts "#{widget false, remote_only, local_only} #{name} #{local_only ? "(local-only) " : ""}is NOT merged in (#{ahead_behind_string ahead, behind})" 236 | show_commits ahead unless $short 237 | end 238 | end 239 | else 240 | puts "\nIntegration branches:" unless ibs.empty? # unlikely 241 | ibs.sort_by { |v, br| v }.each do |v, br| 242 | next if $config["ignore"].member?(br[:local_branch]) || $config["ignore"].member?(br[:remote_branch]) 243 | next if br[:ignore] 244 | local_only = br[:remote_branch].nil? 245 | remote_only = br[:local_branch].nil? 246 | name = remote_only ? cyan(br[:name]) : green(br[:name]) 247 | 248 | ahead = commits_between v, (b[:local_branch] || b[:remote_branch]) 249 | if ahead.empty? 250 | puts "#{widget true, local_only} merged into #{name}" 251 | else 252 | #behind = commits_between b[:local_branch], v 253 | puts "#{widget false, local_only} NOT merged into #{name} (#{ahead.size.pluralize 'commit'} ahead)" 254 | show_commits ahead unless $short 255 | end 256 | end 257 | end 258 | end 259 | 260 | #### EXECUTION STARTS HERE #### 261 | 262 | ## find config file and load it 263 | $config = { "integration-branches" => %w(heads/master heads/next heads/edge), "ignore" => [], "max_commits" => 5 }.merge begin 264 | fn = find_file CONFIG_FN 265 | if fn && (h = YAML::load_file(fn)) # yaml turns empty files into false 266 | h["integration-branches"] ||= h["versions"] # support old nomenclature 267 | h 268 | else 269 | {} 270 | end 271 | end 272 | 273 | if $dump_config 274 | puts $config.to_yaml 275 | exit 276 | end 277 | 278 | ## first, index registered remotes 279 | remotes = `git config --get-regexp ^remote\.\*\.url`.split(/[\r\n]+/).inject({}) do |hash, l| 280 | l =~ /^remote\.(.+?)\.url (.+)$/ or next hash 281 | hash[$1] ||= $2 282 | hash 283 | end 284 | 285 | ## next, index followed branches 286 | branches = `git config --get-regexp ^branch\.`.split(/[\r\n]+/).inject({}) do |hash, l| 287 | case l 288 | when /branch\.(.*?)\.remote (.+)/ 289 | name, remote = $1, $2 290 | 291 | hash[name] ||= {} 292 | hash[name].merge! :remote => remote, :remote_url => remotes[remote] 293 | when /branch\.(.*?)\.merge ((refs\/)?heads\/)?(.+)/ 294 | name, remote_branch = $1, $4 295 | hash[name] ||= {} 296 | hash[name].merge! :remote_mergepoint => remote_branch 297 | end 298 | hash 299 | end 300 | 301 | ## finally, index all branches 302 | remote_branches = {} 303 | `git show-ref`.split(/[\r\n]+/).each do |l| 304 | sha1, ref = l.chomp.split " refs/" 305 | 306 | if ref =~ /^heads\/(.+)$/ # local branch 307 | name = $1 308 | next if name == "HEAD" 309 | branches[name] ||= {} 310 | branches[name].merge! :name => name, :local_branch => ref 311 | elsif ref =~ /^remotes\/(.+?)\/(.+)$/ # remote branch 312 | remote, name = $1, $2 313 | remote_branches["#{remote}/#{name}"] = true 314 | next if name == "HEAD" 315 | ignore = !($all || remote == "origin") 316 | 317 | branch = name 318 | if branches[name] && branches[name][:remote] == remote 319 | # nothing 320 | else 321 | name = "#{remote}/#{branch}" 322 | end 323 | 324 | branches[name] ||= {} 325 | branches[name].merge! :name => name, :remote => remote, :remote_branch => "#{remote}/#{branch}", :remote_url => remotes[remote], :ignore => ignore 326 | end 327 | end 328 | 329 | ## assemble remotes 330 | branches.each do |k, b| 331 | next unless b[:remote] && b[:remote_mergepoint] 332 | b[:remote_branch] = if b[:remote] == "." 333 | b[:remote_mergepoint] 334 | else 335 | t = "#{b[:remote]}/#{b[:remote_mergepoint]}" 336 | remote_branches[t] && t # only if it's still alive 337 | end 338 | end 339 | 340 | show_dirty = ARGV.empty? 341 | targets = if ARGV.empty? 342 | [`git symbolic-ref HEAD`.chomp.sub(/^refs\/heads\//, "")] 343 | else 344 | ARGV.map { |x| x.sub(/^heads\//, "") } 345 | end.map { |t| branches[t] or abort "Error: can't find branch #{t.inspect}." } 346 | 347 | targets.each do |t| 348 | show t 349 | show_relations t, branches if $show_relations || t[:remote_branch].nil? 350 | end 351 | 352 | modified = show_dirty && `git ls-files -m` != "" 353 | uncommitted = show_dirty && `git diff-index --cached HEAD` != "" 354 | 355 | if $key 356 | puts 357 | puts KEY 358 | end 359 | 360 | puts if modified || uncommitted 361 | puts "#{red "NOTE"}: working directory contains modified files." if modified 362 | puts "#{red "NOTE"}: staging area contains staged but uncommitted files." if uncommitted 363 | 364 | # the end! 365 | 366 | -------------------------------------------------------------------------------- /gitanalyzer: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env groovy 2 | 3 | import java.text.SimpleDateFormat 4 | 5 | path = new File(".").getAbsolutePath() 6 | isDebug = false 7 | 8 | def cli = new CliBuilder(usage: 'GitAnalyzer.groovy [-p PATH] [-d]') 9 | detectArguments(cli, args) 10 | 11 | stats = [:] 12 | root = new File(path) 13 | validTypes = ["blob", "tree", "commit", "tag"] 14 | 15 | // ============================ 16 | // CHECKING VALID GIT REPO 17 | // ============================ 18 | 19 | log("\n***** ANALYSIS *****\n\n") 20 | assertGitRepo(cli, path) 21 | 22 | // ============================ 23 | // UNPACKING OBJECTS 24 | // ============================ 25 | 26 | log("Unpacking any packed objects ... ") 27 | 28 | def packsDir = new File(path + "/.git/objects/pack") 29 | def newPackDir = new File(path + "/pack_" + UUID.randomUUID().toString()) 30 | packsDir.renameTo(newPackDir) 31 | runBash("git unpack-objects < ${newPackDir.name}/*.pack") 32 | newPackDir.deleteDir() 33 | 34 | log("OK") 35 | 36 | // ============================ 37 | // ANALYZE 38 | // ============================ 39 | 40 | analyze() 41 | display() 42 | 43 | // ============================ 44 | // METHODS 45 | // ============================ 46 | 47 | def analyze() { 48 | execStartTime = System.nanoTime() 49 | 50 | // CHECKING INDEX 51 | 52 | revsMap = [:] 53 | 54 | log("Checking all revisions of all objects in index ... ") 55 | 56 | "git rev-list --objects --all".execute(null, root).text.eachLine { 57 | def tokens = it.tokenize() 58 | revsMap[tokens[0]] = tokens[1] 59 | count("index count") 60 | } 61 | log("OK") 62 | 63 | // ANALYZING OBJECTS DATABASE 64 | 65 | def objectsDir = new File(path + "/.git/objects") 66 | objectsMap = [:] 67 | 68 | log("Analyzing each object in object warehouse and index ... ") 69 | 70 | objectsDir.eachDir { dir -> 71 | dir.eachFile { file -> 72 | def id = dir.name + file.name 73 | objectsMap[id] = [:] 74 | 75 | def type = "git cat-file $id -t".execute(null, root).text.replace("\n", "") 76 | objectsMap[id]["type"] = type 77 | objectsMap[id]["filePath"] = ( 78 | revsMap[id] ?: (type == "tree" ? "[ROOT]" : "") 79 | ) 80 | objectsMap[id]["id"] = id 81 | 82 | if (type == "commit") { 83 | def commitData = runBash("git show -s --format=\"%ci|%cn|%s\" $id").text.replace("\n", " ").trim() 84 | def commitToken = commitData.tokenize("|") 85 | objectsMap[id]["commitDate"] = commitToken[0] 86 | objectsMap[id]["committer"] = commitToken[1] 87 | objectsMap[id]["commitMessage"] = commitToken[2] 88 | } 89 | 90 | if (type && !revsMap.containsKey(id)) { 91 | objectsMap[id]["obsolete"] = true 92 | count("obsolete") 93 | } 94 | 95 | if (validTypes.contains(type)) { 96 | count(type) 97 | count("object count") 98 | } 99 | } 100 | } 101 | log("OK") 102 | } 103 | 104 | def display() { 105 | // DISPLAY COMMITS 106 | 107 | println "\n***** COMMITS *****\n" 108 | objectsMap.sort { it.value.commitDate }.each { String id, vals -> 109 | if (vals.type == "commit") { 110 | printf "%-3s %.6s => [%s] %20s --- %s\n", markObsoletes(vals), id.substring(0, 8), format(vals.commitDate), vals.committer, vals.commitMessage 111 | } 112 | } 113 | 114 | // DISPLAY OBJECTS 115 | 116 | println "\n***** TREES - BLOBS - TAGS *****\n" 117 | objectsMap.sort { it.value.filePath }.each { String id, vals -> 118 | if (vals.type != "commit" && !vals.id.startsWith("infopa")) { 119 | printf "%-3s %.6s => %-8s %s\n", markObsoletes(vals), id.substring(0, 8), vals.type, vals.filePath 120 | } 121 | } 122 | 123 | // DISPLAY STATS 124 | 125 | println "\n***** STATS *****" 126 | stats["execution time"] = "${((double) System.nanoTime() - execStartTime) / 1000000.0} ms" 127 | println "\n*** INDEX vs OBJECT DATABASE:" 128 | printf "%-15s : %s\n", "index count", stats["index count"] ?: 0 129 | printf "%-15s : %s\n", "object count", stats["object count"] ?: 0 130 | printf "%-15s : %s\n", "obsolete count", stats["obsolete"] ?: 0 131 | println "\n*** OBJECT TYPES:" 132 | printf "%-15s : %s\n", "commit", stats["commit"] ?: 0 133 | printf "%-15s : %s\n", "tree", stats["tree"] ?: 0 134 | printf "%-15s : %s\n", "blob", stats["blob"] ?: 0 135 | printf "%-15s : %s\n", "tag", stats["tag"] ?: 0 136 | println "\n*** TIMINGS:" 137 | printf "%-15s : %s\n", "execution time", stats["execution time"] 138 | } 139 | 140 | def runBash(cmd) { 141 | def env = System.getenv(); 142 | def envList = []; 143 | env.each() { k, v -> envList.push("$k=$v") } 144 | proc = ["bash", "-c", cmd].execute(envList, root); 145 | proc.waitFor() 146 | return proc 147 | } 148 | 149 | def markObsoletes(vals) { 150 | if (vals.obsolete) return "* " 151 | return "" 152 | } 153 | 154 | def count(name) { 155 | stats["$name"] = (stats["$name"] ?: 0) + 1 156 | } 157 | 158 | def format(val) { 159 | if (val instanceof Date) { 160 | def format = new SimpleDateFormat("yyyy-MM-dd HH:mm") 161 | return format.format(val); 162 | } 163 | return val 164 | } 165 | 166 | def detectArguments(cli, args) { 167 | cli.with { 168 | h longOpt: 'help', 'Show usage information' 169 | p longOpt: 'path', args: 1, argName: 'path', 'Analyzes internal structure of git repository in "path"' 170 | d longOpt: 'isDebug', 'Show debug information' 171 | } 172 | 173 | def options = cli.parse(args) 174 | if (!options) { 175 | return 176 | } 177 | if (options.h) { 178 | cli.usage() 179 | return 180 | } 181 | if (options.path) { 182 | path = options.path 183 | } 184 | if (options.isDebug) { 185 | isDebug = true 186 | } 187 | } 188 | 189 | def assertGitRepo(cli, pathOfRepo) { 190 | log("Checking if the given path is a valid git repository ... ") 191 | if (!new File(pathOfRepo + "/.git").exists()) { 192 | println "[ERROR] $pathOfRepo is not a valid git repository" 193 | cli.usage() 194 | System.exit(0) 195 | } 196 | log("OK") 197 | } 198 | 199 | startTime = 0 200 | 201 | def log(msg) { 202 | if (isDebug) { 203 | if (msg != "OK") { 204 | startTime = System.nanoTime() 205 | print msg 206 | } else { 207 | println "$msg [in ${((double) System.nanoTime() - startTime) / 1000000.0} ms]" 208 | startTime = 0 209 | } 210 | } 211 | } 212 | 213 | 214 | -------------------------------------------------------------------------------- /gitcatfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env groovy 2 | import java.util.zip.DataFormatException 3 | import java.util.zip.Inflater 4 | 5 | def cli = new CliBuilder(usage: 'gitcatfile [-e ]') 6 | 7 | cli.with { 8 | h longOpt: 'help', 'Show usage information' 9 | e longOpt: 'extract', args: 1, argName: 'extract', 'Extracts the object in given path' 10 | } 11 | 12 | extract = "" 13 | 14 | def options = cli.parse(args) 15 | if (!options) { 16 | return 17 | } 18 | if (options.h) { 19 | cli.usage() 20 | return 21 | } 22 | if (options.e) { 23 | extract = options.extract 24 | } else { 25 | cli.usage() 26 | return 27 | } 28 | 29 | if (extract) { 30 | def file = new File(extract) 31 | if (! file.exists()) { 32 | println "Object file does not exist in ${file.absolutePath}" 33 | return 34 | } 35 | decompress(file.bytes) 36 | } 37 | 38 | def decompress(byte[] data) throws IOException, DataFormatException { 39 | Inflater inflater = new Inflater() 40 | inflater.setInput(data) 41 | 42 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length) 43 | byte[] buffer = new byte[1024] 44 | while (!inflater.finished()) { 45 | int count = inflater.inflate(buffer) 46 | outputStream.write(buffer, 0, count) 47 | } 48 | outputStream.close() 49 | byte[] output = outputStream.toByteArray() 50 | 51 | def normalizedOutput = [] 52 | 53 | def isHex = false 54 | def hexCount = 0 55 | def isHeader = true 56 | output.each { 57 | if (it == 0 && !isHex) { 58 | normalizedOutput << "\\0 " 59 | if (!isHeader) { 60 | isHex = true 61 | } else { 62 | normalizedOutput << "\n" 63 | } 64 | isHeader = false 65 | } else if (isHex & hexCount <= 20 ) { 66 | normalizedOutput << String.format("%02x", it&0xff) 67 | 68 | hexCount++ 69 | if (hexCount == 20) { 70 | hexCount = 0 71 | isHex = false 72 | normalizedOutput << "\n" 73 | } 74 | } else { 75 | normalizedOutput << new String(it) 76 | } 77 | } 78 | normalizedOutput.each { 79 | print it 80 | } 81 | } 82 | 83 | -------------------------------------------------------------------------------- /live-findfiles: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | path=. 4 | if [ -n "$1" ]; then 5 | path=$1 6 | fi 7 | 8 | while : 9 | do 10 | clear 11 | find $path -type f -print 12 | sleep 1 13 | done 14 | -------------------------------------------------------------------------------- /live-git-index: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | usage() { 4 | echo "USAGE: live-git-index [-a]" 5 | echo " -a: [optional] show objects of all branches" 6 | exit 1 7 | } 8 | 9 | all= 10 | 11 | while getopts ":a" option; do 12 | case "${option}" in 13 | a) 14 | all=1 ;; 15 | *) 16 | usage ;; 17 | esac 18 | done 19 | shift $((OPTIND-1)) 20 | 21 | while : 22 | do 23 | clear 24 | echo "HEAD:" 25 | echo "$( git ls-files -s )" 26 | echo "" 27 | 28 | if [ -n "$all" ]; then 29 | while read branch ; do 30 | echo "${branch}:" 31 | echo "$( git ls-tree -r $branch )" 32 | echo "" 33 | done < <( git for-each-ref --sort=-committerdate refs/heads/ --format='%(refname:short)' ) 34 | fi 35 | sleep 1 36 | done 37 | -------------------------------------------------------------------------------- /live-git-log: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | usage() { 6 | echo "USAGE: live-git-log [-c | --count ]" 7 | exit 1 8 | } 9 | 10 | count=20 11 | 12 | realargs="$@" 13 | while [ $# -gt 0 ]; do 14 | case "$1" in 15 | -c | --count) 16 | count=$2 17 | break 18 | ;; 19 | *) 20 | usage 21 | ;; 22 | esac 23 | shift 24 | done 25 | set -- $realargs 26 | 27 | while : 28 | do 29 | clear 30 | git --no-pager log -${count} --graph --all --pretty=format:'%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative --date-order 31 | sleep 1 32 | done 33 | -------------------------------------------------------------------------------- /live-git-reflog: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | usage() { 6 | echo "USAGE: live-git-reflog [-c | --count ]" 7 | exit 1 8 | } 9 | 10 | count=20 11 | 12 | realargs="$@" 13 | while [ $# -gt 0 ]; do 14 | case "$1" in 15 | -c | --count) 16 | count=$2 17 | break 18 | ;; 19 | *) 20 | usage 21 | ;; 22 | esac 23 | shift 24 | done 25 | set -- $realargs 26 | 27 | while : 28 | do 29 | clear 30 | git --no-pager reflog show -${count} 31 | sleep 1 32 | done 33 | -------------------------------------------------------------------------------- /live-git-status: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | watch -c -exec bash -c "git status -uall -s" 4 | -------------------------------------------------------------------------------- /live-tree: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while : 4 | do 5 | clear 6 | tree $1 7 | sleep 1 8 | done 9 | --------------------------------------------------------------------------------