└── README.md /README.md: -------------------------------------------------------------------------------- 1 | 2 | # git Cheat Sheet 3 | 4 | by [Ben Nadel](https://www.bennadel.com) for _future Ben Nadel_ 5 | 6 | The [`git` API is so vast](https://git-scm.com/docs) and so esoteric, I can barely manage to keep a fraction of one-percent of it in my head. As such, I wanted to outline a few of the commands that I commonly (and sometimes uncommonly) reach for. This way, when I inevitably get stuck and my brain fails me, I have something that I can refer back to. 7 | 8 | Future Ben, you are welcome! 9 | 10 | ## Table of Contents 11 | 12 | * [I want to show the status of the current branch.](#i-want-to-show-the-status-of-the-current-branch) 13 | * [I want to create a new branch that is based on the current branch.](#i-want-to-create-a-new-branch-that-is-based-on-the-current-branch) 14 | * [I want to checkout the previous branch that I was on.](#i-want-to-checkout-the-previous-branch-that-i-was-on) 15 | * [I want to list the files that have been modified in the current working tree.](#i-want-to-list-the-files-that-have-been-modified-in-the-current-working-tree) 16 | * [I want to view the changes that were made in a given commit.](#i-want-to-view-the-changes-that-were-made-in-a-given-commit) 17 | * [I want to list the files that were changed in a given commit.](#i-want-to-list-the-files-that-were-changed-in-a-given-commit) 18 | * [I want to view the changes that were made across multiple commits.](#i-want-to-view-the-changes-that-were-made-across-multiple-commits) 19 | * [I want to view the changes that were made in a given file.](#i-want-to-view-the-changes-that-were-made-in-a-given-file) 20 | * [I want to view the contents of a file in a given commit.](#i-want-to-view-the-contents-of-a-file-in-a-given-commit) 21 | * [I want to open the contents of a file in a given commit in my editor.](#i-want-to-open-the-contents-of-a-file-in-a-given-commit-in-my-editor) 22 | * [I want to copy a file from a given commit into my current working tree.](#i-want-to-copy-a-file-from-a-given-commit-into-my-current-working-tree) 23 | * [I want to copy the last commit from another branch into my branch.](#i-want-to-copy-the-last-commit-from-another-branch-into-my-branch) 24 | * [I want to copy an earlier commit from the current branch to the `head`.](#i-want-to-copy-an-earlier-commit-from-the-current-branch-to-the-head) 25 | * [I want to update the files in the current commit.](#i-want-to-update-the-files-in-the-current-commit) 26 | * [I want to edit the current commit message.](#i-want-to-edit-the-current-commit-message) 27 | * [I want to copy `master` into my feature branch.](#i-want-to-copy-master-into-my-feature-branch) 28 | * [I want to revert the merge of my feature branch into `master`.](#i-want-to-revert-the-merge-of-my-feature-branch-into-master) 29 | * [I want to extract changes that I accidentally made to `master`.](#i-want-to-extract-changes-that-i-accidentally-made-to-master) 30 | * [I want to undo the changes that I've made to my branch.](#i-want-to-undo-the-changes-that-ive-made-to-my-branch) 31 | * [I want to remove unpublished changes from my branch.](#i-want-to-remove-unpublished-changes-from-my-branch) 32 | * [I want to see which branches have already been merged into `master`.](#i-want-to-see-which-branches-have-already-been-merged-into-master) 33 | * [I want to see which branches have not yet been merged into `master`.](#i-want-to-see-which-branches-have-not-yet-been-merged-into-master) 34 | * [I want to delete my feature branch.](#i-want-to-delete-my-feature-branch) 35 | * [I want to delete a remote branch.](#i-want-to-delete-a-remote-branch) 36 | * [I want to update `master` because my `push` was rejected.](#i-want-to-update-master-because-my-push-was-rejected) 37 | * [I want to remove a file from my staging area.](#i-want-to-remove-a-file-from-my-staging-area) 38 | * [I want to squash several commits into one (or more) commits.](#i-want-to-squash-several-commits-into-one-or-more-commits) 39 | * [I want to squash several commits into one commit without using `rebase`.](#i-want-to-squash-several-commits-into-one-commit-without-using-rebase) 40 | * [I want to temporarily set-aside my feature work.](#i-want-to-temporarily-set-aside-my-feature-work) 41 | * [I want to keep my changes during conflict resolution.](#i-want-to-keep-my-changes-during-conflict-resolution) 42 | * [I want to find the commit that deleted a file.](#i-want-to-find-the-commit-that-deleted-a-file) 43 | * [I want to find the commit that deleted a file that contained a piece of code.](#i-want-to-find-the-commit-that-deleted-a-file-that-contained-a-piece-of-code) 44 | 45 | ## Use Cases 46 | 47 | ### I want to show the status of the current branch. 48 | 49 | The `status` command shows differences between the working tree, the index, and `head` commit. 50 | 51 | ```sh 52 | git status 53 | ``` 54 | 55 | ### I want to create a new branch that is based on the current branch. 56 | 57 | In general, you want to implement new features in short-lived "feature branches" so that changes can be isolated. You can use the `checkout` command to create a new branch based on the current branch: 58 | 59 | ```sh 60 | git checkout master 61 | 62 | # Creates a new branch, `my-feature`, based on `master`. 63 | git checkout -b my-feature 64 | ``` 65 | 66 | ### I want to checkout the previous branch that I was on. 67 | 68 | In some of the `git` commands, the `-` token refers to the "last branch" that you had checked-out. This makes it very easy to jump back-and-forth between two branches: 69 | 70 | ```sh 71 | git checkout master 72 | git checkout my-feature 73 | 74 | # At this point, the `-` refers to the `master` branch. 75 | git checkout - 76 | 77 | # At this point, the `-` refers to the `my-feature` branch. 78 | git checkout - 79 | ``` 80 | 81 | The `-` token can also be used to merge-in the last branch that you had checked-out: 82 | 83 | ```sh 84 | git checkout my-feature 85 | # ... changes to the working tree (your file system). 86 | git add . 87 | git commit -m "Awesome updates." 88 | git checkout master 89 | 90 | # At this point, the `-` refers to the `my-feature` branch. 91 | git merge - 92 | ``` 93 | 94 | The `-` token can also be used to `cherry-pick` the most recent commit of the last branch that you had checked-out: 95 | 96 | ```sh 97 | git checkout my-feature 98 | # ... changes to the working tree (your file system). 99 | git add . 100 | git commit -m "Minor tweaks." 101 | 102 | git checkout master 103 | 104 | # At this point, the `-` refers to the `my-feature` branch. 105 | git cherry-pick - 106 | ``` 107 | 108 | ### I want to list the files that have been modified in the current working tree. 109 | 110 | By default, when you call `git diff`, you see all of the content that has been modified in the current working tree (and not yet staged). However, you can use the `--stat` modifier to simply list the files that have been modified: 111 | 112 | ```sh 113 | git diff --stat 114 | ``` 115 | 116 | ### I want to view the changes that were made in a given commit. 117 | 118 | When `show` is given a branch name, it will default to `head` - the last or most-recent commit on the given branch: 119 | 120 | ```sh 121 | git checkout master 122 | 123 | # Outputs the changes made to the `head` commit of the current (`master`) 124 | # branch. 125 | git show 126 | 127 | # Outputs the changes made to the `head` commit of the `my-feature` branch. 128 | git show my-feature 129 | ``` 130 | 131 | You can also use the `show` command to target a specific commit that is not the `head` commit. This can be done with a specific commit hash; or, a relative commit operator like `~`: 132 | 133 | ```sh 134 | # Outputs the changes made in the commit with the given hash. 135 | git show 19e771 136 | 137 | # Outputs the changes made in in a previous commit of the current (`master`) 138 | # branch. 139 | git show head~ # Show changes in first parent. 140 | git show head~~ # Show changes in first parent's first parent. 141 | git show head~~~ # Show changes in first parent's first parent's first parent. 142 | 143 | # Outputs the changes made in a previous commit of the `my-feature` branch. 144 | git show my-feature~ 145 | git show my-feature~~ 146 | git show my-feature~~~ 147 | ``` 148 | 149 | ### I want to list the files that were changed in a given commit. 150 | 151 | Just as with `git diff`, you can limit the output of the `git show` command using the `--stat` modifier. This will list the files that were changed in the given commit: 152 | 153 | ```sh 154 | # Outputs the list of files changed in the commit with the given hash. 155 | git show 19e771 --stat 156 | ``` 157 | 158 | ### I want to view the changes that were made across multiple commits. 159 | 160 | While the `show` command can show you changes in a given commit, you can use the `diff` command to show changes across multiple commits: 161 | 162 | ```sh 163 | git checkout master 164 | 165 | # Outputs the changes between `head~` and `head` of the current branch. If 166 | # only one commit is provided, other commit is assumed to be `head`. 167 | git diff head~ 168 | 169 | # Outputs the changes between the first commit and the second commit. 170 | git diff head~~~..head~~ 171 | ``` 172 | 173 | And, since branch names are really just aliases for commits, you can use a branch name in order to show the changes between one branch and your branch: 174 | 175 | ```sh 176 | git checkout my-feature 177 | 178 | # At this point, the following are equivalent and output the changes between 179 | # the `head` commit of the `master` branch and the `head` commit of the 180 | # `my-feature` branch. 181 | git diff master 182 | git diff master..head 183 | git diff master..my-feature 184 | ``` 185 | 186 | ### I want to view the changes that were made in a given file. 187 | 188 | By default, the `show` command shows all of the changes in a given commit. You can limit the scope of the output by using the `--` modifier and identifying a filepath: 189 | 190 | ```sh 191 | # Outputs the changes made to the `README.md` file in the `head` commit of the 192 | # `my-feature` branch. 193 | git show my-feature -- README.md 194 | 195 | # Outputs the changes made to the `README.md` file in the `19e771` commit. 196 | git show 19e771 -- README.md 197 | ``` 198 | 199 | ### I want to view the contents of a file in a given commit. 200 | 201 | By default, the `show` command shows you the changes made to a file in a given commit. However, if you want to view the entire contents of a file as defined at that time of a given commit, regardless of the changes made in that particular commit, you can use the `:` modifier to identify a filepath: 202 | 203 | ```sh 204 | # Outputs the contents of the `README.md` file as defined in the `head` commit 205 | # of the `my-feature` branch. 206 | git show my-feature:README.md 207 | 208 | # Outputs the contents of the `README.md` file as defined in the `19e771` 209 | # commit. 210 | git show 19e771:README.md 211 | ``` 212 | 213 | ### I want to open the contents of a file in a given commit in my editor. 214 | 215 | Since you're working on the command-line, the output of any git-command can be piped into another command. As such, you can use the `show` command to open a previous commit's file-content in your editor or viewer of choice: 216 | 217 | ```sh 218 | # Opens the `README.md` file from the `head` commit of the `my-feature` branch 219 | # in the Sublime Text editor. 220 | git show my-feature:README.md | subl 221 | 222 | # Opens the `README.md` file from the `19e771` commit in the `less` viewer. 223 | git show 19e771:README.md | less 224 | ``` 225 | 226 | ### I want to copy a file from a given commit into my current working tree. 227 | 228 | Normally, the `checkout` command will update the entire working tree to point to a given commit. However, you can use the `--` modifier to copy (or checkout) a single file from the given commit into your working tree: 229 | 230 | ```sh 231 | git checkout my-feature 232 | 233 | # While staying on the `my-feature` branch, copy the `README.md` file from 234 | # the `master` branch into the current working tree. This will overwrite the 235 | # current version of `README.md` in your working tree. 236 | git checkout master -- README.md 237 | ``` 238 | 239 | ### I want to copy the last commit from another branch into my branch. 240 | 241 | When you don't want to merge a branch into your current working tree, you can use the `cherry-pick` command to copy specific commit-changes into your working tree. Doing so creates a new commit on top of the current branch: 242 | 243 | ```sh 244 | git checkout master 245 | 246 | # Copy the `head` commit-changes of the `my-feature` branch onto the `master` 247 | # branch. This will create a new `head` commit on `master`. 248 | git cherry-pick my-feature 249 | ``` 250 | 251 | ### I want to copy an earlier commit from the current branch to the `head`. 252 | 253 | Sometimes, after you understand why reverted code was breaking, you want to bring the reverted code back into play and then fix it. You _could_ use the `revert` command in order to "revert the revert"; but, such terminology is unattractive. As such, you can `cherry-pick` the reverted commit to bring it back into the `head` where you can then fix it and commit it: 254 | 255 | ```sh 256 | git checkout master 257 | 258 | # Assuming that `head~~~` and `19e771` are the same commit, the following are 259 | # equivalent and will copy the changes in `19e771` to the `head` of the 260 | # current branch (as a new commit). 261 | git cherry-pick head~~~ 262 | git cherry-pick 19e771 263 | ``` 264 | 265 | ### I want to update the files in the current commit. 266 | 267 | If you want to make changes to a commit after you've already committed the changes in your current working tree, you can use the `--amend` modifier. This will add any staged changes to the existing commit. 268 | 269 | ```sh 270 | git commit -m "Woot, finally finished!" 271 | 272 | # Oops, you forgot a change. Edit the file and stage it. 273 | # ... changes to the working tree (your file system). 274 | git add oops.txt 275 | 276 | # Adds the currently-staged changes (oops.txt) to the current commit, giving 277 | # you a chance to update the commit message. 278 | git commit --amend 279 | ``` 280 | 281 | ### I want to edit the current commit message. 282 | 283 | In addition to adding files to the current commit, the `--amend` modifier can also be used to change the current commit message: 284 | 285 | ```sh 286 | git add . 287 | git commit -m "This is greet." 288 | 289 | # Oh noes! You misspelled "great". You can edit the current commit message: 290 | git commit --amend -m "This is great." 291 | ``` 292 | 293 | Note that if you omit the `-m message` portion of this command, you will be able to edit the commit message in your configured editor. 294 | 295 | ### I want to copy `master` into my feature branch. 296 | 297 | At first, you may be tempted to simply `merge` your `master` branch into your feature branch, but doing so will create an unattactive, non-intuitive, Frankensteinian commit tree. Instead, you should `rebase` your feature branch on `master`. This will ensure that your feature commits are cleanly colocated in the commit tree and align more closely with a human mental model: 298 | 299 | ```sh 300 | git checkout my-feature 301 | 302 | # This will unwind the commits specific to the `my-feature` branch, pull in 303 | # the missing `master` commits, and then replay your `my-feature` commits. 304 | git rebase master 305 | ``` 306 | 307 | Once your `my-feature` branch has been rebased on `master`, you could then, if you wanted to, perform a `--ff-only` merge ("fast forward only") of your feature branch back into `master`: 308 | 309 | ```sh 310 | git checkout my-feature 311 | git rebase master 312 | 313 | # Fast-forward merge of `my-feature` changes into `master`, which means there 314 | # is no creation of a "merge commit" - your `my-features` changes are simply 315 | # added to the top of `master`. 316 | git checkout master 317 | git merge --ff-only my-feature 318 | ``` 319 | 320 | That said, when you're working on a team where everyone uses a different git workflow, you will definitely _want_ a "merge commit". This way, multi-commit merges can be easily reverted. To force a "merge commit", you can use the `--no-ff` modifier ("no fast forward"): 321 | 322 | ```sh 323 | # Get the `my-feature` branch ready for merge. 324 | git checkout my-feature 325 | git rebase master 326 | 327 | # Merge the `my-feature` branch into `master` creating a merge-commit. 328 | git checkout master 329 | git merge --no-ff my-feature 330 | ``` 331 | 332 | Now, if the merge needs to be reverted, you can simply revert the "merge commit" and all commits associated with the merge will be reverted. 333 | 334 | ### I want to revert the merge of my feature branch into `master`. 335 | 336 | If you performed a `--ff-only` merge of your feature branch into `master`, there's no "easy" solution. You either have to reset the branch to an earlier commit (rewriting history); or, you have to revert the individual commits in the merge. 337 | 338 | If, however, you performed a `--no-ff` merge ("no fast forward") that created a "merge commit", all you have to do is revert the merge commit: 339 | 340 | ```sh 341 | git checkout master 342 | 343 | # Merge the feature branch in, creating a "merge commit". 344 | git merge --no-ff my-feature 345 | 346 | # On noes! You didn't mean to merge that in. Assuming that the "merge commit" 347 | # is now the `head` of `master`, you can revert back to the commit's fist 348 | # parent, the `master` branch: -m 1. And, since `head` and `master` are the 349 | # same commit, the following are equivalent: 350 | git revert -m 1 head 351 | git revert -m 1 master 352 | ``` 353 | 354 | ### I want to extract changes that I accidentally made to `master`. 355 | 356 | Sometimes, after you've finished working on your feature branch, you execute `git checkout master`, only find that you've been accidentally working on `master` the whole time (error: "Already on 'master'"). To fix this, you can `checkout` a new branch and `reset` your `master` branch: 357 | 358 | ```sh 359 | git checkout master 360 | # > error: Already on 'master' 361 | 362 | # While on the `master` branch, create the `my-feature` branch as a copy of 363 | # the `master` branch. This way, your `my-feature` branch will contain all of # your recent changes. 364 | git checkout -b my-feature 365 | 366 | # Now that your changes are safely isolated, get back into your `master` 367 | # branch and `reset` it with the `--hard` modifier so that your local index 368 | # and file system will match the remote copy. 369 | git checkout master 370 | git reset --hard origin/master 371 | ``` 372 | 373 | ### I want to undo the changes that I've made to my branch. 374 | 375 | If you've edited some files and then change your mind about keeping those edits, you can reset the branch using the `--hard` modifier. This will update the working tree - your file structure - to match the structure of the last commit on the branch (`head`). 376 | 377 | **Caution**: You will lose data when using the `--hard` option. 378 | 379 | ```sh 380 | git checkout my-feature 381 | # ... changes to the working tree (your file system). 382 | git add . 383 | 384 | # Remove the file from staging AND remove the changes from the file system. 385 | git reset --hard 386 | ``` 387 | 388 | If you call `git reset` without the `--hard` option, it will reset the staging to match the `head` of the branch, but it will leave your file system in place. As such, you will be left with "unstaged changes" that can be modified and re-committed. 389 | 390 | ### I want to remove unpublished changes from my branch. 391 | 392 | If you've committed changes to the local copy of a remote (ie, published) branch, but you want to undo those changes, you can `reset` the local branch to match the remote branch: 393 | 394 | ```sh 395 | git checkout my-feature 396 | 397 | # Update the remote copy of the `my-feature` branch in order to make sure that 398 | # you are working with the most up-to-date remote content. 399 | git fetch origin my-feature 400 | 401 | # Now, reset the local copy of `my-feature` to match the published copy. This 402 | # will update your index and your local file system to match the published 403 | # version of `my-feature`. 404 | git reset --hard origin/my-feature 405 | ``` 406 | 407 | ### I want to see which branches have already been merged into `master`. 408 | 409 | From any branch, you can locate the merged-in branches (that can be safely deleted) by using the `--merged` modifier: 410 | 411 | ```sh 412 | git checkout master 413 | 414 | # List all of the local branches that have been merged into `master`. This 415 | # command will be relative to the branch that you have checked-out. 416 | git branch --merged 417 | ``` 418 | 419 | ### I want to see which branches have not yet been merged into `master`. 420 | 421 | From any branch, you can locate the unmerged branches by using the `--no-merged` modifier: 422 | 423 | ```sh 424 | git checkout master 425 | 426 | # List all of the local branches that have NOT YET been merged into `master`. 427 | # This command will be relative the branch you have checked-out. 428 | git branch --no-merged 429 | ``` 430 | 431 | ### I want to delete my feature branch. 432 | 433 | After you're merged your feature branch into `master`, you can delete your feature branch using the `branch` command: 434 | 435 | ```sh 436 | # Merge your `my-feature` branch into `master` creating a "merge commit." 437 | git checkout master 438 | git merge --no-ff my-feature 439 | 440 | # Safely delete the merged-in `my-feature` branch. The `-d` modifier will 441 | # error-out if the given branch has not yet been merged into the current 442 | # branch. 443 | git branch -d my-feature 444 | ``` 445 | 446 | If you want to abandon a feature branch, you can use the `-D` modifier to force-delete it even if it has not yet been merged into `master`: 447 | 448 | ```sh 449 | git checkout master 450 | 451 | # Force-delete the `my-feature` branch even though it has not been merged 452 | # into the `master` branch. 453 | git branch -D my-feature 454 | ``` 455 | 456 | ### I want to delete a remote branch. 457 | 458 | When you delete a branch using `git branch -d`, it deletes your local copy; but, it doesn't delete the remote copy from your origin (ex, GitHub). To delete the remote copy, you have to `push` the branch using the `:` prefix: 459 | 460 | ```sh 461 | git checkout master 462 | 463 | # Safely delete the local copy of your `my-feature` branch. The `-d` modifier 464 | # will error-out if the given branch has not been fully-merged into `master`. 465 | git branch -d my-feature 466 | 467 | # Delete the remote copy of the `my-feature` branch from the origin. The `:` 468 | # prefix sends this through as a "delete" command for the given branch. 469 | git push origin :my-feature 470 | ``` 471 | 472 | ### I want to update `master` because my `push` was rejected. 473 | 474 | If you've committed changes to `master` but you forgot to `pull` recent changes from the remote `master` branch, your next `push` will be rejected with an error that looks like, _"Updates were rejected because the tip of your current branch is behind its remote counterpart"_. To fix this, you can use the `--rebase` modifier: 475 | 476 | ```sh 477 | git checkout master 478 | git merge --no-ff my-feature 479 | 480 | # Oh noes! You forgot to pull in the latest remote copy of `master` before you 481 | # merged your `my-feature` commits. No problem, just `--rebase` your local 482 | # `master` on the remote branch. This will move your local changes to the tip 483 | # of the `master` branch. 484 | git pull --rebase 485 | 486 | # Now that you've pulled-in the remote changes, you should be able to push 487 | # your updated `master` branch. 488 | git push origin master 489 | ``` 490 | 491 | ### I want to remove a file from my staging area. 492 | 493 | If you accidentally added too many files to the staging area (in preparation for a `git commit`), you can use the `rm --cached` command to remove them from the staging area but keep them in the working tree: 494 | 495 | ```sh 496 | git add . 497 | 498 | # Oh noes! You didn't mean to add all of the files to the staging area. You 499 | # can remove some of the staged files using the `--cached` modifier: 500 | git rm --cached secrets.config 501 | ``` 502 | 503 | If you accidentally added an entire directory to the staging area, you can add the `-r` modifier to recursively apply the `rm` command: 504 | 505 | ```sh 506 | git add . 507 | 508 | # Oh noes! You didn't mean to add the ./config directory. You can recursively 509 | # remove it with the `-r` modifier: 510 | git rm --cached -r config/. 511 | ``` 512 | 513 | When you `rm` files using `--cached`, they will remain in your working tree and will become "unstaged" changes. 514 | 515 | ### I want to squash several commits into one (or more) commits. 516 | 517 | Your commit history is a representation or your personality. It is a manifestation of your self-respect and the respect you have for your team. As such, you will often need to rewrite your feature branch's history before merging it into `master`. This allows you to get rid of intermediary commit messages like, _"still working on it."_ and _"Meh, missed a bug."_. To do this, you can perform an "interactive rebase". 518 | 519 | The "interactive rebase" gives you an opportunity to indicate how intermediary commits should be rearranged. Some commits can be "squashed" (combined) together. Others can omitted (remove). And others can be edited. When performing an interactive rebase, you have to tell `git` which commit to use as the starting point. If you're on an up-to-date feature branch, the starting point _should be_ `master`. 520 | 521 | ```sh 522 | # Create the `my-feature` branch based on `master`. 523 | git checkout master 524 | git checkout -b my-feature 525 | 526 | # ... changes to the working tree (your file system). 527 | git add . 528 | git commit -m "Getting close." 529 | 530 | # ... changes to the working tree (your file system). 531 | git add . 532 | git commit -m "Missed a bug." 533 | 534 | # ... changes to the working tree (your file system). 535 | git add . 536 | git commit -m "Uggggg! Why is this so hard?" 537 | 538 | # ... changes to the working tree (your file system). 539 | git add . 540 | git commit -m "Woot, finally got this working." 541 | 542 | # At this point, your commit history is sloppy and would bring much shame on 543 | # your family if it ended-up in `master`. As such, you need to squash the 544 | # commits down into a single commit using an interactive rebase. Here, you're 545 | # telling `git` to use the `master` commit as the starting point: 546 | git rebase -i master 547 | ``` 548 | 549 | As this point, `git` will open up an editor that outlines the various commits and asks you how you want to rearrange them. It should look something like this, with the commits listed in ascending order (oldest first): 550 | 551 | ```sh 552 | pick 27fb3d2 Getting close. 553 | pick e8214df Missed a bug. 554 | pick ce5ed14 Uggggg! Why is this so hard? 555 | pick f7ee6ab Woot, finally got this working. 556 | 557 | # Rebase b0fced..f7ee6ab onto b0fced (4 commands) 558 | # 559 | # Commands: 560 | # p, pick = use commit 561 | # r, reword = use commit, but edit the commit message 562 | # e, edit = use commit, but stop for amending 563 | # s, squash = use commit, but meld into previous commit 564 | # f, fixup = like "squash", but discard this commit's log message 565 | # x, exec = run command (the rest of the line) using shell 566 | # d, drop = remove commit 567 | ``` 568 | 569 | At this point, you can identify the later commits as needing to be squashed (`s`) down into the oldest commit (the one you are `pick`ing): 570 | 571 | ```sh 572 | pick 27fb3d2 Getting close. 573 | s e8214df Missed a bug. 574 | s ce5ed14 Uggggg! Why is this so hard? 575 | s f7ee6ab Woot, finally got this working. 576 | ``` 577 | 578 | Once saved, `git` will prompt you to provide a cleaner commit message. And, once provided, your four shameful commits will be squashed down into a single, cohesive, meaningful commit. 579 | 580 | ### I want to squash several commits into one commit without using `rebase`. 581 | 582 | In the vast majority of cases, if your `git` workflow is clean and true and your feature branch is short-lived, an interactive rebase should be straightforward and pain-free. However, once you _make the mistake_ of periodically merging `master` into your feature branch, you are inviting a world of hurt. In such a case, you can use the `merge` command with the `--squash` modifier as an escape hatch. 583 | 584 | When you run `git merge --squash`, you copy the file-changes from one branch into another branch without actually copying the commit meta-data. Instead, the changes are brought-over as _staged changes_ on top of the current branch. At that point, you can commit all the staged changes as a single commit. 585 | 586 | Assuming your `my-feature` branch needs to be "fixed", you can use the following workflow: 587 | 588 | ```sh 589 | # Assuming that the `my-feature` branch is the branch that needs to be fixed, 590 | # start off my renaming the branch as a backup (the `-m` modifier performs a 591 | # rename): 592 | git checkout my-feature 593 | git branch -m my-feature-backup 594 | 595 | # Now, checkout the `master` branch and use it to create a new, clean 596 | # `my-feature` branch starting point. 597 | git checkout master 598 | git checkout -b my-feature 599 | 600 | # At this point, your `my-feature` branch and your `master` branch should be 601 | # identical. Now, you can "squash merge" your old feature branch into the new 602 | # and clean `my-feature` branch: 603 | git merge --squash my-feature-backup 604 | 605 | # All the file-changes should now be in the `my-feature` branch as staged 606 | # edits ready to be committed. 607 | git commit -m "This feature is done and awesome." 608 | 609 | # Delete the old backup branch as it is no longer needed. You will have to 610 | # force delete (`-D`) it since it was never merged into `master`. 611 | git branch -D my-feature-backup 612 | ``` 613 | 614 | > **ASIDE**: You should almost never need to do this. If you find yourself having to do this a lot; or, you find yourself dealing with a lot of "conflict resolution", you need to reevaluate your `git` workflow. Chances are, your feature branches are living way too long. 615 | 616 | ### I want to temporarily set-aside my feature work. 617 | 618 | The life of a developer is typically "interrupt driven". As such, there is often a need to briefly set aside your current work in order to attend to more pressing matters. In such a case, it is _tempting_ to use `git stash` and `git stash pop` to store pending changes. But, _do not do this_. Stashing code requires unnecessary mental overhead. Instead, simply commit the changes to your current feature branch and then perform an interactive rebase later on in order to clean up your commits: 619 | 620 | ```sh 621 | # Oh noes! Something urgent just came up - commit your changes to your feature 622 | # branch and then go attend to the more pressing work. 623 | git add . 624 | git commit -m "Saving current work - jumping to more urgent matters." 625 | 626 | git checkout master 627 | ``` 628 | 629 | Now, you never have to remember where those pending changes are. This guarantees that you won't lose your work. 630 | 631 | If you were working directly on `master` when urgent matters came up, you can still avoid having to use `git stash`. To keep your work, simply checkout a new branch and the commit your pending changes to that new branch: 632 | 633 | ```sh 634 | # Oh noes! Something urgent just came up - checkout a new branch. This will 635 | # move all of your current work (staged and unstaged) over to the new branch. 636 | git checkout -b temp-work 637 | 638 | # Commit any unstaged changes. 639 | git add . 640 | git commit -m "Saving current work - jumping to more urgent matters." 641 | 642 | git checkout master 643 | ``` 644 | 645 | Now, your `master` branch should be back in a pristine state and your `temp-work` branch can be continued later. 646 | 647 | ### I want to keep my changes during conflict resolution. 648 | 649 | If your Git workflow is healthy - meaning that you have short-lived feature branches - conflicts should be very few and far between. In fact, I would assert that getting caught-up in frequent conflicts is an indication that something more fundamental to your workflow is primed for optimization. 650 | 651 | That said, conflicts do happen. And, if you want to resolve a conflict by selecting "your version" of a file, you can use `git checkout --theirs` in a `merge` conflict, a `cherry-pick` conflict, and a `rebase` conflict. 652 | 653 | In a `merge` conflict, `--theirs` indicates the branch being merged into the current context: 654 | 655 | ```sh 656 | git checkout master 657 | git merge --no-ff my-feature 658 | 659 | # Oh noes! There is a conflict in "code.js". To keep your version of the 660 | # code.js file, you can check-it-out using --theirs and the file path: 661 | git checkout --theirs code.js 662 | git add . 663 | git merge --continue 664 | ``` 665 | 666 | Similarly, in a `cherry-pick` conflict, `--theirs` indicates the branch being cherry-picked into the current context: 667 | 668 | ```sh 669 | git checkout master 670 | git cherry-pick --no-ff my-feature 671 | 672 | # Oh noes! There is a conflict in "code.js". To keep your version of the 673 | # code.js file, you can check-it-out using --theirs and the file path: 674 | git checkout --theirs code.js 675 | git add . 676 | git merge --continue 677 | ``` 678 | 679 | In a `rebase` conflict, `--theirs` indicates the branch that is being _replayed_ on top of the current context (**See Aside**): 680 | 681 | ```sh 682 | git checkout my-feature 683 | git rebase master 684 | 685 | # Oh noes! There is a conflict in "code.js". To keep your version of the 686 | # code.js file, you can check-it-out using --theirs and the file path: 687 | git checkout --theirs code.js 688 | git add . 689 | git rebase --continue 690 | ``` 691 | 692 | > **ASIDE**: Using `--theirs` in a `rebase` can seem confusing because you are already in "your" feature branch. As such, it would seem logical that your version of the code would be targeted with `--ours`, not `--theirs`. However, a `rebase` operates _kind of like_ a series of `cherry-pick` operations. You can think of a `rebase` as doing the following: 693 | > 694 | > * Check-out an earlier, common commit between your feature branch and the target branch. 695 | > * Cherry-pick your feature branch commits onto the earlier commit. 696 | > * Replace your feature branch with this temporary branch 697 | > 698 | > With this mental model, "your" version - targeted using `--theirs` - is the version being cherry-picked into the "current context" (the temporary branch). 699 | 700 | ### I want to find the commit that deleted a file. 701 | 702 | To find a deleted file, you can use the `git log` command and filter the results based on file status and path patterns. In this case, you want to use `--diff-filter=D` which limits the results to deleted files. And, since the files in question have been deleted, the path pattern must come after the final `--` delimiter: 703 | 704 | ```sh 705 | # Find the commit that deleted the file "projects.js". 706 | git log --diff-filter=D --name-only -- wwwroot/app/projects.js 707 | ``` 708 | 709 | The `--name-only` option includes statistics about the commit (which files were changed); but, limits the file meta-data to include file paths only. 710 | 711 | Of course, since you are looking for a _delete file_, you may not remember the exact file path of the deleted file. In that case, you can use pattern matching on the file path: 712 | 713 | ```sh 714 | # Find the commit that deleted the file "projects.js" that resided somewhere 715 | # in the "app" directory. The double-asterisk matches across directories. 716 | git log --diff-filter=D --name-only -- "wwwroot/app**/projects.js" 717 | 718 | # You can even use pattern matching on the file name itself if you can't 719 | # remember exactly what it was named. 720 | git log --diff-filter=D --name-only -- "wwwroot/**/*project*.js" 721 | ``` 722 | 723 | The default output of the `git log` command can be a bit verbose since it outputs the commit message along with the deleted files. To minify the output, you can use the `--oneline` and `--pretty` options: 724 | 725 | ```sh 726 | # Find the commit that deleted the file "projects.js", but only show a one-line 727 | # commit message and the list of files. 728 | git log --diff-filter=D --name-only --oneline -- "wwwroot/app**/projects.js" 729 | 730 | # To get slightly easier-to-read output, you can use the `--pretty` option with 731 | # some specialized formatting (instead of the `--oneline` option). This 732 | # includes the abbreviated hash, the author, the relative date, and the 733 | # subject line. And, includes some human-friendly line-breaks. 734 | git log --diff-filter=D --name-only --pretty=format:"%Cgreen%h - %an, %ar : %s" -- "wwwroot/app**/projects.js" 735 | ``` 736 | 737 | ### I want to find the commit that deleted a file that contained a piece of code. 738 | 739 | To find a deleted file that contained a given piece of code, you can use the `git log` command and filter based on pattern matching within the diff. In this case, you want to use `--diff-filter=D` which limits the results to deleted files. 740 | 741 | You can use the `-S` option to match on a string literal: 742 | 743 | ```sh 744 | # Find the commit that deleted a file that contained the code 'isProcessed'. 745 | git log -S 'isProcessed' --diff-filter=D --name-only 746 | 747 | # To limit the search to the "app" directory, you can include a file path after 748 | # the final `--` delimiter. 749 | git log -S 'isProcessed' --diff-filter=D --name-only -- wwwroot/app/ 750 | 751 | # To make the search case-insensitive, include the -i option. 752 | git log -S 'isprocessed' -i --diff-filter=D --name-only 753 | ``` 754 | 755 | You can use the `-G` option to match on a Regular Expression pattern instead of a string literal: 756 | 757 | ```sh 758 | # Find the commit that deleted a file that contained the code `isProcessed`, 759 | # matching on strict word-boundaries. 760 | git log -G '\bisProcessed\b' --diff-filter=D --name-only 761 | 762 | # To make the search case-insensitive, include the `-i` option. 763 | git log -G '\bisprocessed\b' -i --diff-filter=D --name-only 764 | ``` 765 | 766 | The default output of the `git log` command can be a bit verbose since it outputs the commit message along with the deleted files. To minify the output, you can use the `--oneline` and `--pretty` options: 767 | 768 | ```sh 769 | # Find the commit that deleted a file that contained the code `isProcessed`, 770 | # matching on strict word-boundaries. 771 | git log -G '\bisProcessed\b' --diff-filter=D --name-only --oneline 772 | 773 | # To get slightly easier-to-read output, you can use the `--pretty` option with 774 | # some specialized formatting (instead of the `--oneline` option). This 775 | # includes the abbreviated hash, the author, the relative date, and the 776 | # subject line. And, includes some human-friendly line-breaks. 777 | git log -G '\bisProcessed\b' --diff-filter=D --name-only --pretty=format:"%Cgreen%h - %an, %ar : %s" 778 | ``` 779 | 780 | Once you locate the commit that appears to contain your code, you can use the `git show` command to view the contents of the commit delta: 781 | 782 | ```sh 783 | # Find the commit that deleted a file that contained the code `isProcessed`, 784 | # matching on strict word-boundaries. 785 | git log -G '\bisProcessed\b' --diff-filter=D --name-only --oneline 786 | 787 | # The above `log` action listed commit `aef037`. Use `git show` to output the 788 | # changes made in the given commit. 789 | git show aef037 790 | ``` 791 | --------------------------------------------------------------------------------