diff --git a/README.md b/README.md index 4dfd818..0485409 100644 --- a/README.md +++ b/README.md @@ -1,144 +1,449 @@ +# GIT Cheat Sheet +# For Future Me! +The git API is so vast 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. -# git Cheat Sheet +This way, when I inevitably get stuck and my brain fails me, I have something that I can refer back to. -by [Ben Nadel](https://www.bennadel.com) for _future Ben Nadel_ +**Future ME, you are welcome!** -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. +## Prefer rebase over merge +There are those argue that using merge maintains ALL your history - which they claim is “always” better. Who cares if it took you 17 attempts to get a file correct before you pushed it? I have never needed to ask - what were all the intermediate versions of a file, between pushes. -Future Ben, you are welcome! +Only on one occasion have I thought it could have helped me to see every single commit that was made to a file that I was struggling to get correct, where I made several commits to a file and I needed to bring back the nth version, where I had it working correctly at one point in time. -## Table of Contents +But that means two things MUST happen: +1. You have to make a commit for each tweak you make, along the way, and, +2. You actually have to remember that you had a previous commit of the file that was correct. -* [I want to show the status of the current branch.](#i-want-to-show-the-status-of-the-current-branch) -* [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) -* [I want to checkout the previous branch that I was on.](#i-want-to-checkout-the-previous-branch-that-i-was-on) -* [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) -* [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) -* [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) -* [I want to view the changes that were made across multiple commits.](#i-want-to-view-the-changes-that-were-made-across-multiple-commits) -* [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) -* [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) -* [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) -* [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) -* [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) -* [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) -* [I want to update the files in the current commit.](#i-want-to-update-the-files-in-the-current-commit) -* [I want to edit the current commit message.](#i-want-to-edit-the-current-commit-message) -* [I want to copy `master` into my feature branch.](#i-want-to-copy-master-into-my-feature-branch) -* [I want to revert the merge of my feature branch into `master`.](#i-want-to-revert-the-merge-of-my-feature-branch-into-master) -* [I want to extract changes that I accidentally made to `master`.](#i-want-to-extract-changes-that-i-accidentally-made-to-master) -* [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) -* [I want to remove unpublished changes from my branch.](#i-want-to-remove-unpublished-changes-from-my-branch) -* [I want to see which branches have already been merged into `master`.](#i-want-to-see-which-branches-have-already-been-merged-into-master) -* [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) -* [I want to delete my feature branch.](#i-want-to-delete-my-feature-branch) -* [I want to delete a remote branch.](#i-want-to-delete-a-remote-branch) -* [I want to update `master` because my `push` was rejected.](#i-want-to-update-master-because-my-push-was-rejected) -* [I want to remove a file from my staging area.](#i-want-to-remove-a-file-from-my-staging-area) -* [I want to squash several commits into one (or more) commits.](#i-want-to-squash-several-commits-into-one-or-more-commits) -* [I want to squash several commits into one commit without using `rebase`.](#i-want-to-squash-several-commits-into-one-commit-without-using-rebase) -* [I want to temporarily set-aside my feature work.](#i-want-to-temporarily-set-aside-my-feature-work) -* [I want to keep my changes during conflict resolution.](#i-want-to-keep-my-changes-during-conflict-resolution) -* [I want to find the commit that deleted a file.](#i-want-to-find-the-commit-that-deleted-a-file) -* [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) +Rebase is just so much easier to work with and seems like such a much more logical concept: -## Use Cases +> Take a certain state of the repository and “replay” commits “on top” of that starting state, in the order the commits were made. No guessing, no hidden (merge) “magic”. -### I want to show the status of the current branch. +Rebasing causes far fewer merge-conflicts and if you follow some very simple rules - it will NEVER create a merge conflict -The `status` command shows differences between the working tree, the index, and `head` commit. +- Never work directly on the main branch of development. +- Always work in a branch, for everything, all the time. +- Only one person ever works on a single (feature/bugfix/hotfix) branch. +- Keep branches as short-lived as possible. +- Keep your branches in-sync with the main branch. -```sh -git status +**You will NOT EVER regret making the change!** + + + + +## Git Settings I like +Keep in mind there are two types of config within Git. +There is Global and local (to the individual repository) settings. + +If you want a global setting to be overridden in a sepcifc repository - you can set a local version of that item - by omitting the `--global` portion of the command. + +You can overwrite the global config +`git config --global column.ui auto` + +with a local setting of: + +`git config column.ui plain` + +### PULL WITH REBASE +When using git pull do a rebase - instead of a merge + +`git config --global pull.rebase true` + +### REBASE AUTOSQUASH +Automatically organizes commits marked with squash! or fixup! into the correct order for squashing, saving you from manually editing the rebase's to-do list. + +`git config --global rebase.autosquash true` + +### BLAME +#### Blame - (just a little) +`git blame -L a,z:path/to/file` + +#### Just give me the details for the line a through z +`git blame -L 15,26:path/to/file` + +Aside: you can also use git log with the `-L` switch and get the results in commit order. + +#### Ignore Whitespace +`git blame -w` + +#### Detect Code movement +##### Detect lines moved or copied in the same commit - and ignore them +(If you move an entire function block - ignore it as a change of that author.) + +`git blame -C` + +##### Detect lines moved or copied in the same commit or the commit that created the file - and ignore them +git blame -C -C + +##### Detect lines moved or copied in the same commit or the commit that created the file or any commit at all - and ignore them +This will ensure that if a filename was changed by “Bob” - that Bob isn't actually blamed for the lines within it. +He didn't write the code - he just renamed the file - so don’t blame Bob - if he didn't actually change a line of code. + +Note the more “nested” your command options (we have 3 “-C”s in this example - the more expensive (in time) an operation is. But since you don’t run git blame very often - you might as well get the very best results you can from it and run the three-C’s version. + +`git blame -C -C -C` + +##### Create an alias for this awesomeness +`git config --global alias.blaame "blame -w -C -C -C"` + +### STATUS +Produces a succinct version of git status but with all the branch name, untracked files and ignored files shown. + +`git config --global alias.sstatus "status -s -b --ignored=traditional"` + +### STASH All local Changes included untracked (new) files +Ensures that ALL files are included when you stash your local working copy. +(It doesn't work on Windows machines with NodeJS applications - because the filepath is too long to work properly.) + +`git config --global alias.staash "stash --all"` + +### PUSH +If you’re using `git push --force` you will overwrite anyone else changes - if you were NOT the last commit on the branch. Effectively losing someone else’s changes. + +Using `--force-with-lease` will stop you from FORCE pushing your changes, if you weren’t the last one. +Be nice. Use the the `--force-with-lease` option. + +`git config --global alias.puush "push --force-with-lease"` + +### RERERE - REuse REcorded REsolution +If your rebasing and see the same kind of conflicts over and over - turning on this option will allow git to use your previous solution to solve this one! + +`git config --global rerere.enabled true` + +### COLUMNS +This will put the output of git commands into columns. +Might confuse you at first - if you're expecting one thing ONLY per line. +But I like how it presents things in columns. + +Of course - if you don’t like it - don’t use it this way! + +`git config --global column.ui auto` + +### SORT BY DATE +When listing branches - show them in the order they were last committed to. +Newest on top. + +`git config --global branch.sort -committerdate` + +### AUTOCORRECT MY SHITTY TYPING +Run the most likely command, don't just tell me what you think I meant. +`git chexout` - will give an error message that there is no such command. + +It will also suggest the "similar" command of "checkout". + +Instead of JUST telling me what you think I mean - Actually go ahead and use CHECKOUT - so I don't have to type it out again, myself - despite the fact that git knows what I meant! + +The number is how many milliseconds you have to cancel git from doing it automatically. +In this example, I have it set for 2.5 seconds (25 ms) + +`git config --global help.autocorrect 25` + +### IGNORE FILE PERMISSION CHANGES +If the files permissions are changed on a file or directory in the working copy : ignore those changes from a git perspective. + +`git config --global core.fileMode` + +### DEFAULT EDITOR +Set the default editor to use with git. + +If you try and do a commit without writing a message in the command, or you doing a +`git rebase -i` operation - load THIS editor so that I can edit the commit metadata. + +I use NeoVim (neovim as my editor). + +`git config core.editor nvim` + +### COMMIT TEMPLATE +Create a template to display for commit messages. +( +I have included mine so you can see it +It is saved at `\users\gavin\.gitmessage` and is added to the global config with; + +`git config --global commit.template C:/users/gavin/.gitmessage` +) + +``` +# Always include the helpdesk ticket number for this task. +# +# Doing so will ensure that the helpdesk system can track git commits that +# belong to a specific helpdesk task. +# +# In the example below we're using a JIRA project with a "ADP" prefix. +# +# Keep the FIRST line brief. Try for less than 50 characters. +# +# ALL LINES you see here with a "#" are ignored by git +# +# ============================================================================= +# ADP-XXX This is a brief description of this change +# +# More detailed explanation if needed. +# This can be as many lines as you require to ensure that any nuanced +# information that is required, is included in the commit message. +``` + +### 3 WAY DIFF FOR MERGE CONFLICTS +Shows "your" change and "their" change, like normal, but ALSO includes what it was before. +It helps "me" when deciding which one is the correct side of the merge-conflict to accept. + +Be careful! Make sure remember that you have configured the diff process with this setting. + +It can be pretty confusing, if you’re not used to it and only expecting a straight default diff. + +`git config --global merge.conflictstyle diff3` + +### Line Endings (Cross-Platform Safe Configuration) +#### TL;DR (What actually matters) +The repository always stores files with `LF` (\n) line endings +Git automatically converts line endings on checkout where needed +File-specific rules are enforced via `.gitattributes` (not user config) + +#### 1. One-Time Repo Setup (Required) +Create a `.gitattributes` file in the root of the repository: + +``` +# Default: normalize all text files to LF in the repo +* text=auto eol=lf + +# Windows-specific files should use CRLF when checked out +*.sln text eol=crlf +*.bat text eol=crlf +*.cmd text eol=crlf +*.ps1 text eol=crlf + +# Scripts and Unix-based files must always use LF +*.sh text eol=lf +*.bash text eol=lf +*.yml text eol=lf +*.yaml text eol=lf +Dockerfile text eol=lf + +# Binary files (never modified by Git) +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.pdf binary +``` + +**Why this works** +`LF` is the canonical format in the repo +Developers on Windows still get CRLF only where appropriate +Shell scripts and Docker-related files are guaranteed to work everywhere + +#### 2. Developer Machine Configuration (Minimal + Safe) +##### Windows +`git config --global core.autocrlf true` +##### macOS / Linux +`git config --global core.autocrlf input` + +**Important Note** +These settings are now secondary safeguards. +The .gitattributes file overrides and enforces correctness. + +#### 3. Normalize an Existing Repository (One-Time Fix) +If the repo already has mixed line endings, normalize it: + +``` +git add --renormalize . +git commit -m "Normalize line endings" +``` + +#### 4. Why .gitattributes is Mandatory (Not Optional) +Relying only on `core.autocrlf` leads to: + +- Inconsistent commits across developers +- Broken shell scripts in Docker/Linux environments +- No enforcement in CI/CD pipelines +- Issues when using IDEs or tools that ignore Git settings + +`.gitattributes` solves all of these by making line-ending behavior deterministic and version-controlled. + +#### 5. Optional: EditorConfig (Extra Layer of Safety) +This ensures IDEs behave consistently even before Git gets involved. + +Add `.editorconfig`: +``` +root = true + +[*] +end_of_line = lf +insert_final_newline = true + +[*.bat] +end_of_line = crlf + +[*.sln] +end_of_line = crlf +``` + +### USE COMPRESSION +`git config --global core.compression 9` + +### SPEED UP GIT +The following commands are on per-repository basis - they are not GLOBAL settings. + +To Perform maintenance on your local working copy + +#### Update indexes/etc +`git maintenance start` + +#### Keep a cache of untracked files +`git config core.untrackedcache true` + +#### Monitor files that have changed locally +`git config core.fsmonitor true` + +### CONDITIONAL GIT CONFIG OPTIONS +A story will help best explain this; +- You have two repositories: one for work - one is an open-source project. +- You have different email addresses for these two repositories. +- You can automatically have git use the right email address in each repo. +- You might also need GPG signing in your OSS repo, that you don’t want in your work one. + +You can tell git which config file to use for a certain repository, +by adding the following code to your you global git config file: +Windows: `C:\Users\\.gitconfig` +Linux: `~/.gitconfig` + +``` +[includeIf "gitdir:~/projects/work/"] + path = ~/projects/work/.gitconfig + +[includeIf "gitdir:~/projects/oss/"] + path = ~/projects/oss/.gitconfig ``` -### I want to create a new branch that is based on the current branch. +### All my faves together (for easier copy/paste) + +``` +git config --global pull.rebase true +git config --global rebase.autosquash true +git config --global alias.blaame "blame -w -C -C -C" +git config --global alias.puush "push --force-with-lease" +git config --global alias.staash "stash --all" +git config --global rerere.enabled true +#Could be annoying at first - use `plain` instead of `auto` +#git config --global column.ui auto +git config --global branch.sort -committerdate +git config --global help.autocorrect 25 +git config --global core.fileMode false +#Insert YOUR favourite text editor here +git config --global core.editor nvim +git config --global core.compression 9 +git config --global merge.conflictstyle diff3 +``` + +## Fixing weird git happenings +### git stash pop deletes files? +I stashed some files using my alias git staash- I then proceeded to do some work in various other branches and merged some of those changes on my branches to develop. + +I also pulled from origin at various times along the way to keep my developbranch up to date with work work that was happening elsewhere. + +Now, I want to return to an existing feature branch and pop the stash I previously created. However the stash popcauses a merge conflict and for reasons known only to git, the new files from my stash (they are not edits to existing files - but entirely new ones that were crested prior to being stashed) are considered “deleted”. While I am sure that git has a perfectly good reason for this - it makes no sense to me at all. + +I tried restoring the file - by using: `git checkout --ours -- public/tests/runner.cfm` but that didn’t work. +Git complained : `error: path 'public/tests/runner.cfm' does not have our version` + +Which didn’t makes sense until I realised that it doesn’t exist in my repository, yet. +It’s a new/unknown file to my repository. +Anyway… to fix the issue of stash pop deleting your newly added file use the following, adjusting the stash “number” - to the stash you’re dealing with. -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: +`git restore --source="stash@{0}" --staged --worktree path/to/deletedfile` -```sh +The switches weren’t so obvious to me at first but they do make sense after a quick RTFM: +`--staged` and `--worktree` switches denote where the file is to be restored to. + +- `--staged` because it is staged as a deleted file in the merge-conflict and we want the file to not be deleted, and +- `--worktree` because we want it back in our working tree, too. + +## How do I do XXX with git? +### Show the status of the current branch. +The `git status` command shows differences between the working tree, the index, and head commit. + +### Create a new branch that is based on the current branch. +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. + +In the below example, create a new feature branch, using the master branch as the source. + +``` git checkout master -# Creates a new branch, `my-feature`, based on `master`. +# Create a new branch, `my-feature`, based on `master`. git checkout -b my-feature + +# you can use the SWITCH command, if you prefer +# with the "-c" (create) switch. +git switch -c my-feature ``` -### I want to checkout the previous branch that I was on. - -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: +### Checkout the previous branch that I was on. +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: -```sh +``` git checkout master git checkout my-feature - # At this point, the `-` refers to the `master` branch. git checkout - +# (you are now on "master") # At this point, the `-` refers to the `my-feature` branch. git checkout - + +# (you are now back on the "my-feature" branch) ``` The `-` token can also be used to merge-in the last branch that you had checked-out: -```sh +``` git checkout my-feature # ... changes to the working tree (your file system). git add . git commit -m "Awesome updates." git checkout master - # At this point, the `-` refers to the `my-feature` branch. git merge - ``` -The `-` token can also be used to `cherry-pick` the most recent commit of the last branch that you had checked-out: +The `-` token can also be used to cherry-pick the most recent commit of the last branch that you had checked-out: -```sh +``` git checkout my-feature # ... changes to the working tree (your file system). git add . git commit -m "Minor tweaks." - git checkout master - # At this point, the `-` refers to the `my-feature` branch. git cherry-pick - ``` -### I want to list the files that have been modified in the current working tree. +### List the files that have been modified in the current working tree. +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: -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: +`git diff --stat` -```sh -git diff --stat +### View the changes that were made in a given commit. +When show is given a branch name, it will default to head (the last or most-recent commit on the given branch): ``` - -### I want to view the changes that were made in a given commit. - -When `show` is given a branch name, it will default to `head` - the last or most-recent commit on the given branch: - -```sh git checkout master - -# Outputs the changes made to the `head` commit of the current (`master`) -# branch. +# Outputs the changes made to the `head` commit of the current (`master`) branch. git show # Outputs the changes made to the `head` commit of the `my-feature` branch. git show my-feature ``` -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 `~`: +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 `~`: -```sh +``` # Outputs the changes made in the commit with the given hash. git show 19e771 +# Outputs the changes made in in a previous commit of the current (`master`) branch. -# Outputs the changes made in in a previous commit of the current (`master`) -# branch. git show head~ # Show changes in first parent. -git show head~~ # Show changes in first parent's first parent. -git show head~~~ # Show changes in first parent's first parent's first parent. +git show head~~ # Show changes in first parent's, first parent. +git show head~~~ # Show changes in first parent's, first parent's, first parent. # Outputs the changes made in a previous commit of the `my-feature` branch. git show my-feature~ @@ -146,24 +451,22 @@ git show my-feature~~ git show my-feature~~~ ``` -### I want to list the files that were changed in a given commit. - -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: +### List the files that were changed in a given commit. +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: -```sh +``` # Outputs the list of files changed in the commit with the given hash. git show 19e771 --stat +git show my-feature~~~ --stat ``` -### I want to view the changes that were made across multiple commits. - -While the `show` command can show you changes in a given commit, you can use the `diff` command to show changes across multiple commits: +### View the changes that were made across multiple commits. +While the show command can show you changes in a given commit, you can use the `diff` command to show changes across multiple commits: -```sh +``` git checkout master - # Outputs the changes between `head~` and `head` of the current branch. If -# only one commit is provided, other commit is assumed to be `head`. +# only one commit is provided, other commit is assumed to be `head`. git diff head~ # Outputs the changes between the first commit and the second commit. @@ -172,22 +475,20 @@ git diff head~~~..head~~ 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: -```sh +``` git checkout my-feature - # At this point, the following are equivalent and output the changes between -# the `head` commit of the `master` branch and the `head` commit of the -# `my-feature` branch. +# the `head` commit of the `master` branch and the `head` commit of the +# `my-feature` branch. git diff master git diff master..head git diff master..my-feature ``` -### I want to view the changes that were made in a given file. - -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: +### View the changes that were made in a given file. +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: -```sh +``` # Outputs the changes made to the `README.md` file in the `head` commit of the # `my-feature` branch. git show my-feature -- README.md @@ -196,130 +497,125 @@ git show my-feature -- README.md git show 19e771 -- README.md ``` -### I want to view the contents of a file in a given commit. - +### View the contents of a file in a given commit. 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: -```sh +``` # Outputs the contents of the `README.md` file as defined in the `head` commit -# of the `my-feature` branch. +# of the `my-feature` branch. git show my-feature:README.md # Outputs the contents of the `README.md` file as defined in the `19e771` -# commit. +# commit. git show 19e771:README.md ``` -### I want to open the contents of a file in a given commit in my editor. - +### Open the contents of a file in a given commit in my editor. 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: -```sh +``` # Opens the `README.md` file from the `head` commit of the `my-feature` branch -# in the Sublime Text editor. +# in the Sublime Text editor. git show my-feature:README.md | subl # Opens the `README.md` file from the `19e771` commit in the `less` viewer. git show 19e771:README.md | less ``` -### I want to copy a file from a given commit into my current working tree. - +### Copy a file from a given commit into my current working tree. 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: -```sh +``` git checkout my-feature - # While staying on the `my-feature` branch, copy the `README.md` file from -# the `master` branch into the current working tree. This will overwrite the -# current version of `README.md` in your working tree. +# the `master` branch into the current working tree. +# NOTE: This will overwrite the current version of `README.md` in your working tree. git checkout master -- README.md ``` -### I want to copy the last commit from another branch into my branch. - -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: +### Copy the last commit from another branch into my branch. +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: -```sh +``` git checkout master - -# Copy the `head` commit-changes of the `my-feature` branch onto the `master` -# branch. This will create a new `head` commit on `master`. +# Copy the `head` commit-changes of the `my-feature` branch onto the `master`branch. +# This will create a new `head` commit on `master`. git cherry-pick my-feature ``` -### I want to copy an earlier commit from the current branch to the `head`. +### Copy an earlier commit from the current branch to the head. +Sometimes, after you understand why reverted code was breaking, you want to bring the reverted code back into play and then fix it. -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: +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: -```sh +``` git checkout master - # Assuming that `head~~~` and `19e771` are the same commit, the following are -# equivalent and will copy the changes in `19e771` to the `head` of the -# current branch (as a new commit). +# equivalent and will copy the changes in `19e771` to the `head` of the +# current branch (as a new commit). git cherry-pick head~~~ git cherry-pick 19e771 ``` -### I want to update the files in the current commit. - +### Update the files in the current commit. 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. -```sh +``` git commit -m "Woot, finally finished!" # Oops, you forgot a change. Edit the file and stage it. -# ... changes to the working tree (your file system). git add oops.txt # Adds the currently-staged changes (oops.txt) to the current commit, giving -# you a chance to update the commit message. +# you a chance to update the commit message. git commit --amend ``` -### I want to edit the current commit message. - +### Edit the current commit message. In addition to adding files to the current commit, the `--amend` modifier can also be used to change the current commit message: -```sh +``` git add . git commit -m "This is greet." -# Oh noes! You misspelled "great". You can edit the current commit message: +# Oh no! You misspelled "great". +# Don't panic! You can edit the current commit message: git commit --amend -m "This is great." ``` 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. -### I want to copy `master` into my feature branch. +### Copy master into my feature branch. +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, Frankenstein-esque commit tree. -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: +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: -```sh +``` git checkout my-feature - # This will unwind the commits specific to the `my-feature` branch, pull in -# the missing `master` commits, and then replay your `my-feature` commits. +# the missing `master` commits, and then replay your `my-feature` commits. git rebase master ``` -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`: +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: -```sh +``` git checkout my-feature git rebase master - # Fast-forward merge of `my-feature` changes into `master`, which means there -# is no creation of a "merge commit" - your `my-features` changes are simply -# added to the top of `master`. +# is no creation of a "merge commit" - your `my-features` changes are simply +# added to the top of `master`. git checkout master git merge --ff-only my-feature ``` -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"): +That said, when you're working on a team where everyone potentially 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"): -```sh +``` # Get the `my-feature` branch ready for merge. git checkout my-feature git rebase master @@ -329,54 +625,69 @@ git checkout master git merge --no-ff my-feature ``` -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. +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, cleanly in one go. -### I want to revert the merge of my feature branch into `master`. +### Revert the merge of my feature branch into master. +If you performed a `--ff-only` merge of your feature branch into master, there's no "easy" solution. -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. +You either have to reset the branch to an earlier commit (rewriting history); or, you have to revert the individual commits in the merge. 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: -```sh +``` git checkout master - # Merge the feature branch in, creating a "merge commit". git merge --no-ff my-feature -# On noes! You didn't mean to merge that in. Assuming that the "merge commit" -# is now the `head` of `master`, you can revert back to the commit's fist -# parent, the `master` branch: -m 1. And, since `head` and `master` are the -# same commit, the following are equivalent: +# On no! You didn't mean to merge that in. Assuming that the "merge commit" +# is now the `head` of `master`, you can revert back to the commit's first +# parent, the `master` branch: -m 1 (that is a numeral, one). +# And, since `head` and `master` are the same commit, the following are equivalent: git revert -m 1 head git revert -m 1 master ``` -### I want to extract changes that I accidentally made to `master`. +### Extract changes that I accidentally made to master. +Sometimes, after you've finished working on your feature branch, you execute `git checkout master`, only to find that you've been accidentally working on master the whole time (`error: "Already on 'master'"`). -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: +To fix this, you can checkout a new branch and reset your master branch: -```sh +``` git checkout master # > error: Already on 'master' - # While on the `master` branch, create the `my-feature` branch as a copy of -# the `master` branch. This way, your `my-feature` branch will contain all of # your recent changes. +# the `master` branch. This way, your `my-feature` branch will contain all +# of your recent changes. git checkout -b my-feature # Now that your changes are safely isolated, get back into your `master` -# branch and `reset` it with the `--hard` modifier so that your local index -# and file system will match the remote copy. +# branch and `reset` it with the `--hard` modifier so that your local index +# and file system will match the remote copy. git checkout master git reset --hard origin/master ``` -### I want to undo the changes that I've made to my branch. +### Undo the changes that I've made to my branch. +Recently git added support for the REVERT command + +`git revert ` -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`). +This will revert a previous commit in your working directory. +You can do this as many times as you need -**Caution**: You will lose data when using the `--hard` option. +You can also specify a range of commits with; -```sh +``` +git revert -n OLDER_COMMIT^..NEWER_COMMIT +git commit -m "revert OLDER_COMMIT to NEWER_COMMIT" +``` + +OR: old school… +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). + +Caution: You will lose data when using the `--hard` option. + +``` git checkout my-feature # ... changes to the working tree (your file system). git add . @@ -385,140 +696,158 @@ git add . git reset --hard ``` -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. +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. -### I want to remove unpublished changes from my branch. +### Remove unpublished changes from my branch. +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: -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: - -```sh +``` git checkout my-feature - # Update the remote copy of the `my-feature` branch in order to make sure that -# you are working with the most up-to-date remote content. +# you are working with the most up-to-date remote content. git fetch origin my-feature # Now, reset the local copy of `my-feature` to match the published copy. This -# will update your index and your local file system to match the published -# version of `my-feature`. +# will update your index and your local file system to match the published +# version of `my-feature`. git reset --hard origin/my-feature ``` -### I want to see which branches have already been merged into `master`. +### Undo my last LOCAL commit and leave my changes in staging. +`git reset --soft HEAD~1` + +### Undo my last LOCAL commit and unstage my changes. +`git reset HEAD~1` + +### Undo my last LOCAL commit and throw away my changes. +`git reset --hard HEAD~1` +### See which branches have already been merged into master. From any branch, you can locate the merged-in branches (that can be safely deleted) by using the `--merged` modifier: -```sh +``` git checkout master - # List all of the local branches that have been merged into `master`. This -# command will be relative to the branch that you have checked-out. +# command will be relative to the branch that you have checked-out. git branch --merged ``` -### I want to see which branches have not yet been merged into `master`. - +### See which branches have not yet been merged into master. From any branch, you can locate the unmerged branches by using the `--no-merged` modifier: -```sh +``` git checkout master - # List all of the local branches that have NOT YET been merged into `master`. -# This command will be relative the branch you have checked-out. +# This command will be relative to the branch you have checked-out. git branch --no-merged ``` -### I want to delete my feature branch. +### See a list of branches that have been merged to main but not master +(Effectively a merge-diff between two branches) -After you're merged your feature branch into `master`, you can delete your feature branch using the `branch` command: +``` +# Update first! +git fetch --all --prune -```sh +comm -23 <(git branch --merged main | grep -vE '^\*|main|master' | sort) \ + <(git branch --merged master | grep -vE '^\*|main|master' | sort) +``` + +### Delete my feature branch. +After you're merged your feature branch into master, you can delete your feature branch using the `branch` command: + +``` # Merge your `my-feature` branch into `master` creating a "merge commit." git checkout master git merge --no-ff my-feature # Safely delete the merged-in `my-feature` branch. The `-d` modifier will -# error-out if the given branch has not yet been merged into the current -# branch. +# error-out if the given branch has not yet been merged into the current +# branch. git branch -d my-feature ``` -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`: +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: -```sh +``` git checkout master - # Force-delete the `my-feature` branch even though it has not been merged -# into the `master` branch. +# into the `master` branch. git branch -D my-feature ``` -### I want to delete a remote branch. +### Delete a remote branch. +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: -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: - -```sh +``` git checkout master - # Safely delete the local copy of your `my-feature` branch. The `-d` modifier -# will error-out if the given branch has not been fully-merged into `master`. +# will error-out if the given branch has not been fully-merged into `master`. git branch -d my-feature # Delete the remote copy of the `my-feature` branch from the origin. The `:` -# prefix sends this through as a "delete" command for the given branch. +# prefix sends this through as a "delete" command for the given branch. git push origin :my-feature ``` -### I want to update `master` because my `push` was rejected. +### Update master because my push was rejected. +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". -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: +To fix this, you can use the `--rebase` modifier: -```sh +``` git checkout master git merge --no-ff my-feature - # Oh noes! You forgot to pull in the latest remote copy of `master` before you -# merged your `my-feature` commits. No problem, just `--rebase` your local -# `master` on the remote branch. This will move your local changes to the tip -# of the `master` branch. -git pull --rebase +# merged your `my-feature` commits. No problem, just `--rebase` your local +# `master` on the remote branch. This will move your local changes to the tip +# of the `master` branch. +git pull --rebase # Now that you've pulled-in the remote changes, you should be able to push -# your updated `master` branch. +# your updated `master` branch. git push origin master ``` -### I want to remove a file from my staging area. - +### Remove a file from my staging area. 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: -```sh +``` git add . -# Oh noes! You didn't mean to add all of the files to the staging area. You -# can remove some of the staged files using the `--cached` modifier: +# Oh no! You didn't mean to add all of the files to the staging area. You +# can remove some of the staged files using the `--cached` modifier: git rm --cached secrets.config ``` -If you accidentally added an entire directory to the staging area, you can add the `-r` modifier to recursively apply the `rm` command: +If you accidentally added an entire directory to the staging area, you can add the `-r` modifier to recursively apply the rm command: -```sh +``` git add . -# Oh noes! You didn't mean to add the ./config directory. You can recursively -# remove it with the `-r` modifier: +# Oh no! You didn't mean to add the ./config directory. You can recursively +# remove it with the `-r` modifier: git rm --cached -r config/. ``` -When you `rm` files using `--cached`, they will remain in your working tree and will become "unstaged" changes. +When you rm files using `--cached`, they will remain in your working tree and will become "unstaged" changes. + +### Squash several commits into one (or more) commits. +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. -### I want to squash several commits into one (or more) commits. +As such, you will often need to rewrite your feature branch's history before merging it into master. -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". +This allows you to get rid of intermediary commit messages like, "still working on it." and "Meh, missed a bug.". -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`. +To do this, you can perform an "interactive rebase". -```sh +The "interactive rebase" gives you an opportunity to indicate how intermediary commits should be rearranged. Some commits can be "squashed" (combined) together. Others can be omitted (removed). 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. + +``` # Create the `my-feature` branch based on `master`. git checkout master git checkout -b my-feature @@ -538,22 +867,22 @@ git commit -m "Uggggg! Why is this so hard?" # ... changes to the working tree (your file system). git add . git commit -m "Woot, finally got this working." - -# At this point, your commit history is sloppy and would bring much shame on -# your family if it ended-up in `master`. As such, you need to squash the -# commits down into a single commit using an interactive rebase. Here, you're -# telling `git` to use the `master` commit as the starting point: -git rebase -i master ``` -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): +At this point, your commit history is sloppy and would bring much shame on +your family if it ended-up in `master`. As such, you need to squash the +commits down into a single commit using an interactive rebase. Here, you're +telling `git` to use the `master` commit as the starting point: -```sh +`git rebase -i master` + +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): + +``` pick 27fb3d2 Getting close. pick e8214df Missed a bug. pick ce5ed14 Uggggg! Why is this so hard? pick f7ee6ab Woot, finally got this working. - # Rebase b0fced..f7ee6ab onto b0fced (4 commands) # # Commands: @@ -566,186 +895,194 @@ pick f7ee6ab Woot, finally got this working. # d, drop = remove commit ``` -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): +At this point, you can identify the later commits as needing to be squashed (s) down into the oldest commit (the one you are picking): -```sh +``` pick 27fb3d2 Getting close. s e8214df Missed a bug. s ce5ed14 Uggggg! Why is this so hard? s f7ee6ab Woot, finally got this working. ``` -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. - -### I want to squash several commits into one commit without using `rebase`. +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. -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. +### Squash several commits into one commit without using rebase. +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. -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. +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. Assuming your `my-feature` branch needs to be "fixed", you can use the following workflow: -```sh +``` # Assuming that the `my-feature` branch is the branch that needs to be fixed, -# start off my renaming the branch as a backup (the `-m` modifier performs a -# rename): +# start off my renaming the branch as a backup (the `-m` modifier performs a rename): git checkout my-feature git branch -m my-feature-backup # Now, checkout the `master` branch and use it to create a new, clean -# `my-feature` branch starting point. +# `my-feature` branch starting point. git checkout master git checkout -b my-feature # At this point, your `my-feature` branch and your `master` branch should be -# identical. Now, you can "squash merge" your old feature branch into the new -# and clean `my-feature` branch: +# identical. Now, you can "squash merge" your old feature branch into the new +# and clean `my-feature` branch: git merge --squash my-feature-backup # All the file-changes should now be in the `my-feature` branch as staged -# edits ready to be committed. +# edits ready to be committed. git commit -m "This feature is done and awesome." # Delete the old backup branch as it is no longer needed. You will have to -# force delete (`-D`) it since it was never merged into `master`. +# force delete (`-D`) it since it was never merged into `master`. git branch -D my-feature-backup ``` -> **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. +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. + +### See the difference between my current branch and main at the point I created the branch +I want to see the difference between `myBranch` and `master` - ensuring that the version of master that is being used for the comparison is the version that myBranch was created from and not the current version of master. +`git diff master...HEAD` + +### Temporarily set-aside my feature work. +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. -### I want to temporarily set-aside my feature work. +In such a case, it is tempting to use git stash and git stash pop to store pending changes. -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: +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: -```sh -# Oh noes! Something urgent just came up - commit your changes to your feature -# branch and then go attend to the more pressing work. +``` +# Oh no! Something urgent just came up - commit your changes to your feature +# branch and then go attend to the more pressing work. git add . git commit -m "Saving current work - jumping to more urgent matters." - git checkout master ``` Now, you never have to remember where those pending changes are. This guarantees that you won't lose your work. -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: +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 then commit your pending changes to that new branch: -```sh -# Oh noes! Something urgent just came up - checkout a new branch. This will -# move all of your current work (staged and unstaged) over to the new branch. +``` +# Oh no! Something urgent just came up - checkout a new branch. This will +# move all of your current work (staged and unstaged) over to the new branch. git checkout -b temp-work # Commit any unstaged changes. git add . git commit -m "Saving current work - jumping to more urgent matters." - git checkout master ``` -Now, your `master` branch should be back in a pristine state and your `temp-work` branch can be continued later. +Now, your master branch should be back in a pristine state and your temp-work branch can be continued later. -### I want to keep my changes during conflict resolution. +### Find the commit that changed a line of code +To find the commit that was reponsible for a line of code you can use git log and the `-L` switch. +The below example shows you how to trace the evolution of lines starting at line 100, until line100+15 more lines in the file `src/cfmapping/gsn/model/customerModel.cfc` + +If you want a single line only use the same value in the range `-L 100:100` +`git log -L 100,+15:src/cfmapping/gsn/model/customerModel.cfc` + +### Keep my changes during conflict resolution. 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. -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. +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. -In a `merge` conflict, `--theirs` indicates the branch being merged into the current context: +In a merge conflict, `--theirs` indicates the branch being merged into the current context: +(It doesn’t mean someone else’s) -```sh +``` git checkout master git merge --no-ff my-feature -# Oh noes! There is a conflict in "code.js". To keep your version of the -# code.js file, you can check-it-out using --theirs and the file path: +# Oh no! There is a conflict in "code.js". To keep your version of the +# code.js file, you can check-it-out using --theirs and the file path: git checkout --theirs code.js git add . git merge --continue ``` -Similarly, in a `cherry-pick` conflict, `--theirs` indicates the branch being cherry-picked into the current context: +Similarly, in a cherry-pick conflict, `--theirs` indicates the branch being cherry-picked into the current context: -```sh +``` git checkout master git cherry-pick --no-ff my-feature -# Oh noes! There is a conflict in "code.js". To keep your version of the -# code.js file, you can check-it-out using --theirs and the file path: +# Oh no! There is a conflict in "code.js". To keep your version of the +# code.js file, you can check-it-out using --theirs and the file path: git checkout --theirs code.js git add . git merge --continue ``` -In a `rebase` conflict, `--theirs` indicates the branch that is being _replayed_ on top of the current context (**See Aside**): +In a rebase conflict, `--theirs` indicates the branch that is being replayed on top of the current context (See Aside): -```sh +``` git checkout my-feature git rebase master -# Oh noes! There is a conflict in "code.js". To keep your version of the -# code.js file, you can check-it-out using --theirs and the file path: +# Oh no! There is a conflict in "code.js". To keep your version of the +# code.js file, you can check-it-out using --theirs and the file path: git checkout --theirs code.js git add . git rebase --continue ``` -> **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: -> -> * Check-out an earlier, common commit between your feature branch and the target branch. -> * Cherry-pick your feature branch commits onto the earlier commit. -> * Replace your feature branch with this temporary branch -> -> With this mental model, "your" version - targeted using `--theirs` - is the version being cherry-picked into the "current context" (the temporary branch). +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: + +Check-out an earlier, common commit between your feature branch and the target branch. Then, cherry-pick your feature branch commits onto the earlier commit. +Then, replace your feature branch with this temporary branch -### I want to find the commit that deleted a file. +With this mental model, "your" version - targeted using `--theirs` - is the version being cherry-picked into the "current context" (the temporary branch). +### Find the commit that deleted a file. 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: -```sh +``` # Find the commit that deleted the file "projects.js". git log --diff-filter=D --name-only -- wwwroot/app/projects.js ``` The `--name-only` option includes statistics about the commit (which files were changed); but, limits the file meta-data to include file paths only. -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: +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: -```sh +``` # Find the commit that deleted the file "projects.js" that resided somewhere -# in the "app" directory. The double-asterisk matches across directories. +# in the "app" directory. The double-asterisk matches across directories. git log --diff-filter=D --name-only -- "wwwroot/app**/projects.js" # You can even use pattern matching on the file name itself if you can't -# remember exactly what it was named. +# remember exactly what it was named. git log --diff-filter=D --name-only -- "wwwroot/**/*project*.js" ``` 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: -```sh +``` # Find the commit that deleted the file "projects.js", but only show a one-line -# commit message and the list of files. +# commit message and the list of files. git log --diff-filter=D --name-only --oneline -- "wwwroot/app**/projects.js" # To get slightly easier-to-read output, you can use the `--pretty` option with -# some specialized formatting (instead of the `--oneline` option). This -# includes the abbreviated hash, the author, the relative date, and the -# subject line. And, includes some human-friendly line-breaks. +# some specialized formatting (instead of the `--oneline` option). This +# includes the abbreviated hash, the author, the relative date, and the +# subject line. And, includes some human-friendly line-breaks. git log --diff-filter=D --name-only --pretty=format:"%Cgreen%h - %an, %ar : %s" -- "wwwroot/app**/projects.js" ``` -### I want to find the commit that deleted a file that contained a piece of code. - +### Find the commit that deleted a file that contained a piece of code. 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. You can use the `-S` option to match on a string literal: -```sh +``` # Find the commit that deleted a file that contained the code 'isProcessed'. git log -S 'isProcessed' --diff-filter=D --name-only # To limit the search to the "app" directory, you can include a file path after -# the final `--` delimiter. +# the final `--` delimiter. git log -S 'isProcessed' --diff-filter=D --name-only -- wwwroot/app/ # To make the search case-insensitive, include the -i option. @@ -754,9 +1091,9 @@ git log -S 'isprocessed' -i --diff-filter=D --name-only You can use the `-G` option to match on a Regular Expression pattern instead of a string literal: -```sh +``` # Find the commit that deleted a file that contained the code `isProcessed`, -# matching on strict word-boundaries. +# matching on strict word-boundaries. git log -G '\bisProcessed\b' --diff-filter=D --name-only # To make the search case-insensitive, include the `-i` option. @@ -765,26 +1102,139 @@ git log -G '\bisprocessed\b' -i --diff-filter=D --name-only 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: -```sh +``` # Find the commit that deleted a file that contained the code `isProcessed`, -# matching on strict word-boundaries. +# matching on strict word-boundaries. git log -G '\bisProcessed\b' --diff-filter=D --name-only --oneline # To get slightly easier-to-read output, you can use the `--pretty` option with -# some specialized formatting (instead of the `--oneline` option). This -# includes the abbreviated hash, the author, the relative date, and the -# subject line. And, includes some human-friendly line-breaks. +# some specialized formatting (instead of the `--oneline` option). This +# includes the abbreviated hash, the author, the relative date, and the +# subject line. And, includes some human-friendly line-breaks. git log -G '\bisProcessed\b' --diff-filter=D --name-only --pretty=format:"%Cgreen%h - %an, %ar : %s" ``` 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: -```sh +``` # Find the commit that deleted a file that contained the code `isProcessed`, -# matching on strict word-boundaries. +# matching on strict word-boundaries. git log -G '\bisProcessed\b' --diff-filter=D --name-only --oneline # The above `log` action listed commit `aef037`. Use `git show` to output the -# changes made in the given commit. +# changes made in the given commit. git show aef037 ``` + +### Reset the state of my repo to be the same as the remote repo +If your branch is completely broken and you need to reset the state of your local woroking copy to be the same as the remote repository in a "git-approved" way, use the following. +Beware: These are destructive and unrecoverable actions. + +``` +# get the lastest state of origin +git fetch origin +git checkout master +git reset --hard origin/master + +# delete untracked files and directories +git clean -d --force + +# repeat checkout/reset/clean for each broken branch +``` + +### Delete local tracking branches that don’t have a remote. +(For Windows use a git bash shell) + +`git switch main && git fetch -p && git branch -vv | awk '/: gone]/{print $1}' | xargs -r git branch -d` + +Branches that have been deleted on remote but were not merged will show a notification but not be deleted on local. If you want to delete those as well change `-d` to `-D` + +`git switch main && git fetch -p && git branch -vv | awk '/: gone]/{print $1}' | xargs git branch -D` + +### Export my working directory like svn export +`git checkout-index -a -f --prefix=..\destination\path\to\export\` + +Notes: +· The destination path must exist - already +- Include the trailing “\" to get the full repository. + +### Know the size of files in my repository + +``` +git rev-list --objects --all | + git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | + sed -n 's/^blob //p' | + sort --numeric-sort --key=2 | + cut -c 1-12,41- | + $(command -v gnumfmt || echo numfmt) --field=2 --to=iec-i --suffix=B --padding=7 --round=nearest +``` + +### Rename a branch locally and remotely + +``` +# Rename the local branch to the new name +git branch -m $old_name $new_name + +# Delete the old branch on remote +git push $remote --delete $old_name + +# Prevent git from using the old name when pushing in the next step. +# Otherwise, git will use the old upstream name instead of $new_name. +git branch --unset-upstream $new_name + +# Push the new branch to remote +git push $remote $new_name + +# Reset the upstream branch for the new_name local branch +git push $remote -u $new_name +``` + +## Git Stash +You can use git stash to temporarily save changes you have made and “hide” them from your current branch, so that they don’t accidentally get committed to the upstream repository. + +Stashes are useful, because they don’t belong to any single branch. You could think of your stashes as a special/separate “scribble-pad” repository. +E.g. You can stash a change to myfile.c that you made in the develop branch, switch branches to the main branch and pop the change you made to myfile.c - as if you had edited the main branch’s version of that file. + +You can have as many stashes as you like. + +Git organises stashed by way of a first-in / first-out “stack” (FIFO). The LAST change you added (or “stashed”) is the first stash to get “popped” from the stack. + +When a specific stash is not mentioned - the latest addition to the stack is used as the default. + +The only drawback to using stashes - is that if you end up with more than a couple of them they can be difficult to manage and using `git stash pop` can end up causing merge conflicts, This is especially true if you your normal workflow sees you working in multiple branches at the same time. + +If you find yourself getting merge-conflicts often enough that is a problem for you, you’re better off creating a new local branch with a descriptive name and committing what you would have stashed. + +#### Get a list of stashes I have saved +`git stash list` + +#### Get a summary of the latest stash +`git stash show` + +#### Get a “patch” of the latest stash +`git stash show -p` + +#### Get a “patch” for a specific stash “n” +`git stash show -p stash@{n}` + +#### Get a patch for stash “5” from git stash list +`git stash show -p stash@{5}` + +#### Retrieve the latest stash into THIS branch +`git stash pop` + +#### Retrieve stash “5” into THIS branch +`git stash pop stash@{5}` + +#### Delete the latest addition to the stash queue +`git stash drop` + +#### Delete a specific stash from the stack +`git stash drop stash@{n}` + +#### Delete ALL stashes +(Warning: This command deletes all the stashes without any confirmation prompt or warning.) +`git stash clear` + +#### Restore a file that git stash popdeleted and marked as a merge-conflict +`git restore --source="stash@{0}" --staged --worktree path/to/deletedfile`